Skip to content

Commit 275c02d

Browse files
committed
feat(tailwindcss-patch): add migration report schema metadata
1 parent 277acbd commit 275c02d

6 files changed

Lines changed: 98 additions & 1 deletion

File tree

.changeset/mean-rats-fold.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 migration report schema metadata and restore compatibility validation.
6+
7+
- include `reportKind`, `schemaVersion`, `generatedAt`, and `tool` metadata in `tw-patch migrate` reports
8+
- validate `reportKind` and `schemaVersion` in `tw-patch restore` for safer report compatibility checks
9+
- keep backward compatibility with legacy reports that do not include envelope metadata

packages/tailwindcss-patch/MIGRATION.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ Migration mapping:
8989
- Optional `--backup-dir` can persist per-file pre-migration snapshots for manual recovery or auditing.
9090
- `--include` / `--exclude` patterns can narrow workspace migration scope, and `--report-file` can persist machine-readable migration reports.
9191
- `tw-patch restore` can restore config files from a migration report (`backupFile` entries) with optional `--dry-run`, `--strict`, and `--json`.
92+
- Migration reports now include envelope metadata: `reportKind`, `schemaVersion`, `generatedAt`, and `tool` (`name` / `version`).
93+
- `tw-patch restore` validates report metadata when present, rejects unknown kinds/newer schema versions, and keeps legacy metadata-free reports backward compatible.
9294
- Commands resolve configuration from `tailwindcss-patch.config.ts` via `@tailwindcss-mangle/config`. Existing configuration files continue to work without changes.
9395

9496
## 4. Cache handling

packages/tailwindcss-patch/README-cn.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ CLI 会通过 `@tailwindcss-mangle/config` 加载 `tailwindcss-patch.config.ts`
7777

7878
迁移写入默认采用“事务式”策略:如果后续文件写入失败,会自动回滚此前已经写入的迁移文件,避免留下半迁移状态。若需要显式保留原始文件快照,可配合 `--backup-dir`;若需要审计产物,可配合 `--report-file`
7979

80+
迁移报告现在会携带元数据:`reportKind``schemaVersion``generatedAt``tool``name` / `version`),便于恢复阶段做兼容性校验。
81+
8082
### `restore` 常用参数
8183

8284
| 参数 | 说明 |
@@ -87,6 +89,8 @@ CLI 会通过 `@tailwindcss-mangle/config` 加载 `tailwindcss-patch.config.ts`
8789
| `--strict` | 报告中的备份文件缺失时直接报错退出。 |
8890
| `--json` | 输出 JSON 格式的恢复结果。 |
8991

92+
`tw-patch restore` 在报告包含元数据时会执行 schema 校验。若 `reportKind` 不匹配或 `schemaVersion` 高于当前支持版本,会拒绝恢复;不包含该元数据的历史报告仍保持兼容。
93+
9094
### `tokens` 常用参数
9195

9296
| 参数 | 说明 |

packages/tailwindcss-patch/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ The CLI loads `tailwindcss-patch.config.ts` via `@tailwindcss-mangle/config`. Le
134134

135135
When writing files, migration uses a transactional strategy by default: if a later file write fails, already written migration files are rolled back to avoid partial updates. Use `--backup-dir` if you want explicit backup snapshots for audit/manual recovery. Use `--report-file` to keep a machine-readable migration artifact.
136136

137+
Migration reports now include envelope metadata: `reportKind`, `schemaVersion`, `generatedAt`, and `tool` (`name` / `version`). This metadata helps restore tooling validate report compatibility.
138+
137139
### Restore options
138140

139141
| Flag | Description |
@@ -144,6 +146,8 @@ When writing files, migration uses a transactional strategy by default: if a lat
144146
| `--strict` | Fail when any backup file in the report is missing. |
145147
| `--json` | Print restore summary as JSON. |
146148

