markshell 1.6.0 → 1.8.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/bin/markshell.js CHANGED
@@ -36,6 +36,51 @@ if (args.length === 0 || args.indexOf('--help') !== -1) {
36
36
  printHelp();
37
37
  }
38
38
 
39
+ // Handle --example flag to run interactive demos
40
+ const exampleIdx = args.indexOf('--example');
41
+ if (exampleIdx !== -1) {
42
+ const path = require('path');
43
+ const exampleName = args[exampleIdx + 1];
44
+
45
+ const examples = {
46
+ 'dynamic-loading': path.join(__dirname, '../examples/dynamic-loading-demo.js'),
47
+ 'syntax-colors': path.join(__dirname, '../examples/syntax-colors-demo.js'),
48
+ 'demo': path.join(__dirname, '../DEMO.md')
49
+ };
50
+
51
+ if (!exampleName || !examples[exampleName]) {
52
+ console.log(`Markshell - Version ${pkg.version}
53
+
54
+ ${chalk.yellow.bold('Available examples:')}
55
+
56
+ ${chalk.cyan('markshell --example dynamic-loading')}
57
+ Interactive demo showing dynamic language loading with statistics
58
+
59
+ ${chalk.cyan('markshell --example syntax-colors')}
60
+ Visual demonstration of syntax highlighting colors
61
+
62
+ ${chalk.cyan('markshell --example demo')}
63
+ Comprehensive demo showcasing all markdown features and 25+ languages
64
+ `);
65
+ process.exit(0);
66
+ }
67
+
68
+ const examplePath = examples[exampleName];
69
+
70
+ if (exampleName === 'demo') {
71
+ // Render the DEMO.md file
72
+ try {
73
+ markshell.toConsole(examplePath);
74
+ } catch (e) {
75
+ printError(e);
76
+ }
77
+ } else {
78
+ // Execute the example script
79
+ require(examplePath);
80
+ }
81
+ process.exit(0);
82
+ }
83
+
39
84
  const theme = args.indexOf('--theme');
40
85
 
