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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { generateIntoPackageDirect, resetFetchFailureTracking, getFetchFailureWarning } from "../generator/index.js";
|
|
2
2
|
import { getCacheConfig, clearAllCaches } from "../generator/core/cacheConfig.js";
|
|
3
3
|
import { buildQualityReport, formatReportSummary, resetDiagnostics } from "../generator/core/sdDiagnostics.js";
|
|
4
4
|
import fs from 'fs';
|
|
@@ -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
|
}
|
|
@@ -94,10 +105,10 @@ function npmInstall(packagePath) {
|
|
|
94
105
|
}
|
|
95
106
|
export async function handleInstallCommand(opts) {
|
|
96
107
|
const { packageToInstall, registry, generationFlags, downloadPackage } = opts;
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
108
|
+
// Keep cache by default — dependency packages are large (hl7.fhir.r4.core ~50 MB)
|
|
109
|
+
// and re-downloading them every run is the single biggest bottleneck.
|
|
110
|
+
// Users can opt in to cache cleanup with --no-cache.
|
|
111
|
+
let generationCleanup;
|
|
101
112
|
try {
|
|
102
113
|
let downloadedPath;
|
|
103
114
|
if (fs.existsSync(packageToInstall) && (packageToInstall.endsWith('.tgz') || packageToInstall.endsWith('.zip'))) {
|
|
@@ -108,11 +119,11 @@ export async function handleInstallCommand(opts) {
|
|
|
108
119
|
downloadedPath = await downloadPackage(packageToInstall, registry);
|
|
109
120
|
}
|
|
110
121
|
console.log(`Processing package...`);
|
|
111
|
-
const baseName = path.basename(downloadedPath, path.extname(downloadedPath));
|
|
112
|
-
const outputArchive = path.join(process.cwd(), `${baseName}.with-generated.tgz`);
|
|
113
122
|
resetFetchFailureTracking();
|
|
114
123
|
resetDiagnostics();
|
|
115
|
-
|
|
124
|
+
// Generate directly into extracted package — no intermediate archive round-trip
|
|
125
|
+
const { generatedDir, cleanup } = await generateIntoPackageDirect(downloadedPath, generationFlags);
|
|
126
|
+
generationCleanup = cleanup;
|
|
116
127
|
// Clean up temp directory only if we downloaded from registry (not for local files)
|
|
117
128
|
if (!fs.existsSync(packageToInstall) || !(packageToInstall.endsWith('.tgz') || packageToInstall.endsWith('.zip'))) {
|
|
118
129
|
const tempDir = path.join(process.cwd(), '.temp-download');
|
|
@@ -120,29 +131,11 @@ export async function handleInstallCommand(opts) {
|
|
|
120
131
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
121
132
|
}
|
|
122
133
|
}
|
|
123
|
-
|
|
124
|
-
// Extract the archive to access generated folder
|
|
125
|
-
console.log(`Extracting generated code...`);
|
|
126
|
-
const { extract } = await import('tar');
|
|
127
|
-
const extractDir = path.join(process.cwd(), '.temp-install');
|
|
128
|
-
if (!fs.existsSync(extractDir)) {
|
|
129
|
-
fs.mkdirSync(extractDir, { recursive: true });
|
|
130
|
-
}
|
|
131
|
-
await extract({ cwd: extractDir, file: outputArchive });
|
|
132
|
-
// Look for generated folder - could be at root or under 'package' subfolder
|
|
133
|
-
let generatedDir = path.join(extractDir, 'generated');
|
|
134
|
-
if (!fs.existsSync(generatedDir)) {
|
|
135
|
-
generatedDir = path.join(extractDir, 'package', 'generated');
|
|
136
|
-
}
|
|
137
|
-
if (!fs.existsSync(generatedDir)) {
|
|
138
|
-
throw new Error('Generated folder not found in package archive');
|
|
139
|
-
}
|
|
140
|
-
// Verify package.json exists
|
|
134
|
+
// Verify package.json exists in generated folder
|
|
141
135
|
const pkgJsonPath = path.join(generatedDir, 'package.json');
|
|
142
136
|
if (!fs.existsSync(pkgJsonPath)) {
|
|
143
137
|
throw new Error(`package.json not found in generated folder: ${generatedDir}`);
|
|
144
138
|
}
|
|
145
|
-
// Create a tarball from the generated folder to avoid symlink issues on Windows
|
|
146
139
|
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
|
|
147
140
|
const packageName = pkgJson.name;
|
|
148
141
|
console.log(`Creating package tarball for ${packageName}...`);
|
|
@@ -163,15 +156,9 @@ export async function handleInstallCommand(opts) {
|
|
|
163
156
|
}, ['.']);
|
|
164
157
|
console.log(`Installing ${packageName}...`);
|
|
165
158
|
await npmInstall(tarballPath);
|
|
166
|
-
// Clean up
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
fs.rmSync(extractDir, { recursive: true, force: true });
|
|
170
|
-
}
|
|
171
|
-
if (fs.existsSync(outputArchive)) {
|
|
172
|
-
fs.unlinkSync(outputArchive);
|
|
173
|
-
console.log(`✓ Removed ${path.basename(outputArchive)}`);
|
|
174
|
-
}
|
|
159
|
+
// Clean up extracted package tree (but keep the tarball under ./lib)
|
|
160
|
+
generationCleanup();
|
|
161
|
+
generationCleanup = undefined;
|
|
175
162
|
console.log(`✓ Package tarball saved to ./lib: ${path.basename(tarballPath)}`);
|
|
176
163
|
showGenerationWarnings();
|
|
177
164
|
const report = buildQualityReport();
|
|
@@ -184,20 +171,12 @@ export async function handleInstallCommand(opts) {
|
|
|
184
171
|
console.log(`\n✓ Package ${packageToInstall} installed and ready to use!`);
|
|
185
172
|
}
|
|
186
173
|
catch (error) {
|
|
187
|
-
// Clean up
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
// Clean up any leftover archive
|
|
195
|
-
const baseName = packageToInstall.includes('@')
|
|
196
|
-
? packageToInstall.replace('@', '-').replace(/\//g, '-')
|
|
197
|
-
: path.basename(packageToInstall, path.extname(packageToInstall));
|
|
198
|
-
const outputArchive = path.join(process.cwd(), `${baseName}.with-generated.tgz`);
|
|
199
|
-
if (fs.existsSync(outputArchive)) {
|
|
200
|
-
fs.unlinkSync(outputArchive);
|
|
174
|
+
// Clean up extracted package on error
|
|
175
|
+
generationCleanup?.();
|
|
176
|
+
// Clean up temp download directory
|
|
177
|
+
const tempDir = path.join(process.cwd(), '.temp-download');
|
|
178
|
+
if (fs.existsSync(tempDir)) {
|
|
179
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
201
180
|
}
|
|
202
181
|
if (generationFlags.noCache) {
|
|
203
182
|
cleanupCache();
|
|
@@ -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
|
}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import fs from 'fs';
|
|
14
14
|
import path from 'path';
|
|
15
15
|
import { getFhirPackagesCacheDir, ensureCacheDir } from '../core/cacheConfig.js';
|
|
16
|
-
import {
|
|
16
|
+
import { downloadFile } from '../core/utils.js';
|
|
17
17
|
import { logger } from '../../logger.js';
|
|
18
18
|
const log = logger.withTag('core-resolver');
|
|
19
19
|
/**
|
|
@@ -58,7 +58,7 @@ export async function ensureCorePackage(corePackageSpec) {
|
|
|
58
58
|
return candidate;
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
-
// Not cached — download from registry
|
|
61
|
+
// Not cached — download from registry (streaming to avoid buffering large packages in RAM)
|
|
62
62
|
ensureCacheDir(configuredCacheDir);
|
|
63
63
|
const packageDir = path.join(configuredCacheDir, dirName);
|
|
64
64
|
let downloaded = false;
|
|
@@ -67,8 +67,7 @@ export async function ensureCorePackage(corePackageSpec) {
|
|
|
67
67
|
const url = `${registry}/${packageName}/${version}`;
|
|
68
68
|
try {
|
|
69
69
|
log.info(`Downloading core package ${corePackageSpec} from ${registry}…`);
|
|
70
|
-
|
|
71
|
-
fs.writeFileSync(tgzPath, Buffer.from(response.data));
|
|
70
|
+
await downloadFile(url, tgzPath);
|
|
72
71
|
downloaded = true;
|
|
73
72
|
break;
|
|
74
73
|
}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import fs from 'fs';
|
|
15
15
|
import path from 'path';
|
|
16
16
|
import { getFhirPackagesCacheDir, ensureCacheDir } from '../core/cacheConfig.js';
|
|
17
|
-
import {
|
|
17
|
+
import { downloadFile } from '../core/utils.js';
|
|
18
18
|
import { logger } from '../../logger.js';
|
|
19
19
|
|
|
20
20
|
const log = logger.withTag('core-resolver');
|
|
@@ -88,7 +88,7 @@ export async function ensureCorePackage(corePackageSpec: string): Promise<string
|
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
// Not cached — download from registry
|
|
91
|
+
// Not cached — download from registry (streaming to avoid buffering large packages in RAM)
|
|
92
92
|
ensureCacheDir(configuredCacheDir);
|
|
93
93
|
const packageDir = path.join(configuredCacheDir, dirName);
|
|
94
94
|
|
|
@@ -98,8 +98,7 @@ export async function ensureCorePackage(corePackageSpec: string): Promise<string
|
|
|
98
98
|
const url = `${registry}/${packageName}/${version}`;
|
|
99
99
|
try {
|
|
100
100
|
log.info(`Downloading core package ${corePackageSpec} from ${registry}…`);
|
|
101
|
-
|
|
102
|
-
fs.writeFileSync(tgzPath, Buffer.from(response.data));
|
|
101
|
+
await downloadFile(url, tgzPath);
|
|
103
102
|
downloaded = true;
|
|
104
103
|
break;
|
|
105
104
|
} catch (err) {
|