tailwindcss-patch 9.0.0-alpha.1 → 9.0.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +20 -0
  2. package/dist/{chunk-Z6OMJZTU.js → chunk-77GHKSKG.js} +59 -732
  3. package/dist/{chunk-SWLOK2S6.mjs → chunk-D6ICWMM4.mjs} +63 -736
  4. package/dist/chunk-PMN7HS4Y.js +25 -0
  5. package/dist/chunk-YYBY7EM5.mjs +21 -0
  6. package/dist/cli.js +7 -5
  7. package/dist/cli.mjs +5 -3
  8. package/dist/commands/cli-runtime.d.mts +13 -0
  9. package/dist/commands/cli-runtime.d.ts +13 -0
  10. package/dist/commands/cli-runtime.js +1331 -0
  11. package/dist/commands/cli-runtime.mjs +1331 -0
  12. package/dist/index.d.mts +17 -677
  13. package/dist/index.d.ts +17 -677
  14. package/dist/index.js +5 -7
  15. package/dist/index.mjs +6 -8
  16. package/dist/validate-nbmOI2w8.d.mts +677 -0
  17. package/dist/validate-nbmOI2w8.d.ts +677 -0
  18. package/package.json +11 -3
  19. package/src/api/tailwindcss-patcher.ts +424 -0
  20. package/src/babel/index.ts +12 -0
  21. package/src/cache/context.ts +212 -0
  22. package/src/cache/store.ts +1440 -0
  23. package/src/cache/types.ts +71 -0
  24. package/src/cli.bundle.ts +20 -0
  25. package/src/cli.ts +20 -0
  26. package/src/commands/basic-handlers.ts +145 -0
  27. package/src/commands/cli.ts +56 -0
  28. package/src/commands/command-context.ts +77 -0
  29. package/src/commands/command-definitions.ts +102 -0
  30. package/src/commands/command-metadata.ts +68 -0
  31. package/src/commands/command-registrar.ts +39 -0
  32. package/src/commands/command-runtime.ts +33 -0
  33. package/src/commands/default-handler-map.ts +25 -0
  34. package/src/commands/migrate-config.ts +104 -0
  35. package/src/commands/migrate-handler.ts +67 -0
  36. package/src/commands/migration-aggregation.ts +100 -0
  37. package/src/commands/migration-args.ts +85 -0
  38. package/src/commands/migration-file-executor.ts +189 -0
  39. package/src/commands/migration-output.ts +115 -0
  40. package/src/commands/migration-report-loader.ts +26 -0
  41. package/src/commands/migration-report.ts +21 -0
  42. package/src/commands/migration-source.ts +318 -0
  43. package/src/commands/migration-target-files.ts +161 -0
  44. package/src/commands/migration-target-resolver.ts +34 -0
  45. package/src/commands/migration-types.ts +65 -0
  46. package/src/commands/restore-handler.ts +24 -0
  47. package/src/commands/status-handler.ts +17 -0
  48. package/src/commands/status-output.ts +60 -0
  49. package/src/commands/token-output.ts +30 -0
  50. package/src/commands/types.ts +137 -0
  51. package/src/commands/validate-handler.ts +42 -0
  52. package/src/commands/validate.ts +83 -0
  53. package/src/config/index.ts +25 -0
  54. package/src/config/workspace.ts +87 -0
  55. package/src/constants.ts +4 -0
  56. package/src/extraction/candidate-extractor.ts +354 -0
  57. package/src/index.bundle.ts +105 -0
  58. package/src/index.ts +57 -0
  59. package/src/install/class-collector.ts +1 -0
  60. package/src/install/context-registry.ts +1 -0
  61. package/src/install/index.ts +5 -0
  62. package/src/install/patch-runner.ts +1 -0
  63. package/src/install/process-tailwindcss.ts +1 -0
  64. package/src/install/status.ts +1 -0
  65. package/src/logger.ts +5 -0
  66. package/src/options/legacy.ts +93 -0
  67. package/src/options/normalize.ts +262 -0
  68. package/src/options/types.ts +217 -0
  69. package/src/patching/operations/export-context/index.ts +110 -0
  70. package/src/patching/operations/export-context/postcss-v2.ts +235 -0
  71. package/src/patching/operations/export-context/postcss-v3.ts +249 -0
  72. package/src/patching/operations/extend-length-units.ts +197 -0
  73. package/src/patching/patch-runner.ts +46 -0
  74. package/src/patching/status.ts +262 -0
  75. package/src/runtime/class-collector.ts +105 -0
  76. package/src/runtime/collector.ts +148 -0
  77. package/src/runtime/context-registry.ts +65 -0
  78. package/src/runtime/process-tailwindcss.ts +115 -0
  79. package/src/types.ts +159 -0
  80. package/src/utils.ts +52 -0
