224 lines
10 KiB
Plaintext
224 lines
10 KiB
Plaintext
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 |