1
0

2 کامیت‌ها 698d0a2707 ... 4f24704551

نویسنده SHA1 پیام تاریخ
  Craig Fletcher 4f24704551 Add config override switch 10 ماه پیش
  Craig Fletcher 698d0a2707 Add config override switch 10 ماه پیش
3فایلهای تغییر یافته به همراه171 افزوده شده و 172 حذف شده
  1. 3 2
      src/lib.js
  2. 153 166
      src/processors.js
  3. 15 4
      src/util/file-system.js

+ 3 - 2
src/lib.js

@@ -6,7 +6,8 @@ import {
   removeBasePaths,
   removeCwd,
   replaceFileExtension,
-  getValueAtPath
+  getValueAtPath,
+  expandTilde
 } from "./util/index.js";
 import path from "path";
 import process from "node:process";
@@ -119,7 +120,7 @@ async function runTask({ meta, config, jobId }) {
 
 async function expandFileTask(patternsToInclude, config, meta) {
   const filesToProcess = await readFilesByGlob(patternsToInclude);
-  const pathsToStrip = config.stripPaths || [];
+  const pathsToStrip = (config.stripPaths || []).map(path => expandTilde(path));
   const outputDir = config.outputDir || "";
   return await Promise.all(
     filesToProcess.map(async filePath => {

+ 153 - 166
src/processors.js

@@ -1,24 +1,24 @@
-import * as sass from "sass"
+import * as sass from "sass";
 import {
   firstFound,
   generateRandomId,
   getCleanPath,
   getHref,
   slugifyString,
-  writeFile,
-} from "./util/index.js"
-import fs from "fs/promises"
-import handlebars from "handlebars"
-import { marked } from "marked"
-import markedCodePreview from "marked-code-preview"
-import matter from "gray-matter"
-import { optimize } from "svgo"
-import sharp from "sharp"
-import path from "path"
-import { minify } from "html-minifier-terser"
-import favicons from "favicons"
+  writeFile
+} from "./util/index.js";
+import fs from "fs/promises";
+import handlebars from "handlebars";
+import { marked } from "marked";
+import markedCodePreview from "marked-code-preview";
+import matter from "gray-matter";
+import { optimize } from "svgo";
+import sharp from "sharp";
+import path from "path";
+import { minify } from "html-minifier-terser";
+import favicons from "favicons";
 
-const templateCache = new Map()
+const templateCache = new Map();
 
 function createMarkdownRenderer(meta) {
   return marked
@@ -27,71 +27,64 @@ function createMarkdownRenderer(meta) {
     .use({
       renderer: {
         image({ href, title, text }) {
-          const attrs = [`alt="${text}"`]
+          const attrs = [`alt="${text}"`];
 
-          const foundSrcSet = meta.resources.images[slugifyString(href)]
+          const foundSrcSet = meta.resources.images?.[slugifyString(href)];
 
           if (foundSrcSet) {
             const srcSetString = foundSrcSet.detail.srcSet
               .map(src => src.join(" "))
-              .join(", ")
-            const defaultSrc = foundSrcSet.detail.srcSet[0][0]
-            attrs.push(`src="${defaultSrc}"`)
-            attrs.push(`srcset="${srcSetString}"`)
-            attrs.push("sizes=\"(min-width: 800px) 40vw, 100vw\"")
+              .join(", ");
+            const defaultSrc = foundSrcSet.detail.srcSet[0][0];
+            attrs.push(`src="${defaultSrc}"`);
+            attrs.push(`srcset="${srcSetString}"`);
+            attrs.push('sizes="(min-width: 800px) 40vw, 100vw"');
             attrs.push(
-              `style="aspect-ratio: ${foundSrcSet.detail.aspectRatio}"`,
-            )
+              `style="aspect-ratio: ${foundSrcSet.detail.aspectRatio}"`
+            );
           } else {
-            attrs.push(`src="${href}"`)
+            attrs.push(`src="${href}"`);
           }
 
           if (title) {
-            attrs.push(`title="${title}"`)
+            attrs.push(`title="${title}"`);
           }
 
-          return `<img ${attrs.join(" ")} >`
-        },
-      },
-    })
+          return `<img ${attrs.join(" ")} >`;
+        }
+      }
+    });
 }
 async function findTemplatePath(templateDirs, templateName) {
-  const templatePath = await firstFound(
-    templateDirs,
-    `${templateName}.hbs`,
-  )
-  if (!templatePath) throw new Error(`Template not found: ${templateName}`)
-  return templatePath
+  const templatePath = await firstFound(templateDirs, `${templateName}.hbs`);
+  if (!templatePath) throw new Error(`Template not found: ${templateName}`);
+  return templatePath;
 }
 
 async function getTemplate(templatePath) {
   if (!templateCache.has(templatePath)) {
-    const templateContent = await fs.readFile(templatePath, "utf8")
+    const templateContent = await fs.readFile(templatePath, "utf8");
     templateCache.set(templatePath, {
       path: templatePath,
-      renderer: handlebars.compile(templateContent),
-    })
+      renderer: handlebars.compile(templateContent)
+    });
   }
