@next/codemod 16.2.0-canary.2 → 16.2.0-canary.20

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,155 @@
1
+ "use strict";
2
+ /**
3
+ * CLI handler for `npx @next/codemod agents-md`.
4
+ * See ../lib/agents-md.ts for the core logic.
5
+ */
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.runAgentsMd = runAgentsMd;
11
+ const fs_1 = __importDefault(require("fs"));
12
+ const path_1 = __importDefault(require("path"));
13
+ const prompts_1 = __importDefault(require("prompts"));
14
+ const picocolors_1 = __importDefault(require("picocolors"));
15
+ const shared_1 = require("./shared");
16
+ const agents_md_1 = require("../lib/agents-md");
17
+ const utils_1 = require("../lib/utils");
18
+ const DOCS_DIR_NAME = '.next-docs';
19
+ function formatSize(bytes) {
20
+ if (bytes < 1024)
21
+ return `${bytes} B`;
22
+ const kb = bytes / 1024;
23
+ if (kb < 1024)
24
+ return `${kb.toFixed(1)} KB`;
25
+ const mb = kb / 1024;
26
+ return `${mb.toFixed(1)} MB`;
27
+ }
28
+ async function runAgentsMd(options) {
29
+ const cwd = process.cwd();
30
+ // Mode logic:
31
+ // 1. No flags → interactive mode (prompts for version + target file)
32
+ // 2. --version provided → --output is REQUIRED (error if missing)
33
+ // 3. --output alone → auto-detect version, error if not found
34
+ let nextjsVersion;
35
+ let targetFile;
36
+ if (options.version) {
37
+ // --version provided: --output is required
38
+ if (!options.output) {
39
+ throw new shared_1.BadInput('When using --version, --output is also required.\n' +
40
+ 'Example: npx @next/codemod agents-md --version 15.1.3 --output CLAUDE.md');
41
+ }
42
+ nextjsVersion = options.version;
43
+ targetFile = options.output;
44
+ }
45
+ else if (options.output) {
46
+ // --output alone: auto-detect version
47
+ const detected = (0, agents_md_1.getNextjsVersion)(cwd);
48
+ if (!detected.version) {
49
+ throw new shared_1.BadInput('Could not detect Next.js version. Use --version to specify.\n' +
50
+ `Example: npx @next/codemod agents-md --version 15.1.3 --output ${options.output}`);
51
+ }
52
+ nextjsVersion = detected.version;
53
+ targetFile = options.output;
54
+ }
55
+ else {
56
+ // No flags: interactive mode
57
+ const promptedOptions = await promptForOptions(cwd);
58
+ nextjsVersion = promptedOptions.nextVersion;
59
+ targetFile = promptedOptions.targetFile;
60
+ }
61
+ const claudeMdPath = path_1.default.join(cwd, targetFile);
62
+ const docsPath = path_1.default.join(cwd, DOCS_DIR_NAME);
63
+ const docsLinkPath = `./${DOCS_DIR_NAME}`;
64
+ let sizeBefore = 0;
65
+ let isNewFile = true;
66
+ let existingContent = '';
67
+ if (fs_1.default.existsSync(claudeMdPath)) {
68
+ existingContent = fs_1.default.readFileSync(claudeMdPath, 'utf-8');
69
+ sizeBefore = Buffer.byteLength(existingContent, 'utf-8');
70
+ isNewFile = false;
71
+ }
72
+ console.log(`\nDownloading Next.js ${picocolors_1.default.cyan(nextjsVersion)} documentation to ${picocolors_1.default.cyan(DOCS_DIR_NAME)}...`);
73
+ const pullResult = await (0, agents_md_1.pullDocs)({
74
+ cwd,
75
+ version: nextjsVersion,
76
+ docsDir: docsPath,
77
+ });
78
+ if (!pullResult.success) {
79
+ throw new shared_1.BadInput(`Failed to pull docs: ${pullResult.error}`);
80
+ }
81
+ const docFiles = (0, agents_md_1.collectDocFiles)(docsPath);
82
+ const sections = (0, agents_md_1.buildDocTree)(docFiles);
83
+ const indexContent = (0, agents_md_1.generateClaudeMdIndex)({
84
+ docsPath: docsLinkPath,
85
+ sections,
86
+ outputFile: targetFile,
87
+ });
88
+ const newContent = (0, agents_md_1.injectIntoClaudeMd)(existingContent, indexContent);
89
+ fs_1.default.writeFileSync(claudeMdPath, newContent, 'utf-8');
90
+ const sizeAfter = Buffer.byteLength(newContent, 'utf-8');
91
+ const gitignoreResult = (0, agents_md_1.ensureGitignoreEntry)(cwd);
92
+ const action = isNewFile ? 'Created' : 'Updated';
93
+ const sizeInfo = isNewFile
94
+ ? formatSize(sizeAfter)
95
+ : `${formatSize(sizeBefore)} → ${formatSize(sizeAfter)}`;
96
+ console.log(`${picocolors_1.default.green('✓')} ${action} ${picocolors_1.default.bold(targetFile)} (${sizeInfo})`);
97
+ if (gitignoreResult.updated) {
98
+ console.log(`${picocolors_1.default.green('✓')} Added ${picocolors_1.default.bold(DOCS_DIR_NAME)} to .gitignore`);
99
+ }
100
+ console.log('');
101
+ }
102
+ async function promptForOptions(cwd) {
103
+ // Detect Next.js version for default
104
+ const versionResult = (0, agents_md_1.getNextjsVersion)(cwd);
105
+ const detectedVersion = versionResult.version;
106
+ console.log(picocolors_1.default.cyan('\n@next/codemod agents-md - Next.js Documentation for AI Agents\n'));
107
+ if (detectedVersion) {
108
+ console.log(picocolors_1.default.gray(` Detected Next.js version: ${detectedVersion}\n`));
109
+ }
110
+ const response = await (0, prompts_1.default)([
111
+ {
112
+ type: 'text',
113
+ name: 'nextVersion',
114
+ message: 'Next.js version',
115
+ initial: detectedVersion || '',
116
+ validate: (value) => value.trim() ? true : 'Please enter a Next.js version',
117
+ },
118
+ {
119
+ type: 'select',
120
+ name: 'targetFile',
121
+ message: 'Target markdown file',
122
+ choices: [
123
+ { title: 'CLAUDE.md', value: 'CLAUDE.md' },
124
+ { title: 'AGENTS.md', value: 'AGENTS.md' },
125
+ { title: 'Custom...', value: '__custom__' },
126
+ ],
127
+ initial: 0,
128
+ },
129
+ ], { onCancel: utils_1.onCancel });
130
+ // Handle cancelled prompts
131
+ if (response.nextVersion === undefined || response.targetFile === undefined) {
132
+ console.log(picocolors_1.default.yellow('\nCancelled.'));
133
+ process.exit(0);
134
+ }
135
+ let targetFile = response.targetFile;
136
+ if (targetFile === '__custom__') {
137
+ const customResponse = await (0, prompts_1.default)({
138
+ type: 'text',
139
+ name: 'customFile',
140
+ message: 'Enter custom file path',
141
+ initial: 'CLAUDE.md',
142
+ validate: (value) => value.trim() ? true : 'Please enter a file path',
143
+ }, { onCancel: utils_1.onCancel });
144
+ if (customResponse.customFile === undefined) {
145
+ console.log(picocolors_1.default.yellow('\nCancelled.'));
146
+ process.exit(0);
147
+ }
148
+ targetFile = customResponse.customFile;
149
+ }
150
+ return {
151
+ nextVersion: response.nextVersion,
152
+ targetFile,
153
+ };
154
+ }
155
+ //# sourceMappingURL=agents-md.js.map
@@ -12,6 +12,7 @@
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
13
  const commander_1 = require("commander");
