@maizzle/framework 6.0.0-rc.22 → 6.0.0-rc.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. package/dist/build.d.ts.map +1 -1
  2. package/dist/build.js +22 -2
  3. package/dist/build.js.map +1 -1
  4. package/dist/components/Heading.vue +1 -1
  5. package/dist/components/Img.vue +60 -10
  6. package/dist/components/Link.vue +1 -1
  7. package/dist/components/Markdown.vue +48 -16
  8. package/dist/components/Preheader.vue +4 -2
  9. package/dist/components/Tailwind.vue +4 -2
  10. package/dist/components/Text.vue +1 -1
  11. package/dist/components/Vml.vue +354 -0
  12. package/dist/components/utils.d.ts.map +1 -1
  13. package/dist/components/utils.js.map +1 -1
  14. package/dist/composables/defineConfig.js.map +1 -1
  15. package/dist/composables/renderContext.d.ts +1 -0
  16. package/dist/composables/renderContext.d.ts.map +1 -1
  17. package/dist/composables/renderContext.js +1 -1
  18. package/dist/composables/renderContext.js.map +1 -1
  19. package/dist/composables/useBaseUrl.d.ts.map +1 -1
  20. package/dist/composables/useBaseUrl.js.map +1 -1
  21. package/dist/composables/useConfig.d.ts +7 -0
  22. package/dist/composables/useConfig.d.ts.map +1 -1
  23. package/dist/composables/useConfig.js +8 -1
  24. package/dist/composables/useConfig.js.map +1 -1
  25. package/dist/composables/useCurrentTemplate.d.ts.map +1 -1
  26. package/dist/composables/useCurrentTemplate.js +10 -3
  27. package/dist/composables/useCurrentTemplate.js.map +1 -1
  28. package/dist/composables/useDoctype.d.ts.map +1 -1
  29. package/dist/composables/useDoctype.js.map +1 -1
  30. package/dist/composables/useEvent.js.map +1 -1
  31. package/dist/composables/useFont.d.ts.map +1 -1
  32. package/dist/composables/useFont.js.map +1 -1
  33. package/dist/composables/useOutlookFallback.d.ts.map +1 -1
  34. package/dist/composables/useOutlookFallback.js.map +1 -1
  35. package/dist/composables/useOutputPath.d.ts +17 -0
  36. package/dist/composables/useOutputPath.d.ts.map +1 -0
  37. package/dist/composables/useOutputPath.js +23 -0
  38. package/dist/composables/useOutputPath.js.map +1 -0
  39. package/dist/composables/usePlaintext.d.ts.map +1 -1
  40. package/dist/composables/usePlaintext.js.map +1 -1
  41. package/dist/composables/usePreheader.d.ts.map +1 -1
  42. package/dist/composables/usePreheader.js.map +1 -1
  43. package/dist/composables/useTransformers.d.ts.map +1 -1
  44. package/dist/composables/useTransformers.js.map +1 -1
  45. package/dist/composables/useUrlQuery.d.ts.map +1 -1
  46. package/dist/composables/useUrlQuery.js.map +1 -1
  47. package/dist/config/defaults.d.ts.map +1 -1
  48. package/dist/config/defaults.js.map +1 -1
  49. package/dist/config/index.js +12 -0
  50. package/dist/config/index.js.map +1 -1
  51. package/dist/events/index.d.ts +5 -0
  52. package/dist/events/index.d.ts.map +1 -1
  53. package/dist/events/index.js +5 -0
  54. package/dist/events/index.js.map +1 -1
  55. package/dist/index.d.ts +2 -1
  56. package/dist/index.js +2 -1
  57. package/dist/plaintext.d.ts.map +1 -1
  58. package/dist/plaintext.js.map +1 -1
  59. package/dist/plugin.js.map +1 -1
  60. package/dist/plugins/postcss/mergeMediaQueries.d.ts.map +1 -1
  61. package/dist/plugins/postcss/mergeMediaQueries.js.map +1 -1
  62. package/dist/plugins/postcss/pruneVars.d.ts.map +1 -1
  63. package/dist/plugins/postcss/pruneVars.js.map +1 -1
  64. package/dist/plugins/postcss/quoteFontFamilies.d.ts.map +1 -1
  65. package/dist/plugins/postcss/quoteFontFamilies.js.map +1 -1
  66. package/dist/plugins/postcss/removeDeclarations.d.ts.map +1 -1
  67. package/dist/plugins/postcss/removeDeclarations.js.map +1 -1
  68. package/dist/plugins/postcss/resolveMaizzleImports.d.ts.map +1 -1
  69. package/dist/plugins/postcss/resolveMaizzleImports.js.map +1 -1
  70. package/dist/plugins/postcss/resolveProps.d.ts.map +1 -1
  71. package/dist/plugins/postcss/resolveProps.js +14 -0
  72. package/dist/plugins/postcss/resolveProps.js.map +1 -1
  73. package/dist/plugins/postcss/tailwindCleanup.d.ts.map +1 -1
  74. package/dist/plugins/postcss/tailwindCleanup.js.map +1 -1
  75. package/dist/prepare.d.ts.map +1 -1
  76. package/dist/prepare.js.map +1 -1
  77. package/dist/render/active.d.ts.map +1 -1
  78. package/dist/render/active.js.map +1 -1
  79. package/dist/render/createRenderer.d.ts +1 -0
  80. package/dist/render/createRenderer.d.ts.map +1 -1
  81. package/dist/render/createRenderer.js +105 -1
  82. package/dist/render/createRenderer.js.map +1 -1
  83. package/dist/render/index.d.ts.map +1 -1
  84. package/dist/render/index.js +7 -1
  85. package/dist/render/index.js.map +1 -1
  86. package/dist/render/injectFonts.js.map +1 -1
  87. package/dist/render/plugins/codeBlockExtract.d.ts.map +1 -1
  88. package/dist/render/plugins/codeBlockExtract.js +4 -0
  89. package/dist/render/plugins/codeBlockExtract.js.map +1 -1
  90. package/dist/render/plugins/markdownExtract.d.ts.map +1 -1
  91. package/dist/render/plugins/markdownExtract.js.map +1 -1
  92. package/dist/render/plugins/rawExtract.d.ts.map +1 -1
  93. package/dist/render/plugins/rawExtract.js.map +1 -1
  94. package/dist/render/plugins/rowSourceLocation.d.ts.map +1 -1
  95. package/dist/render/plugins/rowSourceLocation.js.map +1 -1
  96. package/dist/serve.d.ts.map +1 -1
  97. package/dist/serve.js +51 -18
  98. package/dist/serve.js.map +1 -1
  99. package/dist/server/compatibility.d.ts.map +1 -1
  100. package/dist/server/compatibility.js +48 -0
  101. package/dist/server/compatibility.js.map +1 -1
  102. package/dist/server/email.js.map +1 -1
  103. package/dist/server/linter.js +6 -0
  104. package/dist/server/linter.js.map +1 -1
  105. package/dist/server/sfc-utils.d.ts.map +1 -1
  106. package/dist/server/sfc-utils.js.map +1 -1
  107. package/dist/server/ui/App.vue +16 -16
  108. package/dist/server/ui/components/SidebarClose.vue +1 -1
  109. package/dist/server/ui/components/ui/checkbox/Checkbox.vue +1 -1
  110. package/dist/server/ui/components/ui/command/CommandInput.vue +2 -2
  111. package/dist/server/ui/components/ui/dialog/DialogContent.vue +1 -1
  112. package/dist/server/ui/components/ui/dialog/DialogScrollContent.vue +1 -1
  113. package/dist/server/ui/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +1 -1
  114. package/dist/server/ui/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +1 -1
  115. package/dist/server/ui/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +1 -1
  116. package/dist/server/ui/components/ui/sheet/SheetContent.vue +1 -1
  117. package/dist/server/ui/components/ui/sidebar/SidebarTrigger.vue +1 -1
  118. package/dist/server/ui/components/ui/tags-input/TagsInputItemDelete.vue +1 -1
  119. package/dist/server/ui/lib/emulated-dark-mode.ts +25 -10
  120. package/dist/server/ui/main.css +25 -0
  121. package/dist/server/ui/pages/Home.vue +1 -1
  122. package/dist/server/ui/pages/Preview.vue +37 -19
  123. package/dist/tests/render/_helpers.js.map +1 -1
  124. package/dist/transformers/addAttributes.d.ts.map +1 -1
  125. package/dist/transformers/addAttributes.js.map +1 -1
  126. package/dist/transformers/attributeToStyle.d.ts.map +1 -1
  127. package/dist/transformers/attributeToStyle.js.map +1 -1
  128. package/dist/transformers/base.d.ts.map +1 -1
  129. package/dist/transformers/base.js +4 -0
  130. package/dist/transformers/base.js.map +1 -1
  131. package/dist/transformers/columnWidth.d.ts.map +1 -1
  132. package/dist/transformers/columnWidth.js.map +1 -1
  133. package/dist/transformers/entities.d.ts.map +1 -1
  134. package/dist/transformers/entities.js.map +1 -1
  135. package/dist/transformers/filters/defaults.d.ts.map +1 -1
  136. package/dist/transformers/filters/defaults.js.map +1 -1
  137. package/dist/transformers/filters/index.d.ts.map +1 -1
  138. package/dist/transformers/filters/index.js.map +1 -1
  139. package/dist/transformers/format.d.ts.map +1 -1
  140. package/dist/transformers/format.js.map +1 -1
  141. package/dist/transformers/index.d.ts.map +1 -1
  142. package/dist/transformers/index.js +26 -0
  143. package/dist/transformers/index.js.map +1 -1
  144. package/dist/transformers/inlineCss.d.ts.map +1 -1
  145. package/dist/transformers/inlineCss.js +25 -2
  146. package/dist/transformers/inlineCss.js.map +1 -1
  147. package/dist/transformers/inlineLink.d.ts.map +1 -1
  148. package/dist/transformers/inlineLink.js.map +1 -1
  149. package/dist/transformers/minify.d.ts.map +1 -1
  150. package/dist/transformers/minify.js.map +1 -1
  151. package/dist/transformers/minifyCodeInline.d.ts.map +1 -1
  152. package/dist/transformers/minifyCodeInline.js.map +1 -1
  153. package/dist/transformers/msoPlaceholders.d.ts.map +1 -1
  154. package/dist/transformers/msoPlaceholders.js.map +1 -1
  155. package/dist/transformers/purgeCss.d.ts.map +1 -1
  156. package/dist/transformers/purgeCss.js +29 -3
  157. package/dist/transformers/purgeCss.js.map +1 -1
  158. package/dist/transformers/removeAttributes.d.ts.map +1 -1
  159. package/dist/transformers/removeAttributes.js.map +1 -1
  160. package/dist/transformers/replaceStrings.d.ts.map +1 -1
  161. package/dist/transformers/replaceStrings.js.map +1 -1
  162. package/dist/transformers/safeSelectors.d.ts.map +1 -1
  163. package/dist/transformers/safeSelectors.js +13 -1
  164. package/dist/transformers/safeSelectors.js.map +1 -1
  165. package/dist/transformers/shorthandCss.d.ts.map +1 -1
  166. package/dist/transformers/shorthandCss.js.map +1 -1
  167. package/dist/transformers/sixHex.d.ts.map +1 -1
  168. package/dist/transformers/sixHex.js.map +1 -1
  169. package/dist/transformers/tailwindComponent.js +9 -0
  170. package/dist/transformers/tailwindComponent.js.map +1 -1
  171. package/dist/transformers/tailwindcss.d.ts.map +1 -1
  172. package/dist/transformers/tailwindcss.js +22 -0
  173. package/dist/transformers/tailwindcss.js.map +1 -1
  174. package/dist/transformers/urlQuery.d.ts.map +1 -1
  175. package/dist/transformers/urlQuery.js.map +1 -1
  176. package/dist/types/config.d.ts +4 -8
  177. package/dist/types/config.d.ts.map +1 -1
  178. package/dist/types/index.d.ts +1 -1
  179. package/dist/utils/ast/parser.d.ts.map +1 -1
  180. package/dist/utils/ast/parser.js.map +1 -1
  181. package/dist/utils/ast/serializer.d.ts.map +1 -1
  182. package/dist/utils/ast/serializer.js.map +1 -1
  183. package/dist/utils/ast/walker.d.ts.map +1 -1
  184. package/dist/utils/ast/walker.js.map +1 -1
  185. package/dist/utils/compileTailwindCss.d.ts.map +1 -1
  186. package/dist/utils/compileTailwindCss.js.map +1 -1
  187. package/dist/utils/componentSources.d.ts.map +1 -1
  188. package/dist/utils/componentSources.js.map +1 -1
  189. package/dist/utils/cssBox.d.ts.map +1 -1
  190. package/dist/utils/cssBox.js.map +1 -1
  191. package/dist/utils/decodeStyleEntities.d.ts.map +1 -1
  192. package/dist/utils/decodeStyleEntities.js.map +1 -1
  193. package/dist/utils/detect.d.ts.map +1 -1
  194. package/dist/utils/detect.js.map +1 -1
  195. package/dist/utils/output-markers.d.ts.map +1 -1
  196. package/dist/utils/output-markers.js.map +1 -1
  197. package/dist/utils/url.d.ts.map +1 -1
  198. package/dist/utils/url.js.map +1 -1
  199. package/dist/utils/watchPaths.js.map +1 -1
  200. package/node_modules/@clack/core/CHANGELOG.md +6 -0
  201. package/node_modules/@clack/core/dist/index.d.mts +1 -1
  202. package/node_modules/@clack/core/dist/index.mjs +8 -8
  203. package/node_modules/@clack/core/dist/index.mjs.map +1 -1
  204. package/node_modules/@clack/core/package.json +1 -1
  205. package/node_modules/@clack/prompts/CHANGELOG.md +13 -0
  206. package/node_modules/@clack/prompts/README.md +2 -2
  207. package/node_modules/@clack/prompts/dist/index.d.mts +98 -0
  208. package/node_modules/@clack/prompts/dist/index.mjs +122 -121
  209. package/node_modules/@clack/prompts/dist/index.mjs.map +1 -1
  210. package/node_modules/@clack/prompts/package.json +2 -2
  211. package/node_modules/fast-wrap-ansi/lib/main.js +0 -1
  212. package/node_modules/fast-wrap-ansi/package.json +10 -10
  213. package/node_modules/maizzle/dist/commands/make/config.mjs +7 -6
  214. package/node_modules/maizzle/dist/commands/new.mjs +15 -84
  215. package/node_modules/maizzle/package.json +2 -2
  216. package/node_modules/tinyexec/README.md +8 -0
  217. package/node_modules/tinyexec/dist/main.d.mts +16 -1
  218. package/node_modules/tinyexec/dist/main.mjs +163 -457
  219. package/node_modules/tinyexec/package.json +12 -14
  220. package/package.json +3 -4
  221. package/node_modules/fast-wrap-ansi/lib/main.js.map +0 -1
  222. package/node_modules/tinyexec/dist/LICENSES.txt +0 -83
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","names":[],"sources":["../src/build.ts"],"mappings":";;UAeiB,WAAA;EACf,KAAA;EACA,MAAA,EAAQ,aAAA;AAAA;;;;;;;;;AAaV;;iBAAsB,KAAA,CAAM,WAAA,GAAc,OAAA,CAAQ,aAAA,aAA0B,OAAA,CAAQ,WAAA"}
1
+ {"version":3,"file":"build.d.ts","names":[],"sources":["../src/build.ts"],"mappings":";;UAeiB,WAAA;EACf,KAAA;EACA,MAAA,EAAQ,aAAa;AAAA;;;;;;;;AAAA;AAavB;;iBAAsB,KAAA,CAAM,WAAA,GAAc,OAAA,CAAQ,aAAA,aAA0B,OAAA,CAAQ,WAAA"}
package/dist/build.js CHANGED
@@ -70,12 +70,23 @@ async function build(configInput) {
70
70
  template
71
71
  });
