зеркало из https://github.com/Azure/autorest.git
Feature: Autorest display help from schema (#4323)
This commit is contained in:
Родитель
3416054989
Коммит
85a7d20789
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"changes": [
|
||||
{
|
||||
"packageName": "@autorest/common",
|
||||
"comment": "**Fix** Issue with coloring [] wrapped elements",
|
||||
"type": "patch"
|
||||
}
|
||||
],
|
||||
"packageName": "@autorest/common",
|
||||
"email": "tiguerin@microsoft.com"
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"changes": [
|
||||
{
|
||||
"packageName": "@autorest/configuration",
|
||||
"comment": "**Consolidate** configuration schema to add description and missing settings",
|
||||
"type": "minor"
|
||||
}
|
||||
],
|
||||
"packageName": "@autorest/configuration",
|
||||
"email": "tiguerin@microsoft.com"
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"changes": [
|
||||
{
|
||||
"packageName": "@autorest/core",
|
||||
"comment": "**Updated** `--help` to use configuration schema and be consistent",
|
||||
"type": "minor"
|
||||
}
|
||||
],
|
||||
"packageName": "@autorest/core",
|
||||
"email": "tiguerin@microsoft.com"
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"changes": [
|
||||
{
|
||||
"packageName": "autorest",
|
||||
"comment": "**Remove** legacy code",
|
||||
"type": "minor"
|
||||
}
|
||||
],
|
||||
"packageName": "autorest",
|
||||
"email": "tiguerin@microsoft.com"
|
||||
}
|
|
@ -1,47 +1,3 @@
|
|||
#!/usr/bin/env node
|
||||
/* eslint-disable no-console */
|
||||
|
||||
global.isDebuggerEnabled =
|
||||
!!require("inspector").url() || global.v8debug || /--debug|--inspect/.test(process.execArgv.join(" "));
|
||||
|
||||
const maxMemorySizeArg = process.argv.join(" ").match(/--max-memory-size=(\w+)/);
|
||||
const maxMemorySize = maxMemorySizeArg && parseInt(maxMemorySizeArg[1]);
|
||||
if (isNaN(maxMemorySize)) {
|
||||
console.error(`\nWarning: --max-memory-size parameter '${maxMemorySizeArg[1]}' is not an integer, ignoring it.\n`);
|
||||
}
|
||||
|
||||
// if the process was started with a low heap size (and we're not debugging!) then respawn with a bigger heap size.
|
||||
if (
|
||||
maxMemorySize &&
|
||||
!isDebuggerEnabled &&
|
||||
require("v8").getHeapStatistics().heap_size_limit < maxMemorySize * 1024000
|
||||
) {
|
||||
process.env["NODE_OPTIONS"] = `${
|
||||
process.env["NODE_OPTIONS"] || ""
|
||||
} --max-old-space-size=${maxMemorySize} --no-warnings`;
|
||||
const argv =
|
||||
process.argv.indexOf("--break") === -1
|
||||
? process.argv.slice(1)
|
||||
: ["--inspect-brk", ...process.argv.slice(1).filter((each) => each !== "--break")];
|
||||
require("child_process")
|
||||
.spawn(process.execPath, argv, { argv0: "node", stdio: "inherit" })
|
||||
.on("close", (code) => {
|
||||
process.exit(code);
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
const v = process.versions.node.split(".");
|
||||
if (v[0] < 12) {
|
||||
console.error("\nFATAL: Node v12 or higher (v12.x minimum, v14.x LTS recommended) is required for AutoRest.\n");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (v[0] > 14) {
|
||||
console.error("\nWARNING: AutoRest has not been tested with Node versions greater than v14.\n");
|
||||
}
|
||||
|
||||
require(`${__dirname}/../dist/app.js`);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
// Need to have this intermediate file due to rush issue with generated bin https://github.com/microsoft/rushstack/issues/2400
|
||||
require("../dist/app.js");
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
/* eslint-disable no-console */
|
||||
import "source-map-support/register";
|
||||
|
||||
declare const isDebuggerEnabled: boolean;
|
||||
const cwd = process.cwd();
|
||||
|
||||
import chalk from "chalk";
|
||||
|
@ -17,6 +16,9 @@ import { resetAutorest, showAvailableCoreVersions, showInstalledExtensions } fro
|
|||
import { VERSION } from "./constants";
|
||||
import { loadConfig, resolveCoreVersion } from "./utils";
|
||||
|
||||
const isDebuggerEnabled =
|
||||
// eslint-disable-next-line node/no-unsupported-features/node-builtins
|
||||
!!require("inspector").url() || global.v8debug || /--debug|--inspect/.test(process.execArgv.join(" "));
|
||||
const launchCore = isDebuggerEnabled ? runCoreWithRequire : runCoreOutOfProc;
|
||||
|
||||
// aliases, round one.
|
||||
|
|
|
@ -34,10 +34,9 @@ import {
|
|||
writeBinary,
|
||||
writeString,
|
||||
} from "@azure-tools/uri";
|
||||
import { parseYAML } from "@azure-tools/yaml";
|
||||
import { omit } from "lodash";
|
||||
import { ArtifactWriter } from "./artifact-writer";
|
||||
import { Help } from "./help";
|
||||
import { printAutorestHelp } from "./commands";
|
||||
import { Artifact } from "./lib/artifact";
|
||||
import { AutoRest, IsOpenApiDocument, Shutdown } from "./lib/autorest-core";
|
||||
import { VERSION } from "./lib/constants";
|
||||
|
@ -211,45 +210,7 @@ async function currentMain(autorestArgs: Array<string>): Promise<number> {
|
|||
}
|
||||
|
||||
if (context.config.help) {
|
||||
// no fs operations on --help! Instead, format and print artifacts to console.
|
||||
// - print boilerplate help
|
||||
console.log("");
|
||||
console.log("");
|
||||
console.log(color("**Usage**: autorest `[configuration-file.md] [...options]`"));
|
||||
console.log("");
|
||||
console.log(color(" See: https://aka.ms/autorest/cli for additional documentation"));
|
||||
// - sort artifacts by name (then content, just for stability)
|
||||
const helpArtifacts = artifacts.sort((a, b) =>
|
||||
a.uri === b.uri ? (a.content > b.content ? 1 : -1) : a.uri > b.uri ? 1 : -1,
|
||||
);
|
||||
// - format and print
|
||||
for (const helpArtifact of helpArtifacts) {
|
||||
const { result: help, errors } = parseYAML<Help>(helpArtifact.content);
|
||||
if (errors.length > 0) {
|
||||
for (const { message, position } of errors) {
|
||||
console.error(color(`!Parsing error at **${helpArtifact.uri}**:__${position}: ${message}__`));
|
||||
}
|
||||
}
|
||||
if (!help) {
|
||||
continue;
|
||||
}
|
||||
const activatedBySuffix = help.activationScope ? ` (activated by --${help.activationScope})` : "";
|
||||
console.log("");
|
||||
console.log(color(`### ${help.categoryFriendlyName}${activatedBySuffix}`));
|
||||
if (help.description) {
|
||||
console.log(color(help.description));
|
||||
}
|
||||
console.log("");
|
||||
for (const settingHelp of help.settings) {
|
||||
const keyPart = `--${settingHelp.key}`;
|
||||
const typePart = settingHelp.type ? `=<${settingHelp.type}>` : " "; // `[=<boolean>]`;
|
||||
const settingPart = `${keyPart}\`${typePart}\``;
|
||||
// if (!settingHelp.required) {
|
||||
// settingPart = `[${settingPart}]`;
|
||||
// }
|
||||
console.log(color(` ${settingPart.padEnd(30)} **${settingHelp.description}**`));
|
||||
}
|
||||
}
|
||||
printAutorestHelp(artifacts);
|
||||
} else {
|
||||
// perform file system operations.
|
||||
await doClearFolders(protectFiles, clearFolders);
|
||||
|
|
|
@ -0,0 +1,154 @@
|
|||
/* eslint-disable no-console */
|
||||
import { color } from "@autorest/common";
|
||||
import {
|
||||
AUTOREST_CONFIGURATION_DEFINITION_FOR_HELP,
|
||||
ConfigurationPropertyType,
|
||||
ConfigurationSchemaDefinition,
|
||||
RootConfigurationProperty,
|
||||
} from "@autorest/configuration";
|
||||
import { parseYAML } from "@azure-tools/yaml";
|
||||
import { Artifact } from "../lib/artifact";
|
||||
|
||||
export function printAutorestHelp(artifacts: Artifact[]) {
|
||||
printHelpHeader();
|
||||
printHelpForConfigurationSchema(AUTOREST_CONFIGURATION_DEFINITION_FOR_HELP);
|
||||
|
||||
printHelpFromHelpContent(artifacts);
|
||||
}
|
||||
|
||||
function printHelpHeader() {
|
||||
console.log(
|
||||
[
|
||||
"",
|
||||
"",
|
||||
color("**Usage**: autorest `[configuration-file.md] with [...options]`"),
|
||||
"",
|
||||
color(" See: https://aka.ms/autorest/cli for additional documentation"),
|
||||
].join("\n"),
|
||||
);
|
||||
}
|
||||
|
||||
interface Category {
|
||||
name: string;
|
||||
description?: string;
|
||||
options: Option[];
|
||||
}
|
||||
|
||||
interface Option {
|
||||
key: string;
|
||||
description: string;
|
||||
type: ConfigurationPropertyType;
|
||||
}
|
||||
|
||||
function groupFlagsByCategory(def: ConfigurationSchemaDefinition<any, any>): Category[] {
|
||||
const categories = new Map<string, Category>();
|
||||
const unknownCategories: Option[] = [];
|
||||
for (const [key, { name, description }] of Object.entries(def.categories)) {
|
||||
categories.set(key, { name, description, options: [] });
|
||||
}
|
||||
|
||||
for (const [key, option] of Object.entries<RootConfigurationProperty<any>>(def.schema)) {
|
||||
const category = categories.get(option.category);
|
||||
const options = category === undefined ? unknownCategories : category.options;
|
||||
|
||||
options.push({
|
||||
key,
|
||||
description: option.description ?? "",
|
||||
type: option.type,
|
||||
});
|
||||
}
|
||||
|
||||
return [...categories.values()];
|
||||
}
|
||||
|
||||
function printHelpForConfigurationSchema(def: ConfigurationSchemaDefinition<any, any>) {
|
||||
const categories = groupFlagsByCategory(def);
|
||||
let maxKeyAndTypeLength = 0;
|
||||
const processed = categories.map((category) => {
|
||||
return {
|
||||
...category,
|
||||
options: category.options.map((option) => {
|
||||
const keyPart = `--${option.key}`;
|
||||
const typePart = printConfiguarationPropertyType(option.type);
|
||||
const keyAndType = `${keyPart}\`${typePart}\``;
|
||||
if (keyAndType.length > maxKeyAndTypeLength) {
|
||||
maxKeyAndTypeLength = keyAndType.length;
|
||||
}
|
||||
|
||||
return { keyAndType, description: option.description };
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
for (const category of processed) {
|
||||
console.log("");
|
||||
console.log(color(`### ${category.name}`));
|
||||
if (category.description) {
|
||||
console.log(color(category.description));
|
||||
}
|
||||
for (const option of category.options) {
|
||||
console.log(color(` ${option.keyAndType.padEnd(maxKeyAndTypeLength + 1)} **${option.description}**`));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function printConfiguarationPropertyType(type: ConfigurationPropertyType): string {
|
||||
switch (type) {
|
||||
case "boolean":
|
||||
return " ";
|
||||
default:
|
||||
return `=<${type}>`;
|
||||
}
|
||||
}
|
||||
|
||||
export interface Help {
|
||||
categoryFriendlyName: string; // e.g. "Output Verbosity", "C# generator"
|
||||
activationScope?: string; // e.g. "csharp"
|
||||
description?: string; // inline markdown allowed
|
||||
settings: Array<SettingHelp>;
|
||||
}
|
||||
|
||||
export interface SettingHelp {
|
||||
required?: boolean; // defaults to "false"
|
||||
key: string; // e.g. "namespace"
|
||||
type?: string; // not specified => flag; otherwise, please use TypeScript syntax
|
||||
description: string; // inline markdown allowed
|
||||
}
|
||||
|
||||
/**
|
||||
* Print help defined in an extension `help-content` section in the configuration.
|
||||
*/
|
||||
function printHelpFromHelpContent(artifacts: Artifact[]) {
|
||||
// - sort artifacts by name (then content, just for stability)
|
||||
const helpArtifacts = artifacts.sort((a, b) =>
|
||||
a.uri === b.uri ? (a.content > b.content ? 1 : -1) : a.uri > b.uri ? 1 : -1,
|
||||
);
|
||||
// - format and print
|
||||
for (const helpArtifact of helpArtifacts) {
|
||||
const { result: help, errors } = parseYAML<Help>(helpArtifact.content);
|
||||
if (errors.length > 0) {
|
||||
for (const { message, position } of errors) {
|
||||
console.error(color(`!Parsing error at **${helpArtifact.uri}**:__${position}: ${message}__`));
|
||||
}
|
||||
}
|
||||
if (!help) {
|
||||
continue;
|
||||
}
|
||||
const activatedBySuffix = help.activationScope ? ` (activated by --${help.activationScope})` : "";
|
||||
console.log("");
|
||||
console.log(color(`### ${help.categoryFriendlyName}${activatedBySuffix}`));
|
||||
if (help.description) {
|
||||
console.log(color(help.description));
|
||||
}
|
||||
console.log("");
|
||||
for (const settingHelp of help.settings) {
|
||||
const keyPart = `--${settingHelp.key}`;
|
||||
const typePart = settingHelp.type ? `=<${settingHelp.type}>` : " "; // `[=<boolean>]`;
|
||||
const settingPart = `${keyPart}\`${typePart}\``;
|
||||
// if (!settingHelp.required) {
|
||||
// settingPart = `[${settingPart}]`;
|
||||
// }
|
||||
console.log(color(` ${settingPart.padEnd(30)} **${settingHelp.description}**`));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export * from "./help";
|
|
@ -1,13 +0,0 @@
|
|||
export interface Help {
|
||||
categoryFriendlyName: string; // e.g. "Output Verbosity", "C# generator"
|
||||
activationScope?: string; // e.g. "csharp"
|
||||
description?: string; // inline markdown allowed
|
||||
settings: Array<SettingHelp>;
|
||||
}
|
||||
|
||||
export interface SettingHelp {
|
||||
required?: boolean; // defaults to "false"
|
||||
key: string; // e.g. "namespace"
|
||||
type?: string; // not specified => flag; otherwise, please use TypeScript syntax
|
||||
description: string; // inline markdown allowed
|
||||
}
|
|
@ -4,13 +4,12 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { QuickDataSource } from "@azure-tools/datastore";
|
||||
import { Help } from "../../help";
|
||||
import { PipelinePlugin } from "../pipeline/common";
|
||||
|
||||
/* @internal */
|
||||
export function createHelpPlugin(): PipelinePlugin {
|
||||
return async (config) => {
|
||||
const help: { [helpKey: string]: Help } = config.GetEntry(<any>"help-content");
|
||||
const help: { [helpKey: string]: any } = config.GetEntry(<any>"help-content");
|
||||
for (const helpKey of Object.keys(help).sort()) {
|
||||
config.GeneratedFile.Dispatch({
|
||||
type: "help",
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { ConsoleLogger } from "@autorest/common";
|
||||
import { AutorestConfiguration } from "@autorest/configuration";
|
||||
import { createMockLogger } from "@autorest/test-utils";
|
||||
import { MemoryFileSystem } from "@azure-tools/datastore";
|
||||
import * as AutoRest from "../src/lib/autorest-core";
|
||||
import { createMockLogger } from "@autorest/test-utils";
|
||||
|
||||
describe("Configuration", () => {
|
||||
it("Test config", async () => {
|
||||
|
@ -49,7 +50,11 @@ csharp:
|
|||
]),
|
||||
);
|
||||
|
||||
const autorest = new AutoRest.AutoRest(createMockLogger(), f, MemoryFileSystem.DefaultVirtualRootUri + "readme.md");
|
||||
const autorest = new AutoRest.AutoRest(
|
||||
new ConsoleLogger(),
|
||||
f,
|
||||
MemoryFileSystem.DefaultVirtualRootUri + "readme.md",
|
||||
);
|
||||
const context = await autorest.view;
|
||||
const cfg = context.config;
|
||||
|
||||
|
|
|
@ -43,4 +43,16 @@ describe("Coloring", () => {
|
|||
it("gray ` quoted text", () => {
|
||||
expect(color("some `backtick quote text`")).toEqual(`some ${chalk.gray("backtick quote text")}`);
|
||||
});
|
||||
|
||||
it("color [] wrapped text", () => {
|
||||
expect(color("some [arg] with [option]")).toEqual(
|
||||
`some ${chalk.yellow.bold("[arg]")} with ${chalk.yellow.bold("[option]")}`,
|
||||
);
|
||||
});
|
||||
|
||||
it("color [] wrapped text with bold elements", () => {
|
||||
expect(color("**some** [arg] with [option]")).toEqual(
|
||||
`${chalk.bold("some")} ${chalk.yellow.bold("[arg]")} with ${chalk.yellow.bold("[option]")}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,8 +4,8 @@ const NOT_EMPTY_LINE_REGEXP = /^(?!$)/gm;
|
|||
|
||||
export function color(text: string): string {
|
||||
return text
|
||||
.replace(/(\[.*?\])/gm, (_, x) => chalk.yellow.bold(x))
|
||||
.replace(/\*\*(.*?)\*\*/gm, (_, x) => chalk.bold(x))
|
||||
.replace(/(\[.*?s\])/gm, (_, x) => chalk.yellow.bold(x))
|
||||
.replace(/^# (.*)/gm, (_, x) => chalk.greenBright(x))
|
||||
.replace(/^## (.*)/gm, (_, x) => chalk.green(x))
|
||||
.replace(/^### (.*)/gm, (_, x) => chalk.cyanBright(x))
|
||||
|
|
|
@ -13,118 +13,6 @@ pipeline:
|
|||
|
||||
output-artifact:
|
||||
- help
|
||||
|
||||
help-content: # type: Help as defined in autorest-core/help.ts
|
||||
_autorest-0:
|
||||
categoryFriendlyName: Overall Verbosity
|
||||
settings:
|
||||
# - key: quiet
|
||||
# description: suppress most output information
|
||||
- key: verbose
|
||||
description: display verbose logging information
|
||||
- key: debug
|
||||
description: display debug logging information
|
||||
_autorest-1:
|
||||
categoryFriendlyName: Manage Installation
|
||||
settings:
|
||||
- key: info # list-installed
|
||||
description: display information about the installed version of autorest and its extensions
|
||||
- key: list-available
|
||||
description: display available AutoRest versions
|
||||
- key: reset
|
||||
description: removes all autorest extensions and downloads the latest version of the autorest-core extension
|
||||
- key: preview
|
||||
description: enables using autorest extensions that are not yet released
|
||||
- key: latest
|
||||
description: installs the latest autorest-core extension
|
||||
- key: force
|
||||
description: force the re-installation of the autorest-core extension and frameworks
|
||||
- key: version
|
||||
description: use the specified version of the autorest-core extension
|
||||
type: string
|
||||
- key: use
|
||||
description: "Specify an extension to load and use. Format: `--use=<packagename>[@<version>]` (e.g. `--use=@autorest/modelerfour@~4.19.0`)"
|
||||
type: string
|
||||
_autorest-core-0:
|
||||
categoryFriendlyName: Core Settings and Switches
|
||||
settings:
|
||||
- key: help
|
||||
description: display help (combine with flags like --csharp to get further details about specific functionality)
|
||||
- key: input-file
|
||||
type: string | string[]
|
||||
description: OpenAPI file to use as input (use this setting repeatedly to pass multiple files at once)
|
||||
- key: output-folder
|
||||
type: string
|
||||
description: 'target folder for generated artifacts; default: "<base folder>/generated"'
|
||||
- key: clear-output-folder
|
||||
description: clear the output folder before writing generated artifacts to disk (use with extreme caution!)
|
||||
- key: base-folder
|
||||
type: string
|
||||
description: "path to resolve relative paths (input/output files/folders) against; default: directory of configuration file, current directory otherwise"
|
||||
- key: eol
|
||||
type: '"default" | "lf" | "crlf"'
|
||||
description: "Configure the line endings for generated files. Options: `default`, `lf`, `crlf`"
|
||||
- key: message-format
|
||||
type: '"regular" | "json"'
|
||||
description: 'format of messages (e.g. from OpenAPI validation); default: "regular"'
|
||||
- key: github-auth-token
|
||||
type: string
|
||||
description: OAuth token to use when pointing AutoRest at files living in a private GitHub repository
|
||||
- key: azure-arm
|
||||
description: Generate code in Azure flavor.
|
||||
type: boolean
|
||||
- key: head-as-boolean
|
||||
description: When `true`, HEAD calls to non-existent resources (404) will not raise an error. Instead, if the resource exists, we return `true`, else `false`. Forced to be `true` if `--azure-arm` is set, otherwise defaults to `false`.
|
||||
type: boolean
|
||||
- key: header-text
|
||||
description: Text to include as a header comment in generated files (magic strings:MICROSOFT_MIT, MICROSOFT_APACHE, MICROSOFT_MIT_NO_VERSION, MICROSOFT_APACHE_NO_VERSION, MICROSOFT_MIT_NO_CODEGEN)
|
||||
type: string
|
||||
- key: openapi-type
|
||||
description: 'Open API Type: "arm" or "data-plane"'
|
||||
type: string
|
||||
- key: max-memory-size
|
||||
type: string
|
||||
description: Increases the maximum memory size in MB used by Node.js when running AutoRest (translates to the Node.js parameter --max-old-space-size)
|
||||
- key: output-converted-oai3
|
||||
type: string
|
||||
description: If enabled and the input-files are `swager 2.0` this will output the resulting OpenAPI3.0 converted files to the `output-folder`
|
||||
- key: title
|
||||
type: string
|
||||
description: Override the service client's name listed in the swagger under `title`.
|
||||
- key: override-client-name
|
||||
type: string
|
||||
description: Name to use for the generated client type. By default, uses the value of the 'Title' field from the input files
|
||||
|
||||
_autorest-core-1:
|
||||
categoryFriendlyName: Core Functionality
|
||||
description: "> While AutoRest can be extended arbitrarily by 3rd parties (say, with a custom generator),\n> we officially support and maintain the following functionality.\n> More specific help is shown when combining the following switches with `--help` ."
|
||||
settings:
|
||||
- key: csharp
|
||||
description: generate C# client code
|
||||
- key: go
|
||||
description: generate Go client code
|
||||
- key: java
|
||||
description: generate Java client code
|
||||
- key: python
|
||||
description: generate Python client code
|
||||
- key: az
|
||||
description: generate Azure CLI code
|
||||
- key: nodejs
|
||||
description: generate NodeJS client code
|
||||
- key: typescript
|
||||
description: generate TypeScript client code
|
||||
- key: ruby
|
||||
description: generate Ruby client code
|
||||
- key: php
|
||||
description: generate PHP client code
|
||||
- key: azureresourceschema
|
||||
description: generate Azure resource schemas
|
||||
- key: model-validator
|
||||
description: validates an OpenAPI document against linked examples (see https://github.com/Azure/azure-rest-api-specs/search?q=x-ms-examples )
|
||||
# - key: semantic-validator
|
||||
# description: validates an OpenAPI document semantically
|
||||
- key: azure-validator
|
||||
description: validates an OpenAPI document against guidelines to improve quality (and optionally Azure guidelines)
|
||||
```
|
||||
|
||||
Note: We don't load anything if `--help` is specified.
|
||||
|
|
|
@ -2,84 +2,326 @@ import { AutorestNormalizedConfiguration } from "../autorest-normalized-configur
|
|||
import { ConfigurationSchemaProcessor } from "./processor";
|
||||
import { RawConfiguration } from "./types";
|
||||
|
||||
export const AUTOREST_CONFIGURATION_CATEGORIES = {
|
||||
logging: {
|
||||
name: "Logging",
|
||||
},
|
||||
installation: {
|
||||
name: "Manage installation",
|
||||
},
|
||||
core: {
|
||||
name: "Core Settings",
|
||||
},
|
||||
feature: {
|
||||
name: "Feature flags",
|
||||
},
|
||||
extensions: {
|
||||
name: "Generators and extensions",
|
||||
description:
|
||||
"> While AutoRest can be extended arbitrarily by 3rd parties (say, with a custom generator),\n> we officially support and maintain the following functionality.\n> More specific help is shown when combining the following switches with `--help` .",
|
||||
},
|
||||
};
|
||||
|
||||
export const SUPPORTED_EXTENSIONS_SCHEMA = {
|
||||
csharp: {
|
||||
type: "boolean",
|
||||
category: "extensions",
|
||||
description: "Generate C# client code",
|
||||
},
|
||||
go: {
|
||||
type: "boolean",
|
||||
category: "extensions",
|
||||
description: "Generate Go client code",
|
||||
},
|
||||
java: {
|
||||
type: "boolean",
|
||||
category: "extensions",
|
||||
description: "Generate Java client code",
|
||||
},
|
||||
python: {
|
||||
type: "boolean",
|
||||
category: "extensions",
|
||||
description: "Generate Python client code",
|
||||
},
|
||||
az: {
|
||||
type: "boolean",
|
||||
category: "extensions",
|
||||
description: "Generate Az cli code",
|
||||
},
|
||||
typescript: {
|
||||
type: "boolean",
|
||||
category: "extensions",
|
||||
description: "Generate TypeScript client code",
|
||||
},
|
||||
azureresourceschema: {
|
||||
type: "boolean",
|
||||
category: "extensions",
|
||||
description: "Generate Azurer resource schemas",
|
||||
},
|
||||
"model-validator": {
|
||||
type: "boolean",
|
||||
category: "extensions",
|
||||
description:
|
||||
"Validates an OpenAPI document against linked examples (see https://github.com/Azure/azure-rest-api-specs/search?q=x-ms-examples ",
|
||||
},
|
||||
"azure-validator": {
|
||||
type: "boolean",
|
||||
category: "extensions",
|
||||
description:
|
||||
"Validates an OpenAPI document against guidelines to improve quality (and optionally Azure guidelines)",
|
||||
},
|
||||
} as const;
|
||||
|
||||
// Switch next 2 lines to have autocomplete when writting configuration. Make sure to revert otherwise it lose the detailed typing for each option.
|
||||
// export const AUTOREST_CONFIGURATION_SCHEMA : RootConfigurationSchema<keyof typeof AUTOREST_CONFIGURATION_CATEGORIES> = {
|
||||
export const AUTOREST_CONFIGURATION_SCHEMA = {
|
||||
"allow-no-input": { type: "boolean" },
|
||||
"input-file": { type: "string", array: true },
|
||||
"exclude-file": { type: "string", array: true },
|
||||
"base-folder": { type: "string" },
|
||||
directive: {
|
||||
array: true,
|
||||
type: {
|
||||
from: { type: "string", array: true },
|
||||
where: { type: "string", array: true },
|
||||
reason: { type: "string" },
|
||||
suppress: { type: "string", array: true, deprecated: true },
|
||||
set: { type: "string", array: true },
|
||||
transform: { type: "string", array: true },
|
||||
"text-transform": { type: "string", array: true },
|
||||
test: { type: "string", array: true },
|
||||
debug: {
|
||||
type: "boolean",
|
||||
description:
|
||||
"Debug this directive. When set to true autorest will log additional information regarding that directive.",
|
||||
},
|
||||
},
|
||||
},
|
||||
"declare-directive": {
|
||||
dictionary: true,
|
||||
/**
|
||||
* Verbosity category
|
||||
*/
|
||||
verbose: { type: "boolean", category: "logging", description: "Display verbose logging information" },
|
||||
debug: { type: "boolean", category: "logging", description: "Display debug logging information" },
|
||||
level: {
|
||||
type: "string",
|
||||
category: "logging",
|
||||
enum: ["debug", "verbose", "information", "warning", "error", "fatal"],
|
||||
description: "Set logging level",
|
||||
},
|
||||
"message-format": {
|
||||
type: "string",
|
||||
category: "logging",
|
||||
description: "Format of logging messages",
|
||||
enum: ["json", "regular"],
|
||||
},
|
||||
|
||||
/**
|
||||
* Manage installation category
|
||||
*/
|
||||
info: {
|
||||
type: "boolean",
|
||||
category: "installation",
|
||||
description: "Display information about the installed version of autorest and its extensions",
|
||||
},
|
||||
"list-available": {
|
||||
type: "boolean",
|
||||
category: "installation",
|
||||
description: "Display available AutoRest versions",
|
||||
},
|
||||
reset: {
|
||||
type: "boolean",
|
||||
category: "installation",
|
||||
description: "Removes all autorest extensions and downloads the latest version of the autorest-core extension",
|
||||
},
|
||||
preview: {
|
||||
type: "boolean",
|
||||
category: "installation",
|
||||
description: "Enables using autorest extensions that are not yet released",
|
||||
},
|
||||
latest: {
|
||||
type: "boolean",
|
||||
category: "installation",
|
||||
description: "Install the latest autorest-core extension",
|
||||
},
|
||||
force: {
|
||||
type: "boolean",
|
||||
category: "installation",
|
||||
description: "Force the re-installation of the autorest-core extension and frameworks",
|
||||
},
|
||||
version: {
|
||||
type: "string",
|
||||
category: "installation",
|
||||
description: "Use the specified version of the autorest-core extension",
|
||||
},
|
||||
use: {
|
||||
type: "array",
|
||||
category: "installation",
|
||||
items: { type: "string" },
|
||||
description:
|
||||
"Specify an extension to load and use. Format: --use=<packagename>[@<version>] (e.g. --use=@autorest/modelerfour@~4.19.0)",
|
||||
},
|
||||
"use-extension": {
|
||||
type: "dictionary",
|
||||
category: "installation",
|
||||
description: `Specify extension to load and use. Format: {"<packageName>": "<version>"}`,
|
||||
items: { type: "string" },
|
||||
},
|
||||
|
||||
/**
|
||||
* Core settings
|
||||
*/
|
||||
help: {
|
||||
type: "boolean",
|
||||
category: "core",
|
||||
description: "Display help (combine with flags like --csharp to get further details about specific functionality)",
|
||||
},
|
||||
memory: { type: "string", category: "core", description: "Configure max memory allowed for autorest process(s)" },
|
||||
"input-file": {
|
||||
type: "array",
|
||||
category: "core",
|
||||
description: "OpenAPI file to use as input (use this setting repeatedly to pass multiple files at once)",
|
||||
items: { type: "string" },
|
||||
},
|
||||
"output-folder": {
|
||||
type: "string",
|
||||
category: "core",
|
||||
description: `Target folder for generated artifacts; default: "<base folder>/generated"`,
|
||||
},
|
||||
"github-auth-token": {
|
||||
type: "string",
|
||||
category: "core",
|
||||
description: "OAuth token to use when pointing AutoRest at files living in a private GitHub repository",
|
||||
},
|
||||
"azure-arm": {
|
||||
type: "boolean",
|
||||
category: "core",
|
||||
description: "Generate code in Azure flavor",
|
||||
},
|
||||
"header-text": {
|
||||
type: "string",
|
||||
category: "core",
|
||||
description:
|
||||
"Text to include as a header comment in generated files (magic strings:MICROSOFTMIT, MICROSOFT_APACHE, MICROSOFT_MIT_NO_VERSION, MICROSOFT_APACHE_NO_VERSION, MICROSOFT_MIT_NOCODEGEN)",
|
||||
},
|
||||
"openapi-type": {
|
||||
type: "string",
|
||||
category: "core",
|
||||
description: `Open API Type: "arm" or "data-plane"`,
|
||||
},
|
||||
"output-converted-oai3": {
|
||||
type: "boolean",
|
||||
category: "core",
|
||||
description: `If enabled and the input-files are swager 2.0 this will output the resulting OpenAPI3.0 converted files to the output-folder`,
|
||||
},
|
||||
"output-artifact": { type: "string", array: true },
|
||||
require: { type: "string", array: true, description: "Additional configuration files to include." },
|
||||
"try-require": { type: "string", array: true, description: "Additional configuration files to include." },
|
||||
stats: { type: "boolean", description: "Output some statistics about current autorest run." },
|
||||
use: { type: "string", array: true },
|
||||
"use-extension": { type: "string", dictionary: true },
|
||||
profile: { type: "string", array: true },
|
||||
"pass-thru": { type: "string", array: true },
|
||||
eol: {
|
||||
type: "string",
|
||||
category: "core",
|
||||
enum: ["default", "lf", "crlf"],
|
||||
description: "Change the end of line character for generated output.",
|
||||
},
|
||||
title: {
|
||||
type: "string",
|
||||
category: "core",
|
||||
description: "Override the service client's name listed in the swagger under title.",
|
||||
},
|
||||
"override-client-name": {
|
||||
type: "string",
|
||||
category: "core",
|
||||
description:
|
||||
"Name to use for the generated client type. By default, uses the value of the 'Title' field from the input files",
|
||||
},
|
||||
directive: {
|
||||
type: "array",
|
||||
category: "core",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
from: { type: "array", items: { type: "string" } },
|
||||
where: { type: "array", items: { type: "string" } },
|
||||
reason: { type: "string" },
|
||||
suppress: { type: "array", deprecated: true, items: { type: "string" } },
|
||||
set: { type: "array", items: { type: "string" } },
|
||||
transform: { type: "array", items: { type: "string" } },
|
||||
"text-transform": { type: "array", items: { type: "string" } },
|
||||
test: { type: "array", items: { type: "string" } },
|
||||
debug: {
|
||||
type: "boolean",
|
||||
description:
|
||||
"Debug this directive. When set to true autorest will log additional information regarding that directive.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
require: {
|
||||
type: "array",
|
||||
category: "core",
|
||||
description: "Additional configuration file(s) to include.",
|
||||
items: { type: "string" },
|
||||
},
|
||||
"try-require": {
|
||||
type: "array",
|
||||
category: "core",
|
||||
description:
|
||||
"Additional configuration file(s) to try to include. Will not fail if the configuration file doesn't exist.",
|
||||
items: { type: "string", description: "Additional configuration files to include." },
|
||||
},
|
||||
"declare-directive": {
|
||||
type: "dictionary",
|
||||
category: "core",
|
||||
description:
|
||||
"Declare some reusable directives (https://github.com/Azure/autorest/blob/main/packages/libs/configuration/resources/directives.md#how-it-works)",
|
||||
items: { type: "string" },
|
||||
},
|
||||
"output-artifact": {
|
||||
type: "array",
|
||||
category: "core",
|
||||
description: "Additional artifact type to emit to the output-folder",
|
||||
items: { type: "string" },
|
||||
},
|
||||
|
||||
"allow-no-input": { type: "boolean" },
|
||||
"exclude-file": { type: "array", items: { type: "string" } },
|
||||
"base-folder": { type: "string" },
|
||||
|
||||
stats: { type: "boolean", category: "core", description: "Output some statistics about current autorest run." },
|
||||
|
||||
profile: {
|
||||
type: "array",
|
||||
category: "core",
|
||||
description: "Reservered for future use.",
|
||||
items: { type: "string" },
|
||||
},
|
||||
|
||||
"message-format": { type: "string", enum: ["json", "regular"] },
|
||||
verbose: { type: "boolean" },
|
||||
debug: { type: "boolean" },
|
||||
level: { type: "string", enum: ["debug", "verbose", "information", "warning", "error", "fatal"] },
|
||||
suppressions: {
|
||||
array: true,
|
||||
type: {
|
||||
code: { type: "string" },
|
||||
from: { type: "string", array: true },
|
||||
where: { type: "string", array: true },
|
||||
reason: { type: "string" },
|
||||
type: "array",
|
||||
category: "core",
|
||||
description: "List of warning/error code to ignore.",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
code: { type: "string" },
|
||||
from: { type: "array", items: { type: "string" } },
|
||||
where: { type: "array", items: { type: "string" } },
|
||||
reason: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
title: { type: "string" },
|
||||
"github-auth-token": { type: "string" },
|
||||
"output-file": { type: "string" },
|
||||
"output-folder": { type: "string" },
|
||||
force: {
|
||||
type: "boolean",
|
||||
description: "Force updating the version of core even if there is a local version satisfying the requirement.",
|
||||
},
|
||||
memory: { type: "string", description: "Configure max memory allowed for autorest process(s)" },
|
||||
|
||||
/**
|
||||
* Feature flags
|
||||
*/
|
||||
"deduplicate-inline-models": { type: "boolean", category: "feature", description: "Deduplicate inline models" },
|
||||
|
||||
"include-x-ms-examples-original-file": {
|
||||
type: "boolean",
|
||||
category: "feature",
|
||||
description: "Include x-ms-original-file property in x-ms-examples",
|
||||
},
|
||||
|
||||
// Feature flags
|
||||
"deduplicate-inline-models": { type: "boolean" },
|
||||
/**
|
||||
* Ignore.
|
||||
*/
|
||||
"pass-thru": {
|
||||
type: "array",
|
||||
items: { type: "string" },
|
||||
},
|
||||
} as const;
|
||||
|
||||
export type AutorestRawConfiguration = RawConfiguration<typeof AUTOREST_CONFIGURATION_SCHEMA> & {
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
export const autorestConfigurationProcessor = new ConfigurationSchemaProcessor(AUTOREST_CONFIGURATION_SCHEMA);
|
||||
export const AUTOREST_CONFIGURATION_DEFINITION = {
|
||||
categories: AUTOREST_CONFIGURATION_CATEGORIES,
|
||||
schema: AUTOREST_CONFIGURATION_SCHEMA,
|
||||
};
|
||||
export const AUTOREST_CONFIGURATION_DEFINITION_FOR_HELP = {
|
||||
categories: AUTOREST_CONFIGURATION_CATEGORIES,
|
||||
// SUPPORTED_EXTENSIONS_SCHEMA can either be a flag to enable or a scope which cause issue with the validation.
|
||||
schema: { ...AUTOREST_CONFIGURATION_SCHEMA, ...SUPPORTED_EXTENSIONS_SCHEMA },
|
||||
};
|
||||
|
||||
export const autorestConfigurationProcessor = new ConfigurationSchemaProcessor(AUTOREST_CONFIGURATION_DEFINITION);
|
||||
|
||||
export const AUTOREST_INITIAL_CONFIG: AutorestNormalizedConfiguration =
|
||||
autorestConfigurationProcessor.getInitialConfig();
|
||||
|
|
|
@ -17,19 +17,25 @@ const TestSchema = {
|
|||
enum: ["one", "two", "three"],
|
||||
},
|
||||
numberArray: {
|
||||
type: "number",
|
||||
array: true,
|
||||
type: "array",
|
||||
items: { type: "number" },
|
||||
},
|
||||
numberDict: {
|
||||
type: "number",
|
||||
dictionary: true,
|
||||
type: "dictionary",
|
||||
items: { type: "number" },
|
||||
},
|
||||
nested: {
|
||||
nestedNumber: { type: "number" },
|
||||
type: "object",
|
||||
properties: {
|
||||
nestedNumber: { type: "number" },
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
const processor = new ConfigurationSchemaProcessor(TestSchema);
|
||||
const processor = new ConfigurationSchemaProcessor({
|
||||
schema: TestSchema,
|
||||
categories: { default: { name: "default" } },
|
||||
});
|
||||
|
||||
const logger = createMockLogger();
|
||||
|
||||
|
|
|
@ -4,13 +4,16 @@ import { flatMap } from "lodash";
|
|||
import {
|
||||
ConfigurationProperty,
|
||||
ConfigurationSchema,
|
||||
InferredPrimitiveType,
|
||||
InferredProcessedPrimitiveType,
|
||||
InferredProcessedType,
|
||||
InferredRawPrimitiveType,
|
||||
InferredRawType,
|
||||
ProcessedConfiguration,
|
||||
RawConfiguration,
|
||||
ConfigurationSchemaDefinition,
|
||||
PrimitiveConfigurationProperty,
|
||||
} from "./types";
|
||||
import { RootConfigurationSchema } from ".";
|
||||
|
||||
export enum ProcessingErrorCode {
|
||||
UnknownProperty = "unknownProperty",
|
||||
|
@ -37,21 +40,21 @@ export interface ProcessConfigurationOptions {
|
|||
logger: AutorestLogger;
|
||||
}
|
||||
|
||||
export class ConfigurationSchemaProcessor<S extends ConfigurationSchema> {
|
||||
public constructor(private schema: S) {}
|
||||
export class ConfigurationSchemaProcessor<C extends string, S extends RootConfigurationSchema<C>> {
|
||||
public constructor(private def: ConfigurationSchemaDefinition<C, S>) {}
|
||||
|
||||
public processConfiguration(
|
||||
configuration: RawConfiguration<S>,
|
||||
options: ProcessConfigurationOptions,
|
||||
): Result<ProcessedConfiguration<S>> {
|
||||
return processConfiguration(this.schema, [], configuration, options);
|
||||
return processConfiguration(this.def.schema, [], configuration, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an empty config with all array property to be able to work with @see mergeConfigurations.
|
||||
*/
|
||||
public getInitialConfig(): Readonly<ProcessedConfiguration<S>> {
|
||||
return getInitialConfig(this.schema);
|
||||
return getInitialConfig(this.def.schema);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,10 +62,10 @@ function getInitialConfig<S extends ConfigurationSchema>(schema: S): Readonly<Pr
|
|||
const config: any = {};
|
||||
|
||||
for (const [key, value] of Object.entries(schema)) {
|
||||
if (value.array) {
|
||||
if (value.type === "array") {
|
||||
config[key] = [];
|
||||
} else if (!("type" in value)) {
|
||||
const nested = getInitialConfig(value);
|
||||
} else if (value.type === "object") {
|
||||
const nested = getInitialConfig(value.properties);
|
||||
if (Object.keys(nested).length > 0) {
|
||||
config[key] = nested;
|
||||
}
|
||||
|
@ -93,7 +96,7 @@ function processConfiguration<S extends ConfigurationSchema>(
|
|||
continue;
|
||||
}
|
||||
|
||||
const propertyResult = processProperty(propertySchema as any, propertyPath, value, options);
|
||||
const propertyResult = processProperty(propertySchema, propertyPath, value, options);
|
||||
if (isErrorResult(propertyResult)) {
|
||||
errors.push(...propertyResult.errors);
|
||||
} else {
|
||||
|
@ -116,13 +119,13 @@ function processProperty<T extends ConfigurationProperty>(
|
|||
value: InferredRawType<T>,
|
||||
options: ProcessConfigurationOptions,
|
||||
): Result<InferredProcessedType<T>> {
|
||||
if (schema.array) {
|
||||
if (schema.type === "array") {
|
||||
if (value === undefined) {
|
||||
return { value: [] as any };
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
const result = value.map((x, i) => processPrimitiveProperty(schema, [...path, i.toString()], x, options));
|
||||
const result = value.map((x, i) => processPrimitiveProperty(schema.items, [...path, i.toString()], x, options));
|
||||
const values = result.filter(isNotErrorResult).map((x) => x.value);
|
||||
const errors = flatMap(result.filter(isErrorResult).map((x) => x.errors));
|
||||
if (errors.length > 0) {
|
||||
|
@ -130,14 +133,14 @@ function processProperty<T extends ConfigurationProperty>(
|
|||
}
|
||||
return { value: values as any };
|
||||
} else {
|
||||
const result = processPrimitiveProperty(schema, path, value as InferredRawPrimitiveType<T>, options);
|
||||
const result = processPrimitiveProperty(schema.items, path, value, options);
|
||||
if (isErrorResult(result)) {
|
||||
return result;
|
||||
} else {
|
||||
return { value: [result.value] as any };
|
||||
}
|
||||
}
|
||||
} else if (schema.dictionary) {
|
||||
} else if (schema.type === "dictionary") {
|
||||
if (value === undefined) {
|
||||
return { value: {} as any };
|
||||
}
|
||||
|
@ -148,7 +151,7 @@ function processProperty<T extends ConfigurationProperty>(
|
|||
const result: any = {};
|
||||
|
||||
for (const [key, dictValue] of Object.entries<any>(value ?? {})) {
|
||||
const prop = processPrimitiveProperty(schema, [...path, key], dictValue, options);
|
||||
const prop = processProperty(schema.items, [...path, key], dictValue, options);
|
||||
if ("errors" in prop) {
|
||||
return { errors: prop.errors };
|
||||
}
|
||||
|
@ -157,62 +160,60 @@ function processProperty<T extends ConfigurationProperty>(
|
|||
|
||||
return { value: result };
|
||||
} else {
|
||||
return processPrimitiveProperty(schema, path, value as InferredRawPrimitiveType<T>, options) as any;
|
||||
return processPrimitiveProperty(schema, path, value as any, options) as any;
|
||||
}
|
||||
}
|
||||
|
||||
function processPrimitiveProperty<T extends ConfigurationProperty>(
|
||||
function processPrimitiveProperty<T extends PrimitiveConfigurationProperty>(
|
||||
schema: T,
|
||||
path: string[],
|
||||
value: InferredRawPrimitiveType<T>,
|
||||
options: ProcessConfigurationOptions,
|
||||
): Result<InferredPrimitiveType<T>> {
|
||||
if (schema.type === "number") {
|
||||
if (typeof value !== "number") {
|
||||
return {
|
||||
errors: [createInvalidTypeError(value, "number", path)],
|
||||
};
|
||||
}
|
||||
return { value } as any;
|
||||
}
|
||||
|
||||
if (schema.type === "boolean") {
|
||||
if (typeof value !== "boolean") {
|
||||
return {
|
||||
errors: [createInvalidTypeError(value, "boolean", path)],
|
||||
};
|
||||
}
|
||||
return { value } as any;
|
||||
}
|
||||
|
||||
if (schema.type === "string") {
|
||||
if (typeof value !== "string") {
|
||||
return {
|
||||
errors: [createInvalidTypeError(value, "string", path)],
|
||||
};
|
||||
}
|
||||
|
||||
if (schema.enum) {
|
||||
if (!schema.enum.includes(value)) {
|
||||
const serializedValue = inspect(value);
|
||||
): Result<InferredProcessedPrimitiveType<T>> {
|
||||
switch (schema.type) {
|
||||
case "object":
|
||||
return processConfiguration<any>(schema.properties, path, value as any, options) as any;
|
||||
case "number": {
|
||||
if (typeof value !== "number") {
|
||||
return {
|
||||
errors: [
|
||||
{
|
||||
code: ProcessingErrorCode.InvalidType,
|
||||
message: `Expected a value to be in [${schema.enum
|
||||
.map((x) => `'${x}'`)
|
||||
.join(",")}] but got ${serializedValue}`,
|
||||
path,
|
||||
},
|
||||
],
|
||||
errors: [createInvalidTypeError(value, "number", path)],
|
||||
};
|
||||
}
|
||||
return { value } as any;
|
||||
}
|
||||
return { value } as any;
|
||||
}
|
||||
case "boolean":
|
||||
if (typeof value !== "boolean") {
|
||||
return {
|
||||
errors: [createInvalidTypeError(value, "boolean", path)],
|
||||
};
|
||||
}
|
||||
return { value } as any;
|
||||
case "string": {
|
||||
if (typeof value !== "string") {
|
||||
return {
|
||||
errors: [createInvalidTypeError(value, "string", path)],
|
||||
};
|
||||
}
|
||||
|
||||
// Means this is a nested configuration schema
|
||||
return processConfiguration<any>(schema, path, value as any, options) as any;
|
||||
if (schema.enum) {
|
||||
if (!schema.enum.includes(value)) {
|
||||
const serializedValue = inspect(value);
|
||||
return {
|
||||
errors: [
|
||||
{
|
||||
code: ProcessingErrorCode.InvalidType,
|
||||
message: `Expected a value to be in [${schema.enum
|
||||
.map((x) => `'${x}'`)
|
||||
.join(",")}] but got ${serializedValue}`,
|
||||
path,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
return { value } as any;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createInvalidTypeError(value: unknown, expectedType: string, path: string[]) {
|
||||
|
|
|
@ -1,85 +1,137 @@
|
|||
export type ConfigurationSchema = {
|
||||
readonly [key: string]: ConfigurationSchema | ConfigurationProperty;
|
||||
export type ConfigurationSchemaDefinition<C extends string, S extends RootConfigurationSchema<C>> = {
|
||||
readonly categories: {
|
||||
[key in C]: CategoryDefinition;
|
||||
};
|
||||
readonly schema: S;
|
||||
};
|
||||
|
||||
export type ConfigurationPropertyType = "string" | "number" | "boolean" | ConfigurationSchema;
|
||||
export type RootConfigurationSchema<C extends string> = {
|
||||
readonly [key: string]: RootConfigurationProperty<C>;
|
||||
};
|
||||
|
||||
export type ConfigurationProperty = {
|
||||
export type ConfigurationSchema = {
|
||||
readonly [key: string]: ConfigurationProperty;
|
||||
};
|
||||
|
||||
export type ConfigurationPropertyType = "string" | "number" | "boolean" | "array" | "dictionary" | "object";
|
||||
|
||||
export type CategoryDefinition = {
|
||||
name: string;
|
||||
description?: string;
|
||||
};
|
||||
|
||||
export type RootConfigurationProperty<C extends string> = ConfigurationProperty & {
|
||||
/**
|
||||
* Type.
|
||||
* Category for the configuration. For documentation purposes.
|
||||
*/
|
||||
readonly category?: C;
|
||||
};
|
||||
|
||||
export type ConfigurationPropertyBase = {
|
||||
readonly type: ConfigurationPropertyType;
|
||||
|
||||
/**
|
||||
* If this is an array of the type.
|
||||
*/
|
||||
readonly array?: boolean;
|
||||
|
||||
/**
|
||||
* If this is a map/dictionary of the type.
|
||||
*/
|
||||
readonly dictionary?: boolean;
|
||||
|
||||
/**
|
||||
* List of supported values. Only supported for type: string.
|
||||
*/
|
||||
readonly enum?: readonly string[];
|
||||
|
||||
/**
|
||||
* Description for the flag.
|
||||
* Description for the configuration.
|
||||
*/
|
||||
readonly description?: string;
|
||||
|
||||
/**
|
||||
* Mark this config flag as deprecated.It will log a warning when used.
|
||||
* Mark this config as deprecated.It will log a warning when used.
|
||||
*/
|
||||
readonly deprecated?: boolean;
|
||||
};
|
||||
|
||||
export type StringConfigurationProperty = ConfigurationPropertyBase & {
|
||||
type: "string";
|
||||
/**
|
||||
* List of supported values. Only supported for type: string.
|
||||
*/
|
||||
readonly enum?: readonly string[];
|
||||
};
|
||||
|
||||
export type NumberConfigurationProperty = ConfigurationPropertyBase & {
|
||||
type: "number";
|
||||
};
|
||||
|
||||
export type BooleanConfigurationProperty = ConfigurationPropertyBase & {
|
||||
type: "boolean";
|
||||
};
|
||||
|
||||
export type ArrayConfigurationProperty = ConfigurationPropertyBase & {
|
||||
type: "array";
|
||||
items: PrimitiveConfigurationProperty;
|
||||
};
|
||||
|
||||
export type DictionaryConfigurationProperty = ConfigurationPropertyBase & {
|
||||
type: "dictionary";
|
||||
items: PrimitiveConfigurationProperty;
|
||||
};
|
||||
|
||||
export type ObjectConfigurationProperty = ConfigurationPropertyBase & {
|
||||
type: "object";
|
||||
properties: ConfigurationSchema;
|
||||
};
|
||||
|
||||
export type ConfigurationProperty =
|
||||
| PrimitiveConfigurationProperty
|
||||
| ArrayConfigurationProperty
|
||||
| DictionaryConfigurationProperty;
|
||||
|
||||
export type PrimitiveConfigurationProperty =
|
||||
| ObjectConfigurationProperty
|
||||
| StringConfigurationProperty
|
||||
| NumberConfigurationProperty
|
||||
| BooleanConfigurationProperty;
|
||||
|
||||
export type EnumType<T extends ReadonlyArray<string>> = T[number];
|
||||
|
||||
// prettier-ignore
|
||||
export type InferredProcessedType<T> =
|
||||
T extends { array: true } ? InferredPrimitiveType<T>[]
|
||||
: T extends { dictionary: true } ? Record<string, InferredPrimitiveType<T>>
|
||||
: InferredPrimitiveType<T>;
|
||||
export type ProcessedConfiguration<S extends ConfigurationSchema> = {
|
||||
[K in keyof S]: InferredProcessedType<S[K]>;
|
||||
};
|
||||
|
||||
// prettier-ignore
|
||||
export type InferredPrimitiveType<T> =
|
||||
export type InferredProcessedType<T extends ConfigurationProperty> =
|
||||
T extends ArrayConfigurationProperty ? InferredProcessedArrayType<T["items"]>
|
||||
: T extends DictionaryConfigurationProperty ? InferredProcessedDictionaryType<T["items"]>
|
||||
: T extends PrimitiveConfigurationProperty ? InferredProcessedPrimitiveType<T>
|
||||
: never;
|
||||
|
||||
export type InferredProcessedArrayType<T extends PrimitiveConfigurationProperty> = InferredProcessedPrimitiveType<T>[];
|
||||
export type InferredProcessedDictionaryType<T extends PrimitiveConfigurationProperty> = Record<
|
||||
string,
|
||||
InferredProcessedPrimitiveType<T>
|
||||
>;
|
||||
|
||||
// prettier-ignore
|
||||
export type InferredProcessedPrimitiveType<T extends PrimitiveConfigurationProperty> =
|
||||
T extends { type: "string", enum: ReadonlyArray<string> } ? EnumType<T["enum"]>
|
||||
: T extends { type: "string" } ? string
|
||||
: T extends { type: "number" } ? number
|
||||
: T extends { type: "boolean" } ? boolean
|
||||
: T extends { type: ConfigurationSchema } ? ProcessedConfiguration<T["type"]>
|
||||
: T extends ObjectConfigurationProperty ? ProcessedConfiguration<T["properties"]>
|
||||
: never;
|
||||
|
||||
// prettier-ignore
|
||||
export type InferredRawType<T> =
|
||||
T extends { array: true } ? NonNullable<InferredRawPrimitiveType<T>>[] | InferredRawPrimitiveType<T>
|
||||
: T extends { dictionary: true } ? Record<string, InferredRawPrimitiveType<T>>
|
||||
: InferredRawPrimitiveType<T>;
|
||||
export type InferredRawType<T extends ConfigurationProperty> =
|
||||
T extends ArrayConfigurationProperty ? InferredRawArrayType<T["items"]>
|
||||
: T extends DictionaryConfigurationProperty ? InferredRawDictionaryType<T["items"]>
|
||||
: T extends PrimitiveConfigurationProperty ? InferredRawPrimitiveType<T>
|
||||
: never;
|
||||
|
||||
export type InferredRawArrayType<T extends PrimitiveConfigurationProperty> =
|
||||
| NonNullable<InferredRawPrimitiveType<T>>[]
|
||||
| InferredRawPrimitiveType<T>;
|
||||
export type InferredRawDictionaryType<T extends ConfigurationProperty> = Record<string, InferredRawType<T>>;
|
||||
|
||||
// prettier-ignore
|
||||
export type InferredRawPrimitiveType<T> =
|
||||
export type InferredRawPrimitiveType<T extends PrimitiveConfigurationProperty> =
|
||||
T extends { type: "string", enum: ReadonlyArray<string> } ? EnumType<T["enum"]>
|
||||
: T extends { type: "string" } ? string | undefined
|
||||
: T extends { type: "number" } ? number | undefined
|
||||
: T extends { type: "boolean" } ? boolean | undefined
|
||||
: T extends { type: ConfigurationSchema } ? RawConfiguration<T["type"]> | undefined
|
||||
: T extends { type: "string" } ? string | undefined
|
||||
: T extends { type: "number" } ? number | undefined
|
||||
: T extends { type: "boolean" } ? boolean | undefined
|
||||
: T extends ObjectConfigurationProperty ? RawConfiguration<T["properties"]> | undefined
|
||||
: never;
|
||||
|
||||
export type ProcessedConfiguration<S extends ConfigurationSchema> = {
|
||||
[K in keyof S]: S[K] extends ConfigurationProperty
|
||||
? InferredProcessedType<S[K]>
|
||||
: S[K] extends ConfigurationSchema
|
||||
? ProcessedConfiguration<S[K]>
|
||||
: never;
|
||||
};
|
||||
|
||||
export type RawConfiguration<S extends ConfigurationSchema> = {
|
||||
[K in keyof S]?: S[K] extends ConfigurationProperty
|
||||
? InferredRawType<S[K]>
|
||||
: S[K] extends ConfigurationSchema
|
||||
? RawConfiguration<S[K]>
|
||||
: never;
|
||||
[K in keyof S]?: InferredRawType<S[K]>;
|
||||
};
|
||||
|
|
Загрузка…
Ссылка в новой задаче