UNPKG

@salesforce/pwa-kit-mcp

Version:

MCP server that helps you build Salesforce Commerce Cloud PWA Kit Composable Storefront

269 lines (256 loc) 13.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.EmptyJsonSchema = void 0; exports.generateComponentImportStatement = generateComponentImportStatement; exports.isLocalSharedUIComponent = exports.isLocalComponent = exports.isBaseComponent = exports.getCreateAppCommand = exports.getCopyrightHeader = void 0; exports.isMonoRepo = isMonoRepo; exports.isSharedUIBaseComponent = void 0; exports.logMCPMessage = logMCPMessage; exports.runCommand = void 0; exports.runNpxCommand = runNpxCommand; exports.toKebabCase = toKebabCase; exports.toPascalCase = void 0; var _fs = _interopRequireDefault(require("fs")); var _promises = _interopRequireDefault(require("fs/promises")); var _path = _interopRequireDefault(require("path")); var _crossSpawn = require("cross-spawn"); var _zodToJsonSchema = require("zod-to-json-schema"); var _zod = require("zod"); var _os = _interopRequireDefault(require("os")); var _child_process = require("child_process"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); } function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; } /* * Copyright (c) 2025, Salesforce, Inc. * All rights reserved. * SPDX-License-Identifier: BSD-3-Clause * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ // CONSTANTS const CREATE_APP_VERSION = 'latest'; // Private schema used to generate the JSON schema const emptySchema = _zod.z.object({}).strict(); const EmptyJsonSchema = exports.EmptyJsonSchema = (0, _zodToJsonSchema.zodToJsonSchema)(emptySchema); /** * Converts a string to PascalCase (e.g., product-card -> ProductCard) */ const toPascalCase = str => str.replace(/(^\w|[-_\s]\w)/g, match => match.replace(/[-_\s]/, '').toUpperCase()); /** * Runs a shell command and captures its stdout/stderr as a string. * * @param {string} command - The executable to run (e.g. "node", "npx", "ls"). * @param {string[]} args - Arguments to pass to the command. * @param {Object} [options] - Optional spawn options (e.g. cwd). * @returns {Promise<string>} - Resolves with combined stdout and stderr. */ exports.toPascalCase = toPascalCase; const runCommand = exports.runCommand = /*#__PURE__*/function () { var _ref = _asyncToGenerator(function* (command, args = [], options = {}) { return new Promise((resolve, reject) => { const child = (0, _crossSpawn.spawn)(command, args, _objectSpread(_objectSpread({}, options), {}, { stdio: ['ignore', 'pipe', 'pipe'], // ignore stdin, pipe out/err shell: false // be explicit — set to true if you want shell features })); let output = ''; child.stdout.on('data', chunk => { output += chunk.toString(); }); child.stderr.on('data', chunk => { output += chunk.toString(); // combine stderr into output }); child.on('error', err => { reject(err); }); child.on('close', code => { if (code === 0) { resolve(output); } else { const error = new Error(`Command failed with exit code ${code}`); error.output = output; error.code = code; reject(error); } }); }); }); return function runCommand(_x) { return _ref.apply(this, arguments); }; }(); /** * Checks if the project is a monorepo by verifying the existence of lerna.json in the root directory. * * @returns {boolean} True if lerna.json exists in the current workspace, false otherwise. */ function isMonoRepo() { const lernaPath = _path.default.resolve(...(process.env.WORKSPACE_FOLDER_PATHS ? [process.env.WORKSPACE_FOLDER_PATHS] : []), 'lerna.json'); return _fs.default.existsSync(lernaPath); } /** * Check if the component is the base component under node_modules/@salesforce/retail-react-app/app/components * * @param {string} componentName - The name of the component to check. * @param {string} nodeModulesPath - The absolute path to the node_modules directory. * @returns {boolean} True if the component is the base component, false otherwise. */ const isBaseComponent = (componentName, nodeModulesPath) => { const baseComponentPath = _path.default.join(nodeModulesPath, '@salesforce/retail-react-app/app/components', componentName); return _fs.default.existsSync(baseComponentPath); }; /** * Check if the component is the shared UI base component under node_modules/@salesforce/retail-react-app/app/components/shared/ui * * @param {string} componentName - The name of the component to check. * @param {string} nodeModulesPath - The absolute path to the node_modules directory. * @returns {boolean} True if the component is the shared UI base component, false otherwise. */ exports.isBaseComponent = isBaseComponent; const isSharedUIBaseComponent = (componentName, nodeModulesPath) => { const baseComponentPath = _path.default.join(nodeModulesPath, '@salesforce/retail-react-app/app/components/shared/ui', componentName); return _fs.default.existsSync(baseComponentPath); }; /** * Check if the component is the local component under components folder * * @param {string} componentName - The name of the component to check. * @param {string} componentsPath - The absolute path to the components directory. * @returns {boolean} True if the component is the local component, false otherwise. */ exports.isSharedUIBaseComponent = isSharedUIBaseComponent; const isLocalComponent = (componentName, componentsPath) => { const localComponentPath = _path.default.join(componentsPath, componentName); return _fs.default.existsSync(localComponentPath); }; /** * Check if the component is a local shared UI component under components/shared/ui folder * * @param {string} componentName - The name of the component to check. * @param {string} componentsPath - The absolute path to the components directory. * @returns {boolean} True if the component is a local shared UI component, false otherwise. */ exports.isLocalComponent = isLocalComponent; const isLocalSharedUIComponent = (componentName, componentsPath) => { const localSharedUIComponentPath = _path.default.join(componentsPath, 'shared', 'ui', componentName); return _fs.default.existsSync(localSharedUIComponentPath); }; /** * Returns the command or path to use for creating a new PWA Kit app. * * If the project is a monorepo (detected by the presence of lerna.json), * it returns the absolute path to the local create-mobify-app.js script. * Otherwise, it returns the npm package name with a specific version. * * @returns {string} The command or path to use for app creation. */ exports.isLocalSharedUIComponent = isLocalSharedUIComponent; const getCreateAppCommand = () => { return isMonoRepo() ? _path.default.resolve(`${process.env.WORKSPACE_FOLDER_PATHS}/packages/pwa-kit-create-app/scripts/create-mobify-app.js`) : `@salesforce/pwa-kit-create-app@${CREATE_APP_VERSION}`; }; /** * Runs an NPX command and captures its output. * * @returns {Promise<string>} - Resolves with the command output. */ exports.getCreateAppCommand = getCreateAppCommand; function runNpxCommand(_x2, _x3, _x4) { return _runNpxCommand.apply(this, arguments); } /** * Returns the copyright header with the current year * @returns {string} The copyright header text */ function _runNpxCommand() { _runNpxCommand = _asyncToGenerator(function* (NPX_COMMAND, CREATE_APP_COMMAND, DISPLAY_PROGRAM_COMMAND) { return new Promise((resolve, reject) => { const tempDir = _os.default.tmpdir(); const outputFilePath = _path.default.join(tempDir, 'npx-output.json'); const errorFilePath = _path.default.join(tempDir, 'npx-error.log'); const command = `${NPX_COMMAND} ${CREATE_APP_COMMAND} ${DISPLAY_PROGRAM_COMMAND} > ${outputFilePath} 2> ${errorFilePath}`; (0, _child_process.exec)(command, error => { if (error) { reject(error); return; } _promises.default.promises.readFile(outputFilePath, 'utf-8').then(data => resolve(data)).catch(err => reject(err)); }); }); }); return _runNpxCommand.apply(this, arguments); } const getCopyrightHeader = () => { const year = new Date().getFullYear(); return `/* * Copyright (c) ${year}, Salesforce, Inc. * All rights reserved. * SPDX-License-Identifier: BSD-3-Clause * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause */`; }; /** * Converts a string to kebab-case (e.g., ProductCard -> product-card) */ exports.getCopyrightHeader = getCopyrightHeader; function toKebabCase(str) { return str.replace(/([a-z])([A-Z])/g, '$1-$2').replace(/\s+/g, '-').toLowerCase(); } /** * Logs a message to the mcp-debug.log file in the current directory. * @param {string} message - The message to log. */ function logMCPMessage(_x5) { return _logMCPMessage.apply(this, arguments); } /** * Returns the import statement for a component * @param {string} componentName - The name of the component to import. * @param {string} componentDir - The directory of the component to import. * @param {boolean} isLocal - Whether the component is a local component. * @param {boolean} isBase - Whether the component is a base component. * @param {Object} absolutePaths - Object containing absolute paths for components and pages. * @param {string} absolutePaths.componentsPath - The absolute path to the components directory. * @param {string} absolutePaths.pagesPath - The absolute path to the pages directory. * @param {boolean} hasOverridesDir - Whether ccExtensibility.overridesDir is set in package.json. * @returns {string} The import statement for the component. */ function _logMCPMessage() { _logMCPMessage = _asyncToGenerator(function* (message) { if (process.env.DEBUG) { // Check if DEBUG mode is enabled const logFilePath = _path.default.join(__dirname, 'mcp-debug.log'); const timestamp = new Date().toLocaleString('en-US', { timeZone: 'America/New_York' }); const logMessage = `[${timestamp}] ${message}\n`; try { // Ensure the log file exists, create it if it doesn't yield _promises.default.access(logFilePath).catch(/*#__PURE__*/_asyncToGenerator(function* () { yield _promises.default.writeFile(logFilePath, '', 'utf8'); })); yield _promises.default.appendFile(logFilePath, logMessage, 'utf8'); } catch (error) { console.error(`Failed to write to log file: ${error.message}`); } } }); return _logMCPMessage.apply(this, arguments); } function generateComponentImportStatement(componentName, componentDir, isLocal, isBase, absolutePaths, hasOverridesDir) { const relativePath = _path.default.relative(_path.default.join(absolutePaths.pagesPath, 'dummy'), // dummy file to get parent directory _path.default.join(absolutePaths.componentsPath, componentDir)); if (!hasOverridesDir && isLocal || isBase) { return `import ${componentName} from '@salesforce/retail-react-app/app/components/${componentDir}'`; } // Use local relative path for other cases // Normalize path separators to forward slashes for ES6 imports const normalizedPath = relativePath.replace(/\\/g, '/'); return `import ${componentName} from '${normalizedPath}'`; }