Fix null write error with node 14 for Autorest V2 (#3985)

This commit is contained in:
Timothee Guerin 2021-03-15 09:46:49 -07:00 коммит произвёл GitHub
Родитель 7007600e45
Коммит 1ae4afbfb5
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
4 изменённых файлов: 228 добавлений и 95 удалений

Просмотреть файл

@ -47,9 +47,6 @@
"shelljs": "^0.7.6",
"through2-parallel": "^0.1.3",
"typescript": "2.6.2",
"tslint": "^5.9.1",
"tslint-language-service": "^0.9.9",
"tslint-microsoft-contrib": "^5.0.3",
"vinyl-fs": "^2.4.4",
"yargs": "^6.6.0",
"shx": "0.2.2"

Просмотреть файл

@ -1,7 +1,9 @@
// load modules from static linker filesystem.
if (process.argv.indexOf("--no-static-loader") === -1 && process.env["no-static-loader"] === undefined) {
require('./static-loader.js').load(`${__dirname}/static_modules.fs`)
if (
process.argv.indexOf("--no-static-loader") === -1 &&
process.env["no-static-loader"] === undefined
) {
require("./static-loader.js").load(`${__dirname}/static_modules.fs`);
}
/*---------------------------------------------------------------------------------------------
@ -14,11 +16,10 @@ if (process.argv.indexOf("--no-static-loader") === -1 && process.env["no-static-
if (!String.prototype.padEnd) {
String.prototype.padEnd = function padEnd(targetLength, padString) {
targetLength = targetLength >> 0; //floor if number or convert non-number to 0;
padString = String(padString || ' ');
padString = String(padString || " ");
if (this.length > targetLength) {
return String(this);
}
else {
} else {
targetLength = targetLength - this.length;
if (targetLength > padString.length) {
padString += padString.repeat(targetLength / padString.length); //append to original to ensure we are longer than needed
@ -28,33 +29,50 @@ if (!String.prototype.padEnd) {
};
}
require('events').EventEmitter.defaultMaxListeners = 100;
process.env['ELECTRON_RUN_AS_NODE'] = "1";
delete process.env['ELECTRON_NO_ATTACH_CONSOLE'];
require("events").EventEmitter.defaultMaxListeners = 100;
process.env["ELECTRON_RUN_AS_NODE"] = "1";
delete process.env["ELECTRON_NO_ATTACH_CONSOLE"];
process.on("exit", () => {
Shutdown()
Shutdown();
});
const color: (text: string) => string = (<any>global).color ? (<any>global).color : p => p;
const color: (text: string) => string = (<any>global).color
? (<any>global).color
: (p) => p;
// start of autorest-ng
// the console app starts for real here.
import { Artifact } from './lib/artifact';
import { AutoRestConfigurationImpl, MergeConfigurations } from './lib/configuration';
import { Artifact } from "./lib/artifact";
import {
AutoRestConfigurationImpl,
MergeConfigurations,
} from "./lib/configuration";
import { Parse, Stringify } from "./lib/ref/yaml";
import { CreateObject, nodes } from "./lib/ref/jsonpath";
import { OutstandingTaskAwaiter } from "./lib/outstanding-task-awaiter";
import { AutoRest, ConfigurationView, IsOpenApiDocument, Shutdown } from './lib/autorest-core';
import {
AutoRest,
ConfigurationView,
IsOpenApiDocument,
Shutdown,
} from "./lib/autorest-core";
import { ShallowCopy } from "./lib/source-map/merging";
import { Message, Channel } from "./lib/message";
import { join, resolve as currentDirectory } from "path";
import { ChildProcess } from "child_process";
import { CreateFolderUri, MakeRelativeUri, ReadUri, ResolveUri, WriteString, ClearFolder } from "./lib/ref/uri";
import {
CreateFolderUri,
MakeRelativeUri,
ReadUri,
ResolveUri,
WriteString,
ClearFolder,
} from "./lib/ref/uri";
import { isLegacy, CreateConfiguration } from "./legacyCli";
import { DataStore } from "./lib/data-store/data-store";
import { EnhancedFileSystem, RealFileSystem } from './lib/file-system';
import { EnhancedFileSystem, RealFileSystem } from "./lib/file-system";
import { Exception, OperationCanceledException } from "./lib/exception";
import { Help } from "./help";
@ -78,15 +96,26 @@ async function legacyMain(autorestArgs: string[]): Promise<number> {
const dataStore = new DataStore();
let config: AutoRestConfigurationImpl = {};
try {
config = await CreateConfiguration(currentDirUri, dataStore.GetReadThroughScope(new RealFileSystem()), autorestArgs);
config = await CreateConfiguration(
currentDirUri,
dataStore.GetReadThroughScope(new RealFileSystem()),
autorestArgs
);
} catch (e) {
console.error(color("!Error: You have provided legacy command line arguments (single-dash syntax) that seem broken."));
console.error(
color(
"!Error: You have provided legacy command line arguments (single-dash syntax) that seem broken."
)
);
console.error("");
console.error(color(
"> While AutoRest keeps on supporting the old CLI by converting it over to the new one internally, \n" +
"> it does not have crazy logic determining *what* is wrong with arguments, should conversion fail. \n" +
"> Please try fixing your arguments or consider moving to the new CLI. \n" +
"> isit https://github.com/Azure/autorest/blob/master/docs/user/cli.md for information about the new CLI."));
console.error(
color(
"> While AutoRest keeps on supporting the old CLI by converting it over to the new one internally, \n" +
"> it does not have crazy logic determining *what* is wrong with arguments, should conversion fail. \n" +
"> Please try fixing your arguments or consider moving to the new CLI. \n" +
"> isit https://github.com/Azure/autorest/blob/master/docs/user/cli.md for information about the new CLI."
)
);
console.error("");
console.error(color("!Internal error: " + e));
await showHelp();
@ -95,13 +124,19 @@ async function legacyMain(autorestArgs: string[]): Promise<number> {
// autorest init
if (autorestArgs[0] === "init") {
const clientNameGuess = (config["override-info"] || {}).title || Parse<any>(await ReadUri((config["input-file"] as any)[0])).info.title;
await autorestInit(clientNameGuess, Array.isArray(config["input-file"]) ? config["input-file"] as any : []);
const clientNameGuess =
(config["override-info"] || {}).title ||
Parse<any>(await ReadUri((config["input-file"] as any)[0])).info.title;
await autorestInit(
clientNameGuess,
Array.isArray(config["input-file"]) ? (config["input-file"] as any) : []
);
return 0;
}
// autorest init-min
if (autorestArgs[0] === "init-min") {
console.log(`# AutoRest Configuration (auto-generated, please adjust title)
console.log(
`# AutoRest Configuration (auto-generated, please adjust title)
> see https://aka.ms/autorest
@ -111,7 +146,8 @@ The following configuration was auto-generated and can be adjusted.
${Stringify(config).replace(/^---\n/, "")}
~~~
`.replace(/~/g, "`"));
`.replace(/~/g, "`")
);
return 0;
}
// autorest init-cli
@ -119,7 +155,12 @@ ${Stringify(config).replace(/^---\n/, "")}
const args: string[] = [];
for (const node of nodes(config, "$..*")) {
const path = node.path.join(".");
const values = node.value instanceof Array ? node.value : (typeof node.value === "object" ? [] : [node.value]);
const values =
node.value instanceof Array
? node.value
: typeof node.value === "object"
? []
: [node.value];
for (const value of values) {
args.push(`--${path}=${value}`);
}
@ -133,9 +174,21 @@ ${Stringify(config).replace(/^---\n/, "")}
api.AddConfiguration(config);
const view = await api.view;
let outstanding: Promise<void> = Promise.resolve();
api.GeneratedFile.Subscribe((_, file) => outstanding = outstanding.then(() => WriteString(file.uri, file.content)));
api.ClearFolder.Subscribe((_, folder) => outstanding = outstanding.then(async () => { try { await ClearFolder(folder); } catch (e) { } }));
subscribeMessages(api, () => { });
api.GeneratedFile.Subscribe(
(_, file) =>
(outstanding = outstanding.then(() =>
WriteString(file.uri, file.content)
))
);
api.ClearFolder.Subscribe(
(_, folder) =>
(outstanding = outstanding.then(async () => {
try {
await ClearFolder(folder);
} catch (e) {}
}))
);
subscribeMessages(api, () => {});
// warn about `--` arguments
for (var arg of autorestArgs) {
@ -145,7 +198,7 @@ ${Stringify(config).replace(/^---\n/, "")}
Text:
`The parameter ${arg} looks like it was meant for the new CLI! ` +
"Note that you have invoked the legacy CLI (by using at least one single-dash argument). " +
"Please visit https://github.com/Azure/autorest/blob/master/docs/user/cli.md for information about the new CLI."
"Please visit https://github.com/Azure/autorest/blob/master/docs/user/cli.md for information about the new CLI.",
});
}
}
@ -159,17 +212,20 @@ ${Stringify(config).replace(/^---\n/, "")}
return 0;
}
/**
* Current AutoRest
*/
type CommandLineArgs = { configFileOrFolder?: string, switches: any[], rawSwitches: any };
type CommandLineArgs = {
configFileOrFolder?: string;
switches: any[];
rawSwitches: any;
};
function parseArgs(autorestArgs: string[]): CommandLineArgs {
const result: CommandLineArgs = {
switches: [],
rawSwitches: {}
rawSwitches: {},
};
for (const arg of autorestArgs) {
@ -178,7 +234,9 @@ function parseArgs(autorestArgs: string[]): CommandLineArgs {
// configuration file?
if (match === null) {
if (result.configFileOrFolder) {
throw new Error(`Found multiple configuration file arguments: '${result.configFileOrFolder}', '${arg}'`);
throw new Error(
`Found multiple configuration file arguments: '${result.configFileOrFolder}', '${arg}'`
);
}
result.configFileOrFolder = arg;
continue;
@ -188,15 +246,18 @@ function parseArgs(autorestArgs: string[]): CommandLineArgs {
const key = match[1];
let rawValue = match[3] || "{}";
if (rawValue.startsWith('.')) {
if (rawValue.startsWith(".")) {
// starts with a . or .. -> this is a relative path to current directory
rawValue = join(process.cwd(), rawValue);
}
// quote stuff beginning with '@', YAML doesn't think unquoted strings should start with that
rawValue = rawValue.startsWith('@') ? `'${rawValue}'` : rawValue;
rawValue = rawValue.startsWith("@") ? `'${rawValue}'` : rawValue;
// quote numbers with decimal point, we don't have any use for non-integer numbers (while on the other hand version strings may look like decimal numbers)
rawValue = !isNaN(parseFloat(rawValue)) && rawValue.includes('.') ? `'${rawValue}'` : rawValue;
rawValue =
!isNaN(parseFloat(rawValue)) && rawValue.includes(".")
? `'${rawValue}'`
: rawValue;
const value = Parse(rawValue);
result.rawSwitches[key] = value;
result.switches.push(CreateObject(key.split("."), value));
@ -205,7 +266,11 @@ function parseArgs(autorestArgs: string[]): CommandLineArgs {
return result;
}
function outputMessage(instance: AutoRest, m: Message, errorCounter: () => void) {
function outputMessage(
instance: AutoRest,
m: Message,
errorCounter: () => void
) {
switch (m.Channel) {
case Channel.Debug:
if (debug) {
@ -237,14 +302,18 @@ function subscribeMessages(api: AutoRest, errorCounter: () => void) {
});
}
async function autorestInit(title: string = "API-NAME", inputs: string[] = ["LIST INPUT FILES HERE"]) {
async function autorestInit(
title: string = "API-NAME",
inputs: string[] = ["LIST INPUT FILES HERE"]
) {
const cwdUri = CreateFolderUri(currentDirectory());
for (let i = 0; i < inputs.length; ++i) {
try {
inputs[i] = MakeRelativeUri(cwdUri, inputs[i]);
} catch (e) { }
} catch (e) {}
}
console.log(`# ${title}
console.log(
`# ${title}
> see https://aka.ms/autorest
This is the AutoRest configuration file for the ${title}.
@ -271,7 +340,7 @@ These are the global settings for the API.
~~~ yaml
# list all the input OpenAPI files (may be YAML, JSON, or Literate- OpenAPI markdown)
input-file:
${inputs.map(x => " - " + x).join("\n")}
${inputs.map((x) => " - " + x).join("\n")}
~~~
---
@ -284,7 +353,8 @@ csharp:
# override the default output folder
output-folder: generated/csharp
~~~
`.replace(/~/g, "`"));
`.replace(/~/g, "`")
);
}
let exitcode = 0;
@ -309,8 +379,17 @@ async function currentMain(autorestArgs: string[]): Promise<number> {
// parse the args from the command line
args = parseArgs([...autorestArgs, ...more]);
if ((!args.rawSwitches["message-format"]) || args.rawSwitches["message-format"] === "regular") {
console.log(color(`> Loading AutoRest core '${__dirname}' (${require("../package.json").version})`));
if (
!args.rawSwitches["message-format"] ||
args.rawSwitches["message-format"] === "regular"
) {
console.log(
color(
`> Loading AutoRest core '${__dirname}' (${
require("../package.json").version
})`
)
);
}
verbose = verbose || args.rawSwitches["verbose"];
debug = debug || args.rawSwitches["debug"];
@ -319,7 +398,13 @@ async function currentMain(autorestArgs: string[]): Promise<number> {
const currentDirUri = CreateFolderUri(currentDirectory());
// get an instance of AutoRest and add the command line switches to the configuration.
const api = new AutoRest(new EnhancedFileSystem((MergeConfigurations(...args.switches) as any)["github-auth-token"] || process.env.GITHUB_AUTH_TOKEN), ResolveUri(currentDirUri, args.configFileOrFolder || "."));
const api = new AutoRest(
new EnhancedFileSystem(
(MergeConfigurations(...args.switches) as any)["github-auth-token"] ||
process.env.GITHUB_AUTH_TOKEN
),
ResolveUri(currentDirUri, args.configFileOrFolder || ".")
);
api.AddConfiguration(args.switches);
// listen for output messages and file writes
@ -329,7 +414,7 @@ async function currentMain(autorestArgs: string[]): Promise<number> {
api.GeneratedFile.Subscribe((_, artifact) => artifacts.push(artifact));
api.ClearFolder.Subscribe((_, folder) => clearFolders.push(folder));
const config = (await api.view);
const config = await api.view;
// maybe a resource schema batch process
if (config["resource-schema-batch"]) {
@ -338,9 +423,7 @@ async function currentMain(autorestArgs: string[]): Promise<number> {
if (config["batch"]) {
await batch(api);
}
else {
} else {
const result = await api.Process().finish;
if (result !== true) {
throw result;
@ -352,41 +435,70 @@ async function currentMain(autorestArgs: string[]): Promise<number> {
// - print boilerplate help
console.log("");
console.log("");
console.log(color("**Usage**: autorest `[configuration-file.md] [...options]`"));
console.log(
color("**Usage**: autorest `[configuration-file.md] [...options]`")
);
console.log("");
console.log(color(" See: https://aka.ms/autorest/cli for additional documentation"));
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));
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 help: Help = Parse(helpArtifact.content, (message, index) => console.error(color(`!Parsing error at **${helpArtifact.uri}**:__${index}: ${message}__`)));
const help: Help = Parse(helpArtifact.content, (message, index) =>
console.error(
color(
`!Parsing error at **${helpArtifact.uri}**:__${index}: ${message}__`
)
)
);
if (!help) {
continue;
}
const activatedBySuffix = help.activationScope ? ` (activated by --${help.activationScope})` : "";
const activatedBySuffix = help.activationScope
? ` (activated by --${help.activationScope})`
: "";
console.log("");
console.log(color(`### ${help.categoryFriendlyName}${activatedBySuffix}`));
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 typePart = settingHelp.type ? `=<${settingHelp.type}>` : ` `; //`[=<boolean>]`;
let settingPart = `${keyPart}\`${typePart}\``;
// if (!settingHelp.required) {
// settingPart = `[${settingPart}]`;
// }
console.log(color(` ${settingPart.padEnd(30)} **${settingHelp.description}**`));
console.log(
color(` ${settingPart.padEnd(30)} **${settingHelp.description}**`)
);
}
}
} else {
// perform file system operations.
for (const folder of clearFolders) {
try { await ClearFolder(folder); } catch (e) { }
try {
await ClearFolder(folder);
} catch (e) {}
}
for (const artifact of artifacts) {
await WriteString(artifact.uri, artifact.content);
await WriteString(
artifact.uri,
artifact.content == null ? "" : artifact.content
);
}
}
@ -423,7 +535,9 @@ function getRds(schema: any, path: string): Array<string> {
const result = [];
if (schema.resourceDefinitions) {
for (const name of Object.getOwnPropertyNames(schema.resourceDefinitions)) {
result.push(`{ "$ref": "https://schema.management.azure.com/schemas/${apiversion}/${namespace}.json#/resourceDefinitions/${name}" }, `);
result.push(
`{ "$ref": "https://schema.management.azure.com/schemas/${apiversion}/${namespace}.json#/resourceDefinitions/${name}" }, `
);
}
}
return result;
@ -436,27 +550,35 @@ async function resourceSchemaBatch(api: AutoRest): Promise<number> {
let outstanding: Promise<void> = Promise.resolve();
// ask for the view without
// ask for the view without
const config = await api.RegenerateView();
for (const batchConfig of config.GetNestedConfiguration("resource-schema-batch")) { // really, there should be only one
for (const batchConfig of config.GetNestedConfiguration(
"resource-schema-batch"
)) {
// really, there should be only one
for (const eachFile of batchConfig["input-file"]) {
const path = ResolveUri(config.configFileFolderUri, eachFile);
const content = await ReadUri(path);
if (!await IsOpenApiDocument(content)) {
if (!(await IsOpenApiDocument(content))) {
exitcode++;
console.error(color(`!File ${path} is not a OpenAPI file.`));
continue;
}
// Create the autorest instance for that item
const instance = new AutoRest(new RealFileSystem(), config.configFileFolderUri);
const instance = new AutoRest(
new RealFileSystem(),
config.configFileFolderUri
);
instance.GeneratedFile.Subscribe((_, file) => {
if (file.uri.endsWith(".json")) {
const more = JSON.parse(file.content);
if (!outputs.has(file.uri)) {
// Console.Log(` Writing *${file.uri}*`);
outputs.set(file.uri, file.content);
outstanding = outstanding.then(() => WriteString(file.uri, file.content))
outstanding = outstanding.then(() =>
WriteString(file.uri, file.content)
);
schemas.push(...getRds(more, file.uri));
return;
} else {
@ -464,11 +586,19 @@ async function resourceSchemaBatch(api: AutoRest): Promise<number> {
// Console.Log(` Updating *${file.uri}*`);
schemas.push(...getRds(more, file.uri));
existing.resourceDefinitions = shallowMerge(existing.resourceDefinitions, more.resourceDefinitions);
existing.definitions = shallowMerge(existing.definitions, more.definitions);
existing.resourceDefinitions = shallowMerge(
existing.resourceDefinitions,
more.resourceDefinitions
);
existing.definitions = shallowMerge(
existing.definitions,
more.definitions
);
const content = JSON.stringify(existing, null, 2);
outputs.set(file.uri, content);
outstanding = outstanding.then(() => WriteString(file.uri, content));
outstanding = outstanding.then(() =>
WriteString(file.uri, content)
);
}
}
});
@ -504,24 +634,37 @@ async function batch(api: AutoRest): Promise<void> {
const batchTaskConfigReference: any = {};
api.AddConfiguration(batchTaskConfigReference);
for (const batchTaskConfig of config.GetEntry("batch" as any)) {
const isjson = (args.rawSwitches["message-format"] === "json" || args.rawSwitches["message-format"] === "yaml");
const isjson =
args.rawSwitches["message-format"] === "json" ||
args.rawSwitches["message-format"] === "yaml";
if (!isjson) {
outputMessage(api, {
Channel: Channel.Information,
Text: `Processing batch task - ${JSON.stringify(batchTaskConfig)} .`
}, () => { });
outputMessage(
api,
{
Channel: Channel.Information,
Text: `Processing batch task - ${JSON.stringify(batchTaskConfig)} .`,
},
() => {}
);
}
// update batch task config section
for (const key of Object.keys(batchTaskConfigReference)) delete batchTaskConfigReference[key];
for (const key of Object.keys(batchTaskConfigReference))
delete batchTaskConfigReference[key];
Object.assign(batchTaskConfigReference, batchTaskConfig);
api.Invalidate();
const result = await api.Process().finish;
if (result !== true) {
outputMessage(api, {
Channel: Channel.Error,
Text: `Failure during batch task - ${JSON.stringify(batchTaskConfig)} -- ${result}.`
}, () => { });
outputMessage(
api,
{
Channel: Channel.Error,
Text: `Failure during batch task - ${JSON.stringify(
batchTaskConfig
)} -- ${result}.`,
},
() => {}
);
throw result;
}
}
@ -550,7 +693,7 @@ async function mainImpl(): Promise<number> {
} else {
console.log(e.message);
}
return e.exitCode
return e.exitCode;
}
if (e !== false) {
console.error(color(`!${e}`));
@ -565,15 +708,14 @@ async function main() {
exitcode = await mainImpl();
} catch {
exitcode = 102;
}
finally {
} finally {
try {
await Shutdown();
} catch {
} catch {
} finally {
process.exit(exitcode);
}
}
}
main();
main();

Просмотреть файл

@ -43,9 +43,6 @@
"dts-generator": "^2.1.0",
"mocha": "5.2.0",
"shx": "0.2.2",
"tslint": "^5.9.1",
"tslint-language-service": "^0.9.9",
"tslint-microsoft-contrib": "^5.0.3",
"typescript": "^3.1.3",
"mocha-typescript": "1.1.14",
"static-link": "^0.3.0",

Просмотреть файл

@ -44,9 +44,6 @@
"mocha": "5.2.0",
"mocha-typescript": "1.1.14",
"typescript": "^3.1.3",
"tslint": "^5.9.1",
"tslint-language-service": "^0.9.9",
"tslint-microsoft-contrib": "^5.0.3",
"static-link": "^0.3.0",
"@microsoft.azure/async-io": "^2.1.0",
"@microsoft.azure/extension": "^2.2.0",