typespec1-8 Exercise 3: A Markdown Emitter ================================================= md-emitter/package.json and md-emitter/index.js: { "name": "md-emitter", "version": "0.0.1", "type": "module", "main": "index.js" } import { createTypeSpecLibrary, emitFile, resolvePath } from "@typespec/compiler"; import { getAllHttpServices } from "@typespec/http"; export const $lib = createTypeSpecLibrary({ name: "md-emitter", diagnostics: {} }); export async function $onEmit(context) { const [services] = getAllHttpServices(context.program); const rows = ["| Verb | Path | Operation |", "| --- | --- | --- |"]; for (const service of services) { for (const op of service.operations) { rows.push(`| ${op.verb.toUpperCase()} | \`${op.path}\` | ${op.operation.name} |`); } } await emitFile(context.program, { path: resolvePath(context.emitterOutputDir, "operations.md"), content: rows.join("\n") + "\n", }); } The config (tspconfig_ex3.yaml): emit: - "@typespec/openapi3" - "./md-emitter" options: "@typespec/openapi3": openapi-versions: - 3.1.0 Run on chapter 5's Book Service with --list-files: ✔ @typespec/openapi3 41ms tsp-output/@typespec/openapi3/ tsp-output/@typespec/openapi3/openapi.yaml ✔ md-emitter 2ms tsp-output/md-emitter/ tsp-output/@typespec/openapi3/openapi.yaml tsp-output/md-emitter/operations.md The file the new emitter wrote (tsp-output/md-emitter/operations.md): | Verb | Path | Operation | | --- | --- | --- | | GET | `/books` | list | | GET | `/books/{id}` | read | | POST | `/books` | create | | PATCH | `/books/{id}` | update | | DELETE | `/books/{id}` | remove | A STUMBLE ON THE WAY My first config sat in a sub-folder (ex/) and named the emitter as "../md-emitter". The compiler said: error emitter-not-found: Emitter "../md-emitter" not found. Make sure to install it with `npm install ../md-emitter`. I moved the config next to the project and used "./md-emitter", which worked. I did not find out which part of the first attempt was wrong (the ../ form or the config's folder), so I cannot tell you which of the two is the rule. WHAT HAPPENED Two emitters ran on one compile, each writing under its own folder named after it. The Markdown emitter is the chapter's operations-listing emitter with a different output format: it uses the same getAllHttpServices call and changes only the lines it writes. It lists the verb, path and operation name only; it ignores status codes, parameters and schemas. WHY THIS WORKS AS AN ANSWER --------------------------- An emitter is a small program that walks the compiled model. Producing a table of operations is a few lines because the compiler has already resolved routes and verbs: the emitter does not parse TypeSpec, it reads the result.