41
86
  if (theme !== 1) {
package/docs/usage.md CHANGED
@@ -1,16 +1,34 @@
1
1
  # Usage
2
2
 
3
3
  ```bash
4
- markshell [filenpath | -f [filepath] | --filepath [filepath]]
4
+ markshell [filepath | -f [filepath] | --filepath [filepath]]
5
+ markshell --example [example-name]
6
+ markshell --help
5
7
  ```
6
8
 
7
- **filepath or -f **
9
+ ## Options
10
+
11
+ **filepath or -f**
8
12
  : path to markdown file
13
+
14
+ **--example [name]**
15
+ : run interactive examples and demos
16
+ - `dynamic-loading` - Shows dynamic language loading with statistics
17
+ - `syntax-colors` - Visual demonstration of syntax highlighting colors
18
+ - `demo` - Comprehensive showcase of all markdown features
19
+
9
20
  **--help**
10
- : what you see now
21
+ : display usage information
11
22
 
12
23
  ## Examples
13
24
 
14
25
  ```sh
26
+ # Render a markdown file
27
+ markshell ./my/markdownfile.md
15
28
  markshell --filepath './my/markdownfile.md'
16
- ```
29
+
30
+ # Run interactive examples
31
+ markshell --example dynamic-loading
32
+ markshell --example syntax-colors
33
+ markshell --example demo
34
+ ```
@@ -95,21 +95,23 @@ const offset = () => {
95
95
 
96
96
  let offsetLength;
97
97
 
98
- // console.log(typeof this.styles.indent.beforeIndent, this.styles.indent.beforeIndent);
99
- // console.log(typeof this.styles.indent.afterIndent, this.styles.indent.afterIndent);
100
- // console.log(typeof this.styles.indent.titleIndent, this.styles.indent.titleIndent);
98
+ const currentStyles = styles || formatBlock;
101
99
 
102
- offsetLength = (this.styles.indent.beforeIndent) + 2 + this.styles.indent.afterIndent + this.styles.indent.titleIndent;
100
+ // console.log(typeof currentStyles.indent.beforeIndent, currentStyles.indent.beforeIndent);
101
+ // console.log(typeof currentStyles.indent.afterIndent, currentStyles.indent.afterIndent);
102
+ // console.log(typeof currentStyles.indent.titleIndent, currentStyles.indent.titleIndent);
103
+
104
+ offsetLength = (currentStyles.indent.beforeIndent) + 2 + currentStyles.indent.afterIndent + currentStyles.indent.titleIndent;
103
105
 
104
106
  // console.log(process.stdout.columns < offsetLength, offsetLength);
105
107
 
106
108
  if (process.stdout.columns < offsetLength) {
107
109
 
108
- this.styles.indent.beforeIndent = defBeforeIndent;
109
- this.styles.indent.afterIndent = defAfterIndent;
110
- this.styles.indent.titleIndent = defTitleIndent;
110
+ currentStyles.indent.beforeIndent = defBeforeIndent;
111
+ currentStyles.indent.afterIndent = defAfterIndent;
112
+ currentStyles.indent.titleIndent = defTitleIndent;
111
113
 
112
- offsetLength = this.styles.indent.beforeIndent + 2 + this.styles.indent.afterIndent + this.styles.indent.titleIndent;
114
+ offsetLength = currentStyles.indent.beforeIndent + 2 + currentStyles.indent.afterIndent + currentStyles.indent.titleIndent;
113
115
 
114
116
  }
115
117
 
@@ -119,18 +121,19 @@ const offset = () => {
119
121
 
120
122
  const formatOutput = (title, style) => {
121
123
 
124
+ const currentStyles = styles || formatBlock;
122
125
  let indentOffset = offset();
123
126
  let outTitle = " " + title + " "
124
127
 
125
- if (this.styles.indent.beforeIndent === 0 && this.styles.indent.titleIndent === 0) {
128
+ if (currentStyles.indent.beforeIndent === 0 && currentStyles.indent.titleIndent === 0) {
126
129
  outTitle = title + " ";
127
130
  }
128
131
 
129
- return " ".repeat(this.styles.indent.beforeIndent) +
130
- style(" ".repeat(this.styles.indent.titleIndent)) +
132
+ return " ".repeat(currentStyles.indent.beforeIndent) +
133
+ style(" ".repeat(currentStyles.indent.titleIndent)) +
131
134
  chalk.bgHex('#000').white(outTitle) +
132
135
  style(" ".repeat(process.stdout.columns - indentOffset - title.length)) +
133
- chalk.bgHex('#000')(" ".repeat(this.styles.indent.afterIndent)) +
136
+ chalk.bgHex('#000')(" ".repeat(currentStyles.indent.afterIndent)) +
134
137
  EOL;
135
138
 
136
139
  }
@@ -226,13 +229,13 @@ const _getBlocks = (content, beforeIndent, afterIndent, titleIndent, useSafeStyl
226
229
  */
227
230
  const _getStyles = () => {
228
231
 
229
- if (this.styles === null || this.styles === undefined) {
232
+ if (styles === null || styles === undefined) {
230
233
 
231
234
  return formatBlock;
232
235
 
233
236
  } else {
234
237
 
235
- return this.styles;
238
+ return styles;
236
239
 
237
240
  }
238
241
 
@@ -240,7 +243,7 @@ const _getStyles = () => {
240
243
 
241
244
  const _setStyles = (styleBlock) => {
242
245
 
243
- this.styles = styleBlock;
246
+ styles = styleBlock;
244
247
 
245
248
  }
246
249
 
package/lib/index.js CHANGED
@@ -7,6 +7,15 @@ const syntaxHighlighter = require('./syntaxhighlighter');
7
7
  const admonitions = require('./admonitions');
8
8
  const EOL = require('os').EOL;
9
9
 
10
+ /**
11
+ * Escape special regex characters to prevent ReDoS attacks
12
+ * @param {string} str - String to escape
13
+ * @returns {string} - Escaped string safe for use in RegExp
14
+ */
15
+ const escapeRegex = (str) => {
16
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
17
+ };
18
+
10
19
  const theme = require('./syntaxhighlighter/themes/okaidia.theme');
11
20
  let _theme;
12
21
 
@@ -96,7 +105,7 @@ const _highlightText = (content, regexMatch, colorFunction, removeChars = null)
96
105
 
97
106
  if (removeChars === null) {
98
107
 
99
- let findAllRegex = new RegExp(match[0], "ig");
108
+ let findAllRegex = new RegExp(escapeRegex(match[0]), "ig");
100
109
 
101
110
  newContent = newContent.replace(findAllRegex, colorFunction(match[1]));
102
111
 
@@ -221,6 +230,9 @@ const _codeBlock = (content) => {
221
230
  * Formats source code blocks using PrismJS
222
231
  * @param {string} content of MarkDown file
223
232
  */
233
+ // Module-level storage for code block placeholders
234
+ let _codeBlockPlaceholders = [];
235
+
224
236
  const _highlightedCodeBlock = (content) => {
225
237
 
226
238
  let codeRegex = new RegExp(/(\`\`\`)(.*?)(\`\`\`)/igs);
@@ -230,9 +242,13 @@ const _highlightedCodeBlock = (content) => {
230
242
  return content;
231
243
  }
232
244
 
245
+ // Reset and store highlighted code blocks with placeholders to protect from inline code processing
246
+ _codeBlockPlaceholders = [];
247
+ let placeholderIndex = 0;
248
+
233
249
  newContent.forEach((element) => {
234
250
 
235
- let langRegex = new RegExp(/(\`\`\`)(.*?)(\n)/igs);
251
+ let langRegex = new RegExp(/(\`\`\`)(.*?)(\r?\n)/igs);
236
252
  let langIdentifiere = element.match(langRegex);
237
253
 
238
254
  if (langIdentifiere.length === 1) {
@@ -250,7 +266,12 @@ const _highlightedCodeBlock = (content) => {
250
266
  let hlSource = syntaxHighlighter.highlight(source, lang, this._theme.sourceCodeTheme);
251
267
  this._theme.sourceCodeTheme;
252
268
 
253
- content = content.replace(element, hlSource)
269
+ // Use a placeholder that won't be matched by inline code regex
270
+ const placeholder = `__CODEBLOCK_${placeholderIndex}__`;
271
+ _codeBlockPlaceholders.push({ placeholder, code: hlSource });
272
+ placeholderIndex++;
273
+
274
+ content = content.replace(element, placeholder)
254
275
 
255
276
  } catch (e) {
256
277
 
@@ -266,6 +287,14 @@ const _highlightedCodeBlock = (content) => {
266
287
 
267
288
  }
268
289
 
290
+ const _restoreCodeBlocks = (content) => {
291
+ // Restore code blocks from placeholders
292
+ _codeBlockPlaceholders.forEach(({ placeholder, code }) => {
293
+ content = content.replace(placeholder, code);
294
+ });
295
+ return content;
296
+ }
297
+
269
298
  /**
270
299
  * Formats Blockquote
271
300
  * @param {string} content of markdown file
@@ -379,7 +408,7 @@ const _includeExternals = (content, externalFound, baseDir, regexDelimiter) => {
379
408
 
380
409
  for (let i = 0; i < keys.length; i++) {
381
410
 
382
- var regExp = new RegExp(keys[i], 'ig');
411
+ var regExp = new RegExp(escapeRegex(keys[i]), 'ig');
383
412
  newContent = newContent.replace(regExp, replacements[keys[i]]);
384
413
 
385
414
  }
@@ -448,13 +477,23 @@ const _addBold = (content) => {
448
477
 
449
478
  /**
450
479
  * Formats italic elements in MarkDown
480
+ * Supports both underscore (_text_) and asterisk (*text*) emphasis markers
451
481
  * @param {string} content of markdown file
452
482
  */
453
483
  const _addItalic = (content) => {
454
484
 
455
- let regExp = new RegExp(/\b(\_(.*?)\_\b)/ig);
485
+ // First pass: Handle underscore emphasis
486
+ // Pattern: _text_ where underscores are not adjacent to word characters
487
+ let underscoreRegex = new RegExp(/(?<!\w)_([^_\n]+?)_(?!\w)/g);
488
+ content = _highlightText(content, underscoreRegex, this._theme.italic);
456
489
 
457
- return _highlightText(content, regExp, this._theme.italic);
490
+ // Second pass: Handle asterisk emphasis (but not bold **)
491
+ // Pattern: *text* where asterisks are not adjacent to other asterisks (to avoid matching **bold**)
492
+ // Also not adjacent to word characters on the outside to avoid matching mid-word asterisks
493
+ let asteriskRegex = new RegExp(/(?<!\*)(?<!\w)\*([^*\n]+?)\*(?!\*)(?!\w)/g);
494
+ content = _highlightText(content, asteriskRegex, this._theme.italic);
495
+
496
+ return content;
458
497
 
459
498
  }
460
499
 
@@ -484,8 +523,10 @@ const _addCode = (content) => {
484
523
  */
485
524
  const _addInlineCode = (content) => {
486
525
 
487
- // return _highlightText(content, /\`/ig, this._theme.inlineCode);
488
- return _highlightText(content, /`(.*?[^`])`/ig, this._theme.inlineCode);
526
+ // Use negative lookbehind/lookahead to avoid matching backticks that are part of triple-backtick code blocks
527
+ // (?<!`) = not preceded by backtick, (?!`) = not followed by backtick
528
+ // This ensures we only match single backticks for inline code, not triple backticks for code blocks
529
+ return _highlightText(content, /(?<!`)`([^`]+)`(?!`)/g, this._theme.inlineCode);
489
530
 
490
531
  }
491
532
 
@@ -651,7 +692,7 @@ const _toRawContent = (filepath) => {
651
692
  }
652
693
 
653
694
  try {
654
- content = _removeImages(content, /\!\[(?<alttag>.*?)\]\((.*?) \"(.*?)\"\)/ig);
695
+ content = _removeImages(content, /!\[([^\]]*)\]\(([^)"'\s]+)(?:\s+["']([^"']*)["'])?\)/g);
655
696
  } catch (e) {
656
697
  throw `Error in remove images: ${e}`;
657
698
  }
@@ -673,17 +714,26 @@ const _toRawContent = (filepath) => {
673
714
 
674
715
 
675
716
  try {
676
- // console.log('Inline code')
717
+ // Process code blocks FIRST - replaces them with placeholders
718
+ // This prevents inline code regex from matching backticks inside code blocks
719
+ content = _highlightedCodeBlock(content);
720
+ } catch (e) {
721
+ throw `Error in Highlighted Code: ${e}`;
722
+ }
723
+
724
+ try {
725
+ // Process inline code AFTER code blocks are replaced with placeholders
726
+ // At this point, code blocks are hidden as __CODEBLOCK_N__ so inline code won't match them
677
727
  content = _addInlineCode(content);
678
728
  } catch (e) {
679
729
  throw `Error in addInlineCode: ${e}`;
680
730
  }
681
731
 
682
732
  try {
683
- // console.log('Highlight code block');
684
- content = _highlightedCodeBlock(content);
733
+ // Restore code blocks from placeholders
734
+ content = _restoreCodeBlocks(content);
685
735
  } catch (e) {
686
- throw `Error in Highlighted Code: ${e}`;
736
+ throw `Error in restoring code blocks: ${e}`;
687
737
  }
688
738
 
689
739
  try {
@@ -1,5 +1,14 @@
1
1
  export var theme: any;
2
2
  export var themeTokenKeys: string[];
3
+
4
+ /**
5
+ * Information about loaded languages
6
+ */
7
+ export interface LoadedLanguagesInfo {
8
+ count: number;
9
+ languages: string[];
10
+ }
11
+
3
12
  /**
4
13
  *
5
14
  * @param {string} source
@@ -7,6 +16,20 @@ export var themeTokenKeys: string[];
7
16
  * @param {sourceTheme} outTheme
8
17
  */
9
18
  declare function _highlight(source: string, language: string, outTheme: sourceTheme): string;
19
+
20
+ /**
21
+ * Resolves language aliases to canonical names
22
+ * @param language - Language identifier (may be an alias)
23
+ * @returns Canonical language name or null
24
+ */
25
+ declare function _resolveLanguageAlias(language: string): string | null;
26
+
27
+ /**
28
+ * Get information about currently loaded languages
29
+ * @returns Statistics about loaded languages
30
+ */
31
+ declare function _getLoadedLanguagesInfo(): LoadedLanguagesInfo;
32
+
10
33
  type sourceTheme = string;
11
34
  declare namespace sourceTheme {
12
35
  const COY: string;
@@ -18,4 +41,11 @@ declare namespace sourceTheme {
18
41
  const TOMORROW: string;
19
42
  const TWILIGHT: string;
20
43
  }
21
- export { _highlight as highlight, sourceTheme as themes, sourceTheme as availableThemes };
44
+
45
+ export {
46
+ _highlight as highlight,
47
+ sourceTheme as themes,
48
+ sourceTheme as availableThemes,
49
+ _getLoadedLanguagesInfo as getLoadedLanguagesInfo,
50
+ _resolveLanguageAlias as resolveLanguageAlias
51
+ };
@@ -17,7 +17,75 @@ const prismjs = require('prismjs/prism');
17
17
  // load all supported languages
18
18
  // @ts-ignore
19
19
  const loadLanguages = require('prismjs/components/');
20
- loadLanguages();
20
+ // Languages will be loaded on-demand for better performance
21
+
22
+ /**
23
+ * Cache of loaded languages to avoid redundant loading
24
+ * @type {Set<string>}
25
+ */
26
+ const loadedLanguagesCache = new Set();
27
+
28
+ /**
29
+ * Comprehensive language alias mapping based on PrismJS components
30
+ * Maps common aliases to their canonical language names
31
+ * @type {Object<string, string>}
32
+ */
33
+ const languageAliases = {
34
+ // JavaScript ecosystem
35
+ 'js': 'javascript',
36
+ 'mjs': 'javascript',
37
+ 'cjs': 'javascript',
38
+ 'jsx': 'javascript',
39
+ 'ts': 'typescript',
40
+ 'tsx': 'typescript',
41
+
42
+ // Shell/Bash variants
43
+ 'sh': 'bash',
44
+ 'shell': 'bash',
45
+ 'console': 'bash',
46
+ 'command': 'bash',
47
+ 'bash session': 'bash',
48
+ 'zsh': 'bash',
49
+
50
+ // Python
51
+ 'py': 'python',
52
+ 'py3': 'python',
53
+ 'python3': 'python',
54
+
55
+ // Ruby
56
+ 'rb': 'ruby',
57
+
58
+ // Markup languages
59
+ 'html': 'markup',
60
+ 'xml': 'markup',
61
+ 'svg': 'markup',
62
+ 'mathml': 'markup',
63
+ 'ssml': 'markup',
64
+
65
+ // C family
66
+ 'c++': 'cpp',
67
+ 'c#': 'csharp',
68
+ 'cs': 'csharp',
69
+
70
+ // Other common aliases
71
+ 'yml': 'yaml',
72
+ 'md': 'markdown',
73
+ 'dockerfile': 'docker',
74
+ 'objc': 'objectivec',
75
+ 'hs': 'haskell',
76
+ 'rs': 'rust',
77
+ 'kt': 'kotlin',
78
+ 'proto': 'protobuf',
79
+ 'coffee': 'coffeescript',
80
+ 'gawk': 'awk',
81
+ 'hbs': 'handlebars',
82
+ 'mustache': 'handlebars',
83
+ 'g4': 'antlr4',
84
+ 'ino': 'arduino',
85
+ 'adoc': 'asciidoc',
86
+ 'idr': 'idris',
87
+ 'eta': 'ejs',
88
+ };
21
89
 
22
90
  /**
23
91
  * @readonly
@@ -50,9 +118,9 @@ const getHighlightToken = (tokens) => {
50
118
 
51
119
  for (let i = 0; i < tokens.length; i++) {
52
120
 
53
- if (this.themeTokenKeys.indexOf(tokens[i]) !== -1) {
121
+ if (themeTokenKeys.indexOf(tokens[i]) !== -1) {
54
122
 
55
- tokenFound = this.theme.token[tokens[i]];
123
+ tokenFound = theme.token[tokens[i]];
56
124
  break;
57
125
  }
58
126
 
@@ -132,7 +200,7 @@ const _addBackground = (source, originalSource) => {
132
200
  }
133
201
 
134
202
  bgAddedSource.push(
135
- this.theme.background(sourceLines[i]) + this.theme.toEOL((" ").repeat(fill2end))
203
+ theme.background(sourceLines[i]) + theme.toEOL((" ").repeat(fill2end))
136
204
  );
137
205
 
138
206
  }
@@ -142,14 +210,88 @@ const _addBackground = (source, originalSource) => {
142
210
  }
143
211
 
144
212
  /**
145
- *
146
- * @param {string} source
147
- * @param {string} language
148
- * @param {sourceTheme} outTheme
213
+ * Resolves language aliases to canonical names
214
+ * @param {string} language - Language identifier (may be an alias)
215
+ * @returns {string|null} - Canonical language name or null if no language provided
216
+ */
217
+ const resolveLanguageAlias = (language) => {
218
+ if (!language) return null;
219
+
220
+ const normalized = language.toLowerCase().trim();
221
+ return languageAliases[normalized] || normalized;
222
+ };
223
+
224
+ /**
225
+ * Dynamically loads a language component if not already loaded
226
+ * @param {string} language - Language to load (canonical name or alias)
227
+ * @returns {boolean} - True if language is available, false otherwise
228
+ */
229
+ const ensureLanguageLoaded = (language) => {
230
+ if (!language) return false;
231
+
232
+ // Resolve alias to canonical name
233
+ const canonicalLang = resolveLanguageAlias(language);
234
+ if (!canonicalLang) return false;
235
+
236
+ // Check if already loaded in our cache
237
+ if (loadedLanguagesCache.has(canonicalLang)) {
238
+ return true;
239
+ }
240
+
241
+ // Check if language exists in Prism.languages (might be pre-loaded)
242
+ // @ts-ignore
243
+ if (Prism.languages[canonicalLang] !== undefined) {
244
+ loadedLanguagesCache.add(canonicalLang);
245
+ return true;
246
+ }
247
+
248
+ // Attempt to load the language
249
+ try {
250
+ // Suppress PrismJS warnings for this operation
251
+ const originalSilent = loadLanguages.silent;
252
+ loadLanguages.silent = true;
253
+
254
+ // Load the language (PrismJS handles dependencies automatically)
255
+ loadLanguages([canonicalLang]);
256
+
257
+ // Restore original silent setting
258
+ loadLanguages.silent = originalSilent;
259
+
260
+ // Verify it loaded successfully
261
+ // @ts-ignore
262
+ if (Prism.languages[canonicalLang] !== undefined) {
263
+ loadedLanguagesCache.add(canonicalLang);
264
+ return true;
265
+ }
266
+
267
+ return false;
268
+
269
+ } catch (error) {
270
+ // Language loading failed
271
+ return false;
272
+ }
273
+ };
274
+
275
+ /**
276
+ * Get information about loaded languages (for debugging/monitoring)
277
+ * @returns {{count: number, languages: string[]}} - Statistics about loaded languages
278
+ */
279
+ const getLoadedLanguagesInfo = () => {
280
+ return {
281
+ count: loadedLanguagesCache.size,
282
+ languages: Array.from(loadedLanguagesCache).sort()
283
+ };
284
+ };
285
+
286
+ /**
287
+ *
288
+ * @param {string} source
289
+ * @param {string} language
290
+ * @param {sourceTheme} outTheme
149
291
  */
150
292
  const _highlight = (source, language, outTheme) => {
151
293
 
152
- // Detect if theme value is supported - otherwise just use default Okaida theme
294
+ // Detect if theme value is supported - otherwise just use default Okaidia theme
153
295
  if (outTheme !== undefined) {
154
296
 
155
297
  let themePath = path.join(__dirname, './themes/', outTheme + '.theme'),
@@ -157,48 +299,51 @@ const _highlight = (source, language, outTheme) => {
157
299
 
158
300
  if (fs.existsSync(filePath)) {
159
301
 
160
- this.theme = require(filePath);
161
- this.themeTokenKeys = Object.keys(theme.token);
162
-
302
+ theme = require(filePath);
303
+ themeTokenKeys = Object.keys(theme.token);
163
304
 
164
305
  } else {
165
306
 
166
- throw `Theme '${outTheme}' do not exists`
307
+ throw `Theme '${outTheme}' do not exist`
167
308
 
168
309
  }
169
310
 
170
311
  }
171
312
 
172
- // Parse source code and return HTML from PrismJS output
173
- // console.log(language, Prism.languages[language]);
174
- // @ts-ignore
175
- Prism.languages['sh'] = Prism.languages['shell'];
176
- // @ts-ignore
177
- Prism.languages['console'] = Prism.languages['shell'];
178
- // @ts-ignore
179
- Prism.languages['command'] = Prism.languages['shell'];
180
- // @ts-ignore
181
- Prism.languages['bash session'] = Prism.languages['shell'];
313
+ // If no language specified, render as plain text
314
+ if (!language) {
315
+ return _addBackground(source, source);
316
+ }
182
317
 
183
- // @ts-ignore
184
- if (Prism.languages[language] !== undefined) {
318
+ // Dynamically load the language if needed
319
+ const languageLoaded = ensureLanguageLoaded(language);
320
+
321
+ if (!languageLoaded) {
322
+ // Language doesn't exist or failed to load - fallback to plain text
323
+ // Optionally log a warning (can be configured via environment variable)
324
+ if (process.env.MARKSHELL_WARN_UNKNOWN_LANG === 'true') {
325
+ console.warn(`[markshell] Unknown language '${language}' - rendering as plain text`);
326
+ }
327
+ return _addBackground(source, source);
328
+ }
329
+
330
+ // Get the canonical language name for highlighting
331
+ const canonicalLang = resolveLanguageAlias(language);
332
+
333
+ // @ts-ignore - Prism is loaded globally
334
+ if (Prism.languages[canonicalLang] !== undefined) {
185
335
  // @ts-ignore
186
- const prismCode = prismjs.highlight(source, Prism.languages[language], language);
336
+ const prismCode = prismjs.highlight(source, Prism.languages[canonicalLang], canonicalLang);
187
337
  // load HTML fragment
188
338
  const dom = JSDOM.fragment(prismCode);
189
339
 
190
- // console.log('Original Source:\n', source);
191
-
192
340
  var highlightedSource = parseFormatedContent(dom.childNodes, 0);
193
341
 
194
342
  return _addBackground(highlightedSource, source);
195
343
 
196
344
  } else {
197
-
345
+ // Shouldn't reach here if ensureLanguageLoaded worked correctly
198
346
  return _addBackground(source, source);
199
-
200
- // throw `Language '${language}' couldn't be found`;
201
-
202
347
  }
203
348
 
204
349
  }
@@ -206,5 +351,7 @@ const _highlight = (source, language, outTheme) => {
206
351
  module.exports = {
207
352
  highlight: _highlight,
208
353
  themes: sourceTheme,
209
- availableThemes: sourceTheme
354
+ availableThemes: sourceTheme,
355
+ getLoadedLanguagesInfo: getLoadedLanguagesInfo,
356
+ resolveLanguageAlias: resolveLanguageAlias
210
357
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "markshell",
3
- "version": "1.6.0",
3
+ "version": "1.8.0",
4
4
  "description": "markshell allows you to output any markdown file formatted and style to the console",
5
5
  "keywords": [
6
6
  "markdown",
@@ -22,7 +22,11 @@
22
22
  },
23
23
  "homepage": "https://github.com/stfbauer/markshell#readme",
24
24
  "scripts": {
25
- "test": "node test/index.js",
25
+ "test": "vitest",
26
+ "test:ui": "vitest --ui",
27
+ "test:run": "vitest run",
28
+ "test:coverage": "vitest --coverage",
29
+ "test:old": "node test/index.js",
26
30
  "testRun": "node ./lib/index.js",
27
31
  "update-types": "./tools/typings.sh"
28
32
  },
@@ -38,15 +42,18 @@
38
42
  "author": "Stefan Bauer",
39
43
  "license": "MIT",
40
44
  "dependencies": {
41
- "@types/prismjs": "1.16",
45
+ "@types/prismjs": "1.26",
42
46
  "chalk": "^4.1.2",
43
- "jsdom": "^16.7.0",
44
- "prismjs": "^1.25.0",
45
- "wrap-ansi": "^7.0.0"
47
+ "jsdom": "^27.3.0",
48
+ "prismjs": "^1.30.0",
49
+ "wrap-ansi": "^9.0.2"
46
50
  },
47
51
  "devDependencies": {
48
- "@types/node": "^10.17.60",
49
- "@types/prismjs": "^1.16.1"
52
+ "@types/node": "^24.10.2",
53
+ "@types/prismjs": "^1.16.1",
54
+ "@vitest/coverage-v8": "^1.6.1",
55
+ "@vitest/ui": "^1.0.0",
56
+ "vitest": "^1.0.0"
50
57
  },
51
58
  "repository": {
52
59
  "type": "git",