Stuck on Stage #NI6: the `cat` output is not the same as I get in my local device. I'm using Typescript

I’m stuck on Stage #NI6.

I’ve tried adding debuggers and simulating the test scenario. I’m pretty sure my parser is working fine. the problem seems to be the cat output, maybe my code to run external programs is not correct but then again, it works fine locally.

Here are my logs:

[compile] Moved ./.codecrafters/run.sh → ./your_program.sh
[compile] Compilation successful.
[tester::#NI6] Running tests for Stage #NI6 (Quoting - Single quotes)
[tester::#NI6] [setup] export PATH=/tmp/strawberry/pear/pear:$PATH
[tester::#NI6] Running ./your_program.sh
[tester::#NI6] [setup] echo -n "blueberry banana." > "/tmp/ant/f   15"
[tester::#NI6] [setup] echo -n "pineapple raspberry." > "/tmp/ant/f   40"
[tester::#NI6] [setup] echo "grape strawberry." > "/tmp/ant/f   83"
[your-program] $ echo 'hello shell'
[your-program] hello shell
[tester::#NI6] ✓ Received expected response
[your-program] $ echo shell     script
[your-program] shell script
[tester::#NI6] ✓ Received expected response
[your-program] $ echo 'world     example' 'script''hello' shell''test
[your-program] world     example scripthello shelltest
[tester::#NI6] ✓ Received expected response
[your-program] $ cat '/tmp/ant/f   15' '/tmp/ant/f   40' '/tmp/ant/f   83'
[your-program] 
[tester::#NI6] ^ Line does not match expected value.
[tester::#NI6] Expected: "blueberry banana.pineapple raspberry.grape strawberry."
[tester::#NI6] Received: "" (empty line)
[your-program] $ 
[tester::#NI6] Assertion failed.
[tester::#NI6] Test failed

And here’s a snippet of my code:

import fs from "fs";
import path from "path";
import { Interface as RLInterface, createInterface } from "readline";
import { spawnSync } from "child_process";

export const canExecute = (filePath: string) => {
  try {
    fs.accessSync(filePath, fs.constants.X_OK);
    return true;
  } catch (e) {
    return false;
  }
};

export const findExec = (exeName: string) => {
  const PATH = process.env.PATH?.split(path.delimiter) || [];

  for (const basePath of PATH) {
    const filePath = path.join(basePath, exeName);
    if (fs.existsSync(filePath) && canExecute(filePath)) {
      return filePath;
    }
  }

  return null;
};



type CommandHandler = (
  args: string[],
  rl: RLInterface,
  sd: SessionData
) => void;

const builtins = new Set();

const builtinHandlers: Record<string, CommandHandler> = {
  exit: (_, rl: RLInterface) => {
    rl.close();
  },
  echo: (args) => {
    console.log(args.join(" "));
  },
  type: (args) => {
    if (builtins.has(args[0])) {
      console.log(`${args[0]} is a shell builtin`);
    } else {
      const execPath = findExec(args[0]);

      if (execPath) {
        console.log(`${args[0]} is ${execPath}`);
      } else {
        console.log(`${args[0]}: not found`);
      }
    }
  },
  pwd: (_, __, sd: SessionData) => {
    console.log(sd.currentDirectory);
  },
  cd: (args, __, sd: SessionData) => {
    const homeDir = process.env.HOME || "/";

    const newPath =
      args[0] === "/"
        ? "/"
        : args[0].startsWith("~")
        ? path.resolve(sd.currentDirectory, args[0].replace("~", homeDir))
        : path.resolve(sd.currentDirectory, args[0]);

    if (fs.existsSync(newPath) && fs.statSync(newPath).isDirectory()) {
      sd.currentDirectory = newPath;
    } else {
      console.log(`cd: ${newPath}: No such file or directory`);
    }
  },
};

Object.keys(builtinHandlers).forEach((name) => {
  builtins.add(name);
});

export { builtins, builtinHandlers };


const rl = createInterface({
  input: process.stdin,
  output: process.stdout,
});

async function read() {
  const input = await new Promise<string>((resolve) => {
    rl.question("$ ", (command) => {
      resolve(command.trim());
    });
  });
  return input;
}

const runProgram = (command: string, args: string[]) => {
  debugger;

  const prog = spawnSync(command, args, { shell: true });
  if (prog.stdout) console.log(prog.stdout.toString());
  else if (prog.stderr) console.log(prog.stderr.toString());
};

export interface SessionData {
  currentDirectory: string;
}

const sessionData = {
  currentDirectory: process.cwd(),
};

const parseArgs = (input: string) => {
  const args: string[] = [];
  let current = "";
  let i = 0;
  let inSingle = false;

  while (i < input.length) {
    const ch = input[i];

    if (inSingle) {
      if (ch === "'") {
        // end of single-quoted section
        inSingle = false;
      } else {
        current += ch; // literal
      }
      i++;
      continue;
    }

    // Normal mode
    if (ch === "'") {
      inSingle = true;
      i++;
      continue;
    }

    if (/\s/.test(ch)) {
      if (current.length > 0) {
        args.push(current);
        current = "";
      }
      i++;
      continue;
    }

    current += ch;
    i++;
  }

  if (current.length > 0) {
    args.push(current);
  }

  return args;
};

async function evaluate(command: string) {
  const parts = parseArgs(command);
  const [name, ...args] = parts;

  if (!name) {
    return;
  }

  if (builtins.has(name)) {
    const handler = builtinHandlers[name];

    return handler(args, rl, sessionData);
  } else {
    const execPath = findExec(name);
    if (execPath) {
      runProgram(name, args);
    } else {
      console.log(`${command}: command not found`);
    }
  }
}

while (true) {
  const command = await read();

  if (command === "exit") {
    rl.close();
    break;
  }
  await evaluate(command);
}

Hey @alino20, could you upload your code to GitHub and share the link? It will be much easier to debug if I can run it directly.

I have uploaded the code to my github. Thank you.

Hey @alino20, looks like the issue was caused by the { shell: true } option and some unnecessary trailing spaces:

Let me know if that clears it up!

1 Like

That fixed it, thank you!

I think the issue is that I was using windows as the development environment, and it wouldn’t work without the shell:true option. maybe there is a solution for that but I don’t know at the moment.

1 Like

Yes, newline behavior can vary a bit across operating systems. You might need to normalize line endings to ensure consistency across environments.

Alternatively, using WSL can help align your environment more closely with Unix-style behavior.

1 Like

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.