@mintlify/cli 4.0.1743 → 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.
@@ -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: 2, warnings: 0, byRule: { 'parse-error': 1, 'void-tags': 1 } },
118
+ summary: { errors: 1, warnings: 1, byRule: { 'duplicate-h1': 1, 'void-tags': 1 } },
122
119
  diagnostics: [
123
120
  expect.objectContaining({
124
- rule: 'void-tags',
121
+ rule: 'duplicate-h1',
125
122
  group: 'content',
126
- severity: 'error',
123
+ severity: 'warning',
127
124
  file: 'index.mdx',
128
125
  line: 5,
129
- fix: { file: 'index.mdx', start: 25, end: 29, text: '<br />' },
126
+ fix: { file: 'index.mdx', start: 21, end: 29, text: '' },
130
127
  }),
131
128
  expect.objectContaining({
132
- rule: 'parse-error',
129
+ rule: 'void-tags',
133
130
  severity: 'error',
134
- file: 'other.mdx',
135
- message: expect.stringContaining('(other checks were skipped for this file)'),
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': HOME });
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 lists them', async () => {
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, missing-nav-page'))
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\nmore<hr>\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 fsp from 'node:fs/promises';
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(['void-tags', 'missing-file', 'import-error']);
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 = () => ({ baseDir: dir, files: [] }) as unknown as 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(fsp, 'readFile');
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