fhirsmith 0.9.6 → 0.10.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 +51 -0
- package/library/folder-content-loader.js +91 -0
- package/npmprojector/npmprojector.js +2 -6
- package/package.json +1 -1
- package/packages/package-crawler.js +123 -6
- package/packages/packages.js +104 -0
- package/publisher/publisher.js +290 -12
- package/registry/crawler.js +85 -6
- package/registry/registry.js +24 -7
- package/server.js +11 -2
- package/stats.js +26 -18
- package/translations/Messages.properties +2 -1
- package/tx/cs/cs-cs.js +8 -0
- package/tx/cs/cs-loinc.js +1 -0
- package/tx/cs/cs-provider-list.js +2 -1
- package/tx/cs/cs-snomed.js +142 -59
- package/tx/data/snomed-testing.cache +0 -0
- package/tx/html/home-metrics.liquid +10 -10
- package/tx/library/canonical-resource.js +4 -2
- package/tx/library/codesystem.js +31 -0
- package/tx/library/conceptmap.js +24 -0
- package/tx/library/designations.js +27 -20
- package/tx/library/renderer.js +303 -22
- package/tx/library/ucum-types.js +4 -1
- package/tx/library/valueset.js +46 -0
- package/tx/library.js +65 -21
- package/tx/operation-context.js +122 -27
- package/tx/params.js +36 -8
- package/tx/provider.js +6 -3
- package/tx/tx-html.js +34 -0
- package/tx/tx.js +92 -30
- package/tx/vs/vs-vsac.js +157 -9
- package/tx/workers/cache-control.js +186 -0
- package/tx/workers/{related.js → compare.js} +27 -27
- package/tx/workers/expand.js +100 -96
- package/tx/workers/lookup.js +6 -0
- package/tx/workers/metadata.js +11 -6
- package/tx/workers/read.js +1 -1
- package/tx/workers/translate.js +20 -29
- package/tx/workers/validate.js +18 -10
- package/tx/workers/worker.js +134 -47
- package/tx/xversion/xv-bundle.js +1 -2
- package/tx/xversion/xv-codesystem.js +5 -2
- package/tx/xversion/xv-parameters.js +4 -4
- package/tx/xversion/xv-resource.js +2 -2
- package/tx/xversion/xv-terminologyCapabilities.js +11 -6
- package/tx/xversion/xv-valueset.js +7 -7
- package/publisher/task-draft.js +0 -463
- package/tx/data/OperationDefinition-ValueSet-related.json +0 -133
package/publisher/task-draft.js
DELETED
|
@@ -1,463 +0,0 @@
|
|
|
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
|
-
// eslint-disable-next-line no-unused-vars
|
|
195
|
-
let stdout = '';
|
|
196
|
-
let stderr = '';
|
|
197
|
-
|
|
198
|
-
git.stdout.on('data', (data) => {
|
|
199
|
-
stdout += data.toString();
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
git.stderr.on('data', (data) => {
|
|
203
|
-
stderr += data.toString();
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
git.on('close', async (code) => {
|
|
207
|
-
if (code === 0) {
|
|
208
|
-
await this.logTaskMessage(task.id, 'info', 'Repository cloned successfully');
|
|
209
|
-
|
|
210
|
-
// Log some info about what was cloned
|
|
211
|
-
try {
|
|
212
|
-
const stats = fs.statSync(draftDir);
|
|
213
|
-
if (stats.isDirectory()) {
|
|
214
|
-
const files = fs.readdirSync(draftDir);
|
|
215
|
-
await this.logTaskMessage(task.id, 'info', 'Cloned ' + files.length + ' files/directories');
|
|
216
|
-
}
|
|
217
|
-
} catch (e) {
|
|
218
|
-
// Don't fail if we can't get stats
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
resolve();
|
|
222
|
-
} else {
|
|
223
|
-
const error = 'Git clone failed with code ' + code + ': ' + stderr;
|
|
224
|
-
await this.logTaskMessage(task.id, 'error', error);
|
|
225
|
-
reject(new Error(error));
|
|
226
|
-
}
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
git.on('error', async (error) => {
|
|
230
|
-
await this.logTaskMessage(task.id, 'error', 'Git clone error: ' + error.message);
|
|
231
|
-
reject(error);
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
// Timeout for git clone (10 minutes)
|
|
235
|
-
const timeout = setTimeout(async () => {
|
|
236
|
-
git.kill();
|
|
237
|
-
await this.logTaskMessage(task.id, 'error', 'Git clone timed out after 10 minutes');
|
|
238
|
-
reject(new Error('Git clone timed out'));
|
|
239
|
-
}, 10 * 60 * 1000);
|
|
240
|
-
|
|
241
|
-
git.on('close', () => {
|
|
242
|
-
clearTimeout(timeout);
|
|
243
|
-
});
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
async installFshSushi(taskId) {
|
|
248
|
-
await this.logTaskMessage(taskId, 'info', 'Installing FSH Sushi globally...');
|
|
249
|
-
|
|
250
|
-
return new Promise((resolve, reject) => {
|
|
251
|
-
const npm = spawn('npm', [
|
|
252
|
-
'install',
|
|
253
|
-
'-g',
|
|
254
|
-
'fsh-sushi'
|
|
255
|
-
], {
|
|
256
|
-
stdio: ['pipe', 'pipe', 'pipe']
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
// eslint-disable-next-line no-unused-vars
|
|
260
|
-
let stdout = '';
|
|
261
|
-
let stderr = '';
|
|
262
|
-
|
|
263
|
-
npm.stdout.on('data', (data) => {
|
|
264
|
-
stdout += data.toString();
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
npm.stderr.on('data', (data) => {
|
|
268
|
-
stderr += data.toString();
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
npm.on('close', async (code) => {
|
|
272
|
-
if (code === 0) {
|
|
273
|
-
await this.logTaskMessage(taskId, 'info', 'FSH Sushi installed successfully');
|
|
274
|
-
|
|
275
|
-
// Verify installation by checking version
|
|
276
|
-
try {
|
|
277
|
-
await this.checkSushiVersion(taskId);
|
|
278
|
-
} catch (versionError) {
|
|
279
|
-
this.logTaskMessage(taskId, 'warn', 'FSH Sushi installed but version check failed: ' + versionError.message);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
resolve();
|
|
283
|
-
} else {
|
|
284
|
-
const error = 'NPM install failed with code ' + code + ': ' + stderr;
|
|
285
|
-
await this.logTaskMessage(taskId, 'error', error);
|
|
286
|
-
reject(new Error(error));
|
|
287
|
-
}
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
npm.on('error', async (error) => {
|
|
291
|
-
await this.logTaskMessage(taskId, 'error', 'NPM install error: ' + error.message);
|
|
292
|
-
reject(error);
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
// Timeout after 5 minutes
|
|
296
|
-
const timeout = setTimeout(async () => {
|
|
297
|
-
npm.kill('SIGTERM');
|
|
298
|
-
|
|
299
|
-
// Force kill after 10 seconds if still running
|
|
300
|
-
setTimeout(() => {
|
|
301
|
-
npm.kill('SIGKILL');
|
|
302
|
-
}, 10000);
|
|
303
|
-
|
|
304
|
-
await this.logTaskMessage(taskId, 'error', 'FSH Sushi installation timed out after 5 minutes');
|
|
305
|
-
reject(new Error('FSH Sushi installation timed out'));
|
|
306
|
-
}, 5 * 60 * 1000);
|
|
307
|
-
|
|
308
|
-
npm.on('close', () => {
|
|
309
|
-
clearTimeout(timeout);
|
|
310
|
-
});
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
async checkSushiVersion(taskId) {
|
|
315
|
-
return new Promise((resolve, reject) => {
|
|
316
|
-
const sushi = spawn('sushi', ['--version'], {
|
|
317
|
-
stdio: ['pipe', 'pipe', 'pipe']
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
let stdout = '';
|
|
321
|
-
// eslint-disable-next-line no-unused-vars
|
|
322
|
-
let stderr = '';
|
|
323
|
-
|
|
324
|
-
sushi.stdout.on('data', (data) => {
|
|
325
|
-
stdout += data.toString();
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
sushi.stderr.on('data', (data) => {
|
|
329
|
-
stderr += data.toString();
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
sushi.on('close', async (code) => {
|
|
333
|
-
if (code === 0) {
|
|
334
|
-
const version = stdout.trim();
|
|
335
|
-
await this.logTaskMessage(taskId, 'info', 'FSH Sushi version: ' + version);
|
|
336
|
-
resolve(version);
|
|
337
|
-
} else {
|
|
338
|
-
reject(new Error('Sushi version check failed with code ' + code));
|
|
339
|
-
}
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
sushi.on('error', (error) => {
|
|
343
|
-
reject(error);
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
// Quick timeout for version check
|
|
347
|
-
setTimeout(() => {
|
|
348
|
-
sushi.kill();
|
|
349
|
-
reject(new Error('Sushi version check timed out'));
|
|
350
|
-
}, 30000);
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
async runIGPublisher(publisherJar, draftDir, logFile, taskId) {
|
|
355
|
-
await this.logTaskMessage(taskId, 'info', 'Running FHIR IG Publisher...');
|
|
356
|
-
|
|
357
|
-
// Check if sushi.config.yaml exists and log it
|
|
358
|
-
const sushiConfigPath = path.join(draftDir, 'sushi-config.yaml');
|
|
359
|
-
if (fs.existsSync(sushiConfigPath)) {
|
|
360
|
-
await this.logTaskMessage(taskId, 'info', 'Found sushi-config.yaml');
|
|
361
|
-
} else {
|
|
362
|
-
await this.logTaskMessage(taskId, 'info', 'No sushi-config.yaml found');
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
return new Promise((resolve, reject) => {
|
|
366
|
-
const java = spawn('java', [
|
|
367
|
-
'-jar',
|
|
368
|
-
'-Xmx20000m',
|
|
369
|
-
publisherJar,
|
|
370
|
-
'-ig',
|
|
371
|
-
'.'
|
|
372
|
-
], {
|
|
373
|
-
cwd: draftDir,
|
|
374
|
-
stdio: ['pipe', 'pipe', 'pipe']
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
// Create log file stream
|
|
378
|
-
const logStream = fs.createWriteStream(logFile);
|
|
379
|
-
|
|
380
|
-
// Write header to log file
|
|
381
|
-
const startTime = new Date().toISOString();
|
|
382
|
-
logStream.write('=== FHIR IG Publisher Build Log ===\n');
|
|
383
|
-
logStream.write('Started: ' + startTime + '\n');
|
|
384
|
-
logStream.write('Command: java -jar -Xmx20000m publisher.jar -ig .\n');
|
|
385
|
-
logStream.write('Working Directory: ' + draftDir + '\n');
|
|
386
|
-
logStream.write('=====================================\n\n');
|
|
387
|
-
|
|
388
|
-
// eslint-disable-next-line no-unused-vars
|
|
389
|
-
let hasOutput = false;
|
|
390
|
-
let lastProgressUpdate = Date.now();
|
|
391
|
-
|
|
392
|
-
java.stdout.on('data', (data) => {
|
|
393
|
-
hasOutput = true;
|
|
394
|
-
logStream.write(data);
|
|
395
|
-
|
|
396
|
-
// Log progress periodically
|
|
397
|
-
const now = Date.now();
|
|
398
|
-
if (now - lastProgressUpdate > 30000) { // Every 30 seconds
|
|
399
|
-
this.logTaskMessage(taskId, 'info', 'IG Publisher is still running...');
|
|
400
|
-
lastProgressUpdate = now;
|
|
401
|
-
}
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
java.stderr.on('data', (data) => {
|
|
405
|
-
hasOutput = true;
|
|
406
|
-
logStream.write(data);
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
java.on('close', async (code) => {
|
|
410
|
-
const endTime = new Date().toISOString();
|
|
411
|
-
logStream.write('\n=====================================\n');
|
|
412
|
-
logStream.write('Finished: ' + endTime + '\n');
|
|
413
|
-
logStream.write('Exit Code: ' + code + '\n');
|
|
414
|
-
logStream.end();
|
|
415
|
-
|
|
416
|
-
if (code === 0) {
|
|
417
|
-
await this.logTaskMessage(taskId, 'info', 'IG Publisher completed successfully');
|
|
418
|
-
|
|
419
|
-
// Check for QA report
|
|
420
|
-
const qaReportPath = path.join(draftDir, 'output', 'qa.html');
|
|
421
|
-
if (fs.existsSync(qaReportPath)) {
|
|
422
|
-
await this.logTaskMessage(taskId, 'info', 'QA report generated: output/qa.html');
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
resolve();
|
|
426
|
-
} else {
|
|
427
|
-
const error = 'IG Publisher failed with exit code: ' + code;
|
|
428
|
-
await this.logTaskMessage(taskId, 'error', error);
|
|
429
|
-
reject(new Error(error));
|
|
430
|
-
}
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
java.on('error', async (error) => {
|
|
434
|
-
logStream.write('\nERROR: ' + error.message + '\n');
|
|
435
|
-
logStream.end();
|
|
436
|
-
await this.logTaskMessage(taskId, 'error', 'IG Publisher error: ' + error.message);
|
|
437
|
-
reject(error);
|
|
438
|
-
});
|
|
439
|
-
|
|
440
|
-
// Timeout configurable via publisher.igPublisherTimeoutMinutes (default: 60 minutes)
|
|
441
|
-
const timeoutMinutes = this.config.igPublisherTimeoutMinutes || 60;
|
|
442
|
-
const timeout = setTimeout(async () => {
|
|
443
|
-
java.kill('SIGTERM'); // Try graceful shutdown first
|
|
444
|
-
|
|
445
|
-
// Force kill after 10 seconds if still running
|
|
446
|
-
setTimeout(() => {
|
|
447
|
-
java.kill('SIGKILL');
|
|
448
|
-
}, 10000);
|
|
449
|
-
|
|
450
|
-
logStream.write('\nTIMEOUT: Process killed after ' + timeoutMinutes + ' minutes\n');
|
|
451
|
-
logStream.end();
|
|
452
|
-
await this.logTaskMessage(taskId, 'error', 'IG Publisher timed out after ' + timeoutMinutes + ' minutes');
|
|
453
|
-
reject(new Error('IG Publisher timed out'));
|
|
454
|
-
}, timeoutMinutes * 60 * 1000);
|
|
455
|
-
|
|
456
|
-
java.on('close', () => {
|
|
457
|
-
clearTimeout(timeout);
|
|
458
|
-
});
|
|
459
|
-
});
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
module.exports = DraftTaskProcessor;
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"resourceType": "OperationDefinition",
|
|
3
|
-
"id": "ValueSet-related",
|
|
4
|
-
"url": "http://hl7.org/fhir/OperationDefinition/ValueSet-related",
|
|
5
|
-
"version": "5.0.0",
|
|
6
|
-
"name": "ValueSetRelated",
|
|
7
|
-
"title": "Value Set Related Determination",
|
|
8
|
-
"status": "active",
|
|
9
|
-
"kind": "operation",
|
|
10
|
-
"experimental": false,
|
|
11
|
-
"date": "2023-03-26T15:21:02+11:00",
|
|
12
|
-
"publisher": "FHIRsmith",
|
|
13
|
-
"description": "Determine the relationship between two value sets. Different versions of code systems are considered compatible unless versionNeeded = true for the code system",
|
|
14
|
-
"jurisdiction": [
|
|
15
|
-
{
|
|
16
|
-
"coding": [
|
|
17
|
-
{
|
|
18
|
-
"system": "http://unstats.un.org/unsd/methods/m49/m49.htm",
|
|
19
|
-
"code": "001",
|
|
20
|
-
"display": "World"
|
|
21
|
-
}
|
|
22
|
-
]
|
|
23
|
-
}
|
|
24
|
-
],
|
|
25
|
-
"affectsState": false,
|
|
26
|
-
"code": "related",
|
|
27
|
-
"comment": "An $expand will be performed internally if needed.",
|
|
28
|
-
"resource": [
|
|
29
|
-
"ValueSet"
|
|
30
|
-
],
|
|
31
|
-
"system": false,
|
|
32
|
-
"type": true,
|
|
33
|
-
"instance": false,
|
|
34
|
-
"parameter": [
|
|
35
|
-
{
|
|
36
|
-
"name": "thisUrl",
|
|
37
|
-
"use": "in",
|
|
38
|
-
"scope": [
|
|
39
|
-
"type"
|
|
40
|
-
],
|
|
41
|
-
"min": 0,
|
|
42
|
-
"max": "1",
|
|
43
|
-
"documentation": "Value set Canonical URL for the first value set of the pair (the base). The server must know the value set (e.g. it is provided as an attached resource, it is defined explicitly in the server's value sets, or it is defined implicitly by some code system known to the server",
|
|
44
|
-
"type": "uri"
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
"name": "otherUrl",
|
|
48
|
-
"use": "in",
|
|
49
|
-
"scope": [
|
|
50
|
-
"type"
|
|
51
|
-
],
|
|
52
|
-
"min": 0,
|
|
53
|
-
"max": "1",
|
|
54
|
-
"documentation": "Value set Canonical URL for the second value set of the pair (the one being compared to base). The server must know the value set (e.g. it is provided as an attached resource, it is defined explicitly in the server's value sets, or it is defined implicitly by some code system known to the server",
|
|
55
|
-
"type": "uri"
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
"name": "thisValueSet",
|
|
59
|
-
"use": "in",
|
|
60
|
-
"scope": [
|
|
61
|
-
"type"
|
|
62
|
-
],
|
|
63
|
-
"min": 0,
|
|
64
|
-
"max": "1",
|
|
65
|
-
"documentation": "The first value set is provided directly as part of the request. Servers may choose not to accept value sets in this fashion. This parameter is used when the client wants the server to expand a value set that is not stored on the server",
|
|
66
|
-
"type": "ValueSet"
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
"name": "otherValueSet",
|
|
70
|
-
"use": "in",
|
|
71
|
-
"scope": [
|
|
72
|
-
"type"
|
|
73
|
-
],
|
|
74
|
-
"min": 0,
|
|
75
|
-
"max": "1",
|
|
76
|
-
"documentation": "The other value set is provided directly as part of the request. Servers may choose not to accept value sets in this fashion. This parameter is used when the client wants the server to expand a value set that is not stored on the server",
|
|
77
|
-
"type": "ValueSet"
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
"name": "thisVersion",
|
|
81
|
-
"use": "in",
|
|
82
|
-
"scope": [
|
|
83
|
-
"type"
|
|
84
|
-
],
|
|
85
|
-
"min": 0,
|
|
86
|
-
"max": "1",
|
|
87
|
-
"documentation": "The identifier that is used to identify a specific version of the value set to be used when validating the code. This is an arbitrary value managed by the value set author and is not expected to be globally unique. For example, it might be a timestamp (e.g. yyyymmdd) if a managed version is not available.",
|
|
88
|
-
"type": "string"
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
"name": "otherVersion",
|
|
92
|
-
"use": "in",
|
|
93
|
-
"scope": [
|
|
94
|
-
"type"
|
|
95
|
-
],
|
|
96
|
-
"min": 0,
|
|
97
|
-
"max": "1",
|
|
98
|
-
"documentation": "The identifier that is used to identify a specific version of the value set to be used when validating the code. This is an arbitrary value managed by the value set author and is not expected to be globally unique. For example, it might be a timestamp (e.g. yyyymmdd) if a managed version is not available.",
|
|
99
|
-
"type": "string"
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
"name": "useSupplement",
|
|
103
|
-
"use": "in",
|
|
104
|
-
"min": 0,
|
|
105
|
-
"max": "*",
|
|
106
|
-
"documentation": "The supplement must be used when validating the code. Use of this parameter should result in $validate-code behaving the same way as if the supplements were included in the value set definition using the [http://hl7.org/fhir/StructureDefinition/valueset-supplement](http://hl7.org/fhir/extensions/StructureDefinition-valueset-supplement.html)",
|
|
107
|
-
"type": "canonical"
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
"name": "diagnostics",
|
|
111
|
-
"use": "in",
|
|
112
|
-
"min": 0,
|
|
113
|
-
"max": "1",
|
|
114
|
-
"documentation": "Whether to return information about the reasoning process"
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
"name": "result",
|
|
118
|
-
"use": "out",
|
|
119
|
-
"min": 1,
|
|
120
|
-
"max": "1",
|
|
121
|
-
"documentation": "The relationship between the ValueSets. One of: same, superset, subset, overlapping, dsjoint, empty, and indeterminate",
|
|
122
|
-
"type": "boolean"
|
|
123
|
-
},
|
|
124
|
-
{
|
|
125
|
-
"name": "message",
|
|
126
|
-
"use": "out",
|
|
127
|
-
"min": 0,
|
|
128
|
-
"max": "1",
|
|
129
|
-
"documentation": "Explanation of the code, with reason if appropriate",
|
|
130
|
-
"type": "string"
|
|
131
|
-
}
|
|
132
|
-
]
|
|
133
|
-
}
|