@intlayer/chokidar
Version:
Uses chokidar to scan and build Intlayer declaration files into dictionaries based on Intlayer configuration.
329 lines (327 loc) • 14.9 kB
JavaScript
import { initConfig } from "../initConfig/index.mjs";
import { updateAstroConfig, updateNextConfig, updateNuxtConfig, updateSvelteConfig, updateViteConfig } from "./utils/configManipulation.mjs";
import { ensureDirectory, exists, readFileFromRoot, writeFileToRoot } from "./utils/fileSystem.mjs";
import { parseJSONWithComments } from "./utils/jsonParser.mjs";
import { findTsConfigFiles } from "./utils/tsConfig.mjs";
import { join } from "node:path";
import { colorize, colorizePath, logger, v, x } from "@intlayer/config/logger";
import { getConfiguration } from "@intlayer/config/node";
import { getAlias } from "@intlayer/config/utils";
import * as ANSIColors from "@intlayer/config/colors";
//#region src/init/index.ts
/**
* Documentation URL Constants
*/
const DocumentationRouter = {
NextJS: "https://intlayer.org/doc/environment/nextjs.md",
NextJS_15: "https://intlayer.org/doc/environment/nextjs/15.md",
NextJS_14: "https://intlayer.org/doc/environment/nextjs/14.md",
CRA: "https://intlayer.org/doc/environment/create-react-app.md",
Astro: "https://intlayer.org/doc/environment/astro.md",
ViteAndReact: "https://intlayer.org/doc/environment/vite-and-react.md",
ViteAndReact_ReactRouterV7: "https://intlayer.org/doc/environment/vite-and-react/react-router-v7.md",
ViteAndReact_ReactRouterV7_FSRoutes: "https://intlayer.org/doc/environment/vite-and-react/react-router-v7-fs-routes.md",
ViteAndVue: "https://intlayer.org/doc/environment/vite-and-vue.md",
ViteAndSolid: "https://intlayer.org/doc/environment/vite-and-solid.md",
ViteAndSvelte: "https://intlayer.org/doc/environment/vite-and-svelte.md",
ViteAndPreact: "https://intlayer.org/doc/environment/vite-and-preact.md",
TanStackRouter: "https://intlayer.org/doc/environment/tanstack.md",
NuxtAndVue: "https://intlayer.org/doc/environment/nuxt-and-vue.md",
Angular: "https://intlayer.org/doc/environment/angular.md",
SvelteKit: "https://intlayer.org/doc/environment/sveltekit.md",
ReactNativeAndExpo: "https://intlayer.org/doc/environment/react-native-and-expo.md",
Lynx: "https://intlayer.org/doc/environment/lynx-and-react.md",
Express: "https://intlayer.org/doc/environment/express.md",
NestJS: "https://intlayer.org/doc/environment/nestjs.md",
Fastify: "https://intlayer.org/doc/environment/fastify.md",
Default: "https://intlayer.org/doc/get-started",
NextIntl: "https://intlayer.org/blog/intlayer-with-next-intl.md",
ReactI18Next: "https://intlayer.org/blog/intlayer-with-react-i18next.md",
ReactIntl: "https://intlayer.org/blog/intlayer-with-react-intl.md",
NextI18Next: "https://intlayer.org/blog/intlayer-with-next-i18next.md",
VueI18n: "https://intlayer.org/blog/intlayer-with-vue-i18n.md"
};
/**
* Helper: Detects the environment and returns the doc URL
*/
const getDocumentationUrl = (packageJson) => {
const deps = {
...packageJson.dependencies,
...packageJson.devDependencies
};
/**
* Helper to check if a version string matches a specific major version
* Matches: "15", "^15.0.0", "~15.2", "15.0.0-beta"
*/
const isVersion = (versionString, major) => {
if (!versionString || typeof versionString !== "string") return false;
return new RegExp(`^[\\^~]?${major}(?:\\.|$)`).test(versionString);
};
if (deps["@lynx-js/react"] || deps["@lynx-js/core"]) return DocumentationRouter.Lynx;
if (deps["react-native"] || deps.expo) return DocumentationRouter.ReactNativeAndExpo;
if (deps.next) {
const version = deps.next;
if (isVersion(version, 14)) return DocumentationRouter.NextJS_14;
if (isVersion(version, 15)) return DocumentationRouter.NextJS_15;
return DocumentationRouter.NextJS;
}
if (deps.nuxt) return DocumentationRouter.NuxtAndVue;
if (deps.astro) return DocumentationRouter.Astro;
if (deps["@sveltejs/kit"]) return DocumentationRouter.SvelteKit;
if (deps["@tanstack/react-router"]) return DocumentationRouter.TanStackRouter;
const reactRouterVersion = deps["react-router"];
if (reactRouterVersion && typeof reactRouterVersion === "string") {
if (deps["@react-router/fs-routes"]) return DocumentationRouter.ViteAndReact_ReactRouterV7_FSRoutes;
if (isVersion(reactRouterVersion, 7)) return DocumentationRouter.ViteAndReact_ReactRouterV7;
}
if (deps.vite) {
if (deps.vue) return DocumentationRouter.ViteAndVue;
if (deps["solid-js"]) return DocumentationRouter.ViteAndSolid;
if (deps.svelte) return DocumentationRouter.ViteAndSvelte;
if (deps.preact) return DocumentationRouter.ViteAndPreact;
return DocumentationRouter.ViteAndReact;
}
if (deps["react-scripts"]) return DocumentationRouter.CRA;
if (deps["@angular/core"]) return DocumentationRouter.Angular;
if (deps["@nestjs/core"]) return DocumentationRouter.NestJS;
if (deps.express) return DocumentationRouter.Express;
if (deps.fastify) return DocumentationRouter.Fastify;
if (deps["next-intl"]) return DocumentationRouter.NextIntl;
if (deps["react-i18next"] || deps.i18next) return DocumentationRouter.ReactI18Next;
if (deps["react-intl"]) return DocumentationRouter.ReactIntl;
if (deps["next-i18next"]) return DocumentationRouter.NextI18Next;
if (deps["vue-i18n"]) return DocumentationRouter.VueI18n;
return DocumentationRouter.Default;
};
/**
* MAIN LOGIC
*/
const initIntlayer = async (rootDir, options) => {
logger(colorize("Checking Intlayer configuration...", ANSIColors.CYAN));
const packageJsonPath = "package.json";
if (!await exists(rootDir, packageJsonPath)) {
logger(`${x} No ${colorizePath("package.json")} found. Please run this script from the project root.`, { level: "error" });
process.exit(1);
}
const packageJsonContent = await readFileFromRoot(rootDir, packageJsonPath);
let packageJson;
try {
packageJson = JSON.parse(packageJsonContent);
} catch {
logger(`${x} Could not parse ${colorizePath("package.json")}.`, { level: "error" });
process.exit(1);
}
const guideUrl = getDocumentationUrl(packageJson);
const gitignorePath = ".gitignore";
if (!options?.noGitignore && await exists(rootDir, gitignorePath)) {
const gitignoreContent = await readFileFromRoot(rootDir, gitignorePath);
if (!gitignoreContent.includes("intlayer")) {
await writeFileToRoot(rootDir, gitignorePath, `${gitignoreContent}\n# Intlayer\n.intlayer\n`);
logger(`${v} Added ${colorizePath(".intlayer")} to ${colorizePath(gitignorePath)}`);
} else logger(`${v} ${colorizePath(gitignorePath)} already includes .intlayer`);
}
const vscodeDir = ".vscode";
const extensionsJsonPath = join(vscodeDir, "extensions.json");
const extensionId = "intlayer.intlayer-vs-code-extension";
try {
let extensionsConfig = { recommendations: [] };
if (await exists(rootDir, extensionsJsonPath)) extensionsConfig = parseJSONWithComments(await readFileFromRoot(rootDir, extensionsJsonPath));
else await ensureDirectory(rootDir, vscodeDir);
if (!extensionsConfig.recommendations) extensionsConfig.recommendations = [];
if (!extensionsConfig.recommendations.includes(extensionId)) {
extensionsConfig.recommendations.push(extensionId);
await writeFileToRoot(rootDir, extensionsJsonPath, JSON.stringify(extensionsConfig, null, 2));
logger(`${v} Added ${colorize(extensionId, ANSIColors.MAGENTA)} to ${colorizePath(extensionsJsonPath)}`);
} else logger(`${v} ${colorizePath(extensionsJsonPath)} already includes ${colorize(extensionId, ANSIColors.MAGENTA)}`);
} catch {
logger(`${x} Could not update ${colorizePath(extensionsJsonPath)}. You may need to add ${colorize(extensionId, ANSIColors.MAGENTA)} manually.`, { level: "warn" });
}
const tsConfigFiles = await findTsConfigFiles(rootDir);
let hasTsConfig = false;
for (const fileName of tsConfigFiles) if (await exists(rootDir, fileName)) {
hasTsConfig = true;
try {
const config = parseJSONWithComments(await readFileFromRoot(rootDir, fileName));
const typeDefinition = ".intlayer/**/*.ts";
let updated = false;
if (!config.include) {} else if (Array.isArray(config.include) && !config.include.some((pattern) => pattern.includes(".intlayer"))) {
config.include.push(typeDefinition);
updated = true;
} else if (config.include.includes(typeDefinition)) logger(`${v} ${colorizePath(fileName)} already includes intlayer types`);
if (updated) {
await writeFileToRoot(rootDir, fileName, JSON.stringify(config, null, 2));
logger(`${v} Updated ${colorizePath(fileName)} to include intlayer types`);
}
} catch {
logger(`${x} Could not parse or update ${colorizePath(fileName)}. You may need to add ${colorizePath(".intlayer/types/**/*.ts")} manually.`, { level: "warn" });
}
}
await initConfig(hasTsConfig ? "intlayer.config.ts" : "intlayer.config.mjs", rootDir);
let hasAliasConfiguration = false;
for (const file of [
"vite.config.ts",
"vite.config.js",
"vite.config.mjs"
]) if (await exists(rootDir, file)) {
hasAliasConfiguration = true;
const content = await readFileFromRoot(rootDir, file);
if (!content.includes("vite-intlayer")) {
await writeFileToRoot(rootDir, file, updateViteConfig(content, file.split(".").pop()));
logger(`${v} Updated ${colorizePath(file)} to include Intlayer plugin`);
}
break;
}
const nextConfigs = [
"next.config.js",
"next.config.mjs",
"next.config.ts"
];
let isNextJsProject = false;
for (const file of nextConfigs) if (await exists(rootDir, file)) {
isNextJsProject = true;
hasAliasConfiguration = true;
const content = await readFileFromRoot(rootDir, file);
if (!content.includes("next-intlayer")) {
await writeFileToRoot(rootDir, file, updateNextConfig(content, file.split(".").pop()));
logger(`${v} Updated ${colorizePath(file)} to include Intlayer plugin`);
}
break;
}
for (const file of [
"astro.config.mjs",
"astro.config.js",
"astro.config.ts",
"astro.config.cjs"
]) if (await exists(rootDir, file)) {
hasAliasConfiguration = true;
if (file.startsWith("astro.config.")) {
const content = await readFileFromRoot(rootDir, file);
if (!content.includes("astro-intlayer")) {
await writeFileToRoot(rootDir, file, updateAstroConfig(content, file.split(".").pop()));
logger(`${v} Updated ${colorizePath(file)} to include Intlayer integration`);
}
}
break;
}
for (const file of ["nuxt.config.js", "nuxt.config.ts"]) if (await exists(rootDir, file)) {
hasAliasConfiguration = true;
const content = await readFileFromRoot(rootDir, file);
if (!content.includes("nuxt-intlayer")) {
await writeFileToRoot(rootDir, file, updateNuxtConfig(content, file.split(".").pop()));
logger(`${v} Updated ${colorizePath(file)} to include Intlayer module`);
}
break;
}
for (const file of ["svelte.config.js", "svelte.config.ts"]) if (await exists(rootDir, file)) {
hasAliasConfiguration = true;
const content = await readFileFromRoot(rootDir, file);
if (!content.includes("@ts-check")) {
await writeFileToRoot(rootDir, file, updateSvelteConfig(content, file.split(".").pop()));
logger(`${v} Updated ${colorizePath(file)} to include Intlayer typing`);
}
break;
}
const allDeps = {
...packageJson.dependencies,
...packageJson.devDependencies
};
const isVersionGreaterOrEqual = (versionString, major) => {
if (!versionString || typeof versionString !== "string") return false;
const match = versionString.match(/^[^\d]*(\d+)/);
if (!match) return false;
return parseInt(match[1], 10) >= major;
};
const backendIntlayerPackages = [
"express-intlayer",
"fastify-intlayer",
"adonis-intlayer",
"hono-intlayer"
];
const devScript = packageJson.scripts?.dev;
let newDevScript;
if ((isNextJsProject && isVersionGreaterOrEqual(allDeps.next, 16) || backendIntlayerPackages.some((pkg) => allDeps[pkg])) && !devScript.includes("intlayer watch")) newDevScript = `intlayer watch --with '${devScript}'`;
if (newDevScript) {
packageJson.scripts.dev = newDevScript;
await writeFileToRoot(rootDir, packageJsonPath, JSON.stringify(packageJson, null, 2));
logger(`${v} Updated ${colorizePath("package.json")} dev script to run intlayer watch`);
}
for (const file of [
"webpack.config.js",
"webpack.config.ts",
"webpack.config.mjs",
"webpack.config.cjs"
]) if (await exists(rootDir, file)) {
hasAliasConfiguration = true;
logger(`${v} Found ${colorizePath(file)}. Make sure to configure aliases manually or use the Intlayer Webpack plugin.`);
break;
}
if ([
"express",
"fastify",
"@adonisjs/core",
"hono",
...backendIntlayerPackages
].some((pkg) => allDeps[pkg])) hasAliasConfiguration = true;
if (!hasAliasConfiguration) {
const aliases = getAlias({ configuration: getConfiguration({ baseDir: rootDir }) });
if (hasTsConfig && tsConfigFiles.length > 0) {
const tsConfigPath = tsConfigFiles.find((file) => file === "tsconfig.json") || tsConfigFiles[0];
const config = parseJSONWithComments(await readFileFromRoot(rootDir, tsConfigPath));
config.compilerOptions ??= {};
config.compilerOptions.paths ??= {};
let updated = false;
Object.entries(aliases).forEach(([alias, path]) => {
if (!config.compilerOptions.paths[alias]) {
config.compilerOptions.paths[alias] = [path];
updated = true;
}
});
if (updated) {
await writeFileToRoot(rootDir, tsConfigPath, JSON.stringify(config, null, 2));
logger(`${v} Updated ${colorizePath(tsConfigPath)} to include Intlayer aliases`);
}
} else {
const jsConfigPath = "jsconfig.json";
if (await exists(rootDir, jsConfigPath)) {
const config = parseJSONWithComments(await readFileFromRoot(rootDir, jsConfigPath));
config.compilerOptions ??= {};
config.compilerOptions.paths ??= {};
let updated = false;
Object.entries(aliases).forEach(([alias, path]) => {
if (!config.compilerOptions.paths[alias]) {
config.compilerOptions.paths[alias] = [path];
updated = true;
}
});
if (updated) {
await writeFileToRoot(rootDir, jsConfigPath, JSON.stringify(config, null, 2));
logger(`${v} Updated ${colorizePath(jsConfigPath)} to include Intlayer aliases`);
}
} else {
packageJson.imports ??= {};
let updated = false;
Object.entries(aliases).forEach(([alias, path]) => {
const importAlias = alias.replace("@", "#");
const importPath = path.startsWith(".") ? path : `./${path}`;
if (!packageJson.imports[importAlias]) {
packageJson.imports[importAlias] = importPath;
updated = true;
}
});
if (updated) {
await writeFileToRoot(rootDir, packageJsonPath, JSON.stringify(packageJson, null, 2));
logger(`${v} Updated ${colorizePath(packageJsonPath)} to include Intlayer imports`);
}
}
}
}
logger(`${v} ${colorize("Intlayer init setup complete.", ANSIColors.GREEN)}`);
logger([
colorize("Next →", ANSIColors.MAGENTA),
colorize(`Follow the instructions in the documentation to complete the setup:`, ANSIColors.GREY_LIGHT),
colorizePath(guideUrl)
]);
};
//#endregion
export { initIntlayer };
//# sourceMappingURL=index.mjs.map