Skip to content
Open
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
32 changes: 32 additions & 0 deletions src/CLIOptions.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { LanguageName, RendererOptions } from "quicktype-core";

export interface CLIOptions<Lang extends LanguageName = LanguageName> {
// We use this to access the inference flags
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
[option: string]: any;
additionalSchema: string[];
allPropertiesOptional: boolean;
alphabetizeProperties: boolean;
buildMarkovChain?: string;
debug?: string;
graphqlIntrospect?: string;
graphqlSchema?: string;
help: boolean;
httpHeader?: string[];
httpMethod?: string;
lang: Lang;

noRender: boolean;
out?: string;
quiet: boolean;

rendererOptions: RendererOptions<Lang>;

src: string[];
srcLang: string;
srcUrls?: string;
telemetry?: string;
topLevel: string;

version: boolean;
}
1 change: 1 addition & 0 deletions src/GraphQLIntrospection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export async function introspectServer(
headers[matches[1]] = matches[2];
}

// biome-ignore lint/suspicious/noImplicitAnyLet: <explanation>
let result;
try {
const response = await fetch(url, {
Expand Down
126 changes: 126 additions & 0 deletions src/cli.options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { exceptionToString } from "@glideapps/ts-necessities";
import {
// biome-ignore lint/suspicious/noShadowRestrictedNames: <explanation>
hasOwnProperty,
} from "collection-utils";
import commandLineArgs from "command-line-args";
import _ from "lodash";

import {
type OptionDefinition,
type RendererOptions,
type TargetLanguage,
assert,
defaultTargetLanguages,
getTargetLanguage,
isLanguageName,
messageError,
} from "quicktype-core";

import { inferCLIOptions } from "./inference";
import { makeOptionDefinitions } from "./optionDefinitions";
import type { CLIOptions } from "./CLIOptions.types";

// Parse the options in argv and split them into global options and renderer options,
// according to each option definition's `renderer` field. If `partial` is false this
// will throw if it encounters an unknown option.
function parseOptions(
definitions: OptionDefinition[],
argv: string[],
partial: boolean,
): Partial<CLIOptions> {
let opts: commandLineArgs.CommandLineOptions;
try {
opts = commandLineArgs(
definitions.map((def) => ({
...def,
type: def.optionType === "boolean" ? Boolean : String,
})),
{ argv, partial },
);
} catch (e) {
assert(!partial, "Partial option parsing should not have failed");
return messageError("DriverCLIOptionParsingFailed", {
message: exceptionToString(e),
});
}

for (const k of Object.keys(opts)) {
if (opts[k] === null) {
return messageError("DriverCLIOptionParsingFailed", {
message: `Missing value for command line option "${k}"`,
});
}
}

const options: {
[key: string]: unknown;
rendererOptions: RendererOptions;
} = { rendererOptions: {} };
for (const optionDefinition of definitions) {
if (!hasOwnProperty(opts, optionDefinition.name)) {
continue;
}

const optionValue = opts[optionDefinition.name] as string;
if (optionDefinition.kind !== "cli") {
(
options.rendererOptions as Record<
typeof optionDefinition.name,
unknown
>
)[optionDefinition.name] = optionValue;
} else {
const k = _.lowerFirst(
optionDefinition.name.split("-").map(_.upperFirst).join(""),
);
options[k] = optionValue;
}
}

return options;
}

export function parseCLIOptions(
argv: string[],
inputTargetLanguage?: TargetLanguage,
): CLIOptions {
if (argv.length === 0) {
return inferCLIOptions({ help: true }, inputTargetLanguage);
}

const targetLanguages = inputTargetLanguage
? [inputTargetLanguage]
: defaultTargetLanguages;
const optionDefinitions = makeOptionDefinitions(targetLanguages);

// We can only fully parse the options once we know which renderer is selected,
// because there are renderer-specific options. But we only know which renderer
// is selected after we've parsed the options. Hence, we parse the options
// twice. This is the first parse to get the renderer:
const incompleteOptions = inferCLIOptions(
parseOptions(optionDefinitions, argv, true),
inputTargetLanguage,
);

let targetLanguage = inputTargetLanguage as TargetLanguage;
if (inputTargetLanguage === undefined) {
const languageName = isLanguageName(incompleteOptions.lang)
? incompleteOptions.lang
: "typescript";
targetLanguage = getTargetLanguage(languageName);
}

const rendererOptionDefinitions =
targetLanguage.cliOptionDefinitions.actual;
// Use the global options as well as the renderer options from now on:
const allOptionDefinitions = _.concat(
optionDefinitions,
rendererOptionDefinitions,
);
// This is the parse that counts:
return inferCLIOptions(
parseOptions(allOptionDefinitions, argv, false),
targetLanguage,
);
}
Loading
Loading