@mintlify/cli 4.0.737 → 4.0.739
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/accessibility.js +97 -0
- package/bin/accessibilityCheck.js +83 -0
- package/bin/cli.js +5 -0
- package/bin/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +7 -6
- package/src/accessibility.ts +160 -0
- package/src/accessibilityCheck.tsx +150 -0
- package/src/cli.tsx +10 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mintlify/cli",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.739",
|
|
4
4
|
"description": "The Mintlify CLI",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=18.0.0"
|
|
@@ -39,13 +39,14 @@
|
|
|
39
39
|
"format:check": "prettier . --check"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@mintlify/common": "1.0.
|
|
43
|
-
"@mintlify/link-rot": "3.0.
|
|
42
|
+
"@mintlify/common": "1.0.549",
|
|
43
|
+
"@mintlify/link-rot": "3.0.686",
|
|
44
44
|
"@mintlify/models": "0.0.230",
|
|
45
|
-
"@mintlify/prebuild": "1.0.
|
|
46
|
-
"@mintlify/previewing": "4.0.
|
|
45
|
+
"@mintlify/prebuild": "1.0.673",
|
|
46
|
+
"@mintlify/previewing": "4.0.722",
|
|
47
47
|
"@mintlify/validation": "0.1.478",
|
|
48
48
|
"chalk": "^5.2.0",
|
|
49
|
+
"color": "^4.2.3",
|
|
49
50
|
"detect-port": "^1.5.1",
|
|
50
51
|
"fs-extra": "^11.2.0",
|
|
51
52
|
"gray-matter": "^4.0.3",
|
|
@@ -74,5 +75,5 @@
|
|
|
74
75
|
"vitest": "^2.0.4",
|
|
75
76
|
"vitest-mock-process": "^1.0.4"
|
|
76
77
|
},
|
|
77
|
-
"gitHead": "
|
|
78
|
+
"gitHead": "d4f09db38679ce8a37663101c5f3b4fe2d3cfef8"
|
|
78
79
|
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import Color from 'color';
|
|
2
|
+
|
|
3
|
+
export const WCAG_STANDARDS = {
|
|
4
|
+
AA_NORMAL: 4.5,
|
|
5
|
+
AA_LARGE: 3,
|
|
6
|
+
AAA_NORMAL: 7,
|
|
7
|
+
AAA_LARGE: 4.5,
|
|
8
|
+
} as const;
|
|
9
|
+
|
|
10
|
+
export type ContrastResult = {
|
|
11
|
+
ratio: number;
|
|
12
|
+
meetsAA: boolean;
|
|
13
|
+
meetsAAA: boolean;
|
|
14
|
+
recommendation: 'pass' | 'warning' | 'fail';
|
|
15
|
+
message: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function checkColorContrast(foreground: string, background: string): ContrastResult | null {
|
|
19
|
+
try {
|
|
20
|
+
const fg = Color(foreground);
|
|
21
|
+
const bg = Color(background);
|
|
22
|
+
|
|
23
|
+
const ratio = fg.contrast(bg);
|
|
24
|
+
const level = fg.level(bg);
|
|
25
|
+
|
|
26
|
+
const meetsAA = level === 'AA' || level === 'AAA';
|
|
27
|
+
const meetsAAA = level === 'AAA';
|
|
28
|
+
|
|
29
|
+
let recommendation: 'pass' | 'warning' | 'fail';
|
|
30
|
+
let message: string;
|
|
31
|
+
|
|
32
|
+
if (meetsAAA) {
|
|
33
|
+
recommendation = 'pass';
|
|
34
|
+
message = `Excellent contrast ratio: ${ratio.toFixed(2)}:1 (meets WCAG AAA)`;
|
|
35
|
+
} else if (meetsAA) {
|
|
36
|
+
recommendation = 'warning';
|
|
37
|
+
message = `Good contrast ratio: ${ratio.toFixed(
|
|
38
|
+
2
|
|
39
|
+
)}:1 (meets WCAG AA, consider AAA for better accessibility)`;
|
|
40
|
+
} else {
|
|
41
|
+
recommendation = 'fail';
|
|
42
|
+
message = `Poor contrast ratio: ${ratio.toFixed(2)}:1 (fails WCAG AA, minimum required: ${
|
|
43
|
+
WCAG_STANDARDS.AA_NORMAL
|
|
44
|
+
}:1)`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
ratio,
|
|
49
|
+
meetsAA,
|
|
50
|
+
meetsAAA,
|
|
51
|
+
recommendation,
|
|
52
|
+
message,
|
|
53
|
+
};
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface AccessibilityCheckResult {
|
|
60
|
+
primaryContrast: ContrastResult | null;
|
|
61
|
+
lightContrast: ContrastResult | null;
|
|
62
|
+
darkContrast: ContrastResult | null;
|
|
63
|
+
darkOnLightContrast: ContrastResult | null;
|
|
64
|
+
anchorResults: Array<{
|
|
65
|
+
name: string;
|
|
66
|
+
lightContrast: ContrastResult | null;
|
|
67
|
+
darkContrast: ContrastResult | null;
|
|
68
|
+
}>;
|
|
69
|
+
overallScore: 'pass' | 'warning' | 'fail';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function checkDocsColors(
|
|
73
|
+
colors: {
|
|
74
|
+
primary?: string;
|
|
75
|
+
light?: string;
|
|
76
|
+
dark?: string;
|
|
77
|
+
},
|
|
78
|
+
background: {
|
|
79
|
+
lightHex: string;
|
|
80
|
+
darkHex: string;
|
|
81
|
+
},
|
|
82
|
+
navigation?: {
|
|
83
|
+
global?: {
|
|
84
|
+
anchors?: Array<{
|
|
85
|
+
anchor: string;
|
|
86
|
+
color?: {
|
|
87
|
+
light?: string;
|
|
88
|
+
dark?: string;
|
|
89
|
+
};
|
|
90
|
+
}>;
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
): AccessibilityCheckResult {
|
|
94
|
+
const lightBackground = background.lightHex;
|
|
95
|
+
const darkBackground = background.darkHex;
|
|
96
|
+
|
|
97
|
+
const primaryContrast = colors.primary
|
|
98
|
+
? checkColorContrast(colors.primary, lightBackground)
|
|
99
|
+
: null;
|
|
100
|
+
|
|
101
|
+
const lightContrast = colors.light ? checkColorContrast(colors.light, darkBackground) : null;
|
|
102
|
+
|
|
103
|
+
const darkContrast = colors.dark ? checkColorContrast(colors.dark, darkBackground) : null;
|
|
104
|
+
|
|
105
|
+
const darkOnLightContrast = colors.dark ? checkColorContrast(colors.dark, lightBackground) : null;
|
|
106
|
+
|
|
107
|
+
const anchorResults: Array<{
|
|
108
|
+
name: string;
|
|
109
|
+
lightContrast: ContrastResult | null;
|
|
110
|
+
darkContrast: ContrastResult | null;
|
|
111
|
+
}> = [];
|
|
112
|
+
|
|
113
|
+
if (navigation?.global?.anchors) {
|
|
114
|
+
for (const anchor of navigation.global.anchors) {
|
|
115
|
+
if (anchor.color) {
|
|
116
|
+
const lightContrast = anchor.color.light
|
|
117
|
+
? checkColorContrast(anchor.color.light, lightBackground)
|
|
118
|
+
: null;
|
|
119
|
+
const darkContrast = anchor.color.dark
|
|
120
|
+
? checkColorContrast(anchor.color.dark, darkBackground)
|
|
121
|
+
: null;
|
|
122
|
+
|
|
123
|
+
anchorResults.push({
|
|
124
|
+
name: anchor.anchor,
|
|
125
|
+
lightContrast,
|
|
126
|
+
darkContrast,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const results = [
|
|
133
|
+
primaryContrast,
|
|
134
|
+
lightContrast,
|
|
135
|
+
darkContrast,
|
|
136
|
+
darkOnLightContrast,
|
|
137
|
+
...anchorResults.flatMap((anchor) => [anchor.lightContrast, anchor.darkContrast]),
|
|
138
|
+
].filter(Boolean);
|
|
139
|
+
|
|
140
|
+
const hasFailure = results.some((result) => result!.recommendation === 'fail');
|
|
141
|
+
const hasWarning = results.some((result) => result!.recommendation === 'warning');
|
|
142
|
+
|
|
143
|
+
let overallScore: 'pass' | 'warning' | 'fail';
|
|
144
|
+
if (hasFailure) {
|
|
145
|
+
overallScore = 'fail';
|
|
146
|
+
} else if (hasWarning) {
|
|
147
|
+
overallScore = 'warning';
|
|
148
|
+
} else {
|
|
149
|
+
overallScore = 'pass';
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
primaryContrast,
|
|
154
|
+
lightContrast,
|
|
155
|
+
darkContrast,
|
|
156
|
+
darkOnLightContrast,
|
|
157
|
+
anchorResults,
|
|
158
|
+
overallScore,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { getBackgroundColors } from '@mintlify/common';
|
|
2
|
+
import { getConfigObj, getConfigPath } from '@mintlify/prebuild';
|
|
3
|
+
import { addLog, ErrorLog, WarningLog } from '@mintlify/previewing';
|
|
4
|
+
import { Text } from 'ink';
|
|
5
|
+
|
|
6
|
+
import { checkDocsColors, type AccessibilityCheckResult } from './accessibility.js';
|
|
7
|
+
import { ContrastResult } from './accessibility.js';
|
|
8
|
+
import { CMD_EXEC_PATH } from './constants.js';
|
|
9
|
+
import { checkForDocsJson } from './helpers.js';
|
|
10
|
+
|
|
11
|
+
type TerminateCode = 0 | 1;
|
|
12
|
+
|
|
13
|
+
export const accessibilityCheck = async (): Promise<TerminateCode> => {
|
|
14
|
+
try {
|
|
15
|
+
await checkForDocsJson();
|
|
16
|
+
|
|
17
|
+
const docsConfigPath = await getConfigPath(CMD_EXEC_PATH, 'docs');
|
|
18
|
+
const mintConfigPath = await getConfigPath(CMD_EXEC_PATH, 'mint');
|
|
19
|
+
|
|
20
|
+
if (!docsConfigPath && !mintConfigPath) {
|
|
21
|
+
addLog(
|
|
22
|
+
<ErrorLog message="No configuration file found. Please run this command from a directory with a mint.json or docs.json file." />
|
|
23
|
+
);
|
|
24
|
+
return 1;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const configType = docsConfigPath ? 'docs' : 'mint';
|
|
28
|
+
const config = await getConfigObj(CMD_EXEC_PATH, configType);
|
|
29
|
+
|
|
30
|
+
if (!config.colors) {
|
|
31
|
+
addLog(<WarningLog message="No colors section found in configuration file" />);
|
|
32
|
+
return 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const { colors, navigation } = config;
|
|
36
|
+
|
|
37
|
+
const { lightHex, darkHex } = getBackgroundColors(config);
|
|
38
|
+
|
|
39
|
+
const results: AccessibilityCheckResult = checkDocsColors(
|
|
40
|
+
colors,
|
|
41
|
+
{ lightHex, darkHex },
|
|
42
|
+
navigation
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const displayContrastResult = (
|
|
46
|
+
result: ContrastResult | null,
|
|
47
|
+
label: string,
|
|
48
|
+
prefix: string = ''
|
|
49
|
+
) => {
|
|
50
|
+
if (!result) return;
|
|
51
|
+
|
|
52
|
+
const { recommendation, message } = result;
|
|
53
|
+
const icon =
|
|
54
|
+
recommendation === 'pass' ? 'PASS' : recommendation === 'warning' ? 'WARN' : 'FAIL';
|
|
55
|
+
const color =
|
|
56
|
+
recommendation === 'pass' ? 'green' : recommendation === 'warning' ? 'yellow' : 'red';
|
|
57
|
+
|
|
58
|
+
addLog(
|
|
59
|
+
<Text>
|
|
60
|
+
<Text bold={prefix === ''}>
|
|
61
|
+
{prefix}
|
|
62
|
+
{label}:{' '}
|
|
63
|
+
</Text>
|
|
64
|
+
<Text color={color}>
|
|
65
|
+
{icon} {message}
|
|
66
|
+
</Text>
|
|
67
|
+
</Text>
|
|
68
|
+
);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
addLog(
|
|
72
|
+
<Text bold color="cyan">
|
|
73
|
+
Checking color accessibility...
|
|
74
|
+
</Text>
|
|
75
|
+
);
|
|
76
|
+
addLog(<Text></Text>);
|
|
77
|
+
|
|
78
|
+
displayContrastResult(
|
|
79
|
+
results.primaryContrast,
|
|
80
|
+
`Primary Color (${colors.primary}) vs Light Background`
|
|
81
|
+
);
|
|
82
|
+
displayContrastResult(
|
|
83
|
+
results.lightContrast,
|
|
84
|
+
`Light Color (${colors.light}) vs Dark Background`
|
|
85
|
+
);
|
|
86
|
+
displayContrastResult(results.darkContrast, `Dark Color (${colors.dark}) vs Dark Background`);
|
|
87
|
+
displayContrastResult(
|
|
88
|
+
results.darkOnLightContrast,
|
|
89
|
+
`Dark Color (${colors.dark}) vs Light Background`
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const anchorsWithResults = results.anchorResults.filter(
|
|
93
|
+
(anchor) => anchor.lightContrast || anchor.darkContrast
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
if (anchorsWithResults.length > 0) {
|
|
97
|
+
addLog(<Text></Text>);
|
|
98
|
+
addLog(
|
|
99
|
+
<Text bold color="cyan">
|
|
100
|
+
Navigation Anchors:
|
|
101
|
+
</Text>
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
for (const anchor of anchorsWithResults) {
|
|
105
|
+
addLog(<Text bold> {anchor.name}:</Text>);
|
|
106
|
+
displayContrastResult(anchor.lightContrast, 'Light variant vs Light Background', ' ');
|
|
107
|
+
displayContrastResult(anchor.darkContrast, 'Dark variant vs Dark Background', ' ');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
addLog(<Text></Text>);
|
|
112
|
+
const overallIcon =
|
|
113
|
+
results.overallScore === 'pass'
|
|
114
|
+
? 'PASS'
|
|
115
|
+
: results.overallScore === 'warning'
|
|
116
|
+
? 'WARN'
|
|
117
|
+
: 'FAIL';
|
|
118
|
+
const overallColor =
|
|
119
|
+
results.overallScore === 'pass'
|
|
120
|
+
? 'green'
|
|
121
|
+
: results.overallScore === 'warning'
|
|
122
|
+
? 'yellow'
|
|
123
|
+
: 'red';
|
|
124
|
+
const overallMessage =
|
|
125
|
+
results.overallScore === 'pass'
|
|
126
|
+
? 'All colors meet accessibility standards!'
|
|
127
|
+
: results.overallScore === 'warning'
|
|
128
|
+
? 'Some colors could be improved for better accessibility'
|
|
129
|
+
: 'Some colors fail accessibility standards and should be updated';
|
|
130
|
+
|
|
131
|
+
addLog(
|
|
132
|
+
<Text>
|
|
133
|
+
<Text bold color={overallColor}>
|
|
134
|
+
Overall Assessment: {overallIcon} {overallMessage}
|
|
135
|
+
</Text>
|
|
136
|
+
</Text>
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
return results.overallScore === 'fail' ? 1 : 0;
|
|
140
|
+
} catch (error) {
|
|
141
|
+
addLog(
|
|
142
|
+
<ErrorLog
|
|
143
|
+
message={`Accessibility check failed: ${
|
|
144
|
+
error instanceof Error ? error.message : 'Unknown error'
|
|
145
|
+
}`}
|
|
146
|
+
/>
|
|
147
|
+
);
|
|
148
|
+
return 1;
|
|
149
|
+
}
|
|
150
|
+
};
|
package/src/cli.tsx
CHANGED
|
@@ -16,6 +16,7 @@ import path from 'path';
|
|
|
16
16
|
import yargs from 'yargs';
|
|
17
17
|
import { hideBin } from 'yargs/helpers';
|
|
18
18
|
|
|
19
|
+
import { accessibilityCheck } from './accessibilityCheck.js';
|
|
19
20
|
import {
|
|
20
21
|
checkPort,
|
|
21
22
|
checkForMintJson,
|
|
@@ -238,6 +239,15 @@ export const cli = ({ packageName = 'mint' }: { packageName?: string }) => {
|
|
|
238
239
|
await terminate(0);
|
|
239
240
|
}
|
|
240
241
|
)
|
|
242
|
+
.command(
|
|
243
|
+
'accessibility-check',
|
|
244
|
+
false,
|
|
245
|
+
() => undefined,
|
|
246
|
+
async () => {
|
|
247
|
+
const terminateCode = await accessibilityCheck();
|
|
248
|
+
await terminate(terminateCode);
|
|
249
|
+
}
|
|
250
|
+
)
|
|
241
251
|
.command(
|
|
242
252
|
['version', 'v'],
|
|
243
253
|
'display the current version of the CLI and client',
|