UNPKG

@maizzle/framework

Version:

Maizzle is a framework that helps you quickly build HTML emails with Tailwind CSS.

136 lines (135 loc) 5.3 kB
import { defaults } from "./defaults.js"; import { defineConfig } from "../composables/defineConfig.js"; import { existsSync, readFileSync } from "node:fs"; import { fileURLToPath, pathToFileURL } from "node:url"; import { dirname, extname, join, resolve } from "pathe"; import { createJiti } from "jiti"; import { createDefu } from "defu"; //#region src/config/index.ts const merge = createDefu((obj, key, value) => { if (Array.isArray(obj[key])) { obj[key] = value; return true; } }); const CONFIG_FILES = ["maizzle.config.ts", "maizzle.config.js"]; /** * Resolve the Maizzle config. * * Always loads from the config file on disk (maizzle.config.{ts,js}), * then merges the programmatic config on top, then fills in defaults. */ async function resolveConfig(config, cwd = process.cwd()) { const fileConfig = await loadConfig(typeof config === "string" ? config : void 0, cwd); return normalizeConfig(typeof config === "object" && config !== null ? config : {}, fileConfig, cwd); } /** * Resolve config from a programmatic object only — never touches disk. * * `render()` uses this so a stray `maizzle.config.{ts,js}` in `cwd` can't * silently alter output: the config you pass is the config you get, layered * over defaults. Callers who want the project's file config load it * explicitly via {@link resolveConfig} and pass the result in. */ function resolveConfigObject(config, cwd = process.cwd()) { return normalizeConfig(config && typeof config === "object" ? config : {}, {}, cwd); } /** * Merge programmatic config over file config over defaults, then resolve * filesystem paths (root, content, static, components) relative to cwd/root. */ function normalizeConfig(programmaticConfig, fileConfig, cwd) { const merged = merge(programmaticConfig, fileConfig, defaults); const hasExplicitRoot = !!(programmaticConfig.root ?? fileConfig.root); const root = resolve(cwd, merged.root ?? "."); merged.root = root; if (merged.content) merged.content = merged.content.map((p) => { if (p.startsWith("/") || p.startsWith("!")) return p; return resolve(root, p); }); if (merged.static?.source) merged.static.source = merged.static.source.map((p) => { if (p.startsWith("/") || p.startsWith("!")) return p; return resolve(root, p); }); /** * Resolve components.source paths relative to cwd (not root), since extra * component dirs often live outside the root directory. String * entries → resolve in-place. Object entries → resolve * `path`, preserve `prefix`/`pathPrefix`. */ if (merged.components?.source) { const dirs = Array.isArray(merged.components.source) ? merged.components.source : [merged.components.source]; merged.components.source = dirs.map((entry) => { if (typeof entry === "string") return entry.startsWith("/") ? entry : resolve(cwd, entry); return { ...entry, path: entry.path.startsWith("/") ? entry.path : resolve(cwd, entry.path) }; }); } /** * Default css.base to root when root is explicitly set, so Tailwind resolves * @source from the right directory. When root is not set, leave * css.base undefined so Tailwind uses its own default (the * template file's directory). */ if (hasExplicitRoot && !merged.css?.base) { if (!merged.css) merged.css = {}; merged.css.base = root; } return merged; } /** * Whether Node can natively `import()` this file as ESM — true for `.mjs` * and for `.js` whose nearest package.json declares `"type": "module"`. * jiti hands such files to Node's native loader, which caches them in the * (unbustable) module registry, so we import them ourselves with a fresh * query instead. Everything else (CJS, TS) goes through jiti, which already * re-evaluates per instance. */ function isNativeESM(absolutePath) { const ext = extname(absolutePath); if (ext === ".mjs") return true; if (ext === ".cjs" || ext === ".ts" || ext === ".mts" || ext === ".cts") return false; let dir = dirname(absolutePath); for (;;) { const pkgPath = join(dir, "package.json"); if (existsSync(pkgPath)) try { return JSON.parse(readFileSync(pkgPath, "utf-8")).type === "module"; } catch { return false; } const parent = dirname(dir); if (parent === dir) return false; dir = parent; } } /** * Monotonic nonce appended to native-import URLs so each load is fresh even * when two reloads land in the same millisecond. */ let configLoadNonce = 0; async function importConfig(absolutePath, jiti) { if (isNativeESM(absolutePath)) { const mod = await import(`${pathToFileURL(absolutePath).href}?t=${Date.now()}-${configLoadNonce++}`); return mod.default ?? mod; } const mod = await jiti.import(absolutePath); return mod.default ?? mod; } async function loadConfig(configPath, cwd = process.cwd()) { const jiti = createJiti(fileURLToPath(import.meta.url), { moduleCache: false }); if (configPath) { const absolutePath = resolve(cwd, configPath); if (!existsSync(absolutePath)) throw new Error(`Config file not found: ${absolutePath}`); return importConfig(absolutePath, jiti); } for (const filename of CONFIG_FILES) { const filepath = resolve(cwd, filename); if (existsSync(filepath)) return importConfig(filepath, jiti); } return {}; } //#endregion export { defaults, defineConfig, isNativeESM, resolveConfig, resolveConfigObject }; //# sourceMappingURL=index.js.map