UNPKG

@omlet/cli

Version:

Omlet (https://omlet.dev) is a component analytics tool that uses a CLI to scan your codebase to detect components and their usage. Get real usage insights from customizable charts to measure adoption across all projects and identify opportunities to impr

699 lines (697 loc) • 34.4 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.starPatternToGlob = exports.findParentProject = exports.getDefaultRoot = exports.ProjectSetupResolver = exports.CannotLoadTSConfig = exports.NoProjectFound = void 0; const child_process_1 = require("child_process"); const fast_glob_1 = __importDefault(require("fast-glob")); const find_root_1 = __importDefault(require("find-root")); const fs_1 = require("fs"); const get_tsconfig_1 = require("get-tsconfig"); const js_yaml_1 = __importDefault(require("js-yaml")); const json5_1 = __importDefault(require("json5")); const jsonc_parser_1 = require("jsonc-parser"); const micromatch_1 = __importDefault(require("micromatch")); const upath_1 = __importDefault(require("upath")); const util_1 = __importDefault(require("util")); const error_1 = require("../error"); const fileUtils_1 = require("../fileUtils"); const logger_1 = require("../logger"); const nxUtils_1 = require("./nxUtils/nxUtils"); const pathResolutionMap_1 = require("./pathResolutionMap"); const validator_1 = require("./validator"); const exec = util_1.default.promisify(child_process_1.exec); function findWorkspacePaths(pathPatterns, cwd) { return (0, fast_glob_1.default)(pathPatterns, { cwd, onlyDirectories: true, ignore: ["**/node_modules/**"] }); } class NoProjectFound extends error_1.CliError { constructor(path) { super("Could not find package.json", { context: { path } }); this.path = path; } } exports.NoProjectFound = NoProjectFound; class CannotLoadTSConfig extends error_1.CliError { constructor(data) { super("Could not find tsconfig.json", { context: data }); this.detail = data.detail; this.path = data.path; } } exports.CannotLoadTSConfig = CannotLoadTSConfig; var MonorepoType; (function (MonorepoType) { MonorepoType["Yarn"] = "yarn"; MonorepoType["PNPM"] = "pnpm"; MonorepoType["Lerna"] = "lerna"; MonorepoType["Bolt"] = "bolt"; MonorepoType["Nx"] = "nx"; })(MonorepoType || (MonorepoType = {})); async function readPnpmWorkspaceConfig(repoPath) { var _a; const pnpmContent = (_a = await (0, fileUtils_1.readIfExists)(upath_1.default.join(repoPath, "pnpm-workspace.yaml"))) !== null && _a !== void 0 ? _a : await (0, fileUtils_1.readIfExists)(upath_1.default.join(repoPath, "pnpm-workspace.yml")); if (pnpmContent) { return js_yaml_1.default.load(pnpmContent); } } async function readLernaConfig(repoPath) { const lernaConfig = await (0, fileUtils_1.readIfExists)(upath_1.default.join(repoPath, "lerna.json")); if (lernaConfig) { return JSON.parse(lernaConfig); } } class ProjectSetupResolver { constructor(absRepoRoot, rootPackage, userConfig, fileIndex) { this.absRepoRoot = absRepoRoot; this.rootPackage = rootPackage; this.packages = []; this.userConfig = userConfig; this.globCache = {}; this.scannedFileIndex = fileIndex; } static async create(absRepoRoot, rootPackagePath, config) { const packageJsonPath = upath_1.default.join(rootPackagePath, "package.json"); if (!await (0, fileUtils_1.pathExists)(packageJsonPath)) { throw new NoProjectFound(upath_1.default.dirname(packageJsonPath)); } const rootPackage = await ProjectSetupResolver.readPackage(packageJsonPath, config.tsconfigPath); const fileIndex = await (0, fast_glob_1.default)(config.include, { cwd: rootPackagePath, ignore: config.ignore, suppressErrors: true }); const resolver = new ProjectSetupResolver(absRepoRoot, rootPackage, config, fileIndex); await resolver.readPackages(); return resolver; } resolveAbsolutePath(...args) { return upath_1.default.resolve(this.absRepoRoot, this.rootPackage.path, ...args); } relativeProjectPath(otherPath) { return upath_1.default.relative(this.rootPackage.path, otherPath); } get projectRoot() { return this.resolveAbsolutePath(); } static async readTsConfig(tsconfigPath) { var _a; if (!await (0, fileUtils_1.pathExists)(tsconfigPath)) { return; } try { if ((await fs_1.promises.lstat(tsconfigPath)).isFile()) { return { config: (0, get_tsconfig_1.parseTsconfig)(tsconfigPath), path: tsconfigPath, }; } return (_a = (0, get_tsconfig_1.getTsconfig)(tsconfigPath)) !== null && _a !== void 0 ? _a : undefined; } catch (err) { throw new CannotLoadTSConfig({ path: tsconfigPath, detail: err.message }); } } static async readPackage(packageJsonPath, tsConfigPath) { var _a, _b; const pkgJsonContent = await fs_1.promises.readFile(packageJsonPath, "utf8"); const pkgJson = json5_1.default.parse(pkgJsonContent); const packagePath = upath_1.default.dirname(packageJsonPath); const tsconfig = await ProjectSetupResolver.readTsConfig(tsConfigPath !== null && tsConfigPath !== void 0 ? tsConfigPath : upath_1.default.join(packagePath, "tsconfig.json")); return { name: (_a = pkgJson.name) !== null && _a !== void 0 ? _a : "root", version: (_b = pkgJson.version) !== null && _b !== void 0 ? _b : "0.0.0", path: packagePath, info: pkgJson, tsconfig, aliases: new pathResolutionMap_1.PathResolutionMap(pathResolutionMap_1.PathResolutionEntryType.Alias), importMap: new pathResolutionMap_1.PathResolutionMap(pathResolutionMap_1.PathResolutionEntryType.Import), exportMap: new pathResolutionMap_1.PathResolutionMap(pathResolutionMap_1.PathResolutionEntryType.Export), dependencies: new Set(), }; } static async readNxProject(rootPath, packagePath) { const tsconfig = await ProjectSetupResolver.readTsConfig(upath_1.default.join(packagePath, "tsconfig.json")); return (0, nxUtils_1.readNxProject)(rootPath, packagePath, tsconfig); } static async findWorkspaces(root) { const projectRoot = root.path; const workspaces = root.info.workspaces; if (workspaces && "packages" in workspaces) { const workspacePaths = await findWorkspacePaths(workspaces.packages, projectRoot); return { type: MonorepoType.Yarn, workspacePaths, }; } if (workspaces) { const workspacePaths = await findWorkspacePaths(workspaces, projectRoot); return { type: MonorepoType.Yarn, workspacePaths, }; } const pnpmConfig = await readPnpmWorkspaceConfig(projectRoot); if (pnpmConfig && pnpmConfig.packages) { const workspacePaths = await findWorkspacePaths(pnpmConfig.packages, projectRoot); return { type: MonorepoType.PNPM, workspacePaths, }; } const lernaConfig = await readLernaConfig(projectRoot); if (lernaConfig && lernaConfig.packages) { const workspacePaths = await findWorkspacePaths(lernaConfig.packages, projectRoot); return { type: MonorepoType.Lerna, workspacePaths, }; } if (root.info.bolt) { const workspacePaths = await findWorkspacePaths(root.info.bolt.workspaces, projectRoot); return { type: MonorepoType.Bolt, workspacePaths, }; } const nxConfig = await (0, nxUtils_1.readNxConfig)(projectRoot); if (nxConfig) { const workspacePaths = await (0, nxUtils_1.findNxWorkspacePaths)(projectRoot); return { type: MonorepoType.Nx, workspacePaths, }; } return { workspacePaths: [] }; } findPackageByName(name) { if (this.rootPackage.name === name) { return this.rootPackage; } return this.packages.find(p => p.name === name); } findPackageByPath(packagePath) { const absPath = this.resolveAbsolutePath(packagePath); if (this.rootPackage.path === absPath) { return this.rootPackage; } return this.packages.find(p => p.path === absPath); } getPackageConfig(name) { var _a; if (this.rootPackage.name === name) { const { tsconfigPath, aliases, exports } = this.userConfig; return { tsconfigPath, exports, aliases, }; } return (_a = this.userConfig.workspaces) === null || _a === void 0 ? void 0 : _a[name]; } async findGlobMatchesInProject(patterns, globBase) { const globRoot = globBase ? this.resolveAbsolutePath(globBase) : this.projectRoot; const cacheKey = `${globRoot}:${patterns.sort().join(",")}`; if (this.globCache[cacheKey]) { return this.globCache[cacheKey]; } const matches = (await (0, fast_glob_1.default)(patterns, { cwd: globRoot, ignore: ["**/node_modules/**"], suppressErrors: true })).map(fileRelPath => upath_1.default.relative(this.projectRoot, upath_1.default.resolve(globRoot, fileRelPath))); this.globCache[cacheKey] = matches; return matches; } async hasGlobMatchesInProject(patterns, globBase) { return (await this.findGlobMatchesInProject(patterns, globBase)).length > 0; } async isGlobIncluded(patterns) { return micromatch_1.default.some(this.scannedFileIndex, patterns); } async readTypescriptAliases(pkg) { var _a, _b, _c; const isPathsInherited = async (tsconfigInfo) => { var _a, _b; const tsConfigFileContent = await (0, fileUtils_1.readIfExists)(tsconfigInfo.path); if (!tsConfigFileContent) { return false; } const rawTsConfig = (0, jsonc_parser_1.parse)(tsConfigFileContent); return ((_a = tsconfigInfo.config.compilerOptions) === null || _a === void 0 ? void 0 : _a.paths) && !((_b = rawTsConfig.compilerOptions) === null || _b === void 0 ? void 0 : _b.paths); }; const aliases = new pathResolutionMap_1.PathResolutionMap(pathResolutionMap_1.PathResolutionEntryType.Alias); if (!pkg.tsconfig) { return aliases; } const { config: tsConfig, path: tsConfigPath } = pkg.tsconfig; if (!((_a = tsConfig.compilerOptions) === null || _a === void 0 ? void 0 : _a.baseUrl) && !((_b = tsConfig.compilerOptions) === null || _b === void 0 ? void 0 : _b.paths)) { return aliases; } // If compilerOptions.paths is inherited from one of the base config files, // get-tsconfig resolves those paths in "paths" relative to the input config file path // Otherwise, "paths" remains unchanged so they need to be resolved relative to `join(configPath, baseUrl)` const configBaseUrl = this.relativeProjectPath(upath_1.default.join(upath_1.default.dirname(tsConfigPath), (_c = tsConfig.compilerOptions.baseUrl) !== null && _c !== void 0 ? _c : ".")); const pathsBaseUrl = await isPathsInherited(pkg.tsconfig) ? this.relativeProjectPath(upath_1.default.dirname(tsConfigPath)) : configBaseUrl; if (tsConfig.compilerOptions.paths) { for (const [alias, paths] of Object.entries(tsConfig.compilerOptions.paths)) { const patterns = paths.map(p => upath_1.default.join(pathsBaseUrl, p)); if (await this.hasGlobMatchesInProject(patterns.flatMap(p => starPatternToGlob(p, true)))) { aliases.addMapping(alias, patterns, tsConfigPath); } } } // Typescript compiler allows import directly from the baseUrl location // This section adds alias mapping for files and directories under baseUrl // Only files and directories that have matching files included in the scan are added if (tsConfig.compilerOptions.baseUrl) { const basePath = this.resolveAbsolutePath(configBaseUrl); const entries = await (0, fileUtils_1.listDir)(basePath); for (const entry of entries) { const name = entry.name; const pathInProject = upath_1.default.join(configBaseUrl, name); if (name.startsWith(".")) { continue; } else if (entry.isDirectory() && await this.hasGlobMatchesInProject([upath_1.default.join(pathInProject, "**", "*.{js,jsx,ts,tsx}")])) { aliases.addMapping(upath_1.default.join(name, "*"), upath_1.default.join(configBaseUrl, name, "*"), tsConfigPath); if (await this.hasGlobMatchesInProject([upath_1.default.join(pathInProject, "index.{js,jsx,ts,tsx}")])) { aliases.addMapping(name, upath_1.default.join(configBaseUrl, name), tsConfigPath); } } else if (entry.isFile() && /\.[jt]sx?$/.test(name) && await this.hasGlobMatchesInProject([pathInProject])) { aliases.addMapping(name, upath_1.default.join(configBaseUrl, name), tsConfigPath); } } } return aliases; } async readImportMap(pkg) { var _a, _b, _c; const entrySource = upath_1.default.join(pkg.path, "package.json"); const readDependencyMapping = async (dependencies) => { const mapping = new pathResolutionMap_1.PathResolutionMap(pathResolutionMap_1.PathResolutionEntryType.Import); for (const [depName, depDefinition] of Object.entries(dependencies)) { let depPackage; if (depDefinition.startsWith("workspace:")) { /* Workspace protocol can have the following values: - "dep-v": "workspace:1.0.0" - "dep-w": "workspace:*" - "dep-x": "workspace:^" - "dep-y": "workspace:~" - "dep-z": "workspace:path/to/dep-z" In case of "~", "^", "*", we should match the package having the same name with the dependency. In case a path is specified, then this dependency should resolve to the package located at the path. */ const specifier = depDefinition.replace(/^workspace:/, ""); if (/^[\^~*0-9]/.test(specifier)) { depPackage = this.findPackageByName(depName); } else { depPackage = this.findPackageByPath(specifier); } } else { depPackage = this.findPackageByName(depName); } if (!depPackage) { continue; } mapping.merge(await this.getImportMapForPackageEntryPoints(depPackage)); } return mapping; }; const importMap = new pathResolutionMap_1.PathResolutionMap(pathResolutionMap_1.PathResolutionEntryType.Import); for (const [depName, depDefinition] of Object.entries((_a = pkg.info.dependencies) !== null && _a !== void 0 ? _a : {}).concat(Object.entries((_b = pkg.info.devDependencies) !== null && _b !== void 0 ? _b : {}))) { if (depDefinition.startsWith("npm:")) { const targetPackageName = depDefinition .replace(/^npm:/, "") .replace(/@[^@]+$/, ""); importMap.addMapping(depName, targetPackageName, entrySource); } } const pkgPath = this.relativeProjectPath(pkg.path); importMap.addMapping(`${pkg.name}/*`, (0, fileUtils_1.normalizeTrimPath)(upath_1.default.join(pkgPath, "*")), entrySource); const entryPoint = (_c = pkg.exportMap.getEntry(".")) === null || _c === void 0 ? void 0 : _c.patterns[0]; if (entryPoint) { const pattern = upath_1.default.join(pkgPath, entryPoint); if (await this.hasGlobMatchesInProject(starPatternToGlob(pattern, true))) { importMap.addMapping(pkg.name, upath_1.default.join(pkgPath, entryPoint), entrySource); } } if (pkg.info.devDependencies) { importMap.merge(await readDependencyMapping(pkg.info.devDependencies)); } if (pkg.info.dependencies) { importMap.merge(await readDependencyMapping(pkg.info.dependencies)); } return importMap; } async getImportMapForPackageEntryPoints(pkg) { const mapping = new pathResolutionMap_1.PathResolutionMap(pathResolutionMap_1.PathResolutionEntryType.Import); const pkgPath = this.relativeProjectPath(pkg.path); const entrySource = upath_1.default.join(pkg.path, "package.json"); mapping.addMapping(`${pkg.name}/*`, `${pkgPath}/*`, entrySource); for (const entry of pkg.exportMap.entries()) { const patterns = entry.patterns.map(pat => upath_1.default.join(pkgPath, pat)); const globPatterns = patterns.flatMap(pat => starPatternToGlob(pat, true)); if (await this.hasGlobMatchesInProject(globPatterns)) { mapping.addMapping(upath_1.default.join(pkg.name, entry.name), patterns, entrySource); } } return mapping; } async readRootPackageImportMap() { const importMap = await this.readImportMap(this.rootPackage); // Add mappings for monorepo packages since those packages can be used without being listed as a dependency for (const pkg of this.packages) { importMap.merge(await this.getImportMapForPackageEntryPoints(pkg)); } return importMap; } async readPackageAliases(pkg) { var _a; const packageAliases = new pathResolutionMap_1.PathResolutionMap(pathResolutionMap_1.PathResolutionEntryType.Alias); const typescriptAliases = await this.readTypescriptAliases(pkg); const configAliases = (_a = this.getPackageConfig(pkg.name)) === null || _a === void 0 ? void 0 : _a.aliases; if (configAliases) { const entrySource = this.userConfig.configPath ? this.resolveAbsolutePath(this.userConfig.configPath) : "none"; for (const [name, paths] of Object.entries(configAliases)) { packageAliases.addMapping(name, paths.map(fileUtils_1.normalizeTrimPath), entrySource); } } return packageAliases.merge(typescriptAliases); } async readExportMap(pkg) { var _a, _b, _c, _d, _e, _f; const exportMap = new pathResolutionMap_1.PathResolutionMap(pathResolutionMap_1.PathResolutionEntryType.Export); const { rootDir, outDir } = (_a = (() => { var _a, _b, _c, _d; let { rootDir, outDir } = (_c = (_b = (_a = pkg.tsconfig) === null || _a === void 0 ? void 0 : _a.config) === null || _b === void 0 ? void 0 : _b.compilerOptions) !== null && _c !== void 0 ? _c : {}; const tsConfigPath = ((_d = pkg.tsconfig) === null || _d === void 0 ? void 0 : _d.path) && this.relativeProjectPath(upath_1.default.dirname(pkg.tsconfig.path)); if (rootDir && outDir && tsConfigPath) { const configPathRelToPackage = upath_1.default.relative(this.relativeProjectPath(pkg.path), tsConfigPath); rootDir = upath_1.default.join(configPathRelToPackage, rootDir); outDir = upath_1.default.join(configPathRelToPackage, outDir); } return { rootDir, outDir }; })()) !== null && _a !== void 0 ? _a : {}; const rewritePath = (inputPath) => { const formattedPath = (0, fileUtils_1.normalizeTrimPath)(inputPath); if (rootDir && outDir) { return formattedPath.replace(new RegExp(`^${(0, fileUtils_1.normalizeTrimPath)(outDir)}/`), `${(0, fileUtils_1.normalizeTrimPath)(rootDir)}/`).replace(/\.js$/, ".ts"); } return formattedPath; }; async function findPackageMainEntryPoint() { if (pkg.info.exports) { if (typeof pkg.info.exports === "string") { return rewritePath(pkg.info.exports); } if (Array.isArray(pkg.info.exports)) { return rewritePath(pkg.info.exports[0]); } else if (typeof pkg.info.exports["."] === "string") { return rewritePath(pkg.info.exports["."]); } } if (pkg.info.main) { return rewritePath(pkg.info.main); } const entries = await (0, fileUtils_1.listDir)(pkg.path); for (const entry of entries) { const name = entry.name; if (entry.isFile() && /index\.[jt]sx?$/.test(name)) { return name; } } } const sourceUserConfig = this.userConfig.configPath ? this.resolveAbsolutePath(this.userConfig.configPath) : "none"; const sourcePackageJson = upath_1.default.join(pkg.path, "package.json"); const sourceTSConfig = ((_b = pkg.tsconfig) === null || _b === void 0 ? void 0 : _b.path) ? upath_1.default.dirname(pkg.tsconfig.path) : "none"; const exportsInConfig = (_c = this.getPackageConfig(pkg.name)) === null || _c === void 0 ? void 0 : _c.exports; const mainInConfig = (_f = (_e = (_d = this.getPackageConfig(pkg.name)) === null || _d === void 0 ? void 0 : _d.exports) === null || _e === void 0 ? void 0 : _e["."]) === null || _f === void 0 ? void 0 : _f[0]; if (mainInConfig) { exportMap.addMapping(".", (0, fileUtils_1.normalizeTrimPath)(mainInConfig), sourceUserConfig); } else { const mainEntry = await findPackageMainEntryPoint(); if (mainEntry) { exportMap.addMapping(".", mainEntry, sourcePackageJson); } } if (rootDir && outDir) { exportMap.addMapping((0, fileUtils_1.normalizeTrimPath)(upath_1.default.join(outDir, "*")), (0, fileUtils_1.normalizeTrimPath)(upath_1.default.join(rootDir, "*")), sourceTSConfig); } if (exportsInConfig) { for (const [name, entries] of Object.entries(exportsInConfig)) { exportMap.addMapping((0, fileUtils_1.normalizeTrimPath)(name), entries.map(fileUtils_1.normalizeTrimPath), sourceUserConfig); } } const pkgJsonExports = pkg.info.exports; if (pkgJsonExports && typeof pkgJsonExports === "object" && !Array.isArray(pkgJsonExports)) { for (const [originalName, originalEntry] of Object.entries(pkgJsonExports)) { const name = (0, fileUtils_1.normalizeTrimPath)(originalName); if (exportMap.hasEntry(name)) { continue; } const entries = []; if (typeof originalEntry === "string") { entries.push(originalEntry); } else if (Array.isArray(originalEntry)) { entries.push(...originalEntry); } exportMap.addMapping(name, entries.map(fileUtils_1.normalizeTrimPath), sourcePackageJson); } } return exportMap; } readDependencies(pkg) { var _a, _b, _c; const resolveDependency = (dep, version) => { // Ignore workspace protocol dependencies // We consider all packages in the monorepo as dependencies of each other if (version.startsWith("workspace:")) { return undefined; } if (version.startsWith("npm:")) { return version.replace(/^npm:/, "").replace(/@[^@]+$/, ""); } return dep; }; const resolveDependencies = (deps) => { return new Set([ ...Object.entries(deps).map(([dep, version]) => resolveDependency(dep, version)).filter((d) => Boolean(d)), ]); }; return new Set([ ...resolveDependencies((_a = pkg.dependencies) !== null && _a !== void 0 ? _a : {}), ...resolveDependencies((_b = pkg.devDependencies) !== null && _b !== void 0 ? _b : {}), ...resolveDependencies((_c = pkg.peerDependencies) !== null && _c !== void 0 ? _c : {}), ]); } async readPackages() { const { type, workspacePaths } = await ProjectSetupResolver.findWorkspaces(this.rootPackage); const pkgs = (await Promise.all(workspacePaths.map(async (p) => { if (type === MonorepoType.Nx) { const worksapacePath = this.resolveAbsolutePath(p); try { return await ProjectSetupResolver.readNxProject(this.projectRoot, worksapacePath); } catch (err) { console.log("Cannot read nx project at", worksapacePath); } } else { const jsonPath = this.resolveAbsolutePath(upath_1.default.join(p, "package.json")); if (await (0, fileUtils_1.pathExists)(jsonPath)) { try { return await ProjectSetupResolver.readPackage(jsonPath); } catch (err) { console.log("Cannot read package at", jsonPath); } } } }))).filter((p) => !!p); this.packages.push(...pkgs); // The order (exportMap -> importMap -> aliases) is important here. // It'd be better if we could remove this interdependent logic this.rootPackage.exportMap = await this.readExportMap(this.rootPackage); for (const pkg of this.packages) { pkg.exportMap = await this.readExportMap(pkg); } this.rootPackage.importMap = await this.readRootPackageImportMap(); for (const pkg of this.packages) { pkg.importMap = await this.readImportMap(pkg); } this.rootPackage.aliases = await this.readPackageAliases(this.rootPackage); for (const pkg of this.packages) { pkg.aliases = await this.readPackageAliases(pkg); } this.rootPackage.dependencies = this.readDependencies(this.rootPackage.info); for (const pkg of this.packages) { pkg.dependencies = this.readDependencies(pkg.info); } } async isNpmDependency(packageName, sourcePackage) { try { await exec(`npm ls ${packageName} --package-lock-only`, { cwd: sourcePackage.path }); logger_1.logger.debug(`Package ${packageName} is npm dependency of ${sourcePackage.name}`); return true; } catch (error) { logger_1.logger.debug(`Package ${packageName} is not npm dependency of ${sourcePackage.name} - error:${error}`); return false; } } async isYarnDependency(packageName, sourcePackage) { try { const { stderr } = await exec(`yarn why ${packageName}`, { cwd: sourcePackage.path }); // yarn v1 doesn't return exit code 1 when the package is not found // instead it prints a message to stderr const isDependency = stderr === ""; if (isDependency) { logger_1.logger.debug(`Package ${packageName} is yarn dependency of ${sourcePackage.name}`); } else { logger_1.logger.debug(`Got error message from yarn for package ${packageName}: ${stderr}`); } return isDependency; } catch (error) { logger_1.logger.debug(`Package ${packageName} is not yarn dependency of ${sourcePackage.name} - error:${error}`); return false; } } async isPnpmDependency(packageName, sourcePackage) { try { const { stdout } = await exec(`pnpm list ${packageName} --depth Infinity`, { cwd: sourcePackage.path }); const isDependency = stdout !== ""; if (isDependency) { logger_1.logger.debug(`Package ${packageName} is pnpm dependency of ${sourcePackage.name}`); } else { logger_1.logger.debug(`Package ${packageName} is not pnpm dependency of ${sourcePackage.name}`); } return isDependency; } catch (error) { logger_1.logger.debug(`Package ${packageName} is not pnpm dependency of ${sourcePackage.name} - error:${error}`); return false; } } async isValidDependency(packageName, sourcePackageName) { logger_1.logger.debug(`Checking if ${packageName} is a valid dependency for ${sourcePackageName}`); const sourcePackage = this.findPackageByName(sourcePackageName); const dependencyPackage = this.findPackageByName(packageName); if (dependencyPackage) { logger_1.logger.debug(`Found package ${packageName} in the project`); return true; } if (!sourcePackage) { logger_1.logger.debug(`Source package ${sourcePackageName} not found in the project`); return false; } if (sourcePackage.dependencies.has(packageName)) { logger_1.logger.debug(`Package ${packageName} is a direct dependency of ${sourcePackageName}`); return true; } logger_1.logger.debug(`Checking if ${packageName} is an indirect dependency of ${sourcePackageName}`); return await this.isNpmDependency(packageName, sourcePackage) || await this.isYarnDependency(packageName, sourcePackage) || await this.isPnpmDependency(packageName, sourcePackage); } async getProjectSetup(failOnError) { const redactPackage = (p) => { return { name: p.name, path: this.relativeProjectPath(p.path), version: p.version, aliases: Object.fromEntries(p.aliases.entries().map(entry => [entry.name, entry.patterns])), importMap: Object.fromEntries(p.importMap.entries().map(entry => [entry.name, entry.patterns])), exportMap: Object.fromEntries(p.exportMap.entries().map(entry => [entry.name, entry.patterns])), }; }; const setup = { root: redactPackage(this.rootPackage), packages: Object.fromEntries(this.packages.map(p => [p.name, redactPackage(p)])), absolutePath: this.rootPackage.path, isValidDependency: async (packageName, sourcePackageName) => { const result = await this.isValidDependency(packageName, sourcePackageName); if (sourcePackageName === this.rootPackage.name) { return result; } // The package could be defined in the root package // check the root package as well return result || await this.isValidDependency(packageName, this.rootPackage.name); }, }; try { logger_1.logger.debug("Validating project setup"); await (0, validator_1.validate)(setup, this); logger_1.logger.debug("Validation completed"); } catch (error) { if (error instanceof validator_1.InvalidProjectSetup) { setup.issues = error.issues; logger_1.logger.debug(`Validation issues found in the project setup: ${error.message}`); logger_1.logger.debug(`Issues: ${JSON.stringify(error.issues, null, 2)}`); if (failOnError && error.level === validator_1.ResolutionConfigIssueLevel.Error) { throw error; } } else { logger_1.logger.error("Unexpected error occured while validating project setup"); (0, logger_1.logError)(error); } } return setup; } } exports.ProjectSetupResolver = ProjectSetupResolver; function getDefaultRoot() { try { return (0, find_root_1.default)(process.cwd()); } catch (error) { return process.cwd(); } } exports.getDefaultRoot = getDefaultRoot; async function findParentProject(projectPath) { const parentPath = upath_1.default.join(projectPath, ".."); try { const parentPackage = await ProjectSetupResolver.readPackage(upath_1.default.join((0, find_root_1.default)(parentPath), "package.json")); const { workspacePaths } = await ProjectSetupResolver.findWorkspaces(parentPackage); const relativePath = upath_1.default.relative(parentPackage.path, projectPath); if (workspacePaths.includes(relativePath)) { return parentPackage.path; } return null; } catch (e) { return null; } } exports.findParentProject = findParentProject; function starPatternToGlob(pattern, matchModulePaths = true) { let globPattern; if (/\*$/.test(pattern)) { globPattern = pattern.replace("*", "**/*"); } else { globPattern = pattern.replace("*", "**"); } // Following expansion are necessary to match alias/export targets like `path/to/module`. // These paths can correspond to: // - The directory on that path // - path/to/module.[jt]sx? // - path/to/module/index.[jt]sx? if (matchModulePaths) { if (globPattern.endsWith("/index")) { return [globPattern, `${globPattern}.{tsx,ts,jsx,js}`]; } else if (!/\.[tj]sx?$/.test(globPattern)) { return [globPattern, `${globPattern}.{tsx,ts,jsx,js}`, `${globPattern}/index.{tsx,ts,jsx,js}`]; } } return [globPattern]; } exports.starPatternToGlob = starPatternToGlob;