Give each CLI module its own paths instead of importing from lib/

Each module now has a local paths.ts that calls findPaths(__dirname) directly.
This makes modules fully independent of lib/paths.ts, and the pattern will
continue to work when modules are extracted into separate packages since each
package's __dirname will resolve to its own root.

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Patrik Oldsberg
2026-02-20 23:36:59 +01:00
parent ca0fb2d9ef
commit 158bfe8a69
55 changed files with 221 additions and 48 deletions
+4 -1
View File
@@ -16,7 +16,10 @@
import fs from 'fs-extra';
import { resolve as resolvePath } from 'node:path';
import { paths } from '../paths';
import { findPaths } from '@backstage/cli-common';
/* eslint-disable-next-line no-restricted-syntax */
const paths = findPaths(__dirname);
const DEFAULT_CACHE_BASE_PATH = 'node_modules/.cache/backstage-cli';
+4 -1
View File
@@ -20,7 +20,10 @@ import {
} from '@backstage/cli-node';
import { resolve as resolvePath } from 'node:path';
import { Project, SourceFile, SyntaxKind, ts, Type } from 'ts-morph';
import { paths } from './paths';
import { findPaths } from '@backstage/cli-common';
/* eslint-disable-next-line no-restricted-syntax */
const paths = findPaths(__dirname);
export const createTypeDistProject = async () => {
return new Project({
+4 -1
View File
@@ -16,7 +16,10 @@
import fs from 'fs-extra';
import semver from 'semver';
import { paths } from './paths';
import { findPaths } from '@backstage/cli-common';
/* eslint-disable-next-line no-restricted-syntax */
const paths = findPaths(__dirname);
import { Lockfile } from './versioning';
/* eslint-disable @backstage/no-relative-monorepo-imports */
+4 -3
View File
@@ -19,12 +19,13 @@ import { getHasYarnPlugin } from './yarnPlugin';
const mockDir = createMockDirectory();
jest.mock('./paths', () => ({
paths: {
jest.mock('@backstage/cli-common', () => ({
...jest.requireActual('@backstage/cli-common'),
findPaths: () => ({
resolveTargetRoot(filename: string) {
return mockDir.resolve(filename);
},
},
}),
}));
describe('getHasYarnPlugin', () => {
+4 -1
View File
@@ -17,7 +17,10 @@
import fs from 'fs-extra';
import yaml from 'yaml';
import z from 'zod';
import { paths } from './paths';
import { findPaths } from '@backstage/cli-common';
/* eslint-disable-next-line no-restricted-syntax */
const paths = findPaths(__dirname);
const yarnRcSchema = z.object({
plugins: z
@@ -23,7 +23,7 @@ import {
PackageGraph,
PackageRoles,
} from '@backstage/cli-node';
import { paths } from '../../../../../lib/paths';
import { paths } from '../../../paths';
import { buildFrontend } from '../../../lib/buildFrontend';
import { buildBackend } from '../../../lib/buildBackend';
import { isValidUrl } from '../../../lib/urls';
@@ -18,7 +18,7 @@ import { OptionValues } from 'commander';
import { startPackage } from './startPackage';
import { resolveLinkedWorkspace } from './resolveLinkedWorkspace';
import { findRoleFromCommand } from '../../../lib/role';
import { paths } from '../../../../../lib/paths';
import { paths } from '../../../paths';
export async function command(opts: OptionValues): Promise<void> {
await startPackage({
@@ -16,7 +16,7 @@
import fs from 'fs-extra';
import { resolve as resolvePath } from 'node:path';
import { paths } from '../../../../../lib/paths';
import { paths } from '../../../paths';
import { runBackend } from '../../../lib/runner';
interface StartBackendOptions {
@@ -20,7 +20,7 @@ import {
getModuleFederationRemoteOptions,
serveBundle,
} from '../../../../build/lib/bundler';
import { paths } from '../../../../../lib/paths';
import { paths } from '../../../paths';
import { BackstagePackageJson } from '@backstage/cli-node';
import { hasReactDomClient } from '../../../../build/lib/bundler/hasReactDomClient';
@@ -18,7 +18,7 @@ import chalk from 'chalk';
import { Command, OptionValues } from 'commander';
import { relative as relativePath } from 'node:path';
import { buildPackages, getOutputsForRole } from '../../lib/builder';
import { paths } from '../../../../lib/paths';
import { paths } from '../../paths';
import {
BackstagePackage,
PackageGraph,
@@ -17,7 +17,7 @@
import { PackageGraph } from '@backstage/cli-node';
import { findTargetPackages } from './start';
import { posix } from 'node:path';
import { paths } from '../../../../lib/paths';
import { paths } from '../../paths';
const mocks = {
app: {
@@ -20,7 +20,7 @@ import {
PackageRole,
} from '@backstage/cli-node';
import { relative as relativePath } from 'node:path';
import { paths } from '../../../../lib/paths';
import { paths } from '../../paths';
import { resolveLinkedWorkspace } from '../package/start/resolveLinkedWorkspace';
import { startPackage } from '../package/start/startPackage';
import { parseArgs } from 'node:util';
@@ -39,7 +39,7 @@ import {
import { forwardFileImports, cssEntryPoints } from './plugins';
import { BuildOptions, Output } from './types';
import { paths } from '../../../../lib/paths';
import { paths } from '../../paths';
import { BackstagePackageJson } from '@backstage/cli-node';
import { readEntryPoints } from '../entryPoints';
@@ -18,7 +18,7 @@ import fs from 'fs-extra';
import { rollup, RollupOptions } from 'rollup';
import chalk from 'chalk';
import { relative as relativePath, resolve as resolvePath } from 'node:path';
import { paths } from '../../../../lib/paths';
import { paths } from '../../paths';
import { makeRollupConfigs } from './config';
import { BuildOptions, Output } from './types';
import { PackageRoles, runConcurrentTasks } from '@backstage/cli-node';
@@ -25,7 +25,7 @@ import { TsCheckerRspackPlugin } from 'ts-checker-rspack-plugin';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import ModuleScopePlugin from 'react-dev-utils/ModuleScopePlugin';
import { ModuleFederationPlugin } from '@module-federation/enhanced/rspack';
import { paths as cliPaths } from '../../../../lib/paths';
import { paths as cliPaths } from '../../paths';
import fs from 'fs-extra';
import { optimization as optimizationConfig } from './optimization';
import pickBy from 'lodash/pickBy';
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { paths } from '../../../../lib/paths';
import { paths } from '../../paths';
export function hasReactDomClient() {
try {
@@ -17,7 +17,7 @@
import { relative as relativePath } from 'node:path';
import { getPackages } from '@manypkg/get-packages';
import { rspack } from '@rspack/core';
import { paths } from '../../../../lib/paths';
import { paths } from '../../paths';
/**
* This returns of collection of plugins that links a separate workspace into
@@ -20,7 +20,7 @@ import chokidar from 'chokidar';
import fs from 'fs-extra';
import PQueue from 'p-queue';
import { dirname, join as joinPath, resolve as resolvePath } from 'node:path';
import { paths as cliPaths } from '../../../../lib/paths';
import { paths as cliPaths } from '../../paths';
const DETECTED_MODULES_MODULE_NAME = '__backstage-autodetected-plugins__';
@@ -16,7 +16,7 @@
import fs from 'fs-extra';
import { resolve as resolvePath } from 'node:path';
import { paths } from '../../../../lib/paths';
import { paths } from '../../paths';
export type BundlingPathsOptions = {
// bundle entrypoint, e.g. 'src/index'
@@ -22,7 +22,7 @@ import openBrowser from 'react-dev-utils/openBrowser';
import { rspack } from '@rspack/core';
import { RspackDevServer } from '@rspack/dev-server';
import { paths as libPaths } from '../../../../lib/paths';
import { paths as libPaths } from '../../paths';
import { loadCliConfig } from '../../../config/lib/config';
import { createConfig, resolveBaseUrl, resolveEndpoint } from './config';
import { createDetectedModulesEntryPoint } from './packageDetection';
@@ -24,7 +24,7 @@ import {
import { tmpdir } from 'node:os';
import * as tar from 'tar';
import partition from 'lodash/partition';
import { paths } from '../../../../lib/paths';
import { paths } from '../../paths';
import { run } from '@backstage/cli-common';
import {
dependencies as cliDependencies,
@@ -20,7 +20,7 @@ import { findRoleFromCommand } from './role';
const mockDir = createMockDirectory();
jest.mock('../../../lib/paths', () => ({
jest.mock('../paths', () => ({
paths: {
resolveTarget(filename: string) {
return mockDir.resolve(filename);
+1 -1
View File
@@ -16,7 +16,7 @@
import fs from 'fs-extra';
import { OptionValues } from 'commander';
import { paths } from '../../../lib/paths';
import { paths } from '../paths';
import { PackageRoles, PackageRole } from '@backstage/cli-node';
export async function findRoleFromCommand(
@@ -21,7 +21,7 @@ import { IpcServer, ServerDataStore } from '../ipc';
import debounce from 'lodash/debounce';
import { fileURLToPath } from 'node:url';
import { isAbsolute as isAbsolutePath } from 'node:path';
import { paths } from '../../../../lib/paths';
import { paths } from '../../paths';
import spawn from 'cross-spawn';
const loaderArgs = [
@@ -16,7 +16,7 @@
import { ConfigSources, loadConfigSchema } from '@backstage/config-loader';
import { AppConfig, ConfigReader } from '@backstage/config';
import { paths } from '../../../lib/paths';
import { paths } from '../paths';
import { getPackages } from '@manypkg/get-packages';
import { PackageGraph } from '@backstage/cli-node';
import { resolve as resolvePath } from 'node:path';
+20
View File
@@ -0,0 +1,20 @@
/*
* Copyright 2020 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { findPaths } from '@backstage/cli-common';
/* eslint-disable-next-line no-restricted-syntax */
export const paths = findPaths(__dirname);
@@ -18,7 +18,7 @@ import fs from 'fs-extra';
import chalk from 'chalk';
import { stringify as stringifyYaml } from 'yaml';
import inquirer, { Question, Answers } from 'inquirer';
import { paths } from '../../../../lib/paths';
import { paths } from '../../paths';
import { GithubCreateAppServer } from './GithubCreateAppServer';
import openBrowser from 'react-dev-utils/openBrowser';
@@ -0,0 +1,20 @@
/*
* Copyright 2020 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { findPaths } from '@backstage/cli-common';
/* eslint-disable-next-line no-restricted-syntax */
export const paths = findPaths(__dirname);
@@ -17,7 +17,7 @@
import { version as cliVersion } from '../../../../package.json';
import os from 'node:os';
import { runOutput } from '@backstage/cli-common';
import { paths } from '../../../lib/paths';
import { paths } from '../paths';
import { Lockfile } from '../../../lib/versioning';
import { BackstagePackageJson, PackageGraph } from '@backstage/cli-node';
import { minimatch } from 'minimatch';
+20
View File
@@ -0,0 +1,20 @@
/*
* Copyright 2020 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { findPaths } from '@backstage/cli-common';
/* eslint-disable-next-line no-restricted-syntax */
export const paths = findPaths(__dirname);
@@ -16,7 +16,7 @@
import fs from 'fs-extra';
import { OptionValues } from 'commander';
import { paths } from '../../../../lib/paths';
import { paths } from '../../paths';
import { ESLint } from 'eslint';
export default async (directories: string[], opts: OptionValues) => {
@@ -25,7 +25,7 @@ import {
Lockfile,
runWorkerQueueThreads,
} from '@backstage/cli-node';
import { paths } from '../../../../lib/paths';
import { paths } from '../../paths';
import { createScriptOptionsParser } from '../../../../lib/optionsParser';
import { SuccessCache } from '../../../../lib/cache/SuccessCache';
+20
View File
@@ -0,0 +1,20 @@
/*
* Copyright 2020 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { findPaths } from '@backstage/cli-common';
/* eslint-disable-next-line no-restricted-syntax */
export const paths = findPaths(__dirname);
@@ -15,7 +15,7 @@
*/
import fs from 'fs-extra';
import { paths } from '../../../../lib/paths';
import { paths } from '../../paths';
export default async function clean() {
await fs.remove(paths.resolveTarget('dist'));
@@ -18,7 +18,7 @@ import {
productionPack,
revertProductionPack,
} from '../../../../modules/build/lib/packager/productionPack';
import { paths } from '../../../../lib/paths';
import { paths } from '../../paths';
import fs from 'fs-extra';
import { publishPreflightCheck } from '../../lib/publishing';
import { createTypeDistProject } from '../../../../lib/typeDistProject';
@@ -17,7 +17,7 @@
import fs from 'fs-extra';
import { resolve as resolvePath } from 'node:path';
import { PackageGraph } from '@backstage/cli-node';
import { paths } from '../../../../lib/paths';
import { paths } from '../../paths';
import { run } from '@backstage/cli-common';
export async function command(): Promise<void> {
@@ -29,7 +29,7 @@ import {
relative as relativePath,
extname,
} from 'node:path';
import { paths } from '../../../../lib/paths';
import { paths } from '../../paths';
import { publishPreflightCheck } from '../../lib/publishing';
const SCRIPT_EXTS = ['.js', '.jsx', '.ts', '.tsx', '.json'];
@@ -19,7 +19,7 @@ import { ESLint } from 'eslint';
import { OptionValues } from 'commander';
import { relative as relativePath } from 'node:path';
import { PackageGraph } from '@backstage/cli-node';
import { paths } from '../../../../lib/paths';
import { paths } from '../../paths';
export async function command(opts: OptionValues) {
const packages = await PackageGraph.listTargetPackages();
@@ -0,0 +1,20 @@
/*
* Copyright 2020 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { findPaths } from '@backstage/cli-common';
/* eslint-disable-next-line no-restricted-syntax */
export const paths = findPaths(__dirname);
@@ -18,7 +18,7 @@ import fs from 'fs-extra';
import { resolve as resolvePath } from 'node:path';
import { getPackages } from '@manypkg/get-packages';
import { PackageRoles } from '@backstage/cli-node';
import { paths } from '../../../lib/paths';
import { paths } from '../paths';
export default async () => {
const { packages } = await getPackages(paths.targetDir);
@@ -25,7 +25,7 @@ import semver from 'semver';
import { OptionValues } from 'commander';
import { isError, NotFoundError } from '@backstage/errors';
import { resolve as resolvePath } from 'node:path';
import { paths } from '../../../../lib/paths';
import { paths } from '../../paths';
import { getHasYarnPlugin } from '../../../../lib/yarnPlugin';
import {
fetchPackageInfo,
+20
View File
@@ -0,0 +1,20 @@
/*
* Copyright 2020 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { findPaths } from '@backstage/cli-common';
/* eslint-disable-next-line no-restricted-syntax */
export const paths = findPaths(__dirname);
@@ -16,7 +16,7 @@
import fs from 'fs-extra';
import path from 'node:path';
import { paths } from '../../../../lib/paths';
import { paths } from '../../paths';
const TEAM_ID_RE = /^@[-\w]+\/[-\w]+$/;
const USER_ID_RE = /^@[-\w]+$/;
@@ -25,7 +25,7 @@ import upperCase from 'lodash/upperCase';
import upperFirst from 'lodash/upperFirst';
import lowerFirst from 'lodash/lowerFirst';
import { Lockfile } from '../../../../lib/versioning';
import { paths } from '../../../../lib/paths';
import { paths } from '../../paths';
import { createPackageVersionProvider } from '../../../../lib/version';
import { getHasYarnPlugin } from '../../../../lib/yarnPlugin';
@@ -16,7 +16,7 @@
import fs from 'fs-extra';
import upperFirst from 'lodash/upperFirst';
import camelCase from 'lodash/camelCase';
import { paths } from '../../../../lib/paths';
import { paths } from '../../paths';
import { Task } from '../tasks';
import { PortableTemplateInput } from '../types';
@@ -17,7 +17,7 @@
import { relative as relativePath } from 'node:path';
import { writeTemplateContents } from './writeTemplateContents';
import { createMockDirectory } from '@backstage/backend-test-utils';
import { paths } from '../../../../lib/paths';
import { paths } from '../../paths';
const baseConfig = {
version: '0.1.0',
@@ -17,7 +17,7 @@
import fs from 'fs-extra';
import { dirname, resolve as resolvePath } from 'node:path';
import { paths } from '../../../../lib/paths';
import { paths } from '../../paths';
import { PortableTemplate, PortableTemplateInput } from '../types';
import { ForwardedError, InputError } from '@backstage/errors';
import { isMonoRepo as getIsMonoRepo } from '@backstage/cli-node';
@@ -16,7 +16,7 @@
import inquirer, { DistinctQuestion } from 'inquirer';
import { getCodeownersFilePath, parseOwnerIds } from '../codeowners';
import { paths } from '../../../../lib/paths';
import { paths } from '../../paths';
import {
PortableTemplateConfig,
PortableTemplateInput,
@@ -20,7 +20,7 @@ import recursiveReaddir from 'recursive-readdir';
import { resolve as resolvePath, relative as relativePath } from 'node:path';
import { dirname } from 'node:path';
import { parse as parseYaml } from 'yaml';
import { paths } from '../../../../lib/paths';
import { paths } from '../../paths';
import {
PortableTemplateFile,
PortableTemplatePointer,
@@ -16,7 +16,7 @@
import fs from 'fs-extra';
import { resolve as resolvePath, dirname, isAbsolute } from 'node:path';
import { paths } from '../../../../lib/paths';
import { paths } from '../../paths';
import { defaultTemplates } from '../defaultTemplates';
import {
PortableTemplateConfig,
+20
View File
@@ -0,0 +1,20 @@
/*
* Copyright 2020 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { findPaths } from '@backstage/cli-common';
/* eslint-disable-next-line no-restricted-syntax */
export const paths = findPaths(__dirname);
@@ -15,7 +15,7 @@
*/
import { Command, OptionValues } from 'commander';
import { paths } from '../../../../lib/paths';
import { paths } from '../../paths';
import { runCheck } from '@backstage/cli-common';
function includesAnyOf(hayStack: string[], ...needles: string[]) {
@@ -23,7 +23,7 @@ import { run as runJest, yargsOptions as jestYargsOptions } from 'jest-cli';
import { relative as relativePath } from 'node:path';
import { Command, OptionValues } from 'commander';
import { Lockfile, PackageGraph } from '@backstage/cli-node';
import { paths } from '../../../../lib/paths';
import { paths } from '../../paths';
import { runCheck, runOutput } from '@backstage/cli-common';
import { isChildPath } from '@backstage/cli-common';
import { SuccessCache } from '../../../../lib/cache/SuccessCache';
+20
View File
@@ -0,0 +1,20 @@
/*
* Copyright 2020 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { findPaths } from '@backstage/cli-common';
/* eslint-disable-next-line no-restricted-syntax */
export const paths = findPaths(__dirname);