From a6a2ab81c155a90f134e3174969bcb882988609a Mon Sep 17 00:00:00 2001 From: Chris Moesel Date: Fri, 11 Oct 2024 11:47:52 -0400 Subject: [PATCH] WIP --- .tool-versions | 2 +- broken-repos.txt | 33 + .../fhir-package-loader-1.0.0.tgz | Bin 0 -> 28130 bytes npm-shrinkwrap.json | 437 ++++++++++--- package.json | 3 +- regression/find.ts | 2 +- regression/run.ts | 2 +- src/app.ts | 24 +- src/export/StructureDefinitionExporter.ts | 2 +- src/fhirdefs/BaseFHIRDefinitions.ts | 600 ++++++++++++++++++ src/fhirdefs/FHIRDefinitions.ts | 162 +++-- src/fhirdefs/index.ts | 1 + src/fhirdefs/load.ts | 101 +-- src/fhirtypes/common.ts | 40 +- src/ig/IGExporter.ts | 354 +++++------ src/utils/Fishable.ts | 1 + src/utils/Processing.ts | 58 +- test/fhirdefs/FHIRDefinitions.test.ts | 12 - tsconfig.json | 8 +- 19 files changed, 1306 insertions(+), 536 deletions(-) create mode 100644 broken-repos.txt create mode 100644 local-dependencies/fhir-package-loader-1.0.0.tgz create mode 100644 src/fhirdefs/BaseFHIRDefinitions.ts diff --git a/.tool-versions b/.tool-versions index 8f2e342a2..6df261cac 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -nodejs 18.18.0 +nodejs 18.20.1 diff --git a/broken-repos.txt b/broken-repos.txt new file mode 100644 index 000000000..b4632614f --- /dev/null +++ b/broken-repos.txt @@ -0,0 +1,33 @@ +WorldHealthOrganization/ddcc#main +ehealthsuisse/ch-epr-fhir#master +HL7/davinci-pas#master +HL7/physical-activity#master +WorldHealthOrganization/smart-ips-pilgrimage#main +medizininformatik-initiative/kerndatensatz-bildgebung#main +TEHIK-EE/ig-ee-medication-scheme#master +aehrc/smart-forms-ig#master +ahdis/ch-elm#master +bcgov/MOH-HCIM-FHIR#main +bcgov/moh-plr-fhir#master +daniel-thomson/nzhts#main +fhir-fi/finnish-base-profiles#master +FHIR/sql-on-fhir-v2#master +frankoemig/vital-signs-demo#main +fut-infrastructure/implementation-guide#master +hl7-eu/pcsp#master +hl7-eu/unicom-ig#master +HL7/cqf-recommendations#master +HL7/nhsn-dqm#main +HL7/US-Core#master +hl7ch/ch-term#master +HL7Chile/clcore_2.0#master +HL7Chile/IPS-CL#master +JohnMoehrke/testDicom#master +medizininformatik-initiative/kerndatensatz-kardiologie#main +medizininformatik-initiative/kerndatensatzmodul-medikation#master +tewhatuora/fhir-primary-care-acquisition#main +unicom-project-eu/d4.12#master +WorldHealthOrganization/smart-example-immz#main +WorldHealthOrganization/smart-icvp#main +WorldHealthOrganization/smart-immunizations#main + diff --git a/local-dependencies/fhir-package-loader-1.0.0.tgz b/local-dependencies/fhir-package-loader-1.0.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..712af6bdc3fbef6b98c212d8d6e925cfec460eab GIT binary patch literal 28130 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" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1385,6 +1486,15 @@ "node": ">= 8" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -1480,6 +1590,12 @@ "integrity": "sha512-uxpcuwWJGhe2AR1g8hD9F5OYGCqjqWnBUQFD8gMZsDbv8oPHzxJF6iMO6n8Tk0AdzlxoaaoQhOYlIg/PukVU8g==", "dev": true }, + "node_modules/@types/emscripten": { + "version": "1.39.13", + "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.13.tgz", + "integrity": "sha512-cFq+fO/isvhvmuP/+Sl4K4jtU6E23DoivtbO4r50e3odaxAiVdbfSYRDdJ4gCdxx+3aRjhphS5ZMwIH4hFy/Cw==", + "dev": true + }, "node_modules/@types/fs-extra": { "version": "11.0.4", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz", @@ -1611,6 +1727,16 @@ "@types/node": "*" } }, + "node_modules/@types/sql.js": { + "version": "1.4.9", + "resolved": "https://registry.npmjs.org/@types/sql.js/-/sql.js-1.4.9.tgz", + "integrity": "sha512-ep8b36RKHlgWPqjNG9ToUrPiwkhwh0AEzy883mO5Xnd+cL6VBH1EvSjBAAuxLUFF2Vn/moE3Me6v9E1Lo+48GQ==", + "dev": true, + "dependencies": { + "@types/emscripten": "*", + "@types/node": "*" + } + }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", @@ -1962,7 +2088,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -2043,9 +2168,9 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/axios": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.5.tgz", - "integrity": "sha512-fZu86yCo+svH3uqJ/yTdQ0QHpQu5oL+/QE+QPSv6BZSkDAoky9vytxp7u5qk83OJFS3kEBcesWni9WTZAv3tSw==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -2177,7 +2302,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -2390,11 +2514,11 @@ } }, "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/ci-info": { @@ -2673,7 +2797,6 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -3037,6 +3160,11 @@ "node": ">=0.4.0" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, "node_modules/ejs": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", @@ -3073,8 +3201,7 @@ "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/enabled": { "version": "2.0.0", @@ -3576,32 +3703,27 @@ }, "node_modules/fhir-package-loader": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fhir-package-loader/-/fhir-package-loader-1.0.0.tgz", - "integrity": "sha512-x3VY3RY1wkJv8Fd7dA7fY3aw+6Vg7qeCU0pci7wUaEhnJ84k7Lnca6dfH00l36uzH1N5EwVX51iKuuwsS6RdlA==", + "resolved": "file:local-dependencies/fhir-package-loader-1.0.0.tgz", + "integrity": "sha512-COEiV8vXTBMiXv9roi5TgQb9Tx1JRE7NF4PB1ilIOjEohA++3J9RGDFR5uOhNW16OoGE2KoLDiKPc5HcKpa7ig==", "dependencies": { - "axios": "^1.6.7", + "axios": "^1.7.7", "chalk": "^4.1.2", - "commander": "^11.1.0", + "commander": "^12.1.0", + "fhir": "^4.12.0", "fs-extra": "^11.2.0", - "https-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", "lodash": "^4.17.21", - "semver": "^7.5.4", - "tar": "^6.2.0", + "mnemonist": "^0.39.8", + "semver": "^7.6.3", + "sql.js": "^1.11.0", + "tar": "^7.4.3", "temp": "^0.9.1", - "winston": "^3.11.0" + "winston": "^3.14.2" }, "bin": { "fpl": "dist/app.js" } }, - "node_modules/fhir-package-loader/node_modules/commander": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", - "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", - "engines": { - "node": ">=16" - } - }, "node_modules/fhir/node_modules/inherits": { "version": "2.0.3", "inBundle": true, @@ -3766,6 +3888,21 @@ } } }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -3804,33 +3941,6 @@ "node": ">=14.14" } }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fs-minipass/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -4297,7 +4407,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "engines": { "node": ">=8" } @@ -4433,8 +4542,7 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", @@ -4502,6 +4610,20 @@ "node": ">=8" } }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jake": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", @@ -5699,7 +5821,6 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -5733,45 +5854,63 @@ } }, "node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.1.tgz", + "integrity": "sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==", "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" + "minipass": "^7.0.4", + "rimraf": "^5.0.5" }, "engines": { - "node": ">= 8" + "node": ">= 18" } }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "node_modules/minizlib/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dependencies": { - "yallist": "^4.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, - "engines": { - "node": ">=8" + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/minizlib/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "node_modules/minizlib/node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, "bin": { "mkdirp": "bin/cmd.js" }, @@ -5779,6 +5918,14 @@ "node": ">=10" } }, + "node_modules/mnemonist": { + "version": "0.39.8", + "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.39.8.tgz", + "integrity": "sha512-vyWo2K3fjrUw8YeeZ1zF0fy6Mu59RHokURlld8ymdUPjMlD9EC9ov1/YPqTgqRvUN9nTr3Gqfz29LYAmu0PHPQ==", + "dependencies": { + "obliterator": "^2.0.1" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -5938,6 +6085,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/obliterator": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.4.tgz", + "integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==" + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -6067,6 +6219,11 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==" + }, "node_modules/param-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", @@ -6136,7 +6293,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "engines": { "node": ">=8" } @@ -6147,6 +6303,26 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -6701,7 +6877,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -6713,7 +6888,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "engines": { "node": ">=8" } @@ -6722,7 +6896,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, "engines": { "node": ">=14" }, @@ -6816,6 +6989,11 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, + "node_modules/sql.js": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.11.0.tgz", + "integrity": "sha512-GsLUDU3vhOo14Pd5ME0y2te49JQyby6HuoCuadevEV+CGgTUjmYRrm7B7lhRyzOgrmcWmspUfyjNb6sOAEqdsA==" + }, "node_modules/stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -6868,7 +7046,20 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -6882,7 +7073,18 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -6974,25 +7176,42 @@ } }, "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar/node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/tar/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "engines": { + "node": ">=18" + } }, "node_modules/temp": { "version": "0.9.4", @@ -7497,7 +7716,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -7665,6 +7883,23 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index 0cf843680..29f4394a8 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "@types/opener": "^1.4.3", "@types/readline-sync": "^1.4.8", "@types/sax": "^1.2.7", + "@types/sql.js": "^1.4.9", "@types/temp": "^0.9.4", "@types/text-table": "^0.2.5", "@types/valid-url": "^1.0.7", @@ -92,7 +93,7 @@ "chalk": "^4.1.2", "commander": "^12.1.0", "fhir": "^4.12.0", - "fhir-package-loader": "^1.0.0", + "fhir-package-loader": "file:local-dependencies/fhir-package-loader-1.0.0.tgz", "fs-extra": "^11.2.0", "html-minifier-terser": "5.1.1", "https-proxy-agent": "^7.0.5", diff --git a/regression/find.ts b/regression/find.ts index 06c6f536f..aaf7282c1 100644 --- a/regression/find.ts +++ b/regression/find.ts @@ -2,7 +2,7 @@ import axios from 'axios'; import { remove, uniqBy, padEnd } from 'lodash'; import { axiosGet } from '../src/utils/axiosUtils'; -const FSH_FINDER_URL = 'https://fshschool.org/fsh-finder/fshy_repos.json'; +const FSH_FINDER_URL = 'https://fshschool.github.io/fsh-finder/fshy_repos.json'; const BUILD_URL_RE = /^([^/]+)\/([^/]+)\/branches\/([^/]+)\/qa\.json$/; const FSHY_PATHS = ['sushi-config.yaml', 'input/fsh', 'fsh']; const ORGANIZATIONS = [ diff --git a/regression/run.ts b/regression/run.ts index 42e167987..c5e47ba50 100644 --- a/regression/run.ts +++ b/regression/run.ts @@ -366,7 +366,7 @@ async function runSUSHI(num: 1 | 2, repo: Repo, config: Config): Promise { + logMessage(level, `@@@ NEW FPL @@@ ${message}`); + } + }); + const defs = new FHIRDefinitions(false, newFPL); await loadExternalDependencies(defs, config); // Load custom resources. In current tank configuration (input/fsh), resources will be in input/ - loadCustomResources(path.join(input, '..'), originalInput, config.parameters, defs); + const localResourcePaths = getLocalResourcePaths( + path.join(input, '..'), + originalInput, + config.parameters + ); + await newFPL + .loadVirtualPackage( + new DiskBasedVirtualPackage({ name: 'sushi-local', version: 'LOCAL' }, localResourcePaths) + ) + .then(status => logger.info(`Load status for local resources: ${status}`)); // Check for StructureDefinition const structDef = defs.fishForFHIR('StructureDefinition', Type.Resource); diff --git a/src/export/StructureDefinitionExporter.ts b/src/export/StructureDefinitionExporter.ts index 09ff205f9..bba486b85 100644 --- a/src/export/StructureDefinitionExporter.ts +++ b/src/export/StructureDefinitionExporter.ts @@ -984,7 +984,7 @@ export class StructureDefinitionExporter implements Fishable { } } } catch (e) { - logger.error(e.message, rule.sourceInfo); + logger.error(e.message ?? e, rule.sourceInfo); if (e.stack) { logger.debug(e.stack); } diff --git a/src/fhirdefs/BaseFHIRDefinitions.ts b/src/fhirdefs/BaseFHIRDefinitions.ts new file mode 100644 index 000000000..f642974e1 --- /dev/null +++ b/src/fhirdefs/BaseFHIRDefinitions.ts @@ -0,0 +1,600 @@ +// NOTE: This is the original FHIRDefinitions class from fhir-package-loader 1.0. +// This class was removed in fhir-package-loader 2.0, so we have it here as a +// temporary measure while we transition fully to fhir-package-loader 2.0. + +import { cloneDeep, isEqual, uniqWith, uniq } from 'lodash'; + +/** Class representing the FHIR definitions in one or more FHIR packages */ +export class BaseFHIRDefinitions { + protected resources: Map; + protected logicals: Map; + protected profiles: Map; + protected extensions: Map; + protected types: Map; + protected valueSets: Map; + protected codeSystems: Map; + protected implementationGuides: Map; + protected packageJsons: Map; + childFHIRDefs: BaseFHIRDefinitions[]; + package: string; + unsuccessfulPackageLoad: boolean; + + /** Create a FHIRDefinitions */ + constructor() { + this.package = ''; + this.resources = new DoubleMap(); + this.logicals = new DoubleMap(); + this.profiles = new DoubleMap(); + this.extensions = new DoubleMap(); + this.types = new DoubleMap(); + this.valueSets = new DoubleMap(); + this.codeSystems = new DoubleMap(); + this.implementationGuides = new DoubleMap(); + this.packageJsons = new DoubleMap(); + this.childFHIRDefs = []; + this.unsuccessfulPackageLoad = false; + } + + /** Get the total number of definitions */ + size(): number { + return ( + this.allResources().length + + this.allLogicals().length + + this.allProfiles().length + + this.allExtensions().length + + this.allTypes().length + + this.allValueSets().length + + this.allCodeSystems().length + + this.allImplementationGuides().length + ); + } + + // NOTE: These all return clones of the JSON to prevent the source values from being overwritten + + /** + * Get all resources. The array will not contain duplicates. + * @param {string} [fhirPackage] - The package (packageId#version) to search in. If not provided, searches all packages. + * @returns array of resources + */ + allResources(fhirPackage?: string): any[] { + if ( + (this.resources.size > 0 && this.childFHIRDefs.length > 0) || + this.childFHIRDefs.length > 1 + ) { + return uniqWith(this.collectResources(fhirPackage), isEqual); + } + return this.collectResources(fhirPackage); + } + + protected collectResources(fhirPackage?: string): any[] { + const childValues = this.childFHIRDefs + .map(def => def.collectResources(fhirPackage)) + .reduce((a, b) => a.concat(b), []); + let resources = this.resources; + if (fhirPackage) { + resources = new Map(); + if (this.package === fhirPackage) { + resources = this.resources; + } + } + return cloneJsonMapValues(resources).concat(childValues); + } + + /** + * Get all logicals. The array will not contain duplicates. + * @param {string} [fhirPackage] - The package (packageId#version) to search in. If not provided, searches all packages. + * @returns array of logicals + */ + allLogicals(fhirPackage?: string): any[] { + if ( + (this.logicals.size > 0 && this.childFHIRDefs.length > 0) || + this.childFHIRDefs.length > 1 + ) { + return uniqWith(this.collectLogicals(fhirPackage), isEqual); + } + return uniqWith(this.collectLogicals(fhirPackage), isEqual); + } + + protected collectLogicals(fhirPackage?: string): any[] { + const childValues = this.childFHIRDefs + .map(def => def.collectLogicals(fhirPackage)) + .reduce((a, b) => a.concat(b), []); + let logicals = this.logicals; + if (fhirPackage) { + logicals = new Map(); + if (this.package === fhirPackage) { + logicals = this.logicals; + } + } + return cloneJsonMapValues(logicals).concat(childValues); + } + + /** + * Get all profiles. The array will not contain duplicates. + * @param {string} [fhirPackage] - The package (packageId#version) to search in. If not provided, searches all packages. + * @returns array of profiles + */ + allProfiles(fhirPackage?: string): any[] { + if ( + (this.profiles.size > 0 && this.childFHIRDefs.length > 0) || + this.childFHIRDefs.length > 1 + ) { + return uniqWith(this.collectProfiles(fhirPackage), isEqual); + } + return this.collectProfiles(fhirPackage); + } + + protected collectProfiles(fhirPackage?: string): any[] { + const childValues = this.childFHIRDefs + .map(def => def.collectProfiles(fhirPackage)) + .reduce((a, b) => a.concat(b), []); + let profiles = this.profiles; + if (fhirPackage) { + profiles = new Map(); + if (this.package === fhirPackage) { + profiles = this.profiles; + } + } + return cloneJsonMapValues(profiles).concat(childValues); + } + + /** + * Get all extensions. The array will not contain duplicates. + * @param {string} [fhirPackage] - The package (packageId#version) to search in. If not provided, searches all packages. + * @returns array of extensions + */ + allExtensions(fhirPackage?: string): any[] { + if ( + (this.extensions.size > 0 && this.childFHIRDefs.length > 0) || + this.childFHIRDefs.length > 1 + ) { + return uniqWith(this.collectExtensions(fhirPackage), isEqual); + } + return this.collectExtensions(fhirPackage); + } + + protected collectExtensions(fhirPackage?: string): any[] { + const childValues = this.childFHIRDefs + .map(def => def.collectExtensions(fhirPackage)) + .reduce((a, b) => a.concat(b), []); + let extensions = this.extensions; + if (fhirPackage) { + extensions = new Map(); + if (this.package === fhirPackage) { + extensions = this.extensions; + } + } + return cloneJsonMapValues(extensions).concat(childValues); + } + + /** + * Get all types. The array will not contain duplicates. + * @param {string} [fhirPackage] - The package (packageId#version) to search in. If not provided, searches all packages. + * @returns array of types + */ + allTypes(fhirPackage?: string): any[] { + if ((this.types.size > 0 && this.childFHIRDefs.length > 0) || this.childFHIRDefs.length > 1) { + return uniqWith(this.collectTypes(fhirPackage), isEqual); + } + return this.collectTypes(fhirPackage); + } + + protected collectTypes(fhirPackage?: string): any[] { + const childValues = this.childFHIRDefs + .map(def => def.collectTypes(fhirPackage)) + .reduce((a, b) => a.concat(b), []); + let types = this.types; + if (fhirPackage) { + types = new Map(); + if (this.package === fhirPackage) { + types = this.types; + } + } + return cloneJsonMapValues(types).concat(childValues); + } + + /** + * Get all value sets. The array will not contain duplicates. + * @param {string} [fhirPackage] - The package (packageId#version) to search in. If not provided, searches all packages. + * @returns array of value sets + */ + allValueSets(fhirPackage?: string): any[] { + if ( + (this.valueSets.size > 0 && this.childFHIRDefs.length > 0) || + this.childFHIRDefs.length > 1 + ) { + return uniqWith(this.collectValueSets(fhirPackage), isEqual); + } + return this.collectValueSets(fhirPackage); + } + + protected collectValueSets(fhirPackage?: string): any[] { + const childValues = this.childFHIRDefs + .map(def => def.collectValueSets(fhirPackage)) + .reduce((a, b) => a.concat(b), []); + let valueSets = this.valueSets; + if (fhirPackage) { + valueSets = new Map(); + if (this.package === fhirPackage) { + valueSets = this.valueSets; + } + } + return cloneJsonMapValues(valueSets).concat(childValues); + } + + /** + * Get all code systems. The array will not contain duplicates. + * @param {string} [fhirPackage] - The package (packageId#version) to search in. If not provided, searches all packages. + * @returns array of code systems + */ + allCodeSystems(fhirPackage?: string): any[] { + if ( + (this.codeSystems.size > 0 && this.childFHIRDefs.length > 0) || + this.childFHIRDefs.length > 1 + ) { + return uniqWith(this.collectCodeSystems(fhirPackage), isEqual); + } + return this.collectCodeSystems(fhirPackage); + } + + protected collectCodeSystems(fhirPackage?: string): any[] { + const childValues = this.childFHIRDefs + .map(def => def.collectCodeSystems(fhirPackage)) + .reduce((a, b) => a.concat(b), []); + let codeSystems = this.codeSystems; + if (fhirPackage) { + codeSystems = new Map(); + if (this.package === fhirPackage) { + codeSystems = this.codeSystems; + } + } + return cloneJsonMapValues(codeSystems).concat(childValues); + } + + /** + * Get all implementation guides. The array will not contain duplicates. + * @param {string} [fhirPackage] - The package (packageId#version) to search in. If not provided, searches all packages. + * @returns array of implementation guides + */ + allImplementationGuides(fhirPackage?: string): any[] { + if ( + (this.implementationGuides.size > 0 && this.childFHIRDefs.length > 0) || + this.childFHIRDefs.length > 1 + ) { + return uniqWith(this.collectImplementationGuides(fhirPackage), isEqual); + } + return this.collectImplementationGuides(fhirPackage); + } + + protected collectImplementationGuides(fhirPackage?: string): any[] { + const childValues = this.childFHIRDefs + .map(def => def.collectImplementationGuides(fhirPackage)) + .reduce((a, b) => a.concat(b), []); + let implementationGuides = this.implementationGuides; + if (fhirPackage) { + implementationGuides = new Map(); + if (this.package === fhirPackage) { + implementationGuides = this.implementationGuides; + } + } + return cloneJsonMapValues(implementationGuides).concat(childValues); + } + + /** + * Get a list of packages that encountered errors while downloaded and were not loaded + * @param {string} [fhirPackage] - The package (packageId#version) to search in. If not provided, searches all packages. + * @returns array of packages (packageId#version) that were not successfully loaded + */ + allUnsuccessfulPackageLoads(fhirPackage?: string): string[] { + return uniq(this.collectUnsuccessfulPackageLoads(fhirPackage)); + } + + protected collectUnsuccessfulPackageLoads(fhirPackage?: string): string[] { + const childValues = this.childFHIRDefs + .map(def => def.collectUnsuccessfulPackageLoads(fhirPackage)) + .reduce((a, b) => a.concat(b), []); + if (fhirPackage) { + if (this.package === fhirPackage && this.unsuccessfulPackageLoad) { + return childValues.concat(this.package); + } + } else if (this.unsuccessfulPackageLoad) { + return childValues.concat(this.package); + } + return childValues; + } + + /** + * Get a list of all packages that are contained in this FHIRDefinitions + * @param {string} [fhirPackage] The package (packageId#version) to get all packages from. If not provided, all packages are returned. + * @returns array of packages (packageId#version) that are loaded + */ + allPackages(fhirPackage?: string): string[] { + return uniq(this.collectPackages(fhirPackage)); + } + + protected collectPackages(fhirPackage?: string): string[] { + const childValues = this.childFHIRDefs + .map(def => def.collectPackages(fhirPackage)) + .reduce((a, b) => a.concat(b), []); + if (fhirPackage) { + if (this.package === fhirPackage && this.package !== '') { + return childValues.concat(this.package); + } + } else if (this.package !== '') { + return childValues.concat(this.package); + } + return childValues; + } + + /** + * Add a definition + * @param definition - The definition to add + */ + add(definition: any): void { + if (definition.resourceType === 'StructureDefinition') { + if ( + definition.type === 'Extension' && + definition.baseDefinition !== 'http://hl7.org/fhir/StructureDefinition/Element' + ) { + addDefinitionToMap(definition, this.extensions); + } else if ( + definition.kind === 'primitive-type' || + definition.kind === 'complex-type' || + definition.kind === 'datatype' + ) { + addDefinitionToMap(definition, this.types); + } else if (definition.kind === 'resource') { + if (definition.derivation === 'constraint') { + addDefinitionToMap(definition, this.profiles); + } else { + addDefinitionToMap(definition, this.resources); + } + } else if (definition.kind === 'logical') { + if (definition.derivation === 'specialization') { + addDefinitionToMap(definition, this.logicals); + } else { + addDefinitionToMap(definition, this.profiles); + } + } + } else if (definition.resourceType === 'ValueSet') { + addDefinitionToMap(definition, this.valueSets); + } else if (definition.resourceType === 'CodeSystem') { + addDefinitionToMap(definition, this.codeSystems); + } else if (definition.resourceType === 'ImplementationGuide') { + addDefinitionToMap(definition, this.implementationGuides); + } + } + + /** + * Add a package.json + * @param {string} id - package id + * @param {string} definition - package JSON definition + */ + addPackageJson(id: string, definition: any): void { + this.packageJsons.set(id, definition); + } + + /** + * Get a package's package.json + * @param {string} id - package id + * @returns package.json definition + */ + getPackageJson(id: string): any { + return this.packageJsons.get(id); + } + + /** + * Private function for search through current FHIRDefinitions and all childFHIRDefs + * for a specified definition. Uses get for efficient retrieves. + * Breath-first search through childFHIRDefinitions for the item. + * @param item name, id, or url of definition to find + * @param map name of the map to search in + * @returns definition or undefined if it is not found + */ + private getDefinition(item: string, map: maps): any | undefined { + const defsToSearch: BaseFHIRDefinitions[] = [this]; + while (defsToSearch.length > 0) { + const currentFHIRDefs = defsToSearch.shift(); + const [base, ...versionParts] = item?.split('|') ?? ['', '']; + const version = versionParts.join('|') || null; + const def = currentFHIRDefs[map].get(base); + if (def) { + if (version == null || version === def?.version) { + // Only return the found definition if the version matches (if provided) + return def; + } + } + if (currentFHIRDefs.childFHIRDefs.length > 0) { + defsToSearch.push(...currentFHIRDefs.childFHIRDefs); + } + } + + return; + } + + /** + * Search for a definition based on the type it could be + * @param {string} item - the item to search for + * @param {Type[]} types - the possible type the item could be + * @returns the definition that is returned or undefined if none is found + */ + fishForFHIR(item: string, ...types: Type[]): any | undefined { + // No types passed in means to search ALL supported types + if (types.length === 0) { + types = FISHING_ORDER; + } else { + types.sort((a, b) => FISHING_ORDER.indexOf(a) - FISHING_ORDER.indexOf(b)); + } + + for (const type of types) { + let def; + switch (type) { + case Type.Resource: + def = cloneDeep(this.getDefinition(item, 'resources')); + break; + case Type.Logical: + def = cloneDeep(this.getDefinition(item, 'logicals')); + break; + case Type.Type: + def = cloneDeep(this.getDefinition(item, 'types')); + break; + case Type.Profile: + def = cloneDeep(this.getDefinition(item, 'profiles')); + break; + case Type.Extension: + def = cloneDeep(this.getDefinition(item, 'extensions')); + break; + case Type.ValueSet: + def = cloneDeep(this.getDefinition(item, 'valueSets')); + break; + case Type.CodeSystem: + def = cloneDeep(this.getDefinition(item, 'codeSystems')); + break; + case Type.Instance: // don't support resolving to FHIR instances + default: + break; + } + if (def) { + return def; + } + } + } +} + +function addDefinitionToMap(def: any, defMap: Map): void { + if (def.id) { + defMap.set(def.id, def); + } + if (def.url) { + defMap.set(def.url, def); + } + if (def.name) { + defMap.set(def.name, def); + } +} + +function cloneJsonMapValues(map: Map): any { + return Array.from(map.values()).map(v => cloneDeep(v)); +} + +export enum Type { + Profile = 'Profile', + Extension = 'Extension', + ValueSet = 'ValueSet', + CodeSystem = 'CodeSystem', + Instance = 'Instance', + Invariant = 'Invariant', // NOTE: only defined in FSHTanks, not FHIR defs + RuleSet = 'RuleSet', // NOTE: only defined in FSHTanks, not FHIR defs + Mapping = 'Mapping', // NOTE: only defined in FSHTanks, not FHIR defs + Resource = 'Resource', + Type = 'Type', // NOTE: only defined in FHIR defs, not FSHTanks + Logical = 'Logical' +} + +export const FISHING_ORDER = [ + Type.Resource, + Type.Logical, + Type.Type, + Type.Profile, + Type.Extension, + Type.ValueSet, + Type.CodeSystem +]; + +// Type to represent the names of the FHIRDefinition maps of definitions +type maps = + | 'resources' + | 'logicals' + | 'profiles' + | 'extensions' + | 'types' + | 'valueSets' + | 'codeSystems'; + +/** + * The DoubleMap is a Map that contains both forward and reverse mappings between keys and values. + * This allows the DoubleMap to easily provide a list of unique values, + * because each value in the internal forwardMap will be a key in the reverseMap. + * The reported size of a DoubleMap is the number of unique values, + * which is the number of keys in the reverseMap. + * + * Note that because DoubleMap.values() returns the keys from reverseMap, + * it may contain fewer elements than the other functions: keys(), entries(), forEach(), and the for-of iterator. + */ +export class DoubleMap implements Map { + private forwardMap: Map; + private reverseMap: Map>; + + constructor() { + this.forwardMap = new Map(); + this.reverseMap = new Map(); + } + + set(key: K, value: V): this { + if (this.forwardMap.get(key) === value) { + return this; + } + this.delete(key); + this.forwardMap.set(key, value); + if (this.reverseMap.has(value)) { + this.reverseMap.get(value).add(key); + } else { + this.reverseMap.set(value, new Set([key])); + } + return this; + } + + delete(key: K): boolean { + if (this.forwardMap.has(key)) { + const currentValue = this.forwardMap.get(key); + const currentKeys = this.reverseMap.get(currentValue); + currentKeys.delete(key); + if (currentKeys.size === 0) { + this.reverseMap.delete(currentValue); + } + this.forwardMap.delete(key); + return true; + } else { + return false; + } + } + + get(key: K): V { + return this.forwardMap.get(key); + } + + get size(): number { + return this.reverseMap.size; + } + + clear(): void { + this.forwardMap.clear(); + this.reverseMap.clear(); + } + + forEach(callbackfn: (value: V, key: K, map: Map) => void, thisArg?: any): void { + this.forwardMap.forEach(callbackfn, thisArg); + } + + has(key: K): boolean { + return this.forwardMap.has(key); + } + + [Symbol.iterator](): IterableIterator<[K, V]> { + return this.entries(); + } + + entries(): IterableIterator<[K, V]> { + return this.forwardMap.entries(); + } + + keys(): IterableIterator { + return this.forwardMap.keys(); + } + + values(): IterableIterator { + return this.reverseMap.keys(); + } + + [Symbol.toStringTag]: string; +} diff --git a/src/fhirdefs/FHIRDefinitions.ts b/src/fhirdefs/FHIRDefinitions.ts index 3ac06c77a..7fd6d267b 100644 --- a/src/fhirdefs/FHIRDefinitions.ts +++ b/src/fhirdefs/FHIRDefinitions.ts @@ -1,33 +1,18 @@ import { cloneDeep, flatten } from 'lodash'; -import { FHIRDefinitions as BaseFHIRDefinitions } from 'fhir-package-loader'; +import { FindResourceInfoOptions, PackageInfo, PackageLoader } from 'fhir-package-loader'; import { Type, Metadata, Fishable } from '../utils'; +import { BaseFHIRDefinitions } from './BaseFHIRDefinitions'; import { IMPLIED_EXTENSION_REGEX, materializeImpliedExtension } from './impliedExtensions'; -import { R5_DEFINITIONS_NEEDED_IN_R4 } from './R5DefsForR4'; -import { - LOGICAL_TARGET_EXTENSION, - TYPE_CHARACTERISTICS_EXTENSION, - findImposeProfiles -} from '../fhirtypes/common'; export class FHIRDefinitions extends BaseFHIRDefinitions implements Fishable { - private predefinedResources: Map; private supplementalFHIRDefinitions: Map; - constructor(public readonly isSupplementalFHIRDefinitions = false) { + constructor( + public readonly isSupplementalFHIRDefinitions = false, + public readonly newFPL?: PackageLoader + ) { super(); - this.predefinedResources = new Map(); this.supplementalFHIRDefinitions = new Map(); - // There are several R5 resources that are allowed for use in R4 and R4B. - // Add them first so they're always available. If a later version is loaded - // that has these definitions, it will overwrite them, so this should be safe. - if (!isSupplementalFHIRDefinitions) { - R5_DEFINITIONS_NEEDED_IN_R4.forEach(def => this.add(def)); - } - } - - // Expose the package.json files to support extracting the version when "latest" is used - allPackageJsons(): any[] { - return Array.from(this.packageJsons?.values() ?? []); } // This getter is only used in tests to verify what supplemental packages are loaded @@ -35,12 +20,37 @@ export class FHIRDefinitions extends BaseFHIRDefinitions implements Fishable { return flatten(Array.from(this.supplementalFHIRDefinitions.keys())); } - allPredefinedResources(makeClone = true): any[] { - if (makeClone) { - return Array.from(this.predefinedResources.values()).map(v => cloneDeep(v)); - } else { - return Array.from(this.predefinedResources.values()); + allImplementationGuides(fhirPackage?: string): any[] { + const options: FindResourceInfoOptions = { type: ['ImplementationGuide'] }; + if (fhirPackage) { + options.scope = fhirPackage; } + return this.newFPL?.findResourceJSONs('*', options); + } + + // TODO: Should we be cloning resources in other methods calling FPL? + allPredefinedResources(makeClone = true): any[] { + const pdResources = this.newFPL?.findResourceJSONs('*', { scope: 'sushi-local' }) ?? []; + return makeClone ? pdResources.map(r => cloneDeep(r)) : pdResources; + } + + allPredefinedResourceMetadatas(): Metadata[] { + return this.newFPL?.findResourceInfos('*', { scope: 'sushi-local' }).map(info => { + return { + id: info.id, + name: info.name, + sdType: info.sdType, + url: info.url, + parent: info.sdBaseDefinition, + imposeProfiles: info.sdImposeProfiles, + abstract: info.sdAbstract, + version: info.version, + resourceType: info.resourceType, + canBeTarget: info.sdCharacteristics?.some(c => c === 'can-be-target'), + canBind: info.sdCharacteristics?.some(c => c === 'can-bind'), + resourcePath: info.resourcePath + }; + }); } add(definition: any): void { @@ -61,16 +71,9 @@ export class FHIRDefinitions extends BaseFHIRDefinitions implements Fishable { } } - addPredefinedResource(file: string, definition: any): void { - this.predefinedResources.set(file, definition); - } - + // eslint-disable-next-line @typescript-eslint/no-unused-vars getPredefinedResource(file: string): any { - return this.predefinedResources.get(file); - } - - resetPredefinedResources() { - this.predefinedResources = new Map(); + return null; } addSupplementalFHIRDefinitions(fhirPackage: string, definitions: FHIRDefinitions): void { @@ -81,40 +84,40 @@ export class FHIRDefinitions extends BaseFHIRDefinitions implements Fishable { return this.supplementalFHIRDefinitions.get(fhirPackage); } + fishForPackageInfos(name: string): PackageInfo[] { + return this.newFPL?.findPackageInfos(name); + } + fishForPredefinedResource(item: string, ...types: Type[]): any | undefined { - const resource = this.fishForFHIR(item, ...types); - if ( - resource && - this.allPredefinedResources(false).find( - predefResource => - predefResource.id === resource.id && - predefResource.resourceType === resource.resourceType && - predefResource.url === resource.url - ) - ) { - return resource; + const def = this.newFPL?.findResourceJSON(item, { type: types, scope: 'sushi-local' }); + if (def) { + // TODO: Should FPL clone or leave that to FPL consumers? Or lock objects as READ-ONLY? + return cloneDeep(def); } } fishForPredefinedResourceMetadata(item: string, ...types: Type[]): Metadata | undefined { - const resource = this.fishForPredefinedResource(item, ...types); - if (resource) { + const info = this.newFPL?.findResourceInfo(item, { type: types, scope: 'sushi-local' }); + if (info) { return { - id: resource.id as string, - name: resource.name as string, - sdType: resource.type as string, - url: resource.url as string, - parent: resource.baseDefinition as string, - imposeProfiles: findImposeProfiles(resource), - abstract: resource.abstract as boolean, - version: resource.version as string, - resourceType: resource.resourceType as string + id: info.id, + name: info.name, + sdType: info.sdType, + url: info.url, + parent: info.sdBaseDefinition, + imposeProfiles: info.sdImposeProfiles, + abstract: info.sdAbstract, + version: info.version, + resourceType: info.resourceType, + canBeTarget: info.sdCharacteristics?.some(c => c === 'can-be-target'), + canBind: info.sdCharacteristics?.some(c => c === 'can-bind'), + resourcePath: info.resourcePath }; } } fishForFHIR(item: string, ...types: Type[]): any | undefined { - const def = super.fishForFHIR(item, ...types); + const def = this.newFPL?.findResourceJSON(item, { type: types }); if (def) { return def; } @@ -125,36 +128,21 @@ export class FHIRDefinitions extends BaseFHIRDefinitions implements Fishable { } fishForMetadata(item: string, ...types: Type[]): Metadata | undefined { - const result = this.fishForFHIR(item, ...types); - if (result) { - let canBeTarget: boolean; - let canBind: boolean; - if (result.resourceType === 'StructureDefinition' && result.kind === 'logical') { - canBeTarget = - result.extension?.some((ext: any) => { - return ( - (ext?.url === TYPE_CHARACTERISTICS_EXTENSION && ext?.valueCode === 'can-be-target') || - (ext?.url === LOGICAL_TARGET_EXTENSION && ext?.valueBoolean === true) - ); - }) ?? false; - canBind = - result.extension?.some( - (ext: any) => - ext?.url === TYPE_CHARACTERISTICS_EXTENSION && ext?.valueCode === 'can-bind' - ) ?? false; - } + const info = this.newFPL?.findResourceInfo(item, { type: types }); + if (info) { return { - id: result.id as string, - name: result.name as string, - sdType: result.type as string, - url: result.url as string, - parent: result.baseDefinition as string, - imposeProfiles: findImposeProfiles(result), - abstract: result.abstract as boolean, - version: result.version as string, - resourceType: result.resourceType as string, - canBeTarget, - canBind + id: info.id, + name: info.name, + sdType: info.sdType, + url: info.url, + parent: info.sdBaseDefinition, + imposeProfiles: info.sdImposeProfiles, + abstract: info.sdAbstract, + version: info.version, + resourceType: info.resourceType, + canBeTarget: info.sdCharacteristics?.some(c => c === 'can-be-target'), + canBind: info.sdCharacteristics?.some(c => c === 'can-bind'), + resourcePath: info.resourcePath }; } } diff --git a/src/fhirdefs/index.ts b/src/fhirdefs/index.ts index ec20eaa87..9c24f9ae0 100644 --- a/src/fhirdefs/index.ts +++ b/src/fhirdefs/index.ts @@ -1,3 +1,4 @@ +export * from './BaseFHIRDefinitions'; export * from './FHIRDefinitions'; export * from './load'; export * from './R5DefsForR4'; diff --git a/src/fhirdefs/load.ts b/src/fhirdefs/load.ts index 7c6282c41..f727ac9c6 100644 --- a/src/fhirdefs/load.ts +++ b/src/fhirdefs/load.ts @@ -1,11 +1,9 @@ import { FHIRDefinitions } from './FHIRDefinitions'; -import { mergeDependency } from 'fhir-package-loader'; import fs from 'fs-extra'; import path from 'path'; -import junk from 'junk'; -import { logger, logMessage, getFilesRecursive } from '../utils'; -import { Fhir as FHIRConverter } from 'fhir/fhir'; +import { logger, logMessage } from '../utils'; import { ImplementationGuideDefinitionParameter } from '../fhirtypes'; +import { defaultPackageLoader } from 'fhir-package-loader'; /** * Loads custom resources defined in resourceDir into FHIRDefs @@ -13,14 +11,13 @@ import { ImplementationGuideDefinitionParameter } from '../fhirtypes'; * @param {string} projectDir - User's specified project directory * @param {ImplementationGuideDefinitionParameter[]} configParameters - optional, an array of config parameters in which to * determine if there are additional resource paths for predefined resource - * @param {FHIRDefinitions} defs - The FHIRDefinitions object to load definitions into + * @returns string[] list of paths to search for custom resources */ -export function loadCustomResources( +export function getLocalResourcePaths( resourceDir: string, projectDir: string = null, - configParameters: ImplementationGuideDefinitionParameter[] = null, - defs: FHIRDefinitions -): void { + configParameters: ImplementationGuideDefinitionParameter[] = null +): string[] { // Similar code for loading custom resources exists in IGExporter.ts addPredefinedResources() const pathEnds = [ 'capabilities', @@ -32,7 +29,9 @@ export function loadCustomResources( 'vocabulary', 'examples' ]; - const predefinedResourcePaths = pathEnds.map(pathEnd => path.join(resourceDir, pathEnd)); + const predefinedResourcePaths = pathEnds + .map(pathEnd => path.join(resourceDir, pathEnd)) + .filter(p => fs.existsSync(p)); if (configParameters && projectDir) { const pathResources = configParameters ?.filter(parameter => parameter.value && parameter.code === 'path-resource') @@ -42,70 +41,7 @@ export function loadCustomResources( .filter(directoryPath => fs.existsSync(directoryPath)); if (pathResourceDirectories) predefinedResourcePaths.push(...pathResourceDirectories); } - const converter = new FHIRConverter(); - let invalidFileCount = 0; - for (const dirPath of predefinedResourcePaths) { - let foundSpreadsheets = false; - if (fs.existsSync(dirPath)) { - const files = getFilesRecursive(dirPath); - for (const file of files) { - let resourceJSON: any; - try { - if (junk.is(file)) { - // Ignore "junk" files created by the OS, like .DS_Store on macOS and Thumbs.db on Windows - continue; - } else if (file.endsWith('.json')) { - resourceJSON = fs.readJSONSync(file); - } else if (file.endsWith('-spreadsheet.xml')) { - foundSpreadsheets = true; - continue; - } else if (file.endsWith('xml')) { - const xml = fs.readFileSync(file).toString(); - if (/<\?mso-application progid="Excel\.Sheet"\?>/m.test(xml)) { - foundSpreadsheets = true; - continue; - } - resourceJSON = converter.xmlToObj(xml); - } else { - invalidFileCount++; - logger.debug(`File not processed by SUSHI: ${file}`); - continue; - } - } catch (e) { - if (e.message.startsWith('Unknown resource type:')) { - // Skip unknown FHIR resource types. When we have instances of Logical Models, - // the resourceType will not be recognized as a known FHIR resourceType, but that's okay. - continue; - } - logger.error(`Loading ${file} failed with the following error:\n${e.message}`); - if (e.stack) { - logger.debug(e.stack); - } - continue; - } - // All resources are added to the predefined map, so that this map can later be used to - // access predefined resources in the IG Exporter - defs.addPredefinedResource(file, resourceJSON); - if (path.basename(dirPath) !== 'examples') { - // add() will only add resources of resourceType: - // StructureDefinition, ValueSet, CodeSystem, or ImplementationGuide - defs.add(resourceJSON); - } - } - } - if (foundSpreadsheets) { - logger.info( - `Found spreadsheets in directory ${dirPath}. SUSHI does not support spreadsheets, so any resources in the spreadsheets will be ignored.` - ); - } - } - if (invalidFileCount > 0) { - logger.info( - invalidFileCount > 1 - ? `Found ${invalidFileCount} files in input/* resource folders that were neither XML nor JSON. These files were not processed as resources by SUSHI. To see the unprocessed files in the logs, run SUSHI with the "--log-level debug" flag.` - : `Found ${invalidFileCount} file in an input/* resource folder that was neither XML nor JSON. This file was not processed as a resource by SUSHI. To see the unprocessed file in the logs, run SUSHI with the "--log-level debug" flag.` - ); - } + return [...predefinedResourcePaths]; } /** @@ -122,11 +58,20 @@ export async function loadSupplementalFHIRPackage( fhirPackage: string, defs: FHIRDefinitions ): Promise { - const supplementalDefs = new FHIRDefinitions(true); + const newFPL = await defaultPackageLoader({ + log: (level: string, message: string) => { + logMessage(level, `@@@ NEW FPL for ${fhirPackage} @@@ ${message}`); + } + }); + const supplementalDefs = new FHIRDefinitions(true, newFPL); const [fhirPackageId, fhirPackageVersion] = fhirPackage.split('#'); - return mergeDependency(fhirPackageId, fhirPackageVersion, supplementalDefs, undefined, logMessage) - .then((def: FHIRDefinitions) => defs.addSupplementalFHIRDefinitions(fhirPackage, def)) - .catch((e: Error) => { + await supplementalDefs.newFPL + ?.loadPackage(fhirPackageId, fhirPackageVersion) + .then(status => { + logger.info(`Load status for ${fhirPackage}: ${status}`); + defs.addSupplementalFHIRDefinitions(fhirPackage, supplementalDefs); + }) + .catch(e => { logger.error(`Failed to load supplemental FHIR package ${fhirPackage}: ${e.message}`); if (e.stack) { logger.debug(e.stack); diff --git a/src/fhirtypes/common.ts b/src/fhirtypes/common.ts index 84c6c9044..0c61f044f 100644 --- a/src/fhirtypes/common.ts +++ b/src/fhirtypes/common.ts @@ -992,26 +992,28 @@ export function replaceReferences( } else if (value instanceof FshCode) { // the version on a CodeSystem resource is not the same as the system's actual version out in the world. // so, they don't need to match. - const baseSystem = value.system?.split('|')[0]; - const codeSystemMeta = fisher.fishForMetadata(baseSystem, Type.CodeSystem); - if (codeSystemMeta) { - clone = cloneDeep(rule); - const assignedCode = clone.value as FshCode; - assignedCode.system = value.system.replace(/^[^|]+/, codeSystemMeta.url); + if (value.system != null) { + const baseSystem = value.system.split('|')[0]; + const codeSystemMeta = fisher.fishForMetadata(baseSystem, Type.CodeSystem); + if (codeSystemMeta) { + clone = cloneDeep(rule); + const assignedCode = clone.value as FshCode; + assignedCode.system = value.system.replace(/^[^|]+/, codeSystemMeta.url); - // Find the code system using the returned metadata to avoid duplicate warnings if version mismatches - const matchedCanonical = codeSystemMeta.url - ? `${codeSystemMeta.url}${codeSystemMeta.version ? `|${codeSystemMeta.version}` : ''}` - : value.system; - const codeSystem = fishInTankBestVersion( - tank, - matchedCanonical, - rule.sourceInfo, - Type.CodeSystem - ); - if (codeSystem && (codeSystem instanceof FshCodeSystem || codeSystem instanceof Instance)) { - // if a local system was used, check to make sure the code is actually in that system - listUndefinedLocalCodes(codeSystem, [assignedCode.code], tank, rule); + // Find the code system using the returned metadata to avoid duplicate warnings if version mismatches + const matchedCanonical = codeSystemMeta.url + ? `${codeSystemMeta.url}${codeSystemMeta.version ? `|${codeSystemMeta.version}` : ''}` + : value.system; + const codeSystem = fishInTankBestVersion( + tank, + matchedCanonical, + rule.sourceInfo, + Type.CodeSystem + ); + if (codeSystem && (codeSystem instanceof FshCodeSystem || codeSystem instanceof Instance)) { + // if a local system was used, check to make sure the code is actually in that system + listUndefinedLocalCodes(codeSystem, [assignedCode.code], tank, rule); + } } } } diff --git a/src/ig/IGExporter.ts b/src/ig/IGExporter.ts index aed50d878..5c5a55628 100644 --- a/src/ig/IGExporter.ts +++ b/src/ig/IGExporter.ts @@ -10,7 +10,8 @@ import { outputFileSync, existsSync, readdirSync, - readFileSync + readFileSync, + readJSONSync } from 'fs-extra'; import junk from 'junk'; import { Package } from '../export'; @@ -271,15 +272,16 @@ export class IGExporter { } if (dependsOn.version === 'latest') { + // TODO: This assumes only a single version of a package is in scope const dependencyIG = igs.find(ig => ig.packageId === dependsOn.packageId); if (dependencyIG?.version != null) { dependsOn.version = dependencyIG.version; } else { - const packageJSON = this.fhirDefs - .allPackageJsons() - .find(p => p.name === dependsOn.packageId); - if (packageJSON?.version != null) { - dependsOn.version = packageJSON.version; + const packageInfos = this.fhirDefs + .fishForPackageInfos(dependsOn.packageId) + .filter(info => info.version != null); + if (packageInfos.length) { + dependsOn.version = packageInfos[0].version; } } } @@ -946,229 +948,161 @@ export class IGExporter { this.ig.definition.resource.push(newResource); } - /** - * Adds any user provided resource files to the ImplementationGuide JSON file. - * This includes definitions in: - * capabilities, extensions, models, operations, profiles, resources, vocabulary, examples - * Based on: https://build.fhir.org/ig/FHIR/ig-guidance/using-templates.html#root.input - * - * NOTE: This only includes files nested in subfolders when specified in the path-resource - * parameter, which is based on how the IG Publisher works. - * - * This function has similar operation to addResources, and both should be - * analyzed when making changes to either. - */ private addPredefinedResources(): void { - // Similar code for loading custom resources exists in load.ts loadCustomResources() - const pathEnds = [ - 'capabilities', - 'extensions', - 'models', - 'operations', - 'profiles', - 'resources', - 'vocabulary', - 'examples' - ]; - const predefinedResourcePaths = pathEnds.map(pathEnd => - path.join(this.inputPath, 'input', pathEnd) - ); - const pathResourceDirectories: string[] = []; - const pathResources = this.config.parameters - ?.filter(parameter => parameter.value && parameter.code === 'path-resource') - .map(parameter => parameter.value); - if (pathResources) { - pathResources.forEach(directoryPath => { - const fullPath = path.join(this.inputPath, ...directoryPath.split('/')); - if (existsSync(fullPath)) { - pathResourceDirectories.push(fullPath); - } else if (directoryPath.endsWith('/*') && existsSync(fullPath.slice(0, -2))) { - pathResourceDirectories.push( - ...readdirSync(fullPath.slice(0, -2), { withFileTypes: true, recursive: true }) - .filter(file => file.isDirectory()) - .map(dir => path.join(dir.path, dir.name)) - ); - } - }); - if (pathResourceDirectories) predefinedResourcePaths.push(...pathResourceDirectories); - } - const deeplyNestedFiles: string[] = []; const configuredBinaryResources = (this.config.resources ?? []).filter( resource => resource.reference?.reference?.startsWith('Binary/') && resource.extension?.some(e => IG_RESOURCE_FORMAT_EXTENSIONS.includes(e.url)) ); - for (const dirPath of predefinedResourcePaths) { - if (existsSync(dirPath)) { - const files = getFilesRecursive(dirPath); - for (const file of files) { + + const predefinedResourceMetadatas = this.fhirDefs.allPredefinedResourceMetadatas(); + const predefinedResources = this.fhirDefs.allPredefinedResources(); + for (let i = 0; i < predefinedResources.length; i++) { + const resourceJSON: InstanceDefinition = predefinedResources[i]; + if (resourceJSON) { + const file = predefinedResourceMetadatas[i].resourcePath; + // For predefined examples of Logical Models, the user must provide an entry in config + // that specifies the reference as Binary/[id], the extension that specifies the resource format, + // and the exampleCanonical that references the LogicalModel the resource is an example of. + // Note: the exampleCanonical should reference the resourceType because the resourceType should + // be the absolute URL, but we previously supported using the logical model's id as the example's + // resourceType, so support having an exampleCanonical in either form for now. + // In that case, we do not want to add our own entry for the predefined resource - we just + // want to use the resource entry from the sushi-config.yaml + // For predefined examples of Logical Models that do not have a resourceType or id, + // a Binary resource reference based on the file name can be used, based on Zulip: + // https://chat.fhir.org/#narrow/stream/215610-shorthand/topic/How.20do.20I.20get.20SUSHI.20to.20ignore.20a.20binary.20JSON.20logical.20instance.3F/near/407861211 + const configuredBinaryReference = configuredBinaryResources.find( + resource => + (resource.reference?.reference === `Binary/${resourceJSON.id}` && + (resource.exampleCanonical === + `${this.config.canonical}/StructureDefinition/${resourceJSON.resourceType}` || + resource.exampleCanonical === resourceJSON.resourceType)) || + resource.reference?.reference === `Binary/${path.parse(file).name}` + ); + + if (configuredBinaryReference) { if ( - path.dirname(file) !== dirPath && - !pathResourceDirectories?.includes(path.dirname(file)) + configuredBinaryReference.extension?.some( + ext => ext.url === DEPRECATED_RESOURCE_FORMAT_EXTENSION + ) ) { - if (!deeplyNestedFiles.includes(file)) { - deeplyNestedFiles.push(file); - } - continue; - } - const resourceJSON: InstanceDefinition = this.fhirDefs.getPredefinedResource(file); - if (resourceJSON) { - // For predefined examples of Logical Models, the user must provide an entry in config - // that specifies the reference as Binary/[id], the extension that specifies the resource format, - // and the exampleCanonical that references the LogicalModel the resource is an example of. - // Note: the exampleCanonical should reference the resourceType because the resourceType should - // be the absolute URL, but we previously supported using the logical model's id as the example's - // resourceType, so support having an exampleCanonical in either form for now. - // In that case, we do not want to add our own entry for the predefined resource - we just - // want to use the resource entry from the sushi-config.yaml - // For predefined examples of Logical Models that do not have a resourceType or id, - // a Binary resource reference based on the file name can be used, based on Zulip: - // https://chat.fhir.org/#narrow/stream/215610-shorthand/topic/How.20do.20I.20get.20SUSHI.20to.20ignore.20a.20binary.20JSON.20logical.20instance.3F/near/407861211 - const configuredBinaryReference = configuredBinaryResources.find( - resource => - (resource.reference?.reference === `Binary/${resourceJSON.id}` && - (resource.exampleCanonical === - `${this.config.canonical}/StructureDefinition/${resourceJSON.resourceType}` || - resource.exampleCanonical === resourceJSON.resourceType)) || - resource.reference?.reference === `Binary/${path.parse(file).name}` + logger.warn( + `The extension ${DEPRECATED_RESOURCE_FORMAT_EXTENSION} has been deprecated. Update the configuration for ${configuredBinaryReference.reference?.reference ?? configuredBinaryReference.name} to use the current extension, ${CURRENT_RESOURCE_FORMAT_EXTENSION}.` ); + } + continue; + } - if (configuredBinaryReference) { - if ( - configuredBinaryReference.extension?.some( - ext => ext.url === DEPRECATED_RESOURCE_FORMAT_EXTENSION - ) - ) { - logger.warn( - `The extension ${DEPRECATED_RESOURCE_FORMAT_EXTENSION} has been deprecated. Update the configuration for ${configuredBinaryReference.reference?.reference ?? configuredBinaryReference.name} to use the current extension, ${CURRENT_RESOURCE_FORMAT_EXTENSION}.` - ); - } - continue; - } - - if (resourceJSON.resourceType == null || resourceJSON.id == null) { - logger.warn( - `Resource at ${file} is missing ${ - resourceJSON.resourceType == null ? 'resourceType' : '' - }${resourceJSON.resourceType == null && resourceJSON.id == null ? ' and ' : ''}${ - resourceJSON.id == null ? 'id' : '' - }.` - ); - continue; - } - - const referenceKey = `${resourceJSON.resourceType}/${resourceJSON.id}`; - const newResource: ImplementationGuideDefinitionResource = { - reference: { - reference: referenceKey - } - }; - const configResource = (this.config.resources ?? []).find( - resource => resource.reference?.reference == referenceKey - ); + if (resourceJSON.resourceType == null || resourceJSON.id == null) { + logger.warn( + `Resource at ${file} is missing ${ + resourceJSON.resourceType == null ? 'resourceType' : '' + }${resourceJSON.resourceType == null && resourceJSON.id == null ? ' and ' : ''}${ + resourceJSON.id == null ? 'id' : '' + }.` + ); + continue; + } - if (configResource?.omit !== true) { - const existingIndex = this.ig.definition.resource.findIndex( - r => r.reference.reference === referenceKey - ); - // If the user has provided a resource, it should override the generated resource. - // This can be helpful for working around cases where the generated resource has some incorrect values. - const existingResource = - existingIndex >= 0 ? this.ig.definition.resource[existingIndex] : null; - const existingIsExample = - existingResource?.exampleBoolean || existingResource?.exampleCanonical; - const existingName = existingIsExample ? existingResource.name : null; - const existingDescription = existingIsExample ? existingResource.description : null; - - const metaExtensionDescription = this.getMetaExtensionDescription(resourceJSON); - const metaExtensionName = this.getMetaExtensionName(resourceJSON); - // On some resources (Patient for example) title, name, and description can be objects, avoid using them when this is true - newResource.description = - configResource?.description ?? - metaExtensionDescription ?? - existingDescription ?? - stringOrElse(resourceJSON.description); - if (configResource?.fhirVersion) { - newResource.fhirVersion = configResource.fhirVersion; - } - if (configResource?.groupingId) { - newResource.groupingId = configResource.groupingId; - this.addGroup(newResource.groupingId); - } - if (path.basename(dirPath) === 'examples') { - newResource.name = - configResource?.name ?? - metaExtensionName ?? - existingName ?? - stringOrElse(resourceJSON.title) ?? - stringOrElse(resourceJSON.name) ?? - resourceJSON.id; - newResource._linkRef = resourceJSON.id; - // set exampleCanonical or exampleBoolean, preferring configured values - if (configResource?.exampleCanonical) { - newResource.exampleCanonical = configResource.exampleCanonical; - } else if (typeof configResource?.exampleBoolean === 'boolean') { - newResource.exampleBoolean = configResource.exampleBoolean; - } else { - const exampleUrl = resourceJSON.meta?.profile?.find(url => { - const [baseUrl, version] = url.split('|', 2); - const availableProfile = - this.pkg.fish(baseUrl, Type.Profile) ?? - this.fhirDefs.fishForFHIR(baseUrl, Type.Profile); - return ( - availableProfile != null && - (version == null || - version === (availableProfile.version ?? this.config.version)) - ); - }); - if (exampleUrl) { - newResource.exampleCanonical = exampleUrl.split('|', 1)[0]; - } else { - newResource.exampleBoolean = true; - } - } - } else { - if (configResource?.exampleCanonical) { - newResource.exampleCanonical = configResource.exampleCanonical; - } else if (typeof configResource?.exampleBoolean === 'boolean') { - newResource.exampleBoolean = configResource.exampleBoolean; - } else { - newResource.exampleBoolean = false; - } - newResource.name = - configResource?.name ?? - metaExtensionName ?? - existingResource?.name ?? - stringOrElse(resourceJSON.title) ?? - stringOrElse(resourceJSON.name) ?? - resourceJSON.id; - newResource._linkRef = stringOrElse(resourceJSON.name) ?? resourceJSON.id; - } - if (configResource?.extension?.length) { - newResource.extension = configResource.extension; - } + const referenceKey = `${resourceJSON.resourceType}/${resourceJSON.id}`; + const newResource: ImplementationGuideDefinitionResource = { + reference: { + reference: referenceKey + } + }; + const configResource = (this.config.resources ?? []).find( + resource => resource.reference?.reference == referenceKey + ); - if (existingIndex >= 0) { - this.ig.definition.resource[existingIndex] = newResource; + if (configResource?.omit !== true) { + const existingIndex = this.ig.definition.resource.findIndex( + r => r.reference.reference === referenceKey + ); + // If the user has provided a resource, it should override the generated resource. + // This can be helpful for working around cases where the generated resource has some incorrect values. + const existingResource = + existingIndex >= 0 ? this.ig.definition.resource[existingIndex] : null; + const existingIsExample = + existingResource?.exampleBoolean || existingResource?.exampleCanonical; + const existingName = existingIsExample ? existingResource.name : null; + const existingDescription = existingIsExample ? existingResource.description : null; + + const metaExtensionDescription = this.getMetaExtensionDescription(resourceJSON); + const metaExtensionName = this.getMetaExtensionName(resourceJSON); + // On some resources (Patient for example) title, name, and description can be objects, avoid using them when this is true + newResource.description = + configResource?.description ?? + metaExtensionDescription ?? + existingDescription ?? + stringOrElse(resourceJSON.description); + if (configResource?.fhirVersion) { + newResource.fhirVersion = configResource.fhirVersion; + } + if (configResource?.groupingId) { + newResource.groupingId = configResource.groupingId; + this.addGroup(newResource.groupingId); + } + if (path.basename(path.dirname(file)) === 'examples') { + newResource.name = + configResource?.name ?? + metaExtensionName ?? + existingName ?? + stringOrElse(resourceJSON.title) ?? + stringOrElse(resourceJSON.name) ?? + resourceJSON.id; + newResource._linkRef = resourceJSON.id; + // set exampleCanonical or exampleBoolean, preferring configured values + if (configResource?.exampleCanonical) { + newResource.exampleCanonical = configResource.exampleCanonical; + } else if (typeof configResource?.exampleBoolean === 'boolean') { + newResource.exampleBoolean = configResource.exampleBoolean; + } else { + const exampleUrl = resourceJSON.meta?.profile?.find(url => { + const [baseUrl, version] = url.split('|', 2); + const availableProfile = + this.pkg.fish(baseUrl, Type.Profile) ?? + this.fhirDefs.fishForFHIR(baseUrl, Type.Profile); + return ( + availableProfile != null && + (version == null || version === (availableProfile.version ?? this.config.version)) + ); + }); + if (exampleUrl) { + newResource.exampleCanonical = exampleUrl.split('|', 1)[0]; } else { - this.ig.definition.resource.push(newResource); + newResource.exampleBoolean = true; } } + } else { + if (configResource?.exampleCanonical) { + newResource.exampleCanonical = configResource.exampleCanonical; + } else if (typeof configResource?.exampleBoolean === 'boolean') { + newResource.exampleBoolean = configResource.exampleBoolean; + } else { + newResource.exampleBoolean = false; + } + newResource.name = + configResource?.name ?? + metaExtensionName ?? + existingResource?.name ?? + stringOrElse(resourceJSON.title) ?? + stringOrElse(resourceJSON.name) ?? + resourceJSON.id; + newResource._linkRef = stringOrElse(resourceJSON.name) ?? resourceJSON.id; + } + if (configResource?.extension?.length) { + newResource.extension = configResource.extension; + } + + if (existingIndex >= 0) { + this.ig.definition.resource[existingIndex] = newResource; + } else { + this.ig.definition.resource.push(newResource); } } } } - if (deeplyNestedFiles.length) { - logger.warn( - 'The following files were not added to the ImplementationGuide JSON because they are not in one of the supported ' + - 'input folders or are nested too deep in one of those folders. While SUSHI automatically supports resources in ' + - 'sub-folders, the IG Publisher does not, unless the folder is explicitly added via the template or an IG parameter. ' + - 'To fix any issues you may encounter due to this, adjust your IG parameters or template accordingly or move these ' + - `files so they are directly under a supported input folder (e.g., input/resources, input/profiles, etc.):\n - ${deeplyNestedFiles.join( - '\n - ' - )}` - ); - } } /** diff --git a/src/utils/Fishable.ts b/src/utils/Fishable.ts index ccbf19370..3c5898017 100644 --- a/src/utils/Fishable.ts +++ b/src/utils/Fishable.ts @@ -27,6 +27,7 @@ export interface Metadata { instanceUsage?: Instance['usage']; canBeTarget?: boolean; canBind?: boolean; + resourcePath?: string; } export interface Fishable { diff --git a/src/utils/Processing.ts b/src/utils/Processing.ts index c3a9822f7..f98eecbd1 100644 --- a/src/utils/Processing.ts +++ b/src/utils/Processing.ts @@ -6,11 +6,16 @@ import YAML from 'yaml'; import { execSync } from 'child_process'; import { YAMLMap, Collection } from 'yaml/types'; import { isPlainObject, padEnd, startCase, sortBy, upperFirst } from 'lodash'; -import { mergeDependency, FHIRDefinitions as BaseFHIRDefinitions } from 'fhir-package-loader'; import { EOL } from 'os'; import { AxiosResponse } from 'axios'; +import table from 'text-table'; +import { OptionValues } from 'commander'; import { logger, logMessage } from './FSHLogger'; -import { loadSupplementalFHIRPackage, FHIRDefinitions } from '../fhirdefs'; +import { + loadSupplementalFHIRPackage, + FHIRDefinitions, + R5_DEFINITIONS_NEEDED_IN_R4 +} from '../fhirdefs'; import { FSHTank, RawFSH, @@ -24,8 +29,7 @@ import { Configuration } from '../fshtypes'; import { axiosGet } from './axiosUtils'; import { ImplementationGuideDependsOn } from '../fhirtypes'; import { FHIRVersionName, getFHIRVersionInfo } from '../utils/FHIRVersionUtils'; -import table from 'text-table'; -import { OptionValues } from 'commander'; +import { InMemoryVirtualPackage } from 'fhir-package-loader'; const EXT_PKG_TO_FHIR_PKG_MAP: { [key: string]: string } = { 'hl7.fhir.extensions.r2': 'hl7.fhir.r2.core#1.0.2', @@ -368,9 +372,27 @@ export async function loadExternalDependencies( export async function loadAutomaticDependencies( fhirVersion: string, configuredDependencies: ImplementationGuideDependsOn[], - defs: BaseFHIRDefinitions + defs: FHIRDefinitions ): Promise { const fhirVersionName = getFHIRVersionInfo(fhirVersion).name; + + if (fhirVersionName === 'R4' || fhirVersionName === 'R4B') { + // There are several R5 resources that are allowed for use in R4 and R4B. + // Add them first so they're always available. + const R5forR4Map = new Map(); + R5_DEFINITIONS_NEEDED_IN_R4.forEach(def => R5forR4Map.set(def.id, def)); + const virtualR5forR4Package = new InMemoryVirtualPackage( + { name: 'sushi-r5forR4', version: '1.0.0' }, + R5forR4Map, + { + log: (level: string, message: string) => { + logMessage(level, `@@@ NEW FPL @@@ ${message}`); + } + } + ); + await defs.newFPL.loadVirtualPackage(virtualR5forR4Package); + } + // Load dependencies serially so dependency loading order is predictable and repeatable for (const dep of AUTOMATIC_DEPENDENCIES) { // Skip dependencies not intended for this version of FHIR @@ -388,7 +410,8 @@ export async function loadAutomaticDependencies( }); if (!alreadyConfigured) { try { - await mergeDependency(dep.packageId, dep.version, defs, undefined, logMessage); + const status = await defs.newFPL?.loadPackage(dep.packageId, dep.version); + logger.info(`Load status for ${dep.packageId}#${dep.version}: ${status}`); } catch (e) { let message = `Failed to load automatically-provided ${dep.packageId}#${dep.version}`; if (process.env.FPL_REGISTRY) { @@ -444,16 +467,19 @@ async function loadConfiguredDependencies( ); await loadSupplementalFHIRPackage(EXT_PKG_TO_FHIR_PKG_MAP[dep.packageId], defs); } else { - await mergeDependency(dep.packageId, dep.version, defs, undefined, logMessage).catch(e => { - let message = `Failed to load ${dep.packageId}#${dep.version}: ${e.message}`; - if (/certificate/.test(e.message)) { - message += CERTIFICATE_MESSAGE; - } - logger.error(message); - if (e.stack) { - logger.debug(e.stack); - } - }); + await defs.newFPL + ?.loadPackage(dep.packageId, dep.version) + .then(status => logger.info(`Load status for ${dep.packageId}#${dep.version}: ${status}`)) + .catch(e => { + let message = `Failed to load ${dep.packageId}#${dep.version}: ${e.message}`; + if (/certificate/.test(e.message)) { + message += CERTIFICATE_MESSAGE; + } + logger.error(message); + if (e.stack) { + logger.debug(e.stack); + } + }); } } } diff --git a/test/fhirdefs/FHIRDefinitions.test.ts b/test/fhirdefs/FHIRDefinitions.test.ts index 208328523..7e1eaba83 100644 --- a/test/fhirdefs/FHIRDefinitions.test.ts +++ b/test/fhirdefs/FHIRDefinitions.test.ts @@ -1231,16 +1231,4 @@ describe('FHIRDefinitions', () => { ]); }); }); - - describe('#allPackageJSONs', () => { - it('should return all package jsons', () => { - const testDefs = new FHIRDefinitions(); - testDefs.addPackageJson('sushi.test.1', { name: 'sushi.test.1', version: '0.0.1' }); - testDefs.addPackageJson('sushi.test.2', { name: 'sushi.test.2', version: '0.0.2' }); - expect(testDefs.allPackageJsons()).toEqual([ - { name: 'sushi.test.1', version: '0.0.1' }, - { name: 'sushi.test.2', version: '0.0.2' } - ]); - }); - }); }); diff --git a/tsconfig.json b/tsconfig.json index 7c372fc2d..9ee2466d9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,9 +2,9 @@ "compilerOptions": { /* Basic Options */ // "incremental": true, /* Enable incremental compilation */ - "target": "ES2018", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ + "target": "ES2018", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ - "lib": ["ES2020"], /* Specify library files to be included in the compilation. */ + "lib": ["ES2020", "DOM"], /* Specify library files to be included in the compilation. */ "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ @@ -51,7 +51,7 @@ // "typeRoots": [], /* List of folders to include type definitions from. */ // "types": [], /* Type declaration files to be included in compilation. */ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ - "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ @@ -66,7 +66,7 @@ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ /* Advanced Options */ - "resolveJsonModule": true /* Include modules imported with '.json' extension */ + "resolveJsonModule": true /* Include modules imported with '.json' extension */ }, "include": [ "src/**/*"