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,458 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { spawn } = require('child_process');
4
+ const axios = require('axios');
5
+
6
+ class DraftTaskProcessor {
7
+ constructor(config, logger, logTaskMessage, updateTaskStatus) {
8
+ this.config = config;
9
+ this.logger = logger;
10
+ this.logTaskMessage = logTaskMessage;
11
+ this.updateTaskStatus = updateTaskStatus;
12
+ }
13
+
14
+ async processDraftBuild(task) {
15
+ this.logger.info('Processing draft build for task #' + task.id + ' (' + task.npm_package_id + '#' + task.version + ')');
16
+
17
+ try {
18
+ // Update status to building
19
+ await this.updateTaskStatus(task.id, 'building');
20
+ await this.logTaskMessage(task.id, 'info', 'Started draft build');
21
+
22
+ // Run actual build process
23
+ await this.runDraftBuild(task);
24
+
25
+ // Update status to waiting for approval
26
+ await this.updateTaskStatus(task.id, 'waiting for approval');
27
+ await this.logTaskMessage(task.id, 'info', 'Draft build completed - waiting for approval');
28
+
29
+ this.logger.info('Draft build completed for task #' + task.id);
30
+
31
+ } catch (error) {
32
+ this.logger.error('Draft build failed for task #' + task.id + ':', error);
33
+ await this.updateTaskStatus(task.id, 'failed', {
34
+ failure_reason: error.message
35
+ });
36
+ await this.logTaskMessage(task.id, 'error', 'Draft build failed: ' + error.message);
37
+ throw error; // Re-throw so the main processor knows it failed
38
+ }
39
+ }
40
+
41
+ async runDraftBuild(task) {
42
+ const taskDir = path.join(this.config.workspaceRoot, 'task-' + task.id);
43
+ const draftDir = path.join(taskDir, 'draft');
44
+ const logFile = path.join(taskDir, 'draft-build.log');
45
+
46
+ await this.logTaskMessage(task.id, 'info', 'Creating task directory: ' + taskDir);
47
+
48
+ // Step 1: Create/scrub task directory
49
+ await this.createTaskDirectory(taskDir);
50
+
51
+ // Step 2: Download latest publisher
52
+ const publisherJar = await this.downloadPublisher(taskDir, task.id);
53
+
54
+ // Step 3: Clone GitHub repository
55
+ await this.cloneRepository(task, draftDir);
56
+
57
+ // Step 4: Install FSH Sushi globally
58
+ await this.installFshSushi(task.id);
59
+
60
+ // Step 5: Run IG publisher
61
+ await this.runIGPublisher(publisherJar, draftDir, logFile, task.id);
62
+
63
+ // Update task with build output path
64
+ await this.updateTaskStatus(task.id, 'building', {
65
+ build_output_path: logFile,
66
+ local_folder: taskDir
67
+ });
68
+
69
+ this.logger.info('Draft build completed for ' + task.npm_package_id + '#' + task.version);
70
+ }
71
+
72
+ async createTaskDirectory(taskDir) {
73
+ await this.logTaskMessage(null, 'info', 'Creating/cleaning task directory: ' + taskDir);
74
+
75
+ // Remove existing directory if it exists
76
+ if (fs.existsSync(taskDir)) {
77
+ // Use Node.js built-in fs.rm (Node 14.14+) or fs.rmSync (Node 14.14+)
78
+ if (fs.promises && fs.promises.rm) {
79
+ // Use promise-based API
80
+ await fs.promises.rm(taskDir, { recursive: true, force: true });
81
+ } else if (fs.rmSync) {
82
+ // Use synchronous API
83
+ fs.rmSync(taskDir, { recursive: true, force: true });
84
+ } else {
85
+ // Fallback for older Node versions
86
+ const rimraf = require('rimraf');
87
+ await new Promise((resolve, reject) => {
88
+ if (typeof rimraf === 'function') {
89
+ rimraf(taskDir, (err) => {
90
+ if (err) reject(err);
91
+ else resolve();
92
+ });
93
+ } else if (rimraf.rimraf) {
94
+ rimraf.rimraf(taskDir).then(resolve).catch(reject);
95
+ } else {
96
+ reject(new Error('Unable to remove directory - unsupported rimraf version'));
97
+ }
98
+ });
99
+ }
100
+ }
101
+
102
+ // Create fresh directory
103
+ fs.mkdirSync(taskDir, { recursive: true });
104
+ }
105
+
106
+ async downloadPublisher(taskDir, taskId) {
107
+ const publisherJar = path.join(taskDir, 'publisher.jar');
108
+
109
+ await this.logTaskMessage(taskId, 'info', 'Downloading latest FHIR IG Publisher...');
110
+
111
+ // Ensure the target directory exists
112
+ if (!fs.existsSync(taskDir)) {
113
+ fs.mkdirSync(taskDir, { recursive: true });
114
+ }
115
+
116
+ try {
117
+ // Get latest release info from GitHub API
118
+ const releaseResponse = await axios.get('https://api.github.com/repos/HL7/fhir-ig-publisher/releases/latest', {
119
+ timeout: 30000 // 30 second timeout
120
+ });
121
+
122
+ const downloadUrl = releaseResponse.data.assets.find(asset =>
123
+ asset.name === 'publisher.jar'
124
+ )?.browser_download_url;
125
+
126
+ if (!downloadUrl) {
127
+ throw new Error('Could not find publisher.jar in latest release');
128
+ }
129
+
130
+ await this.logTaskMessage(taskId, 'info', 'Downloading from: ' + downloadUrl);
131
+
132
+ // Download the file with progress tracking
133
+ const response = await axios({
134
+ method: 'GET',
135
+ url: downloadUrl,
136
+ responseType: 'stream',
137
+ timeout: 300000 // 5 minute timeout for download
138
+ });
139
+
140
+ const writer = fs.createWriteStream(publisherJar);
141
+
142
+ // Track download progress
143
+ let downloadedBytes = 0;
144
+ const totalBytes = parseInt(response.headers['content-length'] || '0');
145
+ let lastProgressPercent = -1;
146
+
147
+ response.data.on('data', (chunk) => {
148
+ downloadedBytes += chunk.length;
149
+ if (totalBytes > 0) {
150
+ const progress = Math.round((downloadedBytes / totalBytes) * 100);
151
+ // Log every 10% but avoid duplicate logs
152
+ if (progress % 10 === 0 && progress !== lastProgressPercent) {
153
+ this.logTaskMessage(taskId, 'info', 'Download progress: ' + progress + '%');
154
+ lastProgressPercent = progress;
155
+ }
156
+ }
157
+ });
158
+
159
+ response.data.pipe(writer);
160
+
161
+ await new Promise((resolve, reject) => {
162
+ writer.on('finish', resolve);
163
+ writer.on('error', reject);
164
+ });
165
+
166
+ await this.logTaskMessage(taskId, 'info', 'Publisher downloaded successfully (' + Math.round(downloadedBytes / 1024 / 1024) + 'MB)');
167
+ return publisherJar;
168
+
169
+ } catch (error) {
170
+ if (error.code === 'ECONNABORTED') {
171
+ throw new Error('Publisher download timed out - please try again');
172
+ }
173
+ throw new Error('Failed to download publisher: ' + error.message);
174
+ }
175
+ }
176
+
177
+ async cloneRepository(task, draftDir) {
178
+ const gitUrl = 'https://github.com/' + task.github_org + '/' + task.github_repo + '.git';
179
+
180
+ await this.logTaskMessage(task.id, 'info', 'Cloning repository: ' + gitUrl + ' (branch: ' + task.git_branch + ')');
181
+
182
+ return new Promise((resolve, reject) => {
183
+ const git = spawn('git', [
184
+ 'clone',
185
+ '--branch', task.git_branch,
186
+ '--single-branch',
187
+ '--depth', '1', // Shallow clone for faster download
188
+ gitUrl,
189
+ draftDir
190
+ ], {
191
+ stdio: ['pipe', 'pipe', 'pipe']
192
+ });
193
+
194
+ let stdout = '';
195
+ let stderr = '';
196
+
197
+ git.stdout.on('data', (data) => {
198
+ stdout += data.toString();
199
+ });
200
+
201
+ git.stderr.on('data', (data) => {
202
+ stderr += data.toString();
203
+ });
204
+
205
+ git.on('close', async (code) => {
206
+ if (code === 0) {
207
+ await this.logTaskMessage(task.id, 'info', 'Repository cloned successfully');
208
+
209
+ // Log some info about what was cloned
210
+ try {
211
+ const stats = fs.statSync(draftDir);
212
+ if (stats.isDirectory()) {
213
+ const files = fs.readdirSync(draftDir);
214
+ await this.logTaskMessage(task.id, 'info', 'Cloned ' + files.length + ' files/directories');
215
+ }
216
+ } catch (e) {
217
+ // Don't fail if we can't get stats
218
+ }
219
+
220
+ resolve();
221
+ } else {
222
+ const error = 'Git clone failed with code ' + code + ': ' + stderr;
223
+ await this.logTaskMessage(task.id, 'error', error);
224
+ reject(new Error(error));
225
+ }
226
+ });
227
+
228
+ git.on('error', async (error) => {
229
+ await this.logTaskMessage(task.id, 'error', 'Git clone error: ' + error.message);
230
+ reject(error);
231
+ });
232
+
233
+ // Timeout for git clone (10 minutes)
234
+ const timeout = setTimeout(async () => {
235
+ git.kill();
236
+ await this.logTaskMessage(task.id, 'error', 'Git clone timed out after 10 minutes');
237
+ reject(new Error('Git clone timed out'));
238
+ }, 10 * 60 * 1000);
239
+
240
+ git.on('close', () => {
241
+ clearTimeout(timeout);
242
+ });
243
+ });
244
+ }
245
+
246
+ async installFshSushi(taskId) {
247
+ await this.logTaskMessage(taskId, 'info', 'Installing FSH Sushi globally...');
248
+
249
+ return new Promise((resolve, reject) => {
250
+ const npm = spawn('npm', [
251
+ 'install',
252
+ '-g',
253
+ 'fsh-sushi'
254
+ ], {
255
+ stdio: ['pipe', 'pipe', 'pipe']
256
+ });
257
+
258
+ let stdout = '';
259
+ let stderr = '';
260
+
261
+ npm.stdout.on('data', (data) => {
262
+ stdout += data.toString();
263
+ });
264
+
265
+ npm.stderr.on('data', (data) => {
266
+ stderr += data.toString();
267
+ });
268
+
269
+ npm.on('close', async (code) => {
270
+ if (code === 0) {
271
+ await this.logTaskMessage(taskId, 'info', 'FSH Sushi installed successfully');
272
+
273
+ // Verify installation by checking version
274
+ try {
275
+ await this.checkSushiVersion(taskId);
276
+ } catch (versionError) {
277
+ this.logTaskMessage(taskId, 'warn', 'FSH Sushi installed but version check failed: ' + versionError.message);
278
+ }
279
+
280
+ resolve();
281
+ } else {
282
+ const error = 'NPM install failed with code ' + code + ': ' + stderr;
283
+ await this.logTaskMessage(taskId, 'error', error);
284
+ reject(new Error(error));
285
+ }
286
+ });
287
+
288
+ npm.on('error', async (error) => {
289
+ await this.logTaskMessage(taskId, 'error', 'NPM install error: ' + error.message);
290
+ reject(error);
291
+ });
292
+
293
+ // Timeout after 5 minutes
294
+ const timeout = setTimeout(async () => {
295
+ npm.kill('SIGTERM');
296
+
297
+ // Force kill after 10 seconds if still running
298
+ setTimeout(() => {
299
+ npm.kill('SIGKILL');
300
+ }, 10000);
301
+
302
+ await this.logTaskMessage(taskId, 'error', 'FSH Sushi installation timed out after 5 minutes');
303
+ reject(new Error('FSH Sushi installation timed out'));
304
+ }, 5 * 60 * 1000);
305
+
306
+ npm.on('close', () => {
307
+ clearTimeout(timeout);
308
+ });
309
+ });
310
+ }
311
+
312
+ async checkSushiVersion(taskId) {
313
+ return new Promise((resolve, reject) => {
314
+ const sushi = spawn('sushi', ['--version'], {
315
+ stdio: ['pipe', 'pipe', 'pipe']
316
+ });
317
+
318
+ let stdout = '';
319
+ let stderr = '';
320
+
321
+ sushi.stdout.on('data', (data) => {
322
+ stdout += data.toString();
323
+ });
324
+
325
+ sushi.stderr.on('data', (data) => {
326
+ stderr += data.toString();
327
+ });
328
+
329
+ sushi.on('close', async (code) => {
330
+ if (code === 0) {
331
+ const version = stdout.trim();
332
+ await this.logTaskMessage(taskId, 'info', 'FSH Sushi version: ' + version);
333
+ resolve(version);
334
+ } else {
335
+ reject(new Error('Sushi version check failed with code ' + code));
336
+ }
337
+ });
338
+
339
+ sushi.on('error', (error) => {
340
+ reject(error);
341
+ });
342
+
343
+ // Quick timeout for version check
344
+ setTimeout(() => {
345
+ sushi.kill();
346
+ reject(new Error('Sushi version check timed out'));
347
+ }, 30000);
348
+ });
349
+ }
350
+
351
+ async runIGPublisher(publisherJar, draftDir, logFile, taskId) {
352
+ await this.logTaskMessage(taskId, 'info', 'Running FHIR IG Publisher...');
353
+
354
+ // Check if sushi.config.yaml exists and log it
355
+ const sushiConfigPath = path.join(draftDir, 'sushi-config.yaml');
356
+ if (fs.existsSync(sushiConfigPath)) {
357
+ await this.logTaskMessage(taskId, 'info', 'Found sushi-config.yaml');
358
+ } else {
359
+ await this.logTaskMessage(taskId, 'info', 'No sushi-config.yaml found');
360
+ }
361
+
362
+ return new Promise((resolve, reject) => {
363
+ const java = spawn('java', [
364
+ '-jar',
365
+ '-Xmx20000m',
366
+ publisherJar,
367
+ '-ig',
368
+ '.'
369
+ ], {
370
+ cwd: draftDir,
371
+ stdio: ['pipe', 'pipe', 'pipe']
372
+ });
373
+
374
+ // Create log file stream
375
+ const logStream = fs.createWriteStream(logFile);
376
+
377
+ // Write header to log file
378
+ const startTime = new Date().toISOString();
379
+ logStream.write('=== FHIR IG Publisher Build Log ===\n');
380
+ logStream.write('Started: ' + startTime + '\n');
381
+ logStream.write('Command: java -jar -Xmx20000m publisher.jar -ig .\n');
382
+ logStream.write('Working Directory: ' + draftDir + '\n');
383
+ logStream.write('=====================================\n\n');
384
+
385
+ let hasOutput = false;
386
+ let lastProgressUpdate = Date.now();
387
+
388
+ java.stdout.on('data', (data) => {
389
+ hasOutput = true;
390
+ logStream.write(data);
391
+
392
+ // Log progress periodically
393
+ const now = Date.now();
394
+ if (now - lastProgressUpdate > 30000) { // Every 30 seconds
395
+ this.logTaskMessage(taskId, 'info', 'IG Publisher is still running...');
396
+ lastProgressUpdate = now;
397
+ }
398
+ });
399
+
400
+ java.stderr.on('data', (data) => {
401
+ hasOutput = true;
402
+ logStream.write(data);
403
+ });
404
+
405
+ java.on('close', async (code) => {
406
+ const endTime = new Date().toISOString();
407
+ logStream.write('\n=====================================\n');
408
+ logStream.write('Finished: ' + endTime + '\n');
409
+ logStream.write('Exit Code: ' + code + '\n');
410
+ logStream.end();
411
+
412
+ if (code === 0) {
413
+ await this.logTaskMessage(taskId, 'info', 'IG Publisher completed successfully');
414
+
415
+ // Check for QA report
416
+ const qaReportPath = path.join(draftDir, 'output', 'qa.html');
417
+ if (fs.existsSync(qaReportPath)) {
418
+ await this.logTaskMessage(taskId, 'info', 'QA report generated: output/qa.html');
419
+ }
420
+
421
+ resolve();
422
+ } else {
423
+ const error = 'IG Publisher failed with exit code: ' + code;
424
+ await this.logTaskMessage(taskId, 'error', error);
425
+ reject(new Error(error));
426
+ }
427
+ });
428
+
429
+ java.on('error', async (error) => {
430
+ logStream.write('\nERROR: ' + error.message + '\n');
431
+ logStream.end();
432
+ await this.logTaskMessage(taskId, 'error', 'IG Publisher error: ' + error.message);
433
+ reject(error);
434
+ });
435
+
436
+ // Timeout after 30 minutes
437
+ const timeout = setTimeout(async () => {
438
+ java.kill('SIGTERM'); // Try graceful shutdown first
439
+
440
+ // Force kill after 10 seconds if still running
441
+ setTimeout(() => {
442
+ java.kill('SIGKILL');
443
+ }, 10000);
444
+
445
+ logStream.write('\nTIMEOUT: Process killed after 30 minutes\n');
446
+ logStream.end();
447
+ await this.logTaskMessage(taskId, 'error', 'IG Publisher timed out after 30 minutes');
448
+ reject(new Error('IG Publisher timed out'));
449
+ }, 30 * 60 * 1000);
450
+
451
+ java.on('close', () => {
452
+ clearTimeout(timeout);
453
+ });
454
+ });
455
+ }
456
+ }
457
+
458
+ module.exports = DraftTaskProcessor;