comment-block-replacer 0.1.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/README.md +263 -0
- package/package.json +27 -0
- package/src/index.js +137 -0
- package/test/index.test.js +271 -0
- package/test-manual.js +38 -0
- package/tsconfig.json +17 -0
- package/types/index.d.ts +69 -0
package/README.md
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# comment-block-replacer
|
|
2
|
+
|
|
3
|
+
Process files with comment block replacements using markdown-magic transforms.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install comment-block-replacer
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
The comment block replacer allows you to process files (both from file paths and content strings) with comment block transforms, applying configured transforms to update content within delimited blocks.
|
|
14
|
+
|
|
15
|
+
### Basic Usage
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
const { processFile } = require('comment-block-replacer')
|
|
19
|
+
|
|
20
|
+
// Process file content directly
|
|
21
|
+
const result = await processFile({
|
|
22
|
+
content: `
|
|
23
|
+
<!-- DOCS:START example -->
|
|
24
|
+
Some content to transform
|
|
25
|
+
<!-- DOCS:END -->
|
|
26
|
+
`,
|
|
27
|
+
dryRun: true,
|
|
28
|
+
transforms: {
|
|
29
|
+
example: (api) => {
|
|
30
|
+
return api.content.toUpperCase()
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
console.log(result.updatedContents)
|
|
36
|
+
// Output: Content will be transformed to uppercase
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Processing Files from Path
|
|
40
|
+
|
|
41
|
+
```javascript
|
|
42
|
+
const { processFile } = require('comment-block-replacer')
|
|
43
|
+
|
|
44
|
+
const result = await processFile({
|
|
45
|
+
srcPath: './docs/README.md',
|
|
46
|
+
outputPath: './output/README.md',
|
|
47
|
+
transforms: {
|
|
48
|
+
wordcount: (api) => {
|
|
49
|
+
const words = api.content.trim().split(/\s+/).length
|
|
50
|
+
return `Word count: ${words}`
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### File Processing Options
|
|
57
|
+
|
|
58
|
+
```javascript
|
|
59
|
+
const { processFile } = require('comment-block-replacer')
|
|
60
|
+
|
|
61
|
+
const result = await processFile({
|
|
62
|
+
srcPath: './src/example.md',
|
|
63
|
+
outputPath: './dist/example.md',
|
|
64
|
+
dryRun: false, // Set to true to preview changes without writing
|
|
65
|
+
applyTransformsToSource: true, // Also update the source file
|
|
66
|
+
syntax: 'md', // Override detected syntax
|
|
67
|
+
transforms: {
|
|
68
|
+
uppercase: (api) => api.content.toUpperCase(),
|
|
69
|
+
file: (api) => `Content from ${api.options.src}`
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Output Directory Configuration
|
|
75
|
+
|
|
76
|
+
```javascript
|
|
77
|
+
const result = await processFile({
|
|
78
|
+
content: 'File content...',
|
|
79
|
+
outputPath: './dist/processed.md',
|
|
80
|
+
output: {
|
|
81
|
+
directory: './dist' // Output directory
|
|
82
|
+
},
|
|
83
|
+
outputDir: './dist', // Legacy option (same as output.directory)
|
|
84
|
+
transforms: { /* ... */ }
|
|
85
|
+
})
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Comment Pattern Stripping
|
|
89
|
+
|
|
90
|
+
```javascript
|
|
91
|
+
const result = await processFile({
|
|
92
|
+
srcPath: './docs/source.md',
|
|
93
|
+
outputPath: './dist/clean.md',
|
|
94
|
+
removeComments: true,
|
|
95
|
+
patterns: {
|
|
96
|
+
openPattern: /<!-- DOCS:START .* -->/g,
|
|
97
|
+
closePattern: /<!-- DOCS:END -->/g
|
|
98
|
+
},
|
|
99
|
+
transforms: { /* ... */ }
|
|
100
|
+
})
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## API Reference
|
|
104
|
+
|
|
105
|
+
### processFile(options)
|
|
106
|
+
|
|
107
|
+
Process a file with comment block replacements using configured transforms.
|
|
108
|
+
|
|
109
|
+
#### Parameters
|
|
110
|
+
|
|
111
|
+
- `options` (ProcessFileOptions): Processing configuration options
|
|
112
|
+
|
|
113
|
+
#### Returns
|
|
114
|
+
|
|
115
|
+
Promise<ProcessFileResult> - Result object with processed content and metadata
|
|
116
|
+
|
|
117
|
+
### ProcessFileOptions
|
|
118
|
+
|
|
119
|
+
Configuration object for processing files.
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
interface ProcessFileOptions {
|
|
123
|
+
content?: string // File content as string (mutually exclusive with srcPath)
|
|
124
|
+
srcPath?: string // Source file path (mutually exclusive with content)
|
|
125
|
+
syntax?: string // File syntax type (e.g., 'md', 'js', 'html')
|
|
126
|
+
outputPath?: string // Output file path for processed content
|
|
127
|
+
dryRun?: boolean // If true, process but don't write files (default: false)
|
|
128
|
+
patterns?: { // Comment patterns for stripping
|
|
129
|
+
openPattern?: RegExp // Opening comment pattern regex
|
|
130
|
+
closePattern?: RegExp // Closing comment pattern regex
|
|
131
|
+
}
|
|
132
|
+
output?: { // Output configuration
|
|
133
|
+
directory?: string // Output directory path
|
|
134
|
+
}
|
|
135
|
+
outputDir?: string // Legacy output directory option
|
|
136
|
+
applyTransformsToSource?: boolean // Apply transforms to source file (default: false)
|
|
137
|
+
transforms?: object // Transform functions to apply to blocks
|
|
138
|
+
beforeMiddleware?: Array // Middleware to run before transforms
|
|
139
|
+
afterMiddleware?: Array // Middleware to run after transforms
|
|
140
|
+
removeComments?: boolean // Remove comment blocks from output (default: false)
|
|
141
|
+
open?: string // Opening delimiter for comment blocks
|
|
142
|
+
close?: string // Closing delimiter for comment blocks
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### ProcessFileResult
|
|
147
|
+
|
|
148
|
+
Result object returned by processFile:
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
interface ProcessFileResult {
|
|
152
|
+
isChanged: boolean // Whether the content was modified
|
|
153
|
+
isNewPath: boolean // Whether srcPath differs from outputPath
|
|
154
|
+
stripComments: boolean // Whether comments should be stripped from output
|
|
155
|
+
srcPath?: string // Source file path used
|
|
156
|
+
outputPath?: string // Output file path used
|
|
157
|
+
transforms: Array // Array of transforms that were applied
|
|
158
|
+
missingTransforms: Array // Array of transforms that were not found
|
|
159
|
+
originalContents: string // Original input content
|
|
160
|
+
updatedContents: string // Processed output content
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Transform Function API
|
|
165
|
+
|
|
166
|
+
Transform functions receive an API object with the following properties:
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
interface TransformApi {
|
|
170
|
+
transform: string // Name of the transform
|
|
171
|
+
content: string // Content to transform
|
|
172
|
+
options: object // Transform options
|
|
173
|
+
srcPath?: string // Source file path
|
|
174
|
+
outputPath?: string // Output file path
|
|
175
|
+
settings: object // Additional settings including regex patterns
|
|
176
|
+
currentContent: string // Current file contents
|
|
177
|
+
originalContent: string // Original file contents
|
|
178
|
+
getCurrentContent(): string // Function to get current file contents
|
|
179
|
+
getOriginalContent(): string // Function to get original file contents
|
|
180
|
+
getOriginalBlock(): object // Function to get the original block data
|
|
181
|
+
getBlockDetails(content?: string): object // Function to get detailed block information
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Examples
|
|
186
|
+
|
|
187
|
+
### Multiple Transform Types
|
|
188
|
+
|
|
189
|
+
```javascript
|
|
190
|
+
const { processFile } = require('comment-block-replacer')
|
|
191
|
+
|
|
192
|
+
const result = await processFile({
|
|
193
|
+
srcPath: './docs/api.md',
|
|
194
|
+
transforms: {
|
|
195
|
+
toc: (api) => generateTableOfContents(api.currentContent),
|
|
196
|
+
wordcount: (api) => `Words: ${api.content.trim().split(/\s+/).length}`,
|
|
197
|
+
file: (api) => fs.readFileSync(api.options.src, 'utf8'),
|
|
198
|
+
code: (api) => {
|
|
199
|
+
const code = fs.readFileSync(api.options.src, 'utf8')
|
|
200
|
+
return `\`\`\`${api.options.lang || 'javascript'}\n${code}\n\`\`\``
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
})
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Syntax Detection
|
|
207
|
+
|
|
208
|
+
The processor automatically detects file syntax from the file extension:
|
|
209
|
+
|
|
210
|
+
```javascript
|
|
211
|
+
// JavaScript files (.js)
|
|
212
|
+
await processFile({
|
|
213
|
+
srcPath: './src/example.js', // Syntax: 'js'
|
|
214
|
+
// Uses // comment blocks by default
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
// Markdown files (.md)
|
|
218
|
+
await processFile({
|
|
219
|
+
srcPath: './docs/readme.md', // Syntax: 'md'
|
|
220
|
+
// Uses <!-- --> comment blocks by default
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
// Override syntax detection
|
|
224
|
+
await processFile({
|
|
225
|
+
srcPath: './config.json',
|
|
226
|
+
syntax: 'js', // Force JavaScript syntax
|
|
227
|
+
})
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Error Handling
|
|
231
|
+
|
|
232
|
+
```javascript
|
|
233
|
+
try {
|
|
234
|
+
const result = await processFile({
|
|
235
|
+
srcPath: './docs/file.md',
|
|
236
|
+
content: 'content string', // Error: can't use both
|
|
237
|
+
transforms: {}
|
|
238
|
+
})
|
|
239
|
+
} catch (error) {
|
|
240
|
+
console.error('Processing failed:', error.message)
|
|
241
|
+
// "Can't set both "srcPath" & "content""
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Testing
|
|
246
|
+
|
|
247
|
+
The package uses [uvu](https://github.com/lukeed/uvu) for testing:
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
npm test
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## TypeScript Support
|
|
254
|
+
|
|
255
|
+
This package includes TypeScript declarations. The types are automatically generated from JSDoc comments.
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
npm run build
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## License
|
|
262
|
+
|
|
263
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "comment-block-replacer",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Process files with comment block replacements",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"types": "types/index.d.ts",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"is-valid-path": "^0.1.1",
|
|
9
|
+
"comment-block-transformer": "0.1.1"
|
|
10
|
+
},
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"typescript": "^5.0.0",
|
|
13
|
+
"uvu": "^0.5.6"
|
|
14
|
+
},
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"test": "uvu test",
|
|
20
|
+
"build": "pnpm run types",
|
|
21
|
+
"types": "tsc --emitDeclarationOnly --outDir types",
|
|
22
|
+
"clean": "rimraf types",
|
|
23
|
+
"release:patch": "pnpm run build && pnpm version patch && pnpm publish",
|
|
24
|
+
"release:minor": "pnpm run build && pnpm version minor && pnpm publish",
|
|
25
|
+
"release:major": "pnpm run build && pnpm version major && pnpm publish"
|
|
26
|
+
}
|
|
27
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const fs = require('fs').promises
|
|
3
|
+
const isValidFile = require('is-valid-path')
|
|
4
|
+
const { blockTransformer } = require('comment-block-transformer')
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {import('comment-block-transformer').ProcessContentConfig} ProcessContentConfig
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {import('comment-block-transformer').ProcessContentResult} ProcessContentResult
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Extended configuration for processing files with additional file-specific options
|
|
16
|
+
* @typedef {ProcessContentConfig & {
|
|
17
|
+
* content?: string
|
|
18
|
+
* srcPath?: string
|
|
19
|
+
* outputPath?: string
|
|
20
|
+
* dryRun?: boolean
|
|
21
|
+
* patterns?: {
|
|
22
|
+
* openPattern?: RegExp
|
|
23
|
+
* closePattern?: RegExp
|
|
24
|
+
* }
|
|
25
|
+
* output?: {
|
|
26
|
+
* directory?: string
|
|
27
|
+
* }
|
|
28
|
+
* outputDir?: string
|
|
29
|
+
* applyTransformsToSource?: boolean
|
|
30
|
+
* open?: string
|
|
31
|
+
* close?: string
|
|
32
|
+
* }} ProcessFileOptions
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Result of processing a file with comment block replacements
|
|
37
|
+
* @typedef {Object} ProcessFileResult
|
|
38
|
+
* @property {boolean} isChanged - Whether the content was modified
|
|
39
|
+
* @property {boolean} isNewPath - Whether srcPath differs from outputPath
|
|
40
|
+
* @property {boolean} stripComments - Whether comments should be stripped from output
|
|
41
|
+
* @property {string} [srcPath] - Source file path used
|
|
42
|
+
* @property {string} [outputPath] - Output file path used
|
|
43
|
+
* @property {Array} transforms - Array of transforms that were applied
|
|
44
|
+
* @property {Array} missingTransforms - Array of transforms that were not found
|
|
45
|
+
* @property {string} originalContents - Original input content
|
|
46
|
+
* @property {string} updatedContents - Processed output content
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Write file with directory creation if needed
|
|
51
|
+
* @param {string} filePath - File path to write to
|
|
52
|
+
* @param {string} content - Content to write
|
|
53
|
+
* @returns {Promise<void>}
|
|
54
|
+
*/
|
|
55
|
+
async function writeFile(filePath, content) {
|
|
56
|
+
try {
|
|
57
|
+
await fs.writeFile(filePath, content)
|
|
58
|
+
} catch(e) {
|
|
59
|
+
const dirName = path.dirname(filePath)
|
|
60
|
+
await fs.mkdir(dirName, { recursive: true })
|
|
61
|
+
await fs.writeFile(filePath, content)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Read file content
|
|
67
|
+
* @param {string} filePath - File path to read from
|
|
68
|
+
* @param {string} [encoding='utf8'] - File encoding
|
|
69
|
+
* @returns {Promise<string>} File content
|
|
70
|
+
*/
|
|
71
|
+
async function readFile(filePath, encoding = 'utf8') {
|
|
72
|
+
return fs.readFile(filePath, encoding)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Process a file with comment block replacements using configured transforms
|
|
77
|
+
* @param {ProcessFileOptions} [opts={}] - Processing options
|
|
78
|
+
* @returns {Promise<ProcessContentResult>} Result object with processed content and metadata
|
|
79
|
+
*/
|
|
80
|
+
async function processFile(opts = {}) {
|
|
81
|
+
const { content, syntax, outputPath, dryRun, patterns, output = {}, applyTransformsToSource } = opts
|
|
82
|
+
const outputDir = output.directory || opts.outputDir
|
|
83
|
+
|
|
84
|
+
let srcPath = opts.srcPath
|
|
85
|
+
if (srcPath && content) {
|
|
86
|
+
throw new Error(`Can't set both "srcPath" & "content"`)
|
|
87
|
+
}
|
|
88
|
+
let fileContents
|
|
89
|
+
if (content) {
|
|
90
|
+
const isFile = isValidFile(content) && content.indexOf('\n') === -1
|
|
91
|
+
srcPath = (isFile) ? content : undefined
|
|
92
|
+
fileContents = (!isFile) ? content : undefined
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!fileContents) {
|
|
96
|
+
fileContents = await readFile(srcPath || content, 'utf8')
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let syntaxType = syntax
|
|
100
|
+
if (srcPath && !syntaxType) {
|
|
101
|
+
const ext = path.extname(srcPath).replace(/^\./, '')
|
|
102
|
+
if (ext === 'js') syntaxType = 'js'
|
|
103
|
+
else if (ext === 'md') syntaxType = 'md'
|
|
104
|
+
else syntaxType = ext || 'md'
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const result = await blockTransformer(fileContents, {
|
|
108
|
+
...opts,
|
|
109
|
+
outputPath,
|
|
110
|
+
srcPath,
|
|
111
|
+
syntax: syntaxType,
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
// console.log('resultFromBlockTransformer', result)
|
|
115
|
+
|
|
116
|
+
if (dryRun) {
|
|
117
|
+
return result
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (outputPath && (result.isChanged || result.isNewPath)) {
|
|
121
|
+
let cleanContents = result.updatedContents
|
|
122
|
+
if (result.stripComments && result.patterns.openPattern && result.patterns.closePattern) {
|
|
123
|
+
cleanContents = result.updatedContents.replace(result.patterns.openPattern, '').replace(result.patterns.closePattern, '')
|
|
124
|
+
}
|
|
125
|
+
await writeFile(outputPath, cleanContents)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (applyTransformsToSource && srcPath && result.isChanged) {
|
|
129
|
+
await writeFile(srcPath, result.updatedContents)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return result
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
module.exports = {
|
|
136
|
+
processFile
|
|
137
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
const { test } = require('uvu')
|
|
2
|
+
const assert = require('uvu/assert')
|
|
3
|
+
const path = require('path')
|
|
4
|
+
const fs = require('fs').promises
|
|
5
|
+
const { processFile } = require('../src')
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {import('../src').ProcessFileOptions} ProcessFileOptions
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// Create temporary test files for testing
|
|
12
|
+
const testDir = path.join(__dirname, 'temp')
|
|
13
|
+
const testFile = path.join(testDir, 'test.md')
|
|
14
|
+
const outputFile = path.join(testDir, 'output.md')
|
|
15
|
+
|
|
16
|
+
// Setup and cleanup
|
|
17
|
+
async function setup() {
|
|
18
|
+
try {
|
|
19
|
+
await fs.mkdir(testDir, { recursive: true })
|
|
20
|
+
} catch (e) {
|
|
21
|
+
// Directory might already exist
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function cleanup() {
|
|
26
|
+
try {
|
|
27
|
+
// @ts-ignore
|
|
28
|
+
await fs.rm(testDir, { recursive: true })
|
|
29
|
+
} catch (e) {
|
|
30
|
+
// Directory might not exist or not be empty
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Mock transforms for testing
|
|
35
|
+
const mockTransforms = {
|
|
36
|
+
uppercase: (api) => {
|
|
37
|
+
return api.content.toUpperCase()
|
|
38
|
+
},
|
|
39
|
+
wordcount: (api) => {
|
|
40
|
+
const words = api.content.trim().split(/\s+/).length
|
|
41
|
+
return `Word count: ${words}`
|
|
42
|
+
},
|
|
43
|
+
file: (api) => {
|
|
44
|
+
return `Content from ${api.options.src || 'unknown file'}`
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
test.before(setup)
|
|
49
|
+
test.after(cleanup)
|
|
50
|
+
|
|
51
|
+
test('should process file content with transforms', async () => {
|
|
52
|
+
const content = `
|
|
53
|
+
# Test Document
|
|
54
|
+
|
|
55
|
+
<!-- DOCS:START uppercase -->
|
|
56
|
+
hello world
|
|
57
|
+
<!-- DOCS:END -->
|
|
58
|
+
|
|
59
|
+
Some other content.
|
|
60
|
+
`
|
|
61
|
+
|
|
62
|
+
/** @type {ProcessFileOptions} */
|
|
63
|
+
const options = {
|
|
64
|
+
content,
|
|
65
|
+
dryRun: true,
|
|
66
|
+
transforms: mockTransforms
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const result = await processFile(options)
|
|
70
|
+
|
|
71
|
+
assert.is(result.isChanged, true)
|
|
72
|
+
assert.ok(result.updatedContents.includes('HELLO WORLD'))
|
|
73
|
+
assert.is(result.transforms.length, 1)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test('should process file from path', async () => {
|
|
77
|
+
const content = `
|
|
78
|
+
# Test File
|
|
79
|
+
|
|
80
|
+
<!-- DOCS:START wordcount -->
|
|
81
|
+
This is a test document with multiple words to count.
|
|
82
|
+
<!-- DOCS:END -->
|
|
83
|
+
`
|
|
84
|
+
|
|
85
|
+
await fs.writeFile(testFile, content)
|
|
86
|
+
|
|
87
|
+
/** @type {ProcessFileOptions} */
|
|
88
|
+
const options = {
|
|
89
|
+
srcPath: testFile,
|
|
90
|
+
dryRun: true,
|
|
91
|
+
transforms: mockTransforms
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const result = await processFile(options)
|
|
95
|
+
|
|
96
|
+
assert.is(result.isChanged, true)
|
|
97
|
+
assert.ok(result.updatedContents.includes('Word count: 10'))
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('should write to output file when not dry run', async () => {
|
|
101
|
+
const content = `
|
|
102
|
+
<!-- DOCS:START uppercase -->
|
|
103
|
+
test content
|
|
104
|
+
<!-- DOCS:END -->
|
|
105
|
+
`
|
|
106
|
+
|
|
107
|
+
/** @type {ProcessFileOptions} */
|
|
108
|
+
const options = {
|
|
109
|
+
content,
|
|
110
|
+
outputPath: outputFile,
|
|
111
|
+
transforms: mockTransforms
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const result = await processFile(options)
|
|
115
|
+
|
|
116
|
+
console.log('result', result)
|
|
117
|
+
assert.is(result.isChanged, true, 'isChanged')
|
|
118
|
+
assert.is(result.isNewPath, true, 'isNewPath')
|
|
119
|
+
|
|
120
|
+
// Check output file was created
|
|
121
|
+
const outputContent = await fs.readFile(outputFile, 'utf8')
|
|
122
|
+
assert.ok(outputContent.includes('TEST CONTENT'))
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
test('should apply transforms to source file when applyTransformsToSource is true', async () => {
|
|
126
|
+
const content = `
|
|
127
|
+
<!-- DOCS:START uppercase -->
|
|
128
|
+
source content
|
|
129
|
+
<!-- DOCS:END -->
|
|
130
|
+
`
|
|
131
|
+
|
|
132
|
+
await fs.writeFile(testFile, content)
|
|
133
|
+
|
|
134
|
+
/** @type {ProcessFileOptions} */
|
|
135
|
+
const options = {
|
|
136
|
+
srcPath: testFile,
|
|
137
|
+
applyTransformsToSource: true,
|
|
138
|
+
transforms: mockTransforms
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const result = await processFile(options)
|
|
142
|
+
|
|
143
|
+
assert.is(result.isChanged, true)
|
|
144
|
+
|
|
145
|
+
// Check source file was updated
|
|
146
|
+
const updatedContent = await fs.readFile(testFile, 'utf8')
|
|
147
|
+
assert.ok(updatedContent.includes('SOURCE CONTENT'))
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
test('should detect syntax from file extension', async () => {
|
|
151
|
+
const jsFile = path.join(testDir, 'test.js')
|
|
152
|
+
const content = `
|
|
153
|
+
/* DOCS:START uppercase */
|
|
154
|
+
const test = 'hello world'
|
|
155
|
+
/* DOCS:END */
|
|
156
|
+
`
|
|
157
|
+
|
|
158
|
+
await fs.writeFile(jsFile, content)
|
|
159
|
+
|
|
160
|
+
/** @type {ProcessFileOptions} */
|
|
161
|
+
const options = {
|
|
162
|
+
srcPath: jsFile,
|
|
163
|
+
dryRun: true,
|
|
164
|
+
open: 'DOCS:START',
|
|
165
|
+
close: 'DOCS:END',
|
|
166
|
+
transforms: mockTransforms
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const result = await processFile(options)
|
|
170
|
+
|
|
171
|
+
assert.is(result.isChanged, true)
|
|
172
|
+
assert.ok(result.updatedContents.includes(`CONST TEST = 'HELLO WORLD'`))
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
test('should handle missing transforms', async () => {
|
|
176
|
+
const content = `
|
|
177
|
+
<!-- DOCS:START nonexistent -->
|
|
178
|
+
test content
|
|
179
|
+
<!-- DOCS:END -->
|
|
180
|
+
`
|
|
181
|
+
|
|
182
|
+
/** @type {ProcessFileOptions} */
|
|
183
|
+
const options = {
|
|
184
|
+
content,
|
|
185
|
+
dryRun: true,
|
|
186
|
+
transforms: {}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const result = await processFile(options)
|
|
190
|
+
|
|
191
|
+
assert.is(result.missingTransforms.length, 1)
|
|
192
|
+
assert.is(result.isChanged, false)
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
test('should handle both srcPath and content error', async () => {
|
|
196
|
+
/** @type {ProcessFileOptions} */
|
|
197
|
+
const options = {
|
|
198
|
+
srcPath: '/some/path',
|
|
199
|
+
content: 'some content',
|
|
200
|
+
dryRun: true
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
await processFile(options)
|
|
205
|
+
assert.unreachable('Should have thrown an error')
|
|
206
|
+
} catch (error) {
|
|
207
|
+
assert.ok(error.message.includes('Can\'t set both "srcPath" & "content"'))
|
|
208
|
+
}
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
test('should handle file with output directory', async () => {
|
|
212
|
+
const content = `
|
|
213
|
+
<!-- DOCS:START uppercase -->
|
|
214
|
+
directory test
|
|
215
|
+
<!-- DOCS:END -->
|
|
216
|
+
`
|
|
217
|
+
|
|
218
|
+
const outputDir = path.join(testDir, 'output')
|
|
219
|
+
const expectedOutput = path.join(outputDir, 'result.md')
|
|
220
|
+
|
|
221
|
+
/** @type {ProcessFileOptions} */
|
|
222
|
+
const options = {
|
|
223
|
+
content,
|
|
224
|
+
outputPath: expectedOutput,
|
|
225
|
+
output: {
|
|
226
|
+
directory: outputDir
|
|
227
|
+
},
|
|
228
|
+
transforms: mockTransforms
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const result = await processFile(options)
|
|
232
|
+
|
|
233
|
+
assert.is(result.isChanged, true)
|
|
234
|
+
|
|
235
|
+
// Check output file exists in output directory
|
|
236
|
+
const outputContent = await fs.readFile(expectedOutput, 'utf8')
|
|
237
|
+
assert.ok(outputContent.includes('DIRECTORY TEST'))
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
test('Custom patterns', async () => {
|
|
241
|
+
const content = `
|
|
242
|
+
TotallyCustom uppercase
|
|
243
|
+
content with comments
|
|
244
|
+
BlockHere
|
|
245
|
+
`
|
|
246
|
+
|
|
247
|
+
/** @type {ProcessFileOptions} */
|
|
248
|
+
const options = {
|
|
249
|
+
content,
|
|
250
|
+
outputPath: outputFile,
|
|
251
|
+
removeComments: true,
|
|
252
|
+
customPatterns: {
|
|
253
|
+
openPattern: /TotallyCustom (.*)/g,
|
|
254
|
+
closePattern: /BlockHere/g,
|
|
255
|
+
},
|
|
256
|
+
transforms: Object.assign({}, mockTransforms, {
|
|
257
|
+
custom: (api) => {
|
|
258
|
+
return api.content.toUpperCase()
|
|
259
|
+
}
|
|
260
|
+
})
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const result = await processFile(options)
|
|
264
|
+
|
|
265
|
+
console.log('result', result)
|
|
266
|
+
|
|
267
|
+
assert.is(result.stripComments, true)
|
|
268
|
+
assert.is(result.isNewPath, true)
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
test.run()
|
package/test-manual.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const { processFile } = require('./src')
|
|
2
|
+
|
|
3
|
+
async function testBasic() {
|
|
4
|
+
console.log('Testing basic functionality...')
|
|
5
|
+
|
|
6
|
+
const content = `
|
|
7
|
+
# Test Document
|
|
8
|
+
|
|
9
|
+
<!-- DOCS:START uppercase -->
|
|
10
|
+
hello world
|
|
11
|
+
<!-- DOCS:END -->
|
|
12
|
+
|
|
13
|
+
Some other content.
|
|
14
|
+
`
|
|
15
|
+
|
|
16
|
+
const options = {
|
|
17
|
+
content,
|
|
18
|
+
dryRun: true,
|
|
19
|
+
transforms: {
|
|
20
|
+
uppercase: (api) => {
|
|
21
|
+
return api.content.toUpperCase()
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const result = await processFile(options)
|
|
28
|
+
console.log('✅ Test passed!')
|
|
29
|
+
console.log('isChanged:', result.isChanged)
|
|
30
|
+
console.log('transforms applied:', result.transforms.length)
|
|
31
|
+
console.log('Updated content contains HELLO WORLD:', result.updatedContents.includes('HELLO WORLD'))
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error('❌ Test failed:', error.message)
|
|
34
|
+
console.error(error.stack)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
testBasic()
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": ["ES2020"],
|
|
6
|
+
"allowJs": true,
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"emitDeclarationOnly": true,
|
|
9
|
+
"outDir": "types",
|
|
10
|
+
"strict": false,
|
|
11
|
+
"noImplicitAny": false,
|
|
12
|
+
"skipLibCheck": true,
|
|
13
|
+
"forceConsistentCasingInFileNames": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*"],
|
|
16
|
+
"exclude": ["node_modules", "test", "types"]
|
|
17
|
+
}
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export type ProcessContentConfig = import("comment-block-transformer").ProcessContentConfig;
|
|
2
|
+
export type ProcessContentResult = import("comment-block-transformer").ProcessContentResult;
|
|
3
|
+
/**
|
|
4
|
+
* Extended configuration for processing files with additional file-specific options
|
|
5
|
+
*/
|
|
6
|
+
export type ProcessFileOptions = ProcessContentConfig & {
|
|
7
|
+
content?: string;
|
|
8
|
+
srcPath?: string;
|
|
9
|
+
outputPath?: string;
|
|
10
|
+
dryRun?: boolean;
|
|
11
|
+
patterns?: {
|
|
12
|
+
openPattern?: RegExp;
|
|
13
|
+
closePattern?: RegExp;
|
|
14
|
+
};
|
|
15
|
+
output?: {
|
|
16
|
+
directory?: string;
|
|
17
|
+
};
|
|
18
|
+
outputDir?: string;
|
|
19
|
+
applyTransformsToSource?: boolean;
|
|
20
|
+
open?: string;
|
|
21
|
+
close?: string;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Result of processing a file with comment block replacements
|
|
25
|
+
*/
|
|
26
|
+
export type ProcessFileResult = {
|
|
27
|
+
/**
|
|
28
|
+
* - Whether the content was modified
|
|
29
|
+
*/
|
|
30
|
+
isChanged: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* - Whether srcPath differs from outputPath
|
|
33
|
+
*/
|
|
34
|
+
isNewPath: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* - Whether comments should be stripped from output
|
|
37
|
+
*/
|
|
38
|
+
stripComments: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* - Source file path used
|
|
41
|
+
*/
|
|
42
|
+
srcPath?: string;
|
|
43
|
+
/**
|
|
44
|
+
* - Output file path used
|
|
45
|
+
*/
|
|
46
|
+
outputPath?: string;
|
|
47
|
+
/**
|
|
48
|
+
* - Array of transforms that were applied
|
|
49
|
+
*/
|
|
50
|
+
transforms: any[];
|
|
51
|
+
/**
|
|
52
|
+
* - Array of transforms that were not found
|
|
53
|
+
*/
|
|
54
|
+
missingTransforms: any[];
|
|
55
|
+
/**
|
|
56
|
+
* - Original input content
|
|
57
|
+
*/
|
|
58
|
+
originalContents: string;
|
|
59
|
+
/**
|
|
60
|
+
* - Processed output content
|
|
61
|
+
*/
|
|
62
|
+
updatedContents: string;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Process a file with comment block replacements using configured transforms
|
|
66
|
+
* @param {ProcessFileOptions} [opts={}] - Processing options
|
|
67
|
+
* @returns {Promise<ProcessContentResult>} Result object with processed content and metadata
|
|
68
|
+
*/
|
|
69
|
+
export function processFile(opts?: ProcessFileOptions): Promise<ProcessContentResult>;
|