Skip to content

Commit 818d45e

Browse files
committed
Fix detection of child directories
Using `string.startsWith` is insufficient when two different directories share a prefix, e.g. `./packageA` and `./packageAA`.
1 parent 3a6f053 commit 818d45e

File tree

5 files changed

+48
-4
lines changed

5 files changed

+48
-4
lines changed

.changeset/modern-worms-promise.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@backstage/cli': patch
3+
---
4+
5+
Fix detection of external package child directories

packages/cli/src/lib/bundler/LinkedPackageResolvePlugin.test.ts

+17
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,23 @@ describe('LinkedPackageResolvePlugin', () => {
8484
expect(callbackFalse).toHaveBeenCalledWith();
8585
expect(doResolve).toHaveBeenCalledTimes(0);
8686

87+
// Internal modules with a path prefix of an external module
88+
const callbackY = jest.fn();
89+
tap(
90+
{
91+
request: path.resolve(root, 'external-aa/src/module.ts'),
92+
path: path.resolve(root, 'external-aa/src'),
93+
context: {
94+
issuer: path.resolve(root, 'external-aa/src/index.ts'),
95+
},
96+
},
97+
'some-context',
98+
callbackY,
99+
);
100+
expect(callbackY).toHaveBeenCalledTimes(1);
101+
expect(callbackY).toHaveBeenCalledWith();
102+
expect(doResolve).toHaveBeenCalledTimes(0);
103+
87104
// External modules have their path and issuer context rewritten, but not the request
88105
const callbackA = jest.fn();
89106
tap(

packages/cli/src/lib/bundler/LinkedPackageResolvePlugin.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import { resolve as resolvePath } from 'path';
1818
import { ResolvePlugin } from 'webpack';
19+
import { isChildPath } from './paths';
1920
import { LernaPackage } from './types';
2021

2122
// Enables proper resolution of packages when linking in external packages.
@@ -40,7 +41,7 @@ export class LinkedPackageResolvePlugin implements ResolvePlugin {
4041
callback: () => void,
4142
) => {
4243
const pkg = this.packages.find(
43-
pkg => data.path && data.path.startsWith(pkg.location),
44+
pkg => data.path && isChildPath(pkg.location, data.path),
4445
);
4546
if (!pkg) {
4647
callback();

packages/cli/src/lib/bundler/config.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import webpack from 'webpack';
2424
import nodeExternals from 'webpack-node-externals';
2525
import { optimization } from './optimization';
2626
import { Config } from '@backstage/config';
27-
import { BundlingPaths } from './paths';
27+
import { BundlingPaths, isChildPath } from './paths';
2828
import { transforms } from './transforms';
2929
import { LinkedPackageResolvePlugin } from './LinkedPackageResolvePlugin';
3030
import { BundlingOptions, BackendBundlingOptions, LernaPackage } from './types';
@@ -87,7 +87,9 @@ export async function createConfig(
8787
const { plugins, loaders } = transforms(options);
8888
// Any package that is part of the monorepo but outside the monorepo root dir need
8989
// separate resolution logic.
90-
const externalPkgs = packages.filter(p => !p.location.startsWith(paths.root));
90+
const externalPkgs = packages.filter(
91+
p => !isChildPath(paths.root, p.location),
92+
);
9193

9294
const baseUrl = frontendConfig.getString('app.baseUrl');
9395
const validBaseUrl = new URL(baseUrl);
@@ -199,7 +201,9 @@ export async function createBackendConfig(
199201
const moduleDirs = packages.map((p: any) =>
200202
resolvePath(p.location, 'node_modules'),
201203
);
202-
const externalPkgs = packages.filter(p => !p.location.startsWith(paths.root)); // See frontend config
204+
const externalPkgs = packages.filter(
205+
p => !isChildPath(paths.root, p.location),
206+
); // See frontend config
203207

204208
const { loaders } = transforms(options);
205209

packages/cli/src/lib/bundler/paths.ts

+17
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,25 @@
1515
*/
1616

1717
import fs from 'fs-extra';
18+
import path from 'path';
1819
import { paths } from '../paths';
1920

21+
/**
22+
* Checks if dir is the same as or a child of base.
23+
*/
24+
export function isChildPath(base: string, dir: string): boolean {
25+
const relativePath = path.relative(base, dir);
26+
if (relativePath === '') {
27+
// The same directory
28+
return true;
29+
}
30+
31+
const outsideBase = relativePath.startsWith('..'); // not outside base
32+
const differentDrive = path.isAbsolute(relativePath); // on Windows, this means dir is on a different drive from base.
33+
34+
return !outsideBase && !differentDrive;
35+
}
36+
2037
export type BundlingPathsOptions = {
2138
// bundle entrypoint, e.g. 'src/index'
2239
entry: string;

0 commit comments

Comments
 (0)