DEV Community

Kannav Sethi
Kannav Sethi

Posted on • Edited on

DialectMorph - A CLI Tool To Transpile Code

Introduction

The idea for DialectMorph came to me when I initially tried to encapsulate a function for making an API call in an object using TypeScript. However, I later discovered that the API only supported Python, so I had to use AI agents to help me transpile the code from TypeScript to Python. This challenge inspired me to create a CLI tool that would simplify this process, essentially acting as a CLI wrapper for AI-driven code transpilation, which led to the development of DialectMorph.

Tech Stack Used

The entire project is based on TypeScript with different libraries that support the project, the following libraries were used in the codebase

  • Commander.js
    Commander.js helped me configure the basic requirements for the CLI, I will talk in detail about Commander.js below

  • Figlet
    Ascii Text Generation For CLI Tool

  • Chalk
    Library for styling tools for the terminal

  • Groq SDK
    This is the Software Development Kit that is used to interact with Groq API
    I used the singleton pattern here to instantiate the Groq Client because having more than one client during runtime might lead to unexpected errors

  • Ora
    This was used to make the terminal spinner

  • VHS
    This tool was used to make the demo video for the CLI tool

  • Prettier
    This tool was used to maintain consistent formatting, It was implemented along with a CI pipeline to ensure the code was formatted before being merged/pushed to the repo

Commander.js

This CLI tool uses commander extensively, so before going further
into the codebase, I would like to discuss how Commander works

Getting Started With Commander

To get started, create a commander object that would be later used to run the CLI tool

import Commander from "Commander"
const program = new Commander()
Enter fullscreen mode Exit fullscreen mode

Configuration Methods

Commander library provides various methods to configure how the CLI tool would work, the ones that I use to make this tool can be described below

1.version()

This command is used to specify the version of the CLI tool that is being used and provides the following options to the user

cli-tool -V 
cli-tool --version
Enter fullscreen mode Exit fullscreen mode

2.name()

This command is used to specify the name of the CLI tool that is being used

3.usage()

This command is used to specify how the user would use the CLI tool, it is usually placed at the first line when the -h or --help method is called with the tool

cli-tool -h

Output:
usage: cli-tool <input-file> -o <output-file>
Enter fullscreen mode Exit fullscreen mode

4.description()
This command is used to put the description for the CLI tool, it is displayed once the -h or --help command is called

cli-tool -h

Output:
usage: cli-tool <input-file> -o <output-file>
Description: This is some random description
Enter fullscreen mode Exit fullscreen mode

5.option()
A CLI tool has 2 different types of options that you can provide it, one being a boolean and the other one being a value-based option

Boolean Option
This option doesn't need to have an argument provided to it to work

.option("-d,--display","Used to display some information")

Enter fullscreen mode Exit fullscreen mode

Value-Based Option
This option needs to have an argument provided to it to work

.option("-o, --output <output_file>","Provide the output file for the CLI tool)

Enter fullscreen mode Exit fullscreen mode

All of these options can be accessed via the following command and be used in the program based on the logic that you want

program.options.output()

Enter fullscreen mode Exit fullscreen mode

The above code will output the value provided to the argument

6.argument()
Defines a single argument with a placeholder for a command.
You specify whether it's required or optional using angle brackets <> (required) or square brackets .

program
  .command('run')
  .argument('<file>', 'file to process')  // 'file' is a required argument
  .action((file) => {
    console.log(`Processing file: ${file}`);
  });

Enter fullscreen mode Exit fullscreen mode

7.arguments()

Defines multiple arguments, especially useful when you don't need specific names for each argument.

program
  .command('copy')
  .arguments('<source> <destination>')  // Both are required
  .action((source, destination) => {
    console.log(`Copying from ${source} to ${destination}`);
  });

Enter fullscreen mode Exit fullscreen mode

8.parse()

.parse() is called without arguments, and it automatically parses process.argv, which contains all the arguments passed to your script, in my program I have used Bun to get the arguments

program.parse(Bun.argv)
Enter fullscreen mode Exit fullscreen mode

Logic Flow

The Logic Flow of the program is as follows

  • The input file(s) are inputted in the CLI tool
  • The output language option is provided
  • Optional arguments, including model or API key can be provided
  • Program fetches the input file, calls an API call on Groq
  • The result from Groq API is stored in the files under the transpiledFiles

Issues Encountered

As I delved into the intricacies of code transpilation and API integration, I found myself facing a multitude of challenges that tested my problem-solving skills and pushed me to expand my technical knowledge

The issues are as follows :

1.Getting The Singleton Instantiation Right for the Groq Client

This issue took me a long time to get right, the issue was occurring because I wasn't able to get the API_KEY as an argument in the instantiation for the singleton, however, the thing that helped me solve was making a helper function to address the instantiation

it was a tough one but I eventually got it right.

2.Getting the binary to run on different machines

This was one of the most interesting issues I've worked on, as it required reproducing the problem on a Virtual Machine to identify what was going wrong. Despite using the bun link command, the binary still wouldn't run. The challenge was that the binary worked fine on my local machine, so to troubleshoot without disrupting my working code, I cloned the repo on a VM. I successfully reproduced the issue and quickly found the solution: the problem was that the files weren't being transpiled from TypeScript to JavaScript because the bun run build command hadn't been executed before using bun link.

Conclusion

The development of DialectMorph has been an informative journey, transforming a personal need for code transpilation into a robust CLI tool that bridges multiple programming languages. This project solved the initial challenge of converting TypeScript to Python and evolved into a versatile utility that can assist developers across various language ecosystems.

Top comments (0)