UNPKG

ui5_easy_use

Version:

CLI tool for SAP ui5 and SAPUI5 projects to initialize apps, generate pages, insert form and table components, manage routing, and automate i18n bindings

1,750 lines (1,417 loc) 423 kB
/******/ (() => { // webpackBootstrap /******/ var __webpack_modules__ = ({ /***/ 5004 (module, __unused_webpack_exports, __webpack_require__) { "use strict"; const { StructuredLogger } = __webpack_require__(7692); class ProjectContextResolver { constructor(fileRepository, logger = new StructuredLogger(), options = {}) { if (!fileRepository) { throw new Error("ProjectContextResolver requires a fileRepository."); } this.fileRepository = fileRepository; this.logger = logger; this.routerConfigPath = options.routerConfigPath || "webapp/ez5/initapp/config.json"; this.projectRoot = null; this.webappRoot = null; this.artifactPaths = null; } load() { this.validateProjectStructure(); this.projectRoot = this.fileRepository.rootPath; this.webappRoot = this._resolveWebappRoot(); this.artifactPaths = this._buildArtifactMap(); this.logger.info("Project context loaded", { projectRoot: this.projectRoot, webappRoot: this.webappRoot, }); return { projectRoot: this.projectRoot, webappRoot: this.webappRoot, artifactPaths: this.artifactPaths, }; } validateProjectStructure() { const requiredPaths = ["webapp", "webapp/manifest.json"]; for (const requiredPath of requiredPaths) { if (!this.fileRepository.pathExists(requiredPath)) { throw new Error(`Missing required UI5 project artifact: ${requiredPath}`); } } } getProjectRoot() { if (!this.projectRoot) { this.load(); } return this.projectRoot; } getArtifactPaths() { if (!this.artifactPaths) { this.load(); } return this.artifactPaths; } _resolveWebappRoot() { return this.fileRepository.resolvePath("webapp"); } _buildArtifactMap() { const resolveArtifact = (relativePath) => this.fileRepository.resolvePath(relativePath); const i18nPrimaryPath = this._resolvePrimaryI18nPath(); return { manifestPath: resolveArtifact("webapp/manifest.json"), navListPath: resolveArtifact("webapp/model/navList.json"), rulesNavListPath: resolveArtifact("webapp/model/rulesNavList.json"), routerConfigPath: resolveArtifact(this.routerConfigPath), controllerRoot: resolveArtifact("webapp/controller"), viewRoot: resolveArtifact("webapp/view"), fragmentRoot: resolveArtifact("webapp/fragment"), modelRoot: resolveArtifact("webapp/model"), ez5Root: resolveArtifact("webapp/ez5"), i18nDirectory: resolveArtifact("webapp/i18n"), i18nPrimaryPath, }; } _resolvePrimaryI18nPath() { const candidates = [ "webapp/i18n/i18n_en.properties", "webapp/i18n/i18n.properties", "webapp/i18n/i18n_en_US.properties", "webapp/i18n/i18n_ar.properties", ]; for (const candidate of candidates) { if (this.fileRepository.pathExists(candidate)) { return this.fileRepository.resolvePath(candidate); } } if (this.fileRepository.pathExists("webapp/i18n")) { const propertiesFile = this.fileRepository .getAllFilesRecursively("webapp/i18n") .find((filePath) => filePath.endsWith(".properties")); if (propertiesFile) { return this.fileRepository.resolvePath(propertiesFile); } } return this.fileRepository.resolvePath("webapp/i18n/i18n_en.properties"); } } module.exports = { ProjectContextResolver, }; /***/ }, /***/ 4045 (module, __unused_webpack_exports, __webpack_require__) { "use strict"; const { JsonDocumentRepository } = __webpack_require__(1118); const { StructuredLogger } = __webpack_require__(7692); const DEFAULT_ROUTER_CLASS = "sap.m.routing.Router"; const SUPPORTED_ROUTER_CLASSES = new Set([ "sap.m.routing.Router", "sap.f.routing.Router", ]); class RouterConfigRepository extends JsonDocumentRepository { constructor(fileRepository, documentPath = "webapp/ez5/initapp/config.json", logger = new StructuredLogger()) { super(fileRepository, documentPath, logger); } getDefaultDocument() { return { routerClass: DEFAULT_ROUTER_CLASS, }; } getRouterMode() { const document = this.readDocument(); return this._normalizeRouterClass(document.routerClass); } setRouterMode(routerClass) { const normalizedRouterClass = this._normalizeRouterClass(routerClass); this.writeDocument({ routerClass: normalizedRouterClass, }); this.logger.info("Router mode persisted", { documentPath: this.documentPath, routerClass: normalizedRouterClass, }); return normalizedRouterClass; } _normalizeRouterClass(routerClass) { if (!routerClass) { return DEFAULT_ROUTER_CLASS; } if (!SUPPORTED_ROUTER_CLASSES.has(routerClass)) { throw new Error(`Unsupported router class: ${routerClass}`); } return routerClass; } } module.exports = { DEFAULT_ROUTER_CLASS, RouterConfigRepository, SUPPORTED_ROUTER_CLASSES, }; /***/ }, /***/ 1009 (module, __unused_webpack_exports, __webpack_require__) { "use strict"; const { StructuredLogger } = __webpack_require__(7692); class FileSelectionService { constructor(promptAdapter, fileRepository, logger = new StructuredLogger()) { if (!promptAdapter) { throw new Error("FileSelectionService requires a promptAdapter."); } if (!fileRepository) { throw new Error("FileSelectionService requires a fileRepository."); } this.promptAdapter = promptAdapter; this.fileRepository = fileRepository; this.logger = logger; } async selectPaths(basePath = "") { try { const selectedPaths = await this.selectRecursively(basePath); return this._filterSelectedFiles(selectedPaths); } catch (error) { this.logger.warn("File selection failed; returning empty selection", { basePath, error: error.message, }); return []; } } async selectRecursively(currentPath = "") { const items = this.fileRepository.listItemsWithMetadata(currentPath); if (items.length === 0) { return []; } const selectedValues = await this.promptAdapter.selectMany( items.map((item) => ({ name: item.name, value: item.path, short: item.name, })), `Select files or folders inside: ${currentPath || "root"}` ); return this._expandSelection(items, selectedValues || []); } async _expandSelection(items, selectedValues) { if (!Array.isArray(selectedValues) || selectedValues.length === 0) { return []; } const selectedFolders = selectedValues.filter((selectedValue) => ( items.some((item) => item.path === selectedValue && item.isFolder) )); const selectedFiles = selectedValues.filter((selectedValue) => ( items.some((item) => item.path === selectedValue && item.isFile) )); if (selectedValues.length === 1 && selectedFolders.length === 1) { return this.selectRecursively(selectedFolders[0]); } const resolvedPaths = [...selectedFiles]; for (const selectedFolder of selectedFolders) { resolvedPaths.push(...this.fileRepository.getAllFilesRecursively(selectedFolder)); } return resolvedPaths; } _filterSelectedFiles(selectedPaths) { return Array.from(new Set( (selectedPaths || []).filter((selectedPath) => ( Boolean(selectedPath) && this.fileRepository.pathExists(selectedPath) )) )); } } module.exports = { FileSelectionService, }; /***/ }, /***/ 2699 (module, __unused_webpack_exports, __webpack_require__) { "use strict"; const path = __webpack_require__(6928); const { StructuredLogger } = __webpack_require__(7692); class ViewTargetSelectionService { constructor(options = {}) { const { promptAdapter, fileSelectionService, projectFileRepository, xmlTransformationService, logger = new StructuredLogger(), } = options; if (!promptAdapter) { throw new Error("ViewTargetSelectionService requires a promptAdapter."); } if (!fileSelectionService) { throw new Error("ViewTargetSelectionService requires a fileSelectionService."); } if (!projectFileRepository) { throw new Error("ViewTargetSelectionService requires a projectFileRepository."); } if (!xmlTransformationService) { throw new Error("ViewTargetSelectionService requires an xmlTransformationService."); } this.promptAdapter = promptAdapter; this.fileSelectionService = fileSelectionService; this.projectFileRepository = projectFileRepository; this.xmlTransformationService = xmlTransformationService; this.logger = logger; } async selectViewMode(componentType) { return this.promptAdapter.selectOne([ { name: "Select Position", value: "select-position" }, { name: "Change View", value: "change-view" }, ], `Select target view mode for ${componentType}`); } async resolveTargetForController(controllerPath, viewMode) { const viewPath = viewMode === "change-view" ? await this.selectManualViewPath() : this.resolveMatchingViewPath(controllerPath); if (!viewPath) { this.logger.warn("Component target view could not be resolved", { controllerPath, viewMode, }); return null; } const insertionPath = await this.selectInsertionPath(viewPath); if (!insertionPath) { this.logger.warn("Component insertion path was not selected", { controllerPath, viewPath, }); return null; } return { controllerPath, insertionPath, viewPath, }; } resolveMatchingViewPath(controllerPath) { const artifactName = path .basename(controllerPath, path.extname(controllerPath)) .replace(/\.controller$/, ""); const candidatePath = `webapp/view/${artifactName}.view.xml`; return this.projectFileRepository.pathExists(candidatePath) ? candidatePath : null; } async selectManualViewPath(basePath = "webapp") { const selectedPaths = await this.fileSelectionService.selectPaths(basePath); const xmlPaths = Array.from(new Set( (selectedPaths || []).filter((selectedPath) => selectedPath.endsWith(".xml")) )); if (xmlPaths.length === 0) { return null; } if (xmlPaths.length === 1) { return xmlPaths[0]; } return this.promptAdapter.selectOne( xmlPaths.map((xmlPath) => ({ name: xmlPath, value: xmlPath, short: path.basename(xmlPath), })), "Select target view file" ); } async selectInsertionPath(viewPath) { const hierarchyOptions = this.getHierarchyOptions(viewPath); if (hierarchyOptions.length === 0) { return null; } return this.promptAdapter.selectOne( hierarchyOptions, `Select insertion position inside: ${viewPath}` ); } getHierarchyOptions(viewPath) { const xmlString = this.projectFileRepository.readText(viewPath); const document = this.xmlTransformationService.xmlDocumentAdapter.parse(xmlString); const hierarchyEntries = this.xmlTransformationService.xmlDocumentAdapter.listHierarchy(document); return hierarchyEntries.map((entry) => { const indent = " ".repeat(entry.depth); const nodeId = entry.attributes.id ? ` #${entry.attributes.id}` : ""; return { name: `${indent}${entry.name}${nodeId}`, value: entry.path, short: entry.name, }; }); } } module.exports = { ViewTargetSelectionService, }; /***/ }, /***/ 465 (module, __unused_webpack_exports, __webpack_require__) { "use strict"; const { StructuredLogger } = __webpack_require__(7692); class I18nPropertyService { constructor(fileRepository, logger = new StructuredLogger()) { if (!fileRepository) { throw new Error("I18nPropertyService requires a fileRepository."); } this.fileRepository = fileRepository; this.logger = logger; } appendMissingKeys(filePath, keyPairs, contextLabel) { const missingKeyPairs = this.filterExistingKeys(filePath, keyPairs); if (missingKeyPairs.length === 0) { return []; } const sectionHeader = this.formatSectionHeader(contextLabel); const currentContent = this.fileRepository.pathExists(filePath) ? this.fileRepository.readText(filePath) : ""; const lines = currentContent.length > 0 ? currentContent.split(/\r?\n/) : []; let headerIndex = lines.findIndex((line) => line.trim() === sectionHeader); if (headerIndex === -1) { if (lines.length > 0 && lines[lines.length - 1].trim() !== "") { lines.push(""); } lines.push(sectionHeader); headerIndex = lines.length - 1; } lines.splice(headerIndex + 1, 0, ...missingKeyPairs); const nextContent = `${lines.join("\n").replace(/\n*$/, "")}\n`; this.fileRepository.writeWithFolders(filePath, nextContent); return missingKeyPairs; } filterExistingKeys(filePath, keyPairs) { const normalizedKeyPairs = this._normalizeKeyPairs(keyPairs); if (normalizedKeyPairs.length === 0) { return []; } const existingKeys = this._readExistingKeys(filePath); const seenKeys = new Set(); return normalizedKeyPairs.filter((keyPair) => { const key = keyPair.slice(0, keyPair.indexOf("=")); if (existingKeys.has(key) || seenKeys.has(key)) { return false; } seenKeys.add(key); return true; }); } formatSectionHeader(contextLabel) { const normalizedLabel = String(contextLabel || "Generated").trim() || "Generated"; return `# ${normalizedLabel}`; } _readExistingKeys(filePath) { if (!this.fileRepository.pathExists(filePath)) { return new Set(); } const content = this.fileRepository.readText(filePath); const lines = content.split(/\r?\n/); const keys = new Set(); for (const line of lines) { const trimmedLine = line.trim(); if (!trimmedLine || trimmedLine.startsWith("#") || !trimmedLine.includes("=")) { continue; } const separatorIndex = trimmedLine.indexOf("="); const key = trimmedLine.slice(0, separatorIndex).trim(); if (key) { keys.add(key); } } return keys; } _normalizeKeyPairs(keyPairs) { const lines = Array.isArray(keyPairs) ? keyPairs : String(keyPairs || "").split(/\r?\n/); return lines .map((line) => String(line).trim()) .filter((line) => line.includes("=")) .map((line) => { const separatorIndex = line.indexOf("="); const key = line.slice(0, separatorIndex).trim(); const value = line.slice(separatorIndex + 1).trim(); return key ? `${key}=${value}` : ""; }) .filter(Boolean); } } module.exports = { I18nPropertyService, }; /***/ }, /***/ 1296 (module, __unused_webpack_exports, __webpack_require__) { "use strict"; const { StructuredLogger } = __webpack_require__(7692); const MAIN_MENU_ACTIONS = [ { name: "## Create Init App 🌟", value: "init-app" }, { name: "## Add Page 📄", value: "page-management" }, { name: "## Insert Components 📝", value: "component-insert" }, { name: "## Translate_i18n 🌐", value: "i18n-translation" }, { name: "## Import Templates 🧩", value: "template-import" }, { name: "## Exit ❌", value: "exit" }, ]; class WorkflowRegistry { constructor(logger = new StructuredLogger()) { this.logger = logger; this.workflows = new Map(); this.mainMenuActions = MAIN_MENU_ACTIONS.map((action) => ({ ...action })); } register(actionKey, workflow) { this._validateWorkflow(actionKey, workflow); this.workflows.set(actionKey, workflow); return workflow; } resolve(actionKey) { return this.workflows.get(actionKey) || null; } getMainMenuActions() { return this.mainMenuActions.filter((action) => ( action.value === "exit" || this.workflows.has(action.value) )); } _validateWorkflow(actionKey, workflow) { if (!actionKey) { throw new Error("WorkflowRegistry.register requires an actionKey."); } if (!workflow || typeof workflow.execute !== "function") { throw new Error(`WorkflowRegistry.register requires a workflow with execute(): ${actionKey}`); } } } module.exports = { WorkflowRegistry, }; /***/ }, /***/ 6552 (module, __unused_webpack_exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(9896); const path = __webpack_require__(6928); const { ProjectContextResolver } = __webpack_require__(5004); const { RouterConfigRepository } = __webpack_require__(4045); const { FileSelectionService } = __webpack_require__(1009); const { ViewTargetSelectionService } = __webpack_require__(2699); const { I18nPropertyService } = __webpack_require__(465); const { CliSessionShell } = __webpack_require__(9980); const { NavigationArtifactRepository } = __webpack_require__(5389); const { RouteRoleArtifactRepository } = __webpack_require__(5394); const { RoutingArtifactRepository } = __webpack_require__(569); const { Ui5ControllerMetadataExtractor } = __webpack_require__(3786); const { Ui5JavaScriptTransformer } = __webpack_require__(9963); const { Ui5XmlTransformationService } = __webpack_require__(8450); const { XmlDocumentAdapter } = __webpack_require__(9589); const { ComponentInsertionWorkflow } = __webpack_require__(7240); const { InitAppWorkflow } = __webpack_require__(8894); const { I18nTranslationWorkflow } = __webpack_require__(3354); const { PageManagementWorkflow } = __webpack_require__(2478); const { TemplateImportWorkflow } = __webpack_require__(4898); const { FileRepository } = __webpack_require__(5099); const { PromptAdapter } = __webpack_require__(6700); const { TemplateCatalog } = __webpack_require__(8188); const { StructuredLogger } = __webpack_require__(7692); const { WorkflowRegistry } = __webpack_require__(1296); const DEFAULT_ENVIRONMENT = "prd"; const SUPPORTED_ENVIRONMENTS = new Set(["dev", "prd"]); function createCliRuntime(options = {}) { const environmentName = normalizeEnvironmentName( options.environmentName || options.envName || process.env.EZ5_ENV ); const currentWorkingDirectory = path.resolve(options.cwd || process.cwd()); const projectRoot = path.resolve( options.projectRoot || resolveTargetProjectRoot(currentWorkingDirectory, environmentName) ); const packageRoot = options.packageRoot || resolvePackageRoot(options.runtimeDirectory || __dirname); const filesystemRoot = path.parse(projectRoot).root; const logger = options.logger || new StructuredLogger(options.loggerOptions); const promptAdapter = options.promptAdapter || new PromptAdapter({ logger, promptRuntime: options.promptRuntime, }); const projectFileRepository = new FileRepository(projectRoot, logger); const packageFileRepository = new FileRepository(packageRoot, logger); const workspaceFileRepository = new FileRepository(filesystemRoot, logger); const projectContextResolver = new ProjectContextResolver(projectFileRepository, logger); const templateCatalog = new TemplateCatalog(packageFileRepository, logger, { customTemplateRepository: projectFileRepository, }); const routerConfigRepository = new RouterConfigRepository(projectFileRepository, undefined, logger); const routingArtifactRepository = new RoutingArtifactRepository(projectFileRepository, undefined, logger); const navigationArtifactRepository = new NavigationArtifactRepository(projectFileRepository, undefined, logger); const routeRoleArtifactRepository = new RouteRoleArtifactRepository(projectFileRepository, undefined, logger); const fileSelectionService = new FileSelectionService(promptAdapter, projectFileRepository, logger); const metadataExtractor = new Ui5ControllerMetadataExtractor(workspaceFileRepository, logger); const javaScriptTransformer = new Ui5JavaScriptTransformer(workspaceFileRepository, logger, metadataExtractor); const xmlTransformationService = new Ui5XmlTransformationService(new XmlDocumentAdapter(logger), logger); const viewTargetSelectionService = new ViewTargetSelectionService({ promptAdapter, fileSelectionService, projectFileRepository, xmlTransformationService, logger, }); const i18nPropertyService = new I18nPropertyService(projectFileRepository, logger); const initAppWorkflow = new InitAppWorkflow({ promptAdapter, projectContextResolver, templateCatalog, projectFileRepository, workspaceFileRepository, routerConfigRepository, routingArtifactRepository, javaScriptTransformer, xmlTransformationService, logger, }); const pageManagementWorkflow = new PageManagementWorkflow({ promptAdapter, projectContextResolver, templateCatalog, projectFileRepository, workspaceFileRepository, javaScriptTransformer, routerConfigRepository, routingArtifactRepository, navigationArtifactRepository, routeRoleArtifactRepository, logger, }); const componentInsertionWorkflow = new ComponentInsertionWorkflow({ promptAdapter, fileSelectionService, viewTargetSelectionService, templateCatalog, projectContextResolver, projectFileRepository, workspaceFileRepository, javaScriptTransformer, metadataExtractor, xmlTransformationService, logger, }); const i18nTranslationWorkflow = new I18nTranslationWorkflow({ promptAdapter, fileSelectionService, templateCatalog, projectContextResolver, projectFileRepository, xmlTransformationService, i18nPropertyService, logger, }); const templateImportWorkflow = new TemplateImportWorkflow({ promptAdapter, projectContextResolver, templateCatalog, projectFileRepository, logger, }); const workflowRegistry = new WorkflowRegistry(logger); workflowRegistry.register("init-app", initAppWorkflow); workflowRegistry.register("page-management", pageManagementWorkflow); workflowRegistry.register("component-insert", componentInsertionWorkflow); workflowRegistry.register("i18n-translation", i18nTranslationWorkflow); workflowRegistry.register("template-import", templateImportWorkflow); const cliSessionShell = new CliSessionShell(promptAdapter, workflowRegistry, logger); return { cliSessionShell, environmentName, logger, paths: { sourcePath: packageRoot, targetPath: projectRoot, }, promptAdapter, projectContextResolver, workflowRegistry, workflows: { componentInsertionWorkflow, i18nTranslationWorkflow, initAppWorkflow, pageManagementWorkflow, templateImportWorkflow, }, services: { fileSelectionService, i18nPropertyService, javaScriptTransformer, metadataExtractor, navigationArtifactRepository, projectFileRepository, routeRoleArtifactRepository, routerConfigRepository, routingArtifactRepository, templateCatalog, viewTargetSelectionService, workspaceFileRepository, xmlTransformationService, }, }; } function resolveTargetProjectRoot(currentWorkingDirectory, environmentName) { if (environmentName === "dev") { return path.resolve(currentWorkingDirectory, "../projectxxtest4"); } return currentWorkingDirectory; } function normalizeEnvironmentName(environmentName) { const normalizedEnvironmentName = String(environmentName || DEFAULT_ENVIRONMENT).trim().toLowerCase(); return SUPPORTED_ENVIRONMENTS.has(normalizedEnvironmentName) ? normalizedEnvironmentName : DEFAULT_ENVIRONMENT; } function resolvePackageRoot(runtimeDirectory) { const runtimeRoot = path.resolve(runtimeDirectory || __dirname); const candidateRoots = [ path.resolve(runtimeRoot, "..", "..", ".."), runtimeRoot, ]; for (const candidateRoot of candidateRoots) { if (fs.existsSync(path.join(candidateRoot, "ez5", "templates"))) { return candidateRoot; } } return candidateRoots[0]; } module.exports = { createCliRuntime, DEFAULT_ENVIRONMENT, SUPPORTED_ENVIRONMENTS, resolvePackageRoot, }; /***/ }, /***/ 9980 (module, __unused_webpack_exports, __webpack_require__) { "use strict"; const { StructuredLogger } = __webpack_require__(7692); class CliSessionShell { constructor(promptAdapter, workflowRegistry, logger = new StructuredLogger()) { if (!promptAdapter) { throw new Error("CliSessionShell requires a promptAdapter."); } if (!workflowRegistry) { throw new Error("CliSessionShell requires a workflowRegistry."); } this.promptAdapter = promptAdapter; this.workflowRegistry = workflowRegistry; this.logger = logger; this.navigationState = "main"; this.isPromptActive = false; } async start() { try { while (this.navigationState !== "exit") { await this.showMainMenu(); } } catch (error) { await this._handleWorkflowError("start", error); throw error; } } async showMainMenu() { this.navigationState = "main"; const actionKey = await this._selectOne( this.workflowRegistry.getMainMenuActions(), "\n-----------\nSelect your main action:" ); if (!actionKey) { this.exit(); return; } await this.navigate(actionKey); } async navigate(actionKey) { const resolvedAction = this._resolveAction(actionKey); if (!resolvedAction) { this.logger.error("Unknown CLI action selected", { actionKey }); this.returnToMainMenu(); return; } try { await resolvedAction(); } catch (error) { await this._handleWorkflowError(actionKey, error); this.returnToMainMenu(); } } returnToMainMenu() { this.navigationState = "main"; } exit() { this.navigationState = "exit"; } _resolveAction(actionKey) { if (actionKey === "exit") { return async () => { this.exit(); }; } if (actionKey === "init-app") { return () => this._showInitAppMenu(); } if (actionKey === "component-insert") { return () => this._showComponentMenu(); } if (actionKey === "i18n-translation") { return () => this._showI18nMenu(); } return () => this._executeRegisteredWorkflow(actionKey); } async _executeRegisteredWorkflow(actionKey, ...args) { const workflow = this.workflowRegistry.resolve(actionKey); if (!workflow) { this.logger.error("No workflow registered for action", { actionKey }); return; } await workflow.execute(...args); this.returnToMainMenu(); } async _showInitAppMenu() { this.navigationState = "init-app"; const selectedAction = await this._selectOne([ { name: "Full Features (Full Project)", value: "full-project" }, { name: "Back 🔙", value: "back" }, { name: "Exit ❌", value: "exit" }, ], "\n-----------\nSelect Action"); if (selectedAction === "exit") { this.exit(); return; } if (!selectedAction || selectedAction === "back") { this.returnToMainMenu(); return; } await this._executeRegisteredWorkflow("init-app"); } async _showComponentMenu() { this.navigationState = "component-insert"; const selectedComponentType = await this._selectOne([ { name: "📄 Form Menu:", value: "form" }, { name: "📊 Table Menu:", value: "table" }, { name: "📈 Chart Menu:", value: "chart" }, { name: "Back 🔙", value: "back" }, { name: "Exit ❌", value: "exit" }, ], "\n-----------\nSelect Action"); if (selectedComponentType === "exit") { this.exit(); return; } if (!selectedComponentType || selectedComponentType === "back") { this.returnToMainMenu(); return; } await this._executeRegisteredWorkflow("component-insert", selectedComponentType); } async _showI18nMenu() { this.navigationState = "i18n-translation"; const selectedAction = await this._selectOne([ { name: "Auto Binding", value: "auto-binding" }, { name: "Back 🔙", value: "back" }, { name: "Exit ❌", value: "exit" }, ], "\n-----------\nSelect Action:"); if (selectedAction === "exit") { this.exit(); return; } if (!selectedAction || selectedAction === "back") { this.returnToMainMenu(); return; } await this._executeRegisteredWorkflow("i18n-translation"); } async _handleWorkflowError(actionKey, error) { this.logger.error("CLI workflow execution failed", { actionKey, error: error?.message || String(error), }); } async _selectOne(options, message) { this.isPromptActive = true; try { return await this.promptAdapter.selectOne(options, message); } finally { this.isPromptActive = false; } } } module.exports = { CliSessionShell, }; /***/ }, /***/ 1118 (module, __unused_webpack_exports, __webpack_require__) { "use strict"; const { StructuredLogger } = __webpack_require__(7692); class JsonDocumentRepository { constructor(fileRepository, documentPath, logger = new StructuredLogger()) { if (!fileRepository) { throw new Error("JsonDocumentRepository requires a fileRepository."); } if (!documentPath) { throw new Error("JsonDocumentRepository requires a documentPath."); } this.fileRepository = fileRepository; this.documentPath = documentPath; this.logger = logger; } getDefaultDocument() { return {}; } readDocument() { if (!this.fileRepository.pathExists(this.documentPath)) { return this._clone(this.getDefaultDocument()); } const document = this.fileRepository.readJson(this.documentPath); if (!document || typeof document !== "object" || Array.isArray(document)) { throw new Error(`Invalid JSON document shape: ${this.documentPath}`); } return document; } writeDocument(document) { if (!document || typeof document !== "object" || Array.isArray(document)) { throw new Error(`Cannot persist invalid JSON document: ${this.documentPath}`); } this.fileRepository.writeJson(this.documentPath, document); return document; } updateDocument(mutator) { const document = this.readDocument(); const result = typeof mutator === "function" ? mutator(document) : document; return this.writeDocument(result === undefined ? document : result); } _clone(value) { return JSON.parse(JSON.stringify(value)); } } module.exports = { JsonDocumentRepository, }; /***/ }, /***/ 5389 (module, __unused_webpack_exports, __webpack_require__) { "use strict"; const { JsonDocumentRepository } = __webpack_require__(1118); class NavigationArtifactRepository extends JsonDocumentRepository { constructor(fileRepository, documentPath = "webapp/model/navList.json", logger) { super(fileRepository, documentPath, logger); } getDefaultDocument() { return { navigation: [], additionalNavigation: [], }; } getEntries() { const document = this.readDocument(); document.navigation = Array.isArray(document.navigation) ? document.navigation : []; return document.navigation; } upsertEntry(pageName, icon = "sap-icon://settings") { return this.updateDocument((document) => { document.navigation = Array.isArray(document.navigation) ? document.navigation : []; document.additionalNavigation = Array.isArray(document.additionalNavigation) ? document.additionalNavigation : []; const key = `Route${pageName}`; const existingIndex = document.navigation.findIndex((entry) => entry.key === key); const nextEntry = { title: pageName, icon, key, }; if (existingIndex === -1) { document.navigation.push(nextEntry); } else { document.navigation[existingIndex] = { ...document.navigation[existingIndex], ...nextEntry, }; } return document; }); } clearEntries() { return this.writeDocument(this.getDefaultDocument()); } } module.exports = { NavigationArtifactRepository, }; /***/ }, /***/ 5394 (module, __unused_webpack_exports, __webpack_require__) { "use strict"; const { JsonDocumentRepository } = __webpack_require__(1118); class RouteRoleArtifactRepository extends JsonDocumentRepository { constructor(fileRepository, documentPath = "webapp/model/rulesNavList.json", logger) { super(fileRepository, documentPath, logger); } getDefaultDocument() { return { roles: {}, }; } getRoles(routeKey) { const document = this.readDocument(); document.roles = document.roles || {}; return document.roles[routeKey] || []; } addRole(routeKey, role) { if (!routeKey || !role) { throw new Error("addRole requires both routeKey and role."); } return this.updateDocument((document) => { document.roles = document.roles || {}; document.roles[routeKey] = Array.isArray(document.roles[routeKey]) ? document.roles[routeKey] : []; if (!document.roles[routeKey].includes(role)) { document.roles[routeKey].push(role); } return document; }); } setRoles(routeKey, roles) { return this.updateDocument((document) => { document.roles = document.roles || {}; document.roles[routeKey] = Array.from(new Set(Array.isArray(roles) ? roles : [])); return document; }); } clearRoles(routeKey) { return this.updateDocument((document) => { document.roles = document.roles || {}; delete document.roles[routeKey]; return document; }); } clearAll() { return this.writeDocument(this.getDefaultDocument()); } } module.exports = { RouteRoleArtifactRepository, }; /***/ }, /***/ 569 (module, __unused_webpack_exports, __webpack_require__) { "use strict"; const { JsonDocumentRepository } = __webpack_require__(1118); class RoutingArtifactRepository extends JsonDocumentRepository { constructor(fileRepository, documentPath = "webapp/manifest.json", logger) { super(fileRepository, documentPath, logger); } getDefaultDocument() { return { "sap.app": { id: "", }, "sap.ui5": { routing: { config: { routerClass: "sap.m.routing.Router", controlAggregation: "pages", }, routes: [], targets: {}, }, }, }; } getAppName() { const document = this.readDocument(); return document["sap.app"]?.id || ""; } configureRouter(routerClass) { return this.updateDocument((document) => { const routing = this._ensureRoutingStructures(document); routing.config.routerClass = this._normalizeRouterClass(routerClass); routing.config.controlAggregation = routing.config.routerClass === "sap.f.routing.Router" ? "beginColumnPages" : "pages"; this._syncRootViewId(document, routing.config.routerClass); return document; }); } clearRoutesAndTargets() { return this.updateDocument((document) => { const routing = this._ensureRoutingStructures(document); routing.routes = []; routing.targets = {}; return document; }); } upsertPageRoute(pageName, routeArgs = {}) { if (!pageName) { throw new Error("upsertPageRoute requires a pageName."); } return this.updateDocument((document) => { const routing = this._ensureRoutingStructures(document); const routerClass = this._normalizeRouterClass(routeArgs.routerClass || routeArgs.type); routing.config.routerClass = routerClass; routing.config.controlAggregation = routeArgs.controlAggregation || (routerClass === "sap.f.routing.Router" ? "beginColumnPages" : "pages"); this._syncRootViewId(document, routerClass); const routeDefinition = this._buildRouteDefinition(pageName, routeArgs); const routeIndex = routing.routes.findIndex((route) => route.name === routeDefinition.name); if (routeIndex === -1) { routing.routes.push(routeDefinition); } else { routing.routes[routeIndex] = routeDefinition; } const targetDefinitions = this._buildTargetDefinition(pageName, routeArgs, routerClass); routing.targets = routing.targets || {}; Object.assign(routing.targets, targetDefinitions); return document; }); } _buildRouteDefinition(pageName, routeArgs) { const explicitTargetOrder = Array.isArray(routeArgs.targetsOrdered) ? routeArgs.targetsOrdered : null; const additionalTargets = Array.isArray(routeArgs.targets) ? routeArgs.targets : []; const targetList = explicitTargetOrder ? Array.from(new Set(explicitTargetOrder)) : Array.from(new Set([`Target${pageName}`, ...additionalTargets])); return { name: routeArgs.routeName || `Route${pageName}`, pattern: routeArgs.pattern !== undefined ? routeArgs.pattern : (pageName === "Home" ? "" : `Route${pageName}${routeArgs.parameters || routeArgs.parms || ""}`), target: targetList, }; } _buildTargetDefinition(pageName, routeArgs, routerClass) { if (routeArgs.targetDefinitions && typeof routeArgs.targetDefinitions === "object") { return routeArgs.targetDefinitions; } const targetDefinition = { viewType: "XML", transition: "slide", clearControlAggregation: false, viewId: pageName, viewName: pageName, }; if (routerClass === "sap.f.routing.Router" || routeArgs.controlAggregation) { targetDefinition.controlAggregation = routeArgs.controlAggregation || "beginColumnPages"; } return { [`Target${pageName}`]: targetDefinition, }; } _ensureRoutingStructures(document) { document["sap.app"] = document["sap.app"] || { id: "" }; document["sap.ui5"] = document["sap.ui5"] || {}; document["sap.ui5"].routing = document["sap.ui5"].routing || {}; document["sap.ui5"].routing.config = document["sap.ui5"].routing.config || {}; document["sap.ui5"].routing.routes = document["sap.ui5"].routing.routes || []; document["sap.ui5"].routing.targets = document["sap.ui5"].routing.targets || {}; return document["sap.ui5"].routing; } _syncRootViewId(document, routerClass) { const rootView = document["sap.ui5"]?.rootView; if (!rootView || typeof rootView !== "object") { return; } if (routerClass === "sap.f.routing.Router") { delete rootView.id; } } _normalizeRouterClass(routerClass) { if (!routerClass) { return "sap.m.routing.Router"; } if (routerClass !== "sap.m.routing.Router" && routerClass !== "sap.f.routing.Router") { throw new Error(`Unsupported router class: ${routerClass}`); } return routerClass; } } module.exports = { RoutingArtifactRepository, }; /***/ }, /***/ 3786 (module, __unused_webpack_exports, __webpack_require__) { "use strict"; const acorn = __webpack_require__(8630); const walk = __webpack_require__(2268); const { StructuredLogger } = __webpack_require__(7692); class Ui5ControllerMetadataExtractor { constructor(fileRepository, logger = new StructuredLogger(), parserAdapter = acorn) { if (!fileRepository) { throw new Error("Ui5ControllerMetadataExtractor requires a fileRepository."); } this.fileRepository = fileRepository; this.logger = logger; this.parserAdapter = parserAdapter; } extractVariableFromFunction(controllerPath, functionName = "initialForm", variableName = "this.autoG") { const content = this.fileRepository.readText(controllerPath); try { const ast = this._parseModule(content); const functionNode = this._findFunctionNode(ast, functionName); if (functionNode) { return this._extractDeclarations(functionNode, content, variableName); } } catch (error) { this.logger.debug("Falling back to regex metadata extraction", { controllerPath, functionName, error: error.message, }); } return this._extractVariableFromFunctionText(content, functionName, variableName); } extractAutoG(controllerPath, functionName = "initialForm") { const rawValue = this.extractVariableFromFunction(controllerPath, functionName, "this.autoG"); return rawValue ? this.parseAutoG(rawValue) : null; } parseAutoG(rawValue) { if (rawValue == null) { return null; } if (Array.isArray(rawValue) || (typeof rawValue === "object" && rawValue !== null)) { return rawValue; } if (typeof rawValue !== "string") { return null; } try { return Function(`"use strict"; return (${rawValue});`)(); } catch (error) { this.logger.warn("Failed to parse autoG value", { error: error.message }); return null; } } _parseModule(content) { try { return this.parserAdapter.parse(content, { ecmaVersion: "latest", sourceType: "script", }); } catch (error) { return this.parserAdapter.parse(content, { ecmaVersion: "latest", sourceType: "module", }); } } _findFunctionNode(ast, functionName) { let foundNode = null; walk.simple(ast, { MethodDefinition: (node) => { if (foundNode) { return; } const methodName = node.key?.name || node.key?.value; if (methodName === functionName && node.value) { foundNode = node.value; } }, CallExpression: (node) => { if (foundNode) { return; } if (node.callee?.type !== "MemberExpression" || node.callee.property?.name !== "extend") { return; } const objectExpression = node.arguments[1]; if (!objectExpression || objectExpression.type !== "ObjectExpression") { return; } for (const property of objectExpression.properties) { const propertyName = property.key?.name || property.key?.value; if ( property.type === "Property" && propertyName === functionName && ( property.value.type === "FunctionExpression" || property.value.type === "ArrowFunctionExpression" ) ) { foundNode = property.value; return; } } }, }); return foundNode; } _extractDeclarations(functionNode, content, variableName) { const statements = functionNode.body?.body || []; const isThisAssignment = variableName.startsWith("this."); const targetPropertyName = isThisAssignment ? variableName.slice(5) : variableName; for (const statement of statements) { if ( isThisAssignment && statement.type === "ExpressionStatement" && statement.expression.type === "AssignmentExpression" && statement.expression.left.type === "MemberExpression" && statement.expression.left.object.type === "ThisExpression" && statement.expression.left.property?.name === targetPropertyName ) { return content.slice(statement.expression.right.start, statement.expression.right.end); } if (statement.type === "VariableDeclaration") { for (const declaration of statement.declarations) { if (declaration.id?.name === targetPropertyName && declaration.init) { return content.slice(declaration.init.start, declaration.init.end); } } } } return null; } _extractVariableFromFunctionText(content, functionName, variableName) { const functionBody = this._extractFunctionTextBody(content, functionName); if (!functionBody) { return null; } const targetName = variableName.startsWith("this.") ? variableName.slice(5) : variableName; const assignmentMatch = functionBody.match(new RegExp(`\\bthis\\.${this._escapeRegExp(targetName)}\\s*=`)); if (!assignmentMatch) { return null; } const expressionStart = assignmentMatch.index + assignmentMatch[0].length; return this._readExpressionUntilSemicolon(functionBody, expressionStart); } _extractFunctionTextBody(content, functionName) { const functionPattern = new RegExp(`(?:${this._escapeRegExp(functionName)}\\s*:\\s*(?:async\\s*)?function|(?:async\\s+)?${this._escapeRegExp(functionName)})\\s*\\([^)]*\\)\\s*(?::\\s*[^\\{]+)?\\{`); const match = content.match(functionPattern); if (!match) { return ""; } const openingBrace = match.index + match[0].lastIndexOf("{"); const closingBrace = this._findMatchingBrace(content, openingBrace); if (closingBrace === -1) { return ""; } return content.slice(openingBrace + 1, closingBrace); } _readExpressionUntilSemicolon(content, expressionStart) { let quote = null; let escaped = false; let squareDepth = 0; let curlyDepth = 0; let parenDepth = 0; for (let index = expressionStart; index < content.length; index++) { const character = content[index]; if (quote) { if (escaped) { escaped = false; } else if (character === "\\") { escaped = true; } else if (character === quote) { quote = null; } continue; } if (character === "\"" || character === "'" || character === "`") { quote = character; continue; } if (character === "[") { squareDepth += 1; } else if (character === "]") { squareDepth -= 1; } else if (character === "{") { curlyDepth += 1; } else if (character === "}") { curlyDepth -= 1; } else if (character === "(") { parenDepth += 1; } else if (character === ")") { parenDepth -= 1; } else if (character === ";" && squareDepth === 0 && curlyDepth === 0 && parenDepth === 0) { return content.slice(expressionStart, index).trim(); } } return content.slice(expressionStart).trim(); } _findMatchingBrace(content, openingBraceIndex) { let depth = 0; let quote = null; let escaped = false; for (let index = openingBraceIndex; index < content.length; index++) { const character = content[index]; if (quote) { if (escaped) { escaped = false; } else if (character === "\\") { escaped = true; } else if (character === quote) { quote = null; } continue; } if (character === "\"" || character === "'" || character === "`") { quote = character; continue; } if (character === "{") { depth += 1; } else if (character === "}") { depth -= 1; if (depth === 0) { return index; } } } return -1; } _escapeRegExp(value) { return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } } module.exports = { Ui5ControllerMetadataExtractor, }; /***/ }, /***/ 9963 (module, __unused_webpack_exports, __webpack_require__) { "use strict"; const acorn = __webpack_require__(8630); const walk = __webpack_require__(2268); const { StructuredLogger } = __webpack_require__(7692); const { Ui5ControllerMetadataExtractor } = __webpack_require__(3786); class Ui5JavaScriptTransformer { constructor(fileRepository, logger = new StructuredLogger(), metadataExtractor = null) { if (!fileRepository) { throw new Error("Ui5JavaScriptTransformer requires a fileRepository."); } this.fileRepository = fileRepository; this.logger = logger; this.metadataExtractor = metadataExtractor || new Ui5ControllerMetadataExtractor(fileRepository, logger); } syncTemplate(sourceTemplatePath, destinationPath, placeholderMap = {}) { const { sourceContent, destinationContent } = this._readArtifacts(sourceTemplatePath, destinationPath); const resolvedSourceContent = this._replacePlaceholders(sourceContent, placeholderMap); if (this._isTypeScriptController(destinationPath, destinationContent)) { const updatedContent = this.syncTemplateIntoTypeScriptController(resolvedSourceContent, destinationContent); this.updateDestination(destinationPath, updatedContent); return updatedContent; } let updatedContent = this.mergeDependencies(resolvedSourceContent, destinationContent); updatedContent = this.mergeFunctions(resolvedSourceContent, updatedContent); this.updateDestination(destinationPath, updatedContent); return updatedContent; } syncTemplateIntoTypeScriptController(sourceContent, destinationContent) { const sourceSignature = this._extractDefineSignature(sourceContent); const sourceFunctions = this._extractFunctionProperties(sourceContent); const classRange = this._findTypeScriptControllerClassRange(destinationContent); if (!classRange) { throw new Error( "TypeScript controller insertion requires a class-based controller that extends Controller." ); } let nextContent = this._mergeTypeScriptImports(destinationContent, sourceSignature); nextContent = this._mergeTypeScriptClassMembers(nextContent, sourceFunctions); return this._ensureTypeScriptNoCheck(nextContent); } mergeDependencies(sourceContent, destinationContent) { const sourceSignature = this._extractDefineSignature(sourceContent); const destinationSignature = this._extractDefineSignature(destinationContent); if (!sourceSignature || !destinationSignature) { return destinationContent; } const mergedSignature = this._mergeDefineSignatures(destinationSignature, sourceSignature); return this._replaceDefineSignature(destinationContent, mergedSignature.dependencies, mergedSignature.params); } mergeFunctions(sourceContent, destinationContent) { const sourceFunctions = this._extractFunctionProperties(sourceContent); const destinationFunctions = this._e