Last active
August 24, 2026 20:37
-
-
Save dudo/203ee3ba65c0dc9851b91068b97a01ef to your computer and use it in GitHub Desktop.
Node terminal bootstrap
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { ThingInventory } from "./domain.ts"; | |
| import { runTerminal } from "./terminal.ts"; | |
| const thingInventory = new ThingInventory(); | |
| await runTerminal(thingInventory); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import assert from "node:assert/strict"; | |
| import test from "node:test"; | |
| import { ThingInventory } from "./domain.ts"; | |
| test("creates thing and trims names", () => { | |
| const inventory = new ThingInventory(); | |
| const thing = inventory.add({ | |
| name: " Projector ", | |
| }); | |
| assert.equal(thing.id, "E001"); | |
| assert.equal(thing.name, "Projector"); | |
| }); | |
| test("things are added to the list in sorted order", () => { | |
| const inventory = new ThingInventory(); | |
| inventory.add({ name: "Projector" }); | |
| inventory.add({ name: "Camera" }); | |
| inventory.add({ name: "Microphone" }); | |
| const list = inventory.list(); | |
| assert.equal(list.length, 3); | |
| assert.equal(list[0].name, "Camera"); | |
| assert.equal(list[1].name, "Microphone"); | |
| assert.equal(list[2].name, "Projector"); | |
| }); | |
| test("rejects empty thing names", () => { | |
| const inventory = new ThingInventory(); | |
| assert.throws( | |
| () => | |
| inventory.add({ | |
| name: " ", | |
| }), | |
| /name is required/, | |
| ); | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| export type ThingInput = { | |
| name: string; | |
| }; | |
| export type Thing = { | |
| id: string; | |
| name: string; | |
| } | |
| export class ThingInventory { | |
| private nextId = 1; | |
| private thingList: Thing[] = []; | |
| add(input: ThingInput): Thing { | |
| const name = input.name.trim(); | |
| if (!name) throw new Error("Thing name is required."); | |
| const thing: Thing = { | |
| id: `E${String(this.nextId).padStart(3, "0")}`, | |
| name, | |
| }; | |
| this.nextId += 1; | |
| this.thingList.push(thing); | |
| return { ...thing }; | |
| } | |
| list(): Thing[] { | |
| return this.thingList | |
| .toSorted((left, right) => left.name.localeCompare(right.name)) | |
| .map((thing) => ({ ...thing })); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "name": "node-terminal-bootstrap", | |
| "version": "1.0.0", | |
| "private": true, | |
| "type": "module", | |
| "engines": { | |
| "node": ">=26" | |
| }, | |
| "scripts": { | |
| "start": "node src/app.ts", | |
| "dev": "node --watch src/app.ts", | |
| "check": "node --check src/app.ts && node --check src/terminal.ts && node --check src/domain.ts && node --check src/domain.test.ts", | |
| "test": "node --test", | |
| "test:watch": "node --test --watch" | |
| }, | |
| "devDependencies": { | |
| "@types/node": "^26.2.0" | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { stdin as input, stdout as output } from "node:process"; | |
| import { createInterface } from "node:readline/promises"; | |
| import { | |
| type Thing, | |
| type ThingInventory, | |
| } from "./domain.ts"; | |
| export async function runTerminal( | |
| thingInventory: ThingInventory, | |
| ): Promise<void> { | |
| const terminal = createInterface({ input, output }); | |
| console.log("Thing inventory management"); | |
| console.log("Type 'help' to see the available commands.\n"); | |
| try { | |
| while (true) { | |
| const command = (await terminal.question("inventory> ")) | |
| .trim() | |
| .toLowerCase(); | |
| try { | |
| switch (command) { | |
| case "add": { | |
| const name = await terminal.question("Thing name: "); | |
| const thing = thingInventory.add({ name }); | |
| console.log( | |
| `Created ${thing.id} for ${thing.name}.`, | |
| ); | |
| break; | |
| } | |
| case "list": | |
| printThing(thingInventory.list()); | |
| break; | |
| case "help": | |
| case "?": | |
| printHelp(); | |
| break; | |
| case "quit": | |
| case "exit": | |
| console.log("Goodbye."); | |
| return; | |
| case "": | |
| break; | |
| default: | |
| console.log(`Unknown command: ${command}. Type 'help' for options.`); | |
| } | |
| } catch (error) { | |
| const message = error instanceof Error ? error.message : String(error); | |
| console.error(`Error: ${message}`); | |
| } | |
| } | |
| } finally { | |
| terminal.close(); | |
| } | |
| } | |
| function printThing(thingList: Thing[]): void { | |
| if (thingList.length === 0) { | |
| console.log("No things found."); | |
| return; | |
| } | |
| const rows = thingList.map((thing) => [ | |
| thing.id, | |
| thing.name, | |
| ]); | |
| const headers = ["ID", "NAME"]; | |
| const widths = headers.map((header, column) => | |
| Math.max(header.length, ...rows.map((row) => row[column].length)), | |
| ); | |
| const formatRow = (row: string[]) => | |
| row.map((cell, column) => cell.padEnd(widths[column])).join(" "); | |
| console.log(formatRow(headers)); | |
| console.log(widths.map((width) => "-".repeat(width)).join(" ")); | |
| rows.forEach((row) => console.log(formatRow(row))); | |
| } | |
| function printHelp(): void { | |
| console.log(` | |
| Commands: | |
| add Register thing to inventory | |
| list List all things | |
| help Show this help | |
| quit Exit the application | |
| `); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Show hidden characters
| { | |
| "compilerOptions": { | |
| "lib": ["es2023"], | |
| "skipLibCheck": true, | |
| "types": ["node"], | |
| "strict": true, | |
| "noEmit": true, | |
| "allowImportingTsExtensions": true | |
| }, | |
| "include": ["src/**/*.ts"] | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment