markdown-magic 4.0.5 → 4.2.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/cli.js +1 -0
- package/package.json +5 -5
- package/src/cli-run.js +146 -1
- package/src/index.js +11 -4
- package/src/transforms/code/index.js +3 -2
- package/src/transforms/file.js +9 -2
- package/src/utils/fs.js +24 -1
package/cli.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "markdown-magic",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"description": "Automatically update markdown files with content from external sources",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -42,9 +42,9 @@
|
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@davidwells/md-utils": "0.0.53",
|
|
44
44
|
"color-convert": "^2.0.1",
|
|
45
|
-
"comment-block-parser": "1.1.
|
|
46
|
-
"comment-block-replacer": "0.1.
|
|
47
|
-
"comment-block-transformer": "0.2.
|
|
45
|
+
"comment-block-parser": "1.1.3",
|
|
46
|
+
"comment-block-replacer": "0.1.5",
|
|
47
|
+
"comment-block-transformer": "0.2.4",
|
|
48
48
|
"globrex": "^0.1.2",
|
|
49
49
|
"gray-matter": "^4.0.3",
|
|
50
50
|
"is-glob": "^4.0.3",
|
|
@@ -70,5 +70,5 @@
|
|
|
70
70
|
"publishConfig": {
|
|
71
71
|
"access": "public"
|
|
72
72
|
},
|
|
73
|
-
"gitHead": "
|
|
73
|
+
"gitHead": "966eb812a9044c8bcf7d5caf04a860dade0506af"
|
|
74
74
|
}
|
package/src/cli-run.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
const path = require('path')
|
|
2
|
+
const fs = require('fs')
|
|
2
3
|
const { loadConfig } = require('./utils/load-config')
|
|
3
4
|
const { findUp } = require('./utils/fs')
|
|
4
5
|
const { markdownMagic } = require('./')
|
|
6
|
+
const { processFile } = require('comment-block-replacer')
|
|
5
7
|
const { parse } = require('oparser')
|
|
8
|
+
const defaultTransforms = require('./transforms')
|
|
6
9
|
const { getGlobGroupsFromArgs } = require('./globparse')
|
|
7
10
|
// const { deepLog } = require('./utils/logs')
|
|
8
11
|
// const { uxParse } = require('./argparse/argparse')
|
|
@@ -10,6 +13,87 @@ const argv = process.argv.slice(2)
|
|
|
10
13
|
const cwd = process.cwd()
|
|
11
14
|
const defaultConfigPath = 'md.config.js'
|
|
12
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Render markdown with ANSI styling for terminal output
|
|
18
|
+
* @param {string} content
|
|
19
|
+
*/
|
|
20
|
+
// async function renderMarkdown(content) {
|
|
21
|
+
// const { render, themes } = await import('markdansi')
|
|
22
|
+
// const githubDark = {
|
|
23
|
+
// ...themes.default,
|
|
24
|
+
// heading: { color: 'white', bold: true },
|
|
25
|
+
// strong: { bold: true },
|
|
26
|
+
// emph: { italic: true },
|
|
27
|
+
// inlineCode: { color: 'yellow' },
|
|
28
|
+
// blockCode: { color: 'white' },
|
|
29
|
+
// link: { color: 'cyan', underline: true },
|
|
30
|
+
// quote: { color: 'gray', italic: true },
|
|
31
|
+
// hr: { color: 'gray', dim: true },
|
|
32
|
+
// listMarker: { color: 'blue' },
|
|
33
|
+
// tableHeader: { color: 'white', bold: true },
|
|
34
|
+
// tableCell: { color: 'white' },
|
|
35
|
+
// }
|
|
36
|
+
// return render(content, { theme: githubDark })
|
|
37
|
+
// }
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Read all data from stdin
|
|
41
|
+
* @returns {Promise<string>}
|
|
42
|
+
*/
|
|
43
|
+
function readStdin() {
|
|
44
|
+
return new Promise((resolve, reject) => {
|
|
45
|
+
let data = ''
|
|
46
|
+
process.stdin.setEncoding('utf8')
|
|
47
|
+
process.stdin.on('data', chunk => data += chunk)
|
|
48
|
+
process.stdin.on('end', () => resolve(data))
|
|
49
|
+
process.stdin.on('error', reject)
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Interpret escape sequences in string (e.g., \n -> newline)
|
|
55
|
+
* @param {string} str
|
|
56
|
+
* @returns {string}
|
|
57
|
+
*/
|
|
58
|
+
function interpretEscapes(str) {
|
|
59
|
+
if (!str) return str
|
|
60
|
+
return str.replace(/\\n/g, '\n').replace(/\\t/g, '\t')
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* JSON.stringify replacer that handles RegExp objects
|
|
65
|
+
* @param {string} key
|
|
66
|
+
* @param {any} value
|
|
67
|
+
*/
|
|
68
|
+
function jsonReplacer(key, value) {
|
|
69
|
+
if (value instanceof RegExp) {
|
|
70
|
+
return value.toString()
|
|
71
|
+
}
|
|
72
|
+
return value
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Check if string looks like markdown content vs a file path
|
|
77
|
+
* @param {string} str
|
|
78
|
+
* @returns {boolean}
|
|
79
|
+
*/
|
|
80
|
+
function isMarkdownContent(str) {
|
|
81
|
+
if (!str) return false
|
|
82
|
+
// Has newlines (real or escaped) = likely content
|
|
83
|
+
if (str.includes('\n') || str.includes('\\n')) return true
|
|
84
|
+
// Has markdown comment blocks = likely content
|
|
85
|
+
if (str.includes('<!--')) return true
|
|
86
|
+
// Check if file exists
|
|
87
|
+
try {
|
|
88
|
+
if (fs.existsSync(str)) return false
|
|
89
|
+
} catch (e) {
|
|
90
|
+
// ignore
|
|
91
|
+
}
|
|
92
|
+
// Has markdown heading at start = likely content
|
|
93
|
+
if (str.startsWith('#')) return true
|
|
94
|
+
return false
|
|
95
|
+
}
|
|
96
|
+
|
|
13
97
|
async function getBaseDir(opts = {}) {
|
|
14
98
|
const { currentDir = cwd } = opts
|
|
15
99
|
const gitDir = await findUp(currentDir, '.git')
|
|
@@ -29,6 +113,10 @@ Options:
|
|
|
29
113
|
--files, --file Files or glob patterns to process
|
|
30
114
|
--config Path to config file (default: md.config.js)
|
|
31
115
|
--output Output directory
|
|
116
|
+
--open Opening comment keyword (default: docs)
|
|
117
|
+
--close Closing comment keyword (default: /docs)
|
|
118
|
+
--json Output full result as JSON
|
|
119
|
+
--pretty Render output with ANSI styling
|
|
32
120
|
--dry Dry run - show what would be changed
|
|
33
121
|
--debug Show debug output
|
|
34
122
|
--help, -h Show this help message
|
|
@@ -38,16 +126,73 @@ Examples:
|
|
|
38
126
|
md-magic README.md
|
|
39
127
|
md-magic --files "**/*.md"
|
|
40
128
|
md-magic --config ./my-config.js
|
|
129
|
+
|
|
130
|
+
Stdin/stdout mode:
|
|
131
|
+
cat file.md | md-magic
|
|
132
|
+
echo "<!-- docs TOC --><!-- /docs -->" | md-magic
|
|
133
|
+
md-magic "# Title\\n<!-- docs TOC --><!-- /docs -->"
|
|
41
134
|
`)
|
|
42
135
|
return
|
|
43
136
|
}
|
|
44
137
|
|
|
45
138
|
if (options.version || options.v) {
|
|
139
|
+
// @ts-ignore
|
|
46
140
|
const pkg = require('../package.json')
|
|
47
|
-
console.log(pkg.version)
|
|
141
|
+
console.log(`${pkg.name} v${pkg.version}`)
|
|
48
142
|
return
|
|
49
143
|
}
|
|
50
144
|
|
|
145
|
+
// Check if first positional arg is markdown content (before stdin check)
|
|
146
|
+
// Handle case where mri assigns content to a flag (e.g., --json '# content')
|
|
147
|
+
let firstArg = options._ && options._[0]
|
|
148
|
+
const outputJson = options.json === true || (typeof options.json === 'string' && isMarkdownContent(options.json))
|
|
149
|
+
if (typeof options.json === 'string' && isMarkdownContent(options.json)) {
|
|
150
|
+
firstArg = options.json
|
|
151
|
+
}
|
|
152
|
+
const openKeyword = options.open || 'docs'
|
|
153
|
+
const closeKeyword = options.close || (options.open && options.open !== 'docs' ? `/${options.open}` : '/docs')
|
|
154
|
+
if (firstArg && isMarkdownContent(firstArg)) {
|
|
155
|
+
const content = interpretEscapes(firstArg)
|
|
156
|
+
const result = await processFile({
|
|
157
|
+
content,
|
|
158
|
+
syntax: 'md',
|
|
159
|
+
open: openKeyword,
|
|
160
|
+
close: closeKeyword,
|
|
161
|
+
transforms: defaultTransforms,
|
|
162
|
+
dryRun: true,
|
|
163
|
+
})
|
|
164
|
+
console.log('result', result)
|
|
165
|
+
if (outputJson) {
|
|
166
|
+
console.log(JSON.stringify(result, jsonReplacer, 2))
|
|
167
|
+
} else {
|
|
168
|
+
console.log(result.updatedContents)
|
|
169
|
+
}
|
|
170
|
+
return
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Check for stdin pipe (when no positional file args provided)
|
|
174
|
+
const hasNoFileArgs = !options._ || options._.length === 0
|
|
175
|
+
const hasPipedInput = !process.stdin.isTTY && hasNoFileArgs
|
|
176
|
+
if (hasPipedInput) {
|
|
177
|
+
const content = await readStdin()
|
|
178
|
+
if (content.trim()) {
|
|
179
|
+
const result = await processFile({
|
|
180
|
+
content,
|
|
181
|
+
syntax: 'md',
|
|
182
|
+
open: openKeyword,
|
|
183
|
+
close: closeKeyword,
|
|
184
|
+
transforms: defaultTransforms,
|
|
185
|
+
dryRun: true, // Don't write files
|
|
186
|
+
})
|
|
187
|
+
if (outputJson) {
|
|
188
|
+
console.log(JSON.stringify(result, jsonReplacer, 2))
|
|
189
|
+
} else {
|
|
190
|
+
console.log(result.updatedContents)
|
|
191
|
+
}
|
|
192
|
+
return
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
51
196
|
let configFile
|
|
52
197
|
let opts = {}
|
|
53
198
|
|
package/src/index.js
CHANGED
|
@@ -13,7 +13,7 @@ const remoteTransform = require('./transforms/remote')
|
|
|
13
13
|
const installTransform = require('./transforms/install')
|
|
14
14
|
const { getSyntaxInfo } = require('./utils/syntax')
|
|
15
15
|
const { onlyUnique, getCodeLocation, pluralize } = require('./utils')
|
|
16
|
-
const { readFile, resolveOutputPath, resolveFlatPath } = require('./utils/fs')
|
|
16
|
+
const { readFile, resolveOutputPath, resolveFlatPath, hasIgnoreFile } = require('./utils/fs')
|
|
17
17
|
const stringBreak = require('./utils/string-break')
|
|
18
18
|
const { processFile } = require('comment-block-replacer')
|
|
19
19
|
const { blockTransformer } = require('comment-block-transformer')
|
|
@@ -257,7 +257,10 @@ async function markdownMagic(globOrOpts = {}, options = {}) {
|
|
|
257
257
|
|
|
258
258
|
let files = []
|
|
259
259
|
try {
|
|
260
|
-
files = (await Promise.all(pathsPromise))
|
|
260
|
+
files = (await Promise.all(pathsPromise))
|
|
261
|
+
.flat()
|
|
262
|
+
.filter(onlyUnique)
|
|
263
|
+
.filter((f) => !hasIgnoreFile(f))
|
|
261
264
|
// opts.files = files
|
|
262
265
|
} catch (e) {
|
|
263
266
|
// console.log(e.message)
|
|
@@ -541,12 +544,16 @@ async function markdownMagic(globOrOpts = {}, options = {}) {
|
|
|
541
544
|
let planMsg = `${count} Found ${transformsToRun.length} transforms in ${item.srcPath}`
|
|
542
545
|
planTotal = planTotal + transformsToRun.length
|
|
543
546
|
// logger(`Found ${transformsToRun.length} transforms in ${item.srcPath}`)
|
|
547
|
+
const maxPrefixLen = Math.max(...transformsToRun.map((t) => {
|
|
548
|
+
return `"${t.transform}" on line ${t.lines[0]}`.length
|
|
549
|
+
}))
|
|
544
550
|
transformsToRun.forEach((trn) => {
|
|
545
551
|
const line = trn.lines[0]
|
|
552
|
+
const prefix = `"${trn.transform}" on line ${line}`
|
|
553
|
+
const paddedPrefix = prefix.padEnd(maxPrefixLen)
|
|
546
554
|
const location = getCodeLocation(item.srcPath, line)
|
|
547
|
-
const planData = ` -
|
|
555
|
+
const planData = ` - ${paddedPrefix} → ${location}`
|
|
548
556
|
planMsg += `\n${planData}`
|
|
549
|
-
// logger(` - "${trn.transform}" at line ${trn.lines[0]}`)
|
|
550
557
|
})
|
|
551
558
|
const newLine = plan.length !== i + 1 ? '\n' : ''
|
|
552
559
|
return `${planMsg}${newLine}`
|
|
@@ -91,8 +91,9 @@ module.exports = async function CODE(api) {
|
|
|
91
91
|
if (src === './relative/path/to/code.js') {
|
|
92
92
|
return api.content
|
|
93
93
|
} else {
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
const err = new Error(`FILE NOT FOUND: ${codeFilePath}\n Referenced in: ${srcPath}\n src="${src}"`)
|
|
95
|
+
err.code = 'ENOENT'
|
|
96
|
+
throw err
|
|
96
97
|
}
|
|
97
98
|
}
|
|
98
99
|
}
|
package/src/transforms/file.js
CHANGED
|
@@ -23,8 +23,15 @@ module.exports = function FILE(api) {
|
|
|
23
23
|
if (options.src === './path/to/file') {
|
|
24
24
|
return api.content
|
|
25
25
|
}
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
const err = new Error(`FILE NOT FOUND: ${resolvedFilePath}
|
|
27
|
+
|
|
28
|
+
Referenced in: ${srcPath}
|
|
29
|
+
|
|
30
|
+
Via the "src" attribute: src="${options.src}"
|
|
31
|
+
|
|
32
|
+
`)
|
|
33
|
+
err.code = 'ENOENT'
|
|
34
|
+
throw err
|
|
28
35
|
}
|
|
29
36
|
}
|
|
30
37
|
|
package/src/utils/fs.js
CHANGED
|
@@ -192,11 +192,34 @@ function isLocalPath(filePath) {
|
|
|
192
192
|
return _isLocalPath(filePath)
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
+
const fsSync = require('fs')
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Check if file's directory or any parent has .md-ignore
|
|
199
|
+
* @param {string} filePath - Absolute path to file
|
|
200
|
+
* @returns {boolean} True if should be ignored
|
|
201
|
+
*/
|
|
202
|
+
function hasIgnoreFile(filePath) {
|
|
203
|
+
let dir = dirname(filePath)
|
|
204
|
+
const root = resolve('/')
|
|
205
|
+
while (dir !== root) {
|
|
206
|
+
const ignoreFile = join(dir, '.md-ignore')
|
|
207
|
+
if (fsSync.existsSync(ignoreFile)) {
|
|
208
|
+
return true
|
|
209
|
+
}
|
|
210
|
+
const parent = dirname(dir)
|
|
211
|
+
if (parent === dir) break
|
|
212
|
+
dir = parent
|
|
213
|
+
}
|
|
214
|
+
return false
|
|
215
|
+
}
|
|
216
|
+
|
|
195
217
|
module.exports = {
|
|
196
218
|
isLocalPath,
|
|
197
219
|
writeFile,
|
|
198
|
-
readFile,
|
|
220
|
+
readFile,
|
|
199
221
|
findUp,
|
|
222
|
+
hasIgnoreFile,
|
|
200
223
|
resolveOutputPath,
|
|
201
224
|
resolveFlatPath,
|
|
202
225
|
resolveCommonParent,
|