Repository: teleporthq/teleport-code-generators (ts)

Issue:
Project generators - duplicated files in the output
It was observed that the project generators have as output a `GeneratedFolder` structure that contains duplicated files in the following situation:
- the used UIDL contains a component named, for example, `my-component`
- the used template already contains a component named `my-component` (the same name as a component in the UIDL)

In this case, it is expected not to generate another file but to override the existing one.

In order to fix this we could update the function that injects the files into the folder structure (`injectFilesInFolderStructure`) to check if the file already exists (in the path that it is supposed to be injected) and override it. 

A nice to have feature regarding this would be to diff the files before and only apply the necessary updates. Maybe we can use the Github API for this.

Pull request description:
fix(proj-gen): check for file override instead of duplication
fix #267

Patch:
diff --git a/packages/teleport-component-generator/src/index.ts b/packages/teleport-component-generator/src/index.ts
index e83510ca..2889458f 100644
--- a/packages/teleport-component-generator/src/index.ts
+++ b/packages/teleport-component-generator/src/index.ts
@@ -70,7 +70,7 @@ export const createComponentGenerator = (
       codeChunks = processor(codeChunks)
     })
 
-    const fileName = getComponentFileName(uidl)
+    const fileName = getComponentFileName(resolvedUIDL)
     const files = fileBundler(fileName, codeChunks)
 
     return {
diff --git a/packages/teleport-project-generator/src/utils.ts b/packages/teleport-project-generator/src/utils.ts
index 20a2464d..3ab7dce4 100644
--- a/packages/teleport-project-generator/src/utils.ts
+++ b/packages/teleport-project-generator/src/utils.ts
@@ -141,7 +141,15 @@ export const injectFilesToPath = (
     folder = createFolderInPath(rootFolder, path)
   }
 
-  folder.files = folder.files.concat(files)
+  files.forEach((fileToInject) => {
+    const existingFile = findFileInFolder(fileToInject, folder)
+    if (existingFile) {
+      existingFile.content = fileToInject.content
+      existingFile.contentEncoding = fileToInject.contentEncoding
+    } else {
+      folder.files.push(fileToInject)
+    }
+  })
 }
 
 const createFolderInPath = (rootFolder: GeneratedFolder, folderPath: string[]): GeneratedFolder => {
@@ -182,3 +190,7 @@ const findSubFolderByName = (rootFolder: GeneratedFolder, folderName: string): G
     return folder.name === folderName
   })
 }
