fhirsmith 0.8.5 → 0.9.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/README.md +52 -22
- package/extension-tracker/extension-tracker-template.html +3 -1
- package/library/html-server.js +7 -0
- package/library/logger.js +234 -194
- package/library/regex-utilities.js +13 -0
- package/package.json +4 -2
- package/packages/packages-template.html +3 -1
- package/publisher/publisher-template.html +1 -0
- package/publisher/publisher.js +28 -7
- package/registry/registry-template.html +3 -1
- package/root-bare-template.html +9759 -37
- package/root-template.html +3 -2
- package/server.js +48 -12
- package/translations/Messages.properties +2 -1
- package/translations/rendering-phrases.properties +3 -1
- package/tx/cs/cs-api.js +4 -0
- package/tx/cs/cs-country.js +2 -1
- package/tx/cs/cs-cs.js +9 -4
- package/tx/cs/cs-loinc.js +2 -1
- package/tx/cs/cs-snomed.js +5 -1
- package/tx/data/OperationDefinition-ValueSet-related.json +133 -0
- package/tx/html/tx-template.html +3 -2
- package/tx/importers/atc-to-fhir.js +27 -27
- package/tx/library/codesystem.js +4 -0
- package/tx/library/renderer.js +20 -4
- package/tx/library/ucum-parsers.js +2 -1
- package/tx/ocl/cs-ocl.cjs +48 -15
- package/tx/ocl/vs-ocl.cjs +57 -34
- package/tx/operation-context.js +74 -19
- package/tx/tx-html.js +5 -5
- package/tx/tx.fhir.org.yml +4 -4
- package/tx/tx.js +1 -0
- package/tx/vs/vs-database.js +150 -100
- package/tx/vs/vs-vsac.js +90 -31
- package/tx/workers/expand.js +154 -113
- package/tx/workers/metadata.js +6 -3
- package/tx/workers/read.js +6 -3
- package/tx/workers/related.js +228 -87
- package/xig/xig-template.html +3 -1
- package/library/logger-telnet.js +0 -205
package/library/logger.js
CHANGED
|
@@ -1,8 +1,25 @@
|
|
|
1
|
-
const winston = require('winston');
|
|
2
|
-
require('winston-daily-rotate-file');
|
|
3
1
|
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
4
3
|
const folders = require('./folder-setup');
|
|
5
4
|
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Buffered, daily-rotating logger
|
|
7
|
+
// - Writes are batched and flushed every FLUSH_INTERVAL ms or FLUSH_SIZE lines
|
|
8
|
+
// - this is intended to be highly efficient
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
const DEFAULTS = {
|
|
12
|
+
level: 'info', // error, warn, info, debug, verbose
|
|
13
|
+
console: true, // write to stdout/stderr
|
|
14
|
+
consoleErrors: false, // include error/warn on console (when running as service, these go to journal)
|
|
15
|
+
maxFiles: 14, // number of daily log files to keep
|
|
16
|
+
maxSize: 0, // max bytes per file (0 = unlimited)
|
|
17
|
+
flushInterval: 2000, // ms between flushes
|
|
18
|
+
flushSize: 200, // flush when buffer reaches this many lines
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const LEVELS = { error: 0, warn: 1, info: 2, debug: 3, verbose: 4 };
|
|
22
|
+
|
|
6
23
|
class Logger {
|
|
7
24
|
static _instance = null;
|
|
8
25
|
|
|
@@ -13,260 +30,278 @@ class Logger {
|
|
|
13
30
|
return Logger._instance;
|
|
14
31
|
}
|
|
15
32
|
|
|
33
|
+
// options = config.logging section from config.json (all fields optional)
|
|
34
|
+
//
|
|
35
|
+
// Example config.json:
|
|
36
|
+
// {
|
|
37
|
+
// "logging": {
|
|
38
|
+
// "level": "info",
|
|
39
|
+
// "console": false,
|
|
40
|
+
// "consoleErrors": false,
|
|
41
|
+
// "maxFiles": 14,
|
|
42
|
+
// "maxSize": "50m",
|
|
43
|
+
// "flushInterval": 2000,
|
|
44
|
+
// "flushSize": 200
|
|
45
|
+
// }
|
|
46
|
+
// }
|
|
47
|
+
//
|
|
16
48
|
constructor(options = {}) {
|
|
17
|
-
this.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
maxSize: options.file?.maxSize || '20m',
|
|
27
|
-
maxFiles: options.file?.maxFiles || 14
|
|
28
|
-
}
|
|
29
|
-
};
|
|
49
|
+
this.level = options.level || DEFAULTS.level;
|
|
50
|
+
this.logDir = options.logDir || folders.logsDir();
|
|
51
|
+
this.maxFiles = options.maxFiles ?? DEFAULTS.maxFiles;
|
|
52
|
+
this.maxSize = Logger._parseSize(options.maxSize) || DEFAULTS.maxSize;
|
|
53
|
+
this.showConsole = options.console ?? DEFAULTS.console;
|
|
54
|
+
this.consoleErrors = options.consoleErrors ?? DEFAULTS.consoleErrors;
|
|
55
|
+
|
|
56
|
+
const flushInterval = options.flushInterval ?? DEFAULTS.flushInterval;
|
|
57
|
+
this._flushSize = options.flushSize ?? DEFAULTS.flushSize;
|
|
30
58
|
|
|
31
59
|
// Ensure log directory exists
|
|
32
|
-
if (!fs.existsSync(this.
|
|
33
|
-
fs.mkdirSync(this.
|
|
60
|
+
if (!fs.existsSync(this.logDir)) {
|
|
61
|
+
fs.mkdirSync(this.logDir, { recursive: true });
|
|
34
62
|
}
|
|
35
63
|
|
|
36
|
-
//
|
|
37
|
-
this.
|
|
64
|
+
// Buffer
|
|
65
|
+
this._buffer = [];
|
|
66
|
+
this._currentDate = null;
|
|
67
|
+
this._fd = null;
|
|
68
|
+
this._currentFileSize = 0;
|
|
38
69
|
|
|
39
|
-
|
|
70
|
+
// Periodic flush
|
|
71
|
+
this._flushTimer = setInterval(() => this._flush(), flushInterval);
|
|
72
|
+
if (this._flushTimer.unref) this._flushTimer.unref(); // don't keep process alive
|
|
40
73
|
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
});
|
|
74
|
+
// Flush on exit
|
|
75
|
+
process.on('exit', () => this._flushSync());
|
|
76
|
+
|
|
77
|
+
this.info('Logger initialized @ ' + this.logDir, {});
|
|
46
78
|
}
|
|
47
79
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
]
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
// Add file transport with rotation (includes ALL levels with full metadata)
|
|
61
|
-
const fileTransport = new winston.transports.DailyRotateFile({
|
|
62
|
-
dirname: this.options.logDir,
|
|
63
|
-
filename: this.options.file.filename,
|
|
64
|
-
datePattern: this.options.file.datePattern,
|
|
65
|
-
maxSize: this.options.file.maxSize,
|
|
66
|
-
maxFiles: this.options.file.maxFiles,
|
|
67
|
-
level: this.options.level,
|
|
68
|
-
format: winston.format.combine(...fileFormats)
|
|
69
|
-
});
|
|
70
|
-
transports.push(fileTransport);
|
|
71
|
-
|
|
72
|
-
// Add console transport if enabled
|
|
73
|
-
if (this.options.console) {
|
|
74
|
-
const consoleFormat = winston.format.combine(
|
|
75
|
-
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }),
|
|
76
|
-
winston.format.errors({ stack: true }),
|
|
77
|
-
winston.format.colorize({ all: true }),
|
|
78
|
-
winston.format.printf(info => {
|
|
79
|
-
const stack = info.stack ? `\n${info.stack}` : '';
|
|
80
|
-
return `${info.timestamp} ${info.level.padEnd(7)} ${info.message}${stack}`;
|
|
81
|
-
})
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
const consoleTransport = new winston.transports.Console({
|
|
85
|
-
level: this.options.level,
|
|
86
|
-
format: consoleFormat
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
transports.push(consoleTransport);
|
|
80
|
+
// Parse human-readable size strings: "20m" -> bytes, "1g" -> bytes
|
|
81
|
+
static _parseSize(value) {
|
|
82
|
+
if (!value) return 0;
|
|
83
|
+
if (typeof value === 'number') return value;
|
|
84
|
+
const m = String(value).match(/^(\d+(?:\.\d+)?)\s*([kmg])?b?$/i);
|
|
85
|
+
if (!m) return 0;
|
|
86
|
+
const num = parseFloat(m[1]);
|
|
87
|
+
switch ((m[2] || '').toLowerCase()) {
|
|
88
|
+
case 'k': return num * 1024;
|
|
89
|
+
case 'm': return num * 1024 * 1024;
|
|
90
|
+
case 'g': return num * 1024 * 1024 * 1024;
|
|
91
|
+
default: return num;
|
|
90
92
|
}
|
|
93
|
+
}
|
|
91
94
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
// Compatibility: server.js home page reads Logger.getInstance().options.file.maxFiles etc.
|
|
96
|
+
get options() {
|
|
97
|
+
return {
|
|
98
|
+
level: this.level,
|
|
99
|
+
file: {
|
|
100
|
+
maxFiles: this.maxFiles,
|
|
101
|
+
maxSize: this.maxSize > 0 ? `${Math.round(this.maxSize / 1024 / 1024)}m` : 'unlimited',
|
|
102
|
+
}
|
|
103
|
+
};
|
|
98
104
|
}
|
|
99
105
|
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
106
|
+
// --- formatting (inline, no libraries) ---
|
|
107
|
+
|
|
108
|
+
_timestamp() {
|
|
109
|
+
const d = new Date();
|
|
110
|
+
const Y = d.getFullYear();
|
|
111
|
+
const M = String(d.getMonth() + 1).padStart(2, '0');
|
|
112
|
+
const D = String(d.getDate()).padStart(2, '0');
|
|
113
|
+
const h = String(d.getHours()).padStart(2, '0');
|
|
114
|
+
const m = String(d.getMinutes()).padStart(2, '0');
|
|
115
|
+
const s = String(d.getSeconds()).padStart(2, '0');
|
|
116
|
+
const ms = String(d.getMilliseconds()).padStart(3, '0');
|
|
117
|
+
return `${Y}-${M}-${D} ${h}:${m}:${s}.${ms}`;
|
|
103
118
|
}
|
|
104
119
|
|
|
105
|
-
|
|
106
|
-
|
|
120
|
+
_dateTag() {
|
|
121
|
+
const d = new Date();
|
|
122
|
+
const Y = d.getFullYear();
|
|
123
|
+
const M = String(d.getMonth() + 1).padStart(2, '0');
|
|
124
|
+
const D = String(d.getDate()).padStart(2, '0');
|
|
125
|
+
return `${Y}-${M}-${D}`;
|
|
107
126
|
}
|
|
108
127
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
128
|
+
_formatLine(level, message, stack) {
|
|
129
|
+
const ts = this._timestamp();
|
|
130
|
+
const lv = level.padEnd(7);
|
|
131
|
+
let line = `${ts} ${lv} ${message}\n`;
|
|
132
|
+
if (stack) line += stack + '\n';
|
|
133
|
+
return line;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// --- file management ---
|
|
114
137
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (
|
|
118
|
-
|
|
138
|
+
_openFile(dateTag) {
|
|
139
|
+
// Check if we need to rotate due to size
|
|
140
|
+
if (this._fd !== null && this._currentDate === dateTag) {
|
|
141
|
+
if (this.maxSize <= 0 || this._currentFileSize < this.maxSize) return;
|
|
142
|
+
// Size limit exceeded — close and fall through to open a new file
|
|
143
|
+
try { fs.closeSync(this._fd); } catch (_) { /* intentional */ }
|
|
144
|
+
this._fd = null;
|
|
119
145
|
}
|
|
146
|
+
// Close previous (date changed)
|
|
147
|
+
if (this._fd !== null) {
|
|
148
|
+
try { fs.closeSync(this._fd); } catch (_) { /* intentional */ }
|
|
149
|
+
}
|
|
150
|
+
const filename = `server-${dateTag}.log`;
|
|
151
|
+
const filePath = path.join(this.logDir, filename);
|
|
152
|
+
this._fd = fs.openSync(filePath, 'a');
|
|
153
|
+
try { this._currentFileSize = fs.fstatSync(this._fd).size; } catch (_) { this._currentFileSize = 0; }
|
|
154
|
+
this._currentDate = dateTag;
|
|
155
|
+
|
|
156
|
+
// Maintain a stable symlink so `tail -f server.log` always tracks the current file
|
|
157
|
+
const linkPath = path.join(this.logDir, 'server.log');
|
|
158
|
+
try { fs.unlinkSync(linkPath); } catch (_) { /* intentional */ }
|
|
159
|
+
try { fs.symlinkSync(filename, linkPath); } catch (_) { /* intentional */ }
|
|
160
|
+
|
|
161
|
+
this._purgeOldFiles();
|
|
162
|
+
}
|
|
120
163
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
164
|
+
_purgeOldFiles() {
|
|
165
|
+
try {
|
|
166
|
+
const files = fs.readdirSync(this.logDir)
|
|
167
|
+
.filter(f => f.startsWith('server-') && f.endsWith('.log'))
|
|
168
|
+
.sort();
|
|
169
|
+
while (files.length > this.maxFiles) {
|
|
170
|
+
const old = files.shift();
|
|
171
|
+
fs.unlinkSync(path.join(this.logDir, old));
|
|
127
172
|
}
|
|
128
|
-
}
|
|
173
|
+
} catch (_) { /* intentional */ }
|
|
129
174
|
}
|
|
130
175
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
176
|
+
// --- buffer + flush ---
|
|
177
|
+
|
|
178
|
+
_enqueue(line) {
|
|
179
|
+
this._buffer.push(line);
|
|
180
|
+
if (this._buffer.length >= this._flushSize) {
|
|
181
|
+
this._flush();
|
|
134
182
|
}
|
|
135
|
-
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
_flush() {
|
|
186
|
+
if (this._buffer.length === 0) return;
|
|
187
|
+
const dateTag = this._dateTag();
|
|
188
|
+
this._openFile(dateTag);
|
|
189
|
+
const chunk = this._buffer.join('');
|
|
190
|
+
this._buffer.length = 0;
|
|
191
|
+
// Async write — fire and forget; OS will buffer anyway
|
|
192
|
+
const buf = Buffer.from(chunk);
|
|
193
|
+
this._currentFileSize += buf.length;
|
|
194
|
+
fs.write(this._fd, buf, 0, buf.length, null, (err) => {
|
|
195
|
+
if (err) {
|
|
196
|
+
// If the fd went bad (e.g. date rolled), reopen and retry once
|
|
197
|
+
try {
|
|
198
|
+
this._currentDate = null;
|
|
199
|
+
this._openFile(this._dateTag());
|
|
200
|
+
fs.writeSync(this._fd, buf, 0, buf.length);
|
|
201
|
+
} catch (_) { /* intentional */ }
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
_flushSync() {
|
|
207
|
+
if (this._buffer.length === 0) return;
|
|
208
|
+
const dateTag = this._dateTag();
|
|
209
|
+
this._openFile(dateTag);
|
|
210
|
+
const chunk = this._buffer.join('');
|
|
211
|
+
this._buffer.length = 0;
|
|
212
|
+
try { fs.writeSync(this._fd, chunk); } catch (_) { /* intentional */ }
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// --- core log ---
|
|
216
|
+
|
|
217
|
+
_shouldLog(level) {
|
|
218
|
+
return (LEVELS[level] ?? 99) <= (LEVELS[this.level] ?? 2);
|
|
136
219
|
}
|
|
137
220
|
|
|
138
221
|
_log(level, messageOrError, meta, options) {
|
|
222
|
+
if (!this._shouldLog(level)) return;
|
|
223
|
+
|
|
139
224
|
let message;
|
|
140
225
|
let stack;
|
|
141
226
|
|
|
142
|
-
// Check if we should skip console for errors/warnings
|
|
143
|
-
const skipConsole = !this._shouldLogToConsole(level, options);
|
|
144
|
-
|
|
145
|
-
// Handle Error objects
|
|
146
227
|
if (messageOrError instanceof Error) {
|
|
147
228
|
message = messageOrError.message;
|
|
148
229
|
stack = messageOrError.stack;
|
|
149
|
-
if (skipConsole) {
|
|
150
|
-
// Log only to file transport
|
|
151
|
-
this.logger.transports
|
|
152
|
-
.filter(t => !(t instanceof winston.transports.Console))
|
|
153
|
-
.forEach(t => t.log({ level, message, stack, ...meta }));
|
|
154
|
-
} else {
|
|
155
|
-
this.logger[level](message, {stack, ...meta});
|
|
156
|
-
}
|
|
157
230
|
} else {
|
|
158
231
|
message = String(messageOrError);
|
|
159
232
|
stack = meta?.stack;
|
|
160
|
-
if (skipConsole) {
|
|
161
|
-
this.logger.transports
|
|
162
|
-
.filter(t => !(t instanceof winston.transports.Console))
|
|
163
|
-
.forEach(t => t.log({ level, message, ...meta }));
|
|
164
|
-
} else {
|
|
165
|
-
this.logger[level](message, meta);
|
|
166
|
-
}
|
|
167
233
|
}
|
|
168
234
|
|
|
169
|
-
this.
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
235
|
+
const line = this._formatLine(level, message, stack);
|
|
236
|
+
|
|
237
|
+
// Buffer for file
|
|
238
|
+
this._enqueue(line);
|
|
239
|
+
|
|
240
|
+
// Console
|
|
241
|
+
if (this.showConsole) {
|
|
242
|
+
const isErrWarn = level === 'error' || level === 'warn';
|
|
243
|
+
const consoleErrors = options?.consoleErrors ?? this.consoleErrors;
|
|
244
|
+
if (!isErrWarn || consoleErrors) {
|
|
245
|
+
if (isErrWarn) {
|
|
246
|
+
process.stderr.write(line);
|
|
247
|
+
} else {
|
|
248
|
+
process.stdout.write(line);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
182
252
|
}
|
|
183
253
|
|
|
184
|
-
|
|
185
|
-
this._log('debug', message, meta, this.options);
|
|
186
|
-
}
|
|
254
|
+
// --- public API (same as before) ---
|
|
187
255
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
256
|
+
error(message, meta = {}) { this._log('error', message, meta, this); }
|
|
257
|
+
warn(message, meta = {}) { this._log('warn', message, meta, this); }
|
|
258
|
+
info(message, meta = {}) { this._log('info', message, meta, this); }
|
|
259
|
+
debug(message, meta = {}) { this._log('debug', message, meta, this); }
|
|
260
|
+
verbose(message, meta = {}) { this._log('verbose', message, meta, this); }
|
|
191
261
|
|
|
192
|
-
log(level, message, meta = {}) {
|
|
193
|
-
this._log(level, message, meta, this.options);
|
|
194
|
-
}
|
|
262
|
+
log(level, message, meta = {}) { this._log(level, message, meta, this); }
|
|
195
263
|
|
|
196
264
|
child(defaultMeta = {}) {
|
|
197
265
|
const self = this;
|
|
198
266
|
|
|
199
|
-
// Build module-specific options
|
|
200
267
|
const childOptions = {
|
|
201
|
-
consoleErrors: defaultMeta.consoleErrors ?? self.
|
|
202
|
-
telnetErrors: defaultMeta.telnetErrors ?? self.options.telnetErrors
|
|
268
|
+
consoleErrors: defaultMeta.consoleErrors ?? self.consoleErrors,
|
|
203
269
|
};
|
|
204
270
|
|
|
205
|
-
|
|
206
|
-
const cleanMeta = { ...defaultMeta };
|
|
207
|
-
delete cleanMeta.consoleErrors;
|
|
208
|
-
delete cleanMeta.telnetErrors;
|
|
209
|
-
|
|
210
|
-
if (cleanMeta.module) {
|
|
211
|
-
const modulePrefix = `{${cleanMeta.module}}`;
|
|
212
|
-
|
|
213
|
-
return {
|
|
214
|
-
error: (messageOrError, meta = {}) => {
|
|
215
|
-
if (messageOrError instanceof Error) {
|
|
216
|
-
const prefixedError = new Error(`${modulePrefix}: ${messageOrError.message}`);
|
|
217
|
-
prefixedError.stack = messageOrError.stack;
|
|
218
|
-
self._log('error', prefixedError, meta, childOptions);
|
|
219
|
-
} else {
|
|
220
|
-
self._log('error', `${modulePrefix}: ${messageOrError}`, meta, childOptions);
|
|
221
|
-
}
|
|
222
|
-
},
|
|
223
|
-
warn: (messageOrError, meta = {}) => {
|
|
224
|
-
if (messageOrError instanceof Error) {
|
|
225
|
-
const prefixedError = new Error(`${modulePrefix}: ${messageOrError.message}`);
|
|
226
|
-
prefixedError.stack = messageOrError.stack;
|
|
227
|
-
self._log('warn', prefixedError, meta, childOptions);
|
|
228
|
-
} else {
|
|
229
|
-
self._log('warn', `${modulePrefix}: ${messageOrError}`, meta, childOptions);
|
|
230
|
-
}
|
|
231
|
-
},
|
|
232
|
-
info: (message, meta = {}) => self._log('info', `${modulePrefix}: ${message}`, meta, childOptions),
|
|
233
|
-
debug: (message, meta = {}) => self._log('debug', `${modulePrefix}: ${message}`, meta, childOptions),
|
|
234
|
-
verbose: (message, meta = {}) => self._log('verbose', `${modulePrefix}: ${message}`, meta, childOptions),
|
|
235
|
-
log: (level, message, meta = {}) => self._log(level, `${modulePrefix}: ${message}`, meta, childOptions)
|
|
236
|
-
};
|
|
237
|
-
}
|
|
271
|
+
const modulePrefix = defaultMeta.module ? `{${defaultMeta.module}}` : null;
|
|
238
272
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
273
|
+
const wrap = (level) => (messageOrError, meta = {}) => {
|
|
274
|
+
if (messageOrError instanceof Error) {
|
|
275
|
+
const prefixed = modulePrefix
|
|
276
|
+
? Object.assign(new Error(`${modulePrefix}: ${messageOrError.message}`), { stack: messageOrError.stack })
|
|
277
|
+
: messageOrError;
|
|
278
|
+
self._log(level, prefixed, meta, childOptions);
|
|
279
|
+
} else {
|
|
280
|
+
const msg = modulePrefix ? `${modulePrefix}: ${messageOrError}` : String(messageOrError);
|
|
281
|
+
self._log(level, msg, meta, childOptions);
|
|
282
|
+
}
|
|
247
283
|
};
|
|
248
284
|
|
|
249
|
-
return
|
|
285
|
+
return {
|
|
286
|
+
error: wrap('error'),
|
|
287
|
+
warn: wrap('warn'),
|
|
288
|
+
info: wrap('info'),
|
|
289
|
+
debug: wrap('debug'),
|
|
290
|
+
verbose: wrap('verbose'),
|
|
291
|
+
log: (level, message, meta = {}) => wrap(level)(message, meta)
|
|
292
|
+
};
|
|
250
293
|
}
|
|
251
294
|
|
|
252
295
|
setLevel(level) {
|
|
253
|
-
this.
|
|
254
|
-
this.logger.transports.forEach(transport => {
|
|
255
|
-
transport.level = level;
|
|
256
|
-
});
|
|
296
|
+
this.level = level;
|
|
257
297
|
this.info(`Log level changed to ${level}`);
|
|
258
298
|
}
|
|
259
299
|
|
|
260
300
|
setConsoleErrors(enabled) {
|
|
261
|
-
this.
|
|
301
|
+
this.consoleErrors = enabled;
|
|
262
302
|
this.info(`Console errors ${enabled ? 'enabled' : 'disabled'}`);
|
|
263
303
|
}
|
|
264
304
|
|
|
265
|
-
setTelnetErrors(enabled) {
|
|
266
|
-
this.options.telnetErrors = enabled;
|
|
267
|
-
this.info(`Telnet errors ${enabled ? 'enabled' : 'disabled'}`);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
305
|
stream() {
|
|
271
306
|
return {
|
|
272
307
|
write: (message) => {
|
|
@@ -274,6 +309,11 @@ class Logger {
|
|
|
274
309
|
}
|
|
275
310
|
};
|
|
276
311
|
}
|
|
312
|
+
|
|
313
|
+
// Force an immediate flush (e.g. before graceful shutdown)
|
|
314
|
+
flush() {
|
|
315
|
+
this._flushSync();
|
|
316
|
+
}
|
|
277
317
|
}
|
|
278
318
|
|
|
279
319
|
module.exports = Logger;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const { RE2 } = require('re2-wasm');
|
|
2
|
+
|
|
3
|
+
class RegExUtilities {
|
|
4
|
+
|
|
5
|
+
compile(pattern, flags) {
|
|
6
|
+
// RE2 requires the unicode flag; add it if not already present
|
|
7
|
+
const re2Flags = flags && flags.includes('u') ? flags : (flags || '') + 'u';
|
|
8
|
+
return new RE2(pattern, re2Flags);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
module.exports = new RegExUtilities();
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fhirsmith",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"txVersion": "1.9.1",
|
|
4
5
|
"description": "A Node.js server that provides a collection of tools to serve the FHIR ecosystem",
|
|
5
6
|
"main": "server.js",
|
|
6
7
|
"engines": {
|
|
@@ -50,7 +51,7 @@
|
|
|
50
51
|
"fs-extra": "^11.3.3",
|
|
51
52
|
"ini": "^6.0.0",
|
|
52
53
|
"inquirer": "^8.2.5",
|
|
53
|
-
"liquidjs": "^10.
|
|
54
|
+
"liquidjs": "^10.25.5",
|
|
54
55
|
"lusca": "^1.7.0",
|
|
55
56
|
"natural": "^6.12.0",
|
|
56
57
|
"node-cron": "^3.0.3",
|
|
@@ -60,6 +61,7 @@
|
|
|
60
61
|
"passport-github2": "^0.1.12",
|
|
61
62
|
"passport-google-oauth20": "^2.0.0",
|
|
62
63
|
"properties-file": "^3.6.4",
|
|
64
|
+
"re2-wasm": "^1.0.2",
|
|
63
65
|
"rimraf": "^5.0.10",
|
|
64
66
|
"sqlite3": "^5.1.7",
|
|
65
67
|
"tar": "^7.5.7",
|
|
@@ -92,7 +92,9 @@
|
|
|
92
92
|
<p>
|
|
93
93
|
<a href="http://www.hl7.org/fhir" style="color: gold" title="Fast Healthcare Interoperability Resources - Home Page"><img border="0" src="/icon-fhir-16.png" style="vertical-align: text-bottom"/> <b>FHIR</b></a> © HL7.org 2011+. |
|
|
94
94
|
<a href="https://github.com/HealthIntersections/FHIRsmith/blob/main/README.md" style="color: gold"><img border="0" src="/FHIRsmith16.png" style="vertical-align: text-bottom"/> FHIRsmith</a> [%ver%] © HealthIntersections.com.au 2023+ |
|
|
95
|
-
Package Registry last updated as of [%crawler-date%] | [%total-packages%] packages  
|
|
95
|
+
Package Registry last updated as of [%crawler-date%] | [%total-packages%] packages |
|
|
96
|
+
([%ms%] ms)
|
|
97
|
+
[%sponsorMessage%]
|
|
96
98
|
</p>
|
|
97
99
|
</div> <!-- /inner-wrapper -->
|
|
98
100
|
</div> <!-- /container -->
|
|
@@ -123,6 +123,7 @@
|
|
|
123
123
|
<a href="http://www.hl7.org/fhir" style="color: gold" title="Fast Healthcare Interoperability Resources - Home Page"><img border="0" src="/icon-fhir-16.png" style="vertical-align: text-bottom"/> <b>FHIR</b></a> © HL7.org 2011+. |
|
|
124
124
|
<a href="https://github.com/HealthIntersections/FHIRsmith/blob/main/README.md" style="color: gold"><img border="0" src="/FHIRsmith16.png" style="vertical-align: text-bottom"/> FHIRsmith</a> [%ver%] © HealthIntersections.com.au 2023+ |
|
|
125
125
|
([%ms%] ms)
|
|
126
|
+
[%sponsorMessage%]
|
|
126
127
|
</p>
|
|
127
128
|
</div> <!-- /inner-wrapper -->
|
|
128
129
|
</div> <!-- /container -->
|
package/publisher/publisher.js
CHANGED
|
@@ -714,17 +714,38 @@ class PublisherModule {
|
|
|
714
714
|
|
|
715
715
|
// Step 1: Clone supporting repositories into the task directory
|
|
716
716
|
const registryDir = path.join(taskDir, 'ig-registry');
|
|
717
|
-
const historyDir = path.join(taskDir, 'fhir-ig-history-template');
|
|
718
|
-
const templatesDir = path.join(taskDir, 'fhir-web-templates');
|
|
719
717
|
|
|
720
718
|
await this.runCommand('git', ['clone', 'git@github.com:FHIR/ig-registry.git', registryDir],
|
|
721
719
|
{}, task.id, 'Cloning ig-registry');
|
|
722
720
|
|
|
723
|
-
|
|
724
|
-
|
|
721
|
+
// Use website-configured history templates path if provided, otherwise clone the default repo
|
|
722
|
+
let historyDir;
|
|
723
|
+
if (website.history_templates) {
|
|
724
|
+
historyDir = website.history_templates;
|
|
725
|
+
await this.logTaskMessage(task.id, 'info', 'Using configured history templates: ' + historyDir);
|
|
726
|
+
if (!fs.existsSync(historyDir)) {
|
|
727
|
+
throw new Error('Configured history_templates path does not exist: ' + historyDir);
|
|
728
|
+
}
|
|
729
|
+
} else {
|
|
730
|
+
historyDir = path.join(taskDir, 'fhir-ig-history-template');
|
|
731
|
+
await this.runCommand('git', ['clone', 'https://github.com/HL7/fhir-ig-history-template.git', historyDir],
|
|
732
|
+
{}, task.id, 'Cloning fhir-ig-history-template');
|
|
733
|
+
}
|
|
725
734
|
|
|
726
|
-
|
|
727
|
-
|
|
735
|
+
// Use website-configured web templates path if provided, otherwise clone the default repo.
|
|
736
|
+
// This allows pointing to a subdirectory of the templates repo for different target websites.
|
|
737
|
+
let templatesDir;
|
|
738
|
+
if (website.web_templates) {
|
|
739
|
+
templatesDir = website.web_templates;
|
|
740
|
+
await this.logTaskMessage(task.id, 'info', 'Using configured web templates: ' + templatesDir);
|
|
741
|
+
if (!fs.existsSync(templatesDir)) {
|
|
742
|
+
throw new Error('Configured web_templates path does not exist: ' + templatesDir);
|
|
743
|
+
}
|
|
744
|
+
} else {
|
|
745
|
+
templatesDir = path.join(taskDir, 'fhir-web-templates');
|
|
746
|
+
await this.runCommand('git', ['clone', 'https://github.com/HL7/fhir-web-templates.git', templatesDir],
|
|
747
|
+
{}, task.id, 'Cloning fhir-web-templates');
|
|
748
|
+
}
|
|
728
749
|
|
|
729
750
|
// Step 2: Reuse the publisher.jar from the draft build
|
|
730
751
|
const publisherJar = path.join(taskDir, 'publisher.jar');
|
|
@@ -1853,7 +1874,7 @@ class PublisherModule {
|
|
|
1853
1874
|
} else {
|
|
1854
1875
|
content += '<div class="table-responsive">';
|
|
1855
1876
|
content += '<table class="table table-striped">';
|
|
1856
|
-
content += '<thead><tr><th>Name</th><th>Local Folder</th><th>Git Root</th><th>Update Script</th><th>Active</th><th>Created</th><th>Actions</th></tr></thead>';
|
|
1877
|
+
content += '<thead><tr><th>Name</th><th>Local Folder</th><th>Git Root</th><th>History Templates</th><th>Web Templates</th><th>Update Script</th><th>Active</th><th>Created</th><th>Actions</th></tr></thead>';
|
|
1857
1878
|
content += '<tbody>';
|
|
1858
1879
|
|
|
1859
1880
|
websites.forEach(website => {
|
|
@@ -92,7 +92,9 @@
|
|
|
92
92
|
<p>
|
|
93
93
|
<a href="http://www.hl7.org/fhir" style="color: gold" title="Fast Healthcare Interoperability Resources - Home Page"><img border="0" src="/icon-fhir-16.png" style="vertical-align: text-bottom"/> <b>FHIR</b></a> © HL7.org 2011+. |
|
|
94
94
|
<a href="https://github.com/HealthIntersections/FHIRsmith/blob/main/README.md" style="color: gold"><img border="0" src="/FHIRsmith16.png" style="vertical-align: text-bottom"/> FHIRsmith</a> [%ver%] © HealthIntersections.com.au 2023+ |
|
|
95
|
-
Terminology Registry last updated as of [%crawler-date%] | [%total-packages%] packages  
|
|
95
|
+
Terminology Registry last updated as of [%crawler-date%] | [%total-packages%] packages |
|
|
96
|
+
([%ms%] ms)
|
|
97
|
+
[%sponsorMessage%]
|
|
96
98
|
</p>
|
|
97
99
|
</div> <!-- /inner-wrapper -->
|
|
98
100
|
</div> <!-- /container -->
|