metalsmith-markdown-partials 2.4.0 → 2.5.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/src/index.js DELETED
@@ -1,187 +0,0 @@
1
- /**
2
- * A Metalsmith plugin to merge markdown partials into main markdown files.
3
- *
4
- * @module metalsmith-markdown-partials
5
- */
6
-
7
- /**
8
- * @typedef {Object} Options
9
- * @property {String} libraryPath - Path to the markdown partials library (defaults to './src/content/md-library/')
10
- * @property {String} fileSuffix - File suffix for markdown files (defaults to '.md')
11
- */
12
-
13
- // Define debug namespace at the top of the file
14
- const debugNs = 'metalsmith-markdown-partials';
15
-
16
- /** @type {Options} */
17
- const defaults = {
18
- libraryPath: './src/content/md-library/',
19
- fileSuffix: '.md'
20
- };
21
-
22
- /**
23
- * Normalize plugin options by merging with defaults
24
- * @param {Options} [options] - User provided options
25
- * @returns {Options} Normalized options
26
- */
27
- function normalizeOptions(options) {
28
- return Object.assign({}, defaults, options || {});
29
- }
30
-
31
- /**
32
- * Extract markdown partials from files and prepare them for insertion
33
- *
34
- * @param {Object} files - The metalsmith file object
35
- * @param {Options} options - Plugin options
36
- * @param {Function} debug - Debug function
37
- * @returns {Array} Array with all markdown include objects
38
- */
39
- function getMarkdownIncludes(files, options, debug) {
40
- const markdownIncludes = [];
41
-
42
- // Extract the library name from the path
43
- const libraryPath = options.libraryPath.slice(0, -1).split('/');
44
- const libraryName = libraryPath[libraryPath.length - 1];
45
-
46
- debug('Processing markdown files with libraryName: %s', libraryName);
47
-
48
- // Regex for matching include markers
49
- const markerRegex = /\{#md\s*".+?"\s*#\}/g;
50
- const markerStart = '{#md';
51
- const markerEnd = '#}';
52
-
53
- // Set to track already processed partials to prevent duplicates
54
- const processedPartials = new Set();
55
-
56
- Object.keys(files).forEach((file) => {
57
- /*
58
- * checks if string 'file' ends with options.fileSuffix
59
- * when metalsmith-in-place or metalsmith-layouts are used
60
- * the suffix depends on what templating language is used
61
- * for example with Nunjucks it would be .md.njk
62
- *
63
- * Also check that file does NOT start with libraryName as
64
- * the markdown partials library is also located in the content folder
65
- */
66
- if (file.endsWith(options.fileSuffix) && !file.startsWith(libraryName)) {
67
- const str = files[file].contents.toString();
68
-
69
- // Check if markers are present
70
- const matches = str.match(markerRegex);
71
- if (!matches) {return;}
72
-
73
- debug('Found %d markdown partials in %s', matches.length, file);
74
-
75
- // Process each marker in the file
76
- matches.forEach((marker) => {
77
- // Extract the filename from the marker
78
- const markerFileName = marker.replaceAll(' ', '').replace(`${markerStart}"`, '').replace(`"${markerEnd}`, '');
79
- const partialKey = `${libraryName}/${markerFileName}`;
80
-
81
- // Check if partial file exists
82
- if (!files[partialKey]) {
83
- debug('Warning: Partial file not found: %s', partialKey);
84
- return;
85
- }
86
-
87
- // Skip if we've already processed this exact marker+file combination
88
- const combinedKey = `${file}:${marker}`;
89
- if (processedPartials.has(combinedKey)) {return;}
90
-
91
- processedPartials.add(combinedKey);
92
-
93
- // Get the replacement content
94
- const replacementString = files[partialKey].contents.toString();
95
-
96
- markdownIncludes.push({
97
- marker,
98
- markerReplacement: replacementString,
99
- file
100
- });
101
- });
102
- }
103
- });
104
-
105
- // Remove markdown-partials from metalsmith build process
106
- Object.keys(files).forEach((file) => {
107
- if (file.startsWith(libraryName)) {
108
- delete files[file];
109
- }
110
- });
111
-
112
- debug('Processed %d markdown includes', markdownIncludes.length);
113
- return markdownIncludes;
114
- }
115
-
116
- /**
117
- * Replace markers with their markdown replacement strings
118
- *
119
- * @param {Object} files - The metalsmith file object
120
- * @param {Array} markdownIncludes - Array with all markdown include objects
121
- * @param {Function} debug - Debug function
122
- * @return {void}
123
- */
124
- function resolveMarkdownIncludes(files, markdownIncludes, debug) {
125
- // replace all markers with their markdown replacements
126
- markdownIncludes.forEach((markdownInclude) => {
127
- const fileData = files[markdownInclude.file];
128
-
129
- // replace the include marker with the actual include file content
130
- try {
131
- const contents = fileData.contents.toString();
132
- fileData.contents = Buffer.from(contents.replace(markdownInclude.marker, markdownInclude.markerReplacement));
133
- debug('Replaced marker in %s', markdownInclude.file);
134
- } catch (e) {
135
- debug('Error replacing marker in %s: %s', markdownInclude.file, e.message);
136
- console.error(`Error replacing marker in ${markdownInclude.file}:`, e);
137
- }
138
- });
139
- }
140
-
141
- /**
142
- * A Metalsmith plugin to merge markdown partials into main markdown file
143
- *
144
- * A marker of the form {#md "<file name>.md" #} indicates where the partial
145
- * must be inserted and also provides the file name of the replacement markdown.
146
- *
147
- * @param {Options} [options] - Plugin options
148
- * @returns {import('metalsmith').Plugin} Metalsmith plugin function
149
- * @example
150
- * // In your metalsmith build:
151
- * .use(markdownPartials({
152
- * libraryPath: './src/content/md-partials/',
153
- * fileSuffix: '.md.njk'
154
- * }))
155
- */
156
- function initMarkdownPartials(options) {
157
- options = normalizeOptions(options);
158
-
159
- return function markdownPartials(files, metalsmith, done) {
160
- // Use metalsmith's debug method if available
161
- const debug = metalsmith.debug ? metalsmith.debug(debugNs) : () => {};
162
- debug('Running with options: %o', options);
163
-
164
- try {
165
- // Get all markdown includes
166
- const markdownIncludes = getMarkdownIncludes(files, options, debug);
167
-
168
- // Replace include markers with their respective replacement
169
- if (markdownIncludes.length) {
170
- resolveMarkdownIncludes(files, markdownIncludes, debug);
171
- }
172
-
173
- setImmediate(done);
174
- } catch (err) {
175
- debug('Error processing markdown partials: %s', err.message);
176
- setImmediate(() => done(err));
177
- }
178
- };
179
- }
180
-
181
- // ESM export
182
- export default initMarkdownPartials;
183
-
184
- // CommonJS export compatibility
185
- if (typeof module !== 'undefined') {
186
- module.exports = initMarkdownPartials;
187
- }
package/test/cjs.test.cjs DELETED
@@ -1,55 +0,0 @@
1
- // Minimal CommonJS test file - just verifies the CJS module works
2
- const assert = require('node:assert').strict;
3
- const path = require('node:path');
4
- const fs = require('node:fs');
5
- const metalsmith = require('metalsmith');
6
-
7
- // Import the plugin using the CommonJS format
8
- const plugin = require('../lib/index.cjs');
9
-
10
- describe('metalsmith-markdown-partials (CommonJS)', () => {
11
- // Verify the module loads correctly and exports a function
12
- it('should be properly importable as a CommonJS module', () => {
13
- assert.strictEqual(typeof plugin, 'function', 'Plugin should be a function when required with CommonJS');
14
- assert.strictEqual(typeof plugin(), 'function', 'Plugin should return a function when called');
15
- });
16
-
17
- // Add a basic functionality test to verify the plugin works
18
- it('should process markdown partials correctly', (done) => {
19
- const fixturesDir = path.join(__dirname, 'fixtures');
20
-
21
- const instance = plugin({
22
- libraryPath: path.join(fixturesDir, '/src/md-partials/'),
23
- fileSuffix: '.md',
24
- });
25
-
26
- // Create a fake metalsmith instance
27
- const metalsmithInstance = metalsmith(fixturesDir);
28
-
29
- // Load a test file
30
- const markdownContent = fs.readFileSync(path.join(fixturesDir, 'src/markdown.md'), 'utf8');
31
- const partialContent = fs.readFileSync(path.join(fixturesDir, 'src/md-partials/test-partial.md'), 'utf8');
32
- const expectedContent = fs.readFileSync(path.join(fixturesDir, 'expected/final-markdown.md'), 'utf8');
33
-
34
- // Setup files object
35
- const files = {
36
- 'markdown.md': {
37
- contents: Buffer.from(markdownContent)
38
- },
39
- 'md-partials/test-partial.md': {
40
- contents: Buffer.from(partialContent)
41
- }
42
- };
43
-
44
- // Run the plugin
45
- instance(files, metalsmithInstance, (err) => {
46
- assert.strictEqual(err, undefined, 'No error should occur');
47
- assert.strictEqual(
48
- files['markdown.md'].contents.toString().trim(),
49
- expectedContent.trim(),
50
- 'Content should be properly processed'
51
- );
52
- done();
53
- });
54
- });
55
- });
@@ -1,11 +0,0 @@
1
- # Markdown page... testing markdown partials plugin
2
-
3
- Nullam quis risus eget urna mollis ornare vel eu leo. Curabitur blandit tempus porttitor. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Donec sed odio dui.
4
-
5
- ## Test partial title
6
-
7
- Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Sed posuere consectetur est at lobortis. Maecenas faucibus mollis interdum. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
8
-
9
- Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue. Maecenas faucibus mollis interdum. Maecenas faucibus mollis interdum. Sed posuere consectetur est at lobortis.
10
-
11
- Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Aenean lacinia bibendum nulla sed consectetur.
@@ -1,11 +0,0 @@
1
- # Markdown page... testing markdown partials plugin
2
-
3
- Nullam quis risus eget urna mollis ornare vel eu leo. Curabitur blandit tempus porttitor. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Donec sed odio dui.
4
-
5
- ## Test partial title
6
-
7
- Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Sed posuere consectetur est at lobortis. Maecenas faucibus mollis interdum. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
8
-
9
- Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue. Maecenas faucibus mollis interdum. Maecenas faucibus mollis interdum. Sed posuere consectetur est at lobortis.
10
-
11
- Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Aenean lacinia bibendum nulla sed consectetur.
@@ -1,9 +0,0 @@
1
- # Markdown page... testing markdown partials plugin
2
-
3
- Nullam quis risus eget urna mollis ornare vel eu leo. Curabitur blandit tempus porttitor. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Donec sed odio dui.
4
-
5
- {#md "test-partial.md" #}
6
-
7
- Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue. Maecenas faucibus mollis interdum. Maecenas faucibus mollis interdum. Sed posuere consectetur est at lobortis.
8
-
9
- Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Aenean lacinia bibendum nulla sed consectetur.
@@ -1,3 +0,0 @@
1
- ## Test partial title
2
-
3
- Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Sed posuere consectetur est at lobortis. Maecenas faucibus mollis interdum. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
package/test/index.js DELETED
@@ -1,139 +0,0 @@
1
- // ESM test file for Metalsmith plugins
2
- import { strict as assert } from 'node:assert';
3
- import { fileURLToPath } from 'node:url';
4
- import { dirname, resolve, join } from 'node:path';
5
- import { readFileSync } from 'node:fs';
6
- import metalsmith from 'metalsmith';
7
-
8
- // Import the plugin directly from src for accurate coverage
9
- import plugin from '../src/index.js';
10
-
11
- // Get current directory and setup path utilities
12
- const __dirname = dirname(fileURLToPath(import.meta.url));
13
- const fixture = resolve.bind(resolve, __dirname, 'fixtures');
14
-
15
- function file(_path) {
16
- return readFileSync(fixture(_path), 'utf8');
17
- }
18
-
19
- describe('metalsmith-markdown-partials (ESM)', () => {
20
- // Verify ESM module loading
21
- it('should be importable as an ES module', () => {
22
- assert.strictEqual(typeof plugin, 'function', 'Plugin should be a function when imported with ESM');
23
- assert.strictEqual(typeof plugin(), 'function', 'Plugin should return a function when called');
24
- });
25
-
26
- it('should replace marker with markdown partial', (done) => {
27
- metalsmith(fixture())
28
- .use(
29
- plugin({
30
- libraryPath: join(fixture(), `/src/md-partials/`),
31
- fileSuffix: '.md'
32
- })
33
- )
34
- .build((err) => {
35
- if (err) {
36
- return done(err);
37
- }
38
- assert.strictEqual(
39
- file('build/markdown.md').trim(),
40
- file('expected/final-markdown.md').trim(),
41
- 'Content should match expected output'
42
- );
43
- done();
44
- });
45
- });
46
-
47
- it('should use metalsmith debug when available', (done) => {
48
- // Create a mock debug function that records calls
49
- const debugCalls = [];
50
- const mockMetalsmith = {
51
- debug:
52
- () =>
53
- (...args) => {
54
- debugCalls.push(args);
55
- return true;
56
- }
57
- };
58
-
59
- // Create test files
60
- const files = {
61
- 'test.md': {
62
- contents: Buffer.from('Test content {#md "partial.md" #} more content')
63
- },
64
- 'md-partials/partial.md': {
65
- contents: Buffer.from('Partial content')
66
- }
67
- };
68
-
69
- // Create and call the plugin
70
- const pluginInstance = plugin({
71
- libraryPath: './md-partials/',
72
- fileSuffix: '.md'
73
- });
74
-
75
- pluginInstance(files, mockMetalsmith, () => {
76
- assert(debugCalls.length > 0, 'Debug function should have been called');
77
- assert(debugCalls[0][0].includes('options'), 'Should include options message');
78
- done();
79
- });
80
- });
81
-
82
- it('should handle missing debug method gracefully', (done) => {
83
- // Create a metalsmith mock without debug method
84
- const mockMetalsmith = {};
85
-
86
- // Create test files
87
- const files = {
88
- 'test.md': {
89
- contents: Buffer.from('Test content {#md "partial.md" #} more content')
90
- },
91
- 'md-partials/partial.md': {
92
- contents: Buffer.from('Partial content')
93
- }
94
- };
95
-
96
- // Should not throw error when debug is missing
97
- const pluginInstance = plugin({
98
- libraryPath: './md-partials/',
99
- fileSuffix: '.md'
100
- });
101
-
102
- pluginInstance(files, mockMetalsmith, (err) => {
103
- assert.strictEqual(err, undefined, 'No error should occur when debug is missing');
104
- done();
105
- });
106
- });
107
-
108
- it('should handle files without markers correctly', (done) => {
109
- // Create a metalsmith mock
110
- const mockMetalsmith = {};
111
-
112
- // Create test files
113
- const files = {
114
- 'test.md': {
115
- contents: Buffer.from('Test content without any markers')
116
- },
117
- 'md-partials/partial.md': {
118
- contents: Buffer.from('Partial content')
119
- }
120
- };
121
-
122
- // Should not throw error when no markers are found
123
- const pluginInstance = plugin({
124
- libraryPath: './md-partials/',
125
- fileSuffix: '.md'
126
- });
127
-
128
- pluginInstance(files, mockMetalsmith, (err) => {
129
- assert.strictEqual(err, undefined, 'No error should occur when no markers are present');
130
- // Content should remain unchanged
131
- assert.strictEqual(
132
- files['test.md'].contents.toString(),
133
- 'Test content without any markers',
134
- 'Content should remain unchanged'
135
- );
136
- done();
137
- });
138
- });
139
- });