@mintlify/cli 4.0.739 → 4.0.740

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": "@mintlify/cli",
3
- "version": "4.0.739",
3
+ "version": "4.0.740",
4
4
  "description": "The Mintlify CLI",
5
5
  "engines": {
6
6
  "node": ">=18.0.0"
@@ -53,8 +53,11 @@
53
53
  "ink": "^6.0.1",
54
54
  "inquirer": "^12.3.0",
55
55
  "js-yaml": "^4.1.0",
56
+ "mdast": "^3.0.0",
57
+ "mdast-util-mdx-jsx": "^3.2.0",
56
58
  "react": "^19.1.0",
57
59
  "semver": "^7.7.2",
60
+ "unist-util-visit": "^5.0.0",
58
61
  "yargs": "^17.6.0"
59
62
  },
60
63
  "devDependencies": {
@@ -75,5 +78,5 @@
75
78
  "vitest": "^2.0.4",
76
79
  "vitest-mock-process": "^1.0.4"
77
80
  },
78
- "gitHead": "d4f09db38679ce8a37663101c5f3b4fe2d3cfef8"
81
+ "gitHead": "aa87b8b6de071cfb135106af666f4ba6789ca102"
79
82
  }
@@ -8,7 +8,7 @@ import { ContrastResult } from './accessibility.js';
8
8
  import { CMD_EXEC_PATH } from './constants.js';
9
9
  import { checkForDocsJson } from './helpers.js';
10
10
 
11
- type TerminateCode = 0 | 1;
11
+ export type TerminateCode = 0 | 1;
12
12
 
