From 69e2a6d6951c133295947d00fa1f99547da467d2 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 13 Jan 2025 18:38:02 +0530 Subject: [PATCH] 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. --- .changeset/forty-lamps-fly.md | 7 ++ packages/cli/medusa-cli/src/create-cli.ts | 14 ++- .../framework/src/build-tools/compiler.ts | 111 ++++++++++++++---- packages/medusa/package.json | 11 +- .../medusa/src/commands/plugin/develop.ts | 19 +++ yarn.lock | 80 ++++++++++++- 6 files changed, 216 insertions(+), 26 deletions(-) create mode 100644 .changeset/forty-lamps-fly.md create mode 100644 packages/medusa/src/commands/plugin/develop.ts diff --git a/.changeset/forty-lamps-fly.md b/.changeset/forty-lamps-fly.md new file mode 100644 index 0000000000000..964a8a97db6ee --- /dev/null +++ b/.changeset/forty-lamps-fly.md @@ -0,0 +1,7 @@ +--- +"@medusajs/medusa": patch +"@medusajs/framework": patch +"@medusajs/cli": patch +--- + +Feat/plugin develop diff --git a/packages/cli/medusa-cli/src/create-cli.ts b/packages/cli/medusa-cli/src/create-cli.ts index ca42159b41882..e52ff2cecc1b5 100644 --- a/packages/cli/medusa-cli/src/create-cli.ts +++ b/packages/cli/medusa-cli/src/create-cli.ts @@ -254,6 +254,18 @@ function buildLocalCommands(cli, isLocalProject) { }) ), }) + .command({ + command: "plugin:develop", + desc: "Start plugin development process in watch mode. Changes will be re-published to the local packages registry", + builder: (builder) => {}, + handler: handlerP( + getCommandHandler("plugin/develop", (args, cmd) => { + process.env.NODE_ENV = process.env.NODE_ENV || `development` + cmd(args) + return new Promise(() => {}) + }) + ), + }) .command({ command: `telemetry`, describe: `Enable or disable collection of anonymous usage data.`, @@ -310,7 +322,7 @@ function buildLocalCommands(cli, isLocalProject) { // Return an empty promise to prevent handlerP from exiting early. // The development server shouldn't ever exit until the user directly // kills it so this is fine. - return new Promise((resolve) => {}) + return new Promise(() => {}) }) ), }) diff --git a/packages/core/framework/src/build-tools/compiler.ts b/packages/core/framework/src/build-tools/compiler.ts index 2eb383555d513..91606077c4e32 100644 --- a/packages/core/framework/src/build-tools/compiler.ts +++ b/packages/core/framework/src/build-tools/compiler.ts @@ -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 + buildPluginBackend() {} + /** - * @todo. To be implemented + * 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. */ - buildPluginBackend() {} - developPluginBacked() {} + 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) + } } diff --git a/packages/medusa/package.json b/packages/medusa/package.json index 1a75509a76ce2..28fd26d61b26c 100644 --- a/packages/medusa/package.json +++ b/packages/medusa/package.json @@ -57,7 +57,8 @@ "@types/multer": "^1.4.7", "jest": "^29.7.0", "rimraf": "^5.0.1", - "typescript": "^5.6.2" + "typescript": "^5.6.2", + "yalc": "1.0.0-pre.53" }, "dependencies": { "@inquirer/checkbox": "^2.3.11", @@ -130,7 +131,13 @@ "@mikro-orm/knex": "5.9.7", "@mikro-orm/migrations": "5.9.7", "@mikro-orm/postgresql": "5.9.7", - "awilix": "^8.0.1" + "awilix": "^8.0.1", + "yalc": "1.0.0-pre.53" + }, + "peerDependenciesMeta": { + "yalc": { + "optional": true + } }, "gitHead": "cd1f5afa5aa8c0b15ea957008ee19f1d695cbd2e" } diff --git a/packages/medusa/src/commands/plugin/develop.ts b/packages/medusa/src/commands/plugin/develop.ts new file mode 100644 index 0000000000000..91eb1d1af574b --- /dev/null +++ b/packages/medusa/src/commands/plugin/develop.ts @@ -0,0 +1,19 @@ +import * as yalc from "yalc" +import { logger } from "@medusajs/framework/logger" +import { Compiler } from "@medusajs/framework/build-tools" + +export default async function developPlugin({ + directory, +}: { + directory: string +}) { + const compiler = new Compiler(directory, logger) + await compiler.developPluginBackend(async () => { + await yalc.publishPackage({ + push: true, + workingDir: directory, + changed: true, + replace: true, + }) + }) +} diff --git a/yarn.lock b/yarn.lock index 6ce8ed89ad825..4d4c79f905811 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6132,6 +6132,7 @@ __metadata: slugify: ^1.6.6 typescript: ^5.6.2 uuid: ^9.0.0 + yalc: 1.0.0-pre.53 zod: 3.22.4 peerDependencies: "@medusajs/framework": ^2.0.0 @@ -6140,6 +6141,10 @@ __metadata: "@mikro-orm/migrations": 5.9.7 "@mikro-orm/postgresql": 5.9.7 awilix: ^8.0.1 + yalc: 1.0.0-pre.53 + peerDependenciesMeta: + yalc: + optional: true languageName: unknown linkType: soft @@ -20760,7 +20765,7 @@ __metadata: languageName: node linkType: hard -"fs-extra@npm:8.1.0, fs-extra@npm:^8.1, fs-extra@npm:^8.1.0": +"fs-extra@npm:8.1.0, fs-extra@npm:^8.0.1, fs-extra@npm:^8.1, fs-extra@npm:^8.1.0": version: 8.1.0 resolution: "fs-extra@npm:8.1.0" dependencies: @@ -21797,6 +21802,15 @@ __metadata: languageName: node linkType: hard +"ignore-walk@npm:^3.0.3": + version: 3.0.4 + resolution: "ignore-walk@npm:3.0.4" + dependencies: + minimatch: ^3.0.4 + checksum: 690372b433887796fa3badd25babab7daf60a1882259dcc130ec78eea79745c2416322e10d1a96b367071204471c532647d20b11cd7ab70bd9b49879e461f956 + languageName: node + linkType: hard + "ignore@npm:^4.0.6": version: 4.0.6 resolution: "ignore@npm:4.0.6" @@ -21804,6 +21818,13 @@ __metadata: languageName: node linkType: hard +"ignore@npm:^5.0.4": + version: 5.3.2 + resolution: "ignore@npm:5.3.2" + checksum: f9f652c957983634ded1e7f02da3b559a0d4cc210fca3792cb67f1b153623c9c42efdc1c4121af171e295444459fc4a9201101fb041b1104a3c000bccb188337 + languageName: node + linkType: hard + "ignore@npm:^5.2.0, ignore@npm:^5.2.4": version: 5.3.1 resolution: "ignore@npm:5.3.1" @@ -21936,6 +21957,13 @@ __metadata: languageName: node linkType: hard +"ini@npm:^2.0.0": + version: 2.0.0 + resolution: "ini@npm:2.0.0" + checksum: 2e0c8f386369139029da87819438b20a1ff3fe58372d93fb1a86e9d9344125ace3a806b8ec4eb160a46e64cbc422fe68251869441676af49b7fc441af2389c25 + languageName: node + linkType: hard + "inquirer@npm:^8.0.0": version: 8.2.6 resolution: "inquirer@npm:8.2.6" @@ -25831,6 +25859,36 @@ __metadata: languageName: node linkType: hard +"npm-bundled@npm:^1.1.1": + version: 1.1.2 + resolution: "npm-bundled@npm:1.1.2" + dependencies: + npm-normalize-package-bin: ^1.0.1 + checksum: 3f2337789afc8cb608a0dd71cefe459531053d48a5497db14b07b985c4cab15afcae88600db9f92eae072c89b982eeeec8e4463e1d77bc03a7e90f5dacf29769 + languageName: node + linkType: hard + +"npm-normalize-package-bin@npm:^1.0.1": + version: 1.0.1 + resolution: "npm-normalize-package-bin@npm:1.0.1" + checksum: b0c8c05fe419a122e0ff970ccbe7874ae24b4b4b08941a24d18097fe6e1f4b93e3f6abfb5512f9c5488827a5592f2fb3ce2431c41d338802aed24b9a0c160551 + languageName: node + linkType: hard + +"npm-packlist@npm:^2.1.5": + version: 2.2.2 + resolution: "npm-packlist@npm:2.2.2" + dependencies: + glob: ^7.1.6 + ignore-walk: ^3.0.3 + npm-bundled: ^1.1.1 + npm-normalize-package-bin: ^1.0.1 + bin: + npm-packlist: bin/index.js + checksum: cf0b1350bfa2e4bdef5e283365fb54811bd095f4b6c8e5f1352a12a155f9aafbd22776b5a79fea7c5e952fab2e72c40f54cea2e139d7d705cfc6f6f955f1aa48 + languageName: node + linkType: hard + "npm-run-path@npm:^2.0.0": version: 2.0.2 resolution: "npm-run-path@npm:2.0.2" @@ -33799,6 +33857,24 @@ __metadata: languageName: node linkType: hard +"yalc@npm:1.0.0-pre.53": + version: 1.0.0-pre.53 + resolution: "yalc@npm:1.0.0-pre.53" + dependencies: + chalk: ^4.1.0 + detect-indent: ^6.0.0 + fs-extra: ^8.0.1 + glob: ^7.1.4 + ignore: ^5.0.4 + ini: ^2.0.0 + npm-packlist: ^2.1.5 + yargs: ^16.1.1 + bin: + yalc: src/yalc.js + checksum: 630f65b00740da6d568d46748a40e2bf2c872cf9babe7c319642a5b6db2dcd0a5d4a34e249d20099709e3ba09bb7e9b34ff78af5cd54c690668e094e156551c9 + languageName: node + linkType: hard + "yallist@npm:^2.1.2": version: 2.1.2 resolution: "yallist@npm:2.1.2" @@ -33916,7 +33992,7 @@ __metadata: languageName: node linkType: hard -"yargs@npm:^16.1.0": +"yargs@npm:^16.1.0, yargs@npm:^16.1.1": version: 16.2.0 resolution: "yargs@npm:16.2.0" dependencies: