Skip to content

Commit

Permalink
Allow path-resource paths with a wildcard (#1427)
Browse files Browse the repository at this point in the history
* Allow path-resource paths with wildcard to add deeply nested files

* Use what Node 16 knows

* Fix up tests

* Use what Node 18 knows

* Fix path separator and simplify logic

- Simplify logic when looking for one level and recurive directories
- Update comment to reflect new logic
- Use consitent / path separator in the path-resource parameter

* Allow parameters list to be empty in config
  • Loading branch information
jafeltra authored Feb 26, 2024
1 parent 9720ddc commit 148d0be
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 10 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [16, 18]
node-version: [18, 20]

steps:
- uses: actions/checkout@v1
Expand Down
25 changes: 18 additions & 7 deletions src/ig/IGExporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -942,8 +942,8 @@ export class IGExporter {
* capabilities, extensions, models, operations, profiles, resources, vocabulary, examples
* Based on: https://build.fhir.org/ig/FHIR/ig-guidance/using-templates.html#root.input
*
* NOTE: This does not include files nested in subfolders in supported paths since the
* IG Exporter does not handle those well.
* NOTE: This only includes files nested in subfolders when specified in the path-resource
* parameter, which is based on how the IG Publisher works.
*
* This function has similar operation to addResources, and both should be
* analyzed when making changes to either.
Expand All @@ -963,14 +963,23 @@ export class IGExporter {
const predefinedResourcePaths = pathEnds.map(pathEnd =>
path.join(this.inputPath, 'input', pathEnd)
);
let pathResourceDirectories: string[];
const pathResourceDirectories: string[] = [];
const pathResources = this.config.parameters
?.filter(parameter => parameter.value && parameter.code === 'path-resource')
.map(parameter => parameter.value);
if (pathResources) {
pathResourceDirectories = pathResources
.map(directoryPath => path.join(this.inputPath, directoryPath))
.filter(directoryPath => existsSync(directoryPath));
pathResources.forEach(directoryPath => {
const fullPath = path.join(this.inputPath, ...directoryPath.split('/'));
if (existsSync(fullPath)) {
pathResourceDirectories.push(fullPath);
} else if (directoryPath.endsWith('/*') && existsSync(fullPath.slice(0, -2))) {
pathResourceDirectories.push(
...readdirSync(fullPath.slice(0, -2), { withFileTypes: true, recursive: true })
.filter(file => file.isDirectory())
.map(dir => path.join(dir.path, dir.name))
);
}
});
if (pathResourceDirectories) predefinedResourcePaths.push(...pathResourceDirectories);
}
const deeplyNestedFiles: string[] = [];
Expand All @@ -987,7 +996,9 @@ export class IGExporter {
path.dirname(file) !== dirPath &&
!pathResourceDirectories?.includes(path.dirname(file))
) {
deeplyNestedFiles.push(file);
if (!deeplyNestedFiles.includes(file)) {
deeplyNestedFiles.push(file);
}
continue;
}
const resourceJSON: InstanceDefinition = this.fhirDefs.getPredefinedResource(file);
Expand Down
2 changes: 1 addition & 1 deletion src/import/importConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -748,7 +748,7 @@ function parseParameters(
}
if (yamlConfig.parameters) {
for (const [code, values] of Object.entries(yamlConfig.parameters)) {
normalizeToArray(values).forEach(value => parameters.push({ code, value: `${value}` }));
normalizeToArray(values)?.forEach(value => parameters.push({ code, value: `${value}` }));
}
} else if (parameters.length === 0) {
return; // return undefined rather than an empty []
Expand Down
61 changes: 60 additions & 1 deletion test/ig/IGExporter.IG.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2921,7 +2921,7 @@ describe('IGExporter', () => {
config.parameters = [];
config.parameters.push({
code: 'path-resource',
value: path.join('input', 'resources', 'path-resource-nest')
value: 'input/resources/path-resource-nest'
});
const pkg = new Package(config);
exporter = new IGExporter(pkg, defs, path.resolve(fixtures));
Expand Down Expand Up @@ -2973,6 +2973,12 @@ describe('IGExporter', () => {
);
expect(warning).toInclude(path.join('nested1', 'StructureDefinition-MyTitlePatient.json'));
expect(warning).toInclude(path.join('nested2', 'ValueSet-MyVS.json'));
expect(warning).toInclude(
path.join('path-resource-double-nest', 'john', 'Patient-John.json')
);
expect(warning).toInclude(
path.join('path-resource-double-nest', 'jack', 'examples', 'Patient-Jack.json')
);
expect(warning).not.toInclude('Patient-BarPatient.json');
expect(warning).not.toInclude('StructureDefinition-MyPatient.json');
});
Expand All @@ -2989,10 +2995,63 @@ describe('IGExporter', () => {
);
expect(warning).toInclude(path.join('nested1', 'StructureDefinition-MyTitlePatient.json'));
expect(warning).toInclude(path.join('nested2', 'ValueSet-MyVS.json'));
// path-resource-double-nest is not included in config
expect(warning).toInclude(
path.join('path-resource-double-nest', 'john', 'Patient-John.json')
);
expect(warning).toInclude(
path.join('path-resource-double-nest', 'jack', 'examples', 'Patient-Jack.json')
);
// path-resource-nest is included in config
expect(warning).not.toInclude(
path.join('path-resource-nest', 'StructureDefinition-MyCorrectlyNestedPatient.json')
);
});

it('should not warn on deeply nested resources that are included in the path-resource parameter with a directory and wildcard', () => {
const config = cloneDeep(minimalConfig);
config.parameters = [];
config.parameters.push({
code: 'path-resource',
value: 'input/resources/path-resource-double-nest/*'
});
const pkg = new Package(config);
exporter = new IGExporter(pkg, defs, path.resolve(fixtures));
exporter.export(tempOut);
expect(loggerSpy.getAllMessages('warn')).toHaveLength(1);
const warning = loggerSpy.getFirstMessage('warn');
expect(warning).not.toInclude(
path.join('path-resource-double-nest', 'john', 'Patient-John.json')
);
expect(warning).not.toInclude(
path.join('path-resource-double-nest', 'jack', 'examples', 'Patient-Jack.json')
);
});

it('should warn on deeply nested resources that are included in the path-resource parameter with a directory but NO wildcard', () => {
const config = cloneDeep(minimalConfig);
config.parameters = [];
config.parameters.push({
code: 'path-resource',
// NOTE: file path does not include the "*" portion (it just lists a directory), which is not sufficient
value: 'input/resources/path-resource-double-nest'
});
const pkg = new Package(config);
exporter = new IGExporter(pkg, defs, path.resolve(fixtures));
exporter.export(tempOut);
expect(loggerSpy.getAllMessages('warn')).toHaveLength(1);
const warning = loggerSpy.getFirstMessage('warn');
const warningLines = warning.split('\n');
const johnLine = warningLines.filter(w =>
w.includes(path.join('path-resource-double-nest', 'john', 'Patient-John.json'))
);
const jackLine = warningLines.filter(w =>
w.includes(path.join('path-resource-double-nest', 'john', 'Patient-John.json'))
);
// Check that both nested files are logged in the warning, but check that they're only there once
expect(johnLine).toHaveLength(1);
expect(jackLine).toHaveLength(1);
});
});

describe('#customized-ig-with-logical-model-example', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"resourceType": "Patient",
"id": "Jack",
"name": [
{
"family": "Anyperson",
"given": [
"Jack",
"C."
]
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"resourceType": "Patient",
"id": "John",
"name": [
{
"family": "Anyperson",
"given": [
"John",
"B."
]
}
]
}

0 comments on commit 148d0be

Please sign in to comment.