Add overrideTargetPaths test utility and fix rebase issues

Adds `overrideTargetPaths` to `@backstage/cli-common/testUtils` for
cleanly mocking `targetPaths` in tests without `jest.mock` or
`jest.spyOn`. Migrates all existing test mocks to use the new utility.

Also fixes translations module imports broken by the rebase.

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Patrik Oldsberg
2026-02-23 14:20:59 +01:00
parent 553e727d5f
commit ebc01ef04d
18 changed files with 210 additions and 121 deletions
+16 -3
View File
@@ -6,9 +6,7 @@
"role": "node-library"
},
"publishConfig": {
"access": "public",
"main": "dist/index.cjs.js",
"types": "dist/index.d.ts"
"access": "public"
},
"keywords": [
"backstage"
@@ -20,8 +18,23 @@
"directory": "packages/cli-common"
},
"license": "Apache-2.0",
"exports": {
".": "./src/index.ts",
"./testUtils": "./src/testUtils.ts",
"./package.json": "./package.json"
},
"main": "src/index.ts",
"types": "src/index.ts",
"typesVersions": {
"*": {
"testUtils": [
"src/testUtils.ts"
],
"package.json": [
"package.json"
]
}
},
"files": [
"dist"
],
@@ -0,0 +1,23 @@
## API Report File for "@backstage/cli-common"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
// @public
export function overrideTargetPaths(
dirOrOptions: string | OverrideTargetPathsOptions,
): TargetPathsOverride;
// @public
export interface OverrideTargetPathsOptions {
dir: string;
rootDir?: string;
}
// @public
export interface TargetPathsOverride {
restore(): void;
}
// (No @packageDocumentation comment for this package)
```
-1
View File
@@ -16,7 +16,6 @@ export function bootstrapEnvProxyAgents(): void;
// @public
export class ExitCodeError extends CustomErrorBase {
constructor(code: number, command?: string);
// (undocumented)
readonly code: number;
}
+1
View File
@@ -21,6 +21,7 @@ import { CustomErrorBase } from '@backstage/errors';
* @public
*/
export class ExitCodeError extends CustomErrorBase {
/** The exit code of the child process. */
readonly code: number;
constructor(code: number, command?: string) {
+20
View File
@@ -204,12 +204,23 @@ export function findOwnDir(searchDir: string) {
return OwnPathsImpl.findDir(searchDir);
}
// Used by the test utility in testUtils.ts to override targetPaths
export let targetPathsOverride: TargetPaths | undefined;
/** @internal */
export function setTargetPathsOverride(override: TargetPaths | undefined) {
targetPathsOverride = override;
}
class TargetPathsImpl implements TargetPaths {
#cwd: string | undefined;
#dir: string | undefined;
#rootDir: string | undefined;
get dir(): string {
if (targetPathsOverride) {
return targetPathsOverride.dir;
}
const cwd = process.cwd();
if (this.#dir !== undefined && this.#cwd === cwd) {
return this.#dir;
@@ -224,6 +235,9 @@ class TargetPathsImpl implements TargetPaths {
}
get rootDir(): string {
if (targetPathsOverride) {
return targetPathsOverride.rootDir;
}
// Access dir first to ensure cwd is fresh, which also invalidates rootDir on cwd change
const dir = this.dir;
if (this.#rootDir !== undefined) {
@@ -246,10 +260,16 @@ class TargetPathsImpl implements TargetPaths {
}
resolve = (...paths: string[]): string => {
if (targetPathsOverride) {
return targetPathsOverride.resolve(...paths);
}
return resolvePath(this.dir, ...paths);
};
resolveRoot = (...paths: string[]): string => {
if (targetPathsOverride) {
return targetPathsOverride.resolveRoot(...paths);
}
return resolvePath(this.rootDir, ...paths);
};
}
+78
View File
@@ -0,0 +1,78 @@
/*
* Copyright 2025 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 { resolve as resolvePath } from 'node:path';
import { setTargetPathsOverride } from './paths';
/**
* Options for {@link overrideTargetPaths}.
*
* @public
*/
export interface OverrideTargetPathsOptions {
/** The target package directory. */
dir: string;
/** The target monorepo root directory. Defaults to `dir` if not provided. */
rootDir?: string;
}
/**
* Return value of {@link overrideTargetPaths}.
*
* @public
*/
export interface TargetPathsOverride {
/** Restores `targetPaths` to its normal behavior. */
restore(): void;
}
/**
* Overrides the `targetPaths` singleton to resolve from the given directory
* instead of `process.cwd()`.
*
* When called with a string, that value is used as both `dir` and `rootDir`.
* Pass an options object to set them independently.
*
* Calling `restore()` on the return value reverts to normal behavior.
* Restoration is only needed if you want to change the override within a
* test file; each Jest worker starts with a clean module state.
*
* @public
*/
export function overrideTargetPaths(
dirOrOptions: string | OverrideTargetPathsOptions,
): TargetPathsOverride {
const { dir, rootDir } =
typeof dirOrOptions === 'string'
? { dir: dirOrOptions, rootDir: dirOrOptions }
: {
dir: dirOrOptions.dir,
rootDir: dirOrOptions.rootDir ?? dirOrOptions.dir,
};
setTargetPathsOverride({
dir,
rootDir,
resolve: (...paths: string[]) => resolvePath(dir, ...paths),
resolveRoot: (...paths: string[]) => resolvePath(rootDir, ...paths),
});
return {
restore() {
setTargetPathsOverride(undefined);
},
};
}