@atomic-ehr/fhir-canonical-manager 0.0.7 → 0.0.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atomic-ehr/fhir-canonical-manager",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -39,6 +39,6 @@
39
39
  "search",
40
40
  "pat"
41
41
  ],
42
- "registry": "https://fs.get-ig.org/pkgs"
42
+ "registry": "https://fs.get-ig.org/pkgs/"
43
43
  }
44
44
  }
package/src/cli/index.ts CHANGED
@@ -1,29 +1,29 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import * as fs from 'fs';
4
- import * as path from 'path';
5
- import { fileURLToPath } from 'url';
6
- import { CanonicalManager } from '../index.js';
7
- import type { Config, IndexEntry, PackageInfo } from '../index.js';
3
+ import * as fs from "fs";
4
+ import * as path from "path";
5
+ import { fileURLToPath } from "url";
6
+ import { CanonicalManager } from "../index.js";
7
+ import type { Config, IndexEntry, PackageInfo } from "../index.js";
8
8
 
9
9
  // Command handlers
10
- import { initCommand } from './init.js';
11
- import { listCommand } from './list.js';
12
- import { searchCommand } from './search.js';
13
- import { resolveCommand } from './resolve.js';
10
+ import { initCommand } from "./init.js";
11
+ import { listCommand } from "./list.js";
12
+ import { searchCommand } from "./search.js";
13
+ import { resolveCommand } from "./resolve.js";
14
14
 
15
15
  // Get version from package.json
16
16
  const __filename = fileURLToPath(import.meta.url);
17
17
  const __dirname = path.dirname(__filename);
18
- const packageJsonPath = path.join(__dirname, '..', '..', 'package.json');
18
+ const packageJsonPath = path.join(__dirname, "..", "..", "package.json");
19
19
 
20
- let VERSION = 'unknown';
20
+ let VERSION = "unknown";
21
21
  try {
22
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
22
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
23
23
  VERSION = packageJson.version;
24
24
  } catch (error) {
25
25
  // Fallback version if package.json can't be read
26
- VERSION = '0.0.3';
26
+ VERSION = "0.0.3";
27
27
  }
28
28
 
