#!/usr/bin/env node /** * ═══════════════════════════════════════════════════ * 铸渊图片工作室 · 主入口 * ═══════════════════════════════════════════════════ * * 冰朔,你告诉我内容和想要的类型,我帮你生成。 * * 用法: * node generate.js xiaohongshu --title "标题" --body "正文" * node generate.js jike --title "观点" --body "内容" * node generate.js poster --title "海报标题" --details "时间:5月1日" "地点:广州" * * 也可以直接在代码里调用 API: * import { generate } from './generate.js' * await generate({ type: 'xiaohongshu', title: '...', body: '...' }) */ import { renderToImage, renderCarousel } from './renderer.js' import { xiaohongshuCard, xiaohongshuCarousel } from './templates/xiaohongshu.js' import { jikeCard } from './templates/jike.js' import { posterCard } from './templates/poster.js' import { STYLES, useScheme } from './config.js' /** * 统一生成入口 * @param {object} opts * @param {'xiaohongshu'|'jike'|'poster'} opts.type - 卡片类型 * @param {string} opts.title - 标题 * @param {string} opts.body - 正文 * @param {string} opts.subtitle - 副标题 * @param {string} opts.tag - 标签 * @param {string} opts.quote - 金句(小红书 quote 布局用) * @param {string} opts.emoji - Emoji(即刻用) * @param {string} opts.footer - 底部文字 * @param {string[]} opts.details - 详情列表(海报用) * @param {string} opts.cta - 行动号召(海报用) * @param {'default'|'quote'|'list'|'minimal'} opts.layout - 布局 * @param {'default'|'notice'|'recruit'} opts.style - 海报风格 * @param {string} opts.scheme - 配色方案名 * @param {string} opts.output - 输出文件名(不含扩展名) * @param {boolean} opts.carousel - 是否拆为多页轮播 * @param {string[]} opts.pages - 多页正文(轮播用) * @returns {Promise<{files: string[], type: string}>} */ export async function generate(opts = {}) { const { type = 'xiaohongshu', title = '', body = '', subtitle = '', tag = '', quote = '', emoji = '', footer = '', details = [], cta = '', layout = 'default', style = 'default', scheme = '', output = '', carousel = false, pages = [], } = opts // 切换配色 if (scheme) useScheme(scheme) const baseName = output || `${type}_${Date.now()}` const size = STYLES.sizes[type] || STYLES.sizes.xiaohongshu let files = [] switch (type) { case 'xiaohongshu': { if (carousel && pages.length > 0) { const htmls = xiaohongshuCarousel({ title, subtitle, tag, quote, layout }, pages) files = await renderCarousel(htmls, { ...size, baseName }) } else { const html = xiaohongshuCard({ title, body, subtitle, tag, quote, footer, layout }) files = [await renderToImage(html, { ...size, name: baseName })] } break } case 'jike': { const html = jikeCard({ title, body, meta: subtitle, emoji, layout }) files = [await renderToImage(html, { ...size, name: baseName })] break } case 'poster': { const html = posterCard({ title, subtitle, body, details, cta, footer, tag, style }) files = [await renderToImage(html, { ...size, name: baseName })] break } default: throw new Error(`未知卡片类型: ${type}`) } return { files, type } } /* ── CLI 入口 ── */ async function cli() { const args = process.argv.slice(2) const type = args.find(a => !a.startsWith('--')) if (!type || type === 'help') { console.log(` 铸渊图片工作室 · 用法 node generate.js <类型> [选项] 类型: xiaohongshu 小红书竖版卡片 (1080×1440) jike 即刻方卡 (1080×1080) poster 海报 (1080×1920) 选项: --title 标题 --body 正文 --subtitle 副标题/元信息 --tag 标签 --quote 金句(小红书 quote 布局) --emoji Emoji(即刻) --details 详情(海报,可多个) --cta 行动号召(海报) --footer 底部文字 --layout 布局: default|quote|list|minimal --style 海报风格: default|notice|recruit --scheme 配色: guanghu|cream|night|green --output 输出文件名 示例: node generate.js xiaohongshu --title "如何高效学习" --body "第一点\\n第二点\\n第三点" node generate.js jike --title "一个思考" --body "今天想到的..." --emoji "💡" node generate.js poster --title "放假通知" --style notice --details "时间:5月1日" "地点:广州" `) process.exit(0) } function getArg(key) { const idx = args.indexOf(key) if (idx === -1) return undefined // 收集后续所有非 -- 开头的参数,直到遇到下一个 -- const values = [] for (let i = idx + 1; i < args.length; i++) { if (args[i].startsWith('--')) break values.push(args[i]) } return values.length === 0 ? undefined : values.length === 1 ? values[0] : values } const opts = { type, title: getArg('--title') || '', body: (() => { const b = getArg('--body') const text = Array.isArray(b) ? b.join('\n') : (b || '') // 将 \n 转义符转为真实换行 return text.replace(/\\n/g, '\n') })(), subtitle: getArg('--subtitle') || '', tag: getArg('--tag') || '', quote: getArg('--quote') || '', emoji: getArg('--emoji') || '', footer: getArg('--footer') || '', details: (() => { const d = getArg('--details') return Array.isArray(d) ? d : (d ? [d] : []) })(), cta: getArg('--cta') || '', layout: getArg('--layout') || 'default', style: getArg('--style') || 'default', scheme: getArg('--scheme') || '', output: getArg('--output') || '', } console.log(`🎨 生成${type}卡片...`) const result = await generate(opts) console.log(`✅ 生成完成!`) result.files.forEach(f => console.log(` 📄 ${f}`)) } // 如果直接运行 if (process.argv[1] && (process.argv[1].includes('generate.js'))) { cli().catch(err => { console.error('❌ 生成失败:', err.message) process.exit(1) }) }