Skip to content

Commit a9efa50

Browse files
committed
refactor(tailwindcss-patch): extract migration executors and report loader
1 parent ce485d7 commit a9efa50

9 files changed

Lines changed: 548 additions & 164 deletions

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

Lines changed: 44 additions & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,37 @@
1-
import fs from 'fs-extra'
21
import path from 'pathe'
32
import { pkgName, pkgVersion } from '../constants'
3+
import { executeMigrationFile, restoreConfigEntries } from './migration-file-executor'
4+
import type { MigrationWrittenEntry } from './migration-file-executor'
5+
import { loadMigrationReportForRestore } from './migration-report-loader'
46
import {
57
collectWorkspaceConfigFiles,
68
DEFAULT_WORKSPACE_MAX_DEPTH,
79
filterTargetFiles,
8-
resolveBackupRelativePath,
910
resolveTargetFiles,
1011
} from './migration-target-files'
1112
import {
12-
assertMigrationReportCompatibility,
1313
MIGRATION_REPORT_KIND,
1414
MIGRATION_REPORT_SCHEMA_VERSION,
1515
} from './migration-report'
1616
import { migrateConfigSource } from './migration-source'
17+
import type {
18+
ConfigFileMigrationEntry,
19+
ConfigFileMigrationReport,
20+
MigrateConfigFilesOptions,
21+
RestoreConfigFilesOptions,
22+
RestoreConfigFilesResult,
23+
} from './migration-types'
1724
export { DEFAULT_CONFIG_FILENAMES } from './migration-target-files'
1825
export { MIGRATION_REPORT_KIND, MIGRATION_REPORT_SCHEMA_VERSION } from './migration-report'
1926
export { migrateConfigSource } from './migration-source'
2027
export type { ConfigSourceMigrationResult } from './migration-source'
21-
22-
export interface ConfigFileMigrationEntry {
23-
file: string
24-
changed: boolean
25-
written: boolean
26-
rolledBack: boolean
27-
backupFile?: string
28-
changes: string[]
29-
}
30-
31-
export interface ConfigFileMigrationReport {
32-
reportKind: typeof MIGRATION_REPORT_KIND
33-
schemaVersion: typeof MIGRATION_REPORT_SCHEMA_VERSION
34-
generatedAt: string
35-
tool: {
36-
name: string
37-
version: string
38-
}
39-
cwd: string
40-
dryRun: boolean
41-
rollbackOnError: boolean
42-
backupDirectory?: string
43-
scannedFiles: number
44-
changedFiles: number
45-
writtenFiles: number
46-
backupsWritten: number
47-
unchangedFiles: number
48-
missingFiles: number
49-
entries: ConfigFileMigrationEntry[]
50-
}
51-
52-
export interface MigrateConfigFilesOptions {
53-
cwd: string
54-
files?: string[]
55-
dryRun?: boolean
56-
workspace?: boolean
57-
maxDepth?: number
58-
rollbackOnError?: boolean
59-
backupDir?: string
60-
include?: string[]
61-
exclude?: string[]
62-
}
63-
64-
export interface RestoreConfigFilesOptions {
65-
cwd: string
66-
reportFile: string
67-
dryRun?: boolean
68-
strict?: boolean
69-
}
70-
71-
export interface RestoreConfigFilesResult {
72-
cwd: string
73-
reportFile: string
74-
reportKind?: string
75-
reportSchemaVersion?: number
76-
dryRun: boolean
77-
strict: boolean
78-
scannedEntries: number
79-
restorableEntries: number
80-
restoredFiles: number
81-
missingBackups: number
82-
skippedEntries: number
83-
restored: string[]
84-
}
28+
export type {
29+
ConfigFileMigrationEntry,
30+
ConfigFileMigrationReport,
31+
MigrateConfigFilesOptions,
32+
RestoreConfigFilesOptions,
33+
RestoreConfigFilesResult,
34+
} from './migration-types'
8535

8636
export async function migrateConfigFiles(options: MigrateConfigFilesOptions): Promise<ConfigFileMigrationReport> {
8737
const cwd = path.resolve(options.cwd)
@@ -103,67 +53,33 @@ export async function migrateConfigFiles(options: MigrateConfigFilesOptions): Pr
10353
let backupsWritten = 0
10454
let unchangedFiles = 0
10555
let missingFiles = 0
106-
const wroteEntries: Array<{ file: string, source: string, entry: ConfigFileMigrationEntry }> = []
56+
const wroteEntries: MigrationWrittenEntry[] = []
10757

10858
for (const file of targetFiles) {
109-
const exists = await fs.pathExists(file)
110-
if (!exists) {
59+
const result = await executeMigrationFile({
60+
cwd,
61+
file,
62+
dryRun,
63+
rollbackOnError,
64+
wroteEntries,
65+
...(backupDirectory ? { backupDirectory } : {}),
66+
})
67+
68+
if (result.missing) {
11169
missingFiles += 1
11270
continue
11371
}
11472

11573
scannedFiles += 1
116-
const source = await fs.readFile(file, 'utf8')
117-
const migrated = migrateConfigSource(source)
74+
entries.push(result.entry)
11875

119-
const entry: ConfigFileMigrationEntry = {
120-
file,
121-
changed: migrated.changed,
122-
written: false,
123-
rolledBack: false,
124-
changes: migrated.changes,
125-
}
126-
entries.push(entry)
127-
128-
if (migrated.changed) {
76+
if (result.changed) {
12977
changedFiles += 1
130-
if (!dryRun) {
131-
try {
132-
if (backupDirectory) {
133-
const backupRelativePath = resolveBackupRelativePath(cwd, file)
134-
const backupFile = path.resolve(backupDirectory, backupRelativePath)
135-
await fs.ensureDir(path.dirname(backupFile))
136-
await fs.writeFile(backupFile, source, 'utf8')
137-
entry.backupFile = backupFile
138-
backupsWritten += 1
139-
}
140-
await fs.writeFile(file, migrated.code, 'utf8')
141-
entry.written = true
142-
wroteEntries.push({ file, source, entry })
143-
writtenFiles += 1
144-
}
145-
catch (error) {
146-
let rollbackCount = 0
147-
if (rollbackOnError && wroteEntries.length > 0) {
148-
for (const written of [...wroteEntries].reverse()) {
149-
try {
150-
await fs.writeFile(written.file, written.source, 'utf8')
151-
written.entry.written = false
152-
written.entry.rolledBack = true
153-
rollbackCount += 1
154-
}
155-
catch {
156-
// Continue best-effort rollback to avoid leaving even more partial state.
157-
}
158-
}
159-
writtenFiles = Math.max(0, writtenFiles - rollbackCount)
160-
}
161-
const reason = error instanceof Error ? error.message : String(error)
162-
const rollbackHint = rollbackOnError && rollbackCount > 0
163-
? ` Rolled back ${rollbackCount} previously written file(s).`
164-
: ''
165-
throw new Error(`Failed to write migrated config "${file}": ${reason}.${rollbackHint}`)
166-
}
78+
if (result.wrote) {
79+
writtenFiles += 1
80+
}
81+
if (result.backupWritten) {
82+
backupsWritten += 1
16783
}
16884
}
16985
else {
@@ -199,48 +115,15 @@ export async function restoreConfigFiles(options: RestoreConfigFilesOptions): Pr
199115
const strict = options.strict ?? false
200116
const reportFile = path.resolve(cwd, options.reportFile)
201117

202-
const report = await fs.readJSON(reportFile) as {
203-
reportKind?: string
204-
schemaVersion?: number
205-
entries?: Array<{ file?: string, backupFile?: string }>
206-
}
207-
assertMigrationReportCompatibility(report, reportFile)
208-
const entries = Array.isArray(report.entries) ? report.entries : []
209-
210-
let scannedEntries = 0
211-
let restorableEntries = 0
212-
let restoredFiles = 0
213-
let missingBackups = 0
214-
let skippedEntries = 0
215-
const restored: string[] = []
216-
217-
for (const entry of entries) {
218-
scannedEntries += 1
219-
const targetFile = entry.file ? path.resolve(entry.file) : undefined
220-
const backupFile = entry.backupFile ? path.resolve(entry.backupFile) : undefined
221-
222-
if (!targetFile || !backupFile) {
223-
skippedEntries += 1
224-
continue
225-
}
226-
227-
restorableEntries += 1
228-
229-
const backupExists = await fs.pathExists(backupFile)
230-
if (!backupExists) {
231-
missingBackups += 1
232-
continue
233-
}
234-
235-
if (!dryRun) {
236-
const backupContent = await fs.readFile(backupFile, 'utf8')
237-
await fs.ensureDir(path.dirname(targetFile))
238-
await fs.writeFile(targetFile, backupContent, 'utf8')
239-
}
240-
241-
restoredFiles += 1
242-
restored.push(targetFile)
243-
}
118+
const report = await loadMigrationReportForRestore(reportFile)
119+
const {
120+
scannedEntries,
121+
restorableEntries,
122+
restoredFiles,
123+
missingBackups,
124+
skippedEntries,
125+
restored,
126+
} = await restoreConfigEntries(report.entries, dryRun)
244127

245128
if (strict && missingBackups > 0) {
246129
throw new Error(`Restore failed: ${missingBackups} backup file(s) missing in report ${reportFile}.`)

0 commit comments

Comments
 (0)