29
29
  function showHelp() {
@@ -34,7 +34,7 @@ Usage: fcm <command> [options]
34
34
 
35
35
  Commands:
36
36
  init Initialize FHIR packages in current directory
37
- list List packages or resources
37
+ list List packages or resources
38
38
  search Search for resources
39
39
  resolve Get a resource by canonical URL
40
40
 
@@ -54,13 +54,19 @@ async function main() {
54
54
  const args = process.argv.slice(2);
55
55
  const command = args[0];
56
56
 
57
- if (!command || command === '--help' || command === '-h') {
57
+ if (!command || command === "--help" || command === "-h") {
58
58
  showHelp();
59
+ if (process.env.NODE_ENV === 'test') {
60
+ return;
61
+ }
59
62
  process.exit(0);
60
63
  }
61
64
 
62
- if (command === '--version' || command === '-v') {
65
+ if (command === "--version" || command === "-v") {
63
66
  console.log(VERSION);
67
+ if (process.env.NODE_ENV === 'test') {
68
+ return;
69
+ }
64
70
  process.exit(0);
65
71
  }
66
72
 
@@ -68,56 +74,67 @@ async function main() {
68
74
  init: initCommand,
69
75
  list: listCommand,
70
76
  search: searchCommand,
71
- resolve: resolveCommand
77
+ resolve: resolveCommand,
72
78
  };
73
79
 
74
80
  if (!commands[command]) {
75
81
  console.error(`Error: Unknown command '${command}'`);
76
82
  console.error(`Run 'fcm --help' for usage information`);
83
+ if (process.env.NODE_ENV === 'test') {
84
+ throw new Error(`Unknown command '${command}'`);
85
+ }
77
86
  process.exit(1);
78
87
  }
79
88
 
80
89
  try {
81
90
  await commands[command](args.slice(1));
82
91
  } catch (error) {
83
- console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
92
+ console.error(
93
+ `Error: ${error instanceof Error ? error.message : String(error)}`,
94
+ );
95
+ if (process.env.NODE_ENV === 'test') {
96
+ throw error;
97
+ }
84
98
  process.exit(1);
85
99
  }
86
100
  }
87
101
 
88
102
  // Export utility functions for commands
89
- export function parseArgs(args: string[]): { positional: string[], options: Record<string, string | boolean> } {
103
+ export function parseArgs(args: string[]): {
104
+ positional: string[];
105
+ options: Record<string, string | boolean>;
106
+ } {
90
107
  const positional: string[] = [];
91
108
  const options: Record<string, string | boolean> = {};
92
109
 
93
110
  for (let i = 0; i < args.length; i++) {
94
111
  const arg = args[i];
95
- if (arg && arg.startsWith('--')) {
112
+ if (arg && arg.startsWith("--")) {
96
113
  const key = arg.slice(2);
97
114
  const nextArg = args[i + 1];
98
- if (nextArg && !nextArg.startsWith('--')) {
115
+ if (nextArg && !nextArg.startsWith("--")) {
99
116
  options[key] = nextArg;
100
117
  i++;
101
118
  } else {
102
119
  options[key] = true;
103
120
  }
104
- } else if (arg && arg.startsWith('-')) {
121
+ } else if (arg && arg.startsWith("-")) {
105
122
  // Handle short aliases
106
123
  switch (arg) {
107
- case '-sd':
108
- options.resourceType = 'StructureDefinition';
124
+ case "-sd":
125
+ options.resourceType = "StructureDefinition";
109
126
  break;
110
- case '-cs':
111
- options.resourceType = 'CodeSystem';
127
+ case "-cs":
128
+ options.resourceType = "CodeSystem";
112
129
  break;
113
- case '-vs':
114
- options.resourceType = 'ValueSet';
130
+ case "-vs":
131
+ options.resourceType = "ValueSet";
115
132
  break;
116
133
  default:
117
134
  // Handle other single-letter flags
118
135
  const key = arg.slice(1);
119
136
  const nextArg = args[i + 1];
120
- if (nextArg && !nextArg.startsWith('-')) {
137
+ if (nextArg && !nextArg.startsWith("-")) {
121
138
  options[key] = nextArg;
122
139
  i++;
123
140
  } else {
@@ -133,10 +150,10 @@ export function parseArgs(args: string[]): { positional: string[], options: Reco
133
150
  }
134
151
 
135
152
  export async function loadPackageJson(): Promise<any> {
136
- const packagePath = path.join(process.cwd(), 'package.json');
153
+ const packagePath = path.join(process.cwd(), "package.json");
137
154
  try {
138
155
  if (fs.existsSync(packagePath)) {
139
- const content = fs.readFileSync(packagePath, 'utf-8');
156
+ const content = fs.readFileSync(packagePath, "utf-8");
140
157
  return JSON.parse(content);
141
158
  }
142
159
  } catch (error) {
@@ -146,8 +163,8 @@ export async function loadPackageJson(): Promise<any> {
146
163
  }
147
164
 
148
165
  export async function savePackageJson(data: any): Promise<void> {
149
- const packagePath = path.join(process.cwd(), 'package.json');
150
- fs.writeFileSync(packagePath, JSON.stringify(data, null, 2) + '\n');
166
+ const packagePath = path.join(process.cwd(), "package.json");
167
+ fs.writeFileSync(packagePath, JSON.stringify(data, null, 2) + "\n");
151
168
  }
152
169
 
153
170
  export function getConfigFromPackageJson(packageJson: any): Partial<Config> {
@@ -155,15 +172,8 @@ export function getConfigFromPackageJson(packageJson: any): Partial<Config> {
155
172
  return {
156
173
  packages: fcm.packages || [],
157
174
  registry: fcm.registry,
158
- workingDir: process.cwd()
175
+ workingDir: process.cwd(),
159
176
  };
160
177
  }
161
178
 
162
- // Run CLI
163
- // Check if this file is being run directly
164
- const isMain = import.meta.url === `file://${process.argv[1]}` ||
165
- (typeof Bun !== 'undefined' && import.meta.main);
166
-
167
- if (isMain) {
168
- main();
169
- }
179
+ main();
package/src/cli/init.ts CHANGED
@@ -1,9 +1,9 @@
1
- import * as fs from 'fs';
2
- import * as path from 'path';
3
- import { exec } from 'child_process';
4
- import { promisify } from 'util';
5
- import { parseArgs, loadPackageJson, savePackageJson } from './index.js';
6
- import { CanonicalManager } from '../index.js';
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { exec } from "child_process";
4
+ import { promisify } from "util";
5
+ import { parseArgs, loadPackageJson, savePackageJson } from "./index.js";
6
+ import { CanonicalManager } from "../index.js";
7
7
 
8
8
  const execAsync = promisify(exec);
9
9
 
@@ -15,13 +15,13 @@ export async function initCommand(args: string[]): Promise<void> {
15
15
  // Load or create package.json
16
16
  let packageJson = await loadPackageJson();
17
17
  const isNewPackageJson = !packageJson;
18
-
18
+
19
19
  if (!packageJson) {
20
20
  // Create minimal package.json
21
21
  packageJson = {
22
22
  name: path.basename(process.cwd()),
23
23
  version: "1.0.0",
24
- type: "module"
24
+ type: "module",
25
25
  };
26
26
  }
27
27
 
@@ -29,17 +29,23 @@ export async function initCommand(args: string[]): Promise<void> {
29
29
  if (!packageJson.fcm) {
30
30
  packageJson.fcm = {
31
31
  packages: [],
32
- registry: registry || "https://fs.get-ig.org/pkgs"
32
+ registry: registry
33
+ ? (registry.endsWith('/') ? registry : `${registry}/`)
34
+ : "https://fs.get-ig.org/pkgs/",
33
35
  };
34
36
  }
35
37
 
36
38
  // If no packages specified, use packages from config
37
- const packagesToInstall = packages.length > 0 ? packages : packageJson.fcm.packages;
38
-
39
+ const packagesToInstall =
40
+ packages.length > 0 ? packages : packageJson.fcm.packages;
41
+
39
42
  if (packagesToInstall.length === 0) {
40
43
  console.error("Error: No packages specified");
41
44
  console.error("Usage: fcm init [packages...]");
42
45
  console.error("Example: fcm init hl7.fhir.r4.core hl7.fhir.us.core@5.0.1");
46
+ if (process.env.NODE_ENV === 'test') {
47
+ throw new Error("No packages specified");
48
+ }
43
49
  process.exit(1);
44
50
  }
45
51
 
@@ -50,9 +56,9 @@ export async function initCommand(args: string[]): Promise<void> {
50
56
  });
51
57
  packageJson.fcm.packages = Array.from(existingPackages);
52
58
 
53
- // Update registry if provided
59
+ // Update registry if provided, ensuring it ends with /
54
60
  if (registry) {
55
- packageJson.fcm.registry = registry;
61
+ packageJson.fcm.registry = registry.endsWith('/') ? registry : `${registry}/`;
56
62
  }
57
63
 
58
64
  // Initialize dependencies if not present
@@ -62,24 +68,24 @@ export async function initCommand(args: string[]): Promise<void> {
62
68
 
63
69
  // Save package.json
64
70
  await savePackageJson(packageJson);
65
-
71
+
66
72
  if (isNewPackageJson) {
67
73
  console.log("Created package.json");
68
74
  }
69
75
 
70
76
  // Install packages using npm
71
77
  console.log("Installing FHIR packages...");
72
-
78
+
73
79
  for (const pkg of packagesToInstall) {
74
80
  console.log(`Installing ${pkg}...`);
75
81
  try {
76
- const command = packageJson.fcm.registry
82
+ const command = packageJson.fcm.registry
77
83
  ? `npm install ${pkg} --registry ${packageJson.fcm.registry}`
78
84
  : `npm install ${pkg}`;
79
-
80
- await execAsync(command, {
85
+
86
+ await execAsync(command, {
81
87
  cwd: process.cwd(),
82
- maxBuffer: 10 * 1024 * 1024 // 10MB buffer
88
+ maxBuffer: 10 * 1024 * 1024, // 10MB buffer
83
89
  });
84
90
  } catch (error) {
85
91
  console.error(`Failed to install ${pkg}`);
@@ -89,21 +95,22 @@ export async function initCommand(args: string[]): Promise<void> {
89
95
 
90
96
  // Initialize CanonicalManager to build cache
91
97
  console.log("Building package index...");
98
+ console.log("Registry", packageJson?.fcm?.registry);
92
99
  const manager = CanonicalManager({
93
100
  packages: packageJson.fcm.packages,
94
101
  workingDir: process.cwd(),
95
- registry: packageJson.fcm.registry
102
+ registry: packageJson.fcm.registry,
96
103
  });
97
104
 
98
105
  await manager.init();
99
-
106
+
100
107
  // Show summary
101
108
  const installedPackages = await manager.packages();
102
109
  console.log("\nInstalled packages:");
103
- installedPackages.forEach(pkg => {
110
+ installedPackages.forEach((pkg) => {
104
111
  console.log(` ${pkg.name}@${pkg.version}`);
105
112
  });
106
113
 
107
114
  await manager.destroy();
108
115
  console.log("\nInitialization complete!");
109
- }
116
+ }
package/src/cli/list.ts CHANGED
@@ -12,6 +12,9 @@ export async function listCommand(args: string[]): Promise<void> {
12
12
  if (!packageJson?.fcm?.packages || packageJson.fcm.packages.length === 0) {
13
13
  console.error("Error: No FHIR packages configured");
14
14
  console.error("Run 'fcm init' first to initialize packages");
15
+ if (process.env.NODE_ENV === 'test') {
16
+ throw new Error("No FHIR packages configured");
17
+ }
15
18
  process.exit(1);
16
19
  }
17
20
 
@@ -41,6 +44,9 @@ export async function listCommand(args: string[]): Promise<void> {
41
44
  console.error(`Error: Package '${packageName}' not found`);
42
45
  console.error("Available packages:");
43
46
  packages.forEach(p => console.error(` - ${p.name}`));
47
+ if (process.env.NODE_ENV === 'test') {
48
+ throw new Error(`Package '${packageName}' not found`);
49
+ }
44
50
  process.exit(1);
45
51
  }
46
52
 
@@ -10,6 +10,9 @@ export async function resolveCommand(args: string[]): Promise<void> {
10
10
  console.error("Error: Canonical URL required");
11
11
  console.error("Usage: fcm resolve <canonical-url> [--fields field1,field2]");
12
12
  console.error("Example: fcm resolve http://hl7.org/fhir/StructureDefinition/Patient");
13
+ if (process.env.NODE_ENV === 'test') {
14
+ throw new Error("Canonical URL required");
15
+ }
13
16
  process.exit(1);
14
17
  }
15
18
 
@@ -18,6 +21,9 @@ export async function resolveCommand(args: string[]): Promise<void> {
18
21
  if (!packageJson?.fcm?.packages || packageJson.fcm.packages.length === 0) {
19
22
  console.error("Error: No FHIR packages configured");
20
23
  console.error("Run 'fcm init' first to initialize packages");
24
+ if (process.env.NODE_ENV === 'test') {
25
+ throw new Error("No FHIR packages configured");
26
+ }
21
27
  process.exit(1);
22
28
  }
23
29
 
@@ -47,6 +53,9 @@ export async function resolveCommand(args: string[]): Promise<void> {
47
53
  }
48
54
  } catch (error) {
49
55
  console.error(`Error: Resource not found: ${url}`);
56
+ if (process.env.NODE_ENV === 'test') {
57
+ throw error;
58
+ }
50
59
  process.exit(1);
51
60
  } finally {
52
61
  await manager.destroy();
package/src/cli/search.ts CHANGED
@@ -15,6 +15,9 @@ export async function searchCommand(args: string[]): Promise<void> {
15
15
  if (!packageJson?.fcm?.packages || packageJson.fcm.packages.length === 0) {
16
16
  console.error("Error: No FHIR packages configured");
17
17
  console.error("Run 'fcm init' first to initialize packages");
18
+ if (process.env.NODE_ENV === 'test' || process.env.BUN_TEST) {
19
+ throw new Error("No FHIR packages configured");
20
+ }
18
21
  process.exit(1);
19
22
  }
20
23
 
@@ -23,12 +26,19 @@ export async function searchCommand(args: string[]): Promise<void> {
23
26
  await manager.init();
24
27
 
25
28
  try {
26
- // Build search criteria
27
- const searchCriteria: any = {};
29
+ // Build filters for smart search
30
+ const filters: any = {};
31
+
32
+ if (resourceTypeFilter) {
33
+ filters.resourceType = resourceTypeFilter;
34
+ }
35
+
36
+ if (typeFilter) {
37
+ filters.type = typeFilter;
38
+ }
28
39
 
29
- // Handle kind filter through search criteria
30
40
  if (kindFilter) {
31
- searchCriteria.kind = kindFilter;
41
+ filters.kind = kindFilter;
32
42
  }
33
43
 
34
44
  if (packageFilter) {
@@ -36,42 +46,16 @@ export async function searchCommand(args: string[]): Promise<void> {
36
46
  const pkg = packages.find(p => p.name === packageFilter);
37
47
  if (!pkg) {
38
48
  console.error(`Error: Package '${packageFilter}' not found`);
49
+ if (process.env.NODE_ENV === 'test' || process.env.BUN_TEST) {
50
+ throw new Error(`Package '${packageFilter}' not found`);
51
+ }
39
52
  process.exit(1);
40
53
  }
41
- searchCriteria.package = pkg;
54
+ filters.package = pkg;
42
55
  }
43
56
 
44
- // Search for resources
45
- let results = await manager.searchEntries(searchCriteria);
46
-
47
- // Filter by resourceType if specified
48
- if (resourceTypeFilter) {
49
- results = results.filter(entry => entry.resourceType === resourceTypeFilter);
50
- }
51
-
52
- // Filter by type if specified (e.g., Extension, Patient, Observation)
53
- if (typeFilter) {
54
- results = results.filter(entry => entry.type === typeFilter);
55
- }
56
-
57
- // Filter by URL pattern if provided
58
- if (searchTerms.length > 0) {
59
- // Convert search terms to lowercase for case-insensitive matching
60
- const terms = searchTerms.map(t => t.toLowerCase());
61
-
62
- results = results.filter(entry => {
63
- if (!entry.url) return false;
64
- const urlLower = entry.url.toLowerCase();
65
-
66
- // Check if all search terms match as prefixes in the URL
67
- return terms.every(term => {
68
- // Split the URL into parts (by /, -, _, .)
69
- const urlParts = urlLower.split(/[\/\-_\.]+/);
70
- // Check if any part starts with the search term
71
- return urlParts.some(part => part.startsWith(term));
72
- });
73
- });
74
- }
57
+ // Use the new smartSearch from core
58
+ const results = await manager.smartSearch(searchTerms, filters);
75
59
 
76
60
  if (isJson) {
77
61
  console.log(JSON.stringify(results, null, 2));