Repository: FHIR/sushi (ts)

Issue:
SUSHI appending comments to files in input
Reported by Eric Haas:

Eric Haas3:13 PM
Sushi is prepending text into markdown files in my input/includes directory each time I run it.
```
<!-- example-list-generator.md {% comment %}
*****************************************************************************************
*                            WARNING: DO NOT EDIT THIS FILE                             *
*                                                                                       *
* This file is generated by SUSHI. Any edits you make to this file will be overwritten. *
*                                                                                       *
* To change the contents of this file, edit the original source file at:                *
* Davinci-CDEX/input/includes/example-list-generator.md                                 *
*****************************************************************************************
{% endcomment %} -->
<!-- example-list-generator.md {% comment %}
*****************************************************************************************
*                            WARNING: DO NOT EDIT THIS FILE                             *
*                                                                                       *
* This file is generated by SUSHI. Any edits you make to this file will be overwritten. *
*                                                                                       *
* To change the contents of this file, edit the original source file at:                *
* Davinci-CDEX/input/includes/example-list-generator.md                                 *
*****************************************************************************************
{% endcomment %} -->
<!-- example-list-generator.md {% comment %}
*****************************************************************************************
*                            WARNING: DO NOT EDIT THIS FILE                             *
*                                                                                       *
* This file is generated by SUSHI. Any edits you make to this file will be overwritten. *
*                                                                                       *
* To change the contents of this file, edit the original source file at:                *
* Davinci-CDEX/input/includes/example-list-generator.md                                 *
*****************************************************************************************
{% endcomment %} -->
```

See: https://chat.fhir.org/#narrow/stream/215610-shorthand/topic/Announcing.20SUSHI.201.2E0.2E0.20Beta.201/near/212471917

Pull request description:
Do not copy IG files in new configuration
This PR fixes #626, along with an additional couple cases.

There were a few cases were we would incorrectly copy and write IG publisher related files in the new tank configuration. One was reported in #626 where the `input/includes` content was being copied with the warning text in the new configuration. We still want to include this warning for anyone on the legacy project configuration, but we should not be copying over anything in the new configuration. The other case that Mark discovered was files in `input/pagecontent` were being copied over with the warning text. Similarly, we still want this warning in the legacy project configuration, but not in the new configuration.

I added the same check we use in other functions to decide if we should be copying the files. I did another scan through of the functions and the only other place that we were copying files was in the `input/images` folder. Technically I don't think keeping this copying in this folder made a difference since we didn't add any warnings, but I still decided to add the check there as well.

