|
|
@@ -1,220 +0,0 @@
|
|
|
-import fs from "node:fs/promises"
|
|
|
-import os from "node:os"
|
|
|
-import path from "path"
|
|
|
-import { glob } from "glob"
|
|
|
-
|
|
|
-export async function fileExists(filePath) {
|
|
|
- try {
|
|
|
- await fs.stat(filePath)
|
|
|
- return true
|
|
|
- } catch (err) {
|
|
|
- if (err.code === "ENOENT") {
|
|
|
- return false
|
|
|
- }
|
|
|
- throw err // re-throw other errors
|
|
|
- }
|
|
|
-}
|
|
|
-export async function readDirectoryRecursively(dir, files = []) {
|
|
|
- const exists = await fileExists(dir)
|
|
|
- if (!exists) {
|
|
|
- return files
|
|
|
- }
|
|
|
- const contents = await fs.readdir(dir, { withFileTypes: true })
|
|
|
- for (const item of contents) {
|
|
|
- const itemPath = path.join(dir, item.name)
|
|
|
- if (item.isDirectory()) {
|
|
|
- readDirectoryRecursively(itemPath, files)
|
|
|
- } else {
|
|
|
- files.push(itemPath)
|
|
|
- }
|
|
|
- }
|
|
|
- return files
|
|
|
-}
|
|
|
-// type InputConfig
|
|
|
-// {
|
|
|
-// pattern: String | String[];
|
|
|
-// ignore: String | String[];
|
|
|
-// }
|
|
|
-export async function readFilesByGlob(globConfigs) {
|
|
|
- const matchPromises = globConfigs.reduce(
|
|
|
- async (existingMatches, globConfig) => {
|
|
|
- const { pattern, ignore, dot } = {
|
|
|
- dot: false,
|
|
|
- ignore: [],
|
|
|
- ...globConfig,
|
|
|
- }
|
|
|
- const matches = await glob(pattern, {
|
|
|
- ignore,
|
|
|
- dot,
|
|
|
- })
|
|
|
- return [...(await existingMatches), ...matches]
|
|
|
- },
|
|
|
- [],
|
|
|
- )
|
|
|
- const files = await matchPromises
|
|
|
- return [...new Set(files)]
|
|
|
-}
|
|
|
-
|
|
|
-export function resolvePath(unresolvedPath) {
|
|
|
- return path.resolve(unresolvedPath.replace(/^~/, os.homedir()))
|
|
|
-}
|
|
|
-
|
|
|
-export async function firstFound(dirs, fileName) {
|
|
|
- for (const dir of dirs) {
|
|
|
- const filePath = resolvePath(path.join(dir, fileName))
|
|
|
- const exists = await fileExists(filePath)
|
|
|
- if (exists) {
|
|
|
- return filePath
|
|
|
- }
|
|
|
- }
|
|
|
- return null
|
|
|
-}
|
|
|
-
|
|
|
-export function removeCwd(paths) {
|
|
|
- const cwd = `${process.cwd()}/`
|
|
|
- return paths.map(path => path.replace(cwd, ""))
|
|
|
-}
|
|
|
-
|
|
|
-export function removeBasePaths(baseDirs, fullPath) {
|
|
|
- return baseDirs.reduce((cleanedPath, dir) => {
|
|
|
- return cleanedPath.replace(dir, "")
|
|
|
- }, fullPath)
|
|
|
-}
|
|
|
-
|
|
|
-export function replaceFileExtension(filePath, newExtension) {
|
|
|
- if (!newExtension) {
|
|
|
- return filePath
|
|
|
- }
|
|
|
- return `${stripFileExtension(filePath)}${newExtension}`
|
|
|
-}
|
|
|
-
|
|
|
-export function stripFileExtension(filePath) {
|
|
|
- return path.join(
|
|
|
- path.dirname(filePath),
|
|
|
- path.basename(filePath, path.extname(filePath)),
|
|
|
- )
|
|
|
-}
|
|
|
-
|
|
|
-export function getCleanPath(filePath, meta) {
|
|
|
- return filePath.replace(meta.opts.runDir, "").replace(meta.opts.outDir, "/")
|
|
|
-}
|
|
|
-
|
|
|
-export function getHref(filePath, meta) {
|
|
|
- const route = getCleanPath(filePath, meta)
|
|
|
- if (route.includes("index.html")) {
|
|
|
- return route.replace("index.html", "")
|
|
|
- }
|
|
|
- return route.replace(".html", "")
|
|
|
-}
|
|
|
-
|
|
|
-function stringifyPathPart(part) {
|
|
|
- return typeof part === "symbol" ? part.toString() : String(part)
|
|
|
-}
|
|
|
-
|
|
|
-function trackPropertyAccessDeep(obj, path = [], accessed = new Set()) {
|
|
|
- return new Proxy(obj, {
|
|
|
- get(target, prop, receiver) {
|
|
|
- const fullPath = [...path, prop].map(stringifyPathPart).join(".")
|
|
|
- const value = Reflect.get(target, prop, receiver)
|
|
|
-
|
|
|
- if (typeof target === "object" && Object.prototype.hasOwnProperty.call(target, prop)) {
|
|
|
- accessed.add({ path: fullPath, value })
|
|
|
- }
|
|
|
-
|
|
|
- // Recursively wrap if value is an object and not null
|
|
|
- if (value && typeof value === "object") {
|
|
|
- return trackPropertyAccessDeep(value, [...path, prop], accessed)
|
|
|
- }
|
|
|
-
|
|
|
- return value
|
|
|
- },
|
|
|
- })
|
|
|
-}
|
|
|
-
|
|
|
-export function createTrackedObject(obj) {
|
|
|
- const accessed = new Set()
|
|
|
- const proxy = trackPropertyAccessDeep(obj, [], accessed)
|
|
|
- return { proxy, accessed }
|
|
|
-}
|
|
|
-
|
|
|
-export function getDeepestPropertiesForKey(paths, key) {
|
|
|
- // Sort paths to make prefix comparison easier
|
|
|
- const sorted = paths.slice().sort((a, b) => {
|
|
|
- if (a[key] < b[key]) {
|
|
|
- return -1
|
|
|
- }
|
|
|
- if (a[key] > b[key]) {
|
|
|
- return 1
|
|
|
- }
|
|
|
- return 0
|
|
|
- })
|
|
|
- const result = []
|
|
|
-
|
|
|
- for (let i = 0; i < sorted.length; i++) {
|
|
|
- const current = sorted[i]
|
|
|
- const next = sorted[i + 1]
|
|
|
- // If the next path doesn't start with the current + a dot, it's a leaf node
|
|
|
- const nextKey = next?.[key]
|
|
|
- const currentKey = current[key]
|
|
|
- if (nextKey !== currentKey) {
|
|
|
- if (!next || !next[key].startsWith(current[key] + ".")) {
|
|
|
- result.push(current)
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return result
|
|
|
-}
|
|
|
-
|
|
|
-export function slugifyString(str) {
|
|
|
- return str
|
|
|
- .toLowerCase()
|
|
|
- .trim()
|
|
|
- .replace(/[/\\?%@*:|"<>]/g, "-") // Replace invalid filename characters
|
|
|
- .replace(/\s+/g, "-") // Replace whitespace with dashes
|
|
|
- .replace(/-+/g, "-") // Collapse multiple dashes
|
|
|
- .replace(/\./g, "-") // Replace dots with dashes
|
|
|
- .replace(/^-+|-+$/g, "") // Trim leading/trailing dashes
|
|
|
-}
|
|
|
-
|
|
|
-export function getValueAtPath(obj, path) {
|
|
|
- const parts = path.split(".")
|
|
|
- let val = obj
|
|
|
- for (const part of parts) {
|
|
|
- val = val?.[part]
|
|
|
- if (val === undefined) break
|
|
|
- }
|
|
|
- return val
|
|
|
-}
|
|
|
-
|
|
|
-export async function checkFilesExist(files, baseDir) {
|
|
|
- const filesToCheck = Array.isArray(files) ? files : [files]
|
|
|
- const fileCheckResults = await Promise.all(
|
|
|
- filesToCheck.map(async file => {
|
|
|
- const filePath = path.join(baseDir, file)
|
|
|
- const exists = await fileExists(filePath)
|
|
|
- return { filePath, exists }
|
|
|
- }),
|
|
|
- )
|
|
|
- return fileCheckResults.reduce((sorted, { filePath, exists }) => {
|
|
|
- return exists ? { ...sorted, present: [...sorted.present, filePath] } : { ...sorted, absent: [...sorted.absent, filePath] }
|
|
|
- }, { present: [], absent: [] })
|
|
|
-
|
|
|
-}
|
|
|
-
|
|
|
-export function generateRandomId(length = 8) {
|
|
|
- const chars = "abcdefghijklmnopqrstuvwxyz0123456789"
|
|
|
- let result = ""
|
|
|
- for (let i = 0; i < length; i++) {
|
|
|
- result += chars.charAt(Math.floor(Math.random() * chars.length))
|
|
|
- }
|
|
|
- return result
|
|
|
-}
|
|
|
-
|
|
|
-export async function writeFile(filePath, content) {
|
|
|
- const fileDir = path.dirname(filePath)
|
|
|
- await fs.mkdir(fileDir, { recursive: true })
|
|
|
- return await fs.writeFile(filePath, content, {
|
|
|
- encoding: "utf8",
|
|
|
- })
|
|
|
-}
|