Skip to content

Commit 6353ff5

Browse files
committed
chore: bump version
1 parent a87bd94 commit 6353ff5

File tree

6 files changed

+445
-204
lines changed

6 files changed

+445
-204
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'tailwindcss-patch': minor
3+
---
4+
5+
Expose helpers to mount the Tailwind CSS patch CLI inside other `cac` apps with prefixed or renamed commands.

packages/tailwindcss-patch/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,26 @@ pnpm dlx tw-patch extract
3939
pnpm dlx tw-patch tokens --format lines
4040
```
4141

42+
### Embed into another CLI
43+
44+
Reuse the same commands inside your own `cac` program:
45+
46+
```ts
47+
import cac from 'cac'
48+
import { mountTailwindcssPatchCommands } from 'tailwindcss-patch'
49+
50+
const cli = cac('my-tool')
51+
mountTailwindcssPatchCommands(cli, {
52+
commandPrefix: 'tw:', // optional
53+
commands: ['install', 'tokens'], // mount a subset if needed (defaults to all)
54+
commandOptions: {
55+
install: { name: 'patch-install', aliases: ['tw-install'] }, // override names/aliases
56+
},
57+
})
58+
cli.help()
59+
cli.parse()
60+
```
61+
4262
### Extract options
4363

4464
| Flag | Description |
Lines changed: 2 additions & 204 deletions
Original file line numberDiff line numberDiff line change
@@ -1,207 +1,5 @@
1-
import type { LegacyTailwindcssPatcherOptions } from './options/legacy'
2-
import type {
3-
TailwindcssPatchOptions,
4-
TailwindTokenByFileMap,
5-
TailwindTokenLocation,
6-
} from './types'
7-
import process from 'node:process'
8-
9-
import { CONFIG_NAME, getConfig, initConfig } from '@tailwindcss-mangle/config'
10-
import { defu } from '@tailwindcss-mangle/shared'
11-
import cac from 'cac'
12-
import fs from 'fs-extra'
13-
import path from 'pathe'
14-
15-
import { TailwindcssPatcher } from './api/tailwindcss-patcher'
16-
import { groupTokensByFile } from './extraction/candidate-extractor'
17-
import logger from './logger'
18-
import { fromLegacyOptions, fromUnifiedConfig } from './options/legacy'
19-
20-
const cli = cac('tw-patch')
21-
22-
async function loadPatchOptions(cwd: string, overrides?: TailwindcssPatchOptions) {
23-
const { config } = await getConfig(cwd)
24-
const legacyConfig = config as (typeof config) & { patch?: LegacyTailwindcssPatcherOptions['patch'] }
25-
const base = config?.registry
26-
? fromUnifiedConfig(config.registry)
27-
: legacyConfig?.patch
28-
? fromLegacyOptions({ patch: legacyConfig.patch })
29-
: {}
30-
const merged = defu<TailwindcssPatchOptions, TailwindcssPatchOptions[]>(overrides ?? {}, base)
31-
return merged
32-
}
33-
34-
cli
35-
.command('install', 'Apply Tailwind CSS runtime patches')
36-
.option('--cwd <dir>', 'Working directory', { default: process.cwd() })
37-
.action(async (args: { cwd: string }) => {
38-
const options = await loadPatchOptions(args.cwd)
39-
const patcher = new TailwindcssPatcher(options)
40-
await patcher.patch()
41-
logger.success('Tailwind CSS runtime patched successfully.')
42-
})
43-
44-
cli
45-
.command('extract', 'Collect generated class names into a cache file')
46-
.option('--cwd <dir>', 'Working directory', { default: process.cwd() })
47-
.option('--output <file>', 'Override output file path')
48-
.option('--format <format>', 'Output format (json|lines)')
49-
.option('--css <file>', 'Tailwind CSS entry CSS when using v4')
50-
.option('--no-write', 'Skip writing to disk')
51-
.action(async (args: { cwd: string, output?: string, format?: 'json' | 'lines', css?: string, write?: boolean }) => {
52-
const overrides: TailwindcssPatchOptions = {}
53-
54-
if (args.output || args.format) {
55-
overrides.output = {
56-
file: args.output,
57-
format: args.format,
58-
}
59-
}
60-
61-
if (args.css) {
62-
overrides.tailwind = {
63-
v4: {
64-
cssEntries: [args.css],
65-
},
66-
}
67-
}
68-
69-
const options = await loadPatchOptions(args.cwd, overrides)
70-
const patcher = new TailwindcssPatcher(options)
71-
const result = await patcher.extract({ write: args.write })
72-
73-
if (result.filename) {
74-
logger.success(`Collected ${result.classList.length} classes → ${result.filename}`)
75-
}
76-
else {
77-
logger.success(`Collected ${result.classList.length} classes.`)
78-
}
79-
})
80-
81-
type TokenOutputFormat = 'json' | 'lines' | 'grouped-json'
82-
type TokenGroupKey = 'relative' | 'absolute'
83-
const TOKEN_FORMATS: TokenOutputFormat[] = ['json', 'lines', 'grouped-json']
84-
85-
cli
86-
.command('tokens', 'Extract Tailwind tokens with file/position metadata')
87-
.option('--cwd <dir>', 'Working directory', { default: process.cwd() })
88-
.option('--output <file>', 'Override output file path', { default: '.tw-patch/tw-token-report.json' })
89-
.option('--format <format>', 'Output format (json|lines|grouped-json)', { default: 'json' })
90-
.option('--group-key <key>', 'Grouping key for grouped-json output (relative|absolute)', { default: 'relative' })
91-
.option('--no-write', 'Skip writing to disk')
92-
.action(async (args: {
93-
cwd: string
94-
output?: string
95-
format?: TokenOutputFormat
96-
groupKey?: TokenGroupKey
97-
write?: boolean
98-
}) => {
99-
const options = await loadPatchOptions(args.cwd)
100-
const patcher = new TailwindcssPatcher(options)
101-
const report = await patcher.collectContentTokens()
102-
103-
const shouldWrite = args.write ?? true
104-
let format: TokenOutputFormat = args.format ?? 'json'
105-
if (!TOKEN_FORMATS.includes(format)) {
106-
format = 'json'
107-
}
108-
const targetFile = args.output ?? '.tw-patch/tw-token-report.json'
109-
const groupKey: TokenGroupKey = args.groupKey === 'absolute' ? 'absolute' : 'relative'
110-
const buildGrouped = () =>
111-
groupTokensByFile(report, {
112-
key: groupKey,
113-
stripAbsolutePaths: groupKey !== 'absolute',
114-
})
115-
const grouped = format === 'grouped-json' ? buildGrouped() : null
116-
const resolveGrouped = () => grouped ?? buildGrouped()
117-
118-
if (shouldWrite) {
119-
const target = path.resolve(targetFile)
120-
await fs.ensureDir(path.dirname(target))
121-
if (format === 'json') {
122-
await fs.writeJSON(target, report, { spaces: 2 })
123-
}
124-
else if (format === 'grouped-json') {
125-
await fs.writeJSON(target, resolveGrouped(), { spaces: 2 })
126-
}
127-
else {
128-
const lines = report.entries.map(formatTokenLine)
129-
await fs.writeFile(target, `${lines.join('\n')}\n`, 'utf8')
130-
}
131-
logger.success(
132-
`Collected ${report.entries.length} tokens (${format}) → ${target.replace(process.cwd(), '.')}`,
133-
)
134-
}
135-
else {
136-
logger.success(`Collected ${report.entries.length} tokens from ${report.filesScanned} files.`)
137-
if (format === 'lines') {
138-
const preview = report.entries.slice(0, 5).map(formatTokenLine).join('\n')
139-
if (preview) {
140-
logger.log('')
141-
logger.info(preview)
142-
if (report.entries.length > 5) {
143-
logger.info(`…and ${report.entries.length - 5} more.`)
144-
}
145-
}
146-
}
147-
else if (format === 'grouped-json') {
148-
const map = resolveGrouped()
149-
const { preview, moreFiles } = formatGroupedPreview(map)
150-
if (preview) {
151-
logger.log('')
152-
logger.info(preview)
153-
if (moreFiles > 0) {
154-
logger.info(`…and ${moreFiles} more files.`)
155-
}
156-
}
157-
}
158-
else {
159-
const previewEntries = report.entries.slice(0, 3)
160-
if (previewEntries.length) {
161-
logger.log('')
162-
logger.info(JSON.stringify(previewEntries, null, 2))
163-
}
164-
}
165-
}
166-
167-
if (report.skippedFiles.length) {
168-
logger.warn('Skipped files:')
169-
for (const skipped of report.skippedFiles) {
170-
logger.warn(` • ${skipped.file} (${skipped.reason})`)
171-
}
172-
}
173-
})
174-
175-
cli
176-
.command('init', 'Generate a tailwindcss-patch config file')
177-
.option('--cwd <dir>', 'Working directory', { default: process.cwd() })
178-
.action(async (args: { cwd: string }) => {
179-
await initConfig(args.cwd)
180-
logger.success(`✨ ${CONFIG_NAME}.config.ts initialized!`)
181-
})
1+
import { createTailwindcssPatchCli } from './cli/commands'
1822

3+
const cli = createTailwindcssPatchCli()
1834
cli.help()
1845
cli.parse()
185-
186-
function formatTokenLine(entry: TailwindTokenLocation) {
187-
return `${entry.relativeFile}:${entry.line}:${entry.column} ${entry.rawCandidate} (${entry.start}-${entry.end})`
188-
}
189-
190-
function formatGroupedPreview(map: TailwindTokenByFileMap, limit: number = 3) {
191-
const files = Object.keys(map)
192-
if (!files.length) {
193-
return { preview: '', moreFiles: 0 }
194-
}
195-
196-
const lines = files.slice(0, limit).map((file) => {
197-
const tokens = map[file]
198-
const sample = tokens.slice(0, 3).map(token => token.rawCandidate).join(', ')
199-
const suffix = tokens.length > 3 ? ', …' : ''
200-
return `${file}: ${tokens.length} tokens (${sample}${suffix})`
201-
})
202-
203-
return {
204-
preview: lines.join('\n'),
205-
moreFiles: Math.max(0, files.length - limit),
206-
}
207-
}

0 commit comments

Comments
 (0)