Patch:
diff --git a/src/ig/IGExporter.ts b/src/ig/IGExporter.ts
index 3de2004..fb9e72d 100644
--- a/src/ig/IGExporter.ts
+++ b/src/ig/IGExporter.ts
@@ -467,7 +467,7 @@ export class IGExporter {
    * @see {@link https://build.fhir.org/ig/FHIR/ig-guidance/using-templates.html#root.input}
    * @param {string} igPath - the path where the IG is exported to
    */
-  private addConfiguredPageContent(igPath: string): void {
+  addConfiguredPageContent(igPath: string): void {
     // only configured pages are added to the implementation guide,
     for (const page of this.config.pages) {
       this.addConfiguredPage(page, this.ig.definition.page.page);
@@ -480,10 +480,12 @@ export class IGExporter {
       if (existsSync(inputPageContentPath)) {
         const outputPageContentPath = path.join(igPath, 'input', contentFolder);
         for (const contentFile of readdirSync(inputPageContentPath)) {
-          this.copyWithWarningText(
-            path.join(inputPageContentPath, contentFile),
-            path.join(outputPageContentPath, contentFile)
-          );
+          if (this.shouldCopyFiles) {
+            this.copyWithWarningText(
+              path.join(inputPageContentPath, contentFile),
+              path.join(outputPageContentPath, contentFile)
+            );
+          }
           const fileType = contentFile.slice(contentFile.lastIndexOf('.') + 1);
           if (!(fileType === 'md' || fileType === 'xml') && !junk.is(path.basename(contentFile))) {
             invalidFileTypeIncluded = true;
@@ -644,7 +646,7 @@ export class IGExporter {
   private addImages(igPath: string): void {
     // If the user provided additional image files, include them
     const inputImagesPath = path.join(this.igDataPath, 'input', 'images');
-    if (existsSync(inputImagesPath)) {
+    if (existsSync(inputImagesPath) && this.shouldCopyFiles) {
       const outputPath = path.join(igPath, 'input', 'images');
       const files = readdirSync(inputImagesPath);
       files.forEach(file => {
@@ -659,9 +661,9 @@ export class IGExporter {
    *
    * @param {string} igPath - the path where the IG is exported to
    */
-  private addIncludeContents(igPath: string): void {
+  addIncludeContents(igPath: string): void {
     const includesPath = path.join(this.igDataPath, 'input', 'includes');
-    if (existsSync(includesPath)) {
+    if (existsSync(includesPath) && this.shouldCopyFiles) {
       this.copyWithWarningText(includesPath, path.join(igPath, 'input', 'includes'), src => {
         // Filter out menu.xml because handled separately
         return !path.parse(src).base.startsWith('menu.xml');


Test patch:
diff --git a/test/ig/IGExporter.addConfiguredPageContent.test.ts b/test/ig/IGExporter.addConfiguredPageContent.test.ts
new file mode 100644
index 0000000..df315cc
--- /dev/null
+++ b/test/ig/IGExporter.addConfiguredPageContent.test.ts
@@ -0,0 +1,79 @@
+import fs from 'fs-extra';
+import path from 'path';
+import temp from 'temp';
+import { cloneDeep } from 'lodash';
+import { IGExporter } from '../../src/ig';
+import { Package } from '../../src/export';
+import { loggerSpy } from '../testhelpers/loggerSpy';
+import { FHIRDefinitions } from '../../src/fhirdefs';
+import { Configuration } from '../../src/fshtypes';
+import { minimalConfig } from '../utils/minimalConfig';
+
+describe('IGExporter', () => {
+  // Track temp files/folders for cleanup
+  temp.track();
+
+  describe('#configured-pagecontent', () => {
+    let tempOut: string;
+    let config: Configuration;
+    const outputFileSyncSpy = jest.spyOn(fs, 'outputFileSync');
+    const defs = new FHIRDefinitions();
+
+    beforeEach(() => {
+      tempOut = temp.mkdirSync('sushi-test');
+      config = cloneDeep(minimalConfig);
+      config.pages = [
+        { nameUrl: 'index.md', title: 'Example Home' },
+        {
+          nameUrl: 'examples.xml',
+          title: 'Examples Overview',
+          page: [{ nameUrl: 'simpleExamples.xml' }]
+        }
+      ];
+      loggerSpy.reset();
+    });
+
+    afterEach(() => {
+      temp.cleanupSync();
+    });
+
+    it('should not copy configured page content files', () => {
+      const pkg = new Package(config);
+      const igDataPath = path.resolve(
+        __dirname,
+        'fixtures',
+        'customized-ig-with-pagecontent',
+        'ig-data'
+      );
+      const exporter = new IGExporter(pkg, defs, igDataPath, true); // Current tank configuration
+      exporter.initIG();
+      exporter.addConfiguredPageContent(tempOut);
+      const pageContentPath = path.join(tempOut, 'input', 'pagecontent');
+      expect(fs.existsSync(pageContentPath)).toBeFalsy();
+      expect(outputFileSyncSpy).not.toHaveBeenCalled();
+    });
+
+    it('should copy over configured page content in legacy IG publisher configuration', () => {
+      const pkg = new Package(config);
+      const igDataPath = path.resolve(
+        __dirname,
+        'fixtures',
+        'customized-ig-with-pagecontent',
+        'ig-data'
+      );
+      const exporter = new IGExporter(pkg, defs, igDataPath); // Legacy tank configuration
+      exporter.initIG();
+      exporter.addConfiguredPageContent(tempOut);
+      const pageContentPath = path.join(tempOut, 'input', 'pagecontent');
+      expect(fs.existsSync(pageContentPath)).toBeTruthy();
+      expect(outputFileSyncSpy).toHaveBeenCalledTimes(3);
+      const files = fs.readdirSync(pageContentPath, 'utf8');
+      const expectedPages = ['examples.xml', 'index.md', 'simpleExamples.xml'];
+      expect(files).toEqual(expectedPages); // Copies over all pagecontent files
+      for (const file of expectedPages) {
+        const content = fs.readFileSync(path.join(pageContentPath, file), 'utf8');
+        expect(content).toMatch(/^\*\s+WARNING: DO NOT EDIT THIS FILE\s+\*$/m);
+      }
+    });
+  });
+});
diff --git a/test/ig/IGExporter.addIncludeContents.test.ts b/test/ig/IGExporter.addIncludeContents.test.ts
new file mode 100644
index 0000000..d109646
--- /dev/null
+++ b/test/ig/IGExporter.addIncludeContents.test.ts
@@ -0,0 +1,56 @@
+import fs from 'fs-extra';
+import path from 'path';
+import temp from 'temp';
+import { IGExporter } from '../../src/ig';
+import { Package } from '../../src/export';
+import { loggerSpy } from '../testhelpers/loggerSpy';
+import { minimalConfig } from '../utils/minimalConfig';
+import { Configuration } from '../../src/fshtypes';
+import { cloneDeep } from 'lodash';
+
+describe('IGExporter', () => {
+  // Track temp files/folders for cleanup
+  temp.track();
+
+  describe('#include-contents', () => {
+    let tempOut: string;
+    let config: Configuration;
+    const outputFileSyncSpy = jest.spyOn(fs, 'outputFileSync');
+
+    beforeEach(() => {
+      tempOut = temp.mkdirSync('sushi-test');
+      config = cloneDeep(minimalConfig);
+      loggerSpy.reset();
+    });
+
+    afterEach(() => {
+      temp.cleanupSync();
+    });
+
+    it('should not copy input/includes files', () => {
+      const pkg = new Package(config);
+      const igDataPath = path.resolve(__dirname, 'fixtures', 'customized-ig', 'ig-data');
+      const exporter = new IGExporter(pkg, null, igDataPath, true); // New tank configuration
+      exporter.addIncludeContents(tempOut);
+      const includesPath = path.join(tempOut, 'input', 'includes');
+      expect(fs.existsSync(includesPath)).toBeFalsy();
+      expect(outputFileSyncSpy).not.toHaveBeenCalled();
+      expect(loggerSpy.getAllMessages()).toHaveLength(0);
+    });
+
+    it('should copy input/includes files with warning in legacy IG publisher configuration', () => {
+      const pkg = new Package(config);
+      const igDataPath = path.resolve(__dirname, 'fixtures', 'customized-ig', 'ig-data');
+      const exporter = new IGExporter(pkg, null, igDataPath); // Legacy tank configuration
+      exporter.addIncludeContents(tempOut);
+      const includesPath = path.join(tempOut, 'input', 'includes');
+      expect(fs.existsSync(includesPath)).toBeTruthy();
+      expect(outputFileSyncSpy).toHaveBeenCalledTimes(1);
+      const files = fs.readdirSync(includesPath, 'utf8');
+      expect(files).toEqual(['other.xml']); // Should include other.xml and not menu.xml since that isn't handle in addIncludeContents()
+      const content = fs.readFileSync(path.join(includesPath, 'other.xml'), 'utf8');
+      expect(content).toMatch(/^\*\s+WARNING: DO NOT EDIT THIS FILE\s+\*$/m);
+      expect(loggerSpy.getAllMessages()).toHaveLength(0);
+    });
+  });
+});
diff --git a/test/ig/fixtures/customized-ig-with-pagecontent/ig-data/input/pagecontent/examples.xml b/test/ig/fixtures/customized-ig-with-pagecontent/ig-data/input/pagecontent/examples.xml
new file mode 100644
index 0000000..51fd227
--- /dev/null
+++ b/test/ig/fixtures/customized-ig-with-pagecontent/ig-data/input/pagecontent/examples.xml
@@ -0,0 +1,6 @@
+<div xmlns="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://hl7.org/fhir ../../input-cache/schemas/R5/fhir-single.xsd">
+  <h3>Examples</h3>
+	<p>
+    This are examples
+  </p>
+</div>
\ No newline at end of file
diff --git a/test/ig/fixtures/customized-ig-with-pagecontent/ig-data/input/pagecontent/index.md b/test/ig/fixtures/customized-ig-with-pagecontent/ig-data/input/pagecontent/index.md
new file mode 100644
index 0000000..f2c9272
--- /dev/null
+++ b/test/ig/fixtures/customized-ig-with-pagecontent/ig-data/input/pagecontent/index.md
@@ -0,0 +1,1 @@
+My special index page.
\ No newline at end of file
diff --git a/test/ig/fixtures/customized-ig-with-pagecontent/ig-data/input/pagecontent/simpleExamples.xml b/test/ig/fixtures/customized-ig-with-pagecontent/ig-data/input/pagecontent/simpleExamples.xml
new file mode 100644
index 0000000..125ee30
--- /dev/null
+++ b/test/ig/fixtures/customized-ig-with-pagecontent/ig-data/input/pagecontent/simpleExamples.xml
@@ -0,0 +1,6 @@
+<div xmlns="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://hl7.org/fhir ../../input-cache/schemas/R5/fhir-single.xsd">
+  <h3>Simple Examples</h3>
+	<p>
+    Ah, very simple.
+  </p>
+</div>
\ No newline at end of file