@daz4126/swifty 2.0.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,10 +11,10 @@ It also uses convention over configuration to make is super simple to build site
11
11
  ## Quickstart
12
12
 
13
13
  1. `npm install @daz4126/swifty`
14
- 2. `npx swifty init`
14
+ 2. `npx swifty init` to create a new site
15
+ 3. Edit the `template.html` file to match your default layout
16
+ 4. Change the `sitename` in `config.yaml`
17
+ 5. Add some markdown files to the 'pages' directory
15
18
  3. `npx swifty build` to build the site
16
- 4. Edit the `template.html` file to match your default layout
17
- 5. Change the `sitename` in `config.yaml`
18
- 6. Add some markdown files to the 'pages' directory
19
- 7. `npx swifty start` to rebuild and start the server
19
+ 7. `npx swifty start` to start the server
20
20
  8. Visit [http://localhost:3000](http://localhost:3000) to see your site
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daz4126/swifty",
3
- "version": "2.0.1",
3
+ "version": "2.2.0",
4
4
  "main": "index.js",
5
5
  "type": "module",
6
6
  "bin": {
package/src/assets.js CHANGED
@@ -5,10 +5,10 @@ import sharp from "sharp";
5
5
  import { dirs, defaultConfig } from "./config.js";
6
6
 
7
7
  const validExtensions = {
8
- css: [".css"],
9
- js: [".js"],
10
- images: [".png", ".jpg", ".jpeg", ".gif", ".svg"," .webp"],
11
- };
8
+ css: [".css"],
9
+ js: [".js"],
10
+ images: [".png", ".jpg", ".jpeg", ".gif", ".svg", " .webp"],
11
+ };
12
12
 
13
13
  const ensureAndCopy = async (source, destination, validExts) => {
14
14
  if (await fsExtra.pathExists(source)) {
@@ -17,64 +17,89 @@ const ensureAndCopy = async (source, destination, validExts) => {
17
17
  const files = await fs.readdir(source);
18
18
  await Promise.all(
19
19
  files
20
- .filter(file => validExts.includes(path.extname(file).toLowerCase()))
21
- .map(file => fsExtra.copy(path.join(source, file), path.join(destination, file)))
20
+ .filter((file) => validExts.includes(path.extname(file).toLowerCase()))
21
+ .map((file) =>
22
+ fsExtra.copy(path.join(source, file), path.join(destination, file)),
23
+ ),
22
24
  );
23
25
  console.log(`Copied valid files from ${source} to ${destination}`);
24
26
  } else {
25
27
  console.log(`No ${path.basename(source)} found in ${source}`);
26
28
  }
27
29
  };
28
- const copyAssets = async () => {
29
- await ensureAndCopy(dirs.css, path.join(dirs.dist, 'css'), validExtensions.css);
30
- await ensureAndCopy(dirs.js, path.join(dirs.dist, 'js'), validExtensions.js);
31
- await ensureAndCopy(dirs.images, path.join(dirs.dist, 'images'), validExtensions.images);
30
+ const copyAssets = async (outputDir = dirs.dist) => {
31
+ await ensureAndCopy(
32
+ dirs.css,
33
+ path.join(outputDir, "css"),
34
+ validExtensions.css,
35
+ );
36
+ await ensureAndCopy(dirs.js, path.join(outputDir, "js"), validExtensions.js);
37
+ await ensureAndCopy(
38
+ dirs.images,
39
+ path.join(outputDir, "images"),
40
+ validExtensions.images,
41
+ );
32
42
  };
33
- async function optimizeImages() {
43
+ async function optimizeImages(outputDir = dirs.dist) {
34
44
  try {
35
- const IMAGE_EXTENSIONS = ['.jpg', '.jpeg', '.png'];
36
- const images_folder = path.join(dirs.dist, "images");
45
+ const IMAGE_EXTENSIONS = [".jpg", ".jpeg", ".png"];
46
+ const images_folder = path.join(outputDir, "images");
37
47
  const files = await fs.readdir(images_folder);
38
-
39
- await Promise.all(files.map(async (file) => {
40
- const filePath = path.join(images_folder, file);
41
- const ext = path.extname(file).toLowerCase();
42
-
43
- if (!IMAGE_EXTENSIONS.includes(ext)) return;
44
-
45
- const optimizedPath = path.join(images_folder, `${path.basename(file, ext)}.webp`);
46
-
47
- if (filePath !== optimizedPath) {
48
- const image = sharp(filePath);
49
- const metadata = await image.metadata();
50
- const originalWidth = metadata.width || 0;
51
- const maxWidth = defaultConfig.max_image_size || 800;
52
- const resizeWidth = Math.min(originalWidth, maxWidth);
53
-
54
- await image
55
- .resize({ width: resizeWidth })
56
- .toFormat('webp', { quality: 80 })
57
- .toFile(optimizedPath);
58
-
59
- await fs.unlink(filePath);
60
-
61
- console.log(`Optimized ${file} -> ${optimizedPath}`);
62
- }
63
- }));
48
+
49
+ await Promise.all(
50
+ files.map(async (file) => {
51
+ const filePath = path.join(images_folder, file);
52
+ const ext = path.extname(file).toLowerCase();
53
+
54
+ if (!IMAGE_EXTENSIONS.includes(ext)) return;
55
+
56
+ const optimizedPath = path.join(
57
+ images_folder,
58
+ `${path.basename(file, ext)}.webp`,
59
+ );
60
+
61
+ if (filePath !== optimizedPath) {
62
+ const image = sharp(filePath);
63
+ const metadata = await image.metadata();
64
+ const originalWidth = metadata.width || 0;
65
+ const maxWidth = defaultConfig.max_image_size || 800;
66
+ const resizeWidth = Math.min(originalWidth, maxWidth);
67
+
68
+ await image
69
+ .resize({ width: resizeWidth })
70
+ .toFormat("webp", { quality: 80 })
71
+ .toFile(optimizedPath);
72
+
73
+ await fs.unlink(filePath);
74
+
75
+ console.log(`Optimized ${file} -> ${optimizedPath}`);
76
+ }
77
+ }),
78
+ );
64
79
  } catch (error) {
65
- console.error('Error optimizing images:', error);
80
+ console.error("Error optimizing images:", error);
66
81
  }
67
- };
82
+ }
68
83
  const generateAssetImports = async (dir, tagTemplate, validExts) => {
69
- if (!(await fsExtra.pathExists(dir))) return '';
84
+ if (!(await fsExtra.pathExists(dir))) return "";
70
85
  const files = await fs.readdir(dir);
71
86
  return files
72
- .filter(file => validExts.includes(path.extname(file).toLowerCase()))
73
- .sort()
74
- .map(file => tagTemplate(file))
75
- .join('\n');
87
+ .filter((file) => validExts.includes(path.extname(file).toLowerCase()))
88
+ .sort()
89
+ .map((file) => tagTemplate(file))
90
+ .join("\n");
76
91
  };
77
- const getCssImports = () => generateAssetImports(dirs.css, (file) => `<link rel="stylesheet" href="/css/${file}" />`, validExtensions.css);
78
- const getJsImports = () => generateAssetImports(dirs.js, (file) => `<script src="/js/${file}"></script>`, validExtensions.js);
92
+ const getCssImports = () =>
93
+ generateAssetImports(
94
+ dirs.css,
95
+ (file) => `<link rel="stylesheet" href="/css/${file}" />`,
96
+ validExtensions.css,
97
+ );
98
+ const getJsImports = () =>
99
+ generateAssetImports(
100
+ dirs.js,
101
+ (file) => `<script src="/js/${file}"></script>`,
102
+ validExtensions.js,
103
+ );
79
104
 
80
- export { copyAssets, optimizeImages, getCssImports, getJsImports };
105
+ export { copyAssets, optimizeImages, getCssImports, getJsImports };
package/src/build.js CHANGED
@@ -2,12 +2,10 @@ import { copyAssets, optimizeImages } from "./assets.js";
2
2
  import { generatePages, createPages, addLinks } from "./pages.js";
3
3
  import { dirs } from "./config.js";
4
4
 
5
- async function buildSite() {
6
- await copyAssets();
7
- await optimizeImages();
5
+ export default async function build(outputDir) {
6
+ await copyAssets(outputDir);
7
+ await optimizeImages(outputDir);
8
8
  const pages = await generatePages(dirs.pages);
9
9
  await addLinks(pages);
10
- await createPages(pages);
10
+ await createPages(pages, outputDir);
11
11
  }
12
-
13
- buildSite();
package/src/cli.js CHANGED
@@ -1,32 +1,60 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import { spawn } from "child_process";
4
+
3
5
  const args = process.argv.slice(2);
4
- const command = args[0];
5
- let outDir = 'dist'; // default
6
+ let outDir = "dist"; // default
6
7
 
7
8
  // Look for --out [folder]
8
- const outIndex = args.indexOf('--out');
9
+ const outIndex = args.indexOf("--out");
9
10
  if (outIndex !== -1 && args[outIndex + 1]) {
10
11
  outDir = args[outIndex + 1];
11
12
  }
12
13
 
13
- // Pass outDir as an environment variable
14
+ // Pass outDir as an environment variable too (optional, still useful)
14
15
  process.env.OUT_DIR = outDir;
15
16
 
16
- switch (command) {
17
- case "init":
18
- import('./init.js');
19
- break;
20
- case "build":
21
- import('./build.js');
22
- break;
23
- case "start":
24
- import('./build.js').then(async () => {
25
- const { execSync } = await import('child_process');
26
- execSync(`npx serve ${process.env.OUT_DIR}`, { stdio: 'inherit' });
27
- });
28
- break;
29
- default:
30
- console.log(`Unknown command: ${command}`);
31
- console.log(`Usage: swifty [init|build|start] [--out folder]`);
17
+ async function main() {
18
+ const command = args[0];
19
+
20
+ switch (command) {
21
+ case "init":
22
+ await import("./init.js");
23
+ break;
24
+ case "build": {
25
+ const build = await import("./build.js");
26
+ if (typeof build.default === "function") {
27
+ await build.default(outDir);
28
+ }
29
+ break;
30
+ }
31
+ case "start": {
32
+ const build = await import("./build.js");
33
+ if (typeof build.default === "function") {
34
+ await build.default(outDir);
35
+ }
36
+
37
+ // Run watcher.js (non-blocking)
38
+ const watcher = await import("./watcher.js");
39
+ if (typeof watcher.default === "function") {
40
+ watcher.default(outDir);
41
+ }
42
+
43
+ // Start the server (blocking)
44
+ spawn("npx", ["serve", outDir], { stdio: "inherit" });
45
+ break;
46
+ }
47
+ case "watch": {
48
+ const watch = await import("./watcher.js");
49
+ if (typeof watch.default === "function") {
50
+ await watch.default(outDir);
51
+ }
52
+ break;
53
+ }
54
+ default:
55
+ console.log(`Unknown command: ${command}`);
56
+ console.log(`Usage: swifty [init|build|start|watch] [--out folder]`);
57
+ }
32
58
  }
59
+
60
+ main();
package/src/init.js CHANGED
@@ -22,6 +22,8 @@ breadcrumb_separator: "&raquo;"
22
22
  breadcrumb_class: swifty_breadcrumb
23
23
  link_class: swifty_link
24
24
  tag_class: tag
25
+ default_layout_name: site
26
+ default_link_name: links
25
27
  max_image_size: 800
26
28
 
27
29
  dateFormat:
@@ -34,6 +36,12 @@ dateFormat:
34
36
  <head>
35
37
  <meta charset="UTF-8">
36
38
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
39
+ <link rel="icon" href="favicon.ico" sizes="48x48" type="image/x-icon">
40
+ <link rel="icon" href="favicon-16x16.png" sizes="16x16" type="image/x-icon">
41
+ <link rel="icon" href="favicon-32x32.png" sizes="32x32" type="image/png">
42
+ <link rel="apple-touch-icon" href="path/to/apple-touch-icon.png">
43
+ <link rel="icon" sizes="192x192" href="android-chrome-192x19.png">
44
+ <link rel="icon" sizes="512x512" href="android-chrome-512x512.png">
37
45
  <title>{{sitename}}</title>
38
46
  </head>
39
47
  <body>
package/src/pages.js CHANGED
@@ -11,7 +11,7 @@ import path from "path";
11
11
  const isValid = async (filePath) => {
12
12
  try {
13
13
  const stats = await fs.stat(filePath);
14
- return stats.isDirectory() || path.extname(filePath) === '.md';
14
+ return stats.isDirectory() || path.extname(filePath) === ".md";
15
15
  } catch (err) {
16
16
  return false; // Handle errors like file not found
17
17
  }
@@ -43,24 +43,46 @@ const generatePages = async (sourceDir, baseDir = sourceDir, parent) => {
43
43
  const root = file.name === "index.md" && !parent;
44
44
  const relativePath = path.relative(baseDir, filePath).replace(/\\/g, "/");
45
45
  const finalPath = `/${relativePath.replace(/\.md$/, "")}`;
46
- const name = root ? "Home" : capitalize(file.name.replace(/\.md$/, "").replace(/-/g, " "));
46
+ const name = root
47
+ ? "Home"
48
+ : capitalize(file.name.replace(/\.md$/, "").replace(/-/g, " "));
47
49
  const stats = await fs.stat(filePath);
48
50
  const isDirectory = file.isDirectory();
49
- const layoutFileExists = parent && await fsExtra.pathExists(`${dirs.layouts}/${parent.filename}.html`);
50
- const layout = layoutFileExists ? parent.filename : parent ? parent.layout : "pages";
51
+ const layoutFileExists =
52
+ parent &&
53
+ (await fsExtra.pathExists(`${dirs.layouts}/${parent.filename}.html`));
54
+ const layout = layoutFileExists
55
+ ? parent.filename
56
+ : parent
57
+ ? parent.layout
58
+ : config.default_layout_name;
51
59
 
52
60
  const page = {
53
- name, root, layout, filePath,
61
+ name,
62
+ root,
63
+ layout,
64
+ filePath,
54
65
  filename: file.name.replace(/\.md$/, ""),
55
66
  url: root ? "/" : finalPath,
56
67
  nav: !parent && !root,
57
- parent: parent ? { title: parent.data.title, url: parent.url } : undefined,
68
+ parent: parent
69
+ ? { title: parent.data.title, url: parent.url }
70
+ : undefined,
58
71
  folder: isDirectory,
59
72
  title: name,
60
- created_at: new Date(stats.birthtime).toLocaleDateString(undefined, config.dateFormat),
61
- updated_at: new Date(stats.mtime).toLocaleDateString(undefined, config.dateFormat),
62
- date: new Date(stats.mtime).toLocaleDateString(undefined, config.dateFormat),
63
- data: root ? { ...defaultConfig } : { ...config }
73
+ created_at: new Date(stats.birthtime).toLocaleDateString(
74
+ undefined,
75
+ config.dateFormat,
76
+ ),
77
+ updated_at: new Date(stats.mtime).toLocaleDateString(
78
+ undefined,
79
+ config.dateFormat,
80
+ ),
81
+ date: new Date(stats.mtime).toLocaleDateString(
82
+ undefined,
83
+ config.dateFormat,
84
+ ),
85
+ data: root ? { ...defaultConfig } : { ...config },
64
86
  };
65
87
 
66
88
  if (path.extname(file.name) === ".md") {
@@ -97,7 +119,7 @@ const generatePages = async (sourceDir, baseDir = sourceDir, parent) => {
97
119
 
98
120
  // Add tags
99
121
  if (page.data.tags) {
100
- page.data.tags.forEach(tag => addToTagMap(tag, page));
122
+ page.data.tags.forEach((tag) => addToTagMap(tag, page));
101
123
  }
102
124
 
103
125
  pages.push(page);
@@ -106,7 +128,6 @@ const generatePages = async (sourceDir, baseDir = sourceDir, parent) => {
106
128
 
107
129
  // Await all directory recursion
108
130
  await Promise.all(directoryPromises);
109
-
110
131
  } catch (err) {
111
132
  console.error("Error reading directory:", err);
112
133
  }
@@ -121,23 +142,29 @@ const generatePages = async (sourceDir, baseDir = sourceDir, parent) => {
121
142
  name: "Tags",
122
143
  title: "All Tags",
123
144
  layout: "pages",
124
- updated_at: new Date().toLocaleDateString(undefined, defaultConfig.dateFormat),
145
+ updated_at: new Date().toLocaleDateString(
146
+ undefined,
147
+ defaultConfig.dateFormat,
148
+ ),
125
149
  data: { ...config },
126
- pages: []
150
+ pages: [],
127
151
  };
128
152
 
129
153
  for (const [tag, pagesForTag] of tagsMap) {
130
154
  const page = {
131
155
  name: tag,
132
156
  title: tag,
133
- updated_at: new Date().toLocaleDateString(undefined, defaultConfig.dateFormat),
157
+ updated_at: new Date().toLocaleDateString(
158
+ undefined,
159
+ defaultConfig.dateFormat,
160
+ ),
134
161
  url: `/tags/${tag}`,
135
162
  layout: tagLayout ? "tags" : "pages",
136
163
  data: { ...config, title: `Pages tagged with ${capitalize(tag)}` },
137
164
  };
138
165
  page.content = pagesForTag
139
- .map(p => `* <a href="${p.url}">${p.title}</a>`)
140
- .join('\n');
166
+ .map((p) => `* <a href="${p.url}">${p.title}</a>`)
167
+ .join("\n");
141
168
 
142
169
  tagPage.pages.push(page);
143
170
  }
@@ -148,61 +175,87 @@ const generatePages = async (sourceDir, baseDir = sourceDir, parent) => {
148
175
  return pages;
149
176
  };
150
177
 
151
-
152
- const generateLinkList = async (name,pages) => {
178
+ const generateLinkList = async (name, pages) => {
153
179
  const partial = `${name}.md`;
154
180
  const partialPath = path.join(dirs.partials, partial);
155
- const linksPath = path.join(dirs.partials, "links.md");
181
+ const linksPath = path.join(dirs.partials, defaultConfig.default_link_name);
156
182
  // Check if either file exists in the 'partials' folder
157
183
  const fileExists = await fsExtra.pathExists(partialPath);
158
184
  const defaultExists = await fsExtra.pathExists(linksPath);
159
185
  if (fileExists || defaultExists) {
160
- const partial = await fs.readFile(fileExists ? partialPath : linksPath, "utf-8");
161
- const content = await Promise.all(pages.map(page => replacePlaceholders(partial, page)));
162
- return content.join('\n');
186
+ const partial = await fs.readFile(
187
+ fileExists ? partialPath : linksPath,
188
+ "utf-8",
189
+ );
190
+ const content = await Promise.all(
191
+ pages.map((page) => replacePlaceholders(partial, page)),
192
+ );
193
+ return content.join("\n");
163
194
  } else {
164
- return `${pages.map(page => `<li><a href="${page.url}" class="${defaultConfig.link_class}">${page.title}</a></li>`).join`\n`}`
195
+ return `${pages.map((page) => `<li><a href="${page.url}" class="${defaultConfig.link_class}">${page.title}</a></li>`).join`\n`}`;
165
196
  }
166
197
  };
167
198
 
168
- const render = async page => {
169
- const htmlContent = marked.parse(page.content); // Markdown processed once
199
+ const render = async (page) => {
200
+ const replacedContent = await replacePlaceholders(page.content, page);
201
+ const htmlContent = marked.parse(replacedContent); // Markdown processed once
170
202
  const wrappedContent = await applyLayoutAndWrapContent(page, htmlContent);
171
- const htmlWithTemplate = template.replace(/\{\{\s*content\s*\}\}/g, wrappedContent);
203
+ const htmlWithTemplate = template.replace(
204
+ /\{\{\s*content\s*\}\}/g,
205
+ wrappedContent,
206
+ );
172
207
  const finalContent = await replacePlaceholders(htmlWithTemplate, page);
173
208
  return finalContent;
174
209
  };
175
210
 
176
211
  const createPages = async (pages, distDir = dirs.dist) => {
177
- await Promise.all(pages.map(async (page) => {
178
- const html = await render(page);
179
- const pageDir = path.join(distDir, page.url);
180
- const pagePath = path.join(distDir, page.url, "index.html");
181
-
182
- await fsExtra.ensureDir(pageDir);
183
- await fs.writeFile(pagePath, html);
184
- console.log(`Created file: ${pagePath}`);
185
-
186
- if (page.folder) {
187
- await createPages(page.pages, distDir); // Recursive still needs to await
188
- }
189
- }));
212
+ await Promise.all(
213
+ pages.map(async (page) => {
214
+ const html = await render(page);
215
+ const pageDir = path.join(distDir, page.url);
216
+ const pagePath = path.join(distDir, page.url, "index.html");
217
+ await fsExtra.ensureDir(pageDir);
218
+ await fs.writeFile(pagePath, html);
219
+ if (page.folder) {
220
+ await createPages(page.pages, distDir); // Recursive still needs to await
221
+ }
222
+ }),
223
+ );
190
224
  };
191
225
 
192
226
  const addLinks = async (pages, parent) => {
193
227
  for (const page of pages) {
194
228
  page.data ||= {};
195
229
  page.data.links_to_tags = page?.data?.tags?.length
196
- ? page.data.tags.map(tag => `<a class="${defaultConfig.tag_class}" href="/tags/${tag}">${tag}</a>`).join`` : "";
197
- const crumb = page.root ? "" : ` ${defaultConfig.breadcrumb_separator} <a class="${defaultConfig.breadcrumb_class}" href="${page.url}">${page.name}</a>`;
198
- page.data.breadcrumbs = parent ? parent.data.breadcrumbs + crumb
199
- : `<a class="${defaultConfig.breadcrumb_class}" href="/">Home</a>` + crumb;
200
- page.data.links_to_children = page.pages ? await generateLinkList(page.filename, page.pages) : "";
201
- page.data.links_to_siblings = await generateLinkList(page.parent?.filename || "pages", pages.filter(p => p.url !== page.url));
202
- page.data.links_to_self_and_siblings = await generateLinkList(page.parent?.filename || "pages", pages);
203
- page.data.nav_links = await generateLinkList("nav", pageIndex.filter(p => p.nav));
230
+ ? page.data.tags.map(
231
+ (tag) =>
232
+ `<a class="${defaultConfig.tag_class}" href="/tags/${tag}">${tag}</a>`,
233
+ ).join``
234
+ : "";
235
+ const crumb = page.root
236
+ ? ""
237
+ : ` ${defaultConfig.breadcrumb_separator} <a class="${defaultConfig.breadcrumb_class}" href="${page.url}">${page.name}</a>`;
238
+ page.data.breadcrumbs = parent
239
+ ? parent.data.breadcrumbs + crumb
240
+ : `<a class="${defaultConfig.breadcrumb_class}" href="/">Home</a>` +
241
+ crumb;
242
+ page.data.links_to_children = page.pages
243
+ ? await generateLinkList(page.filename, page.pages)
244
+ : "";
245
+ page.data.links_to_siblings = await generateLinkList(
246
+ page.parent?.filename || "pages",
247
+ pages.filter((p) => p.url !== page.url),
248
+ );
249
+ page.data.links_to_self_and_siblings = await generateLinkList(
250
+ page.parent?.filename || "pages",
251
+ pages,
252
+ );
253
+ page.data.nav_links = await generateLinkList(
254
+ "nav",
255
+ pageIndex.filter((p) => p.nav),
256
+ );
204
257
  if (page.pages) {
205
- await addLinks(page.pages, page); // Recursive call
258
+ await addLinks(page.pages, page); // Recursive call
206
259
  }
207
260
  }
208
261
  };
package/src/partials.js CHANGED
@@ -6,7 +6,6 @@ import { marked } from "marked";
6
6
 
7
7
  const partialCache = new Map();
8
8
 
9
-
10
9
  const loadPartial = async (partialName) => {
11
10
  if (partialCache.has(partialName)) {
12
11
  return partialCache.get(partialName);
@@ -36,28 +35,39 @@ const replacePlaceholders = async (template, values) => {
36
35
  };
37
36
 
38
37
  // Replace partial includes
39
- template = await replaceAsync(template, partialRegex, async (match, partialName) => {
40
- let partialContent = await loadPartial(partialName);
41
- partialContent = await replacePlaceholders(partialContent, values); // Recursive replacement
42
- return marked(partialContent); // Convert Markdown to HTML
38
+ template = await replaceAsync(
39
+ template,
40
+ partialRegex,
41
+ async (match, partialName) => {
42
+ let partialContent = await loadPartial(partialName);
43
+ partialContent = await replacePlaceholders(partialContent, values); // Recursive replacement
44
+ return marked(partialContent); // Convert Markdown to HTML
45
+ },
46
+ );
47
+ // Replace other placeholders **only outside of code blocks**
48
+ const codeBlockRegex =
49
+ /```[\s\S]*?```|`[^`]+`|<(pre|code)[^>]*>[\s\S]*?<\/\1>/g;
50
+ const codeBlocks = [];
51
+ template = template.replace(codeBlockRegex, (match) => {
52
+ codeBlocks.push(match);
53
+ return `{{CODE_BLOCK_${codeBlocks.length - 1}}}`; // Temporary placeholder
43
54
  });
55
+ // Replace placeholders outside of code blocks
56
+ template = template.replace(/{{\s*([^}\s]+)\s*}}/g, (match, key) => {
57
+ return values.data && key in values?.data
58
+ ? values.data[key]
59
+ : key in values
60
+ ? values[key]
61
+ : match;
62
+ });
63
+ // Restore code blocks
64
+ template = template.replace(
65
+ /{{CODE_BLOCK_(\d+)}}/g,
66
+ (_, index) => codeBlocks[index],
67
+ );
44
68
  // replace image extensions with optimized extension
45
69
  template = template.replace(/\.(png|jpe?g|webp)/gi, ".webp");
46
- // Replace other placeholders **only outside of code blocks**
47
- const codeBlockRegex = /```[\s\S]*?```|`[^`]+`|<(pre|code)[^>]*>[\s\S]*?<\/\1>/g;
48
- const codeBlocks = [];
49
- template = template.replace(codeBlockRegex, match => {
50
- codeBlocks.push(match);
51
- return `{{CODE_BLOCK_${codeBlocks.length - 1}}}`; // Temporary placeholder
52
- });
53
- // Replace placeholders outside of code blocks
54
- template = template.replace(/{{\s*([^}\s]+)\s*}}/g, (match, key) => {
55
- return(values.data && key in values?.data ? values.data[key] : key in values ? values[key] : match)
56
- });
57
- // Restore code blocks
58
- template = template.replace(/{{CODE_BLOCK_(\d+)}}/g, (_, index) => codeBlocks[index]);
59
-
60
70
  return template;
61
71
  };
62
72
 
63
- export { loadPartial, replacePlaceholders };
73
+ export { loadPartial, replacePlaceholders };
package/src/watcher.js CHANGED
@@ -1,39 +1,43 @@
1
- import chokidar from 'chokidar';
2
- import { exec } from 'child_process';
1
+ import chokidar from "chokidar";
3
2
 
4
- // Define which files to watch (you can adjust based on your project structure)
5
- const filesToWatch = [
6
- 'pages/**/*.{md,html}', // Watch JavaScript and HTML files in pages directory
7
- 'layouts/**/*.{html}', // Watch JavaScript and HTML files in layouts directory
8
- 'images/**/*', // Watch all files in images directory
9
- 'css/**/*.{css}', // Watch CSS files in css directory
10
- 'js/**/*.{js}', // Watch JS files in js directory
11
- 'partials/**/*.{md,html}', // Watch JavaScript and HTML files in partials directory
12
- 'template.html', // Watch the template HTML file
13
- 'config.yaml', 'config.yml', 'config.json' // Watch YAML and JSON config files
3
+ export default async function startWatcher(outDir = "dist") {
4
+ const filesToWatch = [
5
+ "pages/**/*.{md,html}",
6
+ "layouts/**/*.{html}",
7
+ "images/**/*",
8
+ "css/**/*.{css}",
9
+ "js/**/*.{js}",
10
+ "partials/**/*.{md,html}",
11
+ "template.html",
12
+ "config.yaml",
13
+ "config.yml",
14
+ "config.json",
14
15
  ];
15
- const buildScript = 'npm run build'; // Your build script command
16
16
 
17
- // Initialize watcher
18
- const watcher = chokidar.watch(filesToWatch, {
17
+ const watcher = chokidar.watch(filesToWatch, {
19
18
  persistent: true,
20
- debounceDelay: 200 // Wait 200ms after the last change to trigger the build
21
- })
19
+ ignoreInitial: true,
20
+ awaitWriteFinish: { stabilityThreshold: 100 },
21
+ debounceDelay: 200,
22
+ });
22
23
 
23
- // Event listener for file changes
24
- watcher.on('change', path => {
25
- console.log(`File ${path} has been changed. Running build...`);
26
- exec(buildScript, (error, stdout, stderr) => {
27
- if (error) {
28
- console.error(`Error executing build: ${error.message}`);
29
- return;
30
- }
31
- if (stderr) {
32
- console.error(`stderr: ${stderr}`);
33
- return;
24
+ const buildModule = await import("./build.js");
25
+ const build = buildModule.default;
26
+
27
+ if (typeof build !== "function") {
28
+ console.error("❌ build.js does not export a default function.");
29
+ return;
30
+ }
31
+
32
+ watcher.on("change", async (path) => {
33
+ console.log(`📄 File changed: ${path}`);
34
+ try {
35
+ await build(outDir);
36
+ console.log("✅ Build completed");
37
+ } catch (error) {
38
+ console.error(`❌ Build failed: ${error.message}`);
34
39
  }
35
- console.log(stdout); // Output from build process
36
40
  });
37
- });
38
41
 
39
- console.log(`Watching files for changes ...`);
42
+ console.log(`👀 Watching for changes in ${outDir}...`);
43
+ }