feat(plugin-md-power): improve logger (#607)

This commit is contained in:
pengzhanbo 2025-05-31 02:37:27 +08:00 committed by GitHub
parent 3c384a4362
commit d6a47419e4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 272 additions and 29 deletions

View File

@ -4,10 +4,21 @@ import { alignPlugin } from '../src/node/container/align.js'
describe('alignPlugin', () => {
const md = new MarkdownIt().use(alignPlugin)
it('should work', () => {
it('should work with align', () => {
expect(md.render(':::left\n:::')).toContain('style="text-align:left"')
expect(md.render(':::center\n:::')).toContain('style="text-align:center"')
expect(md.render(':::right\n:::')).toContain('style="text-align:right"')
expect(md.render(':::justify\n:::')).toContain('style="text-align:justify"')
})
it('should work with flex', () => {
expect(md.render(':::flex\n:::')).toContain('display:flex')
expect(md.render(':::flex start\n:::')).toContain('align-items:flex-start')
expect(md.render(':::flex end\n:::')).toContain('align-items:flex-end')
expect(md.render(':::flex center\n:::')).toContain('align-items:center')
expect(md.render(':::flex between\n:::')).toContain('justify-content:space-between')
expect(md.render(':::flex around\n:::')).toContain('justify-content:space-around')
expect(md.render(':::flex wrap\n:::')).toContain('flex-wrap:wrap')
expect(md.render(':::flex column\n:::')).toContain('flex-direction:column')
})
})

View File

@ -3,9 +3,9 @@ import { parseRect } from '../utils/parseRect.js'
import { resolveAttrs } from '../utils/resolveAttrs.js'
import { createContainerPlugin } from './createContainer.js'
const alignList = ['left', 'center', 'right', 'justify']
export function alignPlugin(md: Markdown): void {
const alignList = ['left', 'center', 'right', 'justify']
for (const name of alignList) {
createContainerPlugin(md, name, {
before: () => `<div style="text-align:${name}">`,

View File

@ -61,6 +61,9 @@ const UNSUPPORTED_FILE_TYPES = [
'xlsx',
]
/**
* code-tree
*/
interface CodeTreeMeta {
title?: string
/**
@ -78,6 +81,9 @@ interface CodeTreeMeta {
entry?: string
}
/**
*
*/
interface FileTreeNode {
level: number
children?: FileTreeNode[]
@ -85,6 +91,11 @@ interface FileTreeNode {
filepath?: string
}
/**
*
* @param files
* @returns
*/
function parseFileNodes(files: string[]): FileTreeNode[] {
const nodes: FileTreeNode[] = []
for (const file of files) {
@ -111,7 +122,16 @@ function parseFileNodes(files: string[]): FileTreeNode[] {
return nodes
}
/**
* code-tree markdown
* @param md markdown-it
* @param app vuepress app
* @param options code-tree
*/
export function codeTreePlugin(md: Markdown, app: App, options: CodeTreeOptions = {}): void {
/**
*
*/
const getIcon = (filename: string, type: 'folder' | 'file', mode?: FileTreeIconMode): string => {
mode ||= options.icon || 'colored'
if (mode === 'simple')
@ -119,6 +139,9 @@ export function codeTreePlugin(md: Markdown, app: App, options: CodeTreeOptions
return getFileIcon(filename, type)
}
/**
*
*/
function renderFileTree(nodes: FileTreeNode[], mode?: FileTreeIconMode): string {
return nodes.map((node) => {
const props: FileTreeNodeProps & { filepath?: string } = {
@ -136,8 +159,10 @@ export function codeTreePlugin(md: Markdown, app: App, options: CodeTreeOptions
.join('\n')
}
// 注册 ::: code-tree 容器
createContainerPlugin(md, 'code-tree', {
before: (info, tokens, index) => {
// 收集 code-tree 容器内的文件名和激活文件
const files: string[] = []
let activeFile: string | undefined
for (
@ -172,6 +197,7 @@ export function codeTreePlugin(md: Markdown, app: App, options: CodeTreeOptions
after: () => '</VPCodeTree>',
})
// 注册 @[code-tree](dir) 语法
createEmbedRuleBlock(md, {
type: 'code-tree',
syntaxPattern: /^@\[code-tree([^\]]*)\]\(([^)]*)\)/,
@ -187,8 +213,10 @@ export function codeTreePlugin(md: Markdown, app: App, options: CodeTreeOptions
}
},
content: ({ dir, icon, ...props }, _, env) => {
// codeTreeFiles 用于页面依赖收集
const codeTreeFiles = ((env as any).codeTreeFiles ??= []) as string[]
const root = findFile(app, env, dir)
// 获取目录下所有文件
const files = globSync('**/*', {
cwd: root,
onlyFiles: true,
@ -201,6 +229,7 @@ export function codeTreePlugin(md: Markdown, app: App, options: CodeTreeOptions
})
props.entryFile ||= files[0]
// 生成所有文件的代码块内容
const codeContent = files.map((file) => {
const ext = path.extname(file).slice(1)
if (UNSUPPORTED_FILE_TYPES.includes(ext)) {
@ -220,6 +249,10 @@ export function codeTreePlugin(md: Markdown, app: App, options: CodeTreeOptions
})
}
/**
* codeTreeFiles
* @param page vuepress
*/
export function extendsPageWithCodeTree(page: Page): void {
const markdownEnv = page.markdownEnv
const codeTreeFiles = (markdownEnv.codeTreeFiles ?? []) as string[]

View File

@ -4,29 +4,51 @@ import type { Markdown } from 'vuepress/markdown'
import container from 'markdown-it-container'
import { resolveAttrs } from '../utils/resolveAttrs.js'
/**
* RenderRuleParams RenderRule
*/
type RenderRuleParams = Parameters<RenderRule> extends [...infer Args, infer _] ? Args : never
/**
*
* - before: 渲染容器起始标签时的回调
* - after: 渲染容器结束标签时的回调
*/
export interface ContainerOptions {
before?: (info: string, ...args: RenderRuleParams) => string
after?: (info: string, ...args: RenderRuleParams) => string
}
/**
* markdown-it
*
* @param md markdown-it
* @param type 'tip', 'warning'
* @param options before/after
* @param options.before
* @param options.after
*/
export function createContainerPlugin(
md: Markdown,
type: string,
{ before, after }: ContainerOptions = {},
): void {
// 自定义渲染规则
const render: RenderRule = (tokens, index, options, env): string => {
const token = tokens[index]
// 提取 ::: 后的 info 信息
const info = token.info.trim().slice(type.length).trim() || ''
if (token.nesting === 1) {
// 容器起始标签
return before?.(info, tokens, index, options, env) ?? `<div class="custom-container ${type}">`
}
else {
// 容器结束标签
return after?.(info, tokens, index, options, env) ?? '</div>'
}
}
// 注册 markdown-it-container 插件
md.use(container, type, { render })
}
@ -55,17 +77,27 @@ export function createContainerSyntaxPlugin(
const maker = ':'
const markerMinLen = 3
/**
* block
* @param state block
* @param startLine
* @param endLine
* @param silent
* @returns
*/
function defineContainer(state: StateBlock, startLine: number, endLine: number, silent: boolean): boolean {
const start = state.bMarks[startLine] + state.tShift[startLine]
const max = state.eMarks[startLine]
let pos = start
// check marker
// 检查是否以指定的 maker:)开头
if (state.src[pos] !== maker)
return false
pos += markerMinLen
// 检查 marker 长度是否满足要求
for (pos = start + 1; pos <= max; pos++) {
if (state.src[pos] !== maker)
break
@ -78,6 +110,7 @@ export function createContainerSyntaxPlugin(
const info = state.src.slice(pos, max).trim()
// ::: type
// 检查 info 是否以 type 开头
if (!info.startsWith(type))
return false
@ -87,6 +120,7 @@ export function createContainerSyntaxPlugin(
let line = startLine
let content = ''
// 收集容器内容,直到遇到结束的 marker
while (++line < endLine) {
if (state.src.slice(state.bMarks[line], state.eMarks[line]).trim() === markup) {
break
@ -95,6 +129,7 @@ export function createContainerSyntaxPlugin(
content += `${state.src.slice(state.bMarks[line], state.eMarks[line])}\n`
}
// 创建 token保存内容和属性
const token = state.push(`${type}_container`, '', 0)
token.meta = resolveAttrs(info.slice(type.length)).attrs
token.content = content
@ -106,11 +141,13 @@ export function createContainerSyntaxPlugin(
return true
}
// 默认渲染函数
const defaultRender: RenderRule = (tokens, index) => {
const { content } = tokens[index]
return `<div class="custom-container ${type}">${content}</div>`
}
// 注册 block 规则和渲染规则
md.block.ruler.before('fence', `${type}_definition`, defineContainer)
md.renderer.rules[`${type}_container`] = render ?? defaultRender
}

View File

@ -5,17 +5,26 @@ import { defaultFile, defaultFolder, getFileIcon } from '../fileIcons/index.js'
import { stringifyAttrs } from '../utils/stringifyAttrs.js'
import { createContainerSyntaxPlugin } from './createContainer.js'
/**
*
*/
interface FileTreeNode {
info: string
level: number
children: FileTreeNode[]
}
/**
*
*/
interface FileTreeAttrs {
title?: string
icon?: FileTreeIconMode
}
/**
*
*/
export interface FileTreeNodeProps {
filename: string
comment?: string
@ -26,6 +35,11 @@ export interface FileTreeNodeProps {
level?: number
}
/**
*
* @param content
* @returns
*/
export function parseFileTreeRawContent(content: string): FileTreeNode[] {
const root: FileTreeNode = { info: '', level: -1, children: [] }
const stack: FileTreeNode[] = [root]
@ -54,6 +68,11 @@ export function parseFileTreeRawContent(content: string): FileTreeNode[] {
const RE_FOCUS = /^\*\*(.*)\*\*(?:$|\s+)/
/**
* info
* @param info
* @returns
*/
export function parseFileTreeNodeInfo(info: string): FileTreeNodeProps {
let filename = ''
let comment = ''
@ -62,6 +81,7 @@ export function parseFileTreeNodeInfo(info: string): FileTreeNodeProps {
let type: 'folder' | 'file' = 'file'
let diff: 'add' | 'remove' | undefined
// 处理 diff 标记
if (info.startsWith('++')) {
info = info.slice(2).trim()
diff = 'add'
@ -71,12 +91,14 @@ export function parseFileTreeNodeInfo(info: string): FileTreeNodeProps {
diff = 'remove'
}
// 处理高亮focus标记
info = info.replace(RE_FOCUS, (_, matched) => {
filename = matched
focus = true
return ''
})
// 提取文件名和注释
if (filename === '' && !focus) {
const spaceIndex = info.indexOf(' ')
filename = info.slice(0, spaceIndex === -1 ? info.length : spaceIndex)
@ -85,6 +107,7 @@ export function parseFileTreeNodeInfo(info: string): FileTreeNodeProps {
comment = info.trim()
// 判断是否为文件夹
if (filename.endsWith('/')) {
type = 'folder'
expanded = false
@ -94,7 +117,15 @@ export function parseFileTreeNodeInfo(info: string): FileTreeNodeProps {
return { filename, comment, focus, expanded, type, diff }
}
/**
* markdown
* @param md markdown
* @param options
*/
export function fileTreePlugin(md: Markdown, options: FileTreeOptions = {}): void {
/**
*
*/
const getIcon = (filename: string, type: 'folder' | 'file', mode?: FileTreeIconMode): string => {
mode ||= options.icon || 'colored'
if (mode === 'simple')
@ -102,12 +133,16 @@ export function fileTreePlugin(md: Markdown, options: FileTreeOptions = {}): voi
return getFileIcon(filename, type)
}
/**
*
*/
const renderFileTree = (nodes: FileTreeNode[], meta: FileTreeAttrs): string =>
nodes.map((node) => {
const { info, level, children } = node
const { filename, comment, focus, expanded, type, diff } = parseFileTreeNodeInfo(info)
const isOmit = filename === '…' || filename === '...' /* fallback */
// 文件夹无子节点时补充省略号
if (children.length === 0 && type === 'folder') {
children.push({ info: '…', level: level + 1, children: [] })
}
@ -132,6 +167,7 @@ ${renderedIcon}${renderedComment}${children.length > 0 ? renderFileTree(children
</FileTreeNode>`
}).join('\n')
// 注册自定义容器语法插件
return createContainerSyntaxPlugin(
md,
'file-tree',

View File

@ -28,10 +28,14 @@ import type { CommandConfig, CommandConfigItem } from './npmToPreset.js'
import { isArray } from '@vuepress/helper'
import { colors } from 'vuepress/utils'
import { cleanMarkdownEnv } from '../utils/cleanMarkdownEnv.js'
import { logger } from '../utils/logger.js'
import { resolveAttrs } from '../utils/resolveAttrs.js'
import { createContainerPlugin } from './createContainer.js'
import { ALLOW_LIST, BOOL_FLAGS, DEFAULT_TABS, MANAGERS_CONFIG } from './npmToPreset.js'
/**
* npm-to npm
*/
export function npmToPlugins(md: Markdown, options: NpmToOptions = {}): void {
const opt = isArray(options) ? { tabs: options } : options
const defaultTabs = opt.tabs?.length ? opt.tabs : DEFAULT_TABS
@ -46,19 +50,28 @@ export function npmToPlugins(md: Markdown, options: NpmToOptions = {}): void {
token.hidden = true
token.type = 'text'
token.content = ''
// 拆分命令行内容,转换为多包管理器命令
const lines = content.split(/(\n|\s*&&\s*)/)
return md.render(
resolveNpmTo(lines, token.info.trim(), idx, tabs),
cleanMarkdownEnv(env),
)
}
console.warn(`${colors.yellow('[vuepress-plugin-md-power]')} Invalid npm-to container in ${colors.gray(env.filePathRelative || env.filePath)}`)
// 非法容器警告
logger.warn('npm-to', `Invalid npm-to container in ${colors.gray(env.filePathRelative || env.filePath)}`)
return ''
},
after: () => '',
})
}
/**
* npm
* @param lines
* @param info
* @param idx token
* @param tabs
*/
function resolveNpmTo(lines: string[], info: string, idx: number, tabs: NpmToPackageManager[]): string {
tabs = validateTabs(tabs)
const res: string[] = []
@ -68,6 +81,7 @@ function resolveNpmTo(lines: string[], info: string, idx: number, tabs: NpmToPac
for (const line of lines) {
const config = findConfig(line)
if (config && config[tab]) {
// 解析并替换命令参数
const parsed = (map[line] ??= parseLine(line)) as LineParsed
const { cli, flags } = config[tab] as CommandConfigItem
@ -91,11 +105,15 @@ function resolveNpmTo(lines: string[], info: string, idx: number, tabs: NpmToPac
newLines.push(line)
}
}
// 拼接为 code-tabs 格式
res.push(`@tab ${tab}\n\`\`\`${info}\n${newLines.join('')}\n\`\`\``)
}
return `:::code-tabs#npm-to-${tabs.join('-')}\n${res.join('\n')}\n:::`
}
/**
*
*/
function findConfig(line: string): CommandConfig | undefined {
for (const { pattern, ...config } of Object.values(MANAGERS_CONFIG)) {
if (pattern.test(line)) {
@ -105,6 +123,9 @@ function findConfig(line: string): CommandConfig | undefined {
return undefined
}
/**
* tabs
*/
function validateTabs(tabs: NpmToPackageManager[]): NpmToPackageManager[] {
tabs = tabs.filter(tab => ALLOW_LIST.includes(tab))
if (tabs.length === 0) {
@ -113,16 +134,22 @@ function validateTabs(tabs: NpmToPackageManager[]): NpmToPackageManager[] {
return tabs
}
/**
*
*/
interface LineParsed {
env: string
cli: string
cmd: string
args?: string
scriptArgs?: string
env: string // 环境变量前缀
cli: string // 命令行工具npm/npx ...
cmd: string // 命令/脚本名
args?: string // 参数
scriptArgs?: string // 脚本参数
}
const LINE_REG = /(.*)(npm|npx)\s+(.*)/
/**
* npm/npx
*/
export function parseLine(line: string): false | LineParsed {
const match = line.match(LINE_REG)
if (!match)
@ -149,6 +176,9 @@ export function parseLine(line: string): false | LineParsed {
return { env, cli: `${cli} ${rest.slice(0, idx)}`, ...parseArgs(rest.slice(idx + 1)) }
}
/**
* npm
*/
function parseArgs(line: string): { cmd: string, args?: string, scriptArgs?: string } {
line = line?.trim()
@ -156,6 +186,7 @@ function parseArgs(line: string): { cmd: string, args?: string, scriptArgs?: str
let cmd = ''
let args = ''
if (npmArgs[0] !== '-') {
// 处理命令和参数
if (npmArgs[0] === '"' || npmArgs[0] === '\'') {
const idx = npmArgs.slice(1).indexOf(npmArgs[0])
cmd = npmArgs.slice(0, idx + 2)
@ -173,6 +204,7 @@ function parseArgs(line: string): { cmd: string, args?: string, scriptArgs?: str
}
}
else {
// 处理仅有参数的情况
let newLine = ''
let value = ''
let isQuote = false

View File

@ -4,13 +4,28 @@ import type { App } from 'vuepress'
import type { Markdown } from 'vuepress/markdown'
import type { DemoContainerRender, DemoMeta, MarkdownDemoEnv } from '../../shared/demo.js'
import container from 'markdown-it-container'
import { colors } from 'vuepress/utils'
import { createEmbedRuleBlock } from '../embed/createEmbedRuleBlock.js'
import { logger } from '../utils/logger.js'
import { resolveAttrs } from '../utils/resolveAttrs.js'
import { markdownContainerRender, markdownEmbed } from './markdown.js'
import { normalContainerRender, normalEmbed } from './normal.js'
import { normalizeAlias } from './supports/alias.js'
import { vueContainerRender, vueEmbed } from './vue.js'
const embedMap: Record<
DemoMeta['type'],
(app: App, md: Markdown, env: MarkdownDemoEnv, meta: DemoMeta) => string
> = {
vue: vueEmbed,
normal: normalEmbed,
markdown: markdownEmbed,
}
/**
*
* @[demo type info](url)
*/
export function demoEmbed(app: App, md: Markdown): void {
createEmbedRuleBlock<DemoMeta>(md, {
type: 'demo',
@ -23,21 +38,13 @@ export function demoEmbed(app: App, md: Markdown): void {
content: (meta, content, env: MarkdownDemoEnv) => {
const { url, type } = meta
if (!url) {
console.warn('[vuepress-plugin-md-power] Invalid demo url: ', url)
logger.warn('demo-vue', `Invalid filepath: ${colors.gray(url)}`)
return content
}
if (type === 'vue') {
return vueEmbed(app, md, env, meta)
}
if (type === 'normal') {
return normalEmbed(app, md, env, meta)
if (embedMap[type]) {
return embedMap[type](app, md, env, meta)
}
if (type === 'markdown') {
return markdownEmbed(app, md, env, meta)
}
return content
},
})

View File

@ -1,6 +1,8 @@
import type { App } from 'vuepress'
import type { Markdown } from 'vuepress/markdown'
import type { DemoContainerRender, DemoFile, DemoMeta, MarkdownDemoEnv } from '../../shared/demo.js'
import { colors } from 'vuepress/utils'
import { logger } from '../utils/logger.js'
import { stringifyAttrs } from '../utils/stringifyAttrs.js'
import { findFile, readFileSync } from './supports/file.js'
@ -13,7 +15,7 @@ export function markdownEmbed(
const filepath = findFile(app, env, url)
const code = readFileSync(filepath)
if (code === false) {
console.warn('[vuepress-plugin-md-power] Cannot read markdown file:', filepath)
logger.warn('demo-markdown', `Cannot read markdown file: ${colors.gray(filepath)}\n at: ${colors.gray(env.filePathRelative || '')}`)
return ''
}
const demo: DemoFile = { type: 'markdown', path: filepath }

View File

@ -3,6 +3,8 @@ import type { Markdown } from 'vuepress/markdown'
import type { DemoContainerRender, DemoFile, DemoMeta, MarkdownDemoEnv } from '../../shared/demo.js'
import fs from 'node:fs'
import path from 'node:path'
import { colors } from 'vuepress/utils'
import { logger } from '../utils/logger.js'
import { stringifyAttrs } from '../utils/stringifyAttrs.js'
import { compileScript, compileStyle } from './supports/compiler.js'
import { findFile, readFileSync, writeFileSync } from './supports/file.js'
@ -89,7 +91,7 @@ export async function compileCode(code: NormalCode, output: string): Promise<voi
}
}
catch (e) {
console.error('[vuepress-plugin-md-power] demo parse error: \n', e)
logger.error('demo-normal', 'demo parse error: \n', e)
}
writeFileSync(output, `import { ref } from "vue"\nexport default ref(${JSON.stringify(res, null, 2)})`)
@ -106,7 +108,7 @@ export function normalEmbed(
const code = readFileSync(filepath)
if (code === false) {
console.warn('[vuepress-plugin-md-power] Cannot read demo file:', filepath)
logger.warn('demo-normal', `Cannot read demo file: ${colors.gray(filepath)}\n at: ${colors.gray(env.filePathRelative || '')}`)
return ''
}
const source = parseEmbedCode(code)

View File

@ -2,6 +2,8 @@ import type { App } from 'vuepress'
import type { Markdown } from 'vuepress/markdown'
import type { DemoContainerRender, DemoFile, DemoMeta, MarkdownDemoEnv } from '../../shared/demo.js'
import path from 'node:path'
import { colors } from 'vuepress/utils'
import { logger } from '../utils/logger.js'
import { stringifyAttrs } from '../utils/stringifyAttrs.js'
import { findFile, readFileSync, writeFileSync } from './supports/file.js'
import { insertSetupScript } from './supports/insertScript.js'
@ -15,7 +17,7 @@ export function vueEmbed(
const filepath = findFile(app, env, url)
const code = readFileSync(filepath)
if (code === false) {
console.warn('[vuepress-plugin-md-power] Cannot read vue file:', filepath)
logger.warn('demo-vue', `Cannot read vue demo file: ${colors.gray(filepath)}\n at: ${colors.gray(env.filePathRelative || '')}`)
return ''
}

View File

@ -76,7 +76,7 @@ const audioReader: RuleInline = (state, silent) => {
export const audioReaderPlugin: PluginWithOptions<never> = (md) => {
md.renderer.rules.audio_reader = (tokens, idx) => {
const meta = (tokens[idx].meta ?? {}) as AudioReaderTokenMeta
const meta = tokens[idx].meta as AudioReaderTokenMeta
if (meta.startTime)
meta.startTime = Number(meta.startTime)

View File

@ -6,6 +6,7 @@ import type { PluginWithOptions } from 'markdown-it'
import type { ArtPlayerTokenMeta } from '../../../shared/index.js'
import { isPackageExists } from 'local-pkg'
import { colors } from 'vuepress/utils'
import { logger } from '../../utils/logger.js'
import { parseRect } from '../../utils/parseRect.js'
import { resolveAttrs } from '../../utils/resolveAttrs.js'
import { stringifyAttrs } from '../../utils/stringifyAttrs.js'
@ -75,11 +76,11 @@ function checkSupportType(type?: string) {
}
/* istanbul ignore if -- @preserve */
if (name) {
console.warn(`${colors.yellow('[vuepress-plugin-md-power] artPlayer: ')} ${colors.cyan(name)} is not installed, please install it via npm or yarn or pnpm`)
logger.warn('artPlayer', `${colors.cyan(name)} is not installed, please install it via npm or yarn or pnpm`)
}
}
else {
/* istanbul ignore next -- @preserve */
console.warn(`${colors.yellow('[vuepress-plugin-md-power] artPlayer: ')} unsupported video type: ${colors.cyan(type)}`)
logger.warn('artPlayer', `unsupported video type: ${colors.cyan(type)}`)
}
}

View File

@ -2,6 +2,7 @@ import type { PluginWithOptions } from 'markdown-it'
import type { MarkdownEnv } from 'vuepress/markdown'
import type { IconOptions } from '../../shared/index.js'
import { colors } from 'vuepress/utils'
import { logger } from '../utils/logger.js'
import { stringifyAttrs } from '../utils/stringifyAttrs.js'
import { createIconRule } from './createIconRule.js'
import { resolveIcon } from './resolveIcon.js'
@ -42,7 +43,7 @@ export const iconPlugin: PluginWithOptions<IconOptions> = (md, options = {}) =>
const [size, color] = opt.trim().split('/')
icon = `${name}${size ? ` =${size}` : ''}${color ? ` /${color}` : ''}`
console.warn(`The icon syntax of \`${colors.yellow(`:[${content}]:`)}\` is deprecated, please use \`${colors.green(`::${icon}::`)}\` instead. (${colors.gray(env.filePathRelative || env.filePath)})`)
logger.warn('icon', `The icon syntax of \`${colors.yellow(`:[${content}]:`)}\` is deprecated, please use \`${colors.green(`::${icon}::`)}\` instead. (${colors.gray(env.filePathRelative || env.filePath)})`)
}
return iconRender(icon, options)

View File

@ -2,6 +2,7 @@ import type { IconOptions } from '../../shared/index.js'
import { notNullish, toArray, uniqueBy } from '@pengzhanbo/utils'
import { isLinkAbsolute } from '@vuepress/helper'
import { isLinkHttp } from 'vuepress/shared'
import { logger } from '../utils/logger.js'
interface AssetInfo {
type: 'style' | 'script'
@ -76,7 +77,7 @@ function normalizeAsset(asset: string, provide?: string): AssetInfo | null {
if (asset.endsWith('.css')) {
return { type: 'style', link, provide }
}
console.error(`[vuepress:icon] Can not recognize icon link: "${asset}"`)
logger.error('icon', `Can not recognize icon link: "${asset}"`)
return null
}

View File

@ -0,0 +1,78 @@
/* istanbul ignore file -- @preserve */
/* eslint-disable no-console */
import { colors, ora } from 'vuepress/utils'
type Ora = ReturnType<typeof ora>
/**
* Logger utils
*/
export class Logger {
public constructor(
/**
* Plugin/Theme name
*/
private readonly name = '',
) {}
private init(subname: string, text: string): Ora {
return ora({
prefixText: colors.blue(`${this.name}${subname ? `:${subname}` : ''}: `),
text,
})
}
/**
* Create a loading spinner with text
*/
public load(subname: string, msg: string): {
succeed: (text?: string) => void
fail: (text?: string) => void
} {
const instance = this.init(subname, msg)
return {
succeed: (text?: string) => instance.succeed(text),
fail: (text?: string) => instance.succeed(text),
}
}
public info(subname: string, text = '', ...args: unknown[]): void {
this.init(subname, colors.blue(text)).info()
if (args.length)
console.info(...args)
}
/**
* Log success msg
*/
public succeed(subname: string, text = '', ...args: unknown[]): void {
this.init(subname, colors.green(text)).succeed()
if (args.length)
console.log(...args)
}
/**
* Log warning msg
*/
public warn(subname: string, text = '', ...args: unknown[]): void {
this.init(subname, colors.yellow(text)).warn()
if (args.length)
console.warn(...args)
}
/**
* Log error msg
*/
public error(subname: string, text = '', ...args: unknown[]): void {
this.init(subname, colors.red(text)).fail()
if (args.length)
console.error(...args)
}
}
export const logger: Logger = new Logger('vuepress-plugin-md-power')