UNPKG

@medusajs/framework

Version:
423 lines • 22 kB
"use strict"; var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; var _Compiler_instances, _Compiler_logger, _Compiler_projectRoot, _Compiler_tsConfigPath, _Compiler_pluginsDistFolder, _Compiler_backendIgnoreFiles, _Compiler_adminOnlyDistFolder, _Compiler_tsCompiler, _Compiler_trackDuration, _Compiler_computeDist, _Compiler_loadTSCompiler, _Compiler_copy, _Compiler_copyPkgManagerFiles, _Compiler_clean, _Compiler_isScriptFile, _Compiler_loadMedusaConfig, _Compiler_printDiagnostics, _Compiler_emitBuildOutput; Object.defineProperty(exports, "__esModule", { value: true }); exports.Compiler = void 0; const utils_1 = require("@medusajs/utils"); const chokidar_1 = __importDefault(require("chokidar")); const promises_1 = require("fs/promises"); const path_1 = __importDefault(require("path")); /** * The compiler exposes the opinionated APIs for compiling Medusa * applications and plugins. You can perform the following * actions. * * - loadTSConfigFile: Load and parse the TypeScript config file. All errors * will be reported using the logger. * * - buildAppBackend: Compile the Medusa application backend source code to the * ".medusa/server" directory. The admin source and integration-tests are * skipped. * * - buildAppFrontend: Compile the admin extensions using the "@medusjs/admin-bundler" * package. Admin can be compiled for self hosting (aka adminOnly), or can be compiled * to be bundled with the backend output. */ class Compiler { constructor(projectRoot, logger) { _Compiler_instances.add(this); _Compiler_logger.set(this, void 0); _Compiler_projectRoot.set(this, void 0); _Compiler_tsConfigPath.set(this, void 0); _Compiler_pluginsDistFolder.set(this, void 0); _Compiler_backendIgnoreFiles.set(this, void 0); _Compiler_adminOnlyDistFolder.set(this, void 0); _Compiler_tsCompiler.set(this, void 0); __classPrivateFieldSet(this, _Compiler_projectRoot, projectRoot, "f"); __classPrivateFieldSet(this, _Compiler_logger, logger, "f"); __classPrivateFieldSet(this, _Compiler_tsConfigPath, path_1.default.join(__classPrivateFieldGet(this, _Compiler_projectRoot, "f"), "tsconfig.json"), "f"); __classPrivateFieldSet(this, _Compiler_adminOnlyDistFolder, path_1.default.join(__classPrivateFieldGet(this, _Compiler_projectRoot, "f"), ".medusa/admin"), "f"); __classPrivateFieldSet(this, _Compiler_pluginsDistFolder, path_1.default.join(__classPrivateFieldGet(this, _Compiler_projectRoot, "f"), ".medusa/server"), "f"); __classPrivateFieldSet(this, _Compiler_backendIgnoreFiles, [ "/integration-tests/", "/test/", "/unit-tests/", "/src/admin/", ], "f"); } /** * Loads and parses the TypeScript config file. In case of an error, the errors * will be logged using the logger and undefined it returned */ async loadTSConfigFile() { const ts = await __classPrivateFieldGet(this, _Compiler_instances, "m", _Compiler_loadTSCompiler).call(this); let tsConfigErrors = []; const tsConfig = ts.getParsedCommandLineOfConfigFile(__classPrivateFieldGet(this, _Compiler_tsConfigPath, "f"), { inlineSourceMap: true, excludes: [], }, { ...ts.sys, useCaseSensitiveFileNames: true, getCurrentDirectory: () => __classPrivateFieldGet(this, _Compiler_projectRoot, "f"), onUnRecoverableConfigFileDiagnostic: (error) => (tsConfigErrors = [error]), }); /** * Push errors from the tsConfig parsed output to the * tsConfigErrors array. */ if (tsConfig?.errors.length) { tsConfigErrors.push(...tsConfig.errors); } /** * Display all config errors using the diagnostics reporter */ __classPrivateFieldGet(this, _Compiler_instances, "m", _Compiler_printDiagnostics).call(this, ts, tsConfigErrors); /** * Return undefined when there are errors in parsing the config * file */ if (tsConfigErrors.length) { return; } return tsConfig; } /** * Builds the application backend source code using * TypeScript's official compiler. Also performs * type-checking */ async buildAppBackend(tsConfig) { const tracker = __classPrivateFieldGet(this, _Compiler_instances, "m", _Compiler_trackDuration).call(this); const dist = __classPrivateFieldGet(this, _Compiler_instances, "m", _Compiler_computeDist).call(this, tsConfig); __classPrivateFieldGet(this, _Compiler_logger, "f").info("Compiling backend source..."); /** * Step 1: Cleanup existing build output */ __classPrivateFieldGet(this, _Compiler_logger, "f").info(`Removing existing "${path_1.default.relative(__classPrivateFieldGet(this, _Compiler_projectRoot, "f"), dist)}" folder`); await __classPrivateFieldGet(this, _Compiler_instances, "m", _Compiler_clean).call(this, dist); /** * Create first the target directory now that everything is clean */ await (0, promises_1.mkdir)(dist, { recursive: true }); /** * Step 2: Compile TypeScript source code */ const { emitResult, diagnostics } = await __classPrivateFieldGet(this, _Compiler_instances, "m", _Compiler_emitBuildOutput).call(this, tsConfig, __classPrivateFieldGet(this, _Compiler_backendIgnoreFiles, "f"), dist); /** * Exit early if no output is written to the disk */ if (emitResult.emitSkipped) { __classPrivateFieldGet(this, _Compiler_logger, "f").warn("Backend build completed without emitting any output"); return false; } /** * Step 3: Copy package manager files to the output folder */ await __classPrivateFieldGet(this, _Compiler_instances, "m", _Compiler_copyPkgManagerFiles).call(this, dist); /** * Notify about the state of build */ if (diagnostics.length) { __classPrivateFieldGet(this, _Compiler_logger, "f").warn(`Backend build completed with errors (${tracker.getSeconds()}s)`); return false; } __classPrivateFieldGet(this, _Compiler_logger, "f").info(`Backend build completed successfully (${tracker.getSeconds()}s)`); return true; } /** * Builds the frontend source code of a Medusa application * using the "@medusajs/admin-bundler" package. */ async buildAppFrontend(adminOnly, tsConfig, adminBundler) { const tracker = __classPrivateFieldGet(this, _Compiler_instances, "m", _Compiler_trackDuration).call(this); /** * Step 1: Load the medusa config file to read * admin options */ const configFile = await __classPrivateFieldGet(this, _Compiler_instances, "m", _Compiler_loadMedusaConfig).call(this); if (!configFile) { return false; } /** * Return early when admin is disabled and we are trying to * create a bundled build for the admin. */ if (configFile.configModule.admin.disable && !adminOnly) { __classPrivateFieldGet(this, _Compiler_logger, "f").info("Skipping admin build, since its disabled inside the medusa-config file"); return true; } /** * Warn when we are creating an admin only build, but forgot to disable * the admin inside the config file */ if (!configFile.configModule.admin.disable && adminOnly) { __classPrivateFieldGet(this, _Compiler_logger, "f").warn(`You are building using the flag --admin-only but the admin is enabled in your medusa-config, If you intend to host the dashboard separately you should disable the admin in your medusa config`); } const plugins = await (0, utils_1.getResolvedPlugins)(__classPrivateFieldGet(this, _Compiler_projectRoot, "f"), configFile.configModule, true); const adminSources = plugins .map((plugin) => plugin.admin?.type === "local" ? plugin.admin.resolve : undefined) .filter(Boolean); const adminPlugins = plugins .map((plugin) => plugin.admin?.type === "package" ? plugin.admin.resolve : undefined) .filter(Boolean); try { __classPrivateFieldGet(this, _Compiler_logger, "f").info("Compiling frontend source..."); await adminBundler.build({ disable: false, sources: adminSources, plugins: adminPlugins, ...configFile.configModule.admin, outDir: adminOnly ? __classPrivateFieldGet(this, _Compiler_adminOnlyDistFolder, "f") : path_1.default.join(__classPrivateFieldGet(this, _Compiler_instances, "m", _Compiler_computeDist).call(this, tsConfig), "./public/admin"), }); __classPrivateFieldGet(this, _Compiler_logger, "f").info(`Frontend build completed successfully (${tracker.getSeconds()}s)`); return true; } catch (error) { __classPrivateFieldGet(this, _Compiler_logger, "f").error("Unable to compile frontend source"); __classPrivateFieldGet(this, _Compiler_logger, "f").error(error); return false; } } /** * Compiles the plugin source code to JavaScript using the * TypeScript's official compiler */ async buildPluginBackend(tsConfig) { const tracker = __classPrivateFieldGet(this, _Compiler_instances, "m", _Compiler_trackDuration).call(this); const dist = ".medusa/server"; __classPrivateFieldGet(this, _Compiler_logger, "f").info("Compiling plugin source..."); /** * Step 1: Cleanup existing build output */ __classPrivateFieldGet(this, _Compiler_logger, "f").info(`Removing existing "${path_1.default.relative(__classPrivateFieldGet(this, _Compiler_projectRoot, "f"), dist)}" folder`); await __classPrivateFieldGet(this, _Compiler_instances, "m", _Compiler_clean).call(this, dist); /** * Step 2: Compile TypeScript source code */ const { emitResult, diagnostics } = await __classPrivateFieldGet(this, _Compiler_instances, "m", _Compiler_emitBuildOutput).call(this, tsConfig, __classPrivateFieldGet(this, _Compiler_backendIgnoreFiles, "f"), dist); /** * Exit early if no output is written to the disk */ if (emitResult.emitSkipped) { __classPrivateFieldGet(this, _Compiler_logger, "f").warn("Plugin build completed without emitting any output"); return false; } /** * Notify about the state of build */ if (diagnostics.length) { __classPrivateFieldGet(this, _Compiler_logger, "f").warn(`Plugin build completed with errors (${tracker.getSeconds()}s)`); return false; } __classPrivateFieldGet(this, _Compiler_logger, "f").info(`Plugin build completed successfully (${tracker.getSeconds()}s)`); return true; } /** * Compiles the backend source code of a plugin project in watch * mode. Type-checking is disabled to keep compilation fast. * * The "onFileChange" argument can be used to get notified when * a file has changed. */ async developPluginBackend(transformer, onFileChange) { const fs = new utils_1.FileSystem(__classPrivateFieldGet(this, _Compiler_pluginsDistFolder, "f")); await fs.createJson("medusa-plugin-options.json", { srcDir: path_1.default.join(__classPrivateFieldGet(this, _Compiler_projectRoot, "f"), "src"), }); const watcher = chokidar_1.default.watch(["."], { ignoreInitial: true, cwd: __classPrivateFieldGet(this, _Compiler_projectRoot, "f"), ignored: [ /(^|[\\/\\])\../, "node_modules", "dist", "static", "private", ".medusa/**/*", ...__classPrivateFieldGet(this, _Compiler_backendIgnoreFiles, "f"), ], }); watcher.on("add", async (file) => { if (!__classPrivateFieldGet(this, _Compiler_instances, "m", _Compiler_isScriptFile).call(this, file)) { return; } const relativePath = path_1.default.relative(__classPrivateFieldGet(this, _Compiler_projectRoot, "f"), file); const outputPath = relativePath.replace(/\.ts$/, ".js"); __classPrivateFieldGet(this, _Compiler_logger, "f").info(`${relativePath} updated: Republishing changes`); await fs.create(outputPath, await transformer(file)); onFileChange?.(file, "add"); }); watcher.on("change", async (file) => { if (!__classPrivateFieldGet(this, _Compiler_instances, "m", _Compiler_isScriptFile).call(this, file)) { return; } const relativePath = path_1.default.relative(__classPrivateFieldGet(this, _Compiler_projectRoot, "f"), file); const outputPath = relativePath.replace(/\.ts$/, ".js"); __classPrivateFieldGet(this, _Compiler_logger, "f").info(`${relativePath} updated: Republishing changes`); await fs.create(outputPath, await transformer(file)); onFileChange?.(file, "change"); }); watcher.on("unlink", async (file) => { if (!__classPrivateFieldGet(this, _Compiler_instances, "m", _Compiler_isScriptFile).call(this, file)) { return; } const relativePath = path_1.default.relative(__classPrivateFieldGet(this, _Compiler_projectRoot, "f"), file); const outputPath = relativePath.replace(/\.ts$/, ".js"); __classPrivateFieldGet(this, _Compiler_logger, "f").info(`${relativePath} removed: Republishing changes`); await fs.remove(outputPath); onFileChange?.(file, "unlink"); }); watcher.on("ready", () => { __classPrivateFieldGet(this, _Compiler_logger, "f").info("watching for file changes"); }); } async buildPluginAdminExtensions(bundler) { const tracker = __classPrivateFieldGet(this, _Compiler_instances, "m", _Compiler_trackDuration).call(this); __classPrivateFieldGet(this, _Compiler_logger, "f").info("Compiling plugin admin extensions..."); try { await bundler.plugin({ root: __classPrivateFieldGet(this, _Compiler_projectRoot, "f"), outDir: __classPrivateFieldGet(this, _Compiler_pluginsDistFolder, "f"), }); __classPrivateFieldGet(this, _Compiler_logger, "f").info(`Plugin admin extensions build completed successfully (${tracker.getSeconds()}s)`); return true; } catch (error) { __classPrivateFieldGet(this, _Compiler_logger, "f").error(`Plugin admin extensions build failed`, error); return false; } } } exports.Compiler = Compiler; _Compiler_logger = new WeakMap(), _Compiler_projectRoot = new WeakMap(), _Compiler_tsConfigPath = new WeakMap(), _Compiler_pluginsDistFolder = new WeakMap(), _Compiler_backendIgnoreFiles = new WeakMap(), _Compiler_adminOnlyDistFolder = new WeakMap(), _Compiler_tsCompiler = new WeakMap(), _Compiler_instances = new WeakSet(), _Compiler_trackDuration = function _Compiler_trackDuration() { const startTime = process.hrtime(); return { getSeconds() { const duration = process.hrtime(startTime); return (duration[0] + duration[1] / 1e9).toFixed(2); }, }; }, _Compiler_computeDist = function _Compiler_computeDist(tsConfig) { const distFolder = tsConfig.options.outDir ?? ".medusa/server"; return path_1.default.isAbsolute(distFolder) ? distFolder : path_1.default.join(__classPrivateFieldGet(this, _Compiler_projectRoot, "f"), distFolder); }, _Compiler_loadTSCompiler = /** * Imports and stores a reference to the TypeScript compiler. * We dynamically import "typescript", since its is a dev * only dependency */ async function _Compiler_loadTSCompiler() { if (!__classPrivateFieldGet(this, _Compiler_tsCompiler, "f")) { __classPrivateFieldSet(this, _Compiler_tsCompiler, await import("typescript"), "f"); } return __classPrivateFieldGet(this, _Compiler_tsCompiler, "f"); }, _Compiler_copy = /** * Copies the file to the destination without throwing any * errors if the source file is missing */ async function _Compiler_copy(source, destination) { let sourceExists = false; try { await (0, promises_1.access)(source, promises_1.constants.F_OK); sourceExists = true; } catch (error) { if (error.code !== "ENOENT") { throw error; } } if (sourceExists) { await (0, promises_1.copyFile)(path_1.default.join(source), path_1.default.join(destination)); } }, _Compiler_copyPkgManagerFiles = /** * Copies package manager files from the project root * to the specified dist folder */ async function _Compiler_copyPkgManagerFiles(dist) { /** * Copying package manager files */ await __classPrivateFieldGet(this, _Compiler_instances, "m", _Compiler_copy).call(this, path_1.default.join(__classPrivateFieldGet(this, _Compiler_projectRoot, "f"), "package.json"), path_1.default.join(dist, "package.json")); await __classPrivateFieldGet(this, _Compiler_instances, "m", _Compiler_copy).call(this, path_1.default.join(__classPrivateFieldGet(this, _Compiler_projectRoot, "f"), "yarn.lock"), path_1.default.join(dist, "yarn.lock")); await __classPrivateFieldGet(this, _Compiler_instances, "m", _Compiler_copy).call(this, path_1.default.join(__classPrivateFieldGet(this, _Compiler_projectRoot, "f"), "pnpm.lock"), path_1.default.join(dist, "pnpm.lock")); await __classPrivateFieldGet(this, _Compiler_instances, "m", _Compiler_copy).call(this, path_1.default.join(__classPrivateFieldGet(this, _Compiler_projectRoot, "f"), "package-lock.json"), path_1.default.join(dist, "package-lock.json")); }, _Compiler_clean = /** * Removes the directory and its children recursively and * ignores any errors */ async function _Compiler_clean(path) { await (0, promises_1.rm)(path, { recursive: true }).catch(() => { }); }, _Compiler_isScriptFile = function _Compiler_isScriptFile(filePath) { if (filePath.endsWith(".ts") && !filePath.endsWith(".d.ts")) { return true; } return filePath.endsWith(".js"); }, _Compiler_loadMedusaConfig = /** * Loads the medusa config file and prints the error to * the console (in case of any errors). Otherwise, the * file path and the parsed config is returned */ async function _Compiler_loadMedusaConfig() { const { configModule, configFilePath, error } = await (0, utils_1.getConfigFile)(__classPrivateFieldGet(this, _Compiler_projectRoot, "f"), "medusa-config"); if (error) { __classPrivateFieldGet(this, _Compiler_logger, "f").error(`Failed to load medusa-config.(js|ts) file`); __classPrivateFieldGet(this, _Compiler_logger, "f").error(error); return; } return { configFilePath, configModule }; }, _Compiler_printDiagnostics = function _Compiler_printDiagnostics(ts, diagnostics) { if (diagnostics.length) { console.error(ts.formatDiagnosticsWithColorAndContext(diagnostics, ts.createCompilerHost({}))); } }, _Compiler_emitBuildOutput = /** * Given a tsconfig file, this method will write the compiled * output to the specified destination */ async function _Compiler_emitBuildOutput(tsConfig, chunksToIgnore, dist) { const ts = await __classPrivateFieldGet(this, _Compiler_instances, "m", _Compiler_loadTSCompiler).call(this); const filesToCompile = tsConfig.fileNames.filter((fileName) => { return !chunksToIgnore.some((chunk) => fileName.includes(`${chunk}`)); }); /** * Create emit program to compile and emit output */ const program = ts.createProgram(filesToCompile, { ...tsConfig.options, ...{ outDir: dist, inlineSourceMap: !tsConfig.options.sourceMap, }, }); const emitResult = program.emit(); const diagnostics = ts .getPreEmitDiagnostics(program) .concat(emitResult.diagnostics); /** * Log errors (if any) */ __classPrivateFieldGet(this, _Compiler_instances, "m", _Compiler_printDiagnostics).call(this, ts, diagnostics); return { emitResult, diagnostics }; }; //# sourceMappingURL=compiler.js.map