From aa892fccc5ff61158ffa0e3898c008017afc50a4 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 13 Feb 2022 21:27:01 +0100 Subject: [PATCH] cli: added package graph utility to list changed packages Signed-off-by: Patrik Oldsberg --- .../cli/src/lib/monorepo/PackageGraph.test.ts | 36 ++++++++++++++- packages/cli/src/lib/monorepo/PackageGraph.ts | 45 +++++++++++++++++++ 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/lib/monorepo/PackageGraph.test.ts b/packages/cli/src/lib/monorepo/PackageGraph.test.ts index e8e531bc93..06427a7313 100644 --- a/packages/cli/src/lib/monorepo/PackageGraph.test.ts +++ b/packages/cli/src/lib/monorepo/PackageGraph.test.ts @@ -15,8 +15,20 @@ */ import { getPackages } from '@manypkg/get-packages'; -import { paths } from '../paths'; import { PackageGraph } from './PackageGraph'; +import { listChangedFiles } from '../git'; + +jest.mock('../git'); + +const mockListChangedFiles = listChangedFiles as jest.MockedFunction< + typeof listChangedFiles +>; + +jest.mock('../paths', () => ({ + paths: { + targetRoot: '/', + }, +})); const testPackages = [ { @@ -53,7 +65,7 @@ const testPackages = [ describe('PackageGraph', () => { it('is able to construct a graph from this repo', async () => { - const { packages } = await getPackages(paths.ownDir); + const { packages } = await getPackages(__dirname); const graph = PackageGraph.fromPackages(packages); expect(graph.has('@backstage/cli')).toBe(true); }); @@ -124,4 +136,24 @@ describe('PackageGraph', () => { `Package 'unknown' not found`, ); }); + + it('lists changed packages', async () => { + const graph = PackageGraph.fromPackages(testPackages); + + mockListChangedFiles.mockResolvedValueOnce( + [ + 'README.md', + 'packages/a/src/foo.ts', + 'packages/a/src/bar.ts', + 'packages/b/package.json', + 'packages/f/src/foo.ts', + 'packages/f/README.md', + 'plugins/foo/src/index.ts', + ].sort(), + ); + + await expect( + graph.listChangedPackages({ ref: 'origin/master' }), + ).resolves.toEqual([graph.get('a'), graph.get('b')]); + }); }); diff --git a/packages/cli/src/lib/monorepo/PackageGraph.ts b/packages/cli/src/lib/monorepo/PackageGraph.ts index 89d5f79cd2..a1f77299df 100644 --- a/packages/cli/src/lib/monorepo/PackageGraph.ts +++ b/packages/cli/src/lib/monorepo/PackageGraph.ts @@ -14,9 +14,11 @@ * limitations under the License. */ +import { relative as relativePath, posix as posixPath } from 'path'; import { getPackages, Package } from '@manypkg/get-packages'; import { paths } from '../paths'; import { PackageRole } from '../role'; +import { listChangedFiles } from '../git'; type PackageJSON = Package['packageJson']; @@ -158,4 +160,47 @@ export class PackageGraph extends Map { return targets; } + + async listChangedPackages(options: { ref: string }) { + const changedFiles = await listChangedFiles(options.ref); + + const dirMap = new Map( + Array.from(this.values()).map(pkg => [ + posixPath.normalize(relativePath(paths.targetRoot, pkg.dir)) + + posixPath.sep, + pkg, + ]), + ); + const packageDirs = Array.from(dirMap.keys()); + + const result = new Array(); + let searchIndex = 0; + + changedFiles.sort(); + packageDirs.sort(); + + for (const packageDir of packageDirs) { + // Skip through changes that appear before our package dir + while ( + searchIndex < changedFiles.length && + changedFiles[searchIndex] < packageDir + ) { + searchIndex += 1; + } + + // Check if we arrived at a match, otherwise we move on to the next package dir + if (changedFiles[searchIndex]?.startsWith(packageDir)) { + searchIndex += 1; + + result.push(dirMap.get(packageDir)!); + + // Skip through the rest of the changed files for the same package + while (changedFiles[searchIndex]?.startsWith(packageDir)) { + searchIndex += 1; + } + } + } + + return result; + } }