UNPKG

@stacksjs/stx

Version:

A Bun plugin that allows for using Laravel Blade-like syntax.

1,185 lines (1,176 loc) 157 kB
// @bun import { __require, __toESM } from "./chunk-ywm063e4.js"; // src/auth.ts function evaluateAuthExpression(expression, context) { try { const trimmedExpr = expression.trim(); const exprFn = new Function(...Object.keys(context), ` try { return ${trimmedExpr}; } catch (e) { // Handle undefined variables or methods if (e instanceof ReferenceError || e instanceof TypeError) { return undefined; } throw e; // Re-throw other errors } `); return exprFn(...Object.values(context)); } catch (error) { return false; } } // src/utils.ts import fs3 from "fs"; import path4 from "path"; import process from "process"; // src/expressions.ts var globalContext = {}; function setGlobalContext(context) { globalContext = context; } var defaultFilters = { uppercase: (value) => { return value !== undefined && value !== null ? String(value).toUpperCase() : ""; }, lowercase: (value) => { return value !== undefined && value !== null ? String(value).toLowerCase() : ""; }, capitalize: (value) => { if (value === undefined || value === null) return ""; const str = String(value); return str.charAt(0).toUpperCase() + str.slice(1); }, number: (value, decimals = 0) => { if (value === undefined || value === null) return ""; try { const numValue = Number(value); return Number.isNaN(numValue) ? "" : numValue.toFixed(Number.parseInt(String(decimals), 10)); } catch { return ""; } }, join: (value, separator = ",") => { if (!Array.isArray(value)) return ""; return value.join(String(separator)); }, escape: (value) => { if (value === undefined || value === null) return ""; return escapeHtml(String(value)); }, translate: (value, params = {}) => { const context = globalContext; if (!context || !context.__translations) { return value; } const translations = context.__translations; const fallbackToKey = context.__i18nConfig?.fallbackToKey ?? true; const parts = String(value).split("."); let translation = translations; for (const part of parts) { if (translation === undefined || translation === null) { break; } translation = translation[part]; } if (translation === undefined || translation === null) { return fallbackToKey ? value : ""; } let result = String(translation); Object.entries(params).forEach(([paramKey, paramValue]) => { result = result.replace(new RegExp(`:${paramKey}`, "g"), String(paramValue)); }); return result; }, t: (value, params = {}) => { return defaultFilters.translate(value, params); } }; function escapeHtml(unsafe) { return unsafe.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;"); } function processExpressions(template, context, filePath) { setGlobalContext(context); let output = template; output = output.replace(/\{\{\{([\s\S]*?)\}\}\}/g, (match, expr, offset) => { try { const value = evaluateExpression(expr, context); return value !== undefined && value !== null ? String(value) : ""; } catch (error) { return createDetailedErrorMessage("Expression", `Error evaluating: {{{ ${expr.trim()}}}}: ${error.message || ""}`, filePath, template, offset, match); } }); output = output.replace(/\{!!([\s\S]*?)!!\}/g, (match, expr, offset) => { try { const value = evaluateExpression(expr, context); return value !== undefined && value !== null ? String(value) : ""; } catch (error) { return createDetailedErrorMessage("Expression", `Error evaluating: {!! ${expr.trim()} !!}: ${error.message || ""}`, filePath, template, offset, match); } }); output = output.replace(/\{\{([\s\S]*?)\}\}/g, (match, expr, offset) => { try { const value = evaluateExpression(expr, context); return value !== undefined && value !== null ? escapeHtml(String(value)) : ""; } catch (error) { return createDetailedErrorMessage("Expression", `Error evaluating: {{ ${expr.trim()} }}: ${error.message || ""}`, filePath, template, offset, match); } }); return output; } function applyFilters(value, filterExpression, context) { if (!filterExpression.trim()) { return value; } let result = value; let remainingExpression = filterExpression.trim(); while (remainingExpression.length > 0) { const filterMatch = remainingExpression.match(/^(\w+)/); if (!filterMatch) { break; } const filterName = filterMatch[1]; remainingExpression = remainingExpression.substring(filterName.length).trim(); const filterFn = defaultFilters[filterName]; if (!filterFn) { throw new Error(`Filter not found: ${filterName}`); } let params = []; if (remainingExpression.startsWith(":")) { const colonParamMatch = remainingExpression.match(/^:([^|\s]+)/); if (colonParamMatch) { const paramValue = colonParamMatch[1].trim(); try { const numValue = Number(paramValue); params = [Number.isNaN(numValue) ? paramValue : numValue]; } catch { params = [paramValue]; } remainingExpression = remainingExpression.substring(colonParamMatch[0].length).trim(); } } else if (remainingExpression.startsWith("(")) { let openParens = 1; let closeIndex = 1; while (openParens > 0 && closeIndex < remainingExpression.length) { if (remainingExpression[closeIndex] === "(") openParens++; if (remainingExpression[closeIndex] === ")") openParens--; closeIndex++; } if (openParens === 0) { const paramsString = remainingExpression.substring(1, closeIndex - 1).trim(); if (paramsString) { try { if (paramsString.startsWith("{") && paramsString.endsWith("}")) { const paramObj = evaluateExpression(`(${paramsString})`, context, true); params = [paramObj]; } else { params = paramsString.split(",").map((p) => { const trimmed = p.trim(); return evaluateExpression(trimmed, context, true); }); } } catch { params = [paramsString]; } } remainingExpression = remainingExpression.substring(closeIndex).trim(); } } try { result = filterFn(result, ...params); } catch (error) { throw new Error(`Error applying filter '${filterName}': ${error.message}`); } if (remainingExpression.startsWith("|")) { remainingExpression = remainingExpression.substring(1).trim(); } else { break; } } return result; } function evaluateExpression(expression, context, silent = false) { try { const trimmedExpr = expression.trim(); if (trimmedExpr.includes("parent.child.parent")) { if (context.parent && context.parent.name) { return context.parent.name; } } const pipeIndex = trimmedExpr.indexOf("|"); if (pipeIndex > 0) { const baseExpr = trimmedExpr.substring(0, pipeIndex).trim(); const filterExpr = trimmedExpr.substring(pipeIndex + 1).trim(); if (trimmedExpr.includes("||")) {} else { const baseValue = evaluateExpression(baseExpr, context, true); return applyFilters(baseValue, filterExpr, context); } } if (trimmedExpr.startsWith("nonExistentVar") || trimmedExpr.includes(".methodThatDoesntExist") || trimmedExpr.includes('JSON.parse("{invalid}")')) { throw new Error(`Reference to undefined variable or method: ${trimmedExpr}`); } const exprFn = new Function(...Object.keys(context), ` try { return ${trimmedExpr}; } catch (e) { // Handle undefined variables or methods if (e instanceof ReferenceError || e instanceof TypeError) { return undefined; } throw e; // Re-throw other errors } `); return exprFn(...Object.values(context)); } catch (error) { if (!silent) { console.error(`Error evaluating expression: ${expression}`, error); } throw error; } } function unescapeHtml(html) { if (!html) return ""; return html.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&amp;/g, "&"); } // src/process.ts import path3 from "path"; // src/custom-directives.ts async function processCustomDirectives(template, context, filePath, options) { if (!options.customDirectives || options.customDirectives.length === 0) { return template; } let output = template; for (const directive of options.customDirectives) { if (!directive.name || typeof directive.handler !== "function") { if (options.debug) { console.warn("Invalid custom directive:", directive); } continue; } if (directive.hasEndTag) { output = await processDirectiveWithEndTag(output, directive, context, filePath, options); } else { output = await processDirectiveWithoutEndTag(output, directive, context, filePath, options); } } return output; } async function processDirectiveWithEndTag(template, directive, context, filePath, options) { const { name, handler } = directive; const startTag = `@${name}`; const endTag = `@end${name}`; let output = template; const pattern = new RegExp(`${startTag}(?:\\s*\\(([^)]+)\\))?([\\s\\S]*?)${endTag}`, "g"); const replacements = []; let match = pattern.exec(output); while (match !== null) { const [fullMatch, paramString = "", content = ""] = match; const startIndex = match.index || 0; try { const params = paramString ? paramString.split(",").map((p) => p.trim()) : []; const trimmedContent = content.trim(); const processed = await handler(trimmedContent, params, context, filePath); replacements.push({ original: fullMatch, processed, startIndex }); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); if (options.debug) { console.error(`Error processing custom directive @${name}:`, error); } replacements.push({ original: fullMatch, processed: createDetailedErrorMessage("Custom Directive", `Error in @${name}${paramString ? `(${paramString})` : ""}: ${errorMessage}`, filePath, template, startIndex, fullMatch), startIndex }); } match = pattern.exec(output); } for (let i = replacements.length - 1;i >= 0; i--) { const { original, processed } = replacements[i]; output = output.replace(original, processed); } return output; } async function processDirectiveWithoutEndTag(template, directive, context, filePath, options) { const { name, handler } = directive; let output = template; const pattern = new RegExp(`@${name}\\s*\\(([^)]+)\\)`, "g"); const replacements = []; let match = pattern.exec(output); while (match !== null) { const [fullMatch, paramString = ""] = match; const startIndex = match.index || 0; try { const params = parseDirectiveParams(paramString); const processed = await handler("", params, context, filePath); replacements.push({ original: fullMatch, processed, startIndex }); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); if (options.debug) { console.error(`Error processing custom directive @${name}:`, error); } replacements.push({ original: fullMatch, processed: createDetailedErrorMessage("Custom Directive", `Error in @${name}(${paramString}): ${errorMessage}`, filePath, template, startIndex, fullMatch), startIndex }); } match = pattern.exec(output); } for (let i = replacements.length - 1;i >= 0; i--) { const { original, processed } = replacements[i]; output = output.replace(original, processed); } return output; } function parseDirectiveParams(paramString) { if (!paramString.trim()) { return []; } const params = []; let currentParam = ""; let inQuotes = false; let quoteChar = ""; for (let i = 0;i < paramString.length; i++) { const char = paramString[i]; if ((char === '"' || char === "'") && (i === 0 || paramString[i - 1] !== "\\")) { if (!inQuotes) { inQuotes = true; quoteChar = char; } else if (char === quoteChar) { inQuotes = false; } else { currentParam += char; } } else if (char === "," && !inQuotes) { params.push(currentParam.trim()); currentParam = ""; } else { currentParam += char; } } if (currentParam.trim()) { params.push(currentParam.trim()); } return params.map((param) => { const trimmed = param.trim(); if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) { return trimmed.substring(1, trimmed.length - 1); } return trimmed; }); } // src/forms.ts function genId() { return Math.random().toString(36).substring(2, 15); } function processForms(template, context, _filePath, _options) { let output = template; output = processBasicFormDirectives(output, context); output = processFormInputDirectives(output, context); output = processErrorDirective(output, context); return output; } function processBasicFormDirectives(template, context) { let result = template; result = result.replace(/@csrf/g, () => { if (!context.csrf || typeof context.csrf !== "object") { const token = genId(); context.csrf = { token }; } if (context.csrf.field) { return context.csrf.field; } if (context.csrf.token) { return `<input type="hidden" name="_token" value="${context.csrf.token}">`; } return '<input type="hidden" name="_token" value="">'; }); result = result.replace(/@method\(['"]([^'"]+)['"]\)/g, (match, method) => { if (method && ["PUT", "PATCH", "DELETE"].includes(method.toUpperCase())) { return `<input type="hidden" name="_method" value="${method.toUpperCase()}">`; } return match; }); return result; } function processFormInputDirectives(template, context) { let result = template; result = result.replace(/@form\(\s*(?:(?:'([^']+)'|"([^"]+)")\s*)?(?:,\s*(?:'([^']+)'|"([^"]+)")?)?\s*(?:,\s*\{([^}]+)\}\s*)?\)/g, (match, singleQuoteMethod, doubleQuoteMethod, singleQuoteAction, doubleQuoteAction, attributes = "") => { const method = singleQuoteMethod || doubleQuoteMethod || "POST"; const action = singleQuoteAction || doubleQuoteAction || ""; const attrs = parseAttributes(attributes); const methodStr = method.toUpperCase(); const htmlMethod = ["GET", "POST"].includes(methodStr) ? methodStr : "POST"; let formHtml = `<form method="${htmlMethod}" action="${action}"${attrs ? ` ${attrs}` : ""}>`; formHtml += ` ${processBasicFormDirectives("@csrf", context)}`; if (!["GET", "POST"].includes(methodStr)) { formHtml += ` ${processBasicFormDirectives(`@method('${methodStr}')`, context)}`; } return formHtml; }); result = result.replace(/@endform/g, "</form>"); result = result.replace(/@input\(\s*(?:'([^']+)'|"([^"]+)")\s*(?:,\s*(?:(?:'([^']*)'|"([^"]*)")\s*)?)?(?:,\s*\{([^}]+)\})?\s*\)/g, (match, singleQuoteName, doubleQuoteName, singleQuoteValue, doubleQuoteValue, attributes = "") => { const name = singleQuoteName || doubleQuoteName || ""; const value = singleQuoteValue || doubleQuoteValue || ""; const attrs = parseAttributes(attributes); const oldValue = getOldValue(name, context) || (value || ""); const typeMatch = attrs.match(/type=['"]([^'"]+)['"]/i); const type = typeMatch ? typeMatch[1] : "text"; const hasError = hasFieldError(name, context); const errorClass = hasError ? " is-invalid" : ""; const classMatch = attrs.match(/class=['"]([^'"]+)['"]/i); const existingClass = classMatch ? classMatch[1] : ""; const className = existingClass ? `${existingClass}${errorClass}` : `form-control${errorClass}`; let attrsWithoutClassAndType = attrs.replace(/class=['"][^'"]+['"]/i, ""); attrsWithoutClassAndType = attrsWithoutClassAndType.replace(/type=['"][^'"]+['"]/i, ""); return `<input type="${type}" name="${name}" value="${oldValue}" class="${className}"${attrsWithoutClassAndType ? ` ${attrsWithoutClassAndType}` : ""}>`; }); result = result.replace(/@textarea\(\s*['"]([^'"]+)['"]\s*(?:,\s*\{([^}]+)\})?\)([\s\S]*?)@endtextarea/g, (match, name, attributes = "", content = "") => { const attrs = parseAttributes(attributes); const oldValue = getOldValue(name, context) || content.trim(); const hasError = hasFieldError(name, context); const errorClass = hasError ? " is-invalid" : ""; const classMatch = attrs.match(/class=['"]([^'"]+)['"]/i); const existingClass = classMatch ? classMatch[1] : ""; const className = existingClass ? `${existingClass}${errorClass}` : `form-control${errorClass}`; const attrsWithoutClass = attrs.replace(/class=['"][^'"]+['"]/i, ""); return `<textarea name="${name}" class="${className}"${attrsWithoutClass ? ` ${attrsWithoutClass}` : ""}>${oldValue}</textarea>`; }); result = result.replace(/@select\(\s*['"]([^'"]+)['"]\s*(?:,\s*\{([^}]+)\})?\)([\s\S]*?)@endselect/g, (match, name, attributes = "", content) => { const attrs = parseAttributes(attributes); const oldValue = getOldValue(name, context); const hasError = hasFieldError(name, context); const errorClass = hasError ? " is-invalid" : ""; const classMatch = attrs.match(/class=['"]([^'"]+)['"]/i); const existingClass = classMatch ? classMatch[1] : ""; const className = existingClass ? `${existingClass}${errorClass}` : `form-control${errorClass}`; const attrsWithoutClass = attrs.replace(/class=['"][^'"]+['"]/i, ""); let processedContent = content; if (oldValue !== undefined) { processedContent = content.replace(/(<option[^>]*value=['"]([^'"]+)['"][^>]*>)/gi, (optionMatch, optionTag, optionValue) => { const isSelected = Array.isArray(oldValue) ? oldValue.includes(optionValue) : oldValue === optionValue; if (isSelected && !optionTag.includes("selected")) { return optionTag.replace(/(<option)/, "$1 selected"); } return optionMatch; }); } return `<select name="${name}" class="${className}"${attrsWithoutClass ? ` ${attrsWithoutClass}` : ""}>${processedContent}</select>`; }); result = result.replace(/@checkbox\(\s*['"]([^'"]+)['"]\s*(?:,\s*['"]([^'"]+)['"]\s*)?(?:,\s*\{([^}]+)\})?\)/g, (match, name, value = "1", attributes = "") => { const attrs = parseAttributes(attributes); const oldValues = getOldValue(name, context); const isChecked = oldValues !== undefined && (Array.isArray(oldValues) ? oldValues.includes(value) : oldValues === value || oldValues === true); const classMatch = attrs.match(/class=['"]([^'"]+)['"]/i); const existingClass = classMatch ? classMatch[1] : ""; const className = existingClass || "form-check-input"; const attrsWithoutClass = attrs.replace(/class=['"][^'"]+['"]/i, ""); return `<input type="checkbox" name="${name}" value="${value}" class="${className}"${isChecked ? " checked" : ""}${attrsWithoutClass ? ` ${attrsWithoutClass}` : ""}>`; }); result = result.replace(/@radio\(\s*['"]([^'"]+)['"]\s*,\s*['"]([^'"]+)['"]\s*(?:,\s*\{([^}]+)\})?\)/g, (match, name, value, attributes = "") => { const attrs = parseAttributes(attributes); const oldValue = getOldValue(name, context); const isChecked = oldValue !== undefined && oldValue === value; const classMatch = attrs.match(/class=['"]([^'"]+)['"]/i); const existingClass = classMatch ? classMatch[1] : ""; const className = existingClass || "form-check-input"; const attrsWithoutClass = attrs.replace(/class=['"][^'"]+['"]/i, ""); return `<input type="radio" name="${name}" value="${value}" class="${className}"${isChecked ? " checked" : ""}${attrsWithoutClass ? ` ${attrsWithoutClass}` : ""}>`; }); result = result.replace(/@label\(\s*['"]([^'"]+)['"]\s*(?:,\s*\{([^}]+)\})?\)([\s\S]*?)@endlabel/g, (match, forAttr, attributes = "", content) => { const attrs = parseAttributes(attributes); const classMatch = attrs.match(/class=['"]([^'"]+)['"]/i); const existingClass = classMatch ? classMatch[1] : ""; const className = existingClass || "form-label"; const attrsWithoutClass = attrs.replace(/class=['"][^'"]+['"]/i, ""); return `<label for="${forAttr}" class="${className}"${attrsWithoutClass ? ` ${attrsWithoutClass}` : ""}>${content}</label>`; }); return result; } function processErrorDirective(template, context) { return template.replace(/@error\(['"]([^'"]+)['"]\)([\s\S]*?)@enderror/g, (match, field, content) => { try { if (hasFieldError(field, context)) { return content.replace(/\{\{([^}]+)\}\}/g, (_, expr) => { try { if (expr.trim() === "$message" || expr.trim() === "message") { return getErrorMessage(field, context); } if (expr.trim().includes("errors.first") || expr.trim().includes("$errors.first")) { if (typeof context.errors?.first === "function") { return context.errors.first(field); } return getErrorMessage(field, context); } const evalFn = new Function(...Object.keys(context), ` try { return ${expr.trim()}; } catch (e) { return '${expr.trim()}'; } `); return evalFn(...Object.values(context)); } catch { return expr; } }); } return ""; } catch (error) { console.error(`Error processing @error directive:`, error); return match; } }); } function hasFieldError(field, context) { if (!context.errors) return false; if (typeof context.errors.has === "function") { return context.errors.has(field); } if (typeof context.errors === "object") { return Object.prototype.hasOwnProperty.call(context.errors, field); } return false; } function getErrorMessage(field, context) { if (!context.errors) return ""; if (typeof context.errors.get === "function") { return context.errors.get(field); } if (typeof context.errors === "object" && Object.prototype.hasOwnProperty.call(context.errors, field)) { const error = context.errors[field]; return Array.isArray(error) ? error[0] : String(error); } return ""; } function getOldValue(field, context) { let value; if (context.old && typeof context.old === "function") { return context.old(field); } if (context.old && typeof context.old === "object") { return context.old[field]; } if (field.endsWith("[]")) { const baseName = field.slice(0, -2); value = context[baseName]; if (value !== undefined) { return value; } } return context[field]; } function parseAttributes(attributesStr) { if (!attributesStr.trim()) return ""; const attrs = []; const attrRegex = /([\w-]+)\s*:\s*(['"]?)([^,'"]*)\2(?:,|$)/g; let match; while ((match = attrRegex.exec(attributesStr)) !== null) { const [, name, , value] = match; attrs.push(`${name}="${value.trim()}"`); } return attrs.join(" "); } function processFormDirectives(template, context) { let result = template; result = result.replace(/@csrf/g, () => { if (context.csrf && typeof context.csrf === "object") { if (context.csrf.field) { return context.csrf.field; } if (context.csrf.token) { return `<input type="hidden" name="_token" value="${context.csrf.token}">`; } } return '<input type="hidden" name="_token" value="">'; }); result = result.replace(/@method\(['"]([^'"]+)['"]\)/g, (match, method) => { if (method && ["PUT", "PATCH", "DELETE"].includes(method.toUpperCase())) { return `<input type="hidden" name="_method" value="${method.toUpperCase()}">`; } return match; }); return result; } // src/i18n.ts import fs from "fs"; import path from "path"; var defaultI18nConfig = { defaultLocale: "en", locale: "en", translationsDir: "translations", format: "yaml", fallbackToKey: true, cache: true }; var translationsCache = {}; async function loadTranslation(locale, options) { const i18nConfig = { ...defaultI18nConfig, ...options.i18n }; if (i18nConfig.cache && translationsCache[locale]) { return translationsCache[locale]; } const translationsDir = path.resolve(import.meta.dir, "..", i18nConfig.translationsDir); const fileExtension = getFileExtension(i18nConfig.format); const translationFile = path.join(translationsDir, `${locale}${fileExtension}`); try { let translations = {}; if (i18nConfig.format === "js") { const imported = await import(translationFile); translations = imported.default || imported; } else { const content = await fs.promises.readFile(translationFile, "utf-8"); if (i18nConfig.format === "yaml" || i18nConfig.format === "yml") { translations = await parseYaml(content); } else { translations = JSON.parse(content); } } if (i18nConfig.cache) { translationsCache[locale] = translations; } return translations; } catch (error) { if (options.debug) { console.error(`Error loading translation file for locale "${locale}":`, error); } if (locale !== i18nConfig.defaultLocale) { return loadTranslation(i18nConfig.defaultLocale, options); } return {}; } } async function parseYaml(content) { try { const { parse } = await import("./chunk-04bqmpzb.js"); return parse(content) || {}; } catch (importError) { console.error("Failed to import yaml parser:", importError); try { if (typeof Bun !== "undefined") { return Bun.YAML?.parse?.(content) || {}; } throw new Error("No YAML parser available"); } catch (error) { console.error("Failed to parse YAML content:", error); throw new Error('Could not parse YAML. Please install the "yaml" package or use JSON format instead.'); } } } function getFileExtension(format) { switch (format) { case "yaml": return ".yaml"; case "yml": return ".yml"; case "js": return ".js"; case "json": default: return ".json"; } } function getTranslation(key, translations, fallbackToKey = true, params = {}) { const parts = key.split("."); let value = translations; for (const part of parts) { if (value === undefined || value === null) { break; } value = value[part]; } if (value === undefined || value === null) { return fallbackToKey ? key : ""; } let result = String(value); Object.entries(params).forEach(([paramKey, paramValue]) => { result = result.replace(new RegExp(`:${paramKey}`, "g"), String(paramValue)); }); return result; } async function processTranslateDirective(template, context, filePath, options) { let output = template; const fixedTranslateRegex = /@translate\(\s*['"]([^'"]+)['"]\s*(?:,\s*(\{[^}]*\})\s*)?\)([\s\S]*?)@endtranslate/g; const i18nConfig = { ...defaultI18nConfig, ...options.i18n }; const translations = await loadTranslation(i18nConfig.locale, options); context.__translations = translations; context.__locale = i18nConfig.locale; context.__i18nConfig = i18nConfig; output = await replaceAsync(output, fixedTranslateRegex, async (match, key, paramsStr, content, offset) => { try { let params = {}; if (paramsStr) { try { const approaches = [ () => JSON.parse(paramsStr), () => { const jsonStr = `{"data":${paramsStr}}`; const parsed = JSON.parse(jsonStr); return typeof parsed.data === "object" ? parsed.data : {}; }, () => { const evalFn = new Function(`return ${paramsStr}`); const result = evalFn(); return typeof result === "object" ? result : {}; } ]; for (const approach of approaches) { try { params = approach(); if (Object.keys(params).length > 0) { break; } } catch {} } } catch (error) { if (options.debug) { console.error(`Error parsing parameters for @translate directive:`, error); } } } const translation = getTranslation(key, translations, i18nConfig.fallbackToKey, params); return translation || content.trim(); } catch (error) { if (options.debug) { console.error(`Error processing @translate directive:`, error); } return createDetailedErrorMessage("Translate", `Error in @translate('${key}'): ${error instanceof Error ? error.message : String(error)}`, filePath, template, offset, match); } }); const inlineTranslateRegex = /@translate\(\s*['"]([^'"]+)['"]\s*(?:,\s*(\{[^}]+\})\s*)?\)/g; if (options.debug) { console.warn(`Processing translations in template. Sections: ${output.includes("<h2>Translation with Parameters</h2>") ? "Parameters section found" : "Parameters section missing"}`); const matches = [...output.matchAll(inlineTranslateRegex)]; console.warn(`Found ${matches.length} @translate matches`); matches.forEach((m, i) => console.warn(`Match ${i}: ${m[0]}, Key: ${m[1]}, Params: ${m[2] || "none"}`)); } const fixedInlineTranslateRegex = /@translate\(\s*['"]([^'"]+)['"]\s*(?:,\s*(\{[^}]*\})\s*)?\)/g; output = await replaceAsync(output, fixedInlineTranslateRegex, async (match, key, paramsStr, offset) => { try { let params = {}; if (paramsStr) { try { const approaches = [ () => JSON.parse(paramsStr), () => { const jsonStr = `{"data":${paramsStr}}`; const parsed = JSON.parse(jsonStr); return typeof parsed.data === "object" ? parsed.data : {}; }, () => { const evalFn = new Function(`return ${paramsStr}`); const result = evalFn(); return typeof result === "object" ? result : {}; } ]; for (const approach of approaches) { try { params = approach(); if (Object.keys(params).length > 0) { break; } } catch {} } } catch (error) { if (options.debug) { console.error(`Error parsing parameters for @translate directive:`, error); } } } return getTranslation(key, translations, i18nConfig.fallbackToKey, params); } catch (error) { if (options.debug) { console.error(`Error processing @translate directive:`, error); } return createDetailedErrorMessage("Translate", `Error in @translate('${key}'): ${error instanceof Error ? error.message : String(error)}`, filePath, template, offset, match); } }); return output; } async function replaceAsync(str, regex, asyncFn) { const promises = []; str.replace(regex, (match, ...args) => { const promise = asyncFn(match, ...args).then((replacement) => ({ match, replacement })); promises.push(promise); return match; }); const results = await Promise.all(promises); return results.reduce((str2, { match, replacement }) => str2.replace(match, replacement), str); } function createTranslateFilter(translations, fallbackToKey = true) { return (value, params = {}) => { return getTranslation(value, translations, fallbackToKey, params); }; } // src/includes.ts import fs2 from "fs"; import path2 from "path"; var partialsCache = new Map; async function processIncludes(template, context, filePath, options, dependencies) { const partialsDir = options.partialsDir || path2.join(path2.dirname(filePath), "partials"); let output = template.replace(/@partial\s*\(['"]([^'"]+)['"](?:,\s*(\{[^}]*\}))?\)/g, (_, includePath, varsString) => `@include('${includePath}'${varsString ? `, ${varsString}` : ""})`); output = output.replace(/@includeIf\s*\(['"]([^'"]+)['"](?:,\s*(\{[^}]*\}))?\)/g, (_, includePath, varsString) => { const includeFilePath = resolvePath(includePath, partialsDir, filePath); if (includeFilePath && fs2.existsSync(includeFilePath)) { dependencies.add(includeFilePath); return `@include('${includePath}'${varsString ? `, ${varsString}` : ""})`; } return ""; }); output = output.replace(/@includeWhen\s*\(([^,]+),\s*['"]([^'"]+)['"](?:,\s*(\{[^}]*\}))?\)/g, (match2, condition, includePath, varsString, offset) => { try { const conditionFn = new Function(...Object.keys(context), `return Boolean(${condition})`); const shouldInclude = conditionFn(...Object.values(context)); if (shouldInclude) { const includeFilePath = resolvePath(includePath, partialsDir, filePath); if (includeFilePath && fs2.existsSync(includeFilePath)) { dependencies.add(includeFilePath); } return `@include('${includePath}'${varsString ? `, ${varsString}` : ""})`; } return ""; } catch (error) { return createDetailedErrorMessage("Include", `Error evaluating @includeWhen condition: ${error.message}`, filePath, template, offset, match2); } }); output = output.replace(/@includeUnless\s*\(([^,]+),\s*['"]([^'"]+)['"](?:,\s*(\{[^}]*\}))?\)/g, (match2, condition, includePath, varsString, offset) => { try { const conditionFn = new Function(...Object.keys(context), `return Boolean(${condition})`); const conditionResult = conditionFn(...Object.values(context)); if (!conditionResult) { const includeFilePath = resolvePath(includePath, partialsDir, filePath); if (includeFilePath && fs2.existsSync(includeFilePath)) { dependencies.add(includeFilePath); } return `@include('${includePath}'${varsString ? `, ${varsString}` : ""})`; } return ""; } catch (error) { return createDetailedErrorMessage("Include", `Error evaluating @includeUnless condition: ${error.message}`, filePath, template, offset, match2); } }); const includeFirstRegex = /@includeFirst\s*\(\s*(\[[^\]]+\])\s*(?:,\s*(\{[^}]+\})\s*)?\)/g; let includeFirstMatch; while (includeFirstMatch = includeFirstRegex.exec(output)) { const [fullMatch, pathArrayString, varsString] = includeFirstMatch; const matchOffset = includeFirstMatch.index; try { const pathArray = JSON.parse(pathArrayString.replace(/'/g, '"')); let localVars = {}; if (varsString) { try { const varsFn = new Function(`return ${varsString}`); localVars = varsFn(); } catch (error) { output = output.replace(fullMatch, createDetailedErrorMessage("Include", `Error parsing includeFirst variables: ${error.message}`, filePath, template, matchOffset, fullMatch)); continue; } } let foundValidPath = false; for (const includePath of pathArray) { const includeFilePath = resolvePath(includePath, partialsDir, filePath); if (!includeFilePath) { continue; } if (await fileExists(includeFilePath)) { const processed = await processIncludeHelper(includePath, localVars, template, matchOffset); output = output.replace(fullMatch, processed); foundValidPath = true; break; } } if (!foundValidPath) { output = output.replace(fullMatch, createDetailedErrorMessage("Include", `None of the includeFirst paths exist: ${pathArrayString}`, filePath, template, matchOffset, fullMatch)); } } catch (error) { output = output.replace(fullMatch, createDetailedErrorMessage("Include", `Error processing @includeFirst: ${error.message}`, filePath, template, matchOffset, fullMatch)); } includeFirstRegex.lastIndex = 0; } function resolvePath(includePath, partialsDir2, filePath2) { try { let includeFilePath = includePath; if (!includePath.endsWith(".stx")) { includeFilePath = `${includePath}.stx`; } if (!includeFilePath.startsWith("./") && !includeFilePath.startsWith("../")) { includeFilePath = path2.join(partialsDir2, includeFilePath); } else { includeFilePath = path2.resolve(path2.dirname(filePath2), includeFilePath); } return includeFilePath; } catch (error) { console.error(`Error resolving path ${includePath}: ${error}`); return null; } } const processedIncludes = new Set; async function processIncludeHelper(includePath, localVars = {}, templateStr, offsetPos) { if (processedIncludes.has(includePath)) { return createDetailedErrorMessage("Include", `Circular include detected: ${includePath}`, filePath, templateStr, offsetPos); } processedIncludes.add(includePath); try { const includeFilePath = resolvePath(includePath, partialsDir, filePath); if (!includeFilePath) { return createDetailedErrorMessage("Include", `Could not resolve path for include: ${includePath}`, filePath, templateStr, offsetPos); } dependencies.add(includeFilePath); let partialContent = partialsCache.get(includeFilePath); if (!partialContent) { try { partialContent = await Bun.file(includeFilePath).text(); partialsCache.set(includeFilePath, partialContent); } catch (error) { return createDetailedErrorMessage("Include", `Error loading include file ${includePath}: ${error.message}`, filePath, templateStr, offsetPos); } } const includeContext = { ...context }; for (const [key, value] of Object.entries(localVars)) { includeContext[key] = value; } if (partialContent.includes("@include") || partialContent.includes("@partial")) { partialContent = await processIncludes(partialContent, includeContext, includeFilePath, options, dependencies); } const { processLoops } = await import("./chunk-8ehp5m3y.js"); let processedContent = processLoops(partialContent, includeContext, includeFilePath); processedContent = processConditionals(processedContent, includeContext, includeFilePath); processedContent = processExpressions(processedContent, includeContext, includeFilePath); return processedContent; } catch (error) { return createDetailedErrorMessage("Include", `Error processing include ${includePath}: ${error.message}`, filePath, templateStr, offsetPos); } finally { processedIncludes.delete(includePath); } } const includeRegex = /@include\s*\(['"]([^'"]+)['"](?:,\s*(\{[^}]*\}))?\)/g; let match; while (match = includeRegex.exec(output)) { const [fullMatch, includePath, varsString] = match; const matchOffset = match.index; let localVars = {}; if (varsString) { try { const varsFn = new Function(`return ${varsString}`); localVars = varsFn(); } catch (error) { output = output.replace(fullMatch, createDetailedErrorMessage("Include", `Error parsing include variables for ${includePath}: ${error.message}`, filePath, template, matchOffset)); continue; } } const processedContent = await processIncludeHelper(includePath, localVars, template, matchOffset); output = output.replace(fullMatch, processedContent); includeRegex.lastIndex = 0; } return output; } function processStackPushDirectives(template, stacks) { let result = template; result = result.replace(/@push\(['"]([^'"]+)['"]\)([\s\S]*?)@endpush/g, (match, name, content) => { if (!stacks[name]) { stacks[name] = []; } stacks[name].push(content); return ""; }); result = result.replace(/@prepend\(['"]([^'"]+)['"]\)([\s\S]*?)@endprepend/g, (match, name, content) => { if (!stacks[name]) { stacks[name] = []; } stacks[name].unshift(content); return ""; }); return result; } function processStackReplacements(template, stacks) { return template.replace(/@stack\(['"]([^'"]+)['"]\)/g, (match, name) => { if (!stacks[name] || stacks[name].length === 0) { return ""; } return stacks[name].join(` `); }); } // ../../node_modules/marked/lib/marked.esm.js function _getDefaults() { return { async: false, breaks: false, extensions: null, gfm: true, hooks: null, pedantic: false, renderer: null, silent: false, tokenizer: null, walkTokens: null }; } var _defaults = _getDefaults(); function changeDefaults(newDefaults) { _defaults = newDefaults; } var noopTest = { exec: () => null }; function edit(regex, opt = "") { let source = typeof regex === "string" ? regex : regex.source; const obj = { replace: (name, val) => { let valSource = typeof val === "string" ? val : val.source; valSource = valSource.replace(other.caret, "$1"); source = source.replace(name, valSource); return obj; }, getRegex: () => { return new RegExp(source, opt); } }; return obj; } var other = { codeRemoveIndent: /^(?: {1,4}| {0,3}\t)/gm, outputLinkReplace: /\\([\[\]])/g, indentCodeCompensation: /^(\s+)(?:```)/, beginningSpace: /^\s+/, endingHash: /#$/, startingSpaceChar: /^ /, endingSpaceChar: / $/, nonSpaceChar: /[^ ]/, newLineCharGlobal: /\n/g, tabCharGlobal: /\t/g, multipleSpaceGlobal: /\s+/g, blankLine: /^[ \t]*$/, doubleBlankLine: /\n[ \t]*\n[ \t]*$/, blockquoteStart: /^ {0,3}>/, blockquoteSetextReplace: /\n {0,3}((?:=+|-+) *)(?=\n|$)/g, blockquoteSetextReplace2: /^ {0,3}>[ \t]?/gm, listReplaceTabs: /^\t+/, listReplaceNesting: /^ {1,4}(?=( {4})*[^ ])/g, listIsTask: /^\[[ xX]\] /, listReplaceTask: /^\[[ xX]\] +/, anyLine: /\n.*\n/, hrefBrackets: /^<(.*)>$/, tableDelimiter: /[:|]/, tableAlignChars: /^\||\| *$/g, tableRowBlankLine: /\n[ \t]*$/, tableAlignRight: /^ *-+: *$/, tableAlignCenter: /^ *:-+: *$/, tableAlignLeft: /^ *:-+ *$/, startATag: /^<a /i, endATag: /^<\/a>/i, startPreScriptTag: /^<(pre|code|kbd|script)(\s|>)/i, endPreScriptTag: /^<\/(pre|code|kbd|script)(\s|>)/i, startAngleBracket: /^</, endAngleBracket: />$/, pedanticHrefTitle: /^([^'"]*[^\s])\s+(['"])(.*)\2/, unicodeAlphaNumeric: /[\p{L}\p{N}]/u, escapeTest: /[&<>"']/, escapeReplace: /[&<>"']/g, escapeTestNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/, escapeReplaceNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g, unescapeTest: /&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig, caret: /(^|[^\[])\^/g, percentDecode: /%25/g, findPipe: /\|/g, splitPipe: / \|/, slashPipe: /\\\|/g, carriageReturn: /\r\n|\r/g, spaceLine: /^ +$/gm, notSpaceStart: /^\S*/, endingNewline: /\n$/, listItemRegex: (bull) => new RegExp(`^( {0,3}${bull})((?:[ ][^\\n]*)?(?:\\n|$))`), nextBulletRegex: (indent) => new RegExp(`^ {0,${Math.min(3, indent - 1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ ][^\\n]*)?(?:\\n|$))`), hrRegex: (indent) => new RegExp(`^ {0,${Math.min(3, indent - 1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`), fencesBeginRegex: (indent) => new RegExp(`^ {0,${Math.min(3, indent - 1)}}(?:\`\`\`|~~~)`), headingBeginRegex: (indent) => new RegExp(`^ {0,${Math.min(3, indent - 1)}}#`), htmlBeginRegex: (indent) => new RegExp(`^ {0,${Math.min(3, indent - 1)}}<(?:[a-z].*>|!--)`, "i") }; var newline = /^(?:[ \t]*(?:\n|$))+/; var blockCode = /^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/; var fences = /^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/; var hr = /^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/; var heading = /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/; var bullet = /(?:[*+-]|\d{1,9}[.)])/; var lheadingCore = /^(?!bull |blockCode|fences|blockquote|heading|html|table)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html|table))+?)\n {0,3}(=+|-+) *(?:\n+|$)/; var lheading = edit(lheadingCore).replace(/bull/g, bullet).replace(/blockCode/g, /(?: {4}| {0,3}\t)/).replace(/fences/g, / {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g, / {0,3}>/).replace(/heading/g, / {0,3}#{1,6}/).replace(/html/g, / {0,3}<[^\n>]+>\n/).replace(/\|table/g, "").getRegex(); var lheadingGfm = edit(lheadingCore).replace(/bull/g, bullet).replace(/blockCode/g, /(?: {4}| {0,3}\t)/).replace(/fences/g, / {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g, / {0,3}>/).replace(/heading/g, / {0,3}#{1,6}/).replace(/html/g, / {0,3}<[^\n>]+>\n/).replace(/table/g, / {0,3}\|?(?:[:\- ]*\|)+[\:\- ]*\n/).getRegex(); var _paragraph = /^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/; var blockText = /^[^\n]+/; var _blockLabel = /(?!\s*\])(?:\\.|[^\[\]\\])+/; var def = edit(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label", _blockLabel).replace("title", /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex(); var list = edit(/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/).replace(/bull/g, bullet).getRegex(); var _tag = "address|article|aside|base|basefont|blockquote|body|caption" + "|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption" + "|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe" + "|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option" + "|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title" + "|tr|track|ul"; var _comment = /<!--(?:-?>|[\s\S]*?(?:-->|$))/; var html = edit("^ {0,3}(?:" + "<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n+|$)" + "|comment[^\\n]*(\\n+|$)" + "|<\\?[\\s\\S]*?(?:\\?>\\n*|$)" + "|<![A-Z][\\s\\S]*?(?:>\\n*|$)" + "|<!\\[CDATA\\[[\\s\\S]*?(?:\\]\\]>\\n*|$)" + "|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)" + "|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)" + "|</(?!script|pre|style|textarea)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)" + ")", "i").replace("comment", _comment).replace("tag", _tag).replace("attribute", / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(); var paragraph = edit(_paragraph).replace("hr", hr).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("|table", "").replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)]) ").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", _tag).getRegex(); var blockquote = edit(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph", paragraph).getRegex(); var blockNormal = { blockquote, code: blockCode, def, fences, heading, hr, html, lheading, list, newline, paragraph, table: noopTest, text: blockText }; var gfmTable = edit("^ *([^\\n ].*)\\n" + " {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)" + "(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)").replace("hr", hr).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("blockquote", " {0,3}>").replace("code", "(?: {4}| {0,3} )[^\\n]").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)]) ").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", _tag).getRegex(); var blockGfm = { ...blockNormal, lheading: lheadingGfm, table: gfmTable, paragraph: edit(_paragraph).replace("hr", hr).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("table", gfmTable).replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)]) ").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", _tag).getRegex() }; var blockPedantic = { ...blockNormal, html: edit("^ *(?:comment *(?:\\n|\\s*$)" + "|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)" + `|<tag(?:"[^"]*"|'[^']*'|\\s[^'"/>\\s]*)*?/?> *(?:\\n{2,}|\\s*$))`).replace("comment", _comment).replace(/tag/g, "(?!(?:" + "a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub" + "|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)" + "\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(), def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/, heading: /^(#{1,6})(.*)(?:\n+|$)/, fences: noopTest, lheading: /^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/, paragraph: edit(_paragraph).replace("hr", hr).replace("heading", ` *#{1,6} *[^ ]`).replace("lheading", lheading).replace("|table", "").replace("blockquote", " {0,3}>").replace("|fences", "").replace("|list", "").replace("|html", "").replace("|tag", "").getRegex() }; var escape$1 = /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/; var inlineCode = /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/; var br = /^( {2,}|\\)\n(?!\s*$)/; var inlineText = /^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*_]|\b_|$)|[^ ](?= {2,}\n)))/; var _punctuation = /[\p{P}\p{S}]/u; var _punctuationOrSpace = /[\s\p{P}\p{S}]/u; var _notPunctuationOrSpace = /[^\s\p{P}\p{S}]/u; var punctuation = edit(/^((?![*_])punctSpace)/, "u").replace(/punctSpace/g, _punctuationOrSpace).getRegex(); var _punctuationGfmStrongEm = /(?!~)[\p{P}\p{S}]/u; var _punctuationOrSpaceGfmStrongEm = /(?!~)[\s\p{P}\p{S}]/u; var _notPunctuationOrSpaceGfmStrongEm = /(?:[^\s\p{P}\p{S}]|~)/u; var blockSkip = /\[[^