babelfhir-ts 1.2.2 → 1.2.4

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.
@@ -321,6 +321,11 @@ export async function generateIntoPackage(packageArchivePath, outArchivePath, fl
321
321
  valueSets = await expandValueSetsWithTx(valueSets, valueSetCodesMap, structureDefinitions, flags.txServer);
322
322
  }
323
323
  const outputDir = path.join(extractedRoot, 'generated');
324
+ // Always start with a clean generated/ directory to avoid stale compiled
325
+ // artifacts (e.g. .d.ts/.js without .ts sources) from a cached extraction.
326
+ if (fs.existsSync(outputDir)) {
327
+ fs.rmSync(outputDir, { recursive: true, force: true });
328
+ }
324
329
  ensureDirectoryExists(outputDir);
325
330
  // Generate TypeScript files for ValueSets inside the embedded package
326
331
  const didGenerateValueSets = emitValueSetFiles(valueSets, outputDir, { deduplicateFilenames: true });
@@ -345,6 +350,9 @@ export async function generateIntoPackage(packageArchivePath, outArchivePath, fl
345
350
  packageName = originalPkg.name || packageName;
346
351
  packageVersion = originalPkg.version || packageVersion;
347
352
  }
353
+ const generatedExports = {
354
+ '.': { types: './index.d.ts', import: './index.js' }
355
+ };
348
356
  const generatedPackageJson = {
349
357
  name: `${packageName}-generated`,
350
358
  version: packageVersion,
@@ -352,6 +360,7 @@ export async function generateIntoPackage(packageArchivePath, outArchivePath, fl
352
360
  type: 'module',
353
361
  main: 'index.js',
354
362
  types: 'index.d.ts',
363
+ exports: generatedExports,
355
364
  scripts: {},
356
365
  keywords: ['fhir', 'typescript', 'generated'],
357
366
  license: 'MIT',
@@ -373,9 +382,6 @@ export async function generateIntoPackage(packageArchivePath, outArchivePath, fl
373
382
  generatedPackageJson.dependencies[`@babelfhir-ts/zod-${versionSlug()}`] = '^0.1.0';
374
383
  generatedPackageJson.peerDependencies['zod'] = '^4.0.0';
375
384
  }
376
- const generatedPackageJsonPath = path.join(outputDir, 'package.json');
377
- fs.writeFileSync(generatedPackageJsonPath, JSON.stringify(generatedPackageJson, null, 2));
378
- logger.log(`Created package.json in generated folder: ${packageName}-generated@${packageVersion}`);
379
385
  copyFhirAmbientDeclaration(outputDir);
380
386
  // Generate index.ts that exports all interfaces
381
387
  logger.log('Generating index.ts exports...');
@@ -389,6 +395,13 @@ export async function generateIntoPackage(packageArchivePath, outArchivePath, fl
389
395
  });
390
396
  logger.log('FHIR client generated');
391
397
  }
398
+ // Only add ./fhir-client export if the directory was actually created
399
+ if (fs.existsSync(path.join(outputDir, 'fhir-client'))) {
400
+ generatedExports['./fhir-client'] = { types: './fhir-client/index.d.ts', import: './fhir-client/index.js' };
401
+ }
402
+ const generatedPackageJsonPath = path.join(outputDir, 'package.json');
403
+ fs.writeFileSync(generatedPackageJsonPath, JSON.stringify(generatedPackageJson, null, 2));
404
+ logger.log(`Created package.json in generated folder: ${packageName}-generated@${packageVersion}`);
392
405
  // Install base zod type stubs when --schema zod is enabled (needed for tsc compilation)
393
406
  if (flags?.schema === 'zod') {
394
407
  const { installBaseZodTypes } = await import('./emitters/zod/zodStubInstaller.js');
@@ -458,6 +471,157 @@ export async function generateIntoPackage(packageArchivePath, outArchivePath, fl
458
471
  }
459
472
  }
460
473
  }
