@inlang/paraglide-js
Version:
[](https://www.npmjs.com/package/@inlang/paraglide-js) [ • 9.24 kB
JavaScript
import { prompt } from "../utils.js";
import JSON5 from "json5";
import { pathExists } from "../../services/file-handling/exists.js";
import nodePath from "node:path";
import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
export const maybeUpdateTsConfig = async (ctx) => {
const ctx1 = await maybeUpdateTsConfigAllowJs(ctx);
return ctx1;
// return await maybeUpdateTsConfigModuleResolution(ctx1);
};
/**
* Paraligde JS compiles to JS with JSDoc comments. TypeScript doesn't allow JS files by default.
*/
export const maybeUpdateTsConfigAllowJs = async (ctx) => {
if ((await pathExists("./tsconfig.json", ctx.fs)) === false) {
return ctx;
}
if (await hasAllowJsEnabled("./tsconfig.json", ctx.fs)) {
// all clear, allowJs is already set to true
return ctx;
}
ctx.logger.info(`The option \`compilerOptions.allowJs\` needs to be set to \`true\` in the \`tsconfig.json\` file:
\`{
"compilerOptions": {
"allowJs": true
}
}\``);
let isValid = false;
while (isValid === false) {
const response = await prompt(`Is \`compilerOptions.allowJs\` set to \`true\`?`, {
type: "confirm",
initial: true,
});
if (response === false) {
ctx.logger.warn("Continuing without adjusting the tsconfig.json. This may lead to type errors.");
return ctx;
}
if (await hasAllowJsEnabled("./tsconfig.json", ctx.fs)) {
isValid = true;
return ctx;
}
else {
ctx.logger.error("The compiler options has not been adjusted. Please sets `compilerOptions.allowJs` to `true`.");
}
}
return ctx;
};
/**
* Recursively checks whether allowJs is enabled in the provided tsconfig or any
* referenced configuration files.
*
* @param tsconfigPath The path to the tsconfig to inspect.
* @param fs The file system used to read the configs.
* @param visited A set of already inspected files to avoid circular lookups.
* @example
* ```ts
* await hasAllowJsEnabled("./tsconfig.json", fs);
* ```
*/
export const hasAllowJsEnabled = async (tsconfigPath, fs, visited = new Set()) => {
const normalizedPath = normalizeConfigPath(tsconfigPath);
if (visited.has(normalizedPath)) {
return false;
}
visited.add(normalizedPath);
const file = await fs.readFile(normalizedPath, { encoding: "utf-8" });
const tsconfig = JSON5.parse(file);
if (tsconfig?.compilerOptions?.allowJs === true) {
return true;
}
const baseDir = nodePath.dirname(normalizedPath);
const extendCandidates = Array.isArray(tsconfig?.extends)
? tsconfig.extends
: tsconfig?.extends
? [tsconfig.extends]
: [];
for (const candidate of extendCandidates) {
if (typeof candidate !== "string")
continue;
const resolved = await resolveExtendedConfig(candidate, baseDir, fs);
if (resolved && (await hasAllowJsEnabled(resolved, fs, visited))) {
return true;
}
}
if (Array.isArray(tsconfig?.references)) {
for (const reference of tsconfig.references) {
const referencePath = reference?.path;
if (typeof referencePath !== "string")
continue;
const resolved = await resolveReferenceConfig(referencePath, baseDir, fs);
if (resolved && (await hasAllowJsEnabled(resolved, fs, visited))) {
return true;
}
}
}
return false;
};
/**
* Normalizes a tsconfig path to an absolute path.
*/
const normalizeConfigPath = (configPath) => {
return nodePath.isAbsolute(configPath)
? configPath
: nodePath.resolve(process.cwd(), configPath);
};
/**
* Resolves the extended tsconfig path relative to the base config.
*/
const resolveExtendedConfig = async (extendsSpecifier, baseDir, fs) => {
const candidates = new Set();
const resolvedBase = nodePath.isAbsolute(extendsSpecifier)
? extendsSpecifier
: nodePath.resolve(baseDir, extendsSpecifier);
candidates.add(resolvedBase);
if (nodePath.extname(resolvedBase) === "") {
candidates.add(`${resolvedBase}.json`);
candidates.add(nodePath.join(resolvedBase, "tsconfig.json"));
}
for (const candidate of candidates) {
if (await pathExists(candidate, fs)) {
return candidate;
}
}
try {
return require.resolve(extendsSpecifier, { paths: [baseDir] });
}
catch {
if (extendsSpecifier.endsWith(".json") === false) {
try {
return require.resolve(`${extendsSpecifier}.json`, {
paths: [baseDir],
});
}
catch {
return undefined;
}
}
}
return undefined;
};
/**
* Resolves the tsconfig referenced through the `references` property.
*/
const resolveReferenceConfig = async (referenceSpecifier, baseDir, fs) => {
const candidates = new Set();
const resolvedBase = nodePath.isAbsolute(referenceSpecifier)
? referenceSpecifier
: nodePath.resolve(baseDir, referenceSpecifier);
candidates.add(resolvedBase);
if (nodePath.extname(resolvedBase) === "") {
candidates.add(`${resolvedBase}.json`);
candidates.add(nodePath.join(resolvedBase, "tsconfig.json"));
}
for (const candidate of candidates) {
if (await pathExists(candidate, fs)) {
try {
const stats = await fs.stat(candidate);
if (stats.isDirectory()) {
const directoryConfig = nodePath.join(candidate, "tsconfig.json");
if (await pathExists(directoryConfig, fs)) {
return directoryConfig;
}
continue;
}
}
catch {
// ignore, we'll continue checking other candidates
}
return candidate;
}
}
return undefined;
};
// /**
// * Ensures that the moduleResolution compiler option is set to "bundler" or similar in the tsconfig.json.
// *
// * Otherwise, types defined in `package.exports` are not resolved by TypeScript. Leading to type
// * errors with Paraglide-JS.
// */
// const maybeUpdateTsConfigModuleResolution: CliStep<
// { fs: typeof import("node:fs/promises"); logger: Logger },
// unknown
// > = async (ctx) => {
// if ((await pathExists("./tsconfig.json", ctx.fs)) === false) {
// return ctx;
// }
// const file = await ctx.fs.readFile("./tsconfig.json", {
// encoding: "utf-8",
// });
// // tsconfig allows comments ... FML
// let tsconfig = JSON5.parse(file);
// let parentTsConfig: any | undefined;
// if (tsconfig.extends) {
// try {
// const parentTsConfigPath = nodePath.resolve(
// process.cwd(),
// tsconfig.extends
// );
// const parentTsConfigFile = await ctx.fs.readFile(parentTsConfigPath, {
// encoding: "utf-8",
// });
// parentTsConfig = JSON5.parse(parentTsConfigFile);
// } catch {
// ctx.logger.warn(
// `The tsconfig.json is extended from a tsconfig that couldn't be read. Maybe the file doesn't exist yet or is a NPM package. Continuing without taking the extended from tsconfig into consideration.`
// );
// }
// }
// // options that don't support package.exports
// const invalidOptions = ["classic", "node", "node10"];
// const moduleResolution =
// tsconfig.compilerOptions?.moduleResolution ??
// parentTsConfig?.compilerOptions?.moduleResolution;
// if (
// moduleResolution &&
// invalidOptions.includes(moduleResolution.toLowerCase()) === false
// ) {
// // the moduleResolution is already set to bundler or similar
// return ctx;
// }
// ctx.logger.info(
// `The \`compilerOptions.moduleResolution\` options must be set to "Bundler" in the \`tsconfig.json\` file:
// \`{
// "compilerOptions": {
// "moduleResolution": "Bundler"
// }
// }\``
// );
// let isValid = false;
// while (isValid === false) {
// const response = await prompt(
// `Is \`compilerOptions.moduleResolution\` set to "Bundler"?`,
// {
// type: "confirm",
// initial: true,
// }
// );
// if (response === false) {
// ctx.logger.warn(
// "Continuing without adjusting the tsconfig.json. This may lead to type errors."
// );
// return ctx;
// }
// // don't re-ask the question if there is an `extends` present in the tsconfig
// // just trust that it's correct.
// if (tsconfig.extends) {
// isValid = true;
// return ctx;
// }
// const file = await ctx.fs.readFile("./tsconfig.json", {
// encoding: "utf-8",
// });
// tsconfig = JSON5.parse(file);
// if (
// tsconfig?.compilerOptions?.moduleResolution &&
// tsconfig.compilerOptions.moduleResolution.toLowerCase() === "bundler"
// ) {
// isValid = true;
// return ctx;
// } else {
// ctx.logger.error(
// "The compiler options have not been adjusted. Please set the `compilerOptions.moduleResolution` to `Bundler`."
// );
// }
// }
// return ctx;
// };
//# sourceMappingURL=update-ts-config.js.map