@medusajs/medusa
Version:
Building blocks for digital commerce
218 lines • 8.9 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.traceRequestHandler = void 0;
exports.registerInstrumentation = registerInstrumentation;
const os_1 = __importDefault(require("os"));
const path_1 = __importDefault(require("path"));
const http_1 = __importDefault(require("http"));
const express_1 = __importDefault(require("express"));
const cluster_1 = __importDefault(require("cluster"));
const telemetry_1 = require("@medusajs/telemetry");
const node_schedule_1 = require("node-schedule");
const utils_1 = require("@medusajs/framework/utils");
const logger_1 = require("@medusajs/framework/logger");
const loaders_1 = __importDefault(require("../loaders"));
const modules_sdk_1 = require("@medusajs/framework/modules-sdk");
const url_1 = require("url");
const EVERY_SIXTH_HOUR = "0 */6 * * *";
const CRON_SCHEDULE = EVERY_SIXTH_HOUR;
const INSTRUMENTATION_FILE = "instrumentation";
/**
* Imports the "instrumentation.js" file from the root of the
* directory and invokes the register function. The existence
* of this file is optional, hence we ignore "ENOENT"
* errors.
*/
async function registerInstrumentation(directory) {
const fileSystem = new utils_1.FileSystem(directory);
const exists = (await fileSystem.exists(`${INSTRUMENTATION_FILE}.ts`)) ||
(await fileSystem.exists(`${INSTRUMENTATION_FILE}.js`));
if (!exists) {
return;
}
const instrumentation = await (0, utils_1.dynamicImport)(path_1.default.join(directory, INSTRUMENTATION_FILE));
if (typeof instrumentation.register === "function") {
logger_1.logger.info("OTEL registered");
instrumentation.register();
}
else {
logger_1.logger.info("Skipping instrumentation registration. No register function found.");
}
}
/**
* Wrap request handler inside custom implementation to enabled
* instrumentation.
*/
// eslint-disable-next-line no-var
exports.traceRequestHandler = void 0;
function displayAdminUrl({ host, port, container, }) {
const isProduction = ["production", "prod"].includes(process.env.NODE_ENV || "");
if (isProduction) {
return;
}
const logger = container.resolve("logger");
const { admin: { path: adminPath, disable }, } = container.resolve("configModule");
if (disable) {
return;
}
logger.info(`Admin URL → http://${host || "localhost"}:${port}${adminPath}`);
}
/**
* Retrieve the route path from the express stack based on the input url
* @param stack - The express stack
* @param url - The input url
* @returns The route path
*/
function findExpressRoutePath({ stack, url, }) {
const stackToProcess = [...stack];
while (stackToProcess.length > 0) {
const layer = stackToProcess.pop();
if (layer.name === "bound dispatch" && layer.match(url)) {
return layer.route.path;
}
// Add nested stack items to be processed if they exist
if (layer.handle?.stack?.length) {
stackToProcess.push(...layer.handle.stack);
}
}
return undefined;
}
async function start(args) {
const { port = 9000, host, directory, types } = args;
async function internalStart(generateTypes) {
(0, telemetry_1.track)("CLI_START");
await registerInstrumentation(directory);
const app = (0, express_1.default)();
const http_ = http_1.default.createServer(async (req, res) => {
const stack = app._router.stack;
await new Promise((resolve) => {
res.on("finish", resolve);
if (exports.traceRequestHandler) {
const expressHandlerPath = findExpressRoutePath({
stack,
url: (0, url_1.parse)(req.url, false).pathname,
});
void (0, exports.traceRequestHandler)(async () => {
app(req, res);
}, req, res, expressHandlerPath);
}
else {
app(req, res);
}
});
});
try {
const { shutdown, gqlSchema, container, modules } = await (0, loaders_1.default)({
directory,
expressApp: app,
});
if (generateTypes) {
const typesDirectory = path_1.default.join(directory, ".medusa/types");
/**
* Cleanup existing types directory before creating new artifacts
*/
await new utils_1.FileSystem(typesDirectory).cleanup({ recursive: true });
await (0, utils_1.generateContainerTypes)(modules, {
outputDir: typesDirectory,
interfaceName: "ModuleImplementations",
});
logger_1.logger.debug("Generated container types");
if (gqlSchema) {
await (0, utils_1.gqlSchemaToTypes)({
outputDir: typesDirectory,
filename: "query-entry-points",
interfaceName: "RemoteQueryEntryPoints",
schema: gqlSchema,
joinerConfigs: modules_sdk_1.MedusaModule.getAllJoinerConfigs(),
});
logger_1.logger.debug("Generated modules types");
}
}
const serverActivity = logger_1.logger.activity(`Creating server`);
// Register a health check endpoint. Ideally this also checks the readiness of the service, rather than just returning a static response.
app.get("/health", (_, res) => {
res.status(200).send("OK");
});
const server = utils_1.GracefulShutdownServer.create(http_.listen(port, host).on("listening", () => {
logger_1.logger.success(serverActivity, `Server is ready on port: ${port}`);
displayAdminUrl({ container, host, port });
(0, telemetry_1.track)("CLI_START_COMPLETED");
}));
// Handle graceful shutdown
const gracefulShutDown = () => {
logger_1.logger.info("Gracefully shutting down server");
server
.shutdown()
.then(async () => {
await shutdown();
process.exit(0);
})
.catch((e) => {
logger_1.logger.error("Error received when shutting down the server.", e);
process.exit(1);
});
};
process.on("SIGTERM", gracefulShutDown);
process.on("SIGINT", gracefulShutDown);
(0, node_schedule_1.scheduleJob)(CRON_SCHEDULE, () => {
(0, telemetry_1.track)("PING");
});
return { server };
}
catch (err) {
logger_1.logger.error("Error starting server", err);
process.exit(1);
}
}
/**
* When the cluster flag is used we will start the process in
* cluster mode
*/
if ("cluster" in args) {
const maxCpus = os_1.default.cpus().length;
const cpus = args.cluster ?? maxCpus;
if (cluster_1.default.isPrimary) {
let isShuttingDown = false;
const numCPUs = Math.min(maxCpus, cpus);
const killMainProccess = () => process.exit(0);
const gracefulShutDown = () => {
isShuttingDown = true;
for (const id of Object.keys(cluster_1.default.workers ?? {})) {
cluster_1.default.workers?.[id]?.kill("SIGTERM");
}
};
for (let index = 0; index < numCPUs; index++) {
cluster_1.default.fork().send({ index });
}
cluster_1.default.on("exit", () => {
if (!isShuttingDown) {
cluster_1.default.fork();
}
else if (!(0, utils_1.isPresent)(cluster_1.default.workers)) {
setTimeout(killMainProccess, 100).unref();
}
});
process.on("SIGTERM", gracefulShutDown);
process.on("SIGINT", gracefulShutDown);
}
else {
process.on("message", async (msg) => {
if (msg.index > 0) {
process.env.PLUGIN_ADMIN_UI_SKIP_CACHE = "true";
}
await internalStart(!!types && msg.index === 0);
});
}
}
else {
/**
* Not in cluster mode
*/
await internalStart(!!types);
}
}
exports.default = start;
//# sourceMappingURL=start.js.map
;