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

+ 153 - 166
src/processors.js

@@ -1,24 +1,24 @@
-import * as sass from "sass"
+import * as sass from "sass";
 import {
 import {
   firstFound,
   firstFound,
   generateRandomId,
   generateRandomId,
   getCleanPath,
   getCleanPath,
   getHref,
   getHref,
   slugifyString,
   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) {
 function createMarkdownRenderer(meta) {
   return marked
   return marked
@@ -27,71 +27,64 @@ function createMarkdownRenderer(meta) {
     .use({
     .use({
       renderer: {
       renderer: {
         image({ href, title, text }) {
         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) {
           if (foundSrcSet) {
             const srcSetString = foundSrcSet.detail.srcSet
             const srcSetString = foundSrcSet.detail.srcSet
               .map(src => src.join(" "))
               .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(
             attrs.push(
-              `style="aspect-ratio: ${foundSrcSet.detail.aspectRatio}"`,
-            )
+              `style="aspect-ratio: ${foundSrcSet.detail.aspectRatio}"`
+            );
           } else {
           } else {
-            attrs.push(`src="${href}"`)
+            attrs.push(`src="${href}"`);
           }
           }
 
 
           if (title) {
           if (title) {
-            attrs.push(`title="${title}"`)
+            attrs.push(`title="${title}"`);
           }
           }
 
 
-          return `<img ${attrs.join(" ")} >`
-        },
-      },
-    })
+          return `<img ${attrs.join(" ")} >`;
+        }
+      }
+    });
 }
 }
 async function findTemplatePath(templateDirs, templateName) {
 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) {
 async function getTemplate(templatePath) {
   if (!templateCache.has(templatePath)) {
   if (!templateCache.has(templatePath)) {
-    const templateContent = await fs.readFile(templatePath, "utf8")
+    const templateContent = await fs.readFile(templatePath, "utf8");
     templateCache.set(templatePath, {
     templateCache.set(templatePath, {
       path: 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({
   const html = template.renderer({
     ...meta,
     ...meta,
     href,
     href,
-    ...config,
-  })
+    ...config
+  });
   if (config.writeOut) {
   if (config.writeOut) {
     const minifiedHtml = await minify(html, {
     const minifiedHtml = await minify(html, {
       collapseWhitespace: true,
       collapseWhitespace: true,
@@ -99,188 +92,182 @@ export async function renderTemplate({
       removeRedundantAttributes: true,
       removeRedundantAttributes: true,
       removeEmptyAttributes: true,
       removeEmptyAttributes: true,
       minifyCSS: true,
       minifyCSS: true,
-      minifyJS: true,
-    })
+      minifyJS: true
+    });
 
 
-    await writeFile(fileOutputPath, minifiedHtml)
+    await writeFile(fileOutputPath, minifiedHtml);
     return {
     return {
       detail: { html },
       detail: { html },
       deps: {
       deps: {
-        paths: [template.path],
+        paths: [template.path]
       },
       },
       paths: [fileOutputPath],
       paths: [fileOutputPath],
-      ref: slugifyString(href),
-    }
+      ref: slugifyString(href)
+    };
   }
   }
 
 
   return {
   return {
     detail: { html },
     detail: { html },
     deps: {
     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 {
   return {
     detail: { ...data, href, content: html, fileOutputPath },
     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)) {
   if (!templateCache.has(templateName)) {
     const templatePath = await firstFound(
     const templatePath = await firstFound(
       config.templateDirs,
       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, {
     templateCache.set(templateName, {
       path: templatePath,
       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({
   const html = template.renderer({
     ...data,
     ...data,
     ...meta,
     ...meta,
     href,
     href,
-    content: renderer(markdown),
-  })
+    content: renderer(markdown)
+  });
   const minifiedHtml = await minify(html, {
   const minifiedHtml = await minify(html, {
     collapseWhitespace: true,
     collapseWhitespace: true,
     removeComments: true,
     removeComments: true,
     removeRedundantAttributes: true,
     removeRedundantAttributes: true,
     removeEmptyAttributes: true,
     removeEmptyAttributes: true,
     minifyCSS: true,
     minifyCSS: true,
-    minifyJS: true,
-  })
+    minifyJS: true
+  });
 
 
-  await writeFile(fileOutputPath, minifiedHtml)
+  await writeFile(fileOutputPath, minifiedHtml);
 
 
   return {
   return {
     detail: { ...data, href },
     detail: { ...data, href },
     paths: [fileOutputPath],
     paths: [fileOutputPath],
     deps: {
     deps: {
-      paths: [template.path],
+      paths: [template.path]
     },
     },
-    ref: slugifyString(fileOutputPath),
-  }
+    ref: slugifyString(fileOutputPath)
+  };
 }
 }
 
 
 export async function compileSass({ config, meta }) {
 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 {
   return {
     paths: [fileOutputPath],
     paths: [fileOutputPath],
     ref: slugifyString(fileOutputPath),
     ref: slugifyString(fileOutputPath),
     detail: {
     detail: {
-      href: fileOutputPath.replace(meta.opts.outDir, ""),
+      href: fileOutputPath.replace(meta.opts.outDir, "")
     },
     },
     deps: {
     deps: {
-      paths: [...result.loadedUrls.map(item => item.pathname)],
-    },
-  }
+      paths: [...result.loadedUrls.map(item => item.pathname)]
+    }
+  };
 }
 }
 
 
 export async function optimiseSvg({ config }) {
 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, {
   const result = optimize(svgString, {
-    plugins: ["preset-default"],
-  })
-  await writeFile(fileOutputPath, result.data)
+    plugins: ["preset-default"]
+  });
+  await writeFile(fileOutputPath, result.data);
   return {
   return {
     paths: [fileOutputPath],
     paths: [fileOutputPath],
-    ref: slugifyString(fileOutputPath),
-  }
+    ref: slugifyString(fileOutputPath)
+  };
 }
 }
 
 
 export async function copy({ config }) {
 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 {
   return {
     paths: [fileOutputPath],
     paths: [fileOutputPath],
-    ref: slugifyString(fileOutputPath),
-  }
+    ref: slugifyString(fileOutputPath)
+  };
 }
 }
 
 
 export async function imageToWebP({ meta, config }) {
 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) {
   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(
   const srcSet = await Promise.all(
     config.imageSizes.map(async size => {
     config.imageSizes.map(async size => {
-      const sizeNum = parseInt(size.replace("w", ""), 10)
+      const sizeNum = parseInt(size.replace("w", ""), 10);
       const outputFile = path.join(
       const outputFile = path.join(
         fileOutputDir,
         fileOutputDir,
-        `${name}-${sizeNum}${outputExtension}`,
-      )
+        `${name}-${sizeNum}${outputExtension}`
+      );
 
 
       await original
       await original
         .clone()
         .clone()
         .resize(sizeNum)
         .resize(sizeNum)
         .webp({ quality: config.quality })
         .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 {
   return {
     paths: srcSet.map(src => src[0]),
     paths: srcSet.map(src => src[0]),
     detail: { srcSet, aspectRatio },
     detail: { srcSet, aspectRatio },
-    ref: imageRef,
-  }
+    ref: imageRef
+  };
 }
 }
 
 
 export async function generateFavicons({ meta, config }) {
 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
   // Configuration for favicons package
   const configuration = {
   const configuration = {
     path: getCleanPath(fileOutputDir, meta), // Path for overriding default icons path
     path: getCleanPath(fileOutputDir, meta), // Path for overriding default icons path
@@ -309,45 +296,45 @@ export async function generateFavicons({ meta, config }) {
       appleStartup: true,
       appleStartup: true,
       favicons: true,
       favicons: true,
       windows: true,
       windows: true,
-      yandex: true,
-    },
-  }
+      yandex: true
+    }
+  };
   try {
   try {
-    const response = await favicons(filePath, configuration)
+    const response = await favicons(filePath, configuration);
 
 
     // Write all generated images to disk
     // Write all generated images to disk
     await Promise.all(
     await Promise.all(
       response.images.map(async image => {
       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
     // Write all generated files (manifests, etc.) to disk
     await Promise.all(
     await Promise.all(
       response.files.map(async file => {
       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
     // Combine HTML meta tags
-    const htmlMeta = response.html.join("\n    ")
+    const htmlMeta = response.html.join("\n    ");
     return {
     return {
       detail: {
       detail: {
-        htmlMeta,
+        htmlMeta
       },
       },
       paths: [
       paths: [
         ...response.images.map(img =>
         ...response.images.map(img =>
-          getCleanPath(path.join(fileOutputDir, img.name), meta),
+          getCleanPath(path.join(fileOutputDir, img.name), meta)
         ),
         ),
         ...response.files.map(file =>
         ...response.files.map(file =>
-          getCleanPath(path.join(fileOutputDir, file.name), meta),
-        ),
+          getCleanPath(path.join(fileOutputDir, file.name), meta)
+        )
       ],
       ],
-      ref: "metatags",
-    }
+      ref: "metatags"
+    };
   } catch (error) {
   } 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 fs from "node:fs/promises"
 import path from "path"
 import path from "path"
+import os from "os"
 import { glob } from "glob"
 import { glob } from "glob"
 
 
 export async function fileExists(filePath) {
 export async function fileExists(filePath) {
@@ -44,7 +45,7 @@ export async function readFilesByGlob(globConfigs) {
         ignore: [],
         ignore: [],
         ...globConfig,
         ...globConfig,
       }
       }
-      const matches = await glob(pattern, {
+      const matches = await glob(expandTilde(pattern), {
         ignore,
         ignore,
         dot,
         dot,
       })
       })
@@ -56,6 +57,11 @@ export async function readFilesByGlob(globConfigs) {
   return [...new Set(files)]
   return [...new Set(files)]
 }
 }
 
 
+export function expandTilde(path) {
+  if (!path.startsWith("~")) return path
+  return path.replace(/^~(?=$|\/|\\)/, os.homedir())
+}
+
 export async function checkFilesExist(files, baseDir) {
 export async function checkFilesExist(files, baseDir) {
   const filesToCheck = Array.isArray(files) ? files : [files]
   const filesToCheck = Array.isArray(files) ? files : [files]
   const fileCheckResults = await Promise.all(
   const fileCheckResults = await Promise.all(
@@ -65,9 +71,14 @@ export async function checkFilesExist(files, baseDir) {
       return { filePath, exists }
       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) {
 export async function writeFile(filePath, content) {