fhirsmith 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (277) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/FHIRsmith.png +0 -0
  3. package/README.md +277 -0
  4. package/config-template.json +144 -0
  5. package/library/folder-setup.js +58 -0
  6. package/library/html-server.js +166 -0
  7. package/library/html.js +835 -0
  8. package/library/i18nsupport.js +259 -0
  9. package/library/languages.js +779 -0
  10. package/library/logger-telnet.js +205 -0
  11. package/library/logger.js +279 -0
  12. package/library/package-manager.js +876 -0
  13. package/library/utilities.js +196 -0
  14. package/library/version-utilities.js +1056 -0
  15. package/npmprojector/config-example.json +13 -0
  16. package/npmprojector/indexer.js +394 -0
  17. package/npmprojector/npmprojector.js +395 -0
  18. package/npmprojector/readme.md +174 -0
  19. package/npmprojector/watcher.js +335 -0
  20. package/package.json +119 -0
  21. package/packages/package-crawler.js +846 -0
  22. package/packages/packages-template.html +126 -0
  23. package/packages/packages.js +2838 -0
  24. package/passwords.ini +2 -0
  25. package/publisher/publisher-template.html +208 -0
  26. package/publisher/publisher.js +2167 -0
  27. package/publisher/task-draft.js +458 -0
  28. package/registry/api.js +735 -0
  29. package/registry/crawler.js +637 -0
  30. package/registry/model.js +513 -0
  31. package/registry/readme.md +243 -0
  32. package/registry/registry-data.json +121015 -0
  33. package/registry/registry-template.html +126 -0
  34. package/registry/registry.js +1395 -0
  35. package/registry/test-runner.js +237 -0
  36. package/root-template.html +124 -0
  37. package/server.js +524 -0
  38. package/shl/private-key.pem +5 -0
  39. package/shl/public-key.pem +18 -0
  40. package/shl/shl.js +1125 -0
  41. package/shl/vhl.js +69 -0
  42. package/static/FHIRsmith128.png +0 -0
  43. package/static/FHIRsmith16.png +0 -0
  44. package/static/FHIRsmith32.png +0 -0
  45. package/static/FHIRsmith64.png +0 -0
  46. package/static/assets/css/bootstrap-fhir.css +5302 -0
  47. package/static/assets/css/bootstrap-glyphicons.css +2 -0
  48. package/static/assets/css/bootstrap.css +4097 -0
  49. package/static/assets/css/jquery-ui.css +523 -0
  50. package/static/assets/css/jquery-ui.structure.css +863 -0
  51. package/static/assets/css/jquery-ui.structure.min.css +5 -0
  52. package/static/assets/css/jquery-ui.theme.css +439 -0
  53. package/static/assets/css/jquery-ui.theme.min.css +5 -0
  54. package/static/assets/css/jquery.ui.all.css +7 -0
  55. package/static/assets/css/modules.css +18 -0
  56. package/static/assets/css/project.css +367 -0
  57. package/static/assets/css/pygments-manni.css +66 -0
  58. package/static/assets/css/tags.css +74 -0
  59. package/static/assets/css/xml.css +2 -0
  60. package/static/assets/fonts/glyphiconshalflings-regular.eot +0 -0
  61. package/static/assets/fonts/glyphiconshalflings-regular.otf +0 -0
  62. package/static/assets/fonts/glyphiconshalflings-regular.svg +175 -0
  63. package/static/assets/fonts/glyphiconshalflings-regular.ttf +0 -0
  64. package/static/assets/fonts/glyphiconshalflings-regular.woff +0 -0
  65. package/static/assets/ico/apple-touch-icon-114-precomposed.png +0 -0
  66. package/static/assets/ico/apple-touch-icon-144-precomposed.png +0 -0
  67. package/static/assets/ico/apple-touch-icon-57-precomposed.png +0 -0
  68. package/static/assets/ico/apple-touch-icon-72-precomposed.png +0 -0
  69. package/static/assets/ico/favicon.ico +0 -0
  70. package/static/assets/ico/favicon.png +0 -0
  71. package/static/assets/images/fhir-logo-www.png +0 -0
  72. package/static/assets/images/fhir-logo.png +0 -0
  73. package/static/assets/images/hl7-logo.png +0 -0
  74. package/static/assets/images/logo_ansinew.jpg +0 -0
  75. package/static/assets/images/search.png +0 -0
  76. package/static/assets/images/stripe.png +0 -0
  77. package/static/assets/images/target.png +0 -0
  78. package/static/assets/images/tx-registry-root.gif +0 -0
  79. package/static/assets/images/tx-registry.png +0 -0
  80. package/static/assets/images/tx-server.png +0 -0
  81. package/static/assets/images/tx-version.png +0 -0
  82. package/static/assets/js/bootstrap.min.js +6 -0
  83. package/static/assets/js/fhir-gw.js +259 -0
  84. package/static/assets/js/fhir.js +2 -0
  85. package/static/assets/js/html5shiv.js +8 -0
  86. package/static/assets/js/jcookie.js +96 -0
  87. package/static/assets/js/jquery-ui.min.js +6 -0
  88. package/static/assets/js/jquery.js +10716 -0
  89. package/static/assets/js/jquery.min.js +2 -0
  90. package/static/assets/js/jquery.ui.core.js +314 -0
  91. package/static/assets/js/jquery.ui.draggable.js +825 -0
  92. package/static/assets/js/jquery.ui.mouse.js +162 -0
  93. package/static/assets/js/jquery.ui.resizable.js +842 -0
  94. package/static/assets/js/jquery.ui.widget.js +268 -0
  95. package/static/assets/js/json2.js +487 -0
  96. package/static/assets/js/jtip.js +97 -0
  97. package/static/assets/js/respond.min.js +6 -0
  98. package/static/assets/js/statuspage.js +70 -0
  99. package/static/assets/js/xml.js +2 -0
  100. package/static/dist/js/bootstrap.js +1964 -0
  101. package/static/favicon.png +0 -0
  102. package/static/fhir.css +626 -0
  103. package/static/icon-fhir-16.png +0 -0
  104. package/static/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
  105. package/static/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
  106. package/static/images/ui-bg_flat_10_000000_40x100.png +0 -0
  107. package/static/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
  108. package/static/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
  109. package/static/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  110. package/static/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
  111. package/static/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
  112. package/static/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
  113. package/static/images/ui-icons_222222_256x240.png +0 -0
  114. package/static/images/ui-icons_228ef1_256x240.png +0 -0
  115. package/static/images/ui-icons_ef8c08_256x240.png +0 -0
  116. package/static/images/ui-icons_ffd27a_256x240.png +0 -0
  117. package/static/images/ui-icons_ffffff_256x240.png +0 -0
  118. package/static/js/jquery.effects.blind.js +49 -0
  119. package/static/js/jquery.effects.bounce.js +78 -0
  120. package/static/js/jquery.effects.clip.js +54 -0
  121. package/static/js/jquery.effects.core.js +763 -0
  122. package/static/js/jquery.effects.drop.js +50 -0
  123. package/static/js/jquery.effects.explode.js +79 -0
  124. package/static/js/jquery.effects.fade.js +32 -0
  125. package/static/js/jquery.effects.fold.js +56 -0
  126. package/static/js/jquery.effects.highlight.js +50 -0
  127. package/static/js/jquery.effects.pulsate.js +51 -0
  128. package/static/js/jquery.effects.scale.js +178 -0
  129. package/static/js/jquery.effects.shake.js +57 -0
  130. package/static/js/jquery.effects.slide.js +50 -0
  131. package/static/js/jquery.effects.transfer.js +45 -0
  132. package/static/js/jquery.ui.accordion.js +611 -0
  133. package/static/js/jquery.ui.autocomplete.js +612 -0
  134. package/static/js/jquery.ui.button.js +416 -0
  135. package/static/js/jquery.ui.datepicker.js +1823 -0
  136. package/static/js/jquery.ui.dialog.js +878 -0
  137. package/static/js/jquery.ui.droppable.js +296 -0
  138. package/static/js/jquery.ui.position.js +252 -0
  139. package/static/js/jquery.ui.progressbar.js +109 -0
  140. package/static/js/jquery.ui.selectable.js +266 -0
  141. package/static/js/jquery.ui.slider.js +666 -0
  142. package/static/js/jquery.ui.sortable.js +1077 -0
  143. package/static/js/jquery.ui.tabs.js +758 -0
  144. package/stats.js +80 -0
  145. package/test-cache/vsac/vsac-valuesets.db +0 -0
  146. package/token/nginx_passport_setup.md +383 -0
  147. package/token/security_guide.md +294 -0
  148. package/token/token-template.html +330 -0
  149. package/token/token.js +1300 -0
  150. package/translations/Messages.properties +1510 -0
  151. package/translations/Messages_ar.properties +1399 -0
  152. package/translations/Messages_de.properties +836 -0
  153. package/translations/Messages_es.properties +737 -0
  154. package/translations/Messages_fr.properties +1 -0
  155. package/translations/Messages_ja.properties +893 -0
  156. package/translations/Messages_nl.properties +1357 -0
  157. package/translations/Messages_pt.properties +1302 -0
  158. package/translations/Messages_ru.properties +1 -0
  159. package/translations/Messages_uz.properties +1 -0
  160. package/translations/Messages_zh.properties +1 -0
  161. package/translations/rendering-phrases.properties +1128 -0
  162. package/translations/rendering-phrases_ar.properties +1091 -0
  163. package/translations/rendering-phrases_de.properties +6 -0
  164. package/translations/rendering-phrases_es.properties +6 -0
  165. package/translations/rendering-phrases_fr.properties +624 -0
  166. package/translations/rendering-phrases_ja.properties +21 -0
  167. package/translations/rendering-phrases_nl.properties +970 -0
  168. package/translations/rendering-phrases_pt.properties +1020 -0
  169. package/translations/rendering-phrases_ru.properties +1094 -0
  170. package/translations/rendering-phrases_uz.properties +1 -0
  171. package/translations/rendering-phrases_zh.properties +1 -0
  172. package/tx/README.md +418 -0
  173. package/tx/cm/cm-api.js +110 -0
  174. package/tx/cm/cm-database.js +735 -0
  175. package/tx/cm/cm-package.js +325 -0
  176. package/tx/cs/cs-api.js +789 -0
  177. package/tx/cs/cs-areacode.js +615 -0
  178. package/tx/cs/cs-country.js +1110 -0
  179. package/tx/cs/cs-cpt.js +785 -0
  180. package/tx/cs/cs-cs.js +1579 -0
  181. package/tx/cs/cs-currency.js +539 -0
  182. package/tx/cs/cs-db.js +1321 -0
  183. package/tx/cs/cs-hgvs.js +329 -0
  184. package/tx/cs/cs-lang.js +465 -0
  185. package/tx/cs/cs-loinc.js +1485 -0
  186. package/tx/cs/cs-mimetypes.js +238 -0
  187. package/tx/cs/cs-ndc.js +704 -0
  188. package/tx/cs/cs-omop.js +1025 -0
  189. package/tx/cs/cs-provider-api.js +43 -0
  190. package/tx/cs/cs-provider-list.js +37 -0
  191. package/tx/cs/cs-rxnorm.js +808 -0
  192. package/tx/cs/cs-snomed.js +1102 -0
  193. package/tx/cs/cs-ucum.js +514 -0
  194. package/tx/cs/cs-unii.js +271 -0
  195. package/tx/cs/cs-uri.js +218 -0
  196. package/tx/cs/cs-usstates.js +305 -0
  197. package/tx/dev.fhir.org.yml +14 -0
  198. package/tx/fixtures/test-cases-setup.json +18 -0
  199. package/tx/fixtures/test-cases.yml +16 -0
  200. package/tx/html/codesystem-operations.liquid +25 -0
  201. package/tx/html/home-metrics.liquid +247 -0
  202. package/tx/html/operations-form.liquid +148 -0
  203. package/tx/html/search-form.liquid +62 -0
  204. package/tx/html/tx-template.html +133 -0
  205. package/tx/html/valueset-operations.liquid +54 -0
  206. package/tx/importers/atc-to-fhir.js +316 -0
  207. package/tx/importers/import-loinc.module.js +1536 -0
  208. package/tx/importers/import-ndc.module.js +1088 -0
  209. package/tx/importers/import-rxnorm.module.js +898 -0
  210. package/tx/importers/import-sct.module.js +2457 -0
  211. package/tx/importers/import-unii.module.js +601 -0
  212. package/tx/importers/readme.md +453 -0
  213. package/tx/importers/subset-loinc.module.js +1081 -0
  214. package/tx/importers/subset-rxnorm.module.js +938 -0
  215. package/tx/importers/tx-import-base.js +351 -0
  216. package/tx/importers/tx-import-settings.js +310 -0
  217. package/tx/importers/tx-import.js +357 -0
  218. package/tx/library/canonical-resource.js +88 -0
  219. package/tx/library/capabilitystatement.js +292 -0
  220. package/tx/library/codesystem.js +774 -0
  221. package/tx/library/conceptmap.js +568 -0
  222. package/tx/library/designations.js +932 -0
  223. package/tx/library/errors.js +77 -0
  224. package/tx/library/extensions.js +117 -0
  225. package/tx/library/namingsystem.js +322 -0
  226. package/tx/library/operation-outcome.js +127 -0
  227. package/tx/library/parameters.js +105 -0
  228. package/tx/library/renderer.js +1559 -0
  229. package/tx/library/terminologycapabilities.js +418 -0
  230. package/tx/library/ucum-parsers.js +1029 -0
  231. package/tx/library/ucum-service.js +370 -0
  232. package/tx/library/ucum-types.js +1099 -0
  233. package/tx/library/valueset.js +543 -0
  234. package/tx/library.js +676 -0
  235. package/tx/ocl/cm-ocl.js +106 -0
  236. package/tx/ocl/cs-ocl.js +39 -0
  237. package/tx/ocl/vs-ocl.js +105 -0
  238. package/tx/operation-context.js +568 -0
  239. package/tx/params.js +613 -0
  240. package/tx/provider.js +403 -0
  241. package/tx/sct/ecl.js +1560 -0
  242. package/tx/sct/expressions.js +2077 -0
  243. package/tx/sct/structures.js +1396 -0
  244. package/tx/tx-html.js +1063 -0
  245. package/tx/tx.fhir.org.yml +39 -0
  246. package/tx/tx.js +927 -0
  247. package/tx/vs/vs-api.js +112 -0
  248. package/tx/vs/vs-database.js +786 -0
  249. package/tx/vs/vs-package.js +358 -0
  250. package/tx/vs/vs-vsac.js +366 -0
  251. package/tx/workers/batch-validate.js +129 -0
  252. package/tx/workers/batch.js +361 -0
  253. package/tx/workers/closure.js +32 -0
  254. package/tx/workers/expand.js +1845 -0
  255. package/tx/workers/lookup.js +407 -0
  256. package/tx/workers/metadata.js +467 -0
  257. package/tx/workers/operations.js +34 -0
  258. package/tx/workers/read.js +164 -0
  259. package/tx/workers/search.js +384 -0
  260. package/tx/workers/subsumes.js +334 -0
  261. package/tx/workers/translate.js +492 -0
  262. package/tx/workers/validate.js +2504 -0
  263. package/tx/workers/worker.js +904 -0
  264. package/tx/xml/capabilitystatement-xml.js +63 -0
  265. package/tx/xml/codesystem-xml.js +62 -0
  266. package/tx/xml/conceptmap-xml.js +65 -0
  267. package/tx/xml/namingsystem-xml.js +65 -0
  268. package/tx/xml/operationoutcome-xml.js +127 -0
  269. package/tx/xml/parameters-xml.js +312 -0
  270. package/tx/xml/terminologycapabilities-xml.js +64 -0
  271. package/tx/xml/valueset-xml.js +64 -0
  272. package/tx/xml/xml-base.js +603 -0
  273. package/vcl/vcl-parser.js +1098 -0
  274. package/vcl/vcl.js +253 -0
  275. package/windows-install.js +19 -0
  276. package/xig/xig-template.html +124 -0
  277. package/xig/xig.js +3049 -0
