@next/codemod 16.2.0-canary.9 → 16.2.0-canary.91

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,326 @@
1
+ /* global jest */
2
+ jest.autoMockOff()
3
+
4
+ const fs = require('fs')
5
+ const path = require('path')
6
+ const os = require('os')
7
+ const { runAgentsMd } = require('../../bin/agents-md')
8
+ const { getNextjsVersion } = require('../../lib/agents-md')
9
+
10
+ /**
11
+ * TRUE E2E TESTS
12
+ * These tests invoke the actual CLI entry point (runAgentsMd),
13
+ * simulating what happens when a user runs:
14
+ * npx @next/codemod agents-md --version 15.0.0 --output CLAUDE.md
15
+ */
16
+ describe('agents-md e2e (CLI invocation)', () => {
17
+ let testProjectDir
18
+ let originalConsoleLog
19
+ let consoleOutput
20
+
21
+ beforeEach(() => {
22
+ // Create isolated test project directory
23
+ const tmpBase = process.env.NEXT_TEST_DIR || os.tmpdir()
24
+ testProjectDir = path.join(
25
+ tmpBase,
26
+ `agents-md-e2e-${Date.now()}-${(Math.random() * 1000) | 0}`
27
+ )
28
+ fs.mkdirSync(testProjectDir, { recursive: true })
29
+
30
+ // Mock console.log to capture CLI output
31
+ originalConsoleLog = console.log
32
+ consoleOutput = []
33
+ console.log = (...args) => {
34
+ consoleOutput.push(args.join(' '))
35
+ }
36
+ })
37
+
38
+ afterEach(() => {
39
+ // Restore console.log
40
+ console.log = originalConsoleLog
41
+
42
+ // Clean up test directory
43
+ if (testProjectDir && fs.existsSync(testProjectDir)) {
44
+ fs.rmSync(testProjectDir, { recursive: true, force: true })
45
+ }
46
+ })
47
+
48
+ it('creates CLAUDE.md and .next-docs directory when run with --version and --output', async () => {
49
+ // Create a minimal package.json (not required, but realistic)
50
+ const packageJson = {
51
+ name: 'test-project',
52
+ version: '1.0.0',
53
+ dependencies: {
54
+ next: '15.0.0',
55
+ },
56
+ }
57
+ fs.writeFileSync(
58
+ path.join(testProjectDir, 'package.json'),
59
+ JSON.stringify(packageJson, null, 2)
60
+ )
61
+
62
+ // Change to test directory
63
+ const originalCwd = process.cwd()
64
+ process.chdir(testProjectDir)
65
+
66
+ try {
67
+ // Run the actual CLI command
68
+ await runAgentsMd({
69
+ version: '15.0.0',
70
+ output: 'CLAUDE.md',
71
+ })
72
+
73
+ // Verify .next-docs directory was created and populated
74
+ const docsDir = path.join(testProjectDir, '.next-docs')
75
+ expect(fs.existsSync(docsDir)).toBe(true)
76
+
77
+ const docFiles = fs.readdirSync(docsDir, { recursive: true })
78
+ expect(docFiles.length).toBeGreaterThan(0)
79
+
80
+ // Should contain mdx/md files
81
+ const mdxFiles = docFiles.filter(
82
+ (f) => f.endsWith('.mdx') || f.endsWith('.md')
83
+ )
84
+ expect(mdxFiles.length).toBeGreaterThan(0)
85
+
86
+ // Verify CLAUDE.md was created
87
+ const claudeMdPath = path.join(testProjectDir, 'CLAUDE.md')
88
+ expect(fs.existsSync(claudeMdPath)).toBe(true)
89
+
90
+ const claudeMdContent = fs.readFileSync(claudeMdPath, 'utf-8')
91
+
92
+ // Verify content structure
93
+ expect(claudeMdContent).toContain('<!-- NEXT-AGENTS-MD-START -->')
94
+ expect(claudeMdContent).toContain('<!-- NEXT-AGENTS-MD-END -->')
95
+ expect(claudeMdContent).toContain('[Next.js Docs Index]')
96
+ expect(claudeMdContent).toContain('root: ./.next-docs')
97
+
98
+ // Verify paths are normalized to forward slashes (cross-platform)
99
+ const lines = claudeMdContent.split('|')
100
+ const pathLines = lines.filter((line) => line.includes(':'))
101
+ pathLines.forEach((line) => {
102
+ // Should not contain Windows backslashes in the output
103
+ const pathPart = line.split(':')[0]
104
+ if (pathPart && pathPart.includes('/')) {
105
+ expect(line).not.toMatch(/[^:]\\/)
106
+ }
107
+ })
108
+
109
+ // Verify .gitignore was updated
110
+ const gitignorePath = path.join(testProjectDir, '.gitignore')
111
+ if (fs.existsSync(gitignorePath)) {
112
+ const gitignoreContent = fs.readFileSync(gitignorePath, 'utf-8')
113
+ expect(gitignoreContent).toContain('.next-docs')
114
+ }
115
+
116
+ // Verify console output
117
+ const output = consoleOutput.join('\n')
118
+ expect(output).toContain('Downloading Next.js')
119
+ expect(output).toContain('15.0.0')
120
+ expect(output).toContain('CLAUDE.md')
121
+ } finally {
122
+ // Restore original directory
123
+ process.chdir(originalCwd)
124
+ }
125
+ })
126
+
127
+ it('updates existing CLAUDE.md without losing content', async () => {
128
+ const originalCwd = process.cwd()
129
+ process.chdir(testProjectDir)
130
+
131
+ try {
132
+ // Create existing CLAUDE.md with custom content
133
+ const existingContent = `# My Project
134
+
135
+ This is my project documentation.
136
+
137
+ ## Features
138
+ - Feature 1
139
+ - Feature 2
140
+ `
141
+ fs.writeFileSync(
142
+ path.join(testProjectDir, 'CLAUDE.md'),
143
+ existingContent
144
+ )
145
+
146
+ // Run CLI
147
+ await runAgentsMd({
148
+ version: '15.0.0',
149
+ output: 'CLAUDE.md',
150
+ })
151
+
152
+ // Verify file was updated, not replaced
153
+ const claudeMdContent = fs.readFileSync(
154
+ path.join(testProjectDir, 'CLAUDE.md'),
155
+ 'utf-8'
156
+ )
157
+
158
+ // Original content should still be there
159
+ expect(claudeMdContent).toContain('# My Project')
160
+ expect(claudeMdContent).toContain('This is my project documentation.')
161
+ expect(claudeMdContent).toContain('## Features')
162
+
163
+ // New index should be injected
164
+ expect(claudeMdContent).toContain('<!-- NEXT-AGENTS-MD-START -->')
165
+ expect(claudeMdContent).toContain('[Next.js Docs Index]')
166
+ } finally {
167
+ process.chdir(originalCwd)
168
+ }
169
+ })
170
+
171
+ it('handles custom output filename', async () => {
172
+ const originalCwd = process.cwd()
173
+ process.chdir(testProjectDir)
174
+
175
+ try {
176
+ // Run with custom output file
177
+ await runAgentsMd({
178
+ version: '15.0.0',
179
+ output: 'AGENTS.md',
180
+ })
181
+
182
+ // Verify AGENTS.md was created (not CLAUDE.md)
183
+ expect(fs.existsSync(path.join(testProjectDir, 'AGENTS.md'))).toBe(true)
184
+ expect(fs.existsSync(path.join(testProjectDir, 'CLAUDE.md'))).toBe(false)
185
+
186
+ const agentsMdContent = fs.readFileSync(
187
+ path.join(testProjectDir, 'AGENTS.md'),
188
+ 'utf-8'
189
+ )
190
+ expect(agentsMdContent).toContain('[Next.js Docs Index]')
191
+ } finally {
192
+ process.chdir(originalCwd)
193
+ }
194
+ })
195
+
196
+ it('works when run from a subdirectory', async () => {
197
+ const originalCwd = process.cwd()
198
+
199
+ // Create a subdirectory
200
+ const subDir = path.join(testProjectDir, 'packages', 'app')
201
+ fs.mkdirSync(subDir, { recursive: true })
202
+
203
+ // Create package.json in root
204
+ const packageJson = {
205
+ dependencies: { next: '15.0.0' },
206
+ }
207
+ fs.writeFileSync(
208
+ path.join(testProjectDir, 'package.json'),
209
+ JSON.stringify(packageJson)
210
+ )
211
+
212
+ // Change to subdirectory
213
+ process.chdir(subDir)
214
+
215
+ try {
216
+ // Run from subdirectory - should create files in CWD (subdirectory)
217
+ await runAgentsMd({
218
+ version: '15.0.0',
219
+ output: 'CLAUDE.md',
220
+ })
221
+
222
+ // Verify files created in subdirectory
223
+ expect(fs.existsSync(path.join(subDir, 'CLAUDE.md'))).toBe(true)
224
+ expect(fs.existsSync(path.join(subDir, '.next-docs'))).toBe(true)
225
+ } finally {
226
+ process.chdir(originalCwd)
227
+ }
228
+ })
229
+
230
+ it('normalizes paths on Windows (cross-platform test)', async () => {
231
+ const originalCwd = process.cwd()
232
+ process.chdir(testProjectDir)
233
+
234
+ try {
235
+ await runAgentsMd({
236
+ version: '15.0.0',
237
+ output: 'CLAUDE.md',
238
+ })
239
+
240
+ const claudeMdContent = fs.readFileSync(
241
+ path.join(testProjectDir, 'CLAUDE.md'),
242
+ 'utf-8'
243
+ )
244
+
245
+ // Extract the index content between markers
246
+ const startMarker = '<!-- NEXT-AGENTS-MD-START -->'
247
+ const endMarker = '<!-- NEXT-AGENTS-MD-END -->'
248
+ const startIdx = claudeMdContent.indexOf(startMarker) + startMarker.length
249
+ const endIdx = claudeMdContent.indexOf(endMarker)
250
+ const indexContent = claudeMdContent.slice(startIdx, endIdx)
251
+
252
+ // Parse the index (format: "dir:{file1,file2}|dir2:{file3}")
253
+ const sections = indexContent.split('|').filter((s) => s.includes(':'))
254
+
255
+ sections.forEach((section) => {
256
+ const [dirPath, filesStr] = section.split(':')
257
+ if (dirPath && dirPath.trim() && !dirPath.includes('root')) {
258
+ // Verify no Windows backslashes in directory paths
259
+ expect(dirPath).not.toContain('\\')
260
+ // Verify uses forward slashes
261
+ if (dirPath.includes('/')) {
262
+ expect(dirPath).toMatch(/^[^\\]+$/)
263
+ }
264
+ }
265
+ })
266
+ } finally {
267
+ process.chdir(originalCwd)
268
+ }
269
+ })
270
+
271
+ it('handles version that requires git clone from GitHub', async () => {
272
+ const originalCwd = process.cwd()
273
+ process.chdir(testProjectDir)
274
+
275
+ try {
276
+ // Use a known stable version
277
+ await runAgentsMd({
278
+ version: '14.2.0',
279
+ output: 'CLAUDE.md',
280
+ })
281
+
282
+ // Verify docs were downloaded
283
+ const docsDir = path.join(testProjectDir, '.next-docs')
284
+ expect(fs.existsSync(docsDir)).toBe(true)
285
+
286
+ const docFiles = fs.readdirSync(docsDir, { recursive: true })
287
+ const mdxFiles = docFiles.filter(
288
+ (f) => f.endsWith('.mdx') || f.endsWith('.md')
289
+ )
290
+ expect(mdxFiles.length).toBeGreaterThan(50) // Should have many doc files
291
+ } finally {
292
+ process.chdir(originalCwd)
293
+ }
294
+ }, 30000) // Increase timeout for git clone
295
+
296
+ describe('getNextjsVersion', () => {
297
+ const fixturesDir = path.join(__dirname, 'fixtures/agents-md')
298
+
299
+ it('returns the installed Next.js version from node_modules', () => {
300
+ const fixture = path.join(fixturesDir, 'next-specific-version')
301
+ const result = getNextjsVersion(fixture)
302
+
303
+ expect(result.version).toBe('15.4.0')
304
+ expect(result.error).toBeUndefined()
305
+ })
306
+
307
+ it('returns actual installed version, not the tag from package.json', () => {
308
+ // package.json has "next": "latest", but node_modules has version "16.0.0"
309
+ const fixture = path.join(fixturesDir, 'next-tag')
310
+ const result = getNextjsVersion(fixture)
311
+
312
+ // Should return the actual installed version, not "latest"
313
+ expect(result.version).toBe('16.0.0')
314
+ expect(result.error).toBeUndefined()
315
+ })
316
+
317
+ it('returns error when Next.js is not installed', () => {
318
+ // Use a directory where next is not installed
319
+ const nonNextDir = '/tmp'
320
+ const result = getNextjsVersion(nonNextDir)
321
+
322
+ expect(result.version).toBeNull()
323
+ expect(result.error).toBe('Next.js is not installed in this project.')
324
+ })
325
+ })
326
+ })
package/lib/agents-md.js CHANGED
@@ -16,27 +16,17 @@ exports.buildDocTree = buildDocTree;
16
16
  exports.generateClaudeMdIndex = generateClaudeMdIndex;
