@mintlify/cli 4.0.1410 → 4.0.1412

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.
@@ -287,7 +287,8 @@ var CODE_BLOCK_ATTRS = [
287
287
  { name: "focus", type: "expression" },
288
288
  { name: "add", type: "expression" },
289
289
  { name: "remove", type: "expression" },
290
- { name: "twoslash", type: "boolean" }
290
+ { name: "twoslash", type: "boolean" },
291
+ { name: "nocopy", type: "boolean" }
291
292
  ];
292
293
  var PROMPT_BLOCK_ATTRS = [
293
294
  { name: "icon", type: "expression" },
@@ -1883,7 +1884,7 @@ var parseMetaString = (meta) => {
1883
1884
  }
1884
1885
  continue;
1885
1886
  }
1886
- if (token === "expandable" || token === "lines" || token === "wrap" || token === "twoslash") {
1887
+ if (token === "expandable" || token === "lines" || token === "wrap" || token === "twoslash" || token === "nocopy") {
1887
1888
  result[token] = true;
1888
1889
  continue;
1889
1890
  }
@@ -2189,13 +2190,16 @@ function handleCode(node, ctx) {
2189
2190
  };
2190
2191
  }
2191
2192
  const meta = parseMetaString(node.meta || "");
2193
+ const isNoCopyLanguage = node.lang === "nocopy";
2194
+ if (isNoCopyLanguage && meta.nocopy === void 0)
2195
+ meta.nocopy = true;
2192
2196
  const { cleanedCode, addLines, removeLines } = extractDiffMarkers(node.value || "");
2193
2197
  const diffAttrs = {};
2194
2198
  if (addLines.length > 0)
2195
2199
  diffAttrs.add = addLines;
2196
2200
  if (removeLines.length > 0)
2197
2201
  diffAttrs.remove = removeLines;
2198
- return Object.assign({ type: BLOCK_TYPES.codeBlock, attrs: Object.assign(Object.assign(Object.assign({ language: node.lang || null }, meta), diffAttrs), { __isHidden: ctx.isHidden, __hasGroupParent: ctx.hasGroupParent }) }, cleanedCode ? { content: [createTextNode(cleanedCode)] } : {});
2202
+ return Object.assign({ type: BLOCK_TYPES.codeBlock, attrs: Object.assign(Object.assign(Object.assign({ language: isNoCopyLanguage ? "text" : node.lang || null }, meta), diffAttrs), { __isHidden: ctx.isHidden, __hasGroupParent: ctx.hasGroupParent }) }, cleanedCode ? { content: [createTextNode(cleanedCode)] } : {});
2199
2203
  }
2200
2204
 
2201
2205
  // ../editor/dist/converter/parse/mdast-to-pm.js
@@ -2949,6 +2953,8 @@ function renderCodeBlock(state, node) {
2949
2953
  }
2950
2954
  if (node.attrs.twoslash)
2951
2955
  metaString += ` twoslash`;
2956
+ if (node.attrs.nocopy)
2957
+ metaString += ` nocopy`;
2952
2958
  state.write(fence + lang + metaString + "\n");
2953
2959
  const indentedContent = state.delim ? content.replace(/\n/g, "\n" + state.delim) : content;
2954
2960
  state.write(indentedContent);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mintlify/cli",
3
- "version": "4.0.1410",
3
+ "version": "4.0.1412",
4
4
  "description": "The Mintlify CLI",
5
5
  "engines": {
6
6
  "node": ">=18.0.0"
@@ -45,11 +45,11 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@inquirer/prompts": "7.9.0",
48
- "@mintlify/common": "1.0.1096",
49
- "@mintlify/link-rot": "3.0.1296",
48
+ "@mintlify/common": "1.0.1097",
49
+ "@mintlify/link-rot": "3.0.1297",
50
50
  "@mintlify/models": "0.0.350",
51
- "@mintlify/prebuild": "1.0.1249",
52
- "@mintlify/previewing": "4.0.1320",
51
+ "@mintlify/prebuild": "1.0.1250",
52
+ "@mintlify/previewing": "4.0.1321",
53
53
  "@mintlify/validation": "0.1.832",
54
54
  "adm-zip": "0.6.0",
55
55
  "chalk": "5.2.0",
@@ -82,7 +82,7 @@
82
82
  "keytar": "7.9.0"
83
83
  },
