import chalk from "chalk";
import express, { Request, Response } from "express";
import { invokeAura } from "../utils/invokeAura.js";
import { createServer } from "net";
import { type Plugin } from "vite";
import path from "path";
import * as fs from "fs";
export function addMetaXmlFilePlugin(apiVersion: string): Plugin {
return {
name: "add-meta-xml-file",
generateBundle(_options: any, bundle: any) {
for (const fileName in bundle) {
if (fileName.endsWith(".js")) {
const componentName = fileName.split("/")[0];
const metaXml = `
${apiVersion}
false
`;
this.emitFile({
type: "asset",
fileName: `${componentName}/${componentName}.js-meta.xml`,
source: metaXml,
});
}
}
},
};
}
export function pathAliasPlugin(): Plugin {
return {
name: "path-alias-plugin",
renderChunk(code) {
// replace code like "../_assets_1/_assets_1.js" or "./_assets_2/_assets_2.js"
const updatedCode = code.replace(
/["']\.{1,2}\/(assets_\w+)\/\1\.js["']/g,
'"c/$1"'
);
return {
code: updatedCode,
map: null,
};
},
};
}
// Function to get entry points from pages directory
export function getEntryPoints(reactAppRoot?: string) {
if (!reactAppRoot) {
console.error(
chalk.red("❌ Error reading 'reactAppRoot' in miso.config.json.")
);
process.exit(1);
}
const pagesDir = path.resolve(process.cwd(), `./${reactAppRoot}/src/pages`);
const entries: Record = {};
// Read first level directories
const dirs = fs
.readdirSync(pagesDir, { withFileTypes: true })
.filter((dirent) => dirent.isDirectory())
.map((dirent) => dirent.name);
for (const dir of dirs) {
const mainFile = path.resolve(pagesDir, dir, "main.tsx");
if (fs.existsSync(mainFile)) {
entries[dir] = mainFile;
}
}
return entries;
}
export async function getmisoConfig(rootDir: string) {
try {
const configPath = path.join(rootDir, "miso.config.mjs");
// Convert path to URL format that works on both Windows and Unix
const fileUrl = new URL(`file://${path.resolve(configPath)}`).href;
const ts = await import(fileUrl);
return ts.default;
} catch (error) {
console.error(chalk.red("❌ Error reading miso.config.json:"), error);
process.exit(1);
}
}
export function getSfdxProjectConfig(rootDir: string): any {
const sfdxProjectPath = path.join(rootDir, "sfdx-project.json");
// Check if sfdx-project.json exists
if (!fs.existsSync(sfdxProjectPath)) {
console.error(
chalk.red(
"❌ This is not a Salesforce project. Please run this command in a Salesforce project directory."
)
);
process.exit(1);
}
try {
const sfdxProject = JSON.parse(fs.readFileSync(sfdxProjectPath, "utf-8"));
// Check if sourceApiVersion exists
if (!sfdxProject.sourceApiVersion) {
console.error(
chalk.red(
"❌ sourceApiVersion is missing in sfdx-project.json. Please add it to your project configuration."
)
);
process.exit(1);
}
console.info(
chalk.green("Detected partial required config from sfdx-project.json:"),
{
namespace: sfdxProject.namespace,
apiVersion: sfdxProject.sourceApiVersion,
},
"\n"
);
return sfdxProject;
} catch (error) {
console.error(chalk.red("❌ Error reading sfdx-project.json:"), error);
process.exit(1);
}
}
const isPortInUse = (port: number): Promise => {
return new Promise((resolve) => {
const server = createServer();
server.once("error", () => {
resolve(true);
});
server.once("listening", () => {
server.close();
resolve(false);
});
server.listen(port);
});
};
const findAvailablePort = async (startPort: number): Promise => {
let port = startPort;
while (await isPortInUse(port)) {
port++;
}
return port;
};
export const startServerProxy = async () => {
const app = express();
const initialPort = 4000;
const port = await findAvailablePort(initialPort);
app.use(express.json());
app.post("/aura", async (req: Request, res: Response) => {
try {
const { classname, method, namespace, params } = req.body;
const response = await invokeAura({
classname,
method,
namespace,
params,
});
res.status(response.status);
if (response.status >= 200 && response.status < 300) {
const responseBody = await response.json();
const actionResponse = responseBody.actions[0];
if (actionResponse.error?.length) {
res.json({
code: -1,
message: "error",
error: actionResponse.error,
});
} else {
const returnValue = actionResponse.returnValue.returnValue;
res.json({
code: 0,
message: "success",
data: returnValue,
});
}
} else {
res.send({
code: -2,
message: "error",
error: await response.text(),
});
}
} catch (error) {
console.error(chalk.red("Error forwarding Aura request:"), error);
res.status(500).json({
success: false,
error: error instanceof Error ? error.message : "Unknown error",
});
}
});
app.listen(port);
return port;
};
export function gitignorePlugin(rootDir: string, entries: Record): Plugin {
return {
name: "gitignore-plugin",
buildStart() {
const gitignorePath = path.join(rootDir, ".gitignore");
const lwcPath = "force-app/main/default/lwc";
// Read existing .gitignore content
let gitignoreContent = "";
try {
gitignoreContent = fs.readFileSync(gitignorePath, "utf-8");
} catch (error) {
// If .gitignore doesn't exist, create it
fs.writeFileSync(gitignorePath, "");
}
// Get all entry keys
const entryKeys = Object.keys(entries);
if (entryKeys.length === 0) return;
let hasChanges = false;
let newContent = gitignoreContent;
// Check each entry and add if not present
for (const entryKey of entryKeys) {
const entryPath = `${lwcPath}/${entryKey}`;
if (!gitignoreContent.includes(entryPath)) {
newContent += (newContent ? "\n" : "") + entryPath;
hasChanges = true;
console.log(`Added ${entryPath} to .gitignore`);
}
}
// Only write to file if there were changes
if (hasChanges) {
fs.writeFileSync(gitignorePath, newContent);
}
}
};
}