import { promises as fs } from "fs"; import chalk from "next/dist/compiled/chalk"; import * as CommentJson from "next/dist/compiled/comment-json"; import semver from "next/dist/compiled/semver"; import os from "os"; import { getTypeScriptConfiguration } from "./getTypeScriptConfiguration"; import * as Log from "../../build/output/log"; function getDesiredCompilerOptions(ts, userTsConfig) { var _userTsConfig_compilerOptions; const o = { // These are suggested values and will be set when not present in the // tsconfig.json lib: { suggested: [ "dom", "dom.iterable", "esnext" ] }, allowJs: { suggested: true }, skipLibCheck: { suggested: true }, strict: { suggested: false }, ...semver.lt(ts.version, "5.0.0") ? { forceConsistentCasingInFileNames: { suggested: true } } : undefined, noEmit: { suggested: true }, ...semver.gte(ts.version, "4.4.2") ? { incremental: { suggested: true } } : undefined, // These values are required and cannot be changed by the user // Keep this in sync with the webpack config // 'parsedValue' matches the output value from ts.parseJsonConfigFileContent() esModuleInterop: { value: true, reason: "requirement for SWC / babel" }, module: { parsedValue: ts.ModuleKind.ESNext, // All of these values work: parsedValues: [ ts.ModuleKind.ES2020, ts.ModuleKind.ESNext, ts.ModuleKind.CommonJS, ts.ModuleKind.AMD, ts.ModuleKind.NodeNext, ts.ModuleKind.Node16 ], value: "esnext", reason: "for dynamic import() support" }, moduleResolution: { // In TypeScript 5.0, `NodeJs` has renamed to `Node10` parsedValue: ts.ModuleResolutionKind.Bundler ?? ts.ModuleResolutionKind.NodeNext ?? ts.ModuleResolutionKind.Node10 ?? ts.ModuleResolutionKind.NodeJs, // All of these values work: parsedValues: [ ts.ModuleResolutionKind.Node10 ?? ts.ModuleResolutionKind.NodeJs, // only newer TypeScript versions have this field, it // will be filtered for new versions of TypeScript ts.ModuleResolutionKind.Node12, ts.ModuleResolutionKind.Node16, ts.ModuleResolutionKind.NodeNext, ts.ModuleResolutionKind.Bundler ].filter((val)=>typeof val !== "undefined"), value: "node", reason: "to match webpack resolution" }, resolveJsonModule: { value: true, reason: "to match webpack resolution" }, ...(userTsConfig == null ? void 0 : (_userTsConfig_compilerOptions = userTsConfig.compilerOptions) == null ? void 0 : _userTsConfig_compilerOptions.verbatimModuleSyntax) === true ? undefined : { isolatedModules: { value: true, reason: "requirement for SWC / Babel" } }, jsx: { parsedValue: ts.JsxEmit.Preserve, value: "preserve", reason: "next.js implements its own optimized jsx transform" } }; return o; } export function getRequiredConfiguration(ts) { const res = {}; const desiredCompilerOptions = getDesiredCompilerOptions(ts); for (const optionKey of Object.keys(desiredCompilerOptions)){ const ev = desiredCompilerOptions[optionKey]; if (!("value" in ev)) { continue; } res[optionKey] = ev.parsedValue ?? ev.value; } return res; } export async function writeConfigurationDefaults(ts, tsConfigPath, isFirstTimeSetup, isAppDirEnabled, distDir, hasPagesDir) { if (isFirstTimeSetup) { await fs.writeFile(tsConfigPath, "{}" + os.EOL); } const { options: tsOptions, raw: rawConfig } = await getTypeScriptConfiguration(ts, tsConfigPath, true); const userTsConfigContent = await fs.readFile(tsConfigPath, { encoding: "utf8" }); const userTsConfig = CommentJson.parse(userTsConfigContent); if (userTsConfig.compilerOptions == null && !("extends" in rawConfig)) { userTsConfig.compilerOptions = {}; isFirstTimeSetup = true; } const desiredCompilerOptions = getDesiredCompilerOptions(ts, userTsConfig); const suggestedActions = []; const requiredActions = []; for (const optionKey of Object.keys(desiredCompilerOptions)){ const check = desiredCompilerOptions[optionKey]; if ("suggested" in check) { if (!(optionKey in tsOptions)) { if (!userTsConfig.compilerOptions) { userTsConfig.compilerOptions = {}; } userTsConfig.compilerOptions[optionKey] = check.suggested; suggestedActions.push(chalk.cyan(optionKey) + " was set to " + chalk.bold(check.suggested)); } } else if ("value" in check) { var _check_parsedValues; const ev = tsOptions[optionKey]; if (!("parsedValues" in check ? (_check_parsedValues = check.parsedValues) == null ? void 0 : _check_parsedValues.includes(ev) : "parsedValue" in check ? check.parsedValue === ev : check.value === ev)) { if (!userTsConfig.compilerOptions) { userTsConfig.compilerOptions = {}; } userTsConfig.compilerOptions[optionKey] = check.value; requiredActions.push(chalk.cyan(optionKey) + " was set to " + chalk.bold(check.value) + ` (${check.reason})`); } } else { // eslint-disable-next-line @typescript-eslint/no-unused-vars const _ = check; } } const nextAppTypes = `${distDir}/types/**/*.ts`; if (!("include" in rawConfig)) { userTsConfig.include = isAppDirEnabled ? [ "next-env.d.ts", nextAppTypes, "**/*.ts", "**/*.tsx" ] : [ "next-env.d.ts", "**/*.ts", "**/*.tsx" ]; suggestedActions.push(chalk.cyan("include") + " was set to " + chalk.bold(isAppDirEnabled ? `['next-env.d.ts', '${nextAppTypes}', '**/*.ts', '**/*.tsx']` : `['next-env.d.ts', '**/*.ts', '**/*.tsx']`)); } else if (isAppDirEnabled && !rawConfig.include.includes(nextAppTypes)) { userTsConfig.include.push(nextAppTypes); suggestedActions.push(chalk.cyan("include") + " was updated to add " + chalk.bold(`'${nextAppTypes}'`)); } // Enable the Next.js typescript plugin. if (isAppDirEnabled) { // Check if the config or the resolved config has the plugin already. const plugins = [ ...Array.isArray(tsOptions.plugins) ? tsOptions.plugins : [], ...userTsConfig.compilerOptions && Array.isArray(userTsConfig.compilerOptions.plugins) ? userTsConfig.compilerOptions.plugins : [] ]; const hasNextPlugin = plugins.some(({ name })=>name === "next"); // If the TS config extends on another config, we can't add the `plugin` field // because that will override the parent config's plugins. // Instead we have to show a message to the user to add the plugin manually. if (!userTsConfig.compilerOptions || plugins.length && !hasNextPlugin && "extends" in rawConfig && (!rawConfig.compilerOptions || !rawConfig.compilerOptions.plugins)) { Log.info(`\nYour ${chalk.bold("tsconfig.json")} extends another configuration, which means we cannot add the Next.js TypeScript plugin automatically. To improve your development experience, we recommend adding the Next.js plugin (\`${chalk.cyan('"plugins": [{ "name": "next" }]')}\`) manually to your TypeScript configuration. Learn more: https://nextjs.org/docs/app/building-your-application/configuring/typescript#the-typescript-plugin\n`); } else if (!hasNextPlugin) { if (!("plugins" in userTsConfig.compilerOptions)) { userTsConfig.compilerOptions.plugins = []; } userTsConfig.compilerOptions.plugins.push({ name: "next" }); suggestedActions.push(chalk.cyan("plugins") + " was updated to add " + chalk.bold(`{ name: 'next' }`)); } // If `strict` is set to `false` or `strictNullChecks` is set to `false`, // then set `strictNullChecks` to `true`. if (hasPagesDir && isAppDirEnabled && userTsConfig.compilerOptions && !userTsConfig.compilerOptions.strict && !("strictNullChecks" in userTsConfig.compilerOptions)) { userTsConfig.compilerOptions.strictNullChecks = true; suggestedActions.push(chalk.cyan("strictNullChecks") + " was set to " + chalk.bold(`true`)); } } if (!("exclude" in rawConfig)) { userTsConfig.exclude = [ "node_modules" ]; suggestedActions.push(chalk.cyan("exclude") + " was set to " + chalk.bold(`['node_modules']`)); } if (suggestedActions.length < 1 && requiredActions.length < 1) { return; } await fs.writeFile(tsConfigPath, CommentJson.stringify(userTsConfig, null, 2) + os.EOL); Log.info(""); if (isFirstTimeSetup) { Log.info(`We detected TypeScript in your project and created a ${chalk.cyan("tsconfig.json")} file for you.`); return; } Log.info(`We detected TypeScript in your project and reconfigured your ${chalk.cyan("tsconfig.json")} file for you. Strict-mode is set to ${chalk.cyan("false")} by default.`); if (suggestedActions.length) { Log.info(`The following suggested values were added to your ${chalk.cyan("tsconfig.json")}. These values ${chalk.cyan("can be changed")} to fit your project's needs:\n`); suggestedActions.forEach((action)=>Log.info(`\t- ${action}`)); Log.info(""); } if (requiredActions.length) { Log.info(`The following ${chalk.white("mandatory changes")} were made to your ${chalk.cyan("tsconfig.json")}:\n`); requiredActions.forEach((action)=>Log.info(`\t- ${action}`)); Log.info(""); } } //# sourceMappingURL=writeConfigurationDefaults.js.map