Repository: maizzle/framework (js)

Issue:
Maizzle ignores destination extension
- Maizzle Version: 3.5.2
- Node.js Version: 16.3.0

In `src/`, I use templates with file suffix `.html.twig`. For production builds, I keep the original file suffix.

There was a prior version of maizzle where `.html.twig` suffixed did not work, but in the current version, this works fine for me. The contemporary issue is like the reverse issue of the old issue: I cannot override the `.html` suffix for serve and dev tasks:

### Setup

My config.js:
```js
module.exports = {
  build: {
    templates: {
      source: 'src/templates',
      filetypes: 'html.twig',
      destination: {
        path: 'build_local',
        extension: 'html',
      },
    },
  },
}
```

### Things to reproduce

1. Run `maizzle build` or `maizzle serve`
1a. ❌ The files inside `build_local` still have the `.html.twig` suffix and thus cannot be opened in the browser :-( 
2. Change `destination.extension` to something different, e.g., `txt` or `html5`.
2a. 🟡 The files get transformed to  `txt` or `html5` respectively correctly.
3. Change `destination.extension` to `.html` (contradicts to the documentation).
3a. ✅ The suffix now is `.html.twig..html`, which is working for me.



Pull request description:
fix: destination extension
This PR fixes an issue with files not being output with the correct extension, so you can use non-HTML files when developing and have them output with the `.html` extension so you can preview them in the browser.

Fixes #485 

Patch:
diff --git a/src/generators/output/to-disk.js b/src/generators/output/to-disk.js
index c30b982..787a7fb 100644
--- a/src/generators/output/to-disk.js
+++ b/src/generators/output/to-disk.js
@@ -69,6 +69,12 @@ module.exports = async (env, spinner, config) => {
               const destination = config.permalink || file
               const plaintextConfig = get(config, 'plaintext')
 
+              /**
+               * Generate plaintext
+               *
+               * We do this first so that we can remove the <plaintext>
+               * tags from the markup before outputting the file.
+               */
               if ((typeof plaintextConfig === 'boolean' && plaintextConfig) || !isEmpty(plaintextConfig)) {
                 await Plaintext
                   .generate(html, destination, config)
@@ -77,14 +83,15 @@ module.exports = async (env, spinner, config) => {
 
               html = removePlaintextTags(html, config)
 
+              /**
+               * Output file
+               */
+              const parts = path.parse(destination)
+              const extension = get(templateConfig, 'destination.extension', 'html')
+
               await fs.outputFile(destination, html)
                 .then(async () => {
-                  const extension = get(templateConfig, 'destination.extension', 'html')
-
-                  if (extension !== 'html') {
-                    const parts = path.parse(destination)
-                    await fs.move(destination, `${parts.dir}/${parts.name}.${extension}`)
-                  }
+                  await fs.rename(destination, `${parts.dir}/${parts.name}.${extension}`)
 
                   files.push(file)
                   parsed.push(file)
diff --git a/src/index.js b/src/index.js
index df807af..28f4faf 100644
--- a/src/index.js
+++ b/src/index.js
@@ -50,7 +50,7 @@ const self = module.exports = { // eslint-disable-line
           [...new Set(get(config, 'build.browsersync.watch', []))]
         ]
 
-        // Compile Tailwind so that updates to tailwind.config.js are reflected
+        // Pre-compile Tailwind so that updates to tailwind.config.js are reflected
         const cssString = fs.existsSync(get(config, 'build.tailwind.css')) ? fs.readFileSync(get(config, 'build.tailwind.css'), 'utf8') : '@tailwind components; @tailwind utilities;'
         const css = await Tailwind.compile(cssString, '', {}, config)
 
@@ -65,21 +65,21 @@ const self = module.exports = { // eslint-disable-line
 
             file = file.replace(/\\/g, '/')
 
-            const fileSource = get(config, 'build.currentTemplates.source')
+            const parts = path.parse(file)
             const destination = get(config, 'build.currentTemplates.destination.path')
             const extension = get(config, 'build.currentTemplates.destination.extension', 'html')
-
-            let finalDestination = path.join(destination, file.replace(fileSource, ''))
-
-            if (extension !== 'html') {
-              const parts = path.parse(file)
-              finalDestination = path.join(destination, `${parts.name}.${extension}`)
-            }
+            const finalDestination = path.join(destination, `${parts.name}.${extension}`)
 
             if (config.events && typeof config.events.beforeCreate === 'function') {
               await config.events.beforeCreate(config)
             }
 
+            /**
+             * Tailwind compiler
+             *
+             * Use the JIT engine if user enabled it
+             * Fall back to the classic, Always-On-Time engine
+             */
             let mode = 'aot'
 
             try {
@@ -92,7 +92,7 @@ const self = module.exports = { // eslint-disable-line
               ...config.events
             }
 
-            // Use pre-compiled CSS only when not using JIT
+            // AOT: fall back to pre-compiled CSS
             if (mode !== 'jit') {
               renderOptions.tailwind = {
                 compiled: css


Test patch:
diff --git a/test/test-todisk.js b/test/test-todisk.js
index 6dac2b7..515054f 100644
--- a/test/test-todisk.js
+++ b/test/test-todisk.js
@@ -89,7 +89,7 @@ test('outputs files at the correct location if multiple template sources are use
   t.is(files.length, 4)
 })
 
-test('processes all files in the `filetypes` option', async t => {
+test('copies all files in the `filetypes` option to destination', async t => {
   const {files} = await Maizzle.build('production', {
     build: {
       fail: 'silent',
@@ -109,7 +109,7 @@ test('processes all files in the `filetypes` option', async t => {
   })
 
   t.true(fs.pathExistsSync(t.context.folder))
-  t.is(files.length, 3)
+  t.is(files.length, 4)
 })
 
 test('outputs files with the correct extension', async t => {