mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 10:40:07 -04:00
Implement package linter (#148496)
This PR implements a linter like the TS Project linter, except for packages in the repo. It does this by extracting the reusable bits from the TS Project linter and reusing them for the project linter. The only rule that exists for packages right now is that the "name" in the package.json file matches the "id" in Kibana.jsonc. The goal is to use a rule to migrate kibana.json files on the future. Additionally, a new rule for validating the indentation of tsconfig.json files was added. Validating and fixing violations is what has triggered review by so many teams, but we plan to treat those review requests as notifications of the changes and not as blockers for merging. Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
039ed991d8
commit
d6be4a4b06
218 changed files with 3917 additions and 1650 deletions
|
@ -7,6 +7,7 @@ export DISABLE_BOOTSTRAP_VALIDATION=false
|
|||
|
||||
.buildkite/scripts/steps/checks/precommit_hook.sh
|
||||
.buildkite/scripts/steps/checks/ts_projects.sh
|
||||
.buildkite/scripts/steps/checks/packages.sh
|
||||
.buildkite/scripts/steps/checks/bazel_packages.sh
|
||||
.buildkite/scripts/steps/checks/verify_notice.sh
|
||||
.buildkite/scripts/steps/checks/plugin_list_docs.sh
|
||||
|
|
14
.buildkite/scripts/steps/checks/packages.sh
Executable file
14
.buildkite/scripts/steps/checks/packages.sh
Executable file
|
@ -0,0 +1,14 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source .buildkite/scripts/common/util.sh
|
||||
|
||||
echo --- Lint packages
|
||||
cmd="node scripts/lint_packages"
|
||||
if is_pr && ! is_auto_commit_disabled; then
|
||||
cmd="$cmd --fix"
|
||||
fi
|
||||
|
||||
eval "$cmd"
|
||||
check_for_changed_files "$cmd" true
|
|
@ -4,8 +4,8 @@ set -euo pipefail
|
|||
|
||||
source .buildkite/scripts/common/util.sh
|
||||
|
||||
echo --- Run TS Project Linter
|
||||
cmd="node scripts/ts_project_linter"
|
||||
echo --- Lint TS projects
|
||||
cmd="node scripts/lint_ts_projects"
|
||||
if is_pr && ! is_auto_commit_disabled; then
|
||||
cmd="$cmd --fix"
|
||||
fi
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"incremental": false,
|
||||
"composite": false,
|
||||
"outDir": "target/types",
|
||||
"types": ["node", "mocha"],
|
||||
"paths": {
|
||||
|
|
38
.eslintrc.js
38
.eslintrc.js
|
@ -8,11 +8,7 @@
|
|||
|
||||
require('@kbn/babel-register').install();
|
||||
|
||||
const Path = require('path');
|
||||
const Fs = require('fs');
|
||||
|
||||
const normalizePath = require('normalize-path');
|
||||
const { discoverPackageManifestPaths, Jsonc } = require('@kbn/bazel-packages');
|
||||
const { getPackages } = require('@kbn/repo-packages');
|
||||
const { REPO_ROOT } = require('@kbn/repo-info');
|
||||
|
||||
const APACHE_2_0_LICENSE_HEADER = `
|
||||
|
@ -124,10 +120,9 @@ const VENN_DIAGRAM_HEADER = `
|
|||
`;
|
||||
|
||||
/** Packages which should not be included within production code. */
|
||||
const DEV_PACKAGE_DIRS = discoverPackageManifestPaths(REPO_ROOT).flatMap((path) => {
|
||||
const manifest = Jsonc.parse(Fs.readFileSync(path, 'utf8'));
|
||||
return !!manifest.devOnly ? normalizePath(Path.relative(REPO_ROOT, Path.dirname(path))) : [];
|
||||
});
|
||||
const DEV_PACKAGE_DIRS = getPackages(REPO_ROOT).flatMap((pkg) =>
|
||||
pkg.isDevOnly ? pkg.normalizedRepoRelativeDir : []
|
||||
);
|
||||
|
||||
/** Directories (at any depth) which include dev-only code. */
|
||||
const DEV_DIRECTORIES = [
|
||||
|
@ -1700,13 +1695,6 @@ module.exports = {
|
|||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Prettier disables all conflicting rules, listing as last override so it takes precedence
|
||||
*/
|
||||
{
|
||||
files: ['**/*'],
|
||||
rules: require('eslint-config-prettier').rules,
|
||||
},
|
||||
/**
|
||||
* Enterprise Search Prettier override
|
||||
* Lints unnecessary backticks - @see https://github.com/prettier/eslint-config-prettier/blob/main/README.md#forbid-unnecessary-backticks
|
||||
|
@ -1728,5 +1716,23 @@ module.exports = {
|
|||
'@kbn/imports/no_unresolvable_imports': 'off',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Code inside .buildkite runs separately from everything else in CI, before bootstrap, with ts-node. It needs a few tweaks because of this.
|
||||
*/
|
||||
{
|
||||
files: 'packages/kbn-{package-*,repo-*,dep-*}/**/*',
|
||||
rules: {
|
||||
'max-classes-per-file': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* Prettier disables all conflicting rules, listing as last override so it takes precedence
|
||||
* people kept ignoring that this was last so it's now defined outside of the overrides list
|
||||
*/
|
||||
/** eslint-disable-next-line */
|
||||
module.exports.overrides.push({ files: ['**/*'], rules: require('eslint-config-prettier').rules });
|
||||
/** PLEASE DON'T PUT THINGS AFTER THIS */
|
||||
|
|
13
.github/CODEOWNERS
vendored
13
.github/CODEOWNERS
vendored
|
@ -894,7 +894,6 @@ packages/kbn-babel-plugin-package-imports @elastic/kibana-operations
|
|||
packages/kbn-babel-preset @elastic/kibana-operations
|
||||
packages/kbn-babel-register @elastic/kibana-operations
|
||||
packages/kbn-babel-transform @elastic/kibana-operations
|
||||
packages/kbn-bazel-packages @elastic/kibana-operations
|
||||
packages/kbn-bazel-runner @elastic/kibana-operations
|
||||
packages/kbn-cases-components @elastic/response-ops
|
||||
packages/kbn-chart-icons @elastic/kibana-visualizations
|
||||
|
@ -942,13 +941,17 @@ packages/kbn-hapi-mocks @elastic/kibana-core
|
|||
packages/kbn-health-gateway-server @elastic/kibana-core
|
||||
packages/kbn-i18n @elastic/kibana-core
|
||||
packages/kbn-i18n-react @elastic/kibana-core
|
||||
packages/kbn-import-locator @elastic/kibana-operations
|
||||
packages/kbn-import-resolver @elastic/kibana-operations
|
||||
packages/kbn-interpreter @elastic/kibana-visualizations
|
||||
packages/kbn-io-ts-utils @elastic/apm-ui
|
||||
packages/kbn-jest-serializers @elastic/kibana-operations
|
||||
packages/kbn-journeys @elastic/kibana-operations
|
||||
packages/kbn-json-ast @elastic/kibana-operations
|
||||
packages/kbn-kibana-manifest-schema @elastic/kibana-operations
|
||||
packages/kbn-language-documentation-popover @elastic/kibana-visualizations
|
||||
packages/kbn-lint-packages-cli @elastic/kibana-operations
|
||||
packages/kbn-lint-ts-projects-cli @elastic/kibana-operations
|
||||
packages/kbn-logging @elastic/kibana-core
|
||||
packages/kbn-logging-mocks @elastic/kibana-core
|
||||
packages/kbn-managed-vscode-config @elastic/kibana-operations
|
||||
|
@ -958,15 +961,18 @@ packages/kbn-monaco @elastic/kibana-global-experience
|
|||
packages/kbn-optimizer @elastic/kibana-operations
|
||||
packages/kbn-optimizer-webpack-helpers @elastic/kibana-operations
|
||||
packages/kbn-osquery-io-ts-types @elastic/security-asset-management
|
||||
packages/kbn-package-map @elastic/kibana-operations
|
||||
packages/kbn-peggy @elastic/kibana-operations
|
||||
packages/kbn-peggy-loader @elastic/kibana-operations
|
||||
packages/kbn-performance-testing-dataset-extractor @elastic/kibana-performance-testing
|
||||
packages/kbn-picomatcher @elastic/kibana-operations
|
||||
packages/kbn-plugin-discovery @elastic/kibana-operations
|
||||
packages/kbn-plugin-generator @elastic/kibana-operations
|
||||
packages/kbn-plugin-helpers @elastic/kibana-operations
|
||||
packages/kbn-react-field @elastic/kibana-app-services
|
||||
packages/kbn-repo-file-maps @elastic/kibana-operations
|
||||
packages/kbn-repo-info @elastic/kibana-operations
|
||||
packages/kbn-repo-linter @elastic/kibana-operations
|
||||
packages/kbn-repo-packages @elastic/kibana-operations
|
||||
packages/kbn-repo-path @elastic/kibana-operations
|
||||
packages/kbn-repo-source-classifier @elastic/kibana-operations
|
||||
packages/kbn-repo-source-classifier-cli @elastic/kibana-operations
|
||||
|
@ -990,6 +996,7 @@ packages/kbn-securitysolution-t-grid @elastic/security-solution-platform
|
|||
packages/kbn-securitysolution-utils @elastic/security-solution-platform
|
||||
packages/kbn-server-http-tools @elastic/kibana-core
|
||||
packages/kbn-server-route-repository @elastic/apm-ui
|
||||
packages/kbn-set-map @elastic/kibana-operations
|
||||
packages/kbn-shared-svg @elastic/apm-ui
|
||||
packages/kbn-shared-ux-utility @elastic/kibana-global-experience
|
||||
packages/kbn-slo-schema @elastic/actionable-observability
|
||||
|
@ -1006,8 +1013,6 @@ packages/kbn-test-subj-selector @elastic/kibana-operations
|
|||
packages/kbn-timelion-grammar @elastic/kibana-visualizations
|
||||
packages/kbn-tinymath @elastic/kibana-visualizations
|
||||
packages/kbn-tooling-log @elastic/kibana-operations
|
||||
packages/kbn-ts-project-linter @elastic/kibana-operations
|
||||
packages/kbn-ts-project-linter-cli @elastic/kibana-operations
|
||||
packages/kbn-ts-projects @elastic/kibana-operations
|
||||
packages/kbn-ts-type-check-cli @elastic/kibana-operations
|
||||
packages/kbn-typed-react-router-config @elastic/apm-ui
|
||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -110,7 +110,8 @@ elastic-agent-*
|
|||
fleet-server-*
|
||||
elastic-agent.yml
|
||||
fleet-server.yml
|
||||
/packages/kbn-package-map/package-map.json
|
||||
/packages/*/package-map.json
|
||||
/packages/*/config-paths.json
|
||||
/packages/kbn-synthetic-package-map/
|
||||
**/.synthetics/
|
||||
**/.journeys/
|
||||
|
|
|
@ -14,9 +14,10 @@ import { haveNodeModulesBeenManuallyDeleted, removeYarnIntegrityFileIfExists } f
|
|||
import { setupRemoteCache } from './setup_remote_cache.mjs';
|
||||
import { sortPackageJson } from './sort_package_json.mjs';
|
||||
import { regeneratePackageMap } from './regenerate_package_map.mjs';
|
||||
import { regenerateTsconfigPaths } from './regenerate_tsconfig_paths.mjs';
|
||||
import { regenerateBaseTsconfig } from './regenerate_base_tsconfig.mjs';
|
||||
import { packageDiscovery, pluginDiscovery } from './discovery.mjs';
|
||||
import { validatePackageJson } from './validate_package_json.mjs';
|
||||
import { discovery } from './discovery.mjs';
|
||||
import { updatePackageJson } from './update_package_json.mjs';
|
||||
|
||||
/** @type {import('../../lib/command').Command} */
|
||||
export const command = {
|
||||
|
@ -61,6 +62,11 @@ export const command = {
|
|||
const forceInstall =
|
||||
args.getBooleanValue('force-install') ?? (await haveNodeModulesBeenManuallyDeleted());
|
||||
|
||||
const [{ packages, plugins, tsConfigsPaths }] = await Promise.all([
|
||||
// discover the location of packages, plugins, etc
|
||||
await time('discovery', discovery),
|
||||
|
||||
(async () => {
|
||||
await Bazel.tryRemovingBazeliskFromYarnGlobal(log);
|
||||
|
||||
// Install bazel machinery tools if needed
|
||||
|
@ -68,6 +74,21 @@ export const command = {
|
|||
|
||||
// Setup remote cache settings in .bazelrc.cache if needed
|
||||
await setupRemoteCache(log);
|
||||
})(),
|
||||
]);
|
||||
|
||||
// generate the package map and package.json file, if necessary
|
||||
await Promise.all([
|
||||
time('regenerate package map', async () => {
|
||||
await regeneratePackageMap(packages, plugins, log);
|
||||
}),
|
||||
time('regenerate tsconfig map', async () => {
|
||||
await regenerateTsconfigPaths(tsConfigsPaths, log);
|
||||
}),
|
||||
time('update package json', async () => {
|
||||
await updatePackageJson(packages, log);
|
||||
}),
|
||||
]);
|
||||
|
||||
// Bootstrap process for Bazel packages
|
||||
// Bazel is now managing dependencies so yarn install
|
||||
|
@ -85,34 +106,16 @@ export const command = {
|
|||
});
|
||||
}
|
||||
|
||||
// discover the location of packages and plugins
|
||||
const [plugins, packages] = await Promise.all([
|
||||
time('plugin discovery', pluginDiscovery),
|
||||
time('package discovery', packageDiscovery),
|
||||
]);
|
||||
|
||||
// generate the package map which powers the resolver and several other features
|
||||
// needed as an input to the bazel builds
|
||||
await time('regenerate package map', async () => {
|
||||
await regeneratePackageMap(packages, plugins, log);
|
||||
});
|
||||
|
||||
await time('pre-build webpack bundles for packages', async () => {
|
||||
await Bazel.buildWebpackBundles(log, { offline, quiet });
|
||||
});
|
||||
|
||||
await time('regenerate tsconfig.base.json', async () => {
|
||||
await regenerateBaseTsconfig();
|
||||
});
|
||||
|
||||
await Promise.all([
|
||||
time('sort package json', async () => {
|
||||
await sortPackageJson();
|
||||
time('regenerate tsconfig.base.json', async () => {
|
||||
await regenerateBaseTsconfig();
|
||||
}),
|
||||
time('validate package json', async () => {
|
||||
// now that deps are installed we can import `@kbn/yarn-lock-validator`
|
||||
const { kibanaPackageJson } = External['@kbn/repo-info']();
|
||||
await validatePackageJson(kibanaPackageJson, log);
|
||||
time('sort package json', async () => {
|
||||
await sortPackageJson(log);
|
||||
}),
|
||||
validate
|
||||
? time('validate dependencies', async () => {
|
||||
|
|
|
@ -6,33 +6,84 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import Path from 'path';
|
||||
import Fs from 'fs';
|
||||
import ChildProcess from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
|
||||
import { REPO_ROOT } from '../../lib/paths.mjs';
|
||||
const execAsync = promisify(ChildProcess.execFile);
|
||||
|
||||
// we need to run these in order to generate the pkg map which is used by things
|
||||
// like `@kbn/babel-register`, so we have to import the JS files directory and can't
|
||||
// rely on `@kbn/babel-register`.
|
||||
|
||||
export async function packageDiscovery() {
|
||||
const { discoverBazelPackages } = await import(
|
||||
// eslint-disable-next-line @kbn/imports/uniform_imports
|
||||
'../../../../packages/kbn-bazel-packages/index.js'
|
||||
);
|
||||
|
||||
return await discoverBazelPackages(REPO_ROOT);
|
||||
}
|
||||
|
||||
export async function pluginDiscovery() {
|
||||
export async function discovery() {
|
||||
const { getPluginSearchPaths, simpleKibanaPlatformPluginDiscovery } = await import(
|
||||
// eslint-disable-next-line @kbn/imports/uniform_imports
|
||||
'../../../../packages/kbn-plugin-discovery/index.js'
|
||||
);
|
||||
|
||||
const searchPaths = getPluginSearchPaths({
|
||||
const { Package } = await import(
|
||||
// we need to run this before we install node modules, so it can't rely on @kbn/* imports
|
||||
// eslint-disable-next-line @kbn/imports/uniform_imports
|
||||
'../../../../packages/kbn-repo-packages/index.js'
|
||||
);
|
||||
|
||||
const proc = await execAsync('git', ['ls-files', '-comt', '--exclude-standard'], {
|
||||
cwd: REPO_ROOT,
|
||||
encoding: 'utf8',
|
||||
maxBuffer: Infinity,
|
||||
});
|
||||
|
||||
const paths = new Map();
|
||||
/** @type {Map<string, Set<string>>} */
|
||||
const filesByName = new Map();
|
||||
|
||||
for (const raw of proc.stdout.split('\n')) {
|
||||
const line = raw.trim();
|
||||
if (!line) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const repoRel = line.slice(2); // trim the single char status and separating space from the line
|
||||
const name = repoRel.split('/').pop();
|
||||
if (name !== 'kibana.jsonc' && name !== 'tsconfig.json') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const existingPath = paths.get(repoRel);
|
||||
const path = existingPath ?? Path.resolve(REPO_ROOT, repoRel);
|
||||
if (!existingPath) {
|
||||
paths.set(repoRel, path);
|
||||
}
|
||||
|
||||
let files = filesByName.get(name);
|
||||
if (!files) {
|
||||
files = new Set();
|
||||
filesByName.set(name, files);
|
||||
}
|
||||
|
||||
if (line.startsWith('C ')) {
|
||||
// this line indicates that the previous path is changed in the working
|
||||
// tree, so we need to determine if it was deleted and remove it if so
|
||||
if (!Fs.existsSync(path)) {
|
||||
files.delete(path);
|
||||
}
|
||||
} else {
|
||||
files.add(path);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
plugins: simpleKibanaPlatformPluginDiscovery(
|
||||
getPluginSearchPaths({
|
||||
rootDir: REPO_ROOT,
|
||||
examples: true,
|
||||
oss: false,
|
||||
testPlugins: true,
|
||||
});
|
||||
|
||||
return simpleKibanaPlatformPluginDiscovery(searchPaths, []);
|
||||
}),
|
||||
[]
|
||||
),
|
||||
tsConfigsPaths: Array.from(filesByName.get('tsconfig.json') ?? new Set()),
|
||||
packages: Array.from(filesByName.get('kibana.jsonc') ?? new Set())
|
||||
.map((path) => Package.fromManifest(REPO_ROOT, path))
|
||||
.sort((a, b) => a.id.localeCompare(b.id)),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ import { REPO_ROOT } from '../../lib/paths.mjs';
|
|||
import External from '../../lib/external_packages.js';
|
||||
|
||||
export async function regenerateBaseTsconfig() {
|
||||
const pkgMap = External['@kbn/package-map']().readPackageMap();
|
||||
const pkgMap = External['@kbn/repo-packages']().readPackageMap();
|
||||
const tsconfigPath = Path.resolve(REPO_ROOT, 'tsconfig.base.json');
|
||||
const lines = (await Fsp.readFile(tsconfigPath, 'utf-8')).split('\n');
|
||||
|
||||
|
|
|
@ -16,18 +16,12 @@ import { REPO_ROOT } from '../../lib/paths.mjs';
|
|||
|
||||
/**
|
||||
*
|
||||
* @param {import('@kbn/bazel-packages').BazelPackage[]} packages
|
||||
* @param {import('@kbn/repo-packages').Package[]} packages
|
||||
* @param {import('@kbn/plugin-discovery').KibanaPlatformPlugin[]} plugins
|
||||
* @param {import('@kbn/some-dev-log').SomeDevLog} log
|
||||
*/
|
||||
export async function regeneratePackageMap(packages, plugins, log) {
|
||||
// clean up old version of package map package
|
||||
Fs.rmSync(Path.resolve(REPO_ROOT, 'packages/kbn-synthetic-package-map'), {
|
||||
recursive: true,
|
||||
force: true,
|
||||
});
|
||||
|
||||
const path = Path.resolve(REPO_ROOT, 'packages/kbn-package-map/package-map.json');
|
||||
const path = Path.resolve(REPO_ROOT, 'packages/kbn-repo-packages/package-map.json');
|
||||
const existingContent = Fs.existsSync(path) ? await Fsp.readFile(path, 'utf8') : undefined;
|
||||
|
||||
/** @type {Array<[string, string]>} */
|
||||
|
@ -52,6 +46,6 @@ export async function regeneratePackageMap(packages, plugins, log) {
|
|||
|
||||
if (content !== existingContent) {
|
||||
await Fsp.writeFile(path, content);
|
||||
log.warning('updated package map, many caches may be invalidated');
|
||||
log.warning('updated package map');
|
||||
}
|
||||
}
|
||||
|
|
32
kbn_pm/src/commands/bootstrap/regenerate_tsconfig_paths.mjs
Normal file
32
kbn_pm/src/commands/bootstrap/regenerate_tsconfig_paths.mjs
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import Path from 'path';
|
||||
import Fs from 'fs';
|
||||
import Fsp from 'fs/promises';
|
||||
|
||||
import { REPO_ROOT } from '../../lib/paths.mjs';
|
||||
|
||||
/**
|
||||
* @param {string[]} tsconfigPaths
|
||||
* @param {import('@kbn/some-dev-log').SomeDevLog} log
|
||||
*/
|
||||
export async function regenerateTsconfigPaths(tsconfigPaths, log) {
|
||||
const path = Path.resolve(REPO_ROOT, 'packages/kbn-ts-projects/config-paths.json');
|
||||
const existingContent = Fs.existsSync(path) ? await Fsp.readFile(path, 'utf8') : undefined;
|
||||
|
||||
const entries = [...tsconfigPaths]
|
||||
.map((abs) => Path.relative(REPO_ROOT, abs))
|
||||
.sort((a, b) => a.localeCompare(b));
|
||||
|
||||
const content = JSON.stringify(entries, null, 2);
|
||||
if (content !== existingContent) {
|
||||
await Fsp.writeFile(path, content);
|
||||
log.warning('updated tsconfig.json paths');
|
||||
}
|
||||
}
|
|
@ -7,15 +7,23 @@
|
|||
*/
|
||||
|
||||
import Path from 'path';
|
||||
import Fs from 'fs';
|
||||
import Fsp from 'fs/promises';
|
||||
|
||||
import { REPO_ROOT } from '../../lib/paths.mjs';
|
||||
import External from '../../lib/external_packages.js';
|
||||
|
||||
export async function sortPackageJson() {
|
||||
/**
|
||||
*
|
||||
* @param {import('@kbn/some-dev-log').SomeDevLog} log
|
||||
*/
|
||||
export async function sortPackageJson(log) {
|
||||
const { sortPackageJson } = External['@kbn/sort-package-json']();
|
||||
|
||||
const path = Path.resolve(REPO_ROOT, 'package.json');
|
||||
const json = Fs.readFileSync(path, 'utf8');
|
||||
Fs.writeFileSync(path, sortPackageJson(json));
|
||||
const json = await Fsp.readFile(path, 'utf8');
|
||||
const sorted = sortPackageJson(json);
|
||||
if (sorted !== json) {
|
||||
await Fsp.writeFile(path, sorted, 'utf8');
|
||||
log.success('sorted package.json');
|
||||
}
|
||||
}
|
||||
|
|
86
kbn_pm/src/commands/bootstrap/update_package_json.mjs
Normal file
86
kbn_pm/src/commands/bootstrap/update_package_json.mjs
Normal file
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import Fsp from 'fs/promises';
|
||||
import Path from 'path';
|
||||
|
||||
import { REPO_ROOT } from '../../lib/paths.mjs';
|
||||
|
||||
/**
|
||||
* @param {import('@kbn/repo-info').KibanaPackageJson['dependencies']} depsObj
|
||||
* @param {Map<string, string>} actual
|
||||
* @param {Map<string, string>} expected
|
||||
*/
|
||||
function updatePkgEntries(depsObj, actual, expected) {
|
||||
let changes = false;
|
||||
const keys = new Set([...actual.keys(), ...expected.keys()]);
|
||||
for (const key of keys) {
|
||||
const a = actual.get(key);
|
||||
const e = expected.get(key);
|
||||
|
||||
// if expected and actual match then we don't need to do anything
|
||||
if (a === e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
changes = true;
|
||||
|
||||
// if expected is undefined then this key shouldn't be set
|
||||
if (e === undefined) {
|
||||
delete depsObj[key];
|
||||
continue;
|
||||
}
|
||||
|
||||
// otherwise we just need to update/add this key/value
|
||||
depsObj[key] = e;
|
||||
}
|
||||
return changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@kbn/repo-packages').Package[]} pkgs
|
||||
* @param {import('@kbn/some-dev-log').SomeDevLog} log
|
||||
*/
|
||||
export async function updatePackageJson(pkgs, log) {
|
||||
const path = Path.resolve(REPO_ROOT, 'package.json');
|
||||
/** @type {import('@kbn/repo-info').KibanaPackageJson} */
|
||||
const pkgJson = JSON.parse(await Fsp.readFile(path, 'utf8'));
|
||||
|
||||
let changes = false;
|
||||
const typesInProd = Object.keys(pkgJson.dependencies).filter((id) => id.startsWith('@types/'));
|
||||
for (const t of typesInProd) {
|
||||
changes = true;
|
||||
pkgJson.devDependencies[t] = pkgJson.dependencies[t];
|
||||
delete pkgJson.dependencies[t];
|
||||
}
|
||||
|
||||
changes ||= updatePkgEntries(
|
||||
pkgJson.dependencies,
|
||||
new Map(Object.entries(pkgJson.dependencies).filter(([k]) => k.startsWith('@kbn/'))),
|
||||
new Map(
|
||||
pkgs
|
||||
.filter((p) => !p.isDevOnly)
|
||||
.map((p) => [p.manifest.id, `link:${p.normalizedRepoRelativeDir}`])
|
||||
)
|
||||
);
|
||||
|
||||
changes ||= updatePkgEntries(
|
||||
pkgJson.devDependencies,
|
||||
new Map(Object.entries(pkgJson.devDependencies).filter(([k]) => k.startsWith('@kbn/'))),
|
||||
new Map(
|
||||
pkgs
|
||||
.filter((p) => p.isDevOnly)
|
||||
.map((p) => [p.manifest.id, `link:${p.normalizedRepoRelativeDir}`])
|
||||
)
|
||||
);
|
||||
|
||||
if (changes) {
|
||||
await Fsp.writeFile(path, JSON.stringify(pkgJson, null, 2));
|
||||
log.warning('updated package.json');
|
||||
}
|
||||
}
|
|
@ -40,8 +40,8 @@ export const command = {
|
|||
const exclude = args.getStringValues('exclude') ?? [];
|
||||
const include = args.getStringValues('include') ?? [];
|
||||
|
||||
const { discoverBazelPackages } = External['@kbn/bazel-packages']();
|
||||
const packages = await discoverBazelPackages(REPO_ROOT);
|
||||
const { getPackages } = External['@kbn/repo-packages']();
|
||||
const packages = getPackages(REPO_ROOT);
|
||||
for (const { manifest, pkg, normalizedRepoRelativeDir } of packages) {
|
||||
if (
|
||||
exclude.includes(manifest.id) ||
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
module.exports = {
|
||||
['@kbn/bazel-packages']() {
|
||||
['@kbn/repo-packages']() {
|
||||
require('@kbn/babel-register').install();
|
||||
return require('@kbn/bazel-packages');
|
||||
return require('@kbn/repo-packages');
|
||||
},
|
||||
|
||||
['@kbn/ci-stats-reporter']() {
|
||||
|
@ -26,11 +26,6 @@ module.exports = {
|
|||
return require('@kbn/sort-package-json');
|
||||
},
|
||||
|
||||
['@kbn/package-map']() {
|
||||
require('@kbn/babel-register').install();
|
||||
return require('@kbn/package-map');
|
||||
},
|
||||
|
||||
['@kbn/get-repo-files']() {
|
||||
require('@kbn/babel-register').install();
|
||||
return require('@kbn/get-repo-files');
|
||||
|
|
|
@ -16,11 +16,11 @@ import External from './external_packages.js';
|
|||
* Attempt to load the package map, if bootstrap hasn't run successfully
|
||||
* this might fail.
|
||||
* @param {import('@kbn/some-dev-log').SomeDevLog} log
|
||||
* @returns {Promise<import('@kbn/package-map').PackageMap>}
|
||||
* @returns {Promise<import('@kbn/repo-packages').PackageMap>}
|
||||
*/
|
||||
async function tryToGetPackageMap(log) {
|
||||
try {
|
||||
const { readPackageMap } = External['@kbn/package-map']();
|
||||
const { readPackageMap } = External['@kbn/repo-packages']();
|
||||
return readPackageMap();
|
||||
} catch (error) {
|
||||
log.warning('unable to load package map, unable to clean target directories in packages');
|
||||
|
|
|
@ -15,14 +15,13 @@
|
|||
],
|
||||
"kbn_references": [
|
||||
"@kbn/babel-register",
|
||||
"@kbn/bazel-packages",
|
||||
"@kbn/repo-info",
|
||||
"@kbn/yarn-lock-validator",
|
||||
"@kbn/get-repo-files",
|
||||
"@kbn/sort-package-json",
|
||||
{ "path": "../src/dev/tsconfig.json" },
|
||||
"@kbn/ci-stats-reporter",
|
||||
"@kbn/package-map",
|
||||
"@kbn/ts-projects"
|
||||
"@kbn/ts-projects",
|
||||
"@kbn/repo-packages"
|
||||
]
|
||||
}
|
||||
|
|
26
package.json
26
package.json
|
@ -148,8 +148,8 @@
|
|||
"@kbn/config": "link:packages/kbn-config",
|
||||
"@kbn/config-mocks": "link:packages/kbn-config-mocks",
|
||||
"@kbn/config-schema": "link:packages/kbn-config-schema",
|
||||
"@kbn/content-management-content-editor": "link:bazel-bin/packages/content-management/content_editor",
|
||||
"@kbn/content-management-table-list": "link:bazel-bin/packages/content-management/table_list",
|
||||
"@kbn/content-management-content-editor": "link:packages/content-management/content_editor",
|
||||
"@kbn/content-management-table-list": "link:packages/content-management/table_list",
|
||||
"@kbn/core-analytics-browser": "link:packages/core/analytics/core-analytics-browser",
|
||||
"@kbn/core-analytics-browser-internal": "link:packages/core/analytics/core-analytics-browser-internal",
|
||||
"@kbn/core-analytics-browser-mocks": "link:packages/core/analytics/core-analytics-browser-mocks",
|
||||
|
@ -179,7 +179,6 @@
|
|||
"@kbn/core-chrome-browser-internal": "link:packages/core/chrome/core-chrome-browser-internal",
|
||||
"@kbn/core-chrome-browser-mocks": "link:packages/core/chrome/core-chrome-browser-mocks",
|
||||
"@kbn/core-config-server-internal": "link:packages/core/config/core-config-server-internal",
|
||||
"@kbn/core-config-server-mocks": "link:packages/core/config/core-config-server-mocks",
|
||||
"@kbn/core-deprecations-browser": "link:packages/core/deprecations/core-deprecations-browser",
|
||||
"@kbn/core-deprecations-browser-internal": "link:packages/core/deprecations/core-deprecations-browser-internal",
|
||||
"@kbn/core-deprecations-browser-mocks": "link:packages/core/deprecations/core-deprecations-browser-mocks",
|
||||
|
@ -193,7 +192,6 @@
|
|||
"@kbn/core-doc-links-server": "link:packages/core/doc-links/core-doc-links-server",
|
||||
"@kbn/core-doc-links-server-internal": "link:packages/core/doc-links/core-doc-links-server-internal",
|
||||
"@kbn/core-doc-links-server-mocks": "link:packages/core/doc-links/core-doc-links-server-mocks",
|
||||
"@kbn/core-elasticsearch-client-server": "link:packages/core/elasticsearch/core-elasticsearch-client-server",
|
||||
"@kbn/core-elasticsearch-client-server-internal": "link:packages/core/elasticsearch/core-elasticsearch-client-server-internal",
|
||||
"@kbn/core-elasticsearch-client-server-mocks": "link:packages/core/elasticsearch/core-elasticsearch-client-server-mocks",
|
||||
"@kbn/core-elasticsearch-server": "link:packages/core/elasticsearch/core-elasticsearch-server",
|
||||
|
@ -361,6 +359,7 @@
|
|||
"@kbn/plugin-discovery": "link:packages/kbn-plugin-discovery",
|
||||
"@kbn/react-field": "link:packages/kbn-react-field",
|
||||
"@kbn/repo-info": "link:packages/kbn-repo-info",
|
||||
"@kbn/repo-packages": "link:packages/kbn-repo-packages",
|
||||
"@kbn/rison": "link:packages/kbn-rison",
|
||||
"@kbn/rule-data-utils": "link:packages/kbn-rule-data-utils",
|
||||
"@kbn/safer-lodash-set": "link:packages/kbn-safer-lodash-set",
|
||||
|
@ -381,6 +380,7 @@
|
|||
"@kbn/securitysolution-utils": "link:packages/kbn-securitysolution-utils",
|
||||
"@kbn/server-http-tools": "link:packages/kbn-server-http-tools",
|
||||
"@kbn/server-route-repository": "link:packages/kbn-server-route-repository",
|
||||
"@kbn/set-map": "link:packages/kbn-set-map",
|
||||
"@kbn/shared-svg": "link:packages/kbn-shared-svg",
|
||||
"@kbn/shared-ux-avatar-solution": "link:packages/shared-ux/avatar/solution",
|
||||
"@kbn/shared-ux-avatar-user-profile-components": "link:packages/shared-ux/avatar/user_profile/impl",
|
||||
|
@ -425,9 +425,10 @@
|
|||
"@kbn/shared-ux-prompt-no-data-views-mocks": "link:packages/shared-ux/prompt/no_data_views/mocks",
|
||||
"@kbn/shared-ux-prompt-no-data-views-types": "link:packages/shared-ux/prompt/no_data_views/types",
|
||||
"@kbn/shared-ux-prompt-not-found": "link:packages/shared-ux/prompt/not_found",
|
||||
"@kbn/shared-ux-router": "link:packages/shared-ux/router/impl",
|
||||
"@kbn/shared-ux-router-mocks": "link:packages/shared-ux/router/mocks",
|
||||
"@kbn/shared-ux-services": "link:packages/kbn-shared-ux-services",
|
||||
"@kbn/shared-ux-storybook": "link:packages/kbn-shared-ux-storybook",
|
||||
"@kbn/shared-ux-router-types": "link:packages/shared-ux/router/types",
|
||||
"@kbn/shared-ux-storybook-config": "link:packages/shared-ux/storybook/config",
|
||||
"@kbn/shared-ux-storybook-mock": "link:packages/shared-ux/storybook/mock",
|
||||
"@kbn/shared-ux-utility": "link:packages/kbn-shared-ux-utility",
|
||||
"@kbn/slo-schema": "link:packages/kbn-slo-schema",
|
||||
|
@ -748,7 +749,6 @@
|
|||
"@kbn/babel-preset": "link:packages/kbn-babel-preset",
|
||||
"@kbn/babel-register": "link:packages/kbn-babel-register",
|
||||
"@kbn/babel-transform": "link:packages/kbn-babel-transform",
|
||||
"@kbn/bazel-packages": "link:packages/kbn-bazel-packages",
|
||||
"@kbn/bazel-runner": "link:packages/kbn-bazel-runner",
|
||||
"@kbn/ci-stats-core": "link:packages/kbn-ci-stats-core",
|
||||
"@kbn/ci-stats-performance-metrics": "link:packages/kbn-ci-stats-performance-metrics",
|
||||
|
@ -774,20 +774,26 @@
|
|||
"@kbn/ftr-screenshot-filename": "link:packages/kbn-ftr-screenshot-filename",
|
||||
"@kbn/generate": "link:packages/kbn-generate",
|
||||
"@kbn/get-repo-files": "link:packages/kbn-get-repo-files",
|
||||
"@kbn/import-locator": "link:packages/kbn-import-locator",
|
||||
"@kbn/import-resolver": "link:packages/kbn-import-resolver",
|
||||
"@kbn/jest-serializers": "link:packages/kbn-jest-serializers",
|
||||
"@kbn/journeys": "link:packages/kbn-journeys",
|
||||
"@kbn/json-ast": "link:packages/kbn-json-ast",
|
||||
"@kbn/kibana-manifest-schema": "link:packages/kbn-kibana-manifest-schema",
|
||||
"@kbn/lint-packages-cli": "link:packages/kbn-lint-packages-cli",
|
||||
"@kbn/lint-ts-projects-cli": "link:packages/kbn-lint-ts-projects-cli",
|
||||
"@kbn/managed-vscode-config": "link:packages/kbn-managed-vscode-config",
|
||||
"@kbn/managed-vscode-config-cli": "link:packages/kbn-managed-vscode-config-cli",
|
||||
"@kbn/optimizer": "link:packages/kbn-optimizer",
|
||||
"@kbn/optimizer-webpack-helpers": "link:packages/kbn-optimizer-webpack-helpers",
|
||||
"@kbn/package-map": "link:packages/kbn-package-map",
|
||||
"@kbn/peggy": "link:packages/kbn-peggy",
|
||||
"@kbn/peggy-loader": "link:packages/kbn-peggy-loader",
|
||||
"@kbn/performance-testing-dataset-extractor": "link:packages/kbn-performance-testing-dataset-extractor",
|
||||
"@kbn/picomatcher": "link:packages/kbn-picomatcher",
|
||||
"@kbn/plugin-generator": "link:packages/kbn-plugin-generator",
|
||||
"@kbn/plugin-helpers": "link:packages/kbn-plugin-helpers",
|
||||
"@kbn/repo-file-maps": "link:packages/kbn-repo-file-maps",
|
||||
"@kbn/repo-linter": "link:packages/kbn-repo-linter",
|
||||
"@kbn/repo-path": "link:packages/kbn-repo-path",
|
||||
"@kbn/repo-source-classifier": "link:packages/kbn-repo-source-classifier",
|
||||
"@kbn/repo-source-classifier-cli": "link:packages/kbn-repo-source-classifier-cli",
|
||||
|
@ -801,8 +807,6 @@
|
|||
"@kbn/test-jest-helpers": "link:packages/kbn-test-jest-helpers",
|
||||
"@kbn/test-subj-selector": "link:packages/kbn-test-subj-selector",
|
||||
"@kbn/tooling-log": "link:packages/kbn-tooling-log",
|
||||
"@kbn/ts-project-linter": "link:packages/kbn-ts-project-linter",
|
||||
"@kbn/ts-project-linter-cli": "link:packages/kbn-ts-project-linter-cli",
|
||||
"@kbn/ts-projects": "link:packages/kbn-ts-projects",
|
||||
"@kbn/ts-type-check-cli": "link:packages/kbn-ts-type-check-cli",
|
||||
"@kbn/web-worker-stub": "link:packages/kbn-web-worker-stub",
|
||||
|
@ -931,6 +935,7 @@
|
|||
"@types/pbf": "3.0.2",
|
||||
"@types/pdfmake": "^0.2.2",
|
||||
"@types/pegjs": "^0.10.1",
|
||||
"@types/picomatch": "^2.3.0",
|
||||
"@types/pidusage": "^2.0.2",
|
||||
"@types/pixelmatch": "^5.2.4",
|
||||
"@types/pngjs": "^3.4.0",
|
||||
|
@ -1116,6 +1121,7 @@
|
|||
"openapi-types": "^10.0.0",
|
||||
"pbf": "3.2.1",
|
||||
"peggy": "^1.2.0",
|
||||
"picomatch": "^2.3.1",
|
||||
"pidusage": "^3.0.2",
|
||||
"pirates": "^4.0.1",
|
||||
"piscina": "^3.2.0",
|
||||
|
|
|
@ -19,7 +19,7 @@ BUNDLER_DEPS = [
|
|||
"@npm//@babel/helper-plugin-utils",
|
||||
"@npm//normalize-path",
|
||||
"//packages/kbn-repo-info",
|
||||
"//packages/kbn-package-map",
|
||||
"//packages/kbn-repo-packages",
|
||||
]
|
||||
|
||||
js_library(
|
||||
|
|
|
@ -11,10 +11,10 @@ const Path = require('path');
|
|||
const T = require('@babel/types');
|
||||
const normalizePath = require('normalize-path');
|
||||
const { declare } = require('@babel/helper-plugin-utils');
|
||||
const KbnSyntheticPackageMap = require('@kbn/package-map');
|
||||
const { readPackageMap } = require('@kbn/repo-packages');
|
||||
const { REPO_ROOT } = require('@kbn/repo-info');
|
||||
|
||||
const PKG_MAP = KbnSyntheticPackageMap.readPackageMap();
|
||||
const PKG_MAP = readPackageMap();
|
||||
|
||||
/**
|
||||
* @param {unknown} v
|
||||
|
|
|
@ -17,6 +17,6 @@
|
|||
],
|
||||
"kbn_references": [
|
||||
"@kbn/repo-info",
|
||||
"@kbn/package-map"
|
||||
"@kbn/repo-packages"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ BUNDLER_DEPS = [
|
|||
"@npm//pirates",
|
||||
"@npm//lmdb",
|
||||
"@npm//source-map-support",
|
||||
"//packages/kbn-package-map",
|
||||
"//packages/kbn-repo-packages",
|
||||
"//packages/kbn-repo-info",
|
||||
"//packages/kbn-babel-transform",
|
||||
]
|
||||
|
|
2
packages/kbn-babel-register/cache/index.js
vendored
2
packages/kbn-babel-register/cache/index.js
vendored
|
@ -10,7 +10,7 @@ const Fs = require('fs');
|
|||
const Path = require('path');
|
||||
const Crypto = require('crypto');
|
||||
|
||||
const { readHashOfPackageMap } = require('@kbn/package-map');
|
||||
const { readHashOfPackageMap } = require('@kbn/repo-packages');
|
||||
const babel = require('@babel/core');
|
||||
const peggy = require('@kbn/peggy');
|
||||
const { REPO_ROOT, UPSTREAM_BRANCH } = require('@kbn/repo-info');
|
||||
|
|
|
@ -13,10 +13,10 @@
|
|||
"**/*.ts",
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/package-map",
|
||||
"@kbn/repo-info",
|
||||
"@kbn/babel-transform",
|
||||
"@kbn/peggy",
|
||||
"@kbn/repo-packages",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
# @kbn/bazel-packages
|
||||
|
||||
APIs for dealing with bazel packages in the Kibana repo
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
/** @typedef {import('./src/bazel_package').BazelPackage} BazelPackage */
|
||||
/** @typedef {import('./src/types').KibanaPackageManifest} KibanaPackageManifest */
|
||||
/** @typedef {import('./src/types').KibanaPackageType} KibanaPackageType */
|
||||
/** @typedef {import('./src/types').ParsedPackageJson} ParsedPackageJson */
|
||||
|
||||
const { BAZEL_PACKAGE_DIRS, getAllBazelPackageDirs } = require('./src/bazel_package_dirs');
|
||||
const { discoverPackageManifestPaths, discoverBazelPackages } = require('./src/discover_packages');
|
||||
const {
|
||||
parsePackageManifest,
|
||||
readPackageManifest,
|
||||
validatePackageManifest,
|
||||
} = require('./src/parse_package_manifest');
|
||||
const Jsonc = require('./src/jsonc');
|
||||
|
||||
module.exports = {
|
||||
BAZEL_PACKAGE_DIRS,
|
||||
getAllBazelPackageDirs,
|
||||
discoverPackageManifestPaths,
|
||||
discoverBazelPackages,
|
||||
parsePackageManifest,
|
||||
readPackageManifest,
|
||||
validatePackageManifest,
|
||||
Jsonc,
|
||||
};
|
|
@ -1,97 +0,0 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
const { inspect } = require('util');
|
||||
const Path = require('path');
|
||||
|
||||
const { readPackageJson } = require('./parse_package_json');
|
||||
const { readPackageManifest } = require('./parse_package_manifest');
|
||||
|
||||
/**
|
||||
* Representation of a Bazel Package in the Kibana repository
|
||||
* @class
|
||||
* @property {string} normalizedRepoRelativeDir
|
||||
* @property {import('./types').KibanaPackageManifest} manifest
|
||||
* @property {import('./types').ParsedPackageJson | undefined} pkg
|
||||
*/
|
||||
class BazelPackage {
|
||||
/**
|
||||
* Create a BazelPackage object from a package directory. Reads some files from the package and returns
|
||||
* a Promise for a BazelPackage instance.
|
||||
* @param {string} repoRoot
|
||||
* @param {string} path
|
||||
*/
|
||||
static async fromManifest(repoRoot, path) {
|
||||
const manifest = readPackageManifest(path);
|
||||
const dir = Path.dirname(path);
|
||||
|
||||
return new BazelPackage(
|
||||
Path.relative(repoRoot, dir),
|
||||
manifest,
|
||||
readPackageJson(Path.resolve(dir, 'package.json'))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort a list of bazek packages
|
||||
* @param {BazelPackage[]} pkgs
|
||||
*/
|
||||
static sort(pkgs) {
|
||||
return pkgs.slice().sort(BazelPackage.sorter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort an array of bazel packages
|
||||
* @param {BazelPackage} a
|
||||
* @param {BazelPackage} b
|
||||
*/
|
||||
static sorter(a, b) {
|
||||
return a.normalizedRepoRelativeDir.localeCompare(b.normalizedRepoRelativeDir);
|
||||
}
|
||||
|
||||
constructor(
|
||||
/**
|
||||
* Relative path from the root of the repository to the package directory
|
||||
* @type {string}
|
||||
*/
|
||||
normalizedRepoRelativeDir,
|
||||
/**
|
||||
* Parsed kibana.jsonc manifest from the package
|
||||
* @type {import('./types').KibanaPackageManifest}
|
||||
*/
|
||||
manifest,
|
||||
/**
|
||||
* Parsed package.json file from the package
|
||||
* @type {import('./types').ParsedPackageJson | undefined}
|
||||
*/
|
||||
pkg
|
||||
) {
|
||||
this.normalizedRepoRelativeDir = normalizedRepoRelativeDir;
|
||||
this.manifest = manifest;
|
||||
this.pkg = pkg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the package is not intended to be in the build
|
||||
*/
|
||||
isDevOnly() {
|
||||
return !!this.manifest.devOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom inspect handler so that logging variables in scripts/generate doesn't
|
||||
* print all the BUILD.bazel files
|
||||
*/
|
||||
[inspect.custom]() {
|
||||
return `BazelPackage<${this.normalizedRepoRelativeDir}>`;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
BazelPackage,
|
||||
};
|
|
@ -1,44 +0,0 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
const { expandWildcards } = require('./find_files');
|
||||
|
||||
/**
|
||||
* This is a list of repo-relative paths to directories containing packages. Do not
|
||||
* include `**` in these, one or two `*` segments is acceptable, we need this search
|
||||
* to be super fast so please avoid deep recursive searching.
|
||||
*
|
||||
* eg. src/vis_editors => would find a package at src/vis_editors/foo/package.json
|
||||
* src/vis_editors/* => would find a package at src/vis_editors/foo/bar/package.json
|
||||
*/
|
||||
const BAZEL_PACKAGE_DIRS = [
|
||||
'packages',
|
||||
'packages/shared-ux',
|
||||
'packages/shared-ux/*',
|
||||
'packages/shared-ux/*/*',
|
||||
'packages/analytics',
|
||||
'packages/analytics/shippers',
|
||||
'packages/analytics/shippers/elastic_v3',
|
||||
'packages/core/*',
|
||||
'packages/home',
|
||||
'packages/content-management',
|
||||
'x-pack/packages/ml',
|
||||
];
|
||||
|
||||
/**
|
||||
* Resolve all the BAZEL_PACKAGE_DIRS to absolute paths
|
||||
* @param {string} repoRoot
|
||||
*/
|
||||
function getAllBazelPackageDirs(repoRoot) {
|
||||
return expandWildcards(repoRoot, BAZEL_PACKAGE_DIRS);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
BAZEL_PACKAGE_DIRS,
|
||||
getAllBazelPackageDirs,
|
||||
};
|
|
@ -1,39 +0,0 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
const { BazelPackage } = require('./bazel_package');
|
||||
const { getAllBazelPackageDirs } = require('./bazel_package_dirs');
|
||||
const { findPackages } = require('./find_files');
|
||||
const { asyncMapWithLimit } = require('./async');
|
||||
|
||||
/**
|
||||
* Returns an array of all the package manifest paths in the repository
|
||||
* @param {string} repoRoot
|
||||
*/
|
||||
function discoverPackageManifestPaths(repoRoot) {
|
||||
return getAllBazelPackageDirs(repoRoot)
|
||||
.flatMap((packageDir) => findPackages(packageDir, 'kibana.jsonc'))
|
||||
.sort((a, b) => a.localeCompare(b));
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves to an array of BazelPackage instances which parse the manifest files,
|
||||
* package.json files, and provide useful metadata about each package.
|
||||
* @param {string} repoRoot
|
||||
*/
|
||||
async function discoverBazelPackages(repoRoot) {
|
||||
return BazelPackage.sort(
|
||||
await asyncMapWithLimit(
|
||||
discoverPackageManifestPaths(repoRoot),
|
||||
100,
|
||||
async (path) => await BazelPackage.fromManifest(repoRoot, path)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = { discoverPackageManifestPaths, discoverBazelPackages };
|
|
@ -115,6 +115,7 @@ export class CliDevMode {
|
|||
}
|
||||
|
||||
const { watchPaths, ignorePaths } = getServerWatchPaths({
|
||||
runExamples: cliArgs.runExamples,
|
||||
pluginPaths: config.plugins.additionalPluginPaths,
|
||||
pluginScanDirs: config.plugins.pluginSearchPaths,
|
||||
});
|
||||
|
|
|
@ -10,13 +10,48 @@ import Path from 'path';
|
|||
|
||||
import { createAbsolutePathSerializer } from '@kbn/jest-serializers';
|
||||
import { REPO_ROOT } from '@kbn/repo-info';
|
||||
import type { KibanaPackageType } from '@kbn/repo-packages';
|
||||
|
||||
const TYPES = Object.keys(
|
||||
(() => {
|
||||
const asObj: { [k in KibanaPackageType]: true } = {
|
||||
'functional-tests': true,
|
||||
'plugin-browser': true,
|
||||
'plugin-server': true,
|
||||
'shared-browser': true,
|
||||
'shared-common': true,
|
||||
'shared-scss': true,
|
||||
'shared-server': true,
|
||||
'test-helper': true,
|
||||
};
|
||||
return asObj;
|
||||
})()
|
||||
);
|
||||
|
||||
import { getServerWatchPaths } from './get_server_watch_paths';
|
||||
|
||||
jest.mock('@kbn/repo-packages', () => ({
|
||||
getPackages: jest.fn(),
|
||||
getPluginPackagesFilter: jest.fn().mockReturnValue(() => true),
|
||||
}));
|
||||
const mockGetPluginPackagesFilter = jest.requireMock('@kbn/repo-packages').getPluginPackagesFilter;
|
||||
const mockGetPackages = jest.requireMock('@kbn/repo-packages').getPackages;
|
||||
|
||||
expect.addSnapshotSerializer(createAbsolutePathSerializer());
|
||||
|
||||
it('produces the right watch and ignore list', () => {
|
||||
mockGetPackages.mockReturnValue(
|
||||
TYPES.flatMap((type) => ({
|
||||
isPlugin: type.startsWith('plugin-'),
|
||||
directory: Path.resolve(REPO_ROOT, 'packages', type),
|
||||
manifest: {
|
||||
type,
|
||||
},
|
||||
}))
|
||||
);
|
||||
|
||||
const { watchPaths, ignorePaths } = getServerWatchPaths({
|
||||
runExamples: false,
|
||||
pluginPaths: [Path.resolve(REPO_ROOT, 'x-pack/test/plugin_functional/plugins/resolver_test')],
|
||||
pluginScanDirs: [
|
||||
Path.resolve(REPO_ROOT, 'src/plugins'),
|
||||
|
@ -33,14 +68,9 @@ it('produces the right watch and ignore list', () => {
|
|||
<absolute path>/src/plugins,
|
||||
<absolute path>/test/plugin_functional/plugins,
|
||||
<absolute path>/x-pack/plugins,
|
||||
<absolute path>/packages,
|
||||
<absolute path>/packages/shared-ux,
|
||||
<absolute path>/packages/analytics,
|
||||
<absolute path>/packages/analytics/shippers,
|
||||
<absolute path>/packages/analytics/shippers/elastic_v3,
|
||||
<absolute path>/packages/home,
|
||||
<absolute path>/packages/content-management,
|
||||
<absolute path>/x-pack/packages/ml,
|
||||
<absolute path>/packages/plugin-server,
|
||||
<absolute path>/packages/shared-common,
|
||||
<absolute path>/packages/shared-server,
|
||||
]
|
||||
`);
|
||||
|
||||
|
@ -89,4 +119,22 @@ it('produces the right watch and ignore list', () => {
|
|||
<absolute path>/x-pack/plugins/observability/e2e,
|
||||
]
|
||||
`);
|
||||
|
||||
expect(mockGetPluginPackagesFilter.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
Object {
|
||||
"examples": false,
|
||||
"parentDirs": Array [
|
||||
<absolute path>/src/plugins,
|
||||
<absolute path>/test/plugin_functional/plugins,
|
||||
<absolute path>/x-pack/plugins,
|
||||
],
|
||||
"paths": Array [
|
||||
<absolute path>/x-pack/test/plugin_functional/plugins/resolver_test,
|
||||
],
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
|
|
@ -7,24 +7,24 @@
|
|||
*/
|
||||
|
||||
import Path from 'path';
|
||||
import Fs from 'fs';
|
||||
|
||||
import { REPO_ROOT } from '@kbn/repo-info';
|
||||
import { BAZEL_PACKAGE_DIRS } from '@kbn/bazel-packages';
|
||||
import { getPackages, getPluginPackagesFilter } from '@kbn/repo-packages';
|
||||
|
||||
interface Options {
|
||||
runExamples: boolean;
|
||||
pluginPaths: string[];
|
||||
pluginScanDirs: string[];
|
||||
}
|
||||
|
||||
export type WatchPaths = ReturnType<typeof getServerWatchPaths>;
|
||||
|
||||
export function getServerWatchPaths({ pluginPaths, pluginScanDirs }: Options) {
|
||||
export function getServerWatchPaths(opts: Options) {
|
||||
const fromRoot = (p: string) => Path.resolve(REPO_ROOT, p);
|
||||
|
||||
const pluginInternalDirsIgnore = pluginScanDirs
|
||||
const pluginInternalDirsIgnore = opts.pluginScanDirs
|
||||
.map((scanDir) => Path.resolve(scanDir, '*'))
|
||||
.concat(pluginPaths)
|
||||
.concat(opts.pluginPaths)
|
||||
.reduce(
|
||||
(acc: string[], path) => [
|
||||
...acc,
|
||||
|
@ -38,19 +38,33 @@ export function getServerWatchPaths({ pluginPaths, pluginScanDirs }: Options) {
|
|||
[]
|
||||
);
|
||||
|
||||
function getServerPkgDirs() {
|
||||
const pluginFilter = getPluginPackagesFilter({
|
||||
examples: opts.runExamples,
|
||||
paths: opts.pluginPaths,
|
||||
parentDirs: opts.pluginScanDirs,
|
||||
});
|
||||
|
||||
return getPackages(REPO_ROOT).flatMap((p) => {
|
||||
if (p.isPlugin) {
|
||||
return pluginFilter(p) && p.manifest.type === 'plugin-server' ? p.directory : [];
|
||||
}
|
||||
|
||||
return p.manifest.type === 'shared-common' || p.manifest.type === 'shared-server'
|
||||
? p.directory
|
||||
: [];
|
||||
});
|
||||
}
|
||||
|
||||
const watchPaths = Array.from(
|
||||
new Set(
|
||||
[
|
||||
new Set([
|
||||
fromRoot('src/core'),
|
||||
fromRoot('src/legacy/server'),
|
||||
fromRoot('src/legacy/utils'),
|
||||
fromRoot('config'),
|
||||
...pluginPaths,
|
||||
...pluginScanDirs,
|
||||
...BAZEL_PACKAGE_DIRS,
|
||||
].map((path) => Path.resolve(path))
|
||||
)
|
||||
).filter((path) => Fs.existsSync(fromRoot(path)));
|
||||
...opts.pluginPaths.map((path) => Path.resolve(path)),
|
||||
...opts.pluginScanDirs.map((path) => Path.resolve(path)),
|
||||
...getServerPkgDirs(),
|
||||
])
|
||||
);
|
||||
|
||||
const ignorePaths = [
|
||||
/[\\\/](\..*|node_modules|bower_components|target|public|__[a-z0-9_]+__|coverage)([\\\/]|$)/,
|
||||
|
|
|
@ -20,8 +20,8 @@
|
|||
"@kbn/ci-stats-reporter",
|
||||
"@kbn/jest-serializers",
|
||||
"@kbn/stdio-dev-helpers",
|
||||
"@kbn/bazel-packages",
|
||||
"@kbn/tooling-log",
|
||||
"@kbn/repo-packages",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -10,7 +10,7 @@ import Fsp from 'fs/promises';
|
|||
import Path from 'path';
|
||||
|
||||
import { REPO_ROOT } from '@kbn/repo-info';
|
||||
import { discoverBazelPackages } from '@kbn/bazel-packages';
|
||||
import { getPackages } from '@kbn/repo-packages';
|
||||
|
||||
import type { GenerateCommand } from '../generate_command';
|
||||
|
||||
|
@ -33,7 +33,7 @@ export const CodeownersCommand: GenerateCommand = {
|
|||
async run({ log }) {
|
||||
const coPath = Path.resolve(REPO_ROOT, REL);
|
||||
const codeowners = await Fsp.readFile(coPath, 'utf8');
|
||||
const pkgs = await discoverBazelPackages(REPO_ROOT);
|
||||
const pkgs = getPackages(REPO_ROOT);
|
||||
|
||||
let genStart = codeowners.indexOf(GENERATED_START);
|
||||
if (genStart === -1) {
|
||||
|
|
|
@ -13,9 +13,7 @@ import normalizePath from 'normalize-path';
|
|||
import globby from 'globby';
|
||||
import { ESLint } from 'eslint';
|
||||
|
||||
import micromatch from 'micromatch';
|
||||
import { REPO_ROOT } from '@kbn/repo-info';
|
||||
import { BAZEL_PACKAGE_DIRS } from '@kbn/bazel-packages';
|
||||
import { createFailError, createFlagError, isFailError } from '@kbn/dev-cli-errors';
|
||||
import { sortPackageJson } from '@kbn/sort-package-json';
|
||||
|
||||
|
@ -38,10 +36,7 @@ export const PackageCommand: GenerateCommand = {
|
|||
--dev Generate a package which is intended for dev-only use and can access things like devDependencies
|
||||
--web Build webpack-compatible version of sources for this package. If your package is intended to be
|
||||
used in the browser and Node.js then you need to opt-into these sources being created.
|
||||
--dir Specify where this package will be written. The path must be a direct child of one of the
|
||||
directories selected by the BAZEL_PACKAGE_DIRS const in @kbn/bazel-packages.
|
||||
Valid locations for packages:
|
||||
${BAZEL_PACKAGE_DIRS.map((dir) => ` ./${dir}/*\n`).join('')}
|
||||
--dir Specify where this package will be written.
|
||||
defaults to [./packages/{kebab-case-version-of-name}]
|
||||
--force If the --dir already exists, delete it before generation
|
||||
--owner Github username of the owner for this package, if this is not specified then you will be asked for
|
||||
|
@ -74,13 +69,6 @@ ${BAZEL_PACKAGE_DIRS.map((dir) => ` ./${dir}/*\n`).join
|
|||
const packageDir = flags.dir
|
||||
? Path.resolve(`${flags.dir}`)
|
||||
: Path.resolve(ROOT_PKG_DIR, pkgId.slice(1).replace('/', '-'));
|
||||
const relContainingDir = Path.relative(REPO_ROOT, Path.dirname(packageDir));
|
||||
if (!micromatch.isMatch(relContainingDir, BAZEL_PACKAGE_DIRS)) {
|
||||
throw createFlagError(
|
||||
'Invalid --dir selection. To setup a new --dir option extend the `BAZEL_PACKAGE_DIRS` const in `@kbn/bazel-packages` and make sure to rebuild.'
|
||||
);
|
||||
}
|
||||
|
||||
const normalizedRepoRelativeDir = normalizePath(Path.relative(REPO_ROOT, packageDir));
|
||||
|
||||
try {
|
||||
|
|
|
@ -11,12 +11,12 @@
|
|||
"**/*.ts"
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/bazel-packages",
|
||||
"@kbn/sort-package-json",
|
||||
"@kbn/dev-cli-runner",
|
||||
"@kbn/repo-info",
|
||||
"@kbn/dev-cli-errors",
|
||||
"@kbn/tooling-log",
|
||||
"@kbn/repo-packages",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
3
packages/kbn-import-locator/README.md
Normal file
3
packages/kbn-import-locator/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# @kbn/import-locator
|
||||
|
||||
ImportLocator is extracted from nx and used to very quickly find import statements in JS/TS code.
|
|
@ -11,21 +11,19 @@
|
|||
|
||||
import Fsp from 'fs/promises';
|
||||
import Ts from 'typescript';
|
||||
import { RepoPath } from '@kbn/repo-path';
|
||||
|
||||
import { stripSourceCode } from './strip_source_code';
|
||||
|
||||
const EMPTY = new Set<string>();
|
||||
|
||||
export class TypeScriptImportLocator {
|
||||
export class ImportLocator {
|
||||
private readonly scanner: Ts.Scanner;
|
||||
|
||||
constructor() {
|
||||
this.scanner = Ts.createScanner(Ts.ScriptTarget.Latest, false, Ts.LanguageVariant.JSX);
|
||||
}
|
||||
|
||||
async get(path: RepoPath): Promise<Set<string>> {
|
||||
const content = await Fsp.readFile(path.abs, 'utf8');
|
||||
get(path: string, content: string): Set<string> {
|
||||
const strippedContent = stripSourceCode(this.scanner, content);
|
||||
if (strippedContent === '') {
|
||||
return EMPTY;
|
||||
|
@ -33,7 +31,7 @@ export class TypeScriptImportLocator {
|
|||
|
||||
const imports = new Set<string>();
|
||||
const queue: Ts.Node[] = [
|
||||
Ts.createSourceFile(path.abs, strippedContent, Ts.ScriptTarget.Latest, true),
|
||||
Ts.createSourceFile(path, strippedContent, Ts.ScriptTarget.Latest, true),
|
||||
];
|
||||
const addNodeToQueue = (n: Ts.Node) => {
|
||||
queue.push(n);
|
||||
|
@ -76,4 +74,9 @@ export class TypeScriptImportLocator {
|
|||
|
||||
return imports;
|
||||
}
|
||||
|
||||
async read(path: string): Promise<Set<string>> {
|
||||
const content = await Fsp.readFile(path, 'utf8');
|
||||
return this.get(path, content);
|
||||
}
|
||||
}
|
|
@ -9,5 +9,5 @@
|
|||
module.exports = {
|
||||
preset: '@kbn/test/jest_node',
|
||||
rootDir: '../..',
|
||||
roots: ['<rootDir>/packages/kbn-bazel-packages'],
|
||||
roots: ['<rootDir>/packages/kbn-import-locator'],
|
||||
};
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"type": "shared-common",
|
||||
"id": "@kbn/ts-project-linter",
|
||||
"id": "@kbn/import-locator",
|
||||
"owner": "@elastic/kibana-operations",
|
||||
"devOnly": true
|
||||
}
|
7
packages/kbn-import-locator/package.json
Normal file
7
packages/kbn-import-locator/package.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"name": "@kbn/import-locator",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "SSPL-1.0 OR Elastic License 2.0",
|
||||
"main": "./import_locator"
|
||||
}
|
|
@ -3,13 +3,15 @@
|
|||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"types": [
|
||||
"jest",
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"index.d.ts"
|
||||
"**/*.ts",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
]
|
||||
"target/**/*"
|
||||
],
|
||||
"kbn_references": []
|
||||
}
|
|
@ -10,9 +10,9 @@ import Path from 'path';
|
|||
import Fs from 'fs';
|
||||
|
||||
import Resolve from 'resolve';
|
||||
import { readPackageManifest, type KibanaPackageManifest } from '@kbn/bazel-packages';
|
||||
import { readPackageManifest, type KibanaPackageManifest } from '@kbn/repo-packages';
|
||||
import { REPO_ROOT } from '@kbn/repo-info';
|
||||
import { readPackageMap, PackageMap } from '@kbn/package-map';
|
||||
import { readPackageMap, PackageMap } from '@kbn/repo-packages';
|
||||
|
||||
import { safeStat, readFileSync } from './helpers/fs';
|
||||
import { ResolveResult } from './resolve_result';
|
||||
|
|
|
@ -11,10 +11,9 @@
|
|||
"**/*.ts"
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/bazel-packages",
|
||||
"@kbn/package-map",
|
||||
"@kbn/repo-info",
|
||||
"@kbn/jest-serializers",
|
||||
"@kbn/repo-packages",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
3
packages/kbn-json-ast/README.md
Normal file
3
packages/kbn-json-ast/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# @kbn/json-ast
|
||||
|
||||
Tools for parsing and mutating JSON files without rewriting the whole file. JSON-C is also supported so that we can update kibana.jsonc and tsconfig.json files.
|
|
@ -6,6 +6,13 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { removeCompilerOption, setCompilerOption } from './compiler_options';
|
||||
export { setExclude } from './exclude';
|
||||
export { addReferences, removeReferences, replaceReferences } from './references';
|
||||
export { removeCompilerOption, setCompilerOption } from './src/compiler_options';
|
||||
export {
|
||||
addReferences,
|
||||
removeReferences,
|
||||
replaceReferences,
|
||||
removeAllReferences,
|
||||
} from './src/references';
|
||||
export { setExtends } from './src/extends';
|
||||
export { setProp } from './src/props';
|
||||
export { snip } from './src/snip';
|
|
@ -9,5 +9,5 @@
|
|||
module.exports = {
|
||||
preset: '@kbn/test/jest_node',
|
||||
rootDir: '../..',
|
||||
roots: ['<rootDir>/packages/kbn-ts-project-linter'],
|
||||
roots: ['<rootDir>/packages/kbn-json-ast'],
|
||||
};
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"type": "shared-common",
|
||||
"id": "@kbn/ts-project-linter-cli",
|
||||
"id": "@kbn/json-ast",
|
||||
"owner": "@elastic/kibana-operations",
|
||||
"devOnly": true
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "@kbn/bazel-packages",
|
||||
"name": "@kbn/json-ast",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "SSPL-1.0 OR Elastic License 2.0"
|
|
@ -273,7 +273,6 @@ describe('setCompilerOptions()', () => {
|
|||
`,
|
||||
'skipLibCheck'
|
||||
),
|
||||
|
||||
'outDir',
|
||||
'foo/bar'
|
||||
)
|
||||
|
@ -289,4 +288,89 @@ describe('setCompilerOptions()', () => {
|
|||
}"
|
||||
`);
|
||||
});
|
||||
|
||||
it('handles non-existent compiler options', () => {
|
||||
expect(
|
||||
setCompilerOption(
|
||||
dedent`
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"include": [
|
||||
"expect.d.ts"
|
||||
]
|
||||
}
|
||||
`,
|
||||
'outDir',
|
||||
'foo/bar'
|
||||
)
|
||||
).toMatchInlineSnapshot(`
|
||||
"{
|
||||
\\"extends\\": \\"../../tsconfig.base.json\\",
|
||||
\\"compilerOptions\\": {
|
||||
\\"outDir\\": \\"foo/bar\\"
|
||||
},
|
||||
\\"include\\": [
|
||||
\\"expect.d.ts\\"
|
||||
]
|
||||
}"
|
||||
`);
|
||||
|
||||
expect(
|
||||
setCompilerOption(
|
||||
dedent`
|
||||
{
|
||||
"include": [
|
||||
"expect.d.ts"
|
||||
],
|
||||
"extends": "../../tsconfig.base.json"
|
||||
}
|
||||
`,
|
||||
'outDir',
|
||||
'foo/bar'
|
||||
)
|
||||
).toMatchInlineSnapshot(`
|
||||
"{
|
||||
\\"include\\": [
|
||||
\\"expect.d.ts\\"
|
||||
],
|
||||
\\"extends\\": \\"../../tsconfig.base.json\\",
|
||||
\\"compilerOptions\\": {
|
||||
\\"outDir\\": \\"foo/bar\\"
|
||||
}
|
||||
}"
|
||||
`);
|
||||
expect(
|
||||
setCompilerOption(
|
||||
dedent`
|
||||
{}
|
||||
`,
|
||||
'outDir',
|
||||
'foo/bar'
|
||||
)
|
||||
).toMatchInlineSnapshot(`
|
||||
"{
|
||||
\\"compilerOptions\\": {
|
||||
\\"outDir\\": \\"foo/bar\\"
|
||||
}
|
||||
}"
|
||||
`);
|
||||
expect(
|
||||
setCompilerOption(
|
||||
dedent`
|
||||
{
|
||||
"foo": "bar"
|
||||
}
|
||||
`,
|
||||
'outDir',
|
||||
'foo/bar'
|
||||
)
|
||||
).toMatchInlineSnapshot(`
|
||||
"{
|
||||
\\"compilerOptions\\": {
|
||||
\\"outDir\\": \\"foo/bar\\"
|
||||
},
|
||||
\\"foo\\": \\"bar\\"
|
||||
}"
|
||||
`);
|
||||
});
|
||||
});
|
|
@ -6,15 +6,17 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { Jsonc } from '@kbn/bazel-packages';
|
||||
import { Jsonc } from '@kbn/repo-packages';
|
||||
|
||||
import { T } from './babel';
|
||||
import { getAst } from './ast';
|
||||
import { getEnds, getExpandedEnds } from './ends';
|
||||
import { getProp, getEndOfLastProp } from './props';
|
||||
import { snip } from './snip';
|
||||
import { redentJson, stringify } from './json';
|
||||
|
||||
export function getCompilerOptions(source: string) {
|
||||
const compilerOptions = getProp(getAst(source), 'compilerOptions');
|
||||
export function getCompilerOptions(ast: T.ObjectExpression) {
|
||||
const compilerOptions = getProp(ast, 'compilerOptions');
|
||||
if (!compilerOptions) {
|
||||
throw new Error('unable to find compilerOptions property');
|
||||
}
|
||||
|
@ -26,7 +28,29 @@ export function getCompilerOptions(source: string) {
|
|||
}
|
||||
|
||||
export function setCompilerOption(source: string, name: string, value: any) {
|
||||
const compilerOptions = getCompilerOptions(source);
|
||||
const ast = getAst(source);
|
||||
if (!getProp(ast, 'compilerOptions')) {
|
||||
const firstProp = ast.properties.at(0);
|
||||
if (!firstProp) {
|
||||
return stringify({ compilerOptions: { [name]: value } });
|
||||
}
|
||||
|
||||
const extendsProp = getProp(ast, 'extends');
|
||||
|
||||
if (extendsProp) {
|
||||
const after = getEnds(extendsProp)[1];
|
||||
return snip(source, [
|
||||
[after, after, `,\n "compilerOptions": ${redentJson({ [name]: value })}`],
|
||||
]);
|
||||
}
|
||||
|
||||
const before = getEnds(firstProp)[0];
|
||||
return snip(source, [
|
||||
[before, before, `"compilerOptions": ${redentJson({ [name]: value })},\n `],
|
||||
]);
|
||||
}
|
||||
|
||||
const compilerOptions = getCompilerOptions(ast);
|
||||
|
||||
const existing = getProp(compilerOptions, name);
|
||||
if (existing) {
|
||||
|
@ -41,21 +65,7 @@ export function setCompilerOption(source: string, name: string, value: any) {
|
|||
// convert to multiline
|
||||
const orig = (Jsonc.parse(source) as any).compilerOptions;
|
||||
const [start, end] = getEnds(compilerOptions);
|
||||
return (
|
||||
source.slice(0, start) +
|
||||
JSON.stringify(
|
||||
{
|
||||
...orig,
|
||||
[name]: value,
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
.split('\n')
|
||||
.map((l, i) => (i === 0 ? l : ` ${l}`))
|
||||
.join('\n') +
|
||||
source.slice(end)
|
||||
);
|
||||
return source.slice(0, start) + redentJson({ ...orig, [name]: value }) + source.slice(end);
|
||||
}
|
||||
|
||||
const endOfLastProp = getEndOfLastProp(compilerOptions);
|
||||
|
@ -64,11 +74,12 @@ export function setCompilerOption(source: string, name: string, value: any) {
|
|||
left = left.slice(0, -1);
|
||||
}
|
||||
const right = source.slice(endOfLastProp);
|
||||
return left + `,\n ${JSON.stringify(name)}: ${JSON.stringify(value)}` + right;
|
||||
return left + `,\n ${JSON.stringify(name)}: ${redentJson(value, ' ')}` + right;
|
||||
}
|
||||
|
||||
export function removeCompilerOption(source: string, name: string) {
|
||||
const compilerOptions = getCompilerOptions(source);
|
||||
const ast = getAst(source);
|
||||
const compilerOptions = getCompilerOptions(ast);
|
||||
|
||||
const culprit = getProp(compilerOptions, name);
|
||||
if (!culprit) {
|
|
@ -8,66 +8,61 @@
|
|||
|
||||
import dedent from 'dedent';
|
||||
|
||||
import { setExclude } from './exclude';
|
||||
import { setExtends } from './extends';
|
||||
|
||||
describe('setExclude()', () => {
|
||||
it('overwrites previous formatting', () => {
|
||||
describe('setExtends()', () => {
|
||||
it('overrides the value of the extends key', () => {
|
||||
expect(
|
||||
setExclude(
|
||||
setExtends(
|
||||
dedent`
|
||||
{
|
||||
"exclude": [1, 2,
|
||||
"foo"
|
||||
]
|
||||
"foo": "bar",
|
||||
"extends": "foo",
|
||||
"x": 1
|
||||
}
|
||||
`,
|
||||
['1', 'bar']
|
||||
'new value'
|
||||
)
|
||||
).toMatchInlineSnapshot(`
|
||||
"{
|
||||
\\"exclude\\": [
|
||||
\\"1\\",
|
||||
\\"bar\\",
|
||||
]
|
||||
\\"foo\\": \\"bar\\",
|
||||
\\"extends\\": \\"new value\\",
|
||||
\\"x\\": 1
|
||||
}"
|
||||
`);
|
||||
});
|
||||
|
||||
it('adds the property at the end if it does not exist', () => {
|
||||
it('adds missing values at the top of the object', () => {
|
||||
expect(
|
||||
setExclude(
|
||||
setExtends(
|
||||
dedent`
|
||||
{
|
||||
"foo": 1
|
||||
"foo": "bar",
|
||||
"x": 1
|
||||
}
|
||||
`,
|
||||
['1', 'bar']
|
||||
'new value'
|
||||
)
|
||||
).toMatchInlineSnapshot(`
|
||||
"{
|
||||
\\"foo\\": 1,
|
||||
\\"exclude\\": [
|
||||
\\"1\\",
|
||||
\\"bar\\",
|
||||
]
|
||||
\\"extends\\": \\"new value\\",
|
||||
\\"foo\\": \\"bar\\",
|
||||
\\"x\\": 1
|
||||
}"
|
||||
`);
|
||||
});
|
||||
|
||||
it('supports setting on an empty object', () => {
|
||||
expect(
|
||||
setExclude(
|
||||
setExtends(
|
||||
dedent`
|
||||
{
|
||||
"foo": 1,
|
||||
}
|
||||
{}
|
||||
`,
|
||||
['1', 'bar']
|
||||
'new value'
|
||||
)
|
||||
).toMatchInlineSnapshot(`
|
||||
"{
|
||||
\\"foo\\": 1,
|
||||
\\"exclude\\": [
|
||||
\\"1\\",
|
||||
\\"bar\\",
|
||||
],
|
||||
\\"extends\\": \\"new value\\"
|
||||
}"
|
||||
`);
|
||||
});
|
15
packages/kbn-json-ast/src/extends.ts
Normal file
15
packages/kbn-json-ast/src/extends.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { setProp } from './props';
|
||||
|
||||
export function setExtends(jsonc: string, value: string) {
|
||||
return setProp(jsonc, 'extends', value, {
|
||||
insertAtTop: true,
|
||||
});
|
||||
}
|
51
packages/kbn-json-ast/src/json.test.ts
Normal file
51
packages/kbn-json-ast/src/json.test.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { redent, redentJson, stringify } from './json';
|
||||
|
||||
describe('redent()', () => {
|
||||
it('indents all but the first line of a string', () => {
|
||||
expect(redent('a\nb\nc', ' ')).toMatchInlineSnapshot(`
|
||||
"a
|
||||
b
|
||||
c"
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('redentJson()', () => {
|
||||
it('indents all but the first line of the JSON representation of a value', () => {
|
||||
expect(redentJson({ a: 1, b: 2, foo: [1, 2, 3] }, ' ')).toMatchInlineSnapshot(`
|
||||
"{
|
||||
\\"a\\": 1,
|
||||
\\"b\\": 2,
|
||||
\\"foo\\": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
]
|
||||
}"
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('stringify()', () => {
|
||||
it('stringifies value into pretty JSON', () => {
|
||||
expect(stringify({ a: 1, b: 2, foo: [1, 2, 3] })).toMatchInlineSnapshot(`
|
||||
"{
|
||||
\\"a\\": 1,
|
||||
\\"b\\": 2,
|
||||
\\"foo\\": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
]
|
||||
}"
|
||||
`);
|
||||
});
|
||||
});
|
|
@ -6,13 +6,17 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export type PackageMap = Map<string, string>;
|
||||
export function redent(text: string, spaces: string) {
|
||||
return text
|
||||
.split('\n')
|
||||
.map((l, i) => (i === 0 ? l : `${spaces}${l}`))
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the package map from disk
|
||||
*/
|
||||
export function readPackageMap(): PackageMap;
|
||||
/**
|
||||
* Read the package map and calculate a cache key/hash of the package map
|
||||
*/
|
||||
export function readHashOfPackageMap(): string;
|
||||
export function stringify(obj: any) {
|
||||
return JSON.stringify(obj, null, 2);
|
||||
}
|
||||
|
||||
export function redentJson(obj: any, spaces: string = ' ') {
|
||||
return redent(stringify(obj), spaces);
|
||||
}
|
65
packages/kbn-json-ast/src/props.ts
Normal file
65
packages/kbn-json-ast/src/props.ts
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { getAst } from './ast';
|
||||
import { stringify, redentJson } from './json';
|
||||
import { snip } from './snip';
|
||||
import { T } from './babel';
|
||||
import { getEnds } from './ends';
|
||||
|
||||
export function getProp(obj: T.ObjectExpression, name: string) {
|
||||
return obj.properties.find((p): p is T.ObjectProperty & { key: T.StringLiteral } => {
|
||||
return T.isObjectProperty(p) && T.isStringLiteral(p.key) && p.key.value === name;
|
||||
});
|
||||
}
|
||||
|
||||
export function getEndOfLastProp(obj: T.ObjectExpression) {
|
||||
if (obj.properties.length === 0) {
|
||||
throw new Error('object has no properties');
|
||||
}
|
||||
|
||||
return obj.properties.reduce((acc, prop) => Math.max(acc, getEnds(prop)[1]), 0);
|
||||
}
|
||||
|
||||
export function setProp(
|
||||
/** the jsonc to modify */
|
||||
source: string,
|
||||
/** The key to set */
|
||||
key: string,
|
||||
/** the value of the key */
|
||||
value: any,
|
||||
opts?: {
|
||||
/** by default, if the key isn't already in the json, it will be added at the bottom. Set this to true to add the key at the top instead */
|
||||
insertAtTop?: boolean;
|
||||
/** In order to set the property an object other than the root object, parse the source and pass the node of the desired object here (make sure to also pass spaces) */
|
||||
node?: T.ObjectExpression;
|
||||
/** This overrides the default " " spacing used for multi line or new properties that are added */
|
||||
spaces?: string;
|
||||
}
|
||||
) {
|
||||
const ast = opts?.node ?? getAst(source);
|
||||
const prop = getProp(ast, key);
|
||||
const spaces = opts?.spaces ?? ' ';
|
||||
const newPropJson = `${stringify(key)}: ${redentJson(value, spaces)}`;
|
||||
|
||||
if (!prop) {
|
||||
if (!ast.properties.length) {
|
||||
return `{\n${spaces}${newPropJson}\n}`;
|
||||
}
|
||||
|
||||
if (opts?.insertAtTop) {
|
||||
const [start] = getEnds(ast.properties[0]);
|
||||
return snip(source, [[start, start, `${newPropJson},\n${spaces}`]]);
|
||||
}
|
||||
|
||||
const endOfLastProp = getEndOfLastProp(ast);
|
||||
return snip(source, [[endOfLastProp, endOfLastProp, `,\n${spaces}${newPropJson}`]]);
|
||||
}
|
||||
|
||||
return snip(source, [[...getEnds(prop), newPropJson]]);
|
||||
}
|
|
@ -56,6 +56,15 @@ export function addReferences(source: string, refsToAdd: string[]) {
|
|||
return source.slice(0, start) + refsSrc + source.slice(end);
|
||||
}
|
||||
|
||||
export function removeAllReferences(source: string) {
|
||||
const ast = getAst(source);
|
||||
const existing = getProp(ast, PROP);
|
||||
if (!existing) {
|
||||
return source;
|
||||
}
|
||||
return snip(source, [getExpandedEnds(source, existing)]);
|
||||
}
|
||||
|
||||
export function removeReferences(source: string, refs: string[]) {
|
||||
const ast = getAst(source);
|
||||
|
|
@ -6,7 +6,11 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
type Snip = [number, number] | [number, number, string];
|
||||
type Snip = [start: number, end: number] | [start: number, end: number, replacement: string];
|
||||
|
||||
/**
|
||||
* Replace or remove specific points of the source code
|
||||
*/
|
||||
export function snip(source: string, snips: Snip[]) {
|
||||
const queue = snips
|
||||
.map((s): Snip => {
|
19
packages/kbn-json-ast/tsconfig.json
Normal file
19
packages/kbn-json-ast/tsconfig.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"types": [
|
||||
"jest",
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*"
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/repo-packages",
|
||||
]
|
||||
}
|
3
packages/kbn-lint-packages-cli/README.md
Normal file
3
packages/kbn-lint-packages-cli/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# @kbn/package-linter-cli
|
||||
|
||||
CLI for running the package linter, which just validates a couple rules for each package.
|
|
@ -9,5 +9,5 @@
|
|||
module.exports = {
|
||||
preset: '@kbn/test/jest_node',
|
||||
rootDir: '../..',
|
||||
roots: ['<rootDir>/packages/kbn-ts-project-linter-cli'],
|
||||
roots: ['<rootDir>/packages/kbn-lint-packages-cli'],
|
||||
};
|
6
packages/kbn-lint-packages-cli/kibana.jsonc
Normal file
6
packages/kbn-lint-packages-cli/kibana.jsonc
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"type": "shared-common",
|
||||
"id": "@kbn/lint-packages-cli",
|
||||
"owner": "@elastic/kibana-operations",
|
||||
"devOnly": true
|
||||
}
|
7
packages/kbn-lint-packages-cli/package.json
Normal file
7
packages/kbn-lint-packages-cli/package.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"name": "@kbn/lint-packages-cli",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "SSPL-1.0 OR Elastic License 2.0",
|
||||
"main": "./run_lint_packages_cli"
|
||||
}
|
13
packages/kbn-lint-packages-cli/rules/index.ts
Normal file
13
packages/kbn-lint-packages-cli/rules/index.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { PackageRule } from '@kbn/repo-linter';
|
||||
|
||||
import { matchingPackageNameRule } from './matching_package_name';
|
||||
|
||||
export const RULES: PackageRule[] = [matchingPackageNameRule];
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { PackageRule } from '@kbn/repo-linter';
|
||||
import { setProp } from '@kbn/json-ast';
|
||||
|
||||
export const matchingPackageNameRule = PackageRule.create('matchingPackageName', {
|
||||
async check({ pkg }) {
|
||||
if (pkg.pkg && pkg.pkg?.name !== pkg.manifest.id) {
|
||||
this.err(
|
||||
'The "name" in the package.json file must match the "id" from the kibana.jsonc file',
|
||||
{
|
||||
'package.json': (source) =>
|
||||
setProp(source, 'name', pkg.manifest.id, {
|
||||
insertAtTop: true,
|
||||
}),
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
95
packages/kbn-lint-packages-cli/run_lint_packages_cli.ts
Normal file
95
packages/kbn-lint-packages-cli/run_lint_packages_cli.ts
Normal file
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import Path from 'path';
|
||||
|
||||
import { run } from '@kbn/dev-cli-runner';
|
||||
import { createFailError } from '@kbn/dev-cli-errors';
|
||||
import { getRepoFiles } from '@kbn/get-repo-files';
|
||||
import { PackageFileMap } from '@kbn/repo-file-maps';
|
||||
import { getPackages } from '@kbn/repo-packages';
|
||||
import { REPO_ROOT } from '@kbn/repo-info';
|
||||
import { TS_PROJECTS } from '@kbn/ts-projects';
|
||||
import { runLintRules, PackageLintTarget } from '@kbn/repo-linter';
|
||||
|
||||
import { RULES } from './rules';
|
||||
|
||||
const kebabCase = (input: string) =>
|
||||
input
|
||||
.replace(/([a-z])([A-Z])/, '$1 $2')
|
||||
.split(/\W+/)
|
||||
.filter((f) => !!f)
|
||||
.join('-')
|
||||
.toLowerCase();
|
||||
|
||||
function getFilter(input: string) {
|
||||
const repoRel = Path.relative(REPO_ROOT, Path.resolve(input));
|
||||
return ({ pkg }: PackageLintTarget) =>
|
||||
pkg.name === input ||
|
||||
pkg.name === `@kbn/${input}` ||
|
||||
pkg.name === `@kbn/${kebabCase(input)}` ||
|
||||
pkg.normalizedRepoRelativeDir === input ||
|
||||
repoRel.startsWith(pkg.normalizedRepoRelativeDir + '/');
|
||||
}
|
||||
|
||||
run(
|
||||
async ({ log, flagsReader }) => {
|
||||
const filter = flagsReader.getPositionals();
|
||||
const packages = getPackages(REPO_ROOT);
|
||||
const allTargets = packages
|
||||
.map(
|
||||
(p) =>
|
||||
new PackageLintTarget(
|
||||
p,
|
||||
TS_PROJECTS.find((ts) => ts.repoRelDir === p.normalizedRepoRelativeDir)
|
||||
)
|
||||
)
|
||||
.sort((a, b) => b.repoRel.length - a.repoRel.length);
|
||||
|
||||
const toLint = Array.from(
|
||||
new Set(
|
||||
!filter.length
|
||||
? allTargets
|
||||
: filter.map((input) => {
|
||||
const pkg = allTargets.find(getFilter(input));
|
||||
|
||||
if (!pkg) {
|
||||
throw createFailError(
|
||||
`unable to find a package matching [${input}]. Supply either a package id/name or path to a package`
|
||||
);
|
||||
}
|
||||
|
||||
return pkg;
|
||||
})
|
||||
)
|
||||
).sort((a, b) => a.repoRel.localeCompare(b.repoRel));
|
||||
|
||||
const fileMap = new PackageFileMap(packages, await getRepoFiles());
|
||||
const { lintingErrorCount } = await runLintRules(log, toLint, RULES, {
|
||||
fix: flagsReader.boolean('fix'),
|
||||
getFiles: (target) => fileMap.getFiles(target.pkg),
|
||||
});
|
||||
|
||||
if (!lintingErrorCount) {
|
||||
log.success('All packages linted successfully');
|
||||
} else {
|
||||
throw createFailError('see above errors');
|
||||
}
|
||||
},
|
||||
{
|
||||
usage: `node scripts/lint_packages [...packages]`,
|
||||
flags: {
|
||||
boolean: ['fix'],
|
||||
alias: { f: 'fix' },
|
||||
help: `
|
||||
--fix Automatically fix some issues in tsconfig.json files
|
||||
`,
|
||||
},
|
||||
description: 'Validate packages using a set of rules that evolve over time.',
|
||||
}
|
||||
);
|
|
@ -14,11 +14,14 @@
|
|||
"target/**/*"
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/repo-linter",
|
||||
"@kbn/dev-cli-runner",
|
||||
"@kbn/dev-cli-errors",
|
||||
"@kbn/repo-path",
|
||||
"@kbn/get-repo-files",
|
||||
"@kbn/repo-packages",
|
||||
"@kbn/repo-info",
|
||||
"@kbn/ts-projects",
|
||||
"@kbn/ts-project-linter",
|
||||
"@kbn/repo-file-maps",
|
||||
"@kbn/json-ast",
|
||||
]
|
||||
}
|
3
packages/kbn-lint-ts-projects-cli/README.md
Normal file
3
packages/kbn-lint-ts-projects-cli/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# @kbn/lint-ts-project-cli
|
||||
|
||||
CLI for linting typescript projects in the repo
|
13
packages/kbn-lint-ts-projects-cli/jest.config.js
Normal file
13
packages/kbn-lint-ts-projects-cli/jest.config.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test/jest_node',
|
||||
rootDir: '../..',
|
||||
roots: ['<rootDir>/packages/kbn-lint-ts-projects-cli'],
|
||||
};
|
6
packages/kbn-lint-ts-projects-cli/kibana.jsonc
Normal file
6
packages/kbn-lint-ts-projects-cli/kibana.jsonc
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"type": "shared-common",
|
||||
"id": "@kbn/lint-ts-projects-cli",
|
||||
"owner": "@elastic/kibana-operations",
|
||||
"devOnly": true
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "@kbn/ts-project-linter-cli",
|
||||
"name": "@kbn/lint-ts-projects-cli",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "SSPL-1.0 OR Elastic License 2.0",
|
|
@ -6,8 +6,9 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { Rule } from '../lib/rule';
|
||||
import { removeCompilerOption } from '../ast';
|
||||
import { removeCompilerOption } from '@kbn/json-ast';
|
||||
|
||||
import { TsProjectRule } from '@kbn/repo-linter';
|
||||
|
||||
const NAMES = [
|
||||
'declaration',
|
||||
|
@ -16,23 +17,28 @@ const NAMES = [
|
|||
'skipLibCheck',
|
||||
'target',
|
||||
'paths',
|
||||
'incremental',
|
||||
'composite',
|
||||
'rootDir',
|
||||
];
|
||||
|
||||
export const forbiddenCompilerOptions = Rule.create('forbiddenCompilerOptions', {
|
||||
check({ config, repoRel }) {
|
||||
export const forbiddenCompilerOptions = TsProjectRule.create('forbiddenCompilerOptions', {
|
||||
check({ repoRel, tsProject }) {
|
||||
for (const optName of NAMES) {
|
||||
if (repoRel === '.buildkite/tsconfig.json' && optName === 'paths') {
|
||||
// allow "paths" in this specific config file
|
||||
continue;
|
||||
}
|
||||
|
||||
const value = config.compilerOptions?.[optName];
|
||||
const value = tsProject.config.compilerOptions?.[optName];
|
||||
if (value === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.err(`specifying the "${optName}" compiler option is forbidden`, (source) => {
|
||||
this.err(`specifying the "${optName}" compiler option is forbidden`, {
|
||||
['tsconfig.json']: (source) => {
|
||||
return removeCompilerOption(source, optName);
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
|
@ -6,20 +6,24 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { TsProjectRule } from '@kbn/repo-linter';
|
||||
|
||||
import { forbiddenCompilerOptions } from './forbidden_compiler_options';
|
||||
import { refPkgsIds } from './reference_pkg_ids';
|
||||
import { requiredCompilerOptions } from './required_compiler_options';
|
||||
import { validBaseConfig } from './valid_base_config';
|
||||
import { validBaseTsconfig } from './valid_base_tsconfig';
|
||||
import { requiredExcludes } from './required_excludes';
|
||||
import { requiredFileSelectors } from './required_file_selectors';
|
||||
import { referenceUsedPkgs } from './reference_used_pkgs';
|
||||
import { tsconfigIndentation } from './tsconfig_indentation';
|
||||
|
||||
export const PROJECT_LINTER_RULES = [
|
||||
export const RULES: TsProjectRule[] = [
|
||||
forbiddenCompilerOptions,
|
||||
refPkgsIds,
|
||||
requiredCompilerOptions,
|
||||
validBaseConfig,
|
||||
validBaseTsconfig,
|
||||
requiredExcludes,
|
||||
requiredFileSelectors,
|
||||
referenceUsedPkgs,
|
||||
tsconfigIndentation,
|
||||
];
|
|
@ -9,13 +9,13 @@
|
|||
import Path from 'path';
|
||||
|
||||
import { REPO_ROOT } from '@kbn/repo-info';
|
||||
import { readPackageMap } from '@kbn/package-map';
|
||||
import { readPackageMap } from '@kbn/repo-packages';
|
||||
import { replaceReferences } from '@kbn/json-ast';
|
||||
|
||||
import { Rule } from '../lib/rule';
|
||||
import { replaceReferences } from '../ast';
|
||||
import { TsProjectRule } from '@kbn/repo-linter';
|
||||
|
||||
export const refPkgsIds = Rule.create('refPkgIds', {
|
||||
check(proj) {
|
||||
export const refPkgsIds = TsProjectRule.create('refPkgIds', {
|
||||
check({ tsProject }) {
|
||||
const dirsToPkgIds = this.getCache(() => {
|
||||
const pkgMap = readPackageMap();
|
||||
return new Map(Array.from(pkgMap).map(([k, v]) => [v, k]));
|
||||
|
@ -26,12 +26,12 @@ export const refPkgsIds = Rule.create('refPkgIds', {
|
|||
|
||||
const replaceWithPkgId: Array<[string, string]> = [];
|
||||
|
||||
for (const ref of proj.config.kbn_references ?? []) {
|
||||
for (const ref of tsProject.config.kbn_references ?? []) {
|
||||
if (typeof ref === 'string' || ref.force === true) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const refPath = Path.resolve(proj.directory, ref.path);
|
||||
const refPath = this.resolve(ref.path);
|
||||
const pkgIdJson = getPkgId(refPath);
|
||||
if (pkgIdJson) {
|
||||
replaceWithPkgId.push([ref.path, pkgIdJson]);
|
||||
|
@ -48,9 +48,11 @@ export const refPkgsIds = Rule.create('refPkgIds', {
|
|||
|
||||
return {
|
||||
msg: `kbn_references must use pkgIds to refer to other packages (use --fix to autofix, or add "force": true to ignore):\n${list}`,
|
||||
fix(source) {
|
||||
fixes: {
|
||||
'tsconfig.json': (source) => {
|
||||
return replaceReferences(source, replaceWithPkgId);
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
133
packages/kbn-lint-ts-projects-cli/rules/reference_used_pkgs.ts
Normal file
133
packages/kbn-lint-ts-projects-cli/rules/reference_used_pkgs.ts
Normal file
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { asyncForEachWithLimit } from '@kbn/std';
|
||||
import { addReferences, removeReferences, removeAllReferences } from '@kbn/json-ast';
|
||||
|
||||
import { TS_PROJECTS, type RefableTsProject } from '@kbn/ts-projects';
|
||||
import { parseKbnImportReq } from '@kbn/repo-packages';
|
||||
import { TsProjectRule } from '@kbn/repo-linter';
|
||||
import { ImportLocator } from '@kbn/import-locator';
|
||||
|
||||
function createCache() {
|
||||
const importable = new Map<string, Set<RefableTsProject>>();
|
||||
|
||||
for (const proj of TS_PROJECTS) {
|
||||
if (!proj.isRefable()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const req = parseKbnImportReq(proj.rootImportReq);
|
||||
if (!req) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const pkgId = req.pkgId;
|
||||
const existing = importable.get(pkgId);
|
||||
if (existing) {
|
||||
existing.add(proj);
|
||||
} else {
|
||||
importable.set(pkgId, new Set([proj]));
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
importLocator: new ImportLocator(),
|
||||
tsProjectsByRootImportReq: new Map(
|
||||
TS_PROJECTS.flatMap((p) => (p.isRefable() ? [[p.rootImportReq, p]] : []))
|
||||
),
|
||||
importableTsProjects: new Map(
|
||||
Array.from(importable, ([k, v]) => {
|
||||
const projects = Array.from(v).sort((a, b) => b.directory.localeCompare(a.directory));
|
||||
return [k, projects.length === 1 ? projects[0] : projects];
|
||||
})
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
export const referenceUsedPkgs = TsProjectRule.create('referenceUsedPkgs', {
|
||||
async check({ tsProject }) {
|
||||
if (tsProject.isTypeCheckDisabled()) {
|
||||
if (!tsProject.config.kbn_references) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
msg: 'type checking is disabled, so kbn_references is unnecessary and not kept up-to-date.',
|
||||
fixes: {
|
||||
'tsconfig.json': (source) => removeAllReferences(source),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const { importLocator, importableTsProjects, tsProjectsByRootImportReq } =
|
||||
this.getCache(createCache);
|
||||
|
||||
const usedTsProjects = new Set<RefableTsProject>();
|
||||
await asyncForEachWithLimit(this.getAllFiles(), 30, async (path) => {
|
||||
const reqs = Array.from(await importLocator.read(path.abs)).flatMap(
|
||||
(req) => parseKbnImportReq(req) ?? []
|
||||
);
|
||||
|
||||
for (const req of reqs) {
|
||||
const options = importableTsProjects.get(req.pkgId);
|
||||
if (!options) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Array.isArray(options)) {
|
||||
usedTsProjects.add(options);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const opt of options) {
|
||||
if (req.full.startsWith(opt.rootImportReq)) {
|
||||
usedTsProjects.add(opt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const refs = new Set(
|
||||
(tsProject.config.kbn_references ?? []).flatMap((r) =>
|
||||
typeof r === 'string' ? tsProjectsByRootImportReq.get(r) || r : []
|
||||
)
|
||||
);
|
||||
const missing = new Set<RefableTsProject>();
|
||||
const extra = new Set<RefableTsProject | string>(refs);
|
||||
for (const used of usedTsProjects) {
|
||||
extra.delete(used);
|
||||
if (!refs.has(used)) {
|
||||
missing.add(used);
|
||||
}
|
||||
}
|
||||
|
||||
if (missing.size) {
|
||||
const ids = Array.from(missing, (p) => p.rootImportReq);
|
||||
const list = ids.map((id) => ` - ${id}`).join('\n');
|
||||
this.err(
|
||||
`the following packages are referenced in the code of this package but not listed in kbn_references:\n${list}`,
|
||||
{
|
||||
'tsconfig.json': (source) => addReferences(source, ids),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (extra.size) {
|
||||
const ids = Array.from(extra, (p) => (typeof p === 'string' ? p : p.rootImportReq));
|
||||
const list = ids.map((id) => ` - ${id}`).join('\n');
|
||||
this.err(
|
||||
`the following packages are listed in kbn_references but there are no detectable references in the code:\n${list}`,
|
||||
{
|
||||
'tsconfig.json': (source) => removeReferences(source, ids),
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
|
@ -6,18 +6,19 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { Rule } from '../lib/rule';
|
||||
import { setCompilerOption } from '../ast';
|
||||
import { setCompilerOption } from '@kbn/json-ast';
|
||||
|
||||
import { TsProjectRule } from '@kbn/repo-linter';
|
||||
|
||||
const REQUIRED: Array<[string, string]> = [['outDir', 'target/types']];
|
||||
|
||||
export const requiredCompilerOptions = Rule.create('requiredCompilerOptions', {
|
||||
check({ config }) {
|
||||
export const requiredCompilerOptions = TsProjectRule.create('requiredCompilerOptions', {
|
||||
check({ tsProject }) {
|
||||
for (const [key, value] of REQUIRED) {
|
||||
if (config.compilerOptions?.[key] !== value) {
|
||||
this.err(`the value of the compiler option "${key}" must be set to "${value}"`, (source) =>
|
||||
setCompilerOption(source, key, value)
|
||||
);
|
||||
if (tsProject.config.compilerOptions?.[key] !== value) {
|
||||
this.err(`the value of the compiler option "${key}" must be set to "${value}"`, {
|
||||
'tsconfig.json': (source) => setCompilerOption(source, key, value),
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
46
packages/kbn-lint-ts-projects-cli/rules/required_excludes.ts
Normal file
46
packages/kbn-lint-ts-projects-cli/rules/required_excludes.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { setProp } from '@kbn/json-ast';
|
||||
|
||||
import { TsProjectRule } from '@kbn/repo-linter';
|
||||
|
||||
const REQUIRED_EXCLUDES = ['target/**/*'];
|
||||
|
||||
export const requiredExcludes = TsProjectRule.create('requiredExcludes', {
|
||||
check({ tsProject }) {
|
||||
const existing = tsProject.config.exclude;
|
||||
if (!existing) {
|
||||
return {
|
||||
msg: `excludes must be defined and include "${REQUIRED_EXCLUDES.join('", "')}"`,
|
||||
fixes: {
|
||||
'tsconfig.json': (source) => setProp(source, 'exclude', REQUIRED_EXCLUDES),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const missing = REQUIRED_EXCLUDES.filter((re) => !existing.includes(re));
|
||||
if (missing.length) {
|
||||
return {
|
||||
msg: `excludes must include "${REQUIRED_EXCLUDES.join('", "')}"`,
|
||||
fixes: {
|
||||
'tsconfig.json': (source) =>
|
||||
setProp(source, 'exclude', [
|
||||
...(missing.includes('target/**/*')
|
||||
? existing.filter((e) => {
|
||||
const normalized = e.startsWith('./') ? e.slice(2) : e;
|
||||
return normalized === 'target' || normalized.startsWith('target/');
|
||||
})
|
||||
: existing),
|
||||
...missing,
|
||||
]),
|
||||
},
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
|
@ -6,11 +6,11 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { Rule } from '../lib/rule';
|
||||
import { TsProjectRule } from '@kbn/repo-linter';
|
||||
|
||||
export const requiredFileSelectors = Rule.create('requiredFileSelectors', {
|
||||
check(project) {
|
||||
if (!project.config.include || project.config.files) {
|
||||
export const requiredFileSelectors = TsProjectRule.create('requiredFileSelectors', {
|
||||
check({ tsProject }) {
|
||||
if (tsProject.config.files || !tsProject.config.include) {
|
||||
return {
|
||||
msg: 'every ts project must use the "include" key (and not the "files" key) to select the files for that project',
|
||||
};
|
107
packages/kbn-lint-ts-projects-cli/rules/tsconfig_indentation.ts
Normal file
107
packages/kbn-lint-ts-projects-cli/rules/tsconfig_indentation.ts
Normal file
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { TsProjectRule } from '@kbn/repo-linter';
|
||||
import { snip } from '@kbn/json-ast';
|
||||
|
||||
const INDENT = ' ';
|
||||
|
||||
class Scanner {
|
||||
public pos = 0;
|
||||
constructor(public readonly text: string) {}
|
||||
|
||||
scanToClosing(final: string) {
|
||||
while (this.cont()) {
|
||||
const char = this.text[this.pos++];
|
||||
|
||||
if (char === '\\') {
|
||||
this.pos++; // ignore next char
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === final) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`expected to find closing "${final}"`);
|
||||
}
|
||||
|
||||
collectAll(match: string) {
|
||||
let matched = '';
|
||||
while (this.cont()) {
|
||||
if (this.text[this.pos] === match) {
|
||||
matched += match;
|
||||
this.pos++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
cont() {
|
||||
return this.pos < this.text.length;
|
||||
}
|
||||
|
||||
peek() {
|
||||
return this.text[this.pos];
|
||||
}
|
||||
}
|
||||
|
||||
function getIndentationSnips(text: string) {
|
||||
const scanner = new Scanner(text);
|
||||
let depth = 0;
|
||||
const snips: Array<[from: number, to: number, expected: string]> = [];
|
||||
while (scanner.cont()) {
|
||||
const char = scanner.text[scanner.pos++];
|
||||
|
||||
if (char === '{' || char === '[') {
|
||||
depth += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === '}' || char === ']') {
|
||||
depth -= 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === '"') {
|
||||
scanner.scanToClosing('"');
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === '\n') {
|
||||
const indent = scanner.collectAll(' ');
|
||||
const next = scanner.peek();
|
||||
const expected = INDENT.repeat(
|
||||
next === '\n' ? 0 : next === '}' || next === ']' ? depth - 1 : depth
|
||||
);
|
||||
if (indent !== expected) {
|
||||
snips.push([scanner.pos - indent.length, scanner.pos, expected]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return snips;
|
||||
}
|
||||
|
||||
export const tsconfigIndentation = TsProjectRule.create('tsconfigIndentation', {
|
||||
check() {
|
||||
const content = this.get('tsconfig.json');
|
||||
const fixes = getIndentationSnips(content);
|
||||
|
||||
if (fixes.length || !content.endsWith('\n')) {
|
||||
this.err('file should use two space indentation', {
|
||||
'tsconfig.json': (source) => {
|
||||
const fixed = snip(source, getIndentationSnips(source));
|
||||
return fixed.endsWith('\n') ? fixed : fixed + '\n';
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
|
@ -9,11 +9,12 @@
|
|||
import Path from 'path';
|
||||
|
||||
import { REPO_ROOT } from '@kbn/repo-info';
|
||||
import { Project } from '@kbn/ts-projects';
|
||||
import { TsProject } from '@kbn/ts-projects';
|
||||
import { setExtends } from '@kbn/json-ast';
|
||||
|
||||
import { Rule } from '../lib/rule';
|
||||
import { TsProjectRule } from '@kbn/repo-linter';
|
||||
|
||||
function getBaseConfigRels(proj: Project): string[] {
|
||||
function getBaseConfigRels(proj: TsProject): string[] {
|
||||
const base = proj.getBase();
|
||||
if (!base) {
|
||||
return [];
|
||||
|
@ -21,15 +22,27 @@ function getBaseConfigRels(proj: Project): string[] {
|
|||
return [base.repoRel, ...getBaseConfigRels(base)];
|
||||
}
|
||||
|
||||
export const validBaseConfig = Rule.create('validBaseConfig', {
|
||||
check(proj) {
|
||||
const baseConfigRels = getBaseConfigRels(proj);
|
||||
export const validBaseTsconfig = TsProjectRule.create('validBaseTsconfig', {
|
||||
check({ tsProject }) {
|
||||
const configRel = Path.relative(REPO_ROOT, tsProject.path);
|
||||
if (configRel !== 'tsconfig.base.json' && !tsProject.config.extends) {
|
||||
return {
|
||||
msg: `This tsconfig requires an "extends" setting`,
|
||||
fixes: {
|
||||
'tsconfig.json': (source) =>
|
||||
setExtends(
|
||||
source,
|
||||
Path.relative(tsProject.directory, Path.resolve(REPO_ROOT, 'tsconfig.base.json'))
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const baseConfigRels = getBaseConfigRels(tsProject);
|
||||
if (baseConfigRels[0] === 'tsconfig.json') {
|
||||
return `This tsconfig extends the root tsconfig.json file and shouldn't. The root tsconfig.json file is not a valid base config, you probably want to point to the tsconfig.base.json file.`;
|
||||
}
|
||||
|
||||
const configRel = Path.relative(REPO_ROOT, proj.path);
|
||||
if (configRel !== 'tsconfig.base.json' && !baseConfigRels.includes('tsconfig.base.json')) {
|
||||
return `This tsconfig does not extend the tsconfig.base.json file either directly or indirectly. The TS config setup for the repo expects every tsconfig file to extend this base config file.`;
|
||||
}
|
176
packages/kbn-lint-ts-projects-cli/run_lint_ts_projects_cli.ts
Normal file
176
packages/kbn-lint-ts-projects-cli/run_lint_ts_projects_cli.ts
Normal file
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import Path from 'path';
|
||||
|
||||
import { run } from '@kbn/dev-cli-runner';
|
||||
import { createFailError } from '@kbn/dev-cli-errors';
|
||||
import { RepoPath } from '@kbn/repo-path';
|
||||
import { getRepoFiles } from '@kbn/get-repo-files';
|
||||
import { SomeDevLog } from '@kbn/some-dev-log';
|
||||
import { PackageFileMap, TsProjectFileMap } from '@kbn/repo-file-maps';
|
||||
import { getPackages } from '@kbn/repo-packages';
|
||||
import { REPO_ROOT } from '@kbn/repo-info';
|
||||
import { TS_PROJECTS, TsProject } from '@kbn/ts-projects';
|
||||
import { runLintRules, TsProjectLintTarget } from '@kbn/repo-linter';
|
||||
|
||||
import { RULES } from './rules';
|
||||
|
||||
function getFilter(input: string) {
|
||||
const abs = Path.resolve(input);
|
||||
|
||||
return ({ tsProject }: TsProjectLintTarget) =>
|
||||
tsProject.name === input ||
|
||||
tsProject.repoRel === input ||
|
||||
tsProject.repoRelDir === input ||
|
||||
tsProject.path === abs ||
|
||||
tsProject.directory === abs ||
|
||||
abs.startsWith(tsProject.directory + '/') ||
|
||||
tsProject.pkgInfo?.repoRel === input ||
|
||||
(tsProject.pkgInfo && Path.resolve(REPO_ROOT, tsProject.pkgInfo.repoRel) === abs) ||
|
||||
(tsProject.pkgInfo && abs.startsWith(Path.resolve(REPO_ROOT, tsProject.pkgInfo.repoRel) + '/'));
|
||||
}
|
||||
|
||||
function validateProjectOwnership(
|
||||
allTargets: TsProjectLintTarget[],
|
||||
allFiles: Iterable<RepoPath>,
|
||||
fileMap: TsProjectFileMap,
|
||||
log: SomeDevLog
|
||||
) {
|
||||
let failed = false;
|
||||
|
||||
const isInMultipleTsProjects = new Map<string, Set<TsProject>>();
|
||||
const pathsToTsProject = new Map<string, TsProject>();
|
||||
for (const proj of allTargets) {
|
||||
for (const path of fileMap.getFiles(proj.tsProject)) {
|
||||
const existing = pathsToTsProject.get(path.repoRel);
|
||||
if (!existing) {
|
||||
pathsToTsProject.set(path.repoRel, proj.tsProject);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (path.isTypeScriptAmbient()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const multi = isInMultipleTsProjects.get(path.repoRel);
|
||||
if (multi) {
|
||||
multi.add(proj.tsProject);
|
||||
} else {
|
||||
isInMultipleTsProjects.set(path.repoRel, new Set([existing, proj.tsProject]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isInMultipleTsProjects.size) {
|
||||
failed = true;
|
||||
const details = Array.from(isInMultipleTsProjects)
|
||||
.map(
|
||||
([repoRel, list]) =>
|
||||
` - ${repoRel}:\n${Array.from(list)
|
||||
.map((p) => ` - ${p.repoRel}`)
|
||||
.join('\n')}`
|
||||
)
|
||||
.join('\n');
|
||||
|
||||
log.error(
|
||||
`The following files belong to multiple tsconfig.json files listed in packages/kbn-ts-projects/projects.ts\n${details}`
|
||||
);
|
||||
}
|
||||
|
||||
const isNotInTsProject: RepoPath[] = [];
|
||||
for (const path of allFiles) {
|
||||
if (!path.isTypeScript()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const proj = pathsToTsProject.get(path.repoRel);
|
||||
if (proj === undefined && !path.repoRel.includes('__fixtures__')) {
|
||||
isNotInTsProject.push(path);
|
||||
}
|
||||
}
|
||||
|
||||
if (isNotInTsProject.length) {
|
||||
failed = true;
|
||||
log.error(
|
||||
`The following files do not belong to a tsconfig.json file, or that tsconfig.json file is not listed in packages/kbn-ts-projects/projects.ts\n${isNotInTsProject
|
||||
.map((file) => ` - ${file.repoRel}`)
|
||||
.join('\n')}`
|
||||
);
|
||||
}
|
||||
|
||||
return failed;
|
||||
}
|
||||
|
||||
run(
|
||||
async ({ log, flagsReader }) => {
|
||||
const filter = flagsReader.getPositionals();
|
||||
const packages = getPackages(REPO_ROOT);
|
||||
const allTargets = Array.from(TS_PROJECTS, (p) => new TsProjectLintTarget(p)).sort(
|
||||
(a, b) => b.repoRel.length - a.repoRel.length
|
||||
);
|
||||
|
||||
const toLint = Array.from(
|
||||
new Set(
|
||||
!filter.length
|
||||
? allTargets
|
||||
: filter.map((input) => {
|
||||
const pkg = allTargets.find(getFilter(input));
|
||||
|
||||
if (!pkg) {
|
||||
throw createFailError(
|
||||
`unable to find a package matching [${input}]. Supply either a package id/name or path to a package`
|
||||
);
|
||||
}
|
||||
|
||||
return pkg;
|
||||
})
|
||||
)
|
||||
).sort((a, b) => a.repoRel.localeCompare(b.repoRel));
|
||||
|
||||
const skipRefs =
|
||||
flagsReader.boolean('refs-check') === false || flagsReader.boolean('no-refs-check') === true;
|
||||
|
||||
const allFiles = await getRepoFiles();
|
||||
const fileMap = new TsProjectFileMap(new PackageFileMap(packages, allFiles), TS_PROJECTS);
|
||||
const { lintingErrorCount } = await runLintRules(
|
||||
log,
|
||||
toLint,
|
||||
RULES.filter((r) => r.name !== 'referenceUsedPkgs' || skipRefs === false),
|
||||
{
|
||||
fix: flagsReader.boolean('fix'),
|
||||
getFiles: (target) => fileMap.getFiles(target.tsProject),
|
||||
}
|
||||
);
|
||||
|
||||
const failed =
|
||||
lintingErrorCount > 0 ||
|
||||
(filter.length > 0 ? false : validateProjectOwnership(allTargets, allFiles, fileMap, log));
|
||||
|
||||
if (failed) {
|
||||
throw createFailError('see above errors');
|
||||
} else {
|
||||
log.success('All TS projects linted successfully');
|
||||
}
|
||||
},
|
||||
{
|
||||
usage: `node scripts/package_linter [...packages]`,
|
||||
flags: {
|
||||
boolean: ['fix', 'refs-check', 'no-refs-check'],
|
||||
alias: { f: 'fix', R: 'no-refs-check' },
|
||||
default: { 'refs-check': true },
|
||||
help: `
|
||||
--no-lint Disables linting rules, only validting that every file is a member of just one project
|
||||
--fix Automatically fix some issues in tsconfig.json files
|
||||
-R, --no-refs-check Disables the reference checking rules, making the linting much faster, but less accruate
|
||||
`,
|
||||
},
|
||||
description:
|
||||
'Validate packages using a set of rules that evolve over time. The vast majority of violations can be auto-fixed, so running with `--fix` is recommended.',
|
||||
}
|
||||
);
|
31
packages/kbn-lint-ts-projects-cli/tsconfig.json
Normal file
31
packages/kbn-lint-ts-projects-cli/tsconfig.json
Normal file
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"types": [
|
||||
"jest",
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*"
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/dev-cli-runner",
|
||||
"@kbn/dev-cli-errors",
|
||||
"@kbn/repo-path",
|
||||
"@kbn/get-repo-files",
|
||||
"@kbn/ts-projects",
|
||||
"@kbn/repo-packages",
|
||||
"@kbn/repo-info",
|
||||
"@kbn/repo-linter",
|
||||
"@kbn/json-ast",
|
||||
"@kbn/std",
|
||||
"@kbn/import-locator",
|
||||
"@kbn/repo-file-maps",
|
||||
"@kbn/some-dev-log",
|
||||
]
|
||||
}
|
|
@ -12,7 +12,7 @@ import { createAbsolutePathSerializer } from '@kbn/jest-serializers';
|
|||
import { getOptimizerCacheKey } from './optimizer_cache_key';
|
||||
import { OptimizerConfig } from './optimizer_config';
|
||||
|
||||
jest.mock('@kbn/package-map', () => {
|
||||
jest.mock('@kbn/repo-packages', () => {
|
||||
return {
|
||||
readHashOfPackageMap() {
|
||||
return '<hash of package map>';
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { readHashOfPackageMap } from '@kbn/package-map';
|
||||
import { readHashOfPackageMap } from '@kbn/repo-packages';
|
||||
|
||||
import { CacheableWorkerConfig, Hashes } from '../common';
|
||||
import { OptimizerConfig } from './optimizer_config';
|
||||
|
|
|
@ -22,12 +22,12 @@
|
|||
"@kbn/ui-shared-deps-npm",
|
||||
"@kbn/ui-shared-deps-src",
|
||||
"@kbn/tooling-log",
|
||||
"@kbn/package-map",
|
||||
"@kbn/ci-stats-reporter",
|
||||
"@kbn/dev-cli-errors",
|
||||
"@kbn/repo-info",
|
||||
"@kbn/dev-cli-runner",
|
||||
"@kbn/jest-serializers",
|
||||
"@kbn/plugin-discovery",
|
||||
"@kbn/repo-packages",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
load("@npm//@bazel/typescript:index.bzl", "ts_config")
|
||||
load("@build_bazel_rules_nodejs//:index.bzl", "js_library")
|
||||
load("//src/dev/bazel:index.bzl", "pkg_npm", "ts_project")
|
||||
|
||||
js_library(
|
||||
name = "kbn-package-map",
|
||||
package_name = "@kbn/package-map",
|
||||
srcs = [
|
||||
"package.json",
|
||||
"index.js",
|
||||
"index.d.ts",
|
||||
"package-map.json",
|
||||
],
|
||||
deps = [],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
3
packages/kbn-picomatcher/README.md
Normal file
3
packages/kbn-picomatcher/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# @kbn/picomatcher
|
||||
|
||||
A simple wrapper around picomatch for making fast match functions from a list of globs with negative matching and tests.
|
13
packages/kbn-picomatcher/jest.config.js
Normal file
13
packages/kbn-picomatcher/jest.config.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test/jest_node',
|
||||
rootDir: '../..',
|
||||
roots: ['<rootDir>/packages/kbn-picomatcher'],
|
||||
};
|
6
packages/kbn-picomatcher/kibana.jsonc
Normal file
6
packages/kbn-picomatcher/kibana.jsonc
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"type": "shared-common",
|
||||
"id": "@kbn/picomatcher",
|
||||
"owner": "@elastic/kibana-operations",
|
||||
"devOnly": true
|
||||
}
|
51
packages/kbn-picomatcher/make_matcher.test.ts
Normal file
51
packages/kbn-picomatcher/make_matcher.test.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { makeMatcher } from './make_matcher';
|
||||
|
||||
describe('@kbn/picomatcher makeMatcher()', () => {
|
||||
it('does positive matching', () => {
|
||||
const matcher = makeMatcher(['foo/**', 'bar/**']);
|
||||
|
||||
expect(matcher('foo')).toBe(true);
|
||||
expect(matcher('foo/bar')).toBe(true);
|
||||
expect(matcher('foo/baz')).toBe(true);
|
||||
expect(matcher('bar')).toBe(true);
|
||||
expect(matcher('bar/foo')).toBe(true);
|
||||
expect(matcher('bar/baz')).toBe(true);
|
||||
expect(matcher('baz')).toBe(false);
|
||||
expect(matcher('baz/box')).toBe(false);
|
||||
});
|
||||
|
||||
it('does negative matching', () => {
|
||||
const matcher = makeMatcher(['foo/**', '!foo/bar/**'], {
|
||||
ignore: ['foo/yar?/**'],
|
||||
});
|
||||
|
||||
expect(matcher('foo')).toBe(true);
|
||||
expect(matcher('foo/bar')).toBe(false);
|
||||
expect(matcher('foo/bar/baz')).toBe(false);
|
||||
expect(matcher('foo/box')).toBe(true);
|
||||
expect(matcher('foo/box/baz')).toBe(true);
|
||||
expect(matcher('foo/yar')).toBe(true);
|
||||
expect(matcher('foo/yar/tya')).toBe(true);
|
||||
expect(matcher('foo/yarn/tya')).toBe(false);
|
||||
});
|
||||
|
||||
it('does case-insensitive matching', () => {
|
||||
const matcher = makeMatcher(['foo/**', '!foo/bar/**'], { caseInsensitive: true });
|
||||
|
||||
expect(matcher('FOO')).toBe(true);
|
||||
expect(matcher('FOO/BAR')).toBe(false);
|
||||
expect(matcher('FOO/BAR/BAZ')).toBe(false);
|
||||
expect(matcher('FOO/BOX')).toBe(true);
|
||||
expect(matcher('FOO/BOX/BAZ')).toBe(true);
|
||||
expect(matcher('FOO/YAR')).toBe(true);
|
||||
expect(matcher('FOO/YAR/TYA')).toBe(true);
|
||||
});
|
||||
});
|
50
packages/kbn-picomatcher/make_matcher.ts
Normal file
50
packages/kbn-picomatcher/make_matcher.ts
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import pm from 'picomatch';
|
||||
|
||||
export type Matcher = (path: string) => boolean;
|
||||
|
||||
/**
|
||||
* Simplified version of picomatch options, focusing on options useful to Kibana
|
||||
* that we have actually tested.
|
||||
*/
|
||||
export interface MatchOptions {
|
||||
/**
|
||||
* One or more glob patterns for excluding strings that should not be matched from the result.
|
||||
*/
|
||||
ignore?: string[];
|
||||
/**
|
||||
* Make matching case-insensitive. Equivalent to the regex `i` flag.
|
||||
*/
|
||||
caseInsensitive?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a matcher function which will can test if a string matches any
|
||||
* of the positive patterns and none of the negative patterns
|
||||
*/
|
||||
export function makeMatcher(patterns: string[], options?: MatchOptions) {
|
||||
const negative: string[] = [];
|
||||
const positive: string[] = [];
|
||||
for (const e of patterns) {
|
||||
if (e.startsWith('!')) {
|
||||
negative.push(e.slice(1));
|
||||
} else {
|
||||
positive.push(e);
|
||||
}
|
||||
}
|
||||
|
||||
const matcher = pm(positive, {
|
||||
nocase: options?.caseInsensitive,
|
||||
nonegate: true,
|
||||
ignore: [...(options?.ignore ?? []), ...negative],
|
||||
});
|
||||
|
||||
return (val: string) => matcher(val);
|
||||
}
|
7
packages/kbn-picomatcher/package.json
Normal file
7
packages/kbn-picomatcher/package.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"name": "@kbn/picomatcher",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "SSPL-1.0 OR Elastic License 2.0",
|
||||
"main": "./make_matcher"
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue