This PR lands a quick interface update for template migration with some lessons learned form our existing migrations. Specifically, this version attempts to: - Allow migrations to access the raw candidate. This way we can migrate candidates that _would not parse as valid in v4_. This will help us migrate prefixes in candidates from v3 to v4. - There is no more awkward "return null" if nothing has changed. The return `null` was necessary because we relied on mutating the Variant and since parsing/printing could remove some information, it was not easy to find out wether a candidate needed to be migrated at all. With a string though, we can do this cheaply by returning the `rawCandidate`. - We previously asserted that if `parseCandidate` returns more than one candidate, we only picked the first one. This behavior is now moved into the migrations where we have more context. For now though, we still do not need to worry about this since in all cases, these duplicate candidates would serialize to the same `Candidate`. It is helpful if you only want to run a migration on a specific type of candidate (e.g. if there's a `static` one and a more generic `functional` one). - We need access to the `DesignSystem` inside migrations now to be able to `parseCandidate`s. Opening this up as a separate PR since it can take some time to iron out the edge cases for the individual codemod PRs and I don't want to be rebasing all the time. ## Before ```ts type Migration = (candidate: Candidate) => Candidate | null ``` ## After ```ts type Migration = (designSystem: DesignSystem, rawCandidate: string) => string ```
44 lines
1.4 KiB
TypeScript
44 lines
1.4 KiB
TypeScript
import fs from 'node:fs/promises'
|
|
import path from 'node:path'
|
|
import type { DesignSystem } from '../../../tailwindcss/src/design-system'
|
|
import { extractRawCandidates, replaceCandidateInContent } from './candidates'
|
|
import { bgGradient } from './codemods/bg-gradient'
|
|
import { important } from './codemods/important'
|
|
|
|
export type Migration = (designSystem: DesignSystem, rawCandidate: string) => string
|
|
|
|
export default async function migrateContents(
|
|
designSystem: DesignSystem,
|
|
contents: string,
|
|
migrations: Migration[] = [important, bgGradient],
|
|
): Promise<string> {
|
|
let candidates = await extractRawCandidates(contents)
|
|
|
|
// Sort candidates by starting position desc
|
|
candidates.sort((a, z) => z.start - a.start)
|
|
|
|
let output = contents
|
|
for (let { rawCandidate, start, end } of candidates) {
|
|
let needsMigration = false
|
|
for (let migration of migrations) {
|
|
let candidate = migration(designSystem, rawCandidate)
|
|
if (rawCandidate !== candidate) {
|
|
rawCandidate = candidate
|
|
needsMigration = true
|
|
}
|
|
}
|
|
|
|
if (needsMigration) {
|
|
output = replaceCandidateInContent(output, rawCandidate, start, end)
|
|
}
|
|
}
|
|
|
|
return output
|
|
}
|
|
|
|
export async function migrate(designSystem: DesignSystem, file: string) {
|
|
let fullPath = path.resolve(process.cwd(), file)
|
|
let contents = await fs.readFile(fullPath, 'utf-8')
|
|
|
|
await fs.writeFile(fullPath, await migrateContents(designSystem, contents))
|
|
}
|