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 <poldsberg@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Patrik Oldsberg
2026-02-21 15:16:58 +01:00
parent 29e91e9159
commit 70fc178697
66 changed files with 311 additions and 274 deletions
+29 -20
View File
@@ -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();
+7 -9
View File
@@ -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<void> => {
/* 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<void> => {
// 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<void> => {
// 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));