comment-block-transformer 0.5.7 → 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,36 @@
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
+
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)
18
+
19
+ **Note:** Version bump only for package comment-block-transformer
20
+
21
+
22
+
23
+
24
+
25
+ # [0.6.0](https://github.com/DavidWells/markdown-magic/compare/comment-block-transformer@0.5.7...comment-block-transformer@0.6.0) (2026-01-19)
26
+
27
+
28
+ ### Features
29
+
30
+ * **block-transformer:** add forceRemoveComments option ([7503338](https://github.com/DavidWells/markdown-magic/commit/7503338ee182a935b04e7162276fd3df072b59a9))
31
+
32
+
33
+
34
+
35
+
6
36
  ## [0.5.7](https://github.com/DavidWells/markdown-magic/compare/comment-block-transformer@0.5.6...comment-block-transformer@0.5.7) (2026-01-19)
7
37
 
8
38
  **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.5.7",
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": "2bef6eae03b732f55673524c1e928ecff8b2c86c"
28
+ "gitHead": "37ed26b98df488f005d9ef91ecd8cd9649c5c067"
29
29
  }
package/src/index.js CHANGED
@@ -66,6 +66,8 @@ const CLOSE_WORD = '/block'
66
66
  * @property {Array<Middleware>} [beforeMiddleware=[]] - Middleware functions change inner block content before transforms.
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
+ * @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).
69
71
  * @property {string} [srcPath] - The source path.
70
72
  * @property {string} [outputPath] - The output path.
71
73
  * @property {import('comment-block-parser').CustomPatterns} [customPatterns] - Custom regex patterns for open and close tags.
@@ -103,6 +105,8 @@ async function blockTransformer(inputText, config) {
103
105
  beforeMiddleware = [],
104
106
  afterMiddleware = [],
105
107
  removeComments = false,
108
+ forceRemoveComments = false,
109
+ normalizeBlankLines: normalizeBlankLinesOpt = false,
106
110
  customPatterns
107
111
  } = opts
108
112
  // Don't default close - let undefined pass through to enable pattern mode in block-parser
@@ -285,15 +289,22 @@ async function blockTransformer(inputText, config) {
285
289
 
286
290
  const isNewPath = srcPath !== outputPath
287
291
 
288
- if (removeComments && !isNewPath) {
292
+ if (removeComments && !isNewPath && !forceRemoveComments) {
289
293
  throw new Error('"removeComments" can only be used if "outputPath" option is set. Otherwise this will break doc generation.')
290
294
  }
291
295
 
292
- const stripComments = isNewPath && removeComments
296
+ const stripComments = (isNewPath && removeComments) || forceRemoveComments
293
297
 
298
+ let finalContents = updatedContents
299
+ if (forceRemoveComments && openPattern && closePattern) {
300
+ finalContents = finalContents.replace(openPattern, '').replace(closePattern, '')
301
+ }
302
+ if (normalizeBlankLinesOpt) {
303
+ finalContents = normalizeBlankLines(finalContents)
304
+ }
294
305
 
295
306
  // console.log('inputText', inputText)
296
- // console.log('updatedContents', updatedContents)
307
+ // console.log('updatedContents', updatedContents)
297
308
  return {
298
309
  isChanged: inputText !== updatedContents,
299
310
  isNewPath,
@@ -303,12 +314,11 @@ async function blockTransformer(inputText, config) {
303
314
  transforms: transformsToRun,
304
315
  missingTransforms,
305
316
  originalContents: inputText,
306
- updatedContents,
317
+ updatedContents: finalContents,
307
318
  patterns: regexInfo,
308
319
  }
309
320
  }
310
321
 
311
-
312
322
  /** @typedef {BlockData & { sourceLocation?: string, transform?: string }} BlockDataExtended */
313
323
 
314
324
  function getDetails({
@@ -439,6 +449,43 @@ function getCodeLocation(srcPath, line, column = '0') {
439
449
  return `${srcPath}:${line}:${column}`
440
450
  }
441
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
+
442
489
  if (require.main === module) {
443
490
  const yaml = `
444
491
  - name: Run tests two
@@ -451,4 +498,5 @@ if (require.main === module) {
451
498
  module.exports = {
452
499
  blockTransformer,
453
500
  indentString,
501
+ normalizeBlankLines,
454
502
  }
@@ -248,4 +248,93 @@ original content
248
248
  assert.ok(result.updatedContents.includes('original content'))
249
249
  })
250
250
 
251
+ test('forceRemoveComments bypasses safety check and strips comments from updatedContents', async () => {
252
+ const text = `
253
+ <!-- block test -->
254
+ content
255
+ <!-- /block -->
256
+ `
257
+ const result = await blockTransformer(text, {
258
+ srcPath: '/same/path.md',
259
+ outputPath: '/same/path.md',
260
+ forceRemoveComments: true,
261
+ transforms: {
262
+ test: (api) => 'transformed'
263
+ }
264
+ })
265
+
266
+ assert.is(result.stripComments, true)
267
+ assert.not.ok(result.updatedContents.includes('<!-- block'))
268
+ assert.not.ok(result.updatedContents.includes('<!-- /block'))
269
+ assert.ok(result.updatedContents.includes('transformed'))
270
+ })
271
+
272
+ test('forceRemoveComments works without outputPath', async () => {
273
+ const text = `
274
+ <!-- block test -->
275
+ content
276
+ <!-- /block -->
277
+ `
278
+ const result = await blockTransformer(text, {
279
+ forceRemoveComments: true,
280
+ transforms: {
281
+ test: (api) => 'transformed'
282
+ }
283
+ })
284
+
285
+ assert.is(result.stripComments, true)
286
+ assert.not.ok(result.updatedContents.includes('<!-- block'))
287
+ assert.not.ok(result.updatedContents.includes('<!-- /block'))
288
+ assert.ok(result.updatedContents.includes('transformed'))
289
+ })
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
+
251
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()