@mintlify/cli 4.0.1800 → 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.
@@ -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 = { file, content: source, baseDir, frontmatter: {}, tree: EMPTY_TREE };
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: 'warning',
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.1800",
3
+ "version": "4.0.1801",
4
4
  "description": "The Mintlify CLI",
5
5
  "engines": {
6
6
  "node": ">=18.0.0"
@@ -106,5 +106,5 @@
106
106
  "vitest": "2.1.9",
107
107
  "vitest-mock-process": "1.0.4"
108
108
  },
109
- "gitHead": "36ff9bdfc06d97ff1bc0d958a8119db486ff46ae"
109
+ "gitHead": "a0659a2c5d82fe76a703396ea3f4c51d25526599"
110
110
  }
@@ -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 = { file, content: source, baseDir, frontmatter: {}, tree: EMPTY_TREE };
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: 'warning',
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,
@@ -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. */