fhir-resource-diff 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,615 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ buildEnvelope,
4
+ detectFhirVersion,
5
+ diff,
6
+ formatJson,
7
+ formatMarkdown,
8
+ formatText,
9
+ formatValidationJson,
10
+ formatValidationText,
11
+ getResourceDocUrl,
12
+ getResourceInfo,
13
+ isSupportedFhirVersion,
14
+ listResourceTypes,
15
+ normalize,
16
+ parseJson,
17
+ resolveFhirVersion,
18
+ summarizeDiff,
19
+ validate
20
+ } from "../chunk-2UUKQJDB.js";
21
+
22
+ // src/cli/index.ts
23
+ import { Command } from "commander";
24
+
25
+ // src/cli/commands/compare.ts
26
+ import pc from "picocolors";
27
+
28
+ // src/presets/ignore-fields.ts
29
+ var IGNORE_METADATA = {
30
+ name: "metadata",
31
+ description: "Ignore id, meta, and text fields \u2014 focus on clinical content",
32
+ paths: ["id", "meta", "text"]
33
+ };
34
+ var IGNORE_CLINICAL = {
35
+ name: "clinical",
36
+ description: "Ignore metadata and extensions \u2014 compare core clinical fields only",
37
+ paths: ["id", "meta", "text", "extension", "modifierExtension"]
38
+ };
39
+ var IGNORE_STRICT = {
40
+ name: "strict",
41
+ description: "Ignore nothing \u2014 compare all fields",
42
+ paths: []
43
+ };
44
+
45
+ // src/presets/normalization.ts
46
+ var NORMALIZE_CANONICAL = {
47
+ name: "canonical",
48
+ description: "Sort keys, trim strings, normalize dates \u2014 maximally comparable form",
49
+ options: {
50
+ sortObjectKeys: true,
51
+ trimStrings: true,
52
+ normalizeDates: true
53
+ }
54
+ };
55
+ var NORMALIZE_NONE = {
56
+ name: "none",
57
+ description: "No normalization \u2014 compare exact values",
58
+ options: {}
59
+ };
60
+
61
+ // src/presets/index.ts
62
+ var IGNORE_PRESETS = {
63
+ [IGNORE_METADATA.name]: IGNORE_METADATA,
64
+ [IGNORE_CLINICAL.name]: IGNORE_CLINICAL,
65
+ [IGNORE_STRICT.name]: IGNORE_STRICT
66
+ };
67
+ var NORMALIZATION_PRESETS = {
68
+ [NORMALIZE_CANONICAL.name]: NORMALIZE_CANONICAL,
69
+ [NORMALIZE_NONE.name]: NORMALIZE_NONE
70
+ };
71
+ function getIgnorePreset(name) {
72
+ return IGNORE_PRESETS[name];
73
+ }
74
+ function getNormalizationPreset(name) {
75
+ return NORMALIZATION_PRESETS[name];
76
+ }
77
+ function mergeIgnorePresets(...presets) {
78
+ const seen = /* @__PURE__ */ new Set();
79
+ for (const preset of presets) {
80
+ for (const path of preset.paths) {
81
+ seen.add(path);
82
+ }
83
+ }
84
+ return Array.from(seen);
85
+ }
86
+
87
+ // src/cli/utils/read-file.ts
88
+ import { readFileSync } from "fs";
89
+
90
+ // src/cli/utils/read-stdin.ts
91
+ function readStdinSync() {
92
+ if (process.stdin.isTTY) {
93
+ return Promise.reject(new Error("No input on stdin (stdin is a TTY)"));
94
+ }
95
+ return new Promise((resolve, reject) => {
96
+ const chunks = [];
97
+ process.stdin.on("data", (chunk) => chunks.push(chunk));
98
+ process.stdin.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
99
+ process.stdin.on("error", reject);
100
+ });
101
+ }
102
+
103
+ // src/cli/utils/read-file.ts
104
+ async function readFileOrExit(filePath) {
105
+ if (filePath === "-") {
106
+ try {
107
+ return await readStdinSync();
108
+ } catch {
109
+ process.stderr.write(
110
+ "Error: no input on stdin. Pipe data to stdin or provide a file path.\nUsage: cat resource.json | fhir-resource-diff validate -\n"
111
+ );
112
+ process.exit(2);
113
+ }
114
+ }
115
+ try {
116
+ return readFileSync(filePath, "utf-8");
117
+ } catch (e) {
118
+ process.stderr.write(
119
+ `Error: Cannot read file "${filePath}": ${e instanceof Error ? e.message : String(e)}
120
+ `
121
+ );
122
+ process.exit(2);
123
+ }
124
+ }
125
+
126
+ // src/cli/utils/resolve-version.ts
127
+ function parseVersionFlag(value) {
128
+ if (value === void 0) return void 0;
129
+ if (isSupportedFhirVersion(value)) return value;
130
+ process.stderr.write(
131
+ `Error: Unknown FHIR version "${value}". Supported: R4, R4B, R5
132
+ `
133
+ );
134
+ process.exit(2);
135
+ }
136
+
137
+ // src/cli/commands/compare.ts
138
+ var SECTION_HEADERS = /* @__PURE__ */ new Set(["Changed:", "Added:", "Removed:", "Type-changed:"]);
139
+ function applyColor(text) {
140
+ const lines = text.split("\n");
141
+ let currentSection = null;
142
+ const colored = [];
143
+ for (const line of lines) {
144
+ const trimmed = line.trim();
145
+ if (SECTION_HEADERS.has(trimmed)) {
146
+ currentSection = trimmed;
147
+ colored.push(pc.yellow(line));
148
+ continue;
149
+ }
150
+ if (trimmed === "") {
151
+ currentSection = null;
152
+ colored.push(line);
153
+ continue;
154
+ }
155
+ let processed = line;
156
+ if (line.includes(" \u2192 ")) {
157
+ processed = line.replace(
158
+ " \u2192 ",
159
+ ` ${pc.dim("\u2192")} `
160
+ );
161
+ }
162
+ if (currentSection === "Added:") {
163
+ colored.push(pc.green(processed));
164
+ } else if (currentSection === "Removed:") {
165
+ colored.push(pc.red(processed));
166
+ } else {
167
+ colored.push(processed);
168
+ }
169
+ }
170
+ return colored.join("\n");
171
+ }
172
+ function parseOrExit(filePath, raw) {
173
+ const result = parseJson(raw);
174
+ if (!result.success) {
175
+ process.stderr.write(`Error: "${filePath}" is not valid FHIR JSON: ${result.error}
176
+ `);
177
+ process.exit(2);
178
+ }
179
+ return result.resource;
180
+ }
181
+ function registerCompareCommand(program2) {
182
+ program2.command("compare <file-a> <file-b>").description("Compare two FHIR JSON resource files and report differences").option(
183
+ "--format <fmt>",
184
+ 'Output format: text | json | markdown (default: "text")',
185
+ "text"
186
+ ).option(
187
+ "--ignore <paths>",
188
+ "Comma-separated field paths to exclude from comparison"
189
+ ).option("--preset <name>", "Named ignore preset (e.g. metadata, clinical, strict)").option("--normalize <name>", "Named normalization preset to apply before diffing").option("--no-color", "Disable color output").option("--exit-on-diff", "Exit with code 1 if differences are found").option("--fhir-version <ver>", "FHIR version: R4 | R4B | R5 (default: auto-detect or R4)").option("--quiet", "Suppress all stdout output. Only exit code indicates result.").option("--envelope", "Wrap JSON output in a metadata envelope (requires --format json)").action(async (fileA, fileB, opts) => {
190
+ if (fileA === "-" && fileB === "-") {
191
+ process.stderr.write(
192
+ "Error: cannot read both resources from stdin. Provide at least one file path.\n"
193
+ );
194
+ process.exit(2);
195
+ }
196
+ const rawA = await readFileOrExit(fileA);
197
+ const rawB = await readFileOrExit(fileB);
198
+ let resourceA = parseOrExit(fileA, rawA);
199
+ let resourceB = parseOrExit(fileB, rawB);
200
+ const explicitVersion = parseVersionFlag(opts.fhirVersion);
201
+ const resolvedVersionA = resolveFhirVersion(explicitVersion, resourceA);
202
+ void resolveFhirVersion(explicitVersion, resourceB);
203
+ if (explicitVersion === void 0) {
204
+ const detectedA = detectFhirVersion(resourceA);
205
+ const detectedB = detectFhirVersion(resourceB);
206
+ if (detectedA !== void 0 && detectedB !== void 0 && detectedA !== detectedB) {
207
+ process.stderr.write(
208
+ `Warning: resources appear to be from different FHIR versions (${detectedA} vs ${detectedB})
209
+ `
210
+ );
211
+ }
212
+ }
213
+ const validateVersion = explicitVersion !== void 0 ? resolvedVersionA : void 0;
214
+ const validA = validate(resourceA, validateVersion);
215
+ if (!validA.valid) {
216
+ process.stderr.write(`Warning: "${fileA}" has validation issues
217
+ `);
218
+ }
219
+ const validB = validate(resourceB, validateVersion);
220
+ if (!validB.valid) {
221
+ process.stderr.write(`Warning: "${fileB}" has validation issues
222
+ `);
223
+ }
224
+ if (opts.normalize !== void 0) {
225
+ const normPreset = getNormalizationPreset(opts.normalize);
226
+ if (normPreset === void 0) {
227
+ process.stderr.write(
228
+ `Error: Unknown normalization preset "${opts.normalize}". Available: canonical, none
229
+ `
230
+ );
231
+ process.exit(2);
232
+ }
233
+ resourceA = normalize(resourceA, normPreset.options);
234
+ resourceB = normalize(resourceB, normPreset.options);
235
+ }
236
+ let ignorePaths = [];
237
+ if (opts.preset !== void 0) {
238
+ const namedPreset = getIgnorePreset(opts.preset);
239
+ if (namedPreset === void 0) {
240
+ process.stderr.write(
241
+ `Error: Unknown ignore preset "${opts.preset}". Available: metadata, clinical, strict
242
+ `
243
+ );
244
+ process.exit(2);
245
+ }
246
+ ignorePaths = mergeIgnorePresets(namedPreset);
247
+ }
248
+ if (opts.ignore !== void 0 && opts.ignore.trim() !== "") {
249
+ const manualPaths = opts.ignore.split(",").map((p) => p.trim()).filter((p) => p !== "");
250
+ ignorePaths = Array.from(/* @__PURE__ */ new Set([...ignorePaths, ...manualPaths]));
251
+ }
252
+ const diffOptions = ignorePaths.length > 0 ? { ignorePaths } : {};
253
+ const result = diff(resourceA, resourceB, diffOptions);
254
+ let useEnvelope = opts.envelope;
255
+ if (useEnvelope && opts.format !== "json") {
256
+ process.stderr.write(
257
+ "Warning: --envelope requires --format json. Ignoring --envelope.\n"
258
+ );
259
+ useEnvelope = false;
260
+ }
261
+ const colorsEnabled = opts.color && process.env["NO_COLOR"] === void 0;
262
+ const format = opts.format;
263
+ let output;
264
+ if (format === "json") {
265
+ if (useEnvelope) {
266
+ const summary = summarizeDiff(result);
267
+ const documentation = getResourceDocUrl(result.resourceType, resolvedVersionA);
268
+ const envelopeResult = { ...result, summary, documentation };
269
+ const envelope = buildEnvelope("compare", resolvedVersionA, envelopeResult);
270
+ output = JSON.stringify(envelope, null, 2);
271
+ } else {
272
+ output = formatJson(result);
273
+ }
274
+ } else if (format === "markdown") {
275
+ output = formatMarkdown(result);
276
+ } else {
277
+ const textOutput = formatText(result);
278
+ output = colorsEnabled ? applyColor(textOutput) : textOutput;
279
+ }
280
+ if (!opts.quiet) {
281
+ process.stdout.write(output + "\n");
282
+ }
283
+ if (opts.exitOnDiff && !result.identical) {
284
+ process.exit(1);
285
+ }
286
+ });
287
+ }
288
+
289
+ // src/cli/commands/validate.ts
290
+ function registerValidateCommand(program2) {
291
+ program2.command("validate <file>").description("Validate a FHIR JSON resource file").option(
292
+ "--format <fmt>",
293
+ 'Output format: text | json (default: "text")',
294
+ "text"
295
+ ).option("--fhir-version <ver>", "FHIR version: R4 | R4B | R5 (default: auto-detect or R4)").option("--quiet", "Suppress all stdout output. Only exit code indicates result.").option("--envelope", "Wrap JSON output in a metadata envelope (requires --format json)").action(async (file, opts) => {
296
+ const raw = await readFileOrExit(file);
297
+ const parsed = parseJson(raw);
298
+ if (!parsed.success) {
299
+ process.stderr.write(`Error: "${file}" is not valid FHIR JSON: ${parsed.error}
300
+ `);
301
+ process.exit(2);
302
+ }
303
+ const explicitVersion = parseVersionFlag(opts.fhirVersion);
304
+ const resolvedVersion = resolveFhirVersion(explicitVersion, parsed.resource);
305
+ const result = validate(parsed.resource, explicitVersion !== void 0 ? resolvedVersion : void 0);
306
+ let useEnvelope = opts.envelope;
307
+ if (useEnvelope && opts.format !== "json") {
308
+ process.stderr.write(
309
+ "Warning: --envelope requires --format json. Ignoring --envelope.\n"
310
+ );
311
+ useEnvelope = false;
312
+ }
313
+ const format = opts.format;
314
+ let output;
315
+ if (format === "json") {
316
+ if (useEnvelope) {
317
+ const documentation = getResourceDocUrl(parsed.resource.resourceType, resolvedVersion);
318
+ const envelopeResult = { ...result, documentation };
319
+ const envelope = buildEnvelope("validate", resolvedVersion, envelopeResult);
320
+ output = JSON.stringify(envelope, null, 2);
321
+ } else {
322
+ output = formatValidationJson(result);
323
+ }
324
+ } else {
325
+ output = formatValidationText(result);
326
+ }
327
+ if (!opts.quiet) {
328
+ process.stdout.write(output + "\n");
329
+ }
330
+ const hasErrors = result.valid === false && result.errors.some((e) => e.severity === "error");
331
+ process.exit(hasErrors ? 1 : 0);
332
+ });
333
+ }
334
+
335
+ // src/cli/commands/normalize.ts
336
+ import { writeFileSync } from "fs";
337
+ var DEFAULT_PRESET_NAME = "canonical";
338
+ function registerNormalizeCommand(program2) {
339
+ program2.command("normalize <file>").description("Normalize a FHIR JSON resource file and output the result").option(
340
+ "--preset <name>",
341
+ `Named normalization preset (default: "${DEFAULT_PRESET_NAME}")`,
342
+ DEFAULT_PRESET_NAME
343
+ ).option("--output <path>", "Write output to a file instead of stdout").option("--fhir-version <ver>", "FHIR version: R4 | R4B | R5 (default: auto-detect or R4)").option("--quiet", "Suppress all stdout output.").action(async (file, opts) => {
344
+ const raw = await readFileOrExit(file);
345
+ const parsed = parseJson(raw);
346
+ if (!parsed.success) {
347
+ process.stderr.write(`Error: "${file}" is not valid FHIR JSON: ${parsed.error}
348
+ `);
349
+ process.exit(2);
350
+ }
351
+ const explicitVersion = parseVersionFlag(opts.fhirVersion);
352
+ void resolveFhirVersion(explicitVersion, parsed.resource);
353
+ const presetName = opts.preset;
354
+ const preset = getNormalizationPreset(presetName);
355
+ if (preset === void 0) {
356
+ process.stderr.write(
357
+ `Error: Unknown normalization preset "${presetName}". Available: canonical, none
358
+ `
359
+ );
360
+ process.exit(2);
361
+ }
362
+ const normalized = normalize(parsed.resource, preset.options);
363
+ const output = JSON.stringify(normalized, null, 2);
364
+ if (opts.output !== void 0) {
365
+ try {
366
+ writeFileSync(opts.output, output + "\n", "utf-8");
367
+ } catch (e) {
368
+ process.stderr.write(
369
+ `Error: Cannot write to "${opts.output}": ${e instanceof Error ? e.message : String(e)}
370
+ `
371
+ );
372
+ process.exit(2);
373
+ }
374
+ } else if (!opts.quiet) {
375
+ process.stdout.write(output + "\n");
376
+ }
377
+ });
378
+ }
379
+
380
+ // src/cli/commands/info.ts
381
+ function buildMaturityLabel(maturityLevel) {
382
+ if (maturityLevel === void 0) return "";
383
+ if (maturityLevel === "N") return " \u2014 Normative \u2605";
384
+ return ` \u2014 FMM ${maturityLevel}`;
385
+ }
386
+ function buildTextOutput(resourceType, fhirVersion) {
387
+ const info = getResourceInfo(resourceType);
388
+ if (!info) {
389
+ return [
390
+ `Unknown resource type: "${resourceType}"`,
391
+ "",
392
+ "Run 'fhir-resource-diff list-resources' to see known types.",
393
+ "Full resource list: https://hl7.org/fhir/resourcelist.html"
394
+ ].join("\n");
395
+ }
396
+ const versionsToShow = fhirVersion ? [fhirVersion] : info.versions;
397
+ const versionLine = fhirVersion ? `FHIR version: ${fhirVersion}` : `Available in: ${info.versions.join(" \xB7 ")}`;
398
+ const lines = [];
399
+ lines.push(`${info.resourceType} (${info.category})${buildMaturityLabel(info.maturityLevel)}`);
400
+ lines.push(versionLine);
401
+ lines.push("");
402
+ lines.push(info.description);
403
+ if (info.useCases && info.useCases.length > 0) {
404
+ lines.push("");
405
+ lines.push("Use cases:");
406
+ for (const useCase of info.useCases) {
407
+ lines.push(` \u2022 ${useCase}`);
408
+ }
409
+ }
410
+ if (info.keyFields && info.keyFields.length > 0) {
411
+ lines.push("");
412
+ lines.push("Key fields:");
413
+ const nameColWidth = Math.max(...info.keyFields.map((f) => f.name.length + (f.required ? 2 : 0)));
414
+ for (const field of info.keyFields) {
415
+ const nameWithMarker = field.required ? `${field.name} *` : field.name;
416
+ const paddedName = nameWithMarker.padEnd(nameColWidth);
417
+ lines.push(` ${paddedName} ${field.note}`);
418
+ }
419
+ }
420
+ if (!fhirVersion && info.versionNotes) {
421
+ const r4ToR4b = info.versionNotes["R4\u2192R4B"];
422
+ const r4bToR5 = info.versionNotes["R4B\u2192R5"];
423
+ if (r4ToR4b !== void 0 || r4bToR5 !== void 0) {
424
+ lines.push("");
425
+ lines.push("Version notes:");
426
+ if (r4ToR4b !== void 0) {
427
+ lines.push(` R4 \u2192 R4B ${r4ToR4b}`);
428
+ }
429
+ if (r4bToR5 !== void 0) {
430
+ lines.push(` R4B \u2192 R5 ${r4bToR5}`);
431
+ }
432
+ }
433
+ }
434
+ const docLines = versionsToShow.map((v) => {
435
+ const label = `${v}:`.padEnd(5);
436
+ return ` ${label} ${getResourceDocUrl(resourceType, v)}`;
437
+ });
438
+ lines.push("");
439
+ lines.push("Documentation:");
440
+ lines.push(...docLines);
441
+ return lines.join("\n");
442
+ }
443
+ function buildJsonOutput(resourceType, fhirVersion) {
444
+ const info = getResourceInfo(resourceType);
445
+ if (!info) {
446
+ return JSON.stringify(
447
+ {
448
+ error: "Unknown resource type",
449
+ resourceType,
450
+ help: "https://hl7.org/fhir/resourcelist.html"
451
+ },
452
+ null,
453
+ 2
454
+ );
455
+ }
456
+ const versionsToShow = fhirVersion ? [fhirVersion] : info.versions;
457
+ const documentation = {};
458
+ for (const v of versionsToShow) {
459
+ documentation[v] = getResourceDocUrl(resourceType, v);
460
+ }
461
+ const output = {
462
+ resourceType: info.resourceType,
463
+ category: info.category,
464
+ versions: fhirVersion ? [fhirVersion] : [...info.versions],
465
+ description: info.description
466
+ };
467
+ if (info.maturityLevel !== void 0) {
468
+ output.maturityLevel = info.maturityLevel;
469
+ }
470
+ if (info.useCases !== void 0) {
471
+ output.useCases = [...info.useCases];
472
+ }
473
+ if (info.keyFields !== void 0) {
474
+ output.keyFields = info.keyFields.map((f) => ({ name: f.name, required: f.required, note: f.note }));
475
+ }
476
+ if (!fhirVersion && info.versionNotes !== void 0) {
477
+ output.versionNotes = { ...info.versionNotes };
478
+ }
479
+ output.documentation = documentation;
480
+ return JSON.stringify(output, null, 2);
481
+ }
482
+ function registerInfoCommand(program2) {
483
+ program2.command("info <resourceType>").description("Show metadata and HL7 documentation links for a FHIR resource type").option("--fhir-version <ver>", "Show docs link for a specific version only (R4 | R4B | R5)").option("--format <fmt>", "Output format: text | json", "text").action((resourceType, opts) => {
484
+ const fhirVersion = parseVersionFlag(opts.fhirVersion);
485
+ const format = opts.format ?? "text";
486
+ const isKnown = getResourceInfo(resourceType) !== void 0;
487
+ const exitCode = isKnown ? 0 : 2;
488
+ let output;
489
+ if (format === "json") {
490
+ output = buildJsonOutput(resourceType, fhirVersion);
491
+ } else {
492
+ output = buildTextOutput(resourceType, fhirVersion);
493
+ }
494
+ process.stdout.write(output + "\n");
495
+ process.exit(exitCode);
496
+ });
497
+ }
498
+
499
+ // src/cli/commands/list-resources.ts
500
+ var VALID_CATEGORIES = [
501
+ "foundation",
502
+ "base",
503
+ "clinical",
504
+ "financial",
505
+ "specialized",
506
+ "conformance"
507
+ ];
508
+ var CATEGORY_ORDER = [
509
+ "foundation",
510
+ "base",
511
+ "clinical",
512
+ "financial",
513
+ "specialized",
514
+ "conformance"
515
+ ];
516
+ var HL7_RESOURCE_LIST_URL = "https://hl7.org/fhir/resourcelist.html";
517
+ var RESOURCE_TYPE_COL_WIDTH = 24;
518
+ function buildTitle(fhirVersion, category, total) {
519
+ const parts = [];
520
+ if (category) parts.push(category);
521
+ if (fhirVersion) parts.push(fhirVersion);
522
+ const qualifier = parts.length > 0 ? ` \u2014 ${parts.join(", ")}` : "";
523
+ return `FHIR Resource Types${qualifier} (${total} total)`;
524
+ }
525
+ function buildTextOutput2(resources, fhirVersion, category) {
526
+ if (resources.length === 0) {
527
+ return "No resource types match the given filters.";
528
+ }
529
+ const lines = [buildTitle(fhirVersion, category, resources.length), ""];
530
+ if (category) {
531
+ for (const r of resources) {
532
+ lines.push(` ${r.resourceType.padEnd(RESOURCE_TYPE_COL_WIDTH)}${r.description}`);
533
+ }
534
+ } else {
535
+ for (const cat of CATEGORY_ORDER) {
536
+ const group = resources.filter((r) => r.category === cat);
537
+ if (group.length === 0) continue;
538
+ lines.push(cat);
539
+ for (const r of group) {
540
+ lines.push(` ${r.resourceType.padEnd(RESOURCE_TYPE_COL_WIDTH)}${r.description}`);
541
+ }
542
+ lines.push("");
543
+ }
544
+ }
545
+ lines.push(`Full resource list: ${HL7_RESOURCE_LIST_URL}`);
546
+ return lines.join("\n");
547
+ }
548
+ function buildJsonOutput2(resources, fhirVersion, category) {
549
+ const filters = {};
550
+ if (fhirVersion) filters.fhirVersion = fhirVersion;
551
+ if (category) filters.category = category;
552
+ return JSON.stringify(
553
+ {
554
+ total: resources.length,
555
+ filters,
556
+ resources: resources.map((r) => ({
557
+ resourceType: r.resourceType,
558
+ category: r.category,
559
+ versions: [...r.versions],
560
+ description: r.description
561
+ }))
562
+ },
563
+ null,
564
+ 2
565
+ );
566
+ }
567
+ function registerListResourcesCommand(program2) {
568
+ program2.command("list-resources").description(
569
+ "List known FHIR resource types, optionally filtered by version and category"
570
+ ).option(
571
+ "--fhir-version <ver>",
572
+ "Filter to resource types available in a specific version (R4 | R4B | R5)"
573
+ ).option(
574
+ "--category <cat>",
575
+ "Filter by category (foundation | base | clinical | financial | specialized | conformance)"
576
+ ).option("--format <fmt>", "Output format: text | json", "text").action(
577
+ (opts) => {
578
+ const fhirVersion = parseVersionFlag(opts.fhirVersion);
579
+ const format = opts.format ?? "text";
580
+ let category;
581
+ if (opts.category !== void 0) {
582
+ if (!VALID_CATEGORIES.includes(opts.category)) {
583
+ process.stderr.write(
584
+ `Error: Unknown category "${opts.category}". Available: ${VALID_CATEGORIES.join(", ")}
585
+ `
586
+ );
587
+ process.exit(2);
588
+ }
589
+ category = opts.category;
590
+ }
591
+ const resources = listResourceTypes({
592
+ ...fhirVersion !== void 0 && { version: fhirVersion },
593
+ ...category !== void 0 && { category }
594
+ });
595
+ let output;
596
+ if (format === "json") {
597
+ output = buildJsonOutput2(resources, fhirVersion, category);
598
+ } else {
599
+ output = buildTextOutput2(resources, fhirVersion, category);
600
+ }
601
+ process.stdout.write(output + "\n");
602
+ }
603
+ );
604
+ }
605
+
606
+ // src/cli/index.ts
607
+ var program = new Command();
608
+ program.name("fhir-resource-diff").description("CLI for diffing and validating FHIR JSON resources").version("0.1.0");
609
+ registerCompareCommand(program);
610
+ registerValidateCommand(program);
611
+ registerNormalizeCommand(program);
612
+ registerInfoCommand(program);
613
+ registerListResourcesCommand(program);
614
+ program.parse();
615
+ //# sourceMappingURL=index.js.map