-  return templateCache.get(templatePath)
+  return templateCache.get(templatePath);
 }
 
-export async function renderTemplate({
-  config,
-  meta,
-}) {
-  const templatePath = config.filePath || await findTemplatePath(
-    config.templateDirs,
-    config.template,
-  )
-  const fileOutputPath = config.fileOutputPath
-  const href = getHref(fileOutputPath, meta)
+export async function renderTemplate({ config, meta }) {
+  const templatePath =
+    config.filePath ||
+    (await findTemplatePath(config.templateDirs, config.template));
+  const fileOutputPath = config.fileOutputPath;
+  const href = getHref(fileOutputPath, meta);
 
-  const template = await getTemplate(templatePath)
+  const template = await getTemplate(templatePath);
   const html = template.renderer({
     ...meta,
     href,
-    ...config,
-  })
+    ...config
+  });
   if (config.writeOut) {
     const minifiedHtml = await minify(html, {
       collapseWhitespace: true,
@@ -99,188 +92,182 @@ export async function renderTemplate({
       removeRedundantAttributes: true,
       removeEmptyAttributes: true,
       minifyCSS: true,
-      minifyJS: true,
-    })
+      minifyJS: true
+    });
 
-    await writeFile(fileOutputPath, minifiedHtml)
+    await writeFile(fileOutputPath, minifiedHtml);
     return {
       detail: { html },
       deps: {
-        paths: [template.path],
+        paths: [template.path]
       },
       paths: [fileOutputPath],
-      ref: slugifyString(href),
-    }
+      ref: slugifyString(href)
+    };
   }
 
   return {
     detail: { html },
     deps: {
-      paths: [template.path],
+      paths: [template.path]
     },
-    ref: slugifyString(href),
-  }
+    ref: slugifyString(href)
+  };
 }
-export async function renderMarkdownToHtml({
-  config,
-  meta,
-}) {
-  const filePath = config.filePath
-  const fileOutputPath = config.fileOutputPath
-  const content = await fs.readFile(filePath, "utf8")
-  const { data, content: markdown } = matter(content)
-  const href = getHref(fileOutputPath, meta)
+export async function renderMarkdownToHtml({ config, meta }) {
+  const filePath = config.filePath;
+  const fileOutputPath = config.fileOutputPath;
+  const content = await fs.readFile(filePath, "utf8");
+  const { data, content: markdown } = matter(content);
+  const href = getHref(fileOutputPath, meta);
 
-  const renderer = createMarkdownRenderer(meta)
-  const html = renderer(markdown)
+  const renderer = createMarkdownRenderer(meta);
+  const html = renderer(markdown);
 
   return {
     detail: { ...data, href, content: html, fileOutputPath },
-    ref: slugifyString(filePath),
-  }
+    ref: slugifyString(filePath)
+  };
 }
-export async function renderMarkdownWithTemplate({
-  config,
-  meta,
-}) {
-  const filePath = config.filePath
-  const fileOutputPath = config.fileOutputPath
-  const content = await fs.readFile(filePath, "utf8")
-  const { data, content: markdown } = matter(content)
-  const templateName = data.template || config.defaultTemplate
-  const href = getHref(fileOutputPath, meta)
+export async function renderMarkdownWithTemplate({ config, meta }) {
+  const filePath = config.filePath;
+  const fileOutputPath = config.fileOutputPath;
+  const content = await fs.readFile(filePath, "utf8");
+  const { data, content: markdown } = matter(content);
+  const templateName = data.template || config.defaultTemplate;
+  const href = getHref(fileOutputPath, meta);
 
   if (!templateCache.has(templateName)) {
     const templatePath = await firstFound(
       config.templateDirs,
-      `${templateName}.hbs`,
-    )
-    if (!templatePath) throw new Error(`Template not found: ${templateName}`)
-    const templateContent = await fs.readFile(templatePath, "utf8")
+      `${templateName}.hbs`
+    );
+    if (!templatePath) throw new Error(`Template not found: ${templateName}`);
+    const templateContent = await fs.readFile(templatePath, "utf8");
     templateCache.set(templateName, {
       path: templatePath,
-      renderer: handlebars.compile(templateContent),
-    })
+      renderer: handlebars.compile(templateContent)
+    });
   }
