@next/codemod 16.3.0-canary.7 → 16.3.0-canary.70
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.
- package/bin/agents-md.js +28 -12
- package/bin/next-codemod.js +0 -0
- package/bin/upgrade.js +32 -0
- package/lib/__tests__/agents-md-e2e.test.js +117 -0
- package/lib/agents-md.js +32 -0
- package/lib/handle-package.js +44 -0
- package/lib/utils.js +5 -0
- package/package.json +10 -9
- package/transforms/cache-components-instant-false.js +129 -0
- package/transforms/next-experimental-turbo-to-turbopack.js +48 -4
package/bin/agents-md.js
CHANGED
|
@@ -59,8 +59,17 @@ async function runAgentsMd(options) {
|
|
|
59
59
|
targetFile = promptedOptions.targetFile;
|
|
60
60
|
}
|
|
61
61
|
const claudeMdPath = path_1.default.join(cwd, targetFile);
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
// Next.js >= 16.2.0 ships its docs inside the published package. When the
|
|
63
|
+
// installed version matches the requested one, index the bundled docs
|
|
64
|
+
// directly instead of downloading a copy into .next-docs.
|
|
65
|
+
const bundledDocs = (0, agents_md_1.getBundledDocsInfo)(cwd);
|
|
66
|
+
const useBundledDocs = bundledDocs !== null && bundledDocs.version === nextjsVersion;
|
|
67
|
+
const docsPath = useBundledDocs
|
|
68
|
+
? bundledDocs.docsPath
|
|
69
|
+
: path_1.default.join(cwd, DOCS_DIR_NAME);
|
|
70
|
+
const docsLinkPath = useBundledDocs
|
|
71
|
+
? (0, agents_md_1.getBundledDocsLinkPath)(cwd, bundledDocs.docsPath)
|
|
72
|
+
: `./${DOCS_DIR_NAME}`;
|
|
64
73
|
let sizeBefore = 0;
|
|
65
74
|
let isNewFile = true;
|
|
66
75
|
let existingContent = '';
|
|
@@ -69,14 +78,19 @@ async function runAgentsMd(options) {
|
|
|
69
78
|
sizeBefore = Buffer.byteLength(existingContent, 'utf-8');
|
|
70
79
|
isNewFile = false;
|
|
71
80
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
81
|
+
if (useBundledDocs) {
|
|
82
|
+
console.log(`\nUsing the docs bundled with Next.js ${picocolors_1.default.cyan(nextjsVersion)} at ${picocolors_1.default.cyan(docsLinkPath)} (no download needed).`);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
console.log(`\nDownloading Next.js ${picocolors_1.default.cyan(nextjsVersion)} documentation to ${picocolors_1.default.cyan(DOCS_DIR_NAME)}...`);
|
|
86
|
+
const pullResult = await (0, agents_md_1.pullDocs)({
|
|
87
|
+
cwd,
|
|
88
|
+
version: nextjsVersion,
|
|
89
|
+
docsDir: docsPath,
|
|
90
|
+
});
|
|
91
|
+
if (!pullResult.success) {
|
|
92
|
+
throw new shared_1.BadInput(`Failed to pull docs: ${pullResult.error}`);
|
|
93
|
+
}
|
|
80
94
|
}
|
|
81
95
|
const docFiles = (0, agents_md_1.collectDocFiles)(docsPath);
|
|
82
96
|
const sections = (0, agents_md_1.buildDocTree)(docFiles);
|
|
@@ -88,13 +102,15 @@ async function runAgentsMd(options) {
|
|
|
88
102
|
const newContent = (0, agents_md_1.injectIntoClaudeMd)(existingContent, indexContent);
|
|
89
103
|
fs_1.default.writeFileSync(claudeMdPath, newContent, 'utf-8');
|
|
90
104
|
const sizeAfter = Buffer.byteLength(newContent, 'utf-8');
|
|
91
|
-
|
|
105
|
+
// .next-docs only exists on the download path; bundled docs live in
|
|
106
|
+
// node_modules, which is already ignored.
|
|
107
|
+
const gitignoreResult = useBundledDocs ? null : (0, agents_md_1.ensureGitignoreEntry)(cwd);
|
|
92
108
|
const action = isNewFile ? 'Created' : 'Updated';
|
|
93
109
|
const sizeInfo = isNewFile
|
|
94
110
|
? formatSize(sizeAfter)
|
|
95
111
|
: `${formatSize(sizeBefore)} → ${formatSize(sizeAfter)}`;
|
|
96
112
|
console.log(`${picocolors_1.default.green('✓')} ${action} ${picocolors_1.default.bold(targetFile)} (${sizeInfo})`);
|
|
97
|
-
if (gitignoreResult
|
|
113
|
+
if (gitignoreResult?.updated) {
|
|
98
114
|
console.log(`${picocolors_1.default.green('✓')} Added ${picocolors_1.default.bold(DOCS_DIR_NAME)} to .gitignore`);
|
|
99
115
|
}
|
|
100
116
|
console.log('');
|
package/bin/next-codemod.js
CHANGED
|
File without changes
|
package/bin/upgrade.js
CHANGED
|
@@ -500,6 +500,16 @@ function writeOverridesField(packageJson, packageManager, overrides) {
|
|
|
500
500
|
}
|
|
501
501
|
}
|
|
502
502
|
else if (packageManager === 'pnpm') {
|
|
503
|
+
// pnpm v11 silently ignores `pnpm.overrides` in package.json. The
|
|
504
|
+
// canonical location moved to `pnpm-workspace.yaml#overrides`.
|
|
505
|
+
// See https://pnpm.io/settings and https://github.com/pnpm/pnpm/issues/11536.
|
|
506
|
+
// When the version cannot be detected, assume the current (v11+) layout
|
|
507
|
+
// since that's the surface where silently-dropped overrides hurt most.
|
|
508
|
+
const pnpmMajorVersion = (0, handle_package_1.getPnpmMajorVersion)();
|
|
509
|
+
if (pnpmMajorVersion === null || pnpmMajorVersion >= 11) {
|
|
510
|
+
writePnpmWorkspaceOverrides(overrides);
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
503
513
|
// pnpm supports pnpm.overrides and pnpm.resolutions
|
|
504
514
|
if (packageJson.resolutions) {
|
|
505
515
|
for (const [key, value] of entries) {
|
|
@@ -545,6 +555,28 @@ function writeOverridesField(packageJson, packageManager, overrides) {
|
|
|
545
555
|
}
|
|
546
556
|
}
|
|
547
557
|
}
|
|
558
|
+
function writePnpmWorkspaceOverrides(overrides) {
|
|
559
|
+
// Deferred require so `js-yaml` is only loaded when we hit the pnpm v11+
|
|
560
|
+
// branch (i.e. not for npm/yarn/bun/pnpm-v10 upgrades). The package is CJS,
|
|
561
|
+
// so a sync `require()` keeps this function synchronous.
|
|
562
|
+
const yaml = require('js-yaml');
|
|
563
|
+
const filePath = path_1.default.join(cwd, 'pnpm-workspace.yaml');
|
|
564
|
+
let doc = {};
|
|
565
|
+
if (fs_1.default.existsSync(filePath)) {
|
|
566
|
+
const existing = fs_1.default.readFileSync(filePath, 'utf8');
|
|
567
|
+
const parsed = yaml.load(existing);
|
|
568
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
569
|
+
doc = parsed;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
if (!doc.overrides || typeof doc.overrides !== 'object') {
|
|
573
|
+
doc.overrides = {};
|
|
574
|
+
}
|
|
575
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
576
|
+
doc.overrides[key] = value;
|
|
577
|
+
}
|
|
578
|
+
fs_1.default.writeFileSync(filePath, yaml.dump(doc));
|
|
579
|
+
}
|
|
548
580
|
function warnDependenciesOutOfRange(appPackageJson, versionMapping) {
|
|
549
581
|
const allDirectDependencies = {
|
|
550
582
|
...appPackageJson.dependencies,
|
|
@@ -293,6 +293,123 @@ This is my project documentation.
|
|
|
293
293
|
}
|
|
294
294
|
}, 30000) // Increase timeout for git clone
|
|
295
295
|
|
|
296
|
+
describe('bundled docs (Next.js >= 16.2.0)', () => {
|
|
297
|
+
// Simulate an install of a Next.js version that ships docs inside the
|
|
298
|
+
// published package at node_modules/next/dist/docs.
|
|
299
|
+
function setupBundledNext(projectDir, version) {
|
|
300
|
+
const nextDir = path.join(projectDir, 'node_modules', 'next')
|
|
301
|
+
const gettingStartedDir = path.join(
|
|
302
|
+
nextDir,
|
|
303
|
+
'dist',
|
|
304
|
+
'docs',
|
|
305
|
+
'01-app',
|
|
306
|
+
'01-getting-started'
|
|
307
|
+
)
|
|
308
|
+
fs.mkdirSync(gettingStartedDir, { recursive: true })
|
|
309
|
+
fs.writeFileSync(
|
|
310
|
+
path.join(nextDir, 'package.json'),
|
|
311
|
+
JSON.stringify({ name: 'next', version })
|
|
312
|
+
)
|
|
313
|
+
fs.writeFileSync(
|
|
314
|
+
path.join(nextDir, 'dist', 'docs', 'index.md'),
|
|
315
|
+
'# Next.js Docs'
|
|
316
|
+
)
|
|
317
|
+
fs.writeFileSync(
|
|
318
|
+
path.join(gettingStartedDir, '01-installation.md'),
|
|
319
|
+
'# Installation'
|
|
320
|
+
)
|
|
321
|
+
fs.writeFileSync(
|
|
322
|
+
path.join(gettingStartedDir, '02-project-structure.md'),
|
|
323
|
+
'# Project Structure'
|
|
324
|
+
)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
it('indexes bundled docs instead of downloading when installed Next.js ships them', async () => {
|
|
328
|
+
setupBundledNext(testProjectDir, '16.2.0')
|
|
329
|
+
|
|
330
|
+
const originalCwd = process.cwd()
|
|
331
|
+
process.chdir(testProjectDir)
|
|
332
|
+
|
|
333
|
+
try {
|
|
334
|
+
await runAgentsMd({ output: 'CLAUDE.md' })
|
|
335
|
+
|
|
336
|
+
// No .next-docs copy and no .gitignore entry for it
|
|
337
|
+
expect(fs.existsSync(path.join(testProjectDir, '.next-docs'))).toBe(
|
|
338
|
+
false
|
|
339
|
+
)
|
|
340
|
+
expect(fs.existsSync(path.join(testProjectDir, '.gitignore'))).toBe(
|
|
341
|
+
false
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
const claudeMdContent = fs.readFileSync(
|
|
345
|
+
path.join(testProjectDir, 'CLAUDE.md'),
|
|
346
|
+
'utf-8'
|
|
347
|
+
)
|
|
348
|
+
expect(claudeMdContent).toContain(
|
|
349
|
+
'root: ./node_modules/next/dist/docs'
|
|
350
|
+
)
|
|
351
|
+
expect(claudeMdContent).toContain('01-installation.md')
|
|
352
|
+
|
|
353
|
+
const output = consoleOutput.join('\n')
|
|
354
|
+
expect(output).toContain('bundled with Next.js')
|
|
355
|
+
expect(output).not.toContain('Downloading')
|
|
356
|
+
} finally {
|
|
357
|
+
process.chdir(originalCwd)
|
|
358
|
+
}
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
it('uses bundled docs when --version matches the installed version', async () => {
|
|
362
|
+
setupBundledNext(testProjectDir, '16.2.0')
|
|
363
|
+
|
|
364
|
+
const originalCwd = process.cwd()
|
|
365
|
+
process.chdir(testProjectDir)
|
|
366
|
+
|
|
367
|
+
try {
|
|
368
|
+
await runAgentsMd({ version: '16.2.0', output: 'AGENTS.md' })
|
|
369
|
+
|
|
370
|
+
expect(fs.existsSync(path.join(testProjectDir, '.next-docs'))).toBe(
|
|
371
|
+
false
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
const agentsMdContent = fs.readFileSync(
|
|
375
|
+
path.join(testProjectDir, 'AGENTS.md'),
|
|
376
|
+
'utf-8'
|
|
377
|
+
)
|
|
378
|
+
expect(agentsMdContent).toContain(
|
|
379
|
+
'root: ./node_modules/next/dist/docs'
|
|
380
|
+
)
|
|
381
|
+
} finally {
|
|
382
|
+
process.chdir(originalCwd)
|
|
383
|
+
}
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
it('falls back to downloading when --version differs from the installed version', async () => {
|
|
387
|
+
setupBundledNext(testProjectDir, '16.2.0')
|
|
388
|
+
|
|
389
|
+
const originalCwd = process.cwd()
|
|
390
|
+
process.chdir(testProjectDir)
|
|
391
|
+
|
|
392
|
+
try {
|
|
393
|
+
await runAgentsMd({ version: '15.0.0', output: 'CLAUDE.md' })
|
|
394
|
+
|
|
395
|
+
expect(fs.existsSync(path.join(testProjectDir, '.next-docs'))).toBe(
|
|
396
|
+
true
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
const claudeMdContent = fs.readFileSync(
|
|
400
|
+
path.join(testProjectDir, 'CLAUDE.md'),
|
|
401
|
+
'utf-8'
|
|
402
|
+
)
|
|
403
|
+
expect(claudeMdContent).toContain('root: ./.next-docs')
|
|
404
|
+
|
|
405
|
+
const output = consoleOutput.join('\n')
|
|
406
|
+
expect(output).toContain('Downloading')
|
|
407
|
+
} finally {
|
|
408
|
+
process.chdir(originalCwd)
|
|
409
|
+
}
|
|
410
|
+
}, 30000) // Increase timeout for git clone
|
|
411
|
+
})
|
|
412
|
+
|
|
296
413
|
describe('getNextjsVersion', () => {
|
|
297
414
|
const fixturesDir = path.join(__dirname, 'fixtures/agents-md')
|
|
298
415
|
|
package/lib/agents-md.js
CHANGED
|
@@ -10,6 +10,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.getNextjsVersion = getNextjsVersion;
|
|
13
|
+
exports.getBundledDocsInfo = getBundledDocsInfo;
|
|
14
|
+
exports.getBundledDocsLinkPath = getBundledDocsLinkPath;
|
|
13
15
|
exports.pullDocs = pullDocs;
|
|
14
16
|
exports.collectDocFiles = collectDocFiles;
|
|
15
17
|
exports.buildDocTree = buildDocTree;
|
|
@@ -45,6 +47,36 @@ function getNextjsVersion(cwd) {
|
|
|
45
47
|
};
|
|
46
48
|
}
|
|
47
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Next.js ships its documentation inside the published package (at
|
|
52
|
+
* `dist/docs`) since 16.2.0. When the install resolved from `cwd` has
|
|
53
|
+
* bundled docs, the index can point at them directly instead of
|
|
54
|
+
* downloading a copy into `.next-docs`.
|
|
55
|
+
*/
|
|
56
|
+
function getBundledDocsInfo(cwd) {
|
|
57
|
+
try {
|
|
58
|
+
const nextPkgPath = require.resolve('next/package.json', { paths: [cwd] });
|
|
59
|
+
const pkg = JSON.parse(fs_1.default.readFileSync(nextPkgPath, 'utf-8'));
|
|
60
|
+
const docsPath = path_1.default.join(path_1.default.dirname(nextPkgPath), 'dist', 'docs');
|
|
61
|
+
if (!pkg.version || collectDocFiles(docsPath).length === 0) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
return { docsPath, version: pkg.version };
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function getBundledDocsLinkPath(cwd, docsPath) {
|
|
71
|
+
// Prefer the conventional path when it resolves from the project
|
|
72
|
+
// (covers hoisted installs; pnpm exposes next via a node_modules symlink).
|
|
73
|
+
const conventional = path_1.default.join(cwd, 'node_modules', 'next', 'dist', 'docs');
|
|
74
|
+
if (fs_1.default.existsSync(conventional)) {
|
|
75
|
+
return './node_modules/next/dist/docs';
|
|
76
|
+
}
|
|
77
|
+
const relative = path_1.default.relative(cwd, docsPath).replace(/\\/g, '/');
|
|
78
|
+
return relative.startsWith('.') ? relative : `./${relative}`;
|
|
79
|
+
}
|
|
48
80
|
function versionToGitHubTag(version) {
|
|
49
81
|
return version.startsWith('v') ? version : `v${version}`;
|
|
50
82
|
}
|
package/lib/handle-package.js
CHANGED
|
@@ -3,6 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getPackageManagerVersion = getPackageManagerVersion;
|
|
7
|
+
exports.getPnpmMajorVersion = getPnpmMajorVersion;
|
|
6
8
|
exports.getPkgManager = getPkgManager;
|
|
7
9
|
exports.uninstallPackage = uninstallPackage;
|
|
8
10
|
exports.installPackages = installPackages;
|
|
@@ -10,7 +12,49 @@ exports.runInstallation = runInstallation;
|
|
|
10
12
|
exports.addPackageDependency = addPackageDependency;
|
|
11
13
|
const find_up_1 = __importDefault(require("find-up"));
|
|
12
14
|
const execa_1 = __importDefault(require("execa"));
|
|
15
|
+
const node_child_process_1 = require("node:child_process");
|
|
13
16
|
const node_path_1 = require("node:path");
|
|
17
|
+
/**
|
|
18
|
+
* Get the full version string for the given package manager.
|
|
19
|
+
*
|
|
20
|
+
* First tries to parse from `npm_config_user_agent` (e.g., "pnpm/9.13.2 npm/? ..."),
|
|
21
|
+
* then falls back to spawning `<packageManager> --version`.
|
|
22
|
+
*
|
|
23
|
+
* Returns null if unable to determine the version.
|
|
24
|
+
*
|
|
25
|
+
* Mirrors `packages/create-next-app/helpers/get-pkg-manager.ts`.
|
|
26
|
+
*/
|
|
27
|
+
function getPackageManagerVersion(packageManager) {
|
|
28
|
+
const userAgent = process.env.npm_config_user_agent || '';
|
|
29
|
+
const userAgentMatch = userAgent.match(new RegExp(`${packageManager}/([\\d.]+[\\w.-]*)`));
|
|
30
|
+
if (userAgentMatch) {
|
|
31
|
+
return userAgentMatch[1];
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const version = (0, node_child_process_1.execSync)(`${packageManager} --version`, {
|
|
35
|
+
encoding: 'utf8',
|
|
36
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
37
|
+
}).trim();
|
|
38
|
+
if (/^\d+\.\d+\.\d+/.test(version)) {
|
|
39
|
+
return version;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// package manager not available or failed to run
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get the major version of pnpm being used.
|
|
49
|
+
* Returns null if unable to determine the version.
|
|
50
|
+
*/
|
|
51
|
+
function getPnpmMajorVersion() {
|
|
52
|
+
const version = getPackageManagerVersion('pnpm');
|
|
53
|
+
if (!version)
|
|
54
|
+
return null;
|
|
55
|
+
const major = parseInt(version.split('.')[0], 10);
|
|
56
|
+
return Number.isNaN(major) ? null : major;
|
|
57
|
+
}
|
|
14
58
|
function getPkgManager(baseDir) {
|
|
15
59
|
try {
|
|
16
60
|
const lockFile = find_up_1.default.sync([
|
package/lib/utils.js
CHANGED
|
@@ -132,5 +132,10 @@ exports.TRANSFORMER_INQUIRER_CHOICES = [
|
|
|
132
132
|
value: 'remove-experimental-ppr',
|
|
133
133
|
version: '16.0.0-canary.11',
|
|
134
134
|
},
|
|
135
|
+
{
|
|
136
|
+
title: 'Add `export const instant = false` to App Router pages and layouts to ease Cache Components adoption',
|
|
137
|
+
value: 'cache-components-instant-false',
|
|
138
|
+
version: '16.3.0',
|
|
139
|
+
},
|
|
135
140
|
];
|
|
136
141
|
//# sourceMappingURL=utils.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@next/codemod",
|
|
3
|
-
"version": "16.3.0-canary.
|
|
3
|
+
"version": "16.3.0-canary.70",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"find-up": "4.1.0",
|
|
15
15
|
"globby": "11.0.1",
|
|
16
16
|
"is-git-clean": "1.1.0",
|
|
17
|
+
"js-yaml": "4.2.0",
|
|
17
18
|
"jscodeshift": "17.0.0",
|
|
18
19
|
"picocolors": "1.0.0",
|
|
19
20
|
"prompts": "2.4.2",
|
|
@@ -27,19 +28,19 @@
|
|
|
27
28
|
"lib/**/*.js",
|
|
28
29
|
"lib/cra-to-next/gitignore"
|
|
29
30
|
],
|
|
30
|
-
"scripts": {
|
|
31
|
-
"build": "pnpm tsc -d -p tsconfig.json",
|
|
32
|
-
"prepublishOnly": "cd ../../ && turbo run build",
|
|
33
|
-
"dev": "pnpm tsc -d -w -p tsconfig.json",
|
|
34
|
-
"test": "jest",
|
|
35
|
-
"test:upgrade-fixture": "./scripts/test-upgrade-fixture.sh"
|
|
36
|
-
},
|
|
37
31
|
"bin": "./bin/next-codemod.js",
|
|
38
32
|
"devDependencies": {
|
|
39
33
|
"@types/find-up": "4.0.0",
|
|
34
|
+
"@types/js-yaml": "4.0.9",
|
|
40
35
|
"@types/jscodeshift": "0.11.0",
|
|
41
36
|
"@types/prompts": "2.4.2",
|
|
42
37
|
"@types/semver": "7.3.1",
|
|
43
38
|
"typescript": "6.0.2"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "pnpm tsc -d -p tsconfig.json",
|
|
42
|
+
"dev": "pnpm tsc -d -w -p tsconfig.json",
|
|
43
|
+
"test": "jest",
|
|
44
|
+
"test:upgrade-fixture": "./scripts/test-upgrade-fixture.sh"
|
|
44
45
|
}
|
|
45
|
-
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = transformer;
|
|
4
|
+
const parser_1 = require("../lib/parser");
|
|
5
|
+
/**
|
|
6
|
+
* Blanket-inserts `export const instant = false` into every App Router `page`,
|
|
7
|
+
* `layout`, and `default` file so they're marked as allowed to block when
|
|
8
|
+
* `cacheComponents` is enabled. Each opt-out is meant to be walked back, one
|
|
9
|
+
* route at a time, using the companion adoption skill.
|
|
10
|
+
*
|
|
11
|
+
* - Skips files that already declare or export `instant` in any form (never
|
|
12
|
+
* overrides existing config or appends a duplicate binding).
|
|
13
|
+
* - Skips Client/Server Component modules (`"use client"` / `"use server"`):
|
|
14
|
+
* `instant` is a Server Component route segment config, so exporting it from
|
|
15
|
+
* those modules is a build error.
|
|
16
|
+
* - Targets `page` / `layout` / `default` only (not `route` — `instant` does
|
|
17
|
+
* not apply to route handlers). `default.tsx` is the parallel-route fallback,
|
|
18
|
+
* a server segment that accepts route segment config like the other two.
|
|
19
|
+
*/
|
|
20
|
+
function transformer(file, _api) {
|
|
21
|
+
if (process.env.NODE_ENV !== 'test' &&
|
|
22
|
+
!/(^|[/\\])app[/\\].*?(page|layout|default)\.[^/\\]+$/.test(file.path)) {
|
|
23
|
+
return file.source;
|
|
24
|
+
}
|
|
25
|
+
const j = (0, parser_1.createParserFromPath)(file.path);
|
|
26
|
+
const root = j(file.source);
|
|
27
|
+
// Bail on Client/Server Component modules. `instant` is a Server Component
|
|
28
|
+
// route segment config; exporting it from a `"use client"` (or `"use server"`)
|
|
29
|
+
// module fails the build. Parsers represent the directive either in
|
|
30
|
+
// `program.directives` or as a leading string-literal `ExpressionStatement`.
|
|
31
|
+
const program = root.get().node.program;
|
|
32
|
+
const isClientOrServerDirective = (value) => value === 'use client' || value === 'use server';
|
|
33
|
+
let hasModuleDirective = (program.directives ?? []).some((d) => isClientOrServerDirective(d?.value?.value));
|
|
34
|
+
if (!hasModuleDirective) {
|
|
35
|
+
for (const node of program.body) {
|
|
36
|
+
if (node.type !== 'ExpressionStatement' ||
|
|
37
|
+
(node.expression?.type !== 'StringLiteral' &&
|
|
38
|
+
node.expression?.type !== 'Literal')) {
|
|
39
|
+
// Directives must lead the module; stop at the first non-directive.
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
if (isClientOrServerDirective(node.expression.value)) {
|
|
43
|
+
hasModuleDirective = true;
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (hasModuleDirective) {
|
|
49
|
+
return file.source;
|
|
50
|
+
}
|
|
51
|
+
// Bail if `instant` already exists in any form, so we never append a
|
|
52
|
+
// duplicate declaration (which would be a `SyntaxError`). This covers:
|
|
53
|
+
// export const instant = ...
|
|
54
|
+
// export const a = 1, instant = ... (any declarator position)
|
|
55
|
+
// const instant = ... (local binding)
|
|
56
|
+
// const { instant } = ... (destructured binding)
|
|
57
|
+
// export { instant }
|
|
58
|
+
// export { foo as instant }
|
|
59
|
+
// export function instant() {} / export class instant {}
|
|
60
|
+
const bindsInstant = (node) => {
|
|
61
|
+
switch (node?.type) {
|
|
62
|
+
case 'Identifier':
|
|
63
|
+
return node.name === 'instant';
|
|
64
|
+
case 'ObjectPattern':
|
|
65
|
+
return node.properties.some((prop) => prop.type === 'RestElement'
|
|
66
|
+
? bindsInstant(prop.argument)
|
|
67
|
+
: bindsInstant(prop.value ?? prop.argument));
|
|
68
|
+
case 'ArrayPattern':
|
|
69
|
+
return node.elements.some((el) => el != null && bindsInstant(el));
|
|
70
|
+
case 'AssignmentPattern':
|
|
71
|
+
return bindsInstant(node.left);
|
|
72
|
+
case 'RestElement':
|
|
73
|
+
return bindsInstant(node.argument);
|
|
74
|
+
default:
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
const hasInstantBinding = root
|
|
79
|
+
.find(j.VariableDeclarator)
|
|
80
|
+
.filter((p) => bindsInstant(p.node.id))
|
|
81
|
+
.size() > 0 ||
|
|
82
|
+
root.find(j.ExportSpecifier, { exported: { name: 'instant' } }).size() >
|
|
83
|
+
0 ||
|
|
84
|
+
root.find(j.FunctionDeclaration, { id: { name: 'instant' } }).size() > 0 ||
|
|
85
|
+
root.find(j.ClassDeclaration, { id: { name: 'instant' } }).size() > 0;
|
|
86
|
+
if (hasInstantBinding) {
|
|
87
|
+
return file.source;
|
|
88
|
+
}
|
|
89
|
+
// Build `export const instant = false`. The two `//` comments above it
|
|
90
|
+
// (TODO + See:) are attached as leading comments on the declaration so
|
|
91
|
+
// recast prints them right above it.
|
|
92
|
+
const todoComment = j.commentLine(' TODO: Cache Components adoption. Refactor this route so this opt-out can be removed.', true, false);
|
|
93
|
+
const seeComment = j.commentLine(' See: https://nextjs.org/docs/app/guides/migrating-to-cache-components', true, false);
|
|
94
|
+
const instantExport = j.exportNamedDeclaration(j.variableDeclaration('const', [
|
|
95
|
+
j.variableDeclarator(j.identifier('instant'), j.booleanLiteral(false)),
|
|
96
|
+
]));
|
|
97
|
+
instantExport.comments = [todoComment, seeComment];
|
|
98
|
+
// Insert after the last top-level import, or at the top of the module
|
|
99
|
+
// if there are no imports.
|
|
100
|
+
const body = program.body;
|
|
101
|
+
let lastImportIndex = -1;
|
|
102
|
+
for (let i = 0; i < body.length; i++) {
|
|
103
|
+
if (body[i].type === 'ImportDeclaration')
|
|
104
|
+
lastImportIndex = i;
|
|
105
|
+
}
|
|
106
|
+
if (lastImportIndex !== -1) {
|
|
107
|
+
body.splice(lastImportIndex + 1, 0, instantExport);
|
|
108
|
+
}
|
|
109
|
+
else if (body.length > 0) {
|
|
110
|
+
// No imports. Inserting at index 0 would steal any file-level leading
|
|
111
|
+
// comments (e.g. `// @ts-nocheck`) from `body[0]` because recast
|
|
112
|
+
// attributes them to whatever is first. Move those leading comments
|
|
113
|
+
// off `body[0]` onto the new export *before* its TODO/See: lines, so
|
|
114
|
+
// they print in their original position.
|
|
115
|
+
const first = body[0];
|
|
116
|
+
const allComments = (first.comments ?? []);
|
|
117
|
+
const firstLeading = allComments.filter((c) => c.leading === true);
|
|
118
|
+
if (firstLeading.length > 0) {
|
|
119
|
+
first.comments = allComments.filter((c) => c.leading !== true);
|
|
120
|
+
instantExport.comments = [...firstLeading, todoComment, seeComment];
|
|
121
|
+
}
|
|
122
|
+
body.unshift(instantExport);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
body.push(instantExport);
|
|
126
|
+
}
|
|
127
|
+
return root.toSource();
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=cache-components-instant-false.js.map
|
|
@@ -4,8 +4,12 @@
|
|
|
4
4
|
* the new top-level `turbopack` configuration.
|
|
5
5
|
*
|
|
6
6
|
* It moves most properties from experimental.turbo to the top-level turbopack
|
|
7
|
-
* property, with special handling for certain properties like
|
|
7
|
+
* property, with special handling for certain properties like minify,
|
|
8
8
|
* treeShaking, and sourceMaps which become experimental.turbopack* properties instead.
|
|
9
|
+
*
|
|
10
|
+
* The `memoryLimit` option is no longer supported, so it is removed entirely in
|
|
11
|
+
* both its old location (`experimental.turbo.memoryLimit`) and the location the
|
|
12
|
+
* previous version of this codemod produced (`experimental.turbopackMemoryLimit`).
|
|
9
13
|
*/
|
|
10
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
15
|
exports.default = transformer;
|
|
@@ -13,11 +17,15 @@ const parser_1 = require("../lib/parser");
|
|
|
13
17
|
const utils_1 = require("./lib/utils");
|
|
14
18
|
// Properties that need to be moved to experimental.turbopack*
|
|
15
19
|
const RENAMED_EXPERIMENTAL_PROPERTIES = {
|
|
16
|
-
memoryLimit: 'turbopackMemoryLimit',
|
|
17
20
|
minify: 'turbopackMinify',
|
|
18
21
|
treeShaking: 'turbopackTreeShaking',
|
|
19
22
|
sourceMaps: 'turbopackSourceMaps',
|
|
20
23
|
};
|
|
24
|
+
// `memoryLimit` is no longer supported and is removed entirely. We drop it under
|
|
25
|
+
// both its old `experimental.turbo.*` name and the `experimental.*` name produced
|
|
26
|
+
// by the previous version of this codemod.
|
|
27
|
+
const REMOVED_TURBO_PROPERTY = 'memoryLimit';
|
|
28
|
+
const REMOVED_EXPERIMENTAL_PROPERTY = 'turbopackMemoryLimit';
|
|
21
29
|
function transformer(file, _api, options) {
|
|
22
30
|
const j = (0, parser_1.createParserFromPath)(file.path);
|
|
23
31
|
const root = j(file.source);
|
|
@@ -56,6 +64,13 @@ function transformer(file, _api, options) {
|
|
|
56
64
|
const regularProps = [];
|
|
57
65
|
const specialProps = [];
|
|
58
66
|
turboObj.properties.forEach((prop) => {
|
|
67
|
+
if (isStaticProperty(prop) &&
|
|
68
|
+
prop.key &&
|
|
69
|
+
prop.key.type === 'Identifier' &&
|
|
70
|
+
prop.key.name === REMOVED_TURBO_PROPERTY) {
|
|
71
|
+
// Drop: `memoryLimit` is no longer supported.
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
59
74
|
if (isStaticProperty(prop) &&
|
|
60
75
|
prop.key &&
|
|
61
76
|
prop.key.type === 'Identifier' &&
|
|
@@ -71,7 +86,10 @@ function transformer(file, _api, options) {
|
|
|
71
86
|
const existingProps = experimentalObj.properties.filter((prop) => !(isStaticProperty(prop) &&
|
|
72
87
|
prop.key &&
|
|
73
88
|
prop.key.type === 'Identifier' &&
|
|
74
|
-
|
|
89
|
+
// Drop the `turbo` object (moved above) and a pre-existing
|
|
90
|
+
// `turbopackMemoryLimit` (no longer supported).
|
|
91
|
+
(prop.key.name === 'turbo' ||
|
|
92
|
+
prop.key.name === REMOVED_EXPERIMENTAL_PROPERTY)));
|
|
75
93
|
experimentalObj.properties = [...existingProps, ...specialProps];
|
|
76
94
|
// If experimental has no properties, remove it
|
|
77
95
|
if (experimentalObj.properties.length === 0) {
|
|
@@ -132,7 +150,13 @@ function transformer(file, _api, options) {
|
|
|
132
150
|
else {
|
|
133
151
|
return;
|
|
134
152
|
}
|
|
135
|
-
//
|
|
153
|
+
// `memoryLimit` is no longer supported: remove the assignment entirely.
|
|
154
|
+
if (propName === REMOVED_TURBO_PROPERTY) {
|
|
155
|
+
j(path).remove();
|
|
156
|
+
hasChanges = true;
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
// For special properties like minify, treeShaking, etc.
|
|
136
160
|
if (propName && RENAMED_EXPERIMENTAL_PROPERTIES[propName]) {
|
|
137
161
|
const newAssignment = j.assignmentExpression('=', j.memberExpression(j.memberExpression(j.identifier(varName), j.identifier('experimental')), j.identifier(RENAMED_EXPERIMENTAL_PROPERTIES[propName])), path.node.right);
|
|
138
162
|
j(path).replaceWith(newAssignment);
|
|
@@ -195,6 +219,26 @@ function transformer(file, _api, options) {
|
|
|
195
219
|
hasChanges = true;
|
|
196
220
|
}
|
|
197
221
|
});
|
|
222
|
+
// Remove `config.experimental.turbopackMemoryLimit = value` assignments left
|
|
223
|
+
// behind by the previous version of this codemod (no longer supported).
|
|
224
|
+
root
|
|
225
|
+
.find(j.AssignmentExpression, {
|
|
226
|
+
left: {
|
|
227
|
+
type: 'MemberExpression',
|
|
228
|
+
object: {
|
|
229
|
+
type: 'MemberExpression',
|
|
230
|
+
property: { type: 'Identifier', name: 'experimental' },
|
|
231
|
+
},
|
|
232
|
+
property: {
|
|
233
|
+
type: 'Identifier',
|
|
234
|
+
name: REMOVED_EXPERIMENTAL_PROPERTY,
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
})
|
|
238
|
+
.forEach((path) => {
|
|
239
|
+
j(path).remove();
|
|
240
|
+
hasChanges = true;
|
|
241
|
+
});
|
|
198
242
|
// Only return a string if we changed the AST, otherwise return the original source
|
|
199
243
|
return hasChanges ? root.toSource(options) : file.source;
|
|
200
244
|
}
|