diff --git a/README.md b/README.md index 607cf69f..583d4371 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,10 @@ Commands: ### `generate` +You must provide either `--target` (one or more generators to run) or +`--config-file` (which supplies the targets). Running `generate` without either +exits with an error pointing you to the help output. + ``` Usage: @node-core/doc-kit generate [options] diff --git a/bin/commands/generate.mjs b/bin/commands/generate.mjs index d4586663..30d9a378 100644 --- a/bin/commands/generate.mjs +++ b/bin/commands/generate.mjs @@ -2,7 +2,10 @@ import { Command, Option } from 'commander'; import { publicGenerators } from '../../src/generators/index.mjs'; import createGenerator from '../../src/generators.mjs'; -import { setConfig } from '../../src/utils/configuration/index.mjs'; +import { + assertRunnableOptions, + setConfig, +} from '../../src/utils/configuration/index.mjs'; import { errorWrap } from '../utils.mjs'; const { runGenerators } = createGenerator(); @@ -62,6 +65,8 @@ export default new Command('generate') .action( errorWrap(async opts => { const config = await setConfig(opts); + assertRunnableOptions(config); + await runGenerators(config); }) ); diff --git a/src/utils/configuration/__tests__/index.test.mjs b/src/utils/configuration/__tests__/index.test.mjs index a45de3d9..ac7bf504 100644 --- a/src/utils/configuration/__tests__/index.test.mjs +++ b/src/utils/configuration/__tests__/index.test.mjs @@ -33,6 +33,7 @@ mock.module('../../loaders.mjs', { }); const { + assertRunnableOptions, loadConfigFile, createConfigFromCLIOptions, createRunConfiguration, @@ -125,6 +126,28 @@ describe('config.mjs', () => { }); }); + describe('assertRunnableOptions', () => { + it('should throw when target is missing', () => { + assert.throws( + () => assertRunnableOptions({ global: { input: 'src/' } }), + /Both a `target` and an `input` must be provided/ + ); + }); + + it('should throw when input is missing', () => { + assert.throws( + () => assertRunnableOptions({ target: ['json'], global: {} }), + /Both a `target` and an `input` must be provided/ + ); + }); + + it('should not throw when both target and input are provided', () => { + assert.doesNotThrow(() => + assertRunnableOptions({ target: ['json'], global: { input: 'src/' } }) + ); + }); + }); + describe('createRunConfiguration', () => { it('should merge config sources in correct order', async () => { mockImportFromURL.mock.mockImplementationOnce(async () => diff --git a/src/utils/configuration/index.mjs b/src/utils/configuration/index.mjs index b20a634a..a31bc35a 100644 --- a/src/utils/configuration/index.mjs +++ b/src/utils/configuration/index.mjs @@ -104,6 +104,24 @@ export const createConfigFromCLIOptions = options => ({ chunkSize: options.chunkSize, }); +/** + * Asserts that the resolved configuration has everything needed to run: + * at least one generator `target` and an `input` to read source files from. + * These may come from CLI flags or a config file; by this point both sources + * have been merged, so we validate the result rather than the raw options. + * + * @param {import('./types').Configuration} config - The merged configuration + */ +export const assertRunnableOptions = config => { + if (!config.target || !config.global?.input) { + throw new Error( + 'Both a `target` and an `input` must be provided, either via ' + + '`--target`/`--input` or a `--config-file`. ' + + 'Run `doc-kit generate --help` for usage.' + ); + } +}; + /** * Creates a complete run configuration by merging config file, user options, and defaults. * Processes and validates configuration values including version coercion, changelog parsing,