@mintlify/cli 4.0.1742 → 4.0.1744
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__/foreignSyntax.test.ts +73 -0
- package/__test__/validateCommand.test.ts +17 -20
- package/__test__/validateEngine.test.ts +17 -4
- package/__test__/validateRules.test.ts +659 -8
- package/bin/tsconfig.build.tsbuildinfo +1 -1
- package/bin/validate/engine.js +6 -13
- package/bin/validate/files.js +26 -30
- package/bin/validate/mdx.js +38 -1
- package/bin/validate/rules/content/duplicateH1.js +57 -0
- package/bin/validate/rules/content/foreignSyntax.js +120 -0
- package/bin/validate/rules/content/katex.js +34 -0
- package/bin/validate/rules/content/unknownComponent.js +121 -0
- package/bin/validate/rules/index.js +13 -1
- package/bin/validate/site.js +11 -9
- package/package.json +10 -8
- package/src/validate/engine.ts +6 -12
- package/src/validate/files.ts +41 -31
- package/src/validate/mdx.ts +38 -0
- package/src/validate/rules/content/duplicateH1.ts +78 -0
- package/src/validate/rules/content/foreignSyntax.ts +144 -0
- package/src/validate/rules/content/katex.ts +33 -0
- package/src/validate/rules/content/unknownComponent.ts +130 -0
- package/src/validate/rules/index.ts +13 -1
- package/src/validate/site.ts +9 -8
- package/src/validate/types.ts +3 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { foreignSyntax } from '../src/validate/rules/content/foreignSyntax.js';
|
|
2
|
+
|
|
3
|
+
const check = (content: string) =>
|
|
4
|
+
foreignSyntax.check({
|
|
5
|
+
file: 'page.mdx',
|
|
6
|
+
content,
|
|
7
|
+
baseDir: '/',
|
|
8
|
+
frontmatter: {},
|
|
9
|
+
tree: { type: 'root', children: [] },
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const messages = (content: string) => check(content).map(({ message }) => message);
|
|
13
|
+
|
|
14
|
+
describe('foreign-syntax', () => {
|
|
15
|
+
it.each([
|
|
16
|
+
['GitBook hint', '{% hint style="warning" %}\nCareful\n{% endhint %}\n', '<Warning>'],
|
|
17
|
+
['Liquid tag', '{% include note.html %}\n', 'snippet import'],
|
|
18
|
+
['Hugo shortcode', '{{< figure src="a.png" >}}\n', 'Hugo shortcode'],
|
|
19
|
+
['Liquid variable', 'Version {{ site.version }}\n', 'Liquid variable'],
|
|
20
|
+
['Liquid filter', '[Home]({{ "/" | relative_url }})\n', 'Liquid variable'],
|
|
21
|
+
['Docusaurus admonition', ':::tip\nUse it\n:::\n', '<Tip>'],
|
|
22
|
+
['VitePress admonition with title', '::: warning Heads up\nx\n:::\n', '<Warning>'],
|
|
23
|
+
['MDC block', '::note\nx\n::\n', 'MDC'],
|
|
24
|
+
['MkDocs admonition', '!!! note "Title"\n Body\n', '<Note>'],
|
|
25
|
+
['MkDocs collapsible', '??? info\n Body\n', '<Accordion>'],
|
|
26
|
+
['MkDocs tab', '=== "Python"\n code\n', '<Tabs>'],
|
|
27
|
+
['MkDocs snippet in a fence', '```py\n--8<-- "example.py"\n```\n', 'snippet include'],
|
|
28
|
+
['Kramdown IAL', 'Text\n{: .note }\n', 'Kramdown'],
|
|
29
|
+
['wiki link', 'See [[Getting Started]].\n', 'wiki link'],
|
|
30
|
+
['wiki link with label', 'See [[setup|Install]].\n', 'wiki link'],
|
|
31
|
+
['ReadMe doc link', 'See [setup](doc:setup).\n', 'such as /setup'],
|
|
32
|
+
['ReadMe ref link in href', '<a href="ref:create-user">x</a>\n', 'such as /create-user'],
|
|
33
|
+
[
|
|
34
|
+
'Docusaurus @site import',
|
|
35
|
+
"import Img from '@site/static/img/a.png';\n\n<img src={Img} />\n",
|
|
36
|
+
'@site',
|
|
37
|
+
],
|
|
38
|
+
])('flags %s', (_name, content, expected) => {
|
|
39
|
+
const found = messages(content);
|
|
40
|
+
expect(found).toHaveLength(1);
|
|
41
|
+
expect(found[0]).toContain(expected);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('flags each opening GitBook tag once, not its closing tag', () => {
|
|
45
|
+
expect(messages('{% tabs %}\n{% tab title="A" %}\nx\n{% endtab %}\n{% endtabs %}\n')).toEqual([
|
|
46
|
+
expect.stringContaining('use <Tabs>'),
|
|
47
|
+
expect.stringContaining('use <Tab title="…">'),
|
|
48
|
+
]);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it.each([
|
|
52
|
+
['fenced code', '```liquid\n{% hint style="info" %}\n{{ site.title }}\n:::note\n```\n'],
|
|
53
|
+
['inline code', 'Write `{% hint %}`, `:::note` and `[[Page]]` like this.\n'],
|
|
54
|
+
['frontmatter', '---\ntitle: "{% hint %}"\ndescription: "[[Page]]"\n---\n\nBody\n'],
|
|
55
|
+
['MDX expressions', 'Total: {a | b} and {{ x: 1 }.x} and {[[1, 2]].length} and {{page}}\n'],
|
|
56
|
+
['closing tags and bare fences', ':::\n\n::\n'],
|
|
57
|
+
['Markdown link after a bracket', '[[1]](#note-1) and [a][ref]\n'],
|
|
58
|
+
['ordinary prose', 'Use doc: the guide. Wow!!! Equal === sign.\n'],
|
|
59
|
+
['custom heading ids', '## Install {#install}\n'],
|
|
60
|
+
])('ignores %s', (_name, content) => {
|
|
61
|
+
expect(messages(content)).toEqual([]);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('reports positions in the original file', () => {
|
|
65
|
+
expect(check('---\ntitle: A\n---\n\nText\n :::note\nx\n:::\n')).toEqual([
|
|
66
|
+
expect.objectContaining({ line: 6, column: 3 }),
|
|
67
|
+
]);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('never offers edits', () => {
|
|
71
|
+
expect(check('{% hint %}\n').every(({ edit }) => edit === undefined)).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -109,30 +109,27 @@ describe('mint validate', () => {
|
|
|
109
109
|
});
|
|
110
110
|
|
|
111
111
|
it('prints one JSON report with a summary and every diagnostic', async () => {
|
|
112
|
-
writeSite({
|
|
113
|
-
'index.mdx': '---\ntitle: Home\n---\n\nline<br>\n',
|
|
114
|
-
'other.mdx': '---\ntitle: Other\n---\n\nText {broken\n',
|
|
115
|
-
});
|
|
112
|
+
writeSite({ 'index.mdx': '---\ntitle: Home\n---\n\n# Home\n\nline<br>\n' });
|
|
116
113
|
|
|
117
114
|
const result = await report('validate', '--no-build');
|
|
118
115
|
|
|
119
116
|
expect(result).toEqual({
|
|
120
117
|
passed: false,
|
|
121
|
-
summary: { errors:
|
|
118
|
+
summary: { errors: 1, warnings: 1, byRule: { 'duplicate-h1': 1, 'void-tags': 1 } },
|
|
122
119
|
diagnostics: [
|
|
123
120
|
expect.objectContaining({
|
|
124
|
-
rule: '
|
|
121
|
+
rule: 'duplicate-h1',
|
|
125
122
|
group: 'content',
|
|
126
|
-
severity: '
|
|
123
|
+
severity: 'warning',
|
|
127
124
|
file: 'index.mdx',
|
|
128
125
|
line: 5,
|
|
129
|
-
fix: { file: 'index.mdx', start:
|
|
126
|
+
fix: { file: 'index.mdx', start: 21, end: 29, text: '' },
|
|
130
127
|
}),
|
|
131
128
|
expect.objectContaining({
|
|
132
|
-
rule: '
|
|
129
|
+
rule: 'void-tags',
|
|
133
130
|
severity: 'error',
|
|
134
|
-
|
|
135
|
-
|
|
131
|
+
line: 7,
|
|
132
|
+
fix: { file: 'index.mdx', start: 33, end: 37, text: '<br />' },
|
|
136
133
|
}),
|
|
137
134
|
],
|
|
138
135
|
build: { ran: false, reason: 'disabled' },
|
|
@@ -141,13 +138,13 @@ describe('mint validate', () => {
|
|
|
141
138
|
expect(processExitMock).toHaveBeenCalledWith(1);
|
|
142
139
|
});
|
|
143
140
|
|
|
144
|
-
it('passes and runs the build', async () => {
|
|
145
|
-
writeSite({ 'index.mdx':
|
|
141
|
+
it('passes with warnings and runs the build', async () => {
|
|
142
|
+
writeSite({ 'index.mdx': '---\ntitle: Home\n---\n\n# Home\n' });
|
|
146
143
|
|
|
147
144
|
const result = await report('validate');
|
|
148
145
|
|
|
149
146
|
expect(result).toMatchObject({ passed: true, build: { ran: true, passed: true } });
|
|
150
|
-
expect(rules(result)).toEqual([]);
|
|
147
|
+
expect(rules(result)).toEqual(['duplicate-h1']);
|
|
151
148
|
expect(checkBuild).toHaveBeenCalled();
|
|
152
149
|
expect(sendPublishPrompt).not.toHaveBeenCalled();
|
|
153
150
|
expect(processExitMock).toHaveBeenCalledWith(0);
|
|
@@ -417,8 +414,8 @@ describe('mint validate', () => {
|
|
|
417
414
|
expect(errors).toContain('progress line\n');
|
|
418
415
|
});
|
|
419
416
|
|
|
420
|
-
it('applies fixes with --fix and
|
|
421
|
-
writeSite({ 'index.mdx': '---\ntitle: Home\n---\n\nline<br>\n' });
|
|
417
|
+
it('applies fixes with --fix, lists them and leaves unfixable issues', async () => {
|
|
418
|
+
writeSite({ 'index.mdx': '---\ntitle: Home\n---\n\nline<br>\n\n<Missing />\n' });
|
|
422
419
|
|
|
423
420
|
const result = await report('validate', '--no-build', '--fix');
|
|
424
421
|
|
|
@@ -433,13 +430,13 @@ describe('mint validate', () => {
|
|
|
433
430
|
after: '<br />',
|
|
434
431
|
},
|
|
435
432
|
]);
|
|
436
|
-
expect(rules(result)).toEqual([]);
|
|
433
|
+
expect(rules(result)).toEqual(['unknown-component']);
|
|
437
434
|
expect(processExitMock).toHaveBeenCalledWith(0);
|
|
438
435
|
});
|
|
439
436
|
|
|
440
437
|
it('turns rules and groups on and off', async () => {
|
|
441
438
|
writeSite({
|
|
442
|
-
'index.mdx': '---\ntitle: Home\n---\n\nline<br>\n',
|
|
439
|
+
'index.mdx': '---\ntitle: Home\n---\n\n# Home\n\nline<br>\n',
|
|
443
440
|
});
|
|
444
441
|
|
|
445
442
|
// Without void-tags, nothing makes the page parseable
|
|
@@ -453,7 +450,7 @@ describe('mint validate', () => {
|
|
|
453
450
|
rules(await report('validate', '--no-build', '--disable', 'all', '--enable', 'void-tags'))
|
|
454
451
|
).toEqual(['void-tags']);
|
|
455
452
|
expect(
|
|
456
|
-
rules(await report('validate', '--no-build', '--disable', 'openapi,
|
|
453
|
+
rules(await report('validate', '--no-build', '--disable', 'openapi, duplicate-h1'))
|
|
457
454
|
).toEqual(['void-tags']);
|
|
458
455
|
});
|
|
459
456
|
|
|
@@ -474,7 +471,7 @@ describe('mint validate', () => {
|
|
|
474
471
|
writeSite(
|
|
475
472
|
{
|
|
476
473
|
'index.mdx': '---\ntitle: Home\n---\n\nline<br>\n',
|
|
477
|
-
'other.mdx': '---\ntitle: Other\n---\n\nline<br>\n
|
|
474
|
+
'other.mdx': '---\ntitle: Other\n---\n\n# Other\n\nline<br>\n',
|
|
478
475
|
},
|
|
479
476
|
{ navigation: { pages: ['index', 'other'] } }
|
|
480
477
|
);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import fs from 'fs-extra';
|
|
2
|
-
import
|
|
2
|
+
import fsSync from 'node:fs';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
|
|
@@ -24,7 +24,15 @@ describe('selectRules', () => {
|
|
|
24
24
|
it('turns on the rules of an enabled group', () => {
|
|
25
25
|
const content = ids(selectRules({ disable: ['all'], enable: ['content'] }));
|
|
26
26
|
|
|
27
|
-
expect(content).toEqual([
|
|
27
|
+
expect(content).toEqual([
|
|
28
|
+
'void-tags',
|
|
29
|
+
'duplicate-h1',
|
|
30
|
+
'unknown-component',
|
|
31
|
+
'katex',
|
|
32
|
+
'foreign-syntax',
|
|
33
|
+
'missing-file',
|
|
34
|
+
'import-error',
|
|
35
|
+
]);
|
|
28
36
|
});
|
|
29
37
|
|
|
30
38
|
it('applies --enable after --disable', () => {
|
|
@@ -67,7 +75,12 @@ describe('applyFixes', () => {
|
|
|
67
75
|
fs.removeSync(dir);
|
|
68
76
|
});
|
|
69
77
|
|
|
70
|
-
const site = () =>
|
|
78
|
+
const site = () =>
|
|
79
|
+
({
|
|
80
|
+
baseDir: dir,
|
|
81
|
+
files: [],
|
|
82
|
+
read: (file: string) => fs.readFileSync(path.join(dir, file), 'utf-8'),
|
|
83
|
+
}) as unknown as Site;
|
|
71
84
|
|
|
72
85
|
const diagnostic = (fix: Diagnostic['fix'], rule = 'r'): Diagnostic => ({
|
|
73
86
|
rule,
|
|
@@ -209,7 +222,7 @@ describe('checkSite with fix', () => {
|
|
|
209
222
|
|
|
210
223
|
it('rechecks only changed files between passes and runs site rules once more at the end', async () => {
|
|
211
224
|
const site = siteRule();
|
|
212
|
-
const reads = vi.spyOn(
|
|
225
|
+
const reads = vi.spyOn(fsSync, 'readFileSync');
|
|
213
226
|
|
|
214
227
|
const result = await checkSite(dir, [bang(3), site], { localSchema: false, fix: true });
|
|
215
228
|
|