14
14
  const upgrade_1 = require("./upgrade");
15
+ const agents_md_1 = require("./agents-md");
15
16
  const transform_1 = require("./transform");
16
17
  const shared_1 = require("./shared");
17
18
  const packageJson = require('../package.json');
@@ -56,5 +57,24 @@ program
56
57
  process.exit(1);
57
58
  }
58
59
  });
60
+ program
61
+ .command('agents-md')
62
+ .description('Generate Next.js documentation index for AI coding agents (Claude, Cursor, etc.).')
63
+ .option('--version <version>', 'Next.js version (auto-detected if not provided)')
64
+ .option('--output <file>', 'Target file path (e.g., CLAUDE.md, AGENTS.md)')
65
+ .action(async (options) => {
66
+ try {
67
+ await (0, agents_md_1.runAgentsMd)(options);
68
+ }
69
+ catch (error) {
70
+ if (error instanceof shared_1.BadInput) {
71
+ console.error(error.message);
72
+ }
73
+ else {
74
+ console.error(error);
75
+ }
76
+ process.exit(1);
77
+ }
78
+ });
59
79
  program.parse(process.argv);
60
80
  //# sourceMappingURL=next-codemod.js.map
@@ -0,0 +1,142 @@
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
+ })
@@ -0,0 +1,519 @@
1
+ "use strict";
2
+ /**
3
+ * agents-md: Generate Next.js documentation index for AI coding agents.
4
+ *
5
+ * Downloads docs from GitHub via git sparse-checkout, builds a compact
6
+ * index of all doc files, and injects it into CLAUDE.md or AGENTS.md.
7
+ */
8
+ var __importDefault = (this && this.__importDefault) || function (mod) {
9
+ return (mod && mod.__esModule) ? mod : { "default": mod };
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.getNextjsVersion = getNextjsVersion;
13
+ exports.pullDocs = pullDocs;
14
+ exports.collectDocFiles = collectDocFiles;
15
+ exports.buildDocTree = buildDocTree;
16
+ exports.generateClaudeMdIndex = generateClaudeMdIndex;
17
+ exports.injectIntoClaudeMd = injectIntoClaudeMd;
18
+ exports.ensureGitignoreEntry = ensureGitignoreEntry;
19
+ const child_process_1 = require("child_process");
20
+ const fs_1 = __importDefault(require("fs"));
21
+ const path_1 = __importDefault(require("path"));
22
+ const os_1 = __importDefault(require("os"));
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
+ 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
+ }
40
+ // Not found at root - check for monorepo workspace
41
+ const workspace = detectWorkspace(cwd);
42
+ if (workspace.isMonorepo && workspace.packages.length > 0) {
43
+ const highestVersion = findNextjsInWorkspace(cwd, workspace.packages);
44
+ if (highestVersion) {
45
+ return { version: highestVersion };
46
+ }
47
+ return {
48
+ version: null,
49
+ error: `No Next.js found in ${workspace.type} workspace packages.`,
50
+ };
51
+ }
52
+ return {
53
+ version: null,
54
+ error: 'Next.js is not installed in this project.',
55
+ };
56
+ }
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
+ }
64
+ function versionToGitHubTag(version) {
65
+ return version.startsWith('v') ? version : `v${version}`;
66
+ }
67
+ async function pullDocs(options) {
68
+ const { cwd, version: versionOverride, docsDir } = options;
69
+ let nextjsVersion;
70
+ if (versionOverride) {
71
+ nextjsVersion = versionOverride;
72
+ }
73
+ else {
74
+ const versionResult = getNextjsVersion(cwd);
75
+ if (!versionResult.version) {
76
+ return {
77
+ success: false,
78
+ error: versionResult.error || 'Could not detect Next.js version',
79
+ };
80
+ }
81
+ nextjsVersion = versionResult.version;
82
+ }
83
+ const docsPath = docsDir ?? fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'next-agents-md-'));
84
+ const useTempDir = !docsDir;
85
+ try {
86
+ if (useTempDir && fs_1.default.existsSync(docsPath)) {
87
+ fs_1.default.rmSync(docsPath, { recursive: true });
88
+ }
89
+ const tag = versionToGitHubTag(nextjsVersion);
90
+ await cloneDocsFolder(tag, docsPath);
91
+ return {
92
+ success: true,
93
+ docsPath,
94
+ nextjsVersion,
95
+ };
96
+ }
97
+ catch (error) {
98
+ if (useTempDir && fs_1.default.existsSync(docsPath)) {
99
+ fs_1.default.rmSync(docsPath, { recursive: true });
100
+ }
101
+ return {
102
+ success: false,
103
+ error: error instanceof Error ? error.message : String(error),
104
+ };
105
+ }
106
+ }
107
+ async function cloneDocsFolder(tag, destDir) {
108
+ const tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'next-agents-md-'));
109
+ try {
110
+ 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' });
112
+ }
113
+ catch (error) {
114
+ const message = error instanceof Error ? error.message : String(error);
115
+ if (message.includes('not found') || message.includes('did not match')) {
116
+ throw new Error(`Could not find documentation for Next.js ${tag}. This version may not exist on GitHub yet.`);
117
+ }
118
+ throw error;
119
+ }
120
+ (0, child_process_1.execSync)('git sparse-checkout set docs', { cwd: tempDir, stdio: 'pipe' });
121
+ const sourceDocsDir = path_1.default.join(tempDir, 'docs');
122
+ if (!fs_1.default.existsSync(sourceDocsDir)) {
123
+ throw new Error('docs folder not found in cloned repository');
124
+ }
125
+ if (fs_1.default.existsSync(destDir)) {
126
+ fs_1.default.rmSync(destDir, { recursive: true });
127
+ }
128
+ fs_1.default.mkdirSync(destDir, { recursive: true });
129
+ fs_1.default.cpSync(sourceDocsDir, destDir, { recursive: true });
130
+ }
131
+ finally {
132
+ if (fs_1.default.existsSync(tempDir)) {
133
+ fs_1.default.rmSync(tempDir, { recursive: true });
134
+ }
135
+ }
136
+ }
137
+ function collectDocFiles(dir) {
138
+ return fs_1.default.readdirSync(dir, { recursive: true })
139
+ .filter((f) => (f.endsWith('.mdx') || f.endsWith('.md')) &&
140
+ !f.endsWith('/index.mdx') &&
141
+ !f.endsWith('/index.md') &&
142
+ !f.startsWith('index.'))
143
+ .sort()
144
+ .map((f) => ({ relativePath: f }));
145
+ }
146
+ function buildDocTree(files) {
147
+ const sections = new Map();
148
+ for (const file of files) {
149
+ const parts = file.relativePath.split('/');
150
+ if (parts.length < 2)
151
+ continue;
152
+ const topLevelDir = parts[0];
153
+ if (!sections.has(topLevelDir)) {
154
+ sections.set(topLevelDir, {
155
+ name: topLevelDir,
156
+ files: [],
157
+ subsections: [],
158
+ });
159
+ }
160
+ const section = sections.get(topLevelDir);
161
+ if (parts.length === 2) {
162
+ section.files.push({ relativePath: file.relativePath });
163
+ }
164
+ else {
165
+ const subsectionDir = parts[1];
166
+ let subsection = section.subsections.find((s) => s.name === subsectionDir);
167
+ if (!subsection) {
168
+ subsection = { name: subsectionDir, files: [], subsections: [] };
169
+ section.subsections.push(subsection);
170
+ }
171
+ if (parts.length === 3) {
172
+ subsection.files.push({ relativePath: file.relativePath });
173
+ }
174
+ else {
175
+ const subSubDir = parts[2];
176
+ let subSubsection = subsection.subsections.find((s) => s.name === subSubDir);
177
+ if (!subSubsection) {
178
+ subSubsection = { name: subSubDir, files: [], subsections: [] };
179
+ subsection.subsections.push(subSubsection);
180
+ }
181
+ subSubsection.files.push({ relativePath: file.relativePath });
182
+ }
183
+ }
184
+ }
185
+ const sortedSections = Array.from(sections.values()).sort((a, b) => a.name.localeCompare(b.name));
186
+ for (const section of sortedSections) {
187
+ section.files.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
188
+ section.subsections.sort((a, b) => a.name.localeCompare(b.name));
189
+ for (const subsection of section.subsections) {
190
+ subsection.files.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
191
+ subsection.subsections.sort((a, b) => a.name.localeCompare(b.name));
192
+ }
193
+ }
194
+ return sortedSections;
195
+ }
196
+ function generateClaudeMdIndex(data) {
197
+ const { docsPath, sections, outputFile } = data;
198
+ const parts = [];
199
+ parts.push('[Next.js Docs Index]');
200
+ parts.push(`root: ${docsPath}`);
201
+ parts.push('STOP. What you remember about Next.js is WRONG for this project. Always search docs and read before any task.');
202
+ const targetFile = outputFile || 'CLAUDE.md';
203
+ parts.push(`If docs missing, run this command first: npx @next/codemod agents-md --output ${targetFile}`);
204
+ const allFiles = collectAllFilesFromSections(sections);
205
+ const grouped = groupByDirectory(allFiles);
206
+ for (const [dir, files] of grouped) {
207
+ parts.push(`${dir}:{${files.join(',')}}`);
208
+ }
209
+ return parts.join('|');
210
+ }
211
+ function collectAllFilesFromSections(sections) {
212
+ const files = [];
213
+ for (const section of sections) {
214
+ for (const file of section.files) {
215
+ files.push(file.relativePath);
216
+ }
217
+ files.push(...collectAllFilesFromSections(section.subsections));
218
+ }
219
+ return files;
220
+ }
221
+ function groupByDirectory(files) {
222
+ const grouped = new Map();
223
+ for (const filePath of files) {
224
+ const lastSlash = filePath.lastIndexOf('/');
225
+ const dir = lastSlash === -1 ? '.' : filePath.slice(0, lastSlash);
226
+ const fileName = lastSlash === -1 ? filePath : filePath.slice(lastSlash + 1);
227
+ const existing = grouped.get(dir);
228
+ if (existing) {
229
+ existing.push(fileName);
230
+ }
231
+ else {
232
+ grouped.set(dir, [fileName]);
233
+ }
234
+ }
235
+ return grouped;
236
+ }
237
+ const START_MARKER = '<!-- NEXT-AGENTS-MD-START -->';
238
+ const END_MARKER = '<!-- NEXT-AGENTS-MD-END -->';
239
+ function hasExistingIndex(content) {
240
+ return content.includes(START_MARKER);
241
+ }
242
+ function wrapWithMarkers(content) {
243
+ return `${START_MARKER}${content}${END_MARKER}`;
244
+ }
245
+ function injectIntoClaudeMd(claudeMdContent, indexContent) {
246
+ const wrappedContent = wrapWithMarkers(indexContent);
247
+ if (hasExistingIndex(claudeMdContent)) {
248
+ const startIdx = claudeMdContent.indexOf(START_MARKER);
249
+ const endIdx = claudeMdContent.indexOf(END_MARKER) + END_MARKER.length;
250
+ return (claudeMdContent.slice(0, startIdx) +
251
+ wrappedContent +
252
+ claudeMdContent.slice(endIdx));
253
+ }
254
+ const separator = claudeMdContent.endsWith('\n') ? '\n' : '\n\n';
255
+ return claudeMdContent + separator + wrappedContent + '\n';
256
+ }
257
+ const GITIGNORE_ENTRY = '.next-docs/';
258
+ function ensureGitignoreEntry(cwd) {
259
+ const gitignorePath = path_1.default.join(cwd, '.gitignore');
260
+ const entryRegex = /^\s*\.next-docs(?:\/.*)?$/;
261
+ let content = '';
262
+ if (fs_1.default.existsSync(gitignorePath)) {
263
+ content = fs_1.default.readFileSync(gitignorePath, 'utf-8');
264
+ }
265
+ const hasEntry = content.split(/\r?\n/).some((line) => entryRegex.test(line));
266
+ if (hasEntry) {
267
+ return { path: gitignorePath, updated: false, alreadyPresent: true };
268
+ }
269
+ const needsNewline = content.length > 0 && !content.endsWith('\n');
270
+ const header = content.includes('# next-agents-md')
271
+ ? ''
272
+ : '# next-agents-md\n';
273
+ const newContent = content + (needsNewline ? '\n' : '') + header + `${GITIGNORE_ENTRY}\n`;
274
+ fs_1.default.writeFileSync(gitignorePath, newContent, 'utf-8');
275
+ return { path: gitignorePath, updated: true, alreadyPresent: false };
276
+ }
277
+ function detectWorkspace(cwd) {
278
+ const packageJsonPath = path_1.default.join(cwd, 'package.json');
279
+ // Check pnpm workspaces (pnpm-workspace.yaml)
280
+ const pnpmWorkspacePath = path_1.default.join(cwd, 'pnpm-workspace.yaml');
281
+ if (fs_1.default.existsSync(pnpmWorkspacePath)) {
282
+ const packages = parsePnpmWorkspace(pnpmWorkspacePath);
283
+ if (packages.length > 0) {
284
+ return { isMonorepo: true, type: 'pnpm', packages };
285
+ }
286
+ }
287
+ // Check npm/yarn workspaces (package.json workspaces field)
288
+ if (fs_1.default.existsSync(packageJsonPath)) {
289
+ const packages = parsePackageJsonWorkspaces(packageJsonPath);
290
+ if (packages.length > 0) {
291
+ return { isMonorepo: true, type: 'npm', packages };
292
+ }
293
+ }
294
+ // Check Lerna (lerna.json)
295
+ const lernaPath = path_1.default.join(cwd, 'lerna.json');
296
+ if (fs_1.default.existsSync(lernaPath)) {
297
+ const packages = parseLernaConfig(lernaPath);
298
+ if (packages.length > 0) {
299
+ return { isMonorepo: true, type: 'lerna', packages };
300
+ }
301
+ }
302
+ // Check Nx (nx.json)
303
+ const nxPath = path_1.default.join(cwd, 'nx.json');
304
+ if (fs_1.default.existsSync(nxPath)) {
305
+ const packages = parseNxWorkspace(cwd, packageJsonPath);
306
+ if (packages.length > 0) {
307
+ return { isMonorepo: true, type: 'nx', packages };
308
+ }
309
+ }
310
+ return { isMonorepo: false, type: null, packages: [] };
311
+ }
312
+ function parsePnpmWorkspace(filePath) {
313
+ try {
314
+ const content = fs_1.default.readFileSync(filePath, 'utf-8');
315
+ const lines = content.split('\n');
316
+ const packages = [];
317
+ let inPackages = false;
318
+ for (const line of lines) {
319
+ const trimmed = line.trim();
320
+ if (trimmed === 'packages:') {
321
+ inPackages = true;
322
+ continue;
323
+ }
324
+ if (inPackages) {
325
+ if (trimmed && !trimmed.startsWith('-') && !trimmed.startsWith('#')) {
326
+ break;
327
+ }
328
+ const match = trimmed.match(/^-\s*['"]?([^'"]+)['"]?$/);
329
+ if (match) {
330
+ packages.push(match[1]);
331
+ }
332
+ }
333
+ }
334
+ return packages;
335
+ }
336
+ catch {
337
+ return [];
338
+ }
339
+ }
340
+ function parsePackageJsonWorkspaces(filePath) {
341
+ try {
342
+ const content = fs_1.default.readFileSync(filePath, 'utf-8');
343
+ const pkg = JSON.parse(content);
344
+ if (Array.isArray(pkg.workspaces)) {
345
+ return pkg.workspaces;
346
+ }
347
+ if (pkg.workspaces?.packages && Array.isArray(pkg.workspaces.packages)) {
348
+ return pkg.workspaces.packages;
349
+ }
350
+ return [];
351
+ }
352
+ catch {
353
+ return [];
354
+ }
355
+ }
356
+ function parseLernaConfig(filePath) {
357
+ try {
358
+ const content = fs_1.default.readFileSync(filePath, 'utf-8');
359
+ const config = JSON.parse(content);
360
+ if (Array.isArray(config.packages)) {
361
+ return config.packages;
362
+ }
363
+ return ['packages/*'];
364
+ }
365
+ catch {
366
+ return [];
367
+ }
368
+ }
369
+ function parseNxWorkspace(cwd, packageJsonPath) {
370
+ if (fs_1.default.existsSync(packageJsonPath)) {
371
+ const packages = parsePackageJsonWorkspaces(packageJsonPath);
372
+ if (packages.length > 0) {
373
+ return packages;
374
+ }
375
+ }
376
+ const defaultPatterns = ['apps/*', 'libs/*', 'packages/*'];
377
+ const existingPatterns = [];
378
+ for (const pattern of defaultPatterns) {
379
+ const basePath = path_1.default.join(cwd, pattern.replace('/*', ''));
380
+ if (fs_1.default.existsSync(basePath)) {
381
+ existingPatterns.push(pattern);
382
+ }
383
+ }
384
+ return existingPatterns;
385
+ }
386
+ function findNextjsInWorkspace(cwd, patterns) {
387
+ const packagePaths = expandWorkspacePatterns(cwd, patterns);
388
+ const versions = [];
389
+ 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
+ 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(/^[\^~>=<]+/, ''));
399
+ }
400
+ }
401
+ catch {
402
+ // Skip invalid package.json
403
+ }
404
+ }
405
+ return findHighestVersion(versions);
406
+ }
407
+ function expandWorkspacePatterns(cwd, patterns) {
408
+ const packagePaths = [];
409
+ for (const pattern of patterns) {
410
+ if (pattern.startsWith('!'))
411
+ continue;
412
+ if (pattern.includes('*')) {
413
+ packagePaths.push(...expandGlobPattern(cwd, pattern));
414
+ }
415
+ else {
416
+ const fullPath = path_1.default.join(cwd, pattern);
417
+ if (fs_1.default.existsSync(fullPath)) {
418
+ packagePaths.push(fullPath);
419
+ }
420
+ }
421
+ }
422
+ return [...new Set(packagePaths)];
423
+ }
424
+ function expandGlobPattern(cwd, pattern) {
425
+ const parts = pattern.split('/');
426
+ const results = [];
427
+ function walk(currentPath, partIndex) {
428
+ if (partIndex >= parts.length) {
429
+ if (fs_1.default.existsSync(path_1.default.join(currentPath, 'package.json'))) {
430
+ results.push(currentPath);
431
+ }
432
+ return;
433
+ }
434
+ const part = parts[partIndex];
435
+ if (part === '*') {
436
+ if (!fs_1.default.existsSync(currentPath))
437
+ return;
438
+ try {
439
+ for (const entry of fs_1.default.readdirSync(currentPath)) {
440
+ const fullPath = path_1.default.join(currentPath, entry);
441
+ if (isDirectory(fullPath)) {
442
+ if (partIndex === parts.length - 1) {
443
+ if (fs_1.default.existsSync(path_1.default.join(fullPath, 'package.json'))) {
444
+ results.push(fullPath);
445
+ }
446
+ }
447
+ else {
448
+ walk(fullPath, partIndex + 1);
449
+ }
450
+ }
451
+ }
452
+ }
453
+ catch {
454
+ // Permission denied
455
+ }
456
+ }
457
+ else if (part === '**') {
458
+ walkRecursive(currentPath, results);
459
+ }
460
+ else {
461
+ walk(path_1.default.join(currentPath, part), partIndex + 1);
462
+ }
463
+ }
464
+ walk(cwd, 0);
465
+ return results;
466
+ }
467
+ function walkRecursive(dir, results) {
468
+ if (!fs_1.default.existsSync(dir))
469
+ return;
470
+ if (fs_1.default.existsSync(path_1.default.join(dir, 'package.json'))) {
471
+ results.push(dir);
472
+ }
473
+ try {
474
+ for (const entry of fs_1.default.readdirSync(dir)) {
475
+ if (entry === 'node_modules' || entry.startsWith('.'))
476
+ continue;
477
+ const fullPath = path_1.default.join(dir, entry);
478
+ if (isDirectory(fullPath)) {
479
+ walkRecursive(fullPath, results);
480
+ }
481
+ }
482
+ }
483
+ catch {
484
+ // Permission denied
485
+ }
486
+ }
487
+ function isDirectory(dirPath) {
488
+ try {
489
+ return fs_1.default.statSync(dirPath).isDirectory();
490
+ }
491
+ catch {
492
+ return false;
493
+ }
494
+ }
495
+ function findHighestVersion(versions) {
496
+ if (versions.length === 0)
497
+ return null;
498
+ if (versions.length === 1)
499
+ return versions[0];
500
+ return versions.reduce((highest, current) => {
501
+ return compareVersions(current, highest) > 0 ? current : highest;
502
+ });
503
+ }
504
+ function compareVersions(a, b) {
505
+ const parseVersion = (v) => {
506
+ const match = v.match(/^(\d+)\.(\d+)\.(\d+)/);
507
+ if (!match)
508
+ return [0, 0, 0];
509
+ return [parseInt(match[1]), parseInt(match[2]), parseInt(match[3])];
510
+ };
511
+ const [aMajor, aMinor, aPatch] = parseVersion(a);
512
+ const [bMajor, bMinor, bPatch] = parseVersion(b);
513
+ if (aMajor !== bMajor)
514
+ return aMajor - bMajor;
515
+ if (aMinor !== bMinor)
516
+ return aMinor - bMinor;
517
+ return aPatch - bPatch;
518
+ }
519
+ //# sourceMappingURL=agents-md.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@next/codemod",
3
- "version": "16.2.0-canary.2",
3
+ "version": "16.2.0-canary.20",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",