@mermaid-js/mermaid-cli 9.2.2 → 9.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mermaid-js/mermaid-cli",
3
- "version": "9.2.2",
3
+ "version": "9.4.0",
4
4
  "description": "Command-line interface for mermaid",
5
5
  "license": "MIT",
6
6
  "repository": "git@github.com:mermaid-js/mermaid-cli.git",
@@ -10,7 +10,7 @@
10
10
  "mmdc": "./src/cli.js"
11
11
  },
12
12
  "engines": {
13
- "node": ">=14.1.0"
13
+ "node": "^14.13 || >=16.0"
14
14
  },
15
15
  "exports": "./src/index.js",
16
16
  "scripts": {
@@ -22,7 +22,7 @@
22
22
  },
23
23
  "dependencies": {
24
24
  "chalk": "^5.0.1",
25
- "commander": "^9.0.0",
25
+ "commander": "^10.0.0",
26
26
  "puppeteer": "^19.0.0"
27
27
  },
28
28
  "devDependencies": {
@@ -31,9 +31,9 @@
31
31
  "mermaid": "^9.2.2",
32
32
  "jest": "^29.0.1",
33
33
  "standard": "^17.0.0",
34
- "vite": "^3.2.3",
34
+ "vite": "^4.0.3",
35
35
  "vite-plugin-singlefile": "^0.13.1",
36
- "vite-svg-loader": "^3.6.0",
36
+ "vite-svg-loader": "^4.0.0",
37
37
  "yarn-upgrade-all": "^0.7.0"
38
38
  },
39
39
  "files": [
package/src/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Command } from 'commander'
1
+ import { Command, Option, InvalidArgumentError } from 'commander'
2
2
  import chalk from 'chalk'
3
3
  import fs from 'fs'
4
4
  import path from 'path'
@@ -18,7 +18,7 @@ const error = message => {
18
18
  }
19
19
 
20
20
  const warn = message => {
21
- console.log(chalk.yellow(`\n${message}\n`))
21
+ console.warn(chalk.yellow(`\n${message}\n`))
22
22
  }
23
23
 
24
24
  const checkConfigFile = file => {
@@ -27,8 +27,6 @@ const checkConfigFile = file => {
27
27
  }
28
28
  }
29
29
 
30
- const inputPipedFromStdin = () => fs.fstatSync(0).isFIFO()
31
-
32
30
  const getInputData = async inputFile => new Promise((resolve, reject) => {
33
31
  // if an input file has been specified using '-i', it takes precedence over
34
32
  // piping from stdin
@@ -60,23 +58,40 @@ const getInputData = async inputFile => new Promise((resolve, reject) => {
60
58
  })
61
59
  })
62
60
 
