Skip to content

Commit f69f17c

Browse files
committed
feat(tailwindcss-patch): add migration report validate command
1 parent 0f472ae commit f69f17c

6 files changed

Lines changed: 163 additions & 2 deletions

File tree

.changeset/sour-planets-juggle.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"tailwindcss-patch": patch
3+
---
4+
5+
Add a migration report validation CLI command.
6+
7+
- introduce `tw-patch validate` to verify migration report compatibility without restoring files
8+
- reuse restore dry-run checks for schema validation and backup reference scanning
9+
- support `--report-file`, `--strict`, and `--json` for validation workflows

packages/tailwindcss-patch/MIGRATION.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ Migration mapping:
9292
- Migration reports now include envelope metadata: `reportKind`, `schemaVersion`, `generatedAt`, and `tool` (`name` / `version`).
9393
- `tw-patch restore` validates report metadata when present, rejects unknown kinds/newer schema versions, and keeps legacy metadata-free reports backward compatible.
9494
- `tw-patch restore --json` now exposes report metadata fields (`reportKind`, `reportSchemaVersion`) when available for easier diagnostics.
95+
- `tw-patch validate` can validate migration report compatibility in dry-run mode without restoring files.
9596
- Commands resolve configuration from `tailwindcss-patch.config.ts` via `@tailwindcss-mangle/config`. Existing configuration files continue to work without changes.
9697

9798
## 4. Cache handling

packages/tailwindcss-patch/README-cn.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ pnpm dlx tw-patch migrate --dry-run
4343

4444
# 基于迁移报告中的备份快照恢复配置(预览模式)
4545
pnpm dlx tw-patch restore --report-file .tw-patch/migrate-report.json --dry-run
46+
47+
# 仅校验迁移报告兼容性(不写回文件)
48+
pnpm dlx tw-patch validate --report-file .tw-patch/migrate-report.json --json
4649
```
4750

4851
### `extract` 常用参数
@@ -92,6 +95,17 @@ CLI 会通过 `@tailwindcss-mangle/config` 加载 `tailwindcss-patch.config.ts`
9295
`tw-patch restore` 在报告包含元数据时会执行 schema 校验。若 `reportKind` 不匹配或 `schemaVersion` 高于当前支持版本,会拒绝恢复;不包含该元数据的历史报告仍保持兼容。
9396
使用 `--json` 时,恢复结果会在报告含元数据时附带 `reportKind` / `reportSchemaVersion` 字段。
9497

98+
### `validate` 常用参数
99+
100+
| 参数 | 说明 |
101+
| ---------------------- | ------------------------------------------------------------ |
102+
| `--cwd <dir>` | 指定校验时使用的工作目录。 |
103+
| `--report-file <file>` | 指定迁移报告路径(默认 `.tw-patch/migrate-report.json`)。 |
104+
| `--strict` | 报告中的备份文件缺失时直接报错退出。 |
105+
| `--json` | 输出 JSON 格式的校验结果。 |
106+
107+
`tw-patch validate` 会以 dry-run 模式执行迁移报告校验,不写回任何恢复文件,同时校验报告 schema 与备份引用状态。
108+
95109
### `tokens` 常用参数
96110

97111
| 参数 | 说明 |

packages/tailwindcss-patch/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ pnpm dlx tw-patch migrate --dry-run
4646

4747
# Restore configs from a migration report backup snapshot
4848
pnpm dlx tw-patch restore --report-file .tw-patch/migrate-report.json --dry-run
49+
50+
# Validate migration report compatibility without modifying files
51+
pnpm dlx tw-patch validate --report-file .tw-patch/migrate-report.json --json
4952
```
5053

5154
### Embed into another CLI
@@ -149,6 +152,17 @@ Migration reports now include envelope metadata: `reportKind`, `schemaVersion`,
149152
`tw-patch restore` validates report schema metadata when available. Reports with unsupported `reportKind` or newer `schemaVersion` are rejected to avoid unsafe restores. Legacy reports without metadata are still supported.
150153
With `--json`, restore output includes `reportKind` / `reportSchemaVersion` when report metadata is present.
151154