+
+const findFileInFolder = (file: GeneratedFile, folder: GeneratedFolder) => {
+  return folder.files.find((f) => f.name === file.name && f.fileType === file.fileType)
+}
diff --git a/packages/teleport-uidl-validator/src/uidl-schemas/component.json b/packages/teleport-uidl-validator/src/uidl-schemas/component.json
index 34da2cdf..31df1e2a 100644
--- a/packages/teleport-uidl-validator/src/uidl-schemas/component.json
+++ b/packages/teleport-uidl-validator/src/uidl-schemas/component.json
@@ -266,7 +266,7 @@
       "additionalProperties": false,
       "properties": {
         "additionalProperties": false,
-        "elementType": {"type": "string"},
+        "elementType": {"type": "string", "pattern": "^([a-zA-Z0-9])*$"},
         "name": {"type": "string"},
         "key": {"type": "string"},
         "dependency": {"$ref": "#/definitions/componentDependency"},


Test patch:
diff --git a/packages/teleport-project-generator/__tests__/mocks.ts b/packages/teleport-project-generator/__tests__/mocks.ts
index 1239f227..4f266a62 100644
--- a/packages/teleport-project-generator/__tests__/mocks.ts
+++ b/packages/teleport-project-generator/__tests__/mocks.ts
@@ -1,4 +1,4 @@
-import { Mapping, CompiledComponent } from '@teleporthq/teleport-types'
+import { Mapping, CompiledComponent, GeneratedFolder } from '@teleporthq/teleport-types'
 import { ProjectStrategy } from '../src/types'
 
 export const mockMapping: Mapping = {
@@ -101,3 +101,30 @@ export const secondStrategy: ProjectStrategy = {
     path: ['test', 'static'],
   },
 }
+
+export const emptyFolder = (name: string = 'test'): GeneratedFolder => {
+  return {
+    name,
+    files: [],
+    subFolders: [],
+  }
+}
+
+export const folderWithFiles = (name: string = 'test'): GeneratedFolder => {
+  return {
+    name,
+    files: [
+      {
+        name: 'index',
+        fileType: 'js',
+        content: 'var x = 0;',
+      },
+      {
+        name: 'index',
+        fileType: 'css',
+        content: 'h1 { margin: 10px; }',
+      },
+    ],
+    subFolders: [],
+  }
+}
diff --git a/packages/teleport-project-generator/__tests__/utils.ts b/packages/teleport-project-generator/__tests__/utils.ts
index 7412b7e5..44d70947 100644
--- a/packages/teleport-project-generator/__tests__/utils.ts
+++ b/packages/teleport-project-generator/__tests__/utils.ts
@@ -1,4 +1,5 @@
-import { generateLocalDependenciesPrefix } from '../src/utils'
+import { generateLocalDependenciesPrefix, injectFilesToPath } from '../src/utils'
+import { emptyFolder, folderWithFiles } from './mocks'
 
 describe('generateLocalDependenciesPrefix', () => {
   it('works when there is a common parent', () => {
@@ -36,3 +37,101 @@ describe('generateLocalDependenciesPrefix', () => {
     expect(generateLocalDependenciesPrefix(from, to)).toBe('./')
   })
 })
+
+describe('injectFilesToPath', () => {
+  it('adds a file in an empty folder', () => {
+    const folder = emptyFolder()
+    const newFile = {
+      name: 'test',
+      fileType: 'js',
+      content: 'random',
+    }
+
+    injectFilesToPath(folder, [], [newFile])
+
+    expect(folder.files.length).toBe(1)
+    expect(folder.files[0].name).toBe('test')
+    expect(folder.files[0].fileType).toBe('js')
+    expect(folder.files[0].content).toBe('random')
+  })
+
+  it('adds a file in a folder with existing files', () => {
+    const folder = folderWithFiles()
+    const newFile = {
+      name: 'test',
+      fileType: 'js',
+      content: 'random',
+    }
+
+    injectFilesToPath(folder, [], [newFile])
+
+    expect(folder.files.length).toBe(3)
+    expect(folder.files[2].name).toBe('test')
+    expect(folder.files[2].fileType).toBe('js')
+    expect(folder.files[2].content).toBe('random')
+  })
+
+  it('overrides existing files', () => {
+    const folder = folderWithFiles()
+    const newFiles = [
+      {
+        name: 'index',
+        fileType: 'js',
+        content: 'new-content',
+      },
+      {
+        name: 'index',
+        fileType: 'html',
+        content: '<html>',
+      },
+    ]
+
+    injectFilesToPath(folder, [], newFiles)
+
+    expect(folder.files.length).toBe(3)
+    expect(folder.files[0].name).toBe('index')
+    expect(folder.files[0].fileType).toBe('js')
+    expect(folder.files[0].content).toBe('new-content')
+    expect(folder.files[1].name).toBe('index')
+    expect(folder.files[1].fileType).toBe('css')
+    expect(folder.files[1].content).toBe('h1 { margin: 10px; }')
+    expect(folder.files[2].name).toBe('index')
+    expect(folder.files[2].fileType).toBe('html')
+    expect(folder.files[2].content).toBe('<html>')
+  })
+
+  it('adds the files in a newly created subfolder', () => {
+    const folder = folderWithFiles()
+    const newFiles = [
+      {
+        name: 'index',
+        fileType: 'js',
+        content: 'new-content',
+      },
+      {
+        name: 'index',
+        fileType: 'html',
+        content: '<html>',
+      },
+    ]
+
+    injectFilesToPath(folder, ['subfolder'], newFiles)
+
+    expect(folder.files.length).toBe(2)
+    expect(folder.files[0].name).toBe('index')
+    expect(folder.files[0].fileType).toBe('js')
+    expect(folder.files[0].content).toBe('var x = 0;')
+    expect(folder.files[1].name).toBe('index')
+    expect(folder.files[1].fileType).toBe('css')
+    expect(folder.files[1].content).toBe('h1 { margin: 10px; }')
+
+    expect(folder.subFolders[0].name).toBe('subfolder')
+    expect(folder.subFolders[0].files.length).toBe(2)
+    expect(folder.subFolders[0].files[0].name).toBe('index')
+    expect(folder.subFolders[0].files[0].fileType).toBe('js')
+    expect(folder.subFolders[0].files[0].content).toBe('new-content')
+    expect(folder.subFolders[0].files[1].name).toBe('index')
+    expect(folder.subFolders[0].files[1].fileType).toBe('html')
+    expect(folder.subFolders[0].files[1].content).toBe('<html>')
+  })
+})