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:
Harminder Virk
2025-01-13 18:38:02 +05:30
committed by GitHub
parent ecf73780e0
commit 69e2a6d695
6 changed files with 217 additions and 27 deletions

View File

@@ -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)
}
}