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.
- package/out/src/cli/installCommand.js +40 -61
- package/out/src/generator/core/utils.js +50 -24
- package/out/src/generator/fhir/corePackageResolver.js +3 -4
- package/out/src/generator/fhir/corePackageResolver.ts +3 -4
- package/out/src/generator/fhir/fallback/r5.json +18377 -0
- package/out/src/generator/index.js +167 -3
- package/out/src/generator/parser/packageParser.js +54 -46
- package/package.json +1 -1
|
@@ -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 {
|
|
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
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
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.
|
|
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",
|