comment-block-transformer 0.6.1 → 0.7.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/CHANGELOG.md CHANGED
@@ -3,6 +3,17 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [0.7.0](https://github.com/DavidWells/markdown-magic/compare/comment-block-transformer@0.6.1...comment-block-transformer@0.7.0) (2026-01-19)
7
+
8
+
9
+ ### Features
10
+
11
+ * **block-transformer:** add normalizeBlankLines option and utility ([bde5038](https://github.com/DavidWells/markdown-magic/commit/bde50389800da417787c6c4aae8165dd9f7de0b5))
12
+
13
+
14
+
15
+
16
+
6
17
  ## [0.6.1](https://github.com/DavidWells/markdown-magic/compare/comment-block-transformer@0.6.0...comment-block-transformer@0.6.1) (2026-01-19)
7
18
 
8
19
  **Note:** Version bump only for package comment-block-transformer
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "comment-block-transformer",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "description": "Transform markdown blocks based on configured transforms",
5
5
  "main": "src/index.js",
6
6
  "types": "types/index.d.ts",
@@ -25,5 +25,5 @@
25
25
  "publishConfig": {
26
26
  "access": "public"
27
27
  },
28
- "gitHead": "e149a2e69992f108553ed3f84dc5cbd1db361987"
28
+ "gitHead": "37ed26b98df488f005d9ef91ecd8cd9649c5c067"
29
29
  }
package/src/index.js CHANGED
@@ -67,6 +67,7 @@ const CLOSE_WORD = '/block'
67
67
  * @property {Array<Middleware>} [afterMiddleware=[]] - Middleware functions change inner block content after transforms.
68
68
  * @property {boolean} [removeComments=false] - Remove comments from the processed contents.
69
69
  * @property {boolean} [forceRemoveComments=false] - Force remove comments even when srcPath === outputPath, strips comments directly in updatedContents.
70
+ * @property {boolean} [normalizeBlankLines=false] - Collapse multiple consecutive blank lines to single blank line (preserves blank lines in code blocks).
70
71
  * @property {string} [srcPath] - The source path.
71
72
  * @property {string} [outputPath] - The output path.
72
73
  * @property {import('comment-block-parser').CustomPatterns} [customPatterns] - Custom regex patterns for open and close tags.
@@ -105,6 +106,7 @@ async function blockTransformer(inputText, config) {
105
106
  afterMiddleware = [],
106
107
  removeComments = false,
107
108
  forceRemoveComments = false,
109
+ normalizeBlankLines: normalizeBlankLinesOpt = false,
108
110
  customPatterns
109
111
  } = opts
110
112
  // Don't default close - let undefined pass through to enable pattern mode in block-parser
@@ -295,7 +297,10 @@ async function blockTransformer(inputText, config) {
295
297
 
296
298
  let finalContents = updatedContents
297
299
  if (forceRemoveComments && openPattern && closePattern) {
298
- finalContents = updatedContents.replace(openPattern, '').replace(closePattern, '')
300
+ finalContents = finalContents.replace(openPattern, '').replace(closePattern, '')
301
+ }
302
+ if (normalizeBlankLinesOpt) {
303
+ finalContents = normalizeBlankLines(finalContents)
299
304
  }
300
305
 
301
306
  // console.log('inputText', inputText)
@@ -444,6 +449,43 @@ function getCodeLocation(srcPath, line, column = '0') {
444
449
  return `${srcPath}:${line}:${column}`
445
450
  }
446
451
 
452
+ /**
453
+ * Normalize blank lines in content, collapsing multiple consecutive blank lines to a single one.
454
+ * Preserves blank lines inside fenced code blocks (``` or ~~~).
455
+ * @param {string} content - The content to normalize
456
+ * @returns {string} Content with normalized blank lines
457
+ */
458
+ function normalizeBlankLines(content) {
459
+ if (!content) return content
460
+
461
+ const lines = content.split('\n')
462
+ const result = []
463
+ let inCodeBlock = false
464
+ let consecutiveBlanks = 0
465
+
466
+ for (const line of lines) {
467
+ // Check for code fence (``` or ~~~), possibly with language specifier, possibly indented
468
+ if (/^\s*(`{3,}|~{3,})/.test(line)) {
469
+ inCodeBlock = !inCodeBlock
470
+ consecutiveBlanks = 0
471
+ result.push(line)
472
+ continue
473
+ }
474
+
475
+ if (line.trim() === '' && !inCodeBlock) {
476
+ consecutiveBlanks++
477
+ if (consecutiveBlanks <= 1) {
478
+ result.push(line)
479
+ }
480
+ } else {
481
+ consecutiveBlanks = 0
482
+ result.push(line)
483
+ }
484
+ }
485
+
486
+ return result.join('\n')
487
+ }
488
+
447
489
  if (require.main === module) {
448
490
  const yaml = `
449
491
  - name: Run tests two
@@ -456,4 +498,5 @@ if (require.main === module) {
456
498
  module.exports = {
457
499
  blockTransformer,
458
500
  indentString,
501
+ normalizeBlankLines,
459
502
  }
@@ -288,4 +288,53 @@ content
288
288
  assert.ok(result.updatedContents.includes('transformed'))
289
289
  })
290
290
 
291
+ test('normalizeBlankLines option collapses multiple blank lines', async () => {
292
+ const text = `## Section
293
+
294
+ <!-- block test -->
295
+ content
296
+ <!-- /block -->
297
+
298
+ <!-- block empty -->
299
+ <!-- /block -->
300
+
301
+ ---`
302
+ const result = await blockTransformer(text, {
303
+ forceRemoveComments: true,
304
+ normalizeBlankLines: true,
305
+ transforms: {
306
+ test: () => 'transformed',
307
+ empty: () => ''
308
+ }
309
+ })
310
+
311
+ // Should not have 2+ consecutive blank lines outside code blocks
312
+ assert.not.ok(result.updatedContents.includes('\n\n\n'))
313
+ })
314
+
315
+ test('normalizeBlankLines preserves blank lines in code blocks', async () => {
316
+ const text = `## Section
317
+
318
+ <!-- block code -->
319
+ \`\`\`bash
320
+ line 1
321
+
322
+
323
+ line 2
324
+ \`\`\`
325
+ <!-- /block -->
326
+
327
+ ---`
328
+ const result = await blockTransformer(text, {
329
+ forceRemoveComments: true,
330
+ normalizeBlankLines: true,
331
+ transforms: {
332
+ code: (api) => api.content
333
+ }
334
+ })
335
+
336
+ // Code block should preserve its blank lines
337
+ assert.ok(result.updatedContents.includes('line 1\n\n\nline 2'))
338
+ })
339
+
291
340
  test.run()
@@ -0,0 +1,181 @@
1
+ // Tests for normalizeBlankLines utility
2
+
3
+ const { test } = require('uvu')
4
+ const assert = require('uvu/assert')
5
+ const { normalizeBlankLines } = require('../src')
6
+
7
+ test('collapses multiple blank lines to single blank line', () => {
8
+ const input = `line 1
9
+
10
+
11
+ line 2`
12
+ const result = normalizeBlankLines(input)
13
+ assert.is(result, `line 1
14
+
15
+ line 2`)
16
+ })
17
+
18
+ test('preserves single blank lines', () => {
19
+ const input = `line 1
20
+
21
+ line 2`
22
+ const result = normalizeBlankLines(input)
23
+ assert.is(result, input)
24
+ })
25
+
26
+ test('collapses 3+ blank lines to single', () => {
27
+ const input = `line 1
28
+
29
+
30
+
31
+
32
+ line 2`
33
+ const result = normalizeBlankLines(input)
34
+ assert.is(result, `line 1
35
+
36
+ line 2`)
37
+ })
38
+
39
+ test('preserves blank lines inside fenced code blocks (```)', () => {
40
+ const input = `before
41
+
42
+ \`\`\`bash
43
+ line 1
44
+
45
+
46
+ line 2
47
+ \`\`\`
48
+
49
+ after`
50
+ const result = normalizeBlankLines(input)
51
+ assert.is(result, input)
52
+ })
53
+
54
+ test('preserves blank lines inside fenced code blocks (~~~)', () => {
55
+ const input = `before
56
+
57
+ ~~~js
58
+ line 1
59
+
60
+
61
+ line 2
62
+ ~~~
63
+
64
+ after`
65
+ const result = normalizeBlankLines(input)
66
+ assert.is(result, input)
67
+ })
68
+
69
+ test('handles multiple code blocks', () => {
70
+ const input = `start
71
+
72
+
73
+ \`\`\`
74
+ code 1
75
+
76
+
77
+ more code
78
+ \`\`\`
79
+
80
+
81
+ middle
82
+
83
+
84
+ \`\`\`
85
+ code 2
86
+
87
+
88
+ more
89
+ \`\`\`
90
+
91
+
92
+ end`
93
+ const expected = `start
94
+
95
+ \`\`\`
96
+ code 1
97
+
98
+
99
+ more code
100
+ \`\`\`
101
+
102
+ middle
103
+
104
+ \`\`\`
105
+ code 2
106
+
107
+
108
+ more
109
+ \`\`\`
110
+
111
+ end`
112
+ const result = normalizeBlankLines(input)
113
+ assert.is(result, expected)
114
+ })
115
+
116
+ test('handles code block with language specifier', () => {
117
+ const input = `text
118
+
119
+
120
+ \`\`\`javascript
121
+ const x = 1
122
+
123
+
124
+ const y = 2
125
+ \`\`\`
126
+
127
+
128
+ more text`
129
+ const expected = `text
130
+
131
+ \`\`\`javascript
132
+ const x = 1
133
+
134
+
135
+ const y = 2
136
+ \`\`\`
137
+
138
+ more text`
139
+ const result = normalizeBlankLines(input)
140
+ assert.is(result, expected)
141
+ })
142
+
143
+ test('handles empty input', () => {
144
+ assert.is(normalizeBlankLines(''), '')
145
+ })
146
+
147
+ test('handles input with no blank lines', () => {
148
+ const input = `line 1
149
+ line 2
150
+ line 3`
151
+ assert.is(normalizeBlankLines(input), input)
152
+ })
153
+
154
+ test('handles indented code fences', () => {
155
+ const input = `text
156
+
157
+
158
+ \`\`\`
159
+ code
160
+
161
+
162
+ more
163
+ \`\`\`
164
+
165
+
166
+ end`
167
+ const expected = `text
168
+
169
+ \`\`\`
170
+ code
171
+
172
+
173
+ more
174
+ \`\`\`
175
+
176
+ end`
177
+ const result = normalizeBlankLines(input)
178
+ assert.is(result, expected)
179
+ })
180
+
181
+ test.run()