@mintlify/cli 4.0.737 → 4.0.738

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.737",
3
+ "version": "4.0.738",
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.547",
43
- "@mintlify/link-rot": "3.0.684",
42
+ "@mintlify/common": "1.0.548",
43
+ "@mintlify/link-rot": "3.0.685",
44
44
  "@mintlify/models": "0.0.230",
45
- "@mintlify/prebuild": "1.0.671",
46
- "@mintlify/previewing": "4.0.720",
45
+ "@mintlify/prebuild": "1.0.672",
46
+ "@mintlify/previewing": "4.0.721",
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": "e52bbb1b754a21125285e7be5a3b9c102e1c4208"
78
+ "gitHead": "69ad789dbb3ea65dd6c3998327e67d0131c5d196"
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',