13
13
  export const accessibilityCheck = async (): Promise<TerminateCode> => {
14
14
  try {
package/src/cli.tsx CHANGED
@@ -28,6 +28,7 @@ import {
28
28
  terminate,
29
29
  readLocalOpenApiFile,
30
30
  } from './helpers.js';
31
+ import { mdxLinter } from './mdxLinter.js';
31
32
  import { migrateMdx } from './migrateMdx.js';
32
33
  import { update } from './update.js';
33
34
 
@@ -240,12 +241,13 @@ export const cli = ({ packageName = 'mint' }: { packageName?: string }) => {
240
241
  }
241
242
  )
242
243
  .command(
243
- 'accessibility-check',
244
+ ['accessibility-check', 'a11y-check', 'accessibility', 'a11y'],
244
245
  false,
245
246
  () => undefined,
246
247
  async () => {
247
- const terminateCode = await accessibilityCheck();
248
- await terminate(terminateCode);
248
+ const accessibilityCheckTerminateCode = await accessibilityCheck();
249
+ const mdxLinterTerminateCode = await mdxLinter();
250
+ await terminate(accessibilityCheckTerminateCode || mdxLinterTerminateCode);
249
251
  }
250
252
  )
251
253
  .command(
@@ -0,0 +1,132 @@
1
+ import { coreRemark } from '@mintlify/common';
2
+ import { categorizeFilePaths } from '@mintlify/prebuild';
3
+ import fs from 'fs';
4
+ import type { Root, Text } from 'mdast';
5
+ import type { MdxJsxFlowElement } from 'mdast-util-mdx-jsx';
6
+ import path from 'path';
7
+ import type { Node } from 'unist';
8
+ import { visit } from 'unist-util-visit';
9
+
10
+ export interface AccessibilityFixAttribute {
11
+ filePath: string;
12
+ line?: number;
13
+ column?: number;
14
+ element: 'img' | 'video' | 'a';
15
+ tagName: string;
16
+ }
17
+
18
+ export interface MdxAccessibilityResult {
19
+ missingAltAttributes: AccessibilityFixAttribute[];
20
+ totalFiles: number;
21
+ filesWithIssues: number;
22
+ }
23
+
24
+ const checkAltAttributes = (filePath: string, content: string): AccessibilityFixAttribute[] => {
25
+ const issues: AccessibilityFixAttribute[] = [];
26
+
27
+ const visitElements = () => {
28
+ return (tree: Root) => {
29
+ visit(tree, (node) => {
30
+ if (node.type === 'image') {
31
+ if (!node.alt || node.alt.trim() === '') {
32
+ issues.push({
33
+ filePath,
34
+ line: node.position?.start.line,
35
+ column: node.position?.start.column,
36
+ element: 'img',
37
+ tagName: 'image (markdown)',
38
+ });
39
+ }
40
+ return;
41
+ }
42
+
43
+ const mdxJsxElement = node as MdxJsxFlowElement;
44
+ if (mdxJsxElement.name === 'img' || mdxJsxElement.name === 'video') {
45
+ const altAttrIndex = mdxJsxElement.attributes.findIndex(
46
+ (attr) => attr.type === 'mdxJsxAttribute' && attr.name === 'alt'
47
+ );
48
+
49
+ const altAttribute = mdxJsxElement.attributes[altAttrIndex];
50
+ const hasValidAlt =
51
+ altAttribute &&
52
+ typeof altAttribute.value === 'string' &&
53
+ altAttribute.value.trim() !== '';
54
+
55
+ if (!hasValidAlt) {
56
+ issues.push({
57
+ filePath,
58
+ line: node.position?.start.line,
59
+ column: node.position?.start.column,
60
+ element: mdxJsxElement.name,
61
+ tagName: mdxJsxElement.name,
62
+ });
63
+ }
64
+ } else if (mdxJsxElement.name === 'a') {
65
+ const hasTextContent = (children: Node[]): boolean => {
66
+ return children.some((child) => {
67
+ if (child.type === 'text') {
68
+ const textNode = child as Text;
69
+ return textNode.value.trim() !== '';
70
+ }
71
+ if ('children' in child && Array.isArray(child.children)) {
72
+ return hasTextContent(child.children as Node[]);
73
+ }
74
+ return false;
75
+ });
76
+ };
77
+
78
+ if (!hasTextContent(mdxJsxElement.children as Node[])) {
79
+ issues.push({
80
+ filePath,
81
+ line: node.position?.start.line,
82
+ column: node.position?.start.column,
83
+ element: 'a',
84
+ tagName: '<a>',
85
+ });
86
+ }
87
+ }
88
+ });
89
+ return tree;
90
+ };
91
+ };
92
+
93
+ try {
94
+ coreRemark().use(visitElements).processSync(content);
95
+ } catch (error) {
96
+ console.warn(`Warning: Could not parse ${filePath}: ${error}`);
97
+ }
98
+
99
+ return issues;
100
+ };
101
+
102
+ export const checkMdxAccessibility = async (
103
+ baseDir: string = process.cwd()
104
+ ): Promise<MdxAccessibilityResult> => {
105
+ const { contentFilenames } = await categorizeFilePaths(baseDir);
106
+ const mdxFiles: string[] = [];
107
+ for (const file of contentFilenames) {
108
+ mdxFiles.push(path.join(baseDir, file));
109
+ }
110
+ const allIssues: AccessibilityFixAttribute[] = [];
111
+ const filesWithIssues = new Set<string>();
112
+
113
+ for (const filePath of mdxFiles) {
114
+ try {
115
+ const content = fs.readFileSync(filePath, 'utf-8');
116
+ const issues = checkAltAttributes(filePath, content);
117
+
118
+ if (issues.length > 0) {
119
+ allIssues.push(...issues);
120
+ filesWithIssues.add(filePath);
121
+ }
122
+ } catch (error) {
123
+ console.warn(`Warning: Could not read file ${filePath}: ${error}`);
124
+ }
125
+ }
126
+
127
+ return {
128
+ missingAltAttributes: allIssues,
129
+ totalFiles: mdxFiles.length,
130
+ filesWithIssues: filesWithIssues.size,
131
+ };
132
+ };
@@ -0,0 +1,88 @@
1
+ import { addLog, ErrorLog, SuccessLog } from '@mintlify/previewing';
2
+ import { Text } from 'ink';
3
+ import path from 'path';
4
+
5
+ import { TerminateCode } from './accessibilityCheck.js';
6
+ import { checkMdxAccessibility, type AccessibilityFixAttribute } from './mdxAccessibility.js';
7
+
8
+ export const mdxLinter = async (): Promise<TerminateCode> => {
9
+ try {
10
+ addLog(
11
+ <Text bold color="cyan">
12
+ Checking mdx files for accessibility issues...
13
+ </Text>
14
+ );
15
+
16
+ const results = await checkMdxAccessibility();
17
+
18
+ if (results.missingAltAttributes.length === 0) {
19
+ addLog(<SuccessLog message="no accessibility issues found" />);
20
+ addLog(
21
+ <Text>
22
+ Checked {results.totalFiles} MDX files - all images and videos have alt attributes.
23
+ </Text>
24
+ );
25
+ return 0;
26
+ }
27
+
28
+ const issuesByFile: Record<string, AccessibilityFixAttribute[]> = {};
29
+ results.missingAltAttributes.forEach((issue) => {
30
+ if (!issuesByFile[issue.filePath]) {
31
+ issuesByFile[issue.filePath] = [];
32
+ }
33
+ issuesByFile[issue.filePath]?.push(issue);
34
+ });
35
+
36
+ addLog(
37
+ <Text bold color="red">
38
+ Found {results.missingAltAttributes.length} accessibility issues in{' '}
39
+ {results.filesWithIssues} files:
40
+ </Text>
41
+ );
42
+ addLog(<Text></Text>);
43
+
44
+ for (const [filePath, issues] of Object.entries(issuesByFile)) {
45
+ const relativePath = path.relative(process.cwd(), filePath);
46
+ addLog(<Text bold>{relativePath}:</Text>);
47
+
48
+ for (const issue of issues) {
49
+ const location =
50
+ issue.line && issue.column ? ` (line ${issue.line}, col ${issue.column})` : '';
51
+ if (issue.element === 'a') {
52
+ addLog(
53
+ <Text>
54
+ <Text color="red"> ✗</Text> Missing text attribute <Text bold>{issue.tagName}</Text>{' '}
55
+ element{location}
56
+ </Text>
57
+ );
58
+ } else {
59
+ addLog(
60
+ <Text>
61
+ <Text color="red"> ✗</Text> Missing alt attribute on <Text bold>{issue.tagName}</Text>{' '}
62
+ element{location}
63
+ </Text>
64
+ );
65
+ }
66
+ }
67
+ addLog(<Text></Text>);
68
+ }
69
+
70
+ addLog(
71
+ <Text color="yellow">
72
+ <Text bold>Recommendation:</Text> Add alt attributes to all images and videos for better
73
+ accessibility.
74
+ </Text>
75
+ );
76
+
77
+ return 1;
78
+ } catch (error) {
79
+ addLog(
80
+ <ErrorLog
81
+ message={`MDX accessibility check failed: ${
82
+ error instanceof Error ? error.message : 'Unknown error'
83
+ }`}
84
+ />
85
+ );
86
+ return 1;
87
+ }
88
+ };