Repository: FHIR/sushi
Pull request #1546 (2024-12-21 19:04:35)

Update FPL and avoid using readdirSync w/ recursive flag
**Description:** Update FHIR Package Loader to the patch release that does not use features requiring Node JS 18.20. Also update recursive file traversal to avoid using `readdirSync` with `recursive` flag (added in Node 18.17) and `Dirent.parentPath` (added in Node 18.20).

Using readdirSync with the `recursive` flag and `Dirent.parentPath` resulted in errors when SUSHI was run on earlier versions of Node 18.x. See: https://chat.fhir.org/#narrow/channel/215610-shorthand/topic/Issue.20w.2F.20newest.20SUSHI/near/490266928

**Testing Instructions:** Run `npm test` on the master branch using a Node 18 version < Node 18.17 (e.g, Node 18.16.0). You will get 1800+ errors. Now run this PR branch using the same version of Node (e.g., Node 18.16.0). The tests should all pass.

**Related Issue:** N/A


cmoesel: Breaking the glass (no peer code reviews) to get this urgent bug fix out ASAP (on the weekend).

Patch:
diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json
index 0075835..fe211c3 100644
--- a/npm-shrinkwrap.json
+++ b/npm-shrinkwrap.json
@@ -15,7 +15,7 @@
         "chalk": "^4.1.2",
         "commander": "^12.1.0",
         "fhir": "^4.12.0",
-        "fhir-package-loader": "^2.0.0",
+        "fhir-package-loader": "^2.0.1",
         "fs-extra": "^11.2.0",
         "html-minifier-terser": "5.1.1",
         "https-proxy-agent": "^7.0.5",
@@ -3602,9 +3602,9 @@
       }
     },
     "node_modules/fhir-package-loader": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/fhir-package-loader/-/fhir-package-loader-2.0.0.tgz",
-      "integrity": "sha512-26TQYVU6frmlluiDY0Nm/hjgGertOdN092BRHja+yCbAx1kvk4sBzAW9SmmaD33SeHzsDxEPWvTRBEDq/5nSFw==",
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/fhir-package-loader/-/fhir-package-loader-2.0.1.tgz",
+      "integrity": "sha512-6TuYbOpaUySPa3dgpTrtWS51/KKWtzxHgvcZ5PAuRJS4Y0rhbWM8rZ5zhi87uqPqKIP9Sv5AvpxtyRpsTp0WwQ==",
       "dependencies": {
         "axios": "^1.7.8",
         "chalk": "^4.1.2",
diff --git a/package.json b/package.json
index ae464de..b105279 100644
--- a/package.json
+++ b/package.json
@@ -93,7 +93,7 @@
     "chalk": "^4.1.2",
     "commander": "^12.1.0",
     "fhir": "^4.12.0",
-    "fhir-package-loader": "^2.0.0",
+    "fhir-package-loader": "^2.0.1",
     "fs-extra": "^11.2.0",
     "html-minifier-terser": "5.1.1",
     "https-proxy-agent": "^7.0.5",
diff --git a/src/app.ts b/src/app.ts
index 913d1d9..b7c4a12 100644
--- a/src/app.ts
+++ b/src/app.ts
@@ -285,7 +285,12 @@ async function runBuild(input: string, program: OptionValues, helpText: string)
   await loadExternalDependencies(defs, config);
 
   // Load custom resources from typical input/* paths and custom configured paths
-  await loadPredefinedResources(defs, path.join(input, '..'), originalInput, config.parameters);
+  await loadPredefinedResources(
+    defs,
+    path.resolve(input, '..'),
+    path.resolve(originalInput),
+    config.parameters
+  );
 
   // Optimize the database after loading to ensure the most efficient queries
   defs.optimize();
diff --git a/src/ig/predefinedResources.ts b/src/ig/predefinedResources.ts
index f40f9fc..8d971cf 100644
--- a/src/ig/predefinedResources.ts
+++ b/src/ig/predefinedResources.ts
@@ -47,10 +47,20 @@ export function getPredefinedResourcePaths(
         predefinedResourcePaths.add(fullPath);
         // path-resource paths ending with /* should recursively include subfolders
         if (pathResource.endsWith('/*')) {
-          fs.readdirSync(fullPath, { withFileTypes: true, recursive: true })
-            .filter(file => file.isDirectory())
-            .map(dir => path.join(dir.parentPath, dir.name))
-            .forEach(p => predefinedResourcePaths.add(p));
+          // Note: Do not use readdirSync w/ {recursive: true} since it was only added in Node 18.17.
+          const addRecursiveChildFolders = (folderPath: string) => {
+            const stat = fs.statSync(folderPath);
+            if (stat.isDirectory()) {
+              fs.readdirSync(folderPath, { withFileTypes: true }).forEach(entry => {
+                if (entry.isDirectory()) {
+                  const childFolder = path.resolve(folderPath, entry.name);
+                  predefinedResourcePaths.add(childFolder);
+                  addRecursiveChildFolders(childFolder);
+                }
+              });
+            }
+          };
+          addRecursiveChildFolders(fullPath);
         }
       }
     });


Test patch:
diff --git a/test/ig/predefinedResources.test.ts b/test/ig/predefinedResources.test.ts
index 5b3477f..fd18222 100644
--- a/test/ig/predefinedResources.test.ts
+++ b/test/ig/predefinedResources.test.ts
@@ -103,10 +103,10 @@ describe('#getPredefinedResourcePaths', () => {
       path.join(inputDir, 'resources', 'nested1'),
       path.join(inputDir, 'resources', 'nested2'),
       path.join(inputDir, 'resources', 'path-resource-double-nest'),
-      path.join(inputDir, 'resources', 'path-resource-nest'),
       path.join(inputDir, 'resources', 'path-resource-double-nest', 'jack'),
+      path.join(inputDir, 'resources', 'path-resource-double-nest', 'jack', 'examples'),
       path.join(inputDir, 'resources', 'path-resource-double-nest', 'john'),
-      path.join(inputDir, 'resources', 'path-resource-double-nest', 'jack', 'examples')
+      path.join(inputDir, 'resources', 'path-resource-nest')
     ]);
   });