-  const template = templateCache.get(templateName)
-  const renderer = createMarkdownRenderer(meta)
+  const template = templateCache.get(templateName);
+  const renderer = createMarkdownRenderer(meta);
   const html = template.renderer({
     ...data,
     ...meta,
     href,
-    content: renderer(markdown),
-  })
+    content: renderer(markdown)
+  });
   const minifiedHtml = await minify(html, {
     collapseWhitespace: true,
     removeComments: true,
     removeRedundantAttributes: true,
     removeEmptyAttributes: true,
     minifyCSS: true,
-    minifyJS: true,
-  })
+    minifyJS: true
+  });
 
-  await writeFile(fileOutputPath, minifiedHtml)
+  await writeFile(fileOutputPath, minifiedHtml);
 
   return {
     detail: { ...data, href },
     paths: [fileOutputPath],
     deps: {
-      paths: [template.path],
+      paths: [template.path]
     },
-    ref: slugifyString(fileOutputPath),
-  }
+    ref: slugifyString(fileOutputPath)
+  };
 }
 
 export async function compileSass({ config, meta }) {
-  const filePath = config.filePath
-  const fileOutputPath = config.fileOutputPath
-  const result = await sass.compileAsync(filePath, { style: "compressed" })
-  await writeFile(fileOutputPath, result.css)
+  const filePath = config.filePath;
+  const fileOutputPath = config.fileOutputPath;
+  const result = await sass.compileAsync(filePath, { style: "compressed" });
+  await writeFile(fileOutputPath, result.css);
   return {
     paths: [fileOutputPath],
     ref: slugifyString(fileOutputPath),
     detail: {
-      href: fileOutputPath.replace(meta.opts.outDir, ""),
+      href: fileOutputPath.replace(meta.opts.outDir, "")
     },
     deps: {
-      paths: [...result.loadedUrls.map(item => item.pathname)],
-    },
-  }
+      paths: [...result.loadedUrls.map(item => item.pathname)]
+    }
+  };
 }
 
 export async function optimiseSvg({ config }) {
-  const filePath = config.filePath
-  const fileOutputPath = config.fileOutputPath
-  const svgString = await fs.readFile(filePath, "utf8")
+  const filePath = config.filePath;
+  const fileOutputPath = config.fileOutputPath;
+  const svgString = await fs.readFile(filePath, "utf8");
   const result = optimize(svgString, {
-    plugins: ["preset-default"],
-  })
-  await writeFile(fileOutputPath, result.data)
+    plugins: ["preset-default"]
+  });
+  await writeFile(fileOutputPath, result.data);
   return {
     paths: [fileOutputPath],
-    ref: slugifyString(fileOutputPath),
-  }
+    ref: slugifyString(fileOutputPath)
+  };
 }
 
 export async function copy({ config }) {
-  const filePath = config.filePath
-  const fileOutputPath = config.fileOutputPath
-  await fs.mkdir(config.fileOutputDir, { recursive: true })
-  await fs.copyFile(filePath, fileOutputPath)
+  const filePath = config.filePath;
+  const fileOutputPath = config.fileOutputPath;
+  await fs.mkdir(config.fileOutputDir, { recursive: true });
+  await fs.copyFile(filePath, fileOutputPath);
   return {
     paths: [fileOutputPath],
-    ref: slugifyString(fileOutputPath),
-  }
+    ref: slugifyString(fileOutputPath)
+  };
 }
 
 export async function imageToWebP({ meta, config }) {
-  const filePath = config.filePath
-  const fileOutputDir = config.fileOutputDir
-  const sourceExtension = path.extname(filePath)
-  const outputExtension = config.outputFileExtension
-  const base = path.basename(filePath, sourceExtension)
-  await fs.mkdir(fileOutputDir, { recursive: true })
+  const filePath = config.filePath;
+  const fileOutputDir = config.fileOutputDir;
+  const sourceExtension = path.extname(filePath);
+  const outputExtension = config.outputFileExtension;
+  const base = path.basename(filePath, sourceExtension);
+  await fs.mkdir(fileOutputDir, { recursive: true });
 
-  const original = sharp(filePath)
-  const metadata = await original.metadata()
-  const { width, height } = metadata
+  const original = sharp(filePath);
+  const metadata = await original.metadata();
+  const { width, height } = metadata;
 
   if (!width || !height) {
-    throw new Error("Could not determine image dimensions")
+    throw new Error("Could not determine image dimensions");
   }
 
-  const aspectRatio = width / height
-  const name = config.uniqueFilenames ? base : `${base}-${generateRandomId()}`
+  const aspectRatio = width / height;
+  const name = config.uniqueFilenames ? base : `${base}-${generateRandomId()}`;
   const srcSet = await Promise.all(
     config.imageSizes.map(async size => {
-      const sizeNum = parseInt(size.replace("w", ""), 10)
+      const sizeNum = parseInt(size.replace("w", ""), 10);
       const outputFile = path.join(
         fileOutputDir,
-        `${name}-${sizeNum}${outputExtension}`,
-      )
+        `${name}-${sizeNum}${outputExtension}`
+      );
 
       await original
         .clone()
         .resize(sizeNum)
         .webp({ quality: config.quality })
-        .toFile(outputFile)
+        .toFile(outputFile);
 
-      return [getCleanPath(outputFile, meta), size]
-    }),
-  )
+      return [getCleanPath(outputFile, meta), size];
+    })
+  );
 