155+
### Validate options
156+
157+
| Flag | Description |
158+
| --------------------- | ----------------------------------------------------------------- |
159+
| `--cwd <dir>` | Working directory used to resolve report paths. |
160+
| `--report-file <file>`| Migration report file path (defaults to `.tw-patch/migrate-report.json`). |
161+
| `--strict` | Fail when any backup file in the report is missing. |
162+
| `--json` | Print validation result as JSON. |
163+
164+
`tw-patch validate` performs migration report compatibility checks without writing restored files. It runs report schema validation and scans backup references in dry-run mode.
165+
152166
### Token report options
153167

154168
| Flag | Description |

packages/tailwindcss-patch/src/cli/commands.ts

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ import { groupTokensByFile } from '../extraction/candidate-extractor'
2727
import logger from '../logger'
2828
import { fromLegacyOptions, fromUnifiedConfig } from '../options/legacy'
2929

30-
export type TailwindcssPatchCommand = 'install' | 'extract' | 'tokens' | 'init' | 'migrate' | 'restore' | 'status'
30+
export type TailwindcssPatchCommand = 'install' | 'extract' | 'tokens' | 'init' | 'migrate' | 'restore' | 'validate' | 'status'
3131

32-
export const tailwindcssPatchCommands: TailwindcssPatchCommand[] = ['install', 'extract', 'tokens', 'init', 'migrate', 'restore', 'status']
32+
export const tailwindcssPatchCommands: TailwindcssPatchCommand[] = ['install', 'extract', 'tokens', 'init', 'migrate', 'restore', 'validate', 'status']
3333

3434
type TokenOutputFormat = 'json' | 'lines' | 'grouped-json'
3535
type TokenGroupKey = 'relative' | 'absolute'
@@ -85,6 +85,11 @@ interface RestoreCommandArgs extends BaseCommandArgs {
8585
strict?: boolean
8686
json?: boolean
8787
}
88+
interface ValidateCommandArgs extends BaseCommandArgs {
89+
reportFile?: string
90+
strict?: boolean
91+
json?: boolean
92+
}
8893
interface StatusCommandArgs extends BaseCommandArgs {
8994
json?: boolean
9095
}
@@ -96,6 +101,7 @@ interface TailwindcssPatchCommandArgMap {
96101
init: InitCommandArgs
97102
migrate: MigrateCommandArgs
98103
restore: RestoreCommandArgs
104+
validate: ValidateCommandArgs
99105
status: StatusCommandArgs
100106
}
101107

@@ -106,6 +112,7 @@ interface TailwindcssPatchCommandResultMap {
106112
init: void
107113
migrate: ConfigFileMigrationReport
108114
restore: RestoreConfigFilesResult
115+
validate: RestoreConfigFilesResult
109116
status: PatchStatusReport
110117
}
111118