72
72
  const rendered = await renderer.render(absolutePath, config);
73
+ /**
74
+ * Register SFC event handlers collected during render so they take
75
+ * part in the post-render events (afterRender / afterTransform).
76
+ * They're cleared at the end of the iteration so they don't
77
+ * leak into the next template.
78
+ */
73
79
  for (const { name, handler } of rendered.sfcEventHandlers) events.on(name, handler);
74
80
  let html = await events.fireAfterRender({
75
81
  config,
76
82
  template,
77
83
  html: rendered.html
78
84
  });
85
+ /**
86
+ * Use the per-template merged config (from defineConfig() in the SFC) so
87
+ * that template-level overrides like css.safe: false are respected
88
+ * by transformers.
89
+ */
79
90
  const templateConfig = rendered.templateConfig;
80
91
  const doctype = rendered.doctype ?? templateConfig.doctype ?? "<!DOCTYPE html>";
81
92
  if (templateConfig.useTransformers !== false) html = await runTransformers(html, templateConfig, absolutePath, doctype, rendered.tailwindBlocks);
@@ -84,9 +95,15 @@ async function build(configInput) {
84
95
  template,
85
96
  html
86
97
  });
87
- html = `${doctype}\n${html}`;
98
+ if (doctype) html = `${doctype}\n${html}`;
88
99
  const htmlOut = stripForHtml(html);
