From 6c1f31078bb64b9b751e3d519dacd8611e79c4b7 Mon Sep 17 00:00:00 2001 From: Aleksandr Penskoi Date: Mon, 20 Apr 2026 17:10:15 +0200 Subject: [PATCH 1/8] test: Add regression tests for duplicate meta key in createResource (#137) --- .../profile-meta-required.test.ts.snap | 14 +++ examples/local-package-folder/generate.ts | 5 +- .../profile-meta-required.test.ts | 117 ++++++++++++++++++ ...ent-meta-required.structuredefinition.json | 31 +++++ 4 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 examples/local-package-folder/__snapshots__/profile-meta-required.test.ts.snap create mode 100644 examples/local-package-folder/profile-meta-required.test.ts create mode 100644 examples/local-package-folder/structure-definitions/patient-meta-required.structuredefinition.json diff --git a/examples/local-package-folder/__snapshots__/profile-meta-required.test.ts.snap b/examples/local-package-folder/__snapshots__/profile-meta-required.test.ts.snap new file mode 100644 index 00000000..3e35f811 --- /dev/null +++ b/examples/local-package-folder/__snapshots__/profile-meta-required.test.ts.snap @@ -0,0 +1,14 @@ +// Bun Snapshot v1, https://bun.sh/docs/test/snapshots + +exports[`demo: create a patient with required meta create sets meta.profile and preserves caller meta fields 1`] = ` +{ + "meta": { + "lastUpdated": "2024-01-01T00:00:00Z", + "profile": [ + "http://example.org/fhir/StructureDefinition/PatientMetaRequired", + ], + "versionId": "1", + }, + "resourceType": "Patient", +} +`; diff --git a/examples/local-package-folder/generate.ts b/examples/local-package-folder/generate.ts index f6b189ce..cb2b3684 100644 --- a/examples/local-package-folder/generate.ts +++ b/examples/local-package-folder/generate.ts @@ -13,13 +13,16 @@ async function generateFromLocalPackageFolder() { path: Path.join(__dirname, "structure-definitions"), dependencies: [{ name: "hl7.fhir.r4.core", version: "4.0.1" }], }) - .typescript({}) + .typescript({ + generateProfile: true, + }) .throwException(true) .typeSchema({ treeShake: { "example.folder.structures": { "http://example.org/fhir/StructureDefinition/ExampleNotebook": {}, "http://example.org/fhir/StructureDefinition/ExampleTypedBundle": {}, + "http://example.org/fhir/StructureDefinition/PatientMetaRequired": {}, }, "hl7.fhir.r4.core": { "http://hl7.org/fhir/StructureDefinition/Patient": {}, diff --git a/examples/local-package-folder/profile-meta-required.test.ts b/examples/local-package-folder/profile-meta-required.test.ts new file mode 100644 index 00000000..f88fe603 --- /dev/null +++ b/examples/local-package-folder/profile-meta-required.test.ts @@ -0,0 +1,117 @@ +/** + * PatientMetaRequired profile demo — exercises a profile where `meta` is required (min: 1). + * + * Regression test for #137: `createResource()` merges the caller's meta fields + * with the auto-set `meta.profile` array via spread. + */ + +import { describe, expect, test } from "bun:test"; +import { PatientMetaRequiredProfile } from "./fhir-types/example-folder-structures/profiles/Patient_PatientMetaRequired"; +import type { Patient } from "./fhir-types/hl7-fhir-r4-core/Patient"; + +const canonicalUrl = "http://example.org/fhir/StructureDefinition/PatientMetaRequired"; + +describe("demo: create a patient with required meta", () => { + test("create sets meta.profile and preserves caller meta fields", () => { + const profile = PatientMetaRequiredProfile.create({ + meta: { versionId: "1", lastUpdated: "2024-01-01T00:00:00Z" }, + }); + + const resource = profile.toResource(); + + // meta.profile is auto-set by the profile class + expect(resource.meta?.profile).toContain(canonicalUrl); + // Caller-provided meta fields are preserved via spread + expect(resource.meta?.versionId).toBe("1"); + expect(resource.meta?.lastUpdated).toBe("2024-01-01T00:00:00Z"); + + // Valid — meta is present and required field is satisfied + expect(profile.validate().errors).toEqual([]); + expect(resource).toMatchSnapshot(); + }); + + test("createResource also merges meta correctly", () => { + const resource = PatientMetaRequiredProfile.createResource({ + meta: { versionId: "2" }, + }); + + expect(resource.meta?.profile).toContain(canonicalUrl); + expect(resource.meta?.versionId).toBe("2"); + }); +}); + +describe("demo: apply profile to an existing Patient", () => { + test("apply adds meta.profile to a plain Patient", () => { + const patient: Patient = { + resourceType: "Patient", + name: [{ family: "Smith" }], + }; + + const profile = PatientMetaRequiredProfile.apply(patient); + + // apply() mutates the original resource + expect(profile.toResource()).toBe(patient); + expect(patient.meta?.profile).toContain(canonicalUrl); + + // Valid — meta is now present + expect(profile.validate().errors).toEqual([]); + }); + + test("apply preserves existing meta fields", () => { + const patient: Patient = { + resourceType: "Patient", + meta: { versionId: "5", lastUpdated: "2025-06-15T12:00:00Z" }, + name: [{ family: "Doe" }], + }; + + const profile = PatientMetaRequiredProfile.apply(patient); + + // Existing meta fields are preserved + expect(patient.meta?.versionId).toBe("5"); + expect(patient.meta?.lastUpdated).toBe("2025-06-15T12:00:00Z"); + // meta.profile is added alongside existing fields + expect(patient.meta?.profile).toContain(canonicalUrl); + + expect(profile.validate().errors).toEqual([]); + }); +}); + +describe("demo: read a Patient from JSON via from()", () => { + test("from() wraps a valid Patient with meta.profile set", () => { + const json: Patient = { + resourceType: "Patient", + meta: { + profile: [canonicalUrl], + versionId: "3", + }, + name: [{ family: "Jones" }], + }; + + const profile = PatientMetaRequiredProfile.from(json); + + expect(profile.getMeta()?.versionId).toBe("3"); + expect(profile.toResource()).toBe(json); + }); + + test("from() throws when meta.profile is missing", () => { + const json: Patient = { + resourceType: "Patient", + name: [{ family: "Jones" }], + }; + + expect(() => PatientMetaRequiredProfile.from(json)).toThrow(/meta.profile must include/); + }); +}); + +describe("validate", () => { + test("validate reports error when meta is absent", () => { + // Construct directly to bypass factory methods that auto-set meta + const profile = new ( + PatientMetaRequiredProfile as unknown as { + new (r: Patient): PatientMetaRequiredProfile; + } + )({ resourceType: "Patient" }); + + expect(profile.validate().errors).toEqual(["PatientMetaRequired: required field 'meta' is missing"]); + }); +}); diff --git a/examples/local-package-folder/structure-definitions/patient-meta-required.structuredefinition.json b/examples/local-package-folder/structure-definitions/patient-meta-required.structuredefinition.json new file mode 100644 index 00000000..690b12df --- /dev/null +++ b/examples/local-package-folder/structure-definitions/patient-meta-required.structuredefinition.json @@ -0,0 +1,31 @@ +{ + "resourceType": "StructureDefinition", + "id": "patient-meta-required", + "url": "http://example.org/fhir/StructureDefinition/PatientMetaRequired", + "version": "0.0.1", + "name": "PatientMetaRequired", + "title": "Patient Profile with required meta", + "status": "draft", + "fhirVersion": "4.0.1", + "kind": "resource", + "abstract": false, + "type": "Patient", + "baseDefinition": "http://hl7.org/fhir/StructureDefinition/Patient", + "derivation": "constraint", + "differential": { + "element": [ + { + "id": "Patient.meta", + "path": "Patient.meta", + "min": 1, + "mustSupport": true + }, + { + "id": "Patient.meta.profile", + "path": "Patient.meta.profile", + "min": 1, + "mustSupport": true + } + ] + } +} From d932e37bfe87800e2f0c6263f1762238b4607420 Mon Sep 17 00:00:00 2001 From: Aleksandr Penskoi Date: Mon, 20 Apr 2026 17:10:18 +0200 Subject: [PATCH 2/8] chore: Update fhir-canonical-manager to 0.0.23 --- bun.lock | 4 ++-- package.json | 20 +------------------- 2 files changed, 3 insertions(+), 21 deletions(-) diff --git a/bun.lock b/bun.lock index e04df3b5..25715b7d 100644 --- a/bun.lock +++ b/bun.lock @@ -5,7 +5,7 @@ "": { "name": "@atomic-ehr/codegen", "dependencies": { - "@atomic-ehr/fhir-canonical-manager": "0.0.22", + "@atomic-ehr/fhir-canonical-manager": "0.0.23", "@atomic-ehr/fhirschema": "0.0.11", "mustache": "^4.2.0", "picocolors": "^1.1.1", @@ -32,7 +32,7 @@ "smol-toml": ">=1.6.1", }, "packages": { - "@atomic-ehr/fhir-canonical-manager": ["@atomic-ehr/fhir-canonical-manager@0.0.22", "", { "peerDependencies": { "typescript": "^5" }, "bin": { "fcm": "dist/cli/index.js" } }, "sha512-pX+glAxQPKs13AV/ZQgLCsKK9q79kBb1ft2xdt9I8EmuooLwNCHJgDz1xsTEVW6y34b680dtAYAvVKejxtn5dw=="], + "@atomic-ehr/fhir-canonical-manager": ["@atomic-ehr/fhir-canonical-manager@0.0.23", "", { "peerDependencies": { "typescript": "^5" }, "bin": { "fcm": "dist/cli/index.js" } }, "sha512-16VE1YEFRhF7bJRAnHGeFhLFnMip2wwJTyKq8UJcr0iJxv8KErEoGU5fvVnM6rHWvzCGaFRWxeMq1xTStYfphA=="], "@atomic-ehr/fhirschema": ["@atomic-ehr/fhirschema@0.0.11", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-oMNxhncEGspGI+QlK/FPjc7akLbfwMYw/hDfW6SbO8xF1KvSSH7NWqc3CJg/k5/309ZuJ6lKsHkgmgVDxo80sQ=="], diff --git a/package.json b/package.json index 59621896..732d03d8 100644 --- a/package.json +++ b/package.json @@ -29,29 +29,11 @@ "assets" ], "scripts": { - "test": "bun test", - "test:short-logs": "FORCE_COLOR=true bun test 2>&1 | grep -v '✓' | grep -v '^$' | grep -v '(pass)'", - "test:unit": "bun test test/unit/", - "test:integration": "bun test test/integration/", - "test:performance": "bun test test/performance/", - "test:watch": "bun test --watch", - "test:coverage": "bun test --coverage", - "test:ci": "bun test --coverage --bail", - "test:verbose": "bun test --verbose", - "test:helpers": "bun test test/helpers/", - "test:quick": "bun test test/unit/ --timeout 5000", "build": "tsup", "typecheck": "bunx tsc --noEmit", "lint": "biome check", - "lint:fix": "biome check --write", - "lint:fix:unsafe": "biome check --write --unsafe", - "format": "biome format --write", - "quality": "bun run typecheck && bun run lint && bun run test:unit", "cli": "bun run src/cli/index.ts", - "codegen": "bun run src/cli/index.ts", - "codegen:all": "bun run src/cli/index.ts generate", "prune": "knip", - "prune:strict": "knip --strict", "prune:fix": "knip --fix", "release": "bash ./scripts/release.sh" }, @@ -66,7 +48,7 @@ }, "homepage": "https://github.com/atomic-ehr/codegen#readme", "dependencies": { - "@atomic-ehr/fhir-canonical-manager": "0.0.22", + "@atomic-ehr/fhir-canonical-manager": "0.0.23", "@atomic-ehr/fhirschema": "0.0.11", "mustache": "^4.2.0", "picocolors": "^1.1.1", From 7a0c41a9583fd1beb374b00d2022f0912312bc25 Mon Sep 17 00:00:00 2001 From: Aleksandr Penskoi Date: Mon, 20 Apr 2026 17:10:21 +0200 Subject: [PATCH 3/8] ref: Refactor APIBuilder options filtering and add ignorePackageIndex --- src/api/builder.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/api/builder.ts b/src/api/builder.ts index 0ecf033f..93987e0b 100644 --- a/src/api/builder.ts +++ b/src/api/builder.ts @@ -125,6 +125,7 @@ export class APIBuilder { manager?: ReturnType; register?: Register; preprocessPackage?: (context: PreprocessContext) => PreprocessContext; + ignorePackageIndex?: boolean; logger?: CodegenLogManager; } = {}, ) { @@ -135,18 +136,17 @@ export class APIBuilder { registry: undefined, dropCanonicalManagerCache: false, }; + const apiBuilderKeys: (keyof APIBuilderOptions)[] = [ + "outputDir", + "cleanOutput", + "throwException", + "typeSchema", + "registry", + "dropCanonicalManagerCache", + ]; const opts: APIBuilderOptions = { ...defaultOpts, - ...Object.fromEntries( - Object.entries(userOpts).filter( - ([k, v]) => - v !== undefined && - k !== "manager" && - k !== "register" && - k !== "preprocessPackage" && - k !== "logger", - ), - ), + ...Object.fromEntries(apiBuilderKeys.filter((k) => userOpts[k] !== undefined).map((k) => [k, userOpts[k]])), }; if (userOpts.manager && userOpts.register) { @@ -167,6 +167,7 @@ export class APIBuilder { registry: userOpts.registry, dropCache: userOpts.dropCanonicalManagerCache, preprocessPackage: userOpts.preprocessPackage, + ignorePackageIndex: userOpts.ignorePackageIndex, }); this.logger = userOpts.logger ?? mkLogger({ prefix: "api" }); this.options = opts; From cd733fab43a4f4e2bb5790f40fd6480c52d51c09 Mon Sep 17 00:00:00 2001 From: Aleksandr Penskoi Date: Mon, 20 Apr 2026 17:10:44 +0200 Subject: [PATCH 4/8] ref: Move norge-r4 and add KBV R4 to examples/on-the-fly/ --- .gitignore | 1 - biome.json | 1 + examples/README.md | 12 +++ examples/on-the-fly/kbv-r4/.gitignore | 1 + examples/on-the-fly/kbv-r4/generate.ts | 50 ++++++++++++ .../on-the-fly/kbv-r4/profile-patient.test.ts | 81 +++++++++++++++++++ examples/on-the-fly/kbv-r4/tsconfig.json | 4 + examples/on-the-fly/norge-r4/.gitignore | 1 + .../norge-r4/generate.ts} | 11 +-- examples/on-the-fly/norge-r4/tsconfig.json | 4 + 10 files changed, 158 insertions(+), 8 deletions(-) create mode 100644 examples/on-the-fly/kbv-r4/.gitignore create mode 100644 examples/on-the-fly/kbv-r4/generate.ts create mode 100644 examples/on-the-fly/kbv-r4/profile-patient.test.ts create mode 100644 examples/on-the-fly/kbv-r4/tsconfig.json create mode 100644 examples/on-the-fly/norge-r4/.gitignore rename examples/{nodge-r4.ts => on-the-fly/norge-r4/generate.ts} (92%) create mode 100644 examples/on-the-fly/norge-r4/tsconfig.json diff --git a/.gitignore b/.gitignore index 51b1f35a..9ebcbc4c 100644 --- a/.gitignore +++ b/.gitignore @@ -42,4 +42,3 @@ test-output /generated .typeschema-cache *.cpuprofile -examples/tmp/ diff --git a/biome.json b/biome.json index 99e2e879..6cdfa6f9 100644 --- a/biome.json +++ b/biome.json @@ -16,6 +16,7 @@ "biome.json", "package.json", "examples/*/*.ts", + "examples/*/*/*.ts", "examples/*.ts", "assets/api/writer-generator/**/*.ts", ".zed/*.json" diff --git a/examples/README.md b/examples/README.md index 8371de6d..41930b3f 100644 --- a/examples/README.md +++ b/examples/README.md @@ -58,6 +58,18 @@ This directory contains working examples demonstrating the capabilities of Atomi - Demonstrates dependency resolution with FHIR R4 core - Shows tree shaking for custom logical models +### On-the-Fly Generation + +These examples pull packages from the FHIR registry and generate types on-the-fly (generated types are gitignored and regenerated on CI). + +- **[on-the-fly/norge-r4/](on-the-fly/norge-r4/)** - Norwegian FHIR profiles (Grunndata, no-basis, SFM) + - `generate.ts` - Fetches multiple Norwegian FHIR packages from Simplifier + - Demonstrates `preprocessPackage` for fixing package metadata and dependency injection + +- **[on-the-fly/kbv-r4/](on-the-fly/kbv-r4/)** - German KBV profiles (kbv.ita.for) + - `generate.ts` - Fetches KBV packages with `ignorePackageIndex: true` for corrupt package indices + - `profile-patient.test.ts` - Regression test for `meta` merge in profiles with required `meta` (#137) + ## Running Examples Each example contains a `generate.ts` script that can be run with: diff --git a/examples/on-the-fly/kbv-r4/.gitignore b/examples/on-the-fly/kbv-r4/.gitignore new file mode 100644 index 00000000..eca62015 --- /dev/null +++ b/examples/on-the-fly/kbv-r4/.gitignore @@ -0,0 +1 @@ +fhir-types/ diff --git a/examples/on-the-fly/kbv-r4/generate.ts b/examples/on-the-fly/kbv-r4/generate.ts new file mode 100644 index 00000000..d10b1980 --- /dev/null +++ b/examples/on-the-fly/kbv-r4/generate.ts @@ -0,0 +1,50 @@ +import type { PreprocessContext } from "@atomic-ehr/fhir-canonical-manager"; +import { APIBuilder, prettyReport } from "../../../src/api/builder"; + +const preprocessPackage = (ctx: PreprocessContext): PreprocessContext => { + if (ctx.kind !== "package") return ctx; + const json = ctx.packageJson; + const name = json.name as string; + + // de.basisprofil.r4 doesn't declare hl7.fhir.r4.core as a dependency + if (name === "de.basisprofil.r4") { + const deps = (json.dependencies as Record) || {}; + if (!deps["hl7.fhir.r4.core"]) { + return { + ...ctx, + kind: "package", + packageJson: { + ...json, + dependencies: { ...deps, "hl7.fhir.r4.core": "4.0.1" }, + }, + }; + } + } + + return ctx; +}; + +if (require.main === module) { + console.log("Generating KBV R4 types..."); + + const builder = new APIBuilder({ + preprocessPackage, + registry: "https://packages.simplifier.net", + ignorePackageIndex: true, + }) + .fromPackage("hl7.fhir.r4.core", "4.0.1") + .fromPackage("kbv.ita.for", "1.3.1") + .throwException() + .typescript({ + withDebugComment: false, + generateProfile: true, + openResourceTypeSet: false, + }) + .typeSchema({}) + .outputTo("./examples/on-the-fly/kbv-r4/fhir-types") + .cleanOutput(true); + + const report = await builder.generate(); + console.log(prettyReport(report)); + if (!report.success) process.exit(1); +} diff --git a/examples/on-the-fly/kbv-r4/profile-patient.test.ts b/examples/on-the-fly/kbv-r4/profile-patient.test.ts new file mode 100644 index 00000000..a2665c16 --- /dev/null +++ b/examples/on-the-fly/kbv-r4/profile-patient.test.ts @@ -0,0 +1,81 @@ +/** + * KBV_PR_FOR_Patient profile — regression test for duplicate meta key (#137). + * + * KBV profiles require `meta` (min: 1). The codegen now merges caller meta + * with the auto-set meta.profile via spread. + */ + +import { describe, expect, test } from "bun:test"; +import type { Patient } from "./fhir-types/hl7-fhir-r4-core/Patient"; +import { KBV_PR_FOR_PatientProfile } from "./fhir-types/kbv-ita-for/profiles/Patient_KBV_PR_FOR_Patient"; + +const canonicalUrl = "https://fhir.kbv.de/StructureDefinition/KBV_PR_FOR_Patient"; + +describe("demo: KBV Patient with required meta", () => { + test("create preserves caller meta fields alongside meta.profile", () => { + const profile = KBV_PR_FOR_PatientProfile.create({ + name: [{ family: "Müller", given: ["Hans"] }], + birthDate: "1990-01-15", + id: "patient-1", + meta: { versionId: "1", lastUpdated: "2024-01-01T00:00:00Z" }, + }); + + const resource = profile.toResource(); + expect(resource.meta?.profile).toContain(canonicalUrl); + expect(resource.meta?.versionId).toBe("1"); + expect(resource.meta?.lastUpdated).toBe("2024-01-01T00:00:00Z"); + }); + + test("createResource merges meta correctly", () => { + const resource = KBV_PR_FOR_PatientProfile.createResource({ + name: [{ family: "Schmidt" }], + birthDate: "1985-06-20", + id: "patient-2", + meta: { versionId: "2" }, + }); + + expect(resource.meta?.profile).toContain(canonicalUrl); + expect(resource.meta?.versionId).toBe("2"); + }); +}); + +describe("demo: apply and from work correctly", () => { + test("apply adds meta.profile to an existing Patient", () => { + const patient: Patient = { + resourceType: "Patient", + name: [{ family: "Weber" }], + birthDate: "2000-03-10", + }; + + const profile = KBV_PR_FOR_PatientProfile.apply(patient); + + expect(profile.toResource()).toBe(patient); + expect(patient.meta?.profile).toContain(canonicalUrl); + }); + + test("from wraps a valid KBV Patient", () => { + const json: Patient = { + resourceType: "Patient", + id: "patient-3", + meta: { profile: [canonicalUrl], versionId: "3" }, + name: [{ family: "Fischer", given: ["Anna"] }], + birthDate: "1975-11-30", + }; + + const profile = KBV_PR_FOR_PatientProfile.from(json); + + expect(profile.getMeta()?.versionId).toBe("3"); + expect(profile.getName()).toEqual([{ family: "Fischer", given: ["Anna"] }]); + expect(profile.getBirthDate()).toBe("1975-11-30"); + }); + + test("from throws when meta.profile is missing", () => { + const json: Patient = { + resourceType: "Patient", + name: [{ family: "Koch" }], + birthDate: "1960-01-01", + }; + + expect(() => KBV_PR_FOR_PatientProfile.from(json)).toThrow(/meta.profile must include/); + }); +}); diff --git a/examples/on-the-fly/kbv-r4/tsconfig.json b/examples/on-the-fly/kbv-r4/tsconfig.json new file mode 100644 index 00000000..808dfaf4 --- /dev/null +++ b/examples/on-the-fly/kbv-r4/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../../tsconfig.json", + "include": ["."] +} diff --git a/examples/on-the-fly/norge-r4/.gitignore b/examples/on-the-fly/norge-r4/.gitignore new file mode 100644 index 00000000..eca62015 --- /dev/null +++ b/examples/on-the-fly/norge-r4/.gitignore @@ -0,0 +1 @@ +fhir-types/ diff --git a/examples/nodge-r4.ts b/examples/on-the-fly/norge-r4/generate.ts similarity index 92% rename from examples/nodge-r4.ts rename to examples/on-the-fly/norge-r4/generate.ts index e3c97283..e805a21a 100644 --- a/examples/nodge-r4.ts +++ b/examples/on-the-fly/norge-r4/generate.ts @@ -1,8 +1,5 @@ -// Run this script using Bun CLI with: -// bun run scripts/generate-fhir-types.ts - import type { PreprocessContext } from "@atomic-ehr/fhir-canonical-manager"; -import { APIBuilder, prettyReport } from "../src/api/builder"; +import { APIBuilder, prettyReport } from "../../../src/api/builder"; // Fix known package name typos (in-memory transformation) const packageNameFixes: Record = { @@ -66,11 +63,11 @@ const preprocessPackage = (ctx: PreprocessContext): PreprocessContext => { } } - return { kind: "package", packageJson: json }; + return { ...ctx, kind: "package", packageJson: json }; }; if (require.main === module) { - console.log("📦 Generating FHIR R4 Core Types..."); + console.log("Generating Norge R4 types..."); const builder = new APIBuilder({ preprocessPackage, @@ -93,7 +90,7 @@ if (require.main === module) { fhirSchemas: "fhir-schemas", structureDefinitions: "structure-definitions", }) - .outputTo("./examples/tmp/norge-r4") + .outputTo("./examples/on-the-fly/norge-r4/fhir-types") .cleanOutput(true); const report = await builder.generate(); diff --git a/examples/on-the-fly/norge-r4/tsconfig.json b/examples/on-the-fly/norge-r4/tsconfig.json new file mode 100644 index 00000000..808dfaf4 --- /dev/null +++ b/examples/on-the-fly/norge-r4/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../../tsconfig.json", + "include": ["."] +} From 53bf99d0f35f121011a264407ac152e63c0e7946 Mon Sep 17 00:00:00 2001 From: Aleksandr Penskoi Date: Mon, 20 Apr 2026 17:10:50 +0200 Subject: [PATCH 5/8] ci: Add example CI jobs, simplify workflows and scripts --- .github/workflows/ci.yml | 18 ++----- .github/workflows/release.yml | 22 +++----- .github/workflows/sdk-tests.yml | 90 +++++++++++++-------------------- .zed/tasks.json | 12 +---- CLAUDE.md | 2 +- Makefile | 56 ++++++++++---------- 6 files changed, 80 insertions(+), 120 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ef612aa4..8e407065 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,19 +16,17 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 - with: - bun-version: latest - name: Install dependencies run: bun install --frozen-lockfile - - name: Run linting - run: bun run lint + - name: Lint + run: make lint - - name: Run tests - run: bun run test:ci + - name: Test + run: make test - - name: Build project + - name: Build run: bun run build security: @@ -39,8 +37,6 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 - with: - bun-version: latest - name: Install dependencies run: bun install --frozen-lockfile @@ -56,8 +52,6 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 - with: - bun-version: latest - name: Build and pack run: | @@ -89,8 +83,6 @@ jobs: - name: Setup Bun if: matrix.package-manager == 'bun' uses: oven-sh/setup-bun@v2 - with: - bun-version: latest - name: Setup Node.js if: matrix.package-manager != 'bun' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 13c7d62e..3f6b3980 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,17 +25,12 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 - with: - bun-version: latest - name: Install dependencies - run: bun install - - - name: Run tests - run: bun test + run: bun install --frozen-lockfile - - name: Run typecheck - run: bun run typecheck + - name: Test + run: make test - name: Build run: bun run build @@ -81,17 +76,12 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 - with: - bun-version: latest - name: Install dependencies - run: bun install - - - name: Run tests - run: bun test + run: bun install --frozen-lockfile - - name: Run typecheck - run: bun run typecheck + - name: Test + run: make test - name: Build run: bun run build diff --git a/.github/workflows/sdk-tests.yml b/.github/workflows/sdk-tests.yml index d05ae8bc..3c04dc45 100644 --- a/.github/workflows/sdk-tests.yml +++ b/.github/workflows/sdk-tests.yml @@ -10,23 +10,16 @@ jobs: test-csharp-sdk: runs-on: ubuntu-latest - strategy: - matrix: - bun-version: [latest] - dotnet-version: [8.0.x] - steps: - uses: actions/checkout@v5 - name: Setup Bun uses: oven-sh/setup-bun@v2 - with: - bun-version: ${{ matrix.bun-version }} - - name: Setup .NET ${{ matrix.dotnet-version }} + - name: Setup .NET uses: actions/setup-dotnet@v5 with: - dotnet-version: ${{ matrix.dotnet-version }} + dotnet-version: 8.0.x - name: Install dependencies run: bun install --frozen-lockfile @@ -52,20 +45,11 @@ jobs: test-typescript-sdk-r4: runs-on: ubuntu-latest - strategy: - matrix: - bun-version: [latest] - steps: - uses: actions/checkout@v5 - - name: Checkout code - uses: actions/checkout@v5 - - name: Setup Bun uses: oven-sh/setup-bun@v2 - with: - bun-version: ${{ matrix.bun-version }} - name: Install dependencies run: bun install --frozen-lockfile @@ -89,17 +73,11 @@ jobs: test-typescript-us-core-example: runs-on: ubuntu-latest - strategy: - matrix: - bun-version: [latest] - steps: - uses: actions/checkout@v5 - name: Setup Bun uses: oven-sh/setup-bun@v2 - with: - bun-version: ${{ matrix.bun-version }} - name: Install dependencies run: bun install --frozen-lockfile @@ -124,20 +102,11 @@ jobs: test-typescript-ccda-example: runs-on: ubuntu-latest - strategy: - matrix: - bun-version: [latest] - steps: - uses: actions/checkout@v5 - - name: Checkout code - uses: actions/checkout@v5 - - name: Setup Bun uses: oven-sh/setup-bun@v2 - with: - bun-version: ${{ matrix.bun-version }} - name: Install dependencies run: bun install --frozen-lockfile @@ -145,25 +114,18 @@ jobs: - name: Run tests run: make test-typescript-ccda-example - test-python-sdk-test: + test-python-sdk: runs-on: ubuntu-latest - strategy: - matrix: - bun-version: [ latest ] - python-version: [ "3.13" ] - steps: - uses: actions/checkout@v5 - name: Setup Bun uses: oven-sh/setup-bun@v2 - with: - bun-version: ${{ matrix.bun-version }} - uses: actions/setup-python@v6 with: - python-version: ${{ matrix.python-version }} + python-version: "3.13" - name: Install dependencies run: bun install --frozen-lockfile @@ -186,25 +148,18 @@ jobs: exit 1 fi - test-python-fhirpy-sdk-test: + test-python-fhirpy-sdk: runs-on: ubuntu-latest - strategy: - matrix: - bun-version: [ latest ] - python-version: [ "3.13" ] - steps: - uses: actions/checkout@v5 - name: Setup Bun uses: oven-sh/setup-bun@v2 - with: - bun-version: ${{ matrix.bun-version }} - uses: actions/setup-python@v6 with: - python-version: ${{ matrix.python-version }} + python-version: "3.13" - name: Install dependencies run: bun install --frozen-lockfile @@ -227,12 +182,41 @@ jobs: exit 1 fi + test-local-package-folder-example: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v5 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Run tests + run: make test-local-package-folder-example + + test-on-the-fly-example: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v5 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Run tests + run: make test-on-the-fly-example + test-mustache-java-r4-example: runs-on: ubuntu-latest strategy: matrix: - bun-version: [latest] java-version: [21, 25] steps: @@ -240,8 +224,6 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 - with: - bun-version: ${{ matrix.bun-version }} - name: Setup Java uses: actions/setup-java@v5 diff --git a/.zed/tasks.json b/.zed/tasks.json index c0da7e09..11a32b72 100644 --- a/.zed/tasks.json +++ b/.zed/tasks.json @@ -1,15 +1,7 @@ [ { - "label": "test-typeschema", - "command": "make test-typeschema" - }, - { - "label": "test-register", - "command": "make test-register" - }, - { - "label": "test-codegen", - "command": "make test-codegen" + "label": "test", + "command": "make test" }, { "label": "test-ts-r4-example", diff --git a/CLAUDE.md b/CLAUDE.md index d1ad5d08..2b5a0a85 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -361,7 +361,7 @@ Detection uses `mkIsFamilyType(tsIndex)` which checks `schema.typeFamily.resourc 2. Enable caching in APIBuilder 3. Process large packages in batches 4. Use `build()` instead of `generate()` for testing -5. Run `bun run quality` before committing (combines typecheck, lint, test:unit) +5. Run `make test` before committing (typecheck + tests) ## Useful External Resources diff --git a/Makefile b/Makefile index b178aeb8..d01e7f41 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,19 @@ AIDBOX_LICENSE_ID ?= TYPECHECK = bunx tsc --noEmit -FORMAT = bunx biome format --write -LINT = bunx biome check --write +LINT = bunx biome check TEST = bun test VERSION = $(shell cat package.json | grep version | sed -E 's/ *"version": "//' | sed -E 's/",.*//') -.PHONY: all typecheck test-typeschema test-register test-codegen test-typescript-r4-example test-local-package-folder-example +.PHONY: all generate-types lint lint-fix lint-unsafe typecheck \ + test \ + prepare-aidbox-runme test-all-example-generation test-other-example-generation test-on-the-fly-example \ + test-typescript-r4-example test-typescript-us-core-example test-typescript-sql-on-fhir-example \ + test-typescript-ccda-example test-local-package-folder-example test-mustache-java-r4-example \ + test-csharp-sdk generate-python-sdk generate-python-sdk-fhirpy \ + python-test-setup python-fhirpy-test-setup test-python-sdk test-python-fhirpy-sdk -all: test-codegen test-typescript-r4-example test-typescript-us-core-example test-typescript-ccda-example test-typescript-sql-on-fhir-example test-local-package-folder-example lint-unsafe test-all-example-generation +all: test test-typescript-r4-example test-typescript-us-core-example test-typescript-ccda-example test-typescript-sql-on-fhir-example test-local-package-folder-example lint-unsafe test-all-example-generation generate-types: bun run scripts/generate-types.ts @@ -16,22 +21,16 @@ generate-types: lint: $(LINT) +lint-fix: + bunx biome check --write + lint-unsafe: - bunx biome lint --write --unsafe + bunx biome check --write --unsafe typecheck: $(TYPECHECK) -format: - $(FORMAT) - -test-typeschema: typecheck format lint - $(TEST) typeschema - -test-register: typecheck format lint - $(TEST) register - -test-codegen: typecheck format lint +test: typecheck $(TEST) prepare-aidbox-runme: @@ -57,22 +56,27 @@ test-all-example-generation: test-other-example-generation bun run examples/typescript-sql-on-fhir/generate.ts bun run examples/typescript-us-core/generate.ts -test-other-example-generation: - bun run examples/nodge-r4.ts - echo '{ "extends": "../../tsconfig.json", "include": ["."] }' > examples/tmp/tsconfig.json - $(TYPECHECK) --project examples/tmp/tsconfig.json +test-other-example-generation: test-on-the-fly-example + +test-on-the-fly-example: typecheck + bun run examples/on-the-fly/norge-r4/generate.ts + $(TYPECHECK) --project examples/on-the-fly/norge-r4/tsconfig.json + $(TEST) ./examples/on-the-fly/norge-r4/ + bun run examples/on-the-fly/kbv-r4/generate.ts + $(TYPECHECK) --project examples/on-the-fly/kbv-r4/tsconfig.json + $(TEST) ./examples/on-the-fly/kbv-r4/ -test-typescript-r4-example: typecheck format lint +test-typescript-r4-example: typecheck bun run examples/typescript-r4/generate.ts $(TYPECHECK) --project examples/typescript-r4/tsconfig.json $(TEST) ./examples/typescript-r4/ -test-typescript-us-core-example: typecheck format lint +test-typescript-us-core-example: typecheck bun run examples/typescript-us-core/generate.ts $(TYPECHECK) --project examples/typescript-us-core/tsconfig.json $(TEST) ./examples/typescript-us-core/ -test-typescript-sql-on-fhir-example: typecheck format lint +test-typescript-sql-on-fhir-example: typecheck bun run examples/typescript-sql-on-fhir/generate.ts $(TYPECHECK) --project examples/typescript-sql-on-fhir/tsconfig.json @@ -89,11 +93,11 @@ test-local-package-folder-example: typecheck $(TYPECHECK) --project examples/local-package-folder/tsconfig.json $(TEST) ./examples/local-package-folder/ -test-mustache-java-r4-example: typecheck format lint +test-mustache-java-r4-example: typecheck bun run examples/mustache/mustache-java-r4-gen.ts $(TYPECHECK) --project examples/mustache/tsconfig.examples-mustache.json -test-csharp-sdk: typecheck format prepare-aidbox-runme lint +test-csharp-sdk: typecheck prepare-aidbox-runme $(TYPECHECK) --project examples/csharp/tsconfig.json bun run examples/csharp/generate.ts cd examples/csharp && dotnet restore @@ -129,7 +133,7 @@ python-fhirpy-test-setup: pip install fhirpy; \ fi -test-python-sdk: typecheck format prepare-aidbox-runme lint generate-python-sdk python-test-setup +test-python-sdk: typecheck prepare-aidbox-runme generate-python-sdk python-test-setup # Run mypy in strict mode cd $(PYTHON_EXAMPLE) && \ . venv/bin/activate && \ @@ -143,7 +147,7 @@ test-python-sdk: typecheck format prepare-aidbox-runme lint generate-python-sdk . venv/bin/activate && \ python -m pytest test_raw_extension.py -v -test-python-fhirpy-sdk: typecheck format prepare-aidbox-runme lint generate-python-sdk-fhirpy python-fhirpy-test-setup +test-python-fhirpy-sdk: typecheck prepare-aidbox-runme generate-python-sdk-fhirpy python-fhirpy-test-setup # Run mypy in strict mode cd $(PYTHON_FHIRPY_EXAMPLE) && \ . venv/bin/activate && \ From 3084ee42b9fb2be2aeb87ae1a6538f6c8cab513b Mon Sep 17 00:00:00 2001 From: Aleksandr Penskoi Date: Mon, 20 Apr 2026 17:14:13 +0200 Subject: [PATCH 6/8] =?UTF-8?q?ref(make):=20Inline=20TEST=20variable=20?= =?UTF-8?q?=E2=80=94=20replace=20$(TEST)=20with=20bun=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makefile | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index d01e7f41..7956773e 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,7 @@ AIDBOX_LICENSE_ID ?= TYPECHECK = bunx tsc --noEmit -LINT = bunx biome check -TEST = bun test + VERSION = $(shell cat package.json | grep version | sed -E 's/ *"version": "//' | sed -E 's/",.*//') .PHONY: all generate-types lint lint-fix lint-unsafe typecheck \ @@ -19,7 +18,7 @@ generate-types: bun run scripts/generate-types.ts lint: - $(LINT) + bunx biome check lint-fix: bunx biome check --write @@ -31,7 +30,7 @@ typecheck: $(TYPECHECK) test: typecheck - $(TEST) + bun test prepare-aidbox-runme: @if [ ! -f "example/docker-compose.yaml" ]; then \ @@ -61,37 +60,37 @@ test-other-example-generation: test-on-the-fly-example test-on-the-fly-example: typecheck bun run examples/on-the-fly/norge-r4/generate.ts $(TYPECHECK) --project examples/on-the-fly/norge-r4/tsconfig.json - $(TEST) ./examples/on-the-fly/norge-r4/ + bun test ./examples/on-the-fly/norge-r4/ bun run examples/on-the-fly/kbv-r4/generate.ts $(TYPECHECK) --project examples/on-the-fly/kbv-r4/tsconfig.json - $(TEST) ./examples/on-the-fly/kbv-r4/ + bun test ./examples/on-the-fly/kbv-r4/ test-typescript-r4-example: typecheck bun run examples/typescript-r4/generate.ts $(TYPECHECK) --project examples/typescript-r4/tsconfig.json - $(TEST) ./examples/typescript-r4/ + bun test ./examples/typescript-r4/ test-typescript-us-core-example: typecheck bun run examples/typescript-us-core/generate.ts $(TYPECHECK) --project examples/typescript-us-core/tsconfig.json - $(TEST) ./examples/typescript-us-core/ + bun test ./examples/typescript-us-core/ test-typescript-sql-on-fhir-example: typecheck bun run examples/typescript-sql-on-fhir/generate.ts $(TYPECHECK) --project examples/typescript-sql-on-fhir/tsconfig.json test-typescript-ccda-example: typecheck - $(TEST) test/unit/typeschema/transformer/ccda.test.ts + bun test test/unit/typeschema/transformer/ccda.test.ts bun run examples/typescript-ccda/generate.ts $(TYPECHECK) --project examples/typescript-ccda/tsconfig.json - $(TEST) --project examples/typescript-ccda/tsconfig.json \ + bun test --project examples/typescript-ccda/tsconfig.json \ ./examples/typescript-ccda/demo-cda.test.ts \ ./examples/typescript-ccda/demo-ccda.test.ts test-local-package-folder-example: typecheck bun run examples/local-package-folder/generate.ts $(TYPECHECK) --project examples/local-package-folder/tsconfig.json - $(TEST) ./examples/local-package-folder/ + bun test ./examples/local-package-folder/ test-mustache-java-r4-example: typecheck bun run examples/mustache/mustache-java-r4-gen.ts From 2408eb5089d61f108838126b776b7cd4811bc944 Mon Sep 17 00:00:00 2001 From: Aleksandr Penskoi Date: Mon, 20 Apr 2026 17:19:46 +0200 Subject: [PATCH 7/8] ref(make): Split test-on-the-fly-example into per-package targets --- Makefile | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 7956773e..6bcf8fb9 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,8 @@ VERSION = $(shell cat package.json | grep version | sed -E 's/ *"version": "//' .PHONY: all generate-types lint lint-fix lint-unsafe typecheck \ test \ - prepare-aidbox-runme test-all-example-generation test-other-example-generation test-on-the-fly-example \ + prepare-aidbox-runme test-all-example-generation test-other-example-generation \ + test-on-the-fly-example test-on-the-fly-norge-r4 test-on-the-fly-kbv-r4 \ test-typescript-r4-example test-typescript-us-core-example test-typescript-sql-on-fhir-example \ test-typescript-ccda-example test-local-package-folder-example test-mustache-java-r4-example \ test-csharp-sdk generate-python-sdk generate-python-sdk-fhirpy \ @@ -57,10 +58,14 @@ test-all-example-generation: test-other-example-generation test-other-example-generation: test-on-the-fly-example -test-on-the-fly-example: typecheck +test-on-the-fly-example: test-on-the-fly-norge-r4 test-on-the-fly-kbv-r4 + +test-on-the-fly-norge-r4: typecheck bun run examples/on-the-fly/norge-r4/generate.ts $(TYPECHECK) --project examples/on-the-fly/norge-r4/tsconfig.json bun test ./examples/on-the-fly/norge-r4/ + +test-on-the-fly-kbv-r4: typecheck bun run examples/on-the-fly/kbv-r4/generate.ts $(TYPECHECK) --project examples/on-the-fly/kbv-r4/tsconfig.json bun test ./examples/on-the-fly/kbv-r4/ From 0206b8cd5bb877af965ae4dd7212eeba60d327a9 Mon Sep 17 00:00:00 2001 From: Aleksandr Penskoi Date: Mon, 20 Apr 2026 17:19:53 +0200 Subject: [PATCH 8/8] ref: Remove cli script from package.json --- CLAUDE.md | 6 +++--- package.json | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 2b5a0a85..121b230a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -15,11 +15,11 @@ bun run lint # Lint and format code with Biome # Building bun run build # Build the project (creates dist/) -bun run cli # Run CLI in development mode +bun run src/cli/index.ts # Run CLI in development mode # CLI Usage -bun run cli typeschema generate hl7.fhir.r4.core@4.0.1 -o schemas.ndjson -bun run cli generate typescript -i schemas.ndjson -o ./types +bun run src/cli/index.ts typeschema generate hl7.fhir.r4.core@4.0.1 -o schemas.ndjson +bun run src/cli/index.ts generate typescript -i schemas.ndjson -o ./types ``` ## Verification diff --git a/package.json b/package.json index 732d03d8..c1925a0c 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,6 @@ "build": "tsup", "typecheck": "bunx tsc --noEmit", "lint": "biome check", - "cli": "bun run src/cli/index.ts", "prune": "knip", "prune:fix": "knip --fix", "release": "bash ./scripts/release.sh"