mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
Add ESLINT constraints to detect inter-group dependencies (#194810)
## Summary Addresses https://github.com/elastic/kibana-team/issues/1175 As part of the **Sustainable Kibana Architecture** initiative, this PR sets the foundation to start classifying plugins in isolated groups, matching our current solutions / project types: * It adds support for the following fields in the packages' manifests (kibana.jsonc): * `group?: 'search' | 'security' | 'observability' | 'platform' | 'common'` * `visibility?: 'private' | 'shared'` * It proposes a folder structure to automatically infer groups: ```javascript 'src/platform/plugins/shared': { group: 'platform', visibility: 'shared', }, 'src/platform/plugins/internal': { group: 'platform', visibility: 'private', }, 'x-pack/platform/plugins/shared': { group: 'platform', visibility: 'shared', }, 'x-pack/platform/plugins/internal': { group: 'platform', visibility: 'private', }, 'x-pack/solutions/observability/plugins': { group: 'observability', visibility: 'private', }, 'x-pack/solutions/security/plugins': { group: 'security', visibility: 'private', }, 'x-pack/solutions/search/plugins': { group: 'search', visibility: 'private', }, ``` * If a plugin is moved to one of the specific locations above, the group and visibility in the manifest (if specified) must match those inferred from the path. * Plugins that are not relocated are considered: `group: 'common', visibility: 'shared'` by default. As soon as we specify a custom `group`, the ESLINT rules will check violations against dependencies / dependants. The ESLINT rules are pretty simple: * Plugins can only depend on: * Plugins in the same group * OR plugins with `'shared'` visibility * Plugins in `'observability', 'security', 'search'` groups are mandatorily `'private'`. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
300678ca85
commit
2a085e103a
36 changed files with 1278 additions and 49 deletions
63
packages/kbn-repo-source-classifier/src/group.ts
Normal file
63
packages/kbn-repo-source-classifier/src/group.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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 type { ModuleGroup, ModuleVisibility } from '@kbn/repo-info/types';
|
||||
|
||||
interface ModuleAttrs {
|
||||
group: ModuleGroup;
|
||||
visibility: ModuleVisibility;
|
||||
}
|
||||
|
||||
const DEFAULT_MODULE_ATTRS: ModuleAttrs = {
|
||||
group: 'common',
|
||||
visibility: 'shared',
|
||||
};
|
||||
|
||||
const MODULE_GROUPING_BY_PATH: Record<string, ModuleAttrs> = {
|
||||
'src/platform/plugins/shared': {
|
||||
group: 'platform',
|
||||
visibility: 'shared',
|
||||
},
|
||||
'src/platform/plugins/internal': {
|
||||
group: 'platform',
|
||||
visibility: 'private',
|
||||
},
|
||||
'x-pack/platform/plugins/shared': {
|
||||
group: 'platform',
|
||||
visibility: 'shared',
|
||||
},
|
||||
'x-pack/platform/plugins/internal': {
|
||||
group: 'platform',
|
||||
visibility: 'private',
|
||||
},
|
||||
'x-pack/solutions/observability/plugins': {
|
||||
group: 'observability',
|
||||
visibility: 'private',
|
||||
},
|
||||
'x-pack/solutions/security/plugins': {
|
||||
group: 'security',
|
||||
visibility: 'private',
|
||||
},
|
||||
'x-pack/solutions/search/plugins': {
|
||||
group: 'search',
|
||||
visibility: 'private',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine a plugin's grouping information based on the path where it is defined
|
||||
* @param packageRelativePath the path in the repo where the package is located
|
||||
* @returns The grouping information that corresponds to the given path
|
||||
*/
|
||||
export function inferGroupAttrsFromPath(packageRelativePath: string): ModuleAttrs {
|
||||
const grouping = Object.entries(MODULE_GROUPING_BY_PATH).find(([chunk]) =>
|
||||
packageRelativePath.startsWith(chunk)
|
||||
)?.[1];
|
||||
return grouping ?? DEFAULT_MODULE_ATTRS;
|
||||
}
|
|
@ -7,16 +7,24 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { ModuleType } from './module_type';
|
||||
import { PkgInfo } from './pkg_info';
|
||||
import type { KibanaPackageManifest } from '@kbn/repo-packages';
|
||||
import type { ModuleGroup, ModuleVisibility } from '@kbn/repo-info/types';
|
||||
import type { ModuleType } from './module_type';
|
||||
import type { PkgInfo } from './pkg_info';
|
||||
|
||||
export interface ModuleId {
|
||||
/** Type of the module */
|
||||
type: ModuleType;
|
||||
/** Specifies the group to which this module belongs */
|
||||
group: ModuleGroup;
|
||||
/** Specifies the module visibility, i.e. whether it can be accessed by everybody or only modules in the same group */
|
||||
visibility: ModuleVisibility;
|
||||
/** repo relative path to the module's source file */
|
||||
repoRel: string;
|
||||
/** info about the package the source file is within, in the case the file is found within a package */
|
||||
pkgInfo?: PkgInfo;
|
||||
/** The type of package, as described in the manifest */
|
||||
manifest?: KibanaPackageManifest;
|
||||
/** path segments of the dirname of this */
|
||||
dirs: string[];
|
||||
}
|
||||
|
|
|
@ -7,11 +7,14 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { ImportResolver } from '@kbn/import-resolver';
|
||||
import { ModuleId } from './module_id';
|
||||
import { ModuleType } from './module_type';
|
||||
import type { ImportResolver } from '@kbn/import-resolver';
|
||||
import type { ModuleGroup, ModuleVisibility } from '@kbn/repo-info/types';
|
||||
import type { KibanaPackageManifest } from '@kbn/repo-packages/modern/types';
|
||||
import type { ModuleId } from './module_id';
|
||||
import type { ModuleType } from './module_type';
|
||||
import { RANDOM_TEST_FILE_NAMES, TEST_DIR, TEST_TAG } from './config';
|
||||
import { RepoPath } from './repo_path';
|
||||
import { inferGroupAttrsFromPath } from './group';
|
||||
|
||||
const STATIC_EXTS = new Set(
|
||||
'json|woff|woff2|ttf|eot|svg|ico|png|jpg|gif|jpeg|html|md|txt|tmpl|xml'
|
||||
|
@ -231,7 +234,43 @@ export class RepoSourceClassifier {
|
|||
return 'common package';
|
||||
}
|
||||
|
||||
classify(absolute: string) {
|
||||
private getManifest(path: RepoPath): KibanaPackageManifest | undefined {
|
||||
const pkgInfo = path.getPkgInfo();
|
||||
return pkgInfo?.pkgId ? this.resolver.getPkgManifest(pkgInfo!.pkgId) : undefined;
|
||||
}
|
||||
/**
|
||||
* Determine the "group" of a file
|
||||
*/
|
||||
private getGroup(path: RepoPath): ModuleGroup {
|
||||
const attrs = inferGroupAttrsFromPath(path.getRepoRel());
|
||||
const manifest = this.getManifest(path);
|
||||
|
||||
if (attrs.group !== 'common') {
|
||||
// this package has been moved to a 'group-specific' folder, the group is determined by its location
|
||||
return attrs.group;
|
||||
} else {
|
||||
// the package is still in its original location, allow Manifest to dictate its group
|
||||
return manifest?.group ?? 'common';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the "visibility" of a file
|
||||
*/
|
||||
private getVisibility(path: RepoPath): ModuleVisibility {
|
||||
const attrs = inferGroupAttrsFromPath(path.getRepoRel());
|
||||
const manifest = this.getManifest(path);
|
||||
|
||||
if (attrs.group !== 'common') {
|
||||
// this package has been moved to a 'group-specific' folder, the visibility is determined by its location
|
||||
return attrs.visibility;
|
||||
} else {
|
||||
// the package is still in its original location, allow Manifest to dictate its visibility
|
||||
return manifest?.visibility ?? 'shared';
|
||||
}
|
||||
}
|
||||
|
||||
classify(absolute: string): ModuleId {
|
||||
const path = this.getRepoPath(absolute);
|
||||
const cached = this.ids.get(path);
|
||||
|
||||
|
@ -241,8 +280,12 @@ export class RepoSourceClassifier {
|
|||
|
||||
const id: ModuleId = {
|
||||
type: this.getType(path),
|
||||
group: this.getGroup(path),
|
||||
visibility: this.getVisibility(path),
|
||||
repoRel: path.getRepoRel(),
|
||||
pkgInfo: path.getPkgInfo() ?? undefined,
|
||||
manifest:
|
||||
(path.getPkgInfo() && this.resolver.getPkgManifest(path.getPkgInfo()!.pkgId)) ?? undefined,
|
||||
dirs: path.getSegs(),
|
||||
};
|
||||
this.ids.set(path, id);
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"kbn_references": [
|
||||
"@kbn/import-resolver",
|
||||
"@kbn/repo-info",
|
||||
"@kbn/repo-packages",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue