Feat/plugin develop (#10926)
Fixes: FRMW-2865 In this PR we add support for developing a plugin in watch mode. During the file change, we re-compile the source code (incrementally), publishes the package, and updates the installations of the plugin. We are using `yalc` under the hood and it must be installed as a dev dependency in the plugin project and the main Medusa app.
This commit is contained in:
@@ -23,15 +23,26 @@ import type { AdminOptions, ConfigModule, Logger } from "@medusajs/types"
|
||||
export class Compiler {
|
||||
#logger: Logger
|
||||
#projectRoot: string
|
||||
#tsConfigPath: string
|
||||
#adminSourceFolder: string
|
||||
#pluginsDistFolder: string
|
||||
#backendIgnoreFiles: string[]
|
||||
#adminOnlyDistFolder: string
|
||||
#tsCompiler?: typeof tsStatic
|
||||
|
||||
constructor(projectRoot: string, logger: Logger) {
|
||||
this.#projectRoot = projectRoot
|
||||
this.#logger = logger
|
||||
this.#tsConfigPath = path.join(this.#projectRoot, "tsconfig.json")
|
||||
this.#adminSourceFolder = path.join(this.#projectRoot, "src/admin")
|
||||
this.#adminOnlyDistFolder = path.join(this.#projectRoot, ".medusa/admin")
|
||||
this.#pluginsDistFolder = path.join(this.#projectRoot, ".medusa/server")
|
||||
this.#backendIgnoreFiles = [
|
||||
"integration-tests",
|
||||
"test",
|
||||
"unit-tests",
|
||||
"src/admin",
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,6 +152,20 @@ export class Compiler {
|
||||
return { configFilePath, configModule }
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints typescript diagnostic messages
|
||||
*/
|
||||
#printDiagnostics(ts: typeof tsStatic, diagnostics: tsStatic.Diagnostic[]) {
|
||||
if (diagnostics.length) {
|
||||
console.error(
|
||||
ts.formatDiagnosticsWithColorAndContext(
|
||||
diagnostics,
|
||||
ts.createCompilerHost({})
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a tsconfig file, this method will write the compiled
|
||||
* output to the specified destination
|
||||
@@ -177,14 +202,7 @@ export class Compiler {
|
||||
/**
|
||||
* Log errors (if any)
|
||||
*/
|
||||
if (diagnostics.length) {
|
||||
console.error(
|
||||
ts.formatDiagnosticsWithColorAndContext(
|
||||
diagnostics,
|
||||
ts.createCompilerHost({})
|
||||
)
|
||||
)
|
||||
}
|
||||
this.#printDiagnostics(ts, diagnostics)
|
||||
|
||||
return { emitResult, diagnostics }
|
||||
}
|
||||
@@ -198,7 +216,7 @@ export class Compiler {
|
||||
let tsConfigErrors: tsStatic.Diagnostic[] = []
|
||||
|
||||
const tsConfig = ts.getParsedCommandLineOfConfigFile(
|
||||
path.join(this.#projectRoot, "tsconfig.json"),
|
||||
this.#tsConfigPath,
|
||||
{
|
||||
inlineSourceMap: true,
|
||||
excludes: [],
|
||||
@@ -223,18 +241,17 @@ export class Compiler {
|
||||
/**
|
||||
* Display all config errors using the diagnostics reporter
|
||||
*/
|
||||
this.#printDiagnostics(ts, tsConfigErrors)
|
||||
|
||||
/**
|
||||
* Return undefined when there are errors in parsing the config
|
||||
* file
|
||||
*/
|
||||
if (tsConfigErrors.length) {
|
||||
const compilerHost = ts.createCompilerHost({})
|
||||
this.#logger.error(
|
||||
ts.formatDiagnosticsWithColorAndContext(tsConfigErrors, compilerHost)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
/**
|
||||
* If there are no errors, the `tsConfig` object will always exist.
|
||||
*/
|
||||
return tsConfig!
|
||||
return tsConfig
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -262,7 +279,7 @@ export class Compiler {
|
||||
*/
|
||||
const { emitResult, diagnostics } = await this.#emitBuildOutput(
|
||||
tsConfig,
|
||||
["integration-tests", "test", "unit-tests", "src/admin"],
|
||||
this.#backendIgnoreFiles,
|
||||
dist
|
||||
)
|
||||
|
||||
@@ -365,9 +382,61 @@ export class Compiler {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo. To be implemented
|
||||
*/
|
||||
// @todo
|
||||
buildPluginBackend() {}
|
||||
developPluginBacked() {}
|
||||
|
||||
/**
|
||||
* 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(onFileChange?: () => void) {
|
||||
const ts = await this.#loadTSCompiler()
|
||||
|
||||
/**
|
||||
* Format host is needed to print diagnostic messages
|
||||
*/
|
||||
const formatHost: tsStatic.FormatDiagnosticsHost = {
|
||||
getCanonicalFileName: (path) => path,
|
||||
getCurrentDirectory: ts.sys.getCurrentDirectory,
|
||||
getNewLine: () => ts.sys.newLine,
|
||||
}
|
||||
|
||||
/**
|
||||
* Creating a watcher compiler host to watch files and recompile
|
||||
* them as they are changed
|
||||
*/
|
||||
const host = ts.createWatchCompilerHost(
|
||||
this.#tsConfigPath,
|
||||
{
|
||||
outDir: this.#pluginsDistFolder,
|
||||
noCheck: true,
|
||||
},
|
||||
ts.sys,
|
||||
ts.createEmitAndSemanticDiagnosticsBuilderProgram,
|
||||
(diagnostic) => this.#printDiagnostics(ts, [diagnostic]),
|
||||
(diagnostic) => {
|
||||
if (typeof diagnostic.messageText === "string") {
|
||||
this.#logger.info(diagnostic.messageText)
|
||||
} else {
|
||||
this.#logger.info(
|
||||
ts.formatDiagnosticsWithColorAndContext([diagnostic], formatHost)
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
excludeDirectories: this.#backendIgnoreFiles,
|
||||
}
|
||||
)
|
||||
|
||||
const origPostProgramCreate = host.afterProgramCreate
|
||||
host.afterProgramCreate = (program) => {
|
||||
origPostProgramCreate!(program)
|
||||
onFileChange?.()
|
||||
}
|
||||
|
||||
ts.createWatchProgram(host)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user