From d2e166cbb207d633474931fba34bc5e726524064 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 22 Oct 2024 12:54:41 +0200 Subject: [PATCH] cli: add local linking of backend packages too Signed-off-by: Patrik Oldsberg --- .../local-dev/linking-local-packages.md | 3 +- packages/cli/config/nodeTransform.cjs | 33 +++++++++++++++++++ packages/cli/src/commands/start/command.ts | 10 ------ .../cli/src/commands/start/startBackend.ts | 3 ++ packages/cli/src/lib/runner/runBackend.ts | 3 ++ 5 files changed, 41 insertions(+), 11 deletions(-) diff --git a/docs/tooling/local-dev/linking-local-packages.md b/docs/tooling/local-dev/linking-local-packages.md index a7e967ac59..92e9bd95fa 100644 --- a/docs/tooling/local-dev/linking-local-packages.md +++ b/docs/tooling/local-dev/linking-local-packages.md @@ -22,7 +22,6 @@ instance of Backstage. :::info Workspace linking is an experimental feature and may not work in all cases. -It currently only works for frontend packages. ::: The `backstage-cli package start` command that is used for local development of all packages supports a `--link` flag that can be used to link a single external workspace to the current workspace. It hooks into the module resolution and will override all imports of packages in the linked workspace to be imported from there instead. The only exception are the `react` and `react-dom` packages, which will always be resolved from the target package. @@ -39,6 +38,8 @@ The path provided to the `--link` option can be a relative or absolute path, and With the `start` command up and running and serving the development version of your frontend app in the browser, you can now make changes to both workspaces and see the changes reflected in the browser. +You can also link backend packages using the exact same process, simply start your backend package with the same `--link ` option. + ## Common Problems ### React errors diff --git a/packages/cli/config/nodeTransform.cjs b/packages/cli/config/nodeTransform.cjs index a5eb4e8fb0..9fb3cadeb5 100644 --- a/packages/cli/config/nodeTransform.cjs +++ b/packages/cli/config/nodeTransform.cjs @@ -16,6 +16,39 @@ const { transformSync } = require('@swc/core'); const { addHook } = require('pirates'); +const { Module } = require('module'); + +// This hooks into module resolution and overrides imports of packages that +// exist in the linked workspace to instead be resolved from the linked workspace. +if (process.env.LINKED_WORKSPACE) { + const { join: joinPath } = require('path'); + const { getPackagesSync } = require('@manypkg/get-packages'); + const { packages: linkedPackages, root: linkedRoot } = getPackagesSync( + process.env.LINKED_WORKSPACE, + ); + + // Matches all packages in the linked workspaces, as well as sub-path exports from them + const replacementRegex = new RegExp( + `^(?:${linkedPackages + .map(pkg => pkg.packageJson.name) + .join('|')})(?:/.*)?$`, + ); + + const origLoad = Module._load; + Module._load = function requireHook(request, parent) { + if (!replacementRegex.test(request)) { + return origLoad.call(this, request, parent); + } + + // The package import that we're overriding will always existing in the root + // node_modules of the linked workspace, so it's enough to override the the + // parent paths with that single entry + return origLoad.call(this, request, { + ...parent, + paths: [joinPath(linkedRoot.dir, 'node_modules')], + }); + }; +} addHook( (code, filename) => { diff --git a/packages/cli/src/commands/start/command.ts b/packages/cli/src/commands/start/command.ts index 08d84ee2eb..10dc19cad8 100644 --- a/packages/cli/src/commands/start/command.ts +++ b/packages/cli/src/commands/start/command.ts @@ -34,20 +34,10 @@ export async function command(opts: OptionValues): Promise { switch (role) { case 'backend': - if (options.linkedWorkspace) { - throw new Error( - 'The --link flag is not supported for this package role', - ); - } return startBackend(options); case 'backend-plugin': case 'backend-plugin-module': case 'node-library': - if (options.linkedWorkspace) { - throw new Error( - 'The --link flag is not supported for this package role', - ); - } return startBackendPlugin(options); case 'frontend': return startFrontend({ diff --git a/packages/cli/src/commands/start/startBackend.ts b/packages/cli/src/commands/start/startBackend.ts index 4f62044de7..84f9eb0890 100644 --- a/packages/cli/src/commands/start/startBackend.ts +++ b/packages/cli/src/commands/start/startBackend.ts @@ -22,6 +22,7 @@ interface StartBackendOptions { checksEnabled: boolean; inspectEnabled: boolean; inspectBrkEnabled: boolean; + linkedWorkspace?: string; require?: string; } @@ -30,6 +31,7 @@ export async function startBackend(options: StartBackendOptions) { entry: 'src/index', inspectEnabled: options.inspectEnabled, inspectBrkEnabled: options.inspectBrkEnabled, + linkedWorkspace: options.linkedWorkspace, require: options.require, }); @@ -52,6 +54,7 @@ export async function startBackendPlugin(options: StartBackendOptions) { inspectEnabled: options.inspectEnabled, inspectBrkEnabled: options.inspectBrkEnabled, require: options.require, + linkedWorkspace: options.linkedWorkspace, }); await waitForExit(); diff --git a/packages/cli/src/lib/runner/runBackend.ts b/packages/cli/src/lib/runner/runBackend.ts index f0ce971fee..2d6722027e 100644 --- a/packages/cli/src/lib/runner/runBackend.ts +++ b/packages/cli/src/lib/runner/runBackend.ts @@ -40,6 +40,8 @@ export type RunBackendOptions = { inspectBrkEnabled: boolean; /** Additional module to require via the --require flag to the node process */ require?: string; + /** An external linked workspace to override module resolution towards */ + linkedWorkspace?: string; }; export async function runBackend(options: RunBackendOptions) { @@ -119,6 +121,7 @@ export async function runBackend(options: RunBackendOptions) { stdio: ['ignore', 'inherit', 'inherit', 'ipc'], env: { ...process.env, + LINKED_WORKSPACE: options.linkedWorkspace, BACKSTAGE_CLI_CHANNEL: '1', ESBK_TSCONFIG_PATH: paths.resolveTargetRoot('tsconfig.json'), },