@mintlify/cli 4.0.1799 → 4.0.1801
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/__test__/validateCommand.test.ts +1 -1
- package/__test__/validateEngine.test.ts +2 -0
- package/__test__/validateRules.test.ts +56 -1
- package/bin/tsconfig.build.tsbuildinfo +1 -1
- package/bin/validate/files.js +9 -2
- package/bin/validate/rules/content/missingSnippet.js +34 -0
- package/bin/validate/rules/content/undefinedVariable.js +13 -0
- package/bin/validate/rules/content/unknownComponent.js +1 -1
- package/bin/validate/rules/index.js +4 -0
- package/package.json +10 -10
- package/src/validate/files.ts +10 -3
- package/src/validate/rules/content/missingSnippet.ts +36 -0
- package/src/validate/rules/content/undefinedVariable.ts +20 -0
- package/src/validate/rules/content/unknownComponent.ts +1 -1
- package/src/validate/rules/index.ts +4 -0
- package/src/validate/types.ts +1 -0
package/bin/validate/files.js
CHANGED
|
@@ -106,7 +106,14 @@ const checkFile = (allRules, { file, kind }, content, baseDir, extras, variables
|
|
|
106
106
|
const treeRules = rules.filter((rule) => !rule.raw);
|
|
107
107
|
const varEdits = kind === 'snippet' ? variableEdits(content, variables) : [];
|
|
108
108
|
const source = applyEdits(content, varEdits);
|
|
109
|
-
const rawContext = {
|
|
109
|
+
const rawContext = {
|
|
110
|
+
file,
|
|
111
|
+
content: source,
|
|
112
|
+
baseDir,
|
|
113
|
+
frontmatter: {},
|
|
114
|
+
tree: EMPTY_TREE,
|
|
115
|
+
variables,
|
|
116
|
+
};
|
|
110
117
|
const rawFound = raw.flatMap((rule) => rule.check(rawContext).map((finding) => ({ rule, finding })));
|
|
111
118
|
const rawEdits = selectEdits(rawFound.flatMap(({ finding }) => (finding.edit ? [finding.edit] : [])));
|
|
112
119
|
const afterRaw = applyEdits(source, rawEdits);
|
|
@@ -116,7 +123,7 @@ const checkFile = (allRules, { file, kind }, content, baseDir, extras, variables
|
|
|
116
123
|
{ content: afterRaw, edits: [] },
|
|
117
124
|
];
|
|
118
125
|
const diagnostics = rawFound.map(({ rule, finding }) => toDiagnostic(rule, file, finding, stages, SOURCE_STAGE));
|
|
119
|
-
const parsed = parseFile(file, afterRaw, baseDir, extras);
|
|
126
|
+
const parsed = parseFile(file, afterRaw, baseDir, Object.assign(Object.assign({}, extras), { variables }));
|
|
120
127
|
if ('error' in parsed) {
|
|
121
128
|
diagnostics.unshift(parseErrorDiagnostic(file, Object.assign(Object.assign({}, parsed.error), relocate(stages, AFTER_RAW_STAGE, parsed.error))));
|
|
122
129
|
return diagnostics;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { replaceVariables } from '@mintlify/common';
|
|
2
|
+
import { visit } from 'unist-util-visit';
|
|
3
|
+
const exists = (file, readFile) => !file.startsWith('/') &&
|
|
4
|
+
(readFile(`_snippets/${file}`) !== undefined ||
|
|
5
|
+
(file.startsWith('snippets/') && readFile(file) !== undefined));
|
|
6
|
+
export const missingSnippet = {
|
|
7
|
+
id: 'missing-snippet',
|
|
8
|
+
group: 'content',
|
|
9
|
+
severity: 'error',
|
|
10
|
+
scope: 'file',
|
|
11
|
+
description: '<Snippet file> tags whose file the build cannot find',
|
|
12
|
+
check: ({ tree, readFile, variables }) => {
|
|
13
|
+
if (!readFile)
|
|
14
|
+
return [];
|
|
15
|
+
const issues = [];
|
|
16
|
+
visit(tree, 'mdxJsxFlowElement', (node) => {
|
|
17
|
+
var _a, _b, _c;
|
|
18
|
+
if (node.name !== 'Snippet')
|
|
19
|
+
return;
|
|
20
|
+
const value = (_a = node.attributes.find((attribute) => attribute.type === 'mdxJsxAttribute' && attribute.name === 'file')) === null || _a === void 0 ? void 0 : _a.value;
|
|
21
|
+
if (typeof value !== 'string')
|
|
22
|
+
return;
|
|
23
|
+
const file = replaceVariables(value, variables);
|
|
24
|
+
if (exists(file, readFile))
|
|
25
|
+
return;
|
|
26
|
+
issues.push({
|
|
27
|
+
line: (_b = node.position) === null || _b === void 0 ? void 0 : _b.start.line,
|
|
28
|
+
column: (_c = node.position) === null || _c === void 0 ? void 0 : _c.start.column,
|
|
29
|
+
message: `<Snippet file="${value}"> renders nothing: no snippet matches "${file}"`,
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
return issues;
|
|
33
|
+
},
|
|
34
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { maskCode, toPosition } from '../../mdx.js';
|
|
2
|
+
const VARIABLE = /(?<!=\s*)\{\{\s*([\w.\-]+)\s*\}\}/g;
|
|
3
|
+
export const undefinedVariable = {
|
|
4
|
+
id: 'undefined-variable',
|
|
5
|
+
group: 'content',
|
|
6
|
+
severity: 'error',
|
|
7
|
+
scope: 'file',
|
|
8
|
+
description: '{{variables}} that docs.json does not define',
|
|
9
|
+
raw: true,
|
|
10
|
+
check: ({ content, variables = {} }) => [...maskCode(content).matchAll(VARIABLE)]
|
|
11
|
+
.filter(([, name]) => name !== undefined && variables[name] === undefined)
|
|
12
|
+
.map((match) => (Object.assign(Object.assign({}, toPosition(content, match.index)), { message: `{{${match[1]}}} is not defined in the variables of docs.json` }))),
|
|
13
|
+
};
|
|
@@ -55,7 +55,7 @@ const getSpecifier = (snippet, file, name) => {
|
|
|
55
55
|
export const unknownComponent = {
|
|
56
56
|
id: 'unknown-component',
|
|
57
57
|
group: 'content',
|
|
58
|
-
severity: '
|
|
58
|
+
severity: 'error',
|
|
59
59
|
scope: 'file',
|
|
60
60
|
description: 'JSX components that are not built in, imported or exported',
|
|
61
61
|
pagesOnly: true,
|
|
@@ -2,6 +2,8 @@ import { BUILD_RULES } from './build.js';
|
|
|
2
2
|
import { duplicateH1 } from './content/duplicateH1.js';
|
|
3
3
|
import { foreignSyntax } from './content/foreignSyntax.js';
|
|
4
4
|
import { katexRule } from './content/katex.js';
|
|
5
|
+
import { missingSnippet } from './content/missingSnippet.js';
|
|
6
|
+
import { undefinedVariable } from './content/undefinedVariable.js';
|
|
5
7
|
import { unknownComponent } from './content/unknownComponent.js';
|
|
6
8
|
import { voidTags } from './content/voidTags.js';
|
|
7
9
|
import { openApiSpec } from './openapi.js';
|
|
@@ -10,6 +12,8 @@ export const RULES = [
|
|
|
10
12
|
voidTags,
|
|
11
13
|
duplicateH1,
|
|
12
14
|
unknownComponent,
|
|
15
|
+
undefinedVariable,
|
|
16
|
+
missingSnippet,
|
|
13
17
|
katexRule,
|
|
14
18
|
foreignSyntax,
|
|
15
19
|
openApiSpec,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mintlify/cli",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.1801",
|
|
4
4
|
"description": "The Mintlify CLI",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=18.0.0"
|
|
@@ -45,13 +45,13 @@
|
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@inquirer/prompts": "7.9.0",
|
|
48
|
-
"@mintlify/common": "1.0.
|
|
49
|
-
"@mintlify/link-rot": "3.0.
|
|
50
|
-
"@mintlify/models": "0.0.
|
|
51
|
-
"@mintlify/og": "0.0.
|
|
52
|
-
"@mintlify/prebuild": "1.0.
|
|
53
|
-
"@mintlify/previewing": "4.0.
|
|
54
|
-
"@mintlify/validation": "0.1.
|
|
48
|
+
"@mintlify/common": "1.0.1342",
|
|
49
|
+
"@mintlify/link-rot": "3.0.1583",
|
|
50
|
+
"@mintlify/models": "0.0.387",
|
|
51
|
+
"@mintlify/og": "0.0.141",
|
|
52
|
+
"@mintlify/prebuild": "1.0.1515",
|
|
53
|
+
"@mintlify/previewing": "4.0.1601",
|
|
54
|
+
"@mintlify/validation": "0.1.960",
|
|
55
55
|
"adm-zip": "0.6.0",
|
|
56
56
|
"chalk": "5.2.0",
|
|
57
57
|
"color": "4.2.3",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"keytar": "7.9.0"
|
|
88
88
|
},
|
|
89
89
|
"devDependencies": {
|
|
90
|
-
"@mintlify/editor": "0.0.
|
|
90
|
+
"@mintlify/editor": "0.0.619",
|
|
91
91
|
"@mintlify/ts-config": "2.0.2",
|
|
92
92
|
"@tsconfig/recommended": "1.0.2",
|
|
93
93
|
"@types/detect-port": "1.3.2",
|
|
@@ -106,5 +106,5 @@
|
|
|
106
106
|
"vitest": "2.1.9",
|
|
107
107
|
"vitest-mock-process": "1.0.4"
|
|
108
108
|
},
|
|
109
|
-
"gitHead": "
|
|
109
|
+
"gitHead": "a0659a2c5d82fe76a703396ea3f4c51d25526599"
|
|
110
110
|
}
|
package/src/validate/files.ts
CHANGED
|
@@ -89,7 +89,7 @@ export const toParseError = (error: unknown, content = ''): ParseError => {
|
|
|
89
89
|
};
|
|
90
90
|
};
|
|
91
91
|
|
|
92
|
-
type Extras = Pick<FileContext, 'readFile' | 'anchorLinks'>;
|
|
92
|
+
type Extras = Pick<FileContext, 'readFile' | 'anchorLinks' | 'variables'>;
|
|
93
93
|
|
|
94
94
|
const parseErrorDiagnostic = (file: string, error: ParseError): Diagnostic => ({
|
|
95
95
|
...error,
|
|
@@ -176,7 +176,14 @@ const checkFile = (
|
|
|
176
176
|
const treeRules = rules.filter((rule) => !rule.raw);
|
|
177
177
|
const varEdits = kind === 'snippet' ? variableEdits(content, variables) : [];
|
|
178
178
|
const source = applyEdits(content, varEdits);
|
|
179
|
-
const rawContext = {
|
|
179
|
+
const rawContext = {
|
|
180
|
+
file,
|
|
181
|
+
content: source,
|
|
182
|
+
baseDir,
|
|
183
|
+
frontmatter: {},
|
|
184
|
+
tree: EMPTY_TREE,
|
|
185
|
+
variables,
|
|
186
|
+
};
|
|
180
187
|
|
|
181
188
|
const rawFound = raw.flatMap((rule) =>
|
|
182
189
|
rule.check(rawContext).map((finding) => ({ rule, finding }))
|
|
@@ -194,7 +201,7 @@ const checkFile = (
|
|
|
194
201
|
toDiagnostic(rule, file, finding, stages, SOURCE_STAGE)
|
|
195
202
|
);
|
|
196
203
|
|
|
197
|
-
const parsed = parseFile(file, afterRaw, baseDir, extras);
|
|
204
|
+
const parsed = parseFile(file, afterRaw, baseDir, { ...extras, variables });
|
|
198
205
|
if ('error' in parsed) {
|
|
199
206
|
diagnostics.unshift(
|
|
200
207
|
parseErrorDiagnostic(file, {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { replaceVariables } from '@mintlify/common';
|
|
2
|
+
import { visit } from 'unist-util-visit';
|
|
3
|
+
|
|
4
|
+
import type { FileFinding, FileRule } from '../../types.js';
|
|
5
|
+
|
|
6
|
+
const exists = (file: string, readFile: (file: string) => string | undefined) =>
|
|
7
|
+
!file.startsWith('/') &&
|
|
8
|
+
(readFile(`_snippets/${file}`) !== undefined ||
|
|
9
|
+
(file.startsWith('snippets/') && readFile(file) !== undefined));
|
|
10
|
+
|
|
11
|
+
export const missingSnippet: FileRule = {
|
|
12
|
+
id: 'missing-snippet',
|
|
13
|
+
group: 'content',
|
|
14
|
+
severity: 'error',
|
|
15
|
+
scope: 'file',
|
|
16
|
+
description: '<Snippet file> tags whose file the build cannot find',
|
|
17
|
+
check: ({ tree, readFile, variables }) => {
|
|
18
|
+
if (!readFile) return [];
|
|
19
|
+
const issues: FileFinding[] = [];
|
|
20
|
+
visit(tree, 'mdxJsxFlowElement', (node) => {
|
|
21
|
+
if (node.name !== 'Snippet') return;
|
|
22
|
+
const value = node.attributes.find(
|
|
23
|
+
(attribute) => attribute.type === 'mdxJsxAttribute' && attribute.name === 'file'
|
|
24
|
+
)?.value;
|
|
25
|
+
if (typeof value !== 'string') return;
|
|
26
|
+
const file = replaceVariables(value, variables);
|
|
27
|
+
if (exists(file, readFile)) return;
|
|
28
|
+
issues.push({
|
|
29
|
+
line: node.position?.start.line,
|
|
30
|
+
column: node.position?.start.column,
|
|
31
|
+
message: `<Snippet file="${value}"> renders nothing: no snippet matches "${file}"`,
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
return issues;
|
|
35
|
+
},
|
|
36
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { maskCode, toPosition } from '../../mdx.js';
|
|
2
|
+
import type { FileRule } from '../../types.js';
|
|
3
|
+
|
|
4
|
+
const VARIABLE = /(?<!=\s*)\{\{\s*([\w.\-]+)\s*\}\}/g;
|
|
5
|
+
|
|
6
|
+
export const undefinedVariable: FileRule = {
|
|
7
|
+
id: 'undefined-variable',
|
|
8
|
+
group: 'content',
|
|
9
|
+
severity: 'error',
|
|
10
|
+
scope: 'file',
|
|
11
|
+
description: '{{variables}} that docs.json does not define',
|
|
12
|
+
raw: true,
|
|
13
|
+
check: ({ content, variables = {} }) =>
|
|
14
|
+
[...maskCode(content).matchAll(VARIABLE)]
|
|
15
|
+
.filter(([, name]) => name !== undefined && variables[name] === undefined)
|
|
16
|
+
.map((match) => ({
|
|
17
|
+
...toPosition(content, match.index),
|
|
18
|
+
message: `{{${match[1]}}} is not defined in the variables of docs.json`,
|
|
19
|
+
})),
|
|
20
|
+
};
|
|
@@ -61,7 +61,7 @@ const getSpecifier = (snippet: string, file: string, name: string): string | und
|
|
|
61
61
|
export const unknownComponent: FileRule = {
|
|
62
62
|
id: 'unknown-component',
|
|
63
63
|
group: 'content',
|
|
64
|
-
severity: '
|
|
64
|
+
severity: 'error',
|
|
65
65
|
scope: 'file',
|
|
66
66
|
description: 'JSX components that are not built in, imported or exported',
|
|
67
67
|
pagesOnly: true,
|
|
@@ -3,6 +3,8 @@ import { BUILD_RULES } from './build.js';
|
|
|
3
3
|
import { duplicateH1 } from './content/duplicateH1.js';
|
|
4
4
|
import { foreignSyntax } from './content/foreignSyntax.js';
|
|
5
5
|
import { katexRule } from './content/katex.js';
|
|
6
|
+
import { missingSnippet } from './content/missingSnippet.js';
|
|
7
|
+
import { undefinedVariable } from './content/undefinedVariable.js';
|
|
6
8
|
import { unknownComponent } from './content/unknownComponent.js';
|
|
7
9
|
import { voidTags } from './content/voidTags.js';
|
|
8
10
|
import { openApiSpec } from './openapi.js';
|
|
@@ -12,6 +14,8 @@ export const RULES: Rule[] = [
|
|
|
12
14
|
voidTags,
|
|
13
15
|
duplicateH1,
|
|
14
16
|
unknownComponent,
|
|
17
|
+
undefinedVariable,
|
|
18
|
+
missingSnippet,
|
|
15
19
|
katexRule,
|
|
16
20
|
foreignSyntax,
|
|
17
21
|
openApiSpec,
|
package/src/validate/types.ts
CHANGED
|
@@ -55,6 +55,7 @@ export type FileContext = {
|
|
|
55
55
|
tree: Root;
|
|
56
56
|
readFile?: (file: string) => string | undefined;
|
|
57
57
|
anchorLinks?: ReadonlySet<string>;
|
|
58
|
+
variables?: Record<string, string>;
|
|
58
59
|
};
|
|
59
60
|
|
|
60
61
|
/** A finding in one file, with an optional edit against the content the rule saw. */
|