๐Ÿ“ฆ directus / cli

๐Ÿ“„ options.ts ยท 127 lines
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127//import * as util from 'util';
import { GluegunCommand } from 'gluegun';
import yargs, { Argv } from 'yargs';
import yargsParser from 'yargs-parser';

import { Command } from '../command';
import { IEvents } from '../events';
import { IOptions, Option } from '../options';
import { CLIRuntimeError } from './exceptions';

export type Registrator = (builder: Argv, command: Command, raw: any) => void;

export class Options implements IOptions {
	private _raw: any;
	private _parser: Argv;
	private _parsed?: any;
	private _error?: Error;
	private _registrators: Registrator[];

	constructor(events: IEvents, argv: string[]) {
		this._parser = yargs(argv).help(false).version(false);
		this._registrators = [];
		this._parsed = null;
		this._raw = yargsParser(argv);

		events.on('command.options.register', (command: Command) => {
			const cmd = command as any as GluegunCommand;
			const name = cmd.commandPath
				?.concat(...[command.settings?.parameters ?? ''])
				.filter((p) => p != '')
				.join(' ');
			this._parser.fail((message, err) => {
				this._error = err || new CLIRuntimeError(message);
			});

			this._parser.showHelpOnFail(false).exitProcess(false);
			this._parser.command(
				name ?? '$0',
				cmd.description ?? '',
				(builder) => {
					for (const registrator of this._registrators) {
						registrator(builder, command, this._raw);
					}
					return this._parser;
				},
				(values) => {
					this._parsed = values;
				}
			);

			this._parser.argv;
		});
	}

	register(registrator: Registrator): void {
		this._registrators.push(registrator);
	}

	feature(name: string, registrator: Registrator): void {
		this._registrators.push((options: Argv, command: Command) => {
			const { settings } = command;
			if (!settings) {
				return;
			}

			const { features } = settings;
			if (!features || !(name in features) || !features[name]) {
				return;
			}

			registrator(options, command, this._raw);
		});
	}

	failed(): boolean {
		return !!this._error;
	}

	error(): Error | undefined {
		return this._error;
	}

	values(): any {
		return this._parsed || {};
	}

	list(): Option[] {
		const freeParser = this._parser as any;
		const usage = freeParser.getUsageInstance();
		const descriptions = usage.getDescriptions();
		const keys = Object.keys(descriptions);
		const options = freeParser.getOptions() as any;
		const positionalGroup = usage.getPositionalGroupName() as any;
		const groups = freeParser.getGroups() as any;

		return keys.map<Option>((key) => {
			const name = key;
			const description = descriptions[key];
			const value = options.default[key] ?? undefined;
			const required = key in options.demandedOptions;
			const choices = options.choices[key];

			let type = 'string';
			if (options.boolean.indexOf(key) >= 0) {
				type = 'boolean';
			} else if (options.number.indexOf(key) >= 0) {
				type = 'number';
			}

			let positional = false;
			if (positionalGroup in groups) {
				positional = groups[positionalGroup].indexOf(key) >= 0;
			}

			return {
				name,
				description,
				type,
				required,
				choices,
				positional,
				default: value,
			};
		});
	}
}