149+
`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.
150+
147151
### Token report options
148152

149153
| Flag | Description |

packages/tailwindcss-patch/src/cli/migrate-config.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import generate from '@babel/generator'
55
import * as t from '@babel/types'
66
import fs from 'fs-extra'
77
import path from 'pathe'
8+
import { pkgName, pkgVersion } from '../constants'
89

910
export const DEFAULT_CONFIG_FILENAMES = [
1011
'tailwindcss-patch.config.ts',
@@ -30,6 +31,8 @@ const DEFAULT_WORKSPACE_IGNORED_DIRS = new Set([
3031
'tmp',
3132
])
3233
const DEFAULT_WORKSPACE_MAX_DEPTH = 6
34+
const MIGRATION_REPORT_KIND = 'tw-patch-migrate-report'
35+
const MIGRATION_REPORT_SCHEMA_VERSION = 1
3336

3437
const ROOT_LEGACY_KEYS = ['cwd', 'overwrite', 'tailwind', 'features', 'output', 'applyPatches'] as const
3538

@@ -51,6 +54,13 @@ export interface ConfigFileMigrationEntry {
5154
}
5255

5356
export interface ConfigFileMigrationReport {
57+
reportKind: typeof MIGRATION_REPORT_KIND
58+
schemaVersion: typeof MIGRATION_REPORT_SCHEMA_VERSION
59+
generatedAt: string
60+
tool: {
61+
name: string
62+
version: string
63+
}
5464
cwd: string
5565
dryRun: boolean
5666
rollbackOnError: boolean
@@ -86,6 +96,7 @@ export interface RestoreConfigFilesOptions {
8696
export interface RestoreConfigFilesResult {
8797
cwd: string
8898
reportFile: string
99+
reportSchemaVersion?: number
89100
dryRun: boolean
90101
strict: boolean
91102
scannedEntries: number
@@ -620,6 +631,13 @@ export async function migrateConfigFiles(options: MigrateConfigFilesOptions): Pr
620631
}
621632

622633
return {
634+
reportKind: MIGRATION_REPORT_KIND,
635+
schemaVersion: MIGRATION_REPORT_SCHEMA_VERSION,
636+
generatedAt: new Date().toISOString(),
637+
tool: {
638+
name: pkgName,
639+
version: pkgVersion,
640+
},
623641
cwd,
624642
dryRun,
625643
rollbackOnError,
@@ -640,7 +658,22 @@ export async function restoreConfigFiles(options: RestoreConfigFilesOptions): Pr
640658
const strict = options.strict ?? false
641659
const reportFile = path.resolve(cwd, options.reportFile)
642660

643-
const report = await fs.readJSON(reportFile) as { entries?: Array<{ file?: string, backupFile?: string }> }
661+
const report = await fs.readJSON(reportFile) as {
662+
reportKind?: string
663+
schemaVersion?: number
664+
entries?: Array<{ file?: string, backupFile?: string }>
665+
}
666+
if (report.reportKind !== undefined && report.reportKind !== MIGRATION_REPORT_KIND) {
667+
throw new Error(`Unsupported report kind "${report.reportKind}" in ${reportFile}.`)
668+
}
669+
if (
670+
report.schemaVersion !== undefined
671+
&& (!Number.isInteger(report.schemaVersion) || report.schemaVersion > MIGRATION_REPORT_SCHEMA_VERSION)
672+
) {
673+
throw new Error(
674+
`Unsupported report schema version "${String(report.schemaVersion)}" in ${reportFile}. Current supported version is ${MIGRATION_REPORT_SCHEMA_VERSION}.`,
675+
)
676+
}
644677
const entries = Array.isArray(report.entries) ? report.entries : []
645678

646679
let scannedEntries = 0
@@ -685,6 +718,7 @@ export async function restoreConfigFiles(options: RestoreConfigFilesOptions): Pr
685718
return {
686719
cwd,
687720
reportFile,
721+
...(report.schemaVersion === undefined ? {} : { reportSchemaVersion: report.schemaVersion }),
688722
dryRun,
689723
strict,
690724
scannedEntries,

packages/tailwindcss-patch/test/cli.migrate-config.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,10 @@ describe('migrateConfigFiles', () => {
102102
dryRun: true,
103103
})
104104

105+
expect(report.reportKind).toBe('tw-patch-migrate-report')
106+
expect(report.schemaVersion).toBe(1)
107+
expect(report.generatedAt).toBeTypeOf('string')
108+
expect(report.tool.name).toBe('tailwindcss-patch')
105109
expect(report.scannedFiles).toBe(1)
106110
expect(report.changedFiles).toBe(1)
107111
expect(report.writtenFiles).toBe(0)
@@ -300,6 +304,7 @@ describe('restoreConfigFiles', () => {
300304
reportFile: '.tw-patch/migrate-report.json',
301305
})
302306

307+
expect(result.reportSchemaVersion).toBeUndefined()
303308
expect(result.restoredFiles).toBe(1)
304309
expect(result.missingBackups).toBe(0)
305310
const restored = await fs.readFile(target, 'utf8')
@@ -360,4 +365,43 @@ describe('restoreConfigFiles', () => {
360365
await fs.remove(cwd)
361366
}
362367
})
368+
369+
it('throws on unsupported report schema versions', async () => {
370+
const cwd = await fs.mkdtemp(path.join(os.tmpdir(), 'tw-patch-restore-schema-'))
371+
const reportPath = path.resolve(cwd, '.tw-patch/migrate-report.json')
372+
try {
373+
await fs.outputJSON(reportPath, {
374+
reportKind: 'tw-patch-migrate-report',
375+
schemaVersion: 999,
376+
entries: [],
377+
}, { spaces: 2 })
378+
379+
await expect(restoreConfigFiles({
380+
cwd,
381+
reportFile: '.tw-patch/migrate-report.json',
382+
})).rejects.toThrow('Unsupported report schema version')
383+
}
384+
finally {
385+
await fs.remove(cwd)
386+
}
387+
})
388+
389+
it('throws on unsupported report kind', async () => {
390+
const cwd = await fs.mkdtemp(path.join(os.tmpdir(), 'tw-patch-restore-kind-'))
391+
const reportPath = path.resolve(cwd, '.tw-patch/migrate-report.json')
392+
try {
393+
await fs.outputJSON(reportPath, {
394+
reportKind: 'unknown-report',
395+
entries: [],
396+
}, { spaces: 2 })
397+
398+
await expect(restoreConfigFiles({
399+
cwd,
400+
reportFile: '.tw-patch/migrate-report.json',
401+
})).rejects.toThrow('Unsupported report kind')
402+
}
403+
finally {
404+
await fs.remove(cwd)
405+
}
406+
})
363407
})

0 commit comments

Comments
 (0)