From 70fc178697cc59e7622b645c41c5e4cb45b2c6a5 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 21 Feb 2026 15:16:58 +0100 Subject: [PATCH] Replace findPaths with targetPaths and findOwnPaths Split the path resolution API in @backstage/cli-common into targetPaths (cwd-based singleton) and findOwnPaths (package-relative). Migrate all consumers across the repo away from the deprecated findPaths. Rename TargetPaths/OwnPaths properties to resolve/resolveRoot, removing the redundant type prefix from property names. Make findOwnPaths calls lazy in modules - called inside functions rather than at module scope. Signed-off-by: Patrik Oldsberg Co-authored-by: Cursor --- .changeset/cli-common-cached-paths.md | 4 +- .changeset/migrate-to-target-paths.md | 11 +++ .../src/manager/plugin-manager.test.ts | 31 ++++++- .../src/manager/plugin-manager.ts | 4 +- .../src/schemas/schemas.ts | 4 +- packages/backend/src/index.ts | 2 +- packages/cli-common/src/paths.ts | 93 +++++++------------ packages/cli-node/src/paths.ts | 23 ++++- packages/cli/src/lib/cache/SuccessCache.ts | 2 +- packages/cli/src/lib/typeDistProject.ts | 2 +- packages/cli/src/lib/version.ts | 6 +- packages/cli/src/lib/yarnPlugin.test.ts | 17 ++-- packages/cli/src/lib/yarnPlugin.ts | 2 +- .../build/commands/package/build/command.ts | 10 +- .../build/commands/package/start/command.ts | 2 +- .../commands/package/start/startBackend.ts | 2 +- .../commands/package/start/startFrontend.ts | 4 +- .../src/modules/build/commands/repo/build.ts | 2 +- .../modules/build/commands/repo/start.test.ts | 2 +- .../src/modules/build/commands/repo/start.ts | 4 +- .../src/modules/build/lib/builder/config.ts | 6 +- .../src/modules/build/lib/builder/packager.ts | 10 +- .../src/modules/build/lib/bundler/config.ts | 2 +- .../build/lib/bundler/hasReactDomClient.ts | 2 +- .../build/lib/bundler/linkWorkspaces.ts | 2 +- .../build/lib/bundler/packageDetection.ts | 2 +- .../src/modules/build/lib/bundler/paths.ts | 14 ++- .../src/modules/build/lib/bundler/server.ts | 4 +- .../build/lib/packager/createDistWorkspace.ts | 10 +- .../cli/src/modules/build/lib/role.test.ts | 5 +- packages/cli/src/modules/build/lib/role.ts | 2 +- .../modules/build/lib/runner/runBackend.ts | 2 +- packages/cli/src/modules/config/lib/config.ts | 6 +- .../commands/create-github-app/index.ts | 2 +- .../cli/src/modules/info/commands/info.ts | 11 +-- .../src/modules/lint/commands/package/lint.ts | 6 +- .../src/modules/lint/commands/repo/lint.ts | 10 +- .../maintenance/commands/package/clean.ts | 6 +- .../maintenance/commands/package/pack.ts | 8 +- .../maintenance/commands/repo/clean.ts | 6 +- .../modules/maintenance/commands/repo/fix.ts | 12 +-- .../commands/repo/list-deprecations.ts | 6 +- .../modules/migrate/commands/packageRole.ts | 2 +- .../migrate/commands/versions/bump.test.ts | 8 +- .../modules/migrate/commands/versions/bump.ts | 6 +- .../migrate/commands/versions/migrate.test.ts | 8 +- .../modules/new/lib/codeowners/codeowners.ts | 2 +- .../new/lib/execution/PortableTemplater.ts | 2 +- .../new/lib/execution/installNewPackage.ts | 6 +- .../execution/writeTemplateContents.test.ts | 4 +- .../lib/execution/writeTemplateContents.ts | 2 +- .../collectPortableTemplateInput.ts | 2 +- .../lib/preparation/loadPortableTemplate.ts | 2 +- .../preparation/loadPortableTemplateConfig.ts | 2 +- .../src/modules/test/commands/package/test.ts | 4 +- .../src/modules/test/commands/repo/test.ts | 17 ++-- packages/codemods/src/action.ts | 9 +- .../src/sources/ConfigSources.ts | 4 +- packages/create-app/src/createApp.test.ts | 49 ++++++---- packages/create-app/src/createApp.ts | 16 ++-- packages/e2e-test/src/commands/runCommand.ts | 21 +++-- packages/repo-tools/src/lib/paths.ts | 27 ++++-- .../techdocs-cli/src/commands/serve/serve.ts | 5 +- packages/yarn-plugin/src/index.test.ts | 4 +- .../src/util/getWorkspaceRoot.test.ts | 14 +-- .../yarn-plugin/src/util/getWorkspaceRoot.ts | 8 +- 66 files changed, 311 insertions(+), 274 deletions(-) create mode 100644 .changeset/migrate-to-target-paths.md diff --git a/.changeset/cli-common-cached-paths.md b/.changeset/cli-common-cached-paths.md index 27a683e35a..a82a5434b0 100644 --- a/.changeset/cli-common-cached-paths.md +++ b/.changeset/cli-common-cached-paths.md @@ -1,5 +1,5 @@ --- -'@backstage/cli-common': patch +'@backstage/cli-common': minor --- -Added hierarchical caching to `findOwnDir`, avoiding redundant filesystem walks when `findPaths` is called from multiple locations within the same package. +Added `targetPaths` and `findOwnPaths` as replacements for `findPaths`, with a cleaner separation between target project paths and package-relative paths. diff --git a/.changeset/migrate-to-target-paths.md b/.changeset/migrate-to-target-paths.md new file mode 100644 index 0000000000..b3f983bffc --- /dev/null +++ b/.changeset/migrate-to-target-paths.md @@ -0,0 +1,11 @@ +--- +'@backstage/cli-node': patch +'@backstage/backend-dynamic-feature-service': patch +'@backstage/codemods': patch +'@backstage/config-loader': patch +'@backstage/create-app': patch +'@backstage/repo-tools': patch +'@backstage/techdocs-cli': patch +--- + +Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`. diff --git a/packages/backend-dynamic-feature-service/src/manager/plugin-manager.test.ts b/packages/backend-dynamic-feature-service/src/manager/plugin-manager.test.ts index 1f8fa2cfd6..011aae0457 100644 --- a/packages/backend-dynamic-feature-service/src/manager/plugin-manager.test.ts +++ b/packages/backend-dynamic-feature-service/src/manager/plugin-manager.test.ts @@ -38,8 +38,33 @@ import { createSpecializedBackend } from '@backstage/backend-app-api'; import { ConfigSources } from '@backstage/config-loader'; import { Logs, MockedLogger, LogContent } from '../__testUtils__/testUtils'; import { PluginScanner } from '../scanner/plugin-scanner'; -import { findPaths } from '@backstage/cli-common'; +import { targetPaths } from '@backstage/cli-common'; import { createMockDirectory } from '@backstage/backend-test-utils'; + +jest.mock('@backstage/cli-common', () => { + const path = require('path'); + const actual = jest.requireActual( + '@backstage/cli-common', + ); + const mockRoot = path.resolve(__dirname, '..', '..', '..', '..'); + return { + ...actual, + targetPaths: { + resolve: (...paths: string[]) => path.join(mockRoot, ...paths), + resolveRoot: (...paths: string[]) => path.join(mockRoot, ...paths), + }, + findPaths: (searchDir: string) => ({ + targetRoot: mockRoot, + targetDir: mockRoot, + ownDir: path.dirname(searchDir), + ownRoot: mockRoot, + resolveOwn: (...p: string[]) => path.join(path.dirname(searchDir), ...p), + resolveOwnRoot: (...p: string[]) => path.join(mockRoot, ...p), + resolveTarget: (...p: string[]) => path.join(mockRoot, ...p), + resolveTargetRoot: (...p: string[]) => path.join(mockRoot, ...p), + }), + }; +}); import { rootLifecycleServiceFactory } from '@backstage/backend-defaults/rootLifecycle'; import { BackstagePackageJson, PackageRole } from '@backstage/cli-node'; @@ -956,7 +981,7 @@ describe('backend-dynamic-feature-service', () => { mockDir.setContent({ 'package.json': fs.readFileSync( - findPaths(__dirname).resolveTargetRoot('package.json'), + targetPaths.resolveRoot('package.json'), ), 'dynamic-plugins-root': {}, 'dynamic-plugins-root/a-dynamic-plugin': ctx => @@ -1044,7 +1069,7 @@ describe('backend-dynamic-feature-service', () => { otherMockDir.resolve('a-dynamic-plugin'), ); expect(mockedModuleLoader.bootstrap).toHaveBeenCalledWith( - findPaths(__dirname).targetRoot, + targetPaths.resolveRoot(), [realPath], new Map([ [ diff --git a/packages/backend-dynamic-feature-service/src/manager/plugin-manager.ts b/packages/backend-dynamic-feature-service/src/manager/plugin-manager.ts index c4cb33892d..5fe4f7241c 100644 --- a/packages/backend-dynamic-feature-service/src/manager/plugin-manager.ts +++ b/packages/backend-dynamic-feature-service/src/manager/plugin-manager.ts @@ -35,7 +35,7 @@ import { createServiceRef, } from '@backstage/backend-plugin-api'; import { PackageRole, PackageRoles } from '@backstage/cli-node'; -import { findPaths } from '@backstage/cli-common'; +import { targetPaths } from '@backstage/cli-common'; import * as fs from 'node:fs'; /** @@ -56,7 +56,7 @@ export class DynamicPluginManager implements DynamicPluginProvider { options: DynamicPluginManagerOptions, ): Promise { /* eslint-disable-next-line no-restricted-syntax */ - const backstageRoot = findPaths(__dirname).targetRoot; + const backstageRoot = targetPaths.resolveRoot(); const scanner = PluginScanner.create({ config: options.config, logger: options.logger, diff --git a/packages/backend-dynamic-feature-service/src/schemas/schemas.ts b/packages/backend-dynamic-feature-service/src/schemas/schemas.ts index d50742f8fb..3a1bc666b0 100644 --- a/packages/backend-dynamic-feature-service/src/schemas/schemas.ts +++ b/packages/backend-dynamic-feature-service/src/schemas/schemas.ts @@ -20,7 +20,7 @@ import { createServiceFactory, createServiceRef, } from '@backstage/backend-plugin-api'; -import { findPaths } from '@backstage/cli-common'; +import { targetPaths } from '@backstage/cli-common'; import fs from 'fs-extra'; import * as path from 'node:path'; @@ -100,7 +100,7 @@ const dynamicPluginsSchemasServiceFactoryWithOptions = ( config, logger, // eslint-disable-next-line no-restricted-syntax - backstageRoot: findPaths(__dirname).targetRoot, + backstageRoot: targetPaths.resolveRoot(), preferAlpha: true, }); diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 9c42c6b740..14eb36a3a6 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -32,7 +32,7 @@ const searchLoader = createBackendFeatureLoader({ *loader({ config }) { yield import('@backstage/plugin-search-backend'); yield import('@backstage/plugin-search-backend-module-catalog'); - yield import('@backstage-community/plugin-search-backend-module-explore'); + yield import('@backstage/plugin-search-backend-module-explore'); yield import('@backstage/plugin-search-backend-module-techdocs'); if (config.has('search.elasticsearch')) { yield import('@backstage/plugin-search-backend-module-elasticsearch'); diff --git a/packages/cli-common/src/paths.ts b/packages/cli-common/src/paths.ts index c0e80b24f6..15cdc7b89c 100644 --- a/packages/cli-common/src/paths.ts +++ b/packages/cli-common/src/paths.ts @@ -26,45 +26,31 @@ import { dirname, resolve as resolvePath } from 'node:path'; export type ResolveFunc = (...paths: string[]) => string; /** - * Paths relative to the target project that the CLI is operating on, based on - * `process.cwd()`. This can be imported directly — no `__dirname` needed. - * - * Lazily initialized on first access and re-resolved if `process.cwd()` changes. + * Resolved paths relative to the target project, based on `process.cwd()`. + * Lazily initialized on first property access. Re-resolves automatically + * when `process.cwd()` changes. * * @public */ export type TargetPaths = { - // The location of the app that the cli is being executed in - targetDir: string; + /** Resolve a path relative to the target package directory. */ + resolve: ResolveFunc; - // The monorepo root package of the app that the cli is being executed in. - targetRoot: string; - - // Resolve a path relative to the app - resolveTarget: ResolveFunc; - - // Resolve a path relative to the app repo root - resolveTargetRoot: ResolveFunc; + /** Resolve a path relative to the target repo root. */ + resolveRoot: ResolveFunc; }; /** - * Paths relative to the package that the calling code lives in. Requires - * `__dirname` to locate the package root. + * Resolved paths relative to a specific package in the repository. * * @public */ export type OwnPaths = { - // Root dir of the package itself, containing package.json - ownDir: string; + /** Resolve a path relative to the package root. */ + resolve: ResolveFunc; - // Monorepo root dir of the package. Only accessible when running inside Backstage repo. - ownRoot: string; - - // Resolve a path relative to own package - resolveOwn: ResolveFunc; - - // Resolve a path relative to own monorepo root. Only accessible when running inside Backstage repo. - resolveOwnRoot: ResolveFunc; + /** Resolve a path relative to the monorepo root containing the package. */ + resolveRoot: ResolveFunc; }; /** @@ -211,41 +197,34 @@ function getTargetRoot(): string { * Lazily resolved paths relative to the target project. Import this directly * for cwd-based path resolution without needing `__dirname`. * - * Re-resolves automatically if `process.cwd()` changes. - * * @public * @example * - * import { targetPaths } from '@backstage/cli-common'; + * import { targetPaths } from '\@backstage/cli-common'; * - * const root = targetPaths.targetRoot; - * const lockfile = targetPaths.resolveTargetRoot('yarn.lock'); + * const lockfile = targetPaths.resolveRoot('yarn.lock'); */ export const targetPaths: TargetPaths = { - get targetDir() { - return getTargetDir(); - }, - get targetRoot() { - return getTargetRoot(); - }, - resolveTarget: (...paths) => resolvePath(getTargetDir(), ...paths), - resolveTargetRoot: (...paths) => resolvePath(getTargetRoot(), ...paths), + resolve: (...paths) => resolvePath(getTargetDir(), ...paths), + resolveRoot: (...paths) => resolvePath(getTargetRoot(), ...paths), }; const ownPathsCache = new Map(); /** - * Find paths relative to the package that the calling code lives in. Cheap to - * call repeatedly — results are cached per package root, and the package root - * lookup uses a hierarchical cache so sibling directories share work. + * Find paths relative to the package that the calling code lives in. + * + * Results are cached per package root, and the package root lookup uses a + * hierarchical directory cache so that multiple calls from different + * subdirectories within the same package share work. * * @public * @example * - * import { findOwnPaths } from '@backstage/cli-common'; + * import { findOwnPaths } from '\@backstage/cli-common'; * - * const ownPaths = findOwnPaths(__dirname); - * const config = ownPaths.resolveOwn('config/jest.js'); + * const own = findOwnPaths(__dirname); + * const config = own.resolve('config/jest.js'); */ export function findOwnPaths(searchDir: string): OwnPaths { const ownDir = findOwnDir(searchDir); @@ -263,12 +242,8 @@ export function findOwnPaths(searchDir: string): OwnPaths { }; const paths: OwnPaths = { - ownDir, - get ownRoot() { - return getOwnRoot(); - }, - resolveOwn: (...p) => resolvePath(ownDir, ...p), - resolveOwnRoot: (...p) => resolvePath(getOwnRoot(), ...p), + resolve: (...p) => resolvePath(ownDir, ...p), + resolveRoot: (...p) => resolvePath(getOwnRoot(), ...p), }; ownPathsCache.set(ownDir, paths); @@ -290,21 +265,21 @@ export function findPaths(searchDir: string): Paths { const own = findOwnPaths(searchDir); return { get ownDir() { - return own.ownDir; + return own.resolve(); }, get ownRoot() { - return own.ownRoot; + return own.resolveRoot(); }, get targetDir() { - return targetPaths.targetDir; + return targetPaths.resolve(); }, get targetRoot() { - return targetPaths.targetRoot; + return targetPaths.resolveRoot(); }, - resolveOwn: own.resolveOwn, - resolveOwnRoot: own.resolveOwnRoot, - resolveTarget: targetPaths.resolveTarget, - resolveTargetRoot: targetPaths.resolveTargetRoot, + resolveOwn: own.resolve, + resolveOwnRoot: own.resolveRoot, + resolveTarget: targetPaths.resolve, + resolveTargetRoot: targetPaths.resolveRoot, }; } diff --git a/packages/cli-node/src/paths.ts b/packages/cli-node/src/paths.ts index 2c658c27b3..57c98ea0a8 100644 --- a/packages/cli-node/src/paths.ts +++ b/packages/cli-node/src/paths.ts @@ -14,7 +14,26 @@ * limitations under the License. */ -import { findPaths } from '@backstage/cli-common'; +import { targetPaths, findOwnPaths } from '@backstage/cli-common'; + +const ownPaths = findOwnPaths(__dirname); /* eslint-disable-next-line no-restricted-syntax */ -export const paths = findPaths(__dirname); +export const paths = { + get ownDir() { + return ownPaths.resolve(); + }, + get ownRoot() { + return ownPaths.resolveRoot(); + }, + get targetDir() { + return targetPaths.resolve(); + }, + get targetRoot() { + return targetPaths.resolveRoot(); + }, + resolveOwn: ownPaths.resolve, + resolveOwnRoot: ownPaths.resolveRoot, + resolveTarget: targetPaths.resolve, + resolveTargetRoot: targetPaths.resolveRoot, +}; diff --git a/packages/cli/src/lib/cache/SuccessCache.ts b/packages/cli/src/lib/cache/SuccessCache.ts index bf7436ed62..495baf660c 100644 --- a/packages/cli/src/lib/cache/SuccessCache.ts +++ b/packages/cli/src/lib/cache/SuccessCache.ts @@ -32,7 +32,7 @@ export class SuccessCache { * location. */ static trimPaths(input: string) { - return input.replaceAll(targetPaths.targetRoot, ''); + return input.replaceAll(targetPaths.resolveRoot(), ''); } constructor(name: string, basePath?: string) { diff --git a/packages/cli/src/lib/typeDistProject.ts b/packages/cli/src/lib/typeDistProject.ts index 161ea2be82..11a0019490 100644 --- a/packages/cli/src/lib/typeDistProject.ts +++ b/packages/cli/src/lib/typeDistProject.ts @@ -25,7 +25,7 @@ import { targetPaths } from '@backstage/cli-common'; export const createTypeDistProject = async () => { return new Project({ - tsConfigFilePath: targetPaths.resolveTargetRoot('tsconfig.json'), + tsConfigFilePath: targetPaths.resolveRoot('tsconfig.json'), skipAddingFilesFromTsConfig: true, }); }; diff --git a/packages/cli/src/lib/version.ts b/packages/cli/src/lib/version.ts index 326657a9b0..a28feba015 100644 --- a/packages/cli/src/lib/version.ts +++ b/packages/cli/src/lib/version.ts @@ -17,9 +17,9 @@ import fs from 'fs-extra'; import semver from 'semver'; import { findOwnPaths } from '@backstage/cli-common'; +import { Lockfile } from './versioning'; const ownPaths = findOwnPaths(__dirname); -import { Lockfile } from './versioning'; /* eslint-disable @backstage/no-relative-monorepo-imports */ /* @@ -84,12 +84,12 @@ export const packageVersions: Record = { }; export function findVersion() { - const pkgContent = fs.readFileSync(ownPaths.resolveOwn('package.json'), 'utf8'); + const pkgContent = fs.readFileSync(ownPaths.resolve('package.json'), 'utf8'); return JSON.parse(pkgContent).version; } export const version = findVersion(); -export const isDev = fs.pathExistsSync(ownPaths.resolveOwn('src')); +export const isDev = fs.pathExistsSync(ownPaths.resolve('src')); export function createPackageVersionProvider( lockfile?: Lockfile, diff --git a/packages/cli/src/lib/yarnPlugin.test.ts b/packages/cli/src/lib/yarnPlugin.test.ts index ac9345a4a4..a51fa4864a 100644 --- a/packages/cli/src/lib/yarnPlugin.test.ts +++ b/packages/cli/src/lib/yarnPlugin.test.ts @@ -15,22 +15,21 @@ */ import { createMockDirectory } from '@backstage/backend-test-utils'; +import { targetPaths } from '@backstage/cli-common'; import { getHasYarnPlugin } from './yarnPlugin'; const mockDir = createMockDirectory(); -jest.mock('@backstage/cli-common', () => ({ - ...jest.requireActual('@backstage/cli-common'), - targetPaths: { - resolveTargetRoot(filename: string) { - return mockDir.resolve(filename); - }, - }, -})); - describe('getHasYarnPlugin', () => { beforeEach(() => { mockDir.clear(); + jest + .spyOn(targetPaths, 'resolveRoot') + .mockImplementation((...args: string[]) => mockDir.resolve(...args)); + }); + + afterEach(() => { + jest.restoreAllMocks(); }); it('should return false when .yarnrc.yml does not exist', async () => { diff --git a/packages/cli/src/lib/yarnPlugin.ts b/packages/cli/src/lib/yarnPlugin.ts index b8c56b98ec..fdef27e07f 100644 --- a/packages/cli/src/lib/yarnPlugin.ts +++ b/packages/cli/src/lib/yarnPlugin.ts @@ -36,7 +36,7 @@ const yarnRcSchema = z.object({ * @returns Promise - true if the plugin is installed, false otherwise */ export async function getHasYarnPlugin(): Promise { - const yarnRcPath = targetPaths.resolveTargetRoot('.yarnrc.yml'); + const yarnRcPath = targetPaths.resolveRoot('.yarnrc.yml'); const yarnRcContent = await fs.readFile(yarnRcPath, 'utf-8').catch(e => { if (e.code === 'ENOENT') { // gracefully continue in case the file doesn't exist diff --git a/packages/cli/src/modules/build/commands/package/build/command.ts b/packages/cli/src/modules/build/commands/package/build/command.ts index 6a04524d17..408d7dab52 100644 --- a/packages/cli/src/modules/build/commands/package/build/command.ts +++ b/packages/cli/src/modules/build/commands/package/build/command.ts @@ -42,19 +42,19 @@ export async function command(opts: OptionValues): Promise { if (isValidUrl(arg)) { return arg; } - return targetPaths.resolveTarget(arg); + return targetPaths.resolve(arg); }); if (role === 'frontend') { return buildFrontend({ - targetDir: targetPaths.targetDir, + targetDir: targetPaths.resolve(), configPaths, writeStats: Boolean(opts.stats), webpack, }); } return buildBackend({ - targetDir: targetPaths.targetDir, + targetDir: targetPaths.resolve(), configPaths, skipBuildDependencies: Boolean(opts.skipBuildDependencies), minify: Boolean(opts.minify), @@ -77,7 +77,7 @@ export async function command(opts: OptionValues): Promise { if (isModuleFederationRemote) { console.log('Building package as a module federation remote'); return buildFrontend({ - targetDir: targetPaths.targetDir, + targetDir: targetPaths.resolve(), configPaths: [], writeStats: Boolean(opts.stats), isModuleFederationRemote, @@ -100,7 +100,7 @@ export async function command(opts: OptionValues): Promise { } const packageJson = (await fs.readJson( - targetPaths.resolveTarget('package.json'), + targetPaths.resolve('package.json'), )) as BackstagePackageJson; return buildPackage({ diff --git a/packages/cli/src/modules/build/commands/package/start/command.ts b/packages/cli/src/modules/build/commands/package/start/command.ts index fb9f10deac..9bc0bfc841 100644 --- a/packages/cli/src/modules/build/commands/package/start/command.ts +++ b/packages/cli/src/modules/build/commands/package/start/command.ts @@ -25,7 +25,7 @@ export async function command(opts: OptionValues): Promise { await startPackage({ role: await findRoleFromCommand(opts), entrypoint: opts.entrypoint, - targetDir: targetPaths.targetDir, + targetDir: targetPaths.resolve(), configPaths: opts.config as string[], checksEnabled: Boolean(opts.check), linkedWorkspace: await resolveLinkedWorkspace(opts.link), diff --git a/packages/cli/src/modules/build/commands/package/start/startBackend.ts b/packages/cli/src/modules/build/commands/package/start/startBackend.ts index 7a593bb7d7..3ea1e9bbe9 100644 --- a/packages/cli/src/modules/build/commands/package/start/startBackend.ts +++ b/packages/cli/src/modules/build/commands/package/start/startBackend.ts @@ -44,7 +44,7 @@ export async function startBackend(options: StartBackendOptions) { export async function startBackendPlugin(options: StartBackendOptions) { const hasDevIndexEntry = await fs.pathExists( - resolvePath(options.targetDir ?? targetPaths.targetDir, 'dev/index.ts'), + resolvePath(options.targetDir ?? targetPaths.resolve(), 'dev/index.ts'), ); if (!hasDevIndexEntry) { console.warn( diff --git a/packages/cli/src/modules/build/commands/package/start/startFrontend.ts b/packages/cli/src/modules/build/commands/package/start/startFrontend.ts index 60b4fab14d..413ce6b752 100644 --- a/packages/cli/src/modules/build/commands/package/start/startFrontend.ts +++ b/packages/cli/src/modules/build/commands/package/start/startFrontend.ts @@ -39,7 +39,7 @@ interface StartAppOptions { export async function startFrontend(options: StartAppOptions) { const packageJson = (await readJson( - resolvePath(options.targetDir ?? targetPaths.targetDir, 'package.json'), + resolvePath(options.targetDir ?? targetPaths.resolve(), 'package.json'), )) as BackstagePackageJson; if (!hasReactDomClient()) { @@ -59,7 +59,7 @@ export async function startFrontend(options: StartAppOptions) { moduleFederationRemote: options.isModuleFederationRemote ? await getModuleFederationRemoteOptions( packageJson, - resolvePath(targetPaths.targetDir), + resolvePath(targetPaths.resolve()), ) : undefined, }); diff --git a/packages/cli/src/modules/build/commands/repo/build.ts b/packages/cli/src/modules/build/commands/repo/build.ts index 22530875f5..8b1391ecf9 100644 --- a/packages/cli/src/modules/build/commands/repo/build.ts +++ b/packages/cli/src/modules/build/commands/repo/build.ts @@ -90,7 +90,7 @@ export async function command(opts: OptionValues, cmd: Command): Promise { targetDir: pkg.dir, packageJson: pkg.packageJson, outputs, - logPrefix: `${chalk.cyan(relativePath(targetPaths.targetRoot, pkg.dir))}: `, + logPrefix: `${chalk.cyan(relativePath(targetPaths.resolveRoot(), pkg.dir))}: `, workspacePackages: packages, minify: opts.minify ?? buildOptions.minify, }; diff --git a/packages/cli/src/modules/build/commands/repo/start.test.ts b/packages/cli/src/modules/build/commands/repo/start.test.ts index cc9e5cd8ec..4cb3449e12 100644 --- a/packages/cli/src/modules/build/commands/repo/start.test.ts +++ b/packages/cli/src/modules/build/commands/repo/start.test.ts @@ -99,7 +99,7 @@ describe('findTargetPackages', () => { beforeEach(() => { jest.clearAllMocks(); jest - .spyOn(targetPaths, 'resolveTargetRoot') + .spyOn(targetPaths, 'resolveRoot') .mockImplementation((...parts: string[]) => { return posix.resolve('/root', ...parts); }); diff --git a/packages/cli/src/modules/build/commands/repo/start.ts b/packages/cli/src/modules/build/commands/repo/start.ts index cd45c7941e..f7faa46eb1 100644 --- a/packages/cli/src/modules/build/commands/repo/start.ts +++ b/packages/cli/src/modules/build/commands/repo/start.ts @@ -96,7 +96,7 @@ export async function findTargetPackages( pkg => nameOrPath === pkg.packageJson.name, ); if (!matchingPackage) { - const absPath = targetPaths.resolveTargetRoot(nameOrPath); + const absPath = targetPaths.resolveRoot(nameOrPath); matchingPackage = packages.find( pkg => relativePath(pkg.dir, absPath) === '', ); @@ -118,7 +118,7 @@ export async function findTargetPackages( ); if (matchingPackages.length > 1) { // Final fallback is to check for the package path within the monorepo, packages/app or packages/backend - const expectedPath = targetPaths.resolveTargetRoot( + const expectedPath = targetPaths.resolveRoot( role === 'frontend' ? 'packages/app' : 'packages/backend', ); const matchByPath = matchingPackages.find( diff --git a/packages/cli/src/modules/build/lib/builder/config.ts b/packages/cli/src/modules/build/lib/builder/config.ts index ba8028f2d6..08b8221a77 100644 --- a/packages/cli/src/modules/build/lib/builder/config.ts +++ b/packages/cli/src/modules/build/lib/builder/config.ts @@ -117,7 +117,7 @@ export async function makeRollupConfigs( options: BuildOptions, ): Promise { const configs = new Array(); - const targetDir = options.targetDir ?? targetPaths.targetDir; + const targetDir = options.targetDir ?? targetPaths.resolve(); let targetPkg = options.packageJson; if (!targetPkg) { @@ -285,9 +285,9 @@ export async function makeRollupConfigs( const input = Object.fromEntries( scriptEntryPoints.map(e => [ e.name, - targetPaths.resolveTargetRoot( + targetPaths.resolveRoot( 'dist-types', - relativePath(targetPaths.targetRoot, targetDir), + relativePath(targetPaths.resolveRoot(), targetDir), e.path.replace(/\.(?:ts|tsx)$/, '.d.ts'), ), ]), diff --git a/packages/cli/src/modules/build/lib/builder/packager.ts b/packages/cli/src/modules/build/lib/builder/packager.ts index e1a10b3ac8..527fbbba0e 100644 --- a/packages/cli/src/modules/build/lib/builder/packager.ts +++ b/packages/cli/src/modules/build/lib/builder/packager.ts @@ -34,7 +34,7 @@ export function formatErrorMessage(error: any) { msg += `\n\n`; for (const { text, location } of error.errors) { const { line, column } = location; - const path = relativePath(targetPaths.targetDir, error.id); + const path = relativePath(targetPaths.resolve(), error.id); const loc = chalk.cyan(`${path}:${line}:${column}`); if (text === 'Unexpected "<"' && error.id.endsWith('.js')) { @@ -53,11 +53,11 @@ export function formatErrorMessage(error: any) { } else { // Generic rollup errors, log what's available if (error.loc) { - const file = `${targetPaths.resolveTarget((error.loc.file || error.id)!)}`; + const file = `${targetPaths.resolve((error.loc.file || error.id)!)}`; const pos = `${error.loc.line}:${error.loc.column}`; msg += `${file} [${pos}]\n`; } else if (error.id) { - msg += `${targetPaths.resolveTarget(error.id)}\n`; + msg += `${targetPaths.resolve(error.id)}\n`; } msg += `${error}\n`; @@ -90,7 +90,7 @@ async function rollupBuild(config: RollupOptions) { export const buildPackage = async (options: BuildOptions) => { try { const { resolutions } = await fs.readJson( - targetPaths.resolveTargetRoot('package.json'), + targetPaths.resolveRoot('package.json'), ); if (resolutions?.esbuild) { console.warn( @@ -107,7 +107,7 @@ export const buildPackage = async (options: BuildOptions) => { const rollupConfigs = await makeRollupConfigs(options); - const targetDir = options.targetDir ?? targetPaths.targetDir; + const targetDir = options.targetDir ?? targetPaths.resolve(); await fs.remove(resolvePath(targetDir, 'dist')); const buildTasks = rollupConfigs.map(rollupBuild); diff --git a/packages/cli/src/modules/build/lib/bundler/config.ts b/packages/cli/src/modules/build/lib/bundler/config.ts index c0537bb36c..1fe81cbec8 100644 --- a/packages/cli/src/modules/build/lib/bundler/config.ts +++ b/packages/cli/src/modules/build/lib/bundler/config.ts @@ -97,7 +97,7 @@ async function readBuildInfo() { } const { version: packageVersion } = await fs.readJson( - targetPaths.resolveTarget('package.json'), + targetPaths.resolve('package.json'), ); return { diff --git a/packages/cli/src/modules/build/lib/bundler/hasReactDomClient.ts b/packages/cli/src/modules/build/lib/bundler/hasReactDomClient.ts index ab48599033..6acd96f8c1 100644 --- a/packages/cli/src/modules/build/lib/bundler/hasReactDomClient.ts +++ b/packages/cli/src/modules/build/lib/bundler/hasReactDomClient.ts @@ -20,7 +20,7 @@ import { targetPaths } from '@backstage/cli-common'; export function hasReactDomClient() { try { require.resolve('react-dom/client', { - paths: [targetPaths.targetDir], + paths: [targetPaths.resolve()], }); return true; } catch { diff --git a/packages/cli/src/modules/build/lib/bundler/linkWorkspaces.ts b/packages/cli/src/modules/build/lib/bundler/linkWorkspaces.ts index 6976df99a5..935e14e85a 100644 --- a/packages/cli/src/modules/build/lib/bundler/linkWorkspaces.ts +++ b/packages/cli/src/modules/build/lib/bundler/linkWorkspaces.ts @@ -53,7 +53,7 @@ export async function createWorkspaceLinkingPlugins( /^react(?:-router)?(?:-dom)?$/, resource => { if (!relativePath(linkedRoot.dir, resource.context).startsWith('..')) { - resource.context = targetPaths.targetDir; + resource.context = targetPaths.resolve(); } }, ), diff --git a/packages/cli/src/modules/build/lib/bundler/packageDetection.ts b/packages/cli/src/modules/build/lib/bundler/packageDetection.ts index e0a3069733..3f361819f3 100644 --- a/packages/cli/src/modules/build/lib/bundler/packageDetection.ts +++ b/packages/cli/src/modules/build/lib/bundler/packageDetection.ts @@ -147,7 +147,7 @@ export async function createDetectedModulesEntryPoint(options: { // Previous versions of the CLI would write the detected modules file to the // root `node_modules`, this makes sure that doesn't exist to minimize risk of conflicts const legacyDetectedModulesPath = joinPath( - targetPaths.targetRoot, + targetPaths.resolveRoot(), 'node_modules', `${DETECTED_MODULES_MODULE_NAME}.js`, ); diff --git a/packages/cli/src/modules/build/lib/bundler/paths.ts b/packages/cli/src/modules/build/lib/bundler/paths.ts index 166fc20d5b..d0d56e6396 100644 --- a/packages/cli/src/modules/build/lib/bundler/paths.ts +++ b/packages/cli/src/modules/build/lib/bundler/paths.ts @@ -18,19 +18,17 @@ import fs from 'fs-extra'; import { resolve as resolvePath } from 'node:path'; import { targetPaths, findOwnPaths } from '@backstage/cli-common'; -const ownPaths = findOwnPaths(__dirname); - export type BundlingPathsOptions = { // bundle entrypoint, e.g. 'src/index' entry: string; - // Target directory, defaulting to targetPaths.targetDir + // Target directory, defaulting to targetPaths.resolve() targetDir?: string; // Relative dist directory, defaulting to 'dist' dist?: string; }; export function resolveBundlingPaths(options: BundlingPathsOptions) { - const { entry, targetDir = targetPaths.targetDir } = options; + const { entry, targetDir = targetPaths.resolve() } = options; const resolveTargetModule = (pathString: string) => { for (const ext of ['mjs', 'js', 'ts', 'tsx', 'jsx']) { @@ -51,7 +49,7 @@ export function resolveBundlingPaths(options: BundlingPathsOptions) { } else { targetHtml = resolvePath(targetDir, `${entry}.html`); if (!fs.pathExistsSync(targetHtml)) { - targetHtml = ownPaths.resolveOwn('templates/serve_index.html'); + targetHtml = findOwnPaths(__dirname).resolve('templates/serve_index.html'); } } @@ -69,10 +67,10 @@ export function resolveBundlingPaths(options: BundlingPathsOptions) { targetSrc: resolvePath(targetDir, 'src'), targetDev: resolvePath(targetDir, 'dev'), targetEntry: resolveTargetModule(entry), - targetTsConfig: targetPaths.resolveTargetRoot('tsconfig.json'), + targetTsConfig: targetPaths.resolveRoot('tsconfig.json'), targetPackageJson: resolvePath(targetDir, 'package.json'), - rootNodeModules: targetPaths.resolveTargetRoot('node_modules'), - root: targetPaths.targetRoot, + rootNodeModules: targetPaths.resolveRoot('node_modules'), + root: targetPaths.resolveRoot(), }; } diff --git a/packages/cli/src/modules/build/lib/bundler/server.ts b/packages/cli/src/modules/build/lib/bundler/server.ts index f729a0a084..27e0cbd034 100644 --- a/packages/cli/src/modules/build/lib/bundler/server.ts +++ b/packages/cli/src/modules/build/lib/bundler/server.ts @@ -54,7 +54,7 @@ DEPRECATION WARNING: React Router Beta is deprecated and support for it will be checkReactVersion(); const { name } = await fs.readJson( - resolvePath(options.targetDir ?? targetPaths.targetDir, 'package.json'), + resolvePath(options.targetDir ?? targetPaths.resolve(), 'package.json'), ); let devServer: RspackDevServer | undefined = undefined; @@ -272,7 +272,7 @@ function checkReactVersion() { try { // Make sure we're looking at the root of the target repo const reactPkgPath = require.resolve('react/package.json', { - paths: [targetPaths.targetRoot], + paths: [targetPaths.resolveRoot()], }); const reactPkg = require(reactPkgPath); if (reactPkg.version.startsWith('16.')) { diff --git a/packages/cli/src/modules/build/lib/packager/createDistWorkspace.ts b/packages/cli/src/modules/build/lib/packager/createDistWorkspace.ts index 0312108c88..6d55fdac0f 100644 --- a/packages/cli/src/modules/build/lib/packager/createDistWorkspace.ts +++ b/packages/cli/src/modules/build/lib/packager/createDistWorkspace.ts @@ -211,7 +211,7 @@ export async function createDistWorkspace( targetDir: pkg.dir, packageJson: pkg.packageJson, outputs: outputs, - logPrefix: `${chalk.cyan(relativePath(targetPaths.targetRoot, pkg.dir))}: `, + logPrefix: `${chalk.cyan(relativePath(targetPaths.resolveRoot(), pkg.dir))}: `, minify: options.minify, workspacePackages: packages, }); @@ -246,13 +246,13 @@ export async function createDistWorkspace( for (const file of files) { const src = typeof file === 'string' ? file : file.src; const dest = typeof file === 'string' ? file : file.dest; - await fs.copy(targetPaths.resolveTargetRoot(src), resolvePath(targetDir, dest)); + await fs.copy(targetPaths.resolveRoot(src), resolvePath(targetDir, dest)); } if (options.skeleton) { const skeletonFiles = targets .map(target => { - const dir = relativePath(targetPaths.targetRoot, target.dir); + const dir = relativePath(targetPaths.resolveRoot(), target.dir); return joinPath(dir, 'package.json'); }) .sort(); @@ -301,7 +301,7 @@ async function moveToDistWorkspace( fastPackPackages.map(async target => { console.log(`Moving ${target.name} into dist workspace`); - const outputDir = relativePath(targetPaths.targetRoot, target.dir); + const outputDir = relativePath(targetPaths.resolveRoot(), target.dir); const absoluteOutputPath = resolvePath(workspaceDir, outputDir); await productionPack({ packageDir: target.dir, @@ -321,7 +321,7 @@ async function moveToDistWorkspace( cwd: target.dir, }).waitForExit(); - const outputDir = relativePath(targetPaths.targetRoot, target.dir); + const outputDir = relativePath(targetPaths.resolveRoot(), target.dir); const absoluteOutputPath = resolvePath(workspaceDir, outputDir); await fs.ensureDir(absoluteOutputPath); diff --git a/packages/cli/src/modules/build/lib/role.test.ts b/packages/cli/src/modules/build/lib/role.test.ts index 83372e010f..1e26f78f33 100644 --- a/packages/cli/src/modules/build/lib/role.test.ts +++ b/packages/cli/src/modules/build/lib/role.test.ts @@ -23,9 +23,8 @@ const mockDir = createMockDirectory(); jest.mock('@backstage/cli-common', () => ({ ...jest.requireActual('@backstage/cli-common'), targetPaths: { - resolveTarget(filename: string) { - return mockDir.resolve(filename); - }, + resolve: (...args: string[]) => mockDir.resolve(...args), + resolveRoot: (...args: string[]) => mockDir.resolve(...args), }, })); diff --git a/packages/cli/src/modules/build/lib/role.ts b/packages/cli/src/modules/build/lib/role.ts index 95ef580048..2f3960433b 100644 --- a/packages/cli/src/modules/build/lib/role.ts +++ b/packages/cli/src/modules/build/lib/role.ts @@ -27,7 +27,7 @@ export async function findRoleFromCommand( return PackageRoles.getRoleInfo(opts.role)?.role; } - const pkg = await fs.readJson(targetPaths.resolveTarget('package.json')); + const pkg = await fs.readJson(targetPaths.resolve('package.json')); const info = PackageRoles.getRoleFromPackage(pkg); if (!info) { throw new Error(`Target package must have 'backstage.role' set`); diff --git a/packages/cli/src/modules/build/lib/runner/runBackend.ts b/packages/cli/src/modules/build/lib/runner/runBackend.ts index 2291395246..22fd50b3f4 100644 --- a/packages/cli/src/modules/build/lib/runner/runBackend.ts +++ b/packages/cli/src/modules/build/lib/runner/runBackend.ts @@ -136,7 +136,7 @@ export async function runBackend(options: RunBackendOptions) { ...process.env, BACKSTAGE_CLI_LINKED_WORKSPACE: options.linkedWorkspace, BACKSTAGE_CLI_CHANNEL: '1', - ESBK_TSCONFIG_PATH: targetPaths.resolveTargetRoot('tsconfig.json'), + ESBK_TSCONFIG_PATH: targetPaths.resolveRoot('tsconfig.json'), }, serialization: 'advanced', }, diff --git a/packages/cli/src/modules/config/lib/config.ts b/packages/cli/src/modules/config/lib/config.ts index da212e9269..b819d1da7b 100644 --- a/packages/cli/src/modules/config/lib/config.ts +++ b/packages/cli/src/modules/config/lib/config.ts @@ -35,7 +35,7 @@ type Options = { }; export async function loadCliConfig(options: Options) { - const targetDir = options.targetDir ?? targetPaths.targetDir; + const targetDir = options.targetDir ?? targetPaths.resolve(); // Consider all packages in the monorepo when loading in config const { packages } = await getPackages(targetDir); @@ -64,7 +64,7 @@ export async function loadCliConfig(options: Options) { const schema = await loadConfigSchema({ dependencies: localPackageNames, // Include the package.json in the project root if it exists - packagePaths: [targetPaths.resolveTargetRoot('package.json')], + packagePaths: [targetPaths.resolveRoot('package.json')], noUndeclaredProperties: options.strict, }); @@ -74,7 +74,7 @@ export async function loadCliConfig(options: Options) { ? async name => process.env[name] || 'x' : undefined, watch: Boolean(options.watch), - rootDir: targetPaths.targetRoot, + rootDir: targetPaths.resolveRoot(), argv: options.args.flatMap(t => ['--config', resolvePath(targetDir, t)]), }); diff --git a/packages/cli/src/modules/create-github-app/commands/create-github-app/index.ts b/packages/cli/src/modules/create-github-app/commands/create-github-app/index.ts index 61f7f43cab..57aca5674f 100644 --- a/packages/cli/src/modules/create-github-app/commands/create-github-app/index.ts +++ b/packages/cli/src/modules/create-github-app/commands/create-github-app/index.ts @@ -63,7 +63,7 @@ export default async (org: string) => { const fileName = `github-app-${slug}-credentials.yaml`; const content = `# Name: ${name}\n${stringifyYaml(config)}`; - await fs.writeFile(targetPaths.resolveTargetRoot(fileName), content); + await fs.writeFile(targetPaths.resolveRoot(fileName), content); console.log(`GitHub App configuration written to ${chalk.cyan(fileName)}`); console.log( chalk.yellow( diff --git a/packages/cli/src/modules/info/commands/info.ts b/packages/cli/src/modules/info/commands/info.ts index f74529f7ae..b0ce28fb7d 100644 --- a/packages/cli/src/modules/info/commands/info.ts +++ b/packages/cli/src/modules/info/commands/info.ts @@ -17,9 +17,6 @@ import { version as cliVersion } from '../../../../package.json'; import os from 'node:os'; import { runOutput, targetPaths, findOwnPaths } from '@backstage/cli-common'; - -const ownPaths = findOwnPaths(__dirname); - import { Lockfile } from '../../../lib/versioning'; import { BackstagePackageJson, PackageGraph } from '@backstage/cli-node'; import { minimatch } from 'minimatch'; @@ -58,9 +55,9 @@ function hasBackstageField(packageName: string, targetPath: string): boolean { export default async (options: InfoOptions) => { await new Promise(async () => { const yarnVersion = await runOutput(['yarn', '--version']); - const isLocal = fs.existsSync(ownPaths.resolveOwn('./src')); + const isLocal = fs.existsSync(findOwnPaths(__dirname).resolve('./src')); - const backstageFile = targetPaths.resolveTargetRoot('backstage.json'); + const backstageFile = targetPaths.resolveRoot('backstage.json'); let backstageVersion = 'N/A'; if (fs.existsSync(backstageFile)) { try { @@ -85,9 +82,9 @@ export default async (options: InfoOptions) => { backstage: backstageVersion, }; - const lockfilePath = targetPaths.resolveTargetRoot('yarn.lock'); + const lockfilePath = targetPaths.resolveRoot('yarn.lock'); const lockfile = await Lockfile.load(lockfilePath); - const targetPath = targetPaths.targetRoot; + const targetPath = targetPaths.resolveRoot(); // Get workspace package names and their versions const workspacePackages = new Map(); diff --git a/packages/cli/src/modules/lint/commands/package/lint.ts b/packages/cli/src/modules/lint/commands/package/lint.ts index e5d706373a..f75937f6d1 100644 --- a/packages/cli/src/modules/lint/commands/package/lint.ts +++ b/packages/cli/src/modules/lint/commands/package/lint.ts @@ -22,7 +22,7 @@ import { ESLint } from 'eslint'; export default async (directories: string[], opts: OptionValues) => { const eslint = new ESLint({ - cwd: targetPaths.targetDir, + cwd: targetPaths.resolve(), fix: opts.fix, extensions: ['js', 'jsx', 'ts', 'tsx', 'mjs', 'cjs'], }); @@ -48,14 +48,14 @@ export default async (directories: string[], opts: OptionValues) => { // This formatter uses the cwd to format file paths, so let's have that happen from the root instead if (opts.format === 'eslint-formatter-friendly') { - process.chdir(targetPaths.targetRoot); + process.chdir(targetPaths.resolveRoot()); } const resultText = await formatter.format(results); if (resultText) { if (opts.outputFile) { - await fs.writeFile(targetPaths.resolveTarget(opts.outputFile), resultText); + await fs.writeFile(targetPaths.resolve(opts.outputFile), resultText); } else { console.log(resultText); } diff --git a/packages/cli/src/modules/lint/commands/repo/lint.ts b/packages/cli/src/modules/lint/commands/repo/lint.ts index 67ba77fcca..3bdb8aae97 100644 --- a/packages/cli/src/modules/lint/commands/repo/lint.ts +++ b/packages/cli/src/modules/lint/commands/repo/lint.ts @@ -45,7 +45,7 @@ export async function command(opts: OptionValues, cmd: Command): Promise { const cacheContext = opts.successCache ? { entries: await cache.read(), - lockfile: await Lockfile.load(targetPaths.resolveTargetRoot('yarn.lock')), + lockfile: await Lockfile.load(targetPaths.resolveRoot('yarn.lock')), } : undefined; @@ -63,7 +63,7 @@ export async function command(opts: OptionValues, cmd: Command): Promise { // This formatter uses the cwd to format file paths, so let's have that happen from the root instead if (opts.format === 'eslint-formatter-friendly') { - process.chdir(targetPaths.targetRoot); + process.chdir(targetPaths.resolveRoot()); } // Make sure lint output is colored unless the user explicitly disabled it @@ -78,7 +78,7 @@ export async function command(opts: OptionValues, cmd: Command): Promise { const lintOptions = parseLintScript(pkg.packageJson.scripts?.lint); const base = { fullDir: pkg.dir, - relativeDir: relativePath(targetPaths.targetRoot, pkg.dir), + relativeDir: relativePath(targetPaths.resolveRoot(), pkg.dir), lintOptions, parentHash: undefined, }; @@ -114,7 +114,7 @@ export async function command(opts: OptionValues, cmd: Command): Promise { shouldCache: Boolean(cacheContext), maxWarnings: opts.maxWarnings ?? -1, successCache: cacheContext?.entries, - rootDir: targetPaths.targetRoot, + rootDir: targetPaths.resolveRoot(), }, workerFactory: async ({ fix, @@ -264,7 +264,7 @@ export async function command(opts: OptionValues, cmd: Command): Promise { } if (opts.outputFile && errorOutput) { - await fs.writeFile(targetPaths.resolveTargetRoot(opts.outputFile), errorOutput); + await fs.writeFile(targetPaths.resolveRoot(opts.outputFile), errorOutput); } if (cacheContext) { diff --git a/packages/cli/src/modules/maintenance/commands/package/clean.ts b/packages/cli/src/modules/maintenance/commands/package/clean.ts index bebf5c6ec1..74c6761a50 100644 --- a/packages/cli/src/modules/maintenance/commands/package/clean.ts +++ b/packages/cli/src/modules/maintenance/commands/package/clean.ts @@ -19,7 +19,7 @@ import { targetPaths } from '@backstage/cli-common'; export default async function clean() { - await fs.remove(targetPaths.resolveTarget('dist')); - await fs.remove(targetPaths.resolveTarget('dist-types')); - await fs.remove(targetPaths.resolveTarget('coverage')); + await fs.remove(targetPaths.resolve('dist')); + await fs.remove(targetPaths.resolve('dist-types')); + await fs.remove(targetPaths.resolve('coverage')); } diff --git a/packages/cli/src/modules/maintenance/commands/package/pack.ts b/packages/cli/src/modules/maintenance/commands/package/pack.ts index bd1e93cf5d..fd4c9dc162 100644 --- a/packages/cli/src/modules/maintenance/commands/package/pack.ts +++ b/packages/cli/src/modules/maintenance/commands/package/pack.ts @@ -26,16 +26,16 @@ import { createTypeDistProject } from '../../../../lib/typeDistProject'; export const pre = async () => { publishPreflightCheck({ - dir: targetPaths.targetDir, - packageJson: await fs.readJson(targetPaths.resolveTarget('package.json')), + dir: targetPaths.resolve(), + packageJson: await fs.readJson(targetPaths.resolve('package.json')), }); await productionPack({ - packageDir: targetPaths.targetDir, + packageDir: targetPaths.resolve(), featureDetectionProject: await createTypeDistProject(), }); }; export const post = async () => { - await revertProductionPack(targetPaths.targetDir); + await revertProductionPack(targetPaths.resolve()); }; diff --git a/packages/cli/src/modules/maintenance/commands/repo/clean.ts b/packages/cli/src/modules/maintenance/commands/repo/clean.ts index 490f6a850e..00f45ad967 100644 --- a/packages/cli/src/modules/maintenance/commands/repo/clean.ts +++ b/packages/cli/src/modules/maintenance/commands/repo/clean.ts @@ -24,9 +24,9 @@ import { run, targetPaths } from '@backstage/cli-common'; export async function command(): Promise { const packages = await PackageGraph.listTargetPackages(); - await fs.remove(targetPaths.resolveTargetRoot('dist')); - await fs.remove(targetPaths.resolveTargetRoot('dist-types')); - await fs.remove(targetPaths.resolveTargetRoot('coverage')); + await fs.remove(targetPaths.resolveRoot('dist')); + await fs.remove(targetPaths.resolveRoot('dist-types')); + await fs.remove(targetPaths.resolveRoot('coverage')); await Promise.all( Array.from(Array(10), async () => { diff --git a/packages/cli/src/modules/maintenance/commands/repo/fix.ts b/packages/cli/src/modules/maintenance/commands/repo/fix.ts index 63ade707ef..973436da97 100644 --- a/packages/cli/src/modules/maintenance/commands/repo/fix.ts +++ b/packages/cli/src/modules/maintenance/commands/repo/fix.ts @@ -50,7 +50,7 @@ export async function readFixablePackages(): Promise { export function printPackageFixHint(packages: FixablePackage[]) { const changed = packages.filter(pkg => pkg.changed); if (changed.length > 0) { - const rootPkg = require(targetPaths.resolveTargetRoot('package.json')); + const rootPkg = require(targetPaths.resolveRoot('package.json')); const fixCmd = rootPkg.scripts?.fix === 'backstage-cli repo fix' ? 'fix' @@ -217,7 +217,7 @@ export function fixSideEffects(pkg: FixablePackage) { } export function createRepositoryFieldFixer() { - const rootPkg = require(targetPaths.resolveTargetRoot('package.json')); + const rootPkg = require(targetPaths.resolveRoot('package.json')); const rootRepoField = rootPkg.repository; if (!rootRepoField) { return () => {}; @@ -230,7 +230,7 @@ export function createRepositoryFieldFixer() { return (pkg: FixablePackage) => { const expectedPath = posix.join( rootDir, - relativePath(targetPaths.targetRoot, pkg.dir), + relativePath(targetPaths.resolveRoot(), pkg.dir), ); const repoField = pkg.packageJson.repository; @@ -319,7 +319,7 @@ export function fixPluginId(pkg: FixablePackage) { role === 'backend-plugin-module') ) { const path = relativePath( - targetPaths.targetRoot, + targetPaths.resolveRoot(), resolvePath(pkg.dir, 'package.json'), ); const msg = `Failed to guess plugin ID for "${pkg.packageJson.name}", please set the 'backstage.pluginId' field manually in "${path}"`; @@ -415,7 +415,7 @@ export function fixPluginPackages( return; } const path = relativePath( - targetPaths.targetRoot, + targetPaths.resolveRoot(), resolvePath(pkg.dir, 'package.json'), ); const suggestedRole = @@ -464,7 +464,7 @@ export function fixPeerModules(pkg: FixablePackage) { } const packagePath = relativePath( - targetPaths.targetRoot, + targetPaths.resolveRoot(), resolvePath(pkg.dir, 'package.json'), ); diff --git a/packages/cli/src/modules/maintenance/commands/repo/list-deprecations.ts b/packages/cli/src/modules/maintenance/commands/repo/list-deprecations.ts index 1444d1062e..5f80330247 100644 --- a/packages/cli/src/modules/maintenance/commands/repo/list-deprecations.ts +++ b/packages/cli/src/modules/maintenance/commands/repo/list-deprecations.ts @@ -26,14 +26,14 @@ export async function command(opts: OptionValues) { const packages = await PackageGraph.listTargetPackages(); const eslint = new ESLint({ - cwd: targetPaths.targetDir, + cwd: targetPaths.resolve(), overrideConfig: { plugins: ['deprecation'], rules: { 'deprecation/deprecation': 'error', }, parserOptions: { - project: [targetPaths.resolveTargetRoot('tsconfig.json')], + project: [targetPaths.resolveRoot('tsconfig.json')], }, }, extensions: ['jsx', 'ts', 'tsx', 'mjs', 'cjs'], @@ -53,7 +53,7 @@ export async function command(opts: OptionValues) { continue; } - const path = relativePath(targetPaths.targetRoot, result.filePath); + const path = relativePath(targetPaths.resolveRoot(), result.filePath); deprecations.push({ path, message: message.message, diff --git a/packages/cli/src/modules/migrate/commands/packageRole.ts b/packages/cli/src/modules/migrate/commands/packageRole.ts index ccf3a9c743..878d69a312 100644 --- a/packages/cli/src/modules/migrate/commands/packageRole.ts +++ b/packages/cli/src/modules/migrate/commands/packageRole.ts @@ -22,7 +22,7 @@ import { targetPaths } from '@backstage/cli-common'; export default async () => { - const { packages } = await getPackages(targetPaths.targetDir); + const { packages } = await getPackages(targetPaths.resolve()); await Promise.all( packages.map(async ({ dir, packageJson: pkg }) => { diff --git a/packages/cli/src/modules/migrate/commands/versions/bump.test.ts b/packages/cli/src/modules/migrate/commands/versions/bump.test.ts index 62e0376830..8f7ac2577b 100644 --- a/packages/cli/src/modules/migrate/commands/versions/bump.test.ts +++ b/packages/cli/src/modules/migrate/commands/versions/bump.test.ts @@ -65,13 +65,11 @@ jest.mock('@backstage/cli-common', () => { return { ...actual, targetPaths: { - resolveTargetRoot: (filename: string) => mockDir.resolve(filename), - get targetDir() { - return mockDir.path; - }, + resolve: (...args: string[]) => mockDir.resolve(...args), + resolveRoot: (...args: string[]) => mockDir.resolve(...args), }, findPaths: () => ({ - resolveTargetRoot: (filename: string) => mockDir.resolve(filename), + resolveTargetRoot: (...args: string[]) => mockDir.resolve(...args), get targetDir() { return mockDir.path; }, diff --git a/packages/cli/src/modules/migrate/commands/versions/bump.ts b/packages/cli/src/modules/migrate/commands/versions/bump.ts index c25be51ef8..c508babb05 100644 --- a/packages/cli/src/modules/migrate/commands/versions/bump.ts +++ b/packages/cli/src/modules/migrate/commands/versions/bump.ts @@ -69,7 +69,7 @@ function extendsDefaultPattern(pattern: string): boolean { } export default async (opts: OptionValues) => { - const lockfilePath = targetPaths.resolveTargetRoot('yarn.lock'); + const lockfilePath = targetPaths.resolveRoot('yarn.lock'); const lockfile = await Lockfile.load(lockfilePath); const hasYarnPlugin = await getHasYarnPlugin(); @@ -141,7 +141,7 @@ export default async (opts: OptionValues) => { } // First we discover all Backstage dependencies within our own repo - const dependencyMap = await mapDependencies(targetPaths.targetDir, pattern); + const dependencyMap = await mapDependencies(targetPaths.resolve(), pattern); // Next check with the package registry to see which dependency ranges we need to bump const versionBumps = new Map(); @@ -418,7 +418,7 @@ export function createVersionFinder(options: { } function getBackstageJsonPath() { - return targetPaths.resolveTargetRoot(BACKSTAGE_JSON); + return targetPaths.resolveRoot(BACKSTAGE_JSON); } async function getBackstageJson() { diff --git a/packages/cli/src/modules/migrate/commands/versions/migrate.test.ts b/packages/cli/src/modules/migrate/commands/versions/migrate.test.ts index 0a176c0182..75361b0a07 100644 --- a/packages/cli/src/modules/migrate/commands/versions/migrate.test.ts +++ b/packages/cli/src/modules/migrate/commands/versions/migrate.test.ts @@ -38,13 +38,11 @@ jest.mock('@backstage/cli-common', () => { return { ...actual, targetPaths: { - resolveTargetRoot: (filename: string) => mockDir.resolve(filename), - get targetDir() { - return mockDir.path; - }, + resolve: (...args: string[]) => mockDir.resolve(...args), + resolveRoot: (...args: string[]) => mockDir.resolve(...args), }, findPaths: () => ({ - resolveTargetRoot: (filename: string) => mockDir.resolve(filename), + resolveTargetRoot: (...args: string[]) => mockDir.resolve(...args), get targetDir() { return mockDir.path; }, diff --git a/packages/cli/src/modules/new/lib/codeowners/codeowners.ts b/packages/cli/src/modules/new/lib/codeowners/codeowners.ts index e3b8f0d1b4..7842acaaa9 100644 --- a/packages/cli/src/modules/new/lib/codeowners/codeowners.ts +++ b/packages/cli/src/modules/new/lib/codeowners/codeowners.ts @@ -83,7 +83,7 @@ export async function addCodeownersEntry( let filePath = codeownersFilePath; if (!filePath) { - filePath = await getCodeownersFilePath(targetPaths.targetRoot); + filePath = await getCodeownersFilePath(targetPaths.resolveRoot()); if (!filePath) { return false; } diff --git a/packages/cli/src/modules/new/lib/execution/PortableTemplater.ts b/packages/cli/src/modules/new/lib/execution/PortableTemplater.ts index 1e83828701..511e656c91 100644 --- a/packages/cli/src/modules/new/lib/execution/PortableTemplater.ts +++ b/packages/cli/src/modules/new/lib/execution/PortableTemplater.ts @@ -50,7 +50,7 @@ export class PortableTemplater { static async create(options: CreatePortableTemplaterOptions = {}) { let lockfile: Lockfile | undefined; try { - lockfile = await Lockfile.load(targetPaths.resolveTargetRoot('yarn.lock')); + lockfile = await Lockfile.load(targetPaths.resolveRoot('yarn.lock')); } catch { /* ignored */ } diff --git a/packages/cli/src/modules/new/lib/execution/installNewPackage.ts b/packages/cli/src/modules/new/lib/execution/installNewPackage.ts index 71208b685e..4bbb3730df 100644 --- a/packages/cli/src/modules/new/lib/execution/installNewPackage.ts +++ b/packages/cli/src/modules/new/lib/execution/installNewPackage.ts @@ -53,7 +53,7 @@ export async function installNewPackage(input: PortableTemplateInput) { } async function addDependency(input: PortableTemplateInput, path: string) { - const pkgJsonPath = targetPaths.resolveTargetRoot(path); + const pkgJsonPath = targetPaths.resolveRoot(path); const pkgJson = await fs.readJson(pkgJsonPath).catch(error => { if (error.code === 'ENOENT') { @@ -85,7 +85,7 @@ async function tryAddFrontendLegacy(input: PortableTemplateInput) { ); } - const appDefinitionPath = targetPaths.resolveTargetRoot('packages/app/src/App.tsx'); + const appDefinitionPath = targetPaths.resolveRoot('packages/app/src/App.tsx'); if (!(await fs.pathExists(appDefinitionPath))) { return; } @@ -121,7 +121,7 @@ async function tryAddFrontendLegacy(input: PortableTemplateInput) { } async function tryAddBackend(input: PortableTemplateInput) { - const backendIndexPath = targetPaths.resolveTargetRoot( + const backendIndexPath = targetPaths.resolveRoot( 'packages/backend/src/index.ts', ); if (!(await fs.pathExists(backendIndexPath))) { diff --git a/packages/cli/src/modules/new/lib/execution/writeTemplateContents.test.ts b/packages/cli/src/modules/new/lib/execution/writeTemplateContents.test.ts index d17e1081e0..e991d45b51 100644 --- a/packages/cli/src/modules/new/lib/execution/writeTemplateContents.test.ts +++ b/packages/cli/src/modules/new/lib/execution/writeTemplateContents.test.ts @@ -33,8 +33,8 @@ describe('writeTemplateContents', () => { mockDir.clear(); jest.resetAllMocks(); jest - .spyOn(targetPaths, 'resolveTargetRoot') - .mockImplementation((...args) => mockDir.resolve(...args)); + .spyOn(targetPaths, 'resolveRoot') + .mockImplementation((...args: string[]) => mockDir.resolve(...args)); }); it('should write an empty template', async () => { diff --git a/packages/cli/src/modules/new/lib/execution/writeTemplateContents.ts b/packages/cli/src/modules/new/lib/execution/writeTemplateContents.ts index 8359468ba0..40bbec1ed4 100644 --- a/packages/cli/src/modules/new/lib/execution/writeTemplateContents.ts +++ b/packages/cli/src/modules/new/lib/execution/writeTemplateContents.ts @@ -29,7 +29,7 @@ export async function writeTemplateContents( template: PortableTemplate, input: PortableTemplateInput, ): Promise<{ targetDir: string }> { - const targetDir = targetPaths.resolveTargetRoot(input.packagePath); + const targetDir = targetPaths.resolveRoot(input.packagePath); if (await fs.pathExists(targetDir)) { throw new InputError(`Package '${input.packagePath}' already exists`); diff --git a/packages/cli/src/modules/new/lib/preparation/collectPortableTemplateInput.ts b/packages/cli/src/modules/new/lib/preparation/collectPortableTemplateInput.ts index 168e277fb6..5bf9de64ca 100644 --- a/packages/cli/src/modules/new/lib/preparation/collectPortableTemplateInput.ts +++ b/packages/cli/src/modules/new/lib/preparation/collectPortableTemplateInput.ts @@ -39,7 +39,7 @@ export async function collectPortableTemplateInput( ): Promise { const { config, template, prefilledParams } = options; - const codeOwnersFilePath = await getCodeownersFilePath(targetPaths.targetRoot); + const codeOwnersFilePath = await getCodeownersFilePath(targetPaths.resolveRoot()); const prompts = getPromptsForRole(template.role); diff --git a/packages/cli/src/modules/new/lib/preparation/loadPortableTemplate.ts b/packages/cli/src/modules/new/lib/preparation/loadPortableTemplate.ts index 6d230c14cf..6a683fb31c 100644 --- a/packages/cli/src/modules/new/lib/preparation/loadPortableTemplate.ts +++ b/packages/cli/src/modules/new/lib/preparation/loadPortableTemplate.ts @@ -47,7 +47,7 @@ export async function loadPortableTemplate( throw new Error('Remote templates are not supported yet'); } const templateContent = await fs - .readFile(targetPaths.resolveTargetRoot(pointer.target), 'utf-8') + .readFile(targetPaths.resolveRoot(pointer.target), 'utf-8') .catch(error => { throw new ForwardedError( `Failed to load template definition from '${pointer.target}'`, diff --git a/packages/cli/src/modules/new/lib/preparation/loadPortableTemplateConfig.ts b/packages/cli/src/modules/new/lib/preparation/loadPortableTemplateConfig.ts index c996bc9194..5040904138 100644 --- a/packages/cli/src/modules/new/lib/preparation/loadPortableTemplateConfig.ts +++ b/packages/cli/src/modules/new/lib/preparation/loadPortableTemplateConfig.ts @@ -91,7 +91,7 @@ export async function loadPortableTemplateConfig( ): Promise { const { overrides = {} } = options; const pkgPath = - options.packagePath ?? targetPaths.resolveTargetRoot('package.json'); + options.packagePath ?? targetPaths.resolveRoot('package.json'); const pkgJson = await fs.readJson(pkgPath); const parsed = pkgJsonWithNewConfigSchema.safeParse(pkgJson); diff --git a/packages/cli/src/modules/test/commands/package/test.ts b/packages/cli/src/modules/test/commands/package/test.ts index 3dd3399bd2..6553ad93c9 100644 --- a/packages/cli/src/modules/test/commands/package/test.ts +++ b/packages/cli/src/modules/test/commands/package/test.ts @@ -18,8 +18,6 @@ import { Command, OptionValues } from 'commander'; import { runCheck, findOwnPaths } from '@backstage/cli-common'; -const ownPaths = findOwnPaths(__dirname); - function includesAnyOf(hayStack: string[], ...needles: string[]) { for (const needle of needles) { if (hayStack.includes(needle)) { @@ -40,7 +38,7 @@ export default async (_opts: OptionValues, cmd: Command) => { // Only include our config if caller isn't passing their own config if (!includesAnyOf(args, '-c', '--config')) { - args.push('--config', ownPaths.resolveOwn('config/jest.js')); + args.push('--config', findOwnPaths(__dirname).resolve('config/jest.js')); } if (!includesAnyOf(args, '--no-passWithNoTests', '--passWithNoTests=false')) { diff --git a/packages/cli/src/modules/test/commands/repo/test.ts b/packages/cli/src/modules/test/commands/repo/test.ts index ae0882719f..6e8e22c140 100644 --- a/packages/cli/src/modules/test/commands/repo/test.ts +++ b/packages/cli/src/modules/test/commands/repo/test.ts @@ -24,10 +24,13 @@ import { relative as relativePath } from 'node:path'; import { Command, OptionValues } from 'commander'; import { Lockfile, PackageGraph } from '@backstage/cli-node'; -import { runCheck, runOutput, targetPaths, findOwnPaths } from '@backstage/cli-common'; - -const ownPaths = findOwnPaths(__dirname); -import { isChildPath } from '@backstage/cli-common'; +import { + runCheck, + runOutput, + targetPaths, + findOwnPaths, + isChildPath, +} from '@backstage/cli-common'; import { SuccessCache } from '../../../../lib/cache/SuccessCache'; type JestProject = { @@ -65,7 +68,7 @@ interface TestGlobal extends Global { async function readPackageTreeHashes(graph: PackageGraph) { const pkgs = Array.from(graph.values()).map(pkg => ({ ...pkg, - path: relativePath(targetPaths.targetRoot, pkg.dir), + path: relativePath(targetPaths.resolveRoot(), pkg.dir), })); const output = await runOutput([ 'git', @@ -164,7 +167,7 @@ export async function command(opts: OptionValues, cmd: Command): Promise { // Only include our config if caller isn't passing their own config if (!hasFlags('-c', '--config')) { - args.push('--config', ownPaths.resolveOwn('config/jest.js')); + args.push('--config', findOwnPaths(__dirname).resolve('config/jest.js')); } if (!hasFlags('--passWithNoTests')) { @@ -343,7 +346,7 @@ export async function command(opts: OptionValues, cmd: Command): Promise { async filterConfigs(projectConfigs, globalRootConfig) { const cacheEntries = await cache.read(); const lockfile = await Lockfile.load( - targetPaths.resolveTargetRoot('yarn.lock'), + targetPaths.resolveRoot('yarn.lock'), ); const getPackageTreeHash = await readPackageTreeHashes(graph); diff --git a/packages/codemods/src/action.ts b/packages/codemods/src/action.ts index 2c7b9b8d01..7a6c241497 100644 --- a/packages/codemods/src/action.ts +++ b/packages/codemods/src/action.ts @@ -16,17 +16,16 @@ import { relative as relativePath } from 'node:path'; import { OptionValues } from 'commander'; -import { findPaths, run } from '@backstage/cli-common'; +import { findOwnPaths, run } from '@backstage/cli-common'; import { platform } from 'node:os'; -// eslint-disable-next-line no-restricted-syntax -const paths = findPaths(__dirname); - export function createCodemodAction(name: string) { return async (dirs: string[], opts: OptionValues) => { + // eslint-disable-next-line no-restricted-syntax + const paths = findOwnPaths(__dirname); const transformPath = relativePath( process.cwd(), - paths.resolveOwn('transforms', `${name}.js`), + paths.resolve('transforms', `${name}.js`), ); const args = [ diff --git a/packages/config-loader/src/sources/ConfigSources.ts b/packages/config-loader/src/sources/ConfigSources.ts index 2133eccc29..45659b4cac 100644 --- a/packages/config-loader/src/sources/ConfigSources.ts +++ b/packages/config-loader/src/sources/ConfigSources.ts @@ -27,7 +27,7 @@ import { } from './RemoteConfigSource'; import { ConfigSource, SubstitutionFunc } from './types'; import { ObservableConfigProxy } from './ObservableConfigProxy'; -import { findPaths } from '@backstage/cli-common'; +import { targetPaths } from '@backstage/cli-common'; /** * A target to read configuration from. @@ -157,7 +157,7 @@ export class ConfigSources { static defaultForTargets( options: ConfigSourcesDefaultForTargetsOptions, ): ConfigSource { - const rootDir = options.rootDir ?? findPaths(process.cwd()).targetRoot; + const rootDir = options.rootDir ?? targetPaths.resolveRoot(); const argSources = options.targets.map(arg => { if (arg.type === 'url') { diff --git a/packages/create-app/src/createApp.test.ts b/packages/create-app/src/createApp.test.ts index c854867de6..4fb5438d70 100644 --- a/packages/create-app/src/createApp.test.ts +++ b/packages/create-app/src/createApp.test.ts @@ -19,12 +19,36 @@ import path from 'node:path'; import { Command } from 'commander'; import * as tasks from './lib/tasks'; import createApp from './createApp'; -import { findPaths } from '@backstage/cli-common'; +import { findOwnPaths, targetPaths } from '@backstage/cli-common'; import { tmpdir } from 'node:os'; import { createMockDirectory } from '@backstage/backend-test-utils'; jest.mock('./lib/tasks'); +jest.mock('@backstage/cli-common', () => { + const pathModule = require('node:path'); + const actual = jest.requireActual('@backstage/cli-common'); + const MOCK_CREATE_APP_ROOT = '/mock/create-app-root'; + const MOCK_TARGET_DIR = '/mock/target-dir'; + const mockOwnPaths = { + resolve: (...paths: string[]) => + pathModule.join(MOCK_CREATE_APP_ROOT, ...paths), + resolveRoot: (...paths: string[]) => + pathModule.join('/mock/monorepo-root', ...paths), + }; + return { + ...actual, + findPaths: jest.fn(), + findOwnPaths: () => mockOwnPaths, + targetPaths: { + resolve: (...paths: string[]) => + pathModule.resolve(MOCK_TARGET_DIR, ...paths), + resolveRoot: (...paths: string[]) => + pathModule.resolve('/mock/target-root', ...paths), + }, + }; +}); + // By mocking this the filesystem mocks won't mess with reading all of the package.jsons jest.mock('./lib/versions', () => ({ packageVersions: { root: '1.0.0' }, @@ -64,12 +88,7 @@ describe('command entrypoint', () => { expect(tryInitGitRepositoryMock).toHaveBeenCalled(); expect(templatingMock).toHaveBeenCalled(); expect(templatingMock.mock.lastCall?.[0]).toEqual( - findPaths(__dirname).resolveTarget( - 'packages', - 'create-app', - 'templates', - 'default-app', - ), + findOwnPaths(__dirname).resolve('templates/default-app'), ); expect(templatingMock.mock.lastCall?.[1]).toContain( path.join(tmpdir(), 'MyApp'), @@ -85,12 +104,7 @@ describe('command entrypoint', () => { expect(tryInitGitRepositoryMock).toHaveBeenCalled(); expect(templatingMock).toHaveBeenCalled(); expect(templatingMock.mock.lastCall?.[0]).toEqual( - findPaths(__dirname).resolveTarget( - 'packages', - 'create-app', - 'templates', - 'default-app', - ), + findOwnPaths(__dirname).resolve('templates/default-app'), ); expect(templatingMock.mock.lastCall?.[1]).toEqual('myDirectory'); expect(buildAppMock).toHaveBeenCalled(); @@ -103,12 +117,7 @@ describe('command entrypoint', () => { expect(tryInitGitRepositoryMock).toHaveBeenCalled(); expect(templatingMock).toHaveBeenCalled(); expect(templatingMock.mock.lastCall?.[0]).toEqual( - findPaths(__dirname).resolveTarget( - 'packages', - 'create-app', - 'templates', - 'next-app', - ), + findOwnPaths(__dirname).resolve('templates/next-app'), ); expect(templatingMock.mock.lastCall?.[1]).toContain( path.join(tmpdir(), 'MyApp'), @@ -127,7 +136,7 @@ describe('command entrypoint', () => { expect(tryInitGitRepositoryMock).toHaveBeenCalled(); expect(templatingMock).toHaveBeenCalled(); expect(templatingMock.mock.lastCall?.[0]).toEqual( - findPaths(__dirname).resolveTarget('templateDirectory'), + targetPaths.resolve('templateDirectory'), ); expect(templatingMock.mock.lastCall?.[1]).toEqual('myDirectory'); expect(buildAppMock).toHaveBeenCalled(); diff --git a/packages/create-app/src/createApp.ts b/packages/create-app/src/createApp.ts index 11e5503899..3f63cd0a60 100644 --- a/packages/create-app/src/createApp.ts +++ b/packages/create-app/src/createApp.ts @@ -18,7 +18,7 @@ import chalk from 'chalk'; import { OptionValues } from 'commander'; import inquirer, { Answers } from 'inquirer'; import { resolve as resolvePath } from 'node:path'; -import { findPaths } from '@backstage/cli-common'; +import { targetPaths, findOwnPaths } from '@backstage/cli-common'; import os from 'node:os'; import fs from 'fs-extra'; import { @@ -36,8 +36,6 @@ import { const DEFAULT_BRANCH = 'master'; export default async (opts: OptionValues): Promise => { - /* eslint-disable-next-line no-restricted-syntax */ - const paths = findPaths(__dirname); const answers: Answers = await inquirer.prompt([ { type: 'input', @@ -67,19 +65,19 @@ export default async (opts: OptionValues): Promise => { // Pick the built-in template based on the --next flag const builtInTemplate = opts.next - ? paths.resolveOwn('templates/next-app') - : paths.resolveOwn('templates/default-app'); + ? findOwnPaths(__dirname).resolve('templates/next-app') + : findOwnPaths(__dirname).resolve('templates/default-app'); // Use `--template-path` argument as template when specified. Otherwise, use the default template. const templateDir = opts.templatePath - ? paths.resolveTarget(opts.templatePath) + ? targetPaths.resolve(opts.templatePath) : builtInTemplate; // Use `--path` argument as application directory when specified, otherwise // create a directory using `answers.name` const appDir = opts.path - ? resolvePath(paths.targetDir, opts.path) - : resolvePath(paths.targetDir, answers.name); + ? resolvePath(targetPaths.resolve(), opts.path) + : resolvePath(targetPaths.resolve(), answers.name); Task.log(); Task.log('Creating the app...'); @@ -102,7 +100,7 @@ export default async (opts: OptionValues): Promise => { // Template to temporary location, and then move files Task.section('Checking if the directory is available'); - await checkAppExistsTask(paths.targetDir, answers.name); + await checkAppExistsTask(targetPaths.resolve(), answers.name); Task.section('Creating a temporary app directory'); const tempDir = await fs.mkdtemp(resolvePath(os.tmpdir(), answers.name)); diff --git a/packages/e2e-test/src/commands/runCommand.ts b/packages/e2e-test/src/commands/runCommand.ts index f0701d0e5d..ca95a2fa3e 100644 --- a/packages/e2e-test/src/commands/runCommand.ts +++ b/packages/e2e-test/src/commands/runCommand.ts @@ -27,12 +27,9 @@ import { waitFor, print } from '../lib/helpers'; import mysql from 'mysql2/promise'; import pgtools from 'pgtools'; -import { findPaths, runOutput, run } from '@backstage/cli-common'; +import { findOwnPaths, runOutput, run } from '@backstage/cli-common'; import { OptionValues } from 'commander'; -// eslint-disable-next-line no-restricted-syntax -const paths = findPaths(__dirname); - const templatePackagePaths = [ 'packages/cli/templates/frontend-plugin/package.json.hbs', 'packages/create-app/templates/default-app/package.json.hbs', @@ -138,7 +135,7 @@ async function buildDistWorkspace(workspaceName: string, rootDir: string) { } for (const pkgJsonPath of templatePackagePaths) { - const jsonPath = paths.resolveOwnRoot(pkgJsonPath); + const jsonPath = findOwnPaths(__dirname).resolveRoot(pkgJsonPath); const pkgTemplate = await fs.readFile(jsonPath, 'utf8'); const pkg = JSON.parse( handlebars.compile(pkgTemplate)( @@ -196,7 +193,7 @@ async function buildDistWorkspace(workspaceName: string, rootDir: string) { print('Pinning yarn version in workspace'); await pinYarnVersion(workspaceDir); - const yarnPatchesPath = paths.resolveOwnRoot('.yarn/patches'); + const yarnPatchesPath = findOwnPaths(__dirname).resolveRoot('.yarn/patches'); if (await fs.pathExists(yarnPatchesPath)) { print('Copying yarn patches'); await fs.copy(yarnPatchesPath, resolvePath(workspaceDir, '.yarn/patches')); @@ -214,7 +211,10 @@ async function buildDistWorkspace(workspaceName: string, rootDir: string) { * Pin the yarn version in a directory to the one we're using in the Backstage repo */ async function pinYarnVersion(dir: string) { - const yarnRc = await fs.readFile(paths.resolveOwnRoot('.yarnrc.yml'), 'utf8'); + const yarnRc = await fs.readFile( + findOwnPaths(__dirname).resolveRoot('.yarnrc.yml'), + 'utf8', + ); const yarnRcLines = yarnRc.split(/\r?\n/); const yarnPathLine = yarnRcLines.find(line => line.startsWith('yarnPath:')); if (!yarnPathLine) { @@ -225,8 +225,9 @@ async function pinYarnVersion(dir: string) { throw new Error(`Invalid 'yarnPath' in ${yarnRc}`); } const [, localYarnPath] = match; - const yarnPath = paths.resolveOwnRoot(localYarnPath); - const yarnPluginPath = paths.resolveOwnRoot( + const ownPaths = findOwnPaths(__dirname); + const yarnPath = ownPaths.resolveRoot(localYarnPath); + const yarnPluginPath = ownPaths.resolveRoot( localYarnPath, '../../plugins/@yarnpkg/plugin-workspace-tools.cjs', ); @@ -328,7 +329,7 @@ async function createApp( */ async function overrideYarnLockSeed(appDir: string) { const content = await fs.readFile( - paths.resolveOwnRoot('packages/create-app/seed-yarn.lock'), + findOwnPaths(__dirname).resolveRoot('packages/create-app/seed-yarn.lock'), 'utf8', ); const trimmedContent = content diff --git a/packages/repo-tools/src/lib/paths.ts b/packages/repo-tools/src/lib/paths.ts index ccca6f09c9..2e50b687a4 100644 --- a/packages/repo-tools/src/lib/paths.ts +++ b/packages/repo-tools/src/lib/paths.ts @@ -14,13 +14,26 @@ * limitations under the License. */ -import { findPaths } from '@backstage/cli-common'; +import { targetPaths, findOwnPaths } from '@backstage/cli-common'; import { PackageGraph } from '@backstage/cli-node'; import { Minimatch } from 'minimatch'; import { isAbsolute, relative as relativePath } from 'node:path'; /* eslint-disable-next-line no-restricted-syntax */ -export const paths = findPaths(__dirname); +export const paths = { + get targetDir() { + return targetPaths.resolve(); + }, + get targetRoot() { + return targetPaths.resolveRoot(); + }, + get ownRoot() { + return findOwnPaths(__dirname).resolveRoot(); + }, + resolveTarget: targetPaths.resolve, + resolveTargetRoot: targetPaths.resolveRoot, + resolveOwnRoot: (...p: string[]) => findOwnPaths(__dirname).resolveRoot(...p), +}; /** @internal */ export interface ResolvePackagesOptions { @@ -41,7 +54,7 @@ export async function resolvePackagePaths( for (const path of providedPaths) { const matches = packages.some( ({ dir }) => - new Minimatch(path).match(relativePath(paths.targetRoot, dir)) || + new Minimatch(path).match(relativePath(targetPaths.resolveRoot(), dir)) || isChildPath(dir, path), ); if (!matches) { @@ -57,7 +70,7 @@ export async function resolvePackagePaths( packages = packages.filter(({ dir }) => providedPaths.some( path => - new Minimatch(path).match(relativePath(paths.targetRoot, dir)) || + new Minimatch(path).match(relativePath(targetPaths.resolveRoot(), dir)) || isChildPath(dir, path), ), ); @@ -66,7 +79,7 @@ export async function resolvePackagePaths( if (include) { packages = packages.filter(pkg => include.some(pattern => - new Minimatch(pattern).match(relativePath(paths.targetRoot, pkg.dir)), + new Minimatch(pattern).match(relativePath(targetPaths.resolveRoot(), pkg.dir)), ), ); } @@ -76,13 +89,13 @@ export async function resolvePackagePaths( exclude.some( pattern => !new Minimatch(pattern).match( - relativePath(paths.targetRoot, pkg.dir), + relativePath(targetPaths.resolveRoot(), pkg.dir), ), ), ); } - return packages.map(pkg => relativePath(paths.targetRoot, pkg.dir)); + return packages.map(pkg => relativePath(targetPaths.resolveRoot(), pkg.dir)); } /** @internal */ diff --git a/packages/techdocs-cli/src/commands/serve/serve.ts b/packages/techdocs-cli/src/commands/serve/serve.ts index 864861828b..511cad0472 100644 --- a/packages/techdocs-cli/src/commands/serve/serve.ts +++ b/packages/techdocs-cli/src/commands/serve/serve.ts @@ -17,7 +17,7 @@ import { OptionValues } from 'commander'; import path from 'node:path'; import openBrowser from 'react-dev-utils/openBrowser'; -import { findPaths, RunOnOutput } from '@backstage/cli-common'; +import { findOwnPaths, RunOnOutput } from '@backstage/cli-common'; import HTTPServer from '../../lib/httpServer'; import { runMkdocsServer } from '../../lib/mkdocsServer'; import { createLogger } from '../../lib/utility'; @@ -39,8 +39,7 @@ function findPreviewBundlePath(): string { // This can be tested by running `yarn pack` and extracting the resulting tarball into a directory. // Within the extracted directory, run `npm install --only=prod`. // Once that's done you can test the CLI in any directory using `node /package `. - // eslint-disable-next-line no-restricted-syntax - return findPaths(__dirname).resolveOwn('dist/embedded-app'); + return findOwnPaths(__dirname).resolve('dist/embedded-app'); } } diff --git a/packages/yarn-plugin/src/index.test.ts b/packages/yarn-plugin/src/index.test.ts index 688098a626..2b907d7a96 100644 --- a/packages/yarn-plugin/src/index.test.ts +++ b/packages/yarn-plugin/src/index.test.ts @@ -19,7 +19,7 @@ import { spawn, SpawnOptionsWithoutStdio } from 'node:child_process'; import fs from 'fs-extra'; import yaml from 'yaml'; import { buildDepTreeFromFiles } from 'snyk-nodejs-lockfile-parser'; -import { findPaths } from '@backstage/cli-common'; +import { targetPaths } from '@backstage/cli-common'; import { createMockDirectory } from '@backstage/backend-test-utils'; jest.setTimeout(30_000); @@ -86,7 +86,7 @@ describe('Backstage yarn plugin', () => { let initialLockFileContent: string | undefined; beforeAll(async () => { - const { targetRoot } = findPaths(process.cwd()); + const targetRoot = targetPaths.resolveRoot(); await executeCommand('yarn', ['build'], { cwd: joinPath(targetRoot, 'packages/yarn-plugin'), }); diff --git a/packages/yarn-plugin/src/util/getWorkspaceRoot.test.ts b/packages/yarn-plugin/src/util/getWorkspaceRoot.test.ts index 11538b3d50..750e1d5c5e 100644 --- a/packages/yarn-plugin/src/util/getWorkspaceRoot.test.ts +++ b/packages/yarn-plugin/src/util/getWorkspaceRoot.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { findPaths, Paths } from '@backstage/cli-common'; +import { targetPaths } from '@backstage/cli-common'; const setPlatform = (platform: string) => { Object.defineProperty(process, `platform`, { @@ -37,7 +37,7 @@ describe('getWorkspaceRoot', () => { `('platform: $platform', ({ platform, native, portable }) => { let realPlatform: string; let getWorkspaceRoot: () => string; - let mockFindPaths: jest.MockedFunction; + let mockResolveRoot: jest.MockedFunction; beforeEach(() => { realPlatform = process.platform; @@ -45,11 +45,13 @@ describe('getWorkspaceRoot', () => { jest.resetModules(); - mockFindPaths = jest.fn(); + mockResolveRoot = jest.fn(); jest.doMock('@backstage/cli-common', () => ({ ...jest.requireActual('@backstage/cli-common'), - findPaths: mockFindPaths, + targetPaths: { + resolveRoot: mockResolveRoot, + }, })); getWorkspaceRoot = require('./getWorkspaceRoot').getWorkspaceRoot; @@ -60,9 +62,7 @@ describe('getWorkspaceRoot', () => { }); it('returns an appropriately-formatted workspace root path', () => { - mockFindPaths.mockReturnValue({ - targetRoot: native, - } as Paths); + mockResolveRoot.mockReturnValue(native); expect(getWorkspaceRoot()).toEqual(portable); }); diff --git a/packages/yarn-plugin/src/util/getWorkspaceRoot.ts b/packages/yarn-plugin/src/util/getWorkspaceRoot.ts index d6f3e98e10..9b12abb59d 100644 --- a/packages/yarn-plugin/src/util/getWorkspaceRoot.ts +++ b/packages/yarn-plugin/src/util/getWorkspaceRoot.ts @@ -14,11 +14,9 @@ * limitations under the License. */ -import { npath, ppath } from '@yarnpkg/fslib'; -import { findPaths } from '@backstage/cli-common'; +import { npath } from '@yarnpkg/fslib'; +import { targetPaths } from '@backstage/cli-common'; export const getWorkspaceRoot = () => { - return npath.toPortablePath( - findPaths(npath.fromPortablePath(ppath.cwd())).targetRoot, - ); + return npath.toPortablePath(targetPaths.resolveRoot()); };