Repository: maizzle/framework (js)

Issue:
Exit code is not properly set when template compilation failed
- Maizzle Version: 4.8.4
- Node.js Version: 18.19.0

Hello,

I find that the exit code will be set to zero even if there is an compilation failure across templates:

Log:
```
$ maizzle build production
✖ Failed to compile template: promotional.html
⚠ [components] <x-notExist> could not find notExist.html in the defined root paths (src/components, src/layouts, src/templates)
✔ Built 0 templates in 0.16s
✨  Done in 0.74s.
```
When I echo the exit code, it is zero.

I followed the #986, however the error code is always zero regardless of which `config.build.fail` I choose (verbose, silent or unset). I would like Maizzle to fail so the CI can detect it.

Thanks so much for the help!

Pull request description:
fix: throw if a template cannot be rendered
This PR fixes #1255, an issue where the process was not correctly exiting because of an error that was being caught, but not thrown.

Patch:
diff --git a/src/commands/build.js b/src/commands/build.js
index f25a7fe..906e40a 100644
--- a/src/commands/build.js
+++ b/src/commands/build.js
@@ -7,26 +7,21 @@ const build = async (env = 'local', config = {}) => {
   const start = new Date()
   const spinner = ora('Building emails...').start()
 
-  try {
-    const {files, parsed} = await Output.toDisk(env, spinner, config)
+  const {files, parsed} = await Output.toDisk(env, spinner, config)
 
-    const elapsedSeconds = (Date.now() - start) / 1000
+  const elapsedSeconds = (Date.now() - start) / 1000
 
-    if (get(config, 'build.command') === 'serve') {
-      if (get(config, 'build.console.clear')) {
-        clearConsole()
-      }
-
-      spinner.succeed(`Re-built ${parsed.length} templates in ${elapsedSeconds}s`)
-    } else {
-      spinner.succeed(`Built ${parsed.length} templates in ${elapsedSeconds}s`)
+  if (get(config, 'build.command') === 'serve') {
+    if (get(config, 'build.console.clear')) {
+      clearConsole()
     }
 
-    return {files}
-  } catch (error) {
-    spinner.fail(error.message)
-    throw error
+    spinner.succeed(`Re-built ${parsed.length} templates in ${elapsedSeconds}s`)
+  } else {
+    spinner.succeed(`Built ${parsed.length} templates in ${elapsedSeconds}s`)
   }
+
+  return {files}
 }
 
 module.exports = build
diff --git a/src/generators/output/to-disk.js b/src/generators/output/to-disk.js
index f4b65ce..3bd0ad3 100644
--- a/src/generators/output/to-disk.js
+++ b/src/generators/output/to-disk.js
@@ -236,7 +236,9 @@ module.exports = async (env, spinner, config) => {
               files = [...new Set([...files, ...contents])]
             })
         })
-        .catch(error => spinner.warn(error.message))
+        .catch(error => {
+          throw error
+        })
     }
   }
 


Test patch:
diff --git a/test/test-todisk.js b/test/test-todisk.js
index 2acd403..3da5e90 100644
--- a/test/test-todisk.js
+++ b/test/test-todisk.js
@@ -20,6 +20,21 @@ test('throws if config cannot be computed', async t => {
   }, {instanceOf: Error})
 })
 
+test('throws if a template cannot be rendered', async t => {
+  await t.throwsAsync(async () => {
+    await Maizzle.build('maizzle-ci', {
+      build: {
+        templates: {
+          source: 'test/stubs/breaking',
+          destination: {
+            path: t.context.folder
+          }
+        }
+      }
+    })
+  }, {instanceOf: Error})
+})
+
 test('skips if no templates found', async t => {
   const {files} = await Maizzle.build('maizzle-ci', {
     build: {
@@ -429,21 +444,6 @@ test('supports multiple assets array paths with templates array', async t => {
   t.true(files.includes(`${t.context.folder}/assets/extras4/foo2.bar`))
 })
 
-test('warns if a template cannot be rendered and `fail` option is undefined', async t => {
-  const {files} = await Maizzle.build('maizzle-ci', {
-    build: {
-      templates: {
-        source: 'test/stubs/breaking',
-        destination: {
-          path: t.context.folder
-        }
-      }
-    }
-  })
-
-  t.false(files.includes('empty.html'))
-})
-
 test('warns if a template cannot be rendered and `fail` option is `verbose`', async t => {
   const {files} = await Maizzle.build('maizzle-ci', {
     build: {