backend-common: initial utility for mocking resolved package paths

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2023-10-04 17:48:41 +02:00
parent 8f773cfd02
commit 4c39e38f1e
4 changed files with 94 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/backend-common': patch
---
Added `/testUtils` entry point, with a utility for mocking resolve package paths as returned by `resolvePackagePath`.
+4
View File
@@ -10,6 +10,7 @@
"exports": {
".": "./src/index.ts",
"./alpha": "./src/alpha.ts",
"./testUtils": "./src/testUtils.ts",
"./package.json": "./package.json"
},
"typesVersions": {
@@ -17,6 +18,9 @@
"alpha": [
"src/alpha.ts"
],
"testUtils": [
"src/testUtils.ts"
],
"package.json": [
"package.json"
]
+14
View File
@@ -18,6 +18,12 @@ import { isChildPath } from '@backstage/cli-common';
import { NotAllowedError } from '@backstage/errors';
import { resolve as resolvePath } from 'path';
/** @internal */
export const packagePathMocks = new Map<
string,
(paths: string[]) => string | undefined
>();
/**
* Resolve a path relative to the root of a package directory.
* Additional path arguments are resolved relative to the package dir.
@@ -29,6 +35,14 @@ import { resolve as resolvePath } from 'path';
* @public
*/
export function resolvePackagePath(name: string, ...paths: string[]) {
const mockedResolve = packagePathMocks.get(name);
if (mockedResolve) {
const resolved = mockedResolve(paths);
if (resolved) {
return resolved;
}
}
const req =
typeof __non_webpack_require__ === 'undefined'
? require
+71
View File
@@ -0,0 +1,71 @@
/*
* Copyright 2023 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 { packagePathMocks } from './paths';
import { posix as posixPath, resolve as resolvePath } from 'path';
/** @public */
export interface PackagePathMock {
/** Restored the normal behavior of resolvePackagePath */
restore(): void;
}
/** @public */
export interface PackagePathMockOptions {
/** The name of the package to mock the resolved path of */
name: string;
/** A replacement for the root package path */
path?: string;
/**
* Replacements for package sub-paths, each key must be an exact match of the posix-style path
* that is being resolved within the package.
*
* For example, code calling `resolvePackagePath('x', 'foo', 'bar')` would match only the following
* configuration: `createPackagePathMock({name: 'x', paths: {'foo/bar': baz}})`
*/
paths?: { [path in string]: string | (() => string) };
}
/** @public */
export function createPackagePathMock(
options: PackagePathMockOptions,
): PackagePathMock {
if (packagePathMocks.has(options.name)) {
throw new Error(
`Duplicate package path mock for package '${options.name}'`,
);
}
packagePathMocks.set(options.name, paths => {
const joinedPath = posixPath.join(...paths);
const localResolver = options.paths?.[joinedPath];
if (localResolver) {
return typeof localResolver === 'function'
? localResolver()
: localResolver;
}
if (options.path) {
return resolvePath(options.path, ...paths);
}
return undefined;
});
return {
restore() {
packagePathMocks.delete(options.name);
},
};
}