1- import type { CommonCommandOptions , ExtractCommandOptions , TokensCommandOptions } from './cli/types'
2- import type { UserDefinedOptions } from '@/types'
3- import { writeFile } from 'node:fs/promises'
4- import path from 'node:path'
1+ import type {
2+ TailwindcssPatchCliMountOptions ,
3+ TailwindcssPatchCommand ,
4+ TailwindcssPatchCommandHandler ,
5+ } from 'tailwindcss-patch'
6+ import type { CommonCommandOptions } from './cli/types'
57import process from 'node:process'
6- import cac from 'cac'
78import semver from 'semver'
8- import { groupTokensByFile } from 'tailwindcss-patch'
9+ import { createTailwindcssPatchCli } from 'tailwindcss-patch'
910import { clearTailwindcssPatcherCache } from '@/context'
10- import { loadTailwindcssMangleConfig } from './cli/config'
11- import {
12- buildTailwindcssPatcherOptions ,
13- createCliContext ,
14- formatOutputPath ,
15- resolveEntry ,
16- } from './cli/context'
11+ import { formatOutputPath } from './cli/context'
1712import {
1813 commandAction ,
19- ensureDir ,
20- normalizeExtractFormat ,
21- normalizeTokenFormat ,
2214 readStringArrayOption ,
2315 readStringOption ,
2416 resolveCliCwd ,
2517 toBoolean ,
2618} from './cli/helpers'
27- import { collectTailwindTokens , formatTokenLine , logTokenPreview } from './cli/tokens'
2819import {
2920 DEFAULT_VSCODE_ENTRY_OUTPUT ,
3021 generateVscodeIntellisenseEntry ,
@@ -40,166 +31,88 @@ type VscodeEntryCommandOptions = CommonCommandOptions & {
4031 source ?: string | string [ ]
4132}
4233
43- process . title = 'node (weapp-tailwindcss)'
44-
45- if ( semver . lt ( process . versions . node , WEAPP_TW_REQUIRED_NODE_VERSION ) ) {
46- logger . warn (
47- `You are using Node.js ${ process . versions . node } . For weapp-tailwindcss, Node.js version >= v${ WEAPP_TW_REQUIRED_NODE_VERSION } is required.` ,
48- )
34+ function handleCliError ( error : unknown ) {
35+ if ( error instanceof Error ) {
36+ logger . error ( error . message )
37+ if ( error . stack && process . env . WEAPP_TW_DEBUG === '1' ) {
38+ logger . error ( error . stack )
39+ }
40+ }
41+ else {
42+ logger . error ( String ( error ) )
43+ }
4944}
5045
51- const cli = cac ( 'weapp-tailwindcss' )
46+ function withCommandErrorHandling < TCommand extends TailwindcssPatchCommand > (
47+ handler : TailwindcssPatchCommandHandler < TCommand > ,
48+ ) : TailwindcssPatchCommandHandler < TCommand > {
49+ return ( async ( ctx , next ) => {
50+ try {
51+ return await handler ( ctx , next )
52+ }
53+ catch ( error ) {
54+ handleCliError ( error )
55+ process . exitCode = 1
56+ return undefined as ReturnType < TailwindcssPatchCommandHandler < TCommand > >
57+ }
58+ } ) as TailwindcssPatchCommandHandler < TCommand >
59+ }
5260
53- cli
54- . command ( 'patch' , 'Apply Tailwind CSS runtime patches' )
55- . alias ( 'install' )
56- . option ( '--cwd <dir>' , 'Working directory' )
57- . option ( '--record-target' , 'Write tailwindcss target metadata (node_modules/.cache/weapp-tailwindcss/tailwindcss-target.json). Pass "--record-target false" to skip.' )
58- . option ( '--clear-cache' , 'Clear tailwindcss-patch cache before patch (opt-in)' )
59- . action (
60- commandAction ( async ( options : CommonCommandOptions ) => {
61- const resolvedCwd = resolveCliCwd ( options . cwd )
62- const ctx = createCliContext ( undefined , resolvedCwd )
63- const shouldClearCache = toBoolean ( ( options as any ) . clearCache , false )
61+ const mountOptions : TailwindcssPatchCliMountOptions = {
62+ commandOptions : {
63+ install : {
64+ name : 'patch' ,
65+ aliases : [ 'install' ] ,
66+ optionDefs : [
67+ {
68+ flags : '--record-target' ,
69+ description :
70+ 'Write tailwindcss target metadata (node_modules/.cache/weapp-tailwindcss/tailwindcss-target.json). Pass "--record-target false" to skip.' ,
71+ config : { default : true } ,
72+ } ,
73+ {
74+ flags : '--clear-cache' ,
75+ description : 'Clear tailwindcss-patch cache before patch (opt-in)' ,
76+ } ,
77+ ] ,
78+ } ,
79+ } ,
80+ commandHandlers : {
81+ install : withCommandErrorHandling < 'install' > ( async ( ctx ) => {
82+ const shouldClearCache = toBoolean ( ( ctx as any ) . args . clearCache , false )
83+ const shouldRecordTarget = toBoolean ( ( ctx as any ) . args . recordTarget , true )
84+ const patcher = await ctx . createPatcher ( )
6485 if ( shouldClearCache ) {
65- await clearTailwindcssPatcherCache ( ctx . twPatcher , { removeDirectory : true } )
86+ await clearTailwindcssPatcherCache ( patcher , { removeDirectory : true } )
6687 }
67- logTailwindcssTarget ( 'cli' , ctx . twPatcher , ctx . tailwindcssBasedir )
68- await ctx . twPatcher . patch ( )
69- const shouldRecordTarget = toBoolean ( options . recordTarget , true )
88+ logTailwindcssTarget ( 'cli' , patcher , ctx . cwd )
89+ await patcher . patch ( )
7090 if ( shouldRecordTarget ) {
71- const recordPath = await saveCliPatchTargetRecord ( ctx . tailwindcssBasedir , ctx . twPatcher )
91+ const recordPath = await saveCliPatchTargetRecord ( ctx . cwd , patcher )
7292 if ( recordPath ) {
73- logger . info ( `记录 weapp-tw patch 目标 -> ${ formatOutputPath ( recordPath , resolvedCwd ) } ` )
93+ logger . info ( `记录 weapp-tw patch 目标 -> ${ formatOutputPath ( recordPath , ctx . cwd ) } ` )
7494 }
7595 }
7696 logger . success ( 'Tailwind CSS 运行时补丁已完成。' )
7797 } ) ,
78- )
79-
80- cli
81- . command ( 'extract' , 'Collect generated class names into a cache file' )
82- . option ( '--cwd <dir>' , 'Working directory' )
83- . option ( '--output <file>' , 'Override output file path' )
84- . option ( '--format <format>' , 'Output format (json|lines)' )
85- . option ( '--css <file>' , 'Tailwind CSS entry CSS when using v4' )
86- . option ( '--no-write' , 'Skip writing to disk' )
87- . action (
88- commandAction ( async ( options : ExtractCommandOptions ) => {
89- const resolvedCwd = resolveCliCwd ( options . cwd )
90- const outputPath = readStringOption ( 'output' , options . output )
91- const formatOption = readStringOption ( 'format' , options . format )
92- const cssOption = readStringOption ( 'css' , options . css )
93-
94- const overrides : Partial < UserDefinedOptions > = { }
95- if ( cssOption ) {
96- overrides . cssEntries = [ resolveEntry ( cssOption , resolvedCwd ) ]
97- }
98-
99- const normalizedFormat = normalizeExtractFormat ( formatOption )
100- const outputOverrides = buildTailwindcssPatcherOptions (
101- normalizedFormat || outputPath
102- ? {
103- output : {
104- file : outputPath ,
105- format : normalizedFormat ,
106- } ,
107- }
108- : undefined ,
109- )
110-
111- if ( outputOverrides ) {
112- overrides . tailwindcssPatcherOptions = outputOverrides
113- }
114-
115- const ctx = createCliContext ( overrides , resolvedCwd )
116- const write = toBoolean ( options . write , true )
117- const result = await ctx . twPatcher . extract ( { write } )
118- const classCount = result ?. classList ?. length ?? result ?. classSet ?. size ?? 0
119-
120- if ( result ?. filename ) {
121- logger . success ( `Collected ${ classCount } classes -> ${ formatOutputPath ( result . filename , resolvedCwd ) } ` )
122- }
123- else {
124- logger . success ( `Collected ${ classCount } classes.` )
125- }
126- } ) ,
127- )
128-
129- cli
130- . command ( 'tokens' , 'Extract Tailwind tokens with location metadata' )
131- . option ( '--cwd <dir>' , 'Working directory' )
132- . option ( '--output <file>' , 'Override output file path' )
133- . option ( '--format <format>' , 'Output format (json|lines|grouped-json)' )
134- . option ( '--group-key <key>' , 'Grouping key for grouped-json output (relative|absolute)' )
135- . option ( '--no-write' , 'Skip writing to disk' )
136- . action (
137- commandAction ( async ( options : TokensCommandOptions ) => {
138- const resolvedCwd = resolveCliCwd ( options . cwd )
139- const outputPath = readStringOption ( 'output' , options . output )
140- const formatInput = readStringOption ( 'format' , options . format )
141- const groupKeyInput = readStringOption ( 'group-key' , options . groupKey )
142-
143- const format = normalizeTokenFormat ( formatInput ?? 'json' )
144- const groupKey = groupKeyInput === 'absolute' ? 'absolute' : 'relative'
145- const write = toBoolean ( options . write , true )
146- const ctx = createCliContext ( undefined , resolvedCwd )
147- const report = await collectTailwindTokens ( ctx . twPatcher )
148- const baseDir = resolvedCwd ?? process . cwd ( )
98+ extract : withCommandErrorHandling < 'extract' > ( async ( _ctx , next ) => next ( ) ) ,
99+ tokens : withCommandErrorHandling < 'tokens' > ( async ( _ctx , next ) => next ( ) ) ,
100+ init : withCommandErrorHandling < 'init' > ( async ( _ctx , next ) => next ( ) ) ,
101+ } ,
102+ }
149103
150- if ( write ) {
151- const targetRelative = outputPath ?? '.tw-patch/tw-token-report.json'
152- const target = path . resolve ( baseDir , targetRelative )
153- await ensureDir ( path . dirname ( target ) )
154- if ( format === 'json' ) {
155- await writeFile ( target , `${ JSON . stringify ( report , null , 2 ) } \n` , 'utf8' )
156- }
157- else if ( format === 'grouped-json' ) {
158- const grouped = groupTokensByFile ( report , {
159- key : groupKey ,
160- stripAbsolutePaths : groupKey !== 'absolute' ,
161- } )
162- await writeFile ( target , `${ JSON . stringify ( grouped , null , 2 ) } \n` , 'utf8' )
163- }
164- else {
165- const lines = report . entries . map ( formatTokenLine )
166- await writeFile ( target , `${ lines . join ( '\n' ) } \n` , 'utf8' )
167- }
168- logger . success (
169- `Collected ${ report . entries . length } tokens (${ format } ) -> ${ formatOutputPath ( target , resolvedCwd ) } ` ,
170- )
171- }
172- else {
173- logger . success ( `Collected ${ report . entries . length } tokens from ${ report . filesScanned } files.` )
174- logTokenPreview ( report , format , groupKey )
175- }
104+ process . title = 'node (weapp-tailwindcss)'
176105
177- if ( report . skippedFiles . length > 0 ) {
178- logger . warn ( 'Skipped files:' )
179- for ( const skipped of report . skippedFiles ) {
180- logger . warn ( ` - ${ skipped . file } (${ skipped . reason } )` )
181- }
182- }
183- } ) ,
106+ if ( semver . lt ( process . versions . node , WEAPP_TW_REQUIRED_NODE_VERSION ) ) {
107+ logger . warn (
108+ `You are using Node.js ${ process . versions . node } . For weapp-tailwindcss, Node.js version >= v${ WEAPP_TW_REQUIRED_NODE_VERSION } is required.` ,
184109 )
110+ }
185111
186- cli
187- . command ( 'init' , 'Generate a tailwindcss-patch config file' )
188- . option ( '--cwd <dir>' , 'Working directory' )
189- . action (
190- commandAction ( async ( options : CommonCommandOptions ) => {
191- const resolvedCwd = resolveCliCwd ( options . cwd )
192- const moduleResult = await loadTailwindcssMangleConfig ( )
193- if ( ! moduleResult ) {
194- logger . error ( 'Unable to load @tailwindcss-mangle/config. Please install tailwindcss-patch >= 8.2.0.' )
195- process . exitCode = 1
196- return
197- }
198- const cwd = resolvedCwd ?? process . cwd ( )
199- await moduleResult . initConfig ( cwd )
200- logger . success ( `${ moduleResult . CONFIG_NAME } .config.ts initialized.` )
201- } ) ,
202- )
112+ const cli = createTailwindcssPatchCli ( {
113+ name : 'weapp-tailwindcss' ,
114+ mountOptions,
115+ } )
203116
204117cli
205118 . command ( 'vscode-entry' , 'Generate a VS Code helper CSS for Tailwind IntelliSense' )
0 commit comments