babelfhir-ts 1.4.2 → 1.4.3

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/README.md CHANGED
@@ -234,6 +234,7 @@ Options:
234
234
  --schema <format> Generate schema files alongside outputs (supported: zod)
235
235
  --dicomweb Generate DICOMweb helpers typed to ImagingStudy profiles in the IG
236
236
  --recursive (update only) Recursively search subdirectories for lib/ folders
237
+ --force (update only) Force regeneration even if version and flags haven't changed
237
238
  --outDir <dir> Output directory (alias for second positional argument)
238
239
  --fhir-version <ver> FHIR version to target: r4, r4b, or r5 (auto-detected from package if omitted)
239
240
  --package <pkg[@version]> Download FHIR package from registry and process it (latest if no version)
@@ -105,9 +105,10 @@ function npmInstall(packagePath) {
105
105
  }
106
106
  const { args } = result;
107
107
  console.log(`Installing package with ${pmName}...`);
108
+ // shell required on Windows for .cmd wrappers; disabled on Unix to prevent injection
108
109
  const child = spawn(pm.cmd, args, {
109
110
  stdio: 'inherit',
110
- shell: true
111
+ shell: process.platform === 'win32'
111
112
  });
112
113
  child.on('exit', (code) => {
113
114
  if (code === 0) {
@@ -13,14 +13,14 @@ import fs from 'fs';
13
13
  import os from 'os';
14
14
  import path from 'path';
15
15
  import crypto from 'crypto';
16
- import { generateIntoPackageDirect, resetFetchFailureTracking } from '../generator/index.js';
16
+ import { generateIntoPackageDirect, resetFetchFailureTracking, GENERATOR_VERSION } from '../generator/index.js';
17
17
  import { getFhirPackagesCacheDir } from '../generator/core/cacheConfig.js';
18
18
  import { resetDiagnostics, buildQualityReport, formatReportSummary } from '../generator/core/sdDiagnostics.js';
19
19
  import { detectPackageManager } from './installCommand.js';
20
20
  import { spawn } from 'child_process';
21
21
  // ── Public entry point ──────────────────────────────────────────────────────
22
22
  export async function handleUpdateCommand(opts) {
23
- const { packageFilter, recursive, generationFlags, registry, downloadPackage, confirm } = opts;
23
+ const { packageFilter, recursive, force, generationFlags, registry, downloadPackage, confirm } = opts;
24
24
  const startDir = process.cwd();
25
25
  // 1. Discover tgz files
26
26
  const libDirs = discoverLibDirs(startDir, recursive ?? false);
@@ -55,11 +55,17 @@ export async function handleUpdateCommand(opts) {
55
55
  for (const t of targets)
56
56
  console.log(` ${t.igName}@${t.igVersion} → ${path.relative(startDir, t.tgzPath)}`);
57
57
  console.log('');
58
- // 4. Regenerate each package
58
+ // 4. Regenerate each package (skip if already up to date)
59
59
  let updated = 0;
60
+ let skipped = 0;
60
61
  let failed = 0;
61
62
  const updatedPackages = [];
62
63
  for (const target of targets) {
64
+ if (!force && isUpToDate(target, generationFlags)) {
65
+ skipped++;
66
+ console.log(`✓ ${target.igName}@${target.igVersion} — already up to date (babelfhir-ts ${target.generatorVersion})`);
67
+ continue;
68
+ }
63
69
  try {
64
70
  await regeneratePackage(target, generationFlags, registry, downloadPackage);
65
71
  updatedPackages.push({ generatedName: target.generatedName, tgzPath: target.tgzPath });
@@ -70,25 +76,34 @@ export async function handleUpdateCommand(opts) {
70
76
  console.error(`✗ Failed to update ${target.igName}@${target.igVersion}: ${err.message}`);
71
77
  }
72
78
  }
73
- // 5. Patch bun.lock integrity hashes for regenerated packages (avoids full lockfile delete)
79
+ // 5. Remove stale node_modules/<pkg> so bun re-extracts from the updated tgz
74
80
  if (updatedPackages.length > 0) {
75
- patchBunLockIntegrity(updatedPackages);
81
+ removeStaleNodeModules(updatedPackages.map((p) => p.generatedName));
76
82
  }
77
- // 5b. Remove stale node_modules/<pkg> so bun re-extracts from the updated tgz
83
+ // 6. Patch bun.lock integrity hashes BEFORE install so bun sees matching hashes
78
84
  if (updatedPackages.length > 0) {
79
- removeStaleNodeModules(updatedPackages.map((p) => p.generatedName));
85
+ patchBunLockIntegrity(updatedPackages);
80
86
  }
81
- // 6. Single npm install at the end
87
+ // 7. Single npm install at the end
82
88
  if (updated > 0) {
83
89
  console.log('\nReinstalling dependencies...');
84
90
  await runInstall();
85
91
  }
86
- // 7. Summary
92
+ // 8. Re-patch bun.lock integrity after install (bun may recompute hashes)
93
+ if (updatedPackages.length > 0) {
94
+ patchBunLockIntegrity(updatedPackages);
95
+ }
96
+ // 9. Summary
87
97
  console.log('');
88
98
  if (updated > 0)
89
99
  console.log(`✓ Updated ${updated} package${updated === 1 ? '' : 's'}`);
100
+ if (skipped > 0)
101
+ console.log(`✓ ${skipped} package${skipped === 1 ? '' : 's'} already up to date`);
90
102
  if (failed > 0)
91
103
  console.error(`✗ ${failed} package${failed === 1 ? '' : 's'} failed`);
104
+ if (skipped > 0 && updated === 0 && failed === 0) {
105
+ console.log(' Use --force to regenerate anyway.');
106
+ }
92
107
  return {};
93
108
  }
94
109
  // ── Install redirect prompt ─────────────────────────────────────────────────
@@ -200,10 +215,15 @@ async function readTgzMetadata(tgzPath, listFn) {
200
215
  igVersion,
201
216
  ...(pkg?.fhir?.txServer && { txServer: pkg.fhir.txServer }),
202
217
  ...(pkg?.fhir?.displayLanguage && { displayLanguage: pkg.fhir.displayLanguage }),
218
+ ...(pkg?.fhir?.fhirVersion && { fhirVersion: pkg.fhir.fhirVersion }),
203
219
  ...(pkg?.fhir?.dicomweb && { dicomweb: true }),
204
220
  ...(pkg?.fhir?.noClient && { noClient: true }),
205
221
  ...(pkg?.fhir?.noClasses && { noClasses: true }),
206
222
  ...(pkg?.fhir?.schema && { schema: pkg.fhir.schema }),
223
+ // Extract generator version from devDependencies (e.g. "^1.4.2" → "1.4.2")
224
+ ...(pkg?.devDependencies?.['babelfhir-ts'] && {
225
+ generatorVersion: pkg.devDependencies['babelfhir-ts'].replace(/^[^\d]*/, ''),
226
+ }),
207
227
  };
208
228
  }
209
229
  catch {
@@ -222,6 +242,32 @@ function filterBySpec(all, spec) {
222
242
  // No version — match by name only
223
243
  return all.filter(t => t.igName === spec);
224
244
  }
245
+ // ── Up-to-date check ────────────────────────────────────────────────────────
246
+ /**
247
+ * Check whether a package was already generated with the current babelfhir-ts
248
+ * version and the same effective flags. Returns true if regeneration can be skipped.
249
+ */
250
+ function isUpToDate(target, cliFlags) {
251
+ // No stored version → was generated before this feature existed → regenerate
252
+ if (!target.generatorVersion)
253
+ return false;
254
+ // Compare generator versions (strip pre-release suffixes for comparison: 1.4.2-dev.xxx → 1.4.2)
255
+ const storedMajorMinorPatch = target.generatorVersion.split('-')[0];
256
+ const currentMajorMinorPatch = GENERATOR_VERSION.split('-')[0];
257
+ if (storedMajorMinorPatch !== currentMajorMinorPatch)
258
+ return false;
259
+ // Compare generation flags that affect output
260
+ const flagKeys = ['txServer', 'displayLanguage', 'fhirVersion', 'dicomweb', 'noClient', 'noClasses', 'schema'];
261
+ for (const key of flagKeys) {
262
+ const stored = target[key];
263
+ const cli = cliFlags[key];
264
+ // CLI flag explicitly set and differs from stored → needs regeneration
265
+ if (cli !== undefined && cli !== null && cli !== false && cli !== '' && String(cli) !== String(stored ?? '')) {
266
+ return false;
267
+ }
268
+ }
269
+ return true;
270
+ }
225
271
  // ── Regeneration ────────────────────────────────────────────────────────────
226
272
  async function regeneratePackage(target, generationFlags, registry, downloadPackage) {
227
273
  const spec = `${target.igName}@${target.igVersion}`;
@@ -238,6 +284,10 @@ async function regeneratePackage(target, generationFlags, registry, downloadPack
238
284
  mergedFlags.displayLanguage = target.displayLanguage;
239
285
  console.log(` Using display-language from package metadata: ${target.displayLanguage}`);
240
286
  }
287
+ if (target.fhirVersion && !mergedFlags.fhirVersion) {
288
+ mergedFlags.fhirVersion = target.fhirVersion;
289
+ console.log(` Using fhir-version from package metadata: ${target.fhirVersion}`);
290
+ }
241
291
  if (target.dicomweb && mergedFlags.dicomweb === undefined)
242
292
  mergedFlags.dicomweb = true;
243
293
  if (target.noClient && mergedFlags.noClient === undefined)
@@ -303,41 +353,46 @@ export function patchBunLockIntegrity(updatedPackages) {
303
353
  const pm = detectPackageManager();
304
354
  if (!pm.cmd.includes('bun'))
305
355
  return;
306
- // Walk up to find bun.lock (in monorepos it's at the workspace root, not cwd)
307
- let lockPath = null;
356
+ // Collect ALL bun.lock files walking up (monorepos may have one at the
357
+ // workspace root AND one in a nested app directory).
358
+ const lockPaths = [];
308
359
  let dir = process.cwd();
309
360
  while (true) {
310
361
  const candidate = path.join(dir, 'bun.lock');
311
- if (fs.existsSync(candidate)) {
312
- lockPath = candidate;
313
- break;
314
- }
362
+ if (fs.existsSync(candidate))
363
+ lockPaths.push(candidate);
315
364
  const parent = path.dirname(dir);
316
365
  if (parent === dir)
317
366
  break;
318
367
  dir = parent;
319
368
  }
320
- if (!lockPath)
369
+ if (lockPaths.length === 0)
321
370
  return;
322
- let lockContent = fs.readFileSync(lockPath, 'utf8');
323
- let patched = 0;
324
- for (const { generatedName, tgzPath } of updatedPackages) {
325
- // Compute new SHA-512 integrity hash
326
- const tgzBytes = fs.readFileSync(tgzPath);
327
- const hash = crypto.createHash('sha512').update(tgzBytes).digest('base64');
328
- const newIntegrity = `sha512-${hash}`;
329
- // Match all entries for this package in bun.lock:
330
- // "hl7.fhir.uv.ips-generated": [..., "sha512-oldHash=="],
331
- // "workspace/hl7.fhir.uv.ips-generated": [..., "sha512-oldHash=="],
332
- const pattern = new RegExp(`("(?:[^"]*\\/)?${escapeRegExp(generatedName)}"\\s*:\\s*\\[.*?)(sha512-[A-Za-z0-9+/]+=*)`, 'g');
333
- lockContent = lockContent.replace(pattern, (_, prefix, _oldHash) => {
334
- patched++;
335
- return `${prefix}${newIntegrity}`;
336
- });
371
+ let totalPatched = 0;
372
+ for (const lockPath of lockPaths) {
373
+ let lockContent = fs.readFileSync(lockPath, 'utf8');
374
+ let patched = 0;
375
+ for (const { generatedName, tgzPath } of updatedPackages) {
376
+ // Compute new SHA-512 integrity hash
377
+ const tgzBytes = fs.readFileSync(tgzPath);
378
+ const hash = crypto.createHash('sha512').update(tgzBytes).digest('base64');
379
+ const newIntegrity = `sha512-${hash}`;
380
+ // Match entries with existing integrity hashes and replace them:
381
+ // "hl7.fhir.uv.ips-generated": [..., "sha512-oldHash=="],
382
+ const pattern = new RegExp(`("(?:[^"]*\\/)?${escapeRegExp(generatedName)}"\\s*:\\s*\\[.*?)(sha512-[A-Za-z0-9+/]+=*)`, 'g');
383
+ lockContent = lockContent.replace(pattern, (_, prefix, _oldHash) => {
384
+ patched++;
385
+ return `${prefix}${newIntegrity}`;
386
+ });
387
+ }
388
+ if (patched > 0) {
389
+ fs.writeFileSync(lockPath, lockContent, 'utf8');
390
+ totalPatched += patched;
391
+ console.log(`Patched ${patched} integrity hash${patched === 1 ? '' : 'es'} in ${path.relative(process.cwd(), lockPath) || 'bun.lock'}`);
392
+ }
337
393
  }
338
- if (patched > 0) {
339
- fs.writeFileSync(lockPath, lockContent, 'utf8');
340
- console.log(`Patched ${patched} integrity hash${patched === 1 ? '' : 'es'} in bun.lock`);
394
+ if (totalPatched === 0) {
395
+ console.log('No integrity hashes found to patch in bun.lock');
341
396
  }
342
397
  }
343
398
  function escapeRegExp(s) {
@@ -437,7 +492,8 @@ function runInstall() {
437
492
  const pm = detectPackageManager();
438
493
  const pmName = path.basename(pm.cmd).replace(/\.(cmd|exe)$/, '');
439
494
  console.log(`Running ${pmName} install...`);
440
- const child = spawn(pm.cmd, ['install'], { stdio: 'inherit', shell: true });
495
+ // shell required on Windows for .cmd wrappers; disabled on Unix to prevent injection
496
+ const child = spawn(pm.cmd, ['install'], { stdio: 'inherit', shell: process.platform === 'win32' });
441
497
  child.on('exit', (code) => code === 0 ? resolve() : reject(new Error(`${pmName} install failed (exit ${code})`)));
442
498
  child.on('error', (err) => reject(new Error(`Failed to run ${pmName}: ${err.message}`)));
443
499
  });
@@ -5,6 +5,37 @@
5
5
  import http from "http";
6
6
  import https from "https";
7
7
  import { USER_AGENT, FETCH_TIMEOUT_MS } from './constants.js';
8
+ /** Maximum number of HTTP redirects to follow before aborting. */
9
+ const MAX_REDIRECTS = 10;
10
+ /**
11
+ * Validate that a URL uses an allowed scheme (http/https) and does not target
12
+ * private/internal network addresses (SSRF mitigation).
13
+ */
14
+ export function assertSafeUrl(url) {
15
+ let parsed;
16
+ try {
17
+ parsed = new URL(url);
18
+ }
19
+ catch {
20
+ throw new Error(`Invalid URL: ${url}`);
21
+ }
22
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
23
+ throw new Error(`Blocked request to non-HTTP URL: ${url}`);
24
+ }
25
+ const host = parsed.hostname.toLowerCase();
26
+ if (host === 'localhost' ||
27
+ host === '127.0.0.1' ||
28
+ host === '::1' ||
29
+ host === '0.0.0.0' ||
30
+ host.startsWith('169.254.') ||
31
+ host.startsWith('10.') ||
32
+ host.startsWith('192.168.') ||
33
+ /^172\.(1[6-9]|2\d|3[01])\./.test(host) ||
34
+ host.endsWith('.internal') ||
35
+ host.endsWith('.local')) {
36
+ throw new Error(`Blocked request to private/internal address: ${host}`);
37
+ }
38
+ }
8
39
  /**
9
40
  * Default headers for FHIR API requests
10
41
  */
@@ -84,15 +115,20 @@ export async function fetchArrayBuffer(url, options = {}) {
84
115
  * Fetch with native https module for streaming (used by downloadFile)
85
116
  * This provides better control for large file downloads
86
117
  */
87
- export function fetchStream(url) {
118
+ export function fetchStream(url, redirectCount = 0) {
119
+ assertSafeUrl(url);
88
120
  return new Promise((resolve, reject) => {
89
121
  const client = url.startsWith('https') ? https : http;
90
122
  client.get(url, {
91
123
  headers: FHIR_HEADERS
92
124
  }, (response) => {
93
- // Handle redirects
125
+ // Handle redirects with depth limit
94
126
  if (response.statusCode && response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
95
- fetchStream(response.headers.location).then(resolve).catch(reject);
127
+ if (redirectCount >= MAX_REDIRECTS) {
128
+ reject(new Error(`Too many redirects (max ${MAX_REDIRECTS})`));
129
+ return;
130
+ }
131
+ fetchStream(response.headers.location, redirectCount + 1).then(resolve).catch(reject);
96
132
  return;
97
133
  }
98
134
  if (response.statusCode !== 200) {
@@ -107,7 +143,8 @@ export function fetchStream(url) {
107
143
  * Fetch JSON using native https module with timeout support
108
144
  * Used for fallback URLs where we need more control
109
145
  */
110
- export function fetchJsonNative(url, options = {}) {
146
+ export function fetchJsonNative(url, options = {}, redirectCount = 0) {
147
+ assertSafeUrl(url);
111
148
  return new Promise((resolve, reject) => {
112
149
  const client = url.startsWith('https') ? https : http;
113
150
  const req = client.get(url, {
@@ -117,9 +154,13 @@ export function fetchJsonNative(url, options = {}) {
117
154
  },
118
155
  timeout: options.timeout || FETCH_TIMEOUT_MS
119
156
  }, (res) => {
120
- // Handle redirects
157
+ // Handle redirects with depth limit
121
158
  if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
122
- fetchJsonNative(res.headers.location, options).then(resolve).catch(reject);
159
+ if (redirectCount >= MAX_REDIRECTS) {
160
+ reject(new Error(`Too many redirects (max ${MAX_REDIRECTS})`));
161
+ return;
162
+ }
163
+ fetchJsonNative(res.headers.location, options, redirectCount + 1).then(resolve).catch(reject);
123
164
  return;
124
165
  }
125
166
  if (res.statusCode !== 200) {
@@ -26,8 +26,16 @@ export function buildRequiredConstraints(fields, arrayFieldPaths, fieldPathMap,
26
26
  let rel = relBase;
27
27
  if (!rel)
28
28
  return;
29
- // Skip slicing-declaration fields for choice[x] when slice fields exist.
30
- if (relFull.includes('[x]') && !field.sliceName && !field.typeOptions && slicedChoicePaths.has(relFull)) {
29
+ // Skip slicing-declaration fields for choice[x] when slice fields exist
30
+ // AND the field is not required. When a profile slices value[x], the field list
31
+ // contains both a slicing-declaration field (no typeOptions, inherits base type
32
+ // like Quantity) and slice fields (with typeOptions). For optional fields, skip
33
+ // the declaration since the slice field handles the constraint. But for required
34
+ // fields (min >= 1), we MUST still emit the existence check — e.g.,
35
+ // medication[x] (min:1) needs "medication.exists()" to verify at least one
36
+ // concrete medication variant is present.
37
+ const fieldMin = typeof field.min === 'number' ? field.min : (field.isOptional ? 0 : 1);
38
+ if (relFull.includes('[x]') && !field.sliceName && !field.typeOptions && slicedChoicePaths.has(relFull) && fieldMin < 1) {
31
39
  return;
32
40
  }
33
41
  // For choice types (value[x]), append the concrete type name to get the correct accessor.
@@ -25,7 +25,7 @@ const _generatorPkgPath = [
25
25
  catch {
26
26
  return false;
27
27
  } });
28
- const GENERATOR_VERSION = _generatorPkgPath ? JSON.parse(fs.readFileSync(_generatorPkgPath, 'utf8')).version : 'unknown';
28
+ export const GENERATOR_VERSION = _generatorPkgPath ? JSON.parse(fs.readFileSync(_generatorPkgPath, 'utf8')).version : 'unknown';
29
29
  import { resetTimings, startPhase, getTimingSummary, formatTimingSummary } from './timing.js';
30
30
  export { getTimingSummary, formatTimingSummary } from './timing.js';
31
31
  const log = logger.withTag('generator');
@@ -129,9 +129,10 @@ async function compileTypeScriptToJS(dir) {
129
129
  };
130
130
  const tsconfigPath = path.join(dir, 'tsconfig.temp.json');
131
131
  fs.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2));
132
+ // shell required on Windows for .cmd wrappers; disabled on Unix to prevent injection
132
133
  const child = spawn(tscCmd, ['-p', tsconfigPath], {
133
134
  stdio: 'inherit',
134
- shell: true
135
+ shell: process.platform === 'win32'
135
136
  });
136
137
  child.on('exit', (code) => {
137
138
  // Clean up temp tsconfig
@@ -448,6 +449,7 @@ export async function generateIntoPackage(packageArchivePath, outArchivePath, fl
448
449
  ...(originalPkg.fhirVersions && { fhirVersions: originalPkg.fhirVersions }),
449
450
  ...(flags?.txServer && { txServer: flags.txServer }),
450
451
  ...(flags?.displayLanguage && { displayLanguage: flags.displayLanguage }),
452
+ ...(flags?.fhirVersion && { fhirVersion: flags.fhirVersion }),
451
453
  ...(flags?.dicomweb && { dicomweb: true }),
452
454
  ...(flags?.noClient && { noClient: true }),
453
455
  ...(flags?.noClasses && { noClasses: true }),
@@ -696,6 +698,7 @@ export async function generateIntoPackageDirect(packageArchivePath, flags) {
696
698
  ...(originalPkg.fhirVersions && { fhirVersions: originalPkg.fhirVersions }),
697
699
  ...(flags?.txServer && { txServer: flags.txServer }),
698
700
  ...(flags?.displayLanguage && { displayLanguage: flags.displayLanguage }),
701
+ ...(flags?.fhirVersion && { fhirVersion: flags.fhirVersion }),
699
702
  ...(flags?.dicomweb && { dicomweb: true }),
700
703
  ...(flags?.noClient && { noClient: true }),
701
704
  ...(flags?.noClasses && { noClasses: true }),
@@ -314,7 +314,32 @@ export async function extractPackage(packagePath) {
314
314
  await extract({ cwd: extractDir, file: packagePath });
315
315
  }
316
316
  else if (packagePath.endsWith(".zip")) {
317
- await fs.createReadStream(packagePath).pipe(unzipper.Extract({ path: extractDir })).promise();
317
+ // Use unzipper.Parse to validate each entry path against Zip Slip before extraction
318
+ const resolvedExtractDir = path.resolve(extractDir);
319
+ await new Promise((resolve, reject) => {
320
+ fs.createReadStream(packagePath)
321
+ .pipe(unzipper.Parse())
322
+ .on('entry', (entry) => {
323
+ const entryPath = path.join(extractDir, entry.path);
324
+ const resolvedEntry = path.resolve(entryPath);
325
+ // Zip Slip check: reject entries that escape the extraction directory
326
+ if (!resolvedEntry.startsWith(resolvedExtractDir + path.sep) && resolvedEntry !== resolvedExtractDir) {
327
+ logger.warn(`[security] Skipping zip entry with path traversal: ${entry.path}`);
328
+ entry.autodrain();
329
+ return;
330
+ }
331
+ const dir = path.dirname(entryPath);
332
+ fs.mkdirSync(dir, { recursive: true });
333
+ if (entry.type === 'Directory') {
334
+ entry.autodrain();
335
+ }
336
+ else {
337
+ entry.pipe(fs.createWriteStream(entryPath));
338
+ }
339
+ })
340
+ .on('close', resolve)
341
+ .on('error', reject);
342
+ });
318
343
  }
319
344
  else {
320
345
  throw new Error("Unsupported package format. Only .tgz and .zip are supported.");
package/out/src/main.js CHANGED
@@ -58,6 +58,7 @@ function printUsage() {
58
58
  console.log(" --schema <format> Generate schema files alongside outputs (supported: zod)");
59
59
  console.log(" --dicomweb Generate DICOMweb helpers typed to ImagingStudy profiles in the IG");
60
60
  console.log(" --recursive (update only) Recursively search subdirectories for lib/ folders");
61
+ console.log(" --force (update only) Force regeneration even if version and flags haven't changed");
61
62
  console.log(" --outDir <dir> Output directory (alias for second positional argument)");
62
63
  console.log(" --fhir-version <ver> FHIR version to target: r4, r4b, or r5 (auto-detected from package if omitted)");
63
64
  console.log(" --package <pkg[@version]> Download FHIR package from registry and process it (latest if no version)");
@@ -234,6 +235,10 @@ export function parseCliArgs(argv) {
234
235
  out.flags.recursive = true;
235
236
  continue;
236
237
  }
238
+ if (arg === '--force') {
239
+ out.flags.force = true;
240
+ continue;
241
+ }
237
242
  if (arg === '--cache-dir') {
238
243
  const val = argv[i + 1];
239
244
  if (!val || val.startsWith('-'))
@@ -377,6 +382,7 @@ export async function run() {
377
382
  const result = await handleUpdateCommand({
378
383
  packageFilter: packageSpec || positionals[1] || undefined,
379
384
  recursive: !!generationFlags.recursive,
385
+ force: !!generationFlags.force,
380
386
  generationFlags,
381
387
  registry,
382
388
  downloadPackage,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "babelfhir-ts",
3
- "version": "1.4.2",
3
+ "version": "1.4.3",
4
4
  "description": "BabelFHIR-TS: generate TypeScript interfaces, validators, and helper classes from FHIR R4/R4B/R5 StructureDefinitions (profiles) directly inside package archives.",
5
5
  "type": "module",
6
6
  "main": "out/src/main.js",