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 CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ // @ts-ignore
2
3
  const mri = require('mri')
3
4
  const { runCli } = require('./src/cli-run')
4
5
  const argv = process.argv.slice(2)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "markdown-magic",
3
- "version": "4.0.5",
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.2",
46
- "comment-block-replacer": "0.1.4",
47
- "comment-block-transformer": "0.2.3",
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": "7d81950b7a6d27d1192b1bc2d0f19d1d5ff0f321"
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)).flat().filter(onlyUnique)
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 = ` - "${trn.transform}" at line ${line} ${location}`
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
- console.log(`FILE NOT FOUND ${codeFilePath}`)
95
- throw e
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
  }
@@ -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
- console.log(`FILE NOT FOUND ${resolvedFilePath}`)
27
- throw e
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,