@maizzle/framework 6.0.14 → 6.1.1
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.
- package/dist/build.js +2 -1
- package/dist/build.js.map +1 -1
- package/dist/config/index.js.map +1 -1
- package/dist/plugins/postcss/flattenGradients.d.ts +14 -0
- package/dist/plugins/postcss/flattenGradients.d.ts.map +1 -0
- package/dist/plugins/postcss/flattenGradients.js +144 -0
- package/dist/plugins/postcss/flattenGradients.js.map +1 -0
- package/dist/plugins/postcss/mergeMediaQueries.js +2 -1
- package/dist/plugins/postcss/mergeMediaQueries.js.map +1 -1
- package/dist/plugins/postcss/resolveMaizzleImports.js.map +1 -1
- package/dist/render/buildTemplate.js +3 -1
- package/dist/render/buildTemplate.js.map +1 -1
- package/dist/render/createRenderer.js +8 -1
- package/dist/render/createRenderer.js.map +1 -1
- package/dist/serve.js +18 -9
- package/dist/serve.js.map +1 -1
- package/dist/server/compatibility.js +2 -1
- package/dist/server/compatibility.js.map +1 -1
- package/dist/server/linter.js +2 -1
- package/dist/server/linter.js.map +1 -1
- package/dist/server/sfc-utils.js.map +1 -1
- package/dist/server/ui/.vite/deps/@lucide_vue.js +6845 -6285
- package/dist/server/ui/.vite/deps/@lucide_vue.js.map +1 -1
- package/dist/server/ui/.vite/deps/@vueuse_core.js +2 -6
- package/dist/server/ui/.vite/deps/@vueuse_core.js.map +1 -1
- package/dist/server/ui/.vite/deps/@vueuse_shared.js +1 -1
- package/dist/server/ui/.vite/deps/@vueuse_shared.js.map +1 -1
- package/dist/server/ui/.vite/deps/_metadata.json +15 -15
- package/dist/server/ui/.vite/deps/culori.js +8 -6
- package/dist/server/ui/.vite/deps/culori.js.map +1 -1
- package/dist/server/ui/.vite/deps/reka-ui.js +63 -73
- package/dist/server/ui/.vite/deps/reka-ui.js.map +1 -1
- package/dist/server/ui/.vite/deps/tailwind-merge.js.map +1 -1
- package/dist/server/ui/.vite/deps/vue-router.js +5 -13
- package/dist/server/ui/.vite/deps/vue-router.js.map +1 -1
- package/dist/server/ui/.vite/deps/vue.js +1 -1
- package/dist/server/ui/.vite/deps/{vue.runtime.esm-bundler-BHD3r-bP.js → vue.runtime.esm-bundler-S54atuNJ.js} +323 -259
- package/dist/server/ui/.vite/deps/{vue.runtime.esm-bundler-BHD3r-bP.js.map → vue.runtime.esm-bundler-S54atuNJ.js.map} +1 -1
- package/dist/server/ui/App.vue +82 -36
- package/dist/server/ui/components/ui/command/Command.vue +4 -1
- package/dist/server/ui/components/ui/command/CommandInput.vue +3 -2
- package/dist/transformers/format.js +2 -1
- package/dist/transformers/format.js.map +1 -1
- package/dist/transformers/inlineCss.js +3 -2
- package/dist/transformers/inlineCss.js.map +1 -1
- package/dist/transformers/inlineLink.js +2 -1
- package/dist/transformers/inlineLink.js.map +1 -1
- package/dist/transformers/minify.js +2 -1
- package/dist/transformers/minify.js.map +1 -1
- package/dist/transformers/tailwindComponent.js +2 -1
- package/dist/transformers/tailwindComponent.js.map +1 -1
- package/dist/transformers/tailwindcss.d.ts.map +1 -1
- package/dist/transformers/tailwindcss.js +72 -2
- package/dist/transformers/tailwindcss.js.map +1 -1
- package/dist/transformers/urlQuery.js +2 -1
- package/dist/transformers/urlQuery.js.map +1 -1
- package/dist/utils/compileTailwindCss.d.ts +3 -2
- package/dist/utils/compileTailwindCss.d.ts.map +1 -1
- package/dist/utils/compileTailwindCss.js +5 -3
- package/dist/utils/compileTailwindCss.js.map +1 -1
- package/dist/utils/cssBox.js +2 -6
- package/dist/utils/cssBox.js.map +1 -1
- package/package.json +2 -2
package/dist/build.js
CHANGED
|
@@ -218,7 +218,8 @@ async function copyStatic(config, outputPath) {
|
|
|
218
218
|
const bases = sources.filter((s) => !s.startsWith("!")).map(staticBase);
|
|
219
219
|
for (const file of files) {
|
|
220
220
|
const abs = resolve(file);
|
|
221
|
-
const
|
|
221
|
+
const base = bases.filter((b) => abs === b || abs.startsWith(b + sep)).sort((a, b) => b.length - a.length)[0] ?? bases[0] ?? resolve(".");
|
|
222
|
+
const destPath = join(outputPath, destination, relative(base, abs));
|
|
222
223
|
const destDir = dirname(destPath);
|
|
223
224
|
if (!existsSync(destDir)) mkdirSync(destDir, { recursive: true });
|
|
224
225
|
cpSync(file, destPath);
|
package/dist/build.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.js","names":["parsePath"],"sources":["../src/build.ts"],"sourcesContent":["import { mkdirSync, cpSync, existsSync, rmSync } from 'node:fs'\nimport { resolve, dirname, relative, join, parse as parsePath, sep } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { availableParallelism } from 'node:os'\nimport { glob } from 'tinyglobby'\nimport ora from 'ora'\nimport { resolveConfig } from './config/index.ts'\nimport { EventManager } from './events/index.ts'\nimport { createRenderer } from './render/createRenderer.ts'\nimport { normalizeComponentSources } from './utils/componentSources.ts'\nimport { buildTemplate, computeContentBase } from './render/buildTemplate.ts'\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 try {\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. Guard against a\n // misconfigured output.path (e.g. '.', '', '../..') that resolves to the\n // project root or a parent of it — rmSync would wipe the whole project.\n const cwd = process.cwd()\n if (outputPath === parsePath(outputPath).root || outputPath === cwd || cwd.startsWith(outputPath + sep)) {\n throw new Error(`Refusing to clear output path \"${outputPath}\": it is the filesystem root, the current working directory, or a parent of it. Set output.path to a subdirectory like \"dist\".`)\n }\n\n if (existsSync(outputPath)) {\n rmSync(outputPath, { recursive: true, force: true })\n }\n\n const outputFiles: string[] = []\n let droppedAfterBuild = 0\n\n const parallel = resolveParallel(config, templateFiles.length, configInput)\n\n if (parallel.enabled) {\n spinner.text = `Building ${templateFiles.length} templates across ${parallel.workers} workers...`\n\n const result = await runParallelBuild({\n templateFiles,\n workers: parallel.workers,\n config,\n configInput,\n outputPath,\n outputExtension,\n contentBase,\n })\n\n outputFiles.push(...result.files)\n droppedAfterBuild = result.sfcAfterBuildCount\n\n await copyStatic(config, outputPath)\n await events.fireAfterBuild({ files: outputFiles, config })\n } else {\n const renderer = await createRenderer({ markdown: config.markdown, root: config.root, componentDirs: normalizeComponentSources(config.components?.source, process.cwd()), vite: config.vite, customElements: config.vue?.customElements })\n\n try {\n for (const templatePath of templateFiles) {\n const { files } = await buildTemplate(templatePath, { config, renderer, events, outputPath, outputExtension, contentBase })\n outputFiles.push(...files)\n }\n\n await copyStatic(config, outputPath)\n await events.fireAfterBuild({ files: outputFiles, config })\n } finally {\n await renderer.close()\n }\n }\n\n if (droppedAfterBuild > 0) {\n console.warn(`[maizzle] Skipped ${droppedAfterBuild} SFC-registered afterBuild handler(s): afterBuild can't run inside a parallel build worker. Move build-completion logic to the config's afterBuild hook.`)\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 } catch (err) {\n // Stop the spinner so a thrown error doesn't leave it spinning forever.\n if (spinner.isSpinning) spinner.fail('Build failed')\n throw err\n }\n}\n\n/**\n * Default template count above which parallel build turns on. Benchmarked\n * crossover (with the worker cap below) is ~25 templates; 50 leaves margin so\n * auto-parallel only kicks in where it's a reliable win across hardware.\n * Override per project with `parallel: { threshold }`.\n */\nconst DEFAULT_PARALLEL_THRESHOLD = 50\n\n/**\n * Default worker cap. Each worker runs a full Vite SSR renderer, so startup +\n * contention outweighs added parallelism past ~8 — benchmarks showed 8 beating\n * 12/16/23 at every size. Override with `parallel: { workers }`.\n */\nconst DEFAULT_MAX_WORKERS = 8\n\n/**\n * Decide whether to build in parallel and with how many workers.\n *\n * `config.parallel`:\n * - omitted → parallel when `count > 50`, min(CPU count − 1, 8) workers\n * - `true` → always parallel (ignores threshold), default workers\n * - `false` → always sequential\n * - `{ workers, threshold }` → parallel when `count > threshold` (default 50),\n * using `workers` threads (default min(CPU count − 1, 8))\n *\n * Workers reload the config file to recover function hooks, so parallel only\n * applies to file-based configs (a path or the default cwd config) — an inline\n * config object has no file to reload and always builds sequentially.\n */\nexport function resolveParallel(\n config: MaizzleConfig,\n count: number,\n configInput: Partial<MaizzleConfig> | string | undefined,\n): { enabled: boolean; workers: number } {\n const setting = config.parallel\n if (setting === false) return { enabled: false, workers: 0 }\n\n const fileBased = typeof configInput === 'string' || configInput == null\n if (!fileBased) return { enabled: false, workers: 0 }\n\n const cpus = availableParallelism()\n const defaultWorkers = Math.min(Math.max(1, cpus - 1), DEFAULT_MAX_WORKERS)\n\n let maxWorkers = defaultWorkers\n let threshold = DEFAULT_PARALLEL_THRESHOLD\n // `true` opts in regardless of count; object/omitted stay threshold-gated.\n const ignoreThreshold = setting === true\n\n if (typeof setting === 'object' && setting !== null) {\n if (typeof setting.workers === 'number' && setting.workers > 0) maxWorkers = Math.floor(setting.workers)\n if (typeof setting.threshold === 'number' && setting.threshold >= 0) threshold = Math.floor(setting.threshold)\n }\n\n if (!ignoreThreshold && count <= threshold) return { enabled: false, workers: 0 }\n\n const workers = Math.min(maxWorkers, count)\n return { enabled: workers >= 2 && count >= 2, workers }\n}\n\n/**\n * Run the build across worker threads. Each worker reloads the config (for its\n * function hooks), builds its batch via the same `buildTemplate` as the\n * sequential path, and returns the files it wrote. beforeCreate/afterBuild stay\n * on the main thread (handled by the caller).\n */\nasync function runParallelBuild(opts: {\n templateFiles: string[]\n workers: number\n config: MaizzleConfig\n configInput: Partial<MaizzleConfig> | string | undefined\n outputPath: string\n outputExtension: string\n contentBase: string\n}): Promise<{ files: string[]; sfcAfterBuildCount: number }> {\n const { templateFiles, workers, config, configInput, outputPath, outputExtension, contentBase } = opts\n\n const { default: Tinypool } = await import('tinypool')\n const workerPath = resolve(dirname(fileURLToPath(import.meta.url)), 'render/parallel/worker.mjs')\n\n const configPath = typeof configInput === 'string' ? configInput : undefined\n // Serializable snapshot of the post-beforeCreate config (functions dropped).\n const configData = JSON.parse(JSON.stringify(config)) as Partial<MaizzleConfig>\n\n const batches = shardEvenly(templateFiles, workers)\n\n const pool = new Tinypool({ filename: workerPath, minThreads: batches.length, maxThreads: batches.length })\n\n try {\n const results = await Promise.all(\n batches.map(templatePaths => pool.run({\n templatePaths,\n configPath,\n configData,\n outputPath,\n outputExtension,\n contentBase,\n })),\n )\n\n return {\n files: results.flatMap(r => r.files),\n sfcAfterBuildCount: results.reduce((n, r) => n + r.sfcAfterBuildCount, 0),\n }\n } finally {\n await pool.destroy()\n }\n}\n\n/** Round-robin items into up to `buckets` non-empty groups for even balance. */\nfunction shardEvenly<T>(items: T[], buckets: number): T[][] {\n const out: T[][] = Array.from({ length: buckets }, () => [])\n items.forEach((item, i) => out[i % buckets].push(item))\n return out.filter(b => b.length > 0)\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 // One glob call so negation patterns still apply across the whole set.\n const files = await glob(sources)\n\n // Absolute base dir to strip, per positive source pattern. Each file keeps\n // the structure under its own pattern's base — using only sources[0] sends\n // files from other roots to the wrong place (or escaping the output dir).\n const bases = sources.filter(s => !s.startsWith('!')).map(staticBase)\n\n for (const file of files) {\n const abs = resolve(file)\n const base = bases\n .filter(b => abs === b || abs.startsWith(b + sep))\n .sort((a, b) => b.length - a.length)[0] ?? bases[0] ?? resolve('.')\n\n const destPath = join(outputPath, destination, relative(base, abs))\n const destDir = dirname(destPath)\n\n if (!existsSync(destDir)) {\n mkdirSync(destDir, { recursive: true })\n }\n\n cpSync(file, destPath)\n }\n}\n\n/** Absolute static (non-glob) prefix of a source pattern, used as the strip base. */\nfunction staticBase(pattern: string): string {\n const staticPart = pattern.split(/[*{?[]/)[0]\n // Treat both separators as trailing: resolved patterns use '\\' on Windows.\n return resolve(/[/\\\\]$/.test(staticPart) ? staticPart : dirname(staticPart))\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"build.js","names":["parsePath"],"sources":["../src/build.ts"],"sourcesContent":["import { mkdirSync, cpSync, existsSync, rmSync } from 'node:fs'\nimport { resolve, dirname, relative, join, parse as parsePath, sep } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { availableParallelism } from 'node:os'\nimport { glob } from 'tinyglobby'\nimport ora from 'ora'\nimport { resolveConfig } from './config/index.ts'\nimport { EventManager } from './events/index.ts'\nimport { createRenderer } from './render/createRenderer.ts'\nimport { normalizeComponentSources } from './utils/componentSources.ts'\nimport { buildTemplate, computeContentBase } from './render/buildTemplate.ts'\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 try {\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. Guard against a\n // misconfigured output.path (e.g. '.', '', '../..') that resolves to the\n // project root or a parent of it — rmSync would wipe the whole project.\n const cwd = process.cwd()\n if (outputPath === parsePath(outputPath).root || outputPath === cwd || cwd.startsWith(outputPath + sep)) {\n throw new Error(`Refusing to clear output path \"${outputPath}\": it is the filesystem root, the current working directory, or a parent of it. Set output.path to a subdirectory like \"dist\".`)\n }\n\n if (existsSync(outputPath)) {\n rmSync(outputPath, { recursive: true, force: true })\n }\n\n const outputFiles: string[] = []\n let droppedAfterBuild = 0\n\n const parallel = resolveParallel(config, templateFiles.length, configInput)\n\n if (parallel.enabled) {\n spinner.text = `Building ${templateFiles.length} templates across ${parallel.workers} workers...`\n\n const result = await runParallelBuild({\n templateFiles,\n workers: parallel.workers,\n config,\n configInput,\n outputPath,\n outputExtension,\n contentBase,\n })\n\n outputFiles.push(...result.files)\n droppedAfterBuild = result.sfcAfterBuildCount\n\n await copyStatic(config, outputPath)\n await events.fireAfterBuild({ files: outputFiles, config })\n } else {\n const renderer = await createRenderer({ markdown: config.markdown, root: config.root, componentDirs: normalizeComponentSources(config.components?.source, process.cwd()), vite: config.vite, customElements: config.vue?.customElements })\n\n try {\n for (const templatePath of templateFiles) {\n const { files } = await buildTemplate(templatePath, { config, renderer, events, outputPath, outputExtension, contentBase })\n outputFiles.push(...files)\n }\n\n await copyStatic(config, outputPath)\n await events.fireAfterBuild({ files: outputFiles, config })\n } finally {\n await renderer.close()\n }\n }\n\n if (droppedAfterBuild > 0) {\n console.warn(`[maizzle] Skipped ${droppedAfterBuild} SFC-registered afterBuild handler(s): afterBuild can't run inside a parallel build worker. Move build-completion logic to the config's afterBuild hook.`)\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 } catch (err) {\n // Stop the spinner so a thrown error doesn't leave it spinning forever.\n if (spinner.isSpinning) spinner.fail('Build failed')\n throw err\n }\n}\n\n/**\n * Default template count above which parallel build turns on. Benchmarked\n * crossover (with the worker cap below) is ~25 templates; 50 leaves margin so\n * auto-parallel only kicks in where it's a reliable win across hardware.\n * Override per project with `parallel: { threshold }`.\n */\nconst DEFAULT_PARALLEL_THRESHOLD = 50\n\n/**\n * Default worker cap. Each worker runs a full Vite SSR renderer, so startup +\n * contention outweighs added parallelism past ~8 — benchmarks showed 8 beating\n * 12/16/23 at every size. Override with `parallel: { workers }`.\n */\nconst DEFAULT_MAX_WORKERS = 8\n\n/**\n * Decide whether to build in parallel and with how many workers.\n *\n * `config.parallel`:\n * - omitted → parallel when `count > 50`, min(CPU count − 1, 8) workers\n * - `true` → always parallel (ignores threshold), default workers\n * - `false` → always sequential\n * - `{ workers, threshold }` → parallel when `count > threshold` (default 50),\n * using `workers` threads (default min(CPU count − 1, 8))\n *\n * Workers reload the config file to recover function hooks, so parallel only\n * applies to file-based configs (a path or the default cwd config) — an inline\n * config object has no file to reload and always builds sequentially.\n */\nexport function resolveParallel(\n config: MaizzleConfig,\n count: number,\n configInput: Partial<MaizzleConfig> | string | undefined,\n): { enabled: boolean; workers: number } {\n const setting = config.parallel\n if (setting === false) return { enabled: false, workers: 0 }\n\n const fileBased = typeof configInput === 'string' || configInput == null\n if (!fileBased) return { enabled: false, workers: 0 }\n\n const cpus = availableParallelism()\n const defaultWorkers = Math.min(Math.max(1, cpus - 1), DEFAULT_MAX_WORKERS)\n\n let maxWorkers = defaultWorkers\n let threshold = DEFAULT_PARALLEL_THRESHOLD\n // `true` opts in regardless of count; object/omitted stay threshold-gated.\n const ignoreThreshold = setting === true\n\n if (typeof setting === 'object' && setting !== null) {\n if (typeof setting.workers === 'number' && setting.workers > 0) maxWorkers = Math.floor(setting.workers)\n if (typeof setting.threshold === 'number' && setting.threshold >= 0) threshold = Math.floor(setting.threshold)\n }\n\n if (!ignoreThreshold && count <= threshold) return { enabled: false, workers: 0 }\n\n const workers = Math.min(maxWorkers, count)\n return { enabled: workers >= 2 && count >= 2, workers }\n}\n\n/**\n * Run the build across worker threads. Each worker reloads the config (for its\n * function hooks), builds its batch via the same `buildTemplate` as the\n * sequential path, and returns the files it wrote. beforeCreate/afterBuild stay\n * on the main thread (handled by the caller).\n */\nasync function runParallelBuild(opts: {\n templateFiles: string[]\n workers: number\n config: MaizzleConfig\n configInput: Partial<MaizzleConfig> | string | undefined\n outputPath: string\n outputExtension: string\n contentBase: string\n}): Promise<{ files: string[]; sfcAfterBuildCount: number }> {\n const { templateFiles, workers, config, configInput, outputPath, outputExtension, contentBase } = opts\n\n const { default: Tinypool } = await import('tinypool')\n const workerPath = resolve(dirname(fileURLToPath(import.meta.url)), 'render/parallel/worker.mjs')\n\n const configPath = typeof configInput === 'string' ? configInput : undefined\n // Serializable snapshot of the post-beforeCreate config (functions dropped).\n const configData = JSON.parse(JSON.stringify(config)) as Partial<MaizzleConfig>\n\n const batches = shardEvenly(templateFiles, workers)\n\n const pool = new Tinypool({ filename: workerPath, minThreads: batches.length, maxThreads: batches.length })\n\n try {\n const results = await Promise.all(\n batches.map(templatePaths => pool.run({\n templatePaths,\n configPath,\n configData,\n outputPath,\n outputExtension,\n contentBase,\n })),\n )\n\n return {\n files: results.flatMap(r => r.files),\n sfcAfterBuildCount: results.reduce((n, r) => n + r.sfcAfterBuildCount, 0),\n }\n } finally {\n await pool.destroy()\n }\n}\n\n/** Round-robin items into up to `buckets` non-empty groups for even balance. */\nfunction shardEvenly<T>(items: T[], buckets: number): T[][] {\n const out: T[][] = Array.from({ length: buckets }, () => [])\n items.forEach((item, i) => out[i % buckets].push(item))\n return out.filter(b => b.length > 0)\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 // One glob call so negation patterns still apply across the whole set.\n const files = await glob(sources)\n\n // Absolute base dir to strip, per positive source pattern. Each file keeps\n // the structure under its own pattern's base — using only sources[0] sends\n // files from other roots to the wrong place (or escaping the output dir).\n const bases = sources.filter(s => !s.startsWith('!')).map(staticBase)\n\n for (const file of files) {\n const abs = resolve(file)\n const base = bases\n .filter(b => abs === b || abs.startsWith(b + sep))\n .sort((a, b) => b.length - a.length)[0] ?? bases[0] ?? resolve('.')\n\n const destPath = join(outputPath, destination, relative(base, abs))\n const destDir = dirname(destPath)\n\n if (!existsSync(destDir)) {\n mkdirSync(destDir, { recursive: true })\n }\n\n cpSync(file, destPath)\n }\n}\n\n/** Absolute static (non-glob) prefix of a source pattern, used as the strip base. */\nfunction staticBase(pattern: string): string {\n const staticPart = pattern.split(/[*{?[]/)[0]\n // Treat both separators as trailing: resolved patterns use '\\' on Windows.\n return resolve(/[/\\\\]$/.test(staticPart) ? staticPart : dirname(staticPart))\n}\n"],"mappings":"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"}
|
package/dist/config/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/config/index.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs'\nimport { resolve, dirname, extname, join } from 'pathe'\nimport { createJiti } from 'jiti'\nimport { fileURLToPath, pathToFileURL } from 'node:url'\nimport { createDefu } from 'defu'\n\n// defu that replaces arrays: if user provides content: ['x'], it replaces the default, not appends\nconst merge = createDefu((obj, key, value) => {\n if (Array.isArray(obj[key])) {\n obj[key] = value\n return true\n }\n})\nimport { defaults } from './defaults.ts'\nimport type { MaizzleConfig } from '../types/index.ts'\n\nexport { defineConfig } from '../composables/defineConfig.ts'\nexport { defaults } from './defaults.ts'\n\nconst CONFIG_FILES = [\n 'maizzle.config.ts',\n 'maizzle.config.js',\n]\n\n/**\n * Resolve the Maizzle config.\n *\n * Always loads from the config file on disk (maizzle.config.{ts,js}),\n * then merges the programmatic config on top, then fills in defaults.\n */\nexport async function resolveConfig(\n config?: Partial<MaizzleConfig> | string,\n cwd: string = process.cwd(),\n): Promise<MaizzleConfig> {\n // If a string path was provided, load that specific file\n const fileConfig = await loadConfig(\n typeof config === 'string' ? config : undefined,\n cwd,\n )\n\n // Programmatic config (object) overrides file config, which overrides defaults\n const programmaticConfig = typeof config === 'object' && config !== null ? config : {}\n\n return normalizeConfig(programmaticConfig, fileConfig, cwd)\n}\n\n/**\n * Resolve config from a programmatic object only — never touches disk.\n *\n * `render()` uses this so a stray `maizzle.config.{ts,js}` in `cwd` can't\n * silently alter output: the config you pass is the config you get, layered\n * over defaults. Callers who want the project's file config load it\n * explicitly via {@link resolveConfig} and pass the result in.\n */\nexport function resolveConfigObject(\n config?: Partial<MaizzleConfig>,\n cwd: string = process.cwd(),\n): MaizzleConfig {\n const programmaticConfig = config && typeof config === 'object' ? config : {}\n return normalizeConfig(programmaticConfig, {}, cwd)\n}\n\n/**\n * Merge programmatic config over file config over defaults, then resolve\n * filesystem paths (root, content, static, components) relative to cwd/root.\n */\nfunction normalizeConfig(\n programmaticConfig: Partial<MaizzleConfig>,\n fileConfig: Partial<MaizzleConfig>,\n cwd: string,\n): MaizzleConfig {\n const merged = merge(programmaticConfig, fileConfig, defaults) as MaizzleConfig\n\n // Check if root was explicitly provided before resolving\n const hasExplicitRoot = !!(programmaticConfig.root ?? fileConfig.root)\n\n // Resolve root to an absolute path (defaults to cwd)\n const root = resolve(cwd, merged.root ?? '.')\n merged.root = root\n\n // Resolve content patterns relative to root\n if (merged.content) {\n merged.content = merged.content.map(p => {\n // Skip already-absolute or negated patterns\n if (p.startsWith('/') || p.startsWith('!')) return p\n return resolve(root, p)\n })\n }\n\n // Resolve static source patterns relative to root\n if (merged.static?.source) {\n merged.static.source = merged.static.source.map(p => {\n if (p.startsWith('/') || p.startsWith('!')) return p\n return resolve(root, p)\n })\n }\n\n /**\n * Resolve components.source paths relative to cwd (not root), since extra\n * component dirs often live outside the root directory. String\n * entries → resolve in-place. Object entries → resolve\n * `path`, preserve `prefix`/`pathPrefix`.\n */\n if (merged.components?.source) {\n const dirs = Array.isArray(merged.components.source)\n ? merged.components.source\n : [merged.components.source]\n\n merged.components.source = dirs.map(entry => {\n if (typeof entry === 'string') {\n return entry.startsWith('/') ? entry : resolve(cwd, entry)\n }\n return {\n ...entry,\n path: entry.path.startsWith('/') ? entry.path : resolve(cwd, entry.path),\n }\n })\n }\n\n /**\n * Default css.base to root when root is explicitly set, so Tailwind resolves\n * @source from the right directory. When root is not set, leave\n * css.base undefined so Tailwind uses its own default (the\n * template file's directory).\n */\n if (hasExplicitRoot && !merged.css?.base) {\n if (!merged.css) merged.css = {}\n merged.css.base = root\n }\n\n return merged\n}\n\n/**\n * Whether Node can natively `import()` this file as ESM — true for `.mjs`\n * and for `.js` whose nearest package.json declares `\"type\": \"module\"`.\n * jiti hands such files to Node's native loader, which caches them in the\n * (unbustable) module registry, so we import them ourselves with a fresh\n * query instead. Everything else (CJS, TS) goes through jiti, which already\n * re-evaluates per instance.\n */\nexport function isNativeESM(absolutePath: string): boolean {\n const ext = extname(absolutePath)\n if (ext === '.mjs') return true\n if (ext === '.cjs' || ext === '.ts' || ext === '.mts' || ext === '.cts') return false\n\n let dir = dirname(absolutePath)\n for (;;) {\n const pkgPath = join(dir, 'package.json')\n if (existsSync(pkgPath)) {\n try {\n return JSON.parse(readFileSync(pkgPath, 'utf-8')).type === 'module'\n } catch {\n return false\n }\n }\n const parent = dirname(dir)\n if (parent === dir) return false\n dir = parent\n }\n}\n\n/**\n * Monotonic nonce appended to native-import URLs so each load is fresh even\n * when two reloads land in the same millisecond.\n */\nlet configLoadNonce = 0\n\nasync function importConfig(absolutePath: string, jiti: ReturnType<typeof createJiti>): Promise<MaizzleConfig> {\n if (isNativeESM(absolutePath)) {\n const url = `${pathToFileURL(absolutePath).href}?t=${Date.now()}-${configLoadNonce++}`\n const mod = await import(url) as any\n return mod.default ?? mod\n }\n\n const mod = await jiti.import(absolutePath) as any\n return mod.default ?? mod\n}\n\nasync function loadConfig(\n configPath?: string,\n cwd: string = process.cwd(),\n): Promise<MaizzleConfig> {\n const jiti = createJiti(fileURLToPath(import.meta.url), { moduleCache: false })\n\n // If an explicit path was provided, use it directly\n if (configPath) {\n const absolutePath = resolve(cwd, configPath)\n\n if (!existsSync(absolutePath)) {\n throw new Error(`Config file not found: ${absolutePath}`)\n }\n\n return importConfig(absolutePath, jiti)\n }\n\n // Otherwise scan cwd for known config file names\n for (const filename of CONFIG_FILES) {\n const filepath = resolve(cwd, filename)\n\n if (existsSync(filepath)) {\n return importConfig(filepath, jiti)\n }\n }\n\n // No config file found, return empty (defaults will be applied by resolveConfig)\n return {}\n}\n"],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/config/index.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs'\nimport { resolve, dirname, extname, join } from 'pathe'\nimport { createJiti } from 'jiti'\nimport { fileURLToPath, pathToFileURL } from 'node:url'\nimport { createDefu } from 'defu'\n\n// defu that replaces arrays: if user provides content: ['x'], it replaces the default, not appends\nconst merge = createDefu((obj, key, value) => {\n if (Array.isArray(obj[key])) {\n obj[key] = value\n return true\n }\n})\nimport { defaults } from './defaults.ts'\nimport type { MaizzleConfig } from '../types/index.ts'\n\nexport { defineConfig } from '../composables/defineConfig.ts'\nexport { defaults } from './defaults.ts'\n\nconst CONFIG_FILES = [\n 'maizzle.config.ts',\n 'maizzle.config.js',\n]\n\n/**\n * Resolve the Maizzle config.\n *\n * Always loads from the config file on disk (maizzle.config.{ts,js}),\n * then merges the programmatic config on top, then fills in defaults.\n */\nexport async function resolveConfig(\n config?: Partial<MaizzleConfig> | string,\n cwd: string = process.cwd(),\n): Promise<MaizzleConfig> {\n // If a string path was provided, load that specific file\n const fileConfig = await loadConfig(\n typeof config === 'string' ? config : undefined,\n cwd,\n )\n\n // Programmatic config (object) overrides file config, which overrides defaults\n const programmaticConfig = typeof config === 'object' && config !== null ? config : {}\n\n return normalizeConfig(programmaticConfig, fileConfig, cwd)\n}\n\n/**\n * Resolve config from a programmatic object only — never touches disk.\n *\n * `render()` uses this so a stray `maizzle.config.{ts,js}` in `cwd` can't\n * silently alter output: the config you pass is the config you get, layered\n * over defaults. Callers who want the project's file config load it\n * explicitly via {@link resolveConfig} and pass the result in.\n */\nexport function resolveConfigObject(\n config?: Partial<MaizzleConfig>,\n cwd: string = process.cwd(),\n): MaizzleConfig {\n const programmaticConfig = config && typeof config === 'object' ? config : {}\n return normalizeConfig(programmaticConfig, {}, cwd)\n}\n\n/**\n * Merge programmatic config over file config over defaults, then resolve\n * filesystem paths (root, content, static, components) relative to cwd/root.\n */\nfunction normalizeConfig(\n programmaticConfig: Partial<MaizzleConfig>,\n fileConfig: Partial<MaizzleConfig>,\n cwd: string,\n): MaizzleConfig {\n const merged = merge(programmaticConfig, fileConfig, defaults) as MaizzleConfig\n\n // Check if root was explicitly provided before resolving\n const hasExplicitRoot = !!(programmaticConfig.root ?? fileConfig.root)\n\n // Resolve root to an absolute path (defaults to cwd)\n const root = resolve(cwd, merged.root ?? '.')\n merged.root = root\n\n // Resolve content patterns relative to root\n if (merged.content) {\n merged.content = merged.content.map(p => {\n // Skip already-absolute or negated patterns\n if (p.startsWith('/') || p.startsWith('!')) return p\n return resolve(root, p)\n })\n }\n\n // Resolve static source patterns relative to root\n if (merged.static?.source) {\n merged.static.source = merged.static.source.map(p => {\n if (p.startsWith('/') || p.startsWith('!')) return p\n return resolve(root, p)\n })\n }\n\n /**\n * Resolve components.source paths relative to cwd (not root), since extra\n * component dirs often live outside the root directory. String\n * entries → resolve in-place. Object entries → resolve\n * `path`, preserve `prefix`/`pathPrefix`.\n */\n if (merged.components?.source) {\n const dirs = Array.isArray(merged.components.source)\n ? merged.components.source\n : [merged.components.source]\n\n merged.components.source = dirs.map(entry => {\n if (typeof entry === 'string') {\n return entry.startsWith('/') ? entry : resolve(cwd, entry)\n }\n return {\n ...entry,\n path: entry.path.startsWith('/') ? entry.path : resolve(cwd, entry.path),\n }\n })\n }\n\n /**\n * Default css.base to root when root is explicitly set, so Tailwind resolves\n * @source from the right directory. When root is not set, leave\n * css.base undefined so Tailwind uses its own default (the\n * template file's directory).\n */\n if (hasExplicitRoot && !merged.css?.base) {\n if (!merged.css) merged.css = {}\n merged.css.base = root\n }\n\n return merged\n}\n\n/**\n * Whether Node can natively `import()` this file as ESM — true for `.mjs`\n * and for `.js` whose nearest package.json declares `\"type\": \"module\"`.\n * jiti hands such files to Node's native loader, which caches them in the\n * (unbustable) module registry, so we import them ourselves with a fresh\n * query instead. Everything else (CJS, TS) goes through jiti, which already\n * re-evaluates per instance.\n */\nexport function isNativeESM(absolutePath: string): boolean {\n const ext = extname(absolutePath)\n if (ext === '.mjs') return true\n if (ext === '.cjs' || ext === '.ts' || ext === '.mts' || ext === '.cts') return false\n\n let dir = dirname(absolutePath)\n for (;;) {\n const pkgPath = join(dir, 'package.json')\n if (existsSync(pkgPath)) {\n try {\n return JSON.parse(readFileSync(pkgPath, 'utf-8')).type === 'module'\n } catch {\n return false\n }\n }\n const parent = dirname(dir)\n if (parent === dir) return false\n dir = parent\n }\n}\n\n/**\n * Monotonic nonce appended to native-import URLs so each load is fresh even\n * when two reloads land in the same millisecond.\n */\nlet configLoadNonce = 0\n\nasync function importConfig(absolutePath: string, jiti: ReturnType<typeof createJiti>): Promise<MaizzleConfig> {\n if (isNativeESM(absolutePath)) {\n const url = `${pathToFileURL(absolutePath).href}?t=${Date.now()}-${configLoadNonce++}`\n const mod = await import(url) as any\n return mod.default ?? mod\n }\n\n const mod = await jiti.import(absolutePath) as any\n return mod.default ?? mod\n}\n\nasync function loadConfig(\n configPath?: string,\n cwd: string = process.cwd(),\n): Promise<MaizzleConfig> {\n const jiti = createJiti(fileURLToPath(import.meta.url), { moduleCache: false })\n\n // If an explicit path was provided, use it directly\n if (configPath) {\n const absolutePath = resolve(cwd, configPath)\n\n if (!existsSync(absolutePath)) {\n throw new Error(`Config file not found: ${absolutePath}`)\n }\n\n return importConfig(absolutePath, jiti)\n }\n\n // Otherwise scan cwd for known config file names\n for (const filename of CONFIG_FILES) {\n const filepath = resolve(cwd, filename)\n\n if (existsSync(filepath)) {\n return importConfig(filepath, jiti)\n }\n }\n\n // No config file found, return empty (defaults will be applied by resolveConfig)\n return {}\n}\n"],"mappings":";;;;;;;;AAOA,MAAM,QAAQ,YAAY,KAAK,KAAK,UAAU;CAC5C,IAAI,MAAM,QAAQ,IAAI,IAAI,GAAG;EAC3B,IAAI,OAAO;EACX,OAAO;CACT;AACF,CAAC;AAOD,MAAM,eAAe,CACnB,qBACA,mBACF;;;;;;;AAQA,eAAsB,cACpB,QACA,MAAc,QAAQ,IAAI,GACF;CAExB,MAAM,aAAa,MAAM,WACvB,OAAO,WAAW,WAAW,SAAS,KAAA,GACtC,GACF;CAKA,OAAO,gBAFoB,OAAO,WAAW,YAAY,WAAW,OAAO,SAAS,CAAC,GAE1C,YAAY,GAAG;AAC5D;;;;;;;;;AAUA,SAAgB,oBACd,QACA,MAAc,QAAQ,IAAI,GACX;CAEf,OAAO,gBADoB,UAAU,OAAO,WAAW,WAAW,SAAS,CAAC,GACjC,CAAC,GAAG,GAAG;AACpD;;;;;AAMA,SAAS,gBACP,oBACA,YACA,KACe;CACf,MAAM,SAAS,MAAM,oBAAoB,YAAY,QAAQ;CAG7D,MAAM,kBAAkB,CAAC,EAAE,mBAAmB,QAAQ,WAAW;CAGjE,MAAM,OAAO,QAAQ,KAAK,OAAO,QAAQ,GAAG;CAC5C,OAAO,OAAO;CAGd,IAAI,OAAO,SACT,OAAO,UAAU,OAAO,QAAQ,KAAI,MAAK;EAEvC,IAAI,EAAE,WAAW,GAAG,KAAK,EAAE,WAAW,GAAG,GAAG,OAAO;EACnD,OAAO,QAAQ,MAAM,CAAC;CACxB,CAAC;CAIH,IAAI,OAAO,QAAQ,QACjB,OAAO,OAAO,SAAS,OAAO,OAAO,OAAO,KAAI,MAAK;EACnD,IAAI,EAAE,WAAW,GAAG,KAAK,EAAE,WAAW,GAAG,GAAG,OAAO;EACnD,OAAO,QAAQ,MAAM,CAAC;CACxB,CAAC;;;;;;;CASH,IAAI,OAAO,YAAY,QAAQ;EAC7B,MAAM,OAAO,MAAM,QAAQ,OAAO,WAAW,MAAM,IAC/C,OAAO,WAAW,SAClB,CAAC,OAAO,WAAW,MAAM;EAE7B,OAAO,WAAW,SAAS,KAAK,KAAI,UAAS;GAC3C,IAAI,OAAO,UAAU,UACnB,OAAO,MAAM,WAAW,GAAG,IAAI,QAAQ,QAAQ,KAAK,KAAK;GAE3D,OAAO;IACL,GAAG;IACH,MAAM,MAAM,KAAK,WAAW,GAAG,IAAI,MAAM,OAAO,QAAQ,KAAK,MAAM,IAAI;GACzE;EACF,CAAC;CACH;;;;;;;CAQA,IAAI,mBAAmB,CAAC,OAAO,KAAK,MAAM;EACxC,IAAI,CAAC,OAAO,KAAK,OAAO,MAAM,CAAC;EAC/B,OAAO,IAAI,OAAO;CACpB;CAEA,OAAO;AACT;;;;;;;;;AAUA,SAAgB,YAAY,cAA+B;CACzD,MAAM,MAAM,QAAQ,YAAY;CAChC,IAAI,QAAQ,QAAQ,OAAO;CAC3B,IAAI,QAAQ,UAAU,QAAQ,SAAS,QAAQ,UAAU,QAAQ,QAAQ,OAAO;CAEhF,IAAI,MAAM,QAAQ,YAAY;CAC9B,SAAS;EACP,MAAM,UAAU,KAAK,KAAK,cAAc;EACxC,IAAI,WAAW,OAAO,GACpB,IAAI;GACF,OAAO,KAAK,MAAM,aAAa,SAAS,OAAO,CAAC,CAAC,CAAC,SAAS;EAC7D,QAAQ;GACN,OAAO;EACT;EAEF,MAAM,SAAS,QAAQ,GAAG;EAC1B,IAAI,WAAW,KAAK,OAAO;EAC3B,MAAM;CACR;AACF;;;;;AAMA,IAAI,kBAAkB;AAEtB,eAAe,aAAa,cAAsB,MAA6D;CAC7G,IAAI,YAAY,YAAY,GAAG;EAE7B,MAAM,MAAM,MAAM,OAAO,GADV,cAAc,YAAY,CAAC,CAAC,KAAK,KAAK,KAAK,IAAI,EAAE,GAAG;EAEnE,OAAO,IAAI,WAAW;CACxB;CAEA,MAAM,MAAM,MAAM,KAAK,OAAO,YAAY;CAC1C,OAAO,IAAI,WAAW;AACxB;AAEA,eAAe,WACb,YACA,MAAc,QAAQ,IAAI,GACF;CACxB,MAAM,OAAO,WAAW,cAAc,YAAY,GAAG,GAAG,EAAE,aAAa,MAAM,CAAC;CAG9E,IAAI,YAAY;EACd,MAAM,eAAe,QAAQ,KAAK,UAAU;EAE5C,IAAI,CAAC,WAAW,YAAY,GAC1B,MAAM,IAAI,MAAM,0BAA0B,cAAc;EAG1D,OAAO,aAAa,cAAc,IAAI;CACxC;CAGA,KAAK,MAAM,YAAY,cAAc;EACnC,MAAM,WAAW,QAAQ,KAAK,QAAQ;EAEtC,IAAI,WAAW,QAAQ,GACrB,OAAO,aAAa,UAAU,IAAI;CAEtC;CAGA,OAAO,CAAC;AACV"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Plugin } from "postcss";
|
|
2
|
+
//#region src/plugins/postcss/flattenGradients.d.ts
|
|
3
|
+
/** A gradient combo found on a DOM element. */
|
|
4
|
+
interface GradientCombo {
|
|
5
|
+
/** Generated class name to emit the flat rule for. */
|
|
6
|
+
className: string;
|
|
7
|
+
/** The gradient utility class tokens present on the element. */
|
|
8
|
+
classes: string[];
|
|
9
|
+
}
|
|
10
|
+
declare const _default: (combos?: GradientCombo[]) => Plugin;
|
|
11
|
+
declare const postcss = true;
|
|
12
|
+
//#endregion
|
|
13
|
+
export { GradientCombo, _default as default, postcss };
|
|
14
|
+
//# sourceMappingURL=flattenGradients.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flattenGradients.d.ts","names":[],"sources":["../../../src/plugins/postcss/flattenGradients.ts"],"mappings":";;;UA6BiB;;EAEf;;EAEA;;cAuCc,WAAA,SAAQ,oBAAuB;cA4HlC"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { Rule } from "postcss";
|
|
2
|
+
//#region src/plugins/postcss/flattenGradients.ts
|
|
3
|
+
/**
|
|
4
|
+
* postcss-flatten-gradients
|
|
5
|
+
*
|
|
6
|
+
* Tailwind v4 renders gradients as CSS-variable machinery split across
|
|
7
|
+
* separate utility rules (`bg-linear-*`, `from-*`, `via-*`, `to-*`),
|
|
8
|
+
* combining only on the element that carries every class. Email clients
|
|
9
|
+
* don't support `var()` and Maizzle inlines styles, so the variables
|
|
10
|
+
* never resolve and the gradient renders nothing.
|
|
11
|
+
*
|
|
12
|
+
* Given the per-element gradient combos (computed from the DOM by the
|
|
13
|
+
* tailwindcss transformer), this plugin reads the `--tw-gradient-*`
|
|
14
|
+
* values off the utility rules and emits a single flat rule per combo:
|
|
15
|
+
*
|
|
16
|
+
* .bg-linear-gradient-to-bl-from-indigo-50-to-indigo-600 {
|
|
17
|
+
* background-image: linear-gradient(to bottom left,
|
|
18
|
+
* var(--color-indigo-50), var(--color-indigo-600));
|
|
19
|
+
* }
|
|
20
|
+
*
|
|
21
|
+
* Colors stay as `var(--color-*)` (or literals) so the downstream
|
|
22
|
+
* resolveProps + lightningcss steps convert them to hex, exactly like
|
|
23
|
+
* every other Tailwind color. The dead utility rules are then removed.
|
|
24
|
+
*/
|
|
25
|
+
const PLUGIN_NAME = "postcss-flatten-gradients";
|
|
26
|
+
const GRADIENT_UTILITY_RE = /^(from|via|to)-|^bg-(linear|radial|conic)\b/;
|
|
27
|
+
const GENERATED_RE = /^bg-(linear|radial|conic)-gradient-/;
|
|
28
|
+
/**
|
|
29
|
+
* Color-interpolation method Tailwind appends to the position (e.g.
|
|
30
|
+
* `in oklab`, `in oklch longer hue`). It always trails the direction and
|
|
31
|
+
* is unsupported in email, so strip from `in <space>` to the end.
|
|
32
|
+
*/
|
|
33
|
+
const INTERP_RE = /(?:^|\s+)in\s+\S.*$/i;
|
|
34
|
+
/** Turn a single-class selector into its raw class token (`.from-\[\#f00\]` -> `from-[#f00]`). */
|
|
35
|
+
function selectorToClass(selector) {
|
|
36
|
+
const match = selector.match(/^\.((?:\\.|[^\s,>+~.])+)$/);
|
|
37
|
+
if (!match) return null;
|
|
38
|
+
return match[1].replace(/\\(.)/g, "$1");
|
|
39
|
+
}
|
|
40
|
+
/** Strip the interpolation method so `to top in oklab` becomes `to top`. */
|
|
41
|
+
function stripInterpolation(position) {
|
|
42
|
+
return position.replace(INTERP_RE, "").trim();
|
|
43
|
+
}
|
|
44
|
+
var flattenGradients_default = (combos = []) => {
|
|
45
|
+
return {
|
|
46
|
+
postcssPlugin: PLUGIN_NAME,
|
|
47
|
+
Once(root) {
|
|
48
|
+
if (!combos.length) return;
|
|
49
|
+
const utilities = /* @__PURE__ */ new Map();
|
|
50
|
+
root.walkRules((rule) => {
|
|
51
|
+
const cls = selectorToClass(rule.selector);
|
|
52
|
+
if (!cls || !GRADIENT_UTILITY_RE.test(cls)) return;
|
|
53
|
+
const info = utilities.get(cls) ?? {};
|
|
54
|
+
rule.each((node) => {
|
|
55
|
+
if (node.type !== "decl") return;
|
|
56
|
+
switch (node.prop) {
|
|
57
|
+
case "--tw-gradient-position":
|
|
58
|
+
info.position = node.value;
|
|
59
|
+
break;
|
|
60
|
+
case "--tw-gradient-from":
|
|
61
|
+
info.from = node.value;
|
|
62
|
+
break;
|
|
63
|
+
case "--tw-gradient-via":
|
|
64
|
+
info.via = node.value;
|
|
65
|
+
break;
|
|
66
|
+
case "--tw-gradient-to":
|
|
67
|
+
info.to = node.value;
|
|
68
|
+
break;
|
|
69
|
+
case "--tw-gradient-from-position":
|
|
70
|
+
info.fromPosition = node.value;
|
|
71
|
+
break;
|
|
72
|
+
case "--tw-gradient-via-position":
|
|
73
|
+
info.viaPosition = node.value;
|
|
74
|
+
break;
|
|
75
|
+
case "--tw-gradient-to-position":
|
|
76
|
+
info.toPosition = node.value;
|
|
77
|
+
break;
|
|
78
|
+
case "background-image": {
|
|
79
|
+
const fn = node.value.match(/^(linear|radial|conic)-gradient\(/);
|
|
80
|
+
if (fn) info.fn = fn[1];
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
info.rule = rule;
|
|
86
|
+
utilities.set(cls, info);
|
|
87
|
+
});
|
|
88
|
+
for (const combo of combos) {
|
|
89
|
+
const fnClass = combo.classes.find((c) => /^bg-(linear|radial|conic)\b/.test(c));
|
|
90
|
+
if (!fnClass) continue;
|
|
91
|
+
const fnInfo = utilities.get(fnClass);
|
|
92
|
+
const fn = fnInfo?.fn;
|
|
93
|
+
if (!fn) continue;
|
|
94
|
+
let from;
|
|
95
|
+
let via;
|
|
96
|
+
let to;
|
|
97
|
+
let fromPosition;
|
|
98
|
+
let viaPosition;
|
|
99
|
+
let toPosition;
|
|
100
|
+
for (const cls of combo.classes) {
|
|
101
|
+
const info = utilities.get(cls);
|
|
102
|
+
if (!info) continue;
|
|
103
|
+
if (info.from !== void 0) from = info.from;
|
|
104
|
+
if (info.via !== void 0) via = info.via;
|
|
105
|
+
if (info.to !== void 0) to = info.to;
|
|
106
|
+
if (info.fromPosition !== void 0) fromPosition = info.fromPosition;
|
|
107
|
+
if (info.viaPosition !== void 0) viaPosition = info.viaPosition;
|
|
108
|
+
if (info.toPosition !== void 0) toPosition = info.toPosition;
|
|
109
|
+
}
|
|
110
|
+
from = from ?? "transparent";
|
|
111
|
+
to = to ?? "transparent";
|
|
112
|
+
const direction = fnInfo?.position ? stripInterpolation(fnInfo.position) : "";
|
|
113
|
+
const stops = [];
|
|
114
|
+
stops.push(fromPosition && fromPosition !== "0%" ? `${from} ${fromPosition}` : from);
|
|
115
|
+
if (via !== void 0) stops.push(viaPosition && viaPosition !== "50%" ? `${via} ${viaPosition}` : via);
|
|
116
|
+
stops.push(toPosition && toPosition !== "100%" ? `${to} ${toPosition}` : to);
|
|
117
|
+
const args = direction ? `${direction}, ${stops.join(", ")}` : stops.join(", ");
|
|
118
|
+
const generated = new Rule({ selector: `.${combo.className}` });
|
|
119
|
+
generated.append({
|
|
120
|
+
prop: "background-image",
|
|
121
|
+
value: `${fn}-gradient(${args})`
|
|
122
|
+
});
|
|
123
|
+
/**
|
|
124
|
+
* Insert at the position of the source utility rule (still present
|
|
125
|
+
* — removal happens below) so the gradient keeps that rule's place
|
|
126
|
+
* in the cascade. Appending to the root would move it past any
|
|
127
|
+
* later author CSS that should override it.
|
|
128
|
+
*/
|
|
129
|
+
const anchor = fnInfo?.rule;
|
|
130
|
+
if (anchor?.parent) anchor.before(generated);
|
|
131
|
+
else root.append(generated);
|
|
132
|
+
}
|
|
133
|
+
root.walkRules((rule) => {
|
|
134
|
+
const cls = selectorToClass(rule.selector);
|
|
135
|
+
if (cls && !GENERATED_RE.test(cls) && GRADIENT_UTILITY_RE.test(cls)) rule.remove();
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
const postcss = true;
|
|
141
|
+
//#endregion
|
|
142
|
+
export { flattenGradients_default as default, postcss };
|
|
143
|
+
|
|
144
|
+
//# sourceMappingURL=flattenGradients.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flattenGradients.js","names":[],"sources":["../../../src/plugins/postcss/flattenGradients.ts"],"sourcesContent":["/**\n * postcss-flatten-gradients\n *\n * Tailwind v4 renders gradients as CSS-variable machinery split across\n * separate utility rules (`bg-linear-*`, `from-*`, `via-*`, `to-*`),\n * combining only on the element that carries every class. Email clients\n * don't support `var()` and Maizzle inlines styles, so the variables\n * never resolve and the gradient renders nothing.\n *\n * Given the per-element gradient combos (computed from the DOM by the\n * tailwindcss transformer), this plugin reads the `--tw-gradient-*`\n * values off the utility rules and emits a single flat rule per combo:\n *\n * .bg-linear-gradient-to-bl-from-indigo-50-to-indigo-600 {\n * background-image: linear-gradient(to bottom left,\n * var(--color-indigo-50), var(--color-indigo-600));\n * }\n *\n * Colors stay as `var(--color-*)` (or literals) so the downstream\n * resolveProps + lightningcss steps convert them to hex, exactly like\n * every other Tailwind color. The dead utility rules are then removed.\n */\n\nimport { Rule } from 'postcss'\nimport type { Plugin, Root } from 'postcss'\n\nconst PLUGIN_NAME = 'postcss-flatten-gradients'\n\n/** A gradient combo found on a DOM element. */\nexport interface GradientCombo {\n /** Generated class name to emit the flat rule for. */\n className: string\n /** The gradient utility class tokens present on the element. */\n classes: string[]\n}\n\ntype GradientFn = 'linear' | 'radial' | 'conic'\n\ninterface UtilityInfo {\n fn?: GradientFn\n position?: string\n from?: string\n via?: string\n to?: string\n fromPosition?: string\n viaPosition?: string\n toPosition?: string\n /** The source rule, used to anchor the generated rule in the cascade. */\n rule?: Rule\n}\n\nconst GRADIENT_UTILITY_RE = /^(from|via|to)-|^bg-(linear|radial|conic)\\b/\nconst GENERATED_RE = /^bg-(linear|radial|conic)-gradient-/\n/**\n * Color-interpolation method Tailwind appends to the position (e.g.\n * `in oklab`, `in oklch longer hue`). It always trails the direction and\n * is unsupported in email, so strip from `in <space>` to the end.\n */\nconst INTERP_RE = /(?:^|\\s+)in\\s+\\S.*$/i\n\n/** Turn a single-class selector into its raw class token (`.from-\\[\\#f00\\]` -> `from-[#f00]`). */\nfunction selectorToClass(selector: string): string | null {\n const match = selector.match(/^\\.((?:\\\\.|[^\\s,>+~.])+)$/)\n if (!match) return null\n return match[1].replace(/\\\\(.)/g, '$1')\n}\n\n/** Strip the interpolation method so `to top in oklab` becomes `to top`. */\nfunction stripInterpolation(position: string): string {\n return position.replace(INTERP_RE, '').trim()\n}\n\nexport default (combos: GradientCombo[] = []): Plugin => {\n return {\n postcssPlugin: PLUGIN_NAME,\n\n Once(root: Root) {\n if (!combos.length) return\n\n // Index every single-class gradient utility rule by its class token.\n const utilities = new Map<string, UtilityInfo>()\n\n root.walkRules((rule) => {\n const cls = selectorToClass(rule.selector)\n if (!cls || !GRADIENT_UTILITY_RE.test(cls)) return\n\n const info = utilities.get(cls) ?? {}\n\n // Only read declarations directly on the rule, skipping the\n // nested @supports overrides Tailwind adds for oklab.\n rule.each((node) => {\n if (node.type !== 'decl') return\n switch (node.prop) {\n case '--tw-gradient-position':\n info.position = node.value\n break\n case '--tw-gradient-from':\n info.from = node.value\n break\n case '--tw-gradient-via':\n info.via = node.value\n break\n case '--tw-gradient-to':\n info.to = node.value\n break\n case '--tw-gradient-from-position':\n info.fromPosition = node.value\n break\n case '--tw-gradient-via-position':\n info.viaPosition = node.value\n break\n case '--tw-gradient-to-position':\n info.toPosition = node.value\n break\n case 'background-image': {\n const fn = node.value.match(/^(linear|radial|conic)-gradient\\(/)\n if (fn) info.fn = fn[1] as GradientFn\n break\n }\n }\n })\n\n info.rule = rule\n utilities.set(cls, info)\n })\n\n // Emit one flat rule per combo.\n for (const combo of combos) {\n const fnClass = combo.classes.find(c => /^bg-(linear|radial|conic)\\b/.test(c))\n if (!fnClass) continue\n\n const fnInfo = utilities.get(fnClass)\n const fn = fnInfo?.fn\n if (!fn) continue\n\n let from: string | undefined\n let via: string | undefined\n let to: string | undefined\n let fromPosition: string | undefined\n let viaPosition: string | undefined\n let toPosition: string | undefined\n\n for (const cls of combo.classes) {\n const info = utilities.get(cls)\n if (!info) continue\n if (info.from !== undefined) from = info.from\n if (info.via !== undefined) via = info.via\n if (info.to !== undefined) to = info.to\n if (info.fromPosition !== undefined) fromPosition = info.fromPosition\n if (info.viaPosition !== undefined) viaPosition = info.viaPosition\n if (info.toPosition !== undefined) toPosition = info.toPosition\n }\n\n // Tailwind defaults an unset from/to to transparent (#0000).\n from = from ?? 'transparent'\n to = to ?? 'transparent'\n\n const direction = fnInfo?.position ? stripInterpolation(fnInfo.position) : ''\n\n const stops: string[] = []\n stops.push(fromPosition && fromPosition !== '0%' ? `${from} ${fromPosition}` : from)\n if (via !== undefined) {\n stops.push(viaPosition && viaPosition !== '50%' ? `${via} ${viaPosition}` : via)\n }\n stops.push(toPosition && toPosition !== '100%' ? `${to} ${toPosition}` : to)\n\n const args = direction ? `${direction}, ${stops.join(', ')}` : stops.join(', ')\n\n const generated = new Rule({ selector: `.${combo.className}` })\n generated.append({ prop: 'background-image', value: `${fn}-gradient(${args})` })\n\n /**\n * Insert at the position of the source utility rule (still present\n * — removal happens below) so the gradient keeps that rule's place\n * in the cascade. Appending to the root would move it past any\n * later author CSS that should override it.\n */\n const anchor = fnInfo?.rule\n if (anchor?.parent) {\n anchor.before(generated)\n } else {\n root.append(generated)\n }\n }\n\n // Remove the now-dead gradient utility rules.\n root.walkRules((rule) => {\n const cls = selectorToClass(rule.selector)\n if (cls && !GENERATED_RE.test(cls) && GRADIENT_UTILITY_RE.test(cls)) {\n rule.remove()\n }\n })\n },\n }\n}\n\nexport const postcss = true\n"],"mappings":"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"}
|
|
@@ -13,7 +13,8 @@ import sortMediaQueries from "postcss-sort-media-queries";
|
|
|
13
13
|
function mergeMediaQueries(config) {
|
|
14
14
|
const media = config.css?.media;
|
|
15
15
|
if (media === false) return null;
|
|
16
|
-
|
|
16
|
+
const sort = (typeof media === "object" ? media : {}).sort ?? "mobile-first";
|
|
17
|
+
return sortMediaQueries({ sort });
|
|
17
18
|
}
|
|
18
19
|
//#endregion
|
|
19
20
|
export { mergeMediaQueries };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mergeMediaQueries.js","names":[],"sources":["../../../src/plugins/postcss/mergeMediaQueries.ts"],"sourcesContent":["import sortMediaQueries from 'postcss-sort-media-queries'\nimport type postcss from 'postcss'\nimport type { MaizzleConfig } from '../../types/config.ts'\n\n/**\n * Sorts and merges CSS media queries using postcss-sort-media-queries.\n *\n * Enabled by default. Opt out with css: { media: false }.\n *\n * Config examples:\n * css: { media: true } // merge, mobile-first sort (default)\n * css: { media: { sort: 'desktop-first' } } // merge, desktop-first sort\n * css: { media: false } // disabled\n */\nexport function mergeMediaQueries(config: MaizzleConfig): postcss.Plugin | null {\n const media = config.css?.media\n\n if (media === false) return null\n\n const options = typeof media === 'object' ? media : {}\n const sort = options.sort ?? 'mobile-first'\n\n return sortMediaQueries({ sort }) as postcss.Plugin\n}\n"],"mappings":";;;;;;;;;;;;AAcA,SAAgB,kBAAkB,QAA8C;CAC9E,MAAM,QAAQ,OAAO,KAAK;CAE1B,IAAI,UAAU,OAAO,OAAO;
|
|
1
|
+
{"version":3,"file":"mergeMediaQueries.js","names":[],"sources":["../../../src/plugins/postcss/mergeMediaQueries.ts"],"sourcesContent":["import sortMediaQueries from 'postcss-sort-media-queries'\nimport type postcss from 'postcss'\nimport type { MaizzleConfig } from '../../types/config.ts'\n\n/**\n * Sorts and merges CSS media queries using postcss-sort-media-queries.\n *\n * Enabled by default. Opt out with css: { media: false }.\n *\n * Config examples:\n * css: { media: true } // merge, mobile-first sort (default)\n * css: { media: { sort: 'desktop-first' } } // merge, desktop-first sort\n * css: { media: false } // disabled\n */\nexport function mergeMediaQueries(config: MaizzleConfig): postcss.Plugin | null {\n const media = config.css?.media\n\n if (media === false) return null\n\n const options = typeof media === 'object' ? media : {}\n const sort = options.sort ?? 'mobile-first'\n\n return sortMediaQueries({ sort }) as postcss.Plugin\n}\n"],"mappings":";;;;;;;;;;;;AAcA,SAAgB,kBAAkB,QAA8C;CAC9E,MAAM,QAAQ,OAAO,KAAK;CAE1B,IAAI,UAAU,OAAO,OAAO;CAG5B,MAAM,QADU,OAAO,UAAU,WAAW,QAAQ,CAAC,EAAA,CAChC,QAAQ;CAE7B,OAAO,iBAAiB,EAAE,KAAK,CAAC;AAClC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolveMaizzleImports.js","names":[],"sources":["../../../src/plugins/postcss/resolveMaizzleImports.ts"],"sourcesContent":["import { createRequire } from 'node:module'\nimport type { Plugin } from 'postcss'\n\nconst PKG = '@maizzle/tailwindcss'\nconst SUBPATH_RE = new RegExp(`^${PKG}(?:/|$)`)\n\nconst requireFromFramework = createRequire(import.meta.url)\n\n/**\n * Rewrite `@import \"@maizzle/tailwindcss\"` (and subpaths like\n * `@maizzle/tailwindcss/mso`) to absolute file paths so postcss/Tailwind\n * can resolve them regardless of where the user's template lives or how\n * their package manager hoists dependencies.\n *\n * Resolution order: prefer the user's project copy (so explicit installs\n * win), then fall back to the copy bundled with the framework.\n */\nexport function resolveMaizzleImports(userRoot: string = process.cwd()): Plugin {\n const requireFromUser = createRequire(`${userRoot}/_maizzle.js`)\n\n function resolve(spec: string): string | undefined {\n try { return requireFromUser.resolve(spec) } catch {}\n try { return requireFromFramework.resolve(spec) } catch {}\n return undefined\n }\n\n return {\n postcssPlugin: 'maizzle:resolve-tw-imports',\n AtRule: {\n import(rule) {\n const m = rule.params.match(/^\\s*[\"']([^\"']+)[\"']/)\n if (!m) return\n const spec = m[1]\n if (!SUBPATH_RE.test(spec)) return\n\n const abs = resolve(spec)\n if (abs) rule.params = rule.params.replace(m[0], `\"${abs}\"`)\n },\n },\n }\n}\n"],"mappings":";;AAIA,MAAM,aAAa,IAAI,OAAO,8BAAgB;AAE9C,MAAM,uBAAuB,cAAc,
|
|
1
|
+
{"version":3,"file":"resolveMaizzleImports.js","names":[],"sources":["../../../src/plugins/postcss/resolveMaizzleImports.ts"],"sourcesContent":["import { createRequire } from 'node:module'\nimport type { Plugin } from 'postcss'\n\nconst PKG = '@maizzle/tailwindcss'\nconst SUBPATH_RE = new RegExp(`^${PKG}(?:/|$)`)\n\nconst requireFromFramework = createRequire(import.meta.url)\n\n/**\n * Rewrite `@import \"@maizzle/tailwindcss\"` (and subpaths like\n * `@maizzle/tailwindcss/mso`) to absolute file paths so postcss/Tailwind\n * can resolve them regardless of where the user's template lives or how\n * their package manager hoists dependencies.\n *\n * Resolution order: prefer the user's project copy (so explicit installs\n * win), then fall back to the copy bundled with the framework.\n */\nexport function resolveMaizzleImports(userRoot: string = process.cwd()): Plugin {\n const requireFromUser = createRequire(`${userRoot}/_maizzle.js`)\n\n function resolve(spec: string): string | undefined {\n try { return requireFromUser.resolve(spec) } catch {}\n try { return requireFromFramework.resolve(spec) } catch {}\n return undefined\n }\n\n return {\n postcssPlugin: 'maizzle:resolve-tw-imports',\n AtRule: {\n import(rule) {\n const m = rule.params.match(/^\\s*[\"']([^\"']+)[\"']/)\n if (!m) return\n const spec = m[1]\n if (!SUBPATH_RE.test(spec)) return\n\n const abs = resolve(spec)\n if (abs) rule.params = rule.params.replace(m[0], `\"${abs}\"`)\n },\n },\n }\n}\n"],"mappings":";;AAIA,MAAM,aAAa,IAAI,OAAO,8BAAgB;AAE9C,MAAM,uBAAuB,cAAc,YAAY,GAAG;;;;;;;;;;AAW1D,SAAgB,sBAAsB,WAAmB,QAAQ,IAAI,GAAW;CAC9E,MAAM,kBAAkB,cAAc,GAAG,SAAS,aAAa;CAE/D,SAAS,QAAQ,MAAkC;EACjD,IAAI;GAAE,OAAO,gBAAgB,QAAQ,IAAI;EAAE,QAAQ,CAAC;EACpD,IAAI;GAAE,OAAO,qBAAqB,QAAQ,IAAI;EAAE,QAAQ,CAAC;CAE3D;CAEA,OAAO;EACL,eAAe;EACf,QAAQ,EACN,OAAO,MAAM;GACX,MAAM,IAAI,KAAK,OAAO,MAAM,sBAAsB;GAClD,IAAI,CAAC,GAAG;GACR,MAAM,OAAO,EAAE;GACf,IAAI,CAAC,WAAW,KAAK,IAAI,GAAG;GAE5B,MAAM,MAAM,QAAQ,IAAI;GACxB,IAAI,KAAK,KAAK,SAAS,KAAK,OAAO,QAAQ,EAAE,IAAI,IAAI,IAAI,EAAE;EAC7D,EACF;CACF;AACF"}
|
|
@@ -133,7 +133,9 @@ function commonPath(a, b) {
|
|
|
133
133
|
}
|
|
134
134
|
function resolveOutputPath(templatePath, outputDir, extension, contentBase) {
|
|
135
135
|
const name = basename(templatePath).replace(/\.(vue|md)$/, "");
|
|
136
|
-
|
|
136
|
+
const absTemplate = resolve(templatePath);
|
|
137
|
+
const rel = relative(contentBase, dirname(absTemplate));
|
|
138
|
+
return join(outputDir, rel, `${name}.${extension}`);
|
|
137
139
|
}
|
|
138
140
|
//#endregion
|
|
139
141
|
export { buildTemplate, computeContentBase, resolveOutputPath };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildTemplate.js","names":["parsePath"],"sources":["../../src/render/buildTemplate.ts"],"sourcesContent":["import { readFileSync, writeFileSync, mkdirSync } from 'node:fs'\nimport { resolve, dirname, basename, relative, join, sep, parse as parsePath } from 'node:path'\nimport defu from 'defu'\nimport { runTransformers } from '../transformers/index.ts'\nimport { createPlaintext } from '../plaintext.ts'\nimport { stripForHtml, stripForPlaintext } from '../utils/output-markers.ts'\nimport { _setCurrentTemplate } from '../composables/useCurrentTemplate.ts'\nimport { cloneConfig } from '../utils/cloneConfig.ts'\nimport type { EventManager } from '../events/index.ts'\nimport type { Renderer } from './createRenderer.ts'\nimport type { MaizzleConfig } from '../types/index.ts'\n\nexport interface BuildTemplateContext {\n config: MaizzleConfig\n renderer: Renderer\n events: EventManager\n outputPath: string\n outputExtension: string\n contentBase: string\n}\n\nexport interface BuildTemplateResult {\n /** Output files written for this template (html + optional plaintext). */\n files: string[]\n /**\n * Number of SFC-registered `afterBuild` handlers seen while rendering. They\n * only fire once at end of build on the main thread, so a worker can't run\n * them — the count lets the orchestrator warn instead of silently dropping.\n */\n sfcAfterBuildCount: number\n}\n\n/**\n * Render a single template through the full pipeline and write its output.\n *\n * Shared by the sequential build loop and the parallel build worker so both\n * paths produce byte-identical output. `events` is the manager the per-template\n * events fire on (config handlers registered via `registerConfig`, SFC handlers\n * registered here from the render). The caller owns build-scoped events\n * (`beforeCreate`/`afterBuild`).\n */\nexport async function buildTemplate(\n templatePath: string,\n ctx: BuildTemplateContext,\n): Promise<BuildTemplateResult> {\n const { config, renderer, events, outputPath, outputExtension, contentBase } = ctx\n const absolutePath = resolve(templatePath)\n const parsedPath = parsePath(absolutePath)\n const template = { source: readFileSync(absolutePath, 'utf-8'), path: parsedPath }\n const files: string[] = []\n let sfcAfterBuildCount = 0\n\n _setCurrentTemplate(parsedPath)\n\n try {\n /**\n * Clone config per template so beforeRender mutations (setting a\n * preheader, injecting fetched data, etc.) stay scoped to this template\n * instead of leaking into later ones through the shared config object.\n */\n const renderConfig = cloneConfig(config)\n const originalSource = template.source\n\n await events.fireBeforeRender({ config: renderConfig, template })\n\n const rendered = await renderer.render(\n absolutePath,\n renderConfig,\n template.source !== originalSource ? { source: template.source } : undefined,\n )\n\n /**\n * Register SFC event handlers collected during render so they take part in\n * the post-render events. Cleared at the end of this call so they don't\n * leak into the next template (afterBuild is the exception — it's never\n * cleared by clearSfcHandlers; see the count above).\n */\n for (const { name, handler } of rendered.sfcEventHandlers) {\n if (name === 'afterBuild') sfcAfterBuildCount++\n events.on(name, handler)\n }\n\n const templateConfig = rendered.templateConfig\n\n let html = await events.fireAfterRender({ config: templateConfig, template, html: rendered.html })\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: templateConfig, 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 files.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 ptOutputPath = resolveOutputPath(templatePath, resolve(sfcPlaintext.destination), ptExtension, contentBase)\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 files.push(ptOutputPath)\n }\n } finally {\n _setCurrentTemplate(undefined)\n events.clearSfcHandlers()\n }\n\n return { files, sfcAfterBuildCount }\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 * Used to strip the content base from template paths so the output preserves\n * only the subdirectory structure.\n *\n * With multiple positive patterns (multi-root setups), returns their common\n * ancestor directory so templates from every root keep a clean relative path.\n */\nexport function computeContentBase(patterns: string[]): string {\n const positives = patterns.filter(p => !p.startsWith('!'))\n const sources = positives.length > 0 ? positives : patterns\n\n const bases = sources.map((pattern) => {\n // Split on first glob character (* { ? [) and take the directory part\n const staticPart = pattern.split(/[*{?[]/)[0]\n // Ensure we have a clean directory path (not a partial segment)\n return resolve(staticPart.endsWith('/') ? staticPart : dirname(staticPart))\n })\n\n return bases.reduce(commonPath)\n}\n\n/** Longest common directory path shared by two absolute paths. */\nfunction commonPath(a: string, b: string): string {\n const aSegments = a.split(sep)\n const bSegments = b.split(sep)\n const shared: string[] = []\n\n for (let i = 0; i < Math.min(aSegments.length, bSegments.length); i++) {\n if (aSegments[i] !== bSegments[i]) break\n shared.push(aSegments[i])\n }\n\n return shared.join(sep) || sep\n}\n\nexport function 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"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"buildTemplate.js","names":["parsePath"],"sources":["../../src/render/buildTemplate.ts"],"sourcesContent":["import { readFileSync, writeFileSync, mkdirSync } from 'node:fs'\nimport { resolve, dirname, basename, relative, join, sep, parse as parsePath } from 'node:path'\nimport defu from 'defu'\nimport { runTransformers } from '../transformers/index.ts'\nimport { createPlaintext } from '../plaintext.ts'\nimport { stripForHtml, stripForPlaintext } from '../utils/output-markers.ts'\nimport { _setCurrentTemplate } from '../composables/useCurrentTemplate.ts'\nimport { cloneConfig } from '../utils/cloneConfig.ts'\nimport type { EventManager } from '../events/index.ts'\nimport type { Renderer } from './createRenderer.ts'\nimport type { MaizzleConfig } from '../types/index.ts'\n\nexport interface BuildTemplateContext {\n config: MaizzleConfig\n renderer: Renderer\n events: EventManager\n outputPath: string\n outputExtension: string\n contentBase: string\n}\n\nexport interface BuildTemplateResult {\n /** Output files written for this template (html + optional plaintext). */\n files: string[]\n /**\n * Number of SFC-registered `afterBuild` handlers seen while rendering. They\n * only fire once at end of build on the main thread, so a worker can't run\n * them — the count lets the orchestrator warn instead of silently dropping.\n */\n sfcAfterBuildCount: number\n}\n\n/**\n * Render a single template through the full pipeline and write its output.\n *\n * Shared by the sequential build loop and the parallel build worker so both\n * paths produce byte-identical output. `events` is the manager the per-template\n * events fire on (config handlers registered via `registerConfig`, SFC handlers\n * registered here from the render). The caller owns build-scoped events\n * (`beforeCreate`/`afterBuild`).\n */\nexport async function buildTemplate(\n templatePath: string,\n ctx: BuildTemplateContext,\n): Promise<BuildTemplateResult> {\n const { config, renderer, events, outputPath, outputExtension, contentBase } = ctx\n const absolutePath = resolve(templatePath)\n const parsedPath = parsePath(absolutePath)\n const template = { source: readFileSync(absolutePath, 'utf-8'), path: parsedPath }\n const files: string[] = []\n let sfcAfterBuildCount = 0\n\n _setCurrentTemplate(parsedPath)\n\n try {\n /**\n * Clone config per template so beforeRender mutations (setting a\n * preheader, injecting fetched data, etc.) stay scoped to this template\n * instead of leaking into later ones through the shared config object.\n */\n const renderConfig = cloneConfig(config)\n const originalSource = template.source\n\n await events.fireBeforeRender({ config: renderConfig, template })\n\n const rendered = await renderer.render(\n absolutePath,\n renderConfig,\n template.source !== originalSource ? { source: template.source } : undefined,\n )\n\n /**\n * Register SFC event handlers collected during render so they take part in\n * the post-render events. Cleared at the end of this call so they don't\n * leak into the next template (afterBuild is the exception — it's never\n * cleared by clearSfcHandlers; see the count above).\n */\n for (const { name, handler } of rendered.sfcEventHandlers) {\n if (name === 'afterBuild') sfcAfterBuildCount++\n events.on(name, handler)\n }\n\n const templateConfig = rendered.templateConfig\n\n let html = await events.fireAfterRender({ config: templateConfig, template, html: rendered.html })\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: templateConfig, 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 files.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 ptOutputPath = resolveOutputPath(templatePath, resolve(sfcPlaintext.destination), ptExtension, contentBase)\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 files.push(ptOutputPath)\n }\n } finally {\n _setCurrentTemplate(undefined)\n events.clearSfcHandlers()\n }\n\n return { files, sfcAfterBuildCount }\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 * Used to strip the content base from template paths so the output preserves\n * only the subdirectory structure.\n *\n * With multiple positive patterns (multi-root setups), returns their common\n * ancestor directory so templates from every root keep a clean relative path.\n */\nexport function computeContentBase(patterns: string[]): string {\n const positives = patterns.filter(p => !p.startsWith('!'))\n const sources = positives.length > 0 ? positives : patterns\n\n const bases = sources.map((pattern) => {\n // Split on first glob character (* { ? [) and take the directory part\n const staticPart = pattern.split(/[*{?[]/)[0]\n // Ensure we have a clean directory path (not a partial segment)\n return resolve(staticPart.endsWith('/') ? staticPart : dirname(staticPart))\n })\n\n return bases.reduce(commonPath)\n}\n\n/** Longest common directory path shared by two absolute paths. */\nfunction commonPath(a: string, b: string): string {\n const aSegments = a.split(sep)\n const bSegments = b.split(sep)\n const shared: string[] = []\n\n for (let i = 0; i < Math.min(aSegments.length, bSegments.length); i++) {\n if (aSegments[i] !== bSegments[i]) break\n shared.push(aSegments[i])\n }\n\n return shared.join(sep) || sep\n}\n\nexport function 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"],"mappings":"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"}
|
|
@@ -343,7 +343,14 @@ async function createRenderer(options = {}) {
|
|
|
343
343
|
logLevel: "silent",
|
|
344
344
|
optimizeDeps: { noDiscovery: true }
|
|
345
345
|
};
|
|
346
|
-
|
|
346
|
+
/**
|
|
347
|
+
* Merge user's vite config (from config.vite) under Maizzle's config.
|
|
348
|
+
* mergeConfig(a, b) → b overrides a for scalars, arrays concatenate.
|
|
349
|
+
* This ensures Maizzle's critical settings (middlewareMode, appType,
|
|
350
|
+
* etc.) always win, while user plugins and other options remain.
|
|
351
|
+
*/
|
|
352
|
+
const finalConfig = userViteConfig ? mergeConfig(userViteConfig, maizzleConfig) : maizzleConfig;
|
|
353
|
+
const server = await createServer(finalConfig);
|
|
347
354
|
return {
|
|
348
355
|
async render(input, config, opts) {
|
|
349
356
|
let component;
|