Replies: 1 comment
-
|
You may find yourself having to construct guards such as this: if ('title' in Example && typeof(Example.title === 'string') {
// do something with title
}To eliminate all the ugly boilerplate type-checking code I would otherwise have had to write when handling a schema emitted by TypeBox, I wrote my own simplified version of type TSchema = {
type: 'object' | 'array' | 'string' | 'number' | 'boolean';
properties?: { [key: string]: TSchema };
items?: TSchema;
title?: string;
description?: string;
default?: boolean | number | string | object | null;
enum?: string[];
};Of course, I then had to coerce the TypeBox TSchema type to my own when passing the schema into my function: traverseSchema(section, schema as unknown as TSchema);First you have convert the TypeBox type to Here's my function in full: import type * as tb from 'typebox';
/**
* Traverses the provided TypeBox schema and compiles CLI help text and parsing options
* based on the schema's properties and their descriptions.
* @param section The section name to prefix CLI options with (e.g. 'status' for --status.enabled).
* If the section is empty or undefined, options will be top-level (e.g. --enabled).
* @param schema The schema.
* @returns The compiled CLI data.
*/
export function compileSection(section: string | undefined, schema: tb.TSchema): CliData {
const lines: Line[] = [];
const parseOptions: ParseOptions = {
boolean: [],
negatable: [],
string: [],
collect: [],
default: {},
alias: {},
};
// Helper function to add a CLI option line for a schema property that has a description.
function annotateOption(option: string, prop: TSchema) {
// If this is a section description, add it and return.
if (prop.type === 'object' && prop.description) {
lines.push({ column1: prop.description });
return;
}
// For boolean options, add to the negatable list so that they can be negated with --no- prefix.
if (prop.type === 'boolean') {
parseOptions.negatable.push(option);
}
const column1 = ` %c--${option}`;
const format = [CLI_OPTION_FMT, CLI_REVERT];
let suffix = '';
// Must check for false value of prop.default, to distinguish from absence of a value
if (prop.default || prop.default === false || prop.enum) {
suffix = '(';
if (prop.enum) {
suffix += 'options: %c';
suffix += prop.enum.join(' ');
suffix += '%c';
format.push(CLI_VALUE_FMT, CLI_REVERT);
}
if (prop.default || prop.default === false) {
suffix += prop.enum ? '; default: %c' : 'default: %c';
suffix += `${prop.default}%c`;
format.push(CLI_VALUE_FMT, CLI_REVERT);
}
suffix += ')';
}
const column2 = `%c${prop.description} ${suffix}`;
lines.push({ column1, column2, format });
}
// Helper function that recursively traverses the schema,
// looking for properties with descriptions.
function traverseSchema(section: string | undefined, schema: TSchema) {
if (schema.properties) {
for (const [key, prop] of Object.entries(schema.properties)) {
if (prop.title) {
lines.push({
column1: '\n%c' + prop.title,
format: CLI_SECTION_FMT
});
}
const nextSection = section ? `${section}.${key}` : key;
if (prop.items) { // 'items' indicates an array
if (prop.items.description) {
annotateOption(nextSection, prop.items);
parseOptions.collect.push(nextSection); // multiple values allowed
}
traverseSchema(nextSection, prop.items);
} else {
if (prop.description) {
annotateOption(nextSection, prop);
}
traverseSchema(nextSection, prop);
}
}
}
}
traverseSchema(section, schema as unknown as TSchema); // Coerce to our simplified TSchema type
return { lines, parseOptions };
} |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Example.titleresults inProperty 'title' does not exist on type 'TObject<{ name: TString; age: TNumber; }>'.ts(2339)How to get title and description?
Beta Was this translation helpful? Give feedback.
All reactions