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.
- package/CHANGELOG.md +42 -0
- package/FHIRsmith.png +0 -0
- package/README.md +277 -0
- package/config-template.json +144 -0
- package/library/folder-setup.js +58 -0
- package/library/html-server.js +166 -0
- package/library/html.js +835 -0
- package/library/i18nsupport.js +259 -0
- package/library/languages.js +779 -0
- package/library/logger-telnet.js +205 -0
- package/library/logger.js +279 -0
- package/library/package-manager.js +876 -0
- package/library/utilities.js +196 -0
- package/library/version-utilities.js +1056 -0
- package/npmprojector/config-example.json +13 -0
- package/npmprojector/indexer.js +394 -0
- package/npmprojector/npmprojector.js +395 -0
- package/npmprojector/readme.md +174 -0
- package/npmprojector/watcher.js +335 -0
- package/package.json +119 -0
- package/packages/package-crawler.js +846 -0
- package/packages/packages-template.html +126 -0
- package/packages/packages.js +2838 -0
- package/passwords.ini +2 -0
- package/publisher/publisher-template.html +208 -0
- package/publisher/publisher.js +2167 -0
- package/publisher/task-draft.js +458 -0
- package/registry/api.js +735 -0
- package/registry/crawler.js +637 -0
- package/registry/model.js +513 -0
- package/registry/readme.md +243 -0
- package/registry/registry-data.json +121015 -0
- package/registry/registry-template.html +126 -0
- package/registry/registry.js +1395 -0
- package/registry/test-runner.js +237 -0
- package/root-template.html +124 -0
- package/server.js +524 -0
- package/shl/private-key.pem +5 -0
- package/shl/public-key.pem +18 -0
- package/shl/shl.js +1125 -0
- package/shl/vhl.js +69 -0
- package/static/FHIRsmith128.png +0 -0
- package/static/FHIRsmith16.png +0 -0
- package/static/FHIRsmith32.png +0 -0
- package/static/FHIRsmith64.png +0 -0
- package/static/assets/css/bootstrap-fhir.css +5302 -0
- package/static/assets/css/bootstrap-glyphicons.css +2 -0
- package/static/assets/css/bootstrap.css +4097 -0
- package/static/assets/css/jquery-ui.css +523 -0
- package/static/assets/css/jquery-ui.structure.css +863 -0
- package/static/assets/css/jquery-ui.structure.min.css +5 -0
- package/static/assets/css/jquery-ui.theme.css +439 -0
- package/static/assets/css/jquery-ui.theme.min.css +5 -0
- package/static/assets/css/jquery.ui.all.css +7 -0
- package/static/assets/css/modules.css +18 -0
- package/static/assets/css/project.css +367 -0
- package/static/assets/css/pygments-manni.css +66 -0
- package/static/assets/css/tags.css +74 -0
- package/static/assets/css/xml.css +2 -0
- package/static/assets/fonts/glyphiconshalflings-regular.eot +0 -0
- package/static/assets/fonts/glyphiconshalflings-regular.otf +0 -0
- package/static/assets/fonts/glyphiconshalflings-regular.svg +175 -0
- package/static/assets/fonts/glyphiconshalflings-regular.ttf +0 -0
- package/static/assets/fonts/glyphiconshalflings-regular.woff +0 -0
- package/static/assets/ico/apple-touch-icon-114-precomposed.png +0 -0
- package/static/assets/ico/apple-touch-icon-144-precomposed.png +0 -0
- package/static/assets/ico/apple-touch-icon-57-precomposed.png +0 -0
- package/static/assets/ico/apple-touch-icon-72-precomposed.png +0 -0
- package/static/assets/ico/favicon.ico +0 -0
- package/static/assets/ico/favicon.png +0 -0
- package/static/assets/images/fhir-logo-www.png +0 -0
- package/static/assets/images/fhir-logo.png +0 -0
- package/static/assets/images/hl7-logo.png +0 -0
- package/static/assets/images/logo_ansinew.jpg +0 -0
- package/static/assets/images/search.png +0 -0
- package/static/assets/images/stripe.png +0 -0
- package/static/assets/images/target.png +0 -0
- package/static/assets/images/tx-registry-root.gif +0 -0
- package/static/assets/images/tx-registry.png +0 -0
- package/static/assets/images/tx-server.png +0 -0
- package/static/assets/images/tx-version.png +0 -0
- package/static/assets/js/bootstrap.min.js +6 -0
- package/static/assets/js/fhir-gw.js +259 -0
- package/static/assets/js/fhir.js +2 -0
- package/static/assets/js/html5shiv.js +8 -0
- package/static/assets/js/jcookie.js +96 -0
- package/static/assets/js/jquery-ui.min.js +6 -0
- package/static/assets/js/jquery.js +10716 -0
- package/static/assets/js/jquery.min.js +2 -0
- package/static/assets/js/jquery.ui.core.js +314 -0
- package/static/assets/js/jquery.ui.draggable.js +825 -0
- package/static/assets/js/jquery.ui.mouse.js +162 -0
- package/static/assets/js/jquery.ui.resizable.js +842 -0
- package/static/assets/js/jquery.ui.widget.js +268 -0
- package/static/assets/js/json2.js +487 -0
- package/static/assets/js/jtip.js +97 -0
- package/static/assets/js/respond.min.js +6 -0
- package/static/assets/js/statuspage.js +70 -0
- package/static/assets/js/xml.js +2 -0
- package/static/dist/js/bootstrap.js +1964 -0
- package/static/favicon.png +0 -0
- package/static/fhir.css +626 -0
- package/static/icon-fhir-16.png +0 -0
- package/static/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
- package/static/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
- package/static/images/ui-bg_flat_10_000000_40x100.png +0 -0
- package/static/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
- package/static/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
- package/static/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- package/static/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
- package/static/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
- package/static/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
- package/static/images/ui-icons_222222_256x240.png +0 -0
- package/static/images/ui-icons_228ef1_256x240.png +0 -0
- package/static/images/ui-icons_ef8c08_256x240.png +0 -0
- package/static/images/ui-icons_ffd27a_256x240.png +0 -0
- package/static/images/ui-icons_ffffff_256x240.png +0 -0
- package/static/js/jquery.effects.blind.js +49 -0
- package/static/js/jquery.effects.bounce.js +78 -0
- package/static/js/jquery.effects.clip.js +54 -0
- package/static/js/jquery.effects.core.js +763 -0
- package/static/js/jquery.effects.drop.js +50 -0
- package/static/js/jquery.effects.explode.js +79 -0
- package/static/js/jquery.effects.fade.js +32 -0
- package/static/js/jquery.effects.fold.js +56 -0
- package/static/js/jquery.effects.highlight.js +50 -0
- package/static/js/jquery.effects.pulsate.js +51 -0
- package/static/js/jquery.effects.scale.js +178 -0
- package/static/js/jquery.effects.shake.js +57 -0
- package/static/js/jquery.effects.slide.js +50 -0
- package/static/js/jquery.effects.transfer.js +45 -0
- package/static/js/jquery.ui.accordion.js +611 -0
- package/static/js/jquery.ui.autocomplete.js +612 -0
- package/static/js/jquery.ui.button.js +416 -0
- package/static/js/jquery.ui.datepicker.js +1823 -0
- package/static/js/jquery.ui.dialog.js +878 -0
- package/static/js/jquery.ui.droppable.js +296 -0
- package/static/js/jquery.ui.position.js +252 -0
- package/static/js/jquery.ui.progressbar.js +109 -0
- package/static/js/jquery.ui.selectable.js +266 -0
- package/static/js/jquery.ui.slider.js +666 -0
- package/static/js/jquery.ui.sortable.js +1077 -0
- package/static/js/jquery.ui.tabs.js +758 -0
- package/stats.js +80 -0
- package/test-cache/vsac/vsac-valuesets.db +0 -0
- package/token/nginx_passport_setup.md +383 -0
- package/token/security_guide.md +294 -0
- package/token/token-template.html +330 -0
- package/token/token.js +1300 -0
- package/translations/Messages.properties +1510 -0
- package/translations/Messages_ar.properties +1399 -0
- package/translations/Messages_de.properties +836 -0
- package/translations/Messages_es.properties +737 -0
- package/translations/Messages_fr.properties +1 -0
- package/translations/Messages_ja.properties +893 -0
- package/translations/Messages_nl.properties +1357 -0
- package/translations/Messages_pt.properties +1302 -0
- package/translations/Messages_ru.properties +1 -0
- package/translations/Messages_uz.properties +1 -0
- package/translations/Messages_zh.properties +1 -0
- package/translations/rendering-phrases.properties +1128 -0
- package/translations/rendering-phrases_ar.properties +1091 -0
- package/translations/rendering-phrases_de.properties +6 -0
- package/translations/rendering-phrases_es.properties +6 -0
- package/translations/rendering-phrases_fr.properties +624 -0
- package/translations/rendering-phrases_ja.properties +21 -0
- package/translations/rendering-phrases_nl.properties +970 -0
- package/translations/rendering-phrases_pt.properties +1020 -0
- package/translations/rendering-phrases_ru.properties +1094 -0
- package/translations/rendering-phrases_uz.properties +1 -0
- package/translations/rendering-phrases_zh.properties +1 -0
- package/tx/README.md +418 -0
- package/tx/cm/cm-api.js +110 -0
- package/tx/cm/cm-database.js +735 -0
- package/tx/cm/cm-package.js +325 -0
- package/tx/cs/cs-api.js +789 -0
- package/tx/cs/cs-areacode.js +615 -0
- package/tx/cs/cs-country.js +1110 -0
- package/tx/cs/cs-cpt.js +785 -0
- package/tx/cs/cs-cs.js +1579 -0
- package/tx/cs/cs-currency.js +539 -0
- package/tx/cs/cs-db.js +1321 -0
- package/tx/cs/cs-hgvs.js +329 -0
- package/tx/cs/cs-lang.js +465 -0
- package/tx/cs/cs-loinc.js +1485 -0
- package/tx/cs/cs-mimetypes.js +238 -0
- package/tx/cs/cs-ndc.js +704 -0
- package/tx/cs/cs-omop.js +1025 -0
- package/tx/cs/cs-provider-api.js +43 -0
- package/tx/cs/cs-provider-list.js +37 -0
- package/tx/cs/cs-rxnorm.js +808 -0
- package/tx/cs/cs-snomed.js +1102 -0
- package/tx/cs/cs-ucum.js +514 -0
- package/tx/cs/cs-unii.js +271 -0
- package/tx/cs/cs-uri.js +218 -0
- package/tx/cs/cs-usstates.js +305 -0
- package/tx/dev.fhir.org.yml +14 -0
- package/tx/fixtures/test-cases-setup.json +18 -0
- package/tx/fixtures/test-cases.yml +16 -0
- package/tx/html/codesystem-operations.liquid +25 -0
- package/tx/html/home-metrics.liquid +247 -0
- package/tx/html/operations-form.liquid +148 -0
- package/tx/html/search-form.liquid +62 -0
- package/tx/html/tx-template.html +133 -0
- package/tx/html/valueset-operations.liquid +54 -0
- package/tx/importers/atc-to-fhir.js +316 -0
- package/tx/importers/import-loinc.module.js +1536 -0
- package/tx/importers/import-ndc.module.js +1088 -0
- package/tx/importers/import-rxnorm.module.js +898 -0
- package/tx/importers/import-sct.module.js +2457 -0
- package/tx/importers/import-unii.module.js +601 -0
- package/tx/importers/readme.md +453 -0
- package/tx/importers/subset-loinc.module.js +1081 -0
- package/tx/importers/subset-rxnorm.module.js +938 -0
- package/tx/importers/tx-import-base.js +351 -0
- package/tx/importers/tx-import-settings.js +310 -0
- package/tx/importers/tx-import.js +357 -0
- package/tx/library/canonical-resource.js +88 -0
- package/tx/library/capabilitystatement.js +292 -0
- package/tx/library/codesystem.js +774 -0
- package/tx/library/conceptmap.js +568 -0
- package/tx/library/designations.js +932 -0
- package/tx/library/errors.js +77 -0
- package/tx/library/extensions.js +117 -0
- package/tx/library/namingsystem.js +322 -0
- package/tx/library/operation-outcome.js +127 -0
- package/tx/library/parameters.js +105 -0
- package/tx/library/renderer.js +1559 -0
- package/tx/library/terminologycapabilities.js +418 -0
- package/tx/library/ucum-parsers.js +1029 -0
- package/tx/library/ucum-service.js +370 -0
- package/tx/library/ucum-types.js +1099 -0
- package/tx/library/valueset.js +543 -0
- package/tx/library.js +676 -0
- package/tx/ocl/cm-ocl.js +106 -0
- package/tx/ocl/cs-ocl.js +39 -0
- package/tx/ocl/vs-ocl.js +105 -0
- package/tx/operation-context.js +568 -0
- package/tx/params.js +613 -0
- package/tx/provider.js +403 -0
- package/tx/sct/ecl.js +1560 -0
- package/tx/sct/expressions.js +2077 -0
- package/tx/sct/structures.js +1396 -0
- package/tx/tx-html.js +1063 -0
- package/tx/tx.fhir.org.yml +39 -0
- package/tx/tx.js +927 -0
- package/tx/vs/vs-api.js +112 -0
- package/tx/vs/vs-database.js +786 -0
- package/tx/vs/vs-package.js +358 -0
- package/tx/vs/vs-vsac.js +366 -0
- package/tx/workers/batch-validate.js +129 -0
- package/tx/workers/batch.js +361 -0
- package/tx/workers/closure.js +32 -0
- package/tx/workers/expand.js +1845 -0
- package/tx/workers/lookup.js +407 -0
- package/tx/workers/metadata.js +467 -0
- package/tx/workers/operations.js +34 -0
- package/tx/workers/read.js +164 -0
- package/tx/workers/search.js +384 -0
- package/tx/workers/subsumes.js +334 -0
- package/tx/workers/translate.js +492 -0
- package/tx/workers/validate.js +2504 -0
- package/tx/workers/worker.js +904 -0
- package/tx/xml/capabilitystatement-xml.js +63 -0
- package/tx/xml/codesystem-xml.js +62 -0
- package/tx/xml/conceptmap-xml.js +65 -0
- package/tx/xml/namingsystem-xml.js +65 -0
- package/tx/xml/operationoutcome-xml.js +127 -0
- package/tx/xml/parameters-xml.js +312 -0
- package/tx/xml/terminologycapabilities-xml.js +64 -0
- package/tx/xml/valueset-xml.js +64 -0
- package/tx/xml/xml-base.js +603 -0
- package/vcl/vcl-parser.js +1098 -0
- package/vcl/vcl.js +253 -0
- package/windows-install.js +19 -0
- package/xig/xig-template.html +124 -0
- 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 };
|