@@ -0,0 +1,71 @@
1
+ export const CACHE_SCHEMA_VERSION = 2
2
+ export const CACHE_FINGERPRINT_VERSION = 1
3
+
4
+ export type CacheSchemaVersion = typeof CACHE_SCHEMA_VERSION
5
+ export type CacheFingerprintVersion = typeof CACHE_FINGERPRINT_VERSION
6
+ export type CacheClearScope = 'current' | 'all'
7
+
8
+ export interface CacheContextMetadata {
9
+ fingerprintVersion: CacheFingerprintVersion
10
+ projectRootRealpath: string
11
+ processCwdRealpath: string
12
+ cacheCwdRealpath: string
13
+ tailwindConfigPath?: string
14
+ tailwindConfigMtimeMs?: number
15
+ tailwindPackageRootRealpath: string
16
+ tailwindPackageVersion: string
17
+ patcherVersion: string
18
+ majorVersion: 2 | 3 | 4
19
+ optionsHash: string
20
+ }
21
+
22
+ export interface CacheContextDescriptor {
23
+ fingerprint: string
24
+ metadata: CacheContextMetadata
25
+ }
26
+
27
+ export interface CacheIndexEntry {
28
+ context: CacheContextMetadata
29
+ values: string[]
30
+ updatedAt: string
31
+ }
32
+
33
+ export interface CacheIndexFileV2 {
34
+ schemaVersion: CacheSchemaVersion
35
+ updatedAt: string
36
+ contexts: Record<string, CacheIndexEntry>
37
+ }
38
+
39
+ export type CacheReadReason
40
+ = 'hit'
41
+ | 'cache-disabled'
42
+ | 'noop-driver'
43
+ | 'file-missing'
44
+ | 'context-not-found'
45
+ | 'context-mismatch'
46
+ | 'legacy-schema'
47
+ | 'invalid-schema'
48
+
49
+ export interface CacheReadMeta {
50
+ hit: boolean
51
+ reason: CacheReadReason
52
+ fingerprint?: string
53
+ schemaVersion?: number
54
+ details: string[]
55
+ }
56
+
57
+ export interface CacheReadResult {
58
+ data: Set<string>
59
+ meta: CacheReadMeta
60
+ }
61
+
62
+ export interface CacheClearOptions {
63
+ scope?: CacheClearScope
64
+ }
65
+
66
+ export interface CacheClearResult {
67
+ scope: CacheClearScope
68
+ filesRemoved: number
69
+ entriesRemoved: number
70
+ contextsRemoved: number
71
+ }
@@ -0,0 +1,20 @@
1
+ import process from 'node:process'
2
+ import { createTailwindcssPatchCli, ValidateCommandError } from './index.bundle'
3
+ import logger from './logger'
4
+
5
+ async function main() {
6
+ const cli = createTailwindcssPatchCli()
7
+ cli.help()
8
+ cli.parse(process.argv, { run: false })
9
+ await cli.runMatchedCommand()
10
+ }
11
+
12
+ main().catch((error) => {
13
+ if (error instanceof ValidateCommandError) {
14
+ process.exitCode = error.exitCode
15
+ return
16
+ }
17
+ const message = error instanceof Error ? error.message : String(error)
18
+ logger.error(message)
19
+ process.exitCode = 1
20
+ })
package/src/cli.ts ADDED
@@ -0,0 +1,20 @@
1
+ import process from 'node:process'
2
+ import { createTailwindcssPatchCli, ValidateCommandError } from './commands/cli'
3
+ import logger from './logger'
4
+
5
+ async function main() {
6
+ const cli = createTailwindcssPatchCli()
7
+ cli.help()
8
+ cli.parse(process.argv, { run: false })
9
+ await cli.runMatchedCommand()
10
+ }
11
+
12
+ main().catch((error) => {
13
+ if (error instanceof ValidateCommandError) {
14
+ process.exitCode = error.exitCode
15
+ return
16
+ }
17
+ const message = error instanceof Error ? error.message : String(error)
18
+ logger.error(message)
19
+ process.exitCode = 1
20
+ })
@@ -0,0 +1,145 @@
1
+ import type { TailwindCssPatchOptions } from '../types'
2
+ import type { TokenGroupKey, TokenOutputFormat } from './token-output'
3
+ import type { TailwindcssPatchCommandContext } from './types'
4
+
5
+ import process from 'node:process'
6
+ import fs from 'fs-extra'
7
+ import path from 'pathe'
8
+ import { TailwindcssPatcher } from '../api/tailwindcss-patcher'
9
+ import { loadWorkspaceConfigModule } from '../config/workspace'
10
+ import { groupTokensByFile } from '../extraction/candidate-extractor'
11
+ import logger from '../logger'
12
+ import {
13
+ DEFAULT_TOKEN_REPORT,
14
+ formatGroupedPreview,
15
+ formatTokenLine,
16
+ TOKEN_FORMATS,
17
+ } from './token-output'
18
+
19
+ const DEFAULT_CONFIG_NAME = 'tailwindcss-mangle'
20
+
21
+ export async function installCommandDefaultHandler(_ctx: TailwindcssPatchCommandContext<'install'>) {
22
+ const patcher = await _ctx.createPatcher()
23
+ await patcher.patch()
24
+ logger.success('Tailwind CSS runtime patched successfully.')
25
+ }
26
+
27
+ export async function extractCommandDefaultHandler(ctx: TailwindcssPatchCommandContext<'extract'>) {
28
+ const { args } = ctx
29
+ const overrides: TailwindCssPatchOptions = {}
30
+ let hasOverrides = false
31
+
32
+ if (args.output || args.format) {
33
+ overrides.extract = {
34
+ ...(args.output === undefined ? {} : { file: args.output }),
35
+ ...(args.format === undefined ? {} : { format: args.format }),
36
+ }
37
+ hasOverrides = true
38
+ }
39
+
40
+ if (args.css) {
41
+ overrides.tailwindcss = {
42
+ v4: {
43
+ cssEntries: [args.css],
44
+ },
45
+ }
46
+ hasOverrides = true
47
+ }
48
+
49
+ const patcher = await ctx.createPatcher(hasOverrides ? overrides : undefined)
50
+ const extractOptions = args.write === undefined ? {} : { write: args.write }
51
+ const result = await patcher.extract(extractOptions)
52
+
53
+ if (result.filename) {
54
+ logger.success(`Collected ${result.classList.length} classes → ${result.filename}`)
55
+ }
56
+ else {
57
+ logger.success(`Collected ${result.classList.length} classes.`)
58
+ }
59
+
60
+ return result
61
+ }
62
+
63
+ export async function tokensCommandDefaultHandler(ctx: TailwindcssPatchCommandContext<'tokens'>) {
64
+ const { args } = ctx
65
+ const patcher = await ctx.createPatcher()
66
+ const report = await patcher.collectContentTokens()
67
+
68
+ const shouldWrite = args.write ?? true
69
+ let format: TokenOutputFormat = args.format ?? 'json'
70
+ if (!TOKEN_FORMATS.includes(format)) {
71
+ format = 'json'
72
+ }
73
+ const targetFile = args.output ?? DEFAULT_TOKEN_REPORT
74
+ const groupKey: TokenGroupKey = args.groupKey === 'absolute' ? 'absolute' : 'relative'
75
+ const buildGrouped = () =>
76
+ groupTokensByFile(report, {
77
+ key: groupKey,
78
+ stripAbsolutePaths: groupKey !== 'absolute',
79
+ })
80
+ const grouped = format === 'grouped-json' ? buildGrouped() : null
81
+ const resolveGrouped = () => grouped ?? buildGrouped()
82
+
83
+ if (shouldWrite) {
84
+ const target = path.resolve(targetFile)
85
+ await fs.ensureDir(path.dirname(target))
86
+ if (format === 'json') {
87
+ await fs.writeJSON(target, report, { spaces: 2 })
88
+ }
89
+ else if (format === 'grouped-json') {
90
+ await fs.writeJSON(target, resolveGrouped(), { spaces: 2 })
91
+ }
92
+ else {
93
+ const lines = report.entries.map(formatTokenLine)
94
+ await fs.writeFile(target, `${lines.join('\n')}\n`, 'utf8')
95
+ }
96
+ logger.success(`Collected ${report.entries.length} tokens (${format}) → ${target.replace(process.cwd(), '.')}`)
97
+ }
98
+ else {
99
+ logger.success(`Collected ${report.entries.length} tokens from ${report.filesScanned} files.`)
100
+ if (format === 'lines') {
101
+ const preview = report.entries.slice(0, 5).map(formatTokenLine).join('\n')
102
+ if (preview) {
103
+ logger.log('')
104
+ logger.info(preview)
105
+ if (report.entries.length > 5) {
106
+ logger.info(`…and ${report.entries.length - 5} more.`)
107
+ }
108
+ }
109
+ }
110
+ else if (format === 'grouped-json') {
111
+ const map = resolveGrouped()
112
+ const { preview, moreFiles } = formatGroupedPreview(map)
113
+ if (preview) {
114
+ logger.log('')
115
+ logger.info(preview)
116
+ if (moreFiles > 0) {
117
+ logger.info(`…and ${moreFiles} more files.`)
118
+ }
119
+ }
120
+ }
121
+ else {
122
+ const previewEntries = report.entries.slice(0, 3)
123
+ if (previewEntries.length) {
124
+ logger.log('')
125
+ logger.info(JSON.stringify(previewEntries, null, 2))
126
+ }
127
+ }
128
+ }
129
+
130
+ if (report.skippedFiles.length) {
131
+ logger.warn('Skipped files:')
132
+ for (const skipped of report.skippedFiles) {
133
+ logger.warn(` • ${skipped.file} (${skipped.reason})`)
134
+ }
135
+ }
136
+
137
+ return report
138
+ }
139
+
140
+ export async function initCommandDefaultHandler(ctx: TailwindcssPatchCommandContext<'init'>) {
141
+ const configModule = await loadWorkspaceConfigModule()
142
+ await configModule.initConfig(ctx.cwd)
143
+ const configName = configModule.CONFIG_NAME || DEFAULT_CONFIG_NAME
144
+ logger.success(`✨ ${configName}.config.ts initialized!`)
145
+ }
@@ -0,0 +1,56 @@
1
+ import type { CAC } from 'cac'
2
+ import type {
3
+ TailwindcssPatchCliMountOptions,
4
+ TailwindcssPatchCliOptions,
5
+ } from './types'
6
+
7
+ import cac from 'cac'
8
+ import { buildDefaultCommandDefinitions } from './command-definitions'
9
+ import { registerTailwindcssPatchCommand } from './command-registrar'
10
+ import { tailwindcssPatchCommands } from './types'
11
+ import {
12
+ VALIDATE_EXIT_CODES,
13
+ VALIDATE_FAILURE_REASONS,
14
+ ValidateCommandError,
15
+ } from './validate'
16
+
17
+ export {
18
+ tailwindcssPatchCommands,
19
+ VALIDATE_EXIT_CODES,
20
+ VALIDATE_FAILURE_REASONS,
21
+ ValidateCommandError,
22
+ }
23
+ export type {
24
+ TailwindcssPatchCliMountOptions,
25
+ TailwindcssPatchCliOptions,
26
+ TailwindcssPatchCommand,
27
+ TailwindcssPatchCommandContext,
28
+ TailwindcssPatchCommandHandler,
29
+ TailwindcssPatchCommandHandlerMap,
30
+ TailwindcssPatchCommandOptionDefinition,
31
+ TailwindcssPatchCommandOptions,
32
+ } from './types'
33
+ export type {
34
+ ValidateFailureReason,
35
+ ValidateFailureSummary,
36
+ ValidateJsonFailurePayload,
37
+ ValidateJsonSuccessPayload,
38
+ } from './validate'
39
+
40
+ export function mountTailwindcssPatchCommands(cli: CAC, options: TailwindcssPatchCliMountOptions = {}) {
41
+ const prefix = options.commandPrefix ?? ''
42
+ const selectedCommands = options.commands ?? tailwindcssPatchCommands
43
+ const defaultDefinitions = buildDefaultCommandDefinitions()
44
+
45
+ for (const name of selectedCommands) {
46
+ registerTailwindcssPatchCommand(cli, name, options, prefix, defaultDefinitions)
47
+ }
48
+
49
+ return cli
50
+ }
51
+
52
+ export function createTailwindcssPatchCli(options: TailwindcssPatchCliOptions = {}) {
53
+ const cli = cac(options.name ?? 'tw-patch')
54
+ mountTailwindcssPatchCommands(cli, options.mountOptions)
55
+ return cli
56
+ }
@@ -0,0 +1,77 @@
1
+ import type { CAC, Command } from 'cac'
2
+ import type { TailwindcssConfigResult } from '../config/workspace'
3
+ import type { TailwindCssPatchOptions } from '../types'
4
+ import type {
5
+ TailwindcssPatchCommand,
6
+ TailwindcssPatchCommandArgMap,
7
+ TailwindcssPatchCommandContext,
8
+ } from './types'
9
+
10
+ import process from 'node:process'
11
+ import path from 'pathe'
12
+ import { TailwindcssPatcher } from '../api/tailwindcss-patcher'
13
+ import { loadPatchOptionsForWorkspace, loadWorkspaceConfigModule } from '../config/workspace'
14
+ import logger from '../logger'
15
+
16
+ export function resolveCommandCwd(rawCwd?: string) {
17
+ if (!rawCwd) {
18
+ return process.cwd()
19
+ }
20
+ return path.resolve(rawCwd)
21
+ }
22
+
23
+ export function createMemoizedPromiseRunner<TResult>(factory: () => Promise<TResult>) {
24
+ let promise: Promise<TResult> | undefined
25
+ return () => {
26
+ if (!promise) {
27
+ promise = factory()
28
+ }
29
+ return promise
30
+ }
31
+ }
32
+
33
+ export function createTailwindcssPatchCommandContext<TCommand extends TailwindcssPatchCommand>(
34
+ cli: CAC,
35
+ command: Command,
36
+ commandName: TCommand,
37
+ args: TailwindcssPatchCommandArgMap[TCommand],
38
+ cwd: string,
39
+ ): TailwindcssPatchCommandContext<TCommand> {
40
+ const loadCachedConfig = createMemoizedPromiseRunner<TailwindcssConfigResult>(() =>
41
+ loadWorkspaceConfigModule().then(mod => mod.getConfig(cwd)),
42
+ )
43
+ const loadCachedPatchOptions = createMemoizedPromiseRunner<TailwindCssPatchOptions>(() =>
44
+ loadPatchOptionsForWorkspace(cwd),
45
+ )
46
+ const createCachedPatcher = createMemoizedPromiseRunner<TailwindcssPatcher>(async () => {
47
+ const patchOptions = await loadCachedPatchOptions()
48
+ return new TailwindcssPatcher(patchOptions)
49
+ })
50
+
51
+ const loadPatchOptionsForContext = (overrides?: TailwindCssPatchOptions) => {
52
+ if (overrides) {
53
+ return loadPatchOptionsForWorkspace(cwd, overrides)
54
+ }
55
+ return loadCachedPatchOptions()
56
+ }
57
+
58
+ const createPatcherForContext = async (overrides?: TailwindCssPatchOptions) => {
59
+ if (overrides) {
60
+ const patchOptions = await loadPatchOptionsForWorkspace(cwd, overrides)
61
+ return new TailwindcssPatcher(patchOptions)
62
+ }
63
+ return createCachedPatcher()
64
+ }
65
+
66
+ return {
67
+ cli,
68
+ command,
69
+ commandName,
70
+ args,
71
+ cwd,
72
+ logger,
73
+ loadConfig: loadCachedConfig,
74
+ loadPatchOptions: loadPatchOptionsForContext,
75
+ createPatcher: createPatcherForContext,
76
+ }
77
+ }
@@ -0,0 +1,102 @@
1
+ import type { TailwindcssPatchCommand, TailwindcssPatchCommandOptionDefinition } from './types'
2
+
3
+ import process from 'node:process'
4
+ import { DEFAULT_TOKEN_REPORT } from './token-output'
5
+
6
+ export interface TailwindcssPatchCommandDefinition {
7
+ description: string
8
+ optionDefs: TailwindcssPatchCommandOptionDefinition[]
9
+ }
10
+
11
+ export type TailwindcssPatchCommandDefinitions = Record<TailwindcssPatchCommand, TailwindcssPatchCommandDefinition>
12
+
13
+ function createCwdOptionDefinition(description: string = 'Working directory'): TailwindcssPatchCommandOptionDefinition {
14
+ return {
15
+ flags: '--cwd <dir>',
16
+ description,
17
+ config: { default: process.cwd() },
18
+ }
19
+ }
20
+
21
+ export function buildDefaultCommandDefinitions(): TailwindcssPatchCommandDefinitions {
22
+ return {
23
+ install: {
24
+ description: 'Apply Tailwind CSS runtime patches',
25
+ optionDefs: [createCwdOptionDefinition()],
26
+ },
27
+ extract: {
28
+ description: 'Collect generated class names into a cache file',
29
+ optionDefs: [
30
+ createCwdOptionDefinition(),
31
+ { flags: '--output <file>', description: 'Override output file path' },
32
+ { flags: '--format <format>', description: 'Output format (json|lines)' },
33
+ { flags: '--css <file>', description: 'Tailwind CSS entry CSS when using v4' },
34
+ { flags: '--no-write', description: 'Skip writing to disk' },
35
+ ],
36
+ },
37
+ tokens: {
38
+ description: 'Extract Tailwind tokens with file/position metadata',
39
+ optionDefs: [
40
+ createCwdOptionDefinition(),
41
+ { flags: '--output <file>', description: 'Override output file path', config: { default: DEFAULT_TOKEN_REPORT } },
42
+ {
43
+ flags: '--format <format>',
44
+ description: 'Output format (json|lines|grouped-json)',
45
+ config: { default: 'json' },
46
+ },
47
+ {
48
+ flags: '--group-key <key>',
49
+ description: 'Grouping key for grouped-json output (relative|absolute)',
50
+ config: { default: 'relative' },
51
+ },
52
+ { flags: '--no-write', description: 'Skip writing to disk' },
53
+ ],
54
+ },
55
+ init: {
56
+ description: 'Generate a tailwindcss-patch config file',
57
+ optionDefs: [createCwdOptionDefinition()],
58
+ },
59
+ migrate: {
60
+ description: 'Migrate deprecated config fields to modern options',
61
+ optionDefs: [
62
+ createCwdOptionDefinition(),
63
+ { flags: '--config <file>', description: 'Migrate a specific config file path' },
64
+ { flags: '--workspace', description: 'Scan workspace recursively for config files' },
65
+ { flags: '--max-depth <n>', description: 'Maximum recursion depth for --workspace', config: { default: 6 } },
66
+ { flags: '--include <glob>', description: 'Only migrate files that match this glob (repeatable)' },
67
+ { flags: '--exclude <glob>', description: 'Skip files that match this glob (repeatable)' },
68
+ { flags: '--report-file <file>', description: 'Write migration report JSON to a file' },
69
+ { flags: '--backup-dir <dir>', description: 'Write pre-migration backups into this directory' },
70
+ { flags: '--check', description: 'Exit with an error when migration changes are required' },
71
+ { flags: '--json', description: 'Print the migration report as JSON' },
72
+ { flags: '--dry-run', description: 'Preview changes without writing files' },
73
+ ],
74
+ },
75
+ restore: {
76
+ description: 'Restore config files from a previous migration report backup snapshot',
77
+ optionDefs: [
78
+ createCwdOptionDefinition(),
79
+ { flags: '--report-file <file>', description: 'Migration report file generated by migrate' },
80
+ { flags: '--dry-run', description: 'Preview restore targets without writing files' },
81
+ { flags: '--strict', description: 'Fail when any backup file is missing' },
82
+ { flags: '--json', description: 'Print the restore result as JSON' },
83
+ ],
84
+ },
85
+ validate: {
86
+ description: 'Validate migration report compatibility without modifying files',
87
+ optionDefs: [
88
+ createCwdOptionDefinition(),
89
+ { flags: '--report-file <file>', description: 'Migration report file to validate' },
90
+ { flags: '--strict', description: 'Fail when any backup file is missing' },
91
+ { flags: '--json', description: 'Print validation result as JSON' },
92
+ ],
93
+ },
94
+ status: {
95
+ description: 'Check which Tailwind patches are applied',
96
+ optionDefs: [
97
+ createCwdOptionDefinition(),
98
+ { flags: '--json', description: 'Print a JSON report of patch status' },
99
+ ],
100
+ },
101
+ }
102
+ }
@@ -0,0 +1,68 @@
1
+ import type { Command } from 'cac'
2
+ import type { TailwindcssPatchCommandDefinitions } from './command-definitions'
3
+ import type {
4
+ TailwindcssPatchCliMountOptions,
5
+ TailwindcssPatchCommand,
6
+ TailwindcssPatchCommandOptionDefinition,
7
+ TailwindcssPatchCommandOptions,
8
+ } from './types'
9
+
10
+ function addPrefixIfMissing(value: string, prefix: string) {
11
+ if (!prefix || value.startsWith(prefix)) {
12
+ return value
13
+ }
14
+ return `${prefix}${value}`
15
+ }
16
+
17
+ function resolveCommandNames(
18
+ command: TailwindcssPatchCommand,
19
+ mountOptions: TailwindcssPatchCliMountOptions,
20
+ prefix: string,
21
+ ) {
22
+ const override = mountOptions.commandOptions?.[command]
23
+ const baseName = override?.name ?? command
24
+ const name = addPrefixIfMissing(baseName, prefix)
25
+ const aliases = (override?.aliases ?? []).map(alias => addPrefixIfMissing(alias, prefix))
26
+ return { name, aliases }
27
+ }
28
+
29
+ function resolveOptionDefinitions(
30
+ defaults: TailwindcssPatchCommandOptionDefinition[],
31
+ override?: TailwindcssPatchCommandOptions,
32
+ ) {
33
+ if (!override) {
34
+ return defaults
35
+ }
36
+
37
+ const appendDefaults = override.appendDefaultOptions ?? true
38
+ const customDefs = override.optionDefs ?? []
39
+ if (!appendDefaults) {
40
+ return customDefs
41
+ }
42
+
43
+ if (customDefs.length === 0) {
44
+ return defaults
45
+ }
46
+
47
+ return [...defaults, ...customDefs]
48
+ }
49
+
50
+ export function resolveCommandMetadata(
51
+ command: TailwindcssPatchCommand,
52
+ mountOptions: TailwindcssPatchCliMountOptions,
53
+ prefix: string,
54
+ defaults: TailwindcssPatchCommandDefinitions,
55
+ ) {
56
+ const names = resolveCommandNames(command, mountOptions, prefix)
57
+ const definition = defaults[command]
58
+ const override = mountOptions.commandOptions?.[command]
59
+ const description = override?.description ?? definition.description
60
+ const optionDefs = resolveOptionDefinitions(definition.optionDefs, override)
61
+ return { ...names, description, optionDefs }
62
+ }
63
+
64
+ export function applyCommandOptions(command: Command, optionDefs: TailwindcssPatchCommandOptionDefinition[]) {
65
+ for (const option of optionDefs) {
66
+ command.option(option.flags, option.description ?? '', option.config)
67
+ }
68
+ }
@@ -0,0 +1,39 @@
1
+ import type { CAC } from 'cac'
2
+ import type { TailwindcssPatchCommandDefinitions } from './command-definitions'
3
+ import type {
4
+ TailwindcssPatchCliMountOptions,
5
+ TailwindcssPatchCommand,
6
+ TailwindcssPatchCommandArgMap,
7
+ TailwindcssPatchCommandDefaultHandlerMap,
8
+ } from './types'
9
+
10
+ import {
11
+ applyCommandOptions,
12
+ resolveCommandMetadata,
13
+ } from './command-metadata'
14
+ import { runWithCommandHandler } from './command-runtime'
15
+ import { defaultCommandHandlers } from './default-handler-map'
16
+
17
+ export function registerTailwindcssPatchCommand<TCommand extends TailwindcssPatchCommand>(
18
+ cli: CAC,
19
+ commandName: TCommand,
20
+ options: TailwindcssPatchCliMountOptions,
21
+ prefix: string,
22
+ defaultDefinitions: TailwindcssPatchCommandDefinitions,
23
+ ) {
24
+ const metadata = resolveCommandMetadata(commandName, options, prefix, defaultDefinitions)
25
+ const command = cli.command(metadata.name, metadata.description)
26
+ applyCommandOptions(command, metadata.optionDefs)
27
+ command.action(async (args: TailwindcssPatchCommandArgMap[TCommand]) => {
28
+ const defaultHandler = defaultCommandHandlers[commandName] as TailwindcssPatchCommandDefaultHandlerMap[TCommand]
29
+ return runWithCommandHandler(
30
+ cli,
31
+ command,
32
+ commandName,
33
+ args,
34
+ options.commandHandlers?.[commandName],
35
+ defaultHandler,
36
+ )
37
+ })
38
+ metadata.aliases.forEach(alias => command.alias(alias))
39
+ }
@@ -0,0 +1,33 @@
1
+ import type { CAC, Command } from 'cac'
2
+ import type {
3
+ TailwindcssPatchCommand,
4
+ TailwindcssPatchCommandArgMap,
5
+ TailwindcssPatchCommandContext,
6
+ TailwindcssPatchCommandHandler,
7
+ TailwindcssPatchCommandResultMap,
8
+ } from './types'
9
+
10
+ import {
11
+ createMemoizedPromiseRunner,
12
+ createTailwindcssPatchCommandContext,
13
+ resolveCommandCwd,
14
+ } from './command-context'
15
+
16
+ export function runWithCommandHandler<TCommand extends TailwindcssPatchCommand>(
17
+ cli: CAC,
18
+ command: Command,
19
+ commandName: TCommand,
20
+ args: TailwindcssPatchCommandArgMap[TCommand],
21
+ handler: TailwindcssPatchCommandHandler<TCommand> | undefined,
22
+ defaultHandler: (
23
+ context: TailwindcssPatchCommandContext<TCommand>,
24
+ ) => Promise<TailwindcssPatchCommandResultMap[TCommand]>,
25
+ ) {
26
+ const cwd = resolveCommandCwd(args.cwd)
27
+ const context = createTailwindcssPatchCommandContext(cli, command, commandName, args, cwd)
28
+ const runDefault = createMemoizedPromiseRunner(() => defaultHandler(context))
29
+ if (!handler) {
30
+ return runDefault()
31
+ }
32
+ return handler(context, runDefault)
33
+ }
@@ -0,0 +1,25 @@
1
+ import type { TailwindcssPatchCommandDefaultHandlerMap } from './types'
2
+
3
+ import {
4
+ extractCommandDefaultHandler,
5
+ initCommandDefaultHandler,
6
+ installCommandDefaultHandler,
7
+ tokensCommandDefaultHandler,
8
+ } from './basic-handlers'
9
+ import {
10
+ migrateCommandDefaultHandler,
11
+ } from './migrate-handler'
12
+ import { restoreCommandDefaultHandler } from './restore-handler'
13
+ import { statusCommandDefaultHandler } from './status-handler'
14
+ import { validateCommandDefaultHandler } from './validate-handler'
15
+
16
+ export const defaultCommandHandlers = {
17
+ install: installCommandDefaultHandler,
18
+ extract: extractCommandDefaultHandler,
19
+ tokens: tokensCommandDefaultHandler,
20
+ init: initCommandDefaultHandler,
21
+ migrate: migrateCommandDefaultHandler,
22
+ restore: restoreCommandDefaultHandler,
23
+ validate: validateCommandDefaultHandler,
24
+ status: statusCommandDefaultHandler,
25
+ } satisfies TailwindcssPatchCommandDefaultHandlerMap