babelfhir-ts 1.2.2 → 1.2.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.
@@ -345,6 +345,9 @@ export async function generateIntoPackage(packageArchivePath, outArchivePath, fl
345
345
  packageName = originalPkg.name || packageName;
346
346
  packageVersion = originalPkg.version || packageVersion;
347
347
  }
348
+ const generatedExports = {
349
+ '.': { types: './index.d.ts', import: './index.js' }
350
+ };
348
351
  const generatedPackageJson = {
349
352
  name: `${packageName}-generated`,
350
353
  version: packageVersion,
@@ -352,6 +355,7 @@ export async function generateIntoPackage(packageArchivePath, outArchivePath, fl
352
355
  type: 'module',
353
356
  main: 'index.js',
354
357
  types: 'index.d.ts',
358
+ exports: generatedExports,
355
359
  scripts: {},
356
360
  keywords: ['fhir', 'typescript', 'generated'],
357
361
  license: 'MIT',
@@ -367,6 +371,7 @@ export async function generateIntoPackage(packageArchivePath, outArchivePath, fl
367
371
  // When client is generated, add @babelfhir-ts/client-<version> as dependency (generated client extends base)
368
372
  if (!flags?.noClient) {
369
373
  generatedPackageJson.dependencies[`@babelfhir-ts/client-${versionSlug()}`] = '^0.2.0';
374
+ generatedExports['./fhir-client'] = { types: './fhir-client/index.d.ts', import: './fhir-client/index.js' };
370
375
  }
371
376
  // When zod schemas are generated, add @babelfhir-ts/zod-<version> + zod peer dependency
372
377
  if (flags?.schema === 'zod') {
@@ -458,6 +463,149 @@ export async function generateIntoPackage(packageArchivePath, outArchivePath, fl
458
463
  }
459
464
  }
460
465
  }
466
+ /**
467
+ * Generate TypeScript artefacts into a package but skip the intermediate archive
468
+ * round-trip. Returns the generated directory path and a cleanup callback.
469
+ * Used by the install command to avoid double tar operations.
470
+ */
471
+ export async function generateIntoPackageDirect(packageArchivePath, flags) {
472
+ initGenerationContext(flags, path.dirname(packageArchivePath));
473
+ if (!packageArchivePath.endsWith('.tgz') && !packageArchivePath.endsWith('.zip'))
474
+ throw new Error('Only .tgz or .zip supported.');
475
+ const extractResult = await extractPackage(packageArchivePath);
476
+ const extractedRoot = extractResult.path;
477
+ const shouldCleanup = !extractResult.isCached;
478
+ // Auto-detect FHIR version from package when not explicitly set
479
+ if (!flags?.fhirVersion) {
480
+ const detected = detectFhirVersion(extractedRoot);
481
+ await initVersionContext(detected ?? DEFAULT_FHIR_VERSION);
482
+ }
483
+ else {
484
+ await initVersionContext(flags.fhirVersion);
485
+ }
486
+ const fhirInterfaceNames = ctx().interfaceNames;
487
+ const { getPackageManager } = await import('./parser/packageManager.js');
488
+ const packageManager = getPackageManager();
489
+ packageManager.loadDependenciesFromExtractedPackage(extractedRoot);
490
+ logger.log(`Loaded package dependencies (${packageManager.getTotalStructureDefinitionCount()} total SDs available)`);
491
+ const structureDefinitions = readStructureDefinitionsFromDir(extractedRoot);
492
+ await ensureDependenciesDownloaded(extractedRoot);
493
+ const valueSetCodesMap = readValueSetCodesWithDependencies(extractedRoot);
494
+ let valueSets = readValueSetsFromDir(extractedRoot);
495
+ logger.log(`Package contains ${structureDefinitions.length} StructureDefinitions.`);
496
+ logger.log(`Loaded ${Object.keys(valueSetCodesMap).length} ValueSet code mappings (including dependencies)`);
497
+ const bindingUrls = collectValueSetBindingUrls(structureDefinitions);
498
+ enrichValueSetsFromCodeMap(valueSets, valueSetCodesMap, bindingUrls);
499
+ if (flags?.txServer) {
500
+ valueSets = await expandValueSetsWithTx(valueSets, valueSetCodesMap, structureDefinitions, flags.txServer);
501
+ }
502
+ const outputDir = path.join(extractedRoot, 'generated');
503
+ ensureDirectoryExists(outputDir);
504
+ const didGenerateValueSets = emitValueSetFiles(valueSets, outputDir, { deduplicateFilenames: true });
505
+ if (!didGenerateValueSets)
506
+ cleanupStaleValueSetDir(outputDir);
507
+ const { existingStructureDefinitions, profileIdToName, profileUrlToName } = buildProfileRegistries(structureDefinitions, fhirInterfaceNames);
508
+ registerLocalStructureDefinitions(structureDefinitions);
509
+ const depSDs = readStructureDefinitionsFromDependencies(extractedRoot);
510
+ registerLocalStructureDefinitions(depSDs);
511
+ for (const sd of structureDefinitions) {
512
+ await processStructureDefinition(sd, { outputDir, fhirSourceHint: '', valueSetCodesMap, valueSets, existingStructureDefinitions, profileIdToName, profileUrlToName, flags });
513
+ }
514
+ // Create package.json in generated folder
515
+ const originalPackageJsonPath = path.join(extractedRoot, 'package', 'package.json');
516
+ let packageName = path.basename(packageArchivePath, path.extname(packageArchivePath));
517
+ let packageVersion = '1.0.0';
518
+ if (fs.existsSync(originalPackageJsonPath)) {
519
+ const originalPkg = JSON.parse(fs.readFileSync(originalPackageJsonPath, 'utf-8'));
520
+ packageName = originalPkg.name || packageName;
521
+ packageVersion = originalPkg.version || packageVersion;
522
+ }
523
+ const generatedExports = {
524
+ '.': { types: './index.d.ts', import: './index.js' }
525
+ };
526
+ const generatedPackageJson = {
527
+ name: `${packageName}-generated`,
528
+ version: packageVersion,
529
+ description: `Generated TypeScript interfaces for ${packageName}`,
530
+ type: 'module',
531
+ main: 'index.js',
532
+ types: 'index.d.ts',
533
+ exports: generatedExports,
534
+ scripts: {},
535
+ keywords: ['fhir', 'typescript', 'generated'],
536
+ license: 'MIT',
537
+ peerDependencies: { 'fhirpath': '^3.0.0 || ^4.0.0' },
538
+ dependencies: { '@types/fhir': '^0.0.41' }
539
+ };
540
+ if (!flags?.noClient) {
541
+ generatedPackageJson.dependencies[`@babelfhir-ts/client-${versionSlug()}`] = '^0.2.0';
542
+ generatedExports['./fhir-client'] = { types: './fhir-client/index.d.ts', import: './fhir-client/index.js' };
543
+ }
544
+ if (flags?.schema === 'zod') {
545
+ generatedPackageJson.dependencies[`@babelfhir-ts/zod-${versionSlug()}`] = '^0.1.0';
546
+ generatedPackageJson.peerDependencies['zod'] = '^4.0.0';
547
+ }
548
+ const generatedPackageJsonPath = path.join(outputDir, 'package.json');
549
+ fs.writeFileSync(generatedPackageJsonPath, JSON.stringify(generatedPackageJson, null, 2));
550
+ logger.log(`Created package.json in generated folder: ${packageName}-generated@${packageVersion}`);
551
+ copyFhirAmbientDeclaration(outputDir);
552
+ logger.log('Generating index.ts exports...');
553
+ await generateIndexFile(outputDir);
554
+ if (!flags?.noClient) {
555
+ logger.log('Generating FHIR client...');
556
+ const { generateClient } = await import('./emitters/client/clientGenerator.js');
557
+ generateClient({ outputDir });
558
+ logger.log('FHIR client generated');
559
+ }
560
+ if (flags?.schema === 'zod') {
561
+ const { installBaseZodTypes } = await import('./emitters/zod/zodStubInstaller.js');
562
+ installBaseZodTypes(outputDir);
563
+ }
564
+ const { installFhirpathStub, removeFhirpathStub } = await import('./emitters/validator/fhirpathStubInstaller.js');
565
+ installFhirpathStub(outputDir);
566
+ const nodeTypesDir = path.join(outputDir, 'node_modules', '@types', 'node');
567
+ fs.mkdirSync(nodeTypesDir, { recursive: true });
568
+ fs.writeFileSync(path.join(nodeTypesDir, 'index.d.ts'), `declare module 'module' {\n export function createRequire(filename: string | URL): NodeRequire;\n}\n`);
569
+ fs.writeFileSync(path.join(nodeTypesDir, 'package.json'), JSON.stringify({ name: '@types/node', version: '0.0.0-stub', types: 'index.d.ts' }));
570
+ logger.log('Compiling TypeScript to JavaScript...');
571
+ await compileTypeScriptToJS(outputDir);
572
+ removeFhirpathStub(outputDir);
573
+ try {
574
+ fs.rmSync(path.join(outputDir, 'node_modules', '@types'), { recursive: true, force: true });
575
+ }
576
+ catch { /* ignore */ }
577
+ const babelfhirDir = path.join(outputDir, 'node_modules', '@babelfhir-ts');
578
+ if (fs.existsSync(babelfhirDir)) {
579
+ for (const entry of fs.readdirSync(babelfhirDir)) {
580
+ if (flags?.schema === 'zod' && entry.startsWith('zod-'))
581
+ continue;
582
+ try {
583
+ fs.rmSync(path.join(babelfhirDir, entry), { recursive: true, force: true });
584
+ }
585
+ catch { /* ignore */ }
586
+ }
587
+ try {
588
+ fs.rmdirSync(babelfhirDir);
589
+ }
590
+ catch { /* not empty — fine */ }
591
+ }
592
+ const nodeModulesDir = path.join(outputDir, 'node_modules');
593
+ try {
594
+ fs.rmdirSync(nodeModulesDir);
595
+ }
596
+ catch { /* not empty or doesn't exist — fine */ }
597
+ return {
598
+ generatedDir: outputDir,
599
+ cleanup: () => {
600
+ if (shouldCleanup) {
601
+ try {
602
+ fs.rmSync(extractedRoot, { recursive: true, force: true });
603
+ }
604
+ catch { /* ignore */ }
605
+ }
606
+ }
607
+ };
608
+ }
461
609
  function deriveOutputArchiveName(input) {
462
610
  const ext = input.endsWith('.tgz') ? '.tgz' : '.zip';
463
611
  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.3",
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",