feat(cli): export DOM HTML as Tailwind classes

This commit is contained in:
Danila Poyarkov 2026-07-04 13:16:21 +03:00
parent 7af2dba0ec
commit 13ef7ac941
3 changed files with 53 additions and 5 deletions

View file

@ -10,7 +10,8 @@ import {
serializeHTML,
tailwindHTMLToDesignDocument,
tailwindHTMLToSceneGraph,
type DesignDocument
type DesignDocument,
type SerializeHTMLOptions
} from '@open-pencil/dom-css'
import { requireFile } from '#cli/app-client'
@ -18,6 +19,7 @@ import { fmtList, ok, printError } from '#cli/format'
const io = new IORegistry(BUILTIN_IO_FORMATS)
const OUTPUT_FORMATS = new Set(['fig', 'html', 'json'])
const HTML_STYLES = new Set(['inline', 'tailwind'])
interface DomArgs {
file?: string
@ -28,6 +30,7 @@ interface DomArgs {
tailwind?: string
tailwindFile?: string
pageName: string
htmlStyle: SerializeHTMLOptions['style']
json?: boolean
}
@ -97,7 +100,7 @@ async function writeOutput(
}
if (format === 'html') {
await Bun.write(output, serializeHTML(document))
await Bun.write(output, serializeHTML(document, { style: args.htmlStyle }))
return output
}
@ -151,6 +154,11 @@ export default defineCommand({
description: 'Scene graph page name (default: DOM/CSS)',
default: 'DOM/CSS'
},
htmlStyle: {
type: 'string',
description: 'HTML style output: inline or tailwind (default: inline)',
default: 'inline'
},
json: {
type: 'boolean',
description: 'Print a machine-readable summary to stdout'
@ -162,9 +170,16 @@ export default defineCommand({
printError(`Invalid format "${args.format}". Use fig, html, or json.`)
process.exit(1)
}
const htmlStyleArg = args.htmlStyle.toLowerCase()
if (!HTML_STYLES.has(htmlStyleArg)) {
printError(`Invalid htmlStyle "${args.htmlStyle}". Use inline or tailwind.`)
process.exit(1)
}
const htmlStyle = htmlStyleArg as SerializeHTMLOptions['style']
const { document, graph } = await convertDom(args)
const output = await writeOutput(args, document, graph)
const normalizedArgs = { ...args, htmlStyle }
const { document, graph } = await convertDom(normalizedArgs)
const output = await writeOutput(normalizedArgs, document, graph)
const pages = graph.getPages()
const summary = {
input: requireFile(args.file),

View file

@ -62,8 +62,11 @@ function mergeClassNames(...values: Array<string | undefined>): string | undefin
function serializeAttrs(node: DesignElement, options: SerializeHTMLOptions): string {
const style = serializeStyle(node)
const tailwindClass = options.style === 'tailwind' ? serializeTailwindClasses(node) : undefined
const attrsWithoutStyle = { ...node.attrs }
delete attrsWithoutStyle.style
const sourceAttrs = options.style === 'tailwind' && tailwindClass ? attrsWithoutStyle : node.attrs
const attrs = {
...node.attrs,
...sourceAttrs,
...(tailwindClass ? { class: mergeClassNames(node.attrs.class, tailwindClass) } : {}),
...(style && options.style !== 'tailwind' ? { style } : {})
}

View file

@ -162,6 +162,36 @@ test('dom CLI writes serialized HTML output', async () => {
expect(html).toContain('class="card"')
})
test('dom CLI can write HTML styles as Tailwind classes', async () => {
const dir = await mkdtemp(join(tmpdir(), 'open-pencil-dom-cli-html-tailwind-'))
const htmlPath = join(dir, 'card.html')
const output = join(dir, 'card.out.html')
await Bun.write(
htmlPath,
'<section class="card" style="display:flex;padding:16px;gap:8px;background-color:white">Tailwind HTML</section>'
)
const { stderr, exitCode } = await run([
'dom',
htmlPath,
'--format',
'html',
'--htmlStyle',
'tailwind',
'--output',
output
])
expect(stderr).toBe('')
expect(exitCode).toBe(0)
const html = await Bun.file(output).text()
expect(html).toContain('Tailwind HTML')
expect(html).toContain('class="card flex p-4 gap-2 bg-white"')
expect(html).not.toContain('style=')
})
test('dom CLI writes a .fig that core IO can import', async () => {
const { htmlPath, cssPath, dir } = await createFixture()
const output = join(dir, 'card.fig')