зеркало из https://github.com/Azure/autorest.git
Feature: Better logging for package installation (#4360)
This commit is contained in:
Родитель
511bc2ed08
Коммит
7b20086e16
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"changes": [
|
||||
{
|
||||
"packageName": "@autorest/common",
|
||||
"comment": "**Added** progress bar reporting to the logger",
|
||||
"type": "minor"
|
||||
}
|
||||
],
|
||||
"packageName": "@autorest/common",
|
||||
"email": "tiguerin@microsoft.com"
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"changes": [
|
||||
{
|
||||
"packageName": "@autorest/configuration",
|
||||
"comment": "Uptake changes to the extension loader and report installation progress",
|
||||
"type": "minor"
|
||||
}
|
||||
],
|
||||
"packageName": "@autorest/configuration",
|
||||
"email": "tiguerin@microsoft.com"
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"changes": [
|
||||
{
|
||||
"packageName": "@autorest/core",
|
||||
"comment": "Uptake change to @autorest/extension package.",
|
||||
"type": "minor"
|
||||
}
|
||||
],
|
||||
"packageName": "@autorest/core",
|
||||
"email": "tiguerin@microsoft.com"
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"changes": [
|
||||
{
|
||||
"packageName": "@autorest/test-utils",
|
||||
"comment": "Uptake change to logger",
|
||||
"type": "minor"
|
||||
}
|
||||
],
|
||||
"packageName": "@autorest/test-utils",
|
||||
"email": "tiguerin@microsoft.com"
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"changes": [
|
||||
{
|
||||
"packageName": "@azure-tools/extension",
|
||||
"comment": "**Added** Progress tracking for installation and imrpvoed error reporting",
|
||||
"type": "minor"
|
||||
}
|
||||
],
|
||||
"packageName": "@azure-tools/extension",
|
||||
"email": "tiguerin@microsoft.com"
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"changes": [
|
||||
{
|
||||
"packageName": "autorest",
|
||||
"comment": "Uptake change to @autorest/extension package.",
|
||||
"type": "minor"
|
||||
}
|
||||
],
|
||||
"packageName": "autorest",
|
||||
"email": "tiguerin@microsoft.com"
|
||||
}
|
|
@ -30,6 +30,7 @@ dependencies:
|
|||
'@rush-temp/test-utils': file:projects/test-utils.tgz_prettier@2.3.2+ts-node@9.1.1
|
||||
'@rush-temp/yaml': file:projects/yaml.tgz_prettier@2.3.2+ts-node@9.1.1
|
||||
'@types/body-parser': 1.19.1
|
||||
'@types/cli-progress': 3.9.2
|
||||
'@types/command-exists': 1.2.0
|
||||
'@types/commonmark': 0.27.5
|
||||
'@types/deep-equal': 1.0.1
|
||||
|
@ -58,6 +59,7 @@ dependencies:
|
|||
ajv-formats: 2.1.1
|
||||
body-parser: 1.19.0
|
||||
chalk: 4.1.2
|
||||
cli-progress: 3.9.1
|
||||
command-exists: 1.2.9
|
||||
commonmark: 0.27.0
|
||||
compare-versions: 3.6.0
|
||||
|
@ -938,6 +940,12 @@ packages:
|
|||
dev: false
|
||||
resolution:
|
||||
integrity: sha512-a6bTJ21vFOGIkwM0kzh9Yr89ziVxq4vYH2fQ6N8AeipEzai/cFK6aGMArIkUeIdRIgpwQa+2bXiLuUJCpSf2Cg==
|
||||
/@types/cli-progress/3.9.2:
|
||||
dependencies:
|
||||
'@types/node': 14.14.45
|
||||
dev: false
|
||||
resolution:
|
||||
integrity: sha512-VO5/X5Ij+oVgEVjg5u0IXVe3JQSKJX+Ev8C5x+0hPy0AuWyW+bF8tbajR7cPFnDGhs7pidztcac+ccrDtk5teA==
|
||||
/@types/command-exists/1.2.0:
|
||||
dev: false
|
||||
resolution:
|
||||
|
@ -2243,6 +2251,15 @@ packages:
|
|||
node: '>=4'
|
||||
resolution:
|
||||
integrity: sha1-jffHquUf02h06PjQW5GAvBGj/tc=
|
||||
/cli-progress/3.9.1:
|
||||
dependencies:
|
||||
colors: 1.4.0
|
||||
string-width: 4.2.2
|
||||
dev: false
|
||||
engines:
|
||||
node: '>=4'
|
||||
resolution:
|
||||
integrity: sha512-AXxiCe2a0Lm0VN+9L0jzmfQSkcZm5EYspfqXKaSIQKqIk+0hnkZ3/v1E9B39mkD6vYhKih3c/RPsJBSwq9O99Q==
|
||||
/cliui/5.0.0:
|
||||
dependencies:
|
||||
string-width: 3.1.0
|
||||
|
@ -9191,7 +9208,7 @@ packages:
|
|||
peerDependencies:
|
||||
ts-node: '*'
|
||||
resolution:
|
||||
integrity: sha512-OVeXR+8NDnJgJkJyUTzM8Js4g6tQ0RHG/MdP9LUV72j30aqQgXEGcGmyzCEDB28NfvLj3g+ifEku2P5G2XLD3A==
|
||||
integrity: sha512-MpNqbnbGTn2/x4A+5vzCiGLqBrb3oVtjif9njDDo80AMkwVFDi+/2jkQ7PPOWK22W9am3CPEdeZqBRwjM2lqtA==
|
||||
tarball: file:projects/autorest.tgz
|
||||
version: 0.0.0
|
||||
file:projects/cadl.tgz_ts-node@9.1.1+webpack@5.40.0:
|
||||
|
@ -9226,7 +9243,7 @@ packages:
|
|||
ts-node: '*'
|
||||
webpack: '*'
|
||||
resolution:
|
||||
integrity: sha512-vxvPXgPJTt0yrvFH5oFumu7Ivqdtw4hww5d7nSoy6ICTnPdoZSVK6Pa4Zbpin1+9fpBZAMIu+SBxBfcYZfbPZQ==
|
||||
integrity: sha512-mkJpA8Z3+oRed++5iLDcFJZhEy/WoxartWsu8P8I5Wb+38Fna/0hU4JEF/oF4Wck9Kn5aEtqti28Z4DGICdV6g==
|
||||
tarball: file:projects/cadl.tgz
|
||||
version: 0.0.0
|
||||
file:projects/codegen.tgz_prettier@2.3.2+ts-node@9.1.1:
|
||||
|
@ -9285,7 +9302,7 @@ packages:
|
|||
peerDependencies:
|
||||
prettier: '*'
|
||||
resolution:
|
||||
integrity: sha512-mFz2VfiQVtof+igNYv0kUIsWBL4yKDcNowdGLB46upeKfjS6IoZtJww0HaywCS8JbY1yBOA00TjtFyeCV3x86A==
|
||||
integrity: sha512-QCZ1c+ryLL7x7CKKccHrYVKJXittu3jcDkJAM3IOAaJ6Qgikqcy/5FkHlLIr2zc44N+v+gqlgUk2rAYt5JxlpQ==
|
||||
tarball: file:projects/codemodel.tgz
|
||||
version: 0.0.0
|
||||
file:projects/common.tgz_prettier@2.3.2+ts-node@9.1.1:
|
||||
|
@ -9312,7 +9329,7 @@ packages:
|
|||
prettier: '*'
|
||||
ts-node: '*'
|
||||
resolution:
|
||||
integrity: sha512-VlWPj+ryuGsUB3pc/obhBVIVpM/4S5WD+c5Tr0UYU1iumP0jKmRLyXA0iEOSqa1FyrZQZd4YEQjL0CXRDLunIQ==
|
||||
integrity: sha512-1Hw9B8MqUGJv3N3E0nc/ihlLR5zn1FnQkX0WeTpWq8m2AybiEkQvCKBhp+seMJFrJnzbrhn+IlAQub18zTrI/Q==
|
||||
tarball: file:projects/common.tgz
|
||||
version: 0.0.0
|
||||
file:projects/compare.tgz_prettier@2.3.2:
|
||||
|
@ -9347,7 +9364,7 @@ packages:
|
|||
peerDependencies:
|
||||
prettier: '*'
|
||||
resolution:
|
||||
integrity: sha512-TF01wD/AzM6WP4tkyMk8zI4UNKfBBXa0kmh18GvBMWBiLa1+4dI+LTJ9f2qUwlSIi5WMiNkvwbWVDjRfxGwajw==
|
||||
integrity: sha512-m6OwVTxymkE9FAdUpQgQ5064myIJBvaBPMmpphLCGJtk/HvRpHcMJBdMvuGmoQDiuiAwGk2Dx7V9csQO2N5Qvg==
|
||||
tarball: file:projects/compare.tgz
|
||||
version: 0.0.0
|
||||
file:projects/configuration.tgz_prettier@2.3.2+ts-node@9.1.1:
|
||||
|
@ -9380,7 +9397,7 @@ packages:
|
|||
prettier: '*'
|
||||
ts-node: '*'
|
||||
resolution:
|
||||
integrity: sha512-ixI+T6Q45pf5aZy9px0d/Zr73UMZ5GUYNQ6G/I3tLTagI9we6rcaRUefSHk66xiO0KrMrXK3njM7rFMLJfmCxw==
|
||||
integrity: sha512-UZ53gfYOZaoEX2tIDjPUiKfj1wfI64Gbzw4cTFpw7q5gZX/YTG/20NK/1TntyP88Q7H8+p0wNCRiJlwKBODoPQ==
|
||||
tarball: file:projects/configuration.tgz
|
||||
version: 0.0.0
|
||||
file:projects/core.tgz_ts-node@9.1.1:
|
||||
|
@ -9389,6 +9406,7 @@ packages:
|
|||
'@azure-tools/object-comparison': 3.0.253
|
||||
'@azure-tools/tasks': 3.0.255
|
||||
'@azure-tools/uri': 3.1.1
|
||||
'@types/cli-progress': 3.9.2
|
||||
'@types/commonmark': 0.27.5
|
||||
'@types/jest': 26.0.24
|
||||
'@types/jsonpath': 0.2.0
|
||||
|
@ -9400,6 +9418,7 @@ packages:
|
|||
ajv: 8.6.2
|
||||
ajv-errors: 3.0.0_ajv@8.6.2
|
||||
ajv-formats: 2.1.1
|
||||
cli-progress: 3.9.1
|
||||
commonmark: 0.27.0
|
||||
compare-versions: 3.6.0
|
||||
copy-webpack-plugin: 7.0.0_webpack@5.40.0
|
||||
|
@ -9434,7 +9453,7 @@ packages:
|
|||
peerDependencies:
|
||||
ts-node: '*'
|
||||
resolution:
|
||||
integrity: sha512-TkL/z4SM5fGGP6RHyCrsChzkD2zx2LyR4endGy0IhQNklDwbxzjuU9soFiQ9HfOfyRunmN4F/AxLGa0a47iXEQ==
|
||||
integrity: sha512-hVmBc+yNi5YVO5C9tN11uq2B9rZ7TmwmkGXCbOG7fh0iFyE5+V4YyShK1irpUSYRhN4/yoJfu2HqRFsj6pI+4w==
|
||||
tarball: file:projects/core.tgz
|
||||
version: 0.0.0
|
||||
file:projects/datastore.tgz_prettier@2.3.2+ts-node@9.1.1:
|
||||
|
@ -9468,7 +9487,7 @@ packages:
|
|||
prettier: '*'
|
||||
ts-node: '*'
|
||||
resolution:
|
||||
integrity: sha512-TbjyFxINESWXHSXfu724HtdQcUEkL3/X4TkOPQCGlF4KXsegpg4t33Ji6qLmXA3amutFwS+7gqXnZX05KdEJKQ==
|
||||
integrity: sha512-pkpfsGRn8PBbFx0WOghfw0JSJPY4J01etP36eCfPf5BOUSVQP0VfhYtuajDoqd89Amv+Legq1DFakLsRWJZKQw==
|
||||
tarball: file:projects/datastore.tgz
|
||||
version: 0.0.0
|
||||
file:projects/deduplication.tgz_prettier@2.3.2+ts-node@9.1.1:
|
||||
|
@ -9498,7 +9517,7 @@ packages:
|
|||
prettier: '*'
|
||||
ts-node: '*'
|
||||
resolution:
|
||||
integrity: sha512-MYQ8DPYd8PRILUdoR1N+jrLvOJuM3W8wm2AK3xez8Z1Y2UyV/bYNhaww9aYB/q5BwXMl6c0x19sOKacKGm3ybw==
|
||||
integrity: sha512-OLaxADMpKCeiBvlh/gr55kLVVB7oEOpeO55cLpYvMgnzP59j4VmOPQttEvo8ZmMzBr0iNMNoc1HW1uBNMWBFXA==
|
||||
tarball: file:projects/deduplication.tgz
|
||||
version: 0.0.0
|
||||
file:projects/extension-base.tgz_prettier@2.3.2:
|
||||
|
@ -9523,7 +9542,7 @@ packages:
|
|||
peerDependencies:
|
||||
prettier: '*'
|
||||
resolution:
|
||||
integrity: sha512-IY5+GFmGnLIMllhmV0yuTR6ShoQWsCIjymoXxTmVGY5wxy9WdRuYCpZu/MKkrebu/nD2VHIyvpXaf1JqwI12tQ==
|
||||
integrity: sha512-m1X/JYvivbdTric3atlDqaqHLCbJTECxX96dGHiTRcbIM56jROVbSLsw4oe737MTx0ShOUUEq+vZK7gUdRgZSw==
|
||||
tarball: file:projects/extension-base.tgz
|
||||
version: 0.0.0
|
||||
file:projects/extension.tgz_prettier@2.3.2+ts-node@9.1.1:
|
||||
|
@ -9737,7 +9756,7 @@ packages:
|
|||
peerDependencies:
|
||||
ts-node: '*'
|
||||
resolution:
|
||||
integrity: sha512-T/lhdQdhW3dDTLx/hKUeJqxeXIMlQu1ND+DLUwsXY3lc/NRnP6rC5V+1RGTMjiMUuzb6VnaCkJWw5HwOzUfMsQ==
|
||||
integrity: sha512-wbAs7xv5ovUIfZgMjBg4Jbpym2NOEG79mo/FbGadIvnSmZEHh2h5/ncm9IfYcDJsEjUIN3pVhPW8c/7514Ivhw==
|
||||
tarball: file:projects/modelerfour.tgz
|
||||
version: 0.0.0
|
||||
file:projects/oai2-to-oai3.tgz_prettier@2.3.2+ts-node@9.1.1:
|
||||
|
@ -9766,7 +9785,7 @@ packages:
|
|||
prettier: '*'
|
||||
ts-node: '*'
|
||||
resolution:
|
||||
integrity: sha512-6YP89sECKf2MF6e9cl8NYvhXIU8OeEi6NW5G61thiZzmB8mwOulJ2yNraLs8S150feF4nWbJtfJu1CvMi0iIPg==
|
||||
integrity: sha512-w7ZHAKyQ3PnoCkSaq/W5SDWVQBORIddI4yvCvGTLoeTi4axtImIGhZ8cncslZocFBkKsW8lScnYJ1AQHtXMkJA==
|
||||
tarball: file:projects/oai2-to-oai3.tgz
|
||||
version: 0.0.0
|
||||
file:projects/openapi.tgz_prettier@2.3.2+ts-node@9.1.1:
|
||||
|
@ -9817,7 +9836,7 @@ packages:
|
|||
peerDependencies:
|
||||
prettier: '*'
|
||||
resolution:
|
||||
integrity: sha512-qyjJL+5/hyJbeCF38NSkgiNd5TAK/i5ibrc3MH9g8YLiFcn5t/fmF6gQ44hAP0XLYPyAzqlTJu7hhZhqDE44QA==
|
||||
integrity: sha512-b5uy+rnnvde68qRG9NkFEncryV0ZNel4NZh5HRiFANmC2dbIf4OWpVTJPTIPXo1AJT4Is43e6fSIu8copnn7Tw==
|
||||
tarball: file:projects/test-public-packages.tgz
|
||||
version: 0.0.0
|
||||
file:projects/test-utils.tgz_prettier@2.3.2+ts-node@9.1.1:
|
||||
|
@ -9839,7 +9858,7 @@ packages:
|
|||
prettier: '*'
|
||||
ts-node: '*'
|
||||
resolution:
|
||||
integrity: sha512-KgD9TLvgY49dTKkpIcjQwxn6z0efwqwj6COGl/8yOsQxDA5neMoBwaBtHm6edMOv5yLgnX9gd4CtYeP8Bzw2uA==
|
||||
integrity: sha512-whu9IiGZUbreZKk3Y+hhawjOOucfiBiFlnaRaFG36E9lK9X9uvmkMunHsS1zM2mqRKuiYz2uRqP7cSym7jGSUg==
|
||||
tarball: file:projects/test-utils.tgz
|
||||
version: 0.0.0
|
||||
file:projects/yaml.tgz_prettier@2.3.2+ts-node@9.1.1:
|
||||
|
@ -9905,6 +9924,7 @@ specifiers:
|
|||
'@rush-temp/test-utils': file:./projects/test-utils.tgz
|
||||
'@rush-temp/yaml': file:./projects/yaml.tgz
|
||||
'@types/body-parser': ^1.19.0
|
||||
'@types/cli-progress': ~3.9.2
|
||||
'@types/command-exists': ~1.2.0
|
||||
'@types/commonmark': ^0.27.0
|
||||
'@types/deep-equal': ^1.0.1
|
||||
|
@ -9933,6 +9953,7 @@ specifiers:
|
|||
ajv-formats: ^2.1.0
|
||||
body-parser: ^1.19.0
|
||||
chalk: ^4.1.0
|
||||
cli-progress: ~3.9.1
|
||||
command-exists: ~1.2.9
|
||||
commonmark: ^0.27.0
|
||||
compare-versions: ^3.4.0
|
||||
|
|
|
@ -61,6 +61,7 @@
|
|||
"chalk": "^4.1.0",
|
||||
"copy-webpack-plugin": "^7.0.0",
|
||||
"cpy-cli": "~2.0.0",
|
||||
"eslint-plugin-jest": "~24.3.2",
|
||||
"eslint-plugin-node": "~11.1.0",
|
||||
"eslint-plugin-prettier": "~3.4.0",
|
||||
"eslint-plugin-unicorn": "~33.0.1",
|
||||
|
|
|
@ -8,7 +8,8 @@ import "source-map-support/register";
|
|||
|
||||
const cwd = process.cwd();
|
||||
|
||||
import { AutorestSyncLogger, ConsoleLoggerSink } from "@autorest/common";
|
||||
import { AutorestSyncLogger, ConsoleLoggerSink, FilterLogger } from "@autorest/common";
|
||||
import { getLogLevel } from "@autorest/configuration";
|
||||
import chalk from "chalk";
|
||||
import { clearTempData } from "./actions";
|
||||
import { parseAutorestArgs } from "./args";
|
||||
|
@ -108,7 +109,10 @@ async function main() {
|
|||
logger.info(`AutoRest core version selected from configuration: ${chalk.yellow.bold(config.version)}.`);
|
||||
}
|
||||
|
||||
const coreVersionPath = await resolveCoreVersion(config);
|
||||
const coreVersionPath = await resolveCoreVersion(
|
||||
logger.with(new FilterLogger({ level: getLogLevel({ ...args, ...config }) })),
|
||||
config,
|
||||
);
|
||||
|
||||
// let's strip the extra stuff from the command line before we require the core module.
|
||||
const newArgs: string[] = [];
|
||||
|
|
|
@ -5,14 +5,12 @@ import { spawn } from "child_process";
|
|||
import { lookup } from "dns";
|
||||
import { mkdtempSync, rmdirSync } from "fs";
|
||||
import { homedir, tmpdir } from "os";
|
||||
|
||||
import { join } from "path";
|
||||
import { IAutorestLogger } from "@autorest/common";
|
||||
import { AutorestConfiguration } from "@autorest/configuration";
|
||||
import { isFile, mkdir, isDirectory } from "@azure-tools/async-io";
|
||||
import { Extension, ExtensionManager, Package } from "@azure-tools/extension";
|
||||
|
||||
import { Exception, When } from "@azure-tools/tasks";
|
||||
|
||||
import * as semver from "semver";
|
||||
import { AutorestArgs } from "./args";
|
||||
import { VERSION } from "./constants";
|
||||
|
@ -227,6 +225,7 @@ export async function ensureAutorestHome() {
|
|||
}
|
||||
|
||||
export async function selectVersion(
|
||||
logger: IAutorestLogger,
|
||||
requestedVersion: string,
|
||||
force: boolean,
|
||||
minimumVersion?: string,
|
||||
|
@ -240,20 +239,14 @@ export async function selectVersion(
|
|||
}
|
||||
|
||||
if (currentVersion) {
|
||||
if (args.debug) {
|
||||
console.log(`The most recent installed version is ${currentVersion.version}`);
|
||||
}
|
||||
logger.debug(`The most recent installed version is ${currentVersion.version}`);
|
||||
|
||||
if (requestedVersion === "latest-installed" || (requestedVersion === "latest" && false == (await networkEnabled))) {
|
||||
if (args.debug) {
|
||||
console.log(`requesting current version '${currentVersion.version}'`);
|
||||
}
|
||||
logger.debug(`requesting current version '${currentVersion.version}'`);
|
||||
requestedVersion = currentVersion.version;
|
||||
}
|
||||
} else {
|
||||
if (args.debug) {
|
||||
console.log(`No ${newCorePackage} (or ${oldCorePackage}) is installed.`);
|
||||
}
|
||||
logger.debug(`No ${newCorePackage} (or ${oldCorePackage}) is installed.`);
|
||||
}
|
||||
|
||||
let selectedVersion: Extension | null = null;
|
||||
|
@ -267,9 +260,7 @@ export async function selectVersion(
|
|||
// is the requested version installed?
|
||||
if (!selectedVersion || force) {
|
||||
if (!force) {
|
||||
if (args.debug) {
|
||||
console.log(`${requestedVersion} was not satisfied directly by a previous installation.`);
|
||||
}
|
||||
logger.debug(`${requestedVersion} was not satisfied directly by a previous installation.`);
|
||||
}
|
||||
|
||||
// if it's not a file, and the network isn't available, we can't continue.
|
||||
|
@ -336,13 +327,16 @@ export async function selectVersion(
|
|||
console.log(`**Installing package** ${corePackageName}@${pkg.version}\n[This will take a few moments...]`);
|
||||
}
|
||||
|
||||
// @autorest/core install too fast and this doesn't look good right now as Yarn doesn't give info fast enough.
|
||||
// If we migrate to yarn v2 with the api we might be able to get more info and reenable that
|
||||
// const progress = logger.startProgress("installing core...");
|
||||
selectedVersion = await (
|
||||
await extensionManager
|
||||
).installPackage(pkg, force, 5 * 60 * 1000, (installer) =>
|
||||
installer.Message.Subscribe((s, m) => {
|
||||
if (args.debug) console.log(`Installer: ${m}`);
|
||||
}),
|
||||
);
|
||||
).installPackage(pkg, force, 5 * 60 * 1000, (status) => {
|
||||
// progress.update({ ...status });
|
||||
});
|
||||
// progress.stop();
|
||||
|
||||
if (args.debug) {
|
||||
console.log(`Extension location: ${selectedVersion.packageJsonPath}`);
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
// everything else.
|
||||
import { resolve } from "path";
|
||||
|
||||
import { IAutorestLogger } from "@autorest/common";
|
||||
import { GenerationResults, IFileSystem, AutoRest as IAutoRest } from "autorest-core";
|
||||
import { LanguageClient } from "vscode-languageclient";
|
||||
|
||||
|
@ -76,29 +77,6 @@ let coreModule: any = undefined;
|
|||
let busy = false;
|
||||
let modulePath: string | undefined = undefined;
|
||||
|
||||
/**
|
||||
* Returns the language service entrypoint for autorest-core, bootstrapping the core if necessary
|
||||
*
|
||||
* If initialize has already been called, then it returns the version that was initialized, regardless of parameters
|
||||
*
|
||||
* @param requestedVersion an npm package reference for the version requested @see {@link https://docs.npmjs.com/cli/install#description}
|
||||
*
|
||||
* @param minimumVersion - a semver string representing the lowest autorest- core version that is considered acceptable.
|
||||
*
|
||||
* @see { @link initialize }
|
||||
*/
|
||||
export async function getLanguageServiceEntrypoint(
|
||||
requestedVersion = "latest-installed",
|
||||
minimumVersion?: string,
|
||||
): Promise<string | undefined> {
|
||||
if (!modulePath && !busy) {
|
||||
// if we haven't already got autorest-core, let's do that now with the default settings.
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
await initialize(requestedVersion, minimumVersion);
|
||||
}
|
||||
return resolveEntrypoint(modulePath!, "language-service");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the command-line application entrypoint for autorest-core, bootstrapping the core if necessary
|
||||
*
|
||||
|
@ -111,13 +89,14 @@ export async function getLanguageServiceEntrypoint(
|
|||
* @see {@link initialize}
|
||||
* */
|
||||
export async function getApplicationEntrypoint(
|
||||
logger: IAutorestLogger,
|
||||
requestedVersion = "latest-installed",
|
||||
minimumVersion?: string,
|
||||
): Promise<string | undefined> {
|
||||
if (!modulePath && !busy) {
|
||||
// if we haven't already got autorest-core, let's do that now with the default settings.
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
await initialize(requestedVersion, minimumVersion);
|
||||
await initialize(logger, requestedVersion, minimumVersion);
|
||||
}
|
||||
return resolveEntrypoint(modulePath!, "app");
|
||||
}
|
||||
|
@ -137,7 +116,11 @@ export async function getApplicationEntrypoint(
|
|||
*
|
||||
* @param minimumVersion - a semver string representing the lowest autorest-core version that is considered acceptable.
|
||||
*/
|
||||
export async function initialize(requestedVersion = "latest-installed", minimumVersion?: string) {
|
||||
export async function initialize(
|
||||
logger: IAutorestLogger,
|
||||
requestedVersion = "latest-installed",
|
||||
minimumVersion?: string,
|
||||
) {
|
||||
if (modulePath) {
|
||||
return;
|
||||
}
|
||||
|
@ -166,7 +149,7 @@ export async function initialize(requestedVersion = "latest-installed", minimumV
|
|||
|
||||
// logic to resolve and optionally install a autorest core package.
|
||||
// will throw if it's not doable.
|
||||
const selectedVersion = await selectVersion(requestedVersion, false, minimumVersion);
|
||||
const selectedVersion = await selectVersion(logger, requestedVersion, false, minimumVersion);
|
||||
modulePath = await resolveEntrypoint(await selectedVersion.modulePath, "module");
|
||||
if (!modulePath) {
|
||||
rejectAutoRest(
|
||||
|
@ -180,10 +163,10 @@ export async function initialize(requestedVersion = "latest-installed", minimumV
|
|||
}
|
||||
|
||||
/** Bootstraps the core module if it's not already done and returns the AutoRest class. */
|
||||
async function ensureCoreLoaded(): Promise<IAutoRest> {
|
||||
async function ensureCoreLoaded(logger: IAutorestLogger): Promise<IAutoRest> {
|
||||
if (!modulePath && !busy) {
|
||||
// if we haven't already got autorest-core, let's do that now with the default settings.
|
||||
await initialize();
|
||||
await initialize(logger);
|
||||
}
|
||||
|
||||
if (modulePath && !coreModule) {
|
||||
|
@ -209,10 +192,14 @@ async function ensureCoreLoaded(): Promise<IAutoRest> {
|
|||
*
|
||||
* @param configFileOrFolderUri - a URI pointing to the folder or autorest configuration file
|
||||
*/
|
||||
export async function create(fileSystem?: IFileSystem, configFileOrFolderUri?: string): Promise<AutoRest> {
|
||||
export async function create(
|
||||
logger: IAutorestLogger,
|
||||
fileSystem?: IFileSystem,
|
||||
configFileOrFolderUri?: string,
|
||||
): Promise<AutoRest> {
|
||||
if (!modulePath && !busy) {
|
||||
// if we haven't already got autorest-core, let's do that now with the default settings.
|
||||
await initialize();
|
||||
await initialize(logger);
|
||||
}
|
||||
|
||||
if (modulePath && !coreModule) {
|
||||
|
@ -235,8 +222,8 @@ export async function create(fileSystem?: IFileSystem, configFileOrFolderUri?: s
|
|||
*
|
||||
* @param content - the document content to evaluate
|
||||
*/
|
||||
export async function isOpenApiDocument(content: string): Promise<boolean> {
|
||||
await ensureCoreLoaded();
|
||||
export async function isOpenApiDocument(logger: IAutorestLogger, content: string): Promise<boolean> {
|
||||
await ensureCoreLoaded(logger);
|
||||
return coreModule.IsOpenApiDocument(content);
|
||||
}
|
||||
|
||||
|
@ -250,8 +237,8 @@ export async function isOpenApiDocument(content: string): Promise<boolean> {
|
|||
*
|
||||
* @see {@link DocumentType}
|
||||
*/
|
||||
export async function identifyDocument(content: string): Promise<DocumentType> {
|
||||
await ensureCoreLoaded();
|
||||
export async function identifyDocument(logger: IAutorestLogger, content: string): Promise<DocumentType> {
|
||||
await ensureCoreLoaded(logger);
|
||||
return await coreModule.IdentifyDocument(content);
|
||||
}
|
||||
|
||||
|
@ -261,124 +248,7 @@ export async function identifyDocument(content: string): Promise<DocumentType> {
|
|||
*
|
||||
* @returns the content as a JSON string (not a JSON DOM)
|
||||
*/
|
||||
export async function toJSON(content: string): Promise<string> {
|
||||
await ensureCoreLoaded();
|
||||
export async function toJSON(logger: IAutorestLogger, content: string): Promise<string> {
|
||||
await ensureCoreLoaded(logger);
|
||||
return await coreModule.LiterateToJson(content);
|
||||
}
|
||||
|
||||
/** This is a convenience class for accessing the requests supported by AutoRest when used as a language service */
|
||||
export class AutoRestLanguageService {
|
||||
/**
|
||||
* Represents a convenience layer on the remote language service functions (on top of LSP-defined functions)
|
||||
*
|
||||
* @constructor
|
||||
*
|
||||
* this requires a reference to the language client so that the methods can await the onReady signal
|
||||
* before attempting to send requests.
|
||||
*/
|
||||
public constructor(private languageClient: LanguageClient) {}
|
||||
|
||||
/**
|
||||
* Runs autorest to process a file
|
||||
*
|
||||
* @param documentUri The OpenApi document or AutoRest configuration file to use for the generation
|
||||
*
|
||||
* @param language The language to generate code for. (This is a convenience; it could have been expressed in the configuration)
|
||||
*
|
||||
* @param configuration Additional configuration to pass to AutoRest -- this overrides any defaults or content in the configuration file
|
||||
* @returns async: a 'generated' object containg the output from the generation run.
|
||||
* @see generated
|
||||
*
|
||||
*/
|
||||
|
||||
public async generate(documentUri: string, language: string, configuration: any): Promise<GenerationResults> {
|
||||
// don't call before the client is ready.
|
||||
await this.languageClient.onReady();
|
||||
return await this.languageClient.sendRequest<GenerationResults>("generate", {
|
||||
documentUri: documentUri,
|
||||
language: language,
|
||||
configuration: configuration,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a file is an OpenAPI document (2.0)
|
||||
*
|
||||
* @param contentOrUri either a URL to a file on disk or http/s, or the content of a file itself.
|
||||
* @returns async:
|
||||
* true - the file is an OpenAPI 2.0 document
|
||||
* false - the file was not recognized.
|
||||
*/
|
||||
public async isOpenApiDocument(contentOrUri: string): Promise<boolean> {
|
||||
// don't call before the client is ready.
|
||||
await this.languageClient.onReady();
|
||||
|
||||
return await this.languageClient.sendRequest<boolean>("isOpenApiDocument", { contentOrUri: contentOrUri });
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a file is an AutoRest configuration file (checks for the magic string `\n> see https://aka.ms/autorest` )
|
||||
*
|
||||
* @param contentOrUri either a URL to a file on disk or http/s, or the content of a file itself.
|
||||
* @returns async:
|
||||
* true - the file is an autorest configuration file
|
||||
* false - the file was not recognized.
|
||||
*/
|
||||
public async isConfigurationDocument(contentOrUri: string): Promise<boolean> {
|
||||
// don't call before the client is ready.
|
||||
await this.languageClient.onReady();
|
||||
|
||||
return await this.languageClient.sendRequest<boolean>("isConfigurationDocument", { contentOrUri: contentOrUri });
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file as a JSON string. This can be a .YAML, .MD or .JSON file to begin with.
|
||||
*
|
||||
* @param contentOrUri either a URL to a file on disk or http/s, or the content of a file itself.
|
||||
* @returns async: string containing the file as JSON
|
||||
*/
|
||||
public async toJSON(contentOrUri: string): Promise<string> {
|
||||
// don't call before the client is ready.
|
||||
await this.languageClient.onReady();
|
||||
|
||||
return await this.languageClient.sendRequest<string>("toJSON", { contentOrUri: contentOrUri });
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the configuration file for a given document URI.
|
||||
*
|
||||
* @param documentUri the URL to a file on disk or http/s. The passed in file can be an OpenAPI file or an AutoRest configuration file.
|
||||
* @returns async: the URI to the configuration file or an empty string if no configuration could be found.
|
||||
*
|
||||
*/
|
||||
public async detectConfigurationFile(documentUri: string): Promise<string> {
|
||||
// don't call before the client is ready.
|
||||
await this.languageClient.onReady();
|
||||
|
||||
return await this.languageClient.sendRequest<string>("detectConfigurationFile", { documentUri: documentUri });
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a file is an OpenAPI document or a configuration file in one attempt.
|
||||
*
|
||||
* @param contentOrUri either a URL to a file on disk or http/s, or the content of a file itself.
|
||||
* @returns async:
|
||||
* true - the file is a configuration file or OpenAPI (2.0) file
|
||||
* false - the file was not recognized.
|
||||
*/
|
||||
public async isSupportedDocument(languageId: string, contentOrUri: string): Promise<boolean> {
|
||||
// don't call before the client is ready.
|
||||
await this.languageClient.onReady();
|
||||
|
||||
return await this.languageClient.sendRequest<boolean>("isSupportedDocument", {
|
||||
languageId: languageId,
|
||||
contentOrUri: contentOrUri,
|
||||
});
|
||||
}
|
||||
|
||||
public async identifyDocument(contentOrUri: string): Promise<DocumentType> {
|
||||
// don't call before the client is ready.
|
||||
await this.languageClient.onReady();
|
||||
return await this.languageClient.sendRequest<DocumentType>("identifyDocument", { contentOrUri: contentOrUri });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ export async function loadConfig(sink: LoggerSink, args: AutorestArgs): Promise<
|
|||
});
|
||||
|
||||
const loader = new ConfigurationLoader(logger, defaultConfigUri, configFileOrFolder, {
|
||||
extensionManager: await extensionManager,
|
||||
// extensionManager: await extensionManager,
|
||||
});
|
||||
try {
|
||||
const { config } = await loader.load([args], true);
|
||||
|
@ -77,7 +77,10 @@ export async function resolvePathForLocalVersion(requestedVersion: string | null
|
|||
return undefined;
|
||||
}
|
||||
|
||||
export async function resolveCoreVersion(config: AutorestNormalizedConfiguration = {}): Promise<string> {
|
||||
export async function resolveCoreVersion(
|
||||
logger: IAutorestLogger,
|
||||
config: AutorestNormalizedConfiguration = {},
|
||||
): Promise<string> {
|
||||
const requestedVersion: string = getRequestedCoreVersion(config) ?? "latest-installed";
|
||||
|
||||
const localVersion = await resolvePathForLocalVersion(config.version ? requestedVersion : null);
|
||||
|
@ -88,7 +91,7 @@ export async function resolveCoreVersion(config: AutorestNormalizedConfiguration
|
|||
// failing that, we'll continue on and see if NPM can do something with the version.
|
||||
if (config.debug) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Network Enabled: ${await networkEnabled}`);
|
||||
logger.debug(`Network Enabled: ${await networkEnabled}`);
|
||||
}
|
||||
|
||||
// wait for the bootstrapper check to finish.
|
||||
|
@ -96,7 +99,7 @@ export async function resolveCoreVersion(config: AutorestNormalizedConfiguration
|
|||
|
||||
// logic to resolve and optionally install a autorest core package.
|
||||
// will throw if it's not doable.
|
||||
const selectedVersion = await selectVersion(requestedVersion, config.debugger);
|
||||
const selectedVersion = await selectVersion(logger, requestedVersion, config.debugger);
|
||||
return selectedVersion.modulePath;
|
||||
}
|
||||
|
||||
|
|
|
@ -11,13 +11,12 @@ import {
|
|||
color,
|
||||
ConsoleLogger,
|
||||
FilterLogger,
|
||||
AutorestLogger,
|
||||
AutorestSyncLogger,
|
||||
Exception,
|
||||
IAutorestLogger,
|
||||
} from "@autorest/common";
|
||||
import { AutorestCliArgs, parseAutorestCliArgs, getLogLevel } from "@autorest/configuration";
|
||||
|
||||
import { AutorestCliArgs, parseAutorestCliArgs } from "@autorest/configuration";
|
||||
EventEmitter.defaultMaxListeners = 100;
|
||||
process.env["ELECTRON_RUN_AS_NODE"] = "1";
|
||||
delete process.env["ELECTRON_NO_ATTACH_CONSOLE"];
|
||||
|
@ -41,7 +40,6 @@ import { printAutorestHelp } from "./commands";
|
|||
import { Artifact } from "./lib/artifact";
|
||||
import { AutoRest, IsOpenApiDocument, Shutdown } from "./lib/autorest-core";
|
||||
import { VERSION } from "./lib/constants";
|
||||
import { getLogLevel } from "./lib/context";
|
||||
|
||||
let verbose = false;
|
||||
let debug = false;
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
AutorestConfiguration,
|
||||
AutorestRawConfiguration,
|
||||
ConfigurationLoader,
|
||||
getLogLevel,
|
||||
getNestedConfiguration,
|
||||
mergeConfigurations,
|
||||
ResolvedExtension,
|
||||
|
@ -21,7 +22,7 @@ import { last } from "lodash";
|
|||
import { AppRoot } from "../constants";
|
||||
import { AutoRestExtension } from "../pipeline/plugin-endpoint";
|
||||
import { StatsCollector } from "../stats";
|
||||
import { AutorestContext, getLogLevel } from "./autorest-context";
|
||||
import { AutorestContext } from "./autorest-context";
|
||||
import { MessageEmitter } from "./message-emitter";
|
||||
|
||||
const inWebpack = typeof __webpack_require__ === "function";
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
AutorestConfiguration,
|
||||
arrayOf,
|
||||
extendAutorestConfiguration,
|
||||
getLogLevel,
|
||||
} from "@autorest/configuration";
|
||||
|
||||
import { DataStore, CachingFileSystem } from "@azure-tools/datastore";
|
||||
|
@ -32,7 +33,7 @@ import { MessageEmitter } from "./message-emitter";
|
|||
export class AutorestContext implements IAutorestLogger {
|
||||
public config: AutorestConfiguration;
|
||||
public configFileFolderUri: string;
|
||||
private logger: AutorestLogger;
|
||||
public logger: AutorestLogger;
|
||||
private originalLogger: AutorestLogger;
|
||||
|
||||
public constructor(
|
||||
|
@ -90,6 +91,10 @@ export class AutorestContext implements IAutorestLogger {
|
|||
this.logger.log(log);
|
||||
}
|
||||
|
||||
public startProgress(initialName?: string) {
|
||||
return this.logger.startProgress(initialName);
|
||||
}
|
||||
|
||||
public get diagnostics() {
|
||||
return this.logger.diagnostics;
|
||||
}
|
||||
|
@ -251,10 +256,6 @@ export class AutorestContext implements IAutorestLogger {
|
|||
}
|
||||
}
|
||||
|
||||
export function getLogLevel(config: AutorestNormalizedConfiguration): LogLevel {
|
||||
return config.debug ? "debug" : config.verbose ? "verbose" : config.level ?? "information";
|
||||
}
|
||||
|
||||
export function getLogSuppressions(config: AutorestConfiguration): LogSuppression[] {
|
||||
const legacySuppressions: LogSuppression[] = resolveDirectives(config, (x) => x.suppress.length > 0).map((x) => {
|
||||
return {
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
"@types/node": "~14.14.20",
|
||||
"@typescript-eslint/eslint-plugin": "^4.12.0",
|
||||
"@typescript-eslint/parser": "^4.12.0",
|
||||
"@types/cli-progress": "~3.9.2",
|
||||
"eslint-plugin-jest": "~24.3.2",
|
||||
"eslint-plugin-node": "~11.1.0",
|
||||
"eslint-plugin-prettier": "~3.4.0",
|
||||
|
@ -47,6 +48,7 @@
|
|||
"@azure-tools/yaml": "~1.0.0",
|
||||
"@azure-tools/json": "~1.2.0",
|
||||
"@azure/logger": "^1.0.2",
|
||||
"chalk": "^4.1.0"
|
||||
"chalk": "^4.1.0",
|
||||
"cli-progress": "~3.9.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,149 @@
|
|||
import { Presets, SingleBar } from "cli-progress";
|
||||
import { ConsoleLogger } from "./console-logger-sink";
|
||||
|
||||
class MockTTYStream {
|
||||
// class MockTTYStream implements NodeJS.WritableStream {
|
||||
public readonly isTTY = true;
|
||||
|
||||
public currentOutput = "";
|
||||
private lines: string[] = [];
|
||||
private currentLine = Buffer.from("");
|
||||
private cursor = 0;
|
||||
|
||||
public constructor() {}
|
||||
|
||||
public clearLine(number: string) {
|
||||
this.lines.splice(-number);
|
||||
this.updateOutput();
|
||||
}
|
||||
|
||||
public write(b: Buffer | string) {
|
||||
const content = b.toString();
|
||||
if (content.startsWith("\x1b")) {
|
||||
// Parse the reset cursor value. not really correct but does the job for now for testing.
|
||||
this.cursor = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const [first, ...lines] = content.toString().split("\n");
|
||||
this.currentLine = Buffer.concat([this.currentLine.slice(0, this.cursor), Buffer.from(first)]);
|
||||
this.cursor = this.currentLine.length;
|
||||
for (const line of lines) {
|
||||
this.lines.push(this.currentLine.toString());
|
||||
this.currentLine = Buffer.from(line);
|
||||
this.cursor = this.currentLine.length;
|
||||
}
|
||||
this.updateOutput();
|
||||
}
|
||||
|
||||
private updateOutput() {
|
||||
this.currentOutput = [...this.lines, this.currentLine.toString()].join("\n");
|
||||
}
|
||||
}
|
||||
|
||||
describe("ConsoleLogger", () => {
|
||||
let logger: ConsoleLogger;
|
||||
let mockStream: MockTTYStream;
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
mockStream = new MockTTYStream();
|
||||
});
|
||||
|
||||
function expectStreamWrite(line: string) {
|
||||
expect(mockStream.currentOutput).toEqual(`${line}\n`);
|
||||
}
|
||||
|
||||
describe("pretty format", () => {
|
||||
beforeEach(() => {
|
||||
logger = new ConsoleLogger({
|
||||
stream: mockStream as any,
|
||||
color: false,
|
||||
timestamp: false,
|
||||
progressNoTTYOutput: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("log debug", () => {
|
||||
logger.debug("This is some debug");
|
||||
expectStreamWrite("debug | This is some debug");
|
||||
});
|
||||
|
||||
it("log verbose", () => {
|
||||
logger.verbose("This is some verbose");
|
||||
expectStreamWrite("verbose | This is some verbose");
|
||||
});
|
||||
|
||||
it("log information", () => {
|
||||
logger.info("This is some information");
|
||||
expectStreamWrite("info | This is some information");
|
||||
});
|
||||
|
||||
it("log warning", () => {
|
||||
logger.trackWarning({
|
||||
code: "TestWarning",
|
||||
message: "This is some warning",
|
||||
});
|
||||
expectStreamWrite("warning | TestWarning | This is some warning");
|
||||
});
|
||||
|
||||
it("log error", () => {
|
||||
logger.trackError({
|
||||
code: "TestError",
|
||||
message: "This is some error",
|
||||
});
|
||||
expectStreamWrite("error | TestError | This is some error");
|
||||
});
|
||||
|
||||
it("log fatal", () => {
|
||||
logger.fatal("This is some fatal error");
|
||||
expectStreamWrite("fatal | This is some fatal error");
|
||||
});
|
||||
|
||||
it("log progress", async () => {
|
||||
const progress = logger.startProgress("MyProgress");
|
||||
progress.update({ current: 10, total: 200 });
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
expect(mockStream.currentOutput).toEqual("MyProgress [==--------------------------------------] 5% | 10/200");
|
||||
progress.update({ current: 20, total: 200 });
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
expect(mockStream.currentOutput).toEqual("MyProgress [====------------------------------------] 10% | 20/200");
|
||||
progress.stop();
|
||||
});
|
||||
|
||||
it("append logs above progress", async () => {
|
||||
const progress = logger.startProgress("MyProgress");
|
||||
progress.update({ current: 10, total: 200 });
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
expect(mockStream.currentOutput).toEqual("MyProgress [==--------------------------------------] 5% | 10/200");
|
||||
logger.info("Something happening during progress");
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
expect(mockStream.currentOutput).toEqual(
|
||||
"info | Something happening during progress\nMyProgress [==--------------------------------------] 5% | 10/200",
|
||||
);
|
||||
progress.stop();
|
||||
});
|
||||
});
|
||||
|
||||
describe("json format", () => {
|
||||
beforeEach(() => {
|
||||
logger = new ConsoleLogger({
|
||||
stream: mockStream as any,
|
||||
format: "json",
|
||||
timestamp: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("log debug", () => {
|
||||
logger.debug("This is some debug");
|
||||
expectStreamWrite('{"level":"debug","message":"This is some debug"}');
|
||||
});
|
||||
|
||||
it("log warning", () => {
|
||||
logger.trackWarning({
|
||||
code: "TestWarning",
|
||||
message: "This is some warning",
|
||||
});
|
||||
expectStreamWrite('{"level":"warning","code":"TestWarning","message":"This is some warning"}');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,11 +1,23 @@
|
|||
import { WriteStream } from "tty";
|
||||
import progressBar from "cli-progress";
|
||||
import { createLogFormatter, LogFormatter } from "./formatter";
|
||||
import { AutorestSyncLogger } from "./logger";
|
||||
import { LoggerSink, LogInfo } from "./types";
|
||||
import { LoggerSink, LogInfo, Progress, ProgressTracker } from "./types";
|
||||
|
||||
export interface ConsoleLoggerSinkOptions {
|
||||
/**
|
||||
* Stream to use for output. (@default stdout)
|
||||
*/
|
||||
stream?: NodeJS.WritableStream;
|
||||
|
||||
format?: "json" | "regular";
|
||||
color?: boolean;
|
||||
timestamp?: boolean;
|
||||
|
||||
/**
|
||||
* Enable output for non TTY output(e.g. file) for the progress file.
|
||||
*/
|
||||
progressNoTTYOutput?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -13,15 +25,105 @@ export interface ConsoleLoggerSinkOptions {
|
|||
*/
|
||||
export class ConsoleLoggerSink implements LoggerSink {
|
||||
private formatter: LogFormatter;
|
||||
private currentProgressBar: progressBar.MultiBar | undefined;
|
||||
private bars: progressBar.SingleBar[] = [];
|
||||
private pendingLogs: string[] = [];
|
||||
private format: "json" | "regular";
|
||||
private stream: NodeJS.WritableStream;
|
||||
|
||||
public constructor(options: ConsoleLoggerSinkOptions = {}) {
|
||||
public constructor(private options: ConsoleLoggerSinkOptions = {}) {
|
||||
this.stream = options.stream ?? process.stdout;
|
||||
this.format = options.format ?? "regular";
|
||||
this.formatter = createLogFormatter(options.format, options);
|
||||
}
|
||||
|
||||
public log(log: LogInfo) {
|
||||
const line = this.formatter.log(log);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(line);
|
||||
if (this.currentProgressBar) {
|
||||
this.pendingLogs.push(line);
|
||||
} else {
|
||||
this.writeLine(line);
|
||||
}
|
||||
}
|
||||
|
||||
public startProgress(initialName?: string): ProgressTracker {
|
||||
if (this.format === "regular") {
|
||||
return this.startProgressBar(initialName);
|
||||
} else {
|
||||
return NoopProgress;
|
||||
}
|
||||
}
|
||||
|
||||
private startProgressBar(initialName?: string): ProgressTracker {
|
||||
if (this.currentProgressBar === undefined) {
|
||||
this.currentProgressBar = new progressBar.MultiBar(
|
||||
{
|
||||
hideCursor: true,
|
||||
stream: this.stream,
|
||||
noTTYOutput: this.options.progressNoTTYOutput,
|
||||
format: "{name} [{bar}] {percentage}% | {value}/{total}",
|
||||
forceRedraw: true, // without this the bar is flickering,
|
||||
},
|
||||
progressBar.Presets.legacy,
|
||||
);
|
||||
}
|
||||
const multiBar = this.currentProgressBar;
|
||||
|
||||
multiBar.on("redraw-pre", () => {
|
||||
if (this.pendingLogs.length > 0) {
|
||||
if ("clearLine" in this.stream) {
|
||||
(this.stream as WriteStream).clearLine(1);
|
||||
}
|
||||
}
|
||||
while (this.pendingLogs.length > 0) {
|
||||
this.writeLine(this.pendingLogs.shift());
|
||||
}
|
||||
});
|
||||
|
||||
multiBar.on("stop", () => {
|
||||
this.currentProgressBar = undefined;
|
||||
while (this.pendingLogs.length > 0) {
|
||||
this.writeLine(this.pendingLogs.shift());
|
||||
}
|
||||
});
|
||||
|
||||
let bar: progressBar.SingleBar | undefined;
|
||||
|
||||
const update = (progress: Progress) => {
|
||||
const name = progress.name ?? initialName ?? "progress";
|
||||
if (bar === undefined) {
|
||||
bar = multiBar.create(progress.total, 0, { name });
|
||||
this.bars.push(bar);
|
||||
} else {
|
||||
bar.setTotal(progress.total);
|
||||
}
|
||||
|
||||
bar.update(progress.current, { name });
|
||||
};
|
||||
|
||||
const stop = () => {
|
||||
if (bar) {
|
||||
bar.update(bar.getTotal());
|
||||
bar.stop();
|
||||
multiBar.remove(bar);
|
||||
this.bars = this.bars.filter((x) => x !== bar);
|
||||
if (this.bars.length === 0) {
|
||||
multiBar.stop();
|
||||
this.currentProgressBar = undefined;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
update,
|
||||
stop,
|
||||
};
|
||||
}
|
||||
|
||||
private writeLine(line: string | undefined) {
|
||||
if (line !== undefined) {
|
||||
this.stream.write(Buffer.from(`${line}\n`));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,3 +136,8 @@ export class ConsoleLogger extends AutorestSyncLogger {
|
|||
super({ sinks: [new ConsoleLoggerSink(options)] });
|
||||
}
|
||||
}
|
||||
|
||||
const NoopProgress = {
|
||||
update: () => null,
|
||||
stop: () => null,
|
||||
};
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
/* eslint-disable no-console */
|
||||
import { ConsoleLogger } from ".";
|
||||
|
||||
global.console = { log: jest.fn() } as any;
|
||||
|
||||
describe("ConsoleLogger", () => {
|
||||
let logger: ConsoleLogger;
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe("pretty format", () => {
|
||||
beforeEach(() => {
|
||||
logger = new ConsoleLogger({
|
||||
color: false,
|
||||
timestamp: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("log debug", () => {
|
||||
logger.debug("This is some debug");
|
||||
expect(console.log).toHaveBeenCalledTimes(1);
|
||||
expect(console.log).toHaveBeenCalledWith("debug | This is some debug");
|
||||
});
|
||||
|
||||
it("log verbose", () => {
|
||||
logger.verbose("This is some verbose");
|
||||
expect(console.log).toHaveBeenCalledTimes(1);
|
||||
expect(console.log).toHaveBeenCalledWith("verbose | This is some verbose");
|
||||
});
|
||||
|
||||
it("log information", () => {
|
||||
logger.info("This is some information");
|
||||
expect(console.log).toHaveBeenCalledTimes(1);
|
||||
expect(console.log).toHaveBeenCalledWith("info | This is some information");
|
||||
});
|
||||
|
||||
it("log warning", () => {
|
||||
logger.trackWarning({
|
||||
code: "TestWarning",
|
||||
message: "This is some warning",
|
||||
});
|
||||
expect(console.log).toHaveBeenCalledTimes(1);
|
||||
expect(console.log).toHaveBeenCalledWith("warning | TestWarning | This is some warning");
|
||||
});
|
||||
|
||||
it("log error", () => {
|
||||
logger.trackError({
|
||||
code: "TestError",
|
||||
message: "This is some error",
|
||||
});
|
||||
expect(console.log).toHaveBeenCalledTimes(1);
|
||||
expect(console.log).toHaveBeenCalledWith("error | TestError | This is some error");
|
||||
});
|
||||
|
||||
it("log fatal", () => {
|
||||
logger.fatal("This is some fatal error");
|
||||
expect(console.log).toHaveBeenCalledTimes(1);
|
||||
expect(console.log).toHaveBeenCalledWith("fatal | This is some fatal error");
|
||||
});
|
||||
});
|
||||
|
||||
describe("json format", () => {
|
||||
beforeEach(() => {
|
||||
logger = new ConsoleLogger({
|
||||
format: "json",
|
||||
timestamp: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("log debug", () => {
|
||||
logger.debug("This is some debug");
|
||||
expect(console.log).toHaveBeenCalledTimes(1);
|
||||
expect(console.log).toHaveBeenCalledWith('{"level":"debug","message":"This is some debug"}');
|
||||
});
|
||||
|
||||
it("log warning", () => {
|
||||
logger.trackWarning({
|
||||
code: "TestWarning",
|
||||
message: "This is some warning",
|
||||
});
|
||||
expect(console.log).toHaveBeenCalledTimes(1);
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'{"level":"warning","code":"TestWarning","message":"This is some warning"}',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -3,6 +3,7 @@ import { AutorestSyncLogger, FilterLogger, FilterLoggerOptions } from ".";
|
|||
describe("FilterLogger", () => {
|
||||
const sink = {
|
||||
log: jest.fn(),
|
||||
startProgress: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -8,6 +8,7 @@ describe("Logger", () => {
|
|||
beforeEach(() => {
|
||||
sink = {
|
||||
log: jest.fn(),
|
||||
startProgress: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -28,6 +29,7 @@ describe("Logger", () => {
|
|||
it("sends message to each sink", () => {
|
||||
const otherSink = {
|
||||
log: jest.fn(),
|
||||
startProgress: jest.fn(),
|
||||
};
|
||||
|
||||
const logger = new AutorestSyncLogger({
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
LoggerSink,
|
||||
LogInfo,
|
||||
} from "./types";
|
||||
import { Progress } from ".";
|
||||
|
||||
export interface AutorestLoggerBaseOptions<T> {
|
||||
processors?: T[];
|
||||
|
@ -67,6 +68,27 @@ export abstract class AutorestLoggerBase<T> implements AutorestLogger {
|
|||
});
|
||||
}
|
||||
|
||||
public startProgress(initialName?: string) {
|
||||
const sinkProgressTrackers = this.sinks.map((x) => x.startProgress(initialName));
|
||||
|
||||
const update = (progress: Progress) => {
|
||||
for (const tracker of sinkProgressTrackers) {
|
||||
tracker.update(progress);
|
||||
}
|
||||
};
|
||||
|
||||
const stop = () => {
|
||||
for (const tracker of sinkProgressTrackers) {
|
||||
tracker.stop();
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
update,
|
||||
stop,
|
||||
};
|
||||
}
|
||||
|
||||
protected emitLog(log: LogInfo) {
|
||||
for (const sink of this.sinks) {
|
||||
sink.log(log);
|
||||
|
|
|
@ -67,6 +67,28 @@ export interface AutorestError extends Omit<AutorestDiagnostic, "level"> {}
|
|||
|
||||
export interface AutorestWarning extends Omit<AutorestDiagnostic, "level"> {}
|
||||
|
||||
export interface Progress {
|
||||
/**
|
||||
* Current step.
|
||||
*/
|
||||
current: number;
|
||||
|
||||
/**
|
||||
* Total number of steps.
|
||||
*/
|
||||
total: number;
|
||||
|
||||
/**
|
||||
* Optional name
|
||||
*/
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export interface ProgressTracker {
|
||||
update(progress: Progress): void;
|
||||
stop(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* AutorestLogger is an interface for the autorest logger that can be passed around in plugins.
|
||||
* This can be used to log information, debug logs or track errors and warnings.
|
||||
|
@ -91,6 +113,8 @@ export interface IAutorestLogger {
|
|||
trackWarning(error: AutorestWarning): void;
|
||||
|
||||
log(log: LogInfo): void;
|
||||
|
||||
startProgress(initialName?: string): ProgressTracker;
|
||||
}
|
||||
|
||||
export interface AutorestLogger extends IAutorestLogger {
|
||||
|
@ -119,6 +143,7 @@ export interface LoggerAsyncProcessor {
|
|||
|
||||
export interface LoggerSink {
|
||||
log(info: LogInfo): void;
|
||||
startProgress(initialName?: string): ProgressTracker;
|
||||
}
|
||||
|
||||
export type EnhancedLogInfo = Omit<LogInfo, "source"> & {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { AutorestLogger, OperationAbortedException } from "@autorest/common";
|
||||
import { exists, filePath } from "@azure-tools/async-io";
|
||||
import { DataStore, IFileSystem, RealFileSystem, CachingFileSystem } from "@azure-tools/datastore";
|
||||
import { Extension, ExtensionManager, LocalExtension } from "@azure-tools/extension";
|
||||
import { Extension, ExtensionManager, LocalExtension, PackageInstallProgress } from "@azure-tools/extension";
|
||||
import { createFileUri, resolveUri, simplifyUri, fileUriToPath } from "@azure-tools/uri";
|
||||
import { last } from "lodash";
|
||||
import untildify from "untildify";
|
||||
|
@ -45,6 +45,11 @@ export interface ConfigurationLoaderOptions {
|
|||
extensionManager?: ExtensionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Timeout in ms.
|
||||
*/
|
||||
const InstallPackageTimeout = 5 * 60 * 1000;
|
||||
|
||||
/**
|
||||
* Class handling the loading of an autorest configuration.
|
||||
*/
|
||||
|
@ -280,14 +285,29 @@ export class ConfigurationLoader {
|
|||
} else {
|
||||
// acquire extension
|
||||
const pack = await extMgr.findPackage(extensionDef.name, extensionDef.source);
|
||||
this.logger.info(`> Installing AutoRest extension '${extensionDef.name}' (${extensionDef.source})`);
|
||||
const extension = await extMgr.installPackage(pack, false, 5 * 60 * 1000, (progressInit: any) =>
|
||||
progressInit.Message.Subscribe((s: any, m: any) => this.logger.verbose(m)),
|
||||
);
|
||||
this.logger.info(
|
||||
`> Installed AutoRest extension '${extensionDef.name}' (${extensionDef.source}->${extension.version})`,
|
||||
`> Installing AutoRest extension '${extensionDef.name}' (${extensionDef.source} -> ${pack.version})`,
|
||||
);
|
||||
return extension;
|
||||
const progress = this.logger.startProgress("installing...");
|
||||
try {
|
||||
const extension = await extMgr.installPackage(
|
||||
pack,
|
||||
false,
|
||||
InstallPackageTimeout,
|
||||
(status: PackageInstallProgress) => {
|
||||
progress.update({ ...status });
|
||||
},
|
||||
);
|
||||
progress.stop();
|
||||
|
||||
this.logger.info(
|
||||
`> Installed AutoRest extension '${extensionDef.name}' (${extensionDef.source}->${extension.version})`,
|
||||
);
|
||||
return extension;
|
||||
} catch (e) {
|
||||
progress.stop();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import fs from "fs";
|
||||
import { fileURLToPath, URL, Url } from "url";
|
||||
import { LogLevel } from "@autorest/common";
|
||||
import { AutorestNormalizedConfiguration } from "./autorest-normalized-configuration";
|
||||
|
||||
export function isIterable(target: any): target is Iterable<any> {
|
||||
return !!target && typeof target[Symbol.iterator] === "function";
|
||||
|
@ -26,3 +26,7 @@ export function arrayOf<T>(value: T | T[] | undefined): T[] {
|
|||
}
|
||||
return [value];
|
||||
}
|
||||
|
||||
export function getLogLevel(config: AutorestNormalizedConfiguration): LogLevel {
|
||||
return config.debug ? "debug" : config.verbose ? "verbose" : config.level ?? "information";
|
||||
}
|
||||
|
|
|
@ -41,7 +41,6 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@azure-tools/async-io": "~3.0.0",
|
||||
"@azure-tools/eventing": "~3.0.0",
|
||||
"@azure-tools/tasks": "~3.0.0",
|
||||
"@azure/logger": "^1.0.2",
|
||||
"command-exists": "~1.2.9",
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { Extension } from "./extension";
|
||||
import { PackageManagerLogEntry } from "./package-manager";
|
||||
import { SystemRequirementError } from "./system-requirements";
|
||||
|
||||
export class UnresolvedPackageException extends Error {
|
||||
|
|
|
@ -7,7 +7,6 @@ import { ChildProcess, spawn } from "child_process";
|
|||
import { homedir, tmpdir } from "os";
|
||||
import { basename, delimiter, dirname, extname, isAbsolute, join, normalize, resolve } from "path";
|
||||
import { exists, isDirectory, isFile, mkdir, readdir, readFile, rmdir } from "@azure-tools/async-io";
|
||||
import { Progress, Subscribe } from "@azure-tools/eventing";
|
||||
import { CriticalSection, Delay, Exception, Mutex, shallowCopy, SharedLock } from "@azure-tools/tasks";
|
||||
import { resolve as npmResolvePackage } from "npm-package-arg";
|
||||
import * as pacote from "pacote";
|
||||
|
@ -23,7 +22,7 @@ import {
|
|||
import { Extension, Package } from "./extension";
|
||||
import { logger } from "./logger";
|
||||
import { Npm } from "./npm";
|
||||
import { PackageManager, PackageManagerType } from "./package-manager";
|
||||
import { PackageManager, PackageManagerLogEntry, PackageManagerProgress, PackageManagerType } from "./package-manager";
|
||||
import {
|
||||
patchPythonPath,
|
||||
PythonCommandLine,
|
||||
|
@ -32,6 +31,10 @@ import {
|
|||
} from "./system-requirements";
|
||||
import { Yarn } from "./yarn";
|
||||
|
||||
export interface PackageInstallProgress extends PackageManagerProgress {
|
||||
pkg: Package;
|
||||
}
|
||||
|
||||
function quoteIfNecessary(text: string): string {
|
||||
if (text && text.indexOf(" ") > -1 && text.charAt(0) != '"') {
|
||||
return `"${text}"`;
|
||||
|
@ -321,16 +324,12 @@ export class ExtensionManager {
|
|||
pkg: Package,
|
||||
force?: boolean,
|
||||
maxWait: number = 5 * 60 * 1000,
|
||||
progressInit: Subscribe = () => {},
|
||||
reportProgress: (progress: PackageInstallProgress) => void = () => {},
|
||||
): Promise<Extension> {
|
||||
if (!this.sharedLock) {
|
||||
throw new Exception("Extension manager has been disposed.");
|
||||
}
|
||||
|
||||
const progress = new Progress(progressInit);
|
||||
|
||||
progress.Start.Dispatch(null);
|
||||
|
||||
// will throw if the CriticalSection lock can't be acquired.
|
||||
// we need this so that only one extension at a time can start installing
|
||||
// in this process (since to use NPM right, we have to do a change dir before runinng it)
|
||||
|
@ -343,12 +342,10 @@ export class ExtensionManager {
|
|||
|
||||
const extension = new Extension(pkg, this.installationPath);
|
||||
const release = await new Mutex(extension.location).acquire(maxWait / 2);
|
||||
const cwd = process.cwd();
|
||||
|
||||
try {
|
||||
// change directory
|
||||
process.chdir(this.installationPath);
|
||||
progress.Progress.Dispatch(25);
|
||||
|
||||
if (await isDirectory(extension.location)) {
|
||||
if (!force) {
|
||||
|
@ -359,7 +356,7 @@ export class ExtensionManager {
|
|||
|
||||
// force removal first
|
||||
try {
|
||||
progress.NotifyMessage(`Removing existing extension ${extension.location}`);
|
||||
// progress.NotifyMessage(`Removing existing extension ${extension.location}`);
|
||||
await Delay(100);
|
||||
await rmdir(extension.location);
|
||||
} catch (e) {
|
||||
|
@ -370,23 +367,26 @@ export class ExtensionManager {
|
|||
// create the folder
|
||||
await mkdir(extension.location);
|
||||
|
||||
progress.NotifyMessage(`Installing ${pkg.name}, ${pkg.version}`);
|
||||
|
||||
const results = this.packageManager.install(extension.location, [pkg.packageMetadata._resolved], { force });
|
||||
const promise = this.packageManager.install(
|
||||
extension.location,
|
||||
[pkg.packageMetadata._resolved],
|
||||
{ force },
|
||||
(progress) => {
|
||||
reportProgress({ pkg, ...progress });
|
||||
},
|
||||
);
|
||||
await extensionRelease();
|
||||
|
||||
await results;
|
||||
progress.NotifyMessage(`Package Install completed ${pkg.name}, ${pkg.version}`);
|
||||
|
||||
return extension;
|
||||
} catch (e: any) {
|
||||
progress.NotifyMessage(e);
|
||||
if (e.stack) {
|
||||
progress.NotifyMessage(e.stack);
|
||||
const result = await promise;
|
||||
if (result.success) {
|
||||
return extension;
|
||||
} else {
|
||||
const message = [result.error.message, "", "Installation logs: ", formatLogEntries(result.error.logs)];
|
||||
throw new PackageInstallationException(pkg.name, pkg.version, message.join("\n"));
|
||||
}
|
||||
} catch (e: any) {
|
||||
// clean up the attempted install directory
|
||||
if (await isDirectory(extension.location)) {
|
||||
progress.NotifyMessage(`Cleaning up failed installation: ${extension.location}`);
|
||||
await Delay(100);
|
||||
await rmdir(extension.location);
|
||||
}
|
||||
|
@ -394,13 +394,14 @@ export class ExtensionManager {
|
|||
if (e instanceof Exception) {
|
||||
throw e;
|
||||
}
|
||||
if (e instanceof PackageInstallationException) {
|
||||
throw e;
|
||||
}
|
||||
if (e instanceof Error) {
|
||||
throw new PackageInstallationException(pkg.name, pkg.version, e.message + e.stack);
|
||||
}
|
||||
throw new PackageInstallationException(pkg.name, pkg.version, `${e}`);
|
||||
} finally {
|
||||
progress.Progress.Dispatch(100);
|
||||
progress.End.Dispatch(null);
|
||||
await Promise.all([extensionRelease(), release()]);
|
||||
}
|
||||
}
|
||||
|
@ -516,3 +517,14 @@ export class ExtensionManager {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
function formatLogEntries(entries: PackageManagerLogEntry[]): string {
|
||||
const lines = ["```", ...entries.map(formatLogEntry), "```"];
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
function formatLogEntry(entry: PackageManagerLogEntry): string {
|
||||
const [first, ...lines] = entry.message.split("\n");
|
||||
const spacing = " ".repeat(entry.severity.length);
|
||||
return [`${entry.severity}: ${first}`, ...lines.map((x) => `${spacing} ${x}`)].join("\n");
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { dirname, resolve } from "path";
|
||||
import { execute } from "./exec-cmd";
|
||||
import { ensurePackageJsonExists, InstallOptions, PackageManager } from "./package-manager";
|
||||
import { ensurePackageJsonExists, InstallOptions, PackageInstallationResult, PackageManager } from "./package-manager";
|
||||
|
||||
export const DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org";
|
||||
|
||||
|
@ -21,7 +21,11 @@ export const execNpm = async (cwd: string, ...args: string[]) => {
|
|||
};
|
||||
|
||||
export class Npm implements PackageManager {
|
||||
public async install(directory: string, packages: string[], options?: InstallOptions) {
|
||||
public async install(
|
||||
directory: string,
|
||||
packages: string[],
|
||||
options?: InstallOptions,
|
||||
): Promise<PackageInstallationResult> {
|
||||
await ensurePackageJsonExists(directory);
|
||||
|
||||
const output = await execNpm(
|
||||
|
@ -34,14 +38,16 @@ export class Npm implements PackageManager {
|
|||
...packages,
|
||||
);
|
||||
if (output.error) {
|
||||
/* eslint-disable no-console */
|
||||
console.error("NPM log:");
|
||||
console.log("-".repeat(50));
|
||||
console.error(output.log);
|
||||
console.log("-".repeat(50));
|
||||
/* eslint-enable no-console */
|
||||
throw Error(`Failed to install package '${packages}' -- ${output.error}`);
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
message: `Failed to install package '${packages}' -- ${output.error}`,
|
||||
logs: output.log.split("\n").map((x) => ({ severity: "info", message: x })),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
public async clean(directory: string): Promise<void> {
|
||||
|
|
|
@ -9,8 +9,49 @@ export interface InstallOptions {
|
|||
force?: boolean;
|
||||
}
|
||||
|
||||
export interface PackageManagerProgress {
|
||||
/**
|
||||
* Current step.
|
||||
*/
|
||||
current: number;
|
||||
|
||||
/**
|
||||
* Total number of steps.
|
||||
*/
|
||||
total: number;
|
||||
|
||||
/**
|
||||
* In the case there is multiple progress
|
||||
*/
|
||||
id?: number;
|
||||
}
|
||||
|
||||
export type PackageInstallationResult = { success: false; error: InstallationError } | { success: true };
|
||||
|
||||
export interface InstallationError {
|
||||
/**
|
||||
* Main error message.
|
||||
*/
|
||||
message: string;
|
||||
|
||||
/**
|
||||
* Log entries for the package manager.
|
||||
*/
|
||||
logs: PackageManagerLogEntry[];
|
||||
}
|
||||
|
||||
export interface PackageManagerLogEntry {
|
||||
severity: "info" | "warning" | "error";
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface PackageManager {
|
||||
install(directory: string, packages: string[], options?: InstallOptions): Promise<void>;
|
||||
install(
|
||||
directory: string,
|
||||
packages: string[],
|
||||
options?: InstallOptions,
|
||||
reportProgress?: (progress: PackageManagerProgress) => void,
|
||||
): Promise<PackageInstallationResult>;
|
||||
|
||||
clean(directory: string): Promise<void>;
|
||||
|
||||
|
|
|
@ -4,7 +4,14 @@ import { join, resolve } from "path";
|
|||
import { isFile, writeFile } from "@azure-tools/async-io";
|
||||
import { execute } from "./exec-cmd";
|
||||
import { DEFAULT_NPM_REGISTRY } from "./npm";
|
||||
import { ensurePackageJsonExists, InstallOptions, PackageManager } from "./package-manager";
|
||||
import {
|
||||
ensurePackageJsonExists,
|
||||
InstallOptions,
|
||||
PackageInstallationResult,
|
||||
PackageManager,
|
||||
PackageManagerLogEntry,
|
||||
PackageManagerProgress,
|
||||
} from "./package-manager";
|
||||
|
||||
let _cli: string | undefined;
|
||||
const getPathToYarnCli = async () => {
|
||||
|
@ -35,37 +42,68 @@ const getPathToYarnCli = async () => {
|
|||
export class Yarn implements PackageManager {
|
||||
public constructor(private pathToYarnCli: string | undefined = undefined) {}
|
||||
|
||||
public async install(directory: string, packages: string[], options?: InstallOptions) {
|
||||
public async install(
|
||||
directory: string,
|
||||
packages: string[],
|
||||
options?: InstallOptions,
|
||||
reportProgress?: (progress: PackageManagerProgress) => void,
|
||||
): Promise<PackageInstallationResult> {
|
||||
await ensurePackageJsonExists(directory);
|
||||
|
||||
let total = 1;
|
||||
const logs: PackageManagerLogEntry[] = [];
|
||||
const handleYarnEvent = (event: YarnEvent) => {
|
||||
switch (event.type) {
|
||||
case "progressStart":
|
||||
if (event.data.total !== 0) {
|
||||
reportProgress?.({ current: 0, total, id: event.data.id });
|
||||
total = event.data.total;
|
||||
}
|
||||
break;
|
||||
case "progressFinish":
|
||||
reportProgress?.({ current: 100, total, id: event.data.id });
|
||||
break;
|
||||
case "progressTick":
|
||||
reportProgress?.({ current: Math.min(event.data.current, total), total, id: event.data.id });
|
||||
break;
|
||||
case "error":
|
||||
case "info":
|
||||
case "warning":
|
||||
logs.push({ severity: event.type, message: event.data });
|
||||
break;
|
||||
case "step":
|
||||
logs.push({ severity: "info", message: ` Step: ${event.data.message}` });
|
||||
break;
|
||||
}
|
||||
};
|
||||
const output = await this.execYarn(
|
||||
directory,
|
||||
"add",
|
||||
"--global-folder",
|
||||
directory.replace(/\\/g, "/"),
|
||||
...(options?.force ? ["--force"] : []),
|
||||
...packages,
|
||||
["add", "--global-folder", directory.replace(/\\/g, "/"), ...(options?.force ? ["--force"] : []), ...packages],
|
||||
handleYarnEvent,
|
||||
);
|
||||
if (output.error) {
|
||||
/* eslint-disable no-console */
|
||||
console.error("Yarn log:");
|
||||
console.log("-".repeat(50));
|
||||
console.error(output.log);
|
||||
console.log("-".repeat(50));
|
||||
/* eslint-enable no-console */
|
||||
throw Error(`Failed to install package '${packages}' -- ${output.error}`);
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
message: `Failed to install package '${packages}' -- ${output.error}`,
|
||||
logs,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return { success: true };
|
||||
}
|
||||
}
|
||||
|
||||
public async clean(directory: string): Promise<void> {
|
||||
await this.execYarn(directory, "cache", "clean", "--force");
|
||||
await this.execYarn(directory, ["cache", "clean", "--force"]);
|
||||
}
|
||||
|
||||
public async getPackageVersions(directory: string, packageName: string): Promise<string[]> {
|
||||
const result = await this.execYarn(directory, "info", packageName, "versions", "--json");
|
||||
const result = await this.execYarn(directory, ["info", packageName, "versions", "--json"]);
|
||||
return JSON.parse(result.stdout).data;
|
||||
}
|
||||
|
||||
public async execYarn(cwd: string, ...args: string[]) {
|
||||
public async execYarn(cwd: string, args: string[], onYarnEvent?: (event: YarnEvent) => void) {
|
||||
const procArgs = [
|
||||
this.pathToYarnCli ?? (await getPathToYarnCli()),
|
||||
"--no-node-version-check",
|
||||
|
@ -81,6 +119,47 @@ export class Yarn implements PackageManager {
|
|||
YARN_IGNORE_PATH: "1", // Prevent yarn from using a different version if configured in ~/.yarnrc
|
||||
};
|
||||
|
||||
return await execute(process.execPath, procArgs, { cwd, env: newEnv });
|
||||
const handleYarnLog = (buffer: Buffer) => {
|
||||
const str = buffer.toString();
|
||||
for (const line of str.split(/\r?\n/).filter((x) => x !== "")) {
|
||||
try {
|
||||
const data = JSON.parse(line);
|
||||
onYarnEvent?.(data);
|
||||
} catch (e) {
|
||||
// NOOP
|
||||
}
|
||||
}
|
||||
};
|
||||
return await execute(process.execPath, procArgs, {
|
||||
cwd,
|
||||
env: newEnv,
|
||||
onStdOutData: handleYarnLog,
|
||||
onStdErrData: handleYarnLog,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
type YarnEvent = YarnProgressTick | YarnProgressStart | YarnProgressFinish | YarnStep | YarnLog;
|
||||
|
||||
interface YarnProgressTick {
|
||||
type: "progressTick";
|
||||
data: { id: number; current: number };
|
||||
}
|
||||
|
||||
interface YarnProgressStart {
|
||||
type: "progressStart";
|
||||
data: { id: number; total: number };
|
||||
}
|
||||
interface YarnProgressFinish {
|
||||
type: "progressFinish";
|
||||
data: { id: number };
|
||||
}
|
||||
|
||||
interface YarnStep {
|
||||
type: "step";
|
||||
data: { message: string; current: number; total: number };
|
||||
}
|
||||
interface YarnLog {
|
||||
type: "info" | "warning" | "error";
|
||||
data: string;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable no-console */
|
||||
import * as asyncio from "@azure-tools/async-io";
|
||||
import * as tasks from "@azure-tools/tasks";
|
||||
import assert from "assert";
|
||||
import * as fs from "fs";
|
||||
import * as os from "os";
|
||||
import * as asyncio from "@azure-tools/async-io";
|
||||
import * as tasks from "@azure-tools/tasks";
|
||||
import { ExtensionManager, InvalidPackageIdentityException, UnresolvedPackageException } from "../src";
|
||||
|
||||
const tmpFolder = fs.mkdtempSync(`${fs.mkdtempSync(`${os.tmpdir()}/test`)}/install-pkg`);
|
||||
|
@ -41,11 +41,7 @@ describe("TestExtensions", () => {
|
|||
console.log("Installing Once");
|
||||
// install it once
|
||||
const dni = await extensionManager.findPackage("echo-cli", "*");
|
||||
const installing = extensionManager.installPackage(dni, false, 60000, (i) =>
|
||||
i.Message.Subscribe((s, m) => {
|
||||
console.log(`Installer:${m}`);
|
||||
}),
|
||||
);
|
||||
const installing = extensionManager.installPackage(dni, false, 60000, (i) => {});
|
||||
const extension = await installing;
|
||||
assert.notEqual(await extension.configuration, "the configuration file isnt where it should be?");
|
||||
}
|
||||
|
@ -54,19 +50,11 @@ describe("TestExtensions", () => {
|
|||
console.log("Attempt Overwrite");
|
||||
// install/overwrite
|
||||
const dni = await extensionManager.findPackage("echo-cli", "*");
|
||||
const installing = extensionManager.installPackage(dni, true, 60000, (i) =>
|
||||
i.Message.Subscribe((s, m) => {
|
||||
console.log(`Installer2:${m}`);
|
||||
}),
|
||||
);
|
||||
const installing = extensionManager.installPackage(dni, true, 60000, (i) => {});
|
||||
|
||||
// install at the same time?
|
||||
const dni2 = await extensionManager.findPackage("echo-cli", "*");
|
||||
const installing2 = extensionManager.installPackage(dni2, true, 60000, (i) =>
|
||||
i.Message.Subscribe((s, m) => {
|
||||
console.log(`Installer3:${m}`);
|
||||
}),
|
||||
);
|
||||
const installing2 = extensionManager.installPackage(dni2, true, 60000, (i) => {});
|
||||
|
||||
// wait for it.
|
||||
const extension = await installing;
|
||||
|
@ -149,11 +137,7 @@ describe("TestExtensions", () => {
|
|||
"Install Extension",
|
||||
async () => {
|
||||
const dni = await extensionManager.findPackage("echo-cli", "1.0.8");
|
||||
const installing = extensionManager.installPackage(dni, false, 5 * 60 * 1000, (installing) => {
|
||||
installing.Message.Subscribe((s, m) => {
|
||||
console.log(`Installer:${m}`);
|
||||
});
|
||||
});
|
||||
const installing = extensionManager.installPackage(dni, false, 5 * 60 * 1000, (installing) => {});
|
||||
|
||||
const extension = await installing;
|
||||
|
||||
|
@ -176,11 +160,7 @@ describe("TestExtensions", () => {
|
|||
"Install Extension via star",
|
||||
async () => {
|
||||
const dni = await extensionManager.findPackage("echo-cli", "*");
|
||||
const installing = extensionManager.installPackage(dni, false, 5 * 60 * 1000, (installing) => {
|
||||
installing.Message.Subscribe((s, m) => {
|
||||
console.log(`Installer:${m}`);
|
||||
});
|
||||
});
|
||||
const installing = extensionManager.installPackage(dni, false, 5 * 60 * 1000, (installing) => {});
|
||||
const extension = await installing;
|
||||
|
||||
assert.notEqual(await extension.configuration, "");
|
||||
|
@ -202,11 +182,7 @@ describe("TestExtensions", () => {
|
|||
"Force install",
|
||||
async () => {
|
||||
const dni = await extensionManager.findPackage("echo-cli", "*");
|
||||
const installing = extensionManager.installPackage(dni, false, 5 * 60 * 1000, (installing) => {
|
||||
installing.Message.Subscribe((s, m) => {
|
||||
console.log(`Installer:${m}`);
|
||||
});
|
||||
});
|
||||
const installing = extensionManager.installPackage(dni, false, 5 * 60 * 1000, (installing) => {});
|
||||
const extension = await installing;
|
||||
assert.notEqual(await extension.configuration, "");
|
||||
|
||||
|
@ -214,11 +190,7 @@ describe("TestExtensions", () => {
|
|||
await asyncio.rmFile(await extension.configurationPath);
|
||||
|
||||
// reinstall with force!
|
||||
const installing2 = extensionManager.installPackage(dni, true, 5 * 60 * 1000, (installing) => {
|
||||
installing.Message.Subscribe((s, m) => {
|
||||
console.log(`Installer:${m}`);
|
||||
});
|
||||
});
|
||||
const installing2 = extensionManager.installPackage(dni, true, 5 * 60 * 1000, (installing) => {});
|
||||
const extension2 = await installing2;
|
||||
|
||||
// is the file back?
|
||||
|
@ -232,11 +204,7 @@ describe("TestExtensions", () => {
|
|||
async () => {
|
||||
try {
|
||||
const dni = await extensionManager.findPackage("none", "fearthecowboy/echo-cli");
|
||||
const installing = extensionManager.installPackage(dni, false, 5 * 60 * 1000, (installing) => {
|
||||
installing.Message.Subscribe((s, m) => {
|
||||
console.log(`Installer:${m}`);
|
||||
});
|
||||
});
|
||||
const installing = extensionManager.installPackage(dni, false, 5 * 60 * 1000, (installing) => {});
|
||||
const extension = await installing;
|
||||
assert.notEqual(await extension.configuration, "");
|
||||
const proc = await extension.start();
|
||||
|
|
|
@ -17,6 +17,10 @@ export function createMockLogger(overrides: Partial<AutorestLogger> = {}): Autor
|
|||
trackError: jest.fn(),
|
||||
log: jest.fn(),
|
||||
with: jest.fn(() => logger),
|
||||
startProgress: jest.fn(() => ({
|
||||
update: jest.fn(),
|
||||
stop: jest.fn(),
|
||||
})),
|
||||
diagnostics: [],
|
||||
...overrides,
|
||||
};
|
||||
|
@ -35,7 +39,17 @@ export class AutorestTestLogger extends AutorestLoggerBase<LoggerProcessor> {
|
|||
};
|
||||
|
||||
public constructor() {
|
||||
super({ sinks: [{ log: (x) => this.log(x) }] });
|
||||
super({
|
||||
sinks: [
|
||||
{
|
||||
log: (x) => this.log(x),
|
||||
startProgress: jest.fn(() => ({
|
||||
update: jest.fn(),
|
||||
stop: jest.fn(),
|
||||
})),
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
public log(log: LogInfo): void {
|
||||
|
|
Загрузка…
Ссылка в новой задаче