@@ -0,0 +1,351 @@
1
+ const inquirer = require('inquirer');
2
+ const chalk = require('chalk');
3
+ const cliProgress = require('cli-progress');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const { getConfigManager } = require('./tx-import-settings');
7
+
8
+ /**
9
+ * Base class for terminology import modules
10
+ * All terminology importers should extend this class
11
+ */
12
+ class BaseTerminologyModule {
13
+ constructor() {
14
+ this.progressBar = null;
15
+ this.configManager = getConfigManager();
16
+ }
17
+
18
+ // Abstract methods - must be implemented by subclasses
19
+ getName() {
20
+ throw new Error('getName() must be implemented by subclass');
21
+ }
22
+
23
+ getDescription() {
24
+ throw new Error('getDescription() must be implemented by subclass');
25
+ }
26
+
27
+ // eslint-disable-next-line no-unused-vars
28
+ registerCommands(terminologyCommand, globalOptions) {
29
+ throw new Error('registerCommands() must be implemented by subclass');
30
+ }
31
+
32
+ // Optional methods - can be overridden by subclasses
33
+ getSupportedFormats() {
34
+ return ['txt', 'csv', 'tsv'];
35
+ }
36
+
37
+ getEstimatedDuration() {
38
+ return 'varies';
39
+ }
40
+
41
+ getDefaultConfig() {
42
+ return {
43
+ verbose: true,
44
+ overwrite: false,
45
+ createIndexes: true
46
+ };
47
+ }
48
+
49
+ // Common utility methods available to all modules
50
+ async gatherCommonConfig(options = {}) {
51
+ const terminology = this.getName();
52
+
53
+ // Get intelligent defaults based on previous usage
54
+ const smartDefaults = this.configManager.generateDefaults(terminology);
55
+ const recentSources = this.configManager.getRecentSources(terminology, 3);
56
+
57
+ const questions = [];
58
+
59
+ // Source file/directory
60
+ if (!options.source) {
61
+ // If we have recent sources, offer them as choices
62
+ if (recentSources.length > 0) {
63
+ questions.push({
64
+ type: 'list',
65
+ name: 'sourceChoice',
66
+ message: `Select source for ${terminology}:`,
67
+ choices: [
68
+ ...recentSources.map(src => ({
69
+ name: `${src} ${src === smartDefaults.source ? '(last used)' : ''}`.trim(),
70
+ value: src
71
+ })),
72
+ { name: 'Enter new path...', value: 'NEW_PATH' }
73
+ ]
74
+ });
75
+
76
+ // Follow up question for new path
77
+ questions.push({
78
+ type: 'input',
79
+ name: 'newSource',
80
+ message: `Enter new source path for ${terminology}:`,
81
+ when: (answers) => answers.sourceChoice === 'NEW_PATH',
82
+ validate: (input) => {
83
+ if (!input) return 'Source is required';
84
+ if (!fs.existsSync(input)) return 'Source path does not exist';
85
+ return true;
86
+ },
87
+ filter: (input) => path.resolve(input)
88
+ });
89
+ } else {
90
+ // No recent sources, just ask for input directly
91
+ questions.push({
92
+ type: 'input',
93
+ name: 'source',
94
+ message: `Source file/directory for ${terminology}:`,
95
+ default: smartDefaults.source,
96
+ validate: (input) => {
97
+ if (!input) return 'Source is required';
98
+ if (!fs.existsSync(input)) return 'Source path does not exist';
99
+ return true;
100
+ },
101
+ filter: (input) => path.resolve(input)
102
+ });
103
+ }
104
+ }
105
+
106
+ // Destination database
107
+ if (!options.dest) {
108
+ questions.push({
109
+ type: 'input',
110
+ name: 'dest',
111
+ message: 'Destination database path:',
112
+ default: smartDefaults.dest || `./data/${terminology}.db`,
113
+ validate: (input) => {
114
+ if (!input) return 'Destination path is required';
115
+ const dir = path.dirname(input);
116
+ if (!fs.existsSync(dir)) {
117
+ return `Directory does not exist: ${dir}`;
118
+ }
119
+ return true;
120
+ },
121
+ filter: (input) => path.resolve(input)
122
+ });
123
+ }
124
+
125
+ // Overwrite confirmation
126
+ questions.push({
127
+ type: 'confirm',
128
+ name: 'overwrite',
129
+ message: 'Overwrite existing database if it exists?',
130
+ default: smartDefaults.overwrite !== undefined ? smartDefaults.overwrite : false,
131
+ when: (answers) => {
132
+ const destPath = options.dest || answers.dest;
133
+ return fs.existsSync(destPath);
134
+ }
135
+ });
136
+
137
+ questions.push({
138
+ type: 'confirm',
139
+ name: 'verbose',
140
+ message: 'Show verbose progress output?',
141
+ default: smartDefaults.verbose !== undefined ? smartDefaults.verbose : true
142
+ });
143
+
144
+ const answers = await inquirer.prompt(questions);
145
+
146
+ // Handle source selection logic
147
+ let finalSource = options.source;
148
+ if (!finalSource) {
149
+ if (answers.sourceChoice === 'NEW_PATH') {
150
+ finalSource = answers.newSource;
151
+ } else if (answers.sourceChoice) {
152
+ finalSource = answers.sourceChoice;
153
+ } else {
154
+ finalSource = answers.source;
155
+ }
156
+ }
157
+
158
+ const finalConfig = {
159
+ ...this.getDefaultConfig(),
160
+ ...smartDefaults,
161
+ ...options,
162
+ ...answers,
163
+ source: finalSource
164
+ };
165
+
166
+ return finalConfig;
167
+ }
168
+
169
+ async confirmImport(config) {
170
+ console.log(chalk.cyan(`\n📋 ${this.getName()} Import Configuration:`));
171
+ console.log(` Source: ${chalk.white(config.source)}`);
172
+ console.log(` Destination: ${chalk.white(config.dest)}`);
173
+ console.log(` Version: ${chalk.white(config.version || 'Not specified')}`);
174
+ console.log(` Overwrite: ${chalk.white(config.overwrite ? 'Yes' : 'No')}`);
175
+
176
+ if (config.estimatedDuration) {
177
+ console.log(` Estimated Duration: ${chalk.white(config.estimatedDuration)}`);
178
+ }
179
+
180
+ const { confirmed } = await inquirer.prompt({
181
+ type: 'confirm',
182
+ name: 'confirmed',
183
+ message: 'Proceed with import?',
184
+ default: true
185
+ });
186
+
187
+ return confirmed;
188
+ }
189
+
190
+ createProgressBar(format = null) {
191
+ const defaultFormat = chalk.cyan('Progress') + ' |{bar}| {percentage}% | {value}/{total} | ETA: {eta}s';
192
+
193
+ this.progressBar = new cliProgress.SingleBar({
194
+ format: format || defaultFormat,
195
+ barCompleteChar: '█',
196
+ barIncompleteChar: '░',
197
+ hideCursor: true
198
+ });
199
+
200
+ return this.progressBar;
201
+ }
202
+
203
+ updateProgress(current, total = null) {
204
+ if (this.progressBar) {
205
+ if (total !== null) {
206
+ this.progressBar.start(total, current);
207
+ } else {
208
+ this.progressBar.update(current);
209
+ }
210
+ }
211
+ }
212
+
213
+ stopProgress() {
214
+ if (this.progressBar) {
215
+ this.progressBar.stop();
216
+ this.progressBar = null;
217
+ }
218
+ }
219
+
220
+ logInfo(message) {
221
+ console.log(chalk.blue('ℹ'), message);
222
+ }
223
+
224
+ logSuccess(message) {
225
+ console.log(chalk.green('✓'), message);
226
+ }
227
+
228
+ logWarning(message) {
229
+ console.log(chalk.yellow('⚠'), message);
230
+ }
231
+
232
+ logError(message) {
233
+ console.log(chalk.red('✗'), message);
234
+ }
235
+
236
+ async validatePrerequisites(config) {
237
+ // Base validation - can be extended by subclasses
238
+ const checks = [
239
+ {
240
+ name: 'Source exists',
241
+ check: () => fs.existsSync(config.source)
242
+ },
243
+ {
244
+ name: 'Destination directory writable',
245
+ check: () => {
246
+ const dir = path.dirname(config.dest);
247
+ return fs.existsSync(dir);
248
+ }
249
+ }
250
+ ];
251
+
252
+ let allPassed = true;
253
+
254
+ for (const { name, check } of checks) {
255
+ try {
256
+ const passed = await check();
257
+ if (passed) {
258
+ this.logSuccess(name);
259
+ } else {
260
+ this.logError(name);
261
+ allPassed = false;
262
+ }
263
+ } catch (error) {
264
+ this.logError(`${name}: ${error.message}`);
265
+ allPassed = false;
266
+ }
267
+ }
268
+
269
+ return allPassed;
270
+ }
271
+
272
+ // Helper method for batch processing with progress updates
273
+ async processBatch(items, batchSize, processor, progressMessage = 'Processing') {
274
+ const total = items.length;
275
+ let processed = 0;
276
+
277
+ this.createProgressBar(
278
+ chalk.cyan(progressMessage) + ' |{bar}| {percentage}% | {value}/{total} items'
279
+ );
280
+ this.updateProgress(0, total);
281
+
282
+ for (let i = 0; i < total; i += batchSize) {
283
+ const batch = items.slice(i, i + batchSize);
284
+ await processor(batch);
285
+ processed += batch.length;
286
+ this.updateProgress(processed);
287
+ }
288
+
289
+ this.stopProgress();
290
+ return processed;
291
+ }
292
+
293
+ // Abstract method for actual import logic
294
+ // eslint-disable-next-line no-unused-vars
295
+ async executeImport(config) {
296
+ throw new Error('executeImport() must be implemented by subclass');
297
+ }
298
+
299
+ // Common import workflow
300
+ async runImport(config) {
301
+ try {
302
+ console.log(chalk.blue.bold(`🏥 Starting ${this.getName()} Import...\n`));
303
+
304
+ // Pre-flight checks
305
+ this.logInfo('Running pre-flight checks...');
306
+ const prerequisitesPassed = await this.validatePrerequisites(config);
307
+
308
+ if (!prerequisitesPassed) {
309
+ throw new Error('Pre-flight checks failed');
310
+ }
311
+
312
+ // Execute the import
313
+ await this.executeImport(config);
314
+
315
+ // Remember successful configuration for future use
316
+ this.rememberSuccessfulConfig(config);
317
+
318
+ this.logSuccess(`${this.getName()} import completed successfully!`);
319
+
320
+ } catch (error) {
321
+ this.stopProgress(); // Ensure progress bar is cleaned up
322
+ this.logError(`${this.getName()} import failed: ${error.message}`);
323
+ if (config.verbose) {
324
+ console.error(error.stack);
325
+ }
326
+ process.exit(1);
327
+ }
328
+ }
329
+
330
+ // Remember successful configuration
331
+ rememberSuccessfulConfig(config) {
332
+ try {
333
+ const terminology = this.getName();
334
+
335
+ // Remember the source path in recent sources
336
+ if (config.source) {
337
+ this.configManager.rememberSource(terminology, config.source);
338
+ }
339
+
340
+ // Remember the full configuration
341
+ this.configManager.rememberConfig(terminology, config);
342
+
343
+ this.logInfo('Configuration saved for future use');
344
+ } catch (error) {
345
+ // Don't fail the import if we can't save config
346
+ this.logWarning(`Could not save configuration: ${error.message}`);
347
+ }
348
+ }
349
+ }
350
+
351
+ module.exports = { BaseTerminologyModule };
@@ -0,0 +1,310 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+
5
+ /**
6
+ * Manages persistent configuration for terminology import tools
7
+ * Remembers previous inputs and uses them as defaults
8
+ */
9
+ class ConfigManager {
10
+ constructor() {
11
+ this.configDir = path.join(os.homedir(), '.tx-import');
12
+ this.configFile = path.join(this.configDir, 'config.json');
13
+ this.historyFile = path.join(this.configDir, 'history.json');
14
+ this.config = {};
15
+ this.history = {};
16
+
17
+ this.ensureConfigDir();
18
+ this.loadConfig();
19
+ this.loadHistory();
20
+ }
21
+
22
+ ensureConfigDir() {
23
+ if (!fs.existsSync(this.configDir)) {
24
+ fs.mkdirSync(this.configDir, { recursive: true });
25
+ }
26
+ }
27
+
28
+ loadConfig() {
29
+ try {
30
+ if (fs.existsSync(this.configFile)) {
31
+ this.config = JSON.parse(fs.readFileSync(this.configFile, 'utf8'));
32
+ }
33
+ } catch (error) {
34
+ console.warn(`Warning: Could not load config: ${error.message}`);
35
+ this.config = {};
36
+ }
37
+ }
38
+
39
+ loadHistory() {
40
+ try {
41
+ if (fs.existsSync(this.historyFile)) {
42
+ this.history = JSON.parse(fs.readFileSync(this.historyFile, 'utf8'));
43
+ }
44
+ } catch (error) {
45
+ console.warn(`Warning: Could not load history: ${error.message}`);
46
+ this.history = {};
47
+ }
48
+ }
49
+
50
+ saveConfig() {
51
+ try {
52
+ fs.writeFileSync(this.configFile, JSON.stringify(this.config, null, 2));
53
+ } catch (error) {
54
+ console.warn(`Warning: Could not save config: ${error.message}`);
55
+ }
56
+ }
57
+
58
+ saveHistory() {
59
+ try {
60
+ fs.writeFileSync(this.historyFile, JSON.stringify(this.history, null, 2));
61
+ } catch (error) {
62
+ console.warn(`Warning: Could not save history: ${error.message}`);
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Get previous value for a specific terminology and field
68
+ * @param {string} terminology - e.g., 'unii', 'loinc'
69
+ * @param {string} field - e.g., 'source', 'dest', 'version'
70
+ * @returns {string|null} Previous value or null if not found
71
+ */
72
+ getPreviousValue(terminology, field) {
73
+ return this.history[terminology]?.[field] || null;
74
+ }
75
+
76
+ /**
77
+ * Get all previous values for a terminology
78
+ * @param {string} terminology
79
+ * @returns {object} Object with all previous values
80
+ */
81
+ getPreviousConfig(terminology) {
82
+ return this.history[terminology] || {};
83
+ }
84
+
85
+ /**
86
+ * Remember a successful configuration
87
+ * @param {string} terminology
88
+ * @param {object} config
89
+ */
90
+ rememberConfig(terminology, config) {
91
+ if (!this.history[terminology]) {
92
+ this.history[terminology] = {};
93
+ }
94
+
95
+ // Store important fields
96
+ const fieldsToRemember = ['source', 'dest', 'version', 'languages', 'overwrite', 'verbose'];
97
+
98
+ fieldsToRemember.forEach(field => {
99
+ if (config[field] !== undefined) {
100
+ this.history[terminology][field] = config[field];
101
+ }
102
+ });
103
+
104
+ // Store timestamp of last use
105
+ this.history[terminology].lastUsed = new Date().toISOString();
106
+
107
+ this.saveHistory();
108
+ }
109
+
110
+ /**
111
+ * Get recent source directories for a terminology
112
+ * @param {string} terminology
113
+ * @param {number} limit
114
+ * @returns {string[]} Array of recent source paths
115
+ */
116
+ getRecentSources(terminology, limit = 5) {
117
+ const termHistory = this.history[terminology];
118
+ if (!termHistory || !termHistory.recentSources) {
119
+ return [];
120
+ }
121
+
122
+ return termHistory.recentSources.slice(0, limit);
123
+ }
124
+
125
+ /**
126
+ * Remember a source path
127
+ * @param {string} terminology
128
+ * @param {string} sourcePath
129
+ */
130
+ rememberSource(terminology, sourcePath) {
131
+ if (!this.history[terminology]) {
132
+ this.history[terminology] = {};
133
+ }
134
+
135
+ if (!this.history[terminology].recentSources) {
136
+ this.history[terminology].recentSources = [];
137
+ }
138
+
139
+ const sources = this.history[terminology].recentSources;
140
+
141
+ // Remove if already exists
142
+ const index = sources.indexOf(sourcePath);
143
+ if (index > -1) {
144
+ sources.splice(index, 1);
145
+ }
146
+
147
+ // Add to front
148
+ sources.unshift(sourcePath);
149
+
150
+ // Keep only last 10
151
+ this.history[terminology].recentSources = sources.slice(0, 10);
152
+
153
+ this.saveHistory();
154
+ }
155
+
156
+ /**
157
+ * Clear history for a specific terminology
158
+ * @param {string} terminology
159
+ */
160
+ clearHistory(terminology) {
161
+ if (this.history[terminology]) {
162
+ delete this.history[terminology];
163
+ this.saveHistory();
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Clear all history
169
+ */
170
+ clearAllHistory() {
171
+ this.history = {};
172
+ this.saveHistory();
173
+ }
174
+
175
+ /**
176
+ * Get global preferences
177
+ * @param {string} key
178
+ * @param {any} defaultValue
179
+ * @returns {any}
180
+ */
181
+ getPreference(key, defaultValue = null) {
182
+ return this.config[key] !== undefined ? this.config[key] : defaultValue;
183
+ }
184
+
185
+ /**
186
+ * Set global preference
187
+ * @param {string} key
188
+ * @param {any} value
189
+ */
190
+ setPreference(key, value) {
191
+ this.config[key] = value;
192
+ this.saveConfig();
193
+ }
194
+
195
+ /**
196
+ * Generate intelligent defaults based on previous usage
197
+ * @param {string} terminology
198
+ * @returns {object} Suggested defaults
199
+ */
200
+ generateDefaults(terminology) {
201
+ const previous = this.getPreviousConfig(terminology);
202
+ const defaults = {};
203
+
204
+ // Default source - use most recent
205
+ if (previous.source) {
206
+ defaults.source = previous.source;
207
+ }
208
+
209
+ // Default destination - increment version or use pattern
210
+ if (previous.dest) {
211
+ defaults.dest = this.suggestNextDestination(previous.dest, terminology);
212
+ } else {
213
+ defaults.dest = `./data/${terminology}.db`;
214
+ }
215
+
216
+ // Default version - increment or use date pattern
217
+ if (previous.version) {
218
+ defaults.version = this.suggestNextVersion(previous.version, terminology);
219
+ } else {
220
+ const date = new Date().toISOString().split('T')[0];
221
+ defaults.version = `${terminology.toUpperCase()}-${date}`;
222
+ }
223
+
224
+ // Inherit boolean preferences
225
+ if (previous.verbose !== undefined) defaults.verbose = previous.verbose;
226
+ if (previous.overwrite !== undefined) defaults.overwrite = previous.overwrite;
227
+ if (previous.languages !== undefined) defaults.languages = previous.languages;
228
+
229
+ return defaults;
230
+ }
231
+
232
+ suggestNextDestination(previousDest) {
233
+ // If previous dest was versioned, suggest incrementing
234
+ const versionMatch = previousDest.match(/-v(\d+)\.db$/);
235
+ if (versionMatch) {
236
+ const nextVersion = parseInt(versionMatch[1]) + 1;
237
+ return previousDest.replace(/-v\d+\.db$/, `-v${nextVersion}.db`);
238
+ }
239
+
240
+ // If it had a date, suggest today's date
241
+ const dateMatch = previousDest.match(/-(\d{4}-\d{2}-\d{2})\.db$/);
242
+ if (dateMatch) {
243
+ const today = new Date().toISOString().split('T')[0];
244
+ return previousDest.replace(/-\d{4}-\d{2}-\d{2}\.db$/, `-${today}.db`);
245
+ }
246
+
247
+ // Otherwise, just return the same path
248
+ return previousDest;
249
+ }
250
+
251
+ suggestNextVersion(previousVersion) {
252
+ // If version has a date, suggest today's date
253
+ const dateMatch = previousVersion.match(/-(\d{4}-\d{2}-\d{2})$/);
254
+ if (dateMatch) {
255
+ const today = new Date().toISOString().split('T')[0];
256
+ return previousVersion.replace(/-\d{4}-\d{2}-\d{2}$/, `-${today}`);
257
+ }
258
+
259
+ // If version has a number, increment it
260
+ const numberMatch = previousVersion.match(/-(\d+)$/);
261
+ if (numberMatch) {
262
+ const nextNumber = parseInt(numberMatch[1]) + 1;
263
+ return previousVersion.replace(/-\d+$/, `-${nextNumber}`);
264
+ }
265
+
266
+ // Otherwise, add today's date
267
+ const today = new Date().toISOString().split('T')[0];
268
+ return `${previousVersion}-${today}`;
269
+ }
270
+
271
+ /**
272
+ * Export configuration for backup
273
+ * @returns {object} Complete configuration
274
+ */
275
+ exportConfig() {
276
+ return {
277
+ config: this.config,
278
+ history: this.history,
279
+ exportDate: new Date().toISOString()
280
+ };
281
+ }
282
+
283
+ /**
284
+ * Import configuration from backup
285
+ * @param {object} data
286
+ */
287
+ importConfig(data) {
288
+ if (data.config) {
289
+ this.config = { ...this.config, ...data.config };
290
+ this.saveConfig();
291
+ }
292
+
293
+ if (data.history) {
294
+ this.history = { ...this.history, ...data.history };
295
+ this.saveHistory();
296
+ }
297
+ }
298
+ }
299
+
300
+ // Singleton instance
301
+ let configManagerInstance = null;
302
+
303
+ function getConfigManager() {
304
+ if (!configManagerInstance) {
305
+ configManagerInstance = new ConfigManager();
306
+ }
307
+ return configManagerInstance;
308
+ }
309
+
310
+ module.exports = { ConfigManager, getConfigManager };