mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 02:31:06 -04:00
Allow dependency usage to be grouped by package (#224751)
## Summary This pull request introduces a new feature to group dependency usage by package directories, alongside existing options for grouping by owner or source. It includes updates to the CLI, dependency graph logic, and new utility functions and tests to support the feature. ### CLI updates: * Enhanced the `group-by` option in `configureYargs` to allow grouping by `package` in addition to `owner` and `source`. (`packages/kbn-dependency-usage/src/cli.ts`, [packages/kbn-dependency-usage/src/cli.tsL50-R51](diffhunk://#diff-ef89f6725b6dde85fbfea1050625607f583373da328165c575070f0579e5f23aL50-R51)) ### Dependency graph logic: * Added the `groupByPackage` function to group dependencies by package directories, and integrated it into the `identifyDependencyUsageWithCruiser` method. (`packages/kbn-dependency-usage/src/dependency_graph/providers/cruiser.ts`, [[1]](diffhunk://#diff-19e7d98370cc898df6e0a28a61575490b35a0ff6013a8c5080aa8c9fa0065e71R17) [[2]](diffhunk://#diff-19e7d98370cc898df6e0a28a61575490b35a0ff6013a8c5080aa8c9fa0065e71R120-R123) ### Utility functions: * Implemented `groupByPackage` utility to identify package directories based on the presence of `kibana.jsonc` files and group dependencies accordingly. (`packages/kbn-dependency-usage/src/lib/group_by_package.ts`, [packages/kbn-dependency-usage/src/lib/group_by_package.tsR1-R80](diffhunk://#diff-4e2be55a320215ee636cd352393c09c0e90524a5b6121b034d1360a6afed4d67R1-R80)) ### Unit tests: * Added comprehensive tests for `groupByPackage`, covering scenarios such as nested directories, missing package files, and handling of empty dependencies. (`packages/kbn-dependency-usage/src/lib/group_by_package.test.ts`, [packages/kbn-dependency-usage/src/lib/group_by_package.test.tsR1-R231](diffhunk://#diff-69398d48b1e5a2ea0ed52ca35fc2d877e143a6dcefd71d2399931ba242b962b5R1-R231)) ### Example usage ```sh (base) ➜ kibana git:(dep-usage/group-by-package) ✗ ./scripts/dependency_usage.sh -c 20 -g package -p x-pack/solutions/security Searching for dependencies in paths: x-pack/solutions/security Dependencies will be collapsed to depth: 20 cruiser is used for building dependency graph Successfully built dependency graph using cruiser. Analyzing... No output file specified, displaying results below: { "x-pack/solutions/security/packages/connectors": [ "minimatch", "@elastic/eui", "@emotion/react", "@testing-library/react", "react", "lodash" ], "x-pack/solutions/security/packages/data-stream-adapter": [ "lodash" ], "x-pack/solutions/security/packages/data-table": [ "io-ts", "enzyme", "lodash", "react", "@elastic/eui", "@hello-pangea/dnd", "@tanstack/react-query", "react-redux", "redux", "styled-components", "@testing-library/react", "memoize-one", "typescript-fsa", "typescript-fsa-reducers", "reselect" ], "x-pack/solutions/security/packages/distribution-bar": [ "@elastic/eui", "react", "@testing-library/react", "@elastic/numeral", "@emotion/react" ], "x-pack/solutions/security/packages/ecs-data-quality-dashboard": [ "minimatch", "react", "@testing-library/react", "@testing-library/user-event", "@elastic/eui", "@emotion/react", "@elastic/ecs", "@elastic/numeral", "lodash", "@emotion/styled", "moment-timezone", "moment", "@elastic/charts", "uuid", "@tanstack/react-query", "rxjs", "@testing-library/jest-dom" ], "x-pack/solutions/security/packages/expandable-flyout": [ "@testing-library/react", "react", "@elastic/eui", "@emotion/react", "lodash", "@testing-library/user-event", "@emotion/css", "react-router-dom", "rxjs", "react-redux", "@reduxjs/toolkit", "react-fast-compare", "reselect" ], "x-pack/solutions/security/packages/features": [ "minimatch" ], "x-pack/solutions/security/packages/index-adapter": [ "@elastic/elasticsearch", "lodash", "@elastic/ecs", "rxjs" ], "x-pack/solutions/security/packages/kbn-cloud-security-posture/graph": [ "babel-jest", "@emotion/react", "react", "@storybook/addon-actions", "rxjs", "@tanstack/react-query", "webpack-merge", "@storybook/manager-api", "@storybook/theming", "@testing-library/jest-dom", "@elastic/eui", "@testing-library/react", "react-use", "@xyflow/react", "lodash", "@emotion/styled", "polished", "@storybook/react", "@testing-library/user-event", "expect", "@dagrejs/dagre" ], "x-pack/solutions/security/packages/kbn-cloud-security-posture/public": [ "@elastic/eui", "@emotion/react", "react", "react-dom", "@testing-library/react", "@tanstack/react-query", "rxjs", "react-router-dom" ], "x-pack/solutions/security/packages/kbn-securitysolution-autocomplete": [ "@testing-library/jest-dom", "@testing-library/react", "react", "@elastic/eui", "enzyme", "lodash", "moment" ], "x-pack/solutions/security/packages/kbn-securitysolution-exception-list-components": [ "@testing-library/jest-dom", "@testing-library/react", "react", "@elastic/eui", "@emotion/react", "@emotion/css", "@emotion/styled" ], "x-pack/solutions/security/packages/kbn-securitysolution-hook-utils": [ "@testing-library/react", "react", "rxjs" ], "x-pack/solutions/security/packages/kbn-securitysolution-io-ts-alerting-types": [ "io-ts", "fp-ts", "uuid" ], "x-pack/solutions/security/packages/kbn-securitysolution-io-ts-list-types": [ "fp-ts", "io-ts" ], "x-pack/solutions/security/packages/kbn-securitysolution-list-api": [ "fp-ts" ], "x-pack/solutions/security/packages/kbn-securitysolution-list-hooks": [ "fp-ts", "react", "@tanstack/react-query", "@testing-library/react" ], "x-pack/solutions/security/packages/kbn-securitysolution-list-utils": [ "lodash", "uuid" ], "x-pack/solutions/security/packages/kbn-securitysolution-t-grid": [ "lodash" ], "x-pack/solutions/security/packages/kbn-securitysolution-utils": [ "uuid", "axios", "p-limit" ], "x-pack/solutions/security/packages/navigation": [ "minimatch", "react", "@elastic/eui", "@emotion/react", "@testing-library/react" ], "x-pack/solutions/security/packages/side-nav": [ "minimatch", "@elastic/eui", "@emotion/react", "react", "@emotion/css", "@testing-library/react", "classnames", "@testing-library/user-event", "lodash" ], "x-pack/solutions/security/packages/storybook/config": [ "@storybook/addon-actions", "@storybook/manager-api", "@storybook/theming", "jest-mock" ], "x-pack/solutions/security/packages/upselling": [ "minimatch", "@elastic/eui", "react", "@testing-library/react", "@emotion/react", "@emotion/styled", "rxjs" ], "x-pack/solutions/security/plugins/cloud_security_posture": [ "react", "@testing-library/react", "history", "@tanstack/react-query", "react-router-dom", "@elastic/eui", "@emotion/react", "lodash", "react-use", "chance", "@emotion/css", "uuid", "@testing-library/user-event", "semver", "@testing-library/jest-dom", "moment", "@elastic/charts", "msw", "rxjs", "io-ts", "@testing-library/dom", "@elastic/elasticsearch" ], "x-pack/solutions/security/plugins/ecs_data_quality_dashboard": [ "minimatch", "rxjs", "moment-timezone", "fp-ts", "io-ts" ], "x-pack/solutions/security/plugins/elastic_assistant": [ "@elastic/elasticsearch", "axios", "p-limit", "yargs", "@langchain/core", "langchain", "globby", "uuid", "lodash", "p-retry", "rxjs", "js-yaml", "langsmith", "@langchain/langgraph", "moment", "@elastic/ecs", "p-map", "@testing-library/react", "elastic-apm-node", "moment-timezone", "expect" ], "x-pack/solutions/security/plugins/lists": [ "moment", "react", "@elastic/eui", "enzyme", "styled-components", "@storybook/addon-actions", "@testing-library/react", "fast-deep-equal", "@tanstack/react-query", "uuid", "lodash", "io-ts", "fp-ts", "elastic-apm-node", "sinon", "@hapi/boom", "mustache", "p-retry" ], "x-pack/solutions/security/plugins/security_solution_ess": [ "react", "rxjs", "@testing-library/react", "@elastic/eui" ], "x-pack/solutions/security/plugins/security_solution_serverless": [ "react", "@elastic/eui", "@emotion/react", "@testing-library/react", "rxjs", "@emotion/styled", "chance", "lodash", "node-fetch" ], "x-pack/solutions/security/plugins/security_solution": [ "lodash", "io-ts", "fp-ts", "seedrandom", "uuid", "semver", "moment", "axios", "ipaddr.js", "rxjs", "@emotion/react", "@elastic/eui", "react", "copy-to-clipboard", "@testing-library/react", "react-router-dom", "react-redux", "react-use", "react-reverse-portal", "styled-components", "react-dom", "@elastic/charts", "@testing-library/user-event", "@tanstack/react-query", "@testing-library/dom", "@emotion/css", "@testing-library/jest-dom", "remark-parse-no-trim", "unified", "@emotion/styled", "react-router-dom-v5-compat", "d3", "minimatch", "enzyme", "deepmerge", "fast-deep-equal", "@emotion/jest", "@elastic/numeral", "polished", "@hello-pangea/dnd", "use-resize-observer", "jest-styled-components", "moment-timezone", "classnames", "react-hook-form", "query-string", "fetch-mock", "@storybook/addon-actions", "react-markdown", "reselect", "redux", "mustache", "memoize-one", "history", "react-router", "typescript-fsa", "typescript-fsa-reducers", "@reduxjs/toolkit", "immer", "reduce-reducers", "redux-devtools-extension", "redux-thunk", "@formatjs/intl-utils", "react-diff-view", "unidiff", "diff", "json-stable-stringify", "sinon", "dedent", "object-hash", "papaparse", "d3-scale", "i18n-iso-countries", "@elastic/ecs", "@cypress/grep", "cypress-data-session", "cypress-recurse", "execa", "p-retry", "@cypress/debugging-proxy", "p-map", "pretty-ms", "@elastic/apm-rum", "ts-easing", "rbush", "suricata-sid-db", "react-window", "formik", "extract-zip", "js-yaml", "tar", "chalk", "@hapi/hapi", "node-fetch", "strip-ansi", "inquirer", "@elastic/elasticsearch", "yargs", "minimist", "del", "globby", "xml2js", "@langchain/core", "cypress", "find-cypress-specs", "cli-table3", "@babel/generator", "@babel/parser", "@langchain/langgraph", "langchain", "node-diff3", "snakecase-keys", "@hapi/boom", "elastic-apm-node", "set-value", "murmurhash", "js-sha256", "fastest-levenshtein", "langsmith", "adm-zip", "camelcase-keys" ], "x-pack/solutions/security/plugins/session_view": [ "react", "@elastic/eui", "@emotion/react", "@testing-library/user-event", "@testing-library/react", "lodash", "uuid", "memoize-one", "@tanstack/react-query", "byte-size", "react-use", "xterm", "use-resize-observer", "strip-ansi", "history" ], "x-pack/solutions/security/test": [ "moment", "chance", "lodash", "expect", "fast-deep-equal", "@mswjs/http-middleware", "msw", "uuid" ] } ``` --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
4b925523bf
commit
ea18158480
6 changed files with 322 additions and 4 deletions
|
@ -47,8 +47,8 @@ export const configureYargs = () => {
|
|||
})
|
||||
.option('group-by', {
|
||||
alias: 'g',
|
||||
describe: chalk.magenta('Group results by either owner or source (package/plugin)'),
|
||||
choices: ['owner', 'source'],
|
||||
describe: chalk.magenta('Group results by owner, source, or package'),
|
||||
choices: ['owner', 'source', 'package'],
|
||||
})
|
||||
.option('summary', {
|
||||
alias: 's',
|
||||
|
|
|
@ -7,7 +7,9 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { KIBANA_SOLUTIONS } from '@kbn/projects-solutions-groups';
|
||||
// TODO: This cannot be imported until Kibana supports ESM
|
||||
// import { KIBANA_SOLUTIONS } from '@kbn/projects-solutions-groups';
|
||||
const KIBANA_SOLUTIONS = ['observability', 'security', 'search', 'chat'] as const;
|
||||
|
||||
export const aggregationGroups: string[] = [
|
||||
...KIBANA_SOLUTIONS.flatMap((solution) => [
|
||||
|
|
|
@ -14,6 +14,7 @@ import nodePath from 'path';
|
|||
|
||||
import { groupFilesByOwners } from '../../lib/group_by_owners.ts';
|
||||
import { groupBySource } from '../../lib/group_by_source.ts';
|
||||
import { groupByPackage } from '../../lib/group_by_package.ts';
|
||||
import { createCollapseRegexWithDepth } from '../../lib/collapse_with_depth.ts';
|
||||
import { aggregationGroups, excludePaths } from '../common/constants.ts';
|
||||
|
||||
|
@ -116,6 +117,10 @@ export async function identifyDependencyUsageWithCruiser(
|
|||
return groupFilesByOwners(violations);
|
||||
}
|
||||
|
||||
if (groupBy === 'package') {
|
||||
return groupByPackage(violations);
|
||||
}
|
||||
|
||||
if (dependencyName) {
|
||||
const dependencyRegex = new RegExp(`node_modules/${dependencyName}`);
|
||||
|
||||
|
|
231
packages/kbn-dependency-usage/src/lib/group_by_package.test.ts
Normal file
231
packages/kbn-dependency-usage/src/lib/group_by_package.test.ts
Normal file
|
@ -0,0 +1,231 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the "Elastic License
|
||||
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { groupByPackage } from './group_by_package';
|
||||
|
||||
// Mock fs and path modules
|
||||
jest.mock('fs');
|
||||
jest.mock('path');
|
||||
|
||||
describe('groupByPackage', () => {
|
||||
// Reset mocks before each test
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should group dependencies by their package directories', () => {
|
||||
// Mock path.dirname to simulate directory structure
|
||||
const mockDirname = jest.fn().mockImplementation((filePath) => {
|
||||
if (filePath === 'src/package1/file1.js' || filePath === 'src/package1/sub/file2.js') {
|
||||
// First call for the file's immediate directory
|
||||
if (filePath === 'src/package1/file1.js') return 'src/package1';
|
||||
if (filePath === 'src/package1/sub/file2.js') return 'src/package1/sub';
|
||||
} else if (filePath === 'src/package1/sub') {
|
||||
// When checking parent directory of 'src/package1/sub'
|
||||
return 'src/package1';
|
||||
} else if (filePath === 'src/package2/file2.js') {
|
||||
return 'src/package2';
|
||||
}
|
||||
// Default implementation
|
||||
return filePath;
|
||||
});
|
||||
|
||||
// Mock path.join to handle path concatenation
|
||||
const mockJoin = jest
|
||||
.fn()
|
||||
.mockImplementation((...args) => args.join('/').replace(/\/\//g, '/'));
|
||||
|
||||
// Mock path.parse to handle root directory detection
|
||||
const mockParse = jest.fn().mockReturnValue({ root: '/' });
|
||||
|
||||
// Mock fs.existsSync to simulate kibana.jsonc files
|
||||
const mockExistsSync = jest.fn().mockImplementation((filePath) => {
|
||||
if (filePath === 'src/package1/kibana.jsonc') return true;
|
||||
if (filePath === 'src/package2/kibana.jsonc') return true;
|
||||
// No kibana.jsonc in 'src/package1/sub'
|
||||
if (filePath === 'src/package1/sub/kibana.jsonc') return false;
|
||||
return false;
|
||||
});
|
||||
|
||||
// Apply mocks
|
||||
(path.dirname as jest.Mock).mockImplementation(mockDirname);
|
||||
(path.join as jest.Mock).mockImplementation(mockJoin);
|
||||
(path.parse as jest.Mock).mockImplementation(mockParse);
|
||||
(fs.existsSync as jest.Mock).mockImplementation(mockExistsSync);
|
||||
|
||||
const dependencies = [
|
||||
{ from: 'src/package1/file1.js', to: 'node_modules/module1' },
|
||||
{ from: 'src/package1/sub/file2.js', to: 'node_modules/module2' },
|
||||
{ from: 'src/package2/file2.js', to: 'node_modules/module3' },
|
||||
];
|
||||
|
||||
const result = groupByPackage(dependencies);
|
||||
|
||||
expect(result).toEqual({
|
||||
'src/package1': ['module1', 'module2'],
|
||||
'src/package2': ['module3'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle a dependency with no package directory', () => {
|
||||
// Mock directoryname to return consistent paths
|
||||
const mockDirname = jest.fn().mockImplementation((filePath) => {
|
||||
if (filePath === 'src/no-package/file.js') return 'src/no-package';
|
||||
return filePath;
|
||||
});
|
||||
|
||||
// Mock path.join for consistent behavior
|
||||
const mockJoin = jest
|
||||
.fn()
|
||||
.mockImplementation((...args) => args.join('/').replace(/\/\//g, '/'));
|
||||
|
||||
// Mock path.parse to handle root directory detection
|
||||
const mockParse = jest.fn().mockReturnValue({ root: '/' });
|
||||
|
||||
// Mock fs.existsSync to return false (no kibana.jsonc exists)
|
||||
const mockExistsSync = jest.fn().mockReturnValue(false);
|
||||
|
||||
// Apply mocks
|
||||
(path.dirname as jest.Mock).mockImplementation(mockDirname);
|
||||
(path.join as jest.Mock).mockImplementation(mockJoin);
|
||||
(path.parse as jest.Mock).mockImplementation(mockParse);
|
||||
(fs.existsSync as jest.Mock).mockImplementation(mockExistsSync);
|
||||
|
||||
const dependencies = [{ from: 'src/no-package/file.js', to: 'node_modules/module1' }];
|
||||
|
||||
const result = groupByPackage(dependencies);
|
||||
|
||||
// Should fall back to the file's directory when no package found
|
||||
expect(result).toEqual({
|
||||
'src/no-package': ['module1'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should group multiple dependencies from files in the same package', () => {
|
||||
// Mock path.dirname for consistent behavior
|
||||
const mockDirname = jest.fn().mockImplementation((filePath) => {
|
||||
if (filePath.startsWith('src/package1/')) return 'src/package1';
|
||||
return filePath;
|
||||
});
|
||||
|
||||
// Mock path.join for path concatenation
|
||||
const mockJoin = jest
|
||||
.fn()
|
||||
.mockImplementation((...args) => args.join('/').replace(/\/\//g, '/'));
|
||||
|
||||
// Mock path.parse for root directory
|
||||
const mockParse = jest.fn().mockReturnValue({ root: '/' });
|
||||
|
||||
// Mock fs.existsSync to simulate kibana.jsonc file
|
||||
const mockExistsSync = jest.fn().mockImplementation((filePath) => {
|
||||
return filePath === 'src/package1/kibana.jsonc';
|
||||
});
|
||||
|
||||
// Apply mocks
|
||||
(path.dirname as jest.Mock).mockImplementation(mockDirname);
|
||||
(path.join as jest.Mock).mockImplementation(mockJoin);
|
||||
(path.parse as jest.Mock).mockImplementation(mockParse);
|
||||
(fs.existsSync as jest.Mock).mockImplementation(mockExistsSync);
|
||||
|
||||
const dependencies = [
|
||||
{ from: 'src/package1/file1.js', to: 'node_modules/module1' },
|
||||
{ from: 'src/package1/file2.js', to: 'node_modules/module2' },
|
||||
{ from: 'src/package1/nested/file3.js', to: 'node_modules/module3' },
|
||||
];
|
||||
|
||||
const result = groupByPackage(dependencies);
|
||||
|
||||
expect(result).toEqual({
|
||||
'src/package1': ['module1', 'module2', 'module3'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove "node_modules/" prefix from dependencies', () => {
|
||||
// Mock path.dirname for consistent behavior
|
||||
const mockDirname = jest.fn().mockReturnValue('src/package1');
|
||||
|
||||
// Mock path.join for path concatenation
|
||||
const mockJoin = jest
|
||||
.fn()
|
||||
.mockImplementation((...args) => args.join('/').replace(/\/\//g, '/'));
|
||||
|
||||
// Mock path.parse for root directory
|
||||
const mockParse = jest.fn().mockReturnValue({ root: '/' });
|
||||
|
||||
// Mock fs.existsSync to simulate kibana.jsonc file
|
||||
const mockExistsSync = jest.fn().mockReturnValue(true);
|
||||
|
||||
// Apply mocks
|
||||
(path.dirname as jest.Mock).mockImplementation(mockDirname);
|
||||
(path.join as jest.Mock).mockImplementation(mockJoin);
|
||||
(path.parse as jest.Mock).mockImplementation(mockParse);
|
||||
(fs.existsSync as jest.Mock).mockImplementation(mockExistsSync);
|
||||
|
||||
const dependencies = [
|
||||
{ from: 'src/package1/file1.js', to: 'node_modules/module1' },
|
||||
{ from: 'src/package1/file1.js', to: 'node_modules/module2' },
|
||||
];
|
||||
|
||||
const result = groupByPackage(dependencies);
|
||||
|
||||
expect(result).toEqual({
|
||||
'src/package1': ['module1', 'module2'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an empty object if there are no dependencies', () => {
|
||||
const result = groupByPackage([]);
|
||||
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
|
||||
it('should handle multiple packages in nested directory structure', () => {
|
||||
// Mock path.dirname to simulate directory structure
|
||||
const mockDirname = jest.fn().mockImplementation((filePath) => {
|
||||
if (filePath === 'src/parent/package1/file1.js') return 'src/parent/package1';
|
||||
if (filePath === 'src/parent/package2/file2.js') return 'src/parent/package2';
|
||||
return path.dirname(filePath);
|
||||
});
|
||||
|
||||
// Mock path.join to handle path concatenation
|
||||
const mockJoin = jest
|
||||
.fn()
|
||||
.mockImplementation((...args) => args.join('/').replace(/\/\//g, '/'));
|
||||
|
||||
// Mock path.parse to handle root directory detection
|
||||
const mockParse = jest.fn().mockReturnValue({ root: '/' });
|
||||
|
||||
// Mock fs.existsSync to simulate kibana.jsonc files
|
||||
const mockExistsSync = jest.fn().mockImplementation((filePath) => {
|
||||
if (filePath === 'src/parent/package1/kibana.jsonc') return true;
|
||||
if (filePath === 'src/parent/package2/kibana.jsonc') return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
// Apply mocks
|
||||
(path.dirname as jest.Mock).mockImplementation(mockDirname);
|
||||
(path.join as jest.Mock).mockImplementation(mockJoin);
|
||||
(path.parse as jest.Mock).mockImplementation(mockParse);
|
||||
(fs.existsSync as jest.Mock).mockImplementation(mockExistsSync);
|
||||
|
||||
const dependencies = [
|
||||
{ from: 'src/parent/package1/file1.js', to: 'node_modules/module1' },
|
||||
{ from: 'src/parent/package2/file2.js', to: 'node_modules/module2' },
|
||||
];
|
||||
|
||||
const result = groupByPackage(dependencies);
|
||||
|
||||
expect(result).toEqual({
|
||||
'src/parent/package1': ['module1'],
|
||||
'src/parent/package2': ['module2'],
|
||||
});
|
||||
});
|
||||
});
|
80
packages/kbn-dependency-usage/src/lib/group_by_package.ts
Normal file
80
packages/kbn-dependency-usage/src/lib/group_by_package.ts
Normal file
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the "Elastic License
|
||||
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
/**
|
||||
* Determines the package directory for a given file path
|
||||
* A package is defined as a directory containing a kibana.jsonc file
|
||||
*
|
||||
* @param filePath The path of the file to find the package for
|
||||
* @returns The package directory path or the original path if no package found
|
||||
*/
|
||||
function findPackageDirectory(filePath: string): string {
|
||||
let currentDir = path.dirname(filePath);
|
||||
const rootDir = path.parse(currentDir).root;
|
||||
|
||||
// Traverse up the directory tree until we find a kibana.jsonc file or hit the root
|
||||
while (true) {
|
||||
// Check if current directory has kibana.jsonc
|
||||
if (fs.existsSync(path.join(currentDir, 'kibana.jsonc'))) {
|
||||
return currentDir;
|
||||
}
|
||||
|
||||
// If we've reached the root and haven't found kibana.jsonc, break the loop
|
||||
if (currentDir === rootDir) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Move up to parent directory
|
||||
const parentDir = path.dirname(currentDir);
|
||||
|
||||
// Break if we can't go up any further (safety check)
|
||||
if (parentDir === currentDir) {
|
||||
break;
|
||||
}
|
||||
|
||||
currentDir = parentDir;
|
||||
}
|
||||
|
||||
// If no package directory found, return the directory of the original path
|
||||
return path.dirname(filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Groups dependencies by package (directories containing kibana.jsonc files)
|
||||
*
|
||||
* @param dependencies Array of from/to dependency pairs
|
||||
* @returns Record mapping package paths to their dependencies
|
||||
*/
|
||||
export function groupByPackage(dependencies: Array<{ from: string; to: string }>) {
|
||||
const packageMap = new Map<string, Set<string>>();
|
||||
|
||||
for (const dep of dependencies) {
|
||||
const { from, to } = dep;
|
||||
|
||||
// Find the package directory for the source file
|
||||
const packageDir = findPackageDirectory(from);
|
||||
|
||||
if (!packageMap.has(packageDir)) {
|
||||
packageMap.set(packageDir, new Set());
|
||||
}
|
||||
|
||||
packageMap.get(packageDir)!.add(to.replace(/^node_modules\//, ''));
|
||||
}
|
||||
|
||||
// Convert the map to a record
|
||||
const result: Record<string, string[]> = {};
|
||||
for (const [packageDir, deps] of packageMap.entries()) {
|
||||
result[packageDir] = Array.from(deps);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
|
@ -3,5 +3,5 @@
|
|||
# Need to tun the script with ts-node/esm since dependency-cruiser is only available as an ESM module.
|
||||
# We specify the correct tsconfig.json file to ensure compatibility, as our current setup doesn’t fully support ESM modules.
|
||||
# Should be resolved after https://github.com/elastic/kibana/issues/198790 is done.
|
||||
NODE_NO_WARNINGS=1 TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=packages/kbn-dependency-usage/tsconfig.json \
|
||||
NODE_OPTIONS="--max-old-space-size=8192" NODE_NO_WARNINGS=1 TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=packages/kbn-dependency-usage/tsconfig.json \
|
||||
node --loader ts-node/esm packages/kbn-dependency-usage/src/cli.ts "$@"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue