[kbn/optimizer] allow customizing the limits path from the script (#93153)

Co-authored-by: spalger <spalger@users.noreply.github.com>
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Spencer 2021-03-02 18:29:54 -07:00 committed by GitHub
parent fda67adb9f
commit 57a860ddb0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 206 additions and 163 deletions

View file

@ -12,164 +12,190 @@ import Path from 'path';
import { REPO_ROOT } from '@kbn/utils';
import { lastValueFrom } from '@kbn/std';
import { run, createFlagError } from '@kbn/dev-utils';
import { run, createFlagError, Flags } from '@kbn/dev-utils';
import { logOptimizerState } from './log_optimizer_state';
import { OptimizerConfig } from './optimizer';
import { runOptimizer } from './run_optimizer';
import { validateLimitsForAllBundles, updateBundleLimits } from './limits';
run(
async ({ log, flags }) => {
const watch = flags.watch ?? false;
if (typeof watch !== 'boolean') {
throw createFlagError('expected --watch to have no value');
function getLimitsPath(flags: Flags, defaultPath: string) {
if (flags.limits) {
if (typeof flags.limits !== 'string') {
throw createFlagError('expected --limits to be a string');
}
const oss = flags.oss ?? false;
if (typeof oss !== 'boolean') {
throw createFlagError('expected --oss to have no value');
}
const cache = flags.cache ?? true;
if (typeof cache !== 'boolean') {
throw createFlagError('expected --cache to have no value');
}
const includeCoreBundle = flags.core ?? true;
if (typeof includeCoreBundle !== 'boolean') {
throw createFlagError('expected --core to have no value');
}
const dist = flags.dist ?? false;
if (typeof dist !== 'boolean') {
throw createFlagError('expected --dist to have no value');
}
const examples = flags.examples ?? false;
if (typeof examples !== 'boolean') {
throw createFlagError('expected --no-examples to have no value');
}
const profileWebpack = flags.profile ?? false;
if (typeof profileWebpack !== 'boolean') {
throw createFlagError('expected --profile to have no value');
}
const inspectWorkers = flags['inspect-workers'] ?? false;
if (typeof inspectWorkers !== 'boolean') {
throw createFlagError('expected --no-inspect-workers to have no value');
}
const maxWorkerCount = flags.workers ? Number.parseInt(String(flags.workers), 10) : undefined;
if (maxWorkerCount !== undefined && (!Number.isFinite(maxWorkerCount) || maxWorkerCount < 1)) {
throw createFlagError('expected --workers to be a number greater than 0');
}
const extraPluginScanDirs = ([] as string[])
.concat((flags['scan-dir'] as string | string[]) || [])
.map((p) => Path.resolve(p));
if (!extraPluginScanDirs.every((s) => typeof s === 'string')) {
throw createFlagError('expected --scan-dir to be a string');
}
const reportStats = flags['report-stats'] ?? false;
if (typeof reportStats !== 'boolean') {
throw createFlagError('expected --report-stats to have no value');
}
const filter = typeof flags.filter === 'string' ? [flags.filter] : flags.filter;
if (!Array.isArray(filter) || !filter.every((f) => typeof f === 'string')) {
throw createFlagError('expected --filter to be one or more strings');
}
const focus = typeof flags.focus === 'string' ? [flags.focus] : flags.focus;
if (!Array.isArray(focus) || !focus.every((f) => typeof f === 'string')) {
throw createFlagError('expected --focus to be one or more strings');
}
const validateLimits = flags['validate-limits'] ?? false;
if (typeof validateLimits !== 'boolean') {
throw createFlagError('expected --validate-limits to have no value');
}
const updateLimits = flags['update-limits'] ?? false;
if (typeof updateLimits !== 'boolean') {
throw createFlagError('expected --update-limits to have no value');
}
const config = OptimizerConfig.create({
repoRoot: REPO_ROOT,
watch,
maxWorkerCount,
oss: oss && !(validateLimits || updateLimits),
dist: dist || updateLimits,
cache,
examples: examples && !(validateLimits || updateLimits),
profileWebpack,
extraPluginScanDirs,
inspectWorkers,
includeCoreBundle,
filter,
focus,
});
if (validateLimits) {
validateLimitsForAllBundles(log, config);
return;
}
const update$ = runOptimizer(config);
await lastValueFrom(update$.pipe(logOptimizerState(log, config)));
if (updateLimits) {
updateBundleLimits({
log,
config,
dropMissing: !(focus || filter),
});
}
},
{
flags: {
boolean: [
'core',
'watch',
'oss',
'examples',
'dist',
'cache',
'profile',
'inspect-workers',
'validate-limits',
'update-limits',
],
string: ['workers', 'scan-dir', 'filter'],
default: {
core: true,
examples: true,
cache: true,
'inspect-workers': true,
filter: [],
focus: [],
},
help: `
--watch run the optimizer in watch mode
--workers max number of workers to use
--oss only build oss plugins
--profile profile the webpack builds and write stats.json files to build outputs
--no-core disable generating the core bundle
--no-cache disable the cache
--focus just like --filter, except dependencies are automatically included, --filter applies to result
--filter comma-separated list of bundle id filters, results from multiple flags are merged, * and ! are supported
--no-examples don't build the example plugins
--dist create bundles that are suitable for inclusion in the Kibana distributable, enabled when running with --update-limits
--scan-dir add a directory to the list of directories scanned for plugins (specify as many times as necessary)
--no-inspect-workers when inspecting the parent process, don't inspect the workers
--validate-limits validate the limits.yml config to ensure that there are limits defined for every bundle
--update-limits run a build and rewrite the limits file to include the current bundle sizes +5kb
`,
},
return Path.resolve(flags.limits);
}
);
if (process.env.KBN_OPTIMIZER_LIMITS_PATH) {
return Path.resolve(process.env.KBN_OPTIMIZER_LIMITS_PATH);
}
return defaultPath;
}
export function runKbnOptimizerCli(options: { defaultLimitsPath: string }) {
run(
async ({ log, flags }) => {
const watch = flags.watch ?? false;
if (typeof watch !== 'boolean') {
throw createFlagError('expected --watch to have no value');
}
const oss = flags.oss ?? false;
if (typeof oss !== 'boolean') {
throw createFlagError('expected --oss to have no value');
}
const cache = flags.cache ?? true;
if (typeof cache !== 'boolean') {
throw createFlagError('expected --cache to have no value');
}
const includeCoreBundle = flags.core ?? true;
if (typeof includeCoreBundle !== 'boolean') {
throw createFlagError('expected --core to have no value');
}
const dist = flags.dist ?? false;
if (typeof dist !== 'boolean') {
throw createFlagError('expected --dist to have no value');
}
const examples = flags.examples ?? false;
if (typeof examples !== 'boolean') {
throw createFlagError('expected --no-examples to have no value');
}
const profileWebpack = flags.profile ?? false;
if (typeof profileWebpack !== 'boolean') {
throw createFlagError('expected --profile to have no value');
}
const inspectWorkers = flags['inspect-workers'] ?? false;
if (typeof inspectWorkers !== 'boolean') {
throw createFlagError('expected --no-inspect-workers to have no value');
}
const maxWorkerCount = flags.workers ? Number.parseInt(String(flags.workers), 10) : undefined;
if (
maxWorkerCount !== undefined &&
(!Number.isFinite(maxWorkerCount) || maxWorkerCount < 1)
) {
throw createFlagError('expected --workers to be a number greater than 0');
}
const extraPluginScanDirs = ([] as string[])
.concat((flags['scan-dir'] as string | string[]) || [])
.map((p) => Path.resolve(p));
if (!extraPluginScanDirs.every((s) => typeof s === 'string')) {
throw createFlagError('expected --scan-dir to be a string');
}
const reportStats = flags['report-stats'] ?? false;
if (typeof reportStats !== 'boolean') {
throw createFlagError('expected --report-stats to have no value');
}
const filter = typeof flags.filter === 'string' ? [flags.filter] : flags.filter;
if (!Array.isArray(filter) || !filter.every((f) => typeof f === 'string')) {
throw createFlagError('expected --filter to be one or more strings');
}
const focus = typeof flags.focus === 'string' ? [flags.focus] : flags.focus;
if (!Array.isArray(focus) || !focus.every((f) => typeof f === 'string')) {
throw createFlagError('expected --focus to be one or more strings');
}
const limitsPath = getLimitsPath(flags, options.defaultLimitsPath);
const validateLimits = flags['validate-limits'] ?? false;
if (typeof validateLimits !== 'boolean') {
throw createFlagError('expected --validate-limits to have no value');
}
const updateLimits = flags['update-limits'] ?? false;
if (typeof updateLimits !== 'boolean') {
throw createFlagError('expected --update-limits to have no value');
}
const config = OptimizerConfig.create({
repoRoot: REPO_ROOT,
watch,
maxWorkerCount,
oss: oss && !(validateLimits || updateLimits),
dist: dist || updateLimits,
cache,
examples: examples && !(validateLimits || updateLimits),
profileWebpack,
extraPluginScanDirs,
inspectWorkers,
includeCoreBundle,
filter,
focus,
limitsPath,
});
if (validateLimits) {
validateLimitsForAllBundles(log, config, limitsPath);
return;
}
const update$ = runOptimizer(config);
await lastValueFrom(update$.pipe(logOptimizerState(log, config)));
if (updateLimits) {
updateBundleLimits({
log,
config,
dropMissing: !(focus || filter),
limitsPath,
});
}
},
{
flags: {
boolean: [
'core',
'watch',
'oss',
'examples',
'dist',
'cache',
'profile',
'inspect-workers',
'validate-limits',
'update-limits',
],
string: ['workers', 'scan-dir', 'filter', 'limits'],
default: {
core: true,
examples: true,
cache: true,
'inspect-workers': true,
filter: [],
focus: [],
},
help: `
--watch run the optimizer in watch mode
--workers max number of workers to use
--oss only build oss plugins
--profile profile the webpack builds and write stats.json files to build outputs
--no-core disable generating the core bundle
--no-cache disable the cache
--focus just like --filter, except dependencies are automatically included, --filter applies to result
--filter comma-separated list of bundle id filters, results from multiple flags are merged, * and ! are supported
--no-examples don't build the example plugins
--dist create bundles that are suitable for inclusion in the Kibana distributable, enabled when running with --update-limits
--scan-dir add a directory to the list of directories scanned for plugins (specify as many times as necessary)
--no-inspect-workers when inspecting the parent process, don't inspect the workers
--limits path to a limits.yml file to read, defaults to $KBN_OPTIMIZER_LIMITS_PATH or source file
--validate-limits validate the limits.yml config to ensure that there are limits defined for every bundle
--update-limits run a build and rewrite the limits file to include the current bundle sizes +5kb
`,
},
}
);
}

View file

@ -11,3 +11,4 @@ export * from './run_optimizer';
export * from './log_optimizer_state';
export * from './node';
export * from './limits';
export * from './cli';

View file

@ -15,15 +15,14 @@ import { createFailError, ToolingLog, CiStatsMetrics } from '@kbn/dev-utils';
import { OptimizerConfig, Limits } from './optimizer';
const LIMITS_PATH = require.resolve('../limits.yml');
const DEFAULT_BUDGET = 15000;
const diff = <T>(a: T[], b: T[]): T[] => a.filter((item) => !b.includes(item));
export function readLimits(): Limits {
export function readLimits(path: string): Limits {
let yaml;
try {
yaml = Fs.readFileSync(LIMITS_PATH, 'utf8');
yaml = Fs.readFileSync(path, 'utf8');
} catch (error) {
if (error.code !== 'ENOENT') {
throw error;
@ -33,8 +32,12 @@ export function readLimits(): Limits {
return yaml ? Yaml.safeLoad(yaml) : {};
}
export function validateLimitsForAllBundles(log: ToolingLog, config: OptimizerConfig) {
const limitBundleIds = Object.keys(readLimits().pageLoadAssetSize || {});
export function validateLimitsForAllBundles(
log: ToolingLog,
config: OptimizerConfig,
limitsPath: string
) {
const limitBundleIds = Object.keys(readLimits(limitsPath).pageLoadAssetSize || {});
const configBundleIds = config.bundles.map((b) => b.id);
const missingBundleIds = diff(configBundleIds, limitBundleIds);
@ -73,10 +76,16 @@ interface UpdateBundleLimitsOptions {
log: ToolingLog;
config: OptimizerConfig;
dropMissing: boolean;
limitsPath: string;
}
export function updateBundleLimits({ log, config, dropMissing }: UpdateBundleLimitsOptions) {
const limits = readLimits();
export function updateBundleLimits({
log,
config,
dropMissing,
limitsPath,
}: UpdateBundleLimitsOptions) {
const limits = readLimits(limitsPath);
const metrics: CiStatsMetrics = config.bundles
.map((bundle) =>
JSON.parse(Fs.readFileSync(Path.resolve(bundle.outputDir, 'metrics.json'), 'utf-8'))
@ -102,6 +111,6 @@ export function updateBundleLimits({ log, config, dropMissing }: UpdateBundleLim
pageLoadAssetSize,
};
Fs.writeFileSync(LIMITS_PATH, Yaml.safeDump(newLimits));
log.success(`wrote updated limits to ${LIMITS_PATH}`);
Fs.writeFileSync(limitsPath, Yaml.safeDump(newLimits));
log.success(`wrote updated limits to ${limitsPath}`);
}

View file

@ -427,6 +427,7 @@ describe('OptimizerConfig::create()', () => {
it('passes parsed options to findKibanaPlatformPlugins, getBundles, and assignBundlesToWorkers', () => {
const config = OptimizerConfig.create({
repoRoot: REPO_ROOT,
limitsPath: '/foo/limits.yml',
});
expect(config).toMatchInlineSnapshot(`

View file

@ -115,6 +115,9 @@ interface Options {
* - "k7light"
*/
themes?: ThemeTag | '*' | ThemeTag[];
/** path to a limits.yml file that should be used to inform ci-stats of metric limits */
limitsPath?: string;
}
export interface ParsedOptions {
@ -211,7 +214,7 @@ export class OptimizerConfig {
}
static create(inputOptions: Options) {
const limits = readLimits();
const limits = inputOptions.limitsPath ? readLimits(inputOptions.limitsPath) : {};
const options = OptimizerConfig.parseOptions(inputOptions);
const plugins = findKibanaPlatformPlugins(options.pluginScanDirs, options.pluginPaths);
const bundles = [

View file

@ -6,4 +6,6 @@
* Side Public License, v 1.
*/
require('@kbn/optimizer/target/cli');
require('@kbn/optimizer').runKbnOptimizerCli({
defaultLimitsPath: require.resolve('../packages/kbn-optimizer/limits.yml'),
});

View file

@ -27,6 +27,7 @@ export const BuildKibanaPlatformPlugins: Task = {
watch: false,
dist: true,
includeCoreBundle: true,
limitsPath: Path.resolve(REPO_ROOT, 'packages/kbn-optimizer/limits.yml'),
});
await lastValueFrom(runOptimizer(config).pipe(logOptimizerState(log, config)));