474
+ /**
475
+ * Generate TypeScript artefacts into a package but skip the intermediate archive
476
+ * round-trip. Returns the generated directory path and a cleanup callback.
477
+ * Used by the install command to avoid double tar operations.
478
+ */
479
+ export async function generateIntoPackageDirect(packageArchivePath, flags) {
480
+ initGenerationContext(flags, path.dirname(packageArchivePath));
481
+ if (!packageArchivePath.endsWith('.tgz') && !packageArchivePath.endsWith('.zip'))
482
+ throw new Error('Only .tgz or .zip supported.');
483
+ const extractResult = await extractPackage(packageArchivePath);
484
+ const extractedRoot = extractResult.path;
485
+ const shouldCleanup = !extractResult.isCached;
486
+ // Auto-detect FHIR version from package when not explicitly set
487
+ if (!flags?.fhirVersion) {
488
+ const detected = detectFhirVersion(extractedRoot);
489
+ await initVersionContext(detected ?? DEFAULT_FHIR_VERSION);
490
+ }
491
+ else {
492
+ await initVersionContext(flags.fhirVersion);
493
+ }
494
+ const fhirInterfaceNames = ctx().interfaceNames;
495
+ const { getPackageManager } = await import('./parser/packageManager.js');
496
+ const packageManager = getPackageManager();
497
+ packageManager.loadDependenciesFromExtractedPackage(extractedRoot);
498
+ logger.log(`Loaded package dependencies (${packageManager.getTotalStructureDefinitionCount()} total SDs available)`);
499
+ const structureDefinitions = readStructureDefinitionsFromDir(extractedRoot);
500
+ await ensureDependenciesDownloaded(extractedRoot);
501
+ const valueSetCodesMap = readValueSetCodesWithDependencies(extractedRoot);
502
+ let valueSets = readValueSetsFromDir(extractedRoot);
503
+ logger.log(`Package contains ${structureDefinitions.length} StructureDefinitions.`);
504
+ logger.log(`Loaded ${Object.keys(valueSetCodesMap).length} ValueSet code mappings (including dependencies)`);
505
+ const bindingUrls = collectValueSetBindingUrls(structureDefinitions);
506
+ enrichValueSetsFromCodeMap(valueSets, valueSetCodesMap, bindingUrls);
507
+ if (flags?.txServer) {
508
+ valueSets = await expandValueSetsWithTx(valueSets, valueSetCodesMap, structureDefinitions, flags.txServer);
509
+ }
510
+ const outputDir = path.join(extractedRoot, 'generated');
511
+ // Always start with a clean generated/ directory to avoid stale compiled
512
+ // artifacts (e.g. .d.ts/.js without .ts sources) from a cached extraction.
513
+ if (fs.existsSync(outputDir)) {
514
+ fs.rmSync(outputDir, { recursive: true, force: true });
515
+ }
516
+ ensureDirectoryExists(outputDir);
517
+ const didGenerateValueSets = emitValueSetFiles(valueSets, outputDir, { deduplicateFilenames: true });
518
+ if (!didGenerateValueSets)
519
+ cleanupStaleValueSetDir(outputDir);
520
+ const { existingStructureDefinitions, profileIdToName, profileUrlToName } = buildProfileRegistries(structureDefinitions, fhirInterfaceNames);
521
+ registerLocalStructureDefinitions(structureDefinitions);
522
+ const depSDs = readStructureDefinitionsFromDependencies(extractedRoot);
523
+ registerLocalStructureDefinitions(depSDs);
524
+ for (const sd of structureDefinitions) {
525
+ await processStructureDefinition(sd, { outputDir, fhirSourceHint: '', valueSetCodesMap, valueSets, existingStructureDefinitions, profileIdToName, profileUrlToName, flags });
526
+ }
527
+ // Create package.json in generated folder
528
+ const originalPackageJsonPath = path.join(extractedRoot, 'package', 'package.json');
529
+ let packageName = path.basename(packageArchivePath, path.extname(packageArchivePath));
530
+ let packageVersion = '1.0.0';
531
+ if (fs.existsSync(originalPackageJsonPath)) {
532
+ const originalPkg = JSON.parse(fs.readFileSync(originalPackageJsonPath, 'utf-8'));
533
+ packageName = originalPkg.name || packageName;
534
+ packageVersion = originalPkg.version || packageVersion;
535
+ }
536
+ const generatedExports = {
537
+ '.': { types: './index.d.ts', import: './index.js' }
538
+ };
539
+ const generatedPackageJson = {
540
+ name: `${packageName}-generated`,
541
+ version: packageVersion,
542
+ description: `Generated TypeScript interfaces for ${packageName}`,
543
+ type: 'module',
544
+ main: 'index.js',
545
+ types: 'index.d.ts',
546
+ exports: generatedExports,
547
+ scripts: {},
548
+ keywords: ['fhir', 'typescript', 'generated'],
549
+ license: 'MIT',
550
+ peerDependencies: { 'fhirpath': '^3.0.0 || ^4.0.0' },
551
+ dependencies: { '@types/fhir': '^0.0.41' }
552
+ };
553
+ if (!flags?.noClient) {
554
+ generatedPackageJson.dependencies[`@babelfhir-ts/client-${versionSlug()}`] = '^0.2.0';
555
+ }
556
+ if (flags?.schema === 'zod') {
557
+ generatedPackageJson.dependencies[`@babelfhir-ts/zod-${versionSlug()}`] = '^0.1.0';
558
+ generatedPackageJson.peerDependencies['zod'] = '^4.0.0';
559
+ }
560
+ copyFhirAmbientDeclaration(outputDir);
561
+ logger.log('Generating index.ts exports...');
562
+ await generateIndexFile(outputDir);
563
+ if (!flags?.noClient) {
564
+ logger.log('Generating FHIR client...');
565
+ const { generateClient } = await import('./emitters/client/clientGenerator.js');
566
+ generateClient({ outputDir });
567
+ logger.log('FHIR client generated');
568
+ }
569
+ // Only add ./fhir-client export if the directory was actually created
570
+ if (fs.existsSync(path.join(outputDir, 'fhir-client'))) {
571
+ generatedExports['./fhir-client'] = { types: './fhir-client/index.d.ts', import: './fhir-client/index.js' };
572
+ }
573
+ const generatedPackageJsonPath = path.join(outputDir, 'package.json');
574
+ fs.writeFileSync(generatedPackageJsonPath, JSON.stringify(generatedPackageJson, null, 2));
575
+ logger.log(`Created package.json in generated folder: ${packageName}-generated@${packageVersion}`);
576
+ if (flags?.schema === 'zod') {
577
+ const { installBaseZodTypes } = await import('./emitters/zod/zodStubInstaller.js');
578
+ installBaseZodTypes(outputDir);
579
+ }
580
+ const { installFhirpathStub, removeFhirpathStub } = await import('./emitters/validator/fhirpathStubInstaller.js');
581
+ installFhirpathStub(outputDir);
582
+ const nodeTypesDir = path.join(outputDir, 'node_modules', '@types', 'node');
583
+ fs.mkdirSync(nodeTypesDir, { recursive: true });
584
+ fs.writeFileSync(path.join(nodeTypesDir, 'index.d.ts'), `declare module 'module' {\n export function createRequire(filename: string | URL): NodeRequire;\n}\n`);
585
+ fs.writeFileSync(path.join(nodeTypesDir, 'package.json'), JSON.stringify({ name: '@types/node', version: '0.0.0-stub', types: 'index.d.ts' }));
586
+ logger.log('Compiling TypeScript to JavaScript...');
587
+ await compileTypeScriptToJS(outputDir);
588
+ removeFhirpathStub(outputDir);
589
+ try {
590
+ fs.rmSync(path.join(outputDir, 'node_modules', '@types'), { recursive: true, force: true });
591
+ }
592
+ catch { /* ignore */ }
593
+ const babelfhirDir = path.join(outputDir, 'node_modules', '@babelfhir-ts');
594
+ if (fs.existsSync(babelfhirDir)) {
595
+ for (const entry of fs.readdirSync(babelfhirDir)) {
596
+ if (flags?.schema === 'zod' && entry.startsWith('zod-'))
597
+ continue;
598
+ try {
599
+ fs.rmSync(path.join(babelfhirDir, entry), { recursive: true, force: true });
600
+ }
601
+ catch { /* ignore */ }
602
+ }
603
+ try {
604
+ fs.rmdirSync(babelfhirDir);
605
+ }
606
+ catch { /* not empty — fine */ }
607
+ }
608
+ const nodeModulesDir = path.join(outputDir, 'node_modules');
609
+ try {
610
+ fs.rmdirSync(nodeModulesDir);
611
+ }
612
+ catch { /* not empty or doesn't exist — fine */ }
613
+ return {
614
+ generatedDir: outputDir,
615
+ cleanup: () => {
616
+ if (shouldCleanup) {
617
+ try {
618
+ fs.rmSync(extractedRoot, { recursive: true, force: true });
619
+ }
620
+ catch { /* ignore */ }
621
+ }
622
+ }
623
+ };
624
+ }
461
625
  function deriveOutputArchiveName(input) {
462
626
  const ext = input.endsWith('.tgz') ? '.tgz' : '.zip';
463
627
  return input.replace(ext, `.with-generated${ext}`);
@@ -5,7 +5,7 @@ import unzipper from "unzipper";
5
5
  import { parseValueSet } from "./vsParser.js";
6
6
  import { logger } from "../../logger.js";
7
7
  import { getFhirPackagesCacheDir, ensureCacheDir } from "../core/cacheConfig.js";
8
- import { fetchArrayBuffer } from '../core/fetchUtils.js';
8
+ import { downloadFile } from '../core/utils.js';
9
9
  import { versionSlug } from '../fhir/versionContext.js';
10
10
  /**
11
11
  * FHIR package registries in priority order.
@@ -451,7 +451,51 @@ export function readValueSetCodesFromDir(extractedRoot, codeSystemMap) {
451
451
  */
452
452
  export async function ensureDependenciesDownloaded(extractedRoot) {
453
453
  const cacheDir = getFhirPackagesCacheDir();
454
+ ensureCacheDir(cacheDir);
454
455
  const visited = new Set();
456
+ async function downloadAndExtract(depName, depVersion) {
457
+ const depDir = path.join(cacheDir, `${depName}@${depVersion}`);
458
+ // Already cached (@ separator)?
459
+ if (fs.existsSync(depDir) && fs.readdirSync(depDir).length > 0) {
460
+ return depDir;
461
+ }
462
+ // Already cached (# separator used by Java tooling)?
463
+ const altDir = path.join(cacheDir, `${depName}#${depVersion}`);
464
+ if (fs.existsSync(altDir) && fs.readdirSync(altDir).length > 0) {
465
+ return altDir;
466
+ }
467
+ // Download from registries (streaming to avoid buffering large packages in RAM)
468
+ log.info(`Downloading dependency: ${depName}@${depVersion}`);
469
+ const tgzPath = path.join(cacheDir, `${depName}-${depVersion}.tgz`);
470
+ let downloaded = false;
471
+ for (const registry of FHIR_REGISTRIES) {
472
+ const url = `${registry}/${depName}/${depVersion}`;
473
+ try {
474
+ await downloadFile(url, tgzPath);
475
+ downloaded = true;
476
+ break;
477
+ }
478
+ catch (err) {
479
+ log.debug(`Failed to download ${depName}@${depVersion} from ${registry}: ${err.message}`);
480
+ }
481
+ }
482
+ if (!downloaded) {
483
+ log.warn(`Could not download dependency ${depName}@${depVersion} from any registry`);
484
+ return null;
485
+ }
486
+ // Extract the downloaded package
487
+ fs.mkdirSync(depDir, { recursive: true });
488
+ try {
489
+ const { extract } = await import('tar');
490
+ await extract({ cwd: depDir, file: tgzPath });
491
+ log.info(`Cached dependency: ${depName}@${depVersion}`);
492
+ return depDir;
493
+ }
494
+ catch (err) {
495
+ log.warn(`Failed to extract ${depName}@${depVersion}: ${err.message}`);
496
+ return null;
497
+ }
498
+ }
455
499
  async function walkDeps(pkgDir) {
456
500
  const pkgDirNorm = path.normalize(pkgDir);
457
501
  if (visited.has(pkgDirNorm))
@@ -472,52 +516,16 @@ export async function ensureDependenciesDownloaded(extractedRoot) {
472
516
  catch {
473
517
  return;
474
518
  }
475
- for (const [depName, depVersion] of Object.entries(deps)) {
476
- const depDir = path.join(cacheDir, `${depName}@${depVersion}`);
477
- // Already cached (@ separator)?
478
- if (fs.existsSync(depDir) && fs.readdirSync(depDir).length > 0) {
479
- await walkDeps(depDir);
480
- continue;
481
- }
482
- // Already cached (# separator used by Java tooling)?
483
- const altDir = path.join(cacheDir, `${depName}#${depVersion}`);
484
- if (fs.existsSync(altDir) && fs.readdirSync(altDir).length > 0) {
485
- await walkDeps(altDir);
486
- continue;
487
- }
488
- // Download from registries
489
- log.info(`Downloading dependency: ${depName}@${depVersion}`);
490
- const tgzPath = path.join(cacheDir, `${depName}-${depVersion}.tgz`);
491
- let downloaded = false;
492
- for (const registry of FHIR_REGISTRIES) {
493
- const url = `${registry}/${depName}/${depVersion}`;
494
- try {
495
- const response = await fetchArrayBuffer(url);
496
- ensureCacheDir(cacheDir);
497
- fs.writeFileSync(tgzPath, Buffer.from(response.data));
498
- downloaded = true;
499
- break;
500
- }
501
- catch (err) {
502
- log.debug(`Failed to download ${depName}@${depVersion} from ${registry}: ${err.message}`);
503
- }
504
- }
505
- if (!downloaded) {
506
- log.warn(`Could not download dependency ${depName}@${depVersion} from any registry`);
507
- continue;
508
- }
509
- // Extract the downloaded package
510
- fs.mkdirSync(depDir, { recursive: true });
511
- try {
512
- const { extract } = await import('tar');
513
- await extract({ cwd: depDir, file: tgzPath });
514
- log.info(`Cached dependency: ${depName}@${depVersion}`);
515
- await walkDeps(depDir);
516
- }
517
- catch (err) {
518
- log.warn(`Failed to extract ${depName}@${depVersion}: ${err.message}`);
519
- }
519
+ // Download all sibling dependencies in parallel
520
+ const depEntries = Object.entries(deps);
521
+ const results = await Promise.all(depEntries.map(([depName, depVersion]) => downloadAndExtract(depName, depVersion)));
522
+ // Recursively walk downloaded dependencies in parallel
523
+ const walkPromises = [];
524
+ for (const depDir of results) {
525
+ if (depDir)
526
+ walkPromises.push(walkDeps(depDir));
520
527
  }
528
+ await Promise.all(walkPromises);
521
529
  }
522
530
  await walkDeps(extractedRoot);
523
531
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "babelfhir-ts",
3
- "version": "1.2.2",
3
+ "version": "1.2.4",
4
4
  "description": "BabelFHIR-TS: generate TypeScript interfaces, validators, and helper classes from FHIR R4/R4B StructureDefinitions (profiles) directly inside package archives.",
5
5
  "type": "module",
6
6
  "main": "out/src/main.js",