@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 +1 -0
- package/lib/content.js +62 -1
- package/lib/plugins.js +52 -10
- package/lib/server.js +2 -1
- package/package.json +1 -1
package/lib/build.js
CHANGED
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
//
|
|
95
|
-
|
|
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
|
-
|
|
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, {
|