diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1158451..ae69f81 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -87,6 +87,10 @@ jobs: run: | pip install -e . + - name: Run Azure Pipelines task unit tests + if: matrix.test-type == 'unit' + run: npm test --prefix azure-pipelines/MicrobotsLogAnalyzerTask + - name: Build Docker images for integration tests if: matrix.test-type != 'unit' run: | diff --git a/.gitignore b/.gitignore index 7a440d3..177e7db 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ # Log files *.log +node_modules/ + # Byte-compiled / optimized / DLL files __pycache__/ *.py[codz] diff --git a/README.md b/README.md index a28723e..26e0a79 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,10 @@ The `WritingBot` will read and write the files inside `code` folder based on spe The MicroBots create a containerized environment and mount the specified directory with restricting the permissions to read-only or read/write based on Bot used. It ensures that the AI agents operate within defined boundaries which enhances security and control over code modifications as well as protecting the local environment. +## Azure Pipelines Log Analyzer + +Microbots includes a custom Azure DevOps task, `MicrobotsLogAnalyzer@0`, for analyzing logs with Azure OpenAI models through an Azure Resource Manager Service Connection. See [docs/azure-pipelines-log-analyzer.md](docs/azure-pipelines-log-analyzer.md) to publish and use the task. + #Legal Notice Trademarks This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow Microsoft’s Trademark & Brand Guidelines. Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos are subject to those third-party’s policies. \ No newline at end of file diff --git a/azure-pipelines/MicrobotsLogAnalyzerTask/index.js b/azure-pipelines/MicrobotsLogAnalyzerTask/index.js new file mode 100644 index 0000000..7f936fc --- /dev/null +++ b/azure-pipelines/MicrobotsLogAnalyzerTask/index.js @@ -0,0 +1,194 @@ +"use strict"; + +const fs = require("fs"); +const path = require("path"); +const { spawnSync } = require("child_process"); +const tl = require("azure-pipelines-task-lib/task"); +const { loginAzureRM } = require("azure-pipelines-tasks-azure-arm-rest/azCliUtility"); + +const DEFAULT_TIMEOUT_SECONDS = "600"; +const VENV_NAME = "microbots-log-analyzer-venv"; +const VENV_READY_MARKER = ".microbots-venv-ready-v1"; + +function runCommand(command, args, env) { + const result = spawnSync(command, args, { + stdio: ["ignore", "pipe", "pipe"], + env: env || process.env, + encoding: "utf8", + }); + + if (result.stdout) { + if (process.stdout && typeof process.stdout.write === "function") process.stdout.write(result.stdout); + else console.log(result.stdout.trimEnd()); + } + if (result.stderr) { + if (process.stderr && typeof process.stderr.write === "function") process.stderr.write(result.stderr); + else console.error(result.stderr.trimEnd()); + } + + if (result.error) throw new Error(`Failed to run ${command}: ${result.error.message}`); + if (result.status !== 0) { + const output = [result.stderr, result.stdout].filter(Boolean).join("\n").trim(); + const details = output ? `: ${output.split(/\r?\n/).slice(-10).join("\n")}` : ""; + throw new Error(`${command} ${args.join(" ")} -> exit ${result.status}${details}`); + } +} + +function input(name, required) { + const value = tl.getInput(name, required); + return value ? value.trim() : value; +} + +function azureSubscriptionInput() { + const value = input("azureSubscription", false) || input("serviceConnection", false); + if (!value) throw new Error("azureSubscription is required"); + return value; +} + +function resolveLogPath(codebasePath, logFilePath) { + return path.isAbsolute(logFilePath) + ? path.resolve(logFilePath) + : path.resolve(codebasePath, logFilePath); +} + +function getInputs() { + const inputs = { + serviceConnection: azureSubscriptionInput(), + deploymentName: input("deploymentName", true), + endpoint: input("endpoint", true), + apiVersion: input("apiVersion", true), + codebasePath: tl.getPathInput("codebasePath", true, true), + logFilePath: input("logFilePath", true), + timeoutSeconds: input("timeoutSeconds", false) || DEFAULT_TIMEOUT_SECONDS, + maxIterations: input("maxIterations", false), + }; + + validateInputs(inputs); + return inputs; +} + +function validateInputs(inputs) { + if (!fs.existsSync(inputs.codebasePath)) { + throw new Error(`codebasePath does not exist: ${inputs.codebasePath}`); + } + + if (!fs.statSync(inputs.codebasePath).isDirectory()) { + throw new Error(`codebasePath must be a directory: ${inputs.codebasePath}`); + } + + const logPath = resolveLogPath(inputs.codebasePath, inputs.logFilePath); + if (!fs.existsSync(logPath) || !fs.statSync(logPath).isFile()) { + throw new Error(`logFilePath does not exist: ${logPath}`); + } + inputs.logFilePath = logPath; + + try { + const endpoint = new URL(inputs.endpoint); + if (endpoint.protocol !== "https:") throw new Error(); + } catch (_) { + throw new Error(`endpoint must be a valid HTTPS URL: ${inputs.endpoint}`); + } + + const timeoutSeconds = Number(inputs.timeoutSeconds); + if (!Number.isSafeInteger(timeoutSeconds) || timeoutSeconds <= 0) { + throw new Error(`timeoutSeconds must be a positive integer: ${inputs.timeoutSeconds}`); + } + inputs.timeoutSeconds = String(timeoutSeconds); + + if (inputs.maxIterations) { + const maxIterations = Number(inputs.maxIterations); + if (!Number.isSafeInteger(maxIterations) || maxIterations <= 0) { + throw new Error(`maxIterations must be a positive integer: ${inputs.maxIterations}`); + } + inputs.maxIterations = String(maxIterations); + } +} + +async function loginWithServiceConnection(serviceConnection) { + console.log("##[section]MicrobotsLogAnalyzer: authenticating with Azure service connection"); + const previousAzureOutput = process.env.AZURE_CORE_OUTPUT; + const originalLoc = tl.loc; + + process.env.AZURE_CORE_OUTPUT = "none"; + tl.loc = function loc(key, ...args) { + if (key === "LoginFailed" || key === "ErrorInSettingUpSubscription") return key; + return originalLoc(key, ...args); + }; + + try { + await loginAzureRM(serviceConnection); + } catch (error) { + throw new Error(`Azure service connection login failed for '${serviceConnection}': ${error.message || String(error)}`); + } finally { + tl.loc = originalLoc; + if (previousAzureOutput === undefined) delete process.env.AZURE_CORE_OUTPUT; + else process.env.AZURE_CORE_OUTPUT = previousAzureOutput; + } + + console.log("##[section]MicrobotsLogAnalyzer: Azure authentication complete"); +} + +function venvPythonPath(venvDir) { + return process.platform === "win32" + ? path.join(venvDir, "Scripts", "python.exe") + : path.join(venvDir, "bin", "python"); +} + +function setupVenv() { + const venvRoot = process.env.AGENT_TEMPDIRECTORY + || process.env.PIPELINE_WORKSPACE + || process.env.RUNNER_TEMP + || "/tmp"; + const venvDir = path.join(venvRoot, VENV_NAME); + const python = venvPythonPath(venvDir); + const venvReadyFile = path.join(venvDir, VENV_READY_MARKER); + + if (fs.existsSync(python) && fs.existsSync(venvReadyFile)) { + console.log(`##[section]MicrobotsLogAnalyzer: reusing Python environment at ${venvDir}`); + return python; + } + + if (fs.existsSync(venvDir)) fs.rmSync(venvDir, { recursive: true, force: true }); + + console.log(`##[section]MicrobotsLogAnalyzer: creating Python environment at ${venvDir}`); + runCommand("python3", ["-m", "venv", venvDir]); + console.log("Installing Python dependencies (microbots, Azure identity)..."); + runCommand(python, ["-m", "pip", "install", "--quiet", "--upgrade", "pip"]); + runCommand(python, ["-m", "pip", "install", "--quiet", "microbots[azure_ad]"]); + fs.writeFileSync(venvReadyFile, new Date().toISOString()); + + return python; +} + +function microbotsEnvironment(inputs) { + return Object.assign({}, process.env, { + AZURE_OPENAI_DEPLOYMENT_NAME: inputs.deploymentName, + AZURE_OPENAI_ENDPOINT: inputs.endpoint, + AZURE_OPENAI_API_VERSION: inputs.apiVersion, + }); +} + +function runLogAnalyzer(python, inputs) { + const scriptPath = path.join(__dirname, "log_analyzer_runner.py"); + const args = [scriptPath, inputs.codebasePath, inputs.logFilePath, inputs.timeoutSeconds]; + + if (inputs.maxIterations) args.push(inputs.maxIterations); + + runCommand(python, args, microbotsEnvironment(inputs)); +} + +async function run() { + try { + const inputs = getInputs(); + await loginWithServiceConnection(inputs.serviceConnection); + const python = setupVenv(); + runLogAnalyzer(python, inputs); + tl.setResult(tl.TaskResult.Succeeded, "LogAnalysisBot completed"); + } catch (error) { + tl.setResult(tl.TaskResult.Failed, error.message || String(error)); + } finally { + try { spawnSync("az", ["account", "clear"], { stdio: "ignore" }); } catch (_) {} + } +} + +run(); diff --git a/azure-pipelines/MicrobotsLogAnalyzerTask/log_analyzer_runner.py b/azure-pipelines/MicrobotsLogAnalyzerTask/log_analyzer_runner.py new file mode 100644 index 0000000..56f197b --- /dev/null +++ b/azure-pipelines/MicrobotsLogAnalyzerTask/log_analyzer_runner.py @@ -0,0 +1,72 @@ +import os +import sys +import textwrap + +from azure.identity import AzureCliCredential, get_bearer_token_provider +from microbots import LogAnalysisBot + + +def is_docker_access_error(error): + return "docker" in type(error).__module__.lower() or "docker" in str(error).lower() + + +def main(): + codebase_path = os.path.abspath(sys.argv[1]) + log_file_path = sys.argv[2] + timeout_seconds = int(sys.argv[3]) + max_iterations = int(sys.argv[4]) if len(sys.argv) > 4 else None + + os.chdir(codebase_path) + print( + f"MicrobotsLogAnalyzer: analyzing {log_file_path} with deployment " + f"{os.environ['AZURE_OPENAI_DEPLOYMENT_NAME']}", + flush=True, + ) + print(f"MicrobotsLogAnalyzer: timeout is {timeout_seconds} seconds", flush=True) + if max_iterations is not None: + print(f"MicrobotsLogAnalyzer: max iterations is {max_iterations}", flush=True) + + token_provider = get_bearer_token_provider( + AzureCliCredential(), + "https://cognitiveservices.azure.com/.default", + ) + run_kwargs = { + "file_name": log_file_path, + "timeout_in_seconds": timeout_seconds, + } + if max_iterations is not None: + run_kwargs["max_iterations"] = max_iterations + + try: + bot = LogAnalysisBot( + model=f"azure-openai/{os.environ['AZURE_OPENAI_DEPLOYMENT_NAME']}", + folder_to_mount=codebase_path, + token_provider=token_provider, + ) + result = bot.run(**run_kwargs) + except Exception as error: + if not is_docker_access_error(error): + raise + print( + "MicrobotsLogAnalyzer: Docker-compatible daemon was not accessible " + "while starting the Microbots sandbox.", + file=sys.stderr, + ) + print(f"Details: {error}", file=sys.stderr) + return 1 + + message = result.result or result.error or "" + + print("##[section]MicrobotsLogAnalyzer: LLM analysis") + print("============================================================") + print("MICROBOTS LOG ANALYSIS") + print("============================================================") + for paragraph in str(message).splitlines() or [""]: + print(textwrap.fill(paragraph, width=125) if paragraph.strip() else "") + print("============================================================") + + return 0 if result.status else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/azure-pipelines/MicrobotsLogAnalyzerTask/package-lock.json b/azure-pipelines/MicrobotsLogAnalyzerTask/package-lock.json new file mode 100644 index 0000000..db18649 --- /dev/null +++ b/azure-pipelines/MicrobotsLogAnalyzerTask/package-lock.json @@ -0,0 +1,1747 @@ +{ + "name": "microbots-log-analyzer-task", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "microbots-log-analyzer-task", + "version": "0.1.0", + "dependencies": { + "azure-pipelines-task-lib": "^5.2.4", + "azure-pipelines-tasks-azure-arm-rest": "^3.274.0" + } + }, + "node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-auth": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.10.1.tgz", + "integrity": "sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-util": "^1.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-client": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.10.1.tgz", + "integrity": "sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.10.0", + "@azure/core-rest-pipeline": "^1.22.0", + "@azure/core-tracing": "^1.3.0", + "@azure/core-util": "^1.13.0", + "@azure/logger": "^1.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-rest-pipeline": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.23.0.tgz", + "integrity": "sha512-Evs1INHo+jUjwHi1T6SG6Ua/LHOQBCLuKEEE6efIpt4ZOoNonaT1kP32GoOcdNDbfqsD2445CPri3MubBy5DEQ==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.10.0", + "@azure/core-tracing": "^1.3.0", + "@azure/core-util": "^1.13.0", + "@azure/logger": "^1.3.0", + "@typespec/ts-http-runtime": "^0.3.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-tracing": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.3.1.tgz", + "integrity": "sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-util": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.13.1.tgz", + "integrity": "sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@typespec/ts-http-runtime": "^0.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/identity": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.13.1.tgz", + "integrity": "sha512-5C/2WD5Vb1lHnZS16dNQRPMjN6oV/Upba+C9nBIs15PmOi6A3ZGs4Lr2u60zw4S04gi+u3cEXiqTVP7M4Pz3kw==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.9.0", + "@azure/core-client": "^1.9.2", + "@azure/core-rest-pipeline": "^1.17.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.11.0", + "@azure/logger": "^1.0.0", + "@azure/msal-browser": "^5.5.0", + "@azure/msal-node": "^5.1.0", + "open": "^10.1.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/logger": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.3.0.tgz", + "integrity": "sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA==", + "license": "MIT", + "dependencies": { + "@typespec/ts-http-runtime": "^0.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/msal-browser": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-5.9.0.tgz", + "integrity": "sha512-CzE+4PefDSJWj26zU7G1bKchlGRRHMBFreG4tAlGuzyI8hAPiYGobaJvZBgZBf6L63iphX7VH+ityL8VgEQz9Q==", + "license": "MIT", + "dependencies": { + "@azure/msal-common": "16.5.2" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-common": { + "version": "16.5.2", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.5.2.tgz", + "integrity": "sha512-GkDEL6TYo3HgT3UuqakdgE9PZfc1hMki6+Hwgy1uddb/EauvAKfu85vVhuofRSo22D1xTnWt8Ucwfg4vSCVwvA==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-node": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-5.1.5.tgz", + "integrity": "sha512-ObTeMoNPmq19X3z40et9Xvs4ZoWVeJg43PZMRLG5iwVL+2nCtAerG3YTDItqPp1CfXNwmCXBbg8jn1DOx65c3g==", + "license": "MIT", + "dependencies": { + "@azure/msal-common": "16.5.2", + "jsonwebtoken": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/jsonwebtoken": { + "version": "8.5.9", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.9.tgz", + "integrity": "sha512-272FMnFGzAVMGtu9tkr29hRL6bZj4Zs1KZNeHLnKqAvp06tAIcarTMwOh8/8bz4FmKRcMxZhZNeUAQsNLoiPhg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/mocha": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", + "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "10.17.60", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", + "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", + "license": "MIT" + }, + "node_modules/@types/q": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", + "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==", + "license": "MIT" + }, + "node_modules/@typespec/ts-http-runtime": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.5.tgz", + "integrity": "sha512-yURCknZhvywvQItHMMmFSo+fq5arCUIyz/CVk7jD89MSai7dkaX8ufjCWp3NttLojoTVbcE72ri+be/TnEbMHw==", + "license": "MIT", + "dependencies": { + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@typespec/ts-http-runtime/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/adm-zip": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.17.tgz", + "integrity": "sha512-+Ut8d9LLqwEvHHJl1+PIHqoyDxFgVN847JTVM3Izi3xHDWPE4UtzzXysMZQs64DMcrJfBeS/uoEP4AD3HQHnQQ==", + "license": "MIT", + "engines": { + "node": ">=12.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/async-mutex": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.4.1.tgz", + "integrity": "sha512-WfoBo4E/TbCX1G95XTjbWTE3X2XLG0m1Xbv2cwOtuPdyH9CZvnaA5nCt1ucjaKEgW2A5IF71hxrRhr83Je5xjA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/azure-devops-node-api": { + "version": "15.1.3", + "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-15.1.3.tgz", + "integrity": "sha512-YMgxCjQDqBr//vGy658tXTrXAgS2BzIChJ2Mzrq+fzLK+dh42fODWO/kEQMmtp+Rw0jNgEoUA72cHYVBrxrjRw==", + "license": "MIT", + "dependencies": { + "tunnel": "0.0.6", + "typed-rest-client": "2.1.0" + }, + "engines": { + "node": ">= 16.0.0" + } + }, + "node_modules/azure-devops-node-api/node_modules/typed-rest-client": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-2.1.0.tgz", + "integrity": "sha512-Nel9aPbgSzRxfs1+4GoSB4wexCF+4Axlk7OSGVQCMa+4fWcyxIsN/YNmkp0xTT2iQzMD98h8yFLav/cNaULmRA==", + "license": "MIT", + "dependencies": { + "des.js": "^1.1.0", + "js-md4": "^0.3.2", + "qs": "^6.10.3", + "tunnel": "0.0.6", + "underscore": "^1.12.1" + }, + "engines": { + "node": ">= 16.0.0" + } + }, + "node_modules/azure-pipelines-task-lib": { + "version": "5.2.10", + "resolved": "https://registry.npmjs.org/azure-pipelines-task-lib/-/azure-pipelines-task-lib-5.2.10.tgz", + "integrity": "sha512-6Wak5UB+Bs693LkPoE4gZTDNkNL5DDn2buQKMtvqIWh2ZCIJk+tc1nSzroGWntJa7USs8X3cYvdl0RiSsp4/5A==", + "license": "MIT", + "dependencies": { + "adm-zip": "^0.5.10", + "minimatch": "^3.1.5", + "nodejs-file-downloader": "^4.11.1", + "q": "^1.5.1", + "semver": "^5.7.2", + "shelljs": "^0.10.0", + "uuid": "^3.0.1" + } + }, + "node_modules/azure-pipelines-tasks-azure-arm-rest": { + "version": "3.274.0", + "resolved": "https://registry.npmjs.org/azure-pipelines-tasks-azure-arm-rest/-/azure-pipelines-tasks-azure-arm-rest-3.274.0.tgz", + "integrity": "sha512-UDRQ4EMoEaIl5slo19r1xxoRUQqmYXnKZ1NFxx5Bndtnhf3BnLB63B2jCTFSY8OFd0r5j+xUQ1xyuXuYY6hNXQ==", + "license": "MIT", + "dependencies": { + "@azure/identity": "^4.13.1", + "@types/jsonwebtoken": "^8.5.8", + "@types/mocha": "^5.2.7", + "@types/node": "^10.17.0", + "@types/q": "1.5.4", + "async-mutex": "^0.4.0", + "azure-devops-node-api": "^15.1.3", + "azure-pipelines-task-lib": "^5.2.4", + "https-proxy-agent": "^4.0.0", + "jsonwebtoken": "^9.0.3", + "msalv1": "npm:@azure/msal-node@^1.18.4", + "msalv2": "npm:@azure/msal-node@^2.7.0", + "msalv3": "npm:@azure/msal-node@^3.5.3", + "node-fetch": "^2.6.7", + "q": "1.5.1", + "typed-rest-client": "^2.2.0", + "xml2js": "0.6.2" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/des.js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", + "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", + "license": "MIT", + "dependencies": { + "agent-base": "5", + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/https-proxy-agent/node_modules/agent-base": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", + "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/js-md4": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz", + "integrity": "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==", + "license": "MIT" + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/msalv1": { + "name": "@azure/msal-node", + "version": "1.18.4", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-1.18.4.tgz", + "integrity": "sha512-Kc/dRvhZ9Q4+1FSfsTFDME/v6+R2Y1fuMty/TfwqE5p9GTPw08BPbKgeWinE8JRHRp+LemjQbUZsn4Q4l6Lszg==", + "deprecated": "A newer major version of this library is available. Please upgrade to the latest available version.", + "license": "MIT", + "dependencies": { + "@azure/msal-common": "13.3.1", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + }, + "engines": { + "node": "10 || 12 || 14 || 16 || 18" + } + }, + "node_modules/msalv1/node_modules/@azure/msal-common": { + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-13.3.1.tgz", + "integrity": "sha512-Lrk1ozoAtaP/cp53May3v6HtcFSVxdFrg2Pa/1xu5oIvsIwhxW6zSPibKefCOVgd5osgykMi5jjcZHv8XkzZEQ==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/msalv1/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/msalv2": { + "name": "@azure/msal-node", + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.16.3.tgz", + "integrity": "sha512-CO+SE4weOsfJf+C5LM8argzvotrXw252/ZU6SM2Tz63fEblhH1uuVaaO4ISYFuN4Q6BhTo7I3qIdi8ydUQCqhw==", + "license": "MIT", + "dependencies": { + "@azure/msal-common": "14.16.1", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/msalv2/node_modules/@azure/msal-common": { + "version": "14.16.1", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.16.1.tgz", + "integrity": "sha512-nyxsA6NA4SVKh5YyRpbSXiMr7oQbwark7JU9LMeg6tJYTSPyAGkdx61wPT4gyxZfxlSxMMEyAsWaubBlNyIa1w==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/msalv2/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/msalv3": { + "name": "@azure/msal-node", + "version": "3.8.10", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-3.8.10.tgz", + "integrity": "sha512-0Hz7Kx4hs70KZWep/Rd7aw/qOLUF92wUOhn7ZsOuB5xNR/06NL1E2RAI9+UKH1FtvN8nD6mFjH7UKSjv6vOWvQ==", + "license": "MIT", + "dependencies": { + "@azure/msal-common": "15.17.0", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/msalv3/node_modules/@azure/msal-common": { + "version": "15.17.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-15.17.0.tgz", + "integrity": "sha512-VQ5/gTLFADkwue+FohVuCqlzFPUq4xSrX8jeZe+iwZuY6moliNC8xt86qPVNYdtbQfELDf2Nu6LI+demFPHGgw==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/msalv3/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/nodejs-file-downloader": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/nodejs-file-downloader/-/nodejs-file-downloader-4.13.0.tgz", + "integrity": "sha512-nI2fKnmJWWFZF6SgMPe1iBodKhfpztLKJTtCtNYGhm/9QXmWa/Pk9Sv00qHgzEvNLe1x7hjGDRor7gcm/ChaIQ==", + "license": "ISC", + "dependencies": { + "follow-redirects": "^1.15.6", + "https-proxy-agent": "^5.0.0", + "mime-types": "^2.1.27", + "sanitize-filename": "^1.6.3" + } + }, + "node_modules/nodejs-file-downloader/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/nodejs-file-downloader/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)", + "license": "MIT", + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, + "node_modules/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/sanitize-filename": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.4.tgz", + "integrity": "sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==", + "license": "WTFPL OR ISC", + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shelljs": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.10.0.tgz", + "integrity": "sha512-Jex+xw5Mg2qMZL3qnzXIfaxEtBaC4n7xifqaqtrZDdlheR70OGkydrPJWT0V1cA1k3nanC86x9FwAmQl6w3Klw==", + "license": "BSD-3-Clause", + "dependencies": { + "execa": "^5.1.1", + "fast-glob": "^3.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", + "license": "WTFPL", + "dependencies": { + "utf8-byte-length": "^1.0.1" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "license": "MIT", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, + "node_modules/typed-rest-client": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-2.3.1.tgz", + "integrity": "sha512-k4kX5Up6qA68D0Cby2AK+6+vM5k3qTxe+/3FqhnHRExjY5cfbOnzjQZbP/LXleF8hVoDvDqxlgk9KK83HoBZlQ==", + "license": "MIT", + "dependencies": { + "des.js": "^1.1.0", + "js-md4": "^0.3.2", + "qs": "6.15.1", + "tunnel": "0.0.6", + "underscore": "^1.13.8" + }, + "engines": { + "node": ">= 16.0.0" + } + }, + "node_modules/underscore": { + "version": "1.13.8", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz", + "integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==", + "license": "MIT" + }, + "node_modules/utf8-byte-length": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", + "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", + "license": "(WTFPL OR MIT)" + }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "license": "MIT", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xml2js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "license": "MIT", + "engines": { + "node": ">=4.0" + } + } + } +} diff --git a/azure-pipelines/MicrobotsLogAnalyzerTask/package.json b/azure-pipelines/MicrobotsLogAnalyzerTask/package.json new file mode 100644 index 0000000..76308fe --- /dev/null +++ b/azure-pipelines/MicrobotsLogAnalyzerTask/package.json @@ -0,0 +1,13 @@ +{ + "name": "microbots-log-analyzer-task", + "version": "0.1.0", + "description": "Azure DevOps custom task that runs the Microbots LogAnalysisBot.", + "main": "index.js", + "scripts": { + "test": "node --test test/*.test.js" + }, + "dependencies": { + "azure-pipelines-task-lib": "^5.2.4", + "azure-pipelines-tasks-azure-arm-rest": "^3.274.0" + } +} diff --git a/azure-pipelines/MicrobotsLogAnalyzerTask/task.json b/azure-pipelines/MicrobotsLogAnalyzerTask/task.json new file mode 100644 index 0000000..b7142c0 --- /dev/null +++ b/azure-pipelines/MicrobotsLogAnalyzerTask/task.json @@ -0,0 +1,89 @@ +{ + "$schema": "https://raw.githubusercontent.com/Microsoft/azure-pipelines-task-lib/master/tasks.schema.json", + "id": "90104301-67ce-42b6-8f74-e520533e6a66", + "name": "MicrobotsLogAnalyzer", + "friendlyName": "Microbots Log Analyzer", + "description": "Run Microbots LogAnalysisBot against a log file using Azure OpenAI. Authenticates via the supplied Azure Resource Manager service connection.", + "category": "Utility", + "author": "Microbots contributors", + "version": { + "Major": 0, + "Minor": 1, + "Patch": 0 + }, + "instanceNameFormat": "Microbots Log Analyzer: $(logFilePath)", + "inputs": [ + { + "name": "azureSubscription", + "aliases": [ + "serviceConnection" + ], + "type": "connectedService:AzureRM", + "label": "Azure Subscription", + "required": true, + "helpMarkDown": "Service connection used to obtain a token for Azure OpenAI." + }, + { + "name": "deploymentName", + "type": "string", + "label": "Deployment name", + "required": true, + "helpMarkDown": "Azure OpenAI deployment name used by Microbots." + }, + { + "name": "endpoint", + "type": "string", + "label": "Azure OpenAI endpoint", + "required": true, + "helpMarkDown": "Azure OpenAI resource endpoint, for example https://my-resource.openai.azure.com/." + }, + { + "name": "apiVersion", + "type": "string", + "label": "API version", + "required": true, + "helpMarkDown": "Azure OpenAI API version, for example 2025-03-01-preview." + }, + { + "name": "codebasePath", + "type": "filePath", + "label": "Codebase path", + "required": true, + "helpMarkDown": "Path to the repository or source folder Microbots can inspect while analyzing the log." + }, + { + "name": "logFilePath", + "type": "string", + "label": "Log file path", + "required": true, + "helpMarkDown": "Path to the log file. (Both Absolute Path and Relative to Codebase Path are supported.)" + }, + { + "name": "timeoutSeconds", + "type": "string", + "label": "Timeout in seconds", + "defaultValue": "600", + "required": false + }, + { + "name": "maxIterations", + "type": "string", + "label": "Maximum iterations", + "required": false, + "helpMarkDown": "Optional maximum number of Microbots iterations." + } + ], + "execution": { + "Node20_1": { + "target": "index.js" + } + }, + "restrictions": { + "commands": { + "mode": "restricted" + }, + "settableVariables": { + "allowed": [] + } + } +} diff --git a/azure-pipelines/MicrobotsLogAnalyzerTask/test/index.test.js b/azure-pipelines/MicrobotsLogAnalyzerTask/test/index.test.js new file mode 100644 index 0000000..19d0ea0 --- /dev/null +++ b/azure-pipelines/MicrobotsLogAnalyzerTask/test/index.test.js @@ -0,0 +1,510 @@ +"use strict"; + +const assert = require("node:assert/strict"); +const childProcess = require("node:child_process"); +const fs = require("node:fs"); +const os = require("node:os"); +const path = require("node:path"); +const test = require("node:test"); +const vm = require("node:vm"); + +const taskDir = path.resolve(__dirname, ".."); + +function loadTask(options = {}) { + const source = fs.readFileSync(path.join(taskDir, "index.js"), "utf8").replace( + /run\(\);\s*$/, + `module.exports = { + runCommand, + input, + azureSubscriptionInput, + resolveLogPath, + getInputs, + validateInputs, + loginWithServiceConnection, + venvPythonPath, + setupVenv, + microbotsEnvironment, + runLogAnalyzer, + run, + };` + ); + + const calls = { + spawnSync: [], + loginAzureRM: [], + setResult: [], + rmSync: [], + writeFileSync: [], + }; + const events = []; + + const taskLib = options.taskLib || { + inputs: options.inputs || {}, + pathInputs: options.pathInputs || {}, + TaskResult: { Succeeded: "Succeeded", Failed: "Failed" }, + getInput(name, required) { + const value = this.inputs[name]; + if (required && (value === undefined || value === null || value === "")) { + throw new Error(`${name} is required`); + } + return value; + }, + getPathInput(name, required) { + const value = this.pathInputs[name] ?? this.inputs[name]; + if (required && (value === undefined || value === null || value === "")) { + throw new Error(`${name} is required`); + } + return value; + }, + loc(key, ...args) { + return [key, ...args].join(" "); + }, + setResult(result, message) { + calls.setResult.push({ result, message }); + events.push({ name: "setResult", result }); + }, + }; + + const mockFs = options.fs || fs; + const mockProcess = options.process || { env: {}, platform: "linux" }; + const mockSpawnSync = options.spawnSync || ((command, args, spawnOptions) => { + calls.spawnSync.push({ command, args, options: spawnOptions }); + events.push({ name: "spawnSync", command, args }); + return { status: 0 }; + }); + const mockLoginAzureRM = options.loginAzureRM || (async (serviceConnection) => { + calls.loginAzureRM.push(serviceConnection); + events.push({ name: "loginAzureRM", serviceConnection }); + }); + + const module = { exports: {} }; + const context = { + Date, + Error, + URL, + __dirname: taskDir, + console: options.console || { log() {}, warn() {}, error() {} }, + module, + exports: module.exports, + process: mockProcess, + require(name) { + if (name === "fs") return mockFs; + if (name === "path") return path; + if (name === "child_process") return { spawnSync: mockSpawnSync }; + if (name === "azure-pipelines-task-lib/task") return taskLib; + if (name === "azure-pipelines-tasks-azure-arm-rest/azCliUtility") { + return { loginAzureRM: mockLoginAzureRM }; + } + return require(name); + }, + }; + + vm.runInNewContext(source, context, { filename: path.join(taskDir, "index.js") }); + return { task: module.exports, calls, events, taskLib, process: mockProcess }; +} + +function makeProjectWithLog() { + const root = fs.mkdtempSync(path.join(os.tmpdir(), "microbots-task-test-")); + const logDir = path.join(root, "logs"); + fs.mkdirSync(logDir); + fs.writeFileSync(path.join(logDir, "build.log"), "error log"); + return root; +} + +function mockCompletedLinuxVenv(tempDir = "/tmp") { + const venvDir = path.join(tempDir, "microbots-log-analyzer-venv"); + const python = path.join(venvDir, "bin", "python"); + const marker = path.join(venvDir, ".microbots-venv-ready-v1"); + const existing = new Set([python, marker]); + const mockFs = { + existsSync(filePath) { return existing.has(filePath) || fs.existsSync(filePath); }, + statSync(filePath) { return fs.statSync(filePath); }, + rmSync() {}, + writeFileSync() {}, + }; + + return { mockFs, python }; +} + +function writeFile(filePath, content) { + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + fs.writeFileSync(filePath, content); +} + +function mockMicrobotsEnvironment(options = {}) { + const root = fs.mkdtempSync(path.join(os.tmpdir(), "microbots-runner-test-")); + const mockModules = path.join(root, "mocks"); + const recordPath = path.join(root, "record.json"); + + writeFile(path.join(mockModules, "azure", "__init__.py"), ""); + writeFile(path.join(mockModules, "azure", "identity.py"), ` +class AzureCliCredential: + pass + +def get_bearer_token_provider(credential, scope): + def token_provider(): + return "mock-token" + token_provider.scope = scope + token_provider.credential_type = type(credential).__name__ + return token_provider +`); + writeFile(path.join(mockModules, "microbots.py"), ` +import json +import os +from types import SimpleNamespace + +class LogAnalysisBot: + def __init__(self, model, folder_to_mount, token_provider): + if os.environ.get("MICROBOTS_MOCK_INIT_ERROR") == "docker": + class DockerException(Exception): + pass + DockerException.__module__ = "docker.errors" + raise DockerException("Error while fetching server API version") + self.model = model + self.folder_to_mount = folder_to_mount + self.token_provider = token_provider + + def run(self, **kwargs): + record = { + "model": self.model, + "folder_to_mount": self.folder_to_mount, + "token_provider_scope": getattr(self.token_provider, "scope", None), + "token_provider_credential_type": getattr(self.token_provider, "credential_type", None), + "token": self.token_provider(), + "run_kwargs": kwargs, + } + with open(os.environ["MICROBOTS_MOCK_RECORD"], "w", encoding="utf-8") as record_file: + json.dump(record, record_file) + return SimpleNamespace( + status=os.environ["MICROBOTS_MOCK_STATUS"] == "true", + result=os.environ.get("MICROBOTS_MOCK_RESULT") or None, + error=os.environ.get("MICROBOTS_MOCK_ERROR") or None, + ) +`); + + const env = Object.assign({}, process.env, { + AZURE_OPENAI_DEPLOYMENT_NAME: options.deploymentName || "gpt-test", + MICROBOTS_MOCK_RECORD: recordPath, + MICROBOTS_MOCK_STATUS: options.status === false ? "false" : "true", + MICROBOTS_MOCK_RESULT: Object.hasOwn(options, "result") ? options.result : "Root cause found", + MICROBOTS_MOCK_ERROR: Object.hasOwn(options, "error") ? options.error : "", + MICROBOTS_MOCK_INIT_ERROR: options.initError || "", + PYTHONPATH: mockModules + (process.env.PYTHONPATH ? path.delimiter + process.env.PYTHONPATH : ""), + }); + + return { env, recordPath }; +} + +async function runTaskWithMockServiceConnectionAndMockMicrobots(options = {}) { + const codebasePath = makeProjectWithLog(); + const { mockFs, python } = mockCompletedLinuxVenv(); + const logFilePath = path.join(codebasePath, "logs", "build.log"); + const inputs = Object.assign({ + azureSubscription: "service-id", + deploymentName: "gpt-test", + endpoint: "https://example.openai.azure.com/", + apiVersion: "2025-03-01-preview", + codebasePath, + logFilePath: "logs/build.log", + timeoutSeconds: "600", + maxIterations: "12", + }, options.inputs || {}); + let runnerResult; + let runnerRecord; + let runnerRecordPath; + let loaded; + + loaded = loadTask({ + fs: mockFs, + process: { + env: Object.assign({ AGENT_TEMPDIRECTORY: "/tmp" }, options.processEnv || {}), + platform: "linux", + }, + inputs, + spawnSync(command, args, spawnOptions) { + const commandArgs = Array.from(args || []); + loaded.calls.spawnSync.push({ command, args, options: spawnOptions }); + loaded.events.push({ name: "spawnSync", command, args }); + + if (commandArgs[0] === path.join(taskDir, "log_analyzer_runner.py")) { + const mockMicrobots = mockMicrobotsEnvironment(Object.assign({ + deploymentName: inputs.deploymentName, + result: "Root cause found", + }, options.mockMicrobots || {})); + runnerRecordPath = mockMicrobots.recordPath; + runnerResult = childProcess.spawnSync("python", Array.from(args), { + env: Object.assign({}, spawnOptions.env, mockMicrobots.env), + encoding: "utf8", + }); + runnerRecord = fs.existsSync(runnerRecordPath) + ? JSON.parse(fs.readFileSync(runnerRecordPath, "utf8")) + : null; + return { + status: runnerResult.status, + stdout: runnerResult.stdout, + stderr: runnerResult.stderr, + }; + } + return { status: 0 }; + }, + }); + + await loaded.task.run(); + return { ...loaded, codebasePath, logFilePath, python, runnerResult, runnerRecord }; +} + +test("Input Parameter azureSubscription has serviceConnection as alias", () => { + const primary = loadTask({ inputs: { azureSubscription: " primary " } }); + assert.equal(primary.task.azureSubscriptionInput(), "primary"); + + const alias = loadTask({ inputs: { serviceConnection: " alias " } }); + assert.equal(alias.task.azureSubscriptionInput(), "alias"); + + const missing = loadTask(); + assert.throws(() => missing.task.azureSubscriptionInput(), /azureSubscription is required/); +}); + +test("Valid Inputs Resolve Correctly: log path and numeric values", () => { + const { task } = loadTask(); + const codebasePath = makeProjectWithLog(); + const inputs = { + codebasePath, + logFilePath: "logs/build.log", + endpoint: "https://example.openai.azure.com/", + timeoutSeconds: " 600 ", + maxIterations: "20", + }; + + task.validateInputs(inputs); + + assert.equal(inputs.logFilePath, path.join(codebasePath, "logs", "build.log")); + assert.equal(inputs.timeoutSeconds, "600"); + assert.equal(inputs.maxIterations, "20"); +}); + +test("Invalid Inputs Are Rejected: endpoint, timeout, and maxIterations", () => { + const { task } = loadTask(); + const codebasePath = makeProjectWithLog(); + const validInputs = { + codebasePath, + logFilePath: "logs/build.log", + endpoint: "https://example.openai.azure.com/", + timeoutSeconds: "600", + }; + + assert.throws( + () => task.validateInputs({ ...validInputs, endpoint: "http://example.openai.azure.com/" }), + /valid HTTPS URL/ + ); + assert.throws( + () => task.validateInputs({ ...validInputs, timeoutSeconds: "0" }), + /timeoutSeconds must be a positive integer/ + ); + assert.throws( + () => task.validateInputs({ ...validInputs, maxIterations: "-1" }), + /maxIterations must be a positive integer/ + ); +}); + +test("End To End Flow Works: ServiceConnection Login and LogAnalysisBot Output is Displayed", async () => { + const { + calls, + events, + codebasePath, + logFilePath, + python, + runnerResult, + runnerRecord, + } = await runTaskWithMockServiceConnectionAndMockMicrobots({ + processEnv: { + AGENT_TEMPDIRECTORY: "/tmp", + AZURE_OPENAI_DEPLOYMENT_NAME: "stale-deployment", + AZURE_OPENAI_ENDPOINT: "https://stale.openai.azure.com/", + AZURE_OPENAI_API_VERSION: "stale-version", + KEEP_ME: "yes", + }, + mockMicrobots: { result: "The deployment returned analysis." }, + }); + + assert.deepEqual(calls.setResult, [{ result: "Succeeded", message: "LogAnalysisBot completed" }]); + const loginIndex = events.findIndex((event) => event.name === "loginAzureRM"); + const runnerIndex = events.findIndex((event) => ( + event.name === "spawnSync" && event.args[0] === path.join(taskDir, "log_analyzer_runner.py") + )); + assert.notEqual(loginIndex, -1); + assert.notEqual(runnerIndex, -1); + assert.equal(events[loginIndex].serviceConnection, "service-id"); + assert.ok(loginIndex < runnerIndex); + + const runnerCall = calls.spawnSync.find((call) => ( + call.args[0] === path.join(taskDir, "log_analyzer_runner.py") + )); + assert.ok(runnerCall); + assert.equal(runnerResult.status, 0, runnerResult.stderr); + assert.match(runnerResult.stdout, /The deployment returned analysis\./); + assert.equal(runnerCall.command, python); + assert.deepEqual(Array.from(runnerCall.args).slice(1), [ + codebasePath, + logFilePath, + "600", + "12", + ]); + assert.equal(runnerCall.options.env.KEEP_ME, "yes"); + assert.equal(runnerCall.options.env.AZURE_OPENAI_DEPLOYMENT_NAME, "gpt-test"); + assert.equal(runnerCall.options.env.AZURE_OPENAI_ENDPOINT, "https://example.openai.azure.com/"); + assert.equal(runnerCall.options.env.AZURE_OPENAI_API_VERSION, "2025-03-01-preview"); + assert.equal(runnerRecord.model, "azure-openai/gpt-test"); + assert.equal(runnerRecord.token_provider_scope, "https://cognitiveservices.azure.com/.default"); + assert.deepEqual(runnerRecord.run_kwargs, { + file_name: logFilePath, + timeout_in_seconds: 600, + max_iterations: 12, + }); + + const logoutCall = calls.spawnSync.at(-1); + assert.equal(logoutCall.command, "az"); + assert.deepEqual(Array.from(logoutCall.args), ["account", "clear"]); + assert.equal(logoutCall.options.stdio, "ignore"); +}); + +test("Existing Python Environment Is Reused Only After A Completed Setup", () => { + const tempDir = path.join(path.parse(process.cwd()).root, "tmp"); + const venvDir = path.join(tempDir, "microbots-log-analyzer-venv"); + const python = path.join(venvDir, "bin", "python"); + const marker = path.join(venvDir, ".microbots-venv-ready-v1"); + const exists = new Set([ + python, + marker, + ]); + const mockFs = { + existsSync(filePath) { return exists.has(filePath); }, + rmSync() { throw new Error("rmSync should not be called"); }, + writeFileSync() { throw new Error("writeFileSync should not be called"); }, + }; + const { task, calls } = loadTask({ fs: mockFs, process: { env: { AGENT_TEMPDIRECTORY: tempDir }, platform: "linux" } }); + + assert.equal(task.setupVenv(), python); + assert.equal(calls.spawnSync.length, 0); +}); + +test("Incomplete Python Environment Is Deleted And Rebuilt", () => { + const tempDir = path.join(path.parse(process.cwd()).root, "tmp"); + const venvDir = path.join(tempDir, "microbots-log-analyzer-venv"); + const python = path.join(venvDir, "bin", "python"); + const marker = path.join(venvDir, ".microbots-venv-ready-v1"); + const existing = new Set([venvDir]); + const mockFs = { + existsSync(filePath) { return existing.has(filePath); }, + rmSync(filePath, options) { existing.delete(filePath); calls.rmSync.push({ filePath, options }); }, + writeFileSync(filePath, value) { calls.writeFileSync.push({ filePath, value }); }, + }; + const calls = { rmSync: [], writeFileSync: [] }; + const loaded = loadTask({ + fs: mockFs, + process: { env: { AGENT_TEMPDIRECTORY: tempDir }, platform: "linux" }, + }); + + assert.equal(loaded.task.setupVenv(), python); + assert.equal(calls.rmSync[0].filePath, venvDir); + assert.equal(calls.rmSync[0].options.recursive, true); + assert.equal(calls.rmSync[0].options.force, true); + assert.deepEqual(loaded.calls.spawnSync.map((call) => [call.command, Array.from(call.args)]), [ + ["python3", ["-m", "venv", venvDir]], + [python, ["-m", "pip", "install", "--quiet", "--upgrade", "pip"]], + [python, ["-m", "pip", "install", "--quiet", "microbots[azure_ad]"]], + ]); + assert.equal(calls.writeFileSync[0].filePath, marker); +}); + +test("AzureRM Login Receives ServiceConnection ID", async () => { + const { task, calls, process } = loadTask({ + process: { env: { AZURE_CORE_OUTPUT: "json" }, platform: "linux" }, + }); + + await task.loginWithServiceConnection("service-id"); + + assert.deepEqual(calls.loginAzureRM, ["service-id"]); + assert.equal(process.env.AZURE_CORE_OUTPUT, "json"); +}); + +test("ServiceConnection Login Failures Are Properly Handled And Stop The Analyzer", async () => { + const codebasePath = makeProjectWithLog(); + const { task, calls } = loadTask({ + inputs: { + azureSubscription: "service-id", + deploymentName: "gpt-test", + endpoint: "https://example.openai.azure.com/", + apiVersion: "2025-03-01-preview", + codebasePath, + logFilePath: "logs/build.log", + timeoutSeconds: "600", + }, + loginAzureRM: async () => { throw new Error("authentication failed"); }, + }); + + await task.run(); + + assert.deepEqual(calls.setResult, [{ + result: "Failed", + message: "Azure service connection login failed for 'service-id': authentication failed", + }]); + assert.equal(calls.spawnSync.some((call) => ( + Array.from(call.args || [])[0] === path.join(taskDir, "log_analyzer_runner.py") + )), false); +}); + +test("Python Setup Command Failures Include Error Details", () => { + const spawnError = loadTask({ spawnSync: () => ({ error: new Error("missing") }) }); + assert.throws(() => spawnError.task.runCommand("python3", ["--version"]), /Failed to run python3: missing/); + + const nonZero = loadTask({ spawnSync: () => ({ status: 2, stderr: "venv creation failed" }) }); + assert.throws(() => nonZero.task.runCommand("python3", ["-m", "venv"]), /exit 2: venv creation failed/); +}); + +test("Runner Reports Docker Sandbox Startup Failures", async () => { + const { calls, events, runnerResult } = await runTaskWithMockServiceConnectionAndMockMicrobots({ + mockMicrobots: { initError: "docker" }, + }); + + assert.equal(events.some((event) => event.name === "loginAzureRM"), true); + assert.equal(events.filter((event) => ( + event.name === "spawnSync" && Array.from(event.args || [])[0] === path.join(taskDir, "log_analyzer_runner.py") + )).length, 1); + assert.equal(runnerResult.status, 1); + assert.match(runnerResult.stderr, /Docker-compatible daemon was not accessible/); + assert.equal(calls.setResult[0].result, "Failed"); + assert.match(calls.setResult[0].message, /Docker-compatible daemon was not accessible/); +}); + +test("Task Fails With Proper Error Message When LLM Deployment Cannot Be Reached (After Login With ServiceConnection)", async () => { + const { calls, events, runnerResult } = await runTaskWithMockServiceConnectionAndMockMicrobots({ + mockMicrobots: { + status: false, + result: "", + error: "Deployment access failed", + }, + }); + + assert.equal(events.some((event) => event.name === "loginAzureRM"), true); + assert.equal(runnerResult.status, 1); + assert.match(runnerResult.stdout, /Deployment access failed/); + assert.equal(calls.setResult[0].result, "Failed"); + assert.match(calls.setResult[0].message, /exit 1/); + assert.match(calls.setResult[0].message, /Deployment access failed/); +}); + +test("Task Correctly Reports Failures While Analyzing Logs By The LLM", async () => { + const { calls, runnerResult, runnerRecord } = await runTaskWithMockServiceConnectionAndMockMicrobots({ + mockMicrobots: { + status: false, + result: "", + error: "Log analysis timed out", + }, + }); + + assert.equal(runnerResult.status, 1); + assert.equal(runnerRecord.token, "mock-token"); + assert.match(runnerResult.stdout, /Log analysis timed out/); + assert.equal(calls.setResult[0].result, "Failed"); + assert.match(calls.setResult[0].message, /Log analysis timed out/); +}); diff --git a/azure-pipelines/vss-extension.json b/azure-pipelines/vss-extension.json new file mode 100644 index 0000000..9cbb5b7 --- /dev/null +++ b/azure-pipelines/vss-extension.json @@ -0,0 +1,33 @@ +{ + "manifestVersion": 1, + "id": "microbots-log-analyzer", + "name": "Microbots Log Analyzer", + "version": "0.1.0", + "publisher": "microsoft", + "targets": [ + { + "id": "Microsoft.VisualStudio.Services" + } + ], + "description": "Azure Pipelines task for running Microbots log analysis with Azure OpenAI.", + "categories": [ + "Azure Pipelines" + ], + "files": [ + { + "path": "MicrobotsLogAnalyzerTask" + } + ], + "contributions": [ + { + "id": "microbots-log-analyzer-task", + "type": "ms.vss-distributed-task.task", + "targets": [ + "ms.vss-distributed-task.tasks" + ], + "properties": { + "name": "MicrobotsLogAnalyzerTask" + } + } + ] +} diff --git a/docs/azure-pipelines-log-analyzer.md b/docs/azure-pipelines-log-analyzer.md new file mode 100644 index 0000000..a66a043 --- /dev/null +++ b/docs/azure-pipelines-log-analyzer.md @@ -0,0 +1,80 @@ +# MicrobotsLogAnalyzer Azure Pipelines Task + +`MicrobotsLogAnalyzer` is an Azure DevOps custom task that runs Microbots `LogAnalysisBot` against a log file. It authenticates to Azure OpenAI through an Azure Resource Manager Service Connection, creates an isolated Python venv on the build agent, installs `microbots[azure_ad]`, and prints the root-cause analysis into the pipeline logs. + +## Prerequisites + +- Azure DevOps organization where you can install custom extensions. +- Azure Resource Manager Service Connection with permission to request tokens for the Azure OpenAI resource. The pipeline must be authorized to use this service connection. +- Azure Pipelines agent with `azure-cli`, `python3`, `pip` and `python3 -m venv` support. Microbots uses Docker sandboxing by default, so the agent also needs a reachable Docker-compatible daemon. +- Azure OpenAI deployment that works with Microbots and is reachable by the service connection. +- Node.js on the machine where you package and publish the extension. + +## Publish The Task + +From a clone of this repository: + +```bash +npm install -g tfx-cli + +cd azure-pipelines/MicrobotsLogAnalyzerTask +npm ci --omit=dev + +cd .. +tfx extension create --manifest-globs vss-extension.json +tfx extension publish --manifest-globs vss-extension.json --token --share-with +``` + +Update the `publisher` value in `vss-extension.json` before running `tfx extension create`. The task folder must contain `node_modules` when the VSIX is created, so run `npm ci --omit=dev` before packaging. + +After publishing, install the extension into the Azure DevOps organization that owns your pipelines. + +When publishing an update, increment both versions before packaging: + +- `azure-pipelines/vss-extension.json` controls the extension version. +- `azure-pipelines/MicrobotsLogAnalyzerTask/task.json` controls the task version shown to Azure Pipelines. + +You can use `tfx extension create --manifest-globs vss-extension.json --rev-version` to increment the extension patch version, but task behavior changes still need a `task.json` version bump. + +## Use It In A Pipeline + +See the complete sample pipeline at [docs/examples/azure-pipelines/microbots-log-analyzer.yml](examples/azure-pipelines/microbots-log-analyzer.yml). + +```yaml +- task: MicrobotsLogAnalyzer@0 + displayName: Analyze build log + inputs: + azureSubscription: my-azure-service-connection + deploymentName: my-azure-openai-deployment + endpoint: https://my-azure-openai-resource.openai.azure.com/ + apiVersion: 2025-03-01-preview + codebasePath: $(Build.SourcesDirectory) + logFilePath: logs/build.log + timeoutSeconds: 600 + maxIterations: 20 +``` + +The log file must exist before `MicrobotsLogAnalyzer@0` runs. Relative `logFilePath` values are resolved from `codebasePath`; absolute paths are also supported. + +## Inputs + +| Input | Required | Default | Description | +|---|---:|---|---| +| `azureSubscription` | Yes | - | Azure Resource Manager service connection used for Azure CLI login. Alias: `serviceConnection`. | +| `deploymentName` | Yes | - | Azure OpenAI deployment name. | +| `endpoint` | Yes | - | Azure OpenAI endpoint, for example `https://my-resource.openai.azure.com/`. | +| `apiVersion` | Yes | - | Azure OpenAI API version passed to Microbots, for example `2025-03-01-preview`. | +| `codebasePath` | Yes | - | Repository or source folder Microbots can inspect while analyzing the log. | +| `logFilePath` | Yes | - | Log file path. Use an absolute path, or a relative path resolved from `codebasePath`. | +| `timeoutSeconds` | No | `600` | Maximum time for `LogAnalysisBot.run()`. | +| `maxIterations` | No | LogAnalysisBot default | Maximum number of Microbots iterations. Leave unset to use the default from `LogAnalysisBot.run()`. | + +## How It Works + +1. Azure Pipelines runs the task with the `Node20_1` task handler. +2. The task logs in with the supplied Azure Resource Manager Service Connection. +3. The task creates or reuses a virtual environment (`microbots-log-analyzer-venv`). +4. The task installs `microbots[azure_ad]` into that virtual environment. +5. A short Python runner creates `LogAnalysisBot` with `AzureCliCredential`, mounts `codebasePath` as context, passes `logFilePath`, optional `maxIterations`, and `timeoutSeconds` to `LogAnalysisBot.run()`, and prints the analysis result. + +The task clears the Azure CLI account at the end of the run. Its task manifest also uses Azure Pipelines command restrictions so analyzed log content cannot set arbitrary pipeline variables. diff --git a/docs/examples/azure-pipelines/microbots-log-analyzer.yml b/docs/examples/azure-pipelines/microbots-log-analyzer.yml new file mode 100644 index 0000000..8385fbb --- /dev/null +++ b/docs/examples/azure-pipelines/microbots-log-analyzer.yml @@ -0,0 +1,29 @@ +trigger: +- none + +jobs: +- job: MicrobotsLogAnalyzer + pool: + vmImage: ubuntu-latest + timeoutInMinutes: 30 + steps: + - checkout: self + + - bash: | + set -euo pipefail + command -v az >/dev/null 2>&1 || { echo '##[error]azure-cli is required on the agent'; exit 1; } + command -v python3 >/dev/null 2>&1 || { echo '##[error]python3 is required on the agent'; exit 1; } + python3 -m venv --help >/dev/null 2>&1 || { echo '##[error]python3 venv support is required'; exit 1; } + displayName: Check MicrobotsLogAnalyzer prerequisites + + - task: MicrobotsLogAnalyzer@0 + displayName: Analyze build log + inputs: + azureSubscription: my-azure-service-connection + deploymentName: my-azure-openai-deployment + endpoint: https://my-azure-openai-resource.openai.azure.com/ + apiVersion: 2025-03-01-preview + codebasePath: $(Build.SourcesDirectory) + logFilePath: logs/build.log + timeoutSeconds: 600 + maxIterations: 20 diff --git a/mkdocs.yml b/mkdocs.yml index 4028ca9..dbc61de 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -66,6 +66,7 @@ nav: - CopilotBot: copilot-bot.md - "Authentication Setup": authentication.md - "Azure Managed Identity & Service Connection Setup": guides/azure-managed-identity-setup.md + - Azure Pipelines Log Analyzer: azure-pipelines-log-analyzer.md - Tools: - "Custom Tool Integration Walkthrough": blog/guides/tesseract_ocr_tool_use.md - Blogs: