@medusajs/framework
Version:
423 lines • 22 kB
JavaScript
"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