UNPKG

@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
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