@mintlify/cli 4.0.1813 → 4.1.1
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/dist/assets/export-scripts/README.classic.md +15 -0
- package/dist/assets/export-scripts/README.md +14 -0
- package/dist/assets/export-scripts/serve.js +114 -0
- package/dist/assets/export-scripts/start-docs.bat +10 -0
- package/dist/assets/export-scripts/start-docs.sh +9 -0
- package/dist/assets/og/Inter-Bold.ttf +0 -0
- package/dist/assets/og/Inter-Medium.ttf +0 -0
- package/dist/assets/og/Inter-Regular.ttf +0 -0
- package/dist/assets/og/Inter-SemiBold.ttf +0 -0
- package/dist/assets/og/PaperMono-Regular.ttf +0 -0
- package/dist/assets/og/index_bg.wasm +0 -0
- package/dist/assets/openChrome.applescript +94 -0
- package/dist/assets/socket.io/socket.io.esm.min.js +7 -0
- package/dist/assets/socket.io/socket.io.esm.min.js.map +1 -0
- package/dist/assets/socket.io/socket.io.js +4907 -0
- package/dist/assets/socket.io/socket.io.js.map +1 -0
- package/dist/assets/socket.io/socket.io.min.js +7 -0
- package/dist/assets/socket.io/socket.io.min.js.map +1 -0
- package/dist/assets/socket.io/socket.io.msgpack.min.js +7 -0
- package/dist/assets/socket.io/socket.io.msgpack.min.js.map +1 -0
- package/dist/assets/xdg-open +1066 -0
- package/dist/assets/yargs-locales/be.json +46 -0
- package/dist/assets/yargs-locales/cs.json +51 -0
- package/dist/assets/yargs-locales/de.json +46 -0
- package/dist/assets/yargs-locales/en.json +55 -0
- package/dist/assets/yargs-locales/es.json +46 -0
- package/dist/assets/yargs-locales/fi.json +49 -0
- package/dist/assets/yargs-locales/fr.json +53 -0
- package/dist/assets/yargs-locales/hi.json +49 -0
- package/dist/assets/yargs-locales/hu.json +46 -0
- package/dist/assets/yargs-locales/id.json +50 -0
- package/dist/assets/yargs-locales/it.json +46 -0
- package/dist/assets/yargs-locales/ja.json +51 -0
- package/dist/assets/yargs-locales/ko.json +49 -0
- package/dist/assets/yargs-locales/nb.json +44 -0
- package/dist/assets/yargs-locales/nl.json +49 -0
- package/dist/assets/yargs-locales/nn.json +44 -0
- package/dist/assets/yargs-locales/pirate.json +13 -0
- package/dist/assets/yargs-locales/pl.json +49 -0
- package/dist/assets/yargs-locales/pt.json +45 -0
- package/dist/assets/yargs-locales/pt_BR.json +48 -0
- package/dist/assets/yargs-locales/ru.json +51 -0
- package/dist/assets/yargs-locales/th.json +46 -0
- package/dist/assets/yargs-locales/tr.json +48 -0
- package/dist/assets/yargs-locales/uk_UA.json +51 -0
- package/dist/assets/yargs-locales/uz.json +52 -0
- package/dist/assets/yargs-locales/zh_CN.json +48 -0
- package/dist/assets/yargs-locales/zh_TW.json +51 -0
- package/dist/converter-d8O3xkdU.js +102 -0
- package/dist/devtools-CGNZQKFV.js +24 -0
- package/dist/index.js +8 -0
- package/dist/rolldown-runtime-CEAe2Uac.js +7 -0
- package/dist/schemas-CBsmCNpK.js +372 -0
- package/dist/start.js +2310 -0
- package/package.json +38 -31
- package/.oxlintrc.json +0 -5
- package/.prettierignore +0 -2
- package/.turbo/turbo-build.log +0 -1
- package/CONTRIBUTING.md +0 -45
- package/__test__/agent-auth.test.ts +0 -114
- package/__test__/agent-claim.test.ts +0 -164
- package/__test__/agent-credentials.test.ts +0 -149
- package/__test__/agent-deployment.test.ts +0 -106
- package/__test__/agent-setup-config.test.ts +0 -324
- package/__test__/agent-setup.test.ts +0 -207
- package/__test__/agent.test.ts +0 -64
- package/__test__/analytics/client.test.ts +0 -180
- package/__test__/analytics/dates.test.ts +0 -12
- package/__test__/analytics/filters.test.ts +0 -116
- package/__test__/analytics/format.test.ts +0 -137
- package/__test__/authenticatedFetch.test.ts +0 -182
- package/__test__/brokenLinks.test.ts +0 -283
- package/__test__/checkPort.test.ts +0 -92
- package/__test__/deploy-github-actions.test.ts +0 -202
- package/__test__/deploy-github-setup.test.ts +0 -240
- package/__test__/deploy-instructions.test.ts +0 -155
- package/__test__/deploy.test.ts +0 -202
- package/__test__/feedback.test.ts +0 -34
- package/__test__/foreignSyntax.test.ts +0 -73
- package/__test__/format.test.ts +0 -72
- package/__test__/helpers.test.ts +0 -52
- package/__test__/init.test.ts +0 -119
- package/__test__/instructions.test.ts +0 -53
- package/__test__/keyring.test.ts +0 -82
- package/__test__/mdxAccessibility.test.ts +0 -81
- package/__test__/migrate.test.ts +0 -186
- package/__test__/openApiCheck.test.ts +0 -185
- package/__test__/pathValidation.test.ts +0 -25
- package/__test__/resolveFiles.test.ts +0 -70
- package/__test__/sessionId.test.ts +0 -46
- package/__test__/signup.test.ts +0 -36
- package/__test__/update.test.ts +0 -117
- package/__test__/utils.ts +0 -25
- package/__test__/validateCommand.test.ts +0 -647
- package/__test__/validateEngine.test.ts +0 -245
- package/__test__/validateRules.test.ts +0 -1275
- package/__test__/welcome.test.ts +0 -15
- package/__test__/workflows/client.test.ts +0 -126
- package/bin/accessibility.js +0 -111
- package/bin/accessibilityCheck.js +0 -82
- package/bin/addDomain.js +0 -130
- package/bin/agent-auth.js +0 -104
- package/bin/agent-claim.js +0 -97
- package/bin/agent-credentials.js +0 -124
- package/bin/agent-deployment.js +0 -96
- package/bin/agent-http.js +0 -45
- package/bin/agent-setup-config.js +0 -141
- package/bin/agent-setup.js +0 -146
- package/bin/agent.js +0 -42
- package/bin/analytics/client.js +0 -60
- package/bin/analytics/dates.js +0 -6
- package/bin/analytics/filters.js +0 -27
- package/bin/analytics/format.js +0 -13
- package/bin/analytics/index.js +0 -496
- package/bin/analytics/output.js +0 -75
- package/bin/analytics/types.js +0 -1
- package/bin/authenticatedFetch.js +0 -46
- package/bin/brokenLinks.js +0 -122
- package/bin/callbackServer.js +0 -65
- package/bin/cli.js +0 -813
- package/bin/comingSoon.js +0 -26
- package/bin/config.js +0 -78
- package/bin/constants.js +0 -16
- package/bin/deploy-base.js +0 -20
- package/bin/deploy-github-actions.js +0 -102
- package/bin/deploy-github-setup.js +0 -165
- package/bin/deploy-instructions.js +0 -71
- package/bin/deploy.js +0 -123
- package/bin/feedback.js +0 -36
- package/bin/format.js +0 -121
- package/bin/helpers.js +0 -238
- package/bin/idToken.js +0 -14
- package/bin/index.js +0 -102
- package/bin/init.js +0 -182
- package/bin/instructions.js +0 -101
- package/bin/keyring.js +0 -78
- package/bin/login.js +0 -123
- package/bin/logout.js +0 -18
- package/bin/mdxAccessibility.js +0 -113
- package/bin/mdxLinter.js +0 -55
- package/bin/middlewares/subdomainMiddleware.js +0 -28
- package/bin/middlewares/telemetryMiddleware.js +0 -61
- package/bin/migrate.js +0 -54
- package/bin/og.js +0 -145
- package/bin/openApiCheck.js +0 -99
- package/bin/resolveFiles.js +0 -114
- package/bin/score/client.js +0 -40
- package/bin/score/index.js +0 -211
- package/bin/score/types.js +0 -1
- package/bin/signup.js +0 -118
- package/bin/start.js +0 -36
- package/bin/status.js +0 -106
- package/bin/telemetry/client.js +0 -53
- package/bin/telemetry/distinctId.js +0 -68
- package/bin/telemetry/sessionId.js +0 -36
- package/bin/telemetry/track.js +0 -149
- package/bin/templates.js +0 -127
- package/bin/tokenRefresh.js +0 -50
- package/bin/tsconfig.build.tsbuildinfo +0 -1
- package/bin/update.js +0 -65
- package/bin/validate/command.js +0 -127
- package/bin/validate/engine.js +0 -154
- package/bin/validate/files.js +0 -163
- package/bin/validate/mdx.js +0 -238
- package/bin/validate/output.js +0 -44
- package/bin/validate/report.js +0 -11
- package/bin/validate/rules/build.js +0 -102
- package/bin/validate/rules/content/duplicateH1.js +0 -57
- package/bin/validate/rules/content/foreignSyntax.js +0 -120
- package/bin/validate/rules/content/katex.js +0 -34
- package/bin/validate/rules/content/missingSnippet.js +0 -34
- package/bin/validate/rules/content/undefinedVariable.js +0 -13
- package/bin/validate/rules/content/unknownComponent.js +0 -121
- package/bin/validate/rules/content/voidTags.js +0 -38
- package/bin/validate/rules/index.js +0 -21
- package/bin/validate/rules/openapi.js +0 -50
- package/bin/validate/rules/parseError.js +0 -7
- package/bin/validate/site.js +0 -57
- package/bin/validate/types.js +0 -1
- package/bin/vendor/converter.js +0 -30882
- package/bin/welcome.js +0 -108
- package/bin/workflows/client.js +0 -34
- package/bin/workflows/index.js +0 -269
- package/bin/workflows/types.js +0 -11
- package/scripts/bundle-converter.mjs +0 -26
- package/scripts/generate-logo.mjs +0 -79
- package/src/accessibility.ts +0 -180
- package/src/accessibilityCheck.tsx +0 -150
- package/src/addDomain.tsx +0 -206
- package/src/agent-auth.ts +0 -119
- package/src/agent-claim.ts +0 -124
- package/src/agent-credentials.ts +0 -104
- package/src/agent-deployment.ts +0 -116
- package/src/agent-http.ts +0 -34
- package/src/agent-setup-config.ts +0 -144
- package/src/agent-setup.ts +0 -165
- package/src/agent.ts +0 -56
- package/src/analytics/client.ts +0 -153
- package/src/analytics/dates.ts +0 -6
- package/src/analytics/filters.ts +0 -50
- package/src/analytics/format.ts +0 -13
- package/src/analytics/index.tsx +0 -599
- package/src/analytics/output.ts +0 -99
- package/src/analytics/types.ts +0 -145
- package/src/authenticatedFetch.ts +0 -42
- package/src/brokenLinks.tsx +0 -152
- package/src/callbackServer.ts +0 -64
- package/src/cli.tsx +0 -1105
- package/src/comingSoon.tsx +0 -29
- package/src/config.ts +0 -75
- package/src/constants.ts +0 -28
- package/src/deploy-base.ts +0 -18
- package/src/deploy-github-actions.ts +0 -139
- package/src/deploy-github-setup.ts +0 -189
- package/src/deploy-instructions.ts +0 -76
- package/src/deploy.ts +0 -165
- package/src/feedback.tsx +0 -31
- package/src/format.tsx +0 -82
- package/src/helpers.tsx +0 -279
- package/src/idToken.ts +0 -17
- package/src/index.ts +0 -109
- package/src/init.tsx +0 -237
- package/src/instructions.ts +0 -99
- package/src/keyring.ts +0 -65
- package/src/login.tsx +0 -166
- package/src/logout.tsx +0 -8
- package/src/mdxAccessibility.ts +0 -134
- package/src/mdxLinter.tsx +0 -88
- package/src/middlewares/subdomainMiddleware.ts +0 -20
- package/src/middlewares/telemetryMiddleware.ts +0 -55
- package/src/migrate.ts +0 -76
- package/src/og.tsx +0 -185
- package/src/openApiCheck.tsx +0 -125
- package/src/resolveFiles.ts +0 -101
- package/src/score/client.ts +0 -36
- package/src/score/index.tsx +0 -205
- package/src/score/types.ts +0 -15
- package/src/signup.tsx +0 -172
- package/src/start.ts +0 -27
- package/src/status.tsx +0 -129
- package/src/telemetry/client.ts +0 -46
- package/src/telemetry/distinctId.ts +0 -70
- package/src/telemetry/sessionId.ts +0 -40
- package/src/telemetry/track.ts +0 -163
- package/src/templates.tsx +0 -143
- package/src/tokenRefresh.ts +0 -53
- package/src/update.tsx +0 -89
- package/src/validate/command.tsx +0 -135
- package/src/validate/engine.ts +0 -210
- package/src/validate/files.ts +0 -249
- package/src/validate/mdx.ts +0 -290
- package/src/validate/output.tsx +0 -76
- package/src/validate/report.ts +0 -40
- package/src/validate/rules/build.ts +0 -146
- package/src/validate/rules/content/duplicateH1.ts +0 -78
- package/src/validate/rules/content/foreignSyntax.ts +0 -144
- package/src/validate/rules/content/katex.ts +0 -33
- package/src/validate/rules/content/missingSnippet.ts +0 -36
- package/src/validate/rules/content/undefinedVariable.ts +0 -20
- package/src/validate/rules/content/unknownComponent.ts +0 -130
- package/src/validate/rules/content/voidTags.ts +0 -43
- package/src/validate/rules/index.ts +0 -23
- package/src/validate/rules/openapi.ts +0 -59
- package/src/validate/rules/parseError.ts +0 -9
- package/src/validate/site.ts +0 -53
- package/src/validate/types.ts +0 -102
- package/src/vendor/converter.ts +0 -1
- package/src/welcome.ts +0 -154
- package/src/workflows/client.ts +0 -39
- package/src/workflows/index.tsx +0 -287
- package/src/workflows/types.ts +0 -73
- package/tsconfig.build.json +0 -4
- package/tsconfig.json +0 -10
- package/vitest.config.ts +0 -9
package/src/accessibility.ts
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
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(
|
|
19
|
-
foreground: string,
|
|
20
|
-
background: string,
|
|
21
|
-
minThreshold: number = WCAG_STANDARDS.AA_NORMAL
|
|
22
|
-
): ContrastResult | null {
|
|
23
|
-
try {
|
|
24
|
-
const fg = Color(foreground);
|
|
25
|
-
const bg = Color(background);
|
|
26
|
-
|
|
27
|
-
const ratio = fg.contrast(bg);
|
|
28
|
-
const level = fg.level(bg);
|
|
29
|
-
|
|
30
|
-
const meetsAA = level === 'AA' || level === 'AAA';
|
|
31
|
-
const meetsAAA = level === 'AAA';
|
|
32
|
-
|
|
33
|
-
let recommendation: 'pass' | 'warning' | 'fail';
|
|
34
|
-
let message: string;
|
|
35
|
-
|
|
36
|
-
if (minThreshold !== WCAG_STANDARDS.AA_NORMAL) {
|
|
37
|
-
if (ratio >= minThreshold) {
|
|
38
|
-
recommendation = 'pass';
|
|
39
|
-
message = `Contrast ratio: ${ratio.toFixed(
|
|
40
|
-
2
|
|
41
|
-
)}:1 (meets minimum threshold of ${minThreshold}:1)`;
|
|
42
|
-
} else {
|
|
43
|
-
recommendation = 'fail';
|
|
44
|
-
message = `Poor contrast ratio: ${ratio.toFixed(
|
|
45
|
-
2
|
|
46
|
-
)}:1 (fails minimum threshold, required: ${minThreshold}:1)`;
|
|
47
|
-
}
|
|
48
|
-
} else {
|
|
49
|
-
if (meetsAAA) {
|
|
50
|
-
recommendation = 'pass';
|
|
51
|
-
message = `Excellent contrast ratio: ${ratio.toFixed(2)}:1 (meets WCAG AAA)`;
|
|
52
|
-
} else if (meetsAA) {
|
|
53
|
-
recommendation = 'warning';
|
|
54
|
-
message = `Good contrast ratio: ${ratio.toFixed(
|
|
55
|
-
2
|
|
56
|
-
)}:1 (meets WCAG AA, consider AAA for better accessibility)`;
|
|
57
|
-
} else {
|
|
58
|
-
recommendation = 'fail';
|
|
59
|
-
message = `Poor contrast ratio: ${ratio.toFixed(2)}:1 (fails WCAG AA, minimum required: ${
|
|
60
|
-
WCAG_STANDARDS.AA_NORMAL
|
|
61
|
-
}:1)`;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return {
|
|
66
|
-
ratio,
|
|
67
|
-
meetsAA,
|
|
68
|
-
meetsAAA,
|
|
69
|
-
recommendation,
|
|
70
|
-
message,
|
|
71
|
-
};
|
|
72
|
-
} catch {
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export interface AccessibilityCheckResult {
|
|
78
|
-
primaryContrast: ContrastResult | null;
|
|
79
|
-
lightContrast: ContrastResult | null;
|
|
80
|
-
darkContrast: ContrastResult | null;
|
|
81
|
-
darkOnLightContrast: ContrastResult | null;
|
|
82
|
-
anchorResults: Array<{
|
|
83
|
-
name: string;
|
|
84
|
-
lightContrast: ContrastResult | null;
|
|
85
|
-
darkContrast: ContrastResult | null;
|
|
86
|
-
}>;
|
|
87
|
-
overallScore: 'pass' | 'warning' | 'fail';
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export function checkDocsColors(
|
|
91
|
-
colors: {
|
|
92
|
-
primary?: string;
|
|
93
|
-
light?: string;
|
|
94
|
-
dark?: string;
|
|
95
|
-
},
|
|
96
|
-
background: {
|
|
97
|
-
lightHex: string;
|
|
98
|
-
darkHex: string;
|
|
99
|
-
},
|
|
100
|
-
navigation?: {
|
|
101
|
-
global?: {
|
|
102
|
-
anchors?: Array<{
|
|
103
|
-
anchor: string;
|
|
104
|
-
color?: {
|
|
105
|
-
light?: string;
|
|
106
|
-
dark?: string;
|
|
107
|
-
};
|
|
108
|
-
}>;
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
): AccessibilityCheckResult {
|
|
112
|
-
const lightBackground = background.lightHex;
|
|
113
|
-
const darkBackground = background.darkHex;
|
|
114
|
-
|
|
115
|
-
const primaryContrast = colors.primary
|
|
116
|
-
? checkColorContrast(colors.primary, lightBackground)
|
|
117
|
-
: null;
|
|
118
|
-
|
|
119
|
-
const lightContrast = colors.light ? checkColorContrast(colors.light, darkBackground) : null;
|
|
120
|
-
|
|
121
|
-
const darkContrast = colors.dark ? checkColorContrast(colors.dark, darkBackground, 3) : null;
|
|
122
|
-
|
|
123
|
-
const darkOnLightContrast = colors.dark
|
|
124
|
-
? checkColorContrast(colors.dark, lightBackground, 3)
|
|
125
|
-
: null;
|
|
126
|
-
|
|
127
|
-
const anchorResults: Array<{
|
|
128
|
-
name: string;
|
|
129
|
-
lightContrast: ContrastResult | null;
|
|
130
|
-
darkContrast: ContrastResult | null;
|
|
131
|
-
}> = [];
|
|
132
|
-
|
|
133
|
-
if (navigation?.global?.anchors) {
|
|
134
|
-
for (const anchor of navigation.global.anchors) {
|
|
135
|
-
if (anchor.color) {
|
|
136
|
-
const lightContrast = anchor.color.light
|
|
137
|
-
? checkColorContrast(anchor.color.light, lightBackground)
|
|
138
|
-
: null;
|
|
139
|
-
const darkContrast = anchor.color.dark
|
|
140
|
-
? checkColorContrast(anchor.color.dark, darkBackground)
|
|
141
|
-
: null;
|
|
142
|
-
|
|
143
|
-
anchorResults.push({
|
|
144
|
-
name: anchor.anchor,
|
|
145
|
-
lightContrast,
|
|
146
|
-
darkContrast,
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const results = [
|
|
153
|
-
primaryContrast,
|
|
154
|
-
lightContrast,
|
|
155
|
-
darkContrast,
|
|
156
|
-
darkOnLightContrast,
|
|
157
|
-
...anchorResults.flatMap((anchor) => [anchor.lightContrast, anchor.darkContrast]),
|
|
158
|
-
].filter(Boolean);
|
|
159
|
-
|
|
160
|
-
const hasFailure = results.some((result) => result!.recommendation === 'fail');
|
|
161
|
-
const hasWarning = results.some((result) => result!.recommendation === 'warning');
|
|
162
|
-
|
|
163
|
-
let overallScore: 'pass' | 'warning' | 'fail';
|
|
164
|
-
if (hasFailure) {
|
|
165
|
-
overallScore = 'fail';
|
|
166
|
-
} else if (hasWarning) {
|
|
167
|
-
overallScore = 'warning';
|
|
168
|
-
} else {
|
|
169
|
-
overallScore = 'pass';
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return {
|
|
173
|
-
primaryContrast,
|
|
174
|
-
lightContrast,
|
|
175
|
-
darkContrast,
|
|
176
|
-
darkOnLightContrast,
|
|
177
|
-
anchorResults,
|
|
178
|
-
overallScore,
|
|
179
|
-
};
|
|
180
|
-
}
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import { getBackgroundColors } from '@mintlify/common';
|
|
2
|
-
import { getConfigObj, getConfigPath } from '@mintlify/prebuild';
|
|
3
|
-
import { addLog, ErrorLog, WarningLog } from '@mintlify/previewing';
|
|
4
|
-
import type { DocsConfig } from '@mintlify/validation';
|
|
5
|
-
import { Text } from 'ink';
|
|
6
|
-
|
|
7
|
-
import { checkDocsColors, type AccessibilityCheckResult } from './accessibility.js';
|
|
8
|
-
import { ContrastResult } from './accessibility.js';
|
|
9
|
-
import { CMD_EXEC_PATH, checkForDocsJson } from './helpers.js';
|
|
10
|
-
|
|
11
|
-
export 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)) as DocsConfig | null;
|
|
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/addDomain.tsx
DELETED
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getRelativeRecordName,
|
|
3
|
-
normalizeBasePath,
|
|
4
|
-
validateCustomDomainBasePath,
|
|
5
|
-
} from '@mintlify/common';
|
|
6
|
-
import { addLog, ErrorLog, removeLastLog, SpinnerLog, SuccessLog } from '@mintlify/previewing';
|
|
7
|
-
import { Box, Text } from 'ink';
|
|
8
|
-
|
|
9
|
-
import { authenticatedFetch } from './authenticatedFetch.js';
|
|
10
|
-
import { getConfigValue } from './config.js';
|
|
11
|
-
import { API_URL, CUSTOM_DOMAIN_CNAME_TARGET } from './constants.js';
|
|
12
|
-
import { getAccessToken } from './keyring.js';
|
|
13
|
-
import { getCliSubdomains } from './status.js';
|
|
14
|
-
|
|
15
|
-
interface DnsRecord {
|
|
16
|
-
type: string;
|
|
17
|
-
domain: string;
|
|
18
|
-
value: string;
|
|
19
|
-
isConfigured?: boolean;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
interface CustomDomainCheckResponse {
|
|
23
|
-
cloudflare?: { dnsRecords?: DnsRecord[] };
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const DNS_POLL_INTERVAL_MS = 5_000;
|
|
27
|
-
const DNS_POLL_MAX_ATTEMPTS = 3;
|
|
28
|
-
|
|
29
|
-
const DOMAIN_PATTERN = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/i;
|
|
30
|
-
|
|
31
|
-
export async function addDomain(domain: string, basePath?: string): Promise<void> {
|
|
32
|
-
if (!DOMAIN_PATTERN.test(domain)) {
|
|
33
|
-
addLog(
|
|
34
|
-
<ErrorLog
|
|
35
|
-
message={`invalid domain "${domain}". Expected a bare hostname, e.g. docs.example.com`}
|
|
36
|
-
/>
|
|
37
|
-
);
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const normalizedBasePath = normalizeBasePath(basePath?.trim());
|
|
42
|
-
if (normalizedBasePath) {
|
|
43
|
-
const basePathError = validateCustomDomainBasePath(normalizedBasePath);
|
|
44
|
-
if (basePathError) {
|
|
45
|
-
addLog(<ErrorLog message={`invalid base path "${basePath}": ${basePathError}`} />);
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const accessToken = await getAccessToken();
|
|
51
|
-
if (!accessToken) {
|
|
52
|
-
addLog(<ErrorLog message="not logged in. Run `mint login` to authenticate." />);
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const subdomains = await getCliSubdomains(accessToken);
|
|
57
|
-
const configuredSubdomain = getConfigValue('subdomain');
|
|
58
|
-
const subdomain =
|
|
59
|
-
configuredSubdomain && subdomains.includes(configuredSubdomain)
|
|
60
|
-
? configuredSubdomain
|
|
61
|
-
: subdomains[0];
|
|
62
|
-
if (!subdomain) {
|
|
63
|
-
addLog(<ErrorLog message="no subdomain found. Run `mint status` to check your account." />);
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
try {
|
|
68
|
-
const addRes = await authenticatedFetch(
|
|
69
|
-
`${API_URL}/api/cli/deployment/custom-domain/${subdomain}/${domain}`,
|
|
70
|
-
{
|
|
71
|
-
method: 'POST',
|
|
72
|
-
headers: { 'Content-Type': 'application/json' },
|
|
73
|
-
body: JSON.stringify({ basePath: normalizedBasePath ? '/docs' : '' }),
|
|
74
|
-
}
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
if (!addRes.ok) {
|
|
78
|
-
const body = await addRes.json().catch(() => ({}));
|
|
79
|
-
addLog(<ErrorLog message={`could not add domain: ${body.error ?? 'unknown error'}`} />);
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
} catch {
|
|
83
|
-
addLog(<ErrorLog message="could not reach the server" />);
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
let basePathSaved = false;
|
|
88
|
-
let basePathPending = false;
|
|
89
|
-
if (normalizedBasePath) {
|
|
90
|
-
addLog(<SpinnerLog message={`setting base path to ${normalizedBasePath}...`} />);
|
|
91
|
-
try {
|
|
92
|
-
const basePathRes = await authenticatedFetch(
|
|
93
|
-
`${API_URL}/api/cli/deployment/base-path/${subdomain}`,
|
|
94
|
-
{
|
|
95
|
-
method: 'PUT',
|
|
96
|
-
headers: { 'Content-Type': 'application/json' },
|
|
97
|
-
body: JSON.stringify({ basePath: normalizedBasePath }),
|
|
98
|
-
}
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
removeLastLog();
|
|
102
|
-
|
|
103
|
-
if (basePathRes.ok) {
|
|
104
|
-
const body = (await basePathRes.json().catch(() => ({}))) as {
|
|
105
|
-
pendingBasePathUpdate?: unknown;
|
|
106
|
-
};
|
|
107
|
-
basePathSaved = true;
|
|
108
|
-
basePathPending = Boolean(body.pendingBasePathUpdate);
|
|
109
|
-
} else {
|
|
110
|
-
const body = (await basePathRes.json().catch(() => ({}))) as {
|
|
111
|
-
error?: string;
|
|
112
|
-
issues?: Array<{ message?: string }>;
|
|
113
|
-
};
|
|
114
|
-
const reason = body.issues?.[0]?.message ?? body.error ?? 'unknown error';
|
|
115
|
-
addLog(<ErrorLog message={`domain added, but base path was not saved: ${reason}`} />);
|
|
116
|
-
}
|
|
117
|
-
} catch {
|
|
118
|
-
removeLastLog();
|
|
119
|
-
addLog(
|
|
120
|
-
<ErrorLog message="domain added, but base path was not saved: could not reach the server" />
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const maxWaitSeconds = ((DNS_POLL_MAX_ATTEMPTS - 1) * DNS_POLL_INTERVAL_MS) / 1000;
|
|
126
|
-
addLog(
|
|
127
|
-
<SpinnerLog message={`waiting for DNS records to generate (up to ${maxWaitSeconds}s)...`} />
|
|
128
|
-
);
|
|
129
|
-
|
|
130
|
-
let dnsRecords: DnsRecord[] = [];
|
|
131
|
-
let recordsReady = false;
|
|
132
|
-
try {
|
|
133
|
-
for (let attempt = 0; attempt < DNS_POLL_MAX_ATTEMPTS; attempt++) {
|
|
134
|
-
const checkRes = await authenticatedFetch(
|
|
135
|
-
`${API_URL}/api/cli/deployment/custom-domain/${subdomain}/${domain}`
|
|
136
|
-
);
|
|
137
|
-
|
|
138
|
-
if (checkRes.ok) {
|
|
139
|
-
const body = (await checkRes.json()) as CustomDomainCheckResponse;
|
|
140
|
-
dnsRecords = body.cloudflare?.dnsRecords ?? [];
|
|
141
|
-
|
|
142
|
-
const stillGenerating = dnsRecords.some((record) => record.value === 'loading');
|
|
143
|
-
if (dnsRecords.length > 0 && !stillGenerating) {
|
|
144
|
-
recordsReady = true;
|
|
145
|
-
break;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (attempt < DNS_POLL_MAX_ATTEMPTS - 1) {
|
|
150
|
-
await new Promise((resolve) => setTimeout(resolve, DNS_POLL_INTERVAL_MS));
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
} catch {
|
|
154
|
-
removeLastLog();
|
|
155
|
-
addLog(<ErrorLog message="domain added, but could not fetch DNS records" />);
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
removeLastLog();
|
|
160
|
-
|
|
161
|
-
const readyTxtRecords = dnsRecords.filter(
|
|
162
|
-
(record) => record.type === 'TXT' && record.value !== 'loading'
|
|
163
|
-
);
|
|
164
|
-
|
|
165
|
-
const successMessage = basePathPending
|
|
166
|
-
? `domain ${domain} added; base path ${normalizedBasePath} queued for the next deploy`
|
|
167
|
-
: basePathSaved
|
|
168
|
-
? `domain ${domain} added (serving at ${normalizedBasePath})`
|
|
169
|
-
: `domain ${domain} added`;
|
|
170
|
-
|
|
171
|
-
addLog(<SuccessLog message={successMessage} />);
|
|
172
|
-
addLog(
|
|
173
|
-
<Box flexDirection="column" gap={1} paddingY={1}>
|
|
174
|
-
<Text bold>Add these DNS records with your domain provider:</Text>
|
|
175
|
-
<Box flexDirection="column" paddingLeft={3}>
|
|
176
|
-
{readyTxtRecords.map((record) => (
|
|
177
|
-
<Text key={record.domain}>
|
|
178
|
-
TXT {getRelativeRecordName(record.domain, domain)} → {record.value}
|
|
179
|
-
</Text>
|
|
180
|
-
))}
|
|
181
|
-
<Text>
|
|
182
|
-
CNAME {getRelativeRecordName(domain, domain)} → {CUSTOM_DOMAIN_CNAME_TARGET}
|
|
183
|
-
</Text>
|
|
184
|
-
</Box>
|
|
185
|
-
<Text dimColor>Add the TXT records first; add the CNAME once they're validated.</Text>
|
|
186
|
-
{basePathPending && (
|
|
187
|
-
<Text dimColor>
|
|
188
|
-
The base path takes effect when that deploy finishes; the current path stays up until
|
|
189
|
-
then.
|
|
190
|
-
</Text>
|
|
191
|
-
)}
|
|
192
|
-
{basePathSaved && (
|
|
193
|
-
<Text dimColor>
|
|
194
|
-
The CNAME sends all paths on {domain} to Mintlify. Only add it if this domain hosts
|
|
195
|
-
nothing else. Otherwise, set up a reverse proxy for {normalizedBasePath}:
|
|
196
|
-
https://www.mintlify.com/docs/deploy/docs-subpath
|
|
197
|
-
</Text>
|
|
198
|
-
)}
|
|
199
|
-
{!recordsReady && (
|
|
200
|
-
<Text dimColor>
|
|
201
|
-
Some TXT records are still being generated. Check the dashboard in a moment for the rest.
|
|
202
|
-
</Text>
|
|
203
|
-
)}
|
|
204
|
-
</Box>
|
|
205
|
-
);
|
|
206
|
-
}
|
package/src/agent-auth.ts
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
|
|
3
|
-
import type { AgentCredentials } from './agent-credentials.js';
|
|
4
|
-
import { agentRequest, agentResponse } from './agent-http.js';
|
|
5
|
-
|
|
6
|
-
const resourceSchema = z.object({ authorization_servers: z.array(z.url()).min(1) });
|
|
7
|
-
const discoverySchema = z.object({
|
|
8
|
-
issuer: z.url(),
|
|
9
|
-
token_endpoint: z.url(),
|
|
10
|
-
agent_auth: z.object({
|
|
11
|
-
identity_endpoint: z.url(),
|
|
12
|
-
claim_endpoint: z.url(),
|
|
13
|
-
identity_types_supported: z.array(z.string()),
|
|
14
|
-
}),
|
|
15
|
-
});
|
|
16
|
-
export const agentIdentityAssertionSchema = z.object({ assertion: z.string().min(1) });
|
|
17
|
-
const registrationSchema = z.object({
|
|
18
|
-
id: z.string().min(1),
|
|
19
|
-
identity: agentIdentityAssertionSchema,
|
|
20
|
-
claim: z.object({ token: z.string().min(1) }),
|
|
21
|
-
});
|
|
22
|
-
const tokenSchema = z.object({
|
|
23
|
-
access_token: z.string().min(1),
|
|
24
|
-
expires_in: z.number().positive(),
|
|
25
|
-
token_type: z.string().regex(/^bearer$/i),
|
|
26
|
-
});
|
|
27
|
-
const identitySchema = z.discriminatedUnion('identityType', [
|
|
28
|
-
z.object({ registrationId: z.string(), identityType: z.literal('anonymous') }),
|
|
29
|
-
z.object({
|
|
30
|
-
registrationId: z.string(),
|
|
31
|
-
identityType: z.literal('user_bound'),
|
|
32
|
-
userId: z.string(),
|
|
33
|
-
orgId: z.string(),
|
|
34
|
-
}),
|
|
35
|
-
]);
|
|
36
|
-
|
|
37
|
-
export type AgentDiscovery = z.infer<typeof discoverySchema>;
|
|
38
|
-
|
|
39
|
-
export async function discoverAgentAuth(apiUrl: string): Promise<AgentDiscovery> {
|
|
40
|
-
const resource = await agentResponse(
|
|
41
|
-
await agentRequest(`${apiUrl}/api/agent/.well-known/oauth-protected-resource`),
|
|
42
|
-
resourceSchema
|
|
43
|
-
);
|
|
44
|
-
const issuer = resource.authorization_servers[0];
|
|
45
|
-
if (!issuer || new URL(issuer).protocol !== 'https:')
|
|
46
|
-
throw new Error('Agent authentication requires an HTTPS issuer.');
|
|
47
|
-
const discovery = await agentResponse(
|
|
48
|
-
await agentRequest(`${issuer.replace(/\/$/, '')}/.well-known/oauth-authorization-server`),
|
|
49
|
-
discoverySchema
|
|
50
|
-
);
|
|
51
|
-
const origin = new URL(issuer).origin;
|
|
52
|
-
if (
|
|
53
|
-
[
|
|
54
|
-
discovery.issuer,
|
|
55
|
-
discovery.token_endpoint,
|
|
56
|
-
discovery.agent_auth.identity_endpoint,
|
|
57
|
-
discovery.agent_auth.claim_endpoint,
|
|
58
|
-
].some((url) => new URL(url).origin !== origin)
|
|
59
|
-
) {
|
|
60
|
-
throw new Error('Agent authentication endpoints do not match the configured issuer.');
|
|
61
|
-
}
|
|
62
|
-
return discovery;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export async function registerAgent(discovery: AgentDiscovery): Promise<AgentCredentials> {
|
|
66
|
-
if (!discovery.agent_auth.identity_types_supported.includes('anonymous')) {
|
|
67
|
-
throw new Error('Anonymous registration is not enabled for this environment.');
|
|
68
|
-
}
|
|
69
|
-
const result = await agentResponse(
|
|
70
|
-
await agentRequest(discovery.agent_auth.identity_endpoint, {
|
|
71
|
-
method: 'POST',
|
|
72
|
-
headers: { 'Content-Type': 'application/json' },
|
|
73
|
-
body: JSON.stringify({ type: 'anonymous' }),
|
|
74
|
-
}),
|
|
75
|
-
registrationSchema
|
|
76
|
-
);
|
|
77
|
-
return {
|
|
78
|
-
registrationId: result.id,
|
|
79
|
-
issuer: discovery.issuer,
|
|
80
|
-
assertion: result.identity.assertion,
|
|
81
|
-
claimToken: result.claim.token,
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export async function exchangeAgentToken(
|
|
86
|
-
discovery: AgentDiscovery,
|
|
87
|
-
credentials: AgentCredentials
|
|
88
|
-
): Promise<AgentCredentials & { accessToken: string }> {
|
|
89
|
-
if (new URL(credentials.issuer).origin !== new URL(discovery.issuer).origin) {
|
|
90
|
-
throw new Error('Saved agent credentials belong to another issuer.');
|
|
91
|
-
}
|
|
92
|
-
if (credentials.accessToken && (credentials.accessTokenExpiresAt ?? 0) > Date.now() + 30_000) {
|
|
93
|
-
return { ...credentials, accessToken: credentials.accessToken };
|
|
94
|
-
}
|
|
95
|
-
const token = await agentResponse(
|
|
96
|
-
await agentRequest(discovery.token_endpoint, {
|
|
97
|
-
method: 'POST',
|
|
98
|
-
body: new URLSearchParams({
|
|
99
|
-
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
|
100
|
-
assertion: credentials.assertion,
|
|
101
|
-
}),
|
|
102
|
-
}),
|
|
103
|
-
tokenSchema
|
|
104
|
-
);
|
|
105
|
-
return {
|
|
106
|
-
...credentials,
|
|
107
|
-
accessToken: token.access_token,
|
|
108
|
-
accessTokenExpiresAt: Date.now() + token.expires_in * 1000,
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export async function getAgentIdentity(apiUrl: string, accessToken: string) {
|
|
113
|
-
return agentResponse(
|
|
114
|
-
await agentRequest(`${apiUrl}/api/agent/identity`, {
|
|
115
|
-
headers: { Authorization: `Bearer ${accessToken}` },
|
|
116
|
-
}),
|
|
117
|
-
identitySchema
|
|
118
|
-
);
|
|
119
|
-
}
|