89
- const outputFilePath = resolveOutputPath(templatePath, outputPath, outputExtension, contentBase);
100
+ const sfcOutputPath = rendered.outputPath;
101
+ let outputFilePath;
102
+ if (sfcOutputPath) {
103
+ const parsed = parse(resolve(sfcOutputPath));
104
+ const ext = parsed.ext ? parsed.ext.slice(1) : outputExtension;
105
+ outputFilePath = join(parsed.dir, `${parsed.name}.${ext}`);
106
+ } else outputFilePath = resolveOutputPath(templatePath, outputPath, outputExtension, contentBase);
90
107
  mkdirSync(dirname(outputFilePath), { recursive: true });
91
108
  writeFileSync(outputFilePath, htmlOut);
92
109
  outputFiles.push(outputFilePath);
@@ -101,6 +118,9 @@ async function build(configInput) {
101
118
  if (sfcPlaintext?.destination) {
102
119
  const name = basename(templatePath).replace(/\.(vue|md)$/, "");
103
120
  ptOutputPath = join(resolve(sfcPlaintext.destination), `${name}.${ptExtension}`);
121
+ } else if (sfcOutputPath) {
122
+ const parsed = parse(outputFilePath);
123
+ ptOutputPath = join(parsed.dir, `${parsed.name}.${ptExtension}`);
104
124
  } else if (globalCfg.destination) ptOutputPath = resolveOutputPath(templatePath, resolve(globalCfg.destination), ptExtension, contentBase);
105
125
  else ptOutputPath = resolveOutputPath(templatePath, outputPath, ptExtension, contentBase);
106
126
  mkdirSync(dirname(ptOutputPath), { recursive: true });
package/dist/build.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"build.js","names":["parsePath"],"sources":["../src/build.ts"],"sourcesContent":["import { readFileSync, writeFileSync, mkdirSync, cpSync, existsSync, rmSync } from 'node:fs'\nimport { resolve, dirname, basename, relative, join, parse as parsePath } from 'node:path'\nimport { glob } from 'tinyglobby'\nimport ora from 'ora'\nimport { resolveConfig } from './config/index.ts'\nimport { EventManager } from './events/index.ts'\nimport { runTransformers } from './transformers/index.ts'\nimport { createRenderer } from './render/createRenderer.ts'\nimport { createPlaintext } from './plaintext.ts'\nimport { stripForHtml, stripForPlaintext } from './utils/output-markers.ts'\nimport { normalizeComponentSources } from './utils/componentSources.ts'\nimport { _setCurrentTemplate } from './composables/useCurrentTemplate.ts'\nimport defu from 'defu'\nimport type { MaizzleConfig } from './types/index.ts'\n\nexport interface BuildResult {\n files: string[]\n config: MaizzleConfig\n}\n\n/**\n * Build all SFC email templates to HTML files.\n *\n * Creates a single Renderer instance, then loops through each template\n * calling render → transformers → write to disk.\n *\n * Pass a `Partial<MaizzleConfig>` to override config inline, or a string\n * to load config from a specific file path. Omit to load `maizzle.config`\n * from the working directory.\n */\nexport async function build(configInput?: Partial<MaizzleConfig> | string): Promise<BuildResult> {\n const start = Date.now()\n const spinner = ora({ text: 'Building templates...', spinner: 'circleHalves' }).start()\n\n const config = await resolveConfig(configInput)\n\n const events = new EventManager()\n events.registerConfig(config)\n await events.fireBeforeCreate({ config })\n\n const outputPath = resolve(config.output?.path ?? 'dist')\n const outputExtension = config.output?.extension ?? 'html'\n\n const contentPatterns = config.content ?? ['emails/**/*.vue']\n const contentBase = computeContentBase(contentPatterns)\n const templateFiles = await glob(contentPatterns)\n\n if (templateFiles.length === 0) {\n spinner.succeed('No templates found')\n return { files: [], config }\n }\n\n // Clear the output directory before writing fresh output\n if (existsSync(outputPath)) {\n rmSync(outputPath, { recursive: true, force: true })\n }\n\n const renderer = await createRenderer({ markdown: config.markdown, root: config.root, componentDirs: normalizeComponentSources(config.components?.source, process.cwd()), vite: config.vite })\n const outputFiles: string[] = []\n\n try {\n for (const templatePath of templateFiles) {\n const absolutePath = resolve(templatePath)\n const parsedPath = parsePath(absolutePath)\n const template = { source: readFileSync(absolutePath, 'utf-8'), path: parsedPath }\n\n _setCurrentTemplate(parsedPath)\n\n try {\n await events.fireBeforeRender({ config, template })\n\n const rendered = await renderer.render(absolutePath, config)\n\n // Register SFC event handlers collected during render so they participate\n // in the post-render events (afterRender / afterTransform). They're cleared\n // at the end of the iteration so they don't leak into the next template.\n for (const { name, handler } of rendered.sfcEventHandlers) {\n events.on(name, handler)\n }\n\n let html = await events.fireAfterRender({ config, template, html: rendered.html })\n\n // Use the per-template merged config (from defineConfig() in the SFC) so that\n // template-level overrides like css.safe: false are respected by transformers.\n const templateConfig = rendered.templateConfig\n\n const doctype = rendered.doctype ?? templateConfig.doctype ?? '<!DOCTYPE html>'\n\n if (templateConfig.useTransformers !== false) {\n html = await runTransformers(html, templateConfig, absolutePath, doctype, rendered.tailwindBlocks)\n }\n\n html = await events.fireAfterTransform({ config, template, html })\n html = `${doctype}\\n${html}`\n\n const htmlOut = stripForHtml(html)\n const outputFilePath = resolveOutputPath(templatePath, outputPath, outputExtension, contentBase)\n mkdirSync(dirname(outputFilePath), { recursive: true })\n writeFileSync(outputFilePath, htmlOut)\n outputFiles.push(outputFilePath)\n\n // Generate plaintext version if configured\n const globalPlaintext = templateConfig.plaintext\n const sfcPlaintext = rendered.plaintext\n\n if (globalPlaintext || sfcPlaintext) {\n const globalCfg = typeof globalPlaintext === 'object' ? globalPlaintext : {}\n const stripOptions = defu(sfcPlaintext?.options, globalCfg.options)\n const plaintext = createPlaintext(stripForPlaintext(html), stripOptions)\n const ptExtension = sfcPlaintext?.extension ?? globalCfg.extension ?? 'txt'\n\n let ptOutputPath: string\n\n if (sfcPlaintext?.destination) {\n const name = basename(templatePath).replace(/\\.(vue|md)$/, '')\n ptOutputPath = join(resolve(sfcPlaintext.destination), `${name}.${ptExtension}`)\n } else if (globalCfg.destination) {\n ptOutputPath = resolveOutputPath(templatePath, resolve(globalCfg.destination), ptExtension, contentBase)\n } else {\n ptOutputPath = resolveOutputPath(templatePath, outputPath, ptExtension, contentBase)\n }\n\n mkdirSync(dirname(ptOutputPath), { recursive: true })\n writeFileSync(ptOutputPath, plaintext)\n }\n } finally {\n _setCurrentTemplate(undefined)\n events.clearSfcHandlers()\n }\n }\n\n await copyStatic(config, outputPath)\n await events.fireAfterBuild({ files: outputFiles, config })\n } finally {\n await renderer.close()\n }\n\n const duration = ((Date.now() - start) / 1000).toFixed(2)\n const count = outputFiles.length\n spinner.stopAndPersist({\n symbol: '✅',\n text: `Built ${count} template${count !== 1 ? 's' : ''} in ${duration}s`,\n })\n\n return { files: outputFiles, config }\n}\n\n/**\n * Extract the static (non-glob) prefix from content patterns.\n *\n * For example, `['/abs/path/emails/**\\/*.vue']` → `'/abs/path/emails'`\n *\n * This is used to strip the content base from template paths\n * so the output preserves only the subdirectory structure.\n */\nfunction computeContentBase(patterns: string[]): string {\n // Use the first non-negated pattern\n const pattern = patterns.find(p => !p.startsWith('!')) ?? patterns[0]\n\n // Split on first glob character (* { ? [) and take the directory part\n const staticPart = pattern.split(/[*{?[]/)[0]\n\n // Ensure we have a clean directory path (not a partial segment)\n return resolve(staticPart.endsWith('/') ? staticPart : dirname(staticPart))\n}\n\nfunction resolveOutputPath(templatePath: string, outputDir: string, extension: string, contentBase: string): string {\n const name = basename(templatePath).replace(/\\.(vue|md)$/, '')\n const absTemplate = resolve(templatePath)\n const rel = relative(contentBase, dirname(absTemplate))\n\n return join(outputDir, rel, `${name}.${extension}`)\n}\n\nasync function copyStatic(config: MaizzleConfig, outputPath: string): Promise<void> {\n const sources = config.static?.source ?? ['public/**/*.*']\n const destination = config.static?.destination ?? 'public'\n\n const files = await glob(sources)\n\n for (const file of files) {\n const destPath = join(outputPath, destination, relative(dirname(sources[0]).replace(/\\*.*$/, ''), file))\n const destDir = dirname(destPath)\n\n if (!existsSync(destDir)) {\n mkdirSync(destDir, { recursive: true })\n }\n\n cpSync(file, destPath)\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"build.js","names":["parsePath"],"sources":["../src/build.ts"],"sourcesContent":["import { readFileSync, writeFileSync, mkdirSync, cpSync, existsSync, rmSync } from 'node:fs'\nimport { resolve, dirname, basename, relative, join, parse as parsePath } from 'node:path'\nimport { glob } from 'tinyglobby'\nimport ora from 'ora'\nimport { resolveConfig } from './config/index.ts'\nimport { EventManager } from './events/index.ts'\nimport { runTransformers } from './transformers/index.ts'\nimport { createRenderer } from './render/createRenderer.ts'\nimport { createPlaintext } from './plaintext.ts'\nimport { stripForHtml, stripForPlaintext } from './utils/output-markers.ts'\nimport { normalizeComponentSources } from './utils/componentSources.ts'\nimport { _setCurrentTemplate } from './composables/useCurrentTemplate.ts'\nimport defu from 'defu'\nimport type { MaizzleConfig } from './types/index.ts'\n\nexport interface BuildResult {\n files: string[]\n config: MaizzleConfig\n}\n\n/**\n * Build all SFC email templates to HTML files.\n *\n * Creates a single Renderer instance, then loops through each template\n * calling render → transformers → write to disk.\n *\n * Pass a `Partial<MaizzleConfig>` to override config inline, or a string\n * to load config from a specific file path. Omit to load `maizzle.config`\n * from the working directory.\n */\nexport async function build(configInput?: Partial<MaizzleConfig> | string): Promise<BuildResult> {\n const start = Date.now()\n const spinner = ora({ text: 'Building templates...', spinner: 'circleHalves' }).start()\n\n const config = await resolveConfig(configInput)\n\n const events = new EventManager()\n events.registerConfig(config)\n await events.fireBeforeCreate({ config })\n\n const outputPath = resolve(config.output?.path ?? 'dist')\n const outputExtension = config.output?.extension ?? 'html'\n\n const contentPatterns = config.content ?? ['emails/**/*.vue']\n const contentBase = computeContentBase(contentPatterns)\n const templateFiles = await glob(contentPatterns)\n\n if (templateFiles.length === 0) {\n spinner.succeed('No templates found')\n return { files: [], config }\n }\n\n // Clear the output directory before writing fresh output\n if (existsSync(outputPath)) {\n rmSync(outputPath, { recursive: true, force: true })\n }\n\n const renderer = await createRenderer({ markdown: config.markdown, root: config.root, componentDirs: normalizeComponentSources(config.components?.source, process.cwd()), vite: config.vite })\n const outputFiles: string[] = []\n\n try {\n for (const templatePath of templateFiles) {\n const absolutePath = resolve(templatePath)\n const parsedPath = parsePath(absolutePath)\n const template = { source: readFileSync(absolutePath, 'utf-8'), path: parsedPath }\n\n _setCurrentTemplate(parsedPath)\n\n try {\n await events.fireBeforeRender({ config, template })\n\n const rendered = await renderer.render(absolutePath, config)\n\n /**\n * Register SFC event handlers collected during render so they take\n * part in the post-render events (afterRender / afterTransform).\n * They're cleared at the end of the iteration so they don't\n * leak into the next template.\n */\n for (const { name, handler } of rendered.sfcEventHandlers) {\n events.on(name, handler)\n }\n\n let html = await events.fireAfterRender({ config, template, html: rendered.html })\n\n /**\n * Use the per-template merged config (from defineConfig() in the SFC) so\n * that template-level overrides like css.safe: false are respected\n * by transformers.\n */\n const templateConfig = rendered.templateConfig\n\n const doctype = rendered.doctype ?? templateConfig.doctype ?? '<!DOCTYPE html>'\n\n if (templateConfig.useTransformers !== false) {\n html = await runTransformers(html, templateConfig, absolutePath, doctype, rendered.tailwindBlocks)\n }\n\n html = await events.fireAfterTransform({ config, template, html })\n if (doctype) html = `${doctype}\\n${html}`\n\n const htmlOut = stripForHtml(html)\n const sfcOutputPath = rendered.outputPath\n let outputFilePath: string\n\n if (sfcOutputPath) {\n const parsed = parsePath(resolve(sfcOutputPath))\n const ext = parsed.ext ? parsed.ext.slice(1) : outputExtension\n outputFilePath = join(parsed.dir, `${parsed.name}.${ext}`)\n } else {\n outputFilePath = resolveOutputPath(templatePath, outputPath, outputExtension, contentBase)\n }\n\n mkdirSync(dirname(outputFilePath), { recursive: true })\n writeFileSync(outputFilePath, htmlOut)\n outputFiles.push(outputFilePath)\n\n // Generate plaintext version if configured\n const globalPlaintext = templateConfig.plaintext\n const sfcPlaintext = rendered.plaintext\n\n if (globalPlaintext || sfcPlaintext) {\n const globalCfg = typeof globalPlaintext === 'object' ? globalPlaintext : {}\n const stripOptions = defu(sfcPlaintext?.options, globalCfg.options)\n const plaintext = createPlaintext(stripForPlaintext(html), stripOptions)\n const ptExtension = sfcPlaintext?.extension ?? globalCfg.extension ?? 'txt'\n\n let ptOutputPath: string\n\n if (sfcPlaintext?.destination) {\n const name = basename(templatePath).replace(/\\.(vue|md)$/, '')\n ptOutputPath = join(resolve(sfcPlaintext.destination), `${name}.${ptExtension}`)\n } else if (sfcOutputPath) {\n const parsed = parsePath(outputFilePath)\n ptOutputPath = join(parsed.dir, `${parsed.name}.${ptExtension}`)\n } else if (globalCfg.destination) {\n ptOutputPath = resolveOutputPath(templatePath, resolve(globalCfg.destination), ptExtension, contentBase)\n } else {\n ptOutputPath = resolveOutputPath(templatePath, outputPath, ptExtension, contentBase)\n }\n\n mkdirSync(dirname(ptOutputPath), { recursive: true })\n writeFileSync(ptOutputPath, plaintext)\n }\n } finally {\n _setCurrentTemplate(undefined)\n events.clearSfcHandlers()\n }\n }\n\n await copyStatic(config, outputPath)\n await events.fireAfterBuild({ files: outputFiles, config })\n } finally {\n await renderer.close()\n }\n\n const duration = ((Date.now() - start) / 1000).toFixed(2)\n const count = outputFiles.length\n spinner.stopAndPersist({\n symbol: '✅',\n text: `Built ${count} template${count !== 1 ? 's' : ''} in ${duration}s`,\n })\n\n return { files: outputFiles, config }\n}\n\n/**\n * Extract the static (non-glob) prefix from content patterns.\n *\n * For example, `['/abs/path/emails/**\\/*.vue']` → `'/abs/path/emails'`\n *\n * This is used to strip the content base from template paths\n * so the output preserves only the subdirectory structure.\n */\nfunction computeContentBase(patterns: string[]): string {\n // Use the first non-negated pattern\n const pattern = patterns.find(p => !p.startsWith('!')) ?? patterns[0]\n\n // Split on first glob character (* { ? [) and take the directory part\n const staticPart = pattern.split(/[*{?[]/)[0]\n\n // Ensure we have a clean directory path (not a partial segment)\n return resolve(staticPart.endsWith('/') ? staticPart : dirname(staticPart))\n}\n\nfunction resolveOutputPath(templatePath: string, outputDir: string, extension: string, contentBase: string): string {\n const name = basename(templatePath).replace(/\\.(vue|md)$/, '')\n const absTemplate = resolve(templatePath)\n const rel = relative(contentBase, dirname(absTemplate))\n\n return join(outputDir, rel, `${name}.${extension}`)\n}\n\nasync function copyStatic(config: MaizzleConfig, outputPath: string): Promise<void> {\n const sources = config.static?.source ?? ['public/**/*.*']\n const destination = config.static?.destination ?? 'public'\n\n const files = await glob(sources)\n\n for (const file of files) {\n const destPath = join(outputPath, destination, relative(dirname(sources[0]).replace(/\\*.*$/, ''), file))\n const destDir = dirname(destPath)\n\n if (!existsSync(destDir)) {\n mkdirSync(destDir, { recursive: true })\n }\n\n cpSync(file, destPath)\n }\n}\n"],"mappings":"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"}
@@ -22,7 +22,7 @@ const mergedClass = computed(() => twMerge('m-0', attrs.class as string))
22
22
  </script>
23
23
 
24
24
  <template>
25
- <component :is="tag" v-bind="$attrs" :class="mergedClass">
25
+ <component :is="tag" v-bind="{ ...$attrs, class: mergedClass }">
26
26
  <slot />
27
27
  </component>
28
28
  </template>
@@ -85,13 +85,27 @@ const props = defineProps({
85
85
  /**
86
86
  * Toggle Outlook (MSO) and VML fallback markup for this image.
87
87
  *
88
- * Only relevant in cropped mode (`aspect`). When `false`, the VML
89
- * `<v:rect>` shape is skipped and the modern padding-hack div renders
90
- * to all clients including Outlook (which will show an empty area).
88
+ * Inherits from an ancestor (e.g. a Layout calling
89
+ * `useOutlookFallback(false)`); an explicit value overrides. When
90
+ * `false`, the VML `<v:rect>` emitted in cropped mode (`aspect`)
91
+ * is skipped and the modern padding-hack div renders to all
92
+ * clients including Outlook (which will show an empty area).
91
93
  *
92
- * @default true
94
+ * @default inherits — root default `true`
93
95
  */
94
96
  outlookFallback: outlookFallbackProp,
97
+ /**
98
+ * URL to navigate to when the image is clicked.
99
+ *
100
+ * Modern clients: output is wrapped in `<a href>`. In cropped mode the
101
+ * anchor is `display:block` so the whole padding-hack area is clickable.
102
+ * Outlook: emitted as the `href` attribute on the `<v:rect>` (a
103
+ * documented VML Shape attribute).
104
+ */
105
+ href: {
106
+ type: String,
107
+ default: ''
108
+ },
95
109
  })
96
110
 
97
111
  const outlookFallback = useOutlookFallback(props.outlookFallback)
@@ -217,8 +231,10 @@ const vmlAspect = computed(() => {
217
231
  const VmlRect = () => {
218
232
  if (!isCropped.value || !heightPx.value || !Number.isFinite(imgWidth.value)) return null
219
233
  const aspectAttr = vmlAspect.value ? ` aspect="${vmlAspect.value}"` : ''
234
+ const altAttr = props.alt ? ` alt="${escapeAttr(props.alt)}"` : ''
235
+ const hrefAttr = props.href ? ` href="${escapeAttr(props.href)}"` : ''
220
236
  return createStaticVNode(
221
- `<!--[if mso]><v:rect xmlns:v="urn:schemas-microsoft-com:vml" fill="true" stroke="false" style="width:${imgWidth.value}px;height:${heightPx.value}px;"><v:fill type="frame" src="${escapeAttr(props.src)}"${aspectAttr} /></v:rect><![endif]-->`,
237
+ `<!--[if mso]><v:rect xmlns:v="urn:schemas-microsoft-com:vml" fill="true" stroke="false"${hrefAttr}${altAttr} style="width:${imgWidth.value}px;height:${heightPx.value}px;"><v:fill type="frame" src="${escapeAttr(props.src)}"${aspectAttr} /></v:rect><![endif]-->`,
222
238
  1
223
239
  )
224
240
  }
@@ -226,19 +242,43 @@ const VmlRect = () => {
226
242
  const NotMsoBefore = () => createStaticVNode('<!--[if !mso]><!-->', 1)
227
243
  const NotMsoAfter = () => createStaticVNode('<!--<![endif]-->', 1)
228
244
 
229
- const imgStyle = 'max-width: 100%; vertical-align: middle;'
245
+ const imgClass = 'max-w-full align-middle'
230
246
  </script>
231
247
 
232
248
  <template>
233
249
  <template v-if="isCropped">
234
250
  <VmlRect v-if="outlookFallback" />
235
251
  <NotMsoBefore v-if="outlookFallback" />
252
+ <a v-if="href" :href="href" class="block no-underline">
253
+ <div
254
+ v-bind="{ ...attrs, class: undefined }"
255
+ role="img"
256
+ :aria-label="alt || undefined"
257
+ :class="['overflow-hidden table max-w-full', parsedClass.className]"
258
+ :style="`width: ${imgWidth}px;`"
259
+ >
260
+ <div
261
+ :class="[
262
+ 'table-cell w-full h-0 bg-no-repeat',
263
+ darkSrc ? `dark:bg-[url('${escapeForClass(darkSrc)}')]!` : '',
264
+ motionSrc ? `motion-safe:bg-[url('${escapeForClass(motionSrc)}')]!` : '',
265
+ ]"
266
+ :style="{
267
+ paddingBottom: ratio!.paddingBottom,
268
+ backgroundImage: `url('${escapeForCssUrl(src)}')`,
269
+ backgroundSize: size,
270
+ backgroundPosition: position,
271
+ }"
272
+ />
273
+ </div>
274
+ </a>
236
275
  <div
276
+ v-else
237
277
  v-bind="{ ...attrs, class: undefined }"
238
278
  role="img"
239
279
  :aria-label="alt || undefined"
240
- :class="['overflow-hidden table', parsedClass.className]"
241
- :style="`width: ${imgWidth}px; max-width: 100%;`"
280
+ :class="['overflow-hidden table max-w-full', parsedClass.className]"
281
+ :style="`width: ${imgWidth}px;`"
242
282
  >
243
283
  <div
244
284
  :class="[
@@ -256,10 +296,20 @@ const imgStyle = 'max-width: 100%; vertical-align: middle;'
256
296
  </div>
257
297
  <NotMsoAfter v-if="outlookFallback" />
258
298
  </template>
299
+ <a v-else-if="href && usePicture" :href="href">
300
+ <picture>
301
+ <source v-if="darkSrc" :srcset="darkSrc" media="(prefers-color-scheme: dark)">
302
+ <source v-if="motionSrc" :srcset="motionSrc" :type="motionType || undefined" media="(prefers-reduced-motion: no-preference)">
303
+ <img v-bind="attrs" :src="src" :alt="alt" :width="imgWidth" :class="imgClass">
304
+ </picture>
305
+ </a>
259
306
  <picture v-else-if="usePicture">
260
307
  <source v-if="darkSrc" :srcset="darkSrc" media="(prefers-color-scheme: dark)">
261
308
  <source v-if="motionSrc" :srcset="motionSrc" :type="motionType || undefined" media="(prefers-reduced-motion: no-preference)">
262
- <img v-bind="attrs" :src="src" :alt="alt" :width="imgWidth" :style="imgStyle">
309
+ <img v-bind="attrs" :src="src" :alt="alt" :width="imgWidth" :class="imgClass">
263
310
  </picture>
264
- <img v-else v-bind="attrs" :src="src" :alt="alt" :width="imgWidth" :style="imgStyle">
311
+ <a v-else-if="href" :href="href">
312
+ <img v-bind="attrs" :src="src" :alt="alt" :width="imgWidth" :class="imgClass">
313
+ </a>
314
+ <img v-else v-bind="attrs" :src="src" :alt="alt" :width="imgWidth" :class="imgClass">
265
315
  </template>
@@ -20,7 +20,7 @@ const mergedClass = computed(() => twMerge('no-underline', attrs.class as string
20
20
  </script>
21
21
 
22
22
  <template>
23
- <a :href="href" v-bind="$attrs" :class="mergedClass">
23
+ <a :href="href" v-bind="{ ...$attrs, class: mergedClass }">
24
24
  <slot />
25
25
  </a>
26
26
  </template>
@@ -1,7 +1,9 @@
1
1
  <script lang="ts">
2
- import { createStaticVNode, type PropType } from 'vue'
2
+ import { createStaticVNode, inject, type PropType } from 'vue'
3
3
  import { createMarkdownExit, type MarkdownExitOptions } from 'markdown-exit'
4
4
  import { codeToHtml, type BundledTheme } from 'shiki'
5
+ import { defu } from 'defu'
6
+ import { MaizzleConfigKey } from '../composables/useConfig.ts'
5
7
 
6
8
  export default {
7
9
  props: {
@@ -15,17 +17,21 @@ export default {
15
17
  type: String,
16
18
  default: ''
17
19
  },
18
- /** Shiki theme for fenced code blocks. @default 'github-dark-high-contrast' */
20
+ /**
21
+ * Shiki theme for fenced code blocks. Falls back to
22
+ * `markdown.shikiTheme` from the config, then to
23
+ * `'github-dark-high-contrast'`.
24
+ */
19
25
  shikiTheme: {
20
26
  type: String as PropType<BundledTheme>,
21
- default: 'github-dark-high-contrast'
27
+ default: undefined
22
28
  },
23
29
  /** Wrap output in a div element. @default false */
24
30
  wrapper: {
25
31
  type: Boolean,
26
32
  default: false
27
33
  },
28
- /** markdown-exit configuration options. Merged with defaults. */
34
+ /** markdown-exit configuration options. Takes precedence over `markdown.markdownOptions` from the config. */
29
35
  config: {
30
36
  type: Object as PropType<MarkdownExitOptions>,
31
37
  default: () => ({})
@@ -48,19 +54,45 @@ export default {
48
54
  return () => createStaticVNode('', 0)
49
55
  }
50
56
 
51
- const md = createMarkdownExit({
52
- html: true,
53
- linkify: true,
54
- typographer: true,
55
- highlight: async (code, lang) => {
56
- try {
57
- return await codeToHtml(code, { lang, theme: props.shikiTheme })
58
- } catch {
59
- return ''
60
- }
57
+ /**
58
+ * Pull the global `markdown` config (when rendered inside a Maizzle
59
+ * build) so the component honors the same options and plugins as
60
+ * `.md` templates. Props override the config; both fall back to the
61
+ * component defaults. `inject` over `useConfig()` so the component
62
+ * still works standalone, when no config is provided.
63
+ */
64
+ const mdConfig = inject(MaizzleConfigKey, undefined)?.markdown ?? {}
65
+ const markdownOptions = mdConfig.markdownOptions ?? mdConfig.markdownItOptions
66
+ const markdownUses = mdConfig.markdownUses ?? mdConfig.markdownItUses
67
+ const markdownSetup = mdConfig.markdownSetup ?? mdConfig.markdownItSetup
68
+ const theme = props.shikiTheme ?? mdConfig.shikiTheme ?? 'github-dark-high-contrast'
69
+
70
+ const md = createMarkdownExit(defu(
71
+ props.config,
72
+ markdownOptions ?? {},
73
+ {
74
+ html: true,
75
+ linkify: true,
76
+ typographer: true,
77
+ highlight: async (code: string, lang: string) => {
78
+ try {
79
+ return await codeToHtml(code, { lang, theme })
80
+ } catch {
81
+ return ''
82
+ }
83
+ },
61
84
  },
62
- ...props.config,
63
- })
85
+ ))
86
+
87
+ /**
88
+ * Apply config plugins before overriding the fence rules, so the
89
+ * code-block wrapping below composes over whatever they emit.
90
+ */
91
+ for (const use of markdownUses ?? []) {
92
+ if (Array.isArray(use)) md.use(...use)
93
+ else md.use(use)
94
+ }
95
+ await markdownSetup?.(md)
64
96
 
65
97
  const wrapPre = (html: string) =>
66
98
  `<table class="w-full"><tr><td class="max-w-0 mso-padding-alt-4">${html}</td></tr></table>\n`
@@ -24,8 +24,10 @@ function vnodesToText(nodes: unknown): string {
24
24
  return ''
25
25
  }
26
26
 
27
- // Inbox preview budget. Pad with invisible fillers so the
28
- // client doesn't pull body content into the snippet.
27
+ /**
28
+ * Inbox preview budget. Pad with invisible fillers so the client
29
+ * doesn't pull body content into the snippet.
30
+ */
29
31
  const PREVIEW_LENGTH = 200
30
32
 
31
33
  const text = computed(() => vnodesToText(slots.default?.()))
@@ -27,8 +27,10 @@ export default defineComponent({
27
27
  if (!ctx.tailwindBlocks) ctx.tailwindBlocks = []
28
28
  const id = `tw${ctx.tailwindBlocks.length}`
29
29
 
30
- // Extract optional `#config` slot content as raw CSS. Evaluated at
31
- // setup time; the slot is NOT rendered into the document.
30
+ /**
31
+ * Extract optional `#config` slot content as raw CSS. Evaluated at setup
32
+ * time; the slot is NOT rendered into the document.
33
+ */
32
34
  const css = slots.config ? vnodeText(slots.config()).trim() : undefined
33
35
 
34
36
  ctx.tailwindBlocks.push({ id, css: css || undefined })
@@ -23,7 +23,7 @@ const mergedClass = computed(() => twMerge(defaultClass.value, attrs.class as st
23
23
  </script>
24
24
 
25
25
  <template>
26
- <component :is="props.as" v-bind="$attrs" :class="mergedClass">
26
+ <component :is="props.as" v-bind="{ ...$attrs, class: mergedClass }">
27
27
  <slot />
28
28
  </component>
29
29
  </template>