61
+ /**
62
+ * Commander parser that converts a string to an integer.
63
+ *
64
+ * @param {string} value - The value from commander.
65
+ * @param {*} _unused - Unused.
66
+ * @returns {number} The value parsed as a number.
67
+ * @throws {InvalidArgumentError} If the arg is not valid.
68
+ * @see https://github.com/tj/commander.js/wiki/Class:-Option#argparserfn
69
+ */
70
+ function parseCommanderInt (value, _unused) {
71
+ const parsedValue = parseInt(value, 10)
72
+ if (isNaN(parsedValue) || parsedValue < 1) {
73
+ throw new InvalidArgumentError('Not an positive integer.')
74
+ }
75
+ return parsedValue
76
+ }
77
+
63
78
  async function cli () {
64
79
  const commander = new Command()
65
80
  commander
66
81
  .version(pkg.version)
67
- .option('-t, --theme [theme]', 'Theme of the chart, could be default, forest, dark or neutral. Optional. Default: default', /^default|forest|dark|neutral$/, 'default')
68
- .option('-w, --width [width]', 'Width of the page. Optional. Default: 800', /^\d+$/, '800')
69
- .option('-H, --height [height]', 'Height of the page. Optional. Default: 600', /^\d+$/, '600')
70
- .option('-i, --input <input>', 'Input mermaid file. Files ending in .md will be treated as Markdown and all charts (e.g. ```mermaid (...)```) will be extracted and generated. Required.')
82
+ .addOption(new Option('-t, --theme [theme]', 'Theme of the chart').choices(['default', 'forest', 'dark', 'neutral']).default('default'))
83
+ .addOption(new Option('-w, --width [width]', 'Width of the page').argParser(parseCommanderInt).default(800))
84
+ .addOption(new Option('-H, --height [height]', 'Height of the page').argParser(parseCommanderInt).default(600))
85
+ .option('-i, --input <input>', 'Input mermaid file. Files ending in .md will be treated as Markdown and all charts (e.g. ```mermaid (...)```) will be extracted and generated. Use `-` to read from stdin.')
71
86
  .option('-o, --output [output]', 'Output file. It should be either md, svg, png or pdf. Optional. Default: input + ".svg"')
72
- .option('-e, --outputFormat <format>', 'Output format for the generated image. It should be either svg, png or pdf. Optional. Default: output file extension')
73
- .option('-b, --backgroundColor [backgroundColor]', 'Background color for pngs/svgs (not pdfs). Example: transparent, red, \'#F0F0F0\'. Optional. Default: white')
74
- .option('-c, --configFile [configFile]', 'JSON configuration file for mermaid. Optional')
75
- .option('-C, --cssFile [cssFile]', 'CSS file for the page. Optional')
76
- .option('-s, --scale [scale]', 'Puppeteer scale factor, default 1. Optional')
87
+ .addOption(new Option('-e, --outputFormat [format]', 'Output format for the generated image.').choices(['svg', 'png', 'pdf']).default(null, 'Loaded from the output file extension'))
88
+ .addOption(new Option('-b, --backgroundColor [backgroundColor]', 'Background color for pngs/svgs (not pdfs). Example: transparent, red, \'#F0F0F0\'.').default('white'))
89
+ .option('-c, --configFile [configFile]', 'JSON configuration file for mermaid.')
90
+ .option('-C, --cssFile [cssFile]', 'CSS file for the page.')
91
+ .addOption(new Option('-s, --scale [scale]', 'Puppeteer scale factor').argParser(parseCommanderInt).default(1))
77
92
  .option('-f, --pdfFit [pdfFit]', 'Scale PDF to fit chart')
78
93
  .option('-q, --quiet', 'Suppress log output')
79
- .option('-p --puppeteerConfigFile [puppeteerConfigFile]', 'JSON configuration file for puppeteer. Optional')
94
+ .option('-p --puppeteerConfigFile [puppeteerConfigFile]', 'JSON configuration file for puppeteer.')
80
95
  .parse(process.argv)
81
96
 
82
97
  const options = commander.opts()
@@ -84,12 +99,15 @@ async function cli () {
84
99
  let { theme, width, height, input, output, outputFormat, backgroundColor, configFile, cssFile, puppeteerConfigFile, scale, pdfFit, quiet } = options
85
100
 
86
101
  // check input file
87
- if (!(input || inputPipedFromStdin())) {
88
- console.error(chalk.red('\nPlease specify input file: -i <input>\n'))
89
- // Log to stderr, and return with error exitCode
90
- commander.help({ error: true })
91
- }
92
- if (input && !fs.existsSync(input)) {
102
+ if (!input) {
103
+ warn('No input file specfied, reading from stdin. ' +
104
+ 'If you want to specify an input file, please use `-i <input>.` ' +
105
+ 'You can use `-i -` to read from stdin and to suppress this warning.'
106
+ )
107
+ } else if (input === '-') {
108
+ // `--input -` means read from stdin, but suppress the above warning
109
+ input = undefined
110
+ } else if (!fs.existsSync(input)) {
93
111
  error(`Input file "${input}" doesn't exist`)
94
112
  }
95
113
 
@@ -104,8 +122,8 @@ async function cli () {
104
122
  output = input ? (`${input}.svg`) : 'out.svg'
105
123
  }
106
124
  }
107
- if (!/\.(?:svg|png|pdf|md)$/.test(output)) {
108
- error('Output file must end with ".md", ".svg", ".png" or ".pdf"')
125
+ if (!/\.(?:svg|png|pdf|md|markdown)$/.test(output)) {
126
+ error('Output file must end with ".md"/".markdown", ".svg", ".png" or ".pdf"')
109
127
  }
110
128
  const outputDir = path.dirname(output)
111
129
  if (!fs.existsSync(outputDir)) {
@@ -133,19 +151,13 @@ async function cli () {
133
151
  myCSS = fs.readFileSync(cssFile, 'utf-8')
134
152
  }
135
153
 
136
- // normalize args
137
- width = parseInt(width)
138
- height = parseInt(height)
139
- backgroundColor = backgroundColor || 'white'
140
- const deviceScaleFactor = parseInt(scale || 1, 10)
141
-
142
154
  await run(
143
155
  input, output, {
144
156
  puppeteerConfig,
145
157
  quiet,
146
158
  outputFormat,
147
159
  parseMMDOptions: {
148
- mermaidConfig, backgroundColor, myCSS, pdfFit, viewport: { width, height, deviceScaleFactor }
160
+ mermaidConfig, backgroundColor, myCSS, pdfFit, viewport: { width, height, deviceScaleFactor: scale }
149
161
  }
150
162
  }
151
163
  )
@@ -335,10 +347,11 @@ function markdownImage ({ url, title, alt }) {
335
347
  /**
336
348
  * Renders a mermaid diagram or mermaid markdown file.
337
349
  *
338
- * @param {`${string}.md` | string} [input] - If this ends with `.md`, path to a markdown file containing mermaid.
350
+ * @param {`${string}.${"md" | "markdown"}` | string} [input] - If this ends with `.md`/`.markdown`,
351
+ * path to a markdown file containing mermaid.
339
352
  * If this is a string, loads the mermaid definition from the given file.
340
353
  * If this is `undefined`, loads the mermaid definition from stdin.
341
- * @param {`${string}.${"md" | "svg" | "png" | "pdf"}`} output - Path to the output file.
354
+ * @param {`${string}.${"md" | "markdown" | "svg" | "png" | "pdf"}`} output - Path to the output file.
342
355
  * @param {Object} [opts] - Options
343
356
  * @param {puppeteer.LaunchOptions} [opts.puppeteerConfig] - Puppeteer launch options.
344
357
  * @param {boolean} [opts.quiet] - If set, suppress log output.
@@ -353,14 +366,15 @@ async function run (input, output, { puppeteerConfig = {}, quiet = false, output
353
366
  }
354
367
  }
355
368
 
356
- const mermaidChartsInMarkdown = /^\s*```(?:mermaid)(\r?\n([\s\S]*?))```\s*$/
369
+ // TODO: should we use a Markdown parser like remark instead of rolling our own parser?
370
+ const mermaidChartsInMarkdown = /^[^\S\n]*```(?:mermaid)(\r?\n([\s\S]*?))```[^\S\n]*$/
357
371
  const mermaidChartsInMarkdownRegexGlobal = new RegExp(mermaidChartsInMarkdown, 'gm')
358
372
  const browser = await puppeteer.launch(puppeteerConfig)
359
373
  try {
360
374
  if (!outputFormat) {
361
375
  outputFormat = path.extname(output).replace('.', '')
362
376
  }
363
- if (outputFormat === 'md') {
377
+ if (outputFormat === 'md' || outputFormat === 'markdown') {
364
378
  // fallback to svg in case no outputFormat is given and output file is MD
365
379
  outputFormat = 'svg'
366
380
  }
@@ -369,7 +383,7 @@ async function run (input, output, { puppeteerConfig = {}, quiet = false, output
369
383
  }
370
384
 
371
385
  const definition = await getInputData(input)
372
- if (/\.md$/.test(input)) {
386
+ if (/\.(md|markdown)$/.test(input)) {
373
387
  const imagePromises = []
374
388
  for (const mermaidCodeblockMatch of definition.matchAll(mermaidChartsInMarkdownRegexGlobal)) {
375
389
  const mermaidDefinition = mermaidCodeblockMatch[1]
@@ -379,7 +393,10 @@ async function run (input, output, { puppeteerConfig = {}, quiet = false, output
379
393
  // I.e. if "out.png", use "out-1.png", "out-2.png", etc
380
394
  // If it is an output `.md` file, use that to base .svg numbered diagrams on
381
395
  // I.e. if "out.md". use "out-1.svg", "out-2.svg", etc
382
- const outputFile = output.replace(/(\.(md|png|svg|pdf))$/, `-${imagePromises.length + 1}$1`).replace(/(\.md)$/, `.${outputFormat}`)
396
+ const outputFile = output.replace(
397
+ /(\.(md|markdown|png|svg|pdf))$/,
398
+ `-${imagePromises.length + 1}$1`
399
+ ).replace(/\.(md|markdown)$/, `.${outputFormat}`)
383
400
  const outputFileRelative = `./${path.relative(path.dirname(path.resolve(output)), path.resolve(outputFile))}`
384
401
 
385
402
  const imagePromise = (async () => {
@@ -404,7 +421,7 @@ async function run (input, output, { puppeteerConfig = {}, quiet = false, output
404
421
 
405
422
  const images = await Promise.all(imagePromises)
406
423
 
407
- if (/\.md$/.test(output)) {
424
+ if (/\.(md|markdown)$/.test(output)) {
408
425
  const outDefinition = definition.replace(mermaidChartsInMarkdownRegexGlobal, (_mermaidMd) => {
409
426
  // pop first image from front of array
410
427
  const { url, title, alt } = images.shift()