babelfhir-ts 1.2.3 → 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.
|
@@ -22,18 +22,29 @@ function cleanupCache() {
|
|
|
22
22
|
console.log('Cache cleaned.');
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
-
/** Detect the package manager
|
|
26
|
-
function detectPackageManager() {
|
|
27
|
-
const cwd = process.cwd();
|
|
25
|
+
/** Detect the package manager by walking up from startDir (default: cwd) to find a lock file */
|
|
26
|
+
export function detectPackageManager(startDir) {
|
|
28
27
|
const isWin = process.platform === 'win32';
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
28
|
+
// Walk up the directory tree — in monorepos the lock file lives at the root,
|
|
29
|
+
// not in the package subdirectory where the command is run.
|
|
30
|
+
let dir = startDir ?? process.cwd();
|
|
31
|
+
while (true) {
|
|
32
|
+
if (fs.existsSync(path.join(dir, 'bun.lock')) || fs.existsSync(path.join(dir, 'bun.lockb'))) {
|
|
33
|
+
return { cmd: isWin ? 'bun.exe' : 'bun', args: ['add'] };
|
|
34
|
+
}
|
|
35
|
+
if (fs.existsSync(path.join(dir, 'pnpm-lock.yaml'))) {
|
|
36
|
+
return { cmd: isWin ? 'pnpm.cmd' : 'pnpm', args: ['add'] };
|
|
37
|
+
}
|
|
38
|
+
if (fs.existsSync(path.join(dir, 'yarn.lock'))) {
|
|
39
|
+
return { cmd: isWin ? 'yarn.cmd' : 'yarn', args: ['add'] };
|
|
40
|
+
}
|
|
41
|
+
if (fs.existsSync(path.join(dir, 'package-lock.json'))) {
|
|
42
|
+
return { cmd: isWin ? 'npm.cmd' : 'npm', args: ['install'] };
|
|
43
|
+
}
|
|
44
|
+
const parent = path.dirname(dir);
|
|
45
|
+
if (parent === dir)
|
|
46
|
+
break; // reached filesystem root
|
|
47
|
+
dir = parent;
|
|
37
48
|
}
|
|
38
49
|
return { cmd: isWin ? 'npm.cmd' : 'npm', args: ['install'] };
|
|
39
50
|
}
|
|
@@ -44,6 +44,25 @@ export function capitalize(str) {
|
|
|
44
44
|
return str;
|
|
45
45
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Extract base FHIR types from all interface declarations in a file.
|
|
49
|
+
* Handles:
|
|
50
|
+
* - `export interface Foo extends Bar { ... }`
|
|
51
|
+
* - `export interface Foo extends Omit<Bar, 'a' | 'b'> { ... }`
|
|
52
|
+
* Returns ALL candidates — files may have nested types (e.g.,
|
|
53
|
+
* AllergyIntoleranceReaction) before the main resource interface.
|
|
54
|
+
*/
|
|
55
|
+
function extractBaseTypes(content) {
|
|
56
|
+
const results = [];
|
|
57
|
+
const allMatches = [...content.matchAll(/export\s+interface\s+\w+\s+extends\s+(?:Omit|Pick|Partial)<(\w+)|export\s+interface\s+\w+\s+extends\s+(\w+)/g)];
|
|
58
|
+
for (const m of allMatches) {
|
|
59
|
+
const candidate = m[1] ?? m[2];
|
|
60
|
+
if (candidate && candidate !== 'Omit' && candidate !== 'Pick' && candidate !== 'Partial') {
|
|
61
|
+
results.push(candidate);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return results;
|
|
65
|
+
}
|
|
47
66
|
export function getResourceTypes(options) {
|
|
48
67
|
const outputDir = options.outputDir;
|
|
49
68
|
if (!fs.existsSync(outputDir)) {
|
|
@@ -51,40 +70,44 @@ export function getResourceTypes(options) {
|
|
|
51
70
|
}
|
|
52
71
|
const files = fs.readdirSync(outputDir);
|
|
53
72
|
const resourceTypes = new Map(); // Maps profile name -> base resource type
|
|
54
|
-
// Check if index.ts exists
|
|
55
|
-
const indexPath = path.join(outputDir, "index.ts")
|
|
73
|
+
// Check if index.ts (or index.d.ts after compilation) exists
|
|
74
|
+
const indexPath = fs.existsSync(path.join(outputDir, "index.ts"))
|
|
75
|
+
? path.join(outputDir, "index.ts")
|
|
76
|
+
: fs.existsSync(path.join(outputDir, "index.d.ts"))
|
|
77
|
+
? path.join(outputDir, "index.d.ts")
|
|
78
|
+
: null;
|
|
56
79
|
let exportedTypes = null;
|
|
57
|
-
if (
|
|
80
|
+
if (indexPath) {
|
|
58
81
|
const indexContent = fs.readFileSync(indexPath, "utf-8");
|
|
59
82
|
exportedTypes = new Set();
|
|
60
|
-
// Extract
|
|
61
|
-
const exportRegex = /export
|
|
83
|
+
// Extract module names from export statements (both wildcard and named)
|
|
84
|
+
const exportRegex = /export\s+(?:\*|{[^}]+})\s+from\s+['"]\.\/([\w-]+)\.js['"]/g;
|
|
62
85
|
let match;
|
|
63
86
|
while ((match = exportRegex.exec(indexContent)) !== null) {
|
|
64
87
|
exportedTypes.add(match[1]);
|
|
65
88
|
}
|
|
66
89
|
}
|
|
67
90
|
for (const file of files) {
|
|
68
|
-
if (file.endsWith("Class.ts")) {
|
|
69
|
-
const profileName = file.replace(
|
|
70
|
-
if (!exportedTypes || exportedTypes.has(profileName)) {
|
|
91
|
+
if (file.endsWith("Class.ts") || file.endsWith("Class.d.ts")) {
|
|
92
|
+
const profileName = file.replace(/Class\.(ts|d\.ts)$/, "");
|
|
93
|
+
if (!exportedTypes || exportedTypes.has(profileName) || exportedTypes.has(`${profileName}Class`)) {
|
|
71
94
|
const interfaceFile = path.join(outputDir, `${profileName}.ts`);
|
|
72
|
-
|
|
73
|
-
|
|
95
|
+
const interfaceDtsFile = path.join(outputDir, `${profileName}.d.ts`);
|
|
96
|
+
const actualFile = fs.existsSync(interfaceFile) ? interfaceFile : fs.existsSync(interfaceDtsFile) ? interfaceDtsFile : null;
|
|
97
|
+
if (actualFile) {
|
|
98
|
+
const content = fs.readFileSync(actualFile, "utf-8");
|
|
74
99
|
// Check if it has resourceType property (base resource)
|
|
75
100
|
const resourceTypeMatch = content.match(/resourceType:\s*["'](\w+)["']/);
|
|
76
101
|
if (resourceTypeMatch) {
|
|
77
102
|
resourceTypes.set(profileName, resourceTypeMatch[1]);
|
|
78
103
|
continue;
|
|
79
104
|
}
|
|
80
|
-
// Check if it extends a FHIR resource
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
resourceTypes.set(profileName, baseType);
|
|
87
|
-
}
|
|
105
|
+
// Check if it extends a FHIR resource (handles both `extends Foo` and `extends Omit<Foo, ...>`)
|
|
106
|
+
// Try all interface declarations — files may contain nested types before the main resource.
|
|
107
|
+
const baseTypes = extractBaseTypes(content);
|
|
108
|
+
const knownBase = baseTypes.find(bt => ctx().resourceNames.includes(bt));
|
|
109
|
+
if (knownBase) {
|
|
110
|
+
resourceTypes.set(profileName, knownBase);
|
|
88
111
|
}
|
|
89
112
|
}
|
|
90
113
|
}
|
|
@@ -94,19 +117,22 @@ export function getResourceTypes(options) {
|
|
|
94
117
|
}
|
|
95
118
|
export function getBaseResourceType(profileName, outputDir) {
|
|
96
119
|
const interfaceFile = path.join(outputDir, `${profileName}.ts`);
|
|
97
|
-
|
|
120
|
+
const interfaceDtsFile = path.join(outputDir, `${profileName}.d.ts`);
|
|
121
|
+
const actualFile = fs.existsSync(interfaceFile) ? interfaceFile : fs.existsSync(interfaceDtsFile) ? interfaceDtsFile : null;
|
|
122
|
+
if (!actualFile) {
|
|
98
123
|
return profileName; // fallback
|
|
99
124
|
}
|
|
100
|
-
const content = fs.readFileSync(
|
|
125
|
+
const content = fs.readFileSync(actualFile, "utf-8");
|
|
101
126
|
// Check for explicit resourceType property
|
|
102
127
|
const resourceTypeMatch = content.match(/resourceType:\s*["'](\w+)["']/);
|
|
103
128
|
if (resourceTypeMatch) {
|
|
104
129
|
return resourceTypeMatch[1];
|
|
105
130
|
}
|
|
106
|
-
// Check for extends clause
|
|
107
|
-
const
|
|
108
|
-
if (
|
|
109
|
-
|
|
131
|
+
// Check for extends clause (handles both `extends Foo` and `extends Omit<Foo, ...>`)
|
|
132
|
+
const baseTypes = extractBaseTypes(content);
|
|
133
|
+
if (baseTypes.length > 0) {
|
|
134
|
+
// Prefer a known resource type, fall back to the first candidate
|
|
135
|
+
return baseTypes.find(bt => ctx().resourceNames.includes(bt)) ?? baseTypes[0];
|
|
110
136
|
}
|
|
111
137
|
return profileName; // fallback
|
|
112
138
|
}
|
|
@@ -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 });
|
|
@@ -371,16 +376,12 @@ export async function generateIntoPackage(packageArchivePath, outArchivePath, fl
|
|
|
371
376
|
// When client is generated, add @babelfhir-ts/client-<version> as dependency (generated client extends base)
|
|
372
377
|
if (!flags?.noClient) {
|
|
373
378
|
generatedPackageJson.dependencies[`@babelfhir-ts/client-${versionSlug()}`] = '^0.2.0';
|
|
374
|
-
generatedExports['./fhir-client'] = { types: './fhir-client/index.d.ts', import: './fhir-client/index.js' };
|
|
375
379
|
}
|
|
376
380
|
// When zod schemas are generated, add @babelfhir-ts/zod-<version> + zod peer dependency
|
|
377
381
|
if (flags?.schema === 'zod') {
|
|
378
382
|
generatedPackageJson.dependencies[`@babelfhir-ts/zod-${versionSlug()}`] = '^0.1.0';
|
|
379
383
|
generatedPackageJson.peerDependencies['zod'] = '^4.0.0';
|
|
380
384
|
}
|
|
381
|
-
const generatedPackageJsonPath = path.join(outputDir, 'package.json');
|
|
382
|
-
fs.writeFileSync(generatedPackageJsonPath, JSON.stringify(generatedPackageJson, null, 2));
|
|
383
|
-
logger.log(`Created package.json in generated folder: ${packageName}-generated@${packageVersion}`);
|
|
384
385
|
copyFhirAmbientDeclaration(outputDir);
|
|
385
386
|
// Generate index.ts that exports all interfaces
|
|
386
387
|
logger.log('Generating index.ts exports...');
|
|
@@ -394,6 +395,13 @@ export async function generateIntoPackage(packageArchivePath, outArchivePath, fl
|
|
|
394
395
|
});
|
|
395
396
|
logger.log('FHIR client generated');
|
|
396
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}`);
|
|
397
405
|
// Install base zod type stubs when --schema zod is enabled (needed for tsc compilation)
|
|
398
406
|
if (flags?.schema === 'zod') {
|
|
399
407
|
const { installBaseZodTypes } = await import('./emitters/zod/zodStubInstaller.js');
|
|
@@ -500,6 +508,11 @@ export async function generateIntoPackageDirect(packageArchivePath, flags) {
|
|
|
500
508
|
valueSets = await expandValueSetsWithTx(valueSets, valueSetCodesMap, structureDefinitions, flags.txServer);
|
|
501
509
|
}
|
|
502
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
|
+
}
|
|
503
516
|
ensureDirectoryExists(outputDir);
|
|
504
517
|
const didGenerateValueSets = emitValueSetFiles(valueSets, outputDir, { deduplicateFilenames: true });
|
|
505
518
|
if (!didGenerateValueSets)
|
|
@@ -539,15 +552,11 @@ export async function generateIntoPackageDirect(packageArchivePath, flags) {
|
|
|
539
552
|
};
|
|
540
553
|
if (!flags?.noClient) {
|
|
541
554
|
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
555
|
}
|
|
544
556
|
if (flags?.schema === 'zod') {
|
|
545
557
|
generatedPackageJson.dependencies[`@babelfhir-ts/zod-${versionSlug()}`] = '^0.1.0';
|
|
546
558
|
generatedPackageJson.peerDependencies['zod'] = '^4.0.0';
|
|
547
559
|
}
|
|
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
560
|
copyFhirAmbientDeclaration(outputDir);
|
|
552
561
|
logger.log('Generating index.ts exports...');
|
|
553
562
|
await generateIndexFile(outputDir);
|
|
@@ -557,6 +566,13 @@ export async function generateIntoPackageDirect(packageArchivePath, flags) {
|
|
|
557
566
|
generateClient({ outputDir });
|
|
558
567
|
logger.log('FHIR client generated');
|
|
559
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}`);
|
|
560
576
|
if (flags?.schema === 'zod') {
|
|
561
577
|
const { installBaseZodTypes } = await import('./emitters/zod/zodStubInstaller.js');
|
|
562
578
|
installBaseZodTypes(outputDir);
|
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",
|