UNPKG

sfcc-dev-mcp

Version:

MCP server for Salesforce B2C Commerce Cloud development assistance including logs, debugging, and development tools

384 lines (382 loc) 15.7 kB
/** * SFCC Cartridge Generation Client * * This client handles the generation of SFCC cartridge directory structures * with all necessary files and configurations, replacing the outdated sgmf-scripts * with a modern, integrated approach. */ import { Logger } from '../utils/logger.js'; export class CartridgeGenerationClient { logger; templates; fileSystem; pathService; constructor(fileSystem, pathService) { this.logger = Logger.getChildLogger('CartridgeGenerationClient'); this.fileSystem = fileSystem; this.pathService = pathService; this.templates = this.initializeTemplates(); } /** * Normalize the target path by removing /cartridges or /cartridges/ from the end * The cartridge creation always happens from the root folder */ normalizeTargetPath(targetPath) { // Remove trailing slashes first let normalized = targetPath.replace(/\/+$/, ''); // Remove /cartridges from the end if present if (normalized.endsWith('/cartridges')) { normalized = normalized.slice(0, -11); // Remove '/cartridges' (11 characters) } this.logger.debug(`Normalized target path from '${targetPath}' to '${normalized}'`); return normalized; } /** * Generate a complete cartridge structure */ async generateCartridgeStructure(options) { const { cartridgeName, targetPath, fullProjectSetup = true } = options; const createdFiles = []; const createdDirectories = []; const skippedFiles = []; try { this.logger.info(`Starting cartridge generation for: ${cartridgeName}`); // Determine the working directory and normalize path let workingDir = targetPath ?? process.cwd(); workingDir = this.normalizeTargetPath(workingDir); if (fullProjectSetup) { // Full project setup - create everything directly in the working directory this.logger.info(`Creating full project setup directly in: ${workingDir}`); // Ensure the working directory exists await this.ensureDirectory(workingDir); if (!createdDirectories.includes(workingDir)) { createdDirectories.push(workingDir); } // Create root files directly in working directory await this.createRootFiles(workingDir, cartridgeName, createdFiles, skippedFiles); // Create cartridge structure directly in working directory await this.createCartridgeStructure(workingDir, cartridgeName, createdFiles, createdDirectories, skippedFiles); return { success: true, message: `Successfully created full project setup for cartridge '${cartridgeName}' in '${workingDir}'`, createdFiles, createdDirectories, skippedFiles, }; } else { // Cartridge-only setup - add to existing project const cartridgesDir = this.pathService.join(workingDir, 'cartridges'); // Ensure cartridges directory exists await this.ensureDirectory(cartridgesDir); if (!createdDirectories.includes(cartridgesDir)) { createdDirectories.push(cartridgesDir); } // Create cartridge structure await this.createCartridgeStructure(workingDir, cartridgeName, createdFiles, createdDirectories, skippedFiles); return { success: true, message: `Successfully created cartridge '${cartridgeName}' in existing project at '${workingDir}'`, createdFiles, createdDirectories, skippedFiles, }; } } catch (error) { this.logger.error('Error generating cartridge structure:', error); return { success: false, message: `Failed to generate cartridge structure: ${error instanceof Error ? error.message : 'Unknown error'}`, createdFiles, createdDirectories, skippedFiles, }; } } /** * Create root project files (package.json, webpack, etc.) */ async createRootFiles(projectDir, cartridgeName, createdFiles, skippedFiles) { // Create package.json const packageJsonPath = this.pathService.join(projectDir, 'package.json'); await this.safeWriteFile(packageJsonPath, JSON.stringify(this.templates.packageJson(cartridgeName), null, 2), createdFiles, skippedFiles); // Create dw.json const dwJsonPath = this.pathService.join(projectDir, 'dw.json'); await this.safeWriteFile(dwJsonPath, JSON.stringify(this.templates.dwJson(), null, 2), createdFiles, skippedFiles); // Create webpack.config.js const webpackPath = this.pathService.join(projectDir, 'webpack.config.js'); await this.safeWriteFile(webpackPath, this.templates.webpackConfig(cartridgeName), createdFiles, skippedFiles); // Create .eslintrc.json const eslintrcPath = this.pathService.join(projectDir, '.eslintrc.json'); await this.safeWriteFile(eslintrcPath, JSON.stringify(this.templates.eslintrc(), null, 2), createdFiles, skippedFiles); // Create .stylelintrc.json const stylelintrcPath = this.pathService.join(projectDir, '.stylelintrc.json'); await this.safeWriteFile(stylelintrcPath, JSON.stringify(this.templates.stylelintrc(), null, 2), createdFiles, skippedFiles); // Create .eslintignore const eslintignorePath = this.pathService.join(projectDir, '.eslintignore'); await this.safeWriteFile(eslintignorePath, this.templates.eslintignore(), createdFiles, skippedFiles); // Create .gitignore const gitignorePath = this.pathService.join(projectDir, '.gitignore'); await this.safeWriteFile(gitignorePath, this.templates.gitignore(), createdFiles, skippedFiles); } /** * Create the cartridge directory structure */ async createCartridgeStructure(baseDir, cartridgeName, createdFiles, createdDirectories, skippedFiles) { // Create cartridges directory const cartridgesDir = this.pathService.join(baseDir, 'cartridges'); await this.ensureDirectory(cartridgesDir); createdDirectories.push(cartridgesDir); // Create specific cartridge directory const cartridgeDir = this.pathService.join(cartridgesDir, cartridgeName); await this.ensureDirectory(cartridgeDir); createdDirectories.push(cartridgeDir); // Create .project file const projectPath = this.pathService.join(cartridgeDir, '.project'); await this.safeWriteFile(projectPath, this.templates.dotProject(cartridgeName), createdFiles, skippedFiles); // Create cartridge subdirectory const cartridgeSubDir = this.pathService.join(cartridgeDir, 'cartridge'); await this.ensureDirectory(cartridgeSubDir); createdDirectories.push(cartridgeSubDir); // Create cartridge properties file const propertiesPath = this.pathService.join(cartridgeSubDir, `${cartridgeName}.properties`); await this.safeWriteFile(propertiesPath, this.templates.projectProperties(cartridgeName), createdFiles, skippedFiles); // Create directory structure const directories = [ 'controllers', 'models', 'templates', 'templates/default', 'templates/resources', 'client', 'client/default', 'client/default/js', 'client/default/scss', ]; for (const dir of directories) { const fullPath = this.pathService.join(cartridgeSubDir, dir); await this.ensureDirectory(fullPath); createdDirectories.push(fullPath); } } /** * Ensure a directory exists, create if it doesn't */ async ensureDirectory(dirPath) { try { await this.fileSystem.access(dirPath); } catch { await this.fileSystem.mkdir(dirPath, { recursive: true }); this.logger.info(`Created directory: ${dirPath}`); } } /** * Safely write a file, skipping if it already exists */ async safeWriteFile(filePath, content, createdFiles, skippedFiles) { try { await this.fileSystem.access(filePath); // File exists, skip it skippedFiles.push(filePath); this.logger.info(`Skipped existing file: ${filePath}`); } catch { // File doesn't exist, create it await this.fileSystem.writeFile(filePath, content); createdFiles.push(filePath); this.logger.info(`Created file: ${filePath}`); } } /** * Initialize all file templates */ initializeTemplates() { return { packageJson: (cartridgeName) => ({ name: cartridgeName, version: '0.0.1', description: 'New overlay cartridge', main: 'index.js', scripts: { 'lint': 'npm run lint:css && npm run lint:js', 'lint:css': 'sgmf-scripts --lint css', 'lint:js': 'sgmf-scripts --lint js', 'lint:fix': 'eslint cartridges --fix', upload: 'sgmf-scripts --upload -- ', uploadCartridge: `sgmf-scripts --uploadCartridge ${cartridgeName}`, 'compile:js': 'sgmf-scripts --compile js', 'compile:scss': 'sgmf-scripts --compile css', }, devDependencies: { autoprefixer: '^10.4.14', bestzip: '^2.2.1', 'css-loader': '^6.0.0', 'css-minimizer-webpack-plugin': '^5.0.1', eslint: '^8.56.0', 'eslint-config-airbnb-base': '^15.0.0', 'eslint-config-prettier': '^9.1.0', 'eslint-plugin-import': '^2.29.0', 'mini-css-extract-plugin': '^2.7.6', 'postcss-loader': '^7.0.0', sass: '^1.69.7', 'sass-loader': '^13.3.2', 'sgmf-scripts': '^3.0.0', shx: '^0.3.4', stylelint: '^15.4.0', 'stylelint-config-standard-scss': '^11.0.0', 'webpack-remove-empty-scripts': '^1.0.4', }, browserslist: [ 'last 2 versions', 'ie >= 10', ], }), dwJson: () => ({ hostname: '', username: '', password: '', 'code-version': '', }), webpackConfig: (cartridgeName) => `'use strict'; var path = require('path'); var MiniCssExtractPlugin = require('mini-css-extract-plugin'); var CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); var sgmfScripts = require('sgmf-scripts'); var RemoveEmptyScriptsPlugin = require('webpack-remove-empty-scripts'); module.exports = [{ mode: 'development', name: 'js', entry: sgmfScripts.createJsPath(), output: { path: path.resolve('./cartridges/${cartridgeName}/cartridge/static'), filename: '[name].js' } }, { mode: 'none', name: 'scss', entry: sgmfScripts.createScssPath(), output: { path: path.resolve('./cartridges/${cartridgeName}/cartridge/static') }, module: { rules: [{ test: /\\.scss$/, use: [{ loader: MiniCssExtractPlugin.loader, options: { esModule: false } }, { loader: 'css-loader', options: { url: false } }, { loader: 'postcss-loader', options: { postcssOptions: { plugins: [require('autoprefixer')] } } }, { loader: 'sass-loader', options: { implementation: require('sass'), sassOptions: { includePaths: [ path.resolve(path.resolve(process.cwd(), '../storefront-reference-architecture/node_modules/')), path.resolve(process.cwd(), '../storefront-reference-architecture/node_modules/flag-icons/sass') ] } } }] }] }, plugins: [ new RemoveEmptyScriptsPlugin(), new MiniCssExtractPlugin({ filename: '[name].css', chunkFilename: '[name].css' }) ], optimization: { minimizer: ['...', new CssMinimizerPlugin()] } }];`, dotProject: (cartridgeName) => `<?xml version="1.0" encoding="UTF-8"?> <projectDescription> <name>${cartridgeName}</name> <comment></comment> <projects> </projects> <buildSpec> <buildCommand> <name>com.demandware.studio.core.beehiveElementBuilder</name> <arguments> </arguments> </buildCommand> </buildSpec> <natures> <nature>com.demandware.studio.core.beehiveNature</nature> </natures> </projectDescription>`, projectProperties: (cartridgeName) => `## cartridge.properties for cartridge ${cartridgeName} #demandware.cartridges.${cartridgeName}.multipleLanguageStorefront=true`, eslintrc: () => ({ root: true, extends: 'airbnb-base/legacy', globals: { session: 'readonly', request: 'readonly', }, rules: { 'import/no-unresolved': 'off', indent: ['error', 4, { SwitchCase: 1, VariableDeclarator: 1 }], 'func-names': 'off', 'require-jsdoc': 'error', 'valid-jsdoc': ['error', { preferType: { Boolean: 'boolean', Number: 'number', object: 'Object', String: 'string', }, requireReturn: false, }], 'vars-on-top': 'off', 'global-require': 'off', 'no-shadow': ['error', { allow: ['err', 'callback'] }], 'max-len': 'off', 'no-plusplus': 'off', }, }), stylelintrc: () => ({ extends: 'stylelint-config-standard-scss', plugins: [ 'stylelint-scss', ], }), eslintignore: () => `node_modules/ cartridges/**/cartridge/static/ coverage/ doc/ bin/ codecept.conf.js`, gitignore: () => `node_modules/ cartridges/*/cartridge/static/ .DS_Store *.log npm-debug.log* yarn-debug.log* yarn-error.log* coverage/ .nyc_output/ .env dw.json`, }; } } //# sourceMappingURL=cartridge-generation-client.js.map