@terrymooreii/sia 2.3.0 → 2.3.2

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/lib/build.js CHANGED
@@ -236,6 +236,7 @@ export async function build(options = {}) {
236
236
  // Load plugins
237
237
  let plugins = [];
238
238
  try {
239
+ console.log('🔌 Loading plugins...');
239
240
  plugins = await loadPlugins(config);
240
241
 
241
242
  // Register plugin hooks
package/lib/content.js CHANGED
@@ -402,10 +402,66 @@ export function getDateFromFilename(filename) {
402
402
  return null;
403
403
  }
404
404
 
405
+ /**
406
+ * Convert relative image and link paths to absolute paths based on base URL
407
+ * @param {string} html - HTML content with potential relative paths
408
+ * @param {string} baseUrl - Base URL for the content item (e.g., '/blog/my-post/')
409
+ * @returns {string} HTML with absolute paths
410
+ */
411
+ function fixRelativePaths(html, baseUrl) {
412
+ if (!html || !baseUrl) return html;
413
+
414
+ // Normalize baseUrl to ensure it ends with a slash
415
+ const normalizedBaseUrl = baseUrl.endsWith('/') ? baseUrl : baseUrl + '/';
416
+
417
+ // Helper function to convert relative path to absolute
418
+ const makeAbsolute = (path) => {
419
+ // Skip if it's already an absolute URL (http://, https://) or absolute path (/)
420
+ if (/^(https?:|\/|#|mailto:)/.test(path)) {
421
+ return path;
422
+ }
423
+
424
+ // Convert relative path to absolute path
425
+ // Remove leading ./ if present
426
+ const cleanPath = path.replace(/^\.\//, '');
427
+
428
+ // Combine base URL with path
429
+ return normalizedBaseUrl + cleanPath;
430
+ };
431
+
432
+ // Fix relative image paths
433
+ html = html.replace(
434
+ /<img\s+([^>]*?)(?:src\s*=\s*(["'])([^"']+)\2)([^>]*)>/gi,
435
+ (match, beforeAttrs, quote, src, afterAttrs) => {
436
+ const absolutePath = makeAbsolute(src);
437
+
438
+ // Reconstruct the img tag with the absolute path
439
+ const before = beforeAttrs ? beforeAttrs.trim() + ' ' : '';
440
+ const after = afterAttrs ? ' ' + afterAttrs.trim() : '';
441
+ return `<img ${before}src=${quote}${absolutePath}${quote}${after}>`;
442
+ }
443
+ );
444
+
445
+ // Fix relative link paths (but skip anchor links and external URLs)
446
+ html = html.replace(
447
+ /<a\s+([^>]*?)(?:href\s*=\s*(["'])([^"']+)\2)([^>]*)>/gi,
448
+ (match, beforeAttrs, quote, href, afterAttrs) => {
449
+ const absolutePath = makeAbsolute(href);
450
+
451
+ // Reconstruct the link tag with the absolute path
452
+ const before = beforeAttrs ? beforeAttrs.trim() + ' ' : '';
453
+ const after = afterAttrs ? ' ' + afterAttrs.trim() : '';
454
+ return `<a ${before}href=${quote}${absolutePath}${quote}${after}>`;
455
+ }
456
+ );
457
+
458
+ return html;
459
+ }
460
+
405
461
  /**
406
462
  * Parse a markdown file with front matter
407
463
  */
408
- export async function parseContent(filePath) {
464
+ export async function parseContent(filePath, options = {}) {
409
465
  let content = readFileSync(filePath, 'utf-8');
410
466
 
411
467
  // Execute beforeParse hook
@@ -546,6 +602,11 @@ export async function loadCollection(config, collectionName) {
546
602
  item.url = basePath + permalink;
547
603
  item.outputPath = join(config.outputDir, permalink, 'index.html');
548
604
 
605
+ // Fix relative image and link paths in content and excerptHtml
606
+ // This ensures images and links work on both individual pages and list pages
607
+ item.content = fixRelativePaths(item.content, item.url);
608
+ item.excerptHtml = fixRelativePaths(item.excerptHtml, item.url);
609
+
549
610
  return item;
550
611
  } catch (err) {
551
612
  console.error(`Error parsing ${filePath}:`, err.message);
package/lib/plugins.js CHANGED
@@ -45,13 +45,21 @@ export function validatePlugin(plugin, pluginPath) {
45
45
  export async function loadPlugin(pluginPath, config) {
46
46
  try {
47
47
  // Support both .js and .mjs files
48
+ // Add cache busting query parameter to ensure fresh reloads during dev
49
+ // This prevents Node.js from using cached versions of plugins during hot reload
50
+ const cacheBuster = `?t=${Date.now()}`;
48
51
  let module;
49
52
 
50
- if (pluginPath.endsWith('.mjs')) {
51
- module = await import(`file://${pluginPath}`);
52
- } else {
53
- module = await import(pluginPath);
54
- }
53
+ // Normalize path to use file:// protocol for consistent cache busting
54
+ // All plugin paths from discovery are absolute, so we can safely use file://
55
+ const normalizedPath = pluginPath.startsWith('file://')
56
+ ? pluginPath
57
+ : pluginPath.startsWith('/') || /^[A-Za-z]:/.test(pluginPath) // Unix absolute or Windows drive
58
+ ? `file://${pluginPath}`
59
+ : pluginPath; // Relative path (shouldn't happen, but handle it)
60
+
61
+ // Import with cache busting to ensure fresh reloads during dev
62
+ module = await import(`${normalizedPath}${cacheBuster}`);
55
63
 
56
64
  // Support both default export and named export
57
65
  const plugin = module.default || module;
@@ -86,18 +94,31 @@ export function discoverLocalPlugins(rootDir) {
86
94
 
87
95
  try {
88
96
  const items = readdirSync(pluginsDir);
97
+ console.log(`🔍 Scanning _plugins directory: found ${items.length} item(s)`);
89
98
 
90
99
  for (const item of items) {
91
100
  const itemPath = join(pluginsDir, item);
92
101
  const stat = statSync(itemPath);
93
102
 
94
- // Only process .js and .mjs files
95
- if (stat.isFile() && (item.endsWith('.js') || item.endsWith('.mjs'))) {
103
+ // Check extension case-insensitively
104
+ const lowerItem = item.toLowerCase();
105
+ const isJsFile = lowerItem.endsWith('.js') || lowerItem.endsWith('.mjs');
106
+
107
+ // Debug: log what we found
108
+ if (stat.isFile()) {
109
+ console.log(` 📄 Found file: ${item} (${isJsFile ? 'plugin candidate' : 'skipped - not .js/.mjs'})`);
110
+ } else if (stat.isDirectory()) {
111
+ console.log(` 📁 Found directory: ${item} (skipped - plugins must be files)`);
112
+ }
113
+
114
+ // Only process .js and .mjs files (case-insensitive)
115
+ if (stat.isFile() && isJsFile) {
96
116
  plugins.push({
97
117
  type: 'local',
98
118
  path: itemPath,
99
- name: item.replace(/\.(js|mjs)$/, '')
119
+ name: item.replace(/\.(js|mjs)$/i, '') // Case-insensitive replacement
100
120
  });
121
+ console.log(` ✓ Added plugin: ${item}`);
101
122
  }
102
123
  }
103
124
  } catch (err) {
@@ -250,9 +271,15 @@ export async function discoverPlugins(config) {
250
271
 
251
272
  // Filter by explicit plugin list if provided
252
273
  const explicitPlugins = config.plugins?.plugins;
253
- if (explicitPlugins && Array.isArray(explicitPlugins)) {
274
+ if (explicitPlugins && Array.isArray(explicitPlugins) && explicitPlugins.length > 0) {
275
+ console.log(`🔍 Filtering plugins: only loading ${explicitPlugins.join(', ')}`);
254
276
  const explicitSet = new Set(explicitPlugins);
255
- return discovered.filter(p => explicitSet.has(p.name));
277
+ const filtered = discovered.filter(p => explicitSet.has(p.name));
278
+ const filteredOut = discovered.filter(p => !explicitSet.has(p.name));
279
+ if (filteredOut.length > 0) {
280
+ console.log(` ⚠️ Filtered out ${filteredOut.length} plugin(s): ${filteredOut.map(p => p.name).join(', ')}`);
281
+ }
282
+ return filtered;
256
283
  }
257
284
 
258
285
  return discovered;
@@ -262,9 +289,24 @@ export async function discoverPlugins(config) {
262
289
  * Load all discovered plugins
263
290
  */
264
291
  export async function loadPlugins(config) {
292
+ // Check if plugins are disabled
293
+ if (config.plugins?.enabled === false) {
294
+ console.log('🔌 Plugins are disabled in config');
295
+ return [];
296
+ }
297
+
298
+ const rootDir = config.rootDir || process.cwd();
265
299
  const discovered = await discoverPlugins(config);
266
300
 
267
301
  if (discovered.length === 0) {
302
+ // Log that we checked but found nothing (helps with debugging)
303
+ const pluginsDir = join(rootDir, '_plugins');
304
+ const hasPluginsDir = existsSync(pluginsDir);
305
+ if (hasPluginsDir) {
306
+ console.log('🔌 No plugins discovered (directory exists but no valid plugins found)');
307
+ } else {
308
+ console.log('🔌 No plugins directory found, skipping plugin discovery');
309
+ }
268
310
  return [];
269
311
  }
270
312
 
package/lib/server.js CHANGED
@@ -166,7 +166,8 @@ function setupWatcher(config, wss) {
166
166
  config.includesDir,
167
167
  join(config.rootDir, '_config.yml'),
168
168
  join(config.rootDir, '_config.json'),
169
- join(config.rootDir, 'styles')
169
+ join(config.rootDir, 'styles'),
170
+ join(config.rootDir, '_plugins')
170
171
  ].filter(p => existsSync(p));
171
172
 
172
173
  const watcher = chokidar.watch(watchPaths, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@terrymooreii/sia",
3
- "version": "2.3.0",
3
+ "version": "2.3.2",
4
4
  "description": "A simple, powerful static site generator with markdown, front matter, and Nunjucks templates",
5
5
  "main": "lib/index.js",
6
6
  "bin": {