import path from 'path'; import * as fs from 'fs'; import { EnvironmentConfig, IEnvironmentConfig } from './environment'; /** * Path type enum for different package paths */ export enum PathType { /** Root directory of the CLI workspace */ CLI_ROOT = 'CLI_ROOT', /** Private application distribution directory */ PRIVATE_APP_DIST = 'PRIVATE_APP_DIST', /** Private application root directory */ PRIVATE_APP_ROOT = 'PRIVATE_APP_ROOT', /** Public libs templates directory */ PUBLIC_LIBS_TEMPLATES = 'PUBLIC_LIBS_TEMPLATES', /** Public libs UI web directory */ PUBLIC_LIBS_UI_WEB = 'PUBLIC_LIBS_UI_WEB' } /** * Path resolver interface */ export interface IPathResolver { /** * Resolves a path based on the current environment * @param {PathType} pathType - Type of path to resolve * @returns {string} Resolved path */ resolvePath(pathType: PathType): string; /** * Gets the CLI workspace root path * @returns {string} Path to CLI workspace root */ getCliRoot(): string; /** * Gets the private application distribution directory path * @returns {string} Path to private application dist directory */ getPrivateAppDistDir(): string; /** * Gets the private application root directory path * @returns {string} Path to private application root directory */ getPrivateAppRootDir(): string; /** * Gets the public libs templates directory path * @returns {string} Path to public libs templates directory */ getPublicLibsTemplatesDir(): string; /** * Gets the public libs UI web directory path * @returns {string} Path to public libs UI web directory */ getPublicLibsUiWebDir(): string; } /** * Path resolver class for resolving paths based on environment * @class PathResolver * @implements {IPathResolver} * * @description * This class provides centralized path resolution for all packages in the CLI. * It detects the environment (development vs production) and resolves paths accordingly: * - In development: Uses workspace-relative paths * - In production: Uses require.resolve to find installed packages * * This eliminates the need for try-catch error handling scattered across the codebase. * * @example * ```typescript * const pathResolver = new PathResolver(); * const templatesDir = pathResolver.getPublicLibsTemplatesDir(); * console.log(`Templates directory: ${templatesDir}`); * ``` */ export class PathResolver implements IPathResolver { private envConfig: IEnvironmentConfig; private cliRoot: string | null = null; constructor(envConfig?: IEnvironmentConfig) { this.envConfig = envConfig || new EnvironmentConfig(); } /** * Resolves a path based on the path type and environment * @param {PathType} pathType - Type of path to resolve * @returns {string} Resolved path * @throws {Error} If path cannot be resolved */ public resolvePath(pathType: PathType): string { switch (pathType) { case PathType.CLI_ROOT: return this.getCliRoot(); case PathType.PRIVATE_APP_DIST: return this.getPrivateAppDistDir(); case PathType.PRIVATE_APP_ROOT: return this.getPrivateAppRootDir(); case PathType.PUBLIC_LIBS_TEMPLATES: return this.getPublicLibsTemplatesDir(); case PathType.PUBLIC_LIBS_UI_WEB: return this.getPublicLibsUiWebDir(); default: throw new Error(`Unknown path type: ${pathType}`); } } /** * Gets the CLI workspace root path * @returns {string} Path to CLI workspace root */ public getCliRoot(): string { if (this.cliRoot) { return this.cliRoot; } if (this.envConfig.isDevelopment()) { // Development: Find the project root by looking for package.json with workspaces this.cliRoot = this.findProjectRootWithWorkspaces(__dirname); } else { // Production: Use the package installation directory // In production, packages are installed via npm and there's no workspace structure this.cliRoot = path.resolve(__dirname, '../..'); } return this.cliRoot; } /** * Gets the private application distribution directory path * @returns {string} Path to private application dist directory */ public getPrivateAppDistDir(): string { if (this.envConfig.isDevelopment()) { // Development: Use workspace path const cliRoot = this.getCliRoot(); return path.join(cliRoot, 'packages', 'private-application', 'dist'); } else { // Production: Find the installed package try { const resolvedPath = require.resolve('@chargebee-private/chargebee-apps-private-application'); return path.dirname(resolvedPath); } catch (error) { throw new Error( 'Failed to resolve private-application path. ' + 'Ensure @chargebee-private/chargebee-apps-private-application is installed.' ); } } } /** * Gets the private application root directory path * @returns {string} Path to private application root directory */ public getPrivateAppRootDir(): string { if (this.envConfig.isDevelopment()) { // Development: Use workspace path const cliRoot = this.getCliRoot(); return path.join(cliRoot, 'packages', 'private-application'); } else { // Production: Find the installed package root try { const packageJsonPath = require.resolve('@chargebee-private/chargebee-apps-private-application/package.json'); return path.dirname(packageJsonPath); } catch (error) { throw new Error( 'Failed to resolve private-application root path. ' + 'Ensure @chargebee-private/chargebee-apps-private-application is installed.' ); } } } /** * Gets the public libs templates directory path * @returns {string} Path to public libs templates directory */ public getPublicLibsTemplatesDir(): string { if (this.envConfig.isDevelopment()) { // Development: Use workspace path const cliRoot = this.getCliRoot(); return path.join(cliRoot, 'packages', 'public-libs', 'templates'); } else { // Production: Find the installed package and get templates from package root try { const publicLibsPath = require.resolve('@testorgdb/cb-app-lib'); const packageRoot = path.dirname(path.dirname(publicLibsPath)); return path.join(packageRoot, 'templates'); } catch (error) { throw new Error( 'Failed to resolve public-libs templates path. ' + 'Ensure @testorgdb/cb-app-lib is installed.' ); } } } /** * Gets the public libs UI web directory path * @returns {string} Path to public libs UI web directory */ public getPublicLibsUiWebDir(): string { if (this.envConfig.isDevelopment()) { // Development: Use workspace path const cliRoot = this.getCliRoot(); return path.join(cliRoot, 'packages', 'public-libs', 'ui', 'web'); } else { // Production: Find the installed package and get UI from package root try { const publicLibsPath = require.resolve('@testorgdb/cb-app-lib'); const packageRoot = path.dirname(path.dirname(publicLibsPath)); return path.join(packageRoot, 'ui', 'web'); } catch (error) { throw new Error( 'Failed to resolve public-libs UI web path. ' + 'Ensure @testorgdb/cb-app-lib is installed.' ); } } } /** * Finds the project root by looking for package.json with workspaces * @param {string} startDir - Directory to start searching from * @returns {string} Path to project root * @throws {Error} If project root cannot be found * @private */ private findProjectRootWithWorkspaces(startDir: string): string { let currentDir = startDir; const root = path.parse(currentDir).root; while (currentDir !== root) { const packageJsonPath = path.join(currentDir, 'package.json'); if (fs.existsSync(packageJsonPath)) { try { const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); if (packageJson.workspaces) { return currentDir; } } catch (error) { // Continue searching if package.json is invalid } } const parent = path.dirname(currentDir); if (parent === currentDir) { break; } currentDir = parent; } throw new Error('Could not find project root with workspaces'); } }