зеркало из https://github.com/Azure/autorest.git
Refactor/Cleanup extension base package (#4370)
This commit is contained in:
Родитель
cfa8997aa1
Коммит
1447f04f75
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"changes": [
|
||||
{
|
||||
"packageName": "@autorest/cadl",
|
||||
"comment": "**Uptake** breaking change from e",
|
||||
"type": "minor"
|
||||
}
|
||||
],
|
||||
"packageName": "@autorest/cadl",
|
||||
"email": "tiguerin@microsoft.com"
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"changes": [
|
||||
{
|
||||
"packageName": "@autorest/extension-base",
|
||||
"comment": "**BREAKING** Refactor host, session. Rename PascalCase methods, cleanup",
|
||||
"type": "minor"
|
||||
},
|
||||
{
|
||||
"packageName": "@autorest/extension-base",
|
||||
"comment": "**Added** testing functionality for extension via @autorest/extension-base/testing import",
|
||||
"type": "minor"
|
||||
}
|
||||
],
|
||||
"packageName": "@autorest/extension-base",
|
||||
"email": "tiguerin@microsoft.com"
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"changes": [
|
||||
{
|
||||
"packageName": "@autorest/modelerfour",
|
||||
"comment": "**Uptake** breaking change from e",
|
||||
"type": "minor"
|
||||
}
|
||||
],
|
||||
"packageName": "@autorest/modelerfour",
|
||||
"email": "tiguerin@microsoft.com"
|
||||
}
|
|
@ -1,15 +1,16 @@
|
|||
import { fileURLToPath } from "url";
|
||||
import { Channel, Host } from "@autorest/extension-base";
|
||||
import { AutorestExtensionHost, Channel } from "@autorest/extension-base";
|
||||
import { compileAdl } from "./cadl-compiler.js";
|
||||
|
||||
export async function setupAdlCompilerPlugin(host: Host) {
|
||||
const inputFiles = await host.GetValue("inputFileUris");
|
||||
const entrypoint = inputFiles[0];
|
||||
export async function setupAdlCompilerPlugin(host: AutorestExtensionHost) {
|
||||
const inputFiles = await host.getValue<string[]>("inputFileUris");
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const entrypoint = inputFiles![0];
|
||||
const result = await compileAdl(fileURLToPath(entrypoint));
|
||||
|
||||
if ("diagnostics" in result) {
|
||||
for (const diagnostic of result.diagnostics) {
|
||||
host.Message({
|
||||
host.message({
|
||||
Channel: Channel.Error,
|
||||
Text: diagnostic.message,
|
||||
Source:
|
||||
|
@ -28,7 +29,7 @@ export async function setupAdlCompilerPlugin(host: Host) {
|
|||
}
|
||||
|
||||
for (const [name, content] of Object.entries(result.compiledFiles)) {
|
||||
host.WriteFile(name, content, undefined, "swagger-document");
|
||||
host.writeFile({ filename: name, content, artifactType: "swagger-document" });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,13 +2,13 @@ import { AutoRestExtension } from "@autorest/extension-base";
|
|||
import { setupAdlCompilerPlugin } from "./cadl-compiler-plugin.js";
|
||||
|
||||
export async function initializePlugins(pluginHost: AutoRestExtension) {
|
||||
pluginHost.Add("adl-compiler", setupAdlCompilerPlugin);
|
||||
pluginHost.add("adl-compiler", setupAdlCompilerPlugin);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const pluginHost = new AutoRestExtension();
|
||||
await initializePlugins(pluginHost);
|
||||
await pluginHost.Run();
|
||||
await pluginHost.run();
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
|
|
|
@ -4,15 +4,15 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { codeModelSchema, CodeModel } from "@autorest/codemodel";
|
||||
import { Host, startSession } from "@autorest/extension-base";
|
||||
import { AutorestExtensionHost, startSession } from "@autorest/extension-base";
|
||||
import { serialize } from "@azure-tools/codegen";
|
||||
import { Checker } from "./checker";
|
||||
|
||||
export async function processRequest(host: Host) {
|
||||
const debug = (await host.GetValue("debug")) || false;
|
||||
export async function processRequest(host: AutorestExtensionHost) {
|
||||
const debug = (await host.getValue("debug")) || false;
|
||||
|
||||
try {
|
||||
const session = await startSession<CodeModel>(host, {}, codeModelSchema);
|
||||
const session = await startSession<CodeModel>(host, codeModelSchema);
|
||||
const options = <any>await session.getValue("modelerfour", {});
|
||||
|
||||
// process
|
||||
|
@ -28,11 +28,19 @@ export async function processRequest(host: Host) {
|
|||
|
||||
// output the model to the pipeline
|
||||
if (options["emit-yaml-tags"] !== false) {
|
||||
host.WriteFile("code-model-v4.yaml", serialize(result, codeModelSchema), undefined, "code-model-v4");
|
||||
host.writeFile({
|
||||
filename: "code-model-v4.yaml",
|
||||
content: serialize(result, codeModelSchema),
|
||||
artifactType: "code-model-v4",
|
||||
});
|
||||
}
|
||||
|
||||
if (options["emit-yaml-tags"] !== true) {
|
||||
host.WriteFile("code-model-v4-no-tags.yaml", serialize(result), undefined, "code-model-v4-no-tags");
|
||||
host.writeFile({
|
||||
filename: "code-model-v4-no-tags.yaml",
|
||||
content: serialize(result),
|
||||
artifactType: "code-model-v4-no-tags",
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (debug) {
|
||||
|
|
|
@ -4,15 +4,15 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { codeModelSchema, CodeModel } from "@autorest/codemodel";
|
||||
import { Host, startSession } from "@autorest/extension-base";
|
||||
import { AutorestExtensionHost, startSession } from "@autorest/extension-base";
|
||||
import { serialize } from "@azure-tools/codegen";
|
||||
import { Example } from "./example";
|
||||
|
||||
export async function processRequest(host: Host) {
|
||||
const debug = (await host.GetValue("debug")) || false;
|
||||
export async function processRequest(host: AutorestExtensionHost) {
|
||||
const debug = (await host.getValue("debug")) || false;
|
||||
|
||||
try {
|
||||
const session = await startSession<CodeModel>(host, {}, codeModelSchema);
|
||||
const session = await startSession<CodeModel>(host, codeModelSchema);
|
||||
|
||||
// process
|
||||
const plugin = new Example(session);
|
||||
|
@ -21,8 +21,16 @@ export async function processRequest(host: Host) {
|
|||
const result = plugin.process();
|
||||
|
||||
// output the model to the pipeline
|
||||
host.WriteFile("code-model-v4.yaml", serialize(result, codeModelSchema), undefined, "code-model-v4");
|
||||
host.WriteFile("code-model-v4-no-tags.yaml", serialize(result), undefined, "code-model-v4-no-tags");
|
||||
host.writeFile({
|
||||
filename: "code-model-v4.yaml",
|
||||
content: serialize(result, codeModelSchema),
|
||||
artifactType: "code-model-v4",
|
||||
});
|
||||
host.writeFile({
|
||||
filename: "code-model-v4-no-tags.yaml",
|
||||
content: serialize(result),
|
||||
artifactType: "code-model-v4-no-tags",
|
||||
});
|
||||
} catch (error: any) {
|
||||
if (debug) {
|
||||
// eslint-disable-next-line no-console
|
||||
|
|
|
@ -4,15 +4,15 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { codeModelSchema, CodeModel } from "@autorest/codemodel";
|
||||
import { Host, startSession } from "@autorest/extension-base";
|
||||
import { AutorestExtensionHost, startSession } from "@autorest/extension-base";
|
||||
import { serialize } from "@azure-tools/codegen";
|
||||
import { Flattener } from "./flattener";
|
||||
|
||||
export async function processRequest(host: Host) {
|
||||
const debug = (await host.GetValue("debug")) || false;
|
||||
export async function processRequest(host: AutorestExtensionHost) {
|
||||
const debug = (await host.getValue("debug")) || false;
|
||||
|
||||
try {
|
||||
const session = await startSession<CodeModel>(host, {}, codeModelSchema);
|
||||
const session = await startSession<CodeModel>(host, codeModelSchema);
|
||||
const options = <any>await session.getValue("modelerfour", {});
|
||||
|
||||
// process
|
||||
|
@ -23,11 +23,19 @@ export async function processRequest(host: Host) {
|
|||
|
||||
// output the model to the pipeline
|
||||
if (options["emit-yaml-tags"] !== false) {
|
||||
host.WriteFile("code-model-v4.yaml", serialize(result, codeModelSchema), undefined, "code-model-v4");
|
||||
host.writeFile({
|
||||
filename: "code-model-v4.yaml",
|
||||
content: serialize(result, codeModelSchema),
|
||||
artifactType: "code-model-v4",
|
||||
});
|
||||
}
|
||||
|
||||
if (options["emit-yaml-tags"] !== true) {
|
||||
host.WriteFile("code-model-v4-no-tags.yaml", serialize(result), undefined, "code-model-v4-no-tags");
|
||||
host.writeFile({
|
||||
filename: "code-model-v4-no-tags.yaml",
|
||||
content: serialize(result),
|
||||
artifactType: "code-model-v4-no-tags",
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (debug) {
|
||||
|
|
|
@ -4,15 +4,15 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { codeModelSchema, CodeModel } from "@autorest/codemodel";
|
||||
import { Host, startSession } from "@autorest/extension-base";
|
||||
import { AutorestExtensionHost, startSession } from "@autorest/extension-base";
|
||||
import { serialize } from "@azure-tools/codegen";
|
||||
import { Grouper } from "./grouper";
|
||||
|
||||
export async function processRequest(host: Host) {
|
||||
const debug = (await host.GetValue("debug")) || false;
|
||||
export async function processRequest(host: AutorestExtensionHost) {
|
||||
const debug = (await host.getValue("debug")) || false;
|
||||
|
||||
try {
|
||||
const session = await startSession<CodeModel>(host, {}, codeModelSchema);
|
||||
const session = await startSession<CodeModel>(host, codeModelSchema);
|
||||
const options = <any>await session.getValue("modelerfour", {});
|
||||
|
||||
// process
|
||||
|
@ -23,11 +23,19 @@ export async function processRequest(host: Host) {
|
|||
|
||||
// output the model to the pipeline
|
||||
if (options["emit-yaml-tags"] !== false) {
|
||||
host.WriteFile("code-model-v4.yaml", serialize(result, codeModelSchema), undefined, "code-model-v4");
|
||||
host.writeFile({
|
||||
filename: "code-model-v4.yaml",
|
||||
content: serialize(result, codeModelSchema),
|
||||
artifactType: "code-model-v4",
|
||||
});
|
||||
}
|
||||
|
||||
if (options["emit-yaml-tags"] !== true) {
|
||||
host.WriteFile("code-model-v4-no-tags.yaml", serialize(result), undefined, "code-model-v4-no-tags");
|
||||
host.writeFile({
|
||||
filename: "code-model-v4-no-tags.yaml",
|
||||
content: serialize(result),
|
||||
artifactType: "code-model-v4-no-tags",
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (debug) {
|
||||
|
|
|
@ -8,18 +8,18 @@ import { processRequest as preNamer } from "./prenamer/plugin-prenamer";
|
|||
import { processRequest as prechecker } from "./quality-precheck/prechecker";
|
||||
|
||||
export async function initializePlugins(pluginHost: AutoRestExtension) {
|
||||
pluginHost.Add("prechecker", prechecker);
|
||||
pluginHost.Add("modelerfour", modelerfour);
|
||||
pluginHost.Add("grouper", grouper);
|
||||
pluginHost.Add("pre-namer", preNamer);
|
||||
pluginHost.Add("flattener", flattener);
|
||||
pluginHost.Add("checker", checker);
|
||||
pluginHost.add("prechecker", prechecker);
|
||||
pluginHost.add("modelerfour", modelerfour);
|
||||
pluginHost.add("grouper", grouper);
|
||||
pluginHost.add("pre-namer", preNamer);
|
||||
pluginHost.add("flattener", flattener);
|
||||
pluginHost.add("checker", checker);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const pluginHost = new AutoRestExtension();
|
||||
await initializePlugins(pluginHost);
|
||||
await pluginHost.Run();
|
||||
await pluginHost.run();
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
|
|
|
@ -1072,7 +1072,7 @@ export class ModelerFour {
|
|||
|
||||
if (isSchemaBinary(schema)) {
|
||||
// handle inconsistency in file format handling.
|
||||
this.session.hint(
|
||||
this.session.warning(
|
||||
`'The schema ${schema?.["x-ms-metadata"]?.name || name} with 'type: ${schema.type}', format: ${
|
||||
schema.format
|
||||
}' will be treated as a binary blob for binary media types.`,
|
||||
|
|
|
@ -3,17 +3,17 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { codeModelSchema, CodeModel } from "@autorest/codemodel";
|
||||
import { Host, startSession } from "@autorest/extension-base";
|
||||
import { deserialize, serialize } from "@azure-tools/codegen";
|
||||
import { codeModelSchema } from "@autorest/codemodel";
|
||||
import { AutorestExtensionHost, startSession } from "@autorest/extension-base";
|
||||
import { serialize } from "@azure-tools/codegen";
|
||||
import * as OpenAPI from "@azure-tools/openapi";
|
||||
import { ModelerFour } from "./modelerfour";
|
||||
|
||||
export async function processRequest(host: Host) {
|
||||
const debug = (await host.GetValue("debug")) || false;
|
||||
export async function processRequest(host: AutorestExtensionHost) {
|
||||
const debug = (await host.getValue("debug")) || false;
|
||||
|
||||
try {
|
||||
const session = await startSession<OpenAPI.Model>(host, undefined, undefined, "prechecked-openapi-document");
|
||||
const session = await startSession<OpenAPI.Model>(host, undefined, "prechecked-openapi-document");
|
||||
const options = <any>await session.getValue("modelerfour", {});
|
||||
|
||||
// process
|
||||
|
@ -29,10 +29,18 @@ export async function processRequest(host: Host) {
|
|||
|
||||
// output the model to the pipeline
|
||||
if (options["emit-yaml-tags"] !== false) {
|
||||
host.WriteFile("code-model-v4.yaml", serialize(codeModel, codeModelSchema), undefined, "code-model-v4");
|
||||
host.writeFile({
|
||||
filename: "code-model-v4.yaml",
|
||||
content: serialize(codeModel, codeModelSchema),
|
||||
artifactType: "code-model-v4",
|
||||
});
|
||||
}
|
||||
if (options["emit-yaml-tags"] !== true) {
|
||||
host.WriteFile("code-model-v4-no-tags.yaml", serialize(codeModel), undefined, "code-model-v4-no-tags");
|
||||
host.writeFile({
|
||||
filename: "code-model-v4-no-tags.yaml",
|
||||
content: serialize(codeModel),
|
||||
artifactType: "code-model-v4-no-tags",
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (debug) {
|
||||
|
|
|
@ -4,15 +4,15 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { codeModelSchema, CodeModel } from "@autorest/codemodel";
|
||||
import { Host, startSession } from "@autorest/extension-base";
|
||||
import { AutorestExtensionHost, startSession } from "@autorest/extension-base";
|
||||
import { serialize } from "@azure-tools/codegen";
|
||||
import { PreNamer } from "./prenamer";
|
||||
|
||||
export async function processRequest(host: Host) {
|
||||
const debug = (await host.GetValue("debug")) || false;
|
||||
export async function processRequest(host: AutorestExtensionHost) {
|
||||
const debug = (await host.getValue("debug")) || false;
|
||||
|
||||
try {
|
||||
const session = await startSession<CodeModel>(host, {}, codeModelSchema);
|
||||
const session = await startSession<CodeModel>(host, codeModelSchema);
|
||||
const options = <any>await session.getValue("modelerfour", {});
|
||||
|
||||
// process
|
||||
|
@ -23,10 +23,18 @@ export async function processRequest(host: Host) {
|
|||
|
||||
// output the model to the pipeline
|
||||
if (options["emit-yaml-tags"] !== false) {
|
||||
host.WriteFile("code-model-v4.yaml", serialize(result, codeModelSchema), undefined, "code-model-v4");
|
||||
host.writeFile({
|
||||
filename: "code-model-v4.yaml",
|
||||
content: serialize(result, codeModelSchema),
|
||||
artifactType: "code-model-v4",
|
||||
});
|
||||
}
|
||||
if (options["emit-yaml-tags"] !== true) {
|
||||
host.WriteFile("code-model-v4-no-tags.yaml", serialize(result), undefined, "code-model-v4-no-tags");
|
||||
host.writeFile({
|
||||
filename: "code-model-v4-no-tags.yaml",
|
||||
content: serialize(result),
|
||||
artifactType: "code-model-v4-no-tags",
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (debug) {
|
||||
|
|
|
@ -23,7 +23,7 @@ export class DuplicateSchemaMerger {
|
|||
break;
|
||||
}
|
||||
}
|
||||
this.session.verbose(`Found and removed ${count} duplicate schema`, {});
|
||||
this.session.verbose(`Found and removed ${count} duplicate schema`);
|
||||
this.findDifferentSchemasWithSameName(spec);
|
||||
|
||||
return spec;
|
||||
|
@ -162,7 +162,6 @@ export class DuplicateSchemaMerger {
|
|||
|
||||
this.session.verbose(
|
||||
`Schema ${name} has multiple identical declarations, reducing to just one - removing: ${schemasToRemove.length}, keeping: ${schemaToKeep.key}`,
|
||||
["PreCheck", "ReducingSchema"],
|
||||
);
|
||||
|
||||
return newSpec;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Session, Host, startSession, JsonPointerSegments } from "@autorest/extension-base";
|
||||
import { ShadowedNodePath, shadowPosition } from "@azure-tools/codegen";
|
||||
import { Session, AutorestExtensionHost, startSession } from "@autorest/extension-base";
|
||||
import { shadowPosition } from "@azure-tools/codegen";
|
||||
import {
|
||||
Model as oai3,
|
||||
Refable,
|
||||
|
@ -9,21 +9,20 @@ import {
|
|||
JsonType,
|
||||
StringFormat,
|
||||
} from "@azure-tools/openapi";
|
||||
|
||||
import { getDiff } from "recursive-diff";
|
||||
import { Interpretations } from "../modeler/interpretations";
|
||||
|
||||
import { ModelerFourOptions } from "../modeler/modelerfour-options";
|
||||
import { DuplicateSchemaMerger } from "./duplicate-schema-merger";
|
||||
|
||||
export async function processRequest(host: Host) {
|
||||
const debug = (await host.GetValue("debug")) || false;
|
||||
export async function processRequest(host: AutorestExtensionHost) {
|
||||
const debug = (await host.getValue("debug")) || false;
|
||||
|
||||
try {
|
||||
const session = await startSession<oai3>(host);
|
||||
|
||||
// process
|
||||
const plugin = await new QualityPreChecker(session).init();
|
||||
const plugin = new QualityPreChecker(session);
|
||||
|
||||
const input = plugin.input;
|
||||
// go!
|
||||
|
@ -34,13 +33,16 @@ export async function processRequest(host: Host) {
|
|||
session.checkpoint();
|
||||
}
|
||||
|
||||
host.WriteFile(
|
||||
"prechecked-openapi-document.yaml",
|
||||
JSON.stringify(result, null, 2),
|
||||
undefined,
|
||||
"prechecked-openapi-document",
|
||||
);
|
||||
host.WriteFile("original-openapi-document.yaml", JSON.stringify(input, null, 2), undefined, "openapi-document");
|
||||
host.writeFile({
|
||||
filename: "prechecked-openapi-document.yaml",
|
||||
content: JSON.stringify(result, null, 2),
|
||||
artifactType: "prechecked-openapi-document",
|
||||
});
|
||||
host.writeFile({
|
||||
filename: "original-openapi-document.yaml",
|
||||
content: JSON.stringify(input, null, 2),
|
||||
artifactType: "openapi-document",
|
||||
});
|
||||
} catch (error: any) {
|
||||
if (debug) {
|
||||
// eslint-disable-next-line no-console
|
||||
|
@ -52,21 +54,15 @@ export async function processRequest(host: Host) {
|
|||
|
||||
export class QualityPreChecker {
|
||||
input: oai3;
|
||||
options: ModelerFourOptions = {};
|
||||
private options: ModelerFourOptions;
|
||||
protected interpret: Interpretations;
|
||||
|
||||
constructor(protected session: Session<oai3>) {
|
||||
this.input = shadowPosition(session.model); // shadow(session.model, filename);
|
||||
this.options = this.session.configuration.modelerfour ?? {};
|
||||
this.interpret = new Interpretations(session);
|
||||
}
|
||||
|
||||
async init() {
|
||||
// get our configuration for this run.
|
||||
this.options = await this.session.getValue("modelerfour", {});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private resolve<T>(item: Refable<T>): Dereferenced<T> {
|
||||
return dereference(this.input, item);
|
||||
}
|
||||
|
@ -239,7 +235,7 @@ export class QualityPreChecker {
|
|||
for (const { name, schema } of this.listSchemas()) {
|
||||
if (<any>schema.type === "file" || <any>schema.format === "file" || <any>schema.format === "binary") {
|
||||
// handle inconsistency in file format handling.
|
||||
this.session.hint(
|
||||
this.session.warning(
|
||||
`'The schema ${schema?.["x-ms-metadata"]?.name || name} with 'type: ${schema.type}', format: ${
|
||||
schema.format
|
||||
}' will be treated as a binary blob for binary media types.`,
|
||||
|
@ -353,6 +349,7 @@ export class QualityPreChecker {
|
|||
` - ${schemaName}: ${schema.type}`,
|
||||
` - ${parentName}: ${parent.type}`,
|
||||
];
|
||||
|
||||
this.session.error(lines.join("\n"), ["PreCheck", "AllOfTypeDifferent"], parentRef);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ class PreCheckerClient {
|
|||
|
||||
static async create(spec: Model, config: ModelerFourOptions = {}): Promise<PreCheckerClient> {
|
||||
const { session, errors } = await createTestSessionFromModel<Model>({ modelerfour: config }, spec);
|
||||
const prechecker = await new QualityPreChecker(session).init();
|
||||
const prechecker = new QualityPreChecker(session);
|
||||
expect(errors.length).toBe(0);
|
||||
|
||||
return new PreCheckerClient(prechecker.input, prechecker.process());
|
||||
|
@ -161,7 +161,7 @@ describe("Prechecker", () => {
|
|||
);
|
||||
|
||||
const { session, errors } = await createTestSessionFromModel<Model>({}, spec);
|
||||
const prechecker = await new QualityPreChecker(session).init();
|
||||
const prechecker = new QualityPreChecker(session);
|
||||
prechecker.process();
|
||||
expect(errors).toHaveLength(1);
|
||||
expect(errors[0]).toEqual({
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { readdirSync } from "fs";
|
||||
import { codeModelSchema } from "@autorest/codemodel";
|
||||
import { createTestSessionFromFiles } from "@autorest/extension-base/testing";
|
||||
import { serialize } from "@azure-tools/codegen";
|
||||
import { Model } from "@azure-tools/openapi";
|
||||
import { ModelerFour } from "../../src/modeler/modelerfour";
|
||||
import { createTestSessionFromFiles } from "../utils";
|
||||
|
||||
const cfg = {
|
||||
modelerfour: {
|
||||
|
|
|
@ -1,42 +1,4 @@
|
|||
import { Session, startSession } from "@autorest/extension-base";
|
||||
import { readFile } from "@azure-tools/async-io";
|
||||
import { deserialize, fail } from "@azure-tools/codegen";
|
||||
import { Model } from "@azure-tools/openapi";
|
||||
|
||||
export interface TestSessionInput {
|
||||
model: any;
|
||||
filename: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
export interface TestSession<T> {
|
||||
session: Session<T>;
|
||||
errors: Array<any>;
|
||||
}
|
||||
|
||||
async function readData(folder: string, ...files: Array<string>): Promise<Map<string, TestSessionInput>> {
|
||||
const results = new Map<string, { model: any; filename: string; content: string }>();
|
||||
|
||||
for (const filename of files) {
|
||||
const content = await readFile(`${folder}/${filename}`);
|
||||
const model = deserialize<any>(content, filename);
|
||||
results.set(filename, {
|
||||
model,
|
||||
filename,
|
||||
content,
|
||||
});
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
export async function createTestSessionFromFiles<TInputModel>(
|
||||
config: any,
|
||||
folder: string,
|
||||
inputs: Array<string>,
|
||||
): Promise<TestSession<TInputModel>> {
|
||||
const models = await readData(folder, ...inputs);
|
||||
return createTestSession(config, models);
|
||||
}
|
||||
import { createTestSession, TestSession } from "@autorest/extension-base/testing";
|
||||
|
||||
export async function createTestSessionFromModel<TInputModel>(
|
||||
config: any,
|
||||
|
@ -50,30 +12,3 @@ export async function createTestSessionFromModel<TInputModel>(
|
|||
},
|
||||
]);
|
||||
}
|
||||
|
||||
export async function createTestSession<TInputModel>(
|
||||
config: any,
|
||||
inputs: Array<TestSessionInput> | Map<string, TestSessionInput>,
|
||||
): Promise<TestSession<TInputModel>> {
|
||||
const models = Array.isArray(inputs) ? inputs.reduce((m, x) => m.set(x.filename, x), new Map()) : inputs;
|
||||
const errors: Array<any> = [];
|
||||
const session = await startSession<TInputModel>({
|
||||
ReadFile: (filename: string) =>
|
||||
Promise.resolve(models.get(filename)?.content ?? fail(`missing input '${filename}'`)),
|
||||
GetValue: (key: string) => Promise.resolve(key ? config[key] : config),
|
||||
ListInputs: (artifactType?: string) => Promise.resolve([...models.values()].map((x) => x.filename)),
|
||||
ProtectFiles: (path: string) => Promise.resolve(),
|
||||
WriteFile: (filename: string, content: string, sourceMap?: any, artifactType?: string) => Promise.resolve(),
|
||||
Message: (message: any): void => {
|
||||
if (message.Channel === "warning" || message.Channel === "error" || message.Channel === "verbose") {
|
||||
// console.error(`${message.Channel} ${message.Text}`);
|
||||
if (message.Channel === "error") {
|
||||
errors.push(message);
|
||||
}
|
||||
}
|
||||
},
|
||||
UpdateConfigurationFile: (filename: string, content: string) => {},
|
||||
GetConfigurationFile: (filename: string) => Promise.resolve(""),
|
||||
});
|
||||
return { session, errors };
|
||||
}
|
||||
|
|
|
@ -3,6 +3,10 @@
|
|||
"version": "3.3.2",
|
||||
"description": "Library for creating AutoRest extensions",
|
||||
"main": "dist/index.js",
|
||||
"exports": {
|
||||
".": "./dist/index.js",
|
||||
"./testing": "./dist/testing/index.js"
|
||||
},
|
||||
"typings": "./dist/index.d.ts",
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
import { createMessageConnection, RequestType0, RequestType2 } from "vscode-jsonrpc";
|
||||
import { AutorestExtensionHost, AutorestExtensionRpcHost } from "./extension-host";
|
||||
import { Channel } from "./types";
|
||||
|
||||
namespace IAutoRestPluginTargetTypes {
|
||||
export const GetPluginNames = new RequestType0<Array<string>, Error, void>("GetPluginNames");
|
||||
export const Process = new RequestType2<string, string, boolean, Error, void>("Process");
|
||||
}
|
||||
|
||||
export type AutoRestPluginHandler = (initiator: AutorestExtensionHost) => Promise<void>;
|
||||
|
||||
export class AutoRestExtension {
|
||||
private readonly plugins: Record<string, AutoRestPluginHandler> = {};
|
||||
|
||||
public add(name: string, handler: AutoRestPluginHandler): void {
|
||||
this.plugins[name] = handler;
|
||||
}
|
||||
|
||||
public async run(
|
||||
input: NodeJS.ReadableStream = process.stdin,
|
||||
output: NodeJS.WritableStream = process.stdout,
|
||||
): Promise<void> {
|
||||
// connection setup
|
||||
const channel = createMessageConnection(input, output, {
|
||||
error(message) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("rpc:error: ", message);
|
||||
},
|
||||
info(message) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("rpc:info: ", message);
|
||||
},
|
||||
log(message) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("rpc:log: ", message);
|
||||
},
|
||||
warn(message) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("rpc:warn: ", message);
|
||||
},
|
||||
});
|
||||
|
||||
channel.onRequest(IAutoRestPluginTargetTypes.GetPluginNames, async () => Object.keys(this.plugins));
|
||||
channel.onRequest(IAutoRestPluginTargetTypes.Process, async (pluginName: string, sessionId: string) => {
|
||||
const host = new AutorestExtensionRpcHost(channel, sessionId);
|
||||
|
||||
try {
|
||||
const handler = this.plugins[pluginName];
|
||||
if (!handler) {
|
||||
throw new Error(`Plugin host could not find requested plugin '${pluginName}'.`);
|
||||
}
|
||||
await handler(host);
|
||||
return true;
|
||||
} catch (e: any) {
|
||||
if (await host.getValue<boolean>("debug")) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`PLUGIN FAILURE: ${e.message}, ${e.stack}, ${JSON.stringify(e, null, 2)}`);
|
||||
}
|
||||
host.message({
|
||||
Channel: Channel.Fatal,
|
||||
Text: "" + e,
|
||||
Details: e,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// activate
|
||||
channel.listen();
|
||||
}
|
||||
}
|
|
@ -1,271 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createSandbox, deserialize, ShadowedNodePath } from "@azure-tools/codegen";
|
||||
import { Schema, DEFAULT_SCHEMA } from "js-yaml";
|
||||
import {
|
||||
Channel,
|
||||
Message,
|
||||
Mapping,
|
||||
RawSourceMap,
|
||||
JsonPointerSegments,
|
||||
Position,
|
||||
PathPosition,
|
||||
SourceLocation,
|
||||
LogSource,
|
||||
} from "./types";
|
||||
import { Host } from ".";
|
||||
|
||||
const safeEval = createSandbox();
|
||||
|
||||
async function getModel<T>(service: Host, yamlSchema: Schema = DEFAULT_SCHEMA, artifactType?: string) {
|
||||
const files = await service.ListInputs(artifactType);
|
||||
const filename = files[0];
|
||||
if (files.length === 0) {
|
||||
throw new Error("Inputs missing.");
|
||||
}
|
||||
const content = await service.ReadFile(filename);
|
||||
|
||||
return {
|
||||
filename,
|
||||
model: deserialize<T>(content, filename, yamlSchema),
|
||||
};
|
||||
}
|
||||
|
||||
export class Session<TInputModel> {
|
||||
private context!: any;
|
||||
private _debug = false;
|
||||
private _verbose = false;
|
||||
model!: TInputModel;
|
||||
filename!: string;
|
||||
|
||||
/* @internal */ constructor(public readonly service: Host) {}
|
||||
|
||||
/* @internal */ async init<TProject>(project?: TProject, schema: Schema = DEFAULT_SCHEMA, artifactType?: string) {
|
||||
const m = await getModel<TInputModel>(this.service, schema, artifactType);
|
||||
this.model = m.model;
|
||||
this.filename = m.filename;
|
||||
|
||||
void this.initContext(project);
|
||||
this._debug = await this.getValue("debug", false);
|
||||
this._verbose = await this.getValue("verbose", false);
|
||||
return this;
|
||||
}
|
||||
|
||||
/* @internal */ async initContext<TP>(project?: TP) {
|
||||
this.context = this.context || {
|
||||
$config: await this.service.GetValue(""),
|
||||
$project: project,
|
||||
$lib: {
|
||||
path: require("path"),
|
||||
},
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
async readFile(filename: string): Promise<string> {
|
||||
return this.service.ReadFile(filename);
|
||||
}
|
||||
|
||||
async getValue<V>(key: string, defaultValue?: V): Promise<V> {
|
||||
// check if it's in the model first
|
||||
const m = <any>this.model;
|
||||
let value = m && m.language && m.language.default ? m.language.default[key] : undefined;
|
||||
|
||||
// fall back to the configuration
|
||||
if (value == null || value === undefined) {
|
||||
value = await this.service.GetValue(key);
|
||||
}
|
||||
|
||||
// try as a safe eval execution.
|
||||
if (value === null || value === undefined) {
|
||||
try {
|
||||
value = safeEval(key, this.context);
|
||||
} catch {
|
||||
value = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (defaultValue === undefined && value === null) {
|
||||
throw new Error(`No value for configuration key '${key}' was provided`);
|
||||
}
|
||||
|
||||
if (typeof value === "string") {
|
||||
value = await this.resolveVariables(value);
|
||||
}
|
||||
|
||||
// ensure that any content variables are resolved at the end.
|
||||
return <V>(value !== null ? value : defaultValue);
|
||||
}
|
||||
|
||||
async setValue<V>(key: string, value: V) {
|
||||
(<any>this.model).language.default[key] = value;
|
||||
}
|
||||
|
||||
async listInputs(artifactType?: string | undefined): Promise<string[]> {
|
||||
return this.service.ListInputs(artifactType);
|
||||
}
|
||||
|
||||
async protectFiles(path: string): Promise<void> {
|
||||
return this.service.ProtectFiles(path);
|
||||
}
|
||||
writeFile(
|
||||
filename: string,
|
||||
content: string,
|
||||
sourceMap?: Array<Mapping> | RawSourceMap | undefined,
|
||||
artifactType?: string | undefined,
|
||||
): void {
|
||||
return this.service.WriteFile(filename, content, sourceMap, artifactType);
|
||||
}
|
||||
|
||||
message(message: Message): void {
|
||||
if (message.Channel === Channel.Debug && this._debug === false) {
|
||||
return;
|
||||
}
|
||||
if (message.Channel === Channel.Verbose && this._verbose === false) {
|
||||
return;
|
||||
}
|
||||
return this.service.Message(message);
|
||||
}
|
||||
|
||||
updateConfigurationFile(filename: string, content: string): void {
|
||||
return this.service.UpdateConfigurationFile(filename, content);
|
||||
}
|
||||
async getConfigurationFile(filename: string): Promise<string> {
|
||||
return this.service.GetConfigurationFile(filename);
|
||||
}
|
||||
protected errorCount = 0;
|
||||
|
||||
protected static async getModel<T>(service: Host) {
|
||||
const files = await service.ListInputs();
|
||||
const filename = files[0];
|
||||
if (files.length === 0) {
|
||||
throw new Error("Inputs missing.");
|
||||
}
|
||||
return {
|
||||
filename,
|
||||
model: deserialize<T>(await service.ReadFile(filename), filename),
|
||||
};
|
||||
}
|
||||
|
||||
cache = new Array<any>();
|
||||
replacer(key: string, value: any) {
|
||||
if (typeof value === "object" && value !== null) {
|
||||
if (this.cache.indexOf(value) !== -1) {
|
||||
// Duplicate reference found
|
||||
try {
|
||||
// If this value does not reference a parent it can be deduped
|
||||
return JSON.parse(JSON.stringify(value));
|
||||
} catch (error) {
|
||||
// discard key if value cannot be deduped
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Store value in our collection
|
||||
this.cache.push(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
async resolveVariables(input: string): Promise<string> {
|
||||
let output = input;
|
||||
for (const rx of [/\$\((.*?)\)/g, /\$\{(.*?)\}/g]) {
|
||||
/* eslint-disable */
|
||||
for (let match; (match = rx.exec(input)); ) {
|
||||
const text = match[0];
|
||||
const inner = match[1];
|
||||
let value = await this.getValue<any>(inner, null);
|
||||
|
||||
if (value !== undefined && value !== null) {
|
||||
if (typeof value === "object") {
|
||||
value = JSON.stringify(value, this.replacer, 2);
|
||||
}
|
||||
if (value === "{}") {
|
||||
value = "true";
|
||||
}
|
||||
output = output.replace(text, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
public checkpoint() {
|
||||
if (this.errorCount > 0) {
|
||||
throw new Error(`${this.errorCount} errors occured -- cannot continue.`);
|
||||
}
|
||||
}
|
||||
|
||||
protected msg(channel: Channel, message: string, key: string[], source?: LogSource, details?: any) {
|
||||
const sourcePosition = source ? getPosition(this.filename, source) : undefined;
|
||||
|
||||
const sources = sourcePosition ? [sourcePosition] : [];
|
||||
this.message({
|
||||
Channel: channel,
|
||||
Key: key,
|
||||
Source: sources,
|
||||
Text: message,
|
||||
Details: details,
|
||||
});
|
||||
}
|
||||
|
||||
public warning(message: string, key: string[], source?: LogSource, details?: any) {
|
||||
this.msg(Channel.Warning, message, key, source, details);
|
||||
}
|
||||
public hint(message: string, key: string[], source?: LogSource, details?: any) {
|
||||
this.msg(Channel.Hint, message, key, source, details);
|
||||
}
|
||||
|
||||
public error(message: string, key: string[], source?: LogSource, details?: any) {
|
||||
this.errorCount++;
|
||||
this.msg(Channel.Error, message, key, source, details);
|
||||
}
|
||||
public fatal(message: string, key: string[], source?: LogSource, details?: any) {
|
||||
this.errorCount++;
|
||||
this.msg(Channel.Fatal, message, key, source, details);
|
||||
}
|
||||
|
||||
protected output(channel: Channel, message: string, details?: any) {
|
||||
this.message({
|
||||
Channel: channel,
|
||||
Text: message,
|
||||
Details: details,
|
||||
});
|
||||
}
|
||||
|
||||
public debug(message: string, details: any) {
|
||||
this.output(Channel.Debug, message, details);
|
||||
}
|
||||
public verbose(message: string, details: any) {
|
||||
this.output(Channel.Verbose, message, details);
|
||||
}
|
||||
public log(message: string, details: any) {
|
||||
this.output(Channel.Information, message, details);
|
||||
}
|
||||
}
|
||||
|
||||
export async function startSession<TInputModel>(
|
||||
service: Host,
|
||||
project?: any,
|
||||
schema: Schema = DEFAULT_SCHEMA,
|
||||
artifactType?: string,
|
||||
) {
|
||||
return await new Session<TInputModel>(service).init(project, schema, artifactType);
|
||||
}
|
||||
|
||||
function getPosition(document: string, source: LogSource): SourceLocation | undefined {
|
||||
if (typeof source === "string") {
|
||||
return { Position: { path: source }, document };
|
||||
}
|
||||
|
||||
if (source[ShadowedNodePath]) {
|
||||
return { Position: { path: source[ShadowedNodePath] }, document };
|
||||
}
|
||||
|
||||
if ("path" in source || "line" in source) {
|
||||
return { Position: source as any, document };
|
||||
}
|
||||
return undefined;
|
||||
}
|
|
@ -1,179 +0,0 @@
|
|||
import { basename, dirname } from "path";
|
||||
import { Readable } from "stream";
|
||||
import {
|
||||
createMessageConnection,
|
||||
Logger,
|
||||
RequestType0,
|
||||
RequestType1,
|
||||
RequestType2,
|
||||
NotificationType2,
|
||||
NotificationType4,
|
||||
} from "vscode-jsonrpc";
|
||||
import { Mapping, Message, RawSourceMap, Channel } from "./types";
|
||||
|
||||
namespace IAutoRestPluginTargetTypes {
|
||||
export const GetPluginNames = new RequestType0<Array<string>, Error, void>("GetPluginNames");
|
||||
export const Process = new RequestType2<string, string, boolean, Error, void>("Process");
|
||||
}
|
||||
interface IAutoRestPluginTarget {
|
||||
GetPluginNames(): Promise<Array<string>>;
|
||||
Process(pluginName: string, sessionId: string): Promise<boolean>;
|
||||
}
|
||||
|
||||
namespace IAutoRestPluginInitiatorTypes {
|
||||
export const ReadFile = new RequestType2<string, string, string, Error, void>("ReadFile");
|
||||
export const GetValue = new RequestType2<string, string, any, Error, void>("GetValue");
|
||||
export const ListInputs = new RequestType2<string, string | undefined, Array<string>, Error, void>("ListInputs");
|
||||
export const ProtectFiles = new NotificationType2<string, string, void>("ProtectFiles");
|
||||
export const WriteFile = new NotificationType4<
|
||||
string,
|
||||
string,
|
||||
string,
|
||||
Array<Mapping> | RawSourceMap | undefined,
|
||||
void
|
||||
>("WriteFile");
|
||||
export const Message = new NotificationType2<string, Message, void>("Message");
|
||||
}
|
||||
|
||||
export interface IAutoRestPluginInitiator {
|
||||
ReadFile(filename: string): Promise<string>;
|
||||
GetValue(key: string): Promise<any>;
|
||||
ListInputs(artifactType?: string): Promise<Array<string>>;
|
||||
ProtectFiles(path: string): Promise<void>;
|
||||
WriteFile(filename: string, content: string, sourceMap?: Array<Mapping> | RawSourceMap, artifactType?: string): void;
|
||||
Message(message: Message): void;
|
||||
UpdateConfigurationFile(filename: string, content: string): void;
|
||||
GetConfigurationFile(filename: string): Promise<string>;
|
||||
}
|
||||
|
||||
export type AutoRestPluginHandler = (initiator: IAutoRestPluginInitiator) => Promise<void>;
|
||||
|
||||
export class AutoRestExtension {
|
||||
private readonly plugins: { [name: string]: AutoRestPluginHandler } = {};
|
||||
|
||||
public Add(name: string, handler: AutoRestPluginHandler): void {
|
||||
this.plugins[name] = handler;
|
||||
}
|
||||
|
||||
public async Run(
|
||||
input: NodeJS.ReadableStream = process.stdin,
|
||||
output: NodeJS.WritableStream = process.stdout,
|
||||
): Promise<void> {
|
||||
// connection setup
|
||||
const channel = createMessageConnection(input, output, {
|
||||
error(message) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("error: ", message);
|
||||
},
|
||||
info(message) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("info: ", message);
|
||||
},
|
||||
log(message) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("log: ", message);
|
||||
},
|
||||
warn(message) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("warn: ", message);
|
||||
},
|
||||
});
|
||||
|
||||
channel.onRequest(IAutoRestPluginTargetTypes.GetPluginNames, async () => Object.keys(this.plugins));
|
||||
channel.onRequest(IAutoRestPluginTargetTypes.Process, async (pluginName: string, sessionId: string) => {
|
||||
try {
|
||||
const handler = this.plugins[pluginName];
|
||||
if (!handler) {
|
||||
throw new Error(`Plugin host could not find requested plugin '${pluginName}'.`);
|
||||
}
|
||||
await handler({
|
||||
async ProtectFiles(path: string): Promise<void> {
|
||||
channel.sendNotification(IAutoRestPluginInitiatorTypes.ProtectFiles, sessionId, path);
|
||||
},
|
||||
UpdateConfigurationFile(filename: string, content: string): void {
|
||||
channel.sendNotification(IAutoRestPluginInitiatorTypes.Message, sessionId, {
|
||||
Channel: Channel.Configuration,
|
||||
Key: [filename],
|
||||
Text: content,
|
||||
});
|
||||
},
|
||||
async GetConfigurationFile(filename: string): Promise<string> {
|
||||
const configurations = await channel.sendRequest(
|
||||
IAutoRestPluginInitiatorTypes.GetValue,
|
||||
sessionId,
|
||||
"configurationFiles",
|
||||
);
|
||||
|
||||
const filenames = Object.getOwnPropertyNames(configurations);
|
||||
if (filenames.length > 0) {
|
||||
const basePath = dirname(filenames[0]);
|
||||
for (const configFile of filenames) {
|
||||
if (configFile.startsWith(basePath) && filename === basename(configFile)) {
|
||||
return configurations[configFile];
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
},
|
||||
async ReadFile(filename: string): Promise<string> {
|
||||
return await channel.sendRequest(IAutoRestPluginInitiatorTypes.ReadFile, sessionId, filename);
|
||||
},
|
||||
async GetValue(key: string): Promise<any> {
|
||||
return await channel.sendRequest(IAutoRestPluginInitiatorTypes.GetValue, sessionId, key);
|
||||
},
|
||||
async ListInputs(artifactType?: string): Promise<Array<string>> {
|
||||
return await channel.sendRequest(IAutoRestPluginInitiatorTypes.ListInputs, sessionId, artifactType);
|
||||
},
|
||||
WriteFile(
|
||||
filename: string,
|
||||
content: string,
|
||||
sourceMap?: Array<Mapping> | RawSourceMap,
|
||||
artifactType?: string,
|
||||
): void {
|
||||
if (artifactType) {
|
||||
channel.sendNotification(IAutoRestPluginInitiatorTypes.Message, sessionId, {
|
||||
Channel: Channel.File,
|
||||
Details: {
|
||||
content: content,
|
||||
type: artifactType,
|
||||
uri: filename,
|
||||
sourceMap: sourceMap,
|
||||
},
|
||||
Text: content,
|
||||
Key: [artifactType, filename],
|
||||
});
|
||||
} else {
|
||||
channel.sendNotification(
|
||||
IAutoRestPluginInitiatorTypes.WriteFile,
|
||||
sessionId,
|
||||
filename,
|
||||
content,
|
||||
sourceMap,
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
Message(message: Message): void {
|
||||
channel.sendNotification(IAutoRestPluginInitiatorTypes.Message, sessionId, message);
|
||||
},
|
||||
});
|
||||
return true;
|
||||
} catch (e: any) {
|
||||
if (await channel.sendRequest(IAutoRestPluginInitiatorTypes.GetValue, sessionId, "debug")) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`PLUGIN FAILURE: ${e.message}, ${e.stack}, ${JSON.stringify(e, null, 2)}`);
|
||||
}
|
||||
|
||||
channel.sendNotification(IAutoRestPluginInitiatorTypes.Message, sessionId, <Message>{
|
||||
Channel: <any>"fatal",
|
||||
Text: "" + e,
|
||||
Details: e,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// activate
|
||||
channel.listen();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,168 @@
|
|||
import { basename, dirname } from "path";
|
||||
import { RequestType2, NotificationType2, NotificationType4, MessageConnection } from "vscode-jsonrpc";
|
||||
import { AutorestExtensionLogger } from "./extension-logger";
|
||||
import { Mapping, Message, RawSourceMap, Channel } from "./types";
|
||||
|
||||
namespace IAutoRestPluginInitiatorTypes {
|
||||
export const ReadFile = new RequestType2<string, string, string, Error, void>("ReadFile");
|
||||
export const GetValue = new RequestType2<string, string, any, Error, void>("GetValue");
|
||||
export const ListInputs = new RequestType2<string, string | undefined, Array<string>, Error, void>("ListInputs");
|
||||
export const ProtectFiles = new NotificationType2<string, string, void>("ProtectFiles");
|
||||
export const WriteFile = new NotificationType4<
|
||||
string,
|
||||
string,
|
||||
string,
|
||||
Array<Mapping> | RawSourceMap | undefined,
|
||||
void
|
||||
>("WriteFile");
|
||||
export const Message = new NotificationType2<string, Message, void>("Message");
|
||||
}
|
||||
|
||||
export interface WriteFileOptions {
|
||||
/**
|
||||
* @param filename Name of the file.
|
||||
*/
|
||||
filename: string;
|
||||
|
||||
/**
|
||||
* @param content Content of the file.
|
||||
*/
|
||||
content: string;
|
||||
|
||||
/**
|
||||
* @param sourceMap Source map that can be used to trace back source position in case of error.
|
||||
*/
|
||||
sourceMap?: Mapping[] | RawSourceMap;
|
||||
|
||||
/**
|
||||
* @param artifactType Artifact type
|
||||
*/
|
||||
artifactType?: string;
|
||||
}
|
||||
|
||||
export interface AutorestExtensionHost {
|
||||
logger: AutorestExtensionLogger;
|
||||
|
||||
protectFiles(path: string): Promise<void>;
|
||||
readFile(filename: string): Promise<string>;
|
||||
getValue<T>(key: string): Promise<T | undefined>;
|
||||
listInputs(artifactType?: string): Promise<Array<string>>;
|
||||
writeFile({ filename, content, sourceMap, artifactType }: WriteFileOptions): void;
|
||||
message(message: Message): void;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
UpdateConfigurationFile(filename: string, content: string): void;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
GetConfigurationFile(filename: string): Promise<string>;
|
||||
}
|
||||
|
||||
export class AutorestExtensionRpcHost implements AutorestExtensionHost {
|
||||
public logger: AutorestExtensionLogger;
|
||||
|
||||
public constructor(private channel: MessageConnection, private sessionId: string) {
|
||||
this.logger = new AutorestExtensionLogger((x) => this.message(x));
|
||||
}
|
||||
|
||||
/**
|
||||
* Protect files that will not be cleared when using clear-output-folder.
|
||||
* @param path Path to the file/folder to protect.
|
||||
*/
|
||||
public async protectFiles(path: string): Promise<void> {
|
||||
this.channel.sendNotification(IAutoRestPluginInitiatorTypes.ProtectFiles, this.sessionId, path);
|
||||
}
|
||||
|
||||
public async readFile(filename: string): Promise<string> {
|
||||
return await this.channel.sendRequest(IAutoRestPluginInitiatorTypes.ReadFile, this.sessionId, filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a configuration key form the resolved configuration.
|
||||
* @param key Path to the configuration entry. (Keys dot seperated)
|
||||
* @returns Value of the configuration.
|
||||
*/
|
||||
public async getValue<T>(key: string): Promise<T | undefined> {
|
||||
return await this.channel.sendRequest(IAutoRestPluginInitiatorTypes.GetValue, this.sessionId, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* List inputs
|
||||
* @param artifactType @optional Filter by artifact type.
|
||||
* @returns name of the input files.
|
||||
*/
|
||||
public async listInputs(artifactType?: string): Promise<Array<string>> {
|
||||
return await this.channel.sendRequest(IAutoRestPluginInitiatorTypes.ListInputs, this.sessionId, artifactType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a file.
|
||||
*/
|
||||
public writeFile({ filename, content, sourceMap, artifactType }: WriteFileOptions): void {
|
||||
if (artifactType) {
|
||||
this.channel.sendNotification(IAutoRestPluginInitiatorTypes.Message, this.sessionId, {
|
||||
Channel: Channel.File,
|
||||
Details: {
|
||||
content: content,
|
||||
type: artifactType,
|
||||
uri: filename,
|
||||
sourceMap: sourceMap,
|
||||
},
|
||||
Text: content,
|
||||
Key: [artifactType, filename],
|
||||
});
|
||||
} else {
|
||||
this.channel.sendNotification(
|
||||
IAutoRestPluginInitiatorTypes.WriteFile,
|
||||
this.sessionId,
|
||||
filename,
|
||||
content,
|
||||
sourceMap,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message.
|
||||
* @param message Message to send.
|
||||
*/
|
||||
public message(message: Message): void {
|
||||
this.channel.sendNotification(IAutoRestPluginInitiatorTypes.Message, this.sessionId, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public UpdateConfigurationFile(filename: string, content: string): void {
|
||||
this.channel.sendNotification(IAutoRestPluginInitiatorTypes.Message, this.sessionId, {
|
||||
Channel: Channel.Configuration,
|
||||
Key: [filename],
|
||||
Text: content,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public async GetConfigurationFile(filename: string): Promise<string> {
|
||||
const configurations = await this.channel.sendRequest(
|
||||
IAutoRestPluginInitiatorTypes.GetValue,
|
||||
this.sessionId,
|
||||
"configurationFiles",
|
||||
);
|
||||
|
||||
const filenames = Object.getOwnPropertyNames(configurations);
|
||||
if (filenames.length > 0) {
|
||||
const basePath = dirname(filenames[0]);
|
||||
for (const configFile of filenames) {
|
||||
if (configFile.startsWith(basePath) && filename === basename(configFile)) {
|
||||
return configurations[configFile];
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
import { SourceLocation, Channel, Message } from "./types";
|
||||
|
||||
export type LogLevel = "debug" | "verbose" | "info" | "warning" | "error" | "fatal";
|
||||
|
||||
export interface LogInfo {
|
||||
level: LogLevel;
|
||||
message: string;
|
||||
|
||||
/**
|
||||
* Code for the log.
|
||||
* @example -> ["MyPlugin", "CannotDoThat"] -> "MyPlugin/CannotDoThat"
|
||||
*/
|
||||
key?: string[];
|
||||
|
||||
/**
|
||||
* Position where this log can be traced to.
|
||||
*/
|
||||
source?: SourceLocation;
|
||||
|
||||
/**
|
||||
* Additional details.
|
||||
*/
|
||||
details?: any;
|
||||
}
|
||||
|
||||
export class AutorestExtensionLogger {
|
||||
public constructor(private sendMessage: (message: Message) => void) {}
|
||||
|
||||
public debug(message: string) {
|
||||
this.log({ level: "debug", message });
|
||||
}
|
||||
|
||||
public verbose(message: string) {
|
||||
this.log({ level: "verbose", message });
|
||||
}
|
||||
|
||||
public info(message: string) {
|
||||
this.log({ level: "info", message });
|
||||
}
|
||||
|
||||
public warning(message: string, key: string[], source?: SourceLocation, details?: any) {
|
||||
this.log({ level: "warning", message, key, source, details });
|
||||
}
|
||||
|
||||
public error(message: string, key: string[], source?: SourceLocation, details?: any) {
|
||||
this.log({ level: "error", message, key, source, details });
|
||||
}
|
||||
|
||||
public fatal(message: string, key: string[], source?: SourceLocation, details?: any) {
|
||||
this.log({ level: "fatal", message, key, source, details });
|
||||
}
|
||||
|
||||
public log(info: LogInfo) {
|
||||
const sources = info.source ? [info.source] : [];
|
||||
this.sendMessage({
|
||||
Channel: getChannel(info.level),
|
||||
Key: info.key,
|
||||
Source: sources,
|
||||
Text: info.message,
|
||||
Details: info.details,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getChannel(level: LogLevel): Channel {
|
||||
switch (level) {
|
||||
case "debug":
|
||||
return Channel.Debug;
|
||||
case "verbose":
|
||||
return Channel.Verbose;
|
||||
case "info":
|
||||
return Channel.Information;
|
||||
case "warning":
|
||||
return Channel.Warning;
|
||||
case "error":
|
||||
return Channel.Error;
|
||||
case "fatal":
|
||||
return Channel.Fatal;
|
||||
}
|
||||
}
|
|
@ -1,13 +1,5 @@
|
|||
export { AutoRestExtension, IAutoRestPluginInitiator as Host } from "./extension-base";
|
||||
export {
|
||||
ArtifactMessage,
|
||||
Message,
|
||||
Artifact,
|
||||
Channel,
|
||||
Mapping,
|
||||
RawSourceMap,
|
||||
SourceLocation,
|
||||
JsonPointerSegments,
|
||||
Position,
|
||||
} from "./types";
|
||||
export * from "./convenience";
|
||||
export { AutoRestExtension } from "./autorest-extension";
|
||||
export * from "./extension-host";
|
||||
export * from "./extension-logger";
|
||||
export * from "./types";
|
||||
export * from "./session";
|
||||
|
|
|
@ -0,0 +1,175 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createSandbox, deserialize, ShadowedNodePath } from "@azure-tools/codegen";
|
||||
import { Schema, DEFAULT_SCHEMA } from "js-yaml";
|
||||
import { AutorestExtensionHost, WriteFileOptions } from "./extension-host";
|
||||
import { LogLevel } from "./extension-logger";
|
||||
import { Channel, Message, SourceLocation, LogSource } from "./types";
|
||||
|
||||
export interface SessionOptions<T> {
|
||||
host: AutorestExtensionHost;
|
||||
filename: string;
|
||||
model: T;
|
||||
configuration: any;
|
||||
}
|
||||
|
||||
const safeEval = createSandbox();
|
||||
|
||||
export class Session<TInputModel> {
|
||||
public model: TInputModel;
|
||||
public filename: string;
|
||||
public configuration: Record<string, any>;
|
||||
|
||||
private context: any;
|
||||
protected errorCount = 0;
|
||||
private _debug = false;
|
||||
private _verbose = false;
|
||||
private service: AutorestExtensionHost;
|
||||
|
||||
/* @internal */ constructor(options: SessionOptions<TInputModel>) {
|
||||
this.service = options.host;
|
||||
this.filename = options.filename;
|
||||
this.model = options.model;
|
||||
|
||||
this.configuration = options.configuration;
|
||||
this._debug = options.configuration.debug;
|
||||
this._verbose = options.configuration.verbose;
|
||||
this.context = {
|
||||
$config: options.configuration,
|
||||
$lib: {
|
||||
path: require("path"),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public async readFile(filename: string): Promise<string> {
|
||||
return this.service.readFile(filename);
|
||||
}
|
||||
|
||||
public async getValue<V>(key: string, defaultValue?: V): Promise<V> {
|
||||
let value = await this.service.getValue(key);
|
||||
|
||||
// try as a safe eval execution.
|
||||
if (value === null || value === undefined) {
|
||||
try {
|
||||
value = safeEval(key, this.context);
|
||||
} catch {
|
||||
value = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (defaultValue === undefined && value === null) {
|
||||
throw new Error(`No value for configuration key '${key}' was provided`);
|
||||
}
|
||||
|
||||
// ensure that any content variables are resolved at the end.
|
||||
return <V>(value !== null ? value : defaultValue);
|
||||
}
|
||||
|
||||
async listInputs(artifactType?: string | undefined): Promise<string[]> {
|
||||
return this.service.listInputs(artifactType);
|
||||
}
|
||||
|
||||
async protectFiles(path: string): Promise<void> {
|
||||
return this.service.protectFiles(path);
|
||||
}
|
||||
|
||||
public writeFile(options: WriteFileOptions): void {
|
||||
return this.service.writeFile(options);
|
||||
}
|
||||
|
||||
public message(message: Message): void {
|
||||
if (message.Channel === Channel.Debug && this._debug === false) {
|
||||
return;
|
||||
}
|
||||
if (message.Channel === Channel.Verbose && this._verbose === false) {
|
||||
return;
|
||||
}
|
||||
return this.service.message(message);
|
||||
}
|
||||
|
||||
public checkpoint() {
|
||||
if (this.errorCount > 0) {
|
||||
throw new Error(`${this.errorCount} errors occured -- cannot continue.`);
|
||||
}
|
||||
}
|
||||
|
||||
public debug(message: string) {
|
||||
this.msg("debug", message);
|
||||
}
|
||||
|
||||
public verbose(message: string) {
|
||||
this.msg("verbose", message);
|
||||
}
|
||||
|
||||
public info(message: string) {
|
||||
this.msg("info", message);
|
||||
}
|
||||
|
||||
public warning(message: string, key: string[], source?: LogSource, details?: any) {
|
||||
this.msg("warning", message, key, source, details);
|
||||
}
|
||||
|
||||
public error(message: string, key: string[], source?: LogSource, details?: any) {
|
||||
this.errorCount++;
|
||||
this.msg("error", message, key, source, details);
|
||||
}
|
||||
|
||||
public fatal(message: string, key: string[], source?: LogSource, details?: any) {
|
||||
this.errorCount++;
|
||||
this.msg("fatal", message, key, source, details);
|
||||
}
|
||||
|
||||
protected msg(level: LogLevel, message: string, key?: string[], source?: LogSource, details?: any) {
|
||||
const sourcePosition = source ? getPosition(this.filename, source) : undefined;
|
||||
this.service.logger.log({
|
||||
level,
|
||||
message,
|
||||
key,
|
||||
source: sourcePosition,
|
||||
details,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function getModel<T>(service: AutorestExtensionHost, yamlSchema: Schema = DEFAULT_SCHEMA, artifactType?: string) {
|
||||
const files = await service.listInputs(artifactType);
|
||||
const filename = files[0];
|
||||
if (files.length === 0) {
|
||||
throw new Error("Inputs missing.");
|
||||
}
|
||||
const content = await service.readFile(filename);
|
||||
|
||||
return {
|
||||
filename,
|
||||
model: deserialize<T>(content, filename, yamlSchema),
|
||||
};
|
||||
}
|
||||
|
||||
export async function startSession<TInputModel>(
|
||||
host: AutorestExtensionHost,
|
||||
schema: Schema = DEFAULT_SCHEMA,
|
||||
artifactType?: string,
|
||||
) {
|
||||
const { model, filename } = await getModel<TInputModel>(host, schema, artifactType);
|
||||
const configuration = await host.getValue("");
|
||||
return new Session<TInputModel>({ host, filename, model, configuration });
|
||||
}
|
||||
|
||||
function getPosition(document: string, source: LogSource): SourceLocation | undefined {
|
||||
if (typeof source === "string") {
|
||||
return { Position: { path: source }, document };
|
||||
}
|
||||
|
||||
if (source[ShadowedNodePath]) {
|
||||
return { Position: { path: source[ShadowedNodePath] }, document };
|
||||
}
|
||||
|
||||
if ("path" in source || "line" in source) {
|
||||
return { Position: source as any, document };
|
||||
}
|
||||
return undefined;
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export * from "./test-session";
|
|
@ -0,0 +1,77 @@
|
|||
import { readFile } from "fs/promises";
|
||||
import { deserialize, fail } from "@azure-tools/codegen";
|
||||
import { WriteFileOptions } from "../extension-host";
|
||||
import { AutorestExtensionLogger } from "../extension-logger";
|
||||
import { Session, startSession } from "../session";
|
||||
|
||||
export interface TestSessionInput {
|
||||
model: any;
|
||||
filename: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
export interface TestSession<T> {
|
||||
session: Session<T>;
|
||||
errors: Array<any>;
|
||||
}
|
||||
|
||||
async function readData(folder: string, ...files: Array<string>): Promise<Map<string, TestSessionInput>> {
|
||||
const results = new Map<string, { model: any; filename: string; content: string }>();
|
||||
|
||||
for (const filename of files) {
|
||||
const buffer = await readFile(`${folder}/${filename}`);
|
||||
const content = buffer.toString();
|
||||
const model = deserialize<any>(content, filename);
|
||||
results.set(filename, {
|
||||
model,
|
||||
filename,
|
||||
content,
|
||||
});
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
export async function createTestSessionFromFiles<TInputModel>(
|
||||
config: any,
|
||||
folder: string,
|
||||
inputs: Array<string>,
|
||||
): Promise<TestSession<TInputModel>> {
|
||||
const models = await readData(folder, ...inputs);
|
||||
return createTestSession(config, models);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Test session to use for testing extension.
|
||||
* @param config Autorest configuration to use in this session.
|
||||
* @param inputs List of inputs.
|
||||
* @returns Test session
|
||||
*/
|
||||
export async function createTestSession<TInputModel>(
|
||||
config: any,
|
||||
inputs: Array<TestSessionInput> | Map<string, TestSessionInput>,
|
||||
): Promise<TestSession<TInputModel>> {
|
||||
const models = Array.isArray(inputs) ? inputs.reduce((m, x) => m.set(x.filename, x), new Map()) : inputs;
|
||||
const errors: Array<any> = [];
|
||||
|
||||
const sendMessage = (message: any): void => {
|
||||
if (message.Channel === "warning" || message.Channel === "error" || message.Channel === "verbose") {
|
||||
if (message.Channel === "error") {
|
||||
errors.push(message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const session = await startSession<TInputModel>({
|
||||
logger: new AutorestExtensionLogger(sendMessage),
|
||||
readFile: (filename: string) =>
|
||||
Promise.resolve(models.get(filename)?.content ?? fail(`missing input '${filename}'`)),
|
||||
getValue: (key: string) => Promise.resolve(key ? config[key] : config),
|
||||
listInputs: (artifactType?: string) => Promise.resolve([...models.values()].map((x) => x.filename)),
|
||||
protectFiles: (path: string) => Promise.resolve(),
|
||||
writeFile: (options: WriteFileOptions) => Promise.resolve(),
|
||||
message: sendMessage,
|
||||
UpdateConfigurationFile: (filename: string, content: string) => {},
|
||||
GetConfigurationFile: (filename: string) => Promise.resolve(""),
|
||||
});
|
||||
return { session, errors };
|
||||
}
|
|
@ -30,34 +30,34 @@ export interface Artifact {
|
|||
*/
|
||||
export enum Channel {
|
||||
/** Information is considered the mildest of responses; not necesarily actionable. */
|
||||
Information = <any>"information",
|
||||
Information = "information",
|
||||
|
||||
/** Warnings are considered important for best practices, but not catastrophic in nature. */
|
||||
Warning = <any>"warning",
|
||||
Warning = "warning",
|
||||
|
||||
/** Errors are considered blocking issues that block a successful operation. */
|
||||
Error = <any>"error",
|
||||
Error = "error",
|
||||
|
||||
/** Debug messages are designed for the developer to communicate internal autorest implementation details. */
|
||||
Debug = <any>"debug",
|
||||
Debug = "debug",
|
||||
|
||||
/** Verbose messages give the user additional clarity on the process. */
|
||||
Verbose = <any>"verbose",
|
||||
Verbose = "verbose",
|
||||
|
||||
/** Catastrophic failure, likely abending the process. */
|
||||
Fatal = <any>"fatal",
|
||||
Fatal = "fatal",
|
||||
|
||||
/** Hint messages offer guidance or support without forcing action. */
|
||||
Hint = <any>"hint",
|
||||
Hint = "hint",
|
||||
|
||||
/** File represents a file output from an extension. Details are a Artifact and are required. */
|
||||
File = <any>"file",
|
||||
File = "file",
|
||||
|
||||
/** content represents an update/creation of a configuration file. The final uri will be in the same folder as the primary config file. */
|
||||
Configuration = <any>"configuration",
|
||||
Configuration = "configuration",
|
||||
|
||||
/** Protect is a path to not remove during a clear-output-folder. */
|
||||
Protect = <any>"protect",
|
||||
Protect = "protect",
|
||||
}
|
||||
|
||||
export interface Message {
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
// Workaround for es6 modules exports not working well with typescript for now. https://github.com/microsoft/TypeScript/issues/33079
|
||||
export * from "./dist/testing";
|
|
@ -0,0 +1,2 @@
|
|||
// Workaround for es6 modules exports not working well with typescript for now. https://github.com/microsoft/TypeScript/issues/33079
|
||||
module.exports = require("./dist/testing");
|
Загрузка…
Ссылка в новой задаче