84
84
  "devDependencies": {
85
- "@mintlify/editor": "0.0.307",
85
+ "@mintlify/editor": "0.0.308",
86
86
  "@mintlify/ts-config": "2.0.2",
87
87
  "@tsconfig/recommended": "1.0.2",
88
88
  "@types/detect-port": "1.3.2",
@@ -101,5 +101,5 @@
101
101
  "vitest": "2.1.9",
102
102
  "vitest-mock-process": "1.0.4"
103
103
  },
104
- "gitHead": "bbee26e7f0c4a9522b6709c544fc93f93a845e92"
104
+ "gitHead": "1bfd03ce61fae544f73b2d45300b161d2cc158f2"
105
105
  }
package/src/cli.tsx CHANGED
@@ -24,8 +24,6 @@ import { comingSoon } from './comingSoon.js';
24
24
  import { setTelemetryEnabled } from './config.js';
25
25
  import { getConfigValue, setConfigValue, clearConfigValue } from './config.js';
26
26
  import { API_URL } from './constants.js';
27
- import { deslopHandler } from './deslop/index.js';
28
- import { resolveExplicitFiles } from './deslop/resolveFiles.js';
29
27
  import { formatHandler } from './format.js';
30
28
  import {
31
29
  CMD_EXEC_PATH,
@@ -44,9 +42,9 @@ import { getAccessToken } from './keyring.js';
44
42
  import { login } from './login.js';
45
43
  import { logout } from './logout.js';
46
44
  import { mdxLinter } from './mdxLinter.js';
47
- import { subdomainMiddleware } from './middlewares/subdomainMiddleware.js';
48
45
  import { createTelemetryMiddleware } from './middlewares/telemetryMiddleware.js';
49
46
  import { checkOpenApiFile, getOpenApiFilenamesFromDocsConfig } from './openApiCheck.js';
47
+ import { resolveExplicitFiles } from './resolveFiles.js';
50
48
  import { scoreHandler } from './score/index.js';
51
49
  import { signup } from './signup.js';
52
50
  import { status, getCliSubdomains } from './status.js';
@@ -746,54 +744,6 @@ export const cli = ({ packageName = 'mint' }: { packageName?: string }) => {
746
744
  .example('mint score docs.example.com', 'Run agent readiness checks on a URL'),
747
745
  scoreHandler
748
746
  )
749
- .command(
750
- 'deslop [files..]',
751
- 'Detect AI-sounding prose and get humanization suggestions',
752
- (yargs: Argv) =>
753
- yargs
754
- .middleware(subdomainMiddleware)
755
- .positional('files', {
756
- type: 'string',
757
- array: true,
758
- description: 'Files or globs to check (defaults to git-changed pages)',
759
- })
760
- .option('format', {
761
- type: 'string',
762
- choices: ['table', 'plain', 'json'] as const,
763
- description: 'Output format',
764
- })
765
- .option('subdomain', {
766
- type: 'string',
767
- description: 'Documentation subdomain (default: mint config set subdomain)',
768
- })
769
- .option('threshold', {
770
- type: 'number',
771
- default: 0.5,
772
- description: 'Fail a page when its AI-written fraction meets this value (0-1)',
773
- })
774
- .option('fix-whitespace', {
775
- type: 'boolean',
776
- default: false,
777
- description:
778
- 'Normalize prose whitespace in the checked files (skips code and frontmatter)',
779
- })
780
- .check((argv) => {
781
- if (
782
- typeof argv.threshold === 'number' &&
783
- (argv.threshold < 0 || argv.threshold > 1 || Number.isNaN(argv.threshold))
784
- ) {
785
- throw new Error('--threshold must be a number between 0 and 1');
786
- }
787
- return true;
788
- })
789
- .example('mint deslop', 'Check git-changed pages for AI-sounding prose')
790
- .example('mint deslop docs/guide.mdx', 'Check a specific page')
791
- .example(
792
- 'mint deslop "docs/**/*.mdx" --format json',
793
- 'Check pages by glob with agent-friendly JSON output'
794
- ),
795
- deslopHandler
796
- )
797
747
  .command(
798
748
  'format',
799
749
  'Format MDX files in the current directory',
@@ -1,5 +1,3 @@
1
- import { categorizeFilePaths, getMintIgnore } from '@mintlify/prebuild';
2
- import { execFile } from 'node:child_process';
3
1
  import fs from 'node:fs/promises';
4
2
  import path from 'node:path';
5
3
 
@@ -45,15 +43,6 @@ function globToRegExp(pattern: string): RegExp {
45
43
  return new RegExp(`^${source}$`);
46
44
  }
47
45
 
48
- async function exists(filePath: string): Promise<boolean> {
49
- try {
50
- await fs.access(filePath);
51
- return true;
52
- } catch {
53
- return false;
54
- }
55
- }
56
-
57
46
  async function isFile(filePath: string): Promise<boolean> {
58
47
  try {
59
48
  return (await fs.stat(filePath)).isFile();
@@ -108,69 +97,3 @@ export async function resolveExplicitFiles(baseDir: string, args: string[]): Pro
108
97
 
109
98
  return [...new Set(matched)];
110
99
  }
111
-
112
- function runGit(args: string[], cwd: string): Promise<string> {
113
- return new Promise((resolve, reject) => {
114
- execFile('git', args, { cwd }, (error, stdout) => {
115
- if (error) reject(error);
116
- else resolve(stdout);
117
- });
118
- });
119
- }
120
-
121
- function parseGitLines(stdout: string): string[] {
122
- return stdout
123
- .split('\n')
124
- .map((line) => line.trim())
125
- .filter(Boolean);
126
- }
127
-
128
- export async function resolveChangedFiles(baseDir: string): Promise<string[]> {
129
- try {
130
- await runGit(['rev-parse', '--is-inside-work-tree'], baseDir);
131
- } catch {
132
- throw new Error(
133
- 'Not a git repository, so changed pages cannot be detected. Pass explicit paths: `mint deslop <files..>`.'
134
- );
135
- }
136
-
137
- let diff: string;
138
- let untracked: string;
139
- try {
140
- [diff, untracked] = await Promise.all([
141
- runGit(['-c', 'core.quotePath=false', 'diff', '--name-only', '--relative', 'HEAD'], baseDir),
142
- runGit(['-c', 'core.quotePath=false', 'ls-files', '--others', '--exclude-standard'], baseDir),
143
- ]);
144
- } catch {
145
- throw new Error(
146
- 'Could not detect changed pages (does the repository have any commits?). Pass explicit paths: `mint deslop <files..>`.'
147
- );
148
- }
149
-
150
- const candidates = [...new Set([...parseGitLines(diff), ...parseGitLines(untracked)])]
151
- .map(normalizeArg)
152
- .filter((file) => DOCS_FILE.test(file));
153
-
154
- const existing: string[] = [];
155
- for (const file of candidates) {
156
- if (await exists(path.join(baseDir, file))) existing.push(file);
157
- }
158
- if (existing.length === 0) return [];
159
-
160
- if (!(await exists(path.join(baseDir, 'docs.json')))) {
161
- return existing;
162
- }
163
-
164
- try {
165
- const mintIgnore = await getMintIgnore(baseDir);
166
- const { contentFilenames } = await categorizeFilePaths(baseDir, mintIgnore);
167
- const contentSet = new Set(contentFilenames.map((file) => toPosix(file).replace(/^\//, '')));
168
- return existing.filter((file) => contentSet.has(file));
169
- } catch {
170
- // docs.json is present but its navigation could not be resolved. Fail closed
171
- // rather than sending possibly-ignored files to the credit-consuming API.
172
- throw new Error(
173
- 'Could not read docs.json to determine which pages to check. Pass explicit paths: `mint deslop <files..>`.'
174
- );
175
- }
176
- }
@@ -1,167 +0,0 @@
1
- import { checkPage, checkPages } from '../../src/deslop/client.js';
2
- import type { DeslopCheckedResponse } from '../../src/deslop/types.js';
3
-
4
- vi.mock('../../src/keyring.js', () => ({
5
- getAccessToken: vi.fn().mockResolvedValue(null),
6
- getRefreshToken: vi.fn().mockResolvedValue(null),
7
- storeCredentials: vi.fn().mockResolvedValue(undefined),
8
- }));
9
-
10
- const mockFetch = vi.fn();
11
- global.fetch = mockFetch;
12
-
13
- beforeEach(() => {
14
- vi.stubEnv('MINTLIFY_SESSION_TOKEN', 'test-token');
15
- vi.stubEnv('MINTLIFY_API_URL', 'http://test-server:5000');
16
- mockFetch.mockReset();
17
- });
18
-
19
- afterEach(() => {
20
- vi.unstubAllEnvs();
21
- });
22
-
23
- const checked: DeslopCheckedResponse = {
24
- path: 'docs/foo.mdx',
25
- skipped: null,
26
- predictionShort: 'AI',
27
- fractionAi: 0.8,
28
- fractionAiAssisted: 0.1,
29
- fractionHuman: 0.1,
30
- windows: [],
31
- creditsCharged: 1,
32
- };
33
-
34
- function mockOk(data: unknown) {
35
- mockFetch.mockResolvedValueOnce({
36
- ok: true,
37
- status: 200,
38
- json: () => Promise.resolve(data),
39
- });
40
- }
41
-
42
- function mockStatus(status: number, body: string) {
43
- mockFetch.mockResolvedValueOnce({
44
- ok: false,
45
- status,
46
- statusText: 'Error',
47
- text: () => Promise.resolve(body),
48
- });
49
- }
50
-
51
- function calledUrl(): URL {
52
- const arg = mockFetch.mock.calls[0]![0];
53
- return new URL(typeof arg === 'string' ? arg : String(arg));
54
- }
55
-
56
- function calledInit(): RequestInit {
57
- return mockFetch.mock.calls[0]![1] as RequestInit;
58
- }
59
-
60
- describe('checkPage auth', () => {
61
- it('throws when no session token is set', async () => {
62
- vi.stubEnv('MINTLIFY_SESSION_TOKEN', '');
63
- await expect(checkPage('docs', 'a.mdx', '# hi')).rejects.toThrow('Not authenticated');
64
- });
65
-
66
- it('sends bearer token header', async () => {
67
- mockOk(checked);
68
- await checkPage('docs', 'a.mdx', '# hi');
69
- expect(mockFetch).toHaveBeenCalledWith(
70
- expect.any(String),
71
- expect.objectContaining({
72
- headers: expect.objectContaining({ Authorization: 'Bearer test-token' }),
73
- })
74
- );
75
- });
76
- });
77
-
78
- describe('checkPage request', () => {
79
- it('POSTs path and content to the deslop route with the subdomain', async () => {
80
- mockOk(checked);
81
- const result = await checkPage('docs', 'docs/foo.mdx', '# Hello\n');
82
-
83
- const url = calledUrl();
84
- expect(url.pathname).toBe('/api/cli/deslop');
85
- expect(url.searchParams.get('subdomain')).toBe('docs');
86
- const init = calledInit();
87
- expect(init.method).toBe('POST');
88
- expect(init.body).toBe(JSON.stringify({ path: 'docs/foo.mdx', content: '# Hello\n' }));
89
- expect(init.headers).toMatchObject({ 'Content-Type': 'application/json' });
90
- expect(result).toMatchObject({ path: 'docs/foo.mdx', predictionShort: 'AI' });
91
- });
92
-
93
- it('maps 402 to an out-of-credits message', async () => {
94
- mockStatus(402, '{"error":"insufficient_credits"}');
95
- await expect(checkPage('docs', 'a.mdx', 'x')).rejects.toThrow(
96
- 'Out of AI credits — upgrade or top up in the Mintlify dashboard.'
97
- );
98
- });
99
-
100
- it('maps 503 to an unavailable message', async () => {
101
- mockStatus(503, '{"error":"deslop_unavailable"}');
102
- await expect(checkPage('docs', 'a.mdx', 'x')).rejects.toThrow(
103
- 'Deslop is temporarily unavailable, try again shortly.'
104
- );
105
- });
106
-
107
- it('throws a generic API error for other failures', async () => {
108
- mockStatus(500, 'boom');
109
- await expect(checkPage('docs', 'a.mdx', 'x')).rejects.toThrow('API error (500): boom');
110
- });
111
- });
112
-
113
- describe('checkPages', () => {
114
- it('records a per-file error without aborting the batch', async () => {
115
- mockFetch.mockImplementation((_url: string, init: RequestInit) => {
116
- const body = JSON.parse(String(init.body)) as { path: string };
117
- if (body.path === 'docs/bad.mdx') {
118
- return Promise.reject(new Error('socket hang up'));
119
- }
120
- return Promise.resolve({
121
- ok: true,
122
- status: 200,
123
- json: () => Promise.resolve({ ...checked, path: body.path }),
124
- });
125
- });
126
-
127
- const pages = [
128
- { path: 'docs/a.mdx', content: 'a' },
129
- { path: 'docs/bad.mdx', content: 'b' },
130
- { path: 'docs/c.mdx', content: 'c' },
131
- ];
132
- const results = await checkPages('docs', pages);
133
-
134
- expect(results).toHaveLength(3);
135
- expect(results[0]).toMatchObject({ file: 'docs/a.mdx', response: { path: 'docs/a.mdx' } });
136
- expect(results[1]).toEqual({ file: 'docs/bad.mdx', error: 'socket hang up' });
137
- expect(results[2]).toMatchObject({ file: 'docs/c.mdx', response: { path: 'docs/c.mdx' } });
138
- });
139
-
140
- it('preserves input order for more pages than the concurrency limit', async () => {
141
- mockFetch.mockImplementation((_url: string, init: RequestInit) => {
142
- const body = JSON.parse(String(init.body)) as { path: string };
143
- const delay = body.path === 'p0.mdx' ? 30 : 1;
144
- return new Promise((resolve) =>
145
- setTimeout(
146
- () =>
147
- resolve({
148
- ok: true,
149
- status: 200,
150
- json: () => Promise.resolve({ ...checked, path: body.path }),
151
- }),
152
- delay
153
- )
154
- );
155
- });
156
-
157
- const pages = Array.from({ length: 9 }, (_, i) => ({ path: `p${i}.mdx`, content: 'x' }));
158
- const results = await checkPages('docs', pages);
159
-
160
- expect(results).toHaveLength(9);
161
- results.forEach((result, i) => {
162
- expect(result.file).toBe(`p${i}.mdx`);
163
- expect(result).toMatchObject({ response: { path: `p${i}.mdx` } });
164
- });
165
- expect(mockFetch).toHaveBeenCalledTimes(9);
166
- });
167
- });
@@ -1,183 +0,0 @@
1
- import { categorizeFilePaths } from '@mintlify/prebuild';
2
- import { execFile } from 'node:child_process';
3
- import fs from 'node:fs/promises';
4
- import os from 'node:os';
5
- import path from 'node:path';
6
-
7
- import { resolveChangedFiles, resolveExplicitFiles } from '../../src/deslop/resolveFiles.js';
8
-
9
- vi.mock('node:child_process', () => ({ execFile: vi.fn() }));
10
-
11
- vi.mock('@mintlify/prebuild', () => ({
12
- getMintIgnore: vi.fn().mockResolvedValue([]),
13
- categorizeFilePaths: vi.fn().mockResolvedValue({ contentFilenames: [] }),
14
- }));
15
-
16
- const mockExecFile = vi.mocked(execFile);
17
- const mockCategorize = vi.mocked(categorizeFilePaths);
18
-
19
- type GitCallback = (error: Error | null, stdout: string, stderr: string) => void;
20
-
21
- function mockGit(outputs: Record<string, string | Error>) {
22
- mockExecFile.mockImplementation(((
23
- _file: string,
24
- args: string[],
25
- _options: unknown,
26
- callback: GitCallback
27
- ) => {
28
- const key = args.join(' ');
29
- const value = outputs[key];
30
- if (value === undefined) {
31
- callback(new Error(`unexpected git command: ${key}`), '', '');
32
- } else if (value instanceof Error) {
33
- callback(value, '', '');
34
- } else {
35
- callback(null, value, '');
36
- }
37
- }) as never);
38
- }
39
-
40
- let baseDir: string;
41
-
42
- async function writeFixture(rel: string) {
43
- const filePath = path.join(baseDir, rel);
44
- await fs.mkdir(path.dirname(filePath), { recursive: true });
45
- await fs.writeFile(filePath, '# fixture\n');
46
- }
47
-
48
- beforeEach(async () => {
49
- baseDir = await fs.mkdtemp(path.join(os.tmpdir(), 'deslop-test-'));
50
- mockExecFile.mockReset();
51
- mockCategorize.mockClear();
52
- mockCategorize.mockResolvedValue({ contentFilenames: [] } as never);
53
- });
54
-
55
- afterEach(async () => {
56
- await fs.rm(baseDir, { recursive: true, force: true });
57
- });
58
-
59
- describe('resolveExplicitFiles', () => {
60
- it('resolves literal paths', async () => {
61
- await writeFixture('docs/a.mdx');
62
- await expect(resolveExplicitFiles(baseDir, ['docs/a.mdx'])).resolves.toEqual(['docs/a.mdx']);
63
- });
64
-
65
- it('normalizes leading ./', async () => {
66
- await writeFixture('docs/a.md');
67
- await expect(resolveExplicitFiles(baseDir, ['./docs/a.md'])).resolves.toEqual(['docs/a.md']);
68
- });
69
-
70
- it('errors listing paths that matched nothing', async () => {
71
- await writeFixture('docs/a.mdx');
72
- await expect(
73
- resolveExplicitFiles(baseDir, ['docs/a.mdx', 'docs/missing.mdx', 'nope/*.mdx'])
74
- ).rejects.toThrow('No matching files for: docs/missing.mdx, nope/*.mdx');
75
- });
76
-
77
- it('accepts existing literal paths regardless of extension', async () => {
78
- await writeFixture('README.txt');
79
- await expect(resolveExplicitFiles(baseDir, ['README.txt'])).resolves.toEqual(['README.txt']);
80
- });
81
-
82
- it('rejects a directory passed as a literal path', async () => {
83
- await fs.mkdir(path.join(baseDir, 'docs'), { recursive: true });
84
- await expect(resolveExplicitFiles(baseDir, ['docs'])).rejects.toThrow('No matching files');
85
- });
86
-
87
- it('expands globs recursively and keeps only docs files', async () => {
88
- await writeFixture('docs/a.mdx');
89
- await writeFixture('docs/nested/deep/b.mdx');
90
- await writeFixture('docs/nested/notes.txt');
91
- await writeFixture('top.md');
92
-
93
- await expect(resolveExplicitFiles(baseDir, ['docs/**/*.mdx'])).resolves.toEqual([
94
- 'docs/a.mdx',
95
- 'docs/nested/deep/b.mdx',
96
- ]);
97
- await expect(resolveExplicitFiles(baseDir, ['*.md'])).resolves.toEqual(['top.md']);
98
- });
99
-
100
- it('dedupes overlapping literal and glob matches', async () => {
101
- await writeFixture('docs/a.mdx');
102
- await expect(resolveExplicitFiles(baseDir, ['docs/a.mdx', 'docs/*.mdx'])).resolves.toEqual([
103
- 'docs/a.mdx',
104
- ]);
105
- });
106
- });
107
-
108
- describe('resolveChangedFiles', () => {
109
- it('throws a friendly error outside a git repo', async () => {
110
- mockGit({ 'rev-parse --is-inside-work-tree': new Error('not a git repository') });
111
- await expect(resolveChangedFiles(baseDir)).rejects.toThrow(
112
- 'Not a git repository, so changed pages cannot be detected. Pass explicit paths: `mint deslop <files..>`.'
113
- );
114
- });
115
-
116
- it('unions diff and untracked files, keeping existing docs pages in the content set', async () => {
117
- await writeFixture('docs.json');
118
- await writeFixture('docs/a.mdx');
119
- await writeFixture('docs/b.md');
120
- mockGit({
121
- 'rev-parse --is-inside-work-tree': 'true\n',
122
- '-c core.quotePath=false diff --name-only --relative HEAD':
123
- 'docs/a.mdx\nsrc/code.ts\ndocs/gone.mdx\n',
124
- '-c core.quotePath=false ls-files --others --exclude-standard': 'docs/b.md\nREADME.txt\n',
125
- });
126
- mockCategorize.mockResolvedValue({
127
- contentFilenames: ['/docs/a.mdx', 'docs/b.md', '/docs/c.mdx'],
128
- } as never);
129
-
130
- await expect(resolveChangedFiles(baseDir)).resolves.toEqual(['docs/a.mdx', 'docs/b.md']);
131
- });
132
-
133
- it('skips the content-set filter outside a docs repo', async () => {
134
- await writeFixture('README.md');
135
- mockGit({
136
- 'rev-parse --is-inside-work-tree': 'true\n',
137
- '-c core.quotePath=false diff --name-only --relative HEAD': 'README.md\nsrc/code.ts\n',
138
- '-c core.quotePath=false ls-files --others --exclude-standard': '',
139
- });
140
-
141
- await expect(resolveChangedFiles(baseDir)).resolves.toEqual(['README.md']);
142
- expect(mockCategorize).not.toHaveBeenCalled();
143
- });
144
-
145
- it('excludes changed pages missing from the content set (mintignored)', async () => {
146
- await writeFixture('docs.json');
147
- await writeFixture('docs/a.mdx');
148
- await writeFixture('internal/secret.mdx');
149
- mockGit({
150
- 'rev-parse --is-inside-work-tree': 'true\n',
151
- '-c core.quotePath=false diff --name-only --relative HEAD':
152
- 'docs/a.mdx\ninternal/secret.mdx\n',
153
- '-c core.quotePath=false ls-files --others --exclude-standard': '',
154
- });
155
- mockCategorize.mockResolvedValue({ contentFilenames: ['docs/a.mdx'] } as never);
156
-
157
- await expect(resolveChangedFiles(baseDir)).resolves.toEqual(['docs/a.mdx']);
158
- });
159
-
160
- it('fails closed when the content set cannot be resolved in a docs repo', async () => {
161
- await writeFixture('docs.json');
162
- await writeFixture('docs/a.mdx');
163
- mockGit({
164
- 'rev-parse --is-inside-work-tree': 'true\n',
165
- '-c core.quotePath=false diff --name-only --relative HEAD': 'docs/a.mdx\n',
166
- '-c core.quotePath=false ls-files --others --exclude-standard': '',
167
- });
168
- mockCategorize.mockRejectedValue(new Error('bad docs.json'));
169
-
170
- await expect(resolveChangedFiles(baseDir)).rejects.toThrow('Could not read docs.json');
171
- });
172
-
173
- it('returns empty without scanning content when nothing changed', async () => {
174
- mockGit({
175
- 'rev-parse --is-inside-work-tree': 'true\n',
176
- '-c core.quotePath=false diff --name-only --relative HEAD': 'src/code.ts\n',
177
- '-c core.quotePath=false ls-files --others --exclude-standard': '',
178
- });
179
-
180
- await expect(resolveChangedFiles(baseDir)).resolves.toEqual([]);
181
- expect(mockCategorize).not.toHaveBeenCalled();
182
- });
183
- });