@maizzle/framework 6.0.0-rc.19 → 6.0.0-rc.20
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.d.ts.map +1 -1
- package/dist/build.js +2 -1
- package/dist/build.js.map +1 -1
- package/dist/config/index.js +6 -3
- package/dist/config/index.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/prepare.d.ts.map +1 -1
- package/dist/prepare.js +2 -1
- package/dist/prepare.js.map +1 -1
- package/dist/render/createRenderer.d.ts +6 -2
- package/dist/render/createRenderer.d.ts.map +1 -1
- package/dist/render/createRenderer.js +93 -6
- package/dist/render/createRenderer.js.map +1 -1
- package/dist/render/index.d.ts.map +1 -1
- package/dist/render/index.js +2 -1
- package/dist/render/index.js.map +1 -1
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +5 -4
- package/dist/serve.js.map +1 -1
- package/dist/server/compatibility.d.ts +3 -1
- package/dist/server/compatibility.d.ts.map +1 -1
- package/dist/server/compatibility.js +2 -1
- package/dist/server/compatibility.js.map +1 -1
- package/dist/server/linter.d.ts +4 -2
- package/dist/server/linter.d.ts.map +1 -1
- package/dist/server/linter.js +2 -2
- package/dist/server/linter.js.map +1 -1
- package/dist/server/sfc-utils.d.ts +5 -2
- package/dist/server/sfc-utils.d.ts.map +1 -1
- package/dist/server/sfc-utils.js +25 -10
- package/dist/server/sfc-utils.js.map +1 -1
- package/dist/types/config.d.ts +38 -3
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/utils/componentSources.d.ts +50 -0
- package/dist/utils/componentSources.d.ts.map +1 -0
- package/dist/utils/componentSources.js +50 -0
- package/dist/utils/componentSources.js.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"linter.js","names":[],"sources":["../../src/server/linter.ts"],"sourcesContent":["import { readFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { parseSfcBlocks, findComponentTags, buildComponentMap } from './sfc-utils.ts'\nimport type { MaizzleConfig } from '../types/index.ts'\n\nexport interface LintIssue {\n type: 'error' | 'warning'\n title: string\n message: string\n /** Which tab this lands in when merged into the Checks panel. */\n category: 'css' | 'html' | 'image' | 'others'\n /** Optional caniemail slug for URL enrichment (e.g. \"html-html\"). */\n slug?: string\n line?: number\n file: string\n}\n\ninterface Presence {\n html: boolean\n head: boolean\n body: boolean\n}\n\n/**\n * Maizzle auto-adds role=\"none\" to every <table> by default via the\n * addAttributes transformer. Warn about missing role only when that won't\n * happen:\n * - useTransformers: false → whole pipeline off\n * - html.attributes.add: false → auto-add disabled globally\n * - add.table: false → table selector opted out\n * - add.table.role: false → role attribute specifically opted out\n * An empty `add.table: {}` still inherits role via defu merge, so it's fine.\n */\nfunction tableRoleAutoAdded(config: MaizzleConfig): boolean {\n if (config.useTransformers === false) return false\n const add = config.html?.attributes?.add\n if (add === false) return false\n if (!add || typeof add !== 'object') return true\n const table = (add as any).table\n if (table === false) return false\n if (table && typeof table === 'object' && table.role === false) return false\n return true\n}\n\nexport async function scanLint(\n rootFile: string,\n config: MaizzleConfig,\n componentDirs: string[],\n): Promise<LintIssue[]> {\n const root = config.root ?? process.cwd()\n const componentMap = await buildComponentMap(root, componentDirs)\n const visited = new Set<string>()\n const presence: Presence = { html: false, head: false, body: false }\n const checkTableRole = !tableRoleAutoAdded(config)\n const issues = checkFile(rootFile, componentMap, visited, presence, checkTableRole)\n\n if (!presence.html) issues.push({ type: 'warning', category: 'html', title: 'Missing <html>', message: 'Root <html> tag not found in the template or any of its components.', slug: 'html-html', line: 1, file: rootFile })\n if (!presence.head) issues.push({ type: 'warning', category: 'html', title: 'Missing <head>', message: 'Root <head> tag not found in the template or any of its components.', slug: 'html-head', line: 1, file: rootFile })\n if (!presence.body) issues.push({ type: 'warning', category: 'html', title: 'Missing <body>', message: 'Root <body> tag not found in the template or any of its components.', slug: 'html-body', line: 1, file: rootFile })\n\n return issues\n}\n\nexport async function serveLint(url: string, res: any, config: MaizzleConfig, componentDirs: string[]) {\n const filePath = url.replace('/__maizzle/lint/', '').replace(/\\?.*$/, '')\n\n try {\n const absolutePath = resolve(filePath)\n const issues = await scanLint(absolutePath, config, componentDirs)\n\n // Sort: errors first, then warnings, then by line\n issues.sort((a, b) => {\n if (a.type !== b.type) return a.type === 'error' ? -1 : 1\n return (a.line ?? 0) - (b.line ?? 0)\n })\n\n res.setHeader('Content-Type', 'application/json')\n res.end(JSON.stringify(issues))\n } catch (error: any) {\n res.statusCode = 500\n res.end(JSON.stringify({ error: error.message }))\n }\n}\n\nfunction checkFile(\n filePath: string,\n componentMap: Map<string, string>,\n visited: Set<string>,\n presence: Presence,\n checkTableRole: boolean,\n): LintIssue[] {\n if (visited.has(filePath)) return []\n visited.add(filePath)\n\n let source: string\n try {\n source = readFileSync(filePath, 'utf-8')\n } catch {\n return []\n }\n\n const { template } = parseSfcBlocks(source)\n const issues: LintIssue[] = []\n\n if (template) {\n issues.push(...lintHtml(template.content, template.offset, filePath, presence, checkTableRole))\n\n // Recurse into components\n const componentTags = findComponentTags(template.content)\n for (const tag of componentTags) {\n const componentPath = componentMap.get(tag.toLowerCase())\n if (componentPath) {\n issues.push(...checkFile(componentPath, componentMap, visited, presence, checkTableRole))\n }\n }\n }\n\n return issues\n}\n\nfunction lineAt(html: string, offset: number, lineOffset: number): number {\n return html.slice(0, offset).split('\\n').length + lineOffset\n}\n\n/**\n * True if the <img> tag has a width defined via any of:\n * - `width` attribute\n * - inline `style` with a `width` property\n * - class attribute with a Tailwind `w-` utility (any variant prefix like\n * sm:, hover:), or an arbitrary `[width:…]` utility\n */\nfunction hasWidthDefined(imgTag: string): boolean {\n if (/\\bwidth\\s*=/i.test(imgTag)) return true\n\n const styleMatch = imgTag.match(/\\bstyle\\s*=\\s*[\"']([^\"']*)[\"']/i)\n if (styleMatch && /(^|[;\\s])width\\s*:/i.test(styleMatch[1])) return true\n\n const classMatch = imgTag.match(/\\bclass\\s*=\\s*[\"']([^\"']*)[\"']/i)\n if (classMatch) {\n const classes = classMatch[1]\n if (/(?:^|\\s)(?:[a-z0-9-]+:)*w-\\S+/i.test(classes)) return true\n if (/\\[width:/i.test(classes)) return true\n }\n return false\n}\n\nfunction lintHtml(html: string, lineOffset: number, filePath: string, presence: Presence, checkTableRole: boolean): LintIssue[] {\n const issues: LintIssue[] = []\n\n // Match all tags (multiline) — [^>] doesn't cross > so use [\\s\\S] with lazy quantifier\n const tagRe = /<([a-zA-Z][a-zA-Z0-9]*)\\b([\\s\\S]*?)>/g\n\n for (const m of Array.from(html.matchAll(tagRe))) {\n const tag = m[0]\n const tagName = m[1].toLowerCase()\n const line = lineAt(html, m.index!, lineOffset)\n\n if (tagName === 'html') presence.html = true\n else if (tagName === 'head') presence.head = true\n else if (tagName === 'body') presence.body = true\n\n // Layout tables — accessibility requires role=\"none\" so screen readers\n // skip the table structure. Only surface the warning when the user has\n // disabled Maizzle's auto-role-add; otherwise every build-step output\n // already has role=\"none\" set.\n if (checkTableRole && tagName === 'table') {\n const roleMatch = tag.match(/\\brole\\s*=\\s*[\"']([^\"']*)[\"']/i)\n if (!roleMatch) {\n const tableEndIdx = html.indexOf('</table>', m.index!)\n const inner = tableEndIdx >= 0 ? html.slice(m.index!, tableEndIdx) : ''\n const isDataTable = /<th\\b/i.test(inner) || /<caption\\b/i.test(inner)\n if (!isDataTable) {\n issues.push({ type: 'warning', category: 'html', title: 'Layout table missing role', message: 'Add role=\"none\" so screen readers skip this layout table.', slug: 'html-role', line, file: filePath })\n }\n }\n }\n\n // Images\n if (tagName === 'img') {\n if (!/\\balt\\s*=/i.test(tag)) {\n issues.push({ type: 'warning', category: 'image', title: 'Missing alt text', message: 'Image is missing the alt attribute', line, file: filePath })\n }\n\n const srcMatch = tag.match(/\\bsrc\\s*=\\s*[\"']([^\"']*)[\"']/i)\n if (!srcMatch) {\n issues.push({ type: 'error', category: 'image', title: 'Missing image src', message: 'Image tag has no src attribute', line, file: filePath })\n } else if (!srcMatch[1].trim()) {\n issues.push({ type: 'error', category: 'image', title: 'Empty image src', message: 'Image src attribute is empty', line, file: filePath })\n } else if (srcMatch[1].trim().startsWith('http:')) {\n issues.push({ type: 'warning', category: 'image', title: 'Insecure image src', message: 'Image loads over HTTP instead of HTTPS', line, file: filePath })\n }\n\n if (!hasWidthDefined(tag)) {\n issues.push({ type: 'warning', category: 'image', title: 'Missing image width', message: 'Use a `width=\"\"` attribute for best results in Outlook', line, file: filePath })\n }\n }\n\n // Any tag with href — skip resource tags handled below\n if (!['link', 'script', 'source'].includes(tagName)) {\n const hrefMatch = tag.match(/\\bhref\\s*=\\s*[\"']([^\"']*)[\"']/i)\n if (hrefMatch) {\n const href = hrefMatch[1].trim()\n if (!href) {\n issues.push({ type: 'error', category: 'html', title: 'Empty link href', message: 'Link href attribute is empty', line, file: filePath })\n } else if (href === '#' || href === '/') {\n issues.push({ type: 'error', category: 'html', title: 'Placeholder link', message: `Link href is \"${href}\"`, line, file: filePath })\n } else if (href.startsWith('http:')) {\n issues.push({ type: 'warning', category: 'html', title: 'Insecure link', message: 'Link uses HTTP instead of HTTPS', line, file: filePath })\n } else if (href.startsWith('http') && !/^https?:\\/\\/.+\\..+/i.test(href)) {\n issues.push({ type: 'error', category: 'html', title: 'Invalid link', message: `Link href \"${href}\" looks malformed`, line, file: filePath })\n }\n }\n }\n\n // Insecure resources (<link>, <script>, <source>)\n if (['link', 'script', 'source'].includes(tagName)) {\n const attrMatch = tag.match(/\\b(?:href|src)\\s*=\\s*[\"']([^\"']*)[\"']/i)\n if (attrMatch && attrMatch[1].trim().startsWith('http:')) {\n issues.push({ type: 'warning', category: 'html', title: 'Insecure resource', message: 'Resource loads over HTTP instead of HTTPS', line, file: filePath })\n }\n }\n }\n\n // Insecure CSS url() references\n for (const m of Array.from(html.matchAll(/url\\s*\\(\\s*[\"']?(http:[^\"')]+)[\"']?\\s*\\)/gi))) {\n issues.push({ type: 'warning', category: 'css', title: 'Insecure CSS url()', message: 'CSS url() loads over HTTP instead of HTTPS', line: lineAt(html, m.index!, lineOffset), file: filePath })\n }\n\n // Check for unclosed tags (block-level and common inline elements)\n const voidElements = new Set([\n 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',\n 'link', 'meta', 'param', 'source', 'track', 'wbr',\n ])\n\n const trackedTags = new Set([\n 'a', 'b', 'body', 'div', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',\n 'head', 'html', 'i', 'li', 'ol', 'p', 'span', 'strong', 'style',\n 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'title', 'tr', 'u', 'ul',\n ])\n\n const stack: Array<{ tag: string, line: number }> = []\n\n // Strip comments and content inside <style>/<script> to avoid false matches\n const stripped = html\n .replace(/<!--[\\s\\S]*?-->/g, (m) => '\\n'.repeat((m.match(/\\n/g) || []).length))\n .replace(/<(style|script)\\b[^>]*>[\\s\\S]*?<\\/\\1>/gi, (m) => '\\n'.repeat((m.match(/\\n/g) || []).length))\n\n const strippedLines = stripped.split('\\n')\n\n for (let i = 0; i < strippedLines.length; i++) {\n const line = strippedLines[i]\n const tagRegex = /<\\/?([a-zA-Z][a-zA-Z0-9]*)\\b[^>]*\\/?>/g\n let m\n\n while ((m = tagRegex.exec(line)) !== null) {\n const fullMatch = m[0]\n const tagName = m[1].toLowerCase()\n\n if (!trackedTags.has(tagName) || voidElements.has(tagName)) continue\n if (fullMatch.endsWith('/>')) continue\n\n if (fullMatch.startsWith('</')) {\n // Closing tag\n let lastOpen = -1\n for (let j = stack.length - 1; j >= 0; j--) {\n if (stack[j].tag === tagName) { lastOpen = j; break }\n }\n if (lastOpen !== -1) {\n stack.splice(lastOpen, 1)\n }\n } else {\n // Opening tag\n stack.push({ tag: tagName, line: i + 1 + lineOffset })\n }\n }\n }\n\n for (const unclosed of stack) {\n issues.push({\n type: 'error',\n category: 'html',\n title: 'Unclosed tag',\n message: `<${unclosed.tag}> tag is not closed`,\n line: unclosed.line,\n file: filePath,\n })\n }\n\n return issues\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"linter.js","names":[],"sources":["../../src/server/linter.ts"],"sourcesContent":["import { readFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { parseSfcBlocks, findComponentTags, buildComponentMap, isFrameworkComponent } from './sfc-utils.ts'\nimport type { MaizzleConfig } from '../types/index.ts'\nimport type { NormalizedComponentSource } from '../utils/componentSources.ts'\n\nexport interface LintIssue {\n type: 'error' | 'warning'\n title: string\n message: string\n /** Which tab this lands in when merged into the Checks panel. */\n category: 'css' | 'html' | 'image' | 'others'\n /** Optional caniemail slug for URL enrichment (e.g. \"html-html\"). */\n slug?: string\n line?: number\n file: string\n}\n\ninterface Presence {\n html: boolean\n head: boolean\n body: boolean\n}\n\n/**\n * Maizzle auto-adds role=\"none\" to every <table> by default via the\n * addAttributes transformer. Warn about missing role only when that won't\n * happen:\n * - useTransformers: false → whole pipeline off\n * - html.attributes.add: false → auto-add disabled globally\n * - add.table: false → table selector opted out\n * - add.table.role: false → role attribute specifically opted out\n * An empty `add.table: {}` still inherits role via defu merge, so it's fine.\n */\nfunction tableRoleAutoAdded(config: MaizzleConfig): boolean {\n if (config.useTransformers === false) return false\n const add = config.html?.attributes?.add\n if (add === false) return false\n if (!add || typeof add !== 'object') return true\n const table = (add as any).table\n if (table === false) return false\n if (table && typeof table === 'object' && table.role === false) return false\n return true\n}\n\nexport async function scanLint(\n rootFile: string,\n config: MaizzleConfig,\n componentDirs: NormalizedComponentSource[],\n): Promise<LintIssue[]> {\n const root = config.root ?? process.cwd()\n const componentMap = await buildComponentMap(root, componentDirs)\n const visited = new Set<string>()\n const presence: Presence = { html: false, head: false, body: false }\n const checkTableRole = !tableRoleAutoAdded(config)\n const issues = checkFile(rootFile, componentMap, visited, presence, checkTableRole)\n\n if (!presence.html) issues.push({ type: 'warning', category: 'html', title: 'Missing <html>', message: 'Root <html> tag not found in the template or any of its components.', slug: 'html-html', line: 1, file: rootFile })\n if (!presence.head) issues.push({ type: 'warning', category: 'html', title: 'Missing <head>', message: 'Root <head> tag not found in the template or any of its components.', slug: 'html-head', line: 1, file: rootFile })\n if (!presence.body) issues.push({ type: 'warning', category: 'html', title: 'Missing <body>', message: 'Root <body> tag not found in the template or any of its components.', slug: 'html-body', line: 1, file: rootFile })\n\n return issues\n}\n\nexport async function serveLint(url: string, res: any, config: MaizzleConfig, componentDirs: NormalizedComponentSource[]) {\n const filePath = url.replace('/__maizzle/lint/', '').replace(/\\?.*$/, '')\n\n try {\n const absolutePath = resolve(filePath)\n const issues = (await scanLint(absolutePath, config, componentDirs))\n .filter(i => !isFrameworkComponent(i.file))\n\n // Sort: errors first, then warnings, then by line\n issues.sort((a, b) => {\n if (a.type !== b.type) return a.type === 'error' ? -1 : 1\n return (a.line ?? 0) - (b.line ?? 0)\n })\n\n res.setHeader('Content-Type', 'application/json')\n res.end(JSON.stringify(issues))\n } catch (error: any) {\n res.statusCode = 500\n res.end(JSON.stringify({ error: error.message }))\n }\n}\n\nfunction checkFile(\n filePath: string,\n componentMap: Map<string, string>,\n visited: Set<string>,\n presence: Presence,\n checkTableRole: boolean,\n): LintIssue[] {\n if (visited.has(filePath)) return []\n visited.add(filePath)\n\n let source: string\n try {\n source = readFileSync(filePath, 'utf-8')\n } catch {\n return []\n }\n\n const { template } = parseSfcBlocks(source)\n const issues: LintIssue[] = []\n\n if (template) {\n issues.push(...lintHtml(template.content, template.offset, filePath, presence, checkTableRole))\n\n // Recurse into components\n const componentTags = findComponentTags(template.content)\n for (const tag of componentTags) {\n const componentPath = componentMap.get(tag.toLowerCase())\n if (componentPath) {\n issues.push(...checkFile(componentPath, componentMap, visited, presence, checkTableRole))\n }\n }\n }\n\n return issues\n}\n\nfunction lineAt(html: string, offset: number, lineOffset: number): number {\n return html.slice(0, offset).split('\\n').length + lineOffset\n}\n\n/**\n * True if the <img> tag has a width defined via any of:\n * - `width` attribute\n * - inline `style` with a `width` property\n * - class attribute with a Tailwind `w-` utility (any variant prefix like\n * sm:, hover:), or an arbitrary `[width:…]` utility\n */\nfunction hasWidthDefined(imgTag: string): boolean {\n if (/\\bwidth\\s*=/i.test(imgTag)) return true\n\n const styleMatch = imgTag.match(/\\bstyle\\s*=\\s*[\"']([^\"']*)[\"']/i)\n if (styleMatch && /(^|[;\\s])width\\s*:/i.test(styleMatch[1])) return true\n\n const classMatch = imgTag.match(/\\bclass\\s*=\\s*[\"']([^\"']*)[\"']/i)\n if (classMatch) {\n const classes = classMatch[1]\n if (/(?:^|\\s)(?:[a-z0-9-]+:)*w-\\S+/i.test(classes)) return true\n if (/\\[width:/i.test(classes)) return true\n }\n return false\n}\n\nfunction lintHtml(html: string, lineOffset: number, filePath: string, presence: Presence, checkTableRole: boolean): LintIssue[] {\n const issues: LintIssue[] = []\n\n // Match all tags (multiline) — [^>] doesn't cross > so use [\\s\\S] with lazy quantifier\n const tagRe = /<([a-zA-Z][a-zA-Z0-9]*)\\b([\\s\\S]*?)>/g\n\n for (const m of Array.from(html.matchAll(tagRe))) {\n const tag = m[0]\n const tagName = m[1].toLowerCase()\n const line = lineAt(html, m.index!, lineOffset)\n\n if (tagName === 'html') presence.html = true\n else if (tagName === 'head') presence.head = true\n else if (tagName === 'body') presence.body = true\n\n // Layout tables — accessibility requires role=\"none\" so screen readers\n // skip the table structure. Only surface the warning when the user has\n // disabled Maizzle's auto-role-add; otherwise every build-step output\n // already has role=\"none\" set.\n if (checkTableRole && tagName === 'table') {\n const roleMatch = tag.match(/\\brole\\s*=\\s*[\"']([^\"']*)[\"']/i)\n if (!roleMatch) {\n const tableEndIdx = html.indexOf('</table>', m.index!)\n const inner = tableEndIdx >= 0 ? html.slice(m.index!, tableEndIdx) : ''\n const isDataTable = /<th\\b/i.test(inner) || /<caption\\b/i.test(inner)\n if (!isDataTable) {\n issues.push({ type: 'warning', category: 'html', title: 'Layout table missing role', message: 'Add role=\"none\" so screen readers skip this layout table.', slug: 'html-role', line, file: filePath })\n }\n }\n }\n\n // Images\n if (tagName === 'img') {\n if (!/\\balt\\s*=/i.test(tag)) {\n issues.push({ type: 'warning', category: 'image', title: 'Missing alt text', message: 'Image is missing the alt attribute', line, file: filePath })\n }\n\n const srcMatch = tag.match(/\\bsrc\\s*=\\s*[\"']([^\"']*)[\"']/i)\n if (!srcMatch) {\n issues.push({ type: 'error', category: 'image', title: 'Missing image src', message: 'Image tag has no src attribute', line, file: filePath })\n } else if (!srcMatch[1].trim()) {\n issues.push({ type: 'error', category: 'image', title: 'Empty image src', message: 'Image src attribute is empty', line, file: filePath })\n } else if (srcMatch[1].trim().startsWith('http:')) {\n issues.push({ type: 'warning', category: 'image', title: 'Insecure image src', message: 'Image loads over HTTP instead of HTTPS', line, file: filePath })\n }\n\n if (!hasWidthDefined(tag)) {\n issues.push({ type: 'warning', category: 'image', title: 'Missing image width', message: 'Use a `width=\"\"` attribute for best results in Outlook', line, file: filePath })\n }\n }\n\n // Any tag with href — skip resource tags handled below\n if (!['link', 'script', 'source'].includes(tagName)) {\n const hrefMatch = tag.match(/\\bhref\\s*=\\s*[\"']([^\"']*)[\"']/i)\n if (hrefMatch) {\n const href = hrefMatch[1].trim()\n if (!href) {\n issues.push({ type: 'error', category: 'html', title: 'Empty link href', message: 'Link href attribute is empty', line, file: filePath })\n } else if (href === '#' || href === '/') {\n issues.push({ type: 'error', category: 'html', title: 'Placeholder link', message: `Link href is \"${href}\"`, line, file: filePath })\n } else if (href.startsWith('http:')) {\n issues.push({ type: 'warning', category: 'html', title: 'Insecure link', message: 'Link uses HTTP instead of HTTPS', line, file: filePath })\n } else if (href.startsWith('http') && !/^https?:\\/\\/.+\\..+/i.test(href)) {\n issues.push({ type: 'error', category: 'html', title: 'Invalid link', message: `Link href \"${href}\" looks malformed`, line, file: filePath })\n }\n }\n }\n\n // Insecure resources (<link>, <script>, <source>)\n if (['link', 'script', 'source'].includes(tagName)) {\n const attrMatch = tag.match(/\\b(?:href|src)\\s*=\\s*[\"']([^\"']*)[\"']/i)\n if (attrMatch && attrMatch[1].trim().startsWith('http:')) {\n issues.push({ type: 'warning', category: 'html', title: 'Insecure resource', message: 'Resource loads over HTTP instead of HTTPS', line, file: filePath })\n }\n }\n }\n\n // Insecure CSS url() references\n for (const m of Array.from(html.matchAll(/url\\s*\\(\\s*[\"']?(http:[^\"')]+)[\"']?\\s*\\)/gi))) {\n issues.push({ type: 'warning', category: 'css', title: 'Insecure CSS url()', message: 'CSS url() loads over HTTP instead of HTTPS', line: lineAt(html, m.index!, lineOffset), file: filePath })\n }\n\n // Check for unclosed tags (block-level and common inline elements)\n const voidElements = new Set([\n 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',\n 'link', 'meta', 'param', 'source', 'track', 'wbr',\n ])\n\n const trackedTags = new Set([\n 'a', 'b', 'body', 'div', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',\n 'head', 'html', 'i', 'li', 'ol', 'p', 'span', 'strong', 'style',\n 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'title', 'tr', 'u', 'ul',\n ])\n\n const stack: Array<{ tag: string, line: number }> = []\n\n // Strip comments and content inside <style>/<script> to avoid false matches\n const stripped = html\n .replace(/<!--[\\s\\S]*?-->/g, (m) => '\\n'.repeat((m.match(/\\n/g) || []).length))\n .replace(/<(style|script)\\b[^>]*>[\\s\\S]*?<\\/\\1>/gi, (m) => '\\n'.repeat((m.match(/\\n/g) || []).length))\n\n const strippedLines = stripped.split('\\n')\n\n for (let i = 0; i < strippedLines.length; i++) {\n const line = strippedLines[i]\n const tagRegex = /<\\/?([a-zA-Z][a-zA-Z0-9]*)\\b[^>]*\\/?>/g\n let m\n\n while ((m = tagRegex.exec(line)) !== null) {\n const fullMatch = m[0]\n const tagName = m[1].toLowerCase()\n\n if (!trackedTags.has(tagName) || voidElements.has(tagName)) continue\n if (fullMatch.endsWith('/>')) continue\n\n if (fullMatch.startsWith('</')) {\n // Closing tag\n let lastOpen = -1\n for (let j = stack.length - 1; j >= 0; j--) {\n if (stack[j].tag === tagName) { lastOpen = j; break }\n }\n if (lastOpen !== -1) {\n stack.splice(lastOpen, 1)\n }\n } else {\n // Opening tag\n stack.push({ tag: tagName, line: i + 1 + lineOffset })\n }\n }\n }\n\n for (const unclosed of stack) {\n issues.push({\n type: 'error',\n category: 'html',\n title: 'Unclosed tag',\n message: `<${unclosed.tag}> tag is not closed`,\n line: unclosed.line,\n file: filePath,\n })\n }\n\n return issues\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { NormalizedComponentSource } from "../utils/componentSources.js";
|
|
2
|
+
|
|
1
3
|
//#region src/server/sfc-utils.d.ts
|
|
4
|
+
declare function isFrameworkComponent(file: string): boolean;
|
|
2
5
|
interface SfcBlock {
|
|
3
6
|
content: string;
|
|
4
7
|
offset: number;
|
|
@@ -12,7 +15,7 @@ declare function parseSfcBlocks(source: string): {
|
|
|
12
15
|
*/
|
|
13
16
|
declare const HTML_ELEMENTS: Set<string>;
|
|
14
17
|
declare function findComponentTags(templateContent: string): string[];
|
|
15
|
-
declare function buildComponentMap(root: string, componentDirs:
|
|
18
|
+
declare function buildComponentMap(root: string, componentDirs: NormalizedComponentSource[]): Promise<Map<string, string>>;
|
|
16
19
|
//#endregion
|
|
17
|
-
export { HTML_ELEMENTS, SfcBlock, buildComponentMap, findComponentTags, parseSfcBlocks };
|
|
20
|
+
export { HTML_ELEMENTS, SfcBlock, buildComponentMap, findComponentTags, isFrameworkComponent, parseSfcBlocks };
|
|
18
21
|
//# sourceMappingURL=sfc-utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sfc-utils.d.ts","names":[],"sources":["../../src/server/sfc-utils.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"sfc-utils.d.ts","names":[],"sources":["../../src/server/sfc-utils.ts"],"mappings":";;;iBAegB,oBAAA,CAAqB,IAAA;AAAA,UAIpB,QAAA;EACf,OAAA;EACA,MAAA;AAAA;AAAA,iBAGc,cAAA,CAAe,MAAA;EAAmB,QAAA,EAAU,QAAA;EAAiB,MAAA,EAAQ,QAAA;AAAA;;;;cA4BxE,aAAA,EAAa,GAAA;AAAA,iBAgBV,iBAAA,CAAkB,eAAA;AAAA,iBAqBZ,iBAAA,CACpB,IAAA,UACA,aAAA,EAAe,yBAAA,KACd,OAAA,CAAQ,GAAA"}
|
package/dist/server/sfc-utils.js
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
|
+
import { componentNameFromPath } from "../utils/componentSources.js";
|
|
1
2
|
import { existsSync } from "node:fs";
|
|
2
|
-
import {
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
3
4
|
import { glob } from "tinyglobby";
|
|
4
5
|
import { fileURLToPath } from "node:url";
|
|
5
6
|
//#region src/server/sfc-utils.ts
|
|
6
7
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
/**
|
|
9
|
+
* Built-in framework components Maizzle ships internally. Issues raised
|
|
10
|
+
* against these files are framework-owned, not the user's responsibility,
|
|
11
|
+
* so the dev UI's Checks tab filters them out.
|
|
12
|
+
*/
|
|
13
|
+
const FRAMEWORK_COMPONENTS_DIR = resolve(__dirname, "../components").replace(/\\/g, "/") + "/";
|
|
14
|
+
function isFrameworkComponent(file) {
|
|
15
|
+
return file.replace(/\\/g, "/").startsWith(FRAMEWORK_COMPONENTS_DIR);
|
|
16
|
+
}
|
|
7
17
|
function parseSfcBlocks(source) {
|
|
8
18
|
let template = null;
|
|
9
19
|
const styles = [];
|
|
@@ -160,24 +170,29 @@ function findComponentTags(templateContent) {
|
|
|
160
170
|
}
|
|
161
171
|
async function buildComponentMap(root, componentDirs) {
|
|
162
172
|
const map = /* @__PURE__ */ new Map();
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
for (const
|
|
173
|
+
const allSources = [...[resolve(__dirname, "../components"), resolve(root, "components")].filter(existsSync).map((path) => ({
|
|
174
|
+
path,
|
|
175
|
+
prefix: void 0,
|
|
176
|
+
pathPrefix: true
|
|
177
|
+
})), ...componentDirs.filter((s) => existsSync(s.path))];
|
|
178
|
+
for (const source of allSources) {
|
|
169
179
|
const files = await glob(["**/*.vue"], {
|
|
170
|
-
cwd:
|
|
180
|
+
cwd: source.path,
|
|
171
181
|
absolute: true
|
|
172
182
|
});
|
|
173
183
|
for (const file of files) {
|
|
174
|
-
const name =
|
|
184
|
+
const name = componentNameFromPath({
|
|
185
|
+
filePath: file,
|
|
186
|
+
dirRoot: source.path,
|
|
187
|
+
prefix: source.prefix,
|
|
188
|
+
pathPrefix: source.pathPrefix
|
|
189
|
+
});
|
|
175
190
|
map.set(name.toLowerCase(), file);
|
|
176
191
|
}
|
|
177
192
|
}
|
|
178
193
|
return map;
|
|
179
194
|
}
|
|
180
195
|
//#endregion
|
|
181
|
-
export { HTML_ELEMENTS, buildComponentMap, findComponentTags, parseSfcBlocks };
|
|
196
|
+
export { HTML_ELEMENTS, buildComponentMap, findComponentTags, isFrameworkComponent, parseSfcBlocks };
|
|
182
197
|
|
|
183
198
|
//# sourceMappingURL=sfc-utils.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sfc-utils.js","names":[],"sources":["../../src/server/sfc-utils.ts"],"sourcesContent":["import { existsSync } from 'node:fs'\nimport { resolve, dirname
|
|
1
|
+
{"version":3,"file":"sfc-utils.js","names":[],"sources":["../../src/server/sfc-utils.ts"],"sourcesContent":["import { existsSync } from 'node:fs'\nimport { resolve, dirname } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { glob } from 'tinyglobby'\nimport { componentNameFromPath, type NormalizedComponentSource } from '../utils/componentSources.ts'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\n\n/**\n * Built-in framework components Maizzle ships internally. Issues raised\n * against these files are framework-owned, not the user's responsibility,\n * so the dev UI's Checks tab filters them out.\n */\nconst FRAMEWORK_COMPONENTS_DIR = resolve(__dirname, '../components').replace(/\\\\/g, '/') + '/'\n\nexport function isFrameworkComponent(file: string): boolean {\n return file.replace(/\\\\/g, '/').startsWith(FRAMEWORK_COMPONENTS_DIR)\n}\n\nexport interface SfcBlock {\n content: string\n offset: number\n}\n\nexport function parseSfcBlocks(source: string): { template: SfcBlock | null, styles: SfcBlock[] } {\n let template: SfcBlock | null = null\n const styles: SfcBlock[] = []\n\n const templateMatch = source.match(/<template\\b[^>]*>([\\s\\S]*)<\\/template>/)\n if (templateMatch) {\n const contentStart = source.indexOf(templateMatch[0]) + templateMatch[0].indexOf(templateMatch[1])\n const offset = source.slice(0, contentStart).split('\\n').length - 1\n template = { content: templateMatch[1], offset }\n }\n\n const styleRe = /<style\\b([^>]*)>([\\s\\S]*?)<\\/style>/g\n let m\n while ((m = styleRe.exec(source)) !== null) {\n // Skip preprocessor styles (scss, less, etc.) — caniemail only parses plain CSS\n if (/\\blang\\s*=\\s*[\"'](?!css)/i.test(m[1])) continue\n\n const contentStart = m.index + m[0].indexOf(m[2])\n const offset = source.slice(0, contentStart).split('\\n').length - 1\n styles.push({ content: m[2], offset })\n }\n\n return { template, styles }\n}\n\n/**\n * Standard HTML elements — anything not in this set is treated as a component.\n */\nexport const HTML_ELEMENTS = new Set([\n 'a', 'abbr', 'address', 'area', 'article', 'aside', 'audio', 'b', 'base',\n 'bdi', 'bdo', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption',\n 'cite', 'code', 'col', 'colgroup', 'data', 'datalist', 'dd', 'del',\n 'details', 'dfn', 'dialog', 'div', 'dl', 'dt', 'em', 'embed', 'fieldset',\n 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5',\n 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'iframe', 'img',\n 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'link', 'main', 'map',\n 'mark', 'menu', 'meta', 'meter', 'nav', 'noscript', 'object', 'ol',\n 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q',\n 'rp', 'rt', 'ruby', 's', 'samp', 'script', 'search', 'section', 'select',\n 'slot', 'small', 'source', 'span', 'strong', 'style', 'sub', 'summary',\n 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th',\n 'thead', 'time', 'title', 'tr', 'track', 'u', 'ul', 'var', 'video', 'wbr',\n])\n\nexport function findComponentTags(templateContent: string): string[] {\n const tags = new Set<string>()\n\n // PascalCase tags like <Section>, <Button>\n const pascalRe = /<([A-Z][a-zA-Z0-9]*)\\b/g\n let m\n while ((m = pascalRe.exec(templateContent)) !== null) {\n tags.add(m[1])\n }\n\n // kebab-case tags like <my-component>\n const kebabRe = /<([a-z][a-z0-9]*(?:-[a-z0-9]+)+)\\b/g\n while ((m = kebabRe.exec(templateContent)) !== null) {\n if (!HTML_ELEMENTS.has(m[1])) {\n tags.add(m[1])\n }\n }\n\n return [...tags]\n}\n\nexport async function buildComponentMap(\n root: string,\n componentDirs: NormalizedComponentSource[],\n): Promise<Map<string, string>> {\n const map = new Map<string, string>()\n\n // Built-in framework components + the user's default `components/` dir use\n // unplugin's directoryAsNamespace + collapseSamePrefixes behavior — i.e.\n // no explicit prefix, folder name becomes the namespace.\n const implicitDirs = [\n resolve(__dirname, '../components'),\n resolve(root, 'components'),\n ].filter(existsSync)\n\n const allSources: NormalizedComponentSource[] = [\n ...implicitDirs.map(path => ({ path, prefix: undefined, pathPrefix: true })),\n ...componentDirs.filter(s => existsSync(s.path)),\n ]\n\n for (const source of allSources) {\n const files = await glob(['**/*.vue'], { cwd: source.path, absolute: true })\n for (const file of files) {\n const name = componentNameFromPath({\n filePath: file,\n dirRoot: source.path,\n prefix: source.prefix,\n pathPrefix: source.pathPrefix,\n })\n // Lowercased for case-insensitive lookups in the linter/compat scanners.\n map.set(name.toLowerCase(), file)\n }\n }\n\n return map\n}\n"],"mappings":"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"}
|
package/dist/types/config.d.ts
CHANGED
|
@@ -441,6 +441,33 @@ interface PlaintextConfig {
|
|
|
441
441
|
*/
|
|
442
442
|
options?: Partial<_$string_strip_html0.Opts>;
|
|
443
443
|
}
|
|
444
|
+
/**
|
|
445
|
+
* Source directory entry for component auto-import.
|
|
446
|
+
*
|
|
447
|
+
* String → folder name becomes a namespace prefix automatically
|
|
448
|
+
* (e.g. `widgets/Button.vue` → `<WidgetsButton />`).
|
|
449
|
+
*
|
|
450
|
+
* Object → custom prefix overrides the folder-derived name. With
|
|
451
|
+
* `pathPrefix: false`, intermediate subfolders are dropped from the
|
|
452
|
+
* resolved name (useful for icon sets organized by category).
|
|
453
|
+
*/
|
|
454
|
+
type ComponentSource = string | {
|
|
455
|
+
/** Directory to scan, resolved relative to `cwd`. */path: string;
|
|
456
|
+
/**
|
|
457
|
+
* Custom prefix prepended to every resolved component name.
|
|
458
|
+
* Empty string disables the auto folder-name prefix.
|
|
459
|
+
*/
|
|
460
|
+
prefix?: string;
|
|
461
|
+
/**
|
|
462
|
+
* Include intermediate subfolder names in the resolved component
|
|
463
|
+
* name. Defaults to `true`.
|
|
464
|
+
*
|
|
465
|
+
* @example
|
|
466
|
+
* // pathPrefix: true → icons/social/twitter.vue → <IconSocialTwitter />
|
|
467
|
+
* // pathPrefix: false → icons/social/twitter.vue → <IconTwitter />
|
|
468
|
+
*/
|
|
469
|
+
pathPrefix?: boolean;
|
|
470
|
+
};
|
|
444
471
|
interface MaizzleConfig {
|
|
445
472
|
/**
|
|
446
473
|
* Root directory for the Maizzle email project.
|
|
@@ -510,12 +537,20 @@ interface MaizzleConfig {
|
|
|
510
537
|
* Resolved relative to `cwd` (not `root`), so paths outside the
|
|
511
538
|
* email root directory work as expected.
|
|
512
539
|
*
|
|
540
|
+
* String entries use the folder name as a namespace prefix
|
|
541
|
+
* automatically (e.g. `widgets/Button.vue` → `<WidgetsButton />`).
|
|
542
|
+
* Object entries override that with a custom prefix.
|
|
543
|
+
*
|
|
513
544
|
* @example
|
|
514
545
|
* components: {
|
|
515
|
-
* source: [
|
|
546
|
+
* source: [
|
|
547
|
+
* 'resources/js/components/email',
|
|
548
|
+
* { path: 'src/widgets', prefix: 'W' },
|
|
549
|
+
* { path: 'src/icons', prefix: 'Icon', pathPrefix: false },
|
|
550
|
+
* ],
|
|
516
551
|
* }
|
|
517
552
|
*/
|
|
518
|
-
source?:
|
|
553
|
+
source?: ComponentSource | ComponentSource[];
|
|
519
554
|
};
|
|
520
555
|
/** Dev server configuration. */
|
|
521
556
|
server?: {
|
|
@@ -688,5 +723,5 @@ interface MaizzleConfig {
|
|
|
688
723
|
[key: string]: any;
|
|
689
724
|
}
|
|
690
725
|
//#endregion
|
|
691
|
-
export { AttributesConfig, CaniemailClient, ChecksConfig, CssConfig, EntitiesConfig, FilterFunction, FiltersConfig, HtmlConfig, MaizzleConfig, MarkdownConfig, PlaintextConfig, PostcssConfig, TransformerToggles, UrlConfig, UrlQuery, UrlQueryOptions, VueConfig };
|
|
726
|
+
export { AttributesConfig, CaniemailClient, ChecksConfig, ComponentSource, CssConfig, EntitiesConfig, FilterFunction, FiltersConfig, HtmlConfig, MaizzleConfig, MarkdownConfig, PlaintextConfig, PostcssConfig, TransformerToggles, UrlConfig, UrlQuery, UrlQueryOptions, VueConfig };
|
|
692
727
|
//# sourceMappingURL=config.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","names":[],"sources":["../../src/types/config.ts"],"mappings":";;;;;;;;;;;UAKiB,eAAA;;;;;;EAMf,IAAA;EANe;;;;;EAYf,UAAA;EAMA;;;;;EAAA,MAAA;EASkB;;;;;EAHlB,EAAA,GAAK,MAAA;AAAA;AAAA,KAGK,QAAA,GAAW,MAAA;EACrB,QAAA,GAAW,eAAA;AAAA;AAAA,UAGI,SAAA;EAYP;;;;;;;;;;;EAAR,KAAA,GAAQ,QAAA;EAe2B;;;;;;;AAUrC;;;EAdE,IAAA;IA0CmB,+BAxCjB,GAAA,WAoGa;IAlGb,IAAA,cAAkB,MAAA,SAAe,MAAA,6BA0IZ;IAxIrB,UAAA,GAAa,MAAA,kBAmKY;IAjKzB,QAAA,YAYF;IAVE,SAAA;EAAA;AAAA;AAAA,UAIa,SAAA;EAmCb;;;;;EA7BF,IAAA;EAoEE;;;;;;;EA5DF,KAAA,aAAkB,MAAA;EAmGhB;;;;;;;;;;;;;EArFF,MAAA,aAAmB,OAAA;IA4IZ;AAGT;;;;;IAxII,gBAAA;IAgLO;;;;;IA1KP,oBAAA;IA0KF;;;;;;IAnKE,QAAA;IAsKQ;;;;;AAOZ;;;;;AAOA;IAxKI,gBAAA,GAAmB,MAAA;IA8KX;;;;;;IAvKR,aAAA;IAkLa;;;;;AAyBjB;IApMI,cAAA;IAoMuB;;;;;;IA7LvB,kBAAA;IAsNwB;;;;;;IA/MxB,UAAA,GAAa,MAAA;MAAiB,KAAA;MAAe,GAAA;IAAA;IA+MrB;;AAG5B;;IA7MI,SAAA;EAAA;EA6MoD;AACxD;;;;;AAEA;;;;;;EAlME,KAAA;IAwMU;;AAGZ;;;IArMI,IAAA,wCAA4C,CAAA,UAAW,CAAA;EAAA;EAyM5C;;;;;;;EAhMb,cAAA;EAgM4B;;;;;EA1L5B,IAAA,aAAiB,MAAA;EAiNgB;;;;;;;EAzMjC,SAAA;IAAwB,IAAA;EAAA;EAiNxB;;;;;;;EAzMA,MAAA;EAgNM;;AASR;;;;;;;;EA9ME,kBAAA,GAAqB,MAAA,SA3BE,WAAA;EA0PN;;
|
|
1
|
+
{"version":3,"file":"config.d.ts","names":[],"sources":["../../src/types/config.ts"],"mappings":";;;;;;;;;;;UAKiB,eAAA;;;;;;EAMf,IAAA;EANe;;;;;EAYf,UAAA;EAMA;;;;;EAAA,MAAA;EASkB;;;;;EAHlB,EAAA,GAAK,MAAA;AAAA;AAAA,KAGK,QAAA,GAAW,MAAA;EACrB,QAAA,GAAW,eAAA;AAAA;AAAA,UAGI,SAAA;EAYP;;;;;;;;;;;EAAR,KAAA,GAAQ,QAAA;EAe2B;;;;;;;AAUrC;;;EAdE,IAAA;IA0CmB,+BAxCjB,GAAA,WAoGa;IAlGb,IAAA,cAAkB,MAAA,SAAe,MAAA,6BA0IZ;IAxIrB,UAAA,GAAa,MAAA,kBAmKY;IAjKzB,QAAA,YAYF;IAVE,SAAA;EAAA;AAAA;AAAA,UAIa,SAAA;EAmCb;;;;;EA7BF,IAAA;EAoEE;;;;;;;EA5DF,KAAA,aAAkB,MAAA;EAmGhB;;;;;;;;;;;;;EArFF,MAAA,aAAmB,OAAA;IA4IZ;AAGT;;;;;IAxII,gBAAA;IAgLO;;;;;IA1KP,oBAAA;IA0KF;;;;;;IAnKE,QAAA;IAsKQ;;;;;AAOZ;;;;;AAOA;IAxKI,gBAAA,GAAmB,MAAA;IA8KX;;;;;;IAvKR,aAAA;IAkLa;;;;;AAyBjB;IApMI,cAAA;IAoMuB;;;;;;IA7LvB,kBAAA;IAsNwB;;;;;;IA/MxB,UAAA,GAAa,MAAA;MAAiB,KAAA;MAAe,GAAA;IAAA;IA+MrB;;AAG5B;;IA7MI,SAAA;EAAA;EA6MoD;AACxD;;;;;AAEA;;;;;;EAlME,KAAA;IAwMU;;AAGZ;;;IArMI,IAAA,wCAA4C,CAAA,UAAW,CAAA;EAAA;EAyM5C;;;;;;;EAhMb,cAAA;EAgM4B;;;;;EA1L5B,IAAA,aAAiB,MAAA;EAiNgB;;;;;;;EAzMjC,SAAA;IAAwB,IAAA;EAAA;EAiNxB;;;;;;;EAzMA,MAAA;EAgNM;;AASR;;;;;;;;EA9ME,kBAAA,GAAqB,MAAA,SA3BE,WAAA;EA0PN;;AAanB;;;;;;;;;AAqBA;EApPE,OAAA;AAAA;AAAA,UAGe,gBAAA;EAmUJ;;;;;;;;;;;;;;;;;EAjTX,GAAA,WAAc,MAAA,iBAAuB,MAAA;EA6cJ;;;;;;;;;;;;;;;;;;;;;EAvbjC,MAAA,GAAS,KAAA;IAAiB,IAAA;IAAc,KAAA,YAAiB,MAAA;EAAA;AAAA;AAAA,KAG/C,cAAA,aAA2B,MAAA;;;;;;KAO3B,eAAA;AAAA,UAOK,YAAA;EA4UT;;;;;EAtUN,OAAA,GAAU,eAAA;EAmWkB;;;;;;;EA3V5B,KAAA;AAAA;AAAA,UAGe,aAAA;EA8XR;;;;;;;;;;EAnXP,eAAA;EAwYiD;;;;;;;;;;EA7XjD,aAAA;AAAA;AAAA,UAGe,UAAA;EA8XoC;EA5XnD,UAAA,GAAa,gBAAA;EA4XK;;;;;;;EApXlB,cAAA,GAAiB,cAAA;EAyXhB;;;;;EAnXD,MAAA,aAN+B,OAAA,CAMI,aAAA;;;;;;;;;EASnC,MAAA,aAAmB,OAAA,CAT6B,aAAA,CASA,IAAA;AAAA;AAAA,KAGtC,cAAA,IAAkB,GAAA,UAAa,KAAA;AAAA,KAC/B,aAAA,WAAwB,MAAA,SAAe,cAAA;AAAA,UAElC,cAAA,SAAuB,SAAA;;;;;;EAMtC,UAAA,GAN8B,OAAA,CAMD,YAAA;AAAA;AAAA,UAGd,SAAA;;EAEf,OAAA,GAAU,MAAA;;EAEV,UAAA,GAAa,MAAA,SAAe,SAAA;;EAE5B,gBAAA,GAAmB,MAAA;AAAA;;;;;;;;;;;;;;;;;;;UAqBJ,kBAAA;EACf,cAAA;EACA,gBAAA;EACA,SAAA;EACA,gBAAA;EACA,YAAA;EACA,MAAA;EACA,aAAA;EACA,OAAA;EACA,OAAA;EACA,QAAA;EACA,QAAA;EACA,QAAA;EACA,cAAA;EACA,QAAA;EACA,MAAA;AAAA;;;;;;;UASe,eAAA;;;;;EAKf,WAAA;;;;;;EAMA,SAAA;;;;;;EAMA,OAAA,GAAU,OAAA,CAjBoB,oBAAA,CAiBgB,IAAA;AAAA;;;;;;;;;;;KAapC,eAAA;uDAIR,IAAA;;;;;EAKA,MAAA;;;;;;;;;EASA,UAAA;AAAA;AAAA,UAGa,aAAA;;;;;;;;;;;;;;;EAef,IAAA;;EAEA,QAAA,GAAW,cAAA;;;;;;;;EAQX,OAAA;;EAEA,MAAA;;;;;;IAME,IAAA;;;;;;;;;;;IAWA,SAAA;EAAA;;EAGF,MAAA;;;;;;IAME,MAAA;;;;;;IAMA,WAAA;EAAA;;EAGF,UAAA;;;;;;;;;;;;;;;;;;;;IAoBE,MAAA,GAAS,eAAA,GAAkB,eAAA;EAAA;;EAG7B,MAAA;;;;;;IAME,IAAA;;;;;;;;;;;IAWA,KAAA;;;;;;;;;;;;;;;;;;;;IAoBA,KAAA;kCAEE,EAAA;MAEA,IAAA;MAEA,OAAA;MAEA,SAAA,GAAY,MAAA;IAAA;;;;;;;;;;;;;;IAed,MAAA,WAAiB,YAAA;EAAA;;EAGnB,GAAA,GAAM,SAAA;;;;;;;;;;;;;;;EAeN,SAAA,aAAsB,eAAA;;EAEtB,OAAA,GAAU,aAAA;;;;;;;;;;;;EAYV,eAAA,aAA4B,kBAAA;;;;;;;;;EAS5B,cAAA,GAAiB,MAAA;;;;;;;;;;;;EAYjB,OAAA,GAAU,aAAA;;EAEV,GAAA,GAAM,SAAA;;EAEN,IAAA,GAAO,UAAA;;;;;;;;;;;;;EAaP,IAAA,GAAO,YAAA;;;;;;;;;;;;;;EAcP,GAAA,GAAM,SAAA;;EAKN,YAAA,IAAgB,MAAA;IAAU,MAAA,EAAQ,aAAA;EAAA,aAA2B,OAAA;;EAE7D,YAAA,IAAgB,MAAA;IAAU,MAAA,EAAQ,aAAA;IAAe,QAAA;EAAA,sBAAuC,OAAA;;EAExF,WAAA,IAAe,MAAA;IAAU,MAAA,EAAQ,aAAA;IAAe,QAAA;IAAkB,IAAA;EAAA,sBAAmC,OAAA;;EAErG,cAAA,IAAkB,MAAA;IAAU,MAAA,EAAQ,aAAA;IAAe,QAAA;IAAkB,IAAA;EAAA,sBAAmC,OAAA;;EAExG,UAAA,IAAc,MAAA;IAAU,KAAA;IAAiB,MAAA,EAAQ,aAAA;EAAA,aAA2B,OAAA;EAAA,CAG3E,GAAA;AAAA"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { AttributesConfig, CaniemailClient, ChecksConfig, CssConfig, EntitiesConfig, FilterFunction, FiltersConfig, HtmlConfig, MaizzleConfig, MarkdownConfig, PlaintextConfig, PostcssConfig, TransformerToggles, UrlConfig, UrlQuery, UrlQueryOptions } from "./config.js";
|
|
2
|
-
export { type AttributesConfig, type CaniemailClient, type ChecksConfig, type CssConfig, type EntitiesConfig, type FilterFunction, type FiltersConfig, type HtmlConfig, type MaizzleConfig, type MarkdownConfig, type PlaintextConfig, type PostcssConfig, type TransformerToggles, type UrlConfig, type UrlQuery, type UrlQueryOptions };
|
|
1
|
+
import { AttributesConfig, CaniemailClient, ChecksConfig, ComponentSource, CssConfig, EntitiesConfig, FilterFunction, FiltersConfig, HtmlConfig, MaizzleConfig, MarkdownConfig, PlaintextConfig, PostcssConfig, TransformerToggles, UrlConfig, UrlQuery, UrlQueryOptions } from "./config.js";
|
|
2
|
+
export { type AttributesConfig, type CaniemailClient, type ChecksConfig, type ComponentSource, type CssConfig, type EntitiesConfig, type FilterFunction, type FiltersConfig, type HtmlConfig, type MaizzleConfig, type MarkdownConfig, type PlaintextConfig, type PostcssConfig, type TransformerToggles, type UrlConfig, type UrlQuery, type UrlQueryOptions };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { ComponentSource } from "../types/config.js";
|
|
2
|
+
|
|
3
|
+
//#region src/utils/componentSources.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Internal representation of a component source after defaults are
|
|
6
|
+
* resolved and `path` is made absolute. Always carries the same shape
|
|
7
|
+
* regardless of which user-facing form produced it.
|
|
8
|
+
*/
|
|
9
|
+
interface NormalizedComponentSource {
|
|
10
|
+
/** Absolute directory path. */
|
|
11
|
+
path: string;
|
|
12
|
+
/**
|
|
13
|
+
* Custom prefix prepended to resolved component names.
|
|
14
|
+
* - `undefined` → use folder-name namespace (the `directoryAsNamespace`
|
|
15
|
+
* behavior of unplugin-vue-components).
|
|
16
|
+
* - `''` (empty string) → no prefix at all; use the bare filename.
|
|
17
|
+
* - any other string → exact prefix.
|
|
18
|
+
*/
|
|
19
|
+
prefix?: string;
|
|
20
|
+
/** Include intermediate subfolder names in the resolved name. */
|
|
21
|
+
pathPrefix: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Normalize a user-supplied `components.source` value into an array
|
|
25
|
+
* of absolute, fully-defaulted entries.
|
|
26
|
+
*/
|
|
27
|
+
declare function normalizeComponentSources(sources: ComponentSource | ComponentSource[] | undefined, cwd: string): NormalizedComponentSource[];
|
|
28
|
+
interface ComponentNameOptions {
|
|
29
|
+
/** Absolute path to the component file. */
|
|
30
|
+
filePath: string;
|
|
31
|
+
/** Absolute path to the dir root the component was discovered under. */
|
|
32
|
+
dirRoot: string;
|
|
33
|
+
/** See {@link NormalizedComponentSource.prefix}. */
|
|
34
|
+
prefix?: string;
|
|
35
|
+
/** See {@link NormalizedComponentSource.pathPrefix}. */
|
|
36
|
+
pathPrefix: boolean;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Compute the component name unplugin-vue-components would assign for a
|
|
40
|
+
* given file under a given dir, mirroring the plugin's
|
|
41
|
+
* `directoryAsNamespace: true` + `collapseSamePrefixes: true` behavior
|
|
42
|
+
* and layering custom-prefix sources on top.
|
|
43
|
+
*
|
|
44
|
+
* Used both at render time (to register a custom resolver) and at lint
|
|
45
|
+
* time (so the linter can follow component graph correctly).
|
|
46
|
+
*/
|
|
47
|
+
declare function componentNameFromPath(opts: ComponentNameOptions): string;
|
|
48
|
+
//#endregion
|
|
49
|
+
export { ComponentNameOptions, NormalizedComponentSource, componentNameFromPath, normalizeComponentSources };
|
|
50
|
+
//# sourceMappingURL=componentSources.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"componentSources.d.ts","names":[],"sources":["../../src/utils/componentSources.ts"],"mappings":";;;;;AAQA;;;UAAiB,yBAAA;EAEf;EAAA,IAAA;EAUA;;;AAOF;;;;EATE,MAAA;EAYC;EAVD,UAAA;AAAA;;;;;iBAOc,yBAAA,CACd,OAAA,EAAS,eAAA,GAAkB,eAAA,gBAC3B,GAAA,WACC,yBAAA;AAAA,UAqBc,oBAAA;EArBW;EAuB1B,QAAA;EAFmC;EAInC,OAAA;EAJmC;EAMnC,MAAA;EAFA;EAIA,UAAA;AAAA;;;AAYF;;;;;;;iBAAgB,qBAAA,CAAsB,IAAA,EAAM,oBAAA"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { relative, resolve } from "node:path";
|
|
2
|
+
//#region src/utils/componentSources.ts
|
|
3
|
+
/**
|
|
4
|
+
* Normalize a user-supplied `components.source` value into an array
|
|
5
|
+
* of absolute, fully-defaulted entries.
|
|
6
|
+
*/
|
|
7
|
+
function normalizeComponentSources(sources, cwd) {
|
|
8
|
+
if (!sources) return [];
|
|
9
|
+
return (Array.isArray(sources) ? sources : [sources]).map((s) => {
|
|
10
|
+
if (typeof s === "string") return {
|
|
11
|
+
path: resolve(cwd, s),
|
|
12
|
+
prefix: void 0,
|
|
13
|
+
pathPrefix: true
|
|
14
|
+
};
|
|
15
|
+
return {
|
|
16
|
+
path: resolve(cwd, s.path),
|
|
17
|
+
prefix: s.prefix,
|
|
18
|
+
pathPrefix: s.pathPrefix ?? true
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function pascalCase(s) {
|
|
23
|
+
return s.replace(/[-_\s]+(.)/g, (_, c) => c.toUpperCase()).replace(/^(.)/, (c) => c.toUpperCase());
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Compute the component name unplugin-vue-components would assign for a
|
|
27
|
+
* given file under a given dir, mirroring the plugin's
|
|
28
|
+
* `directoryAsNamespace: true` + `collapseSamePrefixes: true` behavior
|
|
29
|
+
* and layering custom-prefix sources on top.
|
|
30
|
+
*
|
|
31
|
+
* Used both at render time (to register a custom resolver) and at lint
|
|
32
|
+
* time (so the linter can follow component graph correctly).
|
|
33
|
+
*/
|
|
34
|
+
function componentNameFromPath(opts) {
|
|
35
|
+
const { filePath, dirRoot, prefix, pathPrefix } = opts;
|
|
36
|
+
const segments = relative(dirRoot, filePath).replace(/\\/g, "/").replace(/\.(vue|md)$/, "").split("/").map(pascalCase);
|
|
37
|
+
const fileName = segments.pop() ?? "";
|
|
38
|
+
if (prefix !== void 0) {
|
|
39
|
+
const folderPart = pathPrefix ? segments.join("") : "";
|
|
40
|
+
const stripped = prefix && fileName.startsWith(prefix) ? fileName.slice(prefix.length) : fileName;
|
|
41
|
+
return prefix + folderPart + stripped;
|
|
42
|
+
}
|
|
43
|
+
const folderPart = segments.join("");
|
|
44
|
+
if (folderPart && fileName.startsWith(folderPart)) return fileName;
|
|
45
|
+
return folderPart + fileName;
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
export { componentNameFromPath, normalizeComponentSources };
|
|
49
|
+
|
|
50
|
+
//# sourceMappingURL=componentSources.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"componentSources.js","names":[],"sources":["../../src/utils/componentSources.ts"],"sourcesContent":["import { resolve, relative } from 'node:path'\nimport type { ComponentSource } from '../types/config.ts'\n\n/**\n * Internal representation of a component source after defaults are\n * resolved and `path` is made absolute. Always carries the same shape\n * regardless of which user-facing form produced it.\n */\nexport interface NormalizedComponentSource {\n /** Absolute directory path. */\n path: string\n /**\n * Custom prefix prepended to resolved component names.\n * - `undefined` → use folder-name namespace (the `directoryAsNamespace`\n * behavior of unplugin-vue-components).\n * - `''` (empty string) → no prefix at all; use the bare filename.\n * - any other string → exact prefix.\n */\n prefix?: string\n /** Include intermediate subfolder names in the resolved name. */\n pathPrefix: boolean\n}\n\n/**\n * Normalize a user-supplied `components.source` value into an array\n * of absolute, fully-defaulted entries.\n */\nexport function normalizeComponentSources(\n sources: ComponentSource | ComponentSource[] | undefined,\n cwd: string,\n): NormalizedComponentSource[] {\n if (!sources) return []\n const list = Array.isArray(sources) ? sources : [sources]\n return list.map((s) => {\n if (typeof s === 'string') {\n return { path: resolve(cwd, s), prefix: undefined, pathPrefix: true }\n }\n return {\n path: resolve(cwd, s.path),\n prefix: s.prefix,\n pathPrefix: s.pathPrefix ?? true,\n }\n })\n}\n\nfunction pascalCase(s: string): string {\n return s\n .replace(/[-_\\s]+(.)/g, (_, c) => c.toUpperCase())\n .replace(/^(.)/, c => c.toUpperCase())\n}\n\nexport interface ComponentNameOptions {\n /** Absolute path to the component file. */\n filePath: string\n /** Absolute path to the dir root the component was discovered under. */\n dirRoot: string\n /** See {@link NormalizedComponentSource.prefix}. */\n prefix?: string\n /** See {@link NormalizedComponentSource.pathPrefix}. */\n pathPrefix: boolean\n}\n\n/**\n * Compute the component name unplugin-vue-components would assign for a\n * given file under a given dir, mirroring the plugin's\n * `directoryAsNamespace: true` + `collapseSamePrefixes: true` behavior\n * and layering custom-prefix sources on top.\n *\n * Used both at render time (to register a custom resolver) and at lint\n * time (so the linter can follow component graph correctly).\n */\nexport function componentNameFromPath(opts: ComponentNameOptions): string {\n const { filePath, dirRoot, prefix, pathPrefix } = opts\n\n const rel = relative(dirRoot, filePath).replace(/\\\\/g, '/')\n const noExt = rel.replace(/\\.(vue|md)$/, '')\n const segments = noExt.split('/').map(pascalCase)\n const fileName = segments.pop() ?? ''\n\n if (prefix !== undefined) {\n const folderPart = pathPrefix ? segments.join('') : ''\n const stripped = prefix && fileName.startsWith(prefix)\n ? fileName.slice(prefix.length)\n : fileName\n return prefix + folderPart + stripped\n }\n\n const folderPart = segments.join('')\n if (folderPart && fileName.startsWith(folderPart)) {\n return fileName\n }\n return folderPart + fileName\n}\n"],"mappings":";;;;;;AA2BA,SAAgB,0BACd,SACA,KAC6B;CAC7B,IAAI,CAAC,SAAS,OAAO,EAAE;CAEvB,QADa,MAAM,QAAQ,QAAQ,GAAG,UAAU,CAAC,QAAQ,EAC7C,KAAK,MAAM;EACrB,IAAI,OAAO,MAAM,UACf,OAAO;GAAE,MAAM,QAAQ,KAAK,EAAE;GAAE,QAAQ,KAAA;GAAW,YAAY;GAAM;EAEvE,OAAO;GACL,MAAM,QAAQ,KAAK,EAAE,KAAK;GAC1B,QAAQ,EAAE;GACV,YAAY,EAAE,cAAc;GAC7B;GACD;;AAGJ,SAAS,WAAW,GAAmB;CACrC,OAAO,EACJ,QAAQ,gBAAgB,GAAG,MAAM,EAAE,aAAa,CAAC,CACjD,QAAQ,SAAQ,MAAK,EAAE,aAAa,CAAC;;;;;;;;;;;AAuB1C,SAAgB,sBAAsB,MAAoC;CACxE,MAAM,EAAE,UAAU,SAAS,QAAQ,eAAe;CAIlD,MAAM,WAFM,SAAS,SAAS,SAAS,CAAC,QAAQ,OAAO,IACtC,CAAC,QAAQ,eAAe,GACnB,CAAC,MAAM,IAAI,CAAC,IAAI,WAAW;CACjD,MAAM,WAAW,SAAS,KAAK,IAAI;CAEnC,IAAI,WAAW,KAAA,GAAW;EACxB,MAAM,aAAa,aAAa,SAAS,KAAK,GAAG,GAAG;EACpD,MAAM,WAAW,UAAU,SAAS,WAAW,OAAO,GAClD,SAAS,MAAM,OAAO,OAAO,GAC7B;EACJ,OAAO,SAAS,aAAa;;CAG/B,MAAM,aAAa,SAAS,KAAK,GAAG;CACpC,IAAI,cAAc,SAAS,WAAW,WAAW,EAC/C,OAAO;CAET,OAAO,aAAa"}
|