@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
JavaScript
;
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}'`;
}