Welcome to the first chapter of the release-notes project tutorial! Here, we are going to learn how to introduce a new command to our Command Line Interface (CLI) application.
Imagine walking into a restaurant. Before you order food, you look at a menu. The menu tells you the name of the dish and a short description of what it is.
Crucially, the chef does not cook every single dish on the menu the moment you walk in. They only cook the specific dish you ask for.
If the chef tried to cook everything at once, the restaurant would be:
Command Registration solves this exact problem for software. It allows our application to show a "menu" of available commands (like release-notes) without actually loading the heavy code required to execute them.
To register a command, we create a lightweight "identity card" for it. This includes:
release-notes).
Let's look at how we define the identity of our command in the file index.ts. We use a specific structure (or "Type") called Command to ensure we provide all the necessary details.
First, we define the basic metadata. This is the text that shows up when a user types --help.
// --- File: index.ts ---
import type { Command } from '../../commands.js'
// We create an object that follows the 'Command' rules
const releaseNotes: Command = {
description: 'View release notes',
name: 'release-notes',
type: 'local',
// ... more settings coming next
}
Explanation:
Command type definition. This acts like a template form we have to fill out.name ('release-notes').description. This is lightweight text that is very fast to load.Next, we add configuration flags. These tell the CLI how to treat the command without running it.
// --- File: index.ts (continued) ---
// Adding specific settings to our object
const releaseNotes: Command = {
// ... previous properties
supportsNonInteractive: true,
// ...
}
Explanation:
supportsNonInteractive: This tells the CLI, "It is safe to run this command in a script (CI/CD) where no human can press keys."Finally, we tell the registration where the actual code lives, but we don't run it yet.
// --- File: index.ts (continued) ---
const releaseNotes: Command = {
// ... previous properties
// The 'load' function acts as a pointer
load: () => import('./release-notes.js'),
}
export default releaseNotes
Explanation:
load: This is a function. Note that we are not importing release-notes.js at the top of the file.How does the CLI use this registration? Let's visualize the flow.
When you start the application, it needs to build the list of available commands. It scans for these registration files (index.ts) because they are small and fast to read.
Here is what happens when a user asks for help:
The core application iterates through folders looking for these index.ts files. Because we separated the definition from the execution, the application stays fast.
Here is a simplified look at what the Command interface might look like in the core system:
// --- File: commands.ts (Simplified) ---
export interface Command {
name: string;
description: string;
// This function returns a Promise containing the logic
load: () => Promise<unknown>;
}
Explanation:
Command interface enforces that every command must have a name and a load function.release-notes or something else.In this chapter, we learned that Command Registration is about defining the "Menu Entry" for our command.
But what actually happens when we call that load function? How do we efficiently load the heavy code only when the user selects release-notes?
Let's find out in the next chapter: Lazy Module Loading.
Generated by Code IQ