-  const imageRef = slugifyString(getCleanPath(path.join(filePath), meta))
+  const imageRef = slugifyString(getCleanPath(path.join(filePath), meta));
 
   return {
     paths: srcSet.map(src => src[0]),
     detail: { srcSet, aspectRatio },
-    ref: imageRef,
-  }
+    ref: imageRef
+  };
 }
 
 export async function generateFavicons({ meta, config }) {
-  const filePath = config.filePath
-  const fileOutputDir = config.fileOutputDir
+  const filePath = config.filePath;
+  const fileOutputDir = config.fileOutputDir;
   // Configuration for favicons package
   const configuration = {
     path: getCleanPath(fileOutputDir, meta), // Path for overriding default icons path
@@ -309,45 +296,45 @@ export async function generateFavicons({ meta, config }) {
       appleStartup: true,
       favicons: true,
       windows: true,
-      yandex: true,
-    },
-  }
+      yandex: true
+    }
+  };
   try {
-    const response = await favicons(filePath, configuration)
+    const response = await favicons(filePath, configuration);
 
     // Write all generated images to disk
     await Promise.all(
       response.images.map(async image => {
-        const outputPath = path.join(fileOutputDir, image.name)
-        await writeFile(outputPath, image.contents)
-      }),
-    )
+        const outputPath = path.join(fileOutputDir, image.name);
+        await writeFile(outputPath, image.contents);
+      })
+    );
 
     // Write all generated files (manifests, etc.) to disk
     await Promise.all(
       response.files.map(async file => {
-        const outputPath = path.join(fileOutputDir, file.name)
-        await writeFile(outputPath, file.contents)
-      }),
-    )
+        const outputPath = path.join(fileOutputDir, file.name);
+        await writeFile(outputPath, file.contents);
+      })
+    );
 
     // Combine HTML meta tags
-    const htmlMeta = response.html.join("\n    ")
+    const htmlMeta = response.html.join("\n    ");
     return {
       detail: {
-        htmlMeta,
+        htmlMeta
       },
       paths: [
         ...response.images.map(img =>
-          getCleanPath(path.join(fileOutputDir, img.name), meta),
+          getCleanPath(path.join(fileOutputDir, img.name), meta)
         ),
         ...response.files.map(file =>
-          getCleanPath(path.join(fileOutputDir, file.name), meta),
-        ),
+          getCleanPath(path.join(fileOutputDir, file.name), meta)
+        )
       ],
-      ref: "metatags",
-    }
+      ref: "metatags"
+    };
   } catch (error) {
-    throw new Error(`Failed to generate favicons: ${error.message}`)
+    throw new Error(`Failed to generate favicons: ${error.message}`);
   }
 }

+ 15 - 4
src/util/file-system.js

@@ -1,5 +1,6 @@
 import fs from "node:fs/promises"
 import path from "path"
+import os from "os"
 import { glob } from "glob"
 
 export async function fileExists(filePath) {
@@ -44,7 +45,7 @@ export async function readFilesByGlob(globConfigs) {
         ignore: [],
         ...globConfig,
       }
-      const matches = await glob(pattern, {
+      const matches = await glob(expandTilde(pattern), {
         ignore,
         dot,
       })
@@ -56,6 +57,11 @@ export async function readFilesByGlob(globConfigs) {
   return [...new Set(files)]
 }
 
+export function expandTilde(path) {
+  if (!path.startsWith("~")) return path
+  return path.replace(/^~(?=$|\/|\\)/, os.homedir())
+}
+
 export async function checkFilesExist(files, baseDir) {
   const filesToCheck = Array.isArray(files) ? files : [files]
   const fileCheckResults = await Promise.all(
@@ -65,9 +71,14 @@ export async function checkFilesExist(files, baseDir) {
       return { filePath, exists }
     }),
   )
-  return fileCheckResults.reduce((sorted, { filePath, exists }) => {
-    return exists ? { ...sorted, present: [...sorted.present, filePath] } : { ...sorted, absent: [...sorted.absent, filePath] }
-  }, { present: [], absent: [] })
+  return fileCheckResults.reduce(
+    (sorted, { filePath, exists }) => {
+      return exists
+        ? { ...sorted, present: [...sorted.present, filePath] }
+        : { ...sorted, absent: [...sorted.absent, filePath] }
+    },
+    { present: [], absent: [] },
+  )
 }
 
 export async function writeFile(filePath, content) {