Stuck on #MG5 (problem finding executables). Language JS/TS

Ok, so, i’m stuck on the Stage #MG5 where we hae to find executables in the $PATH.

Problem is, everything works on my machine, on Windows and Linux, but it doesn’t work in the codecrafters test environment.

I tested almost everything i could,i logged to see if there was problem with me loading the commands and nothing appeared off to me, maybe it comes from the accessSync or anything, i don’t know.

If someone could help me thank you.

My code:

Main.ts

import readline from "readline";

import fs from "fs";

import { Engine } from "./engine.ts";

import { Arg } from "./args/arg.ts";

import { ExitCommand } from "./commands/ExitCommand.ts";

import { EchoCommand } from "./commands/EchoCommand.ts";

import { TypeCommand } from "./commands/TypeCommand.ts";

import { NotFoundCommand } from "./commands/NotFoundCommand.ts";

import { UserCommand } from "./commands/UserCommand.ts";




const rl = readline.createInterface({

  input: process.stdin,

  output: process.stdout,

});




const engine = new Engine();




engine.registerCommand(new ExitCommand(() => rl.close()));

engine.registerCommand(new EchoCommand(engine, (value: string) => console.log(value)));

engine.registerCommand(new TypeCommand(engine))




let envPATH = process.env["PATH"]!;

// console.log(envPATH)

let PATH: Array<string> = [];

let separator: "/" | "\\";




switch(process.platform) {

  case "win32":

    PATH = envPATH.split(";").filter(dir => dir.trim() != "")

    separator = "\\"

    break;

  case "linux":

    PATH = envPATH.split(":").filter(dir => dir.trim() != "")

    separator = "/"

    break;

}




PATH.forEach(path => {

  if(fs.existsSync(path)) {

    const files = fs.readdirSync(path, { withFileTypes: true });

    files.forEach(file => {

      if(file.isFile()) {

        const full_path = `${path}${separator}${file.name}`

        const [name, ..._ext] = file.name.split(".");

        try {

          fs.accessSync(full_path, fs.constants.X_OK)

          if(!engine.find(name)) {

            engine.registerCommand(new UserCommand(name, full_path))

          }

        } catch(e) {}

      }

    })

  }

})




rl.setPrompt("$ ");

rl.prompt();




// TODO: Uncomment the code below to pass the first stage

rl.on("line", async (input) => {

  const [command, ...argums] = input.split(" ");

  const args = argums.map((arg) => new Arg(arg));

  engine.currentCommand = command;

  engine.setArgs(args);




  let current = engine.find(command);

  if(current) current.execute();

  else console.log("" + new NotFoundCommand(command));




  rl.prompt();

});




rl.on("close", () => {

  process.exit();

})

Engine.ts (i will probably change it because it doesn’t look like an engine lol)

import { Arg } from "./args/arg.ts";

import { Command } from "./commands/command.ts";




export class Engine {

  private commands: Array<Command> = [];

  private args: Array<Arg> = [];

  public currentCommand: string = "";




  registerCommand(command: Command) {

    this.commands.push(command);

  };




  getCommands(): Array<Command> {

    return this.commands;

  }




  find(key: string) {

    return this.commands.find((command) => command.name === key);

  }




  getArgs() {

    return this.args;

  }




  setArgs(args: Array<Arg>) {

    this.args = args;

  }

}

UserCommand.ts

import { Command } from "./command.ts";
import { CommandType } from "./command_type.ts";

export class UserCommand extends Command {
  constructor(name: string, private path: string) {
    super(name, CommandType.User)
  }
  public execute(): void {};

  public toString(): string {
    return `${this.name} is ${this.path.replace("C:", "").replace(".exe", "").replaceAll("\\", "/")}`
  }
}

TypeCommand.ts

import { Arg } from "../args/arg.ts";

import { Engine } from "../engine.ts";

import { Command } from "./command.ts";

import { CommandType } from "./command_type.ts";

import { TypeNotFoundCommand } from "./TypeNotFoundCommand.ts";




export class TypeCommand extends Command {

  constructor(engine: Engine) {

    super("type", CommandType.BuiltIn, engine)

  }

  public execute(): void {

    const engine = this.engine!;

    const engineArgs = engine.getArgs().map((arg: Arg) => arg.value).join("")

    let command = engine.find(engineArgs);

    if(!command) {

      command = new TypeNotFoundCommand(engineArgs);

    }




    console.log("" + command)

  }

}

command.ts

import { Engine } from "../engine.ts";

import { CommandType } from "./command_type.ts";






export abstract class Command {

  public constructor(public name: string, public type: CommandType, protected engine?: Engine) {};




  public abstract execute(): void;




  public toString(): string {

    switch(this.type) {

      case CommandType.BuiltIn:

        return `${this.name} is a shell builtin`;

      case CommandType.User:

        return `${this.name} is user-created`;

      case CommandType.TypeNotFound:

        return `${this.name}: not found`;

      case CommandType.NotFound:

        return `${this.name}: command not found`;

    }

  }

}

command_type.ts

export enum CommandType {

  BuiltIn,

  User,

  NotFound,

  TypeNotFound

}

I thank everyone who will help me

Can you show us what the test runner says? It’s expected output versus yours?

But it seems like you precompute the expected user binaries. That is not in general how shells work since both PATH itself can change, but also the binaries located at those directories.

1 Like

The test runner answer:


[tester::#MG5] Running tests for Stage #MG5 (Locate executable files)`
[tester::#MG5] [setup] export PATH=/tmp/owl:$PATH
[tester::#MG5] [setup] export PATH=/tmp/cow:$PATH
[tester::#MG5] [setup] export PATH=/tmp/ant:$PATH
[tester::#MG5] [setup] PATH is now: /tmp/ant:/tmp/cow:/tmp/owl:…
[tester::#MG5] [setup] Available executables:
[tester::#MG5] [setup] - my_exe
[tester::#MG5] Running ./your_program.sh
[your-program] $ type cat
[your-program] cat: not found
[tester::#MG5] ^ Line does not match expected value.
[tester::#MG5] Expected: “cat is /bin/cat”
[tester::#MG5] Received: “cat: not found”
[your-program] $
[tester::#MG5] Assertion failed.
[tester::#MG5] Test failed

I know it’s not how things work but i plan to change it soon enough. If it’s because they are not dynamically added, then it’s my fault but, since i don’t know which commands are being ran against my code, i don’t know if they want me to have a dynamic or static import at this point.

Ok so, i edited my code to have a dynamic import (which is reload every path every line). It’s not good in terms of time but if i can pass this, i don’t care. And, if i’m here, it didn’t work. So, i’m stumped.

I want to add the true solution to this problem, i found the error.

In linux or any OS there are symlinks (everyone knows this).
Problem, my code didn’t check for symlinks, so i added them, and everything works now, so guys, don’t make the same error as me.

Fun fact:

fs.stat() and fs.statSync() both follow symlinks but not Dirent.isFile() for some reason;

Thanks again to everyone who helped!

1 Like

Hey @LemonsSquirrel, would you mind sharing the logs or error output from the failing case related to symlinks? :folded_hands:

This was my error code

@andy1li

1 Like

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