import fs from 'fs'
import path from 'path'
import isGlob from 'is-glob'
import fastGlob from 'fast-glob'
import normalizePath from 'normalize-path'
import { parseGlob } from '../util/parseGlob'
import { env } from './sharedState'
export function parseCandidateFiles(context, tailwindConfig) {
let files = tailwindConfig.content.files
files = files.filter((filePath) => typeof filePath === 'string')
files = files.map(normalizePath)
let tasks = fastGlob.generateTasks(files)
let included = []
let excluded = []
for (const task of tasks) {
included.push(...task.positive.map((filePath) => parseFilePath(filePath, false)))
excluded.push(...task.negative.map((filePath) => parseFilePath(filePath, true)))
}
let paths = [...included, ...excluded]
paths = resolveRelativePaths(context, paths)
paths = paths.flatMap(resolvePathSymlinks)
paths = paths.map(resolveGlobPattern)
return paths
}
function parseFilePath(filePath, ignore) {
let contentPath = {
original: filePath,
base: filePath,
ignore,
pattern: filePath,
glob: null,
}
if (isGlob(filePath)) {
Object.assign(contentPath, parseGlob(filePath))
}
return contentPath
}
function resolveGlobPattern(contentPath) {
let base = normalizePath(contentPath.base)
base = fastGlob.escapePath(base)
contentPath.pattern = contentPath.glob ? `${base}/${contentPath.glob}` : base
contentPath.pattern = contentPath.ignore ? `!${contentPath.pattern}` : contentPath.pattern
return contentPath
}
function resolveRelativePaths(context, contentPaths) {
let resolveFrom = []
if (context.userConfigPath && context.tailwindConfig.content.relative) {
resolveFrom = [path.dirname(context.userConfigPath)]
}
return contentPaths.map((contentPath) => {
contentPath.base = path.resolve(...resolveFrom, contentPath.base)
return contentPath
})
}
function resolvePathSymlinks(contentPath) {
let paths = [contentPath]
try {
let resolvedPath = fs.realpathSync(contentPath.base)
if (resolvedPath !== contentPath.base) {
paths.push({
...contentPath,
base: resolvedPath,
})
}
} catch {
}
return paths
}
export function resolvedChangedContent(context, candidateFiles, fileModifiedMap) {
let changedContent = context.tailwindConfig.content.files
.filter((item) => typeof item.raw === 'string')
.map(({ raw, extension = 'html' }) => ({ content: raw, extension }))
let [changedFiles, mTimesToCommit] = resolveChangedFiles(candidateFiles, fileModifiedMap)
for (let changedFile of changedFiles) {
let extension = path.extname(changedFile).slice(1)
changedContent.push({ file: changedFile, extension })
}
return [changedContent, mTimesToCommit]
}
function resolveChangedFiles(candidateFiles, fileModifiedMap) {
let paths = candidateFiles.map((contentPath) => contentPath.pattern)
let mTimesToCommit = new Map()
let changedFiles = new Set()
env.DEBUG && console.time('Finding changed files')
let files = fastGlob.sync(paths, { absolute: true })
for (let file of files) {
let prevModified = fileModifiedMap.get(file) || -Infinity
let modified = fs.statSync(file).mtimeMs
if (modified > prevModified) {
changedFiles.add(file)
mTimesToCommit.set(file, modified)
}
}
env.DEBUG && console.timeEnd('Finding changed files')
return [changedFiles, mTimesToCommit]
}