Setting Up the Project Foundations
The entire project is based on TypeScript (for the TypeSpec emitter) and Go (currently for everything else). Even though there is no direct link between these areas, everything is packaged into a single repository. The emitter—without using the emitter framework—is called directly, as is the Go CLI. This means the actual CLI will not handle creating the JSON IR itself; it will merely be based on it. Consequently, the user has complete freedom to choose whether to use Node, Deno, Bun, or another runtime. While this could be made configurable for the CLI, maintaining a clean separation without hidden processes is very important to me.
All Relevant Tasks Are Provided via a Taskfile in the Repository
Instead of using Makefiles or scripts in package.json etc., all relevant tasks are provided via a Taskfile in the
repository. To leverage greater scripting functionality here, shell scripts are used, which—thanks to gosh (also used
internally by Taskfile)—are runnable on all platforms.
The first version of the JSON IR specification is available on my site: ir-schema.v0.0.1.json
Repository Structure
The actual CLI will be located under /cmd/spec-cli/. Its associated logic will reside within /internal/, as we do
not intend to share functionality directly as a Go package. The TypeScript emitter will be in /emitter/. Examples will
later be in /examples/, and the aforementioned shell scripts in /tasks/.
The repository itself has already been created: Spec CLI Repo—it is
currently still private, as there is nothing truly usable in it yet. However, an initial commit with the basic
structure, go.mod, a package.json in the emitter folder, and the Taskfile.yml is already present. Nothing more.
Taskfiles Are Excellent for Mixed-Technology Projects
Especially in a project like this—where technologies are mixed—Taskfiles are excellent. They can manage all tasks cleanly and automatically provide a way to list all tasks in the console without needing to open the file. Unlike Makefiles, they also guarantee equivalent handling across all platforms.
The repo is still far from ready in the sense of being a functioning tool, but the foundation is laid. This is precisely the point where I can finally begin implementing the emitter.
The deliberate separation between the TypeScript-based JSON IR emitter and the Go-based toolchain represents a key architectural decision. While it requires maintaining two distinct tech stacks, it offers crucial benefits: it keeps the core generation logic pure and framework-independent, gives users full control over their runtime environment, and ensures the Go components remain lightweight and focused solely on their domain. This clean boundary turns what could be a complexity into a defining strength of the project’s design.