Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/kbn-docs-utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

export { runBuildApiDocsCli } from './src';
export { runBuildApiDocsCli, runCheckPackageDocsCli } from './src';

export { findPlugins, findTeamPlugins } from './src/find_plugins';
1 change: 1 addition & 0 deletions packages/kbn-docs-utils/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ module.exports = {
'!<rootDir>/packages/kbn-docs-utils/src/**/*.test.ts',
'!<rootDir>/packages/kbn-docs-utils/src/**/__fixtures__/**',
],
coveragePathIgnorePatterns: ['<rootDir>/packages/kbn-docs-utils/src/integration_tests/'],
};
13 changes: 13 additions & 0 deletions packages/kbn-docs-utils/src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,16 @@ To generate the docs run
```
node scripts/build_api_docs
```

To validate documentation without writing files, run

```
node scripts/check_package_docs --plugin <pluginId>
```

You can use `--plugin` to filter by plugin id and `--package` to filter by package id (manifest.id). These filters can be used independently or together. Validation flags:

- `--check <any|comments|exports|all>` (optional, defaults to `all`).
- You may pass multiple `--check` flags to combine specific checks.

The `--stats` flag on `build_api_docs` is deprecated and routes to `check_package_docs`.
154 changes: 98 additions & 56 deletions packages/kbn-docs-utils/src/build_api_docs_cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,71 +7,113 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { createFlagError } from '@kbn/dev-cli-errors';

// Test flag validation logic directly without executing the full CLI
describe('build_api_docs_cli flag validation', () => {
// Test the validation logic that would be in build_api_docs_cli
function isStringArray(arr: unknown | string[]): arr is string[] {
return Array.isArray(arr) && arr.every((p) => typeof p === 'string');
}

it('validates plugin flag must be string array', () => {
const pluginFilter = { invalid: 'object' };

if (pluginFilter && !isStringArray(pluginFilter)) {
expect(() => {
throw createFlagError('expected --plugin must only contain strings');
}).toThrow('expected --plugin must only contain strings');
}
});
import apm from 'elastic-apm-node';
import { runBuildApiDocsCli } from './build_api_docs_cli';
import {
parseCliFlags,
setupProject,
buildApiMap,
collectStats,
reportMetrics,
writeDocs,
} from './cli';
import { runCheckPackageDocs } from './check_package_docs_cli';

it('validates stats flag values', () => {
const stats = ['invalid'];

if (
(stats &&
isStringArray(stats) &&
stats.find((s) => s !== 'any' && s !== 'comments' && s !== 'exports')) ||
(stats && !isStringArray(stats))
) {
expect(() => {
throw createFlagError(
'expected --stats must only contain `any`, `comments` and/or `exports`'
);
}).toThrow('expected --stats must only contain');
}
});
jest.mock('elastic-apm-node', () => {
const tx = {
startSpan: jest.fn(),
end: jest.fn(),
setOutcome: jest.fn(),
};
return {
startTransaction: jest.fn(() => tx),
isStarted: jest.fn(() => false),
flush: jest.fn(),
__tx: tx,
};
});

jest.mock('@kbn/apm-config-loader', () => ({
initApm: jest.fn(),
}));

let registeredHandler: any;
jest.mock('@kbn/dev-cli-runner', () => ({
run: jest.fn((handler: any) => {
registeredHandler = handler;
}),
}));

it('accepts valid stats values', () => {
const stats = ['any', 'comments'];
jest.mock('./cli', () => ({
parseCliFlags: jest.fn(),
setupProject: jest.fn(),
buildApiMap: jest.fn(),
collectStats: jest.fn(),
reportMetrics: jest.fn(),
writeDocs: jest.fn(),
}));

if (
(stats &&
isStringArray(stats) &&
stats.find((s) => s !== 'any' && s !== 'comments' && s !== 'exports')) ||
(stats && !isStringArray(stats))
) {
throw new Error('Should not throw for valid stats');
}
jest.mock('./check_package_docs_cli', () => ({
runCheckPackageDocs: jest.fn(),
}));

// Should not throw
expect(stats).toEqual(['any', 'comments']);
const mockTx = (apm as any).__tx;

describe('build_api_docs_cli', () => {
const log = { info: jest.fn(), warning: jest.fn(), error: jest.fn() };

beforeEach(() => {
registeredHandler = undefined;
jest.clearAllMocks();
});

it('handles single string plugin flag', () => {
const plugin = 'single-plugin';
const pluginFilter = typeof plugin === 'string' ? [plugin] : plugin;
it('routes --stats to check CLI and skips build tasks', async () => {
(parseCliFlags as jest.Mock).mockReturnValue({ stats: ['any'], collectReferences: false });

runBuildApiDocsCli();
expect(registeredHandler).toBeDefined();

expect(Array.isArray(pluginFilter)).toBe(true);
expect(pluginFilter).toEqual(['single-plugin']);
await registeredHandler({ log, flags: { stats: 'any' } });

expect(log.warning).toHaveBeenCalledWith(expect.stringContaining('--stats is deprecated'));
expect(runCheckPackageDocs).toHaveBeenCalledWith(log, { stats: 'any' });
expect(setupProject).not.toHaveBeenCalled();
expect(mockTx.end).toHaveBeenCalled();
});

it('handles array plugin flag', () => {
const plugin = ['plugin1', 'plugin2'];
const pluginFilter = typeof plugin === 'string' ? [plugin] : plugin;
it('runs build flow when stats are not provided', async () => {
const setupResult = {
project: {},
plugins: [
{ id: 'p1', manifest: { owner: { name: 'team' }, serviceFolders: [] }, isPlugin: true },
],
};
const apiMapResult = {
pluginApiMap: {},
missingApiItems: {},
referencedDeprecations: {},
unreferencedDeprecations: {},
adoptionTrackedAPIs: {},
};
(parseCliFlags as jest.Mock).mockReturnValue({ stats: undefined, collectReferences: false });
(setupProject as jest.Mock).mockResolvedValue(setupResult);
(buildApiMap as jest.Mock).mockReturnValue(apiMapResult);
(collectStats as jest.Mock).mockResolvedValue({});

runBuildApiDocsCli();
await registeredHandler({ log, flags: {} });

expect(Array.isArray(pluginFilter)).toBe(true);
expect(pluginFilter).toEqual(['plugin1', 'plugin2']);
expect(setupProject).toHaveBeenCalled();
expect(buildApiMap).toHaveBeenCalledWith(
setupResult.project,
setupResult.plugins,
log,
mockTx,
{ stats: undefined, collectReferences: false }
);
expect(collectStats).toHaveBeenCalled();
expect(reportMetrics).toHaveBeenCalled();
expect(writeDocs).toHaveBeenCalled();
expect(mockTx.end).toHaveBeenCalled();
});
});
32 changes: 24 additions & 8 deletions packages/kbn-docs-utils/src/build_api_docs_cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,15 @@ import {
type CliFlags,
type CliContext,
} from './cli';
import { runCheckPackageDocs } from './check_package_docs_cli';

const rootDir = Path.join(__dirname, '../../..');
initApm(process.argv, rootDir, false, 'build_api_docs_cli');

const startApm = () => {
if (!apm.isStarted()) {
initApm(process.argv, rootDir, false, 'build_api_docs_cli');
}
};

async function endTransactionWithFailure(transaction: Transaction | null) {
if (transaction !== null) {
Expand All @@ -39,10 +45,10 @@ async function endTransactionWithFailure(transaction: Transaction | null) {
}

export function runBuildApiDocsCli() {
startApm();
run(
async ({ log, flags }) => {
const transaction = apm.startTransaction('build-api-docs', 'kibana-cli');
const spanSetup = transaction.startSpan('build_api_docs.setup', 'setup');

let options;
try {
Expand All @@ -52,6 +58,16 @@ export function runBuildApiDocsCli() {
throw error;
}

if (options.stats && options.stats.length > 0) {
log.warning('--stats is deprecated. Please run check_package_docs_cli instead.');
transaction?.end();
await apm.flush();
await runCheckPackageDocs(log, flags as CliFlags);
return;
}

const spanSetup = transaction.startSpan('build_api_docs.setup', 'setup');

const outputFolder = Path.resolve(REPO_ROOT, 'api_docs');

const context: CliContext = {
Expand Down Expand Up @@ -101,14 +117,14 @@ export function runBuildApiDocsCli() {
defaultLevel: 'info',
},
flags: {
string: ['plugin', 'stats'],
string: ['plugin', 'package', 'stats'],
boolean: ['references'],
help: `
--plugin Optionally, run for only a specific plugin
--stats Optionally print API stats. Must be one or more of: any, comments or exports.
In combination with a single plugin filter this option will skip writing any
API docs as a tradeoff to just produce the stats output more quickly.
--references Collect references for API items
--plugin Optionally, run for only a specific plugin by its plugin ID (plugin.id in kibana.jsonc).
--package Optionally, run for only a specific package by its package ID (id in kibana.jsonc, e.g., @kbn/core).
--stats Deprecated. Use check_package_docs_cli instead. When provided, validation is routed
to the new CLI and build outputs are skipped. Must be one or more of: any, comments or exports.
--references Collect references for API items.
`,
},
}
Expand Down
Loading
Loading