@@ -324,6 +331,15 @@ function buildDefaultCommandDefinitions(): Record<TailwindcssPatchCommand, Tailw
324331
{ flags: '--json', description: 'Print the restore result as JSON' },
325332
],
326333
},
334+
validate: {
335+
description: 'Validate migration report compatibility without modifying files',
336+
optionDefs: [
337+
createCwdOptionDefinition(),
338+
{ flags: '--report-file <file>', description: 'Migration report file to validate' },
339+
{ flags: '--strict', description: 'Fail when any backup file is missing' },
340+
{ flags: '--json', description: 'Print validation result as JSON' },
341+
],
342+
},
327343
status: {
328344
description: 'Check which Tailwind patches are applied',
329345
optionDefs: [
@@ -655,6 +671,33 @@ async function restoreCommandDefaultHandler(ctx: TailwindcssPatchCommandContext<
655671
return result
656672
}
657673

674+
async function validateCommandDefaultHandler(ctx: TailwindcssPatchCommandContext<'validate'>) {
675+
const { args } = ctx
676+
const reportFile = args.reportFile ?? '.tw-patch/migrate-report.json'
677+
const result = await restoreConfigFiles({
678+
cwd: ctx.cwd,
679+
reportFile,
680+
dryRun: true,
681+
strict: args.strict ?? false,
682+
})
683+
684+
if (args.json) {
685+
logger.log(JSON.stringify(result, null, 2))
686+
return result
687+
}
688+
689+
logger.success(
690+
`Migration report validated: scanned=${result.scannedEntries}, restorable=${result.restorableEntries}, missingBackups=${result.missingBackups}, skipped=${result.skippedEntries}`,
691+
)
692+
693+
if (result.reportKind || result.reportSchemaVersion !== undefined) {
694+
const kind = result.reportKind ?? 'unknown'
695+
const schema = result.reportSchemaVersion === undefined ? 'unknown' : String(result.reportSchemaVersion)
696+
logger.info(` metadata: kind=${kind}, schema=${schema}`)
697+
}
698+
return result
699+
}
700+
658701
function formatFilesHint(entry: PatchStatusReport['entries'][number]) {
659702
if (!entry.files.length) {
660703
return ''
@@ -807,6 +850,22 @@ export function mountTailwindcssPatchCommands(cli: CAC, options: TailwindcssPatc
807850
})
808851
metadata.aliases.forEach(alias => command.alias(alias))
809852
},
853+
validate: () => {
854+
const metadata = resolveCommandMetadata('validate', options, prefix, defaultDefinitions)
855+
const command = cli.command(metadata.name, metadata.description)
856+
applyCommandOptions(command, metadata.optionDefs)
857+
command.action(async (args: ValidateCommandArgs) => {
858+
return runWithCommandHandler(
859+
cli,
860+
command,
861+
'validate',
862+
args,
863+
options.commandHandlers?.validate,
864+
validateCommandDefaultHandler,
865+
)
866+
})
867+
metadata.aliases.forEach(alias => command.alias(alias))
868+
},
810869
status: () => {
811870
const metadata = resolveCommandMetadata('status', options, prefix, defaultDefinitions)
812871
const command = cli.command(metadata.name, metadata.description)

packages/tailwindcss-patch/test/cli.mount.test.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,4 +478,68 @@ describe('mountTailwindcssPatchCommands', () => {
478478
restored: ['/tmp/project/tailwindcss-mangle.config.ts'],
479479
}, null, 2))
480480
})
481+
482+
it('runs validate command in strict mode without writing files', async () => {
483+
const cli = cac('embedded')
484+
mountTailwindcssPatchCommands(cli)
485+
486+
cli.parse(
487+
['node', 'embedded', 'validate', '--cwd', '/tmp/project', '--report-file', '.tw-patch/migrate-report.json', '--strict'],
488+
{ run: false },
489+
)
490+
await cli.runMatchedCommand()
491+
492+
expect(restoreConfigFilesMock).toHaveBeenCalledTimes(1)
493+
expect(restoreConfigFilesMock).toHaveBeenCalledWith({
494+
cwd: '/tmp/project',
495+
reportFile: '.tw-patch/migrate-report.json',
496+
dryRun: true,
497+
strict: true,
498+
})
499+
})
500+
501+
it('prints validate result as json', async () => {
502+
restoreConfigFilesMock.mockResolvedValueOnce({
503+
cwd: '/tmp/project',
504+
reportFile: '/tmp/project/.tw-patch/migrate-report.json',
505+
reportKind: 'tw-patch-migrate-report',
506+
reportSchemaVersion: 1,
507+
dryRun: true,
508+
strict: false,
509+
scannedEntries: 1,
510+
restorableEntries: 1,
511+
restoredFiles: 1,
512+
missingBackups: 0,
513+
skippedEntries: 0,
514+
restored: ['/tmp/project/tailwindcss-mangle.config.ts'],
515+
})
516+
517+
const cli = cac('embedded')
518+
mountTailwindcssPatchCommands(cli)
519+
520+
cli.parse(['node', 'embedded', 'validate', '--cwd', '/tmp/project', '--json'], { run: false })
521+
await cli.runMatchedCommand()
522+
523+
expect(restoreConfigFilesMock).toHaveBeenCalledTimes(1)
524+
expect(restoreConfigFilesMock).toHaveBeenCalledWith({
525+
cwd: '/tmp/project',
526+
reportFile: '.tw-patch/migrate-report.json',
527+
dryRun: true,
528+
strict: false,
529+
})
530+
expect(logger.log).toHaveBeenCalledWith(JSON.stringify({
531+
cwd: '/tmp/project',
532+
reportFile: '/tmp/project/.tw-patch/migrate-report.json',
533+
reportKind: 'tw-patch-migrate-report',
534+
reportSchemaVersion: 1,
535+
dryRun: true,
536+
strict: false,
537+
scannedEntries: 1,
538+
restorableEntries: 1,
539+
restoredFiles: 1,
540+
missingBackups: 0,
541+
skippedEntries: 0,
542+
restored: ['/tmp/project/tailwindcss-mangle.config.ts'],
543+
}, null, 2))
544+
})
481545
})

0 commit comments

Comments
 (0)