17
17
  exports.injectIntoClaudeMd = injectIntoClaudeMd;
18
18
  exports.ensureGitignoreEntry = ensureGitignoreEntry;
19
- const child_process_1 = require("child_process");
19
+ const execa_1 = __importDefault(require("execa"));
20
20
  const fs_1 = __importDefault(require("fs"));
21
21
  const path_1 = __importDefault(require("path"));
22
22
  const os_1 = __importDefault(require("os"));
23
23
  function getNextjsVersion(cwd) {
24
- const packageJsonPath = path_1.default.join(cwd, 'package.json');
25
- if (!fs_1.default.existsSync(packageJsonPath)) {
26
- return {
27
- version: null,
28
- error: 'No package.json found in the current directory',
29
- };
30
- }
31
24
  try {
32
- const packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf-8'));
33
- const dependencies = packageJson.dependencies || {};
34
- const devDependencies = packageJson.devDependencies || {};
35
- const nextVersion = dependencies.next || devDependencies.next;
36
- if (nextVersion) {
37
- const cleanVersion = nextVersion.replace(/^[\^~>=<]+/, '');
38
- return { version: cleanVersion };
39
- }
25
+ const nextPkgPath = require.resolve('next/package.json', { paths: [cwd] });
26
+ const pkg = JSON.parse(fs_1.default.readFileSync(nextPkgPath, 'utf-8'));
27
+ return { version: pkg.version };
28
+ }
29
+ catch {
40
30
  // Not found at root - check for monorepo workspace
41
31
  const workspace = detectWorkspace(cwd);
42
32
  if (workspace.isMonorepo && workspace.packages.length > 0) {
@@ -54,12 +44,6 @@ function getNextjsVersion(cwd) {
54
44
  error: 'Next.js is not installed in this project.',
55
45
  };
56
46
  }
57
- catch (err) {
58
- return {
59
- version: null,
60
- error: `Failed to parse package.json: ${err instanceof Error ? err.message : String(err)}`,
61
- };
62
- }
63
47
  }
64
48
  function versionToGitHubTag(version) {
65
49
  return version.startsWith('v') ? version : `v${version}`;
@@ -108,7 +92,17 @@ async function cloneDocsFolder(tag, destDir) {
108
92
  const tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'next-agents-md-'));
109
93
  try {
110
94
  try {
111
- (0, child_process_1.execSync)(`git clone --depth 1 --filter=blob:none --sparse --branch ${tag} https://github.com/vercel/next.js.git .`, { cwd: tempDir, stdio: 'pipe' });
95
+ await (0, execa_1.default)('git', [
96
+ 'clone',
97
+ '--depth',
98
+ '1',
99
+ '--filter=blob:none',
100
+ '--sparse',
101
+ '--branch',
102
+ tag,
103
+ 'https://github.com/vercel/next.js.git',
104
+ '.',
105
+ ], { cwd: tempDir });
112
106
  }
113
107
  catch (error) {
114
108
  const message = error instanceof Error ? error.message : String(error);
@@ -117,7 +111,7 @@ async function cloneDocsFolder(tag, destDir) {
117
111
  }
118
112
  throw error;
119
113
  }
120
- (0, child_process_1.execSync)('git sparse-checkout set docs', { cwd: tempDir, stdio: 'pipe' });
114
+ await (0, execa_1.default)('git', ['sparse-checkout', 'set', 'docs'], { cwd: tempDir });
121
115
  const sourceDocsDir = path_1.default.join(tempDir, 'docs');
122
116
  if (!fs_1.default.existsSync(sourceDocsDir)) {
123
117
  throw new Error('docs folder not found in cloned repository');
@@ -137,16 +131,16 @@ async function cloneDocsFolder(tag, destDir) {
137
131
  function collectDocFiles(dir) {
138
132
  return fs_1.default.readdirSync(dir, { recursive: true })
139
133
  .filter((f) => (f.endsWith('.mdx') || f.endsWith('.md')) &&
140
- !f.endsWith('/index.mdx') &&
141
- !f.endsWith('/index.md') &&
134
+ !/[/\\]index\.mdx$/.test(f) &&
135
+ !/[/\\]index\.md$/.test(f) &&
142
136
  !f.startsWith('index.'))
143
137
  .sort()
144
- .map((f) => ({ relativePath: f }));
138
+ .map((f) => ({ relativePath: f.replace(/\\/g, '/') }));
145
139
  }
146
140
  function buildDocTree(files) {
147
141
  const sections = new Map();
148
142
  for (const file of files) {
149
- const parts = file.relativePath.split('/');
143
+ const parts = file.relativePath.split(/[/\\]/);
150
144
  if (parts.length < 2)
151
145
  continue;
152
146
  const topLevelDir = parts[0];
@@ -221,7 +215,7 @@ function collectAllFilesFromSections(sections) {
221
215
  function groupByDirectory(files) {
222
216
  const grouped = new Map();
223
217
  for (const filePath of files) {
224
- const lastSlash = filePath.lastIndexOf('/');
218
+ const lastSlash = Math.max(filePath.lastIndexOf('/'), filePath.lastIndexOf('\\'));
225
219
  const dir = lastSlash === -1 ? '.' : filePath.slice(0, lastSlash);
226
220
  const fileName = lastSlash === -1 ? filePath : filePath.slice(lastSlash + 1);
227
221
  const existing = grouped.get(dir);
@@ -387,19 +381,17 @@ function findNextjsInWorkspace(cwd, patterns) {
387
381
  const packagePaths = expandWorkspacePatterns(cwd, patterns);
388
382
  const versions = [];
389
383
  for (const pkgPath of packagePaths) {
390
- const packageJsonPath = path_1.default.join(pkgPath, 'package.json');
391
- if (!fs_1.default.existsSync(packageJsonPath))
392
- continue;
393
384
  try {
394
- const content = fs_1.default.readFileSync(packageJsonPath, 'utf-8');
395
- const pkg = JSON.parse(content);
396
- const nextVersion = pkg.dependencies?.next || pkg.devDependencies?.next;
397
- if (nextVersion) {
398
- versions.push(nextVersion.replace(/^[\^~>=<]+/, ''));
385
+ const nextPkgPath = require.resolve('next/package.json', {
386
+ paths: [pkgPath],
387
+ });
388
+ const pkg = JSON.parse(fs_1.default.readFileSync(nextPkgPath, 'utf-8'));
389
+ if (pkg.version) {
390
+ versions.push(pkg.version);
399
391
  }
400
392
  }
401
393
  catch {
402
- // Skip invalid package.json
394
+ // Next.js not installed in this package
403
395
  }
404
396
  }
405
397
  return findHighestVersion(versions);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@next/codemod",
3
- "version": "16.2.0-canary.9",
3
+ "version": "16.2.0-canary.91",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,142 +0,0 @@
1
- /* global jest */
2
- jest.autoMockOff()
3
-
4
- const { injectIntoClaudeMd, buildDocTree } = require('../agents-md')
5
-
6
- describe('agents-md', () => {
7
- describe('injectIntoClaudeMd', () => {
8
- const START_MARKER = '<!-- NEXT-AGENTS-MD-START -->'
9
- const END_MARKER = '<!-- NEXT-AGENTS-MD-END -->'
10
-
11
- it('appends to empty file', () => {
12
- const result = injectIntoClaudeMd('', 'index content')
13
- // Empty string doesn't end with \n, so separator is \n\n
14
- expect(result).toBe(`\n\n${START_MARKER}index content${END_MARKER}\n`)
15
- })
16
-
17
- it('appends to file without markers', () => {
18
- const existing = '# My Project\n\nSome existing content.'
19
- const result = injectIntoClaudeMd(existing, 'index content')
20
- expect(result).toBe(
21
- `${existing}\n\n${START_MARKER}index content${END_MARKER}\n`
22
- )
23
- })
24
-
25
- it('replaces content between existing markers', () => {
26
- const existing = `# My Project
27
-
28
- Some content before.
29
-
30
- ${START_MARKER}old index${END_MARKER}
31
-
32
- Some content after.`
33
- const result = injectIntoClaudeMd(existing, 'new index')
34
- expect(result).toBe(`# My Project
35
-
36
- Some content before.
37
-
38
- ${START_MARKER}new index${END_MARKER}
39
-
40
- Some content after.`)
41
- })
42
-
43
- it('is idempotent - running twice produces same result', () => {
44
- const initial = '# Project\n'
45
- const first = injectIntoClaudeMd(initial, 'index v1')
46
- const second = injectIntoClaudeMd(first, 'index v1')
47
- expect(second).toBe(first)
48
- })
49
-
50
- it('preserves content before and after markers on update', () => {
51
- const before = '# Header\n\nIntro paragraph.'
52
- const after = '\n\n## Footer\n\nMore content.'
53
- const existing = `${before}\n\n${START_MARKER}old${END_MARKER}${after}`
54
- const result = injectIntoClaudeMd(existing, 'new')
55
- expect(result).toContain(before)
56
- expect(result).toContain(after)
57
- expect(result).toContain(`${START_MARKER}new${END_MARKER}`)
58
- expect(result).not.toContain('old')
59
- })
60
- })
61
-
62
- describe('buildDocTree', () => {
63
- it('groups files by top-level directory', () => {
64
- const files = [
65
- { relativePath: '01-getting-started/installation.mdx' },
66
- { relativePath: '01-getting-started/project-structure.mdx' },
67
- { relativePath: '02-app/routing.mdx' },
68
- ]
69
- const tree = buildDocTree(files)
70
-
71
- expect(tree).toHaveLength(2)
72
- expect(tree[0].name).toBe('01-getting-started')
73
- expect(tree[0].files).toHaveLength(2)
74
- expect(tree[1].name).toBe('02-app')
75
- expect(tree[1].files).toHaveLength(1)
76
- })
77
-
78
- it('creates nested subsections for deeper paths', () => {
79
- const files = [
80
- { relativePath: '02-app/01-building/layouts.mdx' },
81
- { relativePath: '02-app/01-building/pages.mdx' },
82
- { relativePath: '02-app/02-api/route-handlers.mdx' },
83
- ]
84
- const tree = buildDocTree(files)
85
-
86
- expect(tree).toHaveLength(1)
87
- const appSection = tree[0]
88
- expect(appSection.name).toBe('02-app')
89
- expect(appSection.files).toHaveLength(0) // no direct files
90
- expect(appSection.subsections).toHaveLength(2)
91
-
92
- const building = appSection.subsections.find(
93
- (s) => s.name === '01-building'
94
- )
95
- expect(building).toBeDefined()
96
- expect(building.files).toHaveLength(2)
97
-
98
- const api = appSection.subsections.find((s) => s.name === '02-api')
99
- expect(api).toBeDefined()
100
- expect(api.files).toHaveLength(1)
101
- })
102
-
103
- it('handles 4-level deep paths with sub-subsections', () => {
104
- const files = [
105
- { relativePath: '02-app/01-building/01-routing/dynamic-routes.mdx' },
106
- { relativePath: '02-app/01-building/01-routing/parallel-routes.mdx' },
107
- ]
108
- const tree = buildDocTree(files)
109
-
110
- const routing = tree[0].subsections[0].subsections[0]
111
- expect(routing.name).toBe('01-routing')
112
- expect(routing.files).toHaveLength(2)
113
- })
114
-
115
- it('skips single-segment paths (root-level files)', () => {
116
- const files = [
117
- { relativePath: 'index.mdx' },
118
- { relativePath: '01-getting-started/intro.mdx' },
119
- ]
120
- const tree = buildDocTree(files)
121
-
122
- // Root-level index.mdx should be skipped (parts.length < 2)
123
- expect(tree).toHaveLength(1)
124
- expect(tree[0].name).toBe('01-getting-started')
125
- })
126
-
127
- it('sorts sections and files alphabetically', () => {
128
- const files = [
129
- { relativePath: 'z-section/b-file.mdx' },
130
- { relativePath: 'a-section/z-file.mdx' },
131
- { relativePath: 'a-section/a-file.mdx' },
132
- { relativePath: 'z-section/a-file.mdx' },
133
- ]
134
- const tree = buildDocTree(files)
135
-
136
- expect(tree[0].name).toBe('a-section')
137
- expect(tree[1].name).toBe('z-section')
138
- expect(tree[0].files[0].relativePath).toBe('a-section/a-file.mdx')
139
- expect(tree[0].files[1].relativePath).toBe('a-section/z-file.mdx')
140
- })
141
- })
142
- })