diff --git a/build/yaml/templates/dotnet-build-test-steps.yml b/build/yaml/templates/dotnet-build-test-steps.yml index 679bc36bc0..cf6901e5b2 100644 --- a/build/yaml/templates/dotnet-build-test-steps.yml +++ b/build/yaml/templates/dotnet-build-test-steps.yml @@ -18,5 +18,5 @@ steps: displayName: 'Run `dotnet test`' inputs: command: test - projects: 'tests\**\*.csproj' + projects: 'tests\unit\**\*.csproj' arguments: '--configuration $(BuildConfiguration)' diff --git a/tests/functional/.editorconfig b/tests/functional/.editorconfig new file mode 100644 index 0000000000..6e13120105 --- /dev/null +++ b/tests/functional/.editorconfig @@ -0,0 +1,162 @@ +# EditorConfig is awesome:http://EditorConfig.org + +############################### +# Core EditorConfig Options # +############################### +root = true + +# All files +[*] +indent_style = space +# (Please don't specify an indent_size here; that has too many unintended consequences.) + +# Xml project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] +indent_size = 2 + +# Xml config files +[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] +indent_size = 2 + +# JSON files +[*.json] +indent_size = 2 + +# Schema files +[*.{schema,uischema}] +indent_size = 4 + +# Code files +[*.{cs,csx,vb,vbx}] +indent_size = 4 +insert_final_newline = true +charset = utf-8-bom + +############################### +# .NET Coding Conventions # +############################### +[*.{cs,vb}] +# Organize usings +dotnet_sort_system_directives_first = true +dotnet_separate_import_directive_groups = false + +# this. preferences +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_event = false:suggestion + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion +dotnet_style_readonly_field = true:suggestion + +# Expression-level preferences +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:none +dotnet_prefer_inferred_tuple_names = true:suggestion +dotnet_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent + +############################### +# Naming Conventions # +############################### + +# Style Definitions +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +# Use PascalCase for constant fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.applicable_accessibilities = * +dotnet_naming_symbols.constant_fields.required_modifiers = const + +############################### +# C# Code Style Rules # +############################### +[*.cs] +# var preferences +csharp_style_var_for_built_in_types = true:suggestion +csharp_style_var_when_type_is_apparent = true:suggestion +csharp_style_var_elsewhere = true:suggestion + +# Expression-bodied members +csharp_style_expression_bodied_methods = false:none +csharp_style_expression_bodied_constructors = false:none +csharp_style_expression_bodied_operators = true:suggestion +csharp_style_expression_bodied_properties = true:suggestion +csharp_style_expression_bodied_indexers = true:suggestion +csharp_style_expression_bodied_accessors = true:suggestion + +# Pattern-matching preferences +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion + +# Null-checking preferences +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion + +# Modifier preferences +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion + +# Expression-level preferences +csharp_prefer_braces = true:suggestion +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_pattern_local_over_anonymous_function = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion + +############################### +# C# Formatting Rules # +############################### +# New line preferences +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_case_contents = true +csharp_indent_switch_labels = true +csharp_indent_labels = flush_left + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_around_binary_operators = before_and_after +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_after_comma = true +csharp_space_after_dot = false + +# Wrapping preferences +csharp_preserve_single_line_statements = true +csharp_preserve_single_line_blocks = true + +# CS0618: Type or member is obsolete +dotnet_diagnostic.CS0618.severity = none diff --git a/tests/functional/.gitignore b/tests/functional/.gitignore new file mode 100644 index 0000000000..70b79471d0 --- /dev/null +++ b/tests/functional/.gitignore @@ -0,0 +1,377 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj +PublishProfiles/ +**/Properties/ServiceDependencies/ +**/.config + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +/**/package-lock.json + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Python virtual env +venv/ +virtualenv/ + +# Local appsettings +appsettings.Development.json + +# VS code settings +.vscode/ + +# PyCharm settings +.idea/ + +# Ignore correct yarn files (no zero install support) +.yarn/* +!.yarn/patches +!.yarn/releases +!.yarn/plugins +!.yarn/sdks +!.yarn/versions +.pnp.* diff --git a/tests/functional/.yarn/releases/yarn-berry.cjs b/tests/functional/.yarn/releases/yarn-berry.cjs new file mode 100644 index 0000000000..65ec5dde19 --- /dev/null +++ b/tests/functional/.yarn/releases/yarn-berry.cjs @@ -0,0 +1,55 @@ +#!/usr/bin/env node +module.exports=(()=>{var e={25545:e=>{function t(e){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}t.keys=()=>[],t.resolve=t,t.id=25545,e.exports=t},44692:(e,t,r)=>{"use strict";r.r(t),r.d(t,{default:()=>g});var A=r(54143);const n={optional:!0},o=[["@samverschueren/stream-to-observable@<0.3.1",{peerDependenciesMeta:{rxjs:n,zenObservable:n}}],["any-observable@<0.5.1",{peerDependenciesMeta:{rxjs:n,zenObservable:n}}],["@pm2/agent@<1.0.4",{dependencies:{debug:"*"}}],["debug@<4.2.0",{peerDependenciesMeta:{"supports-color":n}}],["got@<11",{dependencies:{"@types/responselike":"^1.0.0","@types/keyv":"^3.1.1"}}],["cacheable-lookup@<4.1.2",{dependencies:{"@types/keyv":"^3.1.1"}}],["http-link-dataloader@*",{peerDependencies:{graphql:"^0.13.1 || ^14.0.0"}}],["typescript-language-server@*",{dependencies:{"vscode-jsonrpc":"^5.0.1","vscode-languageserver-protocol":"^3.15.0"}}],["postcss-syntax@*",{peerDependenciesMeta:{"postcss-html":n,"postcss-jsx":n,"postcss-less":n,"postcss-markdown":n,"postcss-scss":n}}],["jss-plugin-rule-value-function@<=10.1.1",{dependencies:{"tiny-warning":"^1.0.2"}}],["ink-select-input@<4.1.0",{peerDependencies:{react:"^16.8.2"}}],["promise-inflight@*",{peerDependenciesMeta:{bluebird:n}}],["reactcss@*",{peerDependencies:{react:"*"}}],["react-color@<=2.19.0",{peerDependencies:{react:"*"}}],["gatsby-plugin-i18n@*",{dependencies:{ramda:"^0.24.1"}}],["useragent@^2.0.0",{dependencies:{request:"^2.88.0",yamlparser:"0.0.x",semver:"5.5.x"}}],["@apollographql/apollo-tools@*",{peerDependencies:{graphql:"^14.2.1 || ^15.0.0"}}],["material-table@^2.0.0",{dependencies:{"@babel/runtime":"^7.11.2"}}],["@babel/parser@*",{dependencies:{"@babel/types":"^7.8.3"}}],["fork-ts-checker-webpack-plugin@*",{peerDependencies:{eslint:">= 6",typescript:">= 2.7",webpack:">= 4"},peerDependenciesMeta:{eslint:n}}],["rc-animate@*",{peerDependencies:{react:"^15.0.0 || ^16.0.0","react-dom":"^15.0.0 || ^16.0.0"}}],["react-bootstrap-table2-paginator@*",{dependencies:{classnames:"^2.2.6"}}],["react-draggable@<=4.4.3",{peerDependencies:{react:">= 16.3.0","react-dom":">= 16.3.0"}}],["apollo-upload-client@<14",{peerDependencies:{graphql:"14 - 15"}}],["react-instantsearch-core@<=6.7.0",{peerDependencies:{algoliasearch:">= 3.1 < 5"}}],["react-instantsearch-dom@<=6.7.0",{dependencies:{"react-fast-compare":"^3.0.0"}}],["ws@<7.2.1",{peerDependencies:{bufferutil:"^4.0.1","utf-8-validate":"^5.0.2"},peerDependenciesMeta:{bufferutil:n,"utf-8-validate":n}}],["react-portal@*",{peerDependencies:{"react-dom":"^15.0.0-0 || ^16.0.0-0 || ^17.0.0-0"}}]];let i,s,a;const c=new Map([[A.makeIdent(null,"fsevents").identHash,function(){return void 0===i&&(i=r(78761).brotliDecompressSync(Buffer.from("G7weAByFTVk3Vs7UfHhq4yykgEM7pbW7TI43SG2S5tvGrwHBAzdz+s/npQ6tgEvobvxisrPIadkXeUAJotBn5bDZ5kAhcRqsIHe3F75Walet5hNalwgFDtxb0BiDUjiUQkjG0yW2hto9HPgiCkm316d6bC0kST72YN7D7rfkhCE9x4J0XwB0yavalxpUu2t9xszHrmtwalOxT7VslsxWcB1qpqZwERUra4psWhTV8BgwWeizurec82Caf1ABL11YMfbf8FJ9JBceZOkgmvrQPbC9DUldX/yMbmX06UQluCEjSwUoyO+EZPIjofr+/oAZUck2enraRD+oWLlnlYnj8xB+gwSo9lmmks4fXv574qSqcWA6z21uYkzMu3EWj+K23RxeQlLqiE35/rC8GcS4CGkKHKKq+zAIQwD9iRDNfiAqueLLpicFFrNsAI4zeTD/eO9MHcnRa5m8UT+M2+V+AkFST4BlKneiAQRSdST8KEAIyFlULt6wa9EBd0Ds28VmpaxquJdVt+nwdEs5xUskI13OVtFyY0UrQIRAlCuvvWivvlSKQfTO+2Q8OyUR1W5RvetaPz4jD27hdtwHFFA1Ptx6Ee/t2cY2rg2G46M1pNDRf2pWhvpy8pqMnuI3++4OF3+7OFIWXGjh+o7Nr2jNvbiYcQdQS1h903/jVFgOpA0yJ78z+x759bFA0rq+6aY5qPB4FzS3oYoLupDUhD9nDz6F6H7hpnlMf18KNKDu4IKjTWwrAnY6MFQw1W6ymOALHlFyCZmQhldg1MQHaMVVQTVgDC60TfaBqG++Y8PEoFhN/PBTZT175KNP/BlHDYGOOBmnBdzqJKplZ/ljiVG0ZBzfqeBRrrUkn6rA54462SgiliKoYVnbeptMdXNfAuaupIEi0bApF10TlgHfmEJAPUVidRVFyDupSem5po5vErPqWKhKbUIp0LozpYsIKK57dM/HKr+nguF+7924IIWMICkQ8JUigs9D+W+c4LnNoRtPPKNRUiCYmP+Jfo2lfKCKw8qpraEeWU3uiNRO6zcyKQoXPR5htmzzLznke7b4YbXW3I1lIRzmgG02Udb58U+7TpwyN7XymCgH+wuPDthZVQvRZuEP+SnLtMicz9m5zASWOBiAcLmkuFlTKuHspSIhCBD0yUPKcxu81A+4YD78rA2vtwsUEday9WNyrShyrl60rWmA+SmbYZkQOwFJWArxRYYc5jGhA5ikxYw1rx3ei4NmeX/lKiwpZ9Ln1tV2Ae7sArvxuVLbJjqJRjW1vFXAyHpvLG+8MJ6T2Ubx5M2KDa2SN6vuIGxJ9WQM9Mk3Q7aCNiZONXllhqq24DmoLbQfW2rYWsOgHWjtOmIQMyMKdiHZDjoyIq5+U700nZ6odJAoYXPQBvFNiQ78d5jaXliBqLTJEqUCwi+LiH2mx92EmNKDsJL74Z613+3lf20pxkV1+erOrjj8pW00vsPaahKUM+05ssd5uwM7K482KWEf3TCwlg/o3e5ngto7qSMz7YteIgCsF1UOcsLk7F7MxWbvrPMY473ew0G+noVL8EPbkmEMftMSeL6HFub/zy+2JQ==","base64")).toString()),i}],[A.makeIdent(null,"resolve").identHash,function(){return void 0===s&&(s=r(78761).brotliDecompressSync(Buffer.from("G1QTIIzURnVBnGa0VPvr81orV8AFIqdU0sqrdcVgCdukgAZwi8a50gLk9+19Z2NcUILjmzXkzt4dzm5a6Yoys+/9qnKiaApXukOiuoyUaMcynG4X7X4vBaIE/PL30gwG6HSGJkLxb9PnLjfMr+748n7sM6C/NycK6ber/bX1reVVxta6W/31tZIhfrS+upoE/TPRHj0S/l0T59gTGdtKOp1OmMOJt9rhfucDdLJ2tgyfnO+u4YMkQAcYq/nebTcDmbXhqhgo6iQA4M3m4xya4Cos3p6klmkmQT+S4DLDZfwfMF+sUCx36KleOtaHLQfEIz0Bmncj/Ngi3lqOl4391EWEfIss6gVp3oDUGwsSZJKeOVONJWZg+Mue3KUMV3aMqYJ+7b2219D+GFDi8EV5y/Y+5J+He0oNjKAgqLsJziEsS9uIaCu3BHBKSXxNKKa2ShbfglcWoiiVT2kfGI7Gw+YJ/Sqy1H6wdFWtyVUQIa82JPwbeV25YKLzc5ZIFM6GCPSA+J9dTvJbs5LuuKnLP3f09gCu2jxqsAv6CA+ZySVaUJr2d3A70BC/uBCKr2OVrWgC3fSwb7NlfkgSEEiejrMGvhya9lMbVI6lMsFKN330A1/FOaefHQdNGLEZ3IwFF87H3xVlM0Xxsmbi/7A60oymRcIe0tH90alG6ez/yA7jwYotxuHWZdR+1HlMcddGHAV6QD/gXYPV0wnNv47I+5FGevzZFMqWSO8GU4nQ3FjsdgdJcD+c1rvudERKuLyd7bxiBpnsMDHsvPP4nXdXkld/gUNks3GAE1Otmb90bavDyiw4Mrx496Iw+jbLTgsCZGZXSZ9vM55C7KGe4HyJAKXEk0iT/Cj/PFwLJBN7pcP7ZFfYtUApGTWKkYhI9IE2zt/5ByH72wdvH+88b71zuv/FMCX3w6x5nzhY44Cg5IYv9LeKwHuHIWgPbfgrAcUxOlKkPRdQOIDF/aBuLPJAXD+TgxCNXx4jQxeR/qlBWVikFPfEI4rXMUc4kZ2w9KbPKYRvFUag0dVlVoyUP4zfidbTXAdZF88jAckl+NHjLFCNdX7EQ1PbLSOl+P+MqgwEOCi6dxgWZ7NCwJBjWKpk1LaxwKrhZ4aEC/0lMPJYe5S8xAakDcmA2kSS86GjEMTrv3VEu0S0YGZcxToMV524G4WAc4CReePePdipvs4aXRL5p+aeN96yfMGjsiTbQNxgbdRKc+keQ+NxYIEm1mBtEO29WrcbrqNbQRMR66KpGG4aG0NtmRyZ2JhUvu0paCklRlID8PT3gSiwZrqr4XZXoBBzBMrveWCuOg7iTgGDXDdbGi8XHkQf5KXDGFUxWueu5wkSa6gMWY1599g2piQjwBKIAPt4N5cOZdFBidz2feGwEAy1j1UydGxDSCCUsh314cUIIRV/dWCheceubL2gU8CibewmP7UxmN5kN4I7zfQhPxkP0NCcei8GXQpw4c3krEzW7PR2hgi/hqqqR58UJ/ZVfWxfcH5ZKMo4itkmPK0FCGxzzIRP20lK/gz28Y03sY233KvSVWUKl9rcbX6MbHjpUG8MvNlw72p6FwTejv92zgpnCxVJnIHHZhCBxNcHF5RTveRp513hUtTHHq4BIndlytZT5xoTSYfHKqKNr4o9kcGINIz6tZSKRdtbON3Ydr9cgqxHIeisMNIsvPg/IFMZuBbSqqDLeSO5dak1cGr76FtH2PC7hs0S0Oq3GsmF1Ga4YABAMGcdPAWzTk26B7cKV91I2b0V/GYvnsEQ1YGntRqi5EQqTlgZszbV/32GuZtUF49JOA/r4jAdwUOsbPo6mNoBlJPYjM5axrZaWQf33bFsLWqiyvvDOM4x0Ng802T7cuP2a3q98GWq6yiq6q3M77hcZlOUnmryctRYmI4Hb2F5XixFohkBmySCjU+M7/WQVE5YAtnlxiUJDhFN0y1tNeMWY9E0MfZi2rQ4eC72WXjsAA==","base64")).toString()),s}],[A.makeIdent(null,"typescript").identHash,function(){return void 0===a&&(a=r(78761).brotliDecompressSync(Buffer.from("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","base64")).toString()),a}]]),g={hooks:{registerPackageExtensions:async(e,t)=>{for(const[e,r]of o)t(A.parseDescriptor(e,!0),r)},getBuiltinPatch:async(e,t)=>{var r;if(!t.startsWith("compat/"))return;const n=A.parseIdent(t.slice("compat/".length)),o=null===(r=c.get(n.identHash))||void 0===r?void 0:r();return void 0!==o?o:null},reduceDependency:async(e,t,r,n)=>void 0===c.get(e.identHash)?e:A.makeDescriptor(e,A.makeRange({protocol:"patch:",source:A.stringifyDescriptor(e),selector:`builtin`,params:null}))}}},10189:(e,t,r)=>{"use strict";r.r(t),r.d(t,{default:()=>p});var A=r(36370),n=r(25413),o=r(54143),i=r(40822);class s extends n.BaseCommand{constructor(){super(...arguments),this.quiet=!1,this.args=[]}async execute(){const e=[];this.pkg&&e.push("--package",this.pkg),this.quiet&&e.push("--quiet");const t=o.parseIdent(this.command),r=o.makeIdent(t.scope,"create-"+t.name);return this.cli.run(["dlx",...e,o.stringifyIdent(r),...this.args])}}(0,A.gn)([i.Command.String("-p,--package",{description:"The package to run the provided command from"})],s.prototype,"pkg",void 0),(0,A.gn)([i.Command.Boolean("-q,--quiet",{description:"Only report critical errors instead of printing the full install logs"})],s.prototype,"quiet",void 0),(0,A.gn)([i.Command.String()],s.prototype,"command",void 0),(0,A.gn)([i.Command.Proxy()],s.prototype,"args",void 0),(0,A.gn)([i.Command.Path("create")],s.prototype,"execute",null);var a=r(39922),c=r(85824),g=r(63088),l=r(43896),u=r(46009);class h extends n.BaseCommand{constructor(){super(...arguments),this.quiet=!1,this.args=[]}async execute(){return a.VK.telemetry=null,await l.xfs.mktempPromise(async e=>{const t=u.y1.join(e,"dlx-"+process.pid);await l.xfs.mkdirPromise(t),await l.xfs.writeFilePromise(u.y1.join(t,"package.json"),"{}\n"),await l.xfs.writeFilePromise(u.y1.join(t,"yarn.lock"),"");const r=u.y1.join(t,".yarnrc.yml"),A=await a.VK.findProjectCwd(this.context.cwd,u.QS.lockfile),i=null!==A?u.y1.join(A,".yarnrc.yml"):null;null!==i&&l.xfs.existsSync(i)?(await l.xfs.copyFilePromise(i,r),await a.VK.updateConfiguration(t,e=>{const t={...e,enableGlobalCache:!0,enableTelemetry:!1};return Array.isArray(e.plugins)&&(t.plugins=e.plugins.map(e=>{const t="string"==typeof e?e:e.path,r=u.cS.isAbsolute(t)?t:u.cS.resolve(u.cS.fromPortablePath(A),t);return"string"==typeof e?r:{path:r,spec:e.spec}})),t})):await l.xfs.writeFilePromise(r,"enableGlobalCache: true\nenableTelemetry: false\n");const s=void 0!==this.pkg?[this.pkg]:[this.command],h=o.parseDescriptor(this.command).name,p=await this.cli.run(["add","--",...s],{cwd:t,quiet:this.quiet});if(0!==p)return p;this.quiet||this.context.stdout.write("\n");const d=await a.VK.find(t,this.context.plugins),{project:C,workspace:f}=await c.I.find(d,t);if(null===f)throw new n.WorkspaceRequiredError(C.cwd,t);return await C.restoreInstallState(),await g.executeWorkspaceAccessibleBinary(f,h,this.args,{cwd:this.context.cwd,stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr})})}}h.usage=i.Command.Usage({description:"run a package in a temporary environment",details:"\n This command will install a package within a temporary environment, and run its binary script if it contains any. The binary will run within the current cwd.\n\n By default Yarn will download the package named `command`, but this can be changed through the use of the `-p,--package` flag which will instruct Yarn to still run the same command but from a different package.\n\n Using `yarn dlx` as a replacement of `yarn add` isn't recommended, as it makes your project non-deterministic (Yarn doesn't keep track of the packages installed through `dlx` - neither their name, nor their version).\n ",examples:[["Use create-react-app to create a new React app","yarn dlx create-react-app ./my-app"]]}),(0,A.gn)([i.Command.String("-p,--package",{description:"The package to run the provided command from"})],h.prototype,"pkg",void 0),(0,A.gn)([i.Command.Boolean("-q,--quiet",{description:"Only report critical errors instead of printing the full install logs"})],h.prototype,"quiet",void 0),(0,A.gn)([i.Command.String()],h.prototype,"command",void 0),(0,A.gn)([i.Command.Proxy()],h.prototype,"args",void 0),(0,A.gn)([i.Command.Path("dlx")],h.prototype,"execute",null);const p={commands:[s,h]}},34777:(e,t,r)=>{"use strict";r.r(t),r.d(t,{dedupeUtils:()=>A,default:()=>We,suggestUtils:()=>A});var A={};r.r(A),r.d(A,{Modifier:()=>o,Strategy:()=>i,Target:()=>n,applyModifier:()=>S,extractDescriptorFromPath:()=>N,extractRangeModifier:()=>v,fetchDescriptorFrom:()=>K,findProjectDescriptors:()=>k,getModifier:()=>D,getSuggestedDescriptors:()=>F});var n,o,i,s=r(39922),a=r(36370),c=r(25413),g=r(28148),l=r(62152),u=r(92659),h=r(85824),p=r(15815),d=r(54143),C=r(40822),f=r(61899),I=r(33720),E=r(46611),B=r(71643),y=r(43896),m=r(46009),w=r(53887),Q=r.n(w);function D(e,t){return e.exact?o.EXACT:e.caret?o.CARET:e.tilde?o.TILDE:t.configuration.get("defaultSemverRangePrefix")}!function(e){e.REGULAR="dependencies",e.DEVELOPMENT="devDependencies",e.PEER="peerDependencies"}(n||(n={})),function(e){e.CARET="^",e.TILDE="~",e.EXACT=""}(o||(o={})),function(e){e.KEEP="keep",e.REUSE="reuse",e.PROJECT="project",e.LATEST="latest",e.CACHE="cache"}(i||(i={}));const b=/^([\^~]?)[0-9]+(?:\.[0-9]+){0,2}(?:-\S+)?$/;function v(e,{project:t}){const r=e.match(b);return r?r[1]:t.configuration.get("defaultSemverRangePrefix")}function S(e,t){let{protocol:r,source:A,params:n,selector:o}=d.parseRange(e.range);return Q().valid(o)&&(o=`${t}${e.range}`),d.makeDescriptor(e,d.makeRange({protocol:r,source:A,params:n,selector:o}))}async function k(e,{project:t,target:r}){const A=new Map,o=e=>{let t=A.get(e.descriptorHash);return t||A.set(e.descriptorHash,t={descriptor:e,locators:[]}),t};for(const A of t.workspaces)if(r===n.PEER){const t=A.manifest.peerDependencies.get(e.identHash);void 0!==t&&o(t).locators.push(A.locator)}else{const t=A.manifest.dependencies.get(e.identHash),i=A.manifest.devDependencies.get(e.identHash);r===n.DEVELOPMENT?void 0!==i?o(i).locators.push(A.locator):void 0!==t&&o(t).locators.push(A.locator):void 0!==t?o(t).locators.push(A.locator):void 0!==i&&o(i).locators.push(A.locator)}return A}async function N(e,{cwd:t,workspace:r}){return await async function(e){return await y.xfs.mktempPromise(async t=>{const r=s.VK.create(t);return r.useWithSource(t,{enableMirror:!1,compressionLevel:0},t,{overwrite:!0}),await e(new g.C(t,{configuration:r,check:!1,immutable:!1}))})}(async A=>{m.y1.isAbsolute(e)||(e=m.y1.relative(r.cwd,m.y1.resolve(t,e))).match(/^\.{0,2}\//)||(e="./"+e);const{project:n}=r,o=await K(d.makeIdent(null,"archive"),e,{project:r.project,cache:A,workspace:r});if(!o)throw new Error("Assertion failed: The descriptor should have been found");const i=new I.$,s=n.configuration.makeResolver(),a=n.configuration.makeFetcher(),c={checksums:n.storedChecksums,project:n,cache:A,fetcher:a,report:i,resolver:s},g=s.bindDescriptor(o,r.anchoredLocator,c),l=d.convertDescriptorToLocator(g),u=await a.fetch(l,c),h=await E.G.find(u.prefixPath,{baseFs:u.packageFs});if(!h.name)throw new Error("Target path doesn't have a name");return d.makeDescriptor(h.name,e)})}async function F(e,{project:t,workspace:r,cache:A,target:o,modifier:s,strategies:a,maxResults:c=1/0}){if(!(c>=0))throw new Error(`Invalid maxResults (${c})`);if("unknown"!==e.range)return{suggestions:[{descriptor:e,name:"Use "+d.prettyDescriptor(t.configuration,e),reason:"(unambiguous explicit request)"}],rejections:[]};const g=null!=r&&r.manifest[o].get(e.identHash)||null,l=[],u=[],h=async e=>{try{await e()}catch(e){u.push(e)}};for(const u of a){if(l.length>=c)break;switch(u){case i.KEEP:await h(async()=>{g&&l.push({descriptor:g,name:"Keep "+d.prettyDescriptor(t.configuration,g),reason:"(no changes)"})});break;case i.REUSE:await h(async()=>{for(const{descriptor:A,locators:n}of(await k(e,{project:t,target:o})).values()){if(1===n.length&&n[0].locatorHash===r.anchoredLocator.locatorHash&&a.includes(i.KEEP))continue;let e="(originally used by "+d.prettyLocator(t.configuration,n[0]);e+=n.length>1?` and ${n.length-1} other${n.length>2?"s":""})`:")",l.push({descriptor:A,name:"Reuse "+d.prettyDescriptor(t.configuration,A),reason:e})}});break;case i.CACHE:await h(async()=>{for(const r of t.storedDescriptors.values())r.identHash===e.identHash&&l.push({descriptor:r,name:"Reuse "+d.prettyDescriptor(t.configuration,r),reason:"(already used somewhere in the lockfile)"})});break;case i.PROJECT:await h(async()=>{if(null!==r.manifest.name&&e.identHash===r.manifest.name.identHash)return;const A=t.tryWorkspaceByIdent(e);null!==A&&l.push({descriptor:A.anchoredDescriptor,name:"Attach "+d.prettyWorkspace(t.configuration,A),reason:`(local workspace at ${A.cwd})`})});break;case i.LATEST:await h(async()=>{if("unknown"!==e.range)l.push({descriptor:e,name:"Use "+d.prettyRange(t.configuration,e.range),reason:"(explicit range requested)"});else if(o===n.PEER)l.push({descriptor:d.makeDescriptor(e,"*"),name:"Use *",reason:"(catch-all peer dependency pattern)"});else if(t.configuration.get("enableNetwork")){let n=await K(e,"latest",{project:t,cache:A,workspace:r,preserveModifier:!1});n&&(n=S(n,s),l.push({descriptor:n,name:"Use "+d.prettyDescriptor(t.configuration,n),reason:"(resolved from latest)"}))}else l.push({descriptor:null,name:"Resolve from latest",reason:B.pretty(t.configuration,"(unavailable because enableNetwork is toggled off)","grey")})})}}return{suggestions:l.slice(0,c),rejections:u.slice(0,c)}}async function K(e,t,{project:r,cache:A,workspace:n,preserveModifier:o=!0}){const i=d.makeDescriptor(e,t),s=new I.$,a=r.configuration.makeFetcher(),c=r.configuration.makeResolver(),g={project:r,fetcher:a,cache:A,checksums:r.storedChecksums,report:s,skipIntegrityCheck:!0},l={...g,resolver:c,fetchOptions:g},u=c.bindDescriptor(i,n.anchoredLocator,l),h=await c.getCandidates(u,new Map,l);if(0===h.length)return null;const p=h[0];let{protocol:C,source:f,params:E,selector:B}=d.parseRange(d.convertToManifestRange(p.reference));if(C===r.configuration.get("defaultProtocol")&&(C=null),Q().valid(B)&&!1!==o){B=v("string"==typeof o?o:i.range,{project:r})+B}return d.makeDescriptor(p,d.makeRange({protocol:C,source:f,params:E,selector:B}))}class M extends c.BaseCommand{constructor(){super(...arguments),this.packages=[],this.json=!1,this.exact=!1,this.tilde=!1,this.caret=!1,this.dev=!1,this.peer=!1,this.optional=!1,this.preferDev=!1,this.interactive=null,this.cached=!1}async execute(){var e;const t=await s.VK.find(this.context.cwd,this.context.plugins),{project:r,workspace:A}=await h.I.find(t,this.context.cwd),o=await g.C.find(t);if(!A)throw new c.WorkspaceRequiredError(r.cwd,this.context.cwd);await r.restoreInstallState({restoreResolutions:!1});const a=null!==(e=this.interactive)&&void 0!==e?e:t.get("preferInteractive"),I=D(this,r),E=[...a?[i.REUSE]:[],i.PROJECT,...this.cached?[i.CACHE]:[],i.LATEST],B=a?1/0:1,y=await Promise.all(this.packages.map(async e=>{const t=e.match(/^\.{0,2}\//)?await N(e,{cwd:this.context.cwd,workspace:A}):d.parseDescriptor(e),i=function(e,t,{dev:r,peer:A,preferDev:o,optional:i}){const s=e.manifest[n.REGULAR].has(t.identHash),a=e.manifest[n.DEVELOPMENT].has(t.identHash),c=e.manifest[n.PEER].has(t.identHash);if((r||A)&&s)throw new C.UsageError(`Package "${d.prettyIdent(e.project.configuration,t)}" is already listed as a regular dependency - remove the -D,-P flags or remove it from your dependencies first`);if(!r&&!A&&c)throw new C.UsageError(`Package "${d.prettyIdent(e.project.configuration,t)}" is already listed as a peer dependency - use either of -D or -P, or remove it from your peer dependencies first`);if(i&&a)throw new C.UsageError(`Package "${d.prettyIdent(e.project.configuration,t)}" is already listed as a dev dependency - remove the -O flag or remove it from your dev dependencies first`);if(i&&!A&&c)throw new C.UsageError(`Package "${d.prettyIdent(e.project.configuration,t)}" is already listed as a peer dependency - remove the -O flag or add the -P flag or remove it from your peer dependencies first`);if((r||o)&&i)throw new C.UsageError(`Package "${d.prettyIdent(e.project.configuration,t)}" cannot simultaneously be a dev dependency and an optional dependency`);return A?n.PEER:r||o?n.DEVELOPMENT:s?n.REGULAR:a?n.DEVELOPMENT:n.REGULAR}(A,t,{dev:this.dev,peer:this.peer,preferDev:this.preferDev,optional:this.optional});return[t,await F(t,{project:r,workspace:A,cache:o,target:i,modifier:I,strategies:E,maxResults:B}),i]})),m=await l.h.start({configuration:t,stdout:this.context.stdout,suggestInstall:!1},async e=>{for(const[A,{suggestions:n,rejections:o}]of y){if(0===n.filter(e=>null!==e.descriptor).length){const[n]=o;if(void 0===n)throw new Error("Assertion failed: Expected an error to have been set");const i=this.cli.error(n);r.configuration.get("enableNetwork")?e.reportError(u.b.CANT_SUGGEST_RESOLUTIONS,`${d.prettyDescriptor(t,A)} can't be resolved to a satisfying range:\n\n${i}`):e.reportError(u.b.CANT_SUGGEST_RESOLUTIONS,`${d.prettyDescriptor(t,A)} can't be resolved to a satisfying range (note: network resolution has been disabled):\n\n${i}`)}}});if(m.hasErrors())return m.exitCode();let w=!1;const Q=[],b=[];for(const[,{suggestions:e},t]of y){let r;const n=e.filter(e=>null!==e.descriptor),o=n[0].descriptor,i=n.every(e=>d.areDescriptorsEqual(e.descriptor,o));1===n.length||i?r=o:(w=!0,({answer:r}=await(0,f.prompt)({type:"select",name:"answer",message:"Which range do you want to use?",choices:e.map(({descriptor:e,name:t,reason:r})=>e?{name:t,hint:r,descriptor:e}:{name:t,hint:r,disabled:!0}),onCancel:()=>process.exit(130),result(e){return this.find(e,"descriptor")},stdin:this.context.stdin,stdout:this.context.stdout})));const s=A.manifest[t].get(r.identHash);void 0!==s&&s.descriptorHash===r.descriptorHash||(A.manifest[t].set(r.identHash,r),this.optional&&("dependencies"===t?A.manifest.ensureDependencyMeta({...r,range:"unknown"}).optional=!0:"peerDependencies"===t&&(A.manifest.ensurePeerDependencyMeta({...r,range:"unknown"}).optional=!0)),void 0===s?Q.push([A,t,r,E]):b.push([A,t,s,r]))}await t.triggerMultipleHooks(e=>e.afterWorkspaceDependencyAddition,Q),await t.triggerMultipleHooks(e=>e.afterWorkspaceDependencyReplacement,b),w&&this.context.stdout.write("\n");return(await p.Pk.start({configuration:t,json:this.json,stdout:this.context.stdout,includeLogs:!this.context.quiet},async e=>{await r.install({cache:o,report:e})})).exitCode()}}M.usage=C.Command.Usage({description:"add dependencies to the project",details:"\n This command adds a package to the package.json for the nearest workspace.\n\n - If it didn't exist before, the package will by default be added to the regular `dependencies` field, but this behavior can be overriden thanks to the `-D,--dev` flag (which will cause the dependency to be added to the `devDependencies` field instead) and the `-P,--peer` flag (which will do the same but for `peerDependencies`).\n\n - If the package was already listed in your dependencies, it will by default be upgraded whether it's part of your `dependencies` or `devDependencies` (it won't ever update `peerDependencies`, though).\n\n - If set, the `--prefer-dev` flag will operate as a more flexible `-D,--dev` in that it will add the package to your `devDependencies` if it isn't already listed in either `dependencies` or `devDependencies`, but it will also happily upgrade your `dependencies` if that's what you already use (whereas `-D,--dev` would throw an exception).\n\n - If set, the `-O,--optional` flag will add the package to the `optionalDependencies` field and, in combination with the `-P,--peer` flag, it will add the package as an optional peer dependency. If the package was already listed in your `dependencies`, it will be upgraded to `optionalDependencies`. If the package was already listed in your `peerDependencies`, in combination with the `-P,--peer` flag, it will be upgraded to an optional peer dependency: `\"peerDependenciesMeta\": { \"\": { \"optional\": true } }`\n\n - If the added package doesn't specify a range at all its `latest` tag will be resolved and the returned version will be used to generate a new semver range (using the `^` modifier by default unless otherwise configured via the `defaultSemverRangePrefix` configuration, or the `~` modifier if `-T,--tilde` is specified, or no modifier at all if `-E,--exact` is specified). Two exceptions to this rule: the first one is that if the package is a workspace then its local version will be used, and the second one is that if you use `-P,--peer` the default range will be `*` and won't be resolved at all.\n\n - If the added package specifies a range (such as `^1.0.0`, `latest`, or `rc`), Yarn will add this range as-is in the resulting package.json entry (in particular, tags such as `rc` will be encoded as-is rather than being converted into a semver range).\n\n If the `--cached` option is used, Yarn will preferably reuse the highest version already used somewhere within the project, even if through a transitive dependency.\n\n If the `-i,--interactive` option is used (or if the `preferInteractive` settings is toggled on) the command will first try to check whether other workspaces in the project use the specified package and, if so, will offer to reuse them.\n\n For a compilation of all the supported protocols, please consult the dedicated page from our website: https://yarnpkg.com/features/protocols.\n ",examples:[["Add a regular package to the current workspace","$0 add lodash"],["Add a specific version for a package to the current workspace","$0 add lodash@1.2.3"],["Add a package from a GitHub repository (the master branch) to the current workspace using a URL","$0 add lodash@https://github.com/lodash/lodash"],["Add a package from a GitHub repository (the master branch) to the current workspace using the GitHub protocol","$0 add lodash@github:lodash/lodash"],["Add a package from a GitHub repository (the master branch) to the current workspace using the GitHub protocol (shorthand)","$0 add lodash@lodash/lodash"],["Add a package from a specific branch of a GitHub repository to the current workspace using the GitHub protocol (shorthand)","$0 add lodash-es@lodash/lodash#es"]]}),(0,a.gn)([C.Command.Rest()],M.prototype,"packages",void 0),(0,a.gn)([C.Command.Boolean("--json",{description:"Format the output as an NDJSON stream"})],M.prototype,"json",void 0),(0,a.gn)([C.Command.Boolean("-E,--exact",{description:"Don't use any semver modifier on the resolved range"})],M.prototype,"exact",void 0),(0,a.gn)([C.Command.Boolean("-T,--tilde",{description:"Use the `~` semver modifier on the resolved range"})],M.prototype,"tilde",void 0),(0,a.gn)([C.Command.Boolean("-C,--caret",{description:"Use the `^` semver modifier on the resolved range"})],M.prototype,"caret",void 0),(0,a.gn)([C.Command.Boolean("-D,--dev",{description:"Add a package as a dev dependency"})],M.prototype,"dev",void 0),(0,a.gn)([C.Command.Boolean("-P,--peer",{description:"Add a package as a peer dependency"})],M.prototype,"peer",void 0),(0,a.gn)([C.Command.Boolean("-O,--optional",{description:"Add / upgrade a package to an optional regular / peer dependency"})],M.prototype,"optional",void 0),(0,a.gn)([C.Command.Boolean("--prefer-dev",{description:"Add / upgrade a package to a dev dependency"})],M.prototype,"preferDev",void 0),(0,a.gn)([C.Command.Boolean("-i,--interactive",{description:"Reuse the specified package from other workspaces in the project"})],M.prototype,"interactive",void 0),(0,a.gn)([C.Command.Boolean("--cached",{description:"Reuse the highest version already used somewhere within the project"})],M.prototype,"cached",void 0),(0,a.gn)([C.Command.Path("add")],M.prototype,"execute",null);var R=r(63088);class x extends c.BaseCommand{constructor(){super(...arguments),this.verbose=!1,this.json=!1}async execute(){const e=await s.VK.find(this.context.cwd,this.context.plugins),{project:t,locator:r}=await h.I.find(e,this.context.cwd);if(await t.restoreInstallState(),this.name){const A=(await R.getPackageAccessibleBinaries(r,{project:t})).get(this.name);if(!A)throw new C.UsageError(`Couldn't find a binary named "${this.name}" for package "${d.prettyLocator(e,r)}"`);const[,n]=A;return this.context.stdout.write(n+"\n"),0}return(await p.Pk.start({configuration:e,json:this.json,stdout:this.context.stdout},async A=>{const n=await R.getPackageAccessibleBinaries(r,{project:t}),o=Array.from(n.keys()).reduce((e,t)=>Math.max(e,t.length),0);for(const[e,[t,r]]of n)A.reportJson({name:e,source:d.stringifyIdent(t),path:r});if(this.verbose)for(const[t,[r]]of n)A.reportInfo(null,`${t.padEnd(o," ")} ${d.prettyLocator(e,r)}`);else for(const e of n.keys())A.reportInfo(null,e)})).exitCode()}}x.usage=C.Command.Usage({description:"get the path to a binary script",details:"\n When used without arguments, this command will print the list of all the binaries available in the current workspace. Adding the `-v,--verbose` flag will cause the output to contain both the binary name and the locator of the package that provides the binary.\n\n When an argument is specified, this command will just print the path to the binary on the standard output and exit. Note that the reported path may be stored within a zip archive.\n ",examples:[["List all the available binaries","$0 bin"],["Print the path to a specific binary","$0 bin eslint"]]}),(0,a.gn)([C.Command.String({required:!1})],x.prototype,"name",void 0),(0,a.gn)([C.Command.Boolean("-v,--verbose",{description:"Print both the binary name and the locator of the package that provides the binary"})],x.prototype,"verbose",void 0),(0,a.gn)([C.Command.Boolean("--json",{description:"Format the output as an NDJSON stream"})],x.prototype,"json",void 0),(0,a.gn)([C.Command.Path("bin")],x.prototype,"execute",null);class L extends c.BaseCommand{constructor(){super(...arguments),this.mirror=!1,this.all=!1}async execute(){const e=await s.VK.find(this.context.cwd,this.context.plugins),t=await g.C.find(e);return(await p.Pk.start({configuration:e,stdout:this.context.stdout},async()=>{const e=(this.all||this.mirror)&&null!==t.mirrorCwd,r=!this.mirror;e&&await y.xfs.removePromise(t.mirrorCwd),r&&await y.xfs.removePromise(t.cwd)})).exitCode()}}L.usage=C.Command.Usage({description:"remove the shared cache files",details:"\n This command will remove all the files from the cache.\n ",examples:[["Remove all the local archives","$0 cache clean"],["Remove all the archives stored in the ~/.yarn directory","$0 cache clean --mirror"]]}),(0,a.gn)([C.Command.Boolean("--mirror",{description:"Remove the global cache files instead of the local cache files"})],L.prototype,"mirror",void 0),(0,a.gn)([C.Command.Boolean("--all",{description:"Remove both the global cache files and the local cache files of the current project"})],L.prototype,"all",void 0),(0,a.gn)([C.Command.Path("cache","clean")],L.prototype,"execute",null);var P=r(73632),O=r(44674),U=r.n(O),T=r(31669);class j extends c.BaseCommand{constructor(){super(...arguments),this.json=!1,this.unsafe=!1}async execute(){const e=await s.VK.find(this.context.cwd,this.context.plugins),t=this.name.replace(/[.[].*$/,""),r=this.name.replace(/^[^.[]*/,"");if(void 0===e.settings.get(t))throw new C.UsageError(`Couldn't find a configuration settings named "${t}"`);const A=e.getSpecial(t,{hideSecrets:!this.unsafe,getNativePaths:!0}),n=P.convertMapsToIndexableObjects(A),o=r?U()(n,r):n,i=await p.Pk.start({configuration:e,includeFooter:!1,json:this.json,stdout:this.context.stdout},async e=>{e.reportJson(o)});if(!this.json){if("string"==typeof o)return this.context.stdout.write(o+"\n"),i.exitCode();T.inspect.styles.name="cyan",this.context.stdout.write((0,T.inspect)(o,{depth:1/0,colors:e.get("enableColors"),compact:!1})+"\n")}return i.exitCode()}}j.usage=C.Command.Usage({description:"read a configuration settings",details:"\n This command will print a configuration setting.\n\n Secrets (such as tokens) will be redacted from the output by default. If this behavior isn't desired, set the `--no-redacted` to get the untransformed value.\n ",examples:[["Print a simple configuration setting","yarn config get yarnPath"],["Print a complex configuration setting","yarn config get packageExtensions"],["Print a nested field from the configuration","yarn config get 'npmScopes[\"my-company\"].npmRegistryServer'"],["Print a token from the configuration","yarn config get npmAuthToken --no-redacted"],["Print a configuration setting as JSON","yarn config get packageExtensions --json"]]}),(0,a.gn)([C.Command.String()],j.prototype,"name",void 0),(0,a.gn)([C.Command.Boolean("--json",{description:"Format the output as an NDJSON stream"})],j.prototype,"json",void 0),(0,a.gn)([C.Command.Boolean("--no-redacted",{description:"Don't redact secrets (such as tokens) from the output"})],j.prototype,"unsafe",void 0),(0,a.gn)([C.Command.Path("config","get")],j.prototype,"execute",null);var Y=r(82558),G=r.n(Y),H=r(81534),J=r.n(H);class q extends c.BaseCommand{constructor(){super(...arguments),this.json=!1,this.home=!1}async execute(){const e=await s.VK.find(this.context.cwd,this.context.plugins);if(!e.projectCwd)throw new C.UsageError("This command must be run from within a project folder");const t=this.name.replace(/[.[].*$/,""),r=this.name.replace(/^[^.[]*\.?/,"");if(void 0===e.settings.get(t))throw new C.UsageError(`Couldn't find a configuration settings named "${t}"`);const A=this.json?JSON.parse(this.value):this.value,n=this.home?e=>s.VK.updateHomeConfiguration(e):t=>s.VK.updateConfiguration(e.projectCwd,t);await n(e=>{if(r){const t=G()(e);return J()(t,this.name,A),t}return{...e,[t]:A}});const o=(await s.VK.find(this.context.cwd,this.context.plugins)).getSpecial(t,{hideSecrets:!0,getNativePaths:!0}),i=P.convertMapsToIndexableObjects(o),a=r?U()(i,r):i;return(await p.Pk.start({configuration:e,includeFooter:!1,stdout:this.context.stdout},async t=>{T.inspect.styles.name="cyan",t.reportInfo(u.b.UNNAMED,`Successfully set ${this.name} to ${(0,T.inspect)(a,{depth:1/0,colors:e.get("enableColors"),compact:!1})}`)})).exitCode()}}q.usage=C.Command.Usage({description:"change a configuration settings",details:"\n This command will set a configuration setting.\n\n When used without the `--json` flag, it can only set a simple configuration setting (a string, a number, or a boolean).\n\n When used with the `--json` flag, it can set both simple and complex configuration settings, including Arrays and Objects.\n ",examples:[["Set a simple configuration setting (a string, a number, or a boolean)","yarn config set initScope myScope"],["Set a simple configuration setting (a string, a number, or a boolean) using the `--json` flag",'yarn config set initScope --json \\"myScope\\"'],["Set a complex configuration setting (an Array) using the `--json` flag",'yarn config set unsafeHttpWhitelist --json \'["*.example.com", "example.com"]\''],["Set a complex configuration setting (an Object) using the `--json` flag",'yarn config set packageExtensions --json \'{ "@babel/parser@*": { "dependencies": { "@babel/types": "*" } } }\''],["Set a nested configuration setting",'yarn config set npmScopes.company.npmRegistryServer "https://npm.example.com"'],["Set a nested configuration setting using indexed access for non-simple keys",'yarn config set \'npmRegistries["//npm.example.com"].npmAuthToken\' "ffffffff-ffff-ffff-ffff-ffffffffffff"']]}),(0,a.gn)([C.Command.String()],q.prototype,"name",void 0),(0,a.gn)([C.Command.String()],q.prototype,"value",void 0),(0,a.gn)([C.Command.Boolean("--json",{description:"Set complex configuration settings to JSON values"})],q.prototype,"json",void 0),(0,a.gn)([C.Command.Boolean("-H,--home",{description:"Update the home configuration instead of the project configuration"})],q.prototype,"home",void 0),(0,a.gn)([C.Command.Path("config","set")],q.prototype,"execute",null);class z extends c.BaseCommand{constructor(){super(...arguments),this.verbose=!1,this.why=!1,this.json=!1}async execute(){const e=await s.VK.find(this.context.cwd,this.context.plugins,{strict:!1});return(await p.Pk.start({configuration:e,json:this.json,stdout:this.context.stdout},async t=>{if(e.invalid.size>0&&!this.json){for(const[r,A]of e.invalid)t.reportError(u.b.INVALID_CONFIGURATION_KEY,`Invalid configuration key "${r}" in ${A}`);t.reportSeparator()}if(this.json){const r=P.sortMap(e.settings.keys(),e=>e);for(const A of r){const r=e.settings.get(A),n=e.getSpecial(A,{hideSecrets:!0,getNativePaths:!0}),o=e.sources.get(A);this.verbose?t.reportJson({key:A,effective:n,source:o}):t.reportJson({key:A,effective:n,source:o,...r})}}else{const r=P.sortMap(e.settings.keys(),e=>e),A=r.reduce((e,t)=>Math.max(e,t.length),0),n={breakLength:1/0,colors:e.get("enableColors"),maxArrayLength:2};if(this.why||this.verbose){const o=r.map(t=>{const r=e.settings.get(t);if(!r)throw new Error(`Assertion failed: This settings ("${t}") should have been registered`);return[t,this.why?e.sources.get(t)||"":r.description]}),i=o.reduce((e,[,t])=>Math.max(e,t.length),0);for(const[r,s]of o)t.reportInfo(null,`${r.padEnd(A," ")} ${s.padEnd(i," ")} ${(0,T.inspect)(e.getSpecial(r,{hideSecrets:!0,getNativePaths:!0}),n)}`)}else for(const o of r)t.reportInfo(null,`${o.padEnd(A," ")} ${(0,T.inspect)(e.getSpecial(o,{hideSecrets:!0,getNativePaths:!0}),n)}`)}})).exitCode()}}z.usage=C.Command.Usage({description:"display the current configuration",details:"\n This command prints the current active configuration settings.\n ",examples:[["Print the active configuration settings","$0 config"]]}),(0,a.gn)([C.Command.Boolean("-v,--verbose",{description:"Print the setting description on top of the regular key/value information"})],z.prototype,"verbose",void 0),(0,a.gn)([C.Command.Boolean("--why",{description:"Print the reason why a setting is set a particular way"})],z.prototype,"why",void 0),(0,a.gn)([C.Command.Boolean("--json",{description:"Format the output as an NDJSON stream"})],z.prototype,"json",void 0),(0,a.gn)([C.Command.Path("config")],z.prototype,"execute",null);var W,V=r(15966),X=r(35691),_=r(2401),Z=r.n(_);!function(e){e.HIGHEST="highest"}(W||(W={}));const $=new Set(Object.values(W)),ee={highest:async(e,t,{resolver:r,fetcher:A,resolveOptions:n,fetchOptions:o})=>{const i=new Map;for(const[t,r]of e.storedResolutions){const A=e.storedDescriptors.get(t);if(void 0===A)throw new Error(`Assertion failed: The descriptor (${t}) should have been registered`);P.getSetWithDefault(i,A.identHash).add(r)}return Array.from(e.storedDescriptors.values(),async A=>{if(t.length&&!Z().isMatch(d.stringifyIdent(A),t))return null;const o=e.storedResolutions.get(A.descriptorHash);if(void 0===o)throw new Error(`Assertion failed: The resolution (${A.descriptorHash}) should have been registered`);const s=e.originalPackages.get(o);if(void 0===s)return null;if(!r.shouldPersistResolution(s,n))return null;const a=i.get(A.identHash);if(void 0===a)throw new Error(`Assertion failed: The resolutions (${A.identHash}) should have been registered`);if(1===a.size)return null;const c=[...a].map(t=>{const r=e.originalPackages.get(t);if(void 0===r)throw new Error(`Assertion failed: The package (${t}) should have been registered`);return r.reference}),g=await r.getSatisfying(A,c,n),l=null==g?void 0:g[0];if(void 0===l)return null;const u=l.locatorHash,h=e.originalPackages.get(u);if(void 0===h)throw new Error(`Assertion failed: The package (${u}) should have been registered`);return u===o?null:{descriptor:A,currentPackage:s,updatedPackage:h}})}};class te extends c.BaseCommand{constructor(){super(...arguments),this.patterns=[],this.strategy=W.HIGHEST,this.check=!1,this.json=!1}async execute(){const e=await s.VK.find(this.context.cwd,this.context.plugins),{project:t}=await h.I.find(e,this.context.cwd),r=await g.C.find(e);let A=0;const n=await p.Pk.start({configuration:e,includeFooter:!1,stdout:this.context.stdout,json:this.json},async e=>{A=await async function(e,{strategy:t,patterns:r,cache:A,report:n}){const{configuration:o}=e,i=new I.$,s=o.makeResolver(),a=o.makeFetcher(),c={cache:A,checksums:e.storedChecksums,fetcher:a,project:e,report:i,skipIntegrityCheck:!0},g={project:e,resolver:s,report:i,fetchOptions:c};return await n.startTimerPromise("Deduplication step",async()=>{const A=ee[t],i=await A(e,r,{resolver:s,resolveOptions:g,fetcher:a,fetchOptions:c}),l=X.yG.progressViaCounter(i.length);n.reportProgress(l);let h,p=0;switch(await Promise.all(i.map(t=>t.then(t=>{if(null===t)return;p++;const{descriptor:r,currentPackage:A,updatedPackage:i}=t;n.reportInfo(u.b.UNNAMED,`${d.prettyDescriptor(o,r)} can be deduped from ${d.prettyLocator(o,A)} to ${d.prettyLocator(o,i)}`),n.reportJson({descriptor:d.stringifyDescriptor(r),currentResolution:d.stringifyLocator(A),updatedResolution:d.stringifyLocator(i)}),e.storedResolutions.set(r.descriptorHash,i.locatorHash)}).finally(()=>l.tick()))),p){case 0:h="No packages";break;case 1:h="One package";break;default:h=p+" packages"}const C=B.pretty(o,t,B.Type.CODE);return n.reportInfo(u.b.UNNAMED,`${h} can be deduped using the ${C} strategy`),p})}(t,{strategy:this.strategy,patterns:this.patterns,cache:r,report:e})});if(n.hasErrors())return n.exitCode();if(this.check)return A?1:0;return(await p.Pk.start({configuration:e,stdout:this.context.stdout,json:this.json},async e=>{await t.install({cache:r,report:e})})).exitCode()}}te.schema=V.object().shape({strategy:V.string().test({name:"strategy",message:"${path} must be one of ${strategies}",params:{strategies:[...$].join(", ")},test:e=>$.has(e)})}),te.usage=C.Command.Usage({description:"deduplicate dependencies with overlapping ranges",details:"\n Duplicates are defined as descriptors with overlapping ranges being resolved and locked to different locators. They are a natural consequence of Yarn's deterministic installs, but they can sometimes pile up and unnecessarily increase the size of your project.\n\n This command dedupes dependencies in the current project using different strategies (only one is implemented at the moment):\n\n - `highest`: Reuses (where possible) the locators with the highest versions. This means that dependencies can only be upgraded, never downgraded. It's also guaranteed that it never takes more than a single pass to dedupe the entire dependency tree.\n\n **Note:** Even though it never produces a wrong dependency tree, this command should be used with caution, as it modifies the dependency tree, which can sometimes cause problems when packages don't strictly follow semver recommendations. Because of this, it is recommended to also review the changes manually.\n\n If set, the `-c,--check` flag will only report the found duplicates, without persisting the modified dependency tree. If changes are found, the command will exit with a non-zero exit code, making it suitable for CI purposes.\n\n This command accepts glob patterns as arguments (if valid Idents and supported by [micromatch](https://github.com/micromatch/micromatch)). Make sure to escape the patterns, to prevent your own shell from trying to expand them.\n\n ### In-depth explanation:\n\n Yarn doesn't deduplicate dependencies by default, otherwise installs wouldn't be deterministic and the lockfile would be useless. What it actually does is that it tries to not duplicate dependencies in the first place.\n\n **Example:** If `foo@^2.3.4` (a dependency of a dependency) has already been resolved to `foo@2.3.4`, running `yarn add foo@*`will cause Yarn to reuse `foo@2.3.4`, even if the latest `foo` is actually `foo@2.10.14`, thus preventing unnecessary duplication.\n\n Duplication happens when Yarn can't unlock dependencies that have already been locked inside the lockfile.\n\n **Example:** If `foo@^2.3.4` (a dependency of a dependency) has already been resolved to `foo@2.3.4`, running `yarn add foo@2.10.14` will cause Yarn to install `foo@2.10.14` because the existing resolution doesn't satisfy the range `2.10.14`. This behavior can lead to (sometimes) unwanted duplication, since now the lockfile contains 2 separate resolutions for the 2 `foo` descriptors, even though they have overlapping ranges, which means that the lockfile can be simplified so that both descriptors resolve to `foo@2.10.14`.\n ",examples:[["Dedupe all packages","$0 dedupe"],["Dedupe all packages using a specific strategy","$0 dedupe --strategy highest"],["Dedupe a specific package","$0 dedupe lodash"],["Dedupe all packages with the `@babel/*` scope","$0 dedupe '@babel/*'"],["Check for duplicates (can be used as a CI step)","$0 dedupe --check"]]}),(0,a.gn)([C.Command.Rest()],te.prototype,"patterns",void 0),(0,a.gn)([C.Command.String("-s,--strategy",{description:"The strategy to use when deduping dependencies"})],te.prototype,"strategy",void 0),(0,a.gn)([C.Command.Boolean("-c,--check",{description:"Exit with exit code 1 when duplicates are found, without persisting the dependency tree"})],te.prototype,"check",void 0),(0,a.gn)([C.Command.Boolean("--json",{description:"Format the output as an NDJSON stream"})],te.prototype,"json",void 0),(0,a.gn)([C.Command.Path("dedupe")],te.prototype,"execute",null);class re extends C.Command{async execute(){const{plugins:e}=await s.VK.find(this.context.cwd,this.context.plugins),t=[];for(const r of e){const{commands:e}=r[1];if(e){const A=C.Cli.from(e).definitions();t.push([r[0],A])}}const A=this.cli.definitions(),n=r(60306)["@yarnpkg/builder"].bundles.standard;for(const e of t){const t=e[1];for(const r of t)A.find(e=>{return t=e.path,A=r.path,t.split(" ").slice(1).join()===A.split(" ").slice(1).join();var t,A}).plugin={name:e[0],isDefault:n.includes(e[0])}}this.context.stdout.write(JSON.stringify({commands:A},null,2)+"\n")}}(0,a.gn)([C.Command.Path("--clipanion=definitions")],re.prototype,"execute",null);class Ae extends C.Command{async execute(){this.context.stdout.write(this.cli.usage(null))}}(0,a.gn)([C.Command.Path("help"),C.Command.Path("--help"),C.Command.Path("-h")],Ae.prototype,"execute",null);class ne extends C.Command{constructor(){super(...arguments),this.args=[]}async execute(){if(this.leadingArgument.match(/[\\/]/)&&!d.tryParseIdent(this.leadingArgument)){const e=m.y1.resolve(this.context.cwd,m.cS.toPortablePath(this.leadingArgument));return await this.cli.run(this.args,{cwd:e})}return await this.cli.run(["run",this.leadingArgument,...this.args])}}(0,a.gn)([C.Command.String()],ne.prototype,"leadingArgument",void 0),(0,a.gn)([C.Command.Proxy()],ne.prototype,"args",void 0);var oe=r(59355);class ie extends C.Command{async execute(){this.context.stdout.write((oe.o||"")+"\n")}}(0,a.gn)([C.Command.Path("-v"),C.Command.Path("--version")],ie.prototype,"execute",null);var se=r(6220);class ae extends c.BaseCommand{constructor(){super(...arguments),this.args=[]}async execute(){const e=await s.VK.find(this.context.cwd,this.context.plugins),{project:t}=await h.I.find(e,this.context.cwd);return await y.xfs.mktempPromise(async e=>{const{code:r}=await se.pipevp(this.commandName,this.args,{cwd:this.context.cwd,stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr,env:await R.makeScriptEnv({project:t,binFolder:e})});return r})}}ae.usage=C.Command.Usage({description:"execute a shell command",details:"\n This command simply executes a shell binary within the context of the root directory of the active workspace.\n\n It also makes sure to call it in a way that's compatible with the current project (for example, on PnP projects the environment will be setup in such a way that PnP will be correctly injected into the environment).\n ",examples:[["Execute a shell command","$0 exec echo Hello World"]]}),(0,a.gn)([C.Command.String()],ae.prototype,"commandName",void 0),(0,a.gn)([C.Command.Proxy()],ae.prototype,"args",void 0),(0,a.gn)([C.Command.Path("exec")],ae.prototype,"execute",null);var ce=r(36545);class ge extends c.BaseCommand{async execute(){const e=await s.VK.find(this.context.cwd,this.context.plugins),{project:t}=await h.I.find(e,this.context.cwd);if(await t.applyLightResolution(),void 0!==this.hash)return await async function(e,t,r){const{configuration:A}=t,n=t.peerRequirements.get(e);if(void 0===n)throw new Error(`No peerDependency requirements found for hash: "${e}"`);return(await p.Pk.start({configuration:A,stdout:r.stdout,includeFooter:!1},async e=>{var r,o;const i=t.storedPackages.get(n.subject);if(void 0===i)throw new Error("Assertion failed: Expected the subject package to have been registered");const s=t.storedPackages.get(n.rootRequester);if(void 0===s)throw new Error("Assertion failed: Expected the root package to have been registered");const a=null!==(r=i.dependencies.get(n.requested.identHash))&&void 0!==r?r:null,c=null!==a?t.storedResolutions.get(a.descriptorHash):null;if(void 0===c)throw new Error("Assertion failed: Expected the resolution to have been registered");const g=null!==c?t.storedPackages.get(c):null;if(void 0===g)throw new Error("Assertion failed: Expected the provided package to have been registered");const l=[...n.allRequesters.values()].map(e=>{const r=t.storedPackages.get(e);if(void 0===r)throw new Error("Assertion failed: Expected the package to be registered");const A=d.devirtualizeLocator(r),o=t.storedPackages.get(A.locatorHash);if(void 0===o)throw new Error("Assertion failed: Expected the package to be registered");const i=o.peerDependencies.get(n.requested.identHash);if(void 0===i)throw new Error("Assertion failed: Expected the peer dependency to be registered");return{pkg:r,peerDependency:i}});if(null!==g){const t=l.every(({peerDependency:e})=>ce.satisfiesWithPrereleases(g.version,e.range));e.reportInfo(u.b.UNNAMED,`${d.prettyLocator(A,i)} provides ${d.prettyLocator(A,g)} with version ${d.prettyReference(A,null!==(o=g.version)&&void 0!==o?o:"")}, which ${t?"satisfies":"doesn't satisfy"} the following requirements:`)}else e.reportInfo(u.b.UNNAMED,`${d.prettyLocator(A,i)} doesn't provide ${d.prettyIdent(A,n.requested)}, breaking the following requirements:`);e.reportSeparator();const h=B.mark(A),p=[];for(const{pkg:e,peerDependency:t}of P.sortMap(l,e=>d.stringifyLocator(e.pkg))){const r=null!==g&&ce.satisfiesWithPrereleases(g.version,t.range)?h.Check:h.Cross;p.push({stringifiedLocator:d.stringifyLocator(e),prettyLocator:d.prettyLocator(A,e),prettyRange:d.prettyRange(A,t.range),mark:r})}const C=Math.max(...p.map(({stringifiedLocator:e})=>e.length)),f=Math.max(...p.map(({prettyRange:e})=>e.length));for(const{stringifiedLocator:t,prettyLocator:r,prettyRange:A,mark:n}of P.sortMap(p,({stringifiedLocator:e})=>e))e.reportInfo(null,`${r.padEnd(C+(r.length-t.length)," ")} → ${A.padEnd(f," ")} ${n}`);p.length>1&&(e.reportSeparator(),e.reportInfo(u.b.UNNAMED,"Note: these requirements start with "+d.prettyLocator(t.configuration,s)))})).exitCode()}(this.hash,t,{stdout:this.context.stdout});return(await p.Pk.start({configuration:e,stdout:this.context.stdout,includeFooter:!1},async r=>{var A;const n=[([,e])=>d.stringifyLocator(t.storedPackages.get(e.subject)),([,e])=>d.stringifyIdent(e.requested)];for(const[o,i]of P.sortMap(t.peerRequirements,n)){const n=t.storedPackages.get(i.subject);if(void 0===n)throw new Error("Assertion failed: Expected the subject package to have been registered");const s=t.storedPackages.get(i.rootRequester);if(void 0===s)throw new Error("Assertion failed: Expected the root package to have been registered");const a=null!==(A=n.dependencies.get(i.requested.identHash))&&void 0!==A?A:null,c=B.pretty(e,o,B.Type.CODE),g=d.prettyLocator(e,n),l=d.prettyIdent(e,i.requested),u=d.prettyIdent(e,s),h=i.allRequesters.length-1,p="descendant"+(1===h?"":"s"),C=h>0?` and ${h} ${p}`:"",f=null!==a?"provides":"doesn't provide";r.reportInfo(null,`${c} → ${g} ${f} ${l} to ${u}${C}`)}})).exitCode()}}ge.schema=V.object().shape({hash:V.string().matches(/^p[0-9a-f]{5}$/)}),ge.usage=C.Command.Usage({description:"explain a set of peer requirements",details:"\n A set of peer requirements represents all peer requirements that a dependent must satisfy when providing a given peer request to a requester and its descendants.\n\n When the hash argument is specified, this command prints a detailed explanation of all requirements of the set corresponding to the hash and whether they're satisfied or not.\n\n When used without arguments, this command lists all sets of peer requirements and the corresponding hash that can be used to get detailed information about a given set.\n\n **Note:** A hash is a six-letter p-prefixed code that can be obtained from peer dependency warnings or from the list of all peer requirements (`yarn explain peer-requirements`).\n ",examples:[["Explain the corresponding set of peer requirements for a hash","$0 explain peer-requirements p1a4ed"],["List all sets of peer requirements","$0 explain peer-requirements"]]}),(0,a.gn)([C.Command.String({required:!1})],ge.prototype,"hash",void 0),(0,a.gn)([C.Command.Path("explain","peer-requirements")],ge.prototype,"execute",null);var le=r(85875);class ue extends c.BaseCommand{constructor(){super(...arguments),this.all=!1,this.recursive=!1,this.extra=[],this.cache=!1,this.dependents=!1,this.manifest=!1,this.nameOnly=!1,this.virtuals=!1,this.json=!1,this.patterns=[]}async execute(){const e=await s.VK.find(this.context.cwd,this.context.plugins),{project:t,workspace:r}=await h.I.find(e,this.context.cwd),A=await g.C.find(e);if(!r&&!this.all)throw new c.WorkspaceRequiredError(t.cwd,this.context.cwd);await t.restoreInstallState();const n=new Set(this.extra);this.cache&&n.add("cache"),this.dependents&&n.add("dependents"),this.manifest&&n.add("manifest");const o=(e,{recursive:r})=>{const A=e.anchoredLocator.locatorHash,n=new Map,o=[A];for(;o.length>0;){const e=o.shift();if(n.has(e))continue;const i=t.storedPackages.get(e);if(void 0===i)throw new Error("Assertion failed: Expected the package to be registered");if(n.set(e,i),d.isVirtualLocator(i)&&o.push(d.devirtualizeLocator(i).locatorHash),r||e===A)for(const e of i.dependencies.values()){const r=t.storedResolutions.get(e.descriptorHash);if(void 0===r)throw new Error("Assertion failed: Expected the resolution to be registered");o.push(r)}}return n.values()},i=({all:e,recursive:A})=>e&&A?t.storedPackages.values():e?(({recursive:e})=>{const r=new Map;for(const A of t.workspaces)for(const t of o(A,{recursive:e}))r.set(t.locatorHash,t);return r.values()})({recursive:A}):o(r,{recursive:A}),{selection:a,sortedLookup:l}=(({all:e,recursive:t})=>{const r=i({all:e,recursive:t}),A=this.patterns.map(e=>{const t=d.parseLocator(e),r=Z().makeRe(d.stringifyIdent(t)),A=d.isVirtualLocator(t),n=A?d.devirtualizeLocator(t):t;return e=>{const o=d.stringifyIdent(e);if(!r.test(o))return!1;if("unknown"===t.reference)return!0;const i=d.isVirtualLocator(e),s=i?d.devirtualizeLocator(e):e;return(!A||!i||t.reference===e.reference)&&n.reference===s.reference}}),n=P.sortMap([...r],e=>d.stringifyLocator(e));return{selection:n.filter(e=>0===A.length||A.some(t=>t(e))),sortedLookup:n}})({all:this.all,recursive:this.recursive});if(0===a.length)throw new C.UsageError("No package matched your request");const u=new Map;if(this.dependents)for(const e of l)for(const r of e.dependencies.values()){const A=t.storedResolutions.get(r.descriptorHash);if(void 0===A)throw new Error("Assertion failed: Expected the resolution to be registered");P.getArrayWithDefault(u,A).push(e)}const p=new Map;for(const e of l){if(!d.isVirtualLocator(e))continue;const t=d.devirtualizeLocator(e);P.getArrayWithDefault(p,t.locatorHash).push(e)}const f={},m={children:f},w=e.makeFetcher(),Q={project:t,fetcher:w,cache:A,checksums:t.storedChecksums,report:new I.$,skipIntegrityCheck:!0},D=[async(e,t,r)=>{var A,n;if(!t.has("manifest"))return;const o=await w.fetch(e,Q);let i;try{i=await E.G.find(o.prefixPath,{baseFs:o.packageFs})}finally{null===(A=o.releaseFs)||void 0===A||A.call(o)}r("Manifest",{License:B.tuple(B.Type.NO_HINT,i.license),Homepage:B.tuple(B.Type.URL,null!==(n=i.raw.homepage)&&void 0!==n?n:null)})},async(e,r,n)=>{var o;if(!r.has("cache"))return;const i=null!==(o=t.storedChecksums.get(e.locatorHash))&&void 0!==o?o:null,s=A.getLocatorPath(e,i);let a;if(null!==s)try{a=y.xfs.statSync(s)}catch(e){}const c=void 0!==a?[a.size,B.Type.SIZE]:void 0;n("Cache",{Checksum:B.tuple(B.Type.NO_HINT,i),Path:B.tuple(B.Type.PATH,s),Size:c})}];for(const r of a){const A=d.isVirtualLocator(r);if(!this.virtuals&&A)continue;const o={},i={value:[r,B.Type.LOCATOR],children:o};if(f[d.stringifyLocator(r)]=i,this.nameOnly){delete i.children;continue}const s=p.get(r.locatorHash);void 0!==s&&(o.Instances={label:"Instances",value:B.tuple(B.Type.NUMBER,s.length)}),o.Version={label:"Version",value:B.tuple(B.Type.NO_HINT,r.version)};const a=(e,t)=>{const r={};if(o[e]=r,Array.isArray(t))r.children=t.map(e=>({value:e}));else{const e={};r.children=e;for(const[r,A]of Object.entries(t))void 0!==A&&(e[r]={label:r,value:A})}};if(!A){for(const e of D)await e(r,n,a);await e.triggerHook(e=>e.fetchPackageInfo,r,n,a)}r.bin.size>0&&!A&&a("Exported Binaries",[...r.bin.keys()].map(e=>B.tuple(B.Type.PATH,e)));const c=u.get(r.locatorHash);void 0!==c&&c.length>0&&a("Dependents",c.map(e=>B.tuple(B.Type.LOCATOR,e))),r.dependencies.size>0&&!A&&a("Dependencies",[...r.dependencies.values()].map(e=>{var r;const A=t.storedResolutions.get(e.descriptorHash),n=void 0!==A&&null!==(r=t.storedPackages.get(A))&&void 0!==r?r:null;return B.tuple(B.Type.RESOLUTION,{descriptor:e,locator:n})})),r.peerDependencies.size>0&&A&&a("Peer dependencies",[...r.peerDependencies.values()].map(e=>{var A,n;const o=r.dependencies.get(e.identHash),i=void 0!==o&&null!==(A=t.storedResolutions.get(o.descriptorHash))&&void 0!==A?A:null,s=null!==i&&null!==(n=t.storedPackages.get(i))&&void 0!==n?n:null;return B.tuple(B.Type.RESOLUTION,{descriptor:e,locator:s})}))}le.emitTree(m,{configuration:e,json:this.json,stdout:this.context.stdout,separators:this.nameOnly?0:2})}}ue.usage=C.Command.Usage({description:"see information related to packages",details:"\n This command prints various information related to the specified packages, accepting glob patterns.\n\n By default, if the locator reference is missing, Yarn will default to print the information about all the matching direct dependencies of the package for the active workspace. To instead print all versions of the package that are direct dependencies of any of your workspaces, use the `-A,--all` flag. Adding the `-R,--recursive` flag will also report transitive dependencies.\n\n Some fields will be hidden by default in order to keep the output readable, but can be selectively displayed by using additional options (`--dependents`, `--manifest`, `--virtuals`, ...) described in the option descriptions.\n\n Note that this command will only print the information directly related to the selected packages - if you wish to know why the package is there in the first place, use `yarn why` which will do just that (it also provides a `-R,--recursive` flag that may be of some help).\n ",examples:[["Show information about Lodash","$0 info lodash"]]}),(0,a.gn)([C.Command.Boolean("-A,--all",{description:"Print versions of a package from the whole project"})],ue.prototype,"all",void 0),(0,a.gn)([C.Command.Boolean("-R,--recursive",{description:"Print information for all packages, including transitive dependencies"})],ue.prototype,"recursive",void 0),(0,a.gn)([C.Command.Array("-X,--extra",{description:"An array of requests of extra data provided by plugins"})],ue.prototype,"extra",void 0),(0,a.gn)([C.Command.Boolean("--cache",{description:"Print information about the cache entry of a package (path, size, checksum)"})],ue.prototype,"cache",void 0),(0,a.gn)([C.Command.Boolean("--dependents",{description:"Print all dependents for each matching package"})],ue.prototype,"dependents",void 0),(0,a.gn)([C.Command.Boolean("--manifest",{description:"Print data obtained by looking at the package archive (license, homepage, ...)"})],ue.prototype,"manifest",void 0),(0,a.gn)([C.Command.Boolean("--name-only",{description:"Only print the name for the matching packages"})],ue.prototype,"nameOnly",void 0),(0,a.gn)([C.Command.Boolean("--virtuals",{description:"Print each instance of the virtual packages"})],ue.prototype,"virtuals",void 0),(0,a.gn)([C.Command.Boolean("--json",{description:"Format the output as an NDJSON stream"})],ue.prototype,"json",void 0),(0,a.gn)([C.Command.Rest()],ue.prototype,"patterns",void 0),(0,a.gn)([C.Command.Path("info")],ue.prototype,"execute",null);var he=r(11640),pe=r(5864);class de extends c.BaseCommand{constructor(){super(...arguments),this.json=!1,this.checkCache=!1,this.skipBuilds=!1,this.silent=!1}async execute(){var e,t,r;const A=await s.VK.find(this.context.cwd,this.context.plugins);void 0!==this.inlineBuilds&&A.useWithSource("",{enableInlineBuilds:this.inlineBuilds},A.startingCwd,{overwrite:!0});const n=!!process.env.NOW_BUILDER,o=!!process.env.NETLIFY,i=!!process.env.FUNCTION_TARGET||!!process.env.GOOGLE_RUNTIME,a=async(e,{error:t})=>{const r=await p.Pk.start({configuration:A,stdout:this.context.stdout,includeFooter:!1},async r=>{t?r.reportError(u.b.DEPRECATED_CLI_SETTINGS,e):r.reportWarning(u.b.DEPRECATED_CLI_SETTINGS,e)});return r.hasErrors()?r.exitCode():null};if(void 0!==this.ignoreEngines){const e=await a("The --ignore-engines option is deprecated; engine checking isn't a core feature anymore",{error:!n});if(null!==e)return e}if(void 0!==this.registry){const e=await a("The --registry option is deprecated; prefer setting npmRegistryServer in your .yarnrc.yml file",{error:!1});if(null!==e)return e}if(void 0!==this.preferOffline){const e=await a("The --prefer-offline flag is deprecated; use the --cached flag with 'yarn add' instead",{error:!n});if(null!==e)return e}if(void 0!==this.production){const e=await a("The --production option is deprecated on 'install'; use 'yarn workspaces focus' instead",{error:!0});if(null!==e)return e}if(void 0!==this.nonInteractive){const e=await a("The --non-interactive option is deprecated",{error:!i});if(null!==e)return e}if(void 0!==this.frozenLockfile){const e=await a("The --frozen-lockfile option is deprecated; use --immutable and/or --immutable-cache instead",{error:!i&&!pe.TRAVIS});if(null!==e)return e}if(void 0!==this.cacheFolder){const e=await a("The cache-folder option has been deprecated; use rc settings instead",{error:!o});if(null!==e)return e}const l=void 0===this.immutable&&void 0===this.frozenLockfile?null!==(e=A.get("enableImmutableInstalls"))&&void 0!==e&&e:null!==(r=null!==(t=this.immutable)&&void 0!==t?t:this.frozenLockfile)&&void 0!==r&&r;if(null!==A.projectCwd){const e=await p.Pk.start({configuration:A,json:this.json,stdout:this.context.stdout,includeFooter:!1},async e=>{await async function(e,t){if(!e.projectCwd)return!1;const r=m.y1.join(e.projectCwd,e.get("lockfileFilename"));if(!await y.xfs.existsPromise(r))return!1;const A=await y.xfs.readFilePromise(r,"utf8");if(!A.includes("<<<<<<<"))return!1;if(t)throw new X.lk(u.b.AUTOMERGE_IMMUTABLE,"Cannot autofix a lockfile when running an immutable install");const[n,o]=function(e){const t=[[],[]],r=e.split(/\r?\n/g);let A=!1;for(;r.length>0;){const e=r.shift();if(void 0===e)throw new Error("Assertion failed: Some lines should remain");if(e.startsWith("<<<<<<<")){for(;r.length>0;){const e=r.shift();if(void 0===e)throw new Error("Assertion failed: Some lines should remain");if("======="===e){A=!1;break}A||e.startsWith("|||||||")?A=!0:t[0].push(e)}for(;r.length>0;){const e=r.shift();if(void 0===e)throw new Error("Assertion failed: Some lines should remain");if(e.startsWith(">>>>>>>"))break;t[1].push(e)}}else t[0].push(e),t[1].push(e)}return[t[0].join("\n"),t[1].join("\n")]}(A);let i,s;try{i=(0,he.parseSyml)(n),s=(0,he.parseSyml)(o)}catch(e){throw new X.lk(u.b.AUTOMERGE_FAILED_TO_PARSE,"The individual variants of the lockfile failed to parse")}const a={...i,...s};for(const[e,t]of Object.entries(a))"string"==typeof t&&delete a[e];return await y.xfs.changeFilePromise(r,(0,he.stringifySyml)(a),{automaticNewlines:!0}),!0}(A,l)&&(e.reportInfo(u.b.AUTOMERGE_SUCCESS,"Automatically fixed merge conflicts 👍"),e.reportSeparator())});if(e.hasErrors())return e.exitCode()}if(null!==A.projectCwd){const e=await p.Pk.start({configuration:A,json:this.json,stdout:this.context.stdout,includeFooter:!1},async e=>{var t;(null===(t=s.VK.telemetry)||void 0===t?void 0:t.isNew)&&(e.reportInfo(u.b.TELEMETRY_NOTICE,"Yarn will periodically gather anonymous telemetry: https://yarnpkg.com/advanced/telemetry"),e.reportInfo(u.b.TELEMETRY_NOTICE,`Run ${B.pretty(A,"yarn config set --home enableTelemetry 0",B.Type.CODE)} to disable`),e.reportSeparator())});if(e.hasErrors())return e.exitCode()}const{project:d,workspace:C}=await h.I.find(A,this.context.cwd),f=await g.C.find(A,{immutable:this.immutableCache,check:this.checkCache});if(!C)throw new c.WorkspaceRequiredError(d.cwd,this.context.cwd);await d.restoreInstallState({restoreResolutions:!1});return(await p.Pk.start({configuration:A,json:this.json,stdout:this.context.stdout,includeLogs:!0},async e=>{await d.install({cache:f,report:e,immutable:l,skipBuild:this.skipBuilds})})).exitCode()}}de.usage=C.Command.Usage({description:"install the project dependencies",details:"\n This command setup your project if needed. The installation is splitted in four different steps that each have their own characteristics:\n\n - **Resolution:** First the package manager will resolve your dependencies. The exact way a dependency version is privileged over another isn't standardized outside of the regular semver guarantees. If a package doesn't resolve to what you would expect, check that all dependencies are correctly declared (also check our website for more information: ).\n\n - **Fetch:** Then we download all the dependencies if needed, and make sure that they're all stored within our cache (check the value of `cacheFolder` in `yarn config` to see where are stored the cache files).\n\n - **Link:** Then we send the dependency tree information to internal plugins tasked from writing them on the disk in some form (for example by generating the .pnp.js file you might know).\n\n - **Build:** Once the dependency tree has been written on the disk, the package manager will now be free to run the build scripts for all packages that might need it, in a topological order compatible with the way they depend on one another.\n\n Note that running this command is not part of the recommended workflow. Yarn supports zero-installs, which means that as long as you store your cache and your .pnp.js file inside your repository, everything will work without requiring any install right after cloning your repository or switching branches.\n\n If the `--immutable` option is set, Yarn will abort with an error exit code if the lockfile was to be modified (other paths can be added using the `immutablePaths` configuration setting). For backward compatibility we offer an alias under the name of `--frozen-lockfile`, but it will be removed in a later release.\n\n If the `--immutable-cache` option is set, Yarn will abort with an error exit code if the cache folder was to be modified (either because files would be added, or because they'd be removed).\n\n If the `--check-cache` option is set, Yarn will always refetch the packages and will ensure that their checksum matches what's 1/ described in the lockfile 2/ inside the existing cache files (if present). This is recommended as part of your CI workflow if you're both following the Zero-Installs model and accepting PRs from third-parties, as they'd otherwise have the ability to alter the checked-in packages before submitting them.\n\n If the `--inline-builds` option is set, Yarn will verbosely print the output of the build steps of your dependencies (instead of writing them into individual files). This is likely useful mostly for debug purposes only when using Docker-like environments.\n\n If the `--skip-builds` option is set, Yarn will not run the build scripts at all. Note that this is different from setting `enableScripts` to false because the later will disable build scripts, and thus affect the content of the artifacts generated on disk, whereas the former will just disable the build step - but not the scripts themselves, which just won't run.\n ",examples:[["Install the project","$0 install"],["Validate a project when using Zero-Installs","$0 install --immutable --immutable-cache"],["Validate a project when using Zero-Installs (slightly safer if you accept external PRs)","$0 install --immutable --immutable-cache --check-cache"]]}),(0,a.gn)([C.Command.Boolean("--json",{description:"Format the output as an NDJSON stream"})],de.prototype,"json",void 0),(0,a.gn)([C.Command.Boolean("--immutable",{description:"Abort with an error exit code if the lockfile was to be modified"})],de.prototype,"immutable",void 0),(0,a.gn)([C.Command.Boolean("--immutable-cache",{description:"Abort with an error exit code if the cache folder was to be modified"})],de.prototype,"immutableCache",void 0),(0,a.gn)([C.Command.Boolean("--check-cache",{description:"Always refetch the packages and ensure that their checksums are consistent"})],de.prototype,"checkCache",void 0),(0,a.gn)([C.Command.Boolean("--production",{hidden:!0})],de.prototype,"production",void 0),(0,a.gn)([C.Command.Boolean("--non-interactive",{hidden:!0})],de.prototype,"nonInteractive",void 0),(0,a.gn)([C.Command.Boolean("--frozen-lockfile",{hidden:!0})],de.prototype,"frozenLockfile",void 0),(0,a.gn)([C.Command.Boolean("--prefer-offline",{hidden:!0})],de.prototype,"preferOffline",void 0),(0,a.gn)([C.Command.Boolean("--ignore-engines",{hidden:!0})],de.prototype,"ignoreEngines",void 0),(0,a.gn)([C.Command.String("--registry",{hidden:!0})],de.prototype,"registry",void 0),(0,a.gn)([C.Command.Boolean("--inline-builds",{description:"Verbosely print the output of the build steps of dependencies"})],de.prototype,"inlineBuilds",void 0),(0,a.gn)([C.Command.Boolean("--skip-builds",{description:"Skip the build step altogether"})],de.prototype,"skipBuilds",void 0),(0,a.gn)([C.Command.String("--cache-folder",{hidden:!0})],de.prototype,"cacheFolder",void 0),(0,a.gn)([C.Command.Boolean("--silent",{hidden:!0})],de.prototype,"silent",void 0),(0,a.gn)([C.Command.Path(),C.Command.Path("install")],de.prototype,"execute",null);class Ce extends c.BaseCommand{constructor(){super(...arguments),this.all=!1,this.private=!1,this.relative=!1}async execute(){const e=await s.VK.find(this.context.cwd,this.context.plugins),{project:t,workspace:r}=await h.I.find(e,this.context.cwd),A=await g.C.find(e);if(!r)throw new c.WorkspaceRequiredError(t.cwd,this.context.cwd);const n=m.y1.resolve(this.context.cwd,m.cS.toPortablePath(this.destination)),o=await s.VK.find(n,this.context.plugins),{project:i,workspace:a}=await h.I.find(o,n);if(!a)throw new c.WorkspaceRequiredError(i.cwd,n);const l=t.topLevelWorkspace,u=[];if(this.all){for(const e of i.workspaces)!e.manifest.name||e.manifest.private&&!this.private||u.push(e);if(0===u.length)throw new C.UsageError("No workspace found to be linked in the target project")}else{if(!a.manifest.name)throw new C.UsageError("The target workspace doesn't have a name and thus cannot be linked");if(a.manifest.private&&!this.private)throw new C.UsageError("The target workspace is marked private - use the --private flag to link it anyway");u.push(a)}for(const e of u){const r=d.stringifyIdent(e.locator),A=this.relative?m.y1.relative(t.cwd,e.cwd):e.cwd;l.manifest.resolutions.push({pattern:{descriptor:{fullName:r}},reference:"portal:"+A})}return(await p.Pk.start({configuration:e,stdout:this.context.stdout},async e=>{await t.install({cache:A,report:e})})).exitCode()}}Ce.usage=C.Command.Usage({description:"connect the local project to another one",details:"\n This command will set a new `resolutions` field in the project-level manifest and point it to the workspace at the specified location (even if part of another project).\n\n There is no `yarn unlink` command. To unlink the workspaces from the current project one must revert the changes made to the `resolutions` field.\n ",examples:[["Register a remote workspace for use in the current project","$0 link ~/ts-loader"],["Register all workspaces from a remote project for use in the current project","$0 link ~/jest --all"]]}),(0,a.gn)([C.Command.String()],Ce.prototype,"destination",void 0),(0,a.gn)([C.Command.Boolean("-A,--all",{description:"Link all workspaces belonging to the target project to the current one"})],Ce.prototype,"all",void 0),(0,a.gn)([C.Command.Boolean("-p,--private",{description:"Also link private workspaces belonging to the target project to the current one"})],Ce.prototype,"private",void 0),(0,a.gn)([C.Command.Boolean("-r,--relative",{description:"Link workspaces using relative paths instead of absolute paths"})],Ce.prototype,"relative",void 0),(0,a.gn)([C.Command.Path("link")],Ce.prototype,"execute",null);class fe extends c.BaseCommand{constructor(){super(...arguments),this.args=[]}async execute(){return this.cli.run(["exec","node",...this.args])}}fe.usage=C.Command.Usage({description:"run node with the hook already setup",details:"\n This command simply runs Node. It also makes sure to call it in a way that's compatible with the current project (for example, on PnP projects the environment will be setup in such a way that PnP will be correctly injected into the environment).\n\n The Node process will use the exact same version of Node as the one used to run Yarn itself, which might be a good way to ensure that your commands always use a consistent Node version.\n ",examples:[["Run a Node script","$0 node ./my-script.js"]]}),(0,a.gn)([C.Command.Proxy()],fe.prototype,"args",void 0),(0,a.gn)([C.Command.Path("node")],fe.prototype,"execute",null);var Ie=r(20624),Ee=r(12087),Be=r(85622),ye=r.n(Be),me=r(79669);class we extends c.BaseCommand{constructor(){super(...arguments),this.onlyIfNeeded=!1}async execute(){const e=await s.VK.find(this.context.cwd,this.context.plugins);if(e.get("yarnPath")&&this.onlyIfNeeded)return 0;let t;if("latest"===this.version||"berry"===this.version)t="https://github.com/yarnpkg/berry/raw/master/packages/yarnpkg-cli/bin/yarn.js";else if("classic"===this.version)t="https://nightly.yarnpkg.com/latest.js";else if(ce.satisfiesWithPrereleases(this.version,">=2.0.0"))t=`https://github.com/yarnpkg/berry/raw/%40yarnpkg/cli/${this.version}/packages/yarnpkg-cli/bin/yarn.js`;else{if(!ce.satisfiesWithPrereleases(this.version,"^0.x || ^1.x"))throw Q().validRange(this.version)?new C.UsageError("Support for ranges got removed - please use the exact version you want to install, or 'latest' to get the latest build available"):new C.UsageError(`Invalid version descriptor "${this.version}"`);t=`https://github.com/yarnpkg/yarn/releases/download/v${this.version}/yarn-${this.version}.js`}return(await p.Pk.start({configuration:e,stdout:this.context.stdout},async r=>{r.reportInfo(u.b.UNNAMED,"Downloading "+B.pretty(e,t,"green"));const A=await me.get(t,{configuration:e});await Qe(e,null,A,{report:r})})).exitCode()}}async function Qe(e,t,r,{report:A}){const n=e.projectCwd?e.projectCwd:e.startingCwd;null===t&&await y.xfs.mktempPromise(async e=>{const A=m.y1.join(e,"yarn.cjs");await y.xfs.writeFilePromise(A,r);const{stdout:o}=await se.execvp(process.execPath,[m.cS.fromPortablePath(A),"--version"],{cwd:n,env:{...process.env,YARN_IGNORE_PATH:"1"}});if(t=o.trim(),!Q().valid(t))throw new Error("Invalid semver version")});const o=m.y1.resolve(n,".yarn/releases"),i=m.y1.resolve(o,`yarn-${t}.cjs`),a=m.y1.relative(e.startingCwd,i),c=m.y1.relative(n,i),g=e.get("yarnPath"),l=null===g||g.startsWith(o+"/");A.reportInfo(u.b.UNNAMED,"Saving the new release in "+B.pretty(e,a,"magenta")),await y.xfs.removePromise(m.y1.dirname(i)),await y.xfs.mkdirPromise(m.y1.dirname(i),{recursive:!0}),await y.xfs.writeFilePromise(i,r),await y.xfs.chmodPromise(i,493),l&&await s.VK.updateConfiguration(n,{yarnPath:c})}we.usage=C.Command.Usage({description:"lock the Yarn version used by the project",details:"\n This command will download a specific release of Yarn directly from the Yarn GitHub repository, will store it inside your project, and will change the `yarnPath` settings from your project `.yarnrc.yml` file to point to the new file.\n\n A very good use case for this command is to enforce the version of Yarn used by the any single member of your team inside a same project - by doing this you ensure that you have control on Yarn upgrades and downgrades (including on your deployment servers), and get rid of most of the headaches related to someone using a slightly different version and getting a different behavior than you.\n ",examples:[["Download the latest release from the Yarn repository","$0 set version latest"],["Download the latest classic release from the Yarn repository","$0 set version classic"],["Download a specific Yarn 2 build","$0 set version 2.0.0-rc.30"],["Switch back to a specific Yarn 1 release","$0 set version 1.22.1"]]}),(0,a.gn)([C.Command.Boolean("--only-if-needed",{description:"Only lock the Yarn version if it isn't already locked"})],we.prototype,"onlyIfNeeded",void 0),(0,a.gn)([C.Command.String()],we.prototype,"version",void 0),(0,a.gn)([C.Command.Path("policies","set-version"),C.Command.Path("set","version")],we.prototype,"execute",null);const De=/^[0-9]+$/;function be(e){return De.test(e)?`pull/${e}/head`:e}class ve extends c.BaseCommand{constructor(){super(...arguments),this.repository="https://github.com/yarnpkg/berry.git",this.branch="master",this.plugins=[],this.noMinify=!1,this.force=!1}async execute(){const e=await s.VK.find(this.context.cwd,this.context.plugins),t=void 0!==this.installPath?m.y1.resolve(this.context.cwd,m.cS.toPortablePath(this.installPath)):m.y1.resolve(m.cS.toPortablePath((0,Ee.tmpdir)()),"yarnpkg-sources",Ie.makeHash(this.repository).slice(0,6));return(await p.Pk.start({configuration:e,stdout:this.context.stdout},async r=>{await ke(this,{configuration:e,report:r,target:t}),r.reportSeparator(),r.reportInfo(u.b.UNNAMED,"Building a fresh bundle"),r.reportSeparator(),await Se((({plugins:e,noMinify:t},r)=>[["yarn","build:cli",...(new Array).concat(...e.map(e=>["--plugin",ye().resolve(r,e)])),...t?["--no-minify"]:[],"|"]])(this,t),{configuration:e,context:this.context,target:t}),r.reportSeparator();const A=m.y1.resolve(t,"packages/yarnpkg-cli/bundles/yarn.js"),n=await y.xfs.readFilePromise(A);await Qe(e,"sources",n,{report:r})})).exitCode()}}async function Se(e,{configuration:t,context:r,target:A}){for(const[n,...o]of e){const e="|"===o[o.length-1];if(e&&o.pop(),e)await se.pipevp(n,o,{cwd:A,stdin:r.stdin,stdout:r.stdout,stderr:r.stderr,strict:!0});else{r.stdout.write(B.pretty(t," $ "+[n,...o].join(" "),"grey")+"\n");try{await se.execvp(n,o,{cwd:A,strict:!0})}catch(e){throw r.stdout.write(e.stdout||e.stack),e}}}}async function ke(e,{configuration:t,report:r,target:A}){let n=!1;if(!e.force&&y.xfs.existsSync(m.y1.join(A,".git"))){r.reportInfo(u.b.UNNAMED,"Fetching the latest commits"),r.reportSeparator();try{await Se((({branch:e})=>[["git","fetch","origin",be(e),"--force"],["git","reset","--hard","FETCH_HEAD"],["git","clean","-dfx"]])(e),{configuration:t,context:e.context,target:A}),n=!0}catch(e){r.reportSeparator(),r.reportWarning(u.b.UNNAMED,"Repository update failed; we'll try to regenerate it")}}n||(r.reportInfo(u.b.UNNAMED,"Cloning the remote repository"),r.reportSeparator(),await y.xfs.removePromise(A),await y.xfs.mkdirPromise(A,{recursive:!0}),await Se((({repository:e,branch:t},r)=>[["git","init",m.cS.fromPortablePath(r)],["git","remote","add","origin",e],["git","fetch","origin",be(t)],["git","reset","--hard","FETCH_HEAD"]])(e,A),{configuration:t,context:e.context,target:A}))}ve.usage=C.Command.Usage({description:"build Yarn from master",details:"\n This command will clone the Yarn repository into a temporary folder, then build it. The resulting bundle will then be copied into the local project.\n ",examples:[["Build Yarn from master","$0 set version from sources"]]}),(0,a.gn)([C.Command.String("--path",{description:"The path where the repository should be cloned to"})],ve.prototype,"installPath",void 0),(0,a.gn)([C.Command.String("--repository",{description:"The repository that should be cloned"})],ve.prototype,"repository",void 0),(0,a.gn)([C.Command.String("--branch",{description:"The branch of the repository that should be cloned"})],ve.prototype,"branch",void 0),(0,a.gn)([C.Command.Array("--plugin",{description:"An array of additional plugins that should be included in the bundle"})],ve.prototype,"plugins",void 0),(0,a.gn)([C.Command.Boolean("--no-minify",{description:"Build a bundle for development (debugging) - non-minified and non-mangled"})],ve.prototype,"noMinify",void 0),(0,a.gn)([C.Command.Boolean("-f,--force",{description:"Always clone the repository instead of trying to fetch the latest commits"})],ve.prototype,"force",void 0),(0,a.gn)([C.Command.Path("set","version","from","sources")],ve.prototype,"execute",null);var Ne=r(78835);const Fe=require("vm");async function Ke(e){const t=await me.get("https://raw.githubusercontent.com/yarnpkg/berry/master/plugins.yml",{configuration:e});return(0,he.parseSyml)(t.toString())}class Me extends c.BaseCommand{constructor(){super(...arguments),this.json=!1}async execute(){const e=await s.VK.find(this.context.cwd,this.context.plugins);return(await p.Pk.start({configuration:e,json:this.json,stdout:this.context.stdout},async t=>{const r=await Ke(e);for(const[e,{experimental:A,...n}]of Object.entries(r)){let r=e;A&&(r+=" [experimental]"),t.reportJson({name:e,experimental:A,...n}),t.reportInfo(null,r)}})).exitCode()}}Me.usage=C.Command.Usage({category:"Plugin-related commands",description:"list the available official plugins",details:"\n This command prints the plugins available directly from the Yarn repository. Only those plugins can be referenced by name in `yarn plugin import`.\n ",examples:[["List the official plugins","$0 plugin list"]]}),(0,a.gn)([C.Command.Boolean("--json",{description:"Format the output as an NDJSON stream"})],Me.prototype,"json",void 0),(0,a.gn)([C.Command.Path("plugin","list")],Me.prototype,"execute",null);class Re extends c.BaseCommand{async execute(){const e=await s.VK.find(this.context.cwd,this.context.plugins);return(await p.Pk.start({configuration:e,stdout:this.context.stdout},async t=>{const{project:r}=await h.I.find(e,this.context.cwd);let A,n;if(this.name.match(/^\.{0,2}[\\/]/)||m.cS.isAbsolute(this.name)){const o=m.y1.resolve(this.context.cwd,m.cS.toPortablePath(this.name));t.reportInfo(u.b.UNNAMED,"Reading "+B.pretty(e,o,B.Type.PATH)),A=m.y1.relative(r.cwd,o),n=await y.xfs.readFilePromise(o)}else{let r;if(this.name.match(/^https?:/)){try{new Ne.URL(this.name)}catch(e){throw new X.lk(u.b.INVALID_PLUGIN_REFERENCE,`Plugin specifier "${this.name}" is neither a plugin name nor a valid url`)}A=this.name,r=this.name}else{const t=d.parseIdent(this.name.replace(/^((@yarnpkg\/)?plugin-)?/,"@yarnpkg/plugin-")),n=d.stringifyIdent(t),o=await Ke(e);if(!Object.prototype.hasOwnProperty.call(o,n))throw new X.lk(u.b.PLUGIN_NAME_NOT_FOUND,`Couldn't find a plugin named "${n}" on the remote registry. Note that only the plugins referenced on our website (https://github.com/yarnpkg/berry/blob/master/plugins.yml) can be referenced by their name; any other plugin will have to be referenced through its public url (for example https://github.com/yarnpkg/berry/raw/master/packages/plugin-typescript/bin/%40yarnpkg/plugin-typescript.js).`);A=n,r=o[n].url}t.reportInfo(u.b.UNNAMED,"Downloading "+B.pretty(e,r,"green")),n=await me.get(r,{configuration:e})}await xe(A,n,{project:r,report:t})})).exitCode()}}async function xe(e,t,{project:r,report:A}){const{configuration:n}=r,o={},i={exports:o};(0,Fe.runInNewContext)(t.toString(),{module:i,exports:o});const a=i.exports.name,c=`.yarn/plugins/${a}.cjs`,g=m.y1.resolve(r.cwd,c);A.reportInfo(u.b.UNNAMED,"Saving the new plugin in "+B.pretty(n,c,"magenta")),await y.xfs.mkdirPromise(m.y1.dirname(g),{recursive:!0}),await y.xfs.writeFilePromise(g,t);const l={path:c,spec:e};await s.VK.updateConfiguration(r.cwd,e=>{const t=[];let A=!1;for(const n of e.plugins||[]){const e="string"!=typeof n?n.path:n,o=m.y1.resolve(r.cwd,m.cS.toPortablePath(e)),{name:i}=P.dynamicRequire(m.cS.fromPortablePath(o));i!==a?t.push(n):(t.push(l),A=!0)}return A||t.push(l),{...e,plugins:t}})}Re.usage=C.Command.Usage({category:"Plugin-related commands",description:"download a plugin",details:"\n This command downloads the specified plugin from its remote location and updates the configuration to reference it in further CLI invocations.\n\n Three types of plugin references are accepted:\n\n - If the plugin is stored within the Yarn repository, it can be referenced by name.\n - Third-party plugins can be referenced directly through their public urls.\n - Local plugins can be referenced by their path on the disk.\n\n Plugins cannot be downloaded from the npm registry, and aren't allowed to have dependencies (they need to be bundled into a single file, possibly thanks to the `@yarnpkg/builder` package).\n ",examples:[['Download and activate the "@yarnpkg/plugin-exec" plugin',"$0 plugin import @yarnpkg/plugin-exec"],['Download and activate the "@yarnpkg/plugin-exec" plugin (shorthand)',"$0 plugin import exec"],["Download and activate a community plugin","$0 plugin import https://example.org/path/to/plugin.js"],["Activate a local plugin","$0 plugin import ./path/to/plugin.js"]]}),(0,a.gn)([C.Command.String()],Re.prototype,"name",void 0),(0,a.gn)([C.Command.Path("plugin","import")],Re.prototype,"execute",null);class Le extends c.BaseCommand{constructor(){super(...arguments),this.repository="https://github.com/yarnpkg/berry.git",this.branch="master",this.noMinify=!1,this.force=!1}async execute(){const e=await s.VK.find(this.context.cwd,this.context.plugins),t=void 0!==this.installPath?m.y1.resolve(this.context.cwd,m.cS.toPortablePath(this.installPath)):m.y1.resolve(m.cS.toPortablePath((0,Ee.tmpdir)()),"yarnpkg-sources",Ie.makeHash(this.repository).slice(0,6));return(await p.Pk.start({configuration:e,stdout:this.context.stdout},async r=>{const{project:A}=await h.I.find(e,this.context.cwd),n=d.parseIdent(this.name.replace(/^((@yarnpkg\/)?plugin-)?/,"@yarnpkg/plugin-")),o=d.stringifyIdent(n),i=await Ke(e);if(!Object.prototype.hasOwnProperty.call(i,o))throw new X.lk(u.b.PLUGIN_NAME_NOT_FOUND,`Couldn't find a plugin named "${o}" on the remote registry. Note that only the plugins referenced on our website (https://github.com/yarnpkg/berry/blob/master/plugins.yml) can be built and imported from sources.`);const s=o,a=s.replace(/@yarnpkg\//,"");await ke(this,{configuration:e,report:r,target:t}),r.reportSeparator(),r.reportInfo(u.b.UNNAMED,"Building a fresh "+a),r.reportSeparator(),await Se((({pluginName:e,noMinify:t},r)=>[["yarn","build:"+e,...t?["--no-minify"]:[],"|"]])({pluginName:a,noMinify:this.noMinify}),{configuration:e,context:this.context,target:t}),r.reportSeparator();const c=m.y1.resolve(t,`packages/${a}/bundles/${s}.js`),g=await y.xfs.readFilePromise(c);await xe(s,g,{project:A,report:r})})).exitCode()}}Le.usage=C.Command.Usage({category:"Plugin-related commands",description:"build a plugin from sources",details:"\n This command clones the Yarn repository into a temporary folder, builds the specified contrib plugin and updates the configuration to reference it in further CLI invocations.\n\n The plugins can be referenced by their short name if sourced from the official Yarn repository.\n ",examples:[['Build and activate the "@yarnpkg/plugin-exec" plugin',"$0 plugin import from sources @yarnpkg/plugin-exec"],['Build and activate the "@yarnpkg/plugin-exec" plugin (shorthand)',"$0 plugin import from sources exec"]]}),(0,a.gn)([C.Command.String()],Le.prototype,"name",void 0),(0,a.gn)([C.Command.String("--path",{description:"The path where the repository should be cloned to"})],Le.prototype,"installPath",void 0),(0,a.gn)([C.Command.String("--repository",{description:"The repository that should be cloned"})],Le.prototype,"repository",void 0),(0,a.gn)([C.Command.String("--branch",{description:"The branch of the repository that should be cloned"})],Le.prototype,"branch",void 0),(0,a.gn)([C.Command.Boolean("--no-minify",{description:"Build a plugin for development (debugging) - non-minified and non-mangled"})],Le.prototype,"noMinify",void 0),(0,a.gn)([C.Command.Boolean("-f,--force",{description:"Always clone the repository instead of trying to fetch the latest commits"})],Le.prototype,"force",void 0),(0,a.gn)([C.Command.Path("plugin","import","from","sources")],Le.prototype,"execute",null);class Pe extends c.BaseCommand{async execute(){const e=await s.VK.find(this.context.cwd,this.context.plugins),{project:t}=await h.I.find(e,this.context.cwd);return(await p.Pk.start({configuration:e,stdout:this.context.stdout},async r=>{const A=this.name,n=d.parseIdent(A);if(!e.plugins.has(A))throw new C.UsageError(d.prettyIdent(e,n)+" isn't referenced by the current configuration");const o=`.yarn/plugins/${A}.cjs`,i=m.y1.resolve(t.cwd,o);y.xfs.existsSync(i)&&(r.reportInfo(u.b.UNNAMED,`Removing ${B.pretty(e,o,B.Type.PATH)}...`),await y.xfs.removePromise(i)),r.reportInfo(u.b.UNNAMED,"Updating the configuration..."),await s.VK.updateConfiguration(t.cwd,e=>{if(!Array.isArray(e.plugins))return e;const t=e.plugins.filter(e=>e.path!==o);return e.plugins.length===t.length?e:{...e,plugins:t}})})).exitCode()}}Pe.usage=C.Command.Usage({category:"Plugin-related commands",description:"remove a plugin",details:"\n This command deletes the specified plugin from the .yarn/plugins folder and removes it from the configuration.\n\n **Note:** The plugins have to be referenced by their name property, which can be obtained using the `yarn plugin runtime` command. Shorthands are not allowed.\n ",examples:[["Remove a plugin imported from the Yarn repository","$0 plugin remove @yarnpkg/plugin-typescript"],["Remove a plugin imported from a local file","$0 plugin remove my-local-plugin"]]}),(0,a.gn)([C.Command.String()],Pe.prototype,"name",void 0),(0,a.gn)([C.Command.Path("plugin","remove")],Pe.prototype,"execute",null);class Oe extends c.BaseCommand{constructor(){super(...arguments),this.json=!1}async execute(){const e=await s.VK.find(this.context.cwd,this.context.plugins);return(await p.Pk.start({configuration:e,json:this.json,stdout:this.context.stdout},async t=>{for(const r of e.plugins.keys()){const e=this.context.plugins.plugins.has(r);let A=r;e&&(A+=" [builtin]"),t.reportJson({name:r,builtin:e}),t.reportInfo(null,""+A)}})).exitCode()}}Oe.usage=C.Command.Usage({category:"Plugin-related commands",description:"list the active plugins",details:"\n This command prints the currently active plugins. Will be displayed both builtin plugins and external plugins.\n ",examples:[["List the currently active plugins","$0 plugin runtime"]]}),(0,a.gn)([C.Command.Boolean("--json",{description:"Format the output as an NDJSON stream"})],Oe.prototype,"json",void 0),(0,a.gn)([C.Command.Path("plugin","runtime")],Oe.prototype,"execute",null);class Ue extends c.BaseCommand{constructor(){super(...arguments),this.idents=[]}async execute(){const e=await s.VK.find(this.context.cwd,this.context.plugins),{project:t,workspace:r}=await h.I.find(e,this.context.cwd),A=await g.C.find(e);if(!r)throw new c.WorkspaceRequiredError(t.cwd,this.context.cwd);const n=new Set;for(const e of this.idents)n.add(d.parseIdent(e).identHash);await t.resolveEverything({cache:A,report:new I.$});const o=e.get("bstatePath"),i=y.xfs.existsSync(o)?(0,he.parseSyml)(await y.xfs.readFilePromise(o,"utf8")):{},a=new Map;for(const e of t.storedPackages.values()){if(!Object.prototype.hasOwnProperty.call(i,e.locatorHash))continue;if(0===n.size||n.has(e.identHash))continue;const t=i[e.locatorHash];a.set(e.locatorHash,t)}if(a.size>0){const r=e.get("bstatePath"),A=h.I.generateBuildStateFile(a,t.storedPackages);await y.xfs.mkdirPromise(m.y1.dirname(r),{recursive:!0}),await y.xfs.changeFilePromise(r,A,{automaticNewlines:!0})}else await y.xfs.removePromise(o);return(await p.Pk.start({configuration:e,stdout:this.context.stdout,includeLogs:!this.context.quiet},async e=>{await t.install({cache:A,report:e})})).exitCode()}}Ue.usage=C.Command.Usage({description:"rebuild the project's native packages",details:"\n This command will automatically cause Yarn to forget about previous compilations of the given packages and to run them again.\n\n Note that while Yarn forgets the compilation, the previous artifacts aren't erased from the filesystem and may affect the next builds (in good or bad). To avoid this, you may remove the .yarn/unplugged folder, or any other relevant location where packages might have been stored (Yarn may offer a way to do that automatically in the future).\n\n By default all packages will be rebuilt, but you can filter the list by specifying the names of the packages you want to clear from memory.\n ",examples:[["Rebuild all packages","$0 rebuild"],["Rebuild fsevents only","$0 rebuild fsevents"]]}),(0,a.gn)([C.Command.Rest()],Ue.prototype,"idents",void 0),(0,a.gn)([C.Command.Path("rebuild")],Ue.prototype,"execute",null);class Te extends c.BaseCommand{constructor(){super(...arguments),this.all=!1,this.patterns=[]}async execute(){const e=await s.VK.find(this.context.cwd,this.context.plugins),{project:t,workspace:r}=await h.I.find(e,this.context.cwd),A=await g.C.find(e);if(!r)throw new c.WorkspaceRequiredError(t.cwd,this.context.cwd);await t.restoreInstallState({restoreResolutions:!1});const o=this.all?t.workspaces:[r],i=[n.REGULAR,n.DEVELOPMENT,n.PEER],a=[];let l=!1;const u=[];for(const e of this.patterns){let t=!1;const r=d.parseIdent(e);for(const A of o){const n=[...A.manifest.peerDependenciesMeta.keys()];for(const r of Z()(n,e))A.manifest.peerDependenciesMeta.delete(r),l=!0,t=!0;for(const e of i){const n=A.manifest.getForScope(e),o=[...n.values()].map(e=>d.stringifyIdent(e));for(const i of Z()(o,d.stringifyIdent(r))){const{identHash:r}=d.parseIdent(i),o=n.get(r);if(void 0===o)throw new Error("Assertion failed: Expected the descriptor to be registered");A.manifest[e].delete(r),u.push([A,e,o]),l=!0,t=!0}}}t||a.push(e)}const f=a.length>1?"Patterns":"Pattern",I=a.length>1?"don't":"doesn't",E=this.all?"any":"this";if(a.length>0)throw new C.UsageError(`${f} ${B.prettyList(e,a,s.a5.CODE)} ${I} match any packages referenced by ${E} workspace`);if(l){await e.triggerMultipleHooks(e=>e.afterWorkspaceDependencyRemoval,u);return(await p.Pk.start({configuration:e,stdout:this.context.stdout},async e=>{await t.install({cache:A,report:e})})).exitCode()}return 0}}Te.usage=C.Command.Usage({description:"remove dependencies from the project",details:"\n This command will remove the packages matching the specified patterns from the current workspace.\n\n This command accepts glob patterns as arguments (if valid Idents and supported by [micromatch](https://github.com/micromatch/micromatch)). Make sure to escape the patterns, to prevent your own shell from trying to expand them.\n ",examples:[["Remove a dependency from the current project","$0 remove lodash"],["Remove a dependency from all workspaces at once","$0 remove lodash --all"],["Remove all dependencies starting with `eslint-`","$0 remove 'eslint-*'"],["Remove all dependencies with the `@babel` scope","$0 remove '@babel/*'"],["Remove all dependencies matching `react-dom` or `react-helmet`","$0 remove 'react-{dom,helmet}'"]]}),(0,a.gn)([C.Command.Boolean("-A,--all",{description:"Apply the operation to all workspaces from the current project"})],Te.prototype,"all",void 0),(0,a.gn)([C.Command.Rest()],Te.prototype,"patterns",void 0),(0,a.gn)([C.Command.Path("remove")],Te.prototype,"execute",null);class je extends c.BaseCommand{async execute(){const e=await s.VK.find(this.context.cwd,this.context.plugins),{project:t,workspace:r}=await h.I.find(e,this.context.cwd);if(!r)throw new c.WorkspaceRequiredError(t.cwd,this.context.cwd);return(await p.Pk.start({configuration:e,stdout:this.context.stdout},async t=>{const A=r.manifest.scripts,n=P.sortMap(A.keys(),e=>e),o={breakLength:1/0,colors:e.get("enableColors"),maxArrayLength:2},i=n.reduce((e,t)=>Math.max(e,t.length),0);for(const[e,r]of A.entries())t.reportInfo(null,`${e.padEnd(i," ")} ${(0,T.inspect)(r,o)}`)})).exitCode()}}(0,a.gn)([C.Command.Path("run")],je.prototype,"execute",null);class Ye extends c.BaseCommand{constructor(){super(...arguments),this.inspect=!1,this.inspectBrk=!1,this.topLevel=!1,this.binariesOnly=!1,this.args=[]}async execute(){const e=await s.VK.find(this.context.cwd,this.context.plugins),{project:t,workspace:r,locator:A}=await h.I.find(e,this.context.cwd);await t.restoreInstallState();const n=this.topLevel?t.topLevelWorkspace.anchoredLocator:A;if(!this.binariesOnly&&await R.hasPackageScript(n,this.scriptName,{project:t}))return await R.executePackageScript(n,this.scriptName,this.args,{project:t,stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr});if((await R.getPackageAccessibleBinaries(n,{project:t})).get(this.scriptName)){const e=[];return this.inspect&&("string"==typeof this.inspect?e.push("--inspect="+this.inspect):e.push("--inspect")),this.inspectBrk&&("string"==typeof this.inspectBrk?e.push("--inspect-brk="+this.inspectBrk):e.push("--inspect-brk")),await R.executePackageAccessibleBinary(n,this.scriptName,this.args,{cwd:this.context.cwd,project:t,stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr,nodeArgs:e})}if(!this.topLevel&&!this.binariesOnly&&r&&this.scriptName.includes(":")){const e=(await Promise.all(t.workspaces.map(async e=>e.manifest.scripts.has(this.scriptName)?e:null))).filter(e=>null!==e);if(1===e.length)return await R.executeWorkspaceScript(e[0],this.scriptName,this.args,{stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr})}if(this.topLevel)throw"node-gyp"===this.scriptName?new C.UsageError(`Couldn't find a script name "${this.scriptName}" in the top-level (used by ${d.prettyLocator(e,A)}). This typically happens because some package depends on "node-gyp" to build itself, but didn't list it in their dependencies. To fix that, please run "yarn add node-gyp" into your top-level workspace. You also can open an issue on the repository of the specified package to suggest them to use an optional peer dependency.`):new C.UsageError(`Couldn't find a script name "${this.scriptName}" in the top-level (used by ${d.prettyLocator(e,A)}).`);{if("global"===this.scriptName)throw new C.UsageError("The 'yarn global' commands have been removed in 2.x - consider using 'yarn dlx' or a third-party plugin instead");const e=[this.scriptName].concat(this.args);for(const[t,r]of c.pluginCommands)for(const A of r)if(e.length>=A.length&&JSON.stringify(e.slice(0,A.length))===JSON.stringify(A))throw new C.UsageError(`Couldn't find a script named "${this.scriptName}", but a matching command can be found in the ${t} plugin. You can install it with "yarn plugin import ${t}".`);throw new C.UsageError(`Couldn't find a script named "${this.scriptName}".`)}}}Ye.usage=C.Command.Usage({description:"run a script defined in the package.json",details:"\n This command will run a tool. The exact tool that will be executed will depend on the current state of your workspace:\n\n - If the `scripts` field from your local package.json contains a matching script name, its definition will get executed.\n\n - Otherwise, if one of the local workspace's dependencies exposes a binary with a matching name, this binary will get executed.\n\n - Otherwise, if the specified name contains a colon character and if one of the workspaces in the project contains exactly one script with a matching name, then this script will get executed.\n\n Whatever happens, the cwd of the spawned process will be the workspace that declares the script (which makes it possible to call commands cross-workspaces using the third syntax).\n ",examples:[["Run the tests from the local workspace","$0 run test"],['Same thing, but without the "run" keyword',"$0 test"],["Inspect Webpack while running","$0 run --inspect-brk webpack"]]}),(0,a.gn)([C.Command.String("--inspect",{tolerateBoolean:!0,description:"Forwarded to the underlying Node process when executing a binary"})],Ye.prototype,"inspect",void 0),(0,a.gn)([C.Command.String("--inspect-brk",{tolerateBoolean:!0,description:"Forwarded to the underlying Node process when executing a binary"})],Ye.prototype,"inspectBrk",void 0),(0,a.gn)([C.Command.Boolean("-T,--top-level",{hidden:!0})],Ye.prototype,"topLevel",void 0),(0,a.gn)([C.Command.Boolean("-B,--binaries-only",{hidden:!0})],Ye.prototype,"binariesOnly",void 0),(0,a.gn)([C.Command.Boolean("--silent",{hidden:!0})],Ye.prototype,"silent",void 0),(0,a.gn)([C.Command.String()],Ye.prototype,"scriptName",void 0),(0,a.gn)([C.Command.Proxy()],Ye.prototype,"args",void 0),(0,a.gn)([C.Command.Path("run")],Ye.prototype,"execute",null);class Ge extends c.BaseCommand{constructor(){super(...arguments),this.save=!1}async execute(){const e=await s.VK.find(this.context.cwd,this.context.plugins),{project:t,workspace:r}=await h.I.find(e,this.context.cwd),A=await g.C.find(e);if(!r)throw new c.WorkspaceRequiredError(t.cwd,this.context.cwd);const n=d.parseDescriptor(this.descriptor,!0),o=d.makeDescriptor(n,this.resolution);t.storedDescriptors.set(n.descriptorHash,n),t.storedDescriptors.set(o.descriptorHash,o),t.resolutionAliases.set(n.descriptorHash,o.descriptorHash);return(await p.Pk.start({configuration:e,stdout:this.context.stdout},async e=>{await t.install({cache:A,report:e})})).exitCode()}}Ge.usage=C.Command.Usage({description:"enforce a package resolution",details:'\n This command updates the resolution table so that `descriptor` is resolved by `resolution`.\n\n Note that by default this command only affect the current resolution table - meaning that this "manual override" will disappear if you remove the lockfile, or if the package disappear from the table. If you wish to make the enforced resolution persist whatever happens, add the `-s,--save` flag which will also edit the `resolutions` field from your top-level manifest.\n\n Note that no attempt is made at validating that `resolution` is a valid resolution entry for `descriptor`.\n ',examples:[["Force all instances of lodash@npm:^1.2.3 to resolve to 1.5.0","$0 set resolution lodash@npm:^1.2.3 1.5.0"]]}),(0,a.gn)([C.Command.String()],Ge.prototype,"descriptor",void 0),(0,a.gn)([C.Command.String()],Ge.prototype,"resolution",void 0),(0,a.gn)([C.Command.Boolean("-s,--save",{description:"Persist the resolution inside the top-level manifest"})],Ge.prototype,"save",void 0),(0,a.gn)([C.Command.Path("set","resolution")],Ge.prototype,"execute",null);class He extends c.BaseCommand{constructor(){super(...arguments),this.patterns=[],this.interactive=null,this.exact=!1,this.tilde=!1,this.caret=!1}async execute(){var e;const t=await s.VK.find(this.context.cwd,this.context.plugins),{project:r,workspace:A}=await h.I.find(t,this.context.cwd),o=await g.C.find(t);if(!A)throw new c.WorkspaceRequiredError(r.cwd,this.context.cwd);const a=null!==(e=this.interactive)&&void 0!==e?e:t.get("preferInteractive"),I=D(this,r),E=a?[i.KEEP,i.REUSE,i.PROJECT,i.LATEST]:[i.PROJECT,i.LATEST],y=[],m=[];for(const e of this.patterns){let t=!1;const A=d.parseDescriptor(e);for(const e of r.workspaces)for(const i of[n.REGULAR,n.DEVELOPMENT]){const n=[...e.manifest.getForScope(i).values()].map(e=>d.stringifyIdent(e));for(const s of Z()(n,d.stringifyIdent(A))){const n=d.parseIdent(s),a=e.manifest[i].get(n.identHash);if(void 0===a)throw new Error("Assertion failed: Expected the descriptor to be registered");const c=d.makeDescriptor(n,A.range);y.push(Promise.resolve().then(async()=>[e,i,a,await F(c,{project:r,workspace:e,cache:o,target:i,modifier:I,strategies:E})])),t=!0}}t||m.push(e)}if(m.length>1)throw new C.UsageError(`Patterns ${B.prettyList(t,m,s.a5.CODE)} don't match any packages referenced by any workspace`);if(m.length>0)throw new C.UsageError(`Pattern ${B.prettyList(t,m,s.a5.CODE)} doesn't match any packages referenced by any workspace`);const w=await Promise.all(y),Q=await l.h.start({configuration:t,stdout:this.context.stdout,suggestInstall:!1},async e=>{for(const[,,A,{suggestions:n,rejections:o}]of w){const i=n.filter(e=>null!==e.descriptor);if(0===i.length){const[n]=o;if(void 0===n)throw new Error("Assertion failed: Expected an error to have been set");const i=this.cli.error(n);r.configuration.get("enableNetwork")?e.reportError(u.b.CANT_SUGGEST_RESOLUTIONS,`${d.prettyDescriptor(t,A)} can't be resolved to a satisfying range\n\n${i}`):e.reportError(u.b.CANT_SUGGEST_RESOLUTIONS,`${d.prettyDescriptor(t,A)} can't be resolved to a satisfying range (note: network resolution has been disabled)\n\n${i}`)}else i.length>1&&!a&&e.reportError(u.b.CANT_SUGGEST_RESOLUTIONS,d.prettyDescriptor(t,A)+" has multiple possible upgrade strategies; use -i to disambiguate manually")}});if(Q.hasErrors())return Q.exitCode();let b=!1;const v=[];for(const[e,A,,{suggestions:n}]of w){let o;const i=n.filter(e=>null!==e.descriptor),s=i[0].descriptor,a=i.every(e=>d.areDescriptorsEqual(e.descriptor,s));1===i.length||a?o=s:(b=!0,({answer:o}=await(0,f.prompt)({type:"select",name:"answer",message:`Which range to you want to use in ${d.prettyWorkspace(t,e)} ❯ ${A}?`,choices:n.map(({descriptor:e,name:t,reason:r})=>e?{name:t,hint:r,descriptor:e}:{name:t,hint:r,disabled:!0}),onCancel:()=>process.exit(130),result(e){return this.find(e,"descriptor")},stdin:this.context.stdin,stdout:this.context.stdout})));const c=e.manifest[A].get(o.identHash);if(void 0===c)throw new Error("Assertion failed: This descriptor should have a matching entry");if(c.descriptorHash!==o.descriptorHash)e.manifest[A].set(o.identHash,o),v.push([e,A,c,o]);else{const A=t.makeResolver(),n={project:r,resolver:A},o=A.bindDescriptor(c,e.anchoredLocator,n);r.forgetResolution(o)}}await t.triggerMultipleHooks(e=>e.afterWorkspaceDependencyReplacement,v),b&&this.context.stdout.write("\n");return(await p.Pk.start({configuration:t,stdout:this.context.stdout},async e=>{await r.install({cache:o,report:e})})).exitCode()}}He.usage=C.Command.Usage({description:"upgrade dependencies across the project",details:"\n This command upgrades the packages matching the list of specified patterns to their latest available version across the whole project (regardless of whether they're part of `dependencies` or `devDependencies` - `peerDependencies` won't be affected). This is a project-wide command: all workspaces will be upgraded in the process.\n\n If `-i,--interactive` is set (or if the `preferInteractive` settings is toggled on) the command will offer various choices, depending on the detected upgrade paths. Some upgrades require this flag in order to resolve ambiguities.\n\n The, `-C,--caret`, `-E,--exact` and `-T,--tilde` options have the same meaning as in the `add` command (they change the modifier used when the range is missing or a tag, and are ignored when the range is explicitly set).\n\n Generally you can see `yarn up` as a counterpart to what was `yarn upgrade --latest` in Yarn 1 (ie it ignores the ranges previously listed in your manifests), but unlike `yarn upgrade` which only upgraded dependencies in the current workspace, `yarn up` will upgrade all workspaces at the same time.\n\n This command accepts glob patterns as arguments (if valid Descriptors and supported by [micromatch](https://github.com/micromatch/micromatch)). Make sure to escape the patterns, to prevent your own shell from trying to expand them.\n\n **Note:** The ranges have to be static, only the package scopes and names can contain glob patterns.\n ",examples:[["Upgrade all instances of lodash to the latest release","$0 up lodash"],["Upgrade all instances of lodash to the latest release, but ask confirmation for each","$0 up lodash -i"],["Upgrade all instances of lodash to 1.2.3","$0 up lodash@1.2.3"],["Upgrade all instances of packages with the `@babel` scope to the latest release","$0 up '@babel/*'"],["Upgrade all instances of packages containing the word `jest` to the latest release","$0 up '*jest*'"],["Upgrade all instances of packages with the `@babel` scope to 7.0.0","$0 up '@babel/*@7.0.0'"]]}),(0,a.gn)([C.Command.Rest()],He.prototype,"patterns",void 0),(0,a.gn)([C.Command.Boolean("-i,--interactive",{description:"Offer various choices, depending on the detected upgrade paths"})],He.prototype,"interactive",void 0),(0,a.gn)([C.Command.Boolean("-E,--exact",{description:"Don't use any semver modifier on the resolved range"})],He.prototype,"exact",void 0),(0,a.gn)([C.Command.Boolean("-T,--tilde",{description:"Use the `~` semver modifier on the resolved range"})],He.prototype,"tilde",void 0),(0,a.gn)([C.Command.Boolean("-C,--caret",{description:"Use the `^` semver modifier on the resolved range"})],He.prototype,"caret",void 0),(0,a.gn)([C.Command.Path("up")],He.prototype,"execute",null);class Je extends c.BaseCommand{constructor(){super(...arguments),this.recursive=!1,this.json=!1,this.peers=!1}async execute(){const e=await s.VK.find(this.context.cwd,this.context.plugins),{project:t,workspace:r}=await h.I.find(e,this.context.cwd);if(!r)throw new c.WorkspaceRequiredError(t.cwd,this.context.cwd);await t.restoreInstallState();const A=d.parseIdent(this.package).identHash,n=this.recursive?function(e,t,{configuration:r,peers:A}){const n=P.sortMap(e.workspaces,e=>d.stringifyLocator(e.anchoredLocator)),o=new Set,i=new Set,s=r=>{if(o.has(r.locatorHash))return i.has(r.locatorHash);if(o.add(r.locatorHash),r.identHash===t)return i.add(r.locatorHash),!0;let n=!1;r.identHash===t&&(n=!0);for(const t of r.dependencies.values()){if(!A&&r.peerDependencies.has(t.identHash))continue;const o=e.storedResolutions.get(t.descriptorHash);if(!o)throw new Error("Assertion failed: The resolution should have been registered");const i=e.storedPackages.get(o);if(!i)throw new Error("Assertion failed: The package should have been registered");s(i)&&(n=!0)}return n&&i.add(r.locatorHash),n};for(const t of n){const r=e.storedPackages.get(t.anchoredLocator.locatorHash);if(!r)throw new Error("Assertion failed: The package should have been registered");s(r)}const a=new Set,c={},g={children:c},l=(t,r,n)=>{if(!i.has(t.locatorHash))return;const o={},s={value:null!==n?B.tuple(B.Type.DEPENDENT,{locator:t,descriptor:n}):B.tuple(B.Type.LOCATOR,t),children:o};if(r[d.stringifyLocator(t)]=s,!a.has(t.locatorHash)&&(a.add(t.locatorHash),null===n||!e.tryWorkspaceByLocator(t)))for(const r of t.dependencies.values()){if(!A&&t.peerDependencies.has(r.identHash))continue;const n=e.storedResolutions.get(r.descriptorHash);if(!n)throw new Error("Assertion failed: The resolution should have been registered");const i=e.storedPackages.get(n);if(!i)throw new Error("Assertion failed: The package should have been registered");l(i,o,r)}};for(const t of n){const r=e.storedPackages.get(t.anchoredLocator.locatorHash);if(!r)throw new Error("Assertion failed: The package should have been registered");l(r,c,null)}return g}(t,A,{configuration:e,peers:this.peers}):function(e,t,{configuration:r,peers:A}){const n=P.sortMap(e.storedPackages.values(),e=>d.stringifyLocator(e)),o={},i={children:o};for(const r of n){const n={},i=null;for(const s of r.dependencies.values()){if(!A&&r.peerDependencies.has(s.identHash))continue;const a=e.storedResolutions.get(s.descriptorHash);if(!a)throw new Error("Assertion failed: The resolution should have been registered");const c=e.storedPackages.get(a);if(!c)throw new Error("Assertion failed: The package should have been registered");if(c.identHash!==t)continue;if(null===i){const e=d.stringifyLocator(r);o[e]={value:[r,B.Type.LOCATOR],children:n}}const g=d.stringifyLocator(c);n[g]={value:[{descriptor:s,locator:c},B.Type.DEPENDENT]}}}return i}(t,A,{configuration:e,peers:this.peers});le.emitTree(n,{configuration:e,stdout:this.context.stdout,json:this.json,separators:1})}}Je.usage=C.Command.Usage({description:"display the reason why a package is needed",details:'\n This command prints the exact reasons why a package appears in the dependency tree.\n\n If `-R,--recursive` is set, the listing will go in depth and will list, for each workspaces, what are all the paths that lead to the dependency. Note that the display is somewhat optimized in that it will not print the package listing twice for a single package, so if you see a leaf named "Foo" when looking for "Bar", it means that "Foo" already got printed higher in the tree.\n ',examples:[["Explain why lodash is used in your project","$0 why lodash"]]}),(0,a.gn)([C.Command.String()],Je.prototype,"package",void 0),(0,a.gn)([C.Command.Boolean("-R,--recursive",{description:"List, for each workspace, what are all the paths that lead to the dependency"})],Je.prototype,"recursive",void 0),(0,a.gn)([C.Command.Boolean("--json",{description:"Format the output as an NDJSON stream"})],Je.prototype,"json",void 0),(0,a.gn)([C.Command.Boolean("--peers",{description:"Also print the peer dependencies that match the specified name"})],Je.prototype,"peers",void 0),(0,a.gn)([C.Command.Path("why")],Je.prototype,"execute",null);class qe extends c.BaseCommand{constructor(){super(...arguments),this.verbose=!1,this.json=!1}async execute(){const e=await s.VK.find(this.context.cwd,this.context.plugins),{project:t}=await h.I.find(e,this.context.cwd);return(await p.Pk.start({configuration:e,json:this.json,stdout:this.context.stdout},async e=>{for(const r of t.workspaces){const{manifest:A}=r;let n;if(this.verbose){const e=new Set,r=new Set;for(const n of E.G.hardDependencies)for(const[o,i]of A.getForScope(n)){const A=t.tryWorkspaceByDescriptor(i);null===A?t.workspacesByIdent.has(o)&&r.add(i):e.add(A)}n={workspaceDependencies:Array.from(e).map(e=>e.relativeCwd),mismatchedWorkspaceDependencies:Array.from(r).map(e=>d.stringifyDescriptor(e))}}e.reportInfo(null,""+r.relativeCwd),e.reportJson({location:r.relativeCwd,name:A.name?d.stringifyIdent(A.name):null,...n})}})).exitCode()}}qe.usage=C.Command.Usage({category:"Workspace-related commands",description:"list all available workspaces",details:"\n This command will print the list of all workspaces in the project. If both the `-v,--verbose` and `--json` options are set, Yarn will also return the cross-dependencies between each workspaces (useful when you wish to automatically generate Buck / Bazel rules).\n "}),(0,a.gn)([C.Command.Boolean("-v,--verbose",{description:"Also return the cross-dependencies between workspaces"})],qe.prototype,"verbose",void 0),(0,a.gn)([C.Command.Boolean("--json",{description:"Format the output as an NDJSON stream"})],qe.prototype,"json",void 0),(0,a.gn)([C.Command.Path("workspaces","list")],qe.prototype,"execute",null);class ze extends C.Command{constructor(){super(...arguments),this.args=[]}async execute(){const e=await s.VK.find(this.context.cwd,this.context.plugins),{project:t,workspace:r}=await h.I.find(e,this.context.cwd);if(!r)throw new c.WorkspaceRequiredError(t.cwd,this.context.cwd);const A=t.workspaces,n=new Map(A.map(e=>{const t=d.convertToIdent(e.locator);return[d.stringifyIdent(t),e]})),o=n.get(this.workspaceName);if(void 0===o){const e=Array.from(n.keys()).sort();throw new C.UsageError(`Workspace '${this.workspaceName}' not found. Did you mean any of the following:\n - ${e.join("\n - ")}?`)}return this.cli.run([this.commandName,...this.args],{cwd:o.cwd})}}ze.usage=C.Command.Usage({category:"Workspace-related commands",description:"run a command within the specified workspace",details:"\n This command will run a given sub-command on a single workspace.\n ",examples:[["Add a package to a single workspace","yarn workspace components add -D react"],["Run build script on a single workspace","yarn workspace components run build"]]}),(0,a.gn)([C.Command.String()],ze.prototype,"workspaceName",void 0),(0,a.gn)([C.Command.String()],ze.prototype,"commandName",void 0),(0,a.gn)([C.Command.Proxy()],ze.prototype,"args",void 0),(0,a.gn)([C.Command.Path("workspace")],ze.prototype,"execute",null);const We={configuration:{enableImmutableInstalls:{description:"If true, prevents the install command from modifying the lockfile",type:s.a2.BOOLEAN,default:!1},defaultSemverRangePrefix:{description:"The default save prefix: '^', '~' or ''",type:s.a2.STRING,values:["^","~",""],default:o.CARET}},commands:[L,j,q,Ge,ve,we,qe,re,Ae,ne,ie,M,x,z,te,ae,ge,ue,de,Ce,fe,Le,Re,Pe,Me,Oe,Ue,Te,je,Ye,He,Je,ze]}},68023:(e,t,r)=>{"use strict";r.r(t),r.d(t,{default:()=>E,fileUtils:()=>A});var A={};r.r(A),r.d(A,{makeArchiveFromLocator:()=>p,makeBufferFromLocator:()=>d,makeLocator:()=>h,makeSpec:()=>u,parseSpec:()=>l});var n=r(54143),o=r(46009);const i=/^(?:[a-zA-Z]:[\\/]|\.{0,2}\/)/,s=/^[^?]*\.(?:tar\.gz|tgz)(?:::.*)?$/;var a=r(73632),c=r(72785),g=r(75448);function l(e){const{params:t,selector:r}=n.parseRange(e),A=o.cS.toPortablePath(r);return{parentLocator:t&&"string"==typeof t.locator?n.parseLocator(t.locator):null,path:A}}function u({parentLocator:e,path:t,folderHash:r,protocol:A}){const o=null!==e?{locator:n.stringifyLocator(e)}:{},i=void 0!==r?{hash:r}:{};return n.makeRange({protocol:A,source:t,selector:t,params:{...i,...o}})}function h(e,{parentLocator:t,path:r,folderHash:A,protocol:o}){return n.makeLocator(e,u({parentLocator:t,path:r,folderHash:A,protocol:o}))}async function p(e,{protocol:t,fetchOptions:r,inMemory:A=!1}){const{parentLocator:i,path:s}=n.parseFileStyleRange(e.reference,{protocol:t}),l=o.y1.isAbsolute(s)?{packageFs:new g.M(o.LZ.root),prefixPath:o.LZ.dot,localPath:o.LZ.root}:await r.fetcher.fetch(i,r),u=l.localPath?{packageFs:new g.M(o.LZ.root),prefixPath:o.y1.relative(o.LZ.root,l.localPath)}:l;l!==u&&l.releaseFs&&l.releaseFs();const h=u.packageFs,p=o.y1.join(u.prefixPath,s);return await a.releaseAfterUseAsync(async()=>await c.makeArchiveFromDirectory(p,{baseFs:h,prefixPath:n.getIdentVendorPath(e),compressionLevel:r.project.configuration.get("compressionLevel"),inMemory:A}),u.releaseFs)}async function d(e,{protocol:t,fetchOptions:r}){return(await p(e,{protocol:t,fetchOptions:r,inMemory:!0})).getBufferAndClose()}var C=r(20624),f=r(32485),I=r(46611);const E={fetchers:[class{supports(e,t){return!!s.test(e.reference)&&!!e.reference.startsWith("file:")}getLocalPath(e,t){return null}async fetch(e,t){const r=t.checksums.get(e.locatorHash)||null,[A,o,i]=await t.cache.fetchPackageFromCache(e,r,{onHit:()=>t.report.reportCacheHit(e),onMiss:()=>t.report.reportCacheMiss(e,n.prettyLocator(t.project.configuration,e)+" can't be found in the cache and will be fetched from the disk"),loader:()=>this.fetchFromDisk(e,t),skipIntegrityCheck:t.skipIntegrityCheck});return{packageFs:A,releaseFs:o,prefixPath:n.getIdentVendorPath(e),checksum:i}}async fetchFromDisk(e,t){const{parentLocator:r,path:A}=n.parseFileStyleRange(e.reference,{protocol:"file:"}),i=o.y1.isAbsolute(A)?{packageFs:new g.M(o.LZ.root),prefixPath:o.LZ.dot,localPath:o.LZ.root}:await t.fetcher.fetch(r,t),s=i.localPath?{packageFs:new g.M(o.LZ.root),prefixPath:o.y1.relative(o.LZ.root,i.localPath)}:i;i!==s&&i.releaseFs&&i.releaseFs();const l=s.packageFs,u=o.y1.join(s.prefixPath,A),h=await l.readFilePromise(u);return await a.releaseAfterUseAsync(async()=>await c.convertToZip(h,{compressionLevel:t.project.configuration.get("compressionLevel"),prefixPath:n.getIdentVendorPath(e),stripComponents:1}),s.releaseFs)}},class{supports(e,t){return!!e.reference.startsWith("file:")}getLocalPath(e,t){const{parentLocator:r,path:A}=n.parseFileStyleRange(e.reference,{protocol:"file:"});if(o.y1.isAbsolute(A))return A;const i=t.fetcher.getLocalPath(r,t);return null===i?null:o.y1.resolve(i,A)}async fetch(e,t){const r=t.checksums.get(e.locatorHash)||null,[A,o,i]=await t.cache.fetchPackageFromCache(e,r,{onHit:()=>t.report.reportCacheHit(e),onMiss:()=>t.report.reportCacheMiss(e,n.prettyLocator(t.project.configuration,e)+" can't be found in the cache and will be fetched from the disk"),loader:()=>this.fetchFromDisk(e,t),skipIntegrityCheck:t.skipIntegrityCheck});return{packageFs:A,releaseFs:o,prefixPath:n.getIdentVendorPath(e),localPath:this.getLocalPath(e,t),checksum:i}}async fetchFromDisk(e,t){return p(e,{protocol:"file:",fetchOptions:t})}}],resolvers:[class{supportsDescriptor(e,t){return!!s.test(e.range)&&(!!e.range.startsWith("file:")||!!i.test(e.range))}supportsLocator(e,t){return!!s.test(e.reference)&&!!e.reference.startsWith("file:")}shouldPersistResolution(e,t){return!0}bindDescriptor(e,t,r){return i.test(e.range)&&(e=n.makeDescriptor(e,"file:"+e.range)),n.bindDescriptor(e,{locator:n.stringifyLocator(t)})}getResolutionDependencies(e,t){return[]}async getCandidates(e,t,r){let A=e.range;return A.startsWith("file:")&&(A=A.slice("file:".length)),[n.makeLocator(e,"file:"+o.cS.toPortablePath(A))]}async getSatisfying(e,t,r){return null}async resolve(e,t){if(!t.fetchOptions)throw new Error("Assertion failed: This resolver cannot be used unless a fetcher is configured");const r=await t.fetchOptions.fetcher.fetch(e,t.fetchOptions),A=await a.releaseAfterUseAsync(async()=>await I.G.find(r.prefixPath,{baseFs:r.packageFs}),r.releaseFs);return{...e,version:A.version||"0.0.0",languageName:t.project.configuration.get("defaultLanguageName"),linkType:f.Un.HARD,dependencies:A.dependencies,peerDependencies:A.peerDependencies,dependenciesMeta:A.dependenciesMeta,peerDependenciesMeta:A.peerDependenciesMeta,bin:A.bin}}},class{supportsDescriptor(e,t){return!!e.range.match(i)||!!e.range.startsWith("file:")}supportsLocator(e,t){return!!e.reference.startsWith("file:")}shouldPersistResolution(e,t){return!1}bindDescriptor(e,t,r){return i.test(e.range)&&(e=n.makeDescriptor(e,"file:"+e.range)),n.bindDescriptor(e,{locator:n.stringifyLocator(t)})}getResolutionDependencies(e,t){return[]}async getCandidates(e,t,r){if(!r.fetchOptions)throw new Error("Assertion failed: This resolver cannot be used unless a fetcher is configured");const{path:A,parentLocator:o}=l(e.range);if(null===o)throw new Error("Assertion failed: The descriptor should have been bound");const i=await d(n.makeLocator(e,n.makeRange({protocol:"file:",source:A,selector:A,params:{locator:n.stringifyLocator(o)}})),{protocol:"file:",fetchOptions:r.fetchOptions});return[h(e,{parentLocator:o,path:A,folderHash:C.makeHash("1",i).slice(0,6),protocol:"file:"})]}async getSatisfying(e,t,r){return null}async resolve(e,t){if(!t.fetchOptions)throw new Error("Assertion failed: This resolver cannot be used unless a fetcher is configured");const r=await t.fetchOptions.fetcher.fetch(e,t.fetchOptions),A=await a.releaseAfterUseAsync(async()=>await I.G.find(r.prefixPath,{baseFs:r.packageFs}),r.releaseFs);return{...e,version:A.version||"0.0.0",languageName:t.project.configuration.get("defaultLanguageName"),linkType:f.Un.HARD,dependencies:A.dependencies,peerDependencies:A.peerDependencies,dependenciesMeta:A.dependenciesMeta,peerDependenciesMeta:A.peerDependenciesMeta,bin:A.bin}}}]}},75641:(e,t,r)=>{"use strict";r.r(t),r.d(t,{default:()=>F,gitUtils:()=>A});var A={};r.r(A),r.d(A,{TreeishProtocols:()=>y,clone:()=>S,isGitUrl:()=>m,lsRemote:()=>b,normalizeLocator:()=>D,normalizeRepoUrl:()=>Q,resolveUrl:()=>v,splitRepoUrl:()=>w});var n=r(39922),o=r(54143),i=r(63088),s=r(73632),a=r(72785),c=r(43896),g=r(46009),l=r(79669),u=r(6220),h=r(71191),p=r.n(h),d=r(53887),C=r.n(d),f=r(78835),I=r.n(f);function E(){return{...process.env,GIT_SSH_COMMAND:"ssh -o BatchMode=yes"}}const B=[/^ssh:/,/^git(?:\+[^:]+)?:/,/^(?:git\+)?https?:[^#]+\/[^#]+(?:\.git)(?:#.*)?$/,/^git@[^#]+\/[^#]+\.git(?:#.*)?$/,/^(?:github:|https:\/\/github\.com\/)?(?!\.{1,2}\/)([a-zA-Z._0-9-]+)\/(?!\.{1,2}(?:#|$))([a-zA-Z._0-9-]+?)(?:\.git)?(?:#.*)?$/,/^https:\/\/github\.com\/(?!\.{1,2}\/)([a-zA-Z0-9._-]+)\/(?!\.{1,2}(?:#|$))([a-zA-Z0-9._-]+?)\/tarball\/(.+)?$/];var y;function m(e){return!!e&&B.some(t=>!!e.match(t))}function w(e){const t=(e=Q(e)).indexOf("#");if(-1===t)return{repo:e,treeish:{protocol:y.Head,request:"master"},extra:{}};const r=e.slice(0,t),A=e.slice(t+1);if(A.match(/^[a-z]+=/)){const e=p().parse(A);for(const[t,r]of Object.entries(e))if("string"!=typeof r)throw new Error(`Assertion failed: The ${t} parameter must be a literal string`);const t=Object.values(y).find(t=>Object.prototype.hasOwnProperty.call(e,t));let n,o;void 0!==t?(n=t,o=e[t]):(n=y.Head,o="master");for(const t of Object.values(y))delete e[t];return{repo:r,treeish:{protocol:n,request:o},extra:e}}{const e=A.indexOf(":");let t,n;return-1===e?(t=null,n=A):(t=A.slice(0,e),n=A.slice(e+1)),{repo:r,treeish:{protocol:t,request:n},extra:{}}}}function Q(e,{git:t=!1}={}){var r;if(e=(e=(e=e.replace(/^git\+https:/,"https:")).replace(/^(?:github:|https:\/\/github\.com\/)?(?!\.{1,2}\/)([a-zA-Z0-9._-]+)\/(?!\.{1,2}(?:#|$))([a-zA-Z0-9._-]+?)(?:\.git)?(#.*)?$/,"https://github.com/$1/$2.git$3")).replace(/^https:\/\/github\.com\/(?!\.{1,2}\/)([a-zA-Z0-9._-]+)\/(?!\.{1,2}(?:#|$))([a-zA-Z0-9._-]+?)\/tarball\/(.+)?$/,"https://github.com/$1/$2.git#$3"),t){let t;e=e.replace(/^git\+([^:]+):/,"$1:");try{t=I().parse(e)}catch(e){t=null}t&&"ssh:"===t.protocol&&(null===(r=t.path)||void 0===r?void 0:r.startsWith("/:"))&&(e=e.replace(/^ssh:\/\//,""))}return e}function D(e){return o.makeLocator(e,Q(e.reference))}async function b(e,t){const r=Q(e,{git:!0});if(!l.getNetworkSettings(r,{configuration:t}).enableNetwork)throw new Error(`Request to '${r}' has been blocked because of your configuration settings`);let A;try{A=await u.execvp("git",["ls-remote","--refs",r],{cwd:t.startingCwd,env:E(),strict:!0})}catch(t){throw t.message=`Listing the refs for ${e} failed`,t}const n=new Map,o=/^([a-f0-9]{40})\t(refs\/[^\n]+)/gm;let i;for(;null!==(i=o.exec(A.stdout));)n.set(i[2],i[1]);return n}async function v(e,t){const{repo:r,treeish:{protocol:A,request:n},extra:o}=w(e),i=await b(r,t),s=(e,t)=>{switch(e){case y.Commit:if(!t.match(/^[a-f0-9]{40}$/))throw new Error("Invalid commit hash");return p().stringify({...o,commit:t});case y.Head:{const e=i.get("refs/heads/"+t);if(void 0===e)throw new Error(`Unknown head ("${t}")`);return p().stringify({...o,commit:e})}case y.Tag:{const e=i.get("refs/tags/"+t);if(void 0===e)throw new Error(`Unknown tag ("${t}")`);return p().stringify({...o,commit:e})}case y.Semver:{if(!C().validRange(t))throw new Error(`Invalid range ("${t}")`);const e=new Map([...i.entries()].filter(([e])=>e.startsWith("refs/tags/")).map(([e,t])=>[C().parse(e.slice(10)),t]).filter(e=>null!==e[0])),r=C().maxSatisfying([...e.keys()],t);if(null===r)throw new Error(`No matching range ("${t}")`);return p().stringify({...o,commit:e.get(r)})}case null:{let e;if(null!==(e=a(y.Commit,t)))return e;if(null!==(e=a(y.Tag,t)))return e;if(null!==(e=a(y.Head,t)))return e;throw t.match(/^[a-f0-9]+$/)?new Error(`Couldn't resolve "${t}" as either a commit, a tag, or a head - if a commit, use the 40-characters commit hash`):new Error(`Couldn't resolve "${t}" as either a commit, a tag, or a head`)}default:throw new Error(`Invalid Git resolution protocol ("${e}")`)}},a=(e,t)=>{try{return s(e,t)}catch(e){return null}};return`${r}#${s(A,n)}`}async function S(e,t){return await t.getLimit("cloneConcurrency")(async()=>{const{repo:r,treeish:{protocol:A,request:n}}=w(e);if("commit"!==A)throw new Error("Invalid treeish protocol when cloning");const o=Q(r,{git:!0});if(!1===l.getNetworkSettings(o,{configuration:t}).enableNetwork)throw new Error(`Request to '${o}' has been blocked because of your configuration settings`);const i=await c.xfs.mktempPromise(),s={cwd:i,env:E(),strict:!0};try{await u.execvp("git",["clone","-c core.autocrlf=false",o,g.cS.fromPortablePath(i)],s),await u.execvp("git",["checkout",""+n],s)}catch(e){throw e.message="Repository clone failed: "+e.message,e}return i})}!function(e){e.Commit="commit",e.Head="head",e.Tag="tag",e.Semver="semver"}(y||(y={}));var k=r(32485),N=r(46611);const F={configuration:{cloneConcurrency:{description:"Maximal number of concurrent clones",type:n.a2.NUMBER,default:2}},fetchers:[class{supports(e,t){return m(e.reference)}getLocalPath(e,t){return null}async fetch(e,t){const r=t.checksums.get(e.locatorHash)||null,A=D(e),n=new Map(t.checksums);n.set(A.locatorHash,r);const i={...t,checksums:n},s=await this.downloadHosted(A,i);if(null!==s)return s;const[a,c,g]=await t.cache.fetchPackageFromCache(e,r,{onHit:()=>t.report.reportCacheHit(e),onMiss:()=>t.report.reportCacheMiss(e,o.prettyLocator(t.project.configuration,e)+" can't be found in the cache and will be fetched from the remote repository"),loader:()=>this.cloneFromRemote(A,i),skipIntegrityCheck:t.skipIntegrityCheck});return{packageFs:a,releaseFs:c,prefixPath:o.getIdentVendorPath(e),checksum:g}}async downloadHosted(e,t){return t.project.configuration.reduceHook(e=>e.fetchHostedRepository,null,e,t)}async cloneFromRemote(e,t){const r=await S(e.reference,t.project.configuration),A=w(e.reference),n=g.y1.join(r,"package.tgz");await i.prepareExternalProject(r,n,{configuration:t.project.configuration,report:t.report,workspace:A.extra.workspace});const l=await c.xfs.readFilePromise(n);return await s.releaseAfterUseAsync(async()=>await a.convertToZip(l,{compressionLevel:t.project.configuration.get("compressionLevel"),prefixPath:o.getIdentVendorPath(e),stripComponents:1}))}}],resolvers:[class{supportsDescriptor(e,t){return m(e.range)}supportsLocator(e,t){return m(e.reference)}shouldPersistResolution(e,t){return!0}bindDescriptor(e,t,r){return e}getResolutionDependencies(e,t){return[]}async getCandidates(e,t,r){const A=await v(e.range,r.project.configuration);return[o.makeLocator(e,A)]}async getSatisfying(e,t,r){return null}async resolve(e,t){if(!t.fetchOptions)throw new Error("Assertion failed: This resolver cannot be used unless a fetcher is configured");const r=await t.fetchOptions.fetcher.fetch(e,t.fetchOptions),A=await s.releaseAfterUseAsync(async()=>await N.G.find(r.prefixPath,{baseFs:r.packageFs}),r.releaseFs);return{...e,version:A.version||"0.0.0",languageName:t.project.configuration.get("defaultLanguageName"),linkType:k.Un.HARD,dependencies:A.dependencies,peerDependencies:A.peerDependencies,dependenciesMeta:A.dependenciesMeta,peerDependenciesMeta:A.peerDependenciesMeta,bin:A.bin}}}]}},68126:(e,t,r)=>{"use strict";r.r(t),r.d(t,{default:()=>d});var A=r(54143),n=r(79669),o=r(72785),i=r(63088),s=r(43896),a=r(75448),c=r(46009),g=r(75641),l=r(71191),u=r.n(l);const h=[/^https?:\/\/(?:([^/]+?)@)?github.com\/([^/#]+)\/([^/#]+)\/tarball\/([^/#]+)(?:#(.*))?$/,/^https?:\/\/(?:([^/]+?)@)?github.com\/([^/#]+)\/([^/#]+?)(?:\.git)?(?:#(.*))?$/];class p{supports(e,t){return!(!(r=e.reference)||!h.some(e=>!!r.match(e)));var r}getLocalPath(e,t){return null}async fetch(e,t){const r=t.checksums.get(e.locatorHash)||null,[n,o,i]=await t.cache.fetchPackageFromCache(e,r,{onHit:()=>t.report.reportCacheHit(e),onMiss:()=>t.report.reportCacheMiss(e,A.prettyLocator(t.project.configuration,e)+" can't be found in the cache and will be fetched from GitHub"),loader:()=>this.fetchFromNetwork(e,t),skipIntegrityCheck:t.skipIntegrityCheck});return{packageFs:n,releaseFs:o,prefixPath:A.getIdentVendorPath(e),checksum:i}}async fetchFromNetwork(e,t){const r=await n.get(this.getLocatorUrl(e,t),{configuration:t.project.configuration});return await s.xfs.mktempPromise(async n=>{const l=new a.M(n);await o.extractArchiveTo(r,l,{stripComponents:1});const u=g.gitUtils.splitRepoUrl(e.reference),h=c.y1.join(n,"package.tgz");await i.prepareExternalProject(n,h,{configuration:t.project.configuration,report:t.report,workspace:u.extra.workspace});const p=await s.xfs.readFilePromise(h);return await o.convertToZip(p,{compressionLevel:t.project.configuration.get("compressionLevel"),prefixPath:A.getIdentVendorPath(e),stripComponents:1})})}getLocatorUrl(e,t){const{auth:r,username:A,reponame:n,treeish:o}=function(e){let t;for(const r of h)if(t=e.match(r),t)break;if(!t)throw new Error(`Input cannot be parsed as a valid GitHub URL ('${e}').`);let[,r,A,n,o="master"]=t;const{commit:i}=u().parse(o);return o=i||o.replace(/[^:]*:/,""),{auth:r,username:A,reponame:n,treeish:o}}(e.reference);return`https://${r?r+"@":""}github.com/${A}/${n}/archive/${o}.tar.gz`}}const d={hooks:{async fetchHostedRepository(e,t,r){if(null!==e)return e;const A=new p;if(!A.supports(t,r))return null;try{return await A.fetch(t,r)}catch(e){return null}}}}},99148:(e,t,r)=>{"use strict";r.r(t),r.d(t,{default:()=>l});var A=r(54143),n=r(79669),o=r(72785);const i=/^[^?]*\.(?:tar\.gz|tgz)(?:\?.*)?$/,s=/^https?:/;var a=r(46611),c=r(32485),g=r(73632);const l={fetchers:[class{supports(e,t){return!!i.test(e.reference)&&!!s.test(e.reference)}getLocalPath(e,t){return null}async fetch(e,t){const r=t.checksums.get(e.locatorHash)||null,[n,o,i]=await t.cache.fetchPackageFromCache(e,r,{onHit:()=>t.report.reportCacheHit(e),onMiss:()=>t.report.reportCacheMiss(e,A.prettyLocator(t.project.configuration,e)+" can't be found in the cache and will be fetched from the remote server"),loader:()=>this.fetchFromNetwork(e,t),skipIntegrityCheck:t.skipIntegrityCheck});return{packageFs:n,releaseFs:o,prefixPath:A.getIdentVendorPath(e),checksum:i}}async fetchFromNetwork(e,t){const r=await n.get(e.reference,{configuration:t.project.configuration});return await o.convertToZip(r,{compressionLevel:t.project.configuration.get("compressionLevel"),prefixPath:A.getIdentVendorPath(e),stripComponents:1})}}],resolvers:[class{supportsDescriptor(e,t){return!!i.test(e.range)&&!!s.test(e.range)}supportsLocator(e,t){return!!i.test(e.reference)&&!!s.test(e.reference)}shouldPersistResolution(e,t){return!0}bindDescriptor(e,t,r){return e}getResolutionDependencies(e,t){return[]}async getCandidates(e,t,r){return[A.convertDescriptorToLocator(e)]}async getSatisfying(e,t,r){return null}async resolve(e,t){if(!t.fetchOptions)throw new Error("Assertion failed: This resolver cannot be used unless a fetcher is configured");const r=await t.fetchOptions.fetcher.fetch(e,t.fetchOptions),A=await g.releaseAfterUseAsync(async()=>await a.G.find(r.prefixPath,{baseFs:r.packageFs}),r.releaseFs);return{...e,version:A.version||"0.0.0",languageName:t.project.configuration.get("defaultLanguageName"),linkType:c.Un.HARD,dependencies:A.dependencies,peerDependencies:A.peerDependencies,dependenciesMeta:A.dependenciesMeta,peerDependenciesMeta:A.peerDependenciesMeta,bin:A.bin}}}]}},64314:(e,t,r)=>{"use strict";r.r(t),r.d(t,{default:()=>I});var A=r(39922),n=r(36370),o=r(25413),i=r(46611),s=r(85824),a=r(6220),c=r(63088),g=r(54143),l=r(43896),u=r(46009),h=r(40822),p=r(80305),d=r.n(p),C=r(31669);class f extends o.BaseCommand{constructor(){super(...arguments),this.usev2=!1,this.assumeFreshProject=!1,this.yes=!1,this.private=!1,this.workspace=!1,this.install=!1}async execute(){if(l.xfs.existsSync(u.y1.join(this.context.cwd,i.G.fileName)))throw new h.UsageError("A package.json already exists in the specified directory");const e=await A.VK.find(this.context.cwd,this.context.plugins),t=this.install?!0===this.install?"latest":this.install:null;return null!==t?await this.executeProxy(e,t):await this.executeRegular(e)}async executeProxy(e,t){if(null!==e.get("yarnPath"))throw new h.UsageError(`Cannot use the --install flag when the current directory already uses yarnPath (from ${e.sources.get("yarnPath")})`);if(null!==e.projectCwd)throw new h.UsageError("Cannot use the --install flag when the current directory is already part of a project");l.xfs.existsSync(this.context.cwd)||await l.xfs.mkdirPromise(this.context.cwd,{recursive:!0});const r=u.y1.join(this.context.cwd,e.get("lockfileFilename"));l.xfs.existsSync(r)||await l.xfs.writeFilePromise(r,"");const A=await this.cli.run(["set","version",t]);if(0!==A)return A;this.context.stdout.write("\n");const n=["--assume-fresh-project"];return this.private&&n.push("-p"),this.workspace&&n.push("-w"),this.yes&&n.push("-y"),await l.xfs.mktempPromise(async e=>{const{code:t}=await a.pipevp("yarn",["init",...n],{cwd:this.context.cwd,stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr,env:await c.makeScriptEnv({binFolder:e})});return t})}async executeRegular(e){let t=null;if(!this.assumeFreshProject)try{t=await s.I.find(e,this.context.cwd)}catch(e){t=null}l.xfs.existsSync(this.context.cwd)||await l.xfs.mkdirPromise(this.context.cwd,{recursive:!0});const r=new i.G,A=Object.fromEntries(e.get("initFields").entries());r.load(A),r.name=g.makeIdent(e.get("initScope"),u.y1.basename(this.context.cwd)),r.version=e.get("initVersion"),r.private=this.private||this.workspace,r.license=e.get("initLicense"),this.workspace&&(await l.xfs.mkdirPromise(u.y1.join(this.context.cwd,"packages"),{recursive:!0}),r.workspaceDefinitions=[{pattern:"packages/*"}]);const n={};r.exportTo(n),C.inspect.styles.name="cyan",this.context.stdout.write((0,C.inspect)(n,{depth:1/0,colors:!0,compact:!1})+"\n");const o=u.y1.join(this.context.cwd,i.G.fileName);await l.xfs.changeFilePromise(o,JSON.stringify(n,null,2)+"\n");const c=u.y1.join(this.context.cwd,"README.md");if(l.xfs.existsSync(c)||await l.xfs.writeFilePromise(c,`# ${g.stringifyIdent(r.name)}\n`),!t){const t=u.y1.join(this.context.cwd,u.QS.lockfile);await l.xfs.writeFilePromise(t,"");const r=["/.yarn/** linguist-vendored"].map(e=>e+"\n").join(""),A=u.y1.join(this.context.cwd,".gitattributes");l.xfs.existsSync(A)||await l.xfs.writeFilePromise(A,r);const n=["/.yarn/*","!/.yarn/releases","!/.yarn/plugins","!/.yarn/sdks","","# Swap the comments on the following lines if you don't wish to use zero-installs","# Documentation here: https://yarnpkg.com/features/zero-installs","!/.yarn/cache","#/.pnp.*"].map(e=>e+"\n").join(""),o=u.y1.join(this.context.cwd,".gitignore");l.xfs.existsSync(o)||await l.xfs.writeFilePromise(o,n);const i={"*":{endOfLine:"lf",insertFinalNewline:!0},"*.{js,json,.yml}":{charset:"utf-8",indentStyle:"space",indentSize:2}};d()(i,e.get("initEditorConfig"));let s="root = true\n";for(const[e,t]of Object.entries(i)){s+=`\n[${e}]\n`;for(const[e,r]of Object.entries(t)){s+=`${e.replace(/[A-Z]/g,e=>"_"+e.toLowerCase())} = ${r}\n`}}const c=u.y1.join(this.context.cwd,".editorconfig");l.xfs.existsSync(c)||await l.xfs.writeFilePromise(c,s),await a.execvp("git",["init"],{cwd:this.context.cwd})}}}f.usage=h.Command.Usage({description:"create a new package",details:"\n This command will setup a new package in your local directory.\n\n If the `-p,--private` or `-w,--workspace` options are set, the package will be private by default.\n\n If the `-w,--workspace` option is set, the package will be configured to accept a set of workspaces in the `packages/` directory.\n\n If the `-i,--install` option is given a value, Yarn will first download it using `yarn set version` and only then forward the init call to the newly downloaded bundle. Without arguments, the downloaded bundle will be `latest`.\n\n The initial settings of the manifest can be changed by using the `initScope` and `initFields` configuration values. Additionally, Yarn will generate an EditorConfig file whose rules can be altered via `initEditorConfig`, and will initialize a Git repository in the current directory.\n ",examples:[["Create a new package in the local directory","yarn init"],["Create a new private package in the local directory","yarn init -p"],["Create a new package and store the Yarn release inside","yarn init -i latest"],["Create a new private package and defines it as a workspace root","yarn init -w"]]}),(0,n.gn)([h.Command.Boolean("-2",{hidden:!0})],f.prototype,"usev2",void 0),(0,n.gn)([h.Command.Boolean("--assume-fresh-project",{hidden:!0})],f.prototype,"assumeFreshProject",void 0),(0,n.gn)([h.Command.Boolean("-y,--yes",{hidden:!0})],f.prototype,"yes",void 0),(0,n.gn)([h.Command.Boolean("-p,--private",{description:"Initialize a private package"})],f.prototype,"private",void 0),(0,n.gn)([h.Command.Boolean("-w,--workspace",{description:"Initialize a private workspace root with a `packages/` directory"})],f.prototype,"workspace",void 0),(0,n.gn)([h.Command.String("-i,--install",{tolerateBoolean:!0,description:"Initialize a package with a specific bundle that will be locked in the project"})],f.prototype,"install",void 0),(0,n.gn)([h.Command.Path("init")],f.prototype,"execute",null);const I={configuration:{initLicense:{description:"License used when creating packages via the init command",type:A.a2.STRING,default:null},initScope:{description:"Scope used when creating packages via the init command",type:A.a2.STRING,default:null},initVersion:{description:"Version used when creating packages via the init command",type:A.a2.STRING,default:null},initFields:{description:"Additional fields to set when creating packages via the init command",type:A.a2.MAP,valueDefinition:{description:"",type:A.a2.ANY}},initEditorConfig:{description:"Extra rules to define in the generator editorconfig",type:A.a2.MAP,valueDefinition:{description:"",type:A.a2.ANY}}},commands:[f]}},92994:(e,t,r)=>{"use strict";r.r(t),r.d(t,{default:()=>g});var A=r(54143),n=r(46009),o=r(75448),i=r(10489);var s=r(46611),a=r(32485),c=r(73632);const g={fetchers:[class{supports(e,t){return!!e.reference.startsWith("link:")}getLocalPath(e,t){const{parentLocator:r,path:o}=A.parseFileStyleRange(e.reference,{protocol:"link:"});if(n.y1.isAbsolute(o))return o;const i=t.fetcher.getLocalPath(r,t);return null===i?null:n.y1.resolve(i,o)}async fetch(e,t){const{parentLocator:r,path:s}=A.parseFileStyleRange(e.reference,{protocol:"link:"}),a=n.y1.isAbsolute(s)?{packageFs:new o.M(n.LZ.root),prefixPath:n.LZ.dot,localPath:n.LZ.root}:await t.fetcher.fetch(r,t),c=a.localPath?{packageFs:new o.M(n.LZ.root),prefixPath:n.y1.relative(n.LZ.root,a.localPath)}:a;a!==c&&a.releaseFs&&a.releaseFs();const g=c.packageFs,l=n.y1.join(c.prefixPath,s);return a.localPath?{packageFs:new o.M(l,{baseFs:g}),releaseFs:c.releaseFs,prefixPath:n.LZ.dot,discardFromLookup:!0,localPath:l}:{packageFs:new i.n(l,{baseFs:g}),releaseFs:c.releaseFs,prefixPath:n.LZ.dot,discardFromLookup:!0}}},class{supports(e,t){return!!e.reference.startsWith("portal:")}getLocalPath(e,t){const{parentLocator:r,path:o}=A.parseFileStyleRange(e.reference,{protocol:"portal:"});if(n.y1.isAbsolute(o))return o;const i=t.fetcher.getLocalPath(r,t);return null===i?null:n.y1.resolve(i,o)}async fetch(e,t){const{parentLocator:r,path:s}=A.parseFileStyleRange(e.reference,{protocol:"portal:"}),a=n.y1.isAbsolute(s)?{packageFs:new o.M(n.LZ.root),prefixPath:n.LZ.dot,localPath:n.LZ.root}:await t.fetcher.fetch(r,t),c=a.localPath?{packageFs:new o.M(n.LZ.root),prefixPath:n.y1.relative(n.LZ.root,a.localPath)}:a;a!==c&&a.releaseFs&&a.releaseFs();const g=c.packageFs,l=n.y1.join(c.prefixPath,s);return a.localPath?{packageFs:new o.M(l,{baseFs:g}),releaseFs:c.releaseFs,prefixPath:n.LZ.dot,localPath:l}:{packageFs:new i.n(l,{baseFs:g}),releaseFs:c.releaseFs,prefixPath:n.LZ.dot}}}],resolvers:[class{supportsDescriptor(e,t){return!!e.range.startsWith("link:")}supportsLocator(e,t){return!!e.reference.startsWith("link:")}shouldPersistResolution(e,t){return!1}bindDescriptor(e,t,r){return A.bindDescriptor(e,{locator:A.stringifyLocator(t)})}getResolutionDependencies(e,t){return[]}async getCandidates(e,t,r){const o=e.range.slice("link:".length);return[A.makeLocator(e,"link:"+n.cS.toPortablePath(o))]}async getSatisfying(e,t,r){return null}async resolve(e,t){return{...e,version:"0.0.0",languageName:t.project.configuration.get("defaultLanguageName"),linkType:a.Un.SOFT,dependencies:new Map,peerDependencies:new Map,dependenciesMeta:new Map,peerDependenciesMeta:new Map,bin:new Map}}},class{supportsDescriptor(e,t){return!!e.range.startsWith("portal:")}supportsLocator(e,t){return!!e.reference.startsWith("portal:")}shouldPersistResolution(e,t){return!1}bindDescriptor(e,t,r){return A.bindDescriptor(e,{locator:A.stringifyLocator(t)})}getResolutionDependencies(e,t){return[]}async getCandidates(e,t,r){const o=e.range.slice("portal:".length);return[A.makeLocator(e,"portal:"+n.cS.toPortablePath(o))]}async getSatisfying(e,t,r){return null}async resolve(e,t){if(!t.fetchOptions)throw new Error("Assertion failed: This resolver cannot be used unless a fetcher is configured");const r=await t.fetchOptions.fetcher.fetch(e,t.fetchOptions),A=await c.releaseAfterUseAsync(async()=>await s.G.find(r.prefixPath,{baseFs:r.packageFs}),r.releaseFs);return{...e,version:A.version||"0.0.0",languageName:t.project.configuration.get("defaultLanguageName"),linkType:a.Un.SOFT,dependencies:new Map([...A.dependencies,...A.devDependencies]),peerDependencies:A.peerDependencies,dependenciesMeta:A.dependenciesMeta,peerDependenciesMeta:A.peerDependenciesMeta,bin:A.bin}}}]}},8375:(e,t,r)=>{"use strict";r.r(t),r.d(t,{default:()=>ne,getPnpPath:()=>Ae});var A,n=r(39922),o=r(46009),i=r(54143);!function(e){e[e.YES=0]="YES",e[e.NO=1]="NO",e[e.DEPENDS=2]="DEPENDS"}(A||(A={}));const s=(e,t)=>`${e}@${t}`,a=(e,t)=>{const r=t.indexOf("#"),A=r>=0?t.substring(r+1):t;return s(e,A)};var c;!function(e){e[e.NONE=-1]="NONE",e[e.PERF=0]="PERF",e[e.CHECK=1]="CHECK",e[e.REASONS=2]="REASONS",e[e.INTENSIVE_CHECK=9]="INTENSIVE_CHECK"}(c||(c={}));const g=(e,t)=>{if(t.decoupled)return t;const{name:r,references:A,ident:n,locator:o,dependencies:i,originalDependencies:s,hoistedDependencies:a,peerNames:c,reasons:g,isHoistBorder:l}=t,u={name:r,references:new Set(A),ident:n,locator:o,dependencies:new Map(i),originalDependencies:new Map(s),hoistedDependencies:new Map(a),peerNames:new Set(c),reasons:new Map(g),decoupled:!0,isHoistBorder:l},h=u.dependencies.get(r);return h&&h.ident==u.ident&&u.dependencies.set(r,u),e.dependencies.set(u.name,u),u},l=e=>{const t=new Set,r=(A,n=new Set)=>{if(!n.has(A)){n.add(A);for(const o of A.peerNames)if(!e.peerNames.has(o)){const A=e.dependencies.get(o);A&&!t.has(A)&&r(A,n)}t.add(A)}};for(const t of e.dependencies.values())e.peerNames.has(t.name)||r(t);return t},u=(e,t,r,A,n=new Set)=>{const o=t[t.length-1];if(n.has(o))return;n.add(o);const i=((e,t)=>{const r=new Map([[e.name,[e.ident]]]);for(const t of e.dependencies.values())e.peerNames.has(t.name)||r.set(t.name,[t.ident]);const A=Array.from(t.keys());A.sort((e,r)=>{const A=t.get(e),n=t.get(r);return n.peerDependents.size!==A.peerDependents.size?n.peerDependents.size-A.peerDependents.size:n.dependents.size-A.dependents.size});for(const t of A){const A=t.substring(0,t.indexOf("@",1)),n=t.substring(A.length+1);if(!e.peerNames.has(A)){let e=r.get(A);e||(e=[],r.set(A,e)),e.indexOf(n)<0&&e.push(n)}}return r})(o,E(o)),s=new Map(Array.from(i.entries()).map(([e,t])=>[e,t[0]])),a=o===e?new Map:(e=>{const t=new Map,r=new Set,A=n=>{if(!r.has(n)){r.add(n);for(const r of n.hoistedDependencies.values())e.dependencies.has(r.name)||t.set(r.name,r);for(const e of n.dependencies.values())n.peerNames.has(e.name)||A(e)}};return A(e),t})(o);let c;do{p(e,t,r,a,s,i,A),c=!1;for(const[e,t]of i)t.length>1&&!o.dependencies.has(e)&&(s.delete(e),t.shift(),s.set(e,t[0]),c=!0)}while(c);for(const n of o.dependencies.values())o.peerNames.has(n.name)||r.has(n.locator)||(r.add(n.locator),u(e,[...t,n],r,A),r.delete(n.locator))},h=(e,t,r,n,o,i,{outputReason:s})=>{let a,c=null,g=new Set;s&&(a=""+Array.from(e).map(e=>B(e)).join("→"));const l=t[t.length-1],u=r.ident===l.ident,h=o.get(r.name);let p=h===r.ident&&!u;if(s&&!p&&h&&!u&&(c=`- filled by: ${B(i.get(r.name)[0])} at ${a}`),p){let e=!1;const A=n.get(r.name);if(e=!A||A.ident===r.ident,s&&!e&&(c=`- filled by: ${B(A.locator)} at ${a}`),e)for(let A=1;A=1;r--){const n=t[r];for(const o of A){if(n.peerNames.has(o)&&n.originalDependencies.has(o))continue;const i=n.dependencies.get(o);i&&(r===t.length-1?g.add(i):(g=null,e=!1,s&&(c=`- peer dependency ${B(i.locator)} from parent ${B(n.locator)} was not hoisted to ${a}`))),A.delete(o)}if(!e)break}p=e}return null!==g&&g.size>0?{isHoistable:A.DEPENDS,dependsOn:g,reason:c}:{isHoistable:p?A.YES:A.NO,reason:c}},p=(e,t,r,n,o,i,s)=>{const a=t[t.length-1],u=new Set,p=(t,C,I,E)=>{if(u.has(I))return;const m=[...C,I.locator],w=new Map,Q=new Map;for(const e of l(I)){let g=null;if(g||(g=h(r,[a,...t,I],e,n,o,i,{outputReason:s.debugLevel>=c.REASONS})),Q.set(e,g),g.isHoistable===A.DEPENDS)for(const t of g.dependsOn){const r=w.get(t.name)||new Set;r.add(e.name),w.set(t.name,r)}}const D=new Set,b=(e,t,r)=>{if(!D.has(e)){D.add(e),e.ident!==I.ident&&Q.set(e,{isHoistable:A.NO,reason:r});for(const A of w.get(e.name)||[])b(I.dependencies.get(A),t,r)}};let v;s.debugLevel>=c.REASONS&&(v=""+Array.from(r).map(e=>B(e)).join("→"));for(const[e,t]of Q)t.isHoistable===A.NO&&b(e,t,`- peer dependency ${B(e.locator)} from parent ${B(I.locator)} was not hoisted to ${v}`);for(const e of Q.keys())if(!D.has(e)){I.dependencies.delete(e.name),I.hoistedDependencies.set(e.name,e),I.reasons.delete(e.name);const t=a.dependencies.get(e.name);if(t)for(const r of e.references)t.references.add(r);else a.ident!==e.ident&&(a.dependencies.set(e.name,e),E.add(e))}if(s.check){const r=d(e);if(r)throw new Error(`${r}, after hoisting dependencies of ${[a,...t,I].map(e=>B(e.locator)).join("→")}:\n${y(e)}`)}const S=l(I);for(const e of S)if(D.has(e)&&m.indexOf(e.locator)<0){const r=Q.get(e);if(r.isHoistable!==A.YES&&I.reasons.set(e.name,r.reason),!e.isHoistBorder){u.add(I);const r=g(I,e);p([...t,I],[...C,I.locator],r,f),u.delete(I)}}};let C,f=new Set(l(a));do{C=f,f=new Set;for(const e of C){if(e.locator===a.locator||e.isHoistBorder)continue;const t=g(a,e);p([],Array.from(r),t,f)}}while(f.size>0)},d=e=>{const t=[],r=new Set,A=new Set,n=(e,o)=>{if(r.has(e))return;if(r.add(e),A.has(e))return;const i=new Map(o);for(const t of e.dependencies.values())e.peerNames.has(t.name)||i.set(t.name,t);for(const r of e.originalDependencies.values()){const n=i.get(r.name),s=()=>""+Array.from(A).concat([e]).map(e=>B(e.locator)).join("→");if(e.peerNames.has(r.name)){const e=o.get(r.name);e===n&&e&&e.ident===r.ident||t.push(`${s()} - broken peer promise: expected ${r.ident} but found ${e?e.ident:e}`)}else n?n.ident!==r.ident&&t.push(`${s()} - broken require promise for ${r.name}: expected ${r.ident}, but found: ${n.ident}`):t.push(`${s()} - broken require promise: no required dependency ${r.locator} found`)}A.add(e);for(const t of e.dependencies.values())e.peerNames.has(t.name)||n(t,i);A.delete(e)};return n(e,e.dependencies),t.join("\n")},C=(e,t)=>{const{identName:r,name:A,reference:n,peerNames:o}=e,i={name:A,references:new Set([n]),locator:s(r,n),ident:a(r,n),dependencies:new Map,originalDependencies:new Map,hoistedDependencies:new Map,peerNames:new Set(o),reasons:new Map,decoupled:!0,isHoistBorder:!0},c=new Map([[e,i]]),g=(e,r)=>{let A=c.get(e);const n=!!A;if(!A){const{name:n,identName:o,reference:i,peerNames:g}=e,l=t.hoistingLimits.get(r.locator);A={name:n,references:new Set([i]),locator:s(o,i),ident:a(o,i),dependencies:new Map,originalDependencies:new Map,hoistedDependencies:new Map,peerNames:new Set(g),reasons:new Map,decoupled:!0,isHoistBorder:!!l&&l.has(n)},c.set(e,A)}if(r.dependencies.set(e.name,A),r.originalDependencies.set(e.name,A),n){const e=new Set,t=r=>{if(!e.has(r)){e.add(r),r.decoupled=!1;for(const e of r.dependencies.values())r.peerNames.has(e.name)||t(e)}};t(A)}else for(const t of e.dependencies)g(t,A)};for(const t of e.dependencies)g(t,i);return i},f=e=>e.substring(0,e.indexOf("@",1)),I=e=>{const t={name:e.name,identName:f(e.locator),references:new Set(e.references),dependencies:new Set},r=new Set([e]),A=(e,t,n)=>{const o=r.has(e);let i;if(t===e)i=n;else{const{name:t,references:r,locator:A}=e;i={name:t,identName:f(A),references:r,dependencies:new Set}}if(n.dependencies.add(i),!o){r.add(e);for(const t of e.dependencies.values())e.peerNames.has(t.name)||A(t,e,i);r.delete(e)}};for(const r of e.dependencies.values())A(r,e,t);return t},E=e=>{const t=new Map,r=new Set([e]),A=e=>{const r=(e=>`${e.name}@${e.ident}`)(e);let A=t.get(r);return A||(A={dependents:new Set,peerDependents:new Set},t.set(r,A)),A},n=(e,t)=>{const o=!!r.has(t);if(A(t).dependents.add(e.ident),!o){r.add(t);for(const e of t.dependencies.values())if(t.peerNames.has(e.name)){A(e).peerDependents.add(t.ident)}else n(t,e)}};for(const t of e.dependencies.values())e.peerNames.has(t.name)||n(e,t);return t},B=e=>{const t=e.indexOf("@",1),r=e.substring(0,t),A=e.substring(t+1);if("workspace:."===A)return".";if(A){const e=(A.indexOf("#")>0?A.split("#")[1]:A).replace("npm:","");return A.startsWith("virtual")?`v:${r}@${e}`:`${r}@${e}`}return""+r},y=e=>{let t=0;const r=(e,A,n="")=>{if(t>5e4||A.has(e))return"";t++;const o=Array.from(e.dependencies.values());let i="";A.add(e);for(let t=0;t":"")+(c!==s.name?`a:${s.name}:`:"")+B(s.locator)+(a?" "+a:"")}\n`,i+=r(s,A,`${n}${t5e4?"\nTree is too large, part of the tree has been dunped\n":"")};var m,w;!function(e){e.HARD="HARD",e.SOFT="SOFT"}(m||(m={})),function(e){e.WORKSPACES="workspaces",e.DEPENDENCIES="dependencies",e.NONE="none"}(w||(w={}));const Q=(e,t)=>{const{packageTree:r,hoistingLimits:A}=b(e,t),n=((e,t={})=>{const r=t.debugLevel||Number(process.env.NM_DEBUG_LEVEL||c.NONE),A={check:t.check||r>=c.INTENSIVE_CHECK,debugLevel:r,hoistingLimits:t.hoistingLimits||new Map};A.debugLevel>=c.PERF&&console.time("hoist");const n=C(e,A);if(u(n,[n],new Set([n.locator]),A),A.debugLevel>=c.PERF&&console.timeEnd("hoist"),A.debugLevel>=c.CHECK){const e=d(n);if(e)throw new Error(`${e}, after hoisting finished:\n${y(n)}`)}return A.debugLevel>=c.REASONS&&console.log(y(n)),I(n)})(r,{hoistingLimits:A});return v(e,n,t)},D=e=>`${e.name}@${e.reference}`;const b=(e,t)=>{const r=e.getDependencyTreeRoots(),A=new Map,n=new Map,s=e.getPackageInformation(e.topLevel);if(null===s)throw new Error("Assertion failed: Expected the top-level package to have been registered");const a=e.findPackageLocator(s.packageLocation);if(null===a)throw new Error("Assertion failed: Expected the top-level package to have a physical locator");const c=o.cS.toPortablePath(s.packageLocation),g=D(a);if(t.project){const e={children:new Map},r=t.project.cwd.split(o.y1.sep);for(const[A,n]of t.project.workspacesByCwd){const t=A.split(o.y1.sep).slice(r.length);let s=e;for(const e of t){let t=s.children.get(e);t||(t={children:new Map},s.children.set(e,t)),s=t}s.workspaceLocator={name:i.stringifyIdent(n.anchoredLocator),reference:n.anchoredLocator.reference}}const A=(e,t)=>{if(e.workspaceLocator){const r=D(t);let A=n.get(r);A||(A=new Set,n.set(r,A)),A.add(e.workspaceLocator)}for(const r of e.children.values())A(r,e.workspaceLocator||t)};for(const t of e.children.values())A(t,e.workspaceLocator)}else for(const e of r)if(e.name!==a.name||e.reference!==a.reference){let t=n.get(g);t||(t=new Set,n.set(g,t)),t.add(e)}const l={name:a.name,identName:a.name,reference:a.reference,peerNames:s.packagePeers,dependencies:new Set},u=new Map,h=(r,s,g,p,d,C,f)=>{var I,E;const B=((e,t)=>`${D(t)}:${e}`)(r,g);let y=u.get(B);const m=!!y;if(m||g.name!==a.name||g.reference!==a.reference||(y=l,u.set(B,l)),y||(y={name:r,identName:g.name,reference:g.reference,dependencies:new Set,peerNames:s.packagePeers},u.set(B,y)),f){const e=D({name:p.identName,reference:p.reference}),t=A.get(e)||new Set;A.set(e,t),t.add(y.name)}const Q=new Map(s.packageDependencies);if(t.project){const e=t.project.workspacesByCwd.get(o.cS.toPortablePath(s.packageLocation.slice(0,-1)));if(e){const t=new Set([...Array.from(e.manifest.peerDependencies.values(),e=>i.stringifyIdent(e)),...Array.from(e.manifest.peerDependenciesMeta.keys())]);for(const e of t)Q.has(e)||(Q.set(e,d.get(e)||null),y.peerNames.add(e))}}const b=D(g),v=n.get(b);if(v)for(const e of v)Q.set(e.name+"$wsroot$",e.reference);p.dependencies.add(y);const S=t.pnpifyFs||!function(e){let t=i.parseDescriptor(e);return i.isVirtualDescriptor(t)&&(t=i.devirtualizeDescriptor(t)),t.range.startsWith("portal:")}(B);if(!m&&S)for(const[r,A]of Q)if(null!==A){const n=e.getLocator(r,A),i=e.getLocator(r.replace("$wsroot$",""),A),s=e.getPackageInformation(i);if(null===s)throw new Error("Assertion failed: Expected the package to have been registered");const a=null===(I=t.hoistingLimitsByCwd)||void 0===I?void 0:I.get(C),g=o.y1.relative(c,o.cS.toPortablePath(s.packageLocation))||o.LZ.dot,l=null===(E=t.hoistingLimitsByCwd)||void 0===E?void 0:E.get(g),u=a===w.DEPENDENCIES||l===w.DEPENDENCIES||l===w.WORKSPACES;h(r,s,n,y,Q,g,u)}};return h(a.name,s,a,l,s.packageDependencies,o.LZ.dot,!1),{packageTree:l,hoistingLimits:A}};const v=(e,t,r)=>{const A=new Map,n=(t,A)=>{const{linkType:n,target:i}=function(e,t,r){const A=t.getLocator(e.name.replace("$wsroot$",""),e.reference),n=t.getPackageInformation(A);if(null===n)throw new Error("Assertion failed: Expected the package to be registered");let i,s;if(r.pnpifyFs)s=o.cS.toPortablePath(n.packageLocation),i=m.SOFT;else{const r=t.resolveVirtual&&e.reference&&e.reference.startsWith("virtual:")?t.resolveVirtual(n.packageLocation):n.packageLocation;s=o.cS.toPortablePath(r||n.packageLocation),i=n.linkType}return{linkType:i,target:s}}(t,e,r);return{locator:D(t),target:i,linkType:n,aliases:A}},s=e=>{const[t,r]=e.split("/");return r?{scope:(0,o.Zu)(t),name:(0,o.Zu)(r)}:{scope:null,name:(0,o.Zu)(t)}},a=new Set,c=(e,t)=>{if(!a.has(e)){a.add(e);for(const r of e.dependencies){if(r===e||e.identName.endsWith("$wsroot$")&&r.identName===e.identName.replace("$wsroot$",""))continue;const a=Array.from(r.references).sort(),g={name:r.identName,reference:a[0]},{name:l,scope:u}=s(r.name),h=u?[u,l]:[l],p=o.y1.join(t,"node_modules"),d=o.y1.join(p,...h),C=n(g,a.slice(1));if(!r.name.endsWith("$wsroot$")){const e=A.get(d);if(e){if(e.dirList)throw new Error(`Assertion failed: ${d} cannot merge dir node with leaf node`);{const t=i.parseLocator(e.locator),r=i.parseLocator(C.locator);if(e.linkType!==C.linkType)throw new Error(`Assertion failed: ${d} cannot merge nodes with different link types`);if(t.identHash!==r.identHash)throw new Error(`Assertion failed: ${d} cannot merge nodes with different idents ${i.stringifyLocator(t)} and ${i.stringifyLocator(r)}`);C.aliases=[...C.aliases,...e.aliases,i.parseLocator(e.locator).reference]}}A.set(d,C);const t=d.split("/"),r=t.indexOf("node_modules");let n=t.length-1;for(;r>=0&&n>r;){const e=o.cS.toPortablePath(t.slice(0,n).join(o.y1.sep)),r=(0,o.Zu)(t[n]),i=A.get(e);if(i){if(i.dirList){if(i.dirList.has(r))break;i.dirList.add(r)}}else A.set(e,{dirList:new Set([r])});n--}}c(r,C.linkType===m.SOFT?C.target:d)}}},g=n({name:t.name,reference:Array.from(t.references)[0]},[]),l=g.target;return A.set(l,g),c(t,l),A};var S=r(92659),k=r(32485),N=r(73632),F=r(46611),K=r(35691),M=r(43896),R=r(17674),x=r(53660),L=r(65281),P=r(11640),O=r(83228),U=r(58069),T=r.n(U),j=r(40822),Y=r(35747),G=r.n(Y);const H="node_modules";class J{constructor(e){this.opts=e,this.localStore=new Map,this.customData={store:new Map}}getCustomDataKey(){return JSON.stringify({name:"NodeModulesInstaller",version:1})}attachCustomData(e){this.customData=e}async installPackage(e,t){var r;const A=o.y1.resolve(t.packageFs.getRealPath(),t.prefixPath);let n=this.customData.store.get(e.locatorHash);if(void 0===n&&(n=await async function(e,t){var r;const A=null!==(r=await F.G.tryFind(t.prefixPath,{baseFs:t.packageFs}))&&void 0!==r?r:new F.G,n=new Set(["preinstall","install","postinstall"]);for(const e of A.scripts.keys())n.has(e)||A.scripts.delete(e);return{manifest:{bin:A.bin,os:A.os,cpu:A.cpu,scripts:A.scripts},misc:{extractHint:O.jsInstallUtils.getExtractHint(t),hasBindingGyp:O.jsInstallUtils.hasBindingGyp(t)}}}(0,t),e.linkType===k.Un.HARD&&this.customData.store.set(e.locatorHash,n)),!O.jsInstallUtils.checkAndReportManifestCompatibility(e,n,"link",{configuration:this.opts.project.configuration,report:this.opts.report}))return{packageLocation:null,buildDirective:null};const s=new Map,a=new Set;if(s.has(i.stringifyIdent(e))||s.set(i.stringifyIdent(e),e.reference),i.isVirtualLocator(e))for(const t of e.peerDependencies.values())s.set(i.stringifyIdent(t),null),a.add(i.stringifyIdent(t));const c={packageLocation:o.cS.fromPortablePath(A)+"/",packageDependencies:s,packagePeers:a,linkType:e.linkType,discardFromLookup:null!==(r=t.discardFromLookup)&&void 0!==r&&r};return this.localStore.set(e.locatorHash,{pkg:e,customPackageData:n,dependencyMeta:this.opts.project.getDependencyMeta(e,e.version),pnpNode:c}),{packageLocation:A,buildDirective:null}}async attachInternalDependencies(e,t){const r=this.localStore.get(e.locatorHash);if(void 0===r)throw new Error("Assertion failed: Expected information object to have been registered");for(const[e,A]of t){const t=i.areIdentsEqual(e,A)?A.reference:[i.requirableIdent(A),A.reference];r.pnpNode.packageDependencies.set(i.requirableIdent(e),t)}}async attachExternalDependents(e,t){throw new Error("External dependencies haven't been implemented for the node-modules linker")}async finalizeInstall(){if("node-modules"!==this.opts.project.configuration.get("nodeLinker"))return;const e=new R.p({baseFs:new x.A({libzip:await(0,L.getLibzipPromise)(),maxOpenFiles:80,readOnlyArchives:!0})});let t=await q(this.opts.project);if(null===t){const e=this.opts.project.configuration.get("bstatePath");await M.xfs.existsPromise(e)&&await M.xfs.unlinkPromise(e),t={locatorMap:new Map,binSymlinks:new Map,locationTree:new Map}}const r=new Map(this.opts.project.workspaces.map(e=>{var t,r;let A=this.opts.project.configuration.get("nmHoistingLimits");try{A=N.validateEnum(w,null!==(r=null===(t=e.manifest.installConfig)||void 0===t?void 0:t.hoistingLimits)&&void 0!==r?r:A)}catch(t){const r=i.prettyWorkspace(this.opts.project.configuration,e);this.opts.report.reportWarning(S.b.INVALID_MANIFEST,`${r}: Invalid 'installConfig.hoistingLimits' value. Expected one of ${Object.values(w).join(", ")}, using default: "${A}"`)}return[e.relativeCwd,A]})),A=(e=>{const t=new Map;for(const[r,A]of e.entries())if(!A.dirList){let e=t.get(A.locator);e||(e={target:A.target,linkType:A.linkType,locations:[],aliases:A.aliases},t.set(A.locator,e)),e.locations.push(r)}for(const e of t.values())e.locations=e.locations.sort((e,t)=>{const r=e.split(o.y1.delimiter).length,A=t.split(o.y1.delimiter).length;return r!==A?A-r:t.localeCompare(e)});return t})(Q({VERSIONS:{std:1},topLevel:{name:null,reference:null},getLocator:(e,t)=>Array.isArray(t)?{name:t[0],reference:t[1]}:{name:e,reference:t},getDependencyTreeRoots:()=>this.opts.project.workspaces.map(e=>{const t=e.anchoredLocator;return{name:i.stringifyIdent(e.locator),reference:t.reference}}),getPackageInformation:e=>{const t=null===e.reference?this.opts.project.topLevelWorkspace.anchoredLocator:i.makeLocator(i.parseIdent(e.name),e.reference),r=this.localStore.get(t.locatorHash);if(void 0===r)throw new Error("Assertion failed: Expected the package reference to have been registered");return r.pnpNode},findPackageLocator:e=>{const t=this.opts.project.tryWorkspaceByCwd(o.cS.toPortablePath(e));if(null!==t){const e=t.anchoredLocator;return{name:i.stringifyIdent(e),reference:e.reference}}throw new Error("Assertion failed: Unimplemented")},resolveToUnqualified:()=>{throw new Error("Assertion failed: Unimplemented")},resolveUnqualified:()=>{throw new Error("Assertion failed: Unimplemented")},resolveRequest:()=>{throw new Error("Assertion failed: Unimplemented")},resolveVirtual:e=>o.cS.fromPortablePath(R.p.resolveVirtual(o.cS.toPortablePath(e)))},{pnpifyFs:!1,hoistingLimitsByCwd:r,project:this.opts.project}));await async function(e,t,{baseFs:r,project:A,report:n,loadManifest:s}){const a=o.y1.join(A.cwd,H),{locationTree:c,binSymlinks:g}=function(e,t){const r=new Map([...e]),A=new Map([...t]);for(const[t,r]of e){const e=o.y1.join(t,H);if(!M.xfs.existsSync(e)){r.children.delete(H);for(const t of A.keys())null!==o.y1.contains(e,t)&&A.delete(t)}}return{locationTree:r,binSymlinks:A}}(e.locationTree,e.binSymlinks),l=V(t,{skipPrefix:A.cwd}),u=[],h=async({srcDir:e,dstDir:t,linkType:A})=>{const n=(async()=>{try{A===k.Un.SOFT?(await M.xfs.mkdirPromise(o.y1.dirname(t),{recursive:!0}),await X(o.y1.resolve(e),t)):await _(t,e,{baseFs:r})}catch(r){throw r.message=`While persisting ${e} -> ${t} ${r.message}`,r}finally{I.tick()}})().then(()=>u.splice(u.indexOf(n),1));u.push(n),u.length>4&&await Promise.race(u)},p=async(e,t,r)=>{const A=(async()=>{const A=async(e,t,r)=>{try{r&&r.innerLoop||await M.xfs.mkdirPromise(t,{recursive:!0});const n=await M.xfs.readdirPromise(e,{withFileTypes:!0});for(const i of n){if(!(r&&r.innerLoop||".bin"!==i.name))continue;const n=o.y1.join(e,i.name),s=o.y1.join(t,i.name);i.isDirectory()?(i.name!==H||r&&r.innerLoop)&&(await M.xfs.mkdirPromise(s,{recursive:!0}),await A(n,s,{innerLoop:!0})):await M.xfs.copyFilePromise(n,s,G().constants.COPYFILE_FICLONE)}}catch(A){throw r&&r.innerLoop||(A.message=`While cloning ${e} -> ${t} ${A.message}`),A}finally{r&&r.innerLoop||I.tick()}};await A(e,t,r)})().then(()=>u.splice(u.indexOf(A),1));u.push(A),u.length>4&&await Promise.race(u)},d=async(e,t,r)=>{if(r)for(const[A,n]of t.children){const t=r.children.get(A);await d(o.y1.join(e,A),n,t)}else t.children.has(H)&&await z(o.y1.join(e,H),{contentsOnly:!1}),await z(e,{contentsOnly:e===a})};for(const[e,t]of c){const r=l.get(e);for(const[A,n]of t.children){if("."===A)continue;const t=r?r.children.get(A):r;await d(o.y1.join(e,A),n,t)}}const C=async(e,t,r)=>{if(r){$(t.locator,r.locator)||await z(e,{contentsOnly:t.linkType===k.Un.HARD});for(const[A,n]of t.children){const t=r.children.get(A);await C(o.y1.join(e,A),n,t)}}else t.children.has(H)&&await z(o.y1.join(e,H),{contentsOnly:!0}),await z(e,{contentsOnly:t.linkType===k.Un.HARD})};for(const[e,t]of l){const r=c.get(e);for(const[A,n]of t.children){if("."===A)continue;const t=r?r.children.get(A):r;await C(o.y1.join(e,A),n,t)}}const f=[];for(const[r,{locations:n}]of e.locatorMap.entries())for(const e of n){const{locationRoot:n,segments:i}=W(e,{skipPrefix:A.cwd});let s=l.get(n),a=n;if(s){for(const e of i)if(a=o.y1.join(a,e),s=s.children.get(e),!s)break;if(s&&!$(s.locator,r)){const e=t.get(s.locator),r=e.target,A=a,n=e.linkType;r!==A&&f.push({srcDir:r,dstDir:A,linkType:n})}}}for(const[e,{locations:r}]of t.entries())for(const n of r){const{locationRoot:r,segments:i}=W(n,{skipPrefix:A.cwd});let s=c.get(r),a=l.get(r),g=r;const u=t.get(e),h=u.target,p=n;if(h===p)continue;const d=u.linkType;for(const e of i)a=a.children.get(e);if(s){for(const e of i)if(g=o.y1.join(g,e),s=s.children.get(e),!s){f.push({srcDir:h,dstDir:p,linkType:d});break}}else f.push({srcDir:h,dstDir:p,linkType:d})}const I=K.yG.progressViaCounter(f.length),E=n.reportProgress(I);try{const e=new Map;for(const t of f)t.linkType!==k.Un.SOFT&&e.has(t.srcDir)||(e.set(t.srcDir,t.dstDir),await h({...t}));await Promise.all(u),u.length=0;for(const t of f){const r=e.get(t.srcDir);t.linkType!==k.Un.SOFT&&t.dstDir!==r&&await p(r,t.dstDir)}await Promise.all(u),await M.xfs.mkdirPromise(a,{recursive:!0});const r=await async function(e,t,r,{loadManifest:A}){const n=new Map;for(const[t,{locations:r}]of e){const e=Z(t)?null:await A(t,r[0]),i=new Map;if(e)for(const[t,A]of e.bin){const e=o.y1.join(r[0],A);""!==A&&M.xfs.existsSync(e)&&i.set(t,A)}n.set(t,i)}const i=new Map,s=(e,t,A)=>{const a=new Map,c=o.y1.contains(r,e);if(A.locator&&null!==c){const t=n.get(A.locator);for(const[r,A]of t){const t=o.y1.join(e,o.cS.toPortablePath(A));a.set((0,o.Zu)(r),t)}for(const[t,r]of A.children){const A=o.y1.join(e,t),n=s(A,A,r);n.size>0&&i.set(e,new Map([...i.get(e)||new Map,...n]))}}else for(const[r,n]of A.children){const A=s(o.y1.join(e,r),t,n);for(const[e,t]of A)a.set(e,t)}return a};for(const[e,r]of t){const t=s(e,e,r);t.size>0&&i.set(e,new Map([...i.get(e)||new Map,...t]))}return i}(t,l,A.cwd,{loadManifest:s});await async function(e,t){for(const r of e.keys())if(!t.has(r)){const e=o.y1.join(r,H,".bin");await M.xfs.removePromise(e)}for(const[r,A]of t){const t=o.y1.join(r,H,".bin"),n=e.get(r)||new Map;await M.xfs.mkdirPromise(t,{recursive:!0});for(const e of n.keys())A.has(e)||(await M.xfs.removePromise(o.y1.join(t,e)),"win32"===process.platform&&await M.xfs.removePromise(o.y1.join(t,(0,o.Zu)(e+".cmd"))));for(const[e,r]of A){const A=n.get(e),i=o.y1.join(t,e);A!==r&&("win32"===process.platform?await T()(o.cS.fromPortablePath(r),o.cS.fromPortablePath(i),{createPwshFile:!1}):(await M.xfs.removePromise(i),await X(r,i),await M.xfs.chmodPromise(r,493)))}}}(g,r),await async function(e,t,r){let A="";A+="# Warning: This file is automatically generated. Removing it is fine, but will\n",A+="# cause your node_modules installation to become invalidated.\n",A+="\n",A+="__metadata:\n",A+=" version: 1\n";const n=Array.from(t.keys()).sort(),s=i.stringifyLocator(e.topLevelWorkspace.anchoredLocator);for(const i of n){const n=t.get(i);A+="\n",A+=JSON.stringify(i)+":\n",A+=" locations:\n";for(const t of n.locations){const r=o.y1.contains(e.cwd,t);if(null===r)throw new Error(`Assertion failed: Expected the path to be within the project (${t})`);A+=` - ${JSON.stringify(r)}\n`}if(n.aliases.length>0){A+=" aliases:\n";for(const e of n.aliases)A+=` - ${JSON.stringify(e)}\n`}if(i===s&&r.size>0){A+=" bin:\n";for(const[t,n]of r){const r=o.y1.contains(e.cwd,t);if(null===r)throw new Error(`Assertion failed: Expected the path to be within the project (${t})`);A+=` ${JSON.stringify(r)}:\n`;for(const[e,r]of n){const n=o.y1.relative(o.y1.join(t,H),r);A+=` ${JSON.stringify(e)}: ${JSON.stringify(n)}\n`}}}}const a=e.cwd,c=o.y1.join(a,H,".yarn-state.yml");await M.xfs.changeFilePromise(c,A,{automaticNewlines:!0})}(A,t,r)}finally{E.stop()}}(t,A,{baseFs:e,project:this.opts.project,report:this.opts.report,loadManifest:async e=>{const t=i.parseLocator(e),r=this.localStore.get(t.locatorHash);if(void 0===r)throw new Error("Assertion failed: Expected the slot to exist");return r.customPackageData.manifest}});const n=[];for(const[e,t]of A.entries()){if(Z(e))continue;const r=i.parseLocator(e),A=this.localStore.get(r.locatorHash);if(void 0===A)throw new Error("Assertion failed: Expected the slot to exist");const o=O.jsInstallUtils.extractBuildScripts(A.pkg,A.customPackageData,A.dependencyMeta,{configuration:this.opts.project.configuration,report:this.opts.report});0!==o.length&&n.push({buildLocations:t.locations,locatorHash:r.locatorHash,buildDirective:o})}return{customData:this.customData,records:n}}}async function q(e,{unrollAliases:t=!1}={}){const r=e.cwd,A=o.y1.join(r,H,".yarn-state.yml");if(!M.xfs.existsSync(A))return null;const n=(0,P.parseSyml)(await M.xfs.readFilePromise(A,"utf8"));if(n.__metadata.version>1)return null;const s=new Map,a=new Map;delete n.__metadata;for(const[e,A]of Object.entries(n)){const n=A.locations.map(e=>o.y1.join(r,e)),c=A.bin;if(c)for(const[e,t]of Object.entries(c)){const A=o.y1.join(r,o.cS.toPortablePath(e)),n=N.getMapWithDefault(a,A);for(const[e,r]of Object.entries(t))n.set((0,o.Zu)(e),o.cS.toPortablePath([A,H,r].join(o.y1.delimiter)))}if(s.set(e,{target:o.LZ.dot,linkType:k.Un.HARD,locations:n,aliases:A.aliases||[]}),t&&A.aliases)for(const t of A.aliases){const{scope:r,name:A}=i.parseLocator(e),a=i.makeLocator(i.makeIdent(r,A),t),c=i.stringifyLocator(a);s.set(c,{target:o.LZ.dot,linkType:k.Un.HARD,locations:n,aliases:[]})}}return{locatorMap:s,binSymlinks:a,locationTree:V(s,{skipPrefix:e.cwd})}}const z=async(e,t)=>{if(e.split(o.y1.sep).indexOf(H)<0)throw new Error("Assertion failed: trying to remove dir that doesn't contain node_modules: "+e);try{if(!t.innerLoop){if((await M.xfs.lstatPromise(e)).isSymbolicLink())return void await M.xfs.unlinkPromise(e)}const r=await M.xfs.readdirPromise(e,{withFileTypes:!0});for(const A of r){const r=o.y1.join(e,(0,o.Zu)(A.name));A.isDirectory()?(A.name!==H||t&&t.innerLoop)&&await z(r,{innerLoop:!0,contentsOnly:!1}):await M.xfs.unlinkPromise(r)}t.contentsOnly||await M.xfs.rmdirPromise(e)}catch(e){if("ENOENT"!==e.code&&"ENOTEMPTY"!==e.code)throw e}},W=(e,{skipPrefix:t})=>{const r=o.y1.contains(t,e);if(null===r)throw new Error(`Assertion failed: Cannot process a path that isn't part of the requested prefix (${e} isn't within ${t})`);const A=r.split(o.y1.sep).filter(e=>""!==e),n=A.indexOf(H),i=A.slice(0,n).join(o.y1.sep);return{locationRoot:o.y1.join(t,i),segments:A.slice(n)}},V=(e,{skipPrefix:t})=>{const r=new Map;if(null===e)return r;const A=()=>({children:new Map,linkType:k.Un.HARD});for(const[n,i]of e.entries()){if(i.linkType===k.Un.SOFT){if(null!==o.y1.contains(t,i.target)){const e=N.getFactoryWithDefault(r,i.target,A);e.locator=n,e.linkType=i.linkType}}for(const e of i.locations){const{locationRoot:o,segments:s}=W(e,{skipPrefix:t});let a=N.getFactoryWithDefault(r,o,A);for(let e=0;e{let r;try{"win32"===process.platform&&(r=M.xfs.lstatSync(e))}catch(e){}"win32"!=process.platform||r&&!r.isDirectory()?M.xfs.symlinkPromise(o.y1.relative(o.y1.dirname(t),e),t):M.xfs.symlinkPromise(e,t,"junction")},_=async(e,t,{baseFs:r,innerLoop:A})=>{await M.xfs.mkdirPromise(e,{recursive:!0});const n=await r.readdirPromise(t,{withFileTypes:!0}),i=async(e,t,A)=>{if(A.isFile()){const A=await r.lstatPromise(t);await r.copyFilePromise(t,e);const n=511&A.mode;420!==n&&await M.xfs.chmodPromise(e,n)}else{if(!A.isSymbolicLink())throw new Error(`Unsupported file type (file: ${t}, mode: 0o${await M.xfs.statSync(t).mode.toString(8).padStart(6,"0")})`);{const A=await r.readlinkPromise(t);await X(o.y1.resolve(o.y1.dirname(e),A),e)}}};for(const s of n){const n=o.y1.join(t,(0,o.Zu)(s.name)),a=o.y1.join(e,(0,o.Zu)(s.name));s.isDirectory()?(s.name!==H||A)&&await _(a,n,{baseFs:r,innerLoop:!0}):await i(a,n,s)}};function Z(e){let t=i.parseDescriptor(e);return i.isVirtualDescriptor(t)&&(t=i.devirtualizeDescriptor(t)),t.range.startsWith("link:")}const $=(e,t)=>{if(!e||!t)return e===t;let r=i.parseLocator(e);i.isVirtualLocator(r)&&(r=i.devirtualizeLocator(r));let A=i.parseLocator(t);return i.isVirtualLocator(A)&&(A=i.devirtualizeLocator(A)),i.areLocatorsEqual(r,A)};var ee=r(34432);class te extends O.PnpLinker{constructor(){super(...arguments),this.mode="loose"}makeInstaller(e){return new re(e)}}class re extends O.PnpInstaller{constructor(){super(...arguments),this.mode="loose"}async finalizeInstallWithPnp(e){if(this.opts.project.configuration.get("pnpMode")!==this.mode)return;const t=new R.p({baseFs:new x.A({libzip:await(0,L.getLibzipPromise)(),maxOpenFiles:80,readOnlyArchives:!0})}),r=(0,ee.oC)(e,this.opts.project.cwd,t),A=Q(r,{pnpifyFs:!1,project:this.opts.project}),n=new Map;e.fallbackPool=n;const s=(e,t)=>{const r=i.parseLocator(t.locator),A=i.stringifyIdent(r);A===e?n.set(e,r.reference):n.set(e,[A,r.reference])},a=o.y1.join(this.opts.project.cwd,o.QS.nodeModules),c=A.get(a);if(void 0===c)throw new Error("Assertion failed: Expected a root junction point");if("target"in c)throw new Error("Assertion failed: Expected the root junction point to be a directory");for(const e of c.dirList){const t=o.y1.join(a,e),r=A.get(t);if(void 0===r)throw new Error("Assertion failed: Expected the child to have been registered");if("target"in r)s(e,r);else for(const n of r.dirList){const r=o.y1.join(t,n),i=A.get(r);if(void 0===i)throw new Error("Assertion failed: Expected the subchild to have been registered");if(!("target"in i))throw new Error("Assertion failed: Expected the leaf junction to be a package");s(`${e}/${n}`,i)}}return super.finalizeInstallWithPnp(e)}}const Ae=e=>o.y1.join(e.cwd,".pnp.js"),ne={configuration:{nmHoistingLimits:{description:"Prevent packages can be hoisted past specific levels",type:n.a2.STRING,values:[w.WORKSPACES,w.DEPENDENCIES,w.NONE],default:"none"}},linkers:[class{supportsPackage(e,t){return"node-modules"===t.project.configuration.get("nodeLinker")}async findPackageLocation(e,t){const r=t.project.tryWorkspaceByLocator(e);if(r)return r.cwd;const A=await q(t.project,{unrollAliases:!0});if(null===A)throw new j.UsageError("Couldn't find the node_modules state file - running an install might help (findPackageLocation)");const n=A.locatorMap.get(i.stringifyLocator(e));if(!n){const r=new j.UsageError(`Couldn't find ${i.prettyLocator(t.project.configuration,e)} in the currently installed node_modules map - running an install might help`);throw r.code="LOCATOR_NOT_INSTALLED",r}return n.locations[0]}async findPackageLocator(e,t){const r=await q(t.project,{unrollAliases:!0});if(null===r)return null;const{locationRoot:A,segments:n}=W(o.y1.resolve(e),{skipPrefix:t.project.cwd});let s=r.locationTree.get(A);if(!s)return null;let a=s.locator;for(const e of n){if(s=s.children.get(e),!s)break;a=s.locator||a}return i.parseLocator(a)}makeInstaller(e){return new J(e)}},te]}},8190:(e,t,r)=>{"use strict";r.r(t),r.d(t,{default:()=>X});var A,n,o=r(39922),i=r(36370),s=r(25413),a=r(85824),c=r(62152),g=r(35691),l=r(92659),u=r(85875),h=r(15815),p=r(14224),d=r(40822);!function(e){e.All="all",e.Production="production",e.Development="development"}(A||(A={})),function(e){e.Info="info",e.Low="low",e.Moderate="moderate",e.High="high",e.Critical="critical"}(n||(n={}));var C=r(54143),f=r(73632),I=r(71643);const E=[n.Info,n.Low,n.Moderate,n.High,n.Critical];function B(e,t){const r=[],A=new Set,n=e=>{A.has(e)||(A.add(e),r.push(e))};for(const e of t)n(e);const o=new Set;for(;r.length>0;){const t=r.shift(),A=e.storedResolutions.get(t);if(void 0===A)throw new Error("Assertion failed: Expected the resolution to have been registered");const i=e.storedPackages.get(A);if(i){o.add(t);for(const e of i.dependencies.values())n(e.descriptorHash)}}return o}function y(e,t,{all:r}){const A=r?e.workspaces:[t],n=A.map(e=>e.manifest),o=new Set(n.map(e=>[...e.dependencies].map(([e,t])=>e)).flat()),i=new Set(n.map(e=>[...e.devDependencies].map(([e,t])=>e)).flat()),s=A.map(e=>[...e.dependencies.values()]).flat(),a=s.filter(e=>o.has(e.identHash)).map(e=>e.descriptorHash),c=s.filter(e=>i.has(e.identHash)).map(e=>e.descriptorHash),g=B(e,a),l=B(e,c);return u=l,h=g,new Set([...u].filter(e=>!h.has(e)));var u,h}function m(e){const t={};for(const r of e)t[C.stringifyIdent(r)]=C.parseRange(r.range).selector;return t}function w(e){if(void 0===e)return new Set;const t=E.indexOf(e),r=E.slice(t);return new Set(r)}function Q(e,t){var r;const A=function(e,t){const r=w(t),A={};for(const t of r)A[t]=e[t];return A}(e,t);for(const e of Object.keys(A))if(null!==(r=A[e])&&void 0!==r&&r)return!0;return!1}class D extends s.BaseCommand{constructor(){super(...arguments),this.all=!1,this.recursive=!1,this.environment=A.All,this.json=!1,this.severity=n.Info}async execute(){const e=await o.VK.find(this.context.cwd,this.context.plugins),{project:t,workspace:r}=await a.I.find(e,this.context.cwd);if(!r)throw new s.WorkspaceRequiredError(t.cwd,this.context.cwd);await t.restoreInstallState();const n=function(e,t,{all:r,environment:n}){const o=r?e.workspaces:[t],i=[];if([A.All,A.Production].includes(n))for(const e of o)for(const t of e.manifest.dependencies.values())i.push(t);const s=[];if([A.All,A.Development].includes(n))for(const e of o)for(const t of e.manifest.devDependencies.values())s.push(t);return m([...i,...s].filter(e=>null===C.parseRange(e.range).protocol))}(t,r,{all:this.all,environment:this.environment}),i=function(e,t,{all:r}){var A;const n=y(e,t,{all:r}),o={};for(const t of e.storedPackages.values())o[C.stringifyIdent(t)]={version:null!==(A=t.version)&&void 0!==A?A:"0.0.0",integrity:t.identHash,requires:m(t.dependencies.values()),dev:n.has(C.convertLocatorToDescriptor(t).descriptorHash)};return o}(t,r,{all:this.all});if(!this.recursive)for(const e of Object.keys(i))Object.prototype.hasOwnProperty.call(n,e)?i[e].requires={}:delete i[e];const d={requires:n,dependencies:i},E=p.npmConfigUtils.getPublishRegistry(r.manifest,{configuration:e});let B;const D=await c.h.start({configuration:e,stdout:this.context.stdout},async()=>{try{B=await p.npmHttpUtils.post("/-/npm/v1/security/audits/quick",d,{authType:p.npmHttpUtils.AuthType.NO_AUTH,configuration:e,jsonResponse:!0,registry:E})}catch(e){throw"HTTPError"!==e.name?e:new g.lk(l.b.EXCEPTION,e.toString())}});if(D.hasErrors())return D.exitCode();const b=Q(B.metadata.vulnerabilities,this.severity);if(!this.json&&b)return u.emitTree(function(e,t){const r={},A={children:r};let n=Object.values(e.advisories);if(null!=t){const e=w(t);n=n.filter(t=>e.has(t.severity))}for(const e of f.sortMap(n,e=>e.module_name))r[e.module_name]={label:e.module_name,value:I.tuple(I.Type.RANGE,e.findings.map(e=>e.version).join(", ")),children:{Issue:{label:"Issue",value:I.tuple(I.Type.NO_HINT,e.title)},URL:{label:"URL",value:I.tuple(I.Type.URL,e.url)},Severity:{label:"Severity",value:I.tuple(I.Type.NO_HINT,e.severity)},"Vulnerable Versions":{label:"Vulnerable Versions",value:I.tuple(I.Type.RANGE,e.vulnerable_versions)},"Patched Versions":{label:"Patched Versions",value:I.tuple(I.Type.RANGE,e.patched_versions)},Via:{label:"Via",value:I.tuple(I.Type.NO_HINT,Array.from(new Set(e.findings.map(e=>e.paths).flat().map(e=>e.split(">")[0]))).join(", "))},Recommendation:{label:"Recommendation",value:I.tuple(I.Type.NO_HINT,e.recommendation.replace(/\n/g," "))}}};return A}(B,this.severity),{configuration:e,json:this.json,stdout:this.context.stdout,separators:2}),1;return(await h.Pk.start({configuration:e,includeFooter:!1,json:this.json,stdout:this.context.stdout},async e=>{e.reportJson(B),b||e.reportInfo(l.b.EXCEPTION,"No audit suggestions")})).exitCode()}}D.usage=d.Command.Usage({description:"perform a vulnerability audit against the installed packages",details:`\n This command checks for known security reports on the packages you use. The reports are by default extracted from the npm registry, and may or may not be relevant to your actual program (not all vulnerabilities affect all code paths).\n\n For consistency with our other commands the default is to only check the direct dependencies for the active workspace. To extend this search to all workspaces, use \`-A,--all\`. To extend this search to both direct and transitive dependencies, use \`-R,--recursive\`.\n\n Applying the \`--severity\` flag will limit the audit table to vulnerabilities of the corresponding severity and above. Valid values are ${E.map(e=>`\`${e}\``).join(", ")}.\n\n If the \`--json\` flag is set, Yarn will print the output exactly as received from the registry. Regardless of this flag, the process will exit with a non-zero exit code if a report is found for the selected packages.\n\n To understand the dependency tree requiring vulnerable packages, check the raw report with the \`--json\` flag or use \`yarn why \` to get more information as to who depends on them.\n `,examples:[["Checks for known security issues with the installed packages. The output is a list of known issues.","yarn npm audit"],["Audit dependencies in all workspaces","yarn npm audit --all"],["Limit auditing to `dependencies` (excludes `devDependencies`)","yarn npm audit --environment production"],["Show audit report as valid JSON","yarn npm audit --json"],["Audit all direct and transitive dependencies","yarn npm audit --recursive"],["Output moderate (or more severe) vulnerabilities","yarn npm audit --severity moderate"]]}),(0,i.gn)([d.Command.Boolean("-A,--all")],D.prototype,"all",void 0),(0,i.gn)([d.Command.Boolean("-R,--recursive")],D.prototype,"recursive",void 0),(0,i.gn)([d.Command.String("--environment")],D.prototype,"environment",void 0),(0,i.gn)([d.Command.Boolean("--json")],D.prototype,"json",void 0),(0,i.gn)([d.Command.String("--severity")],D.prototype,"severity",void 0),(0,i.gn)([d.Command.Path("npm","audit")],D.prototype,"execute",null);var b=r(85622),v=r.n(b),S=r(53887),k=r.n(S),N=r(31669);class F extends s.BaseCommand{constructor(){super(...arguments),this.json=!1}async execute(){const e=await o.VK.find(this.context.cwd,this.context.plugins),{project:t}=await a.I.find(e,this.context.cwd),r=void 0!==this.fields?new Set(["name",...this.fields.split(/\s*,\s*/)]):null,A=[];let n=!1;const i=await h.Pk.start({configuration:e,includeFooter:!1,json:this.json,stdout:this.context.stdout},async o=>{for(const i of this.packages){let s;if("."===i){const e=t.topLevelWorkspace;if(!e.manifest.name)throw new d.UsageError("Missing 'name' field in "+v().join(e.cwd,"package.json"));s=C.makeDescriptor(e.manifest.name,"unknown")}else s=C.parseDescriptor(i);const a=p.npmHttpUtils.getIdentUrl(s);let c;try{c=K(await p.npmHttpUtils.get(a,{configuration:e,ident:s,jsonResponse:!0}))}catch(e){throw"HTTPError"!==e.name?e:404===e.response.statusCode?new g.lk(l.b.EXCEPTION,"Package not found"):new g.lk(l.b.EXCEPTION,e.toString())}const u=Object.keys(c.versions).sort(k().compareLoose);let h=c["dist-tags"].latest||u[u.length-1];if(k().validRange(s.range)){const t=k().maxSatisfying(u,s.range);null!==t?h=t:(o.reportWarning(l.b.UNNAMED,`Unmet range ${C.prettyRange(e,s.range)}; falling back to the latest version`),n=!0)}else"unknown"!==s.range&&(o.reportWarning(l.b.UNNAMED,`Invalid range ${C.prettyRange(e,s.range)}; falling back to the latest version`),n=!0);const f=c.versions[h],I={...c,...f,version:h,versions:u};let E;if(null!==r){E={};for(const t of r){const r=I[t];void 0!==r?E[t]=r:(o.reportWarning(l.b.EXCEPTION,`The '${t}' field doesn't exist inside ${C.prettyIdent(e,s)}'s informations`),n=!0)}}else this.json||(delete I.dist,delete I.readme,delete I.users),E=I;o.reportJson(E),this.json||A.push(E)}});N.inspect.styles.name="cyan";for(const e of A)(e!==A[0]||n)&&this.context.stdout.write("\n"),this.context.stdout.write((0,N.inspect)(e,{depth:1/0,colors:!0,compact:!1})+"\n");return i.exitCode()}}function K(e){if(Array.isArray(e)){const t=[];for(let r of e)r=K(r),r&&t.push(r);return t}if("object"==typeof e&&null!==e){const t={};for(const r of Object.keys(e)){if(r.startsWith("_"))continue;const A=K(e[r]);A&&(t[r]=A)}return t}return e||null}F.usage=d.Command.Usage({category:"Npm-related commands",description:"show information about a package",details:"\n This command will fetch information about a package from the npm registry, and prints it in a tree format.\n\n The package does not have to be installed locally, but needs to have been published (in particular, local changes will be ignored even for workspaces).\n\n Append `@` to the package argument to provide information specific to the latest version that satisfies the range. If the range is invalid or if there is no version satisfying the range, the command will print a warning and fall back to the latest version.\n\n If the `-f,--fields` option is set, it's a comma-separated list of fields which will be used to only display part of the package informations.\n\n By default, this command won't return the `dist`, `readme`, and `users` fields, since they are often very long. To explicitly request those fields, explicitly list them with the `--fields` flag or request the output in JSON mode.\n ",examples:[["Show all available information about react (except the `dist`, `readme`, and `users` fields)","yarn npm info react"],["Show all available information about react as valid JSON (including the `dist`, `readme`, and `users` fields)","yarn npm info react --json"],["Show all available information about react 16.12.0","yarn npm info react@16.12.0"],["Show the description of react","yarn npm info react --fields description"],["Show all available versions of react","yarn npm info react --fields versions"],["Show the readme of react","yarn npm info react --fields readme"],["Show a few fields of react","yarn npm info react --fields homepage,repository"]]}),(0,i.gn)([d.Command.Rest()],F.prototype,"packages",void 0),(0,i.gn)([d.Command.String("-f,--fields",{description:"A comma-separated list of manifest fields that should be displayed"})],F.prototype,"fields",void 0),(0,i.gn)([d.Command.Boolean("--json",{description:"Format the output as an NDJSON stream"})],F.prototype,"json",void 0),(0,i.gn)([d.Command.Path("npm","info")],F.prototype,"execute",null);var M=r(61899);class R extends s.BaseCommand{constructor(){super(...arguments),this.publish=!1}async execute(){const e=await o.VK.find(this.context.cwd,this.context.plugins),t=await x({configuration:e,cwd:this.context.cwd,publish:this.publish,scope:this.scope});return(await h.Pk.start({configuration:e,stdout:this.context.stdout},async r=>{const A=await async function({registry:e,report:t,stdin:r,stdout:A}){if(process.env.TEST_ENV)return{name:process.env.TEST_NPM_USER||"",password:process.env.TEST_NPM_PASSWORD||""};t.reportInfo(l.b.UNNAMED,"Logging in to "+e);let n=!1;e.match(/^https:\/\/npm\.pkg\.github\.com(\/|$)/)&&(t.reportInfo(l.b.UNNAMED,"You seem to be using the GitHub Package Registry. Tokens must be generated with the 'repo', 'write:packages', and 'read:packages' permissions."),n=!0);t.reportSeparator();const{username:o,password:i}=await(0,M.prompt)([{type:"input",name:"username",message:"Username:",required:!0,onCancel:()=>process.exit(130),stdin:r,stdout:A},{type:"password",name:"password",message:n?"Token:":"Password:",required:!0,onCancel:()=>process.exit(130),stdin:r,stdout:A}]);return t.reportSeparator(),{name:o,password:i}}({registry:t,report:r,stdin:this.context.stdin,stdout:this.context.stdout}),n="/-/user/org.couchdb.user:"+encodeURIComponent(A.name),i=await p.npmHttpUtils.put(n,A,{attemptedAs:A.name,configuration:e,registry:t,jsonResponse:!0,authType:p.npmHttpUtils.AuthType.NO_AUTH});return await async function(e,t,{configuration:r,scope:A}){const n=e=>r=>{const A=f.isIndexableObject(r)?r:{},n=A[e],o=f.isIndexableObject(n)?n:{};return{...A,[e]:{...o,npmAuthToken:t}}},i=A?{npmScopes:n(A)}:{npmRegistries:n(e)};return await o.VK.updateHomeConfiguration(i)}(t,i.token,{configuration:e,scope:this.scope}),r.reportInfo(l.b.UNNAMED,"Successfully logged in")})).exitCode()}}async function x({scope:e,publish:t,configuration:r,cwd:A}){return e&&t?p.npmConfigUtils.getScopeRegistry(e,{configuration:r,type:p.npmConfigUtils.RegistryType.PUBLISH_REGISTRY}):e?p.npmConfigUtils.getScopeRegistry(e,{configuration:r}):t?p.npmConfigUtils.getPublishRegistry((await(0,s.openWorkspace)(r,A)).manifest,{configuration:r}):p.npmConfigUtils.getDefaultRegistry({configuration:r})}R.usage=d.Command.Usage({category:"Npm-related commands",description:"store new login info to access the npm registry",details:"\n This command will ask you for your username, password, and 2FA One-Time-Password (when it applies). It will then modify your local configuration (in your home folder, never in the project itself) to reference the new tokens thus generated.\n\n Adding the `-s,--scope` flag will cause the authentication to be done against whatever registry is configured for the associated scope (see also `npmScopes`).\n\n Adding the `--publish` flag will cause the authentication to be done against the registry used when publishing the package (see also `publishConfig.registry` and `npmPublishRegistry`).\n ",examples:[["Login to the default registry","yarn npm login"],["Login to the registry linked to the @my-scope registry","yarn npm login --scope my-scope"],["Login to the publish registry for the current package","yarn npm login --publish"]]}),(0,i.gn)([d.Command.String("-s,--scope",{description:"Login to the registry configured for a given scope"})],R.prototype,"scope",void 0),(0,i.gn)([d.Command.Boolean("--publish",{description:"Login to the publish registry"})],R.prototype,"publish",void 0),(0,i.gn)([d.Command.Path("npm","login")],R.prototype,"execute",null);const L=new Set(["npmAuthIdent","npmAuthToken"]);class P extends s.BaseCommand{constructor(){super(...arguments),this.publish=!1,this.all=!1}async execute(){const e=await o.VK.find(this.context.cwd,this.context.plugins),t=async()=>{var t;const r=await x({configuration:e,cwd:this.context.cwd,publish:this.publish,scope:this.scope}),A=await o.VK.find(this.context.cwd,this.context.plugins),n=C.makeIdent(null!==(t=this.scope)&&void 0!==t?t:null,"pkg");return!p.npmConfigUtils.getAuthConfiguration(r,{configuration:A,ident:n}).get("npmAuthToken")};return(await h.Pk.start({configuration:e,stdout:this.context.stdout},async r=>{if(this.all&&(await async function(){const e=e=>{let t=!1;const r=f.isIndexableObject(e)?{...e}:{};r.npmAuthToken&&(delete r.npmAuthToken,t=!0);for(const e of Object.keys(r))O(r,e)&&(t=!0);if(0!==Object.keys(r).length)return t?r:e};return await o.VK.updateHomeConfiguration({npmRegistries:e,npmScopes:e})}(),r.reportInfo(l.b.UNNAMED,"Successfully logged out from everything")),this.scope)return await U("npmScopes",this.scope),void(await t()?r.reportInfo(l.b.UNNAMED,"Successfully logged out from "+this.scope):r.reportWarning(l.b.UNNAMED,"Scope authentication settings removed, but some other ones settings still apply to it"));const A=await x({configuration:e,cwd:this.context.cwd,publish:this.publish});await U("npmRegistries",A),await t()?r.reportInfo(l.b.UNNAMED,"Successfully logged out from "+A):r.reportWarning(l.b.UNNAMED,"Registry authentication settings removed, but some other ones settings still apply to it")})).exitCode()}}function O(e,t){const r=e[t];if(!f.isIndexableObject(r))return!1;const A=new Set(Object.keys(r));if([...L].every(e=>!A.has(e)))return!1;for(const e of L)A.delete(e);if(0===A.size)return e[t]=void 0,!0;const n={...r};for(const e of L)delete n[e];return e[t]=n,!0}async function U(e,t){return await o.VK.updateHomeConfiguration({[e]:e=>{const r=f.isIndexableObject(e)?e:{};if(!Object.prototype.hasOwnProperty.call(r,t))return e;const A=r[t],n=f.isIndexableObject(A)?A:{},o=new Set(Object.keys(n));if([...L].every(e=>!o.has(e)))return e;for(const e of L)o.delete(e);if(0===o.size){if(1===Object.keys(r).length)return;return{...r,[t]:void 0}}const i={};for(const e of L)i[e]=void 0;return{...r,[t]:{...n,...i}}}})}P.usage=d.Command.Usage({category:"Npm-related commands",description:"logout of the npm registry",details:"\n This command will log you out by modifying your local configuration (in your home folder, never in the project itself) to delete all credentials linked to a registry.\n\n Adding the `-s,--scope` flag will cause the deletion to be done against whatever registry is configured for the associated scope (see also `npmScopes`).\n\n Adding the `--publish` flag will cause the deletion to be done against the registry used when publishing the package (see also `publishConfig.registry` and `npmPublishRegistry`).\n\n Adding the `-A,--all` flag will cause the deletion to be done against all registries and scopes.\n ",examples:[["Logout of the default registry","yarn npm logout"],["Logout of the @my-scope scope","yarn npm logout --scope my-scope"],["Logout of the publish registry for the current package","yarn npm logout --publish"],["Logout of all registries","yarn npm logout --all"]]}),(0,i.gn)([d.Command.String("-s,--scope",{description:"Logout of the registry configured for a given scope"})],P.prototype,"scope",void 0),(0,i.gn)([d.Command.Boolean("--publish",{description:"Logout of the publish registry"})],P.prototype,"publish",void 0),(0,i.gn)([d.Command.Boolean("-A,--all",{description:"Logout of all registries"})],P.prototype,"all",void 0),(0,i.gn)([d.Command.Path("npm","logout")],P.prototype,"execute",null);var T=r(63088),j=r(49881);class Y extends s.BaseCommand{constructor(){super(...arguments),this.tag="latest",this.tolerateRepublish=!1}async execute(){const e=await o.VK.find(this.context.cwd,this.context.plugins),{project:t,workspace:r}=await a.I.find(e,this.context.cwd);if(!r)throw new s.WorkspaceRequiredError(t.cwd,this.context.cwd);if(r.manifest.private)throw new d.UsageError("Private workspaces cannot be published");if(null===r.manifest.name||null===r.manifest.version)throw new d.UsageError("Workspaces must have valid names and versions to be published on an external registry");await t.restoreInstallState();const A=r.manifest.name,n=r.manifest.version,i=p.npmConfigUtils.getPublishRegistry(r.manifest,{configuration:e});return(await h.Pk.start({configuration:e,stdout:this.context.stdout},async t=>{if(this.tolerateRepublish)try{const r=await p.npmHttpUtils.get(p.npmHttpUtils.getIdentUrl(A),{configuration:e,registry:i,ident:A,jsonResponse:!0});if(!Object.prototype.hasOwnProperty.call(r,"versions"))throw new g.lk(l.b.REMOTE_INVALID,'Registry returned invalid data for - missing "versions" field');if(Object.prototype.hasOwnProperty.call(r.versions,n))return void t.reportWarning(l.b.UNNAMED,`Registry already knows about version ${n}; skipping.`)}catch(e){if("HTTPError"!==e.name)throw e;if(404!==e.response.statusCode)throw new g.lk(l.b.NETWORK_ERROR,`The remote server answered with HTTP ${e.response.statusCode} ${e.response.statusMessage}`)}await T.maybeExecuteWorkspaceLifecycleScript(r,"prepublish",{report:t}),await j.packUtils.prepareForPack(r,{report:t},async()=>{const n=await j.packUtils.genPackList(r);for(const e of n)t.reportInfo(null,e);const o=await j.packUtils.genPackStream(r,n),s=await f.bufferStream(o),a=await p.npmPublishUtils.makePublishBody(r,s,{access:this.access,tag:this.tag,registry:i});try{await p.npmHttpUtils.put(p.npmHttpUtils.getIdentUrl(A),a,{configuration:e,registry:i,ident:A,jsonResponse:!0})}catch(e){if("HTTPError"!==e.name)throw e;{const r=e.response.body&&e.response.body.error?e.response.body.error:`The remote server answered with HTTP ${e.response.statusCode} ${e.response.statusMessage}`;t.reportError(l.b.NETWORK_ERROR,r)}}}),t.hasErrors()||t.reportInfo(l.b.UNNAMED,"Package archive published")})).exitCode()}}Y.usage=d.Command.Usage({category:"Npm-related commands",description:"publish the active workspace to the npm registry",details:'\n This command will pack the active workspace into a fresh archive and upload it to the npm registry.\n\n The package will by default be attached to the `latest` tag on the registry, but this behavior can be overriden by using the `--tag` option.\n\n Note that for legacy reasons scoped packages are by default published with an access set to `restricted` (aka "private packages"). This requires you to register for a paid npm plan. In case you simply wish to publish a public scoped package to the registry (for free), just add the `--access public` flag. This behavior can be enabled by default through the `npmPublishAccess` settings.\n ',examples:[["Publish the active workspace","yarn npm publish"]]}),(0,i.gn)([d.Command.String("--access",{description:"The access for the published package (public or restricted)"})],Y.prototype,"access",void 0),(0,i.gn)([d.Command.String("--tag",{description:"The tag on the registry that the package should be attached to"})],Y.prototype,"tag",void 0),(0,i.gn)([d.Command.Boolean("--tolerate-republish",{description:"Warn and exit when republishing an already existing version of a package"})],Y.prototype,"tolerateRepublish",void 0),(0,i.gn)([d.Command.Path("npm","publish")],Y.prototype,"execute",null);var G=r(46009);class H extends s.BaseCommand{constructor(){super(...arguments),this.json=!1}async execute(){const e=await o.VK.find(this.context.cwd,this.context.plugins),{project:t,workspace:r}=await a.I.find(e,this.context.cwd);let A;if(void 0!==this.package)A=C.parseIdent(this.package);else{if(!r)throw new s.WorkspaceRequiredError(t.cwd,this.context.cwd);if(!r.manifest.name)throw new d.UsageError("Missing 'name' field in "+G.y1.join(r.cwd,G.QS.manifest));A=r.manifest.name}const n=await J(A,e),i={children:f.sortMap(Object.entries(n),([e])=>e).map(([e,t])=>({value:I.tuple(I.Type.RESOLUTION,{descriptor:C.makeDescriptor(A,e),locator:C.makeLocator(A,t)})}))};return u.emitTree(i,{configuration:e,json:this.json,stdout:this.context.stdout})}}async function J(e,t){const r=`/-/package${p.npmHttpUtils.getIdentUrl(e)}/dist-tags`;return p.npmHttpUtils.get(r,{configuration:t,ident:e,jsonResponse:!0}).catch(e=>{throw"HTTPError"!==e.name?e:404===e.response.statusCode?new g.lk(l.b.EXCEPTION,"Package not found"):new g.lk(l.b.EXCEPTION,e.toString())})}H.usage=d.Command.Usage({category:"Npm-related commands",description:"list all dist-tags of a package",details:"\n This command will list all tags of a package from the npm registry.\n\n If the package is not specified, Yarn will default to the current workspace.\n ",examples:[["List all tags of package `my-pkg`","yarn npm tag list my-pkg"]]}),(0,i.gn)([d.Command.String({required:!1})],H.prototype,"package",void 0),(0,i.gn)([d.Command.Boolean("--json",{description:"Format the output as an NDJSON stream"})],H.prototype,"json",void 0),(0,i.gn)([d.Command.Path("npm","tag","list")],H.prototype,"execute",null);class q extends s.BaseCommand{async execute(){const e=await o.VK.find(this.context.cwd,this.context.plugins),{project:t,workspace:r}=await a.I.find(e,this.context.cwd);if(!r)throw new s.WorkspaceRequiredError(t.cwd,this.context.cwd);const A=C.parseDescriptor(this.package,!0),n=A.range;if(!k().valid(n))throw new d.UsageError(`The range ${I.pretty(e,A.range,I.Type.RANGE)} must be a valid semver version`);const i=p.npmConfigUtils.getPublishRegistry(r.manifest,{configuration:e}),c=I.pretty(e,A,I.Type.IDENT),g=I.pretty(e,n,I.Type.RANGE),u=I.pretty(e,this.tag,I.Type.CODE);return(await h.Pk.start({configuration:e,stdout:this.context.stdout},async t=>{const r=await J(A,e);Object.prototype.hasOwnProperty.call(r,this.tag)&&r[this.tag]===n&&t.reportWarning(l.b.UNNAMED,`Tag ${u} is already set to version ${g}`);try{const t=`/-/package${p.npmHttpUtils.getIdentUrl(A)}/dist-tags/${encodeURIComponent(this.tag)}`;await p.npmHttpUtils.put(t,n,{configuration:e,registry:i,ident:A,jsonRequest:!0,jsonResponse:!0})}catch(e){if("HTTPError"!==e.name)throw e;{const r=e.response.body&&e.response.body.error?e.response.body.error:`The remote server answered with HTTP ${e.response.statusCode} ${e.response.statusMessage}`;t.reportError(l.b.NETWORK_ERROR,r)}}t.hasErrors()||t.reportInfo(l.b.UNNAMED,`Tag ${u} added to version ${g} of package ${c}`)})).exitCode()}}q.usage=d.Command.Usage({category:"Npm-related commands",description:"add a tag for a specific version of a package",details:"\n This command will add a tag to the npm registry for a specific version of a package. If the tag already exists, it will be overwritten.\n ",examples:[["Add a `beta` tag for version `2.3.4-beta.4` of package `my-pkg`","yarn npm tag add my-pkg@2.3.4-beta.4 beta"]]}),(0,i.gn)([d.Command.String()],q.prototype,"package",void 0),(0,i.gn)([d.Command.String()],q.prototype,"tag",void 0),(0,i.gn)([d.Command.Path("npm","tag","add")],q.prototype,"execute",null);var z=r(15966);class W extends s.BaseCommand{async execute(){const e=await o.VK.find(this.context.cwd,this.context.plugins),{project:t,workspace:r}=await a.I.find(e,this.context.cwd);if(!r)throw new s.WorkspaceRequiredError(t.cwd,this.context.cwd);const A=C.parseIdent(this.package),n=p.npmConfigUtils.getPublishRegistry(r.manifest,{configuration:e}),i=I.pretty(e,this.tag,I.Type.CODE),c=I.pretty(e,A,I.Type.IDENT),g=await J(A,e);if(!Object.prototype.hasOwnProperty.call(g,this.tag))throw new d.UsageError(`${i} is not a tag of package ${c}`);return(await h.Pk.start({configuration:e,stdout:this.context.stdout},async t=>{try{const t=`/-/package${p.npmHttpUtils.getIdentUrl(A)}/dist-tags/${encodeURIComponent(this.tag)}`;await p.npmHttpUtils.del(t,{configuration:e,registry:n,ident:A,jsonResponse:!0})}catch(e){if("HTTPError"!==e.name)throw e;{const r=e.response.body&&e.response.body.error?e.response.body.error:`The remote server answered with HTTP ${e.response.statusCode} ${e.response.statusMessage}`;t.reportError(l.b.NETWORK_ERROR,r)}}t.hasErrors()||t.reportInfo(l.b.UNNAMED,`Tag ${i} removed from package ${c}`)})).exitCode()}}W.schema=z.object().shape({tag:z.string().notOneOf(["latest"])}),W.usage=d.Command.Usage({category:"Npm-related commands",description:"remove a tag from a package",details:"\n This command will remove a tag from a package from the npm registry.\n ",examples:[["Remove the `beta` tag from package `my-pkg`","yarn npm tag remove my-pkg beta"]]}),(0,i.gn)([d.Command.String()],W.prototype,"package",void 0),(0,i.gn)([d.Command.String()],W.prototype,"tag",void 0),(0,i.gn)([d.Command.Path("npm","tag","remove")],W.prototype,"execute",null);class V extends s.BaseCommand{constructor(){super(...arguments),this.publish=!1}async execute(){const e=await o.VK.find(this.context.cwd,this.context.plugins);let t;t=this.scope&&this.publish?p.npmConfigUtils.getScopeRegistry(this.scope,{configuration:e,type:p.npmConfigUtils.RegistryType.PUBLISH_REGISTRY}):this.scope?p.npmConfigUtils.getScopeRegistry(this.scope,{configuration:e}):this.publish?p.npmConfigUtils.getPublishRegistry((await(0,s.openWorkspace)(e,this.context.cwd)).manifest,{configuration:e}):p.npmConfigUtils.getDefaultRegistry({configuration:e});return(await h.Pk.start({configuration:e,stdout:this.context.stdout},async r=>{try{const A=await p.npmHttpUtils.get("/-/whoami",{configuration:e,registry:t,authType:p.npmHttpUtils.AuthType.ALWAYS_AUTH,jsonResponse:!0,ident:this.scope?C.makeIdent(this.scope,""):void 0});r.reportInfo(l.b.UNNAMED,A.username)}catch(e){if("HTTPError"!==e.name)throw e;401===e.response.statusCode||403===e.response.statusCode?r.reportError(l.b.AUTHENTICATION_INVALID,"Authentication failed - your credentials may have expired"):r.reportError(l.b.AUTHENTICATION_INVALID,e.toString())}})).exitCode()}}V.usage=d.Command.Usage({category:"Npm-related commands",description:"display the name of the authenticated user",details:"\n Print the username associated with the current authentication settings to the standard output.\n\n When using `-s,--scope`, the username printed will be the one that matches the authentication settings of the registry associated with the given scope (those settings can be overriden using the `npmRegistries` map, and the registry associated with the scope is configured via the `npmScopes` map).\n\n When using `--publish`, the registry we'll select will by default be the one used when publishing packages (`publishConfig.registry` or `npmPublishRegistry` if available, otherwise we'll fallback to the regular `npmRegistryServer`).\n ",examples:[["Print username for the default registry","yarn npm whoami"],["Print username for the registry on a given scope","yarn npm whoami --scope company"]]}),(0,i.gn)([d.Command.String("-s,--scope",{description:"Print username for the registry configured for a given scope"})],V.prototype,"scope",void 0),(0,i.gn)([d.Command.Boolean("--publish",{description:"Print username for the publish registry"})],V.prototype,"publish",void 0),(0,i.gn)([d.Command.Path("npm","whoami")],V.prototype,"execute",null);const X={configuration:{npmPublishAccess:{description:"Default access of the published packages",type:o.a2.STRING,default:null}},commands:[D,F,R,P,Y,q,H,W,V]}},14224:(e,t,r)=>{"use strict";r.r(t),r.d(t,{default:()=>W,npmConfigUtils:()=>A,npmHttpUtils:()=>n,npmPublishUtils:()=>o});var A={};r.r(A),r.d(A,{RegistryType:()=>l,getAuthConfiguration:()=>Q,getDefaultRegistry:()=>y,getPublishRegistry:()=>E,getRegistryConfiguration:()=>m,getScopeConfiguration:()=>w,getScopeRegistry:()=>B,normalizeRegistry:()=>I});var n={};r.r(n),r.d(n,{AuthType:()=>u,del:()=>N,get:()=>v,getIdentUrl:()=>b,handleInvalidAuthenticationError:()=>D,post:()=>S,put:()=>k});var o={};r.r(o),r.d(o,{makePublishBody:()=>J});var i=r(39922),s=r(54143),a=r(72785),c=r(53887),g=r.n(c);var l,u,h=r(79669),p=r(35691),d=r(92659),C=r(61899),f=r(78835);function I(e){return e.replace(/\/$/,"")}function E(e,{configuration:t}){return e.publishConfig&&e.publishConfig.registry?I(e.publishConfig.registry):e.name?B(e.name.scope,{configuration:t,type:l.PUBLISH_REGISTRY}):y({configuration:t,type:l.PUBLISH_REGISTRY})}function B(e,{configuration:t,type:r=l.FETCH_REGISTRY}){const A=w(e,{configuration:t});if(null===A)return y({configuration:t,type:r});const n=A.get(r);return null===n?y({configuration:t,type:r}):I(n)}function y({configuration:e,type:t=l.FETCH_REGISTRY}){const r=e.get(t);return I(null!==r?r:e.get(l.FETCH_REGISTRY))}function m(e,{configuration:t}){const r=t.get("npmRegistries"),A=r.get(e);if(void 0!==A)return A;const n=r.get(e.replace(/^[a-z]+:/,""));return void 0!==n?n:null}function w(e,{configuration:t}){if(null===e)return null;const r=t.get("npmScopes").get(e);return r||null}function Q(e,{configuration:t,ident:r}){const A=r&&w(r.scope,{configuration:t});if((null==A?void 0:A.get("npmAuthIdent"))||(null==A?void 0:A.get("npmAuthToken")))return A;return m(e,{configuration:t})||t}async function D(e,{attemptedAs:t,registry:r,headers:A,configuration:n}){if("HTTPError"===e.name&&401===e.response.statusCode)throw new p.lk(d.b.AUTHENTICATION_INVALID,`Invalid authentication (${"string"!=typeof t?"as "+await async function(e,t,{configuration:r}){var A;if(void 0===t||void 0===t.authorization)return"an anonymous user";try{const n=await h.get(new f.URL(e+"/-/whoami").href,{configuration:r,headers:t,jsonResponse:!0});return null!==(A=n.username)&&void 0!==A?A:"an unknown user"}catch(e){return"an unknown user"}}(r,A,{configuration:n}):"attempted as "+t})`)}function b(e){return e.scope?`/@${e.scope}%2f${e.name}`:"/"+e.name}async function v(e,{configuration:t,headers:r,ident:A,authType:n,registry:o,...i}){if(A&&void 0===o&&(o=B(A.scope,{configuration:t})),A&&A.scope&&void 0===n&&(n=u.BEST_EFFORT),"string"!=typeof o)throw new Error("Assertion failed: The registry should be a string");const s=F(o,{authType:n,configuration:t,ident:A});let a;s&&(r={...r,authorization:s});try{a=new f.URL(e)}catch(t){a=new f.URL(o+e)}try{return await h.get(a.href,{configuration:t,headers:r,...i})}catch(e){throw await D(e,{registry:o,configuration:t,headers:r}),e}}async function S(e,t,{attemptedAs:r,configuration:A,headers:n,ident:o,authType:i=u.ALWAYS_AUTH,registry:s,...a}){if(o&&void 0===s&&(s=B(o.scope,{configuration:A})),"string"!=typeof s)throw new Error("Assertion failed: The registry should be a string");const c=F(s,{authType:i,configuration:A,ident:o});c&&(n={...n,authorization:c});try{return await h.post(s+e,t,{configuration:A,headers:n,...a})}catch(o){if(!M(o))throw await D(o,{attemptedAs:r,registry:s,configuration:A,headers:n}),o;const i=await K(),c={...n,...R(i)};try{return await h.post(`${s}${e}`,t,{configuration:A,headers:c,...a})}catch(e){throw await D(e,{attemptedAs:r,registry:s,configuration:A,headers:n}),e}}}async function k(e,t,{attemptedAs:r,configuration:A,headers:n,ident:o,authType:i=u.ALWAYS_AUTH,registry:s,...a}){if(o&&void 0===s&&(s=B(o.scope,{configuration:A})),"string"!=typeof s)throw new Error("Assertion failed: The registry should be a string");const c=F(s,{authType:i,configuration:A,ident:o});c&&(n={...n,authorization:c});try{return await h.put(s+e,t,{configuration:A,headers:n,...a})}catch(o){if(!M(o))throw await D(o,{attemptedAs:r,registry:s,configuration:A,headers:n}),o;const i=await K(),c={...n,...R(i)};try{return await h.put(`${s}${e}`,t,{configuration:A,headers:c,...a})}catch(e){throw await D(e,{attemptedAs:r,registry:s,configuration:A,headers:n}),e}}}async function N(e,{attemptedAs:t,configuration:r,headers:A,ident:n,authType:o=u.ALWAYS_AUTH,registry:i,...s}){if(n&&void 0===i&&(i=B(n.scope,{configuration:r})),"string"!=typeof i)throw new Error("Assertion failed: The registry should be a string");const a=F(i,{authType:o,configuration:r,ident:n});a&&(A={...A,authorization:a});try{return await h.del(i+e,{configuration:r,headers:A,...s})}catch(n){if(!M(n))throw await D(n,{attemptedAs:t,registry:i,configuration:r,headers:A}),n;const o=await K(),a={...A,...R(o)};try{return await h.del(`${i}${e}`,{configuration:r,headers:a,...s})}catch(e){throw await D(e,{attemptedAs:t,registry:i,configuration:r,headers:A}),e}}}function F(e,{authType:t=u.CONFIGURATION,configuration:r,ident:A}){const n=Q(e,{configuration:r,ident:A}),o=function(e,t){switch(t){case u.CONFIGURATION:return e.get("npmAlwaysAuth");case u.BEST_EFFORT:case u.ALWAYS_AUTH:return!0;case u.NO_AUTH:return!1;default:throw new Error("Unreachable")}}(n,t);if(!o)return null;if(n.get("npmAuthToken"))return"Bearer "+n.get("npmAuthToken");if(n.get("npmAuthIdent"))return"Basic "+n.get("npmAuthIdent");if(o&&t!==u.BEST_EFFORT)throw new p.lk(d.b.AUTHENTICATION_NOT_FOUND,"No authentication configured for request");return null}async function K(){if(process.env.TEST_ENV)return process.env.TEST_NPM_2FA_TOKEN||"";const{otp:e}=await(0,C.prompt)({type:"password",name:"otp",message:"One-time password:",required:!0,onCancel:()=>process.exit(130)});return e}function M(e){if("HTTPError"!==e.name)return!1;try{return e.response.headers["www-authenticate"].split(/,\s*/).map(e=>e.toLowerCase()).includes("otp")}catch(e){return!1}}function R(e){return{"npm-otp":e}}!function(e){e.FETCH_REGISTRY="npmRegistryServer",e.PUBLISH_REGISTRY="npmPublishRegistry"}(l||(l={})),function(e){e[e.NO_AUTH=0]="NO_AUTH",e[e.BEST_EFFORT=1]="BEST_EFFORT",e[e.CONFIGURATION=2]="CONFIGURATION",e[e.ALWAYS_AUTH=3]="ALWAYS_AUTH"}(u||(u={}));class x{supports(e,t){if(!e.reference.startsWith("npm:"))return!1;const r=new f.URL(e.reference);return!!g().valid(r.pathname)&&!r.searchParams.has("__archiveUrl")}getLocalPath(e,t){return null}async fetch(e,t){const r=t.checksums.get(e.locatorHash)||null,[A,n,o]=await t.cache.fetchPackageFromCache(e,r,{onHit:()=>t.report.reportCacheHit(e),onMiss:()=>t.report.reportCacheMiss(e,s.prettyLocator(t.project.configuration,e)+" can't be found in the cache and will be fetched from the remote registry"),loader:()=>this.fetchFromNetwork(e,t),skipIntegrityCheck:t.skipIntegrityCheck});return{packageFs:A,releaseFs:n,prefixPath:s.getIdentVendorPath(e),checksum:o}}async fetchFromNetwork(e,t){let r;try{r=await v(x.getLocatorUrl(e),{configuration:t.project.configuration,ident:e})}catch(A){r=await v(x.getLocatorUrl(e).replace(/%2f/g,"/"),{configuration:t.project.configuration,ident:e})}return await a.convertToZip(r,{compressionLevel:t.project.configuration.get("compressionLevel"),prefixPath:s.getIdentVendorPath(e),stripComponents:1})}static isConventionalTarballUrl(e,t,{configuration:r}){let A=B(e.scope,{configuration:r});const n=x.getLocatorUrl(e);return t=t.replace(/^https?:(\/\/(?:[^/]+\.)?npmjs.org(?:$|\/))/,"https:$1"),A=A.replace(/^https:\/\/registry\.npmjs\.org($|\/)/,"https://registry.yarnpkg.com$1"),(t=t.replace(/^https:\/\/registry\.npmjs\.org($|\/)/,"https://registry.yarnpkg.com$1"))===A+n||t===A+n.replace(/%2f/g,"/")}static getLocatorUrl(e){const t=g().clean(e.reference.slice("npm:".length));if(null===t)throw new p.lk(d.b.RESOLVER_NOT_FOUND,"The npm semver resolver got selected, but the version isn't semver");return`${b(e)}/-/${e.name}-${t}.tgz`}}var L=r(46611),P=r(36545),O=r(32485);const U=s.makeIdent(null,"node-gyp"),T=/\b(node-gyp|prebuild-install)\b/;var j=r(52779);var Y=r(49881),G=r(76417),H=r(10129);async function J(e,t,{access:r,tag:A,registry:n}){const o=e.project.configuration,i=e.manifest.name,a=e.manifest.version,c=s.stringifyIdent(i),g=(0,G.createHash)("sha1").update(t).digest("hex"),l=H.Sd(t).toString();void 0===r&&(r=e.manifest.publishConfig&&"string"==typeof e.manifest.publishConfig.access?e.manifest.publishConfig.access:null!==o.get("npmPublishAccess")?o.get("npmPublishAccess"):i.scope?"restricted":"public");const u=await Y.packUtils.genPackageManifest(e),h=`${c}-${a}.tgz`,p=new f.URL(`${c}/-/${h}`,n);return{_id:c,_attachments:{[h]:{content_type:"application/octet-stream",data:t.toString("base64"),length:t.length}},name:c,access:r,"dist-tags":{[A]:a},versions:{[a]:{...u,_id:`${c}@${a}`,name:c,version:a,dist:{shasum:g,integrity:l,tarball:p.toString()}}}}}const q={npmAlwaysAuth:{description:"URL of the selected npm registry (note: npm enterprise isn't supported)",type:i.a2.BOOLEAN,default:!1},npmAuthIdent:{description:"Authentication identity for the npm registry (_auth in npm and yarn v1)",type:i.a2.SECRET,default:null},npmAuthToken:{description:"Authentication token for the npm registry (_authToken in npm and yarn v1)",type:i.a2.SECRET,default:null}},z={npmPublishRegistry:{description:"Registry to push packages to",type:i.a2.STRING,default:null},npmRegistryServer:{description:"URL of the selected npm registry (note: npm enterprise isn't supported)",type:i.a2.STRING,default:"https://registry.yarnpkg.com"}},W={configuration:{...q,...z,npmScopes:{description:"Settings per package scope",type:i.a2.MAP,valueDefinition:{description:"",type:i.a2.SHAPE,properties:{...q,...z}}},npmRegistries:{description:"Settings per registry",type:i.a2.MAP,normalizeKeys:I,valueDefinition:{description:"",type:i.a2.SHAPE,properties:{...q}}}},fetchers:[class{supports(e,t){if(!e.reference.startsWith("npm:"))return!1;const{selector:r,params:A}=s.parseRange(e.reference);return!!g().valid(r)&&(null!==A&&"string"==typeof A.__archiveUrl)}getLocalPath(e,t){return null}async fetch(e,t){const r=t.checksums.get(e.locatorHash)||null,[A,n,o]=await t.cache.fetchPackageFromCache(e,r,{onHit:()=>t.report.reportCacheHit(e),onMiss:()=>t.report.reportCacheMiss(e,s.prettyLocator(t.project.configuration,e)+" can't be found in the cache and will be fetched from the remote server"),loader:()=>this.fetchFromNetwork(e,t),skipIntegrityCheck:t.skipIntegrityCheck});return{packageFs:A,releaseFs:n,prefixPath:s.getIdentVendorPath(e),checksum:o}}async fetchFromNetwork(e,t){const{params:r}=s.parseRange(e.reference);if(null===r||"string"!=typeof r.__archiveUrl)throw new Error("Assertion failed: The archiveUrl querystring parameter should have been available");const A=await v(r.__archiveUrl,{configuration:t.project.configuration,ident:e});return await a.convertToZip(A,{compressionLevel:t.project.configuration.get("compressionLevel"),prefixPath:s.getIdentVendorPath(e),stripComponents:1})}},x],resolvers:[class{supportsDescriptor(e,t){return!!e.range.startsWith("npm:")&&!!s.tryParseDescriptor(e.range.slice("npm:".length),!0)}supportsLocator(e,t){return!1}shouldPersistResolution(e,t){throw new Error("Unreachable")}bindDescriptor(e,t,r){return e}getResolutionDependencies(e,t){const r=s.parseDescriptor(e.range.slice("npm:".length),!0);return t.resolver.getResolutionDependencies(r,t)}async getCandidates(e,t,r){const A=s.parseDescriptor(e.range.slice("npm:".length),!0);return await r.resolver.getCandidates(A,t,r)}async getSatisfying(e,t,r){const A=s.parseDescriptor(e.range.slice("npm:".length),!0);return r.resolver.getSatisfying(A,t,r)}resolve(e,t){throw new Error("Unreachable")}},class{supportsDescriptor(e,t){return!!e.range.startsWith("npm:")&&!!P.validRange(e.range.slice("npm:".length))}supportsLocator(e,t){if(!e.reference.startsWith("npm:"))return!1;const{selector:r}=s.parseRange(e.reference);return!!g().valid(r)}shouldPersistResolution(e,t){return!0}bindDescriptor(e,t,r){return e}getResolutionDependencies(e,t){return[]}async getCandidates(e,t,r){const A=P.validRange(e.range.slice("npm:".length));if(null===A)throw new Error("Expected a valid range, got "+e.range.slice("npm:".length));const n=await v(b(e),{configuration:r.project.configuration,ident:e,jsonResponse:!0}),o=Object.keys(n.versions).map(e=>new(g().SemVer)(e)).filter(e=>A.test(e)),i=o.filter(e=>!n.versions[e.raw].deprecated),a=i.length>0?i:o;return a.sort((e,t)=>-e.compare(t)),a.map(t=>{const A=s.makeLocator(e,"npm:"+t.raw),o=n.versions[t.raw].dist.tarball;return x.isConventionalTarballUrl(A,o,{configuration:r.project.configuration})?A:s.bindLocator(A,{__archiveUrl:o})})}async getSatisfying(e,t,r){const A=P.validRange(e.range.slice("npm:".length));if(null===A)throw new Error("Expected a valid range, got "+e.range.slice("npm:".length));return t.map(e=>{try{return new(g().SemVer)(e.slice("npm:".length))}catch(e){return null}}).filter(e=>null!==e).filter(e=>A.test(e)).sort((e,t)=>-e.compare(t)).map(t=>s.makeLocator(e,"npm:"+t.raw))}async resolve(e,t){const{selector:r}=s.parseRange(e.reference),A=g().clean(r);if(null===A)throw new p.lk(d.b.RESOLVER_NOT_FOUND,"The npm semver resolver got selected, but the version isn't semver");const n=await v(b(e),{configuration:t.project.configuration,ident:e,jsonResponse:!0});if(!Object.prototype.hasOwnProperty.call(n,"versions"))throw new p.lk(d.b.REMOTE_INVALID,'Registry returned invalid data for - missing "versions" field');if(!Object.prototype.hasOwnProperty.call(n.versions,A))throw new p.lk(d.b.REMOTE_NOT_FOUND,`Registry failed to return reference "${A}"`);const o=new L.G;if(o.load(n.versions[A]),!o.dependencies.has(U.identHash)&&!o.peerDependencies.has(U.identHash))for(const r of o.scripts.values())if(r.match(T)){o.dependencies.set(U.identHash,s.makeDescriptor(U,"latest")),t.report.reportWarning(d.b.NODE_GYP_INJECTED,s.prettyLocator(t.project.configuration,e)+": Implicit dependencies on node-gyp are discouraged");break}return"string"==typeof o.raw.deprecated&&t.report.reportWarning(d.b.DEPRECATED_PACKAGE,`${s.prettyLocator(t.project.configuration,e)} is deprecated: ${o.raw.deprecated}`),{...e,version:A,languageName:"node",linkType:O.Un.HARD,dependencies:o.dependencies,peerDependencies:o.peerDependencies,dependenciesMeta:o.dependenciesMeta,peerDependenciesMeta:o.peerDependenciesMeta,bin:o.bin}}},class{supportsDescriptor(e,t){return!!e.range.startsWith("npm:")&&!!j.c.test(e.range.slice("npm:".length))}supportsLocator(e,t){return!1}shouldPersistResolution(e,t){throw new Error("Unreachable")}bindDescriptor(e,t,r){return e}getResolutionDependencies(e,t){return[]}async getCandidates(e,t,r){const A=e.range.slice("npm:".length),n=await v(b(e),{configuration:r.project.configuration,ident:e,jsonResponse:!0});if(!Object.prototype.hasOwnProperty.call(n,"dist-tags"))throw new p.lk(d.b.REMOTE_INVALID,'Registry returned invalid data - missing "dist-tags" field');const o=n["dist-tags"];if(!Object.prototype.hasOwnProperty.call(o,A))throw new p.lk(d.b.REMOTE_NOT_FOUND,`Registry failed to return tag "${A}"`);const i=o[A],a=s.makeLocator(e,"npm:"+i),c=n.versions[i].dist.tarball;return x.isConventionalTarballUrl(a,c,{configuration:r.project.configuration})?[a]:[s.bindLocator(a,{__archiveUrl:c})]}async getSatisfying(e,t,r){return null}async resolve(e,t){throw new Error("Unreachable")}}]}},49881:(e,t,r)=>{"use strict";r.r(t),r.d(t,{default:()=>T,packUtils:()=>A});var A={};r.r(A),r.d(A,{genPackList:()=>K,genPackStream:()=>N,genPackageManifest:()=>F,hasPackScripts:()=>S,prepareForPack:()=>k});var n=r(54143),o=r(35691),i=r(92659),s=r(36370),a=r(40822);class c extends a.Command{}(0,s.gn)([a.Command.String("--cwd",{hidden:!0})],c.prototype,"cwd",void 0);var g=r(46611),l=r(46009);class u extends a.UsageError{constructor(e,t){super(`This command can only be run from within a workspace of your project (${l.y1.relative(e,t)} isn't a workspace of ${l.y1.join(e,g.G.fileName)}).`)}}r(63129),r(5864),r(35747);new Map([["constraints",[["constraints","query"],["constraints","source"],["constraints"]]],["exec",[]],["interactive-tools",[["search"],["upgrade-interactive"]]],["stage",[["stage"]]],["typescript",[]],["version",[["version","apply"],["version","check"],["version"]]],["workspace-tools",[["workspaces","focus"],["workspaces","foreach"]]]]);var h=r(71643),p=r(39922);(0,s.gn)([a.Command.Path("--welcome")],class extends c{async execute(){const e=await p.VK.find(this.context.cwd,this.context.plugins);this.context.stdout.write((e=>`\n${h.pretty(e,"Welcome on Yarn 2!","bold")} 🎉 Thanks for helping us shape our vision of how projects\nshould be managed going forward.\n\nBeing still in RC, Yarn 2 isn't completely stable yet. Some features might be\nmissing, and some behaviors may have received major overhaul. In case of doubt,\nuse the following URLs to get some insight:\n\n - The changelog:\n ${h.pretty(e,"https://github.com/yarnpkg/berry/tree/CHANGELOG.md","cyan")}\n\n - Our issue tracker:\n ${h.pretty(e,"https://github.com/yarnpkg/berry","cyan")}\n\n - Our Discord server:\n ${h.pretty(e,"https://discord.gg/yarnpkg","cyan")}\n\nWe're hoping you will enjoy the experience. For now, a good start is to run\nthe two following commands:\n\n ${h.pretty(e,"find . -name node_modules -prune -exec rm -r {} \\;","magenta")}\n ${h.pretty(e,"yarn install","magenta")}\n\nOne last trick! If you need at some point to upgrade Yarn to a nightly build,\nthe following command will install the CLI straight from master:\n\n ${h.pretty(e,"yarn set version from sources","magenta")}\n\nSee you later 👋\n`)(e).trim()+"\n")}}.prototype,"execute",null);var d=r(85824),C=r(28148),f=r(33720),I=r(15815),E=r(43896),B=r(63088),y=r(10489),m=r(2401),w=r.n(m),Q=r(59938),D=r(78761);const b=["/package.json","/readme","/readme.*","/license","/license.*","/licence","/licence.*","/changelog","/changelog.*"],v=["/package.tgz",".github",".git",".hg","node_modules",".npmignore",".gitignore",".#*",".DS_Store"];async function S(e){return!!B.hasWorkspaceScript(e,"prepack")||!!B.hasWorkspaceScript(e,"postpack")}async function k(e,{report:t},r){await B.maybeExecuteWorkspaceLifecycleScript(e,"prepack",{report:t});try{await r()}finally{await B.maybeExecuteWorkspaceLifecycleScript(e,"postpack",{report:t})}}async function N(e,t){var r,A;void 0===t&&(t=await K(e));const n=new Set;for(const t of null!==(A=null===(r=e.manifest.publishConfig)||void 0===r?void 0:r.executableFiles)&&void 0!==A?A:new Set)n.add(l.y1.normalize(t));for(const t of e.manifest.bin.values())n.add(l.y1.normalize(t));const o=Q.pack();process.nextTick(async()=>{for(const r of t){const t=l.y1.normalize(r),A=l.y1.resolve(e.cwd,t),i=l.y1.join("package",t),s=await E.xfs.lstatPromise(A),a={name:i,mtime:new Date(3155328e5)},c=n.has(t)?493:420;let g,u;const h=new Promise((e,t)=>{g=e,u=t}),p=e=>{e?u(e):g()};if(s.isFile()){let r;r="package.json"===t?Buffer.from(JSON.stringify(await F(e),null,2)):await E.xfs.readFilePromise(A),o.entry({...a,mode:c,type:"file"},r,p)}else s.isSymbolicLink()?o.entry({...a,mode:c,type:"symlink",linkname:await E.xfs.readlinkPromise(A)},p):p(new Error(`Unsupported file type ${s.mode} for ${l.cS.fromPortablePath(t)}`));await h}o.finalize()});const i=(0,D.createGzip)();return o.pipe(i),i}async function F(e){const t=JSON.parse(JSON.stringify(e.manifest.raw));return await e.project.configuration.triggerHook(e=>e.beforeWorkspacePacking,e,t),t}async function K(e){var t,r,A,n,o,i,s,a;const c=e.project,g=c.configuration,u={accept:[],reject:[]};for(const e of v)u.reject.push(e);for(const e of b)u.accept.push(e);u.reject.push(g.get("rcFilename"));const h=t=>{if(null===t||!t.startsWith(e.cwd+"/"))return;const r=l.y1.relative(e.cwd,t),A=l.y1.resolve(l.LZ.root,r);u.reject.push(A)};h(l.y1.resolve(c.cwd,g.get("lockfileFilename"))),h(g.get("bstatePath")),h(g.get("cacheFolder")),h(g.get("globalFolder")),h(g.get("installStatePath")),h(g.get("virtualFolder")),h(g.get("yarnPath")),await g.triggerHook(e=>e.populateYarnPaths,c,e=>{h(e)});for(const t of c.workspaces){const r=l.y1.relative(e.cwd,t.cwd);""===r||r.match(/^(\.\.)?\//)||u.reject.push("/"+r)}const p={accept:[],reject:[]},d=null!==(r=null===(t=e.manifest.publishConfig)||void 0===t?void 0:t.main)&&void 0!==r?r:e.manifest.main,C=null!==(n=null===(A=e.manifest.publishConfig)||void 0===A?void 0:A.module)&&void 0!==n?n:e.manifest.module,f=null!==(i=null===(o=e.manifest.publishConfig)||void 0===o?void 0:o.browser)&&void 0!==i?i:e.manifest.browser,I=null!==(a=null===(s=e.manifest.publishConfig)||void 0===s?void 0:s.bin)&&void 0!==a?a:e.manifest.bin;null!=d&&p.accept.push(l.y1.resolve(l.LZ.root,d)),null!=C&&p.accept.push(l.y1.resolve(l.LZ.root,C)),"string"==typeof f&&p.accept.push(l.y1.resolve(l.LZ.root,f));for(const e of I.values())p.accept.push(l.y1.resolve(l.LZ.root,e));if(f instanceof Map)for(const[e,t]of f.entries())p.accept.push(l.y1.resolve(l.LZ.root,e)),"string"==typeof t&&p.accept.push(l.y1.resolve(l.LZ.root,t));const E=null!==e.manifest.files;if(E){p.reject.push("/*");for(const t of e.manifest.files)R(p.accept,t,{cwd:l.LZ.root})}return await async function(e,{hasExplicitFileList:t,globalList:r,ignoreList:A}){const n=[],o=new y.n(e),i=[[l.LZ.root,[A]]];for(;i.length>0;){const[e,A]=i.pop(),s=await o.lstatPromise(e);if(!x(e,{globalList:r,ignoreLists:s.isDirectory()?null:A}))if(s.isDirectory()){const n=await o.readdirPromise(e);let s=!1,a=!1;if(!t||e!==l.LZ.root)for(const e of n)s=s||".gitignore"===e,a=a||".npmignore"===e;const c=a?await M(o,e,".npmignore"):s?await M(o,e,".gitignore"):null;let g=null!==c?[c].concat(A):A;x(e,{globalList:r,ignoreLists:A})&&(g=[...A,{accept:[],reject:["**/*"]}]);for(const t of n)i.push([l.y1.resolve(e,t),g])}else(s.isFile()||s.isSymbolicLink())&&n.push(l.y1.relative(l.LZ.root,e))}return n.sort()}(e.cwd,{hasExplicitFileList:E,globalList:u,ignoreList:p})}async function M(e,t,r){const A={accept:[],reject:[]},n=await e.readFilePromise(l.y1.join(t,r),"utf8");for(const e of n.split(/\n/g))R(A.reject,e,{cwd:t});return A}function R(e,t,{cwd:r}){const A=t.trim();""!==A&&"#"!==A[0]&&e.push(function(e,{cwd:t}){const r="!"===e[0];return r&&(e=e.slice(1)),e.match(/\.{0,1}\//)&&(e=l.y1.resolve(t,e)),r&&(e="!"+e),e}(A,{cwd:r}))}function x(e,{globalList:t,ignoreLists:r}){if(L(e,t.accept))return!1;if(L(e,t.reject))return!0;if(null!==r)for(const t of r){if(L(e,t.accept))return!1;if(L(e,t.reject))return!0}return!1}function L(e,t){let r=t;const A=[];for(let e=0;e{await k(r,{report:t},async()=>{t.reportJson({base:r.cwd});const e=await K(r);for(const r of e)t.reportInfo(null,r),t.reportJson({location:r});if(!this.dryRun){const t=await N(r,e),n=E.xfs.createWriteStream(A);t.pipe(n),await new Promise(e=>{n.on("finish",e)})}}),this.dryRun||(t.reportInfo(i.b.UNNAMED,"Package archive generated in "+h.pretty(e,A,h.Type.PATH)),t.reportJson({output:A}))})).exitCode()}}O.usage=a.Command.Usage({description:"generate a tarball from the active workspace",details:"\n This command will turn the active workspace into a compressed archive suitable for publishing. The archive will by default be stored at the root of the workspace (`package.tgz`).\n\n If the `-o,---out` is set the archive will be created at the specified path. The `%s` and `%v` variables can be used within the path and will be respectively replaced by the package name and version.\n ",examples:[["Create an archive from the active workspace","yarn pack"],["List the files that would be made part of the workspace's archive","yarn pack --dry-run"],["Name and output the archive in a dedicated folder","yarn pack --out /artifacts/%s-%v.tgz"]]}),(0,s.gn)([a.Command.Boolean("--install-if-needed",{description:"Run a preliminary `yarn install` if the package contains build scripts"})],O.prototype,"installIfNeeded",void 0),(0,s.gn)([a.Command.Boolean("-n,--dry-run",{description:"Print the file paths without actually generating the package archive"})],O.prototype,"dryRun",void 0),(0,s.gn)([a.Command.Boolean("--json",{description:"Format the output as an NDJSON stream"})],O.prototype,"json",void 0),(0,s.gn)([a.Command.String("--filename",{hidden:!1,description:"Create the archive at the specified path"}),a.Command.String("-o,--out",{description:"Create the archive at the specified path"})],O.prototype,"out",void 0),(0,s.gn)([a.Command.Path("pack")],O.prototype,"execute",null);const U=["dependencies","devDependencies","peerDependencies"],T={hooks:{beforeWorkspacePacking:(e,t)=>{t.publishConfig&&(t.publishConfig.main&&(t.main=t.publishConfig.main),t.publishConfig.browser&&(t.browser=t.publishConfig.browser),t.publishConfig.module&&(t.module=t.publishConfig.module),t.publishConfig.browser&&(t.browser=t.publishConfig.browser),t.publishConfig.bin&&(t.bin=t.publishConfig.bin));const r=e.project;for(const A of U)for(const s of e.manifest.getForScope(A).values()){const e=r.tryWorkspaceByDescriptor(s),a=n.parseRange(s.range);if("workspace:"===a.protocol)if(null===e){if(null===r.tryWorkspaceByIdent(s))throw new o.lk(i.b.WORKSPACE_NOT_FOUND,n.prettyDescriptor(r.configuration,s)+": No local workspace found for this range")}else{let r;r=n.areDescriptorsEqual(s,e.anchoredDescriptor)||"*"===a.selector?e.manifest.version:a.selector,t[A][n.stringifyIdent(s)]=r}}}},commands:[O]}},29936:(e,t,r)=>{"use strict";r.r(t),r.d(t,{default:()=>re,patchUtils:()=>A});var A={};r.r(A),r.d(A,{applyPatchFile:()=>S,diffFolders:()=>H,extractPackageToDisk:()=>G,isParentRequired:()=>j,loadPatchFiles:()=>Y,makeDescriptor:()=>O,makeLocator:()=>U,parseDescriptor:()=>x,parseLocator:()=>L,parsePatchFile:()=>D});var n=r(39922),o=r(35691),i=r(92659),s=r(54143),a=r(73632),c=r(43896),g=r(46009),l=r(90739),u=r(75448),h=r(65281),p=r(33720),d=r(6220),C=r(36545),f=r(78420);class I extends Error{constructor(e,t){super("Cannot apply hunk #"+(e+1)),this.hunk=t}}const E=/^@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@.*/;function B(e){return g.y1.relative(g.LZ.root,g.y1.resolve(g.LZ.root,g.cS.toPortablePath(e)))}function y(e){const t=e.trim().match(E);if(!t)throw new Error(`Bad header line: '${e}'`);return{original:{start:Math.max(Number(t[1]),1),length:Number(t[3]||1)},patched:{start:Math.max(Number(t[4]),1),length:Number(t[6]||1)}}}var m;!function(e){e.Context="context",e.Insertion="insertion",e.Deletion="deletion"}(m||(m={}));const w={"@":"header","-":m.Deletion,"+":m.Insertion," ":m.Context,"\\":"pragma",undefined:m.Context};function Q(e){const t=511&parseInt(e,8);if(420!==t&&493!==t)throw new Error("Unexpected file mode string: "+e);return t}function D(e){const t=e.split(/\n/g);return""===t[t.length-1]&&t.pop(),function(e){const t=[];for(const r of e){const{semverExclusivity:e,diffLineFromPath:A,diffLineToPath:n,oldMode:o,newMode:i,deletedFileMode:s,newFileMode:c,renameFrom:g,renameTo:l,beforeHash:u,afterHash:h,fromPath:p,toPath:d,hunks:C}=r,f=g?"rename":s?"file deletion":c?"file creation":C&&C.length>0?"patch":"mode change";let I=null;switch(f){case"rename":if(!g||!l)throw new Error("Bad parser state: rename from & to not given");t.push({type:"rename",semverExclusivity:e,fromPath:B(g),toPath:B(l)}),I=l;break;case"file deletion":{const r=A||p;if(!r)throw new Error("Bad parse state: no path given for file deletion");t.push({type:"file deletion",semverExclusivity:e,hunk:C&&C[0]||null,path:B(r),mode:Q(s),hash:u})}break;case"file creation":{const r=n||d;if(!r)throw new Error("Bad parse state: no path given for file creation");t.push({type:"file creation",semverExclusivity:e,hunk:C&&C[0]||null,path:B(r),mode:Q(c),hash:h})}break;case"patch":case"mode change":I=d||n;break;default:a.assertNever(f)}I&&o&&i&&o!==i&&t.push({type:"mode change",semverExclusivity:e,path:B(I),oldMode:Q(o),newMode:Q(i)}),I&&C&&C.length&&t.push({type:"patch",semverExclusivity:e,path:B(I),hunks:C,beforeHash:u,afterHash:h})}return t}(function(e){const t=[];let r={semverExclusivity:null,diffLineFromPath:null,diffLineToPath:null,oldMode:null,newMode:null,deletedFileMode:null,newFileMode:null,renameFrom:null,renameTo:null,beforeHash:null,afterHash:null,fromPath:null,toPath:null,hunks:null},A="parsing header",n=null,o=null;function i(){n&&(o&&(n.parts.push(o),o=null),r.hunks.push(n),n=null)}function s(){i(),t.push(r),r={semverExclusivity:null,diffLineFromPath:null,diffLineToPath:null,oldMode:null,newMode:null,deletedFileMode:null,newFileMode:null,renameFrom:null,renameTo:null,beforeHash:null,afterHash:null,fromPath:null,toPath:null,hunks:null}}for(let t=0;te<0?e:"+"+e;throw new Error(`hunk header integrity check failed (expected @@ ${A(e.header.original.length)} ${A(e.header.patched.length)} @@, got @@ ${A(t)} ${A(r)} @@)`)}}async function v(e,t,r){const A=await e.lstatPromise(t),n=await r();if(void 0!==n&&(t=n),e.lutimesPromise)await e.lutimesPromise(t,A.atime,A.mtime);else{if(A.isSymbolicLink())throw new Error("Cannot preserve the time values of a symlink");await e.utimesPromise(t,A.atime,A.mtime)}}async function S(e,{baseFs:t=new f.S,dryRun:r=!1,version:A=null}={}){for(const n of e)if(null===n.semverExclusivity||null===A||C.satisfiesWithPrereleases(A,n.semverExclusivity))switch(n.type){case"file deletion":if(r){if(!t.existsSync(n.path))throw new Error("Trying to delete a file that doesn't exist: "+n.path)}else await v(t,g.y1.dirname(n.path),async()=>{await t.unlinkPromise(n.path)});break;case"rename":if(r){if(!t.existsSync(n.fromPath))throw new Error("Trying to move a file that doesn't exist: "+n.fromPath)}else await v(t,g.y1.dirname(n.fromPath),async()=>{await v(t,g.y1.dirname(n.toPath),async()=>{await v(t,n.fromPath,async()=>(await t.movePromise(n.fromPath,n.toPath),n.toPath))})});break;case"file creation":if(r){if(t.existsSync(n.path))throw new Error("Trying to create a file that already exists: "+n.path)}else{const e=n.hunk?n.hunk.parts[0].lines.join("\n")+(n.hunk.parts[0].noNewlineAtEndOfFile?"":"\n"):"";await t.mkdirpPromise(g.y1.dirname(n.path),{chmod:493,utimes:[315532800,315532800]}),await t.writeFilePromise(n.path,e,{mode:n.mode}),await t.utimesPromise(n.path,315532800,315532800)}break;case"patch":await v(t,n.path,async()=>{await F(n,{baseFs:t,dryRun:r})});break;case"mode change":{const e=(await t.statPromise(n.path)).mode;if(k(n.newMode)!==k(e))continue;await v(t,n.path,async()=>{await t.chmodPromise(n.path,n.newMode)})}break;default:a.assertNever(n)}}function k(e){return(64&e)>0}function N(e){return e.replace(/\s+$/,"")}async function F({hunks:e,path:t},{baseFs:r,dryRun:A=!1}){const n=await r.statSync(t).mode,o=(await r.readFileSync(t,"utf8")).split(/\n/),i=[];let s=0,c=0;for(const t of e){const r=Math.max(c,t.header.patched.start+s),A=Math.max(0,r-c),n=Math.max(0,o.length-r-t.header.original.length),a=Math.max(A,n);let g=0,l=0,u=null;for(;g<=a;){if(g<=A&&(l=r-g,u=K(t,o,l),null!==u)){g=-g;break}if(g<=n&&(l=r+g,u=K(t,o,l),null!==u))break;g+=1}if(null===u)throw new I(e.indexOf(t),t);i.push(u),s+=g,c=l+t.header.original.length}if(A)return;let g=0;for(const e of i)for(const t of e)switch(t.type){case"splice":{const e=t.index+g;o.splice(e,t.numToDelete,...t.linesToInsert),g+=t.linesToInsert.length-t.numToDelete}break;case"pop":o.pop();break;case"push":o.push(t.line);break;default:a.assertNever(t)}await r.writeFilePromise(t,o.join("\n"),{mode:n})}function K(e,t,r){const A=[];for(const o of e.parts)switch(o.type){case m.Context:case m.Deletion:for(const e of o.lines){const A=t[r];if(null==A||(n=e,N(A)!==N(n)))return null;r+=1}o.type===m.Deletion&&(A.push({type:"splice",index:r-o.lines.length,numToDelete:o.lines.length,linesToInsert:[]}),o.noNewlineAtEndOfFile&&A.push({type:"push",line:""}));break;case m.Insertion:A.push({type:"splice",index:r,numToDelete:0,linesToInsert:o.lines}),o.noNewlineAtEndOfFile&&A.push({type:"pop"});break;default:a.assertNever(o.type)}var n;return A}const M=/^builtin<([^>]+)>$/;function R(e,t){const{source:r,selector:A,params:n}=s.parseRange(e);if(null===r)throw new Error("Patch locators must explicitly define their source");const o=A?A.split(/&/).map(e=>g.cS.toPortablePath(e)):[],i=n&&"string"==typeof n.locator?s.parseLocator(n.locator):null,a=n&&"string"==typeof n.version?n.version:null;return{parentLocator:i,sourceItem:t(r),patchPaths:o,sourceVersion:a}}function x(e){const{sourceItem:t,...r}=R(e.range,s.parseDescriptor);return{...r,sourceDescriptor:t}}function L(e){const{sourceItem:t,...r}=R(e.reference,s.parseLocator);return{...r,sourceLocator:t}}function P({parentLocator:e,sourceItem:t,patchPaths:r,sourceVersion:A,patchHash:n},o){const i=null!==e?{locator:s.stringifyLocator(e)}:{},a=void 0!==A?{version:A}:{},c=void 0!==n?{hash:n}:{};return s.makeRange({protocol:"patch:",source:o(t),selector:r.join("&"),params:{...a,...c,...i}})}function O(e,{parentLocator:t,sourceDescriptor:r,patchPaths:A}){return s.makeLocator(e,P({parentLocator:t,sourceItem:r,patchPaths:A},s.stringifyDescriptor))}function U(e,{parentLocator:t,sourcePackage:r,patchPaths:A,patchHash:n}){return s.makeLocator(e,P({parentLocator:t,sourceItem:r,sourceVersion:r.version,patchPaths:A,patchHash:n},s.stringifyLocator))}function T({onAbsolute:e,onRelative:t,onBuiltin:r},A){const n=A.match(M);return null!==n?r(n[1]):g.y1.isAbsolute(A)?e(A):t(A)}function j(e){return T({onAbsolute:()=>!1,onRelative:()=>!0,onBuiltin:()=>!1},e)}async function Y(e,t,r){const A=null!==e?await r.fetcher.fetch(e,r):null,n=A&&A.localPath?{packageFs:new u.M(g.LZ.root),prefixPath:g.y1.relative(g.LZ.root,A.localPath)}:A;A&&A!==n&&A.releaseFs&&A.releaseFs();return(await a.releaseAfterUseAsync(async()=>await Promise.all(t.map(async e=>T({onAbsolute:async()=>await c.xfs.readFilePromise(e,"utf8"),onRelative:async()=>{if(null===A)throw new Error("Assertion failed: The parent locator should have been fetched");return await A.packageFs.readFilePromise(e,"utf8")},onBuiltin:async e=>await r.project.configuration.firstHook(e=>e.getBuiltinPatch,r.project,e)},e))))).map(e=>"string"==typeof e?e.replace(/\r\n?/g,"\n"):e)}async function G(e,{cache:t,project:r}){const A=r.storedChecksums,n=new p.$,o=r.configuration.makeFetcher(),i=await o.fetch(e,{cache:t,project:r,fetcher:o,checksums:A,report:n}),a=await c.xfs.mktempPromise();return await c.xfs.copyPromise(a,i.prefixPath,{baseFs:i.packageFs}),await c.xfs.writeJsonPromise(g.y1.join(a,".yarn-patch.json"),{locator:s.stringifyLocator(e)}),c.xfs.detachTemp(a),a}async function H(e,t){const r=g.cS.fromPortablePath(e).replace(/\\/g,"/"),A=g.cS.fromPortablePath(t).replace(/\\/g,"/"),{stdout:n}=await d.execvp("git",["diff","--src-prefix=a/","--dst-prefix=b/","--ignore-cr-at-eol","--full-index","--no-index",r,A],{cwd:g.cS.toPortablePath(process.cwd())}),o=r.startsWith("/")?e=>e.slice(1):e=>e;return n.replace(new RegExp(`(a|b)(${a.escapeRegExp(`/${o(r)}/`)})`,"g"),"$1/").replace(new RegExp("(a|b)"+a.escapeRegExp(`/${o(A)}/`),"g"),"$1/").replace(new RegExp(a.escapeRegExp(r+"/"),"g"),"").replace(new RegExp(a.escapeRegExp(A+"/"),"g"),"")}var J=r(71643);function q(e,{configuration:t,report:r}){for(const A of e.parts)for(const e of A.lines)switch(A.type){case m.Context:r.reportInfo(null," "+J.pretty(t,e,"grey"));break;case m.Deletion:r.reportError(i.b.FROZEN_LOCKFILE_EXCEPTION,"- "+J.pretty(t,e,J.Type.REMOVED));break;case m.Insertion:r.reportError(i.b.FROZEN_LOCKFILE_EXCEPTION,"+ "+J.pretty(t,e,J.Type.ADDED));break;default:a.assertNever(A.type)}}var z=r(20624);var W=r(36370),V=r(25413),X=r(85824),_=r(28148),Z=r(40822);class $ extends V.BaseCommand{async execute(){const e=await n.VK.find(this.context.cwd,this.context.plugins),{project:t,workspace:r}=await X.I.find(e,this.context.cwd),A=await _.C.find(e);if(!r)throw new V.WorkspaceRequiredError(t.cwd,this.context.cwd);await t.restoreInstallState();const o=g.y1.resolve(this.context.cwd,g.cS.toPortablePath(this.patchFolder)),i=g.y1.join(o,".yarn-patch.json");if(!c.xfs.existsSync(i))throw new Z.UsageError("The argument folder didn't get created by 'yarn patch'");const a=await c.xfs.readJsonPromise(i),l=s.parseLocator(a.locator,!0);if(!t.storedPackages.has(l.locatorHash))throw new Z.UsageError("No package found in the project for the given locator");const u=await G(l,{cache:A,project:t});this.context.stdout.write(await H(u,o))}}$.usage=Z.Command.Usage({description:"\n This will turn the folder passed in parameter into a patchfile suitable for consumption with the `patch:` protocol.\n\n Only folders generated through `yarn patch` are accepted as valid input for `yarn patch-commit`.\n "}),(0,W.gn)([Z.Command.String()],$.prototype,"patchFolder",void 0),(0,W.gn)([Z.Command.Path("patch-commit")],$.prototype,"execute",null);var ee=r(15815);class te extends V.BaseCommand{async execute(){const e=await n.VK.find(this.context.cwd,this.context.plugins),{project:t,workspace:r}=await X.I.find(e,this.context.cwd),A=await _.C.find(e);if(!r)throw new V.WorkspaceRequiredError(t.cwd,this.context.cwd);await t.restoreInstallState();let o=s.parseLocator(this.package);if("unknown"===o.reference){const r=a.mapAndFilter([...t.storedPackages.values()],e=>e.identHash!==o.identHash||s.isVirtualLocator(e)?a.mapAndFilter.skip:e);if(0===r.length)throw new Z.UsageError("No package found in the project for the given locator");if(r.length>1)throw new Z.UsageError("Multiple candidate packages found; explicitly choose one of them (use `yarn why ` to get more information as to who depends on them):\n"+r.map(t=>"\n- "+s.prettyLocator(e,t)).join(""));o=r[0]}if(!t.storedPackages.has(o.locatorHash))throw new Z.UsageError("No package found in the project for the given locator");await ee.Pk.start({configuration:e,stdout:this.context.stdout},async r=>{const n=await G(o,{cache:A,project:t});r.reportInfo(i.b.UNNAMED,`Package ${s.prettyLocator(e,o)} got extracted with success!`),r.reportInfo(i.b.UNNAMED,"You can now edit the following folder: "+J.pretty(e,g.cS.fromPortablePath(n),"magenta")),r.reportInfo(i.b.UNNAMED,`Once you are done run ${J.pretty(e,"yarn patch-commit "+g.cS.fromPortablePath(n),"cyan")} and Yarn will store a patchfile based on your changes.`)})}}te.usage=Z.Command.Usage({description:'\n This command will cause a package to be extracted in a temporary directory (under a folder named "patch-workdir"). This folder will be editable at will; running `yarn patch` inside it will then cause Yarn to generate a patchfile and register it into your top-level manifest (cf the `patch:` protocol).\n '}),(0,W.gn)([Z.Command.String()],te.prototype,"package",void 0),(0,W.gn)([Z.Command.Path("patch")],te.prototype,"execute",null);const re={configuration:{enableInlineHunks:{description:"If true, the installs will print unmatched patch hunks",type:n.a2.BOOLEAN,default:!1}},commands:[$,te],fetchers:[class{supports(e,t){return!!e.reference.startsWith("patch:")}getLocalPath(e,t){return null}async fetch(e,t){const r=t.checksums.get(e.locatorHash)||null,[A,n,o]=await t.cache.fetchPackageFromCache(e,r,{onHit:()=>t.report.reportCacheHit(e),onMiss:()=>t.report.reportCacheMiss(e,s.prettyLocator(t.project.configuration,e)+" can't be found in the cache and will be fetched from the disk"),loader:()=>this.patchPackage(e,t),skipIntegrityCheck:t.skipIntegrityCheck});return{packageFs:A,releaseFs:n,prefixPath:s.getIdentVendorPath(e),localPath:this.getLocalPath(e,t),checksum:o}}async patchPackage(e,t){const{parentLocator:r,sourceLocator:A,sourceVersion:n,patchPaths:p}=L(e),d=await Y(r,p,t),C=await c.xfs.mktempPromise(),f=g.y1.join(C,"patched.zip"),E=await t.fetcher.fetch(A,t),B=s.getIdentVendorPath(e),y=await(0,h.getLibzipPromise)(),m=new l.d(f,{libzip:y,create:!0,level:t.project.configuration.get("compressionLevel")});await m.mkdirpPromise(B),await a.releaseAfterUseAsync(async()=>{await m.copyPromise(B,E.prefixPath,{baseFs:E.packageFs,stableSort:!0})},E.releaseFs);const w=new u.M(g.y1.resolve(g.LZ.root,B),{baseFs:m});for(const e of d)if(null!==e)try{await S(D(e),{baseFs:w,version:n})}catch(e){if(!(e instanceof I))throw e;const r=t.project.configuration.get("enableInlineHunks"),A=r?"":" (set enableInlineHunks for details)";throw new o.lk(i.b.PATCH_HUNK_FAILED,e.message+A,A=>{r&&q(e.hunk,{configuration:t.project.configuration,report:A})})}return m}}],resolvers:[class{supportsDescriptor(e,t){return!!e.range.startsWith("patch:")}supportsLocator(e,t){return!!e.reference.startsWith("patch:")}shouldPersistResolution(e,t){return!1}bindDescriptor(e,t,r){const{patchPaths:A}=x(e);return A.every(e=>!j(e))?e:s.bindDescriptor(e,{locator:s.stringifyLocator(t)})}getResolutionDependencies(e,t){const{sourceDescriptor:r}=x(e);return[r]}async getCandidates(e,t,r){if(!r.fetchOptions)throw new Error("Assertion failed: This resolver cannot be used unless a fetcher is configured");const{parentLocator:A,sourceDescriptor:n,patchPaths:o}=x(e),i=await Y(A,o,r.fetchOptions),s=t.get(n.descriptorHash);if(void 0===s)throw new Error("Assertion failed: The dependency should have been resolved");return[U(e,{parentLocator:A,sourcePackage:s,patchPaths:o,patchHash:z.makeHash("2",...i).slice(0,6)})]}async getSatisfying(e,t,r){return null}async resolve(e,t){const{sourceLocator:r}=L(e);return{...await t.resolver.resolve(r,t),...e}}}]}},83228:(e,t,r)=>{"use strict";r.r(t),r.d(t,{PnpInstaller:()=>k,PnpLinker:()=>S,default:()=>Y,getPnpPath:()=>T,jsInstallUtils:()=>A,pnpUtils:()=>n,quotePathIfNeeded:()=>j});var A={};r.r(A),r.d(A,{checkAndReportManifestCompatibility:()=>y,extractBuildScripts:()=>m,getExtractHint:()=>Q,hasBindingGyp:()=>D});var n={};r.r(n),r.d(n,{getUnpluggedPath:()=>b});var o=r(39922),i=r(43896),s=r(46009),a=r(53887),c=r.n(a),g=r(54143),l=r(71643),u=r(73632),h=r(32485),p=r(92659),d=r(46611),C=r(17674),f=r(75448),I=r(34432),E=r(40822),B=r(92409);function y(e,t,r,{configuration:A,report:n}){return d.G.isManifestFieldCompatible(t.manifest.os,process.platform)?!!d.G.isManifestFieldCompatible(t.manifest.cpu,process.arch)||(null==n||n.reportWarningOnce(p.b.INCOMPATIBLE_CPU,`${g.prettyLocator(A,e)} The CPU architecture ${process.arch} is incompatible with this module, ${r} skipped.`),!1):(null==n||n.reportWarningOnce(p.b.INCOMPATIBLE_OS,`${g.prettyLocator(A,e)} The platform ${process.platform} is incompatible with this module, ${r} skipped.`),!1)}function m(e,t,r,{configuration:A,report:n}){const o=[];for(const e of["preinstall","install","postinstall"])t.manifest.scripts.has(e)&&o.push([B.k.SCRIPT,e]);if(!t.manifest.scripts.has("install")&&t.misc.hasBindingGyp&&o.push([B.k.SHELLCODE,"node-gyp rebuild"]),0===o.length)return[];if(!A.get("enableScripts")&&!r.built)return null==n||n.reportWarningOnce(p.b.DISABLED_BUILD_SCRIPTS,g.prettyLocator(A,e)+" lists build scripts, but all build scripts have been disabled."),[];if(e.linkType!==h.Un.HARD)return null==n||n.reportWarningOnce(p.b.SOFT_LINK_BUILD,g.prettyLocator(A,e)+" lists build scripts, but is referenced through a soft link. Soft links don't support build scripts, so they'll be ignored."),[];if(r&&!1===r.built)return null==n||n.reportInfoOnce(p.b.BUILD_DISABLED,g.prettyLocator(A,e)+" lists build scripts, but its build has been explicitly disabled through configuration."),[];return y(e,t,"build",{configuration:A,report:n})?o:[]}const w=new Set([".exe",".h",".hh",".hpp",".c",".cc",".cpp",".java",".jar",".node"]);function Q(e){return e.packageFs.getExtractHint({relevantExtensions:w})}function D(e){const t=s.y1.join(e.prefixPath,"binding.gyp");return e.packageFs.existsSync(t)}function b(e,{configuration:t}){return s.y1.resolve(t.get("pnpUnpluggedFolder"),g.slugifyLocator(e))}const v=new Set([g.makeIdent(null,"nan").identHash,g.makeIdent(null,"node-gyp").identHash,g.makeIdent(null,"node-pre-gyp").identHash,g.makeIdent(null,"node-addon-api").identHash,g.makeIdent(null,"fsevents").identHash]);class S{constructor(){this.mode="strict"}supportsPackage(e,t){return"pnp"===t.project.configuration.get("nodeLinker")&&t.project.configuration.get("pnpMode")===this.mode}async findPackageLocation(e,t){const r=T(t.project).main;if(!i.xfs.existsSync(r))throw new E.UsageError(`The project in ${l.pretty(t.project.configuration,t.project.cwd+"/package.json",l.Type.PATH)} doesn't seem to have been installed - running an install there might help`);const A=u.dynamicRequireNoCache(r),n={name:g.requirableIdent(e),reference:e.reference},o=A.getPackageInformation(n);if(!o)throw new E.UsageError(`Couldn't find ${g.prettyLocator(t.project.configuration,e)} in the currently installed PnP map - running an install might help`);return s.cS.toPortablePath(o.packageLocation)}async findPackageLocator(e,t){const A=T(t.project).main;if(!i.xfs.existsSync(A))return null;const n=s.cS.fromPortablePath(A),o=u.dynamicRequire(n);delete r.c[n];const a=o.findPackageLocator(s.cS.fromPortablePath(e));return a?g.makeLocator(g.parseIdent(a.name),a.reference):null}makeInstaller(e){return new k(e)}}class k{constructor(e){this.opts=e,this.mode="strict",this.packageRegistry=new Map,this.virtualTemplates=new Map,this.customData={store:new Map},this.unpluggedPaths=new Set,this.opts=e}getCustomDataKey(){return JSON.stringify({name:"PnpInstaller",version:1})}attachCustomData(e){this.customData=e}async installPackage(e,t){const r=g.requirableIdent(e),A=e.reference,n=!!this.opts.project.tryWorkspaceByLocator(e),o=e.peerDependencies.size>0&&!g.isVirtualLocator(e),i=!o&&!n,a=!o&&e.linkType!==h.Un.SOFT;let c=this.customData.store.get(e.locatorHash);void 0===c&&(c=await async function(e,t){var r;const A=null!==(r=await d.G.tryFind(t.prefixPath,{baseFs:t.packageFs}))&&void 0!==r?r:new d.G,n=new Set(["preinstall","install","postinstall"]);for(const e of A.scripts.keys())n.has(e)||A.scripts.delete(e);return{manifest:{os:A.os,cpu:A.cpu,scripts:A.scripts,preferUnplugged:A.preferUnplugged},misc:{extractHint:Q(t),hasBindingGyp:D(t)}}}(0,t),e.linkType===h.Un.HARD&&this.customData.store.set(e.locatorHash,c));const l=this.opts.project.getDependencyMeta(e,e.version),p=i?m(e,c,l,{configuration:this.opts.project.configuration,report:this.opts.report}):[],f=a?await this.unplugPackageIfNeeded(e,c,t,l):t.packageFs;if(s.y1.isAbsolute(t.prefixPath))throw new Error(`Assertion failed: Expected the prefix path (${t.prefixPath}) to be relative to the parent`);const I=s.y1.resolve(f.getRealPath(),t.prefixPath),E=N(this.opts.project.cwd,I),B=new Map,y=new Set;if(g.isVirtualLocator(e)){for(const t of e.peerDependencies.values())B.set(g.requirableIdent(t),null),y.add(g.stringifyIdent(t));if(!this.opts.project.tryWorkspaceByLocator(e)){const t=g.devirtualizeLocator(e);this.virtualTemplates.set(t.locatorHash,{location:N(this.opts.project.cwd,C.p.resolveVirtual(I)),locator:t})}}return u.getMapWithDefault(this.packageRegistry,r).set(A,{packageLocation:E,packageDependencies:B,packagePeers:y,linkType:e.linkType,discardFromLookup:t.discardFromLookup||!1}),{packageLocation:I,buildDirective:p.length>0?p:null}}async attachInternalDependencies(e,t){const r=this.getPackageInformation(e);for(const[e,A]of t){const t=g.areIdentsEqual(e,A)?A.reference:[g.requirableIdent(A),A.reference];r.packageDependencies.set(g.requirableIdent(e),t)}}async attachExternalDependents(e,t){for(const r of t){this.getDiskInformation(r).packageDependencies.set(g.requirableIdent(e),e.reference)}}async finalizeInstall(){const e=new Set;for(const{locator:e,location:t}of this.virtualTemplates.values())u.getMapWithDefault(this.packageRegistry,g.stringifyIdent(e)).set(e.reference,{packageLocation:t,packageDependencies:new Map,packagePeers:new Set,linkType:h.Un.SOFT,discardFromLookup:!1});this.packageRegistry.set(null,new Map([[null,this.getPackageInformation(this.opts.project.topLevelWorkspace.anchoredLocator)]]));const t=this.opts.project.configuration.get("pnpFallbackMode"),r=e,A=this.opts.project.workspaces.map(({anchoredLocator:e})=>({name:g.requirableIdent(e),reference:e.reference})),n="none"!==t,o=[],i=new Map,s=u.buildIgnorePattern([".yarn/sdks/**",...this.opts.project.configuration.get("pnpIgnorePatterns")]),a=this.packageRegistry,c=this.opts.project.configuration.get("pnpShebang");if("dependencies-only"===t)for(const e of this.opts.project.storedPackages.values())this.opts.project.tryWorkspaceByLocator(e)&&o.push({name:g.requirableIdent(e),reference:e.reference});return await this.finalizeInstallWithPnp({blacklistedLocations:r,dependencyTreeRoots:A,enableTopLevelFallback:n,fallbackExclusionList:o,fallbackPool:i,ignorePattern:s,packageRegistry:a,shebang:c}),{customData:this.customData}}async finalizeInstallWithPnp(e){if(this.opts.project.configuration.get("pnpMode")!==this.mode)return;const t=T(this.opts.project),r=this.opts.project.configuration.get("pnpDataPath");if(await i.xfs.removePromise(t.other),"pnp"!==this.opts.project.configuration.get("nodeLinker"))return await i.xfs.removePromise(t.main),void await i.xfs.removePromise(r);const A=await this.locateNodeModules(e.ignorePattern);if(A.length>0){this.opts.report.reportWarning(p.b.DANGEROUS_NODE_MODULES,"One or more node_modules have been detected and will be removed. This operation may take some time.");for(const e of A)await i.xfs.removePromise(e)}if(this.opts.project.configuration.get("pnpEnableInlining")){const A=(0,I.gY)(e);await i.xfs.changeFilePromise(t.main,A,{automaticNewlines:!0}),await i.xfs.chmodPromise(t.main,493),await i.xfs.removePromise(r)}else{const A=s.y1.relative(s.y1.dirname(t.main),r),{dataFile:n,loaderFile:o}=(0,I.Q$)({...e,dataLocation:A});await i.xfs.changeFilePromise(t.main,o,{automaticNewlines:!0}),await i.xfs.chmodPromise(t.main,493),await i.xfs.changeFilePromise(r,n,{automaticNewlines:!0}),await i.xfs.chmodPromise(r,420)}const n=this.opts.project.configuration.get("pnpUnpluggedFolder");if(0===this.unpluggedPaths.size)await i.xfs.removePromise(n);else for(const e of await i.xfs.readdirPromise(n)){const t=s.y1.resolve(n,e);this.unpluggedPaths.has(t)||await i.xfs.removePromise(t)}}async locateNodeModules(e){const t=[],r=e?new RegExp(e):null;for(const e of this.opts.project.workspaces){const A=s.y1.join(e.cwd,"node_modules");if(r&&r.test(s.y1.relative(this.opts.project.cwd,e.cwd))||!i.xfs.existsSync(A))continue;const n=await i.xfs.readdirPromise(A,{withFileTypes:!0}),o=n.filter(e=>!e.isDirectory()||".bin"===e.name||!e.name.startsWith("."));if(o.length===n.length)t.push(A);else for(const e of o)t.push(s.y1.join(A,e.name))}return t}async unplugPackageIfNeeded(e,t,r,A){return this.shouldBeUnplugged(e,t,A)?this.unplugPackage(e,r):r.packageFs}shouldBeUnplugged(e,t,r){return void 0!==r.unplugged?r.unplugged:!!v.has(e.identHash)||(null!==t.manifest.preferUnplugged?t.manifest.preferUnplugged:!!(m(e,t,r,{configuration:this.opts.project.configuration}).length>0||t.misc.extractHint))}async unplugPackage(e,t){const r=b(e,{configuration:this.opts.project.configuration});this.unpluggedPaths.add(r);const A=s.y1.join(r,t.prefixPath,".ready");return await i.xfs.existsPromise(A)||(await i.xfs.mkdirPromise(r,{recursive:!0}),await i.xfs.copyPromise(r,s.LZ.dot,{baseFs:t.packageFs,overwrite:!1}),await i.xfs.writeFilePromise(A,"")),new f.M(r)}getPackageInformation(e){const t=g.requirableIdent(e),r=e.reference,A=this.packageRegistry.get(t);if(!A)throw new Error(`Assertion failed: The package information store should have been available (for ${g.prettyIdent(this.opts.project.configuration,e)})`);const n=A.get(r);if(!n)throw new Error(`Assertion failed: The package information should have been available (for ${g.prettyLocator(this.opts.project.configuration,e)})`);return n}getDiskInformation(e){const t=u.getMapWithDefault(this.packageRegistry,"@@disk"),r=N(this.opts.project.cwd,e);return u.getFactoryWithDefault(t,r,()=>({packageLocation:r,packageDependencies:new Map,packagePeers:new Set,linkType:h.Un.SOFT,discardFromLookup:!1}))}}function N(e,t){let r=s.y1.relative(e,t);return r.match(/^\.{0,2}\//)||(r="./"+r),r.replace(/\/?$/,"/")}var F=r(36370),K=r(25413),M=r(85824),R=r(28148),x=r(15815),L=r(36545),P=r(2401),O=r.n(P);class U extends K.BaseCommand{constructor(){super(...arguments),this.patterns=[],this.all=!1,this.recursive=!1,this.json=!1}async execute(){const e=await o.VK.find(this.context.cwd,this.context.plugins),{project:t,workspace:r}=await M.I.find(e,this.context.cwd),A=await R.C.find(e);if(!r)throw new K.WorkspaceRequiredError(t.cwd,this.context.cwd);if("pnp"!==e.get("nodeLinker"))throw new E.UsageError("This command can only be used if the `nodeLinker` option is set to `pnp`");await t.restoreInstallState();const n=new Set(this.patterns),i=this.patterns.map(t=>{const r=g.parseDescriptor(t),A="unknown"!==r.range?r:g.makeDescriptor(r,"*");if(!c().validRange(A.range))throw new E.UsageError(`The range of the descriptor patterns must be a valid semver range (${g.prettyDescriptor(e,A)})`);return e=>{const r=g.stringifyIdent(e);return!!O().isMatch(r,g.stringifyIdent(A))&&(!(e.version&&!L.satisfiesWithPrereleases(e.version,A.range))&&(n.delete(t),!0))}}),s=e=>{const r=new Set,A=[],n=(e,o)=>{if(!r.has(e.locatorHash)&&(r.add(e.locatorHash),!t.tryWorkspaceByLocator(e)&&i.some(t=>t(e))&&A.push(e),!(o>0)||this.recursive))for(const r of e.dependencies.values()){const e=t.storedResolutions.get(r.descriptorHash);if(!e)throw new Error("Assertion failed: The resolution should have been registered");const A=t.storedPackages.get(e);if(!A)throw new Error("Assertion failed: The package should have been registered");n(A,o+1)}};for(const r of e){const e=t.storedPackages.get(r.anchoredLocator.locatorHash);if(!e)throw new Error("Assertion failed: The package should have been registered");n(e,0)}return A};let a,h;if(this.all&&this.recursive?(a=(()=>{const e=[];for(const r of t.storedPackages.values())t.tryWorkspaceByLocator(r)||g.isVirtualLocator(r)||!i.some(e=>e(r))||e.push(r);return e})(),h="the project"):this.all?(a=s(t.workspaces),h="any workspace"):(a=s([r]),h="this workspace"),n.size>1)throw new E.UsageError(`Patterns ${l.prettyList(e,n,l.Type.CODE)} don't match any packages referenced by ${h}`);if(n.size>0)throw new E.UsageError(`Pattern ${l.prettyList(e,n,l.Type.CODE)} doesn't match any packages referenced by ${h}`);a=u.sortMap(a,e=>g.stringifyLocator(e));return(await x.Pk.start({configuration:e,stdout:this.context.stdout,json:this.json},async r=>{var n;for(const A of a){const o=null!==(n=A.version)&&void 0!==n?n:"unknown";t.topLevelWorkspace.manifest.ensureDependencyMeta(g.makeDescriptor(A,o)).unplugged=!0,r.reportInfo(p.b.UNNAMED,`Will unpack ${g.prettyLocator(e,A)} to ${l.pretty(e,b(A,{configuration:e}),l.Type.PATH)}`),r.reportJson({locator:g.stringifyLocator(A),version:o})}await t.topLevelWorkspace.persistManifest(),r.reportSeparator(),await t.install({cache:A,report:r})})).exitCode()}}U.usage=E.Command.Usage({description:"force the unpacking of a list of packages",details:"\n This command will add the selectors matching the specified patterns to the list of packages that must be unplugged when installed.\n\n A package being unplugged means that instead of being referenced directly through its archive, it will be unpacked at install time in the directory configured via `pnpUnpluggedFolder`. Note that unpacking packages this way is generally not recommended because it'll make it harder to store your packages within the repository. However, it's a good approach to quickly and safely debug some packages, and can even sometimes be required depending on the context (for example when the package contains shellscripts).\n\n Running the command will set a persistent flag inside your top-level `package.json`, in the `dependenciesMeta` field. As such, to undo its effects, you'll need to revert the changes made to the manifest and run `yarn install` to apply the modification.\n\n By default, only direct dependencies from the current workspace are affected. If `-A,--all` is set, direct dependencies from the entire project are affected. Using the `-R,--recursive` flag will affect transitive dependencies as well as direct ones.\n\n This command accepts glob patterns inside the scope and name components (not the range). Make sure to escape the patterns to prevent your own shell from trying to expand them.\n ",examples:[["Unplug the lodash dependency from the active workspace","yarn unplug lodash"],["Unplug all instances of lodash referenced by any workspace","yarn unplug lodash -A"],["Unplug all instances of lodash referenced by the active workspace and its dependencies","yarn unplug lodash -R"],["Unplug all instances of lodash, anywhere","yarn unplug lodash -AR"],["Unplug one specific version of lodash","yarn unplug lodash@1.2.3"],["Unplug all packages with the `@babel` scope","yarn unplug '@babel/*'"],["Unplug all packages (only for testing, not recommended)","yarn unplug -R '*'"]]}),(0,F.gn)([E.Command.Rest()],U.prototype,"patterns",void 0),(0,F.gn)([E.Command.Boolean("-A,--all",{description:"Unplug direct dependencies from the entire project"})],U.prototype,"all",void 0),(0,F.gn)([E.Command.Boolean("-R,--recursive",{description:"Unplug both direct and transitive dependencies"})],U.prototype,"recursive",void 0),(0,F.gn)([E.Command.Boolean("--json",{description:"Format the output as an NDJSON stream"})],U.prototype,"json",void 0),(0,F.gn)([E.Command.Path("unplug")],U.prototype,"execute",null);const T=e=>{let t,r;return"module"===e.topLevelWorkspace.manifest.type?(t=".pnp.cjs",r=".pnp.js"):(t=".pnp.js",r=".pnp.cjs"),{main:s.y1.join(e.cwd,t),other:s.y1.join(e.cwd,r)}},j=e=>/\s/.test(e)?JSON.stringify(e):e;const Y={hooks:{populateYarnPaths:async function(e,t){t(T(e).main),t(T(e).other),t(e.configuration.get("pnpDataPath")),t(e.configuration.get("pnpUnpluggedFolder"))},setupScriptEnvironment:async function(e,t,r){const A=T(e).main,n="--require "+j(s.cS.fromPortablePath(A));if(A.includes(" ")&&c().lt(process.versions.node,"12.0.0"))throw new Error(`Expected the build location to not include spaces when using Node < 12.0.0 (${process.versions.node})`);if(i.xfs.existsSync(A)){let e=t.NODE_OPTIONS||"";const r=/\s*--require\s+\S*\.pnp\.c?js\s*/g;e=e.replace(r," ").trim(),e=e?`${n} ${e}`:n,t.NODE_OPTIONS=e}}},configuration:{nodeLinker:{description:'The linker used for installing Node packages, one of: "pnp", "node-modules"',type:o.a2.STRING,default:"pnp"},pnpMode:{description:"If 'strict', generates standard PnP maps. If 'loose', merges them with the n_m resolution.",type:o.a2.STRING,default:"strict"},pnpShebang:{description:"String to prepend to the generated PnP script",type:o.a2.STRING,default:"#!/usr/bin/env node"},pnpIgnorePatterns:{description:"Array of glob patterns; files matching them will use the classic resolution",type:o.a2.STRING,default:[],isArray:!0},pnpEnableInlining:{description:"If true, the PnP data will be inlined along with the generated loader",type:o.a2.BOOLEAN,default:!0},pnpFallbackMode:{description:"If true, the generated PnP loader will follow the top-level fallback rule",type:o.a2.STRING,default:"dependencies-only"},pnpUnpluggedFolder:{description:"Folder where the unplugged packages must be stored",type:o.a2.ABSOLUTE_PATH,default:"./.yarn/unplugged"},pnpDataPath:{description:"Path of the file where the PnP data (used by the loader) must be written",type:o.a2.ABSOLUTE_PATH,default:"./.pnp.data.json"}},linkers:[S],commands:[U]}},43418:(e,t,r)=>{"use strict";r.r(t);var A=r(50683),n=r.n(A);Object.fromEntries||(Object.fromEntries=n());var o=r(59355),i=r(10419),s=r(45330);(0,i.D)({binaryVersion:o.o||"",pluginConfiguration:(0,s.e)()})},25413:(e,t,r)=>{"use strict";r.r(t),r.d(t,{BaseCommand:()=>A.F,WorkspaceRequiredError:()=>s,getDynamicLibs:()=>c,getPluginConfiguration:()=>g.e,main:()=>h.D,openWorkspace:()=>u,pluginCommands:()=>p.f});var A=r(56087),n=r(46611),o=r(46009),i=r(40822);class s extends i.UsageError{constructor(e,t){super(`This command can only be run from within a workspace of your project (${o.y1.relative(e,t)} isn't a workspace of ${o.y1.join(e,n.G.fileName)}).`)}}const a=["@yarnpkg/cli","@yarnpkg/core","@yarnpkg/fslib","@yarnpkg/libzip","@yarnpkg/parsers","@yarnpkg/shell","clipanion","semver","yup"],c=()=>new Map(a.map(e=>[e,r(98497)(e)]));var g=r(45330),l=r(85824);async function u(e,t){const{project:r,workspace:A}=await l.I.find(e,t);if(!A)throw new s(r.cwd,t);return A}var h=r(10419),p=r(15683)},10419:(e,t,r)=>{"use strict";r.d(t,{D:()=>f});var A=r(36545),n=r(39922),o=r(81832),i=r(43896),s=r(46009),a=r(63129),c=r(5864),g=r(40822),l=r(35747),u=r(15683),h=r(36370),p=r(71643),d=r(56087);class C extends d.F{async execute(){const e=await n.VK.find(this.context.cwd,this.context.plugins);this.context.stdout.write((e=>`\n${p.pretty(e,"Welcome on Yarn 2!","bold")} 🎉 Thanks for helping us shape our vision of how projects\nshould be managed going forward.\n\nBeing still in RC, Yarn 2 isn't completely stable yet. Some features might be\nmissing, and some behaviors may have received major overhaul. In case of doubt,\nuse the following URLs to get some insight:\n\n - The changelog:\n ${p.pretty(e,"https://github.com/yarnpkg/berry/tree/CHANGELOG.md","cyan")}\n\n - Our issue tracker:\n ${p.pretty(e,"https://github.com/yarnpkg/berry","cyan")}\n\n - Our Discord server:\n ${p.pretty(e,"https://discord.gg/yarnpkg","cyan")}\n\nWe're hoping you will enjoy the experience. For now, a good start is to run\nthe two following commands:\n\n ${p.pretty(e,"find . -name node_modules -prune -exec rm -r {} \\;","magenta")}\n ${p.pretty(e,"yarn install","magenta")}\n\nOne last trick! If you need at some point to upgrade Yarn to a nightly build,\nthe following command will install the CLI straight from master:\n\n ${p.pretty(e,"yarn set version from sources","magenta")}\n\nSee you later 👋\n`)(e).trim()+"\n")}}async function f({binaryVersion:e,pluginConfiguration:t}){async function r(){const h=new g.Cli({binaryLabel:"Yarn Package Manager",binaryName:"yarn",binaryVersion:e});h.register(C);try{await async function h(p){var d,C,f,I,E;const B=process.versions.node,y=">=10.17 <14 || >14.1";if("1"!==process.env.YARN_IGNORE_NODE&&!A.satisfiesWithPrereleases(B,y))throw new g.UsageError(`This tool requires a Node version compatible with ${y} (got ${B}). Upgrade Node, or set \`YARN_IGNORE_NODE=1\` in your environment.`);const m=await n.VK.find(s.cS.toPortablePath(process.cwd()),t,{usePath:!0,strict:!1}),w=m.get("yarnPath"),Q=m.get("ignorePath"),D=m.get("ignoreCwd");if(!Q&&!D&&w===s.cS.toPortablePath(s.cS.resolve(process.argv[1])))return process.env.YARN_IGNORE_PATH="1",process.env.YARN_IGNORE_CWD="1",void await h(p);if(null===w||Q){Q&&delete process.env.YARN_IGNORE_PATH;m.get("enableTelemetry")&&!c.isCI&&process.stdout.isTTY&&(n.VK.telemetry=new o.E(m,"puba9cdc10ec5790a2cf4969dd413a47270")),null===(d=n.VK.telemetry)||void 0===d||d.reportVersion(e);for(const[e,t]of m.plugins.entries()){u.f.has(null!==(f=null===(C=e.match(/^@yarnpkg\/plugin-(.*)$/))||void 0===C?void 0:C[1])&&void 0!==f?f:"")&&(null===(I=n.VK.telemetry)||void 0===I||I.reportPluginName(e));for(const e of t.commands||[])p.register(e)}const A=p.process(process.argv.slice(2));A.help||null===(E=n.VK.telemetry)||void 0===E||E.reportCommandName(A.path.join(" "));const i=A.cwd;if(void 0!==i&&!D){const e=(0,l.realpathSync)(process.cwd()),t=(0,l.realpathSync)(i);if(e!==t)return process.chdir(i),void await r()}await p.runExit(A,{cwd:s.cS.toPortablePath(process.cwd()),plugins:t,quiet:!1,stdin:process.stdin,stdout:process.stdout,stderr:process.stderr})}else if(i.xfs.existsSync(w))try{!function(e){const t=s.cS.fromPortablePath(e);process.on("SIGINT",()=>{}),t?(0,a.execFileSync)(process.execPath,[t,...process.argv.slice(2)],{stdio:"inherit",env:{...process.env,YARN_IGNORE_PATH:"1",YARN_IGNORE_CWD:"1"}}):(0,a.execFileSync)(t,process.argv.slice(2),{stdio:"inherit",env:{...process.env,YARN_IGNORE_PATH:"1",YARN_IGNORE_CWD:"1"}})}(w)}catch(e){process.exitCode=e.code||1}else process.stdout.write(p.error(new Error(`The "yarn-path" option has been set (in ${m.sources.get("yarnPath")}), but the specified location doesn't exist (${w}).`))),process.exitCode=1}(h)}catch(e){process.stdout.write(h.error(e)),process.exitCode=1}}return r().catch(e=>{process.stdout.write(e.stack||e.message),process.exitCode=1}).finally(()=>i.xfs.rmtempPromise())}(0,h.gn)([g.Command.Path("--welcome")],C.prototype,"execute",null)},15683:(e,t,r)=>{"use strict";r.d(t,{f:()=>A});const A=new Map([["constraints",[["constraints","query"],["constraints","source"],["constraints"]]],["exec",[]],["interactive-tools",[["search"],["upgrade-interactive"]]],["stage",[["stage"]]],["typescript",[]],["version",[["version","apply"],["version","check"],["version"]]],["workspace-tools",[["workspaces","focus"],["workspaces","foreach"]]]])},56087:(e,t,r)=>{"use strict";r.d(t,{F:()=>o});var A=r(36370),n=r(40822);class o extends n.Command{}(0,A.gn)([n.Command.String("--cwd",{hidden:!0})],o.prototype,"cwd",void 0)},28148:(e,t,r)=>{"use strict";r.d(t,{C:()=>I});var A=r(78420),n=r(15037),o=r(90739),i=r(14626),s=r(46009),a=r(43896),c=r(65281),g=r(35747),l=r.n(g),u=r(92659),h=r(35691),p=r(20624),d=r(73632),C=r(54143);const f=7;class I{constructor(e,{configuration:t,immutable:r=t.get("enableImmutableCache"),check:A=!1}){this.markedFiles=new Set,this.mutexes=new Map,this.configuration=t,this.cwd=e,this.immutable=r,this.check=A;const n=t.get("cacheKeyOverride");if(null!==n)this.cacheKey=""+n;else{const e=t.get("compressionLevel"),r=e!==o.k?"c"+e:"";this.cacheKey=[f,r].join("")}}static async find(e,{immutable:t,check:r}={}){const A=new I(e.get("cacheFolder"),{configuration:e,immutable:t,check:r});return await A.setup(),A}get mirrorCwd(){if(!this.configuration.get("enableMirror"))return null;const e=this.configuration.get("globalFolder")+"/cache";return e!==this.cwd?e:null}getVersionFilename(e){return`${C.slugifyLocator(e)}-${this.cacheKey}.zip`}getChecksumFilename(e,t){const r=function(e){const t=e.indexOf("/");return-1!==t?e.slice(t+1):e}(t).slice(0,10);return`${C.slugifyLocator(e)}-${r}.zip`}getLocatorPath(e,t){if(null===this.mirrorCwd)return s.y1.resolve(this.cwd,this.getVersionFilename(e));if(null===t)return null;return E(t)!==this.cacheKey?null:s.y1.resolve(this.cwd,this.getChecksumFilename(e,t))}getLocatorMirrorPath(e){const t=this.mirrorCwd;return null!==t?s.y1.resolve(t,this.getVersionFilename(e)):null}async setup(){if(!this.configuration.get("enableGlobalCache")){await a.xfs.mkdirPromise(this.cwd,{recursive:!0});const e=s.y1.resolve(this.cwd,".gitignore");await a.xfs.changeFilePromise(e,"/.gitignore\n*.flock\n")}}async fetchPackageFromCache(e,t,{onHit:r,onMiss:g,loader:f,skipIntegrityCheck:I}){const B=this.getLocatorMirrorPath(e),y=new A.S,m=async(e,r=null)=>{const A=I&&t?t:`${this.cacheKey}/${await p.checksumFile(e)}`;if(null!==r){if(A!==(I&&t?t:`${this.cacheKey}/${await p.checksumFile(r)}`))throw new h.lk(u.b.CACHE_CHECKSUM_MISMATCH,"The remote archive doesn't match the local checksum - has the local cache been corrupted?")}if(null!==t&&A!==t){let e;switch(e=this.check?"throw":E(t)!==E(A)?"update":this.configuration.get("checksumBehavior"),e){case"ignore":return t;case"update":return A;default:case"throw":throw new h.lk(u.b.CACHE_CHECKSUM_MISMATCH,"The remote archive doesn't match the expected checksum")}}return A},w=async t=>{if(!f)throw new Error("Cache check required but no loader configured for "+C.prettyLocator(this.configuration,e));const r=await f(),A=r.getRealPath();return r.saveAndClose(),await a.xfs.chmodPromise(A,420),await m(t,A)},Q=async()=>{if(null===B||!await a.xfs.existsPromise(B)){const e=await f(),t=e.getRealPath();return e.saveAndClose(),t}const t=await a.xfs.mktempPromise(),r=s.y1.join(t,this.getVersionFilename(e));return await a.xfs.copyFilePromise(B,r,l().constants.COPYFILE_FICLONE),r},D=async()=>{if(!f)throw new Error("Cache entry required but missing for "+C.prettyLocator(this.configuration,e));if(this.immutable)throw new h.lk(u.b.IMMUTABLE_CACHE,"Cache entry required but missing for "+C.prettyLocator(this.configuration,e));const t=await Q();await a.xfs.chmodPromise(t,420);const r=await m(t),A=this.getLocatorPath(e,r);if(!A)throw new Error("Assertion failed: Expected the cache path to be available");return await this.writeFileWithLock(A,async()=>await this.writeFileWithLock(B,async()=>(await a.xfs.movePromise(t,A),null!==B&&await a.xfs.copyFilePromise(A,B,l().constants.COPYFILE_FICLONE),[A,r])))};for(let t;t=this.mutexes.get(e.locatorHash);)await t;const[b,v]=await(async()=>{const A=(async()=>{const A=this.getLocatorPath(e,t),n=null!==A&&await y.existsPromise(A),o=n?r:g;if(o&&o(),n){let e=null;const t=A;return e=this.check?await w(t):await m(t),[t,e]}return D()})();this.mutexes.set(e.locatorHash,A);try{return await A}finally{this.mutexes.delete(e.locatorHash)}})();this.markedFiles.add(b);let S=null;const k=await(0,c.getLibzipPromise)(),N=new n.v(()=>d.prettifySyncErrors(()=>S=new o.d(b,{baseFs:y,libzip:k,readOnly:!0}),t=>`Failed to open the cache entry for ${C.prettyLocator(this.configuration,e)}: ${t}`),s.y1);return[new i.K(b,{baseFs:N,pathUtils:s.y1}),()=>{null!==S&&S.discardAndClose()},v]}async writeFileWithLock(e,t){return null===e?await t():(await a.xfs.mkdirPromise(s.y1.dirname(e),{recursive:!0}),await a.xfs.lockPromise(e,async()=>await t()))}}function E(e){const t=e.indexOf("/");return-1!==t?e.slice(0,t):null}},39922:(e,t,r)=>{"use strict";r.d(t,{VK:()=>W,nh:()=>U,tr:()=>O,a5:()=>j,EW:()=>z,a2:()=>T});var A=r(43896),n=r(46009),o=r(90739),i=r(11640),s=r(54738),a=r.n(s),c=r(5864),g=r(40822),l=r(61578),u=r.n(l),h=r(53887),p=r.n(h),d=r(92413),C=r(92659),f=r(54143);const I={hooks:{reduceDependency:(e,t,r,A,{resolver:n,resolveOptions:o})=>{for(const{pattern:A,reference:i}of t.topLevelWorkspace.manifest.resolutions){if(A.from&&A.from.fullName!==f.requirableIdent(r))continue;if(A.from&&A.from.description&&A.from.description!==r.reference)continue;if(A.descriptor.fullName!==f.requirableIdent(e))continue;if(A.descriptor.description&&A.descriptor.description!==e.range)continue;return n.bindDescriptor(f.makeDescriptor(e,i),t.topLevelWorkspace.anchoredLocator,o)}return e},validateProject:async(e,t)=>{for(const r of e.workspaces){const A=f.prettyWorkspace(e.configuration,r);await e.configuration.triggerHook(e=>e.validateWorkspace,r,{reportWarning:(e,r)=>t.reportWarning(e,`${A}: ${r}`),reportError:(e,r)=>t.reportError(e,`${A}: ${r}`)})}},validateWorkspace:async(e,t)=>{const{manifest:r}=e;r.resolutions.length&&e.cwd!==e.project.cwd&&r.errors.push(new Error("Resolutions field will be ignored"));for(const e of r.errors)t.reportWarning(C.b.INVALID_MANIFEST,e.message)}}};var E=r(46611),B=r(35691);class y{constructor(e){this.fetchers=e}supports(e,t){return!!this.tryFetcher(e,t)}getLocalPath(e,t){return this.getFetcher(e,t).getLocalPath(e,t)}async fetch(e,t){const r=this.getFetcher(e,t);return await r.fetch(e,t)}tryFetcher(e,t){const r=this.fetchers.find(r=>r.supports(e,t));return r||null}getFetcher(e,t){const r=this.fetchers.find(r=>r.supports(e,t));if(!r)throw new B.lk(C.b.FETCHER_NOT_FOUND,f.prettyLocator(t.project.configuration,e)+" isn't supported by any available fetcher");return r}}var m=r(27092),w=r(52779),Q=r(60895);class D{static isVirtualDescriptor(e){return!!e.range.startsWith(D.protocol)}static isVirtualLocator(e){return!!e.reference.startsWith(D.protocol)}supportsDescriptor(e,t){return D.isVirtualDescriptor(e)}supportsLocator(e,t){return D.isVirtualLocator(e)}shouldPersistResolution(e,t){return!1}bindDescriptor(e,t,r){throw new Error('Assertion failed: calling "bindDescriptor" on a virtual descriptor is unsupported')}getResolutionDependencies(e,t){throw new Error('Assertion failed: calling "getResolutionDependencies" on a virtual descriptor is unsupported')}async getCandidates(e,t,r){throw new Error('Assertion failed: calling "getCandidates" on a virtual descriptor is unsupported')}async getSatisfying(e,t,r){throw new Error('Assertion failed: calling "getSatisfying" on a virtual descriptor is unsupported')}async resolve(e,t){throw new Error('Assertion failed: calling "resolve" on a virtual locator is unsupported')}}D.protocol="virtual:";var b=r(75448),v=r(94538);class S{supports(e){return!!e.reference.startsWith(v.d.protocol)}getLocalPath(e,t){return this.getWorkspace(e,t).cwd}async fetch(e,t){const r=this.getWorkspace(e,t).cwd;return{packageFs:new b.M(r),prefixPath:n.LZ.dot,localPath:r}}getWorkspace(e,t){return t.project.getWorkspaceByCwd(e.reference.slice(v.d.protocol.length))}}var k=r(81111),N=r(71643),F=r(73632),K=r(32282),M=r.n(K);function R(e){return("undefined"!=typeof require?require:r(32178))(e)}var x=r(36545),L=r(32485);const P=new Set(["binFolder","version","flags","profile","gpg","ignoreNode","wrapOutput"]),O=".yarnrc.yml",U="yarn.lock";var T;!function(e){e.ANY="ANY",e.BOOLEAN="BOOLEAN",e.ABSOLUTE_PATH="ABSOLUTE_PATH",e.LOCATOR="LOCATOR",e.LOCATOR_LOOSE="LOCATOR_LOOSE",e.NUMBER="NUMBER",e.STRING="STRING",e.SECRET="SECRET",e.SHAPE="SHAPE",e.MAP="MAP"}(T||(T={}));const j=N.Type,Y={lastUpdateCheck:{description:"Last timestamp we checked whether new Yarn versions were available",type:T.STRING,default:null},yarnPath:{description:"Path to the local executable that must be used over the global one",type:T.ABSOLUTE_PATH,default:null},ignorePath:{description:"If true, the local executable will be ignored when using the global one",type:T.BOOLEAN,default:!1},ignoreCwd:{description:"If true, the `--cwd` flag will be ignored",type:T.BOOLEAN,default:!1},cacheKeyOverride:{description:"A global cache key override; used only for test purposes",type:T.STRING,default:null},globalFolder:{description:"Folder where are stored the system-wide settings",type:T.ABSOLUTE_PATH,default:k.getDefaultGlobalFolder()},cacheFolder:{description:"Folder where the cache files must be written",type:T.ABSOLUTE_PATH,default:"./.yarn/cache"},compressionLevel:{description:"Zip files compression level, from 0 to 9 or mixed (a variant of 9, which stores some files uncompressed, when compression doesn't yield good results)",type:T.NUMBER,values:["mixed",0,1,2,3,4,5,6,7,8,9],default:o.k},virtualFolder:{description:"Folder where the virtual packages (cf doc) will be mapped on the disk (must be named $$virtual)",type:T.ABSOLUTE_PATH,default:"./.yarn/$$virtual"},bstatePath:{description:"Path of the file where the current state of the built packages must be stored",type:T.ABSOLUTE_PATH,default:"./.yarn/build-state.yml"},lockfileFilename:{description:"Name of the files where the Yarn dependency tree entries must be stored",type:T.STRING,default:U},installStatePath:{description:"Path of the file where the install state will be persisted",type:T.ABSOLUTE_PATH,default:"./.yarn/install-state.gz"},immutablePatterns:{description:"Array of glob patterns; files matching them won't be allowed to change during immutable installs",type:T.STRING,default:[],isArray:!0},rcFilename:{description:"Name of the files where the configuration can be found",type:T.STRING,default:q()},enableGlobalCache:{description:"If true, the system-wide cache folder will be used regardless of `cache-folder`",type:T.BOOLEAN,default:!1},enableAbsoluteVirtuals:{description:"If true, the virtual symlinks will use absolute paths if required [non portable!!]",type:T.BOOLEAN,default:!1},enableColors:{description:"If true, the CLI is allowed to use colors in its output",type:T.BOOLEAN,default:N.supportsColor,defaultText:""},enableHyperlinks:{description:"If true, the CLI is allowed to use hyperlinks in its output",type:T.BOOLEAN,default:N.supportsHyperlinks,defaultText:""},enableInlineBuilds:{description:"If true, the CLI will print the build output on the command line",type:T.BOOLEAN,default:c.isCI,defaultText:""},enableProgressBars:{description:"If true, the CLI is allowed to show a progress bar for long-running events",type:T.BOOLEAN,default:!c.isCI&&process.stdout.isTTY&&process.stdout.columns>22,defaultText:""},enableTimers:{description:"If true, the CLI is allowed to print the time spent executing commands",type:T.BOOLEAN,default:!0},preferAggregateCacheInfo:{description:"If true, the CLI will only print a one-line report of any cache changes",type:T.BOOLEAN,default:c.isCI},preferInteractive:{description:"If true, the CLI will automatically use the interactive mode when called from a TTY",type:T.BOOLEAN,default:!1},preferTruncatedLines:{description:"If true, the CLI will truncate lines that would go beyond the size of the terminal",type:T.BOOLEAN,default:!1},progressBarStyle:{description:"Which style of progress bar should be used (only when progress bars are enabled)",type:T.STRING,default:void 0,defaultText:""},defaultLanguageName:{description:"Default language mode that should be used when a package doesn't offer any insight",type:T.STRING,default:"node"},defaultProtocol:{description:"Default resolution protocol used when resolving pure semver and tag ranges",type:T.STRING,default:"npm:"},enableTransparentWorkspaces:{description:"If false, Yarn won't automatically resolve workspace dependencies unless they use the `workspace:` protocol",type:T.BOOLEAN,default:!0},enableMirror:{description:"If true, the downloaded packages will be retrieved and stored in both the local and global folders",type:T.BOOLEAN,default:!0},enableNetwork:{description:"If false, the package manager will refuse to use the network if required to",type:T.BOOLEAN,default:!0},httpProxy:{description:"URL of the http proxy that must be used for outgoing http requests",type:T.STRING,default:null},httpsProxy:{description:"URL of the http proxy that must be used for outgoing https requests",type:T.STRING,default:null},unsafeHttpWhitelist:{description:"List of the hostnames for which http queries are allowed (glob patterns are supported)",type:T.STRING,default:[],isArray:!0},httpTimeout:{description:"Timeout of each http request in milliseconds",type:T.NUMBER,default:6e4},httpRetry:{description:"Retry times on http failure",type:T.NUMBER,default:3},networkConcurrency:{description:"Maximal number of concurrent requests",type:T.NUMBER,default:1/0},networkSettings:{description:"Network settings per hostname (glob patterns are supported)",type:T.MAP,valueDefinition:{description:"",type:T.SHAPE,properties:{caFilePath:{description:"Path to file containing one or multiple Certificate Authority signing certificates",type:T.ABSOLUTE_PATH,default:null},enableNetwork:{description:"If false, the package manager will refuse to use the network if required to",type:T.BOOLEAN,default:null},httpProxy:{description:"URL of the http proxy that must be used for outgoing http requests",type:T.STRING,default:null},httpsProxy:{description:"URL of the http proxy that must be used for outgoing https requests",type:T.STRING,default:null}}}},caFilePath:{description:"A path to a file containing one or multiple Certificate Authority signing certificates",type:T.ABSOLUTE_PATH,default:null},enableStrictSsl:{description:"If false, SSL certificate errors will be ignored",type:T.BOOLEAN,default:!0},logFilters:{description:"Overrides for log levels",type:T.SHAPE,isArray:!0,concatenateValues:!0,properties:{code:{description:"Code of the messages covered by this override",type:T.STRING,default:void 0},text:{description:"Code of the texts covered by this override",type:T.STRING,default:void 0},level:{description:"Log level override, set to null to remove override",type:T.STRING,values:Object.values(N.LogLevel),isNullable:!0,default:void 0}}},enableTelemetry:{description:"If true, telemetry will be periodically sent, following the rules in https://yarnpkg.com/advanced/telemetry",type:T.BOOLEAN,default:!0},telemetryInterval:{description:"Minimal amount of time between two telemetry uploads, in days",type:T.NUMBER,default:7},telemetryUserId:{description:"If you desire to tell us which project you are, you can set this field. Completely optional and opt-in.",type:T.STRING,default:null},enableScripts:{description:"If true, packages are allowed to have install scripts by default",type:T.BOOLEAN,default:!0},enableImmutableCache:{description:"If true, the cache is reputed immutable and actions that would modify it will throw",type:T.BOOLEAN,default:!1},checksumBehavior:{description:"Enumeration defining what to do when a checksum doesn't match expectations",type:T.STRING,default:"throw"},packageExtensions:{description:"Map of package corrections to apply on the dependency tree",type:T.MAP,valueDefinition:{description:"The extension that will be applied to any package whose version matches the specified range",type:T.SHAPE,properties:{dependencies:{description:"The set of dependencies that must be made available to the current package in order for it to work properly",type:T.MAP,valueDefinition:{description:"A range",type:T.STRING}},peerDependencies:{description:"Inherited dependencies - the consumer of the package will be tasked to provide them",type:T.MAP,valueDefinition:{description:"A semver range",type:T.STRING}},peerDependenciesMeta:{description:"Extra information related to the dependencies listed in the peerDependencies field",type:T.MAP,valueDefinition:{description:"The peerDependency meta",type:T.SHAPE,properties:{optional:{description:"If true, the selected peer dependency will be marked as optional by the package manager and the consumer omitting it won't be reported as an error",type:T.BOOLEAN,default:!1}}}}}}}};function G(e,t,r,A,n){if(A.isArray)return Array.isArray(r)?r.map((r,o)=>H(e,`${t}[${o}]`,r,A,n)):String(r).split(/,/).map(r=>H(e,t,r,A,n));if(Array.isArray(r))throw new Error(`Non-array configuration settings "${t}" cannot be an array`);return H(e,t,r,A,n)}function H(e,t,r,A,o){var i;switch(A.type){case T.ANY:return r;case T.SHAPE:return function(e,t,r,A,n){if("object"!=typeof r||Array.isArray(r))throw new g.UsageError(`Object configuration settings "${t}" must be an object`);const o=J(e,A,{ignoreArrays:!0});if(null===r)return o;for(const[i,s]of Object.entries(r)){const r=`${t}.${i}`;if(!A.properties[i])throw new g.UsageError(`Unrecognized configuration settings found: ${t}.${i} - run "yarn config -v" to see the list of settings supported in Yarn`);o.set(i,G(e,r,s,A.properties[i],n))}return o}(e,t,r,A,o);case T.MAP:return function(e,t,r,A,n){const o=new Map;if("object"!=typeof r||Array.isArray(r))throw new g.UsageError(`Map configuration settings "${t}" must be an object`);if(null===r)return o;for(const[i,s]of Object.entries(r)){const r=A.normalizeKeys?A.normalizeKeys(i):i,a=`${t}['${r}']`,c=A.valueDefinition;o.set(r,G(e,a,s,c,n))}return o}(e,t,r,A,o)}if(null===r&&!A.isNullable&&null!==A.default)throw new Error(`Non-nullable configuration settings "${t}" cannot be set to null`);if(null===(i=A.values)||void 0===i?void 0:i.includes(r))return r;const s=(()=>{if(A.type===T.BOOLEAN)return F.parseBoolean(r);if("string"!=typeof r)throw new Error(`Expected value (${r}) to be a string`);const e=F.replaceEnvVariables(r,{env:process.env});switch(A.type){case T.ABSOLUTE_PATH:return n.y1.resolve(o,n.cS.toPortablePath(e));case T.LOCATOR_LOOSE:return f.parseLocator(e,!1);case T.NUMBER:return parseInt(e);case T.LOCATOR:return f.parseLocator(e);default:return e}})();if(A.values&&!A.values.includes(s))throw new Error("Invalid value, expected one of "+A.values.join(", "));return s}function J(e,t,{ignoreArrays:r=!1}={}){switch(t.type){case T.SHAPE:{if(t.isArray&&!r)return[];const A=new Map;for(const[r,n]of Object.entries(t.properties))A.set(r,J(e,n));return A}case T.MAP:return t.isArray&&!r?[]:new Map;case T.ABSOLUTE_PATH:return null===t.default?null:null===e.projectCwd?n.y1.isAbsolute(t.default)?n.y1.normalize(t.default):t.isNullable?null:void 0:Array.isArray(t.default)?t.default.map(t=>n.y1.resolve(e.projectCwd,t)):n.y1.resolve(e.projectCwd,t.default);default:return t.default}}function q(){for(const[e,t]of Object.entries(process.env))if("yarn_rc_filename"===e.toLowerCase()&&"string"==typeof t)return t;return O}var z;!function(e){e[e.LOCKFILE=0]="LOCKFILE",e[e.MANIFEST=1]="MANIFEST",e[e.NONE=2]="NONE"}(z||(z={}));class W{constructor(e){this.projectCwd=null,this.plugins=new Map,this.settings=new Map,this.values=new Map,this.sources=new Map,this.invalid=new Map,this.packageExtensions=new Map,this.limits=new Map,this.startingCwd=e}static create(e,t,r){const A=new W(e);void 0===t||t instanceof Map||(A.projectCwd=t),A.importSettings(Y);const n=void 0!==r?r:t instanceof Map?t:new Map;for(const[e,t]of n)A.activatePlugin(e,t);return A}static async find(e,t,{lookup:r=z.LOCKFILE,strict:o=!0,usePath:i=!1,useRc:s=!0}={}){const c=function(){const e={};for(let[t,r]of Object.entries(process.env))t=t.toLowerCase(),t.startsWith("yarn_")&&(t=a()(t.slice("yarn_".length)),e[t]=r);return e}();delete c.rcFilename;const l=await W.findRcFiles(e),u=await W.findHomeRcFile(),h=({ignoreCwd:e,yarnPath:t,ignorePath:r,lockfileFilename:A})=>({ignoreCwd:e,yarnPath:t,ignorePath:r,lockfileFilename:A}),p=({ignoreCwd:e,yarnPath:t,ignorePath:r,lockfileFilename:A,...n})=>n,d=new W(e);d.importSettings(h(Y)),d.useWithSource("",h(c),e,{strict:!1});for(const{path:e,cwd:t,data:r}of l)d.useWithSource(e,h(r),t,{strict:!1});if(u&&d.useWithSource(u.path,h(u.data),u.cwd,{strict:!1}),i){const e=d.get("yarnPath"),t=d.get("ignorePath");if(null!==e&&!t)return d}const C=d.get("lockfileFilename");let f;switch(r){case z.LOCKFILE:f=await W.findProjectCwd(e,C);break;case z.MANIFEST:f=await W.findProjectCwd(e,null);break;case z.NONE:f=A.xfs.existsSync(n.y1.join(e,"package.json"))?n.y1.resolve(e):null}d.startingCwd=e,d.projectCwd=f,d.importSettings(p(Y));const E=new Map([["@@core",I]]);if(null!==t){for(const e of t.plugins.keys())E.set(e,(B=t.modules.get(e)).__esModule?B.default:B);const r=new Map;for(const e of new Set(M().builtinModules||Object.keys(process.binding("natives"))))r.set(e,()=>R(e));for(const[e,A]of t.modules)r.set(e,()=>A);const A=new Set,o=e=>e.default||e,i=(e,t)=>{const{factory:i,name:s}=R(n.cS.fromPortablePath(e));if(A.has(s))return;const a=new Map(r),c=e=>{if(a.has(e))return a.get(e)();throw new g.UsageError(`This plugin cannot access the package referenced via ${e} which is neither a builtin, nor an exposed entry`)},l=F.prettifySyncErrors(()=>o(i(c)),e=>`${e} (when initializing ${s}, defined in ${t})`);r.set(s,()=>l),A.add(s),E.set(s,l)};if(c.plugins)for(const t of c.plugins.split(";")){i(n.y1.resolve(e,n.cS.toPortablePath(t)),"")}for(const{path:e,cwd:t,data:r}of l)if(s&&Array.isArray(r.plugins))for(const A of r.plugins){const r="string"!=typeof A?A.path:A;i(n.y1.resolve(t,n.cS.toPortablePath(r)),e)}}var B;for(const[e,t]of E)d.activatePlugin(e,t);d.useWithSource("",p(c),e,{strict:o});for(const{path:e,cwd:t,data:r}of l)d.useWithSource(e,p(r),t,{strict:o});return u&&d.useWithSource(u.path,p(u.data),u.cwd,{strict:!1}),d.get("enableGlobalCache")&&(d.values.set("cacheFolder",d.get("globalFolder")+"/cache"),d.sources.set("cacheFolder","")),await d.refreshPackageExtensions(),d}static async findRcFiles(e){const t=q(),r=[];let o=e,s=null;for(;o!==s;){s=o;const e=n.y1.join(s,t);if(A.xfs.existsSync(e)){const t=await A.xfs.readFilePromise(e,"utf8");let n;try{n=(0,i.parseSyml)(t)}catch(r){let A="";throw t.match(/^\s+(?!-)[^:]+\s+\S+/m)&&(A=" (in particular, make sure you list the colons after each key name)"),new g.UsageError(`Parse error when loading ${e}; please check it's proper Yaml${A}`)}r.push({path:e,cwd:s,data:n})}o=n.y1.dirname(s)}return r}static async findHomeRcFile(){const e=q(),t=k.getHomeFolder(),r=n.y1.join(t,e);if(A.xfs.existsSync(r)){const e=await A.xfs.readFilePromise(r,"utf8");return{path:r,cwd:t,data:(0,i.parseSyml)(e)}}return null}static async findProjectCwd(e,t){let r=null,o=e,i=null;for(;o!==i;){if(i=o,A.xfs.existsSync(n.y1.join(i,"package.json"))&&(r=i),null!==t){if(A.xfs.existsSync(n.y1.join(i,t))){r=i;break}}else if(null!==r)break;o=n.y1.dirname(i)}return r}static async updateConfiguration(e,t){const r=q(),o=n.y1.join(e,r),s=A.xfs.existsSync(o)?(0,i.parseSyml)(await A.xfs.readFilePromise(o,"utf8")):{};let a,c=!1;if("function"==typeof t){try{a=t(s)}catch(e){a=t({})}if(a===s)return}else{a=s;for(const e of Object.keys(t)){const r=s[e],A=t[e];let n;if("function"==typeof A)try{n=A(r)}catch(e){n=A(void 0)}else n=A;r!==n&&(a[e]=n,c=!0)}if(!c)return}await A.xfs.changeFilePromise(o,(0,i.stringifySyml)(a),{automaticNewlines:!0})}static async updateHomeConfiguration(e){const t=k.getHomeFolder();return await W.updateConfiguration(t,e)}activatePlugin(e,t){this.plugins.set(e,t),void 0!==t.configuration&&this.importSettings(t.configuration)}importSettings(e){for(const[t,r]of Object.entries(e))if(null!=r){if(this.settings.has(t))throw new Error(`Cannot redefine settings "${t}"`);this.settings.set(t,r),this.values.set(t,J(this,r))}}useWithSource(e,t,r,A){try{this.use(e,t,r,A)}catch(t){throw t.message+=` (in ${N.pretty(this,e,N.Type.PATH)})`,t}}use(e,t,r,{strict:A=!0,overwrite:n=!1}={}){for(const o of Object.keys(t)){if(void 0===t[o])continue;if("plugins"===o)continue;if(""===e&&P.has(o))continue;if("rcFilename"===o)throw new g.UsageError(`The rcFilename settings can only be set via ${"yarn_RC_FILENAME".toUpperCase()}, not via a rc file`);const i=this.settings.get(o);if(!i){if(A)throw new g.UsageError(`Unrecognized or legacy configuration settings found: ${o} - run "yarn config -v" to see the list of settings supported in Yarn`);this.invalid.set(o,e);continue}if(this.sources.has(o)&&!(n||i.type===T.MAP||i.isArray&&i.concatenateValues))continue;let s;try{s=G(this,o,t[o],i,r)}catch(t){throw t.message+=" in "+N.pretty(this,e,N.Type.PATH),t}if(i.type===T.MAP){const t=this.values.get(o);this.values.set(o,new Map(n?[...t,...s]:[...s,...t])),this.sources.set(o,`${this.sources.get(o)}, ${e}`)}else if(i.isArray&&i.concatenateValues){const t=this.values.get(o);this.values.set(o,n?[...t,...s]:[...s,...t]),this.sources.set(o,`${this.sources.get(o)}, ${e}`)}else this.values.set(o,s),this.sources.set(o,e)}}get(e){if(!this.values.has(e))throw new Error(`Invalid configuration key "${e}"`);return this.values.get(e)}getSpecial(e,{hideSecrets:t=!1,getNativePaths:r=!1}){const A=this.get(e),o=this.settings.get(e);if(void 0===o)throw new g.UsageError(`Couldn't find a configuration settings named "${e}"`);return function e(t,r,A){if(r.type===T.SECRET&&"string"==typeof t&&A.hideSecrets)return"********";if(r.type===T.ABSOLUTE_PATH&&"string"==typeof t&&A.getNativePaths)return n.cS.fromPortablePath(t);if(r.isArray&&Array.isArray(t)){const n=[];for(const o of t)n.push(e(o,r,A));return n}if(r.type===T.MAP&&t instanceof Map){const n=new Map;for(const[o,i]of t.entries())n.set(o,e(i,r.valueDefinition,A));return n}if(r.type===T.SHAPE&&t instanceof Map){const n=new Map;for(const[o,i]of t.entries()){const t=r.properties[o];n.set(o,e(i,t,A))}return n}return t}(A,o,{hideSecrets:t,getNativePaths:r})}getSubprocessStreams(e,{header:t,prefix:r,report:n}){let o,i;const s=A.xfs.createWriteStream(e);if(this.get("enableInlineBuilds")){const e=n.createStreamReporter(`${r} ${N.pretty(this,"STDOUT","green")}`),t=n.createStreamReporter(`${r} ${N.pretty(this,"STDERR","red")}`);o=new d.PassThrough,o.pipe(e),o.pipe(s),i=new d.PassThrough,i.pipe(t),i.pipe(s)}else o=s,i=s,void 0!==t&&o.write(t+"\n");return{stdout:o,stderr:i}}makeResolver(){const e=[];for(const t of this.plugins.values())for(const r of t.resolvers||[])e.push(new r);return new m.B([new D,new v.d,new w.O,...e])}makeFetcher(){const e=[];for(const t of this.plugins.values())for(const r of t.fetchers||[])e.push(new r);return new y([new Q.N,new S,...e])}getLinkers(){const e=[];for(const t of this.plugins.values())for(const r of t.linkers||[])e.push(new r);return e}async refreshPackageExtensions(){this.packageExtensions=new Map;const e=this.packageExtensions,t=(t,r,{userProvided:A=!1}={})=>{if(!p().validRange(t.range))throw new Error("Only semver ranges are allowed as keys for the lockfileExtensions setting");const n=new E.G;n.load(r,{yamlCompatibilityMode:!0});const o=[];F.getArrayWithDefault(e,t.identHash).push([t.range,o]);const i={status:L._u.Inactive,userProvided:A,parentDescriptor:t};for(const e of n.dependencies.values())o.push({...i,type:L.HN.Dependency,descriptor:e,description:`${f.stringifyIdent(t)} > ${f.stringifyIdent(e)}`});for(const e of n.peerDependencies.values())o.push({...i,type:L.HN.PeerDependency,descriptor:e,description:`${f.stringifyIdent(t)} >> ${f.stringifyIdent(e)}`});for(const[e,r]of n.peerDependenciesMeta)for(const[A,n]of Object.entries(r))o.push({...i,type:L.HN.PeerDependencyMeta,selector:e,key:A,value:n,description:`${f.stringifyIdent(t)} >> ${e} / ${A}`})};await this.triggerHook(e=>e.registerPackageExtensions,this,t);for(const[e,r]of this.get("packageExtensions"))t(f.parseDescriptor(e,!0),F.convertMapsToIndexableObjects(r),{userProvided:!0})}normalizePackage(e){const t=f.copyPackage(e);if(null==this.packageExtensions)throw new Error("refreshPackageExtensions has to be called before normalizing packages");const r=this.packageExtensions.get(e.identHash);if(void 0!==r){const A=e.version;if(null!==A)for(const[e,n]of r)if(x.satisfiesWithPrereleases(A,e))for(const e of n)switch(e.status===L._u.Inactive&&(e.status=L._u.Redundant),e.type){case L.HN.Dependency:void 0===t.dependencies.get(e.descriptor.identHash)&&(e.status=L._u.Active,t.dependencies.set(e.descriptor.identHash,e.descriptor));break;case L.HN.PeerDependency:void 0===t.peerDependencies.get(e.descriptor.identHash)&&(e.status=L._u.Active,t.peerDependencies.set(e.descriptor.identHash,e.descriptor));break;case L.HN.PeerDependencyMeta:{const r=t.peerDependenciesMeta.get(e.selector);void 0!==r&&Object.prototype.hasOwnProperty.call(r,e.key)&&r[e.key]===e.value||(e.status=L._u.Active,F.getFactoryWithDefault(t.peerDependenciesMeta,e.selector,()=>({}))[e.key]=e.value)}break;default:F.assertNever(e)}}const A=e=>e.scope?`${e.scope}__${e.name}`:""+e.name;for(const e of t.peerDependencies.values()){if("@types"===e.scope)continue;const r=A(e),n=f.makeIdent("types",r);t.peerDependencies.has(n.identHash)||t.peerDependenciesMeta.has(n.identHash)||t.peerDependenciesMeta.set(f.stringifyIdent(n),{optional:!0})}for(const e of t.peerDependenciesMeta.keys()){const r=f.parseIdent(e);t.peerDependencies.has(r.identHash)||t.peerDependencies.set(r.identHash,f.makeDescriptor(r,"*"))}return t.dependencies=new Map(F.sortMap(t.dependencies,([,e])=>f.stringifyDescriptor(e))),t.peerDependencies=new Map(F.sortMap(t.peerDependencies,([,e])=>f.stringifyDescriptor(e))),t}getLimit(e){return F.getFactoryWithDefault(this.limits,e,()=>u()(this.get(e)))}async triggerHook(e,...t){for(const r of this.plugins.values()){const A=r.hooks;if(!A)continue;const n=e(A);n&&await n(...t)}}async triggerMultipleHooks(e,t){for(const r of t)await this.triggerHook(e,...r)}async reduceHook(e,t,...r){let A=t;for(const t of this.plugins.values()){const n=t.hooks;if(!n)continue;const o=e(n);o&&(A=await o(A,...r))}return A}async firstHook(e,...t){for(const r of this.plugins.values()){const A=r.hooks;if(!A)continue;const n=e(A);if(!n)continue;const o=await n(...t);if(void 0!==o)return o}return null}format(e,t){return N.pretty(this,e,t)}}W.telemetry=null},92409:(e,t,r)=>{"use strict";var A;r.d(t,{k:()=>A}),function(e){e[e.SCRIPT=0]="SCRIPT",e[e.SHELLCODE=1]="SHELLCODE"}(A||(A={}))},62152:(e,t,r)=>{"use strict";r.d(t,{h:()=>i});var A=r(35691),n=r(15815),o=r(71643);class i extends A.yG{constructor({configuration:e,stdout:t,suggestInstall:r=!0}){super(),this.errorCount=0,o.addLogFilterSupport(this,{configuration:e}),this.configuration=e,this.stdout=t,this.suggestInstall=r}static async start(e,t){const r=new this(e);try{await t(r)}catch(e){r.reportExceptionOnce(e)}finally{await r.finalize()}return r}hasErrors(){return this.errorCount>0}exitCode(){return this.hasErrors()?1:0}reportCacheHit(e){}reportCacheMiss(e){}startTimerSync(e,t,r){return("function"==typeof t?t:r)()}async startTimerPromise(e,t,r){const A="function"==typeof t?t:r;return await A()}async startCacheReport(e){return await e()}reportSeparator(){}reportInfo(e,t){}reportWarning(e,t){}reportError(e,t){this.errorCount+=1,this.stdout.write(`${o.pretty(this.configuration,"➤","redBright")} ${this.formatNameWithHyperlink(e)}: ${t}\n`)}reportProgress(e){return{...Promise.resolve().then(async()=>{for await(const{}of e);}),stop:()=>{}}}reportJson(e){}async finalize(){this.errorCount>0&&(this.stdout.write(o.pretty(this.configuration,"➤","redBright")+" Errors happened when preparing the environment required to run this command.\n"),this.suggestInstall&&this.stdout.write(o.pretty(this.configuration,"➤","redBright")+' This might be caused by packages being missing from the lockfile, in which case running "yarn install" might help.\n'))}formatNameWithHyperlink(e){return(0,n.Qw)(e,{configuration:this.configuration,json:!1})}}},46611:(e,t,r)=>{"use strict";r.d(t,{G:()=>l});var A=r(78420),n=r(46009),o=r(11640),i=r(53887),s=r.n(i),a=r(73632),c=r(36545),g=r(54143);class l{constructor(){this.indent=" ",this.name=null,this.version=null,this.os=null,this.cpu=null,this.type=null,this.private=!1,this.license=null,this.main=null,this.module=null,this.browser=null,this.languageName=null,this.bin=new Map,this.scripts=new Map,this.dependencies=new Map,this.devDependencies=new Map,this.peerDependencies=new Map,this.workspaceDefinitions=[],this.dependenciesMeta=new Map,this.peerDependenciesMeta=new Map,this.resolutions=[],this.files=null,this.publishConfig=null,this.installConfig=null,this.preferUnplugged=null,this.raw={},this.errors=[]}static async tryFind(e,{baseFs:t=new A.S}={}){const r=n.y1.join(e,"package.json");return await t.existsPromise(r)?await l.fromFile(r,{baseFs:t}):null}static async find(e,{baseFs:t}={}){const r=await l.tryFind(e,{baseFs:t});if(null===r)throw new Error("Manifest not found");return r}static async fromFile(e,{baseFs:t=new A.S}={}){const r=new l;return await r.loadFile(e,{baseFs:t}),r}static fromText(e){const t=new l;return t.loadFromText(e),t}static isManifestFieldCompatible(e,t){if(null===e)return!0;let r=!0,A=!1;for(const n of e)if("!"===n[0]){if(A=!0,t===n.slice(1))return!1}else if(r=!1,n===t)return!0;return A&&r}loadFromText(e){let t;try{t=JSON.parse(h(e)||"{}")}catch(t){throw t.message+=` (when parsing ${e})`,t}this.load(t),this.indent=u(e)}async loadFile(e,{baseFs:t=new A.S}){const r=await t.readFilePromise(e,"utf8");let n;try{n=JSON.parse(h(r)||"{}")}catch(t){throw t.message+=` (when parsing ${e})`,t}this.load(n),this.indent=u(r)}load(e,{yamlCompatibilityMode:t=!1}={}){if("object"!=typeof e||null===e)throw new Error(`Utterly invalid manifest data (${e})`);this.raw=e;const r=[];if("string"==typeof e.name)try{this.name=g.parseIdent(e.name)}catch(e){r.push(new Error("Parsing failed for the 'name' field"))}if("string"==typeof e.version&&(this.version=e.version),Array.isArray(e.os)){const t=[];this.os=t;for(const A of e.os)"string"!=typeof A?r.push(new Error("Parsing failed for the 'os' field")):t.push(A)}if(Array.isArray(e.cpu)){const t=[];this.cpu=t;for(const A of e.cpu)"string"!=typeof A?r.push(new Error("Parsing failed for the 'cpu' field")):t.push(A)}if("string"==typeof e.type&&(this.type=e.type),"boolean"==typeof e.private&&(this.private=e.private),"string"==typeof e.license&&(this.license=e.license),"string"==typeof e.languageName&&(this.languageName=e.languageName),"string"==typeof e.main&&(this.main=p(e.main)),"string"==typeof e.module&&(this.module=p(e.module)),null!=e.browser)if("string"==typeof e.browser)this.browser=p(e.browser);else{this.browser=new Map;for(const[t,r]of Object.entries(e.browser))this.browser.set(p(t),"string"==typeof r?p(r):r)}if("string"==typeof e.bin)null!==this.name?this.bin=new Map([[this.name.name,p(e.bin)]]):r.push(new Error("String bin field, but no attached package name"));else if("object"==typeof e.bin&&null!==e.bin)for(const[t,A]of Object.entries(e.bin))"string"==typeof A?this.bin.set(t,p(A)):r.push(new Error(`Invalid bin definition for '${t}'`));if("object"==typeof e.scripts&&null!==e.scripts)for(const[t,A]of Object.entries(e.scripts))"string"==typeof A?this.scripts.set(t,A):r.push(new Error(`Invalid script definition for '${t}'`));if("object"==typeof e.dependencies&&null!==e.dependencies)for(const[t,A]of Object.entries(e.dependencies)){if("string"!=typeof A){r.push(new Error(`Invalid dependency range for '${t}'`));continue}let e;try{e=g.parseIdent(t)}catch(e){r.push(new Error(`Parsing failed for the dependency name '${t}'`));continue}const n=g.makeDescriptor(e,A);this.dependencies.set(n.identHash,n)}if("object"==typeof e.devDependencies&&null!==e.devDependencies)for(const[t,A]of Object.entries(e.devDependencies)){if("string"!=typeof A){r.push(new Error(`Invalid dependency range for '${t}'`));continue}let e;try{e=g.parseIdent(t)}catch(e){r.push(new Error(`Parsing failed for the dependency name '${t}'`));continue}const n=g.makeDescriptor(e,A);this.devDependencies.set(n.identHash,n)}if("object"==typeof e.peerDependencies&&null!==e.peerDependencies)for(let[t,A]of Object.entries(e.peerDependencies)){let e;try{e=g.parseIdent(t)}catch(e){r.push(new Error(`Parsing failed for the dependency name '${t}'`));continue}"string"==typeof A&&c.validRange(A)||(r.push(new Error(`Invalid dependency range for '${t}'`)),A="*");const n=g.makeDescriptor(e,A);this.peerDependencies.set(n.identHash,n)}"object"==typeof e.workspaces&&e.workspaces.nohoist&&r.push(new Error("'nohoist' is deprecated, please use 'installConfig.hoistingLimits' instead"));const A=Array.isArray(e.workspaces)?e.workspaces:"object"==typeof e.workspaces&&null!==e.workspaces&&Array.isArray(e.workspaces.packages)?e.workspaces.packages:[];for(const e of A)"string"==typeof e?this.workspaceDefinitions.push({pattern:e}):r.push(new Error(`Invalid workspace definition for '${e}'`));if("object"==typeof e.dependenciesMeta&&null!==e.dependenciesMeta)for(const[A,n]of Object.entries(e.dependenciesMeta)){if("object"!=typeof n||null===n){r.push(new Error("Invalid meta field for '"+A));continue}const e=g.parseDescriptor(A),o=this.ensureDependencyMeta(e),i=d(n.built,{yamlCompatibilityMode:t});if(null===i){r.push(new Error(`Invalid built meta field for '${A}'`));continue}const s=d(n.optional,{yamlCompatibilityMode:t});if(null===s){r.push(new Error(`Invalid optional meta field for '${A}'`));continue}const a=d(n.unplugged,{yamlCompatibilityMode:t});null!==a?Object.assign(o,{built:i,optional:s,unplugged:a}):r.push(new Error(`Invalid unplugged meta field for '${A}'`))}if("object"==typeof e.peerDependenciesMeta&&null!==e.peerDependenciesMeta)for(const[A,n]of Object.entries(e.peerDependenciesMeta)){if("object"!=typeof n||null===n){r.push(new Error(`Invalid meta field for '${A}'`));continue}const e=g.parseDescriptor(A),o=this.ensurePeerDependencyMeta(e),i=d(n.optional,{yamlCompatibilityMode:t});null!==i?Object.assign(o,{optional:i}):r.push(new Error(`Invalid optional meta field for '${A}'`))}if("object"==typeof e.resolutions&&null!==e.resolutions)for(const[t,A]of Object.entries(e.resolutions))if("string"==typeof A)try{this.resolutions.push({pattern:(0,o.parseResolution)(t),reference:A})}catch(e){r.push(e);continue}else r.push(new Error(`Invalid resolution entry for '${t}'`));if(Array.isArray(e.files)){this.files=new Set;for(const t of e.files)"string"==typeof t?this.files.add(t):r.push(new Error(`Invalid files entry for '${t}'`))}if("object"==typeof e.publishConfig&&null!==e.publishConfig){if(this.publishConfig={},"string"==typeof e.publishConfig.access&&(this.publishConfig.access=e.publishConfig.access),"string"==typeof e.publishConfig.main&&(this.publishConfig.main=p(e.publishConfig.main)),"string"==typeof e.publishConfig.module&&(this.publishConfig.module=p(e.publishConfig.module)),null!=e.publishConfig.browser)if("string"==typeof e.publishConfig.browser)this.publishConfig.browser=p(e.publishConfig.browser);else{this.publishConfig.browser=new Map;for(const[t,r]of Object.entries(e.publishConfig.browser))this.publishConfig.browser.set(p(t),"string"==typeof r?p(r):r)}if("string"==typeof e.publishConfig.registry&&(this.publishConfig.registry=e.publishConfig.registry),"string"==typeof e.publishConfig.bin)null!==this.name?this.publishConfig.bin=new Map([[this.name.name,p(e.publishConfig.bin)]]):r.push(new Error("String bin field, but no attached package name"));else if("object"==typeof e.publishConfig.bin&&null!==e.publishConfig.bin){this.publishConfig.bin=new Map;for(const[t,A]of Object.entries(e.publishConfig.bin))"string"==typeof A?this.publishConfig.bin.set(t,p(A)):r.push(new Error(`Invalid bin definition for '${t}'`))}if(Array.isArray(e.publishConfig.executableFiles)){this.publishConfig.executableFiles=new Set;for(const t of e.publishConfig.executableFiles)"string"==typeof t?this.publishConfig.executableFiles.add(p(t)):r.push(new Error("Invalid executable file definition"))}}if("object"==typeof e.installConfig&&null!==e.installConfig){this.installConfig={};for(const t of Object.keys(e.installConfig))"hoistingLimits"===t?"string"==typeof e.installConfig.hoistingLimits?this.installConfig.hoistingLimits=e.installConfig.hoistingLimits:r.push(new Error("Invalid hoisting limits definition")):r.push(new Error("Unrecognized installConfig key: "+t))}if("object"==typeof e.optionalDependencies&&null!==e.optionalDependencies)for(const[t,A]of Object.entries(e.optionalDependencies)){if("string"!=typeof A){r.push(new Error(`Invalid dependency range for '${t}'`));continue}let e;try{e=g.parseIdent(t)}catch(e){r.push(new Error(`Parsing failed for the dependency name '${t}'`));continue}const n=g.makeDescriptor(e,A);this.dependencies.set(n.identHash,n);const o=g.makeDescriptor(e,"unknown"),i=this.ensureDependencyMeta(o);Object.assign(i,{optional:!0})}"boolean"==typeof e.preferUnplugged&&(this.preferUnplugged=e.preferUnplugged),this.errors=r}getForScope(e){switch(e){case"dependencies":return this.dependencies;case"devDependencies":return this.devDependencies;case"peerDependencies":return this.peerDependencies;default:throw new Error(`Unsupported value ("${e}")`)}}hasConsumerDependency(e){return!!this.dependencies.has(e.identHash)||!!this.peerDependencies.has(e.identHash)}hasHardDependency(e){return!!this.dependencies.has(e.identHash)||!!this.devDependencies.has(e.identHash)}hasSoftDependency(e){return!!this.peerDependencies.has(e.identHash)}hasDependency(e){return!!this.hasHardDependency(e)||!!this.hasSoftDependency(e)}isCompatibleWithOS(e){return l.isManifestFieldCompatible(this.os,e)}isCompatibleWithCPU(e){return l.isManifestFieldCompatible(this.cpu,e)}ensureDependencyMeta(e){if("unknown"!==e.range&&!s().valid(e.range))throw new Error(`Invalid meta field range for '${g.stringifyDescriptor(e)}'`);const t=g.stringifyIdent(e),r="unknown"!==e.range?e.range:null;let A=this.dependenciesMeta.get(t);A||this.dependenciesMeta.set(t,A=new Map);let n=A.get(r);return n||A.set(r,n={}),n}ensurePeerDependencyMeta(e){if("unknown"!==e.range)throw new Error(`Invalid meta field range for '${g.stringifyDescriptor(e)}'`);const t=g.stringifyIdent(e);let r=this.peerDependenciesMeta.get(t);return r||this.peerDependenciesMeta.set(t,r={}),r}setRawField(e,t,{after:r=[]}={}){const A=new Set(r.filter(e=>Object.prototype.hasOwnProperty.call(this.raw,e)));if(0===A.size||Object.prototype.hasOwnProperty.call(this.raw,e))this.raw[e]=t;else{const r=this.raw,n=this.raw={};let o=!1;for(const i of Object.keys(r))n[i]=r[i],o||(A.delete(i),0===A.size&&(n[e]=t,o=!0))}}exportTo(e,{compatibilityMode:t=!0}={}){if(Object.assign(e,this.raw),null!==this.name?e.name=g.stringifyIdent(this.name):delete e.name,null!==this.version?e.version=this.version:delete e.version,null!==this.os?e.os=this.os:delete e.os,null!==this.cpu?e.cpu=this.cpu:delete e.cpu,null!==this.type?e.type=this.type:delete e.type,this.private?e.private=!0:delete e.private,null!==this.license?e.license=this.license:delete e.license,null!==this.languageName?e.languageName=this.languageName:delete e.languageName,null!==this.main?e.main=this.main:delete e.main,null!==this.module?e.module=this.module:delete e.module,null!==this.browser){const t=this.browser;"string"==typeof t?e.browser=t:t instanceof Map&&(e.browser=Object.assign({},...Array.from(t.keys()).sort().map(e=>({[e]:t.get(e)}))))}else delete e.browser;1===this.bin.size&&null!==this.name&&this.bin.has(this.name.name)?e.bin=this.bin.get(this.name.name):this.bin.size>0?e.bin=Object.assign({},...Array.from(this.bin.keys()).sort().map(e=>({[e]:this.bin.get(e)}))):delete e.bin,this.workspaceDefinitions.length>0?this.raw.workspaces&&!Array.isArray(this.raw.workspaces)?e.workspaces={...this.raw.workspaces,packages:this.workspaceDefinitions.map(({pattern:e})=>e)}:e.workspaces=this.workspaceDefinitions.map(({pattern:e})=>e):this.raw.workspaces&&!Array.isArray(this.raw.workspaces)&&Object.keys(this.raw.workspaces).length>0?e.workspaces=this.raw.workspaces:delete e.workspaces;const r=[],A=[];for(const e of this.dependencies.values()){const n=this.dependenciesMeta.get(g.stringifyIdent(e));let o=!1;if(t&&n){const e=n.get(null);e&&e.optional&&(o=!0)}o?A.push(e):r.push(e)}r.length>0?e.dependencies=Object.assign({},...g.sortDescriptors(r).map(e=>({[g.stringifyIdent(e)]:e.range}))):delete e.dependencies,A.length>0?e.optionalDependencies=Object.assign({},...g.sortDescriptors(A).map(e=>({[g.stringifyIdent(e)]:e.range}))):delete e.optionalDependencies,this.devDependencies.size>0?e.devDependencies=Object.assign({},...g.sortDescriptors(this.devDependencies.values()).map(e=>({[g.stringifyIdent(e)]:e.range}))):delete e.devDependencies,this.peerDependencies.size>0?e.peerDependencies=Object.assign({},...g.sortDescriptors(this.peerDependencies.values()).map(e=>({[g.stringifyIdent(e)]:e.range}))):delete e.peerDependencies,e.dependenciesMeta={};for(const[r,A]of a.sortMap(this.dependenciesMeta.entries(),([e,t])=>e))for(const[n,o]of a.sortMap(A.entries(),([e,t])=>null!==e?"0"+e:"1")){const A=null!==n?g.stringifyDescriptor(g.makeDescriptor(g.parseIdent(r),n)):r,i={...o};t&&null===n&&delete i.optional,0!==Object.keys(i).length&&(e.dependenciesMeta[A]=i)}return 0===Object.keys(e.dependenciesMeta).length&&delete e.dependenciesMeta,this.peerDependenciesMeta.size>0?e.peerDependenciesMeta=Object.assign({},...a.sortMap(this.peerDependenciesMeta.entries(),([e,t])=>e).map(([e,t])=>({[e]:t}))):delete e.peerDependenciesMeta,this.resolutions.length>0?e.resolutions=Object.assign({},...this.resolutions.map(({pattern:e,reference:t})=>({[(0,o.stringifyResolution)(e)]:t}))):delete e.resolutions,null!==this.files?e.files=Array.from(this.files):delete e.files,null!==this.preferUnplugged?e.preferUnplugged=this.preferUnplugged:delete e.preferUnplugged,e}}function u(e){const t=e.match(/^[ \t]+/m);return t?t[0]:" "}function h(e){return 65279===e.charCodeAt(0)?e.slice(1):e}function p(e){return e.replace(/\\/g,"/")}function d(e,{yamlCompatibilityMode:t}){return t?a.tryParseOptionalBoolean(e):void 0===e||"boolean"==typeof e?e:null}l.fileName="package.json",l.allDependencies=["dependencies","devDependencies","peerDependencies"],l.hardDependencies=["dependencies","devDependencies"]},92659:(e,t,r)=>{"use strict";var A;function n(e){return"YN"+e.toString(10).padStart(4,"0")}r.d(t,{b:()=>A,i:()=>n}),function(e){e[e.UNNAMED=0]="UNNAMED",e[e.EXCEPTION=1]="EXCEPTION",e[e.MISSING_PEER_DEPENDENCY=2]="MISSING_PEER_DEPENDENCY",e[e.CYCLIC_DEPENDENCIES=3]="CYCLIC_DEPENDENCIES",e[e.DISABLED_BUILD_SCRIPTS=4]="DISABLED_BUILD_SCRIPTS",e[e.BUILD_DISABLED=5]="BUILD_DISABLED",e[e.SOFT_LINK_BUILD=6]="SOFT_LINK_BUILD",e[e.MUST_BUILD=7]="MUST_BUILD",e[e.MUST_REBUILD=8]="MUST_REBUILD",e[e.BUILD_FAILED=9]="BUILD_FAILED",e[e.RESOLVER_NOT_FOUND=10]="RESOLVER_NOT_FOUND",e[e.FETCHER_NOT_FOUND=11]="FETCHER_NOT_FOUND",e[e.LINKER_NOT_FOUND=12]="LINKER_NOT_FOUND",e[e.FETCH_NOT_CACHED=13]="FETCH_NOT_CACHED",e[e.YARN_IMPORT_FAILED=14]="YARN_IMPORT_FAILED",e[e.REMOTE_INVALID=15]="REMOTE_INVALID",e[e.REMOTE_NOT_FOUND=16]="REMOTE_NOT_FOUND",e[e.RESOLUTION_PACK=17]="RESOLUTION_PACK",e[e.CACHE_CHECKSUM_MISMATCH=18]="CACHE_CHECKSUM_MISMATCH",e[e.UNUSED_CACHE_ENTRY=19]="UNUSED_CACHE_ENTRY",e[e.MISSING_LOCKFILE_ENTRY=20]="MISSING_LOCKFILE_ENTRY",e[e.WORKSPACE_NOT_FOUND=21]="WORKSPACE_NOT_FOUND",e[e.TOO_MANY_MATCHING_WORKSPACES=22]="TOO_MANY_MATCHING_WORKSPACES",e[e.CONSTRAINTS_MISSING_DEPENDENCY=23]="CONSTRAINTS_MISSING_DEPENDENCY",e[e.CONSTRAINTS_INCOMPATIBLE_DEPENDENCY=24]="CONSTRAINTS_INCOMPATIBLE_DEPENDENCY",e[e.CONSTRAINTS_EXTRANEOUS_DEPENDENCY=25]="CONSTRAINTS_EXTRANEOUS_DEPENDENCY",e[e.CONSTRAINTS_INVALID_DEPENDENCY=26]="CONSTRAINTS_INVALID_DEPENDENCY",e[e.CANT_SUGGEST_RESOLUTIONS=27]="CANT_SUGGEST_RESOLUTIONS",e[e.FROZEN_LOCKFILE_EXCEPTION=28]="FROZEN_LOCKFILE_EXCEPTION",e[e.CROSS_DRIVE_VIRTUAL_LOCAL=29]="CROSS_DRIVE_VIRTUAL_LOCAL",e[e.FETCH_FAILED=30]="FETCH_FAILED",e[e.DANGEROUS_NODE_MODULES=31]="DANGEROUS_NODE_MODULES",e[e.NODE_GYP_INJECTED=32]="NODE_GYP_INJECTED",e[e.AUTHENTICATION_NOT_FOUND=33]="AUTHENTICATION_NOT_FOUND",e[e.INVALID_CONFIGURATION_KEY=34]="INVALID_CONFIGURATION_KEY",e[e.NETWORK_ERROR=35]="NETWORK_ERROR",e[e.LIFECYCLE_SCRIPT=36]="LIFECYCLE_SCRIPT",e[e.CONSTRAINTS_MISSING_FIELD=37]="CONSTRAINTS_MISSING_FIELD",e[e.CONSTRAINTS_INCOMPATIBLE_FIELD=38]="CONSTRAINTS_INCOMPATIBLE_FIELD",e[e.CONSTRAINTS_EXTRANEOUS_FIELD=39]="CONSTRAINTS_EXTRANEOUS_FIELD",e[e.CONSTRAINTS_INVALID_FIELD=40]="CONSTRAINTS_INVALID_FIELD",e[e.AUTHENTICATION_INVALID=41]="AUTHENTICATION_INVALID",e[e.PROLOG_UNKNOWN_ERROR=42]="PROLOG_UNKNOWN_ERROR",e[e.PROLOG_SYNTAX_ERROR=43]="PROLOG_SYNTAX_ERROR",e[e.PROLOG_EXISTENCE_ERROR=44]="PROLOG_EXISTENCE_ERROR",e[e.STACK_OVERFLOW_RESOLUTION=45]="STACK_OVERFLOW_RESOLUTION",e[e.AUTOMERGE_FAILED_TO_PARSE=46]="AUTOMERGE_FAILED_TO_PARSE",e[e.AUTOMERGE_IMMUTABLE=47]="AUTOMERGE_IMMUTABLE",e[e.AUTOMERGE_SUCCESS=48]="AUTOMERGE_SUCCESS",e[e.AUTOMERGE_REQUIRED=49]="AUTOMERGE_REQUIRED",e[e.DEPRECATED_CLI_SETTINGS=50]="DEPRECATED_CLI_SETTINGS",e[e.PLUGIN_NAME_NOT_FOUND=51]="PLUGIN_NAME_NOT_FOUND",e[e.INVALID_PLUGIN_REFERENCE=52]="INVALID_PLUGIN_REFERENCE",e[e.CONSTRAINTS_AMBIGUITY=53]="CONSTRAINTS_AMBIGUITY",e[e.CACHE_OUTSIDE_PROJECT=54]="CACHE_OUTSIDE_PROJECT",e[e.IMMUTABLE_INSTALL=55]="IMMUTABLE_INSTALL",e[e.IMMUTABLE_CACHE=56]="IMMUTABLE_CACHE",e[e.INVALID_MANIFEST=57]="INVALID_MANIFEST",e[e.PACKAGE_PREPARATION_FAILED=58]="PACKAGE_PREPARATION_FAILED",e[e.INVALID_RANGE_PEER_DEPENDENCY=59]="INVALID_RANGE_PEER_DEPENDENCY",e[e.INCOMPATIBLE_PEER_DEPENDENCY=60]="INCOMPATIBLE_PEER_DEPENDENCY",e[e.DEPRECATED_PACKAGE=61]="DEPRECATED_PACKAGE",e[e.INCOMPATIBLE_OS=62]="INCOMPATIBLE_OS",e[e.INCOMPATIBLE_CPU=63]="INCOMPATIBLE_CPU",e[e.FROZEN_ARTIFACT_EXCEPTION=64]="FROZEN_ARTIFACT_EXCEPTION",e[e.TELEMETRY_NOTICE=65]="TELEMETRY_NOTICE",e[e.PATCH_HUNK_FAILED=66]="PATCH_HUNK_FAILED",e[e.INVALID_CONFIGURATION_VALUE=67]="INVALID_CONFIGURATION_VALUE",e[e.UNUSED_PACKAGE_EXTENSION=68]="UNUSED_PACKAGE_EXTENSION",e[e.REDUNDANT_PACKAGE_EXTENSION=69]="REDUNDANT_PACKAGE_EXTENSION"}(A||(A={}))},27092:(e,t,r)=>{"use strict";r.d(t,{B:()=>n});var A=r(54143);class n{constructor(e){this.resolvers=e.filter(e=>e)}supportsDescriptor(e,t){return!!this.tryResolverByDescriptor(e,t)}supportsLocator(e,t){return!!this.tryResolverByLocator(e,t)}shouldPersistResolution(e,t){return this.getResolverByLocator(e,t).shouldPersistResolution(e,t)}bindDescriptor(e,t,r){return this.getResolverByDescriptor(e,r).bindDescriptor(e,t,r)}getResolutionDependencies(e,t){return this.getResolverByDescriptor(e,t).getResolutionDependencies(e,t)}async getCandidates(e,t,r){const A=this.getResolverByDescriptor(e,r);return await A.getCandidates(e,t,r)}async getSatisfying(e,t,r){return this.getResolverByDescriptor(e,r).getSatisfying(e,t,r)}async resolve(e,t){const r=this.getResolverByLocator(e,t);return await r.resolve(e,t)}tryResolverByDescriptor(e,t){const r=this.resolvers.find(r=>r.supportsDescriptor(e,t));return r||null}getResolverByDescriptor(e,t){const r=this.resolvers.find(r=>r.supportsDescriptor(e,t));if(!r)throw new Error(A.prettyDescriptor(t.project.configuration,e)+" isn't supported by any available resolver");return r}tryResolverByLocator(e,t){const r=this.resolvers.find(r=>r.supportsLocator(e,t));return r||null}getResolverByLocator(e,t){const r=this.resolvers.find(r=>r.supportsLocator(e,t));if(!r)throw new Error(A.prettyLocator(t.project.configuration,e)+" isn't supported by any available resolver");return r}}},85824:(e,t,r)=>{"use strict";r.d(t,{I:()=>ie});var A=r(43896),n=r(46009),o=r(5944),i=r(11640),s=r(40822),a=r(76417);function c(){}function g(e,t,r,A,n){for(var o=0,i=t.length,s=0,a=0;oe.length?r:e})),c.value=e.join(l)}else c.value=e.join(r.slice(s,s+c.count));s+=c.count,c.added||(a+=c.count)}}var u=t[i-1];return i>1&&"string"==typeof u.value&&(u.added||u.removed)&&e.equals("",u.value)&&(t[i-2].value+=u.value,t.pop()),t}function l(e){return{newPos:e.newPos,components:e.components.slice(0)}}c.prototype={diff:function(e,t){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},A=r.callback;"function"==typeof r&&(A=r,r={}),this.options=r;var n=this;function o(e){return A?(setTimeout((function(){A(void 0,e)}),0),!0):e}e=this.castInput(e),t=this.castInput(t),e=this.removeEmpty(this.tokenize(e));var i=(t=this.removeEmpty(this.tokenize(t))).length,s=e.length,a=1,c=i+s,u=[{newPos:-1,components:[]}],h=this.extractCommon(u[0],t,e,0);if(u[0].newPos+1>=i&&h+1>=s)return o([{value:this.join(t),count:t.length}]);function p(){for(var r=-1*a;r<=a;r+=2){var A=void 0,c=u[r-1],h=u[r+1],p=(h?h.newPos:0)-r;c&&(u[r-1]=void 0);var d=c&&c.newPos+1=i&&p+1>=s)return o(g(n,A.components,t,e,n.useLongestToken));u[r]=A}else u[r]=void 0}a++}if(A)!function e(){setTimeout((function(){if(a>c)return A();p()||e()}),0)}();else for(;a<=c;){var d=p();if(d)return d}},pushComponent:function(e,t,r){var A=e[e.length-1];A&&A.added===t&&A.removed===r?e[e.length-1]={count:A.count+1,added:t,removed:r}:e.push({count:1,added:t,removed:r})},extractCommon:function(e,t,r,A){for(var n=t.length,o=r.length,i=e.newPos,s=i-A,a=0;i+10?a(d.lines.slice(-i.context)):[],g-=u.length,l-=u.length)}(o=u).push.apply(o,E(n.map((function(e){return(t.added?"+":"-")+e})))),t.added?p+=n.length:h+=n.length}else{if(g)if(n.length<=2*i.context&&e=s.length-2&&n.length<=i.context){var y=/\n$/.test(r),m=/\n$/.test(A),w=0==n.length&&u.length>B.oldLines;!y&&w&&u.splice(B.oldLines,0,"\\ No newline at end of file"),(y||w)&&m||u.push("\\ No newline at end of file")}c.push(B),g=0,l=0,u=[]}h+=n.length,p+=n.length}},f=0;f`${r}#commit=${A}`],[/^https:\/\/((?:[^/]+?)@)?codeload\.github\.com\/([^/]+\/[^/]+)\/tar\.gz\/([0-9a-f]+)$/,(e,t,r="",A,n)=>`https://${r}github.com/${A}.git#commit=${n}`],[/^https:\/\/((?:[^/]+?)@)?github\.com\/([^/]+\/[^/]+?)(?:\.git)?#([0-9a-f]+)$/,(e,t,r="",A,n)=>`https://${r}github.com/${A}.git#commit=${n}`],[/^https?:\/\/[^/]+\/(?:[^/]+\/)*(?:@[^/]+\/)?([^/]+)\/(?:-|download)\/\1-[^/]+\.tgz(?:#|$)/,e=>"npm:"+e],[/^https:\/\/npm\.pkg\.github\.com\/download\/(?:@[^/]+)\/(?:[^/]+)\/(?:[^/]+)\/(?:[0-9a-f]+)$/,e=>"npm:"+e],[/^https:\/\/npm\.fontawesome\.com\/(?:@[^/]+)\/([^/]+)\/-\/([^/]+)\/\1-\2.tgz(?:#|$)/,e=>"npm:"+e],[/^[^/]+\.tgz#[0-9a-f]+$/,e=>"npm:"+e]];class T{constructor(){this.resolutions=null}async setup(e,{report:t}){const r=n.y1.join(e.cwd,e.configuration.get("lockfileFilename"));if(!A.xfs.existsSync(r))return;const o=await A.xfs.readFilePromise(r,"utf8"),s=(0,i.parseSyml)(o);if(Object.prototype.hasOwnProperty.call(s,"__metadata"))return;const a=this.resolutions=new Map;for(const r of Object.keys(s)){let A=O.tryParseDescriptor(r);if(!A){t.reportWarning(P.b.YARN_IMPORT_FAILED,`Failed to parse the string "${r}" into a proper descriptor`);continue}k().validRange(A.range)&&(A=O.makeDescriptor(A,"npm:"+A.range));const{version:n,resolved:o}=s[r];if(!o)continue;let i;for(const[e,t]of U){const r=o.match(e);if(r){i=t(n,...r);break}}if(!i){t.reportWarning(P.b.YARN_IMPORT_FAILED,`${O.prettyDescriptor(e.configuration,A)}: Only some patterns can be imported from legacy lockfiles (not "${o}")`);continue}const c=O.makeLocator(A,i);a.set(A.descriptorHash,c)}}supportsDescriptor(e,t){return!!this.resolutions&&this.resolutions.has(e.descriptorHash)}supportsLocator(e,t){return!1}shouldPersistResolution(e,t){throw new Error("Assertion failed: This resolver doesn't support resolving locators to packages")}bindDescriptor(e,t,r){return e}getResolutionDependencies(e,t){return[]}async getCandidates(e,t,r){if(!this.resolutions)throw new Error("Assertion failed: The resolution store should have been setup");const A=this.resolutions.get(e.descriptorHash);if(!A)throw new Error("Assertion failed: The resolution should have been registered");return[A]}async getSatisfying(e,t,r){return null}async resolve(e,t){throw new Error("Assertion failed: This resolver doesn't support resolving locators to packages")}}class j{supportsDescriptor(e,t){return!!t.project.storedResolutions.get(e.descriptorHash)||!!t.project.originalPackages.has(O.convertDescriptorToLocator(e).locatorHash)}supportsLocator(e,t){return!!t.project.originalPackages.has(e.locatorHash)}shouldPersistResolution(e,t){throw new Error("The shouldPersistResolution method shouldn't be called on the lockfile resolver, which would always answer yes")}bindDescriptor(e,t,r){return e}getResolutionDependencies(e,t){return[]}async getCandidates(e,t,r){let A=r.project.originalPackages.get(O.convertDescriptorToLocator(e).locatorHash);if(A)return[A];const n=r.project.storedResolutions.get(e.descriptorHash);if(!n)throw new Error("Expected the resolution to have been successful - resolution not found");if(A=r.project.originalPackages.get(n),!A)throw new Error("Expected the resolution to have been successful - package not found");return[A]}async getSatisfying(e,t,r){return null}async resolve(e,t){const r=t.project.originalPackages.get(e.locatorHash);if(!r)throw new Error("The lockfile resolver isn't meant to resolve packages - they should already have been stored into a cache");return r}}var Y=r(46611),G=r(27092),H=r(35691);class J{constructor(e){this.resolver=e}supportsDescriptor(e,t){return this.resolver.supportsDescriptor(e,t)}supportsLocator(e,t){return this.resolver.supportsLocator(e,t)}shouldPersistResolution(e,t){return this.resolver.shouldPersistResolution(e,t)}bindDescriptor(e,t,r){return this.resolver.bindDescriptor(e,t,r)}getResolutionDependencies(e,t){return this.resolver.getResolutionDependencies(e,t)}async getCandidates(e,t,r){throw new H.lk(P.b.MISSING_LOCKFILE_ENTRY,"This package doesn't seem to be present in your lockfile; try to make an install to update your resolutions")}async getSatisfying(e,t,r){throw new H.lk(P.b.MISSING_LOCKFILE_ENTRY,"This package doesn't seem to be present in your lockfile; try to make an install to update your resolutions")}async resolve(e,t){throw new H.lk(P.b.MISSING_LOCKFILE_ENTRY,"This package doesn't seem to be present in your lockfile; try to make an install to update your resolutions")}}var q=r(33720),z=r(17722),W=r(81111),V=r(71643),X=r(20624),_=r(73632),Z=r(63088),$=r(36545),ee=r(32485);const te=/ *, */g,re=/\/$/,Ae=(0,N.promisify)(R().gzip),ne=(0,N.promisify)(R().gunzip),oe={restoreInstallersCustomData:["installersCustomData"],restoreResolutions:["accessibleLocators","optionalBuilds","storedDescriptors","storedResolutions","storedPackages","lockFileChecksum"]};class ie{constructor(e,{configuration:t}){this.resolutionAliases=new Map,this.workspaces=[],this.workspacesByCwd=new Map,this.workspacesByIdent=new Map,this.storedResolutions=new Map,this.storedDescriptors=new Map,this.storedPackages=new Map,this.storedChecksums=new Map,this.accessibleLocators=new Set,this.originalPackages=new Map,this.optionalBuilds=new Set,this.peerRequirements=new Map,this.installersCustomData=new Map,this.lockFileChecksum=null,this.configuration=t,this.cwd=e}static async find(e,t){var r,o,i;if(!e.projectCwd)throw new s.UsageError("No project found in "+t);let a=e.projectCwd,c=t,g=null;for(;g!==e.projectCwd;){if(g=c,A.xfs.existsSync(n.y1.join(g,n.QS.manifest))){a=g;break}c=n.y1.dirname(g)}const l=new ie(e.projectCwd,{configuration:e});null===(r=x.VK.telemetry)||void 0===r||r.reportProject(l.cwd),await l.setupResolutions(),await l.setupWorkspaces(),null===(o=x.VK.telemetry)||void 0===o||o.reportWorkspaceCount(l.workspaces.length),null===(i=x.VK.telemetry)||void 0===i||i.reportDependencyCount(l.workspaces.reduce((e,t)=>e+t.manifest.dependencies.size+t.manifest.devDependencies.size,0));const u=l.tryWorkspaceByCwd(a);if(u)return{project:l,workspace:u,locator:u.anchoredLocator};const h=await l.findLocatorForLocation(a+"/",{strict:!0});if(h)return{project:l,locator:h,workspace:null};throw new s.UsageError(`The nearest package directory (${V.pretty(e,a,V.Type.PATH)}) doesn't seem to be part of the project declared in ${V.pretty(e,l.cwd,V.Type.PATH)}.\n\n- If the project directory is right, it might be that you forgot to list ${V.pretty(e,n.y1.relative(l.cwd,a),V.Type.PATH)} as a workspace.\n- If it isn't, it's likely because you have a yarn.lock or package.json file there, confusing the project root detection.`)}static generateBuildStateFile(e,t){let r="# Warning: This file is automatically generated. Removing it is fine, but will\n# cause all your builds to become invalidated.\n";const A=[...e].map(([e,r])=>{const A=t.get(e);if(void 0===A)throw new Error("Assertion failed: The locator should have been registered");return[O.stringifyLocator(A),A.locatorHash,r]});for(const[e,t,n]of _.sortMap(A,[e=>e[0],e=>e[1]]))r+="\n",r+=`# ${e}\n`,r+=JSON.stringify(t)+":\n",r+=` ${n}\n`;return r}async setupResolutions(){this.storedResolutions=new Map,this.storedDescriptors=new Map,this.storedPackages=new Map,this.lockFileChecksum=null;const e=n.y1.join(this.cwd,this.configuration.get("lockfileFilename")),t=this.configuration.get("defaultLanguageName");if(A.xfs.existsSync(e)){const r=await A.xfs.readFilePromise(e,"utf8");this.lockFileChecksum=X.makeHash("1",r);const n=(0,i.parseSyml)(r);if(n.__metadata){const e=n.__metadata.version,r=n.__metadata.cacheKey;for(const A of Object.keys(n)){if("__metadata"===A)continue;const o=n[A];if(void 0===o.resolution)throw new Error(`Assertion failed: Expected the lockfile entry to have a resolution field (${A})`);const i=O.parseLocator(o.resolution,!0),s=new Y.G;s.load(o,{yamlCompatibilityMode:!0});const a=s.version,c=s.languageName||t,g=o.linkType.toUpperCase(),l=s.dependencies,u=s.peerDependencies,h=s.dependenciesMeta,p=s.peerDependenciesMeta,d=s.bin;if(null!=o.checksum){const e=void 0===r||o.checksum.includes("/")?o.checksum:`${r}/${o.checksum}`;this.storedChecksums.set(i.locatorHash,e)}if(e>=4){const e={...i,version:a,languageName:c,linkType:g,dependencies:l,peerDependencies:u,dependenciesMeta:h,peerDependenciesMeta:p,bin:d};this.originalPackages.set(e.locatorHash,e)}for(const t of A.split(te)){const r=O.parseDescriptor(t);if(this.storedDescriptors.set(r.descriptorHash,r),e>=4)this.storedResolutions.set(r.descriptorHash,i.locatorHash);else{const e=O.convertLocatorToDescriptor(i);e.descriptorHash!==r.descriptorHash&&(this.storedDescriptors.set(e.descriptorHash,e),this.resolutionAliases.set(r.descriptorHash,e.descriptorHash))}}}}}}async setupWorkspaces(){this.workspaces=[],this.workspacesByCwd=new Map,this.workspacesByIdent=new Map;let e=[this.cwd];for(;e.length>0;){const t=e;e=[];for(const r of t){if(this.workspacesByCwd.has(r))continue;const t=await this.addWorkspace(r),A=this.storedPackages.get(t.anchoredLocator.locatorHash);A&&(t.dependencies=A.dependencies);for(const r of t.workspacesCwds)e.push(r)}}}async addWorkspace(e){const t=new z.j(e,{project:this});await t.setup();const r=this.workspacesByIdent.get(t.locator.identHash);if(void 0!==r)throw new Error(`Duplicate workspace name ${O.prettyIdent(this.configuration,t.locator)}: ${e} conflicts with ${r.cwd}`);return this.workspaces.push(t),this.workspacesByCwd.set(e,t),this.workspacesByIdent.set(t.locator.identHash,t),t}get topLevelWorkspace(){return this.getWorkspaceByCwd(this.cwd)}tryWorkspaceByCwd(e){n.y1.isAbsolute(e)||(e=n.y1.resolve(this.cwd,e)),e=n.y1.normalize(e).replace(/\/+$/,"");const t=this.workspacesByCwd.get(e);return t||null}getWorkspaceByCwd(e){const t=this.tryWorkspaceByCwd(e);if(!t)throw new Error(`Workspace not found (${e})`);return t}tryWorkspaceByFilePath(e){let t=null;for(const r of this.workspaces){n.y1.relative(r.cwd,e).startsWith("../")||(t&&t.cwd.length>=r.cwd.length||(t=r))}return t||null}getWorkspaceByFilePath(e){const t=this.tryWorkspaceByFilePath(e);if(!t)throw new Error(`Workspace not found (${e})`);return t}tryWorkspaceByIdent(e){const t=this.workspacesByIdent.get(e.identHash);return void 0===t?null:t}getWorkspaceByIdent(e){const t=this.tryWorkspaceByIdent(e);if(!t)throw new Error(`Workspace not found (${O.prettyIdent(this.configuration,e)})`);return t}tryWorkspaceByDescriptor(e){const t=this.tryWorkspaceByIdent(e);return null!==t&&t.accepts(e.range)?t:null}getWorkspaceByDescriptor(e){const t=this.tryWorkspaceByDescriptor(e);if(null===t)throw new Error(`Workspace not found (${O.prettyDescriptor(this.configuration,e)})`);return t}tryWorkspaceByLocator(e){O.isVirtualLocator(e)&&(e=O.devirtualizeLocator(e));const t=this.tryWorkspaceByIdent(e);return null===t||t.locator.locatorHash!==e.locatorHash&&t.anchoredLocator.locatorHash!==e.locatorHash?null:t}getWorkspaceByLocator(e){const t=this.tryWorkspaceByLocator(e);if(!t)throw new Error(`Workspace not found (${O.prettyLocator(this.configuration,e)})`);return t}refreshWorkspaceDependencies(){for(const e of this.workspaces){const t=this.storedPackages.get(e.anchoredLocator.locatorHash);if(!t)throw new Error("Assertion failed: Expected workspace to have been resolved");e.dependencies=new Map(t.dependencies)}}forgetResolution(e){const t=e=>{this.storedResolutions.delete(e),this.storedDescriptors.delete(e)},r=e=>{this.originalPackages.delete(e),this.storedPackages.delete(e),this.accessibleLocators.delete(e)};if("descriptorHash"in e){const A=this.storedResolutions.get(e.descriptorHash);t(e.descriptorHash);const n=new Set(this.storedResolutions.values());void 0===A||n.has(A)||r(A)}if("locatorHash"in e){r(e.locatorHash);for(const[r,A]of this.storedResolutions)A===e.locatorHash&&t(r)}}forgetTransientResolutions(){const e=this.configuration.makeResolver();for(const t of this.originalPackages.values()){let r;try{r=e.shouldPersistResolution(t,{project:this,resolver:e})}catch(e){r=!1}r||this.forgetResolution(t)}}forgetVirtualResolutions(){for(const e of this.storedPackages.values())for(const[t,r]of e.dependencies)O.isVirtualDescriptor(r)&&e.dependencies.set(t,O.devirtualizeDescriptor(r))}getDependencyMeta(e,t){const r={},A=this.topLevelWorkspace.manifest.dependenciesMeta.get(O.stringifyIdent(e));if(!A)return r;const n=A.get(null);if(n&&Object.assign(r,n),null===t||!k().valid(t))return r;for(const[e,n]of A)null!==e&&e===t&&Object.assign(r,n);return r}async findLocatorForLocation(e,{strict:t=!1}={}){const r=new q.$,A=this.configuration.getLinkers(),n={project:this,report:r};for(const r of A){const A=await r.findPackageLocator(e,n);if(A){if(t){if((await r.findPackageLocation(A,n)).replace(re,"")!==e.replace(re,""))continue}return A}}return null}async resolveEverything(e){if(!this.workspacesByCwd||!this.workspacesByIdent)throw new Error("Workspaces must have been setup before calling this function");this.forgetVirtualResolutions(),e.lockfileOnly||this.forgetTransientResolutions();const t=e.resolver||this.configuration.makeResolver(),r=new T;await r.setup(this,{report:e.report});const o=e.lockfileOnly?new G.B([new j,new J(t)]):new G.B([new j,r,t]),i=this.configuration.makeFetcher(),s=e.lockfileOnly?{project:this,report:e.report,resolver:o}:{project:this,report:e.report,resolver:o,fetchOptions:{project:this,cache:e.cache,checksums:this.storedChecksums,report:e.report,fetcher:i}},a=new Map,c=new Map,g=new Map,l=new Map,u=new Map,h=new Map,p=[],d=async e=>{const t=await _.prettifyAsyncErrors(async()=>await o.resolve(e,s),t=>`${O.prettyLocator(this.configuration,e)}: ${t}`);if(!O.areLocatorsEqual(e,t))throw new Error(`Assertion failed: The locator cannot be changed by the resolver (went from ${O.prettyLocator(this.configuration,e)} to ${O.prettyLocator(this.configuration,t)})`);l.set(t.locatorHash,t);const r=this.configuration.normalizePackage(t);for(const[t,A]of r.dependencies){const n=await this.configuration.reduceHook(e=>e.reduceDependency,A,this,r,A,{resolver:o,resolveOptions:s});if(!O.areIdentsEqual(A,n))throw new Error("Assertion failed: The descriptor ident cannot be changed through aliases");const i=o.bindDescriptor(n,e,s);r.dependencies.set(t,i)}return p.push(Promise.all([...r.dependencies.values()].map(e=>f(e)))),c.set(r.locatorHash,r),r},C=async e=>{const t=this.resolutionAliases.get(e.descriptorHash);if(void 0!==t)return(async(e,t)=>{const r=await f(t);return a.set(e.descriptorHash,e),g.set(e.descriptorHash,r.locatorHash),r})(e,this.storedDescriptors.get(t));const r=o.getResolutionDependencies(e,s),A=new Map(await Promise.all(r.map(async e=>[e.descriptorHash,await f(e)]))),n=(await _.prettifyAsyncErrors(async()=>await o.getCandidates(e,A,s),t=>`${O.prettyDescriptor(this.configuration,e)}: ${t}`))[0];if(void 0===n)throw new Error(O.prettyDescriptor(this.configuration,e)+": No candidates found");return a.set(e.descriptorHash,e),g.set(e.descriptorHash,n.locatorHash),(async e=>{const t=u.get(e.locatorHash);if(void 0!==t)return t;const r=Promise.resolve().then(()=>d(e));return u.set(e.locatorHash,r),r})(n)},f=e=>{const t=h.get(e.descriptorHash);if(void 0!==t)return t;a.set(e.descriptorHash,e);const r=Promise.resolve().then(()=>C(e));return h.set(e.descriptorHash,r),r};for(const e of this.workspaces){const t=e.anchoredDescriptor;p.push(f(t))}for(;p.length>0;){const e=[...p];p.length=0,await Promise.all(e)}const I=new Set(this.resolutionAliases.values()),E=new Set(c.keys()),B=new Set,y=new Map;!function({project:e,allDescriptors:t,allResolutions:r,allPackages:o,accessibleLocators:i=new Set,optionalBuilds:s=new Set,volatileDescriptors:a=new Set,peerRequirements:c=new Map,report:g,tolerateMissingPackages:l=!1}){var u;const h=new Map,p=[],d=new Map,C=new Map,f=new Map,I=new Map,E=new Map,B=new Map(e.workspaces.map(e=>{const t=e.anchoredLocator.locatorHash,r=o.get(t);if(void 0===r){if(l)return[t,null];throw new Error("Assertion failed: The workspace should have an associated package")}return[t,O.copyPackage(r)]})),y=()=>{const e=A.xfs.mktempSync(),t=n.y1.join(e,"stacktrace.log"),r=String(p.length+1).length,o=p.map((e,t)=>`${(t+1+".").padStart(r," ")} ${O.stringifyLocator(e)}\n`).join("");throw A.xfs.writeFileSync(t,o),new H.lk(P.b.STACK_OVERFLOW_RESOLUTION,"Encountered a stack overflow when resolving peer dependencies; cf "+t)},m=e=>{const t=r.get(e.descriptorHash);if(void 0===t)throw new Error("Assertion failed: The resolution should have been registered");const A=o.get(t);if(!A)throw new Error("Assertion failed: The package could not be found");return A},w=(e,t,{first:r,optional:A})=>{p.length>1e3&&y(),p.push(e);const n=Q(e,t,{first:r,optional:A});return p.pop(),n},Q=(A,n,{first:c,optional:g})=>{if(i.has(A.locatorHash))return;i.add(A.locatorHash),g||s.delete(A.locatorHash);const u=o.get(A.locatorHash);if(!u){if(l)return;throw new Error(`Assertion failed: The package (${O.prettyLocator(e.configuration,A)}) should have been registered`)}const p=[],m=[],Q=[],D=[],b=[];for(const i of Array.from(u.dependencies.values())){if(u.peerDependencies.has(i.identHash)&&!c)continue;if(O.isVirtualDescriptor(i))throw new Error("Assertion failed: Virtual packages shouldn't be encountered when virtualizing a branch");a.delete(i.descriptorHash);let s=g;if(!s){const e=u.dependenciesMeta.get(O.stringifyIdent(i));if(void 0!==e){const t=e.get(null);void 0!==t&&t.optional&&(s=!0)}}const C=r.get(i.descriptorHash);if(!C){if(l)continue;throw new Error(`Assertion failed: The resolution (${O.prettyDescriptor(e.configuration,i)}) should have been registered`)}const v=B.get(C)||o.get(C);if(!v)throw new Error(`Assertion failed: The package (${C}, resolved from ${O.prettyDescriptor(e.configuration,i)}) should have been registered`);if(0===v.peerDependencies.size){w(v,new Map,{first:!1,optional:s});continue}const S=h.get(v.locatorHash);let k,N;"number"==typeof S&&S>=2&&y();const F=new Set;let K;m.push(()=>{k=O.virtualizeDescriptor(i,A.locatorHash),N=O.virtualizePackage(v,A.locatorHash),u.dependencies.delete(i.identHash),u.dependencies.set(k.identHash,k),r.set(k.descriptorHash,N.locatorHash),t.set(k.descriptorHash,k),o.set(N.locatorHash,N),p.push([v,k,N])}),Q.push(()=>{var e;K=new Map;for(const o of N.peerDependencies.values()){let i=u.dependencies.get(o.identHash);if(!i&&O.areIdentsEqual(A,o)&&(i=O.convertLocatorToDescriptor(A),t.set(i.descriptorHash,i),r.set(i.descriptorHash,A.locatorHash),a.delete(i.descriptorHash)),i||!N.dependencies.has(o.identHash)){if(i||(i=O.makeDescriptor(o,"missing:")),N.dependencies.set(i.identHash,i),O.isVirtualDescriptor(i)){_.getSetWithDefault(f,i.descriptorHash).add(N.locatorHash)}d.set(i.identHash,i),"missing:"===i.range&&F.add(i.identHash),K.set(o.identHash,null!==(e=n.get(o.identHash))&&void 0!==e?e:N.locatorHash)}else N.peerDependencies.delete(o.identHash)}N.dependencies=new Map(_.sortMap(N.dependencies,([e,t])=>O.stringifyIdent(t)))}),D.push(()=>{if(!o.has(N.locatorHash))return;const e=h.get(v.locatorHash),t=void 0!==e?e+1:1;h.set(v.locatorHash,t),w(N,K,{first:!1,optional:s}),h.set(v.locatorHash,t-1)}),b.push(()=>{const e=u.dependencies.get(i.identHash);if(void 0===e)throw new Error("Assertion failed: Expected the peer dependency to have been turned into a dependency");const t=r.get(e.descriptorHash);if(void 0===t)throw new Error("Assertion failed: Expected the descriptor to be registered");if(_.getSetWithDefault(E,t).add(A.locatorHash),o.has(N.locatorHash)){for(const e of N.peerDependencies.values()){const t=K.get(e.identHash);if(void 0===t)throw new Error("Assertion failed: Expected the peer dependency ident to be registered");_.getArrayWithDefault(_.getMapWithDefault(I,t),O.stringifyIdent(e)).push(N.locatorHash)}for(const e of F)N.dependencies.delete(e)}})}for(const e of[...m,...Q])e();let v;do{v=!0;for(const[A,n,s]of p){if(!o.has(s.locatorHash))continue;const a=_.getMapWithDefault(C,A.locatorHash),c=X.makeHash(...[...s.dependencies.values()].map(t=>{const A="missing:"!==t.range?r.get(t.descriptorHash):"missing:";if(void 0===A)throw new Error(`Assertion failed: Expected the resolution for ${O.prettyDescriptor(e.configuration,t)} to have been registered`);return A}),n.identHash),g=a.get(c);if(void 0===g){a.set(c,n);continue}if(g===n)continue;v=!1,o.delete(s.locatorHash),t.delete(n.descriptorHash),r.delete(n.descriptorHash),i.delete(s.locatorHash);const l=f.get(n.descriptorHash)||[],h=[u.locatorHash,...l];f.delete(n.descriptorHash);for(const e of h){const t=o.get(e);void 0!==t&&t.dependencies.set(n.identHash,g)}}}while(!v);for(const e of[...D,...b])e()};for(const t of e.workspaces)a.delete(t.anchoredDescriptor.descriptorHash),w(t.anchoredLocator,new Map,{first:!0,optional:!1});let D;!function(e){e[e.NotProvided=0]="NotProvided",e[e.NotCompatible=1]="NotCompatible"}(D||(D={}));const b=[];for(const[e,t]of E){const r=o.get(e);if(void 0===r)throw new Error("Assertion failed: Expected the root to be registered");const A=I.get(e);if(void 0!==A)for(const n of t){const t=o.get(n);if(void 0!==t)for(const[i,s]of A){const A=O.parseIdent(i);if(t.peerDependencies.has(A.identHash))continue;const a="p"+X.makeHash(n,i,e).slice(0,5);c.set(a,{subject:n,requested:A,rootRequester:e,allRequesters:s});const g=r.dependencies.get(A.identHash);if(void 0!==g){const e=m(g),n=null!==(u=e.version)&&void 0!==u?u:"0.0.0",i=new Set;for(const e of s){const t=o.get(e);if(void 0===t)throw new Error("Assertion failed: Expected the link to be registered");const r=t.peerDependencies.get(A.identHash);if(void 0===r)throw new Error("Assertion failed: Expected the ident to be registered");i.add(r.range)}[...i].every(e=>$.satisfiesWithPrereleases(n,e))||b.push({type:D.NotCompatible,subject:t,requested:A,requester:r,version:n,hash:a,requirementCount:s.length})}else{const e=r.peerDependenciesMeta.get(i);(null==e?void 0:e.optional)||b.push({type:D.NotProvided,subject:t,requested:A,requester:r,hash:a})}}}}const v=[e=>O.prettyLocatorNoColors(e.subject),e=>O.stringifyIdent(e.requested),e=>""+e.type];for(const t of _.sortMap(b,v))switch(t.type){case D.NotProvided:null==g||g.reportWarning(P.b.MISSING_PEER_DEPENDENCY,`${O.prettyLocator(e.configuration,t.subject)} doesn't provide ${O.prettyIdent(e.configuration,t.requested)} (${V.pretty(e.configuration,t.hash,V.Type.CODE)}), requested by ${O.prettyIdent(e.configuration,t.requester)}`);break;case D.NotCompatible:{const r=t.requirementCount>1?"and some of its descendants request":"requests";null==g||g.reportWarning(P.b.INCOMPATIBLE_PEER_DEPENDENCY,`${O.prettyLocator(e.configuration,t.subject)} provides ${O.prettyIdent(e.configuration,t.requested)} (${V.pretty(e.configuration,t.hash,V.Type.CODE)}) with version ${O.prettyReference(e.configuration,t.version)}, which doesn't satisfy what ${O.prettyIdent(e.configuration,t.requester)} ${r}`)}}b.length>0&&(null==g||g.reportWarning(P.b.UNNAMED,`Some peer dependencies are incorrectly met; run ${V.pretty(e.configuration,"yarn explain peer-requirements ",V.Type.CODE)} for details, where ${V.pretty(e.configuration,"",V.Type.CODE)} is the six-letter p-prefixed code`))}({project:this,report:e.report,accessibleLocators:B,volatileDescriptors:I,optionalBuilds:E,peerRequirements:y,allDescriptors:a,allResolutions:g,allPackages:c});for(const e of I)a.delete(e),g.delete(e);this.storedResolutions=g,this.storedDescriptors=a,this.storedPackages=c,this.accessibleLocators=B,this.originalPackages=l,this.optionalBuilds=E,this.peerRequirements=y,this.refreshWorkspaceDependencies()}async fetchEverything({cache:e,report:t,fetcher:r}){const A=r||this.configuration.makeFetcher(),n={checksums:this.storedChecksums,project:this,cache:e,fetcher:A,report:t},o=Array.from(new Set(_.sortMap(this.storedResolutions.values(),[e=>{const t=this.storedPackages.get(e);if(!t)throw new Error("Assertion failed: The locator should have been registered");return O.stringifyLocator(t)}])));let i=!1;const s=H.yG.progressViaCounter(o.length);t.reportProgress(s);const a=v()(32);if(await t.startCacheReport(async()=>{await Promise.all(o.map(e=>a(async()=>{const r=this.storedPackages.get(e);if(!r)throw new Error("Assertion failed: The locator should have been registered");if(O.isVirtualLocator(r))return;let o;try{o=await A.fetch(r,n)}catch(e){return e.message=`${O.prettyLocator(this.configuration,r)}: ${e.message}`,t.reportExceptionOnce(e),void(i=e)}o.checksum?this.storedChecksums.set(r.locatorHash,o.checksum):this.storedChecksums.delete(r.locatorHash),o.releaseFs&&o.releaseFs()}).finally(()=>{s.tick()})))}),i)throw i}async linkEverything({cache:e,report:t,fetcher:r,skipBuild:o}){var s;const c=r||this.configuration.makeFetcher(),g={checksums:this.storedChecksums,project:this,cache:e,fetcher:c,report:t,skipIntegrityCheck:!0},l=this.configuration.getLinkers(),u={project:this,report:t},h=new Map(l.map(e=>{const t=e.makeInstaller(u),r=t.getCustomDataKey(),A=this.installersCustomData.get(r);return void 0!==A&&t.attachCustomData(A),[e,t]})),p=new Map,d=new Map,C=new Map,f=new Map(await Promise.all([...this.accessibleLocators].map(async e=>{const t=this.storedPackages.get(e);if(!t)throw new Error("Assertion failed: The locator should have been registered");return[e,await c.fetch(t,g)]})));for(const e of this.accessibleLocators){const t=this.storedPackages.get(e);if(void 0===t)throw new Error("Assertion failed: The locator should have been registered");const r=f.get(t.locatorHash);if(void 0===r)throw new Error("Assertion failed: The fetch result should have been registered");const A=this.tryWorkspaceByLocator(t);if(null!==A){const e=[],{scripts:o}=A.manifest;for(const t of["preinstall","install","postinstall"])o.has(t)&&e.push([L.k.SCRIPT,t]);try{for(const e of h.values()){if(null!==(await e.installPackage(t,r)).buildDirective)throw new Error("Assertion failed: Linkers can't return build directives for workspaces; this responsibility befalls to the Yarn core")}}finally{r.releaseFs&&r.releaseFs()}const i=n.y1.join(r.packageFs.getRealPath(),r.prefixPath);d.set(t.locatorHash,i),e.length>0&&C.set(t.locatorHash,{directives:e,buildLocations:[i]})}else{const e=l.find(e=>e.supportsPackage(t,u));if(!e)throw new H.lk(P.b.LINKER_NOT_FOUND,O.prettyLocator(this.configuration,t)+" isn't supported by any available linker");const A=h.get(e);if(!A)throw new Error("Assertion failed: The installer should have been registered");let n;try{n=await A.installPackage(t,r)}finally{r.releaseFs&&r.releaseFs()}p.set(t.locatorHash,e),d.set(t.locatorHash,n.packageLocation),n.buildDirective&&n.packageLocation&&C.set(t.locatorHash,{directives:n.buildDirective,buildLocations:[n.packageLocation]})}}const I=new Map;for(const e of this.accessibleLocators){const t=this.storedPackages.get(e);if(!t)throw new Error("Assertion failed: The locator should have been registered");const r=null!==this.tryWorkspaceByLocator(t),A=async(e,A)=>{const n=d.get(t.locatorHash);if(void 0===n)throw new Error(`Assertion failed: The package (${O.prettyLocator(this.configuration,t)}) should have been registered`);const o=[];for(const A of t.dependencies.values()){const i=this.storedResolutions.get(A.descriptorHash);if(void 0===i)throw new Error(`Assertion failed: The resolution (${O.prettyDescriptor(this.configuration,A)}, from ${O.prettyLocator(this.configuration,t)})should have been registered`);const s=this.storedPackages.get(i);if(void 0===s)throw new Error(`Assertion failed: The package (${i}, resolved from ${O.prettyDescriptor(this.configuration,A)}) should have been registered`);const a=null===this.tryWorkspaceByLocator(s)?p.get(i):null;if(void 0===a)throw new Error(`Assertion failed: The package (${i}, resolved from ${O.prettyDescriptor(this.configuration,A)}) should have been registered`);const c=null===a;if(a===e||r||c)null!==d.get(s.locatorHash)&&o.push([A,s]);else if(null!==n){_.getArrayWithDefault(I,i).push(n)}}null!==n&&await A.attachInternalDependencies(t,o)};if(r)for(const[e,t]of h)await A(e,t);else{const e=p.get(t.locatorHash);if(!e)throw new Error("Assertion failed: The linker should have been found");const r=h.get(e);if(!r)throw new Error("Assertion failed: The installer should have been registered");await A(e,r)}}for(const[e,t]of I){const r=this.storedPackages.get(e);if(!r)throw new Error("Assertion failed: The package should have been registered");const A=p.get(r.locatorHash);if(!A)throw new Error("Assertion failed: The linker should have been found");const n=h.get(A);if(!n)throw new Error("Assertion failed: The installer should have been registered");await n.attachExternalDependents(r,t)}const E=new Map;for(const e of h.values()){const t=await e.finalizeInstall();for(const e of null!==(s=null==t?void 0:t.records)&&void 0!==s?s:[])C.set(e.locatorHash,{directives:e.buildDirective,buildLocations:e.buildLocations});void 0!==(null==t?void 0:t.customData)&&E.set(e.getCustomDataKey(),t.customData)}if(this.installersCustomData=E,await this.persistInstallStateFile(),o)return;const B=new Set(this.storedPackages.keys()),y=new Set(C.keys());for(const e of y)B.delete(e);const m=(0,a.createHash)("sha512");m.update(process.versions.node),this.configuration.triggerHook(e=>e.globalHashGeneration,this,e=>{m.update("\0"),m.update(e)});const w=m.digest("hex"),Q=new Map,D=e=>{let t=Q.get(e.locatorHash);if(void 0!==t)return t;const r=this.storedPackages.get(e.locatorHash);if(void 0===r)throw new Error("Assertion failed: The package should have been registered");const A=(0,a.createHash)("sha512");A.update(e.locatorHash),Q.set(e.locatorHash,"");for(const e of r.dependencies.values()){const t=this.storedResolutions.get(e.descriptorHash);if(void 0===t)throw new Error(`Assertion failed: The resolution (${O.prettyDescriptor(this.configuration,e)}) should have been registered`);const r=this.storedPackages.get(t);if(void 0===r)throw new Error("Assertion failed: The package should have been registered");A.update(D(r))}return t=A.digest("hex"),Q.set(e.locatorHash,t),t},b=(e,t)=>{const r=(0,a.createHash)("sha512");r.update(w),r.update(D(e));for(const e of t)r.update(e);return r.digest("hex")},v=this.configuration.get("bstatePath"),S=A.xfs.existsSync(v)?(0,i.parseSyml)(await A.xfs.readFilePromise(v,"utf8")):{},k=new Map;for(;y.size>0;){const e=y.size,r=[];for(const e of y){const o=this.storedPackages.get(e);if(!o)throw new Error("Assertion failed: The package should have been registered");let i=!0;for(const e of o.dependencies.values()){const t=this.storedResolutions.get(e.descriptorHash);if(!t)throw new Error(`Assertion failed: The resolution (${O.prettyDescriptor(this.configuration,e)}) should have been registered`);if(y.has(t)){i=!1;break}}if(!i)continue;y.delete(e);const s=C.get(o.locatorHash);if(!s)throw new Error("Assertion failed: The build directive should have been registered");const a=b(o,s.buildLocations);if(Object.prototype.hasOwnProperty.call(S,o.locatorHash)&&S[o.locatorHash]===a)k.set(o.locatorHash,a);else{Object.prototype.hasOwnProperty.call(S,o.locatorHash)?t.reportInfo(P.b.MUST_REBUILD,O.prettyLocator(this.configuration,o)+" must be rebuilt because its dependency tree changed"):t.reportInfo(P.b.MUST_BUILD,O.prettyLocator(this.configuration,o)+" must be built because it never did before or the last one failed");for(const e of s.buildLocations){if(!n.y1.isAbsolute(e))throw new Error(`Assertion failed: Expected the build location to be absolute (not ${e})`);r.push((async()=>{for(const[r,i]of s.directives){let s=`# This file contains the result of Yarn building a package (${O.stringifyLocator(o)})\n`;switch(r){case L.k.SCRIPT:s+=`# Script name: ${i}\n`;break;case L.k.SHELLCODE:s+=`# Script code: ${i}\n`}const c=null;await A.xfs.mktempPromise(async g=>{const l=n.y1.join(g,"build.log"),{stdout:u,stderr:h}=this.configuration.getSubprocessStreams(l,{header:s,prefix:O.prettyLocator(this.configuration,o),report:t});let p;try{switch(r){case L.k.SCRIPT:p=await Z.executePackageScript(o,i,[],{cwd:e,project:this,stdin:c,stdout:u,stderr:h});break;case L.k.SHELLCODE:p=await Z.executePackageShellcode(o,i,[],{cwd:e,project:this,stdin:c,stdout:u,stderr:h})}}catch(e){h.write(e.stack),p=1}if(u.end(),h.end(),0===p)return k.set(o.locatorHash,a),!0;A.xfs.detachTemp(g);const d=`${O.prettyLocator(this.configuration,o)} couldn't be built successfully (exit code ${V.pretty(this.configuration,p,V.Type.NUMBER)}, logs can be found here: ${V.pretty(this.configuration,l,V.Type.PATH)})`;return t.reportInfo(P.b.BUILD_FAILED,d),this.optionalBuilds.has(o.locatorHash)?(k.set(o.locatorHash,a),!0):(t.reportError(P.b.BUILD_FAILED,d),!1)})}})())}}}if(await Promise.all(r),e===y.size){const e=Array.from(y).map(e=>{const t=this.storedPackages.get(e);if(!t)throw new Error("Assertion failed: The package should have been registered");return O.prettyLocator(this.configuration,t)}).join(", ");t.reportError(P.b.CYCLIC_DEPENDENCIES,`Some packages have circular dependencies that make their build order unsatisfiable - as a result they won't be built (affected packages are: ${e})`);break}}if(k.size>0){const e=this.configuration.get("bstatePath"),t=ie.generateBuildStateFile(k,this.storedPackages);await A.xfs.mkdirPromise(n.y1.dirname(e),{recursive:!0}),await A.xfs.changeFilePromise(e,t,{automaticNewlines:!0})}else await A.xfs.removePromise(v)}async install(e){var t,r;const i=this.configuration.get("nodeLinker");null===(t=x.VK.telemetry)||void 0===t||t.reportInstall(i),await e.report.startTimerPromise("Project validation",{skipIfEmpty:!0},async()=>{await this.configuration.triggerHook(e=>e.validateProject,this,{reportWarning:e.report.reportWarning.bind(e.report),reportError:e.report.reportError.bind(e.report)})});for(const e of this.configuration.packageExtensions.values())for(const[,t]of e)for(const e of t)e.status=ee._u.Inactive;const s=n.y1.join(this.cwd,this.configuration.get("lockfileFilename"));let a=null;if(e.immutable)try{a=await A.xfs.readFilePromise(s,"utf8")}catch(e){throw"ENOENT"===e.code?new H.lk(P.b.FROZEN_LOCKFILE_EXCEPTION,"The lockfile would have been created by this install, which is explicitly forbidden."):e}await e.report.startTimerPromise("Resolution step",async()=>{await this.resolveEverything(e)}),await e.report.startTimerPromise("Post-resolution validation",{skipIfEmpty:!0},async()=>{for(const[,t]of this.configuration.packageExtensions)for(const[,r]of t)for(const t of r)if(t.userProvided){const r=V.pretty(this.configuration,t,V.Type.PACKAGE_EXTENSION);switch(t.status){case ee._u.Inactive:e.report.reportWarning(P.b.UNUSED_PACKAGE_EXTENSION,r+": No matching package in the dependency tree; you may not need this rule anymore.");break;case ee._u.Redundant:e.report.reportWarning(P.b.REDUNDANT_PACKAGE_EXTENSION,r+": This rule seems redundant when applied on the original package; the extension may have been applied upstream.")}}if(null!==a){const t=(0,o.qH)(a,this.generateLockfile());if(t!==a){const r=w(s,s,a,t);e.report.reportSeparator();for(const t of r.hunks){e.report.reportInfo(null,`@@ -${t.oldStart},${t.oldLines} +${t.newStart},${t.newLines} @@`);for(const r of t.lines)r.startsWith("+")?e.report.reportError(P.b.FROZEN_LOCKFILE_EXCEPTION,V.pretty(this.configuration,r,V.Type.ADDED)):r.startsWith("-")?e.report.reportError(P.b.FROZEN_LOCKFILE_EXCEPTION,V.pretty(this.configuration,r,V.Type.REMOVED)):e.report.reportInfo(null,V.pretty(this.configuration,r,"grey"))}throw e.report.reportSeparator(),new H.lk(P.b.FROZEN_LOCKFILE_EXCEPTION,"The lockfile would have been modified by this install, which is explicitly forbidden.")}}});for(const e of this.configuration.packageExtensions.values())for(const[,t]of e)for(const e of t)e.userProvided&&e.status===ee._u.Active&&(null===(r=x.VK.telemetry)||void 0===r||r.reportPackageExtension(V.json(e,V.Type.PACKAGE_EXTENSION)));await e.report.startTimerPromise("Fetch step",async()=>{await this.fetchEverything(e),(void 0===e.persistProject||e.persistProject)&&await this.cacheCleanup(e)}),(void 0===e.persistProject||e.persistProject)&&await this.persist(),await e.report.startTimerPromise("Link step",async()=>{const t=e.immutable?[...new Set(this.configuration.get("immutablePatterns"))].sort():[],r=await Promise.all(t.map(async e=>X.checksumPattern(e,{cwd:this.cwd})));await this.linkEverything(e);const A=await Promise.all(t.map(async e=>X.checksumPattern(e,{cwd:this.cwd})));for(let n=0;ne.afterAllInstalled,this,e)}generateLockfile(){const e=new Map;for(const[t,r]of this.storedResolutions.entries()){let A=e.get(r);A||e.set(r,A=new Set),A.add(t)}const t={__metadata:{version:4}};for(const[r,A]of e.entries()){const e=this.originalPackages.get(r);if(!e)continue;const n=[];for(const e of A){const t=this.storedDescriptors.get(e);if(!t)throw new Error("Assertion failed: The descriptor should have been registered");n.push(t)}const o=n.map(e=>O.stringifyDescriptor(e)).sort().join(", "),i=new Y.G;let s;i.version=e.linkType===ee.Un.HARD?e.version:"0.0.0-use.local",i.languageName=e.languageName,i.dependencies=new Map(e.dependencies),i.peerDependencies=new Map(e.peerDependencies),i.dependenciesMeta=new Map(e.dependenciesMeta),i.peerDependenciesMeta=new Map(e.peerDependenciesMeta),i.bin=new Map(e.bin);const a=this.storedChecksums.get(e.locatorHash);if(void 0!==a){const e=a.indexOf("/");if(-1===e)throw new Error("Assertion failed: Expecte the checksum to reference its cache key");const r=a.slice(0,e),A=a.slice(e+1);void 0===t.__metadata.cacheKey&&(t.__metadata.cacheKey=r),s=r===t.__metadata.cacheKey?A:a}t[o]={...i.exportTo({},{compatibilityMode:!1}),linkType:e.linkType.toLowerCase(),resolution:O.stringifyLocator(e),checksum:s}}return['# This file is generated by running "yarn install" inside your project.\n',"# Manual changes might be lost - proceed with caution!\n"].join("")+"\n"+(0,i.stringifySyml)(t)}async persistLockfile(){const e=n.y1.join(this.cwd,this.configuration.get("lockfileFilename")),t=this.generateLockfile();await A.xfs.changeFilePromise(e,t,{automaticNewlines:!0})}async persistInstallStateFile(){const e=[];for(const t of Object.values(oe))e.push(...t);const t=D()(this,e),r=await Ae(K().serialize(t)),o=this.configuration.get("installStatePath");await A.xfs.mkdirPromise(n.y1.dirname(o),{recursive:!0}),await A.xfs.changeFilePromise(o,r)}async restoreInstallState({restoreInstallersCustomData:e=!0,restoreResolutions:t=!0}={}){const r=this.configuration.get("installStatePath");if(!A.xfs.existsSync(r))return void(t&&await this.applyLightResolution());const n=await A.xfs.readFilePromise(r),o=K().deserialize(await ne(n));e&&void 0!==o.installersCustomData&&(this.installersCustomData=o.installersCustomData),t&&(o.lockFileChecksum===this.lockFileChecksum?(Object.assign(this,D()(o,oe.restoreResolutions)),this.refreshWorkspaceDependencies()):await this.applyLightResolution())}async applyLightResolution(){await this.resolveEverything({lockfileOnly:!0,report:new q.$}),await this.persistInstallStateFile()}async persist(){await this.persistLockfile();for(const e of this.workspacesByCwd.values())await e.persistManifest()}async cacheCleanup({cache:e,report:t}){const r=new Set([".gitignore"]);if(A.xfs.existsSync(e.cwd)&&(0,W.isFolderInside)(e.cwd,this.cwd)){for(const o of await A.xfs.readdirPromise(e.cwd)){if(r.has(o))continue;const i=n.y1.resolve(e.cwd,o);e.markedFiles.has(i)||(e.immutable?t.reportError(P.b.IMMUTABLE_CACHE,V.pretty(this.configuration,n.y1.basename(i),"magenta")+" appears to be unused and would marked for deletion, but the cache is immutable"):(t.reportInfo(P.b.UNUSED_CACHE_ENTRY,V.pretty(this.configuration,n.y1.basename(i),"magenta")+" appears to be unused - removing"),await A.xfs.removePromise(i)))}e.markedFiles.clear()}}}},52779:(e,t,r)=>{"use strict";r.d(t,{c:()=>s,O:()=>a});var A=r(53887),n=r.n(A),o=r(36545),i=r(54143);const s=/^(?!v)[a-z0-9-.]+$/i;class a{supportsDescriptor(e,t){return!!o.validRange(e.range)||!!s.test(e.range)}supportsLocator(e,t){return!!n().valid(e.reference)||!!s.test(e.reference)}shouldPersistResolution(e,t){return t.resolver.shouldPersistResolution(this.forwardLocator(e,t),t)}bindDescriptor(e,t,r){return r.resolver.bindDescriptor(this.forwardDescriptor(e,r),t,r)}getResolutionDependencies(e,t){return t.resolver.getResolutionDependencies(this.forwardDescriptor(e,t),t)}async getCandidates(e,t,r){return await r.resolver.getCandidates(this.forwardDescriptor(e,r),t,r)}async getSatisfying(e,t,r){return await r.resolver.getSatisfying(this.forwardDescriptor(e,r),t,r)}async resolve(e,t){const r=await t.resolver.resolve(this.forwardLocator(e,t),t);return i.renamePackage(r,e)}forwardDescriptor(e,t){return i.makeDescriptor(e,`${t.project.configuration.get("defaultProtocol")}${e.range}`)}forwardLocator(e,t){return i.makeLocator(e,`${t.project.configuration.get("defaultProtocol")}${e.reference}`)}}},35691:(e,t,r)=>{"use strict";r.d(t,{lk:()=>i,yG:()=>s});var A=r(92413),n=r(24304),o=r(92659);class i extends Error{constructor(e,t,r){super(t),this.reportExtra=r,this.reportCode=e}}class s{constructor(){this.reportedInfos=new Set,this.reportedWarnings=new Set,this.reportedErrors=new Set}static progressViaCounter(e){let t,r=0,A=new Promise(e=>{t=e});const n=e=>{const n=t;A=new Promise(e=>{t=e}),r=e,n()},o=async function*(){for(;ro,set:n,tick:(e=0)=>{n(r+1)}}}reportInfoOnce(e,t,r){const A=r&&r.key?r.key:t;this.reportedInfos.has(A)||(this.reportedInfos.add(A),this.reportInfo(e,t))}reportWarningOnce(e,t,r){const A=r&&r.key?r.key:t;this.reportedWarnings.has(A)||(this.reportedWarnings.add(A),this.reportWarning(e,t))}reportErrorOnce(e,t,r){var A;const n=r&&r.key?r.key:t;this.reportedErrors.has(n)||(this.reportedErrors.add(n),this.reportError(e,t),null===(A=null==r?void 0:r.reportExtra)||void 0===A||A.call(r,this))}reportExceptionOnce(e){!function(e){return void 0!==e.reportCode}(e)?this.reportErrorOnce(o.b.EXCEPTION,e.stack||e.message,{key:e}):this.reportErrorOnce(e.reportCode,e.message,{key:e,reportExtra:e.reportExtra})}createStreamReporter(e=null){const t=new A.PassThrough,r=new n.StringDecoder;let o="";return t.on("data",t=>{let A,n=r.write(t);do{if(A=n.indexOf("\n"),-1!==A){const t=o+n.substr(0,A);n=n.substr(A+1),o="",null!==e?this.reportInfo(null,`${e} ${t}`):this.reportInfo(null,t)}}while(-1!==A);o+=n}),t.on("end",()=>{const t=r.end();""!==t&&(null!==e?this.reportInfo(null,`${e} ${t}`):this.reportInfo(null,t))}),t}}},15815:(e,t,r)=>{"use strict";r.d(t,{Qw:()=>C,Pk:()=>f});var A=r(29148),n=r.n(A),o=r(92659),i=r(35691),s=r(71643);const a=["⠋","⠙","⠹","⠸","⠼","⠴","⠦","⠧","⠇","⠏"],c=new Set([o.b.FETCH_NOT_CACHED,o.b.UNUSED_CACHE_ENTRY]),g=process.env.GITHUB_ACTIONS?{start:e=>`::group::${e}\n`,end:e=>"::endgroup::\n"}:process.env.TRAVIS?{start:e=>`travis_fold:start:${e}\n`,end:e=>`travis_fold:end:${e}\n`}:process.env.GITLAB_CI?{start:e=>`section_start:${Math.floor(Date.now()/1e3)}:${e.toLowerCase().replace(/\W+/g,"_")}\r${e}\n`,end:e=>`section_end:${Math.floor(Date.now()/1e3)}:${e.toLowerCase().replace(/\W+/g,"_")}\r`}:null,l=new Date,u=["iTerm.app","Apple_Terminal"].includes(process.env.TERM_PROGRAM)||!!process.env.WT_SESSION,h={patrick:{date:[17,3],chars:["🍀","🌱"],size:40},simba:{date:[19,7],chars:["🦁","🌴"],size:40},jack:{date:[31,10],chars:["🎃","🦇"],size:40},hogsfather:{date:[31,12],chars:["🎉","🎄"],size:40},default:{chars:["=","-"],size:80}},p=u&&Object.keys(h).find(e=>{const t=h[e];return!t.date||t.date[0]===l.getDate()&&t.date[1]===l.getMonth()+1})||"default";function d(e,{configuration:t,json:r}){const A=null===e?0:e,n=(0,o.i)(A);return r||null!==e?n:s.pretty(t,n,"grey")}function C(e,{configuration:t,json:r}){const A=d(e,{configuration:t,json:r});if(!t.get("enableHyperlinks"))return A;if(null===e||e===o.b.UNNAMED)return A;return`]8;;${`https://yarnpkg.com/advanced/error-codes#${A}---${o.b[e]}`.toLowerCase()}${A}]8;;`}class f extends i.yG{constructor({configuration:e,stdout:t,json:r=!1,includeFooter:A=!0,includeLogs:n=!r,includeInfos:o=n,includeWarnings:i=n,forgettableBufferSize:a=5,forgettableNames:g=new Set}){super(),this.uncommitted=new Set,this.cacheHitCount=0,this.cacheMissCount=0,this.warningCount=0,this.errorCount=0,this.startTime=Date.now(),this.indent=0,this.progress=new Map,this.progressTime=0,this.progressFrame=0,this.progressTimeout=null,this.forgettableLines=[],s.addLogFilterSupport(this,{configuration:e}),this.configuration=e,this.forgettableBufferSize=a,this.forgettableNames=new Set([...g,...c]),this.includeFooter=A,this.includeInfos=o,this.includeWarnings=i,this.json=r,this.stdout=t;const l=this.configuration.get("progressBarStyle")||p;if(!Object.prototype.hasOwnProperty.call(h,l))throw new Error("Assertion failed: Invalid progress bar style");this.progressStyle=h[l];const u="➤ YN0000: ┌ ".length,d=Math.max(0,Math.min(process.stdout.columns-u,80));this.progressMaxScaledSize=Math.floor(this.progressStyle.size*d/80)}static async start(e,t){const r=new this(e),A=process.emitWarning;process.emitWarning=(e,t)=>{if("string"!=typeof e){const r=e;e=r.message,t=null!=t?t:r.name}const A=void 0!==t?`${t}: ${e}`:e;r.reportWarning(o.b.UNNAMED,A)};try{await t(r)}catch(e){r.reportExceptionOnce(e)}finally{await r.finalize(),process.emitWarning=A}return r}hasErrors(){return this.errorCount>0}exitCode(){return this.hasErrors()?1:0}reportCacheHit(e){this.cacheHitCount+=1}reportCacheMiss(e,t){this.cacheMissCount+=1,void 0===t||this.configuration.get("preferAggregateCacheInfo")||this.reportInfo(o.b.FETCH_NOT_CACHED,t)}startTimerSync(e,t,r){const A="function"==typeof t?t:r,n={committed:!1,action:()=>{this.reportInfo(null,"┌ "+e),this.indent+=1,null!==g&&this.stdout.write(g.start(e))}};("function"==typeof t?{}:t).skipIfEmpty?this.uncommitted.add(n):(n.action(),n.committed=!0);const o=Date.now();try{return A()}catch(e){throw this.reportExceptionOnce(e),e}finally{const t=Date.now();this.uncommitted.delete(n),n.committed&&(this.indent-=1,null!==g&&this.stdout.write(g.end(e)),this.configuration.get("enableTimers")&&t-o>200?this.reportInfo(null,"└ Completed in "+s.pretty(this.configuration,t-o,s.Type.DURATION)):this.reportInfo(null,"└ Completed"))}}async startTimerPromise(e,t,r){const A="function"==typeof t?t:r,n={committed:!1,action:()=>{this.reportInfo(null,"┌ "+e),this.indent+=1,null!==g&&this.stdout.write(g.start(e))}};("function"==typeof t?{}:t).skipIfEmpty?this.uncommitted.add(n):(n.action(),n.committed=!0);const o=Date.now();try{return await A()}catch(e){throw this.reportExceptionOnce(e),e}finally{const t=Date.now();this.uncommitted.delete(n),n.committed&&(this.indent-=1,null!==g&&this.stdout.write(g.end(e)),this.configuration.get("enableTimers")&&t-o>200?this.reportInfo(null,"└ Completed in "+s.pretty(this.configuration,t-o,s.Type.DURATION)):this.reportInfo(null,"└ Completed"))}}async startCacheReport(e){const t=this.configuration.get("preferAggregateCacheInfo")?{cacheHitCount:this.cacheHitCount,cacheMissCount:this.cacheMissCount}:null;try{return await e()}catch(e){throw this.reportExceptionOnce(e),e}finally{null!==t&&this.reportCacheChanges(t)}}reportSeparator(){0===this.indent?this.writeLineWithForgettableReset(""):this.reportInfo(null,"")}reportInfo(e,t){if(!this.includeInfos)return;this.commit();const r=`${s.pretty(this.configuration,"➤","blueBright")} ${this.formatNameWithHyperlink(e)}: ${this.formatIndent()}${t}`;if(this.json)this.reportJson({type:"info",name:e,displayName:this.formatName(e),indent:this.formatIndent(),data:t});else if(this.forgettableNames.has(e))if(this.forgettableLines.push(r),this.forgettableLines.length>this.forgettableBufferSize){for(;this.forgettableLines.length>this.forgettableBufferSize;)this.forgettableLines.shift();this.writeLines(this.forgettableLines,{truncate:!0})}else this.writeLine(r,{truncate:!0});else this.writeLineWithForgettableReset(r)}reportWarning(e,t){this.warningCount+=1,this.includeWarnings&&(this.commit(),this.json?this.reportJson({type:"warning",name:e,displayName:this.formatName(e),indent:this.formatIndent(),data:t}):this.writeLineWithForgettableReset(`${s.pretty(this.configuration,"➤","yellowBright")} ${this.formatNameWithHyperlink(e)}: ${this.formatIndent()}${t}`))}reportError(e,t){this.errorCount+=1,this.commit(),this.json?this.reportJson({type:"error",name:e,displayName:this.formatName(e),indent:this.formatIndent(),data:t}):this.writeLineWithForgettableReset(`${s.pretty(this.configuration,"➤","redBright")} ${this.formatNameWithHyperlink(e)}: ${this.formatIndent()}${t}`,{truncate:!1})}reportProgress(e){let t=!1;const r=Promise.resolve().then(async()=>{const r={progress:0,title:void 0};this.progress.set(e,{definition:r,lastScaledSize:-1}),this.refreshProgress(-1);for await(const{progress:A,title:n}of e)t||r.progress===A&&r.title===n||(r.progress=A,r.title=n,this.refreshProgress());A()}),A=()=>{t||(t=!0,this.progress.delete(e),this.refreshProgress(1))};return{...r,stop:A}}reportJson(e){this.json&&this.writeLineWithForgettableReset(""+JSON.stringify(e))}async finalize(){if(!this.includeFooter)return;let e="";e=this.errorCount>0?"Failed with errors":this.warningCount>0?"Done with warnings":"Done";const t=s.pretty(this.configuration,Date.now()-this.startTime,s.Type.DURATION),r=this.configuration.get("enableTimers")?`${e} in ${t}`:e;this.errorCount>0?this.reportError(o.b.UNNAMED,r):this.warningCount>0?this.reportWarning(o.b.UNNAMED,r):this.reportInfo(o.b.UNNAMED,r)}writeLine(e,{truncate:t}={}){this.clearProgress({clear:!0}),this.stdout.write(this.truncate(e,{truncate:t})+"\n"),this.writeProgress()}writeLineWithForgettableReset(e,{truncate:t}={}){this.forgettableLines=[],this.writeLine(e,{truncate:t})}writeLines(e,{truncate:t}={}){this.clearProgress({delta:e.length});for(const r of e)this.stdout.write(this.truncate(r,{truncate:t})+"\n");this.writeProgress()}reportCacheChanges({cacheHitCount:e,cacheMissCount:t}){const r=this.cacheHitCount-e,A=this.cacheMissCount-t;if(0===r&&0===A)return;let n="";this.cacheHitCount>1?n+=this.cacheHitCount+" packages were already cached":1===this.cacheHitCount?n+=" - one package was already cached":n+="No packages were cached",this.cacheHitCount>0?this.cacheMissCount>1?n+=`, ${this.cacheMissCount} had to be fetched`:1===this.cacheMissCount&&(n+=", one had to be fetched"):this.cacheMissCount>1?n+=` - ${this.cacheMissCount} packages had to be fetched`:1===this.cacheMissCount&&(n+=" - one package had to be fetched"),this.reportInfo(o.b.FETCH_NOT_CACHED,n)}commit(){const e=this.uncommitted;this.uncommitted=new Set;for(const t of e)t.committed=!0,t.action()}clearProgress({delta:e=0,clear:t=!1}){this.configuration.get("enableProgressBars")&&!this.json&&this.progress.size+e>0&&(this.stdout.write(`[${this.progress.size+e}A`),(e>0||t)&&this.stdout.write(""))}writeProgress(){if(!this.configuration.get("enableProgressBars")||this.json)return;if(null!==this.progressTimeout&&clearTimeout(this.progressTimeout),this.progressTimeout=null,0===this.progress.size)return;const e=Date.now();e-this.progressTime>80&&(this.progressFrame=(this.progressFrame+1)%a.length,this.progressTime=e);const t=a[this.progressFrame];for(const e of this.progress.values()){const r=this.progressStyle.chars[0].repeat(e.lastScaledSize),A=this.progressStyle.chars[1].repeat(this.progressMaxScaledSize-e.lastScaledSize);this.stdout.write(`${s.pretty(this.configuration,"➤","blueBright")} ${this.formatName(null)}: ${t} ${r}${A}\n`)}this.progressTimeout=setTimeout(()=>{this.refreshProgress()},80)}refreshProgress(e=0){let t=!1;if(0===this.progress.size)t=!0;else for(const e of this.progress.values()){const r=Math.trunc(this.progressMaxScaledSize*e.definition.progress),A=e.lastScaledSize;if(e.lastScaledSize=r,r!==A){t=!0;break}}t&&(this.clearProgress({delta:e}),this.writeProgress())}truncate(e,{truncate:t}={}){return this.configuration.get("enableProgressBars")||(t=!1),void 0===t&&(t=this.configuration.get("preferTruncatedLines")),t&&(e=n()(e,0,process.stdout.columns-1)),e}formatName(e){return d(e,{configuration:this.configuration,json:this.json})}formatNameWithHyperlink(e){return C(e,{configuration:this.configuration,json:this.json})}formatIndent(){return"│ ".repeat(this.indent)}}},81832:(e,t,r)=>{"use strict";r.d(t,{E:()=>a});var A,n=r(43896),o=r(46009),i=r(79669),s=r(73632);!function(e){e.VERSION="version",e.COMMAND_NAME="commandName",e.PLUGIN_NAME="pluginName",e.INSTALL_COUNT="installCount",e.PROJECT_COUNT="projectCount",e.WORKSPACE_COUNT="workspaceCount",e.DEPENDENCY_COUNT="dependencyCount",e.EXTENSION="packageExtension"}(A||(A={}));class a{constructor(e,t){this.values=new Map,this.hits=new Map,this.enumerators=new Map,this.configuration=e;const r=this.getRegistryPath();this.isNew=!n.xfs.existsSync(r),this.sendReport(t),this.startBuffer()}reportVersion(e){this.reportValue(A.VERSION,e)}reportCommandName(e){this.reportValue(A.COMMAND_NAME,e||"")}reportPluginName(e){this.reportValue(A.PLUGIN_NAME,e)}reportProject(e){this.reportEnumerator(A.PROJECT_COUNT,e)}reportInstall(e){this.reportHit(A.INSTALL_COUNT,e)}reportPackageExtension(e){this.reportValue(A.EXTENSION,e)}reportWorkspaceCount(e){this.reportValue(A.WORKSPACE_COUNT,String(e))}reportDependencyCount(e){this.reportValue(A.DEPENDENCY_COUNT,String(e))}reportValue(e,t){s.getSetWithDefault(this.values,e).add(t)}reportEnumerator(e,t){s.getSetWithDefault(this.enumerators,e).add(t)}reportHit(e,t="*"){const r=s.getMapWithDefault(this.hits,e),A=s.getFactoryWithDefault(r,t,()=>0);r.set(t,A+1)}getRegistryPath(){const e=this.configuration.get("globalFolder");return o.y1.join(e,"telemetry.json")}sendReport(e){var t,r,A;const s=this.getRegistryPath();let a;try{a=n.xfs.readJsonSync(s)}catch(e){a={}}const c=Date.now(),g=24*this.configuration.get("telemetryInterval")*60*60*1e3,l=(null!==(t=a.lastUpdate)&&void 0!==t?t:c+g+Math.floor(g*Math.random()))+g;if(!(l>c&&null!=a.lastUpdate)){try{n.xfs.mkdirSync(o.y1.dirname(s),{recursive:!0}),n.xfs.writeJsonSync(s,{lastUpdate:c})}catch(e){return}if(!(l>c)&&a.blocks)for(const[t,n]of Object.entries(null!==(r=a.blocks)&&void 0!==r?r:{})){if(0===Object.keys(n).length)continue;const r=n;r.userId=t;for(const e of Object.keys(null!==(A=r.enumerators)&&void 0!==A?A:{}))r.enumerators[e]=r.enumerators[e].length;const o=`https://browser-http-intake.logs.datadoghq.eu/v1/input/${e}?ddsource=yarn`;i.post(o,r,{configuration:this.configuration}).catch(()=>{})}}}applyChanges(){var e,t,r,A,i,s,a,c,g;const l=this.getRegistryPath();let u;try{u=n.xfs.readJsonSync(l)}catch(e){u={}}const h=null!==(e=this.configuration.get("telemetryUserId"))&&void 0!==e?e:"*",p=u.blocks=null!==(t=u.blocks)&&void 0!==t?t:{},d=p[h]=null!==(r=p[h])&&void 0!==r?r:{};for(const e of this.hits.keys()){const t=d.hits=null!==(A=d.hits)&&void 0!==A?A:{},r=t[e]=null!==(i=t[e])&&void 0!==i?i:{};for(const[t,A]of this.hits.get(e))r[t]=(null!==(s=r[t])&&void 0!==s?s:0)+A}for(const e of["values","enumerators"])for(const t of this[e].keys()){const r=d[e]=null!==(a=d[e])&&void 0!==a?a:{};r[t]=[...new Set([...null!==(c=r[t])&&void 0!==c?c:[],...null!==(g=this[e].get(t))&&void 0!==g?g:[]])]}n.xfs.mkdirSync(o.y1.dirname(l),{recursive:!0}),n.xfs.writeJsonSync(l,u)}startBuffer(){process.on("exit",()=>{try{this.applyChanges()}catch(e){}})}}},33720:(e,t,r)=>{"use strict";r.d(t,{$:()=>n});var A=r(35691);class n extends A.yG{reportCacheHit(e){}reportCacheMiss(e){}startTimerSync(e,t,r){return("function"==typeof t?t:r)()}async startTimerPromise(e,t,r){const A="function"==typeof t?t:r;return await A()}async startCacheReport(e){return await e()}reportSeparator(){}reportInfo(e,t){}reportWarning(e,t){}reportError(e,t){}reportProgress(e){return{...Promise.resolve().then(async()=>{for await(const{}of e);}),stop:()=>{}}}reportJson(e){}async finalize(){}}},60895:(e,t,r)=>{"use strict";r.d(t,{N:()=>s});var A=r(17674),n=r(14626),o=r(46009),i=r(54143);class s{supports(e){return!!e.reference.startsWith("virtual:")}getLocalPath(e,t){const r=e.reference.indexOf("#");if(-1===r)throw new Error("Invalid virtual package reference");const A=e.reference.slice(r+1),n=i.makeLocator(e,A);return t.fetcher.getLocalPath(n,t)}async fetch(e,t){const r=e.reference.indexOf("#");if(-1===r)throw new Error("Invalid virtual package reference");const A=e.reference.slice(r+1),n=i.makeLocator(e,A),o=await t.fetcher.fetch(n,t);return await this.ensureVirtualLink(e,o,t)}getLocatorFilename(e){return i.slugifyLocator(e)}async ensureVirtualLink(e,t,r){const i=t.packageFs.getRealPath(),s=r.project.configuration.get("virtualFolder"),a=this.getLocatorFilename(e),c=A.p.makeVirtualPath(s,a,i),g=new n.K(c,{baseFs:t.packageFs,pathUtils:o.y1});return{...t,packageFs:g}}}},17722:(e,t,r)=>{"use strict";r.d(t,{j:()=>h});var A=r(43896),n=r(46009),o=r(58592),i=r.n(o),s=r(53887),a=r.n(s),c=r(46611),g=r(94538),l=r(20624),u=r(54143);class h{constructor(e,{project:t}){this.workspacesCwds=new Set,this.dependencies=new Map,this.project=t,this.cwd=e}async setup(){this.manifest=A.xfs.existsSync(n.y1.join(this.cwd,c.G.fileName))?await c.G.find(this.cwd):new c.G,this.relativeCwd=n.y1.relative(this.project.cwd,this.cwd)||n.LZ.dot;const e=this.manifest.name?this.manifest.name:u.makeIdent(null,`${this.computeCandidateName()}-${l.makeHash(this.relativeCwd).substr(0,6)}`),t=this.manifest.version?this.manifest.version:"0.0.0";this.locator=u.makeLocator(e,t),this.anchoredDescriptor=u.makeDescriptor(this.locator,`${g.d.protocol}${this.relativeCwd}`),this.anchoredLocator=u.makeLocator(this.locator,`${g.d.protocol}${this.relativeCwd}`);const r=this.manifest.workspaceDefinitions.map(({pattern:e})=>e),o=await i()(r,{absolute:!0,cwd:n.cS.fromPortablePath(this.cwd),expandDirectories:!1,onlyDirectories:!0,onlyFiles:!1,ignore:["**/node_modules","**/.git","**/.yarn"]});o.sort();for(const e of o){const t=n.y1.resolve(this.cwd,n.cS.toPortablePath(e));A.xfs.existsSync(n.y1.join(t,"package.json"))&&this.workspacesCwds.add(t)}}accepts(e){const t=e.indexOf(":"),r=-1!==t?e.slice(0,t+1):null,A=-1!==t?e.slice(t+1):e;return r===g.d.protocol&&n.y1.normalize(A)===this.relativeCwd||(r===g.d.protocol&&"*"===A||!!a().validRange(A)&&(r===g.d.protocol?a().satisfies(null!==this.manifest.version?this.manifest.version:"0.0.0",A):!!this.project.configuration.get("enableTransparentWorkspaces")&&(null!==this.manifest.version&&a().satisfies(this.manifest.version,A))))}computeCandidateName(){return this.cwd===this.project.cwd?"root-workspace":""+n.y1.basename(this.cwd)||"unnamed-workspace"}async persistManifest(){const e={};this.manifest.exportTo(e);const t=n.y1.join(this.cwd,c.G.fileName),r=JSON.stringify(e,null,this.manifest.indent)+"\n";await A.xfs.changeFilePromise(t,r,{automaticNewlines:!0})}}},94538:(e,t,r)=>{"use strict";r.d(t,{d:()=>n});var A=r(32485);class n{supportsDescriptor(e,t){if(e.range.startsWith(n.protocol))return!0;return null!==t.project.tryWorkspaceByDescriptor(e)}supportsLocator(e,t){return!!e.reference.startsWith(n.protocol)}shouldPersistResolution(e,t){return!1}bindDescriptor(e,t,r){return e}getResolutionDependencies(e,t){return[]}async getCandidates(e,t,r){return[r.project.getWorkspaceByDescriptor(e).anchoredLocator]}async getSatisfying(e,t,r){return null}async resolve(e,t){const r=t.project.getWorkspaceByCwd(e.reference.slice(n.protocol.length));return{...e,version:r.manifest.version||"0.0.0",languageName:"unknown",linkType:A.Un.SOFT,dependencies:new Map([...r.manifest.dependencies,...r.manifest.devDependencies]),peerDependencies:new Map([...r.manifest.peerDependencies]),dependenciesMeta:r.manifest.dependenciesMeta,peerDependenciesMeta:r.manifest.peerDependenciesMeta,bin:r.manifest.bin}}}n.protocol="workspace:"},59355:(e,t,r)=>{"use strict";r.d(t,{o:()=>A});const A="2.4.1"},6220:(e,t,r)=>{"use strict";r.r(t),r.d(t,{EndStrategy:()=>A,pipevp:()=>g,execvp:()=>l});var A,n=r(46009),o=r(67566),i=r.n(o);function s(e){return null!==e&&"number"==typeof e.fd}function a(){}!function(e){e[e.Never=0]="Never",e[e.ErrorCode=1]="ErrorCode",e[e.Always=2]="Always"}(A||(A={}));let c=0;async function g(e,t,{cwd:r,env:o=process.env,strict:g=!1,stdin:l=null,stdout:u,stderr:p,end:d=A.Always}){const C=["pipe","pipe","pipe"];null===l?C[0]="ignore":s(l)&&(C[0]=l),s(u)&&(C[1]=u),s(p)&&(C[2]=p),0==c++&&process.on("SIGINT",a);const f=i()(e,t,{cwd:n.cS.fromPortablePath(r),env:{...o,PWD:n.cS.fromPortablePath(r)},stdio:C});s(l)||null===l||l.pipe(f.stdin),s(u)||f.stdout.pipe(u,{end:!1}),s(p)||f.stderr.pipe(p,{end:!1});const I=()=>{for(const e of new Set([u,p]))s(e)||e.end()};return new Promise((t,r)=>{f.on("error",e=>{0==--c&&process.off("SIGINT",a),d!==A.Always&&d!==A.ErrorCode||I(),r(e)}),f.on("close",(n,o)=>{0==--c&&process.off("SIGINT",a),(d===A.Always||d===A.ErrorCode&&n>0)&&I(),0!==n&&g?r(null!==n?new Error(`Child "${e}" exited with exit code ${n}`):new Error(`Child "${e}" exited with signal ${o}`)):t({code:h(n,o)})})})}async function l(e,t,{cwd:r,env:A=process.env,encoding:o="utf8",strict:s=!1}){const a=["ignore","pipe","pipe"],c=[],g=[],l=n.cS.fromPortablePath(r);void 0!==A.PWD&&(A={...A,PWD:l});const u=i()(e,t,{cwd:l,env:A,stdio:a});return u.stdout.on("data",e=>{c.push(e)}),u.stderr.on("data",e=>{g.push(e)}),await new Promise((t,r)=>{u.on("error",r),u.on("close",(A,n)=>{const i="buffer"===o?Buffer.concat(c):Buffer.concat(c).toString(o),a="buffer"===o?Buffer.concat(g):Buffer.concat(g).toString(o);0!==A&&s?r(Object.assign(new Error(`Child "${e}" exited with exit code ${A}\n\n${a}`),{code:h(A,n),stdout:i,stderr:a})):t({code:h(A,n),stdout:i,stderr:a})})})}const u=new Map([["SIGINT",2],["SIGQUIT",3],["SIGKILL",9],["SIGTERM",15]]);function h(e,t){const r=u.get(t);return void 0!==r?128+r:null!=e?e:1}},81111:(e,t,r)=>{"use strict";r.r(t),r.d(t,{getDefaultGlobalFolder:()=>o,getHomeFolder:()=>i,isFolderInside:()=>s});var A=r(46009),n=r(12087);function o(){if("win32"===process.platform){const e=A.cS.toPortablePath(process.env.LOCALAPPDATA||A.cS.join((0,n.homedir)(),"AppData","Local"));return A.y1.resolve(e,"Yarn/Berry")}if(process.env.XDG_DATA_HOME){const e=A.cS.toPortablePath(process.env.XDG_DATA_HOME);return A.y1.resolve(e,"yarn/berry")}return A.y1.resolve(i(),".yarn/berry")}function i(){return A.cS.toPortablePath((0,n.homedir)()||"/usr/local/share")}function s(e,t){const r=A.y1.relative(t,e);return r&&!r.startsWith("..")&&!A.y1.isAbsolute(r)}},71643:(e,t,r)=>{"use strict";r.r(t),r.d(t,{Type:()=>A,Style:()=>n,supportsColor:()=>h,supportsHyperlinks:()=>p,tuple:()=>I,applyStyle:()=>E,applyColor:()=>B,pretty:()=>y,prettyList:()=>m,json:()=>w,mark:()=>Q,LogLevel:()=>D,addLogFilterSupport:()=>b});var A,n,o=r(46009),i=r(95882),s=r.n(i),a=r(92659),c=r(73632),g=r(54143),l=r(32485);!function(e){e.NO_HINT="NO_HINT",e.NULL="NULL",e.SCOPE="SCOPE",e.NAME="NAME",e.RANGE="RANGE",e.REFERENCE="REFERENCE",e.NUMBER="NUMBER",e.PATH="PATH",e.URL="URL",e.ADDED="ADDED",e.REMOVED="REMOVED",e.CODE="CODE",e.DURATION="DURATION",e.SIZE="SIZE",e.IDENT="IDENT",e.DESCRIPTOR="DESCRIPTOR",e.LOCATOR="LOCATOR",e.RESOLUTION="RESOLUTION",e.DEPENDENT="DEPENDENT",e.PACKAGE_EXTENSION="PACKAGE_EXTENSION"}(A||(A={})),function(e){e[e.BOLD=2]="BOLD"}(n||(n={}));const u=process.env.GITHUB_ACTIONS?{level:2}:s().supportsColor?{level:s().supportsColor.level}:{level:0},h=0!==u.level,p=h&&!process.env.GITHUB_ACTIONS,d=new(s().Instance)(u),C=new Map([[A.NO_HINT,null],[A.NULL,["#a853b5",129]],[A.SCOPE,["#d75f00",166]],[A.NAME,["#d7875f",173]],[A.RANGE,["#00afaf",37]],[A.REFERENCE,["#87afff",111]],[A.NUMBER,["#ffd700",220]],[A.PATH,["#d75fd7",170]],[A.URL,["#d75fd7",170]],[A.ADDED,["#5faf00",70]],[A.REMOVED,["#d70000",160]],[A.CODE,["#87afff",111]],[A.SIZE,["#ffd700",220]]]),f={[A.NUMBER]:{pretty:(e,t)=>""+t,json:e=>e},[A.IDENT]:{pretty:(e,t)=>g.prettyIdent(e,t),json:e=>g.stringifyIdent(e)},[A.LOCATOR]:{pretty:(e,t)=>g.prettyLocator(e,t),json:e=>g.stringifyLocator(e)},[A.DESCRIPTOR]:{pretty:(e,t)=>g.prettyDescriptor(e,t),json:e=>g.stringifyDescriptor(e)},[A.RESOLUTION]:{pretty:(e,{descriptor:t,locator:r})=>g.prettyResolution(e,t,r),json:({descriptor:e,locator:t})=>({descriptor:g.stringifyDescriptor(e),locator:null!==t?g.stringifyLocator(t):null})},[A.DEPENDENT]:{pretty:(e,{locator:t,descriptor:r})=>g.prettyDependent(e,t,r),json:({locator:e,descriptor:t})=>({locator:g.stringifyLocator(e),descriptor:g.stringifyDescriptor(t)})},[A.PACKAGE_EXTENSION]:{pretty:(e,t)=>{switch(t.type){case l.HN.Dependency:return`${g.prettyIdent(e,t.parentDescriptor)} ➤ ${B(e,"dependencies",A.CODE)} ➤ ${g.prettyIdent(e,t.descriptor)}`;case l.HN.PeerDependency:return`${g.prettyIdent(e,t.parentDescriptor)} ➤ ${B(e,"peerDependencies",A.CODE)} ➤ ${g.prettyIdent(e,t.descriptor)}`;case l.HN.PeerDependencyMeta:return`${g.prettyIdent(e,t.parentDescriptor)} ➤ ${B(e,"peerDependenciesMeta",A.CODE)} ➤ ${g.prettyIdent(e,g.parseIdent(t.selector))} ➤ ${B(e,t.key,A.CODE)}`;default:throw new Error("Assertion failed: Unsupported package extension type: "+t.type)}},json:e=>{switch(e.type){case l.HN.Dependency:return`${g.stringifyIdent(e.parentDescriptor)} > ${g.stringifyIdent(e.descriptor)}`;case l.HN.PeerDependency:return`${g.stringifyIdent(e.parentDescriptor)} >> ${g.stringifyIdent(e.descriptor)}`;case l.HN.PeerDependencyMeta:return`${g.stringifyIdent(e.parentDescriptor)} >> ${e.selector} / ${e.key}`;default:throw new Error("Assertion failed: Unsupported package extension type: "+e.type)}}},[A.DURATION]:{pretty:(e,t)=>{if(t>6e4){const e=Math.floor(t/1e3/60),r=Math.ceil((t-60*e*1e3)/1e3);return 0===r?e+"m":`${e}m ${r}s`}{const e=Math.floor(t/1e3),r=t-1e3*e;return 0===r?e+"s":`${e}s ${r}ms`}},json:e=>e},[A.SIZE]:{pretty:(e,t)=>{const r=["KB","MB","GB","TB"];let n=r.length;for(;n>1&&t<1024**n;)n-=1;const o=1024**n;return B(e,`${Math.floor(100*t/o)/100} ${r[n-1]}`,A.NUMBER)},json:e=>e},[A.PATH]:{pretty:(e,t)=>B(e,o.cS.fromPortablePath(t),A.PATH),json:e=>o.cS.fromPortablePath(e)}};function I(e,t){return[t,e]}function E(e,t,r){return e.get("enableColors")?(r&n.BOLD&&(t=s().bold(t)),t):t}function B(e,t,r){if(!e.get("enableColors"))return t;const A=C.get(r);if(null===A)return t;const n=void 0===A?r:u.level>=3?A[0]:A[1],o="number"==typeof n?d.ansi256(n):n.startsWith("#")?d.hex(n):d[n];if("function"!=typeof o)throw new Error("Invalid format type "+n);return o(t)}function y(e,t,r){if(null===t)return B(e,"null",A.NULL);if(Object.prototype.hasOwnProperty.call(f,r)){return f[r].pretty(e,t)}if("string"!=typeof t)throw new Error("Assertion failed: Expected the value to be a string, got "+typeof t);return B(e,t,r)}function m(e,t,r,{separator:A=", "}={}){return[...t].map(t=>y(e,t,r)).join(A)}function w(e,t){if(null===e)return null;if(Object.prototype.hasOwnProperty.call(f,t))return c.overrideType(t),f[t].json(e);if("string"!=typeof e)throw new Error("Assertion failed: Expected the value to be a string, got "+typeof e);return e}function Q(e){return{Check:B(e,"✓","green"),Cross:B(e,"✘","red"),Question:B(e,"?","cyan")}}var D;function b(e,{configuration:t}){const r=t.get("logFilters"),A=new Map,n=new Map;for(const e of r){const t=e.get("level");if(void 0===t)continue;const r=e.get("code");void 0!==r&&A.set(r,t);const o=e.get("text");void 0!==o&&n.set(o,t)}const o=e.reportInfo,i=e.reportWarning,c=e.reportError,g=function(e,t,r,g){switch(((e,t,r)=>{if(null===e||e===a.b.UNNAMED)return r;if(n.size>0){const e=n.get(s().reset(t));if(void 0!==e)return null!=e?e:r}if(A.size>0){const t=A.get((0,a.i)(e));if(void 0!==t)return null!=t?t:r}return r})(t,r,g)){case D.Info:o.call(e,t,r);break;case D.Warning:i.call(e,null!=t?t:a.b.UNNAMED,r);break;case D.Error:c.call(e,null!=t?t:a.b.UNNAMED,r)}};e.reportInfo=function(...e){return g(this,...e,D.Info)},e.reportWarning=function(...e){return g(this,...e,D.Warning)},e.reportError=function(...e){return g(this,...e,D.Error)}}!function(e){e.Error="error",e.Warning="warning",e.Info="info",e.Discard="discard"}(D||(D={}))},20624:(e,t,r)=>{"use strict";r.r(t),r.d(t,{makeHash:()=>a,checksumFile:()=>c,checksumPattern:()=>g});var A=r(43896),n=r(46009),o=r(76417),i=r(58592),s=r.n(i);function a(...e){const t=(0,o.createHash)("sha512");for(const r of e)t.update(r||"");return t.digest("hex")}function c(e){return new Promise((t,r)=>{const n=(0,o.createHash)("sha512"),i=A.xfs.createReadStream(e);i.on("data",e=>{n.update(e)}),i.on("error",e=>{r(e)}),i.on("end",()=>{t(n.digest("hex"))})})}async function g(e,{cwd:t}){const r=(await s()(e,{cwd:n.cS.fromPortablePath(t),expandDirectories:!1,onlyDirectories:!0,unique:!0})).map(e=>e+"/**/*"),i=await s()([e,...r],{cwd:n.cS.fromPortablePath(t),expandDirectories:!1,onlyFiles:!1,unique:!0});i.sort();const a=await Promise.all(i.map(async e=>{const t=[Buffer.from(e)],r=n.cS.toPortablePath(e),o=await A.xfs.lstatPromise(r);return o.isSymbolicLink()?t.push(Buffer.from(await A.xfs.readlinkPromise(r))):o.isFile()&&t.push(await A.xfs.readFilePromise(r)),t.join("\0")})),c=(0,o.createHash)("sha512");for(const e of a)c.update(e);return c.digest("hex")}},79669:(e,t,r)=>{"use strict";r.r(t),r.d(t,{getNetworkSettings:()=>d,Method:()=>C,request:()=>f,get:()=>I,put:()=>E,post:()=>B,del:()=>y});var A=r(43896),n=r(57211),o=r(98605),i=r(2401),s=r.n(i),a=r(98161),c=r(78835);const g=new Map,l=new Map,u=new o.Agent({keepAlive:!0}),h=new n.Agent({keepAlive:!0});function p(e){const t=new c.URL(e),r={host:t.hostname,headers:{}};return t.port&&(r.port=Number(t.port)),{proxy:r}}function d(e,t){const r=[...t.configuration.get("networkSettings")].sort(([e],[t])=>t.length-e.length),A={enableNetwork:void 0,caFilePath:void 0,httpProxy:void 0,httpsProxy:void 0},n=Object.keys(A),o=new c.URL(e);for(const[e,t]of r)if(s().isMatch(o.hostname,e))for(const e of n){const r=t.get(e);null!==r&&void 0===A[e]&&(A[e]=r)}for(const e of n)void 0===A[e]&&(A[e]=t.configuration.get(e));return A}var C;async function f(e,t,{configuration:n,headers:o,json:i,jsonRequest:g=i,jsonResponse:f=i,method:I=C.GET}){const E=d(e,{configuration:n});if(!1===E.enableNetwork)throw new Error(`Request to '${e}' has been blocked because of your configuration settings`);const B=new c.URL(e);if("http:"===B.protocol&&!s().isMatch(B.hostname,n.get("unsafeHttpWhitelist")))throw new Error(`Unsafe http requests must be explicitly whitelisted in your configuration (${B.hostname})`);const y={agent:{http:E.httpProxy?a.httpOverHttp(p(E.httpProxy)):u,https:E.httpsProxy?a.httpsOverHttp(p(E.httpsProxy)):h},headers:o,method:I};y.responseType=f?"json":"buffer",null!==t&&(Buffer.isBuffer(t)||!g&&"string"==typeof t?y.body=t:y.json=t);const m=n.get("httpTimeout"),w=n.get("httpRetry"),Q=n.get("enableStrictSsl"),D=E.caFilePath,{default:b}=await Promise.resolve().then(r.t.bind(r,48722,7)),v=D?await async function(e){let t=l.get(e);return t||(t=A.xfs.readFilePromise(e).then(t=>(l.set(e,t),t)),l.set(e,t)),t}(D):void 0,S=b.extend({timeout:{socket:m},retry:w,https:{rejectUnauthorized:Q,certificateAuthority:v},...y});return n.getLimit("networkConcurrency")(()=>S(e))}async function I(e,{configuration:t,json:r,jsonResponse:A=r,...n}){let o=g.get(e);return o||(o=f(e,null,{configuration:t,...n}).then(t=>(g.set(e,t.body),t.body)),g.set(e,o)),!1===Buffer.isBuffer(o)&&(o=await o),A?JSON.parse(o.toString()):o}async function E(e,t,r){return(await f(e,t,{...r,method:C.PUT})).body}async function B(e,t,r){return(await f(e,t,{...r,method:C.POST})).body}async function y(e,t){return(await f(e,null,{...t,method:C.DELETE})).body}!function(e){e.GET="GET",e.PUT="PUT",e.POST="POST",e.DELETE="DELETE"}(C||(C={}))},53836:(e,t,r)=>{"use strict";r.r(t),r.d(t,{Cache:()=>p.C,DEFAULT_RC_FILENAME:()=>d.tr,DEFAULT_LOCK_FILENAME:()=>d.nh,Configuration:()=>d.VK,FormatType:()=>d.a5,ProjectLookup:()=>d.EW,SettingsType:()=>d.a2,BuildType:()=>C.k,LightReport:()=>f.h,Manifest:()=>I.G,MessageName:()=>E.b,Project:()=>B.I,TAG_REGEXP:()=>y.c,ReportError:()=>m.lk,Report:()=>m.yG,StreamReport:()=>w.Pk,TelemetryManager:()=>Q.E,ThrowReport:()=>D.$,VirtualFetcher:()=>b.N,WorkspaceResolver:()=>v.d,Workspace:()=>S.j,YarnVersion:()=>k.o,LinkType:()=>N.Un,PackageExtensionType:()=>N.HN,PackageExtensionStatus:()=>N._u,hashUtils:()=>i,httpUtils:()=>s,execUtils:()=>A,folderUtils:()=>n,formatUtils:()=>o,miscUtils:()=>a,scriptUtils:()=>c,semverUtils:()=>g,structUtils:()=>l,tgzUtils:()=>u,treeUtils:()=>h});var A=r(6220),n=r(81111),o=r(71643),i=r(20624),s=r(79669),a=r(73632),c=r(63088),g=r(36545),l=r(54143),u=r(72785),h=r(85875),p=r(28148),d=r(39922),C=r(92409),f=r(62152),I=r(46611),E=r(92659),B=r(85824),y=r(52779),m=r(35691),w=r(15815),Q=r(81832),D=r(33720),b=r(60895),v=r(94538),S=r(17722),k=r(59355),N=r(32485)},73632:(e,t,r)=>{"use strict";r.r(t),r.d(t,{escapeRegExp:()=>a,overrideType:()=>c,assertNever:()=>g,validateEnum:()=>l,mapAndFilter:()=>u,mapAndFind:()=>p,isIndexableObject:()=>C,convertMapsToIndexableObjects:()=>f,getFactoryWithDefault:()=>I,getArrayWithDefault:()=>E,getSetWithDefault:()=>B,getMapWithDefault:()=>y,releaseAfterUseAsync:()=>m,prettifyAsyncErrors:()=>w,prettifySyncErrors:()=>Q,bufferStream:()=>D,BufferStream:()=>b,DefaultStream:()=>v,dynamicRequire:()=>S,dynamicRequireNoCache:()=>k,sortMap:()=>N,buildIgnorePattern:()=>F,replaceEnvVariables:()=>K,parseBoolean:()=>M,parseOptionalBoolean:()=>R,tryParseOptionalBoolean:()=>x});var A=r(46009),n=r(40822),o=r(2401),i=r.n(o),s=r(92413);function a(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function c(e){}function g(e){throw new Error(`Assertion failed: Unexpected object '${e}'`)}function l(e,t){if(!Object.values(e).includes(t))throw new Error("Assertion failed: Invalid value for enumeration");return t}function u(e,t){const r=[];for(const A of e){const e=t(A);e!==h&&r.push(e)}return r}e=r.hmd(e);const h=Symbol();function p(e,t){for(const r of e){const e=t(r);if(e!==d)return e}}u.skip=h;const d=Symbol();function C(e){return"object"==typeof e&&null!==e}function f(e){if(e instanceof Map&&(e=Object.fromEntries(e)),C(e))for(const t of Object.keys(e)){const r=e[t];C(r)&&(e[t]=f(r))}return e}function I(e,t,r){let A=e.get(t);return void 0===A&&e.set(t,A=r()),A}function E(e,t){let r=e.get(t);return void 0===r&&e.set(t,r=[]),r}function B(e,t){let r=e.get(t);return void 0===r&&e.set(t,r=new Set),r}function y(e,t){let r=e.get(t);return void 0===r&&e.set(t,r=new Map),r}async function m(e,t){if(null==t)return await e();try{return await e()}finally{await t()}}async function w(e,t){try{return await e()}catch(e){throw e.message=t(e.message),e}}function Q(e,t){try{return e()}catch(e){throw e.message=t(e.message),e}}async function D(e){return await new Promise((t,r)=>{const A=[];e.on("error",e=>{r(e)}),e.on("data",e=>{A.push(e)}),e.on("end",()=>{t(Buffer.concat(A))})})}p.skip=d;class b extends s.Transform{constructor(){super(...arguments),this.chunks=[]}_transform(e,t,r){if("buffer"!==t||!Buffer.isBuffer(e))throw new Error("Assertion failed: BufferStream only accept buffers");this.chunks.push(e),r(null,null)}_flush(e){e(null,Buffer.concat(this.chunks))}}class v extends s.Transform{constructor(e=Buffer.alloc(0)){super(),this.active=!0,this.ifEmpty=e}_transform(e,t,r){if("buffer"!==t||!Buffer.isBuffer(e))throw new Error("Assertion failed: DefaultStream only accept buffers");this.active=!1,r(null,e)}_flush(e){this.active&&this.ifEmpty.length>0&&e(null,this.ifEmpty)}}function S(e){return"undefined"!=typeof require?require(e):r(32178)(e)}function k(t){const n=A.cS.fromPortablePath(t),o=r.c[n];let i;delete r.c[n];try{i=S(n);const t=r.c[n],A=e.children.indexOf(t);-1!==A&&e.children.splice(A,1)}finally{r.c[n]=o}return i}function N(e,t){const r=Array.from(e);Array.isArray(t)||(t=[t]);const A=[];for(const e of t)A.push(r.map(t=>e(t)));const n=r.map((e,t)=>t);return n.sort((e,t)=>{for(const r of A){const A=r[e]r[t]?1:0;if(0!==A)return A}return 0}),n.map(e=>r[e])}function F(e){return 0===e.length?null:e.map(e=>`(${i().makeRe(e,{windows:!1}).source})`).join("|")}function K(e,{env:t}){return e.replace(/\${(?[\d\w_]+)(?:)?(?:-(?[^}]*))?}/g,(...e)=>{const{variableName:r,colon:A,fallback:o}=e[e.length-1],i=Object.prototype.hasOwnProperty.call(t,r),s=t[r];if(s)return s;if(i&&!A)return s;if(null!=o)return o;throw new n.UsageError(`Environment variable not found (${r})`)})}function M(e){switch(e){case"true":case"1":case 1:case!0:return!0;case"false":case"0":case 0:case!1:return!1;default:throw new Error(`Couldn't parse "${e}" as a boolean`)}}function R(e){return void 0===e?e:M(e)}function x(e){try{return R(e)}catch(e){return null}}},63088:(e,t,r)=>{"use strict";r.r(t),r.d(t,{makeScriptEnv:()=>b,prepareExternalProject:()=>S,hasPackageScript:()=>k,executePackageScript:()=>N,executePackageShellcode:()=>F,executeWorkspaceScript:()=>M,hasWorkspaceScript:()=>R,executeWorkspaceLifecycleScript:()=>x,maybeExecuteWorkspaceLifecycleScript:()=>L,getPackageAccessibleBinaries:()=>P,getWorkspaceAccessibleBinaries:()=>O,executePackageAccessibleBinary:()=>U,executeWorkspaceAccessibleBinary:()=>T});var A,n=r(46009),o=r(53660),i=r(75448),s=r(43896),a=r(65281),c=r(76756),g=r(50730),l=r(61814),u=r.n(l),h=r(61578),p=r.n(h),d=r(92413),C=r(46611),f=r(92659),I=r(35691),E=r(15815),B=r(59355),y=r(6220),m=r(71643),w=r(73632),Q=r(54143);async function D(e,t,r,A=[]){"win32"===process.platform&&await Promise.all([s.xfs.writeFilePromise(n.y1.format({dir:e,name:t,ext:".exe"}),(0,g.O9)()),s.xfs.writeFilePromise(n.y1.format({dir:e,name:t,ext:".exe.info"}),[r,...A].join("\n")),s.xfs.writeFilePromise(n.y1.format({dir:e,name:t,ext:".cmd"}),`@"${r}" ${A.map(e=>`"${e.replace('"','""')}"`).join(" ")} %*\n`)]),await s.xfs.writeFilePromise(n.y1.join(e,t),`#!/bin/sh\nexec "${r}" ${A.map(e=>`'${e.replace(/'/g,"'\"'\"'")}'`).join(" ")} "$@"\n`),await s.xfs.chmodPromise(n.y1.join(e,t),493)}async function b({project:e,binFolder:t,lifecycleScript:r}){const A={};for(const[e,t]of Object.entries(process.env))void 0!==t&&(A["path"!==e.toLowerCase()?e:"PATH"]=t);const o=n.cS.fromPortablePath(t);A.BERRY_BIN_FOLDER=n.cS.fromPortablePath(o),await D(t,"node",process.execPath),null!==B.o&&(await D(t,"run",process.execPath,[process.argv[1],"run"]),await D(t,"yarn",process.execPath,[process.argv[1]]),await D(t,"yarnpkg",process.execPath,[process.argv[1]]),await D(t,"node-gyp",process.execPath,[process.argv[1],"run","--top-level","node-gyp"])),e&&(A.INIT_CWD=n.cS.fromPortablePath(e.configuration.startingCwd)),A.PATH=A.PATH?`${o}${n.cS.delimiter}${A.PATH}`:""+o,A.npm_execpath=`${o}${n.cS.sep}yarn`,A.npm_node_execpath=`${o}${n.cS.sep}node`;const i=null!==B.o?"yarn/"+B.o:`yarn/${w.dynamicRequire("@yarnpkg/core").version}-core`;return A.npm_config_user_agent=`${i} npm/? node/${process.versions.node} ${process.platform} ${process.arch}`,r&&(A.npm_lifecycle_event=r),e&&await e.configuration.triggerHook(e=>e.setupScriptEnvironment,e,A,async(e,r,A)=>await D(t,(0,n.Zu)(e),r,A)),A}!function(e){e.Yarn1="Yarn Classic",e.Yarn2="Yarn",e.Npm="npm",e.Pnpm="pnpm"}(A||(A={}));const v=p()(2);async function S(e,t,{configuration:r,report:o,workspace:i=null}){await v(async()=>{await s.xfs.mktempPromise(async a=>{const c=n.y1.join(a,"pack.log"),{stdout:g,stderr:l}=r.getSubprocessStreams(c,{prefix:e,report:o}),u=await async function(e){let t=null;try{t=await s.xfs.readFilePromise(n.y1.join(e,n.QS.lockfile),"utf8")}catch(e){}return null!==t?t.match(/^__metadata:$/m)?A.Yarn2:A.Yarn1:s.xfs.existsSync(n.y1.join(e,"package-lock.json"))?A.Npm:s.xfs.existsSync(n.y1.join(e,"pnpm-lock.yaml"))?A.Pnpm:null}(e);let h;null!==u?(g.write(`Installing the project using ${u}\n\n`),h=u):(g.write("No package manager detected; defaulting to Yarn\n\n"),h=A.Yarn2),await s.xfs.mktempPromise(async r=>{const o=await b({binFolder:r}),u=new Map([[A.Yarn1,async()=>{const r=null!==i?["workspace",i]:[],A=await y.pipevp("yarn",["set","version","classic","--only-if-needed"],{cwd:e,env:o,stdin:null,stdout:g,stderr:l,end:y.EndStrategy.ErrorCode});if(0!==A.code)return A.code;await s.xfs.appendFilePromise(n.y1.join(e,".npmignore"),"/.yarn\n"),g.write("\n");const a=await y.pipevp("yarn",["install"],{cwd:e,env:o,stdin:null,stdout:g,stderr:l,end:y.EndStrategy.ErrorCode});if(0!==a.code)return a.code;g.write("\n");const c=await y.pipevp("yarn",[...r,"pack","--filename",n.cS.fromPortablePath(t)],{cwd:e,env:o,stdin:null,stdout:g,stderr:l});return 0!==c.code?c.code:0}],[A.Yarn2,async()=>{const r=null!==i?["workspace",i]:[];o.YARN_ENABLE_INLINE_BUILDS="1";const A=n.y1.join(e,n.QS.lockfile);await s.xfs.existsPromise(A)||await s.xfs.writeFilePromise(A,"");const a=await y.pipevp("yarn",[...r,"pack","--install-if-needed","--filename",n.cS.fromPortablePath(t)],{cwd:e,env:o,stdin:null,stdout:g,stderr:l});return 0!==a.code?a.code:0}],[A.Npm,async()=>{if(null!==i)throw new Error("Workspaces aren't supported by npm, which has been detected as the primary package manager for "+e);delete o.npm_config_user_agent;const r=await y.pipevp("npm",["install"],{cwd:e,env:o,stdin:null,stdout:g,stderr:l,end:y.EndStrategy.ErrorCode});if(0!==r.code)return r.code;const A=new d.PassThrough,a=w.bufferStream(A);A.pipe(g);const c=await y.pipevp("npm",["pack","--silent"],{cwd:e,env:o,stdin:null,stdout:A,stderr:l});if(0!==c.code)return c.code;const u=(await a).toString().trim(),h=n.y1.resolve(e,n.cS.toPortablePath(u));return await s.xfs.renamePromise(h,t),0}]]).get(h);if(void 0===u)throw new Error("Assertion failed: Unsupported workflow");const p=await u();if(0!==p&&void 0!==p)throw s.xfs.detachTemp(a),new I.lk(f.b.PACKAGE_PREPARATION_FAILED,`Packing the package failed (exit code ${p}, logs can be found here: ${c})`)})})})}async function k(e,t,{project:r}){const A=r.storedPackages.get(e.locatorHash);if(!A)throw new Error(`Package for ${Q.prettyLocator(r.configuration,e)} not found in the project`);return await o.A.openPromise(async e=>{const o=r.configuration,s=r.configuration.getLinkers(),a={project:r,report:new E.Pk({stdout:new d.PassThrough,configuration:o})},c=s.find(e=>e.supportsPackage(A,a));if(!c)throw new Error(`The package ${Q.prettyLocator(r.configuration,A)} isn't supported by any of the available linkers`);const g=await c.findPackageLocation(A,a),l=new i.M(g,{baseFs:e});return(await C.G.find(n.LZ.dot,{baseFs:l})).scripts.has(t)},{libzip:await(0,a.getLibzipPromise)()})}async function N(e,t,r,{cwd:A,project:n,stdin:o,stdout:i,stderr:a}){return await s.xfs.mktempPromise(async s=>{const{manifest:g,env:l,cwd:u}=await K(e,{project:n,binFolder:s,cwd:A,lifecycleScript:t}),h=g.scripts.get(t);if(void 0===h)return 1;const p=await n.configuration.reduceHook(e=>e.wrapScriptExecution,async()=>await(0,c.execute)(h,r,{cwd:u,env:l,stdin:o,stdout:i,stderr:a}),n,e,t,{script:h,args:r,cwd:u,env:l,stdin:o,stdout:i,stderr:a});return await p()})}async function F(e,t,r,{cwd:A,project:n,stdin:o,stdout:i,stderr:a}){return await s.xfs.mktempPromise(async s=>{const{env:g,cwd:l}=await K(e,{project:n,binFolder:s,cwd:A});return await(0,c.execute)(t,r,{cwd:l,env:g,stdin:o,stdout:i,stderr:a})})}async function K(e,{project:t,binFolder:r,cwd:A,lifecycleScript:s}){const c=t.storedPackages.get(e.locatorHash);if(!c)throw new Error(`Package for ${Q.prettyLocator(t.configuration,e)} not found in the project`);return await o.A.openPromise(async o=>{const a=t.configuration,g=t.configuration.getLinkers(),l={project:t,report:new E.Pk({stdout:new d.PassThrough,configuration:a})},u=g.find(e=>e.supportsPackage(c,l));if(!u)throw new Error(`The package ${Q.prettyLocator(t.configuration,c)} isn't supported by any of the available linkers`);const h=await b({project:t,binFolder:r,lifecycleScript:s});await Promise.all(Array.from(await P(e,{project:t}),([e,[,t]])=>D(r,(0,n.Zu)(e),process.execPath,[t])));const p=await u.findPackageLocation(c,l),f=new i.M(p,{baseFs:o}),I=await C.G.find(n.LZ.dot,{baseFs:f});return void 0===A&&(A=p),{manifest:I,binFolder:r,env:h,cwd:A}},{libzip:await(0,a.getLibzipPromise)()})}async function M(e,t,r,{cwd:A,stdin:n,stdout:o,stderr:i}){return await N(e.anchoredLocator,t,r,{cwd:A,project:e.project,stdin:n,stdout:o,stderr:i})}function R(e,t){return e.manifest.scripts.has(t)}async function x(e,t,{cwd:r,report:A}){const{configuration:o}=e.project;await s.xfs.mktempPromise(async i=>{const a=n.y1.join(i,t+".log"),c=`# This file contains the result of Yarn calling the "${t}" lifecycle script inside a workspace ("${e.cwd}")\n`,{stdout:g,stderr:l}=o.getSubprocessStreams(a,{report:A,prefix:Q.prettyLocator(o,e.anchoredLocator),header:c});A.reportInfo(f.b.LIFECYCLE_SCRIPT,`Calling the "${t}" lifecycle script`);const h=await M(e,t,[],{cwd:r,stdin:null,stdout:g,stderr:l});if(g.end(),l.end(),0!==h)throw s.xfs.detachTemp(i),new I.lk(f.b.LIFECYCLE_SCRIPT,`${u()(t)} script failed (exit code ${m.pretty(o,h,m.Type.NUMBER)}, logs can be found here: ${m.pretty(o,a,m.Type.PATH)}); run ${m.pretty(o,"yarn "+t,m.Type.CODE)} to investigate`)})}async function L(e,t,r){R(e,t)&&await x(e,t,r)}async function P(e,{project:t}){const r=t.configuration,A=new Map,o=t.storedPackages.get(e.locatorHash);if(!o)throw new Error(`Package for ${Q.prettyLocator(r,e)} not found in the project`);const i=new d.Writable,s=r.getLinkers(),a={project:t,report:new E.Pk({configuration:r,stdout:i})},c=new Set([e.locatorHash]);for(const e of o.dependencies.values()){const A=t.storedResolutions.get(e.descriptorHash);if(!A)throw new Error(`Assertion failed: The resolution (${Q.prettyDescriptor(r,e)}) should have been registered`);c.add(A)}for(const e of c){const r=t.storedPackages.get(e);if(!r)throw new Error(`Assertion failed: The package (${e}) should have been registered`);if(0===r.bin.size)continue;const o=s.find(e=>e.supportsPackage(r,a));if(!o)continue;let i=null;try{i=await o.findPackageLocation(r,a)}catch(e){if("LOCATOR_NOT_INSTALLED"===e.code)continue;throw e}for(const[e,t]of r.bin)A.set(e,[r,n.cS.fromPortablePath(n.y1.resolve(i,t))])}return A}async function O(e){return await P(e.anchoredLocator,{project:e.project})}async function U(e,t,r,{cwd:A,project:o,stdin:i,stdout:a,stderr:c,nodeArgs:g=[]}){const l=await P(e,{project:o}),u=l.get(t);if(!u)throw new Error(`Binary not found (${t}) for ${Q.prettyLocator(o.configuration,e)}`);return await s.xfs.mktempPromise(async e=>{const[,t]=u,h=await b({project:o,binFolder:e});let p;await Promise.all(Array.from(l,([e,[,t]])=>D(h.BERRY_BIN_FOLDER,(0,n.Zu)(e),process.execPath,[t])));try{p=await y.pipevp(process.execPath,[...g,t,...r],{cwd:A,env:h,stdin:i,stdout:a,stderr:c})}finally{await s.xfs.removePromise(h.BERRY_BIN_FOLDER)}return p.code})}async function T(e,t,r,{cwd:A,stdin:n,stdout:o,stderr:i}){return await U(e.anchoredLocator,t,r,{project:e.project,cwd:A,stdin:n,stdout:o,stderr:i})}},36545:(e,t,r)=>{"use strict";r.r(t),r.d(t,{satisfiesWithPrereleases:()=>o,validRange:()=>s});var A=r(53887),n=r.n(A);function o(e,t,r=!1){let A,o;try{A=new(n().Range)(t,{includePrerelease:!0,loose:r})}catch(e){return!1}if(!e)return!1;try{o=new(n().SemVer)(e,A),o.prerelease&&(o.prerelease=[])}catch(e){return!1}return A.set.some(e=>{for(const t of e)t.semver.prerelease&&(t.semver.prerelease=[]);return e.every(e=>e.test(o))})}const i=new Map;function s(e){if(-1!==e.indexOf(":"))return null;let t=i.get(e);if(void 0!==t)return t;try{t=new(n().Range)(e)}catch(e){t=null}return i.set(e,t),t}},54143:(e,t,r)=>{"use strict";r.r(t),r.d(t,{makeIdent:()=>u,makeDescriptor:()=>h,makeLocator:()=>p,convertToIdent:()=>d,convertDescriptorToLocator:()=>C,convertLocatorToDescriptor:()=>f,convertPackageToLocator:()=>I,renamePackage:()=>E,copyPackage:()=>B,virtualizeDescriptor:()=>y,virtualizePackage:()=>m,isVirtualDescriptor:()=>w,isVirtualLocator:()=>Q,devirtualizeDescriptor:()=>D,devirtualizeLocator:()=>b,bindDescriptor:()=>v,bindLocator:()=>S,areIdentsEqual:()=>k,areDescriptorsEqual:()=>N,areLocatorsEqual:()=>F,areVirtualPackagesEquivalent:()=>K,parseIdent:()=>M,tryParseIdent:()=>R,parseDescriptor:()=>x,tryParseDescriptor:()=>L,parseLocator:()=>P,tryParseLocator:()=>O,parseRange:()=>U,parseFileStyleRange:()=>T,makeRange:()=>Y,convertToManifestRange:()=>G,requirableIdent:()=>H,stringifyIdent:()=>J,stringifyDescriptor:()=>q,stringifyLocator:()=>z,slugifyIdent:()=>W,slugifyLocator:()=>V,prettyIdent:()=>X,prettyRange:()=>Z,prettyDescriptor:()=>$,prettyReference:()=>ee,prettyLocator:()=>te,prettyLocatorNoColors:()=>re,sortDescriptors:()=>Ae,prettyWorkspace:()=>ne,prettyResolution:()=>oe,prettyDependent:()=>ie,getIdentVendorPath:()=>se});var A=r(46009),n=r(71191),o=r.n(n),i=r(53887),s=r.n(i),a=r(71643),c=r(20624),g=r(73632),l=r(54143);function u(e,t){if(null==e?void 0:e.startsWith("@"))throw new Error("Invalid scope: don't prefix it with '@'");return{identHash:c.makeHash(e,t),scope:e,name:t}}function h(e,t){return{identHash:e.identHash,scope:e.scope,name:e.name,descriptorHash:c.makeHash(e.identHash,t),range:t}}function p(e,t){return{identHash:e.identHash,scope:e.scope,name:e.name,locatorHash:c.makeHash(e.identHash,t),reference:t}}function d(e){return{identHash:e.identHash,scope:e.scope,name:e.name}}function C(e){return{identHash:e.identHash,scope:e.scope,name:e.name,locatorHash:e.descriptorHash,reference:e.range}}function f(e){return{identHash:e.identHash,scope:e.scope,name:e.name,descriptorHash:e.locatorHash,range:e.reference}}function I(e){return{identHash:e.identHash,scope:e.scope,name:e.name,locatorHash:e.locatorHash,reference:e.reference}}function E(e,t){return{identHash:t.identHash,scope:t.scope,name:t.name,locatorHash:t.locatorHash,reference:t.reference,version:e.version,languageName:e.languageName,linkType:e.linkType,dependencies:new Map(e.dependencies),peerDependencies:new Map(e.peerDependencies),dependenciesMeta:new Map(e.dependenciesMeta),peerDependenciesMeta:new Map(e.peerDependenciesMeta),bin:new Map(e.bin)}}function B(e){return E(e,e)}function y(e,t){if(t.includes("#"))throw new Error("Invalid entropy");return h(e,`virtual:${t}#${e.range}`)}function m(e,t){if(t.includes("#"))throw new Error("Invalid entropy");return E(e,p(e,`virtual:${t}#${e.reference}`))}function w(e){return e.range.startsWith("virtual:")}function Q(e){return e.reference.startsWith("virtual:")}function D(e){if(!w(e))throw new Error("Not a virtual descriptor");return h(e,e.range.replace(/^[^#]*#/,""))}function b(e){if(!Q(e))throw new Error("Not a virtual descriptor");return p(e,e.reference.replace(/^[^#]*#/,""))}function v(e,t){return e.range.includes("::")?e:h(e,`${e.range}::${o().stringify(t)}`)}function S(e,t){return e.reference.includes("::")?e:p(e,`${e.reference}::${o().stringify(t)}`)}function k(e,t){return e.identHash===t.identHash}function N(e,t){return e.descriptorHash===t.descriptorHash}function F(e,t){return e.locatorHash===t.locatorHash}function K(e,t){if(!Q(e))throw new Error("Invalid package type");if(!Q(t))throw new Error("Invalid package type");if(!k(e,t))return!1;if(e.dependencies.size!==t.dependencies.size)return!1;for(const r of e.dependencies.values()){const e=t.dependencies.get(r.identHash);if(!e)return!1;if(!N(r,e))return!1}return!0}function M(e){const t=R(e);if(!t)throw new Error(`Invalid ident (${e})`);return t}function R(e){const t=e.match(/^(?:@([^/]+?)\/)?([^/]+)$/);if(!t)return null;const[,r,A]=t;return u(void 0!==r?r:null,A)}function x(e,t=!1){const r=L(e,t);if(!r)throw new Error(`Invalid descriptor (${e})`);return r}function L(e,t=!1){const r=t?e.match(/^(?:@([^/]+?)\/)?([^/]+?)(?:@(.+))$/):e.match(/^(?:@([^/]+?)\/)?([^/]+?)(?:@(.+))?$/);if(!r)return null;const[,A,n,o]=r;if("unknown"===o)throw new Error(`Invalid range (${e})`);const i=void 0!==o?o:"unknown";return h(u(void 0!==A?A:null,n),i)}function P(e,t=!1){const r=O(e,t);if(!r)throw new Error(`Invalid locator (${e})`);return r}function O(e,t=!1){const r=t?e.match(/^(?:@([^/]+?)\/)?([^/]+?)(?:@(.+))$/):e.match(/^(?:@([^/]+?)\/)?([^/]+?)(?:@(.+))?$/);if(!r)return null;const[,A,n,o]=r;if("unknown"===o)throw new Error(`Invalid reference (${e})`);const i=void 0!==o?o:"unknown";return p(u(void 0!==A?A:null,n),i)}function U(e,t){const r=e.match(/^([^#:]*:)?((?:(?!::)[^#])*)(?:#((?:(?!::).)*))?(?:::(.*))?$/);if(null===r)throw new Error(`Invalid range (${e})`);const A=void 0!==r[1]?r[1]:null;if("string"==typeof(null==t?void 0:t.requireProtocol)&&A!==t.requireProtocol)throw new Error(`Invalid protocol (${A})`);if((null==t?void 0:t.requireProtocol)&&null===A)throw new Error(`Missing protocol (${A})`);const n=void 0!==r[3]?decodeURIComponent(r[2]):null;if((null==t?void 0:t.requireSource)&&null===n)throw new Error(`Missing source (${e})`);const i=void 0!==r[3]?decodeURIComponent(r[3]):decodeURIComponent(r[2]);return{protocol:A,source:n,selector:(null==t?void 0:t.parseSelector)?o().parse(i):i,params:void 0!==r[4]?o().parse(r[4]):null}}function T(e,{protocol:t}){const{selector:r,params:A}=U(e,{requireProtocol:t,requireBindings:!0});if("string"!=typeof A.locator)throw new Error("Assertion failed: Invalid bindings for "+e);return{parentLocator:P(A.locator,!0),path:r}}function j(e){return e=(e=(e=e.replace(/%/g,"%25")).replace(/:/g,"%3A")).replace(/#/g,"%23")}function Y({protocol:e,source:t,selector:r,params:A}){let n="";return null!==e&&(n+=""+e),null!==t&&(n+=j(t)+"#"),n+=j(r),function(e){return null!==e&&Object.entries(e).length>0}(A)&&(n+="::"+o().stringify(A)),n}function G(e){const{params:t,protocol:r,source:A,selector:n}=U(e);for(const e in t)e.startsWith("__")&&delete t[e];return Y({protocol:r,source:A,params:t,selector:n})}function H(e){return e.scope?`@${e.scope}/${e.name}`:""+e.name}function J(e){return e.scope?`@${e.scope}/${e.name}`:""+e.name}function q(e){return e.scope?`@${e.scope}/${e.name}@${e.range}`:`${e.name}@${e.range}`}function z(e){return e.scope?`@${e.scope}/${e.name}@${e.reference}`:`${e.name}@${e.reference}`}function W(e){return null!==e.scope?`@${e.scope}-${e.name}`:e.name}function V(e){const{protocol:t,selector:r}=U(e.reference),n=null!==t?t.replace(/:$/,""):"exotic",o=s().valid(r),i=null!==o?`${n}-${o}`:""+n,a=(e.scope,`${W(e)}-${i}-${e.locatorHash.slice(0,10)}`);return(0,A.Zu)(a)}function X(e,t){return t.scope?`${a.pretty(e,`@${t.scope}/`,a.Type.SCOPE)}${a.pretty(e,t.name,a.Type.NAME)}`:""+a.pretty(e,t.name,a.Type.NAME)}function _(e){if(e.startsWith("virtual:")){return`${_(e.substr(e.indexOf("#")+1))} [${e.substr("virtual:".length,5)}]`}return e.replace(/\?.*/,"?[...]")}function Z(e,t){return""+a.pretty(e,_(t),a.Type.RANGE)}function $(e,t){return`${X(e,t)}${a.pretty(e,"@",a.Type.RANGE)}${Z(e,t.range)}`}function ee(e,t){return""+a.pretty(e,_(t),a.Type.REFERENCE)}function te(e,t){return`${X(e,t)}${a.pretty(e,"@",a.Type.REFERENCE)}${ee(e,t.reference)}`}function re(e){return`${J(e)}@${_(e.reference)}`}function Ae(e){return g.sortMap(e,[e=>J(e),e=>e.range])}function ne(e,t){return X(e,t.locator)}function oe(e,t,r){const A=w(t)?D(t):t;return null===r?`${l.prettyDescriptor(e,A)} → ${a.mark(e).Cross}`:A.identHash===r.identHash?`${l.prettyDescriptor(e,A)} → ${ee(e,r.reference)}`:`${l.prettyDescriptor(e,A)} → ${te(e,r)}`}function ie(e,t,r){return null===r?""+te(e,t):`${te(e,t)} (via ${l.prettyRange(e,r.range)})`}function se(e){return"node_modules/"+H(e)}},72785:(e,t,r)=>{"use strict";r.r(t),r.d(t,{makeArchiveFromDirectory:()=>h,convertToZip:()=>p,extractArchiveTo:()=>d});var A=r(78420),n=r(46009),o=r(90739),i=r(43896),s=r(65281),a=r(59938),c=r(31669),g=r(78761),l=r.n(g);const u=(0,c.promisify)(l().gunzip);async function h(e,{baseFs:t=new A.S,prefixPath:r=n.LZ.root,compressionLevel:a,inMemory:c=!1}={}){const g=await(0,s.getLibzipPromise)();let l;if(c)l=new o.d(null,{libzip:g,level:a});else{const e=await i.xfs.mktempPromise(),t=n.y1.join(e,"archive.zip");l=new o.d(t,{create:!0,libzip:g,level:a})}const u=n.y1.resolve(n.LZ.root,r);return await l.copyPromise(u,e,{baseFs:t,stableTime:!0,stableSort:!0}),l}async function p(e,t){const r=await i.xfs.mktempPromise(),A=n.y1.join(r,"archive.zip"),{compressionLevel:a,...c}=t;return await d(e,new o.d(A,{create:!0,libzip:await(0,s.getLibzipPromise)(),level:a}),c)}async function d(e,t,{stripComponents:r=0,prefixPath:A=n.LZ.dot}={}){const o=a.extract();o.on("entry",(e,o,i)=>{var s,a;if(function(e){if("/"===e.name[0])return!0;const t=e.name.split(/\//g);return!!t.some(e=>".."===e)||t.length<=r}(e))return void i();const c=n.y1.normalize(n.cS.toPortablePath(e.name)).replace(/\/$/,"").split(/\//g);if(c.length<=r)return o.resume(),void i();const g=c.slice(r).join("/"),l=n.y1.join(A,g);let u=420;switch("directory"!==e.type&&0==(73&(null!==(s=e.mode)&&void 0!==s?s:0))||(u|=73),e.type){case"directory":t.mkdirpSync(n.y1.dirname(l),{chmod:493,utimes:[315532800,315532800]}),t.mkdirSync(l),t.chmodSync(l,u),t.utimesSync(l,315532800,315532800),i();break;case"file":{t.mkdirpSync(n.y1.dirname(l),{chmod:493,utimes:[315532800,315532800]});const e=[];o.on("data",t=>e.push(t)),o.on("end",()=>{t.writeFileSync(l,Buffer.concat(e)),t.chmodSync(l,u),t.utimesSync(l,315532800,315532800),i()})}break;case"symlink":t.mkdirpSync(n.y1.dirname(l),{chmod:493,utimes:[315532800,315532800]}),t.symlinkSync(e.linkname,l),null===(a=t.lutimesSync)||void 0===a||a.call(t,l,315532800,315532800),i();break;default:o.resume(),i()}});const i=await u(e);return await new Promise((e,r)=>{o.on("error",e=>{r(e)}),o.on("finish",()=>{e(t)}),o.end(i)})}},85875:(e,t,r)=>{"use strict";r.r(t),r.d(t,{treeNodeToTreeify:()=>o,treeNodeToJson:()=>i,emitList:()=>s,emitTree:()=>a});var A=r(94682),n=r(71643);function o(e,{configuration:t}){const r={},A=(e,r)=>{const o=Array.isArray(e)?e.entries():Object.entries(e);for(const[e,{label:i,value:s,children:a}]of o){const o=[];void 0!==i&&o.push(n.applyStyle(t,i,n.Style.BOLD)),void 0!==s&&o.push(n.pretty(t,s[0],s[1])),0===o.length&&o.push(n.applyStyle(t,""+e,n.Style.BOLD));const c=r[o.join(": ")]={};void 0!==a&&A(a,c)}};if(void 0===e.children)throw new Error("The root node must only contain children");return A(e.children,r),r}function i(e){const t=e=>{var r;if(void 0===e.children){if(void 0===e.value)throw new Error("Assertion failed: Expected a value to be set if the children are missing");return n.json(e.value[0],e.value[1])}const A=Array.isArray(e.children)?e.children.entries():Object.entries(null!==(r=e.children)&&void 0!==r?r:{}),o=Array.isArray(e.children)?[]:{};for(const[e,r]of A)o[e]=t(r);return void 0===e.value?o:{value:n.json(e.value[0],e.value[1]),children:o}};return t(e)}function s(e,{configuration:t,stdout:r,json:A}){a({children:e.map(e=>({value:e}))},{configuration:t,stdout:r,json:A})}function a(e,{configuration:t,stdout:r,json:n,separators:s=0}){var a;if(n){const t=Array.isArray(e.children)?e.children.values():Object.values(null!==(a=e.children)&&void 0!==a?a:{});for(const e of t)r.write(JSON.stringify(i(e))+"\n");return}let c=(0,A.asTree)(o(e,{configuration:t}),!1,!1);if(s>=1&&(c=c.replace(/^([├└]─)/gm,"│\n$1").replace(/^│\n/,"")),s>=2)for(let e=0;e<2;++e)c=c.replace(/^([│ ].{2}[├│ ].{2}[^\n]+\n)(([│ ]).{2}[├└].{2}[^\n]*\n[│ ].{2}[│ ].{2}[├└]─)/gm,"$1$3 │\n$2").replace(/^│\n/,"");if(s>=3)throw new Error("Only the first two levels are accepted by treeUtils.emitTree");r.write(c)}},32485:(e,t,r)=>{"use strict";var A,n,o;r.d(t,{Un:()=>A,HN:()=>n,_u:()=>o}),function(e){e.HARD="HARD",e.SOFT="SOFT"}(A||(A={})),function(e){e.Dependency="Dependency",e.PeerDependency="PeerDependency",e.PeerDependencyMeta="PeerDependencyMeta"}(n||(n={})),function(e){e.Inactive="inactive",e.Redundant="redundant",e.Active="active"}(o||(o={}))},14626:(e,t,r)=>{"use strict";r.d(t,{K:()=>n});var A=r(42096);class n extends A.p{constructor(e,{baseFs:t,pathUtils:r}){super(r),this.target=e,this.baseFs=t}getRealPath(){return this.target}getBaseFs(){return this.baseFs}mapFromBase(e){return e}mapToBase(e){return e}}},75448:(e,t,r)=>{"use strict";r.d(t,{M:()=>i});var A=r(78420),n=r(42096),o=r(46009);class i extends n.p{constructor(e,{baseFs:t=new A.S}={}){super(o.y1),this.target=this.pathUtils.normalize(e),this.baseFs=t}getRealPath(){return this.pathUtils.resolve(this.baseFs.getRealPath(),this.target)}resolve(e){return this.pathUtils.isAbsolute(e)?o.y1.normalize(e):this.baseFs.resolve(o.y1.join(this.target,e))}mapFromBase(e){return e}mapToBase(e){return this.pathUtils.isAbsolute(e)?e:this.pathUtils.join(this.target,e)}}},5944:(e,t,r)=>{"use strict";r.d(t,{fS:()=>g,uY:()=>c,qH:()=>l});var A=r(12087),n=r(35747),o=r.n(n),i=r(46009);const s=new Date(3155328e5);async function a(e,t,r,A,n,c,g,l){var u,h;const p=await async function(e,t){try{return await e.lstatPromise(t)}catch(e){return null}}(A,n),d=await c.lstatPromise(g),C=l.stableTime?{mtime:s,atime:s}:d;let f;switch(!0){case d.isDirectory():f=await async function(e,t,r,A,n,o,i,s,c,g){if(null!==o&&!o.isDirectory()){if(!g.overwrite)return!1;e.push(async()=>A.removePromise(n)),o=null}let l=!1;null===o&&(e.push(async()=>A.mkdirPromise(n,{mode:c.mode})),l=!0);const u=await i.readdirPromise(s);if(g.stableSort)for(const o of u.sort())await a(e,t,r,A,A.pathUtils.join(n,o),i,i.pathUtils.join(s,o),g)&&(l=!0);else{(await Promise.all(u.map(async o=>{await a(e,t,r,A,A.pathUtils.join(n,o),i,i.pathUtils.join(s,o),g)}))).some(e=>e)&&(l=!0)}return l}(e,t,r,A,n,p,c,g,d,l);break;case d.isFile():f=await async function(e,t,r,A,n,i,s,a,c,g){if(null!==i){if(!g.overwrite)return!1;e.push(async()=>A.removePromise(n)),i=null}const l=A===s?async()=>A.copyFilePromise(a,n,o().constants.COPYFILE_FICLONE):async()=>A.writeFilePromise(n,await s.readFilePromise(a));return e.push(async()=>l()),!0}(e,0,0,A,n,p,c,g,0,l);break;case d.isSymbolicLink():f=await async function(e,t,r,A,n,o,s,a,c,g){if(null!==o){if(!g.overwrite)return!1;e.push(async()=>A.removePromise(n)),o=null}return e.push(async()=>{await A.symlinkPromise((0,i.CI)(A.pathUtils,await s.readlinkPromise(a)),n)}),!0}(e,0,0,A,n,p,c,g,0,l);break;default:throw new Error(`Unsupported file type (${d.mode})`)}return(f||(null===(u=null==p?void 0:p.mtime)||void 0===u?void 0:u.getTime())!==C.mtime.getTime()||(null===(h=null==p?void 0:p.atime)||void 0===h?void 0:h.getTime())!==C.atime.getTime())&&(t.push(()=>r(n,C.atime,C.mtime)),f=!0),null!==p&&(511&p.mode)==(511&d.mode)||(t.push(()=>A.chmodPromise(n,511&d.mode)),f=!0),f}class c{constructor(e){this.pathUtils=e}async*genTraversePromise(e,{stableSort:t=!1}={}){const r=[e];for(;r.length>0;){const e=r.shift();if((await this.lstatPromise(e)).isDirectory()){const A=await this.readdirPromise(e);if(!t)throw new Error("Not supported");for(const t of A.sort())r.push(this.pathUtils.join(e,t))}else yield e}}async removePromise(e,{recursive:t=!0,maxRetries:r=5}={}){let A;try{A=await this.lstatPromise(e)}catch(e){if("ENOENT"===e.code)return;throw e}if(A.isDirectory()){if(t)for(const t of await this.readdirPromise(e))await this.removePromise(this.pathUtils.resolve(e,t));let A=0;do{try{await this.rmdirPromise(e);break}catch(e){if("EBUSY"===e.code||"ENOTEMPTY"===e.code){if(0===r)break;await new Promise(e=>setTimeout(e,100*A));continue}throw e}}while(A++e()))}(this,e,r,t,{overwrite:A,stableSort:n,stableTime:o})}copySync(e,t,{baseFs:r=this,overwrite:A=!0}={}){const n=r.lstatSync(t),o=this.existsSync(e);if(n.isDirectory()){this.mkdirpSync(e);const n=r.readdirSync(t);for(const o of n)this.copySync(this.pathUtils.join(e,o),r.pathUtils.join(t,o),{baseFs:r,overwrite:A})}else if(n.isFile()){if(!o||A){o&&this.removeSync(e);const A=r.readFileSync(t);this.writeFileSync(e,A)}}else{if(!n.isSymbolicLink())throw new Error(`Unsupported file type (file: ${t}, mode: 0o${n.mode.toString(8).padStart(6,"0")})`);if(!o||A){o&&this.removeSync(e);const A=r.readlinkSync(t);this.symlinkSync((0,i.CI)(this.pathUtils,A),e)}}const s=511&n.mode;this.chmodSync(e,s)}async changeFilePromise(e,t,r={}){return Buffer.isBuffer(t)?this.changeFileBufferPromise(e,t):this.changeFileTextPromise(e,t,r)}async changeFileBufferPromise(e,t){let r=Buffer.alloc(0);try{r=await this.readFilePromise(e)}catch(e){}0!==Buffer.compare(r,t)&&await this.writeFilePromise(e,t)}async changeFileTextPromise(e,t,{automaticNewlines:r}={}){let A="";try{A=await this.readFilePromise(e,"utf8")}catch(e){}const n=r?l(A,t):t;A!==n&&await this.writeFilePromise(e,n)}changeFileSync(e,t,r={}){return Buffer.isBuffer(t)?this.changeFileBufferSync(e,t):this.changeFileTextSync(e,t,r)}changeFileBufferSync(e,t){let r=Buffer.alloc(0);try{r=this.readFileSync(e)}catch(e){}0!==Buffer.compare(r,t)&&this.writeFileSync(e,t)}changeFileTextSync(e,t,{automaticNewlines:r=!1}={}){let A="";try{A=this.readFileSync(e,"utf8")}catch(e){}const n=r?l(A,t):t;A!==n&&this.writeFileSync(e,n)}async movePromise(e,t){try{await this.renamePromise(e,t)}catch(r){if("EXDEV"!==r.code)throw r;await this.copyPromise(t,e),await this.removePromise(e)}}moveSync(e,t){try{this.renameSync(e,t)}catch(r){if("EXDEV"!==r.code)throw r;this.copySync(t,e),this.removeSync(e)}}async lockPromise(e,t){const r=e+".flock",A=Date.now();let n=null;const o=async()=>{let e;try{[e]=await this.readJsonPromise(r)}catch(e){return Date.now()-A<500}try{return process.kill(e,0),!0}catch(e){return!1}};for(;null===n;)try{n=await this.openPromise(r,"wx")}catch(e){if("EEXIST"!==e.code)throw e;if(!await o())try{await this.unlinkPromise(r);continue}catch(e){}if(!(Date.now()-A<6e4))throw new Error(`Couldn't acquire a lock in a reasonable time (via ${r})`);await new Promise(e=>setTimeout(e,1e3/60))}await this.writePromise(n,JSON.stringify([process.pid]));try{return await t()}finally{try{await this.closePromise(n),await this.unlinkPromise(r)}catch(e){}}}async readJsonPromise(e){const t=await this.readFilePromise(e,"utf8");try{return JSON.parse(t)}catch(t){throw t.message+=` (in ${e})`,t}}readJsonSync(e){const t=this.readFileSync(e,"utf8");try{return JSON.parse(t)}catch(t){throw t.message+=` (in ${e})`,t}}async writeJsonPromise(e,t){return await this.writeFilePromise(e,JSON.stringify(t,null,2)+"\n")}writeJsonSync(e,t){return this.writeFileSync(e,JSON.stringify(t,null,2)+"\n")}async preserveTimePromise(e,t){const r=await this.lstatPromise(e),A=await t();void 0!==A&&(e=A),this.lutimesPromise?await this.lutimesPromise(e,r.atime,r.mtime):r.isSymbolicLink()||await this.utimesPromise(e,r.atime,r.mtime)}async preserveTimeSync(e,t){const r=this.lstatSync(e),A=t();void 0!==A&&(e=A),this.lutimesSync?this.lutimesSync(e,r.atime,r.mtime):r.isSymbolicLink()||this.utimesSync(e,r.atime,r.mtime)}}c.DEFAULT_TIME=315532800;class g extends c{constructor(){super(i.y1)}}function l(e,t){return t.replace(/\r?\n/g,function(e){const t=e.match(/\r?\n/g);if(null===t)return A.EOL;const r=t.filter(e=>"\r\n"===e).length;return r>t.length-r?"\r\n":"\n"}(e))}},10489:(e,t,r)=>{"use strict";r.d(t,{n:()=>s});var A=r(78420),n=r(42096),o=r(46009);const i=o.LZ.root;class s extends n.p{constructor(e,{baseFs:t=new A.S}={}){super(o.y1),this.target=this.pathUtils.resolve(o.LZ.root,e),this.baseFs=t}getRealPath(){return this.pathUtils.resolve(this.baseFs.getRealPath(),this.pathUtils.relative(o.LZ.root,this.target))}getTarget(){return this.target}getBaseFs(){return this.baseFs}mapToBase(e){const t=this.pathUtils.normalize(e);if(this.pathUtils.isAbsolute(e))return this.pathUtils.resolve(this.target,this.pathUtils.relative(i,e));if(t.match(/^\.\.\/?/))throw new Error(`Resolving this path (${e}) would escape the jail`);return this.pathUtils.resolve(this.target,e)}mapFromBase(e){return this.pathUtils.resolve(i,this.pathUtils.relative(this.target,e))}}},15037:(e,t,r)=>{"use strict";r.d(t,{v:()=>n});var A=r(42096);class n extends A.p{constructor(e,t){super(t),this.instance=null,this.factory=e}get baseFs(){return this.instance||(this.instance=this.factory()),this.instance}set baseFs(e){this.instance=e}mapFromBase(e){return e}mapToBase(e){return e}}},78420:(e,t,r)=>{"use strict";r.d(t,{S:()=>a});var A=r(35747),n=r.n(A),o=r(5944),i=r(26984),s=r(46009);class a extends o.fS{constructor(e=n()){super(),this.realFs=e,void 0!==this.realFs.lutimes&&(this.lutimesPromise=this.lutimesPromiseImpl,this.lutimesSync=this.lutimesSyncImpl)}getExtractHint(){return!1}getRealPath(){return s.LZ.root}resolve(e){return s.y1.resolve(e)}async openPromise(e,t,r){return await new Promise((A,n)=>{this.realFs.open(s.cS.fromPortablePath(e),t,r,this.makeCallback(A,n))})}openSync(e,t,r){return this.realFs.openSync(s.cS.fromPortablePath(e),t,r)}async opendirPromise(e,t){return await new Promise((r,A)=>{void 0!==t?this.realFs.opendir(s.cS.fromPortablePath(e),t,this.makeCallback(r,A)):this.realFs.opendir(s.cS.fromPortablePath(e),this.makeCallback(r,A))}).then(t=>Object.defineProperty(t,"path",{value:e,configurable:!0,writable:!0}))}opendirSync(e,t){const r=void 0!==t?this.realFs.opendirSync(s.cS.fromPortablePath(e),t):this.realFs.opendirSync(s.cS.fromPortablePath(e));return Object.defineProperty(r,"path",{value:e,configurable:!0,writable:!0})}async readPromise(e,t,r=0,A=0,n=-1){return await new Promise((o,i)=>{this.realFs.read(e,t,r,A,n,(e,t)=>{e?i(e):o(t)})})}readSync(e,t,r,A,n){return this.realFs.readSync(e,t,r,A,n)}async writePromise(e,t,r,A,n){return await new Promise((o,i)=>"string"==typeof t?this.realFs.write(e,t,r,this.makeCallback(o,i)):this.realFs.write(e,t,r,A,n,this.makeCallback(o,i)))}writeSync(e,t,r,A,n){return"string"==typeof t?this.realFs.writeSync(e,t,r):this.realFs.writeSync(e,t,r,A,n)}async closePromise(e){await new Promise((t,r)=>{this.realFs.close(e,this.makeCallback(t,r))})}closeSync(e){this.realFs.closeSync(e)}createReadStream(e,t){const r=null!==e?s.cS.fromPortablePath(e):e;return this.realFs.createReadStream(r,t)}createWriteStream(e,t){const r=null!==e?s.cS.fromPortablePath(e):e;return this.realFs.createWriteStream(r,t)}async realpathPromise(e){return await new Promise((t,r)=>{this.realFs.realpath(s.cS.fromPortablePath(e),{},this.makeCallback(t,r))}).then(e=>s.cS.toPortablePath(e))}realpathSync(e){return s.cS.toPortablePath(this.realFs.realpathSync(s.cS.fromPortablePath(e),{}))}async existsPromise(e){return await new Promise(t=>{this.realFs.exists(s.cS.fromPortablePath(e),t)})}accessSync(e,t){return this.realFs.accessSync(s.cS.fromPortablePath(e),t)}async accessPromise(e,t){return await new Promise((r,A)=>{this.realFs.access(s.cS.fromPortablePath(e),t,this.makeCallback(r,A))})}existsSync(e){return this.realFs.existsSync(s.cS.fromPortablePath(e))}async statPromise(e){return await new Promise((t,r)=>{this.realFs.stat(s.cS.fromPortablePath(e),this.makeCallback(t,r))})}statSync(e){return this.realFs.statSync(s.cS.fromPortablePath(e))}async lstatPromise(e){return await new Promise((t,r)=>{this.realFs.lstat(s.cS.fromPortablePath(e),this.makeCallback(t,r))})}lstatSync(e){return this.realFs.lstatSync(s.cS.fromPortablePath(e))}async chmodPromise(e,t){return await new Promise((r,A)=>{this.realFs.chmod(s.cS.fromPortablePath(e),t,this.makeCallback(r,A))})}chmodSync(e,t){return this.realFs.chmodSync(s.cS.fromPortablePath(e),t)}async chownPromise(e,t,r){return await new Promise((A,n)=>{this.realFs.chown(s.cS.fromPortablePath(e),t,r,this.makeCallback(A,n))})}chownSync(e,t,r){return this.realFs.chownSync(s.cS.fromPortablePath(e),t,r)}async renamePromise(e,t){return await new Promise((r,A)=>{this.realFs.rename(s.cS.fromPortablePath(e),s.cS.fromPortablePath(t),this.makeCallback(r,A))})}renameSync(e,t){return this.realFs.renameSync(s.cS.fromPortablePath(e),s.cS.fromPortablePath(t))}async copyFilePromise(e,t,r=0){return await new Promise((A,n)=>{this.realFs.copyFile(s.cS.fromPortablePath(e),s.cS.fromPortablePath(t),r,this.makeCallback(A,n))})}copyFileSync(e,t,r=0){return this.realFs.copyFileSync(s.cS.fromPortablePath(e),s.cS.fromPortablePath(t),r)}async appendFilePromise(e,t,r){return await new Promise((A,n)=>{const o="string"==typeof e?s.cS.fromPortablePath(e):e;r?this.realFs.appendFile(o,t,r,this.makeCallback(A,n)):this.realFs.appendFile(o,t,this.makeCallback(A,n))})}appendFileSync(e,t,r){const A="string"==typeof e?s.cS.fromPortablePath(e):e;r?this.realFs.appendFileSync(A,t,r):this.realFs.appendFileSync(A,t)}async writeFilePromise(e,t,r){return await new Promise((A,n)=>{const o="string"==typeof e?s.cS.fromPortablePath(e):e;r?this.realFs.writeFile(o,t,r,this.makeCallback(A,n)):this.realFs.writeFile(o,t,this.makeCallback(A,n))})}writeFileSync(e,t,r){const A="string"==typeof e?s.cS.fromPortablePath(e):e;r?this.realFs.writeFileSync(A,t,r):this.realFs.writeFileSync(A,t)}async unlinkPromise(e){return await new Promise((t,r)=>{this.realFs.unlink(s.cS.fromPortablePath(e),this.makeCallback(t,r))})}unlinkSync(e){return this.realFs.unlinkSync(s.cS.fromPortablePath(e))}async utimesPromise(e,t,r){return await new Promise((A,n)=>{this.realFs.utimes(s.cS.fromPortablePath(e),t,r,this.makeCallback(A,n))})}utimesSync(e,t,r){this.realFs.utimesSync(s.cS.fromPortablePath(e),t,r)}async lutimesPromiseImpl(e,t,r){const A=this.realFs.lutimes;if(void 0===A)throw(0,i.bk)("unavailable Node binding",`lutimes '${e}'`);return await new Promise((n,o)=>{A.call(this.realFs,s.cS.fromPortablePath(e),t,r,this.makeCallback(n,o))})}lutimesSyncImpl(e,t,r){const A=this.realFs.lutimesSync;if(void 0===A)throw(0,i.bk)("unavailable Node binding",`lutimes '${e}'`);A.call(this.realFs,s.cS.fromPortablePath(e),t,r)}async mkdirPromise(e,t){return await new Promise((r,A)=>{this.realFs.mkdir(s.cS.fromPortablePath(e),t,this.makeCallback(r,A))})}mkdirSync(e,t){return this.realFs.mkdirSync(s.cS.fromPortablePath(e),t)}async rmdirPromise(e,t){return await new Promise((r,A)=>{t?this.realFs.rmdir(s.cS.fromPortablePath(e),t,this.makeCallback(r,A)):this.realFs.rmdir(s.cS.fromPortablePath(e),this.makeCallback(r,A))})}rmdirSync(e,t){return this.realFs.rmdirSync(s.cS.fromPortablePath(e),t)}async linkPromise(e,t){return await new Promise((r,A)=>{this.realFs.link(s.cS.fromPortablePath(e),s.cS.fromPortablePath(t),this.makeCallback(r,A))})}linkSync(e,t){return this.realFs.linkSync(s.cS.fromPortablePath(e),s.cS.fromPortablePath(t))}async symlinkPromise(e,t,r){const A=r||(e.endsWith("/")?"dir":"file");return await new Promise((r,n)=>{this.realFs.symlink(s.cS.fromPortablePath(e.replace(/\/+$/,"")),s.cS.fromPortablePath(t),A,this.makeCallback(r,n))})}symlinkSync(e,t,r){const A=r||(e.endsWith("/")?"dir":"file");return this.realFs.symlinkSync(s.cS.fromPortablePath(e.replace(/\/+$/,"")),s.cS.fromPortablePath(t),A)}async readFilePromise(e,t){return await new Promise((r,A)=>{const n="string"==typeof e?s.cS.fromPortablePath(e):e;this.realFs.readFile(n,t,this.makeCallback(r,A))})}readFileSync(e,t){const r="string"==typeof e?s.cS.fromPortablePath(e):e;return this.realFs.readFileSync(r,t)}async readdirPromise(e,{withFileTypes:t}={}){return await new Promise((r,A)=>{t?this.realFs.readdir(s.cS.fromPortablePath(e),{withFileTypes:!0},this.makeCallback(r,A)):this.realFs.readdir(s.cS.fromPortablePath(e),this.makeCallback(e=>r(e),A))})}readdirSync(e,{withFileTypes:t}={}){return t?this.realFs.readdirSync(s.cS.fromPortablePath(e),{withFileTypes:!0}):this.realFs.readdirSync(s.cS.fromPortablePath(e))}async readlinkPromise(e){return await new Promise((t,r)=>{this.realFs.readlink(s.cS.fromPortablePath(e),this.makeCallback(t,r))}).then(e=>s.cS.toPortablePath(e))}readlinkSync(e){return s.cS.toPortablePath(this.realFs.readlinkSync(s.cS.fromPortablePath(e)))}async truncatePromise(e,t){return await new Promise((r,A)=>{this.realFs.truncate(s.cS.fromPortablePath(e),t,this.makeCallback(r,A))})}truncateSync(e,t){return this.realFs.truncateSync(s.cS.fromPortablePath(e),t)}watch(e,t,r){return this.realFs.watch(s.cS.fromPortablePath(e),t,r)}watchFile(e,t,r){return this.realFs.watchFile(s.cS.fromPortablePath(e),t,r)}unwatchFile(e,t){return this.realFs.unwatchFile(s.cS.fromPortablePath(e),t)}makeCallback(e,t){return(r,A)=>{r?t(r):e(A)}}}},39725:(e,t,r)=>{"use strict";r.d(t,{i:()=>o});var A=r(42096),n=r(46009);class o extends A.p{constructor(e){super(n.cS),this.baseFs=e}mapFromBase(e){return n.cS.fromPortablePath(e)}mapToBase(e){return n.cS.toPortablePath(e)}}},42096:(e,t,r)=>{"use strict";r.d(t,{p:()=>n});var A=r(5944);class n extends A.uY{getExtractHint(e){return this.baseFs.getExtractHint(e)}resolve(e){return this.mapFromBase(this.baseFs.resolve(this.mapToBase(e)))}getRealPath(){return this.mapFromBase(this.baseFs.getRealPath())}async openPromise(e,t,r){return this.baseFs.openPromise(this.mapToBase(e),t,r)}openSync(e,t,r){return this.baseFs.openSync(this.mapToBase(e),t,r)}async opendirPromise(e,t){return Object.assign(await this.baseFs.opendirPromise(this.mapToBase(e),t),{path:e})}opendirSync(e,t){return Object.assign(this.baseFs.opendirSync(this.mapToBase(e),t),{path:e})}async readPromise(e,t,r,A,n){return await this.baseFs.readPromise(e,t,r,A,n)}readSync(e,t,r,A,n){return this.baseFs.readSync(e,t,r,A,n)}async writePromise(e,t,r,A,n){return"string"==typeof t?await this.baseFs.writePromise(e,t,r):await this.baseFs.writePromise(e,t,r,A,n)}writeSync(e,t,r,A,n){return"string"==typeof t?this.baseFs.writeSync(e,t,r):this.baseFs.writeSync(e,t,r,A,n)}async closePromise(e){return this.baseFs.closePromise(e)}closeSync(e){this.baseFs.closeSync(e)}createReadStream(e,t){return this.baseFs.createReadStream(null!==e?this.mapToBase(e):e,t)}createWriteStream(e,t){return this.baseFs.createWriteStream(null!==e?this.mapToBase(e):e,t)}async realpathPromise(e){return this.mapFromBase(await this.baseFs.realpathPromise(this.mapToBase(e)))}realpathSync(e){return this.mapFromBase(this.baseFs.realpathSync(this.mapToBase(e)))}async existsPromise(e){return this.baseFs.existsPromise(this.mapToBase(e))}existsSync(e){return this.baseFs.existsSync(this.mapToBase(e))}accessSync(e,t){return this.baseFs.accessSync(this.mapToBase(e),t)}async accessPromise(e,t){return this.baseFs.accessPromise(this.mapToBase(e),t)}async statPromise(e){return this.baseFs.statPromise(this.mapToBase(e))}statSync(e){return this.baseFs.statSync(this.mapToBase(e))}async lstatPromise(e){return this.baseFs.lstatPromise(this.mapToBase(e))}lstatSync(e){return this.baseFs.lstatSync(this.mapToBase(e))}async chmodPromise(e,t){return this.baseFs.chmodPromise(this.mapToBase(e),t)}chmodSync(e,t){return this.baseFs.chmodSync(this.mapToBase(e),t)}async chownPromise(e,t,r){return this.baseFs.chownPromise(this.mapToBase(e),t,r)}chownSync(e,t,r){return this.baseFs.chownSync(this.mapToBase(e),t,r)}async renamePromise(e,t){return this.baseFs.renamePromise(this.mapToBase(e),this.mapToBase(t))}renameSync(e,t){return this.baseFs.renameSync(this.mapToBase(e),this.mapToBase(t))}async copyFilePromise(e,t,r=0){return this.baseFs.copyFilePromise(this.mapToBase(e),this.mapToBase(t),r)}copyFileSync(e,t,r=0){return this.baseFs.copyFileSync(this.mapToBase(e),this.mapToBase(t),r)}async appendFilePromise(e,t,r){return this.baseFs.appendFilePromise(this.fsMapToBase(e),t,r)}appendFileSync(e,t,r){return this.baseFs.appendFileSync(this.fsMapToBase(e),t,r)}async writeFilePromise(e,t,r){return this.baseFs.writeFilePromise(this.fsMapToBase(e),t,r)}writeFileSync(e,t,r){return this.baseFs.writeFileSync(this.fsMapToBase(e),t,r)}async unlinkPromise(e){return this.baseFs.unlinkPromise(this.mapToBase(e))}unlinkSync(e){return this.baseFs.unlinkSync(this.mapToBase(e))}async utimesPromise(e,t,r){return this.baseFs.utimesPromise(this.mapToBase(e),t,r)}utimesSync(e,t,r){return this.baseFs.utimesSync(this.mapToBase(e),t,r)}async mkdirPromise(e,t){return this.baseFs.mkdirPromise(this.mapToBase(e),t)}mkdirSync(e,t){return this.baseFs.mkdirSync(this.mapToBase(e),t)}async rmdirPromise(e,t){return this.baseFs.rmdirPromise(this.mapToBase(e),t)}rmdirSync(e,t){return this.baseFs.rmdirSync(this.mapToBase(e),t)}async linkPromise(e,t){return this.baseFs.linkPromise(this.mapToBase(e),this.mapToBase(t))}linkSync(e,t){return this.baseFs.linkSync(this.mapToBase(e),this.mapToBase(t))}async symlinkPromise(e,t,r){return this.baseFs.symlinkPromise(this.mapToBase(e),this.mapToBase(t),r)}symlinkSync(e,t,r){return this.baseFs.symlinkSync(this.mapToBase(e),this.mapToBase(t),r)}async readFilePromise(e,t){return this.baseFs.readFilePromise(this.fsMapToBase(e),t)}readFileSync(e,t){return this.baseFs.readFileSync(this.fsMapToBase(e),t)}async readdirPromise(e,{withFileTypes:t}={}){return this.baseFs.readdirPromise(this.mapToBase(e),{withFileTypes:t})}readdirSync(e,{withFileTypes:t}={}){return this.baseFs.readdirSync(this.mapToBase(e),{withFileTypes:t})}async readlinkPromise(e){return this.mapFromBase(await this.baseFs.readlinkPromise(this.mapToBase(e)))}readlinkSync(e){return this.mapFromBase(this.baseFs.readlinkSync(this.mapToBase(e)))}async truncatePromise(e,t){return this.baseFs.truncatePromise(this.mapToBase(e),t)}truncateSync(e,t){return this.baseFs.truncateSync(this.mapToBase(e),t)}watch(e,t,r){return this.baseFs.watch(this.mapToBase(e),t,r)}watchFile(e,t,r){return this.baseFs.watchFile(this.mapToBase(e),t,r)}unwatchFile(e,t){return this.baseFs.unwatchFile(this.mapToBase(e),t)}fsMapToBase(e){return"number"==typeof e?e:this.mapToBase(e)}}},17674:(e,t,r)=>{"use strict";r.d(t,{p:()=>c});var A=r(78420),n=r(42096),o=r(46009);const i=/^[0-9]+$/,s=/^(\/(?:[^/]+\/)*?\$\$virtual)((?:\/((?:[^/]+-)?[a-f0-9]+)(?:\/([^/]+))?)?((?:\/.*)?))$/,a=/^([^/]+-)?[a-f0-9]+$/;class c extends n.p{constructor({baseFs:e=new A.S}={}){super(o.y1),this.baseFs=e}static makeVirtualPath(e,t,r){if("$$virtual"!==o.y1.basename(e))throw new Error('Assertion failed: Virtual folders must be named "$$virtual"');if(!o.y1.basename(t).match(a))throw new Error("Assertion failed: Virtual components must be ended by an hexadecimal hash");const A=o.y1.relative(o.y1.dirname(e),r).split("/");let n=0;for(;n{"use strict";r.d(t,{k:()=>C,d:()=>f});var A=r(35747),n=r(92413),o=r(31669),i=r(78761),s=r.n(i),a=r(5944),c=r(78420),g=r(19697),l=r(38783),u=r(22004),h=r(26984),p=r(46009),d=r(65760);const C="mixed";class f extends a.fS{constructor(e,t){super(),this.lzSource=null,this.listings=new Map,this.entries=new Map,this.fileSources=new Map,this.fds=new Map,this.nextFd=0,this.ready=!1,this.readOnly=!1,this.libzip=t.libzip;const r=t;if(this.level=void 0!==r.level?r.level:C,null===e&&(e=Buffer.from([80,75,5,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0])),"string"==typeof e){const{baseFs:t=new c.S}=r;this.baseFs=t,this.path=e}else this.path=null,this.baseFs=null;if(t.stats)this.stats=t.stats;else if("string"==typeof e)try{this.stats=this.baseFs.statSync(e)}catch(e){if("ENOENT"!==e.code||!r.create)throw e;this.stats=d.makeDefaultStats()}else this.stats=d.makeDefaultStats();const A=this.libzip.malloc(4);try{let n=0;if("string"==typeof e&&r.create&&(n|=this.libzip.ZIP_CREATE|this.libzip.ZIP_TRUNCATE),t.readOnly&&(n|=this.libzip.ZIP_RDONLY,this.readOnly=!0),"string"==typeof e)this.zip=this.libzip.open(p.cS.fromPortablePath(e),n,A);else{const t=this.allocateUnattachedSource(e);try{this.zip=this.libzip.openFromSource(t,n,A),this.lzSource=t}catch(e){throw this.libzip.source.free(t),e}}if(0===this.zip){const e=this.libzip.struct.errorS();throw this.libzip.error.initWithCode(e,this.libzip.getValue(A,"i32")),this.makeLibzipError(e)}}finally{this.libzip.free(A)}this.listings.set(p.LZ.root,new Set);const n=this.libzip.getNumEntries(this.zip,0);for(let e=0;ee)throw new Error("Overread");const A=this.libzip.HEAPU8.subarray(t,t+e);return Buffer.from(A)}finally{this.libzip.free(t)}}finally{this.libzip.source.close(this.lzSource),this.libzip.source.free(this.lzSource),this.ready=!1}}prepareClose(){if(!this.ready)throw h.Vw("archive closed, close");(0,l.L)(this)}saveAndClose(){if(!this.path||!this.baseFs)throw new Error("ZipFS cannot be saved and must be discarded when loaded from a buffer");if(this.prepareClose(),this.readOnly)return void this.discardAndClose();const e=this.baseFs.existsSync(this.path)?511&this.baseFs.statSync(this.path).mode:null;if(-1===this.libzip.close(this.zip))throw this.makeLibzipError(this.libzip.getError(this.zip));null===e?this.baseFs.chmodSync(this.path,this.stats.mode):e!==(511&this.baseFs.statSync(this.path).mode)&&this.baseFs.chmodSync(this.path,e),this.ready=!1}discardAndClose(){this.prepareClose(),this.libzip.discard(this.zip),this.ready=!1}resolve(e){return p.y1.resolve(p.LZ.root,e)}async openPromise(e,t,r){return this.openSync(e,t,r)}openSync(e,t,r){const A=this.nextFd++;return this.fds.set(A,{cursor:0,p:e}),A}hasOpenFileHandles(){return!!this.fds.size}async opendirPromise(e,t){return this.opendirSync(e,t)}opendirSync(e,t={}){const r=this.resolveFilename(`opendir '${e}'`,e);if(!this.entries.has(r)&&!this.listings.has(r))throw h.z6(`opendir '${e}'`);const A=this.listings.get(r);if(!A)throw h.Ab(`opendir '${e}'`);const n=[...A],o=this.openSync(r,"r");return(0,g.a)(this,r,n,{onClose:()=>{this.closeSync(o)}})}async readPromise(e,t,r,A,n){return this.readSync(e,t,r,A,n)}readSync(e,t,r=0,A=0,n=-1){const o=this.fds.get(e);if(void 0===o)throw h.Ch("read");let i;i=-1===n||null===n?o.cursor:n;const s=this.readFileSync(o.p);s.copy(t,r,i,i+A);const a=Math.max(0,Math.min(s.length-i,A));return-1!==n&&null!==n||(o.cursor+=a),a}async writePromise(e,t,r,A,n){return"string"==typeof t?this.writeSync(e,t,n):this.writeSync(e,t,r,A,n)}writeSync(e,t,r,A,n){if(void 0===this.fds.get(e))throw h.Ch("read");throw new Error("Unimplemented")}async closePromise(e){return this.closeSync(e)}closeSync(e){if(void 0===this.fds.get(e))throw h.Ch("read");this.fds.delete(e)}createReadStream(e,{encoding:t}={}){if(null===e)throw new Error("Unimplemented");const r=this.openSync(e,"r"),A=Object.assign(new n.PassThrough({emitClose:!0,autoDestroy:!0,destroy:(e,t)=>{clearImmediate(o),this.closeSync(r),t(e)}}),{close(){A.destroy()},bytesRead:0,path:e}),o=setImmediate(async()=>{try{const r=await this.readFilePromise(e,t);A.bytesRead=r.length,A.end(r)}catch(e){A.destroy(e)}});return A}createWriteStream(e,{encoding:t}={}){if(this.readOnly)throw h.YW(`open '${e}'`);if(null===e)throw new Error("Unimplemented");const r=[],A=this.openSync(e,"w"),o=Object.assign(new n.PassThrough({autoDestroy:!0,emitClose:!0,destroy:(n,o)=>{try{n?o(n):(this.writeFileSync(e,Buffer.concat(r),t),o(null))}catch(e){o(e)}finally{this.closeSync(A)}}}),{bytesWritten:0,path:e,close(){o.destroy()}});return o.on("data",e=>{const t=Buffer.from(e);o.bytesWritten+=t.length,r.push(t)}),o}async realpathPromise(e){return this.realpathSync(e)}realpathSync(e){const t=this.resolveFilename(`lstat '${e}'`,e);if(!this.entries.has(t)&&!this.listings.has(t))throw h.z6(`lstat '${e}'`);return t}async existsPromise(e){return this.existsSync(e)}existsSync(e){if(!this.ready)throw h.Vw(`archive closed, existsSync '${e}'`);if(0===this.symlinkCount){const t=p.y1.resolve(p.LZ.root,e);return this.entries.has(t)||this.listings.has(t)}let t;try{t=this.resolveFilename(`stat '${e}'`,e)}catch(e){return!1}return this.entries.has(t)||this.listings.has(t)}async accessPromise(e,t){return this.accessSync(e,t)}accessSync(e,t=A.constants.F_OK){const r=this.resolveFilename(`access '${e}'`,e);if(!this.entries.has(r)&&!this.listings.has(r))throw h.z6(`access '${e}'`);if(this.readOnly&&t&A.constants.W_OK)throw h.YW(`access '${e}'`)}async statPromise(e){return this.statSync(e)}statSync(e){const t=this.resolveFilename(`stat '${e}'`,e);if(!this.entries.has(t)&&!this.listings.has(t))throw h.z6(`stat '${e}'`);if("/"===e[e.length-1]&&!this.listings.has(t))throw h.Ab(`stat '${e}'`);return this.statImpl(`stat '${e}'`,t)}async lstatPromise(e){return this.lstatSync(e)}lstatSync(e){const t=this.resolveFilename(`lstat '${e}'`,e,!1);if(!this.entries.has(t)&&!this.listings.has(t))throw h.z6(`lstat '${e}'`);if("/"===e[e.length-1]&&!this.listings.has(t))throw h.Ab(`lstat '${e}'`);return this.statImpl(`lstat '${e}'`,t)}statImpl(e,t){const r=this.entries.get(t);if(void 0!==r){const e=this.libzip.struct.statS();if(-1===this.libzip.statIndex(this.zip,r,0,0,e))throw this.makeLibzipError(this.libzip.getError(this.zip));const A=this.stats.uid,n=this.stats.gid,o=this.libzip.struct.statSize(e)>>>0,i=512,s=Math.ceil(o/i),a=1e3*(this.libzip.struct.statMtime(e)>>>0),c=a,g=a,l=a,h=new Date(c),p=new Date(g),C=new Date(l),f=new Date(a),I=this.listings.has(t)?u.QB:this.isSymbolicLink(r)?u.Zv:u.Pe,E=I===u.QB?493:420,B=I|511&this.getUnixMode(r,E);return Object.assign(new d.StatEntry,{uid:A,gid:n,size:o,blksize:i,blocks:s,atime:h,birthtime:p,ctime:C,mtime:f,atimeMs:c,birthtimeMs:g,ctimeMs:l,mtimeMs:a,mode:B})}if(this.listings.has(t)){const e=this.stats.uid,t=this.stats.gid,r=0,A=512,n=0,o=this.stats.mtimeMs,i=this.stats.mtimeMs,s=this.stats.mtimeMs,a=this.stats.mtimeMs,c=new Date(o),g=new Date(i),l=new Date(s),h=new Date(a),p=493|u.QB;return Object.assign(new d.StatEntry,{uid:e,gid:t,size:r,blksize:A,blocks:n,atime:c,birthtime:g,ctime:l,mtime:h,atimeMs:o,birthtimeMs:i,ctimeMs:s,mtimeMs:a,mode:p})}throw new Error("Unreachable")}getUnixMode(e,t){if(-1===this.libzip.file.getExternalAttributes(this.zip,e,0,0,this.libzip.uint08S,this.libzip.uint32S))throw this.makeLibzipError(this.libzip.getError(this.zip));return this.libzip.getValue(this.libzip.uint08S,"i8")>>>0!==this.libzip.ZIP_OPSYS_UNIX?t:this.libzip.getValue(this.libzip.uint32S,"i32")>>>16}registerListing(e){let t=this.listings.get(e);if(t)return t;const r=this.registerListing(p.y1.dirname(e));return t=new Set,r.add(p.y1.basename(e)),this.listings.set(e,t),t}registerEntry(e,t){this.registerListing(p.y1.dirname(e)).add(p.y1.basename(e)),this.entries.set(e,t)}unregisterListing(e){this.listings.delete(e);const t=this.listings.get(p.y1.dirname(e));null==t||t.delete(p.y1.basename(e))}unregisterEntry(e){this.unregisterListing(e);const t=this.entries.get(e);this.entries.delete(e),void 0!==t&&(this.fileSources.delete(t),this.isSymbolicLink(t)&&this.symlinkCount--)}deleteEntry(e,t){this.unregisterEntry(e);if(-1===this.libzip.delete(this.zip,t))throw this.makeLibzipError(this.libzip.getError(this.zip))}resolveFilename(e,t,r=!0){if(!this.ready)throw h.Vw("archive closed, "+e);let A=p.y1.resolve(p.LZ.root,t);if("/"===A)return p.LZ.root;const n=this.entries.get(A);if(r&&void 0!==n){if(0!==this.symlinkCount&&this.isSymbolicLink(n)){const t=this.getFileSource(n).toString();return this.resolveFilename(e,p.y1.resolve(p.y1.dirname(A),t),!0)}return A}for(;;){const t=this.resolveFilename(e,p.y1.dirname(A),!0),n=this.listings.has(t),o=this.entries.has(t);if(!n&&!o)throw h.z6(e);if(!n)throw h.Ab(e);if(A=p.y1.resolve(t,p.y1.basename(A)),!r||0===this.symlinkCount)break;const i=this.libzip.name.locate(this.zip,A.slice(1));if(-1===i)break;if(!this.isSymbolicLink(i))break;{const e=this.getFileSource(i).toString();A=p.y1.resolve(p.y1.dirname(A),e)}}return A}allocateBuffer(e){Buffer.isBuffer(e)||(e=Buffer.from(e));const t=this.libzip.malloc(e.byteLength);if(!t)throw new Error("Couldn't allocate enough memory");return new Uint8Array(this.libzip.HEAPU8.buffer,t,e.byteLength).set(e),{buffer:t,byteLength:e.byteLength}}allocateUnattachedSource(e){const t=this.libzip.struct.errorS(),{buffer:r,byteLength:A}=this.allocateBuffer(e),n=this.libzip.source.fromUnattachedBuffer(r,A,0,!0,t);if(0===n)throw this.libzip.free(t),this.makeLibzipError(t);return n}allocateSource(e){const{buffer:t,byteLength:r}=this.allocateBuffer(e),A=this.libzip.source.fromBuffer(this.zip,t,r,0,!0);if(0===A)throw this.libzip.free(t),this.makeLibzipError(this.libzip.getError(this.zip));return A}setFileSource(e,t){const r=Buffer.isBuffer(t)?t:Buffer.from(t),A=p.y1.relative(p.LZ.root,e),n=this.allocateSource(t);try{const e=this.libzip.file.add(this.zip,A,n,this.libzip.ZIP_FL_OVERWRITE);if(-1===e)throw this.makeLibzipError(this.libzip.getError(this.zip));if("mixed"!==this.level){let t;t=0===this.level?this.libzip.ZIP_CM_STORE:this.libzip.ZIP_CM_DEFLATE;if(-1===this.libzip.file.setCompression(this.zip,e,0,t,this.level))throw this.makeLibzipError(this.libzip.getError(this.zip))}return this.fileSources.set(e,r),e}catch(e){throw this.libzip.source.free(n),e}}isSymbolicLink(e){if(0===this.symlinkCount)return!1;if(-1===this.libzip.file.getExternalAttributes(this.zip,e,0,0,this.libzip.uint08S,this.libzip.uint32S))throw this.makeLibzipError(this.libzip.getError(this.zip));if(this.libzip.getValue(this.libzip.uint08S,"i8")>>>0!==this.libzip.ZIP_OPSYS_UNIX)return!1;return(this.libzip.getValue(this.libzip.uint32S,"i32")>>>16&u.wK)===u.Zv}getFileSource(e,t={asyncDecompress:!1}){const r=this.fileSources.get(e);if(void 0!==r)return r;const A=this.libzip.struct.statS();if(-1===this.libzip.statIndex(this.zip,e,0,0,A))throw this.makeLibzipError(this.libzip.getError(this.zip));const n=this.libzip.struct.statCompSize(A),o=this.libzip.struct.statCompMethod(A),i=this.libzip.malloc(n);try{const r=this.libzip.fopenIndex(this.zip,e,0,this.libzip.ZIP_FL_COMPRESSED);if(0===r)throw this.makeLibzipError(this.libzip.getError(this.zip));try{const A=this.libzip.fread(r,i,n,0);if(-1===A)throw this.makeLibzipError(this.libzip.file.getError(r));if(An)throw new Error("Overread");const a=this.libzip.HEAPU8.subarray(i,i+n),c=Buffer.from(a);if(0===o)return this.fileSources.set(e,c),c;if(t.asyncDecompress)return new Promise((t,r)=>{s().inflateRaw(c,(A,n)=>{A?r(A):(this.fileSources.set(e,n),t(n))})});{const t=s().inflateRawSync(c);return this.fileSources.set(e,t),t}}finally{this.libzip.fclose(r)}}finally{this.libzip.free(i)}}async chmodPromise(e,t){return this.chmodSync(e,t)}chmodSync(e,t){if(this.readOnly)throw h.YW(`chmod '${e}'`);t&=493;const r=this.resolveFilename(`chmod '${e}'`,e,!1),A=this.entries.get(r);if(void 0===A)throw new Error(`Assertion failed: The entry should have been registered (${r})`);const n=-512&this.getUnixMode(A,0|u.Pe)|t;if(-1===this.libzip.file.setExternalAttributes(this.zip,A,0,0,this.libzip.ZIP_OPSYS_UNIX,n<<16))throw this.makeLibzipError(this.libzip.getError(this.zip))}async chownPromise(e,t,r){return this.chownSync(e,t,r)}chownSync(e,t,r){throw new Error("Unimplemented")}async renamePromise(e,t){return this.renameSync(e,t)}renameSync(e,t){throw new Error("Unimplemented")}async copyFilePromise(e,t,r){const{indexSource:A,indexDest:n,resolvedDestP:o}=this.prepareCopyFile(e,t,r),i=await this.getFileSource(A,{asyncDecompress:!0}),s=this.setFileSource(o,i);s!==n&&this.registerEntry(o,s)}copyFileSync(e,t,r=0){const{indexSource:A,indexDest:n,resolvedDestP:o}=this.prepareCopyFile(e,t,r),i=this.getFileSource(A),s=this.setFileSource(o,i);s!==n&&this.registerEntry(o,s)}prepareCopyFile(e,t,r=0){if(this.readOnly)throw h.YW(`copyfile '${e} -> '${t}'`);if(0!=(r&A.constants.COPYFILE_FICLONE_FORCE))throw h.bk("unsupported clone operation",`copyfile '${e}' -> ${t}'`);const n=this.resolveFilename(`copyfile '${e} -> ${t}'`,e),o=this.entries.get(n);if(void 0===o)throw h.hq(`copyfile '${e}' -> '${t}'`);const i=this.resolveFilename(`copyfile '${e}' -> ${t}'`,t),s=this.entries.get(i);if(0!=(r&(A.constants.COPYFILE_EXCL|A.constants.COPYFILE_FICLONE_FORCE))&&void 0!==s)throw h.cT(`copyfile '${e}' -> '${t}'`);return{indexSource:o,resolvedDestP:i,indexDest:s}}async appendFilePromise(e,t,r){if(this.readOnly)throw h.YW(`open '${e}'`);return void 0===r?r={flag:"a"}:"string"==typeof r?r={flag:"a",encoding:r}:void 0===r.flag&&(r={flag:"a",...r}),this.writeFilePromise(e,t,r)}appendFileSync(e,t,r={}){if(this.readOnly)throw h.YW(`open '${e}'`);return void 0===r?r={flag:"a"}:"string"==typeof r?r={flag:"a",encoding:r}:void 0===r.flag&&(r={flag:"a",...r}),this.writeFileSync(e,t,r)}async writeFilePromise(e,t,r){const{encoding:A,index:n,resolvedP:o}=this.prepareWriteFile(e,r);void 0!==n&&"object"==typeof r&&r.flag&&r.flag.includes("a")&&(t=Buffer.concat([await this.getFileSource(n,{asyncDecompress:!0}),Buffer.from(t)])),null!==A&&(t=t.toString(A));const i=this.setFileSource(o,t);i!==n&&this.registerEntry(o,i)}writeFileSync(e,t,r){const{encoding:A,index:n,resolvedP:o}=this.prepareWriteFile(e,r);void 0!==n&&"object"==typeof r&&r.flag&&r.flag.includes("a")&&(t=Buffer.concat([this.getFileSource(n),Buffer.from(t)])),null!==A&&(t=t.toString(A));const i=this.setFileSource(o,t);i!==n&&this.registerEntry(o,i)}prepareWriteFile(e,t){if("string"!=typeof e)throw h.Ch("read");if(this.readOnly)throw h.YW(`open '${e}'`);const r=this.resolveFilename(`open '${e}'`,e);if(this.listings.has(r))throw h.GA(`open '${e}'`);let A=null;"string"==typeof t?A=t:"object"==typeof t&&t.encoding&&(A=t.encoding);return{encoding:A,resolvedP:r,index:this.entries.get(r)}}async unlinkPromise(e){return this.unlinkSync(e)}unlinkSync(e){if(this.readOnly)throw h.YW(`unlink '${e}'`);const t=this.resolveFilename(`unlink '${e}'`,e);if(this.listings.has(t))throw h.GA(`unlink '${e}'`);const r=this.entries.get(t);if(void 0===r)throw h.hq(`unlink '${e}'`);this.deleteEntry(t,r)}async utimesPromise(e,t,r){return this.utimesSync(e,t,r)}utimesSync(e,t,r){if(this.readOnly)throw h.YW(`utimes '${e}'`);const A=this.resolveFilename(`utimes '${e}'`,e);this.utimesImpl(A,r)}async lutimesPromise(e,t,r){return this.lutimesSync(e,t,r)}lutimesSync(e,t,r){if(this.readOnly)throw h.YW(`lutimes '${e}'`);const A=this.resolveFilename(`utimes '${e}'`,e,!1);this.utimesImpl(A,r)}utimesImpl(e,t){this.listings.has(e)&&(this.entries.has(e)||this.hydrateDirectory(e));const r=this.entries.get(e);if(void 0===r)throw new Error("Unreachable");if(-1===this.libzip.file.setMtime(this.zip,r,0,function(e){if("string"==typeof e&&String(+e)===e)return+e;if(Number.isFinite(e))return e<0?Date.now()/1e3:e;if((0,o.isDate)(e))return e.getTime()/1e3;throw new Error("Invalid time")}(t),0))throw this.makeLibzipError(this.libzip.getError(this.zip))}async mkdirPromise(e,t){return this.mkdirSync(e,t)}mkdirSync(e,{mode:t=493,recursive:r=!1}={}){if(r)return void this.mkdirpSync(e,{chmod:t});if(this.readOnly)throw h.YW(`mkdir '${e}'`);const A=this.resolveFilename(`mkdir '${e}'`,e);if(this.entries.has(A)||this.listings.has(A))throw h.cT(`mkdir '${e}'`);this.hydrateDirectory(A),this.chmodSync(A,t)}async rmdirPromise(e,t){return this.rmdirSync(e,t)}rmdirSync(e,{recursive:t=!1}={}){if(this.readOnly)throw h.YW(`rmdir '${e}'`);if(t)return void this.removeSync(e);const r=this.resolveFilename(`rmdir '${e}'`,e),A=this.listings.get(r);if(!A)throw h.Ab(`rmdir '${e}'`);if(A.size>0)throw h.re(`rmdir '${e}'`);const n=this.entries.get(r);if(void 0===n)throw h.hq(`rmdir '${e}'`);this.deleteEntry(e,n)}hydrateDirectory(e){const t=this.libzip.dir.add(this.zip,p.y1.relative(p.LZ.root,e));if(-1===t)throw this.makeLibzipError(this.libzip.getError(this.zip));return this.registerListing(e),this.registerEntry(e,t),t}async linkPromise(e,t){return this.linkSync(e,t)}linkSync(e,t){throw h.Hs(`link '${e}' -> '${t}'`)}async symlinkPromise(e,t){return this.symlinkSync(e,t)}symlinkSync(e,t){if(this.readOnly)throw h.YW(`symlink '${e}' -> '${t}'`);const r=this.resolveFilename(`symlink '${e}' -> '${t}'`,t);if(this.listings.has(r))throw h.GA(`symlink '${e}' -> '${t}'`);if(this.entries.has(r))throw h.cT(`symlink '${e}' -> '${t}'`);const A=this.setFileSource(r,e);this.registerEntry(r,A);if(-1===this.libzip.file.setExternalAttributes(this.zip,A,0,0,this.libzip.ZIP_OPSYS_UNIX,(511|u.Zv)<<16))throw this.makeLibzipError(this.libzip.getError(this.zip));this.symlinkCount+=1}async readFilePromise(e,t){"object"==typeof t&&(t=t?t.encoding:void 0);const r=await this.readFileBuffer(e,{asyncDecompress:!0});return t?r.toString(t):r}readFileSync(e,t){"object"==typeof t&&(t=t?t.encoding:void 0);const r=this.readFileBuffer(e);return t?r.toString(t):r}readFileBuffer(e,t={asyncDecompress:!1}){if("string"!=typeof e)throw h.Ch("read");const r=this.resolveFilename(`open '${e}'`,e);if(!this.entries.has(r)&&!this.listings.has(r))throw h.z6(`open '${e}'`);if("/"===e[e.length-1]&&!this.listings.has(r))throw h.Ab(`open '${e}'`);if(this.listings.has(r))throw h.GA("read");const A=this.entries.get(r);if(void 0===A)throw new Error("Unreachable");return this.getFileSource(A,t)}async readdirPromise(e,{withFileTypes:t}={}){return this.readdirSync(e,{withFileTypes:t})}readdirSync(e,{withFileTypes:t}={}){const r=this.resolveFilename(`scandir '${e}'`,e);if(!this.entries.has(r)&&!this.listings.has(r))throw h.z6(`scandir '${e}'`);const A=this.listings.get(r);if(!A)throw h.Ab(`scandir '${e}'`);const n=[...A];return t?n.map(t=>Object.assign(this.statImpl("lstat",p.y1.join(e,t)),{name:t})):n}async readlinkPromise(e){const t=this.prepareReadlink(e);return(await this.getFileSource(t,{asyncDecompress:!0})).toString()}readlinkSync(e){const t=this.prepareReadlink(e);return this.getFileSource(t).toString()}prepareReadlink(e){const t=this.resolveFilename(`readlink '${e}'`,e,!1);if(!this.entries.has(t)&&!this.listings.has(t))throw h.z6(`readlink '${e}'`);if("/"===e[e.length-1]&&!this.listings.has(t))throw h.Ab(`open '${e}'`);if(this.listings.has(t))throw h.hq(`readlink '${e}'`);const r=this.entries.get(t);if(void 0===r)throw new Error("Unreachable");if(!this.isSymbolicLink(r))throw h.hq(`readlink '${e}'`);return r}async truncatePromise(e,t=0){const r=this.resolveFilename(`open '${e}'`,e),A=this.entries.get(r);if(void 0===A)throw h.hq(`open '${e}'`);const n=await this.getFileSource(A,{asyncDecompress:!0}),o=Buffer.alloc(t,0);return n.copy(o),await this.writeFilePromise(e,o)}truncateSync(e,t=0){const r=this.resolveFilename(`open '${e}'`,e),A=this.entries.get(r);if(void 0===A)throw h.hq(`open '${e}'`);const n=this.getFileSource(A),o=Buffer.alloc(t,0);return n.copy(o),this.writeFileSync(e,o)}watch(e,t,r){let A;switch(typeof t){case"function":case"string":case"undefined":A=!0;break;default:({persistent:A=!0}=t)}if(!A)return{on:()=>{},close:()=>{}};const n=setInterval(()=>{},864e5);return{on:()=>{},close:()=>{clearInterval(n)}}}watchFile(e,t,r){const A=this.resolveFilename(`open '${e}'`,e);return(0,l._x)(this,A,t,r)}unwatchFile(e,t){const r=this.resolveFilename(`open '${e}'`,e);return(0,l.nd)(this,r,t)}}},53660:(e,t,r)=>{"use strict";r.d(t,{A:()=>l});var A=r(35747),n=r(5944),o=r(78420),i=r(90739),s=r(38783),a=r(46009);const c=2147483648,g=/.*?(?await this.baseFs.openPromise(e,t,r),async(e,{subPath:A})=>this.remapFd(e,await e.openPromise(A,t,r)))}openSync(e,t,r){return this.makeCallSync(e,()=>this.baseFs.openSync(e,t,r),(e,{subPath:A})=>this.remapFd(e,e.openSync(A,t,r)))}async opendirPromise(e,t){return await this.makeCallPromise(e,async()=>await this.baseFs.opendirPromise(e,t),async(e,{subPath:r})=>await e.opendirPromise(r,t),{requireSubpath:!1})}opendirSync(e,t){return this.makeCallSync(e,()=>this.baseFs.opendirSync(e,t),(e,{subPath:r})=>e.opendirSync(r,t),{requireSubpath:!1})}async readPromise(e,t,r,A,n){if(0==(e&c))return await this.baseFs.readPromise(e,t,r,A,n);const o=this.fdMap.get(e);if(void 0===o)throw Object.assign(new Error("EBADF: bad file descriptor, read"),{code:"EBADF"});const[i,s]=o;return await i.readPromise(s,t,r,A,n)}readSync(e,t,r,A,n){if(0==(e&c))return this.baseFs.readSync(e,t,r,A,n);const o=this.fdMap.get(e);if(void 0===o)throw Object.assign(new Error("EBADF: bad file descriptor, read"),{code:"EBADF"});const[i,s]=o;return i.readSync(s,t,r,A,n)}async writePromise(e,t,r,A,n){if(0==(e&c))return"string"==typeof t?await this.baseFs.writePromise(e,t,r):await this.baseFs.writePromise(e,t,r,A,n);const o=this.fdMap.get(e);if(void 0===o)throw Object.assign(new Error("EBADF: bad file descriptor, write"),{code:"EBADF"});const[i,s]=o;return"string"==typeof t?await i.writePromise(s,t,r):await i.writePromise(s,t,r,A,n)}writeSync(e,t,r,A,n){if(0==(e&c))return"string"==typeof t?this.baseFs.writeSync(e,t,r):this.baseFs.writeSync(e,t,r,A,n);const o=this.fdMap.get(e);if(void 0===o)throw Object.assign(new Error("EBADF: bad file descriptor, write"),{code:"EBADF"});const[i,s]=o;return"string"==typeof t?i.writeSync(s,t,r):i.writeSync(s,t,r,A,n)}async closePromise(e){if(0==(e&c))return await this.baseFs.closePromise(e);const t=this.fdMap.get(e);if(void 0===t)throw Object.assign(new Error("EBADF: bad file descriptor, close"),{code:"EBADF"});this.fdMap.delete(e);const[r,A]=t;return await r.closePromise(A)}closeSync(e){if(0==(e&c))return this.baseFs.closeSync(e);const t=this.fdMap.get(e);if(void 0===t)throw Object.assign(new Error("EBADF: bad file descriptor, close"),{code:"EBADF"});this.fdMap.delete(e);const[r,A]=t;return r.closeSync(A)}createReadStream(e,t){return null===e?this.baseFs.createReadStream(e,t):this.makeCallSync(e,()=>this.baseFs.createReadStream(e,t),(e,{subPath:r})=>e.createReadStream(r,t))}createWriteStream(e,t){return null===e?this.baseFs.createWriteStream(e,t):this.makeCallSync(e,()=>this.baseFs.createWriteStream(e,t),(e,{subPath:r})=>e.createWriteStream(r,t))}async realpathPromise(e){return await this.makeCallPromise(e,async()=>await this.baseFs.realpathPromise(e),async(e,{archivePath:t,subPath:r})=>{let A=this.realPaths.get(t);return void 0===A&&(A=await this.baseFs.realpathPromise(t),this.realPaths.set(t,A)),this.pathUtils.join(A,this.pathUtils.relative(a.LZ.root,await e.realpathPromise(r)))})}realpathSync(e){return this.makeCallSync(e,()=>this.baseFs.realpathSync(e),(e,{archivePath:t,subPath:r})=>{let A=this.realPaths.get(t);return void 0===A&&(A=this.baseFs.realpathSync(t),this.realPaths.set(t,A)),this.pathUtils.join(A,this.pathUtils.relative(a.LZ.root,e.realpathSync(r)))})}async existsPromise(e){return await this.makeCallPromise(e,async()=>await this.baseFs.existsPromise(e),async(e,{subPath:t})=>await e.existsPromise(t))}existsSync(e){return this.makeCallSync(e,()=>this.baseFs.existsSync(e),(e,{subPath:t})=>e.existsSync(t))}async accessPromise(e,t){return await this.makeCallPromise(e,async()=>await this.baseFs.accessPromise(e,t),async(e,{subPath:r})=>await e.accessPromise(r,t))}accessSync(e,t){return this.makeCallSync(e,()=>this.baseFs.accessSync(e,t),(e,{subPath:r})=>e.accessSync(r,t))}async statPromise(e){return await this.makeCallPromise(e,async()=>await this.baseFs.statPromise(e),async(e,{subPath:t})=>await e.statPromise(t))}statSync(e){return this.makeCallSync(e,()=>this.baseFs.statSync(e),(e,{subPath:t})=>e.statSync(t))}async lstatPromise(e){return await this.makeCallPromise(e,async()=>await this.baseFs.lstatPromise(e),async(e,{subPath:t})=>await e.lstatPromise(t))}lstatSync(e){return this.makeCallSync(e,()=>this.baseFs.lstatSync(e),(e,{subPath:t})=>e.lstatSync(t))}async chmodPromise(e,t){return await this.makeCallPromise(e,async()=>await this.baseFs.chmodPromise(e,t),async(e,{subPath:r})=>await e.chmodPromise(r,t))}chmodSync(e,t){return this.makeCallSync(e,()=>this.baseFs.chmodSync(e,t),(e,{subPath:r})=>e.chmodSync(r,t))}async chownPromise(e,t,r){return await this.makeCallPromise(e,async()=>await this.baseFs.chownPromise(e,t,r),async(e,{subPath:A})=>await e.chownPromise(A,t,r))}chownSync(e,t,r){return this.makeCallSync(e,()=>this.baseFs.chownSync(e,t,r),(e,{subPath:A})=>e.chownSync(A,t,r))}async renamePromise(e,t){return await this.makeCallPromise(e,async()=>await this.makeCallPromise(t,async()=>await this.baseFs.renamePromise(e,t),async()=>{throw Object.assign(new Error("EEXDEV: cross-device link not permitted"),{code:"EEXDEV"})}),async(e,{subPath:r})=>await this.makeCallPromise(t,async()=>{throw Object.assign(new Error("EEXDEV: cross-device link not permitted"),{code:"EEXDEV"})},async(t,{subPath:A})=>{if(e!==t)throw Object.assign(new Error("EEXDEV: cross-device link not permitted"),{code:"EEXDEV"});return await e.renamePromise(r,A)}))}renameSync(e,t){return this.makeCallSync(e,()=>this.makeCallSync(t,()=>this.baseFs.renameSync(e,t),async()=>{throw Object.assign(new Error("EEXDEV: cross-device link not permitted"),{code:"EEXDEV"})}),(e,{subPath:r})=>this.makeCallSync(t,()=>{throw Object.assign(new Error("EEXDEV: cross-device link not permitted"),{code:"EEXDEV"})},(t,{subPath:A})=>{if(e!==t)throw Object.assign(new Error("EEXDEV: cross-device link not permitted"),{code:"EEXDEV"});return e.renameSync(r,A)}))}async copyFilePromise(e,t,r=0){const n=async(e,t,n,o)=>{if(0!=(r&A.constants.COPYFILE_FICLONE_FORCE))throw Object.assign(new Error(`EXDEV: cross-device clone not permitted, copyfile '${t}' -> ${o}'`),{code:"EXDEV"});if(r&A.constants.COPYFILE_EXCL&&await this.existsPromise(t))throw Object.assign(new Error(`EEXIST: file already exists, copyfile '${t}' -> '${o}'`),{code:"EEXIST"});let i;try{i=await e.readFilePromise(t)}catch(e){throw Object.assign(new Error(`EINVAL: invalid argument, copyfile '${t}' -> '${o}'`),{code:"EINVAL"})}await n.writeFilePromise(o,i)};return await this.makeCallPromise(e,async()=>await this.makeCallPromise(t,async()=>await this.baseFs.copyFilePromise(e,t,r),async(t,{subPath:r})=>await n(this.baseFs,e,t,r)),async(e,{subPath:A})=>await this.makeCallPromise(t,async()=>await n(e,A,this.baseFs,t),async(t,{subPath:o})=>e!==t?await n(e,A,t,o):await e.copyFilePromise(A,o,r)))}copyFileSync(e,t,r=0){const n=(e,t,n,o)=>{if(0!=(r&A.constants.COPYFILE_FICLONE_FORCE))throw Object.assign(new Error(`EXDEV: cross-device clone not permitted, copyfile '${t}' -> ${o}'`),{code:"EXDEV"});if(r&A.constants.COPYFILE_EXCL&&this.existsSync(t))throw Object.assign(new Error(`EEXIST: file already exists, copyfile '${t}' -> '${o}'`),{code:"EEXIST"});let i;try{i=e.readFileSync(t)}catch(e){throw Object.assign(new Error(`EINVAL: invalid argument, copyfile '${t}' -> '${o}'`),{code:"EINVAL"})}n.writeFileSync(o,i)};return this.makeCallSync(e,()=>this.makeCallSync(t,()=>this.baseFs.copyFileSync(e,t,r),(t,{subPath:r})=>n(this.baseFs,e,t,r)),(e,{subPath:A})=>this.makeCallSync(t,()=>n(e,A,this.baseFs,t),(t,{subPath:o})=>e!==t?n(e,A,t,o):e.copyFileSync(A,o,r)))}async appendFilePromise(e,t,r){return await this.makeCallPromise(e,async()=>await this.baseFs.appendFilePromise(e,t,r),async(e,{subPath:A})=>await e.appendFilePromise(A,t,r))}appendFileSync(e,t,r){return this.makeCallSync(e,()=>this.baseFs.appendFileSync(e,t,r),(e,{subPath:A})=>e.appendFileSync(A,t,r))}async writeFilePromise(e,t,r){return await this.makeCallPromise(e,async()=>await this.baseFs.writeFilePromise(e,t,r),async(e,{subPath:A})=>await e.writeFilePromise(A,t,r))}writeFileSync(e,t,r){return this.makeCallSync(e,()=>this.baseFs.writeFileSync(e,t,r),(e,{subPath:A})=>e.writeFileSync(A,t,r))}async unlinkPromise(e){return await this.makeCallPromise(e,async()=>await this.baseFs.unlinkPromise(e),async(e,{subPath:t})=>await e.unlinkPromise(t))}unlinkSync(e){return this.makeCallSync(e,()=>this.baseFs.unlinkSync(e),(e,{subPath:t})=>e.unlinkSync(t))}async utimesPromise(e,t,r){return await this.makeCallPromise(e,async()=>await this.baseFs.utimesPromise(e,t,r),async(e,{subPath:A})=>await e.utimesPromise(A,t,r))}utimesSync(e,t,r){return this.makeCallSync(e,()=>this.baseFs.utimesSync(e,t,r),(e,{subPath:A})=>e.utimesSync(A,t,r))}async mkdirPromise(e,t){return await this.makeCallPromise(e,async()=>await this.baseFs.mkdirPromise(e,t),async(e,{subPath:r})=>await e.mkdirPromise(r,t))}mkdirSync(e,t){return this.makeCallSync(e,()=>this.baseFs.mkdirSync(e,t),(e,{subPath:r})=>e.mkdirSync(r,t))}async rmdirPromise(e,t){return await this.makeCallPromise(e,async()=>await this.baseFs.rmdirPromise(e,t),async(e,{subPath:r})=>await e.rmdirPromise(r,t))}rmdirSync(e,t){return this.makeCallSync(e,()=>this.baseFs.rmdirSync(e,t),(e,{subPath:r})=>e.rmdirSync(r,t))}async linkPromise(e,t){return await this.makeCallPromise(t,async()=>await this.baseFs.linkPromise(e,t),async(t,{subPath:r})=>await t.linkPromise(e,r))}linkSync(e,t){return this.makeCallSync(t,()=>this.baseFs.linkSync(e,t),(t,{subPath:r})=>t.linkSync(e,r))}async symlinkPromise(e,t,r){return await this.makeCallPromise(t,async()=>await this.baseFs.symlinkPromise(e,t,r),async(t,{subPath:r})=>await t.symlinkPromise(e,r))}symlinkSync(e,t,r){return this.makeCallSync(t,()=>this.baseFs.symlinkSync(e,t,r),(t,{subPath:r})=>t.symlinkSync(e,r))}async readFilePromise(e,t){return this.makeCallPromise(e,async()=>{switch(t){case"utf8":default:return await this.baseFs.readFilePromise(e,t)}},async(e,{subPath:r})=>await e.readFilePromise(r,t))}readFileSync(e,t){return this.makeCallSync(e,()=>{switch(t){case"utf8":default:return this.baseFs.readFileSync(e,t)}},(e,{subPath:r})=>e.readFileSync(r,t))}async readdirPromise(e,{withFileTypes:t}={}){return await this.makeCallPromise(e,async()=>await this.baseFs.readdirPromise(e,{withFileTypes:t}),async(e,{subPath:r})=>await e.readdirPromise(r,{withFileTypes:t}),{requireSubpath:!1})}readdirSync(e,{withFileTypes:t}={}){return this.makeCallSync(e,()=>this.baseFs.readdirSync(e,{withFileTypes:t}),(e,{subPath:r})=>e.readdirSync(r,{withFileTypes:t}),{requireSubpath:!1})}async readlinkPromise(e){return await this.makeCallPromise(e,async()=>await this.baseFs.readlinkPromise(e),async(e,{subPath:t})=>await e.readlinkPromise(t))}readlinkSync(e){return this.makeCallSync(e,()=>this.baseFs.readlinkSync(e),(e,{subPath:t})=>e.readlinkSync(t))}async truncatePromise(e,t){return await this.makeCallPromise(e,async()=>await this.baseFs.truncatePromise(e,t),async(e,{subPath:r})=>await e.truncatePromise(r,t))}truncateSync(e,t){return this.makeCallSync(e,()=>this.baseFs.truncateSync(e,t),(e,{subPath:r})=>e.truncateSync(r,t))}watch(e,t,r){return this.makeCallSync(e,()=>this.baseFs.watch(e,t,r),(e,{subPath:A})=>e.watch(A,t,r))}watchFile(e,t,r){return this.makeCallSync(e,()=>this.baseFs.watchFile(e,t,r),()=>(0,s._x)(this,e,t,r))}unwatchFile(e,t){return this.makeCallSync(e,()=>this.baseFs.unwatchFile(e,t),()=>(0,s.nd)(this,e,t))}async makeCallPromise(e,t,r,{requireSubpath:A=!0}={}){if("string"!=typeof e)return await t();const n=this.resolve(e),o=this.findZip(n);return o?A&&"/"===o.subPath?await t():await this.getZipPromise(o.archivePath,async e=>await r(e,o)):await t()}makeCallSync(e,t,r,{requireSubpath:A=!0}={}){if("string"!=typeof e)return t();const n=this.resolve(e),o=this.findZip(n);return o?A&&"/"===o.subPath?t():this.getZipSync(o.archivePath,e=>r(e,o)):t()}findZip(e){if(this.filter&&!this.filter.test(e))return null;let t="";for(;;){const r=g.exec(e.substr(t.length));if(!r)return null;if(t=this.pathUtils.join(t,r[0]),!1===this.isZip.has(t)){if(this.notZip.has(t))continue;try{if(!this.baseFs.lstatSync(t).isFile()){this.notZip.add(t);continue}}catch(e){return null}this.isZip.add(t)}return{archivePath:t,subPath:this.pathUtils.join(a.LZ.root,e.substr(t.length))}}}limitOpenFiles(e){if(null===this.zipInstances)return;const t=Date.now();let r=t+this.maxAge,A=null===e?0:this.zipInstances.size-e;for(const[n,{zipFs:o,expiresAt:i,refCount:s}]of this.zipInstances.entries())if(0===s&&!o.hasOpenFileHandles())if(t>=i)o.saveAndClose(),this.zipInstances.delete(n),A-=1;else{if(null===e||A<=0){r=i;break}o.saveAndClose(),this.zipInstances.delete(n),A-=1}null===this.limitOpenFilesTimeout&&(null===e&&this.zipInstances.size>0||null!==e)&&(this.limitOpenFilesTimeout=setTimeout(()=>{this.limitOpenFilesTimeout=null,this.limitOpenFiles(null)},r-t).unref())}async getZipPromise(e,t){const r=async()=>({baseFs:this.baseFs,libzip:this.libzip,readOnly:this.readOnlyArchives,stats:await this.baseFs.statPromise(e)});if(this.zipInstances){let A=this.zipInstances.get(e);if(!A){const t=await r();A=this.zipInstances.get(e),A||(A={zipFs:new i.d(e,t),expiresAt:0,refCount:0})}this.zipInstances.delete(e),this.limitOpenFiles(this.maxOpenFiles-1),this.zipInstances.set(e,A),A.expiresAt=Date.now()+this.maxAge,A.refCount+=1;try{return await t(A.zipFs)}finally{A.refCount-=1}}else{const A=new i.d(e,await r());try{return await t(A)}finally{A.saveAndClose()}}}getZipSync(e,t){const r=()=>({baseFs:this.baseFs,libzip:this.libzip,readOnly:this.readOnlyArchives,stats:this.baseFs.statSync(e)});if(this.zipInstances){let A=this.zipInstances.get(e);return A||(A={zipFs:new i.d(e,r()),expiresAt:0,refCount:0}),this.zipInstances.delete(e),this.limitOpenFiles(this.maxOpenFiles-1),this.zipInstances.set(e,A),A.expiresAt=Date.now()+this.maxAge,t(A.zipFs)}{const A=new i.d(e,r());try{return t(A)}finally{A.saveAndClose()}}}}},19697:(e,t,r)=>{"use strict";r.d(t,{a:()=>o});var A=r(26984);class n{constructor(e,t,r={}){this.path=e,this.nextDirent=t,this.opts=r,this.closed=!1}throwIfClosed(){if(this.closed)throw A.Xh()}async*[Symbol.asyncIterator](){try{let e;for(;null!==(e=await this.read());)yield e}finally{await this.close()}}read(e){const t=this.readSync();return void 0!==e?e(null,t):Promise.resolve(t)}readSync(){return this.throwIfClosed(),this.nextDirent()}close(e){return this.closeSync(),void 0!==e?e(null):Promise.resolve()}closeSync(){var e,t;this.throwIfClosed(),null===(t=(e=this.opts).onClose)||void 0===t||t.call(e),this.closed=!0}}function o(e,t,r,A){return new n(t,()=>{const A=r.shift();return void 0===A?null:Object.assign(e.statSync(e.pathUtils.join(t,A)),{name:A})},A)}},38783:(e,t,r)=>{"use strict";r.d(t,{L:()=>u,nd:()=>l,_x:()=>g});var A,n,o=r(28614),i=r(65760);function s(e,t){if(e!==t)throw new Error(`Invalid StatWatcher status: expected '${t}', got '${e}'`)}!function(e){e.Change="change",e.Stop="stop"}(A||(A={})),function(e){e.Ready="ready",e.Running="running",e.Stopped="stopped"}(n||(n={}));class a extends o.EventEmitter{constructor(e,t,{bigint:r=!1}={}){super(),this.status=n.Ready,this.changeListeners=new Map,this.startTimeout=null,this.fakeFs=e,this.path=t,this.bigint=r,this.lastStats=this.stat()}static create(e,t,r){const A=new a(e,t,r);return A.start(),A}start(){s(this.status,n.Ready),this.status=n.Running,this.startTimeout=setTimeout(()=>{this.startTimeout=null,this.fakeFs.existsSync(this.path)||this.emit(A.Change,this.lastStats,this.lastStats)},3)}stop(){s(this.status,n.Running),this.status=n.Stopped,null!==this.startTimeout&&(clearTimeout(this.startTimeout),this.startTimeout=null),this.emit(A.Stop)}stat(){try{return this.fakeFs.statSync(this.path)}catch(e){if("ENOENT"===e.code)return i.makeEmptyStats();throw e}}makeInterval(e){const t=setInterval(()=>{const e=this.stat(),t=this.lastStats;i.areStatsEqual(e,t)||(this.lastStats=e,this.emit(A.Change,e,t))},e.interval);return e.persistent?t:t.unref()}registerChangeListener(e,t){this.addListener(A.Change,e),this.changeListeners.set(e,this.makeInterval(t))}unregisterChangeListener(e){this.removeListener(A.Change,e);const t=this.changeListeners.get(e);void 0!==t&&clearInterval(t),this.changeListeners.delete(e)}unregisterAllChangeListeners(){for(const e of this.changeListeners.keys())this.unregisterChangeListener(e)}hasChangeListeners(){return this.changeListeners.size>0}ref(){for(const e of this.changeListeners.values())e.ref();return this}unref(){for(const e of this.changeListeners.values())e.unref();return this}}const c=new WeakMap;function g(e,t,r,A){let n,o,i,s;switch(typeof r){case"function":n=!1,o=!0,i=5007,s=r;break;default:({bigint:n=!1,persistent:o=!0,interval:i=5007}=r),s=A}let g=c.get(e);void 0===g&&c.set(e,g=new Map);let l=g.get(t);return void 0===l&&(l=a.create(e,t,{bigint:n}),g.set(t,l)),l.registerChangeListener(s,{persistent:o,interval:i}),l}function l(e,t,r){const A=c.get(e);if(void 0===A)return;const n=A.get(t);void 0!==n&&(void 0===r?n.unregisterAllChangeListeners():n.unregisterChangeListener(r),n.hasChangeListeners()||(n.stop(),A.delete(t)))}function u(e){const t=c.get(e);if(void 0!==t)for(const r of t.keys())l(e,r)}},22004:(e,t,r)=>{"use strict";r.d(t,{wK:()=>A,QB:()=>n,Pe:()=>o,Zv:()=>i});const A=61440,n=16384,o=32768,i=40960},26984:(e,t,r)=>{"use strict";function A(e,t){return Object.assign(new Error(`${e}: ${t}`),{code:e})}function n(e){return A("EBUSY",e)}function o(e,t){return A("ENOSYS",`${e}, ${t}`)}function i(e){return A("EINVAL","invalid argument, "+e)}function s(e){return A("EBADF","bad file descriptor, "+e)}function a(e){return A("ENOENT","no such file or directory, "+e)}function c(e){return A("ENOTDIR","not a directory, "+e)}function g(e){return A("EISDIR","illegal operation on a directory, "+e)}function l(e){return A("EEXIST","file already exists, "+e)}function u(e){return A("EROFS","read-only filesystem, "+e)}function h(e){return A("ENOTEMPTY","directory not empty, "+e)}function p(e){return A("EOPNOTSUPP","operation not supported, "+e)}function d(){return A("ERR_DIR_CLOSED","Directory handle was closed")}r.d(t,{Vw:()=>n,bk:()=>o,hq:()=>i,Ch:()=>s,z6:()=>a,Ab:()=>c,GA:()=>g,cT:()=>l,YW:()=>u,re:()=>h,Hs:()=>p,Xh:()=>d,Yn:()=>C});class C extends Error{constructor(e,t){super(e),this.name="Libzip Error",this.code=t}}},43896:(e,t,r)=>{"use strict";r.r(t),r.d(t,{AliasFS:()=>u.K,CwdFS:()=>h.M,DEFAULT_COMPRESSION_LEVEL:()=>l.k,FakeFS:()=>g.uY,Filename:()=>s.QS,JailFS:()=>p.n,LazyFS:()=>d.v,NoFS:()=>f,NodeFS:()=>i.S,PortablePath:()=>s.LZ,PosixFS:()=>I.i,ProxiedFS:()=>E.p,VirtualFS:()=>B.p,ZipFS:()=>l.d,ZipOpenFS:()=>y.A,extendFs:()=>Q,normalizeLineEndings:()=>g.qH,npath:()=>s.cS,opendir:()=>c.a,patchFs:()=>w,ppath:()=>s.y1,statUtils:()=>a,toFilename:()=>s.Zu,xfs:()=>S});var A=r(12087),n=r.n(A),o=r(31669),i=r(78420),s=r(46009),a=r(65760),c=r(19697),g=r(5944),l=r(90739),u=r(14626),h=r(75448),p=r(10489),d=r(15037);const C=()=>Object.assign(new Error("ENOSYS: unsupported filesystem access"),{code:"ENOSYS"});class f extends g.uY{constructor(){super(s.y1)}getExtractHint(){throw C()}getRealPath(){throw C()}resolve(){throw C()}async openPromise(){throw C()}openSync(){throw C()}async opendirPromise(){throw C()}opendirSync(){throw C()}async readPromise(){throw C()}readSync(){throw C()}async writePromise(){throw C()}writeSync(){throw C()}async closePromise(){throw C()}closeSync(){throw C()}createWriteStream(){throw C()}createReadStream(){throw C()}async realpathPromise(){throw C()}realpathSync(){throw C()}async readdirPromise(){throw C()}readdirSync(){throw C()}async existsPromise(e){throw C()}existsSync(e){throw C()}async accessPromise(){throw C()}accessSync(){throw C()}async statPromise(){throw C()}statSync(){throw C()}async lstatPromise(e){throw C()}lstatSync(e){throw C()}async chmodPromise(){throw C()}chmodSync(){throw C()}async chownPromise(){throw C()}chownSync(){throw C()}async mkdirPromise(){throw C()}mkdirSync(){throw C()}async rmdirPromise(){throw C()}rmdirSync(){throw C()}async linkPromise(){throw C()}linkSync(){throw C()}async symlinkPromise(){throw C()}symlinkSync(){throw C()}async renamePromise(){throw C()}renameSync(){throw C()}async copyFilePromise(){throw C()}copyFileSync(){throw C()}async appendFilePromise(){throw C()}appendFileSync(){throw C()}async writeFilePromise(){throw C()}writeFileSync(){throw C()}async unlinkPromise(){throw C()}unlinkSync(){throw C()}async utimesPromise(){throw C()}utimesSync(){throw C()}async readFilePromise(){throw C()}readFileSync(){throw C()}async readlinkPromise(){throw C()}readlinkSync(){throw C()}async truncatePromise(){throw C()}truncateSync(){throw C()}watch(){throw C()}watchFile(){throw C()}unwatchFile(){throw C()}}f.instance=new f;var I=r(39725),E=r(42096),B=r(17674),y=r(53660);function m(e){const t=s.cS.toPortablePath(n().tmpdir()),r=Math.ceil(4294967296*Math.random()).toString(16).padStart(8,"0");return s.y1.join(t,`${e}${r}`)}function w(e,t){const r=new Set(["accessSync","appendFileSync","createReadStream","chmodSync","chownSync","closeSync","copyFileSync","linkSync","lstatSync","lutimesSync","mkdirSync","openSync","opendirSync","readSync","readlinkSync","readFileSync","readdirSync","readlinkSync","realpathSync","renameSync","rmdirSync","statSync","symlinkSync","truncateSync","unlinkSync","unwatchFile","utimesSync","watch","watchFile","writeFileSync","writeSync"]),A=new Set(["accessPromise","appendFilePromise","chmodPromise","chownPromise","closePromise","copyFilePromise","linkPromise","lstatPromise","lutimesPromise","mkdirPromise","openPromise","opendirPromise","readdirPromise","realpathPromise","readFilePromise","readdirPromise","readlinkPromise","renamePromise","rmdirPromise","statPromise","symlinkPromise","truncatePromise","unlinkPromise","utimesPromise","writeFilePromise","writeSync"]),n=new Set(["appendFilePromise","chmodPromise","chownPromise","closePromise","readPromise","readFilePromise","statPromise","truncatePromise","utimesPromise","writePromise","writeFilePromise"]),i=(e,t,r)=>{const A=e[t];e[t]=r,void 0!==(null==A?void 0:A[o.promisify.custom])&&(r[o.promisify.custom]=A[o.promisify.custom])};i(e,"exists",(e,...r)=>{const A="function"==typeof r[r.length-1]?r.pop():()=>{};process.nextTick(()=>{t.existsPromise(e).then(e=>{A(e)},()=>{A(!1)})})}),i(e,"read",(e,r,...A)=>{const n="function"==typeof A[A.length-1]?A.pop():()=>{};process.nextTick(()=>{t.readPromise(e,r,...A).then(e=>{n(null,e,r)},e=>{n(e)})})});for(const r of A){const A=r.replace(/Promise$/,"");if(void 0===e[A])continue;const n=t[r];if(void 0===n)continue;i(e,A,(...e)=>{const r="function"==typeof e[e.length-1]?e.pop():()=>{};process.nextTick(()=>{n.apply(t,e).then(e=>{r(null,e)},e=>{r(e)})})})}e.realpath.native=e.realpath,i(e,"existsSync",e=>{try{return t.existsSync(e)}catch(e){return!1}});for(const A of r){const r=A;if(void 0===e[r])continue;const n=t[A];void 0!==n&&i(e,r,n.bind(t))}e.realpathSync.native=e.realpathSync;{const r=process.emitWarning;let o;process.emitWarning=()=>{};try{o=e.promises}finally{process.emitWarning=r}if(void 0!==o){for(const e of A){const r=e.replace(/Promise$/,"");if(void 0===o[r])continue;const A=t[e];void 0!==A&&("open"!==e&&i(o,r,A.bind(t)))}class e{constructor(e){this.fd=e}}for(const r of n){const A=r.replace(/Promise$/,""),n=t[r];void 0!==n&&i(e.prototype,A,(function(...e){return n.call(t,this.fd,...e)}))}i(o,"open",async(...r)=>{const A=await t.openPromise(...r);return new e(A)})}}e.read[o.promisify.custom]=async(e,r,...A)=>{const n=t.readPromise(e,r,...A);return{bytesRead:await n,buffer:r}}}function Q(e,t){const r=Object.create(e);return w(r,t),r}const D=new Set;let b=!1;function v(){b||(b=!0,process.once("exit",()=>{S.rmtempSync()}))}const S=Object.assign(new i.S,{detachTemp(e){D.delete(e)},mktempSync(e){for(v();;){const t=m("xfs-");try{this.mkdirSync(t)}catch(e){if("EEXIST"===e.code)continue;throw e}const r=this.realpathSync(t);if(D.add(r),void 0===e)return t;try{return e(r)}finally{if(D.has(r)){D.delete(r);try{this.removeSync(r)}catch(e){}}}}},async mktempPromise(e){for(v();;){const t=m("xfs-");try{await this.mkdirPromise(t)}catch(e){if("EEXIST"===e.code)continue;throw e}const r=await this.realpathPromise(t);if(D.add(r),void 0===e)return r;try{return await e(r)}finally{if(D.has(r)){D.delete(r);try{await this.removePromise(r)}catch(e){}}}}},async rmtempPromise(){await Promise.all(Array.from(D.values()).map(async e=>{try{await S.removePromise(e,{maxRetries:0}),D.delete(e)}catch(e){}}))},rmtempSync(){for(const e of D)try{S.removeSync(e),D.delete(e)}catch(e){}}})},46009:(e,t,r)=>{"use strict";r.d(t,{LZ:()=>i,QS:()=>s,cS:()=>a,y1:()=>c,CI:()=>f,Zu:()=>I});var A,n=r(85622),o=r.n(n);!function(e){e[e.File=0]="File",e[e.Portable=1]="Portable",e[e.Native=2]="Native"}(A||(A={}));const i={root:"/",dot:"."},s={nodeModules:"node_modules",manifest:"package.json",lockfile:"yarn.lock",pnpJs:".pnp.js",rc:".yarnrc.yml"},a=Object.create(o()),c=Object.create(o().posix);a.cwd=()=>process.cwd(),c.cwd=()=>C(process.cwd()),c.resolve=(...e)=>e.length>0&&c.isAbsolute(e[0])?o().posix.resolve(...e):o().posix.resolve(c.cwd(),...e);const g=function(e,t,r){return(t=e.normalize(t))===(r=e.normalize(r))?".":(t.endsWith(e.sep)||(t+=e.sep),r.startsWith(t)?r.slice(t.length):null)};a.fromPortablePath=d,a.toPortablePath=C,a.contains=(e,t)=>g(a,e,t),c.contains=(e,t)=>g(c,e,t);const l=/^([a-zA-Z]:.*)$/,u=/^\\\\(\.\\)?(.*)$/,h=/^\/([a-zA-Z]:.*)$/,p=/^\/unc\/(\.dot\/)?(.*)$/;function d(e){if("win32"!==process.platform)return e;if(e.match(h))e=e.replace(h,"$1");else{if(!e.match(p))return e;e=e.replace(p,(e,t,r)=>`\\\\${t?".\\":""}${r}`)}return e.replace(/\//g,"\\")}function C(e){return"win32"!==process.platform?e:(e.match(l)?e=e.replace(l,"/$1"):e.match(u)&&(e=e.replace(u,(e,t,r)=>`/unc/${t?".dot/":""}${r}`)),e.replace(/\\/g,"/"))}function f(e,t){return e===a?d(t):C(t)}function I(e){if(""!==a.parse(e).dir||""!==c.parse(e).dir)throw new Error(`Invalid filename: "${e}"`);return e}},65760:(e,t,r)=>{"use strict";r.r(t),r.d(t,{DirEntry:()=>n,StatEntry:()=>o,makeDefaultStats:()=>i,makeEmptyStats:()=>s,areStatsEqual:()=>a});var A=r(22004);class n{constructor(){this.name="",this.mode=0}isBlockDevice(){return!1}isCharacterDevice(){return!1}isDirectory(){return(this.mode&A.wK)===A.QB}isFIFO(){return!1}isFile(){return(this.mode&A.wK)===A.Pe}isSocket(){return!1}isSymbolicLink(){return(this.mode&A.wK)===A.Zv}}class o{constructor(){this.dev=0,this.ino=0,this.mode=0,this.nlink=1,this.rdev=0,this.blocks=1}isBlockDevice(){return!1}isCharacterDevice(){return!1}isDirectory(){return(this.mode&A.wK)===A.QB}isFIFO(){return!1}isFile(){return(this.mode&A.wK)===A.Pe}isSocket(){return!1}isSymbolicLink(){return(this.mode&A.wK)===A.Zv}}function i(){return Object.assign(new o,{uid:0,gid:0,size:0,blksize:0,atimeMs:0,mtimeMs:0,ctimeMs:0,birthtimeMs:0,atime:new Date(0),mtime:new Date(0),ctime:new Date(0),birthtime:new Date(0),mode:420|A.Pe})}function s(){return Object.assign(i(),{nlink:0,blocks:0,mode:0})}function a(e,t){return e.atimeMs===t.atimeMs&&(e.birthtimeMs===t.birthtimeMs&&(e.blksize===t.blksize&&(e.blocks===t.blocks&&(e.ctimeMs===t.ctimeMs&&(e.dev===t.dev&&(e.gid===t.gid&&(e.ino===t.ino&&(e.isBlockDevice()===t.isBlockDevice()&&(e.isCharacterDevice()===t.isCharacterDevice()&&(e.isDirectory()===t.isDirectory()&&(e.isFIFO()===t.isFIFO()&&(e.isFile()===t.isFile()&&(e.isSocket()===t.isSocket()&&(e.isSymbolicLink()===t.isSymbolicLink()&&(e.mode===t.mode&&(e.mtimeMs===t.mtimeMs&&(e.nlink===t.nlink&&(e.rdev===t.rdev&&(e.size===t.size&&e.uid===t.uid)))))))))))))))))))}},65281:(e,t,r)=>{"use strict";r.r(t),r.d(t,{getLibzipPromise:()=>s,getLibzipSync:()=>i});const A=["number","number"];var n;!function(e){e[e.ZIP_ER_OK=0]="ZIP_ER_OK",e[e.ZIP_ER_MULTIDISK=1]="ZIP_ER_MULTIDISK",e[e.ZIP_ER_RENAME=2]="ZIP_ER_RENAME",e[e.ZIP_ER_CLOSE=3]="ZIP_ER_CLOSE",e[e.ZIP_ER_SEEK=4]="ZIP_ER_SEEK",e[e.ZIP_ER_READ=5]="ZIP_ER_READ",e[e.ZIP_ER_WRITE=6]="ZIP_ER_WRITE",e[e.ZIP_ER_CRC=7]="ZIP_ER_CRC",e[e.ZIP_ER_ZIPCLOSED=8]="ZIP_ER_ZIPCLOSED",e[e.ZIP_ER_NOENT=9]="ZIP_ER_NOENT",e[e.ZIP_ER_EXISTS=10]="ZIP_ER_EXISTS",e[e.ZIP_ER_OPEN=11]="ZIP_ER_OPEN",e[e.ZIP_ER_TMPOPEN=12]="ZIP_ER_TMPOPEN",e[e.ZIP_ER_ZLIB=13]="ZIP_ER_ZLIB",e[e.ZIP_ER_MEMORY=14]="ZIP_ER_MEMORY",e[e.ZIP_ER_CHANGED=15]="ZIP_ER_CHANGED",e[e.ZIP_ER_COMPNOTSUPP=16]="ZIP_ER_COMPNOTSUPP",e[e.ZIP_ER_EOF=17]="ZIP_ER_EOF",e[e.ZIP_ER_INVAL=18]="ZIP_ER_INVAL",e[e.ZIP_ER_NOZIP=19]="ZIP_ER_NOZIP",e[e.ZIP_ER_INTERNAL=20]="ZIP_ER_INTERNAL",e[e.ZIP_ER_INCONS=21]="ZIP_ER_INCONS",e[e.ZIP_ER_REMOVE=22]="ZIP_ER_REMOVE",e[e.ZIP_ER_DELETED=23]="ZIP_ER_DELETED",e[e.ZIP_ER_ENCRNOTSUPP=24]="ZIP_ER_ENCRNOTSUPP",e[e.ZIP_ER_RDONLY=25]="ZIP_ER_RDONLY",e[e.ZIP_ER_NOPASSWD=26]="ZIP_ER_NOPASSWD",e[e.ZIP_ER_WRONGPASSWD=27]="ZIP_ER_WRONGPASSWD",e[e.ZIP_ER_OPNOTSUPP=28]="ZIP_ER_OPNOTSUPP",e[e.ZIP_ER_INUSE=29]="ZIP_ER_INUSE",e[e.ZIP_ER_TELL=30]="ZIP_ER_TELL",e[e.ZIP_ER_COMPRESSED_DATA=31]="ZIP_ER_COMPRESSED_DATA"}(n||(n={}));let o=null;function i(){var e;return null===o&&(e=r(3368),o={get HEAP8(){return e.HEAP8},get HEAPU8(){return e.HEAPU8},errors:n,SEEK_SET:0,SEEK_CUR:1,SEEK_END:2,ZIP_CHECKCONS:4,ZIP_CREATE:1,ZIP_EXCL:2,ZIP_TRUNCATE:8,ZIP_RDONLY:16,ZIP_FL_OVERWRITE:8192,ZIP_FL_COMPRESSED:4,ZIP_OPSYS_DOS:0,ZIP_OPSYS_AMIGA:1,ZIP_OPSYS_OPENVMS:2,ZIP_OPSYS_UNIX:3,ZIP_OPSYS_VM_CMS:4,ZIP_OPSYS_ATARI_ST:5,ZIP_OPSYS_OS_2:6,ZIP_OPSYS_MACINTOSH:7,ZIP_OPSYS_Z_SYSTEM:8,ZIP_OPSYS_CPM:9,ZIP_OPSYS_WINDOWS_NTFS:10,ZIP_OPSYS_MVS:11,ZIP_OPSYS_VSE:12,ZIP_OPSYS_ACORN_RISC:13,ZIP_OPSYS_VFAT:14,ZIP_OPSYS_ALTERNATE_MVS:15,ZIP_OPSYS_BEOS:16,ZIP_OPSYS_TANDEM:17,ZIP_OPSYS_OS_400:18,ZIP_OPSYS_OS_X:19,ZIP_CM_DEFAULT:-1,ZIP_CM_STORE:0,ZIP_CM_DEFLATE:8,uint08S:e._malloc(1),uint16S:e._malloc(2),uint32S:e._malloc(4),uint64S:e._malloc(8),malloc:e._malloc,free:e._free,getValue:e.getValue,open:e.cwrap("zip_open","number",["string","number","number"]),openFromSource:e.cwrap("zip_open_from_source","number",["number","number","number"]),close:e.cwrap("zip_close","number",["number"]),discard:e.cwrap("zip_discard",null,["number"]),getError:e.cwrap("zip_get_error","number",["number"]),getName:e.cwrap("zip_get_name","string",["number","number","number"]),getNumEntries:e.cwrap("zip_get_num_entries","number",["number","number"]),delete:e.cwrap("zip_delete","number",["number","number"]),stat:e.cwrap("zip_stat","number",["number","string","number","number"]),statIndex:e.cwrap("zip_stat_index","number",["number",...A,"number","number"]),fopen:e.cwrap("zip_fopen","number",["number","string","number"]),fopenIndex:e.cwrap("zip_fopen_index","number",["number",...A,"number"]),fread:e.cwrap("zip_fread","number",["number","number","number","number"]),fclose:e.cwrap("zip_fclose","number",["number"]),dir:{add:e.cwrap("zip_dir_add","number",["number","string"])},file:{add:e.cwrap("zip_file_add","number",["number","string","number","number"]),getError:e.cwrap("zip_file_get_error","number",["number"]),getExternalAttributes:e.cwrap("zip_file_get_external_attributes","number",["number",...A,"number","number","number"]),setExternalAttributes:e.cwrap("zip_file_set_external_attributes","number",["number",...A,"number","number","number"]),setMtime:e.cwrap("zip_file_set_mtime","number",["number",...A,"number","number"]),setCompression:e.cwrap("zip_set_file_compression","number",["number",...A,"number","number"])},ext:{countSymlinks:e.cwrap("zip_ext_count_symlinks","number",["number"])},error:{initWithCode:e.cwrap("zip_error_init_with_code",null,["number","number"]),strerror:e.cwrap("zip_error_strerror","string",["number"])},name:{locate:e.cwrap("zip_name_locate","number",["number","string","number"])},source:{fromUnattachedBuffer:e.cwrap("zip_source_buffer_create","number",["number","number","number","number"]),fromBuffer:e.cwrap("zip_source_buffer","number",["number","number",...A,"number"]),free:e.cwrap("zip_source_free",null,["number"]),keep:e.cwrap("zip_source_keep",null,["number"]),open:e.cwrap("zip_source_open","number",["number"]),close:e.cwrap("zip_source_close","number",["number"]),seek:e.cwrap("zip_source_seek","number",["number",...A,"number"]),tell:e.cwrap("zip_source_tell","number",["number"]),read:e.cwrap("zip_source_read","number",["number","number","number"]),error:e.cwrap("zip_source_error","number",["number"]),setMtime:e.cwrap("zip_source_set_mtime","number",["number","number"])},struct:{stat:e.cwrap("zipstruct_stat","number",[]),statS:e.cwrap("zipstruct_statS","number",[]),statName:e.cwrap("zipstruct_stat_name","string",["number"]),statIndex:e.cwrap("zipstruct_stat_index","number",["number"]),statSize:e.cwrap("zipstruct_stat_size","number",["number"]),statCompSize:e.cwrap("zipstruct_stat_comp_size","number",["number"]),statCompMethod:e.cwrap("zipstruct_stat_comp_method","number",["number"]),statMtime:e.cwrap("zipstruct_stat_mtime","number",["number"]),error:e.cwrap("zipstruct_error","number",[]),errorS:e.cwrap("zipstruct_errorS","number",[]),errorCodeZip:e.cwrap("zipstruct_error_code_zip","number",["number"])}}),o}async function s(){return i()}},11640:(e,t,r)=>{"use strict";r.r(t),r.d(t,{parseResolution:()=>i,parseShell:()=>n,parseSyml:()=>I,stringifyResolution:()=>s,stringifySyml:()=>d});var A=r(92962);function n(e,t={isGlobPattern:()=>!1}){try{return(0,A.parse)(e,t)}catch(e){throw e.location&&(e.message=e.message.replace(/(\.)?$/,` (line ${e.location.start.line}, column ${e.location.start.column})$1`)),e}}var o=r(98261);function i(e){const t=e.match(/^\*{1,2}\/(.*)/);if(t)throw new Error(`The override for '${e}' includes a glob pattern. Glob patterns have been removed since their behaviours don't match what you'd expect. Set the override to '${t[1]}' instead.`);try{return(0,o.parse)(e)}catch(e){throw e.location&&(e.message=e.message.replace(/(\.)?$/,` (line ${e.location.start.line}, column ${e.location.start.column})$1`)),e}}function s(e){let t="";return e.from&&(t+=e.from.fullName,e.from.description&&(t+="@"+e.from.description),t+="/"),t+=e.descriptor.fullName,e.descriptor.description&&(t+="@"+e.descriptor.description),t}var a=r(21194),c=r(85443);const g=/^(?![-?:,\][{}#&*!|>'"%@` \t\r\n]).([ \t]*(?![,\][{}:# \t\r\n]).)*$/,l=["__metadata","version","resolution","dependencies","peerDependencies","dependenciesMeta","peerDependenciesMeta","binaries"];class u{constructor(e){this.data=e}}function h(e){return e.match(g)?e:JSON.stringify(e)}function p(e,t,r){if(null===e)return"null\n";if("number"==typeof e||"boolean"==typeof e)return e.toString()+"\n";if("string"==typeof e)return h(e)+"\n";if(Array.isArray(e)){if(0===e.length)return"[]\n";const r=" ".repeat(t);return"\n"+e.map(e=>`${r}- ${p(e,t+1,!1)}`).join("")}if("object"==typeof e&&e){let A,n;e instanceof u?(A=e.data,n=!1):(A=e,n=!0);const o=" ".repeat(t),i=Object.keys(A);n&&i.sort((e,t)=>{const r=l.indexOf(e),A=l.indexOf(t);return-1===r&&-1===A?et?1:0:-1!==r&&-1===A?-1:-1===r&&-1!==A?1:r-A});const s=i.filter(e=>!function e(t){return void 0===t||"object"==typeof t&&null!==t&&Object.keys(t).every(r=>e(t[r]))}(A[e])).map((e,n)=>{const i=A[e],s=h(e),a=p(i,t+1,!0),c=n>0||r?o:"";return a.startsWith("\n")?`${c}${s}:${a}`:`${c}${s}: ${a}`}).join(0===t?"\n":"")||"\n";return r?"\n"+s:""+s}throw new Error(`Unsupported value type (${e})`)}function d(e){try{const t=p(e,0,!1);return"\n"!==t?t:""}catch(e){throw e.location&&(e.message=e.message.replace(/(\.)?$/,` (line ${e.location.start.line}, column ${e.location.start.column})$1`)),e}}d.PreserveOrdering=u;const C=/^(#.*(\r?\n))*?#\s+yarn\s+lockfile\s+v1\r?\n/i;function f(e){if(C.test(e))return function(e){return e.endsWith("\n")||(e+="\n"),(0,c.parse)(e)}(e);const t=(0,a.safeLoad)(e,{schema:a.FAILSAFE_SCHEMA});if(null==t)return{};if("object"!=typeof t)throw new Error(`Expected an indexed object, got a ${typeof t} instead. Does your file follow Yaml's rules?`);if(Array.isArray(t))throw new Error("Expected an indexed object, got an array instead. Does your file follow Yaml's rules?");return t}function I(e){return f(e)}},34432:(e,t,r)=>{"use strict";var A,n;r.d(t,{gY:()=>E,Q$:()=>B,oC:()=>F}),function(e){e.HARD="HARD",e.SOFT="SOFT"}(A||(A={})),function(e){e.DEFAULT="DEFAULT",e.TOP_LEVEL="TOP_LEVEL",e.FALLBACK_EXCLUSION_LIST="FALLBACK_EXCLUSION_LIST",e.FALLBACK_EXCLUSION_ENTRIES="FALLBACK_EXCLUSION_ENTRIES",e.FALLBACK_EXCLUSION_DATA="FALLBACK_EXCLUSION_DATA",e.PACKAGE_REGISTRY_DATA="PACKAGE_REGISTRY_DATA",e.PACKAGE_REGISTRY_ENTRIES="PACKAGE_REGISTRY_ENTRIES",e.PACKAGE_STORE_DATA="PACKAGE_STORE_DATA",e.PACKAGE_STORE_ENTRIES="PACKAGE_STORE_ENTRIES",e.PACKAGE_INFORMATION_DATA="PACKAGE_INFORMATION_DATA",e.PACKAGE_DEPENDENCIES="PACKAGE_DEPENDENCIES",e.PACKAGE_DEPENDENCY="PACKAGE_DEPENDENCY"}(n||(n={}));const o={[n.DEFAULT]:{collapsed:!1,next:{"*":n.DEFAULT}},[n.TOP_LEVEL]:{collapsed:!1,next:{fallbackExclusionList:n.FALLBACK_EXCLUSION_LIST,packageRegistryData:n.PACKAGE_REGISTRY_DATA,"*":n.DEFAULT}},[n.FALLBACK_EXCLUSION_LIST]:{collapsed:!1,next:{"*":n.FALLBACK_EXCLUSION_ENTRIES}},[n.FALLBACK_EXCLUSION_ENTRIES]:{collapsed:!0,next:{"*":n.FALLBACK_EXCLUSION_DATA}},[n.FALLBACK_EXCLUSION_DATA]:{collapsed:!0,next:{"*":n.DEFAULT}},[n.PACKAGE_REGISTRY_DATA]:{collapsed:!1,next:{"*":n.PACKAGE_REGISTRY_ENTRIES}},[n.PACKAGE_REGISTRY_ENTRIES]:{collapsed:!0,next:{"*":n.PACKAGE_STORE_DATA}},[n.PACKAGE_STORE_DATA]:{collapsed:!1,next:{"*":n.PACKAGE_STORE_ENTRIES}},[n.PACKAGE_STORE_ENTRIES]:{collapsed:!0,next:{"*":n.PACKAGE_INFORMATION_DATA}},[n.PACKAGE_INFORMATION_DATA]:{collapsed:!1,next:{packageDependencies:n.PACKAGE_DEPENDENCIES,"*":n.DEFAULT}},[n.PACKAGE_DEPENDENCIES]:{collapsed:!1,next:{"*":n.PACKAGE_DEPENDENCY}},[n.PACKAGE_DEPENDENCY]:{collapsed:!0,next:{"*":n.DEFAULT}}};function i(e,t,r,A){const{next:n}=o[r];return s(t,n[e]||n["*"],A)}function s(e,t,r){const{collapsed:A}=o[t];return Array.isArray(e)?A?function(e,t,r){let A="";A+="[";for(let n=0,o=e.length;ne(t)));const n=r.map((e,t)=>t);return n.sort((e,t)=>{for(const r of A){const A=r[e]r[t]?1:0;if(0!==A)return A}return 0}),n.map(e=>r[e])}function g(e){const t=new Map,r=c(e.fallbackExclusionList||[],[({name:e,reference:t})=>e,({name:e,reference:t})=>t]);for(const{name:e,reference:A}of r){let r=t.get(e);void 0===r&&t.set(e,r=new Set),r.add(A)}return Array.from(t).map(([e,t])=>[e,Array.from(t)])}function l(e){return c(e.fallbackPool||[],([e])=>e)}function u(e){const t=[];for(const[r,A]of c(e.packageRegistry,([e])=>null===e?"0":"1"+e)){const e=[];t.push([r,e]);for(const[t,{packageLocation:n,packageDependencies:o,packagePeers:i,linkType:s,discardFromLookup:a}]of c(A,([e])=>null===e?"0":"1"+e)){const A=[];null===r||null===t||o.has(r)||A.push([r,t]);for(const[e,t]of c(o.entries(),([e])=>e))A.push([e,t]);const g=i&&i.size>0?Array.from(i):void 0,l=a||void 0;e.push([t,{packageLocation:n,packageDependencies:A,packagePeers:g,linkType:s,discardFromLookup:l}])}}return t}function h(e){return c(e.blacklistedLocations||[],e=>e)}function p(e){return{__info:["This file is automatically generated. Do not touch it, or risk","your modifications being lost. We also recommend you not to read","it either without using the @yarnpkg/pnp package, as the data layout","is entirely unspecified and WILL change from a version to another."],dependencyTreeRoots:e.dependencyTreeRoots,enableTopLevelFallback:e.enableTopLevelFallback||!1,ignorePatternData:e.ignorePattern||null,fallbackExclusionList:g(e),fallbackPool:l(e),locationBlacklistData:h(e),packageRegistryData:u(e)}}var d=r(20103),C=r.n(d);function f(e,t){return[e?e+"\n":"","/* eslint-disable */\n\n","try {\n"," Object.freeze({}).detectStrictMode = true;\n","} catch (error) {\n"," throw new Error(`The whole PnP file got strict-mode-ified, which is known to break (Emscripten libraries aren't strict mode). This usually happens when the file goes through Babel.`);\n","}\n","\n","var __non_webpack_module__ = module;\n","\n","function $$SETUP_STATE(hydrateRuntimeState, basePath) {\n",t.replace(/^/gm," "),"}\n","\n",C()()].join("")}function I(e){return JSON.stringify(e,null,2)}function E(e){const t=function(e){return[`return hydrateRuntimeState(${a(e)}, {basePath: basePath || __dirname});\n`].join("")}(p(e));return f(e.shebang,t)}function B(e){const t=p(e),r=(A=e.dataLocation,["var path = require('path');\n",`var dataLocation = path.resolve(__dirname, ${JSON.stringify(A)});\n`,"return hydrateRuntimeState(require(dataLocation), {basePath: basePath || path.dirname(dataLocation)});\n"].join(""));var A;const n=f(e.shebang,r);return{dataFile:I(t),loaderFile:n}}var y=r(35747),m=(r(85622),r(31669)),w=r(46009);var Q,D=r(17674),b=r(32282);!function(e){e.API_ERROR="API_ERROR",e.BLACKLISTED="BLACKLISTED",e.BUILTIN_NODE_RESOLUTION_FAILED="BUILTIN_NODE_RESOLUTION_FAILED",e.MISSING_DEPENDENCY="MISSING_DEPENDENCY",e.MISSING_PEER_DEPENDENCY="MISSING_PEER_DEPENDENCY",e.QUALIFIED_PATH_RESOLUTION_FAILED="QUALIFIED_PATH_RESOLUTION_FAILED",e.INTERNAL="INTERNAL",e.UNDECLARED_DEPENDENCY="UNDECLARED_DEPENDENCY",e.UNSUPPORTED="UNSUPPORTED"}(Q||(Q={}));const v=new Set([Q.BLACKLISTED,Q.BUILTIN_NODE_RESOLUTION_FAILED,Q.MISSING_DEPENDENCY,Q.MISSING_PEER_DEPENDENCY,Q.QUALIFIED_PATH_RESOLUTION_FAILED,Q.UNDECLARED_DEPENDENCY]);function S(e,t,r={}){const A=v.has(e)?"MODULE_NOT_FOUND":e,n={configurable:!0,writable:!0,enumerable:!1};return Object.defineProperties(new Error(t),{code:{...n,value:A},pnpCode:{...n,value:e},data:{...n,value:r}})}function k(e){return w.cS.normalize(w.cS.fromPortablePath(e))}function N(e,t){const r=Number(process.env.PNP_ALWAYS_WARN_ON_FALLBACK)>0,A=Number(process.env.PNP_DEBUG_LEVEL),n=new Set(b.Module.builtinModules||Object.keys(process.binding("natives"))),o=/^(?![a-zA-Z]:[\\/]|\\\\|\.{0,2}(?:\/|$))((?:@[^/]+\/)?[^/]+)\/*(.*|)$/,i=/^(\/|\.{1,2}(\/|$))/,s=/\/$/,a={name:null,reference:null},c=[],g=new Set;if(!0===e.enableTopLevelFallback&&c.push(a),!1!==t.compatibilityMode)for(const t of["react-scripts","gatsby"]){const r=e.packageRegistry.get(t);if(r)for(const e of r.keys()){if(null===e)throw new Error("Assertion failed: This reference shouldn't be null");c.push({name:t,reference:e})}}const{ignorePattern:l,packageRegistry:u,packageLocatorsByLocations:h,packageLocationLengths:p}=e;function d(e,t){return{fn:e,args:t,error:null,result:null}}function C(e,r){if(!1===t.allowDebug)return r;if(Number.isFinite(A)){if(A>=2)return(...t)=>{const A=d(e,t);try{return A.result=r(...t)}catch(e){throw A.error=e}finally{console.trace(A)}};if(A>=1)return(...t)=>{try{return r(...t)}catch(r){const A=d(e,t);throw A.error=r,console.trace(A),r}}}return r}function f(e){const t=y(e);if(!t)throw S(Q.INTERNAL,"Couldn't find a matching entry in the dependency tree for the specified parent (this is probably an internal error)");return t}function I(t){if(null===t.name)return!0;for(const r of e.dependencyTreeRoots)if(r.name===t.name&&r.reference===t.reference)return!0;return!1}function E(e,t){return t.endsWith("/")&&(t=w.y1.join(t,"internal.js")),b.Module._resolveFilename(w.cS.fromPortablePath(e),function(e){const t=new b.Module(e,null);return t.filename=e,t.paths=b.Module._nodeModulePaths(e),t}(w.cS.fromPortablePath(t)),!1,{plugnplay:!1})}function B(t){if(null===l)return!1;const r=w.y1.contains(e.basePath,t);return null!==r&&!!l.test(r.replace(/\/$/,""))}function y({name:e,reference:t}){const r=u.get(e);if(!r)return null;const A=r.get(t);return A||null}function m(e,t){const r=new Map,A=new Set,n=t=>{const o=JSON.stringify(t.name);if(A.has(o))return;A.add(o);const i=function({name:e,reference:t}){const r=[];for(const[A,n]of u)if(null!==A)for(const[o,i]of n){if(null===o)continue;i.packageDependencies.get(e)===t&&(A===e&&o===t||r.push({name:A,reference:o}))}return r}(t);for(const t of i){if(f(t).packagePeers.has(e))n(t);else{let e=r.get(t.name);void 0===e&&r.set(t.name,e=new Set),e.add(t.reference)}}};n(t);const o=[];for(const e of[...r.keys()].sort())for(const t of[...r.get(e)].sort())o.push({name:e,reference:t});return o}function v(t){if(B(t))return null;let r=(A=w.y1.relative(e.basePath,t),w.cS.toPortablePath(A));var A;r.match(i)||(r="./"+r),t.match(s)&&!r.endsWith("/")&&(r+="/");let n=0;for(;nr.length;)n+=1;for(let e=n;eI(e))?S(Q.MISSING_PEER_DEPENDENCY,`${s.name} tried to access ${t} (a peer dependency) but it isn't provided by your application; this makes the require call ambiguous and unsound.\n\nRequired package: ${t} (via "${l}")\nRequired by: ${s.name}@${s.reference} (via ${u})\n${e.map(e=>`Ancestor breaking the chain: ${e.name}@${e.reference}\n`).join("")}\n`,{request:l,issuer:u,issuerLocator:Object.assign({},s),dependencyName:t,brokenAncestors:e}):S(Q.MISSING_PEER_DEPENDENCY,`${s.name} tried to access ${t} (a peer dependency) but it isn't provided by its ancestors; this makes the require call ambiguous and unsound.\n\nRequired package: ${t} (via "${l}")\nRequired by: ${s.name}@${s.reference} (via ${u})\n${e.map(e=>`Ancestor breaking the chain: ${e.name}@${e.reference}\n`).join("")}\n`,{request:l,issuer:u,issuerLocator:Object.assign({},s),dependencyName:t,brokenAncestors:e})}else void 0===d&&(B=!a&&n.has(A)?I(s)?S(Q.UNDECLARED_DEPENDENCY,`Your application tried to access ${t}. While this module is usually interpreted as a Node builtin, your resolver is running inside a non-Node resolution context where such builtins are ignored. Since ${t} isn't otherwise declared in your dependencies, this makes the require call ambiguous and unsound.\n\nRequired package: ${t} (via "${l}")\nRequired by: ${u}\n`,{request:l,issuer:u,dependencyName:t}):S(Q.UNDECLARED_DEPENDENCY,`${s.name} tried to access ${t}. While this module is usually interpreted as a Node builtin, your resolver is running inside a non-Node resolution context where such builtins are ignored. Since ${t} isn't otherwise declared in ${s.name}'s dependencies, this makes the require call ambiguous and unsound.\n\nRequired package: ${t} (via "${l}")\nRequired by: ${u}\n`,{request:l,issuer:u,issuerLocator:Object.assign({},s),dependencyName:t}):I(s)?S(Q.UNDECLARED_DEPENDENCY,`Your application tried to access ${t}, but it isn't declared in your dependencies; this makes the require call ambiguous and unsound.\n\nRequired package: ${t} (via "${l}")\nRequired by: ${u}\n`,{request:l,issuer:u,dependencyName:t}):S(Q.UNDECLARED_DEPENDENCY,`${s.name} tried to access ${t}, but it isn't declared in its dependencies; this makes the require call ambiguous and unsound.\n\nRequired package: ${t} (via "${l}")\nRequired by: ${s.name}@${s.reference} (via ${u})\n`,{request:l,issuer:u,issuerLocator:Object.assign({},s),dependencyName:t}));if(null==d){if(null===C||null===B)throw B||new Error("Assertion failed: Expected an error to have been set");d=C;const e=B.message.replace(/\n.*/g,"");B.message=e,g.has(e)||(g.add(e),process.emitWarning(B))}const y=Array.isArray(d)?{name:d[0],reference:d[1]}:{name:t,reference:d},D=f(y);if(!D.packageLocation)throw S(Q.MISSING_DEPENDENCY,`A dependency seems valid but didn't get installed for some reason. This might be caused by a partial install, such as dev vs prod.\n\nRequired package: ${y.name}@${y.reference} (via "${l}")\nRequired by: ${s.name}@${s.reference} (via ${u})\n`,{request:l,issuer:u,dependencyLocator:Object.assign({},y)});const b=D.packageLocation;h=o?w.y1.join(b,o):b}else{if(w.y1.isAbsolute(A))h=w.y1.normalize(A);else{if(!i)throw S(Q.API_ERROR,"The resolveToUnqualified function must be called with a valid issuer when the path isn't a builtin nor absolute",{request:l,issuer:u});const e=w.y1.resolve(i);h=i.match(s)?w.y1.normalize(w.y1.join(e,A)):w.y1.normalize(w.y1.join(w.y1.dirname(e),A))}v(h)}return w.y1.normalize(h)}function F(e,{extensions:r=Object.keys(b.Module._extensions)}={}){const A=[],n=function e(r,A,{extensions:n}){let o;try{A.push(r),o=t.fakeFs.statSync(r)}catch(e){}if(o&&!o.isDirectory())return t.fakeFs.realpathSync(r);if(o&&o.isDirectory()){let o,i;try{o=JSON.parse(t.fakeFs.readFileSync(w.y1.join(r,"package.json"),"utf8"))}catch(e){}if(o&&o.main&&(i=w.y1.resolve(r,o.main)),i&&i!==r){const t=e(i,A,{extensions:n});if(null!==t)return t}}for(let e=0,o=n.length;e`Rejected candidate: ${k(e)}\n`).join("")}`,{unqualifiedPath:t})}}return{VERSIONS:{std:3,resolveVirtual:1,getAllLocators:1},topLevel:a,getLocator:(e,t)=>Array.isArray(t)?{name:t[0],reference:t[1]}:{name:e,reference:t},getDependencyTreeRoots:()=>[...e.dependencyTreeRoots],getAllLocators(){const e=[];for(const[t,r]of u)for(const A of r.keys())null!==t&&null!==A&&e.push({name:t,reference:A});return e},getPackageInformation:e=>{const t=y(e);if(null===t)return null;const r=w.cS.fromPortablePath(t.packageLocation);return{...t,packageLocation:r}},findPackageLocator:e=>v(w.cS.toPortablePath(e)),resolveToUnqualified:C("resolveToUnqualified",(e,t,r)=>{const A=null!==t?w.cS.toPortablePath(t):null,n=N(w.cS.toPortablePath(e),A,r);return null===n?null:w.cS.fromPortablePath(n)}),resolveUnqualified:C("resolveUnqualified",(e,t)=>w.cS.fromPortablePath(F(w.cS.toPortablePath(e),t))),resolveRequest:C("resolveRequest",(e,t,r)=>{const A=null!==t?w.cS.toPortablePath(t):null,n=function(e,t,{considerBuiltins:r,extensions:A}={}){const n=N(e,t,{considerBuiltins:r});if(null===n)return null;try{return F(n,{extensions:A})}catch(r){throw"QUALIFIED_PATH_RESOLUTION_FAILED"===r.pnpCode&&Object.assign(r.data,{request:k(e),issuer:t&&k(t)}),r}}(w.cS.toPortablePath(e),A,r);return null===n?null:w.cS.fromPortablePath(n)}),resolveVirtual:C("resolveVirtual",e=>{const t=function(e){const t=w.y1.normalize(e),r=D.p.resolveVirtual(t);return r!==t?r:null}(w.cS.toPortablePath(e));return null!==t?w.cS.fromPortablePath(t):null})}}(0,m.promisify)(y.readFile);const F=(e,t,r)=>N(function(e,{basePath:t}){const r=w.cS.toPortablePath(t),A=w.y1.resolve(r),n=null!==e.ignorePatternData?new RegExp(e.ignorePatternData):null,o=new Map(e.packageRegistryData.map(([e,t])=>[e,new Map(t.map(([e,t])=>[e,{packageLocation:w.y1.join(A,t.packageLocation),packageDependencies:new Map(t.packageDependencies),packagePeers:new Set(t.packagePeers),linkType:t.linkType,discardFromLookup:t.discardFromLookup||!1}]))])),i=new Map,s=new Set;for(const[t,r]of e.packageRegistryData)for(const[e,A]of r){if(null===t!=(null===e))throw new Error("Assertion failed: The name and reference should be null, or neither should");if(A.discardFromLookup)continue;const r={name:t,reference:e};i.set(A.packageLocation,r),s.add(A.packageLocation.length)}for(const t of e.locationBlacklistData)i.set(t,null);const a=new Map(e.fallbackExclusionList.map(([e,t])=>[e,new Set(t)])),c=new Map(e.fallbackPool);return{basePath:r,dependencyTreeRoots:e.dependencyTreeRoots,enableTopLevelFallback:e.enableTopLevelFallback,fallbackExclusionList:a,fallbackPool:c,ignorePattern:n,packageLocationLengths:[...s].sort((e,t)=>t-e),packageLocatorsByLocations:i,packageRegistry:o}}(p(e),{basePath:t}),{fakeFs:r,pnpapiResolution:w.cS.join(t,".pnp.js")})},76756:(e,t,r)=>{"use strict";r.r(t),r.d(t,{ShellError:()=>c,execute:()=>Z,globUtils:()=>A});var A={};r.r(A),r.d(A,{fastGlobOptions:()=>E,isBraceExpansion:()=>m,isGlobPattern:()=>B,match:()=>y,micromatchOptions:()=>I});var n=r(46009),o=r(78420),i=r(11640),s=r(12087),a=r(92413);class c extends Error{constructor(e){super(e),this.name="ShellError"}}var g=r(43896),l=r(39725),u=r(19347),h=r.n(u),p=r(35747),d=r.n(p),C=r(2401),f=r.n(C);const I={strictBrackets:!0},E={onlyDirectories:!1,onlyFiles:!1};function B(e){if(!f().scan(e,I).isGlob)return!1;try{f().parse(e,I)}catch(e){return!1}return!0}function y(e,{cwd:t,baseFs:r}){return h()(e,{...E,cwd:n.cS.fromPortablePath(t),fs:(0,g.extendFs)(d(),new l.i(r))})}function m(e){return f().scan(e,I).isBrace}var w,Q=r(67566),D=r.n(Q);function b(){}!function(e){e[e.STDIN=0]="STDIN",e[e.STDOUT=1]="STDOUT",e[e.STDERR=2]="STDERR"}(w||(w={}));let v=0;class S{constructor(e){this.stream=e}close(){}get(){return this.stream}}class k{constructor(){this.stream=null}close(){if(null===this.stream)throw new Error("Assertion failed: No stream attached");this.stream.end()}attach(e){this.stream=e}get(){if(null===this.stream)throw new Error("Assertion failed: No stream attached");return this.stream}}class N{constructor(e,t){this.stdin=null,this.stdout=null,this.stderr=null,this.pipe=null,this.ancestor=e,this.implementation=t}static start(e,{stdin:t,stdout:r,stderr:A}){const n=new N(null,e);return n.stdin=t,n.stdout=r,n.stderr=A,n}pipeTo(e,t=w.STDOUT){const r=new N(this,e),A=new k;return r.pipe=A,r.stdout=this.stdout,r.stderr=this.stderr,(t&w.STDOUT)===w.STDOUT?this.stdout=A:null!==this.ancestor&&(this.stderr=this.ancestor.stdout),(t&w.STDERR)===w.STDERR?this.stderr=A:null!==this.ancestor&&(this.stderr=this.ancestor.stderr),r}async exec(){const e=["ignore","ignore","ignore"];if(this.pipe)e[0]="pipe";else{if(null===this.stdin)throw new Error("Assertion failed: No input stream registered");e[0]=this.stdin.get()}let t,r;if(null===this.stdout)throw new Error("Assertion failed: No output stream registered");if(t=this.stdout,e[1]=t.get(),null===this.stderr)throw new Error("Assertion failed: No error stream registered");r=this.stderr,e[2]=r.get();const A=this.implementation(e);return this.pipe&&this.pipe.attach(A.stdin),await A.promise.then(e=>(t.close(),r.close(),e))}async run(){const e=[];for(let t=this;t;t=t.ancestor)e.push(t.exec());return(await Promise.all(e))[0]}}function F(e,t){return N.start(e,t)}var K;function M(e,t,r){const A=new a.PassThrough({autoDestroy:!0});switch(e){case w.STDIN:(t&K.Readable)===K.Readable&&r.stdin.pipe(A,{end:!1}),(t&K.Writable)===K.Writable&&r.stdin instanceof a.Writable&&A.pipe(r.stdin,{end:!1});break;case w.STDOUT:(t&K.Readable)===K.Readable&&r.stdout.pipe(A,{end:!1}),(t&K.Writable)===K.Writable&&A.pipe(r.stdout,{end:!1});break;case w.STDERR:(t&K.Readable)===K.Readable&&r.stderr.pipe(A,{end:!1}),(t&K.Writable)===K.Writable&&A.pipe(r.stderr,{end:!1});break;default:throw new c(`Bad file descriptor: "${e}"`)}return A}function R(e,t={}){const r={...e,...t};return r.environment={...e.environment,...t.environment},r.variables={...e.variables,...t.variables},r}!function(e){e[e.Readable=1]="Readable",e[e.Writable=2]="Writable"}(K||(K={}));const x=new Map([["cd",async([e=(0,s.homedir)(),...t],r,A)=>{const o=n.y1.resolve(A.cwd,n.cS.toPortablePath(e));return(await r.baseFs.statPromise(o)).isDirectory()?(A.cwd=o,0):(A.stderr.write("cd: not a directory\n"),1)}],["pwd",async(e,t,r)=>(r.stdout.write(n.cS.fromPortablePath(r.cwd)+"\n"),0)],[":",async(e,t,r)=>0],["true",async(e,t,r)=>0],["false",async(e,t,r)=>1],["exit",async([e,...t],r,A)=>A.exitCode=parseInt(null!=e?e:A.variables["?"],10)],["echo",async(e,t,r)=>(r.stdout.write(e.join(" ")+"\n"),0)],["__ysh_run_procedure",async(e,t,r)=>{const A=r.procedures[e[0]];return await F(A,{stdin:new S(r.stdin),stdout:new S(r.stdout),stderr:new S(r.stderr)}).run()}],["__ysh_set_redirects",async(e,t,r)=>{let A=r.stdin,o=r.stdout;const i=r.stderr,s=[],c=[];let g=0;for(;"--"!==e[g];){const A=e[g++],o=Number(e[g++]),i=g+o;for(let o=g;ot.baseFs.createReadStream(n.y1.resolve(r.cwd,n.cS.toPortablePath(e[o]))));break;case"<<<":s.push(()=>{const t=new a.PassThrough;return process.nextTick(()=>{t.write(e[o]+"\n"),t.end()}),t});break;case"<&":s.push(()=>M(Number(e[o]),K.Readable,r));break;case">":case">>":{const i=n.y1.resolve(r.cwd,n.cS.toPortablePath(e[o]));"/dev/null"===i?c.push(new a.Writable({autoDestroy:!0,emitClose:!0,write(e,t,r){setImmediate(r)}})):c.push(t.baseFs.createWriteStream(i,">>"===A?{flags:"a"}:void 0))}break;case">&":c.push(M(Number(e[o]),K.Writable,r));break;default:throw new Error(`Assertion failed: Unsupported redirection type: "${A}"`)}}if(s.length>0){const e=new a.PassThrough;A=e;const t=r=>{if(r===s.length)e.end();else{const A=s[r]();A.pipe(e,{end:!1}),A.on("end",()=>{t(r+1)})}};t(0)}if(c.length>0){const e=new a.PassThrough;o=e;for(const t of c)e.pipe(t)}const l=await F(G(e.slice(g+1),t,r),{stdin:new S(A),stdout:new S(o),stderr:new S(i)}).run();return await Promise.all(c.map(e=>new Promise(t=>{e.on("close",()=>{t()}),e.end()}))),l}]]);async function L(e,t,r){const A=[],n=new a.PassThrough;return n.on("data",e=>A.push(e)),await W(e,t,R(r,{stdout:n})),Buffer.concat(A).toString().replace(/[\r\n]+$/,"")}async function P(e,t,r){const A=e.map(async e=>{const A=await Y(e.args,t,r);return{name:e.name,value:A.join(" ")}});return(await Promise.all(A)).reduce((e,t)=>(e[t.name]=t.value,e),{})}function O(e){return e.match(/[^ \r\n\t]+/g)||[]}async function U(e,t,r,A,n=A){switch(e.name){case"$":A(String(process.pid));break;case"#":A(String(t.args.length));break;case"@":if(e.quoted)for(const e of t.args)n(e);else for(const e of t.args){const t=O(e);for(let e=0;e=0&&ne+t,subtraction:(e,t)=>e-t,multiplication:(e,t)=>e*t,division:(e,t)=>Math.trunc(e/t)};async function j(e,t,r){if("number"===e.type){if(Number.isInteger(e.value))return e.value;throw new Error(`Invalid number: "${e.value}", only integers are allowed`)}if("variable"===e.type){const A=[];await U({...e,quoted:!0},t,r,e=>A.push(e));const n=Number(A.join(" "));return Number.isNaN(n)?j({type:"variable",name:A.join(" ")},t,r):j({type:"number",value:n},t,r)}return T[e.type](await j(e.left,t,r),await j(e.right,t,r))}async function Y(e,t,r){const A=new Map,n=[];let o=[];const i=e=>{o.push(e)},s=()=>{o.length>0&&n.push(o.join("")),o=[]},a=e=>{i(e),s()},g=(e,t)=>{let r=A.get(e);void 0===r&&A.set(e,r=[]),r.push(t)};for(const A of e){let e=!1;switch(A.type){case"redirection":{const e=await Y(A.args,t,r);for(const t of e)g(A.subtype,t)}break;case"argument":for(const n of A.segments)switch(n.type){case"text":i(n.text);break;case"glob":i(n.pattern),e=!0;break;case"shell":{const e=await L(n.shell,t,r);if(n.quoted)i(e);else{const t=O(e);for(let e=0;e0){const e=[];for(const[t,r]of A.entries())e.splice(e.length,0,t,String(r.length),...r);n.splice(0,0,"__ysh_set_redirects",...e,"--")}return n}function G(e,t,r){t.builtins.has(e[0])||(e=["command",...e]);const A=n.cS.fromPortablePath(r.cwd);let o=r.environment;void 0!==o.PWD&&(o={...o,PWD:A});const[i,...s]=e;if("command"===i)return function(e,t,r,A){return r=>{const n=r[0]instanceof a.Transform?"pipe":r[0],o=r[1]instanceof a.Transform?"pipe":r[1],i=r[2]instanceof a.Transform?"pipe":r[2],s=D()(e,t,{...A,stdio:[n,o,i]});return 0==v++&&process.on("SIGINT",b),r[0]instanceof a.Transform&&r[0].pipe(s.stdin),r[1]instanceof a.Transform&&s.stdout.pipe(r[1],{end:!1}),r[2]instanceof a.Transform&&s.stderr.pipe(r[2],{end:!1}),{stdin:s.stdin,promise:new Promise(t=>{s.on("error",A=>{switch(0==--v&&process.off("SIGINT",b),A.code){case"ENOENT":r[2].write(`command not found: ${e}\n`),t(127);break;case"EACCES":r[2].write(`permission denied: ${e}\n`),t(128);break;default:r[2].write(`uncaught error: ${A.message}\n`),t(1)}}),s.on("exit",e=>{0==--v&&process.off("SIGINT",b),t(null!==e?e:129)})})}}}(s[0],s.slice(1),0,{cwd:A,env:o});const c=t.builtins.get(i);if(void 0===c)throw new Error(`Assertion failed: A builtin should exist for "${i}"`);return function(e){return t=>{const r="pipe"===t[0]?new a.PassThrough:t[0];return{stdin:r,promise:Promise.resolve().then(()=>e({stdin:r,stdout:t[1],stderr:t[2]}))}}}(async({stdin:e,stdout:A,stderr:n})=>(r.stdin=e,r.stdout=A,r.stderr=n,await c(s,t,r)))}function H(e,t,r){return A=>{const n=new a.PassThrough;return{stdin:n,promise:W(e,t,R(r,{stdin:n}))}}}function J(e,t,r){return A=>({stdin:new a.PassThrough,promise:W(e,t,r)})}function q(e,t,r,A){if(0===t.length)return e;{let n;do{n=String(Math.random())}while(Object.prototype.hasOwnProperty.call(A.procedures,n));return A.procedures={...A.procedures},A.procedures[n]=e,G([...t,"__ysh_run_procedure",n],r,A)}}async function z(e,t,r){let A;const n=e=>{A=e,r.variables["?"]=String(e)},o=async e=>{try{return await async function(e,t,r){let A=e,n=null,o=null;for(;A;){const e=A.then?{...r}:r;let i;switch(A.type){case"command":{const n=await Y(A.args,t,r),o=await P(A.envs,t,r);i=A.envs.length?G(n,t,R(e,{environment:o})):G(n,t,e)}break;case"subshell":{const n=await Y(A.args,t,r);i=q(H(A.subshell,t,e),n,t,e)}break;case"group":{const n=await Y(A.args,t,r);i=q(J(A.group,t,e),n,t,e)}break;case"envs":{const n=await P(A.envs,t,r);e.environment={...e.environment,...n},i=G(["true"],t,e)}}if(void 0===i)throw new Error("Assertion failed: An action should have been generated");if(null===n)o=F(i,{stdin:new S(e.stdin),stdout:new S(e.stdout),stderr:new S(e.stderr)});else{if(null===o)throw new Error("Assertion failed: The execution pipeline should have been setup");switch(n){case"|":o=o.pipeTo(i,w.STDOUT);break;case"|&":o=o.pipeTo(i,w.STDOUT|w.STDERR)}}A.then?(n=A.then.type,A=A.then.chain):A=null}if(null===o)throw new Error("Assertion failed: The execution pipeline should have been setup");return await o.run()}(e,t,r)}catch(e){if(!(e instanceof c))throw e;return r.stderr.write(e.message+"\n"),1}};for(n(await o(e.chain));e.then;){if(null!==r.exitCode)return r.exitCode;switch(e.then.type){case"&&":0===A&&n(await o(e.then.line.chain));break;case"||":0!==A&&n(await o(e.then.line.chain));break;default:throw new Error(`Assertion failed: Unsupported command type: "${e.then.type}"`)}e=e.then.line}return A}async function W(e,t,r){let A=0;for(const n of e){if(A=await z(n,t,r),null!==r.exitCode)return r.exitCode;r.variables["?"]=String(A)}return A}function V(e){switch(e.type){case"variable":return"@"===e.name||"#"===e.name||"*"===e.name||Number.isFinite(parseInt(e.name,10))||"defaultValue"in e&&!!e.defaultValue&&e.defaultValue.some(e=>X(e));case"arithmetic":return function e(t){switch(t.type){case"variable":return V(t);case"number":return!1;default:return e(t.left)||e(t.right)}}(e.arithmetic);case"shell":return _(e.shell);default:return!1}}function X(e){switch(e.type){case"redirection":return e.args.some(e=>X(e));case"argument":return e.segments.some(e=>V(e));default:throw new Error(`Assertion failed: Unsupported argument type: "${e.type}"`)}}function _(e){return e.some(e=>{for(;e;){let t=e.chain;for(;t;){let e;switch(t.type){case"subshell":e=_(t.subshell);break;case"command":e=t.envs.some(e=>e.args.some(e=>X(e)))||t.args.some(e=>X(e))}if(e)return!0;if(!t.then)break;t=t.then.chain}if(!e.then)break;e=e.then.line}return!1})}async function Z(e,t=[],{baseFs:r=new o.S,builtins:s={},cwd:c=n.cS.toPortablePath(process.cwd()),env:g=process.env,stdin:l=process.stdin,stdout:u=process.stdout,stderr:h=process.stderr,variables:p={},glob:d=A}={}){const C={};for(const[e,t]of Object.entries(g))void 0!==t&&(C[e]=t);const f=new Map(x);for(const[e,t]of Object.entries(s))f.set(e,t);null===l&&(l=new a.PassThrough).end();const I=(0,i.parseShell)(e,d);if(!_(I)&&I.length>0&&t.length>0){let e=I[I.length-1];for(;e.then;)e=e.then.line;let r=e.chain;for(;r.then;)r=r.then.chain;"command"===r.type&&(r.args=r.args.concat(t.map(e=>({type:"argument",segments:[{type:"text",text:e}]}))))}return await W(I,{args:t,baseFs:r,builtins:f,initialStdin:l,initialStdout:u,initialStderr:h,glob:d},{cwd:c,environment:C,exitCode:null,procedures:{},stdin:l,stdout:u,stderr:h,variables:Object.assign({},p,{"?":0})})}},45330:(e,t,r)=>{t.e=()=>({modules:new Map([["@yarnpkg/cli",r(25413)],["@yarnpkg/core",r(53836)],["@yarnpkg/fslib",r(43896)],["@yarnpkg/libzip",r(65281)],["@yarnpkg/parsers",r(11640)],["@yarnpkg/shell",r(76756)],["clipanion",r(40822)],["semver",r(53887)],["yup",r(15966)],["@yarnpkg/plugin-essentials",r(34777)],["@yarnpkg/plugin-compat",r(44692)],["@yarnpkg/plugin-dlx",r(10189)],["@yarnpkg/plugin-file",r(68023)],["@yarnpkg/plugin-git",r(75641)],["@yarnpkg/plugin-github",r(68126)],["@yarnpkg/plugin-http",r(99148)],["@yarnpkg/plugin-init",r(64314)],["@yarnpkg/plugin-link",r(92994)],["@yarnpkg/plugin-node-modules",r(8375)],["@yarnpkg/plugin-npm",r(14224)],["@yarnpkg/plugin-npm-cli",r(8190)],["@yarnpkg/plugin-pack",r(49881)],["@yarnpkg/plugin-patch",r(29936)],["@yarnpkg/plugin-pnp",r(83228)]]),plugins:new Set(["@yarnpkg/plugin-essentials","@yarnpkg/plugin-compat","@yarnpkg/plugin-dlx","@yarnpkg/plugin-file","@yarnpkg/plugin-git","@yarnpkg/plugin-github","@yarnpkg/plugin-http","@yarnpkg/plugin-init","@yarnpkg/plugin-link","@yarnpkg/plugin-node-modules","@yarnpkg/plugin-npm","@yarnpkg/plugin-npm-cli","@yarnpkg/plugin-pack","@yarnpkg/plugin-patch","@yarnpkg/plugin-pnp"])})},29148:(e,t,r)=>{const A=r(74988),n=/^(.*?)(\x1b\[[^m]+m|\x1b\]8;;.*?(\x1b\\|\u0007))/,o=new A;e.exports=(e,t=0,r=e.length)=>{if(t<0||r<0)throw new RangeError("Negative indices aren't supported by this implementation");const A=r-t;let i="",s=0,a=0;for(;e.length>0;){const r=e.match(n)||[e,e,void 0];let c=o.splitGraphemes(r[1]);const g=Math.min(t-s,c.length);c=c.slice(g);const l=Math.min(A-a,c.length);i+=c.slice(0,l).join(""),s+=g,a+=l,void 0!==r[2]&&(i+=r[2]),e=e.slice(r[0].length)}return i}},72912:e=>{function t(){return e.exports=t=Object.assign||function(e){for(var t=1;t{e.exports=function(e){return e&&e.__esModule?e:{default:e}}},19228:(e,t,r)=>{var A=r(54694);function n(){if("function"!=typeof WeakMap)return null;var e=new WeakMap;return n=function(){return e},e}e.exports=function(e){if(e&&e.__esModule)return e;if(null===e||"object"!==A(e)&&"function"!=typeof e)return{default:e};var t=n();if(t&&t.has(e))return t.get(e);var r={},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var i in e)if(Object.prototype.hasOwnProperty.call(e,i)){var s=o?Object.getOwnPropertyDescriptor(e,i):null;s&&(s.get||s.set)?Object.defineProperty(r,i,s):r[i]=e[i]}return r.default=e,t&&t.set(e,r),r}},74943:e=>{e.exports=function(e,t){if(null==e)return{};var r,A,n={},o=Object.keys(e);for(A=0;A=0||(n[r]=e[r]);return n}},62407:e=>{e.exports=function(e,t){return t||(t=e.slice(0)),e.raw=t,e}},54694:e=>{function t(r){return"function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?e.exports=t=function(e){return typeof e}:e.exports=t=function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},t(r)}e.exports=t},96117:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(35747);t.FILE_SYSTEM_ADAPTER={lstat:A.lstat,stat:A.stat,lstatSync:A.lstatSync,statSync:A.statSync,readdir:A.readdir,readdirSync:A.readdirSync},t.createFileSystemAdapter=function(e){return void 0===e?t.FILE_SYSTEM_ADAPTER:Object.assign(Object.assign({},t.FILE_SYSTEM_ADAPTER),e)}},79774:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=process.versions.node.split("."),A=parseInt(r[0],10),n=parseInt(r[1],10),o=A>10,i=10===A&&n>=10;t.IS_SUPPORT_READDIR_WITH_FILE_TYPES=o||i},85670:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(31020),n=r(35516),o=r(38844);function i(e={}){return e instanceof o.default?e:new o.default(e)}t.Settings=o.default,t.scandir=function(e,t,r){if("function"==typeof t)return A.read(e,i(),t);A.read(e,i(t),r)},t.scandirSync=function(e,t){const r=i(t);return n.read(e,r)}},31020:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(53403),n=r(69078),o=r(79774),i=r(65225);function s(e,t,r){t.fs.readdir(e,{withFileTypes:!0},(A,o)=>{if(null!==A)return c(r,A);const s=o.map(r=>({dirent:r,name:r.name,path:`${e}${t.pathSegmentSeparator}${r.name}`}));if(!t.followSymbolicLinks)return g(r,s);const a=s.map(e=>function(e,t){return r=>{if(!e.dirent.isSymbolicLink())return r(null,e);t.fs.stat(e.path,(A,n)=>null!==A?t.throwErrorOnBrokenSymbolicLink?r(A):r(null,e):(e.dirent=i.fs.createDirentFromStats(e.name,n),r(null,e)))}}(e,t));n(a,(e,t)=>{if(null!==e)return c(r,e);g(r,t)})})}function a(e,t,r){t.fs.readdir(e,(o,s)=>{if(null!==o)return c(r,o);const a=s.map(r=>`${e}${t.pathSegmentSeparator}${r}`),l=a.map(e=>r=>A.stat(e,t.fsStatSettings,r));n(l,(e,A)=>{if(null!==e)return c(r,e);const n=[];s.forEach((e,r)=>{const o=A[r],s={name:e,path:a[r],dirent:i.fs.createDirentFromStats(e,o)};t.stats&&(s.stats=o),n.push(s)}),g(r,n)})})}function c(e,t){e(t)}function g(e,t){e(null,t)}t.read=function(e,t,r){return!t.stats&&o.IS_SUPPORT_READDIR_WITH_FILE_TYPES?s(e,t,r):a(e,t,r)},t.readdirWithFileTypes=s,t.readdir=a},35516:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(53403),n=r(79774),o=r(65225);function i(e,t){return t.fs.readdirSync(e,{withFileTypes:!0}).map(r=>{const A={dirent:r,name:r.name,path:`${e}${t.pathSegmentSeparator}${r.name}`};if(A.dirent.isSymbolicLink()&&t.followSymbolicLinks)try{const e=t.fs.statSync(A.path);A.dirent=o.fs.createDirentFromStats(A.name,e)}catch(e){if(t.throwErrorOnBrokenSymbolicLink)throw e}return A})}function s(e,t){return t.fs.readdirSync(e).map(r=>{const n=`${e}${t.pathSegmentSeparator}${r}`,i=A.statSync(n,t.fsStatSettings),s={name:r,path:n,dirent:o.fs.createDirentFromStats(r,i)};return t.stats&&(s.stats=i),s})}t.read=function(e,t){return!t.stats&&n.IS_SUPPORT_READDIR_WITH_FILE_TYPES?i(e,t):s(e,t)},t.readdirWithFileTypes=i,t.readdir=s},38844:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(85622),n=r(53403),o=r(96117);t.default=class{constructor(e={}){this._options=e,this.followSymbolicLinks=this._getValue(this._options.followSymbolicLinks,!1),this.fs=o.createFileSystemAdapter(this._options.fs),this.pathSegmentSeparator=this._getValue(this._options.pathSegmentSeparator,A.sep),this.stats=this._getValue(this._options.stats,!1),this.throwErrorOnBrokenSymbolicLink=this._getValue(this._options.throwErrorOnBrokenSymbolicLink,!0),this.fsStatSettings=new n.Settings({followSymbolicLink:this.followSymbolicLinks,fs:this.fs,throwErrorOnBrokenSymbolicLink:this.throwErrorOnBrokenSymbolicLink})}_getValue(e,t){return void 0===e?t:e}}},72156:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});class r{constructor(e,t){this.name=e,this.isBlockDevice=t.isBlockDevice.bind(t),this.isCharacterDevice=t.isCharacterDevice.bind(t),this.isDirectory=t.isDirectory.bind(t),this.isFIFO=t.isFIFO.bind(t),this.isFile=t.isFile.bind(t),this.isSocket=t.isSocket.bind(t),this.isSymbolicLink=t.isSymbolicLink.bind(t)}}t.createDirentFromStats=function(e,t){return new r(e,t)}},65225:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(72156);t.fs=A},71208:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(35747);t.FILE_SYSTEM_ADAPTER={lstat:A.lstat,stat:A.stat,lstatSync:A.lstatSync,statSync:A.statSync},t.createFileSystemAdapter=function(e){return void 0===e?t.FILE_SYSTEM_ADAPTER:Object.assign(Object.assign({},t.FILE_SYSTEM_ADAPTER),e)}},53403:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(17790),n=r(34846),o=r(92687);function i(e={}){return e instanceof o.default?e:new o.default(e)}t.Settings=o.default,t.stat=function(e,t,r){if("function"==typeof t)return A.read(e,i(),t);A.read(e,i(t),r)},t.statSync=function(e,t){const r=i(t);return n.read(e,r)}},17790:(e,t)=>{"use strict";function r(e,t){e(t)}function A(e,t){e(null,t)}Object.defineProperty(t,"__esModule",{value:!0}),t.read=function(e,t,n){t.fs.lstat(e,(o,i)=>null!==o?r(n,o):i.isSymbolicLink()&&t.followSymbolicLink?void t.fs.stat(e,(e,o)=>{if(null!==e)return t.throwErrorOnBrokenSymbolicLink?r(n,e):A(n,i);t.markSymbolicLink&&(o.isSymbolicLink=()=>!0),A(n,o)}):A(n,i))}},34846:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.read=function(e,t){const r=t.fs.lstatSync(e);if(!r.isSymbolicLink()||!t.followSymbolicLink)return r;try{const r=t.fs.statSync(e);return t.markSymbolicLink&&(r.isSymbolicLink=()=>!0),r}catch(e){if(!t.throwErrorOnBrokenSymbolicLink)return r;throw e}}},92687:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(71208);t.default=class{constructor(e={}){this._options=e,this.followSymbolicLink=this._getValue(this._options.followSymbolicLink,!0),this.fs=A.createFileSystemAdapter(this._options.fs),this.markSymbolicLink=this._getValue(this._options.markSymbolicLink,!1),this.throwErrorOnBrokenSymbolicLink=this._getValue(this._options.throwErrorOnBrokenSymbolicLink,!0)}_getValue(e,t){return void 0===e?t:e}}},72897:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(42369),n=r(27696),o=r(22111),i=r(14954);function s(e={}){return e instanceof i.default?e:new i.default(e)}t.Settings=i.default,t.walk=function(e,t,r){if("function"==typeof t)return new A.default(e,s()).read(t);new A.default(e,s(t)).read(r)},t.walkSync=function(e,t){const r=s(t);return new o.default(e,r).read()},t.walkStream=function(e,t){const r=s(t);return new n.default(e,r).read()}},42369:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(98566);t.default=class{constructor(e,t){this._root=e,this._settings=t,this._reader=new A.default(this._root,this._settings),this._storage=new Set}read(e){this._reader.onError(t=>{!function(e,t){e(t)}(e,t)}),this._reader.onEntry(e=>{this._storage.add(e)}),this._reader.onEnd(()=>{!function(e,t){e(null,t)}(e,[...this._storage])}),this._reader.read()}}},27696:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(92413),n=r(98566);t.default=class{constructor(e,t){this._root=e,this._settings=t,this._reader=new n.default(this._root,this._settings),this._stream=new A.Readable({objectMode:!0,read:()=>{},destroy:this._reader.destroy.bind(this._reader)})}read(){return this._reader.onError(e=>{this._stream.emit("error",e)}),this._reader.onEntry(e=>{this._stream.push(e)}),this._reader.onEnd(()=>{this._stream.push(null)}),this._reader.read(),this._stream}}},22111:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(97835);t.default=class{constructor(e,t){this._root=e,this._settings=t,this._reader=new A.default(this._root,this._settings)}read(){return this._reader.read()}}},98566:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(28614),n=r(85670),o=r(98360),i=r(10750),s=r(75504);class a extends s.default{constructor(e,t){super(e,t),this._settings=t,this._scandir=n.scandir,this._emitter=new A.EventEmitter,this._queue=o(this._worker.bind(this),this._settings.concurrency),this._isFatalError=!1,this._isDestroyed=!1,this._queue.drain=()=>{this._isFatalError||this._emitter.emit("end")}}read(){return this._isFatalError=!1,this._isDestroyed=!1,setImmediate(()=>{this._pushToQueue(this._root,this._settings.basePath)}),this._emitter}destroy(){if(this._isDestroyed)throw new Error("The reader is already destroyed");this._isDestroyed=!0,this._queue.killAndDrain()}onEntry(e){this._emitter.on("entry",e)}onError(e){this._emitter.once("error",e)}onEnd(e){this._emitter.once("end",e)}_pushToQueue(e,t){const r={directory:e,base:t};this._queue.push(r,e=>{null!==e&&this._handleError(e)})}_worker(e,t){this._scandir(e.directory,this._settings.fsScandirSettings,(r,A)=>{if(null!==r)return t(r,void 0);for(const t of A)this._handleEntry(t,e.base);t(null,void 0)})}_handleError(e){i.isFatalError(this._settings,e)&&(this._isFatalError=!0,this._isDestroyed=!0,this._emitter.emit("error",e))}_handleEntry(e,t){if(this._isDestroyed||this._isFatalError)return;const r=e.path;void 0!==t&&(e.path=i.joinPathSegments(t,e.name,this._settings.pathSegmentSeparator)),i.isAppliedFilter(this._settings.entryFilter,e)&&this._emitEntry(e),e.dirent.isDirectory()&&i.isAppliedFilter(this._settings.deepFilter,e)&&this._pushToQueue(r,e.path)}_emitEntry(e){this._emitter.emit("entry",e)}}t.default=a},10750:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.isFatalError=function(e,t){return null===e.errorFilter||!e.errorFilter(t)},t.isAppliedFilter=function(e,t){return null===e||e(t)},t.replacePathSegmentSeparator=function(e,t){return e.split(/[\\/]/).join(t)},t.joinPathSegments=function(e,t,r){return""===e?t:e+r+t}},75504:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(10750);t.default=class{constructor(e,t){this._root=e,this._settings=t,this._root=A.replacePathSegmentSeparator(e,t.pathSegmentSeparator)}}},97835:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(85670),n=r(10750),o=r(75504);class i extends o.default{constructor(){super(...arguments),this._scandir=A.scandirSync,this._storage=new Set,this._queue=new Set}read(){return this._pushToQueue(this._root,this._settings.basePath),this._handleQueue(),[...this._storage]}_pushToQueue(e,t){this._queue.add({directory:e,base:t})}_handleQueue(){for(const e of this._queue.values())this._handleDirectory(e.directory,e.base)}_handleDirectory(e,t){try{const r=this._scandir(e,this._settings.fsScandirSettings);for(const e of r)this._handleEntry(e,t)}catch(e){this._handleError(e)}}_handleError(e){if(n.isFatalError(this._settings,e))throw e}_handleEntry(e,t){const r=e.path;void 0!==t&&(e.path=n.joinPathSegments(t,e.name,this._settings.pathSegmentSeparator)),n.isAppliedFilter(this._settings.entryFilter,e)&&this._pushToStorage(e),e.dirent.isDirectory()&&n.isAppliedFilter(this._settings.deepFilter,e)&&this._pushToQueue(r,e.path)}_pushToStorage(e){this._storage.add(e)}}t.default=i},14954:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(85622),n=r(85670);t.default=class{constructor(e={}){this._options=e,this.basePath=this._getValue(this._options.basePath,void 0),this.concurrency=this._getValue(this._options.concurrency,1/0),this.deepFilter=this._getValue(this._options.deepFilter,null),this.entryFilter=this._getValue(this._options.entryFilter,null),this.errorFilter=this._getValue(this._options.errorFilter,null),this.pathSegmentSeparator=this._getValue(this._options.pathSegmentSeparator,A.sep),this.fsScandirSettings=new n.Settings({followSymbolicLinks:this._options.followSymbolicLinks,fs:this._options.fs,pathSegmentSeparator:this._options.pathSegmentSeparator,stats:this._options.stats,throwErrorOnBrokenSymbolicLink:this._options.throwErrorOnBrokenSymbolicLink})}_getValue(e,t){return void 0===e?t:e}}},7966:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=["Int8Array","Uint8Array","Uint8ClampedArray","Int16Array","Uint16Array","Int32Array","Uint32Array","Float32Array","Float64Array","BigInt64Array","BigUint64Array"];const A=["Function","Generator","AsyncGenerator","GeneratorFunction","AsyncGeneratorFunction","AsyncFunction","Observable","Array","Buffer","Object","RegExp","Date","Error","Map","Set","WeakMap","WeakSet","ArrayBuffer","SharedArrayBuffer","DataView","Promise","URL","HTMLElement",...r];const n=["null","undefined","string","number","bigint","boolean","symbol"];function o(e){return t=>typeof t===e}const{toString:i}=Object.prototype,s=e=>{const t=i.call(e).slice(8,-1);return/HTML\w+Element/.test(t)&&c.domElement(e)?"HTMLElement":(r=t,A.includes(r)?t:void 0);var r},a=e=>t=>s(t)===e;function c(e){if(null===e)return"null";switch(typeof e){case"undefined":return"undefined";case"string":return"string";case"number":return"number";case"boolean":return"boolean";case"function":return"Function";case"bigint":return"bigint";case"symbol":return"symbol"}if(c.observable(e))return"Observable";if(c.array(e))return"Array";if(c.buffer(e))return"Buffer";const t=s(e);if(t)return t;if(e instanceof String||e instanceof Boolean||e instanceof Number)throw new TypeError("Please don't use object wrappers for primitive types");return"Object"}c.undefined=o("undefined"),c.string=o("string");const g=o("number");c.number=e=>g(e)&&!c.nan(e),c.bigint=o("bigint"),c.function_=o("function"),c.null_=e=>null===e,c.class_=e=>c.function_(e)&&e.toString().startsWith("class "),c.boolean=e=>!0===e||!1===e,c.symbol=o("symbol"),c.numericString=e=>c.string(e)&&!c.emptyStringOrWhitespace(e)&&!Number.isNaN(Number(e)),c.array=(e,t)=>!!Array.isArray(e)&&(!c.function_(t)||e.every(t)),c.buffer=e=>{var t,r,A,n;return null!==(n=null===(A=null===(r=null===(t=e)||void 0===t?void 0:t.constructor)||void 0===r?void 0:r.isBuffer)||void 0===A?void 0:A.call(r,e))&&void 0!==n&&n},c.nullOrUndefined=e=>c.null_(e)||c.undefined(e),c.object=e=>!c.null_(e)&&("object"==typeof e||c.function_(e)),c.iterable=e=>{var t;return c.function_(null===(t=e)||void 0===t?void 0:t[Symbol.iterator])},c.asyncIterable=e=>{var t;return c.function_(null===(t=e)||void 0===t?void 0:t[Symbol.asyncIterator])},c.generator=e=>c.iterable(e)&&c.function_(e.next)&&c.function_(e.throw),c.asyncGenerator=e=>c.asyncIterable(e)&&c.function_(e.next)&&c.function_(e.throw),c.nativePromise=e=>a("Promise")(e);c.promise=e=>c.nativePromise(e)||(e=>{var t,r;return c.function_(null===(t=e)||void 0===t?void 0:t.then)&&c.function_(null===(r=e)||void 0===r?void 0:r.catch)})(e),c.generatorFunction=a("GeneratorFunction"),c.asyncGeneratorFunction=e=>"AsyncGeneratorFunction"===s(e),c.asyncFunction=e=>"AsyncFunction"===s(e),c.boundFunction=e=>c.function_(e)&&!e.hasOwnProperty("prototype"),c.regExp=a("RegExp"),c.date=a("Date"),c.error=a("Error"),c.map=e=>a("Map")(e),c.set=e=>a("Set")(e),c.weakMap=e=>a("WeakMap")(e),c.weakSet=e=>a("WeakSet")(e),c.int8Array=a("Int8Array"),c.uint8Array=a("Uint8Array"),c.uint8ClampedArray=a("Uint8ClampedArray"),c.int16Array=a("Int16Array"),c.uint16Array=a("Uint16Array"),c.int32Array=a("Int32Array"),c.uint32Array=a("Uint32Array"),c.float32Array=a("Float32Array"),c.float64Array=a("Float64Array"),c.bigInt64Array=a("BigInt64Array"),c.bigUint64Array=a("BigUint64Array"),c.arrayBuffer=a("ArrayBuffer"),c.sharedArrayBuffer=a("SharedArrayBuffer"),c.dataView=a("DataView"),c.directInstanceOf=(e,t)=>Object.getPrototypeOf(e)===t.prototype,c.urlInstance=e=>a("URL")(e),c.urlString=e=>{if(!c.string(e))return!1;try{return new URL(e),!0}catch(e){return!1}},c.truthy=e=>Boolean(e),c.falsy=e=>!e,c.nan=e=>Number.isNaN(e),c.primitive=e=>{return c.null_(e)||(t=typeof e,n.includes(t));var t},c.integer=e=>Number.isInteger(e),c.safeInteger=e=>Number.isSafeInteger(e),c.plainObject=e=>{if("[object Object]"!==i.call(e))return!1;const t=Object.getPrototypeOf(e);return null===t||t===Object.getPrototypeOf({})},c.typedArray=e=>{return t=s(e),r.includes(t);var t};c.arrayLike=e=>!c.nullOrUndefined(e)&&!c.function_(e)&&(e=>c.safeInteger(e)&&e>=0)(e.length),c.inRange=(e,t)=>{if(c.number(t))return e>=Math.min(0,t)&&e<=Math.max(t,0);if(c.array(t)&&2===t.length)return e>=Math.min(...t)&&e<=Math.max(...t);throw new TypeError("Invalid range: "+JSON.stringify(t))};const l=["innerHTML","ownerDocument","style","attributes","nodeValue"];c.domElement=e=>c.object(e)&&1===e.nodeType&&c.string(e.nodeName)&&!c.plainObject(e)&&l.every(t=>t in e),c.observable=e=>{var t,r,A,n;return!!e&&(e===(null===(r=(t=e)[Symbol.observable])||void 0===r?void 0:r.call(t))||e===(null===(n=(A=e)["@@observable"])||void 0===n?void 0:n.call(A)))},c.nodeStream=e=>c.object(e)&&c.function_(e.pipe)&&!c.observable(e),c.infinite=e=>e===1/0||e===-1/0;const u=e=>t=>c.integer(t)&&Math.abs(t%2)===e;c.evenInteger=u(0),c.oddInteger=u(1),c.emptyArray=e=>c.array(e)&&0===e.length,c.nonEmptyArray=e=>c.array(e)&&e.length>0,c.emptyString=e=>c.string(e)&&0===e.length,c.nonEmptyString=e=>c.string(e)&&e.length>0;c.emptyStringOrWhitespace=e=>c.emptyString(e)||(e=>c.string(e)&&!/\S/.test(e))(e),c.emptyObject=e=>c.object(e)&&!c.map(e)&&!c.set(e)&&0===Object.keys(e).length,c.nonEmptyObject=e=>c.object(e)&&!c.map(e)&&!c.set(e)&&Object.keys(e).length>0,c.emptySet=e=>c.set(e)&&0===e.size,c.nonEmptySet=e=>c.set(e)&&e.size>0,c.emptyMap=e=>c.map(e)&&0===e.size,c.nonEmptyMap=e=>c.map(e)&&e.size>0;const h=(e,t,r)=>{if(!c.function_(t))throw new TypeError("Invalid predicate: "+JSON.stringify(t));if(0===r.length)throw new TypeError("Invalid number of values");return e.call(r,t)};c.any=(e,...t)=>(c.array(e)?e:[e]).some(e=>h(Array.prototype.some,e,t)),c.all=(e,...t)=>h(Array.prototype.every,e,t);const p=(e,t,r)=>{if(!e)throw new TypeError(`Expected value which is \`${t}\`, received value of type \`${c(r)}\`.`)};t.assert={undefined:e=>p(c.undefined(e),"undefined",e),string:e=>p(c.string(e),"string",e),number:e=>p(c.number(e),"number",e),bigint:e=>p(c.bigint(e),"bigint",e),function_:e=>p(c.function_(e),"Function",e),null_:e=>p(c.null_(e),"null",e),class_:e=>p(c.class_(e),"Class",e),boolean:e=>p(c.boolean(e),"boolean",e),symbol:e=>p(c.symbol(e),"symbol",e),numericString:e=>p(c.numericString(e),"string with a number",e),array:(e,t)=>{p(c.array(e),"Array",e),t&&e.forEach(t)},buffer:e=>p(c.buffer(e),"Buffer",e),nullOrUndefined:e=>p(c.nullOrUndefined(e),"null or undefined",e),object:e=>p(c.object(e),"Object",e),iterable:e=>p(c.iterable(e),"Iterable",e),asyncIterable:e=>p(c.asyncIterable(e),"AsyncIterable",e),generator:e=>p(c.generator(e),"Generator",e),asyncGenerator:e=>p(c.asyncGenerator(e),"AsyncGenerator",e),nativePromise:e=>p(c.nativePromise(e),"native Promise",e),promise:e=>p(c.promise(e),"Promise",e),generatorFunction:e=>p(c.generatorFunction(e),"GeneratorFunction",e),asyncGeneratorFunction:e=>p(c.asyncGeneratorFunction(e),"AsyncGeneratorFunction",e),asyncFunction:e=>p(c.asyncFunction(e),"AsyncFunction",e),boundFunction:e=>p(c.boundFunction(e),"Function",e),regExp:e=>p(c.regExp(e),"RegExp",e),date:e=>p(c.date(e),"Date",e),error:e=>p(c.error(e),"Error",e),map:e=>p(c.map(e),"Map",e),set:e=>p(c.set(e),"Set",e),weakMap:e=>p(c.weakMap(e),"WeakMap",e),weakSet:e=>p(c.weakSet(e),"WeakSet",e),int8Array:e=>p(c.int8Array(e),"Int8Array",e),uint8Array:e=>p(c.uint8Array(e),"Uint8Array",e),uint8ClampedArray:e=>p(c.uint8ClampedArray(e),"Uint8ClampedArray",e),int16Array:e=>p(c.int16Array(e),"Int16Array",e),uint16Array:e=>p(c.uint16Array(e),"Uint16Array",e),int32Array:e=>p(c.int32Array(e),"Int32Array",e),uint32Array:e=>p(c.uint32Array(e),"Uint32Array",e),float32Array:e=>p(c.float32Array(e),"Float32Array",e),float64Array:e=>p(c.float64Array(e),"Float64Array",e),bigInt64Array:e=>p(c.bigInt64Array(e),"BigInt64Array",e),bigUint64Array:e=>p(c.bigUint64Array(e),"BigUint64Array",e),arrayBuffer:e=>p(c.arrayBuffer(e),"ArrayBuffer",e),sharedArrayBuffer:e=>p(c.sharedArrayBuffer(e),"SharedArrayBuffer",e),dataView:e=>p(c.dataView(e),"DataView",e),urlInstance:e=>p(c.urlInstance(e),"URL",e),urlString:e=>p(c.urlString(e),"string with a URL",e),truthy:e=>p(c.truthy(e),"truthy",e),falsy:e=>p(c.falsy(e),"falsy",e),nan:e=>p(c.nan(e),"NaN",e),primitive:e=>p(c.primitive(e),"primitive",e),integer:e=>p(c.integer(e),"integer",e),safeInteger:e=>p(c.safeInteger(e),"integer",e),plainObject:e=>p(c.plainObject(e),"plain object",e),typedArray:e=>p(c.typedArray(e),"TypedArray",e),arrayLike:e=>p(c.arrayLike(e),"array-like",e),domElement:e=>p(c.domElement(e),"HTMLElement",e),observable:e=>p(c.observable(e),"Observable",e),nodeStream:e=>p(c.nodeStream(e),"Node.js Stream",e),infinite:e=>p(c.infinite(e),"infinite number",e),emptyArray:e=>p(c.emptyArray(e),"empty array",e),nonEmptyArray:e=>p(c.nonEmptyArray(e),"non-empty array",e),emptyString:e=>p(c.emptyString(e),"empty string",e),nonEmptyString:e=>p(c.nonEmptyString(e),"non-empty string",e),emptyStringOrWhitespace:e=>p(c.emptyStringOrWhitespace(e),"empty string or whitespace",e),emptyObject:e=>p(c.emptyObject(e),"empty object",e),nonEmptyObject:e=>p(c.nonEmptyObject(e),"non-empty object",e),emptySet:e=>p(c.emptySet(e),"empty set",e),nonEmptySet:e=>p(c.nonEmptySet(e),"non-empty set",e),emptyMap:e=>p(c.emptyMap(e),"empty map",e),nonEmptyMap:e=>p(c.nonEmptyMap(e),"non-empty map",e),evenInteger:e=>p(c.evenInteger(e),"even integer",e),oddInteger:e=>p(c.oddInteger(e),"odd integer",e),directInstanceOf:(e,t)=>p(c.directInstanceOf(e,t),"T",e),inRange:(e,t)=>p(c.inRange(e,t),"in range",e),any:(e,...t)=>p(c.any(e,...t),"predicate returns truthy for any value",t),all:(e,...t)=>p(c.all(e,...t),"predicate returns truthy for all values",t)},Object.defineProperties(c,{class:{value:c.class_},function:{value:c.function_},null:{value:c.null_}}),Object.defineProperties(t.assert,{class:{value:t.assert.class_},function:{value:t.assert.function_},null:{value:t.assert.null_}}),t.default=c,e.exports=c,e.exports.default=c,e.exports.assert=t.assert},98298:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(93121),n=Number(process.versions.node.split(".")[0]),o=e=>{const t={start:Date.now(),socket:void 0,lookup:void 0,connect:void 0,secureConnect:void 0,upload:void 0,response:void 0,end:void 0,error:void 0,abort:void 0,phases:{wait:void 0,dns:void 0,tcp:void 0,tls:void 0,request:void 0,firstByte:void 0,download:void 0,total:void 0}};e.timings=t;const r=e=>{const r=e.emit.bind(e);e.emit=(A,...n)=>("error"===A&&(t.error=Date.now(),t.phases.total=t.error-t.start,e.emit=r),r(A,...n))};r(e),e.prependOnceListener("abort",()=>{t.abort=Date.now(),(!t.response||n>=13)&&(t.phases.total=Date.now()-t.start)});const o=e=>{t.socket=Date.now(),t.phases.wait=t.socket-t.start;const r=()=>{t.lookup=Date.now(),t.phases.dns=t.lookup-t.socket};e.prependOnceListener("lookup",r),A.default(e,{connect:()=>{t.connect=Date.now(),void 0===t.lookup&&(e.removeListener("lookup",r),t.lookup=t.connect,t.phases.dns=t.lookup-t.socket),t.phases.tcp=t.connect-t.lookup},secureConnect:()=>{t.secureConnect=Date.now(),t.phases.tls=t.secureConnect-t.connect}})};e.socket?o(e.socket):e.prependOnceListener("socket",o);const i=()=>{var e;t.upload=Date.now(),t.phases.request=t.upload-(null!=(e=t.secureConnect)?e:t.connect)};return("boolean"==typeof e.writableFinished?!e.writableFinished:!e.finished||0!==e.outputSize||e.socket&&0!==e.socket.writableLength)?e.prependOnceListener("finish",i):i(),e.prependOnceListener("response",e=>{t.response=Date.now(),t.phases.firstByte=t.response-t.upload,e.timings=t,r(e),e.prependOnceListener("end",()=>{t.end=Date.now(),t.phases.download=t.end-t.response,t.phases.total=t.end-t.start})}),t};t.default=o,e.exports=o,e.exports.default=o},58069:(e,t,r)=>{"use strict";l.ifExists=function(e,t,r){return l(e,t,r).catch(()=>{})};const A=r(31669),n=r(46227),o=r(85622),i=r(97369),s=/^#!\s*(?:\/usr\/bin\/env)?\s*([^ \t]+)(.*)$/,a={createPwshFile:!0,createCmdFile:i(),fs:r(35747)},c=new Map([[".js","node"],[".cmd","cmd"],[".bat","cmd"],[".ps1","pwsh"],[".sh","sh"]]);function g(e){const t={...a,...e},r=t.fs;return t.fs_={chmod:r.chmod?A.promisify(r.chmod):async()=>{},stat:A.promisify(r.stat),unlink:A.promisify(r.unlink),readFile:A.promisify(r.readFile),writeFile:A.promisify(r.writeFile)},t}async function l(e,t,r){const A=g(r);await A.fs_.stat(e),await async function(e,t,r){const A=await async function(e,t){const r=await t.fs_.readFile(e,"utf8"),A=r.trim().split(/\r*\n/)[0].match(s);if(!A){const t=o.extname(e).toLowerCase();return{program:c.get(t)||null,additionalArgs:""}}return{program:A[1],additionalArgs:A[2]}}(e,r);return await function(e,t){return n(o.dirname(e),{fs:t.fs})}(t,r),function(e,t,r,A){const n=g(A),o=[{generator:h,extension:""}];n.createCmdFile&&o.push({generator:u,extension:".cmd"});n.createPwshFile&&o.push({generator:p,extension:".ps1"});return Promise.all(o.map(A=>async function(e,t,r,A,n){const o=n.preserveSymlinks?"--preserve-symlinks":"",i=[r.additionalArgs,o].filter(e=>e).join(" ");return n=Object.assign({},n,{prog:r.program,args:i}),await function(e,t){return function(e,t){return t.fs_.unlink(e).catch(()=>{})}(e,t)}(t,n),await n.fs_.writeFile(t,A(e,t,n),"utf8"),function(e,t){return function(e,t){return t.fs_.chmod(e,493)}(e,t)}(t,n)}(e,t+A.extension,r,A.generator,n)))}(e,t,A,r)}(e,t,A)}function u(e,t,r){let A=o.relative(o.dirname(t),e).split("/").join("\\");const n=o.isAbsolute(A)?`"${A}"`:`"%~dp0\\${A}"`;let i,s=r.prog,a=r.args||"";const c=d(r.nodePath).win32;s?(i=`"%~dp0\\${s}.exe"`,A=n):(s=n,a="",A="");let g=r.progArgs?r.progArgs.join(" ")+" ":"",l=c?`@SET NODE_PATH=${c}\r\n`:"";return l+=i?`@IF EXIST ${i} (\r\n ${i} ${a} ${A} ${g}%*\r\n) ELSE (\r\n @SETLOCAL\r\n @SET PATHEXT=%PATHEXT:;.JS;=;%\r\n ${s} ${a} ${A} ${g}%*\r\n)`:`@${s} ${a} ${A} ${g}%*\r\n`,l}function h(e,t,r){let A,n=o.relative(o.dirname(t),e),i=r.prog&&r.prog.split("\\").join("/");n=n.split("\\").join("/");const s=o.isAbsolute(n)?`"${n}"`:`"$basedir/${n}"`;let a=r.args||"";const c=d(r.nodePath).posix;i?(A=`"$basedir/${r.prog}"`,n=s):(i=s,a="",n="");let g=r.progArgs?r.progArgs.join(" ")+" ":"",l="#!/bin/sh\n";l+='basedir=$(dirname "$(echo "$0" | sed -e \'s,\\\\,/,g\')")\n\ncase `uname` in\n *CYGWIN*) basedir=`cygpath -w "$basedir"`;;\nesac\n\n';const u=r.nodePath?`export NODE_PATH="${c}"\n`:"";return l+=A?u+`if [ -x ${A} ]; then\n`+` exec ${A} ${a} ${n} ${g}"$@"\nelse \n`+` exec ${i} ${a} ${n} ${g}"$@"\nfi\n`:`${u}${i} ${a} ${n} ${g}"$@"\nexit $?\n`,l}function p(e,t,r){let A=o.relative(o.dirname(t),e);const n=r.prog&&r.prog.split("\\").join("/");let i,s=n&&`"${n}$exe"`;A=A.split("\\").join("/");const a=o.isAbsolute(A)?`"${A}"`:`"$basedir/${A}"`;let c=r.args||"",g=d(r.nodePath);const l=g.win32,u=g.posix;s?(i=`"$basedir/${r.prog}$exe"`,A=a):(s=a,c="",A="");let h=r.progArgs?r.progArgs.join(" ")+" ":"",p='#!/usr/bin/env pwsh\n$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent\n\n$exe=""\n'+(r.nodePath?`$env_node_path=$env:NODE_PATH\n$env:NODE_PATH="${l}"\n`:"")+'if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {\n # Fix case when both the Windows and Linux builds of Node\n # are installed in the same directory\n $exe=".exe"\n}';return r.nodePath&&(p=p+" else {\n"+` $env:NODE_PATH="${u}"\n}`),p+="\n",p=i?p+"$ret=0\n"+`if (Test-Path ${i}) {\n # Support pipeline input\n if ($MyInvocation.ExpectingInput) {\n`+` $input | & ${i} ${c} ${A} ${h}$args\n } else {\n`+` & ${i} ${c} ${A} ${h}$args\n }\n $ret=$LASTEXITCODE\n} else {\n # Support pipeline input\n if ($MyInvocation.ExpectingInput) {\n`+` $input | & ${s} ${c} ${A} ${h}$args\n } else {\n`+` & ${s} ${c} ${A} ${h}$args\n }\n $ret=$LASTEXITCODE\n}\n`+(r.nodePath?"$env:NODE_PATH=$env_node_path\n":"")+"exit $ret\n":p+"# Support pipeline input\nif ($MyInvocation.ExpectingInput) {\n"+` $input | & ${s} ${c} ${A} ${h}$args\n} else {\n`+` & ${s} ${c} ${A} ${h}$args\n}\n`+(r.nodePath?"$env:NODE_PATH=$env_node_path\n":"")+"exit $LASTEXITCODE\n",p}function d(e){if(!e)return{win32:"",posix:""};let t="string"==typeof e?e.split(o.delimiter):Array.from(e),r={};for(let e=0;e"/mnt/"+t.toLowerCase()):t[e];r.win32=r.win32?`${r.win32};${A}`:A,r.posix=r.posix?`${r.posix}:${n}`:n,r[e]={win32:A,posix:n}}return r}e.exports=l},97991:(e,t,r)=>{"use strict";const A=/[\u001b\u009b][[\]#;?()]*(?:(?:(?:[^\W_]*;?[^\W_]*)\u0007)|(?:(?:[0-9]{1,4}(;[0-9]{0,4})*)?[~0-9=<>cf-nqrtyA-PRZ]))/g,n=()=>{const e={enabled:!0,visible:!0,styles:{},keys:{}};"FORCE_COLOR"in process.env&&(e.enabled="0"!==process.env.FORCE_COLOR);const t=(e,t,r)=>"function"==typeof e?e(t):e.wrap(t,r),n=(r,A)=>{if(""===r||null==r)return"";if(!1===e.enabled)return r;if(!1===e.visible)return"";let n=""+r,o=n.includes("\n"),i=A.length;for(i>0&&A.includes("unstyle")&&(A=[...new Set(["unstyle",...A])].reverse());i-- >0;)n=t(e.styles[A[i]],n,o);return n},o=(t,r,A)=>{e.styles[t]=(e=>{let t=e.open=`[${e.codes[0]}m`,r=e.close=`[${e.codes[1]}m`,A=e.regex=new RegExp(`\\u001b\\[${e.codes[1]}m`,"g");return e.wrap=(e,n)=>{e.includes(r)&&(e=e.replace(A,r+t));let o=t+e+r;return n?o.replace(/\r*\n/g,`${r}$&${t}`):o},e})({name:t,codes:r}),(e.keys[A]||(e.keys[A]=[])).push(t),Reflect.defineProperty(e,t,{configurable:!0,enumerable:!0,set(r){e.alias(t,r)},get(){let r=e=>n(e,r.stack);return Reflect.setPrototypeOf(r,e),r.stack=this.stack?this.stack.concat(t):[t],r}})};return o("reset",[0,0],"modifier"),o("bold",[1,22],"modifier"),o("dim",[2,22],"modifier"),o("italic",[3,23],"modifier"),o("underline",[4,24],"modifier"),o("inverse",[7,27],"modifier"),o("hidden",[8,28],"modifier"),o("strikethrough",[9,29],"modifier"),o("black",[30,39],"color"),o("red",[31,39],"color"),o("green",[32,39],"color"),o("yellow",[33,39],"color"),o("blue",[34,39],"color"),o("magenta",[35,39],"color"),o("cyan",[36,39],"color"),o("white",[37,39],"color"),o("gray",[90,39],"color"),o("grey",[90,39],"color"),o("bgBlack",[40,49],"bg"),o("bgRed",[41,49],"bg"),o("bgGreen",[42,49],"bg"),o("bgYellow",[43,49],"bg"),o("bgBlue",[44,49],"bg"),o("bgMagenta",[45,49],"bg"),o("bgCyan",[46,49],"bg"),o("bgWhite",[47,49],"bg"),o("blackBright",[90,39],"bright"),o("redBright",[91,39],"bright"),o("greenBright",[92,39],"bright"),o("yellowBright",[93,39],"bright"),o("blueBright",[94,39],"bright"),o("magentaBright",[95,39],"bright"),o("cyanBright",[96,39],"bright"),o("whiteBright",[97,39],"bright"),o("bgBlackBright",[100,49],"bgBright"),o("bgRedBright",[101,49],"bgBright"),o("bgGreenBright",[102,49],"bgBright"),o("bgYellowBright",[103,49],"bgBright"),o("bgBlueBright",[104,49],"bgBright"),o("bgMagentaBright",[105,49],"bgBright"),o("bgCyanBright",[106,49],"bgBright"),o("bgWhiteBright",[107,49],"bgBright"),e.ansiRegex=A,e.hasColor=e.hasAnsi=t=>(e.ansiRegex.lastIndex=0,"string"==typeof t&&""!==t&&e.ansiRegex.test(t)),e.alias=(t,r)=>{let A="string"==typeof r?e[r]:r;if("function"!=typeof A)throw new TypeError("Expected alias to be the name of an existing color (string) or a function");A.stack||(Reflect.defineProperty(A,"name",{value:t}),e.styles[t]=A,A.stack=[t]),Reflect.defineProperty(e,t,{configurable:!0,enumerable:!0,set(r){e.alias(t,r)},get(){let t=e=>n(e,t.stack);return Reflect.setPrototypeOf(t,e),t.stack=this.stack?this.stack.concat(A.stack):A.stack,t}})},e.theme=t=>{if(null===(r=t)||"object"!=typeof r||Array.isArray(r))throw new TypeError("Expected theme to be an object");var r;for(let r of Object.keys(t))e.alias(r,t[r]);return e},e.alias("unstyle",t=>"string"==typeof t&&""!==t?(e.ansiRegex.lastIndex=0,t.replace(e.ansiRegex,"")):""),e.alias("noop",e=>e),e.none=e.clear=e.noop,e.stripColor=e.unstyle,e.symbols=r(31283),e.define=o,e};e.exports=n(),e.exports.create=n},31283:e=>{"use strict";const t="Hyper"===process.env.TERM_PROGRAM,r="win32"===process.platform,A="linux"===process.platform,n={ballotDisabled:"☒",ballotOff:"☐",ballotOn:"☑",bullet:"•",bulletWhite:"◦",fullBlock:"█",heart:"❤",identicalTo:"≡",line:"─",mark:"※",middot:"·",minus:"-",multiplication:"×",obelus:"÷",pencilDownRight:"✎",pencilRight:"✏",pencilUpRight:"✐",percent:"%",pilcrow2:"❡",pilcrow:"¶",plusMinus:"±",section:"§",starsOff:"☆",starsOn:"★",upDownArrow:"↕"},o=Object.assign({},n,{check:"√",cross:"×",ellipsisLarge:"...",ellipsis:"...",info:"i",question:"?",questionSmall:"?",pointer:">",pointerSmall:"»",radioOff:"( )",radioOn:"(*)",warning:"‼"}),i=Object.assign({},n,{ballotCross:"✘",check:"✔",cross:"✖",ellipsisLarge:"⋯",ellipsis:"…",info:"ℹ",question:"?",questionFull:"?",questionSmall:"﹖",pointer:A?"▸":"❯",pointerSmall:A?"‣":"›",radioOff:"◯",radioOn:"◉",warning:"⚠"});e.exports=r&&!t?o:i,Reflect.defineProperty(e.exports,"common",{enumerable:!1,value:n}),Reflect.defineProperty(e.exports,"windows",{enumerable:!1,value:o}),Reflect.defineProperty(e.exports,"other",{enumerable:!1,value:i})},18483:(e,t,r)=>{"use strict";e=r.nmd(e);const A=(e,t)=>(...r)=>`[${e(...r)+t}m`,n=(e,t)=>(...r)=>{const A=e(...r);return`[${38+t};5;${A}m`},o=(e,t)=>(...r)=>{const A=e(...r);return`[${38+t};2;${A[0]};${A[1]};${A[2]}m`},i=e=>e,s=(e,t,r)=>[e,t,r],a=(e,t,r)=>{Object.defineProperty(e,t,{get:()=>{const A=r();return Object.defineProperty(e,t,{value:A,enumerable:!0,configurable:!0}),A},enumerable:!0,configurable:!0})};let c;const g=(e,t,A,n)=>{void 0===c&&(c=r(2744));const o=n?10:0,i={};for(const[r,n]of Object.entries(c)){const s="ansi16"===r?"ansi":r;r===t?i[s]=e(A,o):"object"==typeof n&&(i[s]=e(n[t],o))}return i};Object.defineProperty(e,"exports",{enumerable:!0,get:function(){const e=new Map,t={modifier:{reset:[0,0],bold:[1,22],dim:[2,22],italic:[3,23],underline:[4,24],inverse:[7,27],hidden:[8,28],strikethrough:[9,29]},color:{black:[30,39],red:[31,39],green:[32,39],yellow:[33,39],blue:[34,39],magenta:[35,39],cyan:[36,39],white:[37,39],blackBright:[90,39],redBright:[91,39],greenBright:[92,39],yellowBright:[93,39],blueBright:[94,39],magentaBright:[95,39],cyanBright:[96,39],whiteBright:[97,39]},bgColor:{bgBlack:[40,49],bgRed:[41,49],bgGreen:[42,49],bgYellow:[43,49],bgBlue:[44,49],bgMagenta:[45,49],bgCyan:[46,49],bgWhite:[47,49],bgBlackBright:[100,49],bgRedBright:[101,49],bgGreenBright:[102,49],bgYellowBright:[103,49],bgBlueBright:[104,49],bgMagentaBright:[105,49],bgCyanBright:[106,49],bgWhiteBright:[107,49]}};t.color.gray=t.color.blackBright,t.bgColor.bgGray=t.bgColor.bgBlackBright,t.color.grey=t.color.blackBright,t.bgColor.bgGrey=t.bgColor.bgBlackBright;for(const[r,A]of Object.entries(t)){for(const[r,n]of Object.entries(A))t[r]={open:`[${n[0]}m`,close:`[${n[1]}m`},A[r]=t[r],e.set(n[0],n[1]);Object.defineProperty(t,r,{value:A,enumerable:!1})}return Object.defineProperty(t,"codes",{value:e,enumerable:!1}),t.color.close="",t.bgColor.close="",a(t.color,"ansi",()=>g(A,"ansi16",i,!1)),a(t.color,"ansi256",()=>g(n,"ansi256",i,!1)),a(t.color,"ansi16m",()=>g(o,"rgb",s,!1)),a(t.bgColor,"ansi",()=>g(A,"ansi16",i,!0)),a(t.bgColor,"ansi256",()=>g(n,"ansi256",i,!0)),a(t.bgColor,"ansi16m",()=>g(o,"rgb",s,!0)),t}})},39920:e=>{"use strict";e.exports=(...e)=>[...new Set([].concat(...e))]},67648:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getBinjumper=void 0;const A=r(78761);let n=null;t.getBinjumper=function(){return n||(n=A.gunzipSync(Buffer.from("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","base64"))),n}},50730:(e,t,r)=>{"use strict";t.O9=void 0;const A=r(85622),n=r(35747),o=r(31669),i=r(67648);Object.defineProperty(t,"O9",{enumerable:!0,get:function(){return i.getBinjumper}})},73975:(e,t,r)=>{"use strict";var A=r(86897).Duplex;function n(e){if(!(this instanceof n))return new n(e);if(this._bufs=[],this.length=0,"function"==typeof e){this._callback=e;var t=function(e){this._callback&&(this._callback(e),this._callback=null)}.bind(this);this.on("pipe",(function(e){e.on("error",t)})),this.on("unpipe",(function(e){e.removeListener("error",t)}))}else this.append(e);A.call(this)}r(31669).inherits(n,A),n.prototype._offset=function(e){var t,r=0,A=0;if(0===e)return[0,0];for(;Athis.length||e<0)){var t=this._offset(e);return this._bufs[t[0]][t[1]]}},n.prototype.slice=function(e,t){return"number"==typeof e&&e<0&&(e+=this.length),"number"==typeof t&&t<0&&(t+=this.length),this.copy(null,0,e,t)},n.prototype.copy=function(e,t,r,A){if(("number"!=typeof r||r<0)&&(r=0),("number"!=typeof A||A>this.length)&&(A=this.length),r>=this.length)return e||Buffer.alloc(0);if(A<=0)return e||Buffer.alloc(0);var n,o,i=!!e,s=this._offset(r),a=A-r,c=a,g=i&&t||0,l=s[1];if(0===r&&A==this.length){if(!i)return 1===this._bufs.length?this._bufs[0]:Buffer.concat(this._bufs,this.length);for(o=0;o(n=this._bufs[o].length-l))){this._bufs[o].copy(e,g,l,l+c);break}this._bufs[o].copy(e,g,l),g+=n,c-=n,l&&(l=0)}return e},n.prototype.shallowSlice=function(e,t){if(e=e||0,t="number"!=typeof t?this.length:t,e<0&&(e+=this.length),t<0&&(t+=this.length),e===t)return new n;var r=this._offset(e),A=this._offset(t),o=this._bufs.slice(r[0],A[0]+1);return 0==A[1]?o.pop():o[o.length-1]=o[o.length-1].slice(0,A[1]),0!=r[1]&&(o[0]=o[0].slice(r[1])),new n(o)},n.prototype.toString=function(e,t,r){return this.slice(t,r).toString(e)},n.prototype.consume=function(e){for(;this._bufs.length;){if(!(e>=this._bufs[0].length)){this._bufs[0]=this._bufs[0].slice(e),this.length-=e;break}e-=this._bufs[0].length,this.length-=this._bufs[0].length,this._bufs.shift()}return this},n.prototype.duplicate=function(){for(var e=0,t=new n;ethis.length?this.length:t;for(var A=this._offset(t),o=A[0],i=A[1];o=e.length){var a=s.indexOf(e,i);if(-1!==a)return this._reverseOffset([o,a]);i=s.length-e.length+1}else{var c=this._reverseOffset([o,i]);if(this._match(c,e))return c;i++}}i=0}return-1},n.prototype._match=function(e,t){if(this.length-e{"use strict";const A=r(54900),n=r(44617),o=r(1495),i=r(425),s=(e,t={})=>{let r=[];if(Array.isArray(e))for(let A of e){let e=s.create(A,t);Array.isArray(e)?r.push(...e):r.push(e)}else r=[].concat(s.create(e,t));return t&&!0===t.expand&&!0===t.nodupes&&(r=[...new Set(r)]),r};s.parse=(e,t={})=>i(e,t),s.stringify=(e,t={})=>A("string"==typeof e?s.parse(e,t):e,t),s.compile=(e,t={})=>("string"==typeof e&&(e=s.parse(e,t)),n(e,t)),s.expand=(e,t={})=>{"string"==typeof e&&(e=s.parse(e,t));let r=o(e,t);return!0===t.noempty&&(r=r.filter(Boolean)),!0===t.nodupes&&(r=[...new Set(r)]),r},s.create=(e,t={})=>""===e||e.length<3?[e]:!0!==t.expand?s.compile(e,t):s.expand(e,t),e.exports=s},44617:(e,t,r)=>{"use strict";const A=r(52169),n=r(4542);e.exports=(e,t={})=>{let r=(e,o={})=>{let i=n.isInvalidBrace(o),s=!0===e.invalid&&!0===t.escapeInvalid,a=!0===i||!0===s,c=!0===t.escapeInvalid?"\\":"",g="";if(!0===e.isOpen)return c+e.value;if(!0===e.isClose)return c+e.value;if("open"===e.type)return a?c+e.value:"(";if("close"===e.type)return a?c+e.value:")";if("comma"===e.type)return"comma"===e.prev.type?"":a?e.value:"|";if(e.value)return e.value;if(e.nodes&&e.ranges>0){let r=n.reduce(e.nodes),o=A(...r,{...t,wrap:!1,toRegex:!0});if(0!==o.length)return r.length>1&&o.length>1?`(${o})`:o}if(e.nodes)for(let t of e.nodes)g+=r(t,e);return g};return r(e)}},5384:e=>{"use strict";e.exports={MAX_LENGTH:65536,CHAR_0:"0",CHAR_9:"9",CHAR_UPPERCASE_A:"A",CHAR_LOWERCASE_A:"a",CHAR_UPPERCASE_Z:"Z",CHAR_LOWERCASE_Z:"z",CHAR_LEFT_PARENTHESES:"(",CHAR_RIGHT_PARENTHESES:")",CHAR_ASTERISK:"*",CHAR_AMPERSAND:"&",CHAR_AT:"@",CHAR_BACKSLASH:"\\",CHAR_BACKTICK:"`",CHAR_CARRIAGE_RETURN:"\r",CHAR_CIRCUMFLEX_ACCENT:"^",CHAR_COLON:":",CHAR_COMMA:",",CHAR_DOLLAR:"$",CHAR_DOT:".",CHAR_DOUBLE_QUOTE:'"',CHAR_EQUAL:"=",CHAR_EXCLAMATION_MARK:"!",CHAR_FORM_FEED:"\f",CHAR_FORWARD_SLASH:"/",CHAR_HASH:"#",CHAR_HYPHEN_MINUS:"-",CHAR_LEFT_ANGLE_BRACKET:"<",CHAR_LEFT_CURLY_BRACE:"{",CHAR_LEFT_SQUARE_BRACKET:"[",CHAR_LINE_FEED:"\n",CHAR_NO_BREAK_SPACE:" ",CHAR_PERCENT:"%",CHAR_PLUS:"+",CHAR_QUESTION_MARK:"?",CHAR_RIGHT_ANGLE_BRACKET:">",CHAR_RIGHT_CURLY_BRACE:"}",CHAR_RIGHT_SQUARE_BRACKET:"]",CHAR_SEMICOLON:";",CHAR_SINGLE_QUOTE:"'",CHAR_SPACE:" ",CHAR_TAB:"\t",CHAR_UNDERSCORE:"_",CHAR_VERTICAL_LINE:"|",CHAR_ZERO_WIDTH_NOBREAK_SPACE:"\ufeff"}},1495:(e,t,r)=>{"use strict";const A=r(52169),n=r(54900),o=r(4542),i=(e="",t="",r=!1)=>{let A=[];if(e=[].concat(e),!(t=[].concat(t)).length)return e;if(!e.length)return r?o.flatten(t).map(e=>`{${e}}`):t;for(let n of e)if(Array.isArray(n))for(let e of n)A.push(i(e,t,r));else for(let e of t)!0===r&&"string"==typeof e&&(e=`{${e}}`),A.push(Array.isArray(e)?i(n,e,r):n+e);return o.flatten(A)};e.exports=(e,t={})=>{let r=void 0===t.rangeLimit?1e3:t.rangeLimit,s=(e,a={})=>{e.queue=[];let c=a,g=a.queue;for(;"brace"!==c.type&&"root"!==c.type&&c.parent;)c=c.parent,g=c.queue;if(e.invalid||e.dollar)return void g.push(i(g.pop(),n(e,t)));if("brace"===e.type&&!0!==e.invalid&&2===e.nodes.length)return void g.push(i(g.pop(),["{}"]));if(e.nodes&&e.ranges>0){let s=o.reduce(e.nodes);if(o.exceedsLimit(...s,t.step,r))throw new RangeError("expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.");let a=A(...s,t);return 0===a.length&&(a=n(e,t)),g.push(i(g.pop(),a)),void(e.nodes=[])}let l=o.encloseBrace(e),u=e.queue,h=e;for(;"brace"!==h.type&&"root"!==h.type&&h.parent;)h=h.parent,u=h.queue;for(let t=0;t{"use strict";const A=r(54900),{MAX_LENGTH:n,CHAR_BACKSLASH:o,CHAR_BACKTICK:i,CHAR_COMMA:s,CHAR_DOT:a,CHAR_LEFT_PARENTHESES:c,CHAR_RIGHT_PARENTHESES:g,CHAR_LEFT_CURLY_BRACE:l,CHAR_RIGHT_CURLY_BRACE:u,CHAR_LEFT_SQUARE_BRACKET:h,CHAR_RIGHT_SQUARE_BRACKET:p,CHAR_DOUBLE_QUOTE:d,CHAR_SINGLE_QUOTE:C,CHAR_NO_BREAK_SPACE:f,CHAR_ZERO_WIDTH_NOBREAK_SPACE:I}=r(5384);e.exports=(e,t={})=>{if("string"!=typeof e)throw new TypeError("Expected a string");let r=t||{},E="number"==typeof r.maxLength?Math.min(n,r.maxLength):n;if(e.length>E)throw new SyntaxError(`Input length (${e.length}), exceeds max characters (${E})`);let B,y={type:"root",input:e,nodes:[]},m=[y],w=y,Q=y,D=0,b=e.length,v=0,S=0;const k=()=>e[v++],N=e=>{if("text"===e.type&&"dot"===Q.type&&(Q.type="text"),!Q||"text"!==Q.type||"text"!==e.type)return w.nodes.push(e),e.parent=w,e.prev=Q,Q=e,e;Q.value+=e.value};for(N({type:"bos"});v0){if(w.ranges>0){w.ranges=0;let e=w.nodes.shift();w.nodes=[e,{type:"text",value:A(w)}]}N({type:"comma",value:B}),w.commas++}else if(B===a&&S>0&&0===w.commas){let e=w.nodes;if(0===S||0===e.length){N({type:"text",value:B});continue}if("dot"===Q.type){if(w.range=[],Q.value+=B,Q.type="range",3!==w.nodes.length&&5!==w.nodes.length){w.invalid=!0,w.ranges=0,Q.type="text";continue}w.ranges++,w.args=[];continue}if("range"===Q.type){e.pop();let t=e[e.length-1];t.value+=Q.value+B,Q=t,w.ranges--;continue}N({type:"dot",value:B})}else N({type:"text",value:B});else{if("brace"!==w.type){N({type:"text",value:B});continue}let e="close";w=m.pop(),w.close=!0,N({type:e,value:B}),S--,w=m[m.length-1]}else{S++;let e=Q.value&&"$"===Q.value.slice(-1)||!0===w.dollar;w=N({type:"brace",open:!0,close:!1,dollar:e,depth:S,commas:0,ranges:0,nodes:[]}),m.push(w),N({type:"open",value:B})}else{let e,r=B;for(!0!==t.keepQuotes&&(B="");v{e.nodes||("open"===e.type&&(e.isOpen=!0),"close"===e.type&&(e.isClose=!0),e.nodes||(e.type="text"),e.invalid=!0)});let e=m[m.length-1],t=e.nodes.indexOf(w);e.nodes.splice(t,1,...w.nodes)}}while(m.length>0);return N({type:"eos"}),y}},54900:(e,t,r)=>{"use strict";const A=r(4542);e.exports=(e,t={})=>{let r=(e,n={})=>{let o=t.escapeInvalid&&A.isInvalidBrace(n),i=!0===e.invalid&&!0===t.escapeInvalid,s="";if(e.value)return(o||i)&&A.isOpenOrClose(e)?"\\"+e.value:e.value;if(e.value)return e.value;if(e.nodes)for(let t of e.nodes)s+=r(t);return s};return r(e)}},4542:(e,t)=>{"use strict";t.isInteger=e=>"number"==typeof e?Number.isInteger(e):"string"==typeof e&&""!==e.trim()&&Number.isInteger(Number(e)),t.find=(e,t)=>e.nodes.find(e=>e.type===t),t.exceedsLimit=(e,r,A=1,n)=>!1!==n&&(!(!t.isInteger(e)||!t.isInteger(r))&&(Number(r)-Number(e))/Number(A)>=n),t.escapeNode=(e,t=0,r)=>{let A=e.nodes[t];A&&(r&&A.type===r||"open"===A.type||"close"===A.type)&&!0!==A.escaped&&(A.value="\\"+A.value,A.escaped=!0)},t.encloseBrace=e=>"brace"===e.type&&(e.commas>>0+e.ranges>>0==0&&(e.invalid=!0,!0)),t.isInvalidBrace=e=>"brace"===e.type&&(!(!0!==e.invalid&&!e.dollar)||(e.commas>>0+e.ranges>>0==0||!0!==e.open||!0!==e.close)&&(e.invalid=!0,!0)),t.isOpenOrClose=e=>"open"===e.type||"close"===e.type||(!0===e.open||!0===e.close),t.reduce=e=>e.reduce((e,t)=>("text"===t.type&&e.push(t.value),"range"===t.type&&(t.type="text"),e),[]),t.flatten=(...e)=>{const t=[],r=e=>{for(let A=0;A{"use strict";const{V4MAPPED:A,ADDRCONFIG:n,ALL:o,promises:{Resolver:i},lookup:s}=r(40881),{promisify:a}=r(31669),c=r(12087),g=Symbol("cacheableLookupCreateConnection"),l=Symbol("cacheableLookupInstance"),u=Symbol("expires"),h="number"==typeof o,p=e=>{if(!e||"function"!=typeof e.createConnection)throw new Error("Expected an Agent instance as the first argument")},d=()=>{let e=!1,t=!1;for(const r of Object.values(c.networkInterfaces()))for(const A of r)if(!A.internal&&("IPv6"===A.family?t=!0:e=!0,e&&t))return{has4:e,has6:t};return{has4:e,has6:t}},C={ttl:!0},f={all:!0};class I{constructor({cache:e=new Map,maxTtl:t=1/0,fallbackDuration:r=3600,errorTtl:A=.15,resolver:n=new i,lookup:o=s}={}){if(this.maxTtl=t,this.errorTtl=A,this._cache=e,this._resolver=n,this._dnsLookup=a(o),this._resolver instanceof i?(this._resolve4=this._resolver.resolve4.bind(this._resolver),this._resolve6=this._resolver.resolve6.bind(this._resolver)):(this._resolve4=a(this._resolver.resolve4.bind(this._resolver)),this._resolve6=a(this._resolver.resolve6.bind(this._resolver))),this._iface=d(),this._pending={},this._nextRemovalTime=!1,this._hostnamesToFallback=new Set,r<1)this._fallback=!1;else{this._fallback=!0;const e=setInterval(()=>{this._hostnamesToFallback.clear()},1e3*r);e.unref&&e.unref()}this.lookup=this.lookup.bind(this),this.lookupAsync=this.lookupAsync.bind(this)}set servers(e){this.clear(),this._resolver.setServers(e)}get servers(){return this._resolver.getServers()}lookup(e,t,r){if("function"==typeof t?(r=t,t={}):"number"==typeof t&&(t={family:t}),!r)throw new Error("Callback must be a function.");this.lookupAsync(e,t).then(e=>{t.all?r(null,e):r(null,e.address,e.family,e.expires,e.ttl)},r)}async lookupAsync(e,t={}){"number"==typeof t&&(t={family:t});let r=await this.query(e);if(6===t.family){const e=r.filter(e=>6===e.family);t.hints&A&&(h&&t.hints&o||0===e.length)?(e=>{for(const t of e)6!==t.family&&(t.address="::ffff:"+t.address,t.family=6)})(r):r=e}else 4===t.family&&(r=r.filter(e=>4===e.family));if(t.hints&n){const{_iface:e}=this;r=r.filter(t=>6===t.family?e.has6:e.has4)}if(0===r.length){const t=new Error("cacheableLookup ENOTFOUND "+e);throw t.code="ENOTFOUND",t.hostname=e,t}return t.all?r:r[0]}async query(e){let t=await this._cache.get(e);if(!t){const r=this._pending[e];if(r)t=await r;else{const r=this.queryAndCache(e);this._pending[e]=r,t=await r}}return t=t.map(e=>({...e})),t}async _resolve(e){const[t,r]=await Promise.all([this._resolve4(e,C),this._resolve6(e,C)].map(e=>(async e=>{try{return await e}catch(e){if("ENODATA"===e.code||"ENOTFOUND"===e.code)return[];throw e}})(e)));let A=0,n=0,o=0;const i=Date.now();for(const e of t)e.family=4,e.expires=i+1e3*e.ttl,A=Math.max(A,e.ttl);for(const e of r)e.family=6,e.expires=i+1e3*e.ttl,n=Math.max(n,e.ttl);return o=t.length>0?r.length>0?Math.min(A,n):A:n,{entries:[...t,...r],cacheTtl:o}}async _lookup(e){try{return{entries:await this._dnsLookup(e,{all:!0}),cacheTtl:0}}catch(e){return{entries:[],cacheTtl:0}}}async _set(e,t,r){if(this.maxTtl>0&&r>0){r=1e3*Math.min(r,this.maxTtl),t[u]=Date.now()+r;try{await this._cache.set(e,t,r)}catch(e){this.lookupAsync=async()=>{const t=new Error("Cache Error. Please recreate the CacheableLookup instance.");throw t.cause=e,t}}A=this._cache,Symbol.iterator in A&&this._tick(r)}var A}async queryAndCache(e){if(this._hostnamesToFallback.has(e))return this._dnsLookup(e,f);try{let t=await this._resolve(e);0===t.entries.length&&this._fallback&&(t=await this._lookup(e),0!==t.entries.length&&this._hostnamesToFallback.add(e));const r=0===t.entries.length?this.errorTtl:t.cacheTtl;return await this._set(e,t.entries,r),delete this._pending[e],t.entries}catch(t){throw delete this._pending[e],t}}_tick(e){const t=this._nextRemovalTime;(!t||e{this._nextRemovalTime=!1;let e=1/0;const t=Date.now();for(const[r,A]of this._cache){const n=A[u];t>=n?this._cache.delete(r):n("lookup"in t||(t.lookup=this.lookup),e[g](t,r))}uninstall(e){if(p(e),e[g]){if(e[l]!==this)throw new Error("The agent is not owned by this CacheableLookup instance");e.createConnection=e[g],delete e[g],delete e[l]}}updateInterfaceInfo(){const{_iface:e}=this;this._iface=d(),(e.has4&&!this._iface.has4||e.has6&&!this._iface.has6)&&this._cache.clear()}clear(e){e?this._cache.delete(e):this._cache.clear()}}e.exports=I,e.exports.default=I},11200:(e,t,r)=>{"use strict";const A=r(28614),n=r(78835),o=r(19793),i=r(58764),s=r(86834),a=r(48491),c=r(55737),g=r(15751),l=r(72515);class u{constructor(e,t){if("function"!=typeof e)throw new TypeError("Parameter `request` must be a function");return this.cache=new l({uri:"string"==typeof t&&t,store:"string"!=typeof t&&t,namespace:"cacheable-request"}),this.createCacheableRequest(e)}createCacheableRequest(e){return(t,r)=>{let l;if("string"==typeof t)l=p(n.parse(t)),t={};else if(t instanceof n.URL)l=p(n.parse(t.toString())),t={};else{const[e,...r]=(t.path||"").split("?"),A=r.length>0?"?"+r.join("?"):"";l=p({...t,pathname:e,search:A})}(t={headers:{},method:"GET",cache:!0,strictTtl:!1,automaticFailover:!1,...t,...h(l)}).headers=c(t.headers);const d=new A,C=o(n.format(l),{stripWWW:!1,removeTrailingSlash:!1,stripAuthentication:!1}),f=`${t.method}:${C}`;let I=!1,E=!1;const B=t=>{E=!0;let A,n=!1;const o=new Promise(e=>{A=()=>{n||(n=!0,e())}}),c=e=>{if(I&&!t.forceRefresh){e.status=e.statusCode;const r=s.fromObject(I.cachePolicy).revalidatedPolicy(t,e);if(!r.modified){const t=r.policy.responseHeaders();(e=new a(I.statusCode,t,I.body,I.url)).cachePolicy=r.policy,e.fromCache=!0}}let A;e.fromCache||(e.cachePolicy=new s(t,e,t),e.fromCache=!1),t.cache&&e.cachePolicy.storable()?(A=g(e),(async()=>{try{const r=i.buffer(e);if(await Promise.race([o,new Promise(t=>e.once("end",t))]),n)return;const A=await r,s={cachePolicy:e.cachePolicy.toObject(),url:e.url,statusCode:e.fromCache?I.statusCode:e.statusCode,body:A};let a=t.strictTtl?e.cachePolicy.timeToLive():void 0;t.maxTtl&&(a=a?Math.min(a,t.maxTtl):t.maxTtl),await this.cache.set(f,s,a)}catch(e){d.emit("error",new u.CacheError(e))}})()):t.cache&&I&&(async()=>{try{await this.cache.delete(f)}catch(e){d.emit("error",new u.CacheError(e))}})(),d.emit("response",A||e),"function"==typeof r&&r(A||e)};try{const r=e(t,c);r.once("error",A),r.once("abort",A),d.emit("request",r)}catch(e){d.emit("error",new u.RequestError(e))}};return(async()=>{const e=async e=>{await Promise.resolve();const t=e.cache?await this.cache.get(f):void 0;if(void 0===t)return B(e);const A=s.fromObject(t.cachePolicy);if(A.satisfiesWithoutRevalidation(e)&&!e.forceRefresh){const e=A.responseHeaders(),n=new a(t.statusCode,e,t.body,t.url);n.cachePolicy=A,n.fromCache=!0,d.emit("response",n),"function"==typeof r&&r(n)}else I=t,e.headers=A.revalidationHeaders(e),B(e)},A=e=>d.emit("error",new u.CacheError(e));this.cache.once("error",A),d.on("response",()=>this.cache.removeListener("error",A));try{await e(t)}catch(e){t.automaticFailover&&!E&&B(t),d.emit("error",new u.CacheError(e))}})(),d}}}function h(e){const t={...e};return t.path=`${e.pathname||"/"}${e.search||""}`,delete t.pathname,delete t.search,t}function p(e){return{protocol:e.protocol,auth:e.auth,hostname:e.hostname||e.host||"localhost",port:e.port,pathname:e.pathname,search:e.search}}u.RequestError=class extends Error{constructor(e){super(e.message),this.name="RequestError",Object.assign(this,e)}},u.CacheError=class extends Error{constructor(e){super(e.message),this.name="CacheError",Object.assign(this,e)}},e.exports=u},54738:e=>{"use strict";const t=(e,t)=>{if("string"!=typeof e&&!Array.isArray(e))throw new TypeError("Expected the input to be `string | string[]`");t=Object.assign({pascalCase:!1},t);if(0===(e=Array.isArray(e)?e.map(e=>e.trim()).filter(e=>e.length).join("-"):e.trim()).length)return"";if(1===e.length)return t.pascalCase?e.toUpperCase():e.toLowerCase();return e!==e.toLowerCase()&&(e=(e=>{let t=!1,r=!1,A=!1;for(let n=0;nt.toUpperCase()).replace(/\d+(\w|$)/g,e=>e.toUpperCase()),r=e,t.pascalCase?r.charAt(0).toUpperCase()+r.slice(1):r;var r};e.exports=t,e.exports.default=t},95882:(e,t,r)=>{"use strict";const A=r(18483),{stdout:n,stderr:o}=r(59428),{stringReplaceAll:i,stringEncaseCRLFWithFirstIndex:s}=r(73327),a=["ansi","ansi","ansi256","ansi16m"],c=Object.create(null);class g{constructor(e){return l(e)}}const l=e=>{const t={};return((e,t={})=>{if(t.level>3||t.level<0)throw new Error("The `level` option should be an integer from 0 to 3");const r=n?n.level:0;e.level=void 0===t.level?r:t.level})(t,e),t.template=(...e)=>E(t.template,...e),Object.setPrototypeOf(t,u.prototype),Object.setPrototypeOf(t.template,t),t.template.constructor=()=>{throw new Error("`chalk.constructor()` is deprecated. Use `new chalk.Instance()` instead.")},t.template.Instance=g,t.template};function u(e){return l(e)}for(const[e,t]of Object.entries(A))c[e]={get(){const r=C(this,d(t.open,t.close,this._styler),this._isEmpty);return Object.defineProperty(this,e,{value:r}),r}};c.visible={get(){const e=C(this,this._styler,!0);return Object.defineProperty(this,"visible",{value:e}),e}};const h=["rgb","hex","keyword","hsl","hsv","hwb","ansi","ansi256"];for(const e of h)c[e]={get(){const{level:t}=this;return function(...r){const n=d(A.color[a[t]][e](...r),A.color.close,this._styler);return C(this,n,this._isEmpty)}}};for(const e of h){c["bg"+e[0].toUpperCase()+e.slice(1)]={get(){const{level:t}=this;return function(...r){const n=d(A.bgColor[a[t]][e](...r),A.bgColor.close,this._styler);return C(this,n,this._isEmpty)}}}}const p=Object.defineProperties(()=>{},{...c,level:{enumerable:!0,get(){return this._generator.level},set(e){this._generator.level=e}}}),d=(e,t,r)=>{let A,n;return void 0===r?(A=e,n=t):(A=r.openAll+e,n=t+r.closeAll),{open:e,close:t,openAll:A,closeAll:n,parent:r}},C=(e,t,r)=>{const A=(...e)=>f(A,1===e.length?""+e[0]:e.join(" "));return A.__proto__=p,A._generator=e,A._styler=t,A._isEmpty=r,A},f=(e,t)=>{if(e.level<=0||!t)return e._isEmpty?"":t;let r=e._styler;if(void 0===r)return t;const{openAll:A,closeAll:n}=r;if(-1!==t.indexOf(""))for(;void 0!==r;)t=i(t,r.close,r.open),r=r.parent;const o=t.indexOf("\n");return-1!==o&&(t=s(t,n,A,o)),A+t+n};let I;const E=(e,...t)=>{const[A]=t;if(!Array.isArray(A))return t.join(" ");const n=t.slice(1),o=[A.raw[0]];for(let e=1;e{"use strict";const t=/(?:\\(u(?:[a-f\d]{4}|\{[a-f\d]{1,6}\})|x[a-f\d]{2}|.))|(?:\{(~)?(\w+(?:\([^)]*\))?(?:\.\w+(?:\([^)]*\))?)*)(?:[ \t]|(?=\r?\n)))|(\})|((?:.|[\r\n\f])+?)/gi,r=/(?:^|\.)(\w+)(?:\(([^)]*)\))?/g,A=/^(['"])((?:\\.|(?!\1)[^\\])*)\1$/,n=/\\(u(?:[a-f\d]{4}|\{[a-f\d]{1,6}\})|x[a-f\d]{2}|.)|([^\\])/gi,o=new Map([["n","\n"],["r","\r"],["t","\t"],["b","\b"],["f","\f"],["v","\v"],["0","\0"],["\\","\\"],["e",""],["a",""]]);function i(e){const t="u"===e[0],r="{"===e[1];return t&&!r&&5===e.length||"x"===e[0]&&3===e.length?String.fromCharCode(parseInt(e.slice(1),16)):t&&r?String.fromCodePoint(parseInt(e.slice(2,-1),16)):o.get(e)||e}function s(e,t){const r=[],o=t.trim().split(/\s*,\s*/g);let s;for(const t of o){const o=Number(t);if(Number.isNaN(o)){if(!(s=t.match(A)))throw new Error(`Invalid Chalk template style argument: ${t} (in style '${e}')`);r.push(s[2].replace(n,(e,t,r)=>t?i(t):r))}else r.push(o)}return r}function a(e){r.lastIndex=0;const t=[];let A;for(;null!==(A=r.exec(e));){const e=A[1];if(A[2]){const r=s(e,A[2]);t.push([e].concat(r))}else t.push([e])}return t}function c(e,t){const r={};for(const e of t)for(const t of e.styles)r[t[0]]=e.inverse?null:t.slice(1);let A=e;for(const[e,t]of Object.entries(r))if(Array.isArray(t)){if(!(e in A))throw new Error("Unknown Chalk style: "+e);A=t.length>0?A[e](...t):A[e]}return A}e.exports=(e,r)=>{const A=[],n=[];let o=[];if(r.replace(t,(t,r,s,g,l,u)=>{if(r)o.push(i(r));else if(g){const t=o.join("");o=[],n.push(0===A.length?t:c(e,A)(t)),A.push({inverse:s,styles:a(g)})}else if(l){if(0===A.length)throw new Error("Found extraneous } in Chalk template literal");n.push(c(e,A)(o.join(""))),o=[],A.pop()}else o.push(u)}),n.push(o.join("")),A.length>0){const e=`Chalk template literal is missing ${A.length} closing bracket${1===A.length?"":"s"} (\`}\`)`;throw new Error(e)}return n.join("")}},73327:e=>{"use strict";e.exports={stringReplaceAll:(e,t,r)=>{let A=e.indexOf(t);if(-1===A)return e;const n=t.length;let o=0,i="";do{i+=e.substr(o,A-o)+t+r,o=A+n,A=e.indexOf(t,o)}while(-1!==A);return i+=e.substr(o),i},stringEncaseCRLFWithFirstIndex:(e,t,r,A)=>{let n=0,o="";do{const i="\r"===e[A-1];o+=e.substr(n,(i?A-1:A)-n)+t+(i?"\r\n":"\n")+r,n=A+1,A=e.indexOf("\n",n)}while(-1!==A);return o+=e.substr(n),o}}},5864:(e,t,r)=>{"use strict";var A=r(85832),n=process.env;function o(e){return"string"==typeof e?!!n[e]:Object.keys(e).every((function(t){return n[t]===e[t]}))}Object.defineProperty(t,"_vendors",{value:A.map((function(e){return e.constant}))}),t.name=null,t.isPR=null,A.forEach((function(e){var r=(Array.isArray(e.env)?e.env:[e.env]).every((function(e){return o(e)}));if(t[e.constant]=r,r)switch(t.name=e.name,typeof e.pr){case"string":t.isPR=!!n[e.pr];break;case"object":"env"in e.pr?t.isPR=e.pr.env in n&&n[e.pr.env]!==e.pr.ne:"any"in e.pr?t.isPR=e.pr.any.some((function(e){return!!n[e]})):t.isPR=o(e.pr);break;default:t.isPR=null}})),t.isCI=!!(n.CI||n.CONTINUOUS_INTEGRATION||n.BUILD_NUMBER||n.RUN_ID||t.name)},85832:e=>{"use strict";e.exports=JSON.parse('[{"name":"AppVeyor","constant":"APPVEYOR","env":"APPVEYOR","pr":"APPVEYOR_PULL_REQUEST_NUMBER"},{"name":"Azure Pipelines","constant":"AZURE_PIPELINES","env":"SYSTEM_TEAMFOUNDATIONCOLLECTIONURI","pr":"SYSTEM_PULLREQUEST_PULLREQUESTID"},{"name":"Bamboo","constant":"BAMBOO","env":"bamboo_planKey"},{"name":"Bitbucket Pipelines","constant":"BITBUCKET","env":"BITBUCKET_COMMIT","pr":"BITBUCKET_PR_ID"},{"name":"Bitrise","constant":"BITRISE","env":"BITRISE_IO","pr":"BITRISE_PULL_REQUEST"},{"name":"Buddy","constant":"BUDDY","env":"BUDDY_WORKSPACE_ID","pr":"BUDDY_EXECUTION_PULL_REQUEST_ID"},{"name":"Buildkite","constant":"BUILDKITE","env":"BUILDKITE","pr":{"env":"BUILDKITE_PULL_REQUEST","ne":"false"}},{"name":"CircleCI","constant":"CIRCLE","env":"CIRCLECI","pr":"CIRCLE_PULL_REQUEST"},{"name":"Cirrus CI","constant":"CIRRUS","env":"CIRRUS_CI","pr":"CIRRUS_PR"},{"name":"AWS CodeBuild","constant":"CODEBUILD","env":"CODEBUILD_BUILD_ARN"},{"name":"Codeship","constant":"CODESHIP","env":{"CI_NAME":"codeship"}},{"name":"Drone","constant":"DRONE","env":"DRONE","pr":{"DRONE_BUILD_EVENT":"pull_request"}},{"name":"dsari","constant":"DSARI","env":"DSARI"},{"name":"GitLab CI","constant":"GITLAB","env":"GITLAB_CI"},{"name":"GoCD","constant":"GOCD","env":"GO_PIPELINE_LABEL"},{"name":"Hudson","constant":"HUDSON","env":"HUDSON_URL"},{"name":"Jenkins","constant":"JENKINS","env":["JENKINS_URL","BUILD_ID"],"pr":{"any":["ghprbPullId","CHANGE_ID"]}},{"name":"Magnum CI","constant":"MAGNUM","env":"MAGNUM"},{"name":"Netlify CI","constant":"NETLIFY","env":"NETLIFY_BUILD_BASE","pr":{"env":"PULL_REQUEST","ne":"false"}},{"name":"Sail CI","constant":"SAIL","env":"SAILCI","pr":"SAIL_PULL_REQUEST_NUMBER"},{"name":"Semaphore","constant":"SEMAPHORE","env":"SEMAPHORE","pr":"PULL_REQUEST_NUMBER"},{"name":"Shippable","constant":"SHIPPABLE","env":"SHIPPABLE","pr":{"IS_PULL_REQUEST":"true"}},{"name":"Solano CI","constant":"SOLANO","env":"TDDIUM","pr":"TDDIUM_PR_ID"},{"name":"Strider CD","constant":"STRIDER","env":"STRIDER"},{"name":"TaskCluster","constant":"TASKCLUSTER","env":["TASK_ID","RUN_ID"]},{"name":"TeamCity","constant":"TEAMCITY","env":"TEAMCITY_VERSION"},{"name":"Travis CI","constant":"TRAVIS","env":"TRAVIS","pr":{"env":"TRAVIS_PULL_REQUEST","ne":"false"}}]')},40822:(e,t,r)=>{"use strict";r.r(t),r.d(t,{Cli:()=>Y,Command:()=>M,UsageError:()=>a});const A=/^(-h|--help)(?:=([0-9]+))?$/,n=/^(--[a-z]+(?:-[a-z]+)*|-[a-zA-Z]+)$/,o=/^-[a-zA-Z]{2,}$/,i=/^([^=]+)=([\s\S]*)$/,s="1"===process.env.DEBUG_CLI;class a extends Error{constructor(e){super(e),this.clipanion={type:"usage"},this.name="UsageError"}}class c extends Error{constructor(e,t){if(super(),this.input=e,this.candidates=t,this.clipanion={type:"none"},this.name="UnknownSyntaxError",0===this.candidates.length)this.message="Command not found, but we're not sure what's the alternative.";else if(1===this.candidates.length&&null!==this.candidates[0].reason){const[{usage:e,reason:t}]=this.candidates;this.message=`${t}\n\n$ ${e}`}else if(1===this.candidates.length){const[{usage:t}]=this.candidates;this.message=`Command not found; did you mean:\n\n$ ${t}\n${l(e)}`}else this.message=`Command not found; did you mean one of:\n\n${this.candidates.map(({usage:e},t)=>`${(t+".").padStart(4)} ${e}`).join("\n")}\n\n${l(e)}`}}class g extends Error{constructor(e,t){super(),this.input=e,this.usages=t,this.clipanion={type:"none"},this.name="AmbiguousSyntaxError",this.message=`Cannot find who to pick amongst the following alternatives:\n\n${this.usages.map((e,t)=>`${(t+".").padStart(4)} ${e}`).join("\n")}\n\n${l(e)}`}}const l=e=>"While running "+e.filter(e=>"\0"!==e).map(e=>{const t=JSON.stringify(e);return e.match(/\s/)||0===e.length||t!==`"${e}"`?t:e}).join(" ");function u(e){s&&console.log(e)}const h={candidateUsage:null,errorMessage:null,ignoreOptions:!1,path:[],positionals:[],options:[],remainder:null,selectedIndex:-1};function p(e,t){return e.nodes.push(t),e.nodes.length-1}function d(e,t,r=!1){u("Running a vm on "+JSON.stringify(t));let A=[{node:0,state:{candidateUsage:null,errorMessage:null,ignoreOptions:!1,options:[],path:[],positionals:[],remainder:null,selectedIndex:null}}];!function(e,{prefix:t=""}={}){u(t+"Nodes are:");for(let r=0;r2!==e).map(({state:e})=>({usage:e.candidateUsage,reason:null})));if(s.every(({node:e})=>2===e))throw new c(t,s.map(({state:e})=>({usage:e.candidateUsage,reason:e.errorMessage})));A=I(s)}if(A.length>0){u(" Results:");for(const e of A)u(` - ${e.node} -> ${JSON.stringify(e.state)}`)}else u(" No results");return A}function C(e,t){if(null!==t.selectedIndex)return!0;if(Object.prototype.hasOwnProperty.call(e.statics,"\0"))for(const{to:t}of e.statics["\0"])if(1===t)return!0;return!1}function f(e,t){return function(e,t){const r=t.filter(e=>null!==e.selectedIndex);if(0===r.length)throw new Error;let A=0;for(const e of r)e.path.length>A&&(A=e.path.length);const n=r.filter(e=>e.path.length===A),o=e=>e.positionals.filter(({extra:e})=>!e).length+e.options.length,i=n.map(e=>({state:e,positionalCount:o(e)}));let s=0;for(const{positionalCount:e}of i)e>s&&(s=e);const a=function(e){const t=[],r=[];for(const A of e)-1===A.selectedIndex?r.push(A):t.push(A);r.length>0&&t.push(Object.assign(Object.assign({},h),{path:E(...r.map(e=>e.path)),options:r.reduce((e,t)=>e.concat(t.options),[])}));return t}(i.filter(({positionalCount:e})=>e===s).map(({state:e})=>e));if(a.length>1)throw new g(e,a.map(e=>e.candidateUsage));return a[0]}(t,d(e,[...t,"\0"]).map(({state:e})=>e))}function I(e){let t=0;for(const{state:r}of e)r.path.length>t&&(t=r.path.length);return e.filter(({state:e})=>e.path.length===t)}function E(e,t,...r){return void 0===t?Array.from(e):E(e.filter((e,r)=>e===t[r]),...r)}function B(e){return 1===e||2===e}function y(e,t=0){return{to:B(e.to)?e.to:e.to>2?e.to+t-2:e.to+t,reducer:e.reducer}}function m(e,t=0){const r={dynamics:[],shortcuts:[],statics:{}};for(const[A,n]of e.dynamics)r.dynamics.push([A,y(n,t)]);for(const A of e.shortcuts)r.shortcuts.push(y(A,t));for(const[A,n]of Object.entries(e.statics))r.statics[A]=n.map(e=>y(e,t));return r}function w(e,t,r,A,n){e.nodes[t].dynamics.push([r,{to:A,reducer:n}])}function Q(e,t,r,A){e.nodes[t].shortcuts.push({to:r,reducer:A})}function D(e,t,r,A,n){(Object.prototype.hasOwnProperty.call(e.nodes[t].statics,r)?e.nodes[t].statics[r]:e.nodes[t].statics[r]=[]).push({to:A,reducer:n})}function b(e,t,r,A){if(Array.isArray(t)){const[n,...o]=t;return e[n](r,A,...o)}return e[t](r,A)}function v(e,t){const r=Array.isArray(e)?S[e[0]]:S[e];if(void 0===r.suggest)return null;const A=Array.isArray(e)?e.slice(1):[];return r.suggest(t,...A)}const S={always:()=>!0,isOptionLike:(e,t)=>!e.ignoreOptions&&t.startsWith("-"),isNotOptionLike:(e,t)=>e.ignoreOptions||!t.startsWith("-"),isOption:(e,t,r,A)=>!e.ignoreOptions&&t===r,isBatchOption:(e,t,r)=>!e.ignoreOptions&&o.test(t)&&[...t.slice(1)].every(e=>r.includes("-"+e)),isBoundOption:(e,t,r,A)=>{const o=t.match(i);return!e.ignoreOptions&&!!o&&n.test(o[1])&&r.includes(o[1])&&A.filter(e=>e.names.includes(o[1])).every(e=>e.allowBinding)},isNegatedOption:(e,t,r)=>!e.ignoreOptions&&t==="--no-"+r.slice(2),isHelp:(e,t)=>!e.ignoreOptions&&A.test(t),isUnsupportedOption:(e,t,r)=>!e.ignoreOptions&&t.startsWith("-")&&n.test(t)&&!r.includes(t),isInvalidOption:(e,t)=>!e.ignoreOptions&&t.startsWith("-")&&!n.test(t)};S.isOption.suggest=(e,t,r=!0)=>r?null:[t];const k={setCandidateUsage:(e,t,r)=>Object.assign(Object.assign({},e),{candidateUsage:r}),setSelectedIndex:(e,t,r)=>Object.assign(Object.assign({},e),{selectedIndex:r}),pushBatch:(e,t)=>Object.assign(Object.assign({},e),{options:e.options.concat([...t.slice(1)].map(e=>({name:"-"+e,value:!0})))}),pushBound:(e,t)=>{const[,r,A]=t.match(i);return Object.assign(Object.assign({},e),{options:e.options.concat({name:r,value:A})})},pushPath:(e,t)=>Object.assign(Object.assign({},e),{path:e.path.concat(t)}),pushPositional:(e,t)=>Object.assign(Object.assign({},e),{positionals:e.positionals.concat({value:t,extra:!1})}),pushExtra:(e,t)=>Object.assign(Object.assign({},e),{positionals:e.positionals.concat({value:t,extra:!0})}),pushExtraNoLimits:(e,t)=>Object.assign(Object.assign({},e),{positionals:e.positionals.concat({value:t,extra:N})}),pushTrue:(e,t,r=t)=>Object.assign(Object.assign({},e),{options:e.options.concat({name:t,value:!0})}),pushFalse:(e,t,r=t)=>Object.assign(Object.assign({},e),{options:e.options.concat({name:r,value:!1})}),pushUndefined:(e,t)=>Object.assign(Object.assign({},e),{options:e.options.concat({name:t,value:void 0})}),pushStringValue:(e,t)=>{var r;const A=Object.assign(Object.assign({},e),{options:[...e.options]}),n=e.options[e.options.length-1];return n.value=(null!==(r=n.value)&&void 0!==r?r:[]).concat([t]),A},setStringValue:(e,t)=>{const r=Object.assign(Object.assign({},e),{options:[...e.options]});return e.options[e.options.length-1].value=t,r},inhibateOptions:e=>Object.assign(Object.assign({},e),{ignoreOptions:!0}),useHelp:(e,t,r)=>{const[,n,o]=t.match(A);return void 0!==o?Object.assign(Object.assign({},e),{options:[{name:"-c",value:String(r)},{name:"-i",value:o}]}):Object.assign(Object.assign({},e),{options:[{name:"-c",value:String(r)}]})},setError:(e,t,r)=>"\0"===t?Object.assign(Object.assign({},e),{errorMessage:r+"."}):Object.assign(Object.assign({},e),{errorMessage:`${r} ("${t}").`}),setOptionArityError:(e,t)=>{const r=e.options[e.options.length-1];return Object.assign(Object.assign({},e),{errorMessage:`Not enough arguments to option ${r.name}.`})}},N=Symbol();class F{constructor(e,t){this.allOptionNames=[],this.arity={leading:[],trailing:[],extra:[],proxy:!1},this.options=[],this.paths=[],this.cliIndex=e,this.cliOpts=t}addPath(e){this.paths.push(e)}setArity({leading:e=this.arity.leading,trailing:t=this.arity.trailing,extra:r=this.arity.extra,proxy:A=this.arity.proxy}){Object.assign(this.arity,{leading:e,trailing:t,extra:r,proxy:A})}addPositional({name:e="arg",required:t=!0}={}){if(!t&&this.arity.extra===N)throw new Error("Optional parameters cannot be declared when using .rest() or .proxy()");if(!t&&this.arity.trailing.length>0)throw new Error("Optional parameters cannot be declared after the required trailing positional arguments");t||this.arity.extra===N?this.arity.extra!==N&&0===this.arity.extra.length?this.arity.leading.push(e):this.arity.trailing.push(e):this.arity.extra.push(e)}addRest({name:e="arg",required:t=0}={}){if(this.arity.extra===N)throw new Error("Infinite lists cannot be declared multiple times in the same command");if(this.arity.trailing.length>0)throw new Error("Infinite lists cannot be declared after the required trailing positional arguments");for(let r=0;r1)throw new Error("The arity cannot be higher than 1 when the option only supports the --arg=value syntax");if(!Number.isInteger(r))throw new Error("The arity must be an integer, got "+r);if(r<0)throw new Error("The arity must be positive, got "+r);this.allOptionNames.push(...e),this.options.push({names:e,description:t,arity:r,hidden:A,allowBinding:n})}setContext(e){this.context=e}usage({detailed:e=!0,inlineOptions:t=!0}={}){const r=[this.cliOpts.binaryName],A=[];if(this.paths.length>0&&r.push(...this.paths[0]),e){for(const{names:e,arity:n,hidden:o,description:i}of this.options){if(o)continue;const s=[];for(let e=0;e`<${e}>`)),this.arity.extra===N?r.push("..."):r.push(...this.arity.extra.map(e=>`[${e}]`)),r.push(...this.arity.trailing.map(e=>`<${e}>`))}return{usage:r.join(" "),options:A}}compile(){if(void 0===this.context)throw new Error("Assertion failed: No context attached");const e={nodes:[{dynamics:[],shortcuts:[],statics:{}},{dynamics:[],shortcuts:[],statics:{}},{dynamics:[],shortcuts:[],statics:{}}]};let t=0;t=p(e,{dynamics:[],shortcuts:[],statics:{}}),D(e,0,"",t,["setCandidateUsage",this.usage().usage]);const r=this.arity.proxy?"always":"isNotOptionLike",A=this.paths.length>0?this.paths:[[]];for(const n of A){let A=t;if(n.length>0){const t=p(e,{dynamics:[],shortcuts:[],statics:{}});Q(e,A,t),this.registerOptions(e,t),A=t}for(let t=0;t0||!this.arity.proxy){const t=p(e,{dynamics:[],shortcuts:[],statics:{}});w(e,A,"isHelp",t,["useHelp",this.cliIndex]),D(e,t,"\0",1,["setSelectedIndex",-1]),this.registerOptions(e,A)}this.arity.leading.length>0&&D(e,A,"\0",2,["setError","Not enough positional arguments"]);let o=A;for(let t=0;t0||t+1!==this.arity.leading.length)&&D(e,r,"\0",2,["setError","Not enough positional arguments"]),w(e,o,"isNotOptionLike",r,"pushPositional"),o=r}let i=o;if(this.arity.extra===N||this.arity.extra.length>0){const t=p(e,{dynamics:[],shortcuts:[],statics:{}});if(Q(e,o,t),this.arity.extra===N){const A=p(e,{dynamics:[],shortcuts:[],statics:{}});this.arity.proxy||this.registerOptions(e,A),w(e,o,r,A,"pushExtraNoLimits"),w(e,A,r,A,"pushExtraNoLimits"),Q(e,A,t)}else for(let A=0;A0&&D(e,i,"\0",2,["setError","Not enough positional arguments"]);let s=i;for(let t=0;tt.length>e.length?t:e,"");if(0===r.arity)for(const n of r.names)w(e,t,["isOption",n,r.hidden||n!==A],t,"pushTrue"),n.startsWith("--")&&!n.startsWith("--no-")&&w(e,t,["isNegatedOption",n],t,["pushFalse",n]);else{let n=p(e,{dynamics:[],shortcuts:[],statics:{}});for(const o of r.names)w(e,t,["isOption",o,r.hidden||o!==A],n,"pushUndefined");for(let t=0;t=0&&e{if(t.has(A))return;t.add(A);const n=e.nodes[A];for(const e of Object.values(n.statics))for(const{to:t}of e)r(t);for(const[,{to:e}]of n.dynamics)r(e);for(const{to:e}of n.shortcuts)r(e);const o=new Set(n.shortcuts.map(({to:e})=>e));for(;n.shortcuts.length>0;){const{to:t}=n.shortcuts.shift(),r=e.nodes[t];for(const[e,t]of Object.entries(r.statics)){let r=Object.prototype.hasOwnProperty.call(n.statics,e)?n.statics[e]:n.statics[e]=[];for(const e of t)r.some(({to:t})=>e.to===t)||r.push(e)}for(const[e,t]of r.dynamics)n.dynamics.some(([r,{to:A}])=>e===r&&t.to===A)||n.dynamics.push([e,t]);for(const e of r.shortcuts)o.has(e.to)||(n.shortcuts.push(e),o.add(e.to))}};r(0)}(r),{machine:r,contexts:t,process:e=>f(r,e),suggest:(e,t)=>function(e,t,r){const A=r&&t.length>0?[""]:[],n=d(e,t,r),o=[],i=new Set,s=(t,r,A=!0)=>{let n=[r];for(;n.length>0;){const r=n;n=[];for(const o of r){const r=e.nodes[o],i=Object.keys(r.statics);for(const e of Object.keys(r.statics)){const e=i[0];for(const{to:o,reducer:i}of r.statics[e])"pushPath"===i&&(A||t.push(e),n.push(o))}}A=!1}const s=JSON.stringify(t);i.has(s)||(o.push(t),i.add(s))};for(const{node:t,state:r}of n){if(null!==r.remainder){s([r.remainder],t);continue}const n=e.nodes[t],o=C(n,r);for(const[e,r]of Object.entries(n.statics))(o&&"\0"!==e||!e.startsWith("-")&&r.some(({reducer:e})=>"pushPath"===e))&&s([...A,e],t);if(o)for(const[e,{to:o}]of n.dynamics){if(2===o)continue;const n=v(e,r);if(null!==n)for(const e of n)s([...A,e],t)}}return[...o].sort()}(r,e,t)}}}class M{constructor(){this.help=!1}static getMeta(e){const t=e.constructor;return t.meta=Object.prototype.hasOwnProperty.call(t,"meta")?t.meta:{definitions:[],transformers:[(e,t)=>{for(const{name:r,value:A}of e.options)"-h"!==r&&"--help"!==r||(t.help=A)}]}}static resolveMeta(e){const t=[],r=[];for(let A=e;A instanceof M;A=A.__proto__){const e=this.getMeta(A);for(const r of e.definitions)t.push(r);for(const t of e.transformers)r.push(t)}return{definitions:t,transformers:r}}static registerDefinition(e,t){this.getMeta(e).definitions.push(t)}static registerTransformer(e,t){this.getMeta(e).transformers.push(t)}static addPath(...e){this.Path(...e)(this.prototype,"execute")}static addOption(e,t){t(this.prototype,e)}static Path(...e){return(t,r)=>{this.registerDefinition(t,t=>{t.addPath(e)})}}static Boolean(e,{hidden:t=!1,description:r}={}){return(A,n)=>{const o=e.split(",");this.registerDefinition(A,e=>{e.addOption({names:o,arity:0,hidden:t,allowBinding:!1,description:r})}),this.registerTransformer(A,(e,t)=>{for(const{name:r,value:A}of e.options)o.includes(r)&&(t[n]=A)})}}static Counter(e,{hidden:t=!1,description:r}={}){return(A,n)=>{const o=e.split(",");this.registerDefinition(A,e=>{e.addOption({names:o,arity:0,hidden:t,allowBinding:!1,description:r})}),this.registerTransformer(A,(e,t)=>{var r;for(const{name:A,value:i}of e.options)o.includes(A)&&(null!==(r=t[n])&&void 0!==r||(t[n]=0),i?t[n]++:t[n]=0)})}}static String(e={},{arity:t=1,tolerateBoolean:r=!1,hidden:A=!1,description:n}={}){return(o,i)=>{if("string"==typeof e){const s=e.split(",");this.registerDefinition(o,e=>{e.addOption({names:s,arity:r?0:t,hidden:A,description:n})}),this.registerTransformer(o,(e,t)=>{for(const{name:r,value:A}of e.options)s.includes(r)&&(t[i]=A)})}else{const{name:t=i,required:r=!0}=e;this.registerDefinition(o,e=>{e.addPositional({name:t,required:r})}),this.registerTransformer(o,(e,t)=>{for(let A=0;A{if(0===t)throw new Error("Array options are expected to have at least an arity of 1");const i=e.split(",");this.registerDefinition(n,e=>{e.addOption({names:i,arity:t,hidden:r,description:A})}),this.registerTransformer(n,(e,t)=>{for(const{name:r,value:A}of e.options)i.includes(r)&&(t[o]=t[o]||[],t[o].push(A))})}}static Rest({required:e=0}={}){return(t,r)=>{this.registerDefinition(t,t=>{t.addRest({name:r,required:e})}),this.registerTransformer(t,(e,t,A)=>{const n=t=>{const r=e.positionals[t];return r.extra===N||!1===r.extra&&te)})}}static Proxy({required:e=0}={}){return(t,r)=>{this.registerDefinition(t,t=>{t.addProxy({required:e})}),this.registerTransformer(t,(e,t)=>{t[r]=e.positionals.map(({value:e})=>e)})}}static Usage(e){return e}static Schema(e){return e}async catch(e){throw e}async validateAndExecute(){const e=this.constructor.schema;if(void 0!==e)try{await e.validate(this)}catch(e){throw"ValidationError"===e.name&&(e.clipanion={type:"usage"}),e}const t=await this.execute();return void 0!==t?t:0}} +/*! ***************************************************************************** +Copyright (c) Microsoft Corporation. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. +***************************************************************************** */ +function R(e,t,r,A){var n,o=arguments.length,i=o<3?t:null===A?A=Object.getOwnPropertyDescriptor(t,r):A;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,r,A);else for(var s=e.length-1;s>=0;s--)(n=e[s])&&(i=(o<3?n(i):o>3?n(t,r,i):n(t,r))||i);return o>3&&i&&Object.defineProperty(t,r,i),i}M.Entries={};class x extends M{async execute(){this.context.stdout.write(this.cli.usage(null))}}R([M.Path("--help"),M.Path("-h")],x.prototype,"execute",null);class L extends M{async execute(){var e;this.context.stdout.write((null!==(e=this.cli.binaryVersion)&&void 0!==e?e:"")+"\n")}}R([M.Path("--version"),M.Path("-v")],L.prototype,"execute",null);const P={bold:e=>`${e}`,error:e=>`${e}`,code:e=>`${e}`},O={bold:e=>e,error:e=>e,code:e=>e};function U(e,{format:t,paragraphs:r}){return e=(e=(e=(e=(e=e.replace(/\r\n?/g,"\n")).replace(/^[\t ]+|[\t ]+$/gm,"")).replace(/^\n+|\n+$/g,"")).replace(/^-([^\n]*?)\n+/gm,"-$1\n\n")).replace(/\n(\n)?\n*/g,"$1"),r&&(e=e.split(/\n/).map((function(e){let t=e.match(/^[*-][\t ]+(.*)/);return t?t[1].match(/(.{1,78})(?: |$)/g).map((e,t)=>(0===t?"- ":" ")+e).join("\n"):e.match(/(.{1,80})(?: |$)/g).join("\n")})).join("\n\n")),(e=e.replace(/(`+)((?:.|[\n])*?)\1/g,(function(e,r,A){return t.code(r+A+r)})))?e+"\n":""}class T extends M{constructor(e){super(),this.contexts=e,this.commands=[]}static from(e,t){const r=new T(t);r.path=e.path;for(const t of e.options)switch(t.name){case"-c":r.commands.push(Number(t.value));break;case"-i":r.index=Number(t.value)}return r}async execute(){let e=this.commands;if(void 0!==this.index&&this.index>=0&&this.index1){this.context.stdout.write("Multiple commands match your selection:\n"),this.context.stdout.write("\n");let e=0;for(const t of this.commands)this.context.stdout.write(this.cli.usage(this.contexts[t].commandClass,{prefix:(e+++". ").padStart(5)}));this.context.stdout.write("\n"),this.context.stdout.write("Run again with -h= to see the longer details of any of those commands.\n")}}}function j(){return"0"!==process.env.FORCE_COLOR&&("1"===process.env.FORCE_COLOR||!(void 0===process.stdout||!process.stdout.isTTY))}class Y{constructor({binaryLabel:e,binaryName:t="...",binaryVersion:r,enableColors:A=j()}={}){this.registrations=new Map,this.builder=new K({binaryName:t}),this.binaryLabel=e,this.binaryName=t,this.binaryVersion=r,this.enableColors=A}static from(e,t={}){const r=new Y(t);for(const t of e)r.register(t);return r}register(e){const t=this.builder.command();this.registrations.set(e,t.cliIndex);const{definitions:r}=e.resolveMeta(e.prototype);for(const e of r)e(t);t.setContext({commandClass:e})}process(e){const{contexts:t,process:r}=this.builder.compile(),A=r(e);switch(A.selectedIndex){case-1:return T.from(A,t);default:{const{commandClass:e}=t[A.selectedIndex],r=this.registrations.get(e);if(void 0===r)throw new Error("Assertion failed: Expected the command class to have been registered.");const n=this.builder.getBuilderByIndex(r),o=new e;o.path=A.path;const{transformers:i}=e.resolveMeta(e.prototype);for(const e of i)e(A,o,n);return o}}}async run(e,t){let r,A;if(Array.isArray(e))try{r=this.process(e)}catch(e){return t.stdout.write(this.error(e)),1}else r=e;if(r.help)return t.stdout.write(this.usage(r,{detailed:!0})),0;r.context=t,r.cli={binaryLabel:this.binaryLabel,binaryName:this.binaryName,binaryVersion:this.binaryVersion,enableColors:this.enableColors,definitions:()=>this.definitions(),error:(e,t)=>this.error(e,t),process:e=>this.process(e),run:(e,r)=>this.run(e,Object.assign(Object.assign({},t),r)),usage:(e,t)=>this.usage(e,t)};try{A=await r.validateAndExecute().catch(e=>r.catch(e).then(()=>0))}catch(e){return t.stdout.write(this.error(e,{command:r})),1}return A}async runExit(e,t){process.exitCode=await this.run(e,t)}suggest(e,t){const{contexts:r,process:A,suggest:n}=this.builder.compile();return n(e,t)}definitions({colored:e=!1}={}){const t=[];for(const[r,A]of this.registrations){if(void 0===r.usage)continue;const{usage:n}=this.getUsageByIndex(A,{detailed:!1}),{usage:o,options:i}=this.getUsageByIndex(A,{detailed:!0,inlineOptions:!1}),s=void 0!==r.usage.category?U(r.usage.category,{format:this.format(e),paragraphs:!1}):void 0,a=void 0!==r.usage.description?U(r.usage.description,{format:this.format(e),paragraphs:!1}):void 0,c=void 0!==r.usage.details?U(r.usage.details,{format:this.format(e),paragraphs:!0}):void 0,g=void 0!==r.usage.examples?r.usage.examples.map(([t,r])=>[U(t,{format:this.format(e),paragraphs:!1}),r.replace(/\$0/g,this.binaryName)]):void 0;t.push({path:n,usage:o,category:s,description:a,details:c,examples:g,options:i})}return t}usage(e=null,{colored:t,detailed:r=!1,prefix:A="$ "}={}){const n=null!==e&&void 0===e.getMeta?e.constructor:e;let o="";if(n)if(r){const{description:e="",details:r="",examples:i=[]}=n.usage||{};""!==e&&(o+=U(e,{format:this.format(t),paragraphs:!1}).replace(/^./,e=>e.toUpperCase()),o+="\n"),(""!==r||i.length>0)&&(o+=this.format(t).bold("Usage:")+"\n",o+="\n");const{usage:s,options:a}=this.getUsageByRegistration(n,{inlineOptions:!1});if(o+=`${this.format(t).bold(A)}${s}\n`,a.length>0){o+="\n",o+=P.bold("Options:")+"\n";const e=a.reduce((e,t)=>Math.max(e,t.definition.length),0);o+="\n";for(const{definition:r,description:A}of a)o+=` ${r.padEnd(e)} ${U(A,{format:this.format(t),paragraphs:!1})}`}if(""!==r&&(o+="\n",o+=this.format(t).bold("Details:")+"\n",o+="\n",o+=U(r,{format:this.format(t),paragraphs:!0})),i.length>0){o+="\n",o+=this.format(t).bold("Examples:")+"\n";for(let[e,r]of i)o+="\n",o+=U(e,{format:this.format(t),paragraphs:!1}),o+=r.replace(/^/m," "+this.format(t).bold(A)).replace(/\$0/g,this.binaryName)+"\n"}}else{const{usage:e}=this.getUsageByRegistration(n);o+=`${this.format(t).bold(A)}${e}\n`}else{const e=new Map;for(const[r,A]of this.registrations.entries()){if(void 0===r.usage)continue;const n=void 0!==r.usage.category?U(r.usage.category,{format:this.format(t),paragraphs:!1}):null;let o=e.get(n);void 0===o&&e.set(n,o=[]);const{usage:i}=this.getUsageByIndex(A);o.push({commandClass:r,usage:i})}const r=Array.from(e.keys()).sort((e,t)=>null===e?-1:null===t?1:e.localeCompare(t,"en",{usage:"sort",caseFirst:"upper"})),n=void 0!==this.binaryLabel,i=void 0!==this.binaryVersion;n||i?(o+=n&&i?this.format(t).bold(`${this.binaryLabel} - ${this.binaryVersion}`)+"\n\n":n?this.format(t).bold(""+this.binaryLabel)+"\n":this.format(t).bold(""+this.binaryVersion)+"\n",o+=` ${this.format(t).bold(A)}${this.binaryName} \n`):o+=`${this.format(t).bold(A)}${this.binaryName} \n`;for(let A of r){const r=e.get(A).slice().sort((e,t)=>e.usage.localeCompare(t.usage,"en",{usage:"sort",caseFirst:"upper"})),n=null!==A?A.trim():"Where is one of";o+="\n",o+=this.format(t).bold(n+":")+"\n";for(let{commandClass:e,usage:A}of r){const r=e.usage.description||"undocumented";o+="\n",o+=` ${this.format(t).bold(A)}\n`,o+=" "+U(r,{format:this.format(t),paragraphs:!1})}}o+="\n",o+=U("You can also print more details about any of these commands by calling them after adding the `-h,--help` flag right after the command name.",{format:this.format(t),paragraphs:!0})}return o}error(e,{colored:t,command:r=null}={}){e instanceof Error||(e=new Error(`Execution failed with a non-error rejection (rejected value: ${JSON.stringify(e)})`));let A="",n=e.name.replace(/([a-z])([A-Z])/g,"$1 $2");"Error"===n&&(n="Internal Error"),A+=`${this.format(t).error(n)}: ${e.message}\n`;const o=e.clipanion;return void 0!==o?"usage"===o.type&&(A+="\n",A+=this.usage(r)):e.stack&&(A+=e.stack.replace(/^.*\n/,"")+"\n"),A}getUsageByRegistration(e,t){const r=this.registrations.get(e);if(void 0===r)throw new Error("Assertion failed: Unregistered command");return this.getUsageByIndex(r,t)}getUsageByIndex(e,t){return this.builder.getBuilderByIndex(e).usage(t)}format(e=this.enableColors){return e?P:O}}Y.defaultContext={stdin:process.stdin,stdout:process.stdout,stderr:process.stderr},M.Entries.Help=x,M.Entries.Version=L},15751:(e,t,r)=>{"use strict";const A=r(92413).PassThrough,n=r(65007);e.exports=e=>{if(!e||!e.pipe)throw new TypeError("Parameter `response` must be a response stream.");const t=new A;return n(e,t),e.pipe(t)}},15311:(e,t,r)=>{const A=r(93300),n={};for(const e of Object.keys(A))n[A[e]]=e;const o={rgb:{channels:3,labels:"rgb"},hsl:{channels:3,labels:"hsl"},hsv:{channels:3,labels:"hsv"},hwb:{channels:3,labels:"hwb"},cmyk:{channels:4,labels:"cmyk"},xyz:{channels:3,labels:"xyz"},lab:{channels:3,labels:"lab"},lch:{channels:3,labels:"lch"},hex:{channels:1,labels:["hex"]},keyword:{channels:1,labels:["keyword"]},ansi16:{channels:1,labels:["ansi16"]},ansi256:{channels:1,labels:["ansi256"]},hcg:{channels:3,labels:["h","c","g"]},apple:{channels:3,labels:["r16","g16","b16"]},gray:{channels:1,labels:["gray"]}};e.exports=o;for(const e of Object.keys(o)){if(!("channels"in o[e]))throw new Error("missing channels property: "+e);if(!("labels"in o[e]))throw new Error("missing channel labels property: "+e);if(o[e].labels.length!==o[e].channels)throw new Error("channel and label counts mismatch: "+e);const{channels:t,labels:r}=o[e];delete o[e].channels,delete o[e].labels,Object.defineProperty(o[e],"channels",{value:t}),Object.defineProperty(o[e],"labels",{value:r})}o.rgb.hsl=function(e){const t=e[0]/255,r=e[1]/255,A=e[2]/255,n=Math.min(t,r,A),o=Math.max(t,r,A),i=o-n;let s,a;o===n?s=0:t===o?s=(r-A)/i:r===o?s=2+(A-t)/i:A===o&&(s=4+(t-r)/i),s=Math.min(60*s,360),s<0&&(s+=360);const c=(n+o)/2;return a=o===n?0:c<=.5?i/(o+n):i/(2-o-n),[s,100*a,100*c]},o.rgb.hsv=function(e){let t,r,A,n,o;const i=e[0]/255,s=e[1]/255,a=e[2]/255,c=Math.max(i,s,a),g=c-Math.min(i,s,a),l=function(e){return(c-e)/6/g+.5};return 0===g?(n=0,o=0):(o=g/c,t=l(i),r=l(s),A=l(a),i===c?n=A-r:s===c?n=1/3+t-A:a===c&&(n=2/3+r-t),n<0?n+=1:n>1&&(n-=1)),[360*n,100*o,100*c]},o.rgb.hwb=function(e){const t=e[0],r=e[1];let A=e[2];const n=o.rgb.hsl(e)[0],i=1/255*Math.min(t,Math.min(r,A));return A=1-1/255*Math.max(t,Math.max(r,A)),[n,100*i,100*A]},o.rgb.cmyk=function(e){const t=e[0]/255,r=e[1]/255,A=e[2]/255,n=Math.min(1-t,1-r,1-A);return[100*((1-t-n)/(1-n)||0),100*((1-r-n)/(1-n)||0),100*((1-A-n)/(1-n)||0),100*n]},o.rgb.keyword=function(e){const t=n[e];if(t)return t;let r,o=1/0;for(const t of Object.keys(A)){const n=A[t],a=(s=n,((i=e)[0]-s[0])**2+(i[1]-s[1])**2+(i[2]-s[2])**2);a.04045?((t+.055)/1.055)**2.4:t/12.92,r=r>.04045?((r+.055)/1.055)**2.4:r/12.92,A=A>.04045?((A+.055)/1.055)**2.4:A/12.92;return[100*(.4124*t+.3576*r+.1805*A),100*(.2126*t+.7152*r+.0722*A),100*(.0193*t+.1192*r+.9505*A)]},o.rgb.lab=function(e){const t=o.rgb.xyz(e);let r=t[0],A=t[1],n=t[2];r/=95.047,A/=100,n/=108.883,r=r>.008856?r**(1/3):7.787*r+16/116,A=A>.008856?A**(1/3):7.787*A+16/116,n=n>.008856?n**(1/3):7.787*n+16/116;return[116*A-16,500*(r-A),200*(A-n)]},o.hsl.rgb=function(e){const t=e[0]/360,r=e[1]/100,A=e[2]/100;let n,o,i;if(0===r)return i=255*A,[i,i,i];n=A<.5?A*(1+r):A+r-A*r;const s=2*A-n,a=[0,0,0];for(let e=0;e<3;e++)o=t+1/3*-(e-1),o<0&&o++,o>1&&o--,i=6*o<1?s+6*(n-s)*o:2*o<1?n:3*o<2?s+(n-s)*(2/3-o)*6:s,a[e]=255*i;return a},o.hsl.hsv=function(e){const t=e[0];let r=e[1]/100,A=e[2]/100,n=r;const o=Math.max(A,.01);A*=2,r*=A<=1?A:2-A,n*=o<=1?o:2-o;return[t,100*(0===A?2*n/(o+n):2*r/(A+r)),100*((A+r)/2)]},o.hsv.rgb=function(e){const t=e[0]/60,r=e[1]/100;let A=e[2]/100;const n=Math.floor(t)%6,o=t-Math.floor(t),i=255*A*(1-r),s=255*A*(1-r*o),a=255*A*(1-r*(1-o));switch(A*=255,n){case 0:return[A,a,i];case 1:return[s,A,i];case 2:return[i,A,a];case 3:return[i,s,A];case 4:return[a,i,A];case 5:return[A,i,s]}},o.hsv.hsl=function(e){const t=e[0],r=e[1]/100,A=e[2]/100,n=Math.max(A,.01);let o,i;i=(2-r)*A;const s=(2-r)*n;return o=r*n,o/=s<=1?s:2-s,o=o||0,i/=2,[t,100*o,100*i]},o.hwb.rgb=function(e){const t=e[0]/360;let r=e[1]/100,A=e[2]/100;const n=r+A;let o;n>1&&(r/=n,A/=n);const i=Math.floor(6*t),s=1-A;o=6*t-i,0!=(1&i)&&(o=1-o);const a=r+o*(s-r);let c,g,l;switch(i){default:case 6:case 0:c=s,g=a,l=r;break;case 1:c=a,g=s,l=r;break;case 2:c=r,g=s,l=a;break;case 3:c=r,g=a,l=s;break;case 4:c=a,g=r,l=s;break;case 5:c=s,g=r,l=a}return[255*c,255*g,255*l]},o.cmyk.rgb=function(e){const t=e[0]/100,r=e[1]/100,A=e[2]/100,n=e[3]/100;return[255*(1-Math.min(1,t*(1-n)+n)),255*(1-Math.min(1,r*(1-n)+n)),255*(1-Math.min(1,A*(1-n)+n))]},o.xyz.rgb=function(e){const t=e[0]/100,r=e[1]/100,A=e[2]/100;let n,o,i;return n=3.2406*t+-1.5372*r+-.4986*A,o=-.9689*t+1.8758*r+.0415*A,i=.0557*t+-.204*r+1.057*A,n=n>.0031308?1.055*n**(1/2.4)-.055:12.92*n,o=o>.0031308?1.055*o**(1/2.4)-.055:12.92*o,i=i>.0031308?1.055*i**(1/2.4)-.055:12.92*i,n=Math.min(Math.max(0,n),1),o=Math.min(Math.max(0,o),1),i=Math.min(Math.max(0,i),1),[255*n,255*o,255*i]},o.xyz.lab=function(e){let t=e[0],r=e[1],A=e[2];t/=95.047,r/=100,A/=108.883,t=t>.008856?t**(1/3):7.787*t+16/116,r=r>.008856?r**(1/3):7.787*r+16/116,A=A>.008856?A**(1/3):7.787*A+16/116;return[116*r-16,500*(t-r),200*(r-A)]},o.lab.xyz=function(e){let t,r,A;r=(e[0]+16)/116,t=e[1]/500+r,A=r-e[2]/200;const n=r**3,o=t**3,i=A**3;return r=n>.008856?n:(r-16/116)/7.787,t=o>.008856?o:(t-16/116)/7.787,A=i>.008856?i:(A-16/116)/7.787,t*=95.047,r*=100,A*=108.883,[t,r,A]},o.lab.lch=function(e){const t=e[0],r=e[1],A=e[2];let n;n=360*Math.atan2(A,r)/2/Math.PI,n<0&&(n+=360);return[t,Math.sqrt(r*r+A*A),n]},o.lch.lab=function(e){const t=e[0],r=e[1],A=e[2]/360*2*Math.PI;return[t,r*Math.cos(A),r*Math.sin(A)]},o.rgb.ansi16=function(e,t=null){const[r,A,n]=e;let i=null===t?o.rgb.hsv(e)[2]:t;if(i=Math.round(i/50),0===i)return 30;let s=30+(Math.round(n/255)<<2|Math.round(A/255)<<1|Math.round(r/255));return 2===i&&(s+=60),s},o.hsv.ansi16=function(e){return o.rgb.ansi16(o.hsv.rgb(e),e[2])},o.rgb.ansi256=function(e){const t=e[0],r=e[1],A=e[2];if(t===r&&r===A)return t<8?16:t>248?231:Math.round((t-8)/247*24)+232;return 16+36*Math.round(t/255*5)+6*Math.round(r/255*5)+Math.round(A/255*5)},o.ansi16.rgb=function(e){let t=e%10;if(0===t||7===t)return e>50&&(t+=3.5),t=t/10.5*255,[t,t,t];const r=.5*(1+~~(e>50));return[(1&t)*r*255,(t>>1&1)*r*255,(t>>2&1)*r*255]},o.ansi256.rgb=function(e){if(e>=232){const t=10*(e-232)+8;return[t,t,t]}let t;e-=16;return[Math.floor(e/36)/5*255,Math.floor((t=e%36)/6)/5*255,t%6/5*255]},o.rgb.hex=function(e){const t=(((255&Math.round(e[0]))<<16)+((255&Math.round(e[1]))<<8)+(255&Math.round(e[2]))).toString(16).toUpperCase();return"000000".substring(t.length)+t},o.hex.rgb=function(e){const t=e.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i);if(!t)return[0,0,0];let r=t[0];3===t[0].length&&(r=r.split("").map(e=>e+e).join(""));const A=parseInt(r,16);return[A>>16&255,A>>8&255,255&A]},o.rgb.hcg=function(e){const t=e[0]/255,r=e[1]/255,A=e[2]/255,n=Math.max(Math.max(t,r),A),o=Math.min(Math.min(t,r),A),i=n-o;let s,a;return s=i<1?o/(1-i):0,a=i<=0?0:n===t?(r-A)/i%6:n===r?2+(A-t)/i:4+(t-r)/i,a/=6,a%=1,[360*a,100*i,100*s]},o.hsl.hcg=function(e){const t=e[1]/100,r=e[2]/100,A=r<.5?2*t*r:2*t*(1-r);let n=0;return A<1&&(n=(r-.5*A)/(1-A)),[e[0],100*A,100*n]},o.hsv.hcg=function(e){const t=e[1]/100,r=e[2]/100,A=t*r;let n=0;return A<1&&(n=(r-A)/(1-A)),[e[0],100*A,100*n]},o.hcg.rgb=function(e){const t=e[0]/360,r=e[1]/100,A=e[2]/100;if(0===r)return[255*A,255*A,255*A];const n=[0,0,0],o=t%1*6,i=o%1,s=1-i;let a=0;switch(Math.floor(o)){case 0:n[0]=1,n[1]=i,n[2]=0;break;case 1:n[0]=s,n[1]=1,n[2]=0;break;case 2:n[0]=0,n[1]=1,n[2]=i;break;case 3:n[0]=0,n[1]=s,n[2]=1;break;case 4:n[0]=i,n[1]=0,n[2]=1;break;default:n[0]=1,n[1]=0,n[2]=s}return a=(1-r)*A,[255*(r*n[0]+a),255*(r*n[1]+a),255*(r*n[2]+a)]},o.hcg.hsv=function(e){const t=e[1]/100,r=t+e[2]/100*(1-t);let A=0;return r>0&&(A=t/r),[e[0],100*A,100*r]},o.hcg.hsl=function(e){const t=e[1]/100,r=e[2]/100*(1-t)+.5*t;let A=0;return r>0&&r<.5?A=t/(2*r):r>=.5&&r<1&&(A=t/(2*(1-r))),[e[0],100*A,100*r]},o.hcg.hwb=function(e){const t=e[1]/100,r=t+e[2]/100*(1-t);return[e[0],100*(r-t),100*(1-r)]},o.hwb.hcg=function(e){const t=e[1]/100,r=1-e[2]/100,A=r-t;let n=0;return A<1&&(n=(r-A)/(1-A)),[e[0],100*A,100*n]},o.apple.rgb=function(e){return[e[0]/65535*255,e[1]/65535*255,e[2]/65535*255]},o.rgb.apple=function(e){return[e[0]/255*65535,e[1]/255*65535,e[2]/255*65535]},o.gray.rgb=function(e){return[e[0]/100*255,e[0]/100*255,e[0]/100*255]},o.gray.hsl=function(e){return[0,0,e[0]]},o.gray.hsv=o.gray.hsl,o.gray.hwb=function(e){return[0,100,e[0]]},o.gray.cmyk=function(e){return[0,0,0,e[0]]},o.gray.lab=function(e){return[e[0],0,0]},o.gray.hex=function(e){const t=255&Math.round(e[0]/100*255),r=((t<<16)+(t<<8)+t).toString(16).toUpperCase();return"000000".substring(r.length)+r},o.rgb.gray=function(e){return[(e[0]+e[1]+e[2])/3/255*100]}},2744:(e,t,r)=>{const A=r(15311),n=r(78577),o={};Object.keys(A).forEach(e=>{o[e]={},Object.defineProperty(o[e],"channels",{value:A[e].channels}),Object.defineProperty(o[e],"labels",{value:A[e].labels});const t=n(e);Object.keys(t).forEach(r=>{const A=t[r];o[e][r]=function(e){const t=function(...t){const r=t[0];if(null==r)return r;r.length>1&&(t=r);const A=e(t);if("object"==typeof A)for(let e=A.length,t=0;t1&&(t=r),e(t))};return"conversion"in e&&(t.conversion=e.conversion),t}(A)})}),e.exports=o},78577:(e,t,r)=>{const A=r(15311);function n(e){const t=function(){const e={},t=Object.keys(A);for(let r=t.length,A=0;A{"use strict";e.exports={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]}},67566:(e,t,r)=>{"use strict";const A=r(63129),n=r(14951),o=r(10779);function i(e,t,r){const i=n(e,t,r),s=A.spawn(i.command,i.args,i.options);return o.hookChildProcess(s,i),s}e.exports=i,e.exports.spawn=i,e.exports.sync=function(e,t,r){const i=n(e,t,r),s=A.spawnSync(i.command,i.args,i.options);return s.error=s.error||o.verifyENOENTSync(s.status,i),s},e.exports._parse=n,e.exports._enoent=o},10779:e=>{"use strict";const t="win32"===process.platform;function r(e,t){return Object.assign(new Error(`${t} ${e.command} ENOENT`),{code:"ENOENT",errno:"ENOENT",syscall:`${t} ${e.command}`,path:e.command,spawnargs:e.args})}function A(e,A){return t&&1===e&&!A.file?r(A.original,"spawn"):null}e.exports={hookChildProcess:function(e,r){if(!t)return;const n=e.emit;e.emit=function(t,o){if("exit"===t){const t=A(o,r);if(t)return n.call(e,"error",t)}return n.apply(e,arguments)}},verifyENOENT:A,verifyENOENTSync:function(e,A){return t&&1===e&&!A.file?r(A.original,"spawnSync"):null},notFoundError:r}},14951:(e,t,r)=>{"use strict";const A=r(85622),n=r(47447),o=r(27066),i=r(35187),s="win32"===process.platform,a=/\.(?:com|exe)$/i,c=/node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i;function g(e){if(!s)return e;const t=function(e){e.file=n(e);const t=e.file&&i(e.file);return t?(e.args.unshift(e.file),e.command=t,n(e)):e.file}(e),r=!a.test(t);if(e.options.forceShell||r){const r=c.test(t);e.command=A.normalize(e.command),e.command=o.command(e.command),e.args=e.args.map(e=>o.argument(e,r));const n=[e.command].concat(e.args).join(" ");e.args=["/d","/s","/c",`"${n}"`],e.command=process.env.comspec||"cmd.exe",e.options.windowsVerbatimArguments=!0}return e}e.exports=function(e,t,r){t&&!Array.isArray(t)&&(r=t,t=null);const A={command:e,args:t=t?t.slice(0):[],options:r=Object.assign({},r),file:void 0,original:{command:e,args:t}};return r.shell?A:g(A)}},27066:e=>{"use strict";const t=/([()\][%!^"`<>&|;, *?])/g;e.exports.command=function(e){return e=e.replace(t,"^$1")},e.exports.argument=function(e,r){return e=(e=`"${e=(e=(e=""+e).replace(/(\\*)"/g,'$1$1\\"')).replace(/(\\*)$/,"$1$1")}"`).replace(t,"^$1"),r&&(e=e.replace(t,"^$1")),e}},35187:(e,t,r)=>{"use strict";const A=r(35747),n=r(91470);e.exports=function(e){const t=Buffer.alloc(150);let r;try{r=A.openSync(e,"r"),A.readSync(r,t,0,150,0),A.closeSync(r)}catch(e){}return n(t.toString())}},47447:(e,t,r)=>{"use strict";const A=r(85622),n=r(87945),o=r(37127);function i(e,t){const r=e.options.env||process.env,i=process.cwd(),s=null!=e.options.cwd,a=s&&void 0!==process.chdir&&!process.chdir.disabled;if(a)try{process.chdir(e.options.cwd)}catch(e){}let c;try{c=n.sync(e.command,{path:r[o({env:r})],pathExt:t?A.delimiter:void 0})}catch(e){}finally{a&&process.chdir(i)}return c&&(c=A.resolve(s?e.options.cwd:"",c)),c}e.exports=function(e){return i(e)||i(e,!0)}},93868:(e,t,r)=>{"use strict";const{Transform:A,PassThrough:n}=r(92413),o=r(78761),i=r(33527);e.exports=e=>{const t=(e.headers["content-encoding"]||"").toLowerCase();if(!["gzip","deflate","br"].includes(t))return e;const r="br"===t;if(r&&"function"!=typeof o.createBrotliDecompress)return e.destroy(new Error("Brotli is not supported on Node.js < 12")),e;let s=!0;const a=new A({transform(e,t,r){s=!1,r(null,e)},flush(e){e()}}),c=new n({autoDestroy:!1,destroy(t,r){e.destroy(),r(t)}}),g=r?o.createBrotliDecompress():o.createUnzip();return g.once("error",t=>{!s||e.readable?c.destroy(t):c.end()}),i(e,c),e.pipe(a).pipe(g).pipe(c),c}},93121:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(4016),n=(e,t)=>{let r;if("function"==typeof t){r={connect:t}}else r=t;const n="function"==typeof r.connect,o="function"==typeof r.secureConnect,i="function"==typeof r.close,s=()=>{n&&r.connect(),e instanceof A.TLSSocket&&o&&(e.authorized?r.secureConnect():e.authorizationError||e.once("secureConnect",r.secureConnect)),i&&e.once("close",r.close)};e.writable&&!e.connecting?s():e.connecting?e.once("connect",s):e.destroyed&&i&&r.close(e._hadError)};t.default=n,e.exports=n,e.exports.default=n},66241:(e,t,r)=>{"use strict";const A=r(85622),n=r(5763),o=e=>e.length>1?`{${e.join(",")}}`:e[0],i=(e,t)=>{const r="!"===e[0]?e.slice(1):e;return A.isAbsolute(r)?r:A.join(t,r)},s=(e,t)=>{if(t.files&&!Array.isArray(t.files))throw new TypeError(`Expected \`files\` to be of type \`Array\` but received type \`${typeof t.files}\``);if(t.extensions&&!Array.isArray(t.extensions))throw new TypeError(`Expected \`extensions\` to be of type \`Array\` but received type \`${typeof t.extensions}\``);return t.files&&t.extensions?t.files.map(r=>{return A.posix.join(e,(n=r,i=t.extensions,A.extname(n)?"**/"+n:`**/${n}.${o(i)}`));var n,i}):t.files?t.files.map(t=>A.posix.join(e,"**/"+t)):t.extensions?[A.posix.join(e,"**/*."+o(t.extensions))]:[A.posix.join(e,"**")]};e.exports=async(e,t)=>{if("string"!=typeof(t={cwd:process.cwd(),...t}).cwd)throw new TypeError(`Expected \`cwd\` to be of type \`string\` but received type \`${typeof t.cwd}\``);const r=await Promise.all([].concat(e).map(async e=>await n.isDirectory(i(e,t.cwd))?s(e,t):e));return[].concat.apply([],r)},e.exports.sync=(e,t)=>{if("string"!=typeof(t={cwd:process.cwd(),...t}).cwd)throw new TypeError(`Expected \`cwd\` to be of type \`string\` but received type \`${typeof t.cwd}\``);const r=[].concat(e).map(e=>n.isDirectorySync(i(e,t.cwd))?s(e,t):e);return[].concat.apply([],r)}},97681:(e,t,r)=>{var A=r(91162),n=function(){},o=function(e,t,r){if("function"==typeof t)return o(e,null,t);t||(t={}),r=A(r||n);var i=e._writableState,s=e._readableState,a=t.readable||!1!==t.readable&&e.readable,c=t.writable||!1!==t.writable&&e.writable,g=function(){e.writable||l()},l=function(){c=!1,a||r()},u=function(){a=!1,c||r()},h=function(e){r(e?new Error("exited with error code: "+e):null)},p=function(){return(!a||s&&s.ended)&&(!c||i&&i.ended)?void 0:r(new Error("premature close"))},d=function(){e.req.on("finish",l)};return!function(e){return e.setHeader&&"function"==typeof e.abort}(e)?c&&!i&&(e.on("end",g),e.on("close",g)):(e.on("complete",l),e.on("abort",p),e.req?d():e.on("request",d)),function(e){return e.stdio&&Array.isArray(e.stdio)&&3===e.stdio.length}(e)&&e.on("exit",h),e.on("end",u),e.on("finish",l),!1!==t.error&&e.on("error",r),e.on("close",p),function(){e.removeListener("complete",l),e.removeListener("abort",p),e.removeListener("request",d),e.req&&e.req.removeListener("finish",l),e.removeListener("end",g),e.removeListener("close",g),e.removeListener("finish",l),e.removeListener("exit",h),e.removeListener("end",u),e.removeListener("error",r),e.removeListener("close",p)}};e.exports=o},17067:(e,t,r)=>{var A=r(27180),n=function(){},o=function(e,t,r){if("function"==typeof t)return o(e,null,t);t||(t={}),r=A(r||n);var i=e._writableState,s=e._readableState,a=t.readable||!1!==t.readable&&e.readable,c=t.writable||!1!==t.writable&&e.writable,g=function(){e.writable||l()},l=function(){c=!1,a||r.call(e)},u=function(){a=!1,c||r.call(e)},h=function(t){r.call(e,t?new Error("exited with error code: "+t):null)},p=function(t){r.call(e,t)},d=function(){return(!a||s&&s.ended)&&(!c||i&&i.ended)?void 0:r.call(e,new Error("premature close"))},C=function(){e.req.on("finish",l)};return!function(e){return e.setHeader&&"function"==typeof e.abort}(e)?c&&!i&&(e.on("end",g),e.on("close",g)):(e.on("complete",l),e.on("abort",d),e.req?C():e.on("request",C)),function(e){return e.stdio&&Array.isArray(e.stdio)&&3===e.stdio.length}(e)&&e.on("exit",h),e.on("end",u),e.on("finish",l),!1!==t.error&&e.on("error",p),e.on("close",d),function(){e.removeListener("complete",l),e.removeListener("abort",d),e.removeListener("request",C),e.req&&e.req.removeListener("finish",l),e.removeListener("end",g),e.removeListener("close",g),e.removeListener("finish",l),e.removeListener("exit",h),e.removeListener("end",u),e.removeListener("error",p),e.removeListener("close",d)}};e.exports=o},61899:(e,t,r)=>{"use strict";const A=r(42357),n=r(28614),o=r(10278);class i extends n{constructor(e,t){super(),this.options=o.merge({},e),this.answers={...t}}register(e,t){if(o.isObject(e)){for(let t of Object.keys(e))this.register(t,e[t]);return this}A.equal(typeof t,"function","expected a function");let r=e.toLowerCase();return t.prototype instanceof this.Prompt?this.prompts[r]=t:this.prompts[r]=t(this.Prompt,this),this}async prompt(e=[]){for(let t of[].concat(e))try{"function"==typeof t&&(t=await t.call(this)),await this.ask(o.merge({},this.options,t))}catch(e){return Promise.reject(e)}return this.answers}async ask(e){"function"==typeof e&&(e=await e.call(this));let t=o.merge({},this.options,e),{type:r,name:n}=e,{set:i,get:s}=o;if("function"==typeof r&&(r=await r.call(this,e,this.answers)),!r)return this.answers[n];A(this.prompts[r],`Prompt "${r}" is not registered`);let a=new this.prompts[r](t),c=s(this.answers,n);a.state.answers=this.answers,a.enquirer=this,n&&a.on("submit",e=>{this.emit("answer",n,e,a),i(this.answers,n,e)});let g=a.emit.bind(a);return a.emit=(...e)=>(this.emit.call(this,...e),g(...e)),this.emit("prompt",a,this),t.autofill&&null!=c?(a.value=a.input=c,"show"===t.autofill&&await a.submit()):c=a.value=await a.run(),c}use(e){return e.call(this,this),this}set Prompt(e){this._Prompt=e}get Prompt(){return this._Prompt||this.constructor.Prompt}get prompts(){return this.constructor.prompts}static set Prompt(e){this._Prompt=e}static get Prompt(){return this._Prompt||r(58386)}static get prompts(){return r(53609)}static get types(){return r(13235)}static get prompt(){const e=(t,...r)=>{let A=new this(...r),n=A.emit.bind(A);return A.emit=(...t)=>(e.emit(...t),n(...t)),A.prompt(t)};return o.mixinEmitter(e,new n),e}}o.mixinEmitter(i,new n);const s=i.prompts;for(let e of Object.keys(s)){let t=e.toLowerCase(),r=t=>new s[e](t).run();i.prompt[t]=r,i[t]=r,i[e]||Reflect.defineProperty(i,e,{get:()=>s[e]})}const a=e=>{o.defineExport(i,e,()=>i.types[e])};a("ArrayPrompt"),a("AuthPrompt"),a("BooleanPrompt"),a("NumberPrompt"),a("StringPrompt"),e.exports=i},72380:(e,t,r)=>{"use strict";const A="Apple_Terminal"===process.env.TERM_PROGRAM,n=r(97991),o=r(10278),i=e.exports=t,s="[";let a=!1;const c=i.code={bell:"",beep:"",beginning:"",down:"",esc:s,getPosition:"",hide:"[?25l",line:"",lineEnd:"",lineStart:"",restorePosition:s+(A?"8":"u"),savePosition:s+(A?"7":"s"),screen:"",show:"[?25h",up:""},g=i.cursor={get hidden(){return a},hide:()=>(a=!0,c.hide),show:()=>(a=!1,c.show),forward:(e=1)=>`[${e}C`,backward:(e=1)=>`[${e}D`,nextLine:(e=1)=>"".repeat(e),prevLine:(e=1)=>"".repeat(e),up:(e=1)=>e?`[${e}A`:"",down:(e=1)=>e?`[${e}B`:"",right:(e=1)=>e?`[${e}C`:"",left:(e=1)=>e?`[${e}D`:"",to:(e,t)=>t?`[${t+1};${e+1}H`:`[${e+1}G`,move(e=0,t=0){let r="";return r+=e<0?g.left(-e):e>0?g.right(e):"",r+=t<0?g.up(-t):t>0?g.down(t):"",r},restore(e={}){let{after:t,cursor:r,initial:A,input:n,prompt:s,size:a,value:c}=e;if(A=o.isPrimitive(A)?String(A):"",n=o.isPrimitive(n)?String(n):"",c=o.isPrimitive(c)?String(c):"",a){let e=i.cursor.up(a)+i.cursor.to(s.length),t=n.length-r;return t>0&&(e+=i.cursor.left(t)),e}if(c||t){let e=!n&&A?-A.length:-n.length+r;return t&&(e-=t.length),""===n&&A&&!s.includes(A)&&(e+=A.length),i.cursor.move(e)}}},l=i.erase={screen:c.screen,up:c.up,down:c.down,line:c.line,lineEnd:c.lineEnd,lineStart:c.lineStart,lines(e){let t="";for(let r=0;r{if(!t)return l.line+g.to(0);let r=e.split(/\r?\n/),A=0;for(let e of r)A+=1+Math.floor(Math.max((o=e,[...n.unstyle(o)].length-1),0)/t);var o;return(l.line+g.prevLine()).repeat(A-1)+l.line+g.to(0)}},62475:(e,t)=>{"use strict";t.ctrl={a:"first",b:"backward",c:"cancel",d:"deleteForward",e:"last",f:"forward",g:"reset",i:"tab",k:"cutForward",l:"reset",n:"newItem",m:"cancel",j:"submit",p:"search",r:"remove",s:"save",u:"undo",w:"cutLeft",x:"toggleCursor",v:"paste"},t.shift={up:"shiftUp",down:"shiftDown",left:"shiftLeft",right:"shiftRight",tab:"prev"},t.fn={up:"pageUp",down:"pageDown",left:"pageLeft",right:"pageRight",delete:"deleteForward"},t.option={b:"backward",f:"forward",d:"cutRight",left:"cutLeft",up:"altUp",down:"altDown"},t.keys={pageup:"pageUp",pagedown:"pageDown",home:"home",end:"end",cancel:"cancel",delete:"deleteForward",backspace:"delete",down:"down",enter:"submit",escape:"cancel",left:"left",space:"space",number:"number",return:"submit",right:"right",tab:"next",up:"up"}},64083:e=>{"use strict";const t=e=>(e=>e.filter((t,r)=>e.lastIndexOf(t)===r))(e).filter(Boolean);e.exports=(e,r={},A="")=>{let n,o,{past:i=[],present:s=""}=r;switch(e){case"prev":case"undo":return n=i.slice(0,i.length-1),o=i[i.length-1]||"",{past:t([A,...n]),present:o};case"next":case"redo":return n=i.slice(1),o=i[0]||"",{past:t([...n,A]),present:o};case"save":return{past:t([...i,A]),present:""};case"remove":return o=t(i.filter(e=>e!==A)),s="",o.length&&(s=o.pop()),{past:o,present:s};default:throw new Error(`Invalid action: "${e}"`)}}},84368:(e,t,r)=>{"use strict";const A=r(97991);class n{constructor(e){this.name=e.key,this.field=e.field||{},this.value=((e="")=>"string"==typeof e?e.replace(/^['"]|['"]$/g,""):"")(e.initial||this.field.initial||""),this.message=e.message||this.name,this.cursor=0,this.input="",this.lines=[]}}function o(e,t,r,A){return(r,n,o,i)=>"function"==typeof o.field[e]?o.field[e].call(t,r,n,o,i):[A,r].find(e=>t.isValue(e))}e.exports=async e=>{let t=e.options,r=new Set(!0===t.required?[]:t.required||[]),i={...t.values,...t.initial},{tabstops:s,items:a,keys:c}=await(async(e={},t={},r=(e=>e))=>{let A=new Set,o=e.fields||[],i=e.template,s=[],a=[],c=[],g=1;"function"==typeof i&&(i=await i());let l=-1,u=()=>i[++l],h=()=>i[l+1],p=e=>{e.line=g,s.push(e)};for(p({type:"bos",value:""});le.name===s.key);s.field=o.find(e=>e.name===s.key),g||(g=new n(s),a.push(g)),g.lines.push(s.line-1);continue}let i=s[s.length-1];"text"===i.type&&i.line===g?i.value+=e:p({type:"text",value:e})}return p({type:"eos",value:""}),{input:i,tabstops:s,unique:A,keys:c,items:a}})(t,i),g=o("result",e,t),l=o("format",e,t),u=o("validate",e,t,!0),h=e.isValue.bind(e);return async(n={},o=!1)=>{let i=0;n.required=r,n.items=a,n.keys=c,n.output="";let p=async(e,t,r,A)=>{let n=await u(e,t,r,A);return!1===n?"Invalid field "+r.name:n};for(let r of s){let s=r.value,c=r.key;if("template"===r.type){if("template"===r.type){let u=a.find(e=>e.name===c);!0===t.required&&n.required.add(u.name);let d=[u.input,n.values[u.value],u.value,s].find(h),C=(u.field||{}).message||r.inner;if(o){let e=await p(n.values[c],n,u,i);if(e&&"string"==typeof e||!1===e){n.invalid.set(c,e);continue}n.invalid.delete(c);let t=await g(n.values[c],n,u,i);n.output+=A.unstyle(t);continue}u.placeholder=!1;let f=s;s=await l(s,n,u,i),d!==s?(n.values[c]=d,s=e.styles.typing(d),n.missing.delete(C)):(n.values[c]=void 0,d=`<${C}>`,s=e.styles.primary(d),u.placeholder=!0,n.required.has(c)&&n.missing.add(C)),n.missing.has(C)&&n.validating&&(s=e.styles.warning(d)),n.invalid.has(c)&&n.validating&&(s=e.styles.danger(d)),i===n.index&&(s=f!==s?e.styles.underline(s):e.styles.heading(A.unstyle(s))),i++}s&&(n.output+=s)}else s&&(n.output+=s)}let d=n.output.split("\n").map(e=>" "+e),C=a.length,f=0;for(let t of a)n.invalid.has(t.name)&&t.lines.forEach(e=>{" "===d[e][0]&&(d[e]=n.styles.danger(n.symbols.bullet)+d[e].slice(1))}),e.isValue(n.values[t.name])&&f++;return n.completed=(f/C*100).toFixed(0),n.output=d.join("\n"),n.output}}},30650:(e,t,r)=>{"use strict";const A=r(51058),n=r(62475),o=/^(?:\x1b)([a-zA-Z0-9])$/,i=/^(?:\x1b+)(O|N|\[|\[\[)(?:(\d+)(?:;(\d+))?([~^$])|(?:1;)?(\d+)?([a-zA-Z]))/,s={OP:"f1",OQ:"f2",OR:"f3",OS:"f4","[11~":"f1","[12~":"f2","[13~":"f3","[14~":"f4","[[A":"f1","[[B":"f2","[[C":"f3","[[D":"f4","[[E":"f5","[15~":"f5","[17~":"f6","[18~":"f7","[19~":"f8","[20~":"f9","[21~":"f10","[23~":"f11","[24~":"f12","[A":"up","[B":"down","[C":"right","[D":"left","[E":"clear","[F":"end","[H":"home",OA:"up",OB:"down",OC:"right",OD:"left",OE:"clear",OF:"end",OH:"home","[1~":"home","[2~":"insert","[3~":"delete","[4~":"end","[5~":"pageup","[6~":"pagedown","[[5~":"pageup","[[6~":"pagedown","[7~":"home","[8~":"end","[a":"up","[b":"down","[c":"right","[d":"left","[e":"clear","[2$":"insert","[3$":"delete","[5$":"pageup","[6$":"pagedown","[7$":"home","[8$":"end",Oa:"up",Ob:"down",Oc:"right",Od:"left",Oe:"clear","[2^":"insert","[3^":"delete","[5^":"pageup","[6^":"pagedown","[7^":"home","[8^":"end","[Z":"tab"};const a=(e="",t={})=>{let r,A={name:t.name,ctrl:!1,meta:!1,shift:!1,option:!1,sequence:e,raw:e,...t};if(Buffer.isBuffer(e)?e[0]>127&&void 0===e[1]?(e[0]-=128,e=""+String(e)):e=String(e):void 0!==e&&"string"!=typeof e?e=String(e):e||(e=A.sequence||""),A.sequence=A.sequence||e||A.name,"\r"===e)A.raw=void 0,A.name="return";else if("\n"===e)A.name="enter";else if("\t"===e)A.name="tab";else if("\b"===e||""===e||""===e||"\b"===e)A.name="backspace",A.meta=""===e.charAt(0);else if(""===e||""===e)A.name="escape",A.meta=2===e.length;else if(" "===e||" "===e)A.name="space",A.meta=2===e.length;else if(e<="")A.name=String.fromCharCode(e.charCodeAt(0)+"a".charCodeAt(0)-1),A.ctrl=!0;else if(1===e.length&&e>="0"&&e<="9")A.name="number";else if(1===e.length&&e>="a"&&e<="z")A.name=e;else if(1===e.length&&e>="A"&&e<="Z")A.name=e.toLowerCase(),A.shift=!0;else if(r=o.exec(e))A.meta=!0,A.shift=/^[A-Z]$/.test(r[1]);else if(r=i.exec(e)){let t=[...e];""===t[0]&&""===t[1]&&(A.option=!0);let n=[r[1],r[2],r[4],r[6]].filter(Boolean).join(""),o=(r[3]||r[5]||1)-1;A.ctrl=!!(4&o),A.meta=!!(10&o),A.shift=!!(1&o),A.code=n,A.name=s[n],A.shift=function(e){return["[a","[b","[c","[d","[e","[2$","[3$","[5$","[6$","[7$","[8$","[Z"].includes(e)}(n)||A.shift,A.ctrl=function(e){return["Oa","Ob","Oc","Od","Oe","[2^","[3^","[5^","[6^","[7^","[8^"].includes(e)}(n)||A.ctrl}return A};a.listen=(e={},t)=>{let{stdin:r}=e;if(!r||r!==process.stdin&&!r.isTTY)throw new Error("Invalid stream passed");let n=A.createInterface({terminal:!0,input:r});A.emitKeypressEvents(r,n);let o=(e,r)=>t(e,a(e,r),n),i=r.isRaw;r.isTTY&&r.setRawMode(!0),r.on("keypress",o),n.resume();return()=>{r.isTTY&&r.setRawMode(i),r.removeListener("keypress",o),n.pause(),n.close()}},a.action=(e,t,r)=>{let A={...n,...r};return t.ctrl?(t.action=A.ctrl[t.name],t):t.option&&A.option?(t.action=A.option[t.name],t):t.shift?(t.action=A.shift[t.name],t):(t.action=A.keys[t.name],t)},e.exports=a},96496:(e,t,r)=>{"use strict";const A=r(10278);e.exports=(e,t={})=>{e.cursorHide();let{input:r="",initial:n="",pos:o,showCursor:i=!0,color:s}=t,a=s||e.styles.placeholder,c=A.inverse(e.styles.primary),g=t=>c(e.styles.black(t)),l=r,u=g(" ");if(e.blink&&!0===e.blink.off&&(g=e=>e,u=""),i&&0===o&&""===n&&""===r)return g(" ");if(i&&0===o&&(r===n||""===r))return g(n[0])+a(n.slice(1));n=A.isPrimitive(n)?""+n:"",r=A.isPrimitive(r)?""+r:"";let h=n&&n.startsWith(r)&&n!==r,p=h?g(n[r.length]):u;if(o!==r.length&&!0===i&&(l=r.slice(0,o)+g(r[o])+r.slice(o+1),p=""),!1===i&&(p=""),h){let t=e.styles.unstyle(l+p);return l+p+a(n.slice(t.length))}return l+p}},58386:(e,t,r)=>{"use strict";const A=r(28614),n=r(97991),o=r(30650),i=r(47159),s=r(61807),a=r(26205),c=r(10278),g=r(72380);class l extends A{constructor(e={}){super(),this.name=e.name,this.type=e.type,this.options=e,a(this),i(this),this.state=new s(this),this.initial=[e.initial,e.default].find(e=>null!=e),this.stdout=e.stdout||process.stdout,this.stdin=e.stdin||process.stdin,this.scale=e.scale||1,this.term=this.options.term||process.env.TERM_PROGRAM,this.margin=function(e){"number"==typeof e&&(e=[e,e,e,e]);let t=[].concat(e||[]),r=e=>e%2==0?"\n":" ",A=[];for(let e=0;e<4;e++){let n=r(e);t[e]?A.push(n.repeat(t[e])):A.push("")}return A}(this.options.margin),this.setMaxListeners(0),function(e){let t=t=>void 0===e[t]||"function"==typeof e[t],r=["actions","choices","initial","margin","roles","styles","symbols","theme","timers","value"],A=["body","footer","error","header","hint","indicator","message","prefix","separator","skip"];for(let n of Object.keys(e.options)){if(r.includes(n))continue;if(/^on[A-Z]/.test(n))continue;let o=e.options[n];"function"==typeof o&&t(n)?A.includes(n)||(e[n]=o.bind(e)):"function"!=typeof e[n]&&(e[n]=o)}}(this)}async keypress(e,t={}){this.keypressed=!0;let r=o.action(e,o(e,t),this.options.actions);this.state.keypress=r,this.emit("keypress",e,r),this.emit("state",this.state.clone());let A=this.options[r.action]||this[r.action]||this.dispatch;if("function"==typeof A)return await A.call(this,e,r);this.alert()}alert(){delete this.state.alert,!1===this.options.show?this.emit("alert"):this.stdout.write(g.code.beep)}cursorHide(){this.stdout.write(g.cursor.hide()),c.onExit(()=>this.cursorShow())}cursorShow(){this.stdout.write(g.cursor.show())}write(e){e&&(this.stdout&&!1!==this.state.show&&this.stdout.write(e),this.state.buffer+=e)}clear(e=0){let t=this.state.buffer;this.state.buffer="",(t||e)&&!1!==this.options.show&&this.stdout.write(g.cursor.down(e)+g.clear(t,this.width))}restore(){if(this.state.closed||!1===this.options.show)return;let{prompt:e,after:t,rest:r}=this.sections(),{cursor:A,initial:n="",input:o="",value:i=""}=this,s={after:t,cursor:A,initial:n,input:o,prompt:e,size:this.state.size=r.length,value:i},a=g.cursor.restore(s);a&&this.stdout.write(a)}sections(){let{buffer:e,input:t,prompt:r}=this.state;r=n.unstyle(r);let A=n.unstyle(e),o=A.indexOf(r),i=A.slice(0,o),s=A.slice(o).split("\n"),a=s[0],c=s[s.length-1],g=(r+(t?" "+t:"")).length,l=ge.call(this,this.value),this.result=()=>r.call(this,this.value),"function"==typeof t.initial&&(this.initial=await t.initial.call(this,this)),"function"==typeof t.onRun&&await t.onRun.call(this,this),"function"==typeof t.onSubmit){let e=t.onSubmit.bind(this),r=this.submit.bind(this);delete this.options.onSubmit,this.submit=async()=>(await e(this.name,this.value,this),r())}await this.start(),await this.render()}render(){throw new Error("expected prompt to have a custom render method")}run(){return new Promise(async(e,t)=>{if(this.once("submit",e),this.once("cancel",t),await this.skip())return this.render=()=>{},this.submit();await this.initialize(),this.emit("run")})}async element(e,t,r){let{options:A,state:n,symbols:o,timers:i}=this,s=i&&i[e];n.timer=s;let a=A[e]||n[e]||o[e],c=t&&null!=t[e]?t[e]:await a;if(""===c)return c;let g=await this.resolve(c,n,t,r);return!g&&t&&t[e]?this.resolve(a,n,t,r):g}async prefix(){let e=await this.element("prefix")||this.symbols,t=this.timers&&this.timers.prefix,r=this.state;if(r.timer=t,c.isObject(e)&&(e=e[r.status]||e.pending),!c.hasColor(e)){return(this.styles[r.status]||this.styles.pending)(e)}return e}async message(){let e=await this.element("message");return c.hasColor(e)?e:this.styles.strong(e)}async separator(){let e=await this.element("separator")||this.symbols,t=this.timers&&this.timers.separator,r=this.state;r.timer=t;let A=e[r.status]||e.pending||r.separator,n=await this.resolve(A,r);return c.isObject(n)&&(n=n[r.status]||n.pending),c.hasColor(n)?n:this.styles.muted(n)}async pointer(e,t){let r=await this.element("pointer",e,t);if("string"==typeof r&&c.hasColor(r))return r;if(r){let e=this.styles,A=this.index===t,n=A?e.primary:e=>e,o=await this.resolve(r[A?"on":"off"]||r,this.state),i=c.hasColor(o)?o:n(o);return A?i:" ".repeat(o.length)}}async indicator(e,t){let r=await this.element("indicator",e,t);if("string"==typeof r&&c.hasColor(r))return r;if(r){let t=this.styles,A=!0===e.enabled,n=A?t.success:t.dark,o=r[A?"on":"off"]||r;return c.hasColor(o)?o:n(o)}return""}body(){return null}footer(){if("pending"===this.state.status)return this.element("footer")}header(){if("pending"===this.state.status)return this.element("header")}async hint(){if("pending"===this.state.status&&!this.isValue(this.state.input)){let e=await this.element("hint");return c.hasColor(e)?e:this.styles.muted(e)}}error(e){return this.state.submitted?"":e||this.state.error}format(e){return e}result(e){return e}validate(e){return!0!==this.options.required||this.isValue(e)}isValue(e){return null!=e&&""!==e}resolve(e,...t){return c.resolve(this,e,...t)}get base(){return l.prototype}get style(){return this.styles[this.state.status]}get height(){return this.options.rows||c.height(this.stdout,25)}get width(){return this.options.columns||c.width(this.stdout,80)}get size(){return{width:this.width,height:this.height}}set cursor(e){this.state.cursor=e}get cursor(){return this.state.cursor}set input(e){this.state.input=e}get input(){return this.state.input}set value(e){this.state.value=e}get value(){let{input:e,value:t}=this.state,r=[t,e].find(this.isValue.bind(this));return this.isValue(r)?r:this.initial}static get prompt(){return e=>new this(e).run()}}e.exports=l},63310:(e,t,r)=>{"use strict";const A=r(31557);e.exports=class extends A{constructor(e){super(e),this.cursorShow()}moveCursor(e){this.state.cursor+=e}dispatch(e){return this.append(e)}space(e){return this.options.multiple?super.space(e):this.append(e)}append(e){let{cursor:t,input:r}=this.state;return this.input=r.slice(0,t)+e+r.slice(t),this.moveCursor(1),this.complete()}delete(){let{cursor:e,input:t}=this.state;return t?(this.input=t.slice(0,e-1)+t.slice(e),this.moveCursor(-1),this.complete()):this.alert()}deleteForward(){let{cursor:e,input:t}=this.state;return void 0===t[e]?this.alert():(this.input=(""+t).slice(0,e)+(""+t).slice(e+1),this.complete())}number(e){return this.append(e)}async complete(){this.completing=!0,this.choices=await this.suggest(this.input,this.state._choices),this.state.limit=void 0,this.index=Math.min(Math.max(this.visible.length-1,0),this.index),await this.render(),this.completing=!1}suggest(e=this.input,t=this.state._choices){if("function"==typeof this.options.suggest)return this.options.suggest.call(this,e,t);let r=e.toLowerCase();return t.filter(e=>e.message.toLowerCase().includes(r))}pointer(){return""}format(){if(!this.focused)return this.input;if(this.options.multiple&&this.state.submitted)return this.selected.map(e=>this.styles.primary(e.message)).join(", ");if(this.state.submitted){let e=this.value=this.input=this.focused.value;return this.styles.primary(e)}return this.input}async render(){if("pending"!==this.state.status)return super.render();let e=this.options.highlight?this.options.highlight.bind(this):this.styles.placeholder,t=((e,t)=>{let r=e.toLowerCase();return e=>{let A=e.toLowerCase().indexOf(r),n=t(e.slice(A,A+r.length));return A>=0?e.slice(0,A)+n+e.slice(A+r.length):e}})(this.input,e),r=this.choices;this.choices=r.map(e=>({...e,message:t(e.message)})),await super.render(),this.choices=r}submit(){return this.options.multiple&&(this.value=this.selected.map(e=>e.name)),super.submit()}}},52810:(e,t,r)=>{"use strict";const A=r(46614);function n(e,t){return e.username===this.options.username&&e.password===this.options.password}const o=(e=n)=>{const t=[{name:"username",message:"username"},{name:"password",message:"password",format(e){if(this.options.showPassword)return e;return(this.state.submitted?this.styles.primary:this.styles.muted)(this.symbols.asterisk.repeat(e.length))}}];class r extends(A.create(e)){constructor(e){super({...e,choices:t})}static create(e){return o(e)}}return r};e.exports=o()},65742:(e,t,r)=>{"use strict";const A=r(82710);e.exports=class extends A{constructor(e){super(e),this.default=this.options.default||(this.initial?"(Y/n)":"(y/N)")}}},24570:(e,t,r)=>{"use strict";const A=r(31557),n=r(71447).prototype;e.exports=class extends A{constructor(e){super({...e,multiple:!0}),this.align=[this.options.align,"left"].find(e=>null!=e),this.emptyError="",this.values={}}dispatch(e,t){let r=this.focused,A=r.parent||{};return r.editable||A.editable||"a"!==e&&"i"!==e?n.dispatch.call(this,e,t):super[e]()}append(e,t){return n.append.call(this,e,t)}delete(e,t){return n.delete.call(this,e,t)}space(e){return this.focused.editable?this.append(e):super.space()}number(e){return this.focused.editable?this.append(e):super.number(e)}next(){return this.focused.editable?n.next.call(this):super.next()}prev(){return this.focused.editable?n.prev.call(this):super.prev()}async indicator(e,t){let r=e.indicator||"",A=e.editable?r:super.indicator(e,t);return await this.resolve(A,this.state,e,t)||""}indent(e){return"heading"===e.role?"":e.editable?" ":" "}async renderChoice(e,t){return e.indent="",e.editable?n.renderChoice.call(this,e,t):super.renderChoice(e,t)}error(){return""}footer(){return this.state.error}async validate(){let e=!0;for(let t of this.choices){if("function"!=typeof t.validate)continue;if("heading"===t.role)continue;let r=t.parent?this.value[t.parent.name]:this.value;if(t.editable?r=t.value===t.name?t.initial||"":t.value:this.isDisabled(t)||(r=!0===t.enabled),e=await t.validate(r,this.state),!0!==e)break}return!0!==e&&(this.state.error="string"==typeof e?e:"Invalid Input"),e}submit(){if(!0===this.focused.newChoice)return super.submit();if(this.choices.some(e=>e.newChoice))return this.alert();this.value={};for(let e of this.choices){let t=e.parent?this.value[e.parent.name]:this.value;"heading"!==e.role?e.editable?t[e.name]=e.value===e.name?e.initial||"":e.value:this.isDisabled(e)||(t[e.name]=!0===e.enabled):this.value[e.name]={}}return this.base.submit.call(this)}}},71447:(e,t,r)=>{"use strict";const A=r(97991),n=r(31557),o=r(96496);e.exports=class extends n{constructor(e){super({...e,multiple:!0}),this.type="form",this.initial=this.options.initial,this.align=[this.options.align,"right"].find(e=>null!=e),this.emptyError="",this.values={}}async reset(e){return await super.reset(),!0===e&&(this._index=this.index),this.index=this._index,this.values={},this.choices.forEach(e=>e.reset&&e.reset()),this.render()}dispatch(e){return!!e&&this.append(e)}append(e){let t=this.focused;if(!t)return this.alert();let{cursor:r,input:A}=t;return t.value=t.input=A.slice(0,r)+e+A.slice(r),t.cursor++,this.render()}delete(){let e=this.focused;if(!e||e.cursor<=0)return this.alert();let{cursor:t,input:r}=e;return e.value=e.input=r.slice(0,t-1)+r.slice(t),e.cursor--,this.render()}deleteForward(){let e=this.focused;if(!e)return this.alert();let{cursor:t,input:r}=e;if(void 0===r[t])return this.alert();let A=(""+r).slice(0,t)+(""+r).slice(t+1);return e.value=e.input=A,this.render()}right(){let e=this.focused;return e?e.cursor>=e.input.length?this.alert():(e.cursor++,this.render()):this.alert()}left(){let e=this.focused;return e?e.cursor<=0?this.alert():(e.cursor--,this.render()):this.alert()}space(e,t){return this.dispatch(e,t)}number(e,t){return this.dispatch(e,t)}next(){let e=this.focused;if(!e)return this.alert();let{initial:t,input:r}=e;return t&&t.startsWith(r)&&r!==t?(e.value=e.input=t,e.cursor=e.value.length,this.render()):super.next()}prev(){let e=this.focused;return e?0===e.cursor?super.prev():(e.value=e.input="",e.cursor=0,this.render()):this.alert()}separator(){return""}format(e){return this.state.submitted?"":super.format(e)}pointer(){return""}indicator(e){return e.input?"⦿":"⊙"}async choiceSeparator(e,t){let r=await this.resolve(e.separator,this.state,e,t)||":";return r?" "+this.styles.disabled(r):""}async renderChoice(e,t){await this.onChoice(e,t);let{state:r,styles:n}=this,{cursor:i,initial:s="",name:a,hint:c,input:g=""}=e,{muted:l,submitted:u,primary:h,danger:p}=n,d=c,C=this.index===t,f=e.validate||(()=>!0),I=await this.choiceSeparator(e,t),E=e.message;"right"===this.align&&(E=E.padStart(this.longest+1," ")),"left"===this.align&&(E=E.padEnd(this.longest+1," "));let B=this.values[a]=g||s,y=g?"success":"dark";!0!==await f.call(e,B,this.state)&&(y="danger");let m=(0,n[y])(await this.indicator(e,t))+(e.pad||""),w=this.indent(e),Q=()=>[w,m,E+I,g,d].filter(Boolean).join(" ");if(r.submitted)return E=A.unstyle(E),g=u(g),d="",Q();if(e.format)g=await e.format.call(this,g,e,t);else{let e=this.styles.muted;g=o(this,{input:g,initial:s,pos:i,showCursor:C,color:e})}return this.isValue(g)||(g=this.styles.muted(this.symbols.ellipsis)),e.result&&(this.values[a]=await e.result.call(this,B,e,t)),C&&(E=h(E)),e.error?g+=(g?" ":"")+p(e.error.trim()):e.hint&&(g+=(g?" ":"")+l(e.hint.trim())),Q()}async submit(){return this.value=this.values,super.base.submit.call(this)}}},53609:(e,t,r)=>{"use strict";const A=r(10278),n=(e,r)=>{A.defineExport(t,e,r),A.defineExport(t,e.toLowerCase(),r)};n("AutoComplete",()=>r(63310)),n("BasicAuth",()=>r(52810)),n("Confirm",()=>r(65742)),n("Editable",()=>r(24570)),n("Form",()=>r(71447)),n("Input",()=>r(12372)),n("Invisible",()=>r(32684)),n("List",()=>r(40876)),n("MultiSelect",()=>r(42293)),n("Numeral",()=>r(42126)),n("Password",()=>r(84697)),n("Scale",()=>r(99580)),n("Select",()=>r(31557)),n("Snippet",()=>r(98094)),n("Sort",()=>r(60042)),n("Survey",()=>r(25223)),n("Text",()=>r(97298)),n("Toggle",()=>r(41817)),n("Quiz",()=>r(88677))},12372:(e,t,r)=>{"use strict";const A=r(45853),n=r(64083);e.exports=class extends A{constructor(e){super(e);let t=this.options.history;if(t&&t.store){let e=t.values||this.initial;this.autosave=!!t.autosave,this.store=t.store,this.data=this.store.get("values")||{past:[],present:e},this.initial=this.data.present||this.data.past[this.data.past.length-1]}}completion(e){return this.store?(this.data=n(e,this.data,this.input),this.data.present?(this.input=this.data.present,this.cursor=this.input.length,this.render()):this.alert()):this.alert()}altUp(){return this.completion("prev")}altDown(){return this.completion("next")}prev(){return this.save(),super.prev()}save(){this.store&&(this.data=n("save",this.data,this.input),this.store.set("values",this.data))}submit(){return this.store&&!0===this.autosave&&this.save(),super.submit()}}},32684:(e,t,r)=>{"use strict";const A=r(45853);e.exports=class extends A{format(){return""}}},40876:(e,t,r)=>{"use strict";const A=r(45853);e.exports=class extends A{constructor(e={}){super(e),this.sep=this.options.separator||/, */,this.initial=e.initial||""}split(e=this.value){return e?String(e).split(this.sep):[]}format(){let e=this.state.submitted?this.styles.primary:e=>e;return this.list.map(e).join(", ")}async submit(e){let t=this.state.error||await this.validate(this.list,this.state);return!0!==t?(this.state.error=t,super.submit()):(this.value=this.list,super.submit())}get list(){return this.split()}}},42293:(e,t,r)=>{"use strict";const A=r(31557);e.exports=class extends A{constructor(e){super({...e,multiple:!0})}}},42126:(e,t,r)=>{e.exports=r(64987)},84697:(e,t,r)=>{"use strict";const A=r(45853);e.exports=class extends A{constructor(e){super(e),this.cursorShow()}format(e=this.input){if(!this.keypressed)return"";return(this.state.submitted?this.styles.primary:this.styles.muted)(this.symbols.asterisk.repeat(e.length))}}},88677:(e,t,r)=>{"use strict";const A=r(31557);e.exports=class extends A{constructor(e){if(super(e),"number"!=typeof this.options.correctChoice||this.options.correctChoice<0)throw new Error("Please specify the index of the correct answer from the list of choices")}async toChoices(e,t){let r=await super.toChoices(e,t);if(r.length<2)throw new Error("Please give at least two choices to the user");if(this.options.correctChoice>r.length)throw new Error("Please specify the index of the correct answer from the list of choices");return r}check(e){return e.index===this.options.correctChoice}async result(e){return{selectedAnswer:e,correctAnswer:this.options.choices[this.options.correctChoice].value,correct:await this.check(this.state)}}}},99580:(e,t,r)=>{"use strict";const A=r(97991),n=r(14723),o=r(10278);e.exports=class extends n{constructor(e={}){super(e),this.widths=[].concat(e.messageWidth||50),this.align=[].concat(e.align||"left"),this.linebreak=e.linebreak||!1,this.edgeLength=e.edgeLength||3,this.newline=e.newline||"\n ";let t=e.startNumber||1;"number"==typeof this.scale&&(this.scaleKey=!1,this.scale=Array(this.scale).fill(0).map((e,r)=>({name:r+t})))}async reset(){return this.tableized=!1,await super.reset(),this.render()}tableize(){if(!0===this.tableized)return;this.tableized=!0;let e=0;for(let t of this.choices){e=Math.max(e,t.message.length),t.scaleIndex=t.initial||2,t.scale=[];for(let e=0;e=this.scale.length-1?this.alert():(e.scaleIndex++,this.render())}left(){let e=this.focused;return e.scaleIndex<=0?this.alert():(e.scaleIndex--,this.render())}indent(){return""}format(){if(this.state.submitted){return this.choices.map(e=>this.styles.info(e.index)).join(", ")}return""}pointer(){return""}renderScaleKey(){if(!1===this.scaleKey)return"";if(this.state.submitted)return"";return["",...this.scale.map(e=>` ${e.name} - ${e.message}`)].map(e=>this.styles.muted(e)).join("\n")}renderScaleHeading(e){let t=this.scale.map(e=>e.name);"function"==typeof this.options.renderScaleHeading&&(t=this.options.renderScaleHeading.call(this,e));let r=this.scaleLength-t.join("").length,A=Math.round(r/(t.length-1)),n=t.map(e=>this.styles.strong(e)).join(" ".repeat(A)),o=" ".repeat(this.widths[0]);return this.margin[3]+o+this.margin[1]+n}scaleIndicator(e,t,r){if("function"==typeof this.options.scaleIndicator)return this.options.scaleIndicator.call(this,e,t,r);let A=e.scaleIndex===t.index;return t.disabled?this.styles.hint(this.symbols.radio.disabled):A?this.styles.success(this.symbols.radio.on):this.symbols.radio.off}renderScale(e,t){let r=e.scale.map(r=>this.scaleIndicator(e,r,t)),A="Hyper"===this.term?"":" ";return r.join(A+this.symbols.line.repeat(this.edgeLength))}async renderChoice(e,t){await this.onChoice(e,t);let r=this.index===t,n=await this.pointer(e,t),i=await e.hint;i&&!o.hasColor(i)&&(i=this.styles.muted(i));let s=e=>this.margin[3]+e.replace(/\s+$/,"").padEnd(this.widths[0]," "),a=this.newline,c=this.indent(e),g=await this.resolve(e.message,this.state,e,t),l=await this.renderScale(e,t),u=this.margin[1]+this.margin[3];this.scaleLength=A.unstyle(l).length,this.widths[0]=Math.min(this.widths[0],this.width-this.scaleLength-u.length);let h=o.wordWrap(g,{width:this.widths[0],newline:a}).split("\n").map(e=>s(e)+this.margin[1]);return r&&(l=this.styles.info(l),h=h.map(e=>this.styles.info(e))),h[0]+=l,this.linebreak&&h.push(""),[c+n,h.join("\n")].filter(Boolean)}async renderChoices(){if(this.state.submitted)return"";this.tableize();let e=this.visible.map(async(e,t)=>await this.renderChoice(e,t)),t=await Promise.all(e),r=await this.renderScaleHeading();return this.margin[0]+[r,...t.map(e=>e.join(" "))].join("\n")}async render(){let{submitted:e,size:t}=this.state,r=await this.prefix(),A=await this.separator(),n=await this.message(),o="";!1!==this.options.promptLine&&(o=[r,n,A,""].join(" "),this.state.prompt=o);let i=await this.header(),s=await this.format(),a=await this.renderScaleKey(),c=await this.error()||await this.hint(),g=await this.renderChoices(),l=await this.footer(),u=this.emptyError;s&&(o+=s),c&&!o.includes(c)&&(o+=" "+c),e&&!s&&!g.trim()&&this.multiple&&null!=u&&(o+=this.styles.danger(u)),this.clear(t),this.write([i,o,a,g,l].filter(Boolean).join("\n")),this.state.submitted||this.write(this.margin[2]),this.restore()}submit(){this.value={};for(let e of this.choices)this.value[e.name]=e.scaleIndex;return this.base.submit.call(this)}}},31557:(e,t,r)=>{"use strict";const A=r(14723),n=r(10278);e.exports=class extends A{constructor(e){super(e),this.emptyError=this.options.emptyError||"No items were selected"}async dispatch(e,t){if(this.multiple)return this[t.name]?await this[t.name](e,t):await super.dispatch(e,t);this.alert()}separator(){if(this.options.separator)return super.separator();let e=this.styles.muted(this.symbols.ellipsis);return this.state.submitted?super.separator():e}pointer(e,t){return!this.multiple||this.options.pointer?super.pointer(e,t):""}indicator(e,t){return this.multiple?super.indicator(e,t):""}choiceMessage(e,t){let r=this.resolve(e.message,this.state,e,t);return"heading"!==e.role||n.hasColor(r)||(r=this.styles.strong(r)),this.resolve(r,this.state,e,t)}choiceSeparator(){return":"}async renderChoice(e,t){await this.onChoice(e,t);let r=this.index===t,A=await this.pointer(e,t),o=await this.indicator(e,t)+(e.pad||""),i=await this.resolve(e.hint,this.state,e,t);i&&!n.hasColor(i)&&(i=this.styles.muted(i));let s=this.indent(e),a=await this.choiceMessage(e,t),c=()=>[this.margin[3],s+A+o,a,this.margin[1],i].filter(Boolean).join(" ");return"heading"===e.role?c():e.disabled?(n.hasColor(a)||(a=this.styles.disabled(a)),c()):(r&&(a=this.styles.em(a)),c())}async renderChoices(){if("choices"===this.state.loading)return this.styles.warning("Loading choices");if(this.state.submitted)return"";let e=this.visible.map(async(e,t)=>await this.renderChoice(e,t)),t=await Promise.all(e);t.length||t.push(this.styles.danger("No matching choices"));let r,A=this.margin[0]+t.join("\n");return this.options.choicesHeader&&(r=await this.resolve(this.options.choicesHeader,this.state)),[r,A].filter(Boolean).join("\n")}format(){return!this.state.submitted||this.state.cancelled?"":Array.isArray(this.selected)?this.selected.map(e=>this.styles.primary(e.name)).join(", "):this.styles.primary(this.selected.name)}async render(){let{submitted:e,size:t}=this.state,r="",A=await this.header(),n=await this.prefix(),o=await this.separator(),i=await this.message();!1!==this.options.promptLine&&(r=[n,i,o,""].join(" "),this.state.prompt=r);let s=await this.format(),a=await this.error()||await this.hint(),c=await this.renderChoices(),g=await this.footer();s&&(r+=s),a&&!r.includes(a)&&(r+=" "+a),e&&!s&&!c.trim()&&this.multiple&&null!=this.emptyError&&(r+=this.styles.danger(this.emptyError)),this.clear(t),this.write([A,r,c,g].filter(Boolean).join("\n")),this.write(this.margin[2]),this.restore()}}},98094:(e,t,r)=>{"use strict";const A=r(97991),n=r(84368),o=r(58386);e.exports=class extends o{constructor(e){super(e),this.cursorHide(),this.reset(!0)}async initialize(){this.interpolate=await n(this),await super.initialize()}async reset(e){this.state.keys=[],this.state.invalid=new Map,this.state.missing=new Set,this.state.completed=0,this.state.values={},!0!==e&&(await this.initialize(),await this.render())}moveCursor(e){let t=this.getItem();this.cursor+=e,t.cursor+=e}dispatch(e,t){t.code||t.ctrl||null==e||!this.getItem()?this.alert():this.append(e,t)}append(e,t){let r=this.getItem(),A=r.input.slice(0,this.cursor),n=r.input.slice(this.cursor);this.input=r.input=`${A}${e}${n}`,this.moveCursor(1),this.render()}delete(){let e=this.getItem();if(this.cursor<=0||!e.input)return this.alert();let t=e.input.slice(this.cursor),r=e.input.slice(0,this.cursor-1);this.input=e.input=`${r}${t}`,this.moveCursor(-1),this.render()}increment(e){return e>=this.state.keys.length-1?0:e+1}decrement(e){return e<=0?this.state.keys.length-1:e-1}first(){this.state.index=0,this.render()}last(){this.state.index=this.state.keys.length-1,this.render()}right(){if(this.cursor>=this.input.length)return this.alert();this.moveCursor(1),this.render()}left(){if(this.cursor<=0)return this.alert();this.moveCursor(-1),this.render()}prev(){this.state.index=this.decrement(this.state.index),this.getItem(),this.render()}next(){this.state.index=this.increment(this.state.index),this.getItem(),this.render()}up(){this.prev()}down(){this.next()}format(e){let t=this.state.completed<100?this.styles.warning:this.styles.success;return!0===this.state.submitted&&100!==this.state.completed&&(t=this.styles.danger),t(this.state.completed+"% completed")}async render(){let{index:e,keys:t=[],submitted:r,size:A}=this.state,n=[this.options.newline,"\n"].find(e=>null!=e),o=await this.prefix(),i=await this.separator(),s=[o,await this.message(),i].filter(Boolean).join(" ");this.state.prompt=s;let a=await this.header(),c=await this.error()||"",g=await this.hint()||"",l=r?"":await this.interpolate(this.state),u=this.state.key=t[e]||"",h=await this.format(u),p=await this.footer();h&&(s+=" "+h),g&&!h&&0===this.state.completed&&(s+=" "+g),this.clear(A);let d=[a,s,l,p,c.trim()];this.write(d.filter(Boolean).join(n)),this.restore()}getItem(e){let{items:t,keys:r,index:A}=this.state,n=t.find(e=>e.name===r[A]);return n&&null!=n.input&&(this.input=n.input,this.cursor=n.cursor),n}async submit(){"function"!=typeof this.interpolate&&await this.initialize(),await this.interpolate(this.state,!0);let{invalid:e,missing:t,output:r,values:n}=this.state;if(e.size){let t="";for(let[r,A]of e)t+=`Invalid ${r}: ${A}\n`;return this.state.error=t,super.submit()}if(t.size)return this.state.error="Required: "+[...t.keys()].join(", "),super.submit();let o=A.unstyle(r).split("\n").map(e=>e.slice(1)).join("\n");return this.value={values:n,result:o},super.submit()}}},60042:(e,t,r)=>{"use strict";const A="(Use + to sort)",n=r(31557);e.exports=class extends n{constructor(e){super({...e,reorder:!1,sort:!0,multiple:!0}),this.state.hint=[this.options.hint,A].find(this.isValue.bind(this))}indicator(){return""}async renderChoice(e,t){let r=await super.renderChoice(e,t),A=this.symbols.identicalTo+" ",n=this.index===t&&this.sorting?this.styles.muted(A):" ";return!1===this.options.drag&&(n=""),!0===this.options.numbered?n+(t+1+" - ")+r:n+r}get selected(){return this.choices}submit(){return this.value=this.choices.map(e=>e.value),super.submit()}}},25223:(e,t,r)=>{"use strict";const A=r(14723);function n(e,t={}){if(Array.isArray(t.scale))return t.scale.map(e=>({...e}));let r=[];for(let t=1;tthis.styles.muted(e)),this.state.header=e.join("\n ")}}async toChoices(...e){if(this.createdScales)return!1;this.createdScales=!0;let t=await super.toChoices(...e);for(let e of t)e.scale=n(5,this.options),e.scaleIdx=2;return t}dispatch(){this.alert()}space(){let e=this.focused,t=e.scale[e.scaleIdx],r=t.selected;return e.scale.forEach(e=>e.selected=!1),t.selected=!r,this.render()}indicator(){return""}pointer(){return""}separator(){return this.styles.muted(this.symbols.ellipsis)}right(){let e=this.focused;return e.scaleIdx>=e.scale.length-1?this.alert():(e.scaleIdx++,this.render())}left(){let e=this.focused;return e.scaleIdx<=0?this.alert():(e.scaleIdx--,this.render())}indent(){return" "}async renderChoice(e,t){await this.onChoice(e,t);let r=this.index===t,A="Hyper"===this.term,n=A?9:8,o=A?"":" ",i=this.symbols.line.repeat(n),s=" ".repeat(n+(A?0:1)),a=e=>(e?this.styles.success("◉"):"◯")+o,c=t+1+".",g=r?this.styles.heading:this.styles.noop,l=await this.resolve(e.message,this.state,e,t),u=this.indent(e),h=u+e.scale.map((t,r)=>a(r===e.scaleIdx)).join(i),p=u+e.scale.map((t,r)=>(t=>t===e.scaleIdx?g(t):t)(r)).join(s);return r&&(h=this.styles.cyan(h),p=this.styles.cyan(p)),[[c,l].filter(Boolean).join(" "),h,p," "].filter(Boolean).join("\n")}async renderChoices(){if(this.state.submitted)return"";let e=this.visible.map(async(e,t)=>await this.renderChoice(e,t)),t=await Promise.all(e);return t.length||t.push(this.styles.danger("No matching choices")),t.join("\n")}format(){if(this.state.submitted){return this.choices.map(e=>this.styles.info(e.scaleIdx)).join(", ")}return""}async render(){let{submitted:e,size:t}=this.state,r=await this.prefix(),A=await this.separator(),n=[r,await this.message(),A].filter(Boolean).join(" ");this.state.prompt=n;let o=await this.header(),i=await this.format(),s=await this.error()||await this.hint(),a=await this.renderChoices(),c=await this.footer();!i&&s||(n+=" "+i),s&&!n.includes(s)&&(n+=" "+s),e&&!i&&!a&&this.multiple&&"form"!==this.type&&(n+=this.styles.danger(this.emptyError)),this.clear(t),this.write([n,o,a,c].filter(Boolean).join("\n")),this.restore()}submit(){this.value={};for(let e of this.choices)this.value[e.name]=e.scaleIdx;return this.base.submit.call(this)}}},97298:(e,t,r)=>{e.exports=r(12372)},41817:(e,t,r)=>{"use strict";const A=r(82710);e.exports=class extends A{async initialize(){await super.initialize(),this.value=this.initial=!!this.options.initial,this.disabled=this.options.disabled||"no",this.enabled=this.options.enabled||"yes",await this.render()}reset(){this.value=this.initial,this.render()}delete(){this.alert()}toggle(){this.value=!this.value,this.render()}enable(){if(!0===this.value)return this.alert();this.value=!0,this.render()}disable(){if(!1===this.value)return this.alert();this.value=!1,this.render()}up(){this.toggle()}down(){this.toggle()}right(){this.toggle()}left(){this.toggle()}next(){this.toggle()}prev(){this.toggle()}dispatch(e="",t){switch(e.toLowerCase()){case" ":return this.toggle();case"1":case"y":case"t":return this.enable();case"0":case"n":case"f":return this.disable();default:return this.alert()}}format(){let e=e=>this.styles.primary.underline(e);return[this.value?this.disabled:e(this.disabled),this.value?e(this.enabled):this.enabled].join(this.styles.muted(" / "))}async render(){let{size:e}=this.state,t=await this.header(),r=await this.prefix(),A=await this.separator(),n=await this.message(),o=await this.format(),i=await this.error()||await this.hint(),s=await this.footer(),a=[r,n,A,o].join(" ");this.state.prompt=a,i&&!a.includes(i)&&(a+=" "+i),this.clear(e),this.write([t,a,s].filter(Boolean).join("\n")),this.write(this.margin[2]),this.restore()}}},27011:(e,t,r)=>{"use strict";const A=r(10278),n={default:(e,t)=>t,checkbox(e,t){throw new Error("checkbox role is not implemented yet")},editable(e,t){throw new Error("editable role is not implemented yet")},expandable(e,t){throw new Error("expandable role is not implemented yet")},heading:(e,t)=>(t.disabled="",t.indicator=[t.indicator," "].find(e=>null!=e),t.message=t.message||"",t),input(e,t){throw new Error("input role is not implemented yet")},option:(e,t)=>n.default(e,t),radio(e,t){throw new Error("radio role is not implemented yet")},separator:(e,t)=>(t.disabled="",t.indicator=[t.indicator," "].find(e=>null!=e),t.message=t.message||e.symbols.line.repeat(5),t),spacer:(e,t)=>t};e.exports=(e,t={})=>{let r=A.merge({},n,t.roles);return r[e]||r.default}},61807:(e,t,r)=>{"use strict";const{define:A,width:n}=r(10278);e.exports=class{constructor(e){let t=e.options;A(this,"_prompt",e),this.type=e.type,this.name=e.name,this.message="",this.header="",this.footer="",this.error="",this.hint="",this.input="",this.cursor=0,this.index=0,this.lines=0,this.tick=0,this.prompt="",this.buffer="",this.width=n(t.stdout||process.stdout),Object.assign(this,t),this.name=this.name||this.message,this.message=this.message||this.name,this.symbols=e.symbols,this.styles=e.styles,this.required=new Set,this.cancelled=!1,this.submitted=!1}clone(){let e={...this};return e.status=this.status,e.buffer=Buffer.from(e.buffer),delete e.clone,e}set color(e){this._color=e}get color(){let e=this.prompt.styles;if(this.cancelled)return e.cancelled;if(this.submitted)return e.submitted;let t=this._color||e[this.status];return"function"==typeof t?t:e.pending}set loading(e){this._loading=e}get loading(){return"boolean"==typeof this._loading?this._loading:!!this.loadingChoices&&"choices"}get status(){return this.cancelled?"cancelled":this.submitted?"submitted":"pending"}}},64402:(e,t,r)=>{"use strict";const A=r(10278),n=r(97991),o={default:n.noop,noop:n.noop,set inverse(e){this._inverse=e},get inverse(){return this._inverse||A.inverse(this.primary)},set complement(e){this._complement=e},get complement(){return this._complement||A.complement(this.primary)},primary:n.cyan,success:n.green,danger:n.magenta,strong:n.bold,warning:n.yellow,muted:n.dim,disabled:n.gray,dark:n.dim.gray,underline:n.underline,set info(e){this._info=e},get info(){return this._info||this.primary},set em(e){this._em=e},get em(){return this._em||this.primary.underline},set heading(e){this._heading=e},get heading(){return this._heading||this.muted.underline},set pending(e){this._pending=e},get pending(){return this._pending||this.primary},set submitted(e){this._submitted=e},get submitted(){return this._submitted||this.success},set cancelled(e){this._cancelled=e},get cancelled(){return this._cancelled||this.danger},set typing(e){this._typing=e},get typing(){return this._typing||this.dim},set placeholder(e){this._placeholder=e},get placeholder(){return this._placeholder||this.primary.dim},set highlight(e){this._highlight=e},get highlight(){return this._highlight||this.inverse},merge:(e={})=>{e.styles&&"boolean"==typeof e.styles.enabled&&(n.enabled=e.styles.enabled),e.styles&&"boolean"==typeof e.styles.visible&&(n.visible=e.styles.visible);let t=A.merge({},o,e.styles);delete t.merge;for(let e of Object.keys(n))t.hasOwnProperty(e)||Reflect.defineProperty(t,e,{get:()=>n[e]});for(let e of Object.keys(n.styles))t.hasOwnProperty(e)||Reflect.defineProperty(t,e,{get:()=>n[e]});return t}};e.exports=o},50511:(e,t,r)=>{"use strict";const A="win32"===process.platform,n=r(97991),o=r(10278),i={...n.symbols,upDownDoubleArrow:"⇕",upDownDoubleArrow2:"⬍",upDownArrow:"↕",asterisk:"*",asterism:"⁂",bulletWhite:"◦",electricArrow:"⌁",ellipsisLarge:"⋯",ellipsisSmall:"…",fullBlock:"█",identicalTo:"≡",indicator:n.symbols.check,leftAngle:"‹",mark:"※",minus:"−",multiplication:"×",obelus:"÷",percent:"%",pilcrow:"¶",pilcrow2:"❡",pencilUpRight:"✐",pencilDownRight:"✎",pencilRight:"✏",plus:"+",plusMinus:"±",pointRight:"☞",rightAngle:"›",section:"§",hexagon:{off:"⬡",on:"⬢",disabled:"⬢"},ballot:{on:"☑",off:"☐",disabled:"☒"},stars:{on:"★",off:"☆",disabled:"☆"},folder:{on:"▼",off:"▶",disabled:"▶"},prefix:{pending:n.symbols.question,submitted:n.symbols.check,cancelled:n.symbols.cross},separator:{pending:n.symbols.pointerSmall,submitted:n.symbols.middot,cancelled:n.symbols.middot},radio:{off:A?"( )":"◯",on:A?"(*)":"◉",disabled:A?"(|)":"Ⓘ"},numbers:["⓪","①","②","③","④","⑤","⑥","⑦","⑧","⑨","⑩","⑪","⑫","⑬","⑭","⑮","⑯","⑰","⑱","⑲","⑳","㉑","㉒","㉓","㉔","㉕","㉖","㉗","㉘","㉙","㉚","㉛","㉜","㉝","㉞","㉟","㊱","㊲","㊳","㊴","㊵","㊶","㊷","㊸","㊹","㊺","㊻","㊼","㊽","㊾","㊿"]};i.merge=e=>{let t=o.merge({},n.symbols,i,e.symbols);return delete t.merge,t},e.exports=i},26205:(e,t,r)=>{"use strict";const A=r(64402),n=r(50511),o=r(10278);e.exports=e=>{e.options=o.merge({},e.options.theme,e.options),e.symbols=n.merge(e.options),e.styles=A.merge(e.options)}},47159:e=>{"use strict";function t(e,t,r={}){let A=e.timers[t]={name:t,start:Date.now(),ms:0,tick:0},n=r.interval||120;A.frames=r.frames||[],A.loading=!0;let o=setInterval(()=>{A.ms=Date.now()-A.start,A.tick++,e.render()},n);return A.stop=()=>{A.loading=!1,clearInterval(o)},Reflect.defineProperty(A,"interval",{value:o}),e.once("close",()=>A.stop()),A.stop}e.exports=e=>{e.timers=e.timers||{};let r=e.options.timers;if(r)for(let A of Object.keys(r)){let n=r[A];"number"==typeof n&&(n={interval:n}),t(e,A,n)}}},14723:(e,t,r)=>{"use strict";const A=r(97991),n=r(58386),o=r(27011),i=r(10278),{reorder:s,scrollUp:a,scrollDown:c,isObject:g,swap:l}=i;function u(e,t){if(t instanceof Promise)return t;if("function"==typeof t){if(i.isAsyncFn(t))return t;t=t.call(e,e)}for(let r of t){if(Array.isArray(r.choices)){let t=r.choices.filter(t=>!e.isDisabled(t));r.enabled=t.every(e=>!0===e.enabled)}!0===e.isDisabled(r)&&delete r.enabled}return t}e.exports=class extends n{constructor(e){super(e),this.cursorHide(),this.maxSelected=e.maxSelected||1/0,this.multiple=e.multiple||!1,this.initial=e.initial||0,this.delay=e.delay||0,this.longest=0,this.num=""}async initialize(){"function"==typeof this.options.initial&&(this.initial=await this.options.initial.call(this)),await this.reset(!0),await super.initialize()}async reset(){let{choices:e,initial:t,autofocus:r,suggest:A}=this.options;if(this.state._choices=[],this.state.choices=[],this.choices=await Promise.all(await this.toChoices(e)),this.choices.forEach(e=>e.enabled=!1),"function"!=typeof A&&0===this.selectable.length)throw new Error("At least one choice must be selectable");g(t)&&(t=Object.keys(t)),Array.isArray(t)?(null!=r&&(this.index=this.findIndex(r)),t.forEach(e=>this.enable(this.find(e))),await this.render()):(null!=r&&(t=r),"string"==typeof t&&(t=this.findIndex(t)),"number"==typeof t&&t>-1&&(this.index=Math.max(0,Math.min(t,this.choices.length)),this.enable(this.find(this.index)))),this.isDisabled(this.focused)&&await this.down()}async toChoices(e,t){this.state.loadingChoices=!0;let r=[],A=0,n=async(e,t)=>{"function"==typeof e&&(e=await e.call(this)),e instanceof Promise&&(e=await e);for(let o=0;o(this.state.loadingChoices=!1,e))}async toChoice(e,t,r){if("function"==typeof e&&(e=await e.call(this,this)),e instanceof Promise&&(e=await e),"string"==typeof e&&(e={name:e}),e.normalized)return e;e.normalized=!0;let n=e.value,s=o(e.role,this.options);if("string"!=typeof(e=s(this,e)).disabled||e.hint||(e.hint=e.disabled,e.disabled=!0),!0===e.disabled&&null==e.hint&&(e.hint="(disabled)"),null!=e.index)return e;e.name=e.name||e.key||e.title||e.value||e.message,e.message=e.message||e.name||"",e.value=[e.value,e.name].find(this.isValue.bind(this)),e.input="",e.index=t,e.cursor=0,i.define(e,"parent",r),e.level=r?r.level+1:1,null==e.indent&&(e.indent=r?r.indent+" ":e.indent||""),e.path=r?r.path+"."+e.name:e.name,e.enabled=!(!this.multiple||this.isDisabled(e)||!e.enabled&&!this.isSelected(e)),this.isDisabled(e)||(this.longest=Math.max(this.longest,A.unstyle(e.message).length));let a={...e};return e.reset=(t=a.input,r=a.value)=>{for(let t of Object.keys(a))e[t]=a[t];e.input=t,e.value=r},null==n&&"function"==typeof e.initial&&(e.input=await e.initial.call(this,this.state,e,t)),e}async onChoice(e,t){this.emit("choice",e,t,this),"function"==typeof e.onChoice&&await e.onChoice.call(this,this.state,e,t)}async addChoice(e,t,r){let A=await this.toChoice(e,t,r);return this.choices.push(A),this.index=this.choices.length-1,this.limit=this.choices.length,A}async newItem(e,t,r){let A={name:"New choice name?",editable:!0,newChoice:!0,...e},n=await this.addChoice(A,t,r);return n.updateChoice=()=>{delete n.newChoice,n.name=n.message=n.input,n.input="",n.cursor=0},this.render()}indent(e){return null==e.indent?e.level>1?" ".repeat(e.level-1):"":e.indent}dispatch(e,t){if(this.multiple&&this[t.name])return this[t.name]();this.alert()}focus(e,t){return"boolean"!=typeof t&&(t=e.enabled),t&&!e.enabled&&this.selected.length>=this.maxSelected?this.alert():(this.index=e.index,e.enabled=t&&!this.isDisabled(e),e)}space(){return this.multiple?(this.toggle(this.focused),this.render()):this.alert()}a(){if(this.maxSelectede.enabled);return this.choices.forEach(t=>t.enabled=!e),this.render()}i(){return this.choices.length-this.selected.length>this.maxSelected?this.alert():(this.choices.forEach(e=>e.enabled=!e.enabled),this.render())}g(e=this.focused){return this.choices.some(e=>!!e.parent)?(this.toggle(e.parent&&!e.choices?e.parent:e),this.render()):this.a()}toggle(e,t){if(!e.enabled&&this.selected.length>=this.maxSelected)return this.alert();"boolean"!=typeof t&&(t=!e.enabled),e.enabled=t,e.choices&&e.choices.forEach(e=>this.toggle(e,t));let r=e.parent;for(;r;){let e=r.choices.filter(e=>this.isDisabled(e));r.enabled=e.every(e=>!0===e.enabled),r=r.parent}return u(this,this.choices),this.emit("toggle",e,this),e}enable(e){return this.selected.length>=this.maxSelected?this.alert():(e.enabled=!this.isDisabled(e),e.choices&&e.choices.forEach(this.enable.bind(this)),e)}disable(e){return e.enabled=!1,e.choices&&e.choices.forEach(this.disable.bind(this)),e}number(e){this.num+=e;let t=e=>{let t=Number(e);if(t>this.choices.length-1)return this.alert();let r=this.focused,A=this.choices.find(e=>t===e.index);if(!A.enabled&&this.selected.length>=this.maxSelected)return this.alert();if(-1===this.visible.indexOf(A)){let e=s(this.choices),t=e.indexOf(A);if(r.index>t){let r=e.slice(t,t+this.limit),A=e.filter(e=>!r.includes(e));this.choices=r.concat(A)}else{let r=t-this.limit+1;this.choices=e.slice(r).concat(e.slice(0,r))}}return this.index=this.choices.indexOf(A),this.toggle(this.focused),this.render()};return clearTimeout(this.numberTimeout),new Promise(e=>{let r=this.choices.length,A=this.num,n=(r=!1,n)=>{clearTimeout(this.numberTimeout),r&&(n=t(A)),this.num="",e(n)};return"0"===A||1===A.length&&Number(A+"0")>r?n(!0):Number(A)>r?n(!1,this.alert()):void(this.numberTimeout=setTimeout(()=>n(!0),this.delay))})}home(){return this.choices=s(this.choices),this.index=0,this.render()}end(){let e=this.choices.length-this.limit,t=s(this.choices);return this.choices=t.slice(e).concat(t.slice(0,e)),this.index=this.limit-1,this.render()}first(){return this.index=0,this.render()}last(){return this.index=this.visible.length-1,this.render()}prev(){return this.visible.length<=1?this.alert():this.up()}next(){return this.visible.length<=1?this.alert():this.down()}right(){return this.cursor>=this.input.length?this.alert():(this.cursor++,this.render())}left(){return this.cursor<=0?this.alert():(this.cursor--,this.render())}up(){let e=this.choices.length,t=this.visible.length,r=this.index;return!1===this.options.scroll&&0===r?this.alert():e>t&&0===r?this.scrollUp():(this.index=(r-1%e+e)%e,this.isDisabled()?this.up():this.render())}down(){let e=this.choices.length,t=this.visible.length,r=this.index;return!1===this.options.scroll&&r===t-1?this.alert():e>t&&r===t-1?this.scrollDown():(this.index=(r+1)%e,this.isDisabled()?this.down():this.render())}scrollUp(e=0){return this.choices=a(this.choices),this.index=e,this.isDisabled()?this.up():this.render()}scrollDown(e=this.visible.length-1){return this.choices=c(this.choices),this.index=e,this.isDisabled()?this.down():this.render()}async shiftUp(){return!0===this.options.sort?(this.sorting=!0,this.swap(this.index-1),await this.up(),void(this.sorting=!1)):this.scrollUp(this.index)}async shiftDown(){return!0===this.options.sort?(this.sorting=!0,this.swap(this.index+1),await this.down(),void(this.sorting=!1)):this.scrollDown(this.index)}pageUp(){return this.visible.length<=1?this.alert():(this.limit=Math.max(this.limit-1,0),this.index=Math.min(this.limit-1,this.index),this._limit=this.limit,this.isDisabled()?this.up():this.render())}pageDown(){return this.visible.length>=this.choices.length?this.alert():(this.index=Math.max(0,this.index),this.limit=Math.min(this.limit+1,this.choices.length),this._limit=this.limit,this.isDisabled()?this.down():this.render())}swap(e){l(this.choices,this.index,e)}isDisabled(e=this.focused){return!(!e||!["disabled","collapsed","hidden","completing","readonly"].some(t=>!0===e[t]))||e&&"heading"===e.role}isEnabled(e=this.focused){if(Array.isArray(e))return e.every(e=>this.isEnabled(e));if(e.choices){let t=e.choices.filter(e=>!this.isDisabled(e));return e.enabled&&t.every(e=>this.isEnabled(e))}return e.enabled&&!this.isDisabled(e)}isChoice(e,t){return e.name===t||e.index===Number(t)}isSelected(e){return Array.isArray(this.initial)?this.initial.some(t=>this.isChoice(e,t)):this.isChoice(e,this.initial)}map(e=[],t="value"){return[].concat(e||[]).reduce((e,r)=>(e[r]=this.find(r,t),e),{})}filter(e,t){let r="function"==typeof e?e:(t,r)=>[t.name,r].includes(e),A=(this.options.multiple?this.state._choices:this.choices).filter(r);return t?A.map(e=>e[t]):A}find(e,t){if(g(e))return t?e[t]:e;let r="function"==typeof e?e:(t,r)=>[t.name,r].includes(e),A=this.choices.find(r);return A?t?A[t]:A:void 0}findIndex(e){return this.choices.indexOf(this.find(e))}async submit(){let e=this.focused;if(!e)return this.alert();if(e.newChoice)return e.input?(e.updateChoice(),this.render()):this.alert();if(this.choices.some(e=>e.newChoice))return this.alert();let{reorder:t,sort:r}=this.options,A=!0===this.multiple,n=this.selected;return void 0===n?this.alert():(Array.isArray(n)&&!1!==t&&!0!==r&&(n=i.reorder(n)),this.value=A?n.map(e=>e.name):n.name,super.submit())}set choices(e=[]){this.state._choices=this.state._choices||[],this.state.choices=e;for(let t of e)this.state._choices.some(e=>e.name===t.name)||this.state._choices.push(t);if(!this._initial&&this.options.initial){this._initial=!0;let e=this.initial;if("string"==typeof e||"number"==typeof e){let t=this.find(e);t&&(this.initial=t.index,this.focus(t,!0))}}}get choices(){return u(this,this.state.choices||[])}set visible(e){this.state.visible=e}get visible(){return(this.state.visible||this.choices).slice(0,this.limit)}set limit(e){this.state.limit=e}get limit(){let{state:e,options:t,choices:r}=this,A=e.limit||this._limit||t.limit||r.length;return Math.min(A,this.height)}set value(e){super.value=e}get value(){return"string"!=typeof super.value&&super.value===this.initial?this.input:super.value}set index(e){this.state.index=e}get index(){return Math.max(0,this.state?this.state.index:0)}get enabled(){return this.filter(this.isEnabled.bind(this))}get focused(){let e=this.choices[this.index];return e&&this.state.submitted&&!0!==this.multiple&&(e.enabled=!0),e}get selectable(){return this.choices.filter(e=>!this.isDisabled(e))}get selected(){return this.multiple?this.enabled:this.focused}}},46614:(e,t,r)=>{"use strict";const A=r(71447),n=()=>{throw new Error("expected prompt to have a custom authenticate method")},o=(e=n)=>class extends A{constructor(e){super(e)}async submit(){this.value=await e.call(this,this.values,this.state),super.base.submit.call(this)}static create(e){return o(e)}};e.exports=o()},82710:(e,t,r)=>{"use strict";const A=r(58386),{isPrimitive:n,hasColor:o}=r(10278);e.exports=class extends A{constructor(e){super(e),this.cursorHide()}async initialize(){let e=await this.resolve(this.initial,this.state);this.input=await this.cast(e),await super.initialize()}dispatch(e){return this.isValue(e)?(this.input=e,this.submit()):this.alert()}format(e){let{styles:t,state:r}=this;return r.submitted?t.success(e):t.primary(e)}cast(e){return this.isTrue(e)}isTrue(e){return/^[ty1]/i.test(e)}isFalse(e){return/^[fn0]/i.test(e)}isValue(e){return n(e)&&(this.isTrue(e)||this.isFalse(e))}async hint(){if("pending"===this.state.status){let e=await this.element("hint");return o(e)?e:this.styles.muted(e)}}async render(){let{input:e,size:t}=this.state,r=await this.prefix(),A=await this.separator(),n=[r,await this.message(),this.styles.muted(this.default),A].filter(Boolean).join(" ");this.state.prompt=n;let o=await this.header(),i=this.value=this.cast(e),s=await this.format(i),a=await this.error()||await this.hint(),c=await this.footer();a&&!n.includes(a)&&(s+=" "+a),n+=" "+s,this.clear(t),this.write([o,n,c].filter(Boolean).join("\n")),this.restore()}set value(e){super.value=e}get value(){return this.cast(super.value)}}},13235:(e,t,r)=>{e.exports={ArrayPrompt:r(14723),AuthPrompt:r(46614),BooleanPrompt:r(82710),NumberPrompt:r(64987),StringPrompt:r(45853)}},64987:(e,t,r)=>{"use strict";const A=r(45853);e.exports=class extends A{constructor(e={}){super({style:"number",...e}),this.min=this.isValue(e.min)?this.toNumber(e.min):-1/0,this.max=this.isValue(e.max)?this.toNumber(e.max):1/0,this.delay=null!=e.delay?e.delay:1e3,this.float=!1!==e.float,this.round=!0===e.round||!1===e.float,this.major=e.major||10,this.minor=e.minor||1,this.initial=null!=e.initial?e.initial:"",this.input=String(this.initial),this.cursor=this.input.length,this.cursorShow()}append(e){return!/[-+.]/.test(e)||"."===e&&this.input.includes(".")?this.alert("invalid number"):super.append(e)}number(e){return super.append(e)}next(){return this.input&&this.input!==this.initial?this.alert():this.isValue(this.initial)?(this.input=this.initial,this.cursor=String(this.initial).length,this.render()):this.alert()}up(e){let t=e||this.minor,r=this.toNumber(this.input);return r>this.max+t?this.alert():(this.input=""+(r+t),this.render())}down(e){let t=e||this.minor,r=this.toNumber(this.input);return rthis.isValue(e));return this.value=this.toNumber(e||0),super.submit()}}},45853:(e,t,r)=>{"use strict";const A=r(58386),n=r(96496),{isPrimitive:o}=r(10278);e.exports=class extends A{constructor(e){super(e),this.initial=o(this.initial)?String(this.initial):"",this.initial&&this.cursorHide(),this.state.prevCursor=0,this.state.clipboard=[]}async keypress(e,t={}){let r=this.state.prevKeypress;return this.state.prevKeypress=t,!0!==this.options.multiline||"return"!==t.name||r&&"return"===r.name?super.keypress(e,t):this.append("\n",t)}moveCursor(e){this.cursor+=e}reset(){return this.input=this.value="",this.cursor=0,this.render()}dispatch(e,t){if(!e||t.ctrl||t.code)return this.alert();this.append(e)}append(e){let{cursor:t,input:r}=this.state;this.input=(""+r).slice(0,t)+e+(""+r).slice(t),this.moveCursor(String(e).length),this.render()}insert(e){this.append(e)}delete(){let{cursor:e,input:t}=this.state;if(e<=0)return this.alert();this.input=(""+t).slice(0,e-1)+(""+t).slice(e),this.moveCursor(-1),this.render()}deleteForward(){let{cursor:e,input:t}=this.state;if(void 0===t[e])return this.alert();this.input=(""+t).slice(0,e)+(""+t).slice(e+1),this.render()}cutForward(){let e=this.cursor;if(this.input.length<=e)return this.alert();this.state.clipboard.push(this.input.slice(e)),this.input=this.input.slice(0,e),this.render()}cutLeft(){let e=this.cursor;if(0===e)return this.alert();let t=this.input.slice(0,e),r=this.input.slice(e),A=t.split(" ");this.state.clipboard.push(A.pop()),this.input=A.join(" "),this.cursor=this.input.length,this.input+=r,this.render()}paste(){if(!this.state.clipboard.length)return this.alert();this.insert(this.state.clipboard.pop()),this.render()}toggleCursor(){this.state.prevCursor?(this.cursor=this.state.prevCursor,this.state.prevCursor=0):(this.state.prevCursor=this.cursor,this.cursor=0),this.render()}first(){this.cursor=0,this.render()}last(){this.cursor=this.input.length-1,this.render()}next(){let e=null!=this.initial?String(this.initial):"";if(!e||!e.startsWith(this.input))return this.alert();this.input=this.initial,this.cursor=this.initial.length,this.render()}prev(){if(!this.input)return this.alert();this.reset()}backward(){return this.left()}forward(){return this.right()}right(){return this.cursor>=this.input.length?this.alert():(this.moveCursor(1),this.render())}left(){return this.cursor<=0?this.alert():(this.moveCursor(-1),this.render())}isValue(e){return!!e}async format(e=this.value){let t=await this.resolve(this.initial,this.state);return this.state.submitted?this.styles.submitted(e||t):n(this,{input:e,initial:t,pos:this.cursor})}async render(){let e=this.state.size,t=await this.prefix(),r=await this.separator(),A=[t,await this.message(),r].filter(Boolean).join(" ");this.state.prompt=A;let n=await this.header(),o=await this.format(),i=await this.error()||await this.hint(),s=await this.footer();i&&!o.includes(i)&&(o+=" "+i),A+=" "+o,this.clear(e),this.write([n,A,s].filter(Boolean).join("\n")),this.restore()}}},10278:(e,t,r)=>{"use strict";const A=Object.prototype.toString,n=r(97991);let o=!1,i=[];const s={yellow:"blue",cyan:"red",green:"magenta",black:"white",blue:"yellow",red:"cyan",magenta:"green",white:"black"};t.longest=(e,t)=>e.reduce((e,r)=>Math.max(e,t?r[t].length:r.length),0),t.hasColor=e=>!!e&&n.hasColor(e);const a=t.isObject=e=>null!==e&&"object"==typeof e&&!Array.isArray(e);t.nativeType=e=>A.call(e).slice(8,-1).toLowerCase().replace(/\s/g,""),t.isAsyncFn=e=>"asyncfunction"===t.nativeType(e),t.isPrimitive=e=>null!=e&&"object"!=typeof e&&"function"!=typeof e,t.resolve=(e,t,...r)=>"function"==typeof t?t.call(e,...r):t,t.scrollDown=(e=[])=>[...e.slice(1),e[0]],t.scrollUp=(e=[])=>[e.pop(),...e],t.reorder=(e=[])=>{let t=e.slice();return t.sort((e,t)=>e.index>t.index?1:e.index{let A=e.length,n=r===A?0:r<0?A-1:r,o=e[t];e[t]=e[n],e[n]=o},t.width=(e,t=80)=>{let r=e&&e.columns?e.columns:t;return e&&"function"==typeof e.getWindowSize&&(r=e.getWindowSize()[0]),"win32"===process.platform?r-1:r},t.height=(e,t=20)=>{let r=e&&e.rows?e.rows:t;return e&&"function"==typeof e.getWindowSize&&(r=e.getWindowSize()[1]),r},t.wordWrap=(e,t={})=>{if(!e)return e;"number"==typeof t&&(t={width:t});let{indent:r="",newline:A="\n"+r,width:n=80}=t,o=(A+r).match(/[^\S\n]/g)||[];n-=o.length;let i=`.{1,${n}}([\\s\\u200B]+|$)|[^\\s\\u200B]+?([\\s\\u200B]+|$)`,s=e.trim(),a=new RegExp(i,"g"),c=s.match(a)||[];return c=c.map(e=>e.replace(/\n$/,"")),t.padEnd&&(c=c.map(e=>e.padEnd(n," "))),t.padStart&&(c=c.map(e=>e.padStart(n," "))),r+c.join(A)},t.unmute=e=>{let t=e.stack.find(e=>n.keys.color.includes(e));return t?n[t]:e.stack.find(e=>"bg"===e.slice(2))?n[t.slice(2)]:e=>e},t.pascal=e=>e?e[0].toUpperCase()+e.slice(1):"",t.inverse=e=>{if(!e||!e.stack)return e;let r=e.stack.find(e=>n.keys.color.includes(e));if(r){let A=n["bg"+t.pascal(r)];return A?A.black:e}let A=e.stack.find(e=>"bg"===e.slice(0,2));return A?n[A.slice(2).toLowerCase()]||e:n.none},t.complement=e=>{if(!e||!e.stack)return e;let r=e.stack.find(e=>n.keys.color.includes(e)),A=e.stack.find(e=>"bg"===e.slice(0,2));if(r&&!A)return n[s[r]||r];if(A){let r=A.slice(2).toLowerCase(),o=s[r];return o&&n["bg"+t.pascal(o)]||e}return n.none},t.meridiem=e=>{let t=e.getHours(),r=e.getMinutes(),A=t>=12?"pm":"am";return t%=12,(0===t?12:t)+":"+(r<10?"0"+r:r)+" "+A},t.set=(e={},r="",A)=>r.split(".").reduce((e,r,n,o)=>{let i=o.length-1>n?e[r]||{}:A;return!t.isObject(i)&&n{let A=null==e[t]?t.split(".").reduce((e,t)=>e&&e[t],e):e[t];return null==A?r:A},t.mixin=(e,r)=>{if(!a(e))return r;if(!a(r))return e;for(let A of Object.keys(r)){let n=Object.getOwnPropertyDescriptor(r,A);if(n.hasOwnProperty("value"))if(e.hasOwnProperty(A)&&a(n.value)){let o=Object.getOwnPropertyDescriptor(e,A);a(o.value)?e[A]=t.merge({},e[A],r[A]):Reflect.defineProperty(e,A,n)}else Reflect.defineProperty(e,A,n);else Reflect.defineProperty(e,A,n)}return e},t.merge=(...e)=>{let r={};for(let A of e)t.mixin(r,A);return r},t.mixinEmitter=(e,r)=>{let A=r.constructor.prototype;for(let n of Object.keys(A)){let o=A[n];"function"==typeof o?t.define(e,n,o.bind(r)):t.define(e,n,o)}},t.onExit=e=>{const t=(e,t)=>{o||(o=!0,i.forEach(e=>e()),!0===e&&process.exit(128+t))};0===i.length&&(process.once("SIGTERM",t.bind(null,!0,15)),process.once("SIGINT",t.bind(null,!0,2)),process.once("exit",t)),i.push(e)},t.define=(e,t,r)=>{Reflect.defineProperty(e,t,{value:r})},t.defineExport=(e,t,r)=>{let A;Reflect.defineProperty(e,t,{enumerable:!0,configurable:!0,set(e){A=e},get:()=>A?A():r()})}},19347:(e,t,r)=>{"use strict";const A=r(80598),n=r(58182),o=r(67652),i=r(81340),s=r(43754),a=r(16777);async function c(e,t){l(e);const r=g(e,n.default,t),A=await Promise.all(r);return a.array.flatten(A)}function g(e,t,r){const n=[].concat(e),o=new s.default(r),i=A.generate(n,o),a=new t(o);return i.map(a.read,a)}function l(e){if(![].concat(e).every(e=>a.string.isString(e)&&!a.string.isEmpty(e)))throw new TypeError("Patterns must be a string (non empty) or an array of strings")}!function(e){e.sync=function(e,t){l(e);const r=g(e,i.default,t);return a.array.flatten(r)},e.stream=function(e,t){l(e);const r=g(e,o.default,t);return a.stream.merge(r)},e.generateTasks=function(e,t){l(e);const r=[].concat(e),n=new s.default(t);return A.generate(r,n)},e.isDynamicPattern=function(e,t){l(e);const r=new s.default(t);return a.pattern.isDynamicPattern(e,r)},e.escapePath=function(e){return l(e),a.path.escape(e)}}(c||(c={})),e.exports=c},80598:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(16777);function n(e,t,r){const A=s(e);if("."in A){return[c(".",e,t,r)]}return a(A,t,r)}function o(e){return A.pattern.getPositivePatterns(e)}function i(e,t){return A.pattern.getNegativePatterns(e).concat(t).map(A.pattern.convertToPositivePattern)}function s(e){return e.reduce((e,t)=>{const r=A.pattern.getBaseDirectory(t);return r in e?e[r].push(t):e[r]=[t],e},{})}function a(e,t,r){return Object.keys(e).map(A=>c(A,e[A],t,r))}function c(e,t,r,n){return{dynamic:n,positive:t,negative:r,base:e,patterns:[].concat(t,r.map(A.pattern.convertToNegativePattern))}}t.generate=function(e,t){const r=o(e),s=i(e,t.ignore),a=r.filter(e=>A.pattern.isStaticPattern(e,t)),c=r.filter(e=>A.pattern.isDynamicPattern(e,t)),g=n(a,s,!1),l=n(c,s,!0);return g.concat(l)},t.convertPatternsToTasks=n,t.getPositivePatterns=o,t.getNegativePatternsAsPositive=i,t.groupPatternsByBaseDirectory=s,t.convertPatternGroupsToTasks=a,t.convertPatternGroupToTask=c},58182:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(82774),n=r(40545);class o extends n.default{constructor(){super(...arguments),this._reader=new A.default(this._settings)}read(e){const t=this._getRootDirectory(e),r=this._getReaderOptions(e),A=[];return new Promise((n,o)=>{const i=this.api(t,e,r);i.once("error",o),i.on("data",e=>A.push(r.transform(e))),i.once("end",()=>n(A))})}api(e,t,r){return t.dynamic?this._reader.dynamic(e,r):this._reader.static(t.patterns,r)}}t.default=o},65989:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(16777),n=r(42585);t.default=class{constructor(e,t){this._settings=e,this._micromatchOptions=t}getFilter(e,t,r){const A=this._getMatcher(t),n=this._getNegativePatternsRe(r);return t=>this._filter(e,t,A,n)}_getMatcher(e){return new n.default(e,this._settings,this._micromatchOptions)}_getNegativePatternsRe(e){const t=e.filter(A.pattern.isAffectDepthOfReadingPattern);return A.pattern.convertPatternsToRe(t,this._micromatchOptions)}_filter(e,t,r,n){const o=this._getEntryLevel(e,t.path);if(this._isSkippedByDeep(o))return!1;if(this._isSkippedSymbolicLink(t))return!1;const i=A.path.removeLeadingDotSegment(t.path);return!this._isSkippedByPositivePatterns(i,r)&&this._isSkippedByNegativePatterns(i,n)}_isSkippedByDeep(e){return e>=this._settings.deep}_isSkippedSymbolicLink(e){return!this._settings.followSymbolicLinks&&e.dirent.isSymbolicLink()}_getEntryLevel(e,t){const r=e.split("/").length;return t.split("/").length-(""===e?0:r)}_isSkippedByPositivePatterns(e,t){return!this._settings.baseNameMatch&&!t.match(e)}_isSkippedByNegativePatterns(e,t){return!A.pattern.matchAny(e,t)}}},37338:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(16777);t.default=class{constructor(e,t){this._settings=e,this._micromatchOptions=t,this.index=new Map}getFilter(e,t){const r=A.pattern.convertPatternsToRe(e,this._micromatchOptions),n=A.pattern.convertPatternsToRe(t,this._micromatchOptions);return e=>this._filter(e,r,n)}_filter(e,t,r){if(this._settings.unique){if(this._isDuplicateEntry(e))return!1;this._createIndexRecord(e)}if(this._onlyFileFilter(e)||this._onlyDirectoryFilter(e))return!1;if(this._isSkippedByAbsoluteNegativePatterns(e,r))return!1;const A=this._settings.baseNameMatch?e.name:e.path;return this._isMatchToPatterns(A,t)&&!this._isMatchToPatterns(e.path,r)}_isDuplicateEntry(e){return this.index.has(e.path)}_createIndexRecord(e){this.index.set(e.path,void 0)}_onlyFileFilter(e){return this._settings.onlyFiles&&!e.dirent.isFile()}_onlyDirectoryFilter(e){return this._settings.onlyDirectories&&!e.dirent.isDirectory()}_isSkippedByAbsoluteNegativePatterns(e,t){if(!this._settings.absolute)return!1;const r=A.path.makeAbsolute(this._settings.cwd,e.path);return this._isMatchToPatterns(r,t)}_isMatchToPatterns(e,t){const r=A.path.removeLeadingDotSegment(e);return A.pattern.matchAny(r,t)}}},54345:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(16777);t.default=class{constructor(e){this._settings=e}getFilter(){return e=>this._isNonFatalError(e)}_isNonFatalError(e){return A.errno.isEnoentCodeError(e)||this._settings.suppressErrors}}},34789:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(16777);t.default=class{constructor(e,t,r){this._patterns=e,this._settings=t,this._micromatchOptions=r,this._storage=[],this._fillStorage()}_fillStorage(){const e=A.pattern.expandPatternsWithBraceExpansion(this._patterns);for(const t of e){const e=this._getPatternSegments(t),r=this._splitSegmentsIntoSections(e);this._storage.push({complete:r.length<=1,pattern:t,segments:e,sections:r})}}_getPatternSegments(e){return A.pattern.getPatternParts(e,this._micromatchOptions).map(e=>A.pattern.isDynamicPattern(e,this._settings)?{dynamic:!0,pattern:e,patternRe:A.pattern.makeRe(e,this._micromatchOptions)}:{dynamic:!1,pattern:e})}_splitSegmentsIntoSections(e){return A.array.splitWhen(e,e=>e.dynamic&&A.pattern.hasGlobStar(e.pattern))}}},42585:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(34789);class n extends A.default{match(e){const t=e.split("/"),r=t.length,A=this._storage.filter(e=>!e.complete||e.segments.length>r);for(const e of A){const A=e.sections[0];if(!e.complete&&r>A.length)return!0;if(t.every((t,r)=>{const A=e.segments[r];return!(!A.dynamic||!A.patternRe.test(t))||!A.dynamic&&A.pattern===t}))return!0}return!1}}t.default=n},40545:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(85622),n=r(65989),o=r(37338),i=r(54345),s=r(77541);t.default=class{constructor(e){this._settings=e,this.errorFilter=new i.default(this._settings),this.entryFilter=new o.default(this._settings,this._getMicromatchOptions()),this.deepFilter=new n.default(this._settings,this._getMicromatchOptions()),this.entryTransformer=new s.default(this._settings)}_getRootDirectory(e){return A.resolve(this._settings.cwd,e.base)}_getReaderOptions(e){const t="."===e.base?"":e.base;return{basePath:t,pathSegmentSeparator:"/",concurrency:this._settings.concurrency,deepFilter:this.deepFilter.getFilter(t,e.positive,e.negative),entryFilter:this.entryFilter.getFilter(e.positive,e.negative),errorFilter:this.errorFilter.getFilter(),followSymbolicLinks:this._settings.followSymbolicLinks,fs:this._settings.fs,stats:this._settings.stats,throwErrorOnBrokenSymbolicLink:this._settings.throwErrorOnBrokenSymbolicLink,transform:this.entryTransformer.getTransformer()}}_getMicromatchOptions(){return{dot:this._settings.dot,matchBase:this._settings.baseNameMatch,nobrace:!this._settings.braceExpansion,nocase:!this._settings.caseSensitiveMatch,noext:!this._settings.extglob,noglobstar:!this._settings.globstar,posix:!0,strictSlashes:!1}}}},67652:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(92413),n=r(82774),o=r(40545);class i extends o.default{constructor(){super(...arguments),this._reader=new n.default(this._settings)}read(e){const t=this._getRootDirectory(e),r=this._getReaderOptions(e),n=this.api(t,e,r),o=new A.Readable({objectMode:!0,read:()=>{}});return n.once("error",e=>o.emit("error",e)).on("data",e=>o.emit("data",r.transform(e))).once("end",()=>o.emit("end")),o.once("close",()=>n.destroy()),o}api(e,t,r){return t.dynamic?this._reader.dynamic(e,r):this._reader.static(t.patterns,r)}}t.default=i},81340:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(29543),n=r(40545);class o extends n.default{constructor(){super(...arguments),this._reader=new A.default(this._settings)}read(e){const t=this._getRootDirectory(e),r=this._getReaderOptions(e);return this.api(t,e,r).map(r.transform)}api(e,t,r){return t.dynamic?this._reader.dynamic(e,r):this._reader.static(t.patterns,r)}}t.default=o},77541:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(16777);t.default=class{constructor(e){this._settings=e}getTransformer(){return e=>this._transform(e)}_transform(e){let t=e.path;return this._settings.absolute&&(t=A.path.makeAbsolute(this._settings.cwd,t),t=A.path.unixify(t)),this._settings.markDirectories&&e.dirent.isDirectory()&&(t+="/"),this._settings.objectMode?Object.assign(Object.assign({},e),{path:t}):t}}},99458:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(85622),n=r(53403),o=r(16777);t.default=class{constructor(e){this._settings=e,this._fsStatSettings=new n.Settings({followSymbolicLink:this._settings.followSymbolicLinks,fs:this._settings.fs,throwErrorOnBrokenSymbolicLink:this._settings.followSymbolicLinks})}_getFullEntryPath(e){return A.resolve(this._settings.cwd,e)}_makeEntry(e,t){const r={name:t,path:t,dirent:o.fs.createDirentFromStats(t,e)};return this._settings.stats&&(r.stats=e),r}_isFatalError(e){return!o.errno.isEnoentCodeError(e)&&!this._settings.suppressErrors}}},82774:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(92413),n=r(53403),o=r(72897),i=r(99458);class s extends i.default{constructor(){super(...arguments),this._walkStream=o.walkStream,this._stat=n.stat}dynamic(e,t){return this._walkStream(e,t)}static(e,t){const r=e.map(this._getFullEntryPath,this),n=new A.PassThrough({objectMode:!0});n._write=(A,o,i)=>this._getEntry(r[A],e[A],t).then(e=>{null!==e&&t.entryFilter(e)&&n.push(e),A===r.length-1&&n.end(),i()}).catch(i);for(let e=0;ethis._makeEntry(e,t)).catch(e=>{if(r.errorFilter(e))return null;throw e})}_getStat(e){return new Promise((t,r)=>{this._stat(e,this._fsStatSettings,(e,A)=>null===e?t(A):r(e))})}}t.default=s},29543:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(53403),n=r(72897),o=r(99458);class i extends o.default{constructor(){super(...arguments),this._walkSync=n.walkSync,this._statSync=A.statSync}dynamic(e,t){return this._walkSync(e,t)}static(e,t){const r=[];for(const A of e){const e=this._getFullEntryPath(A),n=this._getEntry(e,A,t);null!==n&&t.entryFilter(n)&&r.push(n)}return r}_getEntry(e,t,r){try{const r=this._getStat(e);return this._makeEntry(r,t)}catch(e){if(r.errorFilter(e))return null;throw e}}_getStat(e){return this._statSync(e,this._fsStatSettings)}}t.default=i},43754:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(35747),n=r(12087).cpus().length;t.DEFAULT_FILE_SYSTEM_ADAPTER={lstat:A.lstat,lstatSync:A.lstatSync,stat:A.stat,statSync:A.statSync,readdir:A.readdir,readdirSync:A.readdirSync};t.default=class{constructor(e={}){this._options=e,this.absolute=this._getValue(this._options.absolute,!1),this.baseNameMatch=this._getValue(this._options.baseNameMatch,!1),this.braceExpansion=this._getValue(this._options.braceExpansion,!0),this.caseSensitiveMatch=this._getValue(this._options.caseSensitiveMatch,!0),this.concurrency=this._getValue(this._options.concurrency,n),this.cwd=this._getValue(this._options.cwd,process.cwd()),this.deep=this._getValue(this._options.deep,1/0),this.dot=this._getValue(this._options.dot,!1),this.extglob=this._getValue(this._options.extglob,!0),this.followSymbolicLinks=this._getValue(this._options.followSymbolicLinks,!0),this.fs=this._getFileSystemMethods(this._options.fs),this.globstar=this._getValue(this._options.globstar,!0),this.ignore=this._getValue(this._options.ignore,[]),this.markDirectories=this._getValue(this._options.markDirectories,!1),this.objectMode=this._getValue(this._options.objectMode,!1),this.onlyDirectories=this._getValue(this._options.onlyDirectories,!1),this.onlyFiles=this._getValue(this._options.onlyFiles,!0),this.stats=this._getValue(this._options.stats,!1),this.suppressErrors=this._getValue(this._options.suppressErrors,!1),this.throwErrorOnBrokenSymbolicLink=this._getValue(this._options.throwErrorOnBrokenSymbolicLink,!1),this.unique=this._getValue(this._options.unique,!0),this.onlyDirectories&&(this.onlyFiles=!1),this.stats&&(this.objectMode=!0)}_getValue(e,t){return void 0===e?t:e}_getFileSystemMethods(e={}){return Object.assign(Object.assign({},t.DEFAULT_FILE_SYSTEM_ADAPTER),e)}}},60919:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.flatten=function(e){return e.reduce((e,t)=>[].concat(e,t),[])},t.splitWhen=function(e,t){const r=[[]];let A=0;for(const n of e)t(n)?(A++,r[A]=[]):r[A].push(n);return r}},35525:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.isEnoentCodeError=function(e){return"ENOENT"===e.code}},62524:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});class r{constructor(e,t){this.name=e,this.isBlockDevice=t.isBlockDevice.bind(t),this.isCharacterDevice=t.isCharacterDevice.bind(t),this.isDirectory=t.isDirectory.bind(t),this.isFIFO=t.isFIFO.bind(t),this.isFile=t.isFile.bind(t),this.isSocket=t.isSocket.bind(t),this.isSymbolicLink=t.isSymbolicLink.bind(t)}}t.createDirentFromStats=function(e,t){return new r(e,t)}},16777:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(60919);t.array=A;const n=r(35525);t.errno=n;const o=r(62524);t.fs=o;const i=r(71462);t.path=i;const s=r(14659);t.pattern=s;const a=r(2042);t.stream=a;const c=r(10217);t.string=c},71462:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(85622),n=/(\\?)([()*?[\]{|}]|^!|[!+@](?=\())/g;t.unixify=function(e){return e.replace(/\\/g,"/")},t.makeAbsolute=function(e,t){return A.resolve(e,t)},t.escape=function(e){return e.replace(n,"\\$2")},t.removeLeadingDotSegment=function(e){if("."===e.charAt(0)){const t=e.charAt(1);if("/"===t||"\\"===t)return e.slice(2)}return e}},14659:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(85622),n=r(97098),o=r(2401),i=r(54722),s=/[*?]|^!/,a=/\[.*]/,c=/(?:^|[^!*+?@])\(.*\|.*\)/,g=/[!*+?@]\(.*\)/,l=/{.*(?:,|\.\.).*}/;function u(e,t={}){return!h(e,t)}function h(e,t={}){return!(!1!==t.caseSensitiveMatch&&!e.includes("\\"))||(!!(s.test(e)||a.test(e)||c.test(e))||(!(!1===t.extglob||!g.test(e))||!(!1===t.braceExpansion||!l.test(e))))}function p(e){return e.startsWith("!")&&"("!==e[1]}function d(e){return!p(e)}function C(e){return e.endsWith("/**")}function f(e){return o.braces(e,{expand:!0,nodupes:!0})}function I(e,t){return o.makeRe(e,t)}t.isStaticPattern=u,t.isDynamicPattern=h,t.convertToPositivePattern=function(e){return p(e)?e.slice(1):e},t.convertToNegativePattern=function(e){return"!"+e},t.isNegativePattern=p,t.isPositivePattern=d,t.getNegativePatterns=function(e){return e.filter(p)},t.getPositivePatterns=function(e){return e.filter(d)},t.getBaseDirectory=function(e){return n(e,{flipBackslashes:!1})},t.hasGlobStar=function(e){return e.includes("**")},t.endsWithSlashGlobStar=C,t.isAffectDepthOfReadingPattern=function(e){const t=A.basename(e);return C(e)||u(t)},t.expandPatternsWithBraceExpansion=function(e){return e.reduce((e,t)=>e.concat(f(t)),[])},t.expandBraceExpansion=f,t.getPatternParts=function(e,t){const r=i.scan(e,Object.assign(Object.assign({},t),{parts:!0}));return 0===r.parts.length?[e]:r.parts},t.makeRe=I,t.convertPatternsToRe=function(e,t){return e.map(e=>I(e,t))},t.matchAny=function(e,t){return t.some(t=>t.test(e))}},2042:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(55598);function n(e){e.forEach(e=>e.emit("close"))}t.merge=function(e){const t=A(e);return e.forEach(e=>{e.once("error",e=>t.emit("error",e))}),t.once("close",()=>n(e)),t.once("end",()=>n(e)),t}},10217:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.isString=function(e){return"string"==typeof e},t.isEmpty=function(e){return""===e}},98360:(e,t,r)=>{"use strict";var A=r(2383);function n(){}function o(){this.value=null,this.callback=n,this.next=null,this.release=n,this.context=null;var e=this;this.worked=function(t,r){var A=e.callback;e.value=null,e.callback=n,A.call(e.context,t,r),e.release(e)}}e.exports=function(e,t,r){"function"==typeof e&&(r=t,t=e,e=null);var i=A(o),s=null,a=null,c=0,g={push:function(r,A){var o=i.get();o.context=e,o.release=l,o.value=r,o.callback=A||n,c===g.concurrency||g.paused?a?(a.next=o,a=o):(s=o,a=o,g.saturated()):(c++,t.call(e,o.value,o.worked))},drain:n,saturated:n,pause:function(){g.paused=!0},paused:!1,concurrency:r,running:function(){return c},resume:function(){if(!g.paused)return;g.paused=!1;for(var e=0;e{"use strict";class A{constructor(e,t,r){this.__specs=e||{},Object.keys(this.__specs).forEach(e=>{if("string"==typeof this.__specs[e]){const t=this.__specs[e],r=this.__specs[t];if(!r)throw new Error(`Alias refers to invalid key: ${t} -> ${e}`);{const A=r.aliases||[];A.push(e,t),r.aliases=[...new Set(A)],this.__specs[e]=r}}}),this.__opts=t||{},this.__providers=s(r.filter(e=>null!=e&&"object"==typeof e)),this.__isFiggyPudding=!0}get(e){return n(this,e,!0)}get[Symbol.toStringTag](){return"FiggyPudding"}forEach(e,t=this){for(let[r,A]of this.entries())e.call(t,A,r,this)}toJSON(){const e={};return this.forEach((t,r)=>{e[r]=t}),e}*entries(e){for(let e of Object.keys(this.__specs))yield[e,this.get(e)];const t=e||this.__opts.other;if(t){const e=new Set;for(let r of this.__providers){const A=r.entries?r.entries(t):a(r);for(let[r,n]of A)t(r)&&!e.has(r)&&(e.add(r),yield[r,n])}}}*[Symbol.iterator](){for(let[e,t]of this.entries())yield[e,t]}*keys(){for(let[e]of this.entries())yield e}*values(){for(let[,e]of this.entries())yield e}concat(...e){return new Proxy(new A(this.__specs,this.__opts,s(this.__providers).concat(e)),i)}}try{const e=r(31669);A.prototype[e.inspect.custom]=function(t,r){return this[Symbol.toStringTag]+" "+e.inspect(this.toJSON(),r)}}catch(e){}function n(e,t,r){let A=e.__specs[t];if(!r||A||e.__opts.other&&e.__opts.other(t)){let r;A||(A={});for(let n of e.__providers){if(r=o(t,n),void 0===r&&A.aliases&&A.aliases.length)for(let e of A.aliases)if(e!==t&&(r=o(e,n),void 0!==r))break;if(void 0!==r)break}return void 0===r&&void 0!==A.default?"function"==typeof A.default?A.default(e):A.default:r}!function(e){throw Object.assign(new Error("invalid config key requested: "+e),{code:"EBADKEY"})}(t)}function o(e,t){let r;return r=t.__isFiggyPudding?n(t,e,!1):"function"==typeof t.get?t.get(e):t[e],r}const i={has:(e,t)=>t in e.__specs&&void 0!==n(e,t,!1),ownKeys:e=>Object.keys(e.__specs),get:(e,t)=>"symbol"==typeof t||"__"===t.slice(0,2)||t in A.prototype?e[t]:e.get(t),set(e,t,r){if("symbol"==typeof t||"__"===t.slice(0,2))return e[t]=r,!0;throw new Error("figgyPudding options cannot be modified. Use .concat() instead.")},deleteProperty(){throw new Error("figgyPudding options cannot be deleted. Use .concat() and shadow them instead.")}};function s(e){const t=[];return e.forEach(e=>t.unshift(e)),t}function a(e){return Object.keys(e).map(t=>[t,e[t]])}e.exports=function(e,t){return function(...r){return new Proxy(new A(e,t,r),i)}}},52169:(e,t,r)=>{"use strict"; +/*! + * fill-range + * + * Copyright (c) 2014-present, Jon Schlinkert. + * Licensed under the MIT License. + */const A=r(31669),n=r(84615),o=e=>null!==e&&"object"==typeof e&&!Array.isArray(e),i=e=>"number"==typeof e||"string"==typeof e&&""!==e,s=e=>Number.isInteger(+e),a=e=>{let t=""+e,r=-1;if("-"===t[0]&&(t=t.slice(1)),"0"===t)return!1;for(;"0"===t[++r];);return r>0},c=(e,t,r)=>{if(t>0){let r="-"===e[0]?"-":"";r&&(e=e.slice(1)),e=r+e.padStart(r?t-1:t,"0")}return!1===r?String(e):e},g=(e,t)=>{let r="-"===e[0]?"-":"";for(r&&(e=e.slice(1),t--);e.length{if(r)return n(e,t,{wrap:!1,...A});let o=String.fromCharCode(e);return e===t?o:`[${o}-${String.fromCharCode(t)}]`},u=(e,t,r)=>{if(Array.isArray(e)){let t=!0===r.wrap,A=r.capture?"":"?:";return t?`(${A}${e.join("|")})`:e.join("|")}return n(e,t,r)},h=(...e)=>new RangeError("Invalid range arguments: "+A.inspect(...e)),p=(e,t,r)=>{if(!0===r.strictRanges)throw h([e,t]);return[]},d=(e,t,r=1,A={})=>{let n=Number(e),o=Number(t);if(!Number.isInteger(n)||!Number.isInteger(o)){if(!0===A.strictRanges)throw h([e,t]);return[]}0===n&&(n=0),0===o&&(o=0);let i=n>o,s=String(e),p=String(t),d=String(r);r=Math.max(Math.abs(r),1);let C=a(s)||a(p)||a(d),f=C?Math.max(s.length,p.length,d.length):0,I=!1===C&&!1===((e,t,r)=>"string"==typeof e||"string"==typeof t||!0===r.stringify)(e,t,A),E=A.transform||(e=>t=>!0===e?Number(t):String(t))(I);if(A.toRegex&&1===r)return l(g(e,f),g(t,f),!0,A);let B={negatives:[],positives:[]},y=[],m=0;for(;i?n>=o:n<=o;)!0===A.toRegex&&r>1?B[(w=n)<0?"negatives":"positives"].push(Math.abs(w)):y.push(c(E(n,m),f,I)),n=i?n-r:n+r,m++;var w;return!0===A.toRegex?r>1?((e,t)=>{e.negatives.sort((e,t)=>et?1:0),e.positives.sort((e,t)=>et?1:0);let r,A=t.capture?"":"?:",n="",o="";return e.positives.length&&(n=e.positives.join("|")),e.negatives.length&&(o=`-(${A}${e.negatives.join("|")})`),r=n&&o?`${n}|${o}`:n||o,t.wrap?`(${A}${r})`:r})(B,A):u(y,null,{wrap:!1,...A}):y},C=(e,t,r,A={})=>{if(null==t&&i(e))return[e];if(!i(e)||!i(t))return p(e,t,A);if("function"==typeof r)return C(e,t,1,{transform:r});if(o(r))return C(e,t,0,r);let n={...A};return!0===n.capture&&(n.wrap=!0),r=r||n.step||1,s(r)?s(e)&&s(t)?d(e,t,r,n):((e,t,r=1,A={})=>{if(!s(e)&&e.length>1||!s(t)&&t.length>1)return p(e,t,A);let n=A.transform||(e=>String.fromCharCode(e)),o=(""+e).charCodeAt(0),i=(""+t).charCodeAt(0),a=o>i,c=Math.min(o,i),g=Math.max(o,i);if(A.toRegex&&1===r)return l(c,g,!1,A);let h=[],d=0;for(;a?o>=i:o<=i;)h.push(n(o,d)),o=a?o-r:o+r,d++;return!0===A.toRegex?u(h,null,{wrap:!1,options:A}):h})(e,t,Math.max(Math.abs(r),1),n):null==r||o(r)?C(e,t,1,r):((e,t)=>{if(!0===t.strictRanges)throw new TypeError(`Expected step "${e}" to be a number`);return[]})(r,n)};e.exports=C},50683:e=>{e.exports=function(e){return[...e].reduce((e,[t,r])=>(e[t]=r,e),{})}},13302:(e,t,r)=>{e.exports=r(35747).constants||r(27619)},72137:(e,t,r)=>{"use strict";const{PassThrough:A}=r(92413);e.exports=e=>{e={...e};const{array:t}=e;let{encoding:r}=e;const n="buffer"===r;let o=!1;t?o=!(r||n):r=r||"utf8",n&&(r=null);const i=new A({objectMode:o});r&&i.setEncoding(r);let s=0;const a=[];return i.on("data",e=>{a.push(e),o?s=a.length:s+=e.length}),i.getBufferedValue=()=>t?a:n?Buffer.concat(a,s):a.join(""),i.getBufferedLength=()=>s,i}},58764:(e,t,r)=>{"use strict";const A=r(50372),n=r(72137);class o extends Error{constructor(){super("maxBuffer exceeded"),this.name="MaxBufferError"}}async function i(e,t){if(!e)return Promise.reject(new Error("Expected a stream"));t={maxBuffer:1/0,...t};const{maxBuffer:r}=t;let i;return await new Promise((s,a)=>{const c=e=>{e&&(e.bufferedData=i.getBufferedValue()),a(e)};i=A(e,n(t),e=>{e?c(e):s()}),i.on("data",()=>{i.getBufferedLength()>r&&c(new o)})}),i.getBufferedValue()}e.exports=i,e.exports.default=i,e.exports.buffer=(e,t)=>i(e,{...t,encoding:"buffer"}),e.exports.array=(e,t)=>i(e,{...t,array:!0}),e.exports.MaxBufferError=o},97098:(e,t,r)=>{"use strict";var A=r(18193),n=r(85622).posix.dirname,o="win32"===r(12087).platform(),i=/\\/g,s=/[\{\[].*[\/]*.*[\}\]]$/,a=/(^|[^\\])([\{\[]|\([^\)]+$)/,c=/\\([\*\?\|\[\]\(\)\{\}])/g;e.exports=function(e,t){Object.assign({flipBackslashes:!0},t).flipBackslashes&&o&&e.indexOf("/")<0&&(e=e.replace(i,"/")),s.test(e)&&(e+="/"),e+="a";do{e=n(e)}while(A(e)||a.test(e));return e.replace(c,"$1")}},90734:(e,t,r)=>{"use strict";const{promisify:A}=r(31669),n=r(35747),o=r(85622),i=r(19347),s=r(46458),a=r(17234),c=["**/node_modules/**","**/flow-typed/**","**/coverage/**","**/.git"],g=A(n.readFile),l=(e,t)=>{const r=a(o.relative(t.cwd,o.dirname(t.fileName)));return e.split(/\r?\n/).filter(Boolean).filter(e=>!e.startsWith("#")).map((e=>t=>t.startsWith("!")?"!"+o.posix.join(e,t.slice(1)):o.posix.join(e,t))(r))},u=e=>e.reduce((e,t)=>(e.add(l(t.content,{cwd:t.cwd,fileName:t.filePath})),e),s()),h=(e,t)=>r=>e.ignores(a(o.relative(t,((e,t)=>{if(e=a(e),o.isAbsolute(t)){if(t.startsWith(e))return t;throw new Error(`Path ${t} is not in cwd ${e}`)}return o.join(e,t)})(t,r)))),p=({ignore:e=[],cwd:t=a(process.cwd())}={})=>({ignore:e,cwd:t});e.exports=async e=>{e=p(e);const t=await i("**/.gitignore",{ignore:c.concat(e.ignore),cwd:e.cwd}),r=await Promise.all(t.map(t=>(async(e,t)=>{const r=o.join(t,e);return{cwd:t,filePath:r,content:await g(r,"utf8")}})(t,e.cwd))),A=u(r);return h(A,e.cwd)},e.exports.sync=e=>{e=p(e);const t=i.sync("**/.gitignore",{ignore:c.concat(e.ignore),cwd:e.cwd}).map(t=>((e,t)=>{const r=o.join(t,e);return{cwd:t,filePath:r,content:n.readFileSync(r,"utf8")}})(t,e.cwd)),r=u(t);return h(r,e.cwd)}},58592:(e,t,r)=>{"use strict";const A=r(35747),n=r(39920),o=r(55598),i=r(19347),s=r(66241),a=r(90734),{FilterStream:c,UniqueStream:g}=r(66160),l=()=>!1,u=e=>"!"===e[0],h=(e,t)=>{(e=>{if(!e.every(e=>"string"==typeof e))throw new TypeError("Patterns must be a string or an array of strings")})(e=n([].concat(e))),((e={})=>{if(!e.cwd)return;let t;try{t=A.statSync(e.cwd)}catch(e){return}if(!t.isDirectory())throw new Error("The `cwd` option must be a path to a directory")})(t);const r=[];t={ignore:[],expandDirectories:!0,...t};for(const[A,n]of e.entries()){if(u(n))continue;const o=e.slice(A).filter(u).map(e=>e.slice(1)),i={...t,ignore:t.ignore.concat(o)};r.push({pattern:n,options:i})}return r},p=(e,t)=>e.options.expandDirectories?((e,t)=>{let r={};return e.options.cwd&&(r.cwd=e.options.cwd),Array.isArray(e.options.expandDirectories)?r={...r,files:e.options.expandDirectories}:"object"==typeof e.options.expandDirectories&&(r={...r,...e.options.expandDirectories}),t(e.pattern,r)})(e,t):[e.pattern],d=e=>e&&e.gitignore?a.sync({cwd:e.cwd,ignore:e.ignore}):l,C=e=>t=>{const{options:r}=e;return r.ignore&&Array.isArray(r.ignore)&&r.expandDirectories&&(r.ignore=s.sync(r.ignore)),{pattern:t,options:r}};e.exports=async(e,t)=>{const r=h(e,t),[o,c]=await Promise.all([(async()=>t&&t.gitignore?a({cwd:t.cwd,ignore:t.ignore}):l)(),(async()=>{const e=await Promise.all(r.map(async e=>{const t=await p(e,s);return Promise.all(t.map(C(e)))}));return n(...e)})()]),g=await Promise.all(c.map(e=>i(e.pattern,e.options)));return n(...g).filter(e=>{return!o((t=e,t.stats instanceof A.Stats?t.path:t));var t})},e.exports.sync=(e,t)=>{const r=h(e,t).reduce((e,t)=>{const r=p(t,s.sync).map(C(t));return e.concat(r)},[]),A=d(t);return r.reduce((e,t)=>n(e,i.sync(t.pattern,t.options)),[]).filter(e=>!A(e))},e.exports.stream=(e,t)=>{const r=h(e,t).reduce((e,t)=>{const r=p(t,s.sync).map(C(t));return e.concat(r)},[]),A=d(t),n=new c(e=>!A(e)),a=new g;return o(r.map(e=>i.stream(e.pattern,e.options))).pipe(n).pipe(a)},e.exports.generateGlobTasks=h,e.exports.hasMagic=(e,t)=>[].concat(e).some(e=>i.isDynamicPattern(e,t)),e.exports.gitignore=a},66160:(e,t,r)=>{"use strict";const{Transform:A}=r(92413);class n extends A{constructor(){super({objectMode:!0})}}e.exports={FilterStream:class extends n{constructor(e){super(),this._filter=e}_transform(e,t,r){this._filter(e)&&this.push(e),r()}},UniqueStream:class extends n{constructor(){super(),this._pushed=new Set}_transform(e,t,r){this._pushed.has(e)||(this.push(e),this._pushed.add(e)),r()}}}},93576:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(14756);t.default=function(e,...t){const r=(async()=>{if(e instanceof A.RequestError)try{for(const r of t)if(r)for(const t of r)e=await t(e)}catch(t){e=t}throw e})(),n=()=>r;return r.json=n,r.text=n,r.buffer=n,r.on=n,r}},81588:function(e,t,r){"use strict";var A=this&&this.__createBinding||(Object.create?function(e,t,r,A){void 0===A&&(A=r),Object.defineProperty(e,A,{enumerable:!0,get:function(){return t[r]}})}:function(e,t,r,A){void 0===A&&(A=r),e[A]=t[r]}),n=this&&this.__exportStar||function(e,t){for(var r in e)"default"===r||Object.prototype.hasOwnProperty.call(t,r)||A(t,e,r)};Object.defineProperty(t,"__esModule",{value:!0});const o=r(28614),i=r(7966),s=r(59351),a=r(14756),c=r(54718),g=r(9048),l=r(51743),u=r(57854),h=r(38206),p=["request","response","redirect","uploadProgress","downloadProgress"];t.default=function e(t){let r,A;const n=new o.EventEmitter,d=new s((o,s,C)=>{const f=I=>{const E=new g.default(void 0,t);E.retryCount=I,E._noPipe=!0,C(()=>E.destroy()),C.shouldReject=!1,C(()=>s(new a.CancelError(E))),r=E,E.once("response",async t=>{var r;if(t.retryCount=I,t.request.aborted)return;let n;try{n=await u.default(E),t.rawBody=n}catch(e){return}if(E._isAboutToError)return;const i=(null!==(r=t.headers["content-encoding"])&&void 0!==r?r:"").toLowerCase(),s=["gzip","deflate","br"].includes(i),{options:l}=E;if(s&&!l.decompress)t.body=n;else try{t.body=c.default(t,l.responseType,l.parseJson,l.encoding)}catch(e){if(t.body=n.toString(),h.isResponseOk(t))return void E._beforeError(e)}try{for(const[r,A]of l.hooks.afterResponse.entries())t=await A(t,async t=>{const A=g.default.normalizeArguments(void 0,{...t,retry:{calculateDelay:()=>0},throwHttpErrors:!1,resolveBodyOnly:!1},l);A.hooks.afterResponse=A.hooks.afterResponse.slice(0,r);for(const e of A.hooks.beforeRetry)await e(A);const n=e(A);return C(()=>{n.catch(()=>{}),n.cancel()}),n})}catch(e){return void E._beforeError(new a.RequestError(e.message,e,E))}h.isResponseOk(t)?(A=t,o(E.options.resolveBodyOnly?t.body:t)):E._beforeError(new a.HTTPError(t))});const B=e=>{if(d.isCanceled)return;const{options:t}=E;if(e instanceof a.HTTPError&&!t.throwHttpErrors){const{response:t}=e;o(E.options.resolveBodyOnly?t.body:t)}else s(e)};E.once("error",B),E.once("retry",(e,t)=>{var r;i.default.nodeStream(null===(r=t.request)||void 0===r?void 0:r.options.body)?B(t):f(e)}),l.default(E,n,p)};f(0)});d.on=(e,t)=>(n.on(e,t),d);const C=e=>{const t=(async()=>{await d;const{options:t}=A.request;return c.default(A,e,t.parseJson,t.encoding)})();return Object.defineProperties(t,Object.getOwnPropertyDescriptors(d)),t};return d.json=()=>{const{headers:e}=r.options;return r.writableFinished||void 0!==e.accept||(e.accept="application/json"),C("json")},d.buffer=()=>C("buffer"),d.text=()=>C("text"),d},n(r(14756),t)},41514:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(7966);t.default=(e,t)=>{if(A.default.null_(e.encoding))throw new TypeError("To get a Buffer, set `options.responseType` to `buffer` instead");A.assert.any([A.default.string,A.default.undefined],e.encoding),A.assert.any([A.default.boolean,A.default.undefined],e.resolveBodyOnly),A.assert.any([A.default.boolean,A.default.undefined],e.methodRewriting),A.assert.any([A.default.boolean,A.default.undefined],e.isStream),A.assert.any([A.default.string,A.default.undefined],e.responseType),void 0===e.responseType&&(e.responseType="text");const{retry:r}=e;if(e.retry=t?{...t.retry}:{calculateDelay:e=>e.computedValue,limit:0,methods:[],statusCodes:[],errorCodes:[],maxRetryAfter:void 0},A.default.object(r)?(e.retry={...e.retry,...r},e.retry.methods=[...new Set(e.retry.methods.map(e=>e.toUpperCase()))],e.retry.statusCodes=[...new Set(e.retry.statusCodes)],e.retry.errorCodes=[...new Set(e.retry.errorCodes)]):A.default.number(r)&&(e.retry.limit=r),A.default.undefined(e.retry.maxRetryAfter)&&(e.retry.maxRetryAfter=Math.min(...[e.timeout.request,e.timeout.connect].filter(A.default.number))),A.default.object(e.pagination)){t&&(e.pagination={...t.pagination,...e.pagination});const{pagination:r}=e;if(!A.default.function_(r.transform))throw new Error("`options.pagination.transform` must be implemented");if(!A.default.function_(r.shouldContinue))throw new Error("`options.pagination.shouldContinue` must be implemented");if(!A.default.function_(r.filter))throw new TypeError("`options.pagination.filter` must be implemented");if(!A.default.function_(r.paginate))throw new Error("`options.pagination.paginate` must be implemented")}return"json"===e.responseType&&void 0===e.headers.accept&&(e.headers.accept="application/json"),e}},54718:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(14756);t.default=(e,t,r,n)=>{const{rawBody:o}=e;try{if("text"===t)return o.toString(n);if("json"===t)return 0===o.length?"":r(o.toString());if("buffer"===t)return o;throw new A.ParseError({message:`Unknown body type '${t}'`,name:"Error"},e)}catch(t){throw new A.ParseError(t,e)}}},14756:function(e,t,r){"use strict";var A=this&&this.__createBinding||(Object.create?function(e,t,r,A){void 0===A&&(A=r),Object.defineProperty(e,A,{enumerable:!0,get:function(){return t[r]}})}:function(e,t,r,A){void 0===A&&(A=r),e[A]=t[r]}),n=this&&this.__exportStar||function(e,t){for(var r in e)"default"===r||Object.prototype.hasOwnProperty.call(t,r)||A(t,e,r)};Object.defineProperty(t,"__esModule",{value:!0}),t.CancelError=t.ParseError=void 0;const o=r(9048);class i extends o.RequestError{constructor(e,t){const{options:r}=t.request;super(`${e.message} in "${r.url.toString()}"`,e,t.request),this.name="ParseError"}}t.ParseError=i;class s extends o.RequestError{constructor(e){super("Promise was canceled",{},e),this.name="CancelError"}get isCanceled(){return!0}}t.CancelError=s,n(r(9048),t)},53843:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.retryAfterStatusCodes=void 0,t.retryAfterStatusCodes=new Set([413,429,503]);t.default=({attemptCount:e,retryOptions:t,error:r,retryAfter:A})=>{if(e>t.limit)return 0;const n=t.methods.includes(r.options.method),o=t.errorCodes.includes(r.code),i=r.response&&t.statusCodes.includes(r.response.statusCode);if(!n||!o&&!i)return 0;if(r.response){if(A)return void 0===t.maxRetryAfter||A>t.maxRetryAfter?0:A;if(413===r.response.statusCode)return 0}return 2**(e-1)*1e3+100*Math.random()}},9048:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.UnsupportedProtocolError=t.ReadError=t.TimeoutError=t.UploadError=t.CacheError=t.HTTPError=t.MaxRedirectsError=t.RequestError=t.setNonEnumerableProperties=t.knownHookEvents=t.withoutBody=t.kIsNormalizedAlready=void 0;const A=r(31669),n=r(92413),o=r(35747),i=r(78835),s=r(98605),a=r(98605),c=r(57211),g=r(98298),l=r(30093),u=r(11200),h=r(93868),p=r(92353),d=r(55737),C=r(7966),f=r(78586),I=r(2920),E=r(51743),B=r(44947),y=r(50116),m=r(82524),w=r(85551),Q=r(57854),D=r(11338),b=r(38206),v=r(54595),S=r(41514),k=r(53843),N=new l.default,F=Symbol("request"),K=Symbol("response"),M=Symbol("responseSize"),R=Symbol("downloadedSize"),x=Symbol("bodySize"),L=Symbol("uploadedSize"),P=Symbol("serverResponsesPiped"),O=Symbol("unproxyEvents"),U=Symbol("isFromCache"),T=Symbol("cancelTimeouts"),j=Symbol("startedReading"),Y=Symbol("stopReading"),G=Symbol("triggerRead"),H=Symbol("body"),J=Symbol("jobs"),q=Symbol("originalResponse"),z=Symbol("retryTimeout");t.kIsNormalizedAlready=Symbol("isNormalizedAlready");const W=C.default.string(process.versions.brotli);t.withoutBody=new Set(["GET","HEAD"]),t.knownHookEvents=["init","beforeRequest","beforeRedirect","beforeError","beforeRetry","afterResponse"];const V=new w.default,X=new Set([300,301,302,303,304,307,308]),_=["context","body","json","form"];t.setNonEnumerableProperties=(e,t)=>{const r={};for(const t of e)if(t)for(const e of _)e in t&&(r[e]={writable:!0,configurable:!0,enumerable:!1,value:t[e]});Object.defineProperties(t,r)};class Z extends Error{constructor(e,t,r){var A;if(super(e),Error.captureStackTrace(this,this.constructor),this.name="RequestError",this.code=t.code,r instanceof se?(Object.defineProperty(this,"request",{enumerable:!1,value:r}),Object.defineProperty(this,"response",{enumerable:!1,value:r[K]}),Object.defineProperty(this,"options",{enumerable:!1,value:r.options})):Object.defineProperty(this,"options",{enumerable:!1,value:r}),this.timings=null===(A=this.request)||void 0===A?void 0:A.timings,!C.default.undefined(t.stack)){const e=this.stack.indexOf(this.message)+this.message.length,r=this.stack.slice(e).split("\n").reverse(),A=t.stack.slice(t.stack.indexOf(t.message)+t.message.length).split("\n").reverse();for(;0!==A.length&&A[0]===r[0];)r.shift();this.stack=`${this.stack.slice(0,e)}${r.reverse().join("\n")}${A.reverse().join("\n")}`}}}t.RequestError=Z;class $ extends Z{constructor(e){super(`Redirected ${e.options.maxRedirects} times. Aborting.`,{},e),this.name="MaxRedirectsError"}}t.MaxRedirectsError=$;class ee extends Z{constructor(e){super(`Response code ${e.statusCode} (${e.statusMessage})`,{},e.request),this.name="HTTPError"}}t.HTTPError=ee;class te extends Z{constructor(e,t){super(e.message,e,t),this.name="CacheError"}}t.CacheError=te;class re extends Z{constructor(e,t){super(e.message,e,t),this.name="UploadError"}}t.UploadError=re;class Ae extends Z{constructor(e,t,r){super(e.message,e,r),this.name="TimeoutError",this.event=e.event,this.timings=t}}t.TimeoutError=Ae;class ne extends Z{constructor(e,t){super(e.message,e,t),this.name="ReadError"}}t.ReadError=ne;class oe extends Z{constructor(e){super(`Unsupported protocol "${e.url.protocol}"`,{},e),this.name="UnsupportedProtocolError"}}t.UnsupportedProtocolError=oe;const ie=["socket","connect","continue","information","upgrade","timeout"];class se extends n.Duplex{constructor(e,r={},A){super({autoDestroy:!1,highWaterMark:0}),this[R]=0,this[L]=0,this.requestInitialized=!1,this[P]=new Set,this.redirects=[],this[Y]=!1,this[G]=!1,this[J]=[],this.retryCount=0,this._progressCallbacks=[];const n=()=>this._unlockWrite(),i=()=>this._lockWrite();this.on("pipe",e=>{e.prependListener("data",n),e.on("data",i),e.prependListener("end",n),e.on("end",i)}),this.on("unpipe",e=>{e.off("data",n),e.off("data",i),e.off("end",n),e.off("end",i)}),this.on("pipe",e=>{e instanceof a.IncomingMessage&&(this.options.headers={...e.headers,...this.options.headers})});const{json:s,body:c,form:g}=r;if((s||c||g)&&this._lockWrite(),t.kIsNormalizedAlready in r)this.options=r;else try{this.options=this.constructor.normalizeArguments(e,r,A)}catch(e){return C.default.nodeStream(r.body)&&r.body.destroy(),void this.destroy(e)}(async()=>{var e;try{this.options.body instanceof o.ReadStream&&await(async e=>new Promise((t,r)=>{const A=e=>{r(e)};e.pending||t(),e.once("error",A),e.once("ready",()=>{e.off("error",A),t()})}))(this.options.body);const{url:t}=this.options;if(!t)throw new TypeError("Missing `url` property");if(this.requestUrl=t.toString(),decodeURI(this.requestUrl),await this._finalizeBody(),await this._makeRequest(),this.destroyed)return void(null===(e=this[F])||void 0===e||e.destroy());for(const e of this[J])e();this[J].length=0,this.requestInitialized=!0}catch(e){if(e instanceof Z)return void this._beforeError(e);this.destroyed||this.destroy(e)}})()}static normalizeArguments(e,r,n){var o,s,a,c,g;const l=r;if(C.default.object(e)&&!C.default.urlInstance(e))r={...n,...e,...r};else{if(e&&r&&void 0!==r.url)throw new TypeError("The `url` option is mutually exclusive with the `input` argument");r={...n,...r},void 0!==e&&(r.url=e),C.default.urlInstance(r.url)&&(r.url=new i.URL(r.url.toString()))}if(!1===r.cache&&(r.cache=void 0),!1===r.dnsCache&&(r.dnsCache=void 0),C.assert.any([C.default.string,C.default.undefined],r.method),C.assert.any([C.default.object,C.default.undefined],r.headers),C.assert.any([C.default.string,C.default.urlInstance,C.default.undefined],r.prefixUrl),C.assert.any([C.default.object,C.default.undefined],r.cookieJar),C.assert.any([C.default.object,C.default.string,C.default.undefined],r.searchParams),C.assert.any([C.default.object,C.default.string,C.default.undefined],r.cache),C.assert.any([C.default.object,C.default.number,C.default.undefined],r.timeout),C.assert.any([C.default.object,C.default.undefined],r.context),C.assert.any([C.default.object,C.default.undefined],r.hooks),C.assert.any([C.default.boolean,C.default.undefined],r.decompress),C.assert.any([C.default.boolean,C.default.undefined],r.ignoreInvalidCookies),C.assert.any([C.default.boolean,C.default.undefined],r.followRedirect),C.assert.any([C.default.number,C.default.undefined],r.maxRedirects),C.assert.any([C.default.boolean,C.default.undefined],r.throwHttpErrors),C.assert.any([C.default.boolean,C.default.undefined],r.http2),C.assert.any([C.default.boolean,C.default.undefined],r.allowGetBody),C.assert.any([C.default.string,C.default.undefined],r.localAddress),C.assert.any([D.isDnsLookupIpVersion,C.default.undefined],r.dnsLookupIpVersion),C.assert.any([C.default.object,C.default.undefined],r.https),C.assert.any([C.default.boolean,C.default.undefined],r.rejectUnauthorized),r.https&&(C.assert.any([C.default.boolean,C.default.undefined],r.https.rejectUnauthorized),C.assert.any([C.default.function_,C.default.undefined],r.https.checkServerIdentity),C.assert.any([C.default.string,C.default.object,C.default.array,C.default.undefined],r.https.certificateAuthority),C.assert.any([C.default.string,C.default.object,C.default.array,C.default.undefined],r.https.key),C.assert.any([C.default.string,C.default.object,C.default.array,C.default.undefined],r.https.certificate),C.assert.any([C.default.string,C.default.undefined],r.https.passphrase),C.assert.any([C.default.string,C.default.buffer,C.default.array,C.default.undefined],r.https.pfx)),C.assert.any([C.default.object,C.default.undefined],r.cacheOptions),C.default.string(r.method)?r.method=r.method.toUpperCase():r.method="GET",r.headers===(null==n?void 0:n.headers)?r.headers={...r.headers}:r.headers=d({...null==n?void 0:n.headers,...r.headers}),"slashes"in r)throw new TypeError("The legacy `url.Url` has been deprecated. Use `URL` instead.");if("auth"in r)throw new TypeError("Parameter `auth` is deprecated. Use `username` / `password` instead.");if("searchParams"in r&&r.searchParams&&r.searchParams!==(null==n?void 0:n.searchParams)){let e;if(C.default.string(r.searchParams)||r.searchParams instanceof i.URLSearchParams)e=new i.URLSearchParams(r.searchParams);else{!function(e){for(const t in e){const r=e[t];if(!(C.default.string(r)||C.default.number(r)||C.default.boolean(r)||C.default.null_(r)||C.default.undefined(r)))throw new TypeError(`The \`searchParams\` value '${String(r)}' must be a string, number, boolean or null`)}}(r.searchParams),e=new i.URLSearchParams;for(const t in r.searchParams){const A=r.searchParams[t];null===A?e.append(t,""):void 0!==A&&e.append(t,A)}}null===(o=null==n?void 0:n.searchParams)||void 0===o||o.forEach((t,r)=>{e.has(r)||e.append(r,t)}),r.searchParams=e}if(r.username=null!==(s=r.username)&&void 0!==s?s:"",r.password=null!==(a=r.password)&&void 0!==a?a:"",C.default.undefined(r.prefixUrl)?r.prefixUrl=null!==(c=null==n?void 0:n.prefixUrl)&&void 0!==c?c:"":(r.prefixUrl=r.prefixUrl.toString(),""===r.prefixUrl||r.prefixUrl.endsWith("/")||(r.prefixUrl+="/")),C.default.string(r.url)){if(r.url.startsWith("/"))throw new Error("`input` must not start with a slash when using `prefixUrl`");r.url=m.default(r.prefixUrl+r.url,r)}else(C.default.undefined(r.url)&&""!==r.prefixUrl||r.protocol)&&(r.url=m.default(r.prefixUrl,r));if(r.url){"port"in r&&delete r.port;let{prefixUrl:e}=r;Object.defineProperty(r,"prefixUrl",{set:t=>{const A=r.url;if(!A.href.startsWith(t))throw new Error(`Cannot change \`prefixUrl\` from ${e} to ${t}: ${A.href}`);r.url=new i.URL(t+A.href.slice(e.length)),e=t},get:()=>e});let{protocol:t}=r.url;if("unix:"===t&&(t="http:",r.url=new i.URL(`http://unix${r.url.pathname}${r.url.search}`)),r.searchParams&&(r.url.search=r.searchParams.toString()),"http:"!==t&&"https:"!==t)throw new oe(r);""===r.username?r.username=r.url.username:r.url.username=r.username,""===r.password?r.password=r.url.password:r.url.password=r.password}const{cookieJar:h}=r;if(h){let{setCookie:e,getCookieString:t}=h;C.assert.function_(e),C.assert.function_(t),4===e.length&&0===t.length&&(e=A.promisify(e.bind(r.cookieJar)),t=A.promisify(t.bind(r.cookieJar)),r.cookieJar={setCookie:e,getCookieString:t})}const{cache:p}=r;if(p&&(V.has(p)||V.set(p,new u((e,t)=>{const r=e[F](e,t);return C.default.promise(r)&&(r.once=(e,t)=>{if("error"===e)r.catch(t);else{if("abort"!==e)throw new Error("Unknown HTTP2 promise event: "+e);(async()=>{try{(await r).once("abort",t)}catch(e){}})()}return r}),r},p))),r.cacheOptions={...r.cacheOptions},!0===r.dnsCache)r.dnsCache=N;else if(!C.default.undefined(r.dnsCache)&&!r.dnsCache.lookup)throw new TypeError("Parameter `dnsCache` must be a CacheableLookup instance or a boolean, got "+C.default(r.dnsCache));C.default.number(r.timeout)?r.timeout={request:r.timeout}:n&&r.timeout!==n.timeout?r.timeout={...n.timeout,...r.timeout}:r.timeout={...r.timeout},r.context||(r.context={});const f=r.hooks===(null==n?void 0:n.hooks);r.hooks={...r.hooks};for(const e of t.knownHookEvents)if(e in r.hooks){if(!C.default.array(r.hooks[e]))throw new TypeError(`Parameter \`${e}\` must be an Array, got ${C.default(r.hooks[e])}`);r.hooks[e]=[...r.hooks[e]]}else r.hooks[e]=[];if(n&&!f)for(const e of t.knownHookEvents){0!==n.hooks[e].length&&(r.hooks[e]=[...n.hooks[e],...r.hooks[e]])}if("family"in r&&v.default('"options.family" was never documented, please use "options.dnsLookupIpVersion"'),(null==n?void 0:n.https)&&(r.https={...n.https,...r.https}),"rejectUnauthorized"in r&&v.default('"options.rejectUnauthorized" is now deprecated, please use "options.https.rejectUnauthorized"'),"checkServerIdentity"in r&&v.default('"options.checkServerIdentity" was never documented, please use "options.https.checkServerIdentity"'),"ca"in r&&v.default('"options.ca" was never documented, please use "options.https.certificateAuthority"'),"key"in r&&v.default('"options.key" was never documented, please use "options.https.key"'),"cert"in r&&v.default('"options.cert" was never documented, please use "options.https.certificate"'),"passphrase"in r&&v.default('"options.passphrase" was never documented, please use "options.https.passphrase"'),"pfx"in r&&v.default('"options.pfx" was never documented, please use "options.https.pfx"'),"followRedirects"in r)throw new TypeError("The `followRedirects` option does not exist. Use `followRedirect` instead.");if(r.agent)for(const e in r.agent)if("http"!==e&&"https"!==e&&"http2"!==e)throw new TypeError(`Expected the \`options.agent\` properties to be \`http\`, \`https\` or \`http2\`, got \`${e}\``);return r.maxRedirects=null!==(g=r.maxRedirects)&&void 0!==g?g:0,t.setNonEnumerableProperties([n,l],r),S.default(r,n)}_lockWrite(){const e=()=>{throw new TypeError("The payload has been already provided")};this.write=e,this.end=e}_unlockWrite(){this.write=super.write,this.end=super.end}async _finalizeBody(){const{options:e}=this,{headers:r}=e,A=!C.default.undefined(e.form),o=!C.default.undefined(e.json),s=!C.default.undefined(e.body),a=A||o||s,c=t.withoutBody.has(e.method)&&!("GET"===e.method&&e.allowGetBody);if(this._cannotHaveBody=c,a){if(c)throw new TypeError(`The \`${e.method}\` method cannot be used with a body`);if([s,A,o].filter(e=>e).length>1)throw new TypeError("The `body`, `json` and `form` options are mutually exclusive");if(s&&!(e.body instanceof n.Readable)&&!C.default.string(e.body)&&!C.default.buffer(e.body)&&!I.default(e.body))throw new TypeError("The `body` option must be a stream.Readable, string or Buffer");if(A&&!C.default.object(e.form))throw new TypeError("The `form` option must be an Object");{const t=!C.default.string(r["content-type"]);s?(I.default(e.body)&&t&&(r["content-type"]="multipart/form-data; boundary="+e.body.getBoundary()),this[H]=e.body):A?(t&&(r["content-type"]="application/x-www-form-urlencoded"),this[H]=new i.URLSearchParams(e.form).toString()):(t&&(r["content-type"]="application/json"),this[H]=e.stringifyJson(e.json));const n=await f.default(this[H],e.headers);C.default.undefined(r["content-length"])&&C.default.undefined(r["transfer-encoding"])&&(c||C.default.undefined(n)||(r["content-length"]=String(n)))}}else c?this._lockWrite():this._unlockWrite();this[x]=Number(r["content-length"])||void 0}async _onResponseBase(e){const{options:t}=this,{url:r}=t;this[q]=e,t.decompress&&(e=h(e));const A=e.statusCode,n=e;n.statusMessage=n.statusMessage?n.statusMessage:s.STATUS_CODES[A],n.url=t.url.toString(),n.requestUrl=this.requestUrl,n.redirectUrls=this.redirects,n.request=this,n.isFromCache=e.fromCache||!1,n.ip=this.ip,n.retryCount=this.retryCount,this[U]=n.isFromCache,this[M]=Number(e.headers["content-length"])||void 0,this[K]=e,e.once("end",()=>{this[M]=this[R],this.emit("downloadProgress",this.downloadProgress)}),e.once("error",t=>{e.destroy(),this._beforeError(new ne(t,this))}),e.once("aborted",()=>{this._beforeError(new ne({name:"Error",message:"The server aborted pending request",code:"ECONNRESET"},this))}),this.emit("downloadProgress",this.downloadProgress);const o=e.headers["set-cookie"];if(C.default.object(t.cookieJar)&&o){let e=o.map(async e=>t.cookieJar.setCookie(e,r.toString()));t.ignoreInvalidCookies&&(e=e.map(async e=>e.catch(()=>{})));try{await Promise.all(e)}catch(e){return void this._beforeError(e)}}if(t.followRedirect&&e.headers.location&&X.has(A)){e.resume(),this[F]&&(this[T](),delete this[F],this[O]());if(!(303===A&&"GET"!==t.method&&"HEAD"!==t.method)&&t.methodRewriting||(t.method="GET","body"in t&&delete t.body,"json"in t&&delete t.json,"form"in t&&delete t.form,this[H]=void 0,delete t.headers["content-length"]),this.redirects.length>=t.maxRedirects)return void this._beforeError(new $(this));try{const A=Buffer.from(e.headers.location,"binary").toString(),o=new i.URL(A,r),s=o.toString();decodeURI(s),o.hostname!==r.hostname||o.port!==r.port?("host"in t.headers&&delete t.headers.host,"cookie"in t.headers&&delete t.headers.cookie,"authorization"in t.headers&&delete t.headers.authorization,(t.username||t.password)&&(t.username="",t.password="")):(o.username=t.username,o.password=t.password),this.redirects.push(s),t.url=o;for(const e of t.hooks.beforeRedirect)await e(t,n);this.emit("redirect",n,t),await this._makeRequest()}catch(e){return void this._beforeError(e)}}else if(t.isStream&&t.throwHttpErrors&&!b.isResponseOk(n))this._beforeError(new ee(n));else{e.on("readable",()=>{this[G]&&this._read()}),this.on("resume",()=>{e.resume()}),this.on("pause",()=>{e.pause()}),e.once("end",()=>{this.push(null)}),this.emit("response",e);for(const r of this[P])if(!r.headersSent){for(const A in e.headers){const n=!t.decompress||"content-encoding"!==A,o=e.headers[A];n&&r.setHeader(A,o)}r.statusCode=A}}}async _onResponse(e){try{await this._onResponseBase(e)}catch(e){this._beforeError(e)}}_onRequest(e){const{options:t}=this,{timeout:r,url:A}=t;g.default(e),this[T]=B.default(e,r,A);const n=t.cache?"cacheableResponse":"response";e.once(n,e=>{this._onResponse(e)}),e.once("error",t=>{var r;e.destroy(),null===(r=e.res)||void 0===r||r.removeAllListeners("end"),t=t instanceof B.TimeoutError?new Ae(t,this.timings,this):new Z(t.message,t,this),this._beforeError(t)}),this[O]=E.default(e,this,ie),this[F]=e,this.emit("uploadProgress",this.uploadProgress);const o=this[H],i=0===this.redirects.length?this:e;C.default.nodeStream(o)?(o.pipe(i),o.once("error",e=>{this._beforeError(new re(e,this))})):(this._unlockWrite(),C.default.undefined(o)?(this._cannotHaveBody||this._noPipe)&&(i.end(),this._lockWrite()):(this._writeRequest(o,void 0,()=>{}),i.end(),this._lockWrite())),this.emit("request",e)}async _createCacheableRequest(e,t){return new Promise((r,A)=>{let n;Object.assign(t,y.default(e)),delete t.url;const o=V.get(t.cache)(t,async e=>{e._readableState.autoDestroy=!1,n&&(await n).emit("cacheableResponse",e),r(e)});t.url=e,o.once("error",A),o.once("request",async e=>{n=e,r(n)})})}async _makeRequest(){var e,t,r,A,n;const{options:o}=this,{headers:i}=o;for(const e in i)if(C.default.undefined(i[e]))delete i[e];else if(C.default.null_(i[e]))throw new TypeError(`Use \`undefined\` instead of \`null\` to delete the \`${e}\` header`);if(o.decompress&&C.default.undefined(i["accept-encoding"])&&(i["accept-encoding"]=W?"gzip, deflate, br":"gzip, deflate"),o.cookieJar){const e=await o.cookieJar.getCookieString(o.url.toString());C.default.nonEmptyString(e)&&(o.headers.cookie=e)}for(const e of o.hooks.beforeRequest){const t=await e(o);if(!C.default.undefined(t)){o.request=()=>t;break}}o.body&&this[H]!==o.body&&(this[H]=o.body);const{agent:a,request:g,timeout:l,url:h}=o;if(o.dnsCache&&!("lookup"in o)&&(o.lookup=o.dnsCache.lookup),"unix"===h.hostname){const e=/(?.+?):(?.+)/.exec(`${h.pathname}${h.search}`);if(null==e?void 0:e.groups){const{socketPath:t,path:r}=e.groups;Object.assign(o,{socketPath:t,path:r,host:""})}}const d="https:"===h.protocol;let f;f=o.http2?p.auto:d?c.request:s.request;const I=null!==(e=o.request)&&void 0!==e?e:f,E=o.cache?this._createCacheableRequest:I;a&&!o.http2&&(o.agent=a[d?"https":"http"]),o[F]=I,delete o.request,delete o.timeout;const B=o;if(B.shared=null===(t=o.cacheOptions)||void 0===t?void 0:t.shared,B.cacheHeuristic=null===(r=o.cacheOptions)||void 0===r?void 0:r.cacheHeuristic,B.immutableMinTimeToLive=null===(A=o.cacheOptions)||void 0===A?void 0:A.immutableMinTimeToLive,B.ignoreCargoCult=null===(n=o.cacheOptions)||void 0===n?void 0:n.ignoreCargoCult,void 0!==o.dnsLookupIpVersion)try{B.family=D.dnsLookupIpVersionToFamily(o.dnsLookupIpVersion)}catch(e){throw new Error("Invalid `dnsLookupIpVersion` option value")}o.https&&("rejectUnauthorized"in o.https&&(B.rejectUnauthorized=o.https.rejectUnauthorized),o.https.checkServerIdentity&&(B.checkServerIdentity=o.https.checkServerIdentity),o.https.certificateAuthority&&(B.ca=o.https.certificateAuthority),o.https.certificate&&(B.cert=o.https.certificate),o.https.key&&(B.key=o.https.key),o.https.passphrase&&(B.passphrase=o.https.passphrase),o.https.pfx&&(B.pfx=o.https.pfx));try{let e=await E(h,B);C.default.undefined(e)&&(e=f(h,B)),o.request=g,o.timeout=l,o.agent=a,o.https&&("rejectUnauthorized"in o.https&&delete B.rejectUnauthorized,o.https.checkServerIdentity&&delete B.checkServerIdentity,o.https.certificateAuthority&&delete B.ca,o.https.certificate&&delete B.cert,o.https.key&&delete B.key,o.https.passphrase&&delete B.passphrase,o.https.pfx&&delete B.pfx),y=e,C.default.object(y)&&!("statusCode"in y)?this._onRequest(e):this.writable?(this.once("finish",()=>{this._onResponse(e)}),this._unlockWrite(),this.end(),this._lockWrite()):this._onResponse(e)}catch(e){if(e instanceof u.CacheError)throw new te(e,this);throw new Z(e.message,e,this)}var y}async _error(e){try{for(const t of this.options.hooks.beforeError)e=await t(e)}catch(t){e=new Z(t.message,t,this)}this.destroy(e)}_beforeError(e){if(this[Y])return;const{options:t}=this,r=this.retryCount+1;this[Y]=!0,e instanceof Z||(e=new Z(e.message,e,this));const A=e,{response:n}=A;(async()=>{if(n&&!n.body){n.setEncoding(this._readableState.encoding);try{n.rawBody=await Q.default(n),n.body=n.rawBody.toString()}catch(e){}}if(0!==this.listenerCount("retry")){let o;try{let e;n&&"retry-after"in n.headers&&(e=Number(n.headers["retry-after"]),Number.isNaN(e)?(e=Date.parse(n.headers["retry-after"])-Date.now(),e<=0&&(e=1)):e*=1e3),o=await t.retry.calculateDelay({attemptCount:r,retryOptions:t.retry,error:A,retryAfter:e,computedValue:k.default({attemptCount:r,retryOptions:t.retry,error:A,retryAfter:e,computedValue:0})})}catch(e){return void this._error(new Z(e.message,e,this))}if(o){const t=async()=>{try{for(const e of this.options.hooks.beforeRetry)await e(this.options,A,r)}catch(t){return void this._error(new Z(t.message,e,this))}this.destroyed||(this.destroy(),this.emit("retry",r,e))};return void(this[z]=setTimeout(t,o))}}this._error(A)})()}_read(){this[G]=!0;const e=this[K];if(e&&!this[Y]){let t;for(e.readableLength&&(this[G]=!1);null!==(t=e.read());){this[R]+=t.length,this[j]=!0;const e=this.downloadProgress;e.percent<1&&this.emit("downloadProgress",e),this.push(t)}}}_write(e,t,r){const A=()=>{this._writeRequest(e,t,r)};this.requestInitialized?A():this[J].push(A)}_writeRequest(e,t,r){this[F].destroyed||(this._progressCallbacks.push(()=>{this[L]+=Buffer.byteLength(e,t);const r=this.uploadProgress;r.percent<1&&this.emit("uploadProgress",r)}),this[F].write(e,t,e=>{e||0===this._progressCallbacks.length||this._progressCallbacks.shift()(),r(e)}))}_final(e){const t=()=>{for(;0!==this._progressCallbacks.length;)this._progressCallbacks.shift()();F in this?this[F].destroyed?e():this[F].end(t=>{t||(this[x]=this[L],this.emit("uploadProgress",this.uploadProgress),this[F].emit("upload-complete")),e(t)}):e()};this.requestInitialized?t():this[J].push(t)}_destroy(e,t){var r;this[Y]=!0,clearTimeout(this[z]),F in this&&(this[T](),(null===(r=this[K])||void 0===r?void 0:r.complete)||this[F].destroy()),null===e||C.default.undefined(e)||e instanceof Z||(e=new Z(e.message,e,this)),t(e)}get _isAboutToError(){return this[Y]}get ip(){var e;return null===(e=this[F])||void 0===e?void 0:e.socket.remoteAddress}get aborted(){var e,t,r;return(null!==(t=null===(e=this[F])||void 0===e?void 0:e.destroyed)&&void 0!==t?t:this.destroyed)&&!(null===(r=this[q])||void 0===r?void 0:r.complete)}get socket(){var e;return null===(e=this[F])||void 0===e?void 0:e.socket}get downloadProgress(){let e;return e=this[M]?this[R]/this[M]:this[M]===this[R]?1:0,{percent:e,transferred:this[R],total:this[M]}}get uploadProgress(){let e;return e=this[x]?this[L]/this[x]:this[x]===this[L]?1:0,{percent:e,transferred:this[L],total:this[x]}}get timings(){var e;return null===(e=this[F])||void 0===e?void 0:e.timings}get isFromCache(){return this[U]}pipe(e,t){if(this[j])throw new Error("Failed to pipe. The response has been emitted already.");return e instanceof a.ServerResponse&&this[P].add(e),super.pipe(e,t)}unpipe(e){return e instanceof a.ServerResponse&&this[P].delete(e),super.unpipe(e),this}}t.default=se},11338:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.dnsLookupIpVersionToFamily=t.isDnsLookupIpVersion=void 0;const r={auto:0,ipv4:4,ipv6:6};t.isDnsLookupIpVersion=e=>e in r,t.dnsLookupIpVersionToFamily=e=>{if(t.isDnsLookupIpVersion(e))return r[e];throw new Error("Invalid DNS lookup IP version")}},78586:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(35747),n=r(31669),o=r(7966),i=r(2920),s=n.promisify(A.stat);t.default=async(e,t)=>{if(t&&"content-length"in t)return Number(t["content-length"]);if(!e)return 0;if(o.default.string(e))return Buffer.byteLength(e);if(o.default.buffer(e))return e.length;if(i.default(e))return n.promisify(e.getLength.bind(e))();if(e instanceof A.ReadStream){const{size:t}=await s(e.path);return t}}},57854:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});t.default=async e=>{const t=[];let r=0;for await(const A of e)t.push(A),r+=Buffer.byteLength(A);return Buffer.isBuffer(t[0])?Buffer.concat(t,r):Buffer.from(t.join(""))}},2920:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(7966);t.default=e=>A.default.nodeStream(e)&&A.default.function_(e.getBoundary)},38206:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.isResponseOk=void 0,t.isResponseOk=e=>{const{statusCode:t}=e,r=e.request.options.followRedirect?299:399;return t>=200&&t<=r||304===t}},82524:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(78835),n=["protocol","host","hostname","port","pathname","search"];t.default=(e,t)=>{var r,o;if(t.path){if(t.pathname)throw new TypeError("Parameters `path` and `pathname` are mutually exclusive.");if(t.search)throw new TypeError("Parameters `path` and `search` are mutually exclusive.");if(t.searchParams)throw new TypeError("Parameters `path` and `searchParams` are mutually exclusive.")}if(t.search&&t.searchParams)throw new TypeError("Parameters `search` and `searchParams` are mutually exclusive.");if(!e){if(!t.protocol)throw new TypeError("No URL protocol specified");e=`${t.protocol}//${null!==(o=null!==(r=t.hostname)&&void 0!==r?r:t.host)&&void 0!==o?o:""}`}const i=new A.URL(e);if(t.path){const e=t.path.indexOf("?");-1===e?t.pathname=t.path:(t.pathname=t.path.slice(0,e),t.search=t.path.slice(e+1)),delete t.path}for(const e of n)t[e]&&(i[e]=t[e].toString());return i}},51743:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t,r){const A={};for(const n of r)A[n]=(...e)=>{t.emit(n,...e)},e.on(n,A[n]);return()=>{for(const t of r)e.off(t,A[t])}}},44947:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.TimeoutError=void 0;const A=r(11631),n=r(70148),o=Symbol("reentry"),i=()=>{};class s extends Error{constructor(e,t){super(`Timeout awaiting '${t}' for ${e}ms`),this.event=t,this.name="TimeoutError",this.code="ETIMEDOUT"}}t.TimeoutError=s,t.default=(e,t,r)=>{if(o in e)return i;e[o]=!0;const a=[],{once:c,unhandleAll:g}=n.default(),l=(e,t,r)=>{var A;const n=setTimeout(t,e,e,r);null===(A=n.unref)||void 0===A||A.call(n);const o=()=>{clearTimeout(n)};return a.push(o),o},{host:u,hostname:h}=r,p=(t,r)=>{e.destroy(new s(t,r))},d=()=>{for(const e of a)e();g()};if(e.once("error",t=>{if(d(),0===e.listenerCount("error"))throw t}),e.once("close",d),c(e,"response",e=>{c(e,"end",d)}),void 0!==t.request&&l(t.request,p,"request"),void 0!==t.socket){const r=()=>{p(t.socket,"socket")};e.setTimeout(t.socket,r),a.push(()=>{e.removeListener("timeout",r)})}return c(e,"socket",n=>{var o;const{socketPath:i}=e;if(n.connecting){const e=Boolean(null!=i?i:0!==A.isIP(null!==(o=null!=h?h:u)&&void 0!==o?o:""));if(void 0!==t.lookup&&!e&&void 0===n.address().address){const e=l(t.lookup,p,"lookup");c(n,"lookup",e)}if(void 0!==t.connect){const r=()=>l(t.connect,p,"connect");e?c(n,"connect",r()):c(n,"lookup",e=>{null===e&&c(n,"connect",r())})}void 0!==t.secureConnect&&"https:"===r.protocol&&c(n,"connect",()=>{const e=l(t.secureConnect,p,"secureConnect");c(n,"secureConnect",e)})}if(void 0!==t.send){const r=()=>l(t.send,p,"send");n.connecting?c(n,"connect",()=>{c(e,"upload-complete",r())}):c(e,"upload-complete",r())}}),void 0!==t.response&&c(e,"upload-complete",()=>{const r=l(t.response,p,"response");c(e,"response",r)}),d}},70148:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=()=>{const e=[];return{once(t,r,A){t.once(r,A),e.push({origin:t,event:r,fn:A})},unhandleAll(){for(const t of e){const{origin:e,event:r,fn:A}=t;e.removeListener(r,A)}e.length=0}}}},50116:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(7966);t.default=e=>{const t={protocol:(e=e).protocol,hostname:A.default.string(e.hostname)&&e.hostname.startsWith("[")?e.hostname.slice(1,-1):e.hostname,host:e.host,hash:e.hash,search:e.search,pathname:e.pathname,href:e.href,path:`${e.pathname||""}${e.search||""}`};return A.default.string(e.port)&&0!==e.port.length&&(t.port=Number(e.port)),(e.username||e.password)&&(t.auth=`${e.username||""}:${e.password||""}`),t}},85551:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});t.default=class{constructor(){this.weakMap=new WeakMap,this.map=new Map}set(e,t){"object"==typeof e?this.weakMap.set(e,t):this.map.set(e,t)}get(e){return"object"==typeof e?this.weakMap.get(e):this.map.get(e)}has(e){return"object"==typeof e?this.weakMap.has(e):this.map.has(e)}}},39226:function(e,t,r){"use strict";var A=this&&this.__createBinding||(Object.create?function(e,t,r,A){void 0===A&&(A=r),Object.defineProperty(e,A,{enumerable:!0,get:function(){return t[r]}})}:function(e,t,r,A){void 0===A&&(A=r),e[A]=t[r]}),n=this&&this.__exportStar||function(e,t){for(var r in e)"default"===r||Object.prototype.hasOwnProperty.call(t,r)||A(t,e,r)};Object.defineProperty(t,"__esModule",{value:!0}),t.defaultHandler=void 0;const o=r(7966),i=r(81588),s=r(93576),a=r(9048),c=r(9743),g={RequestError:i.RequestError,CacheError:i.CacheError,ReadError:i.ReadError,HTTPError:i.HTTPError,MaxRedirectsError:i.MaxRedirectsError,TimeoutError:i.TimeoutError,ParseError:i.ParseError,CancelError:i.CancelError,UnsupportedProtocolError:i.UnsupportedProtocolError,UploadError:i.UploadError},l=async e=>new Promise(t=>{setTimeout(t,e)}),{normalizeArguments:u}=a.default,h=(...e)=>{let t;for(const r of e)t=u(void 0,r,t);return t},p=e=>e.isStream?new a.default(void 0,e):i.default(e),d=e=>"defaults"in e&&"options"in e.defaults,C=["get","post","put","patch","head","delete"];t.defaultHandler=(e,t)=>t(e);const f=(e,t)=>{if(e)for(const r of e)r(t)},I=e=>{e._rawHandlers=e.handlers,e.handlers=e.handlers.map(e=>(t,r)=>{let A;const n=e(t,e=>(A=r(e),A));if(n!==A&&!t.isStream&&A){const e=n,{then:t,catch:r,finally:o}=e;Object.setPrototypeOf(e,Object.getPrototypeOf(A)),Object.defineProperties(e,Object.getOwnPropertyDescriptors(A)),e.then=t,e.catch=r,e.finally=o}return n});const r=(t,r,A)=>{var n,c;let g=0;const l=t=>e.handlers[g++](t,g===e.handlers.length?p:l);if(o.default.plainObject(t)){const e={...t,...r};a.setNonEnumerableProperties([t,r],e),r=e,t=void 0}try{let o;try{f(e.options.hooks.init,r),f(null===(n=null==r?void 0:r.hooks)||void 0===n?void 0:n.init,r)}catch(e){o=e}const s=u(t,r,null!=A?A:e.options);if(s[a.kIsNormalizedAlready]=!0,o)throw new i.RequestError(o.message,o,s);return l(s)}catch(t){if(null==r?void 0:r.isStream)throw t;return s.default(t,e.options.hooks.beforeError,null===(c=null==r?void 0:r.hooks)||void 0===c?void 0:c.beforeError)}};r.extend=(...r)=>{const A=[e.options];let n,o=[...e._rawHandlers];for(const e of r)d(e)?(A.push(e.defaults.options),o.push(...e.defaults._rawHandlers),n=e.defaults.mutableDefaults):(A.push(e),"handlers"in e&&o.push(...e.handlers),n=e.mutableDefaults);return o=o.filter(e=>e!==t.defaultHandler),0===o.length&&o.push(t.defaultHandler),I({options:h(...A),handlers:o,mutableDefaults:Boolean(n)})};const A=async function*(t,A){let n=u(t,A,e.options);n.resolveBodyOnly=!1;const i=n.pagination;if(!o.default.object(i))throw new TypeError("`options.pagination` must be implemented");const s=[];let{countLimit:a}=i,c=0;for(;c{const r=[];for await(const n of A(e,t))r.push(n);return r},r.paginate.each=A,r.stream=(e,t)=>r(e,{...t,isStream:!0});for(const e of C)r[e]=(t,A)=>r(t,{...A,method:e}),r.stream[e]=(t,A)=>r(t,{...A,method:e,isStream:!0});return Object.assign(r,g),Object.defineProperty(r,"defaults",{value:e.mutableDefaults?e:c.default(e),writable:e.mutableDefaults,configurable:e.mutableDefaults,enumerable:!0}),r.mergeOptions=h,r};t.default=I,n(r(69022),t)},48722:function(e,t,r){"use strict";var A=this&&this.__createBinding||(Object.create?function(e,t,r,A){void 0===A&&(A=r),Object.defineProperty(e,A,{enumerable:!0,get:function(){return t[r]}})}:function(e,t,r,A){void 0===A&&(A=r),e[A]=t[r]}),n=this&&this.__exportStar||function(e,t){for(var r in e)"default"===r||Object.prototype.hasOwnProperty.call(t,r)||A(t,e,r)};Object.defineProperty(t,"__esModule",{value:!0});const o=r(78835),i=r(39226),s={options:{method:"GET",retry:{limit:2,methods:["GET","PUT","HEAD","DELETE","OPTIONS","TRACE"],statusCodes:[408,413,429,500,502,503,504,521,522,524],errorCodes:["ETIMEDOUT","ECONNRESET","EADDRINUSE","ECONNREFUSED","EPIPE","ENOTFOUND","ENETUNREACH","EAI_AGAIN"],maxRetryAfter:void 0,calculateDelay:({computedValue:e})=>e},timeout:{},headers:{"user-agent":"got (https://github.com/sindresorhus/got)"},hooks:{init:[],beforeRequest:[],beforeRedirect:[],beforeRetry:[],beforeError:[],afterResponse:[]},cache:void 0,dnsCache:void 0,decompress:!0,throwHttpErrors:!0,followRedirect:!0,isStream:!1,responseType:"text",resolveBodyOnly:!1,maxRedirects:10,prefixUrl:"",methodRewriting:!0,ignoreInvalidCookies:!1,context:{},http2:!1,allowGetBody:!1,https:void 0,pagination:{transform:e=>"json"===e.request.options.responseType?e.body:JSON.parse(e.body),paginate:e=>{if(!Reflect.has(e.headers,"link"))return!1;const t=e.headers.link.split(",");let r;for(const e of t){const t=e.split(";");if(t[1].includes("next")){r=t[0].trimStart().trim(),r=r.slice(1,-1);break}}if(r){return{url:new o.URL(r)}}return!1},filter:()=>!0,shouldContinue:()=>!0,countLimit:1/0,backoff:0,requestLimit:1e4,stackAllItems:!0},parseJson:e=>JSON.parse(e),stringifyJson:e=>JSON.stringify(e),cacheOptions:{}},handlers:[i.defaultHandler],mutableDefaults:!1},a=i.default(s);t.default=a,e.exports=a,e.exports.default=a,e.exports.__esModule=!0,n(r(39226),t),n(r(81588),t)},69022:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0})},9743:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const A=r(7966);t.default=function e(t){for(const r of Object.values(t))(A.default.plainObject(r)||A.default.array(r))&&e(r);return Object.freeze(t)}},54595:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=new Set;t.default=e=>{r.has(e)||(r.add(e),process.emitWarning("Got: "+e,{type:"DeprecationWarning"}))}},74988:e=>{e.exports&&(e.exports=function(){var e=3,t=4,r=12,A=13,n=16,o=17;function i(e,t){void 0===t&&(t=0);var r=e.charCodeAt(t);if(55296<=r&&r<=56319&&t=1){var n=r;return 55296<=(A=e.charCodeAt(t-1))&&A<=56319?1024*(A-55296)+(n-56320)+65536:n}return r}function s(i,s,a){var c=[i].concat(s).concat([a]),g=c[c.length-2],l=a,u=c.lastIndexOf(14);if(u>1&&c.slice(1,u).every((function(t){return t==e}))&&-1==[e,A,o].indexOf(i))return 2;var h=c.lastIndexOf(t);if(h>0&&c.slice(1,h).every((function(e){return e==t}))&&-1==[r,t].indexOf(g))return c.filter((function(e){return e==t})).length%2==1?3:4;if(0==g&&1==l)return 0;if(2==g||0==g||1==g)return 14==l&&s.every((function(t){return t==e}))?2:1;if(2==l||0==l||1==l)return 1;if(6==g&&(6==l||7==l||9==l||10==l))return 0;if(!(9!=g&&7!=g||7!=l&&8!=l))return 0;if((10==g||8==g)&&8==l)return 0;if(l==e||15==l)return 0;if(5==l)return 0;if(g==r)return 0;var p=-1!=c.indexOf(e)?c.lastIndexOf(e)-1:c.length-2;return-1!=[A,o].indexOf(c[p])&&c.slice(p+1,-1).every((function(t){return t==e}))&&14==l||15==g&&-1!=[n,o].indexOf(l)?0:-1!=s.indexOf(t)?2:g==t&&l==t?0:1}function a(i){return 1536<=i&&i<=1541||1757==i||1807==i||2274==i||3406==i||69821==i||70082<=i&&i<=70083||72250==i||72326<=i&&i<=72329||73030==i?r:13==i?0:10==i?1:0<=i&&i<=9||11<=i&&i<=12||14<=i&&i<=31||127<=i&&i<=159||173==i||1564==i||6158==i||8203==i||8206<=i&&i<=8207||8232==i||8233==i||8234<=i&&i<=8238||8288<=i&&i<=8292||8293==i||8294<=i&&i<=8303||55296<=i&&i<=57343||65279==i||65520<=i&&i<=65528||65529<=i&&i<=65531||113824<=i&&i<=113827||119155<=i&&i<=119162||917504==i||917505==i||917506<=i&&i<=917535||917632<=i&&i<=917759||918e3<=i&&i<=921599?2:768<=i&&i<=879||1155<=i&&i<=1159||1160<=i&&i<=1161||1425<=i&&i<=1469||1471==i||1473<=i&&i<=1474||1476<=i&&i<=1477||1479==i||1552<=i&&i<=1562||1611<=i&&i<=1631||1648==i||1750<=i&&i<=1756||1759<=i&&i<=1764||1767<=i&&i<=1768||1770<=i&&i<=1773||1809==i||1840<=i&&i<=1866||1958<=i&&i<=1968||2027<=i&&i<=2035||2070<=i&&i<=2073||2075<=i&&i<=2083||2085<=i&&i<=2087||2089<=i&&i<=2093||2137<=i&&i<=2139||2260<=i&&i<=2273||2275<=i&&i<=2306||2362==i||2364==i||2369<=i&&i<=2376||2381==i||2385<=i&&i<=2391||2402<=i&&i<=2403||2433==i||2492==i||2494==i||2497<=i&&i<=2500||2509==i||2519==i||2530<=i&&i<=2531||2561<=i&&i<=2562||2620==i||2625<=i&&i<=2626||2631<=i&&i<=2632||2635<=i&&i<=2637||2641==i||2672<=i&&i<=2673||2677==i||2689<=i&&i<=2690||2748==i||2753<=i&&i<=2757||2759<=i&&i<=2760||2765==i||2786<=i&&i<=2787||2810<=i&&i<=2815||2817==i||2876==i||2878==i||2879==i||2881<=i&&i<=2884||2893==i||2902==i||2903==i||2914<=i&&i<=2915||2946==i||3006==i||3008==i||3021==i||3031==i||3072==i||3134<=i&&i<=3136||3142<=i&&i<=3144||3146<=i&&i<=3149||3157<=i&&i<=3158||3170<=i&&i<=3171||3201==i||3260==i||3263==i||3266==i||3270==i||3276<=i&&i<=3277||3285<=i&&i<=3286||3298<=i&&i<=3299||3328<=i&&i<=3329||3387<=i&&i<=3388||3390==i||3393<=i&&i<=3396||3405==i||3415==i||3426<=i&&i<=3427||3530==i||3535==i||3538<=i&&i<=3540||3542==i||3551==i||3633==i||3636<=i&&i<=3642||3655<=i&&i<=3662||3761==i||3764<=i&&i<=3769||3771<=i&&i<=3772||3784<=i&&i<=3789||3864<=i&&i<=3865||3893==i||3895==i||3897==i||3953<=i&&i<=3966||3968<=i&&i<=3972||3974<=i&&i<=3975||3981<=i&&i<=3991||3993<=i&&i<=4028||4038==i||4141<=i&&i<=4144||4146<=i&&i<=4151||4153<=i&&i<=4154||4157<=i&&i<=4158||4184<=i&&i<=4185||4190<=i&&i<=4192||4209<=i&&i<=4212||4226==i||4229<=i&&i<=4230||4237==i||4253==i||4957<=i&&i<=4959||5906<=i&&i<=5908||5938<=i&&i<=5940||5970<=i&&i<=5971||6002<=i&&i<=6003||6068<=i&&i<=6069||6071<=i&&i<=6077||6086==i||6089<=i&&i<=6099||6109==i||6155<=i&&i<=6157||6277<=i&&i<=6278||6313==i||6432<=i&&i<=6434||6439<=i&&i<=6440||6450==i||6457<=i&&i<=6459||6679<=i&&i<=6680||6683==i||6742==i||6744<=i&&i<=6750||6752==i||6754==i||6757<=i&&i<=6764||6771<=i&&i<=6780||6783==i||6832<=i&&i<=6845||6846==i||6912<=i&&i<=6915||6964==i||6966<=i&&i<=6970||6972==i||6978==i||7019<=i&&i<=7027||7040<=i&&i<=7041||7074<=i&&i<=7077||7080<=i&&i<=7081||7083<=i&&i<=7085||7142==i||7144<=i&&i<=7145||7149==i||7151<=i&&i<=7153||7212<=i&&i<=7219||7222<=i&&i<=7223||7376<=i&&i<=7378||7380<=i&&i<=7392||7394<=i&&i<=7400||7405==i||7412==i||7416<=i&&i<=7417||7616<=i&&i<=7673||7675<=i&&i<=7679||8204==i||8400<=i&&i<=8412||8413<=i&&i<=8416||8417==i||8418<=i&&i<=8420||8421<=i&&i<=8432||11503<=i&&i<=11505||11647==i||11744<=i&&i<=11775||12330<=i&&i<=12333||12334<=i&&i<=12335||12441<=i&&i<=12442||42607==i||42608<=i&&i<=42610||42612<=i&&i<=42621||42654<=i&&i<=42655||42736<=i&&i<=42737||43010==i||43014==i||43019==i||43045<=i&&i<=43046||43204<=i&&i<=43205||43232<=i&&i<=43249||43302<=i&&i<=43309||43335<=i&&i<=43345||43392<=i&&i<=43394||43443==i||43446<=i&&i<=43449||43452==i||43493==i||43561<=i&&i<=43566||43569<=i&&i<=43570||43573<=i&&i<=43574||43587==i||43596==i||43644==i||43696==i||43698<=i&&i<=43700||43703<=i&&i<=43704||43710<=i&&i<=43711||43713==i||43756<=i&&i<=43757||43766==i||44005==i||44008==i||44013==i||64286==i||65024<=i&&i<=65039||65056<=i&&i<=65071||65438<=i&&i<=65439||66045==i||66272==i||66422<=i&&i<=66426||68097<=i&&i<=68099||68101<=i&&i<=68102||68108<=i&&i<=68111||68152<=i&&i<=68154||68159==i||68325<=i&&i<=68326||69633==i||69688<=i&&i<=69702||69759<=i&&i<=69761||69811<=i&&i<=69814||69817<=i&&i<=69818||69888<=i&&i<=69890||69927<=i&&i<=69931||69933<=i&&i<=69940||70003==i||70016<=i&&i<=70017||70070<=i&&i<=70078||70090<=i&&i<=70092||70191<=i&&i<=70193||70196==i||70198<=i&&i<=70199||70206==i||70367==i||70371<=i&&i<=70378||70400<=i&&i<=70401||70460==i||70462==i||70464==i||70487==i||70502<=i&&i<=70508||70512<=i&&i<=70516||70712<=i&&i<=70719||70722<=i&&i<=70724||70726==i||70832==i||70835<=i&&i<=70840||70842==i||70845==i||70847<=i&&i<=70848||70850<=i&&i<=70851||71087==i||71090<=i&&i<=71093||71100<=i&&i<=71101||71103<=i&&i<=71104||71132<=i&&i<=71133||71219<=i&&i<=71226||71229==i||71231<=i&&i<=71232||71339==i||71341==i||71344<=i&&i<=71349||71351==i||71453<=i&&i<=71455||71458<=i&&i<=71461||71463<=i&&i<=71467||72193<=i&&i<=72198||72201<=i&&i<=72202||72243<=i&&i<=72248||72251<=i&&i<=72254||72263==i||72273<=i&&i<=72278||72281<=i&&i<=72283||72330<=i&&i<=72342||72344<=i&&i<=72345||72752<=i&&i<=72758||72760<=i&&i<=72765||72767==i||72850<=i&&i<=72871||72874<=i&&i<=72880||72882<=i&&i<=72883||72885<=i&&i<=72886||73009<=i&&i<=73014||73018==i||73020<=i&&i<=73021||73023<=i&&i<=73029||73031==i||92912<=i&&i<=92916||92976<=i&&i<=92982||94095<=i&&i<=94098||113821<=i&&i<=113822||119141==i||119143<=i&&i<=119145||119150<=i&&i<=119154||119163<=i&&i<=119170||119173<=i&&i<=119179||119210<=i&&i<=119213||119362<=i&&i<=119364||121344<=i&&i<=121398||121403<=i&&i<=121452||121461==i||121476==i||121499<=i&&i<=121503||121505<=i&&i<=121519||122880<=i&&i<=122886||122888<=i&&i<=122904||122907<=i&&i<=122913||122915<=i&&i<=122916||122918<=i&&i<=122922||125136<=i&&i<=125142||125252<=i&&i<=125258||917536<=i&&i<=917631||917760<=i&&i<=917999?e:127462<=i&&i<=127487?t:2307==i||2363==i||2366<=i&&i<=2368||2377<=i&&i<=2380||2382<=i&&i<=2383||2434<=i&&i<=2435||2495<=i&&i<=2496||2503<=i&&i<=2504||2507<=i&&i<=2508||2563==i||2622<=i&&i<=2624||2691==i||2750<=i&&i<=2752||2761==i||2763<=i&&i<=2764||2818<=i&&i<=2819||2880==i||2887<=i&&i<=2888||2891<=i&&i<=2892||3007==i||3009<=i&&i<=3010||3014<=i&&i<=3016||3018<=i&&i<=3020||3073<=i&&i<=3075||3137<=i&&i<=3140||3202<=i&&i<=3203||3262==i||3264<=i&&i<=3265||3267<=i&&i<=3268||3271<=i&&i<=3272||3274<=i&&i<=3275||3330<=i&&i<=3331||3391<=i&&i<=3392||3398<=i&&i<=3400||3402<=i&&i<=3404||3458<=i&&i<=3459||3536<=i&&i<=3537||3544<=i&&i<=3550||3570<=i&&i<=3571||3635==i||3763==i||3902<=i&&i<=3903||3967==i||4145==i||4155<=i&&i<=4156||4182<=i&&i<=4183||4228==i||6070==i||6078<=i&&i<=6085||6087<=i&&i<=6088||6435<=i&&i<=6438||6441<=i&&i<=6443||6448<=i&&i<=6449||6451<=i&&i<=6456||6681<=i&&i<=6682||6741==i||6743==i||6765<=i&&i<=6770||6916==i||6965==i||6971==i||6973<=i&&i<=6977||6979<=i&&i<=6980||7042==i||7073==i||7078<=i&&i<=7079||7082==i||7143==i||7146<=i&&i<=7148||7150==i||7154<=i&&i<=7155||7204<=i&&i<=7211||7220<=i&&i<=7221||7393==i||7410<=i&&i<=7411||7415==i||43043<=i&&i<=43044||43047==i||43136<=i&&i<=43137||43188<=i&&i<=43203||43346<=i&&i<=43347||43395==i||43444<=i&&i<=43445||43450<=i&&i<=43451||43453<=i&&i<=43456||43567<=i&&i<=43568||43571<=i&&i<=43572||43597==i||43755==i||43758<=i&&i<=43759||43765==i||44003<=i&&i<=44004||44006<=i&&i<=44007||44009<=i&&i<=44010||44012==i||69632==i||69634==i||69762==i||69808<=i&&i<=69810||69815<=i&&i<=69816||69932==i||70018==i||70067<=i&&i<=70069||70079<=i&&i<=70080||70188<=i&&i<=70190||70194<=i&&i<=70195||70197==i||70368<=i&&i<=70370||70402<=i&&i<=70403||70463==i||70465<=i&&i<=70468||70471<=i&&i<=70472||70475<=i&&i<=70477||70498<=i&&i<=70499||70709<=i&&i<=70711||70720<=i&&i<=70721||70725==i||70833<=i&&i<=70834||70841==i||70843<=i&&i<=70844||70846==i||70849==i||71088<=i&&i<=71089||71096<=i&&i<=71099||71102==i||71216<=i&&i<=71218||71227<=i&&i<=71228||71230==i||71340==i||71342<=i&&i<=71343||71350==i||71456<=i&&i<=71457||71462==i||72199<=i&&i<=72200||72249==i||72279<=i&&i<=72280||72343==i||72751==i||72766==i||72873==i||72881==i||72884==i||94033<=i&&i<=94078||119142==i||119149==i?5:4352<=i&&i<=4447||43360<=i&&i<=43388?6:4448<=i&&i<=4519||55216<=i&&i<=55238?7:4520<=i&&i<=4607||55243<=i&&i<=55291?8:44032==i||44060==i||44088==i||44116==i||44144==i||44172==i||44200==i||44228==i||44256==i||44284==i||44312==i||44340==i||44368==i||44396==i||44424==i||44452==i||44480==i||44508==i||44536==i||44564==i||44592==i||44620==i||44648==i||44676==i||44704==i||44732==i||44760==i||44788==i||44816==i||44844==i||44872==i||44900==i||44928==i||44956==i||44984==i||45012==i||45040==i||45068==i||45096==i||45124==i||45152==i||45180==i||45208==i||45236==i||45264==i||45292==i||45320==i||45348==i||45376==i||45404==i||45432==i||45460==i||45488==i||45516==i||45544==i||45572==i||45600==i||45628==i||45656==i||45684==i||45712==i||45740==i||45768==i||45796==i||45824==i||45852==i||45880==i||45908==i||45936==i||45964==i||45992==i||46020==i||46048==i||46076==i||46104==i||46132==i||46160==i||46188==i||46216==i||46244==i||46272==i||46300==i||46328==i||46356==i||46384==i||46412==i||46440==i||46468==i||46496==i||46524==i||46552==i||46580==i||46608==i||46636==i||46664==i||46692==i||46720==i||46748==i||46776==i||46804==i||46832==i||46860==i||46888==i||46916==i||46944==i||46972==i||47e3==i||47028==i||47056==i||47084==i||47112==i||47140==i||47168==i||47196==i||47224==i||47252==i||47280==i||47308==i||47336==i||47364==i||47392==i||47420==i||47448==i||47476==i||47504==i||47532==i||47560==i||47588==i||47616==i||47644==i||47672==i||47700==i||47728==i||47756==i||47784==i||47812==i||47840==i||47868==i||47896==i||47924==i||47952==i||47980==i||48008==i||48036==i||48064==i||48092==i||48120==i||48148==i||48176==i||48204==i||48232==i||48260==i||48288==i||48316==i||48344==i||48372==i||48400==i||48428==i||48456==i||48484==i||48512==i||48540==i||48568==i||48596==i||48624==i||48652==i||48680==i||48708==i||48736==i||48764==i||48792==i||48820==i||48848==i||48876==i||48904==i||48932==i||48960==i||48988==i||49016==i||49044==i||49072==i||49100==i||49128==i||49156==i||49184==i||49212==i||49240==i||49268==i||49296==i||49324==i||49352==i||49380==i||49408==i||49436==i||49464==i||49492==i||49520==i||49548==i||49576==i||49604==i||49632==i||49660==i||49688==i||49716==i||49744==i||49772==i||49800==i||49828==i||49856==i||49884==i||49912==i||49940==i||49968==i||49996==i||50024==i||50052==i||50080==i||50108==i||50136==i||50164==i||50192==i||50220==i||50248==i||50276==i||50304==i||50332==i||50360==i||50388==i||50416==i||50444==i||50472==i||50500==i||50528==i||50556==i||50584==i||50612==i||50640==i||50668==i||50696==i||50724==i||50752==i||50780==i||50808==i||50836==i||50864==i||50892==i||50920==i||50948==i||50976==i||51004==i||51032==i||51060==i||51088==i||51116==i||51144==i||51172==i||51200==i||51228==i||51256==i||51284==i||51312==i||51340==i||51368==i||51396==i||51424==i||51452==i||51480==i||51508==i||51536==i||51564==i||51592==i||51620==i||51648==i||51676==i||51704==i||51732==i||51760==i||51788==i||51816==i||51844==i||51872==i||51900==i||51928==i||51956==i||51984==i||52012==i||52040==i||52068==i||52096==i||52124==i||52152==i||52180==i||52208==i||52236==i||52264==i||52292==i||52320==i||52348==i||52376==i||52404==i||52432==i||52460==i||52488==i||52516==i||52544==i||52572==i||52600==i||52628==i||52656==i||52684==i||52712==i||52740==i||52768==i||52796==i||52824==i||52852==i||52880==i||52908==i||52936==i||52964==i||52992==i||53020==i||53048==i||53076==i||53104==i||53132==i||53160==i||53188==i||53216==i||53244==i||53272==i||53300==i||53328==i||53356==i||53384==i||53412==i||53440==i||53468==i||53496==i||53524==i||53552==i||53580==i||53608==i||53636==i||53664==i||53692==i||53720==i||53748==i||53776==i||53804==i||53832==i||53860==i||53888==i||53916==i||53944==i||53972==i||54e3==i||54028==i||54056==i||54084==i||54112==i||54140==i||54168==i||54196==i||54224==i||54252==i||54280==i||54308==i||54336==i||54364==i||54392==i||54420==i||54448==i||54476==i||54504==i||54532==i||54560==i||54588==i||54616==i||54644==i||54672==i||54700==i||54728==i||54756==i||54784==i||54812==i||54840==i||54868==i||54896==i||54924==i||54952==i||54980==i||55008==i||55036==i||55064==i||55092==i||55120==i||55148==i||55176==i?9:44033<=i&&i<=44059||44061<=i&&i<=44087||44089<=i&&i<=44115||44117<=i&&i<=44143||44145<=i&&i<=44171||44173<=i&&i<=44199||44201<=i&&i<=44227||44229<=i&&i<=44255||44257<=i&&i<=44283||44285<=i&&i<=44311||44313<=i&&i<=44339||44341<=i&&i<=44367||44369<=i&&i<=44395||44397<=i&&i<=44423||44425<=i&&i<=44451||44453<=i&&i<=44479||44481<=i&&i<=44507||44509<=i&&i<=44535||44537<=i&&i<=44563||44565<=i&&i<=44591||44593<=i&&i<=44619||44621<=i&&i<=44647||44649<=i&&i<=44675||44677<=i&&i<=44703||44705<=i&&i<=44731||44733<=i&&i<=44759||44761<=i&&i<=44787||44789<=i&&i<=44815||44817<=i&&i<=44843||44845<=i&&i<=44871||44873<=i&&i<=44899||44901<=i&&i<=44927||44929<=i&&i<=44955||44957<=i&&i<=44983||44985<=i&&i<=45011||45013<=i&&i<=45039||45041<=i&&i<=45067||45069<=i&&i<=45095||45097<=i&&i<=45123||45125<=i&&i<=45151||45153<=i&&i<=45179||45181<=i&&i<=45207||45209<=i&&i<=45235||45237<=i&&i<=45263||45265<=i&&i<=45291||45293<=i&&i<=45319||45321<=i&&i<=45347||45349<=i&&i<=45375||45377<=i&&i<=45403||45405<=i&&i<=45431||45433<=i&&i<=45459||45461<=i&&i<=45487||45489<=i&&i<=45515||45517<=i&&i<=45543||45545<=i&&i<=45571||45573<=i&&i<=45599||45601<=i&&i<=45627||45629<=i&&i<=45655||45657<=i&&i<=45683||45685<=i&&i<=45711||45713<=i&&i<=45739||45741<=i&&i<=45767||45769<=i&&i<=45795||45797<=i&&i<=45823||45825<=i&&i<=45851||45853<=i&&i<=45879||45881<=i&&i<=45907||45909<=i&&i<=45935||45937<=i&&i<=45963||45965<=i&&i<=45991||45993<=i&&i<=46019||46021<=i&&i<=46047||46049<=i&&i<=46075||46077<=i&&i<=46103||46105<=i&&i<=46131||46133<=i&&i<=46159||46161<=i&&i<=46187||46189<=i&&i<=46215||46217<=i&&i<=46243||46245<=i&&i<=46271||46273<=i&&i<=46299||46301<=i&&i<=46327||46329<=i&&i<=46355||46357<=i&&i<=46383||46385<=i&&i<=46411||46413<=i&&i<=46439||46441<=i&&i<=46467||46469<=i&&i<=46495||46497<=i&&i<=46523||46525<=i&&i<=46551||46553<=i&&i<=46579||46581<=i&&i<=46607||46609<=i&&i<=46635||46637<=i&&i<=46663||46665<=i&&i<=46691||46693<=i&&i<=46719||46721<=i&&i<=46747||46749<=i&&i<=46775||46777<=i&&i<=46803||46805<=i&&i<=46831||46833<=i&&i<=46859||46861<=i&&i<=46887||46889<=i&&i<=46915||46917<=i&&i<=46943||46945<=i&&i<=46971||46973<=i&&i<=46999||47001<=i&&i<=47027||47029<=i&&i<=47055||47057<=i&&i<=47083||47085<=i&&i<=47111||47113<=i&&i<=47139||47141<=i&&i<=47167||47169<=i&&i<=47195||47197<=i&&i<=47223||47225<=i&&i<=47251||47253<=i&&i<=47279||47281<=i&&i<=47307||47309<=i&&i<=47335||47337<=i&&i<=47363||47365<=i&&i<=47391||47393<=i&&i<=47419||47421<=i&&i<=47447||47449<=i&&i<=47475||47477<=i&&i<=47503||47505<=i&&i<=47531||47533<=i&&i<=47559||47561<=i&&i<=47587||47589<=i&&i<=47615||47617<=i&&i<=47643||47645<=i&&i<=47671||47673<=i&&i<=47699||47701<=i&&i<=47727||47729<=i&&i<=47755||47757<=i&&i<=47783||47785<=i&&i<=47811||47813<=i&&i<=47839||47841<=i&&i<=47867||47869<=i&&i<=47895||47897<=i&&i<=47923||47925<=i&&i<=47951||47953<=i&&i<=47979||47981<=i&&i<=48007||48009<=i&&i<=48035||48037<=i&&i<=48063||48065<=i&&i<=48091||48093<=i&&i<=48119||48121<=i&&i<=48147||48149<=i&&i<=48175||48177<=i&&i<=48203||48205<=i&&i<=48231||48233<=i&&i<=48259||48261<=i&&i<=48287||48289<=i&&i<=48315||48317<=i&&i<=48343||48345<=i&&i<=48371||48373<=i&&i<=48399||48401<=i&&i<=48427||48429<=i&&i<=48455||48457<=i&&i<=48483||48485<=i&&i<=48511||48513<=i&&i<=48539||48541<=i&&i<=48567||48569<=i&&i<=48595||48597<=i&&i<=48623||48625<=i&&i<=48651||48653<=i&&i<=48679||48681<=i&&i<=48707||48709<=i&&i<=48735||48737<=i&&i<=48763||48765<=i&&i<=48791||48793<=i&&i<=48819||48821<=i&&i<=48847||48849<=i&&i<=48875||48877<=i&&i<=48903||48905<=i&&i<=48931||48933<=i&&i<=48959||48961<=i&&i<=48987||48989<=i&&i<=49015||49017<=i&&i<=49043||49045<=i&&i<=49071||49073<=i&&i<=49099||49101<=i&&i<=49127||49129<=i&&i<=49155||49157<=i&&i<=49183||49185<=i&&i<=49211||49213<=i&&i<=49239||49241<=i&&i<=49267||49269<=i&&i<=49295||49297<=i&&i<=49323||49325<=i&&i<=49351||49353<=i&&i<=49379||49381<=i&&i<=49407||49409<=i&&i<=49435||49437<=i&&i<=49463||49465<=i&&i<=49491||49493<=i&&i<=49519||49521<=i&&i<=49547||49549<=i&&i<=49575||49577<=i&&i<=49603||49605<=i&&i<=49631||49633<=i&&i<=49659||49661<=i&&i<=49687||49689<=i&&i<=49715||49717<=i&&i<=49743||49745<=i&&i<=49771||49773<=i&&i<=49799||49801<=i&&i<=49827||49829<=i&&i<=49855||49857<=i&&i<=49883||49885<=i&&i<=49911||49913<=i&&i<=49939||49941<=i&&i<=49967||49969<=i&&i<=49995||49997<=i&&i<=50023||50025<=i&&i<=50051||50053<=i&&i<=50079||50081<=i&&i<=50107||50109<=i&&i<=50135||50137<=i&&i<=50163||50165<=i&&i<=50191||50193<=i&&i<=50219||50221<=i&&i<=50247||50249<=i&&i<=50275||50277<=i&&i<=50303||50305<=i&&i<=50331||50333<=i&&i<=50359||50361<=i&&i<=50387||50389<=i&&i<=50415||50417<=i&&i<=50443||50445<=i&&i<=50471||50473<=i&&i<=50499||50501<=i&&i<=50527||50529<=i&&i<=50555||50557<=i&&i<=50583||50585<=i&&i<=50611||50613<=i&&i<=50639||50641<=i&&i<=50667||50669<=i&&i<=50695||50697<=i&&i<=50723||50725<=i&&i<=50751||50753<=i&&i<=50779||50781<=i&&i<=50807||50809<=i&&i<=50835||50837<=i&&i<=50863||50865<=i&&i<=50891||50893<=i&&i<=50919||50921<=i&&i<=50947||50949<=i&&i<=50975||50977<=i&&i<=51003||51005<=i&&i<=51031||51033<=i&&i<=51059||51061<=i&&i<=51087||51089<=i&&i<=51115||51117<=i&&i<=51143||51145<=i&&i<=51171||51173<=i&&i<=51199||51201<=i&&i<=51227||51229<=i&&i<=51255||51257<=i&&i<=51283||51285<=i&&i<=51311||51313<=i&&i<=51339||51341<=i&&i<=51367||51369<=i&&i<=51395||51397<=i&&i<=51423||51425<=i&&i<=51451||51453<=i&&i<=51479||51481<=i&&i<=51507||51509<=i&&i<=51535||51537<=i&&i<=51563||51565<=i&&i<=51591||51593<=i&&i<=51619||51621<=i&&i<=51647||51649<=i&&i<=51675||51677<=i&&i<=51703||51705<=i&&i<=51731||51733<=i&&i<=51759||51761<=i&&i<=51787||51789<=i&&i<=51815||51817<=i&&i<=51843||51845<=i&&i<=51871||51873<=i&&i<=51899||51901<=i&&i<=51927||51929<=i&&i<=51955||51957<=i&&i<=51983||51985<=i&&i<=52011||52013<=i&&i<=52039||52041<=i&&i<=52067||52069<=i&&i<=52095||52097<=i&&i<=52123||52125<=i&&i<=52151||52153<=i&&i<=52179||52181<=i&&i<=52207||52209<=i&&i<=52235||52237<=i&&i<=52263||52265<=i&&i<=52291||52293<=i&&i<=52319||52321<=i&&i<=52347||52349<=i&&i<=52375||52377<=i&&i<=52403||52405<=i&&i<=52431||52433<=i&&i<=52459||52461<=i&&i<=52487||52489<=i&&i<=52515||52517<=i&&i<=52543||52545<=i&&i<=52571||52573<=i&&i<=52599||52601<=i&&i<=52627||52629<=i&&i<=52655||52657<=i&&i<=52683||52685<=i&&i<=52711||52713<=i&&i<=52739||52741<=i&&i<=52767||52769<=i&&i<=52795||52797<=i&&i<=52823||52825<=i&&i<=52851||52853<=i&&i<=52879||52881<=i&&i<=52907||52909<=i&&i<=52935||52937<=i&&i<=52963||52965<=i&&i<=52991||52993<=i&&i<=53019||53021<=i&&i<=53047||53049<=i&&i<=53075||53077<=i&&i<=53103||53105<=i&&i<=53131||53133<=i&&i<=53159||53161<=i&&i<=53187||53189<=i&&i<=53215||53217<=i&&i<=53243||53245<=i&&i<=53271||53273<=i&&i<=53299||53301<=i&&i<=53327||53329<=i&&i<=53355||53357<=i&&i<=53383||53385<=i&&i<=53411||53413<=i&&i<=53439||53441<=i&&i<=53467||53469<=i&&i<=53495||53497<=i&&i<=53523||53525<=i&&i<=53551||53553<=i&&i<=53579||53581<=i&&i<=53607||53609<=i&&i<=53635||53637<=i&&i<=53663||53665<=i&&i<=53691||53693<=i&&i<=53719||53721<=i&&i<=53747||53749<=i&&i<=53775||53777<=i&&i<=53803||53805<=i&&i<=53831||53833<=i&&i<=53859||53861<=i&&i<=53887||53889<=i&&i<=53915||53917<=i&&i<=53943||53945<=i&&i<=53971||53973<=i&&i<=53999||54001<=i&&i<=54027||54029<=i&&i<=54055||54057<=i&&i<=54083||54085<=i&&i<=54111||54113<=i&&i<=54139||54141<=i&&i<=54167||54169<=i&&i<=54195||54197<=i&&i<=54223||54225<=i&&i<=54251||54253<=i&&i<=54279||54281<=i&&i<=54307||54309<=i&&i<=54335||54337<=i&&i<=54363||54365<=i&&i<=54391||54393<=i&&i<=54419||54421<=i&&i<=54447||54449<=i&&i<=54475||54477<=i&&i<=54503||54505<=i&&i<=54531||54533<=i&&i<=54559||54561<=i&&i<=54587||54589<=i&&i<=54615||54617<=i&&i<=54643||54645<=i&&i<=54671||54673<=i&&i<=54699||54701<=i&&i<=54727||54729<=i&&i<=54755||54757<=i&&i<=54783||54785<=i&&i<=54811||54813<=i&&i<=54839||54841<=i&&i<=54867||54869<=i&&i<=54895||54897<=i&&i<=54923||54925<=i&&i<=54951||54953<=i&&i<=54979||54981<=i&&i<=55007||55009<=i&&i<=55035||55037<=i&&i<=55063||55065<=i&&i<=55091||55093<=i&&i<=55119||55121<=i&&i<=55147||55149<=i&&i<=55175||55177<=i&&i<=55203?10:9757==i||9977==i||9994<=i&&i<=9997||127877==i||127938<=i&&i<=127940||127943==i||127946<=i&&i<=127948||128066<=i&&i<=128067||128070<=i&&i<=128080||128110==i||128112<=i&&i<=128120||128124==i||128129<=i&&i<=128131||128133<=i&&i<=128135||128170==i||128372<=i&&i<=128373||128378==i||128400==i||128405<=i&&i<=128406||128581<=i&&i<=128583||128587<=i&&i<=128591||128675==i||128692<=i&&i<=128694||128704==i||128716==i||129304<=i&&i<=129308||129310<=i&&i<=129311||129318==i||129328<=i&&i<=129337||129341<=i&&i<=129342||129489<=i&&i<=129501?A:127995<=i&&i<=127999?14:8205==i?15:9792==i||9794==i||9877<=i&&i<=9878||9992==i||10084==i||127752==i||127806==i||127859==i||127891==i||127908==i||127912==i||127979==i||127981==i||128139==i||128187<=i&&i<=128188||128295==i||128300==i||128488==i||128640==i||128658==i?n:128102<=i&&i<=128105?o:11}return this.nextBreak=function(e,t){if(void 0===t&&(t=0),t<0)return 0;if(t>=e.length-1)return e.length;for(var r,A,n=a(i(e,t)),o=[],c=t+1;c{"use strict";e.exports=(e,t=process.argv)=>{const r=e.startsWith("-")?"":1===e.length?"-":"--",A=t.indexOf(r+e),n=t.indexOf("--");return-1!==A&&(-1===n||A{"use strict";const t=[200,203,204,206,300,301,404,405,410,414,501],r=[200,203,204,300,301,302,303,307,308,404,405,410,414,501],A={date:!0,connection:!0,"keep-alive":!0,"proxy-authenticate":!0,"proxy-authorization":!0,te:!0,trailer:!0,"transfer-encoding":!0,upgrade:!0},n={"content-length":!0,"content-encoding":!0,"transfer-encoding":!0,"content-range":!0};function o(e){const t={};if(!e)return t;const r=e.trim().split(/\s*,\s*/);for(const e of r){const[r,A]=e.split(/\s*=\s*/,2);t[r]=void 0===A||A.replace(/^"|"$/g,"")}return t}function i(e){let t=[];for(const r in e){const A=e[r];t.push(!0===A?r:r+"="+A)}if(t.length)return t.join(", ")}e.exports=class{constructor(e,t,{shared:r,cacheHeuristic:A,immutableMinTimeToLive:n,ignoreCargoCult:s,trustServerDate:a,_fromObject:c}={}){if(c)this._fromObject(c);else{if(!t||!t.headers)throw Error("Response headers missing");this._assertRequestHasHeaders(e),this._responseTime=this.now(),this._isShared=!1!==r,this._trustServerDate=void 0===a||a,this._cacheHeuristic=void 0!==A?A:.1,this._immutableMinTtl=void 0!==n?n:864e5,this._status="status"in t?t.status:200,this._resHeaders=t.headers,this._rescc=o(t.headers["cache-control"]),this._method="method"in e?e.method:"GET",this._url=e.url,this._host=e.headers.host,this._noAuthorization=!e.headers.authorization,this._reqHeaders=t.headers.vary?e.headers:null,this._reqcc=o(e.headers["cache-control"]),s&&"pre-check"in this._rescc&&"post-check"in this._rescc&&(delete this._rescc["pre-check"],delete this._rescc["post-check"],delete this._rescc["no-cache"],delete this._rescc["no-store"],delete this._rescc["must-revalidate"],this._resHeaders=Object.assign({},this._resHeaders,{"cache-control":i(this._rescc)}),delete this._resHeaders.expires,delete this._resHeaders.pragma),!t.headers["cache-control"]&&/no-cache/.test(t.headers.pragma)&&(this._rescc["no-cache"]=!0)}}now(){return Date.now()}storable(){return!(this._reqcc["no-store"]||!("GET"===this._method||"HEAD"===this._method||"POST"===this._method&&this._hasExplicitExpiration())||-1===r.indexOf(this._status)||this._rescc["no-store"]||this._isShared&&this._rescc.private||this._isShared&&!this._noAuthorization&&!this._allowsStoringAuthenticated()||!(this._resHeaders.expires||this._rescc.public||this._rescc["max-age"]||this._rescc["s-maxage"]||-1!==t.indexOf(this._status)))}_hasExplicitExpiration(){return this._isShared&&this._rescc["s-maxage"]||this._rescc["max-age"]||this._resHeaders.expires}_assertRequestHasHeaders(e){if(!e||!e.headers)throw Error("Request headers missing")}satisfiesWithoutRevalidation(e){this._assertRequestHasHeaders(e);const t=o(e.headers["cache-control"]);if(t["no-cache"]||/no-cache/.test(e.headers.pragma))return!1;if(t["max-age"]&&this.age()>t["max-age"])return!1;if(t["min-fresh"]&&this.timeToLive()<1e3*t["min-fresh"])return!1;if(this.stale()){if(!(t["max-stale"]&&!this._rescc["must-revalidate"]&&(!0===t["max-stale"]||t["max-stale"]>this.age()-this.maxAge())))return!1}return this._requestMatches(e,!1)}_requestMatches(e,t){return(!this._url||this._url===e.url)&&this._host===e.headers.host&&(!e.method||this._method===e.method||t&&"HEAD"===e.method)&&this._varyMatches(e)}_allowsStoringAuthenticated(){return this._rescc["must-revalidate"]||this._rescc.public||this._rescc["s-maxage"]}_varyMatches(e){if(!this._resHeaders.vary)return!0;if("*"===this._resHeaders.vary)return!1;const t=this._resHeaders.vary.trim().toLowerCase().split(/\s*,\s*/);for(const r of t)if(e.headers[r]!==this._reqHeaders[r])return!1;return!0}_copyWithoutHopByHopHeaders(e){const t={};for(const r in e)A[r]||(t[r]=e[r]);if(e.connection){const r=e.connection.trim().split(/\s*,\s*/);for(const e of r)delete t[e]}if(t.warning){const e=t.warning.split(/,/).filter(e=>!/^\s*1[0-9][0-9]/.test(e));e.length?t.warning=e.join(",").trim():delete t.warning}return t}responseHeaders(){const e=this._copyWithoutHopByHopHeaders(this._resHeaders),t=this.age();return t>86400&&!this._hasExplicitExpiration()&&this.maxAge()>86400&&(e.warning=(e.warning?e.warning+", ":"")+'113 - "rfc7234 5.5.4"'),e.age=""+Math.round(t),e.date=new Date(this.now()).toUTCString(),e}date(){return this._trustServerDate?this._serverDate():this._responseTime}_serverDate(){const e=Date.parse(this._resHeaders.date);if(isFinite(e)){const t=288e5;if(Math.abs(this._responseTime-e)e&&(e=t)}return e+(this.now()-this._responseTime)/1e3}_ageValue(){const e=parseInt(this._resHeaders.age);return isFinite(e)?e:0}maxAge(){if(!this.storable()||this._rescc["no-cache"])return 0;if(this._isShared&&this._resHeaders["set-cookie"]&&!this._rescc.public&&!this._rescc.immutable)return 0;if("*"===this._resHeaders.vary)return 0;if(this._isShared){if(this._rescc["proxy-revalidate"])return 0;if(this._rescc["s-maxage"])return parseInt(this._rescc["s-maxage"],10)}if(this._rescc["max-age"])return parseInt(this._rescc["max-age"],10);const e=this._rescc.immutable?this._immutableMinTtl:0,t=this._serverDate();if(this._resHeaders.expires){const r=Date.parse(this._resHeaders.expires);return Number.isNaN(r)||rr)return Math.max(e,(t-r)/1e3*this._cacheHeuristic)}return e}timeToLive(){return 1e3*Math.max(0,this.maxAge()-this.age())}stale(){return this.maxAge()<=this.age()}static fromObject(e){return new this(void 0,void 0,{_fromObject:e})}_fromObject(e){if(this._responseTime)throw Error("Reinitialized");if(!e||1!==e.v)throw Error("Invalid serialization");this._responseTime=e.t,this._isShared=e.sh,this._cacheHeuristic=e.ch,this._immutableMinTtl=void 0!==e.imm?e.imm:864e5,this._status=e.st,this._resHeaders=e.resh,this._rescc=e.rescc,this._method=e.m,this._url=e.u,this._host=e.h,this._noAuthorization=e.a,this._reqHeaders=e.reqh,this._reqcc=e.reqcc}toObject(){return{v:1,t:this._responseTime,sh:this._isShared,ch:this._cacheHeuristic,imm:this._immutableMinTtl,st:this._status,resh:this._resHeaders,rescc:this._rescc,m:this._method,u:this._url,h:this._host,a:this._noAuthorization,reqh:this._reqHeaders,reqcc:this._reqcc}}revalidationHeaders(e){this._assertRequestHasHeaders(e);const t=this._copyWithoutHopByHopHeaders(e.headers);if(delete t["if-range"],!this._requestMatches(e,!0)||!this.storable())return delete t["if-none-match"],delete t["if-modified-since"],t;this._resHeaders.etag&&(t["if-none-match"]=t["if-none-match"]?`${t["if-none-match"]}, ${this._resHeaders.etag}`:this._resHeaders.etag);if(t["accept-ranges"]||t["if-match"]||t["if-unmodified-since"]||this._method&&"GET"!=this._method){if(delete t["if-modified-since"],t["if-none-match"]){const e=t["if-none-match"].split(/,/).filter(e=>!/^\s*W\//.test(e));e.length?t["if-none-match"]=e.join(",").trim():delete t["if-none-match"]}}else this._resHeaders["last-modified"]&&!t["if-modified-since"]&&(t["if-modified-since"]=this._resHeaders["last-modified"]);return t}revalidatedPolicy(e,t){if(this._assertRequestHasHeaders(e),!t||!t.headers)throw Error("Response headers missing");let r=!1;if(void 0!==t.status&&304!=t.status?r=!1:t.headers.etag&&!/^\s*W\//.test(t.headers.etag)?r=this._resHeaders.etag&&this._resHeaders.etag.replace(/^\s*W\//,"")===t.headers.etag:this._resHeaders.etag&&t.headers.etag?r=this._resHeaders.etag.replace(/^\s*W\//,"")===t.headers.etag.replace(/^\s*W\//,""):this._resHeaders["last-modified"]?r=this._resHeaders["last-modified"]===t.headers["last-modified"]:this._resHeaders.etag||this._resHeaders["last-modified"]||t.headers.etag||t.headers["last-modified"]||(r=!0),!r)return{policy:new this.constructor(e,t),modified:304!=t.status,matches:!1};const A={};for(const e in this._resHeaders)A[e]=e in t.headers&&!n[e]?t.headers[e]:this._resHeaders[e];const o=Object.assign({},t,{status:this._status,method:this._method,headers:A});return{policy:new this.constructor(e,o,{shared:this._isShared,cacheHeuristic:this._cacheHeuristic,immutableMinTimeToLive:this._immutableMinTtl,trustServerDate:this._trustServerDate}),modified:!1,matches:!0}}}},94935:(e,t,r)=>{"use strict";const A=r(28614),n=r(4016),o=r(97565),i=r(49601),s=Symbol("currentStreamsCount"),a=Symbol("request"),c=Symbol("cachedOriginSet"),g=Symbol("gracefullyClosing"),l=["maxDeflateDynamicTableSize","maxSessionMemory","maxHeaderListPairs","maxOutstandingPings","maxReservedRemoteStreams","maxSendHeaderBlockLength","paddingStrategy","localAddress","path","rejectUnauthorized","minDHSize","ca","cert","clientCertEngine","ciphers","key","pfx","servername","minVersion","maxVersion","secureProtocol","crl","honorCipherOrder","ecdhCurve","dhparam","secureOptions","sessionIdContext"],u=(e,t)=>e.remoteSettings.maxConcurrentStreams>t.remoteSettings.maxConcurrentStreams,h=(e,t)=>{for(const r of e)r[c].lengtht[c].includes(e))&&r[s]+t[s]<=t.remoteSettings.maxConcurrentStreams&&d(r)},p=({agent:e,isFree:t})=>{const r={};for(const A in e.sessions){const n=e.sessions[A].filter(e=>{const r=e[C.kCurrentStreamsCount]{e[g]=!0,0===e[s]&&e.close()};class C extends A{constructor({timeout:e=6e4,maxSessions:t=1/0,maxFreeSessions:r=10,maxCachedTlsSessions:A=100}={}){super(),this.sessions={},this.queue={},this.timeout=e,this.maxSessions=t,this.maxFreeSessions=r,this._freeSessionsCount=0,this._sessionsCount=0,this.settings={enablePush:!1},this.tlsSessionCache=new i({maxSize:A})}static normalizeOrigin(e,t){return"string"==typeof e&&(e=new URL(e)),t&&e.hostname!==t&&(e.hostname=t),e.origin}normalizeOptions(e){let t="";if(e)for(const r of l)e[r]&&(t+=":"+e[r]);return t}_tryToCreateNewSession(e,t){if(!(e in this.queue)||!(t in this.queue[e]))return;const r=this.queue[e][t];this._sessionsCount{Array.isArray(r)?(r=[...r],A()):r=[{resolve:A,reject:n}];const i=this.normalizeOptions(t),l=C.normalizeOrigin(e,t&&t.servername);if(void 0===l){for(const{reject:e}of r)e(new TypeError("The `origin` argument needs to be a string or an URL object"));return}if(i in this.sessions){const e=this.sessions[i];let t,A=-1,n=-1;for(const r of e){const e=r.remoteSettings.maxConcurrentStreams;if(e=e||r[g]||r.destroyed)continue;t||(A=e),o>n&&(t=r,n=o)}}if(t){if(1!==r.length){for(const{reject:e}of r){e(new Error(`Expected the length of listeners to be 1, got ${r.length}.\nPlease report this to https://github.com/szmarczak/http2-wrapper/`))}return}return void r[0].resolve(t)}}if(i in this.queue){if(l in this.queue[i])return this.queue[i][l].listeners.push(...r),void this._tryToCreateNewSession(i,l)}else this.queue[i]={};const p=()=>{i in this.queue&&this.queue[i][l]===f&&(delete this.queue[i][l],0===Object.keys(this.queue[i]).length&&delete this.queue[i])},f=()=>{const A=`${l}:${i}`;let n=!1;try{const C=o.connect(e,{createConnection:this.createConnection,settings:this.settings,session:this.tlsSessionCache.get(A),...t});C[s]=0,C[g]=!1;const I=()=>C[s]{this.tlsSessionCache.set(A,e)}),C.once("error",e=>{for(const{reject:t}of r)t(e);this.tlsSessionCache.delete(A)}),C.setTimeout(this.timeout,()=>{C.destroy()}),C.once("close",()=>{if(n){E&&this._freeSessionsCount--,this._sessionsCount--;const e=this.sessions[i];e.splice(e.indexOf(C),1),0===e.length&&delete this.sessions[i]}else{const e=new Error("Session closed without receiving a SETTINGS frame");e.code="HTTP2WRAPPER_NOSETTINGS";for(const{reject:t}of r)t(e);p()}this._tryToCreateNewSession(i,l)});const B=()=>{if(i in this.queue&&I())for(const e of C[c])if(e in this.queue[i]){const{listeners:t}=this.queue[i][e];for(;0!==t.length&&I();)t.shift().resolve(C);const r=this.queue[i];if(0===r[e].listeners.length&&(delete r[e],0===Object.keys(r).length)){delete this.queue[i];break}if(!I())break}};C.on("origin",()=>{C[c]=C.originSet,I()&&(B(),h(this.sessions[i],C))}),C.once("remoteSettings",()=>{if(C.ref(),C.unref(),this._sessionsCount++,f.destroyed){const e=new Error("Agent has been destroyed");for(const t of r)t.reject(e);C.destroy()}else{C[c]=C.originSet;{const e=this.sessions;if(i in e){const t=e[i];t.splice(((e,t,r)=>{let A=0,n=e.length;for(;A>>1;r(e[o],t)?A=o+1:n=o}return A})(t,C,u),0,C)}else e[i]=[C]}this._freeSessionsCount+=1,n=!0,this.emit("session",C),B(),p(),0===C[s]&&this._freeSessionsCount>this.maxFreeSessions&&C.close(),0!==r.length&&(this.getSession(l,t,r),r.length=0),C.on("remoteSettings",()=>{B(),h(this.sessions[i],C)})}}),C[a]=C.request,C.request=(e,t)=>{if(C[g])throw new Error("The session is gracefully closing. No new streams are allowed.");const r=C[a](e,t);return C.ref(),++C[s],C[s]===C.remoteSettings.maxConcurrentStreams&&this._freeSessionsCount--,r.once("close",()=>{if(E=I(),--C[s],!C.destroyed&&!C.closed&&(((e,t)=>{for(const r of e)t[c].lengthr[c].includes(e))&&t[s]+r[s]<=r.remoteSettings.maxConcurrentStreams&&d(t)})(this.sessions[i],C),I()&&!C.closed)){E||(this._freeSessionsCount++,E=!0);const e=0===C[s];e&&C.unref(),e&&(this._freeSessionsCount>this.maxFreeSessions||C[g])?C.close():(h(this.sessions[i],C),B())}}),r}}catch(e){for(const t of r)t.reject(e);p()}};f.listeners=r,f.completed=!1,f.destroyed=!1,this.queue[i][l]=f,this._tryToCreateNewSession(i,l)})}request(e,t,r,A){return new Promise((n,o)=>{this.getSession(e,t,[{reject:o,resolve:e=>{try{n(e.request(r,A))}catch(e){o(e)}}}])})}createConnection(e,t){return C.connect(e,t)}static connect(e,t){t.ALPNProtocols=["h2"];const r=e.port||443,A=e.hostname||e.host;return void 0===t.servername&&(t.servername=A),n.connect(r,A,t)}closeFreeSessions(){for(const e of Object.values(this.sessions))for(const t of e)0===t[s]&&t.close()}destroy(e){for(const t of Object.values(this.sessions))for(const r of t)r.destroy(e);for(const e of Object.values(this.queue))for(const t of Object.values(e))t.destroyed=!0;this.queue={}}get freeSessions(){return p({agent:this,isFree:!0})}get busySessions(){return p({agent:this,isFree:!1})}}C.kCurrentStreamsCount=s,C.kGracefullyClosing=g,e.exports={Agent:C,globalAgent:new C}},2398:(e,t,r)=>{"use strict";const A=r(98605),n=r(57211),o=r(19476),i=r(49601),s=r(33134),a=r(5209),c=r(50075),g=new i({maxSize:100}),l=new Map,u=(e,t,r)=>{t._httpMessage={shouldKeepAlive:!0};const A=()=>{e.emit("free",t,r)};t.on("free",A);const n=()=>{e.removeSocket(t,r)};t.on("close",n);const o=()=>{e.removeSocket(t,r),t.off("close",n),t.off("free",A),t.off("agentRemove",o)};t.on("agentRemove",o),e.emit("free",t,r)};e.exports=async(e,t,r)=>{if(("string"==typeof e||e instanceof URL)&&(e=c(new URL(e))),"function"==typeof t&&(r=t,t=void 0),t={ALPNProtocols:["h2","http/1.1"],...e,...t,resolveSocket:!0},!Array.isArray(t.ALPNProtocols)||0===t.ALPNProtocols.length)throw new Error("The `ALPNProtocols` option must be an Array with at least one entry");t.protocol=t.protocol||"https:";const i="https:"===t.protocol;t.host=t.hostname||t.host||"localhost",t.session=t.tlsSession,t.servername=t.servername||a(t),t.port=t.port||(i?443:80),t._defaultAgent=i?n.globalAgent:A.globalAgent;const h=t.agent;if(h){if(h.addRequest)throw new Error("The `options.agent` object can contain only `http`, `https` or `http2` properties");t.agent=h[i?"https":"http"]}if(i){if("h2"===await(async e=>{const t=`${e.host}:${e.port}:${e.ALPNProtocols.sort()}`;if(!g.has(t)){if(l.has(t)){return(await l.get(t)).alpnProtocol}const{path:r,agent:A}=e;e.path=e.socketPath;const i=o(e);l.set(t,i);try{const{socket:o,alpnProtocol:s}=await i;if(g.set(t,s),e.path=r,"h2"===s)o.destroy();else{const{globalAgent:t}=n,r=n.Agent.prototype.createConnection;A?A.createConnection===r?u(A,o,e):o.destroy():t.createConnection===r?u(t,o,e):o.destroy()}return l.delete(t),s}catch(e){throw l.delete(t),e}}return g.get(t)})(t))return h&&(t.agent=h.http2),new s(t,r)}return A.request(t,r)},e.exports.protocolCache=g},33134:(e,t,r)=>{"use strict";const A=r(97565),{Writable:n}=r(92413),{Agent:o,globalAgent:i}=r(94935),s=r(53433),a=r(50075),c=r(66192),g=r(50978),{ERR_INVALID_ARG_TYPE:l,ERR_INVALID_PROTOCOL:u,ERR_HTTP_HEADERS_SENT:h,ERR_INVALID_HTTP_TOKEN:p,ERR_HTTP_INVALID_HEADER_VALUE:d,ERR_INVALID_CHAR:C}=r(64080),{HTTP2_HEADER_STATUS:f,HTTP2_HEADER_METHOD:I,HTTP2_HEADER_PATH:E,HTTP2_METHOD_CONNECT:B}=A.constants,y=Symbol("headers"),m=Symbol("origin"),w=Symbol("session"),Q=Symbol("options"),D=Symbol("flushedHeaders"),b=Symbol("jobs"),v=/^[\^`\-\w!#$%&*+.|~]+$/,S=/[^\t\u0020-\u007E\u0080-\u00FF]/;e.exports=class extends n{constructor(e,t,r){super({autoDestroy:!1});const A="string"==typeof e||e instanceof URL;if(A&&(e=a(e instanceof URL?e:new URL(e))),"function"==typeof t||void 0===t?(r=t,t=A?e:{...e}):t={...e,...t},t.h2session)this[w]=t.h2session;else if(!1===t.agent)this.agent=new o({maxFreeSessions:0});else if(void 0===t.agent||null===t.agent)"function"==typeof t.createConnection?(this.agent=new o({maxFreeSessions:0}),this.agent.createConnection=t.createConnection):this.agent=i;else{if("function"!=typeof t.agent.request)throw new l("options.agent",["Agent-like Object","undefined","false"],t.agent);this.agent=t.agent}if(t.protocol&&"https:"!==t.protocol)throw new u(t.protocol,"https:");const n=t.port||t.defaultPort||this.agent&&this.agent.defaultPort||443,s=t.hostname||t.host||"localhost";delete t.hostname,delete t.host,delete t.port;const{timeout:c}=t;if(t.timeout=void 0,this[y]=Object.create(null),this[b]=[],this.socket=null,this.connection=null,this.method=t.method||"GET",this.path=t.path,this.res=null,this.aborted=!1,this.reusedSocket=!1,t.headers)for(const[e,r]of Object.entries(t.headers))this.setHeader(e,r);t.auth&&!("authorization"in this[y])&&(this[y].authorization="Basic "+Buffer.from(t.auth).toString("base64")),t.session=t.tlsSession,t.path=t.socketPath,this[Q]=t,443===n?(this[m]="https://"+s,":authority"in this[y]||(this[y][":authority"]=s)):(this[m]=`https://${s}:${n}`,":authority"in this[y]||(this[y][":authority"]=`${s}:${n}`)),c&&this.setTimeout(c),r&&this.once("response",r),this[D]=!1}get method(){return this[y][I]}set method(e){e&&(this[y][I]=e.toUpperCase())}get path(){return this[y][E]}set path(e){e&&(this[y][E]=e)}get _mustNotHaveABody(){return"GET"===this.method||"HEAD"===this.method||"DELETE"===this.method}_write(e,t,r){if(this._mustNotHaveABody)return void r(new Error("The GET, HEAD and DELETE methods must NOT have a body"));this.flushHeaders();const A=()=>this._request.write(e,t,r);this._request?A():this[b].push(A)}_final(e){if(this.destroyed)return;this.flushHeaders();const t=()=>{this._mustNotHaveABody?e():this._request.end(e)};this._request?t():this[b].push(t)}abort(){this.res&&this.res.complete||(this.aborted||process.nextTick(()=>this.emit("abort")),this.aborted=!0,this.destroy())}_destroy(e,t){this.res&&this.res._dump(),this._request&&this._request.destroy(),t(e)}async flushHeaders(){if(this[D]||this.destroyed)return;this[D]=!0;const e=this.method===B,t=t=>{if(this._request=t,this.destroyed)return void t.destroy();e||c(t,this,["timeout","continue","close","error"]);const r=e=>(...t)=>{this.writable||this.destroyed?this.once("finish",()=>{e(...t)}):e(...t)};t.once("response",r((r,A,n)=>{const o=new s(this.socket,t.readableHighWaterMark);this.res=o,o.req=this,o.statusCode=r[f],o.headers=r,o.rawHeaders=n,o.once("end",()=>{this.aborted?(o.aborted=!0,o.emit("aborted")):(o.complete=!0,o.socket=null,o.connection=null)}),e?(o.upgrade=!0,this.emit("connect",o,t,Buffer.alloc(0))?this.emit("close"):t.destroy()):(t.on("data",e=>{o._dumped||o.push(e)||t.pause()}),t.once("end",()=>{o.push(null)}),this.emit("response",o)||o._dump())})),t.once("headers",r(e=>this.emit("information",{statusCode:e[f]}))),t.once("trailers",r((e,t,r)=>{const{res:A}=this;A.trailers=e,A.rawTrailers=r}));const{socket:A}=t.session;this.socket=A,this.connection=A;for(const e of this[b])e();this.emit("socket",this.socket)};if(this[w])try{t(this[w].request(this[y]))}catch(e){this.emit("error",e)}else{this.reusedSocket=!0;try{t(await this.agent.request(this[m],this[Q],this[y]))}catch(e){this.emit("error",e)}}}getHeader(e){if("string"!=typeof e)throw new l("name","string",e);return this[y][e.toLowerCase()]}get headersSent(){return this[D]}removeHeader(e){if("string"!=typeof e)throw new l("name","string",e);if(this.headersSent)throw new h("remove");delete this[y][e.toLowerCase()]}setHeader(e,t){if(this.headersSent)throw new h("set");if("string"!=typeof e||!v.test(e)&&!g(e))throw new p("Header name",e);if(void 0===t)throw new d(t,e);if(S.test(t))throw new C("header content",e);this[y][e.toLowerCase()]=t}setNoDelay(){}setSocketKeepAlive(){}setTimeout(e,t){const r=()=>this._request.setTimeout(e,t);return this._request?r():this[b].push(r),this}get maxHeadersCount(){if(!this.destroyed&&this._request)return this._request.session.localSettings.maxHeaderListSize}set maxHeadersCount(e){}}},53433:(e,t,r)=>{"use strict";const{Readable:A}=r(92413);e.exports=class extends A{constructor(e,t){super({highWaterMark:t,autoDestroy:!1}),this.statusCode=null,this.statusMessage="",this.httpVersion="2.0",this.httpVersionMajor=2,this.httpVersionMinor=0,this.headers={},this.trailers={},this.req=null,this.aborted=!1,this.complete=!1,this.upgrade=null,this.rawHeaders=[],this.rawTrailers=[],this.socket=e,this.connection=e,this._dumped=!1}_destroy(e){this.req._request.destroy(e)}setTimeout(e,t){return this.req.setTimeout(e,t),this}_dump(){this._dumped||(this._dumped=!0,this.removeAllListeners("data"),this.resume())}_read(){this.req&&this.req._request.resume()}}},92353:(e,t,r)=>{"use strict";const A=r(97565),n=r(94935),o=r(33134),i=r(53433),s=r(2398);e.exports={...A,ClientRequest:o,IncomingMessage:i,...n,request:(e,t,r)=>new o(e,t,r),get:(e,t,r)=>{const A=new o(e,t,r);return A.end(),A},auto:s}},5209:(e,t,r)=>{"use strict";const A=r(11631);e.exports=e=>{let t=e.host;const r=e.headers&&e.headers.host;if(r)if(r.startsWith("[")){t=-1===r.indexOf("]")?r:r.slice(1,-1)}else t=r.split(":",1)[0];return A.isIP(t)?"":t}},64080:e=>{"use strict";const t=(t,r,A)=>{e.exports[r]=class extends t{constructor(...e){super("string"==typeof A?A:A(e)),this.name=`${super.name} [${r}]`,this.code=r}}};t(TypeError,"ERR_INVALID_ARG_TYPE",e=>{const t=e[0].includes(".")?"property":"argument";let r=e[1];const A=Array.isArray(r);return A&&(r=`${r.slice(0,-1).join(", ")} or ${r.slice(-1)}`),`The "${e[0]}" ${t} must be ${A?"one of":"of"} type ${r}. Received ${typeof e[2]}`}),t(TypeError,"ERR_INVALID_PROTOCOL",e=>`Protocol "${e[0]}" not supported. Expected "${e[1]}"`),t(Error,"ERR_HTTP_HEADERS_SENT",e=>`Cannot ${e[0]} headers after they are sent to the client`),t(TypeError,"ERR_INVALID_HTTP_TOKEN",e=>`${e[0]} must be a valid HTTP token [${e[1]}]`),t(TypeError,"ERR_HTTP_INVALID_HEADER_VALUE",e=>`Invalid value "${e[0]} for header "${e[1]}"`),t(TypeError,"ERR_INVALID_CHAR",e=>`Invalid character in ${e[0]} [${e[1]}]`)},50978:e=>{"use strict";e.exports=e=>{switch(e){case":method":case":scheme":case":authority":case":path":return!0;default:return!1}}},66192:e=>{"use strict";e.exports=(e,t,r)=>{for(const A of r)e.on(A,(...e)=>t.emit(A,...e))}},50075:e=>{"use strict";e.exports=e=>{const t={protocol:e.protocol,hostname:"string"==typeof e.hostname&&e.hostname.startsWith("[")?e.hostname.slice(1,-1):e.hostname,host:e.host,hash:e.hash,search:e.search,pathname:e.pathname,href:e.href,path:`${e.pathname||""}${e.search||""}`};return"string"==typeof e.port&&0!==e.port.length&&(t.port=Number(e.port)),(e.username||e.password)&&(t.auth=`${e.username||""}:${e.password||""}`),t}},46458:e=>{function t(e){return Array.isArray(e)?e:[e]}const r=/^\s+$/,A=/^\\!/,n=/^\\#/,o=/\r?\n/g,i=/^\.*\/|^\.+$/,s="undefined"!=typeof Symbol?Symbol.for("node-ignore"):"node-ignore",a=/([0-z])-([0-z])/g,c=[[/\\?\s+$/,e=>0===e.indexOf("\\")?" ":""],[/\\\s/g,()=>" "],[/[\\^$.|*+(){]/g,e=>"\\"+e],[/\[([^\]/]*)($|\])/g,(e,t,r)=>{return"]"===r?`[${A=t,A.replace(a,(e,t,r)=>t.charCodeAt(0)<=r.charCodeAt(0)?e:"")}]`:"\\"+e;var A}],[/(?!\\)\?/g,()=>"[^/]"],[/^\//,()=>"^"],[/\//g,()=>"\\/"],[/^\^*\\\*\\\*\\\//,()=>"^(?:.*\\/)?"],[/(?:[^*])$/,e=>/\/$/.test(e)?e+"$":e+"(?=$|\\/$)"],[/^(?=[^^])/,function(){return/\/(?!$)/.test(this)?"^":"(?:^|\\/)"}],[/\\\/\\\*\\\*(?=\\\/|$)/g,(e,t,r)=>t+6t+"[^\\/]*"],[/(\^|\\\/)?\\\*$/,(e,t)=>(t?t+"[^/]+":"[^/]*")+"(?=$|\\/$)"],[/\\\\\\/g,()=>"\\"]],g=Object.create(null),l=e=>"string"==typeof e;class u{constructor(e,t,r,A){this.origin=e,this.pattern=t,this.negative=r,this.regex=A}}const h=(e,t)=>{const r=e;let o=!1;0===e.indexOf("!")&&(o=!0,e=e.substr(1));const i=((e,t,r)=>{const A=g[e];if(A)return A;const n=c.reduce((t,r)=>t.replace(r[0],r[1].bind(e)),e);return g[e]=r?new RegExp(n,"i"):new RegExp(n)})(e=e.replace(A,"!").replace(n,"#"),0,t);return new u(r,e,o,i)},p=(e,t)=>{throw new t(e)},d=(e,t,r)=>{if(!l(e))return r(`path must be a string, but got \`${t}\``,TypeError);if(!e)return r("path must not be empty",TypeError);if(d.isNotRelative(e)){return r(`path should be a ${"`path.relative()`d"} string, but got "${t}"`,RangeError)}return!0},C=e=>i.test(e);d.isNotRelative=C,d.convert=e=>e;class f{constructor({ignorecase:e=!0}={}){var t,r,A;this._rules=[],this._ignorecase=e,t=this,r=s,A=!0,Object.defineProperty(t,r,{value:A}),this._initCache()}_initCache(){this._ignoreCache=Object.create(null),this._testCache=Object.create(null)}_addPattern(e){if(e&&e[s])return this._rules=this._rules.concat(e._rules),void(this._added=!0);if((e=>e&&l(e)&&!r.test(e)&&0!==e.indexOf("#"))(e)){const t=h(e,this._ignorecase);this._added=!0,this._rules.push(t)}}add(e){return this._added=!1,t(l(e)?(e=>e.split(o))(e):e).forEach(this._addPattern,this),this._added&&this._initCache(),this}addPattern(e){return this.add(e)}_testOne(e,t){let r=!1,A=!1;return this._rules.forEach(n=>{const{negative:o}=n;if(A===o&&r!==A||o&&!r&&!A&&!t)return;n.regex.test(e)&&(r=!o,A=o)}),{ignored:r,unignored:A}}_test(e,t,r,A){const n=e&&d.convert(e);return d(n,e,p),this._t(n,t,r,A)}_t(e,t,r,A){if(e in t)return t[e];if(A||(A=e.split("/")),A.pop(),!A.length)return t[e]=this._testOne(e,r);const n=this._t(A.join("/")+"/",t,r,A);return t[e]=n.ignored?n:this._testOne(e,r)}ignores(e){return this._test(e,this._ignoreCache,!1).ignored}createFilter(){return e=>!this.ignores(e)}filter(e){return t(e).filter(this.createFilter())}test(e){return this._test(e,this._testCache,!0)}}const I=e=>new f(e),E=()=>!1;if(I.isPathValid=e=>d(e&&d.convert(e),e,E),I.default=I,e.exports=I,"undefined"!=typeof process&&(process.env&&process.env.IGNORE_TEST_WIN32||"win32"===process.platform)){const e=e=>/^\\\\\?\\/.test(e)||/["<>|\u0000-\u001F]+/u.test(e)?e:e.replace(/\\/g,"/");d.convert=e;const t=/^[a-z]:\//i;d.isNotRelative=e=>t.test(e)||C(e)}},85870:(e,t,r)=>{try{var A=r(31669);if("function"!=typeof A.inherits)throw"";e.exports=A.inherits}catch(t){e.exports=r(48145)}},48145:e=>{"function"==typeof Object.create?e.exports=function(e,t){e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})}:e.exports=function(e,t){e.super_=t;var r=function(){};r.prototype=t.prototype,e.prototype=new r,e.prototype.constructor=e}},44486:e=>{ +/*! + * is-extglob + * + * Copyright (c) 2014-2016, Jon Schlinkert. + * Licensed under the MIT License. + */ +e.exports=function(e){if("string"!=typeof e||""===e)return!1;for(var t;t=/(\\).|([@?!+*]\(.*\))/g.exec(e);){if(t[2])return!0;e=e.slice(t.index+t[0].length)}return!1}},18193:(e,t,r)=>{ +/*! + * is-glob + * + * Copyright (c) 2014-2017, Jon Schlinkert. + * Released under the MIT License. + */ +var A=r(44486),n={"{":"}","(":")","[":"]"},o=/\\(.)|(^!|\*|[\].+)]\?|\[[^\\\]]+\]|\{[^\\}]+\}|\(\?[:!=][^\\)]+\)|\([^|]+\|[^\\)]+\))/,i=/\\(.)|(^!|[*?{}()[\]]|\(\?)/;e.exports=function(e,t){if("string"!=typeof e||""===e)return!1;if(A(e))return!0;var r,s=o;for(t&&!1===t.strict&&(s=i);r=s.exec(e);){if(r[2])return!0;var a=r.index+r[0].length,c=r[1],g=c?n[c]:null;if(c&&g){var l=e.indexOf(g,a);-1!==l&&(a=l+1)}e=e.slice(a)}return!1}},59235:e=>{"use strict"; +/*! + * is-number + * + * Copyright (c) 2014-present, Jon Schlinkert. + * Released under the MIT License. + */e.exports=function(e){return"number"==typeof e?e-e==0:"string"==typeof e&&""!==e.trim()&&(Number.isFinite?Number.isFinite(+e):isFinite(+e))}},97369:(e,t)=>{var r,A,n,o; +/*! + * is-windows + * + * Copyright © 2015-2018, Jon Schlinkert. + * Released under the MIT License. + */o=function(){"use strict";return function(){return process&&("win32"===process.platform||/^(msys|cygwin)$/.test(process.env.OSTYPE))}},t&&"object"==typeof t?e.exports=o():(A=[],void 0===(n="function"==typeof(r=o)?r.apply(t,A):r)||(e.exports=n))},64151:(e,t,r)=>{var A;r(35747);function n(e,t,r){if("function"==typeof t&&(r=t,t={}),!r){if("function"!=typeof Promise)throw new TypeError("callback not provided");return new Promise((function(r,A){n(e,t||{},(function(e,t){e?A(e):r(t)}))}))}A(e,t||{},(function(e,A){e&&("EACCES"===e.code||t&&t.ignoreErrors)&&(e=null,A=!1),r(e,A)}))}A="win32"===process.platform||global.TESTING_WINDOWS?r(3202):r(2151),e.exports=n,n.sync=function(e,t){try{return A.sync(e,t||{})}catch(e){if(t&&t.ignoreErrors||"EACCES"===e.code)return!1;throw e}}},2151:(e,t,r)=>{e.exports=n,n.sync=function(e,t){return o(A.statSync(e),t)};var A=r(35747);function n(e,t,r){A.stat(e,(function(e,A){r(e,!e&&o(A,t))}))}function o(e,t){return e.isFile()&&function(e,t){var r=e.mode,A=e.uid,n=e.gid,o=void 0!==t.uid?t.uid:process.getuid&&process.getuid(),i=void 0!==t.gid?t.gid:process.getgid&&process.getgid(),s=parseInt("100",8),a=parseInt("010",8),c=parseInt("001",8),g=s|a;return r&c||r&a&&n===i||r&s&&A===o||r&g&&0===o}(e,t)}},3202:(e,t,r)=>{e.exports=o,o.sync=function(e,t){return n(A.statSync(e),e,t)};var A=r(35747);function n(e,t,r){return!(!e.isSymbolicLink()&&!e.isFile())&&function(e,t){var r=void 0!==t.pathExt?t.pathExt:process.env.PATHEXT;if(!r)return!0;if(-1!==(r=r.split(";")).indexOf(""))return!0;for(var A=0;A{"use strict";var A=r(40744);e.exports=A},40744:(e,t,r)=>{"use strict";var A=r(55384),n=r(24129);function o(e){return function(){throw new Error("Function "+e+" is deprecated and cannot be used.")}}e.exports.Type=r(81704),e.exports.Schema=r(8212),e.exports.FAILSAFE_SCHEMA=r(44413),e.exports.JSON_SCHEMA=r(45247),e.exports.CORE_SCHEMA=r(8769),e.exports.DEFAULT_SAFE_SCHEMA=r(65483),e.exports.DEFAULT_FULL_SCHEMA=r(5235),e.exports.load=A.load,e.exports.loadAll=A.loadAll,e.exports.safeLoad=A.safeLoad,e.exports.safeLoadAll=A.safeLoadAll,e.exports.dump=n.dump,e.exports.safeDump=n.safeDump,e.exports.YAMLException=r(17345),e.exports.MINIMAL_SCHEMA=r(44413),e.exports.SAFE_SCHEMA=r(65483),e.exports.DEFAULT_SCHEMA=r(5235),e.exports.scan=o("scan"),e.exports.parse=o("parse"),e.exports.compose=o("compose"),e.exports.addConstructor=o("addConstructor")},28149:e=>{"use strict";function t(e){return null==e}e.exports.isNothing=t,e.exports.isObject=function(e){return"object"==typeof e&&null!==e},e.exports.toArray=function(e){return Array.isArray(e)?e:t(e)?[]:[e]},e.exports.repeat=function(e,t){var r,A="";for(r=0;r{"use strict";var A=r(28149),n=r(17345),o=r(5235),i=r(65483),s=Object.prototype.toString,a=Object.prototype.hasOwnProperty,c={0:"\\0",7:"\\a",8:"\\b",9:"\\t",10:"\\n",11:"\\v",12:"\\f",13:"\\r",27:"\\e",34:'\\"',92:"\\\\",133:"\\N",160:"\\_",8232:"\\L",8233:"\\P"},g=["y","Y","yes","Yes","YES","on","On","ON","n","N","no","No","NO","off","Off","OFF"];function l(e){var t,r,o;if(t=e.toString(16).toUpperCase(),e<=255)r="x",o=2;else if(e<=65535)r="u",o=4;else{if(!(e<=4294967295))throw new n("code point within a string may not be greater than 0xFFFFFFFF");r="U",o=8}return"\\"+r+A.repeat("0",o-t.length)+t}function u(e){this.schema=e.schema||o,this.indent=Math.max(1,e.indent||2),this.noArrayIndent=e.noArrayIndent||!1,this.skipInvalid=e.skipInvalid||!1,this.flowLevel=A.isNothing(e.flowLevel)?-1:e.flowLevel,this.styleMap=function(e,t){var r,A,n,o,i,s,c;if(null===t)return{};for(r={},n=0,o=(A=Object.keys(t)).length;nA&&" "!==e[l+1],l=o);else if(!C(i))return 5;u=u&&f(i)}c=c||g&&o-l-1>A&&" "!==e[l+1]}return a||c?r>9&&I(e)?5:c?4:3:u&&!n(e)?1:2}function B(e,t,r,A){e.dump=function(){if(0===t.length)return"''";if(!e.noCompatMode&&-1!==g.indexOf(t))return"'"+t+"'";var o=e.indent*Math.max(1,r),i=-1===e.lineWidth?-1:Math.max(Math.min(e.lineWidth,40),e.lineWidth-o),s=A||e.flowLevel>-1&&r>=e.flowLevel;switch(E(t,s,e.indent,i,(function(t){return function(e,t){var r,A;for(r=0,A=e.implicitTypes.length;r"+y(t,e.indent)+m(h(function(e,t){var r,A,n=/(\n+)([^\n]*)/g,o=(s=e.indexOf("\n"),s=-1!==s?s:e.length,n.lastIndex=s,w(e.slice(0,s),t)),i="\n"===e[0]||" "===e[0];var s;for(;A=n.exec(e);){var a=A[1],c=A[2];r=" "===c[0],o+=a+(i||r||""===c?"":"\n")+w(c,t),i=r}return o}(t,i),o));case 5:return'"'+function(e){for(var t,r,A,n="",o=0;o=55296&&t<=56319&&(r=e.charCodeAt(o+1))>=56320&&r<=57343?(n+=l(1024*(t-55296)+r-56320+65536),o++):(A=c[t],n+=!A&&C(t)?e[o]:A||l(t));return n}(t)+'"';default:throw new n("impossible error: invalid scalar style")}}()}function y(e,t){var r=I(e)?String(t):"",A="\n"===e[e.length-1];return r+(A&&("\n"===e[e.length-2]||"\n"===e)?"+":A?"":"-")+"\n"}function m(e){return"\n"===e[e.length-1]?e.slice(0,-1):e}function w(e,t){if(""===e||" "===e[0])return e;for(var r,A,n=/ [^ ]/g,o=0,i=0,s=0,a="";r=n.exec(e);)(s=r.index)-o>t&&(A=i>o?i:s,a+="\n"+e.slice(o,A),o=A+1),i=s;return a+="\n",e.length-o>t&&i>o?a+=e.slice(o,i)+"\n"+e.slice(i+1):a+=e.slice(o),a.slice(1)}function Q(e,t,r){var A,o,i,c,g,l;for(i=0,c=(o=r?e.explicitTypes:e.implicitTypes).length;i tag resolver accepts not "'+l+'" style');A=g.represent[l](t,l)}e.dump=A}return!0}return!1}function D(e,t,r,A,o,i){e.tag=null,e.dump=r,Q(e,r,!1)||Q(e,r,!0);var a=s.call(e.dump);A&&(A=e.flowLevel<0||e.flowLevel>t);var c,g,l="[object Object]"===a||"[object Array]"===a;if(l&&(g=-1!==(c=e.duplicates.indexOf(r))),(null!==e.tag&&"?"!==e.tag||g||2!==e.indent&&t>0)&&(o=!1),g&&e.usedDuplicates[c])e.dump="*ref_"+c;else{if(l&&g&&!e.usedDuplicates[c]&&(e.usedDuplicates[c]=!0),"[object Object]"===a)A&&0!==Object.keys(e.dump).length?(!function(e,t,r,A){var o,i,s,a,c,g,l="",u=e.tag,h=Object.keys(r);if(!0===e.sortKeys)h.sort();else if("function"==typeof e.sortKeys)h.sort(e.sortKeys);else if(e.sortKeys)throw new n("sortKeys must be a boolean or a function");for(o=0,i=h.length;o1024)&&(e.dump&&10===e.dump.charCodeAt(0)?g+="?":g+="? "),g+=e.dump,c&&(g+=p(e,t)),D(e,t+1,a,!0,c)&&(e.dump&&10===e.dump.charCodeAt(0)?g+=":":g+=": ",l+=g+=e.dump));e.tag=u,e.dump=l||"{}"}(e,t,e.dump,o),g&&(e.dump="&ref_"+c+e.dump)):(!function(e,t,r){var A,n,o,i,s,a="",c=e.tag,g=Object.keys(r);for(A=0,n=g.length;A1024&&(s+="? "),s+=e.dump+(e.condenseFlow?'"':"")+":"+(e.condenseFlow?"":" "),D(e,t,i,!1,!1)&&(a+=s+=e.dump));e.tag=c,e.dump="{"+a+"}"}(e,t,e.dump),g&&(e.dump="&ref_"+c+" "+e.dump));else if("[object Array]"===a){var u=e.noArrayIndent&&t>0?t-1:t;A&&0!==e.dump.length?(!function(e,t,r,A){var n,o,i="",s=e.tag;for(n=0,o=r.length;n "+e.dump)}return!0}function b(e,t){var r,A,n=[],o=[];for(function e(t,r,A){var n,o,i;if(null!==t&&"object"==typeof t)if(-1!==(o=r.indexOf(t)))-1===A.indexOf(o)&&A.push(o);else if(r.push(t),Array.isArray(t))for(o=0,i=t.length;o{"use strict";function t(e,t){Error.call(this),this.name="YAMLException",this.reason=e,this.mark=t,this.message=(this.reason||"(unknown reason)")+(this.mark?" "+this.mark.toString():""),Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=(new Error).stack||""}t.prototype=Object.create(Error.prototype),t.prototype.constructor=t,t.prototype.toString=function(e){var t=this.name+": ";return t+=this.reason||"(unknown reason)",!e&&this.mark&&(t+=" "+this.mark.toString()),t},e.exports=t},55384:(e,t,r)=>{"use strict";var A=r(28149),n=r(17345),o=r(30399),i=r(65483),s=r(5235),a=Object.prototype.hasOwnProperty,c=/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x84\x86-\x9F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/,g=/[\x85\u2028\u2029]/,l=/[,\[\]\{\}]/,u=/^(?:!|!!|![a-z\-]+!)$/i,h=/^(?:!|[^,\[\]\{\}])(?:%[0-9a-f]{2}|[0-9a-z\-#;\/\?:@&=\+\$,_\.!~\*'\(\)\[\]])*$/i;function p(e){return 10===e||13===e}function d(e){return 9===e||32===e}function C(e){return 9===e||32===e||10===e||13===e}function f(e){return 44===e||91===e||93===e||123===e||125===e}function I(e){var t;return 48<=e&&e<=57?e-48:97<=(t=32|e)&&t<=102?t-97+10:-1}function E(e){return 48===e?"\0":97===e?"":98===e?"\b":116===e||9===e?"\t":110===e?"\n":118===e?"\v":102===e?"\f":114===e?"\r":101===e?"":32===e?" ":34===e?'"':47===e?"/":92===e?"\\":78===e?"…":95===e?" ":76===e?"\u2028":80===e?"\u2029":""}function B(e){return e<=65535?String.fromCharCode(e):String.fromCharCode(55296+(e-65536>>10),56320+(e-65536&1023))}for(var y=new Array(256),m=new Array(256),w=0;w<256;w++)y[w]=E(w)?1:0,m[w]=E(w);function Q(e,t){this.input=e,this.filename=t.filename||null,this.schema=t.schema||s,this.onWarning=t.onWarning||null,this.legacy=t.legacy||!1,this.json=t.json||!1,this.listener=t.listener||null,this.implicitTypes=this.schema.compiledImplicit,this.typeMap=this.schema.compiledTypeMap,this.length=e.length,this.position=0,this.line=0,this.lineStart=0,this.lineIndent=0,this.documents=[]}function D(e,t){return new n(t,new o(e.filename,e.input,e.position,e.line,e.position-e.lineStart))}function b(e,t){throw D(e,t)}function v(e,t){e.onWarning&&e.onWarning.call(null,D(e,t))}var S={YAML:function(e,t,r){var A,n,o;null!==e.version&&b(e,"duplication of %YAML directive"),1!==r.length&&b(e,"YAML directive accepts exactly one argument"),null===(A=/^([0-9]+)\.([0-9]+)$/.exec(r[0]))&&b(e,"ill-formed argument of the YAML directive"),n=parseInt(A[1],10),o=parseInt(A[2],10),1!==n&&b(e,"unacceptable YAML version of the document"),e.version=r[0],e.checkLineBreaks=o<2,1!==o&&2!==o&&v(e,"unsupported YAML version of the document")},TAG:function(e,t,r){var A,n;2!==r.length&&b(e,"TAG directive accepts exactly two arguments"),A=r[0],n=r[1],u.test(A)||b(e,"ill-formed tag handle (first argument) of the TAG directive"),a.call(e.tagMap,A)&&b(e,'there is a previously declared suffix for "'+A+'" tag handle'),h.test(n)||b(e,"ill-formed tag prefix (second argument) of the TAG directive"),e.tagMap[A]=n}};function k(e,t,r,A){var n,o,i,s;if(t1&&(e.result+=A.repeat("\n",t-1))}function L(e,t){var r,A,n=e.tag,o=e.anchor,i=[],s=!1;for(null!==e.anchor&&(e.anchorMap[e.anchor]=i),A=e.input.charCodeAt(e.position);0!==A&&45===A&&C(e.input.charCodeAt(e.position+1));)if(s=!0,e.position++,M(e,!0,-1)&&e.lineIndent<=t)i.push(null),A=e.input.charCodeAt(e.position);else if(r=e.line,U(e,t,3,!1,!0),i.push(e.result),M(e,!0,-1),A=e.input.charCodeAt(e.position),(e.line===r||e.lineIndent>t)&&0!==A)b(e,"bad indentation of a sequence entry");else if(e.lineIndentt?w=1:e.lineIndent===t?w=0:e.lineIndentt?w=1:e.lineIndent===t?w=0:e.lineIndentt)&&(U(e,t,4,!0,n)&&(f?h=e.result:p=e.result),f||(F(e,g,l,u,h,p,o,i),u=h=p=null),M(e,!0,-1),s=e.input.charCodeAt(e.position)),e.lineIndent>t&&0!==s)b(e,"bad indentation of a mapping entry");else if(e.lineIndent=0))break;0===o?b(e,"bad explicit indentation width of a block scalar; it cannot be less than one"):g?b(e,"repeat of an indentation width identifier"):(l=t+o-1,g=!0)}if(d(i)){do{i=e.input.charCodeAt(++e.position)}while(d(i));if(35===i)do{i=e.input.charCodeAt(++e.position)}while(!p(i)&&0!==i)}for(;0!==i;){for(K(e),e.lineIndent=0,i=e.input.charCodeAt(e.position);(!g||e.lineIndentl&&(l=e.lineIndent),p(i))u++;else{if(e.lineIndent0){for(n=i,o=0;n>0;n--)(i=I(s=e.input.charCodeAt(++e.position)))>=0?o=(o<<4)+i:b(e,"expected hexadecimal character");e.result+=B(o),e.position++}else b(e,"unknown escape sequence");r=A=e.position}else p(s)?(k(e,r,A,!0),x(e,M(e,!1,t)),r=A=e.position):e.position===e.lineStart&&R(e)?b(e,"unexpected end of the document within a double quoted scalar"):(e.position++,A=e.position)}b(e,"unexpected end of the stream within a double quoted scalar")}(e,h)?D=!0:!function(e){var t,r,A;if(42!==(A=e.input.charCodeAt(e.position)))return!1;for(A=e.input.charCodeAt(++e.position),t=e.position;0!==A&&!C(A)&&!f(A);)A=e.input.charCodeAt(++e.position);return e.position===t&&b(e,"name of an alias node must contain at least one character"),r=e.input.slice(t,e.position),e.anchorMap.hasOwnProperty(r)||b(e,'unidentified alias "'+r+'"'),e.result=e.anchorMap[r],M(e,!0,-1),!0}(e)?function(e,t,r){var A,n,o,i,s,a,c,g,l=e.kind,u=e.result;if(C(g=e.input.charCodeAt(e.position))||f(g)||35===g||38===g||42===g||33===g||124===g||62===g||39===g||34===g||37===g||64===g||96===g)return!1;if((63===g||45===g)&&(C(A=e.input.charCodeAt(e.position+1))||r&&f(A)))return!1;for(e.kind="scalar",e.result="",n=o=e.position,i=!1;0!==g;){if(58===g){if(C(A=e.input.charCodeAt(e.position+1))||r&&f(A))break}else if(35===g){if(C(e.input.charCodeAt(e.position-1)))break}else{if(e.position===e.lineStart&&R(e)||r&&f(g))break;if(p(g)){if(s=e.line,a=e.lineStart,c=e.lineIndent,M(e,!1,-1),e.lineIndent>=t){i=!0,g=e.input.charCodeAt(e.position);continue}e.position=o,e.line=s,e.lineStart=a,e.lineIndent=c;break}}i&&(k(e,n,o,!1),x(e,e.line-s),n=o=e.position,i=!1),d(g)||(o=e.position+1),g=e.input.charCodeAt(++e.position)}return k(e,n,o,!1),!!e.result||(e.kind=l,e.result=u,!1)}(e,h,1===r)&&(D=!0,null===e.tag&&(e.tag="?")):(D=!0,null===e.tag&&null===e.anchor||b(e,"alias node should not have any properties")),null!==e.anchor&&(e.anchorMap[e.anchor]=e.result)):0===w&&(D=c&&L(e,E))),null!==e.tag&&"!"!==e.tag)if("?"===e.tag){for(g=0,l=e.implicitTypes.length;g tag; it should be "'+u.kind+'", not "'+e.kind+'"'),u.resolve(e.result)?(e.result=u.construct(e.result),null!==e.anchor&&(e.anchorMap[e.anchor]=e.result)):b(e,"cannot resolve a node with !<"+e.tag+"> explicit tag")):b(e,"unknown tag !<"+e.tag+">");return null!==e.listener&&e.listener("close",e),null!==e.tag||null!==e.anchor||D}function T(e){var t,r,A,n,o=e.position,i=!1;for(e.version=null,e.checkLineBreaks=e.legacy,e.tagMap={},e.anchorMap={};0!==(n=e.input.charCodeAt(e.position))&&(M(e,!0,-1),n=e.input.charCodeAt(e.position),!(e.lineIndent>0||37!==n));){for(i=!0,n=e.input.charCodeAt(++e.position),t=e.position;0!==n&&!C(n);)n=e.input.charCodeAt(++e.position);for(A=[],(r=e.input.slice(t,e.position)).length<1&&b(e,"directive name must not be less than one character in length");0!==n;){for(;d(n);)n=e.input.charCodeAt(++e.position);if(35===n){do{n=e.input.charCodeAt(++e.position)}while(0!==n&&!p(n));break}if(p(n))break;for(t=e.position;0!==n&&!C(n);)n=e.input.charCodeAt(++e.position);A.push(e.input.slice(t,e.position))}0!==n&&K(e),a.call(S,r)?S[r](e,r,A):v(e,'unknown document directive "'+r+'"')}M(e,!0,-1),0===e.lineIndent&&45===e.input.charCodeAt(e.position)&&45===e.input.charCodeAt(e.position+1)&&45===e.input.charCodeAt(e.position+2)?(e.position+=3,M(e,!0,-1)):i&&b(e,"directives end mark is expected"),U(e,e.lineIndent-1,4,!1,!0),M(e,!0,-1),e.checkLineBreaks&&g.test(e.input.slice(o,e.position))&&v(e,"non-ASCII line breaks are interpreted as content"),e.documents.push(e.result),e.position===e.lineStart&&R(e)?46===e.input.charCodeAt(e.position)&&(e.position+=3,M(e,!0,-1)):e.position{"use strict";var A=r(28149);function n(e,t,r,A,n){this.name=e,this.buffer=t,this.position=r,this.line=A,this.column=n}n.prototype.getSnippet=function(e,t){var r,n,o,i,s;if(!this.buffer)return null;for(e=e||4,t=t||75,r="",n=this.position;n>0&&-1==="\0\r\n…\u2028\u2029".indexOf(this.buffer.charAt(n-1));)if(n-=1,this.position-n>t/2-1){r=" ... ",n+=5;break}for(o="",i=this.position;it/2-1){o=" ... ",i-=5;break}return s=this.buffer.slice(n,i),A.repeat(" ",e)+r+s+o+"\n"+A.repeat(" ",e+this.position-n+r.length)+"^"},n.prototype.toString=function(e){var t,r="";return this.name&&(r+='in "'+this.name+'" '),r+="at line "+(this.line+1)+", column "+(this.column+1),e||(t=this.getSnippet())&&(r+=":\n"+t),r},e.exports=n},8212:(e,t,r)=>{"use strict";var A=r(28149),n=r(17345),o=r(81704);function i(e,t,r){var A=[];return e.include.forEach((function(e){r=i(e,t,r)})),e[t].forEach((function(e){r.forEach((function(t,r){t.tag===e.tag&&t.kind===e.kind&&A.push(r)})),r.push(e)})),r.filter((function(e,t){return-1===A.indexOf(t)}))}function s(e){this.include=e.include||[],this.implicit=e.implicit||[],this.explicit=e.explicit||[],this.implicit.forEach((function(e){if(e.loadKind&&"scalar"!==e.loadKind)throw new n("There is a non-scalar type in the implicit list of a schema. Implicit resolving of such types is not supported.")})),this.compiledImplicit=i(this,"implicit",[]),this.compiledExplicit=i(this,"explicit",[]),this.compiledTypeMap=function(){var e,t,r={scalar:{},sequence:{},mapping:{},fallback:{}};function A(e){r[e.kind][e.tag]=r.fallback[e.tag]=e}for(e=0,t=arguments.length;e{"use strict";var A=r(8212);e.exports=new A({include:[r(45247)]})},5235:(e,t,r)=>{"use strict";var A=r(8212);e.exports=A.DEFAULT=new A({include:[r(65483)],explicit:[r(61425),r(61872),r(79982)]})},65483:(e,t,r)=>{"use strict";var A=r(8212);e.exports=new A({include:[r(8769)],implicit:[r(83516),r(95441)],explicit:[r(34836),r(6847),r(65173),r(92025)]})},44413:(e,t,r)=>{"use strict";var A=r(8212);e.exports=new A({explicit:[r(19952),r(46557),r(90173)]})},45247:(e,t,r)=>{"use strict";var A=r(8212);e.exports=new A({include:[r(44413)],implicit:[r(40188),r(58357),r(82106),r(71945)]})},81704:(e,t,r)=>{"use strict";var A=r(17345),n=["kind","resolve","construct","instanceOf","predicate","represent","defaultStyle","styleAliases"],o=["scalar","sequence","mapping"];e.exports=function(e,t){var r,i;if(t=t||{},Object.keys(t).forEach((function(t){if(-1===n.indexOf(t))throw new A('Unknown option "'+t+'" is met in definition of "'+e+'" YAML type.')})),this.tag=e,this.kind=t.kind||null,this.resolve=t.resolve||function(){return!0},this.construct=t.construct||function(e){return e},this.instanceOf=t.instanceOf||null,this.predicate=t.predicate||null,this.represent=t.represent||null,this.defaultStyle=t.defaultStyle||null,this.styleAliases=(r=t.styleAliases||null,i={},null!==r&&Object.keys(r).forEach((function(e){r[e].forEach((function(t){i[String(t)]=e}))})),i),-1===o.indexOf(this.kind))throw new A('Unknown kind "'+this.kind+'" is specified for "'+e+'" YAML type.')}},34836:(e,t,r)=>{"use strict";var A;try{A=r(64293).Buffer}catch(e){}var n=r(81704),o="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n\r";e.exports=new n("tag:yaml.org,2002:binary",{kind:"scalar",resolve:function(e){if(null===e)return!1;var t,r,A=0,n=e.length,i=o;for(r=0;r64)){if(t<0)return!1;A+=6}return A%8==0},construct:function(e){var t,r,n=e.replace(/[\r\n=]/g,""),i=n.length,s=o,a=0,c=[];for(t=0;t>16&255),c.push(a>>8&255),c.push(255&a)),a=a<<6|s.indexOf(n.charAt(t));return 0===(r=i%4*6)?(c.push(a>>16&255),c.push(a>>8&255),c.push(255&a)):18===r?(c.push(a>>10&255),c.push(a>>2&255)):12===r&&c.push(a>>4&255),A?A.from?A.from(c):new A(c):c},predicate:function(e){return A&&A.isBuffer(e)},represent:function(e){var t,r,A="",n=0,i=e.length,s=o;for(t=0;t>18&63],A+=s[n>>12&63],A+=s[n>>6&63],A+=s[63&n]),n=(n<<8)+e[t];return 0===(r=i%3)?(A+=s[n>>18&63],A+=s[n>>12&63],A+=s[n>>6&63],A+=s[63&n]):2===r?(A+=s[n>>10&63],A+=s[n>>4&63],A+=s[n<<2&63],A+=s[64]):1===r&&(A+=s[n>>2&63],A+=s[n<<4&63],A+=s[64],A+=s[64]),A}})},58357:(e,t,r)=>{"use strict";var A=r(81704);e.exports=new A("tag:yaml.org,2002:bool",{kind:"scalar",resolve:function(e){if(null===e)return!1;var t=e.length;return 4===t&&("true"===e||"True"===e||"TRUE"===e)||5===t&&("false"===e||"False"===e||"FALSE"===e)},construct:function(e){return"true"===e||"True"===e||"TRUE"===e},predicate:function(e){return"[object Boolean]"===Object.prototype.toString.call(e)},represent:{lowercase:function(e){return e?"true":"false"},uppercase:function(e){return e?"TRUE":"FALSE"},camelcase:function(e){return e?"True":"False"}},defaultStyle:"lowercase"})},71945:(e,t,r)=>{"use strict";var A=r(28149),n=r(81704),o=new RegExp("^(?:[-+]?(?:0|[1-9][0-9_]*)(?:\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?|\\.[0-9_]+(?:[eE][-+]?[0-9]+)?|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]*|[-+]?\\.(?:inf|Inf|INF)|\\.(?:nan|NaN|NAN))$");var i=/^[-+]?[0-9]+e/;e.exports=new n("tag:yaml.org,2002:float",{kind:"scalar",resolve:function(e){return null!==e&&!(!o.test(e)||"_"===e[e.length-1])},construct:function(e){var t,r,A,n;return r="-"===(t=e.replace(/_/g,"").toLowerCase())[0]?-1:1,n=[],"+-".indexOf(t[0])>=0&&(t=t.slice(1)),".inf"===t?1===r?Number.POSITIVE_INFINITY:Number.NEGATIVE_INFINITY:".nan"===t?NaN:t.indexOf(":")>=0?(t.split(":").forEach((function(e){n.unshift(parseFloat(e,10))})),t=0,A=1,n.forEach((function(e){t+=e*A,A*=60})),r*t):r*parseFloat(t,10)},predicate:function(e){return"[object Number]"===Object.prototype.toString.call(e)&&(e%1!=0||A.isNegativeZero(e))},represent:function(e,t){var r;if(isNaN(e))switch(t){case"lowercase":return".nan";case"uppercase":return".NAN";case"camelcase":return".NaN"}else if(Number.POSITIVE_INFINITY===e)switch(t){case"lowercase":return".inf";case"uppercase":return".INF";case"camelcase":return".Inf"}else if(Number.NEGATIVE_INFINITY===e)switch(t){case"lowercase":return"-.inf";case"uppercase":return"-.INF";case"camelcase":return"-.Inf"}else if(A.isNegativeZero(e))return"-0.0";return r=e.toString(10),i.test(r)?r.replace("e",".e"):r},defaultStyle:"lowercase"})},82106:(e,t,r)=>{"use strict";var A=r(28149),n=r(81704);function o(e){return 48<=e&&e<=55}function i(e){return 48<=e&&e<=57}e.exports=new n("tag:yaml.org,2002:int",{kind:"scalar",resolve:function(e){if(null===e)return!1;var t,r,A=e.length,n=0,s=!1;if(!A)return!1;if("-"!==(t=e[n])&&"+"!==t||(t=e[++n]),"0"===t){if(n+1===A)return!0;if("b"===(t=e[++n])){for(n++;n=0?"0b"+e.toString(2):"-0b"+e.toString(2).slice(1)},octal:function(e){return e>=0?"0"+e.toString(8):"-0"+e.toString(8).slice(1)},decimal:function(e){return e.toString(10)},hexadecimal:function(e){return e>=0?"0x"+e.toString(16).toUpperCase():"-0x"+e.toString(16).toUpperCase().slice(1)}},defaultStyle:"decimal",styleAliases:{binary:[2,"bin"],octal:[8,"oct"],decimal:[10,"dec"],hexadecimal:[16,"hex"]}})},79982:(e,t,r)=>{"use strict";var A;try{A=r(Object(function(){var e=new Error("Cannot find module 'esprima'");throw e.code="MODULE_NOT_FOUND",e}()))}catch(e){"undefined"!=typeof window&&(A=window.esprima)}var n=r(81704);e.exports=new n("tag:yaml.org,2002:js/function",{kind:"scalar",resolve:function(e){if(null===e)return!1;try{var t="("+e+")",r=A.parse(t,{range:!0});return"Program"===r.type&&1===r.body.length&&"ExpressionStatement"===r.body[0].type&&("ArrowFunctionExpression"===r.body[0].expression.type||"FunctionExpression"===r.body[0].expression.type)}catch(e){return!1}},construct:function(e){var t,r="("+e+")",n=A.parse(r,{range:!0}),o=[];if("Program"!==n.type||1!==n.body.length||"ExpressionStatement"!==n.body[0].type||"ArrowFunctionExpression"!==n.body[0].expression.type&&"FunctionExpression"!==n.body[0].expression.type)throw new Error("Failed to resolve function");return n.body[0].expression.params.forEach((function(e){o.push(e.name)})),t=n.body[0].expression.body.range,"BlockStatement"===n.body[0].expression.body.type?new Function(o,r.slice(t[0]+1,t[1]-1)):new Function(o,"return "+r.slice(t[0],t[1]))},predicate:function(e){return"[object Function]"===Object.prototype.toString.call(e)},represent:function(e){return e.toString()}})},61872:(e,t,r)=>{"use strict";var A=r(81704);e.exports=new A("tag:yaml.org,2002:js/regexp",{kind:"scalar",resolve:function(e){if(null===e)return!1;if(0===e.length)return!1;var t=e,r=/\/([gim]*)$/.exec(e),A="";if("/"===t[0]){if(r&&(A=r[1]),A.length>3)return!1;if("/"!==t[t.length-A.length-1])return!1}return!0},construct:function(e){var t=e,r=/\/([gim]*)$/.exec(e),A="";return"/"===t[0]&&(r&&(A=r[1]),t=t.slice(1,t.length-A.length-1)),new RegExp(t,A)},predicate:function(e){return"[object RegExp]"===Object.prototype.toString.call(e)},represent:function(e){var t="/"+e.source+"/";return e.global&&(t+="g"),e.multiline&&(t+="m"),e.ignoreCase&&(t+="i"),t}})},61425:(e,t,r)=>{"use strict";var A=r(81704);e.exports=new A("tag:yaml.org,2002:js/undefined",{kind:"scalar",resolve:function(){return!0},construct:function(){},predicate:function(e){return void 0===e},represent:function(){return""}})},90173:(e,t,r)=>{"use strict";var A=r(81704);e.exports=new A("tag:yaml.org,2002:map",{kind:"mapping",construct:function(e){return null!==e?e:{}}})},95441:(e,t,r)=>{"use strict";var A=r(81704);e.exports=new A("tag:yaml.org,2002:merge",{kind:"scalar",resolve:function(e){return"<<"===e||null===e}})},40188:(e,t,r)=>{"use strict";var A=r(81704);e.exports=new A("tag:yaml.org,2002:null",{kind:"scalar",resolve:function(e){if(null===e)return!0;var t=e.length;return 1===t&&"~"===e||4===t&&("null"===e||"Null"===e||"NULL"===e)},construct:function(){return null},predicate:function(e){return null===e},represent:{canonical:function(){return"~"},lowercase:function(){return"null"},uppercase:function(){return"NULL"},camelcase:function(){return"Null"}},defaultStyle:"lowercase"})},6847:(e,t,r)=>{"use strict";var A=r(81704),n=Object.prototype.hasOwnProperty,o=Object.prototype.toString;e.exports=new A("tag:yaml.org,2002:omap",{kind:"sequence",resolve:function(e){if(null===e)return!0;var t,r,A,i,s,a=[],c=e;for(t=0,r=c.length;t{"use strict";var A=r(81704),n=Object.prototype.toString;e.exports=new A("tag:yaml.org,2002:pairs",{kind:"sequence",resolve:function(e){if(null===e)return!0;var t,r,A,o,i,s=e;for(i=new Array(s.length),t=0,r=s.length;t{"use strict";var A=r(81704);e.exports=new A("tag:yaml.org,2002:seq",{kind:"sequence",construct:function(e){return null!==e?e:[]}})},92025:(e,t,r)=>{"use strict";var A=r(81704),n=Object.prototype.hasOwnProperty;e.exports=new A("tag:yaml.org,2002:set",{kind:"mapping",resolve:function(e){if(null===e)return!0;var t,r=e;for(t in r)if(n.call(r,t)&&null!==r[t])return!1;return!0},construct:function(e){return null!==e?e:{}}})},19952:(e,t,r)=>{"use strict";var A=r(81704);e.exports=new A("tag:yaml.org,2002:str",{kind:"scalar",construct:function(e){return null!==e?e:""}})},83516:(e,t,r)=>{"use strict";var A=r(81704),n=new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])$"),o=new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)(?:[Tt]|[ \\t]+)([0-9][0-9]?):([0-9][0-9]):([0-9][0-9])(?:\\.([0-9]*))?(?:[ \\t]*(Z|([-+])([0-9][0-9]?)(?::([0-9][0-9]))?))?$");e.exports=new A("tag:yaml.org,2002:timestamp",{kind:"scalar",resolve:function(e){return null!==e&&(null!==n.exec(e)||null!==o.exec(e))},construct:function(e){var t,r,A,i,s,a,c,g,l=0,u=null;if(null===(t=n.exec(e))&&(t=o.exec(e)),null===t)throw new Error("Date resolve error");if(r=+t[1],A=+t[2]-1,i=+t[3],!t[4])return new Date(Date.UTC(r,A,i));if(s=+t[4],a=+t[5],c=+t[6],t[7]){for(l=t[7].slice(0,3);l.length<3;)l+="0";l=+l}return t[9]&&(u=6e4*(60*+t[10]+ +(t[11]||0)),"-"===t[9]&&(u=-u)),g=new Date(Date.UTC(r,A,i,s,a,c,l)),u&&g.setTime(g.getTime()-u),g},instanceOf:Date,represent:function(e){return e.toISOString()}})},7427:(e,t)=>{t.stringify=function e(t){if(void 0===t)return t;if(t&&Buffer.isBuffer(t))return JSON.stringify(":base64:"+t.toString("base64"));if(t&&t.toJSON&&(t=t.toJSON()),t&&"object"==typeof t){var r="",A=Array.isArray(t);r=A?"[":"{";var n=!0;for(var o in t){var i="function"==typeof t[o]||!A&&void 0===t[o];Object.hasOwnProperty.call(t,o)&&!i&&(n||(r+=","),n=!1,A?null==t[o]?r+="null":r+=e(t[o]):void 0!==t[o]&&(r+=e(o)+":"+e(t[o])))}return r+=A?"]":"}"}return"string"==typeof t?JSON.stringify(/^:/.test(t)?":"+t:t):void 0===t?"null":JSON.stringify(t)},t.parse=function(e){return JSON.parse(e,(function(e,t){return"string"==typeof t?/^:base64:/.test(t)?Buffer.from(t.substring(8),"base64"):/^:/.test(t)?t.substring(1):t:t}))}},72515:(e,t,r)=>{"use strict";const A=r(28614),n=r(7427);e.exports=class extends A{constructor(e,t){if(super(),this.opts=Object.assign({namespace:"keyv",serialize:n.stringify,deserialize:n.parse},"string"==typeof e?{uri:e}:e,t),!this.opts.store){const e=Object.assign({},this.opts);this.opts.store=(e=>{const t={redis:"@keyv/redis",mongodb:"@keyv/mongo",mongo:"@keyv/mongo",sqlite:"@keyv/sqlite",postgresql:"@keyv/postgres",postgres:"@keyv/postgres",mysql:"@keyv/mysql"};if(e.adapter||e.uri){const A=e.adapter||/^[^:]*/.exec(e.uri)[0];return new(r(89112)(t[A]))(e)}return new Map})(e)}"function"==typeof this.opts.store.on&&this.opts.store.on("error",e=>this.emit("error",e)),this.opts.store.namespace=this.opts.namespace}_getKeyPrefix(e){return`${this.opts.namespace}:${e}`}get(e,t){e=this._getKeyPrefix(e);const{store:r}=this.opts;return Promise.resolve().then(()=>r.get(e)).then(e=>"string"==typeof e?this.opts.deserialize(e):e).then(r=>{if(void 0!==r){if(!("number"==typeof r.expires&&Date.now()>r.expires))return t&&t.raw?r:r.value;this.delete(e)}})}set(e,t,r){e=this._getKeyPrefix(e),void 0===r&&(r=this.opts.ttl),0===r&&(r=void 0);const{store:A}=this.opts;return Promise.resolve().then(()=>{const e="number"==typeof r?Date.now()+r:null;return t={value:t,expires:e},this.opts.serialize(t)}).then(t=>A.set(e,t,r)).then(()=>!0)}delete(e){e=this._getKeyPrefix(e);const{store:t}=this.opts;return Promise.resolve().then(()=>t.delete(e))}clear(){const{store:e}=this.opts;return Promise.resolve().then(()=>e.clear())}}},89112:e=>{function t(e){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}t.keys=()=>[],t.resolve=t,t.id=89112,e.exports=t},78962:(e,t,r)=>{var A=r(99513)(r(76169),"DataView");e.exports=A},72574:(e,t,r)=>{var A=r(31713),n=r(86688),o=r(45937),i=r(5017),s=r(79457);function a(e){var t=-1,r=null==e?0:e.length;for(this.clear();++t{var A=r(14620),n=r(73682),o=r(43112),i=r(90640),s=r(9380);function a(e){var t=-1,r=null==e?0:e.length;for(this.clear();++t{var A=r(99513)(r(76169),"Map");e.exports=A},75009:(e,t,r)=>{var A=r(18209),n=r(89706),o=r(43786),i=r(17926),s=r(87345);function a(e){var t=-1,r=null==e?0:e.length;for(this.clear();++t{var A=r(99513)(r(76169),"Promise");e.exports=A},43231:(e,t,r)=>{var A=r(99513)(r(76169),"Set");e.exports=A},46235:(e,t,r)=>{var A=r(75009),n=r(74785),o=r(87760);function i(e){var t=-1,r=null==e?0:e.length;for(this.__data__=new A;++t{var A=r(29197),n=r(35678),o=r(33336),i=r(97163),s=r(43737),a=r(48548);function c(e){var t=this.__data__=new A(e);this.size=t.size}c.prototype.clear=n,c.prototype.delete=o,c.prototype.get=i,c.prototype.has=s,c.prototype.set=a,e.exports=c},69976:(e,t,r)=>{var A=r(76169).Symbol;e.exports=A},2740:(e,t,r)=>{var A=r(76169).Uint8Array;e.exports=A},47063:(e,t,r)=>{var A=r(99513)(r(76169),"WeakMap");e.exports=A},66636:e=>{e.exports=function(e,t,r){switch(r.length){case 0:return e.call(t);case 1:return e.call(t,r[0]);case 2:return e.call(t,r[0],r[1]);case 3:return e.call(t,r[0],r[1],r[2])}return e.apply(t,r)}},33326:e=>{e.exports=function(e,t){for(var r=-1,A=null==e?0:e.length;++r{e.exports=function(e,t){for(var r=-1,A=null==e?0:e.length,n=0,o=[];++r{var A=r(7089),n=r(61771),o=r(82664),i=r(10667),s=r(98041),a=r(32565),c=Object.prototype.hasOwnProperty;e.exports=function(e,t){var r=o(e),g=!r&&n(e),l=!r&&!g&&i(e),u=!r&&!g&&!l&&a(e),h=r||g||l||u,p=h?A(e.length,String):[],d=p.length;for(var C in e)!t&&!c.call(e,C)||h&&("length"==C||l&&("offset"==C||"parent"==C)||u&&("buffer"==C||"byteLength"==C||"byteOffset"==C)||s(C,d))||p.push(C);return p}},60783:e=>{e.exports=function(e,t){for(var r=-1,A=null==e?0:e.length,n=Array(A);++r{e.exports=function(e,t){for(var r=-1,A=t.length,n=e.length;++r{e.exports=function(e,t,r,A){var n=-1,o=null==e?0:e.length;for(A&&o&&(r=e[++n]);++n{e.exports=function(e,t){for(var r=-1,A=null==e?0:e.length;++r{e.exports=function(e){return e.split("")}},11852:e=>{var t=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g;e.exports=function(e){return e.match(t)||[]}},26943:(e,t,r)=>{var A=r(91198),n=r(71074);e.exports=function(e,t,r){(void 0!==r&&!n(e[t],r)||void 0===r&&!(t in e))&&A(e,t,r)}},65759:(e,t,r)=>{var A=r(91198),n=r(71074),o=Object.prototype.hasOwnProperty;e.exports=function(e,t,r){var i=e[t];o.call(e,t)&&n(i,r)&&(void 0!==r||t in e)||A(e,t,r)}},39836:(e,t,r)=>{var A=r(71074);e.exports=function(e,t){for(var r=e.length;r--;)if(A(e[r][0],t))return r;return-1}},28628:(e,t,r)=>{var A=r(75182),n=r(42185);e.exports=function(e,t){return e&&A(t,n(t),e)}},78707:(e,t,r)=>{var A=r(75182),n=r(24887);e.exports=function(e,t){return e&&A(t,n(t),e)}},91198:(e,t,r)=>{var A=r(65);e.exports=function(e,t,r){"__proto__"==t&&A?A(e,t,{configurable:!0,enumerable:!0,value:r,writable:!0}):e[t]=r}},41076:(e,t,r)=>{var A=r(22851),n=r(33326),o=r(65759),i=r(28628),s=r(78707),a=r(64266),c=r(87229),g=r(23105),l=r(60741),u=r(60753),h=r(64420),p=r(79435),d=r(27908),C=r(37836),f=r(88438),I=r(82664),E=r(10667),B=r(13349),y=r(46778),m=r(33931),w=r(42185),Q={};Q["[object Arguments]"]=Q["[object Array]"]=Q["[object ArrayBuffer]"]=Q["[object DataView]"]=Q["[object Boolean]"]=Q["[object Date]"]=Q["[object Float32Array]"]=Q["[object Float64Array]"]=Q["[object Int8Array]"]=Q["[object Int16Array]"]=Q["[object Int32Array]"]=Q["[object Map]"]=Q["[object Number]"]=Q["[object Object]"]=Q["[object RegExp]"]=Q["[object Set]"]=Q["[object String]"]=Q["[object Symbol]"]=Q["[object Uint8Array]"]=Q["[object Uint8ClampedArray]"]=Q["[object Uint16Array]"]=Q["[object Uint32Array]"]=!0,Q["[object Error]"]=Q["[object Function]"]=Q["[object WeakMap]"]=!1,e.exports=function e(t,r,D,b,v,S){var k,N=1&r,F=2&r,K=4&r;if(D&&(k=v?D(t,b,v,S):D(t)),void 0!==k)return k;if(!y(t))return t;var M=I(t);if(M){if(k=d(t),!N)return c(t,k)}else{var R=p(t),x="[object Function]"==R||"[object GeneratorFunction]"==R;if(E(t))return a(t,N);if("[object Object]"==R||"[object Arguments]"==R||x&&!v){if(k=F||x?{}:f(t),!N)return F?l(t,s(k,t)):g(t,i(k,t))}else{if(!Q[R])return v?t:{};k=C(t,R,N)}}S||(S=new A);var L=S.get(t);if(L)return L;S.set(t,k),m(t)?t.forEach((function(A){k.add(e(A,r,D,A,t,S))})):B(t)&&t.forEach((function(A,n){k.set(n,e(A,r,D,n,t,S))}));var P=K?F?h:u:F?keysIn:w,O=M?void 0:P(t);return n(O||t,(function(A,n){O&&(A=t[n=A]),o(k,n,e(A,r,D,n,t,S))})),k}},15178:(e,t,r)=>{var A=r(46778),n=Object.create,o=function(){function e(){}return function(t){if(!A(t))return{};if(n)return n(t);e.prototype=t;var r=new e;return e.prototype=void 0,r}}();e.exports=o},93274:(e,t,r)=>{var A=r(40945),n=r(958);e.exports=function e(t,r,o,i,s){var a=-1,c=t.length;for(o||(o=n),s||(s=[]);++a0&&o(g)?r>1?e(g,r-1,o,i,s):A(s,g):i||(s[s.length]=g)}return s}},31689:(e,t,r)=>{var A=r(59907)();e.exports=A},62164:(e,t,r)=>{var A=r(31689),n=r(42185);e.exports=function(e,t){return e&&A(e,t,n)}},84173:(e,t,r)=>{var A=r(56725),n=r(49874);e.exports=function(e,t){for(var r=0,o=(t=A(t,e)).length;null!=e&&r{var A=r(40945),n=r(82664);e.exports=function(e,t,r){var o=t(e);return n(e)?o:A(o,r(e))}},52502:(e,t,r)=>{var A=r(69976),n=r(2854),o=r(87427),i=A?A.toStringTag:void 0;e.exports=function(e){return null==e?void 0===e?"[object Undefined]":"[object Null]":i&&i in Object(e)?n(e):o(e)}},95325:e=>{var t=Object.prototype.hasOwnProperty;e.exports=function(e,r){return null!=e&&t.call(e,r)}},3881:e=>{e.exports=function(e,t){return null!=e&&t in Object(e)}},76357:(e,t,r)=>{var A=r(52502),n=r(38496);e.exports=function(e){return n(e)&&"[object Arguments]"==A(e)}},74195:(e,t,r)=>{var A=r(48957),n=r(38496);e.exports=function e(t,r,o,i,s){return t===r||(null==t||null==r||!n(t)&&!n(r)?t!=t&&r!=r:A(t,r,o,i,e,s))}},48957:(e,t,r)=>{var A=r(22851),n=r(75500),o=r(28475),i=r(50245),s=r(79435),a=r(82664),c=r(10667),g=r(32565),l="[object Object]",u=Object.prototype.hasOwnProperty;e.exports=function(e,t,r,h,p,d){var C=a(e),f=a(t),I=C?"[object Array]":s(e),E=f?"[object Array]":s(t),B=(I="[object Arguments]"==I?l:I)==l,y=(E="[object Arguments]"==E?l:E)==l,m=I==E;if(m&&c(e)){if(!c(t))return!1;C=!0,B=!1}if(m&&!B)return d||(d=new A),C||g(e)?n(e,t,r,h,p,d):o(e,t,I,r,h,p,d);if(!(1&r)){var w=B&&u.call(e,"__wrapped__"),Q=y&&u.call(t,"__wrapped__");if(w||Q){var D=w?e.value():e,b=Q?t.value():t;return d||(d=new A),p(D,b,r,h,d)}}return!!m&&(d||(d=new A),i(e,t,r,h,p,d))}},55994:(e,t,r)=>{var A=r(79435),n=r(38496);e.exports=function(e){return n(e)&&"[object Map]"==A(e)}},66470:(e,t,r)=>{var A=r(22851),n=r(74195);e.exports=function(e,t,r,o){var i=r.length,s=i,a=!o;if(null==e)return!s;for(e=Object(e);i--;){var c=r[i];if(a&&c[2]?c[1]!==e[c[0]]:!(c[0]in e))return!1}for(;++i{var A=r(92533),n=r(15061),o=r(46778),i=r(76384),s=/^\[object .+?Constructor\]$/,a=Function.prototype,c=Object.prototype,g=a.toString,l=c.hasOwnProperty,u=RegExp("^"+g.call(l).replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");e.exports=function(e){return!(!o(e)||n(e))&&(A(e)?u:s).test(i(e))}},28612:(e,t,r)=>{var A=r(79435),n=r(38496);e.exports=function(e){return n(e)&&"[object Set]"==A(e)}},98998:(e,t,r)=>{var A=r(52502),n=r(46369),o=r(38496),i={};i["[object Float32Array]"]=i["[object Float64Array]"]=i["[object Int8Array]"]=i["[object Int16Array]"]=i["[object Int32Array]"]=i["[object Uint8Array]"]=i["[object Uint8ClampedArray]"]=i["[object Uint16Array]"]=i["[object Uint32Array]"]=!0,i["[object Arguments]"]=i["[object Array]"]=i["[object ArrayBuffer]"]=i["[object Boolean]"]=i["[object DataView]"]=i["[object Date]"]=i["[object Error]"]=i["[object Function]"]=i["[object Map]"]=i["[object Number]"]=i["[object Object]"]=i["[object RegExp]"]=i["[object Set]"]=i["[object String]"]=i["[object WeakMap]"]=!1,e.exports=function(e){return o(e)&&n(e.length)&&!!i[A(e)]}},42208:(e,t,r)=>{var A=r(96962),n=r(90348),o=r(61977),i=r(82664),s=r(7430);e.exports=function(e){return"function"==typeof e?e:null==e?o:"object"==typeof e?i(e)?n(e[0],e[1]):A(e):s(e)}},50994:(e,t,r)=>{var A=r(89513),n=r(60657),o=Object.prototype.hasOwnProperty;e.exports=function(e){if(!A(e))return n(e);var t=[];for(var r in Object(e))o.call(e,r)&&"constructor"!=r&&t.push(r);return t}},8372:(e,t,r)=>{var A=r(46778),n=r(89513),o=r(95632),i=Object.prototype.hasOwnProperty;e.exports=function(e){if(!A(e))return o(e);var t=n(e),r=[];for(var s in e)("constructor"!=s||!t&&i.call(e,s))&&r.push(s);return r}},96962:(e,t,r)=>{var A=r(66470),n=r(98705),o=r(12757);e.exports=function(e){var t=n(e);return 1==t.length&&t[0][2]?o(t[0][0],t[0][1]):function(r){return r===e||A(r,e,t)}}},90348:(e,t,r)=>{var A=r(74195),n=r(44674),o=r(34878),i=r(70474),s=r(20925),a=r(12757),c=r(49874);e.exports=function(e,t){return i(e)&&s(t)?a(c(e),t):function(r){var i=n(r,e);return void 0===i&&i===t?o(r,e):A(t,i,3)}}},51264:(e,t,r)=>{var A=r(22851),n=r(26943),o=r(31689),i=r(16834),s=r(46778),a=r(24887),c=r(36883);e.exports=function e(t,r,g,l,u){t!==r&&o(r,(function(o,a){if(u||(u=new A),s(o))i(t,r,a,g,e,l,u);else{var h=l?l(c(t,a),o,a+"",t,r,u):void 0;void 0===h&&(h=o),n(t,a,h)}}),a)}},16834:(e,t,r)=>{var A=r(26943),n=r(64266),o=r(58042),i=r(87229),s=r(88438),a=r(61771),c=r(82664),g=r(16064),l=r(10667),u=r(92533),h=r(46778),p=r(11672),d=r(32565),C=r(36883),f=r(36506);e.exports=function(e,t,r,I,E,B,y){var m=C(e,r),w=C(t,r),Q=y.get(w);if(Q)A(e,r,Q);else{var D=B?B(m,w,r+"",e,t,y):void 0,b=void 0===D;if(b){var v=c(w),S=!v&&l(w),k=!v&&!S&&d(w);D=w,v||S||k?c(m)?D=m:g(m)?D=i(m):S?(b=!1,D=n(w,!0)):k?(b=!1,D=o(w,!0)):D=[]:p(w)||a(w)?(D=m,a(m)?D=f(m):h(m)&&!u(m)||(D=s(w))):b=!1}b&&(y.set(w,D),E(D,w,I,B,y),y.delete(w)),A(e,r,D)}}},72204:(e,t,r)=>{var A=r(35314),n=r(34878);e.exports=function(e,t){return A(e,t,(function(t,r){return n(e,r)}))}},35314:(e,t,r)=>{var A=r(84173),n=r(10624),o=r(56725);e.exports=function(e,t,r){for(var i=-1,s=t.length,a={};++i{e.exports=function(e){return function(t){return null==t?void 0:t[e]}}},43018:(e,t,r)=>{var A=r(84173);e.exports=function(e){return function(t){return A(t,e)}}},51587:e=>{e.exports=function(e){return function(t){return null==e?void 0:e[t]}}},30383:(e,t,r)=>{var A=r(61977),n=r(44322),o=r(3111);e.exports=function(e,t){return o(n(e,t,A),e+"")}},10624:(e,t,r)=>{var A=r(65759),n=r(56725),o=r(98041),i=r(46778),s=r(49874);e.exports=function(e,t,r,a){if(!i(e))return e;for(var c=-1,g=(t=n(t,e)).length,l=g-1,u=e;null!=u&&++c{var A=r(4967),n=r(65),o=r(61977),i=n?function(e,t){return n(e,"toString",{configurable:!0,enumerable:!1,value:A(t),writable:!0})}:o;e.exports=i},27708:e=>{e.exports=function(e,t,r){var A=-1,n=e.length;t<0&&(t=-t>n?0:n+t),(r=r>n?n:r)<0&&(r+=n),n=t>r?0:r-t>>>0,t>>>=0;for(var o=Array(n);++A{e.exports=function(e,t){for(var r=-1,A=Array(e);++r{var A=r(69976),n=r(60783),o=r(82664),i=r(65558),s=A?A.prototype:void 0,a=s?s.toString:void 0;e.exports=function e(t){if("string"==typeof t)return t;if(o(t))return n(t,e)+"";if(i(t))return a?a.call(t):"";var r=t+"";return"0"==r&&1/t==-1/0?"-0":r}},73635:e=>{e.exports=function(e){return function(t){return e(t)}}},18290:(e,t,r)=>{var A=r(60783);e.exports=function(e,t){return A(t,(function(t){return e[t]}))}},93022:e=>{e.exports=function(e,t){return e.has(t)}},56725:(e,t,r)=>{var A=r(82664),n=r(70474),o=r(8689),i=r(33580);e.exports=function(e,t){return A(e)?e:n(e,t)?[e]:o(i(e))}},92568:(e,t,r)=>{var A=r(27708);e.exports=function(e,t,r){var n=e.length;return r=void 0===r?n:r,!t&&r>=n?e:A(e,t,r)}},76255:(e,t,r)=>{var A=r(2740);e.exports=function(e){var t=new e.constructor(e.byteLength);return new A(t).set(new A(e)),t}},64266:(e,t,r)=>{e=r.nmd(e);var A=r(76169),n=t&&!t.nodeType&&t,o=n&&e&&!e.nodeType&&e,i=o&&o.exports===n?A.Buffer:void 0,s=i?i.allocUnsafe:void 0;e.exports=function(e,t){if(t)return e.slice();var r=e.length,A=s?s(r):new e.constructor(r);return e.copy(A),A}},63749:(e,t,r)=>{var A=r(76255);e.exports=function(e,t){var r=t?A(e.buffer):e.buffer;return new e.constructor(r,e.byteOffset,e.byteLength)}},41705:e=>{var t=/\w*$/;e.exports=function(e){var r=new e.constructor(e.source,t.exec(e));return r.lastIndex=e.lastIndex,r}},25791:(e,t,r)=>{var A=r(69976),n=A?A.prototype:void 0,o=n?n.valueOf:void 0;e.exports=function(e){return o?Object(o.call(e)):{}}},58042:(e,t,r)=>{var A=r(76255);e.exports=function(e,t){var r=t?A(e.buffer):e.buffer;return new e.constructor(r,e.byteOffset,e.length)}},87229:e=>{e.exports=function(e,t){var r=-1,A=e.length;for(t||(t=Array(A));++r{var A=r(65759),n=r(91198);e.exports=function(e,t,r,o){var i=!r;r||(r={});for(var s=-1,a=t.length;++s{var A=r(75182),n=r(68727);e.exports=function(e,t){return A(e,n(e),t)}},60741:(e,t,r)=>{var A=r(75182),n=r(35368);e.exports=function(e,t){return A(e,n(e),t)}},14429:(e,t,r)=>{var A=r(76169)["__core-js_shared__"];e.exports=A},27913:(e,t,r)=>{var A=r(30383),n=r(33193);e.exports=function(e){return A((function(t,r){var A=-1,o=r.length,i=o>1?r[o-1]:void 0,s=o>2?r[2]:void 0;for(i=e.length>3&&"function"==typeof i?(o--,i):void 0,s&&n(r[0],r[1],s)&&(i=o<3?void 0:i,o=1),t=Object(t);++A{e.exports=function(e){return function(t,r,A){for(var n=-1,o=Object(t),i=A(t),s=i.length;s--;){var a=i[e?s:++n];if(!1===r(o[a],a,o))break}return t}}},56989:(e,t,r)=>{var A=r(92568),n=r(93024),o=r(30475),i=r(33580);e.exports=function(e){return function(t){t=i(t);var r=n(t)?o(t):void 0,s=r?r[0]:t.charAt(0),a=r?A(r,1).join(""):t.slice(1);return s[e]()+a}}},30369:(e,t,r)=>{var A=r(66054),n=r(68968),o=r(97684),i=RegExp("['’]","g");e.exports=function(e){return function(t){return A(o(n(t).replace(i,"")),e,"")}}},69922:(e,t,r)=>{var A=r(51587)({À:"A",Á:"A",Â:"A",Ã:"A",Ä:"A",Å:"A",à:"a",á:"a",â:"a",ã:"a",ä:"a",å:"a",Ç:"C",ç:"c",Ð:"D",ð:"d",È:"E",É:"E",Ê:"E",Ë:"E",è:"e",é:"e",ê:"e",ë:"e",Ì:"I",Í:"I",Î:"I",Ï:"I",ì:"i",í:"i",î:"i",ï:"i",Ñ:"N",ñ:"n",Ò:"O",Ó:"O",Ô:"O",Õ:"O",Ö:"O",Ø:"O",ò:"o",ó:"o",ô:"o",õ:"o",ö:"o",ø:"o",Ù:"U",Ú:"U",Û:"U",Ü:"U",ù:"u",ú:"u",û:"u",ü:"u",Ý:"Y",ý:"y",ÿ:"y",Æ:"Ae",æ:"ae",Þ:"Th",þ:"th",ß:"ss",Ā:"A",Ă:"A",Ą:"A",ā:"a",ă:"a",ą:"a",Ć:"C",Ĉ:"C",Ċ:"C",Č:"C",ć:"c",ĉ:"c",ċ:"c",č:"c",Ď:"D",Đ:"D",ď:"d",đ:"d",Ē:"E",Ĕ:"E",Ė:"E",Ę:"E",Ě:"E",ē:"e",ĕ:"e",ė:"e",ę:"e",ě:"e",Ĝ:"G",Ğ:"G",Ġ:"G",Ģ:"G",ĝ:"g",ğ:"g",ġ:"g",ģ:"g",Ĥ:"H",Ħ:"H",ĥ:"h",ħ:"h",Ĩ:"I",Ī:"I",Ĭ:"I",Į:"I",İ:"I",ĩ:"i",ī:"i",ĭ:"i",į:"i",ı:"i",Ĵ:"J",ĵ:"j",Ķ:"K",ķ:"k",ĸ:"k",Ĺ:"L",Ļ:"L",Ľ:"L",Ŀ:"L",Ł:"L",ĺ:"l",ļ:"l",ľ:"l",ŀ:"l",ł:"l",Ń:"N",Ņ:"N",Ň:"N",Ŋ:"N",ń:"n",ņ:"n",ň:"n",ŋ:"n",Ō:"O",Ŏ:"O",Ő:"O",ō:"o",ŏ:"o",ő:"o",Ŕ:"R",Ŗ:"R",Ř:"R",ŕ:"r",ŗ:"r",ř:"r",Ś:"S",Ŝ:"S",Ş:"S",Š:"S",ś:"s",ŝ:"s",ş:"s",š:"s",Ţ:"T",Ť:"T",Ŧ:"T",ţ:"t",ť:"t",ŧ:"t",Ũ:"U",Ū:"U",Ŭ:"U",Ů:"U",Ű:"U",Ų:"U",ũ:"u",ū:"u",ŭ:"u",ů:"u",ű:"u",ų:"u",Ŵ:"W",ŵ:"w",Ŷ:"Y",ŷ:"y",Ÿ:"Y",Ź:"Z",Ż:"Z",Ž:"Z",ź:"z",ż:"z",ž:"z",IJ:"IJ",ij:"ij",Œ:"Oe",œ:"oe",ʼn:"'n",ſ:"s"});e.exports=A},65:(e,t,r)=>{var A=r(99513),n=function(){try{var e=A(Object,"defineProperty");return e({},"",{}),e}catch(e){}}();e.exports=n},75500:(e,t,r)=>{var A=r(46235),n=r(17765),o=r(93022);e.exports=function(e,t,r,i,s,a){var c=1&r,g=e.length,l=t.length;if(g!=l&&!(c&&l>g))return!1;var u=a.get(e);if(u&&a.get(t))return u==t;var h=-1,p=!0,d=2&r?new A:void 0;for(a.set(e,t),a.set(t,e);++h{var A=r(69976),n=r(2740),o=r(71074),i=r(75500),s=r(7877),a=r(7442),c=A?A.prototype:void 0,g=c?c.valueOf:void 0;e.exports=function(e,t,r,A,c,l,u){switch(r){case"[object DataView]":if(e.byteLength!=t.byteLength||e.byteOffset!=t.byteOffset)return!1;e=e.buffer,t=t.buffer;case"[object ArrayBuffer]":return!(e.byteLength!=t.byteLength||!l(new n(e),new n(t)));case"[object Boolean]":case"[object Date]":case"[object Number]":return o(+e,+t);case"[object Error]":return e.name==t.name&&e.message==t.message;case"[object RegExp]":case"[object String]":return e==t+"";case"[object Map]":var h=s;case"[object Set]":var p=1&A;if(h||(h=a),e.size!=t.size&&!p)return!1;var d=u.get(e);if(d)return d==t;A|=2,u.set(e,t);var C=i(h(e),h(t),A,c,l,u);return u.delete(e),C;case"[object Symbol]":if(g)return g.call(e)==g.call(t)}return!1}},50245:(e,t,r)=>{var A=r(60753),n=Object.prototype.hasOwnProperty;e.exports=function(e,t,r,o,i,s){var a=1&r,c=A(e),g=c.length;if(g!=A(t).length&&!a)return!1;for(var l=g;l--;){var u=c[l];if(!(a?u in t:n.call(t,u)))return!1}var h=s.get(e);if(h&&s.get(t))return h==t;var p=!0;s.set(e,t),s.set(t,e);for(var d=a;++l{var A=r(54690),n=r(44322),o=r(3111);e.exports=function(e){return o(n(e,void 0,A),e+"")}},68399:e=>{var t="object"==typeof global&&global&&global.Object===Object&&global;e.exports=t},60753:(e,t,r)=>{var A=r(40104),n=r(68727),o=r(42185);e.exports=function(e){return A(e,o,n)}},64420:(e,t,r)=>{var A=r(40104),n=r(35368),o=r(24887);e.exports=function(e){return A(e,o,n)}},59253:(e,t,r)=>{var A=r(69448);e.exports=function(e,t){var r=e.__data__;return A(t)?r["string"==typeof t?"string":"hash"]:r.map}},98705:(e,t,r)=>{var A=r(20925),n=r(42185);e.exports=function(e){for(var t=n(e),r=t.length;r--;){var o=t[r],i=e[o];t[r]=[o,i,A(i)]}return t}},99513:(e,t,r)=>{var A=r(91686),n=r(98054);e.exports=function(e,t){var r=n(e,t);return A(r)?r:void 0}},41181:(e,t,r)=>{var A=r(64309)(Object.getPrototypeOf,Object);e.exports=A},2854:(e,t,r)=>{var A=r(69976),n=Object.prototype,o=n.hasOwnProperty,i=n.toString,s=A?A.toStringTag:void 0;e.exports=function(e){var t=o.call(e,s),r=e[s];try{e[s]=void 0;var A=!0}catch(e){}var n=i.call(e);return A&&(t?e[s]=r:delete e[s]),n}},68727:(e,t,r)=>{var A=r(9073),n=r(62162),o=Object.prototype.propertyIsEnumerable,i=Object.getOwnPropertySymbols,s=i?function(e){return null==e?[]:(e=Object(e),A(i(e),(function(t){return o.call(e,t)})))}:n;e.exports=s},35368:(e,t,r)=>{var A=r(40945),n=r(41181),o=r(68727),i=r(62162),s=Object.getOwnPropertySymbols?function(e){for(var t=[];e;)A(t,o(e)),e=n(e);return t}:i;e.exports=s},79435:(e,t,r)=>{var A=r(78962),n=r(63603),o=r(5825),i=r(43231),s=r(47063),a=r(52502),c=r(76384),g=c(A),l=c(n),u=c(o),h=c(i),p=c(s),d=a;(A&&"[object DataView]"!=d(new A(new ArrayBuffer(1)))||n&&"[object Map]"!=d(new n)||o&&"[object Promise]"!=d(o.resolve())||i&&"[object Set]"!=d(new i)||s&&"[object WeakMap]"!=d(new s))&&(d=function(e){var t=a(e),r="[object Object]"==t?e.constructor:void 0,A=r?c(r):"";if(A)switch(A){case g:return"[object DataView]";case l:return"[object Map]";case u:return"[object Promise]";case h:return"[object Set]";case p:return"[object WeakMap]"}return t}),e.exports=d},98054:e=>{e.exports=function(e,t){return null==e?void 0:e[t]}},71507:(e,t,r)=>{var A=r(56725),n=r(61771),o=r(82664),i=r(98041),s=r(46369),a=r(49874);e.exports=function(e,t,r){for(var c=-1,g=(t=A(t,e)).length,l=!1;++c{var t=RegExp("[\\u200d\\ud800-\\udfff\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff\\ufe0e\\ufe0f]");e.exports=function(e){return t.test(e)}},60466:e=>{var t=/[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/;e.exports=function(e){return t.test(e)}},31713:(e,t,r)=>{var A=r(52437);e.exports=function(){this.__data__=A?A(null):{},this.size=0}},86688:e=>{e.exports=function(e){var t=this.has(e)&&delete this.__data__[e];return this.size-=t?1:0,t}},45937:(e,t,r)=>{var A=r(52437),n=Object.prototype.hasOwnProperty;e.exports=function(e){var t=this.__data__;if(A){var r=t[e];return"__lodash_hash_undefined__"===r?void 0:r}return n.call(t,e)?t[e]:void 0}},5017:(e,t,r)=>{var A=r(52437),n=Object.prototype.hasOwnProperty;e.exports=function(e){var t=this.__data__;return A?void 0!==t[e]:n.call(t,e)}},79457:(e,t,r)=>{var A=r(52437);e.exports=function(e,t){var r=this.__data__;return this.size+=this.has(e)?0:1,r[e]=A&&void 0===t?"__lodash_hash_undefined__":t,this}},27908:e=>{var t=Object.prototype.hasOwnProperty;e.exports=function(e){var r=e.length,A=new e.constructor(r);return r&&"string"==typeof e[0]&&t.call(e,"index")&&(A.index=e.index,A.input=e.input),A}},37836:(e,t,r)=>{var A=r(76255),n=r(63749),o=r(41705),i=r(25791),s=r(58042);e.exports=function(e,t,r){var a=e.constructor;switch(t){case"[object ArrayBuffer]":return A(e);case"[object Boolean]":case"[object Date]":return new a(+e);case"[object DataView]":return n(e,r);case"[object Float32Array]":case"[object Float64Array]":case"[object Int8Array]":case"[object Int16Array]":case"[object Int32Array]":case"[object Uint8Array]":case"[object Uint8ClampedArray]":case"[object Uint16Array]":case"[object Uint32Array]":return s(e,r);case"[object Map]":return new a;case"[object Number]":case"[object String]":return new a(e);case"[object RegExp]":return o(e);case"[object Set]":return new a;case"[object Symbol]":return i(e)}}},88438:(e,t,r)=>{var A=r(15178),n=r(41181),o=r(89513);e.exports=function(e){return"function"!=typeof e.constructor||o(e)?{}:A(n(e))}},958:(e,t,r)=>{var A=r(69976),n=r(61771),o=r(82664),i=A?A.isConcatSpreadable:void 0;e.exports=function(e){return o(e)||n(e)||!!(i&&e&&e[i])}},98041:e=>{var t=/^(?:0|[1-9]\d*)$/;e.exports=function(e,r){var A=typeof e;return!!(r=null==r?9007199254740991:r)&&("number"==A||"symbol"!=A&&t.test(e))&&e>-1&&e%1==0&&e{var A=r(71074),n=r(41929),o=r(98041),i=r(46778);e.exports=function(e,t,r){if(!i(r))return!1;var s=typeof t;return!!("number"==s?n(r)&&o(t,r.length):"string"==s&&t in r)&&A(r[t],e)}},70474:(e,t,r)=>{var A=r(82664),n=r(65558),o=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,i=/^\w*$/;e.exports=function(e,t){if(A(e))return!1;var r=typeof e;return!("number"!=r&&"symbol"!=r&&"boolean"!=r&&null!=e&&!n(e))||(i.test(e)||!o.test(e)||null!=t&&e in Object(t))}},69448:e=>{e.exports=function(e){var t=typeof e;return"string"==t||"number"==t||"symbol"==t||"boolean"==t?"__proto__"!==e:null===e}},15061:(e,t,r)=>{var A,n=r(14429),o=(A=/[^.]+$/.exec(n&&n.keys&&n.keys.IE_PROTO||""))?"Symbol(src)_1."+A:"";e.exports=function(e){return!!o&&o in e}},89513:e=>{var t=Object.prototype;e.exports=function(e){var r=e&&e.constructor;return e===("function"==typeof r&&r.prototype||t)}},20925:(e,t,r)=>{var A=r(46778);e.exports=function(e){return e==e&&!A(e)}},82262:e=>{e.exports=function(e){for(var t,r=[];!(t=e.next()).done;)r.push(t.value);return r}},14620:e=>{e.exports=function(){this.__data__=[],this.size=0}},73682:(e,t,r)=>{var A=r(39836),n=Array.prototype.splice;e.exports=function(e){var t=this.__data__,r=A(t,e);return!(r<0)&&(r==t.length-1?t.pop():n.call(t,r,1),--this.size,!0)}},43112:(e,t,r)=>{var A=r(39836);e.exports=function(e){var t=this.__data__,r=A(t,e);return r<0?void 0:t[r][1]}},90640:(e,t,r)=>{var A=r(39836);e.exports=function(e){return A(this.__data__,e)>-1}},9380:(e,t,r)=>{var A=r(39836);e.exports=function(e,t){var r=this.__data__,n=A(r,e);return n<0?(++this.size,r.push([e,t])):r[n][1]=t,this}},18209:(e,t,r)=>{var A=r(72574),n=r(29197),o=r(63603);e.exports=function(){this.size=0,this.__data__={hash:new A,map:new(o||n),string:new A}}},89706:(e,t,r)=>{var A=r(59253);e.exports=function(e){var t=A(this,e).delete(e);return this.size-=t?1:0,t}},43786:(e,t,r)=>{var A=r(59253);e.exports=function(e){return A(this,e).get(e)}},17926:(e,t,r)=>{var A=r(59253);e.exports=function(e){return A(this,e).has(e)}},87345:(e,t,r)=>{var A=r(59253);e.exports=function(e,t){var r=A(this,e),n=r.size;return r.set(e,t),this.size+=r.size==n?0:1,this}},7877:e=>{e.exports=function(e){var t=-1,r=Array(e.size);return e.forEach((function(e,A){r[++t]=[A,e]})),r}},12757:e=>{e.exports=function(e,t){return function(r){return null!=r&&(r[e]===t&&(void 0!==t||e in Object(r)))}}},31948:(e,t,r)=>{var A=r(74499);e.exports=function(e){var t=A(e,(function(e){return 500===r.size&&r.clear(),e})),r=t.cache;return t}},52437:(e,t,r)=>{var A=r(99513)(Object,"create");e.exports=A},60657:(e,t,r)=>{var A=r(64309)(Object.keys,Object);e.exports=A},95632:e=>{e.exports=function(e){var t=[];if(null!=e)for(var r in Object(e))t.push(r);return t}},26391:(e,t,r)=>{e=r.nmd(e);var A=r(68399),n=t&&!t.nodeType&&t,o=n&&e&&!e.nodeType&&e,i=o&&o.exports===n&&A.process,s=function(){try{var e=o&&o.require&&o.require("util").types;return e||i&&i.binding&&i.binding("util")}catch(e){}}();e.exports=s},87427:e=>{var t=Object.prototype.toString;e.exports=function(e){return t.call(e)}},64309:e=>{e.exports=function(e,t){return function(r){return e(t(r))}}},44322:(e,t,r)=>{var A=r(66636),n=Math.max;e.exports=function(e,t,r){return t=n(void 0===t?e.length-1:t,0),function(){for(var o=arguments,i=-1,s=n(o.length-t,0),a=Array(s);++i{var A=r(68399),n="object"==typeof self&&self&&self.Object===Object&&self,o=A||n||Function("return this")();e.exports=o},36883:e=>{e.exports=function(e,t){if(("constructor"!==t||"function"!=typeof e[t])&&"__proto__"!=t)return e[t]}},74785:e=>{e.exports=function(e){return this.__data__.set(e,"__lodash_hash_undefined__"),this}},87760:e=>{e.exports=function(e){return this.__data__.has(e)}},7442:e=>{e.exports=function(e){var t=-1,r=Array(e.size);return e.forEach((function(e){r[++t]=e})),r}},3111:(e,t,r)=>{var A=r(4899),n=r(19908)(A);e.exports=n},19908:e=>{var t=Date.now;e.exports=function(e){var r=0,A=0;return function(){var n=t(),o=16-(n-A);if(A=n,o>0){if(++r>=800)return arguments[0]}else r=0;return e.apply(void 0,arguments)}}},35678:(e,t,r)=>{var A=r(29197);e.exports=function(){this.__data__=new A,this.size=0}},33336:e=>{e.exports=function(e){var t=this.__data__,r=t.delete(e);return this.size=t.size,r}},97163:e=>{e.exports=function(e){return this.__data__.get(e)}},43737:e=>{e.exports=function(e){return this.__data__.has(e)}},48548:(e,t,r)=>{var A=r(29197),n=r(63603),o=r(75009);e.exports=function(e,t){var r=this.__data__;if(r instanceof A){var i=r.__data__;if(!n||i.length<199)return i.push([e,t]),this.size=++r.size,this;r=this.__data__=new o(i)}return r.set(e,t),this.size=r.size,this}},30475:(e,t,r)=>{var A=r(1051),n=r(93024),o=r(297);e.exports=function(e){return n(e)?o(e):A(e)}},8689:(e,t,r)=>{var A=r(31948),n=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,o=/\\(\\)?/g,i=A((function(e){var t=[];return 46===e.charCodeAt(0)&&t.push(""),e.replace(n,(function(e,r,A,n){t.push(A?n.replace(o,"$1"):r||e)})),t}));e.exports=i},49874:(e,t,r)=>{var A=r(65558);e.exports=function(e){if("string"==typeof e||A(e))return e;var t=e+"";return"0"==t&&1/e==-1/0?"-0":t}},76384:e=>{var t=Function.prototype.toString;e.exports=function(e){if(null!=e){try{return t.call(e)}catch(e){}try{return e+""}catch(e){}}return""}},297:e=>{var t="[\\ud800-\\udfff]",r="[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]",A="\\ud83c[\\udffb-\\udfff]",n="[^\\ud800-\\udfff]",o="(?:\\ud83c[\\udde6-\\uddff]){2}",i="[\\ud800-\\udbff][\\udc00-\\udfff]",s="(?:"+r+"|"+A+")"+"?",a="[\\ufe0e\\ufe0f]?"+s+("(?:\\u200d(?:"+[n,o,i].join("|")+")[\\ufe0e\\ufe0f]?"+s+")*"),c="(?:"+[n+r+"?",r,o,i,t].join("|")+")",g=RegExp(A+"(?="+A+")|"+c+a,"g");e.exports=function(e){return e.match(g)||[]}},89887:e=>{var t="\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000",r="["+t+"]",A="\\d+",n="[\\u2700-\\u27bf]",o="[a-z\\xdf-\\xf6\\xf8-\\xff]",i="[^\\ud800-\\udfff"+t+A+"\\u2700-\\u27bfa-z\\xdf-\\xf6\\xf8-\\xffA-Z\\xc0-\\xd6\\xd8-\\xde]",s="(?:\\ud83c[\\udde6-\\uddff]){2}",a="[\\ud800-\\udbff][\\udc00-\\udfff]",c="[A-Z\\xc0-\\xd6\\xd8-\\xde]",g="(?:"+o+"|"+i+")",l="(?:"+c+"|"+i+")",u="(?:[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]|\\ud83c[\\udffb-\\udfff])?",h="[\\ufe0e\\ufe0f]?"+u+("(?:\\u200d(?:"+["[^\\ud800-\\udfff]",s,a].join("|")+")[\\ufe0e\\ufe0f]?"+u+")*"),p="(?:"+[n,s,a].join("|")+")"+h,d=RegExp([c+"?"+o+"+(?:['’](?:d|ll|m|re|s|t|ve))?(?="+[r,c,"$"].join("|")+")",l+"+(?:['’](?:D|LL|M|RE|S|T|VE))?(?="+[r,c+g,"$"].join("|")+")",c+"?"+g+"+(?:['’](?:d|ll|m|re|s|t|ve))?",c+"+(?:['’](?:D|LL|M|RE|S|T|VE))?","\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])","\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])",A,p].join("|"),"g");e.exports=function(e){return e.match(d)||[]}},89170:(e,t,r)=>{var A=r(61814),n=r(30369)((function(e,t,r){return t=t.toLowerCase(),e+(r?A(t):t)}));e.exports=n},61814:(e,t,r)=>{var A=r(33580),n=r(72609);e.exports=function(e){return n(A(e).toLowerCase())}},82558:(e,t,r)=>{var A=r(41076);e.exports=function(e){return A(e,5)}},26052:(e,t,r)=>{var A=r(41076);e.exports=function(e,t){return A(e,5,t="function"==typeof t?t:void 0)}},4967:e=>{e.exports=function(e){return function(){return e}}},68968:(e,t,r)=>{var A=r(69922),n=r(33580),o=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,i=RegExp("[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]","g");e.exports=function(e){return(e=n(e))&&e.replace(o,A).replace(i,"")}},71074:e=>{e.exports=function(e,t){return e===t||e!=e&&t!=t}},54690:(e,t,r)=>{var A=r(93274);e.exports=function(e){return(null==e?0:e.length)?A(e,1):[]}},44674:(e,t,r)=>{var A=r(84173);e.exports=function(e,t,r){var n=null==e?void 0:A(e,t);return void 0===n?r:n}},15215:(e,t,r)=>{var A=r(95325),n=r(71507);e.exports=function(e,t){return null!=e&&n(e,t,A)}},34878:(e,t,r)=>{var A=r(3881),n=r(71507);e.exports=function(e,t){return null!=e&&n(e,t,A)}},61977:e=>{e.exports=function(e){return e}},61771:(e,t,r)=>{var A=r(76357),n=r(38496),o=Object.prototype,i=o.hasOwnProperty,s=o.propertyIsEnumerable,a=A(function(){return arguments}())?A:function(e){return n(e)&&i.call(e,"callee")&&!s.call(e,"callee")};e.exports=a},82664:e=>{var t=Array.isArray;e.exports=t},41929:(e,t,r)=>{var A=r(92533),n=r(46369);e.exports=function(e){return null!=e&&n(e.length)&&!A(e)}},16064:(e,t,r)=>{var A=r(41929),n=r(38496);e.exports=function(e){return n(e)&&A(e)}},10667:(e,t,r)=>{e=r.nmd(e);var A=r(76169),n=r(88988),o=t&&!t.nodeType&&t,i=o&&e&&!e.nodeType&&e,s=i&&i.exports===o?A.Buffer:void 0,a=(s?s.isBuffer:void 0)||n;e.exports=a},92533:(e,t,r)=>{var A=r(52502),n=r(46778);e.exports=function(e){if(!n(e))return!1;var t=A(e);return"[object Function]"==t||"[object GeneratorFunction]"==t||"[object AsyncFunction]"==t||"[object Proxy]"==t}},46369:e=>{e.exports=function(e){return"number"==typeof e&&e>-1&&e%1==0&&e<=9007199254740991}},13349:(e,t,r)=>{var A=r(55994),n=r(73635),o=r(26391),i=o&&o.isMap,s=i?n(i):A;e.exports=s},46778:e=>{e.exports=function(e){var t=typeof e;return null!=e&&("object"==t||"function"==t)}},38496:e=>{e.exports=function(e){return null!=e&&"object"==typeof e}},11672:(e,t,r)=>{var A=r(52502),n=r(41181),o=r(38496),i=Function.prototype,s=Object.prototype,a=i.toString,c=s.hasOwnProperty,g=a.call(Object);e.exports=function(e){if(!o(e)||"[object Object]"!=A(e))return!1;var t=n(e);if(null===t)return!0;var r=c.call(t,"constructor")&&t.constructor;return"function"==typeof r&&r instanceof r&&a.call(r)==g}},33931:(e,t,r)=>{var A=r(28612),n=r(73635),o=r(26391),i=o&&o.isSet,s=i?n(i):A;e.exports=s},221:(e,t,r)=>{var A=r(52502),n=r(82664),o=r(38496);e.exports=function(e){return"string"==typeof e||!n(e)&&o(e)&&"[object String]"==A(e)}},65558:(e,t,r)=>{var A=r(52502),n=r(38496);e.exports=function(e){return"symbol"==typeof e||n(e)&&"[object Symbol]"==A(e)}},32565:(e,t,r)=>{var A=r(98998),n=r(73635),o=r(26391),i=o&&o.isTypedArray,s=i?n(i):A;e.exports=s},42185:(e,t,r)=>{var A=r(11886),n=r(50994),o=r(41929);e.exports=function(e){return o(e)?A(e):n(e)}},24887:(e,t,r)=>{var A=r(11886),n=r(8372),o=r(41929);e.exports=function(e){return o(e)?A(e,!0):n(e)}},5253:(e,t,r)=>{var A=r(91198),n=r(62164),o=r(42208);e.exports=function(e,t){var r={};return t=o(t,3),n(e,(function(e,n,o){A(r,t(e,n,o),e)})),r}},89612:(e,t,r)=>{var A=r(91198),n=r(62164),o=r(42208);e.exports=function(e,t){var r={};return t=o(t,3),n(e,(function(e,n,o){A(r,n,t(e,n,o))})),r}},74499:(e,t,r)=>{var A=r(75009);function n(e,t){if("function"!=typeof e||null!=t&&"function"!=typeof t)throw new TypeError("Expected a function");var r=function(){var A=arguments,n=t?t.apply(this,A):A[0],o=r.cache;if(o.has(n))return o.get(n);var i=e.apply(this,A);return r.cache=o.set(n,i)||o,i};return r.cache=new(n.Cache||A),r}n.Cache=A,e.exports=n},80305:(e,t,r)=>{var A=r(51264),n=r(27913)((function(e,t,r){A(e,t,r)}));e.exports=n},75130:(e,t,r)=>{var A=r(72204),n=r(87298)((function(e,t){return null==e?{}:A(e,t)}));e.exports=n},7430:(e,t,r)=>{var A=r(35400),n=r(43018),o=r(70474),i=r(49874);e.exports=function(e){return o(e)?A(i(e)):n(e)}},81534:(e,t,r)=>{var A=r(10624);e.exports=function(e,t,r){return null==e?e:A(e,t,r)}},36494:(e,t,r)=>{var A=r(30369)((function(e,t,r){return e+(r?"_":"")+t.toLowerCase()}));e.exports=A},62162:e=>{e.exports=function(){return[]}},88988:e=>{e.exports=function(){return!1}},78700:(e,t,r)=>{var A=r(69976),n=r(87229),o=r(79435),i=r(41929),s=r(221),a=r(82262),c=r(7877),g=r(7442),l=r(30475),u=r(24448),h=A?A.iterator:void 0;e.exports=function(e){if(!e)return[];if(i(e))return s(e)?l(e):n(e);if(h&&e[h])return a(e[h]());var t=o(e);return("[object Map]"==t?c:"[object Set]"==t?g:u)(e)}},36506:(e,t,r)=>{var A=r(75182),n=r(24887);e.exports=function(e){return A(e,n(e))}},33580:(e,t,r)=>{var A=r(35);e.exports=function(e){return null==e?"":A(e)}},72609:(e,t,r)=>{var A=r(56989)("toUpperCase");e.exports=A},24448:(e,t,r)=>{var A=r(18290),n=r(42185);e.exports=function(e){return null==e?[]:A(e,n(e))}},97684:(e,t,r)=>{var A=r(11852),n=r(60466),o=r(33580),i=r(89887);e.exports=function(e,t,r){return e=o(e),void 0===(t=r?void 0:t)?n(e)?i(e):A(e):e.match(t)||[]}},55737:e=>{"use strict";e.exports=e=>{const t={};for(const[r,A]of Object.entries(e))t[r.toLowerCase()]=A;return t}},46227:(e,t,r)=>{"use strict";const A=r(35747),n=r(85622),{promisify:o}=r(31669),i=r(95584).satisfies(process.version,">=10.12.0"),s=e=>{if("win32"===process.platform){if(/[<>:"|?*]/.test(e.replace(n.parse(e).root,""))){const t=new Error("Path contains invalid characters: "+e);throw t.code="EINVAL",t}}},a=e=>({...{mode:511,fs:A},...e}),c=e=>{const t=new Error(`operation not permitted, mkdir '${e}'`);return t.code="EPERM",t.errno=-4048,t.path=e,t.syscall="mkdir",t};e.exports=async(e,t)=>{s(e),t=a(t);const r=o(t.fs.mkdir),g=o(t.fs.stat);if(i&&t.fs.mkdir===A.mkdir){const A=n.resolve(e);return await r(A,{mode:t.mode,recursive:!0}),A}const l=async e=>{try{return await r(e,t.mode),e}catch(t){if("EPERM"===t.code)throw t;if("ENOENT"===t.code){if(n.dirname(e)===e)throw c(e);if(t.message.includes("null bytes"))throw t;return await l(n.dirname(e)),l(e)}try{if(!(await g(e)).isDirectory())throw new Error("The path is not a directory")}catch(e){throw t}return e}};return l(n.resolve(e))},e.exports.sync=(e,t)=>{if(s(e),t=a(t),i&&t.fs.mkdirSync===A.mkdirSync){const r=n.resolve(e);return A.mkdirSync(r,{mode:t.mode,recursive:!0}),r}const r=e=>{try{t.fs.mkdirSync(e,t.mode)}catch(A){if("EPERM"===A.code)throw A;if("ENOENT"===A.code){if(n.dirname(e)===e)throw c(e);if(A.message.includes("null bytes"))throw A;return r(n.dirname(e)),r(e)}try{if(!t.fs.statSync(e).isDirectory())throw new Error("The path is not a directory")}catch(e){throw A}}return e};return r(n.resolve(e))}},55598:(e,t,r)=>{"use strict";const A=r(92413).PassThrough,n=Array.prototype.slice;function o(e,t){if(Array.isArray(e))for(let r=0,A=e.length;r0||(t=!1,g())}function o(e){function t(){e.removeListener("merge2UnpipeEnd",t),e.removeListener("end",t),n()}if(e._readableState.endEmitted)return n();e.on("merge2UnpipeEnd",t),e.on("end",t),e.pipe(a,{end:!1}),e.resume()}for(let e=0;e{"use strict";const A=r(31669),n=r(12235),o=r(54722),i=r(3598),s=e=>"string"==typeof e&&(""===e||"./"===e),a=(e,t,r)=>{t=[].concat(t),e=[].concat(e);let A=new Set,n=new Set,i=new Set,s=0,a=e=>{i.add(e.output),r&&r.onResult&&r.onResult(e)};for(let i=0;i!A.has(e));if(r&&0===c.length){if(!0===r.failglob)throw new Error(`No matches found for "${t.join(", ")}"`);if(!0===r.nonull||!0===r.nullglob)return r.unescape?t.map(e=>e.replace(/\\/g,"")):t}return c};a.match=a,a.matcher=(e,t)=>o(e,t),a.any=a.isMatch=(e,t,r)=>o(t,r)(e),a.not=(e,t,r={})=>{t=[].concat(t).map(String);let A=new Set,n=[],o=a(e,t,{...r,onResult:e=>{r.onResult&&r.onResult(e),n.push(e.output)}});for(let e of n)o.includes(e)||A.add(e);return[...A]},a.contains=(e,t,r)=>{if("string"!=typeof e)throw new TypeError(`Expected a string: "${A.inspect(e)}"`);if(Array.isArray(t))return t.some(t=>a.contains(e,t,r));if("string"==typeof t){if(s(e)||s(t))return!1;if(e.includes(t)||e.startsWith("./")&&e.slice(2).includes(t))return!0}return a.isMatch(e,t,{...r,contains:!0})},a.matchKeys=(e,t,r)=>{if(!i.isObject(e))throw new TypeError("Expected the first argument to be an object");let A=a(Object.keys(e),t,r),n={};for(let t of A)n[t]=e[t];return n},a.some=(e,t,r)=>{let A=[].concat(e);for(let e of[].concat(t)){let t=o(String(e),r);if(A.some(e=>t(e)))return!0}return!1},a.every=(e,t,r)=>{let A=[].concat(e);for(let e of[].concat(t)){let t=o(String(e),r);if(!A.every(e=>t(e)))return!1}return!0},a.all=(e,t,r)=>{if("string"!=typeof e)throw new TypeError(`Expected a string: "${A.inspect(e)}"`);return[].concat(t).every(t=>o(t,r)(e))},a.capture=(e,t,r)=>{let A=i.isWindows(r),n=o.makeRe(String(e),{...r,capture:!0}).exec(A?i.toPosixSlashes(t):t);if(n)return n.slice(1).map(e=>void 0===e?"":e)},a.makeRe=(...e)=>o.makeRe(...e),a.scan=(...e)=>o.scan(...e),a.parse=(e,t)=>{let r=[];for(let A of[].concat(e||[]))for(let e of n(String(A),t))r.push(o.parse(e,t));return r},a.braces=(e,t)=>{if("string"!=typeof e)throw new TypeError("Expected a string");return t&&!0===t.nobrace||!/\{.*\}/.test(e)?[e]:n(e,t)},a.braceExpand=(e,t)=>{if("string"!=typeof e)throw new TypeError("Expected a string");return a.braces(e,{...t,expand:!0})},e.exports=a},65007:e=>{"use strict";const t=["destroy","setTimeout","socket","headers","trailers","rawHeaders","statusCode","httpVersion","httpVersionMinor","httpVersionMajor","rawTrailers","statusMessage"];e.exports=(e,r)=>{const A=new Set(Object.keys(e).concat(t));for(const t of A)t in r||(r[t]="function"==typeof e[t]?e[t].bind(e):e[t])}},33527:e=>{"use strict";const t=["aborted","complete","headers","httpVersion","httpVersionMinor","httpVersionMajor","method","rawHeaders","rawTrailers","setTimeout","socket","statusCode","statusMessage","trailers","url"];e.exports=(e,r)=>{if(r._readableState.autoDestroy)throw new Error("The second stream must have the `autoDestroy` option set to `false`");const A=new Set(Object.keys(e).concat(t)),n={};for(const t of A)t in r||(n[t]={get(){const r=e[t];return"function"==typeof r?r.bind(e):r},set(r){e[t]=r},enumerable:!0,configurable:!1});return Object.defineProperties(r,n),e.once("aborted",()=>{r.destroy(),r.emit("aborted")}),e.once("close",()=>{e.complete&&r.readable?r.once("end",()=>{r.emit("close")}):r.emit("close")}),r}},19793:(e,t,r)=>{"use strict";const A="undefined"==typeof URL?r(78835).URL:URL,n=(e,t)=>t.some(t=>t instanceof RegExp?t.test(e):t===e),o=(e,t)=>{if(t={defaultProtocol:"http:",normalizeProtocol:!0,forceHttp:!1,forceHttps:!1,stripAuthentication:!0,stripHash:!1,stripWWW:!0,removeQueryParameters:[/^utm_\w+/i],removeTrailingSlash:!0,removeDirectoryIndex:!1,sortQueryParameters:!0,...t},Reflect.has(t,"normalizeHttps"))throw new Error("options.normalizeHttps is renamed to options.forceHttp");if(Reflect.has(t,"normalizeHttp"))throw new Error("options.normalizeHttp is renamed to options.forceHttps");if(Reflect.has(t,"stripFragment"))throw new Error("options.stripFragment is renamed to options.stripHash");if(e=e.trim(),/^data:/i.test(e))return((e,{stripHash:t})=>{const r=e.match(/^data:(.*?),(.*?)(?:#(.*))?$/);if(!r)throw new Error("Invalid URL: "+e);const A=r[1].split(";"),n=r[2],o=t?"":r[3];let i=!1;"base64"===A[A.length-1]&&(A.pop(),i=!0);const s=(A.shift()||"").toLowerCase(),a=[...A.map(e=>{let[t,r=""]=e.split("=").map(e=>e.trim());return"charset"===t&&(r=r.toLowerCase(),"us-ascii"===r)?"":`${t}${r?"="+r:""}`}).filter(Boolean)];return i&&a.push("base64"),(0!==a.length||s&&"text/plain"!==s)&&a.unshift(s),`data:${a.join(";")},${i?n.trim():n}${o?"#"+o:""}`})(e,t);const r=e.startsWith("//");!r&&/^\.*\//.test(e)||(e=e.replace(/^(?!(?:\w+:)?\/\/)|^\/\//,t.defaultProtocol));const o=new A(e);if(t.forceHttp&&t.forceHttps)throw new Error("The `forceHttp` and `forceHttps` options cannot be used together");if(t.forceHttp&&"https:"===o.protocol&&(o.protocol="http:"),t.forceHttps&&"http:"===o.protocol&&(o.protocol="https:"),t.stripAuthentication&&(o.username="",o.password=""),t.stripHash&&(o.hash=""),o.pathname&&(o.pathname=o.pathname.replace(/((?!:).|^)\/{2,}/g,(e,t)=>/^(?!\/)/g.test(t)?t+"/":"/")),o.pathname&&(o.pathname=decodeURI(o.pathname)),!0===t.removeDirectoryIndex&&(t.removeDirectoryIndex=[/^index\.[a-z]+$/]),Array.isArray(t.removeDirectoryIndex)&&t.removeDirectoryIndex.length>0){let e=o.pathname.split("/");const r=e[e.length-1];n(r,t.removeDirectoryIndex)&&(e=e.slice(0,e.length-1),o.pathname=e.slice(1).join("/")+"/")}if(o.hostname&&(o.hostname=o.hostname.replace(/\.$/,""),t.stripWWW&&/^www\.([a-z\-\d]{2,63})\.([a-z.]{2,5})$/.test(o.hostname)&&(o.hostname=o.hostname.replace(/^www\./,""))),Array.isArray(t.removeQueryParameters))for(const e of[...o.searchParams.keys()])n(e,t.removeQueryParameters)&&o.searchParams.delete(e);return t.sortQueryParameters&&o.searchParams.sort(),t.removeTrailingSlash&&(o.pathname=o.pathname.replace(/\/$/,"")),e=o.toString(),!t.removeTrailingSlash&&"/"!==o.pathname||""!==o.hash||(e=e.replace(/\/$/,"")),r&&!t.normalizeProtocol&&(e=e.replace(/^http:\/\//,"//")),t.stripProtocol&&(e=e.replace(/^(?:https?:)?\/\//,"")),e};e.exports=o,e.exports.default=o},91162:(e,t,r)=>{var A=r(98984);function n(e){var t=function(){return t.called?t.value:(t.called=!0,t.value=e.apply(this,arguments))};return t.called=!1,t}e.exports=A(n),n.proto=n((function(){Object.defineProperty(Function.prototype,"once",{value:function(){return n(this)},configurable:!0})}))},27180:(e,t,r)=>{var A=r(98984);function n(e){var t=function(){return t.called?t.value:(t.called=!0,t.value=e.apply(this,arguments))};return t.called=!1,t}function o(e){var t=function(){if(t.called)throw new Error(t.onceError);return t.called=!0,t.value=e.apply(this,arguments)},r=e.name||"Function wrapped with `once`";return t.onceError=r+" shouldn't be called more than once",t.called=!1,t}e.exports=A(n),e.exports.strict=A(o),n.proto=n((function(){Object.defineProperty(Function.prototype,"once",{value:function(){return n(this)},configurable:!0}),Object.defineProperty(Function.prototype,"onceStrict",{value:function(){return o(this)},configurable:!0})}))},59351:e=>{"use strict";class t extends Error{constructor(e){super(e||"Promise was canceled"),this.name="CancelError"}get isCanceled(){return!0}}class r{static fn(e){return(...t)=>new r((r,A,n)=>{t.push(n),e(...t).then(r,A)})}constructor(e){this._cancelHandlers=[],this._isPending=!0,this._isCanceled=!1,this._rejectOnCancel=!0,this._promise=new Promise((t,r)=>{this._reject=r;const A=e=>{if(!this._isPending)throw new Error("The `onCancel` handler was attached after the promise settled.");this._cancelHandlers.push(e)};return Object.defineProperties(A,{shouldReject:{get:()=>this._rejectOnCancel,set:e=>{this._rejectOnCancel=e}}}),e(e=>{this._isPending=!1,t(e)},e=>{this._isPending=!1,r(e)},A)})}then(e,t){return this._promise.then(e,t)}catch(e){return this._promise.catch(e)}finally(e){return this._promise.finally(e)}cancel(e){if(this._isPending&&!this._isCanceled){if(this._cancelHandlers.length>0)try{for(const e of this._cancelHandlers)e()}catch(e){this._reject(e)}this._isCanceled=!0,this._rejectOnCancel&&this._reject(new t(e))}}get isCanceled(){return this._isCanceled}}Object.setPrototypeOf(r.prototype,Promise.prototype),e.exports=r,e.exports.CancelError=t},61578:(e,t,r)=>{"use strict";const A=r(60550),n=e=>{if(e<1)throw new TypeError("Expected `concurrency` to be a number from 1 and up");const t=[];let r=0;const n=()=>{r--,t.length>0&&t.shift()()},o=(e,t,...o)=>{r++;const i=A(e,...o);t(i),i.then(n,n)},i=(A,...n)=>new Promise(i=>((A,n,...i)=>{rr},pendingCount:{get:()=>t.length}}),i};e.exports=n,e.exports.default=n},60550:e=>{"use strict";e.exports=(e,...t)=>new Promise(r=>{r(e(...t))})},37127:e=>{"use strict";const t=(e={})=>{const t=e.env||process.env;return"win32"!==(e.platform||process.platform)?"PATH":Object.keys(t).reverse().find(e=>"PATH"===e.toUpperCase())||"Path"};e.exports=t,e.exports.default=t},5763:(e,t,r)=>{"use strict";const{promisify:A}=r(31669),n=r(35747);async function o(e,t,r){if("string"!=typeof r)throw new TypeError("Expected a string, got "+typeof r);try{return(await A(n[e])(r))[t]()}catch(e){if("ENOENT"===e.code)return!1;throw e}}function i(e,t,r){if("string"!=typeof r)throw new TypeError("Expected a string, got "+typeof r);try{return n[e](r)[t]()}catch(e){if("ENOENT"===e.code)return!1;throw e}}t.isFile=o.bind(null,"stat","isFile"),t.isDirectory=o.bind(null,"stat","isDirectory"),t.isSymlink=o.bind(null,"lstat","isSymbolicLink"),t.isFileSync=i.bind(null,"statSync","isFile"),t.isDirectorySync=i.bind(null,"statSync","isDirectory"),t.isSymlinkSync=i.bind(null,"lstatSync","isSymbolicLink")},54722:(e,t,r)=>{"use strict";e.exports=r(18828)},71086:(e,t,r)=>{"use strict";const A=r(85622),n={DOT_LITERAL:"\\.",PLUS_LITERAL:"\\+",QMARK_LITERAL:"\\?",SLASH_LITERAL:"\\/",ONE_CHAR:"(?=.)",QMARK:"[^/]",END_ANCHOR:"(?:\\/|$)",DOTS_SLASH:"\\.{1,2}(?:\\/|$)",NO_DOT:"(?!\\.)",NO_DOTS:"(?!(?:^|\\/)\\.{1,2}(?:\\/|$))",NO_DOT_SLASH:"(?!\\.{0,1}(?:\\/|$))",NO_DOTS_SLASH:"(?!\\.{1,2}(?:\\/|$))",QMARK_NO_DOT:"[^.\\/]",STAR:"[^/]*?",START_ANCHOR:"(?:^|\\/)"},o={...n,SLASH_LITERAL:"[\\\\/]",QMARK:"[^\\\\/]",STAR:"[^\\\\/]*?",DOTS_SLASH:"\\.{1,2}(?:[\\\\/]|$)",NO_DOT:"(?!\\.)",NO_DOTS:"(?!(?:^|[\\\\/])\\.{1,2}(?:[\\\\/]|$))",NO_DOT_SLASH:"(?!\\.{0,1}(?:[\\\\/]|$))",NO_DOTS_SLASH:"(?!\\.{1,2}(?:[\\\\/]|$))",QMARK_NO_DOT:"[^.\\\\/]",START_ANCHOR:"(?:^|[\\\\/])",END_ANCHOR:"(?:[\\\\/]|$)"};e.exports={MAX_LENGTH:65536,POSIX_REGEX_SOURCE:{alnum:"a-zA-Z0-9",alpha:"a-zA-Z",ascii:"\\x00-\\x7F",blank:" \\t",cntrl:"\\x00-\\x1F\\x7F",digit:"0-9",graph:"\\x21-\\x7E",lower:"a-z",print:"\\x20-\\x7E ",punct:"\\-!\"#$%&'()\\*+,./:;<=>?@[\\]^_`{|}~",space:" \\t\\r\\n\\v\\f",upper:"A-Z",word:"A-Za-z0-9_",xdigit:"A-Fa-f0-9"},REGEX_BACKSLASH:/\\(?![*+?^${}(|)[\]])/g,REGEX_NON_SPECIAL_CHARS:/^[^@![\].,$*+?^{}()|\\/]+/,REGEX_SPECIAL_CHARS:/[-*+?.^${}(|)[\]]/,REGEX_SPECIAL_CHARS_BACKREF:/(\\?)((\W)(\3*))/g,REGEX_SPECIAL_CHARS_GLOBAL:/([-*+?.^${}(|)[\]])/g,REGEX_REMOVE_BACKSLASH:/(?:\[.*?[^\\]\]|\\(?=.))/g,REPLACEMENTS:{"***":"*","**/**":"**","**/**/**":"**"},CHAR_0:48,CHAR_9:57,CHAR_UPPERCASE_A:65,CHAR_LOWERCASE_A:97,CHAR_UPPERCASE_Z:90,CHAR_LOWERCASE_Z:122,CHAR_LEFT_PARENTHESES:40,CHAR_RIGHT_PARENTHESES:41,CHAR_ASTERISK:42,CHAR_AMPERSAND:38,CHAR_AT:64,CHAR_BACKWARD_SLASH:92,CHAR_CARRIAGE_RETURN:13,CHAR_CIRCUMFLEX_ACCENT:94,CHAR_COLON:58,CHAR_COMMA:44,CHAR_DOT:46,CHAR_DOUBLE_QUOTE:34,CHAR_EQUAL:61,CHAR_EXCLAMATION_MARK:33,CHAR_FORM_FEED:12,CHAR_FORWARD_SLASH:47,CHAR_GRAVE_ACCENT:96,CHAR_HASH:35,CHAR_HYPHEN_MINUS:45,CHAR_LEFT_ANGLE_BRACKET:60,CHAR_LEFT_CURLY_BRACE:123,CHAR_LEFT_SQUARE_BRACKET:91,CHAR_LINE_FEED:10,CHAR_NO_BREAK_SPACE:160,CHAR_PERCENT:37,CHAR_PLUS:43,CHAR_QUESTION_MARK:63,CHAR_RIGHT_ANGLE_BRACKET:62,CHAR_RIGHT_CURLY_BRACE:125,CHAR_RIGHT_SQUARE_BRACKET:93,CHAR_SEMICOLON:59,CHAR_SINGLE_QUOTE:39,CHAR_SPACE:32,CHAR_TAB:9,CHAR_UNDERSCORE:95,CHAR_VERTICAL_LINE:124,CHAR_ZERO_WIDTH_NOBREAK_SPACE:65279,SEP:A.sep,extglobChars:e=>({"!":{type:"negate",open:"(?:(?!(?:",close:`))${e.STAR})`},"?":{type:"qmark",open:"(?:",close:")?"},"+":{type:"plus",open:"(?:",close:")+"},"*":{type:"star",open:"(?:",close:")*"},"@":{type:"at",open:"(?:",close:")"}}),globChars:e=>!0===e?o:n}},47974:(e,t,r)=>{"use strict";const A=r(71086),n=r(3598),{MAX_LENGTH:o,POSIX_REGEX_SOURCE:i,REGEX_NON_SPECIAL_CHARS:s,REGEX_SPECIAL_CHARS_BACKREF:a,REPLACEMENTS:c}=A,g=(e,t)=>{if("function"==typeof t.expandRange)return t.expandRange(...e,t);e.sort();const r=`[${e.join("-")}]`;try{new RegExp(r)}catch(t){return e.map(e=>n.escapeRegex(e)).join("..")}return r},l=(e,t)=>`Missing ${e}: "${t}" - use "\\\\${t}" to match literal characters`,u=(e,t)=>{if("string"!=typeof e)throw new TypeError("Expected a string");e=c[e]||e;const r={...t},u="number"==typeof r.maxLength?Math.min(o,r.maxLength):o;let h=e.length;if(h>u)throw new SyntaxError(`Input length: ${h}, exceeds maximum allowed length: ${u}`);const p={type:"bos",value:"",output:r.prepend||""},d=[p],C=r.capture?"":"?:",f=n.isWindows(t),I=A.globChars(f),E=A.extglobChars(I),{DOT_LITERAL:B,PLUS_LITERAL:y,SLASH_LITERAL:m,ONE_CHAR:w,DOTS_SLASH:Q,NO_DOT:D,NO_DOT_SLASH:b,NO_DOTS_SLASH:v,QMARK:S,QMARK_NO_DOT:k,STAR:N,START_ANCHOR:F}=I,K=e=>`(${C}(?:(?!${F}${e.dot?Q:B}).)*?)`,M=r.dot?"":D,R=r.dot?S:k;let x=!0===r.bash?K(r):N;r.capture&&(x=`(${x})`),"boolean"==typeof r.noext&&(r.noextglob=r.noext);const L={input:e,index:-1,start:0,dot:!0===r.dot,consumed:"",output:"",prefix:"",backtrack:!1,negated:!1,brackets:0,braces:0,parens:0,quotes:0,globstar:!1,tokens:d};e=n.removePrefix(e,L),h=e.length;const P=[],O=[],U=[];let T,j=p;const Y=()=>L.index===h-1,G=L.peek=(t=1)=>e[L.index+t],H=L.advance=()=>e[++L.index],J=()=>e.slice(L.index+1),q=(e="",t=0)=>{L.consumed+=e,L.index+=t},z=e=>{L.output+=null!=e.output?e.output:e.value,q(e.value)},W=()=>{let e=1;for(;"!"===G()&&("("!==G(2)||"?"===G(3));)H(),L.start++,e++;return e%2!=0&&(L.negated=!0,L.start++,!0)},V=e=>{L[e]++,U.push(e)},X=e=>{L[e]--,U.pop()},_=e=>{if("globstar"===j.type){const t=L.braces>0&&("comma"===e.type||"brace"===e.type),r=!0===e.extglob||P.length&&("pipe"===e.type||"paren"===e.type);"slash"===e.type||"paren"===e.type||t||r||(L.output=L.output.slice(0,-j.output.length),j.type="star",j.value="*",j.output=x,L.output+=j.output)}if(P.length&&"paren"!==e.type&&!E[e.value]&&(P[P.length-1].inner+=e.value),(e.value||e.output)&&z(e),j&&"text"===j.type&&"text"===e.type)return j.value+=e.value,void(j.output=(j.output||"")+e.value);e.prev=j,d.push(e),j=e},Z=(e,t)=>{const A={...E[t],conditions:1,inner:""};A.prev=j,A.parens=L.parens,A.output=L.output;const n=(r.capture?"(":"")+A.open;V("parens"),_({type:e,value:t,output:L.output?"":w}),_({type:"paren",extglob:!0,value:H(),output:n}),P.push(A)},$=e=>{let t=e.close+(r.capture?")":"");if("negate"===e.type){let A=x;e.inner&&e.inner.length>1&&e.inner.includes("/")&&(A=K(r)),(A!==x||Y()||/^\)+$/.test(J()))&&(t=e.close=")$))"+A),"bos"===e.prev.type&&Y()&&(L.negatedExtglob=!0)}_({type:"paren",extglob:!0,value:T,output:t}),X("parens")};if(!1!==r.fastpaths&&!/(^[*!]|[/()[\]{}"])/.test(e)){let A=!1,o=e.replace(a,(e,t,r,n,o,i)=>"\\"===n?(A=!0,e):"?"===n?t?t+n+(o?S.repeat(o.length):""):0===i?R+(o?S.repeat(o.length):""):S.repeat(r.length):"."===n?B.repeat(r.length):"*"===n?t?t+n+(o?x:""):x:t?e:"\\"+e);return!0===A&&(o=!0===r.unescape?o.replace(/\\/g,""):o.replace(/\\+/g,e=>e.length%2==0?"\\\\":e?"\\":"")),o===e&&!0===r.contains?(L.output=e,L):(L.output=n.wrapOutput(o,L,t),L)}for(;!Y();){if(T=H(),"\0"===T)continue;if("\\"===T){const e=G();if("/"===e&&!0!==r.bash)continue;if("."===e||";"===e)continue;if(!e){T+="\\",_({type:"text",value:T});continue}const t=/^\\+/.exec(J());let A=0;if(t&&t[0].length>2&&(A=t[0].length,L.index+=A,A%2!=0&&(T+="\\")),!0===r.unescape?T=H()||"":T+=H()||"",0===L.brackets){_({type:"text",value:T});continue}}if(L.brackets>0&&("]"!==T||"["===j.value||"[^"===j.value)){if(!1!==r.posix&&":"===T){const e=j.value.slice(1);if(e.includes("[")&&(j.posix=!0,e.includes(":"))){const e=j.value.lastIndexOf("["),t=j.value.slice(0,e),r=j.value.slice(e+2),A=i[r];if(A){j.value=t+A,L.backtrack=!0,H(),p.output||1!==d.indexOf(j)||(p.output=w);continue}}}("["===T&&":"!==G()||"-"===T&&"]"===G())&&(T="\\"+T),"]"!==T||"["!==j.value&&"[^"!==j.value||(T="\\"+T),!0===r.posix&&"!"===T&&"["===j.value&&(T="^"),j.value+=T,z({value:T});continue}if(1===L.quotes&&'"'!==T){T=n.escapeRegex(T),j.value+=T,z({value:T});continue}if('"'===T){L.quotes=1===L.quotes?0:1,!0===r.keepQuotes&&_({type:"text",value:T});continue}if("("===T){V("parens"),_({type:"paren",value:T});continue}if(")"===T){if(0===L.parens&&!0===r.strictBrackets)throw new SyntaxError(l("opening","("));const e=P[P.length-1];if(e&&L.parens===e.parens+1){$(P.pop());continue}_({type:"paren",value:T,output:L.parens?")":"\\)"}),X("parens");continue}if("["===T){if(!0!==r.nobracket&&J().includes("]"))V("brackets");else{if(!0!==r.nobracket&&!0===r.strictBrackets)throw new SyntaxError(l("closing","]"));T="\\"+T}_({type:"bracket",value:T});continue}if("]"===T){if(!0===r.nobracket||j&&"bracket"===j.type&&1===j.value.length){_({type:"text",value:T,output:"\\"+T});continue}if(0===L.brackets){if(!0===r.strictBrackets)throw new SyntaxError(l("opening","["));_({type:"text",value:T,output:"\\"+T});continue}X("brackets");const e=j.value.slice(1);if(!0===j.posix||"^"!==e[0]||e.includes("/")||(T="/"+T),j.value+=T,z({value:T}),!1===r.literalBrackets||n.hasRegexChars(e))continue;const t=n.escapeRegex(j.value);if(L.output=L.output.slice(0,-j.value.length),!0===r.literalBrackets){L.output+=t,j.value=t;continue}j.value=`(${C}${t}|${j.value})`,L.output+=j.value;continue}if("{"===T&&!0!==r.nobrace){V("braces");const e={type:"brace",value:T,output:"(",outputIndex:L.output.length,tokensIndex:L.tokens.length};O.push(e),_(e);continue}if("}"===T){const e=O[O.length-1];if(!0===r.nobrace||!e){_({type:"text",value:T,output:T});continue}let t=")";if(!0===e.dots){const e=d.slice(),A=[];for(let t=e.length-1;t>=0&&(d.pop(),"brace"!==e[t].type);t--)"dots"!==e[t].type&&A.unshift(e[t].value);t=g(A,r),L.backtrack=!0}if(!0!==e.comma&&!0!==e.dots){const r=L.output.slice(0,e.outputIndex),A=L.tokens.slice(e.tokensIndex);e.value=e.output="\\{",T=t="\\}",L.output=r;for(const e of A)L.output+=e.output||e.value}_({type:"brace",value:T,output:t}),X("braces"),O.pop();continue}if("|"===T){P.length>0&&P[P.length-1].conditions++,_({type:"text",value:T});continue}if(","===T){let e=T;const t=O[O.length-1];t&&"braces"===U[U.length-1]&&(t.comma=!0,e="|"),_({type:"comma",value:T,output:e});continue}if("/"===T){if("dot"===j.type&&L.index===L.start+1){L.start=L.index+1,L.consumed="",L.output="",d.pop(),j=p;continue}_({type:"slash",value:T,output:m});continue}if("."===T){if(L.braces>0&&"dot"===j.type){"."===j.value&&(j.output=B);const e=O[O.length-1];j.type="dots",j.output+=T,j.value+=T,e.dots=!0;continue}if(L.braces+L.parens===0&&"bos"!==j.type&&"slash"!==j.type){_({type:"text",value:T,output:B});continue}_({type:"dot",value:T,output:B});continue}if("?"===T){if(!(j&&"("===j.value)&&!0!==r.noextglob&&"("===G()&&"?"!==G(2)){Z("qmark",T);continue}if(j&&"paren"===j.type){const e=G();let t=T;if("<"===e&&!n.supportsLookbehinds())throw new Error("Node.js v10 or higher is required for regex lookbehinds");("("===j.value&&!/[!=<:]/.test(e)||"<"===e&&!/<([!=]|\w+>)/.test(J()))&&(t="\\"+T),_({type:"text",value:T,output:t});continue}if(!0!==r.dot&&("slash"===j.type||"bos"===j.type)){_({type:"qmark",value:T,output:k});continue}_({type:"qmark",value:T,output:S});continue}if("!"===T){if(!0!==r.noextglob&&"("===G()&&("?"!==G(2)||!/[!=<:]/.test(G(3)))){Z("negate",T);continue}if(!0!==r.nonegate&&0===L.index){W();continue}}if("+"===T){if(!0!==r.noextglob&&"("===G()&&"?"!==G(2)){Z("plus",T);continue}if(j&&"("===j.value||!1===r.regex){_({type:"plus",value:T,output:y});continue}if(j&&("bracket"===j.type||"paren"===j.type||"brace"===j.type)||L.parens>0){_({type:"plus",value:T});continue}_({type:"plus",value:y});continue}if("@"===T){if(!0!==r.noextglob&&"("===G()&&"?"!==G(2)){_({type:"at",extglob:!0,value:T,output:""});continue}_({type:"text",value:T});continue}if("*"!==T){"$"!==T&&"^"!==T||(T="\\"+T);const e=s.exec(J());e&&(T+=e[0],L.index+=e[0].length),_({type:"text",value:T});continue}if(j&&("globstar"===j.type||!0===j.star)){j.type="star",j.star=!0,j.value+=T,j.output=x,L.backtrack=!0,L.globstar=!0,q(T);continue}let t=J();if(!0!==r.noextglob&&/^\([^?]/.test(t)){Z("star",T);continue}if("star"===j.type){if(!0===r.noglobstar){q(T);continue}const A=j.prev,n=A.prev,o="slash"===A.type||"bos"===A.type,i=n&&("star"===n.type||"globstar"===n.type);if(!0===r.bash&&(!o||t[0]&&"/"!==t[0])){_({type:"star",value:T,output:""});continue}const s=L.braces>0&&("comma"===A.type||"brace"===A.type),a=P.length&&("pipe"===A.type||"paren"===A.type);if(!o&&"paren"!==A.type&&!s&&!a){_({type:"star",value:T,output:""});continue}for(;"/**"===t.slice(0,3);){const r=e[L.index+4];if(r&&"/"!==r)break;t=t.slice(3),q("/**",3)}if("bos"===A.type&&Y()){j.type="globstar",j.value+=T,j.output=K(r),L.output=j.output,L.globstar=!0,q(T);continue}if("slash"===A.type&&"bos"!==A.prev.type&&!i&&Y()){L.output=L.output.slice(0,-(A.output+j.output).length),A.output="(?:"+A.output,j.type="globstar",j.output=K(r)+(r.strictSlashes?")":"|$)"),j.value+=T,L.globstar=!0,L.output+=A.output+j.output,q(T);continue}if("slash"===A.type&&"bos"!==A.prev.type&&"/"===t[0]){const e=void 0!==t[1]?"|$":"";L.output=L.output.slice(0,-(A.output+j.output).length),A.output="(?:"+A.output,j.type="globstar",j.output=`${K(r)}${m}|${m}${e})`,j.value+=T,L.output+=A.output+j.output,L.globstar=!0,q(T+H()),_({type:"slash",value:"/",output:""});continue}if("bos"===A.type&&"/"===t[0]){j.type="globstar",j.value+=T,j.output=`(?:^|${m}|${K(r)}${m})`,L.output=j.output,L.globstar=!0,q(T+H()),_({type:"slash",value:"/",output:""});continue}L.output=L.output.slice(0,-j.output.length),j.type="globstar",j.output=K(r),j.value+=T,L.output+=j.output,L.globstar=!0,q(T);continue}const A={type:"star",value:T,output:x};!0!==r.bash?!j||"bracket"!==j.type&&"paren"!==j.type||!0!==r.regex?(L.index!==L.start&&"slash"!==j.type&&"dot"!==j.type||("dot"===j.type?(L.output+=b,j.output+=b):!0===r.dot?(L.output+=v,j.output+=v):(L.output+=M,j.output+=M),"*"!==G()&&(L.output+=w,j.output+=w)),_(A)):(A.output=T,_(A)):(A.output=".*?","bos"!==j.type&&"slash"!==j.type||(A.output=M+A.output),_(A))}for(;L.brackets>0;){if(!0===r.strictBrackets)throw new SyntaxError(l("closing","]"));L.output=n.escapeLast(L.output,"["),X("brackets")}for(;L.parens>0;){if(!0===r.strictBrackets)throw new SyntaxError(l("closing",")"));L.output=n.escapeLast(L.output,"("),X("parens")}for(;L.braces>0;){if(!0===r.strictBrackets)throw new SyntaxError(l("closing","}"));L.output=n.escapeLast(L.output,"{"),X("braces")}if(!0===r.strictSlashes||"star"!==j.type&&"bracket"!==j.type||_({type:"maybe_slash",value:"",output:m+"?"}),!0===L.backtrack){L.output="";for(const e of L.tokens)L.output+=null!=e.output?e.output:e.value,e.suffix&&(L.output+=e.suffix)}return L};u.fastpaths=(e,t)=>{const r={...t},i="number"==typeof r.maxLength?Math.min(o,r.maxLength):o,s=e.length;if(s>i)throw new SyntaxError(`Input length: ${s}, exceeds maximum allowed length: ${i}`);e=c[e]||e;const a=n.isWindows(t),{DOT_LITERAL:g,SLASH_LITERAL:l,ONE_CHAR:u,DOTS_SLASH:h,NO_DOT:p,NO_DOTS:d,NO_DOTS_SLASH:C,STAR:f,START_ANCHOR:I}=A.globChars(a),E=r.dot?d:p,B=r.dot?C:p,y=r.capture?"":"?:";let m=!0===r.bash?".*?":f;r.capture&&(m=`(${m})`);const w=e=>!0===e.noglobstar?m:`(${y}(?:(?!${I}${e.dot?h:g}).)*?)`,Q=e=>{switch(e){case"*":return`${E}${u}${m}`;case".*":return`${g}${u}${m}`;case"*.*":return`${E}${m}${g}${u}${m}`;case"*/*":return`${E}${m}${l}${u}${B}${m}`;case"**":return E+w(r);case"**/*":return`(?:${E}${w(r)}${l})?${B}${u}${m}`;case"**/*.*":return`(?:${E}${w(r)}${l})?${B}${m}${g}${u}${m}`;case"**/.*":return`(?:${E}${w(r)}${l})?${g}${u}${m}`;default:{const t=/^(.*?)\.(\w+)$/.exec(e);if(!t)return;const r=Q(t[1]);if(!r)return;return r+g+t[2]}}},D=n.removePrefix(e,{negated:!1,prefix:""});let b=Q(D);return b&&!0!==r.strictSlashes&&(b+=l+"?"),b},e.exports=u},18828:(e,t,r)=>{"use strict";const A=r(85622),n=r(95321),o=r(47974),i=r(3598),s=r(71086),a=(e,t,r=!1)=>{if(Array.isArray(e)){const A=e.map(e=>a(e,t,r));return e=>{for(const t of A){const r=t(e);if(r)return r}return!1}}const A=(n=e)&&"object"==typeof n&&!Array.isArray(n)&&e.tokens&&e.input;var n;if(""===e||"string"!=typeof e&&!A)throw new TypeError("Expected pattern to be a non-empty string");const o=t||{},s=i.isWindows(t),c=A?a.compileRe(e,t):a.makeRe(e,t,!1,!0),g=c.state;delete c.state;let l=()=>!1;if(o.ignore){const e={...t,ignore:null,onMatch:null,onResult:null};l=a(o.ignore,e,r)}const u=(r,A=!1)=>{const{isMatch:n,match:i,output:u}=a.test(r,c,t,{glob:e,posix:s}),h={glob:e,state:g,regex:c,posix:s,input:r,output:u,match:i,isMatch:n};return"function"==typeof o.onResult&&o.onResult(h),!1===n?(h.isMatch=!1,!!A&&h):l(r)?("function"==typeof o.onIgnore&&o.onIgnore(h),h.isMatch=!1,!!A&&h):("function"==typeof o.onMatch&&o.onMatch(h),!A||h)};return r&&(u.state=g),u};a.test=(e,t,r,{glob:A,posix:n}={})=>{if("string"!=typeof e)throw new TypeError("Expected input to be a string");if(""===e)return{isMatch:!1,output:""};const o=r||{},s=o.format||(n?i.toPosixSlashes:null);let c=e===A,g=c&&s?s(e):e;return!1===c&&(g=s?s(e):e,c=g===A),!1!==c&&!0!==o.capture||(c=!0===o.matchBase||!0===o.basename?a.matchBase(e,t,r,n):t.exec(g)),{isMatch:Boolean(c),match:c,output:g}},a.matchBase=(e,t,r,n=i.isWindows(r))=>(t instanceof RegExp?t:a.makeRe(t,r)).test(A.basename(e)),a.isMatch=(e,t,r)=>a(t,r)(e),a.parse=(e,t)=>Array.isArray(e)?e.map(e=>a.parse(e,t)):o(e,{...t,fastpaths:!1}),a.scan=(e,t)=>n(e,t),a.compileRe=(e,t,r=!1,A=!1)=>{if(!0===r)return e.output;const n=t||{},o=n.contains?"":"^",i=n.contains?"":"$";let s=`${o}(?:${e.output})${i}`;e&&!0===e.negated&&(s=`^(?!${s}).*$`);const c=a.toRegex(s,t);return!0===A&&(c.state=e),c},a.makeRe=(e,t,r=!1,A=!1)=>{if(!e||"string"!=typeof e)throw new TypeError("Expected a non-empty string");const n=t||{};let i,s={negated:!1,fastpaths:!0},c="";return e.startsWith("./")&&(e=e.slice(2),c=s.prefix="./"),!1===n.fastpaths||"."!==e[0]&&"*"!==e[0]||(i=o.fastpaths(e,t)),void 0===i?(s=o(e,t),s.prefix=c+(s.prefix||"")):s.output=i,a.compileRe(s,t,r,A)},a.toRegex=(e,t)=>{try{const r=t||{};return new RegExp(e,r.flags||(r.nocase?"i":""))}catch(e){if(t&&!0===t.debug)throw e;return/$^/}},a.constants=s,e.exports=a},95321:(e,t,r)=>{"use strict";const A=r(3598),{CHAR_ASTERISK:n,CHAR_AT:o,CHAR_BACKWARD_SLASH:i,CHAR_COMMA:s,CHAR_DOT:a,CHAR_EXCLAMATION_MARK:c,CHAR_FORWARD_SLASH:g,CHAR_LEFT_CURLY_BRACE:l,CHAR_LEFT_PARENTHESES:u,CHAR_LEFT_SQUARE_BRACKET:h,CHAR_PLUS:p,CHAR_QUESTION_MARK:d,CHAR_RIGHT_CURLY_BRACE:C,CHAR_RIGHT_PARENTHESES:f,CHAR_RIGHT_SQUARE_BRACKET:I}=r(71086),E=e=>e===g||e===i,B=e=>{!0!==e.isPrefix&&(e.depth=e.isGlobstar?1/0:1)};e.exports=(e,t)=>{const r=t||{},y=e.length-1,m=!0===r.parts||!0===r.scanToEnd,w=[],Q=[],D=[];let b,v,S=e,k=-1,N=0,F=0,K=!1,M=!1,R=!1,x=!1,L=!1,P=!1,O=!1,U=!1,T=!1,j=0,Y={value:"",depth:0,isGlob:!1};const G=()=>k>=y,H=()=>(b=v,S.charCodeAt(++k));for(;k0&&(q=S.slice(0,N),S=S.slice(N),F-=N),J&&!0===R&&F>0?(J=S.slice(0,F),z=S.slice(F)):!0===R?(J="",z=S):J=S,J&&""!==J&&"/"!==J&&J!==S&&E(J.charCodeAt(J.length-1))&&(J=J.slice(0,-1)),!0===r.unescape&&(z&&(z=A.removeBackslashes(z)),J&&!0===O&&(J=A.removeBackslashes(J)));const W={prefix:q,input:e,start:N,base:J,glob:z,isBrace:K,isBracket:M,isGlob:R,isExtglob:x,isGlobstar:L,negated:U};if(!0===r.tokens&&(W.maxDepth=0,E(v)||Q.push(Y),W.tokens=Q),!0===r.parts||!0===r.tokens){let t;for(let A=0;A{"use strict";const A=r(85622),n="win32"===process.platform,{REGEX_BACKSLASH:o,REGEX_REMOVE_BACKSLASH:i,REGEX_SPECIAL_CHARS:s,REGEX_SPECIAL_CHARS_GLOBAL:a}=r(71086);t.isObject=e=>null!==e&&"object"==typeof e&&!Array.isArray(e),t.hasRegexChars=e=>s.test(e),t.isRegexChar=e=>1===e.length&&t.hasRegexChars(e),t.escapeRegex=e=>e.replace(a,"\\$1"),t.toPosixSlashes=e=>e.replace(o,"/"),t.removeBackslashes=e=>e.replace(i,e=>"\\"===e?"":e),t.supportsLookbehinds=()=>{const e=process.version.slice(1).split(".").map(Number);return 3===e.length&&e[0]>=9||8===e[0]&&e[1]>=10},t.isWindows=e=>e&&"boolean"==typeof e.windows?e.windows:!0===n||"\\"===A.sep,t.escapeLast=(e,r,A)=>{const n=e.lastIndexOf(r,A);return-1===n?e:"\\"===e[n-1]?t.escapeLast(e,r,n-1):`${e.slice(0,n)}\\${e.slice(n)}`},t.removePrefix=(e,t={})=>{let r=e;return r.startsWith("./")&&(r=r.slice(2),t.prefix="./"),r},t.wrapOutput=(e,t={},r={})=>{let A=`${r.contains?"":"^"}(?:${e})${r.contains?"":"$"}`;return!0===t.negated&&(A=`(?:^(?!${A}).*$)`),A}},79588:e=>{"use strict";function t(e){this._maxSize=e,this.clear()}t.prototype.clear=function(){this._size=0,this._values={}},t.prototype.get=function(e){return this._values[e]},t.prototype.set=function(e,t){return this._size>=this._maxSize&&this.clear(),this._values.hasOwnProperty(e)||this._size++,this._values[e]=t};var r=/[^.^\]^[]+|(?=\[\]|\.\.)/g,A=/^\d+$/,n=/^\d/,o=/[~`!#$%\^&*+=\-\[\]\\';,/{}|\\":<>\?]/g,i=/^\s*(['"]?)(.*?)(\1)\s*$/,s=!1,a=new t(512),c=new t(512),g=new t(512);try{new Function("")}catch(e){s=!0}function l(e){return a.get(e)||a.set(e,u(e).map((function(e){return e.replace(i,"$2")})))}function u(e){return e.match(r)}function h(e,t,r){return"string"==typeof t&&(r=t,t=!1),r=r||"data",(e=e||"")&&"["!==e.charAt(0)&&(e="."+e),t?function(e,t){var r,A=t,n=u(e);return p(n,(function(e,t,n,o,i){r=o===i.length-1,A+=(e=t||n?"["+e+"]":"."+e)+(r?")":" || {})")})),new Array(n.length+1).join("(")+A}(e,r):r+e}function p(e,t,r){var A,n,o,i,s=e.length;for(n=0;n{var A=r(91162),n=r(97681),o=r(35747),i=function(){},s=/^v?\.0/.test(process.version),a=function(e){return"function"==typeof e},c=function(e,t,r,c){c=A(c);var g=!1;e.on("close",(function(){g=!0})),n(e,{readable:t,writable:r},(function(e){if(e)return c(e);g=!0,c()}));var l=!1;return function(t){if(!g&&!l)return l=!0,function(e){return!!s&&(!!o&&((e instanceof(o.ReadStream||i)||e instanceof(o.WriteStream||i))&&a(e.close)))}(e)?e.close(i):function(e){return e.setHeader&&a(e.abort)}(e)?e.abort():a(e.destroy)?e.destroy():void c(t||new Error("stream was destroyed"))}},g=function(e){e()},l=function(e,t){return e.pipe(t)};e.exports=function(){var e,t=Array.prototype.slice.call(arguments),r=a(t[t.length-1]||i)&&t.pop()||i;if(Array.isArray(t[0])&&(t=t[0]),t.length<2)throw new Error("pump requires two streams per minimum");var A=t.map((function(n,o){var i=o0,(function(t){e||(e=t),t&&A.forEach(g),i||(A.forEach(g),r(e))}))}));return t.reduce(l)}},49601:e=>{"use strict";class t{constructor(e={}){if(!(e.maxSize&&e.maxSize>0))throw new TypeError("`maxSize` must be a number greater than 0");this.maxSize=e.maxSize,this.onEviction=e.onEviction,this.cache=new Map,this.oldCache=new Map,this._size=0}_set(e,t){if(this.cache.set(e,t),this._size++,this._size>=this.maxSize){if(this._size=0,"function"==typeof this.onEviction)for(const[e,t]of this.oldCache.entries())this.onEviction(e,t);this.oldCache=this.cache,this.cache=new Map}}get(e){if(this.cache.has(e))return this.cache.get(e);if(this.oldCache.has(e)){const t=this.oldCache.get(e);return this.oldCache.delete(e),this._set(e,t),t}}set(e,t){return this.cache.has(e)?this.cache.set(e,t):this._set(e,t),this}has(e){return this.cache.has(e)||this.oldCache.has(e)}peek(e){return this.cache.has(e)?this.cache.get(e):this.oldCache.has(e)?this.oldCache.get(e):void 0}delete(e){const t=this.cache.delete(e);return t&&this._size--,this.oldCache.delete(e)||t}clear(){this.cache.clear(),this.oldCache.clear(),this._size=0}*keys(){for(const[e]of this)yield e}*values(){for(const[,e]of this)yield e}*[Symbol.iterator](){for(const e of this.cache)yield e;for(const e of this.oldCache){const[t]=e;this.cache.has(t)||(yield e)}}get size(){let e=0;for(const t of this.oldCache.keys())this.cache.has(t)||e++;return Math.min(this._size+e,this.maxSize)}}e.exports=t},20663:e=>{"use strict";const t={};function r(e,r,A){A||(A=Error);class n extends A{constructor(e,t,A){super(function(e,t,A){return"string"==typeof r?r:r(e,t,A)}(e,t,A))}}n.prototype.name=A.name,n.prototype.code=e,t[e]=n}function A(e,t){if(Array.isArray(e)){const r=e.length;return e=e.map(e=>String(e)),r>2?`one of ${t} ${e.slice(0,r-1).join(", ")}, or `+e[r-1]:2===r?`one of ${t} ${e[0]} or ${e[1]}`:`of ${t} ${e[0]}`}return`of ${t} ${String(e)}`}r("ERR_INVALID_OPT_VALUE",(function(e,t){return'The value "'+t+'" is invalid for option "'+e+'"'}),TypeError),r("ERR_INVALID_ARG_TYPE",(function(e,t,r){let n;var o,i;let s;if("string"==typeof t&&(o="not ",t.substr(!i||i<0?0:+i,o.length)===o)?(n="must not be",t=t.replace(/^not /,"")):n="must be",function(e,t,r){return(void 0===r||r>e.length)&&(r=e.length),e.substring(r-t.length,r)===t}(e," argument"))s=`The ${e} ${n} ${A(t,"type")}`;else{s=`The "${e}" ${function(e,t,r){return"number"!=typeof r&&(r=0),!(r+t.length>e.length)&&-1!==e.indexOf(t,r)}(e,".")?"property":"argument"} ${n} ${A(t,"type")}`}return s+=". Received type "+typeof r,s}),TypeError),r("ERR_STREAM_PUSH_AFTER_EOF","stream.push() after EOF"),r("ERR_METHOD_NOT_IMPLEMENTED",(function(e){return"The "+e+" method is not implemented"})),r("ERR_STREAM_PREMATURE_CLOSE","Premature close"),r("ERR_STREAM_DESTROYED",(function(e){return"Cannot call "+e+" after a stream was destroyed"})),r("ERR_MULTIPLE_CALLBACK","Callback called multiple times"),r("ERR_STREAM_CANNOT_PIPE","Cannot pipe, not readable"),r("ERR_STREAM_WRITE_AFTER_END","write after end"),r("ERR_STREAM_NULL_VALUES","May not write null values to stream",TypeError),r("ERR_UNKNOWN_ENCODING",(function(e){return"Unknown encoding: "+e}),TypeError),r("ERR_STREAM_UNSHIFT_AFTER_END_EVENT","stream.unshift() after end event"),e.exports.q=t},39138:e=>{"use strict";var t=new Set;e.exports.emitExperimentalWarning=process.emitWarning?function(e){if(!t.has(e)){var r=e+" is an experimental feature. This feature could change at any time";t.add(e),process.emitWarning(r,"ExperimentalWarning")}}:function(){}},72434:(e,t,r)=>{"use strict";var A=Object.keys||function(e){var t=[];for(var r in e)t.push(r);return t};e.exports=c;var n=r(58020),o=r(6729);r(85870)(c,n);for(var i=A(o.prototype),s=0;s{"use strict";e.exports=n;var A=r(54801);function n(e){if(!(this instanceof n))return new n(e);A.call(this,e)}r(85870)(n,A),n.prototype._transform=function(e,t,r){r(null,e)}},58020:(e,t,r)=>{"use strict";var A;e.exports=w,w.ReadableState=m;r(28614).EventEmitter;var n=function(e,t){return e.listeners(t).length},o=r(49298),i=r(64293).Buffer,s=global.Uint8Array||function(){};var a,c=r(31669);a=c&&c.debuglog?c.debuglog("stream"):function(){};var g,l,u=r(43117),h=r(32340),p=r(77433).getHighWaterMark,d=r(20663).q,C=d.ERR_INVALID_ARG_TYPE,f=d.ERR_STREAM_PUSH_AFTER_EOF,I=d.ERR_METHOD_NOT_IMPLEMENTED,E=d.ERR_STREAM_UNSHIFT_AFTER_END_EVENT,B=r(39138).emitExperimentalWarning;r(85870)(w,o);var y=["error","close","destroy","pause","resume"];function m(e,t,n){A=A||r(72434),e=e||{},"boolean"!=typeof n&&(n=t instanceof A),this.objectMode=!!e.objectMode,n&&(this.objectMode=this.objectMode||!!e.readableObjectMode),this.highWaterMark=p(this,e,"readableHighWaterMark",n),this.buffer=new u,this.length=0,this.pipes=null,this.pipesCount=0,this.flowing=null,this.ended=!1,this.endEmitted=!1,this.reading=!1,this.sync=!0,this.needReadable=!1,this.emittedReadable=!1,this.readableListening=!1,this.resumeScheduled=!1,this.paused=!0,this.emitClose=!1!==e.emitClose,this.destroyed=!1,this.defaultEncoding=e.defaultEncoding||"utf8",this.awaitDrain=0,this.readingMore=!1,this.decoder=null,this.encoding=null,e.encoding&&(g||(g=r(69538).s),this.decoder=new g(e.encoding),this.encoding=e.encoding)}function w(e){if(A=A||r(72434),!(this instanceof w))return new w(e);var t=this instanceof A;this._readableState=new m(e,this,t),this.readable=!0,e&&("function"==typeof e.read&&(this._read=e.read),"function"==typeof e.destroy&&(this._destroy=e.destroy)),o.call(this)}function Q(e,t,r,A,n){a("readableAddChunk",t);var o,c=e._readableState;if(null===t)c.reading=!1,function(e,t){if(t.ended)return;if(t.decoder){var r=t.decoder.end();r&&r.length&&(t.buffer.push(r),t.length+=t.objectMode?1:r.length)}t.ended=!0,t.sync?v(e):(t.needReadable=!1,t.emittedReadable||(t.emittedReadable=!0,S(e)))}(e,c);else if(n||(o=function(e,t){var r;A=t,i.isBuffer(A)||A instanceof s||"string"==typeof t||void 0===t||e.objectMode||(r=new C("chunk",["string","Buffer","Uint8Array"],t));var A;return r}(c,t)),o)e.emit("error",o);else if(c.objectMode||t&&t.length>0)if("string"==typeof t||c.objectMode||Object.getPrototypeOf(t)===i.prototype||(t=function(e){return i.from(e)}(t)),A)c.endEmitted?e.emit("error",new E):D(e,c,t,!0);else if(c.ended)e.emit("error",new f);else{if(c.destroyed)return!1;c.reading=!1,c.decoder&&!r?(t=c.decoder.write(t),c.objectMode||0!==t.length?D(e,c,t,!1):k(e,c)):D(e,c,t,!1)}else A||(c.reading=!1,k(e,c));return!c.ended&&(c.lengtht.highWaterMark&&(t.highWaterMark=function(e){return e>=8388608?e=8388608:(e--,e|=e>>>1,e|=e>>>2,e|=e>>>4,e|=e>>>8,e|=e>>>16,e++),e}(e)),e<=t.length?e:t.ended?t.length:(t.needReadable=!0,0))}function v(e){var t=e._readableState;t.needReadable=!1,t.emittedReadable||(a("emitReadable",t.flowing),t.emittedReadable=!0,process.nextTick(S,e))}function S(e){var t=e._readableState;a("emitReadable_",t.destroyed,t.length,t.ended),t.destroyed||!t.length&&!t.ended||e.emit("readable"),t.needReadable=!t.flowing&&!t.ended&&t.length<=t.highWaterMark,R(e)}function k(e,t){t.readingMore||(t.readingMore=!0,process.nextTick(N,e,t))}function N(e,t){for(var r=t.length;!t.reading&&!t.ended&&t.length0,t.resumeScheduled&&!t.paused?t.flowing=!0:e.listenerCount("data")>0&&e.resume()}function K(e){a("readable nexttick read 0"),e.read(0)}function M(e,t){a("resume",t.reading),t.reading||e.read(0),t.resumeScheduled=!1,e.emit("resume"),R(e),t.flowing&&!t.reading&&e.read(0)}function R(e){var t=e._readableState;for(a("flow",t.flowing);t.flowing&&null!==e.read(););}function x(e,t){return 0===t.length?null:(t.objectMode?r=t.buffer.shift():!e||e>=t.length?(r=t.decoder?t.buffer.join(""):1===t.buffer.length?t.buffer.first():t.buffer.concat(t.length),t.buffer.clear()):r=t.buffer.consume(e,t.decoder),r);var r}function L(e){var t=e._readableState;a("endReadable",t.endEmitted),t.endEmitted||(t.ended=!0,process.nextTick(P,t,e))}function P(e,t){a("endReadableNT",e.endEmitted,e.length),e.endEmitted||0!==e.length||(e.endEmitted=!0,t.readable=!1,t.emit("end"))}function O(e,t){for(var r=0,A=e.length;r=t.highWaterMark:t.length>0)||t.ended))return a("read: emitReadable",t.length,t.ended),0===t.length&&t.ended?L(this):v(this),null;if(0===(e=b(e,t))&&t.ended)return 0===t.length&&L(this),null;var A,n=t.needReadable;return a("need readable",n),(0===t.length||t.length-e0?x(e,t):null)?(t.needReadable=!0,e=0):(t.length-=e,t.awaitDrain=0),0===t.length&&(t.ended||(t.needReadable=!0),r!==e&&t.ended&&L(this)),null!==A&&this.emit("data",A),A},w.prototype._read=function(e){this.emit("error",new I("_read()"))},w.prototype.pipe=function(e,t){var r=this,A=this._readableState;switch(A.pipesCount){case 0:A.pipes=e;break;case 1:A.pipes=[A.pipes,e];break;default:A.pipes.push(e)}A.pipesCount+=1,a("pipe count=%d opts=%j",A.pipesCount,t);var o=(!t||!1!==t.end)&&e!==process.stdout&&e!==process.stderr?s:d;function i(t,n){a("onunpipe"),t===r&&n&&!1===n.hasUnpiped&&(n.hasUnpiped=!0,a("cleanup"),e.removeListener("close",h),e.removeListener("finish",p),e.removeListener("drain",c),e.removeListener("error",u),e.removeListener("unpipe",i),r.removeListener("end",s),r.removeListener("end",d),r.removeListener("data",l),g=!0,!A.awaitDrain||e._writableState&&!e._writableState.needDrain||c())}function s(){a("onend"),e.end()}A.endEmitted?process.nextTick(o):r.once("end",o),e.on("unpipe",i);var c=function(e){return function(){var t=e._readableState;a("pipeOnDrain",t.awaitDrain),t.awaitDrain&&t.awaitDrain--,0===t.awaitDrain&&n(e,"data")&&(t.flowing=!0,R(e))}}(r);e.on("drain",c);var g=!1;function l(t){a("ondata");var n=e.write(t);a("dest.write",n),!1===n&&((1===A.pipesCount&&A.pipes===e||A.pipesCount>1&&-1!==O(A.pipes,e))&&!g&&(a("false write response, pause",A.awaitDrain),A.awaitDrain++),r.pause())}function u(t){a("onerror",t),d(),e.removeListener("error",u),0===n(e,"error")&&e.emit("error",t)}function h(){e.removeListener("finish",p),d()}function p(){a("onfinish"),e.removeListener("close",h),d()}function d(){a("unpipe"),r.unpipe(e)}return r.on("data",l),function(e,t,r){if("function"==typeof e.prependListener)return e.prependListener(t,r);e._events&&e._events[t]?Array.isArray(e._events[t])?e._events[t].unshift(r):e._events[t]=[r,e._events[t]]:e.on(t,r)}(e,"error",u),e.once("close",h),e.once("finish",p),e.emit("pipe",r),A.flowing||(a("pipe resume"),r.resume()),e},w.prototype.unpipe=function(e){var t=this._readableState,r={hasUnpiped:!1};if(0===t.pipesCount)return this;if(1===t.pipesCount)return e&&e!==t.pipes||(e||(e=t.pipes),t.pipes=null,t.pipesCount=0,t.flowing=!1,e&&e.emit("unpipe",this,r)),this;if(!e){var A=t.pipes,n=t.pipesCount;t.pipes=null,t.pipesCount=0,t.flowing=!1;for(var o=0;o0,!1!==A.flowing&&this.resume()):"readable"===e&&(A.endEmitted||A.readableListening||(A.readableListening=A.needReadable=!0,A.flowing=!1,A.emittedReadable=!1,a("on readable",A.length,A.reading),A.length?v(this):A.reading||process.nextTick(K,this))),r},w.prototype.addListener=w.prototype.on,w.prototype.removeListener=function(e,t){var r=o.prototype.removeListener.call(this,e,t);return"readable"===e&&process.nextTick(F,this),r},w.prototype.removeAllListeners=function(e){var t=o.prototype.removeAllListeners.apply(this,arguments);return"readable"!==e&&void 0!==e||process.nextTick(F,this),t},w.prototype.resume=function(){var e=this._readableState;return e.flowing||(a("resume"),e.flowing=!e.readableListening,function(e,t){t.resumeScheduled||(t.resumeScheduled=!0,process.nextTick(M,e,t))}(this,e)),e.paused=!1,this},w.prototype.pause=function(){return a("call pause flowing=%j",this._readableState.flowing),!1!==this._readableState.flowing&&(a("pause"),this._readableState.flowing=!1,this.emit("pause")),this._readableState.paused=!0,this},w.prototype.wrap=function(e){var t=this,r=this._readableState,A=!1;for(var n in e.on("end",(function(){if(a("wrapped end"),r.decoder&&!r.ended){var e=r.decoder.end();e&&e.length&&t.push(e)}t.push(null)})),e.on("data",(function(n){(a("wrapped data"),r.decoder&&(n=r.decoder.write(n)),r.objectMode&&null==n)||(r.objectMode||n&&n.length)&&(t.push(n)||(A=!0,e.pause()))})),e)void 0===this[n]&&"function"==typeof e[n]&&(this[n]=function(t){return function(){return e[t].apply(e,arguments)}}(n));for(var o=0;o{"use strict";e.exports=g;var A=r(20663).q,n=A.ERR_METHOD_NOT_IMPLEMENTED,o=A.ERR_MULTIPLE_CALLBACK,i=A.ERR_TRANSFORM_ALREADY_TRANSFORMING,s=A.ERR_TRANSFORM_WITH_LENGTH_0,a=r(72434);function c(e,t){var r=this._transformState;r.transforming=!1;var A=r.writecb;if(null===A)return this.emit("error",new o);r.writechunk=null,r.writecb=null,null!=t&&this.push(t),A(e);var n=this._readableState;n.reading=!1,(n.needReadable||n.length{"use strict";function A(e){var t=this;this.next=null,this.entry=null,this.finish=function(){!function(e,t,r){var A=e.entry;e.entry=null;for(;A;){var n=A.callback;t.pendingcb--,n(r),A=A.next}t.corkedRequestsFree.next=e}(t,e)}}var n;e.exports=w,w.WritableState=m;var o={deprecate:r(73212)},i=r(49298),s=r(64293).Buffer,a=global.Uint8Array||function(){};var c,g=r(32340),l=r(77433).getHighWaterMark,u=r(20663).q,h=u.ERR_INVALID_ARG_TYPE,p=u.ERR_METHOD_NOT_IMPLEMENTED,d=u.ERR_MULTIPLE_CALLBACK,C=u.ERR_STREAM_CANNOT_PIPE,f=u.ERR_STREAM_DESTROYED,I=u.ERR_STREAM_NULL_VALUES,E=u.ERR_STREAM_WRITE_AFTER_END,B=u.ERR_UNKNOWN_ENCODING;function y(){}function m(e,t,o){n=n||r(72434),e=e||{},"boolean"!=typeof o&&(o=t instanceof n),this.objectMode=!!e.objectMode,o&&(this.objectMode=this.objectMode||!!e.writableObjectMode),this.highWaterMark=l(this,e,"writableHighWaterMark",o),this.finalCalled=!1,this.needDrain=!1,this.ending=!1,this.ended=!1,this.finished=!1,this.destroyed=!1;var i=!1===e.decodeStrings;this.decodeStrings=!i,this.defaultEncoding=e.defaultEncoding||"utf8",this.length=0,this.writing=!1,this.corked=0,this.sync=!0,this.bufferProcessing=!1,this.onwrite=function(e){!function(e,t){var r=e._writableState,A=r.sync,n=r.writecb;if("function"!=typeof n)throw new d;if(function(e){e.writing=!1,e.writecb=null,e.length-=e.writelen,e.writelen=0}(r),t)!function(e,t,r,A,n){--t.pendingcb,r?(process.nextTick(n,A),process.nextTick(k,e,t),e._writableState.errorEmitted=!0,e.emit("error",A)):(n(A),e._writableState.errorEmitted=!0,e.emit("error",A),k(e,t))}(e,r,A,t,n);else{var o=v(r)||e.destroyed;o||r.corked||r.bufferProcessing||!r.bufferedRequest||b(e,r),A?process.nextTick(D,e,r,o,n):D(e,r,o,n)}}(t,e)},this.writecb=null,this.writelen=0,this.bufferedRequest=null,this.lastBufferedRequest=null,this.pendingcb=0,this.prefinished=!1,this.errorEmitted=!1,this.emitClose=!1!==e.emitClose,this.bufferedRequestCount=0,this.corkedRequestsFree=new A(this)}function w(e){var t=this instanceof(n=n||r(72434));if(!t&&!c.call(w,this))return new w(e);this._writableState=new m(e,this,t),this.writable=!0,e&&("function"==typeof e.write&&(this._write=e.write),"function"==typeof e.writev&&(this._writev=e.writev),"function"==typeof e.destroy&&(this._destroy=e.destroy),"function"==typeof e.final&&(this._final=e.final)),i.call(this)}function Q(e,t,r,A,n,o,i){t.writelen=A,t.writecb=i,t.writing=!0,t.sync=!0,t.destroyed?t.onwrite(new f("write")):r?e._writev(n,t.onwrite):e._write(n,o,t.onwrite),t.sync=!1}function D(e,t,r,A){r||function(e,t){0===t.length&&t.needDrain&&(t.needDrain=!1,e.emit("drain"))}(e,t),t.pendingcb--,A(),k(e,t)}function b(e,t){t.bufferProcessing=!0;var r=t.bufferedRequest;if(e._writev&&r&&r.next){var n=t.bufferedRequestCount,o=new Array(n),i=t.corkedRequestsFree;i.entry=r;for(var s=0,a=!0;r;)o[s]=r,r.isBuf||(a=!1),r=r.next,s+=1;o.allBuffers=a,Q(e,t,!0,t.length,o,"",i.finish),t.pendingcb++,t.lastBufferedRequest=null,i.next?(t.corkedRequestsFree=i.next,i.next=null):t.corkedRequestsFree=new A(t),t.bufferedRequestCount=0}else{for(;r;){var c=r.chunk,g=r.encoding,l=r.callback;if(Q(e,t,!1,t.objectMode?1:c.length,c,g,l),r=r.next,t.bufferedRequestCount--,t.writing)break}null===r&&(t.lastBufferedRequest=null)}t.bufferedRequest=r,t.bufferProcessing=!1}function v(e){return e.ending&&0===e.length&&null===e.bufferedRequest&&!e.finished&&!e.writing}function S(e,t){e._final((function(r){t.pendingcb--,r&&e.emit("error",r),t.prefinished=!0,e.emit("prefinish"),k(e,t)}))}function k(e,t){var r=v(t);return r&&(!function(e,t){t.prefinished||t.finalCalled||("function"!=typeof e._final||t.destroyed?(t.prefinished=!0,e.emit("prefinish")):(t.pendingcb++,t.finalCalled=!0,process.nextTick(S,e,t)))}(e,t),0===t.pendingcb&&(t.finished=!0,e.emit("finish"))),r}r(85870)(w,i),m.prototype.getBuffer=function(){for(var e=this.bufferedRequest,t=[];e;)t.push(e),e=e.next;return t},function(){try{Object.defineProperty(m.prototype,"buffer",{get:o.deprecate((function(){return this.getBuffer()}),"_writableState.buffer is deprecated. Use _writableState.getBuffer instead.","DEP0003")})}catch(e){}}(),"function"==typeof Symbol&&Symbol.hasInstance&&"function"==typeof Function.prototype[Symbol.hasInstance]?(c=Function.prototype[Symbol.hasInstance],Object.defineProperty(w,Symbol.hasInstance,{value:function(e){return!!c.call(this,e)||this===w&&(e&&e._writableState instanceof m)}})):c=function(e){return e instanceof this},w.prototype.pipe=function(){this.emit("error",new C)},w.prototype.write=function(e,t,r){var A,n=this._writableState,o=!1,i=!n.objectMode&&(A=e,s.isBuffer(A)||A instanceof a);return i&&!s.isBuffer(e)&&(e=function(e){return s.from(e)}(e)),"function"==typeof t&&(r=t,t=null),i?t="buffer":t||(t=n.defaultEncoding),"function"!=typeof r&&(r=y),n.ending?function(e,t){var r=new E;e.emit("error",r),process.nextTick(t,r)}(this,r):(i||function(e,t,r,A){var n;return null===r?n=new I:"string"==typeof r||t.objectMode||(n=new h("chunk",["string","Buffer"],r)),!n||(e.emit("error",n),process.nextTick(A,n),!1)}(this,n,e,r))&&(n.pendingcb++,o=function(e,t,r,A,n,o){if(!r){var i=function(e,t,r){e.objectMode||!1===e.decodeStrings||"string"!=typeof t||(t=s.from(t,r));return t}(t,A,n);A!==i&&(r=!0,n="buffer",A=i)}var a=t.objectMode?1:A.length;t.length+=a;var c=t.length-1))throw new B(e);return this._writableState.defaultEncoding=e,this},Object.defineProperty(w.prototype,"writableBuffer",{enumerable:!1,get:function(){return this._writableState&&this._writableState.getBuffer()}}),Object.defineProperty(w.prototype,"writableHighWaterMark",{enumerable:!1,get:function(){return this._writableState.highWaterMark}}),w.prototype._write=function(e,t,r){r(new p("_write()"))},w.prototype._writev=null,w.prototype.end=function(e,t,r){var A=this._writableState;return"function"==typeof e?(r=e,e=null,t=null):"function"==typeof t&&(r=t,t=null),null!=e&&this.write(e,t),A.corked&&(A.corked=1,this.uncork()),A.ending||function(e,t,r){t.ending=!0,k(e,t),r&&(t.finished?process.nextTick(r):e.once("finish",r));t.ended=!0,e.writable=!1}(this,A,r),this},Object.defineProperty(w.prototype,"writableLength",{enumerable:!1,get:function(){return this._writableState.length}}),Object.defineProperty(w.prototype,"destroyed",{enumerable:!1,get:function(){return void 0!==this._writableState&&this._writableState.destroyed},set:function(e){this._writableState&&(this._writableState.destroyed=e)}}),w.prototype.destroy=g.destroy,w.prototype._undestroy=g.undestroy,w.prototype._destroy=function(e,t){t(e)}},4245:(e,t,r)=>{"use strict";var A;function n(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}var o=r(91327),i=Symbol("lastResolve"),s=Symbol("lastReject"),a=Symbol("error"),c=Symbol("ended"),g=Symbol("lastPromise"),l=Symbol("handlePromise"),u=Symbol("stream");function h(e,t){return{value:e,done:t}}function p(e){var t=e[i];if(null!==t){var r=e[u].read();null!==r&&(e[g]=null,e[i]=null,e[s]=null,t(h(r,!1)))}}function d(e){process.nextTick(p,e)}var C=Object.getPrototypeOf((function(){})),f=Object.setPrototypeOf((n(A={get stream(){return this[u]},next:function(){var e=this,t=this[a];if(null!==t)return Promise.reject(t);if(this[c])return Promise.resolve(h(null,!0));if(this[u].destroyed)return new Promise((function(t,r){process.nextTick((function(){e[a]?r(e[a]):t(h(null,!0))}))}));var r,A=this[g];if(A)r=new Promise(function(e,t){return function(r,A){e.then((function(){t[l](r,A)}),A)}}(A,this));else{var n=this[u].read();if(null!==n)return Promise.resolve(h(n,!1));r=new Promise(this[l])}return this[g]=r,r}},Symbol.asyncIterator,(function(){return this})),n(A,"return",(function(){var e=this;return new Promise((function(t,r){e[u].destroy(null,(function(e){e?r(e):t(h(null,!0))}))}))})),A),C);e.exports=function(e){var t,r=Object.create(f,(n(t={},u,{value:e,writable:!0}),n(t,i,{value:null,writable:!0}),n(t,s,{value:null,writable:!0}),n(t,a,{value:null,writable:!0}),n(t,c,{value:e._readableState.endEmitted,writable:!0}),n(t,g,{value:null,writable:!0}),n(t,l,{value:function(e,t){var A=r[u].read();A?(r[g]=null,r[i]=null,r[s]=null,e(h(A,!1))):(r[i]=e,r[s]=t)},writable:!0}),t));return o(e,(function(e){if(e&&"ERR_STREAM_PREMATURE_CLOSE"!==e.code){var t=r[s];return null!==t&&(r[g]=null,r[i]=null,r[s]=null,t(e)),void(r[a]=e)}var A=r[i];null!==A&&(r[g]=null,r[i]=null,r[s]=null,A(h(null,!0))),r[c]=!0})),e.on("readable",d.bind(null,r)),r}},43117:(e,t,r)=>{"use strict";function A(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}var n=r(64293).Buffer,o=r(31669).inspect,i=o&&o.custom||"inspect";e.exports=function(){function e(){this.head=null,this.tail=null,this.length=0}var t=e.prototype;return t.push=function(e){var t={data:e,next:null};this.length>0?this.tail.next=t:this.head=t,this.tail=t,++this.length},t.unshift=function(e){var t={data:e,next:this.head};0===this.length&&(this.tail=t),this.head=t,++this.length},t.shift=function(){if(0!==this.length){var e=this.head.data;return 1===this.length?this.head=this.tail=null:this.head=this.head.next,--this.length,e}},t.clear=function(){this.head=this.tail=null,this.length=0},t.join=function(e){if(0===this.length)return"";for(var t=this.head,r=""+t.data;t=t.next;)r+=e+t.data;return r},t.concat=function(e){if(0===this.length)return n.alloc(0);for(var t,r,A,o=n.allocUnsafe(e>>>0),i=this.head,s=0;i;)t=i.data,r=o,A=s,n.prototype.copy.call(t,r,A),s+=i.data.length,i=i.next;return o},t.consume=function(e,t){var r;return en.length?n.length:e;if(o===n.length?A+=n:A+=n.slice(0,e),0===(e-=o)){o===n.length?(++r,t.next?this.head=t.next:this.head=this.tail=null):(this.head=t,t.data=n.slice(o));break}++r}return this.length-=r,A},t._getBuffer=function(e){var t=n.allocUnsafe(e),r=this.head,A=1;for(r.data.copy(t),e-=r.data.length;r=r.next;){var o=r.data,i=e>o.length?o.length:e;if(o.copy(t,t.length-e,0,i),0===(e-=i)){i===o.length?(++A,r.next?this.head=r.next:this.head=this.tail=null):(this.head=r,r.data=o.slice(i));break}++A}return this.length-=A,t},t[i]=function(e,t){return o(this,function(e){for(var t=1;t{"use strict";function t(e,t){A(e,t),r(e)}function r(e){e._writableState&&!e._writableState.emitClose||e._readableState&&!e._readableState.emitClose||e.emit("close")}function A(e,t){e.emit("error",t)}e.exports={destroy:function(e,n){var o=this,i=this._readableState&&this._readableState.destroyed,s=this._writableState&&this._writableState.destroyed;return i||s?(n?n(e):!e||this._writableState&&this._writableState.errorEmitted||process.nextTick(A,this,e),this):(this._readableState&&(this._readableState.destroyed=!0),this._writableState&&(this._writableState.destroyed=!0),this._destroy(e||null,(function(e){!n&&e?(process.nextTick(t,o,e),o._writableState&&(o._writableState.errorEmitted=!0)):n?(process.nextTick(r,o),n(e)):process.nextTick(r,o)})),this)},undestroy:function(){this._readableState&&(this._readableState.destroyed=!1,this._readableState.reading=!1,this._readableState.ended=!1,this._readableState.endEmitted=!1),this._writableState&&(this._writableState.destroyed=!1,this._writableState.ended=!1,this._writableState.ending=!1,this._writableState.finalCalled=!1,this._writableState.prefinished=!1,this._writableState.finished=!1,this._writableState.errorEmitted=!1)}}},91327:(e,t,r)=>{"use strict";var A=r(20663).q.ERR_STREAM_PREMATURE_CLOSE;function n(){}e.exports=function e(t,r,o){if("function"==typeof r)return e(t,null,r);r||(r={}),o=function(e){var t=!1;return function(r){t||(t=!0,e.call(this,r))}}(o||n);var i=t._writableState,s=t._readableState,a=r.readable||!1!==r.readable&&t.readable,c=r.writable||!1!==r.writable&&t.writable,g=function(){t.writable||l()},l=function(){c=!1,a||o.call(t)},u=function(){a=!1,c||o.call(t)},h=function(e){o.call(t,e)},p=function(){return(!a||s&&s.ended)&&(!c||i&&i.ended)?void 0:o.call(t,new A)},d=function(){t.req.on("finish",l)};return!function(e){return e.setHeader&&"function"==typeof e.abort}(t)?c&&!i&&(t.on("end",g),t.on("close",g)):(t.on("complete",l),t.on("abort",p),t.req?d():t.on("request",d)),t.on("end",u),t.on("finish",l),!1!==r.error&&t.on("error",h),t.on("close",p),function(){t.removeListener("complete",l),t.removeListener("abort",p),t.removeListener("request",d),t.req&&t.req.removeListener("finish",l),t.removeListener("end",g),t.removeListener("close",g),t.removeListener("finish",l),t.removeListener("end",u),t.removeListener("error",h),t.removeListener("close",p)}}},4939:(e,t,r)=>{"use strict";var A;var n=r(20663).q,o=n.ERR_MISSING_ARGS,i=n.ERR_STREAM_DESTROYED;function s(e){if(e)throw e}function a(e,t,n,o){o=function(e){var t=!1;return function(){t||(t=!0,e.apply(void 0,arguments))}}(o);var s=!1;e.on("close",(function(){s=!0})),void 0===A&&(A=r(91327)),A(e,{readable:t,writable:n},(function(e){if(e)return o(e);s=!0,o()}));var a=!1;return function(t){if(!s&&!a)return a=!0,function(e){return e.setHeader&&"function"==typeof e.abort}(e)?e.abort():"function"==typeof e.destroy?e.destroy():void o(t||new i("pipe"))}}function c(e){e()}function g(e,t){return e.pipe(t)}function l(e){return e.length?"function"!=typeof e[e.length-1]?s:e.pop():s}e.exports=function(){for(var e=arguments.length,t=new Array(e),r=0;r0,(function(e){A||(A=e),e&&i.forEach(c),o||(i.forEach(c),n(A))}))}));return t.reduce(g)}},77433:(e,t,r)=>{"use strict";var A=r(20663).q.ERR_INVALID_OPT_VALUE;e.exports={getHighWaterMark:function(e,t,r,n){var o=function(e,t,r){return null!=e.highWaterMark?e.highWaterMark:t?e[r]:null}(t,n,r);if(null!=o){if(!isFinite(o)||Math.floor(o)!==o||o<0)throw new A(n?r:"highWaterMark",o);return Math.floor(o)}return e.objectMode?16:16384}}},49298:(e,t,r)=>{e.exports=r(92413)},86897:(e,t,r)=>{var A=r(92413);"disable"===process.env.READABLE_STREAM&&A?(e.exports=A.Readable,Object.assign(e.exports,A),e.exports.Stream=A):((t=e.exports=r(58020)).Stream=A||t,t.Readable=t,t.Writable=r(6729),t.Duplex=r(72434),t.Transform=r(54801),t.PassThrough=r(52444),t.finished=r(91327),t.pipeline=r(4939))},19476:(e,t,r)=>{"use strict";const A=r(4016);e.exports=(e={})=>new Promise((t,r)=>{const n=A.connect(e,()=>{e.resolveSocket?(n.off("error",r),t({alpnProtocol:n.alpnProtocol,socket:n})):(n.destroy(),t({alpnProtocol:n.alpnProtocol}))});n.on("error",r)})},48491:(e,t,r)=>{"use strict";const A=r(92413).Readable,n=r(55737);e.exports=class extends A{constructor(e,t,r,A){if("number"!=typeof e)throw new TypeError("Argument `statusCode` should be a number");if("object"!=typeof t)throw new TypeError("Argument `headers` should be an object");if(!(r instanceof Buffer))throw new TypeError("Argument `body` should be a buffer");if("string"!=typeof A)throw new TypeError("Argument `url` should be a string");super(),this.statusCode=e,this.headers=n(t),this.body=r,this.url=A}_read(){this.push(this.body),this.push(null)}}},2383:e=>{"use strict";e.exports=function(e){var t=new e,r=t;return{get:function(){var A=t;return A.next?t=A.next:(t=new e,r=t),A.next=null,A},release:function(e){r.next=e,r=e}}}},69078:e=>{e.exports=function(e,t){var r,A,n,o=!0;Array.isArray(e)?(r=[],A=e.length):(n=Object.keys(e),r={},A=n.length);function i(e){function A(){t&&t(e,r),t=null}o?process.nextTick(A):A()}function s(e,t,n){r[e]=n,(0==--A||t)&&i(t)}A?n?n.forEach((function(t){e[t]((function(e,r){s(t,e,r)}))})):e.forEach((function(e,t){e((function(e,r){s(t,e,r)}))})):i(null);o=!1}},13499:(e,t,r)=>{var A=r(64293),n=A.Buffer;function o(e,t){for(var r in e)t[r]=e[r]}function i(e,t,r){return n(e,t,r)}n.from&&n.alloc&&n.allocUnsafe&&n.allocUnsafeSlow?e.exports=A:(o(A,t),t.Buffer=i),o(n,i),i.from=function(e,t,r){if("number"==typeof e)throw new TypeError("Argument must not be a number");return n(e,t,r)},i.alloc=function(e,t,r){if("number"!=typeof e)throw new TypeError("Argument must be a number");var A=n(e);return void 0!==t?"string"==typeof r?A.fill(t,r):A.fill(t):A.fill(0),A},i.allocUnsafe=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return n(e)},i.allocUnsafeSlow=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return A.SlowBuffer(e)}},95584:(e,t)=>{var r;t=e.exports=l,r="object"==typeof process&&process.env&&process.env.NODE_DEBUG&&/\bsemver\b/i.test(process.env.NODE_DEBUG)?function(){var e=Array.prototype.slice.call(arguments,0);e.unshift("SEMVER"),console.log.apply(console,e)}:function(){},t.SEMVER_SPEC_VERSION="2.0.0";var A=Number.MAX_SAFE_INTEGER||9007199254740991,n=t.re=[],o=t.src=[],i=t.tokens={},s=0;function a(e){i[e]=s++}a("NUMERICIDENTIFIER"),o[i.NUMERICIDENTIFIER]="0|[1-9]\\d*",a("NUMERICIDENTIFIERLOOSE"),o[i.NUMERICIDENTIFIERLOOSE]="[0-9]+",a("NONNUMERICIDENTIFIER"),o[i.NONNUMERICIDENTIFIER]="\\d*[a-zA-Z-][a-zA-Z0-9-]*",a("MAINVERSION"),o[i.MAINVERSION]="("+o[i.NUMERICIDENTIFIER]+")\\.("+o[i.NUMERICIDENTIFIER]+")\\.("+o[i.NUMERICIDENTIFIER]+")",a("MAINVERSIONLOOSE"),o[i.MAINVERSIONLOOSE]="("+o[i.NUMERICIDENTIFIERLOOSE]+")\\.("+o[i.NUMERICIDENTIFIERLOOSE]+")\\.("+o[i.NUMERICIDENTIFIERLOOSE]+")",a("PRERELEASEIDENTIFIER"),o[i.PRERELEASEIDENTIFIER]="(?:"+o[i.NUMERICIDENTIFIER]+"|"+o[i.NONNUMERICIDENTIFIER]+")",a("PRERELEASEIDENTIFIERLOOSE"),o[i.PRERELEASEIDENTIFIERLOOSE]="(?:"+o[i.NUMERICIDENTIFIERLOOSE]+"|"+o[i.NONNUMERICIDENTIFIER]+")",a("PRERELEASE"),o[i.PRERELEASE]="(?:-("+o[i.PRERELEASEIDENTIFIER]+"(?:\\."+o[i.PRERELEASEIDENTIFIER]+")*))",a("PRERELEASELOOSE"),o[i.PRERELEASELOOSE]="(?:-?("+o[i.PRERELEASEIDENTIFIERLOOSE]+"(?:\\."+o[i.PRERELEASEIDENTIFIERLOOSE]+")*))",a("BUILDIDENTIFIER"),o[i.BUILDIDENTIFIER]="[0-9A-Za-z-]+",a("BUILD"),o[i.BUILD]="(?:\\+("+o[i.BUILDIDENTIFIER]+"(?:\\."+o[i.BUILDIDENTIFIER]+")*))",a("FULL"),a("FULLPLAIN"),o[i.FULLPLAIN]="v?"+o[i.MAINVERSION]+o[i.PRERELEASE]+"?"+o[i.BUILD]+"?",o[i.FULL]="^"+o[i.FULLPLAIN]+"$",a("LOOSEPLAIN"),o[i.LOOSEPLAIN]="[v=\\s]*"+o[i.MAINVERSIONLOOSE]+o[i.PRERELEASELOOSE]+"?"+o[i.BUILD]+"?",a("LOOSE"),o[i.LOOSE]="^"+o[i.LOOSEPLAIN]+"$",a("GTLT"),o[i.GTLT]="((?:<|>)?=?)",a("XRANGEIDENTIFIERLOOSE"),o[i.XRANGEIDENTIFIERLOOSE]=o[i.NUMERICIDENTIFIERLOOSE]+"|x|X|\\*",a("XRANGEIDENTIFIER"),o[i.XRANGEIDENTIFIER]=o[i.NUMERICIDENTIFIER]+"|x|X|\\*",a("XRANGEPLAIN"),o[i.XRANGEPLAIN]="[v=\\s]*("+o[i.XRANGEIDENTIFIER]+")(?:\\.("+o[i.XRANGEIDENTIFIER]+")(?:\\.("+o[i.XRANGEIDENTIFIER]+")(?:"+o[i.PRERELEASE]+")?"+o[i.BUILD]+"?)?)?",a("XRANGEPLAINLOOSE"),o[i.XRANGEPLAINLOOSE]="[v=\\s]*("+o[i.XRANGEIDENTIFIERLOOSE]+")(?:\\.("+o[i.XRANGEIDENTIFIERLOOSE]+")(?:\\.("+o[i.XRANGEIDENTIFIERLOOSE]+")(?:"+o[i.PRERELEASELOOSE]+")?"+o[i.BUILD]+"?)?)?",a("XRANGE"),o[i.XRANGE]="^"+o[i.GTLT]+"\\s*"+o[i.XRANGEPLAIN]+"$",a("XRANGELOOSE"),o[i.XRANGELOOSE]="^"+o[i.GTLT]+"\\s*"+o[i.XRANGEPLAINLOOSE]+"$",a("COERCE"),o[i.COERCE]="(^|[^\\d])(\\d{1,16})(?:\\.(\\d{1,16}))?(?:\\.(\\d{1,16}))?(?:$|[^\\d])",a("COERCERTL"),n[i.COERCERTL]=new RegExp(o[i.COERCE],"g"),a("LONETILDE"),o[i.LONETILDE]="(?:~>?)",a("TILDETRIM"),o[i.TILDETRIM]="(\\s*)"+o[i.LONETILDE]+"\\s+",n[i.TILDETRIM]=new RegExp(o[i.TILDETRIM],"g");a("TILDE"),o[i.TILDE]="^"+o[i.LONETILDE]+o[i.XRANGEPLAIN]+"$",a("TILDELOOSE"),o[i.TILDELOOSE]="^"+o[i.LONETILDE]+o[i.XRANGEPLAINLOOSE]+"$",a("LONECARET"),o[i.LONECARET]="(?:\\^)",a("CARETTRIM"),o[i.CARETTRIM]="(\\s*)"+o[i.LONECARET]+"\\s+",n[i.CARETTRIM]=new RegExp(o[i.CARETTRIM],"g");a("CARET"),o[i.CARET]="^"+o[i.LONECARET]+o[i.XRANGEPLAIN]+"$",a("CARETLOOSE"),o[i.CARETLOOSE]="^"+o[i.LONECARET]+o[i.XRANGEPLAINLOOSE]+"$",a("COMPARATORLOOSE"),o[i.COMPARATORLOOSE]="^"+o[i.GTLT]+"\\s*("+o[i.LOOSEPLAIN]+")$|^$",a("COMPARATOR"),o[i.COMPARATOR]="^"+o[i.GTLT]+"\\s*("+o[i.FULLPLAIN]+")$|^$",a("COMPARATORTRIM"),o[i.COMPARATORTRIM]="(\\s*)"+o[i.GTLT]+"\\s*("+o[i.LOOSEPLAIN]+"|"+o[i.XRANGEPLAIN]+")",n[i.COMPARATORTRIM]=new RegExp(o[i.COMPARATORTRIM],"g");a("HYPHENRANGE"),o[i.HYPHENRANGE]="^\\s*("+o[i.XRANGEPLAIN]+")\\s+-\\s+("+o[i.XRANGEPLAIN]+")\\s*$",a("HYPHENRANGELOOSE"),o[i.HYPHENRANGELOOSE]="^\\s*("+o[i.XRANGEPLAINLOOSE]+")\\s+-\\s+("+o[i.XRANGEPLAINLOOSE]+")\\s*$",a("STAR"),o[i.STAR]="(<|>)?=?\\s*\\*";for(var c=0;c256)return null;if(!(t.loose?n[i.LOOSE]:n[i.FULL]).test(e))return null;try{return new l(e,t)}catch(e){return null}}function l(e,t){if(t&&"object"==typeof t||(t={loose:!!t,includePrerelease:!1}),e instanceof l){if(e.loose===t.loose)return e;e=e.version}else if("string"!=typeof e)throw new TypeError("Invalid Version: "+e);if(e.length>256)throw new TypeError("version is longer than 256 characters");if(!(this instanceof l))return new l(e,t);r("SemVer",e,t),this.options=t,this.loose=!!t.loose;var o=e.trim().match(t.loose?n[i.LOOSE]:n[i.FULL]);if(!o)throw new TypeError("Invalid Version: "+e);if(this.raw=e,this.major=+o[1],this.minor=+o[2],this.patch=+o[3],this.major>A||this.major<0)throw new TypeError("Invalid major version");if(this.minor>A||this.minor<0)throw new TypeError("Invalid minor version");if(this.patch>A||this.patch<0)throw new TypeError("Invalid patch version");o[4]?this.prerelease=o[4].split(".").map((function(e){if(/^[0-9]+$/.test(e)){var t=+e;if(t>=0&&t=0;)"number"==typeof this.prerelease[r]&&(this.prerelease[r]++,r=-2);-1===r&&this.prerelease.push(0)}t&&(this.prerelease[0]===t?isNaN(this.prerelease[1])&&(this.prerelease=[t,0]):this.prerelease=[t,0]);break;default:throw new Error("invalid increment argument: "+e)}return this.format(),this.raw=this.version,this},t.inc=function(e,t,r,A){"string"==typeof r&&(A=r,r=void 0);try{return new l(e,r).inc(t,A).version}catch(e){return null}},t.diff=function(e,t){if(f(e,t))return null;var r=g(e),A=g(t),n="";if(r.prerelease.length||A.prerelease.length){n="pre";var o="prerelease"}for(var i in r)if(("major"===i||"minor"===i||"patch"===i)&&r[i]!==A[i])return n+i;return o},t.compareIdentifiers=h;var u=/^[0-9]+$/;function h(e,t){var r=u.test(e),A=u.test(t);return r&&A&&(e=+e,t=+t),e===t?0:r&&!A?-1:A&&!r?1:e0}function C(e,t,r){return p(e,t,r)<0}function f(e,t,r){return 0===p(e,t,r)}function I(e,t,r){return 0!==p(e,t,r)}function E(e,t,r){return p(e,t,r)>=0}function B(e,t,r){return p(e,t,r)<=0}function y(e,t,r,A){switch(t){case"===":return"object"==typeof e&&(e=e.version),"object"==typeof r&&(r=r.version),e===r;case"!==":return"object"==typeof e&&(e=e.version),"object"==typeof r&&(r=r.version),e!==r;case"":case"=":case"==":return f(e,r,A);case"!=":return I(e,r,A);case">":return d(e,r,A);case">=":return E(e,r,A);case"<":return C(e,r,A);case"<=":return B(e,r,A);default:throw new TypeError("Invalid operator: "+t)}}function m(e,t){if(t&&"object"==typeof t||(t={loose:!!t,includePrerelease:!1}),e instanceof m){if(e.loose===!!t.loose)return e;e=e.value}if(!(this instanceof m))return new m(e,t);r("comparator",e,t),this.options=t,this.loose=!!t.loose,this.parse(e),this.semver===w?this.value="":this.value=this.operator+this.semver.version,r("comp",this)}t.rcompareIdentifiers=function(e,t){return h(t,e)},t.major=function(e,t){return new l(e,t).major},t.minor=function(e,t){return new l(e,t).minor},t.patch=function(e,t){return new l(e,t).patch},t.compare=p,t.compareLoose=function(e,t){return p(e,t,!0)},t.compareBuild=function(e,t,r){var A=new l(e,r),n=new l(t,r);return A.compare(n)||A.compareBuild(n)},t.rcompare=function(e,t,r){return p(t,e,r)},t.sort=function(e,r){return e.sort((function(e,A){return t.compareBuild(e,A,r)}))},t.rsort=function(e,r){return e.sort((function(e,A){return t.compareBuild(A,e,r)}))},t.gt=d,t.lt=C,t.eq=f,t.neq=I,t.gte=E,t.lte=B,t.cmp=y,t.Comparator=m;var w={};function Q(e,t){if(t&&"object"==typeof t||(t={loose:!!t,includePrerelease:!1}),e instanceof Q)return e.loose===!!t.loose&&e.includePrerelease===!!t.includePrerelease?e:new Q(e.raw,t);if(e instanceof m)return new Q(e.value,t);if(!(this instanceof Q))return new Q(e,t);if(this.options=t,this.loose=!!t.loose,this.includePrerelease=!!t.includePrerelease,this.raw=e,this.set=e.split(/\s*\|\|\s*/).map((function(e){return this.parseRange(e.trim())}),this).filter((function(e){return e.length})),!this.set.length)throw new TypeError("Invalid SemVer Range: "+e);this.format()}function D(e,t){for(var r=!0,A=e.slice(),n=A.pop();r&&A.length;)r=A.every((function(e){return n.intersects(e,t)})),n=A.pop();return r}function b(e){return!e||"x"===e.toLowerCase()||"*"===e}function v(e,t,r,A,n,o,i,s,a,c,g,l,u){return((t=b(r)?"":b(A)?">="+r+".0.0":b(n)?">="+r+"."+A+".0":">="+t)+" "+(s=b(a)?"":b(c)?"<"+(+a+1)+".0.0":b(g)?"<"+a+"."+(+c+1)+".0":l?"<="+a+"."+c+"."+g+"-"+l:"<="+s)).trim()}function S(e,t,A){for(var n=0;n0){var o=e[n].semver;if(o.major===t.major&&o.minor===t.minor&&o.patch===t.patch)return!0}return!1}return!0}function k(e,t,r){try{t=new Q(t,r)}catch(e){return!1}return t.test(e)}function N(e,t,r,A){var n,o,i,s,a;switch(e=new l(e,A),t=new Q(t,A),r){case">":n=d,o=B,i=C,s=">",a=">=";break;case"<":n=C,o=E,i=d,s="<",a="<=";break;default:throw new TypeError('Must provide a hilo val of "<" or ">"')}if(k(e,t,A))return!1;for(var c=0;c=0.0.0")),u=u||e,h=h||e,n(e.semver,u.semver,A)?u=e:i(e.semver,h.semver,A)&&(h=e)})),u.operator===s||u.operator===a)return!1;if((!h.operator||h.operator===s)&&o(e,h.semver))return!1;if(h.operator===a&&i(e,h.semver))return!1}return!0}m.prototype.parse=function(e){var t=this.options.loose?n[i.COMPARATORLOOSE]:n[i.COMPARATOR],r=e.match(t);if(!r)throw new TypeError("Invalid comparator: "+e);this.operator=void 0!==r[1]?r[1]:"","="===this.operator&&(this.operator=""),r[2]?this.semver=new l(r[2],this.options.loose):this.semver=w},m.prototype.toString=function(){return this.value},m.prototype.test=function(e){if(r("Comparator.test",e,this.options.loose),this.semver===w||e===w)return!0;if("string"==typeof e)try{e=new l(e,this.options)}catch(e){return!1}return y(e,this.operator,this.semver,this.options)},m.prototype.intersects=function(e,t){if(!(e instanceof m))throw new TypeError("a Comparator is required");var r;if(t&&"object"==typeof t||(t={loose:!!t,includePrerelease:!1}),""===this.operator)return""===this.value||(r=new Q(e.value,t),k(this.value,r,t));if(""===e.operator)return""===e.value||(r=new Q(this.value,t),k(e.semver,r,t));var A=!(">="!==this.operator&&">"!==this.operator||">="!==e.operator&&">"!==e.operator),n=!("<="!==this.operator&&"<"!==this.operator||"<="!==e.operator&&"<"!==e.operator),o=this.semver.version===e.semver.version,i=!(">="!==this.operator&&"<="!==this.operator||">="!==e.operator&&"<="!==e.operator),s=y(this.semver,"<",e.semver,t)&&(">="===this.operator||">"===this.operator)&&("<="===e.operator||"<"===e.operator),a=y(this.semver,">",e.semver,t)&&("<="===this.operator||"<"===this.operator)&&(">="===e.operator||">"===e.operator);return A||n||o&&i||s||a},t.Range=Q,Q.prototype.format=function(){return this.range=this.set.map((function(e){return e.join(" ").trim()})).join("||").trim(),this.range},Q.prototype.toString=function(){return this.range},Q.prototype.parseRange=function(e){var t=this.options.loose;e=e.trim();var A=t?n[i.HYPHENRANGELOOSE]:n[i.HYPHENRANGE];e=e.replace(A,v),r("hyphen replace",e),e=e.replace(n[i.COMPARATORTRIM],"$1$2$3"),r("comparator trim",e,n[i.COMPARATORTRIM]),e=(e=(e=e.replace(n[i.TILDETRIM],"$1~")).replace(n[i.CARETTRIM],"$1^")).split(/\s+/).join(" ");var o=t?n[i.COMPARATORLOOSE]:n[i.COMPARATOR],s=e.split(" ").map((function(e){return function(e,t){return r("comp",e,t),e=function(e,t){return e.trim().split(/\s+/).map((function(e){return function(e,t){r("caret",e,t);var A=t.loose?n[i.CARETLOOSE]:n[i.CARET];return e.replace(A,(function(t,A,n,o,i){var s;return r("caret",e,t,A,n,o,i),b(A)?s="":b(n)?s=">="+A+".0.0 <"+(+A+1)+".0.0":b(o)?s="0"===A?">="+A+"."+n+".0 <"+A+"."+(+n+1)+".0":">="+A+"."+n+".0 <"+(+A+1)+".0.0":i?(r("replaceCaret pr",i),s="0"===A?"0"===n?">="+A+"."+n+"."+o+"-"+i+" <"+A+"."+n+"."+(+o+1):">="+A+"."+n+"."+o+"-"+i+" <"+A+"."+(+n+1)+".0":">="+A+"."+n+"."+o+"-"+i+" <"+(+A+1)+".0.0"):(r("no pr"),s="0"===A?"0"===n?">="+A+"."+n+"."+o+" <"+A+"."+n+"."+(+o+1):">="+A+"."+n+"."+o+" <"+A+"."+(+n+1)+".0":">="+A+"."+n+"."+o+" <"+(+A+1)+".0.0"),r("caret return",s),s}))}(e,t)})).join(" ")}(e,t),r("caret",e),e=function(e,t){return e.trim().split(/\s+/).map((function(e){return function(e,t){var A=t.loose?n[i.TILDELOOSE]:n[i.TILDE];return e.replace(A,(function(t,A,n,o,i){var s;return r("tilde",e,t,A,n,o,i),b(A)?s="":b(n)?s=">="+A+".0.0 <"+(+A+1)+".0.0":b(o)?s=">="+A+"."+n+".0 <"+A+"."+(+n+1)+".0":i?(r("replaceTilde pr",i),s=">="+A+"."+n+"."+o+"-"+i+" <"+A+"."+(+n+1)+".0"):s=">="+A+"."+n+"."+o+" <"+A+"."+(+n+1)+".0",r("tilde return",s),s}))}(e,t)})).join(" ")}(e,t),r("tildes",e),e=function(e,t){return r("replaceXRanges",e,t),e.split(/\s+/).map((function(e){return function(e,t){e=e.trim();var A=t.loose?n[i.XRANGELOOSE]:n[i.XRANGE];return e.replace(A,(function(A,n,o,i,s,a){r("xRange",e,A,n,o,i,s,a);var c=b(o),g=c||b(i),l=g||b(s),u=l;return"="===n&&u&&(n=""),a=t.includePrerelease?"-0":"",c?A=">"===n||"<"===n?"<0.0.0-0":"*":n&&u?(g&&(i=0),s=0,">"===n?(n=">=",g?(o=+o+1,i=0,s=0):(i=+i+1,s=0)):"<="===n&&(n="<",g?o=+o+1:i=+i+1),A=n+o+"."+i+"."+s+a):g?A=">="+o+".0.0"+a+" <"+(+o+1)+".0.0"+a:l&&(A=">="+o+"."+i+".0"+a+" <"+o+"."+(+i+1)+".0"+a),r("xRange return",A),A}))}(e,t)})).join(" ")}(e,t),r("xrange",e),e=function(e,t){return r("replaceStars",e,t),e.trim().replace(n[i.STAR],"")}(e,t),r("stars",e),e}(e,this.options)}),this).join(" ").split(/\s+/);return this.options.loose&&(s=s.filter((function(e){return!!e.match(o)}))),s=s.map((function(e){return new m(e,this.options)}),this)},Q.prototype.intersects=function(e,t){if(!(e instanceof Q))throw new TypeError("a Range is required");return this.set.some((function(r){return D(r,t)&&e.set.some((function(e){return D(e,t)&&r.every((function(r){return e.every((function(e){return r.intersects(e,t)}))}))}))}))},t.toComparators=function(e,t){return new Q(e,t).set.map((function(e){return e.map((function(e){return e.value})).join(" ").trim().split(" ")}))},Q.prototype.test=function(e){if(!e)return!1;if("string"==typeof e)try{e=new l(e,this.options)}catch(e){return!1}for(var t=0;t":0===t.prerelease.length?t.patch++:t.prerelease.push(0),t.raw=t.format();case"":case">=":r&&!d(r,t)||(r=t);break;case"<":case"<=":break;default:throw new Error("Unexpected operation: "+e.operator)}}))}if(r&&e.test(r))return r;return null},t.validRange=function(e,t){try{return new Q(e,t).range||"*"}catch(e){return null}},t.ltr=function(e,t,r){return N(e,t,"<",r)},t.gtr=function(e,t,r){return N(e,t,">",r)},t.outside=N,t.prerelease=function(e,t){var r=g(e,t);return r&&r.prerelease.length?r.prerelease:null},t.intersects=function(e,t,r){return e=new Q(e,r),t=new Q(t,r),e.intersects(t)},t.coerce=function(e,t){if(e instanceof l)return e;"number"==typeof e&&(e=String(e));if("string"!=typeof e)return null;var r=null;if((t=t||{}).rtl){for(var A;(A=n[i.COERCERTL].exec(e))&&(!r||r.index+r[0].length!==e.length);)r&&A.index+A[0].length===r.index+r[0].length||(r=A),n[i.COERCERTL].lastIndex=A.index+A[1].length+A[2].length;n[i.COERCERTL].lastIndex=-1}else r=e.match(n[i.COERCE]);if(null===r)return null;return g(r[2]+"."+(r[3]||"0")+"."+(r[4]||"0"),t)}},29069:(e,t,r)=>{const A=Symbol("SemVer ANY");class n{static get ANY(){return A}constructor(e,t){if(t&&"object"==typeof t||(t={loose:!!t,includePrerelease:!1}),e instanceof n){if(e.loose===!!t.loose)return e;e=e.value}a("comparator",e,t),this.options=t,this.loose=!!t.loose,this.parse(e),this.semver===A?this.value="":this.value=this.operator+this.semver.version,a("comp",this)}parse(e){const t=this.options.loose?o[i.COMPARATORLOOSE]:o[i.COMPARATOR],r=e.match(t);if(!r)throw new TypeError("Invalid comparator: "+e);this.operator=void 0!==r[1]?r[1]:"","="===this.operator&&(this.operator=""),r[2]?this.semver=new c(r[2],this.options.loose):this.semver=A}toString(){return this.value}test(e){if(a("Comparator.test",e,this.options.loose),this.semver===A||e===A)return!0;if("string"==typeof e)try{e=new c(e,this.options)}catch(e){return!1}return s(e,this.operator,this.semver,this.options)}intersects(e,t){if(!(e instanceof n))throw new TypeError("a Comparator is required");if(t&&"object"==typeof t||(t={loose:!!t,includePrerelease:!1}),""===this.operator)return""===this.value||new g(e.value,t).test(this.value);if(""===e.operator)return""===e.value||new g(this.value,t).test(e.semver);const r=!(">="!==this.operator&&">"!==this.operator||">="!==e.operator&&">"!==e.operator),A=!("<="!==this.operator&&"<"!==this.operator||"<="!==e.operator&&"<"!==e.operator),o=this.semver.version===e.semver.version,i=!(">="!==this.operator&&"<="!==this.operator||">="!==e.operator&&"<="!==e.operator),a=s(this.semver,"<",e.semver,t)&&(">="===this.operator||">"===this.operator)&&("<="===e.operator||"<"===e.operator),c=s(this.semver,">",e.semver,t)&&("<="===this.operator||"<"===this.operator)&&(">="===e.operator||">"===e.operator);return r||A||o&&i||a||c}}e.exports=n;const{re:o,t:i}=r(49439),s=r(38754),a=r(6029),c=r(14772),g=r(73004)},73004:(e,t,r)=>{class A{constructor(e,t){if(t&&"object"==typeof t||(t={loose:!!t,includePrerelease:!1}),e instanceof A)return e.loose===!!t.loose&&e.includePrerelease===!!t.includePrerelease?e:new A(e.raw,t);if(e instanceof n)return this.raw=e.value,this.set=[[e]],this.format(),this;if(this.options=t,this.loose=!!t.loose,this.includePrerelease=!!t.includePrerelease,this.raw=e,this.set=e.split(/\s*\|\|\s*/).map(e=>this.parseRange(e.trim())).filter(e=>e.length),!this.set.length)throw new TypeError("Invalid SemVer Range: "+e);this.format()}format(){return this.range=this.set.map(e=>e.join(" ").trim()).join("||").trim(),this.range}toString(){return this.range}parseRange(e){const t=this.options.loose;e=e.trim();const r=t?s[a.HYPHENRANGELOOSE]:s[a.HYPHENRANGE];e=e.replace(r,w(this.options.includePrerelease)),o("hyphen replace",e),e=e.replace(s[a.COMPARATORTRIM],c),o("comparator trim",e,s[a.COMPARATORTRIM]),e=(e=(e=e.replace(s[a.TILDETRIM],g)).replace(s[a.CARETTRIM],l)).split(/\s+/).join(" ");const A=t?s[a.COMPARATORLOOSE]:s[a.COMPARATOR];return e.split(" ").map(e=>h(e,this.options)).join(" ").split(/\s+/).map(e=>m(e,this.options)).filter(this.options.loose?e=>!!e.match(A):()=>!0).map(e=>new n(e,this.options))}intersects(e,t){if(!(e instanceof A))throw new TypeError("a Range is required");return this.set.some(r=>u(r,t)&&e.set.some(e=>u(e,t)&&r.every(r=>e.every(e=>r.intersects(e,t)))))}test(e){if(!e)return!1;if("string"==typeof e)try{e=new i(e,this.options)}catch(e){return!1}for(let t=0;t{let r=!0;const A=e.slice();let n=A.pop();for(;r&&A.length;)r=A.every(e=>n.intersects(e,t)),n=A.pop();return r},h=(e,t)=>(o("comp",e,t),e=f(e,t),o("caret",e),e=d(e,t),o("tildes",e),e=E(e,t),o("xrange",e),e=y(e,t),o("stars",e),e),p=e=>!e||"x"===e.toLowerCase()||"*"===e,d=(e,t)=>e.trim().split(/\s+/).map(e=>C(e,t)).join(" "),C=(e,t)=>{const r=t.loose?s[a.TILDELOOSE]:s[a.TILDE];return e.replace(r,(t,r,A,n,i)=>{let s;return o("tilde",e,t,r,A,n,i),p(r)?s="":p(A)?s=`>=${r}.0.0 <${+r+1}.0.0-0`:p(n)?s=`>=${r}.${A}.0 <${r}.${+A+1}.0-0`:i?(o("replaceTilde pr",i),s=`>=${r}.${A}.${n}-${i} <${r}.${+A+1}.0-0`):s=`>=${r}.${A}.${n} <${r}.${+A+1}.0-0`,o("tilde return",s),s})},f=(e,t)=>e.trim().split(/\s+/).map(e=>I(e,t)).join(" "),I=(e,t)=>{o("caret",e,t);const r=t.loose?s[a.CARETLOOSE]:s[a.CARET],A=t.includePrerelease?"-0":"";return e.replace(r,(t,r,n,i,s)=>{let a;return o("caret",e,t,r,n,i,s),p(r)?a="":p(n)?a=`>=${r}.0.0${A} <${+r+1}.0.0-0`:p(i)?a="0"===r?`>=${r}.${n}.0${A} <${r}.${+n+1}.0-0`:`>=${r}.${n}.0${A} <${+r+1}.0.0-0`:s?(o("replaceCaret pr",s),a="0"===r?"0"===n?`>=${r}.${n}.${i}-${s} <${r}.${n}.${+i+1}-0`:`>=${r}.${n}.${i}-${s} <${r}.${+n+1}.0-0`:`>=${r}.${n}.${i}-${s} <${+r+1}.0.0-0`):(o("no pr"),a="0"===r?"0"===n?`>=${r}.${n}.${i}${A} <${r}.${n}.${+i+1}-0`:`>=${r}.${n}.${i}${A} <${r}.${+n+1}.0-0`:`>=${r}.${n}.${i} <${+r+1}.0.0-0`),o("caret return",a),a})},E=(e,t)=>(o("replaceXRanges",e,t),e.split(/\s+/).map(e=>B(e,t)).join(" ")),B=(e,t)=>{e=e.trim();const r=t.loose?s[a.XRANGELOOSE]:s[a.XRANGE];return e.replace(r,(r,A,n,i,s,a)=>{o("xRange",e,r,A,n,i,s,a);const c=p(n),g=c||p(i),l=g||p(s),u=l;return"="===A&&u&&(A=""),a=t.includePrerelease?"-0":"",c?r=">"===A||"<"===A?"<0.0.0-0":"*":A&&u?(g&&(i=0),s=0,">"===A?(A=">=",g?(n=+n+1,i=0,s=0):(i=+i+1,s=0)):"<="===A&&(A="<",g?n=+n+1:i=+i+1),"<"===A&&(a="-0"),r=`${A+n}.${i}.${s}${a}`):g?r=`>=${n}.0.0${a} <${+n+1}.0.0-0`:l&&(r=`>=${n}.${i}.0${a} <${n}.${+i+1}.0-0`),o("xRange return",r),r})},y=(e,t)=>(o("replaceStars",e,t),e.trim().replace(s[a.STAR],"")),m=(e,t)=>(o("replaceGTE0",e,t),e.trim().replace(s[t.includePrerelease?a.GTE0PRE:a.GTE0],"")),w=e=>(t,r,A,n,o,i,s,a,c,g,l,u,h)=>`${r=p(A)?"":p(n)?`>=${A}.0.0${e?"-0":""}`:p(o)?`>=${A}.${n}.0${e?"-0":""}`:i?">="+r:`>=${r}${e?"-0":""}`} ${a=p(c)?"":p(g)?`<${+c+1}.0.0-0`:p(l)?`<${c}.${+g+1}.0-0`:u?`<=${c}.${g}.${l}-${u}`:e?`<${c}.${g}.${+l+1}-0`:"<="+a}`.trim(),Q=(e,t,r)=>{for(let r=0;r0){const A=e[r].semver;if(A.major===t.major&&A.minor===t.minor&&A.patch===t.patch)return!0}return!1}return!0}},14772:(e,t,r)=>{const A=r(6029),{MAX_LENGTH:n,MAX_SAFE_INTEGER:o}=r(76483),{re:i,t:s}=r(49439),{compareIdentifiers:a}=r(99297);class c{constructor(e,t){if(t&&"object"==typeof t||(t={loose:!!t,includePrerelease:!1}),e instanceof c){if(e.loose===!!t.loose&&e.includePrerelease===!!t.includePrerelease)return e;e=e.version}else if("string"!=typeof e)throw new TypeError("Invalid Version: "+e);if(e.length>n)throw new TypeError(`version is longer than ${n} characters`);A("SemVer",e,t),this.options=t,this.loose=!!t.loose,this.includePrerelease=!!t.includePrerelease;const r=e.trim().match(t.loose?i[s.LOOSE]:i[s.FULL]);if(!r)throw new TypeError("Invalid Version: "+e);if(this.raw=e,this.major=+r[1],this.minor=+r[2],this.patch=+r[3],this.major>o||this.major<0)throw new TypeError("Invalid major version");if(this.minor>o||this.minor<0)throw new TypeError("Invalid minor version");if(this.patch>o||this.patch<0)throw new TypeError("Invalid patch version");r[4]?this.prerelease=r[4].split(".").map(e=>{if(/^[0-9]+$/.test(e)){const t=+e;if(t>=0&&t=0;)"number"==typeof this.prerelease[e]&&(this.prerelease[e]++,e=-2);-1===e&&this.prerelease.push(0)}t&&(this.prerelease[0]===t?isNaN(this.prerelease[1])&&(this.prerelease=[t,0]):this.prerelease=[t,0]);break;default:throw new Error("invalid increment argument: "+e)}return this.format(),this.raw=this.version,this}}e.exports=c},31192:(e,t,r)=>{const A=r(21883);e.exports=(e,t)=>{const r=A(e.trim().replace(/^[=v]+/,""),t);return r?r.version:null}},38754:(e,t,r)=>{const A=r(78760),n=r(83286),o=r(26544),i=r(44984),s=r(65069),a=r(93845);e.exports=(e,t,r,c)=>{switch(t){case"===":return"object"==typeof e&&(e=e.version),"object"==typeof r&&(r=r.version),e===r;case"!==":return"object"==typeof e&&(e=e.version),"object"==typeof r&&(r=r.version),e!==r;case"":case"=":case"==":return A(e,r,c);case"!=":return n(e,r,c);case">":return o(e,r,c);case">=":return i(e,r,c);case"<":return s(e,r,c);case"<=":return a(e,r,c);default:throw new TypeError("Invalid operator: "+t)}}},38113:(e,t,r)=>{const A=r(14772),n=r(21883),{re:o,t:i}=r(49439);e.exports=(e,t)=>{if(e instanceof A)return e;if("number"==typeof e&&(e=String(e)),"string"!=typeof e)return null;let r=null;if((t=t||{}).rtl){let t;for(;(t=o[i.COERCERTL].exec(e))&&(!r||r.index+r[0].length!==e.length);)r&&t.index+t[0].length===r.index+r[0].length||(r=t),o[i.COERCERTL].lastIndex=t.index+t[1].length+t[2].length;o[i.COERCERTL].lastIndex=-1}else r=e.match(o[i.COERCE]);return null===r?null:n(`${r[2]}.${r[3]||"0"}.${r[4]||"0"}`,t)}},63353:(e,t,r)=>{const A=r(14772);e.exports=(e,t,r)=>{const n=new A(e,r),o=new A(t,r);return n.compare(o)||n.compareBuild(o)}},58566:(e,t,r)=>{const A=r(17340);e.exports=(e,t)=>A(e,t,!0)},17340:(e,t,r)=>{const A=r(14772);e.exports=(e,t,r)=>new A(e,r).compare(new A(t,r))},29301:(e,t,r)=>{const A=r(21883),n=r(78760);e.exports=(e,t)=>{if(n(e,t))return null;{const r=A(e),n=A(t),o=r.prerelease.length||n.prerelease.length,i=o?"pre":"",s=o?"prerelease":"";for(const e in r)if(("major"===e||"minor"===e||"patch"===e)&&r[e]!==n[e])return i+e;return s}}},78760:(e,t,r)=>{const A=r(17340);e.exports=(e,t,r)=>0===A(e,t,r)},26544:(e,t,r)=>{const A=r(17340);e.exports=(e,t,r)=>A(e,t,r)>0},44984:(e,t,r)=>{const A=r(17340);e.exports=(e,t,r)=>A(e,t,r)>=0},24063:(e,t,r)=>{const A=r(14772);e.exports=(e,t,r,n)=>{"string"==typeof r&&(n=r,r=void 0);try{return new A(e,r).inc(t,n).version}catch(e){return null}}},65069:(e,t,r)=>{const A=r(17340);e.exports=(e,t,r)=>A(e,t,r)<0},93845:(e,t,r)=>{const A=r(17340);e.exports=(e,t,r)=>A(e,t,r)<=0},75157:(e,t,r)=>{const A=r(14772);e.exports=(e,t)=>new A(e,t).major},5195:(e,t,r)=>{const A=r(14772);e.exports=(e,t)=>new A(e,t).minor},83286:(e,t,r)=>{const A=r(17340);e.exports=(e,t,r)=>0!==A(e,t,r)},21883:(e,t,r)=>{const{MAX_LENGTH:A}=r(76483),{re:n,t:o}=r(49439),i=r(14772);e.exports=(e,t)=>{if(t&&"object"==typeof t||(t={loose:!!t,includePrerelease:!1}),e instanceof i)return e;if("string"!=typeof e)return null;if(e.length>A)return null;if(!(t.loose?n[o.LOOSE]:n[o.FULL]).test(e))return null;try{return new i(e,t)}catch(e){return null}}},39592:(e,t,r)=>{const A=r(14772);e.exports=(e,t)=>new A(e,t).patch},27050:(e,t,r)=>{const A=r(21883);e.exports=(e,t)=>{const r=A(e,t);return r&&r.prerelease.length?r.prerelease:null}},93788:(e,t,r)=>{const A=r(17340);e.exports=(e,t,r)=>A(t,e,r)},15213:(e,t,r)=>{const A=r(63353);e.exports=(e,t)=>e.sort((e,r)=>A(r,e,t))},73011:(e,t,r)=>{const A=r(73004);e.exports=(e,t,r)=>{try{t=new A(t,r)}catch(e){return!1}return t.test(e)}},71102:(e,t,r)=>{const A=r(63353);e.exports=(e,t)=>e.sort((e,r)=>A(e,r,t))},99589:(e,t,r)=>{const A=r(21883);e.exports=(e,t)=>{const r=A(e,t);return r?r.version:null}},53887:(e,t,r)=>{const A=r(49439);e.exports={re:A.re,src:A.src,tokens:A.t,SEMVER_SPEC_VERSION:r(76483).SEMVER_SPEC_VERSION,SemVer:r(14772),compareIdentifiers:r(99297).compareIdentifiers,rcompareIdentifiers:r(99297).rcompareIdentifiers,parse:r(21883),valid:r(99589),clean:r(31192),inc:r(24063),diff:r(29301),major:r(75157),minor:r(5195),patch:r(39592),prerelease:r(27050),compare:r(17340),rcompare:r(93788),compareLoose:r(58566),compareBuild:r(63353),sort:r(71102),rsort:r(15213),gt:r(26544),lt:r(65069),eq:r(78760),neq:r(83286),gte:r(44984),lte:r(93845),cmp:r(38754),coerce:r(38113),Comparator:r(29069),Range:r(73004),satisfies:r(73011),toComparators:r(47753),maxSatisfying:r(1895),minSatisfying:r(33252),minVersion:r(4224),validRange:r(44315),outside:r(842),gtr:r(69258),ltr:r(36928),intersects:r(87395),simplifyRange:r(3530),subset:r(74264)}},76483:e=>{const t=Number.MAX_SAFE_INTEGER||9007199254740991;e.exports={SEMVER_SPEC_VERSION:"2.0.0",MAX_LENGTH:256,MAX_SAFE_INTEGER:t,MAX_SAFE_COMPONENT_LENGTH:16}},6029:e=>{const t="object"==typeof process&&process.env&&process.env.NODE_DEBUG&&/\bsemver\b/i.test(process.env.NODE_DEBUG)?(...e)=>console.error("SEMVER",...e):()=>{};e.exports=t},99297:e=>{const t=/^[0-9]+$/,r=(e,r)=>{const A=t.test(e),n=t.test(r);return A&&n&&(e=+e,r=+r),e===r?0:A&&!n?-1:n&&!A?1:er(t,e)}},49439:(e,t,r)=>{const{MAX_SAFE_COMPONENT_LENGTH:A}=r(76483),n=r(6029),o=(t=e.exports={}).re=[],i=t.src=[],s=t.t={};let a=0;const c=(e,t,r)=>{const A=a++;n(A,t),s[e]=A,i[A]=t,o[A]=new RegExp(t,r?"g":void 0)};c("NUMERICIDENTIFIER","0|[1-9]\\d*"),c("NUMERICIDENTIFIERLOOSE","[0-9]+"),c("NONNUMERICIDENTIFIER","\\d*[a-zA-Z-][a-zA-Z0-9-]*"),c("MAINVERSION",`(${i[s.NUMERICIDENTIFIER]})\\.(${i[s.NUMERICIDENTIFIER]})\\.(${i[s.NUMERICIDENTIFIER]})`),c("MAINVERSIONLOOSE",`(${i[s.NUMERICIDENTIFIERLOOSE]})\\.(${i[s.NUMERICIDENTIFIERLOOSE]})\\.(${i[s.NUMERICIDENTIFIERLOOSE]})`),c("PRERELEASEIDENTIFIER",`(?:${i[s.NUMERICIDENTIFIER]}|${i[s.NONNUMERICIDENTIFIER]})`),c("PRERELEASEIDENTIFIERLOOSE",`(?:${i[s.NUMERICIDENTIFIERLOOSE]}|${i[s.NONNUMERICIDENTIFIER]})`),c("PRERELEASE",`(?:-(${i[s.PRERELEASEIDENTIFIER]}(?:\\.${i[s.PRERELEASEIDENTIFIER]})*))`),c("PRERELEASELOOSE",`(?:-?(${i[s.PRERELEASEIDENTIFIERLOOSE]}(?:\\.${i[s.PRERELEASEIDENTIFIERLOOSE]})*))`),c("BUILDIDENTIFIER","[0-9A-Za-z-]+"),c("BUILD",`(?:\\+(${i[s.BUILDIDENTIFIER]}(?:\\.${i[s.BUILDIDENTIFIER]})*))`),c("FULLPLAIN",`v?${i[s.MAINVERSION]}${i[s.PRERELEASE]}?${i[s.BUILD]}?`),c("FULL",`^${i[s.FULLPLAIN]}$`),c("LOOSEPLAIN",`[v=\\s]*${i[s.MAINVERSIONLOOSE]}${i[s.PRERELEASELOOSE]}?${i[s.BUILD]}?`),c("LOOSE",`^${i[s.LOOSEPLAIN]}$`),c("GTLT","((?:<|>)?=?)"),c("XRANGEIDENTIFIERLOOSE",i[s.NUMERICIDENTIFIERLOOSE]+"|x|X|\\*"),c("XRANGEIDENTIFIER",i[s.NUMERICIDENTIFIER]+"|x|X|\\*"),c("XRANGEPLAIN",`[v=\\s]*(${i[s.XRANGEIDENTIFIER]})(?:\\.(${i[s.XRANGEIDENTIFIER]})(?:\\.(${i[s.XRANGEIDENTIFIER]})(?:${i[s.PRERELEASE]})?${i[s.BUILD]}?)?)?`),c("XRANGEPLAINLOOSE",`[v=\\s]*(${i[s.XRANGEIDENTIFIERLOOSE]})(?:\\.(${i[s.XRANGEIDENTIFIERLOOSE]})(?:\\.(${i[s.XRANGEIDENTIFIERLOOSE]})(?:${i[s.PRERELEASELOOSE]})?${i[s.BUILD]}?)?)?`),c("XRANGE",`^${i[s.GTLT]}\\s*${i[s.XRANGEPLAIN]}$`),c("XRANGELOOSE",`^${i[s.GTLT]}\\s*${i[s.XRANGEPLAINLOOSE]}$`),c("COERCE",`(^|[^\\d])(\\d{1,${A}})(?:\\.(\\d{1,${A}}))?(?:\\.(\\d{1,${A}}))?(?:$|[^\\d])`),c("COERCERTL",i[s.COERCE],!0),c("LONETILDE","(?:~>?)"),c("TILDETRIM",`(\\s*)${i[s.LONETILDE]}\\s+`,!0),t.tildeTrimReplace="$1~",c("TILDE",`^${i[s.LONETILDE]}${i[s.XRANGEPLAIN]}$`),c("TILDELOOSE",`^${i[s.LONETILDE]}${i[s.XRANGEPLAINLOOSE]}$`),c("LONECARET","(?:\\^)"),c("CARETTRIM",`(\\s*)${i[s.LONECARET]}\\s+`,!0),t.caretTrimReplace="$1^",c("CARET",`^${i[s.LONECARET]}${i[s.XRANGEPLAIN]}$`),c("CARETLOOSE",`^${i[s.LONECARET]}${i[s.XRANGEPLAINLOOSE]}$`),c("COMPARATORLOOSE",`^${i[s.GTLT]}\\s*(${i[s.LOOSEPLAIN]})$|^$`),c("COMPARATOR",`^${i[s.GTLT]}\\s*(${i[s.FULLPLAIN]})$|^$`),c("COMPARATORTRIM",`(\\s*)${i[s.GTLT]}\\s*(${i[s.LOOSEPLAIN]}|${i[s.XRANGEPLAIN]})`,!0),t.comparatorTrimReplace="$1$2$3",c("HYPHENRANGE",`^\\s*(${i[s.XRANGEPLAIN]})\\s+-\\s+(${i[s.XRANGEPLAIN]})\\s*$`),c("HYPHENRANGELOOSE",`^\\s*(${i[s.XRANGEPLAINLOOSE]})\\s+-\\s+(${i[s.XRANGEPLAINLOOSE]})\\s*$`),c("STAR","(<|>)?=?\\s*\\*"),c("GTE0","^\\s*>=\\s*0.0.0\\s*$"),c("GTE0PRE","^\\s*>=\\s*0.0.0-0\\s*$")},69258:(e,t,r)=>{const A=r(842);e.exports=(e,t,r)=>A(e,t,">",r)},87395:(e,t,r)=>{const A=r(73004);e.exports=(e,t,r)=>(e=new A(e,r),t=new A(t,r),e.intersects(t))},36928:(e,t,r)=>{const A=r(842);e.exports=(e,t,r)=>A(e,t,"<",r)},1895:(e,t,r)=>{const A=r(14772),n=r(73004);e.exports=(e,t,r)=>{let o=null,i=null,s=null;try{s=new n(t,r)}catch(e){return null}return e.forEach(e=>{s.test(e)&&(o&&-1!==i.compare(e)||(o=e,i=new A(o,r)))}),o}},33252:(e,t,r)=>{const A=r(14772),n=r(73004);e.exports=(e,t,r)=>{let o=null,i=null,s=null;try{s=new n(t,r)}catch(e){return null}return e.forEach(e=>{s.test(e)&&(o&&1!==i.compare(e)||(o=e,i=new A(o,r)))}),o}},4224:(e,t,r)=>{const A=r(14772),n=r(73004),o=r(26544);e.exports=(e,t)=>{e=new n(e,t);let r=new A("0.0.0");if(e.test(r))return r;if(r=new A("0.0.0-0"),e.test(r))return r;r=null;for(let t=0;t{const t=new A(e.semver.version);switch(e.operator){case">":0===t.prerelease.length?t.patch++:t.prerelease.push(0),t.raw=t.format();case"":case">=":r&&!o(r,t)||(r=t);break;case"<":case"<=":break;default:throw new Error("Unexpected operation: "+e.operator)}})}return r&&e.test(r)?r:null}},842:(e,t,r)=>{const A=r(14772),n=r(29069),{ANY:o}=n,i=r(73004),s=r(73011),a=r(26544),c=r(65069),g=r(93845),l=r(44984);e.exports=(e,t,r,u)=>{let h,p,d,C,f;switch(e=new A(e,u),t=new i(t,u),r){case">":h=a,p=g,d=c,C=">",f=">=";break;case"<":h=c,p=l,d=a,C="<",f="<=";break;default:throw new TypeError('Must provide a hilo val of "<" or ">"')}if(s(e,t,u))return!1;for(let r=0;r{e.semver===o&&(e=new n(">=0.0.0")),i=i||e,s=s||e,h(e.semver,i.semver,u)?i=e:d(e.semver,s.semver,u)&&(s=e)}),i.operator===C||i.operator===f)return!1;if((!s.operator||s.operator===C)&&p(e,s.semver))return!1;if(s.operator===f&&d(e,s.semver))return!1}return!0}},3530:(e,t,r)=>{const A=r(73011),n=r(17340);e.exports=(e,t,r)=>{const o=[];let i=null,s=null;const a=e.sort((e,t)=>n(e,t,r));for(const e of a){A(e,t,r)?(s=e,i||(i=e)):(s&&o.push([i,s]),s=null,i=null)}i&&o.push([i,null]);const c=[];for(const[e,t]of o)e===t?c.push(e):t||e!==a[0]?t?e===a[0]?c.push("<="+t):c.push(`${e} - ${t}`):c.push(">="+e):c.push("*");const g=c.join(" || "),l="string"==typeof t.raw?t.raw:String(t);return g.length{const A=r(73004),{ANY:n}=r(29069),o=r(73011),i=r(17340),s=(e,t,r)=>{if(1===e.length&&e[0].semver===n)return 1===t.length&&t[0].semver===n;const A=new Set;let s,g,l,u,h,p,d;for(const t of e)">"===t.operator||">="===t.operator?s=a(s,t,r):"<"===t.operator||"<="===t.operator?g=c(g,t,r):A.add(t.semver);if(A.size>1)return null;if(s&&g){if(l=i(s.semver,g.semver,r),l>0)return null;if(0===l&&(">="!==s.operator||"<="!==g.operator))return null}for(const e of A){if(s&&!o(e,String(s),r))return null;if(g&&!o(e,String(g),r))return null;for(const A of t)if(!o(e,String(A),r))return!1;return!0}for(const e of t){if(d=d||">"===e.operator||">="===e.operator,p=p||"<"===e.operator||"<="===e.operator,s)if(">"===e.operator||">="===e.operator){if(u=a(s,e,r),u===e)return!1}else if(">="===s.operator&&!o(s.semver,String(e),r))return!1;if(g)if("<"===e.operator||"<="===e.operator){if(h=c(g,e,r),h===e)return!1}else if("<="===g.operator&&!o(g.semver,String(e),r))return!1;if(!e.operator&&(g||s)&&0!==l)return!1}return!(s&&p&&!g&&0!==l)&&!(g&&d&&!s&&0!==l)},a=(e,t,r)=>{if(!e)return t;const A=i(e.semver,t.semver,r);return A>0?e:A<0||">"===t.operator&&">="===e.operator?t:e},c=(e,t,r)=>{if(!e)return t;const A=i(e.semver,t.semver,r);return A<0?e:A>0||"<"===t.operator&&"<="===e.operator?t:e};e.exports=(e,t,r)=>{e=new A(e,r),t=new A(t,r);let n=!1;e:for(const A of e.set){for(const e of t.set){const t=s(A,e,r);if(n=n||null!==t,t)continue e}if(n)return!1}return!0}},47753:(e,t,r)=>{const A=r(73004);e.exports=(e,t)=>new A(e,t).set.map(e=>e.map(e=>e.value).join(" ").trim().split(" "))},44315:(e,t,r)=>{const A=r(73004);e.exports=(e,t)=>{try{return new A(e,t).range||"*"}catch(e){return null}}},91470:(e,t,r)=>{"use strict";const A=r(67719);e.exports=(e="")=>{const t=e.match(A);if(!t)return null;const[r,n]=t[0].replace(/#! ?/,"").split(" "),o=r.split("/").pop();return"env"===o?n:n?`${o} ${n}`:o}},67719:e=>{"use strict";e.exports=/^#!(.*)/},17234:e=>{"use strict";e.exports=e=>{const t=/^\\\\\?\\/.test(e),r=/[^\u0000-\u0080]+/.test(e);return t||r?e:e.replace(/\\/g,"/")}},10129:(e,t,r)=>{"use strict";const A=r(76417),n=r(19184),o=r(92413).Transform,i=["sha256","sha384","sha512"],s=/^[a-z0-9+/]+(?:=?=?)$/i,a=/^([^-]+)-([^?]+)([?\S*]*)$/,c=/^([^-]+)-([A-Za-z0-9+/=]{44,88})(\?[\x21-\x7E]*)*$/,g=/^[\x21-\x7E]+$/,l=n({algorithms:{default:["sha512"]},error:{default:!1},integrity:{},options:{default:[]},pickAlgorithm:{default:()=>B},Promise:{default:()=>Promise},sep:{default:" "},single:{default:!1},size:{},strict:{default:!1}});class u{get isHash(){return!0}constructor(e,t){const r=!!(t=l(t)).strict;this.source=e.trim();const A=this.source.match(r?c:a);if(!A)return;if(r&&!i.some(e=>e===A[1]))return;this.algorithm=A[1],this.digest=A[2];const n=A[3];this.options=n?n.slice(1).split("?"):[]}hexDigest(){return this.digest&&Buffer.from(this.digest,"base64").toString("hex")}toJSON(){return this.toString()}toString(e){if((e=l(e)).strict&&!(i.some(e=>e===this.algorithm)&&this.digest.match(s)&&(this.options||[]).every(e=>e.match(g))))return"";const t=this.options&&this.options.length?"?"+this.options.join("?"):"";return`${this.algorithm}-${this.digest}${t}`}}class h{get isIntegrity(){return!0}toJSON(){return this.toString()}toString(e){let t=(e=l(e)).sep||" ";return e.strict&&(t=t.replace(/\S+/g," ")),Object.keys(this).map(r=>this[r].map(t=>u.prototype.toString.call(t,e)).filter(e=>e.length).join(t)).filter(e=>e.length).join(t)}concat(e,t){t=l(t);const r="string"==typeof e?e:C(e,t);return p(`${this.toString(t)} ${r}`,t)}hexDigest(){return p(this,{single:!0}).hexDigest()}match(e,t){const r=p(e,t=l(t)),A=r.pickAlgorithm(t);return this[A]&&r[A]&&this[A].find(e=>r[A].find(t=>e.digest===t.digest))||!1}pickAlgorithm(e){const t=(e=l(e)).pickAlgorithm,r=Object.keys(this);if(!r.length)throw new Error("No algorithms available for "+JSON.stringify(this.toString()));return r.reduce((e,r)=>t(e,r)||e)}}function p(e,t){if(t=l(t),"string"==typeof e)return d(e,t);if(e.algorithm&&e.digest){const r=new h;return r[e.algorithm]=[e],d(C(r,t),t)}return d(C(e,t),t)}function d(e,t){return t.single?new u(e,t):e.trim().split(/\s+/).reduce((e,r)=>{const A=new u(r,t);if(A.algorithm&&A.digest){const t=A.algorithm;e[t]||(e[t]=[]),e[t].push(A)}return e},new h)}function C(e,t){return t=l(t),e.algorithm&&e.digest?u.prototype.toString.call(e,t):"string"==typeof e?C(p(e,t),t):h.prototype.toString.call(e,t)}function f(e){const t=(e=l(e)).integrity&&p(e.integrity,e),r=t&&Object.keys(t).length,n=r&&t.pickAlgorithm(e),i=r&&t[n],s=Array.from(new Set(e.algorithms.concat(n?[n]:[]))),a=s.map(A.createHash);let c=0;const g=new o({transform(e,t,r){c+=e.length,a.forEach(r=>r.update(e,t)),r(null,e,t)}}).on("end",()=>{const A=e.options&&e.options.length?"?"+e.options.join("?"):"",o=p(a.map((e,t)=>`${s[t]}-${e.digest("base64")}${A}`).join(" "),e),l=r&&o.match(t,e);if("number"==typeof e.size&&c!==e.size){const r=new Error(`stream size mismatch when checking ${t}.\n Wanted: ${e.size}\n Found: ${c}`);r.code="EBADSIZE",r.found=c,r.expected=e.size,r.sri=t,g.emit("error",r)}else if(e.integrity&&!l){const e=new Error(`${t} integrity checksum failed when using ${n}: wanted ${i} but got ${o}. (${c} bytes)`);e.code="EINTEGRITY",e.found=o,e.expected=i,e.algorithm=n,e.sri=t,g.emit("error",e)}else g.emit("size",c),g.emit("integrity",o),l&&g.emit("verified",l)});return g}e.exports.Sd=function(e,t){const r=(t=l(t)).algorithms,n=t.options&&t.options.length?"?"+t.options.join("?"):"";return r.reduce((r,o)=>{const i=A.createHash(o).update(e).digest("base64"),s=new u(`${o}-${i}${n}`,t);if(s.algorithm&&s.digest){const e=s.algorithm;r[e]||(r[e]=[]),r[e].push(s)}return r},new h)};const I=new Set(A.getHashes()),E=["md5","whirlpool","sha1","sha224","sha256","sha384","sha512","sha3","sha3-256","sha3-384","sha3-512","sha3_256","sha3_384","sha3_512"].filter(e=>I.has(e));function B(e,t){return E.indexOf(e.toLowerCase())>=E.indexOf(t.toLowerCase())?e:t}},69538:(e,t,r)=>{"use strict";var A=r(13499).Buffer,n=A.isEncoding||function(e){switch((e=""+e)&&e.toLowerCase()){case"hex":case"utf8":case"utf-8":case"ascii":case"binary":case"base64":case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":case"raw":return!0;default:return!1}};function o(e){var t;switch(this.encoding=function(e){var t=function(e){if(!e)return"utf8";for(var t;;)switch(e){case"utf8":case"utf-8":return"utf8";case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return"utf16le";case"latin1":case"binary":return"latin1";case"base64":case"ascii":case"hex":return e;default:if(t)return;e=(""+e).toLowerCase(),t=!0}}(e);if("string"!=typeof t&&(A.isEncoding===n||!n(e)))throw new Error("Unknown encoding: "+e);return t||e}(e),this.encoding){case"utf16le":this.text=a,this.end=c,t=4;break;case"utf8":this.fillLast=s,t=4;break;case"base64":this.text=g,this.end=l,t=3;break;default:return this.write=u,void(this.end=h)}this.lastNeed=0,this.lastTotal=0,this.lastChar=A.allocUnsafe(t)}function i(e){return e<=127?0:e>>5==6?2:e>>4==14?3:e>>3==30?4:e>>6==2?-1:-2}function s(e){var t=this.lastTotal-this.lastNeed,r=function(e,t,r){if(128!=(192&t[0]))return e.lastNeed=0,"�";if(e.lastNeed>1&&t.length>1){if(128!=(192&t[1]))return e.lastNeed=1,"�";if(e.lastNeed>2&&t.length>2&&128!=(192&t[2]))return e.lastNeed=2,"�"}}(this,e);return void 0!==r?r:this.lastNeed<=e.length?(e.copy(this.lastChar,t,0,this.lastNeed),this.lastChar.toString(this.encoding,0,this.lastTotal)):(e.copy(this.lastChar,t,0,e.length),void(this.lastNeed-=e.length))}function a(e,t){if((e.length-t)%2==0){var r=e.toString("utf16le",t);if(r){var A=r.charCodeAt(r.length-1);if(A>=55296&&A<=56319)return this.lastNeed=2,this.lastTotal=4,this.lastChar[0]=e[e.length-2],this.lastChar[1]=e[e.length-1],r.slice(0,-1)}return r}return this.lastNeed=1,this.lastTotal=2,this.lastChar[0]=e[e.length-1],e.toString("utf16le",t,e.length-1)}function c(e){var t=e&&e.length?this.write(e):"";if(this.lastNeed){var r=this.lastTotal-this.lastNeed;return t+this.lastChar.toString("utf16le",0,r)}return t}function g(e,t){var r=(e.length-t)%3;return 0===r?e.toString("base64",t):(this.lastNeed=3-r,this.lastTotal=3,1===r?this.lastChar[0]=e[e.length-1]:(this.lastChar[0]=e[e.length-2],this.lastChar[1]=e[e.length-1]),e.toString("base64",t,e.length-r))}function l(e){var t=e&&e.length?this.write(e):"";return this.lastNeed?t+this.lastChar.toString("base64",0,3-this.lastNeed):t}function u(e){return e.toString(this.encoding)}function h(e){return e&&e.length?this.write(e):""}t.s=o,o.prototype.write=function(e){if(0===e.length)return"";var t,r;if(this.lastNeed){if(void 0===(t=this.fillLast(e)))return"";r=this.lastNeed,this.lastNeed=0}else r=0;return r=0)return n>0&&(e.lastNeed=n-1),n;if(--A=0)return n>0&&(e.lastNeed=n-2),n;if(--A=0)return n>0&&(2===n?n=0:e.lastNeed=n-3),n;return 0}(this,e,t);if(!this.lastNeed)return e.toString("utf8",t);this.lastTotal=r;var A=e.length-(r-this.lastNeed);return e.copy(this.lastChar,0,A),e.toString("utf8",t,A)},o.prototype.fillLast=function(e){if(this.lastNeed<=e.length)return e.copy(this.lastChar,this.lastTotal-this.lastNeed,0,this.lastNeed),this.lastChar.toString(this.encoding,0,this.lastTotal);e.copy(this.lastChar,this.lastTotal-this.lastNeed,0,e.length),this.lastNeed-=e.length}},59428:(e,t,r)=>{"use strict";const A=r(12087),n=r(33867),o=r(72918),{env:i}=process;let s;function a(e){return 0!==e&&{level:e,hasBasic:!0,has256:e>=2,has16m:e>=3}}function c(e,t){if(0===s)return 0;if(o("color=16m")||o("color=full")||o("color=truecolor"))return 3;if(o("color=256"))return 2;if(e&&!t&&void 0===s)return 0;const r=s||0;if("dumb"===i.TERM)return r;if("win32"===process.platform){const e=A.release().split(".");return Number(e[0])>=10&&Number(e[2])>=10586?Number(e[2])>=14931?3:2:1}if("CI"in i)return["TRAVIS","CIRCLECI","APPVEYOR","GITLAB_CI"].some(e=>e in i)||"codeship"===i.CI_NAME?1:r;if("TEAMCITY_VERSION"in i)return/^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(i.TEAMCITY_VERSION)?1:0;if("GITHUB_ACTIONS"in i)return 1;if("truecolor"===i.COLORTERM)return 3;if("TERM_PROGRAM"in i){const e=parseInt((i.TERM_PROGRAM_VERSION||"").split(".")[0],10);switch(i.TERM_PROGRAM){case"iTerm.app":return e>=3?3:2;case"Apple_Terminal":return 2}}return/-256(color)?$/i.test(i.TERM)?2:/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(i.TERM)||"COLORTERM"in i?1:r}o("no-color")||o("no-colors")||o("color=false")||o("color=never")?s=0:(o("color")||o("colors")||o("color=true")||o("color=always"))&&(s=1),"FORCE_COLOR"in i&&(s="true"===i.FORCE_COLOR?1:"false"===i.FORCE_COLOR?0:0===i.FORCE_COLOR.length?1:Math.min(parseInt(i.FORCE_COLOR,10),3)),e.exports={supportsColor:function(e){return a(c(e,e&&e.isTTY))},stdout:a(c(!0,n.isatty(1))),stderr:a(c(!0,n.isatty(2)))}},93255:e=>{"use strict";function t(e){return Array.prototype.slice.apply(e)}function r(e){this.status="pending",this._continuations=[],this._parent=null,this._paused=!1,e&&e.call(this,this._continueWith.bind(this),this._failWith.bind(this))}function A(e){return e&&"function"==typeof e.then}function n(e){return e}if(r.prototype={then:function(e,t){var n=r.unresolved()._setParent(this);if(this._isRejected()){if(this._paused)return this._continuations.push({promise:n,nextFn:e,catchFn:t}),n;if(t)try{var o=t(this._error);return A(o)?(this._chainPromiseData(o,n),n):r.resolve(o)._setParent(this)}catch(e){return r.reject(e)._setParent(this)}return r.reject(this._error)._setParent(this)}return this._continuations.push({promise:n,nextFn:e,catchFn:t}),this._runResolutions(),n},catch:function(e){if(this._isResolved())return r.resolve(this._data)._setParent(this);var t=r.unresolved()._setParent(this);return this._continuations.push({promise:t,catchFn:e}),this._runRejections(),t},finally:function(e){var t=!1;function r(r,o){if(!t){t=!0,e||(e=n);var i=e(r);return A(i)?i.then((function(){if(o)throw o;return r})):r}}return this.then((function(e){return r(e)})).catch((function(e){return r(null,e)}))},pause:function(){return this._paused=!0,this},resume:function(){var e=this._findFirstPaused();return e&&(e._paused=!1,e._runResolutions(),e._runRejections()),this},_findAncestry:function(){return this._continuations.reduce((function(e,t){if(t.promise){var r={promise:t.promise,children:t.promise._findAncestry()};e.push(r)}return e}),[])},_setParent:function(e){if(this._parent)throw new Error("parent already set");return this._parent=e,this},_continueWith:function(e){var t=this._findFirstPending();t&&(t._data=e,t._setResolved())},_findFirstPending:function(){return this._findFirstAncestor((function(e){return e._isPending&&e._isPending()}))},_findFirstPaused:function(){return this._findFirstAncestor((function(e){return e._paused}))},_findFirstAncestor:function(e){for(var t,r=this;r;)e(r)&&(t=r),r=r._parent;return t},_failWith:function(e){var t=this._findFirstPending();t&&(t._error=e,t._setRejected())},_takeContinuations:function(){return this._continuations.splice(0,this._continuations.length)},_runRejections:function(){if(!this._paused&&this._isRejected()){var e=this._error,t=this._takeContinuations(),r=this;t.forEach((function(t){if(t.catchFn)try{var A=t.catchFn(e);r._handleUserFunctionResult(A,t.promise)}catch(e){t.promise.reject(e)}else t.promise.reject(e)}))}},_runResolutions:function(){if(!this._paused&&this._isResolved()&&!this._isPending()){var e=this._takeContinuations();if(A(this._data))return this._handleWhenResolvedDataIsPromise(this._data);var t=this._data,r=this;e.forEach((function(e){if(e.nextFn)try{var A=e.nextFn(t);r._handleUserFunctionResult(A,e.promise)}catch(t){r._handleResolutionError(t,e)}else e.promise&&e.promise.resolve(t)}))}},_handleResolutionError:function(e,t){if(this._setRejected(),t.catchFn)try{return void t.catchFn(e)}catch(t){e=t}t.promise&&t.promise.reject(e)},_handleWhenResolvedDataIsPromise:function(e){var t=this;return e.then((function(e){t._data=e,t._runResolutions()})).catch((function(e){t._error=e,t._setRejected(),t._runRejections()}))},_handleUserFunctionResult:function(e,t){A(e)?this._chainPromiseData(e,t):t.resolve(e)},_chainPromiseData:function(e,t){e.then((function(e){t.resolve(e)})).catch((function(e){t.reject(e)}))},_setResolved:function(){this.status="resolved",this._paused||this._runResolutions()},_setRejected:function(){this.status="rejected",this._paused||this._runRejections()},_isPending:function(){return"pending"===this.status},_isResolved:function(){return"resolved"===this.status},_isRejected:function(){return"rejected"===this.status}},r.resolve=function(e){return new r((function(t,r){A(e)?e.then((function(e){t(e)})).catch((function(e){r(e)})):t(e)}))},r.reject=function(e){return new r((function(t,r){r(e)}))},r.unresolved=function(){return new r((function(e,t){this.resolve=e,this.reject=t}))},r.all=function(){var e=t(arguments);return Array.isArray(e[0])&&(e=e[0]),e.length?new r((function(t,A){var n=[],o=0,i=!1;e.forEach((function(s,a){r.resolve(s).then((function(r){n[a]=r,(o+=1)===e.length&&t(n)})).catch((function(e){!function(e){i||(i=!0,A(e))}(e)}))}))})):r.resolve([])},Promise===r)throw new Error("Please use SynchronousPromise.installGlobally() to install globally");var o=Promise;r.installGlobally=function(e){if(Promise===r)return e;var A=function(e){if(void 0===e||e.__patched)return e;var r=e;return(e=function(){r.apply(this,t(arguments))}).__patched=!0,e}(e);return Promise=r,A},r.uninstallGlobally=function(){Promise===r&&(Promise=o)},e.exports={SynchronousPromise:r}},75799:(e,t,r)=>{var A=r(31669),n=r(73975),o=r(77686),i=r(86897).Writable,s=r(86897).PassThrough,a=function(){},c=function(e){return(e&=511)&&512-e},g=function(e,t){this._parent=e,this.offset=t,s.call(this)};A.inherits(g,s),g.prototype.destroy=function(e){this._parent.destroy(e)};var l=function(e){if(!(this instanceof l))return new l(e);i.call(this,e),e=e||{},this._offset=0,this._buffer=n(),this._missing=0,this._partial=!1,this._onparse=a,this._header=null,this._stream=null,this._overflow=null,this._cb=null,this._locked=!1,this._destroyed=!1,this._pax=null,this._paxGlobal=null,this._gnuLongPath=null,this._gnuLongLinkPath=null;var t=this,r=t._buffer,A=function(){t._continue()},s=function(e){if(t._locked=!1,e)return t.destroy(e);t._stream||A()},u=function(){t._stream=null;var e=c(t._header.size);e?t._parse(e,h):t._parse(512,I),t._locked||A()},h=function(){t._buffer.consume(c(t._header.size)),t._parse(512,I),A()},p=function(){var e=t._header.size;t._paxGlobal=o.decodePax(r.slice(0,e)),r.consume(e),u()},d=function(){var e=t._header.size;t._pax=o.decodePax(r.slice(0,e)),t._paxGlobal&&(t._pax=Object.assign({},t._paxGlobal,t._pax)),r.consume(e),u()},C=function(){var A=t._header.size;this._gnuLongPath=o.decodeLongPath(r.slice(0,A),e.filenameEncoding),r.consume(A),u()},f=function(){var A=t._header.size;this._gnuLongLinkPath=o.decodeLongPath(r.slice(0,A),e.filenameEncoding),r.consume(A),u()},I=function(){var n,i=t._offset;try{n=t._header=o.decode(r.slice(0,512),e.filenameEncoding)}catch(e){t.emit("error",e)}return r.consume(512),n?"gnu-long-path"===n.type?(t._parse(n.size,C),void A()):"gnu-long-link-path"===n.type?(t._parse(n.size,f),void A()):"pax-global-header"===n.type?(t._parse(n.size,p),void A()):"pax-header"===n.type?(t._parse(n.size,d),void A()):(t._gnuLongPath&&(n.name=t._gnuLongPath,t._gnuLongPath=null),t._gnuLongLinkPath&&(n.linkname=t._gnuLongLinkPath,t._gnuLongLinkPath=null),t._pax&&(t._header=n=function(e,t){return t.path&&(e.name=t.path),t.linkpath&&(e.linkname=t.linkpath),t.size&&(e.size=parseInt(t.size,10)),e.pax=t,e}(n,t._pax),t._pax=null),t._locked=!0,n.size&&"directory"!==n.type?(t._stream=new g(t,i),t.emit("entry",n,t._stream,s),t._parse(n.size,u),void A()):(t._parse(512,I),void t.emit("entry",n,function(e,t){var r=new g(e,t);return r.end(),r}(t,i),s))):(t._parse(512,I),void A())};this._onheader=I,this._parse(512,I)};A.inherits(l,i),l.prototype.destroy=function(e){this._destroyed||(this._destroyed=!0,e&&this.emit("error",e),this.emit("close"),this._stream&&this._stream.emit("close"))},l.prototype._parse=function(e,t){this._destroyed||(this._offset+=e,this._missing=e,t===this._onheader&&(this._partial=!1),this._onparse=t)},l.prototype._continue=function(){if(!this._destroyed){var e=this._cb;this._cb=a,this._overflow?this._write(this._overflow,void 0,e):e()}},l.prototype._write=function(e,t,r){if(!this._destroyed){var A=this._stream,n=this._buffer,o=this._missing;if(e.length&&(this._partial=!0),e.lengtho&&(i=e.slice(o),e=e.slice(0,o)),A?A.end(e):n.append(e),this._overflow=i,this._onparse()}},l.prototype._final=function(e){if(this._partial)return this.destroy(new Error("Unexpected end of data"));e()},e.exports=l},77686:(e,t)=>{var r=Buffer.alloc,A="0".charCodeAt(0),n=parseInt("7777",8),o=function(e,t,r,A){for(;rt?"7777777777777777777".slice(0,t)+" ":"0000000000000000000".slice(0,t-e.length)+e+" "};var a=function(e,t,r){if(128&(e=e.slice(t,t+r))[t=0])return function(e){var t;if(128===e[0])t=!0;else{if(255!==e[0])return null;t=!1}for(var r=!1,A=[],n=e.length-1;n>0;n--){var o=e[n];t?A.push(o):r&&0===o?A.push(0):r?(r=!1,A.push(256-o)):A.push(255-o)}var i=0,s=A.length;for(n=0;n=i?i:n>=0||(n+=i)>=0?n:0);t=Math.pow(10,r)&&r++,t+r+e};t.decodeLongPath=function(e,t){return c(e,0,e.length,t)},t.encodePax=function(e){var t="";e.name&&(t+=g(" path="+e.name+"\n")),e.linkname&&(t+=g(" linkpath="+e.linkname+"\n"));var r=e.pax;if(r)for(var A in r)t+=g(" "+A+"="+r[A]+"\n");return Buffer.from(t)},t.decodePax=function(e){for(var t={};e.length;){for(var r=0;r100;){var c=o.indexOf("/");if(-1===c)return null;a+=a?"/"+o.slice(0,c):o.slice(0,c),o=o.slice(c+1)}return Buffer.byteLength(o)>100||Buffer.byteLength(a)>155||e.linkname&&Buffer.byteLength(e.linkname)>100?null:(t.write(o),t.write(s(e.mode&n,6),100),t.write(s(e.uid,6),108),t.write(s(e.gid,6),116),t.write(s(e.size,11),124),t.write(s(e.mtime.getTime()/1e3|0,11),136),t[156]=A+function(e){switch(e){case"file":return 0;case"link":return 1;case"symlink":return 2;case"character-device":return 3;case"block-device":return 4;case"directory":return 5;case"fifo":return 6;case"contiguous-file":return 7;case"pax-header":return 72}return 0}(e.type),e.linkname&&t.write(e.linkname,157),t.write("ustar\x0000",257),e.uname&&t.write(e.uname,265),e.gname&&t.write(e.gname,297),t.write(s(e.devmajor||0,6),329),t.write(s(e.devminor||0,6),337),a&&t.write(a,345),t.write(s(i(t),6),148),t)},t.decode=function(e,t){var r=0===e[156]?0:e[156]-A,n=c(e,0,100,t),o=a(e,100,8),s=a(e,108,8),g=a(e,116,8),l=a(e,124,12),u=a(e,136,12),h=function(e){switch(e){case 0:return"file";case 1:return"link";case 2:return"symlink";case 3:return"character-device";case 4:return"block-device";case 5:return"directory";case 6:return"fifo";case 7:return"contiguous-file";case 72:return"pax-header";case 55:return"pax-global-header";case 27:return"gnu-long-link-path";case 28:case 30:return"gnu-long-path"}return null}(r),p=0===e[157]?null:c(e,157,100,t),d=c(e,265,32),C=c(e,297,32),f=a(e,329,8),I=a(e,337,8);e[345]&&(n=c(e,345,155,t)+"/"+n),0===r&&n&&"/"===n[n.length-1]&&(r=5);var E=i(e);if(256===E)return null;if(E!==a(e,148,8))throw new Error("Invalid tar header. Maybe the tar is corrupted or it needs to be gunzipped?");return{name:n,mode:o,uid:s,gid:g,size:l,mtime:new Date(1e3*u),type:h,linkname:p,uname:d,gname:C,devmajor:f,devminor:I}}},59938:(e,t,r)=>{t.extract=r(75799),t.pack=r(72203)},72203:(e,t,r)=>{var A=r(13302),n=r(17067),o=r(85870),i=Buffer.alloc,s=r(86897).Readable,a=r(86897).Writable,c=r(24304).StringDecoder,g=r(77686),l=parseInt("755",8),u=parseInt("644",8),h=i(1024),p=function(){},d=function(e,t){(t&=511)&&e.push(h.slice(0,512-t))};var C=function(e){a.call(this),this.written=0,this._to=e,this._destroyed=!1};o(C,a),C.prototype._write=function(e,t,r){if(this.written+=e.length,this._to.push(e))return r();this._to._drain=r},C.prototype.destroy=function(){this._destroyed||(this._destroyed=!0,this.emit("close"))};var f=function(){a.call(this),this.linkname="",this._decoder=new c("utf-8"),this._destroyed=!1};o(f,a),f.prototype._write=function(e,t,r){this.linkname+=this._decoder.write(e),r()},f.prototype.destroy=function(){this._destroyed||(this._destroyed=!0,this.emit("close"))};var I=function(){a.call(this),this._destroyed=!1};o(I,a),I.prototype._write=function(e,t,r){r(new Error("No body allowed for this entry"))},I.prototype.destroy=function(){this._destroyed||(this._destroyed=!0,this.emit("close"))};var E=function(e){if(!(this instanceof E))return new E(e);s.call(this,e),this._drain=p,this._finalized=!1,this._finalizing=!1,this._destroyed=!1,this._stream=null};o(E,s),E.prototype.entry=function(e,t,r){if(this._stream)throw new Error("already piping an entry");if(!this._finalized&&!this._destroyed){"function"==typeof t&&(r=t,t=null),r||(r=p);var o=this;if(e.size&&"symlink"!==e.type||(e.size=0),e.type||(e.type=function(e){switch(e&A.S_IFMT){case A.S_IFBLK:return"block-device";case A.S_IFCHR:return"character-device";case A.S_IFDIR:return"directory";case A.S_IFIFO:return"fifo";case A.S_IFLNK:return"symlink"}return"file"}(e.mode)),e.mode||(e.mode="directory"===e.type?l:u),e.uid||(e.uid=0),e.gid||(e.gid=0),e.mtime||(e.mtime=new Date),"string"==typeof t&&(t=Buffer.from(t)),Buffer.isBuffer(t))return e.size=t.length,this._encode(e),this.push(t),d(o,e.size),process.nextTick(r),new I;if("symlink"===e.type&&!e.linkname){var i=new f;return n(i,(function(t){if(t)return o.destroy(),r(t);e.linkname=i.linkname,o._encode(e),r()})),i}if(this._encode(e),"file"!==e.type&&"contiguous-file"!==e.type)return process.nextTick(r),new I;var s=new C(this);return this._stream=s,n(s,(function(t){return o._stream=null,t?(o.destroy(),r(t)):s.written!==e.size?(o.destroy(),r(new Error("size mismatch"))):(d(o,e.size),o._finalizing&&o.finalize(),void r())})),s}},E.prototype.finalize=function(){this._stream?this._finalizing=!0:this._finalized||(this._finalized=!0,this.push(h),this.push(null))},E.prototype.destroy=function(e){this._destroyed||(this._destroyed=!0,e&&this.emit("error",e),this.emit("close"),this._stream&&this._stream.destroy&&this._stream.destroy())},E.prototype._encode=function(e){if(!e.pax){var t=g.encode(e);if(t)return void this.push(t)}this._encodePax(e)},E.prototype._encodePax=function(e){var t=g.encodePax({name:e.name,linkname:e.linkname,pax:e.pax}),r={name:"PaxHeader",mode:e.mode,uid:e.uid,gid:e.gid,size:t.length,mtime:e.mtime,type:"pax-header",linkname:e.linkname&&"PaxHeader",uname:e.uname,gname:e.gname,devmajor:e.devmajor,devminor:e.devminor};this.push(g.encode(r)),this.push(t),d(this,t.length),r.size=e.size,r.type=e.type,this.push(g.encode(r))},E.prototype._read=function(e){var t=this._drain;this._drain=p,t()},e.exports=E},84615:(e,t,r)=>{"use strict"; +/*! + * to-regex-range + * + * Copyright (c) 2015-present, Jon Schlinkert. + * Released under the MIT License. + */const A=r(59235),n=(e,t,r)=>{if(!1===A(e))throw new TypeError("toRegexRange: expected the first argument to be a number");if(void 0===t||e===t)return String(e);if(!1===A(t))throw new TypeError("toRegexRange: expected the second argument to be a number.");let o={relaxZeros:!0,...r};"boolean"==typeof o.strictZeros&&(o.relaxZeros=!1===o.strictZeros);let a=e+":"+t+"="+String(o.relaxZeros)+String(o.shorthand)+String(o.capture)+String(o.wrap);if(n.cache.hasOwnProperty(a))return n.cache[a].result;let c=Math.min(e,t),g=Math.max(e,t);if(1===Math.abs(c-g)){let r=e+"|"+t;return o.capture?`(${r})`:!1===o.wrap?r:`(?:${r})`}let l=p(e)||p(t),u={min:e,max:t,a:c,b:g},h=[],d=[];if(l&&(u.isPadded=l,u.maxLen=String(u.max).length),c<0){d=i(g<0?Math.abs(g):1,Math.abs(c),u,o),c=u.a=0}return g>=0&&(h=i(c,g,u,o)),u.negatives=d,u.positives=h,u.result=function(e,t,r){let A=s(e,t,"-",!1,r)||[],n=s(t,e,"",!1,r)||[],o=s(e,t,"-?",!0,r)||[];return A.concat(o).concat(n).join("|")}(d,h,o),!0===o.capture?u.result=`(${u.result})`:!1!==o.wrap&&h.length+d.length>1&&(u.result=`(?:${u.result})`),n.cache[a]=u,u.result};function o(e,t,r){if(e===t)return{pattern:e,count:[],digits:0};let A=function(e,t){let r=[];for(let A=0;A1&&n.count.pop(),n.count.push(a.count[0]),n.string=n.pattern+u(n.count),c=t+1)}return s}function s(e,t,r,A,n){let o=[];for(let n of e){let{string:e}=n;A||c(t,"string",e)||o.push(r+e),A&&c(t,"string",e)&&o.push(r+e)}return o}function a(e,t){return e>t?1:t>e?-1:0}function c(e,t,r){return e.some(e=>e[t]===r)}function g(e,t){return Number(String(e).slice(0,-t)+"9".repeat(t))}function l(e,t){return e-e%Math.pow(10,t)}function u(e){let[t=0,r=""]=e;return r||t>1?`{${t+(r?","+r:"")}}`:""}function h(e,t,r){return`[${e}${t-e==1?"":"-"}${t}]`}function p(e){return/^-?(0+)\d/.test(e)}function d(e,t,r){if(!t.isPadded)return e;let A=Math.abs(t.maxLen-String(e).length),n=!1!==r.relaxZeros;switch(A){case 0:return"";case 1:return n?"0?":"0";case 2:return n?"0{0,2}":"00";default:return n?`0{0,${A}}`:`0{${A}}`}}n.cache={},n.clearCache=()=>n.cache={},e.exports=n},75158:e=>{function t(e,t){var r=e.length,A=new Array(r),n={},o=r,i=function(e){for(var t=new Map,r=0,A=e.length;r0&&(n.forEach((function(e,t){t>0&&(g+=(e[1]?" ":"│")+" "),c||e[0]!==r||(c=!0)})),g+=function(e,t){var r=t?"└":"├";return r+=e?"─ ":"──┐"}(t,A)+t,o&&("object"!=typeof r||r instanceof Date)&&(g+=": "+r),c&&(g+=" (circular ref.)"),s(g)),!c&&"object"==typeof r){var h=function(e,t){var r=[];for(var A in e)e.hasOwnProperty(A)&&(t&&"function"==typeof e[A]||r.push(A));return r}(r,i);h.forEach((function(t){a=++l===h.length,e(t,r[t],a,u,o,i,s)}))}}var t={asLines:function(t,r,A,n){e(".",t,!1,[],r,"function"!=typeof A&&A,n||A)},asTree:function(t,r,A){var n="";return e(".",t,!1,[],r,A,(function(e){n+=e+"\n"})),n}};return t}()},36370:(e,t,r)=>{"use strict";r.d(t,{gn:()=>A});function A(e,t,r,A){var n,o=arguments.length,i=o<3?t:null===A?A=Object.getOwnPropertyDescriptor(t,r):A;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,r,A);else for(var s=e.length-1;s>=0;s--)(n=e[s])&&(i=(o<3?n(i):o>3?n(t,r,i):n(t,r))||i);return o>3&&i&&Object.defineProperty(t,r,i),i}},98161:(e,t,r)=>{e.exports=r(69876)},69876:(e,t,r)=>{"use strict";r(11631);var A,n=r(4016),o=r(98605),i=r(57211),s=r(28614),a=(r(42357),r(31669));function c(e){var t=this;t.options=e||{},t.proxyOptions=t.options.proxy||{},t.maxSockets=t.options.maxSockets||o.Agent.defaultMaxSockets,t.requests=[],t.sockets=[],t.on("free",(function(e,r,A,n){for(var o=l(r,A,n),i=0,s=t.requests.length;i=this.maxSockets?n.requests.push(o):n.createSocket(o,(function(t){function r(){n.emit("free",t,o)}function A(e){n.removeSocket(t),t.removeListener("free",r),t.removeListener("close",A),t.removeListener("agentRemove",A)}t.on("free",r),t.on("close",A),t.on("agentRemove",A),e.onSocket(t)}))},c.prototype.createSocket=function(e,t){var r=this,n={};r.sockets.push(n);var o=u({},r.proxyOptions,{method:"CONNECT",path:e.host+":"+e.port,agent:!1,headers:{host:e.host+":"+e.port}});e.localAddress&&(o.localAddress=e.localAddress),o.proxyAuth&&(o.headers=o.headers||{},o.headers["Proxy-Authorization"]="Basic "+new Buffer(o.proxyAuth).toString("base64")),A("making CONNECT request");var i=r.request(o);function s(o,s,a){var c;return i.removeAllListeners(),s.removeAllListeners(),200!==o.statusCode?(A("tunneling socket could not be established, statusCode=%d",o.statusCode),s.destroy(),(c=new Error("tunneling socket could not be established, statusCode="+o.statusCode)).code="ECONNRESET",e.request.emit("error",c),void r.removeSocket(n)):a.length>0?(A("got illegal response body from proxy"),s.destroy(),(c=new Error("got illegal response body from proxy")).code="ECONNRESET",e.request.emit("error",c),void r.removeSocket(n)):(A("tunneling connection has established"),r.sockets[r.sockets.indexOf(n)]=s,t(s))}i.useChunkedEncodingByDefault=!1,i.once("response",(function(e){e.upgrade=!0})),i.once("upgrade",(function(e,t,r){process.nextTick((function(){s(e,t,r)}))})),i.once("connect",s),i.once("error",(function(t){i.removeAllListeners(),A("tunneling socket could not be established, cause=%s\n",t.message,t.stack);var o=new Error("tunneling socket could not be established, cause="+t.message);o.code="ECONNRESET",e.request.emit("error",o),r.removeSocket(n)})),i.end()},c.prototype.removeSocket=function(e){var t=this.sockets.indexOf(e);if(-1!==t){this.sockets.splice(t,1);var r=this.requests.shift();r&&this.createSocket(r,(function(e){r.request.onSocket(e)}))}},A=process.env.NODE_DEBUG&&/\btunnel\b/.test(process.env.NODE_DEBUG)?function(){var e=Array.prototype.slice.call(arguments);"string"==typeof e[0]?e[0]="TUNNEL: "+e[0]:e.unshift("TUNNEL:"),console.error.apply(console,e)}:function(){}},73212:(e,t,r)=>{e.exports=r(31669).deprecate},87945:(e,t,r)=>{const A="win32"===process.platform||"cygwin"===process.env.OSTYPE||"msys"===process.env.OSTYPE,n=r(85622),o=A?";":":",i=r(64151),s=e=>Object.assign(new Error("not found: "+e),{code:"ENOENT"}),a=(e,t)=>{const r=t.colon||o,n=e.match(/\//)||A&&e.match(/\\/)?[""]:[...A?[process.cwd()]:[],...(t.path||process.env.PATH||"").split(r)],i=A?t.pathExt||process.env.PATHEXT||".EXE;.CMD;.BAT;.COM":"",s=A?i.split(r):[""];return A&&-1!==e.indexOf(".")&&""!==s[0]&&s.unshift(""),{pathEnv:n,pathExt:s,pathExtExe:i}},c=(e,t,r)=>{"function"==typeof t&&(r=t,t={}),t||(t={});const{pathEnv:A,pathExt:o,pathExtExe:c}=a(e,t),g=[],l=r=>new Promise((o,i)=>{if(r===A.length)return t.all&&g.length?o(g):i(s(e));const a=A[r],c=/^".*"$/.test(a)?a.slice(1,-1):a,l=n.join(c,e),h=!c&&/^\.[\\\/]/.test(e)?e.slice(0,2)+l:l;o(u(h,r,0))}),u=(e,r,A)=>new Promise((n,s)=>{if(A===o.length)return n(l(r+1));const a=o[A];i(e+a,{pathExt:c},(o,i)=>{if(!o&&i){if(!t.all)return n(e+a);g.push(e+a)}return n(u(e,r,A+1))})});return r?l(0).then(e=>r(null,e),r):l(0)};e.exports=c,c.sync=(e,t)=>{t=t||{};const{pathEnv:r,pathExt:A,pathExtExe:o}=a(e,t),c=[];for(let s=0;s{e.exports=function e(t,r){if(t&&r)return e(t)(r);if("function"!=typeof t)throw new TypeError("need wrapper function");return Object.keys(t).forEach((function(e){A[e]=t[e]})),A;function A(){for(var e=new Array(arguments.length),r=0;r{"use strict";var A=r(60087);t.__esModule=!0,t.default=void 0;var n=A(r(15215)),o=A(r(11050)),i=function(){function e(e,t){if(this.refs=e,"function"!=typeof t){if(!(0,n.default)(t,"is"))throw new TypeError("`is:` is required for `when()` conditions");if(!t.then&&!t.otherwise)throw new TypeError("either `then:` or `otherwise:` is required for `when()` conditions");var r=t.is,A=t.then,o=t.otherwise,i="function"==typeof r?r:function(){for(var e=arguments.length,t=new Array(e),A=0;A{"use strict";var A=r(60087);t.__esModule=!0,t.default=void 0;var n=A(r(11050)),o=function(){function e(e){this._resolve=function(t,r){var A=e(t,r);if(!(0,n.default)(A))throw new TypeError("lazy() functions must return a valid schema");return A.resolve(r)}}var t=e.prototype;return t.resolve=function(e){return this._resolve(e.value,e)},t.cast=function(e,t){return this._resolve(e,t).cast(e,t)},t.validate=function(e,t){return this._resolve(e,t).validate(e,t)},t.validateSync=function(e,t){return this._resolve(e,t).validateSync(e,t)},t.validateAt=function(e,t,r){return this._resolve(t,r).validateAt(e,t,r)},t.validateSyncAt=function(e,t,r){return this._resolve(t,r).validateSyncAt(e,t,r)},e}();o.prototype.__isYupSchema__=!0;var i=o;t.default=i,e.exports=t.default},95814:(e,t,r)=>{"use strict";var A=r(60087);t.__esModule=!0,t.default=void 0;var n=A(r(72912)),o=r(79588),i="$",s=".",a=function(){function e(e,t){if(void 0===t&&(t={}),"string"!=typeof e)throw new TypeError("ref must be a string, got: "+e);if(this.key=e.trim(),""===e)throw new TypeError("ref must be a non-empty string");this.isContext=this.key[0]===i,this.isValue=this.key[0]===s,this.isSibling=!this.isContext&&!this.isValue;var r=this.isContext?i:this.isValue?s:"";this.path=this.key.slice(r.length),this.getter=this.path&&(0,o.getter)(this.path,!0),this.map=t.map}var t=e.prototype;return t.getValue=function(e){var t=this.isContext?e.context:this.isValue?e.value:e.parent;return this.getter&&(t=this.getter(t||{})),this.map&&(t=this.map(t)),t},t.cast=function(e,t){return this.getValue((0,n.default)({},t,{value:e}))},t.resolve=function(){return this},t.describe=function(){return{type:"ref",key:this.key}},t.toString=function(){return"Ref("+this.key+")"},e.isRef=function(e){return e&&e.__isYupRef},e}();t.default=a,a.prototype.__isYupRef=!0,e.exports=t.default},40828:(e,t,r)=>{"use strict";var A=r(60087);t.__esModule=!0,t.default=s;var n=A(r(21043)),o=/\$\{\s*(\w+)\s*\}/g,i=function(e){return function(t){return e.replace(o,(function(e,r){return(0,n.default)(t[r])}))}};function s(e,t,r,A){var n=this;this.name="ValidationError",this.value=t,this.path=r,this.type=A,this.errors=[],this.inner=[],e&&[].concat(e).forEach((function(e){n.errors=n.errors.concat(e.errors||e),e.inner&&(n.inner=n.inner.concat(e.inner.length?e.inner:e))})),this.message=this.errors.length>1?this.errors.length+" errors occurred":this.errors[0],Error.captureStackTrace&&Error.captureStackTrace(this,s)}s.prototype=Object.create(Error.prototype),s.prototype.constructor=s,s.isError=function(e){return e&&"ValidationError"===e.name},s.formatError=function(e,t){"string"==typeof e&&(e=i(e));var r=function(t){return t.path=t.label||t.path||"this","function"==typeof e?e(t):e};return 1===arguments.length?r:r(t)},e.exports=t.default},18830:(e,t,r)=>{"use strict";var A=r(19228),n=r(60087);t.__esModule=!0,t.default=void 0;var o=n(r(72912)),i=n(r(62407)),s=n(r(31490)),a=n(r(71665)),c=n(r(11050)),g=n(r(7045)),l=n(r(21043)),u=n(r(16434)),h=r(63802),p=A(r(80180));function d(){var e=(0,i.default)(["","[","]"]);return d=function(){return e},e}var C=f;function f(e){var t=this;if(!(this instanceof f))return new f(e);u.default.call(this,{type:"array"}),this._subType=void 0,this.withMutation((function(){t.transform((function(e){if("string"==typeof e)try{e=JSON.parse(e)}catch(t){e=null}return this.isType(e)?e:null})),e&&t.of(e)}))}t.default=C,(0,s.default)(f,u.default,{_typeCheck:function(e){return Array.isArray(e)},_cast:function(e,t){var r=this,A=u.default.prototype._cast.call(this,e,t);if(!this._typeCheck(A)||!this._subType)return A;var n=!1,o=A.map((function(e){var A=r._subType.cast(e,t);return A!==e&&(n=!0),A}));return n?o:A},_validate:function(e,t){var r=this;void 0===t&&(t={});var A=[],n=t.sync,i=t.path,s=this._subType,a=this._option("abortEarly",t),c=this._option("recursive",t),l=null!=t.originalValue?t.originalValue:e;return u.default.prototype._validate.call(this,e,t).catch((0,p.propagateErrors)(a,A)).then((function(e){if(!c||!s||!r._typeCheck(e)){if(A.length)throw A[0];return e}l=l||e;var u=e.map((function(r,A){var n=(0,g.default)(d(),t.path,A),i=(0,o.default)({},t,{path:n,strict:!0,parent:e,originalValue:l[A]});return!s.validate||s.validate(r,i)}));return(0,p.default)({sync:n,path:i,value:e,errors:A,endEarly:a,validations:u})}))},_isPresent:function(e){return u.default.prototype._cast.call(this,e)&&e.length>0},of:function(e){var t=this.clone();if(!1!==e&&!(0,c.default)(e))throw new TypeError("`array.of()` sub-schema must be a valid yup schema, or `false` to negate a current sub-schema. not: "+(0,l.default)(e));return t._subType=e,t},min:function(e,t){return t=t||h.array.min,this.test({message:t,name:"min",exclusive:!0,params:{min:e},test:function(t){return(0,a.default)(t)||t.length>=this.resolve(e)}})},max:function(e,t){return t=t||h.array.max,this.test({message:t,name:"max",exclusive:!0,params:{max:e},test:function(t){return(0,a.default)(t)||t.length<=this.resolve(e)}})},ensure:function(){var e=this;return this.default((function(){return[]})).transform((function(t){return e.isType(t)?t:null===t?[]:[].concat(t)}))},compact:function(e){var t=e?function(t,r,A){return!e(t,r,A)}:function(e){return!!e};return this.transform((function(e){return null!=e?e.filter(t):e}))},describe:function(){var e=u.default.prototype.describe.call(this);return this._subType&&(e.innerType=this._subType.describe()),e}}),e.exports=t.default},76595:(e,t,r)=>{"use strict";var A=r(60087);t.__esModule=!0,t.default=void 0;var n=A(r(31490)),o=A(r(16434)),i=s;function s(){var e=this;if(!(this instanceof s))return new s;o.default.call(this,{type:"boolean"}),this.withMutation((function(){e.transform((function(e){if(!this.isType(e)){if(/^(true|1)$/i.test(e))return!0;if(/^(false|0)$/i.test(e))return!1}return e}))}))}t.default=i,(0,n.default)(s,o.default,{_typeCheck:function(e){return e instanceof Boolean&&(e=e.valueOf()),"boolean"==typeof e}}),e.exports=t.default},41755:(e,t,r)=>{"use strict";var A=r(60087);t.__esModule=!0,t.default=void 0;var n=A(r(16434)),o=A(r(31490)),i=A(r(76813)),s=r(63802),a=A(r(71665)),c=A(r(95814)),g=new Date(""),l=u;function u(){var e=this;if(!(this instanceof u))return new u;n.default.call(this,{type:"date"}),this.withMutation((function(){e.transform((function(e){return this.isType(e)?e:(e=(0,i.default)(e))?new Date(e):g}))}))}t.default=l,(0,o.default)(u,n.default,{_typeCheck:function(e){return t=e,"[object Date]"===Object.prototype.toString.call(t)&&!isNaN(e.getTime());var t},min:function(e,t){void 0===t&&(t=s.date.min);var r=e;if(!c.default.isRef(r)&&(r=this.cast(e),!this._typeCheck(r)))throw new TypeError("`min` must be a Date or a value that can be `cast()` to a Date");return this.test({message:t,name:"min",exclusive:!0,params:{min:e},test:function(e){return(0,a.default)(e)||e>=this.resolve(r)}})},max:function(e,t){void 0===t&&(t=s.date.max);var r=e;if(!c.default.isRef(r)&&(r=this.cast(e),!this._typeCheck(r)))throw new TypeError("`max` must be a Date or a value that can be `cast()` to a Date");return this.test({message:t,name:"max",exclusive:!0,params:{max:e},test:function(e){return(0,a.default)(e)||e<=this.resolve(r)}})}}),e.exports=t.default},15966:(e,t,r)=>{"use strict";var A=r(60087);t.__esModule=!0,t.addMethod=function(e,t,r){if(!e||!(0,d.default)(e.prototype))throw new TypeError("You must provide a yup schema constructor function");if("string"!=typeof t)throw new TypeError("A Method name must be provided");if("function"!=typeof r)throw new TypeError("Method function must be provided");e.prototype[t]=r},t.lazy=t.ref=t.boolean=void 0;var n=A(r(16434));t.mixed=n.default;var o=A(r(76595));t.bool=o.default;var i=A(r(45167));t.string=i.default;var s=A(r(72068));t.number=s.default;var a=A(r(41755));t.date=a.default;var c=A(r(51727));t.object=c.default;var g=A(r(18830));t.array=g.default;var l=A(r(95814)),u=A(r(6856)),h=A(r(40828));t.ValidationError=h.default;var p=A(r(43910));t.reach=p.default;var d=A(r(11050));t.isSchema=d.default;var C=A(r(24280));t.setLocale=C.default;var f=o.default;t.boolean=f;t.ref=function(e,t){return new l.default(e,t)};t.lazy=function(e){return new u.default(e)}},63802:(e,t,r)=>{"use strict";var A=r(60087);t.__esModule=!0,t.default=t.array=t.object=t.boolean=t.date=t.number=t.string=t.mixed=void 0;var n=A(r(21043)),o={default:"${path} is invalid",required:"${path} is a required field",oneOf:"${path} must be one of the following values: ${values}",notOneOf:"${path} must not be one of the following values: ${values}",notType:function(e){var t=e.path,r=e.type,A=e.value,o=e.originalValue,i=null!=o&&o!==A,s=t+" must be a `"+r+"` type, but the final value was: `"+(0,n.default)(A,!0)+"`"+(i?" (cast from the value `"+(0,n.default)(o,!0)+"`).":".");return null===A&&(s+='\n If "null" is intended as an empty value be sure to mark the schema as `.nullable()`'),s}};t.mixed=o;var i={length:"${path} must be exactly ${length} characters",min:"${path} must be at least ${min} characters",max:"${path} must be at most ${max} characters",matches:'${path} must match the following: "${regex}"',email:"${path} must be a valid email",url:"${path} must be a valid URL",trim:"${path} must be a trimmed string",lowercase:"${path} must be a lowercase string",uppercase:"${path} must be a upper case string"};t.string=i;var s={min:"${path} must be greater than or equal to ${min}",max:"${path} must be less than or equal to ${max}",lessThan:"${path} must be less than ${less}",moreThan:"${path} must be greater than ${more}",notEqual:"${path} must be not equal to ${notEqual}",positive:"${path} must be a positive number",negative:"${path} must be a negative number",integer:"${path} must be an integer"};t.number=s;var a={min:"${path} field must be later than ${min}",max:"${path} field must be at earlier than ${max}"};t.date=a;var c={};t.boolean=c;var g={noUnknown:"${path} field cannot have keys not specified in the object shape"};t.object=g;var l={min:"${path} field must have at least ${min} items",max:"${path} field must have less than or equal to ${max} items"};t.array=l;var u={mixed:o,string:i,number:s,date:a,object:g,array:l,boolean:c};t.default=u},16434:(e,t,r)=>{"use strict";var A=r(60087);t.__esModule=!0,t.default=I;var n=A(r(72912)),o=A(r(15215)),i=A(r(26052)),s=A(r(78700)),a=r(63802),c=A(r(94916)),g=A(r(80180)),l=A(r(22808)),u=A(r(11050)),h=A(r(54107)),p=A(r(21043)),d=A(r(95814)),C=r(43910),f=function(){function e(){this.list=new Set,this.refs=new Map}var t=e.prototype;return t.toArray=function(){return(0,s.default)(this.list).concat((0,s.default)(this.refs.values()))},t.add=function(e){d.default.isRef(e)?this.refs.set(e.key,e):this.list.add(e)},t.delete=function(e){d.default.isRef(e)?this.refs.delete(e.key,e):this.list.delete(e)},t.has=function(e,t){if(this.list.has(e))return!0;for(var r,A=this.refs.values();!(r=A.next()).done;)if(t(r.value)===e)return!0;return!1},e}();function I(e){var t=this;if(void 0===e&&(e={}),!(this instanceof I))return new I;this._deps=[],this._conditions=[],this._options={abortEarly:!0,recursive:!0},this._exclusive=Object.create(null),this._whitelist=new f,this._blacklist=new f,this.tests=[],this.transforms=[],this.withMutation((function(){t.typeError(a.mixed.notType)})),(0,o.default)(e,"default")&&(this._defaultDefault=e.default),this._type=e.type||"mixed"}for(var E=I.prototype={__isYupSchema__:!0,constructor:I,clone:function(){var e=this;return this._mutate?this:(0,i.default)(this,(function(t){if((0,u.default)(t)&&t!==e)return t}))},label:function(e){var t=this.clone();return t._label=e,t},meta:function(e){if(0===arguments.length)return this._meta;var t=this.clone();return t._meta=(0,n.default)(t._meta||{},e),t},withMutation:function(e){var t=this._mutate;this._mutate=!0;var r=e(this);return this._mutate=t,r},concat:function(e){if(!e||e===this)return this;if(e._type!==this._type&&"mixed"!==this._type)throw new TypeError("You cannot `concat()` schema's of different types: "+this._type+" and "+e._type);var t=(0,l.default)(e.clone(),this);return(0,o.default)(e,"_default")&&(t._default=e._default),t.tests=this.tests,t._exclusive=this._exclusive,t.withMutation((function(t){e.tests.forEach((function(e){t.test(e.OPTIONS)}))})),t},isType:function(e){return!(!this._nullable||null!==e)||(!this._typeCheck||this._typeCheck(e))},resolve:function(e){var t=this;if(t._conditions.length){var r=t._conditions;(t=t.clone())._conditions=[],t=(t=r.reduce((function(t,r){return r.resolve(t,e)}),t)).resolve(e)}return t},cast:function(e,t){void 0===t&&(t={});var r=this.resolve((0,n.default)({},t,{value:e})),A=r._cast(e,t);if(void 0!==e&&!1!==t.assert&&!0!==r.isType(A)){var o=(0,p.default)(e),i=(0,p.default)(A);throw new TypeError("The value of "+(t.path||"field")+' could not be cast to a value that satisfies the schema type: "'+r._type+'". \n\nattempted value: '+o+" \n"+(i!==o?"result of cast: "+i:""))}return A},_cast:function(e){var t=this,r=void 0===e?e:this.transforms.reduce((function(r,A){return A.call(t,r,e)}),e);return void 0===r&&(0,o.default)(this,"_default")&&(r=this.default()),r},_validate:function(e,t){var r=this;void 0===t&&(t={});var A=e,o=null!=t.originalValue?t.originalValue:e,i=this._option("strict",t),s=this._option("abortEarly",t),a=t.sync,c=t.path,l=this._label;i||(A=this._cast(A,(0,n.default)({assert:!1},t)));var u={value:A,path:c,schema:this,options:t,label:l,originalValue:o,sync:a},h=[];return this._typeError&&h.push(this._typeError(u)),this._whitelistError&&h.push(this._whitelistError(u)),this._blacklistError&&h.push(this._blacklistError(u)),(0,g.default)({validations:h,endEarly:s,value:A,path:c,sync:a}).then((function(e){return(0,g.default)({path:c,sync:a,value:e,endEarly:s,validations:r.tests.map((function(e){return e(u)}))})}))},validate:function(e,t){return void 0===t&&(t={}),this.resolve((0,n.default)({},t,{value:e}))._validate(e,t)},validateSync:function(e,t){var r,A;if(void 0===t&&(t={}),this.resolve((0,n.default)({},t,{value:e}))._validate(e,(0,n.default)({},t,{sync:!0})).then((function(e){return r=e})).catch((function(e){return A=e})),A)throw A;return r},isValid:function(e,t){return this.validate(e,t).then((function(){return!0})).catch((function(e){if("ValidationError"===e.name)return!1;throw e}))},isValidSync:function(e,t){try{return this.validateSync(e,t),!0}catch(e){if("ValidationError"===e.name)return!1;throw e}},getDefault:function(e){return void 0===e&&(e={}),this.resolve(e).default()},default:function(e){if(0===arguments.length){var t=(0,o.default)(this,"_default")?this._default:this._defaultDefault;return"function"==typeof t?t.call(this):(0,i.default)(t)}var r=this.clone();return r._default=e,r},strict:function(e){void 0===e&&(e=!0);var t=this.clone();return t._options.strict=e,t},_isPresent:function(e){return null!=e},required:function(e){return void 0===e&&(e=a.mixed.required),this.test({message:e,name:"required",exclusive:!0,test:function(e){return this.schema._isPresent(e)}})},notRequired:function(){var e=this.clone();return e.tests=e.tests.filter((function(e){return"required"!==e.OPTIONS.name})),e},nullable:function(e){void 0===e&&(e=!0);var t=this.clone();return t._nullable=e,t},transform:function(e){var t=this.clone();return t.transforms.push(e),t},test:function(){var e;if(void 0===(e=1===arguments.length?"function"==typeof(arguments.length<=0?void 0:arguments[0])?{test:arguments.length<=0?void 0:arguments[0]}:arguments.length<=0?void 0:arguments[0]:2===arguments.length?{name:arguments.length<=0?void 0:arguments[0],test:arguments.length<=1?void 0:arguments[1]}:{name:arguments.length<=0?void 0:arguments[0],message:arguments.length<=1?void 0:arguments[1],test:arguments.length<=2?void 0:arguments[2]}).message&&(e.message=a.mixed.default),"function"!=typeof e.test)throw new TypeError("`test` is a required parameters");var t=this.clone(),r=(0,h.default)(e),A=e.exclusive||e.name&&!0===t._exclusive[e.name];if(e.exclusive&&!e.name)throw new TypeError("Exclusive tests must provide a unique `name` identifying the test");return t._exclusive[e.name]=!!e.exclusive,t.tests=t.tests.filter((function(t){if(t.OPTIONS.name===e.name){if(A)return!1;if(t.OPTIONS.test===r.OPTIONS.test)return!1}return!0})),t.tests.push(r),t},when:function(e,t){1===arguments.length&&(t=e,e=".");var r=this.clone(),A=[].concat(e).map((function(e){return new d.default(e)}));return A.forEach((function(e){e.isSibling&&r._deps.push(e.key)})),r._conditions.push(new c.default(A,t)),r},typeError:function(e){var t=this.clone();return t._typeError=(0,h.default)({message:e,name:"typeError",test:function(e){return!(void 0!==e&&!this.schema.isType(e))||this.createError({params:{type:this.schema._type}})}}),t},oneOf:function(e,t){void 0===t&&(t=a.mixed.oneOf);var r=this.clone();return e.forEach((function(e){r._whitelist.add(e),r._blacklist.delete(e)})),r._whitelistError=(0,h.default)({message:t,name:"oneOf",test:function(e){if(void 0===e)return!0;var t=this.schema._whitelist;return!!t.has(e,this.resolve)||this.createError({params:{values:t.toArray().join(", ")}})}}),r},notOneOf:function(e,t){void 0===t&&(t=a.mixed.notOneOf);var r=this.clone();return e.forEach((function(e){r._blacklist.add(e),r._whitelist.delete(e)})),r._blacklistError=(0,h.default)({message:t,name:"notOneOf",test:function(e){var t=this.schema._blacklist;return!t.has(e,this.resolve)||this.createError({params:{values:t.toArray().join(", ")}})}}),r},strip:function(e){void 0===e&&(e=!0);var t=this.clone();return t._strip=e,t},_option:function(e,t){return(0,o.default)(t,e)?t[e]:this._options[e]},describe:function(){var e=this.clone();return{type:e._type,meta:e._meta,label:e._label,tests:e.tests.map((function(e){return{name:e.OPTIONS.name,params:e.OPTIONS.params}})).filter((function(e,t,r){return r.findIndex((function(t){return t.name===e.name}))===t}))}}},B=["validate","validateSync"],y=function(){var e=B[m];E[e+"At"]=function(t,r,A){void 0===A&&(A={});var o=(0,C.getIn)(this,t,r,A.context),i=o.parent,s=o.parentPath;return o.schema[e](i&&i[s],(0,n.default)({},A,{parent:i,path:t}))}},m=0;m{"use strict";var A=r(60087);t.__esModule=!0,t.default=c;var n=A(r(31490)),o=A(r(16434)),i=r(63802),s=A(r(71665)),a=function(e){return(0,s.default)(e)||e===(0|e)};function c(){var e=this;if(!(this instanceof c))return new c;o.default.call(this,{type:"number"}),this.withMutation((function(){e.transform((function(e){var t=e;if("string"==typeof t){if(""===(t=t.replace(/\s/g,"")))return NaN;t=+t}return this.isType(t)?t:parseFloat(t)}))}))}(0,n.default)(c,o.default,{_typeCheck:function(e){return e instanceof Number&&(e=e.valueOf()),"number"==typeof e&&!function(e){return e!=+e}(e)},min:function(e,t){return void 0===t&&(t=i.number.min),this.test({message:t,name:"min",exclusive:!0,params:{min:e},test:function(t){return(0,s.default)(t)||t>=this.resolve(e)}})},max:function(e,t){return void 0===t&&(t=i.number.max),this.test({message:t,name:"max",exclusive:!0,params:{max:e},test:function(t){return(0,s.default)(t)||t<=this.resolve(e)}})},lessThan:function(e,t){return void 0===t&&(t=i.number.lessThan),this.test({message:t,name:"max",exclusive:!0,params:{less:e},test:function(t){return(0,s.default)(t)||tthis.resolve(e)}})},positive:function(e){return void 0===e&&(e=i.number.positive),this.moreThan(0,e)},negative:function(e){return void 0===e&&(e=i.number.negative),this.lessThan(0,e)},integer:function(e){return void 0===e&&(e=i.number.integer),this.test({name:"integer",message:e,test:a})},truncate:function(){return this.transform((function(e){return(0,s.default)(e)?e:0|e}))},round:function(e){var t=["ceil","floor","round","trunc"];if("trunc"===(e=e&&e.toLowerCase()||"round"))return this.truncate();if(-1===t.indexOf(e.toLowerCase()))throw new TypeError("Only valid options for round() are: "+t.join(", "));return this.transform((function(t){return(0,s.default)(t)?t:Math[e](t)}))}}),e.exports=t.default},51727:(e,t,r)=>{"use strict";var A=r(19228),n=r(60087);t.__esModule=!0,t.default=w;var o=n(r(62407)),i=n(r(72912)),s=n(r(15215)),a=n(r(36494)),c=n(r(89170)),g=n(r(5253)),l=n(r(89612)),u=r(79588),h=n(r(16434)),p=r(63802),d=n(r(18417)),C=n(r(23316)),f=n(r(31490)),I=n(r(7045)),E=A(r(80180));function B(){var e=(0,o.default)(["",".",""]);return B=function(){return e},e}function y(){var e=(0,o.default)(["",".",""]);return y=function(){return e},e}var m=function(e){return"[object Object]"===Object.prototype.toString.call(e)};function w(e){var t=this;if(!(this instanceof w))return new w(e);h.default.call(this,{type:"object",default:function(){var e=this;if(this._nodes.length){var t={};return this._nodes.forEach((function(r){t[r]=e.fields[r].default?e.fields[r].default():void 0})),t}}}),this.fields=Object.create(null),this._nodes=[],this._excludedEdges=[],this.withMutation((function(){t.transform((function(e){if("string"==typeof e)try{e=JSON.parse(e)}catch(t){e=null}return this.isType(e)?e:null})),e&&t.shape(e)}))}(0,f.default)(w,h.default,{_typeCheck:function(e){return m(e)||"function"==typeof e},_cast:function(e,t){var r=this;void 0===t&&(t={});var A=h.default.prototype._cast.call(this,e,t);if(void 0===A)return this.default();if(!this._typeCheck(A))return A;var n=this.fields,o=!0===this._option("stripUnknown",t),a=this._nodes.concat(Object.keys(A).filter((function(e){return-1===r._nodes.indexOf(e)}))),c={},g=(0,i.default)({},t,{parent:c,__validating:!1}),l=!1;return a.forEach((function(e){var r=n[e],i=(0,s.default)(A,e);if(r){var a,u=r._options&&r._options.strict;if(g.path=(0,I.default)(y(),t.path,e),g.value=A[e],!0===(r=r.resolve(g))._strip)return void(l=l||e in A);void 0!==(a=t.__validating&&u?A[e]:r.cast(A[e],g))&&(c[e]=a)}else i&&!o&&(c[e]=A[e]);c[e]!==A[e]&&(l=!0)})),l?c:A},_validate:function(e,t){var r,A,n=this;void 0===t&&(t={});var o=t.sync,s=[],a=null!=t.originalValue?t.originalValue:e;return r=this._option("abortEarly",t),A=this._option("recursive",t),t=(0,i.default)({},t,{__validating:!0,originalValue:a}),h.default.prototype._validate.call(this,e,t).catch((0,E.propagateErrors)(r,s)).then((function(e){if(!A||!m(e)){if(s.length)throw s[0];return e}a=a||e;var c=n._nodes.map((function(r){var A=(0,I.default)(B(),t.path,r),o=n.fields[r],s=(0,i.default)({},t,{path:A,parent:e,originalValue:a[r]});return o&&o.validate?(s.strict=!0,o.validate(e[r],s)):Promise.resolve(!0)}));return(0,E.default)({sync:o,validations:c,value:e,errors:s,endEarly:r,path:t.path,sort:(0,C.default)(n.fields)})}))},concat:function(e){var t=h.default.prototype.concat.call(this,e);return t._nodes=(0,d.default)(t.fields,t._excludedEdges),t},shape:function(e,t){void 0===t&&(t=[]);var r=this.clone(),A=(0,i.default)(r.fields,e);if(r.fields=A,t.length){Array.isArray(t[0])||(t=[t]);var n=t.map((function(e){return e[0]+"-"+e[1]}));r._excludedEdges=r._excludedEdges.concat(n)}return r._nodes=(0,d.default)(A,r._excludedEdges),r},from:function(e,t,r){var A=(0,u.getter)(e,!0);return this.transform((function(n){if(null==n)return n;var o=n;return(0,s.default)(n,e)&&(o=(0,i.default)({},n),r||delete o[e],o[t]=A(n)),o}))},noUnknown:function(e,t){void 0===e&&(e=!0),void 0===t&&(t=p.object.noUnknown),"string"==typeof e&&(t=e,e=!0);var r=this.test({name:"noUnknown",exclusive:!0,message:t,test:function(t){return null==t||!e||0===function(e,t){var r=Object.keys(e.fields);return Object.keys(t).filter((function(e){return-1===r.indexOf(e)}))}(this.schema,t).length}});return r._options.stripUnknown=e,r},unknown:function(e,t){return void 0===e&&(e=!0),void 0===t&&(t=p.object.noUnknown),this.noUnknown(!e,t)},transformKeys:function(e){return this.transform((function(t){return t&&(0,g.default)(t,(function(t,r){return e(r)}))}))},camelCase:function(){return this.transformKeys(c.default)},snakeCase:function(){return this.transformKeys(a.default)},constantCase:function(){return this.transformKeys((function(e){return(0,a.default)(e).toUpperCase()}))},describe:function(){var e=h.default.prototype.describe.call(this);return e.fields=(0,l.default)(this.fields,(function(e){return e.describe()})),e}}),e.exports=t.default},24280:(e,t,r)=>{"use strict";var A=r(60087);t.__esModule=!0,t.default=function(e){Object.keys(e).forEach((function(t){Object.keys(e[t]).forEach((function(r){n.default[t][r]=e[t][r]}))}))};var n=A(r(63802));e.exports=t.default},45167:(e,t,r)=>{"use strict";var A=r(60087);t.__esModule=!0,t.default=l;var n=A(r(31490)),o=A(r(16434)),i=r(63802),s=A(r(71665)),a=/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i,c=/^((https?|ftp):)?\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i,g=function(e){return(0,s.default)(e)||e===e.trim()};function l(){var e=this;if(!(this instanceof l))return new l;o.default.call(this,{type:"string"}),this.withMutation((function(){e.transform((function(e){return this.isType(e)?e:null!=e&&e.toString?e.toString():e}))}))}(0,n.default)(l,o.default,{_typeCheck:function(e){return e instanceof String&&(e=e.valueOf()),"string"==typeof e},_isPresent:function(e){return o.default.prototype._cast.call(this,e)&&e.length>0},length:function(e,t){return void 0===t&&(t=i.string.length),this.test({message:t,name:"length",exclusive:!0,params:{length:e},test:function(t){return(0,s.default)(t)||t.length===this.resolve(e)}})},min:function(e,t){return void 0===t&&(t=i.string.min),this.test({message:t,name:"min",exclusive:!0,params:{min:e},test:function(t){return(0,s.default)(t)||t.length>=this.resolve(e)}})},max:function(e,t){return void 0===t&&(t=i.string.max),this.test({name:"max",exclusive:!0,message:t,params:{max:e},test:function(t){return(0,s.default)(t)||t.length<=this.resolve(e)}})},matches:function(e,t){var r,A=!1;return t&&(t.message||t.hasOwnProperty("excludeEmptyString")?(A=t.excludeEmptyString,r=t.message):r=t),this.test({message:r||i.string.matches,params:{regex:e},test:function(t){return(0,s.default)(t)||""===t&&A||e.test(t)}})},email:function(e){return void 0===e&&(e=i.string.email),this.matches(a,{message:e,excludeEmptyString:!0})},url:function(e){return void 0===e&&(e=i.string.url),this.matches(c,{message:e,excludeEmptyString:!0})},ensure:function(){return this.default("").transform((function(e){return null===e?"":e}))},trim:function(e){return void 0===e&&(e=i.string.trim),this.transform((function(e){return null!=e?e.trim():e})).test({message:e,name:"trim",test:g})},lowercase:function(e){return void 0===e&&(e=i.string.lowercase),this.transform((function(e){return(0,s.default)(e)?e:e.toLowerCase()})).test({message:e,name:"string_case",exclusive:!0,test:function(e){return(0,s.default)(e)||e===e.toLowerCase()}})},uppercase:function(e){return void 0===e&&(e=i.string.uppercase),this.transform((function(e){return(0,s.default)(e)?e:e.toUpperCase()})).test({message:e,name:"string_case",exclusive:!0,test:function(e){return(0,s.default)(e)||e===e.toUpperCase()}})}}),e.exports=t.default},54107:(e,t,r)=>{"use strict";var A=r(60087);t.__esModule=!0,t.createErrorFactory=l,t.default=function(e){var t=e.name,r=e.message,A=e.test,i=e.params;function g(e){var g=e.value,u=e.path,h=e.label,p=e.options,d=e.originalValue,C=e.sync,f=(0,n.default)(e,["value","path","label","options","originalValue","sync"]),I=p.parent,E=function(e){return a.default.isRef(e)?e.getValue({value:g,parent:I,context:p.context}):e},B=l({message:r,path:u,value:g,originalValue:d,params:i,label:h,resolve:E,name:t}),y=(0,o.default)({path:u,parent:I,type:t,createError:B,resolve:E,options:p},f);return function(e,t,r,A){var n=e.call(t,r);if(!A)return Promise.resolve(n);if(o=n,o&&"function"==typeof o.then&&"function"==typeof o.catch)throw new Error('Validation test of type: "'+t.type+'" returned a Promise during a synchronous validate. This test will finish after the validate call has returned');var o;return c.SynchronousPromise.resolve(n)}(A,y,g,C).then((function(e){if(s.default.isError(e))throw e;if(!e)throw B()}))}return g.OPTIONS=e,g};var n=A(r(74943)),o=A(r(72912)),i=A(r(89612)),s=A(r(40828)),a=A(r(95814)),c=r(93255),g=s.default.formatError;function l(e){var t=e.value,r=e.label,A=e.resolve,a=e.originalValue,c=(0,n.default)(e,["value","label","resolve","originalValue"]);return function(e){var n=void 0===e?{}:e,l=n.path,u=void 0===l?c.path:l,h=n.message,p=void 0===h?c.message:h,d=n.type,C=void 0===d?c.name:d,f=n.params;return f=(0,o.default)({path:u,value:t,originalValue:a,label:r},function(e,t,r){return(0,i.default)((0,o.default)({},e,t),r)}(c.params,f,A)),(0,o.default)(new s.default(g(p,f),t,u,C),{params:f})}}},31490:(e,t,r)=>{"use strict";var A=r(60087);t.__esModule=!0,t.default=function(e,t,r){e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),(0,n.default)(e.prototype,r)};var n=A(r(72912));e.exports=t.default},71665:(e,t)=>{"use strict";t.__esModule=!0,t.default=void 0;t.default=function(e){return null==e},e.exports=t.default},11050:(e,t)=>{"use strict";t.__esModule=!0,t.default=void 0;t.default=function(e){return e&&e.__isYupSchema__},e.exports=t.default},76813:(e,t)=>{"use strict";t.__esModule=!0,t.default=function(e){var t,A,n=[1,4,5,6,7,10,11],o=0;if(A=r.exec(e)){for(var i,s=0;i=n[s];++s)A[i]=+A[i]||0;A[2]=(+A[2]||1)-1,A[3]=+A[3]||1,A[7]=A[7]?String(A[7]).substr(0,3):0,void 0!==A[8]&&""!==A[8]||void 0!==A[9]&&""!==A[9]?("Z"!==A[8]&&void 0!==A[9]&&(o=60*A[10]+A[11],"+"===A[9]&&(o=0-o)),t=Date.UTC(A[1],A[2],A[3],A[4],A[5]+o,A[6],A[7])):t=+new Date(A[1],A[2],A[3],A[4],A[5],A[6],A[7])}else t=Date.parse?Date.parse(e):NaN;return t};var r=/^(\d{4}|[+\-]\d{6})(?:-?(\d{2})(?:-?(\d{2}))?)?(?:[ T]?(\d{2}):?(\d{2})(?::?(\d{2})(?:[,\.](\d{1,}))?)?(?:(Z)|([+\-])(\d{2})(?::?(\d{2}))?)?)?$/;e.exports=t.default},7045:(e,t)=>{"use strict";t.__esModule=!0,t.default=function(e){for(var t=arguments.length,r=new Array(t>1?t-1:0),A=1;A{"use strict";var A=r(60087);t.__esModule=!0,t.default=function e(t,r){for(var A in r)if((0,n.default)(r,A)){var s=r[A],a=t[A];if(void 0===a)t[A]=s;else{if(a===s)continue;(0,o.default)(a)?(0,o.default)(s)&&(t[A]=s.concat(a)):i(a)?i(s)&&(t[A]=e(a,s)):Array.isArray(a)&&Array.isArray(s)&&(t[A]=s.concat(a))}}return t};var n=A(r(15215)),o=A(r(11050)),i=function(e){return"[object Object]"===Object.prototype.toString.call(e)};e.exports=t.default},21043:(e,t)=>{"use strict";t.__esModule=!0,t.default=function(e,t){var r=s(e,t);return null!==r?r:JSON.stringify(e,(function(e,r){var A=s(this[e],t);return null!==A?A:r}),2)};var r=Object.prototype.toString,A=Error.prototype.toString,n=RegExp.prototype.toString,o="undefined"!=typeof Symbol?Symbol.prototype.toString:function(){return""},i=/^Symbol\((.*)\)(.*)$/;function s(e,t){if(void 0===t&&(t=!1),null==e||!0===e||!1===e)return""+e;var s=typeof e;if("number"===s)return function(e){return e!=+e?"NaN":0===e&&1/e<0?"-0":""+e}(e);if("string"===s)return t?'"'+e+'"':e;if("function"===s)return"[Function "+(e.name||"anonymous")+"]";if("symbol"===s)return o.call(e).replace(i,"Symbol($1)");var a=r.call(e).slice(8,-1);return"Date"===a?isNaN(e.getTime())?""+e:e.toISOString(e):"Error"===a||e instanceof Error?"["+A.call(e)+"]":"RegExp"===a?n.call(e):null}e.exports=t.default},43910:(e,t,r)=>{"use strict";var A=r(60087);t.__esModule=!0,t.getIn=i,t.default=void 0;var n=r(79588),o=A(r(15215));function i(e,t,r,A){var i,s,a;return A=A||r,t?((0,n.forEach)(t,(function(n,c,g){var l=c?function(e){return e.substr(0,e.length-1).substr(1)}(n):n;if(g||(0,o.default)(e,"_subType")){var u=g?parseInt(l,10):0;if(e=e.resolve({context:A,parent:i,value:r})._subType,r){if(g&&u>=r.length)throw new Error("Yup.reach cannot resolve an array item at index: "+n+", in the path: "+t+". because there is no value at that index. ");r=r[u]}}if(!g){if(e=e.resolve({context:A,parent:i,value:r}),!(0,o.default)(e,"fields")||!(0,o.default)(e.fields,l))throw new Error("The schema does not contain the path: "+t+". (failed at: "+a+' which is a type: "'+e._type+'") ');e=e.fields[l],i=r,r=r&&r[l],s=l,a=c?"["+n+"]":"."+n}})),{schema:e,parent:i,parentPath:s}):{parent:i,parentPath:t,schema:e}}var s=function(e,t,r,A){return i(e,t,r,A).schema};t.default=s},80180:(e,t,r)=>{"use strict";var A=r(60087);t.__esModule=!0,t.propagateErrors=function(e,t){return e?null:function(e){return t.push(e),e.value}},t.settled=a,t.collectErrors=c,t.default=function(e){var t=e.endEarly,r=(0,n.default)(e,["endEarly"]);return t?function(e,t,r){return s(r).all(e).catch((function(e){throw"ValidationError"===e.name&&(e.value=t),e})).then((function(){return t}))}(r.validations,r.value,r.sync):c(r)};var n=A(r(74943)),o=r(93255),i=A(r(40828)),s=function(e){return e?o.SynchronousPromise:Promise};function a(e,t){var r=s(t);return r.all(e.map((function(e){return r.resolve(e).then((function(e){return{fulfilled:!0,value:e}}),(function(e){return{fulfilled:!1,value:e}}))})))}function c(e){var t=e.validations,r=e.value,A=e.path,n=e.sync,o=e.errors,s=e.sort;return o=function(e){return void 0===e&&(e=[]),e.inner&&e.inner.length?e.inner:[].concat(e)}(o),a(t,n).then((function(e){var t=e.filter((function(e){return!e.fulfilled})).reduce((function(e,t){var r=t.value;if(!i.default.isError(r))throw r;return e.concat(r)}),[]);if(s&&t.sort(s),(o=t.concat(o)).length)throw new i.default(o,r,A);return r}))}},23316:(e,t)=>{"use strict";function r(e,t){var r=1/0;return e.some((function(e,A){if(-1!==t.path.indexOf(e))return r=A,!0})),r}t.__esModule=!0,t.default=function(e){var t=Object.keys(e);return function(e,A){return r(t,e)-r(t,A)}},e.exports=t.default},18417:(e,t,r)=>{"use strict";var A=r(60087);t.__esModule=!0,t.default=function(e,t){void 0===t&&(t=[]);var r=[],A=[];function c(e,n){var o=(0,i.split)(e)[0];~A.indexOf(o)||A.push(o),~t.indexOf(n+"-"+o)||r.push([n,o])}for(var g in e)if((0,n.default)(e,g)){var l=e[g];~A.indexOf(g)||A.push(g),s.default.isRef(l)&&l.isSibling?c(l.path,g):(0,a.default)(l)&&l._deps&&l._deps.forEach((function(e){return c(e,g)}))}return o.default.array(A,r).reverse()};var n=A(r(15215)),o=A(r(75158)),i=r(79588),s=A(r(95814)),a=A(r(11050));e.exports=t.default},60306:e=>{"use strict";e.exports=JSON.parse('{"name":"@yarnpkg/cli","version":"2.4.1","license":"BSD-2-Clause","main":"./sources/index.ts","dependencies":{"@yarnpkg/core":"workspace:^2.4.0","@yarnpkg/fslib":"workspace:^2.4.0","@yarnpkg/libzip":"workspace:^2.2.1","@yarnpkg/parsers":"workspace:^2.3.0","@yarnpkg/plugin-compat":"workspace:^2.2.1","@yarnpkg/plugin-dlx":"workspace:^2.1.4","@yarnpkg/plugin-essentials":"workspace:^2.4.0","@yarnpkg/plugin-file":"workspace:^2.2.0","@yarnpkg/plugin-git":"workspace:^2.3.0","@yarnpkg/plugin-github":"workspace:^2.1.2","@yarnpkg/plugin-http":"workspace:^2.1.2","@yarnpkg/plugin-init":"workspace:^2.2.2","@yarnpkg/plugin-link":"workspace:^2.1.1","@yarnpkg/plugin-node-modules":"workspace:^2.3.0","@yarnpkg/plugin-npm":"workspace:^2.4.0","@yarnpkg/plugin-npm-cli":"workspace:^2.3.0","@yarnpkg/plugin-pack":"workspace:^2.2.3","@yarnpkg/plugin-patch":"workspace:^2.1.2","@yarnpkg/plugin-pnp":"workspace:^2.4.0","@yarnpkg/shell":"workspace:^2.4.1","chalk":"^3.0.0","ci-info":"^2.0.0","clipanion":"^2.6.2","fromentries":"^1.2.0","semver":"^7.1.2","tslib":"^1.13.0","yup":"^0.27.0"},"devDependencies":{"@types/ci-info":"^2","@types/yup":"0.26.12","@yarnpkg/builder":"workspace:^2.1.3","@yarnpkg/monorepo":"workspace:0.0.0","@yarnpkg/pnpify":"workspace:^2.4.0","micromatch":"^4.0.2","typescript":"4.1.0-beta"},"peerDependencies":{"@yarnpkg/core":"^2.4.0"},"scripts":{"postpack":"rm -rf lib","prepack":"run build:compile \\"$(pwd)\\"","build:cli+hook":"run build:pnp:hook && builder build bundle","build:cli":"builder build bundle","run:cli":"builder run","update-local":"run build:cli --no-git-hash && rsync -a --delete bundles/ bin/"},"publishConfig":{"main":"./lib/index.js","types":"./lib/index.d.ts","bin":null},"files":["/lib/**/*","!/lib/pluginConfiguration.*","!/lib/cli.*"],"@yarnpkg/builder":{"bundles":{"standard":["@yarnpkg/plugin-essentials","@yarnpkg/plugin-compat","@yarnpkg/plugin-dlx","@yarnpkg/plugin-file","@yarnpkg/plugin-git","@yarnpkg/plugin-github","@yarnpkg/plugin-http","@yarnpkg/plugin-init","@yarnpkg/plugin-link","@yarnpkg/plugin-node-modules","@yarnpkg/plugin-npm","@yarnpkg/plugin-npm-cli","@yarnpkg/plugin-pack","@yarnpkg/plugin-patch","@yarnpkg/plugin-pnp"]}},"repository":{"type":"git","url":"ssh://git@github.com/yarnpkg/berry.git"},"engines":{"node":">=10.19.0"}}')},98497:e=>{function t(e){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}t.keys=()=>[],t.resolve=t,t.id=98497,e.exports=t},32178:e=>{function t(e){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}t.keys=()=>[],t.resolve=t,t.id=32178,e.exports=t},3368:(e,t,r)=>{var A,n=Object.assign({},r(35747)),o=void 0!==o?o:{},i={};for(A in o)o.hasOwnProperty(A)&&(i[A]=o[A]);var s,a,c,g,l=[],u="";u=__dirname+"/",s=function(e,t){var A=Qe(e);return A?t?A:A.toString():(c||(c=n),g||(g=r(85622)),e=g.normalize(e),c.readFileSync(e,t?null:"utf8"))},a=function(e){var t=s(e,!0);return t.buffer||(t=new Uint8Array(t)),E(t.buffer),t},process.argv.length>1&&process.argv[1].replace(/\\/g,"/"),l=process.argv.slice(2),e.exports=o,o.inspect=function(){return"[Emscripten Module object]"};var h=o.print||console.log.bind(console),p=o.printErr||console.warn.bind(console);for(A in i)i.hasOwnProperty(A)&&(o[A]=i[A]);i=null,o.arguments&&(l=o.arguments),o.thisProgram&&o.thisProgram,o.quit&&o.quit;var d,C;o.wasmBinary&&(d=o.wasmBinary),o.noExitRuntime&&o.noExitRuntime,"object"!=typeof WebAssembly&&_("no native wasm support detected");var f=new WebAssembly.Table({initial:31,maximum:31,element:"anyfunc"}),I=!1;function E(e,t){e||_("Assertion failed: "+t)}function B(e){var t=o["_"+e];return E(t,"Cannot call unknown function "+e+", make sure it is exported"),t}function y(e,t,r,A,n){var o={string:function(e){var t=0;if(null!=e&&0!==e){var r=1+(e.length<<2);b(e,t=xe(r),r)}return t},array:function(e){var t=xe(e.length);return function(e,t){N.set(e,t)}(e,t),t}};var i=B(e),s=[],a=0;if(A)for(var c=0;c=A);)++n;if(n-t>16&&e.subarray&&m)return m.decode(e.subarray(t,n));for(var o="";t>10,56320|1023&c)}}else o+=String.fromCharCode((31&i)<<6|s)}else o+=String.fromCharCode(i)}return o}function Q(e,t){return e?w(F,e,t):""}function D(e,t,r,A){if(!(A>0))return 0;for(var n=r,o=r+A-1,i=0;i=55296&&s<=57343)s=65536+((1023&s)<<10)|1023&e.charCodeAt(++i);if(s<=127){if(r>=o)break;t[r++]=s}else if(s<=2047){if(r+1>=o)break;t[r++]=192|s>>6,t[r++]=128|63&s}else if(s<=65535){if(r+2>=o)break;t[r++]=224|s>>12,t[r++]=128|s>>6&63,t[r++]=128|63&s}else{if(r+3>=o)break;t[r++]=240|s>>18,t[r++]=128|s>>12&63,t[r++]=128|s>>6&63,t[r++]=128|63&s}}return t[r]=0,r-n}function b(e,t,r){return D(e,F,t,r)}function v(e){for(var t=0,r=0;r=55296&&A<=57343&&(A=65536+((1023&A)<<10)|1023&e.charCodeAt(++r)),A<=127?++t:t+=A<=2047?2:A<=65535?3:4}return t}function S(e){var t=v(e)+1,r=Le(t);return r&&D(e,N,r,t),r}var k,N,F,K,M,R,x;function L(e){k=e,o.HEAP8=N=new Int8Array(e),o.HEAP16=K=new Int16Array(e),o.HEAP32=M=new Int32Array(e),o.HEAPU8=F=new Uint8Array(e),o.HEAPU16=new Uint16Array(e),o.HEAPU32=new Uint32Array(e),o.HEAPF32=R=new Float32Array(e),o.HEAPF64=x=new Float64Array(e)}var P=o.INITIAL_MEMORY||16777216;(C=o.wasmMemory?o.wasmMemory:new WebAssembly.Memory({initial:P/65536,maximum:32768}))&&(k=C.buffer),P=k.byteLength,L(k);var O=[],U=[],T=[],j=[];var Y=Math.abs,G=Math.ceil,H=Math.floor,J=Math.min,q=0,z=null,W=null;function V(e){q++,o.monitorRunDependencies&&o.monitorRunDependencies(q)}function X(e){if(q--,o.monitorRunDependencies&&o.monitorRunDependencies(q),0==q&&(null!==z&&(clearInterval(z),z=null),W)){var t=W;W=null,t()}}function _(e){throw o.onAbort&&o.onAbort(e),p(e+=""),I=!0,1,e="abort("+e+"). Build with -s ASSERTIONS=1 for more info.",new WebAssembly.RuntimeError(e)}o.preloadedImages={},o.preloadedAudios={};function Z(e){return t=e,r="data:application/octet-stream;base64,",String.prototype.startsWith?t.startsWith(r):0===t.indexOf(r);var t,r}var $,ee,te,re="data:application/octet-stream;base64,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AkEgaiQAC4sDAQF/IwBBMGsiAyQAIAMgADYCJCADIAE2AiAgAyACNwMYAkAgAygCJC0AKEEBcQRAIANCfzcDKAwBCwJAAkAgAygCJCgCIEEATQ0AIAMpAxhC////////////AFYNACADKQMYQgBYDQEgAygCIA0BCyADKAIkQQxqQRJBABAVIANCfzcDKAwBCyADKAIkLQA1QQFxBEAgA0J/NwMoDAELAn8jAEEQayIAIAMoAiQ2AgwgACgCDC0ANEEBcQsEQCADQgA3AygMAQsgAykDGFAEQCADQgA3AygMAQsgA0IANwMQA0AgAykDECADKQMYVARAIAMgAygCJCADKAIgIAMpAxCnaiADKQMYIAMpAxB9QQEQIiICNwMIIAJCAFMEQCADKAIkQQE6ADUgAykDEFAEQCADQn83AygMBAsgAyADKQMQNwMoDAMLIAMpAwhQBEAgAygCJEEBOgA0BSADIAMpAwggAykDEHw3AxAMAgsLCyADIAMpAxA3AygLIAMpAyghAiADQTBqJAAgAgs2AQF/IwBBEGsiASAANgIMAn4gASgCDC0AAEEBcQRAIAEoAgwpAwggASgCDCkDEH0MAQtCAAsLsgECAX8BfiMAQRBrIgEkACABIAA2AgQgASABKAIEQggQHzYCAAJAIAEoAgBFBEAgAUIANwMIDAELIAEgASgCAC0AAK0gASgCAC0AB61COIYgASgCAC0ABq1CMIZ8IAEoAgAtAAWtQiiGfCABKAIALQAErUIghnwgASgCAC0AA61CGIZ8IAEoAgAtAAKtQhCGfCABKAIALQABrUIIhnx8NwMICyABKQMIIQIgAUEQaiQAIAILqAEBAX8jAEEQayIBJAAgASAANgIIAkAgASgCCCgCIEEATQRAIAEoAghBDGpBEkEAEBUgAUF/NgIMDAELIAEoAggiACAAKAIgQX9qNgIgIAEoAggoAiBFBEAgASgCCEEAQgBBAhAiGiABKAIIKAIABEAgASgCCCgCABAyQQBIBEAgASgCCEEMakEUQQAQFQsLCyABQQA2AgwLIAEoAgwhACABQRBqJAAgAAvxAgICfwF+AkAgAkUNACAAIAJqIgNBf2ogAToAACAAIAE6AAAgAkEDSQ0AIANBfmogAToAACAAIAE6AAEgA0F9aiABOgAAIAAgAToAAiACQQdJDQAgA0F8aiAB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ECwQMwsgASgCDCgCVBAWIAEoAgxBADYCVAsgAUEQaiQAC/EBAQF/IwBBEGsiASAANgIMIAEoAgxBADYCACABKAIMQQA6AAQgASgCDEEAOgAFIAEoAgxBAToABiABKAIMQb8GOwEIIAEoAgxBCjsBCiABKAIMQQA7AQwgASgCDEF/NgIQIAEoAgxBADYCFCABKAIMQQA2AhggASgCDEIANwMgIAEoAgxCADcDKCABKAIMQQA2AjAgASgCDEEANgI0IAEoAgxBADYCOCABKAIMQQA2AjwgASgCDEEAOwFAIAEoAgxBgIDYjXg2AkQgASgCDEIANwNIIAEoAgxBADsBUCABKAIMQQA7AVIgASgCDEEANgJUC9oTAQF/IwBBsAFrIgMkACADIAA2AqgBIAMgATYCpAEgAyACNgKgASADQQA2ApABIAMgAygCpAEoAjBBABA7NgKUASADIAMoAqQBKAI4QQAQOzYCmAECQAJAAkACQCADKAKUAUECRgRAIAMoApgBQQFGDQELIAMoApQBQQFGBEAgAygCmAFBAkYNAQsgAygClAFBAkcNASADKAKYAUECRw0BCyADKAKkASIAIAAvAQxBgBByOwEMDAELIAMoAqQBIgAgAC8BDEH/7wNxOwEMIAMoApQBQQJGBEAgA0H14AEgAygCpAEoAjAgAygCqAFBCGoQxAE2ApABIAMoApABRQRAIANBfzYCrAEMAwsLAkAgAygCoAFBgAJxDQAgAygCmAFBAkcNACADQfXGASADKAKkASgCOCADKAKoAUEIahDEATYCSCADKAJIRQRAIAMoApABECQgA0F/NgKsAQwDCyADKAJIIAMoApABNgIAIAMgAygCSDYCkAELCwJAIAMoAqQBLwFSRQRAIAMoAqQBIgAgAC8BDEH+/wNxOwEMDAELIAMoAqQBIgAgAC8BDEEBcjsBDAsgAyADKAKkASADKAKgARCAAUEBcToAhgEgAyADKAKgAUGACnFBgApHBH8gAy0AhgEFQQELQQFxOgCHASADAn9BASADKAKkAS8BUkGBAkYNABpBASADKAKkAS8BUkGCAkYNABogAygCpAEvAVJBgwJGC0EBcToAhQEgAy0AhwFBAXEEQCADIANBIGpCHBAqNgIcIAMoAhxFBEAgAygCqAFBCGpBDkEAEBUgAygCkAEQJCADQX82AqwBDAILAkAgAygCoAFBgAJxBEACQCADKAKgAUGACHENACADKAKkASkDIEL/////D1YNACADKAKkASkDKEL/////D1gNAgsgAygCHCADKAKkASkDKBAuIAMoAhwgAygCpAEpAyAQLgwBCwJAAkAgAygCoAFBgAhxDQAgAygCpAEpAyBC/////w9WDQAgAygCpAEpAyhC/////w9WDQAgAygCpAEpA0hC/////w9YDQELIAMoAqQBKQMoQv////8PWgRAIAMoAhwgAygCpAEpAygQLgsgAygCpAEpAyBC/////w9aBEAgAygCHCADKAKkASkDIBAuCyADKAKkASkDSEL/////D1oEQCADKAIcIAMoAqQBKQNIEC4LCwsCfyMAQRBrIgAgAygCHDYCDCAAKAIMLQAAQQFxRQsEQCADKAKoAUEIakEUQQAQFSADKAIcEBcgAygCkAEQJCADQX82AqwBDAILIANBAQJ/IwBBEGsiACADKAIcNgIMAn4gACgCDC0AAEEBcQRAIAAoAgwpAxAMAQtCAAunQf//A3ELIANBIGpBgAYQUDYCjAEgAygCHBAXIAMoAowBIAMoApABNgIAIAMgAygCjAE2ApABCyADLQCFAUEBcQRAIAMgA0EVakIHECo2AhAgAygCEEUEQCADKAKoAUEIakEOQQAQFSADKAKQARAkIANBfzYCrAEMAgsgAygCEEECECAgAygCEEHP0wBBAhBAIAMoAhAgAygCpAEvAVJB/wFxEIoBIAMoAhAgAygCpAEoAhBB//8DcRAgAn8jAEEQayIAIAMoAhA2AgwgACgCDC0AAEEBcUULBEAgAygCqAFBCGpBFEEAEBUgAygCEBAXIAMoApABECQgA0F/NgKsAQwCCyADQYGyAkEHIANBFWpBgAYQUDYCDCADKAIQEBcgAygCDCADKAKQATYCACADIAMoAgw2ApABCyADIANB0ABqQi4QKiIANgJMIABFBEAgAygCqAFBCGpBDkEAEBUgAygCkAEQJCADQX82AqwBDAELIAMoAkxBxdMAQcrTACADKAKgAUGAAnEbQQQQQCADKAKgAUGAAnFFBEAgAygCTAJ/QS0gAy0AhgFBAXENABogAygCpAEvAQgLQf//A3EQIAsgAygCTAJ/QS0gAy0AhgFBAXENABogAygCpAEvAQoLQf//A3EQICADKAJMIAMoAqQBLwEMECACQCADLQCFAUEBcQRAIAMoAkxB4wAQIAwBCyADKAJMIAMoAqQBKAIQQf//A3EQIAsgAygCpAEoAhQgA0GeAWogA0GcAWoQwwEgAygCTCADLwGeARAgIAMoAkwgAy8BnAEQIAJAAkAgAy0AhQFBAXFFDQAgAygCpAEpAyhCFFoNACADKAJMQQAQIQwBCyADKAJMIAMoAqQBKAIYECELAkACQCADKAKgAUGAAnFBgAJHDQAgAygCpAEpAyBC/////w9UBEAgAygCpAEpAyhC/////w9UDQELIAMoAkxBfxAhIAMoAkxBfxAhDAELAkAgAygCpAEpAyBC/////w9UBEAgAygCTCADKAKkASkDIKcQIQwBCyADKAJMQX8QIQsCQCADKAKkASkDKEL/////D1QEQCADKAJMIAMoAqQBKQMopxAhDAELIAMoAkxBfxAhCwsgAygCTCADKAKkASgCMBBSQf//A3EQICADIAMoAqQBKAI0IAMoAqABEIIBQf//A3EgAygCkAFBgAYQggFB//8DcWo2AogBIAMoAkwgAygCiAFB//8DcRAgIAMoAqABQYACcUUEQCADKAJMIAMoAqQBKAI4EFJB//8DcRAgIAMoAkwgAygCpAEoAjxB//8DcRAgIAMoAkwgAygCpAEvAUAQICADKAJMIAMoAqQBKAJEECECQCADKAKkASkDSEL/////D1QEQCADKAJMIAMoAqQBKQNIpxAhDAELIAMoAkxBfxAhCwsCfyMAQRBrIgAgAygCTDYCDCAAKAIMLQAAQQFxRQsEQCADKAKoAUEIakEUQQAQFSADKAJMEBcgAygCkAEQJCADQX82AqwBDAELIAMoAqgBIANB0ABqAn4jAEEQayIAIAMoAkw2AgwCfiAAKAIMLQAAQQFxBEAgACgCDCkDEAwBC0IACwsQNkEASARAIAMoAkwQFyADKAKQARAkIANBfzYCrAEMAQsgAygCTBAXIAMoAqQBKAIwBEAgAygCqAEgAygCpAEoAjAQhgFBAEgEQCADKAKQARAkIANBfzYCrAEMAgsLIAMoApABBEAgAygCqAEgAygCkAFBgAYQgQFBAEgEQCADKAKQARAkIANBfzYCrAEMAgsLIAMoApABECQgAygCpAEoAjQEQCADKAKoASADKAKkASgCNCADKAKgARCBAUEASARAIANBfzYC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IAMoAoABKAIAIAMpA3CnQQR0aigCACADEPEBBEAgAygCfEEVQQAQFSADEFwgA0J/NwOIAQwDBSADKAKAASgCACADKQNwp0EEdGooAgAoAjQgAygCNBCFASEAIAMoAoABKAIAIAMpA3CnQQR0aigCACAANgI0IAMoAoABKAIAIAMpA3CnQQR0aigCAEEBOgAEIANBADYCNCADEFwgAyADKQNwQgF8NwNwDAILAAsLIAMCfiADKQNgIAMpA2h9Qv///////////wBUBEAgAykDYCADKQNofQwBC0L///////////8ACzcDiAELIAMpA4gBIQQgA0GQAWokACAEC6YBAQF/IwBBIGsiAyQAIAMgADYCGCADIAE2AhQgAyACNgIQIAMgAygCEBD6ASIANgIMAkAgAEUEQCADQQA2AhwMAQsgAygCDCADKAIYNgIAIAMoAgwgAygCFDYCBCADKAIUQRBxBEAgAygCDCIAIAAoAhRBAnI2AhQgAygCDCIAIAAoAhhBAnI2AhgLIAMgAygCDDYCHAsgAygCHCEAIANBIGokACAAC9UBAQF/IwBBIGsiBCQAIAQgADYCGCAEIAE3AxAgBCACNgIMIAQgAzYCCAJAAkAgBCkDEEL///////////8AVwRAIAQpAxBCgICAgICAgICAf1kNAQsgBCgCCEEEQT0QFSAEQX82AhwMAQsCfyAEKQMQIQEgBCgCDCEAIAQoAhgiAigCTEF/TARAIAIgASAAEJYBDAELIAIgASAAEJYBC0EASARAIAQoAghBBEG0nAEoAgAQFSAEQX82AhwMAQsgBEEANgIcCyAEKAIcIQAgBEEgaiQAIAALJwACf0EAQQAgABAFIgAgAEEbRhsiAEUNABpBtJwBIAA2AgBBAAsaC14BAX8jAEEQayIDJAAgAyABQcCAgAJxBH8gAyACQQRqNgIMIAIoAgAFQQALNgIAIAAgAUGAgAJyIAMQESIAQYFgTwRAQbScAUEAIABrNgIAQX8hAAsgA0EQaiQAIAALVQEBfyMAQRBrIgEkACABIAA2AgwCQAJAIAEoAgwoAiRBAUYNACABKAIMKAIkQQJGDQAMAQsgASgCDEEAQgBBChAiGiABKAIMQQA2AiQLIAFBEGokAAszAQF/An8gABAGIgFBYUYEQCAAEBIhAQsgAUGBYE8LBH9BtJwBQQAgAWs2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IEUNACABKAIIKAIkDQELIAFBATYCDAwBCyABIAEoAggoAhw2AgQCQAJAIAEoAgRFDQAgASgCBCgCACABKAIIRw0AIAEoAgQoAgRBKkYNASABKAIEKAIEQTlGDQEgASgCBCgCBEHFAEYNASABKAIEKAIEQckARg0BIAEoAgQoAgRB2wBGDQEgASgCBCgCBEHnAEYNASABKAIEKAIEQfEARg0BIAEoAgQoAgRBmgVGDQELIAFBATYCDAwBCyABQQA2AgwLIAEoAgwL0gQBAX8jAEEgayIDIAA2AhwgAyABNgIYIAMgAjYCFCADIAMoAhxB3BZqIAMoAhRBAnRqKAIANgIQIAMgAygCFEEBdDYCDANAAkAgAygCDCADKAIcKALQKEoNAAJAIAMoAgwgAygCHCgC0ChODQAgAygCGCADKAIcIAMoAgxBAnRqQeAWaigCAEECdGovAQAgAygCGCADKAIcQdwWaiADKAIMQQJ0aigCAEECdGovAQBOBEAgAygCGCADKAIcIAMoAgxBAnRqQeAWaigCAEECdGovAQAgAygCGCADKAIcQdwWaiADKAIMQQJ0aigCAEECdGovAQBHDQEgAygCHCADKAIMQQJ0akHgFmooAgAgAygCHEHYKGpqLQAAIAMoAhxB3BZqIAMoAgxBAnRqKAIAIAMoAhxB2Chqai0AAEoNAQsgAyADKAIMQQFqNgIMCyADKAIYIAMoAhBBAnRqLwEAIAMoAhggAygCHEHcFmogAygCDEECdGooAgBBAnRqLwEASA0AAkAgAygCGCADKAIQQQJ0ai8BACADKAIYIAMoAhxB3BZqIAMoAgxBAnRqKAIAQQJ0ai8BAEcNACADKAIQIAMoAhxB2Chqai0AACADKAIcQdwWaiADKAIMQQJ0aigCACADKAIcQdgoamotAABKDQAMAQsgAygCHEHcFmogAygCFEECdGogAygCHEHcFmogAygCDEECdGooAgA2AgAgAyADKAIMNgIUIAMgAygCDEEBdDYCDAwBCwsgAygCHEHcFmogAygCFEECdGogAygCEDYCAAvnCAEDfyMAQTBrIgIkACACIAA2AiwgAiABNgIoIAIgAigCKCgCADYCJCACIAIoAigoAggoAgA2AiAgAiACKAIoKAIIKAIMNgIcIAJBfzYCECACKAIsQQA2AtAoIAIoAixBvQQ2AtQoIAJBADYCGANAIAIoAhggAigCHE5FBEACQCACKAIkIAIoAhhBAnRqLwEABEAgAiACKAIYIgE2AhAgAigCLEHcFmohAyACKAIsIgQoAtAoQQFqIQAgBCAANgLQKCAAQQJ0IANqIAE2AgAgAigCGCACKAIsQdgoampBADoAAAwBCyACKAIkIAIoAhhBAnRqQQA7AQILIAIgAigCGEEBajYCGAwBCwsDQCACKAIsKALQKEECSARAAkAgAigCEEECSARAIAIgAigCEEEBaiIANgIQDAELQQAhAAsgAigCLEHcFmohAyACKAIsIgQoAtAoQQFqIQEgBCABNgLQKCABQQJ0IANqIAA2AgAgAiAANgIMIAIoAiQgAigCDEECdGpBATsBACACKAIMIAIoAixB2ChqakEAOgAAIAIoAiwiACAAKAKoLUF/ajYCqC0gAigCIARAIAIoAiwiACAAKAKsLSACKAIgIAIoAgxBAnRqLwECazYCrC0LDAELCyACKAIoIAIoAhA2AgQgAiACKAIsKALQKEECbTYCGANAIAIoAhhBAUhFBEAgAigCLCACKAIkIAIoAhgQdSACIAIoAhhBf2o2AhgMAQsLIAIgAigCHDYCDANAIAIgAigCLCgC4BY2AhggAigCLEHcFmohASACKAIsIgMoAtAoIQAgAyAAQX9qNgLQKCACKAIsIABBAnQgAWooAgA2AuAWIAIoAiwgAigCJEEBEHUgAiACKAIsKALgFjYCFCACKAIYIQEgAigCLEHcFmohAyACKAIsIgQoAtQoQX9qIQAgBCAANgLUKCAAQQJ0IANqIAE2AgAgAigCFCEBIAIoAixB3BZqIQMgAigCLCIEKALUKEF/aiEAIAQgADYC1CggAEECdCADaiABNgIAIAIoAiQgAigCDEECdGogAigCJCACKAIYQQJ0ai8BACACKAIkIAIoAhRBAnRqLwEAajsBACACKAIMIAIoAixB2ChqagJ/IAIoAhggAigCLEHYKGpqLQAAIAIoAhQgAigCLEHYKGpqLQAATgRAIAIoAhggAigCLEHYKGpqLQAADAELIAIoAhQgAigCLEHYKGpqLQAAC0EBajoAACACKAIkIAIoAhRBAnRqIAIoAgwiADsBAiACKAIkIAIoAhhBAnRqIAA7AQIgAiACKAIMIgBBAWo2AgwgAigCLCAANgLgFiACKAIs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AykDKCEBIANBMGokACABC3cBAX8jAEEQayICIAA2AgggAiABNgIEAkACQAJAIAIoAggpAyhC/////w9aDQAgAigCCCkDIEL/////D1oNACACKAIEQYAEcUUNASACKAIIKQNIQv////8PVA0BCyACQQE6AA8MAQsgAkEAOgAPCyACLQAPQQFxC9kCAQF/IwBBIGsiAyQAIAMgADYCGCADIAE2AhQgAyACNgIQIAMgA0EMakIEECo2AggCQCADKAIIRQRAIANBfzYCHAwBCwNAIAMoAhQEQCADKAIUKAIEIAMoAhBxQYAGcQRAIAMoAghCABAtGiADKAIIIAMoAhQvAQgQICADKAIIIAMoAhQvAQoQIAJ/IwBBEGsiACADKAIINgIMIAAoAgwtAABBAXFFCwRAIAMoAhhBCGpBFEEAEBUgAygCCBAXIANBfzYCHAwECyADKAIYIANBDGpCBBA2QQBIBEAgAygCCBAXIANBfzYCHAwECyADKAIULwEKQQBKBEAgAygCGCADKAIUKAIMIAMoAhQvAQqtEDZBAEgEQCADKAIIEBcgA0F/NgIcDAULCwsgAyADKAIUKAIANgIUDAELCyADKAIIEBcgA0EANgIcCyADKAIcIQAgA0EgaiQAIAALaAEBfyMAQRBrIgIgADYCDCACIAE2AgggAkEAOwEGA0AgAigCDARAIAIoAgwoAgQgAigCCHFBgAZxBEAgAiACKAIMLwEKIAIvAQZBBGpqOwEGCyACIAIoAgwoAgA2AgwMAQsLIAIvAQYL8AEBAX8jAEEQayIBJAAgASAANgIMIAEgASgCDDYCCCABQQA2AgQDQCABKAIMBEACQAJAIAEoAgwvAQhB9cYBRg0AIAEoAgwvAQhB9eABRg0AIAEoAgwvAQhBgbICRg0AIAEoAgwvAQhBAUcNAQsgASABKAIMKAIANgIAIAEoAgggASgCDEYEQCABIAEoAgA2AggLIAEoAgxBADYCACABKAIMECQgASgCBARAIAEoAgQgASgCADYCAAsgASABKAIANgIMDAILIAEgASgCDDYCBCABIAEoAgwoAgA2AgwMAQsLIAEoAgghACABQRBqJAAgAAuzBAEBfyMAQUBqIgUkACAFIAA2AjggBSABOwE2IAUgAjYCMCAFIAM2AiwgBSAENgIoIAUgBSgCOCAFLwE2rRAqIgA2AiQC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ACgCDCgCBDYCAAsLIAIoAgwEQCACKAIMIAIoAgQ2AgALIAJBEGokAAuVAQEBfyMAQRBrIgEkACABIAA2AggCQAJ/IwBBEGsiACABKAIINgIMIAAoAgwpAxhCgIAQg1ALBEAgASgCCCgCAARAIAEgASgCCCgCABCUAUEBcToADwwCCyABQQE6AA8MAQsgASABKAIIQQBCAEESECI+AgQgASABKAIEQQBHOgAPCyABLQAPQQFxIQAgAUEQaiQAIAALfwEBfyMAQSBrIgMkACADIAA2AhggAyABNwMQIANBADYCDCADIAI2AggCQCADKQMQQv///////////wBWBEAgAygCCEEEQT0QFSADQX82AhwMAQsgAyADKAIYIAMpAxAgAygCDCADKAIIEGc2AhwLIAMoAhwhACADQSBqJAAgAAt9ACACQQFGBEAgASAAKAIIIAAoAgRrrH0hAQsCQCAAKAIUIAAoAhxLBEAgAEEAQQAgACgCJBEBABogACgCFEUNAQsgAEEANgIcIABCADcDECAAIAEgAiAAKAIoERAAQgBTDQAgAEIANwIEIAAgACgCAEFvcTYCAEEADwtBfwviAgECfyMAQSBrIgMkAAJ/AkACQEH0lwEgASwAABCYAUUEQEG0nAFBHDYCAAwBC0GYCRAZIgINAQtBAAwBCyACQQBBkAEQMyABQSsQmAFFBEAgAkEIQQQgAS0AAEHyAEYbNgIACwJAIAEtAABB4QBHBEAgAigCACEBDAELIABBA0EAEAQiAUGACHFFBEAgAyABQYAIcjYCECAAQQQgA0EQahAEGgsgAiACKAIAQYABciIBNgIACyACQf8BOgBLIAJBgAg2AjAgAiAANgI8IAIgAkGYAWo2AiwCQCABQQhxDQAgAyADQRhqNgIAIABBk6gBIAMQDg0AIAJBCjoASwsgAkEaNgIoIAJBGzYCJCACQRw2AiAgAkEdNgIMQdygASgCAEUEQCACQX82AkwLIAJBsKEBKAIANgI4QbChASgCACIABEAgACACNgI0C0GwoQEgAjYCACACCyEAIANBIGokACAACxoAIAAgARCFAiIAQQAgAC0AACABQf8BcUYbCxgAIAAoAkxBf0wEQCAAEJoBDwsgABCaAQtgAgJ/AX4gACgCKCEBQQEhAiAAQgAgAC0AAEGAAXEEf0ECQQEgACgCFCAAKAIc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KAIYKQMwWgRAIAIoAhhBCGpBEkEAEBUgAkF/NgIcDAELIAIoAhgoAhhBAnEEQCACKAIYQQhqQRlBABAVIAJBfzYCHAwBCyACIAIoAhggAikDEEEAIAIoAhhBCGoQTyIANgIMIABFBEAgAkF/NgIcDAELIAIoAhgoAlAgAigCDCACKAIYQQhqEFlBAXFFBEAgAkF/NgIcDAELIAIoAhggAikDEBCgAgRAIAJBfzYCHAwBCyACKAIYKAJAIAIpAxCnQQR0akEBOgAMIAJBADYCHAsgAigCHCEAIAJBIGokACAAC5gCAAJAAkAgAUEUSw0AAkACQAJAAkACQAJAAkACQCABQXdqDgoAAQIJAwQFBgkHCAsgAiACKAIAIgFBBGo2AgAgACABKAIANgIADwsgAiACKAIAIgFBBGo2AgAgACABNAIANwMADwsgAiACKAIAIgFBBGo2AgAgACABNQIANwMADwsgAiACKAIAIgFBBGo2AgAgACABMgEANwMADwsgAiACKAIAIgFBBGo2AgAgACABMwEANwMADwsgAiACKAIAIgFBBGo2AgAgACABMAAANwMADwsgAiACKAIAIgFBBGo2AgAgACABMQAANwMADwsgACACQRYRBAALDwsgAiACKAIAQQdqQXhxIgFBCGo2AgAgACABKQMANwMAC0oBA38gACgCACwAAEFQakEKSQRAA0AgACgCACIBLAAAIQMgACABQQFqNgIAIAMgAkEKbGpBUGohAiABLAABQVBqQQpJDQALCyACC38CAX8BfiAAvSIDQjSIp0H/D3EiAkH/D0cEfCACRQRAIAEgAEQAAAAAAAAAAGEEf0EABSAARAAAAAAAAPBDoiABEKQBIQAgASgCAEFAags2AgAgAA8LIAEgAkGCeGo2AgAgA0L/////////h4B/g0KAgICAgICA8D+EvwUgAAsLEgAgAEUEQEEADwsgACABELQCC+UBAQJ/IAJBAEchAwJAAkACQCACRQ0AIABBA3FFDQAgAUH/AXEhBANAIAAtAAAgBEYNAiAAQQFqIQAgAkF/aiICQQBHIQMgAkUNASAAQQNxDQALCyADRQ0BCwJAIAAtAAAgAUH/AXFGDQAgAkEESQ0AIAFB/wFxQYGChAhsIQMDQCAAKAIAIANzIgRBf3MgBEH//ft3anFBgIGChHhxDQEgAEEEaiEAIAJB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HAvkAQEBfyMAQSBrIgMkACADIAA6ABsgAyABNgIUIAMgAjYCECADQcgAEBkiADYCDAJAIABFBEAgAygCEEEBQbScASgCABAVIANBADYCHAwBCyADKAIMIAMoAhA2AgAgAygCDCADLQAbQQFxOgAEIAMoAgwgAygCFDYCCAJAIAMoAgwoAghBAU4EQCADKAIMKAIIQQlMDQELIAMoAgxBCTYCCAsgAygCDEEAOgAMIAMoAgxBADYCMCADKAIMQQA2AjQgAygCDEEANgI4IAMgAygCDDYCHAsgAygCHCEAIANBIGokACAAC+MIAQF/IwBBQGoiAiAANgI4IAIgATYCNCACIAIoAjgoAnw2AjAgAiACKAI4KAI4IAIoAjgoAmxqNgIsIAIgAigCOCgCeDYCICACIAIoAjgoApABNgIcIAICfyACKAI4KAJsIAIoAjgoAixBhgJrSwRAIAIoAjgoAmwgAigCOCgCLEGGAmtrDAELQQALNgIYIAIgAigCOCgCQDYCFCACIAIoAjgoAjQ2AhAgAiACKAI4KAI4IAIoAjgoAmxqQYICajYCDCACIAIoAiwgAigCIEEBa2otAAA6AAsgAiACKAIsIAIoAiBqLQAAOgAKIAIoAjgoAnggAigCOCgCjAFPBEAgAiACKAIwQQJ2NgIwCyACKAIcIAIoAjgoAnRLBEAgAiACKAI4KAJ0NgIcCwNAAkAgAiACKAI4KAI4IAIoAjRqNgIoAkAgAigCKCACKAIgai0AACACLQAKRw0AIAIoAiggAigCIEEBa2otAAAgAi0AC0cNACACKAIoLQAAIAIoAiwtAABHDQAgAiACKAIoIgBBAWo2AiggAC0AASACKAIsLQABRwRADAELIAIgAigCLEECajYCLCACIAIoAihBAWo2AigDQCACIAIoAiwiAEEBajYCLCAALQABIQEgAiACKAIoIgBBAWo2AigCf0EAIAAtAAEgAUcNABogAiACKAIsIgBBAWo2AiwgAC0AASEBIAIgAigCKCIAQQFqNgIoQQAgAC0AASABRw0AGiACIAIoAiwiAEEBajYCLCAALQABIQEgAiACKAIoIgBBAWo2AihBACAALQABIAFHDQAaIAIgAigCLCIAQQFqNgIsIAAtAAEhASACIAIoAigiAEEBajYCKEEAIAAtAAEgAUcNABogAiACKAIsIgBBAWo2AiwgAC0AASEBIAIgAigCKCIAQQFqNgIoQQAgAC0AASABRw0AGiACIAIoAiwiAEEBajYCLCAALQABIQEgAiACKAIoIgBBAWo2AihBACAALQABIAFHDQAaIAIgAigCLCIAQQFqNgIsIAAtAAEhASACIAIoAigiAEEBajYCKEEAIAAtAAEgAUcNABogAiACKAIsIgBBAWo2AiwgAC0AASEBIAIgAigCKCIAQQFqNgIoQQAgAC0AASABRw0AGiACKAIsIAIoAgxJC0EBcQ0ACyACQYICIAIoAgwgAigCLGtrNgIkIAIgAigCDEH+fWo2AiwgAigCJCACKAIgSgRAIAIoAjggAigCNDYCcCACIAIoAiQ2AiAgAigCJCACKAIcTg0CIAIgAigCLCACKAIgQQFrai0AADoACyACIAIoAiwgAigCIGotAAA6AAoLCyACIAIoAhQgAigCNCACKAIQcUEBdGovAQAiATYCNEEAIQAgASACKAIYSwR/IAIgAigCMEF/aiIANgIwIABBAEcFQQALQQFxDQELCwJAIAIoAiAgAigCOCgCdE0EQCACIAIoAiA2AjwMAQsgAiACKAI4KAJ0NgI8CyACKAI8C5gQAQF/IwBBMGsiAiQAIAIgADYCKCACIAE2AiQgAgJ/IAIoAigoAgxBBWsgAigCKCgCLEsEQCACKAIoKAIsDAELIAIoAigoAgxBBWsLNgIgIAJBADYCECACIAIoAigoAgAoAgQ2AgwDQAJAIAJB//8DNgIcIAIgAigCKCgCvC1BKmpBA3U2AhQgAigCKCgCACgCECACKAIUSQ0AIAIgAigCKCgCACgCECACKAIUazYCFCACIAIoAigoAmwgAigCKCgCXGs2AhggAigCHCACKAIYIAIoAigoAgAoAgRqSwRAIAIgAigCGCACKAIoKAIAKAIEajYCHAsgAigCHCACKAIUSwRAIAIgAigCFDYCHAsCQCACKAIcIAIoAiBPDQACQCACKAIcRQRAIAIoAiRBBEcNAQsgAigCJEUNACACKAIcIAIoAhggAigCKCgCACgCBGpGDQELDAELQQAhACACIAIoAiRBBEYEfyACKAIcIAIoAhggAigCKCgCACgCBGpGBUEAC0EBcUVFNgIQIAIoAihBAEEAIAIoAhAQVyACKAIoKAIIIAIoAigoAhRBBGtqIAIoAhw6AAAgAigCKCgCCCACKAIoKAIUQQNraiACKAIcQQh2OgAAIAIoAigoAgggAigCKCgCFEECa2ogAigCHEF/czoAACACKAIoKAIIIAIoAigoAhRBAWtqIAIoAhxBf3NBCHY6AAAgAigCKCgCABAdIAIoAhgEQCACKAIYIAIoAhxLBEAgAiACKAIcNgIYCyACKAIoKAIAKAIMIAIoAigoAjggAigCKCgCXGogAigCGBAaGiACKAIoKAIAIgAgAigCGCAAKAIMajYCDCACKAIoKAIAIgAgACgCECACKAIYazYCECACKAIoKAIAIgAgAigCGCAAKAIUajYCFCACKAIoIgAgAigCGCAAKAJcajYCXCACIAIoAhwgAigCGGs2AhwLIAIoAhwEQCACKAIoKAIAIAIoAigoAgAoAgwgAigCHBBzGiACKAIoKAIAIgAgAigCHCAAKAIMajYCDCACKAIoKAIAIgAgACgCECACKAIcazYCECACKAIoKAIAIgAgAigCHCAAKAIUajYCFAsgAigCEEUNAQsLIAIgAigCDCACKAIoKAIAKAIEazYCDCACKAIMBEACQCACKAIMIAIoAigoAixPBEAgAigCKEECNgKwLSACKAIoKAI4IAIoAigoAgAoAgAgAigCKCgCLGsgAigCKCgCLBAaGiACKAIoIAIoAigoAiw2AmwMAQsgAigCKCgCPCACKAIoKAJsayACKAIMTQRAIAIoAigiACAAKAJsIAIoAigoAixrNgJsIAIoAigoAjggAigCKCgCOCACKAIoKAIsaiACKAIoKAJsEBoaIAIoAigoArAtQQJJBEAgAigCKCIAIAAoArAtQQFqNgKwLQsLIAIoAigoAjggAigCKCgCbGogAigCKCgCACgCACACKAIMayACKAIMEBoaIAIoAigiACACKAIMIAAoAmxqNgJsCyACKAIoIAIoAigoAmw2AlwgAigCKCIBAn8gAigCDCACKAIoKAIsIAIoAigoArQta0sEQCACKAIoKAIsIAIoAigoArQtawwBCyACKAIMCyABKAK0LWo2ArQtCyACKAIoKALALSACKAIoKAJsSQRAIAIoAiggAigCKCgCbDYCwC0LAkAgAigCEARAIAJBAzYCLAwBCwJAIAIoAiRFDQAgAigCJEEERg0AIAIoAigoAgAoAgQNACACKAIoKAJsIAIoAigoAlxHDQAgAkEBNgIsDAELIAIgAigCKCgCPCACKAIoKAJsa0EBazYCFAJAIAIoAigoAgAoAgQgAigCFE0NACACKAIoKAJcIAIoAigoAixIDQAgAigCKCIAIAAoAlwgAigCKCgCLGs2AlwgAigCKCIAIAAoAmwgAigCKCgCLGs2AmwgAigCKCgCOCACKAIoKAI4IAIoAigoAixqIAIoAigoAmwQGhogAigCKCgCsC1BAkkEQCACKAIoIgAgACgCsC1BAWo2ArAtCyACIAIoAigoAiwgAigCFGo2AhQLIAIoAhQgAigCKCgCACgCBEsEQCACIAIoAigoAgAoAgQ2AhQLIAIoAhQEQCACKAIoKAIAIAIoAigoAjggAigCKCgCbGogAigCFBBzGiACKAIoIgAgAigCFCAAKAJsajYCbAsgAigCKCgCwC0gAigCKCgCbEkEQCACKAIoIAIoAigoAmw2AsAtCyACIAIoAigoArwtQSpqQQN1NgIUIAICf0H//wMgAigCKCgCDCACKAIUa0H//wNLDQAaIAIoAigoAgwgAigCFGsLNgIUIAICfyACKAIUIAIoAigoAixLBEAgAigCKCgCLAwBCyACKAIUCzYCICACIAIoAigoAmwgAigCKCgCXGs2AhgCQCACKAIYIAIoAiBJBEAgAigCGEUEQCACKAIkQQRHDQILIAIoAiRFDQEgAigCKCgCACgCBA0BIAIoAhggAigCFEsNAQsgAgJ/IAIoAhggAigCFEsEQCACKAIUDAELIAIoAhgLNgIcIAICf0EAIAIoAiRBBEcNABpBACACKAIoKAIAKAIEDQAaIAIoAhwgAigCGEYLQQFxRUU2AhAgAigCKCACKAIoKAI4IAIoAigoAlxqIAIoAhwgAigCEBBXIAIoAigiACACKAIcIAAoAlxqNgJcIAIoAigoAgAQHQsgAkECQQAgAigCEBs2AiwLIAIoAiwhACACQTBqJAAgAAuyAgEBfyMAQRBrIgEkACABIAA2AggCQCABKAIIEHQEQCABQX42AgwMAQsgASABKAIIKAIcKAIENgIEIAEoAggoAhwoAggEQCABKAIIKAIoIAEoAggoAhwoAgggASgCCCgCJBEEAAsgASgCCCgCHCgCRARAIAEoAggoAiggASgCCCgCHCgCRCABKAIIKAIkEQQACyABKAIIKAIcKAJABEAgASgCCCgCKCAB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A0hCGnxBABAoQQBIBEAgAigCKEEIaiACKAIoKAIAEBggAkF/NgIsDAELIAIgAigCKCgCAEIEIAJBGGogAigCKEEIahBBIgA2AhQgAEUEQCACQX82AiwMAQsgAiACKAIUEB47ARIgAiACKAIUEB47ARAgAigCFBBIQQFxRQRAIAIoAhQQFyACKAIoQQhqQRRBABAVIAJBfzYCLAwBCyACKAIUEBcgAi8BEEEASgRAIAIoAigoAgAgAi8BEq1BARAoQQBIBEAgAigCKEEIakEEQbScASgCABAVIAJBfzYCLAwCCyACQQAgAigCKCgCACACLwEQQQAgAigCKEEIahBgNgIIIAIoAghFBEAgAkF/NgIsDAILIAIoAgggAi8BEEGAAiACQQxqIAIoAihBCGoQhAFBAXFFBEAgAigCCBAWIAJBfzYCLAwCCyACKAIIEBYgAigCDARAIAIgAigCDBCDATYCDCACKAIcKAIAKAI0IAIoAgwQhQEhACACKAIcKAIAIAA2AjQLCyACKAIcKAIAQQE6AAQCQCACKAIcKAIERQ0AIAIoAhwoAgQtAARBAXENACACKAIcKAIEIAIoAhwoAgAoAjQ2AjQgAigCHCgCBEEBOgAECyACQQA2AiwLIAIoAiwhACACQTBqJAAgAAsHACAAKAIAC4wBAQF/IwBBIGsiAiQAIAIgADYCGCACIAE2AhQgAkEANgIQAkAgAigCFEUEQCACQQA2AhwMAQsgAiACKAIUEBk2AgwgAigCDEUEQCACKAIQQQ5BABAVIAJBADYCHAwBCyACKAIMIAIoAhggAigCFBAaGiACIAIoAgw2AhwLIAIoAhwhACACQSBqJAAgAAsYAEGonAFCADcCAEGwnAFBADYCAEGonAELiAEBAX8jAEEgayIDJAAgAyAANgIUIAMgATYCECADIAI3AwgCQAJAIAMoAhQoAiRBAUYEQCADKQMIQv///////////wBYDQELIAMoAhRBDGpBEkEAEBUgA0J/NwMYDAELIAMgAygCFCADKAIQIAMpAwhBCxAiNwMYCyADKQMYIQIgA0EgaiQAIAILcwEBfyMAQSBrIgEkACABIAA2AhggAUIINwMQIAEgASgCGCkDECABKQMQfDcDCAJAIAEpAwggASgCGCkDEFQEQCABKAIYQQA6AAAgAUF/NgIcDAELIAEgASgCGCAB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IAIgADYCSCACIAE2AkQgAkEIaiIAEDwCQCACKAJIIAAQOQRAIwBBEGsiACACKAJINgIMIAIgACgCDEEMajYCBCMAQRBrIgAgAigCBDYCDAJAIAAoAgwoAgBBBUcNACMAQRBrIgAgAigCBDYCDCAAKAIMKAIEQSxHDQAgAkEANgJMDAILIAIoAkQgAigCBBBEIAJBfzYCTAwBCyACQQE2AkwLIAIoAkwhACACQdAAaiQAIAAL6gEBAX8jAEEwayIDJAAgAyAANgIoIAMgATYCJCADIAI2AiAjAEEQayIAIANBCGoiATYCDCAAKAIMQQA2AgAgACgCDEEANgIEIAAoAgxBADYCCCADIAMoAiggARD7ASIANgIYAkAgAEUEQCADKAIgIANBCGoiABCTASAAEDggA0EANgIsDAELIAMgAygCGCADKAIkIANBCGoQkgEiADYCHCAARQRAIAMoAhgQHCADKAIgIANBCGoiABCTASAAEDggA0EANgIsDAELIANBCGoQOCADIAMoAhw2AiwLIAMoAiwhACADQTBqJAAgAAvIAgEBfyMAQRBrIgEkACABIAA2AgggAUHYABAZNgIEAkAgASgCBEUEQCABKAIIQQ5BABAVIAFBADYCDAwBCyABKAIIEIEDIQAgASgCBCAANgJQIABFBEAgASgCBBAWIAFBADYCDAwBCyABKAIEQQA2AgAgASgCBEEANgIEIwBBEGsiACABKAIEQQhqNgIMIAAoAgxBADYCACAAKAIMQQA2AgQgACgCDEEANgIIIAEoAgRBADYCGCABKAIEQQA2AhQgASgCBEEANgIcIAEoAgRBADYCJCABKAIEQQA2AiAgASgCBEEAOgAoIAEoAgRCADcDOCABKAIEQgA3AzAgASgCBEEANgJAIAEoAgRBADYCSCABKAIEQQA2AkQgASgCBEEANgJMIAEoAgRBADYCVCABIAEoAgQ2AgwLIAEoAgwhACABQRBqJAAgAAuBAQEBfyMAQSBrIgIkACACIAA2AhggAkIANwMQIAJCfzcDCCACIAE2AgQCQAJAIAIoAhgEQCACKQMIQn9ZDQELIAIoAgRBEkEAEBUgAkEANgIcDAELIAIgAigCGCACKQMQIAIpAwggAigCBBD/ATYCHAsgAigCHCEAIAJBIGokACAAC80BAQJ/IwBBIGsiASQAIAEgADYCGCAB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CyAAIAFBf2oiAWoiAi0AAEEvRw0ACyACC6kBAQN/AkAgAC0AACICRQ0AA0AgAS0AACIERQRAIAIhAwwCCwJAIAIgBEYNACACQSByIAIgAkG/f2pBGkkbIAEtAAAiAkEgciACIAJBv39qQRpJG0YNACAALQAAIQMMAgsgAUEBaiEBIAAtAAEhAiAAQQFqIQAgAg0ACwsgA0H/AXEiAEEgciAAIABBv39qQRpJGyABLQAAIgBBIHIgACAAQb9/akEaSRtrC+gDAQN/IwBBsAFrIgEkACABIAA2AqgBIAEoAqgBEDgCQAJAIAEoAqgBKAIAQQBOBEAgASgCqAEoAgBBoA4oAgBIDQELIAEgASgCqAEoAgA2AhAgAUEgakG8lwEgAUEQahBvIAFBADYCpAEgASABQSBqNgKgAQwBCyABIAEoAqgBKAIAQQJ0QaANaigCADYCpAECQAJAAkACQCABKAKoASgCAEECdEGwDmooAgBBf2oOAgABAgsgASABKAKoASgCBEGQmgEoAgAQpAI2AqABDAILIwBBEGsiACABKAKoASgCBDYCDCABQQAgACgCDGtBAnRB2NQAaigCADYCoAEMAQsgAUEANgKgAQsLAkAgASgCoAFFBEAgASABKAKkATYCrAEMAQsgASABKAKgARAsAn8gASgCpAEEQCABKAKkARAsQQJqDAELQQALakEBahAZIgA2AhwgAEUEQCABQdgNKAIANgKsAQwBCyABKAIcIQACfyABKAKkAQRAIAEoAqQBDAELQdSXAQshAkHVlwFB1JcBIAEoAqQBGyEDIAEgASgCoAE2AgggASADNgIEIAEgAjYCACAAQc2XASABEG8gASgCqAEgASgCHDYCCCABIAEoAhw2AqwBCyABKAKsASEAIAFBsAFqJAAgAAtxAQN/AkACQANAIAAgAkHQiAFqLQAARwRAQdcAIQMgAkEBaiICQdcARw0BDAILCyACIgMNAEGwiQEhAAwBC0GwiQEhAgNAIAItAAAhBCACQQFqIgAhAiAEDQAgACECIANBf2oiAw0ACwsgASgCFBogAAszAQF/IAAoAhQiAyABIAIgACgCECADayIBIAEgAksbIgEQGhogACAAKAIUIAFqNgIUIAILigEBAn8jAEGgAWsiAyQAIANBCGpBuIcBQZABEBoaIAMgADYCNCADIAA2AhwgA0F+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QQhxBEAgACABQSByNgIAQX8PCyAAQgA3AgQgACAAKAIsIgE2AhwgACABNgIUIAAgASAAKAIwajYCEEEACwYAQaShAQsGAEGgoQELBgBBmKEBC9kDAgJ/An4jAEEgayICJAACQCABQv///////////wCDIgVCgICAgICAwP9DfCAFQoCAgICAgMCAvH98VARAIAFCBIYgAEI8iIQhBCAAQv//////////D4MiAEKBgICAgICAgAhaBEAgBEKBgICAgICAgMAAfCEEDAILIARCgICAgICAgIBAfSEEIABCgICAgICAgIAIhUIAUg0BIARCAYMgBHwhBAwBCyAAUCAFQoCAgICAgMD//wBUIAVCgICAgICAwP//AFEbRQRAIAFCBIYgAEI8iIRC/////////wODQoCAgICAgID8/wCEIQQMAQtCgICAgICAgPj/ACEEIAVC////////v//DAFYNAEIAIQQgBUIwiKciA0GR9wBJDQAgAkEQaiAAIAFC////////P4NCgICAgICAwACEIgQgA0H/iH9qELMCIAIgACAEQYH4ACADaxCyAiACKQMIQgSGIAIpAwAiAEI8iIQhBCACKQMQIAIpAxiEQgBSrSAAQv//////////D4OEIgBCgYCAgICAgIAIWgRAIARCAXwhBAwBCyAAQoCAgICAgICACIVCAFINACAEQgGDIAR8IQQLIAJBIGokACAEIAFCgICAgICAgICAf4OEvwtQAQF+AkAgA0HAAHEEQCACIANBQGqtiCEBQgAhAgwBCyADRQ0AIAJBwAAgA2uthiABIAOtIgSIhCEBIAIgBIghAgsgACABNwMAIAAgAjcDCAtQAQF+AkAgA0HAAHEEQCABIANBQGqthiECQgAhAQwBCyADRQ0AIAIgA60iBIYgAUHAACADa62IhCECIAEgBIYhAQsgACABNwMAIAAgAjcDCAuLAgACQCAABH8gAUH/AE0NAQJAQZCaASgCACgCAEUEQCABQYB/cUGAvwNGDQMMAQsgAUH/D00EQCAAIAFBP3FBgAFyOgABIAAgAUEGdkHAAXI6AABBAg8LIAFBgLADT0EAIAFBgEBxQYDAA0cbRQRAIAAgAUE/cUGAAXI6AAIgACABQQx2QeABcjoAACAAIAFBBnZBP3FBgAFyOgABQQMPCyABQYCAfGpB//8/TQRAIAAgAUE/cUGAAXI6AAMgACABQRJ2QfABcjoAACAAIAFBBnZBP3FBgAFyOgACIAAgAUEMdkE/cUGAAXI6AAFBBA8LC0G0nAFBGTYCAEF/BUEBCw8LIAAgAToAAEEBC74CAQF/IwBBwMAAayIDJAAgAyAANgK4QCADIAE2ArRAIAMgAjcDqEACQCADKAK0QBBJQQBIBEAgAygCuEBBCGogAygCtEAQGCADQX82ArxADAELIANBADYCDCADQgA3AxADQAJAIAMgAygCtEAgA0EgakKAwAAQLyICNwMYIAJCAFcNACADKAK4QCADQSBqIAMpAxgQNkEASARAIANBfzYCDAUgAykDGEKAwABSDQIgAygCuEAoAlRFDQIgAykDqEBCAFcNAiADIAMpAxggAykDEHw3AxAgAygCuEAoAlQgAykDELkgAykDqEC5oxBYDAILCwsgAykDGEIAUwRAIAMoArhAQQhqIAMoArRAEBggA0F/NgIMCyADKAK0QBAyGiADIAMoAgw2ArxACyADKAK8QCEAIANBwMAAaiQAIAALqgEBAX8jAEEwayIDJAAgAyAANgIoIAMgATYCJCADIAI3AxggAyADKAIoKAIAEDUiAjcDEAJAIAJCAFMEQCADQX82AiwMAQsgAyADKAIoIAMoAiQgAykDGBCQAyICNwMAIAJCAFMEQCADQX82AiwMAQsgAyADKAIoKAIAEDUiAjcDCCACQgBTBEAgA0F/NgIsDAELIANBADYCLAsgAygCLCEAIANBMGokACAAC/4BAQF/IwBBoMAAayICJAAgAiAANgKYQCACIAE3A5BAIAIgAikDkEC6OQMAAkADQCACKQOQQEIAVgRAIAICfkKAwAAgAikDkEBCgMAAVg0AGiACKQOQQAs+AgwgAigCmEAoAgAgAkEQaiACKAIMrSACKAKYQEEIahBhQQBIBEAgAkF/NgKcQAwDCyACKAKYQCACQRBqIAIoAgytEDZBAEgEQCACQX82ApxADAMFIAIgAikDkEAgAjUCDH03A5BAIAIoAphAKAJUIAIrAwAgAikDkEC6oSACKwMAoxBYDAILAAsLIAJBADYCnEALIAIoApxAIQAgAkGgwABqJAAgAAvnEQIBfwF+IwBBoAFrIgMkACADIAA2ApgBIAMgATYClAEgAyACNgKQAQJAIAMoApQBIANBOGoQOUEASARAIAMoApgBQQhqIAMoApQBEBggA0F/NgKcAQwBCyADKQM4QsAAg1AEQCADIAMpAzhCwACENwM4IANBADsBaAsCQAJAIAMoApABKAIQQX9HBEAgAygCkAEoAhBBfkcNAQsgAy8BaEUNACADKAKQASADLwFoNgIQDAELAkACQCADKAKQASgCEA0AIAMpAzhCBINQDQAgAyADKQM4QgiENwM4IAMgAykDUDcDWAwBCyADIAMpAzhC9////w+DNwM4CwsgAykDOEKAAYNQBEAgAyADKQM4QoABhDcDOCADQQA7AWoLIANBgAI2AiQCQCADKQM4QgSDUARAIAMgAygCJEGACHI2AiQgA0J/NwNwDAELIAMoApABIAMpA1A3AyggAyADKQNQNwNwAkAgAykDOEIIg1AEQAJAAkACQAJAAkACfwJAIAMoApABKAIQQX9HBEAgAygCkAEoAhBBfkcNAQtBCAwBCyADKAKQASgCEAtB//8DcQ4NAgMDAwMDAwMBAwMDAAMLIANClMLk8w83AxAMAwsgA0KDg7D/DzcDEAwCCyADQv////8PNwMQDAELIANCADcDEAsgAykDUCADKQMQVgRAIAMgAygCJEGACHI2AiQLDAELIAMoApABIAMpA1g3AyALCyADIAMoApgBKAIAEDUiBDcDiAEgBEIAUwRAIAMoApgBQQhqIAMoApgBKAIAEBggA0F/NgKcAQwBCyADKAKQASIAIAAvAQxB9/8DcTsBDCADIAMoApgBIAMoApABIAMoAiQQXiIANgIoIABBAEgEQCADQX82ApwBDAELIAMgAy8BaAJ/AkAgAygCkAEoAhBBf0cEQCADKAKQASgCEEF+Rw0BC0EIDAELIAMoApABKAIQC0H//wNxRzoAIiADIAMtACJBAXEEfyADLwFoQQBHBUEAC0EBcToAISADIAMvAWgEfyADLQAhBUEBC0EBcToAICADIAMtACJBAXEEfyADKAKQASgCEEEARwVBAAtBAXE6AB8gAwJ/QQEgAy0AIkEBcQ0AGkEBIAMoApABKAIAQYABcQ0AGiADKAKQAS8BUiADLwFqRwtBAXE6AB4gAyADLQAeQQFxBH8gAy8BakEARwVBAAtBAXE6AB0gAyADLQAeQQFxBH8gAygCkAEvAVJBAEcFQQALQQFxOgAcIAMgAygClAE2AjQjAEEQayIAIAMoAjQ2AgwgACgCDCIAIAAoAjBBAWo2AjAgAy0AHUEBcQRAIAMgAy8BakEAEHciADYCDCAARQRAIAMoApgBQQhqQRhBABAVIAMoAjQQHCADQX82ApwBDAILIAMgAygCmAEgAygCNCADLwFqQQAgAygCmAEoAhwgAygCDBEGACIANgIwIABFBEAgAygCNBAcIANBfzYCnAEMAgsgAygCNBAcIAMgAygCMDYCNAsgAy0AIUEBcQRAIAMgAygCmAEgAygCNCADLwFoEKsBIgA2AjAgAEUEQCADKAI0EBwgA0F/NgKcAQwCCyADKAI0EBwgAyADKAIwNgI0CyADLQAgQQFxBEAgAyADKAKYASADKAI0QQAQqgEiADYCMCAARQRAIAMoAjQQHCADQX82ApwBDAILIAMoAjQQHCADIAMoAjA2AjQLIAMtAB9BAXEEQCADIAMoApgBIAMoAjQgAygCkAEoAhAgAygCkAEvAVAQwgIiADYCMCAARQRAIAMoAjQQHCADQX82ApwBDAILIAMoAjQQHCADIAMoAjA2AjQLIAMtABxBAXEEQCADQQA2AgQCQCADKAKQASgCVARAIAMgAygCkAEoAlQ2AgQMAQsgAygCmAEoAhwEQCADIAMoApgBKAIcNgIECwsgAyADKAKQAS8BUkEBEHciADYCCCAARQRAIAMoApgBQQhqQRhBABAVIAMoAjQQHCADQX82ApwBDAILIAMgAygCmAEgAygCNCADKAKQAS8BUkEBIAMoAgQgAygCCBEGACIANgIwIABFBEAgAygCNBAcIANBfzYCnAEMAgsgAygCNBAcIAMgAygCMDYCNAsgAyADKAKYASgCABA1IgQ3A4ABIARCAFMEQCADKAKYAUEIaiADKAKYASgCABAYIANBfzYCnAEMAQsgAyADKAKYASADKAI0IAMpA3AQtQI2AiwgAygCNCADQThqEDlBAEgEQCADKAKYAUEIaiADKAI0EBggA0F/NgIsCyADIAMoAjQQuwIiADoAIyAAQRh0QRh1QQBIBEAgAygCmAFBCGogAygCNBAYIANBfzYCLAsgAygCNBAcIAMoAixBAEgEQCADQX82ApwBDAELIAMgAygCmAEoAgAQNSIENwN4IARCAFMEQCADKAKYAUEIaiADKAKYASgCABAYIANBfzYCnAEMAQsgAygCmAEoAgAgAykDiAEQqAFBAEgEQCADKAKYAUEIaiADKAKYASgCABAYIANBfzYCnAEMAQsgAykDOELkAINC5ABSBEAgAygCmAFBCGpBFEEAEBUgA0F/NgKcAQwBCyADKAKQASgCAEEgcUUEQAJAIAMpAzhCEINCAFIEQCADKAKQASADKAJgNgIUDAELIAMoApABQRRqEAEaCwsgAygCkAEgAy8BaDYCECADKAKQASADKAJkNgIYIAMoApABIAMpA1A3AyggAygCkAEgAykDeCADKQOAAX03AyAgAygCkAEgAygCkAEvAQxB+f8DcSADLQAjQQF0cjsBDCADKAKQASADKAIkQYAIcUEARxCKAyADIAMoApgBIAMoApABIAMoAiQQXiIANgIsIABBAEgEQCADQX82ApwBDAELIAMoAiggAygCLEcEQCADKAKYAUEIakEUQQAQFSADQX82ApwBDAELIAMoApgBKAIAIAMpA3gQqAFBAEgEQCADKAKYAUEIaiADKAKYASgCABAYIANBfzYCnAEMAQsgA0EANgKcAQsgAygCnAEhACADQaABaiQAIAALrwIBAX8jAEEgayICIAA2AhwgAiABNgIYIAJBADYCFCACQgA3AwACQCACKAIcLQAoQQFxRQRAIAIoAhwoAhggAigCHCgCFEYNAQsgAkEBNgIUCyACQgA3AwgDQCACKQMIIAIoAhwpAzBUBEACQAJAIAIoAhwoAkAgAikDCKdBBHRqKAIIDQAgAigCHCgCQCACKQMIp0EEdGotAAxBAXENACACKAIcKAJAIAIpAwinQQR0aigCBEUNASACKAIcKAJAIAIpAwinQQR0aigCBCgCAEUNAQsgAkEBNgIUCyACKAIcKAJAIAIpAwinQQR0ai0ADEEBcUUEQCACIAIpAwBCAXw3AwALIAIgAikDCEIBfDcDCAwBCwsgAigCGARAIAIoAhggAikDADcDAAsgAigCFAuMEAMCfwF+AXwjAEHgAGsiASQAIAEgADYCWAJAIAEoAlhFBEAgAUF/NgJcDAELIAEgASgCWCABQUBrELkCNgIkIAEpA0BQBEACQCABKAJYKAIEQQhxRQRAIAEoAiRFDQELIAEoAlgoAgAQhAJBAEgEQAJAAn8jAEEQayICIAEoAlgoAgA2AgwjAEEQayIAIAIoAgxBDGo2AgwgACgCDCgCAEEWRgsEQCMAQRBrIgIgASgC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NgI8IAEoAlAgASgCODYCQCABKAJYIAEoAjAQ1gIgASABKAJYKAIMNgJIIAEgASgCWCgCEDYCQCABIAEoAlgoAgA2AkwgASABKAJYKAIENgJEIAEgASgCUCgCPDYCPCABIAEoAlAoAkA2AjggASgCUCgCBEG//gBGBEAgASgCUEF/NgLINwsMDQsgASgCUEEANgLINwNAAkAgASABKAJQKAJQIAEoAjxBASABKAJQKAJYdEEBa3FBAnRqKAEANgEgIAEtACEgASgCOE0NACABKAJERQ0NIAEgASgCREF/ajYCRCABIAEoAkwiAEEBajYCTCABIAEoAjwgAC0AACABKAI4dGo2AjwgASABKAI4QQhqNgI4DAELCwJAIAEtACBFDQAgAS0AIEHwAXENACABIAEoASA2ARgDQAJAIAEgASgCUCgCUCABLwEaIAEoAjxBASABLQAZIAEtABhqdEEBa3EgAS0AGXZqQQJ0aigBADYBICABLQAZIAEtACFqIAEoAjhNDQAgASgCREUNDiABIAEoAkRBf2o2AkQgASABKAJMIgBBAWo2AkwgASABKAI8IAAtAAAgASgCOHRqNgI8IAEgASgCOEEIajYCOAwBCwsgASABKAI8IAEtABl2NgI8IAEgASgCOCABLQAZazYCOCABKAJQIgAgAS0AGSAAKALIN2o2Asg3CyABIAEoAjwgAS0AIXY2AjwgASABKAI4IAEtACFrNgI4IAEoAlAiACABLQAhIAAoAsg3ajYCyDcgASgCUCABLwEiNgJEIAEtACBFBEAgASgCUEHN/gA2AgQMDQsgAS0AIEEgcQRAIAEoAlBBfzYCyDcgASgCUEG//gA2AgQMDQsgAS0AIEHAAHEEQCABKAJYQYfxADYCGCABKAJQQdH+ADYCBAwNCyABKAJQIAEtACBBD3E2AkwgASgCUEHJ/gA2AgQLIAEoAlAoAkwEQANAIAEoAjggASgCUCgCTEkEQCABKAJERQ0NIAEgASgCREF/ajYCRCABIAEoAkwiAEEBajYCTCABIAEoAjwgAC0AACABKAI4dGo2AjwgASABKAI4QQhqNgI4DAELCyABKAJQIgAgACgCRCABKAI8QQEgASgCUCgCTHRBAWtxajYCRCABIAEoAjwgASgCUCgCTHY2AjwgASABKAI4IAEoAlAoAkxrNgI4IAEoAlAiACAB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KAIMSQRAIAIgAigCUCIAQQFqNgJQIAIgAigCLCAALQAAIAIoAih0ajYCLCACIAIoAihBCGo2AiggAigCKCACKAIMSQRAIAIgAigCUCIAQQFqNgJQIAIgAigCLCAALQAAIAIoAih0ajYCLCACIAIoAihBCGo2AigLCyACIAIoAgQgAigCLEEBIAIoAgx0QQFrcWo2AgQgAiACKAIsIAIoAgx2NgIsIAIgAigCKCACKAIMazYCKCACIAIoAkggAigCRGs2AgwCQCACKAIEIAIoAgxLBEAgAiACKAIEIAIoAgxrNgIMIAIoAgwgAigCOEsEQCACKAJUKALENwRAIAIoAlxBsO0ANgIYIAIoAlRB0f4ANgIEDAoLCyACIAIoAjA2AgACQCACKAI0RQRAIAIgAigCACACKAI8IAIoAgxrajYCACACKAIMIAIoAghJBEAgAiACKAIIIAIoAgxrNgIIA0AgAiACKAIAIgBBAWo2AgAgAC0AACEAIAIgAigCSCIBQQFqNgJIIAEgADoAACACIAIoAgxBf2oiADYCDCAADQALIAIgAigCSCACKAIEazYCAAsMAQsCQCACKAI0IAIoAgxJBEAgAiACKAIAIAIoAjwgAigCNGogAigCDGtqNgIAIAIgAigCDCACKAI0azYCDCACKAIMIAIoAghJBEAgAiACKAIIIAIoAgxrNgIIA0AgAiACKAIAIgBBAWo2AgAgAC0AACEAIAIgAigCSCIBQQFqNgJIIAEgADoAACACIAIoAgxBf2oiADYCDCAADQALIAIgAigCMDYCACACKAI0IAIoAghJBEAgAiACKAI0NgIMIAIgAigCCCACKAIMazYCCANAIAIgAigCACIAQQFqNgIAIAAtAAAhACACIAIoAkgiAUEBajYCSCABIAA6AAAgAiACKAIMQX9qIgA2AgwgAA0ACyACIAIoAkggAigCBGs2AgALCwwBCyACIAIoAgAgAigCNCACKAIMa2o2AgAgAigCDCACKAIISQRAIAIgAigCCCACKAIMazYCCANAIAIgAigCACIAQQFqNgIAIAAtAAAhACACIAIoAkgiAUEBajYCSCABIAA6AAAgAiACKAIMQX9qIgA2AgwgAA0ACyACIAIoAkggAigCBGs2AgALCwsDQCACKAIIQQJNRQRAIAIgAigCACIAQQFqNgIAIAAtAAAhACACIAIoAkgiAUEBajYCSCABIAA6AAAgAiACKAIAIgBBAWo2AgAgAC0AACEAIAIgAigCSCIBQQFqNgJIIAEgADoAACACIAIoAgAiAEEBajYCACAALQAAIQAgAiACKAJIIgFBAWo2AkggASAAOgAAIAIgAigCCEEDazYCCAwBCwsMAQsgAiACKAJIIAIoAgRrNgIAA0AgAiACKAIAIgBBAWo2AgAgAC0AACEAIAIgAigCSCIBQQFqNgJIIAEgADoAACACIAIoAgAiAEEBajYCACAALQAAIQAgAiACKAJIIgFBAWo2AkggASAAOgAAIAIgAigCACIAQQFqNgIAIAAtAAAhACACIAIoAkgiAUEBajYCSCABIAA6AAAgAiACKAIIQQNrNgIIIAIoAghBAksNAAsLIAIoAggEQCACIAIoAgAiAEEBajYCACAALQAAIQAgAiACKAJIIgFBAWo2AkggASAAOgAAIAIoAghBAUsEQCACIAIoAgAiAEEBajYCACAALQAAIQAgAiACKAJIIgFBAWo2AkggASAAOgAACwsMAgsgAigCDEHAAHFFBEAgAkEQaiACKAIgIAIvARIgAigCLEEBIAIoAgx0QQFrcWpBAnRqKAEANgEADAELCyACKAJcQc7tADYCGCACKAJUQdH+ADYCBAwECwwCCyACKAIMQcAAcUUEQCACQRBqIAIoAiQgAi8BEiACKAIsQQEgAigCDHRBAWtxakECdGooAQA2AQAMAQsLIAIoAgxBIHEEQCACKAJUQb/+ADYCBAwCCyACKAJcQeTtADYCGCACKAJUQdH+ADYCBAwBC0EAIQAgAigCUCACKAJMSQR/IAIoAkggAigCQEkFQQALQQFxDQELCyACIAIoAihBA3Y2AgggAiACKAJQIAIoAghrNgJQIAIgAigCKCACKAIIQQN0azYCKCACIAIoAixBASACKAIodEEBa3E2AiwgAigCXCACKAJQNgIAIAIoAlwgAigCSDYCDCACKAJcAn8gAigCUCACKAJMSQRAIAIoAkwgAigCUGtBBWoMAQtBBSACKAJQIAIoAkxraws2AgQgAigCXAJ/IAIoAkggAigCQEkEQCACKAJAIAIoAkhrQYECagwBC0GBAiACKAJIIAIoAkBraws2AhAgAigCVCACKAIsNgI8IAIoAlQgAigCKDYCQAvBEAECfyMAQSBrIgIk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LSAAKAKgLUEBdGogAi8BEDsBACACLQATIQEgAigCKCIAKAKYLSEDIAAgACgCoC0iAEEBajYCoC0gACADaiABOgAAIAIgAi8BEEF/ajsBECACKAIoIAItABNBgNkAai0AAEECdGpBmAlqIgAgAC8BAEEBajsBACACKAIoQYgTagJ/IAIvARBBgAJIBEAgAi8BEC0AgFUMAQsgAi8BEEEHdUGAAmotAIBVC0ECdGoiACAALwEAQQFqOwEAIAIgAigCKCgCoC0gAigCKCgCnC1BAWtGNgIgIAIoAigiACAAKAJ0IAIoAigoAmBrNgJ0IAIoAigiACACKAIoKAJgIAAoAmxqNgJsIAIoAihBADYCYAwBCyACIAIoAigiACgCOCAAKAJsai0AADoADyACKAIoIgAoAqQtIAAoAqAtQQF0akEAOwEAIAItAA8hASACKAIoIgAoApgtIQMgACAAKAKgLSIAQQFqNgKgLSAAIANqIAE6AAAgAigCKCACLQAPQQJ0aiIAIAAvAZQBQQFqOwGUASACIAIoAigoAqAtIAIoAigoApwtQQFrRjYCICACKAIoIgAgACgCdEF/ajYCdCACKAIoIgAgACgCbEEBajYCbAsgAigCIARAIAIoAigCfyACKAIoKAJcQQBOBEAgAigCKCgCOCACKAIoKAJcagwBC0EACyACKAIoKAJsIAIoAigoAlxrQQAQKSACKAIoIAIoAigoAmw2AlwgAigCKCgCABAdIAIoAigoAgAoAhBFBEAgAkEANgIsDAQLCwwBCwsgAigCKEEANgK0LSACKAIkQQRGBEAgAigCKAJ/IAIoAigoAlxBAE4EQCACKAIoKAI4IAIoAigoAlxqDAELQQALIAIoAigoAmwgAigCKCgCXGtBARApIAIoAiggAigCKCgCbDYCXCACKAIoKAIAEB0gAigCKCgCACgCEEUEQCACQQI2AiwMAgsgAkEDNgIsDAELIAIoAigoAqAtBEAgAigCKAJ/IAIoAigoAlxBAE4EQCACKAIoKAI4IAIoAigoAlxqDAELQQALIAIoAigoAmwgAigCKCgCXGtBABApIAIoAiggAigCKCgCbDYCXCACKAIoKAIAEB0gAigCKCgCACgCEEUEQCACQQA2AiwMAgsLIAJBATYCLAsgAigCLCEAIAJBMGokACAAC8AFAQJ/IwBBIGsiAiQAIAIgADYCGCACIAE2AhQCQANAAkAgAigCGCgCdEUEQCACKAIYEFYgAigCGCgCdEUEQCACKAIURQRAIAJBADYCHAwFCwwCCwsgAigCGEEANgJgIAIgAigCGCIAKAI4IAAoAmxqLQAAOgAPIAIoAhgiACgCpC0gACgCoC1BAXRqQQA7AQAgAi0ADyEBIAIoAhgiACgCmC0hAyAAIAAoAqAtIgBBAWo2AqAtIAAgA2ogAToAACACKAIYIAItAA9BAnRqIgAgAC8BlAFBAWo7AZQBIAIgAigCGCgCoC0gAigCGCgCnC1BAWtGNgIQIAIoAhgiACAAKAJ0QX9qNgJ0IAIoAhgiACAAKAJsQQFqNgJsIAIoAhAEQCACKAIYAn8gAigCGCgCXEEATgRAIAIoAhgoAjggAigCGCgCXGoMAQtBAAsgAigCGCgCbCACKAIYKAJca0EAECkgAigCGCACKAIYKAJsNgJcIAIoAhgoAgAQHSACKAIYKAIAKAIQRQRAIAJBADYCHAwECwsMAQsLIAIoAhhBADYCtC0gAigCFEEERgRAIAIoAhgCfyACKAIYKAJcQQBOBEAgAigCGCgCOCACKAIYKAJcagwBC0EACyACKAIYKAJsIAIoAhgoAlxrQQEQKSACKAIYIAIoAhgoAmw2AlwgAigCGCgCABAdIAIoAhgoAgAoAhBFBEAgAkECNgIcDAILIAJBAzYCHAwBCyACKAIYKAKgLQRAIAIoAhgCfyACKAIYKAJcQQBOBEAgAigCGCgCOCACKAIYKAJcagwBC0EACyACKAIYKAJsIAIoAhgoAlxrQQAQKSACKAIYIAIoAhgoAmw2AlwgAigCGCgCABAdIAIoAhgoAgAoAhBFBEAgAkEANgIcDAILCyACQQE2AhwLIAIoAhwhACACQSBqJAAgAAuuJQEDfyMAQUBqIgIkACACIAA2AjggAiABNgI0AkACQAJAIAIoAjgQdA0AIAIoAjRBBUoNACACKAI0QQBODQELIAJBfjYCPAwBCyACIAIoAjgoAhw2AiwCQAJAIAIoAjgoAgxFDQAgAigCOCgCBARAIAIoAjgoAgBFDQELIAIoAiwoAgRBmgVHDQEgAigCNEEERg0BCyACKAI4QeDUACgCADYCGCACQX42AjwMAQsgAigCOCgCEEUEQCACKAI4QezUACgCADYCGCACQXs2AjwMAQsgAiACKAIsKAIoNgIwIAIoAiwgAigCNDYCKAJAIAIoAiwoAhQEQCACKAI4EB0gAigCOCgCEEUEQCACKAIsQX82AiggAkEANgI8DAMLDAELAkAgAigCOCgCBA0AIAIoAjRBAXRBCUEAIAIoAjRBBEobayACKAIwQQF0QQlBACACKAIwQQRKG2tKDQAgAigCNEEERg0AIAIoAjhB7NQAKAIANgIYIAJBezYCPAwCCwsCQCACKAIsKAIEQZoFRw0AIAIoAjgoAgRFDQAgAigCOEHs1AAoAgA2AhggAkF7NgI8DAELIAIoAiwoAgRBKkYEQCACIAIoAiwoAjBBBHRBiH9qQQh0NgIoAkACQCACKAIsKAKIAUECSARAIAIoAiwoAoQBQQJODQELIAJBADYCJAwBCwJAIAIoAiwoAoQBQQZIBEAgAkEBNgIkDAELAkAgAigCLCgChAFBBkYEQCACQQI2AiQMAQsgAkEDNgIkCwsLIAIgAigCKCACKAIkQQZ0cjYCKCACKAIsKAJsBEAgAiACKAIoQSByNgIoCyACIAIoAihBHyACKAIoQR9wa2o2AiggAigCLCACKAIoEEwgAigCLCgCbARAIAIoAiwgAigCOCgCMEEQdhBMIAIoAiwgAigCOCgCMEH//wNxEEwLQQBBAEEAED4hACACKAI4IAA2AjAgAigCLEHxADYCBCACKAI4EB0gAigCLCgCFARAIAIoAixBfzYCKCACQQA2AjwMAgsLIAIoAiwoAgRBOUYEQEEAQQBBABAbIQAgAigCOCAANgIwIAIoAiwoAgghASACKAIsIgMoAhQhACADIABBAWo2AhQgACABakEfOgAAIAIoAiwoAgghASACKAIsIgMoAhQhACADIABBAWo2AhQgACABakGLAToAACACKAIsKAIIIQEgAigCLCIDKAIUIQAgAyAAQQFqNgIUIAAgAWpBCDoAAAJAIAIoAiwoAhxFBEAgAigCLCgCCCEBIAIoAiwiAygCFCEAIAMgAEEBajYCFCAAIAFqQQA6AAAgAigCLCgCCCEBIAIoAiwiAygCFCEAIAMgAEEBajYCFCAAIAFqQQA6AAAgAigCLCgCCCEBIAIoAiwiAygCFCEAIAMgAEEBajYCFCAAIAFqQQA6AAAgAigCLCgCCCEBIAIoAiwiAygCFCEAIAMgAEEBajYCFCAAIAFqQQA6AAAgAigCLCgCCCEBIAIoAiwiAygCFCEAIAMgAEEBajYCFCAAIAFqQQA6AAACf0ECIAIoAiwoAoQBQQlGDQAaQQEhAEEEQQAgAigCLCgCiAFBAkgEfyACKAIsKAKEAUECSAVBAQtBAXEbCyEAIAIoAiwoAgghAyACKAIsIgQoAhQhASAEIAFBAWo2AhQgASADaiAAOgAAIAIoAiwoAgghASACKAIsIgMoAhQhACADIABBAWo2AhQgACABakEDOgAAIAIoAixB8QA2AgQgAigCOBAdIAIoAiwoAhQEQCACKAIsQX82AiggAkEANgI8DAQLDAELIAIoAiwoAhwoAgBFRUECQQAgAigCLCgCHCgCLBtqQQRBACACKAIsKAIcKAIQG2pBCEEAIAIoAiwoAhwoAhwbakEQQQAgAigCLCgCHCgCJBtqIQEgAigCLCgCCCEDIAIoAiwiBCgCFCEAIAQgAEEBajYCFCAAIANqIAE6AAAgAigCLCgCHCgCBEH/AXEhASACKAIsKAIIIQMgAigCLCIEKAIUIQAgBCAAQQFqNgIUIAAgA2ogAToAACACKAIsKAIcKAIEQQh2Qf8BcSEBIAIoAiwoAgghAyACKAIsIgQoAhQhACAEIABBAWo2AhQgACADaiABOgAAIAIoAiwoAhwoAgRBEHZB/wFxIQEgAigCLCgCCCEDIAIoAiwiBCgCFCEAIAQgAEEBajYCFCAAIANqIAE6AAAgAigCLCgCHCgCBEEYdiEBIAIoAiwoAgghAyACKAIsIgQoAhQhACAEIABBAWo2AhQgACADaiABOgAAAn9BAiACKAIsKAKEAUEJRg0AGkEBIQBBBEEAIAIoAiwoAogBQQJIBH8gAigCLCgChAFBAkgFQQELQQFxGwshACACKAIsKAIIIQMgAigCLCIEKAIUIQEgBCABQQFqNgIUIAEgA2ogADoAACACKAIsKAIcKAIMQf8BcSEBIAIoAiwoAgghAyACKAIsIgQoAhQhACAEIABBAWo2AhQgACADaiABOgAAIAIoAiwoAhwoAhAEQCACKAIsKAIcKAIUQf8BcSEBIAIoAiwoAgghAyACKAIsIgQoAhQhACAEIABBAWo2AhQgACADaiABOgAAIAIoAiwoAhwoAhRBCHZB/wFxIQEgAigCLCgCCCEDIAIoAiwiBCgCFCEAIAQgAEEBajYCFCAAIANqIAE6AAALIAIoAiwoAhwoAiwEQCACKAI4KAIwIAIoAiwoAgggAigCLCgCFBAbIQAgAigCOCAANgIwCyACKAIsQQA2AiAgAigCLEHFADYCBAsLIAIoAiwoAgRBxQBGBEAgAigCLCgCHCgCEARAIAIgAigCLCgCFDYCICACIAIoAiwoAhwoAhRB//8DcSACKAIsKAIgazYCHANAIAIoAiwoAhQgAigCHGogAigCLCgCDEsEQCACIAIoAiwoAgwgAigCLCgCFGs2AhggAigCLCgCCCACKAIsKAIUaiACKAIsKAIcKAIQIAIoAiwoAiBqIAIoAhgQGhogAigCLCACKAIsKAIMNgIUAkAgAigCLCgCHCgCLEUNACACKAIsKAIUIAIoAiBNDQAgAigCOCgCMCACKAIsKAIIIAIoAiBqIAIoAiwoAhQgAigCIGsQGyEAIAIoAjggADYCMAsgAigCLCIAIAIoAhggACgCIGo2AiAgAigCOBAdIAIoAiwoAhQEQCACKAIsQX82AiggAkEANgI8DAUFIAJBADYCICACIAIoAhwgAigCGGs2AhwMAgsACwsgAigCLCgCCCACKAIsKAIUaiACKAIsKAIcKAIQIAIoAiwoAiBqIAIoAhwQGhogAigCLCIAIAIoAhwgACgCFGo2AhQCQCACKAIsKAIcKAIsRQ0AIAIoAiwoAhQgAigCIE0NACACKAI4KAIwIAIoAiwoAgggAigCIGogAigCLCgCFCACKAIgaxAbIQAgAigCOCAANgIwCyACKAIsQQA2AiALIAIoAixByQA2AgQLIAIoAiwoAgRByQBGBEAgAigCLCgCHCgCHARAIAIgAigCLCgCFDYCFANAIAIoAiwoAhQgAigCLCgCDEYEQAJAIAIoAiwoAhwoAixFDQAgAigCLCgCFCACKAIUTQ0AIAIoAjgoAjAgAigCLCgCCCACKAIUaiACKAIsKAIUIAIoAhRrEBshACACKAI4IAA2AjALIAIoAjgQHSACKAIsKAIUBEAgAigCLEF/NgIoIAJBADYCPAwFCyACQQA2AhQLIAIoAiwoAhwoAhwhASACKAIsIgMoAiAhACADIABBAWo2AiAgAiAAIAFqLQAANgIQIAIoAhAhASACKAIsKAIIIQMgAigCLCIEKAIUIQAgBCAAQQFqNgIUIAAgA2ogAToAACACKAIQDQALAkAgAigCLCgCHCgCLEUNACACKAIsKAIUIAIoAhRNDQAgAigCOCgCMCACKAIsKAIIIAIoAhRqIAIoAiwoAhQgAigCFGsQGyEAIAIoAjggADYCMAsgAigCLEEANgIgCyACKAIsQdsANgIECyACKAIsKAIEQdsARgRAIAIoAiwoAhwoAiQEQCACIAIoAiwoAhQ2AgwDQCACKAIsKAIUIAIoAiwoAgxGBEACQCACKAIsKAIcKAIsRQ0AIAIoAiwoAhQgAigCDE0NACACKAI4KAIwIAIoAiwoAgggAigCDGogAigCLCgCFCACKAIMaxAbIQAgAigCOCAANgIwCyACKAI4EB0gAigCLCgCFARAIAIoAixBfzYCKCACQQA2AjwMBQsgAkEANgIMCyACKAIsKAIcKAIkIQEgAigCLCIDKAIgIQAgAyAAQQFqNgIgIAIgACABai0AADYCCCACKAIIIQEgAigCLCgCCCEDIAIoAiwiBCgCFCEAIAQgAEEBajYCFCAAIANqIAE6AAAgAigCCA0ACwJAIAIoAiwoAhwoAixFDQAgAigCLCgCFCACKAIMTQ0AIAIoAjgoAjAgAigCLCgCCCACKAIMaiACKAIsKAIUIAIoAgxrEBshACACKAI4IAA2AjALCyACKAIsQecANgIECyACKAIsKAIEQecARgRAIAIoAiwoAhwoAiwEQCACKAIsKAIUQQJqIAIoAiwoAgxLBEAgAigCOBAdIAIoAiwoAhQEQCACKAIsQX82AiggAkEANgI8DAQLCyACKAI4KAIwQf8BcSEBIAIoAiwoAgghAyACKAIsIgQoAhQhACAEIABBAWo2AhQgACADaiABOgAAIAIoAjgoAjBBCHZB/wFxIQEgAigCLCgCCCEDIAIoAiwiBCgCFCEAIAQgAEEBajYCFCAAIANqIAE6AABBAEEAQQAQGyEAIAIoAjggADYCMAsgAigCLEHxADYCBCACKAI4EB0gAigCLCgCFARAIAIoAixBfzYCKCACQQA2AjwMAgsLAkACQCACKAI4KAIEDQAgAigCLCgCdA0AIAIoAjRFDQEgAigCLCgCBEGaBUYNAQsgAgJ/IAIoAiwoAoQBRQRAIAIoAiwgAigCNBCxAQwBCwJ/IAIoAiwoAogBQQJGBEAgAigCLCACKAI0ENoCDAELAn8gAigCLCgCiAFBA0YEQCACKAIsIAIoAjQQ2QIMAQsgAigCLCACKAI0IAIoAiwoAoQBQQxsQbDqAGooAggRAgALCws2AgQCQCACKAIEQQJHBEAgAigCBEEDRw0BCyACKAIsQZoFNgIECwJAIAIoAgQEQCACKAIEQQJHDQELIAIoAjgoAhBFBEAgAigCLEF/NgIoCyACQQA2AjwMAgsgAigCBEEBRgRAAkAgAigCNEEBRgRAIAIoAiwQ6AIMAQsgAigCNEEFRwRAIAIoAixBAEEAQQAQVyACKAI0QQNGBEAgAigCLCgCRCACKAIsKAJMQQFrQQF0akEAOwEAIAIoAiwoAkRBACACKAIsKAJMQQFrQQF0EDMgAigCLCgCdEUEQCACKAIsQQA2AmwgAigCLEEANgJcIAIoAixBADYCtC0LCwsLIAIoAjgQHSACKAI4KAIQRQRAIAIoAixBfzYCKCACQQA2AjwMAwsLCyACKAI0QQRHBEAgAkEANgI8DAELIAIoAiwoAhhBAEwEQCACQQE2AjwMAQsCQCACKAIsKAIYQQJGBEAgAigCOCgCMEH/AXEhASACKAIsKAIIIQMgAigCLCIEKAIUIQAgBCAAQQFqNgIUIAAgA2ogAToAACACKAI4KAIwQQh2Qf8BcSEBIAIoAiwoAgghAyACKAIsIgQoAhQhACAEIABBAWo2AhQgACADaiABOgAAIAIoAjgoAjBBEHZB/wFxIQEgAigCLCgCCCEDIAIoAiwiBCgCFCEAIAQgAEEBajYCFCAAIANqIAE6AAAgAigCOCgCMEEYdiEBIAIoAiwoAgghAyACKAIsIgQoAhQhACAEIABBAWo2AhQgACADaiABOgAAIAIoAjgoAghB/wFxIQEgAigCLCgCCCEDIAIoAiwiBCgCFCEAIAQgAEEBajYCFCAAIANqIAE6AAAgAigCOCgCCEEIdkH/AXEhASACKAIsKAIIIQMgAigCLCIEKAIUIQAgBCAAQQFqNgIUIAAgA2ogAToAACACKAI4KAIIQRB2Qf8BcSEBIAIoAiwoAgghAyACKAIsIgQoAhQhACAEIABBAWo2AhQgACADaiABOgAAIAIoAjgoAghBGHYhASACKAIsKAIIIQMgAigCLCIEKAIUIQAgBCAAQQFqNgIUIAAgA2ogAToAAAwBCyACKAIsIAIoAjgoAjBBEHYQTCACKAIsIAIoAjgoAjBB//8DcRBMCyACKAI4EB0gAigCLCgCGEEASgRAIAIoAixBACACKAIsKAIYazYCGAsgAiACKAIsKAIURTYCPAsgAigCPCEAIAJBQGskACAAC44CAQF/IwBBIGsiASAANgIcIAEgASgCHCgCLDYCDCABIAEoAhwoAkw2AhggASABKAIcKAJEIAEoAhhBAXRqNgIQA0AgASABKAIQQX5qIgA2AhAgASAALwEANgIUIAEoAhACfyABKAIUIAEoAgxPBEAgASgCFCABKAIMawwBC0EACzsBACABIAEoAhhBf2oiADYCGCAADQALIAEgASgCDDYCGCABIAEoAhwoAkAgASgCGEEBdGo2AhADQCABIAEoAhBBfmoiADYCECABIAAvAQA2AhQgASgCEAJ/IAEoAhQgASgCDE8EQCABKAIUIAEoAgxrDAELQQALOwEAIAEgASgCGEF/aiIANgIYIAANAAsLRQBBoJwBQgA3AwBBmJwBQgA3AwBBkJwBQgA3AwBBiJwBQgA3AwBBgJwBQgA3AwBB+JsBQgA3AwBB8JsBQgA3AwBB8JsBC6gCAQF/IwBBEGsiASQAIAEgADYCDCABKAIMIAEoAgwoAixBAXQ2AjwgASgCDCgCRCABKAIMKAJMQQFrQQF0akEAOwEAIAEoAgwoAkRBACABKAIMKAJMQQFrQQF0EDMgASgCDCABKAIMKAKEAUEMbEGw6gBqLwECNgKAASABKAIMIAEoAgwoAoQBQQxsQbDqAGovAQA2AowBIAEoAgwgASgCDCgChAFBDGxBsOoAai8BBDYCkAEgASgCDCABKAIMKAKEAUEMbEGw6gBqLwEGNgJ8IAEoAgxBADYCbCABKAIMQQA2AlwgASgCDEEANgJ0IAEoAgxBADYCtC0gASgCDEECNgJ4IAEoAgxBAjYCYCABKAIMQQA2AmggASgCDEEANgJIIAFBEGokAAubAgEBfyMAQRBrIgEkACABIAA2AggCQCABKAIIEHQEQCABQX42AgwMAQsgASgCCEEANgIUIAEoAghBADYCCCABKAIIQQA2AhggASgCCEECNgIsIAEgASgCCCgCHDYCBCABKAIEQQA2AhQgASgCBCABKAIEKAIINgIQIAEoAgQoAhhBAEgEQCABKAIEQQAgASgCBCgCGGs2AhgLIAEoAgQCf0E5IAEoAgQoAhhBAkYNABpBKkHxACABKAIEKAIYGws2AgQCfyABKAIEKAIYQQJGBEBBAEEAQQAQGwwBC0EAQQBBABA+CyEAIAEoAgggADYCMCABKAIEQQA2AiggASgCBBDqAiABQQA2AgwLIAEoAgwhACABQRBqJAAgAAtFAQF/IwBBEGsiASQAIAEgADYCDCABIAEoAgwQ3wI2AgggASgCCEUEQCABKAIMKAIcEN4CCyABKAIIIQAgAUEQaiQAIAAL4AgBAX8jAEEwayICJAAgAiAANgIoIAIgATYCJCACQQg2AiAgAkFxNgIcIAJBCTYCGCACQQA2AhQgAkGQgwE2AhAgAkE4NgIMIAJBATYCBAJAAkACQCACKAIQRQ0AIAIoAhAsAABBqOoALAAARw0AIAIoAgxBOEYNAQsgAkF6NgIsDAELIAIoAihFBEAgAkF+NgIsDAELIAIoAihBADYCGCACKAIoKAIgRQRAIAIoAihBBTYCICACKAIoQQA2AigLIAIoAigoAiRFBEAgAigCKEEGNgIkCyACKAIkQX9GBEAgAkEGNgIkCwJAIAIoAhxBAEgEQCACQQA2AgQgAkEAIAIoAhxrNgIcDAELIAIoAhxBD0oEQCACQQI2AgQgAiACKAIcQRBrNgIcCwsCQAJAIAIoAhhBAUgNACACKAIYQQlKDQAgAigCIEEIRw0AIAIoAhxBCEgNACACKAIcQQ9KDQAgAigCJEEASA0AIAIoAiRBCUoNACACKAIUQQBIDQAgAigCFEEESg0AIAIoAhxBCEcNASACKAIEQQFGDQELIAJBfjYCLAwBCyACKAIcQQhGBEAgAkEJNgIcCyACIAIoAigoAihBAUHELSACKAIoKAIgEQEANgIIIAIoAghFBEAgAkF8NgIsDAELIAIoAiggAigCCDYCHCACKAIIIAIoAig2AgAgAigCCEEqNgIEIAIoAgggAigCBDYCGCACKAIIQQA2AhwgAigCCCACKAIcNgIwIAIoAghBASACKAIIKAIwdDYCLCACKAIIIAIoAggoAixBAWs2AjQgAigCCCACKAIYQQdqNgJQIAIoAghBASACKAIIKAJQdDYCTCACKAIIIAIoAggoAkxBAWs2AlQgAigCCCACKAIIKAJQQQJqQQNuNgJYIAIoAigoAiggAigCCCgCLEECIAIoAigoAiARAQAhACACKAIIIAA2AjggAigCKCgCKCACKAIIKAIsQQIgAigCKCgCIBEBACEAIAIoAgggADYCQCACKAIoKAIoIAIoAggoAkxBAiACKAIoKAIgEQEAIQAgAigCCCAANgJEIAIoAghBADYCwC0gAigCCEEBIAIoAhhBBmp0NgKcLSACIAIoAigoAiggAigCCCgCnC1BBCACKAIoKAIgEQEANgIAIAIoAgggAigCADYCCCACKAIIIAIoAggoApwtQQJ0NgIMAkACQCACKAIIKAI4RQ0AIAIoAggoAkBFDQAgAigCCCgCREUNACACKAIIKAIIDQELIAIoAghBmgU2AgQgAigCKEHo1AAoAgA2AhggAigCKBCyARogAkF8NgIsDAELIAIoAgggAigCACACKAIIKAKcLUEBdkEBdGo2AqQtIAIoAgggAigCCCgCCCACKAIIKAKcLUEDbGo2ApgtIAIoAgggAigCJDYChAEgAigCCCACKAIUNgKIASACKAIIIAIoAiA6ACQgAiACKAIoEOACNgIsCyACKAIsIQAgAkEwaiQAIAALbAEBfyMAQRBrIgIgADYCDCACIAE2AgggAkEANgIEA0AgAiACKAIEIAIoAgxBAXFyNgIEIAIgAigCDEEBdjYCDCACIAIoAgRBAXQ2AgQgAiACKAIIQX9qIgA2AgggAEEASg0ACyACKAIEQQF2C5UCAQF/IwBBQGoiAyQAIAMgADYCPCADIAE2AjggAyACNgI0IANBADYCDCADQQE2AggDQCADKAIIQQ9KRQRAIAMgAygCDCADKAI0IAMoAghBAWtBAXRqLwEAakEBdDYCDCADQRBqIAMoAghBAXRqIAMoAgw7AQAgAyADKAIIQQFqNgIIDAELCyADQQA2AgQDQCADKAIEIAMoAjhMBEAgAyADKAI8IAMoAgRBAnRqLwECNgIAIAMoAgAEQCADQRBqIAMoAgBBAXRqIgEvAQAhACABIABBAWo7AQAgAEH//wNxIAMoAgAQ4gIhACADKAI8IAMoAgRBAnRqIAA7AQALIAMgAygCBEEBajYCBAwBCwsgA0FAayQAC4gIAQF/IwBBQGoiAiAANgI8IAIgATYCOCACIAIoAjgoAgA2AjQgAiACKAI4KAIENgIwIAIgAigCOCgCCCgCADYCLCACIAIoAjgoAggoAgQ2AiggAiACKAI4KAIIKAIINgIkIAIgAigCOCgCCCgCEDYCICACQQA2AgQgAkEANgIQA0AgAigCEEEPSkUEQCACKAI8QbwWaiACKAIQQQF0akEAOwEAIAIgAigCEEEBajYCEAwBCwsgAigCNCACKAI8QdwWaiACKAI8KALUKEECdGooAgBBAnRqQQA7AQIgAiACKAI8KALUKEEBajYCHANAIAIoAhxBvQRIBEAgAiACKAI8QdwWaiACKAIcQQJ0aigCADYCGCACIAIoAjQgAigCNCACKAIYQQJ0ai8BAkECdGovAQJBAWo2AhAgAigCECACKAIgSgRAIAIgAigCIDYCECACIAIoAgRBAWo2AgQLIAIoAjQgAigCGEECdGogAigCEDsBAiACKAIYIAIoAjBMBEAgAigCPCACKAIQQQF0akG8FmoiACAALwEAQQFqOwEAIAJBADYCDCACKAIYIAIoAiROBEAgAiACKAIoIAIoAhggAigCJGtBAnRqKAIANgIMCyACIAIoAjQgAigCGEECdGovAQA7AQogAigCPCIAIAAoAqgtIAIvAQogAigCECACKAIMamxqNgKoLSACKAIsBEAgAigCPCIAIAAoAqwtIAIvAQogAigCLCACKAIYQQJ0ai8BAiACKAIMamxqNgKsLQsLIAIgAigCHEEBajYCHAwBCwsCQCACKAIERQ0AA0AgAiACKAIgQQFrNgIQA0AgAigCPEG8FmogAigCEEEBdGovAQBFBEAgAiACKAIQQX9qNgIQDAELCyACKAI8IAIoAhBBAXRqQbwWaiIAIAAvAQBBf2o7AQAgAigCPCACKAIQQQF0akG+FmoiACAALwEAQQJqOwEAIAIoAjwgAigCIEEBdGpBvBZqIgAgAC8BAEF/ajsBACACIAIoAgRBAms2AgQgAigCBEEASg0ACyACIAIoAiA2AhADQCACKAIQRQ0BIAIgAigCPEG8FmogAigCEEEBdGovAQA2AhgDQCACKAIYBEAgAigCPEHcFmohACACIAIoAhxBf2oiATYCHCACIAFBAnQgAGooAgA2AhQgAigCFCACKAIwSg0BIAIoAjQgAigCFEECdGovAQIgAigCEEcEQCACKAI8IgAgACgCqC0gAigCNCACKAIUQQJ0ai8BACACKAIQIAIoAjQgAigCFEECdGovAQJrbGo2AqgtIAIoAjQgAigCFEECdGogAigCEDsBAgsgAiACKAIYQX9qNgIYDAELCyACIAIo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KAIMIAEoAgxB/BRqNgKwFiABKAIMQajbADYCuBYgASgCDEEAOwG4LSABKAIMQQA2ArwtIAEoAgwQuQEgAUEQaiQAC9cNAQF/IwBBIGsiAyAANgIYIAMgATYCFCADIAI2AhAgAyADKAIYQRB2NgIMIAMgAygCGEH//wNxNgIYAkAgAygCEEEBRgRAIAMgAygCFC0AACADKAIYajYCGCADKAIYQfH/A08EQCADIAMoAhhB8f8DazYCGAsgAyADKAIYIAMoAgxqNgIMIAMoAgxB8f8DTwRAIAMgAygCDEHx/wNrNgIMCyADIAMoAhggAygCDEEQdHI2AhwMAQsgAygCFEUEQCADQQE2AhwMAQsgAygCEEEQSQRAA0AgAyADKAIQIgBBf2o2AhAgAARAIAMgAygCFCIAQQFqNgIUIAMgAC0AACADKAIYajYCGCADIAMoAhggAygCDGo2AgwMAQsLIAMoAhhB8f8DTwRAIAMgAygCGEHx/wNrNgIYCyADIAMoAgxB8f8DcDYCDCADIAMoAhggAygCDEEQdHI2AhwMAQsDQCADKAIQQbArSUUEQCADIAMoAhBBsCtrNgIQIANB2wI2AggDQCADIAMoAhQtAAAgAygCGGo2AhggAyADKAIYIAMoAgxqNgIMIAMgAygCFC0AASADKAIYajYCGCADIAMoAhggAygCDGo2AgwgAyADKAIULQACIAMoAhhqNgIYIAMgAygCGCADKAIMajYCDCADIAMoAhQtAAMgAygCGGo2AhggAyADKAIYIAMoAgxqNgIMIAMgAygCFC0ABCADKAIYajYCGCADIAMoAhggAygCDGo2AgwgAyADKAIULQAFIAMoAhhqNgIYIAMgAygCGCADKAIMajYCDCADIAMoAhQtAAYgAygCGGo2AhggAyADKAIYIAMoAgxqNgIMIAMgAygCFC0AByADKAIYajYCGCADIAMoAhggAygCDGo2AgwgAyADKAIULQAIIAMoAhhqNgIYIAMgAygCGCADKAIMajYCDCADIAMoAhQtAAkgAygCGGo2AhggAyADKAIYIAMoAgxqNgIMIAMgAygCFC0ACiADKAIYajYCGCADIAMoAhggAygCDGo2AgwgAyADKAIULQALIAMoAhhqNgIYIAMgAygCGCADKAIMajYCDCADIAMoAhQtAAwgAygCGGo2AhggAyADKAIYIAMoAgxqNgIMIAMgAygCFC0ADSADKAIYajYCGCADIAMoAhggAygCDGo2AgwgAyADKAIULQAOIAMoAhhqNgIYIAMgAygCGCADKAIMajYCDCADIAMoAhQtAA8gAygCGGo2AhggAyADKAIYIAMoAgxqNgIMIAMgAygCFEEQajYCFCADIAMoAghBf2oiADYCCCAADQALIAMgAygCGEHx/wNwNgIYIAMgAygCDEHx/wNwNgIMDAELCyADKAIQBEADQCADKAIQQRBJRQRAIAMgAygCEEEQazYCECADIAMoAhQtAAAgAygCGGo2AhggAyADKAIYIAMoAgxqNgIMIAMgAygCFC0AASADKAIYajYCGCADIAMoAhggAygCDGo2AgwgAyADKAIULQACIAMoAhhqNgIYIAMgAygCGCADKAIMajYCDCADIAMoAhQtAAMgAygCGGo2AhggAyADKAIYIAMoAgxqNgIMIAMgAygCFC0ABCADKAIYajYCGCADIAMoAhggAygCDGo2AgwgAyADKAIULQAFIAMoAhhqNgIYIAMgAygCGCADKAIMajYCDCADIAMoAhQtAAYgAygCGGo2AhggAyADKAIYIAMoAgxqNgIMIAMgAygCFC0AByADKAIYajYCGCADIAMoAhggAygCDGo2AgwgAyADKAIULQAIIAMoAhhqNgIYIAMgAygCGCADKAIMajYCDCADIAMoAhQtAAkgAygCGGo2AhggAyADKAIYIAMoAgxqNgIMIAMgAygCFC0ACiADKAIYajYCGCADIAMoAhggAygCDGo2AgwgAyADKAIULQALIAMoAhhqNgIYIAMgAygCGCADKAIMajYCDCADIAMoAhQtAAwgAygCGGo2AhggAyADKAIYIAMoAgxqNgIMIAMgAygCFC0ADSADKAIYajYCGCADIAMoAhggAygCDGo2AgwgAyADKAIULQAOIAMoAhhqNgIYIAMgAygCGCADKAIMajYCDCADIAMoAhQtAA8gAygCGGo2AhggAyADKAIYIAMoAgxqNgIMIAMgAygCFEEQajYCFAwBCwsDQCADIAMoAhAiAEF/ajYCECAABEAgAyADKAIUIgBBAWo2AhQgAyAALQAAIAMoAhhqNgIYIAMgAygCGCADKAIMajYCDAwBCwsgAyADKAIYQfH/A3A2AhggAyADKAIMQfH/A3A2AgwLIAMgAygC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KAIEDAELIAMoAgwoAgALQYAEEF4iADYCECAAQQBIBEAgA0J/NwO4AQwDCyADKAIQBEAgA0EBOgAXCyADIAMpAxhCAXw3AxgMAQsLIAMgAygCtAEoAgAQNSICNwMgIAJCAFMEQCADKAK0AUEIaiADKAK0ASgCABAYIANCfzcDuAEMAQsgAyADKQMgIAMpA6ABfTcDmAECQCADKQOgAUL/////D1gEQCADKQOoAUL//wNYDQELIANBAToAFwsgAyADQTBqQuIAECoiADYCLCAARQRAIAMoArQBQQhqQQ5BABAVIANCfzcDuAEMAQsgAy0AF0EBcQRAIAMoAixBttMAQQQQQCADKAIsQiwQLiADKAIsQS0QICADKAIsQS0QICADKAIsQQAQISADKAIsQQAQISADKAIsIAMpA6gBEC4gAygCLCADKQOoARAuIAMoAiwgAykDmAEQLiADKAIsIAMpA6ABEC4gAygCLEG70wBBBBBAIAMoAixBABAhIAMoAiwgAykDoAEgAykDmAF8EC4gAygCLEEBECELIAMoAixBwNMAQQQQQCADKAIsQQAQISADKAIsAn5C//8DIAMpA6gBQv//A1oNABogAykDqAELp0H//wNxECAgAygCLAJ+Qv//AyADKQOoAUL//wNaDQAaIAMpA6gBC6dB//8DcRAgIAMoAiwCf0F/IAMpA5gBQv////8PWg0AGiADKQOYAacLECEgAygCLAJ/QX8gAykDoAFC/////w9aDQAaIAMpA6ABpwsQISADAn8gAygCtAEtAChBAXEEQCADKAK0ASgCJAwBCyADKAK0ASgCIAs2ApQBIAMoAiwCfyADKAKUAQRAIAMoApQBLwEEDAELQQALQf//A3EQIAJ/IwBBEGsiACADKAIsNgIMIAAoAgwtAABBAXFFCwRAIAMoArQBQQhqQRRBABAVIAMoAiwQFyADQn83A7gBDAELIAMoArQBAn8jAEEQayIAIAMoAiw2AgwgACgCDCgCBAsCfiMAQRBrIgAgAygCLDYCDAJ+IAAoAgwtAABBAXEEQCAAKAIMKQMQDAELQgALCxA2QQBIBEAgAygCLBAXIANCfzcDuAEMAQsgAygCLBAXIAMoApQBBEAgAygCtAEgAygClAEoAgAgAygClAEvAQStEDZBAEgEQCADQn83A7gBDAILCyADIAMpA5gBNwO4AQsgAykDuAEhAiADQcABaiQAIAILBwAgACgCIAsIAEEBQTgQewsDAAELC/KNAScAQYAIC5QFTm8gZXJyb3IATXVsdGktZGlzayB6aXAgYXJjaGl2ZXMgbm90IHN1cHBvcnRlZABSZW5hbWluZyB0ZW1wb3JhcnkgZmlsZSBmYWlsZWQAQ2xvc2luZyB6aXAgYXJjaGl2ZSBmYWlsZWQAU2VlayBlcnJvcgBSZWFkIGVycm9yAFdyaXRlIGVycm9yAENSQyBlcnJvcgBDb250YWluaW5nIHppcCBhcmNoaXZlIHdhcyBjbG9zZWQATm8gc3VjaCBmaWxlAEZpbGUgYWxyZWFkeSBleGlzdHMAQ2FuJ3Qgb3BlbiBmaWxlAEZhaWx1cmUgdG8gY3JlYXRlIHRlbXBvcmFyeSBmaWxlAFpsaWIgZXJyb3IATWFsbG9jIGZhaWx1cmUARW50cnkgaGFzIGJlZW4gY2hhbmdlZABDb21wcmVzc2lvbiBtZXRob2Qgbm90IHN1cHBvcnRlZABQcmVtYXR1cmUgZW5kIG9mIGZpbGUASW52YWxpZCBhcmd1bWVudABOb3QgYSB6aXAgYXJjaGl2ZQBJbnRlcm5hbCBlcnJvcgBaaXAgYXJjaGl2ZSBpbmNvbnNpc3RlbnQAQ2FuJ3QgcmVtb3ZlIGZpbGUARW50cnkgaGFzIGJlZW4gZGVsZXRlZABFbmNyeXB0aW9uIG1ldGhvZCBub3Qgc3VwcG9ydGVkAFJlYWQtb25seSBhcmNoaXZlAE5vIHBhc3N3b3JkIHByb3ZpZGVkAFdyb25nIHBhc3N3b3JkIHByb3ZpZGVkAE9wZXJhdGlvbiBub3Qgc3VwcG9ydGVkAFJlc291cmNlIHN0aWxsIGluIHVzZQBUZWxsIGVycm9yAENvbXByZXNzZWQgZGF0YSBpbnZhbGlkAEGhDQuAAQQAAAkEAAAvBAAATgQAAGkEAAB0BAAAfwQAAIsEAACVBAAAtwQAAMQEAADYBAAA6AQAAAkFAAAUBQAAIwUAADoFAABbBQAAcQUAAIIFAACUBQAAowUAALwFAADOBQAA5QUAAAUGAAAXBgAALAYAAEQGAABcBgAAcgYAAH0GAAAgAEG4DgsRAQAAAAEAAAABAAAAAQAAAAEAQdwOCwkBAAAAAQAAAAIAQYgPCwEBAEGoDwsB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CAAaAAgAmgAIAFoACADaAAgAOgAIALoACAB6AAgA+gAIAAYACACGAAgARgAIAMYACAAmAAgApgAIAGYACADmAAgAFgAIAJYACABWAAgA1gAIADYACAC2AAgAdgAIAPYACAAOAAgAjgAIAE4ACADOAAgALgAIAK4ACABuAAgA7gAIAB4ACACeAAgAXgAIAN4ACAA+AAgAvgAIAH4ACAD+AAgAAQAIAIEACABBAAgAwQAIACEACAChAAgAYQAIAOEACAARAAgAkQAIAFEACADRAAgAMQAIALEACABxAAgA8QAIAAkACACJAAgASQAIAMkACAApAAgAqQAIAGkACADpAAgAGQAIAJkACABZAAgA2QAIADkACAC5AAgAeQAIAPkACAAFAAgAhQAIAEUACADFAAgAJQAIAKUACABlAAgA5QAIABUACACVAAgAVQAIANUACAA1AAgAtQAIAHUACAD1AAgADQAIAI0ACABNAAgAzQAIAC0ACACtAAgAbQAIAO0ACAAdAAgAnQAIAF0ACADdAAgAPQAIAL0ACAB9AAgA/QAIABMACQATAQkAkwAJAJMBCQBTAAkAUwEJANMACQDTAQkAMwAJADMBCQCzAAkAswEJAHMACQBzAQkA8wAJAPMBCQALAAkACwEJAIsACQCLAQkASwAJAEsBCQDLAAkAywEJACsACQArAQkAqwAJAKsBCQBrAAkAawEJAOsACQDrAQkAGwAJABsBCQCbAAkAmwEJAFsACQBbAQkA2wAJANsBCQA7AAkAOwEJALsACQC7AQkAewAJAHsBCQD7AAkA+wEJAAcACQAHAQkAhwAJAIcBCQBHAAkARwEJAMcACQDHAQkAJwAJACcBCQCnAAkApwEJAGcACQBnAQkA5wAJAOcBCQAXAAkAFwEJAJcACQCXAQkAVwAJAFcBCQDXAAkA1wEJADcACQA3AQkAtwAJALcBCQB3AAkAdwEJAPcACQD3AQkADwAJAA8BCQCPAAkAjwEJAE8ACQBPAQkAzwAJAM8BCQAvAAkALwEJAK8ACQCvAQkAbwAJAG8BCQDvAAkA7wEJAB8ACQAfAQkAnwAJAJ8BCQBfAAkAXwEJAN8ACQDfAQkAPwAJAD8BCQC/AAkAvwEJAH8ACQB/AQkA/wAJAP8BCQAAAAcAQAAHACAABwBgAAcAEAAHAFAABwAwAAcAcAAHAAgA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cnkATm8gc3VjaCBwcm9jZXNzAEZpbGUgZXhpc3RzAFZhbHVlIHRvbyBsYXJnZSBmb3IgZGF0YSB0eXBlAE5vIHNwYWNlIGxlZnQgb24gZGV2aWNlAE91dCBvZiBtZW1vcnkAUmVzb3VyY2UgYnVzeQBJbnRlcnJ1cHRlZCBzeXN0ZW0gY2FsbABSZXNvdXJjZSB0ZW1wb3JhcmlseSB1bmF2YWlsYWJsZQBJbnZhbGlkIHNlZWsAQ3Jvc3MtZGV2aWNlIGxpbmsAUmVhZC1vbmx5IGZpbGUgc3lzdGVtAERpcmVjdG9yeSBub3QgZW1wdHkAQ29ubmVjdGlvbiByZXNldCBieSBwZWVyAE9wZXJhdGlvbiB0aW1lZCBvdXQAQ29ubmVjdGlvbiByZWZ1c2VkAEhvc3QgaXMgZG93bgBIb3N0IGlzIHVucmVhY2hhYmxlAEFkZHJlc3MgaW4gdXNlAEJyb2tlbiBwaXBlAEkvTyBlcnJvcgBObyBzdWNoIGRldmljZSBvciBhZGRyZXNzAEJsb2NrIGRldmljZSByZXF1aXJlZABObyBzdWNoIGRldmljZQBOb3QgYSBkaXJlY3RvcnkASXMgYSBkaXJlY3RvcnkAVGV4dCBmaWxlIGJ1c3kARXhlYyBmb3JtYXQgZXJyb3IASW52YWxpZCBhcmd1bWVudABBcmd1bWVudCBsaXN0IHRvbyBsb25nAFN5bWJvbGljIGxpbmsgbG9vcABGaWxlbmFtZSB0b28gbG9uZwBUb28gbWFueSBvcGVuIGZpbGVzIGluIHN5c3RlbQBObyBmaWxlIGRlc2NyaXB0b3JzIGF2YWlsYWJsZQBCYWQgZmlsZSBkZXNjcmlwdG9yAE5vIGNoaWxkIHByb2Nlc3MAQmFkIGFkZHJlc3MARmlsZSB0b28gbGFyZ2UAVG9vIG1hbnkgbGlua3MATm8gbG9ja3MgYXZhaWxhYmxlAFJlc291cmNlIGRlYWRsb2NrIHdvdWxkIG9jY3VyAFN0YXRlIG5vdCByZWNvdmVyYWJsZQBQcmV2aW91cyBvd25lciBkaWVkAE9wZXJhdGlvbiBjYW5jZWxlZABGdW5jdGlvbiBub3QgaW1wbGVtZW50ZWQATm8gbWVzc2FnZSBvZiBkZXNpcmVkIHR5cGUASWRlbnRpZmllciByZW1vdmVkAERldmljZSBub3QgYSBzdHJlYW0ATm8gZGF0YSBhdmFpbGFibGUARGV2aWNlIHRpbWVvdXQAT3V0IG9mIHN0cmVhbXMgcmVzb3VyY2VzAExpbmsgaGFzIGJlZW4gc2V2ZXJlZABQcm90b2NvbCBlcnJvcgBCYWQgbWVzc2FnZQBGaWxlIGRlc2NyaXB0b3IgaW4gYmFkIHN0YXRlAE5vdCBhIHNvY2tldABEZXN0aW5hdGlvbiBhZGRyZXNzIHJlcXVpcmVkAE1lc3NhZ2UgdG9vIGxhcmdlAFByb3RvY29sIHdyb25nIHR5cGUgZm9yIHNvY2tldABQcm90b2NvbCBub3QgYXZhaWxhYmxlAFByb3RvY29sIG5vdCBzdXBwb3J0ZWQAU29ja2V0IHR5cGUgbm90IHN1cHBvcnRlZABOb3Qgc3VwcG9ydGVkAFByb3RvY29sIGZhbWlseSBub3Qgc3VwcG9ydGVkAEFkZHJlc3MgZmFtaWx5IG5vdCBzdXBwb3J0ZWQgYnkgcHJvdG9jb2wAQWRkcmVzcyBub3QgYXZhaWxhYmxlAE5ldHdvcmsgaXMgZG93bgBOZXR3b3JrIHVucmVhY2hhYmxlAENvbm5lY3Rpb24gcmVzZXQgYnkgbmV0d29yawBDb25uZWN0aW9uIGFib3J0ZWQATm8gYnVmZmVyIHNwYWNlIGF2YWlsYWJsZQBTb2NrZXQgaXMgY29ubmVjdGVkAFNvY2tldCBub3QgY29ubmVjdGVkAENhbm5vdCBzZW5kIGFmdGVyIHNvY2tldCBzaHV0ZG93bgBPcGVyYXRpb24gYWxyZWFkeSBpbiBwcm9ncmVzcwBPcGVyYXRpb24gaW4gcHJvZ3Jlc3MAU3RhbGUgZmlsZSBoYW5kbGUAUmVtb3RlIEkvTyBlcnJvcgBRdW90YSBleGNlZWRlZABObyBtZWRpdW0gZm91bmQAV3JvbmcgbWVkaXVtIHR5cGUATm8gZXJyb3IgaW5mb3JtYXRpb24AAFVua25vd24gZXJyb3IgJWQAJXMlcyVzAAA6IAAvcHJvYy9zZWxmL2ZkLwAvZGV2L3VyYW5kb20AcndhACVzLlhYWFhYWAByK2IAcmIAUEsFBgBBkJgBC04KAAAACwAAAAwAAAANAAAADgAAAA8AAAAQAAAAEQAAABIAAAALAAAADAAAAA0AAAAOAAAADwAAABAAAAARAAAAAQAAAAgAAAAQTAAAMEwAQZCaAQsCgFAAQciaAQsJHwAAAGRNAAADAEHkmgELjAEt9FFYz4yxwEb2tcspMQPHBFtwMLRd/SB4f4ua2FkpUGhIiaunVgNs/7fNiD/Ud7QrpaNw8brkqPxBg/3Zb+GKei8tdJYHHw0JXgN2LHD3QKUsp29XQaiqdN+gWGQDSsfEPFOur18YBBWx420ohqsMpL9D8OlQgTlXFlI3/////////////////////w==";function Ae(e){for(;e.length>0;){var t=e.shift();if("function"!=typeof t){var r=t.func;"number"==typeof r?void 0===t.arg?f.get(r)():f.get(r)(t.arg):r(void 0===t.arg?null:t.arg)}else t(o)}}function ne(){var e=function(){var e=new Error;if(!e.stack){try{throw new Error}catch(t){e=t}if(!e.stack)return"(no stack trace available)"}return e.stack.toString()}();return o.extraStackTrace&&(e+="\n"+o.extraStackTrace()),e.replace(/\b_Z[\w\d_]+/g,(function(e){return e==e?e:e+" ["+e+"]"}))}function oe(e,t){var r=new Date(1e3*M[e>>2]);M[t>>2]=r.getUTCSeconds(),M[t+4>>2]=r.getUTCMinutes(),M[t+8>>2]=r.getUTCHours(),M[t+12>>2]=r.getUTCDate(),M[t+16>>2]=r.getUTCMonth(),M[t+20>>2]=r.getUTCFullYear()-1900,M[t+24>>2]=r.getUTCDay(),M[t+36>>2]=0,M[t+32>>2]=0;var A=Date.UTC(r.getUTCFullYear(),0,1,0,0,0,0),n=(r.getTime()-A)/864e5|0;return M[t+28>>2]=n,oe.GMTString||(oe.GMTString=S("GMT")),M[t+40>>2]=oe.GMTString,t}Z(re)||($=re,re=o.locateFile?o.locateFile($,u):u+$),U.push({func:function(){Se()}});var ie={splitPath:function(e){return/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/.exec(e).slice(1)},normalizeArray:function(e,t){for(var r=0,A=e.length-1;A>=0;A--){var n=e[A];"."===n?e.splice(A,1):".."===n?(e.splice(A,1),r++):r&&(e.splice(A,1),r--)}if(t)for(;r;r--)e.unshift("..");return e},normalize:function(e){var t="/"===e.charAt(0),r="/"===e.substr(-1);return(e=ie.normalizeArray(e.split("/").filter((function(e){return!!e})),!t).join("/"))||t||(e="."),e&&r&&(e+="/"),(t?"/":"")+e},dirname:function(e){var t=ie.splitPath(e),r=t[0],A=t[1];return r||A?(A&&(A=A.substr(0,A.length-1)),r+A):"."},basename:function(e){if("/"===e)return"/";var t=(e=(e=ie.normalize(e)).replace(/\/$/,"")).lastIndexOf("/");return-1===t?e:e.substr(t+1)},extname:function(e){return ie.splitPath(e)[3]},join:function(){var e=Array.prototype.slice.call(arguments,0);return ie.normalize(e.join("/"))},join2:function(e,t){return ie.normalize(e+"/"+t)}};function se(e){return M[ke()>>2]=e,e}var ae={resolve:function(){for(var e="",t=!1,r=arguments.length-1;r>=-1&&!t;r--){var A=r>=0?arguments[r]:pe.cwd();if("string"!=typeof A)throw new TypeError("Arguments to path.resolve must be strings");if(!A)return"";e=A+"/"+e,t="/"===A.charAt(0)}return(t?"/":"")+(e=ie.normalizeArray(e.split("/").filter((function(e){return!!e})),!t).join("/"))||"."},relative:function(e,t){function r(e){for(var t=0;t=0&&""===e[r];r--);return t>r?[]:e.slice(t,r-t+1)}e=ae.resolve(e).substr(1),t=ae.resolve(t).substr(1);for(var A=r(e.split("/")),n=r(t.split("/")),o=Math.min(A.length,n.length),i=o,s=0;s0?r.slice(0,A).toString("utf-8"):null))return null;e.input=we(t,!0)}return e.input.shift()},put_char:function(e,t){null===t||10===t?(h(w(e.output,0)),e.output=[]):0!=t&&e.output.push(t)},flush:function(e){e.output&&e.output.length>0&&(h(w(e.output,0)),e.output=[])}},default_tty1_ops:{put_char:function(e,t){null===t||10===t?(p(w(e.output,0)),e.output=[]):0!=t&&e.output.push(t)},flush:function(e){e.output&&e.output.length>0&&(p(w(e.output,0)),e.output=[])}}},ge={ops_table:null,mount:function(e){return ge.createNode(null,"/",16895,0)},createNode:function(e,t,r,A){if(pe.isBlkdev(r)||pe.isFIFO(r))throw new pe.ErrnoError(63);ge.ops_table||(ge.ops_table={dir:{node:{getattr:ge.node_ops.getattr,setattr:ge.node_ops.setattr,lookup:ge.node_ops.lookup,mknod:ge.node_ops.mknod,rename:ge.node_ops.rename,unlink:ge.node_ops.unlink,rmdir:ge.node_ops.rmdir,readdir:ge.node_ops.readdir,symlink:ge.node_ops.symlink},stream:{llseek:ge.stream_ops.llseek}},file:{node:{getattr:ge.node_ops.getattr,setattr:ge.node_ops.setattr},stream:{llseek:ge.stream_ops.llseek,read:ge.stream_ops.read,write:ge.stream_ops.write,allocate:ge.stream_ops.allocate,mmap:ge.stream_ops.mmap,msync:ge.stream_ops.msync}},link:{node:{getattr:ge.node_ops.getattr,setattr:ge.node_ops.setattr,readlink:ge.node_ops.readlink},stream:{}},chrdev:{node:{getattr:ge.node_ops.getattr,setattr:ge.node_ops.setattr},stream:pe.chrdev_stream_ops}});var n=pe.createNode(e,t,r,A);return pe.isDir(n.mode)?(n.node_ops=ge.ops_table.dir.node,n.stream_ops=ge.ops_table.dir.stream,n.contents={}):pe.isFile(n.mode)?(n.node_ops=ge.ops_table.file.node,n.stream_ops=ge.ops_table.file.stream,n.usedBytes=0,n.contents=null):pe.isLink(n.mode)?(n.node_ops=ge.ops_table.link.node,n.stream_ops=ge.ops_table.link.stream):pe.isChrdev(n.mode)&&(n.node_ops=ge.ops_table.chrdev.node,n.stream_ops=ge.ops_table.chrdev.stream),n.timestamp=Date.now(),e&&(e.contents[t]=n),n},getFileDataAsRegularArray:function(e){if(e.contents&&e.contents.subarray){for(var t=[],r=0;r=t)){t=Math.max(t,r*(r<1048576?2:1.125)>>>0),0!=r&&(t=Math.max(t,256));var A=e.contents;e.contents=new Uint8Array(t),e.usedBytes>0&&e.contents.set(A.subarray(0,e.usedBytes),0)}},resizeFileStorage:function(e,t){if(e.usedBytes!=t){if(0==t)return e.contents=null,void(e.usedBytes=0);if(!e.contents||e.contents.subarray){var r=e.contents;return e.contents=new Uint8Array(t),r&&e.contents.set(r.subarray(0,Math.min(t,e.usedBytes))),void(e.usedBytes=t)}if(e.contents||(e.contents=[]),e.contents.length>t)e.contents.length=t;else for(;e.contents.length=e.node.usedBytes)return 0;var i=Math.min(e.node.usedBytes-n,A);if(i>8&&o.subarray)t.set(o.subarray(n,n+i),r);else for(var s=0;s0||A+r>2)}catch(e){if(!e.code)throw e;throw new pe.ErrnoError(ue.convertNodeCode(e))}return t.mode},realPath:function(e){for(var t=[];e.parent!==e;)t.push(e.name),e=e.parent;return t.push(e.mount.opts.root),t.reverse(),ie.join.apply(null,t)},flagsForNode:function(e){e&=-2097153,e&=-2049,e&=-32769,e&=-524289;var t=0;for(var r in ue.flagsForNodeMap)e&r&&(t|=ue.flagsForNodeMap[r],e^=r);if(e)throw new pe.ErrnoError(28);return t},node_ops:{getattr:function(e){var t,r=ue.realPath(e);try{t=Ie.lstatSync(r)}catch(e){if(!e.code)throw e;throw new pe.ErrnoError(ue.convertNodeCode(e))}return ue.isWindows&&!t.blksize&&(t.blksize=4096),ue.isWindows&&!t.blocks&&(t.blocks=(t.size+t.blksize-1)/t.blksize|0),{dev:t.dev,ino:t.ino,mode:t.mode,nlink:t.nlink,uid:t.uid,gid:t.gid,rdev:t.rdev,size:t.size,atime:t.atime,mtime:t.mtime,ctime:t.ctime,blksize:t.blksize,blocks:t.blocks}},setattr:function(e,t){var r=ue.realPath(e);try{if(void 0!==t.mode&&(Ie.chmodSync(r,t.mode),e.mode=t.mode),void 0!==t.timestamp){var A=new Date(t.timestamp);Ie.utimesSync(r,A,A)}void 0!==t.size&&Ie.truncateSync(r,t.size)}catch(e){if(!e.code)throw e;throw new pe.ErrnoError(ue.convertNodeCode(e))}},lookup:function(e,t){var r=ie.join2(ue.realPath(e),t),A=ue.getMode(r);return ue.createNode(e,t,A)},mknod:function(e,t,r,A){var n=ue.createNode(e,t,r,A),o=ue.realPath(n);try{pe.isDir(n.mode)?Ie.mkdirSync(o,n.mode):Ie.writeFileSync(o,"",{mode:n.mode})}catch(e){if(!e.code)throw e;throw new pe.ErrnoError(ue.convertNodeCode(e))}return n},rename:function(e,t,r){var A=ue.realPath(e),n=ie.join2(ue.realPath(t),r);try{Ie.renameSync(A,n)}catch(e){if(!e.code)throw e;throw new pe.ErrnoError(ue.convertNodeCode(e))}e.name=r},unlink:function(e,t){var r=ie.join2(ue.realPath(e),t);try{Ie.unlinkSync(r)}catch(e){if(!e.code)throw e;throw new pe.ErrnoError(ue.convertNodeCode(e))}},rmdir:function(e,t){var r=ie.join2(ue.realPath(e),t);try{Ie.rmdirSync(r)}catch(e){if(!e.code)throw e;throw new pe.ErrnoError(ue.convertNodeCode(e))}},readdir:function(e){var t=ue.realPath(e);try{return Ie.readdirSync(t)}catch(e){if(!e.code)throw e;throw new pe.ErrnoError(ue.convertNodeCode(e))}},symlink:function(e,t,r){var A=ie.join2(ue.realPath(e),t);try{Ie.symlinkSync(r,A)}catch(e){if(!e.code)throw e;throw new pe.ErrnoError(ue.convertNodeCode(e))}},readlink:function(e){var t=ue.realPath(e);try{return t=Ie.readlinkSync(t),t=Ee.relative(Ee.resolve(e.mount.opts.root),t)}catch(e){if(!e.code)throw e;throw new pe.ErrnoError(ue.convertNodeCode(e))}}},stream_ops:{open:function(e){var t=ue.realPath(e.node);try{pe.isFile(e.node.mode)&&(e.nfd=Ie.openSync(t,ue.flagsForNode(e.flags)))}catch(e){if(!e.code)throw e;throw new pe.ErrnoError(ue.convertNodeCode(e))}},close:function(e){try{pe.isFile(e.node.mode)&&e.nfd&&Ie.closeSync(e.nfd)}catch(e){if(!e.code)throw e;throw new pe.ErrnoError(ue.convertNodeCode(e))}},read:function(e,t,r,A,n){if(0===A)return 0;try{return Ie.readSync(e.nfd,ue.bufferFrom(t.buffer),r,A,n)}catch(e){throw new pe.ErrnoError(ue.convertNodeCode(e))}},write:function(e,t,r,A,n){try{return Ie.writeSync(e.nfd,ue.bufferFrom(t.buffer),r,A,n)}catch(e){throw new pe.ErrnoError(ue.convertNodeCode(e))}},llseek:function(e,t,r){var A=t;if(1===r)A+=e.position;else if(2===r&&pe.isFile(e.node.mode))try{A+=Ie.fstatSync(e.nfd).size}catch(e){throw new pe.ErrnoError(ue.convertNodeCode(e))}if(A<0)throw new pe.ErrnoError(28);return A},mmap:function(e,t,r,A,n,o){if(E(0===t),!pe.isFile(e.node.mode))throw new pe.ErrnoError(43);var i=pe.mmapAlloc(r);return ue.stream_ops.read(e,N,i,r,A),{ptr:i,allocated:!0}},msync:function(e,t,r,A,n){if(!pe.isFile(e.node.mode))throw new pe.ErrnoError(43);if(2&n)return 0;ue.stream_ops.write(e,t,0,A,r,!1);return 0}}},he={lookupPath:function(e){return{path:e,node:{mode:ue.getMode(e)}}},createStandardStreams:function(){pe.streams[0]={fd:0,nfd:0,position:0,path:"",flags:0,tty:!0,seekable:!1};for(var e=1;e<3;e++)pe.streams[e]={fd:e,nfd:e,position:0,path:"",flags:577,tty:!0,seekable:!1}},cwd:function(){return process.cwd()},chdir:function(){process.chdir.apply(void 0,arguments)},mknod:function(e,t){pe.isDir(e)?Ie.mkdirSync(e,t):Ie.writeFileSync(e,"",{mode:t})},mkdir:function(){Ie.mkdirSync.apply(void 0,arguments)},symlink:function(){Ie.symlinkSync.apply(void 0,arguments)},rename:function(){Ie.renameSync.apply(void 0,arguments)},rmdir:function(){Ie.rmdirSync.apply(void 0,arguments)},readdir:function(){Ie.readdirSync.apply(void 0,arguments)},unlink:function(){Ie.unlinkSync.apply(void 0,arguments)},readlink:function(){return Ie.readlinkSync.apply(void 0,arguments)},stat:function(){return Ie.statSync.apply(void 0,arguments)},lstat:function(){return Ie.lstatSync.apply(void 0,arguments)},chmod:function(){Ie.chmodSync.apply(void 0,arguments)},fchmod:function(){Ie.fchmodSync.apply(void 0,arguments)},chown:function(){Ie.chownSync.apply(void 0,arguments)},fchown:function(){Ie.fchownSync.apply(void 0,arguments)},truncate:function(){Ie.truncateSync.apply(void 0,arguments)},ftruncate:function(){Ie.ftruncateSync.apply(void 0,arguments)},utime:function(){Ie.utimesSync.apply(void 0,arguments)},open:function(e,t,r,A){"string"==typeof t&&(t=ye.modeStringToFlags(t));var n=Ie.openSync(e,ue.flagsForNode(t),r),o=null!=A?A:pe.nextfd(n),i={fd:o,nfd:n,position:0,path:e,flags:t,seekable:!0};return pe.streams[o]=i,i},close:function(e){e.stream_ops||Ie.closeSync(e.nfd),pe.closeStream(e.fd)},llseek:function(e,t,r){if(e.stream_ops)return ye.llseek(e,t,r);var A=t;if(1===r)A+=e.position;else if(2===r)A+=Ie.fstatSync(e.nfd).size;else if(0!==r)throw new pe.ErrnoError(le.EINVAL);if(A<0)throw new pe.ErrnoError(le.EINVAL);return e.position=A,A},read:function(e,t,r,A,n){if(e.stream_ops)return ye.read(e,t,r,A,n);var o=void 0!==n;!o&&e.seekable&&(n=e.position);var i=Ie.readSync(e.nfd,ue.bufferFrom(t.buffer),r,A,n);return o||(e.position+=i),i},write:function(e,t,r,A,n){if(e.stream_ops)return ye.write(e,t,r,A,n);1024&e.flags&&pe.llseek(e,0,2);var o=void 0!==n;!o&&e.seekable&&(n=e.position);var i=Ie.writeSync(e.nfd,ue.bufferFrom(t.buffer),r,A,n);return o||(e.position+=i),i},allocate:function(){throw new pe.ErrnoError(le.EOPNOTSUPP)},mmap:function(){throw new pe.ErrnoError(le.ENODEV)},msync:function(){return 0},munmap:function(){return 0},ioctl:function(){throw new pe.ErrnoError(le.ENOTTY)}},pe={root:null,mounts:[],devices:{},streams:[],nextInode:1,nameTable:null,currentPath:"/",initialized:!1,ignorePermissions:!0,trackingDelegate:{},tracking:{openFlags:{READ:1,WRITE:2}},ErrnoError:null,genericErrors:{},filesystems:null,syncFSRequests:0,handleFSError:function(e){if(!(e instanceof pe.ErrnoError))throw e+" : "+ne();return se(e.errno)},lookupPath:function(e,t){if(t=t||{},!(e=ae.resolve(pe.cwd(),e)))return{path:"",node:null};var r={follow_mount:!0,recurse_count:0};for(var A in r)void 0===t[A]&&(t[A]=r[A]);if(t.recurse_count>8)throw new pe.ErrnoError(32);for(var n=ie.normalizeArray(e.split("/").filter((function(e){return!!e})),!1),o=pe.root,i="/",s=0;s40)throw new pe.ErrnoError(32)}}return{path:i,node:o}},getPath:function(e){for(var t;;){if(pe.isRoot(e)){var r=e.mount.mountpoint;return t?"/"!==r[r.length-1]?r+"/"+t:r+t:r}t=t?e.name+"/"+t:e.name,e=e.parent}},hashName:function(e,t){for(var r=0,A=0;A>>0)%pe.nameTable.length},hashAddNode:function(e){var t=pe.hashName(e.parent.id,e.name);e.name_next=pe.nameTable[t],pe.nameTable[t]=e},hashRemoveNode:function(e){var t=pe.hashName(e.parent.id,e.name);if(pe.nameTable[t]===e)pe.nameTable[t]=e.name_next;else for(var r=pe.nameTable[t];r;){if(r.name_next===e){r.name_next=e.name_next;break}r=r.name_next}},lookupNode:function(e,t){var r=pe.mayLookup(e);if(r)throw new pe.ErrnoError(r,e);for(var A=pe.hashName(e.id,t),n=pe.nameTable[A];n;n=n.name_next){var o=n.name;if(n.parent.id===e.id&&o===t)return n}return pe.lookup(e,t)},createNode:function(e,t,r,A){var n=new pe.FSNode(e,t,r,A);return pe.hashAddNode(n),n},destroyNode:function(e){pe.hashRemoveNode(e)},isRoot:function(e){return e===e.parent},isMountpoint:function(e){return!!e.mounted},isFile:function(e){return 32768==(61440&e)},isDir:function(e){return 16384==(61440&e)},isLink:function(e){return 40960==(61440&e)},isChrdev:function(e){return 8192==(61440&e)},isBlkdev:function(e){return 24576==(61440&e)},isFIFO:function(e){return 4096==(61440&e)},isSocket:function(e){return 49152==(49152&e)},flagModes:{r:0,rs:1052672,"r+":2,w:577,wx:705,xw:705,"w+":578,"wx+":706,"xw+":706,a:1089,ax:1217,xa:1217,"a+":1090,"ax+":1218,"xa+":1218},modeStringToFlags:function(e){var t=pe.flagModes[e];if(void 0===t)throw new Error("Unknown file open mode: "+e);return t},flagsToPermissionString:function(e){var t=["r","w","rw"][3&e];return 512&e&&(t+="w"),t},nodePermissions:function(e,t){return pe.ignorePermissions||(-1===t.indexOf("r")||292&e.mode)&&(-1===t.indexOf("w")||146&e.mode)&&(-1===t.indexOf("x")||73&e.mode)?0:2},mayLookup:function(e){var t=pe.nodePermissions(e,"x");return t||(e.node_ops.lookup?0:2)},mayCreate:function(e,t){try{pe.lookupNode(e,t);return 20}catch(e){}return pe.nodePermissions(e,"wx")},mayDelete:function(e,t,r){var A;try{A=pe.lookupNode(e,t)}catch(e){return e.errno}var n=pe.nodePermissions(e,"wx");if(n)return n;if(r){if(!pe.isDir(A.mode))return 54;if(pe.isRoot(A)||pe.getPath(A)===pe.cwd())return 10}else if(pe.isDir(A.mode))return 31;return 0},mayOpen:function(e,t){return e?pe.isLink(e.mode)?32:pe.isDir(e.mode)&&("r"!==pe.flagsToPermissionString(t)||512&t)?31:pe.nodePermissions(e,pe.flagsToPermissionString(t)):44},MAX_OPEN_FDS:4096,nextfd:function(e,t){e=e||0,t=t||pe.MAX_OPEN_FDS;for(var r=e;r<=t;r++)if(!pe.streams[r])return r;throw new pe.ErrnoError(33)},getStream:function(e){return pe.streams[e]},createStream:function(e,t,r){pe.FSStream||(pe.FSStream=function(){},pe.FSStream.prototype={object:{get:function(){return this.node},set:function(e){this.node=e}},isRead:{get:function(){return 1!=(2097155&this.flags)}},isWrite:{get:function(){return 0!=(2097155&this.flags)}},isAppend:{get:function(){return 1024&this.flags}}});var A=new pe.FSStream;for(var n in e)A[n]=e[n];e=A;var o=pe.nextfd(t,r);return e.fd=o,pe.streams[o]=e,e},closeStream:function(e){pe.streams[e]=null},chrdev_stream_ops:{open:function(e){var t=pe.getDevice(e.node.rdev);e.stream_ops=t.stream_ops,e.stream_ops.open&&e.stream_ops.open(e)},llseek:function(){throw new pe.ErrnoError(70)}},major:function(e){return e>>8},minor:function(e){return 255&e},makedev:function(e,t){return e<<8|t},registerDevice:function(e,t){pe.devices[e]={stream_ops:t}},getDevice:function(e){return pe.devices[e]},getMounts:function(e){for(var t=[],r=[e];r.length;){var A=r.pop();t.push(A),r.push.apply(r,A.mounts)}return t},syncfs:function(e,t){"function"==typeof e&&(t=e,e=!1),pe.syncFSRequests++,pe.syncFSRequests>1&&p("warning: "+pe.syncFSRequests+" FS.syncfs operations in flight at once, probably just doing extra work");var r=pe.getMounts(pe.root.mount),A=0;function n(e){return pe.syncFSRequests--,t(e)}function o(e){if(e)return o.errored?void 0:(o.errored=!0,n(e));++A>=r.length&&n(null)}r.forEach((function(t){if(!t.type.syncfs)return o(null);t.type.syncfs(t,e,o)}))},mount:function(e,t,r){var A,n="/"===r,o=!r;if(n&&pe.root)throw new pe.ErrnoError(10);if(!n&&!o){var i=pe.lookupPath(r,{follow_mount:!1});if(r=i.path,A=i.node,pe.isMountpoint(A))throw new pe.ErrnoError(10);if(!pe.isDir(A.mode))throw new pe.ErrnoError(54)}var s={type:e,opts:t,mountpoint:r,mounts:[]},a=e.mount(s);return a.mount=s,s.root=a,n?pe.root=a:A&&(A.mounted=s,A.mount&&A.mount.mounts.push(s)),a},unmount:function(e){var t=pe.lookupPath(e,{follow_mount:!1});if(!pe.isMountpoint(t.node))throw new pe.ErrnoError(28);var r=t.node,A=r.mounted,n=pe.getMounts(A);Object.keys(pe.nameTable).forEach((function(e){for(var t=pe.nameTable[e];t;){var r=t.name_next;-1!==n.indexOf(t.mount)&&pe.destroyNode(t),t=r}})),r.mounted=null;var o=r.mount.mounts.indexOf(A);r.mount.mounts.splice(o,1)},lookup:function(e,t){return e.node_ops.lookup(e,t)},mknod:function(e,t,r){var A=pe.lookupPath(e,{parent:!0}).node,n=ie.basename(e);if(!n||"."===n||".."===n)throw new pe.ErrnoError(28);var o=pe.mayCreate(A,n);if(o)throw new pe.ErrnoError(o);if(!A.node_ops.mknod)throw new pe.ErrnoError(63);return A.node_ops.mknod(A,n,t,r)},create:function(e,t){return t=void 0!==t?t:438,t&=4095,t|=32768,pe.mknod(e,t,0)},mkdir:function(e,t){return t=void 0!==t?t:511,t&=1023,t|=16384,pe.mknod(e,t,0)},mkdirTree:function(e,t){for(var r=e.split("/"),A="",n=0;nthis.length-1||e<0)){var t=e%this.chunkSize,r=e/this.chunkSize|0;return this.getter(r)[t]}},o.prototype.setDataGetter=function(e){this.getter=e},o.prototype.cacheLength=function(){var e=new XMLHttpRequest;if(e.open("HEAD",r,!1),e.send(null),!(e.status>=200&&e.status<300||304===e.status))throw new Error("Couldn't load "+r+". Status: "+e.status);var t,A=Number(e.getResponseHeader("Content-length")),n=(t=e.getResponseHeader("Accept-Ranges"))&&"bytes"===t,o=(t=e.getResponseHeader("Content-Encoding"))&&"gzip"===t,i=1048576;n||(i=A);var s=this;s.setDataGetter((function(e){var t=e*i,n=(e+1)*i-1;if(n=Math.min(n,A-1),void 0===s.chunks[e]&&(s.chunks[e]=function(e,t){if(e>t)throw new Error("invalid range ("+e+", "+t+") or no bytes requested!");if(t>A-1)throw new Error("only "+A+" bytes available! programmer error!");var n=new XMLHttpRequest;if(n.open("GET",r,!1),A!==i&&n.setRequestHeader("Range","bytes="+e+"-"+t),"undefined"!=typeof Uint8Array&&(n.responseType="arraybuffer"),n.overrideMimeType&&n.overrideMimeType("text/plain; charset=x-user-defined"),n.send(null),!(n.status>=200&&n.status<300||304===n.status))throw new Error("Couldn't load "+r+". Status: "+n.status);return void 0!==n.response?new Uint8Array(n.response||[]):we(n.responseText||"",!0)}(t,n)),void 0===s.chunks[e])throw new Error("doXHR failed!");return s.chunks[e]})),!o&&A||(i=A=1,A=this.getter(0).length,i=A,h("LazyFiles on gzip forces download of the whole file when length is accessed")),this._length=A,this._chunkSize=i,this.lengthKnown=!0},"undefined"!=typeof XMLHttpRequest)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";var i={isDevice:!1,url:r},s=pe.createFile(e,t,i,A,n);i.contents?s.contents=i.contents:i.url&&(s.contents=null,s.url=i.url),Object.defineProperties(s,{usedBytes:{get:function(){return this.contents.length}}});var a={};return Object.keys(s.stream_ops).forEach((function(e){var t=s.stream_ops[e];a[e]=function(){if(!pe.forceLoadFile(s))throw new pe.ErrnoError(29);return t.apply(null,arguments)}})),a.read=function(e,t,r,A,n){if(!pe.forceLoadFile(s))throw new pe.ErrnoError(29);var o=e.node.contents;if(n>=o.length)return 0;var i=Math.min(o.length-n,A);if(o.slice)for(var a=0;a>2]=A.dev,M[r+4>>2]=0,M[r+8>>2]=A.ino,M[r+12>>2]=A.mode,M[r+16>>2]=A.nlink,M[r+20>>2]=A.uid,M[r+24>>2]=A.gid,M[r+28>>2]=A.rdev,M[r+32>>2]=0,te=[A.size>>>0,(ee=A.size,+Y(ee)>=1?ee>0?(0|J(+H(ee/4294967296),4294967295))>>>0:~~+G((ee-+(~~ee>>>0))/4294967296)>>>0:0)],M[r+40>>2]=te[0],M[r+44>>2]=te[1],M[r+48>>2]=4096,M[r+52>>2]=A.blocks,M[r+56>>2]=A.atime.getTime()/1e3|0,M[r+60>>2]=0,M[r+64>>2]=A.mtime.getTime()/1e3|0,M[r+68>>2]=0,M[r+72>>2]=A.ctime.getTime()/1e3|0,M[r+76>>2]=0,te=[A.ino>>>0,(ee=A.ino,+Y(ee)>=1?ee>0?(0|J(+H(ee/4294967296),4294967295))>>>0:~~+G((ee-+(~~ee>>>0))/4294967296)>>>0:0)],M[r+80>>2]=te[0],M[r+84>>2]=te[1],0},doMsync:function(e,t,r,A,n){var o=F.slice(e,e+r);pe.msync(t,o,n,r,A)},doMkdir:function(e,t){return"/"===(e=ie.normalize(e))[e.length-1]&&(e=e.substr(0,e.length-1)),pe.mkdir(e,t,0),0},doMknod:function(e,t,r){switch(61440&t){case 32768:case 8192:case 24576:case 4096:case 49152:break;default:return-28}return pe.mknod(e,t,r),0},doReadlink:function(e,t,r){if(r<=0)return-28;var A=pe.readlink(e),n=Math.min(r,v(A)),o=N[t+n];return b(A,t,r+1),N[t+n]=o,n},doAccess:function(e,t){if(-8&t)return-28;var r;if(!(r=pe.lookupPath(e,{follow:!0}).node))return-44;var A="";return 4&t&&(A+="r"),2&t&&(A+="w"),1&t&&(A+="x"),A&&pe.nodePermissions(r,A)?-2:0},doDup:function(e,t,r){var A=pe.getStream(r);return A&&pe.close(A),pe.open(e,t,0,r,r).fd},doReadv:function(e,t,r,A){for(var n=0,o=0;o>2],s=M[t+(8*o+4)>>2],a=pe.read(e,N,i,s,A);if(a<0)return-1;if(n+=a,a>2],s=M[t+(8*o+4)>>2],a=pe.write(e,N,i,s,A);if(a<0)return-1;n+=a}return n},varargs:void 0,get:function(){return de.varargs+=4,M[de.varargs-4>>2]},getStr:function(e){return Q(e)},getStreamFromFD:function(e){var t=pe.getStream(e);if(!t)throw new pe.ErrnoError(8);return t},get64:function(e,t){return e}};function Ce(e){try{return C.grow(e-k.byteLength+65535>>>16),L(C.buffer),1}catch(e){}}var fe=function(e,t,r,A){e||(e=this),this.parent=e,this.mount=e.mount,this.mounted=null,this.id=pe.nextInode++,this.name=t,this.mode=r,this.node_ops={},this.stream_ops={},this.rdev=A};Object.defineProperties(fe.prototype,{read:{get:function(){return 365==(365&this.mode)},set:function(e){e?this.mode|=365:this.mode&=-366}},write:{get:function(){return 146==(146&this.mode)},set:function(e){e?this.mode|=146:this.mode&=-147}},isFolder:{get:function(){return pe.isDir(this.mode)}},isDevice:{get:function(){return pe.isChrdev(this.mode)}}}),pe.FSNode=fe,pe.staticInit();var Ie=n,Ee=r(85622);ue.staticInit();var Be=function(e){return function(){try{return e.apply(this,arguments)}catch(e){if(!e.code)throw e;throw new pe.ErrnoError(le[e.code])}}},ye=Object.assign({},pe);for(var me in he)pe[me]=Be(he[me]);function we(e,t,r){var A=r>0?r:v(e)+1,n=new Array(A),o=D(e,n,0,n.length);return t&&(n.length=o),n}"function"==typeof atob&&atob;function Qe(e){if(Z(e))return function(e){var t;try{t=Buffer.from(e,"base64")}catch(r){t=new Buffer(e,"base64")}return new Uint8Array(t.buffer,t.byteOffset,t.byteLength)}(e.slice("data:application/octet-stream;base64,".length))}var De,be={m:function(e,t){return oe(e,t)},b:f,r:function(e,t){try{return e=de.getStr(e),pe.chmod(e,t),0}catch(e){return void 0!==pe&&e instanceof pe.ErrnoError||_(e),-e.errno}},g:function(e,t,r){de.varargs=r;try{var A=de.getStreamFromFD(e);switch(t){case 0:return(n=de.get())<0?-28:pe.open(A.path,A.flags,0,n).fd;case 1:case 2:return 0;case 3:return A.flags;case 4:var n=de.get();return A.flags|=n,0;case 12:n=de.get();return K[n+0>>1]=2,0;case 13:case 14:return 0;case 16:case 8:return-28;case 9:return se(28),-1;default:return-28}}catch(e){return void 0!==pe&&e instanceof pe.ErrnoError||_(e),-e.errno}},l:function(e,t){try{var r=de.getStreamFromFD(e);return de.doStat(pe.stat,r.path,t)}catch(e){return void 0!==pe&&e instanceof pe.ErrnoError||_(e),-e.errno}},q:function(e,t,r){de.varargs=r;try{var A=de.getStreamFromFD(e);switch(t){case 21509:case 21505:return A.tty?0:-59;case 21510:case 21511:case 21512:case 21506:case 21507:case 21508:return A.tty?0:-59;case 21519:if(!A.tty)return-59;var n=de.get();return M[n>>2]=0,0;case 21520:return A.tty?-28:-59;case 21531:n=de.get();return pe.ioctl(A,t,n);case 21523:case 21524:return A.tty?0:-59;default:_("bad ioctl syscall "+t)}}catch(e){return void 0!==pe&&e instanceof pe.ErrnoError||_(e),-e.errno}},t:function(e,t,r){de.varargs=r;try{var A=de.getStr(e),n=de.get();return pe.open(A,t,n).fd}catch(e){return void 0!==pe&&e instanceof pe.ErrnoError||_(e),-e.errno}},s:function(e,t,r){try{var A=de.getStreamFromFD(e);return pe.read(A,N,t,r)}catch(e){return void 0!==pe&&e instanceof pe.ErrnoError||_(e),-e.errno}},j:function(e,t){try{return e=de.getStr(e),t=de.getStr(t),pe.rename(e,t),0}catch(e){return void 0!==pe&&e instanceof pe.ErrnoError||_(e),-e.errno}},u:function(e){try{return e=de.getStr(e),pe.rmdir(e),0}catch(e){return void 0!==pe&&e instanceof pe.ErrnoError||_(e),-e.errno}},e:function(e,t){try{return e=de.getStr(e),de.doStat(pe.stat,e,t)}catch(e){return void 0!==pe&&e instanceof pe.ErrnoError||_(e),-e.errno}},i:function(e){try{return e=de.getStr(e),pe.unlink(e),0}catch(e){return void 0!==pe&&e instanceof pe.ErrnoError||_(e),-e.errno}},v:function(e,t,r){F.copyWithin(e,t,t+r)},w:function(e){e>>>=0;var t=F.length;if(e>2147483648)return!1;for(var r,A,n=1;n<=4;n*=2){var o=t*(1+.2/n);if(o=Math.min(o,e+100663296),Ce(Math.min(2147483648,((r=Math.max(16777216,e,o))%(A=65536)>0&&(r+=A-r%A),r))))return!0}return!1},h:function(e){try{var t=de.getStreamFromFD(e);return pe.close(t),0}catch(e){return void 0!==pe&&e instanceof pe.ErrnoError||_(e),e.errno}},k:function(e,t){try{var r=de.getStreamFromFD(e),A=r.tty?2:pe.isDir(r.mode)?3:pe.isLink(r.mode)?7:4;return N[t>>0]=A,0}catch(e){return void 0!==pe&&e instanceof pe.ErrnoError||_(e),e.errno}},p:function(e,t,r,A){try{var n=de.getStreamFromFD(e),o=de.doReadv(n,t,r);return M[A>>2]=o,0}catch(e){return void 0!==pe&&e instanceof pe.ErrnoError||_(e),e.errno}},n:function(e,t,r,A,n){try{var o=de.getStreamFromFD(e),i=4294967296*r+(t>>>0);return i<=-9007199254740992||i>=9007199254740992?-61:(pe.llseek(o,i,A),te=[o.position>>>0,(ee=o.position,+Y(ee)>=1?ee>0?(0|J(+H(ee/4294967296),4294967295))>>>0:~~+G((ee-+(~~ee>>>0))/4294967296)>>>0:0)],M[n>>2]=te[0],M[n+4>>2]=te[1],o.getdents&&0===i&&0===A&&(o.getdents=null),0)}catch(e){return void 0!==pe&&e instanceof pe.ErrnoError||_(e),e.errno}},f:function(e,t,r,A){try{var n=de.getStreamFromFD(e),o=de.doWritev(n,t,r);return M[A>>2]=o,0}catch(e){return void 0!==pe&&e instanceof pe.ErrnoError||_(e),e.errno}},a:C,c:function(e){0|e},d:function(e){var t=Date.now()/1e3|0;return e&&(M[e>>2]=t),t},o:function(e){!function e(){if(!e.called){e.called=!0,M[Ke()>>2]=60*(new Date).getTimezoneOffset();var t=(new Date).getFullYear(),r=new Date(t,0,1),A=new Date(t,6,1);M[Fe()>>2]=Number(r.getTimezoneOffset()!=A.getTimezoneOffset());var n=a(r),o=a(A),i=S(n),s=S(o);A.getTimezoneOffset()>2]=i,M[Ne()+4>>2]=s):(M[Ne()>>2]=s,M[Ne()+4>>2]=i)}function a(e){var t=e.toTimeString().match(/\(([A-Za-z ]+)\)$/);return t?t[1]:"GMT"}}();var t=Date.UTC(M[e+20>>2]+1900,M[e+16>>2],M[e+12>>2],M[e+8>>2],M[e+4>>2],M[e>>2],0),r=new Date(t);M[e+24>>2]=r.getUTCDay();var A=Date.UTC(r.getUTCFullYear(),0,1,0,0,0,0),n=(r.getTime()-A)/864e5|0;return M[e+28>>2]=n,r.getTime()/1e3|0}},ve=function(){var e={a:be};function t(e,t){var r=e.exports;o.asm=r,X()}if(V(),o.instantiateWasm)try{return o.instantiateWasm(e,t)}catch(e){return p("Module.instantiateWasm callback failed with error: "+e),!1}return function(){var r,A,n;try{n=function(){try{if(d)return new Uint8Array(d);var e=Qe(re);if(e)return e;if(a)return a(re);throw"sync fetching of the wasm failed: you can preload it to Module['wasmBinary'] manually, or emcc.py will do that for you when generating HTML (but not JS)"}catch(e){_(e)}}(),A=new WebAssembly.Module(n),r=new WebAssembly.Instance(A,e)}catch(e){var o=e.toString();throw p("failed to compile wasm module: "+o),(o.indexOf("imported Memory")>=0||o.indexOf("memory import")>=0)&&p("Memory size incompatibility issues may be due to changing INITIAL_MEMORY at runtime to something too large. Use ALLOW_MEMORY_GROWTH to allow any size memory (and also make sure not to set INITIAL_MEMORY at runtime to something smaller than it was at compile time)."),e}t(r)}(),o.asm}(),Se=o.___wasm_call_ctors=ve.x,ke=(o._zipstruct_stat=ve.y,o._zipstruct_statS=ve.z,o._zipstruct_stat_name=ve.A,o._zipstruct_stat_index=ve.B,o._zipstruct_stat_size=ve.C,o._zipstruct_stat_mtime=ve.D,o._zipstruct_error=ve.E,o._zipstruct_errorS=ve.F,o._zipstruct_error_code_zip=ve.G,o._zipstruct_stat_comp_size=ve.H,o._zipstruct_stat_comp_method=ve.I,o._zip_close=ve.J,o._zip_delete=ve.K,o._zip_dir_add=ve.L,o._zip_discard=ve.M,o._zip_error_init_with_code=ve.N,o._zip_get_error=ve.O,o._zip_file_get_error=ve.P,o._zip_error_strerror=ve.Q,o._zip_fclose=ve.R,o._zip_file_add=ve.S,o._zip_file_get_external_attributes=ve.T,o._zip_file_set_external_attributes=ve.U,o._zip_file_set_mtime=ve.V,o._zip_fopen=ve.W,o._zip_fopen_index=ve.X,o._zip_fread=ve.Y,o._zip_get_name=ve.Z,o._zip_get_num_entries=ve._,o._zip_name_locate=ve.$,o._zip_open=ve.aa,o._zip_open_from_source=ve.ba,o._zip_set_file_compression=ve.ca,o._zip_source_buffer=ve.da,o._zip_source_buffer_create=ve.ea,o._zip_source_close=ve.fa,o._zip_source_error=ve.ga,o._zip_source_free=ve.ha,o._zip_source_keep=ve.ia,o._zip_source_open=ve.ja,o._zip_source_read=ve.ka,o._zip_source_seek=ve.la,o._zip_source_set_mtime=ve.ma,o._zip_source_tell=ve.na,o._zip_stat=ve.oa,o._zip_stat_index=ve.pa,o._zip_ext_count_symlinks=ve.qa,o.___errno_location=ve.ra),Ne=o.__get_tzname=ve.sa,Fe=o.__get_daylight=ve.ta,Ke=o.__get_timezone=ve.ua,Me=o.stackSave=ve.va,Re=o.stackRestore=ve.wa,xe=o.stackAlloc=ve.xa,Le=o._malloc=ve.ya;o._free=ve.za;function Pe(e){function t(){De||(De=!0,o.calledRun=!0,I||(!0,o.noFSInit||pe.init.initialized||pe.init(),ce.init(),Ae(U),pe.ignorePermissions=!1,Ae(T),o.onRuntimeInitialized&&o.onRuntimeInitialized(),function(){if(o.postRun)for("function"==typeof o.postRun&&(o.postRun=[o.postRun]);o.postRun.length;)e=o.postRun.shift(),j.unshift(e);var e;Ae(j)}()))}e=e||l,q>0||(!function(){if(o.preRun)for("function"==typeof o.preRun&&(o.preRun=[o.preRun]);o.preRun.length;)e=o.preRun.shift(),O.unshift(e);var e;Ae(O)}(),q>0||(o.setStatus?(o.setStatus("Running..."),setTimeout((function(){setTimeout((function(){o.setStatus("")}),1),t()}),1)):t()))}if(o.cwrap=function(e,t,r,A){var n=(r=r||[]).every((function(e){return"number"===e}));return"string"!==t&&n&&!A?B(e):function(){return y(e,t,r,arguments)}},o.getValue=function(e,t,r){switch("*"===(t=t||"i8").charAt(t.length-1)&&(t="i32"),t){case"i1":case"i8":return N[e>>0];case"i16":return K[e>>1];case"i32":case"i64":return M[e>>2];case"float":return R[e>>2];case"double":return x[e>>3];default:_("invalid type for getValue: "+t)}return null},W=function e(){De||Pe(),De||(W=e)},o.run=Pe,o.preInit)for("function"==typeof o.preInit&&(o.preInit=[o.preInit]);o.preInit.length>0;)o.preInit.pop()();Pe()},98261:e=>{"use strict";function t(e,r,A,n){this.message=e,this.expected=r,this.found=A,this.location=n,this.name="SyntaxError","function"==typeof Error.captureStackTrace&&Error.captureStackTrace(this,t)}!function(e,t){function r(){this.constructor=e}r.prototype=t.prototype,e.prototype=new r}(t,Error),t.buildMessage=function(e,t){var r={literal:function(e){return`"${n(e.text)}"`},class:function(e){var t,r="";for(t=0;t0){for(t=1,A=1;tf&&(f=p,I=[]),I.push(e))}function Q(e,r,A){return new t(t.buildMessage(e,r),e,r,A)}function D(){var t,r,A,o;return t=p,(r=b())!==n?(47===e.charCodeAt(p)?(A="/",p++):(A=n,w(s)),A!==n&&(o=b())!==n?(d=t,t=r={from:r,descriptor:o}):(p=t,t=n)):(p=t,t=n),t===n&&(t=p,(r=b())!==n&&(d=t,r=function(e){return{descriptor:e}}(r)),t=r),t}function b(){var t,r,A,o;return t=p,(r=v())!==n?(64===e.charCodeAt(p)?(A="@",p++):(A=n,w(a)),A!==n&&(o=function(){var t,r,A;t=p,r=[],u.test(e.charAt(p))?(A=e.charAt(p),p++):(A=n,w(h));if(A!==n)for(;A!==n;)r.push(A),u.test(e.charAt(p))?(A=e.charAt(p),p++):(A=n,w(h));else r=n;r!==n&&(d=t,r=c());return t=r}())!==n?(d=t,t=r={fullName:r,description:o}):(p=t,t=n)):(p=t,t=n),t===n&&(t=p,(r=v())!==n&&(d=t,r=function(e){return{fullName:e}}(r)),t=r),t}function v(){var t,r,A;return t=p,64===e.charCodeAt(p)?(r="@",p++):(r=n,w(a)),r!==n&&S()!==n?(47===e.charCodeAt(p)?(A="/",p++):(A=n,w(s)),A!==n&&S()!==n?(d=t,t=r=c()):(p=t,t=n)):(p=t,t=n),t===n&&(t=p,(r=S())!==n&&(d=t,r=c()),t=r),t}function S(){var t,r,A;if(t=p,r=[],g.test(e.charAt(p))?(A=e.charAt(p),p++):(A=n,w(l)),A!==n)for(;A!==n;)r.push(A),g.test(e.charAt(p))?(A=e.charAt(p),p++):(A=n,w(l));else r=n;return r!==n&&(d=t,r=c()),t=r}if((A=i())!==n&&p===e.length)return A;throw A!==n&&p{"use strict";function t(e,r,A,n){this.message=e,this.expected=r,this.found=A,this.location=n,this.name="SyntaxError","function"==typeof Error.captureStackTrace&&Error.captureStackTrace(this,t)}!function(e,t){function r(){this.constructor=e}r.prototype=t.prototype,e.prototype=new r}(t,Error),t.buildMessage=function(e,t){var r={literal:function(e){return'"'+n(e.text)+'"'},class:function(e){var t,r="";for(t=0;t0){for(t=1,A=1;t>",!1),I=le(">&",!1),E=le(">",!1),B=le("<<<",!1),y=le("<&",!1),m=le("<",!1),w=le("'",!1),Q=le('"',!1),D=function(e){return{type:"text",text:e}},b=le("\\",!1),v={type:"any"},S=/^[^']/,k=ue(["'"],!0,!1),N=function(e){return e.join("")},F=/^[^$"]/,K=ue(["$",'"'],!0,!1),M=le("-",!1),R=le("+",!1),x=/^[0-9]/,L=ue([["0","9"]],!1,!1),P=le(".",!1),O=le("*",!1),U=le("/",!1),T=le("$((",!1),j=le("))",!1),Y=le("$(",!1),G=le("${",!1),H=le(":-",!1),J=le(":-}",!1),q=function(e){return{name:e}},z=le("$",!1),W=/^[a-zA-Z0-9_]/,V=ue([["a","z"],["A","Z"],["0","9"],"_"],!1,!1),X=function(){return e.substring(ie,oe)},_=/^[$@*?#a-zA-Z0-9_\-]/,Z=ue(["$","@","*","?","#",["a","z"],["A","Z"],["0","9"],"_","-"],!1,!1),$=/^[(){}<>$|&; \t"']/,ee=ue(["(",")","{","}","<",">","$","|","&",";"," ","\t",'"',"'"],!1,!1),te=/^[<>&; \t"']/,re=ue(["<",">","&",";"," ","\t",'"',"'"],!1,!1),Ae=/^[ \t]/,ne=ue([" ","\t"],!1,!1),oe=0,ie=0,se=[{line:1,column:1}],ae=0,ce=[],ge=0;if("startRule"in r){if(!(r.startRule in o))throw new Error("Can't start parsing from rule \""+r.startRule+'".');i=o[r.startRule]}function le(e,t){return{type:"literal",text:e,ignoreCase:t}}function ue(e,t,r){return{type:"class",parts:e,inverted:t,ignoreCase:r}}function he(t){var r,A=se[t];if(A)return A;for(r=t-1;!se[r];)r--;for(A={line:(A=se[r]).line,column:A.column};rae&&(ae=oe,ce=[]),ce.push(e))}function Ce(e,r,A){return new t(t.buildMessage(e,r),e,r,A)}function fe(){var e,t;return e=oe,(t=Ie())===n&&(t=null),t!==n&&(ie=e,t=t||[]),e=t}function Ie(){var e,t,r,A,o;if(e=oe,(t=Be())!==n){for(r=[],A=Te();A!==n;)r.push(A),A=Te();r!==n&&(A=Ee())!==n?((o=function(){var e,t,r,A,o;e=oe,t=[],r=Te();for(;r!==n;)t.push(r),r=Te();if(t!==n)if((r=Ie())!==n){for(A=[],o=Te();o!==n;)A.push(o),o=Te();A!==n?(ie=e,e=t=r):(oe=e,e=n)}else oe=e,e=n;else oe=e,e=n;return e}())===n&&(o=null),o!==n?(ie=e,e=t=[t].concat(o||[])):(oe=e,e=n)):(oe=e,e=n)}else oe=e,e=n;if(e===n)if(e=oe,(t=Be())!==n){for(r=[],A=Te();A!==n;)r.push(A),A=Te();r!==n?((A=Ee())===n&&(A=null),A!==n?(ie=e,e=t=function(e,t){return[e]}(t)):(oe=e,e=n)):(oe=e,e=n)}else oe=e,e=n;return e}function Ee(){var t;return 59===e.charCodeAt(oe)?(t=";",oe++):(t=n,0===ge&&de(s)),t}function Be(){var t,r,A,o,i;return t=oe,(r=ye())!==n?((A=function(){var t,r,A,o,i,s,g;t=oe,r=[],A=Te();for(;A!==n;)r.push(A),A=Te();if(r!==n)if((A=function(){var t;"&&"===e.substr(oe,2)?(t="&&",oe+=2):(t=n,0===ge&&de(a));t===n&&("||"===e.substr(oe,2)?(t="||",oe+=2):(t=n,0===ge&&de(c)));return t}())!==n){for(o=[],i=Te();i!==n;)o.push(i),i=Te();if(o!==n)if((i=Be())!==n){for(s=[],g=Te();g!==n;)s.push(g),g=Te();s!==n?(ie=t,t=r={type:A,line:i}):(oe=t,t=n)}else oe=t,t=n;else oe=t,t=n}else oe=t,t=n;else oe=t,t=n;return t}())===n&&(A=null),A!==n?(ie=t,o=r,t=r=(i=A)?{chain:o,then:i}:{chain:o}):(oe=t,t=n)):(oe=t,t=n),t}function ye(){var t,r,A,o,i;return t=oe,(r=function(){var t,r,A,o,i,s,a,c,g,l,u;t=oe,r=[],A=Te();for(;A!==n;)r.push(A),A=Te();if(r!==n)if(40===e.charCodeAt(oe)?(A="(",oe++):(A=n,0===ge&&de(h)),A!==n){for(o=[],i=Te();i!==n;)o.push(i),i=Te();if(o!==n)if((i=Ie())!==n){for(s=[],a=Te();a!==n;)s.push(a),a=Te();if(s!==n)if(41===e.charCodeAt(oe)?(a=")",oe++):(a=n,0===ge&&de(p)),a!==n){for(c=[],g=Te();g!==n;)c.push(g),g=Te();if(c!==n){for(g=[],l=Qe();l!==n;)g.push(l),l=Qe();if(g!==n){for(l=[],u=Te();u!==n;)l.push(u),u=Te();l!==n?(ie=t,t=r={type:"subshell",subshell:i,args:g}):(oe=t,t=n)}else oe=t,t=n}else oe=t,t=n}else oe=t,t=n;else oe=t,t=n}else oe=t,t=n;else oe=t,t=n}else oe=t,t=n;else oe=t,t=n;if(t===n){for(t=oe,r=[],A=Te();A!==n;)r.push(A),A=Te();if(r!==n)if(123===e.charCodeAt(oe)?(A="{",oe++):(A=n,0===ge&&de(d)),A!==n){for(o=[],i=Te();i!==n;)o.push(i),i=Te();if(o!==n)if((i=Ie())!==n){for(s=[],a=Te();a!==n;)s.push(a),a=Te();if(s!==n)if(125===e.charCodeAt(oe)?(a="}",oe++):(a=n,0===ge&&de(C)),a!==n){for(c=[],g=Te();g!==n;)c.push(g),g=Te();if(c!==n){for(g=[],l=Qe();l!==n;)g.push(l),l=Qe();if(g!==n){for(l=[],u=Te();u!==n;)l.push(u),u=Te();l!==n?(ie=t,r=function(e,t){return{type:"group",group:e,args:t}}(i,g),t=r):(oe=t,t=n)}else oe=t,t=n}else oe=t,t=n}else oe=t,t=n;else oe=t,t=n}else oe=t,t=n;else oe=t,t=n}else oe=t,t=n;else oe=t,t=n;if(t===n){for(t=oe,r=[],A=Te();A!==n;)r.push(A),A=Te();if(r!==n){for(A=[],o=me();o!==n;)A.push(o),o=me();if(A!==n){for(o=[],i=Te();i!==n;)o.push(i),i=Te();if(o!==n){if(i=[],(s=we())!==n)for(;s!==n;)i.push(s),s=we();else i=n;if(i!==n){for(s=[],a=Te();a!==n;)s.push(a),a=Te();s!==n?(ie=t,r=function(e,t){return{type:"command",args:t,envs:e}}(A,i),t=r):(oe=t,t=n)}else oe=t,t=n}else oe=t,t=n}else oe=t,t=n}else oe=t,t=n;if(t===n){for(t=oe,r=[],A=Te();A!==n;)r.push(A),A=Te();if(r!==n){if(A=[],(o=me())!==n)for(;o!==n;)A.push(o),o=me();else A=n;if(A!==n){for(o=[],i=Te();i!==n;)o.push(i),i=Te();o!==n?(ie=t,t=r={type:"envs",envs:A}):(oe=t,t=n)}else oe=t,t=n}else oe=t,t=n}}}return t}())!==n?((A=function(){var t,r,A,o,i,s,a;t=oe,r=[],A=Te();for(;A!==n;)r.push(A),A=Te();if(r!==n)if((A=function(){var t;"|&"===e.substr(oe,2)?(t="|&",oe+=2):(t=n,0===ge&&de(g));t===n&&(124===e.charCodeAt(oe)?(t="|",oe++):(t=n,0===ge&&de(l)));return t}())!==n){for(o=[],i=Te();i!==n;)o.push(i),i=Te();if(o!==n)if((i=ye())!==n){for(s=[],a=Te();a!==n;)s.push(a),a=Te();s!==n?(ie=t,t=r={type:A,chain:i}):(oe=t,t=n)}else oe=t,t=n;else oe=t,t=n}else oe=t,t=n;else oe=t,t=n;return t}())===n&&(A=null),A!==n?(ie=t,o=r,t=r=(i=A)?{...o,then:i}:o):(oe=t,t=n)):(oe=t,t=n),t}function me(){var t,r,A,o,i,s;if(t=oe,(r=Le())!==n)if(61===e.charCodeAt(oe)?(A="=",oe++):(A=n,0===ge&&de(u)),A!==n)if((o=be())!==n){for(i=[],s=Te();s!==n;)i.push(s),s=Te();i!==n?(ie=t,t=r={name:r,args:[o]}):(oe=t,t=n)}else oe=t,t=n;else oe=t,t=n;else oe=t,t=n;if(t===n)if(t=oe,(r=Le())!==n)if(61===e.charCodeAt(oe)?(A="=",oe++):(A=n,0===ge&&de(u)),A!==n){for(o=[],i=Te();i!==n;)o.push(i),i=Te();o!==n?(ie=t,t=r=function(e){return{name:e,args:[]}}(r)):(oe=t,t=n)}else oe=t,t=n;else oe=t,t=n;return t}function we(){var e,t,r;for(e=oe,t=[],r=Te();r!==n;)t.push(r),r=Te();if(t!==n&&(r=Qe())!==n?(ie=e,e=t=r):(oe=e,e=n),e===n){for(e=oe,t=[],r=Te();r!==n;)t.push(r),r=Te();t!==n&&(r=De())!==n?(ie=e,e=t=r):(oe=e,e=n)}return e}function Qe(){var t,r,A,o;for(t=oe,r=[],A=Te();A!==n;)r.push(A),A=Te();return r!==n&&(A=function(){var t;">>"===e.substr(oe,2)?(t=">>",oe+=2):(t=n,0===ge&&de(f));t===n&&(">&"===e.substr(oe,2)?(t=">&",oe+=2):(t=n,0===ge&&de(I)),t===n&&(62===e.charCodeAt(oe)?(t=">",oe++):(t=n,0===ge&&de(E)),t===n&&("<<<"===e.substr(oe,3)?(t="<<<",oe+=3):(t=n,0===ge&&de(B)),t===n&&("<&"===e.substr(oe,2)?(t="<&",oe+=2):(t=n,0===ge&&de(y)),t===n&&(60===e.charCodeAt(oe)?(t="<",oe++):(t=n,0===ge&&de(m)))))));return t}())!==n&&(o=De())!==n?(ie=t,t=r={type:"redirection",subtype:A,args:[o]}):(oe=t,t=n),t}function De(){var e,t,r;for(e=oe,t=[],r=Te();r!==n;)t.push(r),r=Te();return t!==n&&(r=be())!==n?(ie=e,e=t=r):(oe=e,e=n),e}function be(){var e,t,r,A;if(e=oe,t=[],(r=ve())!==n)for(;r!==n;)t.push(r),r=ve();else t=n;return t!==n&&(ie=e,A=t,t={type:"argument",segments:[].concat(...A)}),e=t}function ve(){var t,r;return t=oe,(r=function(){var t,r,A,o;t=oe,39===e.charCodeAt(oe)?(r="'",oe++):(r=n,0===ge&&de(w));r!==n&&(A=function(){var t,r,A,o,i;t=oe,r=[],A=oe,92===e.charCodeAt(oe)?(o="\\",oe++):(o=n,0===ge&&de(b));o!==n?(e.length>oe?(i=e.charAt(oe),oe++):(i=n,0===ge&&de(v)),i!==n?(ie=A,A=o=i):(oe=A,A=n)):(oe=A,A=n);A===n&&(S.test(e.charAt(oe))?(A=e.charAt(oe),oe++):(A=n,0===ge&&de(k)));for(;A!==n;)r.push(A),A=oe,92===e.charCodeAt(oe)?(o="\\",oe++):(o=n,0===ge&&de(b)),o!==n?(e.length>oe?(i=e.charAt(oe),oe++):(i=n,0===ge&&de(v)),i!==n?(ie=A,A=o=i):(oe=A,A=n)):(oe=A,A=n),A===n&&(S.test(e.charAt(oe))?(A=e.charAt(oe),oe++):(A=n,0===ge&&de(k)));r!==n&&(ie=t,r=N(r));return t=r}())!==n?(39===e.charCodeAt(oe)?(o="'",oe++):(o=n,0===ge&&de(w)),o!==n?(ie=t,r=function(e){return[{type:"text",text:e}]}(A),t=r):(oe=t,t=n)):(oe=t,t=n);return t}())!==n&&(ie=t,r=r),(t=r)===n&&(t=oe,(r=function(){var t,r,A,o;t=oe,34===e.charCodeAt(oe)?(r='"',oe++):(r=n,0===ge&&de(Q));if(r!==n){for(A=[],o=Se();o!==n;)A.push(o),o=Se();A!==n?(34===e.charCodeAt(oe)?(o='"',oe++):(o=n,0===ge&&de(Q)),o!==n?(ie=t,t=r=A):(oe=t,t=n)):(oe=t,t=n)}else oe=t,t=n;return t}())!==n&&(ie=t,r=r),(t=r)===n&&(t=oe,(r=function(){var e,t,r;if(e=oe,t=[],(r=ke())!==n)for(;r!==n;)t.push(r),r=ke();else t=n;t!==n&&(ie=e,t=t);return e=t}())!==n&&(ie=t,r=r),t=r)),t}function Se(){var t,r,A;return t=oe,(r=Me())!==n&&(ie=t,r={type:"arithmetic",arithmetic:r,quoted:!0}),(t=r)===n&&(t=oe,(r=Re())!==n&&(ie=t,r={type:"shell",shell:r,quoted:!0}),(t=r)===n&&(t=oe,(r=xe())!==n&&(ie=t,A=r,r={type:"variable",...A,quoted:!0}),(t=r)===n&&(t=oe,(r=function(){var t,r,A,o,i;t=oe,r=[],A=oe,92===e.charCodeAt(oe)?(o="\\",oe++):(o=n,0===ge&&de(b));o!==n?(e.length>oe?(i=e.charAt(oe),oe++):(i=n,0===ge&&de(v)),i!==n?(ie=A,A=o=i):(oe=A,A=n)):(oe=A,A=n);A===n&&(F.test(e.charAt(oe))?(A=e.charAt(oe),oe++):(A=n,0===ge&&de(K)));if(A!==n)for(;A!==n;)r.push(A),A=oe,92===e.charCodeAt(oe)?(o="\\",oe++):(o=n,0===ge&&de(b)),o!==n?(e.length>oe?(i=e.charAt(oe),oe++):(i=n,0===ge&&de(v)),i!==n?(ie=A,A=o=i):(oe=A,A=n)):(oe=A,A=n),A===n&&(F.test(e.charAt(oe))?(A=e.charAt(oe),oe++):(A=n,0===ge&&de(K)));else r=n;r!==n&&(ie=t,r=N(r));return t=r}())!==n&&(ie=t,r=D(r)),t=r))),t}function ke(){var t,A,o;return t=oe,(A=Me())!==n&&(ie=t,A={type:"arithmetic",arithmetic:A,quoted:!1}),(t=A)===n&&(t=oe,(A=Re())!==n&&(ie=t,A={type:"shell",shell:A,quoted:!1}),(t=A)===n&&(t=oe,(A=xe())!==n&&(ie=t,o=A,A={type:"variable",...o,quoted:!1}),(t=A)===n&&(t=oe,(A=function(){var t,A;t=oe,(A=function(){var t,r,A,o,i;t=oe,r=[],A=oe,o=oe,ge++,i=Ue(),ge--,i===n?o=void 0:(oe=o,o=n);o!==n?(e.length>oe?(i=e.charAt(oe),oe++):(i=n,0===ge&&de(v)),i!==n?(ie=A,A=o=i):(oe=A,A=n)):(oe=A,A=n);if(A!==n)for(;A!==n;)r.push(A),A=oe,o=oe,ge++,i=Ue(),ge--,i===n?o=void 0:(oe=o,o=n),o!==n?(e.length>oe?(i=e.charAt(oe),oe++):(i=n,0===ge&&de(v)),i!==n?(ie=A,A=o=i):(oe=A,A=n)):(oe=A,A=n);else r=n;r!==n&&(ie=t,r=N(r));return t=r}())!==n?(ie=oe,o=A,(r.isGlobPattern(o)?void 0:n)!==n?(ie=t,t=A=A):(oe=t,t=n)):(oe=t,t=n);var o;return t}())!==n&&(ie=t,A={type:"glob",pattern:A}),(t=A)===n&&(t=oe,(A=function(){var t,r,A,o,i;t=oe,r=[],A=oe,92===e.charCodeAt(oe)?(o="\\",oe++):(o=n,0===ge&&de(b));o!==n?(e.length>oe?(i=e.charAt(oe),oe++):(i=n,0===ge&&de(v)),i!==n?(ie=A,A=o=i):(oe=A,A=n)):(oe=A,A=n);A===n&&(A=oe,o=oe,ge++,i=Oe(),ge--,i===n?o=void 0:(oe=o,o=n),o!==n?(e.length>oe?(i=e.charAt(oe),oe++):(i=n,0===ge&&de(v)),i!==n?(ie=A,A=o=i):(oe=A,A=n)):(oe=A,A=n));if(A!==n)for(;A!==n;)r.push(A),A=oe,92===e.charCodeAt(oe)?(o="\\",oe++):(o=n,0===ge&&de(b)),o!==n?(e.length>oe?(i=e.charAt(oe),oe++):(i=n,0===ge&&de(v)),i!==n?(ie=A,A=o=i):(oe=A,A=n)):(oe=A,A=n),A===n&&(A=oe,o=oe,ge++,i=Oe(),ge--,i===n?o=void 0:(oe=o,o=n),o!==n?(e.length>oe?(i=e.charAt(oe),oe++):(i=n,0===ge&&de(v)),i!==n?(ie=A,A=o=i):(oe=A,A=n)):(oe=A,A=n));else r=n;r!==n&&(ie=t,r=N(r));return t=r}())!==n&&(ie=t,A=D(A)),t=A)))),t}function Ne(){var t,r,A,o,i,s,a,c;if(t=oe,45===e.charCodeAt(oe)?(r="-",oe++):(r=n,0===ge&&de(M)),r===n&&(43===e.charCodeAt(oe)?(r="+",oe++):(r=n,0===ge&&de(R))),r===n&&(r=null),r!==n){if(A=[],x.test(e.charAt(oe))?(o=e.charAt(oe),oe++):(o=n,0===ge&&de(L)),o!==n)for(;o!==n;)A.push(o),x.test(e.charAt(oe))?(o=e.charAt(oe),oe++):(o=n,0===ge&&de(L));else A=n;if(A!==n)if(46===e.charCodeAt(oe)?(o=".",oe++):(o=n,0===ge&&de(P)),o!==n){if(i=[],x.test(e.charAt(oe))?(s=e.charAt(oe),oe++):(s=n,0===ge&&de(L)),s!==n)for(;s!==n;)i.push(s),x.test(e.charAt(oe))?(s=e.charAt(oe),oe++):(s=n,0===ge&&de(L));else i=n;i!==n?(ie=t,a=i,t=r={type:"number",value:("-"===r?-1:1)*parseFloat(A.join("")+"."+a.join(""))}):(oe=t,t=n)}else oe=t,t=n;else oe=t,t=n}else oe=t,t=n;if(t===n){if(t=oe,45===e.charCodeAt(oe)?(r="-",oe++):(r=n,0===ge&&de(M)),r===n&&(43===e.charCodeAt(oe)?(r="+",oe++):(r=n,0===ge&&de(R))),r===n&&(r=null),r!==n){if(A=[],x.test(e.charAt(oe))?(o=e.charAt(oe),oe++):(o=n,0===ge&&de(L)),o!==n)for(;o!==n;)A.push(o),x.test(e.charAt(oe))?(o=e.charAt(oe),oe++):(o=n,0===ge&&de(L));else A=n;A!==n?(ie=t,t=r=function(e,t){return{type:"number",value:("-"===e?-1:1)*parseInt(t.join(""))}}(r,A)):(oe=t,t=n)}else oe=t,t=n;if(t===n&&(t=oe,(r=xe())!==n&&(ie=t,c=r,r={type:"variable",...c}),(t=r)===n&&(t=oe,(r=Pe())!==n&&(ie=t,r={type:"variable",name:r}),(t=r)===n)))if(t=oe,40===e.charCodeAt(oe)?(r="(",oe++):(r=n,0===ge&&de(h)),r!==n){for(A=[],o=Te();o!==n;)A.push(o),o=Te();if(A!==n)if((o=Ke())!==n){for(i=[],s=Te();s!==n;)i.push(s),s=Te();i!==n?(41===e.charCodeAt(oe)?(s=")",oe++):(s=n,0===ge&&de(p)),s!==n?(ie=t,t=r=o):(oe=t,t=n)):(oe=t,t=n)}else oe=t,t=n;else oe=t,t=n}else oe=t,t=n}return t}function Fe(){var t,r,A,o,i,s;if(t=oe,(r=Ne())!==n){for(A=[],o=Te();o!==n;)A.push(o),o=Te();if(A!==n)if(42===e.charCodeAt(oe)?(o="*",oe++):(o=n,0===ge&&de(O)),o!==n){for(i=[],s=Te();s!==n;)i.push(s),s=Te();i!==n&&(s=Fe())!==n?(ie=t,t=r={type:"multiplication",left:r,right:s}):(oe=t,t=n)}else oe=t,t=n;else oe=t,t=n}else oe=t,t=n;if(t===n){if(t=oe,(r=Ne())!==n){for(A=[],o=Te();o!==n;)A.push(o),o=Te();if(A!==n)if(47===e.charCodeAt(oe)?(o="/",oe++):(o=n,0===ge&&de(U)),o!==n){for(i=[],s=Te();s!==n;)i.push(s),s=Te();i!==n&&(s=Fe())!==n?(ie=t,t=r=function(e,t){return{type:"division",left:e,right:t}}(r,s)):(oe=t,t=n)}else oe=t,t=n;else oe=t,t=n}else oe=t,t=n;t===n&&(t=Ne())}return t}function Ke(){var t,r,A,o,i,s;if(t=oe,(r=Fe())!==n){for(A=[],o=Te();o!==n;)A.push(o),o=Te();if(A!==n)if(43===e.charCodeAt(oe)?(o="+",oe++):(o=n,0===ge&&de(R)),o!==n){for(i=[],s=Te();s!==n;)i.push(s),s=Te();i!==n&&(s=Ke())!==n?(ie=t,t=r={type:"addition",left:r,right:s}):(oe=t,t=n)}else oe=t,t=n;else oe=t,t=n}else oe=t,t=n;if(t===n){if(t=oe,(r=Fe())!==n){for(A=[],o=Te();o!==n;)A.push(o),o=Te();if(A!==n)if(45===e.charCodeAt(oe)?(o="-",oe++):(o=n,0===ge&&de(M)),o!==n){for(i=[],s=Te();s!==n;)i.push(s),s=Te();i!==n&&(s=Ke())!==n?(ie=t,t=r=function(e,t){return{type:"subtraction",left:e,right:t}}(r,s)):(oe=t,t=n)}else oe=t,t=n;else oe=t,t=n}else oe=t,t=n;t===n&&(t=Fe())}return t}function Me(){var t,r,A,o,i,s;if(t=oe,"$(("===e.substr(oe,3)?(r="$((",oe+=3):(r=n,0===ge&&de(T)),r!==n){for(A=[],o=Te();o!==n;)A.push(o),o=Te();if(A!==n)if((o=Ke())!==n){for(i=[],s=Te();s!==n;)i.push(s),s=Te();i!==n?("))"===e.substr(oe,2)?(s="))",oe+=2):(s=n,0===ge&&de(j)),s!==n?(ie=t,t=r=o):(oe=t,t=n)):(oe=t,t=n)}else oe=t,t=n;else oe=t,t=n}else oe=t,t=n;return t}function Re(){var t,r,A,o;return t=oe,"$("===e.substr(oe,2)?(r="$(",oe+=2):(r=n,0===ge&&de(Y)),r!==n&&(A=Ie())!==n?(41===e.charCodeAt(oe)?(o=")",oe++):(o=n,0===ge&&de(p)),o!==n?(ie=t,t=r=A):(oe=t,t=n)):(oe=t,t=n),t}function xe(){var t,r,A,o,i,s;return t=oe,"${"===e.substr(oe,2)?(r="${",oe+=2):(r=n,0===ge&&de(G)),r!==n&&(A=Pe())!==n?(":-"===e.substr(oe,2)?(o=":-",oe+=2):(o=n,0===ge&&de(H)),o!==n&&(i=function(){var e,t,r,A,o;for(e=oe,t=[],r=Te();r!==n;)t.push(r),r=Te();if(t!==n){if(r=[],(A=De())!==n)for(;A!==n;)r.push(A),A=De();else r=n;if(r!==n){for(A=[],o=Te();o!==n;)A.push(o),o=Te();A!==n?(ie=e,e=t=r):(oe=e,e=n)}else oe=e,e=n}else oe=e,e=n;return e}())!==n?(125===e.charCodeAt(oe)?(s="}",oe++):(s=n,0===ge&&de(C)),s!==n?(ie=t,t=r={name:A,defaultValue:i}):(oe=t,t=n)):(oe=t,t=n)):(oe=t,t=n),t===n&&(t=oe,"${"===e.substr(oe,2)?(r="${",oe+=2):(r=n,0===ge&&de(G)),r!==n&&(A=Pe())!==n?(":-}"===e.substr(oe,3)?(o=":-}",oe+=3):(o=n,0===ge&&de(J)),o!==n?(ie=t,t=r=function(e){return{name:e,defaultValue:[]}}(A)):(oe=t,t=n)):(oe=t,t=n),t===n&&(t=oe,"${"===e.substr(oe,2)?(r="${",oe+=2):(r=n,0===ge&&de(G)),r!==n&&(A=Pe())!==n?(125===e.charCodeAt(oe)?(o="}",oe++):(o=n,0===ge&&de(C)),o!==n?(ie=t,t=r=q(A)):(oe=t,t=n)):(oe=t,t=n),t===n&&(t=oe,36===e.charCodeAt(oe)?(r="$",oe++):(r=n,0===ge&&de(z)),r!==n&&(A=Pe())!==n?(ie=t,t=r=q(A)):(oe=t,t=n)))),t}function Le(){var t,r,A;if(t=oe,r=[],W.test(e.charAt(oe))?(A=e.charAt(oe),oe++):(A=n,0===ge&&de(V)),A!==n)for(;A!==n;)r.push(A),W.test(e.charAt(oe))?(A=e.charAt(oe),oe++):(A=n,0===ge&&de(V));else r=n;return r!==n&&(ie=t,r=X()),t=r}function Pe(){var t,r,A;if(t=oe,r=[],_.test(e.charAt(oe))?(A=e.charAt(oe),oe++):(A=n,0===ge&&de(Z)),A!==n)for(;A!==n;)r.push(A),_.test(e.charAt(oe))?(A=e.charAt(oe),oe++):(A=n,0===ge&&de(Z));else r=n;return r!==n&&(ie=t,r=X()),t=r}function Oe(){var t;return $.test(e.charAt(oe))?(t=e.charAt(oe),oe++):(t=n,0===ge&&de(ee)),t}function Ue(){var t;return te.test(e.charAt(oe))?(t=e.charAt(oe),oe++):(t=n,0===ge&&de(re)),t}function Te(){var t,r;if(t=[],Ae.test(e.charAt(oe))?(r=e.charAt(oe),oe++):(r=n,0===ge&&de(ne)),r!==n)for(;r!==n;)t.push(r),Ae.test(e.charAt(oe))?(r=e.charAt(oe),oe++):(r=n,0===ge&&de(ne));else t=n;return t}if((A=i())!==n&&oe===e.length)return A;throw A!==n&&oe{"use strict";function t(e,r,A,n){this.message=e,this.expected=r,this.found=A,this.location=n,this.name="SyntaxError","function"==typeof Error.captureStackTrace&&Error.captureStackTrace(this,t)}!function(e,t){function r(){this.constructor=e}r.prototype=t.prototype,e.prototype=new r}(t,Error),t.buildMessage=function(e,t){var r={literal:function(e){return`"${n(e.text)}"`},class:function(e){var t,r="";for(t=0;t0){for(t=1,A=1;t'"%@`\-]/,I=oe(["\r","\n","\t"," ","?",":",",","]","[","{","}","#","&","*","!","|",">","'",'"',"%","@","`","-"],!0,!1),E=/^[^\r\n\t ,\][{}:#"']/,B=oe(["\r","\n","\t"," ",",","]","[","{","}",":","#",'"',"'"],!0,!1),y=function(){return Ae().replace(/^ *| *$/g,"")},m=ne("--",!1),w=/^[a-zA-Z\/0-9]/,Q=oe([["a","z"],["A","Z"],"/",["0","9"]],!1,!1),D=/^[^\r\n\t :,]/,b=oe(["\r","\n","\t"," ",":",","],!0,!1),v=ne("null",!1),S=ne("true",!1),k=ne("false",!1),N=ie("string"),F=ne('"',!1),K=/^[^"\\\0-\x1F\x7F]/,M=oe(['"',"\\",["\0",""],""],!0,!1),R=ne('\\"',!1),x=ne("\\\\",!1),L=ne("\\/",!1),P=ne("\\b",!1),O=ne("\\f",!1),U=ne("\\n",!1),T=ne("\\r",!1),j=ne("\\t",!1),Y=ne("\\u",!1),G=/^[0-9a-fA-F]/,H=oe([["0","9"],["a","f"],["A","F"]],!1,!1),J=ie("blank space"),q=/^[ \t]/,z=oe([" ","\t"],!1,!1),W=(ie("white space"),oe([" ","\t","\n","\r"],!1,!1),ne("\r\n",!1)),V=ne("\n",!1),X=ne("\r",!1),_=0,Z=0,$=[{line:1,column:1}],ee=0,te=[],re=0;if("startRule"in r){if(!(r.startRule in o))throw new Error(`Can't start parsing from rule "${r.startRule}".`);i=o[r.startRule]}function Ae(){return e.substring(Z,_)}function ne(e,t){return{type:"literal",text:e,ignoreCase:t}}function oe(e,t,r){return{type:"class",parts:e,inverted:t,ignoreCase:r}}function ie(e){return{type:"other",description:e}}function se(t){var r,A=$[t];if(A)return A;for(r=t-1;!$[r];)r--;for(A={line:(A=$[r]).line,column:A.column};ree&&(ee=_,te=[]),te.push(e))}function ge(e,r,A){return new t(t.buildMessage(e,r),e,r,A)}function le(){return he()}function ue(){var t,r,A;return t=_,Ce()!==n?(45===e.charCodeAt(_)?(r="-",_++):(r=n,0===re&&ce(s)),r!==n&&be()!==n&&(A=de())!==n?(Z=t,t=A):(_=t,t=n)):(_=t,t=n),t}function he(){var e,t,r,A;for(e=_,t=[],r=pe();r!==n;)t.push(r),r=pe();return t!==n&&(Z=e,A=t,t=Object.assign({},...A)),e=t}function pe(){var t,r,A,o,i,s,p,d,C,f,I,E;if(t=_,(r=be())===n&&(r=null),r!==n){if(A=_,35===e.charCodeAt(_)?(o="#",_++):(o=n,0===re&&ce(a)),o!==n){if(i=[],s=_,p=_,re++,d=Se(),re--,d===n?p=void 0:(_=p,p=n),p!==n?(e.length>_?(d=e.charAt(_),_++):(d=n,0===re&&ce(c)),d!==n?s=p=[p,d]:(_=s,s=n)):(_=s,s=n),s!==n)for(;s!==n;)i.push(s),s=_,p=_,re++,d=Se(),re--,d===n?p=void 0:(_=p,p=n),p!==n?(e.length>_?(d=e.charAt(_),_++):(d=n,0===re&&ce(c)),d!==n?s=p=[p,d]:(_=s,s=n)):(_=s,s=n);else i=n;i!==n?A=o=[o,i]:(_=A,A=n)}else _=A,A=n;if(A===n&&(A=null),A!==n){if(o=[],(i=ve())!==n)for(;i!==n;)o.push(i),i=ve();else o=n;o!==n?(Z=t,t=r={}):(_=t,t=n)}else _=t,t=n}else _=t,t=n;if(t===n&&(t=_,(r=Ce())!==n&&(A=function(){var e;(e=we())===n&&(e=Be());return e}())!==n?((o=be())===n&&(o=null),o!==n?(58===e.charCodeAt(_)?(i=":",_++):(i=n,0===re&&ce(g)),i!==n?((s=be())===n&&(s=null),s!==n&&(p=de())!==n?(Z=t,t=r=l(A,p)):(_=t,t=n)):(_=t,t=n)):(_=t,t=n)):(_=t,t=n),t===n&&(t=_,(r=Ce())!==n&&(A=Ee())!==n?((o=be())===n&&(o=null),o!==n?(58===e.charCodeAt(_)?(i=":",_++):(i=n,0===re&&ce(g)),i!==n?((s=be())===n&&(s=null),s!==n&&(p=de())!==n?(Z=t,t=r=l(A,p)):(_=t,t=n)):(_=t,t=n)):(_=t,t=n)):(_=t,t=n),t===n))){if(t=_,(r=Ce())!==n)if((A=Ee())!==n)if((o=be())!==n)if((i=function(){var e;(e=me())===n&&(e=we())===n&&(e=ye());return e}())!==n){if(s=[],(p=ve())!==n)for(;p!==n;)s.push(p),p=ve();else s=n;s!==n?(Z=t,t=r=l(A,i)):(_=t,t=n)}else _=t,t=n;else _=t,t=n;else _=t,t=n;else _=t,t=n;if(t===n)if(t=_,(r=Ce())!==n)if((A=Ee())!==n){if(o=[],i=_,(s=be())===n&&(s=null),s!==n?(44===e.charCodeAt(_)?(p=",",_++):(p=n,0===re&&ce(u)),p!==n?((d=be())===n&&(d=null),d!==n&&(C=Ee())!==n?(Z=i,i=s=h(0,C)):(_=i,i=n)):(_=i,i=n)):(_=i,i=n),i!==n)for(;i!==n;)o.push(i),i=_,(s=be())===n&&(s=null),s!==n?(44===e.charCodeAt(_)?(p=",",_++):(p=n,0===re&&ce(u)),p!==n?((d=be())===n&&(d=null),d!==n&&(C=Ee())!==n?(Z=i,i=s=h(0,C)):(_=i,i=n)):(_=i,i=n)):(_=i,i=n);else o=n;o!==n?((i=be())===n&&(i=null),i!==n?(58===e.charCodeAt(_)?(s=":",_++):(s=n,0===re&&ce(g)),s!==n?((p=be())===n&&(p=null),p!==n&&(d=de())!==n?(Z=t,f=A,I=o,E=d,t=r=Object.assign({},...[f].concat(I).map(e=>({[e]:E})))):(_=t,t=n)):(_=t,t=n)):(_=t,t=n)):(_=t,t=n)}else _=t,t=n;else _=t,t=n}return t}function de(){var t,r,A,o,i,a,c;if(t=_,r=_,re++,A=_,(o=Se())!==n&&(i=function(){var t,r,A;t=_,r=[],32===e.charCodeAt(_)?(A=" ",_++):(A=n,0===re&&ce(d));for(;A!==n;)r.push(A),32===e.charCodeAt(_)?(A=" ",_++):(A=n,0===re&&ce(d));r!==n?(Z=_,(A=(A=r.length===(Ne+1)*ke)?void 0:n)!==n?t=r=[r,A]:(_=t,t=n)):(_=t,t=n);return t}())!==n?(45===e.charCodeAt(_)?(a="-",_++):(a=n,0===re&&ce(s)),a!==n&&(c=be())!==n?A=o=[o,i,a,c]:(_=A,A=n)):(_=A,A=n),re--,A!==n?(_=r,r=void 0):r=n,r!==n&&(A=ve())!==n&&(o=fe())!==n&&(i=function(){var e,t,r,A;for(e=_,t=[],r=ue();r!==n;)t.push(r),r=ue();return t!==n&&(Z=e,A=t,t=[].concat(...A)),e=t}())!==n&&(a=Ie())!==n?(Z=t,t=r=i):(_=t,t=n),t===n&&(t=_,(r=Se())!==n&&(A=fe())!==n&&(o=he())!==n&&(i=Ie())!==n?(Z=t,t=r=o):(_=t,t=n),t===n))if(t=_,(r=function(){var t;(t=me())===n&&(t=function(){var t,r;t=_,"true"===e.substr(_,4)?(r="true",_+=4):(r=n,0===re&&ce(S));r!==n&&(Z=t,r=!0);(t=r)===n&&(t=_,"false"===e.substr(_,5)?(r="false",_+=5):(r=n,0===re&&ce(k)),r!==n&&(Z=t,r=!1),t=r);return t}())===n&&(t=we())===n&&(t=Be());return t}())!==n){if(A=[],(o=ve())!==n)for(;o!==n;)A.push(o),o=ve();else A=n;A!==n?(Z=t,t=r=r):(_=t,t=n)}else _=t,t=n;return t}function Ce(){var t,r,A;for(re++,t=_,r=[],32===e.charCodeAt(_)?(A=" ",_++):(A=n,0===re&&ce(d));A!==n;)r.push(A),32===e.charCodeAt(_)?(A=" ",_++):(A=n,0===re&&ce(d));return r!==n?(Z=_,(A=(A=r.length===Ne*ke)?void 0:n)!==n?t=r=[r,A]:(_=t,t=n)):(_=t,t=n),re--,t===n&&(r=n,0===re&&ce(p)),t}function fe(){return Z=_,Ne++,!0?void 0:n}function Ie(){return Z=_,Ne--,!0?void 0:n}function Ee(){var e,t,r;if((e=we())===n){if(e=_,t=[],(r=ye())!==n)for(;r!==n;)t.push(r),r=ye();else t=n;t!==n&&(Z=e,t=Ae()),e=t}return e}function Be(){var t,r,A,o,i,s;if(re++,t=_,f.test(e.charAt(_))?(r=e.charAt(_),_++):(r=n,0===re&&ce(I)),r!==n){for(A=[],o=_,(i=be())===n&&(i=null),i!==n?(E.test(e.charAt(_))?(s=e.charAt(_),_++):(s=n,0===re&&ce(B)),s!==n?o=i=[i,s]:(_=o,o=n)):(_=o,o=n);o!==n;)A.push(o),o=_,(i=be())===n&&(i=null),i!==n?(E.test(e.charAt(_))?(s=e.charAt(_),_++):(s=n,0===re&&ce(B)),s!==n?o=i=[i,s]:(_=o,o=n)):(_=o,o=n);A!==n?(Z=t,t=r=y()):(_=t,t=n)}else _=t,t=n;return re--,t===n&&(r=n,0===re&&ce(C)),t}function ye(){var t,r,A,o,i;if(t=_,"--"===e.substr(_,2)?(r="--",_+=2):(r=n,0===re&&ce(m)),r===n&&(r=null),r!==n)if(w.test(e.charAt(_))?(A=e.charAt(_),_++):(A=n,0===re&&ce(Q)),A!==n){for(o=[],D.test(e.charAt(_))?(i=e.charAt(_),_++):(i=n,0===re&&ce(b));i!==n;)o.push(i),D.test(e.charAt(_))?(i=e.charAt(_),_++):(i=n,0===re&&ce(b));o!==n?(Z=t,t=r=y()):(_=t,t=n)}else _=t,t=n;else _=t,t=n;return t}function me(){var t,r;return t=_,"null"===e.substr(_,4)?(r="null",_+=4):(r=n,0===re&&ce(v)),r!==n&&(Z=t,r=null),t=r}function we(){var t,r,A,o;return re++,t=_,34===e.charCodeAt(_)?(r='"',_++):(r=n,0===re&&ce(F)),r!==n?(34===e.charCodeAt(_)?(A='"',_++):(A=n,0===re&&ce(F)),A!==n?(Z=t,t=r=""):(_=t,t=n)):(_=t,t=n),t===n&&(t=_,34===e.charCodeAt(_)?(r='"',_++):(r=n,0===re&&ce(F)),r!==n&&(A=function(){var e,t,r;if(e=_,t=[],(r=Qe())!==n)for(;r!==n;)t.push(r),r=Qe();else t=n;t!==n&&(Z=e,t=t.join(""));return e=t}())!==n?(34===e.charCodeAt(_)?(o='"',_++):(o=n,0===re&&ce(F)),o!==n?(Z=t,t=r=A):(_=t,t=n)):(_=t,t=n)),re--,t===n&&(r=n,0===re&&ce(N)),t}function Qe(){var t,r,A,o,i,s,a,c,g,l;return K.test(e.charAt(_))?(t=e.charAt(_),_++):(t=n,0===re&&ce(M)),t===n&&(t=_,'\\"'===e.substr(_,2)?(r='\\"',_+=2):(r=n,0===re&&ce(R)),r!==n&&(Z=t,r='"'),(t=r)===n&&(t=_,"\\\\"===e.substr(_,2)?(r="\\\\",_+=2):(r=n,0===re&&ce(x)),r!==n&&(Z=t,r="\\"),(t=r)===n&&(t=_,"\\/"===e.substr(_,2)?(r="\\/",_+=2):(r=n,0===re&&ce(L)),r!==n&&(Z=t,r="/"),(t=r)===n&&(t=_,"\\b"===e.substr(_,2)?(r="\\b",_+=2):(r=n,0===re&&ce(P)),r!==n&&(Z=t,r="\b"),(t=r)===n&&(t=_,"\\f"===e.substr(_,2)?(r="\\f",_+=2):(r=n,0===re&&ce(O)),r!==n&&(Z=t,r="\f"),(t=r)===n&&(t=_,"\\n"===e.substr(_,2)?(r="\\n",_+=2):(r=n,0===re&&ce(U)),r!==n&&(Z=t,r="\n"),(t=r)===n&&(t=_,"\\r"===e.substr(_,2)?(r="\\r",_+=2):(r=n,0===re&&ce(T)),r!==n&&(Z=t,r="\r"),(t=r)===n&&(t=_,"\\t"===e.substr(_,2)?(r="\\t",_+=2):(r=n,0===re&&ce(j)),r!==n&&(Z=t,r="\t"),(t=r)===n&&(t=_,"\\u"===e.substr(_,2)?(r="\\u",_+=2):(r=n,0===re&&ce(Y)),r!==n&&(A=De())!==n&&(o=De())!==n&&(i=De())!==n&&(s=De())!==n?(Z=t,a=A,c=o,g=i,l=s,t=r=String.fromCharCode(parseInt(`0x${a}${c}${g}${l}`))):(_=t,t=n)))))))))),t}function De(){var t;return G.test(e.charAt(_))?(t=e.charAt(_),_++):(t=n,0===re&&ce(H)),t}function be(){var t,r;if(re++,t=[],q.test(e.charAt(_))?(r=e.charAt(_),_++):(r=n,0===re&&ce(z)),r!==n)for(;r!==n;)t.push(r),q.test(e.charAt(_))?(r=e.charAt(_),_++):(r=n,0===re&&ce(z));else t=n;return re--,t===n&&(r=n,0===re&&ce(J)),t}function ve(){var e,t,r,A,o,i;if(e=_,(t=Se())!==n){for(r=[],A=_,(o=be())===n&&(o=null),o!==n&&(i=Se())!==n?A=o=[o,i]:(_=A,A=n);A!==n;)r.push(A),A=_,(o=be())===n&&(o=null),o!==n&&(i=Se())!==n?A=o=[o,i]:(_=A,A=n);r!==n?e=t=[t,r]:(_=e,e=n)}else _=e,e=n;return e}function Se(){var t;return"\r\n"===e.substr(_,2)?(t="\r\n",_+=2):(t=n,0===re&&ce(W)),t===n&&(10===e.charCodeAt(_)?(t="\n",_++):(t=n,0===re&&ce(V)),t===n&&(13===e.charCodeAt(_)?(t="\r",_++):(t=n,0===re&&ce(X)))),t}const ke=2;let Ne=0;if((A=i())!==n&&_===e.length)return A;throw A!==n&&_{let A;e.exports=()=>(void 0===A&&(A=r(78761).brotliDecompressSync(Buffer.from("W4VmWMM2BubfuhOQtPrf2v23OidkIrLQsV6vuo6ON5J6yagfMdrY7lWBqNRd9a47LpsBgqCqmpd0iExCZ1KAzk71/+8domYYLado6QgLVcDZGShUGZeMQlqNVNopK7ifA0nn9MKZyFF65wTuzVq9y8KLJIXtKHLGSuK1rAktpPEa3o/D+bTWy0Lum8P5dbi+afFDC2tbv6C+vb8PfoBYODmqfft9Hf5Pe0ggAgnkcyCScddvJcAQUaLFtBxiDzlFX6Xu3f20V3zi9/KX9v3n56uXPdxdESLXXGIvbEJOH2X8Th4liNWx9UwsCsmzw1aZ510Tdb5Rj+J7MJ8y4+/0oG7C5N5U/e6+nCb6u2syhiiXVOk32T1VbmmOnkICBEwLGCIzQ4HSPv1vU+s8vpwklpeRcMyX3CZhQ0hpXNKalPCFW0gBPcDD7EDWf21mpzNkxFiDnHpaxMPpp+2Fb0z5U8DCOE7xbpaa//u8NH5Zl8StbCqWBFeISIAGQJVrsNMLfOS+6WPU487yt6HHvVyqxkCGr9rxWKj5mb72aqpVcNinJQUMBonXOVfO3ff9fGydsqWp75+uSrpgOe34S2n6rY3EkbmxyDG4JPxoICAtZfP8L7kEnGpRcJiK7IrwPCabx4MHO4eKx/eTtA0Q4INF6w2rfFzV6uoWdLNp/e/zQ9s80fgiyQayGUyu1EbdOJV0LmX3p9qP6wXd/TIC/1lwJelIZmsp/rZYUk38z63G5Xvw7dummA0Go0VwYLs5GsIE/AD7Yf7W8eCBquyuHN9MJmn6ZRoK1BsfCbWLiKgVF1+m/efnuW234z4lWU4CSaniecD+KO8qKwbSjr1LjR81tj8eOkhlfTy+WQYYFGxASroh5mLUXxVrJYvaq/HHw/sYfzZRjlU9DQwC5EbGiXyTlXVDtDGWUDwofvwP59Pnx+7u49XU5n2emTsXhgA64E3EvxTrkKDBFhUtPGU2++PxO8t2fC0LEHuTzHaEZNJqi+WnICMb389Zli3hnEpdFg6ZtdTpSzwwO+DAMYS/NbQ/XoGUnXoEW12ZkX5IfFBvSTJfos/EWRVFnv9PNS1bh9RePIHCn43YkDqJK81QPoSd4ffvm5aSJ3dWxvlQSWJ9lrGrbr27/Kb7TDca2AFA8IzhOnJn1pqqeq+xvxuYOQCG2kNyJlhjZZyJdJREihIXKk1WSmX2e/s37pQhjCgxbs/Vfe0coZkJeFKrT/8UkL0B4CVkAeWaGWe0ZYbWf97303pT0HRTxpkkkiISZPMbY5Owa5uzhvVMiSgUMQOAgNQku3+bcc2W8Wftvc+97716hSkUQIoEexzlnMukVEmi/OtnMpHC6KEoQ1mXTaj/m1rSaZq5d76a+NIaQAEsmpEs36Z1QkOlP/4vUXvvdvc2vaLKEo1kZ8c6p5UKaACrhAaQYFi6Yf7eVP+t/sy9uyQFkQ4gFYZy/DH2DnRIsShdi+ecu1e8YWFhF+TX7hK0FwDlB4DSNwA+jgnwPazoAPIl6YeQKjomgZBm4ot0iKlMqQu5+607u/O4c/mLzqWr3lXtonbfurf/fW9p1fb97x7uAYAZRGZSpVDdI/Xa3QGCKrNka71YFd679x2j///+tw5XlQh3DzPAI8KKEQjYkEDArKje++4BvO8IMN2DAMsjCGYGQGYNwGLWgKxqM2YLmcgcxapRcjnTy1lss0Zq23evdkIxc6RYSf1vOpbqyDmE8+0FwlRLnUTiEIb/GtyUgCqbJaZMnSoZTEvmDL9CSqjDUeUqnCzPf9yn+v+5k1ltE9tA3wQoxssOHKGghXxpC0LBAltBtPBSe5swB1i7DYxBub83F2EoxiF03obaFB5bsh0Kc1bzrIwh3LQFCHQJIft/5CJOSAK0iCZowEvBt1E6se+QClLxyQDb/P6zGf+p4F3PzaDCAkTKwIoZSUwHunbpXlxNMWf/zySGe2fKzMwV7SAKgg0s2GpiS2JLsSU2hF26mHr3yxBu1v/vXtvs726Ps7eLFkKQEPAgwYpoQimiQYN4BXwmQ8sJtGRi4JvqJhOIEwjkbtY/rpP199/CClNIYbApTjXqCNN2WnKIGmUPa42wSoQ9jPAOe3hI+9ecvrylsbdHMMEED79ocIIGDTco4QgabtDgnVaQN7MkVf57pnDAhQoXIpigwoUh5hDBEBFUqBBBHCpcDGT9/93z0K/7HuDMzIMgOBAEQRAEQRAEF4IgOLBnrQMbmvl/3eIffPefkNv2BIIlFYgKllQgEAgEAoFAnEBcMsRlQVRUVNRvLp4Hn71PVT+xIiLQiIiIiBERESlERISylBVLiUjpFIYyYiiHNu3/0+fVV+2Rf8gGmIx5Oqweg87ORHPWoCK7ErY0QUikWCgWsCCIpSbaKRaQpO/ufT7JheKaKwOv5/+/KO15Qt3RkRCyzMSKsEuNtuxSK6gaK0YX5977m8Tq6Vkg8WFgFXHmNHyNkNthOPkkpeW3tyfaXr3W/Nhgzz10+7keQmIsRg6Nou1V9G2ouQrSXvz7RuQRM+xkIu5hKxFQDMCnijKYAOB5O1MvmlNyXfsYOqP676qcmPtHtcuoDuGsJDHT4rILl0OMh4Zj3fay5erEe+MJIAy9Y/jQEoCMkOML38mHoY0XTN2PnLn+l9AMOgbfm/WChFjb43o/INsWlyw5TyXJGo0jkzBVQhHpGQWQZe3PQzCf6OWq/mVwdbA6RGmy4IFePesVn5f250+VPdv2wODMfQYJsAZvRPbpDZCkhOhUNSmnVXaZszIeZNX51IJ9Ol16VNEUgkNtPXZqIfxDs1/MGXprB/9PAnj/JMnlUIzwIJyX8qe8LKT/bYffcwJHBscc2utF+e5/57jWSqHVooqW/YjHiFl4XEUJ9s98myoPWIzhQzVTOQ4kLey5KUDYV2MQ1cY4+7d9Cf+Bjv1hF1vvbJWYwy5BvlGKS9DkREkpgx8xST5PU4ikNC5wLB7cOmcGp+bpTrwJ73OkrOWEWGV/hSRJkwh87Z7aHxsaQMuNwvYREDvirh/o6xQ/MKgiU6hXgP2Sc1p6PQTcPPbG9kYfexMBckCiE8YsNTtM+02OimepUlRaPoVsFrSaU5Yd8oVSc0oD9/mSJJ30VeAbYu6fPRizwyyv9iWtAOH6fYetXKdOw73xEh4YJx5Xj7NZdnoNcknKz6B9i2bFto9LVeHtpL4aQBlkNaFQdMjwE/8v1Yr7beThYGvLiZC1769LxOjL0M4UhQIqHajDClFvQdp+wycLk0s6nzBWe3esZZ9hKyGKe5Ib2RI4XcGMnY+N3AKRpDW4dMcuQIm7bt/iJ7Hei2XIrGpFTj1nSVJjTSKeshvOEJV3PyKGVS2rxDCkrrr9QlilCBTyjsKyOhLZJEHH/43MSNIK/76cE+J1GpGrWksmgU1Y0zQShuPd6n2xtG5LBWlRW1xSP0VKlr9TkjFlvUpkTwAMwKfOnKWEGmYB9sjl06lKyNWDom5mkSN8ba+PY37qy1izbKkD/j1fmTLDzYfDN++/b4/PIV//LfryKYWIt/Tin5RpX2t+YFhbfnyyty8EWhXyY2vcfvoD2p9L9pAvbTGNcxsOlKNz/WLlfxU4n1ZKGsakG7dMjpCOY7N55I+jxutb+2jg6/h+JH3z0vnKHzf9o+t9hPrwAO1YpcActX78v14SNmwMb3FfJNbWvrLdzjQxjujYFjj0h1K6v9bH0JX36+g2yUAsD8kBbSxrwb4R5UP25WJf5bhjzAU/xW3ty6FuN/yfjiQAxG9w9up/rSjCsHZdqO9ogNUk9Rg739V0ncE2mB167H9FiyzIP1UEHIzsCZZRf4hrME2lgK0TVIrZjgSrZOJLegE2O/oEtdEO3UPPdbKqZXD4JwDEtQWScWmNgbbHGaqkBYljkIY1sAQzpHTpWK2zpZtLbg1Y11SHxM+0uVGqd9jOez6W45/k1HFwCUYm2LjHI4z/GJEs7M+OOW7rfmk7jWpaJRyn25rmgMfSJyMd9gXOengtpUtG3p44XehGvj3kHe5pHLW1grUtJHk+vznHp13/p0bqbiiRsZmTOprJNCxF7ClPs1mKjyxc+GNRgsW5NnTKNhdBsMS+w9TYO1wGImfKvoZPoMJNsWP5aAQrLUhxtod5bAsvxXSwMeZFjxIHf1fORSeMPxvOxpKgmtI1y4gKxCwt3B25pu94u+I7k8oXzyzmgKwAOcMSiK58m0YylrR6zVGOL7+BKLEcc1BMICUvQbGZj4JSrXnuKfQd5vNUiHUqMrdDhbaFntQVLBY8NU26PPoJ5pGHYZWGbI1EM2PL6ILZKDl9vPQz2X9OBkSEUcYOrNQBtRYijGVSkk6TRx+CkqYzT3eNkqQ5hFg8Mnu5nzUg5zo53n04Jy/Z9eqGUETJk8e9W8jPWknj/CrUWdOwlNyYI8aoxNuF5xsMDVWvLvqJSx3ETwMWBf++Z7npjTCwjtqJ0mWXB7Li6tDclg2/rfqKfycZIKl8KeiRB6blfTGj/KEs1lRKNTl7tqnyYHLc/lonojXr1bRKR3gqOdSU/7dF4J5/afUU+qlkdPZD5vb/T4mabuc5qnQxDu7+WLjQLoRYPTwo3FZQsqjxsrYaGZAzrBdkAtMZrN+UJQaQUW1HZDwsSaoXyrLuyelQv4nNBpgTcDuHLYTrmO4mpnCs4EcZVy8rGg9gdklu0ebi35f+L6qMlY6QGkpbH/Xe9ZpoC1k2k9Up5VamhCmJ8CwTvStCK2xfpadQlX98NrTrp0BGxI+vF5Zeb+CT2PW0Rv2Jt3X8ZKEo7RVi9VyiPfbYWQdMbd69FFx+fbSnK53UrFsNYomC9m1hmXIrqh9KLPK5o5ib57j9MK5TZfhq8jvNNgjlFtIwWlAoyGHkdDm1xVxBXwuqYAJJ9cHWMDN/k/s5UJIh7GjiLnAktTimKNw1aFdnDZUMz914oN7/PxEMy6umhrpujJcj6Ee5WWLc+5LVZNADEFnu+1uyZ4hllPvKek27k5QnC4T0PThr1742tJO1ceahcXcOKXmCxi4CAlN85CLncdMUCbJaRWJf/ITIttifhyGCpxLI9MSYkmjj5poU4UYUE2U+yxs5+ixzOpC02+8ggSRggUlqbHRHr5/G8hquDdvlcDizInUfhPRDgiKst0IKDE4kAVpop0W9ZjVzy8sXcPovaqGKUShVYVwT0KFipjART2vEhGHjbVVC94uIKciKJ1dTDCX+q4JotaAMWTrNtuRiaSBNPFcazlx5HCMDycTHrq2yzDBV+6HzlyWfz1Ozp4n5qJ2j2YZD4CVHK5/euOjmf8SipVYMFEyI379ZETelNf1CqZBI6410/YHWIxJsZq5AzaHEvaUP85zyxB7WPNjfKrxe2K+KOyIEpc6xFPoOL2DYqNfhLatlNe5q94MPTV7oj8YdNxHB4CgEEezyh3BM1kNyyDJv2qyMRC3v2ja7zhvqjmRVJx//f+v8T4EgRvgIZnD/oa6t9gbswIf1J2iNdtsZaO/MX1I/EdCvKfbdE0U69QwshO80slrAeSSr/ISCIgLuh9o58qSjNqX9D3bMguzPknHn5Fw8ELBiDUWE9n2/auRjQeTVubna1JVxS7FZDqEOwZ4DUV3iXky26Nw7+xU8Y61m+ZySuCOxjJlsRAIrERYTI4QhK/yEGN2VKtBXYRbJTkvSK0rwvXnYcT0tk+3C39ANXwhrp4UKoP7Jaa1WMKRdK1nLMiyBNp89NvzR7pb1EtiNGSFuzsMRG/sXlnKltEBSub32/TvWhz2yUxWdMRCLJXrWYSuHMBsC08S/hbf9uK3+E1f1vpwbqS8CEJduz+hKZyFvR0u00YAKB8xp4VKxIllmzHXmYXcP6Pya1eC2VBdLdxXlB9ot4s4LUjh2Gq9Xan6QjLwLsITV98peRxPBe4fZFhvPdV5GbCl/YLOGMkSHqyme2e+vuxj3XassqCypW3B9TyzrvMkt0uC2ZRrHzPmVx04AmjFgJHmSB5jmqhn1zQCh+HEdM7dR4a6Pqojf/YE7JY0ZpXUpVCrsPZluAWlJcJYsJqHniYMk/nBH7jzNsw1N+3zGUA4myFfQXcgN/nHGGwNYCuAYLwbKRd7d8rIqqvdfMUBdCQWKaxvqqyVyNz1ESg7EB1IVUQ6F0jBU53L+soeWsWSNhEk/S6+v6nj6rMXETB4uffJRMy6CuUI8ms7WkZhL5YDnzpy7gGmm0yJ0ihhgzCdwMTEgHKjY58ub2jyBqgcWyBDtzT1a0Kq54eVyz7Mto103NgLn9ShJiXEFonvmBf3o1hG4X2YAYsQNBHfgvf5+dJLeGBubmAdZSK6pAYe6ikIL/ijhEDEcQIJB8JJKSMZn4Xw2UpgvGvzmxYFg21zOchCaOBzwFpO+iSI7+mqWU7OQw8+6e2xhH7rxKYB5TI0QFGjUSpAK5iZzkSBbk1iM2Eag5jRytvX65NviR6NF2hFCfmhrNgvzCX5+cLqguF9bmFigr/yCxSYCg+V7C3Crx3w2mnblE5t2fSvN7a+tbvi6DYxVk6g/C/r3mGTDv1lhYugGGmrjDDgteUKWx2ojGGU3/hLtheHcIDc0Fm+bqs8g03oXtL8ZjWIO0ZGSBs8olWMzcO8CkAPl2Wdi/L5coM3t/go14CLwv/0qZ6TLkE3g8AgSdFI9kBcGC29Q8BbBtvR7C6brqalTy4T7Nw0iT/95OsG1vH+9523stLye1ahJ5i6BSiX3grDHyjjPS2Id/AGX+i2eFlJM/EitK7QaGYovCg4N++hyDIoGCOjaUJsDf6XN2hbEjrC88vaZl1IiVLlqohvtq6K18M6YiskllOFwA5rWG7ZtNIfNf+/PCiDzOjd4h1EdQbN8cveHQ8adr6aax0mfabY9MYgyDuus3fJxgnjnF7p6/XxKwkFiN8THE1wgZdfUyAvoydjWq8ELUHaJwei87FMSh4BIjcnYh9BtwaNzW24kD2PA4gWqx95VH3VCpnKxbs7FeFq+4cRjVFovhlFYd8GsQJfAZB/UZYg/fU+CobAOcDhrYQS9XDyhdd88YI1uG8Rz26wcL50Eu/f+dGzNYLZ9VLxru45es+CfjmXih1ECnVq/900JLJj3GdRwxVP8dn7rgvWoBOXGb/KSWXOP/Nj4jjN7c45br/q9O0zrY2kFg/UMc+aXb3LrCYQkuwjlU2o3H8wz6r7O9z+7w6IDCDUqPzLzVGwh0P0UTsLg0VqLyj0RwNC8wph3F/dQAKPeM9XoBd52ZBd82+sW3o/kdxYDsJdYP3BXtoTkle0iAzV8riQTkUymeUbowNm/cYvckH/PwEOP7Dr18CYHkn1oiRwILsf4RFrC4ZEgHAyo6GnH+gddJFxDIR8l9Z2sGUEUU3RQ2UR2dYccNhiJycflZXCGreabJAka9eg2sjb9iQrY3DXg5tO0HN7DItWqhMxyOH+V8Q3WSxnddMG3qa8xOR9YXAAuHdib+ZBFPqrF2BlcUaBvCWiIj6Cw3UnX/L5P5a0Rj1j8e3dufotL0buKCGvlYz3d4AmRzrFwjpaAwlm7lU9d6B0HhYd+NFjycC8b5MVU3Nz9AfHbShibHDE9MZGVjRZlcao5HxJhMHIriHcabV7IfsLaEVWFFUN7rvr+PEfcjgMMXQv3BLrXS/2kO3w5Qwt4zCFxX+g1J+s49ht1l2VofLebe2D3OAX5w55iyG0v9s6zmlHTts4YMA6X8COkPsaI/a98PENtk68rAw+ZIbVHOvBTPAitwv+nq855mf1bnKuN3aSoSbSr/7MZzvH5gaJKmCrZdeROYaR3qX/0f/Hat/LhX4WwDGHV2jfqiiVqjswil3YZkUP5EOt6yRkkp6WTw8fTDXDC1Zj6fIb0bKNSLRj1KSj+SsN5P+f/D7GMAwg1h7LLtimpIsP0QTJ1raePPj5binPYxg7xrdJ24FmzGKPOO7GlMI8GcJ24bNxL2IX92o3RwEvq5+ZDTCRBrjar1b/NauEvnOXfn/oZ75S+mvBRjBmU2jzlAj+k/BbCHRTGPJA8jldtJxHr2RQnZ/8Byo7jQxTfgR0kVpvjs9zhtu3WYSj8NjcVu2jnVG/AZa6MvPSw25E/mJ5yj2ZpXw3rpg6+32BmjTrQ91P5G4Eu3/+ZP3f7UyEuBsJW2sv7//2kPJP6Kem/QlO6dwAD9je6divfu1Hyc9GGW7o0As3+Cag4YGefgsTROaIbteMq5VLBVnctR9tgF5Z8nqq5R/XXkPhWzjTjHyj257X9XsC77Un0IYGPn2Qnb1N0AnTXax6R9RXlYDPegTDbXhzt7Rg2sXt5y23hHG9XvDx6Zzsussq5ZuLPeai2queckiXW3JkVUUXZ+f6wT7I2YKV6sSXUlvio30C6/xK7GZKrziOlfxSueAf1lITFE2CV7TzTdpyt4vpbcwy5+DSFhM5SrCr2D6T8lI6iZts5ErZ4Er6V62Z6Ru3SRCvxdfBvlWdSvyWy8M9egAh91CvznZH5/I4I+DAX47htxDsCWDwBNL/c+zCmwWrg35obKXaiKSQiJI5jT+JC0VwKF0nBenuEHFIJSfwoNOqSuep7+3Nv/lbZ6P0EceUkGH1gMTykKkOWL8PgJ9Sg/OCNRiaN/yDxWpCTSTZgAtNGYfcD+DFUDo17a54/9Nqq/9vIIlpl+30UQg2ndWnQQneGEYMJd1m6RGpGYrO52OZ7FDNa9mlbkAef/jRXVsFvFyBnSfcjTqvsLK4OUZUNjEHOKd1AZL/Rmetv7tUeP4PIIj6oc9Qj7E/Ro4v7h/mgD/s4pbFke/E+aXeREdg5gNArlhbeH0Tzg0fDoMXx3CFSygMZyOg7gnh/W3k1O8CBw3kEmVs1xA5HRTTBeMtOd2FdedsRxppNXXWTVP7aCzRZznIRiu4CLvPiyHQsFSwp2e0wBzHZ1lVG1jRdIoc6xCwCwQRspnyk63lsiXsuHrJSK/ySGsf5baxVo4p+orzG6GYWGiYk7bKVT4X9qbmRgIIBqfGmPbqDpLwrIF1bjWOffrCrX47lt7YTlm2vN1yocxDdRY3Dgsq5mWMofThHimEoyvOJEPF1SCcN9yMOUFmWpuVpHMgDeQCBcAqQ5ngXqeOEvYzJfRCvXqJ/avdX+SUCA1kjCVsZN42TjouVmF07Sv6NemBeszR4UGlTC3ijyaJ1M3AYbuCk5EWVlCwpdFA5W92acB3WMpLm7HWYM+rIwB0j/Zj1SzUkTfvGOYM8NWMft86f5TVlNV+6UCN0kfYCbRryRJgBLs8En0ws1y+Cus1AxKMS0PDkgw9FpmHQFxPDxUBiFhFSA9jT6xaaFkHZNuV1O0c9nSI1/xnB1eipNGIOGjljJaRXH9XlOfKDjnoYrcuxx41B2MG0GEaq3Dx/TO239qCPI+izcLpzvaaM1Q984OWmOtYwrUBWT+9OCd8mQyaKuK8kcENfjjjMRCB2NygLjA30Eb8M5v18Q4TQQY2LjFwRnG1EZPmda0wskxFRhXzi9a8D4Y+QKUZm4ItQGIsg3jSRL+Pe/6Xzpxf3/kfs/e2qZpSW/5eAD/9TYsWQkZ8BwPUxyERRTEg5xFrCr1/fpePx2qHs/yUrMzxTglUALMzANyXnAFBXeYfUwyyG/Assot8rvFktnH1JVidGsBJWBo7mSmxlbAa+do7NcSbzky71XKzHupl7lFvO/KvVVJcMwQ9E+M5CDTSWqlFldTX6Z5CFc2TgFCKI0wWY86ctlBQdT2n5+ru5igi99tPOb5ym3HXTVNv2UnCwG98Py4nWUBW6LG5lXCCbRDzzkQ7qxmF3EINSUGkNE3sd4AEw2T7N43QP5hskM7HzQZeRweEhkB8ZAr6TEQKdxhBQLw/BtHVUfz5wjQoHpIWRbFW4tujbzdDC+WyGmIJsF8hcOPiGxVeaq5pBJF7CZClIsj7c4YIuW/935VwCTxRghFtEJ4a77McbmG6acuoUnwhY1o4dH5E4DJujr1tOHUHcuFqX89uthMVGoqE8XpsLr1Ym/3MohGOW79yhz83/egW92PD+gywk4IwLCjGzwgJ4WAVG+GYSfnxl65fpvMDNolNi869ZxyqBPasqmYjZGqoX8qnoL0/il6movMux5fQkPXLjeyVjGzPvHgwPFOLfTfs/Js5wVnnb19EMHLyVwDj5GU8V31iRg7YZAOo2tAdAOKxpimoXAnQc4EQFeg/1nDL9QDFnGkLeR27G7XmLT+3UNeWea0vp9/pe1ff/dgzdbYzlHplivn1vbuQTk0urP2eXotFVixp9dLfVH+u7ID80ctXPlNMZZExej54DvoZCAjsHKqhQnZIrxNqT110T49F8wTGbxEBWZlCIcHMZ5c2BZTFXCmKpktNUoty+GvPykHaCWtSAXAzNalNCVGqdp9FZBgEr5pysySQ4fzsLJqcF1aZL8sU5sDMumje9dgpwsJjqeZ6rD3AO8b4eQDYgzlnpaMgTzT3E6QQjVQafR+sDY3MGAMjqQNhDkGL+BbhtpYFXS6EDEX2jVgSUhhmMsuMDEGckZA2JR9gDST20olD6W2qU1G+pqkRSm/obDGTyWg5HWUDKmdl4vMPg3jMApwMnmzNy9WJmwmrxuRIbSKP2RS+05KElaK2Oh2E2AmFjPNOWjR+c/eN6W5svDuCC5DrQeFvCD/FmOLxyz5t58ngLghRHCvc/UUAsigtTMGdEGpYW79ZGAUP71NK2iHFyjGWe6x+IAMEJ07E9wp34GgUQBRBBM1+bEG/rtJqVOWG4CUOGp2+ppfD85J9VrNO8zljBETA5oNyOe+2WE6drcfiduMHQaMBvTczzUmEkD+iMyHXn4vkO1ciADQDcsQAnXibs20EDlgPgDNUosuNx1VDEeHWtczNnAxgMYd10mXdmRPNLlDUzNyOLYb3jTIjaJHUL4YQrJ2Rh3ZS1xt5ge53TMEgAqAGTszAneHGqR7n9LceHVDQi2sjWmPui1m1jM3grPLc37/034v0Gu9VEKCevozs63baUE2I5cNqUZooJBYsC5WK7z0yQZcpt75mpZhQl5d0hsoq1EVhsiwkGAOiQ3/aS01ZiTFyABKMxWx567SIzdwAMkNUOMoME3hb7p9bnupnRyyZ+OhdApENG57m6D2gRAeAtDSVYy0Pc9ooTRkKps4iQFkakDDvtojLFjKWOFlip7dJayix3btOOjBvJ2/MA3PxCJ8Qy5DK7yNRSciWaHQp0QjwjZkeCTLZZJgBXbJdQCRZxARQWSF/X79IEWFwUuEdPXRiBS18P7zMDJCZIXZuGJVr6BhCm7bVsyBwy1T9LqDwcqATxXVr4b45ocIdW7Hnfc/l+sidiL1qMI4/x4DHwUJB2SNrDCRm/PY7jgiY4Cu2Y28CupBFtR5DzdhN1gSfPdMwK09Xws0YWSLsBcpOwLup3aesb9kBF1obzH91L25xTIPVD9Tqza8IIKHUgUq4GgKgGPAu7iVoJo44GzObiq06MZWbhoivwAGDI/YutmTQATt6/yAoAAAXrxdVsPOp5c1gbxdOTEmN3U7beyqzs8T2rptsnAlJG2kd5oMzoj52QAb63TonT4z5A3YtccZEIGcxIAHaYFLCvdSdlDjo6NBRqtqGVjZI3MCo2Xp4zYmWTuGpVICJk4g/ckLEeiGlgUbBf4WXQwNeRdrUb7GmqcN72rO0DflBmaGNiuFP8xvjJhnye9r3bePTw/9Rst4sw4y0oyNGLbzxiZnLSwvvO+fZ/yU6o0jW1VQQvnfIauL4GcXy8QPGPcWRzZfVTfVCIJ77iaaEOJOxirSwKYkICC4TFMuSrdc3dBK5Ci7u7/69n6jzgyysCW1gQTCNJlV54kWI32HGgZhe1ku6RNkvOMuaFW9NG7O5S91Px7QlhYN/KhKzZc2ggL0AL7bk352gVLC6iiDJhqj58DWkckO5ZEduJJCiELE0eMEYnJiMjcZWyJLkA/prI+zVCJA4P1Tk/HJTkvwb47TEYrW+/l8YaKopDdch4pMWdzMMojibSB2WVoB64WwucXVkSgJoH2YaTvvIx1HXfHd9Hjvsz85DcWAVBFIsyxkQJgfsgHBJHqyJf4JnEyts2LwLgKwp/IZFmDEGc0psSSkubTdawSzb0YdVvoa7k5liXhUqgrmXaMX46rEY7K3F+cbLLc1jPUjEHAc01GYkhEH+5GNsMdSD0eubjKXBsBgDc2LBFYuq9HmrmH8g0HLDRhxZKkapaC8ke4F8UOU95XBsUvyWURtGk9WQGuiYt5RZzSbkhZljmglTF0G6bfzbQ+Hyd2uyc/BHmnNnuXKIiIMc46nmy4Tkap3pqWkm96MqFrP2O5ecTh2DwkfL8STZ273kG5g2SOWg24hysGyxOhA13Q/H52s7XGwUzbMCkY0DD5qwMatg8KLDhwWDbPvzGp2KvemZoJjj7nf/dbSXHVYsdA5gx2SY+h1/Z+XyyWxqwOWd+9iSAf790biQtUCeUxJnhhp12KVzcZAfy7VqH5yIArfcA7BoIHdmkGgIpo03biXtP9n/NSejteizFmXnPt06vD+zsbdR/uxf9dfkkq71/TEd1Fd7J7hPipUL43oE7iyHWnbMbRruLJWd/n1FDLTTxtD8h7dtaA8MovkToU5PtjnnkAXKPYcFM2oTPV77rVcwEDTjaicZI4Rwz7xKeM1c3yzbN4CNye0HxQ4Y+qO28WsRMWPgIpw1yTguyAQcRZrtdOGsLMaZ8Nk8k65ELaaF4sYyUIxE1oIEmJSGKzHvUCvQ2DVRAe0H62YnJixnL/Ua8EdoG1/eWWAKHsJrAZVNKJCmlsk4iQtH+B6Ro9xOAnaK06L1mmQFp94qzaS0CmM1jv8dAGDR2IL2ZD2SaxKztG41cavp1gmtQb5bqyaM3w62lydD+F2/OyhbzAMt0rUEnI9mZOCVw8n36Nqi0EZHviB8PUgy4knq5l0mtv5r7vK+44H2Dcr/y1vIipo6cZLxldNei3bs10pMwF/QU85Eg3hEvhxNRok8blnPf9lehIrQAXqEc4bB3lkCD3XqL5vAWLIWs/Hta9Dj/VhVfFiGr45nTELSU7C3/qJTSBxaExX1NFLjlCNRIxk0BZjJqu2+44hzOPtGgc4AwRDadKHHUv1qeQ7hVFv6V93UdFAyuSGArOZqnsMxHx+wFaNYQcaFiAV+uWIN+4PoR/l7Iiyd4lfhw1xhFrIsT95uqsYZZEP5UtR9e2xSVOx8dulm4idZLOwMFrzoWiyO+I6bknOrmmhXFFswTMqvEX4gPX5E/YcZRrJe1OWaP0wK4FkhglveA+zJBEk3uEbFUvS73mDiy+UKuiiTQltw69dxBWRH0iCHwAEGVj6W5W92zRG/hNMTdS8i2Vm1/rZZUgUYrHiW6P8mZEagE/UkNOoFKUGQCjUdIVOQXV4ngCcIhvmwqIQ4URV5WEUKCedfZ/xTzDKgEweQBA6CIceUHXkxWE2em0USIV42N9euNB2sYDTOfErZBuXBfgvvSUt476XLrTbuZ50DIox14N5fPYdR5lzKl+4Vi4BEQHavRMwNJLvtASdQF4r3K2flfPQ+DYeYzSS1yhq+wd5WHXp6BkqoLVnMhBFjeZoKCAih8bvR6SbCw9KXXNAXbZnyYA+4Rs50aMU1hAQ4GRc2LKFW03cQCOZebUc53kOVxIVVngp8T5ijuU2aAxwiM/OTbvorPW55XykpCYqqSLduALba9xzyj+Zh2kxssNQllfy8sPB3/Sm+H7anuJ2LOIqjFOh+3ObPbAI1qCnvXKFg4sJJ9pgfGj3yDiHVOovAp/SQda8jFL/zwvNKROUYKR97hsQc33wGKFSgLT8iwUCwyrLrO5tSlEeKZp10VJ8Ln0SExkvGf4Jr0mi8LVSzMPnW2xUl76qGCyVif3seHYm/ZXjIwFQGkwKnrQ7M/XXExTHxpis3YlDy/2/0YkirXhdSMamfvjVs083w6cHcwdMontKjdySoBMkEYl2nNVULhcHmKGP959/PKSHsVmBKigNxHEkI3IceJIXW6Xs1JIKt+OmhEa7r7FXOoL5dMnMkpJgyVaWPCcHziFKKl5YXaYH0EK8ggmboPo2IVpfr+wrvo9J6aA8dO1CguwfI5XOymrXDer4OV+nspjb0XVJLw93Ajg0/ZqPR8LC7mTmS/8rk37v3as3EmJwPIpH1ngsLID1U9FsGYbUphUF+k4+Y8ZhuO2YE7hyLJYdxvmQfQyjGFa+Ro9dzAmncIYUioAqSQtcEPIqBXBVaNq0vqV6nS5fTKvYTQZd/VM2eM2iS6ccCVMqNvsaf+kdah3d+ZwEwo0yscC/qh0FGvxddfx+PANq5NcO9Ab7WQ9tdKiOt6BGENENyyzc49UfLrDPlCMsHGRW8zzRXBrSGR2yGwMozEvNkjdGIr1wzlgD0vL82dxi6+gsVpt6HYelL8spCpY0lFubGpk3rUYJXUGFUmG33/Ig8sqtrLFk3V9mzgsUak3JYbPx6WxiTjfAEre9KA5hdIpuAzKNGuJMMP73AIorNw0Iza2h9LfJ5zEWYdtFzzoXcGo3lJyEXxahevdwBZst7ER+HYUpX5bs8ThS8D4mVDg7CdmQB7eP8zMt99eYLHx7sGWYnmaZFXuYofCWFQjgwd60bl9KH65fTYiPWzKkR1W1r+4C26soBW1FgASFkD1AWdgtBhL/dhGlKp1hmF1A4FNPg18CCG6Toij0cO9/20u2zyMYS8kruPjzBSBcpoPIFpQlmqx2jHfo+aGDFa7j0CVebXN/CyvEWvcNA/20T0BP3xiHE9uajnJno88C0wMt+aiOSZYPCIHr05zL/ZI+eMvqzyIM7OQPiOt0yen9P/RlI8InugAk8GKIQaVytwiVAgQBYa1xz+K79uiTg8Ald/xGzkUzq9/1oLxT4roohMMkeVDrUNFYqzX6TeAbR+hTm73F0H1ZGG1zggLVH9gGpEB5UmUjyD/1QeS+5qFkBtcIi6gS4hKI9LetgXU2OLkrqS7i87WHsDmzh1LsFLs0UrtyiCtL10PxOyMWusXuhvdGaGWUHfzXz3ZcX+56qI+ZlLzEGfMcUDj1w/kebJ27mHP36Om2rwE0vBXQkGoCr+1XP08fG0jwB1x7vcTPQAkoENfnYXXB4fkknsxa23ZE65TX+f9PWHwE9WXNUVsw2mbcvTDhX3AKKwGgLITqNwUZd0qwdZnppfYEIY2IaTsRw7gOi3PIOd6fFnaOEhw5Uwv3wm1rTOQ9lw6MY8MgxD1HmCoW4clFYbZMKwoWXl7qnBe8iVYKkPCh/5W+mrMpJrevw1jQNj/lTumN1FQVb7cKgAxbx6S+bpvH2/JARQR8zc/wUDtUV+S92hrihFBBT3v8Y26DWYh9YpJSr3JzC0idU2CoqL0BulZjBNsX0xGxxyYt9VRZihoQ9E2NZKXL3kErt/Bw8QA9NM7Wc4d9WIafmaz0TVF2y4YnjwCacVCQdqPs5ZVA1+XA8cq0lDBruEeihvZntLz6vEX59kWU/efTR15W68E8g/hb1/1hxGfV5EIV4jEOmAn0vL5NDbpOQ18sUX5ML7IBauvNqCNxTRmFA2GMrqTUQ8v8yFYOncxLRMxTgeT82CzVcvswBmkVAYo6vSzFBKeCcV/mXhy+bdzC7PcI5tnBKcM+RGCf7vtKoDoLq4LQN8yhfG+YRvbBh14S1Da+w0f3fEsLz/g3r8lyvYHe3ecx09k1iYH2pAyqAauVua39aKRSZuQGNVNaYrtPDfql2ipAx6xILApr7OpcqYPVITV1U/Vy7+XbXC93jYLea6qxRxxRqNg7wE43hbCErhzlp7XIwXSw6NRUPM64wLEB6DFccpidjGQOZRLna6E+viLKTTzbI8szurQqQjKeJS5AsAsXt/7Omeo3y0TUs+AS8kGqs1EogZhop7KXGcbSMvORBxYyE4ZVjiDpfdLcfSwyxEU/TWJ9PoZeURVCXdnYjr+4sW7ntpYPyzBSF5+gvLDXGj7rHLxU3rz5qRgv0kApNb1v92GR4rhPh3XSKRpAmEpgwBEJqUNYKSrowYNrAJkhIqexF5ET9HrLGQ4xsQAFFqeD4BLI9yA5P8mVRG/hK5arZ8151tF7bP1hK/YGceJGIYXv/PsHW+vRYwIOKUw/fqPpjteln6AJfpWcAM7bJlNKkW7IjDsGeBnaeuLuthE/qPKiUq2VIpC6Ubzj/1w2JJw037aqgbslLf7Gd4Nvah9K4c0d1QnSMCueMrCh/Ole2J1F67X6k60RrswKt81D+579/YXVDXUUNQmlYJLdoyvosaf5mm8W0GOn31DfdLIKC/+iTZkADA+B5g72XwGWQextd4ywF20K1hfD8FC0l2A5j+I00tDTfLqm+UtMPm7j6NlbuP4CWIHUlEMNdppy7FEQaMAosayNn6Nll06y9Y28gXYp5x1W7qv2OEk6Du9tnMDRxctKD9iS9WtJl4xEmQozO7I7uXoVmQFSxOcAWGzSE9Q8yzYouhpr2fibm1HhHSr15TWxUV7OpyT2/EgV4iscC3fXxgb/baaX/IdcbnIFsx1UipwKO4rw8QGRNJ1tB01dzMptJCJNsM0IVLi4ufq3BJM2O0jijv55RqQrCZREZdW9+QP7/LeUzCiPN3wqyX9xTxL6KX22v0SyuQcyR83a91YpRdqFyM0tBIiXxTXIBwK0WdhxrzdszcwWH/LzL0VxzFVuXk/VBNQI7WJWXem+Yw6j0h2ywEu5RoVBuBA4ede6wZLCUsgLqfWGB1zSFiDyXRtOfnSpal8iQquvVlEO13NGLeUCW7FePcUcJYFEaJ8w94idDHw01U02s469Hirschh9F08FCdKQFHdCSTOyzQyXpAiGGUxVw86z3NnLk43orZF0Fzxw0zRDnqyxxcYT1i17Zf/+tpE7+lJC50AmTnO9Ds5qh9s04/KlZqaHEx/bpJQe3Lou4a41tuOdZSyOt9wDzGEg0Vur+InPmB3IQebDIz7llzmbG/fFbrk4jGB5mBF0p/NoqRzryiyBt444Q5mL+x7zkefBGmvKtmMYqFAMNngNLIkcGYC5et9XlMa5vqqdZakXnWnJ4PksNSHL8uzKBR9wIIlNEKzmq5ExsNjP0nUso0A7FU5s4+gJJmG+RTXGH8T+6HxrR4w70XdF5P0XNWJvtTpeT5+WiPcSkJI+JEq/KZ5HXmLRRN2QFPB8q8FewBezdIqqmPGpMDMHX6Jss+F5209AFmfdzoNoFKx3CnmvOpo8P5IPFhqfDy4ZGfQbtzGgk/m1w8mt8VojGwe2RXXO49TLNJLhthzkh0GHdYXLkBPMK433Y5pLPqpNct1bKZQjE+upPQ4KU1nHz3NUWZCUari03L1MJw7l1lVorlQM/aFeJtgAEaV9GcoO9p/V4EsBiVEaQBxNBNa/zsiUkctH0rKocREPr1HIUoDjE5FZBCs27rHh5/rvJ93+8n18lqKJQIn0C1lID6Y9+ChDf7wFEs9KptngHEI+G6ifQBexRQmNvR4e1qx1vbzLGL3Uwg7lgpMRTpP1K6ZOqwu40u0Ob64yAX6uqeeYIT87B0AYA6a8WlFwUnaG0MF5wZPUtZu9iVJXKzZcmyhq451GPD34qnHcsfoiHKECYb/HwGOdu5GECKPD9ktL9cQI9i/QatRyDrbBSgB/EolXzMMNnuEnRx1QtHz6E4QkuZfPc8BDW4YjrwHfqOg4dfenIlRnpjnJmn6UQHlkT/saPDjykC8Al9Q4IILZTY6j4Xbr5hK8EoK+vNzto2Nzgx06Wch/U5RrYPfFvOiv6nk245WRbyaItCYB1NSQg3zWdgd23s+YNNLO7ips+fq8NX6LPOfrK4Va6OcIlgCk87N9WhQyfZj51h/v8KpMO19vAP6h20BTPxsEgBxMQXc7VfM40I84EllUt7DKQ4757AEaMaXPz2ujr29Y0aDbquQ7vPEyxPcu4SO4ZcWOZ4WcnHAezSQPFa142+zVm6vtvEEx7aexd9KXZf3uaf/IhL4wxHyH11sJlx+RqdfJTLTQ7VRYRPwhIZHvD+yMOgGcnf88kfOA2jgNN43G+4Zjl/W5egXnDDjib/fsxTapzNUpbg7RstIlbS279UAQVHg7xrzI6mMqpD9x8IxUN6Vr1/UUNhHDMVGHtPHkBe5ifemTZbPTlftXnSvqqK8Dz0MyYbJBDQTCzW1ryELCJHMwwTdxj7GNRnYWCGU54akwz1xPHNj+R2oPod7V/0yeA0eYQ7mmfyk7wVTB/CGzJ7POfh/ZvwIC7zJ34cYFiLXnnXv03PQ7aVzcgdti0MuPZYQMhotN9Q9uqv29CeiEkat6fcKRmDvdGWDTM0Zs/DSXSCbtSlv2yffiVFTnwsgMQwpBbZ8iRUKFVR/BHExDtCn+szBfZmblqWU51wmK3LQafRo/m0XuzTCp/2YQ32s/1wMMwWAtpDKvkA958oCA7T/wRMi53fqZfklKaFcYVFJXM9iQnL29LSnCbSv9klmIcaLc22QeIA7oGs2DGT93xj/FhxOlKCBDftX3AjZiacN0d4kWXtXXb31+sDk+62adFPTxTn+m6SbTLwu5qNx6TrEzhR1prLPFsXS1k7nieg8Go+v0oA+xVyEnw+vHBWk1ygOXmB+HgUmpfLDHeYFGfFyXc2J+fPkt308Ppf/pFlksp/bJepkx2kl0XSyA2i6jdg8F7RmbtSZqhthV/O+xfGXbpH7lH3g+LH1+6j2Ok0yn6PznpKUkpkg6bgMpq3BOUd+VvHTbjDrO8yeyJ++4pzsIURRtRLAEKmNQi5dvA5O/TJTYKrWYwQZwHkM24erpZ5MiG1Hj8nGFb4U0VCjXpgxmjqeqZSCzD5AYMwnvjRNAlf/3DtGhSUxLTyvhy/uFa2vcrihn6iHBkF9xFq3PxofP6ie7fOQeJQrv0GH9p070Ww76cYkczi8h9O12C4qu/O+n/XLb5zNKJV7PvRjyUnu1opp/mbmL2575GAS0Mrg75wq933SbejxbZ2bErvRUw92yGOIB7bPOV03ZFRnPHVL/Rcc/vunQ5o6OZngn7/s8WhlzGNTBlievj+dJAC2MImK/cxHvE2Ft2WEiij3LzI64mKJclywnlmxqFpp/FtvvROnyOMNW4w6KPb0ubyjXmCXTkuNMqMlWA8R3RDmTBmfm8ZTgFG83xBullGHEkFcvFcumVCB3OTLKjCHDPbvQDIFVU835N3Zy2r+fazq+LhdI4RS/QWyJK8rJtvGnaEO+VLa1qwPWeNi8yFsxUYimO8LkfRiE9kZle9zTB5TC9YAmM46WQwuACsNpOxP3/NDLq/kV6ip8oKClQcqVhk1gSItbmBupqoijPiLyaCLdVOUICGZvcX1ZMt5wqkx9K9lKmLLmzVairAhcF1Ui+m7rlTTTGGXjUAiLKNaLinGmlYUGuKmgxmIuca7IKC3bglHwI78Sj1e0Y1f5A11p0kiqphBGVODZiiCTpLS4YkmmiL4larjdNmooFxp3BAPNVvLDFkyzfYDY54a2WKDZvQsSg2ulDtBqb8dsE7JHMO2+o78pJXbcAfdVZi9s5KC3g9ZamolQcIkCtAfW0Pm4SCVxzlmrf4GqT+u3D9Fg8WPH51U0utK7zrD+Z7817phDtg34njpmB8KMGTO9P8vyMMLKMTq/7Ze6dvfSkvvv5thmXXAGHrj4dKuVICMsAjXkFGKE5H66Eucp7CZUVNhZp7uaDindSzA5LXuQVnDXtFv3Ib0Hz70SUXMuVwkffg4J4gjurXzTvyXZKeWkx3EIZlIncTcfkywjEUy+K5zV9D1deU1ZOJ+Yrr0mbtMg6Lm92QxS85G9c8pXrsDpH/ayvyu/PXe411VzSxsiQWALhf5ioNHuM5Fh1XdgXc7mGOKzPjKYi7pca3sKvfIS+3i8A784xK3J5i9zN4MozezPO9O2b4Hkos8cbpPwuLzI/ID2Hywdn5vI3G+vJ2HF2WrYwGV2FLdB54Ujpt8fX3msV6OdlfOWOp4Ei4Pu91g/u/FzQUFwpWy519lz4d98jL2mXY/CUt3gUoHdFqh9Vxz1UdpbRdXLc7GXdbNPJchXl/zHx1u5/E5gUz3f+fC4OfszRarvNozmPwD4xNPHnJN3TsCEwbfhIYiGObmJy4Nca7OXXZ3VB3aIqeQVL7Ix18zy6XnhvOKMazSTG60p2i47iGeCqzgOkCziMGDiswvpKvDSdW8wlLf+Sa9u5R63AtBYPj3hSKLbHIPKw1xG66xN+088U+no9jDhslq7fmEfqUHhyO7aSZMhLs2AB3YXNFhQgYVorXuLcGvnYh37OB8zDTXRo8V1tdVybeutA1inpR/ExU8OwY1WogTwjHn/gWPwkPnoD39oXvXeUDvkF6PVBr+rNDvtZN8sfe+Cqf4BgWKRgjsP4AsWH/iNZLw9/ndEJrIJH7bHINGhSqWvNBC0ZzG5PN9YdTsC5N4mXELP6Fo/GTvBvnttB8zyBZIsFEHAWQOP58jaJym48P/cQDwWgYaTMZUtE/c3IfgNX9K13HOptZQyDheQhZOPVgmjmZoC+2jD/QArEpHl6Nb113/zYyNl9ZrzOuardToezdEahpOg2jV3rHmzVI0vlQFbHRZqgrvvs1fx3//Yz8vw3JIqYALNX+WN6jz2dDVnu+648p+/91uUNkPB6bSZLJBJLkOGDJ4YWts8klmUsTOQI3cg46x0FE5oNx1pHzwRNyIs+r+RlK93GOHcN0vAKJhahCSTpoh5ch83sHoBHhjlr9SGe84tjRNhPiqK2msFFOh39y2wLGeELm27yGJBpGFPLpYFVeJ6k2wq1rMC1GEwOX49dW4ewL73Lp37/xhsHur0ogmPW/ZOTSRS35Ifss4r2SL9cPrRNlL6Avqdzn30Sxsp/dKJQer6xkyVptS7Xa1TsnE5zgkNCIHGXlcqtuHrKrO+PLW/imdm0xIfb9aUVuKp0Ns9izFgeGLJ0mAlOS5pwLMesfhoePcxVzmFt7syCCanWYMdvUmp3IgmtiuS7jxIgbNzuaQf7TeRaZs+KcNwWPohnvgO8paH7Rjpv3CC02GaleNHa0z31CPVbJHEu/hilmcgLpqw+oqBA32D25NKxYheq/AsgYJPf8EsKijcGFjy/CylroVKy/UkCrkmFmSTw3C7UPVRIz+aM++4Zszj+2P2PVZ6D5NdkCGxBcQLUrJ+hOnu8HoSTNWUs88AsnCv0nTHSB56k7Syer9DzOhAu9m9TtIuVsd1YVoFbuObuyRivFOCTHq5IWjq+pZLkmL9ussPFNXkAbymBnrGleLFI7CbLqt/LJj4PjdA1X8vMFphonHNbdy/7QGO+MgVR5UXTDIo8slsQ4SVbcA1ysHwcOwznQGc/bodIzg/dllNsYHoYIFpPec9GOYLyAlLNEyQapk9V0DowWC9BlS4rPvmx9xywr3qUz66a99TZ76x/m04oh9bWJRt2lqMklS1tFYV8X+T89djO0wtLowIPpXsDuPi8IEJf4iSPq8VI7G0avBc9G0r1pjh4zNwC1JjJXc2msd0yxwDQe8Vxr7TsUl+KlPpwnvh88mSTM33Qt9ZImX+ldL14bUcMZcLHWxLUhUDWmwinWi0n5hBDOsWLQehjcmy+eT8JPWMRsoafZK5RJy0lpD6w6eWUZ1xEiWOZvQF0zAiclKsjaAIxQ4T+fz++qgIb+d/asZjN3B7fby1l+xhhN8FVnHl8iVt5kzpjUxZ97RTiou2Y3Dnk6nBjlQO/Jbq7DO59mm4s8eeZZNfL8AHviIGYz1q/exDxzhwY1pVnZT1z+jfX+8eRNeYfGuxfioZ+PxfPYGLbD0LC6bx+Xw43FmBb3GDSYlWCOnWXGhqPQEi18D6jLlE4Tk0t/rsBDX1L77egO5/EdEVnbZLXQW2+BJDLqdbG+7q1jxM3zuEhgXVXqvBMmAq9suQ5bgef9KAYE0yimTTOtZ38HZGCPbPFppT8hyCl7IE5shOTLn2cY61GrAvw84rFfDix7E1B0v7yiTqSLjaJnj/Str6Tsy1wuboeAuVGmkAm5bDTvlMChtV212U5/InMGu5Mu418zKd0wJGkYXZwu+CRPxYsvdSpg3+MA8wHJud4h+JPMfm0Gh72x/sEJhCmy32XfPwbudntINuNSb++oO5qA/18rwvF/7ZrKhoQ5xobmCSl6PKMCyOvsiVIQQF9pEnz9DDB/vIz6Czsg//trKxYIQR+RBXQYLCCamY9nW3Z8Ob+l/Wc8JHiaPM636qJreb7f3CDBXH1JHlcgaXxlofIAdTnEA733b/wom4ikVwAtTvr1Oa0SwDUOcFHco5WHosa9tBJdROM41psFBbTJWXHydw7iqoJhjDpxjq6JAcf52kd6FJCfw6YryYApjaY20fyBphyK5TOF104SBgOvafb86rtPonQmjNcW0ITLFSbLSViYxhHnRcb10m1YivSnTcF9Xx8LAm0rf1F7Fdm1cgTOcGMSnCFpcbkG7HWptvi5LF6Xh+FqAcmQnBhOAECrSN5sXbXWoUdPp+M9uFY7Hz32bV4pnTPMMhiCwQe83zPk2Bg7CXueZOGfmB7EtQ4VDgu9itp1zF5pER8EF4t2nELrjvnpwnF+vp4dzQHwScSjEsgni4IuDj7OVeEv/uurCMRpnbusf9sQkz7/KPYfGnwvYtEO1cDP/l+LGIUHOpdrlAGyk8FPMpbacNueQs8Mc2qGmrfJQdhbSef/quenMSp9vRztt4HhVTa2znKjyZ28vr+WcK8m72eO5UR7Qapykf69smGOCP7o1aNbDQ/0B2uLAsVTSuw3E1rs44NXI5wjUXKQpQwlyX4oa+dBEGPah4UR10jCDRWyKwkCRqbZD/1RxD36tfeUZWLghD2Dg/ZDBPU0E3z8xGW2rZ7zc2M68rc+5zxmnKiHXUaMtUt42JbC4Zvv60Wnh3gX+zLvezRbcVZqqFg+kI1fksGBWe8Ufk34/E5hcyTaVpAeBKgKLE7zdSgXGrdAkYDv6zAhQP3Ml/Udx1OVT1m0mOjccBowlFcJ4kLN+ef3bIodwc9I1zJFta1tUtfheSgoUtv19bS3zbgfNqwubo+HeEuO6bRZDYOXfARg8cO57/GhucsCN6IR4y1E+iPT4uj52D3zmdoGjNq98+QpWcNJ4cLPkM0G0koy6Lu/Ty0onLy3kKBAEIK4zAED5S6P/3u3qqXg8Y+qBKy/sNRkXAVl851IuJLCv3M7nis8bCAQdR4ODm+KYrJNvsz//jki0jQS1Pak7/6TA22Kp0eXeQFvQNjDj+s8Dwqhdt8UQN5M64axNprVM6sCh4sHvp+bXJ/bcy8dNy+XGBRej0jJXc7FdYeQ49eDNyPjkNymOwUFLFLSbzPpLLwFUSRxBNdX2teA2wsnYH4Wt2k0nBEROdvz8wF3StiFubjvp1/RZFNXp0J9GlrEiLJuS+GFfAq+Tg6YBFtH+oblPRTgO9sSPzH+UvmQFZrmEkTw456ftzm3TTUp/50TveD9J66BtYtxwDnOhsDG2XDRt/X59Q8daqgJS4MM+7z/LVIScwDP5YoMzb0Ajyu2GuVyMPtQaa5dCNqmUbRqryIbKnoeUob7RfiaAosIX+6qFFKgnyu1wnQtEa+/dQadAssYjgRW/XxI5rJUkIAmnXol6mX7lOoO1AZpYhB/LIbpxqjCPWZ+E5RySPhcSv4KknAYO5MMv8yQ/qgKQyXJyY+RJwhmAFS1zwLlly0hY5RwJqHlEAddmF42QXZUBIT6kUO8KRGgd0aAWoz8GfYF9wf1/pv3/j+S9/9RvP9mvL/v0nuoDCnxKZIEzgwUZLuuyP1SrYl4ZQsCvTTqrqeHSrnFwCRJAy8AQlpxm+2XOCfklTqI9NOr7+WtSx4YWFTHdCBMgb7m9LKAy4IOCW32bDvq1nen7/Xsvfi7r9mu/M8fgDMt3KWJtEB23ed3W9xQ6msv/R+o37y3TO3i7/ELohbAIwsafGCRgxiT7nm4bXwN2A+OUzPLg/rdQHhDdK2lFl8U/IeKH/S+bK4PoYU6CklZlIMHAgWgHfXBCS2Xnuqoi++oS+AZVvFyqUObuAgyfoAGLI3gI7ni/dYWn+wfKD8vHHWWiN9Tfl9ezBc76JcfOVUEIdMkT/jAZ6RV9mFX9zuzjU219XxUcPevjpJ2Pkfm/Pgp1f3ceY4DDwl/6fluUps3ZNuvStI//f9Nzh5aTsqT2ML2BpHPqrkWQjAo8NoohJA+en/rWxDIe/vuVx/JPVDklyEKZpaGdQPRGgS97PyRvMsaDA2eurWoY3aiICENRF9m+VA5M5+RsZ02cHHVJP5dzHPyGK3d88qH8pWM1+Jf2j+8dqlfoAQK0LF2K+a5sU5uvs71dqs+1Byaq9Psq1sp5PJUQ7l5QjplKDd+STD6JRaI6mGxUqca0tvcuNuZNU2MW3CejmR+KzKr8wi0WUd87YZ+I6HRV+OInAyZAebkd3QdOtbc48c/K6xQNK+XZmqqIOqNAEeoNGBleAjEV4cAa2gIJAeGQE4P9e+NMVQ9KtxDdAGUtHXbSDYlDBzeMOC3w/o9MoZKtM/GGPmCcAYa1OWbfPhl3yRnVgWQ0nPev23CUG2gxMeoQcYpmQlvKWdtqs1E/BEQWGH1/KdJEgbxiQBwysCWchaQiLgBEFABfRd84xpxV9ZdLoYUeZPqjtH4qeXqBTtVAVrUrHoAXHKm9fdAM7SaC1zM0QZOGsC6Y5nzV/8uNaNKcEo14Hknc+sUIX+6md3j39FCad/BMrsA4yhn3p8CXX2WniAcfuwl6CWRpmDs1YRQ3+ElJha+s7p+NTlL9KX/YW4ULbEghnoYa4+Ms6kNRnfsU/uKnCt9X3cE55Qu1rX0sNj3YuPyTO9v0TJ1Q459agxmZotd3899sMaPnKECMRGMuHNAkPCDQM9BfT/voYXaoD5HsiBZABl330O2X8aUirOgMwUlm9kFtT+NINC7F1BQ9qUbVPf4S4h3jyTInx8gsi5PAHS9MAfTQNI98aetJMLZ73trBhJav7D6IufxPTuX4l932ggGHU7+DaLUb8HENUHDay5Bkx9kYq/Vt5cDj7jyvX/i8cLeRjNvIDa+v4snrjGEhEGWxYrFUa0KdcfBRYuo84b1luMeyk3kDQ9du23FY2VMpGzwTCKtmrZcdTIl9Trwonx+C3H+4ssLjopM8zaFIKkxGXku4/vZw/00bnVif7bPwPMkOx67yQ2vdlDCkL0/keFDmOot08Jd0v+Bz46eNdp/Urs1EvE7sURPHKpwM+42IZC6EQCZY8GVfT+OwZ4JcDcut2ZBct0lyLqxyB0heCZK/PzylfVX2tiDXgkK2PF35wL4PSu/XmnMRfID4uRvqPF4YnxRfgML/BAc6Knv6dBT6rX4jiKh2MQnLdDUvUNRhxpjkWeuYM93+W6XGi/yfajjPXkCY7NP+hYxxEOz/21lvJcJIOFWvus6qmSzLZ2aMppHOig4BSVpcUyj8Vc+lj4tu1U2yMYpJHPxEPZAixY2JI/sYvts1HdbVgLds9j1vlI1ssyaZlGGwSZMu1Tt6D6orcq3IOlXUsjoGJXEIYl1j3NHCQsuzgWI2rmpcBIg9eiOn+anzAOlLO6ge+1uiD7sVHLjssoD4vkrSZ/uWeCDhokY58tQ9hoo43LV1tw5lsVyTQrt59v1BW2jtW278ARsdwdVobn/umZWq+K9OPMb1HE+Eu7UfnQL+DWcr4Q5AKP+IUfHNrCwkynW2X/HUzrG5e9diyQnAHI1Nc8dDqDnHhaSzelndq4RvBsMFfXnzdR7brqeCl4/aAVOLf1UPz55wFr2UJNyzd957MKKaIq01vSeynH4m3v4J4oInFOypIzE/rNDrwPMDCvi5njOf13yrSnjo5q81+WG63SxS1f8bF6pMOz4Wg4B5937PYSZw9cajEO2aGbN+J5nWPSmp/FlxoD1C/Mxgf8gXv80Vj3orXV/epD2lcNcLNUavqUlPBZ9IrvylWpKQCynTID73sWJTFy+mkdNdobHmA8R1dmUfLSB+JaJcGohqKsFmVPwgiy/fh+UQCt4nM/ykvioFJ9qHOrX65xPRndnM0WN+9w1tOlGOs9xHRtXL/XTOP2f0rnMT3H6hb0xQeWq1gwTFu8Jy/WhFelxXmpWFjEzozIwhPtQABtNZI+Lw9ufWa3Vdqi9o3gs5tslZZ8/irbH5eOFFITos6/+vXfO7fjLsrK98bL3FM+FR37cH0smkwINpLa4G0WGhnsJovJc3H0oIjCRuUgxRM1BEdlqHyDb4bKYzn55yYQQIrMjedBkb1Ig19ltczwwxKhI6orLPyKcRaFF5orhrymIekDjiUBXUP+oQ9Iuq8hMIT/e+usGkdCOsVJptsBEZrr4PAnRkSjgMBYvj5po45HQRs/SVr5BaVUMEdsZdZSy16SIXFf+TCFRlKh8Y+oOlIKW1YWWd+kkq4KgUyL1hV2SsNmTCug6+SsbmiUIciG+aES7DDcWCe0U+uOkqTwHRfwQtDgLfYpLLqCfYf0jT7nfH0gTkdRG88NcGXhWOUTsVwExNlCR0s/8MgxtaFHZZuSIVedgIjOF3C9X+BoZHHg2OnidARvCo0IBwSu9ccDxGJk7Z8Vktn0FILoiEWVGuqdAzON7qFNh/UEwLrst8xfibYGnhm6AG9n5cOpFvwDg/mGS46LDfu4BTweMFw7QElFaxeXCn9FBhvejeDQNLetR5BxCxqZ6iC5lvv2xQIHYvQElgQDy8+aw8DYOKIZSUT44+cEE7KzqXNJ2tMW5C1DWwcAqTs5ksekHqDPI1qHRAo5qVTVXc9cklIEloSikhq3sWiRbljrPAwelMKcc534vWVzt1YhTDdI9l9mwDVYjm2/iJUJS1hrVTJ2sunGDabvsJcQ1HpptZ6nx+PGdYY2nPwUkUQ4lS8Tjnvk0C0S8xoG7MBPya1ybM6ZbLR04cN7/3VXjc+lGhLA2lLWotLp13mGI/uQkFfNjP1nM2PNqzuQ5hSdTATKxqOq2TuKn0CjjKQxlPkWwUOUhcw8R2ZOHyRZ28RRnW4E4CBtgxYLqdHsA/nR9HdqIeDnWSN/TeKCot5MJhK6IqhpsAEJJztvaZk41w4FMeFUgG8PP/VJgJhyC/K3jFHPSVXODb0JBbS42KXoUvKVruBwU342CkjT5Ki9BEN+jicJraVDz6/0WdM1ST3TIsQGDVLwRDgu18aHUKVQl52BOwzLOSwHd0g1xxU9ZdWXrSxcV7mXzhwKHIpILzw8AAUyLQpEMzqWBKHCiQhYrqeW5i1r4HVJzu9nH5L23SZtDUXt7rlaIHa8wokBYgE6va8UCD2XjxV4dycKSBvxx9ju6hCfX6Xw/Hx1r9+XmV7aiurpK+lneOYIAv/nyE7LARbYFYUNzzYLBcB/UfbDxdjikKv1hda90Z5rhNDgsz4zVMNpAihRR+mPN9LZO8rbWEtZ2WUxiTRuKCpyBSlkefGngGmLne5EqEKblgny0MMf+apsCWHq5JKkaJuR0UdEs9amW5qg+EYop5A8FWq8DuLWXKhTyONXB73d3yTqKpQihkSxAYi2XlflhHAssNzUo5k9bLy1CPnWzuoCPeVWViHVAVaqbM2iq5hkulP4SOlw7cRmcCx3B8nxoi6kwqHboZ3RZC9BCalIuUMJ7ljC3rlO83MEGb3Quy2i9nQnUz8FxxCrYGKJN2SB/RYZl8NlBDGOLwDiIcU7L/cih8awPsSnCgi5SwhW9oo3dGBGo9elbC63cYOu1cp940k9mRelCSfR8Xk50E9X4JjktXiV02yBox9zx9gaYKB5xPDe7VeyfGkXdMFeRIghsHAJMJTLcJMQairSB29luj7tk4DBQCfsjh5XwmB4DvxyLQ28VyF1tmBXOofV3ZPKBdXhk3ljPxgI6w4FGfOdf40Y1gWqkhTdYX+WhG9H1NrFrt0/rGHR/Sf1tzMl3+ZyvrjwuDsIk6Hv2Lla3GpTPUJSvlv/CGyj0N/plXV2WcRUMX9rqiwItpMtCoDf1ycL1I+dJ1HBnBy/s53JOHf/7Y2yKOdm6ob+LT39wike6atNW2nJJ4U95iYmhr64zgvCHUgXDaW3M/U9x6Ygfmm1zgCfR0tNoPpg6318LguZt38DjTmnfEdKTSseGLKQMH+bufSEqYcdPCwISQwaymURRWLz/1CB8Os/xx9ZI3H+UShFOAHU2N3Dw98QBoO6b8xz6KK5S3U1qgDsImQxfxx0CDgg4cMfh7ThQ4MAch9XwCAlogs+WU3JGdNC0KIU/8otcwEJ2JDtHDlkCEHBIBN6jIYAdBb4OPKkEyxOyz1qs7PvzejfoVacxnCenRdXm1TuV4EbhSfATRQYQ4KKg2+lhKxDvUOS3JrJuq1WPK/W2TDqW8RghQds6N0fEwr8M4QherJfRjM+wdoTFw24/uf1CkEHdDiAlfvdGlXuBalOUYBIX2+T99G7CZK9J9fn4Ccnr2+Ivs7tdDZB8odP5jnY5+e67lIEtceTt7zYKs0/Lv9DQzvtwnxTDSGzjkag9rn6brPgNq+SKXnkVBFmPU0XuF3Gd37rlXDkhHKCedFAQcVUJO0JYShyPB+trl2+yAg1OwGQmH839qC9u/jAWcJfPtIM/585v4dq/W7nQ9e4DfbyVWtO8BLsUUJNY8RPUNY3ZmyzvVpOo7LGreHto1vk2z9T5i8d2qsEKuLo9SHBn8X1IyDa4CIyxrvCaW3I8k286jzq+LP4QG9O3rExzztxQqC0bRTcOfxyJ91PmnB+bFRvHToAWCupEbExWHzDtrYfwawHL354oZsg6BixnUJPk0QYC3Eq5VfSafVCJKbntH4bOeVDYr4PaMeFusiRj+NpZ4pWlRbEJNcfM3g1gvlMtrD/C019fX2F1NJBAW9urec+HDjGN+AwxTqqYmMdjt7JE2Lh3celZlvvMzXwijYfNBjsztw4QudHKSY7evZK8k1uXs0pHcChoKDHyI70JYc7vSvai+OFQ7w0Y4YP7YAUILieIUjZqWebfWCbU93ESuh/DIZ1e5/HqzGI15AawU7pSsxploeUqG4aVUZLnYjP39Ed41YhTiP5saqfNPOfD6hacLsyOY1Nj2FEfCwG/QJsAwjl7p/6kkoUMOp5D6ovZoBMUiFMNQSAiWs2IdwgJuleaVc0u8jyfuhLim9C5p4l3gC0AMRld/3UwRLQFkOeG9z87Dr9uhe6XMKwaEONC+fzB8bD9UewdnSMCJJIscZYKubRc6AR8TbgScEBOb7DmhVwJrosMYv4KQ4PlLGN1RCY4L46b0q/4nsiOX9gNroYVuLi1wgQNyVt3L6AcXNYp4foXGFfCXUlMSiDq6eDqYpNt9Lrh+aStDThaxKRa+HsAtWwHNDk/sCTi6ebWfFeGn/wf/2MwqnZXJWZtwi99Pboe5VNpP18P/fHNYbl+EbDHWt/qcfh0KNeX7e9+syrZXL72v35z632qtyHGSjo6GQX/USx4VSzxx1dP+4dh8yWOl0/max/1vhjqQX95cLctrpq4VSwSlxXT/C2+anvhX5gtedSmNMWD6Hj+erHMlCfIx+6VwVjwxGrDCag+6BHq2kaCVMINn/NFSt4ufDE4eSMCHCpE2IMRMJJrNRJU1wHpUptBdTM7/IofbWCVvyKXeN83skaKfMjysA92z6493NdNZlqSIJhUTyx9FKyQAG9fnkSukKZs5rgPY99MgUPjI7Y21WwqSvK1+cByLlXjW4bthKT6CsM6uSNRL1n0fbgTzibAIHCpo4wO5/T44OrN/h79+7P5eKuR4ld2OJhj+EQGNddJa7JNJZhiwLB8RS+guQ6ApOrhFPUtA65v5RrWExh6FLcDBDq7uf7kCCY9E/8Rkp+bLH9ZBTZjPoKU9P259We7WCWhVJGs+v51yNAbAo2m29o1RAlZt+QTD+FizI6rw5eMHM71Ncj4ZJ2oRuzww4CZ53mPBPX9nn3uQ4RSNBAqfoVlstsUOAq0017udNjyx27/+GulZjDiTrvRLRMYzqe0ziNLwGHzzGsW50bJW58BUqpr33l3COWrm2gPsnpvZ6QKB2kXjLQgDjbxXpe8/yfb/hY3Jmd0S9JtyJNgpbYXn607OgveGOz8SpdUNnwu3ZnsusCnq7JfeLsh2OeR7QJkuwDZ5pYv7iYH7icR9iT03ijincMxPp6ql6EkA0rvt3qAMdIpHqtodXloR7j0HovG8yIIRwBkmN7Z0X8OcbBkjPs2+tkoQFVcO0cuw16ewX8uMnbkjJyOQitUTYI+bkdUcQS3Snx/HVCpEOZ7iaK+j1Wmdix+rJ+eqXXd8C/GcHfenzppRRof7OkihAB8Epv0wOlpuG4o1XGkkgL7vhCnYMIO+2QqEVzvBosHqKc1AYygVCto8NtbUO6NvK6G//cFH94G9l1rJGG/4yC/zPcMnfvlMOemQBCOPCa02LTwu5OPv56/uI4qK/6uKptMVXlgo4AS1NY9WVqc6LorDs/Fp1POQVsBUSae55j4dwCKtCLF0P9IgEceH0TIun1cs7nTuX1AyVZnDDdQMN4iE8PguN+p5KE5Yrw00Ud++VE297NjDMUr6oLJfD4enGmT7yoXZrj1KIxSdXIqRqaYPEisvDIoe3MYq+reKfI2UzCvUToIjK9UIca1nd6iupSfudwcIRzAqHS8keKTYNptcciLqgoZNaTCqhtqBKVRos6/2S+qCr+ynMAC3eqaoOzBB/fAHXzlIQDbSNeWttmwiThGYg9gGcyZ33bFACIDucd2d3HFDHZ8xUtCkY9xEMHLmPCopyCGaaD/BEwwwm29aM/ScXDyGMueBTKTu59t/HAsG1Mw6hQZfX1U+5nMH7VTMnb/HumN275tp7t9oRkYfHAl2oFffXswYTluFSLYq5TylsidcIDw9j0TeoEmO6F1/wreJ/01NlN2CuVRfKiNdqbEejklX5T6XuODRR8KFWdpu/KheGBHoYHQC2ENa3FXyrrsctNb5fSamyW0cYdS5c1zFcyeqO1EcDXQ/KB16YoZFUPSWOiAzHnrfV6Jnxa3NTXb4pN+pqckZpGc8S5c2Kabp+nYBwxqlx7EMpUbgzjvhr0RDhtzG7Q7kqQxoMfBS2GCtQFgESsg8jz6nT0sihZkOXtHOzFz/4mu/aVJrWSx7su+vLHAne+JmPseLcYtsMyLTyH4oV+raRkJzzeo1DV2XYAWQTmGxn+vVe6zaAFH27x3iiMkpLyUUCgRIlPK6sL7b22OXJDx+ttWhImlSiP/LLn0Pw2+RPVamuKEn+Nbra+jNas8UnaxWdS2M28xwQQt5Uf2p1aZcHD9a2Yktqs7+3bHUTK5RfO4rhC4p3Zz6YBuzclDrDWupxr46NrqQ+LLYh+vBNvElG1Iom1WFqNpk7JIpdrVXOpHPShmBlDC6FhiEJMzNn6RtgyaqRUlwBuioASg+mvNCzx74OMgapEn6WdBGDR1zYaMIGsd8toJOYcYbAATjOiGqFNH4QbL4wHVh2f6QnJfkDA+/C/HjnirE5F1n0JqbEZqqLHVBp5gi9ZgvNWGP/cDdATxBltux8+jKQKRR11CmpuoBoLUOW9bQBbaDjlaRHTO0w0mUBFC/itMHcGUjoKgilkOESFW9nIZWgXEN5Y1eJjqgsYSR3bVOFgjQWkJjgOayFegG3qxExMVCCQhpek+2Pe+IFXqFqQrM7WriDBBMVEELezNH5jxHA2ud0Qe/TJMV6hFo3SL5L4UwzURHw5SVeTV7CM4Wy8RtsFSvWNu5NrDxPQYZZ6m87moxCq3Y77WtJ9Tdm4eULQtc2tdqS70s+i4HocinhaWSx8s0d+Z0fQKgA5hj3raomfDEeNoxsZlgf46FC5cyCbBFDzrSRj3kh4urMxXzm1pAwj4PJgYrWoZJKygDtZXNACYZA+dzL2v8/NcEO9FEEco5qbZ0RgnDmYO3Xzm7zovX6IDB3pCFzC9A5uDtoaec+PjoezhsGZVxl0IKhWE4MEYOuastAAo0h14dkijuYkmOak3kpBfgNFuih3aLfjoPtQJS/amEXIczHfqAlCePXzQ5GOoXuHZG8ZucHl7MhOvAKkhG2bMXgt9dbI2cOJdDwe2HQqIPcfuDJxpJmQZlFkpPlnS/91jkRC/Qo20RhGxVLODhPCI/TkZmXBjux8CqHDUadTP3XTWZ725nk1oQXpRsEPmpTWOp30tO0TqEKEpDSwqUuPuw7U89dPlJ0RgLBLH3IsYB1XIobH2wPkL9nErX9ePQFZ9xcBQfGrxVcQh2V1LfMliI8baEkKrt8f5JsTCGpT6w6+X2Aah9QD4wMkLwsPNkQgp0riV8b0PQmdjfWFby0k5F/Evl+RUM6Q6OQWhN6DYBzZcgdEQ4AxWZaphG79O7geQJ24JlcEj94UMp1EnqJbCmHlU6tNwiRY6UTnjTBG3HoZqx4ZBnOIKUCTpwpZA5Hy4ojXs6AzFA/a/WrTCQ5QAZ26IOkROlEEF63aBBjGDZxO9wt7Wy8qG2d54NWCDyNbpyA5UWhvXXTmaWMwVIpZZKBjFkF7LnsDBqNxJyghxjXFz7nPNm26SiJhOLe/GkjIp/hs8W3Mke1+jlS53QRTItz7KjrMfWOGxPWpCLVnY5rFtQWPEG7HRdBPKY0hJ8pssdiOeRPnuXDP6lS8hKkwS07Ry7yoC8aoJTnj+X/Sqc5OmY6TbXFGc09Amyao4nhS1y1u5mC6CN1uhFaVvEg+BjzcSWZzPaAfHCqdUsESFjXNNvKG9DEzN2Q4H1hsEyWBwpLPXh8/uKtZiJx4o12CFBYudRa4Dit2lgkpLSuZDLdimXOzgCo36I2CJiH032cpSRNE48zxba3i5LXNad/utWJ//axS8RA0e+In/3Q8UffjLKS4Nj/led6gn3b6QL91xw4FNxa6hoDqsfPi5dSXE6c0C1Gj+0B3bpVw4feqtO8cMBUPYgyZHcTlSwu8/iEgMHx+L1+NJFbMd4IVyC5v+juEiCpl476Oml3HH90WHixXyrm5ODKJ8DR+pfbMUwm3cuQed9QsVGG5ZLGIl9A+aHJZktaxyRMTdYTN1DVegiy22CKk5txSRE7y54Bx5/YN169ies5cDcEHGnql9d9fde+GROLV8bA/A7ZR4zGP3YJULETQtCbINI5DKCZFvVKFj//GWR2okCiFxwA2DpskTfkCqRum9GRrBmTot0EJogYnz/s0DCPo7mmZ2UNvPvBfP1axW90sN2bhE8AvkIzCDDAl8fce2FS/hIPRv6e3dJ8pGQXQtyYhkBz/FfdwEyoRqxc8Gcxv2PYw6LE3bT7FRrE44hVoOpmXp4woD3xOSLuXpDs6isGFA3d5Yn4Eh2VYp1XdIqWdv9qFcUKzUy6GIxAb+Li+mALdA235rcqW63n+AqWCxODuGUu9IKRILUEdMG9ylWw0f18LEJnsB7DChPvoKeXN4Q/+0JvQhaaOF/NhkcAxuHMpdYRMCvoyZbnucQYrBwSLVA3sa+x4eSLP3eaft+JHE1M23yQ8UnHRRjjjrYqHCdPkUhbU0T4HW16AyQ7sGVmn7qEyZyP6mojZJUeFuIKFwT3W9GzDtjqI4kLg4toAMuCVy3j1vkLLi8uBQ0koNisMbudEAJIyTufE15r/vKLFvbrn52QSTKo9fWvjhOgLHhvjg+wB5GfBXAPKjOjsvRUed3VOPRm4ZqtJUKq86LTW0moaFg6KGrUQIG2mPvaOutX/KsRyLeIfIULNjNgt9LCIlMHlwBPoza61zF1iCu/mD1cmP4LZ4kezPVLmfjrsm6Y1gZUj2BCZAgduoRHxcqlHtNruv57NQpBIaAjduLIBzu30dqMIcTattKY7ZB4Qpszv6n0OCmim75jGVr6gSBoWo47EqU06JEyKvcbezaC+EmQngo/cbEia/yqUJaxb93aGEB9RVroxsAZ6XoXYdEXRghDXYhfF54BBWguz1KF1SeSToPY9sQF50uWhtXvDAYsWONCphCCamU6LrMyxAsDj0oAU9786zrCQQsIxkC2xb43zgtmHgG4/14kAUp5SqGD67BLDDltK/+u7NUMXxWNFeyH1As5IEnFe5HQrwxhlrLWm5iMkc9dXRLUJlGWFuxDwed6xuHlmdCU2aukybrHCsgigRHV0rx7puaw3tfTZs62esH+yU7Qj97cS9Kks/w8bL4s0JMyW23TNqb+ncoXMq4TbBm5a3sTT4acOlPhLkV0hdsgFWkM6EDBURjmQ5XwtMlvZW7okTE3raE90eqBwFGOjqzXccip0n4wsBOs5Ivtxr8fwZUzJNwIH/VcXqTtRcPRfiflgD6MoaxboB8Y0uoxf7qdBYYBk+FnCweGiF2QuLpaE6yKOQOWyNtXsKOcmD47WPFmKDDstJdLLUiGBE9f4nkl1VhwZL0vmFIcwMOAwsGB5vuiHUf6W95T0zgcg8W2o03JQxH4Ww2JVkA9enNeratQ3uW8bPBBI9PpixJq1/Mz9RoJwItlMf3WtswaakaYGJo6S0ZPHK61gjFKtEQo8X7INo1sj/yG0U2VA//8BPuyPTuzem6k2VbVkVlcGFsBmKQtpJ9UiKnytF6Kwv4HgEhE4wNJgPRU8ZCne2prqhft3z3yQbd6OoADyZNUXTf9Va61wcFWEmNxi9bxIP778XjgEXcwbz9iGKgjDfkC9G5pium+CarOmpbu5neEpzm3CFfBtyQRMohksHo1bsCwHSZvkeTxB2CW1B55yBrav0BHp5wSxYiAQH1NVeJfx4k6KQ6nu/sxzuRyze8KxFKcXOrBz57L6/0BRirWcah7QxV1a9h+9cmL8hFmRcSRQDswvYADOKodBbymnh2z/d3PCzN2rFcxc1uSRidRpHd4oI9hsL3YpWDbJFEgZbIXe1rTgqMHbNNJDaYBQkdV/hHekBGZElwrcGBu7Vi2A3KBxqZNFgT2XEa++lgyQmM2plQioOsbF47+mxt1LsdDKNtkcMr03Rhaxka2siu4SD4hVhcwdLdFPuRRaHqHtb/C5n3ONC9r5V7EE5+gG+Za0zpO260iQErqPq5NyqEbeekwds4X1LE6L/fV10Poer8T+Hr9UyhsBAmAs5THx8dz2J0x2kGlqDO7tsvTf4sMn/7ScFvDiFJ4CZq4tFKdflT7b4w/kJtlxqsNMrJG7byMZ2QisURpNX017WgKz1Syecggdyo683nDmKlfGEBY7sBAjerR0DSN6ETzpQdS3jmkYOuHfewuMS+8Skmcky+QuZErP+AXyF6HSVdzQu0EN7RpLZNjrb1kyJgpOwkjaZqbCesWocS8VEoGQXvhLSYkO4LohI04o76gIcx2x+eShjLy3/H/LPTUnB2GvTl5saXWEyJ83QLRRdaZwf1U1oCiHB9LvI8W11zZ8zAXAHOKYrBvBKwDUOxcvSorsQJsp9FyPU9wwYlfVuo2sxq855eziSovCeNKY5uvCFMzWk02zrhyIlFaXm14nUX3278hWWnoJKwMuMTY5XLGlvr2Ju0CC+ZEz7dBZ12mfNAtFRg+2L5uYMo7x1Irb2DqcQ/zbCh0/h8+dFVmyo4Sk2NmsNj6wXjQoqTRSmxcbFi+7YwkNgNVq5fzkgA6v0NPzsZDM1hcItKa7cyC9LdfuJYx3VyvdIvhEvdsAnQ2Tk7FJzcwQqy8w+A4giu3CREou4SmqojNRmT00qmoT7vxAYwUM8gKeO9divnP+qZl6FHstKZlOMdFlOQRdBIgYmkoCTV4k5iX7CoD5dD3fqQhxpzT9/s3k+7WrL+TPxKyZ5fs2m47bELFGNJci7lAKao3lts5pBCpP11kVCrT3j+OWY8c96R7nsEZoQAZKeigoz5dNxyF4dNKrveZI1VpcyiITr9XrlfibGz0Gtx/jMLp6LJBnrW9jzlzfP44XVZeDaKLfazB0LvTL/G7sYA62vgql4nAFUd0/v5XwUJw5i5/ZIReA06xYMm+ZtSo8/yJXUY5wSCkytCHJ8yGYVVPTB8hOe3WOvAAiDD22AeZtBkGktR87Vy4woo66khcLA0bfkYxm41X/UD5LCsdLJPLK8VJn71VnDIvj6aK2hx/HouiE0OWkc/iu9pbHUeJC0U2G76aCu1VqGenRCzW20/VC7Xeel4xc7uwqdnOGmQrQpoqeL1cqd+KcnN6XVqTnN6fvWj7GhS27uaJufIEN7jc7OfNgqFMKRIxvPoL9+j8DBZtt6Eb+CNEP8Hbcc3YAS9JErNhTzxZEo8nIl+bxIk6guW8fMXIv7AGidajtNdWzlDvqeGv9h+U/FwA08NGCJajM8iTIVoKy1zmqS6vOyRhhvFJZc5IFpd0BIIhiAvIjRnSvO1eju7F4XTTTsHQ/Pw8kvAvbabQFqN4hPidWDhNxlTyW4LyrxC49mzsuLKkxAz0tk4uDC8n6b7IpDdqpeWaZUR9at8sDKExUVqY6SGoVSjV/TgjmbKhFKr9kqrXmNnkrooHf+fFFMAKDSxlYDLL/sC0uUHKbexQFZpM3E/Ez9Yn11gmusiACEVZw40gOjoZrz2BTOQtQSOcbUSMaDoh3q//O9FKoRqEfpWJdXf5wGABcwy4DmqrzhjqsubqSDIhY5AU6OLiQxh1AO7yLpFLhbJz8zZb6FHF0jUrloXo5+aQW+4QFuaqX0qEshzYsYqWKL2nVs8/Hbs+fds3812ZcAgBSh9fOVB7PhMvdPGVDWFrkIjMA365hRyHkI7vjJiFhLBHOLA9gP2n1wJxGpNuTnZo9aRi+Ey2N5WB4xfejTNG827l8/KwON8W+PTDUi4TmxZlqxUVUett4p/po3UY7NU9rDO03SHuYFyyJ9KLDxZpOmjO0Ysdi/giB+PQgMHfKTJcUCGnRNk11Fou9rEeyRjRizi7cznSodVIpem0FJfrpyb6+CgLST8ADxfcm861Vn4Aw5rrI5v2E1XoyE8y4Utf19ynDUWOWo5LwTem+pGwZtJFKW1HQylyPoK+cwL7FZ3Og3qOkU46MPvefUMT7yYJdCxQQWw5HGAUUxK45HnF4+CReXFQ+uDQa8uXt/CLBmbPNHi6P2GtjukPeyFPGeERToplEgVF7qEtqRGvwaxUbGZ21leP6cE0hASzsVZiUmSxJ/9mxebPcE1r2j/rgYCm/pyrKYse6xwAJVSOg9VzT3UfDgjZKsAPMwtgS2BXxQbEvRsFopRDCV+qSjqUKSUt9iIRxR1FudiMIJ9xkKM71YqRpqPADqGbAApVpUyIPnCMQhFZx90MRVmkO3/U9lweehXKnegRfmjlf1zumxg+7z/jiaADI61y3A42I/Hx6+V6L4lPvynmn78JqKPGjOz4cvZG8ChWjcALFA3fXGUQsb3nBzIc07R1IMMvI5/7OcJ48yeOVtAjFSZTLIV820Wt6IpsMu7SafKmo64hMUZKSnBwJZcf8wTeWJgPlNgSCdVa9qmFoHPoLp4Cghotu6kjwNopbZGcIXHwaa09jAAqPp0fUucOA5udEKSIHubxpHLPIcuv3r/HMNEoq3f7kZzhBiRBdMnzCUjwtOIstu1hfWJYRfzKiazbdkDHkqnaWbhtNkS+8dfozb1JsfmBF6TiAghM7F+QLEI+M2FNDFIIOyMRMP+rFQRp0+jbPAWgePUxSRuaR6sjQpuSYSYYTUrkXnBnNiLr9GKBwyfblmChry36cGnaw8BTzZcQ3jS4Wgpu+PHT7pHX/BLSSZYAyBBol7dVVNY4iGFRgtH1nq8vhE7ERJHKkHxSNQCTxIxMMnT0cuRfSrKjIQxJo4wsL4j6wF0/RZmRk8+rhVpC812TAx/Qvp4Tq2I9wk9EntRloVsTzVmliaOiLSUUXI8ISMMT9heStlkmyINfgZSg2vmoVISv0I3UJfo0xzY8q+SnVNGXWUrQlezdXwOPoa9xPTlkdWDoGk1NjMrBknAl3XDwTQn0c+Ds46kgA1MEKm8HhRXGdSK74j5Xn7hysBLSEiJetTlia5il6iodzAekhjWZEoEbS58veVcsPrCTbNyFpkiMNtVBJcamIbq19DCXAdXz7MaXulc4umSgKtsoEpki+nWZErHUok7M5BvPEJnkwW70ruIoTR/nwYUgoRNbTL0su6NM0Fl1iAu6n+kPHYMbBo7RGq2D3Ac3mPxXTCGrjuVyEWVEuilS20laS7CzzzmWQIrYKQDRmZn6DAJBETA9RzdX7YNEIs9QXzTLcweDKhsmxeTRVsIVoNINqyOgumSVVhJMqUrzYBeJ2c9rbdvKqRmk7RVNGjbjZcV1qhejw1E1Q7DGThlaRVjYhIXstacj9zJJzwgYzMiCGkhAjCoAsnbnQTluvXaVQ3GfUggbTdAysSEkai/NAKrWaSvHihXrvnqTmNaATD2CI9eJA1ebHQ8OT75CPb+P/Wj6gFwIWWLiohOHvGHQXCcC0OynbvFtpBbt5HpGA2MKEBDTW254s7cDO0QEJ0x/ZmelWm0cxhjapPf5ko5CSE5kWk5G1CnQ7L5LSnpODIVP+60hioqse09UQ7I7cOFCmiZtUZwO8Vw2OK3loNTQ3S4rkq7th7SZ3N+ZOsqI4eGfVs5aXa9wxKn11SAMHK4lPiDm6aj1tHTfMxj1MjfmKeKHwhiEaDqUIZwRFziWZbkSRc3NMqC1k7c41+HEyjqy35tNV/ZlG0mZt3CJFVm9rVtQrpFiXeZDHBJ6uRYoLF6l2Psx5vR+68cOGIHj/2lI6ef2Al+5Fs5fNNUrEm3i7ZdnSI3Ih9ssICu9aE4oBYVkAVLavlZzdJ9p3cidK2uCloTSUENDRw16lRNthR0qRdSc3N9JuiD24ruXAeddv5aQR51rGtS+mXrLytboba7cEGXa7AqXAuGhYNqu+YsqO0udXx6+9hSyN8oBZ7vbR66l2Wjz9373iG/ikYR0sJhw5APw9TgerD5yq7ePEkg1uUdJV2j6zZtTM7fIE6Lhk7OQUmlQ5Ju3mqh8PECHFu9Ai77nJKbd789yWWJV+q/kdLAKEJ2saJr+4AVwFxthEDuyKeWbq2+Sc0npYLsOnBA0Y+imoiC8cnb9SnhAEudjTNnb3KpaHAuME0gdeckaPCzr1qyRZSUxsvwYvgZoIsSkGLbNCmAh5omJUmMPEUrtHEo1zznzAsw5t1p1IAob3B/USMRZHAMVSaoSvfWgHb5YRMYcsTsrKXK/QeIJZj6SEa5x/8vfnRKo5ms8uimML534e9qPEtStDkR0cG/Qj+lgTB5kbhPS/hoB96o9VxTRv0cKMHgO3bjV1qOUsven1NM4BE9CCLIgkqDly5DqJUTBMigNp/xJ65HeS+5r0JQj41plMEbfoz+kh8mieAsxlILhlMxSMtXA1VSbzA7xr+XLQy25CUJGhSfInlot+y3QBR6ZqOKXb7oMlTxUO73xmp4XH8ZgOsYGwxmJ381AbgfENeKlqA2mXhozgcz7t8bSzRZVndwEGDZBB2nBcYQXC59YxvIMRt9aHrmOq6McFeDDBOGJNnXG/MN1mfDHGJ+2GvfwPrJXwnipetyxKFBjUiKWmUeUPO9Eb4vUpsy74fCHoGa88L62WTrPkkeBg7oyCu3bUeF+dgVzaIsTZs6kOs/JzA/tIeI3Td86jugZ9Rpk9AQrybo2GrzOYB/dgglhc9Aoq7UP4H70qd/42fubAez1TuzTe6pC1PUXGWChDNi5Bg23KWL1N6e2uBYp/bHqjHZScu2gkRPov/2ANFVLd9aGVO9isJnWiSbTjIIrv6GrZhsc/KxqudRFCV1UyygMM1pAOgiArogCVYyeejDDL8+XSw03Gy3goAuhRT6yJ24PiHBhIBqbnIumiVpjZeohQgZiOwoy8V9JBD7rLYcxTnMKQLhtt6OqMGa2rnb2JMtZreuxw9odQW8TjrBBLd2nJhR+w22uMWjTIXPOtkl2DGyrIHrRDGwSt7NjwSev1/mUpU7h0oNHlRG7R/GapM+4Yq3yV1TqP+YrSVwusE1XKv7UtAgv2dLCAPlE6d8h91oE59YtWOBgAQpaJuYMyQ/iW26rxArRkOOWvob6Kbug7qc2XtT6Ym852Luq5pv+3vJCp2AUXTgRCcYPqz8wKhaXzxTgyQtjV4pSjiTcspoBpeZCefFse0DxJySZZNsdgkCIHklDlooSv5CrS80XHKNhd+V+flAL8VVLJ9RRnf6hokZa52DrdmGzZIKM2TOpI8V20q3DyvgjIlm6TMpqo2BkHUiNOO1rSEQs60o1XBNpskAV51Ve75y8HS2Qlis48qK4QtYYWqArdqBV0nPuFSaxbAvs2mKao382w+wt+BAbhccrZWocrCKMGBFZRyoryQ1tjjA1EAVfHtlOkW7bMIKAStUbw0tXI7SEzRQ7UyUD+wH2mftakwusfl/GztC1ZEhmHgpo3wmMcYoeiBQbjfk6BdpGlhfawyBSMUxhgO2Wk6DnGGvGTx57vedijQZnSsoS6FH2ppLoY5zr2hgap7Zw/T63k0TC11z+RFPYmFnJIieHQSMvz1osyRnDsYkNWsbYNCv1jXVXBRfmuumD3kNm55lRYZF82yPvCbZ51PaXbwDuNYi2rS/h8EWOpBgCj0Wmwford9Ni2bevHqVNrScL/XMyUTA/Ku0f+Q0URXHhGHZsh4BPw0gDPcUU51x08DcygEI/OYb32eKgVBYWmhs/9+I5H9MgEj8q/bQh0IX6n80ja8NH9oTlW4/LAGYdRp22JU+NXSGSbpBxONMMiOBOCmjKotftcr6JzWz0pd4/hxP8CxcmlAeO4Yl2kV1FvXQh8DfbDSswLUA0++QUAwnXnYDJEJS0mwRyfP6oALQp7L8go84AqY/lLk3AnK9xAme+97PvzqAYx4uN9tgDSr29StBkHr6YcxNIZLBva38Qzirjd5POuykvJ58ovjWDhyIkjrxgCIa+hmMpDXvT8iYZtu8vfXeexcxQ9x0cXJJJrwWb0x20hfYmUnygtvyTLIWu85y+Hdxl3xn1VkvY+Gxj5om5POZ0i/MW44LGJgepWstbEIXamWXQJYG+/YWbF+ZV56YZrQRI9n6aPn8bSbrerxhTuhCSPwVb82Mxl7E1ib3i9Ng0CYn1MwAlPmL/nZZCvmZJUN9IoQYpkx+SeCJWYgblkayc5043raGZ7RvACa5lM8l+eqvGxSxWncix+rLLQKoEn9IHdwnpcNHcS+uGBpAPHO6GgQWsUMEsYUl/KWRbWQOPevU2PjGDDGJsbwJQ6pk7STYX+NUgQDq3Xv6gBru+2YJpWmogEE7MiGmPVpPNsZwvyDd0Es5TRx3dDrqv9uxTKEpjMIdXicpUekywYCjDEBsNXrk50Dce0NANkNYqAOBV2rF7UM91RbAL9HMPQTs8HIHBng5/GcV/AZUmlC9W6rHaJ6T866l2EiZ4I6mfepi8Ap9s7OZodQUcZ2UNDaEHy2f8UWgb/VU7zQ3NulTRr9rgEhGQ6mTfT7S64wxhvKefmEbWTBDHnHY6+8Wqnyq4kAvVkSIokM23KzSPfxWw+vaOqXfxOlS6iKR3PU1NOpLE0vEZKdQUjNzXWojBXqvRzfG2AcSLMNvLKO2vOmM0+szghHs/oy9CAMk6d1K4S1DW7DTXXyoVo1AoZugkgYd8z95k/M565zNO6iJrgydnKsNSIbhxiH00fHIiOju/h4Iv+ul9sBw5Y5f4o1Ydm+baYSWxOaTRK9RrpLsxRch0bB5j3Zc0WLNaFxcfPvXIN0+NnzKYGFQbamyh11uTLoXwYWIpI1XE0y0GXgleMJjpTsZvbkaGbQA8msPJhzwcfoJsVS+AGmwvcGaQ4HTmXw7fDCvtKMFE86nu/bssmlD/IYVop5R6eXYvT99A5nn2OaqSXF43yRlXDpnrmPpi61m3SooogjirwEZilmvceWbrRA9Jz3CPJVsdz3kvErVm0A+twtzDTlf4fSXALbgm/ZkB5OvPoaaMtQa+wgI+8/BJrFXTvETxiuHEecywryxFUjjT93bOIf5reVLPd/J/ObWF1gieARGBvxknmWV5lTS0Y32JsAT9nMbGkqF+mnH0Bx81XpDZAyMZRsEwMfCOZeyKzAz8A2GHtu17714iS86ymSmJ+vNONaQlOb+23HS4paIER0DzbwMi12HmUhMTajUqY/mr6OQjd/EBBvjbB5+hpVNMQKo8RCKIQWfJ8owr08cm162yOHEDgDNhm2A8LBLrUayCvb78ck0TDwzERx5Mi4yh51YNd0pNFh8Jvd/+sJoaYK+bkq55Wf7A2KTadsTQOcM+bgxrF4gWyzfmkiI++Pq1qXeFsDwOZruY47zlJkjKgoXTH88NFO5nNHYVxO7cvMsoUd2fBdrofV+Xhdvrd9Texg0U/P3xGLqPcTGIdZ/+8RERd1ap6j1C6wyeFmRMywo9+/aPnbnC7/h2/FjHzIKdBwEsPh4tqO7bfNrQ0kmv+jXvp/dd9X7rx+d61LvvhEU8z2zlVnVSxexrIAwiUTmCyRP75yAiy4Uq7QCJksoSC3RVKRvHPCu4AubDsAKL5e6Np382fPYYURPqj2K/+fuGXDDjqrlRUAXHl8Fd79KaUxTE4+V/v0BRC62+BdrWklJ1SmbUO0epMkEt856mOk//EMOjfCp1ayXwbYwdOtnxLvacN/zgKgrlAzHXUmVipiiJkLoMmVX5a7A+0V6InINZFe3deigKFJh++AA8bvrJ0bEc4KOX37sHyBppdx31p+sKrWBuySbvwvlyhM0fWzsH99k8ZIJ/qzwlgRSMddsw710N81XMHDxBHDaUiQEqQy4aHPeUozPw9crCp0gTyFSfxbuYXu/XOlaMMhxs18kH0bDDSTuiM1VFE3YuEC51iIBAa8j9ROIx2b3DpUtYuSqI3/7Wyp1SLVUNRkj+X4o4XIyCN6coibsz0zMDcKzhE3awaO6doxD7YAttLzhGZoKzlp0+JE69ryts13w5bIMW9vtS9u2nvW+9XuAlmXdW9Cud/bNoBL4fdZrKjNFPaCRfc8pP1OF/Dndx1GI16Dwq67JFZMaiZEp9VyP0a+D50mnJ4kXrxSbRjfDkcIU5R4mPnkSDIjD593chXG4pczzyaCqAYdvi2/l3dkzoFOilmAHE5fJ1ut8ON4FVjBnMgZvwuSfLACcFUCpnzfg1tTT1tCKfrR11zh+FwnAV2szg4/anXd2fX3swtCnuiQ0A7yd+X1cXxLpKlZa7fh+/OMd8W3BMC6cd8E/iuTTtjeM36S3te7RPxWSajiZGeqPt8pFi+/JtQxXzJsaR2UJ4rJ8+jQMB4rqEsYdt4jZiGWLr3AT2bL0R9KhK54XnOKMX4sNSRiXasduV8wc6/DfbfhEkFMG91RjBD7P4xU3a6Iiaij4VBnPt7Ban24JlInnS6Fvfg03t3OJBtK5mYxqki0CgEI5laXxYDXEMANLt+mZEQB9dmW95uDFjxzk8psPJ34yZgMKj1zszJ+1s56RtYNHzSsNhYkaJWUrT/Zq20s0QESh2bNe6eOYZnw831waDcStBxham2bwCwdTbk2nKNpgTGSxa25abhENOKtbDc7Pycqzp0ft324ZNqBaOA6eQFsrZRnw7gepWozWzoeFmBnGuTRcb4sDRTuKqvWVx3iIhMm7J2hFlcmseV+iyZwvNDoaA4bMDPwwgWjfosI1TmCrfKhLItL6d+SvnnquKHGCz4XFT+7UZJQX6ADzBquLmdx9VC+IsiccvuSL42tj/lL6XtuAJIhJhN2OT9J50AAq7/6QI+3+cC2bgZu+TbOdfDVOHnQp+5sobErukmthBxcbB3IvGYCDii9/pgG3ETs4pB5RBRPZB0sA/UDcQXZ14ilHYrqNjSNioMdZDTDG11Mv4wQ/zfleoU4BplYSXv4iwtexkKtcoZzfOcXMU8TuuTjgelNaFSDGEU+dWtugsCcpM/3U7QfKW3OuxeqfkkpHChAYHW4I6tftcDXzTHXHW8j22MR2s0UfsiDw5riba6fwmy3MxLqq6v+bo7wIPHCD7Jor/Tu56gVceMkkmGFIEeEfuazINyODPPaoiG8alkQwVVxsbB8vZydMl+s18pIC1xJ2NHdnzagpYXfJvtE4tsp3cyQB8rD2FeVr0ECT21VDdi9jaiYrj+UzktQGd/LM1vTxO8y9493EVWt5cOa2xVdNhiO3mLjuTRuj8GOAyOmfTBR8mOGeHg1oyAvc5wcC2Z8BVu4g5jDxLKh495g/tKzamn+Zh5wdkFX98c8Vx9PMYXc5Z6DUZ5BJH+Rm6bcSaGUYDwQl9cuhM19g3OcmGSqi9awOfYcfZVd3R9GrDbGxNCgHtfFFY7pnO4jwDT3K37zNxlAXB4lmN8lYzZS894UzVqXBZ6zkQcLNEfIQQLqTUafbttduWzCNd0O9FV5PWtEO6VCTUvIDVa1jtoa9/XYturLog7m9/tmEA8To19MBRAHrArUjlcucHH5ZkASw0Zzq7MoOUltOvS4P9W0T3O0bRjU8+Tkl4sDtccJw8jitZkDc23DaYroEcKp2mPlD4y5VcpVcewklv93PPVlZbrZWDMfOX93sqXW+UNuDTc5qXDk7+enBYZBNWwZLy23goVL29Yjdi58ND87Y4CPvg+62DRDlPL3jOXP/OcRlVEN6KuytrjpHogFhYsLBRoCbDmPOuLb1sfmWKiak97ien/b5klB5ZenJiRtsq+oKrmlcywTfVqLuOHQUnlOk4LiYcQIfLp6TwBEEV9x/ETlD/OeKSGwVKNGi2kMH66iusmgwdpwAFbqbKqn+LNNM61jX4h0pB34Tps0NAn10CyU/Cxf+9n/X0TfAG8ogezZJERsrrg93s4hRPXj5vJcepIUFF8dgJNOu9sFz+5RgTDmnLKlxO6LHFQ4v9vq83SqV5SshfirBezccqVoPTEJqK6RS/mdYuNE7j72PbCpKeg54GnLArCdB3d8eOFva2WDBUPwmDAHv+3p48UrbRXZaULMcSDPUz7z9OjsXeyYglAwXXz9H7myN9fTsmnxFWnDdNhG0unaMHkrvuOlE++0DRvjZ2nGINgeYfz6ro0QwgIRWShntGkSduTEF14s3bIKSS8271fLHBWCEZyTGiMS5hN5F8B1P/jTPv7uc9XmHJkS27UaaFrfRKUE7jKaEqfVJoKYyFfq3ypmZQRtf/yTOzIPPDG+q3vEpXih0PIcgEplCURc+/7K5ZCJYp1kxBQK9+ahUedLvXr1hIPHoZ5uqGLRSx/Y2U3jJwZdHx3geCYATFdqzNVZ34JF4GP5TThu+MLpV4Uvucs8mQUBqdnhFuqE0zmATGLGJHhMbVd10velrxwdwyegbMmJMXhRMV+XeWFDW6RoFmBmXf4ShGURcDV/iYouPNftUo4hlKUjm59LBczxYs+82VokOycXbsYcNwWPVirIZWowvXRwoTB+AacKOD4gTrGghwiSwuofkdg3AzPouyxo8isQdeSs72eB0VUTkd9iDVzz4piPW5LDTAzeSMDAVBy2gvGh/BvUc+XmcY3GWJSZJSyW2dKsuQhlbBj7LBQ+qQ+oMU9MxgqTZZSxDYN94WKYq3aOmGD2DnJxUmuJxFhiVTJunTITX2nBX5SZ/ktM+/fjuHKet7mfz0bMxGMTwyJYk0I0v3NQu8zHQzqpB4Nli7lgop9rsvgMvSu8cZIouhi7SBpdqh75INtCenAhjQZM0WVA6XxiXJNRomHzgu4k8X1jO0INtRnislcBrr6dFQiT+0q4BRtgTs9JPAhAorCokmX6zZIkF4q5/g1nr8cjue12JXx5Kb5h6BCLJy3n6IzvCw+yYqFWUiJM3NmSGEtLr76rg59NzJZ+YrIXfPaPHr75hrqrApIxg8HfpVMKSCns5ehieL6n8OjWfjbv7sFxEHAmHC3Iclk32VzuhGoddtP+obywTxkXfWGTYXHVCf6dHnaJK5/1Ke/1sAltx3mwhR+ovFCFemkDwdCgBCD9l0VOvwibvXiZTxiLvm/H1bhO0LFZQegr33VB8dtPMIIS9/ZgeFIa6jaVcVXOpNY+yo8pakhP2ZT2ptnE2UKBaR8zkVygK00T+eSi6RlJ2xYlTPybHY85fQxyBykJXbT4o3qhEW+xKlfkLmobrGYlixnpO+pFA3mDzwBXTL+elqWPMWb4VInuL9izxUPE+IiSdmXx9yVlc/lm9QcsSZ/b++jPvp1pWN/92y8n4PyYFe13Odd5TJzk+N8kN/N/B7/Ywe1ab6/Ntx9R+j83WpPHHMNgwYwweYqu0z+a56NlNykXze54kfC+58F0EkRG4TApF4ZNb9r6hhCd9R9lDvk1avwibe/f4rHLm7n2cUeJuYUq3UlgzK4ewy5wuks/s/jnBe2nrJtprik27ygu1YqL+Gn6TCmMewDhb9mj2OEXymbZk71iV+RK7EMW0HYWdvWAiTrXAuXlV/LzLWB1ue1nWeBwBUo9x83ph+R9qdoujoDdIOVh9th+1u9fAdcjkyr1YsD12LS817GQU2TeHXOdpnYDc/QwQu6ld/GzbtzZQcK5rd8ec32X6XazCkpMBo30f82xlQR+DAaDblwRadrn2z5yiHsOFN01XoOMIVKjF+NmW38oHgUFOau13uUF16ghMDnrNNb2H5rtU0EW5892H7XOtP6AcB42bhcUY5GL7gUF9OKFFfKs/mF9bsu9Sel54QGUXDXo86ap3mPeOKkpLNASVybfgOU/pC6v+e6Vnv7YT8dGhtX6WYWO+wmVd2L2Gk2cxeo0tW8paa0f7ABLg1ObvjMIjJxJ/Nmql2npcdhOGYbg8s6wiuw7ffClNlO7OkyxBZxNMmph+SXVfWLKp3L7/oYIxMXT75aOQ9BhBQOiXf/XbI+FBGNXW/jEuPaRgxUSh+2JGsu3lv9pdV0QcYsIIoH9ClqLi/+KlqNdYKobybACMS9ZKMuWV2bqYMlh81c5uqmAFtzCyeJV7gbwPm+wxc50FnFYJPrRCyqHMjzrIt2xwOQJEkt56kexTcypLCu4uRB1lubLOYmuMvWPOvGOKn4Jooy0IfZLtbLMbC2TsvjawWNIIOxzJXUxrBnUlDgWDan7d9a9FZNNr2Q0tGywBGKWCFpnUdZFeDgpY2hkHzL0ONWBERhxkW7KG43UjwVLILnFZjzHu9ybt5s5ANbgPaqiQYp0WInz5J8EjXVa2jw5k5YBm5u4xz5aw5bKiJYM51frhqaAeiFINJAaQyLKZ0fBkpxZo0ulid4Ir6I/CRWS4a0+OqRtPFW/uhNXrpHTJ+0EfzC/Mr2gPM8MLppHCjDgJXDi3VyY85ssT95jx8PqMT0rxqfXlKvT/QuBrgud4SHb6v/5gWpeghKxJXBnB/5oCmM8OSqJLufJGEtYSu15ctY3Q4iJ9EgqeAQQz2kh1Dp3J1+/r57GGbTy8PyeaoRxtyHfQUDNgNT9KAM4nPq2I7r7mXgVdp9q6iCeLcpnZ7qFjcrkXmY8guZNHi9qyl2fc0yZ+LgdcHhs3oHnTs5z9mh1BDjcUKL44ON8NjQHr74bUuzg1BtubB3givRqASP9z9ibFz0I+AYq6I9lBL0Gff01fgL5FXtF/Lrkqo5zFRgvTEQd/vo7Xje5lDUMJZOUtXaWi2NXgsGPjAFgyknaC44y3CDUu3B0skHVKjYwai8Vac2B1ScfKWYJR3buJcqP9s+h6g2gxDF43orKRPu/2X0j6uz4MZP/G8hBPECGttSdZ0lJBr/z8RFlQgFdKB0CCE6jmuEqCTePzz47YkkmDoT+osRH5G6BUx9Q9zFK5VdViYq31eBkbAstibePXYR6E6fkZDGPLfn4VzhTM+beJ6Wq7kXnQSxG8q4wDNSPQpku7V5SG/mLbQtFCqv/22ud8E/iv5BVyE1YPB+zblF8XURN7CtWfubzKKVFvi8gsB+4BlDK16xytdiWMo/maYMWuNiQeXarnMrRPA/iLTa6vysPgjU+wtpkQu8rbUB+rElvAq9PhF8ko6iRMFgYqFDHTbqp4L2NipI4VT1y5KEpHbH0iTPAbBaU3TuWUzhdRqMKRnwsomMxJF7m3/c/qE5d1O9XqfVcIjw8AFBM3mGOxSItNGNsd4oPyX1UEQSISegSNBPDjIvtF9Q9CRtCfZacnyDEW7ThWg7f0slfBi+rB4y7UEGu9BhJCx101NzxX/l9PHHZTSMmSmYzhe9hRpHPrqkKMc7q+siFnCYxbJu4gVFzFakyQdOQVRRy+/hjA3tawUAoUMjYYEqw8zvtEKsQUsx6el083tJeL0VmtAZBDvvifRa46gbe0kjxLMBihfeZkn4dPT5rT4MSUSyOU8qjoKHMR6WyWcB51o41ZctR0eYVMNv14eqpWa2rBXf+yYDvfgzqyR2JbWSVoT2pjwPDiEfk8SLBmNEaoFVRZiLec8gyCt78aHrep592Yd7ofrwtTcR7VGieTA+O6SpAeJLav3VVaZFeorEGNlSUfR37Y6Vdpzsl90jgALLMKyy9VkFu/rMIEsG8RceC+vhLkiYQDnIUezsT1uPBQ+5uejoizGSNw4CeW/iLiJl1hkaT1G2ONDTis8Lly4MZepwy/PtmMGpiKnOOl9+rs3f99t4slt/hclTed17KEAXjJgd/Jy6JLCRmuytgC/1vC7ihkfM6NXq99v6xEk2ObA7UfBMO/wiKqQvIYtZZTtmDc4sB/4iSLmQ7DyveXNoytn2lSPIrhztV0Lnd4BEQ21H5b2N3iwzlEKMJ0+Np7u8PKefCm/3r0bvoOIDTWTyK+evKtDAMQiEx+GyB3XeWbM5EGVX+3Fmwo8KkWgC/G82TYGBWbI3cnrxIW/RLVRcEtNkg1Q7brGL6nHknT69Pvj7c5F66AlK7wDV0YDap1XGbQllmCvqKi8isgmM/lvrwM2ubSET6Ffa8mCEvnIIjX62Z94umkvlvepEqUNJUKje7U+B/IkyjiUQRj3FR6hmsroUyoFsjz/WeDQw7kwk7UTzarFo8YKTdrJ4oqKCDSTkr2cKhuBgxf90VlHy2CGFpz0/in5ZbGiHR+aeMJtsfiUIRTlRU1z6OvimiJgvQR2PA7UCg6bRhysPwZplfjvK1FfYV6OFFFhUTW8S+Se1Tj/yJLs65KUFfT7g/c1Wp8oCCUvVj5VhRJCNWBSh1U8cUdr3Y/65QFTKVecjKF1QWWVnxPRw+icC0uC8n91KG1chp6QPLq42T7LBtayHjaMcNqo9d9rm1NFVssICPpnb9vQNi8E5dWTdWD+MG4FPoLVX6+b+wXQuTUyZDZudF4Nbx/+5XsBYvrfX3jN7gKiHjcfaf+Clm7TTrrW8QH/4ZLoj4CGd42PXRTuC2ps3H+8Z2D0FlrLCYYMjUfIvb+hpogb6iXLzjwWA3YvAZN97fNvqsKMltwAYejt8IE1AaKnWqh404zp+/v6c8AzdUVxReDr3RN6Jfcw1YHfRLvMXXjAdAlYnHusU0NR/y2SCk44DNSiEbBHdaoK8on8ST39oS/po7mk+AvuEzacj2+z80xn9tFxj9sGgCxDt1r+bIxUtAY9IFJarMgHAE9CNh48RiypyPSwOoqHM07c+ukwKKgUEOYf8XizJZFixGbraAhNWpmBm8A9p70CKgLWbB4m/k//2myWOsaPAMnrz/vndFHRwn05BMAs2dL9GAGOXXt5IKtC9QcC+8k98cE9qWTZlifwZOdwGwpHr8iFzK0jHmUWSMUd2XM4mmKcV2776nxGXA1n2TwF1jljizVOjtToJLWjI6hm/v1M1PTo28hWkQWxGqh8aOBKrFKk8osu6Oe19weT6zDdE4yuGPnQnXN03QmgBhwJKFh3d+9KxoDr2AE+n47kc0sMixnwjqntuntc/4WkDIWSH0w4iDgRhhHQVHHQUOFKAcBXkZNYjZp9cVlCxGKTti10koa7ULvxRwyAvaAoXfEYpyXCbbH2pUDzPG0WUjBcrb8LSrYD2pPkMzDqt7jz5IhtajC6oN+olW6ySHwM3x/h07a175ZinmftH8uYHku4BmvyS7rRs/rkA/dHGlN7GYs4fBt8dghozWX/yXHWuuTo+HoF1VZjzZ6JdIPR7c/y3Ex1fmb5yx0a3gTtOYP7GcXWnAnA96ldVsE45t5MbDgyu01U45s2tR10XnSB5yck/Xvi6xz1DZ86686ifUxtT68lWBGkZh71LrKs6SqvNuixlxB8VA5YXkNFnc9wEHtG/XNJCX99pZC0XXNTZ+M4aKMkEtVkFWeduiAFuFyXvp63B/Z6gfv3nJz3W8DEoHxf3Rt1pHzUwnPm7AoNrpemJePh6Cn3w9dN36xicWfBs65RYC+WckReOu/+eI24FGlKjg62NOVy8uvDVYKtKhnP0JrSSRBLA1fkFl3k46sMT3BYp8ovChV1wzgXf7GaQxTDxgP/0xqgGzHqcvYQn2H47Q7t9DGQHLn0cNAFJozC3lOXxjPN6cH5byvDW7KXPF/hXEHX5TtGzOD0907Cvl3hZGeDMxqW/OfFcOFwzYq97S8MUZec2XpOv4Ky+PtTxLAoK4wPXlEoDC6KVwIAomorKaON0HwaLLKC1id3ifD3rCzNnhypaqBbazVwFAPdRqFzrfABVlGsWJzUhehUkWF2afFJYpH8ogBkxQEUEyLSM5GIdd8otUCktyEGVn89Xd2eEKp2aa1nKanx7yo/mIVD7NkVUCpg6scew9WkSPdMAabnuwQ8yOMJ7HiyMTxEzj9TWIwpEALDpm09Iq2GaybbBd+v/oFBwtziIcd0FaKzM09abrc86dJyto8GRyQdeSk+c01UsGwHD0lhN03kt4YowGS6CpdkzuBYMUPATMImkDyi0n/YMGR04i1m7JqawIkRwM4Cq6ik0j0AW8EByR8qVTLDC0DosQU/guRAiCKi6PDssJYqxO462zjiJHguEODV4sEhYhuauF5MlpDHGMzChz68SdujnEmbc0cwZDAxoEUgVNKdZWaCxFm6rcNCUaIPeQmnPSbrFBsAAM+O8WNChE55QZOQBTkGLlcoADkESFJWFwGIx2IZXuQ3KA0362OPYPHEPjLDCA347BaFNCEE4BgEcRSEAE1GGRsTQjAyymIgokTw6CRue1XXsb1zD0jMnPXbwlJ4TksGIM6AMsslNW0D+L9XLIpRTzWrCxHjVecm8MOcmzui25vGJZOJrAa46k5u/R0rNMQqP85EAb9Uu0rEiD0HTcENniNuRyRpx6moEvi7TN/hMtE6teaArWuVyxmDqaKW8apcG1WLlhnhy7yFuO1GQ/oGXDqhN2DZcWaGf+FS3vmaljZ/whyJ36P2i5p47CbspEg8zwhs6OrfpLdPbSqtuisz+t+gM661v1z+jstVX/hM7eWuWb09ORcup4Ox6h7PZ3gQuC278FbHX2t1XfobOxVf+Izr5alS9Oly3VILzdtNRT4V2OVL3wXh6pJ8L77IjvPe/LIwWO982RfOp43x3JJ473hyN1Ibw/Hamz8H48Uo/C++sR6t3zW0Bpr4KKoOPz0da/+A9PdfHFV3aj1i1fnfuhWDAO7lsXkbFzJ1uUjOoy+Q2f5j7y/JVL5LtJcmZzYYDPjfPicpid5zVoeR6fIqEaX7iBpcUykoxF65zDR+IFpsbNnGdDJA3EbNzMOd86HH5OTJE0EPUC04W7Gpbo07mRiDRLHPjBWABs25+iNhEcw72bgIV02y9ReYlSsxqQanwLCkeS0PWpQYcgqB0K4zKPuFIu4BmFiUwtOkBRnqPA4cr8P3hGmRLDUcoJUqZmEJZQVbeIon4/JlItAvsIO+xOpTvCitqhaST1bJzP3xHoZzoyh8K2c8f159LGCAH1SmAWPxoTGZDAHuDewGdHdXGGc0xQVEHdnqfEcHJgOllVv1+YiSE391DymEY2SoZZLcuzidn3RQZFQ5w7R+QvogQlAjNFkAoZHBZbgR1Jko6eaAmFulVgkJZ0jiKOaEVIv81QCR6J0BhgcQ79CyyDoraoLqkRVs0C+/C/7JwLW4H0bAaikG9wKCSg7q4HpDzU0E6HEdrf49GANWWTipZq2XNsQCj7y4kGukFie0Re2HhQksu4nnZQiGPaMTM4noxe48yA3XbyWkeLMJAGUpREgL+hRK3HNJRD6TOskx0icgYsdaV41y06Q9BrRLjH3iSE1mcqbNk7IGw0wAc0AVXw1hYJl9yGTtpC73J0P3yioPL0qB/gpFs1UrvtDtEKGDh6smL141mo+3WyjF33kaFFVuKCEGpFivyyBdtasKc/pNXWdlgpInwAdy+nArk0FI3z+6/I3fcCKc4PW+DoETBBkTLiUdWIODmjh+Bq5/uye23tO8I56s/y1k6WKg8cG2xb3I1Y5s4PIc2m29KzDn1gIszcqcC5MftIDO/DgJgxoXDMddnjsLAzyBwh22Swkn0FvzvjxCDupWI5tYhAjY2KQX/2tbHOyUK5OpFlhZm2JUgtSNT9N8kTmlID7GHDLSrCpaPnas7WE0f2yOGoRxrTLyp/B6Y13gmUj4dxhFU+0qUlUWyQW2zAPkkeprWWUgOaKR/eJ+4kIWVipMjZF5bCBN4XLIO5529nnGUWMjBVOUdA4hmSYQf3Ylh/NROCs+TwriifsgJM46mFFXBsEDwtgUG2wrR4iI7w6h3NK8+NR8DlG9fotKUD7cXnKjjXvBWMOYgKjsBrFjUlpDsP+g6PBQasHDrSXSq/81xE46MIanPIAhREJm2OpNSATkwngB1uZuCzr43imEAcKcc/n7NPa+jBtF+i5kn0qCgFNhzxklezFinv5uBZW5eYLu6W269RmWYXH8m9Wtp/tl/PP4+K3Y+M0sTNvcdHjMlRlPbauqPnlyjMihKYSL/enI7zeOGE701GIRiDmmWI/SkngsiDoCTcv18ilJQGu91PERbIjCJDCijkJGyFmJdKazM3jbZNb5GqxkEIRkU+0r2ntlJkajSd5AaP871qS2MwcIYS7iknE7iuPmIa2cJ8xiloMRM69Vr3PL170RTuyMwj6HPaNUgsj5QsI5JJXNWxxVoQZ3m/rL+IpMKo+ZCBIooouQWbQYCaJNbAYW+aUNPYzuMm4WCman+vK6BSr3i4NrCSLlFjAYGxLhsIGVYHZEbKMGzj4C9N9XaSH4qiJM5dAXwc6qjDQpyOQPNKgfSlMuCrRhvlncq2r+RawsFStSV+j+m1clMM+VomHKnY3bNvF39N41sEvBtxZUdpC3dEwKBz71RaI6QnFUFsK85Faa/LzoHDi8hC1EcA7oJFt048RIGuCH6YNC6TE6P5wlIckBV33D8i9aE1yg4pgYmEE7D6NHBcLlfVph2Qah/elcCQsdvd7rSkm5uHRdt3MimfMGAYQUH5sJJeXohGwV5yX78fE5jGNAT5jcr2fXOEhRgYufXUjYAiPuxl24je0vMdKVum4rtiT7aO/5Q9HtT2Ck/y6nQniKRbikVk1PaEK1nkIyL4pcA9GKEdor+V3nGdoV2J4SIinxNWXUaqSAsytJoYw4deZy8+Es84no/RuLD6aAQPJk34kpATDNcqdEqLQbEpOGGtp9oEFsSpdtGZtC+2Xpyi4n90vT+qbPV4HBOqvpLfiIpWjCgmzz7Eci4RBnb/kXF/8e8PIci4WtopD/sLROhgRgYWD/7L+rYzatYIBLATVrRSDD9uZOLxbRXjwoVzkdkxyWHE/hx1dqSs1U3aiw17DSnsg2vHxOVDGCTKXc+brKRiOJHh7VKRJYqsHsn49vmbGXkDqLdycxDCzWRLwO/UNqFVhTgPYPyFh6sGAdHv/U0dKWGxJ/B0sbzWMhzyxgLQJWUSUR6Ud9jf2f4k4ahN6GyL2Qyk6spn5EdAQWQfpzftqLI/ageGCaRUsxt4GnHmxJiVxyLccmrjvfLtfhkQVcppP9CgA+B1ln17hHMSH23N7SSDbbORW15QrxyNK8EsXqr5+OPEY4FAZdGwXUW2LeTL8kLfnkrC/KAAa+AJoNCZbL30NAJ1JHncC2ZO7t5tI8B5nBnBXTnjpmawh2I6+7diwWNGahCdTTgR+BcEfKduklSMDDMjoI8YM9rRx0Scq3HE4K5iEmgnSSYVrq4X7slRXspZKPF6HSvE8QIstUcTt8iA9FyFq+sj3umKsNAWKhOdDn7Z7szspKaSx8MYJ2guXChOdsCdB5IVVsfE6glRztAe+uQaiw+ZMYwNtw+Yau1EE0COeiFl4k8bTEMg8xXxohrZeuLbzTX5Kfiu3lHahEdHcebVH5jDdj6WBPJSUpxPmcVAVWkl4g9ho+wjevQIAUvd+HKXs+K3kgcO/cOeaTlQsBcDB863wS9dgtjo0rykEGgPrDUHCm1U/qGYpnhYYx0M7PcF9+bfJ58tZ4U/KV7ts8OcCB5BBswEEUK8vK3pZvvx63Z/Gu1q+FqSD9MBcSx4T3nJcf1l5DK5DAqK8hL1Z3r7sc1aM5Tje1IQAPLoh38GoifdT5oeXSRrzYS1oFlFOtZ62B4QA0BEBktyTCB117/qHKoMZJhpXTe4jdRmqxwdX/nMV8/XtFpVYS+VCLRYRHBji40fsjRghTXVPYtF0Re1lB4/OIKeajQrQb8ZuCvr4Sv982HlnoIcea5C+ICiN307kVh5vcHBS0t+dh7xTzzA2NBpPtKExgLcrr7Q7LcpbT216kFsH152FRmQGCpMONga0pxVVyI6ZG0ByRvntCtCFA+XfcBivPDXtwmapITRqhc53POQdZqienBh7Mmo3O31nKfZ5qVbkI4O8szLZk5YgLUjz8lfuKCSHGG9t4VfOKtCxp+0Amk5+OJS3s0+12WDuUUH5hCMJMHLIm8HIibjnwWOs9JmIoBjN47SE7TqT08a0ASlO6Y8CO6xekw8jovxPWGu5X9VeOv3pV+yPU9KJs5dYghuDNwaC5Kqt5fGpZFxeby0TyLpqsHShOd4+9op0dhvwl1MNLPivfh4ALk87UPlqduQP1K+JStZFjjXAA0A1LcpTZVEKI8SwSRc3X8ONAeKIhY/SGsclNxWppPRPtaxu5Iecyr3vv3JdL8Ut0jt8AFyF4IyF+S+rOEPnRO7E/OUuKgptI4rqsENtBLgDfBJTrmRaLUtIv0jQVWioMWmJZiIUH4upBHtxQ5xAstXKNp23XWkcfQ67NwkinNR0Yy9v5MO0ac0+Fh0fjTbfrsr5TYlOiq5DabIDXdU4as82WKxvDX/SHiaMko5fU+a29o0/wV4pvOhn67EvKioLUBg0aN5ya4gxVSDXlK2gKg7zhqUpcXa1a2wYQQA7FF9BoO5MmPyoJLb60PFHUj5J90eq5gzEHVRnD/0DqHRV5TtIM2ZYb+brFzmbdymER9lCA0gmB5a8VT/rbrJXy9o+gZo9X20ipK2AZZ8eUXotZp6qqACqy489h1WpsOxEAUZDe/ZQK6Wg2kAs/NUhE23FlIoqNCcWiyrVoUVHzBDV4wyTKUIz26bLRpTz+NAmEUBwUEvGyJEhkFyYUz5w1Fk5LzfiSI7gigK+SNry9dR86ATyiLC7EUa7mHsxX4bX3WEe9ZnC6YCLpG9ZgWQP3RFpkIWaenKmbeiDWZDx+dCObNmh7Eq33+vR/ZxbGTA9AJTYSYU7lCeFCedzmfdYFHXJKenFQNMvadVVZWvBBM0RVTS1osyCcYmCARpyRqBBOaUbuC3nyI8CkInwiB1TYnt0gryKkMg/Pfo2nJ9udEO0WKngygjEV1XYsTLVsixamGFX4HQdOuKoIQ6uUflo0y0U/y9N2VbP7Tth0dLvmgPp5cIDduPu/w9sWWszhFBDHPXthlHir41wQQA7Kvr0OUGiWfUWSMhxH9Enz5c30z+cEaXXqqprFLFJSUKQ6ICpAzNFNxEj4NFjh71LUwL7cTggV5XHbZELeuGLbe2dqpXs6vwnOf/lbkixkIq8QmVXEeSQfeBrQTt1EtZpQgh/mecMYqsC7h84vYTRPRErKLQ8gvBaLitIAdNQJIEum8aSJAkoCHcpMwXvMcs0snATaw2fCxJtLKlgg3OB0ekWA2m2CtMt3h+Pqw5UBphUqQF4tgBikOKSCVOchL2FcGV54pgrV/u8UhVbrWNOTSsYZYI7n51zZd7jDJD4ZpTMDsdrEW5SOOZ+TRADSCgMEBV4D4AaO5dIrcblQmwPhVQycDWtbQ4HCplLbJ2y3eiFRSs2v8ofwizXyzzf0Z+iLQ+uHSkjwkMx2QkCSiIXv10Wu77/JtgvJIXIi/8bdOUKFn/TR/2A8DKRZE8Wg+CDTxch9eK4mpa89IXUC3e3/2KQJtrRbmBgJi6ERFW1QUIht2yjwwQfBgvSPcocPEx2glEFH4XSemkPiiDwuDIfH1aoVaTxYuK+D0U5CtqxJq0AdVAgiBRU0dtWLEWwoDU3BMEtRDKjyD5Pbo+RgikFGdbioLMBsXGqLFsRwUqiQZ23aAaHDJzMqXQjzcesNrXYLW+4GlPRXOp5/D9DnJ9V9YlVPn75ZdQm1iUOHLbbwYf/0/DAe+BIOFwBWkDuTNoSiK+DVPeJsCtmULEbm6U2uk/02IZNI+zRbGCeYuVG1x2C101sewvgLwBcA25Vrstl02z2sBvfPZsXKn1VI1/drg8PGy9WAfoNIBCnRJx9BQqn4EMDpNICgJbFAkW6adciK59EXUDiZftRtN3kJbOuBOmivwv1aq1RVWxye/stm/F2kYkTcbIpF/TxU6TW4nVZ3Ws6B8gRoRCchqWKLlRyREtN2eIJx120WTtijb57nOUEcOZ0pi6bT7AsgMGDSQS/JetFLx2UC34e+UvrLD4hJlAsnn7ExElubW9sNyEKWEt1XHgWuKGIs9BL/mji0SahsSxStg/J1SPxoT78BdYrB0iZtj6ZGBgZFhDU5KjHjClVQQTi5GKpvDm5LXQGxBV2ANZQNX+ZRay0foBK/vpbtnG7NC1xrmb4x00VgrE1ZVijoxR1pC2qjH6zrUqGYhEZjm2HlCw4a5CyCNRyBMncjfOcw2DAF/MiZ20yE2KTrBiYIEV3GOE2Oy9kS600BWDRTCVobHwo1QLicufEXLb1kiBeLq4nEalYg07nFmUWinsKlMArk4MZ0hidynRhf56MCMFYlNjhFOgeOma3pxy2cwPsoj3metJGAoMLlTFD9h8/6l5pVKg/Z/aJyCSScvzZtEtT8CV1+0iKlLrzYZZtaq5secbVdpdgUzsWWquXM9JmqufzJ4Zgq2WEoSBWvsh7IJgAcXGsFYzgcbXAT+sluiSa6YT+spIOrImQ8QFZmMw7BhMukhOennltVk7AAT1fZ6FBu0en86YANxTW/cZ2Mdkb9T7GhaqTbjPdFqYVYRwZHRgsIdRSQdBI6nAEjXX7C/Qh+ooy6+1ciA6SslXZ/tfE3AC3bGjJG2egdpgskEY1fIRCu3KV4+U+erwFNkvES4T6JRe4dOLPKj0yZEPmJkhOFYSmCNvP4dpqcTX946lboueTGFxBbH/q9/4CvyV7GMfEWtwXD+XpkewE8JLWbH1XhEL0fjGb1EfZAAVrXipocSlkkkFWNkQayLFFVavG/dbBkHM2VCiV9CReAvIRtOnIlJ0APMxa1jEZLcooVte35QEwh4VWf1WUeKMuHURqqvdUnx2ay6Nb87h9tDOTsMwuL6GVrGHI2HgHpwl9ZekDqRbEee6xRxDLdLeFDTCBFNpQbhFYvHpMzW1us5HdnKizGs7WDPxWcneedEgdMGErcorlt15nyFwfMqJdyjEP5bmeM2CPBZn7Y4g4Hlk3Pt7Vs3wavQA6PXmc6QmHr3OF4s9tAFEhl2lHWPFBhYzCRkWXRU0ebjSlmPFeHt0B5AeVtJtyxhzhBYB9FBMx5FxtHICirLJQg1oiZnzSvXp9+T5jl2EDhqwjHFmbLFGfJl7yJIdP4XKZl4f7gIPwGsrUa8/CwEOo13Mq27De/akJzdWvAi2F8KyxSUPbyopC/loKqXEtFMygtD6x6MO7HVPlARFmtsVy8wG5ZoQQT4jk5z+lQ3kv8sAZWOIm/+ZjpoarlDSxX4cbygv//O4nTyfFHbNX2U8+sS3ArNv7B6YaqK3/v2XNXaMJOhsh4XjjOIZo+FLrUO3g3hjoS3iBSAdyq1MoN6eJz6ftW1q4rqDbXTVZj7wrVZQ9O1uCoLQ+ukX09PJ+pKhKFjziA+5p/n+IK4yB8aCgxcPT7xVTlVCLEmJ5BjEjgYQ6qBOaiyZj/aawKBVhAex4A50MnOJWmueaDuxDFUuR6X11A63HiYVUWRruMie/NswbIO1zRCFTiwEr2tSlUS78YE3v5TvrAsnM8p3W5QRYb0tp81STOuCqh+Q+8Ts2p5H1DZV1aQhCjvSNuQawbmQgouUhRyD7yL6uBWazLwPPIVI8630mCPNH1xhDDhCGSGIQqkbC3bnU/1YQ+abnH8+XfkVEgKfnvmd6lkSX3Y9tXH9VGsmUFg6FELO8yLcljZGUMC1n6X/QTMysS0KNcooYfVXknrW9x7bK6QLA9Dco71hGp07E5gP69gGag3nRWmsphwty3Ds6yNGVuMTey/jCABfpP1tNB5glQ0zHTK4KedroqVLWhClve466pLGSO+eYzL9t+vtJEFADU1XnWVXqmL3T7oIBYaZQAWpu0JTW07n8iNmh19KK7jR3E/FjGVgnuryQa/xrHT1tIK+bhMVAm3hvMbBPV4q8m3ST5Nqgj3th3E8eNns7WR23dCejXPBDw7qEkLX1jzkteNV7kNHxRe/4kveFIfRWkKwI7CPI5N+0UOlbF6qb4D/iHtWlJOaaFYgyPmamrKYG66y6BNtqTVnQxfY1H8Liyd8nl4pbjeYRZ5KxGZSp0z9uaZHNw9xp8OOXiR2xXJ9LhKCh7rHejhrAuhgBRcaaN20z/Y6VCrOGuQImup+Q3hEYRZyKxFloYcXAksxdi34OeTNtXPUvMKYRRGRiag4lJMR8qPuKJOVg3Y3eD3cLM4en06HvoHvcv41scZbNU5DWEUMntQjmIbukBEVhNGiGYYHEKHXpYs82J2wR4bGbfgGmHTVYGAp7it720E5Da1XtmIuMQtnnKkEEJKpll+9+l9axJIwaW5pqDIVozEq/iL9XYn9m9AprtfzAWQjo0y8My8AfWjS/wGI2jH2MxzyEFDwcj8meI2vCqklFXONdP+nPAkeK9ZJsU4sZ5HcumM1TwtzGJebv1J234m+bzrh3ZKZADtO0i5szOHvIeRRESosxRSZ3r0+4WuQyV80mKCMUnI3G7XshLnh4QY9r4iFzG3l8oFa8a+Yi+rjvjmnl5mSkDdNdSamnO5ww/FVmBsWmk3X2x+Di1GilHO9UWSoGOlhXt0J9VeIsXcfyNveK/T8FpyZsskpkdBD2Ln3Bl8j1E2Mo/W/Q9CBWPs26lXD2WUwYvK0oA3HCqEYO3ix8Fw9PcDnFdMn+upJwEb1lfPG3OHQ+c6YwdZOXB5SxGpRzXTL9r9D0MCuAvybeHUl3Ic8jGqZmlFbN+uWuj7O93RG0ElqLTTDFSc7u+BxN3q+lbX2T3Ee5PIQfgJceLha7+ODJ/xOlUL+9J0P7wkwj+vqROyecJSAhC4uK7wGTxqupNcyfW9NqzNsd6lLeCGg+QEDTXKiTApCteeh/pCnaQta9/DcMtoDnzBqtG3fUmCWhBE27o0ERf4WxstA+DIlKZB+FlJTR3NxrYoNe9bHAo10bkSOio45KEJaa+lwJ/gGcqfSSXJLJ7nmbyEzLl9IZYzrWSdIde9W5FU7DL8KXxRMX0gX0+IWw9YIirIY0+4tc+JPDCgKWr/yUEqS0CA1IUBaNvXkOdl3FOGiSB/js3kgsaWK6hv8AkOTBLYwWiLFKAJVUD/FEUc2VrVzb4ikn1/erOmvUXM70hBBvzf+bH3UY3CCA2eMUQh0omyK9chw5dJpFwXVk8XTRYUhhFxY/JbXLqXBfP008SIxUd5jJbdEA8uEr4N9Ir5yfRnlHU8qviZ89DKSpbwKqp2q6PtB0Ah00j14NWMo3bwM+00uLTGePwTs9qS0zIbJXBHklNl86QYGj3Cogr7yv0PD/c01P4B7EE3ifjtOCY4h+xMR/8Xq+Ab2SkHD9jUQxXBrpXfoH4N4hrAphsRz/Dg5biXccbBRkVNSmkb79myReqT3ZrJ1q2q52BzhIt24LrQnlVgbFpx4o2gv+xzL1sV9wHjmnZLbpguCKkSeP2IkPHU9FLRqv53nGAotJzptadw5ke5512nDoOjQ+gP21ixHAyeRlMtCAjYXQtqSMX1lwWpavU8vH4peQJXgRPP9FgXL5zowobK2WJqTmM9fdes/HsAECNdw52FE4IdsSlUJ9KjpS543Ys6odA+R8EWIq9SkhBFITWZCEmlXYsFF9in/pujGNFeZIqQV3dqYklawWC4Jc16fqFvj/ouVzgY68LdoTIwSPPxyUZwwC/sZZ6wgoCopEB08yS/SWgmYiGi/B4MGmHT34pVXvvNZqWiW9OhC9bGnbQyVIP9hJu4CILIIEpSr4mpU94Wa8BT+zT7VCpieCqOth8pSdlfoKm66cokh5t2b7WIjVdUHCTvw9TCsfHkU5/Cu8hD8xWQiJb1KWGqDQMAWL2Oc/YSUL2MGLEQoY/cIDULBOCUP2Z1uIznRk72lGi8hDTXgtG3PEiP2ss6tHIZQk6atUopkqc+pktcl4+dq0aXGoFY2b1bQo5gOXHmOapFG2jckusRArdqBxqmUcvxYHIMiPEkbg5MaijndNw/yMAmr63kilm/e0HiZOS9SbdXUJzhnbjW1/GR5HbLIObwiPjiyVG79xXdS7ZLiIZmQ5rcPUdtIQpshxD56XgNpcUTPb6V7OSbz3+A8IKT4ykpv/7XgrE58hkL+AYvt9aZiQExlHClX2rpF6CrEcqPYm+PM/Lnz0G/v2I6zaT6xG+hzittm2Z/X6gueLTscyYeoZE1SLJISpCgK62RHyMykJDNbWjLA1z1ZQrl6QCZLlogd9N+HHsmUfvJby5tpGewz+wQCOCVKIQKxwMJM1Am+qoWwvd1E2X42VuQik8v7RshE+PKcjy9pS1nLrQsTvIgTpzX7WicMKX2OEwtSSkjo54iEiEaftUtnlcq1MjgzKaTzMiTpp4MhWmjoO0UvQHsrZ3Yr5AVyyQLRSUEYtCOIjSPvKvMxUUQjkjYeIac3cUFbge74L/UjCgkYcJSob5MA1W82TaI1vuyYWmpiV7IKGVlYS3YMYaFmWT1odujNT9hBJJg8rTbBckwuGdGrDEtUmWVX8hW0K5KmskLCzac9mfsqVfN/2rYERZb/pBd6JEjUEDCsTTgxIl79aSbA7AmdP7P5/8/NRxbWw68JYMB+b2YgCNT42fQwghgOL5a4eT83bCBTSTGLkxk/U3SzC2OXT/MerJT5VO2NtHIy7Mb3kerlHEr1ts5uAic2uZvOsjQGG2sddi7/S7KuLH9cTNSe8FUaOFzxNWgh7cAG0rzZf97+gTs30z5GRwDanFa/tJDMxSVu8UQJY5Gu0coGMHlDtjnliE6SwO5VOKBznKFS5WXcx95XZbodS+BPYYNfIYHNntD1Uue457SwuS8pPGqlsqI9SUU7P7dRg7j3rnsH08ASSrVIMWC2sF+xPV/ORQPCcRxT1RWRGHCb+KGDPfJDadJejq1CzAzo3sg9lJEPNSZeGJEOQxGrlZvTUxJWZ6l3sO3Cgm1KzJY6QDwE6pgsCdoXCD8K68H73ySSY/zIfUjUMLmmDgI4ZWNlIK3PUKi5UAOg4e2QM5CSQbO6MFnimlGCXBABrKRHWGafGhm+mbl5TNEzLpDoJcDzDE2fv1rE9M6FLEf9LEU8v1ZhVj7KkDJNc3l2/xf+I8cxR1VUqvFNJkKztk7gVi+g3lfmyTsFkUKNfUJpzexT2AALsEMf1zlQu3WpyO8PogK0UePbnjStJ+uV/XVEsKCpqHoFVUmZgpWb1b/THYWmD29j0ZwMN2ClrwXHC0YdzsIlQB09R4GSBeiEQAGR7voQw31EYzhYSG7Mh0Dwu1d/MG5uqBUlYfA8aY5g+pJpPcM3FWKRDXKMqYpOSorCPToEruRbsXnKOAsP0CGdJwVAmBVH5jxKNG2TpBLcuUiJh982uNwFlCDmLvnIuryH6+MUnRbmgxtxp9qWVXkhzHxw+W5mDoIVHRNToqLCHG30d9FYIigAEUGo0AXhWlZq2AEjKgq4WJu8oSN81bLiIxCSmxMwZrHhDbFEQ7paj6Cy6SMdFzOGzA0qsXn4SMZGI0lVlaP982hYi9Qkbk0Id7JQHD2hNbY3voQfiyPK/VvClf6M0NDHxPoJdHS9MUtuOI57DPOq/tmRj/mazmUbkISGZ09ly74Zc1F3ZeoAb7gUAsKDOB5xWkJyxgRoc72XXYsmFWv+9SiN9Nx0ka2KTpulqdFaDS41ixxfc0y6715Vc+Ll5CfdqcREBttY/bBBjvVE/jYKwfbJNLJVX3HSCpCpzUdXbt0gkFmca2O/B7Pq08IJ1tZiG8V5Az47yQhPLVJYuavxEmaaNgiEqUssZ8rHYrcbk4vkMV3ZNAE/G66US0CG2KlSvCnMjZDySwVYKoEHxeVJOSgQVUvwKN2pb539snqQqLJHWVEzoSEelP3Ip/MYJNk2Uh/XfzqdXYLiWJOtyvnY4Tmi+efxck9cdedPtHggLqJPOqppYHPnuenBmi5Vf90llseiuufAEB+ty1Dw30eoiiRb9MsS0ErJApnTyp2mx1MQuWz5H122dvJgEj59s+rxoZFuYTQh/ZaYL0/uqZRi110KkS0c3S4ImXzOHP2fey2wX4z2K+aCGkjl9aBKSynnwirXA7lRNFVDwQBv38AeUQ1P1dF16HpS0NbP/kDbFIU8AXUb/z7y/pigBb065az2LJElHZE9L4dKX8DPBmjsebdg7YuweZb4KrF0RoCwZAx0npWqSeW4XHqjWZ9GyFDRy5wuchUEnNnM0qI1hcBRv3EoMrHoLI4GYBp8ZbdedTbKFcME8JhUJcqx1KAuRp8bus40yntgb8UtO9KCyhBs5BXlCzuSqRTpoMMy83LClIiV6JGRxn5HxJCRvQRYMQslDqH5zwVvAJyEQSsF1J8dcY4ZknJkZIvSeOwV80hWTuf53jztCPY6E+2gXCAbNZYTsXXMKA9IKV1Tn5UsuuYvCTJYUz8mR44cThVYpjrjJ31D8dFxLYGq+G8dpBwpfCz3gmMc4W9Y6YzMqNfgkISr5zyQ01gKD+n44ED/6Y3Yj9vw+L6TPmSjltLO6EkR84rjMFypb3Bl9iQSo+IFC+OVP6te6bUcovoNoa67stITS36EHEg4UQCWsy1iBJr05xzRy0LGYDDNLh2JR04sWmLh9oYtmU7+eDlmCbDb6DtTC/O0RZzq4cSdkOwLJ660NFfXOoSsKoQ8YMS2Z9t3ovzUXCMrK7ZjnNDp6A7TcNFCd2tuOAra/OSvi2aBYpzo/SdSA5Jgyr5vGuG10JrSecI5K/vi+hBbNSye+iQa4Jf2PHr1sS8rPYwGkUJLwbuSux9TpZGOUk91HaObXVO/ve4eKFZua+0q13Pbd9cXLx6CoDTKQ1d5x2TjrAI6PbEMQKf5fiqdNu3+7h8+siKfdYBk8xX34D+Y3/a5L+o3HqO8Xd8F9dvIdipmEW7o4mJXGlv5BgDHLau0AnK3wPFKt9OrtPw7e7qpQgPc/ShA2bWsyXwnw8LtYboAAPdC/fZmX1Y3ZG9Kw3G9LDv0TPviDKtopBd+/2Dvz4IwcqAy14uhMqXROZC+Yrkhq64ZqmRNJOVTdZyNNHOVZPruCC8/EdmqhY2nv6i9XJsE01Hfr30MQqN7plQLOFDqLHF5BPDH1pvnPOn6qi7JOV/hpm6Fqts+2ulsivO6Tr9IG2KNcDa3Gcl6NEbueH1OTlRvEgn52JxlyqS8VSPhycaSjZNt6XSxDoxeXVr33PAHcShnBqPcn7HdfmimFWR2Aah9MnOzTPg7NNpjIImu6yUJCZIUa1/TLpbXYkoKQ1cptZnTolK6qGEIL/YUwNZasyKNo3JU1nMyQ0TW0oSySE0aNRiHSGPOc5YURaUqjU53pmTmAP8LEs2iy/gwrdvEz9rhPhfZ1GEQ3HmW46xLZvXnwNCpna/ay9Q1XXZV3t0d40i2nk2c7PHafthsoMNYSQNe/o7P8wExBMEvpnzAOozQP/ii4C+aNIR28TkinAkMd/pW+8Q2LH4t8qHhoG+0/MPgAS1a1AsGu4lOZlDt3+KfwOfZ67E5J1Ca4rwmx8lL58WpHQK3pr4y8zXmR4tDR56N6kMAIu3VxSVrkrKkTA2NG4tz+6RVolaus0Ka0qEUx6NGc5iq49F6suJU8Bg4/s2d4sbxTnU+IRANwoFl08NhDQ5qKPSbKvDeKQAUjYQ8CFCSEJeFdSrIkU6W44/e0AIfxdsU8uZP9JQ9u4jLWS1lIcULzAK0uEeJ0iC0ja1yl4G4dBeUyPWMn12Nj3ViGTXLKMdjwtFYaqzcEv45our13377tbmLn4dwd+yTvgEjqFS/v9EABFrqNI2aN4zjiWd2PgZa85p9HyPhGB08PvvO0fqkRe5BxourOgtEQCfqqoiUSbyyCosUEli6AtPDrGC48/EWWY3bRCYLJyuW/o6FVNM0j8drStksuMmhG0GONKP6cYKJ9xaety1BAIQa5DBNRfpG2f1MgoYyMMx/cEr6MYNiZvvXS8iCbZoSPXhGHi8BU42y6ZtUo+CIhkiXFl6nPe+6iB0BOJ9AjNMaTQd5QczpOIm/aJ6LZlU90XgGBcmgP9GlJdJMon78UxmVjI7bnhGcF2TEZZU8yn+0mRLJHkvg14rDHYxLIH8jGWGAIGkfrsyXPKHkpFF4UV7b9OiVy5FIC7BHoCf1nk4sUdi7KoXgb2iREVizF1mVPkpXsxy3wXp0z90GdCBvxXy5ZZz4uXNHeN+yFvZW+ioFibAMwSti7dedMSZmTl03kUUEDcmbNNT+2jsHHrfuVtV61a5YZwPNF502GQ9Uzj0nzeVYTn14uKocUS6vTZ8xkAPPr+9y+uzNmuO7jdE3nz/iHZO1T7U+lK1qJ/dybalaXkxvRPcZEbjX/+3c2t4JHDA+vT7OLZqvKB+uTsdpXyb8KKLl1S9+GkqweZmt/+74G/yGgHb6ZO0l4XP2v5Pjw6kaN7l6Fkzzcu8J4egk+n+LQ9SBw4YCGHX3BsQzinvIjZE0bRbNGQhVK4o8eQj+ybKFUczu6sr7B0hvQhMXFYUu/YDJEITOZHSesnQBhxR6t88/E8hiffqGfiQAW/Tww6HrId2ZekTuCPJ0DgJLoQrNUhqpvces+czp9RXiBKiiy46krIeoMcNGtisQWVn5PMAzCR25m41EcFFIUVRoAfrqxwOL0byWjJ7+Q6bL6g3pIv8n8oWPktKwnt0bQZWrLH9R0lud0ILMRsh6KmawK51KRU7cWPf6wM+MbKqcCXCEqdZ2ynlVD58XSH6SlPB7k+lcZ4p46OTkklVhcwBbFua+59+JBjpyizJz7s4vRRh/fQheqmT5CH+PUjkXzm+7c2l5mfC+s9T77q+okJVNpYEV57mh1iB/9nlmd6XjGMpOtNYM5CkWRaV9av22Xhcv8CvOGCq0zOwY+Ilwe6aBSDIJvDegPFOI8FzsnNei1MAXnyqGiLskG2tCnhpXLTek2A1Msjlh2c4wGvLlmlQAq5aQ1vtR5001u2/JiIPWoTGf4A5LhPuurztXrta8MkbSWlmpid+0Uvk7ON23JbbMTor2FUn9K7bvulO0mBiVuiD3mRn44uIlSCSMHDqHhSl9v5V2sxRPKqYp2L+D5/JPzAixb5+q1T3OshFSw1gXyk/SJplQtO3Xid9I60De2ND8bO70GBwqB8oSJn/T+17W3+vymKCxlSu+ZEygtUiX9v4/PxtYd34CD57BRd/sL9xCmjeev0A50yrgVcEz2NYTW09L2CuxACoc7YeXs3+gAzOz3Jlxp41pgohCuhBJ2cXCFORYzxQf7NyPg26gY1Js2fi0x+4Ib5PsOL2S/wAqhlGn0HaNY1adtbFboo+KD69smL2e2Z27xBWfd5Jy8UdviEVjjXvTYUF+sKMfHZ5b8G5613XY0G1oEpUP24GesXV3+Gv+x3CwSG+d5nexUhJVE7BpxdnjAiCrUoWnHYUlda3na5b2rjcbh1MV0WPpGir1ngKp7GHWNhqzOqCQqB0txVt/IGG6J0P9unLoHX94ykKGbsiJW1DraBdESTgzZh9cH7h4uKR95DAiqIsjXzJ93z6C/WzVoNChBd2I2YnrRTMLhvIo3so0V2Ij4QcFwCPtRFKzOBOI+VylXS7PgZMabUd6g4oFUhBRGAbPniKkWdVpDHeafe0pN3JlmUyDI4hqVBYTMZJB6I8IydEPC/CwL8ZPuTOIFKTnzJQTQb0WWxSDEA4R3Yi6OMANmjgZyzbKx6lPSEM3JGuCiVYtkvK6Lot1J/RUpFL3A2Nv/sIBklN8P4Ji/UJE62n3hF36uX4iZK5xgfaTov8LnW1+y3IAJFZuTnoLxIcHQVhR06+IZm4v6zqiqtyXNczDUEhiGUQKOR4I4KeT/kYT7YSgLpBsGIRWV4qnY+2YIbK5UVFIvq88u2v3Z8XBTKliZKWKenL4iU7RylLp5shq/KCWxi44n4kZpoJOoooglRqV4UDDnsDTSDHAyF/UZaZBdSt9O8LJ6Ya0UPizEQIUwea7wHZ+Os0ysaSPasOYVTVVIwsdiL2s1grXAEgbY/TKERPgg9AFhor9r1AYqbws6oO+6TjHv6KdQSFN6VlZDvqZlg5/EXbmLLzZaJjDMRnEWYVzeFyfTyqOUIq3wDjWnYxzhGPHigPmFJLOWbo5L3hUdw9wntMBT+76ywYT69NB7jWe0zm+BdjrKkcT1d62fG4iYEx0KkC3BSSPuc/2qazqSMjwixWZ0FzC+rVEghOcmw2eP2S3O7/El3SNdMmFjhLVYvzELqSE7zhLjbsg9BHN4YvYi8AtcsUhB457oxagALurrZfk+OJeMkVkH1Uh0UwrQB3Cs9j4/zrS76M4WID3wQec3/PtZIxqUWMgOxsKGyMKfAbbLnybOtQLgXY6WvOeckDpgOIAiV3VQaKzlnQXrQO9ew43gN38oH2OK9eFkklRitvedEZre2gkBCEM+tmJo5T1q3i6HYl+VnF90+6X94pXYDpb6z7yTYqoIAlzvxNoFXqJR+8RuSzQzKxXxCcPAusT+gO7uLeNU46280RgypANGoPkDjcVtW7P4LobZxYxMq2LEPiWCamvvJfjvRx932unqqMUQQUcVGRi2Rodg8z4yBU6qUvYi7UKgxhXFMoJJtrxcGNQm8M4Io08GL7p736Q0s1R7xi6yS9+mpz79d/rUvAAKuGWRBvedDhQFZ7+tPPzjs4A7iYUPaOTFyb6BODJFew63S8PBTYg2iMKUlWdeEwHIY1L/GSU5obEvRyi9wXUiuarv+gpz0bUPj+EDMK9ve92fQvZRsKPlp9u+7z1fGYR2wQROo4RpOZx1s0RSN8YjsrVm6u/1YLjbIPzIiBlo2/2s2UCBeGgvE6JFzEusx37MVIAvpR5dH6Sy4tM4UBEGsL5P7vjPMI8l/STH/OHSgKWfbh/QELWUTvBwqxW80WQa9iyd0RQWC0qQD2P/lk7RaiVO4E1sX0aO9huu0MSKOjSxgi2DQfAYtPZ4atNgKe2a7dxJkB92pnEkXWMPowHzhnsWZMnBTMOL3/VLvsCRC6eU2PN2YCQjArh83NqeFtTushP5auOcf8YpEPqFTLWGxUGX1KccropZ6ynnhIdzMV0HiM/AocQSTOro3tkGyITmMKMsnhOeTkrk+d8s06AoyWL7SoPsCIiE91NxIIeGMfnn6KhF5AeX/XSYcgW1NoSa3wr3b3LiSqzjCrwSADMoneAnFSlF1jsiD9hUzczhldqsyWrKJ8s8KBxeTo5tJ3liV+ljgpwbJrA4/bWGuqRqM0bJpajKcqdODY6spxggvXVaVGKHiMu7wE2CHrPcdn9IeypeJiyVgTd9Kc4xgRmwgRf88Z3QrzegR66aaGGlfM2tsj/r0GLIWq0ze+nKPMXulHSGHr5GJlZ0SBYQzjMw+nNqO5TReQ8/+YDZfVAhqpaE6nW0UVavaaRcqKiqg8Tdr1xciLgYWwOCrfSUQJUjV1aee5Qj2rPprU9BmIZsjkacc4RR9q8YmzByKQWUaIbzIBIdX4iD6slYjG25ax/iB6QorYeRZVHNVC1OAeQZBWTJrMyrrbBGIL+GsgCwCkkud176qxc1HkUZcw4VDBm0f9j/6H8XtBCIg4XYWpKffLJw2Mcw41xBZD4hV3iPIi4fe719T2SWFtGBxU1qLQ6FcRwajZDgbJPGBXdeNW8YvEgsjxpUZ8xp+8BLhbpThzLPydTd4tntJ+EvSC0pcATc2RzDiIkrThq2vp0x0jfCcxdTisMAIGCl8NIvkhVMNfv/yVlnnGP4JOHbFj2+g0/z1G8h6TACpyinIUOxLbIkTs9V7VWBB0yTRkPEG6KakVg4mMi786bWl6c4aPAwbIaFK0oHms2lPFQFw6lY/6W9mw73kmlUSJZjCtjSySxvFeZS9bVm5dcSZ29qmpWCiBPVxAybpKJQAyOxqm8PGm7KnQsWLj1nKhWVB0gsM2wqF3CMcsrokDmoXCiFc/iPq7brUOqFLb7doHpW3/b+izW0OlFpRsje/q5cYOetW5O876U3+65UnA//kKHuw8yTy2wdECTs4OKJb66FiLNKjKQBR3xcv0DrYXoYFekztiWpPA9ozhS62wjjgMagZozbbSuPAFRTQynNSS4dVd7/MfZjL4+bL0qttVp3IqbNrVsafOtTVrk1VYsPGdgR/TxYqQrc95Q1WFZXANVVtu9ApbcuVhMiySx7qpXdOS5nJ1CHzmS1n8Yuo7YFpF9WEX0jlvCex+TZjEkRRjoAKd/76fT8dCKSx8nG+aQYgPpC6P+qTFDtIC2JX09JN4F5LwUEl8Uo5/7+GYhOsAkELkR9VOfJ9AADeqXAPTrjfHUI2c11eDmjy4++MOT/wr7SLGFofPLDVSHzOlvu57bsTHPhz2SDQrVvnZVhxOMriIjh3IKZkpbJA69fuj4DIsbH+zNRuSWYQgao7E21h/s/aMdpuWVJbDgRURPq0HLu0EeTCUOnIfoRJtUkdM81h+WEeKSjaq4T3qFw2kk5WyqX2ZvwEhPW59sLZtypaiL1g0FpGCNsOOe33Eg+knyHeALlUSkn74m8fOghKTEppNuzIRGDzezCoKzp9XtYp1M2kQy4+KHPVROSdgIj7RzjbyLsNYeg+cWrhG7lgDRn8xfyqkbKe+5boZxHMOgoBjs3dWPOlPrzyihC7OAKDL1kpTPUG4qNgnQ+l0/AlbBgwgUvWZ+ry4EDdWEUu0t/yhki1GlynGWCX/X2TxIK+YG3piJesTFGAWN0W8nEzJfiLJTJVH3FoydYHDsrrVRuLQiR49rweCI2LWesPKZRNDKBPlMJHBBVpspwREGCfsVpsP3F9i8mq/nEhlc6CwwDLxQvJoq3khGbL64Q/w6zhGeF7C6wCaARtMLxDnAA9kGGBZcI6yOiLpIhtZtWwieEW0AF89K3aaIrkrxojaNLcKeA9BDeS89nVJQxAjx2HEaQ9UpV07cDMACmrCVxbBJlbI40oxsSNlcAp25hUUcG0jYgYGeVnm82giiATWfZAcqeD+pBAjb7B4CXh4ySpqwT1ogtfp6YPEImYcbNu0QC/9F4w3risYxOOfhhkGwuiT+GlVVbmiYOwfBBJnJF8A2cERUBRyNIE9GlBsobjfsshUuPWvmPAKJW7WJgCK3PSSTWnRT+2QPeClns/qaXprSkP6kmpYf4akC8y6QCHUpCxYO1O1iOwArW4gl+8V60waGW/j17AJ+m+yxmIIa8pSbEILq7HdwLS9tJZ8b0pMw/PGW0IvJ4UlziTHhUITHoGfHNwGFAIu+QakCihG0EqzaKj628kq374DtEJt3JN8DuHnq/A0s/zYiXwSO3lvwPLZjNvXqOS6cr+lBwagpuNI7PgbX1Gjjl3oeSHJn7P0s5H8UqSKGbj5Cjszgi3ECBiES0ISKylEonMBwHBBC3cOWB8QKpYmVfxBq9SJADt5WwMhxD88VaM0P2jyQh+Y3b46N7clfHYkxoZNzpLN8lYf4BOcq0keD73KIKr7rhTK5R2y0wGe8sqggGrXuFF+Ispu6tAPcODtKIZPz7xy7CnkoS5RX3sAnOedv00BQ1RjRwzdP3k9nBN9gAJSsljpVCkApUFIYdlAQPZSWjrCuUq+2sR+pizWHuf63qEeFUJK5I8FaIJwXaHrgIn8rSrLqwKlxse0eNsd+S9y4WAZjrba2SqC5/ZiP+8BiPjZQRWkKTdoKZXfQz7jls87p9iblnVWc4MnA6hSP+thxBi1gZSROPZuv4DuvqBkFK8CqZPa6eK9LR4Gecg3uWnxdiXzRC7IEjVwzz5XGFjOjyEWqH6+eCRlO9x1ctMfVl/i/34i40uVOmPYpERridOvJQ5wXeYRqKONTNUGLUEl+RlkyZJqJv9whcwGEAdFHEKz2ldi3WCl9aaST6uuHp3bhgwPn1YxcD49w8+WGuSd+sOVxs8Di8BOWdYDxTavJ6so0zF4uxxueScee9JUJAe3qCdT6CJNpvGDyuvL0HpMOsHQlWcfl5ySLjD8DdT3Qc3fa1jTMpprmiSBdTvdCb3vwnU70fuVJZMym6kvn++vETjNQtgfC2HG7QMqgVbePOnajAVbCyFUkPFol5oaJO8+GeDH+Feo9IArLIbnOlXhKkBPW9td8M4Vnz4FrRDuMKskdwUjig8ZZHM5jBW1TdTVbD6U+4MXY9Pcd2rj2sALxc9e6S7H5Ua/BBJAnAzQEf6csuTB+cY3ptRSq/QrAA+MIrcVLJUqf5mbCJyrIJH/QPODltErvXnsDdeDUHXz+8JTuQLGXpSEyL/6B1zB8vVfsGLyYkLqSJGXz1GkThZecX5NU2MRbs76Szfd8utJ/TlmTU0tEXzNURUwKbsdd72hs/Oh5WQwDM4tpz+WqP/7BDcM1ZxPmtFu3OxGsnFZVD0cK9hrPHAZnhgte3rZYvDG+E4gbdnTaMP9OSMwhdkkP4Hq3G7Kuxte1YEflaOkUd4y1+EH+g69Pmas88ZeTFs2nKPiGAWRB7ahH9ED7U22sYvzxlROaGNknAg/SF4BEvotHo2SPxqR3SiNX1Djgr1z6+Zf+b5hUG0iIJJitEuzKHvMTG+UzXc4MXv2yLJRJmS7vAWZxl9w+09MsYy6QWCdXdtc/9lWQknf2YV+t0XxqjMwy6c+GHS87K7JkxHQmln3s/kRF6oKZ2GpyjvzwbVbLrVJTJoKW6sOPea+Q2prSDWvA8vwYs1G55CQPvl3g8WdP6qbOUufB8D8B1DcNdgDFcLj6zj1//7k7QpX2XHkcfXDftc3XKRtD3hNxEUMq3e4I5lrQfR8SP7X/P/lBj377c3bP7JdrN9WJ28zJ3ZXH0iGI9RvxwSPfz49cl/giWliyLPJkIvYiUKDzkoEuef6KLhZCBwsXJNJvvE5tWEq8Yi/sIg/DIuFWv2z7xDQrmBR8/u9Ja+Im5eeol11ZL2XMTx6IiitcNMuXMNlkF3TBPXiQ/vRWZ3JeZuI47dFLpToxvVOPa9QIB7v8NGXXEs+qLnfIBiu6yLqQDKJtJHRUrYOC8dZnZKIoNn06SWrRMhzI7lO0o/bMIfc39RsyqmCiUoBHbqJzmpcAH+8CCam/+z5r4VQ/NSdQk78sCDIyTMYufCnFUHZvFDU5efkGczFaltp9wwz7iZ7fphmYX2b9Ra1bn7XvOfFZSDwVk2FoxyuKPtKdkpC4oCAyYXwyqp2VEU3k7L5X6/L7PHyeTKStN0PxMiAHw8SbbCyyUEbeGWXn/K2gYOLEOeEK7QsrI08NCiV29nQjiG2/Xlg6hjxHFTQHHE3TUUNiE1t854mp+4bC2MylnnQdUMBxVsmFZ2H7erSVpbEO8IIL5fHqpCa9iyTCpCrBzMIgMTPINXdPEwH9KAk00XE/F7jtt+Aa2bJ4VBEmTbkkgRn2jJUpm1oG7VIsNGwWlFrQzeUGkCdvYbfS7ju7T6GKTP7/wJyOTZvvDs33Wo1HXrRzuz3d42oH1jDdzDM++RaFkJNLnV+bpDFWO3Mv1jlPX40OcchM4nMnRgcT3QHSbpXXcuwmtceqybrzxV4EOt5Kb+nOdgDIGY0EWeRgha86gazP08mMDVn0kQjum8MjOFArDdKK+bDU+YINoaX7CfAy69neJ1VBTeTyj4753zHHrZnxwNpnb9P3TQ6vnXSy0mi/68aaaRTq4J6yBiuvmxkNvolllKNxSeQBbF8uBbqhMBOdpbwqWbEoJJyfrkWas0IfUHAajdB8gogzx4q83g30Hm/QV6ClmFqD9fg3WtkjByuvUReSZzI3ipGtQg6lijRp8igwbPFHmZKrdRfqreelVyzjce8azdli7QBNpb4IkWcUqx1Q7mNlFbyQrjzcpx7Rw1wpVkEgSS1BQ5fN/jvsS2GPswLIoVKZCf6GluQWv00uU/M2QokMFB/KkXHxxCtLujZXtcO7H7AxPXwI/KCxDsWtLbtJMiDl7IgGPGL+O41qt/o/yhELKN5nASZeDTllPMZB7PycsREs8QnxhnGVjiOOjWhkO9L1aOvWCLOpz46cH+nYxQAJYDkhQX4OO8fArZjHwGXUcVgLDF5lRzpAbJUjR9b9QLULu8/vC9TWqy5/j5D1eWZ5PGsVEfz/rCpFrhrfiqTssF9e27EBfM1lwX6gK8P2NVhNyCZ8aMOo3GgqSvP++PWcBiwVfHB+BP5sQZcf05HvhxFu2XOtMyCezsl0xvqAxoiejsU4j2T8op8xi18dyEzQf+jovJ5xl5tNZZgFUvj7pl28/DwEvO3Z9qe5bMUEAECL3/YuMAKujG/vi3bdvIuyYR/Pz6KBgTl/ax55TQ65bihtoUdNF35skF2ETs4aJmN3V0o6Wtu2NHhPzGJ+xL1ITDoUendFO7ASuCxvg4XCkniu153OiEKp0PBYKE00g3bKvQ7FMM+vVceMRkkriOFY6uROrkNLSeGW866QOLbIKLJ9YBoxMX6WD7cxE9V8hdclgd540FRPqqV0eLWSwm+UAkgdrawcW1VK8GZXHLqKcvSoXUbtpJ//L7Aotq+sRU42el6d0QuLxc+oH4loSlohgZlL2b7NMhmLkBEB2B2XkT2siB9Xso5MQcb1G79xUpBdmyhXJI7+iiuWcRoG22Ot31GiVjMskLUInOmZ3JHZL5AJm899z43qUw7BRV0QAdkBuvIGQvNoTOmZyVjlIdvpFoVxZu2mSaZc0SBj5GGOed04hglVqcIBHpcXvok4PHKbZGrLUUn9Ay4xHUJdEGAYxxQ9DV2CsiP2EZVUUp99reRKh1RAt0gOtHiQCoh2ch0YdGB3dDZ6+to+EppDuDDH1BRRg/swPWKFD9qGPHE6N5JnvMDnz8hFAtiCyW88CZpkvDkrvK/NxQBD1vYIe9aSNeggAfcLW8UaM6suOpXT0K52ipFJyRSGkbckMUMldI6D9oipXleucJVI9xrWDa8AzyLSpND+TfzrLGfkT1XmrTso/UqyARcgaZOpsYKVqbLMoIDSXDQwjnaReT0XmzotTiuttUz2eOm//8trrcXycBdcYiu7mNSimWHL4TXf0YxZtwQlg0FINXnmUcc7PbLTeE9RZfaucJ8Jh9nf3SWhZejIMI+u6bE2/WgBCJ0IVB+Ui/fkQsv7lQoXrng5Oy1WNqUZEsFokg3dfr4FgP8IqqBSM44mwhArPdb8QHOOsYi9Np87cFY4xYx8tyAXPJiC0i4EqGq8Ymwi6rAOtu/bJ24cr6C//LTJ7CjKHgNMQV3gtnttUoXfMJOTtiAUK3OQIyLkAHznhToICG9FL2bJe/2GWPPsEG48gKpQewJgjAEtx7HhQo0foKUCI76nq/M9GoqSkxDhW51G9sfplZ6g5QqP5F6d4I7zRf17zQ+qUEc9IH6XQYBGSullc/xQ8Er+cXDX02KhVWcD0gGyjLSwvL8vpzFZ/LqtrPU6808S6AfknS57l0RplSVKsBLEte+tN6sQKn7LMR2E4f0mZzwwR797wSek5+yws346jif/l19nfI1ErOuxIzmsCBqr9HvMnumhwzBNDYmOI1Or8qQI5Kk/pVVyQ2GZGXKsTM7Onithswv7chThkfWqj4Uk8mIUtRsgKz5N5KB+elVvOnf4bbjDfHxSy+IvIQxqJAK3HB09uE0M4jGms1xRGs71i7Ba7DZWBIqgnW/3a7Xo7recKIVJZnAjU7dniechtGyK1VPscJA2pgkYvPK0PvOBscFl4vhD8jsmSM0YMYIe9Q1QlDArb+y/SdJcTAT0cgqxxGmERF+ymQTL3L/NVCt+g7TjXB7NJfrl+c3wim/eEmGM6F3YiUOe5XzKsucA0ab8fnrW5pWD49i11jNrRWiSIB8955Mlw2ZhBKAAU06nLcNCDEnSzqwU/9yNaAk0H+qzG/2XIj1oOD0CJ+VsfgQTIjK1Uv7lNhZc92PR0g9vXwFzX9GX0voDzX9PXSpJ4sshjXgoeEM32k2xC5IRAo7Qt21i2YNWjERtUUPbowwJQP60vJEMLLNamVNhOCME5bAXpmZtRpo2YYmfNc24UezQ9Bc9bRD0tJCJQ5AbDVeZKsors2V7gIsYsM1aQkw+B7D3D68JScvAik+l97rnK2XZmWoqjmU3N/ohR9QGW9mtVF45pjyxYi71QXXO5oJYzx9e/4So1llP/Pii356VAq6mT2e8Gzt3ZcIo2qO5pJr0ilk8WbbgqWfgB+6aVEqK46fXq8bPTHDX8YRmWcBSNyQv6/W2Hqi/usm6rvQ7DKIqsjTVdZ9BpgbHEqn7OoSR2TEhXFd1z3j8NR04naiWN4NsxZy4UAW57ntjdR0n/bod3sstIK4113HbmfFaLQ9wuGRwcST+UIyfGe+f0MnyqSv8hdzC9FPMPvuITw9Slw/X3WdNe7agl14tJmoZlr5p0RP3M0RxdVr86+AqkFjT/obqG1gjhLlaeZJy1etqDsMDZoP0+WK9i2FnJcOtFwS8qWBZghmv0IZTik3mgvmDqqBwmR1CvpSWrZsMG46kx5LZ2PmAps8kqG1vOQ/vmB9Izo2adUtT6fH4aCY8jJIpF+k03xT0iuku/UGa40xLUtwLDO3gydzGd64Bc/yo1YICpUEOVrbCrP9whvR31L3pdT9OwD+w4r+6bNgG+Ui+CMkx4Bsa1yvqEA0U2aCGT9NQdtZrsLqROz9fx5A95HUFQahrKNR4zjw7gx5+43iA36nN8Vf/A2FfD+Vx1LPqhVO4aeIZj8/OQbF1KruTHtbmOFQXR2+RTrtK89AQrkGMD8LIXWQi/JwlwGdi2xZueVyfl5CZi4FM9q+53nuqSKjrHsfY7c59YvBFTpjdcbJ4Qqte5fi0B0U/d5GvJNAM2lw2iy4Lhtrxlhy4iub8mhqZXufzT1Hyr9x5vZUdMMean+zw2Zg1i0DeGSLZCy80RWW9H9j9CouVq/RWep6oFFCeLWs7VKYrNttEqTbcQpPbrp5Hz/aQpsWZHPz0K6CSSiHvfoTtvrdczuoFjZrzgGrecsqY/0RD9KmcPPMLjqSLSZK3aPF3CEB5o7MnuK0vtPloU1gJf/hkwa5e0MC8WKRwh5STacBL1Hf3rOhjq9x9TdHRpgbtViitfOwOn8+78m81siRRdxx/m35bsgaKu1LIgs3fDZfGlKwyXMDxfsUe6RRF8+rJv5g1FnU7kBuEGOGkB1kqIDVvRSROaWBtwK3v4U6fLHMrbHn9DHn/FFdwMrtldIxdPEr3EGULY9yGrDVMNoYDbZvFvjhyrCutMzKLPE/xbWeOeW6nt0lf93ON3Qg6MKpzo/hmfUrvzLFQveEV2PWppBzBgGV+uylC5zQ0rHOGnX17T5rIsOtUBJRZvBha4fTblEVUMmb3Ao93pz8nwDhZugKqVLV3q3+ivPeWigHHrxZ+u2RqA4ia6qG4y3wcg5giFnT5/+Hb9ZTS5b6EmYSTaz8knhgfZSYaGq5QNftmB7qDuNhcKeBpQel9GSNww5J35LDNbzD4bAZ0jK5ybHycy2RQlZWrkJp5ydswJqeIB+tMIPdTt6Fho5tXPGM4i1QK1eyZOY7AGtCLFHDZfGSSb4HE38MTXeKXeOEhfY6oUsY9KGf3ZwbBf0nxo4vINEiJPyFSvmi1VDRmT43iTRCLYfULEw1gyyegVqOdEMhcK52pokT3KCZwv2tjBHyW+Fh9o3Ql0lcC/84CImQKIcsKy2XaoIEOk38AU4Xuehl8USMlK9XSmDA4nu51LK2djbnIQu84v8OF1VIpukNa61QvIBb7rRVey/50ueLFwxejR60Rm2rDfE5E5mZ8XKYHJSV/R/JHtPR3KpP6/8r2rrq/OuJrzSMRldZ07ovbnm0nx2s3CWjpS04C03riVP/dzKTTLN0mavDS0cQ4JTA1nzGL98f7w66ETnB+M8sg3xdKJ8b/K0fTIleHVhPs5w6ykhsmVR5hgFm54sq8kOUpC6zK1aWanxm2P7Pp508hkJaBE+5FFHc24LeU/Jx7WRBu9PwpH0/fDkiOZa73s9QgCwd43ALbYVX/sau7bW7LaPFTpzZvMWXHt5BWWyATv0BwZ7uJKgnm1ZJjO5eQRqL+hLD13gBxfCDjSBuFAWE81uSPlVyAyix2xBviV3PIcQ/0h5Y5gaKVUCnq62OcAR1H/lmBkPVcA4aT49u5/Td/xKHd+kppsM2NSBNbYuRT27U/79Y35FUKpcRPldEp8nhDVZukBTSIsf5G2NYu5LS+BuAp2Zz0Z8oAbwe//xV87oXTlr+S01NuhDknXVMX0CnZpyvfWmbH77ayRDH6MDlze7A8DQJedIw/M9AuxQvW3xZqSyiMHCM3DIs+gkLPBPM3QZ/BZeVJcNpKkKGa0Wy0U4uX6hrafrpvQMx+hYMdxzDpFCYvGy9Q8gR8mzSLEHevtBq+KuUx/o7XI9mUZfOPnesBW5kpwJJHVhM5Yqbop6/k9KzhSe08gPD9MqZekgQOWccDCjdyzsLUj+zuc5SYeSj4Mgyv3GBhCIPcFsm9DZmi1TItqqg3TifsQd0P2YPc3cOuoM/gtNJpJvc5VP9MnyEcmdSoB5rbVMrKp9Iprrqqwx2NJeoKG+PYCFXT9coMoZxIxPh+RN++bRJ4U8926DZACEtACif3CoheiZ4hQ0q+F2RazkW8P/3JB6m3MiJEPT6wwntjBd4MWE9x5Xa5zoOUzJ7DSaEnz3goeSYQ52RUZJG9yY+j8fJf9PGobiT/OEq068hvu7FXlBBpIeIPc3M3jhLkuzmANtgRfdfOSfoOQZRILKfdqA4e97mOehWrE+/IFZ3Jjuvl3G1zkXa0xFQu45dfCl7xe/5y12+7+8DAiXln1J8RWPc+lE84vEJov8N/J/kATHhUp/k8DpQNPS9FyoAhAYVPigR2nS5sxuVhaHB6piTiOaTh1e9jAZ73SsZCO8SzqGIVGpXNvT5EdwzKcf/J5cT884UkqfPZtcj/yiDd7k8hToBJidUs2WPJWCBXn+5k7dB7wuJL8M2j1F/J3Oqj9V8iUKmCvomWTUlAJThtuj9dXUqSKg3vRy8AB5PgatWgDfYAgXj3VFpeaTtLKwDXORg8wZW6Cza74p3tjbtvtwJIX+lsegIGV8g8rkboHx342Pqgn1v/mtWz6FEJ7y+NgoO+C7ikRPhs1I/yV+Y2UAVY8e9NkXOQd0pieNs2Io42p23B9sJlv+CPDGxzwMXtVGY2mcP2z2zDHf/nMRdwUBxKcj7kOJ8rNeautlBKq8/JMGCpJ92GcljPzCX3SIPBLRXSZKlpdMWyQWnOdB9SoF3czNV0HTCphBzSihM+ZIO7GFGcPS+yLjE98nxFtIB1NH9RtSy6CPOL1p/3+2NONjamL49LuFlEzRSHyCm3fBGDh4+Z9uDGrqBSXLZS61B2QpXmZwwUfM+Q9P1Ux2f5adlQxU5by3Yed8t31r4FH91mWdphO0jOVtTC+642VClHd3uxo1Sx0Ms11Jl66CnrXjODmk3nMxlEVFoaqxbKKpmrkncf1OVu3knaCwX79P/sujHsAxzel94PDK8vrROcH7boFa6dL1rvBFYdFzcH/KhaHPmOmLAXT9uZ3RzqizyVIqTevz/+CR4sdT0rhjL93BDKWLflPV+EEeTZenD9BDN4tT5t1UEI3p7lC5pJEPM04dCOlSjx8durFau6qhKlIXvg3KubSkURv+X2Rnkh2j2vEfY0Ac/ank6zrvyYCTJT0XMoo+6AdGXPP23yW08IBtHmedaQFHsPBKx5OTGOGbcfEGnx5Grfe3+/FRFF/MczA0KYz+Yjm0gzazqnST6zsJlf6yFLUs1gSQsrfME35+FpF4asyMeh79bF+gwZMqv7WXgt1aNkfa/BDCbqNu2R5qJ3BKJ0JF4aVz6fmlJqmO6M5tWFd96YAjZsi9YpNIv8WJU6LHYOWPMlY5I3QWdCJ8p0/caZ1yXVqf0xnBDKbO9mBDmv7zdBiqFYVfOVlVJabR9R2ap3PzEXzFcW4lgCnV6otP7IOkJ8tdyfYVt15DNEi2w26QK1UNJkxhQNRPOtii5l3u3p7dkaoRvNbTZcjM+53em0qYSK5uY3ZorZNYs8FSovvOeEt1mWv37fosOajRovaTtfm8gWlB2uG5hIp6mVFiYakd7TbBTyAlVKGCs5VVP87cL2czyE7Loci72pIjqc6osiCvmzoBBrKEaiC2liEESKR7tNDZx8hjFTboJpaz9SjHVrl6S4q5OHnMRxtPIYehWe1qLpdjhb80gxlnqqNWfGtHcM2FITfcgApVozcVTVoyGhVrOxmCKACYfWYByGJXfr/w8k6xCy1slUCq6ZCCMWed5iWojb5zwT55ee6c5SnTEOWZCZ545DSfSKQ0PBA50ji1gKMwy2R5zynghUkAuvkbc6hJU0KPscmgy+Q4InnJF2HA2oXiy9YaJBQzms/nLZcWj3u5VdTIi3r9lbxeX2P37ERfFa2lipy0+Nzdfy6SAWVr0MJsNs8zO1ecioZy7OHqbSfsc/JjRXTaAqzkkh5k/H2MA150QJEneQ7dbMimfB3JhfrD4ZS9aSu/Pukr6bqFXyDiyZERF3bGvpuL+rDWgmcXnEPlZTD33pAikvKWTaPMeVqj3fbnVYvmLz0/VZwQTv3zDdS/fGdiZ1/LjeZhJbc0DRnmPPEg0799pKcW+oGm9+aRnMwCO4C/4w1p7f+lSsfJmLzY3g48i5fZNr0NhZ6pa8RUutjJg30CXd3Z6jwkc63AUoY2Wl+HkLYixb9jyD2xGlppM2py02H6eNRV5oizfn8ly1mKZ134m8HIJzub4Cz0jiQi5HNXEFS58Zblr/CKb5a0rP5dY3Mzijy9Wm5PY+btRs5UWDgoyZCtGppslVuFCOTB8j0hRC0iS/Z8F5gpfLOUAqe116CP8EW9GquD4ZN2J8RbDCnTILoAp82kI5qGu8eCJBkw7cODY5pR7p4CvCSwPE+vuzB6eL0gFBsyt8yCUOLrqE7bAEakfz5hVAuSPHNah96U6b2c0slSSsNXCgz9TnTJzBxRcApZFGSGbESUnpVkbAwaJuGDs6xlLBkml5lWTRRNCcFexDPubQvKpxOF+Q/zvjDSCONCTuW7GYGnOCCdlrCfoinY8KoovLi3mXMlY/E+5FRFHJMbSiMLfY/RExrGz/M3bJqdCuni4V9YyONXsaYeLxWpFcz/fahMmzUuLD14HWUmerH5k8rlB2DX6Jhxz+c8NTzFw2LJRObdWuRnDR3I5I8n7ozHPnkWymGkevg8ujU5eCkUsDvFr5bhJhpeQ4AwtftkQgggxf2QbgqneMrjmwV6cNYzaosKxOMavuuX0rT24zDcoWPyVWd9QBU55bSG/j0hgVIDGASj4GuF5Zy1gq84xcBmAzvBpB4j3wIFAg3wGKljeHgj41VweMtJl0/W64Mz4ega0YnUvyra2T8Q7F5CmUEbfVXcaHJpGVnI5Efjc3ONzxx3ZoQfHiwZifR3CAxD+wDdEtIqomYUCQcGykXaLkBPUCtKTELFWnhtFF0IeX+58x3T0wbHFrkNcpfao5nZ87ouR68ij0G0Ee53V+Mi0ue4/EF8Y8yBrxFKA6397SQv2HIc663wngGAs4RAvzbK9JmzgrIDymx7RlvzOu0XD9MD8Vm9ahk5PXhWI2HPEJ4fcUNA2gVRHXbVcy7AWPJAvTXJzhPFx2wZbzp0fVuJVqh9PIMagz+d80JfpLcwLS4xqqUm5FLEdSS5HNbFB6DU5rhNYpo2X9ZBKmiKFr67EOwjGbtB66yU96yi7PCkmq+QC+dB61V457DTjdtFOTjyfL8RufjSFDqpPWYqGeh9D9PPZf/Bt/TLMsLR1w2NWIz8kpy+ywvdvi3GiDHHUKCuUHGceY4mR5yMgnuLSfUyyia2WUh9SK0fiWmlHFmdQNnXA04h0uuttJx2DeIHJKt7nbiePACQTkm+B2gUOkSPXowiBsLDiFOzKYtm6ZjfPysMpdvg2th3SfggLhBrHmncsCy91HrY9u2ZQ7aosS4K51wa9bM38T6J1KVWg//N/sKvfEdFTpF+1SBRVgiSmYmXmAYN3x4NHPysXxD+2k4XX2bDeh+4o2HDs7tFWaYcmwNGRVetySbv13uli4TBbcR+m9VIy/tBQnZiooV1z930Ugqc6nM9ay+Oq3y/DqRse2quyJ+VLayPMjL0P1UZPMvThF9vP5Mp2Qqv5juKAEW67xepI1CPPNcukAPx1h0vr13iULc+RWdCI6XOeFMepyvyKOyBWtiKG1LZwhuZUQbTurMBTO/q5uTUmBpRVShciM2lyixI0KfqvvA1RD0VcgW4Wyyq843+TNgO/5bHHg0y4842anxTJfIxao/kzs9EEY38XMaQXIJyL3gKdY+fIGz3RunHZApIcZN+JOp/i1p+GBWWHkgRpyLtKhZx335zU0SpI/tbV/N67JUxMhdM/SrvSSVtSbZKu0feHin5D2s6lVdY0EFGwUntyY/JMsDShfbpy0sAzQr7CIksjSizuda2nYDgIoh6R7KWfs4sS0tzG0ftIwA6QtKg/dUVOXYKMorC4o69ZnbKOOptaSThfgCM8d7ZD6o/9YOPjqecflSiguqu/iRvoHpEmua2KZCLGGO3fb1G0JqQKSAIy4QADghMTg+ARkgOIxW2AY9fRyBKYoFh9BhJqlCFUlCcwCnhwYUayT+iNty4nSugbgokTrkNB6ujUK9RQqQzorBLXsr3l49s2qel00PyFy+0M+23EF87bsfEGwlm1nuSgs8ExjTTekMTXnJKh17HriD6zUNzFdBN9l0noEXcvlZ937vVynigyQDGX/NLwmnLOsn3l/1+BrBRICwbRJLPMePpj6W87Aqcl2FwpDIaydqw+rBd4/XFNjKfbt9Vjcxb3RNFWgKvSzp0Xy4i/v8nO66zpqhSQfZlX0qOpqVXnka8AmOazssCw2yFpo2WO6E1vi+SfxOYHiEu2XZqyoCTtb+3FZy9kxAT/BqSnS1pIXyYpnzraWiCNL3H8S92Sy01wiOiN5nDFGjpMscfdJvP/XpZ/o7BSHuUXiokVYJnF4icsncUvIjizRlnRx3KysimOWOH8S12RMQlezwq6xHca4Y2L5I1FjtkXwwm7TFUvcjeYwdxrRNN6i20XzAd1ZmZHFKeGBzHj2h2rnurlmeTDdQ8dLyPKpNHsZcxJO5PKwSdKipsWMUnJsVLlRS1Hb/dCtQg2zuLxik1vB6npVChoIGnjnMK8LH+xhYbUX5sx8gkHovrUkqRgqgn7CIMA+9caeEl6xPLSsYewqwOMNa3uAzqmuXCrbOeTuCfZ68mW+p+x3Y9eJyGWYypXoZAFYegPZJPrl8hXz4nmzIucUf5NkhoROf2e4QFx7KSrnnhjwswhFu4t1tmwdYfaHbabLMXBhOnrqzlXUqlyOVabk1XHOz8nR9yWGl+VCIYOpXJWqYPLdmL6wpIlP/OFW94KjbZ/jlN9cS3oK9/yHp5LF3ib6m//FsyNdUByBcUb/pPKExVNYOn8p6tTuMImdl2GgOrORWBpzLY8qUUUvyeQRRVskVBZYjJSofPTcXApJmyziMiUxnAWMi7MXde+5CeD5sN5zAt/fkbWslnC8xC3tMEz/1BhZ4v6TuCeXtZHF0PYOJvOobAI+ES4d+U3CY01WnP7aUnNb73DcaLpfqs+tWq4bB0i0OQ/qw8aUhDX0LuauMk9pC24juhzN6GvMdwkt6vxvk3Nl2w//LJp7WYdz0dq4dJdEzWcrvHHA9MsiAiDVdvVSTRw3W6yzZs+1Eup2kvyOylLsjwXwMl3Q0av523Fh4Pb8nNMIp2K0lbkTjZbF4SUun8QtASs4se6AaRrGVO+jLb47X6QXS5iPHuDDe1Tjv2i6oTUKP4dxFzvf0GjYmfPf8RwkrgmYOYy6qEXsBVS42yeL7bEwbPErKl9sNC5Cra8I/QyLINkljk/iGJWQ5KytbqQHjH++AiQ/8DyuS70RYE/gZQ+ENKlLDmq7S1EEP5wNek07ROjhDv+FDogvAzpziPTCkSJwmVpi8Ul5su5FHtBQEcAaAg4BEQHueOgEf8GL04DKhbPwgUkpAzKX4HAjynNbQYxHgYCYLVwxWDYHCJcguChTqegagHgYGp7Uk4sTDQXpOXzvBvhXRuRKLW6mnSWBcjFZVbIaNp4WHJA6YaGU0WN1l6pUzZrEn4/bCzQN0yeFh8ShsVb2SuPnEcTY83zmQYkcaKK14BXiHCAy/sfBvRRKAt18WSrM6AVNWUfFrIFmtYHW8LRD2FQNzTJO0Mgn5hPh5BKBNSTypHgkU3PM+zOY7ewgY5t/dV+tlyJe3Rde9orCInV14BfJYQmVZyUQ3QU/VBKkVQZkLeUwZ1mnwLtiuQCpmd8B3AJC+EkOIKIqBiRqLLQijdGN0hKVT6I6hwgWjeaYOOg1XOiT9khMPdbQDfVknFEipEkQlB9WEBPCygZIynNHgrKbJFHTMStYOcAfQEkC81EWxWP8EWOMqPQCkiUIuMBWmpeU22E5Jg+9J4bR7iP+fAPN83jsvvCiEwbjjzCRQIvqnvaigRNqg7yGZzAzUfFYczA/Ix29M1ekuUgoc46F36OVUB1gD0xPnnCCispEeyYZ51s0G9jvSinbSaQxJnm5c2byZSXnXJeHhGwCtzI/AmS+XKkdNWKHblcVRK9l4u0okmiWDNPGi9+8qlstazP4j3D+A9UP2CRlGwmDn7XiQY17unwK/9XZS1Vd/CqbUMmU7sI/8KBZM36ExlzGnIlO4Y9XuOuqxbR3lnCwsi5/eBsxaTH72BU+pyLAfMSZ4YniGd2VUQIZ6xGKxnl1sJn/+sxs4W15sY0INDLvMnZ7xUjyt4fv2yu2FA9moc/CFQgnkYZESO4hMcRz4sQsRg5UkKfmX/HnyhIumOOH+pbq96T4HaX5GBkY00/nMrW2NTNUZRYXneO0aEYBmvHZYea42kxkCoQZYOuvxyHqJXOtRnoT1xkufBuYQHm7xBXtdd2NmsBSGF88PIwDwMzPWKsBE5oCYV/O/j8oURlPB1uY3D0OFaYxJcLXcqwa7YTEdobPM7tpTgsrzkbZg+DVjI2gQVqVxQYY5BgABKSajw1eyoWjILwArpWXluWv0qmYUGktZOzXTTKi5e2O6W3vhuqCrwzB88U89N0WR3ZJTpTTV1tKSC5fskDKPetWwCTSO/HTi9arrd8opRI6p7BznUs8nDBTumSi+Hlnnk1c5XeaalQSSqgMHUzm3VfjY45JCAelzBVyrWdrO9sol3x5TA9laqpbNbcWuUo61pn7Jicry5sFZu2tNnuu5iwNPeKyehOw+CPhxuweNWzkzDsr2UvF9KBvFApNwyQj9YFlP+WtXOS63pd2Qp9br3p5gBFaNomS6r8GDf8+Vl4v1iFXhW3/GFCGNg7vspJ00KCSdFY4AOxznQ9pQzg8+Ve2jPqn6IK7JdGF7mjVlC6XgOepa+d0TQ3F8uNiCfgPNr8/oqh/158vimHabxMNBUQEEqVk2iA4qa21WXYmfPlEn6xDXZamD7IoXerj4CZ/NqLB2QakGRxAJFEaJ9FFsb7Biwira+cH6zJ5f0H8M78shAxfoMDZyi8Nc2cIcGwMAf2Tzn5y4dmvlKN6JlJneS+KFM/5O3licDW5ziyiRulDe+YX3XoKWSKisu57Sb4J6/lcp5YXwc8/RVhtMBMwvAyKm+ONdxZofZc+6dTHie1EjDRkIvrRnkleeFGu1UBjwCIsXnR4P1nK7D90tdmRHEIT8Nps7U2OKQbzARRwOIIYcytb4tenwHGzdgyCbvUV3fweeYFruHqh15t2WRArKN53WpIS3p9PPPR8/Tj9XH4UG1aewLX5nA3s0/22+kd8eQ2Zbuc0NTdvcLOqJjncUfACis2EizQbYvmRsaoJxEF57XOmL9Q8faGdnIgaQn4hwf9DDAmG55/OHFmCfaNYpKAZpXTLleKxVj3rZ0FpqeVkbLGjvY2rmQItjUCqREztVAf/sJdhIYrP0HrNA9kiPjOydfOH0CqjXEBk34yrwuKPgp8JmX5/bDEHCgoaZedieevqdH7WliWDwBxNg8UoWpjVD9/466cpuoGTotGUL0LygUyKKW9letRGJWfap6KUKdny/KpSvXX3cOrLLeyvW8duKYlidc42sB9ZKYnx1qavSdz261Ovh3FUlmc5k52tEN78wPjEnMva4H1q3G+bkYlK4ylWgcTsLUAdzHXqb8pzCf0egaVP8SR/vvkNeO8S2733Zio6w3+ouS6zWGWX+BriE7oQ6hB4XKT9uomd6D2XYYZUl7XUx3zgtpLREyp7K4oIHVfM48DKiS5lFbJ7zdlpa0oex5KoX1sQ4djCdDZRmG20AmUaREfZ1+GdfX8QqR5rg4ZQr+kVNPWjgMgU7u1mXAHoGzwt26/ZKFQgqNmUvOKceoev6iTIqaP6QpEaQjZNy7MrPkcVwUwDYV+qG+ZbjzKi7VzouJ1UaD/msffSJAfawzKScqvKVkKXohHw/7k9iqMWzIXuWogwEdFQRbHrYKxt6ImJNSRypyAqv69q3a7EVdXk43dq/5WlkZp048nnQ1sbhcefzX71ZwJr0IA39L54MzhG7IRp+ar5slWs4h+wRbiaWPoBYtWoAx0FEJuqhbIJ3B0ozxPLyAnomq36ldXvdydxX7zzvKDF/jDFW0Hn1PSahQlt09beTxvatl7PWq/Qo391m4wJ2do4+8KzxeA7f2Q5AglEqbR6Q01fz80J5zDgtZLl95TuSmy/MvOmJ0RO5VU1RJdylVjIQgqalFvJCFqMDlfSubvA/ue4CFkKJYwIx06p1OObO7aVvZfI8qf5/HNaX7td00orX8RBAeqp7zkk5N8wCSneBsfd8FuiGLH0/2tOqNSmkkaTEOwvgJTiWytEwKBs+OiiuLWazREO/N7IehUWerwr8Mr82/JyT0q1imumFVzMS0IOMtU+IGjwcgF8EXnoCkJJl17BxTtG2raejJkpmBFPgR2H5AokyeBrER9WZL4advQHbfOAb1T9TrNucB3nkNSPxiTPr9fhODafwTc4WytA6SETxSb/5ZvreIOA8E9fg71EEr3Wkv3YNCX3voTnhEJsPYGiPhzBX6/83IpURX8sij4/C7CObdlcqVB7mtviJSP4fw5mRyHtBSDjGWjgIQKGm2pyvK77v9joeMyfXi0p7kVbuMD2GRbu2KBQHhJ2IuDGhGzuFYgKfJwVtE8mvaiHyWS60PRuGCf3BMwoI0PPGVRHITc61b10aNSxp9sseEdgDJHrZ/p9K2gJ7FOU+c6ICiVVxhcAqWJxgIIfkUaouqAolOrKSU0OxqK5XiQ92/2I2o89pALWiKUbRLc+majhtjFvkjUqPj7VcNLkDA0OftSc6o4R09JN+wS0Eg947JpprQDU3ZBNCxMdlenUWsr13lsDz5PhajGhuoFNzjXi+mFc3Ssd1BM9w93aqXVevuZqMWOQ6Wb0TiW9AJYdJHdHFKmnJQKvosj1jrg6wlq4pVRk0jrDKz0xUxKMYtak9joT1VwHbOLiU0C5lS5jMDeb5BtM40HZ5E+aSAke0qobU32sFbFuUh2EWMycadgDqtCCwN2OumBIevtyH9JcBFOo/PWFSwilvt+PF+SRJYK8TNXbS/3z61NeUZUTyPiYu4nHhyrOH1H+hJunv+OW8YecBbb8RmaJT9m4rkPQTnyKbZ/ulCipgi58SG88wBTl66q/1Ktv8eCu5UBi5xWkrZ4uwHDQgzwRyaNLaZi/m/cHKxeL7YCd/mUiAfecP+ZkEaaNAhFNXF0bVgDcTSXkJ0z6TtFUnLv4Jf9FYJZ8f9S/urYB7uUakIRnN4vFunTiRPvQMGX4lsrsAQTQHLzDErl5Rr+OU8OvvMIOXcXayl0yEPlcKqUs7KzNqxGk05PJ/dXyrdYdTmC/RpfiOh/LiQQy2fuIex6tIxUw38zPdBt3Bs8h5oKpg/NzLA12sv5hnQ5a5v+mLfWoII0BMgOccUCW/Rc+w1rWkIsFrZ28CGKyU4vwWBvnuy2bvJ9AblCmdLR7aeo7iBUR+cbcHVnU6V4XAfkrWMWO05e4Sf4xPOfwcZXuBoNvwaTF0VHdFemiPo5G+7b62cituvT9fOvGx6mhM1R6CawnSQTeHBoJ0/llO2Y0MIUMWU2Uhvt0OV2M1j8T8CpOgoanvY8u9+HINvo90J84sfCfDlrSPkuBrb7plGWLht05X+xZFnnmAWTj2xQqe3fhb+6B9PvjHuvwkuDrEhdmNpy66i/SI6CQifpB8ziSMhRh2nw2wj5i73C/TLFqjMDWSDk8igINTiUSCt7b7JBoT0kPv+pH+5qWWc4apvC0tyj+UkQqIxACQFkrql3k7yGUT8E3hxQ5GUoGc1Nhoq5KuDl5ez1/D/ccf6UrPK3KjXzs08cg0zgsupfR72L735NMy4v+H+bATXxpuazY848d41KxEmE0PVi757cCDFmn95VJNraLBROqXcB1saGtCJYEo+tt6a3v/XZnE93hCevOpfTNbfpsb3/exr8nGo7iQLD0eLYTStWMEZCyOFKSBpLszOSaRfejmm5LSsvGRPW1PRXeHyo5Zn/KeB3IeHnsvf7+twhpZxvu38lcYWoKC1GgCswtSDbz4+gk7w3DPZb3TxB974oGYPN0fr2vUuXH5D0ZAe24tkEMvfC9sT4SOCszPXyJNe7kjvhefMKM/REKYLsV5H6OWHpgQJFfI+yu1gfXC4LA9MPmJ1h8JwCrjgn1GPzrAlDk/+UnggKOY2P4vHWix9ih1iWWtwALShn5ymWLABy216S6miIH8u5D7B9tSWbmpVwQnOex7/h7pfT6nKEeQqp4fbJjUDPzK6mmtNLk898PtbXuv5k2mD9Roo6znYHBpzQ/RcVc401HSkO3gQS7G7t5u+QKp6wfYtOlQ4p9rAisXbZgUmDAtwFL2RhzOxB6LiOGPw4h+XxwSvy1UMeXbJLucM9G+4/uhCPCwwff7UPuIaOBc+Cww2uyqKKzq0+ARfGLOET9+eByvhRmhx720kqHCWPJbUpm0C1yq6fnP8nXQD3D0v60n1vWN2+bsKunfbsfv2zLgVQRqop7QopZoO5Lm8iiP15uwLYWGGqzpuSonQx62Kb56iedkBZejRZYihWxM4/qd5cUC8h5W1JvFGxlN6aAGecoxAjKVi5yoECgZ36tL51G0dd2EYOlJcX7PUyIenv3uc6sLDPl4DClhDtT4ebJ8uB8lShnf0zVt9WJrJLtYRXNdzsGb+0crvhMWokcdynXGCA2TuM5Ijt8iSTbCRnNwkl2/lyY/muaNqxLuHYnHZSIV9NQLtfvqM+MJ+ga0Kq9nVznCAuwxeec5H4L/pV93o/hXq6u10+ff8KlUAvFR+xCE9IcIcWCA5sKmbZqnD30bocA16jsiTK2cvcjUY5XKoWur9oN/LjChhLK8vBXuOW+9Eke+kD9LYbgozyoufv8O+rQaY0GZGIYJhEgceJhers2EyKNM7miNi8SwmGbsGG7fXxR7g65zKLpblo4p089Y8LdAmG14wtAjrVc9UWPO2NfeKT4rYjyTnt3Wczo/pG9xbJFr1vUKMO2iFq4WYrw2LSnN3b3l1WbleKWYhg2zk9oBsNdwivSjQgl+duMx7rrOzlV++dik8yE6n/oRSlo92tY/e2RHFawmTHJENRoDn83Clxnl2G1s30opilbf8AOV6UuqTVnHY+hiVoOUDpv/dYZLbHlJEQJBm4/rvJfLkAfWka89HLUlNJGmd+boHUCzw7WVUtMvbtKHrw3t2vu1VEqzuWh4nv85Q5EKJaSWvGCanliNO2USX4P1jS9Tem4CaMBH+p6gvvTn0qmKiRja5PZ0MbJxgmrr7U9Idiac0hPN2+WD1AYSfLV+dpOkUouRP+3tgeFlOatJTT0xb1dF8olb858GClilfoSxttqZzNIUaDxsC1hoRzslY9F0JoRwlEayRsvjc5AgII3nUJW3aJ8ZWdbP60mOXvcLtMoEY+cG8Qa8vPUOaXKg8S/sRT69XFMSGQrHdN0pJpwmbR0dwhbyicvZss3XShRBgsjAAmmzRU4UNzEUFd/J66hIswMCI1fDQGi/jNn4fW/UxFOk4TeTIO3wWOBdh3jP3MgTegCablR4rGPEc6MSN9ZoTNre3he2YSsF2vtH8hlFtugS08b2ztPNBx8VacJNKHq2woKKGhP7Od6hASttw98ha+2elFnVg06p7jc0U/2F7tsAAqBnTv6sTZHqhOZRvJpPZ19gNRNRw5YecUzmt7m033qzZaBcXNLHB7U+uZ3Hrjsirg1N5Jf1DxynJw7penf7nP9H5D5Wy4NOtEp+hssWd+tR6GFk+S+C9soQSewKOOBHNJQ8UqgHBSS5DkwMP25khrXkENglF8RKvef+RjDOP4w6bAKSt6ZJ6V/RWVVz5Nd5StgGwSo/9d3zDZeTHPWw6Pnn/fccqo4V/LqrquuuAZge1l9PVE6j3Zdzi735SJRsKevEcexFoDkmfss4Zie/C5v+5JfZdezBz9LH0dyBeL01GnTG3aVfSKCZlih+U1lS5cU/ckt1PMnLIf4OmRVOtF5F8BB5HtWNjwOgl9TxrFGg3I6qfxdBPJPpz5GragbaXOlLVNkstTpaJog61o4dxSyl/CcwHg4pMPpOzIkNCknbSRUz9f/+Gng6bf6TFiX9YYJX1j5vP7zfI6Stu408WBE/NVrdD3k5nz7DG03dVd4itGJk+fYXoenPG2CehIZJ0dKNScjB3ZMMFeV952JOPEQLD8M++ghv/w3InYpS0zhZC6n28saBtYsRtEtR7vK+06V/+BSf2YhWXuY44BVnFNzVMiyREAEfPQbRmxKK89EdaTj97X7ojgDGBKoyXVY41atKhrUqVGLBhS35IyjmWrOkcqAo98R5+sFqLTwqAUvGKT4sgpd7Ka6AKpIv65pd+CQPCnauiO7MMkS3F3kZ3v1plrArCo7S5OS+pBT9qzgkbMU99A0wFIJ0z3zEwadsGKFl2wPLRQbK6XQ3Mro8PH1Fr6fHVqn/jNppnJJnj/2voqUkRlb7QxPYDBXjDCtRyirg7xWvdS/SZ39b2aWXbuRYniFd7GxgwgjhgJswqbYjaxsFK/jgpAVEC1xsIgV9lrFvOphjVcpWk/J1RMdNSqyknstQAh2uC51xhi4AbNTm2RA9ThWolwWrz2XC49X7Ctx2EksFdn3DfCA6fBewtvL0i4wZsk+bEdhesDJpNOzx5arcXDXXyMcAKZ35s5C+a0muZ500+hw6zM0gA9IXsNwXv0e6LmIFEYShYZD4tG2GP0P0pdrnw9APz2tcD2TVvNv84Em/hLZm1JXsFQ4isRnCJeHndi3amV3dow1Me5qDkmkLv1ANKbsnn36F+IN8DTt98x9m6ngm6wU8oGbbhactGx93pAz2/6WiPuVDjCUfnssB5NR5GZfzxpDOH1GUqSU3KglcWXVGXwVKK/aL9m+XdHTs6gQtT0lS9C+qpXioaNOHJX77sQnbU4pkIoIWq7OoqYGVjWZLKnIRN0RNN/NZKTDLtPsHb8VBzJ8uXvX17qu90+GPdBUsU0txWRepx7+nLlA0gqLXq97FJU2hwSdvFU9gBqSYMvwSzdeZ6XS2M1hTf5nEoC0ihZBkbiLNvUVvraIKkm/Dk/kVheE57RxMCaN2fO0Lyl2FXcNhGcZC10sriORxugZt63UutRDCLaekah0CENSTFWi+HKFpgE16jNWtI+TWNtxkIJUOhEFgXi1bSRaHTYqw6sbFT0KCjfnnvE+XpCd36TAXdOX2vKLiV/4VQch8pIxH2iRIkhQ0vu2OXvH0otc086p+C1e2QO+69FU4urSu+LcXDmrsGmWQ9wS5H0bf3+189xTBqOYTkxQImsb4frwDwjzx6QELZScuZ7b7C1ceqMUJDanvaQnJasgz11AVDbrmtAs8XttPmWncApjHIxWBa9ymtU+u1matPQzri461sPEfF/UgZs9BshW5og9H7hnumuY4YRHVwuC7dXQBwgN4EL38cqiBaN+oCNKTS8nl7zbmSguFxP6MA6c9K6EfwSaOZeVq6WJ4dkUTU7IiMmBH5KCKH1l00EZ3BugdlBfhKXNsHGFRtfYp4Sz6sMq+gZ8Cp4rt9wq2KxtusYktSSPuXAKvENzH/ImoTqzqVSFk6yLtQQS+u5vIArY2pnZARcl1lJYD38cDbshbSx3rOWZREN87ph0rNIUgZkQDxH5+0T8fUw3GtTaTqikVfwr9OwazrwNKwangr2GDCr/pdLfT4OM88Ix+odOwNh0WK6pR/3S0GzQARKsw+213BWUmfwtHbqmKv1pyW5pDIem9eoMCvZ6l1wR3vJHBLcYUGCEYx+wpcGXxmPugNySc4uoBLGL7IYdCwL5OB8WYqQMjuYLdWXPpdSdcywJggSxTxgtd/JVZfef+MyNYxb8YBdqgrxzckxVCK+qpib7U1UGr3TfseHkChlRcRZ8bJ6aw61Eipm1fL0H0E7YSIyy8QW/Qv2owW81muGHMuzPOcwIy/DIgoQ4ynGH7+AJr9553QPg62OaTc6G2jdKoWvOMAgG1t/tDFB6DCpNaxtcEHzi/Lha5EK+u0yyH05t2j/o0r1t8SNrsKHTRA4pA55LC6kfCaW+/NjHIjRRXCh/s5xb1NAgxW5ie0n3kMdppazR4AB4kMWrqydlvV6WHQOF82Ly0DadnvMAw8BJRETly5gQdm3bEYuv1d7YkhPMMDOe1GCn7d34KQ95RKh2YbkPkB7hVEBPyo6xGProwSs7kkv2VrTtpzi6uhEyO1qzxsfuZ7tyKrVVPf3OhNFQ0zYOJS7AdpvDmOMjz3ZfpQ8dQpifDG8sr3qd+cE+4BL9zcuSf/lWy7pL4UlR3QoszxjLo1HbiaE1E3pq5V9v5O0UqOfeq5+GlTfWXBarsKO4D4DMNBnbse0iQuWQdiFMIQB1g7ZOj2unJVGE3I+tVLCHY/mKHcAUxXDYJA1oGDpDm0WjkhNM7HB7nvaMYYShK1r6hAV6Hhg4XsFA16gCcDI4thLA2xb/ThhP7V0i2OJj6kUQjiBUbqhYeoEORESDS8rMSFARdKhxfi5Fiy052ZIcQcmDQh9v4AasuU3wASjgCWNahem1RVB5prLqGRDZvhaOC46knnHqhOmXaZpNsaVjhoi0Jl5CDvo2IDT1kbaDo85+4Gn4BmtIywHBcfhU8tu27q2rl3/klhsRw8WlJReSAjRL0cUHbE7JO7Hscg/IrMBUm7vN8OFa+emoevcSsXHit6BXfX0YLq6wF5k7bts6DVeOrfYaT34/XSzvV47BXoRdcUAzZ2t1F7qYksR+MXLIrfugkh8+gpUS59u0VOfHdnbM5A5QuOA+hUP7f6gFh04kl7UQKxvBtVXY4TPyB0wWVDV1/kI+7UQb8PJ52ARACYSxvI3xpmnXK++7qxnCLkePAa3z6UWc93Ul31d90hnS1fWAcLvyBQXeJPpsJ9wz9roM1S88jo+K+GiGzs0mOfWvEoSKjMkLATbREV5Qd33NTK1Y8LmsSxaRIT6mPQlSwxW/zUX+ZHKSmc1kvMUjA2OndIdGi+HkKK69rL+MgpDgCh47Q+YVtgcZReWMzys+1LGYanX5YcjIM7B3LL/Q7tDUKE8pg554qvvUUX15yzLk5HcWDXL36l4GFnIOQ5GrqEy/s3BNX4Het4sVA5H0pFIbmJe6foBwBWhpz5M2sexc/lkNiFGHjQEU8grGFdh3CJvSIRhKPE92ES6suMmnqscyukVFTRRBtB+dtmje9x8x1y2WVylqcOVfnel8A80S82v5LNrtBJYmR2jNQ3DA6v8WUzFYYRReUiSNtQLT43ESdXdeHmweO16Wd/88C/Pb9l6SEeI3Fnf6ld+H3+kSQ0NjN7jWQPWNH2DirHfxplaOslAt2JALFn7TfBMtbyb6G+c8TqO7XT3tJ8ofypaeP7obuwYW/MZecU/b6x3INy24LHX8BkJkUpHerydwBQOrQnqKLqx9772m7b3RE6xaY8nmKe054HgMGPZsHrF5AovlvqA1ilMMxN5khBjDjw4PSPIf9oiDmKhcB8nL2WVR4k1Fg4CXV+12LKF9QHRiHSBC0QRL4SsOC65Gq9cj9RjyIyGIOqhp5hPNQjMNpFTOL9788qQAykSKCF+OxvfXCTdt/45bHBjmJD47j5dodE64LFTNZD+5nlPJJDaoWauCn8XA9g4DJg7Bg5CarGCfHty/r+hVJtSMs/illuiZubfcKEyLIP7Ts0QzNG0bZSQeNPsujYz7F8DzBHefE2FaVxTu7dVBfd+49B3BvWonV+zGU8rHNOzkKi4rV4I9lwmZggXA/TmWRA9WmPgxcFIxTTVSe0xfuwx9Fe/ny01mbymfTImuMBVfZCDVb9gNhA53ZiYhzvlRq2JT0Rn7Wvg9wsTetrJKnCfPvwqd3uU10aCubaoJOAJOmoTjp43ox62szBBN31tq9472Ze0218FqFdQVtdx7RH51R/oiq1qoxm1lddYo8a6TVAKJ0qpDtoTe5RaP/2ZMyUh/7zEWuVMgIWLyvggmImgfgBhJBCficm3J/ggTkE36hyzEkrlMtyjZWA6cmor1GecvgLYTEjj1Bk1qO2kxyqOUptHZlZU+EYpZSt9FSXkaExfFNAc0MkBLcox46/CFBqCL0paFvFea1EAubW+5f5E01LXZW2OTeHalLfnEFroIFzb3bQWrtmRVfDEHTLJUWHgmp+9IIUntsIxWbA4KuVheCafYHsFD2C9qrdFVRctZs7lrKkafgwwv9rvxcXa1ILYuSY2A32ZgkK13LlLpvUc2yEJHHPbwPB9q7y+IoH9nPToZavptj5dzVHcJVmAJ8Kwktaqi4lQ8Nhh4iQLfJAraS5S6Jnz+g8wd9cAfAeXLNKJr88XgqxH0OLuoTtcgHmNjTcF2YU4wacHpCsRStswgRq7IMAAdmFAUvC5T55Ega7HR2vdYZzuisY59sw8uDwTWj++OtG1uODhEdVYYqOirWAUebtXHzFd9xNv/1aQxes/XL5VTVG9Azg79HGWewZBjCO9c2vjB703eJp1dMGJKb4H1h6LSEsL91RXQ2O5ikibAEqFxoj9iOjrKMkYgOXX/C2R4QEeX4ISbNCDa6E4RLq968JQoAvdyBdlJ/U+RgGW97rtrJENwpRL7ZwZ6/fPuY7HOkJ0204MlFGBG7QgDTMtzFMQ1nKBMXHY+t2jLCWuAoJyAdAQA6lvVfmAmkM3Qc7BEoPPtG4CPHCt3nMhnKNJCi3OJsJjujzXm9bukoBV7Nj9SGn6avpkHSNFAel5Ttio17A2YuVRMAWvSKRWnJxwyedswR/AO6fqsg9QeZgh7YzQm8saxYbW5/j+tLMU1sMjhCloBleoppj+vggJdgXPYub4/4GiNNxxheswP7mtJRvYv1FqVP/nBhr8h0KSUezCtPtk+ZL1yziTDIh9/xro1Sc44f2wC4aeXvNInILxToMi++a04yX6nF1SiYnMIWIZlTzQxmaT1jl60shE6qArgL+JcUcr33B/4zuBTCiKIHZc7lyrsIeDUg0Xtrq0E9nFTTPp5Tzta7AWwo/pPaxEPR+CNyM/e7O9bys2qjSDS0z8HW8q/EdEpKjgtuoyseS3ere7HP5KgldeGCV+MePKqUiiwLe0HtYNtz0X4iPUiYbn7EcRx29+hZ91SzrS77nFOtmIiTdVZ7p+3b49f6OO2+72b6k9tnh5AZ+Chm7qtx0kzJgjjk3BwqhJpMsG8CmAvOCxtUwrMep6MiUNOI5S9XRTTVu2p+UTMMC273e47meC+AS4ow3sHvbyq3biNx9bL7N4RavYSM1sQMpC1SsFudy6NOI1p4jiF6aVxh2BxDkfmXhXwFdcFgmmoDIHWXN6+7p7a6p9MjwjFk+DejyLqYzsVMAIF02hGS8jsjBlN6b3H0/XpNQVF7IG9ErDs/c2G7pggXzqhmTbKHOgwazrq90fLLFY4HIVwn6n/32Z6jdqAvTBxml0Z/CWQ23h4y9OqfWJdI4wgHdUOT0xgRLOqabyaT0xKj/OhImK0NlTwcYh4+al5TMxCCtcVWoqDtWjjVj8fmc24xEDThPIBFXN3jrNL4gjRthRLFhMsiLMWUYxdlmjisBvmZQJjs3/zyWNH6WNf7Y3PJiKZqKzHNOLyuX1J0z5TfJlWF27/3OCHdSoGBB4H9VVkaG/QVBxjiWi7Fg6phZoQnR20x378jYM7GuQZi27bSZlnyxisTTGWG4k/zRIxFbDqDVMvA6s8e/qTpEpkKNgvuc9xUHMnNczgMq0L0D9CWzLfy9kWxEovxPBEjIMDv5rfJsc1s+i/PgxYt8ICjqgyjsQLAw5EhbTmULi4JUwCW+mOJvHDAIEZsOZ17Z89d78YawZWmXmFE7uLZ0quny0m7SHPemQk7BiAWbaerp4JlK6p+weD7qwphFWPgzMQU1Di2LtuEhYhhHRjZiZS9XHxsy7T6MdCCobw4khyAT/Vb6hA6DCaMbfFxHEjwBdbqxT2QmJgfb3enFWLROS3+GIsLVoifHzxKlew2VKX5ddpCT3F7xTQ2Nd0KYleKRXTQy8Qcno45OyW9mby+L3yN9ac2Ry1TfpJagb1iXxNYBfhLhWpQCbKDrarGpnFdh3PE2J1q7TLpieJPKVqljB0LWhBPo8DdLMq9JK9Xp2FX1FYEr/9Rd5PxcczMMITRV3ztCFsp3y+AqnuXfbViOjYb5TRwiH8yifbvppWH+zhUbaNmidcpRaYL+XXjj0z2UjN+SUgkciPQ3Dx3b3cTDUGvrKYTr/9dMdoR6RQfQN3Dqk61k/o+PVTeEl5VsCNG806FhKht6p8cyj2n2eRo807U9+fZ07cPCb725KNUGldVDl7NdY3WYPVkKvqcOC6TMYkn5c9xiHkluQ6H44gc0/IFqS0VvewXOPkNJJQLOWN28uzequmMdyw/Jl71ojbx/bPWXjYxpPLdxP1E2zjHqVAbkvgJ7LxOpqFL93p6SDnXyYskKBHjNiZMYjzGtEq0PClzolfFqcUmJ2g9jbvmGq/les6lBe75MTibwLGDrHtw2kyYniS7CCDWaKnPaVKyzjsLiWE9FDOEsBrUYTRLI0W182TCBhtOoa3B5gwBCa6L/n3BdOCbPThr2T6P+VG9maSI94uTuV4hMM+wv4cBp5FB10SFRr+Nbb6tkiH7jicxOLzQ10oF04V/RuXl7cR+gmrMR2hhCtyZw/NMTfhC1ZEONwmkLNLDQClwj1dY1rpoJB4jqd9ZLGdU6R01ltxjrSsYyOH74yzkxch6xFnYRwGgKeLP17YsR1M/7US6zfee5psMJmmF0d3lRpOGLUgv2EE+mfEdK7RnkjBMev7XhP3keyLlj+zF8ubinuayYmsAbTv3ZmJ1HfwZFIkQfunCGJYRYRpaqxAPwmfsgqlelWk7+9TEVREUAvMMicbiHR8Xl1pBEJ9tTKzBbPhj41gvoHy3sy09U4RqEdZvGx8rbtM1BDPV+VJqsxOdRJeqYJVyTf14rOp7FUe7HyG0MlmUSwPZiqdBy9w3gqjGdipjUbDAUfOCFd2hd33xGYaWkibkByTxGl+OnEfuf5jM05n4cc48ttEEjVlDLh6QjiCEhw/RB9rJkrvcDcOEMtO6TOEF59rD1jgDDxttsQAg/Mj5mVu/oAm4/xixU8Wd7EzMxL30Yorhs5ZOgjIKDGe924q7ri2hjbE6h0I+kbIx6JAvUxcRVIT4pL3PLJF/u5s7CDcBWMXokHRIA6gDvF5AYeEir/B6Bsen/UwLnuytNFiKj8zYTuU4prqZrOLMtMv2whxAamlVSi4wDoJpSdjQ/Ys9shVL1T1mNcxa6Gs66rBhBNLMu/EwnF0sNl4ER8Wx99IqwlMPFs34NgXZyqR4lb/RnlXxfLRY5lep9RPnpdf1HEeRAXx+X7quJFjkIyljdYVEIknTQzuogj6JYAw9TabuF9++JL+A20gGmkJ28DviSPNUhS/s7sDrGykgV8PPx3UhmTmCl/HK1wWSfDHMWEihbECgm3zx+to1W3jzca4houHgTLAwRTaFdH76HaN4hYaRUEIHdA0vdpB8mKadYsokqaDJex9Uj3to+TPdUA0Z8qS6+bmw+hKhYW72A45s4EcHuzNXmZIJ0I6Uw3baSi1dDVKsH4IFaOTPybccV3mjUhwBZr3twwiiXKnikUnf45H/BAuTruSLwc14Gb8WXBj6IxH/MCT0DD56Eazmqp5PhJ+WyLlOryIrGnyrIm1EUsSEwsyQaQXu51U09TkaYo764bja488uqgexlhQgru1WXOqXBDSg0oBtbxQ2VAfGLqyvfIkQ+tWB5ojGMwhT/pAr4kVB6HL/b6Pr831LHfWwAqAWkfnkBHGqA2PvLhT88QHmEV70cOLHM/zgv/gSKdQnRiRxPelePov6dZwkd9Ptz756fVea+M6LmJlImmEeLzlZZcTaC0n4/TrhNzCOSkqX1uBA88klb424ly/Pe3vW07ovuE3MIIuP/u9ErpALD7j5Mo1YrlVpGNOostTJoFP68ZC/E4Q3Ep78QtHw2LE8tEi5qSVjGrAVk2zs8mT32KP7/QN1zdDuvc2h7IsLdqTLfPhGuwNdPxpFH6Q/XYNGweUcvmVcrqvOrrFMYlW8lh+h5YQjCC84boirLu7RwfiPKyAPP1q0OxIBadxDs6NpbelfYukw1grpR6Vf/HAxA37AzrLv7yw9Edot5tNIPGmguR+VBkNqenm6/kx74OR7ICz8a+sabrBs6NE07BFZ2CGwprZ9F9QpgIZmd5qW8RR5teocGmfDAHfhFZHU8RlDT9JJf2HK36lE/UdxOS2ryG3EGVXXH4nO9Kwo4+I3f+d7pzCMWiKxH4LbiHf/hSLn7Vvgnk3iochzxZU0bnWl98XdCKoGbHOPAvLB17dnaTo4YO7QycKhmwmT0166ENgMjdMxwC8z6xvwiFVd9h7CXlPNxmCfe5v6GWulC/iOMjhtlNB/Psp+eWct+xYSK590gF2JTru9H1doINYA+sWeNOrPiUidK8raJtF2R1Lfh82DrlmUOpxG78Gj6836Jof8dkSWPXfx7BTtThHzaw6l9GvBfTtvfZC0t5+T5Dy3yS/Iq34YetDEhSfQ8rp9NDRo95FEMdOfprmKmsiT1hrSec4gvUD/FcTUWp1O+lGSVwVR4xkR9orYgalYFqHmmGlVFkZxLGp5heaIznBZknJGmPa6QdclHUha4MkmkOKJOjoAW2RgFzfcpuZnyhTfLPsbZMyUsBs7c3RqYj/qGvdg1qnVyXJp/gqZoMaKC6rUBZeziq3+QTyR3g8F1zGMh6/SWs06pvqz8yy/5vbFn99c0yag0yXju3JiYuSHCjGFl3/Q9uBDWh4XG63HXhDf9ZopGWQjT+agUmiRyPSqvS0tkwjvi5x/BIw91hk/mEXel17RVOoQOm7OIjv63XHanG55wY63irN3WGx1DiR6/b22F1ksurwIu9GYJ5N4bSHUzn1QpG8K7NKkPZPAC3qZB0SD44eW1VzMVuUnAWemXf3K93bifx2/5z8ptGIg378QXuML8hIphYomkuEhvaZvcaxEzUoBA8vxHDe0KqW5wjDhOItStfArsx8PWqbP+76XOJMcJ8tDX8a5IlgDoJhEDRQuKOi20Ghnjx/fLGJ7V49vKnmzheHkW3qzuFIbLsU1gmClQKUCx+jHQ2h4/nDtP+tYPDQjGNgtAsiICW9FSivHtU076I0FBkEqF2smCiYBrAj3Tof4gd8JvPmBxHZieWWNxew1OazQdAKu7EJC4fmHLWdr1PEMaCWi4Bh8vca3+kp0QleCXlc9kP0gt6G6NqN+7vV3y+dK/AFPEf9R8xbMLYivBeUuqGzaTlFeOY9ynOEN0L8dV8K9InLoU1+09AzqH7N+yFuTffNE0uJAccxBTToy/QzvkKFPS+F/Czk3AmyCYW1PVtF85M0OR6Xc8y2DUrtXNmExW3utvk/Uej5aok9nL0ULuiJg84fJzRCcZJMSIq3yk163OAftUEoKoE/Wg6XKPFPlVF8o5p8taff3rzfYzrv0+gMv1yGjB4CbpTKS7TF7Yo4ngKoafwemaOiP8Gx1FCT7Qsxv9n1BKE99oy9uI8Bk7tt3aonmzA9d1D4zOxpbVe12PwqeDybZvd6tkTpycfoKx9ZSK7i6wZaQpPYqZ3Lut1OfNhMEx2AAak+vezx1W0eeLbm1lx8dibZqPBgqj6Z2GHRooo2VaZ7vA5budOjNZY+1JexPp4n2d11RzrbXtcjd40JTetxo9sFDyNLFujjdjCT8ikuGbbtBsYKirVFIuqDhUWKBGzammJkT5l4Ell021mAXU3wneY7je0bTywKD/cofCC1u20C73sud92zfk/bx5xu+qyzYL4z8zGQUckfxSWqaL3qV6/D2JyTfNKNHZhWx2+q2opzxtMH93eD4xSKhiDzLmoTpcdXNA4SZ5eeH92YkimlYnMHi0dQPlzRmcJFon9FnG08HLa+1m6H1mbkR0TgLiJPP7c+tI7vvhSuSJH4y4dp3FaTy/bsy5oJKICHmVQLgD09JqtyIfwNC/dcJqqR5H8hKcUXyauoMpLvp3379cs/U42aAEq1a8aEEMjYZoqj58c/pW4/Xw1SJHImmjJoTduQ3Eq7nQnWXX31tsuvK/ivTqYYxLqgBsV4noebTofsmmKdavswmx3wph9m+N4y6LJRuNnsfW1KPagl+pkUa0w4YFPDCdLalHLLpDXApttKhaQzr3O3aZGEzuJaG1yS1XmreMrLhdIXY5P6xkh+qkuSbrqEURGiQddVn3Ltr2xacvkIYiN4JCXO5YZ+4zPvwQOyzUbM2fjw5gT/VbigrSA5E92W/+6FRAb6Jr7p4mJqBqNiqY8yJFnf4BrU0qE2oXsNLjKoRGPtPePo3hNzwIFsnxjNmIq4EN+iQbGisUUCIjWn/0eMuovSsxE8LJMbpgiz8TQP0ncE26ZV3i7WxGdsAxvv2iLXjDsJbdtjmpb1XB8pWFbLiuZNfFMg3EVR1LoVZ3no0723qPNrFBeasvNaGyWKBh6ewJWtdDUmaOtaNTg6WDg7ezzmWccZSW9eAsxU98XIoge/R46nbdgoAvba6c7H06uQmSBd/69zT6Pl4jb6yyXCw995C0u0iK0F9rYTqfj4RnGO9EZPKxr7jY9HiS5O5SJJlu1FLX8ijP0BXVTnpVGK7pU4Fu+ng4HvXs8tMMV09oFNMoy2chtlKDAmGZcJ3q4hXyNxfvybqVnUEsqolNqMQRXalP0OKk/220sV/Z/1JIbGrCZsFnkG3oVtRgJUnCt2y/Mp1Km23rBum/KnjgWLQsNq42zWCV6mpKySebL9gKEdRrY2jzxO3gbIIHytnWArdTFvvMxtDUq65ER+qai9v6MiCKHXq0cZBZK/eCIwghdHVvTMClzn0uaeajKoixTBW9ofRqFwBIqJkWw5JAW61rXHnPJUDfYEazieNcuwoP6W/vWgIC31gBSY4LSPkklXwCuOa+gHL+VwlreiG7DGWxjqZftBK8iRLdgWBausldJ5wHX17xGgSRogWoYqastWxG/kkG+eOZqSBMVDCHBmhM4Owvj4lljAr4SsKqb2y9tHFettSMEjF72skE4VHsCVAum8WSpy9w7p6Q57vNYemnkJPPTihDKIgYYxL8Pbos7wNnLOZoRdZPZCnR2syDaAwh53ZhGyExvOdn5cs5tu8W9AaQaxU00WUIkEvq+CpxWK3cDMdtFgrC6pdW9FSGH7P3LTXz4jt2bCrxLcxgp/FSy0bOZ1tZl2hCpQrXIn3Fb1TdU3GCBRgDhRX/uQP5NZBOx3sPb1CBkLSG4nlYos6XstZBbHAK8muD7oPtHLOGNZfI6N+PNr6Nusj7NwEER1/0O1L2R8lLPV84lv/75IegvW7ZikJwPBDX7+XyMroavlEGM4ji0N8CRyCIQNLiuwvjXZpz9LoON/mZ4444m9qM8tUz+wnvmUOjKItlOKbNJjcUv+2WmWidHQ6NQ+8Gj04RkWNRgE911GLUv3If9q3JMZj9gUEIWaWra6jrPdiCvKRWIWKbBh6rDgN2PP27TPAbG6fTNecHSzk/a5ebP+IhBr0jkYoe3ltlrv2q1bqndghTicyT/YrzGKZbhsS2ps5D8Z1ttRbh0sB8S7pwsq6p/G3b2BjPK7F7OFEbRhNF3xKuuS0wWzff7DSj7SxWCn0ciwCRQsO59vp8n5MgYhtdh9s771Ukl+N/PJP5dhfNHNW9StH+Us3Kb3vqOlsECApw/P8OgSfWoYI1XJc9TxPRursZQKEF98vAiCAxH9MaX9Pt+tdSkNN59LnLUd3no32yYLlHhJG1LG2fqRWhdpfpaqmZ3IKJCPyjm8sVnJ98dSM2dABD965KytETZa1/eyijfrIiA71FyFuHo69I1fu93ph+6OCtpL/18SnFLNq+g81MbgyYrl1c83EcV6vsyO/2AoKF8r88hYH9v6kKD7ko1cjWi/wgdYXSRC5t8D7/MWvgHW+/311Nai/T8dTtXxLujluwWGPXL2KyKZzcLmp4IlpjGtP8smD1gfYHgz+5oD2Lr5kbLGumSrSb5S7ERHKVpz2yDw5gGA1tjCN5F46nWmyjXCSNoI7GzovnEMF8UYceJg5/AuOyA2V6saQtbpaerJLuoZP0fb243G72EPSrdcd3JUfzATyQ6+dFkbmoixsxXadmHCNzzpE3SOr3IBBH8rJNQnJjbLqfFj7+BP69JNEzcKUUIQm1Gdy3PztMPwvIYfms0ldvUsUMGCLuC1K3ynKvPnB6XnTQob5nldA06/q3yoFnufi8Y2wP5twlcfy/+ID+on9YkOC/2iy1axu2t0xSCOEySLjeX1zo7+pLR0qk2g6RVECsuJ+mspJuo4i6y/zZ6iNMi4MfMxQJBzTv9gQeb6xNX3DWroGDYP/itUQc0FG/u1IiZsQmbeVRbhYx38fMVrxdemwjLgyEtlYq3B1oA3j8o/9GEEvhQLEQY2Avg43Lj1+yAjIGPiaT2Ojtz0RRHfAuoeJGXPmIF73Doc7Vw/XVfSmgBs+fKw8vl5GaOIVOHcYkWQIckZkkbng07bsmOyqG5A3PLdAiK9NwNJS6bt/y5r2QF9ocTBAwf2/O0oqLg2Dq4BfYsOJIoT7Nma+vSzdc7BqYfX5IfbUdNRqaIHjWqObhXKyeJkDVXiWUze1+SQCJurfq1D0LwpDD5v2sIbg/WvgE4boM75AvPVFMMqr4QB6kN0r3Vkv/pZ9HcIAhFjs06NdoIwEieqcUvo9fXV0rlCQSXwj6I3upa6i+bFnOthZze7tYU3q7qg4ADi12cibnu60phYpKLObOqWzRrMuvXFAChyqi5/Y90SeOcA+ksS7zDze4FqEEpsmQDzcv53ogFZv4IygjiJr01wj7h4+qkVusEvdtbzZcXIZRrLoU4yVw9KDriVKYtRXPuOTt9wEGJ4FXIlx+AzIetT2v6lGy8xp3TRr2VwMw2JjS0JxgY+oslA8Y4KzWCjOJFnztwuZxN4fCmTOTq2aZ1uWoqFtVuvGWt6jUgx5dMqzKli6rG5HlHpm8JlHWdQp0cAbJOxcRlUhGHigKkHF8XR0nypwJKprMOTDM5xM/LL82cpxE2Zkm+HELgcs52mk3JCRw8q+FXynzWFnUUR5Xw0WnDRTXLm7KSFSbEmst9ulI96/IMN8x5TlG+8GndHxYHQDof22ZblTsFuDrYdjnlE7Jb0J8W6PfbGjXIVOr/i/EPxH2wFhH+j2yM2IEE7E8nNPCvHm+8jhWXWiMCvu7CrPRXyyadyGbImN45C7Jkd3jS5xZ9qxr8PR/cFsLLPtOxvn5aHTwh+lXZiBRpsbDVaz6P88xVSVCLs4EtNBqE07Ft72M4IKS7VTwnrv9mArjFPoCq7C4M2fASZ88UiIp5BDq4RZtcnlihtLg2WvPbGVgU5hnFuCnUqvQ6doGC48mHmIETxo+PAQH06wDL4bzXWh+4O0bafgrOyvsmb6IZOWDh/5HpGe5McX6N+vDeYre1T8FAS4CayAzA/qO5fAS85ccxWIt16wwbfa1FDn3hJ7hmgRL9dEhuCjvTC9TZDrZhVL70EU6nssJNeizPAfuIp8IiHRE/hgHzLlrmkaYldwbAz9tIrJ4dXuXqT4E24gCg32qBz06soK8dI/uWGAFGWZTIgVv24GT4orMTC2g4ErYMJm4DzfIv3haA+Om9i76GWwlgUvXg2Z13NMOUrWcu9VgakNV20H1AZL5BXOKq5dUpCQByVZcRWWAVqAeRAeqyrmMHp9snjaubrlf1Yw/tWIkt/lmiKWBUd0RZEXZmyraRIC5FlVfxUpMNf2aI6l7yIroBzz4BIICpKbCYSiCpyBlHymDJtvK7GKOKxCGeZcLsImMrlZYaXMbNYk6bd+vM9S23uIY1QVCD1bZPXNsVgX0IBdAl6XKHL3qIGpowcI9W+ZkuuQHZMN6dSeZdwefjtpE4AoHWtkpko0szwOODoRbz12tGSG8d3NYnD4uDu42Arr2SBIxcbKrR0EndUEsXSFUsUNz0NKjn3FWpaJVzlMNLEoFZ/DuSEQ6UxuG6rd8OkKOw2x4KTOqsugI++4kox5gwRZpQavW1LO4/hc9RK/7YKLYL7eeRqvSGwlcwFcwkjB7MEJXWikvsajSesKHT/f9smplS8y6NYgpg/6/EV/fsop0UM6w01Y2YRm5xFPXt3m6VO1AQX/3qfS3WfjghYldvGTEgk8939qmRTBQJiW5YUPeGkUjRXTStydit2oG0VIdrZ9c139z9LeY6+fY3QGNxPXyK6T321Uk6c+2WjBkExgaiHRimaADDQO4F4wHQNLEXSw/eB+tfIS4ADIua14YpkKed1d0llMIHZY7j52mkYyMN9oAAKvTnNWpmITQCGzgX+OZYIqXH+NJJQWHsYGtmPSqDKrnHdsQYI8XWc9TMUN2K92L2eiHMITdwA45rmGp6QGXCbFBPA61JNDXkjuM3UoyetnuyilQ3i07yCau+QF2Nu7maOTf0s3PYXzZC2Ftk95OwP1S3F0hAWl20/M/SFZud6mS6M6Gz7sUGOJBSD7BxCwCIiZPStK9U/t+mthRrFo7fXrYVOgdMQ34zMIxJzz2SMhn1SnV/MER4p5X0TJfMQLsnOkLV0qTBm295fdJPFcXpJcUlNhBQmkvHA4IxOm8rdMA2dVewgjpQjW772MTGUYW9jWZKr75u8GXBiWQhYgSVXO54JSIuaMw+HNU9g537hVQ81K3G3mAEdK1Sqw4/16h/uX3t90qH1OQmi5pAKP25ikM6HaZtnoLH+bzq0Lev8MUFDOdJh7BwmWfjf2IoF7Z6DhvZ5Za0c8Hf3tMmOKbHftYufhyEJLbUvIaOe5nZjD37U/Kk4zUX1CNP1Qw5hEKVWbTHAPBldJKg1S/6Rb4IHwhsh7SLpUM1WyR5rbBNW29bwNHfMiK+0XkhhxUsYJzF3zHIE9zj9+GMAjRtjVeo5ouMr3ZaJGgGkDYXbHttLuAV11+/hv9k8JfE7TzJWizERo6WX8a3byEfvK6jBJInJw3nXToOMR0J9tel+S6Oxj6R/O+jaxYwPAHfNIjYv4DrSeulqIR8r1uMRj8jzt/gaOqPQr6ZC7RAUbTrblVvZH7AKQmaH4G9Ledw4RyLdfHtS0lDpYpd/JSLU2O/J3ddJ/+0qzgwcF8/TxVaUWS/wZNf5irRsNmHe1H/O6HJ/CuvdxyMsCqfnpR6nbXpPd7pmIIcCk6Rs0C5qaKpx0aphkMsJ/JM2jYF2u6Kk8zBdZS7QU4sKpIPaUPz2/XCIHxHztA7QfSom5KeZKpd+VYxxifjFP/iBO3hIk9/UUAuudPBTeXyFOM0MC6eC2e1sieYTCSgwuSZQ6CiKULmAzmYaIKtkeMcGsShQ+sn11e8RFm2cNgcYK9UyU3LmHmjl9lN7ijafjKpeosEu5MAU8AHW2eDK/v9PLLgJeZIAY8WgObcwHDXtuCJ/Y2It/kMGBVCFleUmrLN0iAxk6ZQkFIGW5etVR4GpUR2fqiBqnZHt9k1aF0XEszs4FKe57BJ0cxdOaRWlEXnEPM1BoecdLZ+B7LFtRXtSGyVnprJFIDXWefR5gFJBPOhFHmK9PCukUBO1vij8WujVZsO4mhqDForiYHeQUGCloPRZtRgGQuRupcEJaZRcHq53jM/qZYxZzQ/bwMznIHtMZKFrDI/IO0SVlsCP93PJLkpZqK2CSspPmuizL5hoJAz/QUkdvQcO8JIeUJ2lcFXJv1orbjRZOI9FQfU/+RiOjVNY0daJa0mNX5637m40pxO1QErzfKIG9pzy1d/WQXbTi4kLQXoZpSvOfOukZXJaDen5cLMwAgX271WysUQS56vaWYsrA87wja7D2ckB+WWxa0/UtwDfzS6l8vk23Di2DD1ORUHZSUB1P821ZkeTtM3cxmfKH8eU2gRraHXMrd9xHTORZgswiA/HrtJzkq5BnqZC7YVrQLWx3mIR4yZtt+17k+cEb/U41K8by58EPDeKAsf2xf7u6YmMUMYOC36OYyCAtM5a8+mXK/6/8pBhTxD2YWS8mzLJi0UAkVgEAGEZKWt0ibdI85qOGuhgY8n+q+RnvCsNqWOTMJeOOnw5tpD5SDjBF6ml8EX9h5twah1hSTL0MJkvWXDSQpd6DiTLYTyGang3hMwdDFREOWL08cgTyJf7NLVBjdM5hAtIJp69MNcof0yCKqIiK7YYNHK7EGIXafdCdS1WbBF3qYVMbU3roNvld2aJkwZQQfOVhMVz9I8jPEALSdaec53Lm9iwAIrAFMwKnvPIo4czscuYi6Zl6MDHU8/+rIwh2nW2qn97B/WN4xlMBQW6E6W7w2PhIXfxfi62bIbnFuOxNUXrUwpbZefpsUdfDSQOzzsZYXYFSYBIbhTG3YhHa1w+PXn9GoRtHR752WtQV5Ig+qPMdgEgPKA2Cr6UMsmG6pTNsepcalg9n3m/gtNiacaw/E0OWKBNfwwFy3rP7wE4joErxoGydpyUEABdIgbPOueHVJB46wOIUAtZeEFx3DLkcBrGeXS7yGMQ/h4AMcbBdZ31lTqDBM/Vry/UHEyyY1arnAyyl5vL1y0ODzXGLLhlnL4HPmjE9/NA5ooYW+Jru59fe1rOszt6vF+fJEGWIKihQIT6unUzJ+LfR15QXRJkUCac0R4WsJEFxb/ZKpqv/vNKTvekJoShj8s/tkD8t3lSqX6/ISBqAXL394jlf2AAPYKbbYw9CF+wF1Y2G556wvBMd7WHpNYVQqiT31f0hZn3HfjU+pEzLBUqL5E8xtu2renZQ73aZQRMKO6Ww+SY+Ni2KKOtuVOLJonFlFQwr2F93FydZR+NDrGB4IB+dgbzdGSpP8w3PiUSy8fAagXgXvNhzjFBquNYr6FHgMFNwSvRXOJAHPuZjqY05n0NGT5l0hDDNpE7MSUCEXYftKAWdbq0hWseJ96qyhwaAgAK4ptrfd46gcvEs+A8Z1UZzBO4v444YqcQjDmnhOdZSuGqq1ShSq8ypaD+BHQbU9QDy84l9bHEI7h3faCN0RUOKIQrOhtdQjb84PThJHQKQz3AMzIbSZOHLA3fIBhaoDyhu+7XNhLZAsgbwjJ/KYa3zbXUvIZldcL8r2ekOe5jygfMP/kuPM7vUjdgolXO++MdBoUntVXjcRtoE7iykU2GQ564iCd60KiCKGk0ZZmv1geBZuWc3juZk923UMaHhPucZaJz/hbzhzK9HSMMUFWp93S5PgZPWW956ZFrMWe7TgefqbpIAR3iSYajy9OIQE7kW/fEfrgxPzA7zkpUr6l66BBixU10H7BXO3FE+dSzO4T2VIlLaiyl0YROzmk92i5OgQtzpUPNfPybzGIywKAWzoZcsoN0ZMxcZqAagMlzWcp2Z8E9WzG+ZfbhK02w3S5wS1cN7KusDw/ehmAlMNc4h3r2aX2PpmWpXtbh7mv+9+NcravmbFHirnlV/ry8WPTuNseQBM9xglvBu4G/0pPBr9b26JIvYg4AGZ1BR13pHWAy2c3NjJ+DgnadOJ/N2E3sOCcF/rHngSLSGAUiK1QgnCZBelu5MiCgcikwP6WFcxIalAvx+emml4qjBY9Yg/QF2az7c+pvh8y/PkVuf8xO4bgCJoGQcvJesZ0bo/NHJu7405Zxf9i50u1KCv6djHOYZ9YOZ1W7cPe2MsNZ3EoY0BeZPHJomA4kWHILel6BcUo2DjPhNFH91jY/izNdLTqQANVBKoHB1dO8QhfMOT/85ydQcZaqvKEhCKkLBcZO6n8kiLfPp5GYi0Uff034sglgRg4UzmHEW4xPgTPD1oyGbBC5kh/J73ulGG2aTWnK2DFNr9bsnXXY4yjHOc66NS1K7+0+u7+A0/ezNYu05uRVSXf3+mdmM5mJfVlqfCNAx460+rJB87I6XPxLxjxPUpQ6ZLF8uBRC0xO1AiYS64QStswjFt+rj0atJwWbBzoni26seQnkdCB0UJuRj9OBeEzAeteW6SEPTxrmFECpxktC9yT9vj1gu5s7huI/q4EM3TXoUierDLzVNVYvyYfSP5y6UYhL3dhD+LTHxfn4orp74chjlLYQJq4U8iUf/s5MlPwmGcWG9odA3DETn5npmhZHdZ2iH6H08hcU3Mntc0oweU57/vopV2EnxRr+w/r2GZ0gYf65QQ7ljETaU7rxs2x17zVUSlOrpStxbe60rry5x9UCGeqhbVWpN0tnoxKjWfKxyXYl6OlCSpZXxeHNek6qm+07DJ1cxLRZgv0otepUJ/VR+GxiaoD5AkmTgGrLkdSnioFLNxCQLnCaJRukajG2RcWSSS+jh6SGVIou2n27X0MQlTDVEWl/qMMPQbgXbSfP3/Op+CKMlhX1R2o0OYXSu12pvRy8fwNop9iC1hTV8bnOd3AO50+j7WjTGWfsr5/WUNMeyYErKht2XAzgljOXauINfu4XM9afCKgpXC8SEVZwa5qOdaZYXGTH/orFUqBCOHMEHVojIMUemRHVagwm7YunH9M+btMxORbzNDtZkcieKvHdql1JzYc/Q+u0T7pRuajh1CTTWt8EAhO5ZHNtiVpJFNN214ha9bjtJFwcgO4mEXu555TetIkzSVnUj5xRq6lR8tq7Z/KUKFeDn6m5vHQNjCpjOpzW0DpD9XAGc+f6MQWM2pKiN0O5JEf8GbTILY7oc+IRf9OyIo/rU7hTiYmrguRWmwcWhoHZEZZrxgfsSCyZliQDPJfO+Cz16ZIL17f86L+FNczOvh7R2+bdxhUG+FqT8jK3lVM5qV7/F9AApf36LLJ95T7+pMf+G9LO0Ke/J/Yj8/GUWrHMID+in0Q6mH4D8td/6dUS2/Q7Qgmc/j6Qct8/w+RXRAVPnzFmtz9m4pta3BPIdrQa/j1gO4TDOEZ/fAmpnthvXa8SNfKovbR0xziQ6s2whA5lOk8hsT6v47GDVL65gIDHci+ae4EdEu0JE1h9p+FSDlAkyOU5vzZ9TaIyM++7ZMlLmzVDIlB0KZ3IPvSYj/SES9DYL2MdE0/pa9XzE5F9DzwPWbXWww2GJQdnzg0Fcucv5E36i16mgDBWa5SWfysEnoDU/+NKF7iYOrQnO/RRwRl5/zhe5qb1a0KGzrEsga6L4zldfRH2GmNJeWcYtR/a/gOm3vQpZtP2LA8zv934pNf93qxeBnFSpWCBlGG00KBqGqn09xXGLUq6bZ3Hd49Wg1MPwPOU06HSoljsy0vHytFdhUGnQecZ5237WrltuyLI7le1EIqpvvrlqPudwEqkvbspe1X3RDN8T3SfZ6ePOVnL2qU87MsSC3GE3rgl6VJMjNRMr+wZFGpYVsbaUkjsnvW+5WDPc9loe995GZgOs+SO3nvBNNtYKV0Xbm9+G5Kmv3pX5J2hnj/c22Kf0/0SpTJvESB3ffaJl6SiEKPhmv5zKoTh0eIIXBDbsUXFFwJ0ciCJpdEa+zAysEpWah5lSEqRDG5P4f7wSCDfk7n8Wjgbvo2VeskiEvEmyMH79InyRQd4+edxe18/mG3zJjUPeB8T6xdz/79kfL5YI/jJ5JZjclauyrZlM35/NLq7asTllMRyNFap4DuznKGRFXyIRKzMmMuZ1uiQnueOa5SFyiRWZ5ZejGBCo6VQZnuruijPmXhYj10op1Ze/xYgs+/s9wZqREZWmWLIpc+yHmMnhdexxMO+LKWd4BAuLwnI6NUfTv0AwChOjVH17Q2gNXibla2m3P2KvO9SqCgHQCxLMNrLrEEaLMIEQ3pbnWYKlux8nOCR2GyuOwjfPqqJ5kMa5+zS5Wo/VZA45r52KH/Ljl6Tvh+Zz4WHFUPP1TtAzN370eOuSQ7vzxTOWdLdJPWqqhhmh9O2H/4K/P/EHdAK2Sz3GdLhLoU74cQRlbsPjj6j1dqKXWZD8XOmpocber0cEzwQ6fxuHHKBNr6V/gbkfkUaQfl7amc5i+l+QWXmisc2akp5jv5gJHTxsmkqgW/J+afy4eIF9mdtZZE3ISD+Rd2/jdm2Ih+nS19v6KJpPKdhi1BC4GRv7wj35S/orHo07qeyWwx44cVJF3hCjfCXVtww3Nz+X7HMvqFssul71mAX3Kf8EoKSozv4qWuJQgh3umgBUJu3vmmLhmQvrk/Tz5kg9x6XJrTWfCmXu9H1uCldVKTQls7VS4nFyaK+jBGE9EPcvfaNnxvTVckx2WINk9Meb/8tNvtyRXeccVrbT6yVo2tpJmvRXT+07YxHGIbmu/MCqBR+T47hP+eIb2htH5LXPHfinlkpHr5d7E+RoxBwy3z7fu5I8L4uxeeu9D+G6FjzT4lfxQcMRoqlHqhPVdLwnVv83k0JEMkx91XPcVXd65pN6YeLmIkydKewo8ytT5mllOwunxblFkrJJTLjk5w5/y9Y5TjTjzcU1HF+RZrDgopgrAFFj0qf0IqZc1tK0/hyyjRmMal8iNuPO+6z+/rcQmMgEO6sNBF13JIoij0uTzW7HYIm/Uto12I5RBCYnm/rtqOxZOqbTK/bIxDTlZaRZ5406NP/A9Xxw7lohdi+JOB7pw8NJajBoL7pA6OLOHgDDqVqJqeYXCH77n9/VCO8r6sHV5EfzuaTVfBj5k+JX7UeLvUA6QhaQdCZW9ifWoaehW5z2wHK/04Gnv/jnjK38BjD/DVauR+L1fdE/Xk04cNbfW4m/T3eGDbRalCSiEuYOm52YIUSocY3rwpLtmsiGRcRQJrQte9Ltkl77NG/Ik8rS3FkwAHPZdkuhW4jOoknxRhR40HVAYruUm1NFiu6ihDF9MvurHkYU3JOdBR4BSMKdPLm3/6kDLCP1cOOsYhITTqODFKce0BYImsGyS2nmRsHXUtyqLjdhgZYWgBTgtuzWw177qKcGjrGviQIRkuXHURP27hjO7PBXqtoQH/IzcesyXkfd7qBj5DTsU6Frho4bDJOV7y41at9JqHLuKzc+1u0N/ti6K774R+NvtbPWmsUpDvx4+/rgLzYW/G03Nbpr0K6HhiOymeBLu9t1059behi1g7Hd39FXyBaVVe8ybrrO0sOEio/4q3sQ11+gDllsrje0ViPr05+TaB9cwo1VIILYX4btEac+2jGcAvQqx8psCX35/5dfJp0XZPWMlgPGFBj5Uwx4+YOEF2TrMIMQCWQugHbUe+Vm4gPtrYPNbxDxsRnRkcL2dfLwOnhUvbbEbBx3V2lUCJVDaMf2pyiZEnEJmWOBK5RsEqJyjxPlLPKcBc34wcizWT8x72En0sdVMO+Vkzd7Ovn9bqSINDAxSC0wF1hHcBK9xym68m6wmZPEi9PHj4YsSbA5lXOvP0PeFvAdgR12qsijosZ0MpWPE1zoDRhY05K/hm1SFEI8W8yyg7s6DuYDz1Da7ns67BTe0bfTVRCuYg2k5K7TPBlX/cXWgGwJvqBPEAG11qtm17v1vxHN5dq3UQy1Dr4Wh+x0+BlCxxIay6MwNkdJ9+fJVrLA4mdFO73mVVjCkIXNE2s9wGINQJO6XHW2D+21wRRWvd3T3chys3xzlqKNiiTszXquydR9rtiNOccEoeEiKK4Bxuv6/0i3hxLT2GJe2wVZahIgnxqfMWnO4LgObpV1AkGboap5Vcg9VgFWpbsTbYhOeQo99iSYp+EFSkNwj9qeANv2rBx4lwstoiUrBPL8AphNbxgmXJq7mLt9Y+2gSZLM7EPhlDkhh0kYVUYh7xVu4dyQ13v23WLtW/BF2Q0xWMjKKUIgvO0np7vHu9Ypal5E/X/MN8pPj+77CYxYxdTOKAITukF2WZP2I6YV4NU06p2RTAWVKq6Eh9tDLLYU6pPVas51Y7uFR8LvfyFp4WjHbaY1kGC+4s9gjD7bWePdnoYNEew/OKFald6mz+nIH/k2Q6VnYzicG+nZde6mL5e+p0DC3Rh8pwwPq2vjRUR3btQiRfgF5g2zK1QOIaZ+nk4WfnNck8tXSP4JmoCgGBTtQTzqtV8weNPdqjUqhIsA+mqsgO3/mQ0s8sgHJOdbLsOssNO0e2dGyQxxYFuWlxCiwZsY5d1pTrS0ueqS5dLWDFPbmEmv/YssbznzE3Sf6fT9CaRYTjTz2ANcicPuW46luDkyKmNdF5JZ0gxWBY9nNMPjHKA+GGKV1TWKao1Futodp+FGy7lFyuHHzXzQBX9OkeNO2cMkbSOhSvgcHbi8eXPQj60COSYfnmilWrud2dn/i4RJtJqr6n8BSwrVX4MZzzl/hswjUtBApAQoyV+HATwnwsU/5wL8Or8HK+dIENxCL/bJGNndmVQQAqw4xh77hZxPUdy/S1X3HCwrS+8blxXp1+/mxC8OeLUBONzwkxHKCLZokhZWXZ8+BVypS53yFwpMt1ylDXlQBO4/HH/PeAdkB1ISZ2L3MZhYb98VASRGOSgFhBPBE3FBxebqTMPMoB5Tv1+EMXulFECx8py2bltnzO3bNCgCBTRilAl26lvhKU1d/p9nsZMWS4QOAM4XgzqGs/cErKIGy3qn+8xoOjtnJpmRRkuPc5lz4bVcjlM2A8YVJEXI6jycrRgVEHcDsczzv/5ABn2Zad8p5oWF8NM6KhMWqvauVy4CPipSwtUJYH+0ysATRtOO3OPGqVd3BuqB2fRLyg72339uvMHXK7pwJ4QKgMMy3DesDdN25lbS3fixNzevHKbxjKY62rZF9w69oo+Fcwv6uWNdYrmMDxkU+dm5dxHeZJI7U2tPIurXT+7r9T54fy4SpEXP2hcelvKYo95xQIB18GaBwNdR84pCCYaNPviuopRRCH9Im1+Jb25BexKqnDVXbK1r9sYMnlXlPE1XYY3hvNwopMveSpxsxB8ZMTq0oAHFcUoWJD/EVV8UCA86fieLUzxXJhe/y5PPenb7APLDdSTbshTQQWiCAOU94lwIB+6Es4FpsVLmOUyNQhCjkNnWYQAhxcA+APocQXwwSQEOEuNc51lwc1ag3i0mYgSVLZdAsNb2yCqMyPihm1jNYXxxWtC8+5RYmYPvyDlh3cQtQDvOSK8RBp12doh8j0W6xrTNmOLi6B3gYzg0ZEgHMg3mb6ugno94Z2P45kv0isx6fNLv28q6AYNuSN+8oviPOdmTT1sTLYIsZ2MxNjGHz8EyfMT08MwA+j95ra9P20U7fRoRIS0DMqqAvm0EBkOrI/kpXdJAnrncL/SfK5+QPQfUN6lgSZFvzi1B/Bg9D/xpzXVj2NGRpfBYHV/XXvCNBtqH3bQRO8wNEVV/MCebSVO1IOYUJ0ImapD7AH38srWgCu0QFdyJWUz6PAmPdoW9NxaDfZ6DRuipxSzo4qFn1Mf6dI8UfeOFElQCp6QlxFZr+P21+mXyIytffACiiLG2yNdmCw7O2nZ/xrTVkFDiJ6tHJMqYQ0NUJYAubN5KuMayul2nU2tDoLLKGeI5myohBXd3zhxVJEdkPxccO/xShBeqyrYFrf8Ii4NnJJznMjL6ZB9aQTfLTaMKOh9ullcTpKYi2THvqTLpvg+NkAt3vwGaErS/UIo4PKDErrCseLX39he73AtrFA60OOyIou3MkMy3V8n7mTf0CoV3dsGCA/lSEfR0OUMukwWTzquy1zXH+KynRT9pr49yqcllJsHwLirywQrRJMMDTtv6OFIztRgYmIhMelu5fc/7TNVVcx20VIqtccC8JKIVJGrgNWdrcOYkIss2z1QxaRnGNXb+6UkjvWr1KKrSHo2Szy6rGokp2V5I23qvj02fyZB8F2LHqizYaJ7Aq1zdV6Mbcfb4Xbh3+1syras2j9DmvcYJ5ycz5qDZ1ZQEEPdjaCWQxH/0GmExwxjgLX3ePn/X3n/abQTD+22aKK25LUGGfpWF2XIbVyBxBGjW9q98tCFZlU9+MqV8c0fwdIp774rrl8x21Aru6EOtYK+cOpZTCaqkZsqMfaL5iwHAVorSeYBudA7NQFi/o7Mamp2JreSnM7EdkA1svTaH+WEMwNWtLFP+hxJbdp5WAsxwiwBp/QrobKvB5M5S+T0VYPs9tvPbQCbuvlgmXQHBdx3zGDlB02uGwDoO6ZblH09DRq0EHCvsOw91IrmRSUkMlt2GGxhoV8R/ahfpfE4GJ57SLHnvGiVtDLJdiFWjPZZYLDtN9gZTVbE6Vrfpm6dGGL7Y154mn0TE+qs7GJs5galRl33m3+SUAZoODy5rhiybnmeezecqgvaHphIO34a62N3F5oqw/9ncuvvAhbb7bVkO+RK7Re67KrPktzBBeLuf229eHTyqujQOA0rcupUgpWfM4Jj2r+6CGNCu/lJC6Mqtat/amLq6VsVlYkZTsg3lbTlc36f2wjLor/Jlrb6gaq+xnrVxtdkyDCL1QM8QmE7GX6hG7eFXqlFBrUi3NFIo36VBI/jwGVIOsEvACCLrAW5wm6GmEC6um0dL90RQpkmoI6A0fQh9pB36UvEBVgO/CXv8tL4NQkZPEwyTOwyc9QVIHo4Bg7e/DiaCqYPgzc67UoK/EGjwVG9eEvGPxRotdMqgLt/dgVxY25w+rNzy/C8v9os+rYMDUHyzWzbKXOuPjC+1rGRH5hv6DRkceUEjxIUA7Zo3jdDLO/GkQcshM+nGxb6vHIkEyfJ4dyQczVZyNDdTNERxQ9/sA4smwGLAGeGsBPEwE8jznKb+Czy0W7DZjAh9TzL2u7s713uZPhEuClYxeFunEN51KxmzaaV8u9c0UZule1JAAAYG69J6koifp3gTvqT2dQcZSGTedAKM+TabM4rOcye5hL+cVIl+euLMDaSQGODpjvj/jWFiLBM0kC2HGy5B9apV70ox8bVl/oOyW3ncYfK9IMhkdfxh7H9l6iAy+Pl+xL66RideWXl/qWdWXP+e5qG1+/OVT4HrkwXTG57uJ+1jqjWRlnP2sF5iAhfsApMnD3dAevHQai8mCW0zoC6KpOqZRj347R6TFM4L93YR9uCrVSPUh1QOHMYKo79ta6lEctL5uQ3CxIUapx0CsxOtoDXVQqC4+XEYWJpiZVX37tAApRlPfGZb6b+y3ne7QexviQOTL8I/v1m5TJ5uKyNghKD7jLwX6uwxqNb0gfDSyxPLoi9LvbuRkMIaXhdUhdJMd4CbaMDlPxfrIy0zFJzChwfNJEoLek4mZ1M+4xRBhh8WIDu0gRgNeFT2Xr1kcuea+BpvUsBAswVukFx9yMC0bBrYaFlu2WRcWZ+ENPme3D4tftNFs3OWHw/HGji7pikY7bXLNITfIZS2w6viN42NnLwB12y2H5g6eLZfB/lddeYs0elhZJgc3vycnMZFKlFgO1H1PCFs5vmoXXqseC8bvtnzIPeTYbXeL8d3TKuX05t0fdLC2kWRbsir4mOnNHN9PrC7kSO3LLyG7/UrOb6jSGBmsgk9BczEBLpO4ZL9oFfhVEcVXkEi5gG6UVPNJ0c1GUucndPGNC3+Ck3Tb9Ui3Y0QipiFAdpCJc6bxX+XM8nw0LuYE14rOxRq7lgljdKTXRTWQuTiFLpGvPX02RJOVVPSVGip39aAPx9zel1X1SCDNl2jOs5MQbwGDKvp2oRRUm+CLIbiw1LgwDp4YmOdpWDSzXtRJL9uUGAJIVdHlw1qeVPee6VpT3YeM6IiC+tyPtmoTRsmRlM/jDNrH0jphFyBRWltv/w1ElgGCDsQ5Q+X+OOTNJOCXknHIpnnPax2m1YSxmJRMMVDVmIx6Dxmjfb6HMyFktjWoH81JcVnxmmQLjh+6WD31Bi+YOdxIHzRiFHUL0UqjWsgCSC9klHrxzAUrMqqm+uD4U3rR+qEReMVggi8JRUXdwr3XM69hJzi89Hc1NPxVaBzr910K5OUeOSuWTV3E6xA+5A1J9obfGXg2Axufi5ZJdLjgvEe/PZaauV6qi59X0eYvv3xnzf5zjpW614c69jTcdKx8b5RC7ZQAO3fBhjDvpsutNRsNsE8i71JD3c76s4dlXTA2csrfz0pU0k1rfn2BTEN8bdrGjWjH7wBINzZ0ujx5zyFUVTfuMWKapyfQm5e/LcHbfl1+7ITGs/74ohpC4I1mBIOK/1A+swHHzk7BtfCmIU4NGYY5jjqC/hYBlPSqCBPW4aCLU8UesB9EqhaL6i2k0iZszYfrEeTV27fmpdkIuneHl/qo/3sCJIOtTtHAT1pe3NE2bKw+XtFiD0lWavoLH8uqYo1+UdzDRhgEROUrfnsUkbxbmYOG+l7dWkcDQoqHInph6Zm0axcZMuajUAz24r2rhgSVnGSOtkuyQJr2v6IMrbhTlYSi3TL9+GCxrTPTBC3zUNhMksveKw+0OhskPCHhg9NOcEgjmvM/lk6TQBXaDuKsLWFWyqX47Rtb+IyM6HYunTPhPt12mizBcNahparGkyd7nb4ftmpSP4ZjTkGXCmvo5iMVUskh0eT95ScGYzms1kPmIOxZpyLFf1amGFApvhEL8kFTlvUx+0w6OpLvOIRC04A/KyV8Njy05sDMfLiPk/DB0QU5adePnT+M2uVhJif41c2mB2sFZFRsK3bnRjXDSuXrejvCPkncTQtC+o1go0nWC276Z2w3W3AKsoc/PzNzhqkDsZ4eMVJy/4TKtJu0CZFgDRIpNPBWj3iun26XVeAFoyYHNoOuNlLAT0RLE3hNRq6w197W8msNLhkb+YuJJD8gk+wWO3Ms0ybSgiP4beJaJnMUt4SSJ7d/zbMLyKdvChl4qq00gwdk3nDi+qGyVy09EcrlnS90/lUHmstA/fZWOt8I3xs5s2SvhxytDEvVo9o9qAPCNcQsoKn3uJqEN5Slqsd09DP70/MmQdiIOV2bqUQm+udAM1Or5alp6NwczRDa16QwMYQTRT1ZNQ79xOsezjn9N9VYKW0s6Ay1NxzjXAJ7y4bUTZT9H7Tj3O9je5tC/gHd52vpBrOCTRtg+QGzNwANivZ4aW117Zj/vfITBi5rZ8KH8Q1uqT3sQl96R17W7KBef7Sv9/zJQ77qruJljRviGKzrAzJdf7lh9VOWiPKZ7BcS0g2Rz0ZS4nFTP3wB6Yw0deiar+YaxuzXb+o+R+3myCpK8KIUcPq03hs7rSu3M5wx49viNuAKLzZqbHQlPlNa1PEu+S/13TQDzrmDsXZQ0Ko2CaEjnnaTMnnApDUhMBymsjkX8mFrndt16Y4sJjyZhlm/DxbuESnrT3QRNMRdHqoBoNXfbzM+VzPjmeydKvO7DwqVITucPsE0N5IbIH0WHeNgv1LNICibB7pLNwMRQ6IVNxcvsvrHRw0lMK1v5pUUfJ/Bap4Kp6MxWGPNjiUyKXZY2dyyvom4jbWNjEHWPMdA5QMXHyMiW+cVIdpZ0Mud+//lzmVgyndjl0GGMDIhGz34HKhB4uteZmrpSf4Zcb+8C0+HlRRARBgvhVQdY+NhNwqM7KOM1D5nVJxdX3xIzZqCV7ZZafsfOn1LC7YVg2NQiQviadSkLZPU3fH9lJ1VJ/djxeP+1OJhs5SDRA3hDVK8eJLJD7hOhmUykcaZk2ikCb5h68xDPIkXon78sobJYLt34gh3zA7z66rAIbIY1dt9Q1x17W+22fPmCNYe9i2IUnvnqcyaNwMhOlQ5+5rgxrfAdXyDue4xasmvBqdxqvP93YyO5WmLICkssGWVFm/9hKnqDLaaV5upsk3DDTWjpVyh0P8tSzZjXnmFTp5b6z77XRcW20lWXGF8LHdbGGLFIcXmdpU5YhDfWjKobKhoUHApD5yep1Krz4eByW1yhGVIeMb7RlhAplQTE49oINo8ie6n/lXibBlgOeJxn03mTuMYMCoZnfYig2NEoT2tuPHQ/p6/Lc1baVVlXrQ4oqM1Kh1rjlRZW/gsAusqmTgYz2tLEFFsg4aU8aWzBZYPm2GiaYaKujn0LG2aRDfiZRYJ6ONKi8j7cGA7hWF/AqVIjjYfhyiqYde9/WLNyzQ3KDjUZpQik1LNd6gGTSl2Gyoc4EMhSeUePzBg7Kd/jgckVY7OIR0DISoNB3Jj6UTc0NzTctnvLvBb4py2cD99imXkg5j2T9LLmabYD5fhhq4x+IG4dlZGUrvA/64UpkLldL/d76PJ8oEAeQ6jS2oJ0/QaeutbUDSNmVwSAExkRCgZqTz8ac6SF7FHGSYkrG5HH58iQXb8lR+SnT2G7hvZfhSdW4K2RE2VIzJw80fug3nxfiDPnxXjakfKpnbhaeENDNA44aX8ZLnvYpaRMFcM/lPULxI0odcTSboNjgNkbQO9ZtzCRDZauLng61oB8Y9lwkKVo5cbQR4I3s0sKF367bLdN4FwJmDglz3JD0CqIBHCC+oXe78lsXVw81Zr1wtq1LhJZoszYV5k3aM+bJxRzc0E1rdnPi7LqW/nZS8TQDkAGMuEAA4IRL8T/n5E+BoWp+giYzv74xAufNAuYFkLAsAAvAFnl1jazmyDR+JmG0hXByxyM1vN5+3skJLzfYCHWsuT/XDnV5aTsXU+WL3ZfG/kOzPcWKL6O5FzucK5Bazih8AaL2BMEPjlz8CNty4hSG6GZZY1M1UAswjT4mm3kYluJajGtuv4/bgp3wdALc06uu8pJrvo6VUzmS4vCj99enQbNukO8EqVZStdJKmCcOenOAqdCHtZMh0vfvupQnXk+AaUUMsekRnFmT61zbLOmxhMC8TVE46awREkxL5DMa/V5+qZGPNTigNpZGu3LD8vB54+GO7geunod66htu8V0qnexpT8/SAHkrdnqd63u6g8mOcdyix7tLfVDwSpluhzFO1vNzQrT9gg1eDrb6M6b0Ld8WyuHUm4dPvDdiU7TI4FnFzM7vDvtoQQUyvu/PbLgH0yCpwRXQdEnIlNg0otETInZMNEjrO5a9f+sR3DRnPrC8CKiuJW3DCF7kn3hLxPi5m5ATQmxpRVjhrV2C7UOmh+QdkydwDpHwa8fqgeaKMG3w516/eo+UvjIKT1qVAt/uuDfw/Y4vlt8cpz+3pgIqJtwvnYsdzhXL5ei1ePQZYHzZli9HtcYJSuMBxe++QmpmIdU5oJo9X7huwFTowHdlaZ0tSIWZp5so8ucN7VIpm5H8Zrjv7mRI6/t3/fkB1+XR2dIuvA5T0HbawjEYbOhF7oPad5SdfVW/6p38uFn+Cv2CjUlV3nhX+v1vdvX/h4ZvCY/4jV0rnCRhukka7fWwbDPvv07ln6TSeFrb7pX9xn+k08wBdoRJFVMCn3Boty36Qg5FHAEMnttiSw8HJfXaCmK8YRyG6fdPWLuWKKfdShDD4Olt5ZcZrpRZGOXJP8o3tfGnXS8obje++piMGJ76odfZfellzzrnWVN2cZTUFOfizCvGuVAnpi+lEPWw2C7WZOIsCkB8N1aFCw/OFXcZHTkqZN7erDv8lpV7QSaN8mnQZ6uhI7NecG/YJwBIavlxZU/DTfjquugaeDZVCiYgqa6+UWgzdNFOXP0DR+YExhPYn7jpBOqJm37g+HgCqxO3/sCF6gTenKCtXF8d9VieLqTKxi1nOGZVTzjakukTY2fmVstlt6S6MIjrf2uLy4eDQBragzFOb2IGdHJMqk9aSX2DtR1ykBxt942pJ/yltWNfTzVwSUt63Id0lBvC8iHLthzlXS49H/d9Rb8Yk4rT1L/OxJs7+i8rxRdrMBpIokGdjGLNfuH8sPV565El68yNUW8IsNaZjgckWFwci3S4X3g6tuiQCyJRcnokZtw/11vsrhYIyz3SC580EBMowp1LEpS/wV63R50qkJQc0cnsRyLEKZcp4DnoRJiyvMcszW9ljtjm1TnuCWtvS4GV0rB9N8j/CARBLCIVhEUjxqY7N+TbAyfU9WijeG35JmNEsVDem98r+m+RphZeQZH4/LtFpdNJPfNK7xnUHZWsH3ZtIo73YJoxRCykhw7vakes3gtm28V8xhTjQsyYjcQPR9BsBIuEUpkSxCz1KG5siH3eafIQQJWjPuVsDBLZa6yWVUNzXvKoMXW6Ui7ofiZjL1yl4QPF6EdrUOVYvW8KzrusCiNfRvCsOzMIBNatGCPhXD0j2duRLU0iiPK1YdFgg8iJgcbtXWUtLuyaFlGRXF4SjVD+kQLyY+ZHJM6FlV4wGnkB2aBSGLT9EMIknZqhAOLCcuvZQL9WYC9wHeiLRopIUlhvsSb9bg8zzXzVq60cj6cBsxzZxhzC04M090Wv2G2R3UJ5hLVlOO+KfNcoi8/jo7WxAsTjy+/Zsc8IF+NsWazQ2itx5QhKe77DcnGw4aoBk7LJtapHJ5FlRBGZ6viOJXPyeaX0yE9ihNwEjQSwQOBNEZTmP1TyiRaiM2NieSkFO6AKZWFLTokQg/0ZKHiAt+wjiBoC/J1YQ1iww7rp0vdk7OsH5GBgGRT6hpDfw+XT497cVfuMgthuiGWTvlrfKJb5n4qhC88QgcnovN9q1jVlxN7alqnl8RMFYFOQ044K8kX9lTeqMZZoX8N6iEeGd2mTlod/KI3zoCrtzli3f7xXyHVAsh2MLa1XQ9EElVv5t8+T1cRKUucaebPva4tpErCFfBalAYTbQRlEnu4vrNkGhcWgfbVXcBLk9rhtnsBzKWvLXjltc3jMfXK12ZG5CEBNwWB7ojTAcKZZ2sC5IuMOimQuWYEDYtU1TVO+qp/BRIDia2PvBKlp4cN8xkoQhCDIWT8AMaG8on9MvOJ658w59riUkyAdgzBswwgWxeOTUvHPshUQMLewP+R6bkfogOfokMnjCF2uGVzrFzJ0cLySKuhM1eyFxfzcuhL8MvT2+zn/H/+/nEMGuD+cPzym786afFA9u5J4X8aE5dwz5RpGMww6wldIR3JTHibQckDcUn9MKaqA0VXJfy3urJNX+BFWiFH9QDes1AVQehPcB05WOfaNZ/l4jige7yzT2V/aAqjF7bGHGH8EizDhNE4zFffcIhlyCvmnMXw79Bs4xtBsPrNevDwT5Ss3+S8RgV6t0Ec5C9oQdew1vhUGY5z2CsuuwMOFs+T3ixL+/a005gs+rmQQi8MgA14swF9o6JGJbAQRGga4l6GIBu7ChExfs5ivXmSdlUesnkq2VR86amRTJwF97J20QlDzsVFixJ9xY0JVZKHqCdlO4VCBYERG0voLqbnMmbdUAqo4dXYcWhMwoKeFyNjtmsP421RkxMoi+U3JzgfqvTp8Aw1OcB5gips5Uwixr+betsHlPA1kPQ8PfAAK4s6BHLz4/iuZiLuGfraNfqQxcjNDWb/EkqKmkFvJpjX5KLcyQKULIGqIk4uXQPnEGrIEJ5gPde7hZk3ysZFUzL/R8mNW4xEGGNyhn+9YWITCphQWUINacT9FgZlQVLmAynm1zJYsOklRw7AOFvdi+Vfnb3aVhxqEhvsB8BsuQHINFdXEAnrmpgiLtWq8gGbcCd1HXsOQp4TSJorQ82gixIhKlepGV/xSzHQK7k3HGEPybOrhWTkmBbQ4QyfBL4EWKrKljSftyt8B2oWuoLpYOgqCrfmjdTeoBlnpzPU/V23QC0JumGgoA4DgtrK4tnGvIU4q8ixHQO7ZSbFPTghsVbDrFOuHDf+O9M+3IJtQPFIaS6vSneUFUWKzi/I2aVrzGGxSbzVWYKXJefo3tQZmW5fhdHOvAWXuhb0UOEPmnwWzaS3z1KszWcuaLgmHtdlYiVRPRwSsDFQgkhvVQ4nI6vY+1TtlEnnylIeHKF1OsUZcK4jW77kdA3QqzsaAmGWrLmsBoyIrCbsTzH5r6QMLz6Twh3PPTqIuKxe8I+YBe78huGCYvxVN7Dg2JjOPLtpVKMNgYI03B9PRObPMkoWbOrurZ0i5CbZM9ChfqhXNQ6FXY8NOWH4pxeKK+iJkKb/2lR7o5/b40xV6c+TjQEi7yMObkivGJoprjEqpVMlARFRwZ3P8yasCCe4YYmOeOhidNYZJjfqgkBqpyn8EoXKbM2HJ32BLUYDwqi3DMhA845aGZOFYcjaDb0UkrRYoRgGYhZPjsCasJ2y1Z8Ga5CWPsXhqOIpNbhN9hoOGWMEzEh/xHm+UOxf7zsxkRppU49eNP0zkNkHylAFFMQorQrOiLD2lGHSIvZG6ZSLqakwJ1F9wkukI/0hk4wVVxDjMNpNMi5QByxeqm5+B4wICGnFQeX4kHUaLHkU2naBm/m7IL5qRihgUAUsMZIYlEshgWDIKpAeuBMJW6DiipcbAF1ECKwYzpfmEsJs53+IINIP5n+Tki/w7FHHVtQ1DRezTKeXIZnLoPQX1597JcSKh+dfcPUi8V0vm76hpcFyktoY9TbAlAMpLp+T4zhVmJ8fEC+eQo0835rUDLs6Qw1/XMJl04+QAZDwqocsUE1baL2/GWZoYavxTNsCIighuPVxp3jcJ0hP5Zc0DMl5ABozCU81Sli6XkXN11ZLXbugRRVdDpiDIuW9zjxWH6/zQ3Qg2LLWUlHkEfkfwOJZpmIGtrYHbBdviU7m61u7Th30DRy//9i0+/dhA1DVtR2zbKT+tC54+6QYeDttUlOt48yq0Ftb24DGwawjdl32jxbPiyT4/B44G9jpmA65JtLXQsa8OvO3UqU+w/adR6EgF2gNFGCfk7qPcQcRHTNS7rsBlb7m2nTn61n3j+jmK5eDJKtHrWs4xHYMDZzhY1NZTksaU+kWwEOq9JCdpVX+K/ddc6NZ/myesRTjlG//p+28eZzMQoOWdP1MQswEHq7uMgY0w0rsQNROIqnkwziA9bwvhoCeOCK15xLm1h+uR4GWNugxE/PSGQ4jW9spE/HALnpT5/FCT9/ckACHh7AbS9wl8XH2FASMDzxEp+4bw/mZQq6sFDGKCXR0/Fe8XHINQFJ6jCoOlAO3VWAGwktJjItO0QHgzVZx1bhPbUp//NeKhtmskcbbCMUiZohaBibWugrNh1j3mJ2Y8LZ3KtOLLsu0hnuKoJiXNetuU2gN0GgnMZiuUOYR5uti2Qb+7ITM3Fs+GvCsSLOxmGwdjBcBBc5YoqNG0pHjNOjxBdsCQx2U/15Qer1+jGu54bcGuivZFBgtKEO81MMYu4vmvdKmqLmdsuxrHY/cUOntiw+yaV+OCHeVzxGOchhKoWHY1bEaxA0Yz1v3a95Al+BXuzQcBE6R1Xf8IG0XH/Kc+ol0arpGsvCWGgx20E0bupKPIft7CYUC8+IulKBkgbI/lDw/0DKW7pHPOc3eqdgDIDNw35C99i5FhNmMtHzWYwbNZExlCPB10d3MSn5/ba4Irm1aMx/bdK+Sst9uDMpCNFsbF1Jg/h+RjpfyonmFiQ21TUkom+0VxCoHI/KICCHr/sOCepOROJWYOwYDrbYnZvt43rNTcMzt89CidXb7eHhnGl2kqx/8dqtczbwCFauW6NRWOS29cBwR/6n58KPwjmC39WdhFzySKvKeDbF6WfpS/F/hePQE5TKJRN2M7zdj6mvx0F08eFCM7d4ZNI4jNsBsVTniNIfwvB+bTq21DiOIkUQi9/wZwKNZnXl2JCKBW4h/VO53ghh4WB/3NQRmX+SaeFQ6DTlZPimEwFPHx7/vKh+JY9CdjKeOWp17ngC6Dw/STBKGaRfbjvZpQUo7QEpzo43h7AvziZpy36F8PYLOdvSjE8chpe9C1ACFGODhrei/mYphb3bE6G1X6GP2hJy1U4eLMC47OZlnsAGkL0hPoDLH4flceO4XWrafVN1mIJSY3qUJy/S2q+GxP/U3MpQQprn9ReAuABPQ/4oZs53GePOfOTZ4vdSdh8fajuVWBEkD5NtxrFejjy/RmrAFNoI61gv+RMZXMu1a1Efq8ejDSZ597RYm2+9XLKASAjpvNwop7i5saMuP7nzfMsSbaiBTMcJ1OaEcIuQEUQSJiUfSatyUbu6xIs5elEj5bGR20tEiMPU6GiIJTP2HS9MGYnBA6sZ26tbNnMLuKAtTNZg4kznoclt4naiePyRNWUZHNPAjxN6STzJOlzFkePChGo+77LcBHeU33vaWrpVMxefAKnzD9Iuey4xJ5XQY1fIbNUsiy+O2rdsVgDY9EbbF2zM+2hWgmqutBZoa+RLbwgDPPullGUgi6q5BMFsfqHTm9anEzUWhbZu8giODqVlnwseqRY+4dWMfDvmxL6XzrmRWyBvII1dED/behiyK8EIrFeJ7QtRz+2+6B9LdZsRyitHLtsdYNZkInXzYL8w8BKTH/Zk2UwITWBSTpitKrIZVMYmiOXqvxjcxC6V3+9akMUyuQsvr7Bss/60UiEvr67ZkUMUPsRdX8K8ztrcuDLAhdgByzWQPwDHNQ6U1PioER8pC2Pk6yNabSAPETIq7vVBJV4vgbZvfitCk88Fi6rqLudI+WYOZF9PNsNJIteQ4PdyeVUPe/UY9uG/alJVl0P9A1gyrRt+LNbDCEkNT8fXp0lHlYMXf0cJ03a3NiSjj8uX/xd9GVZbx3NDVbsoyT2gQufp2ZTbRl9VY8EYlVwxqX3/JjG2pWNYfnPjfgPFW3p37lS5Z/P6dJ9yBjH9/y3b4cBUidYZXd03ZC1M8r6f2G2cgy1N7oLPSS7H0+DrFh+rFAJnOl9kaPXgcLU3KOThc0uv6LfhGIDqcsA0tw3PBcRhQ85FlY3Zt91uEImgS4c3lVcvI/9B9Y5HNlUiuU0+i3hFdqpicZaBMRDjtcPIScNe2X3ZIyOTe4xV4D810wl+a7F4x4r0eAdUV+aCpNi1SIeHdjuwNKe7p9zbGJuT0qChVY01TsydZSMM4zz5Cd0ia/JkPImlIx4aHYwn1UJElr23P7khipbC92I3vuQ5KyeZTj2JTZ15GreJA9fDl4mD1zphyyonz435Fudj81sjiu/EDn5YWncZmfLMVatE84g4MwD3Yi2Da16yla8f5ouUPCIqMmbo3Xe2YR3vyz/RNPh1OTz/tAfKJ57KuEQYX/i83JpczSNfpjDv8nQIsxD5Q3oqnIQnpzogfoSMtjGhN0PhPtI+BH82sGRHbC6bnAkPKYPR/4se+9awFj/iquxZFY0SEKxxLvkbImtFwRcPM4I9+kr4jqIb95xgKrfw3S1OYFUZtaKHzQ1E88VGAbifcL+ldwr8NimFY9zsNkYxcQB2NrztgkqVhwvhS9NhCJ83cJBg3kRBJBwZwbgtnHzDandSSSltgbRrakdFQGwjxwCF9YZRGlCBo7z5zADDyqa7e4XNlRmLtP1V10gygEkr2+al8JKri4cpPFYjcbDXzCWG/VPx/ey6q6VDvbVyuOP7+9EJjwvOIYnc5CV3i+8tLxuiPew6P67Z7AgQBU4Fmc/8sSPwPkm0AYYeLM0IkQQrYIMXMFmB7bmC42ce22KY8vd/k95mHi2VWmXLSq09ZgVwlhMdj5p5mamNi0cYRCX12vg+OvyyErZ27xDWZI0pQjqS/pcXyvhTlwZabbMHIS2Hvtv3XQKc2x+risMuuYdotssqE/+H2Zx3LHqxEmOVs1siFhdeWV34ZwYww16uh4F2TS1YV3xKoa2Ed/OIWw1/njK0MBwTQWbSMt1Ncvl0K5yi7mZutRoXBLApemwl6u2q7L/1rQPzd9QCphPYjJb/wTn76qIlqQymRarPY64MoC2rsSrwHN4OGN/W9hDoBUZrYs0HWQBspyhn8tLuPbTr1KgEpcPMyBw0ss8Uyz+WMsOvGqN8Sq9XUH+Uv4r6b+LKO3LaqQ59bxAYRrTLdEHd0AKngbz3FM13iWozhcojPCsD0XBdVYNiUTuUbDWrTZoLfP6Oek70UxsJBc/iaax5HY4vK10GcT/Yn9ulW8EIdrIj5z099iZXBZvhO3yD2GMpsemJ3qjBDwkB2gNytHoz1p2gnYdOL2g+FH0e0kdohndbUw7HH9PH3tABIxxBfC7zXK+3S4Aj97OTUorKOzCJbRpUvdLpqfaRmohN/ZFTMwG1lifVnHC4EPJWFxsArVffTChiFLDX9jv24V3VzC4QpzHsyi3r6zMnoWTEsX8TYld2n7lc0mNu8QcXa8Erf/jaK+u7q4XWCtev/c9PzkrOnqRqrbvEWXjwArfberyvudIMuItpwcLKJt5AFXDdAiy/aZn1pLmNXbh17cpmXqWBKjbPXSFBhyzOXK+o/gAJtjdgTCQKNQnFmg+NLtlD98gmdOBgd9Qq0GTiZAOynh9OPod2/Gu8k29SoEyt6e4QfnKit9WiKnidG+eHH1waFGNu1SWropgmVSr81r6bQsiEj7iWkxAPEU7pHq6PUfUAX6APgvwK0pVNNbTIo75ItjbufrkuZbaHAFjZe/extRiLkBfihBHkWh0wZiLME1ojDkBixyCX4lCpfaAE0F3IuBBS0ErUA6MdBZC3VRAYZUE2/jBgUIAAIAe+tTXnHuERbzra1Z+F+8KlSDz6OgIl88Lfr+7i6x70CVqC4JFrmvLpcO6StBcg+6W8FHYwJM/Rh5lbH0Ob95GCoPFVHGTWfO3vidxtlMdK2LAPshGI4L5Y2zg6AXJdVxTza071XblZMTQl36mXTdAxrB7ln60IvFfgNnSc7azCadQ3WPHVj9apqsdswIARI9UoIYNA/uMO905sexlwVjThb8gxPxYTGL83LKA/O/Msy4OpgEJjRdMLfFxKYHcK52n3Pm6kWfDJB/B4b8iTGHBQrFNl/mReSj7kY8D+IlBGPibiPK8cemLaQDMK07rUJ5f7hO4XVQ9poj2Lw3nMZ0ChsCH+L8kmoN+pGUVP5Rri+1yfbC7eNDZ7pnjlO+CDvLOZM/DbQ2m2DGd5dEb+EC8NlkI64DtUF7GKHpKos2EOAwdr816th2CX9JJB+toO7DJ6eWR6oKbCzZEy2Ke4aobc7+iSFFot+wHXUhITu95OoI+VM7SjPWAg/GEpKzY2BNqHBt2L2cVKfPFPqiZTIMJ9LGf21aRhr8AQC37TGgVpAHRMlw1AIoz0W77FrSGozHiR2hApT5JG3mfnaNqwP+ad67UidVJ6S3DTIbJyv4o+wYfww92naMFF9CVOGEzjYInPoyDkcSksL8qpqHS0haYv4xKixl+Ay47d+QODoSyVcOq7L2zqF7C0wVsZ2baeMgkN6fxZ1XZ67vjKJYGJln/40vBrYn/HRqb8mPMlxtd0lrpejlOasYESobHofYfcufw8jjW+4gSzStBEdRFq6iIJca894w2pFich14Pg/y3vJ0cmYrrHyTHDuNZYjo6IeUYyMLSM7OMzudV9neAXgxO4SNWXUuzi/sZYqA9VXPOEkSlkMSqEhH1iHxy0LnUb3a7lL4HMK3HnXSNhVVJBSV8a3lJbsHoPdnn2cuO1+2hvRsNz0svDmxBCvIPInwdVjS82YBVt6L+D2NUq+b7fdufLp/DTRRo3mpS7CGKG88vPtc5OUmnNJRExtGgEXuosZc9LGq2ckdQrabxQqC/ullG9IjfT6HQ87IKiJ5LlWPiZrsY9bMrXQ2P3e0lW2mwv4Ti8DCmUUVP3wYsRk2cHRd4rK0SyNF0mIvhFcvC1oV8z7j8QTAe83JNfLcAXRaknPpAVV3Pxq1V0Mv9e9mQBMe01XnbUmydwlVqGSlGae0T9aAYOoPKR0fnXFlcJ4iO8vwDjBtMENE8UeeqLIUbbFIjy/LoHlc69kN3oqEKSVqVJx4xw/K1StPNO9xLTulCxX3CIkSHYX4x6IbNCAIaiui6qJx8CdVmagB2GD/784R7dl3zVCtPyCy/uQc0Tw5Ynjy+PfGGB09MKWHXp/X6SBbLSkcTkLacuTOW+srgCO9tm9+XqIpMVrQm+kghmEYxvGFXErUzux1PvignTXCIxViqEQRaYXX6MVT1n86DSla9aJ0t7v6GzjulLYMwyejw4+J60yws8FJW1Y7OXB1zmuZHgltv7BdbBjkOwnjeO+wAN8DFCajRFYzoXnCdGIvTtIGZqJvQs+57PuzNQ5hFG/fI7fB2obkYG7w7M+UoVXjXkwBQxGyb0VEdMuJCav6F6f5n+A8Hj3M8TjibNvwMq6u2h2c5ZfOO6vhp9zw3fDnO+qWRmcGmUMgTGb13xzy+mjK8p3FoLCgJ41uiAaN7SJzzshUeHA/1dQYebAvan4rok8cqyqJVfX7++h/sKrN/x76JZuh/A0ESjvxhA1onBJcILkmWQ9A7m3fAHkkTUxd5noIswGafj301NIp880YzLc9UkmAeWx2a1wgn3N4IxaaF4YiPbnX6xvttoTd8iKXMNe4Hu4Bs1MLa1xFZzLrSshm46V1Aqs+/wN3ODbLrmjRqYru6gxdZsZcOwobj2+TVHkDG5kZTFVPVvmROQPhER/3qeYC9YPLwTJZayEIl6m0umub9teKP7ERStvAVtXQC+IBSE8Mb0a+j0uyXRV6lWhDH+JGqb7PpUTijp4HUCdLReVTSHqG0vQWj7U1KoCuvw9arzDxsfLlu554gALy+aZ0Gpw/FzIDejFuA+gzrSEPeUhXLNS/14qieROupPkPbUub7qs3mEt6fW9ZO67omp9+yD0gEIkQgz9UyHnVjHqU3ql2tpB+thFEFBHq2FwLCe8Q680DZk08QSKfrBvENyLPLFP7pYTsFIneTB6oUwxpwGy+QnePGXUfxm4VZtJH30isZpC0q6NF/0yV6K39MgT9+1W9+bnAcDxnhMgfed86U9bLHe2lQruora1gPKO2w5N5P3/PEuCwzb3k1jzxaWFqsUbMgBCdLq1NE6+tj9l8bnPPeUicR924Wsu76618fKZJ2jr+H55vmjAAKiMmBgy3b/IxdSZV0FK8/eCcQreFkP2Dz+2WqkCLlyX9thB6GJJO11xMqZqrL45MCwZ3nQfyFjo++LcnszXrxmu1aH+Ovs9zpdC4sgskshXSUnkMOeeb7OWp0EgCi+9abhLomSHGoD54ogoj9kNUQ6YCrAlJSMG7cn2IH1WTcGsPJrTCzAhAaorUDqZbvyqw2AdENZxJIlCDE0bvQDbeTsA2Uw81cuw9l9CPGjKnhr3+vx6J85lCOV9FJeeAJkDjxWwVYnMKVUHWQGWaPfUpF5enedxC5VboScKlQf027a446gO1ti/5PTlJ+uHsGz8nGsufADS5mfcBvutS4FgF8PJ8p7Cb8EMG0r1SrpprOiLDPS1/KJpJ0XxGn5wTzLOrd28mjFIpb/Ftw8bWD/Q8n2EYhuHL7WnEC1YcPspxiTm/hXPq/TkO6Zg/k1WCyH8iLHANq/9vl6czcXzQloQDI7WHjAstH2EaTU8xzQ7KgkO5Xm1f+aucUp6rTv6GMmGJg/uofELkUzr0ent7+IyG9LMVWLiBhH6tSd0FdwjeX2DdzGaD3rCxoFoITBzQoWrrxh0gDP4+20oI7/MvHGAOwrZwwX4uoe1xzzoYKg0Zzg6bOO7Tu04f1DzszHSkVeI94YaL96Cj0LDWHfr1knGFf7DCXdUqe/NiwKio+QuPUx1eM+lMUiWYTlnnSWHjX+WE5ZSbTaYJQoPSBuTGl+uxhWbfVy9M5Pi+7LODa88UVKc4c5p5FIfbdx+HqsCHH5WVQk1NVINot5z1V0RdcaJDuDI/tR4AxEOpiGNgWol0u9ZcqJEnXx5U7NsYobpR7Z2vR9/fo4VcIC9JwpLAdbDXK1cyOIPvo0QWLujFxfT7P3NacEcUUIzgPt2OPQllloj4ACbSESPhIdUXXpkyM6vYWTHu2GOrVgxGS4ZNvYoj17vfHh1AnXNfa9jLzNfichKQZDpcaQcZyQVTyxFiVlVUweV104nNhIeYDR9GTk4+3YHZhQrdmenN0N5fOHzm6IQz47Fb/yIWIriUoBb5sN5xl+DnLjppXxEviYzS9EiqIeI9l4AhHvjGIhrCO56gew8BRvrj9kRlFLNI+6j0dI3tPtYNCp/uFLyKLyX7kXVgAudbTb0oW7H8K1yJjpRkKLHQAevABcL6k0gWZAMuOpbmjdIbHyxqD6q/KldrYHPSEPweR7qXvDp70jm8CoijK7ZbhXD4v8ZsjuoSurUZN4TydxQEldaM/QNjYkxQ9K9pm57YsjLH2CT3hF9mCunn3j4vio7lqsOBH6WVli7R0fvrAmHeHQboaSszBLHAoMmVE3a8ITmZ/994N5v5CYKhTDUVBxxJUcW94pWxDTlS1WF7+RfT+LkPaiysrjltdqwa99G62y+u+k1A7Z1v9bMFl6NGiIV8w0EQ6Xd3/zGo83J0y1Gze4hrKk+Zy+kuT/L6dcAfYF2zPZS8mLqWbiwiBlN97CW/+idgYF4T31K/tqq3UpUAeGINscRH7fqlvC7lf+oZuEsgOYYuI74Es58m/cfAY044DQFLrsNUlVUmWKEypZO8rCEpK1L06WAJBir3ogl9Y8Dcfh0l0WqbuH3gD06yBrK0dSSXZjMwId12PN+8vZb7fR8OT8sob5eAUy2yc2c7lsqKm/itqRxDeBUE8CRiQau7hoIMYsl2utasciSJDpJt4ayKlN09YIU4pCsQ7nz+/M8V8nCAP2lo9QjDsSPZRTkrMoaa8GpyJCCbd1Yl/FT26lbsRtbRLB43kPS95G4YhmHYMSU/rKMadbuI+3TVeEQ027j5yqTfOvsi3DKrkvw66E1Wm5PhAdPhm0kKFiY7EB72EpH2z0DrGhMTJ4G7OnxHB+i5kWC9WrDXF9Be7jNYEiWQ+UVKnpNVWJ1A649ZcE+DUGKFEyCrwcZJR1KKmTCozFnof9me5t151UUXxd6obxFvpW87OP0PDWI4+ZgGLOAoIwa+GLvZtE2qGPdRt8XS8bQYAg1Xu6coUONNtG35gbhMNBIVE7FgJtNpF9BkTAN5YcZLReuZLhroeL50JIdGcHh6+EWR2TGD5LHf0z+52vQuVMFQUVKzepGpTX1HGcxXShEEblhASLDrxdMaOmK9POKBRSEzR/ZlnyYkNTtoRGSA5dv+lkUBIEg8z57qs4gEKr7/FOXdSvN/LQafiFO6iykskDnY/aHn7sk1vzSU+VHlcsTG+j5u62schtRRkkOeA9I2VY/sRLYHioypqZKDpQmMeB75fWhe3zFPIe183sRgnP6TW5nKTrP0NTw2qvbnNKgb3/HUPVRwMnxjkAgeZDg1xpvA4rsE8YPSRJqABy4Tpg/pLOJnwMzMTOqnQslypUO/aig8jsDx7EZWsfD0DhQVD73lYh7ix2Mr/v1liWu4g4UvA8Kupab9Qs+W0hFYRP5FBAgcx6rQbO/PIAND68RtOGVgjCm3dojPb5FpSoaki4fRU4+sDLvzASJPayGK6i51hgPD9yma1c98MF/QctSjO4h0hCsKYYQN0TyTvQLpZbyU8iSJmWcJcWZ+6+go296PqH+jKkKrRWfgX3bKN7ZS1c0doOFNiaRF41EzVHHE5Rk8w9Sot0Ypm7VvNd4lbnmrPeJZHdf6qwCBKEWnmchkTCekm1z1fp54HSyY+vT1esZ76jbscnMSBVnLi2KdHU7Gk9RzpDRela0bve6QjBgCe//pdWqwzzlNOA34J9fU+m+B284XWlqyzFNoKqjacm/P5rZCanEmsiYsnB55IjAikdjE9aNa/sW2ptzSEEJVJFnAamUo/YuY6zr55cdp39Wy1XupgFotC0iuW3kgbrKE75Tbq44WCK/7ThQ5fmBf6zpnx99MyiKjseS4O/1/bnaVYKaTN448oHdEeoXOBJrkMrfqGycvKl4C9HcYsmWn2rWiiPcUrygSf6GZi1mGT49vDobz6qUNENZCyy7q4UoCAGu71VG5vfklYAkiCI/Q9CDE38UpBV+k9kpgJIeDdmRyi7NYQGEivgl3nBWHfzQ4usFf++wD/s4Y29jEOltRKnqObm4Mko9C0W9FlC45V5QLoJv4rQsTDuH+0ziOOlYZdsSnymdGpe9C7vGk53xwmt83T5/3uq7iHLjAehgv8TcvNEoSnt6CxJYYwzAMUz7FM16YlXhfyoUSwSKoIMXsRivgmhABPE9euDxk3vEqjurdgJnxKq+1hySNeg7PXu8idVcVpjuVc7+IK+eFk0rbo9j6f8Wi0uo4i7Z7BsvwiVb+TpseHCS23xaFn6ttlErX8thwB8SblhbzITJi4SOGlJszwf9w3zuNSfvnpRitkblsBUcsi2vqXMQKxQC+6z1+4NKK2dbIyO3hbubRAgzaN0l/flFztOs/7vB6klumiMIvjJqJ+RO3a4fDhPjcEZQYYx19HCQcCElCpOQ+dIbZ/3K2fa1ReunIyH8Nd6l4zSaN8jZ/euyCTnjOnaOfHEmtIRRiK2cXknYQXZ4vOzByXjhZAnUcRdKBo83S2yaDdO5BPZPgsFYdBvMmxlJTxz88bqqJuilky69NUiOLeT3taXuIm0pfjE9mwUTnHtROkUti10bBg9hqxwj4wa7xKT3SWGGKQhsyzFNunOZ8+rYaSoScfu/9BuqTUkt6KZdUiHSw2psmRYF34EuheUxiYu2h+28Wxch+cP18553A6PhoMpbVABXuCdE5BEDmSkQLlJ8ztUeV4p5rc5mswHLwbzxGiWeL5otgyce/GjDha3MF52QbwL2M8zlP047lcnpBk2PRf0V5wBV4i6t0hd6DpZWzOum8M3G6DBMdFOUGLX4ExCcSeocP+ejrsVcI1nxcdpMWwpB5x8veC637BgcFPpcquj9+rrbhj7IZjyGGLqzuYGGOmUcLUE1rtlmO9T+Xz9VAyTXcpaKQPO/yw+Om0D5dD7UYS03tyh46QCNqHCfsmFJHmvPpm0+HhINkIKGZCn13I5grEU116R0/fpX03e/kDbgVeg9mcyhkCJ9ZT2iyO6AYhhi64NPgF5wSmYLSdIvlbJOBhAbdZw53wnt3tj9L7vMJ790py8prgyL8/RDtjMLBmy/k7U4TUBVne2AflS5Gpkagt4jqgvW5wctVeD2nzHwKJ+5jyHrJ/eYLee8azX3NfoqbF+vmxhp6DBIVDBnUq5tT1cF3tbdInhraQ0r2p87htuAPl6eGNjODtFWHZfb0xyKVyDpsLOAR6C16d8lLUJHCvjcfIv2dBy1EjpcRwMd/QZKFjtBU2562B9tygA3qguY2JtMbny8SIB4ocnlpnpMsirkd6qKVwon7BD70VkiJZFGLyzhn0SNRBYKh26exzUDwIk6/7WaMhs19zJf65TceH/IDf1DanH5pC0lMREPj6kIQkM91vp/i5p1zFJ0vgz7nZNINSWkTtY92deGk6JK6Xf8cDAIjfddK8e07143WP/zMcGrWdJ0MwhZozK5mgI7FqtDjfJPmVHWkkAQbFkdNznTrreEexJEY+jyftYSLiL7HyVyAyTAMw7BDbgTCZ04ap32w/yDu8TouXYKuGI7b3A40Ox61giSLAhUa6dX7TSh7T5uzvuuhzevRVQ9I1XyI1eJN8UM7J4yij3iGU9nuAyHy7aTKtZ9laJLxJLJfcpaF2GBe1LRXEqUHs7ndo9d7vvm5Pl0pGocomEtPAQsCiFJwxq9E2HJe16tUiBHV53YcsMVlS1V9u179D1k1jy7GgBzRl4ZwuW+r4K7gniO3ocnpA0YDbkTXpXBrRj6HzHmPJ/PtiUIJQlFpyZL6WYjuS0vnaOjHRoFVZxQa0rmfW6aMvPBOPJM/yS+eeXXRis4DdVwUVoAmDiL74uk4+7onHZnYZtss7j/xmsy48AzU2dJnIBtZICYL2zsE6WhJTygWaJkzZ2m8yHsSSQuPNm6VLWBI9I+/wTCjCP+ikt9r16F1G1AbccFwsBDsISadadOZOzZEjJ5VEhi1sbnygdW7R9e7PqBqB8PvXlVJ6YgejI6j3apUxLdqwr9BL3kOVpgLrUBSntbwvqL/FF1p/D3D6J8MljKSu29G0q1gZxC1oXgbBUbcqVrSffKgQ66U13yIxOXq8QXNsNGsL1Y7WQ/Jik0hmnEUWNUJogodKjtDXjKOp/WhHpD/FwydeaDjiM5sU1/UiwP5D94k/exgCKki9RtXYyeWZt5bNjgVeeRvUGndnPoD2eCtrqzdyjUTDByDsx+XEgN06UM5lDP4ifigWRqK1dtmJgz25ngG90Tv85Qt6iRcbJ6l3HfoxG4BcxYRJWGeUASSJk1tdsEnXwJdEk1/irjB6b+YYc54rb7HOfMYI5sIPjGMtyjGG2DXlaycd4a2i9a+KB7+TYv1/WxVQJ9TBGeYfFsTKivIMvNylWHBRn67lqHzCaGsstttKSFg63bLlZ4YgsWgyBJsQIhu79fmqDTaRwFBvhXaYCiem35TvXD58GYEBpZEDZT5//O7Tdgw2WjjfqvTF93INz8DsGEOVn9PoeoMzL7xK3erZDTFW6KmUlgSFES3Q8er1PxpH+cq+oF77J96izcg/uuxgZmV0GdAJAwb0nuGPmfs8yke6SQDr8CwJPXtwf5CaFPOwjeEmEJA4sx4sDlmWNU740u63iQC9rpAooBudTzEPGdLOJ7/GDJMDgrUG51ll6yzVYV24lWiLcI8UY8pCX9ImBxPAXOI2bUGNO6nJ5P3MjhZg5RsVd/24SIxt0evMkQ4HBDMbbUKQnCGyhKDooruJvj0qOHZXFWefQvduKpCnu6IpnX195F/Q9CcuRQ8jU56il9UL+p/ezvIDNnP1C0GaOPSV8+8o+rj+r0x5lypZME3XaJ7kUYX/MZ4Rlu4z/k16vEtcjHTdWSCN4OcZlPPP3w+XTQMwzAM+0wLymB+kDeqdI+DzGV9wR43RzyeWeuK6L+ZyLemezGga2GUOWJxxIvxjTlDBkSOgqimz63YyTVn4f5QoW7mI1e+6r5AV75YlbDQKn2UvrP7maRHstEgJ0UTTLsKTB2NT+M9KOTCtLbs76tCaItLINrYo7wZ1s9LXF2uaQmwhPGTrZsF+aWaZTU3dvm0QPYP0C6ObhsMEfQQF0gBErdlYuIvnf9x9vwJJGkDa4QV2PJcCOfGByH3ZrwCi3rujqfMuCXArALUC3bJGVFDnwywoWzMXH38NHlKq9pGqM0dpHEXgbbUCkqspPGYVnjwK9vOcaA5r9+xMnUUumzwMy0oU4T9TKNGFWX4h/JWWuLGKMup638nJILiV5ZR7saEisot6JGpXTuQIs6/nXQi3sjeUAlKNaCLsBgr+R47nevPdUgw4Crlbdl7ld+bQ0QX3E7vVgJvnvGix0zzb/Fht6OUkiBQaCPCviFpQxSim2Z5DEvucFfmpDQWkQLs2gd9/H2sKmZC7MFG7Q+bW8ajjU425beP+kFvduKqOZnI6B5eNgmFRGm06Agk4irK8ASyaQwuw8Pt7wzzpA2W3rNwQEWtE/oGl8IlfPN4dNKJGoA904sKse2nU7N+F+Q4AId7Z1Vtroh2HUX52Y7d0JJiSeACFHMzycD47YZrvXv0iflB3lg8WuR1dxTghaHWiFQJdcD/DJ7jjuftbFwuwTvxrQ4LRl5OwwNfN4/5mdhuS3Bgh1vz2Zn2vv2ZRkxjcdeQf9k8BZW6DK7lMd90yG4zIbXMAdAtIYFT9Qo6Fms4TD386tk616Dg0/LQRAVpLNSO6qQ+/H1XT2NICNypDVJoWolwg+gLGtI71Tlkbl8Gjs4yfJvAP2N3SXuyh/Kljpflzpt9tEX5DVtgcp7BeSyyk2yOAGW8ZdD2VEi7p7Jr+YYkIBheMXK1hHQr8L4t4xVqHB4/P/o/+ZQWtxyMS6ix7mfA8i2gOFO9WlG5yr3y+wuOXBqIlAe2/ZhG9jcarqfSPQ6ck9cvOnQnp6/2qrFTtPZEMSJusOFBR9xagCm8OJO/DKg4sBdAYbFWNNcrvqQdnH9eWj6BP/0S6a37D8WI2fnB0wtnzWKYiYWE/WPn2i1cSSonR9OXygoT1xlJfyZWliodFzEE2LDEgy9ygZySMFBv0IbSlGBFLPGbBMeKuVW6s2hNc63Vlja+tRHuBWTkXsnfrbmqvkfAIWzf+XbRTCVCsvb2rhMuHMbZeP40u9jN+moC4YBWX9vItuxEPOexUNAcxLbzfA7cfO8V9yfT95eHcTzAa5GiyiPa0yFheNFFl+lL6IkAFoCJdKVSYeR/1e0eLVscf8NrhexEYloQwzAMc+nvnGaL+imHd2bg1SNaHP51RfHKmMFgMZcFdhURpFk0zQmAlpNok5oF2z6iBu8ARk17glZ8Xf5mkrxsFLBjPAPdaNg7IahU4Gw/VpQ0+Rjt/BLVWcaK4lColX9ZuMf0o7tCIvrAcIKXLJlcJR3gohLq30Pa83bcwcmr/GXMt/9Y0h9tzvXnfOaazJjMHfDtOgJvzSthSlkKjaeVnCaZDJUQgem+3UN0Vv7GYgP7ICipF8l171howteteyP9o4Fd66lV/YpLZ3B7N4+lXUPjTR81zKl26Gglndc/I/FRDQmEqZDFPuNJ8nrFVhMcAQZFygxCU7M81OPa4lk9Or1uYnvWYsPNK6cc0mUyCr0u8iPb8W2Nfc+UjDk5nyhutci8h7dPfILeeomjIZYUBRX63EYj0Fgs+X2U9aClklGHyRPqf0S9nDi+rJUTtgivkOM9DJo2OFj4FMh4hPRFzu/D7OeJ6HDdcFigJDOfsmByfrALntIlqNEqNqTmJYMOqd4F3+Cbk67CbWPNOsD9eQpuEzI/xv5C5Wd9vd6yfd2EhwKvpO+dGqpHnALPXDDqSwkd+QXPjZ433vBs71ZlmP1AM5sW/4Iza05NXCWLXVxuaVJngYXc5Y8+6Nj5MnHR1WHtL/b1LTYe59gdZXdVqVRDrapjWAmSjpnVbcDf811wevZniJdi5cIscNjgIZVhZ33igSylxBXNR1Tl0y8Ep1lIBausC+QSh4tpbY3Na22VzSVGbSDkxCc5y+kBsU4I7VjSwNaf9LilqnnATQe4ZnQqQmFbJaRZopE4pqh1Ksc99AU342nYBd0Q+0grm24xOo5Y4X8LXoVIPa71qIDy50dHytZxXE/8nJySFdMEQlCK2o8qcVYDmjGshJQcpeSfgv7yZkhm1H96BERSlq2YwJLIdOcU+5TdeUoYIjqNGrzw56/b+HczeKKXJDigjMWeJ5izqW0fsPFNKgQZs8ZJjxjpVEYswbjs9xMk8M22OsHVQHn6LampDGYQvgWBGx8v4leY9N+SmtCvoC7wAAjyAng0HgSSL/eur+5yRuB7dJM2/l6D7maSIdWMlWyikTkFNcES0HIFguCZN/nBpd+4o44ePqXkgt6fEceCZ1pCVBIDoh+Y2w3Tzvzg5OYX33h8j6kiEsE/IkDzGtXoG/pEkiuKpJQOkAS+1FHEVvfV+tMp6Pjt9XxpWcuZelC+Rp5pKpfpjWXSy91GIF+uvskTK1DnChMnyu8a/mxiwcSkZKXfCi7MIKbzaG4MwmFYEUSElLbZbJ/r+3TvjXZfAJGjyjkeS+kShq9RzwcjKZHCJQ+2Zw89mY/DSZQ38sDiPGS5cWhDXTK/VU0ZnqR+xTr5yzAMofbHlHrppE7GlHBJp5jN+H1xqgbK5HDppk7HZHMZoRbOOHoCpegJsz7coWZNO3iSr6RKO3BCraxOe3QRpXo0BKLyAidm8Aq3zDJnuCc3PuBMofyDpyQ4YSIpk+AblsYj3qiVK/zIHG7wE9ZZIATUxBOUjUpF6AjIjDAQsiRCwa3JmhIalS1lYofsKFt2WVrKnpXJIzazVnnGTmmRIy7SZulxDQ8m7zhjo/KJG9lCX7gJj1kyaaAz+SFVnlQuqDteoEvqgZcsU+qCg8Fx2jHh3wd15BJv1A2X2X9Sj/zF/6eecI0PpIZv9W+ULTf4C9aRs+wvWU/5Zb4jjwzZvZJ31lt9pC6YZn/FOlCo+6QY+M5ovMp8q5syD3xmd80sPDi8DnRRpsImh9C4CttnAOVq/HmCV0DSrxxujRX4mDekOW/J5Y1Qly3xM/zfwHsKOUyN+0XJwSi3pZja96czqGdoFXTp8AkX5eaUhsNbmS0N1n2r3y2N1gl1AzGY39ndMsUPee25bjhRv/HH/KeuCk6T7mSre777l3Y7P+GalwcXLd7C680jMf4F1Xb+GxleDt/um5zXAQAAotvcVpSyEOtow1iKiW5zCpSyQCPaNBBwmwJqLgcjTYyytfeK019NfAoBO4mQjRBlOwGbA4boXtsjicIyeN+gDYHf+Hd5JX4GtaORnlqOF7M0gdJuI5/Yy6ivomo8bvg3VrydZNwnX4velhMUUxFDn3wD41vR9WAVP4D/mitcv/Rg3X8AqrmBsB2Aaz4B33MD19sebesd/LsQCI6sQbSpxNpKcldL/s+9Lms/m30Oc2fDp5DHxedHTp5PDy2W/8VW3iY3/pPlqX0jAE9TTXJMt/9TqlubXblp7spjG96ruy9P9GMoFsLvvH+oNr839tVx+L0wPlYn/Iuqw4FfWsVTvblBTwfOMtp5Oi6MeolO74+f0CCz9eYd0bOK4c9Mt+HkugigjwA6Q3CtETgMw7kb9ar/MWz46emyKyDxDRAUaguAKSAkA+oTnzn8zDGfKXvSQktkqSZcI8O4BMYC4i0Zs3G48QEiVYbxyzd8qVnSeeRzUwfrcBftQpznzZMXU5Lbai3j21U9jy8OydyNy33aWMXeSTmvtkiu3B1trtJI8eZdNc+aJXfu+dyFvXWsvF6YzCvMk3/+kdyztWWc+VbPf6EyBNKogmOdCjYNKthUquDUk+rNCqr30UX1b+DMkgcPfO7Si3WUjheaeZl58uoPye0sLeOXT/X8x4DCcr8KX9sqPO1N8QZzi+TG5Cnpf8ot2jNkMmZJnMot9LQ5ZCzxeSWf+3FVJz+erGOBL465hXBhnHfj4q+LefLXxmqeI7lHJ2Xy6MEyrtUWuWsv6nl3Rxe148UuqTXSvIzLvXtXJe/+WMU9a5a7t1TOu+fjt0+L5Le99TxzTHNrrxemydqvFP8V5rn/tqp5/0gcvJklB2vLeXM+d+5bnZybWH9B8il/HUgWC0QZo91hSZSrorPikwm6oDF4JkmjUv9i6liW5mVpfx7557ft19eueZa8JkEZWXdYEs1V0brjkwm5oNF7IEljqY70ueWRjVeE1zQfFmq7fMHFrR5qZoE7ZqHIMkvKd//DZzoxGKuQvksvTT1gWje9jZZXwIx1ffXd5jOmbGDzrS73jfeCFb9kSai4gkXMPFwYP03DJuLoZmJD6bImcsF5C3NCVVCes9fw6VyW4HLzWNut+lb3tq+9d95w96k9kfA9YZmZN/8Gf+kEka/VAP8eJHvpjqN8X9iXiW/zBRvZ770qHMSHf+ll8iYV+bc6wxdFfd9wwjgxUSUMuKWpbpZgw+3Y2R1bxfsmFueYY0cg6Hh316KAwgkcmhP+fRiqfPVQ/LJoQBGlgg6GGQxwpoIpQblFOlzmACfmmV/MiGa5pUp02XnmiZz9HxAFACCAInMWjiibvBGcyBYsObmslI/6vLJTDWxVQsG3gR+mx49tow39HaDHKyck0KoTUgCtBA0OTSKE6ndCkUn86qzF++iFeQWn5BNckbGSwuxPU4cYTnlABpyxhUZS6KCCWgUCcOKrWwC7BcZ/W4njo54lQfXZ6/iYWBWELTHW/9JaJHGt0s1GpgNi0XoVYLNFHZ8Du75n765qxP6h5+/P5VuY9mvVKx76T7nIEJRW2x3d61Lz8EXduvpFkQjx2UO8Y4BK6jThMqTeWfTB9IbQ29NQTLd7hsfqPebPugkGxEH/vyq+x98s2xizOYynpIM1KZ5DffSGELSExLazuMfp0xh6v6MrBvVVJ6emZ+lfV3QI1G8hfwEZ90fPlr463KJJr2uCWvU46umSWLbZCPsnDafORsxmEcdfcbmnLPXtamX0Qf43fXREf2e1n+kku3AmC/Z+jg2qKazU860oEjZ1BSG2i+XDWS4mMMfnR8Macxo+F/w7ngls34SQxG05x/4YnL69y2wO9cacLwi+7lwByaQq66ioB8gSFb96tGwMzpojuq+f8cfChoQe5R+teiKHL5XIuX/7bRBNIz+s+FKX8SNxqTNSg57RMlbJA4ahCtn/hG37IOGP7c2ERQwpDXqL1uNBfkaMCeoGecK8dhvUJ4gzOGdUILHDiGhTVsmK0aAOeHKrK+a9e1HvEK+wRVNYvCKVQu/RPiGscAqoO8izKceIeoFIhnNr0rrZkTpDn6JdcC+nDmNEXUIeDT2hvkHCDtvExMKOlBF9a2rqj2zJd8L4Rl0Z0ql57RaoV0NcxLlH6UgsDId2ySq5w9ijPhryoBx496gvRvwAW2OKS9iRaoD+ifYF4Q84zVH3hhzU7FiingyRlpWOKdUpI3UL/QLtB3eSpxhvqGbIPqNXqGEkvMI2CotTpJygP6O943G6uIJxBnVryFNWvbtQn424Cc5T1AdD4k8YK7RzxOQDY4OaV55cvGLu3Y06KvEBmzOxOEOqgH5EG4ww4ySoVZHnaHasUM+KSA/nrSnVqx2pe+iXaEVnyClgfKAuFHmM6DPUSUn4HbZvYRFI2aDvTJQelnyPGL9Q14p0g1q7JepNEdfg/ImaFImPGCXaVadLNown1KLIw8DG+y3qqxJfYNtLXMqMVAX6F9o/I3yH0xL1oMhhUI4e9bgjkjjoH29KddyROkH/QTuZIXnE+IOqHdm36B7VMwlXsL0Ji2SkHKE/or2afSquYFyjbnbkqTX3bqE+ZeJGOF+gkpH4D4wF2k2nS24w7lGHLALz3v2o90x8B9vKFBafkaqD/o721whHONWou4w8T8yOM9RLRmSK87No3WaknqKfQ/vdmXIaMf6jLjPyOEFfo75lEn7DdqZiASlBV5PGYzblNMXIqCtIF8yOc9QrxIHzEQWJMECbrKySW4wW9RHyEMx711BfIN5g25jCElakMugD2qiEDqeIuoccgtkxoJ4gohz0zx9J9TwjtUIv0L7VneQCo0c1yL5HD6gBCStsH8LiIGWG3qG9qcfp1xWMKeoW8tSr3p2ozxCX4XyJ+gCJO4yEdqZWyQlji5o7h5sn5r17UEcjPmIrTWFxGaki+gvahxIKJ4daDXluzI416tkQGeC8M2mtGakH6Fdov1bu5ZQwPlEXhjw26HPUyUj4A7ZfJhZRpGyh35kVHuQnYVygrg3pCvParVFvhrgWzl+oyZD4CqNCu15ZJfcYz6jFkIfCgfc/qK9G/ATbk8Slykg1Qf+H9l8JP+G0Qj0Ycul9yFgHWMNremxxlm7KwCxCRsovQ166Kf+w0lBD/lJTqm7K87LCDJSYutTq/CdJGdFypoLyKsi3bsqFmtMyWfkq91TW/OXLgZfn3apO6Ka88qVTjeWHMWfJkjlfDvyqTpf7bso77zmlZCifwrx1U274spKEueFt87iR8U9WRvQ9os1jNvyksrHVETTkMH2lWlBoTUqIWHEgZY6RRqDP0aEjHdDYHg01sTUCKSBIDOryaHJH5YDG95gQR/L4Rcp5rK89MMhxgJEqciTCp9lcMVB5Ve1fQG4E89+pUzCkHuKD8La+QaOnJk8tNsnT7WsxfoBz70l7LRlsMXLL+GNNi+EEHn4NxHBreh3Z8iV5IDDV7AGszQpABfQlAEjLLSSnHlyJjbd5oeRMPbYzcKyNH5D/gfRzr2S8DpAi/WAcqxduQDmHuwCixS5+3aZfDARTCdqxrW3s6PLQtOLfKLUrgR5F8D5n92bMLwOSjp0UTqRRD1sAkDFGfPKXxkrk7lsyXntI/ju49rE6OjEv9yEcf9w16hR/oSsPv62BOuKv5tfu28/DwypFo0EXl8cmf+cxfWes3zlI/4Zr+jdg/Rc8vf7eoau/F8ifhldnbfUGlSU9xoHsdQ/JoyNc0eFigYgLXf4f+fV23DbRKPNpKW0U/G0w7zi8FOWmewz6vIm+RbG5fax/d4dt33SIeF4H1kD1QU73ug7zuoi4f0/L+5/ji5ht+I92RZQP6+x4OudkGjcds8/y9XtJt49Ylk16NqIxFvIi23TOH7cv0T2GbOXzyBY5jc6AkCUSNoKKCYMH85zXFM++x5xC8BFeDtaKxTNjIy7YtcvvLpzlVEh0WUwOgwp0LL4yayLH4dMiJBAW6E88p6mw3t0pOoRvTqGcvkVvozezvrn87TuSRWIoosK2feagcdUBci9Rn8Wrl+OzDKhciGukY4np9U2cxi26Fl9fO37GDmscWsadjPo0izrK8+jn2B64QhL2s5NXe3SOyoffPqk/W9RcHh1roo70wh5naJGVGKy2fOknrct0Pe46n2LHoNE7NStZ9i2nsOZnOs5Fh3phhO4iynLlUqHfWIO8PqqhzCN0wLMiLISdXEIfmB3dTjpwbA430MHerRklJdAxFTGM+Jrt5KYOkrKUGNGMbyAGG9IZ19L7F2ogQi5gOuO60zKr0ZvqVmvCJZ4+WIuEIGwY+nGfNMWgfxU61J3RRx+skVAJakl5MJuxRGLIe6/qzX9uhzmC9NtrMWT6b0kzaGmTDXuqGUr/w+epWFCLXpAKWsH4RrOmvfzT18jaq4SPcLRew6uo1wM9fEh9suu8WsSbUdGXx21akIvIyrnz8SwFH+hIGs1xURGIo+rN54xZdr1LhlqWbumHAGuscbttFikV61FKgrDaEMLhAw0096CqjQTKAfBsrnQylmRBtayBq/mvM+XbPKjXvcv0NAnL0qF+J9pdpUWlNUbCjE5cqTUUliSYv2yRlP0UUGYaTge08ZwR1wR1kbGLa0DdiXfPkiEZt2lR24uS3rRHrizzQ7ec2kSjifh3U57D2tihY0hIudA7O8uMsvbDxFA8Vck2NaT7zq3odlNOl4/I1JOyskYIy4DM8/RuAsHhqnhNTEEYm9rDwFnuTttHW0TbVsY0lZIs0H8CSDvtat/0ES5wJEVU07oQ0k2oVd+WPpcNCEpYuZxa4tD1BnLQR8paqrYHYVHGazvrChhXIGmPvwSVjg5dMkDmfAVGltboqqxK1Js0TBH5wEXCXnra6Lv1Az6fi5YFxcYIGKQNJ2hy0EHZzy4sUXtZokmP/7bHRmHvFs6PKwSf73R8l1KzmUhsxqYMVZiSHsGydg1xOt/u3WgKSNJ2zuLdf3y72QTV+qXtUq3ZWKbevlU6Pi5G+yumh9bJ63ouTsXAbITa6HB2UPqCfM73B4CncI9c5Fqx6U/rrTf4Lv5BXUfIR4Vh+0JKqJ5hSJZAosoOe/qpS2scXCpv3yIo4+drvh11ILVjtjLUpZyQAHxWWoGkCWVCUxZtogNEDHmgTxcjSc0ANXNA8qr/VoNAfKaMBxvWEmh+/ZK38xkDY1FyqKdqUk3fvohNvdXfvePXkunGBc3sBZqmzT4uWSGubWhf37bHwhLpFESsvCzFtBFwaiKkFOjcYAxLFUh3OZbH8IYg1hUSxU5GRh5S44xQvkafw0FL/GEhYp6iJC/4Zfbpm+nwYaNf+6sPZYdptCJ0n+tF3+D4gH8ldwX8QNP6IWL4HUe23yQNVSQHD7tqZ3Ubxw5s/yTumCOD1S5b7pQng3ew1OK9HNVoT6tNOuT1ry7w/wpllJgD1o1bZKxSD0BeFgACpBh+u/af4E170qKY/Nj3PkKOnMWxGo4ts+L5GLpbgh6L1diVIeKwBlKrdNk1h415gGRtdjwYlKSzr5n5UPk+bPbLNiCa5oCkVR11lmoiyw+kxo2J6DKPOlpwrAgdO1m3lSbQRJLDe0ct2KJ17TCVsGz0Ygi0tNthrDrNU9boNP4Yi6OCggeqWxn4rpviUXCAQvRuzhIQdkci0EhxdxfAPJrw5uDaxNZOjL9H4oZTlWJGNAMWYRANMNhT8BYqVxWformrdOTs1AnTbAYpTKyGVhQ4ztx2YwTHGYBSsUZCE9ZAZS36iMNMSwZahpg9ixkxhni9yZXlvUmPn2573UilzO8Utlt56fiulbUNUQwofFxLFMiVNQ5rFIlU67UvbD1tiIiJ1QgbTWXIAdxnJBR5BP7odbTV3OO4+YlsDFrytnpERg7ilxbX3Yg44ZIg5kiWOGx/l2ysTCxCwngOTXYTWPHFF5jrKPProH6nMSWceGCK/saNkWXZHGtORPos/nzCYuXlju+PYdosYLSHmZUEIJ0q8guV0UY9pc2p6i47AxZ61ZmmKI+BSREhlLsrMJcCb30LiARaPp5X24hXg4Bdp6iMMOwNGJB0kfEKvwoWhoDDY5EGSLcGaQt9ZFBjwiZqtWGJefIYYGAMOFu87JEEAQr+Yl10NbQoj1gSElcGX8M1CcgJVSxZC99WauP779Hna/1bE2A6hvjfg2a+UAjAOkPXrHyjiluLF7zDQ6dMYjX44aC4FBIsl0uerCJr3WUkw7GAespxQEl0+pFps/HMyRXA/OfHTv914to2Z0Fl+rdpUlxJ71SlSgHGaYobRjcHMIzuKrIORIGIfOWK+FcMappvpPaMfKVQDSYd57GA8VHEhr7F5M/uCWKMDsGGBi1qW5LOUKjMvJHpkinY6fSeXnFT7cBkW6QboFs4L05BuWHHkGx5dDDK8SZIybYAxM/diGxD9hD5bRSZwoi9y3hlIBq9I538M3XChBysNmkfYlT28kA7dNET/qEA4rBY2E1hT75dPNrdAN/JIt9aTAc91dSC5aPnRpxSPNp17BZ2W/ATSsnXTL1HQIlqq4OCE5RaVf7j6X9/vtXN3+G/fwZYDXCRJ+ZTdWsiButW24fE09HJLOi19m3FBVurN1vf/cff4BJ+jsGuPWO4Sk1uPlcjp1ia5aY4wgoQxe1EwTE6T6+ELQCTi5ZsWarj57jxAkmzoa+27bDSdyGbCTiXsK4Go7tZvERz9e2Vh3NxaJYzV/lutKqGOmOac7WG8l4nIOnVyFFUuPyZt0WbXFjd4kC6YuSuHv79S6vOrUV5Gnk7FglJnIY0wkhlzoYiU5HFtgT60dVxlDVeVWnbvduSaXfrp7v0W6MUhM01LPcGETs1iKj89374kHsY1e+3z79gRg7L+uVIf8iBaw3+9umPWaSr/O+731hSPLh5w9wiuu4Oi3s31tm55q5J4IODBKHmfOU8mvd3fVpsDJ6tmg+PkT8Vxv9DGyx0hCJJOu1GWwL6aX9dJY8jOigNhNCv/HI0w6FCW3JukEO1+uzz5tPsBRFM83+yKK1ccODdOOl3fZ21reL+KYQqLjB2Bi6FL8z26dLsYPm+MLN3EwGxplxDEtcxA0EMdcLK9nCDlXKLa4bnlqFRx+vPX9Xf8/t1FF5cRLhuOXUbFrQA90lw8rbgy6k1LSgiS93esgDhQ7e9KQQkOxaHBKNseP+dQOr3rin4THVdrsel8jKr7ugeUiu1WW9iH0yxU6qqPChXjzb+7mC2vzw8PtiA9lJNBeFJir4inLiCmBaXDyOYkyrLTfCjFL4iSJVtnEPh43Xx8qD7w7SO298QHzPvVG1VNuTNfz/1qbbcMkH1qBXLzbEfQLzSzbGDL8m+TUkADSWkhxRQte7RbMnjkaM2CKv1rsyaeKFjiwMtt18xMRxn6xTZcTbc7E4Q2nTQ3sQ38zyZ09B1dkCkafCFbYy5Sjl5ViNlAcKcxUiA7KxRF14Qh0G6r4MuvZkoWmJ9ZLtTbIXYPVPV2N1ARtEIOuZAuGWvGWBsG3eIf220Lqe6yrFVDkmtBGSJWnG15rgu8t5CokJTs0x6uVStH0rRihx2lHwhpwMQXytNBKc7FGRzGBEzN22zU2y0jbnmTUYedEaBs5rgETJCv+HCFAa7T9Sh2eiiJLHDCzfZ0WyAMG2cmgGbiyHqrKlOqLc+FG6fzL4n48DvheFUgvKIzkYHVkd4BpAWjgcH6jbdG1SbeeIn3LZKM73JCbqCLdOieZggJHe25ktOf6/cr8gf7cJCUJql318mSQggOzm6xbou7by/irvzPf4MPvtWFNs0IAa31CNDLaqtFGVVJKObS/kUIvWLbOTUN8oQ0omYKq9cLG0w28V0fygj6j0/i74nRilD4RYcqgIoVZmOd1s3P6Vxx7UPU1vyNCsAcJrk12BxBrbqg6d0GMelBVkWdkJ9ix72j8BrafJs5L3iEkbFm12YKPq5cYEqJtp9FS8aFK/7j6Rsq+NtqbZpSHTM+/0konixYOOhdLBKzWmhKvP3i4lEr1wrT+MSDeASkMLyXoTXirzmAZ9i6rV4DuMpqHrliBYbfZWAJWVLG6ZGF8+E1alOvuCPEldEQseUnuKUi+ZnauMORVGlLtVKL2BQkWdKiobF1kNeu9xH8UIQH6kBYtLyCBZNLAx+aHq/dXktwftYK8/gisIQMU6bti8jJvzpM0+vELgwf1ULidqKdw6iaxS2Ht1YVb6xdQtnQ7tUXDifDzR6sSkRUfiswK4JJU6NoQKxefKaBCv69JeX48xHR6K8kZivVdwGRubUNq0IURsXDfpJKdutT370ZIdSR/qz0Ss+ifPK0afpMvFX4HgS49KRk+HkyqcOS65AAWuPoIHmTjF6s1K72glNiHCaNdftj8Po1UGDgrp4J7NmCU8AlkS/ES+4mOV85nhG5cpDbeQaQCCuNNIYs4FFhoSsvaW6bgck+HedVXUvKK53ZzcU8IRnWOi1mh01LyXYTvmUdtcNhLjhA0QIq2q1auW2DqUiMf7KaHNM26Rc2Utih5DS9+jxVp0ERs0w6hiGjNgZImckdLbJBXq4B36FSNDdCOtaOJ9MtQDxw9N+KXFf4eGqUp2kOdNH0UNlfkxaWR5YLCJw4qI+OCwrj9XtqFUEpYDUlRj4yChW93hkBSNmUWXFq93x8/vM9KIJnVQuGr8IiPcJCsyhOQ8x5hjctnOo27/OLg3DcLIR7YXcMe6r54k24r6R9+PxfNyzVuz59zb9q5YNA0j6F+lax4tCm5h9e2Npp/leo/Wp+/R8WUgIt68OJmTS9+6DUSCy+ra4Y2bpjChMxj4Og7Mr1KZobj9j4gUb9FdXwXw+2wJJh8lxF1ZRSTCGW/PmRhHbGOchx879vFcg4tJ6Zg5UsTYK0R30JUv3PckPnkk5EcHLlUrrANWt9IUEz4BsSaIP5+EFQZqkg3Vzt4jBZUU17luxjNFGQzz66rSa0PPbw7uveW87ApzlPtXlUlSJ2OuKPrzWhzDqgPOBpvsIV5d6qOwgZt5Zv7+yHCu/18KpJm0HV6HgOoS/fFlJA1IZMA3j1CHY0liMjKQM9BHDB0VpwyAyE0COcj4/QiM+3SB/17xAvy/78owrmBuPFL7gt60VjwPc4pyD8LmVIllCFuVYTNUx4i4rHQtwaBHcvHRq7DYqJHHZ/Co3X+yhWp5Zkx18D3Nwla15q9V17oA6Cl8zfEFJobLzp+gW+eFrbC9232+j18HHCMEDRxM/W18r/TN4Qm0aHcri8vtGoMr0Ldh3cFiB7/ZuqFpk3MZ24r2xRjxL8lK/xXSbT5VcvdY4PbGEovDW0jpGgeO8jyjT+8WXRfIuX77ufMn4hxerFJqkUxoxesvhY04UAgy0gh2LX5f9aCuBXbVIyKxYSb7gGmEfnmD613P/a7OKESMLkLfD2Y4RJWLEf488Y6uRMeq/oZMxZFaC/O8cMkaUiBFtvlLMqaLoe3L/0MgtgFBMqRMuf8BZPznZPdfSjEoxQ9HwME+ZFvAc6cFY2Ij/UDiTxA9YgRAteo0xqlSMYrZJhjfSKLt+VmojvZBFtPJZhAxVqh7KP1XSiNZ0nhsgqfiw+QIZh/NS3tLRgHeNKOjRB+NzQ9dasswOrfnL2XO7zaNLbZTYkZi8XSN0YY1LQicZlOZKcP27vKrWmnprkh6FqCBOQEg7eWARhLQwuYDAevvR5LD0QRPBoJHC32nMZrpewxIxKxCm7zHye//V8CDaPhEAGgBS9hazJjc54uo6HQIyQKDMAwYdfs7aO54tGXKAJ3mDgGwH4qm8+7kf0pVrx86mfORThm5oQoJmUlzrMgllFLxax8G+wZGBKWXFGhdpmb0gv0LKZsx9trZ/lWz7VYoPKoVK0NNmZuhsm4L70O16myS3ipSCQhSIxBISiMfzKXAR8IpC23EaJ+/9zV0ZlfZYMW2n9SA7rDwovMJzreoaf8cg1t6q2BQUspnk4+kuXy9Tt93Zjgzlf4vIMuZKvESDw2qZJoFNV7804p4qed54/cjYPrJEQu3qJO+TcPLXUMgo8DmABbnHyzb3gUJ5fYFXYZe4FCtmv837rbk3s74z1Nq8d2U1oJx6XqOh+kb6etO8UQs4GmiKoV0SFM6OzdGvhT9d2qZlhaPJSRMhN1xoqHjebItXnLGxivPaZ9vOZUU4MrSN1+U4tLMMwfl9Nsg9l2M/ayrVKZwOPOO4gffU9W5xlTrDySS7g+IYC9K+ElYLaEWKT0LBCO3XZ+Xp90mY2+jiKl4rvGu8EUD/RJPhZOyAtghBP51bAIq/OSoK84/Wz94yMxAxnT/O0DVsxvSgowyqjI4Tp4KxHrTGwDh7vdOysI9dfcQGps1g5s9p8QMrvXWTz2kHaUY1BvKF6eU4IKJypQnoV8ic0HkksdjToQczuR1Ud5MRv2nrs5f9UPVIGfup9a6U21160cxKYdaHn8/tYKEIA/MkVDUPL+TocZoy42KIPWQMUQxkwNPogFSaEyRTy8Yn8fEVzyUBkrkZFzq23EQZWhnPntgReu9hdvCh5S4PWOAmsXZWdaw1Upz9LrIJl7HqRF/d5YUqt29QsnigAAXqnBgujRw0INtmxTnWg5p4tUAxSyUHkWCLqTEHHK5J7ngUAGMHiEd27T966CGTCC8YPMQVLuSAKgX96pyx7oCTRIt5BXV0BcXXhCAXk2/PLX+jB5Ug62xdnVG+fD0K5tDjlhNoKrkS4GI0flyblZocr8VHty0dTM31OQF5xOL3nALUbVDJYviHH3NdnHiqVX8RydObxFlFnUubEymtys6op4MUyGKeyfWUqYcRdYMdeiTj2tAEarLTczriVQ13QaK/9+mNZ+gpaVEd1xgED2FUcz6VGD6ks1dVInh3JHe0nMMR8NgSBpLuZ9sezdcm7GPyd6RpMdXil6YaWfpcA6uLnNYd0MK2IvBCx70N1nHPdFRcasHFTdc8cToUqRNPC7/eQ54QN9c/KS07+IV4xckOfQNYEdRCk7Bc5nOSQ7n/IRTAZUIl9gqDn8L1F+PZUUi8Ro1sZBWiFmE8pKqAdtwrgGOPWSyZc3pNt6dafYdDYlQx+Yo7YmGNokbtRk01Xw3SOoEaGaiZc01I1n1GNurWMIYL445Qz8eHXsHbMjpcglVLoS13knroh7Rhgan5Ym4OHWcExKVixeQysHZGZUHJDyeOOpjeHhVIPiRBkagPS6WaahCbLRmzKuG4BtHzYfs4hl1y+hS2HdtZU99l/CKqSyMReyWDnlv0WC1FIIOQ6mskMEw4Ek4NqjwEVVisB255P48JoRlHo0yywL55FYGLbr2NcbUUeyQQlbQrJ0iYo1DYy48L5CeBZTpiyp20ka/2xt9autPxoZhvSqkqwdQ3q0C1zYTiP0JGMOFNOYE7oYFG9TGZEymaCSSTAHCRvQkm1EIJunLQ9uRzG8z7LVZk98yU2RjS5RFpHMjWJoH+nqppHM7HSHzIs6rffQ0aAWRamvQKJTwIyfgj+gKuRXVXDROnT8ymBS+4OovCb/2FWW5ImV8uhug51UZVCDulXun4gH6sPt0QxdlUBTs5tXXY30w1I5bir4p2fpgtGbkge+t6YIpZctr+OXzs7+Psm17JXALR0gfJ5PTvYkHExQWAfGcVygfeEzfNjaoxfUSVJSA2CWdN78TSKoZFKSQyVybk919kmjY8lTpL7PouyAVxbhf5AEyFzF59RIuvjM8cMInjwsP06orgH0z2fEkj+iWwADHpiOGxnI1vfWzCAKZnGjY2RTybEpoDLwQx+mBh1ue6LamA0bDj9xgTPpRVIyJCRmspnQlGFmlU85nUeJUEZBHf4DM7qLVbt3Ghio9cNS1aMLqn/Vi7nfrBZ/amkrpUWgkAz1hPSVdvs04d2ZVZlWJhdIYnKvHWJSWmgs/N0nhKK1XgBVMIfEKaLW4XKdd+CayXaeXFSLV/6Xr1pR05KeOeXn9RHd/m2dS3NlaWqbmdDgZXn2VVESJKRlGrmRFj0QQqGSHLzpP5dE1bP1RH4YW+fe+oy/qhjFpBT5YRsLBVYNriU0qWjLTxMVcukgRV5iKM76lUZK4xr0xpBgfQjdipseonMQJetQeMBbYdme7bRWgf/Y+YAclextSwU8wI8g7Xoh3qJuAULE3S2vdCDSIV73yuo9eYXL+RPrHQitizkJOtEJuDx2bdB9MTJCWvS7f0k+uZtFmEzOs0Q0rJEz09Q343kxlLPKEzDQ4ToAGhoK1k2o7mQPpn63llzDdBQxepomjW1FBLaV1I7wcJObhV64UlAIUBmu0qrDMrWg1JxSnOo2NNETuAyoBLOvBjK3MT8kbl8T0Z0xSlMyCJtNpmeJY9tEThRfwLtQFoQt4tWYmH9ReS6HEwi7qoqjPo45WCVmUZs5jZkpU2nlRyw6fSVjZi/BQAS7pCpRaJ/0S35w03WqAqQu6hRDcpmrNdVFQWwmlpthi9KUF/dfVL0QRlkNhOl0khq+BKLA47oDph+UQ9iUYrkkHzV19qgOBJN9CMTQ34qFisyK6mw1B/9NpA8DRilEFqDrU/dUHKI2JibEkzjUberUdTNXwS7Uf+12RgIu+2BJyw1pq85Ij/cTeKMGcs+s75uoja28y/smDNaI890idqPhrLVYc3SayVSQ0fb4XF+3oxBCrveHqMgE1KjNr8jIkG85WdkxO5r5zwWjdf50ly2AirJOFkSWoWJu6nYMt0jeEM9sEK7GuivQBaHt1NNMinRg8LkX5fPe0ymckxyF7iH/w5oHfkfPJIrOkrdPi2SCZkWPLRj8BDqpHpftwCuiQjP/7wYK8R3V/wH+Xq54BLL5XUQ64DmB7E9/aaPyqFziKY18ldZ16euorZnq7V4qmtsKpg4nwh6res9iLKwzCYCd+0ypgQxF/7eZHHjpPbYfg+h+595WTZGQSHe5oqHr9Fx8Osxq/fcbXI4Yb+DUeZYyozDeDhFpwZ5r+hZ023n+1M7+mEJQPhYNcHS4dRfcz0NubOJHIhdukAA0TkNAzEcYDkh3hd1MX71rkXeKr8gcFt1/Fd0KGGg5DnApLNlQNJ+iqwA5FV1aofiEoLsOy2ZMjW1PUkv614pM6c46hNfGZW5fe9A6tItNABnO0WwrgT3NOGHc9MC+zuV9DB4yJ7+2Xev/Ym1VOE4BmtsA1t5Dz/j0IpeVCcPrmGnhd8o9B1PQzhAcImNbdC/aHecfssnerYUl3Jwvn8F2SA330DUSDcQDaLo62CN8YlXhhpuOnASLY1VlNk9DTHHdmzalyzHcc3/sQPNYKCkoRQ5ciXbiqYcHCZ3sUQT6FDcZtKG6EXi4cBgkahvL3WodkgQJU+CAEfCjZrUbkqbnDjebitXJyA+5wPbZrfNuZuHi1gu4uDpy2nVjNkKddaGR15JBNS/ckNuqOTAziSUNCTdajmpmNwdL3ku11jokEY4keq15w2iMWNAuKESYFIIq/C75ia1MGvDjdJr9Y3Wb06Hal4RX6H7dsm9qkPKo53KO5A4zaO6GPIi/oumndggrwGXCEBTQzlj5CPqDlX4H/RMDtY6dnnscF43I4SeVH4NwUCWpR4Toeh8UObuTrhsnvVzYeK5YxWBFIv7QoQTZPRG0fT7PFp5/SuomDxNbNxm9H5ulX9EDkUCV79Dx9w4YDHF9mygWoW1OS+sV/P+dDadcP/PjipJE/0x9xx14vYKo9tLmK4SeX8VV0ZM3zE3OxiKxW3/E8IrMh72/r7XhfKPj7n8w0TB5xpvWjZ4dfD7RdiEZoAz/+kjJ12nyOpKA/Pd2Lc1Xe2HhF/aQiPo3j+q2Dc0feri5dXUw4pTnsYcL6uEbjuZDy/pjgFBwr9xBbks/z6eEPsvDtSd2kkfNxDOtcxvGDKK7k7kJ+g//zKY7571uY1hEe0Zo00vbtYS317M7IVVruZSaanQBZmeHjOJynjD9CdUnTQy6KysnUUvD5zR3Q9SGdu132+j2fkCZwvTOOHvsubHnVpL2O3QREcHEf3Tw+YwA4ls4HEx3qfPH/Vw3l+FrhesZrVFxv4C5GNLssdxektni9t+SCTFgM6sIGHqM5cZxp//lyHr/9Dt6MHJL1BoieSoeAW2gKEaepfo4fdHtVu8wvVgUdU8nUDq6AfKOzLWe8x/tItU25sTdMZn07GBqRUf3fKqRnQPnY7gU79WN7NnDogRvEMruRz5OnklNjC76GdXQpnHX8FyTpnj3+V1FMxov1GAnupq+zXOZZ8O8q2uRGFi/GKB/SyB/SSz6EcAvQgzIRiKId4SwCy2bpO2IJaj3z9HWhsxWU/EJyv+L6zagFgrg/ZCaCbGWO1vWOVGFlCcQLA5CZQ0Mr2G8eNnqzz3NgKnJecuRlLQG+QM3X0YQTWzjxhuFMk9kvl/LVSEnRAOeDowwDMijlguBNxmFKOlwrT5KCznTp9IZiA9sU8+Ih2UDfRMgD7a+NU97Wg4U7C3W+GS0dNTAFU6T6qDWzbagWkYU8h4AD5R7iBupeYUcYu4/AH6HN4IOWjSM2P3dgnFZ94hgG3W8z/up7jvudLsbnU6tbbG+icyJ1fwgNfU5lynGLrvQFrBGXog7IdcOwXAz+M7MO7wSUKyNn4G80XHqsDKJ8OIQoHiMHxPEEUDoloBnCrDqB9XzriKCRa1h3xX3jp55GOPWYMWm3Z9DSauiEW2+VG00SlXlizJIerNSSZUSXisHa9IJ2fjp4OnjLOYgkYhdyU/AvBS1QHe87R5VnvGDAjKcTObASYkx78Gnlp10/nKLubOKSI0dOYF4YcP+xoIZwxoDdU9+MFOwnRRFOgbeTEskiVzm7D1Yx5tdh4lVhWkkNBL6sGOyd33G7y+fF9bcM0qDg8bFBNnhxkYxsn4y2cHhHE3UmS8LxNK/W/E4evghIqBVAouaaCE2wfagAJTfYSffOn8YQMPZVwd0Jl29TE7TlFEMYTpkrGe4MQzxraYgj8+6NpdeiotZcdg3ExUQZzg6jskpk/OEOmfXfU3CBfq0d4yRsNTxUxcxaKpNt3U4lQFGehSqEfKbzwQ/Dda3JhhcCnC2WnUb2b07OLAEoddc3umkHVHKNcfOoDQqZxtF2qHsv4766OXY7iOJyfmeWFzcjE6xVv3i9moU9o2TmIdJH1+WxK9mwHBwiI2x1i9LkF1YcpvyDGZ07G8waDCMf3PqwKEmQEbCxLi5JTWM9rExWX8bCjqAHzB+wQDspb50A1xPI5VhqZCtnpxSllsjGBMFA+jobbXkc883cTufPHvFihRsKWjr5GzEl5rFBsUhyjqb2jN2BY7bpgS5zlVgNPVMrAWINZpscN7gd4QeHg53XAZbJ9kcVvS3saLwlArHWtF3Lw8ijv+2bPZtJf1bviBUksvL/EC5novdAMYnvIxyj55YmLkbzEgw66Lu9BQX3Ow7Ex9wvBZphQFXg8Dwlv9R++GnZTo7Y5Tm6Z0K4U9kEmtJ9f8q+ECHhZVvMU24DMDfvvtGCMLhx4PoYG4gUtrAFB78/PM9Jymd0SnhewPk9yNCB8wQ4B1oXNjcd2NC+H93tiF3kl1/aFiUoSo3MxlcO+41TQKGrwByX4D3h5ICm25DScPIxLZbe4toOc7gktrL516mI29IIU2gXloo4SEV/m12HYwrD1lWnPoXSSbhQzK39QGCYgMNyyItvFGT6O/OWRw77lP6jgcyASXjknY+1wMdyXly/Rhu2W0AjsCvmq/uAzYO4HfxeP4P2iYtzQLlnDhemnioxomaayiiNXNlK3FLsgAiHiOJxHjF5cIhEWOhHo4hLy1AFqTwHx4ajCOL4Ivy0rB2uRq08z0yq3FN5gZ8Hc2ETd7xaSRdFzdri+WmwZKmSr4weink4nutyrmGbbU44P6v+IKrGNuDdh8YTtHdcVpXfr3F1wG+OexHJo6yyObWzqGC23dv5AxhmfxUZU2gN2aXedaxlzXzqZPcf7XsD6FZsre69ytMEsmoOQ4ofuM+Wgggji1Aa3sscZaeCpVvtz7N7Jsx1iMRkK+0OMhIcJLrepfFg0h8G89JmFPN3BnZYDZure3ptLRc8u95tb50awiAlRv09+LDqXXqrGERFSSQJAHkkFNMBpD78cG1Pgtxa8Bxgpr1ghioy7qFt+Dxit/UhP6HWmK9VL+4NIyfl6+DO8wPpzmhvJbYZOAJY3nBS00Z8BtJkyJlWrNJPtlb/Vp/vyJ4WeIbOZQad3IORtlx6aAPgLbavDrvCz3NIppetTGjVBCwTko2FVEQnlugEd7o9dHEwdLorpbtAp47Kp/ioIsASIfl1Vd4pjscRH7a+6HCHK9kXd93skLLykx+XioCSzytKiBqMAwS/QBwROaE6Pa/AK204HRjyxJBVuJguS8HtgVn/IT03Tu4AvxGV9L447/NgJWiCVEf9+nCqameyFn/AbVU2ZE6Pn8Dm/fZS10eNfpOP0x+/RNeqnQzvgMsaRruVSqPVDRu5oEkIJtaadprmharOwO63pj9q0hJVt0B131AIeJdpNMb+gUyy+Ri2mESRiI7Yk/5OCbzT91mbGG+2fMjZZparWsyKkAS9HWdYlCiQ7HvCExiwL48b79UCU14cz+m4Q9TWrL2Z37gBARgtDiwxN3gA8OAcl58kC4oiNRTdZJgUol0rdDHBJzxS4OXIA7JpRlzDlcSTwqlRHm8x3z02K2DLXFUax3rEI/PqLtjDisZCZurwEj0mxvXLAdGgy6lW9qVG/Aku+dWVMYOM/1ujILba9bEuipjgQz+AYu/VlSxYPTikF63JCMuhIR6ikC96mxKoubtF9f1AYxPOTsSoLe+5BY1bqotm9j3AQ3bY/GiWL5mkEsLYrdB87C5qvnqPT0dwob7ougbuyPlYrOKrotAsW6oiC6FHpch0wH6dX1wk2UaKyAzGCO7Jk7PyV1qFe/faioaLMuFGYKVGGLqAnehQCsbDpOnVfcuLtcyPUnfdQLIRL0MzCBjD4MY8/79txSaB5kiOXXy7olpG85rM4M1HMyQDqjqDNFwuy0szbi6YVwNS0L5Si3WQ2tNHSb8CLsi1U8GscEPi10iIT6EJTgyTDDKowc3QK7TtM4QxFcOfCCssyjpsmtbCHIDCeVU1HMrtGRqPDG5AjbjpwyyWpjulFZzCVzdiJrRbNYzOMsw8yTFA+9cupLq2yV+6+68s0ChdRFA28z6PGOcEIKLVxsc6NFIxyPbD+BF3LBp+eXR1Lu/TfAE8OlT/wtiMgg8ht9O79H9GQgMS6S+7gs7DkJwF/11CmdnaMivEbvbeOOraNLZCfKQfphBxjmdJL6dQSGFI5gQjSvhy5kARhW81nWOQbNOJ+C2iwIexQSewiV8fqTYKR7VQnj+APwkcvDP5TF0W+e0+62VQRdAxRobwFsB76yrenz9DTkrPBGugdJLbe7dsEySXv1YxhCb8hmaa2iFZhB1DdHrgeLR8KKQFzbVy6PGTfY2iI/5Rkab2rgVT500oEwGVGMvx6cHbVu9+uxexX9pPuX0nYtvJMVHGZEDPT3TpW9shqtwCBEQYdsRQhbYUN/OaNHRcAjhBa3NG6ubKe6cTDg2JNbu/GmyOCxa6CFKweWXglsml5QQr5KMFhYl8vHKqKYKOZF0Bn7si4OQP60xLWpX7fHc/SHfuT8TLIaHnY5AuLdGx3N9H3kEmaHMAtrG5UootHpjhX7J4bmw6TCZ9ZeEe4OQUbfG0/9R8rBNvBFofGXkq6fKmMPTSWjwcD39c4d8b1gcx1xlzEb27ReGn0MD2YVr3I9GjHi3E0SqAptyroaN0pWihZKtN3FgBqr6TGZ3FhxkmQtyrh5vZxl+vZSBXC+DSar2Wcymi6KCHi0lik2KVCh3gWeWTXebDlU0F+uQ47Gk4VwpRuLRhQnYhgPbiSEQ96g3BEqVduMB77rcNB9TONSNwQx7ct8Jia0ldDK5b4yZZ4+SyMinH0mz8iI/JwkESknXGIZY78cdjiGrCBOxoxlLafJK6LPyePW61TwyHUrePRau++mds92JyFsmjuzNWeSDeTEGH7OkzYZqC+PathdxzEbJR40C7vSeZHyx1Qz54f68P8XM/MjzMLw+PDL/I2oRNWazOOoZdyMZzhaOxwzjCnovX47zJGDlkJwUpdayvYFlSqFtAt5dKs05Cdt9ro+tdl+puLK6HxZJ/db64OEMmSh0Lv0sCYcvFE8Pkce3VgSq5qq/fE9lE4k8Fcb8MASqYE8j6rrcFH63M3Hebk2HDOBbdmbVCAIe7RBXtWm/6GoarNlixPi4la1MtPCvxwm1K7JyKZb4eqUSfjvoW2cP8W5UouEXgkVHOlnWfjGayPTbOmtbdzlHzgJHP4UeI2luejw8Z5M45017wj0o4+vheDcR+FV6kMF1MFrtbobNBKzCxHpECo3Nz6NbgcciVbrfzSvs2gbUPrvdhTuXPY2daRRx/wf4zlUFLrdEyv4dRncmsKUhpfmy2hGYxXHfMBa9w3Z1Yz8eLY7QhH+RIQAOglSiUE3lplw639qmNyGfwk1e8aAL1FoQ9WVSlqrnL+tppLtsVcCCoRGTTUQMefnVdjx4AZTmft5nAKdMGMjro/RREQcrll2zQdMnIOM1rJ+4qT/+VN643t8OIuYLBsWmrx71rgB/CyKBo2xH7xuS8GE+ekylTB38w1ByT7Y0LnkB1yLky9NIq8swWCg5P1SJDWl4dU3vYcMgtzvSjFp2ixEQYLqA4iUQUVcKD/2P6Hrph1ANzw5jgNCIHDUrs7hUtkm1SWlxWJRWjZgWVw+NMy1osw8chUgQaruIfpTeB2BNmBVjGIIGeQNPnZOKvzWn7cTPCwsDvorQd4VZTbcwnYo+2Ig2DUcHa/LE98lNcWpOa+5urGZMdVTUKzRdB8IPsqL6OBxgjQu8YNvpPh/nscNlWs661cJ65POFM4dzRCH0nGOsE+8Zcip2Y5qHLB7Fm2A89ULg0nd7Z4ilPNIFfZgsbfUounfCiug7nHXe6i85QipmFZP+aQOfQRYGOtPy2IxF2XL08+w7OIWsfwh6nxJszi6nFJIffGMpKxKtscX+qpERwqqzPD7YnQEbEZg/iYEhc0ZSRAaXBAR9F6npBqTv66LIhz7AgOabjA8ycejRqluWAw9xc1MHzxFwLQlMDkWH0UF6y1ZFOgp62GBa5koZpTVtkgwQ3Lrg0r9VxQy9vkM669QJMatLS2NmSqj5P3njAUD5up06eNAN+ED6lp2Q9cMX+es1Qsp9jDO9x53gOTFo6CpFqRs45Ggs2bWvJAgMfFb9lwczd3wO0Hp+jywobEghFf+mXhMV74T7Tb0qJrqTRqSkdX7s12fAuHmwUzRbIYcvJTeHvJ37jHtCL1WsOA32kVj9eBJA+I6je5c6NjdNB+cNFBdiJFYRtTp2unp9Yf8HYjIu+/SIfRQkTjSDbAfpZd6ZL2JLIBPwCoDj/CweYMgKZ8mGf2CIj0cIwhRP2rjqefhx5KNRCum/BZ751XJ/vHGk0m5SD1rJ108hf2UC4+e8GpHkNtomc/YAPr0TUePRwxTPAddvQCoZdZ8nzVi27SwBHo5ki6ZU1ZG+l8dyM22Q+xomLCeBZzb2KbuoXjg60PdEfs4o9HOsieYpCGWt/tXltX6at9RR/mXasnkPbLseTbuWet+vWiRLCForOIdgL2MM8Eji+kYiGGR0hxLoQxUJoaxpaOrqKIniZOnK+Ax/wV2FQSoWYgTs8bleEmzo2HRUfV/SHrSM2owgfIFO90cVOJPRZOrLWKwwlclG4rqDMW0UYXmsy8M1ypwObQJg5usKyNae93v4qQzgZgS+5d3uWeetkfVs2tE866wXPS7TRRAwYx8kZdFpfk0doj+/lJr+jFbK9MoAiEScOjcYziwRGgO55sIT0bmCWJuhCGPIJ0SN/ocOdGsW5DKPer7oumSXJ4JPR+3VN7lIEMvjg6forTSHgdnbn1FmG0YpFV093458yX+NeE/BhxGcupBeqabZt4jIUiJEGaWBcFT7SoE0lrQnwFgy6NjHyR7m2ox5naHLzTojR7ggqxr4HR8jiQEyr0YudEEySxEI7eVup3Cvl3kj8h5ER00NI3n3TU7BQccy9PsNnSA2g47FTjjVue8s1bfzj9+Cm4h4ihtYGfOiPtcknGXGofRhB4/iMN20kioK6gMFIxkL1QPn3rjvoMMAABF3bXbwzNGc++BYyrPptyiplD88tddNwGei+amCf/Kbvlb8NUMM63ZHf1JvwIpcCVT+MS22V0hh2QbQ9BvOAe+qR542aDAZ97af57y8tm9FDiLxrfcjYBmUvIjBUoxOcP3rOKhf5JT9hUSf2ZlngMXBrN6tl2GqzZH1yYNjWdnuLxdcC36kmPUX9B9L/Nl9a7Y3lfwWRAecggZUaiv3yJqfWERh1PHxFb9+v7beIZz6ifV9vfirKV0T4Q/J7vVBDBqawfJnrblmWjCAhLR7Ux0yeA0nknHZUghZEXqSSWX6srp6du/ZnIUSZliQNft7Xdv1TNPdgUTdItL0cEsY6RHwqArL2GgmgXH+p2j5gGC+8zfyOtBf81ggtBGg03h5WRvLD4orjplCEC0baAP4+f4UPDqbEFGJW9cvufecfwZ/epFNrUzPKIydBRITWVNYvjw5lmkiniQMG9IplxfB1TKSIosUHw1zBsEE9ogu3b077iZGX5BGTQ5jfGpruob3T5AgGltL+4qF57tfg+w8MGNPaa4EWSJDTWCXLFocjXV3uFGcjoaJ6fB7uA5H8UuUJh7kdYnZeJW3muMw/MMGK0lApqw5WuaJLnToHVuCVvPMHzdMhmUNUcpyHhaX/mxq+4cGuHSl5rykzhkZ5yE+AvoVroTMcjdlckom+JH6eyoGrSm2fe8HojTeTHds3u1GGTpVKXcYUM+SxLWiIhqSDxGOUHEuCDRQv8IccilkgxISLI30NRyRSNvwrUyLgNMXyG29+y30lIUDqwlwO9/7PVyTFgQkKBWXtJF3rBKzRujfIYA5CMnWqSRvOnHyj0DS92QfF0Y2Z2qi9P7GGBSNXXHKMadfr6UuQuYcb0jZzjbW9gprXA+VL0/4u8I+zrjfuu8R3DbCPfKhizAMboJirc2Jxqgvtb8qbAiu8fXVnpxq92n1PiA5KOUHHUAYjfJlTQbLjVW8iHjhy4UcMnscUlh7hV531/yHxu4miCYIbd5pUrj5OI+8zIHOXID+/86Hpy+I3wBqn26G0ZRzC9fnRyrYjpPcJQdpTUUHdcK16OcIOY8Nde6gFmd06KLW0tAv7OZeyDa8WCQkqA8qEHcJS1BfY7Hm5XH4iVii1Mk8XJyZ0Gd5SA9qNoGrRctqugagstTPiZIvheHIzxSqsG30ky5sZx67ng1qoI3ao/yWqOu5hdAcu8nt2ddauL8mHl6TSc5W14OJ3ixJhtBxsNWxtSM4pR+zdSjuJMT5FUn8c5Z4bt6MR4XV7Dc4FOwcnajyYeozVYpLYUd/ohuD1Nii38owAMtP09Tpz+Xecnki1o95Eu43Yqqi1PZctzVSVzhYlX2rCivnVcOM9XvcXGDg3ix8jcv/sx6iZzR9uRMipzlI5VbLGFnr+PaEUXNczODRmzf/swdOk/DbBY8oCTYfD2mRldW9ZJjIZ4gCpJ5xAngiDSGYU3P0FJMbYKYFOjYEw+QyDrh4ed/P9SEAU8zZh0ST3McaSrKUULm2riYXqPhhAXpQU9XgyfRfdnu2a4mjyl8GfvOlG4wSZGxgOc09PqhMrgbznT4AYBMM7JgJNrhZOKc+/K4L5Y27NdSBkORzUHx6BpE/A1yWwQA3ZiAIKkb/WUXeJSiYicAAUr12Kt/T10C0oScmVbzazKE6NQ7MLB/qLkeiMMb3J84Lkh/TvS5By742SdEBt50qxjnHFtMrnMtTTGDG0MRN0gfvM/qJuxPsMkKyBKhurdKjwvXjWRLWDZqWILDNP+kds6QggTVn/CN+HJ3D5KxPrfnoLgU+Tf2i3l4MVswqjCjY2WIZZJl8I8gS4s33a6VRt50+FLxcre2Kz4x9rzPA8b6BewoAN7q/X8XQ7bwmjaQqkw3UX8Ftx6ZO+3FYayNPlKxJcSOLJHPDVM1o9mNVoqzCKtQOeUFm8JDhyABsgJoALYIvZCnaA/v6v1C496/HdjVpkxaF+PjJW+DyoJliuD2YzGhzRgRah407F0dFbMclPlyQDZNSBjNNa4h09dCkwVK1T33C5r+n4B3Oa6jw7Yvp+iZbWeECua4RMaQnUhQiPqcmwGgBkD6D7OOGV+of/cJzXIH+JBhVFi23HnocSCEwfm5AzqGG/bud10rmD+gQ1oswzw3vdMP1c2QTDdsgswXKGJMjK3feX5et/PFBuBLiTTDq4/xl9Z7XkWXxrHbTSDtOc97jfW+7vT5mu/AcjYi6p9QSCY1aq4eEu7clGWFjfJiVar/EizOFguhxGEKOPLX0sYYbundhIno6Jh+ueFYzP6Xj7nx8rJuMwgLzxhhmsWEb3G2AhKWYVnHCJJLwurBTAWCFD3hbTd92ZCqzE5IbC5xI5MDJ3MNdQOClOgFHeuUHteHgwUojx+8Qr6hkbt+TJAWEEsyZ1Xt9uF5SSGVy1kwCIam4mNhahPwYpt3DWchZWjeVTQtL3HujYNfT3tifn/iCpSjXDYy9Hh+CWiYIQSpQWl4uBO1y8/L6E16Rv81zeWwmK20JJ5hGEoAXDmPM6Gyu6FyWDyvJx1zKwhxJJJk0AWH7UDE5Sx1O6nAeXjiT3YV8EWJTIwyDxPuJEnp8xA15+EVMCSl6H5OI+o3SEYf61HWrN/FbxxunfLGm+gjU0GN3LMHCWDvoFDj5rfs+BhuIqhhR3f5EzkCt6ckS0WkLCAG67z51dzdXVXTx/VzMfFibYHw/SoeI0pG+uhY5IYqUtINP280BBY5lRjFa846/tvsfpby8SLHPjSdhE8uMJ4Qz86oiBS7Z9CTG7TC+UrrqWyt1qQ5wL0dXGznwXUpl8/uDqYIIrsq98cFE8mtEGEmmFcVP2BpsxcCJqzy8P0npppG5x1dV6sY4fGAGQU8aTo9C+3n1zOrdhOqiKTe8C6rPrJgLp1rxj4ngas10Ss3s+FjBAksvaNoykCFu7dAhAfoiNA3AhHdARID1mMAOGIf5rG+sKGnFUXF8K2eQWsTvigfMrFtfxEFdqpf5JXXb5XN/hBWnUAYwefOgXZRjgUtUQTYaE3lhGhfO3qD2rf++C8+rMmRq5KqzF9AMjNiOt4zCuAmGrX8hU7W5GX2QhKqzXqCnmpZJ99fwGCxYOEnjF3PrxsxqNtPt0Vm3hkvvO9u6T8SaTsp/m8kBMGUWKx2fcRTTAJW9qgfg8X3Nqx+prv+srP1/fTD4vwxibeZYBnd3xQK1OvPH4wq0OY4+9vD/yWIuQkwnlnGdF+Ht1CJvpF/YMcgJj670ONup0uixjDq8VFI7i8pNb4gD6d3uk35ta0bVhMmV0OSXzsh5DCzCSU+HUIYc7IrLOgikSmPiVcmh7GhsT32271l7mh7gexaETxnaxik2PxzKtesoB7pu64z6PRoZkxvnGvP1UetljnpsYyC2TyqALp/QXbce5WcgsLZOwqSgbyNqwuVVidgLeZM3EENr1wGV8bOejvFkdPJWy2q4tKHqhq+6OBdPAr5C7SiY7CuucRxTWXx93+AnerJwY0xjGgJ+lkZzY18mZgY9oghqytdXOrzbw+hCyydd3GomQWQ3hPFD3WBH/vEiGfBGmEwYjykU4001JzyJdOQUP6oKkgz+1lxAIHJbwLr0RmunEyLG9oLVzldD3K29aBC7WO9LLa0CewGYud7hB9wDhjQuFTHx8hp8j+FZuLr5RvGgDsw+5itMZjQyy/HIlsP0SKMEtwEmi7x//COajPUIX9LUa/bvqt5shf+dPLVzGL24GEdXnQjoPB4r9VG2Z+uDXRVDpv/LQ7BYwmuWCFj3Y3G+W83SqrO2cISNixRSS8L8SAea2+faHhufLBZeLat0g8NCqc1M9iZpJbo5keVvEq8KwErwC9iizLta6Z2MVFptptu5lbf1mAWiq2z48FqR2ZHJ4SlXL/V2ocRqHK6OG6SS2eHT789umn6jbc/OZOz17yAl0eKqe8sHajr9LFTAVGMbcEOY3Mwmy139a//GAYyhu2P3QXh2WfbKOX0WOJ1PYNzteVelHM2BGRF/1FCsMefbSAIykQVPUsM1jrqz45IuLH9JdiweG/2mNn/EVMWR8qcUEhTw4P6OzAzuB28OT6imDyd9ZnfC7tkkf3OY00a8n+fY996DK1Mchi5nMfwQYTnW5G8i6SyZtO6ArSb3RMQo4ATo/WWgc5jis75Ej4M+kAs/amIv3E7+IKALyH79F1p0hO8lgCu3k2b11Zvkc9Sa0DdTKWBPcbqUNMn9Fa9SCsVOjGk0lKG7f1r5qLHCTnNpAeysu7nfuS2xQeRjyoHOviXOMJHXTIRfLQjacfvBFrrYCgUo9R7rD22Nx+HwGczu3XnIjNV+sP30W34h3PQs60uJ5pZo/VfwRiqwKB7MPBPC9q8OEnPn1RG4liLj3ewsxeK8SdllpOyFHgm+g6/mxw3/y20X8F57Y8Lrscuwm+Hyjz0xWhz49SFn+oBrlqADVBAg5ytFyHkTQeT3IYHPAwlE4QD/Ljvn5uAs5ljhVn7GF2oDHXSGbA5sgD8nDXHcuxZhfZpqSaowpeGHVTqcXuk2PROHK77D9WPx08nde2QhKK6aqOu5DZqUNRS3GHDTLlZN1IBy3jmWa3DkR1MxDawSGKISia/gKlRwh8+W3uT9Kypnx1nfHIsq3DjHucrzA2/I+3GA3t6nhFbsL3fISCfPdxnbBGOHnFsZE8erZHKWtLWkATqsfjilhMIjjPjvpgyVXUFJIXgA7OjJ05EphG9urkfrn0SGqw+RB9xm9xO+trmkcBdIL3M480GXk1czxs78L7SPJXJofSqe9z4d2HRopuMR2R85p86M3NS29oOPhuhvhlZuurJtS9oupdJeIJeCAga+UeYBc8GfeLnpQbuGizgU3V7XGR2sZ0PUsjDNnbNRhwPxb+anLYuIgCy+McELYGMSRA7zbfdeTTv+4ecAxnnalcMI4nrV8W5ZH7Wdmawe6CvhQ7QUHwQauwegcpkWkz6Pb07ZH7uE9dGhtAyIdnbBBjdmLxO2BpvPQSaq0UP4RhkyqnUu2xyXx5Rb4NOitrdfiZJWPc4/OIjt/8OZIyYP71Epg9c7NotIYe6eag2xNBO4Hvq0ADH9FkBDkEbMDQZnD9iNZ7YCOwUl57AZFl9KiwphgBBo4yMZ+7L1iJYkyeO0TIAYviSEgZmc8ujaGJS0aa8cvaayG22vHc9c6rHoKUdPBNUYgO+8At1bsYhBt701pAmEHEJeMXdYJXHmH+FrLJPxqyxF3VPn4QcLRPmQjYv2VJmyfofySvrA5WnrCduDzqdnp6On+VpRSt/CE4a1Fsy4aLDhR95ViWkEvYXTIytjdNvn0674MFbuz34Jwq7nsHK+/RP7bnQGyLZS68Wz3Obz7jVnVtZ+AuyBOMjP87x6KG7FIMIpmGL9jjBa76rRW3oZdq4G470oNhrR6WzlvuzjL/b7vsTBqbSyNuauamI8u/K4WnuKft21b42WcVydDNtPkyqlnWsAP5P6NcosadTy/gLOfOOEe2Pwgsh68CHdcZ0AV6c8l5UaBpuuAMIMAOQ1TCAP3aTrgFzuo9xJX4k+bb732AX2obn8BDlvWMC2TER5JyPsKKimw4enubxxsPY/vgn7cFR6GrkqaH6ZHhP5IsvbSECveFdOHOVOLKnl/CI/qdJqHdmHMsOZGyXkcrcitk6dG2xYnjRyU4QHeu9CC8sWw3hvvg8TLjZMwxBiM9osQ9p8s9rKJyeKg6f533BHdSDmtx3FWNTrFe8JPkU31I08AX9aTVSTwfcRk8Z9ZkTb74Iwbm/BO5UT53ty+MieKv/IZ/2IlRavdr4Pire0M4MU9p4y6bKI5h6vDwrf/rhTbCy3PPwef6Y93oeZeDsCvCOGvXksQwDNfKH9Rd4FfXEfDXMoDX/uqTf6wQF2tvoAeJpVT56xo1RlIozwZCol5ql+sgGdx6fPyMmio/BQnR3YsWPNuIjDt7hw1/JNEYx6umuPfxaL/3WKzLrX9FM7TXhsBB/SjfvSzvN7DtatXtoCBUvrBeWN13HMMVU+GDV8bpTDdr8E5Fy6KPUu7LRkVdI+ifUZAPxm1BPIFF4KZrG8EW5EM8qWnQEB9YwydMF7DsJRdsz62dxSaca9tv25v+GUa305qLxkgpF3z+7DaGz1UiOK++8XSXklNnKhuEi2DSbvXqsxFk30LbsIIbJOtY+bPTb1zTUmzu4IO02c0cxCsMb6UIx0zBnxLzXFK90sMrD6eGrmIopLk2B28WesdIwV+ZyGe3xpzejG/kdLPap3jQ1QaKVbZ5pR8M+OkXGFDZFtOPzxe0wda+lEZdRhq13pPcaz3iIhfPzfhx+R9I+ocMgta1EtVrlxAF+hpZ49+ni0WzI9ExfdHGTVUWb+3TOjbvJ8du//sShI1d9pdqsOqAcE94fMrysmnr8UcNujOX+0URsw8B7gVPeSP0vw5vyDzYIyxgmecxyh8bgl5fOTf4qlgoxTk4QChPjyP2p+T+2EgqCDtp5JuYvpb6lcZ489aPGkeMQRxSVgLpC6/yzeu9oV4+pcCzjD6OryATN6xF/a0oMRG11eZRds7q+bCVxllDpeOUP9tHtsBLofWJEsjiLaK3vjy/8TGZkdGSPwZHOkC1L59n0YIeDRCajlecM3yc50VI1D7mU5/3Zicb/3rrAiPzziOlzYufTjQppBObr/7Oy8pb8p2yBKeP62e+9+pL28vd5ZybPjaRa9M/7vIbz+RabNIdoioZbGhFFmWzIFANvJuwW20/dL+wpBCRv0KFZhbLcP72w01vQfulGv3lN1KBi7r+9mNi7+AsIk8TVa+236oDsWxn9p6Ca7NMjNUHHHtfPg76bH3KTzUXEpzQ/M1p2fxYhiLb7bKdi+I/jxBeI6+XXsOv7g3uKS+XcztQPecRY0mehfMf5rTFsft5RXRwE4cKC18damcPV57QEAeAodCEERWYlk36P9nsWa0Ot+h5318evufb1R8/a3sM9wGxSv31VpwQtQqh4jwyGX+8b1cPPXRKH6bEzRyjLpRCH2dcEQ4hY25dXkUoHEglayW0lcQ9qyften6KO+MSHGIEepPX+zlaGml098XD87XJ6tDAugertf5OLe28kl6MlXaFKNtZnWHBNnbXsxzMiJE/qbGa3Hp2a1PKcHUdfDWW0nr7E3l1Y6lD+eWLTx5tzSfJ6BOc6lTYv7+jfOY/xYmSMDQ3ouBRKNmI0n+ofy9J1b1ecgx9WOk+RwevMIcphi4TFmMNz5MKHebmuPKJvdCg3Qvqb+rlo+dHFq9132bm+lj+r7Ktx9B95ofLMDaWBy2zZhze1k73dupO7N/AEgUV+D3yk8+aJZOLvJouwwrt2pkRdrYSPdTTN8ttkCqemzwQoIVG/+mfDp4JF3EvwyRibA0QtQcaaIG9d3Cu2C5zZCHdNzVMFRt8dJaZTPYuHHf6z0lpyx30SGAyZP2RwghdTSgOaoCuUI8mZ1IA+liI7bbNjT8KU3EFR0ElL1GQ8uRaT/8i/svFZHY+FVaeyuEMZ/99sY0f/kwPraCKep5oD2sA0Z6Ir3AiOESvTnZdJJM6YaCq/jc0j/Kriij/hyjq0heYpYe1/DNndjmXp3/rGMVmPFPlAnNzvSl+5RI3zy9ZWf9RtgyJ1p69omWzVB8qEyey5q9dCMcTuTG/nOm4vVD44mkvNWgrof3IOW++1Rn0paAZSGDD6YLeq2DA7hBcv98BQOaq6ySxTR6F50llWvvRasaUB9/Lg08+2mEzKTmmpZI5TlwXqTcd+t5Lsbq8+e9nhRI7m3t9MEX8Mps3aMiapidRAZudA2em17RSKahGzzX1OPROSU/Lpy/4gdh15WTL5CnI6nG7j2ftrp8cHVp6zeHXIHcNgd0uXfkfWCeGbT/qxRyW5CCsylaYDSthdWB6gO/Yl3Ip9OkfTwFR94p5P3ii6MjygLPgHaiimWX1Foyxl58WtonynRfDOlmxF9G26ie/ZZkU6QmgtyW/24BCH7Ady3nGvLL4/+vMyoVv42723pqn24bNqcec4OA8J6XjWMtV4H5gT9Mz/qYbUpXtDfkvm6J/RIahFwx/7cNqR5YSem2Xmm1m/XKGvcT868tz3y+a5KMw3OHt4i9IQqziRmdb5vH97RXTQ4GdRWDAv2YHJUcOnQ/HCWBelBNBDVkMvUux+w8FwiIRhg5evkPcJ3cOeAKApoQIDAQfZE81NfPPUbY/yy2tC8PRj/tvioU93W3CH2/QTntcZXFX7V7M3s4R+sFv0Ptg+HyZpHwI6+cwlczkcPnpFnY8jNVpB7n8mWsqNmBYnMQfkxd9wW8Jv9e8kAnNSwPtVQavhpWDr8Rbtr52ZdvQvUBK3fXjG8iac9PR395NPgVveXzWUL2ZMG2sCtpdCTWtiC9Yrx9G/unOetnqpRa6PsOvSa1GW86Q5rJtsMiZPVh+BYEt4ytpchOcQdCDslyi7XHoYPpEjrKHpX2dmfbFVbuI34RP/2WGa5HUbjiT0o1tKSOfCtEkxP1d642U5Em9+GPvqhh72/K8rVg276SRFmOM+sWVcwxheVzZ6X86DC7hHssXp6UdKlpI3u55Sdn10uK1rp+kwhqxn4tE1joDXP9gL6gjzz503xMj0KFRJ8g/SctT1JJ3p7Tvz3N7VLOnYWDPulMHeSfAHLlBnan41M3zbt5gELqWaMGoCQSJkQbo04nfXVHs9XfkjUM+T3/JRxiC3XC6f2voK3PSDEfy+ehjTN260jf99hVOwog9Odt4eqD+7roWPl6ahva7Z7B+TSkAGC1X2W/05Pa8TUMb5PBHz/hfzNB2V77j39koRcz+BMcwsOi6W4hIt89bjOMP/j89Y2r/kzcvw83wZ+BnPodw6wTfUeSYA75HAJ+33kIKsdZWz8r0GSSzNUczmQFM/pmzOmysfQedWJKidzJsbH+R2U/LjlcZo8MI/Ituxzacsn2CTt8RDk4v2EUsbRvrFIDU1ABsDiBOYj7hPnA9/lvsRyCIHtrEfbJJthn9i/6m87+wqpvcX9H+xrH/lvx1xyonXav9qJZ/QiJx+CPNP5n/dbnZ/Yj9bCanIW2j2u31cz8GwCvs8pLFk4n1sI/Fet+GgMVQSqb6ZFAn16sETSs3wsmqeYs+vFf9nb09nf0IT9DRrUQHAM8F4eRbnfSBxztXr/wVW2flUfLUzE3CmLwGqfklA20a3lJ/f+YFdNhP2JM8vAtihUsv3715QZ/yvoTL9AlHyq+ueHR8DJQRl2Oo2ApgZUmaAN0y5hNnascYJoC4OhcIrDkInSHYJl6ZQINKF5iXGIGhMMcBmlNprMF/c+IohdhrF1RHN3n70yPfsjR9ZIAtl7CZBkXrqzMgWDNVb2yNC87Zd/41pY0uCJcT0P+jInAGnUPrH4MtY1I9kUZmPLgPu0MflsywnT8/Ks6cyfKRFfItjZPgwyfz+on3+O/t/bneJ9Kpfe9t0g9mxsgFIgFhW9JhsT7a/mJAIHwVR+jw+/xtzKNJ1aUnF62hOnFOKK3URy7vq78O44TQ8roaOlCmG4U6dKIOAt2/XNxZ9DopJsStrvFB/+ZhVGLycRRE5OTWlC+62lKNaPOC/bYgrlmjErOa3n67COVKgOnuIVwMljT3mwx1f7wtGQYpN/lxSVy10Jj+H1jVpPwHMEK7EVo/q6wDYKRtEQFbO/zPfe2CKMmKqumGadmO6/lBGMVJmuVFWdVN2/XDOM3Luu3Hed3P+/31vFIYPbL67Xk4nVgo10l6N4CsF3G4Xn92b3a5hc9XuAXAvXFvd7lD2c3PEVWj0fT3vv+gQKj+lHCGvDg9/vCRBh12ibJJ8u3EsacGWkoDzJJvdobmeG+1A88ESIyCxIf3zYf37YfnHTJ+nrRyJW0Pl/p4Wd1kBXe1qM4NaZcSZdK3BJzbD1YmbvmtNnIeVSsKwBMIHu3C+Si1JYX+aD29jRZVYQHFE5vy2ptztib+Mr17I7eQkJ/arYTWAz1uSWZm9v0D5wmppQ5Xf1jtRLPZQ5WF1aXOwSxDip4/jYdsEqjosDY8Xs1OonTtfDXMwyuJkaF1m8uU1c84fPTtrIIpSu5lH7Vd7T7TI2uxBnfjyUQt6JzTxa3vGsAleGi0rhfJnIwcF/th3lsIiNm7nZlxOgUw0BJNNl9D0ZW4dTKWiX5eHx3Yz5i9HL+DZ5SszxjxkuTH/epW7dk/xp2TQQcZMS4zTMMvMPnLzCy0Exuz8a/K4J+F5hHVw8Tk3vrHZ0j1RPXWT2girwtyusZITeDc7iNSXeFl9pSAwm9wVEtVIFZnSKkss/6M1/4ToPsyTjs1PsN2WistG5Qba123r2X1VnsZCV2VXC4PCYpHNJ7L68HSbWCWUIUIx8R4VW9cdmwBbDr2ZDlu7yIhhHaqqaU6MHod00x7reE+SsHktdOAnwGHdP1jCv99lk8AHm3W8HNhVRv2m8rp79ckF4cECs5XWu18aP3aUvGoVAxtDqsuiySl28iOTWc6DzAjGDh2vIUWnETdphw7mVEQJxHBwe7pYTgrYKVOIzd29upS4b5Wn+dDGOXWuIW0mXARZ/VFPkJTApCQAIxe6iSosyGo66t9EkGigjMWlu+0V78Qi7Q5WL3X3uwCELoMbH44h7ct7QEH6o/5gGpZYs1IYF7j0y8cQ69goSo+h33eBSdJu6mLGXfJloiXJwQa16+w4eA5q3jhxkHBTwUJsL69/W704vj/1wMD9OvowtTe3Vny6XjgPqeE1E5V8UVHMwyxCc5I9L7muwJ1GrnWt+1BT1yCMxLPf48C+WEmSMPV/MHDQQYYo24jWy14gLusSFKnkWv8J7v9Qv6pLIF4gMyAWEYwwo0q7wQwS+IZCYnSc9oYHvguwB/gzaYNk/eplwAkJAADdU5uCG109m1KNCmj6mVHcAvpyVrS6cr2ow6sl6TUiSn7TYyIlJls4ClFYcpM1l/O3HwCdGJA39824SMC1c8HwtrpXhJe6TSMDcx5+lV2/qWWQE45pZXKJ5FYPj5jFkSzASNe/F7/w0+AeQ1tBAtCnT3wQZdharHanVBNWFO3RBw4BHhiwEknsf9JMoIyAicrStdPaKDLNK00R72lSnCmKvo//33hx9SC82Id4pdFgrLbNOwGr8L/UwG91EG27WJUNe2imWmcUH/364ZpBOkki37Tuw1nhi8TVswA4zHp/izkgSyZBCO0npos7YVKqPXmtL9eil0z2Qs6F3SUi0qeVA1BwF3WMYEArXBcifRceOQOQL8DoOJxa5qvAF5BVcMI3XTSADtvP5d2DklvCUy9kKHcvRZPl8JT3oDuhhuC3jL9F0idMXck445HvUzrWXW+82jxbx/Jf2dbGMER+Jqx/eDy9F/kcT8zELB94yIbIMlPuaLAOS5jRCD2MJV1DeRT1pODmQYngLITOmXo7L+DEKPYfIs6Z7IIMkW486iabX+rv9GxZuGXmEUgxHTpgOywZXCqJrHs1+0vPo11i36bDgsGW5H9up1CukAGKpPryZ/5Y5T5/7AJRrmgCTc6TRDAjnzdWywWSLcXC06iHjkxJZQ4472sack7YZjRi6teVB2BrdqPJsyAbT+wsTN+t+q1uFbG7aoVq6oRGMSEf84YEduai5VdaeJYKD7DNjndyvF1P6XYRwVQdxWh3XQbBu/3bgmbLKRowGY6TA2Z3XV4e9YQI4JT/8a0gaDFzcPDznLmFjsQ/60xt3JdqLZWaTOpSeq/7FTNKDKci2BesogYcoi35plHnHs7oWR6OfqHjUnWLJRKsowLuwILs0OILHP54l4On6kc8R4qWJ6bFJT1QCIz8r1Y5hkDJIEdBgvbWNyG6ZO6XWf6+4tkwenW46CILMt1q6//DJt7xNG0Z2CIAq5A+e35U8oW4t+31uhEUsvJgsPvl8aCT48qpoFAtTaS2Nk83LSLe181Uo16fC/HNO+zYDIGvDhH9Op+j2NkPvmsn+gzKwGVf1D8yxZfKjISx4Ko8Z6fGwSNeh6beH7towyMRQKY9kAGHb/F2iR2VNkUy2EQoKiOyzOHgWBjz/VXz8ffyn2LBsw97BTAQ4/NEK4kzJ36BaEENtCuKz2Bqt0OMqfREgD3Ovyji9oXuo+74bvgJOz57Xf33jvM34kfbIlxkV9xilAyyabhpF3gwizObhiDzPMjn9q2YJd43ros+vk4bacK","base64")).toString()),A)},42357:e=>{"use strict";e.exports=require("assert")},64293:e=>{"use strict";e.exports=require("buffer")},63129:e=>{"use strict";e.exports=require("child_process")},27619:e=>{"use strict";e.exports=require("constants")},76417:e=>{"use strict";e.exports=require("crypto")},40881:e=>{"use strict";e.exports=require("dns")},28614:e=>{"use strict";e.exports=require("events")},35747:e=>{"use strict";e.exports=require("fs")},98605:e=>{"use strict";e.exports=require("http")},97565:e=>{"use strict";e.exports=require("http2")},57211:e=>{"use strict";e.exports=require("https")},32282:e=>{"use strict";e.exports=require("module")},11631:e=>{"use strict";e.exports=require("net")},12087:e=>{"use strict";e.exports=require("os")},85622:e=>{"use strict";e.exports=require("path")},71191:e=>{"use strict";e.exports=require("querystring")},51058:e=>{"use strict";e.exports=require("readline")},92413:e=>{"use strict";e.exports=require("stream")},24304:e=>{"use strict";e.exports=require("string_decoder")},4016:e=>{"use strict";e.exports=require("tls")},33867:e=>{"use strict";e.exports=require("tty")},78835:e=>{"use strict";e.exports=require("url")},31669:e=>{"use strict";e.exports=require("util")},78761:e=>{"use strict";e.exports=require("zlib")}},t={};function r(A){if(t[A])return t[A].exports;var n=t[A]={id:A,loaded:!1,exports:{}};return e[A].call(n.exports,n,n.exports,r),n.loaded=!0,n.exports}return r.c=t,r.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},r.t=function(e,t){if(1&t&&(e=this(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var A=Object.create(null);r.r(A);var n={};if(2&t&&"object"==typeof e&&e)for(const t in e)n[t]=()=>e[t];return n.default=()=>e,r.d(A,n),A},r.d=(e,t)=>{for(var A in t)r.o(t,A)&&!r.o(e,A)&&Object.defineProperty(e,A,{enumerable:!0,get:t[A]})},r.hmd=e=>((e=Object.create(e)).children||(e.children=[]),Object.defineProperty(e,"exports",{enumerable:!0,set:()=>{throw new Error("ES Modules may not assign module.exports or exports.*, Use ESM export syntax, instead: "+e.id)}}),e),r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),r(43418)})(); \ No newline at end of file diff --git a/tests/functional/.yarnrc.yml b/tests/functional/.yarnrc.yml new file mode 100644 index 0000000000..b758837b73 --- /dev/null +++ b/tests/functional/.yarnrc.yml @@ -0,0 +1 @@ +yarnPath: ".yarn/releases/yarn-berry.cjs" diff --git a/tests/functional/BotFramework-FunctionalTests.ruleset b/tests/functional/BotFramework-FunctionalTests.ruleset new file mode 100644 index 0000000000..08a5ec4ab9 --- /dev/null +++ b/tests/functional/BotFramework-FunctionalTests.ruleset @@ -0,0 +1,198 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/functional/Bots/.gitignore b/tests/functional/Bots/.gitignore new file mode 100644 index 0000000000..6e87fc983e --- /dev/null +++ b/tests/functional/Bots/.gitignore @@ -0,0 +1,2 @@ +# Exclude ComposerDialogs folder created for local deployment +**/azurewebapp/ComposerDialogs \ No newline at end of file diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/AdapterWithErrorHandler.cs b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/AdapterWithErrorHandler.cs new file mode 100644 index 0000000000..091f3062ab --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/AdapterWithErrorHandler.cs @@ -0,0 +1,155 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Bot.Builder.Integration.AspNet.Core.Skills; +using Microsoft.Bot.Builder.Skills; +using Microsoft.Bot.Builder.TraceExtensions; +using Microsoft.Bot.Connector.Authentication; +using Microsoft.Bot.Schema; +using Microsoft.BotFrameworkFunctionalTests.SimpleHostBot21.Bots; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace Microsoft.BotFrameworkFunctionalTests.SimpleHostBot21 +{ + public class AdapterWithErrorHandler : BotFrameworkHttpAdapter + { + private readonly ConversationState _conversationState; + private readonly IConfiguration _configuration; + private readonly ILogger _logger; + private readonly SkillHttpClient _skillClient; + private readonly SkillsConfiguration _skillsConfig; + + /// + /// Initializes a new instance of the class to handle errors. + /// + /// The configuration properties. + /// An instance of a logger. + /// A state management object for the conversation. + /// The HTTP client for the skills. + /// The skills configuration. + public AdapterWithErrorHandler(IConfiguration configuration, ILogger logger, ConversationState conversationState = null, SkillHttpClient skillClient = null, SkillsConfiguration skillsConfig = null) + : base(configuration, logger) + { + _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); + _conversationState = conversationState; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _skillClient = skillClient; + _skillsConfig = skillsConfig; + + OnTurnError = HandleTurnErrorAsync; + } + + /// + /// Handles the error by sending a message to the user and ending the conversation with the skill. + /// + /// Context for the current turn of conversation. + /// The handled exception. + /// A representing the result of the asynchronous operation. + private async Task HandleTurnErrorAsync(ITurnContext turnContext, Exception exception) + { + // Log any leaked exception from the application. + _logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}"); + + await SendErrorMessageAsync(turnContext, exception, default); + await EndSkillConversationAsync(turnContext, default); + await ClearConversationStateAsync(turnContext, default); + } + + /// + /// Sends an error message to the user and a trace activity to be displayed in the Bot Framework Emulator. + /// + /// Context for the current turn of conversation. + /// The exception to be sent in the message. + /// CancellationToken propagates notifications that operations should be cancelled. + /// A representing the result of the asynchronous operation. + private async Task SendErrorMessageAsync(ITurnContext turnContext, Exception exception, CancellationToken cancellationToken) + { + try + { + // Send a message to the user + var errorMessageText = "The bot encountered an error or bug."; + var errorMessage = MessageFactory.Text(errorMessageText, errorMessageText, InputHints.IgnoringInput); + errorMessage.Value = exception; + await turnContext.SendActivityAsync(errorMessage, cancellationToken); + + await turnContext.SendActivityAsync($"Exception: {exception.Message}"); + await turnContext.SendActivityAsync(exception.ToString()); + + errorMessageText = "To continue to run this bot, please fix the bot source code."; + errorMessage = MessageFactory.Text(errorMessageText, errorMessageText, InputHints.ExpectingInput); + await turnContext.SendActivityAsync(errorMessage, cancellationToken); + + // Send a trace activity, which will be displayed in the Bot Framework Emulator + await turnContext.TraceActivityAsync("OnTurnError Trace", exception.ToString(), "https://www.botframework.com/schemas/error", "TurnError", cancellationToken); + } + catch (Exception ex) + { + _logger.LogError(ex, $"Exception caught in SendErrorMessageAsync : {ex}"); + } + } + + /// + /// Informs to the active skill that the conversation is ended so that it has a chance to clean up. + /// + /// Context for the current turn of conversation. + /// CancellationToken propagates notifications that operations should be cancelled. + /// A representing the result of the asynchronous operation. + private async Task EndSkillConversationAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + if (_conversationState == null || _skillClient == null || _skillsConfig == null) + { + return; + } + + try + { + // Note: ActiveSkillPropertyName is set by the HostBot while messages are being + // forwarded to a Skill. + var activeSkill = await _conversationState.CreateProperty(HostBot.ActiveSkillPropertyName).GetAsync(turnContext, () => null, cancellationToken); + if (activeSkill != null) + { + var botId = _configuration.GetSection(MicrosoftAppCredentials.MicrosoftAppIdKey)?.Value; + + var endOfConversation = Activity.CreateEndOfConversationActivity(); + endOfConversation.Code = "HostSkillError"; + endOfConversation.ApplyConversationReference(turnContext.Activity.GetConversationReference(), true); + + await _conversationState.SaveChangesAsync(turnContext, true, cancellationToken); + await _skillClient.PostActivityAsync(botId, activeSkill, _skillsConfig.SkillHostEndpoint, (Activity)endOfConversation, cancellationToken); + } + } + catch (Exception ex) + { + _logger.LogError(ex, $"Exception caught on attempting to send EndOfConversation : {ex}"); + } + } + + /// + /// Deletes the conversationState for the current conversation to prevent the bot from getting stuck in an error-loop caused by being in a bad state. + /// ConversationState should be thought of as similar to "cookie-state" in a Web pages. + /// + /// Context for the current turn of conversation. + /// CancellationToken propagates notifications that operations should be cancelled. + /// A representing the result of the asynchronous operation. + private async Task ClearConversationStateAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + if (_conversationState != null) + { + try + { + await _conversationState.DeleteAsync(turnContext, cancellationToken); + } + catch (Exception ex) + { + _logger.LogError(ex, $"Exception caught on attempting to Delete ConversationState : {ex}"); + } + } + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/Authentication/AllowedSkillsClaimsValidator.cs b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/Authentication/AllowedSkillsClaimsValidator.cs new file mode 100644 index 0000000000..31ec04cb31 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/Authentication/AllowedSkillsClaimsValidator.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.Bot.Connector.Authentication; + +namespace Microsoft.BotFrameworkFunctionalTests.SimpleHostBot21.Authentication +{ + /// + /// Sample claims validator that loads an allowed list from configuration if present + /// and checks that responses are coming from configured skills. + /// + public class AllowedSkillsClaimsValidator : ClaimsValidator + { + private readonly List _allowedSkills; + + /// + /// Initializes a new instance of the class. + /// Loads the appIds for the configured skills. Only allows responses from skills it has configured. + /// + /// The list of configured skills. + public AllowedSkillsClaimsValidator(SkillsConfiguration skillsConfig) + { + if (skillsConfig == null) + { + throw new ArgumentNullException(nameof(skillsConfig)); + } + + _allowedSkills = (from skill in skillsConfig.Skills.Values select skill.AppId).ToList(); + } + + /// + /// Checks that the appId claim in the skill request is in the list of skills configured for this bot. + /// + /// The list of claims to validate. + /// A task that represents the work queued to execute. + public override Task ValidateClaimsAsync(IList claims) + { + if (SkillValidation.IsSkillClaim(claims)) + { + var appId = JwtTokenValidation.GetAppIdFromClaims(claims); + if (!_allowedSkills.Contains(appId)) + { + throw new UnauthorizedAccessException($"Received a request from an application with an appID of \"{appId}\". To enable requests from this skill, add the skill to your configuration file."); + } + } + + return Task.CompletedTask; + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/Bots/HostBot.cs b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/Bots/HostBot.cs new file mode 100644 index 0000000000..79d4c150a7 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/Bots/HostBot.cs @@ -0,0 +1,241 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Builder.Integration.AspNet.Core.Skills; +using Microsoft.Bot.Builder.Skills; +using Microsoft.Bot.Connector.Authentication; +using Microsoft.Bot.Schema; +using Microsoft.BotFrameworkFunctionalTests.SimpleHostBot21.Dialogs; +using Microsoft.Extensions.Configuration; +using Newtonsoft.Json; + +namespace Microsoft.BotFrameworkFunctionalTests.SimpleHostBot21.Bots +{ + public class HostBot : ActivityHandler + { + public const string DeliveryModePropertyName = "deliveryModeProperty"; + public const string ActiveSkillPropertyName = "activeSkillProperty"; + + private readonly IStatePropertyAccessor _deliveryModeProperty; + private readonly IStatePropertyAccessor _activeSkillProperty; + private readonly IStatePropertyAccessor _dialogStateProperty; + private readonly string _botId; + private readonly ConversationState _conversationState; + private readonly SkillHttpClient _skillClient; + private readonly SkillsConfiguration _skillsConfig; + private readonly Dialog _dialog; + + /// + /// Initializes a new instance of the class. + /// + /// A state management object for the conversation. + /// The skills configuration. + /// The HTTP client for the skills. + /// The configuration properties. + /// The dialog to use. + public HostBot(ConversationState conversationState, SkillsConfiguration skillsConfig, SkillHttpClient skillClient, IConfiguration configuration, SetupDialog dialog) + { + _conversationState = conversationState ?? throw new ArgumentNullException(nameof(conversationState)); + _skillsConfig = skillsConfig ?? throw new ArgumentNullException(nameof(skillsConfig)); + _skillClient = skillClient ?? throw new ArgumentNullException(nameof(skillClient)); + _dialog = dialog ?? throw new ArgumentNullException(nameof(dialog)); + _dialogStateProperty = _conversationState.CreateProperty("DialogState"); + if (configuration == null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + _botId = configuration.GetSection(MicrosoftAppCredentials.MicrosoftAppIdKey)?.Value; + + // Create state properties to track the delivery mode and active skill. + _deliveryModeProperty = conversationState.CreateProperty(DeliveryModePropertyName); + _activeSkillProperty = conversationState.CreateProperty(ActiveSkillPropertyName); + } + + /// + public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) + { + // Forward all activities except EndOfConversation to the active skill. + if (turnContext.Activity.Type != ActivityTypes.EndOfConversation) + { + // Try to get the active skill + var activeSkill = await _activeSkillProperty.GetAsync(turnContext, () => null, cancellationToken); + + if (activeSkill != null) + { + var deliveryMode = await _deliveryModeProperty.GetAsync(turnContext, () => null, cancellationToken); + + // Send the activity to the skill + await SendToSkillAsync(turnContext, deliveryMode, activeSkill, cancellationToken); + return; + } + } + + await base.OnTurnAsync(turnContext, cancellationToken); + + // Save any state changes that might have occurred during the turn. + await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken); + } + + /// + /// Processes a message activity. + /// + /// Context for the current turn of conversation. + /// CancellationToken propagates notifications that operations should be cancelled. + /// A representing the result of the asynchronous operation. + protected override async Task OnMessageActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + if (_skillsConfig.Skills.ContainsKey(turnContext.Activity.Text)) + { + var deliveryMode = await _deliveryModeProperty.GetAsync(turnContext, () => null, cancellationToken); + var selectedSkill = _skillsConfig.Skills[turnContext.Activity.Text]; + var v3Bots = new List { "EchoSkillBotDotNetV3", "EchoSkillBotJSV3" }; + + if (selectedSkill != null && deliveryMode == DeliveryModes.ExpectReplies && v3Bots.Contains(selectedSkill.Id)) + { + var message = MessageFactory.Text("V3 Bots do not support 'expectReplies' delivery mode."); + await turnContext.SendActivityAsync(message, cancellationToken); + + // Forget delivery mode and skill invocation. + await _deliveryModeProperty.DeleteAsync(turnContext, cancellationToken); + await _activeSkillProperty.DeleteAsync(turnContext, cancellationToken); + + // Restart setup dialog + await _conversationState.DeleteAsync(turnContext, cancellationToken); + } + } + + await _dialog.RunAsync(turnContext, _dialogStateProperty, cancellationToken); + } + + /// + /// Processes an end of conversation activity. + /// + /// Context for the current turn of conversation. + /// CancellationToken propagates notifications that operations should be cancelled. + /// A representing the result of the asynchronous operation. + protected override async Task OnEndOfConversationActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + await EndConversation((Activity)turnContext.Activity, turnContext, cancellationToken); + } + + /// + /// Processes a member added event. + /// + /// The list of members added to the conversation. + /// Context for the current turn of conversation. + /// CancellationToken propagates notifications that operations should be cancelled. + /// A representing the result of the asynchronous operation. + protected override async Task OnMembersAddedAsync(IList membersAdded, ITurnContext turnContext, CancellationToken cancellationToken) + { + foreach (var member in membersAdded) + { + if (member.Id != turnContext.Activity.Recipient.Id) + { + await turnContext.SendActivityAsync(MessageFactory.Text("Hello and welcome!"), cancellationToken); + await _dialog.RunAsync(turnContext, _dialogStateProperty, cancellationToken); + } + } + } + + /// + /// Clears storage variables and sends the end of conversation activities. + /// + /// End of conversation activity. + /// Context for the current turn of conversation. + /// CancellationToken propagates notifications that operations should be cancelled. + /// A representing the result of the asynchronous operation. + private async Task EndConversation(Activity activity, ITurnContext turnContext, CancellationToken cancellationToken) + { + // Forget delivery mode and skill invocation. + await _deliveryModeProperty.DeleteAsync(turnContext, cancellationToken); + await _activeSkillProperty.DeleteAsync(turnContext, cancellationToken); + + // Show status message, text and value returned by the skill + var eocActivityMessage = $"Received {ActivityTypes.EndOfConversation}.\n\nCode: {activity.Code}."; + if (!string.IsNullOrWhiteSpace(activity.Text)) + { + eocActivityMessage += $"\n\nText: {activity.Text}"; + } + + if (activity.Value != null) + { + eocActivityMessage += $"\n\nValue: {JsonConvert.SerializeObject(activity.Value)}"; + } + + await turnContext.SendActivityAsync(MessageFactory.Text(eocActivityMessage), cancellationToken); + + // We are back at the host. + await turnContext.SendActivityAsync(MessageFactory.Text("Back in the host bot."), cancellationToken); + + // Restart setup dialog. + await _dialog.RunAsync(turnContext, _dialogStateProperty, cancellationToken); + + await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken); + } + + /// + /// Sends an activity to the skill bot. + /// + /// Context for the current turn of conversation. + /// The delivery mode to use when communicating to the skill. + /// The skill that will receive the activity. + /// CancellationToken propagates notifications that operations should be cancelled. + /// A representing the result of the asynchronous operation. + private async Task SendToSkillAsync(ITurnContext turnContext, string deliveryMode, BotFrameworkSkill targetSkill, CancellationToken cancellationToken) + { + // NOTE: Always SaveChanges() before calling a skill so that any activity generated by the skill + // will have access to current accurate state. + await _conversationState.SaveChangesAsync(turnContext, force: true, cancellationToken: cancellationToken); + + if (deliveryMode == DeliveryModes.ExpectReplies) + { + // Clone activity and update its delivery mode. + var activity = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(turnContext.Activity)); + activity.DeliveryMode = deliveryMode; + + // Route the activity to the skill. + var expectRepliesResponse = await _skillClient.PostActivityAsync(_botId, targetSkill, _skillsConfig.SkillHostEndpoint, activity, cancellationToken); + + // Check response status. + if (!expectRepliesResponse.IsSuccessStatusCode()) + { + throw new HttpRequestException($"Error invoking the skill id: \"{targetSkill.Id}\" at \"{targetSkill.SkillEndpoint}\" (status is {expectRepliesResponse.Status}). \r\n {expectRepliesResponse.Body}"); + } + + // Route response activities back to the channel. + var responseActivities = expectRepliesResponse.Body?.Activities; + + foreach (var responseActivity in responseActivities) + { + if (responseActivity.Type == ActivityTypes.EndOfConversation) + { + await EndConversation(responseActivity, turnContext, cancellationToken); + } + else + { + await turnContext.SendActivityAsync(responseActivity, cancellationToken); + } + } + } + else + { + // Route the activity to the skill. + var response = await _skillClient.PostActivityAsync(_botId, targetSkill, _skillsConfig.SkillHostEndpoint, (Activity)turnContext.Activity, cancellationToken); + + // Check response status + if (!response.IsSuccessStatusCode()) + { + throw new HttpRequestException($"Error invoking the skill id: \"{targetSkill.Id}\" at \"{targetSkill.SkillEndpoint}\" (status is {response.Status}). \r\n {response.Body}"); + } + } + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/Controllers/BotController.cs b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/Controllers/BotController.cs new file mode 100644 index 0000000000..9b199e8ac7 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/Controllers/BotController.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; + +namespace Microsoft.BotFrameworkFunctionalTests.SimpleHostBot21.Controllers +{ + /// + /// This ASP Controller is created to handle a request. Dependency Injection will provide the Adapter and IBot implementation at runtime. + /// + [Route("api/messages")] + [ApiController] + public class BotController : ControllerBase + { + private readonly BotFrameworkHttpAdapter _adapter; + private readonly IBot _bot; + + /// + /// Initializes a new instance of the class. + /// + /// Adapter for the BotController. + /// Bot for the BotController. + public BotController(BotFrameworkHttpAdapter adapter, IBot bot) + { + _adapter = adapter; + _bot = bot; + } + + /// + /// Processes an HttpPost request. + /// + /// A representing the result of the asynchronous operation. + [HttpPost] + public async Task PostAsync() + { + await _adapter.ProcessAsync(Request, Response, _bot); + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/Controllers/SkillController.cs b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/Controllers/SkillController.cs new file mode 100644 index 0000000000..4b6a1c7736 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/Controllers/SkillController.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.AspNetCore.Mvc; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Bot.Builder.Skills; + +namespace Microsoft.BotFrameworkFunctionalTests.SimpleHostBot21.Controllers +{ + /// + /// A controller that handles skill replies to the bot. + /// This example uses the that is registered as a in startup.cs. + /// + [ApiController] + [Route("api/skills")] + public class SkillController : ChannelServiceController + { + /// + /// Initializes a new instance of the class. + /// + /// The skill handler registered as ChannelServiceHandler. + public SkillController(ChannelServiceHandler handler) + : base(handler) + { + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/Dialogs/SetupDialog.cs b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/Dialogs/SetupDialog.cs new file mode 100644 index 0000000000..2ae8f6aea5 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/Dialogs/SetupDialog.cs @@ -0,0 +1,119 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Builder.Dialogs.Choices; +using Microsoft.Bot.Builder.Skills; +using Microsoft.Bot.Schema; +using Microsoft.BotFrameworkFunctionalTests.SimpleHostBot21.Bots; + +namespace Microsoft.BotFrameworkFunctionalTests.SimpleHostBot21.Dialogs +{ + /// + /// The setup dialog for this bot. + /// + public class SetupDialog : ComponentDialog + { + private readonly IStatePropertyAccessor _deliveryModeProperty; + private readonly IStatePropertyAccessor _activeSkillProperty; + private readonly SkillsConfiguration _skillsConfig; + private string _deliveryMode; + + public SetupDialog(ConversationState conversationState, SkillsConfiguration skillsConfig) + : base(nameof(SetupDialog)) + { + _skillsConfig = skillsConfig ?? throw new ArgumentNullException(nameof(skillsConfig)); + + _deliveryModeProperty = conversationState.CreateProperty(HostBot.DeliveryModePropertyName); + _activeSkillProperty = conversationState.CreateProperty(HostBot.ActiveSkillPropertyName); + + // Define the setup dialog and its related components. + // Add ChoicePrompt to render available skills. + AddDialog(new ChoicePrompt(nameof(ChoicePrompt))); + + // Add main waterfall dialog for this bot. + var waterfallSteps = new WaterfallStep[] + { + SelectDeliveryModeStepAsync, + SelectSkillStepAsync, + FinalStepAsync + }; + AddDialog(new WaterfallDialog(nameof(WaterfallDialog), waterfallSteps)); + + InitialDialogId = nameof(WaterfallDialog); + } + + // Render a prompt to select the delivery mode to use. + private async Task SelectDeliveryModeStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + // Create the PromptOptions with the delivery modes supported. + const string messageText = "What delivery mode would you like to use?"; + const string repromptMessageText = "That was not a valid choice, please select a valid delivery mode."; + var choices = new List(); + choices.Add(new Choice(DeliveryModes.Normal)); + choices.Add(new Choice(DeliveryModes.ExpectReplies)); + var options = new PromptOptions + { + Prompt = MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput), + RetryPrompt = MessageFactory.Text(repromptMessageText, repromptMessageText, InputHints.ExpectingInput), + Choices = choices + }; + + // Prompt the user to select a delivery mode. + return await stepContext.PromptAsync(nameof(ChoicePrompt), options, cancellationToken); + } + + // Render a prompt to select the skill to call. + private async Task SelectSkillStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + // Set delivery mode. + _deliveryMode = ((FoundChoice)stepContext.Result).Value; + await _deliveryModeProperty.SetAsync(stepContext.Context, ((FoundChoice)stepContext.Result).Value, cancellationToken); + + // Create the PromptOptions from the skill configuration which contains the list of configured skills. + const string messageText = "What skill would you like to call?"; + const string repromptMessageText = "That was not a valid choice, please select a valid skill."; + var options = new PromptOptions + { + Prompt = MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput), + RetryPrompt = MessageFactory.Text(repromptMessageText, repromptMessageText, InputHints.ExpectingInput), + Choices = _skillsConfig.Skills.Select(skill => new Choice(skill.Key)).ToList(), + Style = ListStyle.SuggestedAction + }; + + // Prompt the user to select a skill. + return await stepContext.PromptAsync(nameof(ChoicePrompt), options, cancellationToken); + } + + // The SetupDialog has ended, we go back to the HostBot to connect with the selected skill. + private async Task FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var selectedSkillKey = ((FoundChoice)stepContext.Result).Value; + var selectedSkill = _skillsConfig.Skills.FirstOrDefault(skill => skill.Key == selectedSkillKey); + + var v3Bots = new List { "EchoSkillBotDotNetV3", "EchoSkillBotJSV3" }; + + if (_deliveryMode == DeliveryModes.ExpectReplies && v3Bots.Contains(selectedSkillKey)) + { + await stepContext.Context.SendActivityAsync(MessageFactory.Text("V3 Bots do not support 'expectReplies' delivery mode."), cancellationToken); + + // Restart setup dialog + return await stepContext.ReplaceDialogAsync(InitialDialogId, null, cancellationToken); + } + + // Set active skill + await _activeSkillProperty.SetAsync(stepContext.Context, selectedSkill.Value, cancellationToken); + + var message = MessageFactory.Text("Type anything to send to the skill.", "Type anything to send to the skill.", InputHints.ExpectingInput); + await stepContext.Context.SendActivityAsync(message, cancellationToken); + + return await stepContext.EndDialogAsync(stepContext.Values, cancellationToken); + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/Program.cs b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/Program.cs new file mode 100644 index 0000000000..992a1b61a9 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/Program.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; + +namespace Microsoft.BotFrameworkFunctionalTests.SimpleHostBot21 +{ + public class Program + { + /// + /// The entry point of the application. + /// + /// The command line args. + public static void Main(string[] args) + { + CreateWebHostBuilder(args).Build().Run(); + } + + /// + /// Creates a new instance of the class with pre-configured defaults. + /// + /// The command line args. + /// The initialized . + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup(); + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/Properties/launchSettings.json b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/Properties/launchSettings.json new file mode 100644 index 0000000000..cbde6fb474 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/Properties/launchSettings.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:35005", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "SimpleHostBotDotNet21": { + "commandName": "Project", + "launchBrowser": true, + "applicationUrl": "http://localhost:35005", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/SimpleHostBot-2.1.csproj b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/SimpleHostBot-2.1.csproj new file mode 100644 index 0000000000..c0924fb1cc --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/SimpleHostBot-2.1.csproj @@ -0,0 +1,26 @@ + + + + netcoreapp2.1 + latest + Microsoft.BotFrameworkFunctionalTests.SimpleHostBot21 + Microsoft.BotFrameworkFunctionalTests.SimpleHostBot21 + + + + DEBUG;TRACE + + + + + + + + + + + Always + + + + \ No newline at end of file diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/SkillsConfiguration.cs b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/SkillsConfiguration.cs new file mode 100644 index 0000000000..ee3ef40abb --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/SkillsConfiguration.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using Microsoft.Bot.Builder.Skills; +using Microsoft.Extensions.Configuration; + +namespace Microsoft.BotFrameworkFunctionalTests.SimpleHostBot21 +{ + /// + /// A helper class that loads Skills information from configuration. + /// + public class SkillsConfiguration + { + /// + /// Initializes a new instance of the class to load skills information from configuration. + /// + /// The configuration properties. + public SkillsConfiguration(IConfiguration configuration) + { + var section = configuration?.GetSection("BotFrameworkSkills"); + var skills = section?.Get(); + if (skills != null) + { + foreach (var skill in skills) + { + Skills.Add(skill.Id, skill); + } + } + + var skillHostEndpoint = configuration?.GetValue(nameof(SkillHostEndpoint)); + if (!string.IsNullOrWhiteSpace(skillHostEndpoint)) + { + SkillHostEndpoint = new Uri(skillHostEndpoint); + } + } + + /// + /// Gets the URI representing the endpoint of the host bot. + /// + /// The host bot endpoint URI. + public Uri SkillHostEndpoint { get; } + + /// + /// Gets the key-value pairs with the skills bots. + /// + /// The key-value pairs with the skills bots. + public Dictionary Skills { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/Startup.cs b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/Startup.cs new file mode 100644 index 0000000000..afafc6d9b2 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/Startup.cs @@ -0,0 +1,99 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.BotFramework; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Bot.Builder.Integration.AspNet.Core.Skills; +using Microsoft.Bot.Builder.Skills; +using Microsoft.Bot.Connector.Authentication; +using Microsoft.BotFrameworkFunctionalTests.SimpleHostBot21.Authentication; +using Microsoft.BotFrameworkFunctionalTests.SimpleHostBot21.Bots; +using Microsoft.BotFrameworkFunctionalTests.SimpleHostBot21.Dialogs; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.BotFrameworkFunctionalTests.SimpleHostBot21 +{ + public class Startup + { + public Startup(IConfiguration config) + { + Configuration = config; + } + + public IConfiguration Configuration { get; } + + /// + /// This method gets called by the runtime. Use this method to add services to the container. + /// + /// The collection of services to add to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); + + // Configure credentials + services.AddSingleton(); + + // Register the skills configuration class + services.AddSingleton(); + + // Register AuthConfiguration to enable custom claim validation. + services.AddSingleton(sp => new AuthenticationConfiguration { ClaimsValidator = new AllowedSkillsClaimsValidator(sp.GetService()) }); + + // Register the Bot Framework Adapter with error handling enabled. + // Note: some classes use the base BotAdapter so we add an extra registration that pulls the same instance. + services.AddSingleton(); + services.AddSingleton(sp => sp.GetService()); + + // Register the skills client and skills request handler. + services.AddSingleton(); + services.AddHttpClient(); + services.AddSingleton(); + + // Register the storage we'll be using for User and Conversation state. (Memory is great for testing purposes.) + services.AddSingleton(); + + // Register Conversation state (used by the Dialog system itself). + services.AddSingleton(); + + // Create SetupDialog + services.AddSingleton(); + + // Register the bot as a transient. In this case the ASP Controller is expecting an IBot. + services.AddTransient(); + + if (!string.IsNullOrEmpty(Configuration["ChannelService"])) + { + // Register a ConfigurationChannelProvider -- this is only for Azure Gov. + services.AddSingleton(); + } + } + + /// + /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + /// + /// The application request pipeline to be configured. + /// The web hosting environment. + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + app.UseHsts(); + } + + app.UseDefaultFiles(); + app.UseStaticFiles(); + + // app.UseHttpsRedirection(); + app.UseMvc(); + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/appsettings.json b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/appsettings.json new file mode 100644 index 0000000000..65cb14456e --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/appsettings.json @@ -0,0 +1,43 @@ +{ + "MicrosoftAppId": "", + "MicrosoftAppPassword": "", + "ChannelService": "", + "SkillHostEndpoint": "http://localhost:35005/api/skills/", + "BotFrameworkSkills": [ + { + "Id": "EchoSkillBotComposerDotNet", + "AppId": "", + "SkillEndpoint": "http://localhost:35410/api/messages" + }, + { + "Id": "EchoSkillBotDotNet", + "AppId": "", + "SkillEndpoint": "http://localhost:35400/api/messages" + }, + { + "Id": "EchoSkillBotDotNet21", + "AppId": "", + "SkillEndpoint": "http://localhost:35405/api/messages" + }, + { + "Id": "EchoSkillBotDotNetV3", + "AppId": "", + "SkillEndpoint": "http://localhost:35407/api/messages" + }, + { + "Id": "EchoSkillBotJS", + "AppId": "", + "SkillEndpoint": "http://localhost:36400/api/messages" + }, + { + "Id": "EchoSkillBotJSV3", + "AppId": "", + "SkillEndpoint": "http://localhost:36407/api/messages" + }, + { + "Id": "EchoSkillBotPython", + "AppId": "", + "SkillEndpoint": "http://localhost:37400/api/messages" + } + ] +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/wwwroot/default.htm b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/wwwroot/default.htm new file mode 100644 index 0000000000..39a369dfe8 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1/wwwroot/default.htm @@ -0,0 +1,420 @@ + + + + + + + SimpleHostBot-2.1DotNet + + + + + +
+
+
+
SimpleRootBotDotNet21 Bot
+
+
+
+
+
Your bot is ready!
+
You can test your bot in the Bot Framework Emulator
+ by connecting to http://localhost:3978/api/messages.
+ +
Visit Azure + Bot Service to register your bot and add it to
+ various channels. The bot's endpoint URL typically looks + like this:
+
https://your_bots_hostname/api/messages
+
+
+
+
+ +
+ + + diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/AdapterWithErrorHandler.cs b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/AdapterWithErrorHandler.cs new file mode 100644 index 0000000000..a358155fed --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/AdapterWithErrorHandler.cs @@ -0,0 +1,155 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Bot.Builder.Integration.AspNet.Core.Skills; +using Microsoft.Bot.Builder.Skills; +using Microsoft.Bot.Builder.TraceExtensions; +using Microsoft.Bot.Connector.Authentication; +using Microsoft.Bot.Schema; +using Microsoft.BotFrameworkFunctionalTests.SimpleHostBot.Bots; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace Microsoft.BotFrameworkFunctionalTests.SimpleHostBot +{ + public class AdapterWithErrorHandler : BotFrameworkHttpAdapter + { + private readonly ConversationState _conversationState; + private readonly IConfiguration _configuration; + private readonly ILogger _logger; + private readonly SkillHttpClient _skillClient; + private readonly SkillsConfiguration _skillsConfig; + + /// + /// Initializes a new instance of the class to handle errors. + /// + /// The configuration properties. + /// An instance of a logger. + /// A state management object for the conversation. + /// The HTTP client for the skills. + /// The skills configuration. + public AdapterWithErrorHandler(IConfiguration configuration, ILogger logger, ConversationState conversationState = null, SkillHttpClient skillClient = null, SkillsConfiguration skillsConfig = null) + : base(configuration, logger) + { + _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); + _conversationState = conversationState; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _skillClient = skillClient; + _skillsConfig = skillsConfig; + + OnTurnError = HandleTurnErrorAsync; + } + + /// + /// Handles the error by sending a message to the user and ending the conversation with the skill. + /// + /// Context for the current turn of conversation. + /// The handled exception. + /// A representing the result of the asynchronous operation. + private async Task HandleTurnErrorAsync(ITurnContext turnContext, Exception exception) + { + // Log any leaked exception from the application. + _logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}"); + + await SendErrorMessageAsync(turnContext, exception, default); + await EndSkillConversationAsync(turnContext, default); + await ClearConversationStateAsync(turnContext, default); + } + + /// + /// Sends an error message to the user and a trace activity to be displayed in the Bot Framework Emulator. + /// + /// Context for the current turn of conversation. + /// The exception to be sent in the message. + /// CancellationToken propagates notifications that operations should be cancelled. + /// A representing the result of the asynchronous operation. + private async Task SendErrorMessageAsync(ITurnContext turnContext, Exception exception, CancellationToken cancellationToken) + { + try + { + // Send a message to the user + var errorMessageText = "The bot encountered an error or bug."; + var errorMessage = MessageFactory.Text(errorMessageText, errorMessageText, InputHints.IgnoringInput); + errorMessage.Value = exception; + await turnContext.SendActivityAsync(errorMessage, cancellationToken); + + await turnContext.SendActivityAsync($"Exception: {exception.Message}"); + await turnContext.SendActivityAsync(exception.ToString()); + + errorMessageText = "To continue to run this bot, please fix the bot source code."; + errorMessage = MessageFactory.Text(errorMessageText, errorMessageText, InputHints.ExpectingInput); + await turnContext.SendActivityAsync(errorMessage, cancellationToken); + + // Send a trace activity, which will be displayed in the Bot Framework Emulator + await turnContext.TraceActivityAsync("OnTurnError Trace", exception.ToString(), "https://www.botframework.com/schemas/error", "TurnError", cancellationToken); + } + catch (Exception ex) + { + _logger.LogError(ex, $"Exception caught in SendErrorMessageAsync : {ex}"); + } + } + + /// + /// Informs to the active skill that the conversation is ended so that it has a chance to clean up. + /// + /// Context for the current turn of conversation. + /// CancellationToken propagates notifications that operations should be cancelled. + /// A representing the result of the asynchronous operation. + private async Task EndSkillConversationAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + if (_conversationState == null || _skillClient == null || _skillsConfig == null) + { + return; + } + + try + { + // Note: ActiveSkillPropertyName is set by the HostBot while messages are being + // forwarded to a Skill. + var activeSkill = await _conversationState.CreateProperty(HostBot.ActiveSkillPropertyName).GetAsync(turnContext, () => null, cancellationToken); + if (activeSkill != null) + { + var botId = _configuration.GetSection(MicrosoftAppCredentials.MicrosoftAppIdKey)?.Value; + + var endOfConversation = Activity.CreateEndOfConversationActivity(); + endOfConversation.Code = "HostSkillError"; + endOfConversation.ApplyConversationReference(turnContext.Activity.GetConversationReference(), true); + + await _conversationState.SaveChangesAsync(turnContext, true, cancellationToken); + await _skillClient.PostActivityAsync(botId, activeSkill, _skillsConfig.SkillHostEndpoint, (Activity)endOfConversation, cancellationToken); + } + } + catch (Exception ex) + { + _logger.LogError(ex, $"Exception caught on attempting to send EndOfConversation : {ex}"); + } + } + + /// + /// Deletes the conversationState for the current conversation to prevent the bot from getting stuck in an error-loop caused by being in a bad state. + /// ConversationState should be thought of as similar to "cookie-state" in a Web pages. + /// + /// Context for the current turn of conversation. + /// CancellationToken propagates notifications that operations should be cancelled. + /// A representing the result of the asynchronous operation. + private async Task ClearConversationStateAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + if (_conversationState != null) + { + try + { + await _conversationState.DeleteAsync(turnContext, cancellationToken); + } + catch (Exception ex) + { + _logger.LogError(ex, $"Exception caught on attempting to Delete ConversationState : {ex}"); + } + } + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/Authentication/AllowedSkillsClaimsValidator.cs b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/Authentication/AllowedSkillsClaimsValidator.cs new file mode 100644 index 0000000000..178a1c0a47 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/Authentication/AllowedSkillsClaimsValidator.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.Bot.Connector.Authentication; + +namespace Microsoft.BotFrameworkFunctionalTests.SimpleHostBot.Authentication +{ + /// + /// Sample claims validator that loads an allowed list from configuration if present + /// and checks that responses are coming from configured skills. + /// + public class AllowedSkillsClaimsValidator : ClaimsValidator + { + private readonly List _allowedSkills; + + /// + /// Initializes a new instance of the class. + /// Loads the appIds for the configured skills. Only allows responses from skills it has configured. + /// + /// The list of configured skills. + public AllowedSkillsClaimsValidator(SkillsConfiguration skillsConfig) + { + if (skillsConfig == null) + { + throw new ArgumentNullException(nameof(skillsConfig)); + } + + _allowedSkills = (from skill in skillsConfig.Skills.Values select skill.AppId).ToList(); + } + + /// + /// Checks that the appId claim in the skill request is in the list of skills configured for this bot. + /// + /// The list of claims to validate. + /// A task that represents the work queued to execute. + public override Task ValidateClaimsAsync(IList claims) + { + if (SkillValidation.IsSkillClaim(claims)) + { + var appId = JwtTokenValidation.GetAppIdFromClaims(claims); + if (!_allowedSkills.Contains(appId)) + { + throw new UnauthorizedAccessException($"Received a request from an application with an appID of \"{appId}\". To enable requests from this skill, add the skill to your configuration file."); + } + } + + return Task.CompletedTask; + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/Bots/HostBot.cs b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/Bots/HostBot.cs new file mode 100644 index 0000000000..566610aed0 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/Bots/HostBot.cs @@ -0,0 +1,242 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Builder.Integration.AspNet.Core.Skills; +using Microsoft.Bot.Builder.Skills; +using Microsoft.Bot.Connector.Authentication; +using Microsoft.Bot.Schema; +using Microsoft.BotFrameworkFunctionalTests.SimpleHostBot.Dialogs; +using Microsoft.Extensions.Configuration; +using Newtonsoft.Json; + +namespace Microsoft.BotFrameworkFunctionalTests.SimpleHostBot.Bots +{ + public class HostBot : ActivityHandler + { + public const string DeliveryModePropertyName = "deliveryModeProperty"; + public const string ActiveSkillPropertyName = "activeSkillProperty"; + + private readonly IStatePropertyAccessor _deliveryModeProperty; + private readonly IStatePropertyAccessor _activeSkillProperty; + private readonly IStatePropertyAccessor _dialogStateProperty; + private readonly string _botId; + private readonly ConversationState _conversationState; + private readonly SkillHttpClient _skillClient; + private readonly SkillsConfiguration _skillsConfig; + private readonly Dialog _dialog; + + /// + /// Initializes a new instance of the class. + /// + /// A state management object for the conversation. + /// The skills configuration. + /// The HTTP client for the skills. + /// The configuration properties. + /// The dialog to use. + public HostBot(ConversationState conversationState, SkillsConfiguration skillsConfig, SkillHttpClient skillClient, IConfiguration configuration, SetupDialog dialog) + { + _conversationState = conversationState ?? throw new ArgumentNullException(nameof(conversationState)); + _skillsConfig = skillsConfig ?? throw new ArgumentNullException(nameof(skillsConfig)); + _skillClient = skillClient ?? throw new ArgumentNullException(nameof(skillClient)); + _dialog = dialog ?? throw new ArgumentNullException(nameof(dialog)); + _dialogStateProperty = _conversationState.CreateProperty("DialogState"); + if (configuration == null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + _botId = configuration.GetSection(MicrosoftAppCredentials.MicrosoftAppIdKey)?.Value; + + // Create state properties to track the delivery mode and active skill. + _deliveryModeProperty = conversationState.CreateProperty(DeliveryModePropertyName); + _activeSkillProperty = conversationState.CreateProperty(ActiveSkillPropertyName); + } + + /// + public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) + { + // Forward all activities except EndOfConversation to the active skill. + if (turnContext.Activity.Type != ActivityTypes.EndOfConversation) + { + // Try to get the active skill + var activeSkill = await _activeSkillProperty.GetAsync(turnContext, () => null, cancellationToken); + + if (activeSkill != null) + { + var deliveryMode = await _deliveryModeProperty.GetAsync(turnContext, () => null, cancellationToken); + + // Send the activity to the skill + await SendToSkillAsync(turnContext, deliveryMode, activeSkill, cancellationToken); + return; + } + } + + await base.OnTurnAsync(turnContext, cancellationToken); + + // Save any state changes that might have occurred during the turn. + await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken); + } + + /// + /// Processes a message activity. + /// + /// Context for the current turn of conversation. + /// CancellationToken propagates notifications that operations should be cancelled. + /// A representing the result of the asynchronous operation. + protected override async Task OnMessageActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + if (_skillsConfig.Skills.ContainsKey(turnContext.Activity.Text)) + { + var deliveryMode = await _deliveryModeProperty.GetAsync(turnContext, () => null, cancellationToken); + var selectedSkill = _skillsConfig.Skills[turnContext.Activity.Text]; + var v3Bots = new List { "EchoSkillBotDotNetV3", "EchoSkillBotJSV3" }; + + if (selectedSkill != null && deliveryMode == DeliveryModes.ExpectReplies && v3Bots.Contains(selectedSkill.Id)) + { + var message = MessageFactory.Text("V3 Bots do not support 'expectReplies' delivery mode."); + await turnContext.SendActivityAsync(message, cancellationToken); + + // Forget delivery mode and skill invocation. + await _deliveryModeProperty.DeleteAsync(turnContext, cancellationToken); + await _activeSkillProperty.DeleteAsync(turnContext, cancellationToken); + + // Restart setup dialog + await _conversationState.DeleteAsync(turnContext, cancellationToken); + } + } + + await _dialog.RunAsync(turnContext, _dialogStateProperty, cancellationToken); + } + + /// + /// Processes an end of conversation activity. + /// + /// Context for the current turn of conversation. + /// CancellationToken propagates notifications that operations should be cancelled. + /// A representing the result of the asynchronous operation. + protected override async Task OnEndOfConversationActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + await EndConversation((Activity)turnContext.Activity, turnContext, cancellationToken); + } + + /// + /// Processes a member added event. + /// + /// The list of members added to the conversation. + /// Context for the current turn of conversation. + /// CancellationToken propagates notifications that operations should be cancelled. + /// A representing the result of the asynchronous operation. + protected override async Task OnMembersAddedAsync(IList membersAdded, ITurnContext turnContext, CancellationToken cancellationToken) + { + foreach (var member in membersAdded) + { + if (member.Id != turnContext.Activity.Recipient.Id) + { + await turnContext.SendActivityAsync(MessageFactory.Text("Hello and welcome!"), cancellationToken); + await _dialog.RunAsync(turnContext, _dialogStateProperty, cancellationToken); + } + } + } + + /// + /// Clears storage variables and sends the end of conversation activities. + /// + /// End of conversation activity. + /// Context for the current turn of conversation. + /// CancellationToken propagates notifications that operations should be cancelled. + /// A representing the result of the asynchronous operation. + private async Task EndConversation(Activity activity, ITurnContext turnContext, CancellationToken cancellationToken) + { + // Forget delivery mode and skill invocation. + await _deliveryModeProperty.DeleteAsync(turnContext, cancellationToken); + await _activeSkillProperty.DeleteAsync(turnContext, cancellationToken); + + // Show status message, text and value returned by the skill + var eocActivityMessage = $"Received {ActivityTypes.EndOfConversation}.\n\nCode: {activity.Code}."; + if (!string.IsNullOrWhiteSpace(activity.Text)) + { + eocActivityMessage += $"\n\nText: {activity.Text}"; + } + + if (activity.Value != null) + { + eocActivityMessage += $"\n\nValue: {JsonConvert.SerializeObject(activity.Value)}"; + } + + await turnContext.SendActivityAsync(MessageFactory.Text(eocActivityMessage), cancellationToken); + + // We are back at the host. + await turnContext.SendActivityAsync(MessageFactory.Text("Back in the host bot."), cancellationToken); + + // Restart setup dialog. + await _dialog.RunAsync(turnContext, _dialogStateProperty, cancellationToken); + + await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken); + } + + /// + /// Sends an activity to the skill bot. + /// + /// Context for the current turn of conversation. + /// The delivery mode to use when communicating to the skill. + /// The skill that will receive the activity. + /// CancellationToken propagates notifications that operations should be cancelled. + /// A representing the result of the asynchronous operation. + private async Task SendToSkillAsync(ITurnContext turnContext, string deliveryMode, BotFrameworkSkill targetSkill, CancellationToken cancellationToken) + { + // NOTE: Always SaveChanges() before calling a skill so that any activity generated by the skill + // will have access to current accurate state. + await _conversationState.SaveChangesAsync(turnContext, force: true, cancellationToken: cancellationToken); + + if (deliveryMode == DeliveryModes.ExpectReplies) + { + // Clone activity and update its delivery mode. + var activity = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(turnContext.Activity)); + activity.DeliveryMode = deliveryMode; + + // Route the activity to the skill. + var expectRepliesResponse = await _skillClient.PostActivityAsync(_botId, targetSkill, _skillsConfig.SkillHostEndpoint, activity, cancellationToken); + + // Check response status. + if (!expectRepliesResponse.IsSuccessStatusCode()) + { + throw new HttpRequestException($"Error invoking the skill id: \"{targetSkill.Id}\" at \"{targetSkill.SkillEndpoint}\" (status is {expectRepliesResponse.Status}). \r\n {expectRepliesResponse.Body}"); + } + + // Route response activities back to the channel. + var responseActivities = expectRepliesResponse.Body?.Activities; + + foreach (var responseActivity in responseActivities) + { + if (responseActivity.Type == ActivityTypes.EndOfConversation) + { + await EndConversation(responseActivity, turnContext, cancellationToken); + } + else + { + await turnContext.SendActivityAsync(responseActivity, cancellationToken); + } + } + } + else + { + // Route the activity to the skill. + var response = await _skillClient.PostActivityAsync(_botId, targetSkill, _skillsConfig.SkillHostEndpoint, (Activity)turnContext.Activity, cancellationToken); + + // Check response status + if (!response.IsSuccessStatusCode()) + { + throw new HttpRequestException($"Error invoking the skill id: \"{targetSkill.Id}\" at \"{targetSkill.SkillEndpoint}\" (status is {response.Status}). \r\n {response.Body}"); + } + } + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/Controllers/BotController.cs b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/Controllers/BotController.cs new file mode 100644 index 0000000000..e0a80d0dc0 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/Controllers/BotController.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; + +namespace Microsoft.BotFrameworkFunctionalTests.SimpleHostBot.Controllers +{ + /// + /// This ASP Controller is created to handle a request. Dependency Injection will provide the Adapter and IBot implementation at runtime. + /// + [Route("api/messages")] + [ApiController] + public class BotController : ControllerBase + { + private readonly BotFrameworkHttpAdapter _adapter; + private readonly IBot _bot; + + /// + /// Initializes a new instance of the class. + /// + /// Adapter for the BotController. + /// Bot for the BotController. + public BotController(BotFrameworkHttpAdapter adapter, IBot bot) + { + _adapter = adapter; + _bot = bot; + } + + /// + /// Processes an HttpPost request. + /// + /// A representing the result of the asynchronous operation. + [HttpPost] + public async Task PostAsync() + { + await _adapter.ProcessAsync(Request, Response, _bot); + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/Controllers/SkillController.cs b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/Controllers/SkillController.cs new file mode 100644 index 0000000000..9f270318f6 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/Controllers/SkillController.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.AspNetCore.Mvc; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Bot.Builder.Skills; + +namespace Microsoft.BotFrameworkFunctionalTests.SimpleHostBot.Controllers +{ + /// + /// A controller that handles skill replies to the bot. + /// This example uses the that is registered as a in startup.cs. + /// + [ApiController] + [Route("api/skills")] + public class SkillController : ChannelServiceController + { + /// + /// Initializes a new instance of the class. + /// + /// The skill handler registered as ChannelServiceHandler. + public SkillController(ChannelServiceHandler handler) + : base(handler) + { + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/Dialogs/SetupDialog.cs b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/Dialogs/SetupDialog.cs new file mode 100644 index 0000000000..86b6c6787c --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/Dialogs/SetupDialog.cs @@ -0,0 +1,119 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Builder.Dialogs.Choices; +using Microsoft.Bot.Builder.Skills; +using Microsoft.Bot.Schema; +using Microsoft.BotFrameworkFunctionalTests.SimpleHostBot.Bots; + +namespace Microsoft.BotFrameworkFunctionalTests.SimpleHostBot.Dialogs +{ + /// + /// The setup dialog for this bot. + /// + public class SetupDialog : ComponentDialog + { + private readonly IStatePropertyAccessor _deliveryModeProperty; + private readonly IStatePropertyAccessor _activeSkillProperty; + private readonly SkillsConfiguration _skillsConfig; + private string _deliveryMode; + + public SetupDialog(ConversationState conversationState, SkillsConfiguration skillsConfig) + : base(nameof(SetupDialog)) + { + _skillsConfig = skillsConfig ?? throw new ArgumentNullException(nameof(skillsConfig)); + + _deliveryModeProperty = conversationState.CreateProperty(HostBot.DeliveryModePropertyName); + _activeSkillProperty = conversationState.CreateProperty(HostBot.ActiveSkillPropertyName); + + // Define the setup dialog and its related components. + // Add ChoicePrompt to render available skills. + AddDialog(new ChoicePrompt(nameof(ChoicePrompt))); + + // Add main waterfall dialog for this bot. + var waterfallSteps = new WaterfallStep[] + { + SelectDeliveryModeStepAsync, + SelectSkillStepAsync, + FinalStepAsync + }; + AddDialog(new WaterfallDialog(nameof(WaterfallDialog), waterfallSteps)); + + InitialDialogId = nameof(WaterfallDialog); + } + + // Render a prompt to select the delivery mode to use. + private async Task SelectDeliveryModeStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + // Create the PromptOptions with the delivery modes supported. + const string messageText = "What delivery mode would you like to use?"; + const string repromptMessageText = "That was not a valid choice, please select a valid delivery mode."; + var choices = new List(); + choices.Add(new Choice(DeliveryModes.Normal)); + choices.Add(new Choice(DeliveryModes.ExpectReplies)); + var options = new PromptOptions + { + Prompt = MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput), + RetryPrompt = MessageFactory.Text(repromptMessageText, repromptMessageText, InputHints.ExpectingInput), + Choices = choices + }; + + // Prompt the user to select a delivery mode. + return await stepContext.PromptAsync(nameof(ChoicePrompt), options, cancellationToken); + } + + // Render a prompt to select the skill to call. + private async Task SelectSkillStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + // Set delivery mode. + _deliveryMode = ((FoundChoice)stepContext.Result).Value; + await _deliveryModeProperty.SetAsync(stepContext.Context, ((FoundChoice)stepContext.Result).Value, cancellationToken); + + // Create the PromptOptions from the skill configuration which contains the list of configured skills. + const string messageText = "What skill would you like to call?"; + const string repromptMessageText = "That was not a valid choice, please select a valid skill."; + var options = new PromptOptions + { + Prompt = MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput), + RetryPrompt = MessageFactory.Text(repromptMessageText, repromptMessageText, InputHints.ExpectingInput), + Choices = _skillsConfig.Skills.Select(skill => new Choice(skill.Key)).ToList(), + Style = ListStyle.SuggestedAction + }; + + // Prompt the user to select a skill. + return await stepContext.PromptAsync(nameof(ChoicePrompt), options, cancellationToken); + } + + // The SetupDialog has ended, we go back to the HostBot to connect with the selected skill. + private async Task FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var selectedSkillKey = ((FoundChoice)stepContext.Result).Value; + var selectedSkill = _skillsConfig.Skills.FirstOrDefault(skill => skill.Key == selectedSkillKey); + + var v3Bots = new List { "EchoSkillBotDotNetV3", "EchoSkillBotJSV3" }; + + if (_deliveryMode == DeliveryModes.ExpectReplies && v3Bots.Contains(selectedSkillKey)) + { + await stepContext.Context.SendActivityAsync(MessageFactory.Text("V3 Bots do not support 'expectReplies' delivery mode."), cancellationToken); + + // Restart setup dialog + return await stepContext.ReplaceDialogAsync(InitialDialogId, null, cancellationToken); + } + + // Set active skill + await _activeSkillProperty.SetAsync(stepContext.Context, selectedSkill.Value, cancellationToken); + + var message = MessageFactory.Text("Type anything to send to the skill.", "Type anything to send to the skill.", InputHints.ExpectingInput); + await stepContext.Context.SendActivityAsync(message, cancellationToken); + + return await stepContext.EndDialogAsync(stepContext.Values, cancellationToken); + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/Program.cs b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/Program.cs new file mode 100644 index 0000000000..fb92b18353 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/Program.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; + +namespace Microsoft.BotFrameworkFunctionalTests.SimpleHostBot +{ + public class Program + { + /// + /// The entry point of the application. + /// + /// The command line args. + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + /// + /// Creates a new instance of the class with pre-configured defaults. + /// + /// The command line args. + /// The initialized . + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/Properties/launchSettings.json b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/Properties/launchSettings.json new file mode 100644 index 0000000000..e660cda730 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/Properties/launchSettings.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:35000", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "SimpleHostBotDotNet": { + "commandName": "Project", + "launchBrowser": true, + "applicationUrl": "http://localhost:35000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/SimpleHostBot.csproj b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/SimpleHostBot.csproj new file mode 100644 index 0000000000..5f112edd07 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/SimpleHostBot.csproj @@ -0,0 +1,29 @@ + + + + netcoreapp3.1 + latest + Microsoft.BotFrameworkFunctionalTests.SimpleHostBot + Microsoft.BotFrameworkFunctionalTests.SimpleHostBot + + + + DEBUG;TRACE + + + + + + + + + + + Always + + + Always + + + + \ No newline at end of file diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/SkillsConfiguration.cs b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/SkillsConfiguration.cs new file mode 100644 index 0000000000..aa1989900b --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/SkillsConfiguration.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using Microsoft.Bot.Builder.Skills; +using Microsoft.Extensions.Configuration; + +namespace Microsoft.BotFrameworkFunctionalTests.SimpleHostBot +{ + /// + /// A helper class that loads Skills information from configuration. + /// + public class SkillsConfiguration + { + /// + /// Initializes a new instance of the class to load skills information from configuration. + /// + /// The configuration properties. + public SkillsConfiguration(IConfiguration configuration) + { + var section = configuration?.GetSection("BotFrameworkSkills"); + var skills = section?.Get(); + if (skills != null) + { + foreach (var skill in skills) + { + Skills.Add(skill.Id, skill); + } + } + + var skillHostEndpoint = configuration?.GetValue(nameof(SkillHostEndpoint)); + if (!string.IsNullOrWhiteSpace(skillHostEndpoint)) + { + SkillHostEndpoint = new Uri(skillHostEndpoint); + } + } + + /// + /// Gets the URI representing the endpoint of the host bot. + /// + /// + /// The URI representing the endpoint of the host bot. + /// + public Uri SkillHostEndpoint { get; } + + /// + /// Gets the key-value pairs with the skills bots. + /// + /// + /// The key-value pairs with the skills bots. + /// + public Dictionary Skills { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/Startup.cs b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/Startup.cs new file mode 100644 index 0000000000..42879f11f9 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/Startup.cs @@ -0,0 +1,99 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.BotFramework; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Bot.Builder.Integration.AspNet.Core.Skills; +using Microsoft.Bot.Builder.Skills; +using Microsoft.Bot.Connector.Authentication; +using Microsoft.BotFrameworkFunctionalTests.SimpleHostBot.Authentication; +using Microsoft.BotFrameworkFunctionalTests.SimpleHostBot.Bots; +using Microsoft.BotFrameworkFunctionalTests.SimpleHostBot.Dialogs; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Microsoft.BotFrameworkFunctionalTests.SimpleHostBot +{ + public class Startup + { + public Startup(IConfiguration config) + { + Configuration = config; + } + + public IConfiguration Configuration { get; } + + /// + /// This method gets called by the runtime. Use this method to add services to the container. + /// + /// The collection of services to add to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers().AddNewtonsoftJson(); + + // Configure credentials + services.AddSingleton(); + + // Register the skills configuration class + services.AddSingleton(); + + // Register AuthConfiguration to enable custom claim validation. + services.AddSingleton(sp => new AuthenticationConfiguration { ClaimsValidator = new AllowedSkillsClaimsValidator(sp.GetService()) }); + + // Register the Bot Framework Adapter with error handling enabled. + // Note: some classes use the base BotAdapter so we add an extra registration that pulls the same instance. + services.AddSingleton(); + services.AddSingleton(sp => sp.GetService()); + + // Register the skills client and skills request handler. + services.AddSingleton(); + services.AddHttpClient(); + services.AddSingleton(); + + // Register the storage we'll be using for User and Conversation state. (Memory is great for testing purposes.) + services.AddSingleton(); + + // Register Conversation state (used by the Dialog system itself). + services.AddSingleton(); + + // Create SetupDialog + services.AddSingleton(); + + // Register the bot as a transient. In this case the ASP Controller is expecting an IBot. + services.AddTransient(); + + if (!string.IsNullOrEmpty(Configuration["ChannelService"])) + { + // Register a ConfigurationChannelProvider -- this is only for Azure Gov. + services.AddSingleton(); + } + } + + /// + /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + /// + /// The application request pipeline to be configured. + /// The web hosting environment. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseDefaultFiles() + .UseStaticFiles() + .UseRouting() + .UseAuthorization() + .UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/appsettings.json b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/appsettings.json new file mode 100644 index 0000000000..ba463c7c36 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/appsettings.json @@ -0,0 +1,43 @@ +{ + "MicrosoftAppId": "", + "MicrosoftAppPassword": "", + "ChannelService": "", + "SkillHostEndpoint": "http://localhost:35000/api/skills/", + "BotFrameworkSkills": [ + { + "Id": "EchoSkillBotComposerDotNet", + "AppId": "", + "SkillEndpoint": "http://localhost:35410/api/messages" + }, + { + "Id": "EchoSkillBotDotNet", + "AppId": "", + "SkillEndpoint": "http://localhost:35400/api/messages" + }, + { + "Id": "EchoSkillBotDotNet21", + "AppId": "", + "SkillEndpoint": "http://localhost:35405/api/messages" + }, + { + "Id": "EchoSkillBotDotNetV3", + "AppId": "", + "SkillEndpoint": "http://localhost:35407/api/messages" + }, + { + "Id": "EchoSkillBotJS", + "AppId": "", + "SkillEndpoint": "http://localhost:36400/api/messages" + }, + { + "Id": "EchoSkillBotJSV3", + "AppId": "", + "SkillEndpoint": "http://localhost:36407/api/messages" + }, + { + "Id": "EchoSkillBotPython", + "AppId": "", + "SkillEndpoint": "http://localhost:37400/api/messages" + } + ] +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/wwwroot/default.htm b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/wwwroot/default.htm new file mode 100644 index 0000000000..8d163da5a0 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/SimpleHostBot/wwwroot/default.htm @@ -0,0 +1,420 @@ + + + + + + + SimpleHostBotDotNet + + + + + +
+
+
+
SimpleRootBotDotNet Bot
+
+
+
+
+
Your bot is ready!
+
You can test your bot in the Bot Framework Emulator
+ by connecting to http://localhost:3978/api/messages.
+ +
Visit Azure + Bot Service to register your bot and add it to
+ various channels. The bot's endpoint URL typically looks + like this:
+
https://your_bots_hostname/api/messages
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/AdapterWithErrorHandler.cs b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/AdapterWithErrorHandler.cs new file mode 100644 index 0000000000..1658551aea --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/AdapterWithErrorHandler.cs @@ -0,0 +1,124 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Bot.Builder.Integration.AspNet.Core.Skills; +using Microsoft.Bot.Builder.Skills; +using Microsoft.Bot.Builder.TraceExtensions; +using Microsoft.Bot.Connector.Authentication; +using Microsoft.Bot.Schema; +using Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Dialogs; +using Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Middleware; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot +{ + public class AdapterWithErrorHandler : BotFrameworkHttpAdapter + { + private readonly IConfiguration _configuration; + private readonly ConversationState _conversationState; + private readonly ILogger _logger; + private readonly SkillHttpClient _skillClient; + private readonly SkillsConfiguration _skillsConfig; + + public AdapterWithErrorHandler(IConfiguration configuration, ILogger logger, ConversationState conversationState, SkillHttpClient skillClient = null, SkillsConfiguration skillsConfig = null) + : base(configuration, logger) + { + _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); + _conversationState = conversationState ?? throw new ArgumentNullException(nameof(conversationState)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _skillClient = skillClient; + _skillsConfig = skillsConfig; + + OnTurnError = HandleTurnError; + Use(new LoggerMiddleware(logger)); + } + + private async Task HandleTurnError(ITurnContext turnContext, Exception exception) + { + // Log any leaked exception from the application. + _logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}"); + + await SendErrorMessageAsync(turnContext, exception); + await EndSkillConversationAsync(turnContext); + await ClearConversationStateAsync(turnContext); + } + + private async Task SendErrorMessageAsync(ITurnContext turnContext, Exception exception) + { + try + { + // Send a message to the user. + var errorMessageText = "The bot encountered an error or bug."; + var errorMessage = MessageFactory.Text(errorMessageText, errorMessageText, InputHints.IgnoringInput); + errorMessage.Value = exception; + await turnContext.SendActivityAsync(errorMessage); + + await turnContext.SendActivityAsync($"Exception: {exception.Message}"); + await turnContext.SendActivityAsync(exception.ToString()); + + errorMessageText = "To continue to run this bot, please fix the bot source code."; + errorMessage = MessageFactory.Text(errorMessageText, errorMessageText, InputHints.ExpectingInput); + await turnContext.SendActivityAsync(errorMessage); + + // Send a trace activity, which will be displayed in the Bot Framework Emulator. + await turnContext.TraceActivityAsync("OnTurnError Trace", exception.ToString(), "https://www.botframework.com/schemas/error", "TurnError"); + } + catch (Exception ex) + { + _logger.LogError(ex, $"Exception caught in SendErrorMessageAsync : {ex}"); + } + } + + private async Task EndSkillConversationAsync(ITurnContext turnContext) + { + if (_skillClient == null || _skillsConfig == null) + { + return; + } + + try + { + // Inform the active skill that the conversation is ended so that it has a chance to clean up. + // Note: the root bot manages the ActiveSkillPropertyName, which has a value while the root bot + // has an active conversation with a skill. + var activeSkill = await _conversationState.CreateProperty(MainDialog.ActiveSkillPropertyName).GetAsync(turnContext, () => null); + if (activeSkill != null) + { + var botId = _configuration.GetSection(MicrosoftAppCredentials.MicrosoftAppIdKey)?.Value; + + var endOfConversation = Activity.CreateEndOfConversationActivity(); + endOfConversation.Code = "RootSkillError"; + endOfConversation.ApplyConversationReference(turnContext.Activity.GetConversationReference(), true); + + await _conversationState.SaveChangesAsync(turnContext, true); + await _skillClient.PostActivityAsync(botId, activeSkill, _skillsConfig.SkillHostEndpoint, (Activity)endOfConversation, CancellationToken.None); + } + } + catch (Exception ex) + { + _logger.LogError(ex, $"Exception caught on attempting to send EndOfConversation : {ex}"); + } + } + + private async Task ClearConversationStateAsync(ITurnContext turnContext) + { + try + { + // Delete the conversationState for the current conversation to prevent the + // bot from getting stuck in a error-loop caused by being in a bad state. + // ConversationState should be thought of as similar to "cookie-state" for a Web page. + await _conversationState.DeleteAsync(turnContext); + } + catch (Exception ex) + { + _logger.LogError(ex, $"Exception caught on attempting to Delete ConversationState : {ex}"); + } + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Authentication/AllowedSkillsClaimsValidator.cs b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Authentication/AllowedSkillsClaimsValidator.cs new file mode 100644 index 0000000000..42594c3c85 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Authentication/AllowedSkillsClaimsValidator.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.Bot.Connector.Authentication; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Authentication +{ + /// + /// Sample claims validator that loads an allowed list from configuration if present + /// and checks that responses are coming from configured skills. + /// + public class AllowedSkillsClaimsValidator : ClaimsValidator + { + private readonly List _allowedSkills; + + public AllowedSkillsClaimsValidator(SkillsConfiguration skillsConfig) + { + if (skillsConfig == null) + { + throw new ArgumentNullException(nameof(skillsConfig)); + } + + // Load the appIds for the configured skills (we will only allow responses from skills we have configured). + _allowedSkills = (from skill in skillsConfig.Skills.Values select skill.AppId).ToList(); + } + + public override Task ValidateClaimsAsync(IList claims) + { + if (SkillValidation.IsSkillClaim(claims)) + { + // Check that the appId claim in the skill request is in the list of skills configured for this bot. + var appId = JwtTokenValidation.GetAppIdFromClaims(claims); + if (!_allowedSkills.Contains(appId)) + { + throw new UnauthorizedAccessException($"Received a request from an application with an appID of \"{appId}\". To enable requests from this skill, add the skill to your configuration file."); + } + } + + return Task.CompletedTask; + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Bots/RootBot.cs b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Bots/RootBot.cs new file mode 100644 index 0000000000..75b20d5c30 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Bots/RootBot.cs @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Schema; +using Newtonsoft.Json; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Bots +{ + public class RootBot : ActivityHandler + where T : Dialog + { + private readonly ConversationState _conversationState; + private readonly Dialog _mainDialog; + + public RootBot(ConversationState conversationState, T mainDialog) + { + _conversationState = conversationState; + _mainDialog = mainDialog; + } + + public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) + { + if (turnContext.Activity.Type == ActivityTypes.ConversationUpdate) + { + // Let the base class handle the activity (this will trigger OnMembersAdded). + await base.OnTurnAsync(turnContext, cancellationToken); + } + else + { + // Run the Dialog with the Activity. + await _mainDialog.RunAsync(turnContext, _conversationState.CreateProperty("DialogState"), cancellationToken); + } + + // Save any state changes that might have occurred during the turn. + await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken); + } + + protected override async Task OnMembersAddedAsync(IList membersAdded, ITurnContext turnContext, CancellationToken cancellationToken) + { + foreach (var member in membersAdded) + { + // Greet anyone that was not the target (recipient) of this message. + // To learn more about Adaptive Cards, see https://aka.ms/msbot-adaptivecards. + if (member.Id != turnContext.Activity.Recipient.Id) + { + var welcomeCard = CreateAdaptiveCardAttachment(); + var activity = MessageFactory.Attachment(welcomeCard); + activity.Speak = "Welcome to the waterfall host bot"; + await turnContext.SendActivityAsync(activity, cancellationToken); + await _mainDialog.RunAsync(turnContext, _conversationState.CreateProperty("DialogState"), cancellationToken); + } + } + } + + // Load attachment from embedded resource. + private Attachment CreateAdaptiveCardAttachment() + { + var cardResourcePath = "Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Cards.welcomeCard.json"; + + using (var stream = GetType().Assembly.GetManifestResourceStream(cardResourcePath)) + { + using (var reader = new StreamReader(stream)) + { + var adaptiveCard = reader.ReadToEnd(); + return new Attachment + { + ContentType = "application/vnd.microsoft.card.adaptive", + Content = JsonConvert.DeserializeObject(adaptiveCard) + }; + } + } + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Cards/welcomeCard.json b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Cards/welcomeCard.json new file mode 100644 index 0000000000..1fc98a2237 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Cards/welcomeCard.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.0", + "body": [ + { + "type": "Image", + "url": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQtB3AwMUeNoq4gUBGe6Ocj8kyh3bXa9ZbV7u1fVKQoyKFHdkqU", + "size": "stretch" + }, + { + "type": "TextBlock", + "spacing": "Medium", + "size": "Medium", + "weight": "Bolder", + "text": "Welcome to the Skill Dialog Sample!", + "wrap": true, + "maxLines": 0, + "color": "Accent" + }, + { + "type": "TextBlock", + "size": "default", + "text": "This sample allows you to connect to a skill using a SkillDialog and invoke several actions.", + "wrap": true, + "maxLines": 0 + } + ] +} \ No newline at end of file diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Controllers/BotController.cs b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Controllers/BotController.cs new file mode 100644 index 0000000000..dcf63dabd6 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Controllers/BotController.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Extensions.Logging; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Controllers +{ + // This ASP Controller is created to handle a request. Dependency injection will provide the Adapter and IBot + // implementation at runtime. Multiple different IBot implementations running at different endpoints can be + // achieved by specifying a more specific type for the bot constructor argument. + [Route("api/messages")] + [ApiController] + public class BotController : ControllerBase + { + private readonly IBotFrameworkHttpAdapter _adapter; + private readonly IBot _bot; + private readonly ILogger _logger; + + public BotController(BotFrameworkHttpAdapter adapter, IBot bot, ILogger logger) + { + _adapter = adapter; + _bot = bot; + _logger = logger; + } + + [HttpPost] + [HttpGet] + public async Task PostAsync() + { + try + { + // Delegate the processing of the HTTP POST to the adapter. + // The adapter will invoke the bot. + await _adapter.ProcessAsync(Request, Response, _bot); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error processing request"); + throw; + } + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Controllers/SkillController.cs b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Controllers/SkillController.cs new file mode 100644 index 0000000000..82e404550b --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Controllers/SkillController.cs @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Bot.Builder.Skills; +using Microsoft.Bot.Schema; +using Microsoft.Extensions.Logging; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Controllers +{ + /// + /// A controller that handles skill replies to the bot. + /// This example uses the that is registered as a in startup.cs. + /// + [ApiController] + [Route("api/skills")] + public class SkillController : ChannelServiceController + { + private readonly ILogger _logger; + + public SkillController(ChannelServiceHandler handler, ILogger logger) + : base(handler) + { + _logger = logger; + } + + public override Task ReplyToActivityAsync(string conversationId, string activityId, Activity activity) + { + try + { + return base.ReplyToActivityAsync(conversationId, activityId, activity); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error processing request"); + throw; + } + } + + public override Task SendToConversationAsync(string conversationId, Activity activity) + { + try + { + return base.SendToConversationAsync(conversationId, activity); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error processing request"); + throw; + } + } + + public override Task UpdateActivityAsync(string conversationId, string activityId, Activity activity) + { + try + { + return base.UpdateActivityAsync(conversationId, activityId, activity); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error processing request"); + throw; + } + } + + public override Task DeleteActivityAsync(string conversationId, string activityId) + { + try + { + return base.DeleteActivityAsync(conversationId, activityId); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error processing request"); + throw; + } + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Dialogs/MainDialog.cs b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Dialogs/MainDialog.cs new file mode 100644 index 0000000000..17b08d3f58 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Dialogs/MainDialog.cs @@ -0,0 +1,345 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Builder.Dialogs.Choices; +using Microsoft.Bot.Builder.Integration.AspNet.Core.Skills; +using Microsoft.Bot.Builder.Skills; +using Microsoft.Bot.Connector.Authentication; +using Microsoft.Bot.Schema; +using Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Dialogs.Sso; +using Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Skills; +using Microsoft.Extensions.Configuration; +using Newtonsoft.Json; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Dialogs +{ + /// + /// The main dialog for this bot. It uses a to call skills. + /// + public class MainDialog : ComponentDialog + { + // State property key that stores the active skill (used in AdapterWithErrorHandler to terminate the skills on error). + public static readonly string ActiveSkillPropertyName = $"{typeof(MainDialog).FullName}.ActiveSkillProperty"; + + private const string SsoDialogPrefix = "Sso"; + private readonly IStatePropertyAccessor _activeSkillProperty; + private readonly string _deliveryMode = $"{typeof(MainDialog).FullName}.DeliveryMode"; + private readonly string _selectedSkillKey = $"{typeof(MainDialog).FullName}.SelectedSkillKey"; + private readonly SkillsConfiguration _skillsConfig; + private readonly IConfiguration _configuration; + + // Dependency injection uses this constructor to instantiate MainDialog. + public MainDialog(ConversationState conversationState, SkillConversationIdFactoryBase conversationIdFactory, SkillHttpClient skillClient, SkillsConfiguration skillsConfig, IConfiguration configuration) + : base(nameof(MainDialog)) + { + _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); + + var botId = configuration.GetSection(MicrosoftAppCredentials.MicrosoftAppIdKey)?.Value; + + _skillsConfig = skillsConfig ?? throw new ArgumentNullException(nameof(skillsConfig)); + + if (skillClient == null) + { + throw new ArgumentNullException(nameof(skillClient)); + } + + if (conversationState == null) + { + throw new ArgumentNullException(nameof(conversationState)); + } + + // Create state property to track the active skill. + _activeSkillProperty = conversationState.CreateProperty(ActiveSkillPropertyName); + + // Register the tangent dialog for testing tangents and resume + AddDialog(new TangentDialog()); + + // Create and add SkillDialog instances for the configured skills. + AddSkillDialogs(conversationState, conversationIdFactory, skillClient, skillsConfig, botId); + + // Add ChoicePrompt to render available delivery modes. + AddDialog(new ChoicePrompt("DeliveryModePrompt")); + + // Add ChoicePrompt to render available groups of skills. + AddDialog(new ChoicePrompt("SkillGroupPrompt")); + + // Add ChoicePrompt to render available skills. + AddDialog(new ChoicePrompt("SkillPrompt")); + + // Add ChoicePrompt to render skill actions. + AddDialog(new ChoicePrompt("SkillActionPrompt")); + + // Special case: register SSO dialogs for skills that support SSO actions. + AddSsoDialogs(configuration); + + // Add main waterfall dialog for this bot. + var waterfallSteps = new WaterfallStep[] + { + SelectDeliveryModeStepAsync, + SelectSkillGroupStepAsync, + SelectSkillStepAsync, + SelectSkillActionStepAsync, + CallSkillActionStepAsync, + FinalStepAsync + }; + AddDialog(new WaterfallDialog(nameof(WaterfallDialog), waterfallSteps)); + + // The initial child Dialog to run. + InitialDialogId = nameof(WaterfallDialog); + } + + /// + /// This override is used to test the "abort" command to interrupt skills from the parent and + /// also to test the "tangent" command to start a tangent and resume a skill. + /// + /// The inner for the current turn of conversation. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// A representing the asynchronous operation. + protected override async Task OnContinueDialogAsync(DialogContext innerDc, CancellationToken cancellationToken = default) + { + // This is an example on how to cancel a SkillDialog that is currently in progress from the parent bot. + var activeSkill = await _activeSkillProperty.GetAsync(innerDc.Context, () => null, cancellationToken); + var activity = innerDc.Context.Activity; + if (activeSkill != null && activity.Type == ActivityTypes.Message && !string.IsNullOrWhiteSpace(activity.Text) && activity.Text.Equals("abort", StringComparison.CurrentCultureIgnoreCase)) + { + // Cancel all dialogs when the user says abort. + // The SkillDialog automatically sends an EndOfConversation message to the skill to let the + // skill know that it needs to end its current dialogs, too. + await innerDc.CancelAllDialogsAsync(cancellationToken); + return await innerDc.ReplaceDialogAsync(InitialDialogId, "Canceled! \n\n What delivery mode would you like to use?", cancellationToken); + } + + // Sample to test a tangent when in the middle of a skill conversation. + if (activeSkill != null && activity.Type == ActivityTypes.Message && !string.IsNullOrWhiteSpace(activity.Text) && activity.Text.Equals("tangent", StringComparison.CurrentCultureIgnoreCase)) + { + // Start tangent. + return await innerDc.BeginDialogAsync(nameof(TangentDialog), cancellationToken: cancellationToken); + } + + return await base.OnContinueDialogAsync(innerDc, cancellationToken); + } + + // Render a prompt to select the delivery mode to use. + private async Task SelectDeliveryModeStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + // Create the PromptOptions with the delivery modes supported. + var messageText = stepContext.Options?.ToString() ?? "What delivery mode would you like to use?"; + const string retryMessageText = "That was not a valid choice, please select a valid delivery mode."; + var choices = new List + { + new Choice(DeliveryModes.Normal), + new Choice(DeliveryModes.ExpectReplies) + }; + var options = new PromptOptions + { + Prompt = MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput), + RetryPrompt = MessageFactory.Text(retryMessageText, retryMessageText, InputHints.ExpectingInput), + Choices = choices + }; + + // Prompt the user to select a delivery mode. + return await stepContext.PromptAsync("DeliveryModePrompt", options, cancellationToken); + } + + // Render a prompt to select the group of skills to use. + private async Task SelectSkillGroupStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + // Remember the delivery mode selected by the user. + stepContext.Values[_deliveryMode] = ((FoundChoice)stepContext.Result).Value; + + // Create the PromptOptions with the types of supported skills. + const string messageText = "What group of skills would you like to use?"; + const string retryMessageText = "That was not a valid choice, please select a valid skill group."; + + // Use linq to get a list of the groups for the skills in skillsConfig. + var choices = _skillsConfig.Skills + .GroupBy(skill => skill.Value.Group) + .Select(skillGroup => new Choice(skillGroup.First().Value.Group)) + .ToList(); + + var options = new PromptOptions + { + Prompt = MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput), + RetryPrompt = MessageFactory.Text(retryMessageText, retryMessageText, InputHints.ExpectingInput), + Choices = choices + }; + + // Prompt the user to select a type of skill. + return await stepContext.PromptAsync("SkillGroupPrompt", options, cancellationToken); + } + + // Render a prompt to select the skill to call. + private async Task SelectSkillStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var skillGroup = ((FoundChoice)stepContext.Result).Value; + + // Create the PromptOptions from the skill configuration which contain the list of configured skills. + const string messageText = "What skill would you like to call?"; + const string retryMessageText = "That was not a valid choice, please select a valid skill."; + + // Use linq to return the skills for the selected group. + var choices = _skillsConfig.Skills + .Where(skill => skill.Value.Group == skillGroup) + .Select(skill => new Choice(skill.Value.Id)) + .ToList(); + + var options = new PromptOptions + { + Prompt = MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput), + RetryPrompt = MessageFactory.Text(retryMessageText, retryMessageText, InputHints.ExpectingInput), + Choices = choices + }; + + // Prompt the user to select a skill. + return await stepContext.PromptAsync("SkillPrompt", options, cancellationToken); + } + + // Render a prompt to select the begin action for the skill. + private async Task SelectSkillActionStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + // Get the skill info based on the selected skill. + var selectedSkillId = ((FoundChoice)stepContext.Result).Value; + var deliveryMode = stepContext.Values[_deliveryMode].ToString(); + var v3Bots = new List { "EchoSkillBotDotNetV3", "EchoSkillBotJSV3" }; + + // Exclude v3 bots from ExpectReplies + if (deliveryMode == DeliveryModes.ExpectReplies && v3Bots.Contains(selectedSkillId)) + { + await stepContext.Context.SendActivityAsync(MessageFactory.Text("V3 Bots do not support 'expectReplies' delivery mode."), cancellationToken); + + // Restart setup dialog + return await stepContext.ReplaceDialogAsync(InitialDialogId, null, cancellationToken); + } + + var selectedSkill = _skillsConfig.Skills.FirstOrDefault(keyValuePair => keyValuePair.Value.Id == selectedSkillId).Value; + + // Remember the skill selected by the user. + stepContext.Values[_selectedSkillKey] = selectedSkill; + + var skillActionChoices = selectedSkill.GetActions().Select(action => new Choice(action)).ToList(); + if (skillActionChoices.Count == 1) + { + // The skill only supports one action (e.g. Echo), skip the prompt. + return await stepContext.NextAsync(new FoundChoice { Value = skillActionChoices[0].Value }, cancellationToken); + } + + // Create the PromptOptions with the actions supported by the selected skill. + var messageText = $"Select an action to send to **{selectedSkill.Id}**."; + var options = new PromptOptions + { + Prompt = MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput), + Choices = skillActionChoices + }; + + // Prompt the user to select a skill action. + return await stepContext.PromptAsync("SkillActionPrompt", options, cancellationToken); + } + + // Starts the SkillDialog based on the user's selections. + private async Task CallSkillActionStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var selectedSkill = (SkillDefinition)stepContext.Values[_selectedSkillKey]; + + // Save active skill in state. + await _activeSkillProperty.SetAsync(stepContext.Context, selectedSkill, cancellationToken); + + // Create the initial activity to call the skill. + var skillActivity = selectedSkill.CreateBeginActivity(((FoundChoice)stepContext.Result).Value); + if (skillActivity.Name == "Sso") + { + // Special case, we start the SSO dialog to prepare the host to call the skill. + return await stepContext.BeginDialogAsync($"{SsoDialogPrefix}{selectedSkill.Id}", cancellationToken: cancellationToken); + } + + // We are manually creating the activity to send to the skill; ensure we add the ChannelData and Properties + // from the original activity so the skill gets them. + // Note: this is not necessary if we are just forwarding the current activity from context. + skillActivity.ChannelData = stepContext.Context.Activity.ChannelData; + skillActivity.Properties = stepContext.Context.Activity.Properties; + + // Create the BeginSkillDialogOptions and assign the activity to send. + var skillDialogArgs = new BeginSkillDialogOptions { Activity = skillActivity }; + + if (stepContext.Values[_deliveryMode].ToString() == DeliveryModes.ExpectReplies) + { + skillDialogArgs.Activity.DeliveryMode = DeliveryModes.ExpectReplies; + } + + // Start the skillDialog instance with the arguments. + return await stepContext.BeginDialogAsync(selectedSkill.Id, skillDialogArgs, cancellationToken); + } + + // The SkillDialog has ended, render the results (if any) and restart MainDialog. + private async Task FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var activeSkill = await _activeSkillProperty.GetAsync(stepContext.Context, () => null, cancellationToken); + + // Check if the skill returned any results and display them. + if (stepContext.Result != null) + { + var message = $"Skill \"{activeSkill.Id}\" invocation complete."; + message += $" Result: {JsonConvert.SerializeObject(stepContext.Result)}"; + await stepContext.Context.SendActivityAsync(MessageFactory.Text(message, message, inputHint: InputHints.IgnoringInput), cancellationToken: cancellationToken); + } + + // Clear the delivery mode selected by the user. + stepContext.Values[_deliveryMode] = null; + + // Clear the skill selected by the user. + stepContext.Values[_selectedSkillKey] = null; + + // Clear active skill in state. + await _activeSkillProperty.DeleteAsync(stepContext.Context, cancellationToken); + + // Restart the main dialog with a different message the second time around. + return await stepContext.ReplaceDialogAsync(InitialDialogId, $"Done with \"{activeSkill.Id}\". \n\n What delivery mode would you like to use?", cancellationToken); + } + + // Helper method that creates and adds SkillDialog instances for the configured skills. + private void AddSkillDialogs(ConversationState conversationState, SkillConversationIdFactoryBase conversationIdFactory, SkillHttpClient skillClient, SkillsConfiguration skillsConfig, string botId) + { + foreach (var skillInfo in _skillsConfig.Skills.Values) + { + // Create the dialog options. + var skillDialogOptions = new SkillDialogOptions + { + BotId = botId, + ConversationIdFactory = conversationIdFactory, + SkillClient = skillClient, + SkillHostEndpoint = skillsConfig.SkillHostEndpoint, + ConversationState = conversationState, + Skill = skillInfo + }; + + // Add a SkillDialog for the selected skill. + AddDialog(new SkillDialog(skillDialogOptions, skillInfo.Id)); + } + } + + // Special case. + // SSO needs a dialog in the host to allow the user to sign in. + // We create and several SsoDialog instances for each skill that supports SSO. + private void AddSsoDialogs(IConfiguration configuration) + { + var connectionName = configuration.GetSection("SsoConnectionName")?.Value; + foreach (var ssoSkillDialog in Dialogs.GetDialogs().Where(dialog => dialog.Id.StartsWith("WaterfallSkillBot")).ToList()) + { + AddDialog(new SsoDialog($"{SsoDialogPrefix}{ssoSkillDialog.Id}", ssoSkillDialog, connectionName)); + } + + connectionName = configuration.GetSection("SsoConnectionNameTeams")?.Value; + foreach (var ssoSkillDialog in Dialogs.GetDialogs().Where(dialog => dialog.Id.StartsWith("TeamsSkillBot")).ToList()) + { + AddDialog(new SsoDialog($"{SsoDialogPrefix}{ssoSkillDialog.Id}", ssoSkillDialog, connectionName)); + } + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Dialogs/Sso/SsoDialog.cs b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Dialogs/Sso/SsoDialog.cs new file mode 100644 index 0000000000..9e88adc417 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Dialogs/Sso/SsoDialog.cs @@ -0,0 +1,141 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Builder.Dialogs.Choices; +using Microsoft.Bot.Schema; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Dialogs.Sso +{ + /// + /// Helps prepare the host for SSO operations and provides helpers to check the status and invoke the skill. + /// + public class SsoDialog : ComponentDialog + { + private readonly string _connectionName; + private readonly string _skillDialogId; + + public SsoDialog(string dialogId, Dialog ssoSkillDialog, string connectionName) + : base(dialogId) + { + _connectionName = connectionName; + _skillDialogId = ssoSkillDialog.Id; + + AddDialog(new ChoicePrompt("ActionStepPrompt")); + AddDialog(new SsoSignInDialog(_connectionName)); + AddDialog(ssoSkillDialog); + + var waterfallSteps = new WaterfallStep[] + { + PromptActionStepAsync, + HandleActionStepAsync, + PromptFinalStepAsync, + }; + + AddDialog(new WaterfallDialog(nameof(WaterfallDialog), waterfallSteps)); + + InitialDialogId = nameof(WaterfallDialog); + } + + private async Task PromptActionStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var messageText = "What SSO action do you want to perform?"; + var repromptMessageText = "That was not a valid choice, please select a valid choice."; + var options = new PromptOptions + { + Prompt = MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput), + RetryPrompt = MessageFactory.Text(repromptMessageText, repromptMessageText, InputHints.ExpectingInput), + Choices = await GetPromptChoicesAsync(stepContext, cancellationToken) + }; + + // Prompt the user to select a skill. + return await stepContext.PromptAsync("ActionStepPrompt", options, cancellationToken); + } + + // Create the prompt choices based on the current sign in status + private async Task> GetPromptChoicesAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var promptChoices = new List(); + var adapter = (IUserTokenProvider)stepContext.Context.Adapter; + var token = await adapter.GetUserTokenAsync(stepContext.Context, _connectionName, null, cancellationToken); + + if (token == null) + { + promptChoices.Add(new Choice("Login")); + + // Token exchange will fail when the host is not logged on and the skill should + // show a regular OAuthPrompt + promptChoices.Add(new Choice("Call Skill (without SSO)")); + } + else + { + promptChoices.Add(new Choice("Logout")); + promptChoices.Add(new Choice("Show token")); + promptChoices.Add(new Choice("Call Skill (with SSO)")); + } + + promptChoices.Add(new Choice("Back")); + + return promptChoices; + } + + private async Task HandleActionStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var action = ((FoundChoice)stepContext.Result).Value.ToLowerInvariant(); + + switch (action) + { + case "login": + return await stepContext.BeginDialogAsync(nameof(SsoSignInDialog), null, cancellationToken); + + case "logout": + var adapter = (IUserTokenProvider)stepContext.Context.Adapter; + await adapter.SignOutUserAsync(stepContext.Context, _connectionName, cancellationToken: cancellationToken); + await stepContext.Context.SendActivityAsync("You have been signed out.", cancellationToken: cancellationToken); + return await stepContext.NextAsync(cancellationToken: cancellationToken); + + case "show token": + var tokenProvider = (IUserTokenProvider)stepContext.Context.Adapter; + var token = await tokenProvider.GetUserTokenAsync(stepContext.Context, _connectionName, null, cancellationToken); + if (token == null) + { + await stepContext.Context.SendActivityAsync("User has no cached token.", cancellationToken: cancellationToken); + } + else + { + await stepContext.Context.SendActivityAsync($"Here is your current SSO token: {token.Token}", cancellationToken: cancellationToken); + } + + return await stepContext.NextAsync(cancellationToken: cancellationToken); + + case "call skill (with sso)": + case "call skill (without sso)": + var beginSkillActivity = new Activity + { + Type = ActivityTypes.Event, + Name = "Sso" + }; + + return await stepContext.BeginDialogAsync(_skillDialogId, new BeginSkillDialogOptions { Activity = beginSkillActivity }, cancellationToken); + + case "back": + return new DialogTurnResult(DialogTurnStatus.Complete); + + default: + // This should never be hit since the previous prompt validates the choice + throw new InvalidOperationException($"Unrecognized action: {action}"); + } + } + + private async Task PromptFinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + // Restart the dialog (we will exit when the user says end) + return await stepContext.ReplaceDialogAsync(InitialDialogId, null, cancellationToken); + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Dialogs/Sso/SsoSignInDialog.cs b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Dialogs/Sso/SsoSignInDialog.cs new file mode 100644 index 0000000000..9529a192d5 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Dialogs/Sso/SsoSignInDialog.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Schema; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Dialogs.Sso +{ + public class SsoSignInDialog : ComponentDialog + { + public SsoSignInDialog(string connectionName) + : base(nameof(SsoSignInDialog)) + { + AddDialog(new OAuthPrompt( + nameof(OAuthPrompt), + new OAuthPromptSettings + { + ConnectionName = connectionName, + Text = $"Sign in to the host bot using AAD for SSO and connection {connectionName}", + Title = "Sign In", + Timeout = 60000 + })); + AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[] { SignInStepAsync, DisplayTokenAsync })); + InitialDialogId = nameof(WaterfallDialog); + } + + private async Task SignInStepAsync(WaterfallStepContext context, CancellationToken cancellationToken) + { + return await context.BeginDialogAsync(nameof(OAuthPrompt), null, cancellationToken); + } + + private async Task DisplayTokenAsync(WaterfallStepContext context, CancellationToken cancellationToken) + { + if (!(context.Result is TokenResponse result)) + { + await context.Context.SendActivityAsync("No token was provided.", cancellationToken: cancellationToken); + } + else + { + await context.Context.SendActivityAsync($"Here is your token: {result.Token}", cancellationToken: cancellationToken); + } + + return await context.EndDialogAsync(null, cancellationToken); + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Dialogs/TangentDialog.cs b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Dialogs/TangentDialog.cs new file mode 100644 index 0000000000..1962319bab --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Dialogs/TangentDialog.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Schema; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Dialogs +{ + /// + /// A simple waterfall dialog used to test triggering tangents from . + /// + public class TangentDialog : ComponentDialog + { + public TangentDialog(string dialogId = nameof(TangentDialog)) + : base(dialogId) + { + AddDialog(new TextPrompt(nameof(TextPrompt))); + var waterfallSteps = new WaterfallStep[] + { + Step1Async, + Step2Async, + EndStepAsync + }; + AddDialog(new WaterfallDialog(nameof(WaterfallDialog), waterfallSteps)); + + InitialDialogId = nameof(WaterfallDialog); + } + + private async Task Step1Async(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var messageText = "Tangent step 1 of 2, say something."; + var promptMessage = MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput); + return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = promptMessage }, cancellationToken); + } + + private async Task Step2Async(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var messageText = "Tangent step 2 of 2, say something."; + var promptMessage = MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput); + return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = promptMessage }, cancellationToken); + } + + private async Task EndStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + return await stepContext.EndDialogAsync(cancellationToken: cancellationToken); + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Middleware/LoggerMiddleware.cs b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Middleware/LoggerMiddleware.cs new file mode 100644 index 0000000000..9485dffbda --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Middleware/LoggerMiddleware.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Bot.Schema; +using Microsoft.Extensions.Logging; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Middleware +{ + /// + /// Uses an ILogger instance to log user and bot messages. It filters out ContinueConversation events coming from skill responses. + /// + public class LoggerMiddleware : IMiddleware + { + private readonly ILogger _logger; + + public LoggerMiddleware(ILogger logger) + { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken = default) + { + // Note: skill responses will show as ContinueConversation events; we don't log those. + // We only log incoming messages from users. + if (!(turnContext.Activity.Type == ActivityTypes.Event && turnContext.Activity.Name == ActivityEventNames.ContinueConversation)) + { + var message = $"User said: {turnContext.Activity.Text} Type: \"{turnContext.Activity.Type}\" Name: \"{turnContext.Activity.Name}\""; + _logger.LogInformation(message); + } + + // Register outgoing handler. + turnContext.OnSendActivities(OutgoingHandler); + + // Continue processing messages. + await next(cancellationToken); + } + + private async Task OutgoingHandler(ITurnContext turnContext, List activities, Func> next) + { + foreach (var activity in activities) + { + var message = $"Bot said: {activity.Text} Type: \"{activity.Type}\" Name: \"{activity.Name}\""; + _logger.LogInformation(message); + } + + return await next(); + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Program.cs b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Program.cs new file mode 100644 index 0000000000..306d8a19a4 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Program.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Properties/launchSettings.json b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Properties/launchSettings.json new file mode 100644 index 0000000000..dafa6f0bc2 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Properties/launchSettings.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:35020", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "WaterfallHostBot": { + "commandName": "Project", + "launchBrowser": true, + "applicationUrl": "http://localhost:35020", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Skills/EchoSkill.cs b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Skills/EchoSkill.cs new file mode 100644 index 0000000000..7ff87fa16c --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Skills/EchoSkill.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using Microsoft.Bot.Schema; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Skills +{ + public class EchoSkill : SkillDefinition + { + private enum SkillAction + { + Message + } + + public override IList GetActions() + { + return new List { SkillAction.Message.ToString() }; + } + + public override Activity CreateBeginActivity(string actionId) + { + if (!Enum.TryParse(actionId, true, out _)) + { + throw new InvalidOperationException($"Unable to create begin activity for \"{actionId}\"."); + } + + // We only support one activity for Echo so no further checks are needed + return new Activity(ActivityTypes.Message) + { + Name = SkillAction.Message.ToString(), + Text = "Begin the Echo Skill." + }; + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Skills/SkillDefinition.cs b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Skills/SkillDefinition.cs new file mode 100644 index 0000000000..e150259058 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Skills/SkillDefinition.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using Microsoft.Bot.Builder.Skills; +using Microsoft.Bot.Schema; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Skills +{ + /// + /// Extends and provides methods to return the actions and the begin activity to start a skill. + /// This class also exposes a group property to render skill groups and narrow down the available options. + /// + /// + /// This is just a temporary implementation, ideally, this should be replaced by logic that parses a manifest and creates + /// what's needed. + /// + public class SkillDefinition : BotFrameworkSkill + { + public string Group { get; set; } + + public virtual IList GetActions() + { + throw new NotImplementedException(); + } + + public virtual Activity CreateBeginActivity(string actionId) + { + throw new NotImplementedException(); + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Skills/TeamsSkill.cs b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Skills/TeamsSkill.cs new file mode 100644 index 0000000000..3975757a47 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Skills/TeamsSkill.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using Microsoft.Bot.Schema; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Skills +{ + public class TeamsSkill : SkillDefinition + { + private enum SkillAction + { + TeamsTaskModule, + TeamsCardAction, + TeamsConversation, + Cards, + Proactive, + Attachment, + Auth, + Sso, + Echo, + FileUpload, + Delete, + Update, + } + + public override IList GetActions() + { + return Enum.GetNames(typeof(SkillAction)); + } + + public override Activity CreateBeginActivity(string actionId) + { + if (!Enum.TryParse(actionId, true, out var skillAction)) + { + throw new InvalidOperationException($"Unable to create begin activity for \"{actionId}\"."); + } + + // We don't support special parameters in these skills so a generic event with the right name + // will do in this case. + return new Activity(ActivityTypes.Event) { Name = skillAction.ToString() }; + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Skills/WaterfallSkill.cs b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Skills/WaterfallSkill.cs new file mode 100644 index 0000000000..9725c05217 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Skills/WaterfallSkill.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using Microsoft.Bot.Schema; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Skills +{ + public class WaterfallSkill : SkillDefinition + { + private enum SkillAction + { + Cards, + Proactive, + Auth, + MessageWithAttachment, + Sso, + FileUpload, + Echo, + Delete, + Update + } + + public override IList GetActions() + { + return Enum.GetNames(typeof(SkillAction)); + } + + public override Activity CreateBeginActivity(string actionId) + { + if (!Enum.TryParse(actionId, true, out var skillAction)) + { + throw new InvalidOperationException($"Unable to create begin activity for \"{actionId}\"."); + } + + // We don't support special parameters in these skills so a generic event with the right name + // will do in this case. + return new Activity(ActivityTypes.Event) { Name = skillAction.ToString() }; + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/SkillsConfiguration.cs b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/SkillsConfiguration.cs new file mode 100644 index 0000000000..2c80655e23 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/SkillsConfiguration.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Skills; +using Microsoft.Extensions.Configuration; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot +{ + /// + /// A helper class that loads Skills information from configuration. + /// + /// + /// This class loads the skill settings from config and casts them into derived types of + /// so we can render prompts with the skills and in their groups. + /// + public class SkillsConfiguration + { + public SkillsConfiguration(IConfiguration configuration) + { + var section = configuration?.GetSection("BotFrameworkSkills"); + var skills = section?.Get(); + if (skills != null) + { + foreach (var skill in skills) + { + Skills.Add(skill.Id, CreateSkillDefinition(skill)); + } + } + + var skillHostEndpoint = configuration?.GetValue(nameof(SkillHostEndpoint)); + if (!string.IsNullOrWhiteSpace(skillHostEndpoint)) + { + SkillHostEndpoint = new Uri(skillHostEndpoint); + } + } + + public Uri SkillHostEndpoint { get; } + + public Dictionary Skills { get; } = new Dictionary(); + + private static SkillDefinition CreateSkillDefinition(SkillDefinition skill) + { + // Note: we hard code this for now, we should dynamically create instances based on the manifests. + // For now, this code creates a strong typed version of the SkillDefinition based on the skill group + // and copies the info from settings into it. + SkillDefinition skillDefinition; + switch (skill.Group) + { + case "Echo": + skillDefinition = ObjectPath.Assign(new EchoSkill(), skill); + break; + case "Waterfall": + skillDefinition = ObjectPath.Assign(new WaterfallSkill(), skill); + break; + case "Teams": + skillDefinition = ObjectPath.Assign(new TeamsSkill(), skill); + break; + default: + throw new Exception($"Unable to find definition class for {skill.Id}."); + } + + return skillDefinition; + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Startup.cs b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Startup.cs new file mode 100644 index 0000000000..3795f176d8 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/Startup.cs @@ -0,0 +1,98 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.BotFramework; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Bot.Builder.Integration.AspNet.Core.Skills; +using Microsoft.Bot.Builder.Skills; +using Microsoft.Bot.Connector.Authentication; +using Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Authentication; +using Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Bots; +using Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Dialogs; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers() + .AddNewtonsoftJson(); + + // Register credential provider. + services.AddSingleton(); + + // Register the skills configuration class. + services.AddSingleton(); + + // Register AuthConfiguration to enable custom claim validation. + services.AddSingleton(sp => new AuthenticationConfiguration { ClaimsValidator = new AllowedSkillsClaimsValidator(sp.GetService()) }); + + // Register the Bot Framework Adapter with error handling enabled. + // Note: some classes expect a BotAdapter and some expect a BotFrameworkHttpAdapter, so + // register the same adapter instance for both types. + services.AddSingleton(); + services.AddSingleton(sp => sp.GetService()); + + // Register the skills conversation ID factory, the client and the request handler. + services.AddSingleton(); + services.AddHttpClient(); + + //services.AddSingleton(); + services.AddSingleton(); + + // Register the storage we'll be using for User and Conversation state. (Memory is great for testing purposes.) + services.AddSingleton(); + + // Register Conversation state (used by the Dialog system itself). + services.AddSingleton(); + services.AddSingleton(); + + // Register the MainDialog that will be run by the bot. + services.AddSingleton(); + + // Register the bot as a transient. In this case the ASP Controller is expecting an IBot. + services.AddTransient>(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseDefaultFiles(); + app.UseStaticFiles(); + + // Uncomment this to support HTTPS. + // app.UseHttpsRedirection(); + + app.UseRouting(); + + app.UseWebSockets(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/TeamsAppManifest/icon-color.png b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/TeamsAppManifest/icon-color.png new file mode 100644 index 0000000000..48a2de1330 Binary files /dev/null and b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/TeamsAppManifest/icon-color.png differ diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/TeamsAppManifest/icon-outline.png b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/TeamsAppManifest/icon-outline.png new file mode 100644 index 0000000000..dbfa927729 Binary files /dev/null and b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/TeamsAppManifest/icon-outline.png differ diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/TeamsAppManifest/manifest.json b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/TeamsAppManifest/manifest.json new file mode 100644 index 0000000000..8811c44538 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/TeamsAppManifest/manifest.json @@ -0,0 +1,50 @@ +{ + "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.7/MicrosoftTeams.schema.json", + "manifestVersion": "1.7", + "version": "1.0.0", + "id": "", + "packageName": "com.microsoft.botframeworkfunctionaltests.teamswaterfall", + "developer": { + "name": "Microsoft", + "websiteUrl": "https://example.azurewebsites.net", + "privacyUrl": "https://example.azurewebsites.net/privacy", + "termsOfUseUrl": "https://example.azurewebsites.net/termsofuse" + }, + "icons": { + "color": "icon-color.png", + "outline": "icon-outline.png" + }, + "name": { + "short": "Teams Waterfall", + "full": "Teams Waterfall" + }, + "description": { + "short": "Teams Waterfall", + "full": "Teams Waterfall" + }, + "accentColor": "#FFFFFF", + "bots": [ + { + "botId": "", + "scopes": [ + "personal", + "groupchat", + "team" + ], + "supportsFiles": true, + "isNotificationOnly": false + } + ], + "webApplicationInfo": { + "id": "", + "resource": "api://botid-" + }, + "permissions": [ + "identity", + "messageTeamMembers" + ], + "validDomains": [ + "token.botframework.com", + "ngrok.io" + ] +} \ No newline at end of file diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/TokenExchangeSkillHandler.cs b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/TokenExchangeSkillHandler.cs new file mode 100644 index 0000000000..18871b357c --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/TokenExchangeSkillHandler.cs @@ -0,0 +1,178 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Globalization; +using System.Linq; +using System.Security.Claims; +using System.Security.Principal; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core.Skills; +using Microsoft.Bot.Builder.Skills; +using Microsoft.Bot.Connector.Authentication; +using Microsoft.Bot.Schema; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Newtonsoft.Json.Linq; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot +{ + /// + /// A specialized to support SSO Token exchanges. + /// + public class TokenExchangeSkillHandler : SkillHandler + { + private const string WaterfallSkillBot = "WaterfallSkillBot"; + + private readonly BotAdapter _adapter; + private readonly SkillsConfiguration _skillsConfig; + private readonly SkillHttpClient _skillClient; + private readonly string _botId; + private readonly SkillConversationIdFactoryBase _conversationIdFactory; + private readonly ILogger _logger; + private readonly IExtendedUserTokenProvider _tokenExchangeProvider; + private readonly IConfiguration _configuration; + + public TokenExchangeSkillHandler( + BotAdapter adapter, + IBot bot, + IConfiguration configuration, + SkillConversationIdFactoryBase conversationIdFactory, + SkillsConfiguration skillsConfig, + SkillHttpClient skillClient, + ICredentialProvider credentialProvider, + AuthenticationConfiguration authConfig, + IChannelProvider channelProvider = null, + ILogger logger = null) + : base(adapter, bot, conversationIdFactory, credentialProvider, authConfig, channelProvider, logger) + { + _adapter = adapter; + _tokenExchangeProvider = adapter as IExtendedUserTokenProvider; + if (_tokenExchangeProvider == null) + { + throw new ArgumentException($"{nameof(adapter)} does not support token exchange"); + } + + _configuration = configuration; + _skillsConfig = skillsConfig; + _skillClient = skillClient; + _conversationIdFactory = conversationIdFactory; + _logger = logger ?? NullLogger.Instance; + _botId = configuration.GetSection(MicrosoftAppCredentials.MicrosoftAppIdKey)?.Value; + } + + protected override async Task OnSendToConversationAsync(ClaimsIdentity claimsIdentity, string conversationId, Activity activity, CancellationToken cancellationToken = default(CancellationToken)) + { + if (await InterceptOAuthCards(claimsIdentity, activity).ConfigureAwait(false)) + { + return new ResourceResponse(Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture)); + } + + return await base.OnSendToConversationAsync(claimsIdentity, conversationId, activity, cancellationToken).ConfigureAwait(false); + } + + protected override async Task OnReplyToActivityAsync(ClaimsIdentity claimsIdentity, string conversationId, string activityId, Activity activity, CancellationToken cancellationToken = default(CancellationToken)) + { + if (await InterceptOAuthCards(claimsIdentity, activity).ConfigureAwait(false)) + { + return new ResourceResponse(Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture)); + } + + return await base.OnReplyToActivityAsync(claimsIdentity, conversationId, activityId, activity, cancellationToken).ConfigureAwait(false); + } + + private BotFrameworkSkill GetCallingSkill(ClaimsIdentity claimsIdentity) + { + var appId = JwtTokenValidation.GetAppIdFromClaims(claimsIdentity.Claims); + + if (string.IsNullOrWhiteSpace(appId)) + { + return null; + } + + return _skillsConfig.Skills.Values.FirstOrDefault(s => string.Equals(s.AppId, appId, StringComparison.InvariantCultureIgnoreCase)); + } + + private async Task InterceptOAuthCards(ClaimsIdentity claimsIdentity, Activity activity) + { + var oauthCardAttachment = activity.Attachments?.FirstOrDefault(a => a?.ContentType == OAuthCard.ContentType); + if (oauthCardAttachment != null) + { + var targetSkill = GetCallingSkill(claimsIdentity); + if (targetSkill != null) + { + var oauthCard = ((JObject)oauthCardAttachment.Content).ToObject(); + + if (!string.IsNullOrWhiteSpace(oauthCard?.TokenExchangeResource?.Uri)) + { + using (var context = new TurnContext(_adapter, activity)) + { + context.TurnState.Add("BotIdentity", claimsIdentity); + + // We need to know what connection name to use for the token exchange so we figure that out here + var connectionName = targetSkill.Id.Contains(WaterfallSkillBot) ? _configuration.GetSection("SsoConnectionName").Value : _configuration.GetSection("SsoConnectionNameTeams").Value; + + if (string.IsNullOrEmpty(connectionName)) + { + throw new ArgumentNullException("The connection name cannot be null."); + } + + // AAD token exchange + try + { + var result = await _tokenExchangeProvider.ExchangeTokenAsync( + context, + connectionName, + activity.Recipient.Id, + new TokenExchangeRequest() { Uri = oauthCard.TokenExchangeResource.Uri }).ConfigureAwait(false); + + if (!string.IsNullOrEmpty(result?.Token)) + { + // If token above is null, then SSO has failed and hence we return false. + // If not, send an invoke to the skill with the token. + return await SendTokenExchangeInvokeToSkillAsync(activity, oauthCard.TokenExchangeResource.Id, result.Token, oauthCard.ConnectionName, targetSkill, default).ConfigureAwait(false); + } + } + catch (Exception ex) + { + // Show oauth card if token exchange fails. + _logger.LogWarning("Unable to exchange token.", ex); + return false; + } + + return false; + } + } + } + } + + return false; + } + + private async Task SendTokenExchangeInvokeToSkillAsync(Activity incomingActivity, string id, string token, string connectionName, BotFrameworkSkill targetSkill, CancellationToken cancellationToken) + { + var activity = incomingActivity.CreateReply(); + activity.Type = ActivityTypes.Invoke; + activity.Name = SignInConstants.TokenExchangeOperationName; + activity.Value = new TokenExchangeInvokeRequest() + { + Id = id, + Token = token, + ConnectionName = connectionName, + }; + + var skillConversationReference = await _conversationIdFactory.GetSkillConversationReferenceAsync(incomingActivity.Conversation.Id, cancellationToken).ConfigureAwait(false); + activity.Conversation = skillConversationReference.ConversationReference.Conversation; + activity.ServiceUrl = skillConversationReference.ConversationReference.ServiceUrl; + + // route the activity to the skill + var response = await _skillClient.PostActivityAsync(_botId, targetSkill, _skillsConfig.SkillHostEndpoint, activity, cancellationToken); + + // Check response status: true if success, false if failure + return response.Status >= 200 && response.Status <= 299; + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/WaterfallHostBot.csproj b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/WaterfallHostBot.csproj new file mode 100644 index 0000000000..e1954961e8 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/WaterfallHostBot.csproj @@ -0,0 +1,24 @@ + + + + netcoreapp3.1 + latest + Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot + Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot + + + + + + + + + + + + + + + + + diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/appsettings.json b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/appsettings.json new file mode 100644 index 0000000000..da8a447b35 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/appsettings.json @@ -0,0 +1,90 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*", + + "MicrosoftAppId": "", + "MicrosoftAppPassword": "", + "SsoConnectionName": "", + "SsoConnectionNameTeams": "", + "SkillHostEndpoint": "http://localhost:35020/api/skills/", + "BotFrameworkSkills": [ + { + "Id": "EchoSkillBotDotNet", + "Group": "Echo", + "AppId": "", + "SkillEndpoint": "http://localhost:35400/api/messages" + }, + { + "Id": "EchoSkillBotDotNet21", + "Group": "Echo", + "AppId": "", + "SkillEndpoint": "http://localhost:35405/api/messages" + }, + { + "Id": "EchoSkillBotDotNetV3", + "Group": "Echo", + "AppId": "", + "SkillEndpoint": "http://localhost:35407/api/messages" + }, + { + "Id": "EchoSkillBotJS", + "Group": "Echo", + "AppId": "", + "SkillEndpoint": "http://localhost:36400/api/messages" + }, + { + "Id": "EchoSkillBotJSV3", + "Group": "Echo", + "AppId": "", + "SkillEndpoint": "http://localhost:36407/api/messages" + }, + { + "Id": "EchoSkillBotPython", + "Group": "Echo", + "AppId": "", + "SkillEndpoint": "http://localhost:37400/api/messages" + }, + { + "Id": "WaterfallSkillBotDotNet", + "Group": "Waterfall", + "AppId": "", + "SkillEndpoint": "http://localhost:35420/api/messages" + }, + { + "Id": "WaterfallSkillBotJS", + "Group": "Waterfall", + "AppId": "", + "SkillEndpoint": "http://localhost:36420/api/messages" + }, + { + "Id": "WaterfallSkillBotPython", + "Group": "Waterfall", + "AppId": "", + "SkillEndpoint": "http://localhost:37420/api/messages" + }, + { + "Id": "TeamsSkillBotDotNet", + "Group": "Teams", + "AppId": "", + "SkillEndpoint": "http://localhost:35430/api/messages" + }, + { + "Id": "TeamsSkillBotJS", + "Group": "Teams", + "AppId": "", + "SkillEndpoint": "http://localhost:36430/api/messages" + }, + { + "Id": "TeamsSkillBotPython", + "Group": "Teams", + "AppId": "", + "SkillEndpoint": "http://localhost:37430/api/messages" + } + ] +} diff --git a/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/wwwroot/default.htm b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/wwwroot/default.htm new file mode 100644 index 0000000000..4f00a9a6d4 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot/wwwroot/default.htm @@ -0,0 +1,420 @@ + + + + + + + WaterfallHostBot + + + + + +
+
+
+
WaterfallHostBot Bot
+
+
+
+
+
Your bot is ready!
+
You can test your bot in the Bot Framework Emulator
+ by connecting to http://localhost:3978/api/messages.
+ +
Visit Azure + Bot Service to register your bot and add it to
+ various channels. The bot's endpoint URL typically looks + like this:
+
https://your_bots_hostname/api/messages
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/Directory.Build.props b/tests/functional/Bots/DotNet/Consumers/Composer/Directory.Build.props new file mode 100644 index 0000000000..d4378dfc87 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/Composer/Directory.Build.props @@ -0,0 +1,15 @@ + + + + true + + + + + $(NoWarn);SA1412;NU1701 + + + + + + \ No newline at end of file diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/.gitignore b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/.gitignore new file mode 100644 index 0000000000..eaa9cf70cc --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/.gitignore @@ -0,0 +1,5 @@ +# files generated during the lubuild process +# IMPORTANT: In regular composer bots the generated folder should be excluded and regenerated on the build server +# or by the dev running composer locally. But in this case we include it so we don't have to run bf luis:cross-train +# in the build server +# generated/ diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/Controllers/BotController.cs b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/Controllers/BotController.cs new file mode 100644 index 0000000000..c87f3cba5e --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/Controllers/BotController.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs.Adaptive.Runtime.Settings; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace SimpleHostBotComposer.Controllers +{ + // This ASP Controller is created to handle a request. Dependency Injection will provide the Adapter and IBot + // implementation at runtime. Multiple different IBot implementations running at different endpoints can be + // achieved by specifying a more specific type for the bot constructor argument. + [ApiController] + public class BotController : ControllerBase + { + private readonly Dictionary _adapters = new Dictionary(); + private readonly IBot _bot; + private readonly ILogger _logger; + + public BotController( + IConfiguration configuration, + IEnumerable adapters, + IBot bot, + ILogger logger) + { + _bot = bot ?? throw new ArgumentNullException(nameof(bot)); + _logger = logger; + + var adapterSettings = configuration.GetSection(AdapterSettings.AdapterSettingsKey).Get>() ?? new List(); + adapterSettings.Add(AdapterSettings.CoreBotAdapterSettings); + + foreach (var adapter in adapters ?? throw new ArgumentNullException(nameof(adapters))) + { + var settings = adapterSettings.FirstOrDefault(s => s.Enabled && s.Type == adapter.GetType().FullName); + + if (settings != null) + { + _adapters.Add(settings.Route, adapter); + } + } + } + + [HttpPost] + [HttpGet] + [Route("api/{route}")] + public async Task PostAsync(string route) + { + if (string.IsNullOrEmpty(route)) + { + _logger.LogError($"PostAsync: No route provided."); + throw new ArgumentNullException(nameof(route)); + } + + if (_adapters.TryGetValue(route, out IBotFrameworkHttpAdapter adapter)) + { + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogInformation($"PostAsync: routed '{route}' to {adapter.GetType().Name}"); + } + + // Delegate the processing of the HTTP POST to the appropriate adapter. + // The adapter will invoke the bot. + await adapter.ProcessAsync(Request, Response, _bot).ConfigureAwait(false); + } + else + { + _logger.LogError($"PostAsync: No adapter registered and enabled for route {route}."); + throw new KeyNotFoundException($"No adapter registered and enabled for route {route}."); + } + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/Controllers/SkillController.cs b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/Controllers/SkillController.cs new file mode 100644 index 0000000000..4f90bad170 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/Controllers/SkillController.cs @@ -0,0 +1,62 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Bot.Schema; +using Microsoft.Extensions.Logging; + +namespace SimpleHostBotComposer.Controllers +{ + /// + /// A controller that handles skill replies to the bot. + /// + [ApiController] + [Route("api/skills")] + public class SkillController : ChannelServiceController + { + private readonly ILogger _logger; + + public SkillController(ChannelServiceHandlerBase handler, ILogger logger) + : base(handler) + { + _logger = logger; + } + + public override Task ReplyToActivityAsync(string conversationId, string activityId, Activity activity) + { + try + { + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug($"ReplyToActivityAsync: conversationId={conversationId}, activityId={activityId}"); + } + + return base.ReplyToActivityAsync(conversationId, activityId, activity); + } + catch (Exception ex) + { + _logger.LogError(ex, $"ReplyToActivityAsync: {ex}"); + throw; + } + } + + public override Task SendToConversationAsync(string conversationId, Activity activity) + { + try + { + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug($"SendToConversationAsync: conversationId={conversationId}"); + } + + return base.SendToConversationAsync(conversationId, activity); + } + catch (Exception ex) + { + _logger.LogError(ex, $"SendToConversationAsync: {ex}"); + throw; + } + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/Program.cs b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/Program.cs new file mode 100644 index 0000000000..2fd91a9203 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/Program.cs @@ -0,0 +1,33 @@ +using System; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Bot.Builder.Dialogs.Adaptive.Runtime.Extensions; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; + +namespace SimpleHostBotComposer +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureAppConfiguration((hostingContext, builder) => + { + var applicationRoot = AppDomain.CurrentDomain.BaseDirectory; + var environmentName = hostingContext.HostingEnvironment.EnvironmentName; + var settingsDirectory = "settings"; + + builder.AddBotRuntimeConfiguration(applicationRoot, settingsDirectory, environmentName); + + builder.AddCommandLine(args); + }) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } +} \ No newline at end of file diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/Properties/launchSettings.json b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/Properties/launchSettings.json new file mode 100644 index 0000000000..a9a6d4673c --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:35010/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "SimpleHostBotComposerDotNet": { + "commandName": "Project", + "launchBrowser": true, + "applicationUrl": "http://localhost:35010", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/README.md b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/README.md new file mode 100644 index 0000000000..b48822a762 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/README.md @@ -0,0 +1,27 @@ +# Welcome to your new bot + +This bot project was created using the Empty Bot template, and contains a minimal set of files necessary to have a working bot. + +## Next steps + +### Start building your bot + +Composer can help guide you through getting started building your bot. From your bot settings page (the wrench icon on the left navigation rail), click on the rocket-ship icon on the top right for some quick navigation links. + +Another great resource if you're just getting started is the **[guided tutorial](https://docs.microsoft.com/en-us/composer/tutorial/tutorial-introduction)** in our documentation. + +### Connect with your users + +Your bot comes pre-configured to connect to our Web Chat and DirectLine channels, but there are many more places you can connect your bot to - including Microsoft Teams, Telephony, DirectLine Speech, Slack, Facebook, Outlook and more. Check out all of the places you can connect to on the bot settings page. + +### Publish your bot to Azure from Composer + +Composer can help you provision the Azure resources necessary for your bot, and publish your bot to them. To get started, create a publishing profile from your bot settings page in Composer (the wrench icon on the left navigation rail). Make sure you only provision the optional Azure resources you need! + +### Extend your bot with packages + +From Package Manager in Composer you can find useful packages to help add additional pre-built functionality you can add to your bot - everything from simple dialogs & custom actions for working with specific scenarios to custom adapters for connecting your bot to users on clients like Facebook or Slack. + +### Extend your bot with code + +You can also extend your bot with code - simply open up the folder that was generated for you in the location you chose during the creation process with your favorite IDE (like Visual Studio). You can do things like create custom actions that can be used during dialog flows, create custom middleware to pre-process (or post-process) messages, and more. See [our documentation](https://aka.ms/bf-extend-with-code) for more information. diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/SimpleHostBotComposer.botproj b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/SimpleHostBotComposer.botproj new file mode 100644 index 0000000000..12ebc0273a --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/SimpleHostBotComposer.botproj @@ -0,0 +1,41 @@ +{ + "$schema": "https://raw.githubusercontent.com/microsoft/BotFramework-Composer/main/Composer/packages/server/schemas/botproject.schema", + "name": "SimpleHostBotComposer", + "skills": { + "echoSkillBotComposerDotNet": { + "manifest": "http://localhost:35410/manifests/echoskillbotcomposer-manifest.json", + "remote": true, + "endpointName": "default" + }, + "echoSkillBotDotNet": { + "manifest": "http://localhost:35400/manifests/echoskillbot-manifest-1.0.json", + "remote": true, + "endpointName": "default" + }, + "echoSkillBotDotNet21": { + "manifest": "http://localhost:35405/manifests/echoskillbot-manifest-1.0.json", + "remote": true, + "endpointName": "default" + }, + "echoSkillBotDotNetV3": { + "manifest": "http://localhost:35407/manifests/echoskillbotv3-manifest-1.0.json", + "remote": true, + "endpointName": "default" + }, + "echoSkillBotJs": { + "manifest": "http://localhost:36400/manifests/echoskillbot-manifest-1.0.json", + "remote": true, + "endpointName": "default" + }, + "echoSkillBotJsv3": { + "manifest": "http://localhost:36407/manifests/echoskillbotv3-manifest-1.0.json", + "remote": true, + "endpointName": "default" + }, + "echoSkillBotPython": { + "manifest": "http://localhost:37400/manifests/echoskillbot-manifest-1.0.json", + "remote": true, + "endpointName": "default" + } + } +} \ No newline at end of file diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/SimpleHostBotComposer.csproj b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/SimpleHostBotComposer.csproj new file mode 100644 index 0000000000..796e4f6f76 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/SimpleHostBotComposer.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp3.1 + OutOfProcess + c7eabc4e-9c9c-4b56-82d5-23e24f20ee24 + + + + PreserveNewest + + + + + + + + + \ No newline at end of file diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/Startup.cs b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/Startup.cs new file mode 100644 index 0000000000..f9c021bb07 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/Startup.cs @@ -0,0 +1,56 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.StaticFiles; +using Microsoft.Bot.Builder.Dialogs.Adaptive.Runtime.Extensions; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace SimpleHostBotComposer +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers().AddNewtonsoftJson(); + services.AddBotRuntime(Configuration); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseDefaultFiles(); + + // Set up custom content types - associating file extension to MIME type. + var provider = new FileExtensionContentTypeProvider(); + provider.Mappings[".lu"] = "application/vnd.microsoft.lu"; + provider.Mappings[".qna"] = "application/vnd.microsoft.qna"; + + // Expose static files in manifests folder for skill scenarios. + app.UseStaticFiles(new StaticFileOptions + { + ContentTypeProvider = provider + }); + app.UseWebSockets(); + app.UseRouting(); + app.UseAuthorization(); + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/dialogs/CallEchoSkill/CallEchoSkill.dialog b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/dialogs/CallEchoSkill/CallEchoSkill.dialog new file mode 100644 index 0000000000..2967ebdbc8 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/dialogs/CallEchoSkill/CallEchoSkill.dialog @@ -0,0 +1,244 @@ +{ + "$kind": "Microsoft.AdaptiveDialog", + "$designer": { + "id": "950uG3", + "name": "CallEchoSkill", + "description": "" + }, + "autoEndDialog": true, + "defaultResultProperty": "dialog.result", + "triggers": [ + { + "$kind": "Microsoft.OnBeginDialog", + "$designer": { + "name": "BeginDialog", + "description": "", + "id": "XuIGWc" + }, + "actions": [ + { + "$kind": "Microsoft.ChoiceInput", + "$designer": { + "id": "AtuOWj" + }, + "defaultLocale": "en-us", + "disabled": false, + "maxTurnCount": 3, + "alwaysPrompt": false, + "allowInterruptions": false, + "unrecognizedPrompt": "", + "invalidPrompt": "", + "defaultValueResponse": "", + "prompt": "${ChoiceInput_Prompt_AtuOWj()}", + "choiceOptions": { + "includeNumbers": true + }, + "property": "dialog.deliveryMode", + "style": "suggestedAction", + "choices": [ + "normal", + "expectReplies" + ] + }, + { + "$kind": "Microsoft.ChoiceInput", + "$designer": { + "id": "DIABs2" + }, + "defaultLocale": "en-us", + "disabled": false, + "maxTurnCount": 3, + "alwaysPrompt": false, + "allowInterruptions": false, + "unrecognizedPrompt": "", + "invalidPrompt": "", + "defaultValueResponse": "", + "prompt": "${ChoiceInput_Prompt_DIABs2()}", + "choiceOptions": { + "includeNumbers": true + }, + "style": "suggestedAction", + "property": "dialog.selectedSkill", + "choices": [ + "EchoSkillBotComposerDotNet", + "EchoSkillBotDotNet", + "EchoSkillBotDotNet21", + "EchoSkillBotDotNetV3", + "EchoSkillBotJS", + "EchoSkillBotJSV3", + "EchoSkillBotPython" + ] + }, + { + "$kind": "Microsoft.TextInput", + "$designer": { + "id": "nJf5rj" + }, + "disabled": false, + "maxTurnCount": 3, + "alwaysPrompt": false, + "allowInterruptions": false, + "unrecognizedPrompt": "", + "invalidPrompt": "", + "defaultValueResponse": "", + "prompt": "${TextInput_Prompt_nJf5rj()}", + "property": "dialog.firstUtterance" + }, + { + "$kind": "Microsoft.SwitchCondition", + "$designer": { + "id": "b3M6yt" + }, + "condition": "dialog.selectedSkill", + "cases": [ + { + "value": "EchoSkillBotComposerDotNet", + "actions": [ + { + "$kind": "Microsoft.BeginSkill", + "$designer": { + "id": "KKS0wY" + }, + "activityProcessed": true, + "botId": "=settings.MicrosoftAppId", + "skillHostEndpoint": "=settings.skillHostEndpoint", + "connectionName": "=settings.connectionName", + "allowInterruptions": true, + "skillEndpoint": "=settings.skill['echoSkillBotComposerDotNet'].endpointUrl", + "skillAppId": "=settings.skill['echoSkillBotComposerDotNet'].msAppId", + "activity": "${BeginSkill_Activity_KKS0wY()}" + } + ] + }, + { + "value": "EchoSkillBotDotNet", + "actions": [ + { + "$kind": "Microsoft.BeginSkill", + "$designer": { + "id": "92WLGJ" + }, + "activityProcessed": true, + "botId": "=settings.MicrosoftAppId", + "skillHostEndpoint": "=settings.skillHostEndpoint", + "connectionName": "=settings.connectionName", + "allowInterruptions": true, + "skillEndpoint": "=settings.skill['echoSkillBotDotNet'].endpointUrl", + "skillAppId": "=settings.skill['echoSkillBotDotNet'].msAppId", + "activity": "${BeginSkill_Activity_92WLGJ()}" + } + ] + }, + { + "value": "EchoSkillBotDotNet21", + "actions": [ + { + "$kind": "Microsoft.BeginSkill", + "$designer": { + "id": "9zIod8" + }, + "activityProcessed": true, + "botId": "=settings.MicrosoftAppId", + "skillHostEndpoint": "=settings.skillHostEndpoint", + "connectionName": "=settings.connectionName", + "allowInterruptions": true, + "skillEndpoint": "=settings.skill['echoSkillBotDotNet21'].endpointUrl", + "skillAppId": "=settings.skill['echoSkillBotDotNet21'].msAppId", + "activity": "${BeginSkill_Activity_9zIod8()}" + } + ] + }, + { + "value": "EchoSkillBotDotNetV3", + "actions": [ + { + "$kind": "Microsoft.BeginSkill", + "$designer": { + "id": "GHbR47" + }, + "activityProcessed": true, + "botId": "=settings.MicrosoftAppId", + "skillHostEndpoint": "=settings.skillHostEndpoint", + "connectionName": "=settings.connectionName", + "allowInterruptions": true, + "skillEndpoint": "=settings.skill['echoSkillBotDotNetV3'].endpointUrl", + "skillAppId": "=settings.skill['echoSkillBotDotNetV3'].msAppId", + "activity": "${BeginSkill_Activity_GHbR47()}" + } + ] + }, + { + "value": "EchoSkillBotJS", + "actions": [ + { + "$kind": "Microsoft.BeginSkill", + "$designer": { + "id": "fXOB92" + }, + "activityProcessed": true, + "botId": "=settings.MicrosoftAppId", + "skillHostEndpoint": "=settings.skillHostEndpoint", + "connectionName": "=settings.connectionName", + "allowInterruptions": true, + "skillEndpoint": "=settings.skill['echoSkillBotJs'].endpointUrl", + "skillAppId": "=settings.skill['echoSkillBotJs'].msAppId", + "activity": "${BeginSkill_Activity_fXOB92()}" + } + ] + }, + { + "value": "EchoSkillBotJSV3", + "actions": [ + { + "$kind": "Microsoft.BeginSkill", + "$designer": { + "id": "aEVlOJ" + }, + "activityProcessed": true, + "botId": "=settings.MicrosoftAppId", + "skillHostEndpoint": "=settings.skillHostEndpoint", + "connectionName": "=settings.connectionName", + "allowInterruptions": true, + "skillEndpoint": "=settings.skill['echoSkillBotJsv3'].endpointUrl", + "skillAppId": "=settings.skill['echoSkillBotJsv3'].msAppId", + "activity": "${BeginSkill_Activity_aEVlOJ()}" + } + ] + }, + { + "value": "EchoSkillBotPython", + "actions": [ + { + "$kind": "Microsoft.BeginSkill", + "$designer": { + "id": "WnhW7c" + }, + "activityProcessed": true, + "botId": "=settings.MicrosoftAppId", + "skillHostEndpoint": "=settings.skillHostEndpoint", + "connectionName": "=settings.connectionName", + "allowInterruptions": true, + "skillEndpoint": "=settings.skill['echoSkillBotPython'].endpointUrl", + "skillAppId": "=settings.skill['echoSkillBotPython'].msAppId", + "activity": "${BeginSkill_Activity_WnhW7c()}" + } + ] + } + ], + "default": [ + { + "$kind": "Microsoft.SendActivity", + "$designer": { + "id": "3NY1Ax" + }, + "activity": "${SendActivity_3NY1Ax()}" + } + ] + } + ] + } + ], + "generator": "CallEchoSkill.lg", + "recognizer": "CallEchoSkill.lu.qna", + "id": "CallEchoSkill" +} diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/dialogs/CallEchoSkill/knowledge-base/en-us/CallEchoSkill.en-us.qna b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/dialogs/CallEchoSkill/knowledge-base/en-us/CallEchoSkill.en-us.qna new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/dialogs/CallEchoSkill/language-generation/en-us/CallEchoSkill.en-us.lg b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/dialogs/CallEchoSkill/language-generation/en-us/CallEchoSkill.en-us.lg new file mode 100644 index 0000000000..8b5225cfbc --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/dialogs/CallEchoSkill/language-generation/en-us/CallEchoSkill.en-us.lg @@ -0,0 +1,56 @@ +[import](common.lg) + +# ChoiceInput_Prompt_AtuOWj() +[Activity + Text = What delivery mode would you like to use? +] + +# ChoiceInput_Prompt_DIABs2() +[Activity + Text = What skill would you like to call? +] + +# SendActivity_3NY1Ax() +[Activity + Text = We shouldn't hit this because we have validation in the prompt +] + +# BeginSkill_Activity_KKS0wY() +[Activity + Text = ${dialog.firstUtterance} +] + +# TextInput_Prompt_nJf5rj() +[Activity + Text = Type anything to send to the skill. +] + +# BeginSkill_Activity_92WLGJ() +[Activity + Text = ${dialog.firstUtterance} +] + +# BeginSkill_Activity_9zIod8() +[Activity + Text = ${dialog.firstUtterance} +] + +# BeginSkill_Activity_GHbR47() +[Activity + Text = ${dialog.firstUtterance} +] + +# BeginSkill_Activity_fXOB92() +[Activity + Text = ${dialog.firstUtterance} +] + +# BeginSkill_Activity_aEVlOJ() +[Activity + Text = ${dialog.firstUtterance} +] + +# BeginSkill_Activity_WnhW7c() +[Activity + Text = ${dialog.firstUtterance} +] diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/dialogs/CallEchoSkill/language-understanding/en-us/CallEchoSkill.en-us.lu b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/dialogs/CallEchoSkill/language-understanding/en-us/CallEchoSkill.en-us.lu new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/dialogs/CallEchoSkill/recognizers/CallEchoSkill.en-us.lu.dialog b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/dialogs/CallEchoSkill/recognizers/CallEchoSkill.en-us.lu.dialog new file mode 100644 index 0000000000..9980d2dd76 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/dialogs/CallEchoSkill/recognizers/CallEchoSkill.en-us.lu.dialog @@ -0,0 +1,8 @@ +{ + "$kind": "Microsoft.LuisRecognizer", + "id": "LUIS_CallEchoSkill", + "applicationId": "=settings.luis.CallEchoSkill_en_us_lu.appId", + "version": "=settings.luis.CallEchoSkill_en_us_lu.version", + "endpoint": "=settings.luis.endpoint", + "endpointKey": "=settings.luis.endpointKey" +} diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/dialogs/CallEchoSkill/recognizers/CallEchoSkill.lu.dialog b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/dialogs/CallEchoSkill/recognizers/CallEchoSkill.lu.dialog new file mode 100644 index 0000000000..e07dee15ef --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/dialogs/CallEchoSkill/recognizers/CallEchoSkill.lu.dialog @@ -0,0 +1,5 @@ +{ + "$kind": "Microsoft.MultiLanguageRecognizer", + "id": "LUIS_CallEchoSkill", + "recognizers": {} +} diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/dialogs/CallEchoSkill/recognizers/CallEchoSkill.lu.qna.dialog b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/dialogs/CallEchoSkill/recognizers/CallEchoSkill.lu.qna.dialog new file mode 100644 index 0000000000..80b77f0d0d --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/dialogs/CallEchoSkill/recognizers/CallEchoSkill.lu.qna.dialog @@ -0,0 +1,4 @@ +{ + "$kind": "Microsoft.CrossTrainedRecognizerSet", + "recognizers": [] +} diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/dialogs/emptyBot/knowledge-base/en-us/emptyBot.en-us.qna b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/dialogs/emptyBot/knowledge-base/en-us/emptyBot.en-us.qna new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/generated/interruption/CallEchoSkill.en-us.lu b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/generated/interruption/CallEchoSkill.en-us.lu new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/generated/interruption/CallEchoSkill.en-us.qna b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/generated/interruption/CallEchoSkill.en-us.qna new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/generated/interruption/SimpleHostBotComposer.en-us.lu b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/generated/interruption/SimpleHostBotComposer.en-us.lu new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/generated/interruption/SimpleHostBotComposer.en-us.qna b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/generated/interruption/SimpleHostBotComposer.en-us.qna new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/knowledge-base/en-us/simplehostbotcomposer.en-us.qna b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/knowledge-base/en-us/simplehostbotcomposer.en-us.qna new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/language-generation/en-us/common.en-us.lg b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/language-generation/en-us/common.en-us.lg new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/language-generation/en-us/simplehostbotcomposer.en-us.lg b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/language-generation/en-us/simplehostbotcomposer.en-us.lg new file mode 100644 index 0000000000..333bea8b5e --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/language-generation/en-us/simplehostbotcomposer.en-us.lg @@ -0,0 +1,26 @@ +[import](common.lg) + +# SendActivity_Greeting() +[Activity + Text = Hello and welcome! + InputHint = acceptingInput +] + +# SendActivity_DidNotUnderstand() +[Activity + Text = ${SendActivity_DidNotUnderstand_text()} +] + +# SendActivity_DidNotUnderstand_text() +- Sorry, I didn't get that. +# SendActivity_M2LqJr() +[Activity + Text = Received endOfConversation.\n\nCode: ${turn.activity.code}. + InputHint = acceptingInput +] + +# SendActivity_bgTcyn() +[Activity + Text = Back in the host bot. + InputHint = acceptingInput +] diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/language-understanding/en-us/simplehostbotcomposer.en-us.lu b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/language-understanding/en-us/simplehostbotcomposer.en-us.lu new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/media/create-azure-resource-command-line.png b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/media/create-azure-resource-command-line.png new file mode 100644 index 0000000000..497eb8e649 Binary files /dev/null and b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/media/create-azure-resource-command-line.png differ diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/media/publish-az-login.png b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/media/publish-az-login.png new file mode 100644 index 0000000000..4e721354bc Binary files /dev/null and b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/media/publish-az-login.png differ diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/recognizers/SimpleHostBotComposer.en-us.lu.dialog b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/recognizers/SimpleHostBotComposer.en-us.lu.dialog new file mode 100644 index 0000000000..7d5d67f4f6 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/recognizers/SimpleHostBotComposer.en-us.lu.dialog @@ -0,0 +1,8 @@ +{ + "$kind": "Microsoft.LuisRecognizer", + "id": "LUIS_SimpleHostBotComposer", + "applicationId": "=settings.luis.SimpleHostBotComposer_en_us_lu.appId", + "version": "=settings.luis.SimpleHostBotComposer_en_us_lu.version", + "endpoint": "=settings.luis.endpoint", + "endpointKey": "=settings.luis.endpointKey" +} diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/recognizers/SimpleHostBotComposer.lu.dialog b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/recognizers/SimpleHostBotComposer.lu.dialog new file mode 100644 index 0000000000..1ce9e8bf5f --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/recognizers/SimpleHostBotComposer.lu.dialog @@ -0,0 +1,5 @@ +{ + "$kind": "Microsoft.MultiLanguageRecognizer", + "id": "LUIS_SimpleHostBotComposer", + "recognizers": {} +} diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/recognizers/SimpleHostBotComposer.lu.qna.dialog b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/recognizers/SimpleHostBotComposer.lu.qna.dialog new file mode 100644 index 0000000000..80b77f0d0d --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/recognizers/SimpleHostBotComposer.lu.qna.dialog @@ -0,0 +1,4 @@ +{ + "$kind": "Microsoft.CrossTrainedRecognizerSet", + "recognizers": [] +} diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/schemas/sdk.schema b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/schemas/sdk.schema new file mode 100644 index 0000000000..ee34876994 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/schemas/sdk.schema @@ -0,0 +1,10312 @@ +{ + "$schema": "https://schemas.botframework.com/schemas/component/v1.0/component.schema", + "type": "object", + "title": "Component kinds", + "description": "These are all of the kinds that can be created by the loader.", + "oneOf": [ + { + "$ref": "#/definitions/Microsoft.ActivityTemplate" + }, + { + "$ref": "#/definitions/Microsoft.AdaptiveDialog" + }, + { + "$ref": "#/definitions/Microsoft.AgeEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.Ask" + }, + { + "$ref": "#/definitions/Microsoft.AttachmentInput" + }, + { + "$ref": "#/definitions/Microsoft.BeginDialog" + }, + { + "$ref": "#/definitions/Microsoft.BeginSkill" + }, + { + "$ref": "#/definitions/Microsoft.BreakLoop" + }, + { + "$ref": "#/definitions/Microsoft.CancelAllDialogs" + }, + { + "$ref": "#/definitions/Microsoft.CancelDialog" + }, + { + "$ref": "#/definitions/Microsoft.ChannelMentionEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.ChoiceInput" + }, + { + "$ref": "#/definitions/Microsoft.ConditionalSelector" + }, + { + "$ref": "#/definitions/Microsoft.ConfirmationEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.ConfirmInput" + }, + { + "$ref": "#/definitions/Microsoft.ContinueConversation" + }, + { + "$ref": "#/definitions/Microsoft.ContinueConversationLater" + }, + { + "$ref": "#/definitions/Microsoft.ContinueLoop" + }, + { + "$ref": "#/definitions/Microsoft.CrossTrainedRecognizerSet" + }, + { + "$ref": "#/definitions/Microsoft.CurrencyEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.DateTimeEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.DateTimeInput" + }, + { + "$ref": "#/definitions/Microsoft.DebugBreak" + }, + { + "$ref": "#/definitions/Microsoft.DeleteActivity" + }, + { + "$ref": "#/definitions/Microsoft.DeleteProperties" + }, + { + "$ref": "#/definitions/Microsoft.DeleteProperty" + }, + { + "$ref": "#/definitions/Microsoft.DimensionEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.EditActions" + }, + { + "$ref": "#/definitions/Microsoft.EditArray" + }, + { + "$ref": "#/definitions/Microsoft.EmailEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.EmitEvent" + }, + { + "$ref": "#/definitions/Microsoft.EndDialog" + }, + { + "$ref": "#/definitions/Microsoft.EndTurn" + }, + { + "$ref": "#/definitions/Microsoft.FirstSelector" + }, + { + "$ref": "#/definitions/Microsoft.Foreach" + }, + { + "$ref": "#/definitions/Microsoft.ForeachPage" + }, + { + "$ref": "#/definitions/Microsoft.GetActivityMembers" + }, + { + "$ref": "#/definitions/Microsoft.GetConversationMembers" + }, + { + "$ref": "#/definitions/Microsoft.GetConversationReference" + }, + { + "$ref": "#/definitions/Microsoft.GotoAction" + }, + { + "$ref": "#/definitions/Microsoft.GuidEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.HashtagEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.HttpRequest" + }, + { + "$ref": "#/definitions/Microsoft.IfCondition" + }, + { + "$ref": "#/definitions/Microsoft.IpEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.LogAction" + }, + { + "$ref": "#/definitions/Microsoft.LuisRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.MentionEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.MostSpecificSelector" + }, + { + "$ref": "#/definitions/Microsoft.MultiLanguageRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.NumberEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.NumberInput" + }, + { + "$ref": "#/definitions/Microsoft.NumberRangeEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.OAuthInput" + }, + { + "$ref": "#/definitions/Microsoft.OnActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnAssignEntity" + }, + { + "$ref": "#/definitions/Microsoft.OnBeginDialog" + }, + { + "$ref": "#/definitions/Microsoft.OnCancelDialog" + }, + { + "$ref": "#/definitions/Microsoft.OnChooseEntity" + }, + { + "$ref": "#/definitions/Microsoft.OnChooseIntent" + }, + { + "$ref": "#/definitions/Microsoft.OnChooseProperty" + }, + { + "$ref": "#/definitions/Microsoft.OnCommandActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnCommandResultActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnCondition" + }, + { + "$ref": "#/definitions/Microsoft.OnContinueConversation" + }, + { + "$ref": "#/definitions/Microsoft.OnConversationUpdateActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnDialogEvent" + }, + { + "$ref": "#/definitions/Microsoft.OnEndOfActions" + }, + { + "$ref": "#/definitions/Microsoft.OnEndOfConversationActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnError" + }, + { + "$ref": "#/definitions/Microsoft.OnEventActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnHandoffActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnInstallationUpdateActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnIntent" + }, + { + "$ref": "#/definitions/Microsoft.OnInvokeActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnMessageActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnMessageDeleteActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnMessageReactionActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnMessageUpdateActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnQnAMatch" + }, + { + "$ref": "#/definitions/Microsoft.OnRepromptDialog" + }, + { + "$ref": "#/definitions/Microsoft.OnTypingActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnUnknownIntent" + }, + { + "$ref": "#/definitions/Microsoft.OrdinalEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.PercentageEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.PhoneNumberEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.QnAMakerDialog" + }, + { + "$ref": "#/definitions/Microsoft.QnAMakerRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.RandomSelector" + }, + { + "$ref": "#/definitions/Microsoft.RecognizerSet" + }, + { + "$ref": "#/definitions/Microsoft.RegexEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.RegexRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.RepeatDialog" + }, + { + "$ref": "#/definitions/Microsoft.ReplaceDialog" + }, + { + "$ref": "#/definitions/Microsoft.ResourceMultiLanguageGenerator" + }, + { + "$ref": "#/definitions/Microsoft.SendActivity" + }, + { + "$ref": "#/definitions/Microsoft.SendHandoffActivity" + }, + { + "$ref": "#/definitions/Microsoft.SetProperties" + }, + { + "$ref": "#/definitions/Microsoft.SetProperty" + }, + { + "$ref": "#/definitions/Microsoft.SignOutUser" + }, + { + "$ref": "#/definitions/Microsoft.StaticActivityTemplate" + }, + { + "$ref": "#/definitions/Microsoft.SwitchCondition" + }, + { + "$ref": "#/definitions/Microsoft.TelemetryTrackEventAction" + }, + { + "$ref": "#/definitions/Microsoft.TemperatureEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.TemplateEngineLanguageGenerator" + }, + { + "$ref": "#/definitions/Microsoft.TextInput" + }, + { + "$ref": "#/definitions/Microsoft.TextTemplate" + }, + { + "$ref": "#/definitions/Microsoft.ThrowException" + }, + { + "$ref": "#/definitions/Microsoft.TraceActivity" + }, + { + "$ref": "#/definitions/Microsoft.TrueSelector" + }, + { + "$ref": "#/definitions/Microsoft.UpdateActivity" + }, + { + "$ref": "#/definitions/Microsoft.UrlEntityRecognizer" + } + ], + "definitions": { + "arrayExpression": { + "$role": "expression", + "title": "Array or expression", + "description": "Array or expression to evaluate.", + "oneOf": [ + { + "type": "array", + "title": "Array", + "description": "Array constant." + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "booleanExpression": { + "$role": "expression", + "title": "Boolean or expression", + "description": "Boolean constant or expression to evaluate.", + "oneOf": [ + { + "type": "boolean", + "title": "Boolean", + "description": "Boolean constant.", + "default": false, + "examples": [ + false + ] + }, + { + "$ref": "#/definitions/equalsExpression", + "examples": [ + "=user.isVip" + ] + } + ] + }, + "component": { + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "condition": { + "$role": "expression", + "title": "Boolean condition", + "description": "Boolean constant or expression to evaluate.", + "oneOf": [ + { + "$ref": "#/definitions/expression" + }, + { + "type": "boolean", + "title": "Boolean", + "description": "Boolean value.", + "default": true, + "examples": [ + false + ] + } + ] + }, + "equalsExpression": { + "$role": "expression", + "type": "string", + "title": "Expression", + "description": "Expression starting with =.", + "pattern": "^=.*\\S.*", + "examples": [ + "=user.name" + ] + }, + "expression": { + "$role": "expression", + "type": "string", + "title": "Expression", + "description": "Expression to evaluate.", + "pattern": "^.*\\S.*", + "examples": [ + "user.age > 13" + ] + }, + "integerExpression": { + "$role": "expression", + "title": "Integer or expression", + "description": "Integer constant or expression to evaluate.", + "oneOf": [ + { + "type": "integer", + "title": "Integer", + "description": "Integer constant.", + "default": 0, + "examples": [ + 15 + ] + }, + { + "$ref": "#/definitions/equalsExpression", + "examples": [ + "=user.age" + ] + } + ] + }, + "Microsoft.ActivityTemplate": { + "$role": "implements(Microsoft.IActivityTemplate)", + "title": "Microsoft activity template", + "type": "object", + "required": [ + "template", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "template": { + "title": "Template", + "description": "Language Generator template to use to create the activity", + "type": "string" + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.ActivityTemplate" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.AdaptiveDialog": { + "$role": "implements(Microsoft.IDialog)", + "title": "Adaptive dialog", + "description": "Flexible, data driven dialog that can adapt to the conversation.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "pattern": "^(?!(=)).*", + "title": "Id", + "description": "Optional dialog ID." + }, + "autoEndDialog": { + "$ref": "#/definitions/booleanExpression", + "title": "Auto end dialog", + "description": "If set to true the dialog will automatically end when there are no further actions. If set to false, remember to manually end the dialog using EndDialog action.", + "default": true + }, + "defaultResultProperty": { + "type": "string", + "title": "Default result property", + "description": "Value that will be passed back to the parent dialog.", + "default": "dialog.result" + }, + "dialogs": { + "type": "array", + "title": "Dialogs", + "description": "Dialogs added to DialogSet.", + "items": { + "$kind": "Microsoft.IDialog", + "title": "Dialog", + "description": "Dialog to add to DialogSet.", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "recognizer": { + "$kind": "Microsoft.IRecognizer", + "title": "Recognizer", + "description": "Input recognizer that interprets user input into intent and entities.", + "$ref": "#/definitions/Microsoft.IRecognizer" + }, + "generator": { + "$kind": "Microsoft.ILanguageGenerator", + "title": "Language generator", + "description": "Language generator that generates bot responses.", + "$ref": "#/definitions/Microsoft.ILanguageGenerator" + }, + "selector": { + "$kind": "Microsoft.ITriggerSelector", + "title": "Selector", + "description": "Policy to determine which trigger is executed. Defaults to a 'best match' selector (optional).", + "$ref": "#/definitions/Microsoft.ITriggerSelector" + }, + "triggers": { + "type": "array", + "description": "List of triggers defined for this dialog.", + "title": "Triggers", + "items": { + "$kind": "Microsoft.ITrigger", + "title": "Event triggers", + "description": "Event triggers for handling events.", + "$ref": "#/definitions/Microsoft.ITrigger" + } + }, + "schema": { + "title": "Schema", + "description": "Schema to fill in.", + "anyOf": [ + { + "$ref": "#/definitions/schema" + }, + { + "type": "string", + "title": "Reference to JSON schema", + "description": "Reference to JSON schema .dialog file." + } + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.AdaptiveDialog" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.AgeEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Age entity recognizer", + "description": "Recognizer which recognizes age.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.AgeEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.Ask": { + "$role": [ + "implements(Microsoft.IDialog)", + "extends(Microsoft.SendActivity)" + ], + "title": "Send activity to ask a question", + "description": "This is an action which sends an activity to the user when a response is expected", + "type": "object", + "$policies": { + "interactive": true + }, + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "expectedProperties": { + "$ref": "#/definitions/arrayExpression", + "title": "Expected properties", + "description": "Properties expected from the user.", + "examples": [ + [ + "age", + "name" + ] + ], + "items": { + "type": "string", + "title": "Name", + "description": "Name of the property" + } + }, + "defaultOperation": { + "$ref": "#/definitions/stringExpression", + "title": "Default operation", + "description": "Sets the default operation that will be used when no operation is recognized in the response to this Ask.", + "examples": [ + "Add()", + "Remove()" + ] + }, + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action." + }, + "activity": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Activity", + "description": "Activity to send.", + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.Ask" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.AttachmentInput": { + "$role": [ + "implements(Microsoft.IDialog)", + "extends(Microsoft.InputDialog)" + ], + "title": "Attachment input dialog", + "description": "Collect information - Ask for a file or image.", + "$policies": { + "interactive": true + }, + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "type": "object", + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "defaultValue": { + "$role": "expression", + "title": "Default value", + "description": "'Property' will be set to the object or the result of this expression when max turn count is exceeded.", + "oneOf": [ + { + "$ref": "#/definitions/botframework.json/definitions/Attachment", + "title": "Object", + "description": "Attachment object." + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "value": { + "$role": "expression", + "title": "Value", + "description": "'Property' will be set to the object or the result of this expression unless it evaluates to null.", + "oneOf": [ + { + "$ref": "#/definitions/botframework.json/definitions/Attachment", + "title": "Object", + "description": "Attachment object." + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "outputFormat": { + "$role": "expression", + "title": "Output format", + "description": "Attachment output format.", + "oneOf": [ + { + "type": "string", + "title": "Standard format", + "description": "Standard output formats.", + "enum": [ + "all", + "first" + ], + "default": "first" + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "default": false, + "examples": [ + false, + "=user.isVip" + ] + }, + "prompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Initial prompt", + "description": "Message to send to collect information.", + "examples": [ + "What is your birth date?" + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "unrecognizedPrompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Unrecognized prompt", + "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value.", + "examples": [ + "Sorry, I'm having trouble understanding you. I will just use {this.options.defaultValue} for now. You can say 'I'm 36 years old' to change it." + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "invalidPrompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Invalid prompt", + "description": "Message to send when the user input does not meet any validation expression.", + "examples": [ + "Sorry, '{this.value}' does not work. I need a number between 1-150. What is your age?" + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "defaultValueResponse": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Default value response", + "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value.", + "examples": [ + "Sorry, I'm having trouble understanding you. I will just use {this.options.defaultValue} for now. You can say 'I'm 36 years old' to change it." + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "maxTurnCount": { + "$ref": "#/definitions/integerExpression", + "title": "Max turn count", + "description": "Maximum number of re-prompt attempts to collect information.", + "default": 3, + "minimum": 0, + "maximum": 2147483647, + "examples": [ + 3, + "=settings.xyz" + ] + }, + "validations": { + "type": "array", + "title": "Validation expressions", + "description": "Expression to validate user input.", + "items": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Expression which needs to met for the input to be considered valid", + "examples": [ + "int(this.value) > 1 && int(this.value) <= 150", + "count(this.value) < 300" + ] + } + }, + "property": { + "$ref": "#/definitions/stringExpression", + "title": "Property", + "description": "Property to store collected information. Input will be skipped if property has value (unless 'Always prompt' is true).", + "examples": [ + "$birthday", + "dialog.${user.name}", + "=f(x)" + ] + }, + "alwaysPrompt": { + "$ref": "#/definitions/booleanExpression", + "title": "Always prompt", + "description": "Collect information even if the specified 'property' is not empty.", + "default": false, + "examples": [ + false, + "=$val" + ] + }, + "allowInterruptions": { + "$ref": "#/definitions/booleanExpression", + "title": "Allow Interruptions", + "description": "A boolean expression that determines whether the parent should be allowed to interrupt the input.", + "default": true, + "examples": [ + true, + "=user.xyz" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.AttachmentInput" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.BeginDialog": { + "$role": "implements(Microsoft.IDialog)", + "title": "Begin a dialog", + "description": "Begin another dialog.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "dialog": { + "oneOf": [ + { + "$kind": "Microsoft.IDialog", + "pattern": "^(?!(=)).*", + "title": "Dialog", + "$ref": "#/definitions/Microsoft.IDialog" + }, + { + "$ref": "#/definitions/equalsExpression" + } + ], + "title": "Dialog name", + "description": "Name of the dialog to call." + }, + "options": { + "$ref": "#/definitions/objectExpression", + "title": "Options", + "description": "One or more options that are passed to the dialog that is called.", + "examples": [ + { + "arg1": "=expression" + } + ], + "additionalProperties": { + "type": "string", + "title": "Options", + "description": "Options for dialog." + } + }, + "activityProcessed": { + "$ref": "#/definitions/booleanExpression", + "title": "Activity processed", + "description": "When set to false, the dialog that is called can process the current activity.", + "default": true + }, + "resultProperty": { + "$ref": "#/definitions/stringExpression", + "title": "Property", + "description": "Property to store any value returned by the dialog that is called.", + "examples": [ + "dialog.userName" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.BeginDialog" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.BeginSkill": { + "$role": "implements(Microsoft.IDialog)", + "title": "Begin a skill", + "description": "Begin a remote skill.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the skill dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + true, + "=f(x)" + ] + }, + "activityProcessed": { + "$ref": "#/definitions/booleanExpression", + "title": "Activity processed", + "description": "When set to false, the skill will be started using the activity in the current turn context instead of the activity in the Activity property.", + "default": true, + "examples": [ + true, + "=f(x)" + ] + }, + "resultProperty": { + "$ref": "#/definitions/stringExpression", + "title": "Property", + "description": "Property to store any value returned by the dialog that is called.", + "examples": [ + "dialog.userName" + ] + }, + "botId": { + "$ref": "#/definitions/stringExpression", + "title": "Skill host bot ID", + "description": "The Microsoft App ID that will be calling the skill.", + "default": "=settings.MicrosoftAppId" + }, + "skillHostEndpoint": { + "$ref": "#/definitions/stringExpression", + "title": "Skill host", + "description": "The callback Url for the skill host.", + "default": "=settings.skillHostEndpoint", + "examples": [ + "https://mybot.contoso.com/api/skills/" + ] + }, + "connectionName": { + "$ref": "#/definitions/stringExpression", + "title": "OAuth connection name (SSO)", + "description": "The OAuth Connection Name, that would be used to perform Single SignOn with a skill.", + "default": "=settings.connectionName" + }, + "skillAppId": { + "$ref": "#/definitions/stringExpression", + "title": "Skill App Id", + "description": "The Microsoft App ID for the skill." + }, + "skillEndpoint": { + "$ref": "#/definitions/stringExpression", + "title": "Skill endpoint ", + "description": "The /api/messages endpoint for the skill.", + "examples": [ + "https://myskill.contoso.com/api/messages/" + ] + }, + "activity": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Activity", + "description": "The activity to send to the skill.", + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "allowInterruptions": { + "$ref": "#/definitions/booleanExpression", + "title": "Allow interruptions", + "description": "A boolean expression that determines whether the parent should be allowed to interrupt the skill.", + "default": true, + "examples": [ + true, + "=user.xyz" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.BeginSkill" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.BreakLoop": { + "$role": "implements(Microsoft.IDialog)", + "title": "Break loop", + "description": "Stop executing this loop", + "type": "object", + "required": [ + "$kind" + ], + "$policies": { + "lastAction": true + }, + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.BreakLoop" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.CancelAllDialogs": { + "$role": "implements(Microsoft.IDialog)", + "title": "Cancel all dialogs", + "description": "Cancel all active dialogs. All dialogs in the dialog chain will need a trigger to capture the event configured in this action.", + "type": "object", + "$policies": { + "lastAction": true + }, + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "activityProcessed": { + "$ref": "#/definitions/booleanExpression", + "title": "Activity processed", + "description": "When set to false, the caller dialog is told it should process the current activity.", + "default": true + }, + "eventName": { + "$ref": "#/definitions/stringExpression", + "title": "Event name", + "description": "Name of the event to emit." + }, + "eventValue": { + "$ref": "#/definitions/valueExpression", + "title": "Event value", + "description": "Value to emit with the event (optional).", + "additionalProperties": true + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.CancelAllDialogs" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.CancelDialog": { + "$role": "implements(Microsoft.IDialog)", + "title": "Cancel all dialogs", + "description": "Cancel all active dialogs. All dialogs in the dialog chain will need a trigger to capture the event configured in this action.", + "type": "object", + "$policies": { + "lastAction": true + }, + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "activityProcessed": { + "$ref": "#/definitions/booleanExpression", + "title": "Activity processed", + "description": "When set to false, the caller dialog is told it should process the current activity.", + "default": true + }, + "eventName": { + "$ref": "#/definitions/stringExpression", + "title": "Event name", + "description": "Name of the event to emit." + }, + "eventValue": { + "$ref": "#/definitions/valueExpression", + "title": "Event value", + "description": "Value to emit with the event (optional).", + "additionalProperties": true + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.CancelDialog" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.ChannelMentionEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)" + ], + "title": "Channel mention entity recognizer", + "description": "Promotes mention entities passed by a channel via the activity.entities into recognizer result.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.ChannelMentionEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.ChoiceInput": { + "$role": [ + "implements(Microsoft.IDialog)", + "extends(Microsoft.InputDialog)" + ], + "title": "Choice input dialog", + "description": "Collect information - Pick from a list of choices", + "type": "object", + "$policies": { + "interactive": true + }, + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "defaultValue": { + "$ref": "#/definitions/stringExpression", + "title": "Default value", + "description": "'Property' will be set to the value of this expression when max turn count is exceeded.", + "examples": [ + "hello world", + "Hello ${user.name}", + "=concat(user.firstname, user.lastName)" + ] + }, + "value": { + "$ref": "#/definitions/stringExpression", + "title": "Value", + "description": "'Property' will be set to the value of this expression unless it evaluates to null.", + "examples": [ + "hello world", + "Hello ${user.name}", + "=concat(user.firstname, user.lastName)" + ] + }, + "outputFormat": { + "$role": "expression", + "title": "Output format", + "description": "Sets the desired choice output format (either value or index into choices).", + "oneOf": [ + { + "type": "string", + "title": "Standard", + "description": "Standard output format.", + "enum": [ + "value", + "index" + ], + "default": "value" + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "choices": { + "$role": "expression", + "title": "Array of choices", + "description": "Choices to choose from.", + "oneOf": [ + { + "type": "array", + "title": "Simple choices", + "description": "Simple choices to choose from.", + "items": [ + { + "type": "string", + "title": "Simple choice", + "description": "One choice for choice input." + } + ] + }, + { + "type": "array", + "title": "Structured choices", + "description": "Choices that allow full control.", + "items": [ + { + "type": "object", + "title": "Structured choice", + "description": "Structured choice to choose from.", + "properties": { + "value": { + "type": "string", + "title": "Value", + "description": "Value to return when this choice is selected." + }, + "action": { + "$ref": "#/definitions/botframework.json/definitions/CardAction", + "title": "Action", + "description": "Card action for the choice." + }, + "synonyms": { + "type": "array", + "title": "Synonyms", + "description": "List of synonyms to recognize in addition to the value (optional).", + "items": { + "type": "string", + "title": "Synonym", + "description": "Synonym for value." + } + } + } + } + ] + }, + { + "$ref": "#/definitions/stringExpression" + } + ] + }, + "defaultLocale": { + "$ref": "#/definitions/stringExpression", + "title": "Default locale", + "description": "The default locale to use to parse confirmation choices if there is not one passed by the caller.", + "default": "en-us", + "examples": [ + "en-us" + ] + }, + "style": { + "$role": "expression", + "title": "List style", + "description": "Sets the ListStyle to control how choices are rendered.", + "oneOf": [ + { + "type": "string", + "title": "List style", + "description": "Standard list style.", + "enum": [ + "none", + "auto", + "inline", + "list", + "suggestedAction", + "heroCard" + ], + "default": "auto" + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "choiceOptions": { + "title": "Choice options", + "description": "Sets the choice options used for controlling how choices are combined.", + "oneOf": [ + { + "type": "object", + "title": "Object", + "description": "Choice options object.", + "properties": { + "inlineSeparator": { + "type": "string", + "title": "Inline separator", + "description": "Character used to separate individual choices when there are more than 2 choices", + "default": ", " + }, + "inlineOr": { + "type": "string", + "title": "Inline or", + "description": "Separator inserted between the choices when there are only 2 choices", + "default": " or " + }, + "inlineOrMore": { + "type": "string", + "title": "Inline or more", + "description": "Separator inserted between the last 2 choices when their are more than 2 choices.", + "default": ", or " + }, + "includeNumbers": { + "type": "boolean", + "title": "Include numbers", + "description": "If true, 'inline' and 'list' list style will be prefixed with the index of the choice.", + "default": true + } + } + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "recognizerOptions": { + "title": "Recognizer options", + "description": "Sets how to recognize choices in the response", + "oneOf": [ + { + "type": "object", + "title": "Object", + "description": "Options for recognizer.", + "properties": { + "noValue": { + "type": "boolean", + "title": "No value", + "description": "If true, the choices value field will NOT be search over", + "default": false + }, + "noAction": { + "type": "boolean", + "title": "No action", + "description": "If true, the choices action.title field will NOT be searched over", + "default": false + }, + "recognizeNumbers": { + "type": "boolean", + "title": "Recognize numbers", + "description": "If true, the number recognizer will be used to recognize an index response (1,2,3...) to the prompt.", + "default": true + }, + "recognizeOrdinals": { + "type": "boolean", + "title": "Recognize ordinals", + "description": "If true, the ordinal recognizer will be used to recognize ordinal response (first/second/...) to the prompt.", + "default": true + } + } + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "default": false, + "examples": [ + false, + "=user.isVip" + ] + }, + "prompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Initial prompt", + "description": "Message to send to collect information.", + "examples": [ + "What is your birth date?" + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "unrecognizedPrompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Unrecognized prompt", + "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value.", + "examples": [ + "Sorry, I'm having trouble understanding you. I will just use {this.options.defaultValue} for now. You can say 'I'm 36 years old' to change it." + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "invalidPrompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Invalid prompt", + "description": "Message to send when the user input does not meet any validation expression.", + "examples": [ + "Sorry, '{this.value}' does not work. I need a number between 1-150. What is your age?" + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "defaultValueResponse": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Default value response", + "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value.", + "examples": [ + "Sorry, I'm having trouble understanding you. I will just use {this.options.defaultValue} for now. You can say 'I'm 36 years old' to change it." + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "maxTurnCount": { + "$ref": "#/definitions/integerExpression", + "title": "Max turn count", + "description": "Maximum number of re-prompt attempts to collect information.", + "default": 3, + "minimum": 0, + "maximum": 2147483647, + "examples": [ + 3, + "=settings.xyz" + ] + }, + "validations": { + "type": "array", + "title": "Validation expressions", + "description": "Expression to validate user input.", + "items": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Expression which needs to met for the input to be considered valid", + "examples": [ + "int(this.value) > 1 && int(this.value) <= 150", + "count(this.value) < 300" + ] + } + }, + "property": { + "$ref": "#/definitions/stringExpression", + "title": "Property", + "description": "Property to store collected information. Input will be skipped if property has value (unless 'Always prompt' is true).", + "examples": [ + "$birthday", + "dialog.${user.name}", + "=f(x)" + ] + }, + "alwaysPrompt": { + "$ref": "#/definitions/booleanExpression", + "title": "Always prompt", + "description": "Collect information even if the specified 'property' is not empty.", + "default": false, + "examples": [ + false, + "=$val" + ] + }, + "allowInterruptions": { + "$ref": "#/definitions/booleanExpression", + "title": "Allow Interruptions", + "description": "A boolean expression that determines whether the parent should be allowed to interrupt the input.", + "default": true, + "examples": [ + true, + "=user.xyz" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.ChoiceInput" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.ConditionalSelector": { + "$role": "implements(Microsoft.ITriggerSelector)", + "title": "Conditional trigger selector", + "description": "Use a rule selector based on a condition", + "type": "object", + "required": [ + "condition", + "ifTrue", + "ifFalse", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Expression to evaluate" + }, + "ifTrue": { + "$kind": "Microsoft.ITriggerSelector", + "$ref": "#/definitions/Microsoft.ITriggerSelector" + }, + "ifFalse": { + "$kind": "Microsoft.ITriggerSelector", + "$ref": "#/definitions/Microsoft.ITriggerSelector" + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.ConditionalSelector" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.ConfirmationEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Confirmation entity recognizer", + "description": "Recognizer which recognizes confirmation choices (yes/no).", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.ConfirmationEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.ConfirmInput": { + "$role": [ + "implements(Microsoft.IDialog)", + "extends(Microsoft.InputDialog)" + ], + "title": "Confirm input dialog", + "description": "Collect information - Ask for confirmation (yes or no).", + "type": "object", + "$policies": { + "interactive": true + }, + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "outputFormat": { + "$ref": "#/definitions/valueExpression", + "title": "Output format", + "description": "Optional expression to use to format the output.", + "examples": [ + "=concat('confirmation:', this.value)" + ] + }, + "defaultLocale": { + "$ref": "#/definitions/stringExpression", + "title": "Default locale", + "description": "The Default locale or an expression which provides the default locale to use as default if not found in the activity.", + "default": "en-us", + "examples": [ + "en-us" + ] + }, + "style": { + "$role": "expression", + "title": "List style", + "description": "Sets the ListStyle to control how choices are rendered.", + "oneOf": [ + { + "type": "string", + "title": "Standard style", + "description": "Standard style for rendering choices.", + "enum": [ + "none", + "auto", + "inline", + "list", + "suggestedAction", + "heroCard" + ], + "default": "auto" + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "choiceOptions": { + "title": "Choice options", + "description": "Choice Options or expression which provides Choice Options to control display choices to the user.", + "oneOf": [ + { + "type": "object", + "title": "Choice options", + "description": "Choice options.", + "properties": { + "inlineSeparator": { + "type": "string", + "title": "Inline separator", + "description": "Text to separate individual choices when there are more than 2 choices", + "default": ", " + }, + "inlineOr": { + "type": "string", + "title": "Inline or", + "description": "Text to be inserted between the choices when their are only 2 choices", + "default": " or " + }, + "inlineOrMore": { + "type": "string", + "title": "Inline or more", + "description": "Text to be inserted between the last 2 choices when their are more than 2 choices.", + "default": ", or " + }, + "includeNumbers": { + "type": "boolean", + "title": "Include numbers", + "description": "If true, inline and list style choices will be prefixed with the index of the choice.", + "default": true + } + } + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "defaultValue": { + "$ref": "#/definitions/booleanExpression", + "title": "Default value", + "description": "'Property' will be set to the value of this expression when max turn count is exceeded.", + "examples": [ + true, + "=user.age > 3" + ] + }, + "value": { + "$ref": "#/definitions/booleanExpression", + "title": "Value", + "description": "'Property' will be set to the value of this expression unless it evaluates to null.", + "examples": [ + true, + "=user.isVip" + ] + }, + "confirmChoices": { + "$role": "expression", + "title": "Array of choice objects", + "description": "Array of simple or structured choices.", + "oneOf": [ + { + "type": "array", + "title": "Simple choices", + "description": "Simple choices to confirm from.", + "items": [ + { + "type": "string", + "title": "Simple choice", + "description": "Simple choice to confirm." + } + ] + }, + { + "type": "array", + "title": "Structured choices", + "description": "Structured choices for confirmations.", + "items": [ + { + "type": "object", + "title": "Choice", + "description": "Choice to confirm.", + "properties": { + "value": { + "type": "string", + "title": "Value", + "description": "Value to return when this choice is selected." + }, + "action": { + "$ref": "#/definitions/botframework.json/definitions/CardAction", + "title": "Action", + "description": "Card action for the choice." + }, + "synonyms": { + "type": "array", + "title": "Synonyms", + "description": "List of synonyms to recognize in addition to the value (optional).", + "items": { + "type": "string", + "title": "Synonym", + "description": "Synonym for choice." + } + } + } + } + ] + }, + { + "$ref": "#/definitions/stringExpression" + } + ] + }, + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "default": false, + "examples": [ + false, + "=user.isVip" + ] + }, + "prompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Initial prompt", + "description": "Message to send to collect information.", + "examples": [ + "What is your birth date?" + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "unrecognizedPrompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Unrecognized prompt", + "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value.", + "examples": [ + "Sorry, I'm having trouble understanding you. I will just use {this.options.defaultValue} for now. You can say 'I'm 36 years old' to change it." + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "invalidPrompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Invalid prompt", + "description": "Message to send when the user input does not meet any validation expression.", + "examples": [ + "Sorry, '{this.value}' does not work. I need a number between 1-150. What is your age?" + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "defaultValueResponse": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Default value response", + "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value.", + "examples": [ + "Sorry, I'm having trouble understanding you. I will just use {this.options.defaultValue} for now. You can say 'I'm 36 years old' to change it." + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "maxTurnCount": { + "$ref": "#/definitions/integerExpression", + "title": "Max turn count", + "description": "Maximum number of re-prompt attempts to collect information.", + "default": 3, + "minimum": 0, + "maximum": 2147483647, + "examples": [ + 3, + "=settings.xyz" + ] + }, + "validations": { + "type": "array", + "title": "Validation expressions", + "description": "Expression to validate user input.", + "items": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Expression which needs to met for the input to be considered valid", + "examples": [ + "int(this.value) > 1 && int(this.value) <= 150", + "count(this.value) < 300" + ] + } + }, + "property": { + "$ref": "#/definitions/stringExpression", + "title": "Property", + "description": "Property to store collected information. Input will be skipped if property has value (unless 'Always prompt' is true).", + "examples": [ + "$birthday", + "dialog.${user.name}", + "=f(x)" + ] + }, + "alwaysPrompt": { + "$ref": "#/definitions/booleanExpression", + "title": "Always prompt", + "description": "Collect information even if the specified 'property' is not empty.", + "default": false, + "examples": [ + false, + "=$val" + ] + }, + "allowInterruptions": { + "$ref": "#/definitions/booleanExpression", + "title": "Allow Interruptions", + "description": "A boolean expression that determines whether the parent should be allowed to interrupt the input.", + "default": true, + "examples": [ + true, + "=user.xyz" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.ConfirmInput" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.ContinueConversation": { + "$role": "implements(Microsoft.IDialog)", + "title": "Continue conversation (Queue)", + "description": "Continue a specific conversation (via StorageQueue implementation).", + "type": "object", + "required": [ + "conversationReference", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "conversationReference": { + "$ref": "#/definitions/objectExpression", + "title": "Conversation Reference", + "description": "A conversation reference. (NOTE: Minimum required values or channelId, conversation).", + "examples": [ + { + "channelId": "skype", + "serviceUrl": "http://smba.skype.com", + "conversation": { + "id": "11111" + }, + "bot": { + "id": "22222" + }, + "user": { + "id": "33333" + }, + "locale": "en-us" + } + ] + }, + "value": { + "$ref": "#/definitions/valueExpression", + "title": "Value", + "description": "Value to send in the activity.value." + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.ContinueConversation" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.ContinueConversationLater": { + "$role": "implements(Microsoft.IDialog)", + "title": "Continue conversation later (Queue)", + "description": "Continue conversation at later time (via StorageQueue implementation).", + "type": "object", + "required": [ + "date", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "date": { + "$ref": "#/definitions/stringExpression", + "title": "Date", + "description": "Date in the future as a ISO string when the conversation should continue.", + "examples": [ + "=addHours(utcNow(), 1)" + ] + }, + "value": { + "$ref": "#/definitions/valueExpression", + "title": "Value", + "description": "Value to send in the activity.value." + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.ContinueConversationLater" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.ContinueLoop": { + "$role": "implements(Microsoft.IDialog)", + "title": "Continue loop", + "description": "Stop executing this template and continue with the next iteration of the loop.", + "type": "object", + "required": [ + "$kind" + ], + "$policies": { + "lastAction": true + }, + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.ContinueLoop" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.CrossTrainedRecognizerSet": { + "$role": "implements(Microsoft.IRecognizer)", + "title": "Cross-trained recognizer set", + "description": "Recognizer for selecting between cross trained recognizers.", + "type": "object", + "required": [ + "recognizers", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional unique id using with RecognizerSet." + }, + "recognizers": { + "type": "array", + "title": "Recognizers", + "description": "List of Recognizers defined for this set.", + "items": { + "$kind": "Microsoft.IRecognizer", + "$ref": "#/definitions/Microsoft.IRecognizer" + } + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.CrossTrainedRecognizerSet" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.CurrencyEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Currency entity recognizer", + "description": "Recognizer which recognizes currency.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.CurrencyEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.DateTimeEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Date and time entity recognizer", + "description": "Recognizer which recognizes dates and time fragments.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.DateTimeEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.DateTimeInput": { + "$role": [ + "implements(Microsoft.IDialog)", + "extends(Microsoft.InputDialog)" + ], + "title": "Date/time input dialog", + "description": "Collect information - Ask for date and/ or time", + "type": "object", + "defaultLocale": { + "$ref": "#/definitions/stringExpression", + "title": "Default locale", + "description": "Default locale.", + "default": "en-us" + }, + "$policies": { + "interactive": true + }, + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "defaultValue": { + "$ref": "#/definitions/stringExpression", + "format": "date-time", + "title": "Default date", + "description": "'Property' will be set to the value or the result of the expression when max turn count is exceeded.", + "examples": [ + "=user.birthday" + ] + }, + "value": { + "$ref": "#/definitions/stringExpression", + "format": "date-time", + "title": "Value", + "description": "'Property' will be set to the value or the result of the expression unless it evaluates to null.", + "examples": [ + "=user.birthday" + ] + }, + "outputFormat": { + "$ref": "#/definitions/expression", + "title": "Output format", + "description": "Expression to use for formatting the output.", + "examples": [ + "=this.value[0].Value" + ] + }, + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "default": false, + "examples": [ + false, + "=user.isVip" + ] + }, + "prompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Initial prompt", + "description": "Message to send to collect information.", + "examples": [ + "What is your birth date?" + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "unrecognizedPrompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Unrecognized prompt", + "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value.", + "examples": [ + "Sorry, I'm having trouble understanding you. I will just use {this.options.defaultValue} for now. You can say 'I'm 36 years old' to change it." + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "invalidPrompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Invalid prompt", + "description": "Message to send when the user input does not meet any validation expression.", + "examples": [ + "Sorry, '{this.value}' does not work. I need a number between 1-150. What is your age?" + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "defaultValueResponse": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Default value response", + "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value.", + "examples": [ + "Sorry, I'm having trouble understanding you. I will just use {this.options.defaultValue} for now. You can say 'I'm 36 years old' to change it." + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "maxTurnCount": { + "$ref": "#/definitions/integerExpression", + "title": "Max turn count", + "description": "Maximum number of re-prompt attempts to collect information.", + "default": 3, + "minimum": 0, + "maximum": 2147483647, + "examples": [ + 3, + "=settings.xyz" + ] + }, + "validations": { + "type": "array", + "title": "Validation expressions", + "description": "Expression to validate user input.", + "items": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Expression which needs to met for the input to be considered valid", + "examples": [ + "int(this.value) > 1 && int(this.value) <= 150", + "count(this.value) < 300" + ] + } + }, + "property": { + "$ref": "#/definitions/stringExpression", + "title": "Property", + "description": "Property to store collected information. Input will be skipped if property has value (unless 'Always prompt' is true).", + "examples": [ + "$birthday", + "dialog.${user.name}", + "=f(x)" + ] + }, + "alwaysPrompt": { + "$ref": "#/definitions/booleanExpression", + "title": "Always prompt", + "description": "Collect information even if the specified 'property' is not empty.", + "default": false, + "examples": [ + false, + "=$val" + ] + }, + "allowInterruptions": { + "$ref": "#/definitions/booleanExpression", + "title": "Allow Interruptions", + "description": "A boolean expression that determines whether the parent should be allowed to interrupt the input.", + "default": true, + "examples": [ + true, + "=user.xyz" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.DateTimeInput" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.DebugBreak": { + "$role": "implements(Microsoft.IDialog)", + "title": "Debugger break", + "description": "If debugger is attached, stop the execution at this point in the conversation.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.DebugBreak" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.DeleteActivity": { + "$role": "implements(Microsoft.IDialog)", + "title": "Delete Activity", + "description": "Delete an activity that was previously sent.", + "type": "object", + "required": [ + "activityId", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "activityId": { + "$ref": "#/definitions/stringExpression", + "title": "ActivityId", + "description": "expression to an activityId to delete", + "examples": [ + "=turn.lastresult.id" + ] + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.DeleteActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.DeleteProperties": { + "$role": "implements(Microsoft.IDialog)", + "title": "Delete Properties", + "description": "Delete multiple properties and any value it holds.", + "type": "object", + "required": [ + "properties", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "properties": { + "type": "array", + "title": "Properties", + "description": "Properties to delete.", + "items": { + "$ref": "#/definitions/stringExpression", + "title": "Property", + "description": "Property to delete." + } + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.DeleteProperties" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.DeleteProperty": { + "$role": "implements(Microsoft.IDialog)", + "title": "Delete property", + "description": "Delete a property and any value it holds.", + "type": "object", + "required": [ + "property", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "property": { + "$ref": "#/definitions/stringExpression", + "title": "Property", + "description": "Property to delete." + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.DeleteProperty" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.DimensionEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Dimension entity recognizer", + "description": "Recognizer which recognizes dimension.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.DimensionEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.EditActions": { + "$role": "implements(Microsoft.IDialog)", + "title": "Edit actions.", + "description": "Edit the current list of actions.", + "type": "object", + "required": [ + "changeType", + "actions", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "changeType": { + "title": "Type of change", + "description": "Type of change to apply to the current actions.", + "oneOf": [ + { + "type": "string", + "title": "Standard change", + "description": "Standard change types.", + "enum": [ + "insertActions", + "insertActionsBeforeTags", + "appendActions", + "endSequence", + "replaceSequence" + ] + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Actions to apply.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.EditActions" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.EditArray": { + "$role": "implements(Microsoft.IDialog)", + "title": "Edit array", + "description": "Modify an array in memory", + "type": "object", + "required": [ + "itemsProperty", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "changeType": { + "title": "Type of change", + "description": "Type of change to the array in memory.", + "oneOf": [ + { + "type": "string", + "title": "Change type", + "description": "Standard change type.", + "enum": [ + "push", + "pop", + "take", + "remove", + "clear" + ] + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "itemsProperty": { + "$ref": "#/definitions/stringExpression", + "title": "Items property", + "description": "Property that holds the array to update." + }, + "resultProperty": { + "$ref": "#/definitions/stringExpression", + "title": "Result property", + "description": "Property to store the result of this action." + }, + "value": { + "$ref": "#/definitions/valueExpression", + "title": "Value", + "description": "New value or expression.", + "examples": [ + "milk", + "=dialog.favColor", + "=dialog.favColor == 'red'" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.EditArray" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.EmailEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Email entity recognizer", + "description": "Recognizer which recognizes email.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.EmailEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.EmitEvent": { + "$role": "implements(Microsoft.IDialog)", + "title": "Emit a custom event", + "description": "Emit an event. Capture this event with a trigger.", + "type": "object", + "required": [ + "eventName", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "eventName": { + "$role": "expression", + "title": "Event name", + "description": "Name of the event to emit.", + "oneOf": [ + { + "type": "string", + "title": "Built-in event", + "description": "Standard event type.", + "enum": [ + "beginDialog", + "resumeDialog", + "repromptDialog", + "cancelDialog", + "endDialog", + "activityReceived", + "recognizedIntent", + "unknownIntent", + "actionsStarted", + "actionsSaved", + "actionsEnded", + "actionsResumed" + ] + }, + { + "type": "string", + "title": "Custom event", + "description": "Custom event type", + "pattern": "^(?!(beginDialog$|resumeDialog$|repromptDialog$|cancelDialog$|endDialog$|activityReceived$|recognizedIntent$|unknownIntent$|actionsStarted$|actionsSaved$|actionsEnded$|actionsResumed))(\\S){1}.*" + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "eventValue": { + "$ref": "#/definitions/valueExpression", + "title": "Event value", + "description": "Value to emit with the event (optional)." + }, + "bubbleEvent": { + "$ref": "#/definitions/booleanExpression", + "title": "Bubble event", + "description": "If true this event is passed on to parent dialogs." + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.EmitEvent" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.EndDialog": { + "$role": "implements(Microsoft.IDialog)", + "title": "End dialog", + "description": "End this dialog.", + "type": "object", + "$policies": { + "lastAction": true + }, + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "value": { + "$ref": "#/definitions/valueExpression", + "title": "Value", + "description": "Result value returned to the parent dialog.", + "examples": [ + "=dialog.userName", + "='tomato'" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.EndDialog" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.EndTurn": { + "$role": "implements(Microsoft.IDialog)", + "title": "End turn", + "description": "End the current turn without ending the dialog.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.EndTurn" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.FirstSelector": { + "$role": "implements(Microsoft.ITriggerSelector)", + "title": "First trigger selector", + "description": "Selector for first true rule", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.FirstSelector" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.Foreach": { + "$role": "implements(Microsoft.IDialog)", + "title": "For each item", + "description": "Execute actions on each item in an a collection.", + "type": "object", + "required": [ + "itemsProperty", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "itemsProperty": { + "$ref": "#/definitions/stringExpression", + "title": "Items property", + "description": "Property that holds the array.", + "examples": [ + "user.todoList" + ] + }, + "index": { + "$ref": "#/definitions/stringExpression", + "title": "Index property", + "description": "Property that holds the index of the item.", + "default": "dialog.foreach.index" + }, + "value": { + "$ref": "#/definitions/stringExpression", + "title": "Value property", + "description": "Property that holds the value of the item.", + "default": "dialog.foreach.value" + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Actions to execute for each item. Use '$foreach.value' to access the value of each item. Use '$foreach.index' to access the index of each item.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.Foreach" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.ForeachPage": { + "$role": "implements(Microsoft.IDialog)", + "title": "For each page", + "description": "Execute actions on each page (collection of items) in an array.", + "type": "object", + "required": [ + "itemsProperty", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "itemsProperty": { + "$ref": "#/definitions/stringExpression", + "title": "Items property", + "description": "Property that holds the array.", + "examples": [ + "user.todoList" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Actions to execute for each page. Use '$foreach.page' to access each page.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "pageIndex": { + "$ref": "#/definitions/stringExpression", + "title": "Index property", + "description": "Property that holds the index of the page.", + "default": "dialog.foreach.pageindex" + }, + "page": { + "$ref": "#/definitions/stringExpression", + "title": "Page property", + "description": "Property that holds the value of the page.", + "default": "dialog.foreach.page" + }, + "pageSize": { + "$ref": "#/definitions/integerExpression", + "title": "Page size", + "description": "Number of items in each page.", + "default": 10 + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.ForeachPage" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.GetActivityMembers": { + "$role": "implements(Microsoft.IDialog)", + "title": "Get activity members", + "description": "Get the members who are participating in an activity. (BotFrameworkAdapter only)", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "property": { + "$ref": "#/definitions/stringExpression", + "title": "Property", + "description": "Property (named location to store information).", + "examples": [ + "user.age" + ] + }, + "activityId": { + "$ref": "#/definitions/stringExpression", + "title": "Activity Id", + "description": "Activity ID or expression to an activityId to use to get the members. If none is defined then the current activity id will be used.", + "examples": [ + "turn.lastresult.id" + ] + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.GetActivityMembers" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.GetConversationMembers": { + "$role": "implements(Microsoft.IDialog)", + "title": "Get conversation members", + "description": "Get the members who are participating in an conversation. (BotFrameworkAdapter only)", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "property": { + "$ref": "#/definitions/stringExpression", + "title": "Property", + "description": "Property (named location to store information).", + "examples": [ + "user.age" + ] + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.GetConversationMembers" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.GetConversationReference": { + "$role": "implements(Microsoft.IDialog)", + "title": "Get ConversationReference", + "description": "Gets the ConversationReference from current context and stores it in property so it can be used to with ContinueConversation action.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "property": { + "$ref": "#/definitions/stringExpression", + "title": "Property", + "description": "Property (named location to store information).", + "examples": [ + "user.age" + ] + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.GetConversationReference" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.GotoAction": { + "$role": "implements(Microsoft.IDialog)", + "title": "Go to action", + "description": "Go to an an action by id.", + "type": "object", + "required": [ + "actionId", + "$kind" + ], + "$policies": { + "lastAction": true + }, + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "actionId": { + "$ref": "#/definitions/stringExpression", + "title": "Action Id", + "description": "Action Id to execute next" + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.GotoAction" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.GuidEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Guid entity recognizer", + "description": "Recognizer which recognizes guids.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.GuidEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.HashtagEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Hashtag entity recognizer", + "description": "Recognizer which recognizes Hashtags.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.HashtagEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.HttpRequest": { + "$role": "implements(Microsoft.IDialog)", + "type": "object", + "title": "HTTP request", + "description": "Make a HTTP request.", + "required": [ + "url", + "method", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "method": { + "type": "string", + "title": "HTTP method", + "description": "HTTP method to use.", + "enum": [ + "GET", + "POST", + "PATCH", + "PUT", + "DELETE" + ], + "examples": [ + "GET", + "POST" + ] + }, + "url": { + "$ref": "#/definitions/stringExpression", + "title": "Url", + "description": "URL to call (supports data binding).", + "examples": [ + "https://contoso.com" + ] + }, + "body": { + "$ref": "#/definitions/valueExpression", + "title": "Body", + "description": "Body to include in the HTTP call (supports data binding).", + "additionalProperties": true + }, + "resultProperty": { + "$ref": "#/definitions/stringExpression", + "title": "Result property", + "description": "A property to store the result of this action. The result can include any of the 4 properties from the HTTP response: statusCode, reasonPhrase, content, and headers. If the content is JSON it will be a deserialized object. The values can be accessed via .content for example.", + "default": "turn.results", + "examples": [ + "dialog.contosodata" + ] + }, + "contentType": { + "$ref": "#/definitions/stringExpression", + "title": "Content type", + "description": "Content media type for the body.", + "examples": [ + "application/json", + "text/plain" + ] + }, + "headers": { + "type": "object", + "title": "Headers", + "description": "One or more headers to include in the request (supports data binding).", + "additionalProperties": { + "$ref": "#/definitions/stringExpression" + } + }, + "responseType": { + "$ref": "#/definitions/stringExpression", + "title": "Response type", + "description": "Defines the type of HTTP response. Automatically calls the 'Send a response' action if set to 'Activity' or 'Activities'.", + "oneOf": [ + { + "type": "string", + "title": "Standard response", + "description": "Standard response type.", + "enum": [ + "none", + "json", + "activity", + "activities", + "binary" + ], + "default": "json" + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.HttpRequest" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.IActivityTemplate": { + "title": "Microsoft ActivityTemplates", + "description": "Components which are ActivityTemplate, which is string template, an activity, or a implementation of ActivityTemplate", + "$role": "interface", + "oneOf": [ + { + "$ref": "#/definitions/Microsoft.ActivityTemplate" + }, + { + "$ref": "#/definitions/Microsoft.StaticActivityTemplate" + }, + { + "$ref": "#/definitions/botframework.json/definitions/Activity", + "required": [ + "type" + ] + }, + { + "type": "string" + } + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Declarative", + "version": "4.13.2" + } + }, + "Microsoft.IAdapter": { + "$role": "interface", + "title": "Bot adapter", + "description": "Component that enables connecting bots to chat clients and applications.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive.Runtime", + "version": "4.13.2" + }, + "properties": { + "route": { + "type": "string", + "title": "Adapter http route", + "description": "Route where to expose the adapter." + }, + "type": { + "type": "string", + "title": "Adapter type name", + "description": "Adapter type name" + } + } + }, + "Microsoft.IDialog": { + "title": "Microsoft dialogs", + "description": "Components which derive from Dialog", + "$role": "interface", + "oneOf": [ + { + "$ref": "#/definitions/Microsoft.AdaptiveDialog" + }, + { + "$ref": "#/definitions/Microsoft.Ask" + }, + { + "$ref": "#/definitions/Microsoft.AttachmentInput" + }, + { + "$ref": "#/definitions/Microsoft.BeginDialog" + }, + { + "$ref": "#/definitions/Microsoft.BeginSkill" + }, + { + "$ref": "#/definitions/Microsoft.BreakLoop" + }, + { + "$ref": "#/definitions/Microsoft.CancelAllDialogs" + }, + { + "$ref": "#/definitions/Microsoft.CancelDialog" + }, + { + "$ref": "#/definitions/Microsoft.ChoiceInput" + }, + { + "$ref": "#/definitions/Microsoft.ConfirmInput" + }, + { + "$ref": "#/definitions/Microsoft.ContinueConversation" + }, + { + "$ref": "#/definitions/Microsoft.ContinueConversationLater" + }, + { + "$ref": "#/definitions/Microsoft.ContinueLoop" + }, + { + "$ref": "#/definitions/Microsoft.DateTimeInput" + }, + { + "$ref": "#/definitions/Microsoft.DebugBreak" + }, + { + "$ref": "#/definitions/Microsoft.DeleteActivity" + }, + { + "$ref": "#/definitions/Microsoft.DeleteProperties" + }, + { + "$ref": "#/definitions/Microsoft.DeleteProperty" + }, + { + "$ref": "#/definitions/Microsoft.EditActions" + }, + { + "$ref": "#/definitions/Microsoft.EditArray" + }, + { + "$ref": "#/definitions/Microsoft.EmitEvent" + }, + { + "$ref": "#/definitions/Microsoft.EndDialog" + }, + { + "$ref": "#/definitions/Microsoft.EndTurn" + }, + { + "$ref": "#/definitions/Microsoft.Foreach" + }, + { + "$ref": "#/definitions/Microsoft.ForeachPage" + }, + { + "$ref": "#/definitions/Microsoft.GetActivityMembers" + }, + { + "$ref": "#/definitions/Microsoft.GetConversationMembers" + }, + { + "$ref": "#/definitions/Microsoft.GetConversationReference" + }, + { + "$ref": "#/definitions/Microsoft.GotoAction" + }, + { + "$ref": "#/definitions/Microsoft.HttpRequest" + }, + { + "$ref": "#/definitions/Microsoft.IfCondition" + }, + { + "$ref": "#/definitions/Microsoft.LogAction" + }, + { + "$ref": "#/definitions/Microsoft.NumberInput" + }, + { + "$ref": "#/definitions/Microsoft.OAuthInput" + }, + { + "$ref": "#/definitions/Microsoft.QnAMakerDialog" + }, + { + "$ref": "#/definitions/Microsoft.RepeatDialog" + }, + { + "$ref": "#/definitions/Microsoft.ReplaceDialog" + }, + { + "$ref": "#/definitions/Microsoft.SendActivity" + }, + { + "$ref": "#/definitions/Microsoft.SendHandoffActivity" + }, + { + "$ref": "#/definitions/Microsoft.SetProperties" + }, + { + "$ref": "#/definitions/Microsoft.SetProperty" + }, + { + "$ref": "#/definitions/Microsoft.SignOutUser" + }, + { + "$ref": "#/definitions/Microsoft.SwitchCondition" + }, + { + "$ref": "#/definitions/Microsoft.TelemetryTrackEventAction" + }, + { + "$ref": "#/definitions/Microsoft.TextInput" + }, + { + "$ref": "#/definitions/Microsoft.ThrowException" + }, + { + "$ref": "#/definitions/Microsoft.TraceActivity" + }, + { + "$ref": "#/definitions/Microsoft.UpdateActivity" + }, + { + "type": "string", + "pattern": "^(?!(=)).*" + } + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Declarative", + "version": "4.13.2" + } + }, + "Microsoft.IEntityRecognizer": { + "$role": "interface", + "title": "Entity recognizers", + "description": "Components which derive from EntityRecognizer.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "oneOf": [ + { + "$ref": "#/definitions/Microsoft.AgeEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.ConfirmationEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.CurrencyEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.DateTimeEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.DimensionEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.EmailEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.GuidEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.HashtagEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.IpEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.MentionEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.NumberEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.NumberRangeEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.OrdinalEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.PercentageEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.PhoneNumberEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.RegexEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.TemperatureEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.UrlEntityRecognizer" + }, + { + "type": "string", + "title": "Reference to Microsoft.IEntityRecognizer", + "description": "Reference to Microsoft.IEntityRecognizer .dialog file." + } + ] + }, + "Microsoft.IfCondition": { + "$role": "implements(Microsoft.IDialog)", + "title": "If condition", + "description": "Two-way branch the conversation flow based on a condition.", + "type": "object", + "required": [ + "condition", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Expression to evaluate.", + "examples": [ + "user.age > 3" + ] + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + true, + "=user.age > 3" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Actions to execute if condition is true.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "elseActions": { + "type": "array", + "title": "Else", + "description": "Actions to execute if condition is false.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.IfCondition" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.ILanguageGenerator": { + "title": "Microsoft LanguageGenerator", + "description": "Components which dervie from the LanguageGenerator class", + "$role": "interface", + "oneOf": [ + { + "$ref": "#/definitions/Microsoft.ResourceMultiLanguageGenerator" + }, + { + "$ref": "#/definitions/Microsoft.TemplateEngineLanguageGenerator" + }, + { + "type": "string" + } + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + } + }, + "Microsoft.InputDialog": { + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "default": false, + "examples": [ + false, + "=user.isVip" + ] + }, + "prompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Initial prompt", + "description": "Message to send to collect information.", + "examples": [ + "What is your birth date?" + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "unrecognizedPrompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Unrecognized prompt", + "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value.", + "examples": [ + "Sorry, I'm having trouble understanding you. I will just use {this.options.defaultValue} for now. You can say 'I'm 36 years old' to change it." + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "invalidPrompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Invalid prompt", + "description": "Message to send when the user input does not meet any validation expression.", + "examples": [ + "Sorry, '{this.value}' does not work. I need a number between 1-150. What is your age?" + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "defaultValueResponse": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Default value response", + "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value.", + "examples": [ + "Sorry, I'm having trouble understanding you. I will just use {this.options.defaultValue} for now. You can say 'I'm 36 years old' to change it." + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "maxTurnCount": { + "$ref": "#/definitions/integerExpression", + "title": "Max turn count", + "description": "Maximum number of re-prompt attempts to collect information.", + "default": 3, + "minimum": 0, + "maximum": 2147483647, + "examples": [ + 3, + "=settings.xyz" + ] + }, + "validations": { + "type": "array", + "title": "Validation expressions", + "description": "Expression to validate user input.", + "items": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Expression which needs to met for the input to be considered valid", + "examples": [ + "int(this.value) > 1 && int(this.value) <= 150", + "count(this.value) < 300" + ] + } + }, + "property": { + "$ref": "#/definitions/stringExpression", + "title": "Property", + "description": "Property to store collected information. Input will be skipped if property has value (unless 'Always prompt' is true).", + "examples": [ + "$birthday", + "dialog.${user.name}", + "=f(x)" + ] + }, + "alwaysPrompt": { + "$ref": "#/definitions/booleanExpression", + "title": "Always prompt", + "description": "Collect information even if the specified 'property' is not empty.", + "default": false, + "examples": [ + false, + "=$val" + ] + }, + "allowInterruptions": { + "$ref": "#/definitions/booleanExpression", + "title": "Allow Interruptions", + "description": "A boolean expression that determines whether the parent should be allowed to interrupt the input.", + "default": true, + "examples": [ + true, + "=user.xyz" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.InputDialog" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.IpEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "IP entity recognizer", + "description": "Recognizer which recognizes internet IP patterns (like 192.1.1.1).", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.IpEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.IRecognizer": { + "title": "Microsoft recognizer", + "description": "Components which derive from Recognizer class", + "$role": "interface", + "oneOf": [ + { + "$ref": "#/definitions/Microsoft.AgeEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.ChannelMentionEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.ConfirmationEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.CrossTrainedRecognizerSet" + }, + { + "$ref": "#/definitions/Microsoft.CurrencyEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.DateTimeEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.DimensionEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.EmailEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.GuidEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.HashtagEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.IpEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.LuisRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.MentionEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.MultiLanguageRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.NumberEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.NumberRangeEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.OrdinalEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.PercentageEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.PhoneNumberEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.QnAMakerRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.RecognizerSet" + }, + { + "$ref": "#/definitions/Microsoft.RegexEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.RegexRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.TemperatureEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.UrlEntityRecognizer" + }, + { + "type": "string" + } + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Declarative", + "version": "4.13.2" + } + }, + "Microsoft.ITextTemplate": { + "title": "Microsoft TextTemplate", + "description": "Components which derive from TextTemplate class", + "$role": "interface", + "oneOf": [ + { + "$ref": "#/definitions/Microsoft.TextTemplate" + }, + { + "type": "string" + } + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Declarative", + "version": "4.13.2" + } + }, + "Microsoft.ITrigger": { + "$role": "interface", + "title": "Microsoft Triggers", + "description": "Components which derive from OnCondition class.", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "oneOf": [ + { + "$ref": "#/definitions/Microsoft.OnActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnAssignEntity" + }, + { + "$ref": "#/definitions/Microsoft.OnBeginDialog" + }, + { + "$ref": "#/definitions/Microsoft.OnCancelDialog" + }, + { + "$ref": "#/definitions/Microsoft.OnChooseEntity" + }, + { + "$ref": "#/definitions/Microsoft.OnChooseIntent" + }, + { + "$ref": "#/definitions/Microsoft.OnChooseProperty" + }, + { + "$ref": "#/definitions/Microsoft.OnCommandActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnCommandResultActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnCondition" + }, + { + "$ref": "#/definitions/Microsoft.OnContinueConversation" + }, + { + "$ref": "#/definitions/Microsoft.OnConversationUpdateActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnDialogEvent" + }, + { + "$ref": "#/definitions/Microsoft.OnEndOfActions" + }, + { + "$ref": "#/definitions/Microsoft.OnEndOfConversationActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnError" + }, + { + "$ref": "#/definitions/Microsoft.OnEventActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnHandoffActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnInstallationUpdateActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnIntent" + }, + { + "$ref": "#/definitions/Microsoft.OnInvokeActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnMessageActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnMessageDeleteActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnMessageReactionActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnMessageUpdateActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnQnAMatch" + }, + { + "$ref": "#/definitions/Microsoft.OnRepromptDialog" + }, + { + "$ref": "#/definitions/Microsoft.OnTypingActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnUnknownIntent" + }, + { + "type": "string", + "title": "Reference to Microsoft.ITrigger", + "description": "Reference to Microsoft.ITrigger .dialog file." + } + ] + }, + "Microsoft.ITriggerSelector": { + "$role": "interface", + "title": "Selectors", + "description": "Components which derive from TriggerSelector class.", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "oneOf": [ + { + "$ref": "#/definitions/Microsoft.ConditionalSelector" + }, + { + "$ref": "#/definitions/Microsoft.FirstSelector" + }, + { + "$ref": "#/definitions/Microsoft.MostSpecificSelector" + }, + { + "$ref": "#/definitions/Microsoft.RandomSelector" + }, + { + "$ref": "#/definitions/Microsoft.TrueSelector" + }, + { + "type": "string", + "title": "Reference to Microsoft.ITriggerSelector", + "description": "Reference to Microsoft.ITriggerSelector .dialog file." + } + ] + }, + "Microsoft.LanguagePolicy": { + "title": "Language policy", + "description": "This represents a policy map for locales lookups to use for language", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": { + "type": "array", + "title": "Per-locale policy", + "description": "Language policy per locale.", + "items": { + "type": "string", + "title": "Locale", + "description": "Locale like en-us." + } + }, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.LanguagePolicy" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.LogAction": { + "$role": "implements(Microsoft.IDialog)", + "title": "Log to console", + "description": "Log a message to the host application. Send a TraceActivity to Bot Framework Emulator (optional).", + "type": "object", + "required": [ + "text", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + true, + "=user.age > 3" + ] + }, + "text": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Text", + "description": "Information to log.", + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "label": { + "$ref": "#/definitions/stringExpression", + "title": "Label", + "description": "Label for the trace activity (used to identify it in a list of trace activities.)" + }, + "traceActivity": { + "$ref": "#/definitions/booleanExpression", + "title": "Send trace activity", + "description": "If true, automatically sends a TraceActivity (view in Bot Framework Emulator)." + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.LogAction" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.LuisRecognizer": { + "$role": "implements(Microsoft.IRecognizer)", + "title": "LUIS Recognizer", + "description": "LUIS recognizer.", + "type": "object", + "required": [ + "applicationId", + "endpoint", + "endpointKey", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.AI.Luis", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional unique id using with RecognizerSet. Other recognizers should return 'DeferToRecognizer_{Id}' intent when cross training data for this recognizer." + }, + "applicationId": { + "$ref": "#/definitions/stringExpression", + "title": "LUIS application id", + "description": "Application ID for your model from the LUIS service." + }, + "version": { + "$ref": "#/definitions/stringExpression", + "title": "LUIS version", + "description": "Optional version to target. If null then predictionOptions.Slot is used." + }, + "endpoint": { + "$ref": "#/definitions/stringExpression", + "title": "LUIS endpoint", + "description": "Endpoint to use for LUIS service like https://westus.api.cognitive.microsoft.com." + }, + "endpointKey": { + "$ref": "#/definitions/stringExpression", + "title": "LUIS prediction key", + "description": "LUIS prediction key used to call endpoint." + }, + "externalEntityRecognizer": { + "$kind": "Microsoft.IRecognizer", + "title": "External entity recognizer", + "description": "Entities recognized by this recognizer will be passed to LUIS as external entities.", + "$ref": "#/definitions/Microsoft.IRecognizer" + }, + "dynamicLists": { + "$ref": "#/definitions/arrayExpression", + "title": "Dynamic lists", + "description": "Runtime defined entity lists.", + "items": { + "title": "Entity list", + "description": "Lists of canonical values and synonyms for an entity.", + "type": "object", + "properties": { + "entity": { + "title": "Entity", + "description": "Entity to extend with a dynamic list.", + "type": "string" + }, + "list": { + "title": "Dynamic list", + "description": "List of canonical forms and synonyms.", + "type": "array", + "items": { + "type": "object", + "title": "List entry", + "description": "Canonical form and synonynms.", + "properties": { + "canonicalForm": { + "title": "Canonical form", + "description": "Resolution if any synonym matches.", + "type": "string" + }, + "synonyms": { + "title": "Synonyms", + "description": "List of synonyms for a canonical form.", + "type": "array", + "items": { + "title": "Synonym", + "description": "Synonym for canonical form.", + "type": "string" + } + } + } + } + } + } + } + }, + "predictionOptions": { + "type": "object", + "title": "Prediction options", + "description": "Options to control LUIS prediction behavior.", + "properties": { + "includeAllIntents": { + "$ref": "#/definitions/booleanExpression", + "title": "Include all intents", + "description": "True for all intents, false for only top intent." + }, + "includeInstanceData": { + "$ref": "#/definitions/booleanExpression", + "title": "Include $instance", + "description": "True to include $instance metadata in the LUIS response." + }, + "log": { + "$ref": "#/definitions/booleanExpression", + "title": "Log utterances", + "description": "True to log utterances on LUIS service." + }, + "preferExternalEntities": { + "$ref": "#/definitions/booleanExpression", + "title": "Prefer external entities", + "description": "True to prefer external entities to those generated by LUIS models." + }, + "slot": { + "$ref": "#/definitions/stringExpression", + "title": "Slot", + "description": "Slot to use for talking to LUIS service like production or staging." + } + } + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.LuisRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.MentionEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Mentions entity recognizer", + "description": "Recognizer which recognizes @Mentions", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.MentionEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.MostSpecificSelector": { + "$role": "implements(Microsoft.ITriggerSelector)", + "title": "Most specific trigger selector", + "description": "Select most specific true events with optional additional selector", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "selector": { + "$kind": "Microsoft.ITriggerSelector", + "$ref": "#/definitions/Microsoft.ITriggerSelector" + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.MostSpecificSelector" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.MultiLanguageRecognizer": { + "$role": "implements(Microsoft.IRecognizer)", + "title": "Multi-language recognizer", + "description": "Configure one recognizer per language and the specify the language fallback policy.", + "type": "object", + "required": [ + "recognizers", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional unique id using with RecognizerSet. Other recognizers should return 'DeferToRecognizer_{Id}' intent when cross training data for this recognizer." + }, + "languagePolicy": { + "$kind": "Microsoft.LanguagePolicy", + "type": "object", + "title": "Language policy", + "description": "Defines fall back languages to try per user input language.", + "$ref": "#/definitions/Microsoft.LanguagePolicy" + }, + "recognizers": { + "type": "object", + "title": "Recognizers", + "description": "Map of language -> Recognizer", + "additionalProperties": { + "$kind": "Microsoft.IRecognizer", + "$ref": "#/definitions/Microsoft.IRecognizer" + } + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.MultiLanguageRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.NumberEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Number entity recognizer", + "description": "Recognizer which recognizes numbers.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.NumberEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.NumberInput": { + "$role": [ + "implements(Microsoft.IDialog)", + "extends(Microsoft.InputDialog)" + ], + "title": "Number input dialog", + "description": "Collect information - Ask for a number.", + "type": "object", + "$policies": { + "interactive": true + }, + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "defaultValue": { + "$ref": "#/definitions/numberExpression", + "title": "Default value", + "description": "'Property' will be set to the value of this expression when max turn count is exceeded.", + "examples": [ + 13, + "=user.age" + ] + }, + "value": { + "$ref": "#/definitions/numberExpression", + "title": "Value", + "description": "'Property' will be set to the value of this expression unless it evaluates to null.", + "examples": [ + 13, + "=user.age" + ] + }, + "outputFormat": { + "$ref": "#/definitions/expression", + "title": "Output format", + "description": "Expression to format the number output.", + "examples": [ + "=this.value", + "=int(this.text)" + ] + }, + "defaultLocale": { + "$ref": "#/definitions/stringExpression", + "title": "Default locale", + "description": "Default locale to use if there is no locale available..", + "default": "en-us" + }, + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "default": false, + "examples": [ + false, + "=user.isVip" + ] + }, + "prompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Initial prompt", + "description": "Message to send to collect information.", + "examples": [ + "What is your birth date?" + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "unrecognizedPrompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Unrecognized prompt", + "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value.", + "examples": [ + "Sorry, I'm having trouble understanding you. I will just use {this.options.defaultValue} for now. You can say 'I'm 36 years old' to change it." + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "invalidPrompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Invalid prompt", + "description": "Message to send when the user input does not meet any validation expression.", + "examples": [ + "Sorry, '{this.value}' does not work. I need a number between 1-150. What is your age?" + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "defaultValueResponse": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Default value response", + "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value.", + "examples": [ + "Sorry, I'm having trouble understanding you. I will just use {this.options.defaultValue} for now. You can say 'I'm 36 years old' to change it." + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "maxTurnCount": { + "$ref": "#/definitions/integerExpression", + "title": "Max turn count", + "description": "Maximum number of re-prompt attempts to collect information.", + "default": 3, + "minimum": 0, + "maximum": 2147483647, + "examples": [ + 3, + "=settings.xyz" + ] + }, + "validations": { + "type": "array", + "title": "Validation expressions", + "description": "Expression to validate user input.", + "items": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Expression which needs to met for the input to be considered valid", + "examples": [ + "int(this.value) > 1 && int(this.value) <= 150", + "count(this.value) < 300" + ] + } + }, + "property": { + "$ref": "#/definitions/stringExpression", + "title": "Property", + "description": "Property to store collected information. Input will be skipped if property has value (unless 'Always prompt' is true).", + "examples": [ + "$birthday", + "dialog.${user.name}", + "=f(x)" + ] + }, + "alwaysPrompt": { + "$ref": "#/definitions/booleanExpression", + "title": "Always prompt", + "description": "Collect information even if the specified 'property' is not empty.", + "default": false, + "examples": [ + false, + "=$val" + ] + }, + "allowInterruptions": { + "$ref": "#/definitions/booleanExpression", + "title": "Allow Interruptions", + "description": "A boolean expression that determines whether the parent should be allowed to interrupt the input.", + "default": true, + "examples": [ + true, + "=user.xyz" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.NumberInput" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.NumberRangeEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Number range entity recognizer", + "description": "Recognizer which recognizes ranges of numbers (Example:2 to 5).", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.NumberRangeEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OAuthInput": { + "$role": "implements(Microsoft.IDialog)", + "title": "OAuthInput Dialog", + "description": "Collect login information before each request.", + "type": "object", + "required": [ + "connectionName", + "$kind" + ], + "$policies": { + "interactive": true + }, + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "connectionName": { + "$ref": "#/definitions/stringExpression", + "title": "Connection name", + "description": "The connection name configured in Azure Web App Bot OAuth settings.", + "examples": [ + "msgraphOAuthConnection" + ] + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + true, + "=user.age > 3" + ] + }, + "text": { + "$ref": "#/definitions/stringExpression", + "title": "Text", + "description": "Text shown in the OAuth signin card.", + "examples": [ + "Please sign in. ", + "=concat(x,y,z)" + ] + }, + "title": { + "$ref": "#/definitions/stringExpression", + "title": "Title", + "description": "Title shown in the OAuth signin card.", + "examples": [ + "Login" + ] + }, + "timeout": { + "$ref": "#/definitions/integerExpression", + "title": "Timeout", + "description": "Time out setting for the OAuth signin card.", + "default": 900000 + }, + "property": { + "$ref": "#/definitions/stringExpression", + "title": "Token property", + "description": "Property to store the OAuth token result. WARNING: Changing this location is not recommended as you should call OAuthInput immediately before each use of the token.", + "default": "turn.token", + "examples": [ + "dialog.token" + ] + }, + "invalidPrompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Invalid prompt", + "description": "Message to send if user response is invalid.", + "examples": [ + "Sorry, the login info you provided is not valid." + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "defaultValueResponse": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Default value response", + "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value.", + "examples": [ + "Login failed." + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "maxTurnCount": { + "$ref": "#/definitions/integerExpression", + "title": "Max turn count", + "description": "Maximum number of re-prompt attempts to collect information.", + "default": 3, + "examples": [ + 3 + ] + }, + "defaultValue": { + "$ref": "#/definitions/expression", + "title": "Default value", + "description": "Expression to examine on each turn of the conversation as possible value to the property.", + "examples": [ + "@token" + ] + }, + "allowInterruptions": { + "$ref": "#/definitions/booleanExpression", + "title": "Allow interruptions", + "description": "A boolean expression that determines whether the parent should be allowed to interrupt the input.", + "default": true, + "examples": [ + true, + "=f(x)" + ] + }, + "alwaysPrompt": { + "$ref": "#/definitions/booleanExpression", + "title": "Always prompt", + "description": "Collect information even if the specified 'property' is not empty.", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OAuthInput" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnActivity": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On activity", + "description": "Actions to perform on receipt of a generic activity.", + "type": "object", + "required": [ + "type", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "type": { + "type": "string", + "title": "Activity type", + "description": "The Activity.Type to match" + }, + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnAssignEntity": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On entity assignment", + "description": "Actions to apply an operation on a property and value.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "operation": { + "type": "string", + "title": "Operation", + "description": "Operation filter on event." + }, + "property": { + "type": "string", + "title": "Property", + "description": "Property filter on event." + }, + "value": { + "type": "string", + "title": "Value", + "description": "Value filter on event." + }, + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnAssignEntity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnBeginDialog": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On begin dialog", + "description": "Actions to perform when this dialog begins.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnBeginDialog" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnCancelDialog": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On cancel dialog", + "description": "Actions to perform on cancel dialog event.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnCancelDialog" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnChooseEntity": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On choose entity", + "description": "Actions to be performed when value is ambiguous for operator and property.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "operation": { + "type": "string", + "title": "Operation", + "description": "Operation filter on event." + }, + "property": { + "type": "string", + "title": "Property", + "description": "Property filter on event." + }, + "value": { + "type": "string", + "title": "Ambiguous value", + "description": "Ambiguous value filter on event." + }, + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnChooseEntity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnChooseIntent": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On ambiguous intent", + "description": "Actions to perform on when an intent is ambiguous.", + "type": "object", + "required": [ + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "intents": { + "type": "array", + "title": "Intents", + "description": "Intents that must be in the ChooseIntent result for this condition to trigger.", + "items": { + "title": "Intent", + "description": "Intent name to trigger on.", + "type": "string" + } + }, + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnChooseIntent" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnChooseProperty": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On choose property", + "description": "Actions to take when there are multiple possible mappings of entities to properties and operations.", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "type": "object", + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnChooseProperty" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnCommandActivity": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On Command activity", + "description": "Actions to perform on receipt of an activity with type 'Command'. Overrides Intent trigger.", + "type": "object", + "required": [ + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnCommandActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnCommandResultActivity": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On Command Result activity", + "description": "Actions to perform on receipt of an activity with type 'CommandResult'. Overrides Intent trigger.", + "type": "object", + "required": [ + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnCommandResultActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnCondition": { + "$role": "implements(Microsoft.ITrigger)", + "title": "On condition", + "description": "Actions to perform when specified condition is true.", + "type": "object", + "required": [ + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnCondition" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnContinueConversation": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On continue conversation", + "description": "Actions to perform when a conversation is started up again from a ContinueConversationLater action.", + "type": "object", + "required": [ + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnContinueConversation" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnConversationUpdateActivity": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On ConversationUpdate activity", + "description": "Actions to perform on receipt of an activity with type 'ConversationUpdate'.", + "type": "object", + "required": [ + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnConversationUpdateActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnDialogEvent": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On dialog event", + "description": "Actions to perform when a specific dialog event occurs.", + "type": "object", + "required": [ + "event", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "event": { + "type": "string", + "title": "Dialog event name", + "description": "Name of dialog event." + }, + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnDialogEvent" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnEndOfActions": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On end of actions", + "description": "Actions to take when there are no more actions in the current dialog.", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "type": "object", + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnEndOfActions" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnEndOfConversationActivity": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On EndOfConversation activity", + "description": "Actions to perform on receipt of an activity with type 'EndOfConversation'.", + "type": "object", + "required": [ + "$kind" + ], + "$policies": { + "nonInteractive": true + }, + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnEndOfConversationActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnError": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On error", + "description": "Action to perform when an 'Error' dialog event occurs.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnError" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnEventActivity": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On Event activity", + "description": "Actions to perform on receipt of an activity with type 'Event'.", + "type": "object", + "required": [ + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnEventActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnHandoffActivity": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On Handoff activity", + "description": "Actions to perform on receipt of an activity with type 'HandOff'.", + "type": "object", + "required": [ + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnHandoffActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnInstallationUpdateActivity": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On InstallationUpdate activity", + "description": "Actions to perform on receipt of an activity with type 'InstallationUpdate'.", + "type": "object", + "required": [ + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnInstallationUpdateActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnIntent": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On intent recognition", + "description": "Actions to perform when specified intent is recognized.", + "type": "object", + "required": [ + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "intent": { + "type": "string", + "title": "Intent", + "description": "Name of intent." + }, + "entities": { + "type": "array", + "title": "Entities", + "description": "Required entities.", + "items": { + "type": "string", + "title": "Entity", + "description": "Entity that must be present." + } + }, + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnIntent" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnInvokeActivity": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On Invoke activity", + "description": "Actions to perform on receipt of an activity with type 'Invoke'.", + "type": "object", + "required": [ + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnInvokeActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnMessageActivity": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On Message activity", + "description": "Actions to perform on receipt of an activity with type 'Message'. Overrides Intent trigger.", + "type": "object", + "required": [ + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnMessageActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnMessageDeleteActivity": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On MessageDelete activity", + "description": "Actions to perform on receipt of an activity with type 'MessageDelete'.", + "type": "object", + "required": [ + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnMessageDeleteActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnMessageReactionActivity": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On MessageReaction activity", + "description": "Actions to perform on receipt of an activity with type 'MessageReaction'.", + "type": "object", + "required": [ + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnMessageReactionActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnMessageUpdateActivity": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On MessageUpdate activity", + "description": "Actions to perform on receipt of an activity with type 'MessageUpdate'.", + "type": "object", + "required": [ + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnMessageUpdateActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnQnAMatch": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On QnAMaker match", + "description": "Actions to perform on when an match from QnAMaker is found.", + "type": "object", + "required": [ + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnQnAMatch" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnRepromptDialog": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On RepromptDialog", + "description": "Actions to perform when 'RepromptDialog' event occurs.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnRepromptDialog" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnTypingActivity": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On Typing activity", + "description": "Actions to perform on receipt of an activity with type 'Typing'.", + "type": "object", + "required": [ + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnTypingActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnUnknownIntent": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On unknown intent", + "description": "Action to perform when user input is unrecognized or if none of the 'on intent recognition' triggers match recognized intent.", + "type": "object", + "required": [ + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnUnknownIntent" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OrdinalEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Ordinal entity recognizer", + "description": "Recognizer which recognizes ordinals (example: first, second, 3rd).", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OrdinalEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.PercentageEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Percentage entity recognizer", + "description": "Recognizer which recognizes percentages.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.PercentageEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.PhoneNumberEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Phone number entity recognizer", + "description": "Recognizer which recognizes phone numbers.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.PhoneNumberEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.QnAMakerDialog": { + "$role": "implements(Microsoft.IDialog)", + "title": "QnAMaker dialog", + "description": "Dialog which uses QnAMAker knowledge base to answer questions.", + "type": "object", + "required": [ + "knowledgeBaseId", + "endpointKey", + "hostname", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.AI.QnA", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "knowledgeBaseId": { + "$ref": "#/definitions/stringExpression", + "title": "KnowledgeBase Id", + "description": "KnowledgeBase Id of your QnA Maker KnowledgeBase.", + "default": "=settings.qna.knowledgebaseid" + }, + "endpointKey": { + "$ref": "#/definitions/stringExpression", + "title": "Endpoint key", + "description": "Endpoint key for the QnA Maker KB.", + "default": "=settings.qna.endpointkey" + }, + "hostname": { + "$ref": "#/definitions/stringExpression", + "title": "Hostname", + "description": "Hostname for your QnA Maker service.", + "default": "=settings.qna.hostname", + "examples": [ + "https://yourserver.azurewebsites.net/qnamaker" + ] + }, + "noAnswer": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Fallback answer", + "description": "Default answer to return when none found in KB.", + "default": "Sorry, I did not find an answer.", + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "threshold": { + "$ref": "#/definitions/numberExpression", + "title": "Threshold", + "description": "Threshold score to filter results.", + "default": 0.3 + }, + "activeLearningCardTitle": { + "$ref": "#/definitions/stringExpression", + "title": "Active learning card title", + "description": "Title for active learning suggestions card.", + "default": "Did you mean:" + }, + "cardNoMatchText": { + "$ref": "#/definitions/stringExpression", + "title": "Card no match text", + "description": "Text for no match option.", + "default": "None of the above." + }, + "cardNoMatchResponse": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Card no match response", + "description": "Custom response when no match option was selected.", + "default": "Thanks for the feedback.", + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "strictFilters": { + "$ref": "#/definitions/arrayExpression", + "title": "Strict filters", + "description": "Metadata filters to use when calling the QnA Maker KB.", + "items": { + "type": "object", + "title": "Metadata filter", + "description": "Metadata filter.", + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "Name of filter property.", + "maximum": 100 + }, + "value": { + "type": "string", + "title": "Value", + "description": "Value to filter on.", + "maximum": 100 + } + } + } + }, + "top": { + "$ref": "#/definitions/numberExpression", + "title": "Top", + "description": "The number of answers you want to retrieve.", + "default": 3 + }, + "isTest": { + "type": "boolean", + "title": "IsTest", + "description": "True, if pointing to Test environment, else false.", + "default": false + }, + "rankerType": { + "$ref": "#/definitions/stringExpression", + "title": "Ranker type", + "description": "Type of Ranker.", + "oneOf": [ + { + "title": "Standard ranker", + "description": "Standard ranker types.", + "enum": [ + "default", + "questionOnly", + "autoSuggestQuestion" + ], + "default": "default" + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "strictFiltersJoinOperator": { + "$ref": "#/definitions/stringExpression", + "title": "StrictFiltersJoinOperator", + "description": "Join operator for Strict Filters.", + "oneOf": [ + { + "title": "Join operator", + "description": "Value of Join Operator to be used as conjunction with Strict Filter values.", + "enum": [ + "AND", + "OR" + ], + "default": "AND" + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.QnAMakerDialog" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.QnAMakerRecognizer": { + "$role": "implements(Microsoft.IRecognizer)", + "title": "QnAMaker recognizer", + "description": "Recognizer for generating QnAMatch intents from a KB.", + "type": "object", + "required": [ + "knowledgeBaseId", + "endpointKey", + "hostname", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.AI.QnA", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional unique id using with RecognizerSet." + }, + "knowledgeBaseId": { + "$ref": "#/definitions/stringExpression", + "title": "KnowledgeBase Id", + "description": "Knowledge base Id of your QnA Maker knowledge base.", + "default": "=settings.qna.knowledgebaseid" + }, + "endpointKey": { + "$ref": "#/definitions/stringExpression", + "title": "Endpoint key", + "description": "Endpoint key for the QnA Maker KB.", + "default": "=settings.qna.endpointkey" + }, + "hostname": { + "$ref": "#/definitions/stringExpression", + "title": "Hostname", + "description": "Hostname for your QnA Maker service.", + "default": "=settings.qna.hostname", + "examples": [ + "https://yourserver.azurewebsites.net/qnamaker" + ] + }, + "threshold": { + "$ref": "#/definitions/numberExpression", + "title": "Threshold", + "description": "Threshold score to filter results.", + "default": 0.3 + }, + "strictFilters": { + "$ref": "#/definitions/arrayExpression", + "title": "Strict filters", + "description": "Metadata filters to use when calling the QnA Maker KB.", + "items": { + "type": "object", + "title": "Metadata filters", + "description": "Metadata filters to use when querying QnA Maker KB.", + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "Name to filter on.", + "maximum": 100 + }, + "value": { + "type": "string", + "title": "Value", + "description": "Value to restrict filter.", + "maximum": 100 + } + } + } + }, + "top": { + "$ref": "#/definitions/numberExpression", + "title": "Top", + "description": "The number of answers you want to retrieve.", + "default": 3 + }, + "isTest": { + "$ref": "#/definitions/booleanExpression", + "title": "Use test environment", + "description": "True, if pointing to Test environment, else false.", + "examples": [ + true, + "=f(x)" + ] + }, + "rankerType": { + "title": "Ranker type", + "description": "Type of Ranker.", + "oneOf": [ + { + "type": "string", + "title": "Ranker type", + "description": "Type of Ranker.", + "enum": [ + "default", + "questionOnly", + "autoSuggestQuestion" + ], + "default": "default" + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "strictFiltersJoinOperator": { + "$ref": "#/definitions/stringExpression", + "title": "StrictFiltersJoinOperator", + "description": "Join operator for Strict Filters.", + "oneOf": [ + { + "title": "Join operator", + "description": "Value of Join Operator to be used as onjuction with Strict Filter values.", + "enum": [ + "AND", + "OR" + ], + "default": "AND" + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "includeDialogNameInMetadata": { + "$ref": "#/definitions/booleanExpression", + "title": "Include dialog name", + "description": "When set to false, the dialog name will not be passed to QnAMaker. (default) is true", + "default": true, + "examples": [ + true, + "=f(x)" + ] + }, + "metadata": { + "$ref": "#/definitions/arrayExpression", + "title": "Metadata filters", + "description": "Metadata filters to use when calling the QnA Maker KB.", + "items": { + "type": "object", + "title": "Metadata filter", + "description": "Metadata filter to use when calling the QnA Maker KB.", + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "Name of value to test." + }, + "value": { + "type": "string", + "title": "Value", + "description": "Value to filter against." + } + } + } + }, + "context": { + "$ref": "#/definitions/objectExpression", + "title": "QnA request context", + "description": "Context to use for ranking." + }, + "qnaId": { + "$ref": "#/definitions/integerExpression", + "title": "QnA Id", + "description": "A number or expression which is the QnAId to paass to QnAMaker API." + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.QnAMakerRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.RandomSelector": { + "$role": "implements(Microsoft.ITriggerSelector)", + "title": "Random rule selector", + "description": "Select most specific true rule.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "seed": { + "type": "integer", + "title": "Random seed", + "description": "Random seed to start random number generation." + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.RandomSelector" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.RecognizerSet": { + "$role": "implements(Microsoft.IRecognizer)", + "title": "Recognizer set", + "description": "Creates the union of the intents and entities of the recognizers in the set.", + "type": "object", + "required": [ + "recognizers", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional unique id using with RecognizerSet. Other recognizers should return 'DeferToRecognizer_{Id}' intent when cross training data for this recognizer." + }, + "recognizers": { + "type": "array", + "title": "Recognizers", + "description": "List of Recognizers defined for this set.", + "items": { + "$kind": "Microsoft.IRecognizer", + "$ref": "#/definitions/Microsoft.IRecognizer" + } + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.RecognizerSet" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.RegexEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Regex entity recognizer", + "description": "Recognizer which recognizes patterns of input based on regex.", + "type": "object", + "required": [ + "name", + "pattern", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "Name of the entity" + }, + "pattern": { + "type": "string", + "title": "Pattern", + "description": "Pattern expressed as regular expression." + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.RegexEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.RegexRecognizer": { + "$role": "implements(Microsoft.IRecognizer)", + "title": "Regex recognizer", + "description": "Use regular expressions to recognize intents and entities from user input.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional unique id using with RecognizerSet. Other recognizers should return 'DeferToRecognizer_{Id}' intent when cross training data for this recognizer." + }, + "intents": { + "type": "array", + "title": "RegEx patterns to intents", + "description": "Collection of patterns to match for an intent.", + "items": { + "type": "object", + "title": "Pattern", + "description": "Intent and regex pattern.", + "properties": { + "intent": { + "type": "string", + "title": "Intent", + "description": "The intent name." + }, + "pattern": { + "type": "string", + "title": "Pattern", + "description": "The regular expression pattern." + } + } + } + }, + "entities": { + "type": "array", + "title": "Entity recognizers", + "description": "Collection of entity recognizers to use.", + "items": { + "$kind": "Microsoft.IEntityRecognizer", + "$ref": "#/definitions/Microsoft.IEntityRecognizer" + } + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.RegexRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.RepeatDialog": { + "$role": "implements(Microsoft.IDialog)", + "type": "object", + "title": "Repeat dialog", + "description": "Repeat current dialog.", + "$policies": { + "lastAction": true + }, + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "allowLoop": { + "$ref": "#/definitions/booleanExpression", + "title": "AllowLoop", + "description": "Optional condition which if true will allow loop of the repeated dialog.", + "examples": [ + "user.age > 3" + ] + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "options": { + "$ref": "#/definitions/objectExpression", + "title": "Options", + "description": "One or more options that are passed to the dialog that is called.", + "additionalProperties": { + "type": "string", + "title": "Options", + "description": "Options for repeating dialog." + } + }, + "activityProcessed": { + "$ref": "#/definitions/booleanExpression", + "title": "Activity processed", + "description": "When set to false, the dialog that is called can process the current activity.", + "default": true + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.RepeatDialog" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.ReplaceDialog": { + "$role": "implements(Microsoft.IDialog)", + "type": "object", + "title": "Replace dialog", + "description": "Replace current dialog with another dialog.", + "$policies": { + "lastAction": true + }, + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "dialog": { + "oneOf": [ + { + "$kind": "Microsoft.IDialog", + "pattern": "^(?!(=)).*", + "title": "Dialog", + "$ref": "#/definitions/Microsoft.IDialog" + }, + { + "$ref": "#/definitions/equalsExpression", + "examples": [ + "=settings.dialogId" + ] + } + ], + "title": "Dialog name", + "description": "Name of the dialog to call." + }, + "options": { + "$ref": "#/definitions/objectExpression", + "title": "Options", + "description": "One or more options that are passed to the dialog that is called.", + "additionalProperties": { + "type": "string", + "title": "Options", + "description": "Options for replacing dialog." + } + }, + "activityProcessed": { + "$ref": "#/definitions/booleanExpression", + "title": "Activity processed", + "description": "When set to false, the dialog that is called can process the current activity.", + "default": true + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.ReplaceDialog" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.ResourceMultiLanguageGenerator": { + "$role": "implements(Microsoft.ILanguageGenerator)", + "title": "Resource multi-language generator", + "description": "MultiLanguage Generator which is bound to resource by resource Id.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional generator ID." + }, + "resourceId": { + "type": "string", + "title": "Resource Id", + "description": "Resource which is the root language generator. Other generaters with the same name and language suffix will be loaded into this generator and used based on the Language Policy.", + "default": "dialog.result" + }, + "languagePolicy": { + "type": "object", + "title": "Language policy", + "description": "Set alternate language policy for this generator. If not set, the global language policy will be used." + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.ResourceMultiLanguageGenerator" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.SendActivity": { + "$role": "implements(Microsoft.IDialog)", + "title": "Send an activity", + "description": "Respond with an activity.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action." + }, + "activity": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Activity", + "description": "Activity to send.", + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.SendActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.SendHandoffActivity": { + "$role": "implements(Microsoft.IDialog)", + "title": "Send a handoff activity", + "description": "Sends a handoff activity to trigger a handoff request.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "context": { + "$ref": "#/definitions/objectExpression", + "title": "Context", + "description": "Context to send with the handoff request" + }, + "transcript": { + "$ref": "#/definitions/objectExpression", + "title": "transcript", + "description": "Transcript to send with the handoff request" + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.SendHandoffActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.SetProperties": { + "$role": "implements(Microsoft.IDialog)", + "title": "Set property", + "description": "Set one or more property values.", + "type": "object", + "required": [ + "assignments", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + true, + "=user.age > 3" + ] + }, + "assignments": { + "type": "array", + "title": "Assignments", + "description": "Property value assignments to set.", + "items": { + "type": "object", + "title": "Assignment", + "description": "Property assignment.", + "properties": { + "property": { + "$ref": "#/definitions/stringExpression", + "title": "Property", + "description": "Property (named location to store information).", + "examples": [ + "user.age" + ] + }, + "value": { + "$ref": "#/definitions/valueExpression", + "title": "Value", + "description": "New value or expression.", + "examples": [ + "='milk'", + "=dialog.favColor", + "=dialog.favColor == 'red'" + ] + } + } + } + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.SetProperties" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.SetProperty": { + "$role": "implements(Microsoft.IDialog)", + "title": "Set property", + "description": "Set property to a value.", + "type": "object", + "required": [ + "property", + "value", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + true, + "=user.age > 3" + ] + }, + "property": { + "$ref": "#/definitions/stringExpression", + "title": "Property", + "description": "Property (named location to store information).", + "examples": [ + "user.age" + ] + }, + "value": { + "$ref": "#/definitions/valueExpression", + "title": "Value", + "description": "New value or expression.", + "examples": [ + "='milk'", + "=dialog.favColor", + "=dialog.favColor == 'red'" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.SetProperty" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.SignOutUser": { + "$role": "implements(Microsoft.IDialog)", + "title": "Sign out user", + "description": "Sign a user out that was logged in previously using OAuthInput.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "userId": { + "$ref": "#/definitions/stringExpression", + "title": "UserId", + "description": "Expression to an user to signout. Default is user.id.", + "default": "=user.id" + }, + "connectionName": { + "$ref": "#/definitions/stringExpression", + "title": "Connection name", + "description": "Connection name that was used with OAuthInput to log a user in." + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + true, + "=user.age > 3" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.SignOutUser" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.StaticActivityTemplate": { + "$role": "implements(Microsoft.IActivityTemplate)", + "title": "Microsoft static activity template", + "description": "This allows you to define a static Activity object", + "type": "object", + "required": [ + "activity", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "activity": { + "$ref": "#/definitions/botframework.json/definitions/Activity", + "title": "Activity", + "description": "A static Activity to used.", + "required": [ + "type" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.StaticActivityTemplate" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.SwitchCondition": { + "$role": "implements(Microsoft.IDialog)", + "title": "Switch condition", + "description": "Execute different actions based on the value of a property.", + "type": "object", + "required": [ + "condition", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "condition": { + "$ref": "#/definitions/stringExpression", + "title": "Condition", + "description": "Property to evaluate.", + "examples": [ + "user.favColor" + ] + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + true, + "=user.age > 3" + ] + }, + "cases": { + "type": "array", + "title": "Cases", + "description": "Actions for each possible condition.", + "items": { + "type": "object", + "title": "Case", + "description": "Case and actions.", + "required": [ + "value" + ], + "properties": { + "value": { + "type": [ + "number", + "integer", + "boolean", + "string" + ], + "title": "Value", + "description": "The value to compare the condition with.", + "examples": [ + "red", + "true", + "13" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + } + } + } + }, + "default": { + "type": "array", + "title": "Default", + "description": "Actions to execute if none of the cases meet the condition.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.SwitchCondition" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.TelemetryTrackEventAction": { + "$role": "implements(Microsoft.IDialog)", + "type": "object", + "title": "Telemetry - track event", + "description": "Track a custom event using the registered Telemetry Client.", + "required": [ + "url", + "method", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "eventName": { + "$ref": "#/definitions/stringExpression", + "title": "Event name", + "description": "The name of the event to track.", + "examples": [ + "MyEventStarted", + "MyEventCompleted" + ] + }, + "properties": { + "type": "object", + "title": "Properties", + "description": "One or more properties to attach to the event being tracked.", + "additionalProperties": { + "$ref": "#/definitions/stringExpression" + } + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.TelemetryTrackEventAction" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.TemperatureEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Temperature recognizer", + "description": "Recognizer which recognizes temperatures.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.TemperatureEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.TemplateEngineLanguageGenerator": { + "$role": "implements(Microsoft.ILanguageGenerator)", + "title": "Template multi-language generator", + "description": "Template Generator which allows only inline evaluation of templates.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional generator ID." + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.TemplateEngineLanguageGenerator" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.TextInput": { + "$role": [ + "implements(Microsoft.IDialog)", + "extends(Microsoft.InputDialog)" + ], + "type": "object", + "title": "Text input dialog", + "description": "Collection information - Ask for a word or sentence.", + "$policies": { + "interactive": true + }, + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "defaultValue": { + "$ref": "#/definitions/stringExpression", + "title": "Default value", + "description": "'Property' will be set to the value of this expression when max turn count is exceeded.", + "examples": [ + "hello world", + "Hello ${user.name}", + "=concat(user.firstname, user.lastName)" + ] + }, + "value": { + "$ref": "#/definitions/stringExpression", + "title": "Value", + "description": "'Property' will be set to the value of this expression unless it evaluates to null.", + "examples": [ + "hello world", + "Hello ${user.name}", + "=concat(user.firstname, user.lastName)" + ] + }, + "outputFormat": { + "$ref": "#/definitions/stringExpression", + "title": "Output format", + "description": "Expression to format the output.", + "examples": [ + "=toUpper(this.value)", + "${toUpper(this.value)}" + ] + }, + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "default": false, + "examples": [ + false, + "=user.isVip" + ] + }, + "prompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Initial prompt", + "description": "Message to send to collect information.", + "examples": [ + "What is your birth date?" + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "unrecognizedPrompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Unrecognized prompt", + "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value.", + "examples": [ + "Sorry, I'm having trouble understanding you. I will just use {this.options.defaultValue} for now. You can say 'I'm 36 years old' to change it." + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "invalidPrompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Invalid prompt", + "description": "Message to send when the user input does not meet any validation expression.", + "examples": [ + "Sorry, '{this.value}' does not work. I need a number between 1-150. What is your age?" + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "defaultValueResponse": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Default value response", + "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value.", + "examples": [ + "Sorry, I'm having trouble understanding you. I will just use {this.options.defaultValue} for now. You can say 'I'm 36 years old' to change it." + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "maxTurnCount": { + "$ref": "#/definitions/integerExpression", + "title": "Max turn count", + "description": "Maximum number of re-prompt attempts to collect information.", + "default": 3, + "minimum": 0, + "maximum": 2147483647, + "examples": [ + 3, + "=settings.xyz" + ] + }, + "validations": { + "type": "array", + "title": "Validation expressions", + "description": "Expression to validate user input.", + "items": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Expression which needs to met for the input to be considered valid", + "examples": [ + "int(this.value) > 1 && int(this.value) <= 150", + "count(this.value) < 300" + ] + } + }, + "property": { + "$ref": "#/definitions/stringExpression", + "title": "Property", + "description": "Property to store collected information. Input will be skipped if property has value (unless 'Always prompt' is true).", + "examples": [ + "$birthday", + "dialog.${user.name}", + "=f(x)" + ] + }, + "alwaysPrompt": { + "$ref": "#/definitions/booleanExpression", + "title": "Always prompt", + "description": "Collect information even if the specified 'property' is not empty.", + "default": false, + "examples": [ + false, + "=$val" + ] + }, + "allowInterruptions": { + "$ref": "#/definitions/booleanExpression", + "title": "Allow Interruptions", + "description": "A boolean expression that determines whether the parent should be allowed to interrupt the input.", + "default": true, + "examples": [ + true, + "=user.xyz" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.TextInput" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.TextTemplate": { + "$role": "implements(Microsoft.ITextTemplate)", + "title": "Microsoft TextTemplate", + "description": "Use LG Templates to create text", + "type": "object", + "required": [ + "template", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "template": { + "title": "Template", + "description": "Language Generator template to evaluate to create the text.", + "type": "string" + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.TextTemplate" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.ThrowException": { + "$role": "implements(Microsoft.IDialog)", + "title": "Throw an exception", + "description": "Throw an exception. Capture this exception with OnError trigger.", + "type": "object", + "required": [ + "errorValue", + "$kind" + ], + "$policies": { + "lastAction": true + }, + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "errorValue": { + "$ref": "#/definitions/valueExpression", + "title": "Error value", + "description": "Error value to throw." + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.ThrowException" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.TraceActivity": { + "$role": "implements(Microsoft.IDialog)", + "title": "Send a TraceActivity", + "description": "Send a trace activity to the transcript logger and/ or Bot Framework Emulator.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + true, + "=user.age > 3" + ] + }, + "name": { + "$ref": "#/definitions/stringExpression", + "title": "Name", + "description": "Name of the trace activity" + }, + "label": { + "$ref": "#/definitions/stringExpression", + "title": "Label", + "description": "Label for the trace activity (used to identify it in a list of trace activities.)" + }, + "valueType": { + "$ref": "#/definitions/stringExpression", + "title": "Value type", + "description": "Type of value" + }, + "value": { + "$ref": "#/definitions/valueExpression", + "title": "Value", + "description": "Property that holds the value to send as trace activity." + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.TraceActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.TrueSelector": { + "$role": "implements(Microsoft.ITriggerSelector)", + "title": "True trigger selector", + "description": "Selector for all true events", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.TrueSelector" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.UpdateActivity": { + "$role": "implements(Microsoft.IDialog)", + "title": "Update an activity", + "description": "Respond with an activity.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + true, + "=user.age > 3" + ] + }, + "activityId": { + "$ref": "#/definitions/stringExpression", + "title": "Activity Id", + "description": "An string expression with the activity id to update.", + "examples": [ + "=turn.lastresult.id" + ] + }, + "activity": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Activity", + "description": "Activity to send.", + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.UpdateActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.UrlEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Url recognizer", + "description": "Recognizer which recognizes urls.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.UrlEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "numberExpression": { + "$role": "expression", + "title": "Number or expression", + "description": "Number constant or expression to evaluate.", + "oneOf": [ + { + "type": "number", + "title": "Number", + "description": "Number constant.", + "default": 0, + "examples": [ + 15.5 + ] + }, + { + "$ref": "#/definitions/equalsExpression", + "examples": [ + "=dialog.quantity" + ] + } + ] + }, + "objectExpression": { + "$role": "expression", + "title": "Object or expression", + "description": "Object or expression to evaluate.", + "oneOf": [ + { + "type": "object", + "title": "Object", + "description": "Object constant." + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "role": { + "title": "$role", + "description": "Defines the role played in the dialog schema from [expression|interface|implements($kind)|extends($kind)].", + "type": "string", + "pattern": "^((expression)|(interface)|(implements\\([a-zA-Z][a-zA-Z0-9.]*\\))|(extends\\([a-zA-Z][a-zA-Z0-9.]*\\)))$" + }, + "stringExpression": { + "$role": "expression", + "title": "String or expression", + "description": "Interpolated string or expression to evaluate.", + "oneOf": [ + { + "type": "string", + "title": "String", + "description": "Interpolated string", + "pattern": "^(?!(=)).*", + "examples": [ + "Hello ${user.name}" + ] + }, + { + "$ref": "#/definitions/equalsExpression", + "examples": [ + "=concat('x','y','z')" + ] + } + ] + }, + "valueExpression": { + "$role": "expression", + "title": "Any or expression", + "description": "Any constant or expression to evaluate.", + "oneOf": [ + { + "type": "object", + "title": "Object", + "description": "Object constant." + }, + { + "type": "array", + "title": "Array", + "description": "Array constant." + }, + { + "type": "string", + "title": "String", + "description": "Interpolated string.", + "pattern": "^(?!(=)).*", + "examples": [ + "Hello ${user.name}" + ] + }, + { + "type": "boolean", + "title": "Boolean", + "description": "Boolean constant", + "examples": [ + false + ] + }, + { + "type": "number", + "title": "Number", + "description": "Number constant.", + "examples": [ + 15.5 + ] + }, + { + "$ref": "#/definitions/equalsExpression", + "examples": [ + "=..." + ] + } + ] + }, + "schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Core schema meta-schema", + "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/schema" + } + }, + "nonNegativeInteger": { + "type": "integer", + "minimum": 0 + }, + "nonNegativeIntegerDefault0": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "simpleTypes": { + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] + }, + "stringArray": { + "type": "array", + "uniqueItems": true, + "default": [], + "items": { + "type": "string" + } + } + }, + "type": [ + "object", + "boolean" + ], + "default": true, + "properties": { + "$schema": { + "type": "string", + "format": "uri" + }, + "$ref": { + "type": "string", + "format": "uri-reference" + }, + "$comment": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": true, + "readOnly": { + "type": "boolean", + "default": false + }, + "writeOnly": { + "type": "boolean", + "default": false + }, + "examples": { + "type": "array", + "items": true + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "maxLength": { + "$ref": "#/definitions/schema/definitions/nonNegativeInteger" + }, + "minLength": { + "$ref": "#/definitions/schema/definitions/nonNegativeIntegerDefault0" + }, + "pattern": { + "type": "string", + "format": "regex" + }, + "additionalItems": { + "$ref": "#/definitions/schema" + }, + "items": { + "anyOf": [ + { + "$ref": "#/definitions/schema" + }, + { + "$ref": "#/definitions/schema/definitions/schemaArray" + } + ], + "default": true + }, + "maxItems": { + "$ref": "#/definitions/schema/definitions/nonNegativeInteger" + }, + "minItems": { + "$ref": "#/definitions/schema/definitions/nonNegativeIntegerDefault0" + }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "contains": { + "$ref": "#/definitions/schema" + }, + "maxProperties": { + "$ref": "#/definitions/schema/definitions/nonNegativeInteger" + }, + "minProperties": { + "$ref": "#/definitions/schema/definitions/nonNegativeIntegerDefault0" + }, + "required": { + "$ref": "#/definitions/schema/definitions/stringArray" + }, + "additionalProperties": { + "$ref": "#/definitions/schema" + }, + "definitions": { + "type": "object", + "default": {}, + "additionalProperties": { + "$ref": "#/definitions/schema" + } + }, + "properties": { + "type": "object", + "default": {}, + "additionalProperties": { + "$ref": "#/definitions/schema" + } + }, + "patternProperties": { + "type": "object", + "propertyNames": { + "format": "regex" + }, + "default": {}, + "additionalProperties": { + "$ref": "#/definitions/schema" + } + }, + "dependencies": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "$ref": "#/definitions/schema" + }, + { + "$ref": "#/definitions/schema/definitions/stringArray" + } + ] + } + }, + "propertyNames": { + "$ref": "#/definitions/schema" + }, + "const": true, + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": true + }, + "type": { + "anyOf": [ + { + "$ref": "#/definitions/schema/definitions/simpleTypes" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/schema/definitions/simpleTypes" + }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "format": { + "type": "string" + }, + "contentMediaType": { + "type": "string" + }, + "contentEncoding": { + "type": "string" + }, + "if": { + "$ref": "#/definitions/schema" + }, + "then": { + "$ref": "#/definitions/schema" + }, + "else": { + "$ref": "#/definitions/schema" + }, + "allOf": { + "$ref": "#/definitions/schema/definitions/schemaArray" + }, + "anyOf": { + "$ref": "#/definitions/schema/definitions/schemaArray" + }, + "oneOf": { + "$ref": "#/definitions/schema/definitions/schemaArray" + }, + "not": { + "$ref": "#/definitions/schema" + } + } + }, + "botframework.json": { + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "ChannelAccount": { + "description": "Channel account information needed to route a message", + "title": "ChannelAccount", + "type": "object", + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "description": "Channel id for the user or bot on this channel (Example: joe@smith.com, or @joesmith or\n123456)", + "type": "string", + "title": "id" + }, + "name": { + "description": "Display friendly name", + "type": "string", + "title": "name" + }, + "aadObjectId": { + "description": "This account's object ID within Azure Active Directory (AAD)", + "type": "string", + "title": "aadObjectId" + }, + "role": { + "description": "Role of the entity behind the account (Example: User, Bot, etc.). Possible values include:\n'user', 'bot'", + "type": "string", + "title": "role" + } + } + }, + "ConversationAccount": { + "description": "Channel account information for a conversation", + "title": "ConversationAccount", + "type": "object", + "required": [ + "conversationType", + "id", + "isGroup", + "name" + ], + "properties": { + "isGroup": { + "description": "Indicates whether the conversation contains more than two participants at the time the\nactivity was generated", + "type": "boolean", + "title": "isGroup" + }, + "conversationType": { + "description": "Indicates the type of the conversation in channels that distinguish between conversation types", + "type": "string", + "title": "conversationType" + }, + "id": { + "description": "Channel id for the user or bot on this channel (Example: joe@smith.com, or @joesmith or\n123456)", + "type": "string", + "title": "id" + }, + "name": { + "description": "Display friendly name", + "type": "string", + "title": "name" + }, + "aadObjectId": { + "description": "This account's object ID within Azure Active Directory (AAD)", + "type": "string", + "title": "aadObjectId" + }, + "role": { + "description": "Role of the entity behind the account (Example: User, Bot, etc.). Possible values include:\n'user', 'bot'", + "enum": [ + "bot", + "user" + ], + "type": "string", + "title": "role" + } + } + }, + "MessageReaction": { + "description": "Message reaction object", + "title": "MessageReaction", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "description": "Message reaction type. Possible values include: 'like', 'plusOne'", + "type": "string", + "title": "type" + } + } + }, + "CardAction": { + "description": "A clickable action", + "title": "CardAction", + "type": "object", + "required": [ + "title", + "type", + "value" + ], + "properties": { + "type": { + "description": "The type of action implemented by this button. Possible values include: 'openUrl', 'imBack',\n'postBack', 'playAudio', 'playVideo', 'showImage', 'downloadFile', 'signin', 'call',\n'payment', 'messageBack'", + "type": "string", + "title": "type" + }, + "title": { + "description": "Text description which appears on the button", + "type": "string", + "title": "title" + }, + "image": { + "description": "Image URL which will appear on the button, next to text label", + "type": "string", + "title": "image" + }, + "text": { + "description": "Text for this action", + "type": "string", + "title": "text" + }, + "displayText": { + "description": "(Optional) text to display in the chat feed if the button is clicked", + "type": "string", + "title": "displayText" + }, + "value": { + "description": "Supplementary parameter for action. Content of this property depends on the ActionType", + "title": "value" + }, + "channelData": { + "description": "Channel-specific data associated with this action", + "title": "channelData" + } + } + }, + "SuggestedActions": { + "description": "SuggestedActions that can be performed", + "title": "SuggestedActions", + "type": "object", + "required": [ + "actions", + "to" + ], + "properties": { + "to": { + "description": "Ids of the recipients that the actions should be shown to. These Ids are relative to the\nchannelId and a subset of all recipients of the activity", + "type": "array", + "title": "to", + "items": { + "title": "Id", + "description": "Id of recipient.", + "type": "string" + } + }, + "actions": { + "description": "Actions that can be shown to the user", + "type": "array", + "title": "actions", + "items": { + "$ref": "#/definitions/botframework.json/definitions/CardAction" + } + } + } + }, + "Attachment": { + "description": "An attachment within an activity", + "title": "Attachment", + "type": "object", + "required": [ + "contentType" + ], + "properties": { + "contentType": { + "description": "mimetype/Contenttype for the file", + "type": "string", + "title": "contentType" + }, + "contentUrl": { + "description": "Content Url", + "type": "string", + "title": "contentUrl" + }, + "content": { + "type": "object", + "description": "Embedded content", + "title": "content" + }, + "name": { + "description": "(OPTIONAL) The name of the attachment", + "type": "string", + "title": "name" + }, + "thumbnailUrl": { + "description": "(OPTIONAL) Thumbnail associated with attachment", + "type": "string", + "title": "thumbnailUrl" + } + } + }, + "Entity": { + "description": "Metadata object pertaining to an activity", + "title": "Entity", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "description": "Type of this entity (RFC 3987 IRI)", + "type": "string", + "title": "type" + } + } + }, + "ConversationReference": { + "description": "An object relating to a particular point in a conversation", + "title": "ConversationReference", + "type": "object", + "required": [ + "bot", + "channelId", + "conversation", + "serviceUrl" + ], + "properties": { + "activityId": { + "description": "(Optional) ID of the activity to refer to", + "type": "string", + "title": "activityId" + }, + "user": { + "description": "(Optional) User participating in this conversation", + "$ref": "#/definitions/botframework.json/definitions/ChannelAccount", + "title": "user" + }, + "bot": { + "$ref": "#/definitions/botframework.json/definitions/ChannelAccount", + "description": "Bot participating in this conversation", + "title": "bot" + }, + "conversation": { + "$ref": "#/definitions/botframework.json/definitions/ConversationAccount", + "description": "Conversation reference", + "title": "conversation" + }, + "channelId": { + "description": "Channel ID", + "type": "string", + "title": "channelId" + }, + "serviceUrl": { + "description": "Service endpoint where operations concerning the referenced conversation may be performed", + "type": "string", + "title": "serviceUrl" + } + } + }, + "TextHighlight": { + "description": "Refers to a substring of content within another field", + "title": "TextHighlight", + "type": "object", + "required": [ + "occurrence", + "text" + ], + "properties": { + "text": { + "description": "Defines the snippet of text to highlight", + "type": "string", + "title": "text" + }, + "occurrence": { + "description": "Occurrence of the text field within the referenced text, if multiple exist.", + "type": "number", + "title": "occurrence" + } + } + }, + "SemanticAction": { + "description": "Represents a reference to a programmatic action", + "title": "SemanticAction", + "type": "object", + "required": [ + "entities", + "id" + ], + "properties": { + "id": { + "description": "ID of this action", + "type": "string", + "title": "id" + }, + "entities": { + "description": "Entities associated with this action", + "type": "object", + "title": "entities", + "additionalProperties": { + "$ref": "#/definitions/botframework.json/definitions/Entity" + } + } + } + }, + "Activity": { + "description": "An Activity is the basic communication type for the Bot Framework 3.0 protocol.", + "title": "Activity", + "type": "object", + "properties": { + "type": { + "description": "Contains the activity type. Possible values include: 'message', 'contactRelationUpdate',\n'conversationUpdate', 'typing', 'endOfConversation', 'event', 'invoke', 'deleteUserData',\n'messageUpdate', 'messageDelete', 'installationUpdate', 'messageReaction', 'suggestion',\n'trace', 'handoff'", + "type": "string", + "title": "type" + }, + "id": { + "description": "Contains an ID that uniquely identifies the activity on the channel.", + "type": "string", + "title": "id" + }, + "timestamp": { + "description": "Contains the date and time that the message was sent, in UTC, expressed in ISO-8601 format.", + "type": "string", + "format": "date-time", + "title": "timestamp" + }, + "localTimestamp": { + "description": "Contains the date and time that the message was sent, in local time, expressed in ISO-8601\nformat.\nFor example, 2016-09-23T13:07:49.4714686-07:00.", + "type": "string", + "format": "date-time", + "title": "localTimestamp" + }, + "localTimezone": { + "description": "Contains the name of the timezone in which the message, in local time, expressed in IANA Time\nZone database format.\nFor example, America/Los_Angeles.", + "type": "string", + "title": "localTimezone" + }, + "serviceUrl": { + "description": "Contains the URL that specifies the channel's service endpoint. Set by the channel.", + "type": "string", + "title": "serviceUrl" + }, + "channelId": { + "description": "Contains an ID that uniquely identifies the channel. Set by the channel.", + "type": "string", + "title": "channelId" + }, + "from": { + "$ref": "#/definitions/botframework.json/definitions/ChannelAccount", + "description": "Identifies the sender of the message.", + "title": "from" + }, + "conversation": { + "$ref": "#/definitions/botframework.json/definitions/ConversationAccount", + "description": "Identifies the conversation to which the activity belongs.", + "title": "conversation" + }, + "recipient": { + "$ref": "#/definitions/botframework.json/definitions/ChannelAccount", + "description": "Identifies the recipient of the message.", + "title": "recipient" + }, + "textFormat": { + "description": "Format of text fields Default:markdown. Possible values include: 'markdown', 'plain', 'xml'", + "type": "string", + "title": "textFormat" + }, + "attachmentLayout": { + "description": "The layout hint for multiple attachments. Default: list. Possible values include: 'list',\n'carousel'", + "type": "string", + "title": "attachmentLayout" + }, + "membersAdded": { + "description": "The collection of members added to the conversation.", + "type": "array", + "title": "membersAdded", + "items": { + "$ref": "#/definitions/botframework.json/definitions/ChannelAccount" + } + }, + "membersRemoved": { + "description": "The collection of members removed from the conversation.", + "type": "array", + "title": "membersRemoved", + "items": { + "$ref": "#/definitions/botframework.json/definitions/ChannelAccount" + } + }, + "reactionsAdded": { + "description": "The collection of reactions added to the conversation.", + "type": "array", + "title": "reactionsAdded", + "items": { + "$ref": "#/definitions/botframework.json/definitions/MessageReaction" + } + }, + "reactionsRemoved": { + "description": "The collection of reactions removed from the conversation.", + "type": "array", + "title": "reactionsRemoved", + "items": { + "$ref": "#/definitions/botframework.json/definitions/MessageReaction" + } + }, + "topicName": { + "description": "The updated topic name of the conversation.", + "type": "string", + "title": "topicName" + }, + "historyDisclosed": { + "description": "Indicates whether the prior history of the channel is disclosed.", + "type": "boolean", + "title": "historyDisclosed" + }, + "locale": { + "description": "A locale name for the contents of the text field.\nThe locale name is a combination of an ISO 639 two- or three-letter culture code associated\nwith a language\nand an ISO 3166 two-letter subculture code associated with a country or region.\nThe locale name can also correspond to a valid BCP-47 language tag.", + "type": "string", + "title": "locale" + }, + "text": { + "description": "The text content of the message.", + "type": "string", + "title": "text" + }, + "speak": { + "description": "The text to speak.", + "type": "string", + "title": "speak" + }, + "inputHint": { + "description": "Indicates whether your bot is accepting,\nexpecting, or ignoring user input after the message is delivered to the client. Possible\nvalues include: 'acceptingInput', 'ignoringInput', 'expectingInput'", + "type": "string", + "title": "inputHint" + }, + "summary": { + "description": "The text to display if the channel cannot render cards.", + "type": "string", + "title": "summary" + }, + "suggestedActions": { + "description": "The suggested actions for the activity.", + "$ref": "#/definitions/botframework.json/definitions/SuggestedActions", + "title": "suggestedActions" + }, + "attachments": { + "description": "Attachments", + "type": "array", + "title": "attachments", + "items": { + "$ref": "#/definitions/botframework.json/definitions/Attachment" + } + }, + "entities": { + "description": "Represents the entities that were mentioned in the message.", + "type": "array", + "title": "entities", + "items": { + "$ref": "#/definitions/botframework.json/definitions/Entity" + } + }, + "channelData": { + "description": "Contains channel-specific content.", + "title": "channelData" + }, + "action": { + "description": "Indicates whether the recipient of a contactRelationUpdate was added or removed from the\nsender's contact list.", + "type": "string", + "title": "action" + }, + "replyToId": { + "description": "Contains the ID of the message to which this message is a reply.", + "type": "string", + "title": "replyToId" + }, + "label": { + "description": "A descriptive label for the activity.", + "type": "string", + "title": "label" + }, + "valueType": { + "description": "The type of the activity's value object.", + "type": "string", + "title": "valueType" + }, + "value": { + "description": "A value that is associated with the activity.", + "title": "value" + }, + "name": { + "description": "The name of the operation associated with an invoke or event activity.", + "type": "string", + "title": "name" + }, + "relatesTo": { + "description": "A reference to another conversation or activity.", + "$ref": "#/definitions/botframework.json/definitions/ConversationReference", + "title": "relatesTo" + }, + "code": { + "description": "The a code for endOfConversation activities that indicates why the conversation ended.\nPossible values include: 'unknown', 'completedSuccessfully', 'userCancelled', 'botTimedOut',\n'botIssuedInvalidMessage', 'channelFailed'", + "type": "string", + "title": "code" + }, + "expiration": { + "description": "The time at which the activity should be considered to be \"expired\" and should not be\npresented to the recipient.", + "type": "string", + "format": "date-time", + "title": "expiration" + }, + "importance": { + "description": "The importance of the activity. Possible values include: 'low', 'normal', 'high'", + "type": "string", + "title": "importance" + }, + "deliveryMode": { + "description": "A delivery hint to signal to the recipient alternate delivery paths for the activity.\nThe default delivery mode is \"default\". Possible values include: 'normal', 'notification'", + "type": "string", + "title": "deliveryMode" + }, + "listenFor": { + "description": "List of phrases and references that speech and language priming systems should listen for", + "type": "array", + "title": "listenFor", + "items": { + "type": "string", + "title": "Phrase", + "description": "Phrase to listen for." + } + }, + "textHighlights": { + "description": "The collection of text fragments to highlight when the activity contains a ReplyToId value.", + "type": "array", + "title": "textHighlights", + "items": { + "$ref": "#/definitions/botframework.json/definitions/TextHighlight" + } + }, + "semanticAction": { + "$ref": "#/definitions/botframework.json/definitions/SemanticAction", + "description": "An optional programmatic action accompanying this request", + "title": "semanticAction" + } + } + } + } + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/schemas/sdk.uischema b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/schemas/sdk.uischema new file mode 100644 index 0000000000..9cf19c6919 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/schemas/sdk.uischema @@ -0,0 +1,1409 @@ +{ + "$schema": "https://schemas.botframework.com/schemas/ui/v1.0/ui.schema", + "Microsoft.AdaptiveDialog": { + "form": { + "description": "This configures a data driven dialog via a collection of events and actions.", + "helpLink": "https://aka.ms/bf-composer-docs-dialog", + "hidden": [ + "triggers", + "generator", + "selector", + "schema" + ], + "label": "Adaptive dialog", + "order": [ + "recognizer", + "*" + ], + "properties": { + "recognizer": { + "description": "To understand what the user says, your dialog needs a \"Recognizer\"; that includes example words and sentences that users may use.", + "label": "Language Understanding" + } + } + } + }, + "Microsoft.Ask": { + "flow": { + "body": { + "field": "activity", + "widget": "LgWidget" + }, + "footer": { + "description": "= Default operation", + "property": "=action.defaultOperation", + "widget": "PropertyDescription" + }, + "header": { + "colors": { + "icon": "#5C2E91", + "theme": "#EEEAF4" + }, + "icon": "MessageBot", + "widget": "ActionHeader" + }, + "hideFooter": "=!action.defaultOperation", + "widget": "ActionCard" + }, + "form": { + "helpLink": "https://aka.ms/bfc-send-activity", + "label": "Send a response to ask a question", + "order": [ + "activity", + "*" + ], + "subtitle": "Ask Activity" + } + }, + "Microsoft.AttachmentInput": { + "flow": { + "body": "=action.prompt", + "botAsks": { + "body": { + "defaultContent": "", + "field": "prompt", + "widget": "LgWidget" + }, + "header": { + "colors": { + "icon": "#5C2E91", + "theme": "#EEEAF4" + }, + "icon": "MessageBot", + "widget": "ActionHeader" + }, + "widget": "ActionCard" + }, + "nowrap": true, + "userInput": { + "header": { + "colors": { + "icon": "#0078D4", + "theme": "#E5F0FF" + }, + "disableSDKTitle": true, + "icon": "User", + "menu": "none", + "widget": "ActionHeader" + }, + "widget": "ActionCard" + }, + "widget": "PromptWidget" + }, + "form": { + "helpLink": "https://aka.ms/bfc-ask-for-user-input", + "label": "Prompt for a file or an attachment", + "properties": { + "property": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "Attachment Input" + } + }, + "Microsoft.BeginDialog": { + "flow": { + "body": { + "dialog": "=action.dialog", + "widget": "DialogRef" + }, + "footer": { + "description": "= Return value", + "property": "=action.resultProperty", + "widget": "PropertyDescription" + }, + "hideFooter": "=!action.resultProperty", + "widget": "ActionCard" + }, + "form": { + "helpLink": "https://aka.ms/bfc-understanding-dialogs", + "label": "Begin a new dialog", + "order": [ + "dialog", + "options", + "resultProperty", + "*" + ], + "properties": { + "resultProperty": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "Begin Dialog" + } + }, + "Microsoft.BeginSkill": { + "flow": { + "body": { + "operation": "Host", + "resource": "=coalesce(action.skillEndpoint, \"?\")", + "singleline": true, + "widget": "ResourceOperation" + }, + "colors": { + "color": "#FFFFFF", + "icon": "#FFFFFF", + "theme": "#004578" + }, + "footer": { + "description": "= Result", + "property": "=action.resultProperty", + "widget": "PropertyDescription" + }, + "hideFooter": "=!action.resultProperty", + "icon": "Library", + "widget": "ActionCard" + }, + "form": { + "helpLink": "https://aka.ms/bf-composer-docs-connect-skill", + "label": "Connect to a skill", + "properties": { + "resultProperty": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "Skill Dialog" + } + }, + "Microsoft.BreakLoop": { + "form": { + "label": "Break out of loop", + "subtitle": "Break out of loop" + } + }, + "Microsoft.CancelAllDialogs": { + "flow": { + "body": { + "description": "(Event)", + "property": "=coalesce(action.eventName, \"?\")", + "widget": "PropertyDescription" + }, + "widget": "ActionCard" + }, + "form": { + "helpLink": "https://aka.ms/bfc-understanding-dialogs", + "label": "Cancel all active dialogs", + "subtitle": "Cancel All Dialogs" + } + }, + "Microsoft.ChoiceInput": { + "flow": { + "body": "=action.prompt", + "botAsks": { + "body": { + "defaultContent": "", + "field": "prompt", + "widget": "LgWidget" + }, + "header": { + "colors": { + "icon": "#5C2E91", + "theme": "#EEEAF4" + }, + "icon": "MessageBot", + "widget": "ActionHeader" + }, + "widget": "ActionCard" + }, + "nowrap": true, + "userInput": { + "header": { + "colors": { + "icon": "#0078D4", + "theme": "#E5F0FF" + }, + "disableSDKTitle": true, + "icon": "User", + "menu": "none", + "widget": "ActionHeader" + }, + "widget": "ActionCard" + }, + "widget": "PromptWidget" + }, + "form": { + "helpLink": "https://aka.ms/bfc-ask-for-user-input", + "label": "Prompt with multi-choice", + "properties": { + "property": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "Choice Input" + } + }, + "Microsoft.ConfirmInput": { + "flow": { + "body": "=action.prompt", + "botAsks": { + "body": { + "defaultContent": "", + "field": "prompt", + "widget": "LgWidget" + }, + "header": { + "colors": { + "icon": "#5C2E91", + "theme": "#EEEAF4" + }, + "icon": "MessageBot", + "widget": "ActionHeader" + }, + "widget": "ActionCard" + }, + "nowrap": true, + "userInput": { + "header": { + "colors": { + "icon": "#0078D4", + "theme": "#E5F0FF" + }, + "disableSDKTitle": true, + "icon": "User", + "menu": "none", + "widget": "ActionHeader" + }, + "widget": "ActionCard" + }, + "widget": "PromptWidget" + }, + "form": { + "helpLink": "https://aka.ms/bfc-ask-for-user-input", + "label": "Prompt for confirmation", + "properties": { + "property": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "Confirm Input" + } + }, + "Microsoft.ContinueLoop": { + "form": { + "label": "Continue loop", + "subtitle": "Continue loop" + } + }, + "Microsoft.DateTimeInput": { + "flow": { + "body": "=action.prompt", + "botAsks": { + "body": { + "defaultContent": "", + "field": "prompt", + "widget": "LgWidget" + }, + "header": { + "colors": { + "icon": "#5C2E91", + "theme": "#EEEAF4" + }, + "icon": "MessageBot", + "widget": "ActionHeader" + }, + "widget": "ActionCard" + }, + "nowrap": true, + "userInput": { + "header": { + "colors": { + "icon": "#0078D4", + "theme": "#E5F0FF" + }, + "disableSDKTitle": true, + "icon": "User", + "menu": "none", + "widget": "ActionHeader" + }, + "widget": "ActionCard" + }, + "widget": "PromptWidget" + }, + "form": { + "helpLink": "https://aka.ms/bfc-ask-for-user-input", + "label": "Prompt for a date or a time", + "properties": { + "property": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "Date Time Input" + } + }, + "Microsoft.DebugBreak": { + "form": { + "label": "Debug Break" + } + }, + "Microsoft.DeleteProperties": { + "flow": { + "body": { + "items": "=action.properties", + "widget": "ListOverview" + }, + "widget": "ActionCard" + }, + "form": { + "helpLink": "https://aka.ms/bfc-using-memory", + "label": "Delete properties", + "properties": { + "properties": { + "intellisenseScopes": [ + "user-variables" + ] + } + }, + "subtitle": "Delete Properties" + } + }, + "Microsoft.DeleteProperty": { + "flow": { + "body": "=action.property", + "widget": "ActionCard" + }, + "form": { + "helpLink": "https://aka.ms/bfc-using-memory", + "label": "Delete a property", + "properties": { + "property": { + "intellisenseScopes": [ + "user-variables" + ] + } + }, + "subtitle": "Delete Property" + } + }, + "Microsoft.EditActions": { + "flow": { + "body": "=action.changeType", + "widget": "ActionCard" + }, + "form": { + "label": "Modify active dialog", + "subtitle": "Edit Actions" + } + }, + "Microsoft.EditArray": { + "flow": { + "body": { + "operation": "=coalesce(action.changeType, \"?\")", + "resource": "=coalesce(action.itemsProperty, \"?\")", + "widget": "ResourceOperation" + }, + "footer": { + "description": "= Result", + "property": "=action.resultProperty", + "widget": "PropertyDescription" + }, + "hideFooter": "=!action.resultProperty", + "widget": "ActionCard" + }, + "form": { + "helpLink": "https://aka.ms/bfc-using-memory", + "label": "Edit an array property", + "properties": { + "itemsProperty": { + "intellisenseScopes": [ + "user-variables" + ] + }, + "resultProperty": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "Edit Array" + } + }, + "Microsoft.EmitEvent": { + "flow": { + "body": { + "description": "(Event)", + "property": "=coalesce(action.eventName, \"?\")", + "widget": "PropertyDescription" + }, + "widget": "ActionCard" + }, + "form": { + "helpLink": "https://aka.ms/bfc-custom-events", + "label": "Emit a custom event", + "subtitle": "Emit Event" + } + }, + "Microsoft.EndDialog": { + "form": { + "helpLink": "https://aka.ms/bfc-understanding-dialogs", + "label": "End this dialog", + "subtitle": "End Dialog" + } + }, + "Microsoft.EndTurn": { + "form": { + "helpLink": "https://aka.ms/bfc-understanding-dialogs", + "label": "End turn", + "subtitle": "End Turn" + } + }, + "Microsoft.Foreach": { + "flow": { + "loop": { + "body": "=concat(\"Each value in \", coalesce(action.itemsProperty, \"?\"))", + "widget": "ActionCard" + }, + "nowrap": true, + "widget": "ForeachWidget" + }, + "form": { + "helpLink": "https://aka.ms/bfc-controlling-conversation-flow", + "hidden": [ + "actions" + ], + "label": "Loop: For each item", + "order": [ + "itemsProperty", + "*" + ], + "properties": { + "index": { + "intellisenseScopes": [ + "variable-scopes" + ] + }, + "itemsProperty": { + "intellisenseScopes": [ + "user-variables" + ] + }, + "value": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "For Each" + } + }, + "Microsoft.ForeachPage": { + "flow": { + "loop": { + "body": "=concat(\"Each page of \", coalesce(action.pageSize, \"?\"), \" in \", coalesce(action.page, \"?\"))", + "widget": "ActionCard" + }, + "nowrap": true, + "widget": "ForeachWidget" + }, + "form": { + "helpLink": "https://aka.ms/bfc-controlling-conversation-flow", + "hidden": [ + "actions" + ], + "label": "Loop: For each page (multiple items)", + "order": [ + "itemsProperty", + "pageSize", + "*" + ], + "properties": { + "itemsProperty": { + "intellisenseScopes": [ + "user-variables" + ] + }, + "page": { + "intellisenseScopes": [ + "variable-scopes" + ] + }, + "pageIndex": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "For Each Page" + } + }, + "Microsoft.GetActivityMembers": { + "flow": { + "body": { + "description": "= ActivityId", + "property": "=coalesce(action.activityId, \"?\")", + "widget": "PropertyDescription" + }, + "footer": { + "description": "= Result property", + "property": "=coalesce(action.property, \"?\")", + "widget": "PropertyDescription" + }, + "widget": "ActionCard" + } + }, + "Microsoft.GetConversationMembers": { + "flow": { + "footer": { + "description": "= Result property", + "property": "=action.property", + "widget": "PropertyDescription" + }, + "widget": "ActionCard" + } + }, + "Microsoft.HttpRequest": { + "flow": { + "body": { + "operation": "=action.method", + "resource": "=action.url", + "singleline": true, + "widget": "ResourceOperation" + }, + "footer": { + "description": "= Result property", + "property": "=action.resultProperty", + "widget": "PropertyDescription" + }, + "hideFooter": "=!action.resultProperty", + "widget": "ActionCard" + }, + "form": { + "helpLink": "https://aka.ms/bfc-using-http", + "label": "Send an HTTP request", + "order": [ + "method", + "url", + "body", + "headers", + "*" + ], + "properties": { + "resultProperty": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "HTTP Request" + } + }, + "Microsoft.IfCondition": { + "flow": { + "judgement": { + "body": "=coalesce(action.condition, \"\")", + "widget": "ActionCard" + }, + "nowrap": true, + "widget": "IfConditionWidget" + }, + "form": { + "helpLink": "https://aka.ms/bfc-controlling-conversation-flow", + "hidden": [ + "actions", + "elseActions" + ], + "label": "Branch: If/Else", + "subtitle": "If Condition" + } + }, + "Microsoft.LogAction": { + "form": { + "helpLink": "https://aka.ms/composer-telemetry", + "label": "Log to console", + "subtitle": "Log Action" + } + }, + "Microsoft.NumberInput": { + "flow": { + "body": "=action.prompt", + "botAsks": { + "body": { + "defaultContent": "", + "field": "prompt", + "widget": "LgWidget" + }, + "header": { + "colors": { + "icon": "#5C2E91", + "theme": "#EEEAF4" + }, + "icon": "MessageBot", + "widget": "ActionHeader" + }, + "widget": "ActionCard" + }, + "nowrap": true, + "userInput": { + "header": { + "colors": { + "icon": "#0078D4", + "theme": "#E5F0FF" + }, + "disableSDKTitle": true, + "icon": "User", + "menu": "none", + "widget": "ActionHeader" + }, + "widget": "ActionCard" + }, + "widget": "PromptWidget" + }, + "form": { + "helpLink": "https://aka.ms/bfc-ask-for-user-input", + "label": "Prompt for a number", + "properties": { + "property": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "Number Input" + } + }, + "Microsoft.OAuthInput": { + "flow": { + "body": { + "operation": "Connection", + "resource": "=coalesce(action.connectionName, \"?\")", + "singleline": true, + "widget": "ResourceOperation" + }, + "footer": { + "description": "= Token property", + "property": "=action.property", + "widget": "PropertyDescription" + }, + "hideFooter": "=!action.property", + "widget": "ActionCard" + }, + "form": { + "helpLink": "https://aka.ms/bfc-using-oauth", + "label": "OAuth login", + "order": [ + "connectionName", + "*" + ], + "subtitle": "OAuth Input" + } + }, + "Microsoft.OnActivity": { + "form": { + "hidden": [ + "actions" + ], + "label": "Activities", + "order": [ + "condition", + "*" + ], + "subtitle": "Activity received" + }, + "trigger": { + "label": "Activities (Activity received)", + "order": 5.1, + "submenu": { + "label": "Activities", + "placeholder": "Select an activity type", + "prompt": "Which activity type?" + } + } + }, + "Microsoft.OnAssignEntity": { + "form": { + "hidden": [ + "actions" + ], + "label": "Handle a condition when an entity is assigned", + "order": [ + "condition", + "*" + ], + "subtitle": "EntityAssigned activity" + } + }, + "Microsoft.OnBeginDialog": { + "form": { + "hidden": [ + "actions" + ], + "label": "Dialog started", + "order": [ + "condition", + "*" + ], + "subtitle": "Begin dialog event" + }, + "trigger": { + "label": "Dialog started (Begin dialog event)", + "order": 4.1, + "submenu": { + "label": "Dialog events", + "placeholder": "Select an event type", + "prompt": "Which event?" + } + } + }, + "Microsoft.OnCancelDialog": { + "form": { + "hidden": [ + "actions" + ], + "label": "Dialog cancelled", + "order": [ + "condition", + "*" + ], + "subtitle": "Cancel dialog event" + }, + "trigger": { + "label": "Dialog cancelled (Cancel dialog event)", + "order": 4.2, + "submenu": "Dialog events" + } + }, + "Microsoft.OnChooseEntity": { + "form": { + "hidden": [ + "actions" + ], + "order": [ + "condition", + "*" + ] + } + }, + "Microsoft.OnChooseIntent": { + "form": { + "hidden": [ + "actions" + ], + "order": [ + "condition", + "*" + ] + }, + "trigger": { + "label": "Duplicated intents recognized", + "order": 6 + } + }, + "Microsoft.OnCommandActivity": { + "form": { + "hidden": [ + "actions" + ], + "label": "Command received", + "order": [ + "condition", + "*" + ], + "subtitle": "Command activity received" + }, + "trigger": { + "label": "Command received (Command activity received)", + "order": 5.81, + "submenu": "Activities" + } + }, + "Microsoft.OnCommandResultActivity": { + "form": { + "hidden": [ + "actions" + ], + "label": "Command Result received", + "order": [ + "condition", + "*" + ], + "subtitle": "Command Result activity received" + }, + "trigger": { + "label": "Command Result received (Command Result activity received)", + "order": 5.81, + "submenu": "Activities" + } + }, + "Microsoft.OnCondition": { + "form": { + "hidden": [ + "actions" + ], + "label": "Handle a condition", + "order": [ + "condition", + "*" + ], + "subtitle": "Condition" + } + }, + "Microsoft.OnConversationUpdateActivity": { + "form": { + "description": "Handle the events fired when a user begins a new conversation with the bot.", + "helpLink": "https://aka.ms/bf-composer-docs-conversation-update-activity", + "hidden": [ + "actions" + ], + "label": "Greeting", + "order": [ + "condition", + "*" + ], + "subtitle": "ConversationUpdate activity" + }, + "trigger": { + "label": "Greeting (ConversationUpdate activity)", + "order": 5.2, + "submenu": "Activities" + } + }, + "Microsoft.OnDialogEvent": { + "form": { + "hidden": [ + "actions" + ], + "label": "Dialog events", + "order": [ + "condition", + "*" + ], + "subtitle": "Dialog event" + }, + "trigger": { + "label": "Custom events", + "order": 7 + } + }, + "Microsoft.OnEndOfActions": { + "form": { + "hidden": [ + "actions" + ], + "label": "Handle a condition when actions have ended", + "order": [ + "condition", + "*" + ], + "subtitle": "EndOfActions activity" + } + }, + "Microsoft.OnEndOfConversationActivity": { + "form": { + "hidden": [ + "actions" + ], + "label": "Conversation ended", + "order": [ + "condition", + "*" + ], + "subtitle": "EndOfConversation activity" + }, + "trigger": { + "label": "Conversation ended (EndOfConversation activity)", + "order": 5.3, + "submenu": "Activities" + } + }, + "Microsoft.OnError": { + "form": { + "hidden": [ + "actions" + ], + "label": "Error occurred", + "order": [ + "condition", + "*" + ], + "subtitle": "Error event" + }, + "trigger": { + "label": "Error occurred (Error event)", + "order": 4.3, + "submenu": "Dialog events" + } + }, + "Microsoft.OnEventActivity": { + "form": { + "hidden": [ + "actions" + ], + "label": "Event received", + "order": [ + "condition", + "*" + ], + "subtitle": "Event activity" + }, + "trigger": { + "label": "Event received (Event activity)", + "order": 5.4, + "submenu": "Activities" + } + }, + "Microsoft.OnHandoffActivity": { + "form": { + "hidden": [ + "actions" + ], + "label": "Handover to human", + "order": [ + "condition", + "*" + ], + "subtitle": "Handoff activity" + }, + "trigger": { + "label": "Handover to human (Handoff activity)", + "order": 5.5, + "submenu": "Activities" + } + }, + "Microsoft.OnInstallationUpdateActivity": { + "form": { + "hidden": [ + "actions" + ], + "label": "Installation updated", + "order": [ + "condition", + "*" + ], + "subtitle": "Installation updated activity" + } + }, + "Microsoft.OnIntent": { + "form": { + "hidden": [ + "actions" + ], + "label": "Intent recognized", + "order": [ + "intent", + "condition", + "entities", + "*" + ], + "subtitle": "Intent recognized" + }, + "trigger": { + "label": "Intent recognized", + "order": 1 + } + }, + "Microsoft.OnInvokeActivity": { + "form": { + "hidden": [ + "actions" + ], + "label": "Conversation invoked", + "order": [ + "condition", + "*" + ], + "subtitle": "Invoke activity" + }, + "trigger": { + "label": "Conversation invoked (Invoke activity)", + "order": 5.6, + "submenu": "Activities" + } + }, + "Microsoft.OnMessageActivity": { + "form": { + "hidden": [ + "actions" + ], + "label": "Message received", + "order": [ + "condition", + "*" + ], + "subtitle": "Message activity received" + }, + "trigger": { + "label": "Message received (Message activity received)", + "order": 5.81, + "submenu": "Activities" + } + }, + "Microsoft.OnMessageDeleteActivity": { + "form": { + "hidden": [ + "actions" + ], + "label": "Message deleted", + "order": [ + "condition", + "*" + ], + "subtitle": "Message deleted activity" + }, + "trigger": { + "label": "Message deleted (Message deleted activity)", + "order": 5.82, + "submenu": "Activities" + } + }, + "Microsoft.OnMessageReactionActivity": { + "form": { + "hidden": [ + "actions" + ], + "label": "Message reaction", + "order": [ + "condition", + "*" + ], + "subtitle": "Message reaction activity" + }, + "trigger": { + "label": "Message reaction (Message reaction activity)", + "order": 5.83, + "submenu": "Activities" + } + }, + "Microsoft.OnMessageUpdateActivity": { + "form": { + "hidden": [ + "actions" + ], + "label": "Message updated", + "order": [ + "condition", + "*" + ], + "subtitle": "Message updated activity" + }, + "trigger": { + "label": "Message updated (Message updated activity)", + "order": 5.84, + "submenu": "Activities" + } + }, + "Microsoft.OnQnAMatch": { + "trigger": { + "label": "QnA Intent recognized", + "order": 2 + } + }, + "Microsoft.OnRepromptDialog": { + "form": { + "hidden": [ + "actions" + ], + "label": "Re-prompt for input", + "order": [ + "condition", + "*" + ], + "subtitle": "Reprompt dialog event" + }, + "trigger": { + "label": "Re-prompt for input (Reprompt dialog event)", + "order": 4.4, + "submenu": "Dialog events" + } + }, + "Microsoft.OnTypingActivity": { + "form": { + "hidden": [ + "actions" + ], + "label": "User is typing", + "order": [ + "condition", + "*" + ], + "subtitle": "Typing activity" + }, + "trigger": { + "label": "User is typing (Typing activity)", + "order": 5.7, + "submenu": "Activities" + } + }, + "Microsoft.OnUnknownIntent": { + "form": { + "hidden": [ + "actions" + ], + "label": "Unknown intent", + "order": [ + "condition", + "*" + ], + "subtitle": "Unknown intent recognized" + }, + "trigger": { + "label": "Unknown intent", + "order": 3 + } + }, + "Microsoft.QnAMakerDialog": { + "flow": { + "body": "=action.hostname", + "widget": "ActionCard" + } + }, + "Microsoft.RegexRecognizer": { + "form": { + "hidden": [ + "entities" + ] + } + }, + "Microsoft.RepeatDialog": { + "form": { + "helpLink": "https://aka.ms/bfc-understanding-dialogs", + "label": "Repeat this dialog", + "order": [ + "options", + "*" + ], + "subtitle": "Repeat Dialog" + } + }, + "Microsoft.ReplaceDialog": { + "flow": { + "body": { + "dialog": "=action.dialog", + "widget": "DialogRef" + }, + "widget": "ActionCard" + }, + "form": { + "helpLink": "https://aka.ms/bfc-understanding-dialogs", + "label": "Replace this dialog", + "order": [ + "dialog", + "options", + "*" + ], + "subtitle": "Replace Dialog" + } + }, + "Microsoft.SendActivity": { + "flow": { + "body": { + "field": "activity", + "widget": "LgWidget" + }, + "header": { + "colors": { + "icon": "#5C2E91", + "theme": "#EEEAF4" + }, + "icon": "MessageBot", + "widget": "ActionHeader" + }, + "widget": "ActionCard" + }, + "form": { + "helpLink": "https://aka.ms/bfc-send-activity", + "label": "Send a response", + "order": [ + "activity", + "*" + ], + "subtitle": "Send Activity" + } + }, + "Microsoft.SendHandoffActivity": { + "flow": { + "widget": "ActionHeader" + }, + "form": { + "helpLink": "https://aka.ms/bfc-send-handoff-activity", + "label": "Send a handoff request", + "subtitle": "Send Handoff Activity" + }, + "menu": { + "label": "Send Handoff Event", + "submenu": [ + "Access external resources" + ] + } + }, + "Microsoft.SetProperties": { + "flow": { + "body": { + "items": "=foreach(action.assignments, x => concat(coalesce(x.property, \"?\"), \" : \", coalesce(x.value, \"?\")))", + "widget": "ListOverview" + }, + "widget": "ActionCard" + }, + "form": { + "helpLink": "https://aka.ms/bfc-using-memory", + "label": "Set properties", + "properties": { + "assignments": { + "properties": { + "property": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + } + } + }, + "subtitle": "Set Properties" + } + }, + "Microsoft.SetProperty": { + "flow": { + "body": "${coalesce(action.property, \"?\")} : ${coalesce(action.value, \"?\")}", + "widget": "ActionCard" + }, + "form": { + "helpLink": "https://aka.ms/bfc-using-memory", + "label": "Set a property", + "properties": { + "property": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "Set Property" + } + }, + "Microsoft.SignOutUser": { + "form": { + "label": "Sign out user", + "subtitle": "Signout User" + } + }, + "Microsoft.SwitchCondition": { + "flow": { + "judgement": { + "body": "=coalesce(action.condition, \"\")", + "widget": "ActionCard" + }, + "nowrap": true, + "widget": "SwitchConditionWidget" + }, + "form": { + "helpLink": "https://aka.ms/bfc-controlling-conversation-flow", + "hidden": [ + "default" + ], + "label": "Branch: Switch (multiple options)", + "properties": { + "cases": { + "hidden": [ + "actions" + ] + }, + "condition": { + "intellisenseScopes": [ + "user-variables" + ] + } + }, + "subtitle": "Switch Condition" + } + }, + "Microsoft.TextInput": { + "flow": { + "body": "=action.prompt", + "botAsks": { + "body": { + "defaultContent": "", + "field": "prompt", + "widget": "LgWidget" + }, + "header": { + "colors": { + "icon": "#5C2E91", + "theme": "#EEEAF4" + }, + "icon": "MessageBot", + "widget": "ActionHeader" + }, + "widget": "ActionCard" + }, + "nowrap": true, + "userInput": { + "header": { + "colors": { + "icon": "#0078D4", + "theme": "#E5F0FF" + }, + "disableSDKTitle": true, + "icon": "User", + "menu": "none", + "widget": "ActionHeader" + }, + "widget": "ActionCard" + }, + "widget": "PromptWidget" + }, + "form": { + "helpLink": "https://aka.ms/bfc-ask-for-user-input", + "label": "Prompt for text", + "properties": { + "property": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "Text Input" + } + }, + "Microsoft.ThrowException": { + "flow": { + "body": { + "description": "= ErrorValue", + "property": "=coalesce(action.errorValue, \"?\")", + "widget": "PropertyDescription" + }, + "widget": "ActionCard" + }, + "form": { + "label": "Throw an exception", + "subtitle": "Throw an exception" + } + }, + "Microsoft.TraceActivity": { + "form": { + "helpLink": "https://aka.ms/composer-telemetry", + "label": "Emit a trace event", + "subtitle": "Trace Activity" + } + }, + "Microsoft.UpdateActivity": { + "flow": { + "body": { + "field": "activity", + "widget": "LgWidget" + }, + "header": { + "colors": { + "icon": "#656565", + "theme": "#D7D7D7" + }, + "icon": "MessageBot", + "title": "Update activity", + "widget": "ActionHeader" + }, + "widget": "ActionCard" + }, + "form": { + "label": "Update an activity", + "subtitle": "Update Activity" + } + } +} diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/schemas/update-schema.ps1 b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/schemas/update-schema.ps1 new file mode 100644 index 0000000000..67715586e4 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/schemas/update-schema.ps1 @@ -0,0 +1,27 @@ +$SCHEMA_FILE="sdk.schema" +$UISCHEMA_FILE="sdk.uischema" +$BACKUP_SCHEMA_FILE="sdk-backup.schema" +$BACKUP_UISCHEMA_FILE="sdk-backup.uischema" + +Write-Host "Running schema merge." + +if (Test-Path $SCHEMA_FILE -PathType leaf) { Move-Item -Force -Path $SCHEMA_FILE -Destination $BACKUP_SCHEMA_FILE } +if (Test-Path $UISCHEMA_FILE -PathType leaf) { Move-Item -Force -Path $UISCHEMA_FILE -Destination $BACKUP_UISCHEMA_FILE } + +bf dialog:merge "*.schema" "!**/sdk-backup.schema" "*.uischema" "!**/sdk-backup.uischema" "!**/sdk.override.uischema" "!**/generated" "../*.csproj" "../package.json" -o $SCHEMA_FILE + +if (Test-Path $SCHEMA_FILE -PathType leaf) +{ + if (Test-Path $BACKUP_SCHEMA_FILE -PathType leaf) { Remove-Item -Force -Path $BACKUP_SCHEMA_FILE } + if (Test-Path $BACKUP_UISCHEMA_FILE -PathType leaf) { Remove-Item -Force -Path $BACKUP_UISCHEMA_FILE } + + Write-Host "Schema merged succesfully." + if (Test-Path $SCHEMA_FILE -PathType leaf) { Write-Host " Schema: $SCHEMA_FILE" } + if (Test-Path $UISCHEMA_FILE -PathType leaf) { Write-Host " UI Schema: $UISCHEMA_FILE" } +} +else +{ + Write-Host "Schema merge failed. Restoring previous versions." + if (Test-Path $BACKUP_SCHEMA_FILE -PathType leaf) { Move-Item -Force -Path $BACKUP_SCHEMA_FILE -Destination $SCHEMA_FILE } + if (Test-Path $BACKUP_UISCHEMA_FILE -PathType leaf) { Move-Item -Force -Path $BACKUP_UISCHEMA_FILE -Destination $UISCHEMA_FILE } +} diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/schemas/update-schema.sh b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/schemas/update-schema.sh new file mode 100644 index 0000000000..50beec9c4c --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/schemas/update-schema.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +SCHEMA_FILE=sdk.schema +UISCHEMA_FILE=sdk.uischema +BACKUP_SCHEMA_FILE=sdk-backup.schema +BACKUP_UISCHEMA_FILE=sdk-backup.uischema + +while [ $# -gt 0 ]; do + if [[ $1 == *"-"* ]]; then + param="${1/-/}" + declare $param="$2" + fi + shift +done + +echo "Running schema merge." +[ -f "$SCHEMA_FILE" ] && mv "./$SCHEMA_FILE" "./$BACKUP_SCHEMA_FILE" +[ -f "$UISCHEMA_FILE" ] && mv "./$UISCHEMA_FILE" "./$BACKUP_UISCHEMA_FILE" + +bf dialog:merge "*.schema" "!**/sdk-backup.schema" "*.uischema" "!**/sdk-backup.uischema" "!**/sdk.override.uischema" "!**/generated" "../*.csproj" "../package.json" -o $SCHEMA_FILE + +if [ -f "$SCHEMA_FILE" ]; then + rm -rf "./$BACKUP_SCHEMA_FILE" + rm -rf "./$BACKUP_UISCHEMA_FILE" + echo "Schema merged succesfully." + [ -f "$SCHEMA_FILE" ] && echo " Schema: $SCHEMA_FILE" + [ -f "$UISCHEMA_FILE" ] && echo " UI Schema: $UISCHEMA_FILE" +else + echo "Schema merge failed. Restoring previous versions." + [ -f "$BACKUP_SCHEMA_FILE" ] && mv "./$BACKUP_SCHEMA_FILE" "./$SCHEMA_FILE" + [ -f "$BACKUP_UISCHEMA_FILE" ] && mv "./$BACKUP_UISCHEMA_FILE" "./$UISCHEMA_FILE" +fi diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/settings/appsettings.json b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/settings/appsettings.json new file mode 100644 index 0000000000..5743912bbc --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/settings/appsettings.json @@ -0,0 +1,109 @@ +{ + "customFunctions": [], + "defaultLanguage": "en-us", + "defaultLocale": "en-us", + "importedLibraries": [], + "languages": [ + "en-us" + ], + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "luFeatures": { + "enableCompositeEntities": true, + "enableListEntities": true, + "enableMLEntities": true, + "enablePattern": true, + "enablePhraseLists": true, + "enablePrebuiltEntities": true, + "enableRegexEntities": true + }, + "luis": { + "authoringEndpoint": "", + "authoringRegion": "", + "defaultLanguage": "en-us", + "endpoint": "", + "environment": "composer", + "name": "SimpleHostBotComposer" + }, + "MicrosoftAppId": "", + "MicrosoftAppPassword": "", + "publishTargets": [], + "qna": { + "hostname": "", + "knowledgebaseid": "", + "qnaRegion": "westus" + }, + "runtime": { + "command": "dotnet run --project SimpleHostBotComposer.csproj", + "customRuntime": true, + "key": "adaptive-runtime-dotnet-webapp", + "path": "../" + }, + "runtimeSettings": { + "adapters": [], + "features": { + "removeRecipientMentions": false, + "showTyping": false, + "traceTranscript": false, + "useInspection": false, + "setSpeak": { + "voiceFontName": "en-US-AriaNeural", + "fallbackToTextForSpeechIfEmpty": true + } + }, + "components": [], + "skills": { + "allowedCallers": [ + "*" + ] + }, + "storage": "", + "telemetry": { + "logActivities": true, + "logPersonalInformation": false, + "options": { + "connectionString": "" + } + } + }, + "downsampling": { + "maxImbalanceRatio": -1 + }, + "skillConfiguration": {}, + "skillHostEndpoint": "http://localhost:35010/api/skills", + "skill": { + "echoSkillBotComposerDotNet": { + "endpointUrl": "http://localhost:35410/api/messages", + "msAppId": "00000000-0000-0000-0000-000000000000" + }, + "echoSkillBotDotNet": { + "endpointUrl": "http://localhost:35400/api/messages", + "msAppId": "00000000-0000-0000-0000-000000000000" + }, + "echoSkillBotDotNet21": { + "endpointUrl": "http://localhost:35405/api/messages", + "msAppId": "00000000-0000-0000-0000-000000000000" + }, + "echoSkillBotDotNetV3": { + "endpointUrl": "http://localhost:35407/api/messages", + "msAppId": "00000000-0000-0000-0000-000000000000" + }, + "echoSkillBotJs": { + "endpointUrl": "http://localhost:36400/api/messages", + "msAppId": "00000000-0000-0000-0000-000000000000" + }, + "echoSkillBotJsv3": { + "endpointUrl": "http://localhost:36407/api/messages", + "msAppId": "00000000-0000-0000-0000-000000000000" + }, + "echoSkillBotPython": { + "endpointUrl": "http://localhost:37400/api/messages", + "msAppId": "00000000-0000-0000-0000-000000000000" + } + } +} \ No newline at end of file diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/simplehostbotcomposer.dialog b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/simplehostbotcomposer.dialog new file mode 100644 index 0000000000..d72a4dcbd3 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/simplehostbotcomposer.dialog @@ -0,0 +1,104 @@ +{ + "$kind": "Microsoft.AdaptiveDialog", + "$designer": { + "name": "SimpleHostBotComposer", + "description": "", + "id": "A79tBe" + }, + "autoEndDialog": true, + "defaultResultProperty": "dialog.result", + "triggers": [ + { + "$kind": "Microsoft.OnConversationUpdateActivity", + "$designer": { + "id": "376720" + }, + "actions": [ + { + "$kind": "Microsoft.Foreach", + "$designer": { + "id": "518944", + "name": "Loop: for each item" + }, + "itemsProperty": "turn.Activity.membersAdded", + "actions": [ + { + "$kind": "Microsoft.IfCondition", + "$designer": { + "id": "641773", + "name": "Branch: if/else" + }, + "condition": "string(dialog.foreach.value.id) != string(turn.Activity.Recipient.id)", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "$designer": { + "id": "859266", + "name": "Send a response" + }, + "activity": "${SendActivity_Greeting()}" + }, + { + "$kind": "Microsoft.BeginDialog", + "$designer": { + "id": "fTvoh5" + }, + "activityProcessed": true, + "dialog": "CallEchoSkill" + } + ] + } + ] + } + ] + }, + { + "$kind": "Microsoft.OnUnknownIntent", + "$designer": { + "id": "mb2n1u" + }, + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "$designer": { + "id": "kMjqz1" + }, + "activity": "${SendActivity_DidNotUnderstand()}" + } + ] + }, + { + "$kind": "Microsoft.OnEndOfConversationActivity", + "$designer": { + "id": "xPU1pB" + }, + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "$designer": { + "id": "M2LqJr" + }, + "activity": "${SendActivity_M2LqJr()}" + }, + { + "$kind": "Microsoft.SendActivity", + "$designer": { + "id": "bgTcyn" + }, + "activity": "${SendActivity_bgTcyn()}" + }, + { + "$kind": "Microsoft.BeginDialog", + "$designer": { + "id": "s9BrZr" + }, + "activityProcessed": true, + "dialog": "CallEchoSkill" + } + ] + } + ], + "generator": "SimpleHostBotComposer.lg", + "id": "SimpleHostBotComposer", + "recognizer": "SimpleHostBotComposer.lu.qna" +} diff --git a/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/wwwroot/default.htm b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/wwwroot/default.htm new file mode 100644 index 0000000000..8ddd3ef885 --- /dev/null +++ b/tests/functional/Bots/DotNet/Consumers/Composer/SimpleHostBotComposer/wwwroot/default.htm @@ -0,0 +1,364 @@ + + + + + + + SimpleHostBotComposer + + + + + +
+
+
+
SimpleHostBotComposer
+
+
+
+
+
Your bot is ready!
+
You can test your bot in the Bot Framework Emulator
+ by connecting to http://localhost:3978/api/messages.
+ +
Visit Azure + Bot Service to register your bot and add it to
+ various channels. The bot's endpoint URL typically looks + like this:
+
https://your_bots_hostname/api/messages
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/tests/functional/Bots/DotNet/FunctionalTestsBots.sln b/tests/functional/Bots/DotNet/FunctionalTestsBots.sln new file mode 100644 index 0000000000..ea5591067b --- /dev/null +++ b/tests/functional/Bots/DotNet/FunctionalTestsBots.sln @@ -0,0 +1,106 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30621.155 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Consumers", "Consumers", "{12088D79-235C-4387-9DA8-69AB93D52A8E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Skills", "Skills", "{39117C87-FD02-40CD-A9A9-9A4C0A630521}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CodeFirst", "CodeFirst", "{C7A60083-78C8-4CEF-A358-4E53CBF0D12A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleHostBot", "Consumers\CodeFirst\SimpleHostBot\SimpleHostBot.csproj", "{AAE978F8-D22B-41E8-B445-872FF4194713}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleHostBot-2.1", "Consumers\CodeFirst\SimpleHostBot-2.1\SimpleHostBot-2.1.csproj", "{0443C38C-A0DF-4A1A-8931-4A4BFFEEB386}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CodeFirst", "CodeFirst", "{6EF38D8C-7953-4D54-BA31-FFF055B7B217}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EchoSkillBot-2.1", "Skills\CodeFirst\EchoSkillBot-2.1\EchoSkillBot-2.1.csproj", "{56864219-785B-4600-9849-43CAF90F37E9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EchoSkillBot", "Skills\CodeFirst\EchoSkillBot\EchoSkillBot.csproj", "{692F26DD-F7BA-49F3-AC6D-73047C1E5D61}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Composer", "Composer", "{33FA275E-9E5D-4582-BFF7-24B2C5DB2962}" + ProjectSection(SolutionItems) = preProject + Consumers\Composer\Directory.Build.props = Consumers\Composer\Directory.Build.props + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Composer", "Composer", "{33E2281F-C6A7-40EC-961A-FF9FF254FDDC}" + ProjectSection(SolutionItems) = preProject + Skills\Composer\Directory.Build.props = Skills\Composer\Directory.Build.props + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WaterfallHostBot", "Consumers\CodeFirst\WaterfallHostBot\WaterfallHostBot.csproj", "{15A946BE-39F9-4945-9895-0019ED3392FC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WaterfallSkillBot", "Skills\CodeFirst\WaterfallSkillBot\WaterfallSkillBot.csproj", "{E3BECBEF-E41F-48D1-9EEB-A4D7E1CD34E3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EchoSkillBot-v3", "Skills\CodeFirst\EchoSkillBot-v3\EchoSkillBot-v3.csproj", "{41BC9547-FD9E-40E1-B1D9-C3F25BC9A2F3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleHostBotComposer", "Consumers\Composer\SimpleHostBotComposer\SimpleHostBotComposer.csproj", "{FDC53B3A-0E15-4FDF-A587-05C8F90BC2B6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EchoSkillBotComposer", "Skills\Composer\EchoSkillBotComposer\EchoSkillBotComposer.csproj", "{9BA47CF9-7D90-4B5C-A9FA-01797A435D53}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AAE978F8-D22B-41E8-B445-872FF4194713}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AAE978F8-D22B-41E8-B445-872FF4194713}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AAE978F8-D22B-41E8-B445-872FF4194713}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AAE978F8-D22B-41E8-B445-872FF4194713}.Release|Any CPU.Build.0 = Release|Any CPU + {0443C38C-A0DF-4A1A-8931-4A4BFFEEB386}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0443C38C-A0DF-4A1A-8931-4A4BFFEEB386}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0443C38C-A0DF-4A1A-8931-4A4BFFEEB386}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0443C38C-A0DF-4A1A-8931-4A4BFFEEB386}.Release|Any CPU.Build.0 = Release|Any CPU + {56864219-785B-4600-9849-43CAF90F37E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {56864219-785B-4600-9849-43CAF90F37E9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {56864219-785B-4600-9849-43CAF90F37E9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {56864219-785B-4600-9849-43CAF90F37E9}.Release|Any CPU.Build.0 = Release|Any CPU + {692F26DD-F7BA-49F3-AC6D-73047C1E5D61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {692F26DD-F7BA-49F3-AC6D-73047C1E5D61}.Debug|Any CPU.Build.0 = Debug|Any CPU + {692F26DD-F7BA-49F3-AC6D-73047C1E5D61}.Release|Any CPU.ActiveCfg = Release|Any CPU + {692F26DD-F7BA-49F3-AC6D-73047C1E5D61}.Release|Any CPU.Build.0 = Release|Any CPU + {15A946BE-39F9-4945-9895-0019ED3392FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {15A946BE-39F9-4945-9895-0019ED3392FC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {15A946BE-39F9-4945-9895-0019ED3392FC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {15A946BE-39F9-4945-9895-0019ED3392FC}.Release|Any CPU.Build.0 = Release|Any CPU + {E3BECBEF-E41F-48D1-9EEB-A4D7E1CD34E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E3BECBEF-E41F-48D1-9EEB-A4D7E1CD34E3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E3BECBEF-E41F-48D1-9EEB-A4D7E1CD34E3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E3BECBEF-E41F-48D1-9EEB-A4D7E1CD34E3}.Release|Any CPU.Build.0 = Release|Any CPU + {41BC9547-FD9E-40E1-B1D9-C3F25BC9A2F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {41BC9547-FD9E-40E1-B1D9-C3F25BC9A2F3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {41BC9547-FD9E-40E1-B1D9-C3F25BC9A2F3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {41BC9547-FD9E-40E1-B1D9-C3F25BC9A2F3}.Release|Any CPU.Build.0 = Release|Any CPU + {FDC53B3A-0E15-4FDF-A587-05C8F90BC2B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FDC53B3A-0E15-4FDF-A587-05C8F90BC2B6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FDC53B3A-0E15-4FDF-A587-05C8F90BC2B6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FDC53B3A-0E15-4FDF-A587-05C8F90BC2B6}.Release|Any CPU.Build.0 = Release|Any CPU + {9BA47CF9-7D90-4B5C-A9FA-01797A435D53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9BA47CF9-7D90-4B5C-A9FA-01797A435D53}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9BA47CF9-7D90-4B5C-A9FA-01797A435D53}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9BA47CF9-7D90-4B5C-A9FA-01797A435D53}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {C7A60083-78C8-4CEF-A358-4E53CBF0D12A} = {12088D79-235C-4387-9DA8-69AB93D52A8E} + {AAE978F8-D22B-41E8-B445-872FF4194713} = {C7A60083-78C8-4CEF-A358-4E53CBF0D12A} + {0443C38C-A0DF-4A1A-8931-4A4BFFEEB386} = {C7A60083-78C8-4CEF-A358-4E53CBF0D12A} + {6EF38D8C-7953-4D54-BA31-FFF055B7B217} = {39117C87-FD02-40CD-A9A9-9A4C0A630521} + {56864219-785B-4600-9849-43CAF90F37E9} = {6EF38D8C-7953-4D54-BA31-FFF055B7B217} + {692F26DD-F7BA-49F3-AC6D-73047C1E5D61} = {6EF38D8C-7953-4D54-BA31-FFF055B7B217} + {33FA275E-9E5D-4582-BFF7-24B2C5DB2962} = {12088D79-235C-4387-9DA8-69AB93D52A8E} + {33E2281F-C6A7-40EC-961A-FF9FF254FDDC} = {39117C87-FD02-40CD-A9A9-9A4C0A630521} + {15A946BE-39F9-4945-9895-0019ED3392FC} = {C7A60083-78C8-4CEF-A358-4E53CBF0D12A} + {E3BECBEF-E41F-48D1-9EEB-A4D7E1CD34E3} = {6EF38D8C-7953-4D54-BA31-FFF055B7B217} + {41BC9547-FD9E-40E1-B1D9-C3F25BC9A2F3} = {6EF38D8C-7953-4D54-BA31-FFF055B7B217} + {FDC53B3A-0E15-4FDF-A587-05C8F90BC2B6} = {33FA275E-9E5D-4582-BFF7-24B2C5DB2962} + {9BA47CF9-7D90-4B5C-A9FA-01797A435D53} = {33E2281F-C6A7-40EC-961A-FF9FF254FDDC} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {2B77A921-EEA6-4006-ABD2-159C92F9F874} + EndGlobalSection +EndGlobal diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-2.1/Authentication/AllowedCallersClaimsValidator.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-2.1/Authentication/AllowedCallersClaimsValidator.cs new file mode 100644 index 0000000000..5b0b053639 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-2.1/Authentication/AllowedCallersClaimsValidator.cs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.Bot.Connector.Authentication; +using Microsoft.Extensions.Configuration; + +namespace Microsoft.BotFrameworkFunctionalTests.EchoSkillBot21.Authentication +{ + /// + /// Sample claims validator that loads an allowed list from configuration if present + /// and checks that requests are coming from allowed parent bots. + /// + public class AllowedCallersClaimsValidator : ClaimsValidator + { + private const string ConfigKey = "AllowedCallers"; + private readonly List _allowedCallers; + + /// + /// Initializes a new instance of the class. + /// Loads the appIds for the configured callers. Only allows access to callers it has configured. + /// + /// The list of configured callers. + public AllowedCallersClaimsValidator(IConfiguration config) + { + if (config == null) + { + throw new ArgumentNullException(nameof(config)); + } + + // AllowedCallers is the setting in the appsettings.json file + // that consists of the list of parent bot IDs that are allowed to access the skill. + // To add a new parent bot, simply edit the AllowedCallers and add + // the parent bot's Microsoft app ID to the list. + // In this sample, we allow all callers if AllowedCallers contains an "*". + var section = config.GetSection(ConfigKey); + var appsList = section.Get(); + if (appsList == null) + { + throw new ArgumentNullException($"\"{ConfigKey}\" not found in configuration."); + } + + _allowedCallers = new List(appsList); + } + + /// + /// Checks that the appId claim in the skill request is in the list of callers configured for this bot. + /// + /// The list of claims to validate. + /// A task that represents the work queued to execute. + public override Task ValidateClaimsAsync(IList claims) + { + // If _allowedCallers contains an "*", we allow all callers. + if (SkillValidation.IsSkillClaim(claims) && !_allowedCallers.Contains("*")) + { + // Check that the appId claim in the skill request is in the list of callers configured for this bot. + var appId = JwtTokenValidation.GetAppIdFromClaims(claims); + if (!_allowedCallers.Contains(appId)) + { + throw new UnauthorizedAccessException($"Received a request from a bot with an app ID of \"{appId}\". To enable requests from this caller, add the app ID to your configuration file."); + } + } + + return Task.CompletedTask; + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-2.1/Bots/EchoBot.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-2.1/Bots/EchoBot.cs new file mode 100644 index 0000000000..14cccdbc7a --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-2.1/Bots/EchoBot.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Schema; + +namespace Microsoft.BotFrameworkFunctionalTests.EchoSkillBot21.Bots +{ + public class EchoBot : ActivityHandler + { + /// + /// Processes a message activity. + /// + /// Context for the current turn of conversation. + /// CancellationToken propagates notifications that operations should be cancelled. + /// A representing the result of the asynchronous operation. + protected override async Task OnMessageActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + if (turnContext.Activity.Text.Contains("end") || turnContext.Activity.Text.Contains("stop")) + { + // Send End of conversation at the end. + await turnContext.SendActivityAsync(MessageFactory.Text($"Ending conversation from the skill..."), cancellationToken); + var endOfConversation = Activity.CreateEndOfConversationActivity(); + endOfConversation.Code = EndOfConversationCodes.CompletedSuccessfully; + await turnContext.SendActivityAsync(endOfConversation, cancellationToken); + } + else + { + await turnContext.SendActivityAsync(MessageFactory.Text($"Echo: {turnContext.Activity.Text}"), cancellationToken); + await turnContext.SendActivityAsync(MessageFactory.Text("Say \"end\" or \"stop\" and I'll end the conversation and back to the parent."), cancellationToken); + } + } + + /// + /// Processes an end of conversation activity. + /// + /// Context for the current turn of conversation. + /// CancellationToken propagates notifications that operations should be cancelled. + /// A representing the result of the asynchronous operation. + protected override Task OnEndOfConversationActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + // This will be called if the host bot is ending the conversation. Sending additional messages should be + // avoided as the conversation may have been deleted. + // Perform cleanup of resources if needed. + return Task.CompletedTask; + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-2.1/Controllers/BotController.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-2.1/Controllers/BotController.cs new file mode 100644 index 0000000000..c2fe9a198a --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-2.1/Controllers/BotController.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; + +namespace Microsoft.BotFrameworkFunctionalTests.EchoSkillBot21.Controllers +{ + /// + /// This ASP Controller is created to handle a request. Dependency Injection will provide the Adapter and IBot implementation at runtime. + /// Multiple different IBot implementations running at different endpoints can be achieved by specifying a more specific type for the bot constructor argument. + /// + [Route("api/messages")] + [ApiController] + public class BotController : ControllerBase + { + private readonly IBotFrameworkHttpAdapter _adapter; + private readonly IBot _bot; + + /// + /// Initializes a new instance of the class. + /// + /// Adapter for the BotController. + /// Bot for the BotController. + public BotController(IBotFrameworkHttpAdapter adapter, IBot bot) + { + _adapter = adapter; + _bot = bot; + } + + /// + /// Processes an HttpPost request. + /// + /// A representing the result of the asynchronous operation. + [HttpPost] + public async Task PostAsync() + { + await _adapter.ProcessAsync(Request, Response, _bot); + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-2.1/EchoSkillBot-2.1.csproj b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-2.1/EchoSkillBot-2.1.csproj new file mode 100644 index 0000000000..2df857a613 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-2.1/EchoSkillBot-2.1.csproj @@ -0,0 +1,25 @@ + + + + netcoreapp2.1 + latest + Microsoft.BotFrameworkFunctionalTests.EchoSkillBot21 + Microsoft.BotFrameworkFunctionalTests.EchoSkillBot21 + + + + DEBUG;TRACE + + + + + + + + + + Always + + + + \ No newline at end of file diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-2.1/Program.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-2.1/Program.cs new file mode 100644 index 0000000000..bdfda7669a --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-2.1/Program.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; + +namespace Microsoft.BotFrameworkFunctionalTests.EchoSkillBot21 +{ + public class Program + { + /// + /// The entry point of the application. + /// + /// The command line args. + public static void Main(string[] args) + { + CreateWebHostBuilder(args).Build().Run(); + } + + /// + /// Creates a new instance of the class with pre-configured defaults. + /// + /// The command line args. + /// The initialized . + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup(); + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-2.1/Properties/launchSettings.json b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-2.1/Properties/launchSettings.json new file mode 100644 index 0000000000..791f7e6a3b --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-2.1/Properties/launchSettings.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:35405/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": false, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "EchoSkillBotDotNet21": { + "commandName": "Project", + "launchBrowser": true, + "applicationUrl": "http://localhost:35405/", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-2.1/SkillAdapterWithErrorHandler.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-2.1/SkillAdapterWithErrorHandler.cs new file mode 100644 index 0000000000..76ef40f872 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-2.1/SkillAdapterWithErrorHandler.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Bot.Builder.TraceExtensions; +using Microsoft.Bot.Connector.Authentication; +using Microsoft.Bot.Schema; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace Microsoft.BotFrameworkFunctionalTests.EchoSkillBot21 +{ + public class SkillAdapterWithErrorHandler : BotFrameworkHttpAdapter + { + /// + /// Initializes a new instance of the class to handle errors. + /// + /// The configuration properties. + /// An implementation of the bots credentials. + /// The configuration setting for the authentication. + /// An instance of a logger. + /// A state management object for the conversation. + public SkillAdapterWithErrorHandler(IConfiguration configuration, ICredentialProvider credentialProvider, AuthenticationConfiguration authConfig, ILogger logger) + : base(configuration, credentialProvider, authConfig, logger: logger) + { + OnTurnError = async (turnContext, exception) => + { + try + { + // Log any leaked exception from the application. + logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}"); + + // Send a message to the user + var errorMessageText = "The skill encountered an error or bug."; + var errorMessage = MessageFactory.Text(errorMessageText + Environment.NewLine + exception, errorMessageText, InputHints.IgnoringInput); + errorMessage.Value = exception; + await turnContext.SendActivityAsync(errorMessage); + + errorMessageText = "To continue to run this bot, please fix the bot source code."; + errorMessage = MessageFactory.Text(errorMessageText, errorMessageText, InputHints.ExpectingInput); + await turnContext.SendActivityAsync(errorMessage); + + // Send a trace activity, which will be displayed in the Bot Framework Emulator + // Note: we return the entire exception in the value property to help the developer, this should not be done in prod. + await turnContext.TraceActivityAsync("OnTurnError Trace", exception.ToString(), "https://www.botframework.com/schemas/error", "TurnError"); + + // Send and EndOfConversation activity to the skill caller with the error to end the conversation + // and let the caller decide what to do. + var endOfConversation = Activity.CreateEndOfConversationActivity(); + endOfConversation.Code = "SkillError"; + endOfConversation.Text = exception.Message; + await turnContext.SendActivityAsync(endOfConversation); + } + catch (Exception ex) + { + logger.LogError(ex, $"Exception caught in SkillAdapterWithErrorHandler : {ex}"); + } + }; + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-2.1/Startup.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-2.1/Startup.cs new file mode 100644 index 0000000000..1c0bacc9bd --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-2.1/Startup.cs @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.BotFramework; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Bot.Connector.Authentication; +using Microsoft.BotFrameworkFunctionalTests.EchoSkillBot21.Bots; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.BotFrameworkFunctionalTests.EchoSkillBot21 +{ + public class Startup + { + public Startup(IConfiguration config) + { + Configuration = config; + } + + public IConfiguration Configuration { get; } + + /// + /// This method gets called by the runtime. Use this method to add services to the container. + /// + /// Method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); + + // Configure credentials + services.AddSingleton(); + + // Register AuthConfiguration to enable custom claim validation. + services.AddSingleton(sp => new AuthenticationConfiguration { ClaimsValidator = new Authentication.AllowedCallersClaimsValidator(sp.GetService()) }); + + // Create the Bot Framework Adapter with error handling enabled. + services.AddSingleton(); + + // Create the bot as a transient. In this case the ASP Controller is expecting an IBot. + services.AddTransient(); + + if (!string.IsNullOrEmpty(Configuration["ChannelService"])) + { + // Register a ConfigurationChannelProvider -- this is only for Azure Gov. + services.AddSingleton(); + } + } + + /// + /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + /// + /// The application request pipeline to be configured. + /// The web hosting environment. + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + app.UseHsts(); + } + + app.UseDefaultFiles(); + app.UseStaticFiles(); + + // app.UseHttpsRedirection(); + app.UseMvc(); + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-2.1/appsettings.json b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-2.1/appsettings.json new file mode 100644 index 0000000000..c61cdb4d79 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-2.1/appsettings.json @@ -0,0 +1,11 @@ +{ + "MicrosoftAppId": "", + "MicrosoftAppPassword": "", + "ChannelService": "", + // This is a comma separate list with the App IDs that will have access to the skill. + // This setting is used in AllowedCallersClaimsValidator. + // Examples: + // [ "*" ] allows all callers. + // [ "AppId1", "AppId2" ] only allows access to parent bots with "AppId1" and "AppId2". + "AllowedCallers": [ "*" ] +} \ No newline at end of file diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-2.1/wwwroot/default.htm b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-2.1/wwwroot/default.htm new file mode 100644 index 0000000000..56d1b43377 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-2.1/wwwroot/default.htm @@ -0,0 +1,420 @@ + + + + + + + EchoSkillBot-2.1DotNet + + + + + +
+
+
+
EchoSkillBot-2.1DotNet Bot
+
+
+
+
+
Your bot is ready!
+
You can test your bot in the Bot Framework Emulator
+ by connecting to http://localhost:3978/api/messages.
+ +
Visit Azure + Bot Service to register your bot and add it to
+ various channels. The bot's endpoint URL typically looks + like this:
+
https://your_bots_hostname/api/messages
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-2.1/wwwroot/manifests/echoskillbot-manifest-1.0.json b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-2.1/wwwroot/manifests/echoskillbot-manifest-1.0.json new file mode 100644 index 0000000000..68b6a165ff --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-2.1/wwwroot/manifests/echoskillbot-manifest-1.0.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://schemas.botframework.com/schemas/skills/v2.1/skill-manifest.json", + "$id": "EchoSkillBotDotNet21", + "name": "EchoSkillBotDotNet21", + "version": "1.0", + "description": "This is a skill for echoing what the user sent to the bot (using .netcore 2.1).", + "publisherName": "Microsoft", + "privacyUrl": "https://microsoft.com/privacy", + "copyright": "Copyright (c) Microsoft Corporation. All rights reserved.", + "license": "https://github.com/microsoft/BotFramework-FunctionalTests/blob/main/LICENSE", + "tags": [ + "echo" + ], + "endpoints": [ + { + "name": "default", + "protocol": "BotFrameworkV3", + "description": "Localhost endpoint for the skill (on port 35405)", + "endpointUrl": "http://localhost:35405/api/messages", + "msAppId": "00000000-0000-0000-0000-000000000000" + } + ] +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/ApiControllerActionInvokerWithErrorHandler.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/ApiControllerActionInvokerWithErrorHandler.cs new file mode 100644 index 0000000000..4ad82c1409 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/ApiControllerActionInvokerWithErrorHandler.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Connector; +using Newtonsoft.Json; +using System.IO; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Web.Http.Controllers; + +namespace Microsoft.BotFrameworkFunctionalTests.EchoSkillBotv3 +{ + /// + /// Web Api Controller to intersect HTTP operations when the Action Invoker is triggered to capture exceptions and send it to the bot as an Activity. + /// + internal class ApiControllerActionInvokerWithErrorHandler : ApiControllerActionInvoker + { + public async override Task InvokeActionAsync(HttpActionContext actionContext, CancellationToken cancellationToken) + { + var result = base.InvokeActionAsync(actionContext, cancellationToken); + + if (result.Exception != null && result.Exception.GetBaseException() != null) + { + var stream = new StreamReader(actionContext.Request.Content.ReadAsStreamAsync().Result); + stream.BaseStream.Position = 0; + var rawRequest = stream.ReadToEnd(); + var activity = JsonConvert.DeserializeObject(rawRequest); + + activity.Type = "exception"; + activity.Text = result.Exception.ToString(); + activity.Value = result.Exception; + + await Conversation.SendAsync(activity, () => new Dialogs.RootDialog()); + } + + return await result; + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/App_Start/WebApiConfig.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/App_Start/WebApiConfig.cs new file mode 100644 index 0000000000..97fc72a901 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/App_Start/WebApiConfig.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using System.Web.Http; +using System.Web.Http.Controllers; + +namespace Microsoft.BotFrameworkFunctionalTests.EchoSkillBotv3 +{ + public static class WebApiConfig + { + public static void Register(HttpConfiguration config) + { + // Json settings + config.Formatters.JsonFormatter.SerializerSettings.NullValueHandling = NullValueHandling.Ignore; + config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); + config.Formatters.JsonFormatter.SerializerSettings.Formatting = Formatting.Indented; + JsonConvert.DefaultSettings = () => new JsonSerializerSettings() + { + ContractResolver = new CamelCasePropertyNamesContractResolver(), + Formatting = Newtonsoft.Json.Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + }; + + // Web API configuration and services + + // Web API routes + config.MapHttpAttributeRoutes(); + + config.Routes.MapHttpRoute( + name: "DefaultApi", + routeTemplate: "api/{controller}/{id}", + defaults: new { id = RouteParameter.Optional } + ); + config.Services.Replace(typeof(IHttpActionInvoker), new ApiControllerActionInvokerWithErrorHandler()); + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/Authentication/CustomAllowedCallersClaimsValidator.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/Authentication/CustomAllowedCallersClaimsValidator.cs new file mode 100644 index 0000000000..7d3de967b2 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/Authentication/CustomAllowedCallersClaimsValidator.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Bot.Connector.SkillAuthentication; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; + +namespace Microsoft.BotFrameworkFunctionalTests.EchoSkillBotv3.Authentication +{ + /// + /// Sample claims validator that loads an allowed list from configuration if present + /// and checks that requests are coming from allowed parent bots. + /// + public class CustomAllowedCallersClaimsValidator : ClaimsValidator + { + private readonly IList _allowedCallers; + + public CustomAllowedCallersClaimsValidator(IList allowedCallers) + { + // AllowedCallers is the setting in web.config file + // that consists of the list of parent bot IDs that are allowed to access the skill. + // To add a new parent bot simply go to the AllowedCallers and add + // the parent bot's Microsoft app ID to the list. + + _allowedCallers = allowedCallers ?? throw new ArgumentNullException(nameof(allowedCallers)); + if (!_allowedCallers.Any()) + { + throw new ArgumentNullException(nameof(allowedCallers), "AllowedCallers must contain at least one element of '*' or valid MicrosoftAppId(s)."); + } + } + + /// + /// This method is called from JwtTokenValidation.ValidateClaimsAsync + /// + /// + public override Task ValidateClaimsAsync(IList claims) + { + if (claims == null) + { + throw new ArgumentNullException(nameof(claims)); + } + + if (!claims.Any()) + { + throw new UnauthorizedAccessException("ValidateClaimsAsync.claims parameter must contain at least one element."); + } + + if (SkillValidation.IsSkillClaim(claims)) + { + // if _allowedCallers has one item of '*', allow all parent bot calls and do not validate the appid from claims + if (_allowedCallers.Count == 1 && _allowedCallers[0] == "*") + { + return Task.CompletedTask; + } + + // Check that the appId claim in the skill request is in the list of skills configured for this bot. + var appId = JwtTokenValidation.GetAppIdFromClaims(claims).ToUpperInvariant(); + if (_allowedCallers.Contains(appId)) + { + return Task.CompletedTask; + } + + throw new UnauthorizedAccessException($"Received a request from a bot with an app ID of \"{appId}\". To enable requests from this caller, add the app ID to your configuration file."); + } + + throw new UnauthorizedAccessException($"ValidateClaimsAsync called without a Skill claim in claims."); + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/Authentication/CustomSkillAuthenticationConfiguration.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/Authentication/CustomSkillAuthenticationConfiguration.cs new file mode 100644 index 0000000000..2d01093e67 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/Authentication/CustomSkillAuthenticationConfiguration.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Bot.Connector.SkillAuthentication; +using System.Configuration; +using System.Linq; + +namespace Microsoft.BotFrameworkFunctionalTests.EchoSkillBotv3.Authentication +{ + public class CustomSkillAuthenticationConfiguration : AuthenticationConfiguration + { + private const string AllowedCallersConfigKey = "EchoBotAllowedCallers"; + public CustomSkillAuthenticationConfiguration() + { + // Could pull this list from a DB or anywhere. + var allowedCallers = ConfigurationManager.AppSettings[AllowedCallersConfigKey].Split(',').Select(s => s.Trim().ToUpperInvariant()).ToList(); + ClaimsValidator = new CustomAllowedCallersClaimsValidator(allowedCallers); + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/Controllers/MessagesController.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/Controllers/MessagesController.cs new file mode 100644 index 0000000000..55cc612cd5 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/Controllers/MessagesController.cs @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Diagnostics; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Web.Http; +using Autofac; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Builder.Dialogs.Internals; +using Microsoft.Bot.Connector; +using Microsoft.Bot.Connector.SkillAuthentication; +using Microsoft.BotFrameworkFunctionalTests.EchoSkillBotv3.Authentication; + +namespace Microsoft.BotFrameworkFunctionalTests.EchoSkillBotv3 +{ + // Specify which type provides the authentication configuration to allow for validation for skills. + [SkillBotAuthentication(AuthenticationConfigurationProviderType = typeof(CustomSkillAuthenticationConfiguration))] + public class MessagesController : ApiController + { + /// + /// POST: api/Messages + /// Receive a message from a user and reply to it + /// + public async Task Post([FromBody]Activity activity) + { + if (activity.GetActivityType() == ActivityTypes.Message) + { + await Conversation.SendAsync(activity, () => new Dialogs.RootDialog()); + } + else + { + await HandleSystemMessage(activity); + } + var response = Request.CreateResponse(HttpStatusCode.OK); + return response; + } + + private async Task HandleSystemMessage(Activity message) + { + string messageType = message.GetActivityType(); + + if (messageType == ActivityTypes.EndOfConversation) + { + Trace.TraceInformation($"EndOfConversation: {message}"); + + // This Recipient null check is required for PVA manifest validation. + // PVA will send an EOC activity with null Recipient. + if (message.Recipient != null) + { + // Clear the dialog stack if the root bot has ended the conversation. + using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, message)) + { + var botData = scope.Resolve(); + await botData.LoadAsync(default(CancellationToken)); + + var stack = scope.Resolve(); + stack.Reset(); + + await botData.FlushAsync(default(CancellationToken)); + } + } + } + else if (messageType == ActivityTypes.DeleteUserData) + { + // Implement user deletion here + // If we handle user deletion, return a real message + } + else if (messageType == ActivityTypes.ConversationUpdate) + { + // Handle conversation state changes, like members being added and removed + // Use Activity.MembersAdded and Activity.MembersRemoved and Activity.Action for info + // Not available in all channels + } + else if (messageType == ActivityTypes.ContactRelationUpdate) + { + // Handle add/remove from contact lists + // Activity.From + Activity.Action represent what happened + } + else if (messageType == ActivityTypes.Typing) + { + // Handle knowing that the user is typing + } + else if (messageType == ActivityTypes.Ping) + { + } + + return null; + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/Dialogs/RootDialog.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/Dialogs/RootDialog.cs new file mode 100644 index 0000000000..1ae1313a31 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/Dialogs/RootDialog.cs @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Threading.Tasks; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Connector; + +namespace Microsoft.BotFrameworkFunctionalTests.EchoSkillBotv3.Dialogs +{ + [Serializable] + public class RootDialog : IDialog + { + public Task StartAsync(IDialogContext context) + { + context.Wait(MessageReceivedAsync); + + return Task.CompletedTask; + } + + private async Task MessageReceivedAsync(IDialogContext context, IAwaitable result) + { + var activity = await result as Activity; + + var options = new MessageOptions + { + InputHint = InputHints.AcceptingInput + }; + + try + { + if (activity.Type == "exception") + { + await PostExceptionAsync(context, activity, activity.Value as Exception); + } + else if (activity.Text.ToLower().Contains("end") || activity.Text.ToLower().Contains("stop")) + { + // Send an `endOfconversation` activity if the user cancels the skill. + await context.SayAsync($"Ending conversation from the skill...", options: options); + var endOfConversation = activity.CreateReply(); + endOfConversation.Type = ActivityTypes.EndOfConversation; + endOfConversation.Code = EndOfConversationCodes.CompletedSuccessfully; + endOfConversation.InputHint = InputHints.AcceptingInput; + await context.PostAsync(endOfConversation); + } + else + { + await context.SayAsync($"Echo: {activity.Text}", options: options); + await context.SayAsync($"Say \"end\" or \"stop\" and I'll end the conversation and back to the parent.", options: options); + } + } + catch (Exception exception) + { + await PostExceptionAsync(context, activity, exception); + } + + context.Wait(MessageReceivedAsync); + } + + //Send exception message and trace + private static async Task PostExceptionAsync(IDialogContext context, Activity reply, Exception exception) + { + // Send a message to the user + var errorMessageText = "The skill encountered an error or bug."; + var activity = reply.CreateReply(); + activity.Text = errorMessageText + Environment.NewLine + exception; + activity.Speak = errorMessageText; + activity.InputHint = InputHints.IgnoringInput; + activity.Value = exception; + await context.PostAsync(activity); + + errorMessageText = "To continue to run this bot, please fix the bot source code."; + activity = reply.CreateReply(); + activity.Text = errorMessageText; + activity.Speak = errorMessageText; + activity.InputHint = InputHints.ExpectingInput; + await context.PostAsync(activity); + + // Send and EndOfConversation activity to the skill caller with the error to end the conversation + // and let the caller decide what to do. + activity = reply.CreateReply(); + activity.Type = ActivityTypes.EndOfConversation; + activity.Code = "SkillError"; + activity.Text = exception.Message; + await context.PostAsync(activity); + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/EchoSkillBot-v3.csproj b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/EchoSkillBot-v3.csproj new file mode 100644 index 0000000000..4f5be44f73 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/EchoSkillBot-v3.csproj @@ -0,0 +1,232 @@ + + + + + Debug + AnyCPU + + + 2.0 + {41BC9547-FD9E-40E1-B1D9-C3F25BC9A2F3} + {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + Microsoft.BotFrameworkFunctionalTests.EchoSkillBot + Microsoft.BotFrameworkFunctionalTests.EchoSkillBot + + v4.7.2 + 512 + true + 3979 + enabled + disabled + false + + + + + + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + MSB3276 + + + pdbonly + true + bin\ + TRACE + prompt + 4 + + + + $(SolutionDir)packages\Autofac.4.6.0\lib\net45\Autofac.dll + + + $(SolutionDir)packages\Chronic.Signed.0.3.2\lib\net40\Chronic.dll + + + $(SolutionDir)packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll + + + $(SolutionDir)packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll + + + $(SolutionDir)packages\Microsoft.Azure.DocumentDB.1.22.0\lib\net45\Microsoft.Azure.Documents.Client.dll + + + $(SolutionDir)packages\Microsoft.Azure.KeyVault.Core.1.0.0\lib\net40\Microsoft.Azure.KeyVault.Core.dll + + + $(SolutionDir)packages\Microsoft.Bot.Builder.3.30.0\lib\net46\Microsoft.Bot.Builder.dll + True + + + $(SolutionDir)packages\Microsoft.Bot.Builder.3.30.0\lib\net46\Microsoft.Bot.Builder.Autofac.dll + True + + + $(SolutionDir)packages\Microsoft.Bot.Builder.Azure.3.16.3.40383\lib\net46\Microsoft.Bot.Builder.Azure.dll + + + $(SolutionDir)packages\Microsoft.Bot.Builder.History.3.30.0\lib\net46\Microsoft.Bot.Builder.History.dll + True + + + $(SolutionDir)packages\Microsoft.Bot.Connector.3.30.0\lib\net46\Microsoft.Bot.Connector.dll + True + + + + ..\..\..\packages\Microsoft.Data.Edm.5.8.4\lib\net40\Microsoft.Data.Edm.dll + + + ..\..\..\packages\Microsoft.Data.OData.5.8.4\lib\net40\Microsoft.Data.OData.dll + + + ..\..\..\packages\Microsoft.Data.Services.Client.5.8.4\lib\net40\Microsoft.Data.Services.Client.dll + + + $(SolutionDir)packages\Microsoft.IdentityModel.Clients.ActiveDirectory.4.4.0\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll + + + $(SolutionDir)packages\Microsoft.IdentityModel.Logging.1.1.4\lib\net451\Microsoft.IdentityModel.Logging.dll + + + $(SolutionDir)packages\Microsoft.IdentityModel.Protocol.Extensions.1.0.4.403061554\lib\net45\Microsoft.IdentityModel.Protocol.Extensions.dll + + + $(SolutionDir)packages\Microsoft.IdentityModel.Protocols.2.1.4\lib\net451\Microsoft.IdentityModel.Protocols.dll + + + $(SolutionDir)packages\Microsoft.IdentityModel.Protocols.OpenIdConnect.2.1.4\lib\net451\Microsoft.IdentityModel.Protocols.OpenIdConnect.dll + + + $(SolutionDir)packages\Microsoft.IdentityModel.Tokens.5.1.4\lib\net451\Microsoft.IdentityModel.Tokens.dll + + + $(SolutionDir)packages\Microsoft.Rest.ClientRuntime.2.3.8\lib\net452\Microsoft.Rest.ClientRuntime.dll + + + $(SolutionDir)packages\Microsoft.WindowsAzure.ConfigurationManager.3.2.3\lib\net40\Microsoft.WindowsAzure.Configuration.dll + + + $(SolutionDir)packages\WindowsAzure.Storage.7.2.1\lib\net40\Microsoft.WindowsAzure.Storage.dll + + + $(SolutionDir)packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll + + + + + + + + + + $(SolutionDir)packages\System.IdentityModel.Tokens.Jwt.5.1.4\lib\net451\System.IdentityModel.Tokens.Jwt.dll + + + + $(SolutionDir)packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll + + + ..\..\..\packages\System.Spatial.5.8.4\lib\net40\System.Spatial.dll + + + + + + + + $(SolutionDir)packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll + + + $(SolutionDir)packages\Microsoft.AspNet.WebApi.WebHost.5.2.3\lib\net45\System.Web.Http.WebHost.dll + + + + + + + + + + Designer + + + + + + + + + + Global.asax + + + + + + + Designer + + + Web.config + + + Web.config + + + + + + + + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + true + + + true + true + + + + + + + + + True + True + 3978 + / + http://localhost:35407 + False + False + + + False + + + + + + + + + \ No newline at end of file diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/Global.asax b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/Global.asax new file mode 100644 index 0000000000..745e35c7cc --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/Global.asax @@ -0,0 +1 @@ +<%@ Application Codebehind="Global.asax.cs" Inherits="Microsoft.BotFrameworkFunctionalTests.EchoSkillBotv3.WebApiApplication" Language="C#" %> diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/Global.asax.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/Global.asax.cs new file mode 100644 index 0000000000..29c3e1e36f --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/Global.asax.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Web.Http; +using Microsoft.Bot.Builder.Azure; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Builder.Dialogs.Internals; +using Autofac; +using Microsoft.Bot.Connector; +using System.Reflection; + +namespace Microsoft.BotFrameworkFunctionalTests.EchoSkillBotv3 +{ + public class WebApiApplication : System.Web.HttpApplication + { + protected void Application_Start() + { + GlobalConfiguration.Configure(WebApiConfig.Register); + + Conversation.UpdateContainer( + builder => + { + builder.RegisterModule(new AzureModule(Assembly.GetExecutingAssembly())); + + // Bot Storage: Here we register the state storage for your bot. + // Default store: volatile in-memory store - Only for prototyping! + // We provide adapters for Azure Table, CosmosDb, SQL Azure, or you can implement your own! + // For samples and documentation, see: [https://github.com/Microsoft/BotBuilder-Azure](https://github.com/Microsoft/BotBuilder-Azure) + var store = new InMemoryDataStore(); + + // Other storage options + // var store = new TableBotDataStore("...DataStorageConnectionString..."); // requires Microsoft.BotBuilder.Azure Nuget package + // var store = new DocumentDbBotDataStore("cosmos db uri", "cosmos db key"); // requires Microsoft.BotBuilder.Azure Nuget package + + builder.Register(c => store) + .Keyed>(AzureModule.Key_DataStore) + .AsSelf() + .SingleInstance(); + }); + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/Properties/AssemblyInfo.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..450505c46b --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("BotApplication")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("BotApplication")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("287d6db1-a34e-44db-bbf5-e1312cd4737f")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.1")] +[assembly: AssemblyFileVersion("1.0.0.1")] diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/Web.Debug.config b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/Web.Debug.config new file mode 100644 index 0000000000..fae9cfefa9 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/Web.Debug.config @@ -0,0 +1,30 @@ + + + + + + + + + + \ No newline at end of file diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/Web.Release.config b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/Web.Release.config new file mode 100644 index 0000000000..da6e960b8d --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/Web.Release.config @@ -0,0 +1,31 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/Web.config b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/Web.config new file mode 100644 index 0000000000..e9f3490f0e --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/Web.config @@ -0,0 +1,57 @@ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/default.htm b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/default.htm new file mode 100644 index 0000000000..46acb6def1 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/default.htm @@ -0,0 +1,12 @@ + + + + + + + +

Echo Skill bot

+ This bot demonstrates a Bot Builder V3 bot as a Skill. + + + diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/manifests/echoskillbotv3-manifest-1.0.json b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/manifests/echoskillbotv3-manifest-1.0.json new file mode 100644 index 0000000000..c45e0fe08c --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/manifests/echoskillbotv3-manifest-1.0.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://schemas.botframework.com/schemas/skills/v2.1/skill-manifest.json", + "$id": "EchoSkillBotDotNetV3", + "name": "EchoSkillBotDotNetV3", + "version": "1.0", + "description": "This is a skill for echoing what the user sent to the bot (using BF V3).", + "publisherName": "Microsoft", + "privacyUrl": "https://microsoft.com/privacy", + "copyright": "Copyright (c) Microsoft Corporation. All rights reserved.", + "license": "https://github.com/microsoft/BotFramework-FunctionalTests/blob/main/LICENSE", + "tags": [ + "echo" + ], + "endpoints": [ + { + "name": "default", + "protocol": "BotFrameworkV3", + "description": "Localhost endpoint for the skill (on port 35407)", + "endpointUrl": "http://localhost:35407/api/messages", + "msAppId": "00000000-0000-0000-0000-000000000000" + } + ], + "activities": { + "EchoDotNetV3": { + "description": "Echo user responses", + "type": "message", + "name": "V3Echo" + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/packages.config b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/packages.config new file mode 100644 index 0000000000..dcb3d850a1 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3/packages.config @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot/Authentication/AllowedCallersClaimsValidator.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot/Authentication/AllowedCallersClaimsValidator.cs new file mode 100644 index 0000000000..0f5a4e3152 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot/Authentication/AllowedCallersClaimsValidator.cs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.Bot.Connector.Authentication; +using Microsoft.Extensions.Configuration; + +namespace Microsoft.BotFrameworkFunctionalTests.EchoSkillBot.Authentication +{ + /// + /// Sample claims validator that loads an allowed list from configuration if present + /// and checks that requests are coming from allowed parent bots. + /// + public class AllowedCallersClaimsValidator : ClaimsValidator + { + private const string ConfigKey = "AllowedCallers"; + private readonly List _allowedCallers; + + /// + /// Initializes a new instance of the class. + /// Loads the appIds for the configured callers. Only allows access to callers it has configured. + /// + /// The list of configured callers. + public AllowedCallersClaimsValidator(IConfiguration config) + { + if (config == null) + { + throw new ArgumentNullException(nameof(config)); + } + + // AllowedCallers is the setting in the appsettings.json file + // that consists of the list of parent bot IDs that are allowed to access the skill. + // To add a new parent bot, simply edit the AllowedCallers and add + // the parent bot's Microsoft app ID to the list. + // In this sample, we allow all callers if AllowedCallers contains an "*". + var section = config.GetSection(ConfigKey); + var appsList = section.Get(); + if (appsList == null) + { + throw new ArgumentNullException($"\"{ConfigKey}\" not found in configuration."); + } + + _allowedCallers = new List(appsList); + } + + /// + /// Checks that the appId claim in the skill request is in the list of callers configured for this bot. + /// + /// The list of claims to validate. + /// A task that represents the work queued to execute. + public override Task ValidateClaimsAsync(IList claims) + { + // If _allowedCallers contains an "*", we allow all callers. + if (SkillValidation.IsSkillClaim(claims) && !_allowedCallers.Contains("*")) + { + // Check that the appId claim in the skill request is in the list of callers configured for this bot. + var appId = JwtTokenValidation.GetAppIdFromClaims(claims); + if (!_allowedCallers.Contains(appId)) + { + throw new UnauthorizedAccessException($"Received a request from a bot with an app ID of \"{appId}\". To enable requests from this caller, add the app ID to your configuration file."); + } + } + + return Task.CompletedTask; + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot/Bots/EchoBot.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot/Bots/EchoBot.cs new file mode 100644 index 0000000000..1e740b419c --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot/Bots/EchoBot.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Schema; + +namespace Microsoft.BotFrameworkFunctionalTests.EchoSkillBot.Bots +{ + public class EchoBot : ActivityHandler + { + /// + /// Processes a message activity. + /// + /// Context for the current turn of conversation. + /// CancellationToken propagates notifications that operations should be cancelled. + /// A representing the result of the asynchronous operation. + protected override async Task OnMessageActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + if (turnContext.Activity.Text.Contains("end") || turnContext.Activity.Text.Contains("stop")) + { + // Send End of conversation at the end. + await turnContext.SendActivityAsync(MessageFactory.Text($"Ending conversation from the skill..."), cancellationToken); + var endOfConversation = Activity.CreateEndOfConversationActivity(); + endOfConversation.Code = EndOfConversationCodes.CompletedSuccessfully; + await turnContext.SendActivityAsync(endOfConversation, cancellationToken); + } + else + { + await turnContext.SendActivityAsync(MessageFactory.Text($"Echo: {turnContext.Activity.Text}"), cancellationToken); + await turnContext.SendActivityAsync(MessageFactory.Text("Say \"end\" or \"stop\" and I'll end the conversation and back to the parent."), cancellationToken); + } + } + + /// + /// Processes an end of conversation activity. + /// + /// Context for the current turn of conversation. + /// CancellationToken propagates notifications that operations should be cancelled. + /// A representing the result of the asynchronous operation. + protected override Task OnEndOfConversationActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + // This will be called if the host bot is ending the conversation. Sending additional messages should be + // avoided as the conversation may have been deleted. + // Perform cleanup of resources if needed. + return Task.CompletedTask; + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot/Controllers/BotController.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot/Controllers/BotController.cs new file mode 100644 index 0000000000..3877bc87f3 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot/Controllers/BotController.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; + +namespace Microsoft.BotFrameworkFunctionalTests.EchoSkillBot.Controllers +{ + /// + /// This ASP Controller is created to handle a request. Dependency Injection will provide the Adapter and IBot implementation at runtime. + /// Multiple different IBot implementations running at different endpoints can be achieved by specifying a more specific type for the bot constructor argument. + /// + [Route("api/messages")] + [ApiController] + public class BotController : ControllerBase + { + private readonly IBotFrameworkHttpAdapter _adapter; + private readonly IBot _bot; + + /// + /// Initializes a new instance of the class. + /// + /// Adapter for the BotController. + /// Bot for the BotController. + public BotController(IBotFrameworkHttpAdapter adapter, IBot bot) + { + _adapter = adapter; + _bot = bot; + } + + /// + /// Processes an HttpPost request. + /// + /// A representing the result of the asynchronous operation. + [HttpPost] + public async Task PostAsync() + { + await _adapter.ProcessAsync(Request, Response, _bot); + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot/EchoSkillBot.csproj b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot/EchoSkillBot.csproj new file mode 100644 index 0000000000..75badf2f19 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot/EchoSkillBot.csproj @@ -0,0 +1,25 @@ + + + + netcoreapp3.1 + latest + Microsoft.BotFrameworkFunctionalTests.EchoSkillBot + Microsoft.BotFrameworkFunctionalTests.EchoSkillBot + + + + DEBUG;TRACE + + + + + + + + + + Always + + + + diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot/Program.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot/Program.cs new file mode 100644 index 0000000000..5a43557181 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot/Program.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; + +namespace Microsoft.BotFrameworkFunctionalTests.EchoSkillBot +{ + public class Program + { + /// + /// The entry point of the application. + /// + /// The command line args. + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + /// + /// Creates a new instance of the class with pre-configured defaults. + /// + /// The command line args. + /// The initialized . + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot/Properties/launchSettings.json b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot/Properties/launchSettings.json new file mode 100644 index 0000000000..547cc9c69d --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot/Properties/launchSettings.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:35400/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": false, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "EchoSkillBotDotNet": { + "commandName": "Project", + "launchBrowser": true, + "applicationUrl": "http://localhost:35400/", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot/SkillAdapterWithErrorHandler.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot/SkillAdapterWithErrorHandler.cs new file mode 100644 index 0000000000..340e8a68ea --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot/SkillAdapterWithErrorHandler.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Bot.Builder.TraceExtensions; +using Microsoft.Bot.Connector.Authentication; +using Microsoft.Bot.Schema; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace Microsoft.BotFrameworkFunctionalTests.EchoSkillBot +{ + public class SkillAdapterWithErrorHandler : BotFrameworkHttpAdapter + { + /// + /// Initializes a new instance of the class to handle errors. + /// + /// The configuration properties. + /// An implementation of the bots credentials. + /// The configuration setting for the authentication. + /// An instance of a logger. + public SkillAdapterWithErrorHandler(IConfiguration configuration, ICredentialProvider credentialProvider, AuthenticationConfiguration authConfig, ILogger logger) + : base(configuration, credentialProvider, authConfig, logger: logger) + { + OnTurnError = async (turnContext, exception) => + { + try + { + // Log any leaked exception from the application. + logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}"); + + // Send a message to the user + var errorMessageText = "The skill encountered an error or bug."; + var errorMessage = MessageFactory.Text(errorMessageText + Environment.NewLine + exception, errorMessageText, InputHints.IgnoringInput); + errorMessage.Value = exception; + await turnContext.SendActivityAsync(errorMessage); + + errorMessageText = "To continue to run this bot, please fix the bot source code."; + errorMessage = MessageFactory.Text(errorMessageText, errorMessageText, InputHints.ExpectingInput); + await turnContext.SendActivityAsync(errorMessage); + + // Send a trace activity, which will be displayed in the Bot Framework Emulator + // Note: we return the entire exception in the value property to help the developer, this should not be done in prod. + await turnContext.TraceActivityAsync("OnTurnError Trace", exception.ToString(), "https://www.botframework.com/schemas/error", "TurnError"); + + // Send and EndOfConversation activity to the skill caller with the error to end the conversation + // and let the caller decide what to do. + var endOfConversation = Activity.CreateEndOfConversationActivity(); + endOfConversation.Code = "SkillError"; + endOfConversation.Text = exception.Message; + await turnContext.SendActivityAsync(endOfConversation); + } + catch (Exception ex) + { + logger.LogError(ex, $"Exception caught in SkillAdapterWithErrorHandler : {ex}"); + } + }; + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot/Startup.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot/Startup.cs new file mode 100644 index 0000000000..9af686442b --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot/Startup.cs @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.BotFramework; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Bot.Connector.Authentication; +using Microsoft.BotFrameworkFunctionalTests.EchoSkillBot.Bots; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Microsoft.BotFrameworkFunctionalTests.EchoSkillBot +{ + public class Startup + { + public Startup(IConfiguration config) + { + Configuration = config; + } + + public IConfiguration Configuration { get; } + + /// + /// This method gets called by the runtime. Use this method to add services to the container. + /// + /// Method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers().AddNewtonsoftJson(); + + // Configure credentials + services.AddSingleton(); + + // Register AuthConfiguration to enable custom claim validation. + services.AddSingleton(sp => new AuthenticationConfiguration { ClaimsValidator = new Authentication.AllowedCallersClaimsValidator(sp.GetService()) }); + + // Create the Bot Framework Adapter with error handling enabled. + services.AddSingleton(); + + // Create the bot as a transient. In this case the ASP Controller is expecting an IBot. + services.AddTransient(); + + if (!string.IsNullOrEmpty(Configuration["ChannelService"])) + { + // Register a ConfigurationChannelProvider -- this is only for Azure Gov. + services.AddSingleton(); + } + } + + /// + /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + /// + /// The application request pipeline to be configured. + /// The web hosting environment. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseDefaultFiles() + .UseStaticFiles() + .UseRouting() + .UseAuthorization() + .UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot/appsettings.json b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot/appsettings.json new file mode 100644 index 0000000000..c61cdb4d79 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot/appsettings.json @@ -0,0 +1,11 @@ +{ + "MicrosoftAppId": "", + "MicrosoftAppPassword": "", + "ChannelService": "", + // This is a comma separate list with the App IDs that will have access to the skill. + // This setting is used in AllowedCallersClaimsValidator. + // Examples: + // [ "*" ] allows all callers. + // [ "AppId1", "AppId2" ] only allows access to parent bots with "AppId1" and "AppId2". + "AllowedCallers": [ "*" ] +} \ No newline at end of file diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot/wwwroot/default.htm b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot/wwwroot/default.htm new file mode 100644 index 0000000000..416365e6bc --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot/wwwroot/default.htm @@ -0,0 +1,420 @@ + + + + + + + EchoSkillBotDotNet + + + + + +
+
+
+
EchoSkillBotDotNet Bot
+
+
+
+
+
Your bot is ready!
+
You can test your bot in the Bot Framework Emulator
+ by connecting to http://localhost:3978/api/messages.
+ +
Visit Azure + Bot Service to register your bot and add it to
+ various channels. The bot's endpoint URL typically looks + like this:
+
https://your_bots_hostname/api/messages
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot/wwwroot/manifests/echoskillbot-manifest-1.0.json b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot/wwwroot/manifests/echoskillbot-manifest-1.0.json new file mode 100644 index 0000000000..974de692e4 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/EchoSkillBot/wwwroot/manifests/echoskillbot-manifest-1.0.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://schemas.botframework.com/schemas/skills/v2.1/skill-manifest.json", + "$id": "EchoSkillBotDotNet", + "name": "EchoSkillBotDotNet", + "version": "1.0", + "description": "This is a skill for echoing what the user sent to the bot (using .netcore 3.1).", + "publisherName": "Microsoft", + "privacyUrl": "https://microsoft.com/privacy", + "copyright": "Copyright (c) Microsoft Corporation. All rights reserved.", + "license": "https://github.com/microsoft/BotFramework-FunctionalTests/blob/main/LICENSE", + "tags": [ + "echo" + ], + "endpoints": [ + { + "name": "default", + "protocol": "BotFrameworkV3", + "description": "Localhost endpoint for the skill (on port 35400)", + "endpointUrl": "http://localhost:35400/api/messages", + "msAppId": "00000000-0000-0000-0000-000000000000" + } + ] +} \ No newline at end of file diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Authentication/AllowedCallersClaimsValidator.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Authentication/AllowedCallersClaimsValidator.cs new file mode 100644 index 0000000000..cc811d5d82 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Authentication/AllowedCallersClaimsValidator.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.Bot.Connector.Authentication; +using Microsoft.Extensions.Configuration; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Authentication +{ + /// + /// Sample claims validator that loads an allowed list from configuration if present + /// and checks that requests are coming from allowed parent bots. + /// + public class AllowedCallersClaimsValidator : ClaimsValidator + { + private const string ConfigKey = "AllowedCallers"; + private readonly List _allowedCallers; + + public AllowedCallersClaimsValidator(IConfiguration config) + { + if (config == null) + { + throw new ArgumentNullException(nameof(config)); + } + + // AllowedCallers is the setting in the appsettings.json file + // that consists of the list of parent bot IDs that are allowed to access the skill. + // To add a new parent bot, simply edit the AllowedCallers and add + // the parent bot's Microsoft app ID to the list. + // In this sample, we allow all callers if AllowedCallers contains an "*". + var section = config.GetSection(ConfigKey); + var appsList = section.Get(); + if (appsList == null) + { + throw new ArgumentNullException($"\"{ConfigKey}\" not found in configuration."); + } + + _allowedCallers = new List(appsList); + } + + public override Task ValidateClaimsAsync(IList claims) + { + // If _allowedCallers contains an "*", we allow all callers. + if (SkillValidation.IsSkillClaim(claims) && !_allowedCallers.Contains("*")) + { + // Check that the appId claim in the skill request is in the list of callers configured for this bot. + var appId = JwtTokenValidation.GetAppIdFromClaims(claims); + if (!_allowedCallers.Contains(appId)) + { + throw new UnauthorizedAccessException($"Received a request from a bot with an app ID of \"{appId}\". To enable requests from this caller, add the app ID to your configuration file."); + } + } + + return Task.CompletedTask; + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Bots/SkillBot.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Bots/SkillBot.cs new file mode 100644 index 0000000000..ffad563104 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Bots/SkillBot.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Schema; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Bots +{ + public class SkillBot : ActivityHandler + where T : Dialog + { + private readonly ConversationState _conversationState; + private readonly Dialog _mainDialog; + private readonly Uri _serverUrl; + + public SkillBot(ConversationState conversationState, T mainDialog, IHttpContextAccessor httpContextAccessor) + { + _conversationState = conversationState; + _mainDialog = mainDialog; + _serverUrl = new Uri($"{httpContextAccessor.HttpContext.Request.Scheme}://{httpContextAccessor.HttpContext.Request.Host.Value}"); + } + + public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) + { + if (turnContext.Activity.Type == ActivityTypes.ConversationUpdate) + { + // Let the base class handle the activity (this will trigger OnMembersAddedAsync). + await base.OnTurnAsync(turnContext, cancellationToken); + } + else + { + // Run the Dialog with the Activity. + await _mainDialog.RunAsync(turnContext, _conversationState.CreateProperty("DialogState"), cancellationToken); + } + + // Save any state changes that might have occurred during the turn. + await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken); + } + + protected override async Task OnMembersAddedAsync(IList membersAdded, ITurnContext turnContext, CancellationToken cancellationToken) + { + foreach (var member in membersAdded) + { + if (member.Id != turnContext.Activity.Recipient.Id) + { + var activity = MessageFactory.Text("Welcome to the waterfall skill bot. \n\nThis is a skill, you will need to call it from another bot to use it."); + activity.Speak = "Welcome to the waterfall skill bot. This is a skill, you will need to call it from another bot to use it."; + await turnContext.SendActivityAsync(activity, cancellationToken); + + await turnContext.SendActivityAsync($"You can check the skill manifest to see what it supports here: {_serverUrl}manifests/waterfallskillbot-manifest-1.0.json", cancellationToken: cancellationToken); + } + } + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Controllers/BotController.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Controllers/BotController.cs new file mode 100644 index 0000000000..eff768c82a --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Controllers/BotController.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Extensions.Logging; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Controllers +{ + // This ASP Controller is created to handle a request. Dependency Injection will provide the Adapter and IBot + // implementation at runtime. Multiple different IBot implementations running at different endpoints can be + // achieved by specifying a more specific type for the bot constructor argument. + [ApiController] + public class BotController : ControllerBase + { + private readonly IBotFrameworkHttpAdapter _adapter; + private readonly IBot _bot; + private readonly ILogger _logger; + + public BotController(BotFrameworkHttpAdapter adapter, IBot bot, ILogger logger) + { + _adapter = adapter; + _bot = bot; + _logger = logger; + } + + [Route("api/messages")] + [HttpGet] + [HttpPost] + public async Task PostAsync() + { + try + { + // Delegate the processing of the HTTP POST to the adapter. + // The adapter will invoke the bot. + await _adapter.ProcessAsync(Request, Response, _bot); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error processing request"); + throw; + } + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Controllers/CardsController.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Controllers/CardsController.cs new file mode 100644 index 0000000000..817c197758 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Controllers/CardsController.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.IO; +using Microsoft.AspNetCore.Mvc; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Controllers +{ + // This ASP Controller is created to handle a request. Dependency Injection will provide the Adapter and IBot + // implementation at runtime. Multiple different IBot implementations running at different endpoints can be + // achieved by specifying a more specific type for the bot constructor argument. + [ApiController] + public class CardsController : ControllerBase + { + private static readonly string Music = "music.mp3"; + + [Route("api/music")] + [HttpGet] + public ActionResult ReturnFile() + { + var filename = Music; + var filePath = Path.Combine(Directory.GetCurrentDirectory(), "Dialogs/Cards/Files", filename); + var fileData = System.IO.File.ReadAllBytes(filePath); + + return File(fileData, "audio/mp3"); + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Controllers/ProactiveController.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Controllers/ProactiveController.cs new file mode 100644 index 0000000000..ae96c51bbb --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Controllers/ProactiveController.cs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Concurrent; +using System.Net; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs; +using Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Proactive; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Controllers +{ + [Route("api/notify")] + [ApiController] + public class ProactiveController : ControllerBase + { + private readonly IBotFrameworkHttpAdapter _adapter; + private readonly ConcurrentDictionary _continuationParametersStore; + private readonly ConversationState _conversationState; + private readonly ActivityRouterDialog _mainDialog; + + public ProactiveController(ConversationState conversationState, ActivityRouterDialog mainDialog, BotFrameworkHttpAdapter adapter, ConcurrentDictionary continuationParametersStore) + { + _conversationState = conversationState; + _adapter = adapter; + _continuationParametersStore = continuationParametersStore; + _mainDialog = mainDialog; + } + + // Note: in production scenarios, this controller should be secured. + public async Task Get(string user) + { + _continuationParametersStore.TryGetValue(user, out var continuationParameters); + + if (continuationParameters == null) + { + // Let the caller know a proactive messages have been sent + return new ContentResult + { + Content = $"

No messages sent


There are no conversations registered to receive proactive messages for {user}.", + ContentType = "text/html", + StatusCode = (int)HttpStatusCode.OK, + }; + } + + Exception exception = null; + try + { + async Task ContinuationBotCallback(ITurnContext context, CancellationToken cancellationToken) + { + await context.SendActivityAsync($"Got proactive message for user: {user}", cancellationToken: cancellationToken); + + // If we didn't have dialogs we could remove the code below, but we want to continue the dialog to clear the + // dialog stack. + // Run the main dialog to continue WaitForProactiveDialog and send an EndOfConversation when that one is done. + // ContinueDialogAsync in WaitForProactiveDialog will get a ContinueConversation event when this is called. + await _mainDialog.RunAsync(context, _conversationState.CreateProperty("DialogState"), cancellationToken); + + // Save any state changes so the dialog stack is persisted. + await _conversationState.SaveChangesAsync(context, false, cancellationToken); + } + + // Continue the conversation with the proactive message + await ((BotFrameworkAdapter)_adapter).ContinueConversationAsync((ClaimsIdentity)continuationParameters.ClaimsIdentity, continuationParameters.ConversationReference, continuationParameters.OAuthScope, ContinuationBotCallback, default); + } + catch (Exception ex) + { + exception = ex; + } + + // Let the caller know a proactive messages have been sent + return new ContentResult + { + Content = $"

Proactive messages have been sent


Timestamp: {DateTime.Now}
Exception: {exception}", + ContentType = "text/html", + StatusCode = (int)HttpStatusCode.OK, + }; + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Controllers/SkillController.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Controllers/SkillController.cs new file mode 100644 index 0000000000..ea90b07c02 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Controllers/SkillController.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Bot.Builder.Skills; +using Microsoft.Bot.Schema; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Controllers +{ + /// + /// A controller that handles skill replies to the bot. + /// This example uses the that is registered as a in startup.cs. + /// + [ApiController] + [Route("api/skills")] + public class SkillController : ChannelServiceController + { + public SkillController(ChannelServiceHandler handler) + : base(handler) + { + } + + public override Task ReplyToActivityAsync(string conversationId, string activityId, Activity activity) + { + try + { + return base.ReplyToActivityAsync(conversationId, activityId, activity); + } + catch (Exception ex) + { + Console.WriteLine(ex); + throw; + } + } + + public override Task SendToConversationAsync(string conversationId, Activity activity) + { + try + { + return base.SendToConversationAsync(conversationId, activity); + } + catch (Exception ex) + { + Console.WriteLine(ex); + throw; + } + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/ActivityRouterDialog.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/ActivityRouterDialog.cs new file mode 100644 index 0000000000..11acef467c --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/ActivityRouterDialog.cs @@ -0,0 +1,148 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Concurrent; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Builder.Integration.AspNet.Core.Skills; +using Microsoft.Bot.Builder.Skills; +using Microsoft.Bot.Builder.TraceExtensions; +using Microsoft.Bot.Connector.Authentication; +using Microsoft.Bot.Schema; +using Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Auth; +using Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Cards; +using Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Delete; +using Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.FileUpload; +using Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.MessageWithAttachment; +using Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Proactive; +using Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Sso; +using Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Update; +using Microsoft.Extensions.Configuration; +using Newtonsoft.Json; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs +{ + /// + /// A root dialog that can route activities sent to the skill to different sub-dialogs. + /// + public class ActivityRouterDialog : ComponentDialog + { + private static readonly string _echoSkill = "EchoSkill"; + + public ActivityRouterDialog(IConfiguration configuration, IHttpContextAccessor httpContextAccessor, ConversationState conversationState, SkillConversationIdFactoryBase conversationIdFactory, SkillHttpClient skillClient, ConcurrentDictionary continuationParametersStore) + : base(nameof(ActivityRouterDialog)) + { + AddDialog(new CardDialog(httpContextAccessor)); + AddDialog(new MessageWithAttachmentDialog(new Uri($"{httpContextAccessor.HttpContext.Request.Scheme}://{httpContextAccessor.HttpContext.Request.Host.Value}"))); + AddDialog(new WaitForProactiveDialog(httpContextAccessor, continuationParametersStore)); + AddDialog(new AuthDialog(configuration)); + AddDialog(new SsoSkillDialog(configuration)); + AddDialog(new FileUploadDialog()); + AddDialog(new DeleteDialog()); + AddDialog(new UpdateDialog()); + + AddDialog(CreateEchoSkillDialog(conversationState, conversationIdFactory, skillClient, configuration)); + + AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[] { ProcessActivityAsync })); + + // The initial child Dialog to run. + InitialDialogId = nameof(WaterfallDialog); + } + + private static SkillDialog CreateEchoSkillDialog(ConversationState conversationState, SkillConversationIdFactoryBase conversationIdFactory, SkillHttpClient skillClient, IConfiguration configuration) + { + var botId = configuration.GetSection(MicrosoftAppCredentials.MicrosoftAppIdKey)?.Value; + + var skillHostEndpoint = configuration.GetSection("SkillHostEndpoint")?.Value; + if (string.IsNullOrWhiteSpace(skillHostEndpoint)) + { + throw new ArgumentException("SkillHostEndpoint is not in configuration"); + } + + var skillInfo = configuration.GetSection("EchoSkillInfo").Get() ?? throw new ArgumentException("EchoSkillInfo is not set in configuration"); + + var skillDialogOptions = new SkillDialogOptions + { + BotId = botId, + ConversationIdFactory = conversationIdFactory, + SkillClient = skillClient, + SkillHostEndpoint = new Uri(skillHostEndpoint), + ConversationState = conversationState, + Skill = skillInfo + }; + var echoSkillDialog = new SkillDialog(skillDialogOptions); + + echoSkillDialog.Id = _echoSkill; + return echoSkillDialog; + } + + private async Task ProcessActivityAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + // A skill can send trace activities, if needed. + await stepContext.Context.TraceActivityAsync($"{GetType().Name}.ProcessActivityAsync()", label: $"Got ActivityType: {stepContext.Context.Activity.Type}", cancellationToken: cancellationToken); + + switch (stepContext.Context.Activity.Type) + { + case ActivityTypes.Event: + return await OnEventActivityAsync(stepContext, cancellationToken); + + default: + // We didn't get an activity type we can handle. + await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Unrecognized ActivityType: \"{stepContext.Context.Activity.Type}\".", inputHint: InputHints.IgnoringInput), cancellationToken); + return new DialogTurnResult(DialogTurnStatus.Complete); + } + } + + // This method performs different tasks based on the event name. + private async Task OnEventActivityAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var activity = stepContext.Context.Activity; + await stepContext.Context.TraceActivityAsync($"{GetType().Name}.OnEventActivityAsync()", label: $"Name: {activity.Name}. Value: {GetObjectAsJsonString(activity.Value)}", cancellationToken: cancellationToken); + + // Resolve what to execute based on the event name. + switch (activity.Name) + { + case "Cards": + return await stepContext.BeginDialogAsync(FindDialog(nameof(CardDialog)).Id, cancellationToken: cancellationToken); + + case "Proactive": + return await stepContext.BeginDialogAsync(FindDialog(nameof(WaitForProactiveDialog)).Id, cancellationToken: cancellationToken); + + case "MessageWithAttachment": + return await stepContext.BeginDialogAsync(FindDialog(nameof(MessageWithAttachmentDialog)).Id, cancellationToken: cancellationToken); + + case "Auth": + return await stepContext.BeginDialogAsync(FindDialog(nameof(AuthDialog)).Id, cancellationToken: cancellationToken); + + case "Sso": + return await stepContext.BeginDialogAsync(FindDialog(nameof(SsoSkillDialog)).Id, cancellationToken: cancellationToken); + + case "FileUpload": + return await stepContext.BeginDialogAsync(FindDialog(nameof(FileUploadDialog)).Id, cancellationToken: cancellationToken); + + case "Echo": + // Start the EchoSkillBot + var messageActivity = MessageFactory.Text("I'm the echo skill bot"); + messageActivity.DeliveryMode = stepContext.Context.Activity.DeliveryMode; + return await stepContext.BeginDialogAsync(FindDialog(_echoSkill).Id, new BeginSkillDialogOptions { Activity = messageActivity }, cancellationToken); + + case "Delete": + return await stepContext.BeginDialogAsync(FindDialog(nameof(DeleteDialog)).Id, cancellationToken: cancellationToken); + + case "Update": + return await stepContext.BeginDialogAsync(FindDialog(nameof(UpdateDialog)).Id, cancellationToken: cancellationToken); + + default: + // We didn't get an event name we can handle. + await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Unrecognized EventName: \"{activity.Name}\".", inputHint: InputHints.IgnoringInput), cancellationToken); + return new DialogTurnResult(DialogTurnStatus.Complete); + } + } + + private string GetObjectAsJsonString(object value) => value == null ? string.Empty : JsonConvert.SerializeObject(value); + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Auth/AuthDialog.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Auth/AuthDialog.cs new file mode 100644 index 0000000000..39d26e238d --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Auth/AuthDialog.cs @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Schema; +using Microsoft.Extensions.Configuration; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Auth +{ + public class AuthDialog : ComponentDialog + { + private readonly string _connectionName; + + public AuthDialog(IConfiguration configuration) + : base(nameof(AuthDialog)) + { + _connectionName = configuration["ConnectionName"]; + + // This confirmation dialog should be removed once https://github.com/microsoft/BotFramework-FunctionalTests/issues/299 is resolved (and this class should look like the class in the issue) + AddDialog(new ConfirmPrompt(nameof(ConfirmPrompt))); + AddDialog(new OAuthPrompt( + nameof(OAuthPrompt), + new OAuthPromptSettings + { + ConnectionName = _connectionName, + Text = $"Please Sign In to connection: '{_connectionName}'", + Title = "Sign In", + Timeout = 300000 // User has 5 minutes to login (1000 * 60 * 5) + })); + + AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[] { PromptStepAsync, LoginStepAsync, DisplayTokenAsync })); + + // The initial child Dialog to run. + InitialDialogId = nameof(WaterfallDialog); + } + + private async Task PromptStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + return await stepContext.BeginDialogAsync(nameof(OAuthPrompt), null, cancellationToken); + } + + private async Task LoginStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + // Get the token from the previous step. + var tokenResponse = (TokenResponse)stepContext.Result; + if (tokenResponse != null) + { + stepContext.Values["Token"] = tokenResponse.Token; + + // Show the token + var loggedInMessage = "You are now logged in."; + await stepContext.Context.SendActivityAsync(MessageFactory.Text(loggedInMessage, loggedInMessage, InputHints.IgnoringInput), cancellationToken); + + return await stepContext.PromptAsync(nameof(ConfirmPrompt), new PromptOptions { Prompt = MessageFactory.Text("Would you like to view your token?") }, cancellationToken); + } + + var tryAgainMessage = "Login was not successful please try again."; + await stepContext.Context.SendActivityAsync(MessageFactory.Text(tryAgainMessage, tryAgainMessage, InputHints.IgnoringInput), cancellationToken); + return await stepContext.ReplaceDialogAsync(InitialDialogId, cancellationToken: cancellationToken); + } + + private async Task DisplayTokenAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var result = (bool)stepContext.Result; + if (result) + { + var showTokenMessage = "Here is your token:"; + await stepContext.Context.SendActivityAsync(MessageFactory.Text($"{showTokenMessage} {stepContext.Values["Token"]}", showTokenMessage, InputHints.IgnoringInput), cancellationToken); + } + + // Sign out + var botAdapter = (BotFrameworkAdapter)stepContext.Context.Adapter; + await botAdapter.SignOutUserAsync(stepContext.Context, _connectionName, null, cancellationToken); + var signOutMessage = "I have signed you out."; + await stepContext.Context.SendActivityAsync(MessageFactory.Text(signOutMessage, signOutMessage, inputHint: InputHints.IgnoringInput), cancellationToken); + + return await stepContext.EndDialogAsync(cancellationToken: cancellationToken); + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Cards/AdaptiveCardExtensions.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Cards/AdaptiveCardExtensions.cs new file mode 100644 index 0000000000..a0cb2216a6 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Cards/AdaptiveCardExtensions.cs @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using AdaptiveCards; +using Microsoft.Bot.Schema; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Cards +{ + public static class AdaptiveCardExtensions + { + /// + /// Creates a new attachment from AdaptiveCard. + /// + /// The instance of AdaptiveCard. + /// The generated attachment. + public static Attachment ToAttachment(this AdaptiveCard card) + { + return new Attachment + { + Content = card, + ContentType = AdaptiveCard.ContentType, + }; + } + + /// + /// Wrap BotBuilder action into AdaptiveCard submit action. + /// + /// The instance of adaptive card submit action. + /// Target action to be adapted. + public static void RepresentAsBotBuilderAction(this AdaptiveSubmitAction action, CardAction targetAction) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (targetAction == null) + { + throw new ArgumentNullException(nameof(targetAction)); + } + + var wrappedAction = new CardAction + { + Type = targetAction.Type, + Value = targetAction.Value, + Text = targetAction.Text, + DisplayText = targetAction.DisplayText, + }; + + var serializerSettings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }; + + var jsonStr = action.DataJson ?? "{}"; + JToken dataJson = JObject.Parse(jsonStr); + dataJson["msteams"] = JObject.FromObject(wrappedAction, JsonSerializer.Create(serializerSettings)); + + action.Title = targetAction.Title; + action.DataJson = dataJson.ToString(); + } + + /// + /// Wrap BotBuilder action into AdaptiveCard submit action. + /// + /// Target bot builder action to be adapted. + /// The wrapped adaptive card submit action. + public static AdaptiveSubmitAction ToAdaptiveCardAction(this CardAction action) + { + var adaptiveCardAction = new AdaptiveSubmitAction(); + adaptiveCardAction.RepresentAsBotBuilderAction(action); + return adaptiveCardAction; + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Cards/CardDialog.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Cards/CardDialog.cs new file mode 100644 index 0000000000..8ded48ff2c --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Cards/CardDialog.cs @@ -0,0 +1,340 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using AdaptiveCards; +using Microsoft.AspNetCore.Http; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Builder.Dialogs.Choices; +using Microsoft.Bot.Schema; +using Microsoft.Bot.Schema.Teams; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Cards +{ + public class CardDialog : ComponentDialog + { + // for file upload + private static readonly string TeamsLogoFileName = "teams-logo.png"; + + // for video card + private static readonly string CorgiOnCarouselVideo = "https://www.youtube.com/watch?v=LvqzubPZjHE"; + + // for animation card + private static readonly string MindBlownGif = "https://media3.giphy.com/media/xT0xeJpnrWC4XWblEk/giphy.gif?cid=ecf05e47mye7k75sup6tcmadoom8p1q8u03a7g2p3f76upp9&rid=giphy.gif"; + + // list of cards that exist + private static readonly List _cardOptions = Enum.GetValues(typeof(CardOptions)).Cast().ToList(); + + private readonly Uri _serverUrl; + + public CardDialog(IHttpContextAccessor httpContextAccessor) + : base(nameof(CardDialog)) + { + _serverUrl = new Uri($"{httpContextAccessor.HttpContext.Request.Scheme}://{httpContextAccessor.HttpContext.Request.Host.Value}"); + + AddDialog(new ChoicePrompt("CardPrompt", CardPromptValidatorAsync)); + AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[] { SelectCardAsync, DisplayCardAsync })); + + InitialDialogId = nameof(WaterfallDialog); + } + + private static CardOptions ParseEnum(string card) + { + return (CardOptions)Enum.Parse(typeof(CardOptions), card, true); + } + + private static HeroCard MakeUpdatedHeroCard(WaterfallStepContext stepContext) + { + var heroCard = new HeroCard + { + Title = "Newly updated card.", + Buttons = new List() + }; + + var data = stepContext.Context.Activity.Value as JObject; + data = JObject.FromObject(data); + data["count"] = data["count"].Value() + 1; + heroCard.Text = $"Update count - {data["count"].Value()}"; + heroCard.Title = "Newly updated card"; + + heroCard.Buttons.Add(new CardAction + { + Type = ActionTypes.MessageBack, + Title = "Update Card", + Text = "UpdateCardAction", + Value = data + }); + + return heroCard; + } + + private async Task SelectCardAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + // Create the PromptOptions from the skill configuration which contain the list of configured skills. + var messageText = "What card do you want?"; + var repromptMessageText = "This message will be created in the validation code"; + var options = new PromptOptions + { + Prompt = MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput), + RetryPrompt = MessageFactory.Text(repromptMessageText, repromptMessageText, InputHints.ExpectingInput), + Choices = _cardOptions.Select(card => new Choice(card.ToString())).ToList(), + Style = ListStyle.List + }; + + // Ask the user to enter their name. + return await stepContext.PromptAsync("CardPrompt", options, cancellationToken); + } + + private async Task DisplayCardAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + if (stepContext.Context.Activity.Value != null) + { + await HandleSpecialActivity(stepContext, cancellationToken); + } + else + { + // Checks to see if the activity is an adaptive card update or a bot action respose + var card = ((FoundChoice)stepContext.Result).Value.ToLowerInvariant(); + var cardType = ParseEnum(card); + + if (ChannelSupportedCards.IsCardSupported(stepContext.Context.Activity.ChannelId, cardType)) + { + switch (cardType) + { + case CardOptions.AdaptiveCardBotAction: + await stepContext.Context.SendActivityAsync(MessageFactory.Attachment(MakeAdaptiveCard("botaction").ToAttachment()), cancellationToken); + break; + case CardOptions.AdaptiveCardTeamsTaskModule: + await stepContext.Context.SendActivityAsync(MessageFactory.Attachment(MakeAdaptiveCard("taskmodule").ToAttachment()), cancellationToken); + break; + case CardOptions.AdaptiveCardSubmitAction: + await stepContext.Context.SendActivityAsync(MessageFactory.Attachment(MakeAdaptiveCard("submitaction").ToAttachment()), cancellationToken); + break; + case CardOptions.Hero: + await stepContext.Context.SendActivityAsync(MessageFactory.Attachment(CardSampleHelper.CreateHeroCard().ToAttachment()), cancellationToken).ConfigureAwait(false); + break; + case CardOptions.Thumbnail: + await stepContext.Context.SendActivityAsync(MessageFactory.Attachment(CardSampleHelper.CreateThumbnailCard().ToAttachment()), cancellationToken).ConfigureAwait(false); + break; + case CardOptions.Receipt: + await stepContext.Context.SendActivityAsync(MessageFactory.Attachment(CardSampleHelper.CreateReceiptCard().ToAttachment()), cancellationToken).ConfigureAwait(false); + break; + case CardOptions.Signin: + await stepContext.Context.SendActivityAsync(MessageFactory.Attachment(CardSampleHelper.CreateSigninCard().ToAttachment()), cancellationToken).ConfigureAwait(false); + break; + case CardOptions.Carousel: + // NOTE: if cards are NOT the same height in a carousel, Teams will instead display as AttachmentLayoutTypes.List + await stepContext.Context.SendActivityAsync( + MessageFactory.Carousel(new[] + { + CardSampleHelper.CreateHeroCard().ToAttachment(), + CardSampleHelper.CreateHeroCard().ToAttachment(), + CardSampleHelper.CreateHeroCard().ToAttachment() + }), + cancellationToken).ConfigureAwait(false); + break; + case CardOptions.List: + // NOTE: MessageFactory.Attachment with multiple attachments will default to AttachmentLayoutTypes.List + await stepContext.Context.SendActivityAsync( + MessageFactory.Attachment(new[] + { + CardSampleHelper.CreateHeroCard().ToAttachment(), + CardSampleHelper.CreateHeroCard().ToAttachment(), + CardSampleHelper.CreateHeroCard().ToAttachment() + }), + cancellationToken).ConfigureAwait(false); + break; + case CardOptions.O365: + + await stepContext.Context.SendActivityAsync(MessageFactory.Attachment(MakeO365CardAttachmentAsync()), cancellationToken).ConfigureAwait(false); + break; + case CardOptions.TeamsFileConsent: + await stepContext.Context.SendActivityAsync(MessageFactory.Attachment(MakeTeamsFileConsentCard()), cancellationToken); + break; + case CardOptions.Animation: + await stepContext.Context.SendActivityAsync(MessageFactory.Attachment(MakeAnimationCard().ToAttachment()), cancellationToken); + break; + case CardOptions.Audio: + await stepContext.Context.SendActivityAsync(MessageFactory.Attachment(MakeAudioCard().ToAttachment()), cancellationToken); + break; + case CardOptions.Video: + await stepContext.Context.SendActivityAsync(MessageFactory.Attachment(MakeVideoCard().ToAttachment()), cancellationToken); + break; + case CardOptions.AdaptiveUpdate: + await stepContext.Context.SendActivityAsync(MessageFactory.Attachment(MakeUpdateAdaptiveCard().ToAttachment()), cancellationToken); + break; + case CardOptions.End: + return new DialogTurnResult(DialogTurnStatus.Complete); + } + } + else + { + await stepContext.Context.SendActivityAsync(MessageFactory.Text($"{cardType} cards are not supported in the {stepContext.Context.Activity.ChannelId} channel."), cancellationToken); + } + } + + return await stepContext.ReplaceDialogAsync(InitialDialogId, "What card would you want?", cancellationToken); + } + + private async Task HandleSpecialActivity(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + if (stepContext.Context.Activity.Text == null) + { + await stepContext.Context.SendActivityAsync(MessageFactory.Text($"I received an activity with this data in the value field {stepContext.Context.Activity.Value}"), cancellationToken); + } + else + { + if (stepContext.Context.Activity.Text.ToLowerInvariant().Contains("update")) + { + if (stepContext.Context.Activity.ReplyToId == null) + { + await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Update activity is not supported in the {stepContext.Context.Activity.ChannelId} channel."), cancellationToken); + } + else + { + var heroCard = MakeUpdatedHeroCard(stepContext); + + var activity = MessageFactory.Attachment(heroCard.ToAttachment()); + activity.Id = stepContext.Context.Activity.ReplyToId; + await stepContext.Context.UpdateActivityAsync(activity, cancellationToken); + } + } + else + { + await stepContext.Context.SendActivityAsync(MessageFactory.Text($"I received an activity with this data in the text field {stepContext.Context.Activity.Text} and this data in the value field {stepContext.Context.Activity.Value}"), cancellationToken); + } + } + } + + private async Task CardPromptValidatorAsync(PromptValidatorContext promptContext, CancellationToken cancellationToken) + { + if (!promptContext.Recognized.Succeeded) + { + // This checks to see if this response is the user clicking the update button on the card + if (promptContext.Context.Activity.Value != null) + { + return await Task.FromResult(true); + } + + if (promptContext.Context.Activity.Attachments != null) + { + return await Task.FromResult(true); + } + + // Render the activity so we can assert in tests. + // We may need to simplify the json if it gets too complicated to test. + promptContext.Options.RetryPrompt.Text = $"Got {JsonConvert.SerializeObject(promptContext.Context.Activity, Formatting.Indented)}\n\n{promptContext.Options.Prompt.Text}"; + return await Task.FromResult(false); + } + + return await Task.FromResult(true); + } + + private HeroCard MakeUpdateAdaptiveCard() + { + var heroCard = new HeroCard + { + Title = "Update card", + Text = "Update Card Action", + Buttons = new List() + }; + + var action = new CardAction + { + Type = ActionTypes.MessageBack, + Title = "Update card title", + Text = "Update card text", + Value = new JObject { { "count", 0 } } + }; + + heroCard.Buttons.Add(action); + + return heroCard; + } + + private AdaptiveCard MakeAdaptiveCard(string cardType) + { + var adaptiveCard = cardType switch + { + "botaction" => CardSampleHelper.CreateAdaptiveCardBotAction(), + "taskmodule" => CardSampleHelper.CreateAdaptiveCardTaskModule(), + "submitaction" => CardSampleHelper.CreateAdaptiveCardSubmit(), + _ => throw new ArgumentException(nameof(cardType)), + }; + + return adaptiveCard; + } + + private Attachment MakeO365CardAttachmentAsync() + { + var card = CardSampleHelper.CreateSampleO365ConnectorCard(); + var cardAttachment = new Attachment + { + Content = card, + ContentType = O365ConnectorCard.ContentType, + }; + + return cardAttachment; + } + + private Attachment MakeTeamsFileConsentCard() + { + var filename = TeamsLogoFileName; + var filePath = Path.Combine("Dialogs/Cards/Files", filename); + var fileSize = new FileInfo(filePath).Length; + + return MakeTeamsFileConsentCardAttachment(filename, fileSize); + } + + private Attachment MakeTeamsFileConsentCardAttachment(string filename, long fileSize) + { + var consentContext = new Dictionary + { + { "filename", filename }, + }; + + var fileCard = new FileConsentCard + { + Description = "This is the file I want to send you", + SizeInBytes = fileSize, + AcceptContext = consentContext, + DeclineContext = consentContext, + }; + + var asAttachment = new Attachment + { + Content = fileCard, + ContentType = FileConsentCard.ContentType, + Name = filename, + }; + + return asAttachment; + } + + private AnimationCard MakeAnimationCard() + { + var url = new MediaUrl(url: MindBlownGif); + return new AnimationCard(title: "Animation Card", media: new[] { url }, autostart: true); + } + + private VideoCard MakeVideoCard() + { + var url = new MediaUrl(url: CorgiOnCarouselVideo); + return new VideoCard(title: "Video Card", media: new[] { url }); + } + + private AudioCard MakeAudioCard() + { + var url = new MediaUrl(url: $"{_serverUrl}api/music"); + return new AudioCard(title: "Audio Card", media: new[] { url }, autoloop: true); + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Cards/CardOptions.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Cards/CardOptions.cs new file mode 100644 index 0000000000..f5cd97dafe --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Cards/CardOptions.cs @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Cards +{ + public enum CardOptions + { + /// + /// Adaptive card - Bot action + /// + AdaptiveCardBotAction, + + /// + /// Adaptive card - Task module + /// + AdaptiveCardTeamsTaskModule, + + /// + /// Adaptive card - Submit action + /// + AdaptiveCardSubmitAction, + + /// + /// Hero cards + /// + Hero, + + /// + /// Thumbnail cards + /// + Thumbnail, + + /// + /// Receipt cards + /// + Receipt, + + /// + /// Signin cards + /// + Signin, + + /// + /// Carousel cards + /// + Carousel, + + /// + /// List cards + /// + List, + + /// + /// O365 cards + /// + O365, + + /// + /// File cards + /// + TeamsFileConsent, + + /// + /// Animation cards + /// + Animation, + + /// + /// Audio cards + /// + Audio, + + /// + /// Video cards + /// + Video, + + /// + /// Adaptive update cards + /// + AdaptiveUpdate, + + /// + /// Ends the card selection dialog + /// + End + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Cards/CardSampleHelper.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Cards/CardSampleHelper.cs new file mode 100644 index 0000000000..8ae3df2ada --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Cards/CardSampleHelper.cs @@ -0,0 +1,508 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using AdaptiveCards; +using Microsoft.Bot.Schema; +using Microsoft.Bot.Schema.Teams; +using Newtonsoft.Json.Linq; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Cards +{ + public static class CardSampleHelper + { + public static AdaptiveCard CreateAdaptiveCardBotAction() + { + var adaptiveCard = new AdaptiveCard(new AdaptiveSchemaVersion(1, 2)); + adaptiveCard.Body.Add(new AdaptiveTextBlock("Bot Builder actions")); + + var action1 = new CardAction(ActionTypes.ImBack, "imBack", null, null, null, "text"); + var action2 = new CardAction(ActionTypes.MessageBack, "message back", null, null, null, JObject.Parse(@"{ ""key"" : ""value"" }")); + var action3 = new CardAction(ActionTypes.MessageBack, "message back local echo", null, "text received by bots", "display text message back", JObject.Parse(@"{ ""key"" : ""value"" }")); + var action4 = new CardAction("invoke", "invoke", null, null, null, JObject.Parse(@"{ ""key"" : ""value"" }")); + + adaptiveCard.Actions.Add(action1.ToAdaptiveCardAction()); + adaptiveCard.Actions.Add(action2.ToAdaptiveCardAction()); + adaptiveCard.Actions.Add(action3.ToAdaptiveCardAction()); + adaptiveCard.Actions.Add(action4.ToAdaptiveCardAction()); + + return adaptiveCard; + } + + public static AdaptiveCard CreateAdaptiveCardTaskModule() + { + var taskModuleAction = new TaskModuleAction("Launch Task Module", @"{ ""hiddenKey"": ""hidden value from task module launcher"" }"); + + var adaptiveCard = new AdaptiveCard(new AdaptiveSchemaVersion(1, 2)); + adaptiveCard.Body.Add(new AdaptiveTextBlock("Task Module Adaptive Card")); + adaptiveCard.Actions.Add(taskModuleAction.ToAdaptiveCardAction()); + + return adaptiveCard; + } + + public static AdaptiveCard CreateAdaptiveCardSubmit() + { + var adaptiveCard = new AdaptiveCard(new AdaptiveSchemaVersion(1, 2)); + adaptiveCard.Body.Add(new AdaptiveTextBlock("Bot Builder actions")); + adaptiveCard.Body.Add(new AdaptiveTextInput { Id = "x" }); + adaptiveCard.Actions.Add(new AdaptiveSubmitAction { Type = "Action.Submit", Title = "Action.Submit", Data = new JObject { { "key", "value" } } }); + + return adaptiveCard; + } + + public static Attachment CreateTaskModuleHeroCard() + { + return new HeroCard() + { + Title = "Task Module Invocation from Hero Card", + Subtitle = "This is a hero card with a Task Module Action button. Click the button to show an Adaptive Card within a Task Module.", + Buttons = new List() + { + new TaskModuleAction("Adaptive Card", new { data = "adaptivecard" }), + }, + }.ToAttachment(); + } + + public static SampleData CreateSampleData(AdaptiveCard adaptiveCard) + { + if (adaptiveCard == null) + { + throw new ArgumentNullException(nameof(adaptiveCard)); + } + + if (adaptiveCard.Body.Count < 4) + { + throw new Exception("Adaptive Card Body contains too few elements"); + } + + var userText = (adaptiveCard.Body[1] as AdaptiveTextBlock)?.Text; + var choiceSet = adaptiveCard.Body[3] as AdaptiveChoiceSetInput; + + if (choiceSet?.Choices?.Count < 3) + { + throw new Exception("Adaptive Card Body[3] contains too few choice elements"); + } + + return new SampleData + { + Question = userText, + MultiSelect = choiceSet.IsMultiSelect ? "true" : "false", + Option1 = choiceSet.Choices[0].Title, + Option2 = choiceSet.Choices[1].Title, + Option3 = choiceSet.Choices[2].Title, + }; + } + + public static AdaptiveCard CreateAdaptiveCardEditor(SampleData sampleData = null) + { + var cardData = sampleData ?? new SampleData(); + + return new AdaptiveCard(new AdaptiveSchemaVersion(1, 2)) + { + Body = new List + { + new AdaptiveTextBlock("This is an Adaptive Card within a Task Module") + { + Weight = AdaptiveTextWeight.Bolder, + }, + new AdaptiveTextBlock("Enter text for Question:"), + new AdaptiveTextInput() { Id = "Question", Placeholder = "Question text here", Value = cardData.Question }, + new AdaptiveTextBlock("Options for Question:"), + new AdaptiveTextBlock("Is Multi-Select:"), + new AdaptiveChoiceSetInput + { + Type = AdaptiveChoiceSetInput.TypeName, + Id = "MultiSelect", + Value = cardData.MultiSelect, + IsMultiSelect = false, + Choices = new List + { + new AdaptiveChoice() { Title = "True", Value = "true" }, + new AdaptiveChoice() { Title = "False", Value = "false" }, + }, + }, + new AdaptiveTextInput() { Id = "Option1", Placeholder = "Option 1 here", Value = cardData.Option1 }, + new AdaptiveTextInput() { Id = "Option2", Placeholder = "Option 2 here", Value = cardData.Option2 }, + new AdaptiveTextInput() { Id = "Option3", Placeholder = "Option 3 here", Value = cardData.Option3 }, + }, + Actions = new List + { + new AdaptiveSubmitAction + { + Type = AdaptiveSubmitAction.TypeName, + Title = "Submit", + Data = new JObject { { "submitLocation", "messagingExtensionFetchTask" } }, + }, + }, + }; + } + + public static AdaptiveCard CreateAdaptiveCard(SampleData data) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + return new AdaptiveCard(new AdaptiveSchemaVersion(1, 2)) + { + Body = new List + { + new AdaptiveTextBlock("Adaptive Card from Task Module") { Weight = AdaptiveTextWeight.Bolder }, + new AdaptiveTextBlock($"{data.Question}") { Id = "Question" }, + new AdaptiveTextInput() { Id = "Answer", Placeholder = "Answer here..." }, + new AdaptiveChoiceSetInput + { + Type = AdaptiveChoiceSetInput.TypeName, + Id = "Choices", + IsMultiSelect = bool.Parse(data.MultiSelect), + Choices = new List + { + new AdaptiveChoice() { Title = data.Option1, Value = data.Option1 }, + new AdaptiveChoice() { Title = data.Option2, Value = data.Option2 }, + new AdaptiveChoice() { Title = data.Option3, Value = data.Option3 }, + }, + }, + }, + Actions = new List + { + new AdaptiveSubmitAction + { + Type = AdaptiveSubmitAction.TypeName, + Title = "Submit", + Data = new JObject { { "submitLocation", "messagingExtensionSubmit" } }, + }, + }, + }; + } + + public static HeroCard CreateHeroCard() + { + var heroCard = new HeroCard + { + Title = "BotFramework Hero Card", + Subtitle = "Microsoft Bot Framework", + Text = "Build and connect intelligent bots to interact with your users naturally wherever they are," + + " from text/sms to Skype, Slack, Office 365 mail and other popular services.", + Images = new List { new CardImage("https://sec.ch9.ms/ch9/7ff5/e07cfef0-aa3b-40bb-9baa-7c9ef8ff7ff5/buildreactionbotframework_960.jpg") }, + Buttons = new List { new CardAction(ActionTypes.OpenUrl, "Get Started", value: "https://docs.microsoft.com/bot-framework") }, + }; + + return heroCard; + } + + public static HeroCard CreateHeroCard(string type) + { + var heroCard = new HeroCard + { + Title = "BotFramework Hero Card", + Subtitle = "Microsoft Bot Framework", + Text = "Build and connect intelligent bots to interact with your users naturally wherever they are," + + " from text/sms to Skype, Slack, Office 365 mail and other popular services.", + Images = new List { new CardImage("https://sec.ch9.ms/ch9/7ff5/e07cfef0-aa3b-40bb-9baa-7c9ef8ff7ff5/buildreactionbotframework_960.jpg") }, + Buttons = new List { new CardAction(ActionTypes.OpenUrl, "Get Started", value: "https://docs.microsoft.com/bot-framework") }, + }; + + return heroCard; + } + + public static ThumbnailCard CreateThumbnailCard() + { + var heroCard = new ThumbnailCard + { + Title = "BotFramework Thumbnail Card", + Subtitle = "Microsoft Bot Framework", + Text = "Build and connect intelligent bots to interact with your users naturally wherever they are," + + " from text/sms to Skype, Slack, Office 365 mail and other popular services.", + Images = new List { new CardImage("https://sec.ch9.ms/ch9/7ff5/e07cfef0-aa3b-40bb-9baa-7c9ef8ff7ff5/buildreactionbotframework_960.jpg") }, + Buttons = new List { new CardAction(ActionTypes.OpenUrl, "Get Started", value: "https://docs.microsoft.com/bot-framework") }, + }; + + return heroCard; + } + + public static ReceiptCard CreateReceiptCard() + { + var receiptCard = new ReceiptCard + { + Title = "John Doe", + Facts = new List { new Fact("Order Number", "1234"), new Fact("Payment Method", "VISA 5555-****") }, + Items = new List + { + new ReceiptItem( + "Data Transfer", + price: "$ 38.45", + quantity: "368", + image: new CardImage(url: "https://github.com/amido/azure-vector-icons/raw/master/renders/traffic-manager.png")), + new ReceiptItem( + "App Service", + price: "$ 45.00", + quantity: "720", + image: new CardImage(url: "https://github.com/amido/azure-vector-icons/raw/master/renders/cloud-service.png")), + }, + Tax = "$ 7.50", + Total = "$ 90.95", + Buttons = new List + { + new CardAction( + ActionTypes.OpenUrl, + "More information", + "https://account.windowsazure.com/content/6.10.1.38-.8225.160809-1618/aux-pre/images/offer-icon-freetrial.png", + value: "https://azure.microsoft.com/en-us/pricing/"), + }, + }; + + return receiptCard; + } + + public static SigninCard CreateSigninCard() + { + var signinCard = new SigninCard + { + Text = "BotFramework Sign-in Card", + Buttons = new List { new CardAction(ActionTypes.Signin, "Sign-in", value: "https://login.microsoftonline.com/") }, + }; + + return signinCard; + } + + public static O365ConnectorCard CreateSampleO365ConnectorCard() + { + var actionCard1 = new O365ConnectorCardActionCard( + O365ConnectorCardActionCard.Type, + "Multiple Choice", + "card-1", + new List + { + new O365ConnectorCardMultichoiceInput( + O365ConnectorCardMultichoiceInput.Type, + "list-1", + true, + "Pick multiple options", + null, + new List + { + new O365ConnectorCardMultichoiceInputChoice("Choice 1", "1"), + new O365ConnectorCardMultichoiceInputChoice("Choice 2", "2"), + new O365ConnectorCardMultichoiceInputChoice("Choice 3", "3") + }, + "expanded", + true), + new O365ConnectorCardMultichoiceInput( + O365ConnectorCardMultichoiceInput.Type, + "list-2", + true, + "Pick multiple options", + null, + new List + { + new O365ConnectorCardMultichoiceInputChoice("Choice 4", "4"), + new O365ConnectorCardMultichoiceInputChoice("Choice 5", "5"), + new O365ConnectorCardMultichoiceInputChoice("Choice 6", "6") + }, + "compact", + true), + new O365ConnectorCardMultichoiceInput( + O365ConnectorCardMultichoiceInput.Type, + "list-3", + false, + "Pick an option", + null, + new List + { + new O365ConnectorCardMultichoiceInputChoice("Choice a", "a"), + new O365ConnectorCardMultichoiceInputChoice("Choice b", "b"), + new O365ConnectorCardMultichoiceInputChoice("Choice c", "c") + }, + "expanded", + false), + new O365ConnectorCardMultichoiceInput( + O365ConnectorCardMultichoiceInput.Type, + "list-4", + false, + "Pick an option", + null, + new List + { + new O365ConnectorCardMultichoiceInputChoice("Choice x", "x"), + new O365ConnectorCardMultichoiceInputChoice("Choice y", "y"), + new O365ConnectorCardMultichoiceInputChoice("Choice z", "z") + }, + "compact", + false) + }, + new List + { + new O365ConnectorCardHttpPOST( + O365ConnectorCardHttpPOST.Type, + "Send", + "card-1-btn-1", + @"{""list1"":""{{list-1.value}}"", ""list2"":""{{list-2.value}}"", ""list3"":""{{list-3.value}}"", ""list4"":""{{list-4.value}}""}") + }); + + var actionCard2 = new O365ConnectorCardActionCard( + O365ConnectorCardActionCard.Type, + "Text Input", + "card-2", + new List + { + new O365ConnectorCardTextInput( + O365ConnectorCardTextInput.Type, + "text-1", + false, + "multiline, no maxLength", + null, + true, + null), + new O365ConnectorCardTextInput( + O365ConnectorCardTextInput.Type, + "text-2", + false, + "single line, no maxLength", + null, + false, + null), + new O365ConnectorCardTextInput( + O365ConnectorCardTextInput.Type, + "text-3", + true, + "multiline, max len = 10, isRequired", + null, + true, + 10), + new O365ConnectorCardTextInput( + O365ConnectorCardTextInput.Type, + "text-4", + true, + "single line, max len = 10, isRequired", + null, + false, + 10) + }, + new List + { + new O365ConnectorCardHttpPOST( + O365ConnectorCardHttpPOST.Type, + "Send", + "card-2-btn-1", + @"{""text1"":""{{text-1.value}}"", ""text2"":""{{text-2.value}}"", ""text3"":""{{text-3.value}}"", ""text4"":""{{text-4.value}}""}") + }); + + var actionCard3 = new O365ConnectorCardActionCard( + O365ConnectorCardActionCard.Type, + "Date Input", + "card-3", + new List + { + new O365ConnectorCardDateInput( + O365ConnectorCardDateInput.Type, + "date-1", + true, + "date with time", + null, + true), + new O365ConnectorCardDateInput( + O365ConnectorCardDateInput.Type, + "date-2", + false, + "date only", + null, + false) + }, + new List + { + new O365ConnectorCardHttpPOST( + O365ConnectorCardHttpPOST.Type, + "Send", + "card-3-btn-1", + @"{""date1"":""{{date-1.value}}"", ""date2"":""{{date-2.value}}""}") + }); + + var section = new O365ConnectorCardSection( + "**section title**", + "section text", + "activity title", + "activity subtitle", + "activity text", + "http://connectorsdemo.azurewebsites.net/images/MSC12_Oscar_002.jpg", + "avatar", + true, + new List + { + new O365ConnectorCardFact("Fact name 1", "Fact value 1"), + new O365ConnectorCardFact("Fact name 2", "Fact value 2"), + }, + new List + { + new O365ConnectorCardImage + { + Image = "http://connectorsdemo.azurewebsites.net/images/MicrosoftSurface_024_Cafe_OH-06315_VS_R1c.jpg", + Title = "image 1" + }, + new O365ConnectorCardImage + { + Image = "http://connectorsdemo.azurewebsites.net/images/WIN12_Scene_01.jpg", + Title = "image 2" + }, + new O365ConnectorCardImage + { + Image = "http://connectorsdemo.azurewebsites.net/images/WIN12_Anthony_02.jpg", + Title = "image 3" + } + }); + + var card = new O365ConnectorCard() + { + Summary = "O365 card summary", + ThemeColor = "#E67A9E", + Title = "card title", + Text = "card text", + Sections = new List { section }, + PotentialAction = new List + { + actionCard1, + actionCard2, + actionCard3, + new O365ConnectorCardViewAction( + O365ConnectorCardViewAction.Type, + "View Action", + null, + new List + { + "http://microsoft.com" + }), + new O365ConnectorCardOpenUri( + O365ConnectorCardOpenUri.Type, + "Open Uri", + "open-uri", + new List + { + new O365ConnectorCardOpenUriTarget + { + Os = "default", + Uri = "http://microsoft.com" + }, + new O365ConnectorCardOpenUriTarget + { + Os = "iOS", + Uri = "http://microsoft.com" + }, + new O365ConnectorCardOpenUriTarget + { + Os = "android", + Uri = "http://microsoft.com" + }, + new O365ConnectorCardOpenUriTarget + { + Os = "windows", + Uri = "http://microsoft.com" + } + }) + } + }; + + return card; + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Cards/ChannelSupportedCards.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Cards/ChannelSupportedCards.cs new file mode 100644 index 0000000000..426c3c118b --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Cards/ChannelSupportedCards.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using Microsoft.Bot.Connector; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Cards +{ + public static class ChannelSupportedCards + { + /// + /// This tracks what cards are not supported in a given channel. + /// + private static readonly Dictionary> UnsupportedChannelCards = new Dictionary> + { + { + Channels.Emulator, new List + { + CardOptions.AdaptiveCardTeamsTaskModule, + CardOptions.AdaptiveUpdate, + CardOptions.O365, + CardOptions.TeamsFileConsent + } + }, + { Channels.Directline, new List { CardOptions.AdaptiveUpdate } }, + { + Channels.Telegram, new List + { + CardOptions.AdaptiveCardBotAction, + CardOptions.AdaptiveCardTeamsTaskModule, + CardOptions.AdaptiveCardSubmitAction, + CardOptions.List, + CardOptions.TeamsFileConsent + } + } + }; + + /// + /// This let's you know if a card is supported in a given channel. + /// + /// Bot Connector Channel. + /// Card Option to be checked. + /// A bool if the card is supported in the channel. + public static bool IsCardSupported(string channel, CardOptions type) + { + if (UnsupportedChannelCards.ContainsKey(channel)) + { + if (UnsupportedChannelCards[channel].Contains(type)) + { + return false; + } + } + + return true; + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Cards/Files/buildreactionbotframework.jpg b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Cards/Files/buildreactionbotframework.jpg new file mode 100644 index 0000000000..f410fc137e Binary files /dev/null and b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Cards/Files/buildreactionbotframework.jpg differ diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Cards/Files/music.mp3 b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Cards/Files/music.mp3 new file mode 100644 index 0000000000..b4ff6ee30f Binary files /dev/null and b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Cards/Files/music.mp3 differ diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Cards/Files/teams-logo.png b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Cards/Files/teams-logo.png new file mode 100644 index 0000000000..78b0a0c308 Binary files /dev/null and b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Cards/Files/teams-logo.png differ diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Cards/SampleData.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Cards/SampleData.cs new file mode 100644 index 0000000000..13b7d9d744 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Cards/SampleData.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Cards +{ + public class SampleData + { + public SampleData() + { + MultiSelect = "true"; + } + + public string Question { get; set; } + + public string MultiSelect { get; set; } + + public string Option1 { get; set; } + + public string Option2 { get; set; } + + public string Option3 { get; set; } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Delete/DeleteDialog.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Delete/DeleteDialog.cs new file mode 100644 index 0000000000..616ddfd70c --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Delete/DeleteDialog.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Connector; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Delete +{ + public class DeleteDialog : ComponentDialog + { + private readonly List _deleteSupported = new List + { + Channels.Msteams, + Channels.Slack, + Channels.Telegram + }; + + public DeleteDialog() + : base(nameof(DeleteDialog)) + { + AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[] { HandleDeleteDialog })); + InitialDialogId = nameof(WaterfallDialog); + } + + private async Task HandleDeleteDialog(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var channel = stepContext.Context.Activity.ChannelId; + if (_deleteSupported.Contains(channel)) + { + var id = await stepContext.Context.SendActivityAsync(MessageFactory.Text("I will delete this message in 5 seconds"), cancellationToken); + await Task.Delay(5000, cancellationToken); + await stepContext.Context.DeleteActivityAsync(id.Id, cancellationToken); + } + else + { + await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Delete is not supported in the {channel} channel."), cancellationToken); + } + + return new DialogTurnResult(DialogTurnStatus.Complete); + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/FileUpload/FileUploadDialog.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/FileUpload/FileUploadDialog.cs new file mode 100644 index 0000000000..67a3885d27 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/FileUpload/FileUploadDialog.cs @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.IO; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Schema; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.FileUpload +{ + public class FileUploadDialog : ComponentDialog + { + public FileUploadDialog() + : base(nameof(FileUploadDialog)) + { + AddDialog(new AttachmentPrompt(nameof(AttachmentPrompt))); + AddDialog(new ConfirmPrompt(nameof(ConfirmPrompt))); + AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[] { PromptUploadStepAsync, HandleAttachmentStepAsync, FinalStepAsync })); + + InitialDialogId = nameof(WaterfallDialog); + } + + private async Task PromptUploadStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var promptOptions = new PromptOptions + { + Prompt = MessageFactory.Text("Please upload a file to continue."), + RetryPrompt = MessageFactory.Text("You must upload a file."), + }; + + return await stepContext.PromptAsync(nameof(AttachmentPrompt), promptOptions, cancellationToken); + } + + private async Task HandleAttachmentStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var fileText = string.Empty; + + foreach (var file in stepContext.Context.Activity.Attachments) + { + var remoteFileUrl = file.ContentUrl; + var localFileName = Path.Combine(Path.GetTempPath(), file.Name); + string fileContent; + + using (var webClient = new WebClient()) + { + webClient.DownloadFile(remoteFileUrl, localFileName); + using var reader = new StreamReader(localFileName); + fileContent = await reader.ReadToEndAsync(); + } + + fileText += $"Attachment \"{file.Name}\" has been received.\r\n"; + fileText += $"File content: {fileContent}\r\n"; + } + + await stepContext.Context.SendActivityAsync(MessageFactory.Text(fileText), cancellationToken); + + // Ask to upload another file or end. + const string messageText = "Do you want to upload another file?"; + const string repromptMessageText = "That's an invalid choice."; + var options = new PromptOptions + { + Prompt = MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput), + RetryPrompt = MessageFactory.Text(repromptMessageText, repromptMessageText, InputHints.ExpectingInput) + }; + + return await stepContext.PromptAsync(nameof(ConfirmPrompt), options, cancellationToken); + } + + private async Task FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var tryAnother = (bool)stepContext.Result; + if (tryAnother) + { + return await stepContext.ReplaceDialogAsync(InitialDialogId, cancellationToken: cancellationToken); + } + + return new DialogTurnResult(DialogTurnStatus.Complete); + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/MessageWithAttachment/MessageWithAttachmentDialog.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/MessageWithAttachment/MessageWithAttachmentDialog.cs new file mode 100644 index 0000000000..443413e20d --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/MessageWithAttachment/MessageWithAttachmentDialog.cs @@ -0,0 +1,118 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Builder.Dialogs.Choices; +using Microsoft.Bot.Schema; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.MessageWithAttachment +{ + public class MessageWithAttachmentDialog : ComponentDialog + { + private const string Picture = "architecture-resize.png"; + private readonly Uri _serverUrl; + + public MessageWithAttachmentDialog(Uri serverUrl) + : base(nameof(MessageWithAttachmentDialog)) + { + _serverUrl = serverUrl; + AddDialog(new ChoicePrompt(nameof(ChoicePrompt))); + AddDialog(new ConfirmPrompt(nameof(ConfirmPrompt))); + AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[] { SelectAttachmentTypeAsync, SendActivityWithAttachmentAsync, FinalStepAsync })); + InitialDialogId = nameof(WaterfallDialog); + } + + private async Task SelectAttachmentTypeAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + const string messageText = "What attachment type do you want?"; + const string repromptMessageText = "That was not a valid choice, please select a valid card type."; + var options = new PromptOptions + { + Prompt = MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput), + RetryPrompt = MessageFactory.Text(repromptMessageText, repromptMessageText, InputHints.ExpectingInput), + Choices = new List + { + new Choice("Inline"), + new Choice("Internet") + } + }; + + // Ask the user to enter their name. + return await stepContext.PromptAsync(nameof(ChoicePrompt), options, cancellationToken); + } + + private async Task SendActivityWithAttachmentAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var attachmentType = ((FoundChoice)stepContext.Result).Value.ToLowerInvariant(); + var reply = new Activity(ActivityTypes.Message) { InputHint = InputHints.IgnoringInput }; + switch (attachmentType) + { + case "inline": + reply.Text = "This is an inline attachment."; + reply.Attachments = new List { GetInlineAttachment() }; + break; + + case "internet": + reply.Text = "This is an attachment from a HTTP URL."; + reply.Attachments = new List { GetInternetAttachment() }; + break; + + default: + throw new InvalidOperationException($"Invalid card type {attachmentType}"); + } + + await stepContext.Context.SendActivityAsync(reply, cancellationToken); + + // Ask to submit another or end. + const string messageText = "Do you want another type of attachment?"; + const string repromptMessageText = "That's an invalid choice."; + var options = new PromptOptions + { + Prompt = MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput), + RetryPrompt = MessageFactory.Text(repromptMessageText, repromptMessageText, InputHints.ExpectingInput), + }; + + return await stepContext.PromptAsync(nameof(ConfirmPrompt), options, cancellationToken); + } + + private async Task FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var tryAnother = (bool)stepContext.Result; + if (tryAnother) + { + return await stepContext.ReplaceDialogAsync(InitialDialogId, cancellationToken: cancellationToken); + } + + return new DialogTurnResult(DialogTurnStatus.Complete); + } + + private Attachment GetInlineAttachment() + { + var imagePath = Path.Combine(Environment.CurrentDirectory, "wwwroot", "images", Picture); + var imageData = Convert.ToBase64String(File.ReadAllBytes(imagePath)); + + return new Attachment + { + Name = $"Files/{Picture}", + ContentType = "image/png", + ContentUrl = $"data:image/png;base64,{imageData}", + }; + } + + private Attachment GetInternetAttachment() + { + return new Attachment + { + Name = $"Files/{Picture}", + ContentType = "image/png", + ContentUrl = $"{_serverUrl}images/{Picture}", + }; + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Proactive/ContinuationParameters.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Proactive/ContinuationParameters.cs new file mode 100644 index 0000000000..ef2970a643 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Proactive/ContinuationParameters.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Security.Principal; +using Microsoft.Bot.Schema; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Proactive +{ + /// + /// Stores the information needed to resume a conversation when a proactive message arrives. + /// + public class ContinuationParameters + { + public IIdentity ClaimsIdentity { get; set; } + + public string OAuthScope { get; set; } + + public ConversationReference ConversationReference { get; set; } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Proactive/WaitForProactiveDialog.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Proactive/WaitForProactiveDialog.cs new file mode 100644 index 0000000000..aa6cf4b55a --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Proactive/WaitForProactiveDialog.cs @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Concurrent; +using System.Security.Principal; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Schema; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Proactive +{ + public class WaitForProactiveDialog : Dialog + { + // Message to send to users when the bot receives a Conversation Update event + private const string NotifyMessage = "Navigate to {0}api/notify?user={1} to proactively message the user."; + private readonly ConcurrentDictionary _continuationParametersStore; + + private readonly Uri _serverUrl; + + public WaitForProactiveDialog(IHttpContextAccessor httpContextAccessor, ConcurrentDictionary continuationParametersStore) + { + _continuationParametersStore = continuationParametersStore; + _serverUrl = new Uri($"{httpContextAccessor.HttpContext.Request.Scheme}://{httpContextAccessor.HttpContext.Request.Host.Value}"); + } + + public override async Task BeginDialogAsync(DialogContext dc, object options = null, CancellationToken cancellationToken = default) + { + // Store a reference to the conversation. + AddOrUpdateContinuationParameters(dc.Context); + + // Render message with continuation link. + await dc.Context.SendActivityAsync(MessageFactory.Text(string.Format(NotifyMessage, _serverUrl, dc.Context.Activity.From.Id)), cancellationToken); + return EndOfTurn; + } + + public override async Task ContinueDialogAsync(DialogContext dc, CancellationToken cancellationToken = default) + { + if (dc.Context.Activity.Type == ActivityTypes.Event && dc.Context.Activity.Name == ActivityEventNames.ContinueConversation) + { + // We continued the conversation, forget the proactive reference. + _continuationParametersStore.TryRemove(dc.Context.Activity.From.Id, out _); + + // The continue conversation activity comes from the ProactiveController when the notification is received + await dc.Context.SendActivityAsync("We received a proactive message, ending the dialog", cancellationToken: cancellationToken); + + // End the dialog so the host gets an EoC + return new DialogTurnResult(DialogTurnStatus.Complete); + } + + // Keep waiting for a call to the ProactiveController. + await dc.Context.SendActivityAsync($"We are waiting for a proactive message. {string.Format(NotifyMessage, _serverUrl, dc.Context.Activity.From.Id)}", cancellationToken: cancellationToken); + + return EndOfTurn; + } + + /// + /// Helper to extract and store parameters we need to continue a conversation from a proactive message. + /// + /// A turnContext instance with the parameters we need. + private void AddOrUpdateContinuationParameters(ITurnContext turnContext) + { + var continuationParameters = new ContinuationParameters + { + ClaimsIdentity = turnContext.TurnState.Get(BotAdapter.BotIdentityKey), + ConversationReference = turnContext.Activity.GetConversationReference(), + OAuthScope = turnContext.TurnState.Get(BotAdapter.OAuthScopeKey) + }; + + _continuationParametersStore.AddOrUpdate(turnContext.Activity.From.Id, continuationParameters, (_, __) => continuationParameters); + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Sso/SsoSkillDialog.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Sso/SsoSkillDialog.cs new file mode 100644 index 0000000000..90add0aca3 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Sso/SsoSkillDialog.cs @@ -0,0 +1,119 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Builder.Dialogs.Choices; +using Microsoft.Bot.Schema; +using Microsoft.Extensions.Configuration; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Sso +{ + public class SsoSkillDialog : ComponentDialog + { + private readonly string _connectionName; + + public SsoSkillDialog(IConfiguration configuration) + : base(nameof(SsoSkillDialog)) + { + _connectionName = configuration.GetSection("SsoConnectionName")?.Value; + AddDialog(new SsoSkillSignInDialog(_connectionName)); + AddDialog(new ChoicePrompt("ActionStepPrompt")); + + var waterfallSteps = new WaterfallStep[] + { + PromptActionStepAsync, + HandleActionStepAsync, + PromptFinalStepAsync + }; + + AddDialog(new WaterfallDialog(nameof(WaterfallDialog), waterfallSteps)); + + InitialDialogId = nameof(WaterfallDialog); + } + + private async Task PromptActionStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + const string messageText = "What SSO action would you like to perform on the skill?"; + const string repromptMessageText = "That was not a valid choice, please select a valid choice."; + var options = new PromptOptions + { + Prompt = MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput), + RetryPrompt = MessageFactory.Text(repromptMessageText, repromptMessageText, InputHints.ExpectingInput), + Choices = await GetPromptChoicesAsync(stepContext, cancellationToken) + }; + + // Prompt the user to select a skill. + return await stepContext.PromptAsync("ActionStepPrompt", options, cancellationToken); + } + + private async Task> GetPromptChoicesAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var promptChoices = new List(); + var adapter = (IUserTokenProvider)stepContext.Context.Adapter; + var token = await adapter.GetUserTokenAsync(stepContext.Context, _connectionName, null, cancellationToken); + + if (token == null) + { + promptChoices.Add(new Choice("Login")); + } + else + { + promptChoices.Add(new Choice("Logout")); + promptChoices.Add(new Choice("Show token")); + } + + promptChoices.Add(new Choice("End")); + + return promptChoices; + } + + private async Task HandleActionStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var action = ((FoundChoice)stepContext.Result).Value.ToLowerInvariant(); + + switch (action) + { + case "login": + return await stepContext.BeginDialogAsync(nameof(SsoSkillSignInDialog), null, cancellationToken); + + case "logout": + var adapter = (IUserTokenProvider)stepContext.Context.Adapter; + await adapter.SignOutUserAsync(stepContext.Context, _connectionName, cancellationToken: cancellationToken); + await stepContext.Context.SendActivityAsync("You have been signed out.", cancellationToken: cancellationToken); + return await stepContext.NextAsync(cancellationToken: cancellationToken); + + case "show token": + var tokenProvider = (IUserTokenProvider)stepContext.Context.Adapter; + var token = await tokenProvider.GetUserTokenAsync(stepContext.Context, _connectionName, null, cancellationToken); + if (token == null) + { + await stepContext.Context.SendActivityAsync("User has no cached token.", cancellationToken: cancellationToken); + } + else + { + await stepContext.Context.SendActivityAsync($"Here is your current SSO token: {token.Token}", cancellationToken: cancellationToken); + } + + return await stepContext.NextAsync(cancellationToken: cancellationToken); + + case "end": + return new DialogTurnResult(DialogTurnStatus.Complete); + + default: + // This should never be hit since the previous prompt validates the choice + throw new InvalidOperationException($"Unrecognized action: {action}"); + } + } + + private async Task PromptFinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + // Restart the dialog (we will exit when the user says end) + return await stepContext.ReplaceDialogAsync(InitialDialogId, null, cancellationToken); + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Sso/SsoSkillSignInDialog.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Sso/SsoSkillSignInDialog.cs new file mode 100644 index 0000000000..61c57301dc --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Sso/SsoSkillSignInDialog.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Schema; +using Microsoft.Extensions.Configuration; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Sso +{ + public class SsoSkillSignInDialog : ComponentDialog + { + public SsoSkillSignInDialog(string connectionName) + : base(nameof(SsoSkillSignInDialog)) + { + AddDialog(new OAuthPrompt(nameof(OAuthPrompt), new OAuthPromptSettings + { + ConnectionName = connectionName, + Text = "Sign in to the Skill using AAD", + Title = "Sign In" + })); + AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[] { SignInStepAsync, DisplayTokenAsync })); + InitialDialogId = nameof(WaterfallDialog); + } + + private async Task SignInStepAsync(WaterfallStepContext context, CancellationToken cancellationToken) + { + // This prompt won't show if the user is signed in to the host using SSO. + return await context.BeginDialogAsync(nameof(OAuthPrompt), null, cancellationToken); + } + + private async Task DisplayTokenAsync(WaterfallStepContext context, CancellationToken cancellationToken) + { + if (!(context.Result is TokenResponse result)) + { + await context.Context.SendActivityAsync("No token was provided for the skill.", cancellationToken: cancellationToken); + } + else + { + await context.Context.SendActivityAsync($"Here is your token for the skill: {result.Token}", cancellationToken: cancellationToken); + } + + return await context.EndDialogAsync(null, cancellationToken); + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Update/UpdateDialog.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Update/UpdateDialog.cs new file mode 100644 index 0000000000..1ec0a5217d --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Dialogs/Update/UpdateDialog.cs @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Connector; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Update +{ + public class UpdateDialog : ComponentDialog + { + private readonly List _updateSupported = new List + { + Channels.Msteams, + Channels.Slack, + Channels.Telegram + }; + + private readonly Dictionary _updateTracker; + + public UpdateDialog() + : base(nameof(UpdateDialog)) + { + _updateTracker = new Dictionary(); + AddDialog(new ConfirmPrompt(nameof(ConfirmPrompt))); + AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[] { HandleUpdateDialog, FinalStepAsync })); + InitialDialogId = nameof(WaterfallDialog); + } + + private async Task HandleUpdateDialog(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var channel = stepContext.Context.Activity.ChannelId; + if (_updateSupported.Contains(channel)) + { + if (_updateTracker.ContainsKey(stepContext.Context.Activity.Conversation.Id)) + { + var conversationId = stepContext.Context.Activity.Conversation.Id; + var tuple = _updateTracker[conversationId]; + var activity = MessageFactory.Text($"This message has been updated {tuple.Item2} time(s)."); + tuple.Item2 += 1; + activity.Id = tuple.Item1; + _updateTracker[conversationId] = tuple; + await stepContext.Context.UpdateActivityAsync(activity, cancellationToken); + } + else + { + var id = await stepContext.Context.SendActivityAsync(MessageFactory.Text("Here is the original activity"), cancellationToken); + _updateTracker.Add(stepContext.Context.Activity.Conversation.Id, (id.Id, 1)); + } + } + else + { + await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Delete is not supported in the {channel} channel."), cancellationToken); + return new DialogTurnResult(DialogTurnStatus.Complete); + } + + // Ask if we want to update the activity again. + const string messageText = "Do you want to update the activity again?"; + const string repromptMessageText = "Please select a valid answer"; + var options = new PromptOptions + { + Prompt = MessageFactory.Text(messageText, messageText), + RetryPrompt = MessageFactory.Text(repromptMessageText, repromptMessageText), + }; + + // Ask the user to enter their name. + return await stepContext.PromptAsync(nameof(ConfirmPrompt), options, cancellationToken); + } + + private async Task FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var tryAnother = (bool)stepContext.Result; + if (tryAnother) + { + return await stepContext.ReplaceDialogAsync(InitialDialogId, cancellationToken: cancellationToken); + } + + _updateTracker.Remove(stepContext.Context.Activity.Conversation.Id); + return new DialogTurnResult(DialogTurnStatus.Complete); + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Middleware/SsoSaveStateMiddleware.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Middleware/SsoSaveStateMiddleware.cs new file mode 100644 index 0000000000..f0fb5e5bad --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Middleware/SsoSaveStateMiddleware.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Schema; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Middleware +{ + /// + /// A middleware that ensures conversation state is saved when an OAuthCard is returned by the skill. + /// + /// + /// In SSO, the host will send an Invoke with the token if SSO is enabled. + /// This middleware saves the state of the bot before sending out the SSO card to ensure the dialog state + /// is persisted and in the right state if an InvokeActivity comes back from the Host with the token. + /// + public class SsoSaveStateMiddleware : IMiddleware + { + private readonly ConversationState _conversationState; + + public SsoSaveStateMiddleware(ConversationState conversationState) + { + _conversationState = conversationState; + } + + public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken = new CancellationToken()) + { + // Register outgoing handler. + turnContext.OnSendActivities(OutgoingHandler); + + // Continue processing messages. + await next(cancellationToken); + } + + private async Task OutgoingHandler(ITurnContext turnContext, List activities, Func> next) + { + foreach (var activity in activities) + { + // Check if any of the outgoing activities has an OAuthCard. + if (activity.Attachments != null && activity.Attachments.Any(attachment => attachment.ContentType == OAuthCard.ContentType)) + { + // Save any state changes so the dialog stack is ready for SSO exchanges. + await _conversationState.SaveChangesAsync(turnContext, false, CancellationToken.None); + } + } + + return await next(); + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Program.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Program.cs new file mode 100644 index 0000000000..31e4c693d2 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Program.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Properties/launchSettings.json b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Properties/launchSettings.json new file mode 100644 index 0000000000..f3bdbffd6c --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Properties/launchSettings.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:35420/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": false, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "WaterfallSkillBotDotNet": { + "commandName": "Project", + "launchBrowser": true, + "applicationUrl": "http://localhost:35420/", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/SkillAdapterWithErrorHandler.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/SkillAdapterWithErrorHandler.cs new file mode 100644 index 0000000000..a4098b193c --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/SkillAdapterWithErrorHandler.cs @@ -0,0 +1,100 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Bot.Builder.TraceExtensions; +using Microsoft.Bot.Connector.Authentication; +using Microsoft.Bot.Schema; +using Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Middleware; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot +{ + public class SkillAdapterWithErrorHandler : BotFrameworkHttpAdapter + { + private readonly ConversationState _conversationState; + private readonly ILogger _logger; + + public SkillAdapterWithErrorHandler(IConfiguration configuration, ICredentialProvider credentialProvider, AuthenticationConfiguration authConfig, ILogger logger, ConversationState conversationState) + : base(configuration, credentialProvider, authConfig, logger: logger) + { + _conversationState = conversationState ?? throw new ArgumentNullException(nameof(conversationState)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + OnTurnError = HandleTurnError; + + // Add autosave middleware for SSO. + Use(new SsoSaveStateMiddleware(_conversationState)); + } + + private async Task HandleTurnError(ITurnContext turnContext, Exception exception) + { + // Log any leaked exception from the application. + _logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}"); + + await SendErrorMessageAsync(turnContext, exception); + await SendEoCToParentAsync(turnContext, exception); + await ClearConversationStateAsync(turnContext); + } + + private async Task SendErrorMessageAsync(ITurnContext turnContext, Exception exception) + { + try + { + // Send a message to the user. + var errorMessageText = "The skill encountered an error or bug."; + var errorMessage = MessageFactory.Text(errorMessageText + Environment.NewLine + exception, errorMessageText, InputHints.IgnoringInput); + errorMessage.Value = exception; + await turnContext.SendActivityAsync(errorMessage); + + errorMessageText = "To continue to run this bot, please fix the bot source code."; + errorMessage = MessageFactory.Text(errorMessageText, errorMessageText, InputHints.ExpectingInput); + await turnContext.SendActivityAsync(errorMessage); + + // Send a trace activity, which will be displayed in the Bot Framework Emulator. + // Note: we return the entire exception in the value property to help the developer; + // this should not be done in production. + await turnContext.TraceActivityAsync("OnTurnError Trace", exception.ToString(), "https://www.botframework.com/schemas/error", "TurnError"); + } + catch (Exception ex) + { + _logger.LogError(ex, $"Exception caught in SendErrorMessageAsync : {ex}"); + } + } + + private async Task SendEoCToParentAsync(ITurnContext turnContext, Exception exception) + { + try + { + // Send an EndOfConversation activity to the skill caller with the error to end the conversation, + // and let the caller decide what to do. + var endOfConversation = Activity.CreateEndOfConversationActivity(); + endOfConversation.Code = "SkillError"; + endOfConversation.Text = exception.Message; + await turnContext.SendActivityAsync(endOfConversation); + } + catch (Exception ex) + { + _logger.LogError(ex, $"Exception caught in SendEoCToParentAsync : {ex}"); + } + } + + private async Task ClearConversationStateAsync(ITurnContext turnContext) + { + try + { + // Delete the conversationState for the current conversation to prevent the + // bot from getting stuck in a error-loop caused by being in a bad state. + // ConversationState should be thought of as similar to "cookie-state" for a Web page. + await _conversationState.DeleteAsync(turnContext); + } + catch (Exception ex) + { + _logger.LogError(ex, $"Exception caught on attempting to Delete ConversationState : {ex}"); + } + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Startup.cs b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Startup.cs new file mode 100644 index 0000000000..7405acb74d --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/Startup.cs @@ -0,0 +1,103 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Concurrent; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.BotFramework; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Bot.Builder.Integration.AspNet.Core.Skills; +using Microsoft.Bot.Builder.Skills; +using Microsoft.Bot.Connector.Authentication; +using Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Authentication; +using Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Bots; +using Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs; +using Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Proactive; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers() + .AddNewtonsoftJson(); + + // Configure credentials. + services.AddSingleton(); + if (!string.IsNullOrEmpty(Configuration["ChannelService"])) + { + // Register a ConfigurationChannelProvider -- this is only for Azure Gov. + services.AddSingleton(); + } + + // Register AuthConfiguration to enable custom claim validation. + services.AddSingleton(sp => new AuthenticationConfiguration { ClaimsValidator = new Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Authentication.AllowedCallersClaimsValidator(sp.GetService()) }); + + // Register the Bot Framework Adapter with error handling enabled. + // Note: some classes use the base BotAdapter so we add an extra registration that pulls the same instance. + services.AddSingleton(); + services.AddSingleton(sp => sp.GetService()); + + // Register the skills conversation ID factory, the client and the request handler. + services.AddSingleton(); + services.AddHttpClient(); + services.AddSingleton(); + + // Create the storage we'll be using for User and Conversation state. (Memory is great for testing purposes.) + services.AddSingleton(); + + // Create the Conversation state. (Used by the Dialog system itself.) + services.AddSingleton(); + + // The Dialog that will be run by the bot. + services.AddSingleton(); + + // The Bot needs an HttpClient to download and upload files. + services.AddHttpClient(); + + // Create a global dictionary for our ConversationReferences (used by proactive) + services.AddSingleton>(); + + // Create the bot as a transient. In this case the ASP Controller is expecting an IBot. + services.AddTransient>(); + + // Gives us access to HttpContext so we can create URLs with the host name. + services.AddHttpContextAccessor(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseDefaultFiles() + .UseStaticFiles() + .UseWebSockets() + .UseRouting() + .UseAuthorization() + .UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + + // Uncomment this to support HTTPS. + // app.UseHttpsRedirection(); + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/WaterfallSkillBot.csproj b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/WaterfallSkillBot.csproj new file mode 100644 index 0000000000..a534c34089 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/WaterfallSkillBot.csproj @@ -0,0 +1,46 @@ + + + + netcoreapp3.1 + latest + Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot + Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot + cca62821-0d1d-4b4d-8e5b-8ee934324a2c + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + + + + + + + + + + + + + Always + + + Always + + + PreserveNewest + + + + \ No newline at end of file diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/appsettings.json b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/appsettings.json new file mode 100644 index 0000000000..aeba3b0618 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/appsettings.json @@ -0,0 +1,20 @@ +{ + "MicrosoftAppId": "", + "MicrosoftAppPassword": "", + "ConnectionName": "TestOAuthProvider", + "SsoConnectionName": "", + "ChannelService": "", + // This is a comma separate list with the App IDs that will have access to the skill. + // This setting is used in AllowedCallersClaimsValidator. + // Examples: + // [ "*" ] allows all callers. + // [ "AppId1", "AppId2" ] only allows access to parent bots with "AppId1" and "AppId2". + "AllowedCallers": [ "*" ], + + "SkillHostEndpoint": "http://localhost:35420/api/skills", + "EchoSkillInfo": { + "Id": "EchoSkillBot", + "AppId": "", + "SkillEndpoint": "http://localhost:35400/api/messages" + } +} diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/wwwroot/default.htm b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/wwwroot/default.htm new file mode 100644 index 0000000000..53166d3e75 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/wwwroot/default.htm @@ -0,0 +1,420 @@ + + + + + + + WaterfallSkillBot + + + + + +
+
+
+
WaterfallSkillBot Bot
+
+
+
+
+
Your bot is ready!
+
You can test your bot in the Bot Framework Emulator
+ by connecting to http://localhost:3978/api/messages.
+ +
Visit Azure + Bot Service to register your bot and add it to
+ various channels. The bot's endpoint URL typically looks + like this:
+
https://your_bots_hostname/api/messages
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/wwwroot/images/architecture-resize.png b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/wwwroot/images/architecture-resize.png new file mode 100644 index 0000000000..e65f0f7332 Binary files /dev/null and b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/wwwroot/images/architecture-resize.png differ diff --git a/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/wwwroot/manifests/waterfallskillbot-manifest-1.0.json b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/wwwroot/manifests/waterfallskillbot-manifest-1.0.json new file mode 100644 index 0000000000..414956f361 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot/wwwroot/manifests/waterfallskillbot-manifest-1.0.json @@ -0,0 +1,105 @@ +{ + "$schema": "https://schemas.botframework.com/schemas/skills/v2.1/skill-manifest.json", + "$id": "WaterfallSkillBotDotNet", + "name": "WaterfallSkillBotDotNet", + "version": "1.0", + "description": "This is a skill definition for multiple activity types (implemented using waterfall dialogs).", + "publisherName": "Microsoft", + "privacyUrl": "https://microsoft.com/privacy", + "copyright": "Copyright (c) Microsoft Corporation. All rights reserved.", + "license": "https://github.com/microsoft/BotFramework-FunctionalTests/blob/main/LICENSE", + "tags": [ + "travel", + "weather", + "luis" + ], + "endpoints": [ + { + "name": "default", + "protocol": "BotFrameworkV3", + "description": "Localhost endpoint for the skill (on port 35420)", + "endpointUrl": "http://localhost:35420/api/messages", + "msAppId": "00000000-0000-0000-0000-000000000000" + } + ], + "activities": { + "bookFlight": { + "description": "Books a flight (multi turn).", + "type": "event", + "name": "BookFlight", + "value": { + "$ref": "#/definitions/bookingInfo" + }, + "resultValue": { + "$ref": "#/definitions/bookingInfo" + } + }, + "getWeather": { + "description": "Retrieves and returns the weather for the user's location.", + "type": "event", + "name": "GetWeather", + "value": { + "$ref": "#/definitions/location" + }, + "resultValue": { + "$ref": "#/definitions/weatherReport" + } + }, + "passthroughMessage": { + "type": "message", + "description": "Receives the user's utterance and attempts to resolve it using the skill's LUIS models.", + "value": { + "type": "object" + } + } + }, + "definitions": { + "bookingInfo": { + "type": "object", + "required": [ + "origin" + ], + "properties": { + "origin": { + "type": "string", + "description": "This is the origin city for the flight." + }, + "destination": { + "type": "string", + "description": "This is the destination city for the flight." + }, + "travelDate": { + "type": "string", + "description": "The date for the flight in YYYY-MM-DD format." + } + } + }, + "weatherReport": { + "type": "array", + "description": "Array of forecasts for the next week.", + "items": [ + { + "type": "string" + } + ] + }, + "location": { + "type": "object", + "description": "Location metadata.", + "properties": { + "latitude": { + "type": "number", + "title": "Latitude" + }, + "longitude": { + "type": "number", + "title": "Longitude" + }, + "postalCode": { + "type": "string", + "title": "Postal code" + } + } + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/Composer/Directory.Build.props b/tests/functional/Bots/DotNet/Skills/Composer/Directory.Build.props new file mode 100644 index 0000000000..d4378dfc87 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/Composer/Directory.Build.props @@ -0,0 +1,15 @@ + + + + true + + + + + $(NoWarn);SA1412;NU1701 + + + + + + \ No newline at end of file diff --git a/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/.gitignore b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/.gitignore new file mode 100644 index 0000000000..eaa9cf70cc --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/.gitignore @@ -0,0 +1,5 @@ +# files generated during the lubuild process +# IMPORTANT: In regular composer bots the generated folder should be excluded and regenerated on the build server +# or by the dev running composer locally. But in this case we include it so we don't have to run bf luis:cross-train +# in the build server +# generated/ diff --git a/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/Controllers/BotController.cs b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/Controllers/BotController.cs new file mode 100644 index 0000000000..c9bccdb753 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/Controllers/BotController.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs.Adaptive.Runtime.Settings; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace EchoSkillBotComposer.Controllers +{ + // This ASP Controller is created to handle a request. Dependency Injection will provide the Adapter and IBot + // implementation at runtime. Multiple different IBot implementations running at different endpoints can be + // achieved by specifying a more specific type for the bot constructor argument. + [ApiController] + public class BotController : ControllerBase + { + private readonly Dictionary _adapters = new Dictionary(); + private readonly IBot _bot; + private readonly ILogger _logger; + + public BotController( + IConfiguration configuration, + IEnumerable adapters, + IBot bot, + ILogger logger) + { + _bot = bot ?? throw new ArgumentNullException(nameof(bot)); + _logger = logger; + + var adapterSettings = configuration.GetSection(AdapterSettings.AdapterSettingsKey).Get>() ?? new List(); + adapterSettings.Add(AdapterSettings.CoreBotAdapterSettings); + + foreach (var adapter in adapters ?? throw new ArgumentNullException(nameof(adapters))) + { + var settings = adapterSettings.FirstOrDefault(s => s.Enabled && s.Type == adapter.GetType().FullName); + + if (settings != null) + { + _adapters.Add(settings.Route, adapter); + } + } + } + + [HttpPost] + [HttpGet] + [Route("api/{route}")] + public async Task PostAsync(string route) + { + if (string.IsNullOrEmpty(route)) + { + _logger.LogError($"PostAsync: No route provided."); + throw new ArgumentNullException(nameof(route)); + } + + if (_adapters.TryGetValue(route, out IBotFrameworkHttpAdapter adapter)) + { + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogInformation($"PostAsync: routed '{route}' to {adapter.GetType().Name}"); + } + + // Delegate the processing of the HTTP POST to the appropriate adapter. + // The adapter will invoke the bot. + await adapter.ProcessAsync(Request, Response, _bot).ConfigureAwait(false); + } + else + { + _logger.LogError($"PostAsync: No adapter registered and enabled for route {route}."); + throw new KeyNotFoundException($"No adapter registered and enabled for route {route}."); + } + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/Controllers/SkillController.cs b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/Controllers/SkillController.cs new file mode 100644 index 0000000000..88c36f2b1c --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/Controllers/SkillController.cs @@ -0,0 +1,62 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Bot.Schema; +using Microsoft.Extensions.Logging; + +namespace EchoSkillBotComposer.Controllers +{ + /// + /// A controller that handles skill replies to the bot. + /// + [ApiController] + [Route("api/skills")] + public class SkillController : ChannelServiceController + { + private readonly ILogger _logger; + + public SkillController(ChannelServiceHandlerBase handler, ILogger logger) + : base(handler) + { + _logger = logger; + } + + public override Task ReplyToActivityAsync(string conversationId, string activityId, Activity activity) + { + try + { + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug($"ReplyToActivityAsync: conversationId={conversationId}, activityId={activityId}"); + } + + return base.ReplyToActivityAsync(conversationId, activityId, activity); + } + catch (Exception ex) + { + _logger.LogError(ex, $"ReplyToActivityAsync: {ex}"); + throw; + } + } + + public override Task SendToConversationAsync(string conversationId, Activity activity) + { + try + { + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug($"SendToConversationAsync: conversationId={conversationId}"); + } + + return base.SendToConversationAsync(conversationId, activity); + } + catch (Exception ex) + { + _logger.LogError(ex, $"SendToConversationAsync: {ex}"); + throw; + } + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/EchoSkillBotComposer.botproj b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/EchoSkillBotComposer.botproj new file mode 100644 index 0000000000..247bba8b18 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/EchoSkillBotComposer.botproj @@ -0,0 +1,5 @@ +{ + "$schema": "https://raw.githubusercontent.com/microsoft/BotFramework-Composer/main/Composer/packages/server/schemas/botproject.schema", + "name": "EchoSkillBotComposer", + "skills": {} +} \ No newline at end of file diff --git a/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/EchoSkillBotComposer.csproj b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/EchoSkillBotComposer.csproj new file mode 100644 index 0000000000..d3c03d57e2 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/EchoSkillBotComposer.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp3.1 + OutOfProcess + 720cb418-9c27-47a3-b473-1b8771598297 + + + + PreserveNewest + + + + + + + + + \ No newline at end of file diff --git a/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/Program.cs b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/Program.cs new file mode 100644 index 0000000000..b9cc275c38 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/Program.cs @@ -0,0 +1,33 @@ +using System; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Bot.Builder.Dialogs.Adaptive.Runtime.Extensions; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; + +namespace EchoSkillBotComposer +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureAppConfiguration((hostingContext, builder) => + { + var applicationRoot = AppDomain.CurrentDomain.BaseDirectory; + var environmentName = hostingContext.HostingEnvironment.EnvironmentName; + var settingsDirectory = "settings"; + + builder.AddBotRuntimeConfiguration(applicationRoot, settingsDirectory, environmentName); + + builder.AddCommandLine(args); + }) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } +} \ No newline at end of file diff --git a/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/Properties/launchSettings.json b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/Properties/launchSettings.json new file mode 100644 index 0000000000..5e20497847 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:35410/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": false, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "EchoSkillBotComposerDotNet": { + "commandName": "Project", + "launchBrowser": false, + "applicationUrl": "http://localhost:35410", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/README.md b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/README.md new file mode 100644 index 0000000000..b48822a762 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/README.md @@ -0,0 +1,27 @@ +# Welcome to your new bot + +This bot project was created using the Empty Bot template, and contains a minimal set of files necessary to have a working bot. + +## Next steps + +### Start building your bot + +Composer can help guide you through getting started building your bot. From your bot settings page (the wrench icon on the left navigation rail), click on the rocket-ship icon on the top right for some quick navigation links. + +Another great resource if you're just getting started is the **[guided tutorial](https://docs.microsoft.com/en-us/composer/tutorial/tutorial-introduction)** in our documentation. + +### Connect with your users + +Your bot comes pre-configured to connect to our Web Chat and DirectLine channels, but there are many more places you can connect your bot to - including Microsoft Teams, Telephony, DirectLine Speech, Slack, Facebook, Outlook and more. Check out all of the places you can connect to on the bot settings page. + +### Publish your bot to Azure from Composer + +Composer can help you provision the Azure resources necessary for your bot, and publish your bot to them. To get started, create a publishing profile from your bot settings page in Composer (the wrench icon on the left navigation rail). Make sure you only provision the optional Azure resources you need! + +### Extend your bot with packages + +From Package Manager in Composer you can find useful packages to help add additional pre-built functionality you can add to your bot - everything from simple dialogs & custom actions for working with specific scenarios to custom adapters for connecting your bot to users on clients like Facebook or Slack. + +### Extend your bot with code + +You can also extend your bot with code - simply open up the folder that was generated for you in the location you chose during the creation process with your favorite IDE (like Visual Studio). You can do things like create custom actions that can be used during dialog flows, create custom middleware to pre-process (or post-process) messages, and more. See [our documentation](https://aka.ms/bf-extend-with-code) for more information. diff --git a/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/Startup.cs b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/Startup.cs new file mode 100644 index 0000000000..dae17d4283 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/Startup.cs @@ -0,0 +1,56 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.StaticFiles; +using Microsoft.Bot.Builder.Dialogs.Adaptive.Runtime.Extensions; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace EchoSkillBotComposer +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers().AddNewtonsoftJson(); + services.AddBotRuntime(Configuration); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseDefaultFiles(); + + // Set up custom content types - associating file extension to MIME type. + var provider = new FileExtensionContentTypeProvider(); + provider.Mappings[".lu"] = "application/vnd.microsoft.lu"; + provider.Mappings[".qna"] = "application/vnd.microsoft.qna"; + + // Expose static files in manifests folder for skill scenarios. + app.UseStaticFiles(new StaticFileOptions + { + ContentTypeProvider = provider + }); + app.UseWebSockets(); + app.UseRouting(); + app.UseAuthorization(); + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/dialogs/emptyBot/knowledge-base/en-us/emptyBot.en-us.qna b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/dialogs/emptyBot/knowledge-base/en-us/emptyBot.en-us.qna new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/echoskillbotcomposer.dialog b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/echoskillbotcomposer.dialog new file mode 100644 index 0000000000..5abf65c6df --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/echoskillbotcomposer.dialog @@ -0,0 +1,115 @@ +{ + "$kind": "Microsoft.AdaptiveDialog", + "$designer": { + "name": "EchoSkillBotComposer", + "description": "", + "id": "A79tBe" + }, + "autoEndDialog": true, + "defaultResultProperty": "dialog.result", + "triggers": [ + { + "$kind": "Microsoft.OnConversationUpdateActivity", + "$designer": { + "id": "376720" + }, + "actions": [ + { + "$kind": "Microsoft.Foreach", + "$designer": { + "id": "518944", + "name": "Loop: for each item" + }, + "itemsProperty": "turn.Activity.membersAdded", + "actions": [ + { + "$kind": "Microsoft.IfCondition", + "$designer": { + "id": "641773", + "name": "Branch: if/else" + }, + "condition": "string(dialog.foreach.value.id) != string(turn.Activity.Recipient.id)", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "$designer": { + "id": "859266", + "name": "Send a response" + }, + "activity": "${SendActivity_Greeting()}" + } + ] + } + ] + } + ] + }, + { + "$kind": "Microsoft.OnUnknownIntent", + "$designer": { + "id": "mb2n1u" + }, + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "$designer": { + "id": "kMjqz1" + }, + "activity": "${SendActivity_DidNotUnderstand()}" + } + ] + }, + { + "$kind": "Microsoft.OnMessageActivity", + "$designer": { + "id": "esM1JY" + }, + "actions": [ + { + "$kind": "Microsoft.IfCondition", + "$designer": { + "id": "SK42Qt" + }, + "condition": "turn.activity.text != 'end' && turn.activity.text != 'stop'", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "$designer": { + "id": "HHAFpn" + }, + "activity": "${SendActivity_HHAFpn()}" + }, + { + "$kind": "Microsoft.SendActivity", + "$designer": { + "id": "dZ2zwR" + }, + "activity": "${SendActivity_dZ2zwR()}" + }, + { + "$kind": "Microsoft.EndTurn", + "$designer": { + "id": "YzX0HZ" + } + } + ], + "elseActions": [ + { + "$kind": "Microsoft.SendActivity", + "$designer": { + "id": "xvwomh" + }, + "activity": "${SendActivity_xvwomh()}" + } + ] + } + ] + } + ], + "generator": "EchoSkillBotComposer.lg", + "id": "EchoSkillBotComposer", + "recognizer": { + "$kind": "Microsoft.RegexRecognizer", + "intents": [] + } +} diff --git a/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/generated/interruption/EchoSkillBotComposer.en-us.lu b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/generated/interruption/EchoSkillBotComposer.en-us.lu new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/generated/interruption/EchoSkillBotComposer.en-us.qna b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/generated/interruption/EchoSkillBotComposer.en-us.qna new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/knowledge-base/en-us/echoskillbotcomposer.en-us.qna b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/knowledge-base/en-us/echoskillbotcomposer.en-us.qna new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/language-generation/en-us/common.en-us.lg b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/language-generation/en-us/common.en-us.lg new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/language-generation/en-us/echoskillbotcomposer.en-us.lg b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/language-generation/en-us/echoskillbotcomposer.en-us.lg new file mode 100644 index 0000000000..f49f92107e --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/language-generation/en-us/echoskillbotcomposer.en-us.lg @@ -0,0 +1,31 @@ +[import](common.lg) + +# SendActivity_Greeting() +[Activity + Text = Welcome to EchoSkillBotComposerDotNet. +] + +# SendActivity_DidNotUnderstand() +[Activity + Text = ${SendActivity_DidNotUnderstand_text()} +] + +# SendActivity_DidNotUnderstand_text() +- Sorry, I didn't get that. +# SendActivity_HHAFpn() +[Activity + Text = Echo: ${turn.activity.text} + InputHint = acceptingInput +] + +# SendActivity_dZ2zwR() +[Activity + Text = Say "end" or "stop" and I'll end the conversation and back to the parent. + InputHint = acceptingInput +] + +# SendActivity_xvwomh() +[Activity + Text = Ending conversation from the skill... + InputHint = acceptingInput +] diff --git a/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/language-understanding/en-us/echoskillbotcomposer.en-us.lu b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/language-understanding/en-us/echoskillbotcomposer.en-us.lu new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/manifests/echoskillbotcomposer-manifest.json b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/manifests/echoskillbotcomposer-manifest.json new file mode 100644 index 0000000000..25205b9286 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/manifests/echoskillbotcomposer-manifest.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://schemas.botframework.com/schemas/skills/v2.1/skill-manifest.json", + "$id": "EchoSkillBotComposerDotNet", + "name": "EchoSkillBotComposerDotNet", + "version": "1.0", + "description": "This is a skill for echoing what the user sent to the bot (using Composer with the dotnet runtime).", + "publisherName": "Microsoft", + "privacyUrl": "https://microsoft.com/privacy", + "copyright": "Copyright (c) Microsoft Corporation. All rights reserved.", + "license": "https://github.com/microsoft/BotFramework-FunctionalTests/blob/main/LICENSE", + "tags": [ + "echo" + ], + "endpoints": [ + { + "name": "default", + "protocol": "BotFrameworkV3", + "description": "Localhost endpoint for the skill (on port 35410)", + "endpointUrl": "http://localhost:35410/api/messages", + "msAppId": "00000000-0000-0000-0000-000000000000" + } + ] +} \ No newline at end of file diff --git a/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/media/create-azure-resource-command-line.png b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/media/create-azure-resource-command-line.png new file mode 100644 index 0000000000..497eb8e649 Binary files /dev/null and b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/media/create-azure-resource-command-line.png differ diff --git a/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/media/publish-az-login.png b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/media/publish-az-login.png new file mode 100644 index 0000000000..4e721354bc Binary files /dev/null and b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/media/publish-az-login.png differ diff --git a/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/schemas/sdk.schema b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/schemas/sdk.schema new file mode 100644 index 0000000000..ee34876994 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/schemas/sdk.schema @@ -0,0 +1,10312 @@ +{ + "$schema": "https://schemas.botframework.com/schemas/component/v1.0/component.schema", + "type": "object", + "title": "Component kinds", + "description": "These are all of the kinds that can be created by the loader.", + "oneOf": [ + { + "$ref": "#/definitions/Microsoft.ActivityTemplate" + }, + { + "$ref": "#/definitions/Microsoft.AdaptiveDialog" + }, + { + "$ref": "#/definitions/Microsoft.AgeEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.Ask" + }, + { + "$ref": "#/definitions/Microsoft.AttachmentInput" + }, + { + "$ref": "#/definitions/Microsoft.BeginDialog" + }, + { + "$ref": "#/definitions/Microsoft.BeginSkill" + }, + { + "$ref": "#/definitions/Microsoft.BreakLoop" + }, + { + "$ref": "#/definitions/Microsoft.CancelAllDialogs" + }, + { + "$ref": "#/definitions/Microsoft.CancelDialog" + }, + { + "$ref": "#/definitions/Microsoft.ChannelMentionEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.ChoiceInput" + }, + { + "$ref": "#/definitions/Microsoft.ConditionalSelector" + }, + { + "$ref": "#/definitions/Microsoft.ConfirmationEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.ConfirmInput" + }, + { + "$ref": "#/definitions/Microsoft.ContinueConversation" + }, + { + "$ref": "#/definitions/Microsoft.ContinueConversationLater" + }, + { + "$ref": "#/definitions/Microsoft.ContinueLoop" + }, + { + "$ref": "#/definitions/Microsoft.CrossTrainedRecognizerSet" + }, + { + "$ref": "#/definitions/Microsoft.CurrencyEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.DateTimeEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.DateTimeInput" + }, + { + "$ref": "#/definitions/Microsoft.DebugBreak" + }, + { + "$ref": "#/definitions/Microsoft.DeleteActivity" + }, + { + "$ref": "#/definitions/Microsoft.DeleteProperties" + }, + { + "$ref": "#/definitions/Microsoft.DeleteProperty" + }, + { + "$ref": "#/definitions/Microsoft.DimensionEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.EditActions" + }, + { + "$ref": "#/definitions/Microsoft.EditArray" + }, + { + "$ref": "#/definitions/Microsoft.EmailEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.EmitEvent" + }, + { + "$ref": "#/definitions/Microsoft.EndDialog" + }, + { + "$ref": "#/definitions/Microsoft.EndTurn" + }, + { + "$ref": "#/definitions/Microsoft.FirstSelector" + }, + { + "$ref": "#/definitions/Microsoft.Foreach" + }, + { + "$ref": "#/definitions/Microsoft.ForeachPage" + }, + { + "$ref": "#/definitions/Microsoft.GetActivityMembers" + }, + { + "$ref": "#/definitions/Microsoft.GetConversationMembers" + }, + { + "$ref": "#/definitions/Microsoft.GetConversationReference" + }, + { + "$ref": "#/definitions/Microsoft.GotoAction" + }, + { + "$ref": "#/definitions/Microsoft.GuidEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.HashtagEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.HttpRequest" + }, + { + "$ref": "#/definitions/Microsoft.IfCondition" + }, + { + "$ref": "#/definitions/Microsoft.IpEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.LogAction" + }, + { + "$ref": "#/definitions/Microsoft.LuisRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.MentionEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.MostSpecificSelector" + }, + { + "$ref": "#/definitions/Microsoft.MultiLanguageRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.NumberEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.NumberInput" + }, + { + "$ref": "#/definitions/Microsoft.NumberRangeEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.OAuthInput" + }, + { + "$ref": "#/definitions/Microsoft.OnActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnAssignEntity" + }, + { + "$ref": "#/definitions/Microsoft.OnBeginDialog" + }, + { + "$ref": "#/definitions/Microsoft.OnCancelDialog" + }, + { + "$ref": "#/definitions/Microsoft.OnChooseEntity" + }, + { + "$ref": "#/definitions/Microsoft.OnChooseIntent" + }, + { + "$ref": "#/definitions/Microsoft.OnChooseProperty" + }, + { + "$ref": "#/definitions/Microsoft.OnCommandActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnCommandResultActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnCondition" + }, + { + "$ref": "#/definitions/Microsoft.OnContinueConversation" + }, + { + "$ref": "#/definitions/Microsoft.OnConversationUpdateActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnDialogEvent" + }, + { + "$ref": "#/definitions/Microsoft.OnEndOfActions" + }, + { + "$ref": "#/definitions/Microsoft.OnEndOfConversationActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnError" + }, + { + "$ref": "#/definitions/Microsoft.OnEventActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnHandoffActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnInstallationUpdateActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnIntent" + }, + { + "$ref": "#/definitions/Microsoft.OnInvokeActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnMessageActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnMessageDeleteActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnMessageReactionActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnMessageUpdateActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnQnAMatch" + }, + { + "$ref": "#/definitions/Microsoft.OnRepromptDialog" + }, + { + "$ref": "#/definitions/Microsoft.OnTypingActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnUnknownIntent" + }, + { + "$ref": "#/definitions/Microsoft.OrdinalEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.PercentageEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.PhoneNumberEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.QnAMakerDialog" + }, + { + "$ref": "#/definitions/Microsoft.QnAMakerRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.RandomSelector" + }, + { + "$ref": "#/definitions/Microsoft.RecognizerSet" + }, + { + "$ref": "#/definitions/Microsoft.RegexEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.RegexRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.RepeatDialog" + }, + { + "$ref": "#/definitions/Microsoft.ReplaceDialog" + }, + { + "$ref": "#/definitions/Microsoft.ResourceMultiLanguageGenerator" + }, + { + "$ref": "#/definitions/Microsoft.SendActivity" + }, + { + "$ref": "#/definitions/Microsoft.SendHandoffActivity" + }, + { + "$ref": "#/definitions/Microsoft.SetProperties" + }, + { + "$ref": "#/definitions/Microsoft.SetProperty" + }, + { + "$ref": "#/definitions/Microsoft.SignOutUser" + }, + { + "$ref": "#/definitions/Microsoft.StaticActivityTemplate" + }, + { + "$ref": "#/definitions/Microsoft.SwitchCondition" + }, + { + "$ref": "#/definitions/Microsoft.TelemetryTrackEventAction" + }, + { + "$ref": "#/definitions/Microsoft.TemperatureEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.TemplateEngineLanguageGenerator" + }, + { + "$ref": "#/definitions/Microsoft.TextInput" + }, + { + "$ref": "#/definitions/Microsoft.TextTemplate" + }, + { + "$ref": "#/definitions/Microsoft.ThrowException" + }, + { + "$ref": "#/definitions/Microsoft.TraceActivity" + }, + { + "$ref": "#/definitions/Microsoft.TrueSelector" + }, + { + "$ref": "#/definitions/Microsoft.UpdateActivity" + }, + { + "$ref": "#/definitions/Microsoft.UrlEntityRecognizer" + } + ], + "definitions": { + "arrayExpression": { + "$role": "expression", + "title": "Array or expression", + "description": "Array or expression to evaluate.", + "oneOf": [ + { + "type": "array", + "title": "Array", + "description": "Array constant." + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "booleanExpression": { + "$role": "expression", + "title": "Boolean or expression", + "description": "Boolean constant or expression to evaluate.", + "oneOf": [ + { + "type": "boolean", + "title": "Boolean", + "description": "Boolean constant.", + "default": false, + "examples": [ + false + ] + }, + { + "$ref": "#/definitions/equalsExpression", + "examples": [ + "=user.isVip" + ] + } + ] + }, + "component": { + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "condition": { + "$role": "expression", + "title": "Boolean condition", + "description": "Boolean constant or expression to evaluate.", + "oneOf": [ + { + "$ref": "#/definitions/expression" + }, + { + "type": "boolean", + "title": "Boolean", + "description": "Boolean value.", + "default": true, + "examples": [ + false + ] + } + ] + }, + "equalsExpression": { + "$role": "expression", + "type": "string", + "title": "Expression", + "description": "Expression starting with =.", + "pattern": "^=.*\\S.*", + "examples": [ + "=user.name" + ] + }, + "expression": { + "$role": "expression", + "type": "string", + "title": "Expression", + "description": "Expression to evaluate.", + "pattern": "^.*\\S.*", + "examples": [ + "user.age > 13" + ] + }, + "integerExpression": { + "$role": "expression", + "title": "Integer or expression", + "description": "Integer constant or expression to evaluate.", + "oneOf": [ + { + "type": "integer", + "title": "Integer", + "description": "Integer constant.", + "default": 0, + "examples": [ + 15 + ] + }, + { + "$ref": "#/definitions/equalsExpression", + "examples": [ + "=user.age" + ] + } + ] + }, + "Microsoft.ActivityTemplate": { + "$role": "implements(Microsoft.IActivityTemplate)", + "title": "Microsoft activity template", + "type": "object", + "required": [ + "template", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "template": { + "title": "Template", + "description": "Language Generator template to use to create the activity", + "type": "string" + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.ActivityTemplate" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.AdaptiveDialog": { + "$role": "implements(Microsoft.IDialog)", + "title": "Adaptive dialog", + "description": "Flexible, data driven dialog that can adapt to the conversation.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "pattern": "^(?!(=)).*", + "title": "Id", + "description": "Optional dialog ID." + }, + "autoEndDialog": { + "$ref": "#/definitions/booleanExpression", + "title": "Auto end dialog", + "description": "If set to true the dialog will automatically end when there are no further actions. If set to false, remember to manually end the dialog using EndDialog action.", + "default": true + }, + "defaultResultProperty": { + "type": "string", + "title": "Default result property", + "description": "Value that will be passed back to the parent dialog.", + "default": "dialog.result" + }, + "dialogs": { + "type": "array", + "title": "Dialogs", + "description": "Dialogs added to DialogSet.", + "items": { + "$kind": "Microsoft.IDialog", + "title": "Dialog", + "description": "Dialog to add to DialogSet.", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "recognizer": { + "$kind": "Microsoft.IRecognizer", + "title": "Recognizer", + "description": "Input recognizer that interprets user input into intent and entities.", + "$ref": "#/definitions/Microsoft.IRecognizer" + }, + "generator": { + "$kind": "Microsoft.ILanguageGenerator", + "title": "Language generator", + "description": "Language generator that generates bot responses.", + "$ref": "#/definitions/Microsoft.ILanguageGenerator" + }, + "selector": { + "$kind": "Microsoft.ITriggerSelector", + "title": "Selector", + "description": "Policy to determine which trigger is executed. Defaults to a 'best match' selector (optional).", + "$ref": "#/definitions/Microsoft.ITriggerSelector" + }, + "triggers": { + "type": "array", + "description": "List of triggers defined for this dialog.", + "title": "Triggers", + "items": { + "$kind": "Microsoft.ITrigger", + "title": "Event triggers", + "description": "Event triggers for handling events.", + "$ref": "#/definitions/Microsoft.ITrigger" + } + }, + "schema": { + "title": "Schema", + "description": "Schema to fill in.", + "anyOf": [ + { + "$ref": "#/definitions/schema" + }, + { + "type": "string", + "title": "Reference to JSON schema", + "description": "Reference to JSON schema .dialog file." + } + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.AdaptiveDialog" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.AgeEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Age entity recognizer", + "description": "Recognizer which recognizes age.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.AgeEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.Ask": { + "$role": [ + "implements(Microsoft.IDialog)", + "extends(Microsoft.SendActivity)" + ], + "title": "Send activity to ask a question", + "description": "This is an action which sends an activity to the user when a response is expected", + "type": "object", + "$policies": { + "interactive": true + }, + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "expectedProperties": { + "$ref": "#/definitions/arrayExpression", + "title": "Expected properties", + "description": "Properties expected from the user.", + "examples": [ + [ + "age", + "name" + ] + ], + "items": { + "type": "string", + "title": "Name", + "description": "Name of the property" + } + }, + "defaultOperation": { + "$ref": "#/definitions/stringExpression", + "title": "Default operation", + "description": "Sets the default operation that will be used when no operation is recognized in the response to this Ask.", + "examples": [ + "Add()", + "Remove()" + ] + }, + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action." + }, + "activity": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Activity", + "description": "Activity to send.", + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.Ask" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.AttachmentInput": { + "$role": [ + "implements(Microsoft.IDialog)", + "extends(Microsoft.InputDialog)" + ], + "title": "Attachment input dialog", + "description": "Collect information - Ask for a file or image.", + "$policies": { + "interactive": true + }, + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "type": "object", + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "defaultValue": { + "$role": "expression", + "title": "Default value", + "description": "'Property' will be set to the object or the result of this expression when max turn count is exceeded.", + "oneOf": [ + { + "$ref": "#/definitions/botframework.json/definitions/Attachment", + "title": "Object", + "description": "Attachment object." + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "value": { + "$role": "expression", + "title": "Value", + "description": "'Property' will be set to the object or the result of this expression unless it evaluates to null.", + "oneOf": [ + { + "$ref": "#/definitions/botframework.json/definitions/Attachment", + "title": "Object", + "description": "Attachment object." + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "outputFormat": { + "$role": "expression", + "title": "Output format", + "description": "Attachment output format.", + "oneOf": [ + { + "type": "string", + "title": "Standard format", + "description": "Standard output formats.", + "enum": [ + "all", + "first" + ], + "default": "first" + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "default": false, + "examples": [ + false, + "=user.isVip" + ] + }, + "prompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Initial prompt", + "description": "Message to send to collect information.", + "examples": [ + "What is your birth date?" + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "unrecognizedPrompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Unrecognized prompt", + "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value.", + "examples": [ + "Sorry, I'm having trouble understanding you. I will just use {this.options.defaultValue} for now. You can say 'I'm 36 years old' to change it." + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "invalidPrompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Invalid prompt", + "description": "Message to send when the user input does not meet any validation expression.", + "examples": [ + "Sorry, '{this.value}' does not work. I need a number between 1-150. What is your age?" + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "defaultValueResponse": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Default value response", + "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value.", + "examples": [ + "Sorry, I'm having trouble understanding you. I will just use {this.options.defaultValue} for now. You can say 'I'm 36 years old' to change it." + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "maxTurnCount": { + "$ref": "#/definitions/integerExpression", + "title": "Max turn count", + "description": "Maximum number of re-prompt attempts to collect information.", + "default": 3, + "minimum": 0, + "maximum": 2147483647, + "examples": [ + 3, + "=settings.xyz" + ] + }, + "validations": { + "type": "array", + "title": "Validation expressions", + "description": "Expression to validate user input.", + "items": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Expression which needs to met for the input to be considered valid", + "examples": [ + "int(this.value) > 1 && int(this.value) <= 150", + "count(this.value) < 300" + ] + } + }, + "property": { + "$ref": "#/definitions/stringExpression", + "title": "Property", + "description": "Property to store collected information. Input will be skipped if property has value (unless 'Always prompt' is true).", + "examples": [ + "$birthday", + "dialog.${user.name}", + "=f(x)" + ] + }, + "alwaysPrompt": { + "$ref": "#/definitions/booleanExpression", + "title": "Always prompt", + "description": "Collect information even if the specified 'property' is not empty.", + "default": false, + "examples": [ + false, + "=$val" + ] + }, + "allowInterruptions": { + "$ref": "#/definitions/booleanExpression", + "title": "Allow Interruptions", + "description": "A boolean expression that determines whether the parent should be allowed to interrupt the input.", + "default": true, + "examples": [ + true, + "=user.xyz" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.AttachmentInput" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.BeginDialog": { + "$role": "implements(Microsoft.IDialog)", + "title": "Begin a dialog", + "description": "Begin another dialog.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "dialog": { + "oneOf": [ + { + "$kind": "Microsoft.IDialog", + "pattern": "^(?!(=)).*", + "title": "Dialog", + "$ref": "#/definitions/Microsoft.IDialog" + }, + { + "$ref": "#/definitions/equalsExpression" + } + ], + "title": "Dialog name", + "description": "Name of the dialog to call." + }, + "options": { + "$ref": "#/definitions/objectExpression", + "title": "Options", + "description": "One or more options that are passed to the dialog that is called.", + "examples": [ + { + "arg1": "=expression" + } + ], + "additionalProperties": { + "type": "string", + "title": "Options", + "description": "Options for dialog." + } + }, + "activityProcessed": { + "$ref": "#/definitions/booleanExpression", + "title": "Activity processed", + "description": "When set to false, the dialog that is called can process the current activity.", + "default": true + }, + "resultProperty": { + "$ref": "#/definitions/stringExpression", + "title": "Property", + "description": "Property to store any value returned by the dialog that is called.", + "examples": [ + "dialog.userName" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.BeginDialog" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.BeginSkill": { + "$role": "implements(Microsoft.IDialog)", + "title": "Begin a skill", + "description": "Begin a remote skill.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the skill dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + true, + "=f(x)" + ] + }, + "activityProcessed": { + "$ref": "#/definitions/booleanExpression", + "title": "Activity processed", + "description": "When set to false, the skill will be started using the activity in the current turn context instead of the activity in the Activity property.", + "default": true, + "examples": [ + true, + "=f(x)" + ] + }, + "resultProperty": { + "$ref": "#/definitions/stringExpression", + "title": "Property", + "description": "Property to store any value returned by the dialog that is called.", + "examples": [ + "dialog.userName" + ] + }, + "botId": { + "$ref": "#/definitions/stringExpression", + "title": "Skill host bot ID", + "description": "The Microsoft App ID that will be calling the skill.", + "default": "=settings.MicrosoftAppId" + }, + "skillHostEndpoint": { + "$ref": "#/definitions/stringExpression", + "title": "Skill host", + "description": "The callback Url for the skill host.", + "default": "=settings.skillHostEndpoint", + "examples": [ + "https://mybot.contoso.com/api/skills/" + ] + }, + "connectionName": { + "$ref": "#/definitions/stringExpression", + "title": "OAuth connection name (SSO)", + "description": "The OAuth Connection Name, that would be used to perform Single SignOn with a skill.", + "default": "=settings.connectionName" + }, + "skillAppId": { + "$ref": "#/definitions/stringExpression", + "title": "Skill App Id", + "description": "The Microsoft App ID for the skill." + }, + "skillEndpoint": { + "$ref": "#/definitions/stringExpression", + "title": "Skill endpoint ", + "description": "The /api/messages endpoint for the skill.", + "examples": [ + "https://myskill.contoso.com/api/messages/" + ] + }, + "activity": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Activity", + "description": "The activity to send to the skill.", + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "allowInterruptions": { + "$ref": "#/definitions/booleanExpression", + "title": "Allow interruptions", + "description": "A boolean expression that determines whether the parent should be allowed to interrupt the skill.", + "default": true, + "examples": [ + true, + "=user.xyz" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.BeginSkill" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.BreakLoop": { + "$role": "implements(Microsoft.IDialog)", + "title": "Break loop", + "description": "Stop executing this loop", + "type": "object", + "required": [ + "$kind" + ], + "$policies": { + "lastAction": true + }, + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.BreakLoop" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.CancelAllDialogs": { + "$role": "implements(Microsoft.IDialog)", + "title": "Cancel all dialogs", + "description": "Cancel all active dialogs. All dialogs in the dialog chain will need a trigger to capture the event configured in this action.", + "type": "object", + "$policies": { + "lastAction": true + }, + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "activityProcessed": { + "$ref": "#/definitions/booleanExpression", + "title": "Activity processed", + "description": "When set to false, the caller dialog is told it should process the current activity.", + "default": true + }, + "eventName": { + "$ref": "#/definitions/stringExpression", + "title": "Event name", + "description": "Name of the event to emit." + }, + "eventValue": { + "$ref": "#/definitions/valueExpression", + "title": "Event value", + "description": "Value to emit with the event (optional).", + "additionalProperties": true + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.CancelAllDialogs" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.CancelDialog": { + "$role": "implements(Microsoft.IDialog)", + "title": "Cancel all dialogs", + "description": "Cancel all active dialogs. All dialogs in the dialog chain will need a trigger to capture the event configured in this action.", + "type": "object", + "$policies": { + "lastAction": true + }, + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "activityProcessed": { + "$ref": "#/definitions/booleanExpression", + "title": "Activity processed", + "description": "When set to false, the caller dialog is told it should process the current activity.", + "default": true + }, + "eventName": { + "$ref": "#/definitions/stringExpression", + "title": "Event name", + "description": "Name of the event to emit." + }, + "eventValue": { + "$ref": "#/definitions/valueExpression", + "title": "Event value", + "description": "Value to emit with the event (optional).", + "additionalProperties": true + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.CancelDialog" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.ChannelMentionEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)" + ], + "title": "Channel mention entity recognizer", + "description": "Promotes mention entities passed by a channel via the activity.entities into recognizer result.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.ChannelMentionEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.ChoiceInput": { + "$role": [ + "implements(Microsoft.IDialog)", + "extends(Microsoft.InputDialog)" + ], + "title": "Choice input dialog", + "description": "Collect information - Pick from a list of choices", + "type": "object", + "$policies": { + "interactive": true + }, + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "defaultValue": { + "$ref": "#/definitions/stringExpression", + "title": "Default value", + "description": "'Property' will be set to the value of this expression when max turn count is exceeded.", + "examples": [ + "hello world", + "Hello ${user.name}", + "=concat(user.firstname, user.lastName)" + ] + }, + "value": { + "$ref": "#/definitions/stringExpression", + "title": "Value", + "description": "'Property' will be set to the value of this expression unless it evaluates to null.", + "examples": [ + "hello world", + "Hello ${user.name}", + "=concat(user.firstname, user.lastName)" + ] + }, + "outputFormat": { + "$role": "expression", + "title": "Output format", + "description": "Sets the desired choice output format (either value or index into choices).", + "oneOf": [ + { + "type": "string", + "title": "Standard", + "description": "Standard output format.", + "enum": [ + "value", + "index" + ], + "default": "value" + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "choices": { + "$role": "expression", + "title": "Array of choices", + "description": "Choices to choose from.", + "oneOf": [ + { + "type": "array", + "title": "Simple choices", + "description": "Simple choices to choose from.", + "items": [ + { + "type": "string", + "title": "Simple choice", + "description": "One choice for choice input." + } + ] + }, + { + "type": "array", + "title": "Structured choices", + "description": "Choices that allow full control.", + "items": [ + { + "type": "object", + "title": "Structured choice", + "description": "Structured choice to choose from.", + "properties": { + "value": { + "type": "string", + "title": "Value", + "description": "Value to return when this choice is selected." + }, + "action": { + "$ref": "#/definitions/botframework.json/definitions/CardAction", + "title": "Action", + "description": "Card action for the choice." + }, + "synonyms": { + "type": "array", + "title": "Synonyms", + "description": "List of synonyms to recognize in addition to the value (optional).", + "items": { + "type": "string", + "title": "Synonym", + "description": "Synonym for value." + } + } + } + } + ] + }, + { + "$ref": "#/definitions/stringExpression" + } + ] + }, + "defaultLocale": { + "$ref": "#/definitions/stringExpression", + "title": "Default locale", + "description": "The default locale to use to parse confirmation choices if there is not one passed by the caller.", + "default": "en-us", + "examples": [ + "en-us" + ] + }, + "style": { + "$role": "expression", + "title": "List style", + "description": "Sets the ListStyle to control how choices are rendered.", + "oneOf": [ + { + "type": "string", + "title": "List style", + "description": "Standard list style.", + "enum": [ + "none", + "auto", + "inline", + "list", + "suggestedAction", + "heroCard" + ], + "default": "auto" + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "choiceOptions": { + "title": "Choice options", + "description": "Sets the choice options used for controlling how choices are combined.", + "oneOf": [ + { + "type": "object", + "title": "Object", + "description": "Choice options object.", + "properties": { + "inlineSeparator": { + "type": "string", + "title": "Inline separator", + "description": "Character used to separate individual choices when there are more than 2 choices", + "default": ", " + }, + "inlineOr": { + "type": "string", + "title": "Inline or", + "description": "Separator inserted between the choices when there are only 2 choices", + "default": " or " + }, + "inlineOrMore": { + "type": "string", + "title": "Inline or more", + "description": "Separator inserted between the last 2 choices when their are more than 2 choices.", + "default": ", or " + }, + "includeNumbers": { + "type": "boolean", + "title": "Include numbers", + "description": "If true, 'inline' and 'list' list style will be prefixed with the index of the choice.", + "default": true + } + } + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "recognizerOptions": { + "title": "Recognizer options", + "description": "Sets how to recognize choices in the response", + "oneOf": [ + { + "type": "object", + "title": "Object", + "description": "Options for recognizer.", + "properties": { + "noValue": { + "type": "boolean", + "title": "No value", + "description": "If true, the choices value field will NOT be search over", + "default": false + }, + "noAction": { + "type": "boolean", + "title": "No action", + "description": "If true, the choices action.title field will NOT be searched over", + "default": false + }, + "recognizeNumbers": { + "type": "boolean", + "title": "Recognize numbers", + "description": "If true, the number recognizer will be used to recognize an index response (1,2,3...) to the prompt.", + "default": true + }, + "recognizeOrdinals": { + "type": "boolean", + "title": "Recognize ordinals", + "description": "If true, the ordinal recognizer will be used to recognize ordinal response (first/second/...) to the prompt.", + "default": true + } + } + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "default": false, + "examples": [ + false, + "=user.isVip" + ] + }, + "prompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Initial prompt", + "description": "Message to send to collect information.", + "examples": [ + "What is your birth date?" + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "unrecognizedPrompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Unrecognized prompt", + "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value.", + "examples": [ + "Sorry, I'm having trouble understanding you. I will just use {this.options.defaultValue} for now. You can say 'I'm 36 years old' to change it." + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "invalidPrompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Invalid prompt", + "description": "Message to send when the user input does not meet any validation expression.", + "examples": [ + "Sorry, '{this.value}' does not work. I need a number between 1-150. What is your age?" + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "defaultValueResponse": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Default value response", + "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value.", + "examples": [ + "Sorry, I'm having trouble understanding you. I will just use {this.options.defaultValue} for now. You can say 'I'm 36 years old' to change it." + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "maxTurnCount": { + "$ref": "#/definitions/integerExpression", + "title": "Max turn count", + "description": "Maximum number of re-prompt attempts to collect information.", + "default": 3, + "minimum": 0, + "maximum": 2147483647, + "examples": [ + 3, + "=settings.xyz" + ] + }, + "validations": { + "type": "array", + "title": "Validation expressions", + "description": "Expression to validate user input.", + "items": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Expression which needs to met for the input to be considered valid", + "examples": [ + "int(this.value) > 1 && int(this.value) <= 150", + "count(this.value) < 300" + ] + } + }, + "property": { + "$ref": "#/definitions/stringExpression", + "title": "Property", + "description": "Property to store collected information. Input will be skipped if property has value (unless 'Always prompt' is true).", + "examples": [ + "$birthday", + "dialog.${user.name}", + "=f(x)" + ] + }, + "alwaysPrompt": { + "$ref": "#/definitions/booleanExpression", + "title": "Always prompt", + "description": "Collect information even if the specified 'property' is not empty.", + "default": false, + "examples": [ + false, + "=$val" + ] + }, + "allowInterruptions": { + "$ref": "#/definitions/booleanExpression", + "title": "Allow Interruptions", + "description": "A boolean expression that determines whether the parent should be allowed to interrupt the input.", + "default": true, + "examples": [ + true, + "=user.xyz" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.ChoiceInput" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.ConditionalSelector": { + "$role": "implements(Microsoft.ITriggerSelector)", + "title": "Conditional trigger selector", + "description": "Use a rule selector based on a condition", + "type": "object", + "required": [ + "condition", + "ifTrue", + "ifFalse", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Expression to evaluate" + }, + "ifTrue": { + "$kind": "Microsoft.ITriggerSelector", + "$ref": "#/definitions/Microsoft.ITriggerSelector" + }, + "ifFalse": { + "$kind": "Microsoft.ITriggerSelector", + "$ref": "#/definitions/Microsoft.ITriggerSelector" + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.ConditionalSelector" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.ConfirmationEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Confirmation entity recognizer", + "description": "Recognizer which recognizes confirmation choices (yes/no).", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.ConfirmationEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.ConfirmInput": { + "$role": [ + "implements(Microsoft.IDialog)", + "extends(Microsoft.InputDialog)" + ], + "title": "Confirm input dialog", + "description": "Collect information - Ask for confirmation (yes or no).", + "type": "object", + "$policies": { + "interactive": true + }, + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "outputFormat": { + "$ref": "#/definitions/valueExpression", + "title": "Output format", + "description": "Optional expression to use to format the output.", + "examples": [ + "=concat('confirmation:', this.value)" + ] + }, + "defaultLocale": { + "$ref": "#/definitions/stringExpression", + "title": "Default locale", + "description": "The Default locale or an expression which provides the default locale to use as default if not found in the activity.", + "default": "en-us", + "examples": [ + "en-us" + ] + }, + "style": { + "$role": "expression", + "title": "List style", + "description": "Sets the ListStyle to control how choices are rendered.", + "oneOf": [ + { + "type": "string", + "title": "Standard style", + "description": "Standard style for rendering choices.", + "enum": [ + "none", + "auto", + "inline", + "list", + "suggestedAction", + "heroCard" + ], + "default": "auto" + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "choiceOptions": { + "title": "Choice options", + "description": "Choice Options or expression which provides Choice Options to control display choices to the user.", + "oneOf": [ + { + "type": "object", + "title": "Choice options", + "description": "Choice options.", + "properties": { + "inlineSeparator": { + "type": "string", + "title": "Inline separator", + "description": "Text to separate individual choices when there are more than 2 choices", + "default": ", " + }, + "inlineOr": { + "type": "string", + "title": "Inline or", + "description": "Text to be inserted between the choices when their are only 2 choices", + "default": " or " + }, + "inlineOrMore": { + "type": "string", + "title": "Inline or more", + "description": "Text to be inserted between the last 2 choices when their are more than 2 choices.", + "default": ", or " + }, + "includeNumbers": { + "type": "boolean", + "title": "Include numbers", + "description": "If true, inline and list style choices will be prefixed with the index of the choice.", + "default": true + } + } + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "defaultValue": { + "$ref": "#/definitions/booleanExpression", + "title": "Default value", + "description": "'Property' will be set to the value of this expression when max turn count is exceeded.", + "examples": [ + true, + "=user.age > 3" + ] + }, + "value": { + "$ref": "#/definitions/booleanExpression", + "title": "Value", + "description": "'Property' will be set to the value of this expression unless it evaluates to null.", + "examples": [ + true, + "=user.isVip" + ] + }, + "confirmChoices": { + "$role": "expression", + "title": "Array of choice objects", + "description": "Array of simple or structured choices.", + "oneOf": [ + { + "type": "array", + "title": "Simple choices", + "description": "Simple choices to confirm from.", + "items": [ + { + "type": "string", + "title": "Simple choice", + "description": "Simple choice to confirm." + } + ] + }, + { + "type": "array", + "title": "Structured choices", + "description": "Structured choices for confirmations.", + "items": [ + { + "type": "object", + "title": "Choice", + "description": "Choice to confirm.", + "properties": { + "value": { + "type": "string", + "title": "Value", + "description": "Value to return when this choice is selected." + }, + "action": { + "$ref": "#/definitions/botframework.json/definitions/CardAction", + "title": "Action", + "description": "Card action for the choice." + }, + "synonyms": { + "type": "array", + "title": "Synonyms", + "description": "List of synonyms to recognize in addition to the value (optional).", + "items": { + "type": "string", + "title": "Synonym", + "description": "Synonym for choice." + } + } + } + } + ] + }, + { + "$ref": "#/definitions/stringExpression" + } + ] + }, + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "default": false, + "examples": [ + false, + "=user.isVip" + ] + }, + "prompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Initial prompt", + "description": "Message to send to collect information.", + "examples": [ + "What is your birth date?" + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "unrecognizedPrompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Unrecognized prompt", + "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value.", + "examples": [ + "Sorry, I'm having trouble understanding you. I will just use {this.options.defaultValue} for now. You can say 'I'm 36 years old' to change it." + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "invalidPrompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Invalid prompt", + "description": "Message to send when the user input does not meet any validation expression.", + "examples": [ + "Sorry, '{this.value}' does not work. I need a number between 1-150. What is your age?" + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "defaultValueResponse": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Default value response", + "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value.", + "examples": [ + "Sorry, I'm having trouble understanding you. I will just use {this.options.defaultValue} for now. You can say 'I'm 36 years old' to change it." + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "maxTurnCount": { + "$ref": "#/definitions/integerExpression", + "title": "Max turn count", + "description": "Maximum number of re-prompt attempts to collect information.", + "default": 3, + "minimum": 0, + "maximum": 2147483647, + "examples": [ + 3, + "=settings.xyz" + ] + }, + "validations": { + "type": "array", + "title": "Validation expressions", + "description": "Expression to validate user input.", + "items": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Expression which needs to met for the input to be considered valid", + "examples": [ + "int(this.value) > 1 && int(this.value) <= 150", + "count(this.value) < 300" + ] + } + }, + "property": { + "$ref": "#/definitions/stringExpression", + "title": "Property", + "description": "Property to store collected information. Input will be skipped if property has value (unless 'Always prompt' is true).", + "examples": [ + "$birthday", + "dialog.${user.name}", + "=f(x)" + ] + }, + "alwaysPrompt": { + "$ref": "#/definitions/booleanExpression", + "title": "Always prompt", + "description": "Collect information even if the specified 'property' is not empty.", + "default": false, + "examples": [ + false, + "=$val" + ] + }, + "allowInterruptions": { + "$ref": "#/definitions/booleanExpression", + "title": "Allow Interruptions", + "description": "A boolean expression that determines whether the parent should be allowed to interrupt the input.", + "default": true, + "examples": [ + true, + "=user.xyz" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.ConfirmInput" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.ContinueConversation": { + "$role": "implements(Microsoft.IDialog)", + "title": "Continue conversation (Queue)", + "description": "Continue a specific conversation (via StorageQueue implementation).", + "type": "object", + "required": [ + "conversationReference", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "conversationReference": { + "$ref": "#/definitions/objectExpression", + "title": "Conversation Reference", + "description": "A conversation reference. (NOTE: Minimum required values or channelId, conversation).", + "examples": [ + { + "channelId": "skype", + "serviceUrl": "http://smba.skype.com", + "conversation": { + "id": "11111" + }, + "bot": { + "id": "22222" + }, + "user": { + "id": "33333" + }, + "locale": "en-us" + } + ] + }, + "value": { + "$ref": "#/definitions/valueExpression", + "title": "Value", + "description": "Value to send in the activity.value." + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.ContinueConversation" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.ContinueConversationLater": { + "$role": "implements(Microsoft.IDialog)", + "title": "Continue conversation later (Queue)", + "description": "Continue conversation at later time (via StorageQueue implementation).", + "type": "object", + "required": [ + "date", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "date": { + "$ref": "#/definitions/stringExpression", + "title": "Date", + "description": "Date in the future as a ISO string when the conversation should continue.", + "examples": [ + "=addHours(utcNow(), 1)" + ] + }, + "value": { + "$ref": "#/definitions/valueExpression", + "title": "Value", + "description": "Value to send in the activity.value." + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.ContinueConversationLater" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.ContinueLoop": { + "$role": "implements(Microsoft.IDialog)", + "title": "Continue loop", + "description": "Stop executing this template and continue with the next iteration of the loop.", + "type": "object", + "required": [ + "$kind" + ], + "$policies": { + "lastAction": true + }, + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.ContinueLoop" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.CrossTrainedRecognizerSet": { + "$role": "implements(Microsoft.IRecognizer)", + "title": "Cross-trained recognizer set", + "description": "Recognizer for selecting between cross trained recognizers.", + "type": "object", + "required": [ + "recognizers", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional unique id using with RecognizerSet." + }, + "recognizers": { + "type": "array", + "title": "Recognizers", + "description": "List of Recognizers defined for this set.", + "items": { + "$kind": "Microsoft.IRecognizer", + "$ref": "#/definitions/Microsoft.IRecognizer" + } + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.CrossTrainedRecognizerSet" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.CurrencyEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Currency entity recognizer", + "description": "Recognizer which recognizes currency.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.CurrencyEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.DateTimeEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Date and time entity recognizer", + "description": "Recognizer which recognizes dates and time fragments.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.DateTimeEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.DateTimeInput": { + "$role": [ + "implements(Microsoft.IDialog)", + "extends(Microsoft.InputDialog)" + ], + "title": "Date/time input dialog", + "description": "Collect information - Ask for date and/ or time", + "type": "object", + "defaultLocale": { + "$ref": "#/definitions/stringExpression", + "title": "Default locale", + "description": "Default locale.", + "default": "en-us" + }, + "$policies": { + "interactive": true + }, + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "defaultValue": { + "$ref": "#/definitions/stringExpression", + "format": "date-time", + "title": "Default date", + "description": "'Property' will be set to the value or the result of the expression when max turn count is exceeded.", + "examples": [ + "=user.birthday" + ] + }, + "value": { + "$ref": "#/definitions/stringExpression", + "format": "date-time", + "title": "Value", + "description": "'Property' will be set to the value or the result of the expression unless it evaluates to null.", + "examples": [ + "=user.birthday" + ] + }, + "outputFormat": { + "$ref": "#/definitions/expression", + "title": "Output format", + "description": "Expression to use for formatting the output.", + "examples": [ + "=this.value[0].Value" + ] + }, + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "default": false, + "examples": [ + false, + "=user.isVip" + ] + }, + "prompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Initial prompt", + "description": "Message to send to collect information.", + "examples": [ + "What is your birth date?" + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "unrecognizedPrompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Unrecognized prompt", + "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value.", + "examples": [ + "Sorry, I'm having trouble understanding you. I will just use {this.options.defaultValue} for now. You can say 'I'm 36 years old' to change it." + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "invalidPrompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Invalid prompt", + "description": "Message to send when the user input does not meet any validation expression.", + "examples": [ + "Sorry, '{this.value}' does not work. I need a number between 1-150. What is your age?" + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "defaultValueResponse": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Default value response", + "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value.", + "examples": [ + "Sorry, I'm having trouble understanding you. I will just use {this.options.defaultValue} for now. You can say 'I'm 36 years old' to change it." + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "maxTurnCount": { + "$ref": "#/definitions/integerExpression", + "title": "Max turn count", + "description": "Maximum number of re-prompt attempts to collect information.", + "default": 3, + "minimum": 0, + "maximum": 2147483647, + "examples": [ + 3, + "=settings.xyz" + ] + }, + "validations": { + "type": "array", + "title": "Validation expressions", + "description": "Expression to validate user input.", + "items": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Expression which needs to met for the input to be considered valid", + "examples": [ + "int(this.value) > 1 && int(this.value) <= 150", + "count(this.value) < 300" + ] + } + }, + "property": { + "$ref": "#/definitions/stringExpression", + "title": "Property", + "description": "Property to store collected information. Input will be skipped if property has value (unless 'Always prompt' is true).", + "examples": [ + "$birthday", + "dialog.${user.name}", + "=f(x)" + ] + }, + "alwaysPrompt": { + "$ref": "#/definitions/booleanExpression", + "title": "Always prompt", + "description": "Collect information even if the specified 'property' is not empty.", + "default": false, + "examples": [ + false, + "=$val" + ] + }, + "allowInterruptions": { + "$ref": "#/definitions/booleanExpression", + "title": "Allow Interruptions", + "description": "A boolean expression that determines whether the parent should be allowed to interrupt the input.", + "default": true, + "examples": [ + true, + "=user.xyz" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.DateTimeInput" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.DebugBreak": { + "$role": "implements(Microsoft.IDialog)", + "title": "Debugger break", + "description": "If debugger is attached, stop the execution at this point in the conversation.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.DebugBreak" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.DeleteActivity": { + "$role": "implements(Microsoft.IDialog)", + "title": "Delete Activity", + "description": "Delete an activity that was previously sent.", + "type": "object", + "required": [ + "activityId", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "activityId": { + "$ref": "#/definitions/stringExpression", + "title": "ActivityId", + "description": "expression to an activityId to delete", + "examples": [ + "=turn.lastresult.id" + ] + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.DeleteActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.DeleteProperties": { + "$role": "implements(Microsoft.IDialog)", + "title": "Delete Properties", + "description": "Delete multiple properties and any value it holds.", + "type": "object", + "required": [ + "properties", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "properties": { + "type": "array", + "title": "Properties", + "description": "Properties to delete.", + "items": { + "$ref": "#/definitions/stringExpression", + "title": "Property", + "description": "Property to delete." + } + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.DeleteProperties" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.DeleteProperty": { + "$role": "implements(Microsoft.IDialog)", + "title": "Delete property", + "description": "Delete a property and any value it holds.", + "type": "object", + "required": [ + "property", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "property": { + "$ref": "#/definitions/stringExpression", + "title": "Property", + "description": "Property to delete." + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.DeleteProperty" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.DimensionEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Dimension entity recognizer", + "description": "Recognizer which recognizes dimension.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.DimensionEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.EditActions": { + "$role": "implements(Microsoft.IDialog)", + "title": "Edit actions.", + "description": "Edit the current list of actions.", + "type": "object", + "required": [ + "changeType", + "actions", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "changeType": { + "title": "Type of change", + "description": "Type of change to apply to the current actions.", + "oneOf": [ + { + "type": "string", + "title": "Standard change", + "description": "Standard change types.", + "enum": [ + "insertActions", + "insertActionsBeforeTags", + "appendActions", + "endSequence", + "replaceSequence" + ] + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Actions to apply.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.EditActions" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.EditArray": { + "$role": "implements(Microsoft.IDialog)", + "title": "Edit array", + "description": "Modify an array in memory", + "type": "object", + "required": [ + "itemsProperty", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "changeType": { + "title": "Type of change", + "description": "Type of change to the array in memory.", + "oneOf": [ + { + "type": "string", + "title": "Change type", + "description": "Standard change type.", + "enum": [ + "push", + "pop", + "take", + "remove", + "clear" + ] + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "itemsProperty": { + "$ref": "#/definitions/stringExpression", + "title": "Items property", + "description": "Property that holds the array to update." + }, + "resultProperty": { + "$ref": "#/definitions/stringExpression", + "title": "Result property", + "description": "Property to store the result of this action." + }, + "value": { + "$ref": "#/definitions/valueExpression", + "title": "Value", + "description": "New value or expression.", + "examples": [ + "milk", + "=dialog.favColor", + "=dialog.favColor == 'red'" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.EditArray" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.EmailEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Email entity recognizer", + "description": "Recognizer which recognizes email.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.EmailEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.EmitEvent": { + "$role": "implements(Microsoft.IDialog)", + "title": "Emit a custom event", + "description": "Emit an event. Capture this event with a trigger.", + "type": "object", + "required": [ + "eventName", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "eventName": { + "$role": "expression", + "title": "Event name", + "description": "Name of the event to emit.", + "oneOf": [ + { + "type": "string", + "title": "Built-in event", + "description": "Standard event type.", + "enum": [ + "beginDialog", + "resumeDialog", + "repromptDialog", + "cancelDialog", + "endDialog", + "activityReceived", + "recognizedIntent", + "unknownIntent", + "actionsStarted", + "actionsSaved", + "actionsEnded", + "actionsResumed" + ] + }, + { + "type": "string", + "title": "Custom event", + "description": "Custom event type", + "pattern": "^(?!(beginDialog$|resumeDialog$|repromptDialog$|cancelDialog$|endDialog$|activityReceived$|recognizedIntent$|unknownIntent$|actionsStarted$|actionsSaved$|actionsEnded$|actionsResumed))(\\S){1}.*" + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "eventValue": { + "$ref": "#/definitions/valueExpression", + "title": "Event value", + "description": "Value to emit with the event (optional)." + }, + "bubbleEvent": { + "$ref": "#/definitions/booleanExpression", + "title": "Bubble event", + "description": "If true this event is passed on to parent dialogs." + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.EmitEvent" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.EndDialog": { + "$role": "implements(Microsoft.IDialog)", + "title": "End dialog", + "description": "End this dialog.", + "type": "object", + "$policies": { + "lastAction": true + }, + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "value": { + "$ref": "#/definitions/valueExpression", + "title": "Value", + "description": "Result value returned to the parent dialog.", + "examples": [ + "=dialog.userName", + "='tomato'" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.EndDialog" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.EndTurn": { + "$role": "implements(Microsoft.IDialog)", + "title": "End turn", + "description": "End the current turn without ending the dialog.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.EndTurn" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.FirstSelector": { + "$role": "implements(Microsoft.ITriggerSelector)", + "title": "First trigger selector", + "description": "Selector for first true rule", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.FirstSelector" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.Foreach": { + "$role": "implements(Microsoft.IDialog)", + "title": "For each item", + "description": "Execute actions on each item in an a collection.", + "type": "object", + "required": [ + "itemsProperty", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "itemsProperty": { + "$ref": "#/definitions/stringExpression", + "title": "Items property", + "description": "Property that holds the array.", + "examples": [ + "user.todoList" + ] + }, + "index": { + "$ref": "#/definitions/stringExpression", + "title": "Index property", + "description": "Property that holds the index of the item.", + "default": "dialog.foreach.index" + }, + "value": { + "$ref": "#/definitions/stringExpression", + "title": "Value property", + "description": "Property that holds the value of the item.", + "default": "dialog.foreach.value" + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Actions to execute for each item. Use '$foreach.value' to access the value of each item. Use '$foreach.index' to access the index of each item.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.Foreach" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.ForeachPage": { + "$role": "implements(Microsoft.IDialog)", + "title": "For each page", + "description": "Execute actions on each page (collection of items) in an array.", + "type": "object", + "required": [ + "itemsProperty", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "itemsProperty": { + "$ref": "#/definitions/stringExpression", + "title": "Items property", + "description": "Property that holds the array.", + "examples": [ + "user.todoList" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Actions to execute for each page. Use '$foreach.page' to access each page.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "pageIndex": { + "$ref": "#/definitions/stringExpression", + "title": "Index property", + "description": "Property that holds the index of the page.", + "default": "dialog.foreach.pageindex" + }, + "page": { + "$ref": "#/definitions/stringExpression", + "title": "Page property", + "description": "Property that holds the value of the page.", + "default": "dialog.foreach.page" + }, + "pageSize": { + "$ref": "#/definitions/integerExpression", + "title": "Page size", + "description": "Number of items in each page.", + "default": 10 + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.ForeachPage" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.GetActivityMembers": { + "$role": "implements(Microsoft.IDialog)", + "title": "Get activity members", + "description": "Get the members who are participating in an activity. (BotFrameworkAdapter only)", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "property": { + "$ref": "#/definitions/stringExpression", + "title": "Property", + "description": "Property (named location to store information).", + "examples": [ + "user.age" + ] + }, + "activityId": { + "$ref": "#/definitions/stringExpression", + "title": "Activity Id", + "description": "Activity ID or expression to an activityId to use to get the members. If none is defined then the current activity id will be used.", + "examples": [ + "turn.lastresult.id" + ] + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.GetActivityMembers" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.GetConversationMembers": { + "$role": "implements(Microsoft.IDialog)", + "title": "Get conversation members", + "description": "Get the members who are participating in an conversation. (BotFrameworkAdapter only)", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "property": { + "$ref": "#/definitions/stringExpression", + "title": "Property", + "description": "Property (named location to store information).", + "examples": [ + "user.age" + ] + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.GetConversationMembers" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.GetConversationReference": { + "$role": "implements(Microsoft.IDialog)", + "title": "Get ConversationReference", + "description": "Gets the ConversationReference from current context and stores it in property so it can be used to with ContinueConversation action.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "property": { + "$ref": "#/definitions/stringExpression", + "title": "Property", + "description": "Property (named location to store information).", + "examples": [ + "user.age" + ] + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.GetConversationReference" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.GotoAction": { + "$role": "implements(Microsoft.IDialog)", + "title": "Go to action", + "description": "Go to an an action by id.", + "type": "object", + "required": [ + "actionId", + "$kind" + ], + "$policies": { + "lastAction": true + }, + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "actionId": { + "$ref": "#/definitions/stringExpression", + "title": "Action Id", + "description": "Action Id to execute next" + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.GotoAction" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.GuidEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Guid entity recognizer", + "description": "Recognizer which recognizes guids.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.GuidEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.HashtagEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Hashtag entity recognizer", + "description": "Recognizer which recognizes Hashtags.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.HashtagEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.HttpRequest": { + "$role": "implements(Microsoft.IDialog)", + "type": "object", + "title": "HTTP request", + "description": "Make a HTTP request.", + "required": [ + "url", + "method", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "method": { + "type": "string", + "title": "HTTP method", + "description": "HTTP method to use.", + "enum": [ + "GET", + "POST", + "PATCH", + "PUT", + "DELETE" + ], + "examples": [ + "GET", + "POST" + ] + }, + "url": { + "$ref": "#/definitions/stringExpression", + "title": "Url", + "description": "URL to call (supports data binding).", + "examples": [ + "https://contoso.com" + ] + }, + "body": { + "$ref": "#/definitions/valueExpression", + "title": "Body", + "description": "Body to include in the HTTP call (supports data binding).", + "additionalProperties": true + }, + "resultProperty": { + "$ref": "#/definitions/stringExpression", + "title": "Result property", + "description": "A property to store the result of this action. The result can include any of the 4 properties from the HTTP response: statusCode, reasonPhrase, content, and headers. If the content is JSON it will be a deserialized object. The values can be accessed via .content for example.", + "default": "turn.results", + "examples": [ + "dialog.contosodata" + ] + }, + "contentType": { + "$ref": "#/definitions/stringExpression", + "title": "Content type", + "description": "Content media type for the body.", + "examples": [ + "application/json", + "text/plain" + ] + }, + "headers": { + "type": "object", + "title": "Headers", + "description": "One or more headers to include in the request (supports data binding).", + "additionalProperties": { + "$ref": "#/definitions/stringExpression" + } + }, + "responseType": { + "$ref": "#/definitions/stringExpression", + "title": "Response type", + "description": "Defines the type of HTTP response. Automatically calls the 'Send a response' action if set to 'Activity' or 'Activities'.", + "oneOf": [ + { + "type": "string", + "title": "Standard response", + "description": "Standard response type.", + "enum": [ + "none", + "json", + "activity", + "activities", + "binary" + ], + "default": "json" + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.HttpRequest" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.IActivityTemplate": { + "title": "Microsoft ActivityTemplates", + "description": "Components which are ActivityTemplate, which is string template, an activity, or a implementation of ActivityTemplate", + "$role": "interface", + "oneOf": [ + { + "$ref": "#/definitions/Microsoft.ActivityTemplate" + }, + { + "$ref": "#/definitions/Microsoft.StaticActivityTemplate" + }, + { + "$ref": "#/definitions/botframework.json/definitions/Activity", + "required": [ + "type" + ] + }, + { + "type": "string" + } + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Declarative", + "version": "4.13.2" + } + }, + "Microsoft.IAdapter": { + "$role": "interface", + "title": "Bot adapter", + "description": "Component that enables connecting bots to chat clients and applications.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive.Runtime", + "version": "4.13.2" + }, + "properties": { + "route": { + "type": "string", + "title": "Adapter http route", + "description": "Route where to expose the adapter." + }, + "type": { + "type": "string", + "title": "Adapter type name", + "description": "Adapter type name" + } + } + }, + "Microsoft.IDialog": { + "title": "Microsoft dialogs", + "description": "Components which derive from Dialog", + "$role": "interface", + "oneOf": [ + { + "$ref": "#/definitions/Microsoft.AdaptiveDialog" + }, + { + "$ref": "#/definitions/Microsoft.Ask" + }, + { + "$ref": "#/definitions/Microsoft.AttachmentInput" + }, + { + "$ref": "#/definitions/Microsoft.BeginDialog" + }, + { + "$ref": "#/definitions/Microsoft.BeginSkill" + }, + { + "$ref": "#/definitions/Microsoft.BreakLoop" + }, + { + "$ref": "#/definitions/Microsoft.CancelAllDialogs" + }, + { + "$ref": "#/definitions/Microsoft.CancelDialog" + }, + { + "$ref": "#/definitions/Microsoft.ChoiceInput" + }, + { + "$ref": "#/definitions/Microsoft.ConfirmInput" + }, + { + "$ref": "#/definitions/Microsoft.ContinueConversation" + }, + { + "$ref": "#/definitions/Microsoft.ContinueConversationLater" + }, + { + "$ref": "#/definitions/Microsoft.ContinueLoop" + }, + { + "$ref": "#/definitions/Microsoft.DateTimeInput" + }, + { + "$ref": "#/definitions/Microsoft.DebugBreak" + }, + { + "$ref": "#/definitions/Microsoft.DeleteActivity" + }, + { + "$ref": "#/definitions/Microsoft.DeleteProperties" + }, + { + "$ref": "#/definitions/Microsoft.DeleteProperty" + }, + { + "$ref": "#/definitions/Microsoft.EditActions" + }, + { + "$ref": "#/definitions/Microsoft.EditArray" + }, + { + "$ref": "#/definitions/Microsoft.EmitEvent" + }, + { + "$ref": "#/definitions/Microsoft.EndDialog" + }, + { + "$ref": "#/definitions/Microsoft.EndTurn" + }, + { + "$ref": "#/definitions/Microsoft.Foreach" + }, + { + "$ref": "#/definitions/Microsoft.ForeachPage" + }, + { + "$ref": "#/definitions/Microsoft.GetActivityMembers" + }, + { + "$ref": "#/definitions/Microsoft.GetConversationMembers" + }, + { + "$ref": "#/definitions/Microsoft.GetConversationReference" + }, + { + "$ref": "#/definitions/Microsoft.GotoAction" + }, + { + "$ref": "#/definitions/Microsoft.HttpRequest" + }, + { + "$ref": "#/definitions/Microsoft.IfCondition" + }, + { + "$ref": "#/definitions/Microsoft.LogAction" + }, + { + "$ref": "#/definitions/Microsoft.NumberInput" + }, + { + "$ref": "#/definitions/Microsoft.OAuthInput" + }, + { + "$ref": "#/definitions/Microsoft.QnAMakerDialog" + }, + { + "$ref": "#/definitions/Microsoft.RepeatDialog" + }, + { + "$ref": "#/definitions/Microsoft.ReplaceDialog" + }, + { + "$ref": "#/definitions/Microsoft.SendActivity" + }, + { + "$ref": "#/definitions/Microsoft.SendHandoffActivity" + }, + { + "$ref": "#/definitions/Microsoft.SetProperties" + }, + { + "$ref": "#/definitions/Microsoft.SetProperty" + }, + { + "$ref": "#/definitions/Microsoft.SignOutUser" + }, + { + "$ref": "#/definitions/Microsoft.SwitchCondition" + }, + { + "$ref": "#/definitions/Microsoft.TelemetryTrackEventAction" + }, + { + "$ref": "#/definitions/Microsoft.TextInput" + }, + { + "$ref": "#/definitions/Microsoft.ThrowException" + }, + { + "$ref": "#/definitions/Microsoft.TraceActivity" + }, + { + "$ref": "#/definitions/Microsoft.UpdateActivity" + }, + { + "type": "string", + "pattern": "^(?!(=)).*" + } + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Declarative", + "version": "4.13.2" + } + }, + "Microsoft.IEntityRecognizer": { + "$role": "interface", + "title": "Entity recognizers", + "description": "Components which derive from EntityRecognizer.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "oneOf": [ + { + "$ref": "#/definitions/Microsoft.AgeEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.ConfirmationEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.CurrencyEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.DateTimeEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.DimensionEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.EmailEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.GuidEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.HashtagEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.IpEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.MentionEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.NumberEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.NumberRangeEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.OrdinalEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.PercentageEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.PhoneNumberEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.RegexEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.TemperatureEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.UrlEntityRecognizer" + }, + { + "type": "string", + "title": "Reference to Microsoft.IEntityRecognizer", + "description": "Reference to Microsoft.IEntityRecognizer .dialog file." + } + ] + }, + "Microsoft.IfCondition": { + "$role": "implements(Microsoft.IDialog)", + "title": "If condition", + "description": "Two-way branch the conversation flow based on a condition.", + "type": "object", + "required": [ + "condition", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Expression to evaluate.", + "examples": [ + "user.age > 3" + ] + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + true, + "=user.age > 3" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Actions to execute if condition is true.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "elseActions": { + "type": "array", + "title": "Else", + "description": "Actions to execute if condition is false.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.IfCondition" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.ILanguageGenerator": { + "title": "Microsoft LanguageGenerator", + "description": "Components which dervie from the LanguageGenerator class", + "$role": "interface", + "oneOf": [ + { + "$ref": "#/definitions/Microsoft.ResourceMultiLanguageGenerator" + }, + { + "$ref": "#/definitions/Microsoft.TemplateEngineLanguageGenerator" + }, + { + "type": "string" + } + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + } + }, + "Microsoft.InputDialog": { + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "default": false, + "examples": [ + false, + "=user.isVip" + ] + }, + "prompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Initial prompt", + "description": "Message to send to collect information.", + "examples": [ + "What is your birth date?" + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "unrecognizedPrompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Unrecognized prompt", + "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value.", + "examples": [ + "Sorry, I'm having trouble understanding you. I will just use {this.options.defaultValue} for now. You can say 'I'm 36 years old' to change it." + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "invalidPrompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Invalid prompt", + "description": "Message to send when the user input does not meet any validation expression.", + "examples": [ + "Sorry, '{this.value}' does not work. I need a number between 1-150. What is your age?" + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "defaultValueResponse": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Default value response", + "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value.", + "examples": [ + "Sorry, I'm having trouble understanding you. I will just use {this.options.defaultValue} for now. You can say 'I'm 36 years old' to change it." + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "maxTurnCount": { + "$ref": "#/definitions/integerExpression", + "title": "Max turn count", + "description": "Maximum number of re-prompt attempts to collect information.", + "default": 3, + "minimum": 0, + "maximum": 2147483647, + "examples": [ + 3, + "=settings.xyz" + ] + }, + "validations": { + "type": "array", + "title": "Validation expressions", + "description": "Expression to validate user input.", + "items": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Expression which needs to met for the input to be considered valid", + "examples": [ + "int(this.value) > 1 && int(this.value) <= 150", + "count(this.value) < 300" + ] + } + }, + "property": { + "$ref": "#/definitions/stringExpression", + "title": "Property", + "description": "Property to store collected information. Input will be skipped if property has value (unless 'Always prompt' is true).", + "examples": [ + "$birthday", + "dialog.${user.name}", + "=f(x)" + ] + }, + "alwaysPrompt": { + "$ref": "#/definitions/booleanExpression", + "title": "Always prompt", + "description": "Collect information even if the specified 'property' is not empty.", + "default": false, + "examples": [ + false, + "=$val" + ] + }, + "allowInterruptions": { + "$ref": "#/definitions/booleanExpression", + "title": "Allow Interruptions", + "description": "A boolean expression that determines whether the parent should be allowed to interrupt the input.", + "default": true, + "examples": [ + true, + "=user.xyz" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.InputDialog" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.IpEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "IP entity recognizer", + "description": "Recognizer which recognizes internet IP patterns (like 192.1.1.1).", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.IpEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.IRecognizer": { + "title": "Microsoft recognizer", + "description": "Components which derive from Recognizer class", + "$role": "interface", + "oneOf": [ + { + "$ref": "#/definitions/Microsoft.AgeEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.ChannelMentionEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.ConfirmationEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.CrossTrainedRecognizerSet" + }, + { + "$ref": "#/definitions/Microsoft.CurrencyEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.DateTimeEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.DimensionEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.EmailEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.GuidEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.HashtagEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.IpEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.LuisRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.MentionEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.MultiLanguageRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.NumberEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.NumberRangeEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.OrdinalEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.PercentageEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.PhoneNumberEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.QnAMakerRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.RecognizerSet" + }, + { + "$ref": "#/definitions/Microsoft.RegexEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.RegexRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.TemperatureEntityRecognizer" + }, + { + "$ref": "#/definitions/Microsoft.UrlEntityRecognizer" + }, + { + "type": "string" + } + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Declarative", + "version": "4.13.2" + } + }, + "Microsoft.ITextTemplate": { + "title": "Microsoft TextTemplate", + "description": "Components which derive from TextTemplate class", + "$role": "interface", + "oneOf": [ + { + "$ref": "#/definitions/Microsoft.TextTemplate" + }, + { + "type": "string" + } + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Declarative", + "version": "4.13.2" + } + }, + "Microsoft.ITrigger": { + "$role": "interface", + "title": "Microsoft Triggers", + "description": "Components which derive from OnCondition class.", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "oneOf": [ + { + "$ref": "#/definitions/Microsoft.OnActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnAssignEntity" + }, + { + "$ref": "#/definitions/Microsoft.OnBeginDialog" + }, + { + "$ref": "#/definitions/Microsoft.OnCancelDialog" + }, + { + "$ref": "#/definitions/Microsoft.OnChooseEntity" + }, + { + "$ref": "#/definitions/Microsoft.OnChooseIntent" + }, + { + "$ref": "#/definitions/Microsoft.OnChooseProperty" + }, + { + "$ref": "#/definitions/Microsoft.OnCommandActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnCommandResultActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnCondition" + }, + { + "$ref": "#/definitions/Microsoft.OnContinueConversation" + }, + { + "$ref": "#/definitions/Microsoft.OnConversationUpdateActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnDialogEvent" + }, + { + "$ref": "#/definitions/Microsoft.OnEndOfActions" + }, + { + "$ref": "#/definitions/Microsoft.OnEndOfConversationActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnError" + }, + { + "$ref": "#/definitions/Microsoft.OnEventActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnHandoffActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnInstallationUpdateActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnIntent" + }, + { + "$ref": "#/definitions/Microsoft.OnInvokeActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnMessageActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnMessageDeleteActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnMessageReactionActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnMessageUpdateActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnQnAMatch" + }, + { + "$ref": "#/definitions/Microsoft.OnRepromptDialog" + }, + { + "$ref": "#/definitions/Microsoft.OnTypingActivity" + }, + { + "$ref": "#/definitions/Microsoft.OnUnknownIntent" + }, + { + "type": "string", + "title": "Reference to Microsoft.ITrigger", + "description": "Reference to Microsoft.ITrigger .dialog file." + } + ] + }, + "Microsoft.ITriggerSelector": { + "$role": "interface", + "title": "Selectors", + "description": "Components which derive from TriggerSelector class.", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "oneOf": [ + { + "$ref": "#/definitions/Microsoft.ConditionalSelector" + }, + { + "$ref": "#/definitions/Microsoft.FirstSelector" + }, + { + "$ref": "#/definitions/Microsoft.MostSpecificSelector" + }, + { + "$ref": "#/definitions/Microsoft.RandomSelector" + }, + { + "$ref": "#/definitions/Microsoft.TrueSelector" + }, + { + "type": "string", + "title": "Reference to Microsoft.ITriggerSelector", + "description": "Reference to Microsoft.ITriggerSelector .dialog file." + } + ] + }, + "Microsoft.LanguagePolicy": { + "title": "Language policy", + "description": "This represents a policy map for locales lookups to use for language", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": { + "type": "array", + "title": "Per-locale policy", + "description": "Language policy per locale.", + "items": { + "type": "string", + "title": "Locale", + "description": "Locale like en-us." + } + }, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.LanguagePolicy" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.LogAction": { + "$role": "implements(Microsoft.IDialog)", + "title": "Log to console", + "description": "Log a message to the host application. Send a TraceActivity to Bot Framework Emulator (optional).", + "type": "object", + "required": [ + "text", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + true, + "=user.age > 3" + ] + }, + "text": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Text", + "description": "Information to log.", + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "label": { + "$ref": "#/definitions/stringExpression", + "title": "Label", + "description": "Label for the trace activity (used to identify it in a list of trace activities.)" + }, + "traceActivity": { + "$ref": "#/definitions/booleanExpression", + "title": "Send trace activity", + "description": "If true, automatically sends a TraceActivity (view in Bot Framework Emulator)." + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.LogAction" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.LuisRecognizer": { + "$role": "implements(Microsoft.IRecognizer)", + "title": "LUIS Recognizer", + "description": "LUIS recognizer.", + "type": "object", + "required": [ + "applicationId", + "endpoint", + "endpointKey", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.AI.Luis", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional unique id using with RecognizerSet. Other recognizers should return 'DeferToRecognizer_{Id}' intent when cross training data for this recognizer." + }, + "applicationId": { + "$ref": "#/definitions/stringExpression", + "title": "LUIS application id", + "description": "Application ID for your model from the LUIS service." + }, + "version": { + "$ref": "#/definitions/stringExpression", + "title": "LUIS version", + "description": "Optional version to target. If null then predictionOptions.Slot is used." + }, + "endpoint": { + "$ref": "#/definitions/stringExpression", + "title": "LUIS endpoint", + "description": "Endpoint to use for LUIS service like https://westus.api.cognitive.microsoft.com." + }, + "endpointKey": { + "$ref": "#/definitions/stringExpression", + "title": "LUIS prediction key", + "description": "LUIS prediction key used to call endpoint." + }, + "externalEntityRecognizer": { + "$kind": "Microsoft.IRecognizer", + "title": "External entity recognizer", + "description": "Entities recognized by this recognizer will be passed to LUIS as external entities.", + "$ref": "#/definitions/Microsoft.IRecognizer" + }, + "dynamicLists": { + "$ref": "#/definitions/arrayExpression", + "title": "Dynamic lists", + "description": "Runtime defined entity lists.", + "items": { + "title": "Entity list", + "description": "Lists of canonical values and synonyms for an entity.", + "type": "object", + "properties": { + "entity": { + "title": "Entity", + "description": "Entity to extend with a dynamic list.", + "type": "string" + }, + "list": { + "title": "Dynamic list", + "description": "List of canonical forms and synonyms.", + "type": "array", + "items": { + "type": "object", + "title": "List entry", + "description": "Canonical form and synonynms.", + "properties": { + "canonicalForm": { + "title": "Canonical form", + "description": "Resolution if any synonym matches.", + "type": "string" + }, + "synonyms": { + "title": "Synonyms", + "description": "List of synonyms for a canonical form.", + "type": "array", + "items": { + "title": "Synonym", + "description": "Synonym for canonical form.", + "type": "string" + } + } + } + } + } + } + } + }, + "predictionOptions": { + "type": "object", + "title": "Prediction options", + "description": "Options to control LUIS prediction behavior.", + "properties": { + "includeAllIntents": { + "$ref": "#/definitions/booleanExpression", + "title": "Include all intents", + "description": "True for all intents, false for only top intent." + }, + "includeInstanceData": { + "$ref": "#/definitions/booleanExpression", + "title": "Include $instance", + "description": "True to include $instance metadata in the LUIS response." + }, + "log": { + "$ref": "#/definitions/booleanExpression", + "title": "Log utterances", + "description": "True to log utterances on LUIS service." + }, + "preferExternalEntities": { + "$ref": "#/definitions/booleanExpression", + "title": "Prefer external entities", + "description": "True to prefer external entities to those generated by LUIS models." + }, + "slot": { + "$ref": "#/definitions/stringExpression", + "title": "Slot", + "description": "Slot to use for talking to LUIS service like production or staging." + } + } + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.LuisRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.MentionEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Mentions entity recognizer", + "description": "Recognizer which recognizes @Mentions", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.MentionEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.MostSpecificSelector": { + "$role": "implements(Microsoft.ITriggerSelector)", + "title": "Most specific trigger selector", + "description": "Select most specific true events with optional additional selector", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "selector": { + "$kind": "Microsoft.ITriggerSelector", + "$ref": "#/definitions/Microsoft.ITriggerSelector" + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.MostSpecificSelector" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.MultiLanguageRecognizer": { + "$role": "implements(Microsoft.IRecognizer)", + "title": "Multi-language recognizer", + "description": "Configure one recognizer per language and the specify the language fallback policy.", + "type": "object", + "required": [ + "recognizers", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional unique id using with RecognizerSet. Other recognizers should return 'DeferToRecognizer_{Id}' intent when cross training data for this recognizer." + }, + "languagePolicy": { + "$kind": "Microsoft.LanguagePolicy", + "type": "object", + "title": "Language policy", + "description": "Defines fall back languages to try per user input language.", + "$ref": "#/definitions/Microsoft.LanguagePolicy" + }, + "recognizers": { + "type": "object", + "title": "Recognizers", + "description": "Map of language -> Recognizer", + "additionalProperties": { + "$kind": "Microsoft.IRecognizer", + "$ref": "#/definitions/Microsoft.IRecognizer" + } + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.MultiLanguageRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.NumberEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Number entity recognizer", + "description": "Recognizer which recognizes numbers.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.NumberEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.NumberInput": { + "$role": [ + "implements(Microsoft.IDialog)", + "extends(Microsoft.InputDialog)" + ], + "title": "Number input dialog", + "description": "Collect information - Ask for a number.", + "type": "object", + "$policies": { + "interactive": true + }, + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "defaultValue": { + "$ref": "#/definitions/numberExpression", + "title": "Default value", + "description": "'Property' will be set to the value of this expression when max turn count is exceeded.", + "examples": [ + 13, + "=user.age" + ] + }, + "value": { + "$ref": "#/definitions/numberExpression", + "title": "Value", + "description": "'Property' will be set to the value of this expression unless it evaluates to null.", + "examples": [ + 13, + "=user.age" + ] + }, + "outputFormat": { + "$ref": "#/definitions/expression", + "title": "Output format", + "description": "Expression to format the number output.", + "examples": [ + "=this.value", + "=int(this.text)" + ] + }, + "defaultLocale": { + "$ref": "#/definitions/stringExpression", + "title": "Default locale", + "description": "Default locale to use if there is no locale available..", + "default": "en-us" + }, + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "default": false, + "examples": [ + false, + "=user.isVip" + ] + }, + "prompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Initial prompt", + "description": "Message to send to collect information.", + "examples": [ + "What is your birth date?" + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "unrecognizedPrompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Unrecognized prompt", + "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value.", + "examples": [ + "Sorry, I'm having trouble understanding you. I will just use {this.options.defaultValue} for now. You can say 'I'm 36 years old' to change it." + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "invalidPrompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Invalid prompt", + "description": "Message to send when the user input does not meet any validation expression.", + "examples": [ + "Sorry, '{this.value}' does not work. I need a number between 1-150. What is your age?" + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "defaultValueResponse": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Default value response", + "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value.", + "examples": [ + "Sorry, I'm having trouble understanding you. I will just use {this.options.defaultValue} for now. You can say 'I'm 36 years old' to change it." + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "maxTurnCount": { + "$ref": "#/definitions/integerExpression", + "title": "Max turn count", + "description": "Maximum number of re-prompt attempts to collect information.", + "default": 3, + "minimum": 0, + "maximum": 2147483647, + "examples": [ + 3, + "=settings.xyz" + ] + }, + "validations": { + "type": "array", + "title": "Validation expressions", + "description": "Expression to validate user input.", + "items": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Expression which needs to met for the input to be considered valid", + "examples": [ + "int(this.value) > 1 && int(this.value) <= 150", + "count(this.value) < 300" + ] + } + }, + "property": { + "$ref": "#/definitions/stringExpression", + "title": "Property", + "description": "Property to store collected information. Input will be skipped if property has value (unless 'Always prompt' is true).", + "examples": [ + "$birthday", + "dialog.${user.name}", + "=f(x)" + ] + }, + "alwaysPrompt": { + "$ref": "#/definitions/booleanExpression", + "title": "Always prompt", + "description": "Collect information even if the specified 'property' is not empty.", + "default": false, + "examples": [ + false, + "=$val" + ] + }, + "allowInterruptions": { + "$ref": "#/definitions/booleanExpression", + "title": "Allow Interruptions", + "description": "A boolean expression that determines whether the parent should be allowed to interrupt the input.", + "default": true, + "examples": [ + true, + "=user.xyz" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.NumberInput" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.NumberRangeEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Number range entity recognizer", + "description": "Recognizer which recognizes ranges of numbers (Example:2 to 5).", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.NumberRangeEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OAuthInput": { + "$role": "implements(Microsoft.IDialog)", + "title": "OAuthInput Dialog", + "description": "Collect login information before each request.", + "type": "object", + "required": [ + "connectionName", + "$kind" + ], + "$policies": { + "interactive": true + }, + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "connectionName": { + "$ref": "#/definitions/stringExpression", + "title": "Connection name", + "description": "The connection name configured in Azure Web App Bot OAuth settings.", + "examples": [ + "msgraphOAuthConnection" + ] + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + true, + "=user.age > 3" + ] + }, + "text": { + "$ref": "#/definitions/stringExpression", + "title": "Text", + "description": "Text shown in the OAuth signin card.", + "examples": [ + "Please sign in. ", + "=concat(x,y,z)" + ] + }, + "title": { + "$ref": "#/definitions/stringExpression", + "title": "Title", + "description": "Title shown in the OAuth signin card.", + "examples": [ + "Login" + ] + }, + "timeout": { + "$ref": "#/definitions/integerExpression", + "title": "Timeout", + "description": "Time out setting for the OAuth signin card.", + "default": 900000 + }, + "property": { + "$ref": "#/definitions/stringExpression", + "title": "Token property", + "description": "Property to store the OAuth token result. WARNING: Changing this location is not recommended as you should call OAuthInput immediately before each use of the token.", + "default": "turn.token", + "examples": [ + "dialog.token" + ] + }, + "invalidPrompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Invalid prompt", + "description": "Message to send if user response is invalid.", + "examples": [ + "Sorry, the login info you provided is not valid." + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "defaultValueResponse": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Default value response", + "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value.", + "examples": [ + "Login failed." + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "maxTurnCount": { + "$ref": "#/definitions/integerExpression", + "title": "Max turn count", + "description": "Maximum number of re-prompt attempts to collect information.", + "default": 3, + "examples": [ + 3 + ] + }, + "defaultValue": { + "$ref": "#/definitions/expression", + "title": "Default value", + "description": "Expression to examine on each turn of the conversation as possible value to the property.", + "examples": [ + "@token" + ] + }, + "allowInterruptions": { + "$ref": "#/definitions/booleanExpression", + "title": "Allow interruptions", + "description": "A boolean expression that determines whether the parent should be allowed to interrupt the input.", + "default": true, + "examples": [ + true, + "=f(x)" + ] + }, + "alwaysPrompt": { + "$ref": "#/definitions/booleanExpression", + "title": "Always prompt", + "description": "Collect information even if the specified 'property' is not empty.", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OAuthInput" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnActivity": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On activity", + "description": "Actions to perform on receipt of a generic activity.", + "type": "object", + "required": [ + "type", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "type": { + "type": "string", + "title": "Activity type", + "description": "The Activity.Type to match" + }, + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnAssignEntity": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On entity assignment", + "description": "Actions to apply an operation on a property and value.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "operation": { + "type": "string", + "title": "Operation", + "description": "Operation filter on event." + }, + "property": { + "type": "string", + "title": "Property", + "description": "Property filter on event." + }, + "value": { + "type": "string", + "title": "Value", + "description": "Value filter on event." + }, + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnAssignEntity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnBeginDialog": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On begin dialog", + "description": "Actions to perform when this dialog begins.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnBeginDialog" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnCancelDialog": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On cancel dialog", + "description": "Actions to perform on cancel dialog event.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnCancelDialog" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnChooseEntity": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On choose entity", + "description": "Actions to be performed when value is ambiguous for operator and property.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "operation": { + "type": "string", + "title": "Operation", + "description": "Operation filter on event." + }, + "property": { + "type": "string", + "title": "Property", + "description": "Property filter on event." + }, + "value": { + "type": "string", + "title": "Ambiguous value", + "description": "Ambiguous value filter on event." + }, + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnChooseEntity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnChooseIntent": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On ambiguous intent", + "description": "Actions to perform on when an intent is ambiguous.", + "type": "object", + "required": [ + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "intents": { + "type": "array", + "title": "Intents", + "description": "Intents that must be in the ChooseIntent result for this condition to trigger.", + "items": { + "title": "Intent", + "description": "Intent name to trigger on.", + "type": "string" + } + }, + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnChooseIntent" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnChooseProperty": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On choose property", + "description": "Actions to take when there are multiple possible mappings of entities to properties and operations.", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "type": "object", + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnChooseProperty" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnCommandActivity": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On Command activity", + "description": "Actions to perform on receipt of an activity with type 'Command'. Overrides Intent trigger.", + "type": "object", + "required": [ + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnCommandActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnCommandResultActivity": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On Command Result activity", + "description": "Actions to perform on receipt of an activity with type 'CommandResult'. Overrides Intent trigger.", + "type": "object", + "required": [ + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnCommandResultActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnCondition": { + "$role": "implements(Microsoft.ITrigger)", + "title": "On condition", + "description": "Actions to perform when specified condition is true.", + "type": "object", + "required": [ + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnCondition" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnContinueConversation": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On continue conversation", + "description": "Actions to perform when a conversation is started up again from a ContinueConversationLater action.", + "type": "object", + "required": [ + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnContinueConversation" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnConversationUpdateActivity": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On ConversationUpdate activity", + "description": "Actions to perform on receipt of an activity with type 'ConversationUpdate'.", + "type": "object", + "required": [ + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnConversationUpdateActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnDialogEvent": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On dialog event", + "description": "Actions to perform when a specific dialog event occurs.", + "type": "object", + "required": [ + "event", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "event": { + "type": "string", + "title": "Dialog event name", + "description": "Name of dialog event." + }, + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnDialogEvent" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnEndOfActions": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On end of actions", + "description": "Actions to take when there are no more actions in the current dialog.", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "type": "object", + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnEndOfActions" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnEndOfConversationActivity": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On EndOfConversation activity", + "description": "Actions to perform on receipt of an activity with type 'EndOfConversation'.", + "type": "object", + "required": [ + "$kind" + ], + "$policies": { + "nonInteractive": true + }, + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnEndOfConversationActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnError": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On error", + "description": "Action to perform when an 'Error' dialog event occurs.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnError" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnEventActivity": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On Event activity", + "description": "Actions to perform on receipt of an activity with type 'Event'.", + "type": "object", + "required": [ + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnEventActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnHandoffActivity": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On Handoff activity", + "description": "Actions to perform on receipt of an activity with type 'HandOff'.", + "type": "object", + "required": [ + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnHandoffActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnInstallationUpdateActivity": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On InstallationUpdate activity", + "description": "Actions to perform on receipt of an activity with type 'InstallationUpdate'.", + "type": "object", + "required": [ + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnInstallationUpdateActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnIntent": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On intent recognition", + "description": "Actions to perform when specified intent is recognized.", + "type": "object", + "required": [ + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "intent": { + "type": "string", + "title": "Intent", + "description": "Name of intent." + }, + "entities": { + "type": "array", + "title": "Entities", + "description": "Required entities.", + "items": { + "type": "string", + "title": "Entity", + "description": "Entity that must be present." + } + }, + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnIntent" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnInvokeActivity": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On Invoke activity", + "description": "Actions to perform on receipt of an activity with type 'Invoke'.", + "type": "object", + "required": [ + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnInvokeActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnMessageActivity": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On Message activity", + "description": "Actions to perform on receipt of an activity with type 'Message'. Overrides Intent trigger.", + "type": "object", + "required": [ + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnMessageActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnMessageDeleteActivity": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On MessageDelete activity", + "description": "Actions to perform on receipt of an activity with type 'MessageDelete'.", + "type": "object", + "required": [ + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnMessageDeleteActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnMessageReactionActivity": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On MessageReaction activity", + "description": "Actions to perform on receipt of an activity with type 'MessageReaction'.", + "type": "object", + "required": [ + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnMessageReactionActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnMessageUpdateActivity": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On MessageUpdate activity", + "description": "Actions to perform on receipt of an activity with type 'MessageUpdate'.", + "type": "object", + "required": [ + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnMessageUpdateActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnQnAMatch": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On QnAMaker match", + "description": "Actions to perform on when an match from QnAMaker is found.", + "type": "object", + "required": [ + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnQnAMatch" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnRepromptDialog": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On RepromptDialog", + "description": "Actions to perform when 'RepromptDialog' event occurs.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnRepromptDialog" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnTypingActivity": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On Typing activity", + "description": "Actions to perform on receipt of an activity with type 'Typing'.", + "type": "object", + "required": [ + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnTypingActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OnUnknownIntent": { + "$role": [ + "implements(Microsoft.ITrigger)", + "extends(Microsoft.OnCondition)" + ], + "title": "On unknown intent", + "description": "Action to perform when user input is unrecognized or if none of the 'on intent recognition' triggers match recognized intent.", + "type": "object", + "required": [ + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "condition": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Condition (expression).", + "examples": [ + "user.vip == true" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Sequence of actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "priority": { + "$ref": "#/definitions/numberExpression", + "title": "Priority", + "description": "Priority for trigger with 0 being the highest and < 0 ignored." + }, + "runOnce": { + "$ref": "#/definitions/booleanExpression", + "title": "Run Once", + "description": "True if rule should run once per unique conditions", + "examples": [ + true, + "=f(x)" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OnUnknownIntent" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.OrdinalEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Ordinal entity recognizer", + "description": "Recognizer which recognizes ordinals (example: first, second, 3rd).", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.OrdinalEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.PercentageEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Percentage entity recognizer", + "description": "Recognizer which recognizes percentages.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.PercentageEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.PhoneNumberEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Phone number entity recognizer", + "description": "Recognizer which recognizes phone numbers.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.PhoneNumberEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.QnAMakerDialog": { + "$role": "implements(Microsoft.IDialog)", + "title": "QnAMaker dialog", + "description": "Dialog which uses QnAMAker knowledge base to answer questions.", + "type": "object", + "required": [ + "knowledgeBaseId", + "endpointKey", + "hostname", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.AI.QnA", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "knowledgeBaseId": { + "$ref": "#/definitions/stringExpression", + "title": "KnowledgeBase Id", + "description": "KnowledgeBase Id of your QnA Maker KnowledgeBase.", + "default": "=settings.qna.knowledgebaseid" + }, + "endpointKey": { + "$ref": "#/definitions/stringExpression", + "title": "Endpoint key", + "description": "Endpoint key for the QnA Maker KB.", + "default": "=settings.qna.endpointkey" + }, + "hostname": { + "$ref": "#/definitions/stringExpression", + "title": "Hostname", + "description": "Hostname for your QnA Maker service.", + "default": "=settings.qna.hostname", + "examples": [ + "https://yourserver.azurewebsites.net/qnamaker" + ] + }, + "noAnswer": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Fallback answer", + "description": "Default answer to return when none found in KB.", + "default": "Sorry, I did not find an answer.", + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "threshold": { + "$ref": "#/definitions/numberExpression", + "title": "Threshold", + "description": "Threshold score to filter results.", + "default": 0.3 + }, + "activeLearningCardTitle": { + "$ref": "#/definitions/stringExpression", + "title": "Active learning card title", + "description": "Title for active learning suggestions card.", + "default": "Did you mean:" + }, + "cardNoMatchText": { + "$ref": "#/definitions/stringExpression", + "title": "Card no match text", + "description": "Text for no match option.", + "default": "None of the above." + }, + "cardNoMatchResponse": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Card no match response", + "description": "Custom response when no match option was selected.", + "default": "Thanks for the feedback.", + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "strictFilters": { + "$ref": "#/definitions/arrayExpression", + "title": "Strict filters", + "description": "Metadata filters to use when calling the QnA Maker KB.", + "items": { + "type": "object", + "title": "Metadata filter", + "description": "Metadata filter.", + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "Name of filter property.", + "maximum": 100 + }, + "value": { + "type": "string", + "title": "Value", + "description": "Value to filter on.", + "maximum": 100 + } + } + } + }, + "top": { + "$ref": "#/definitions/numberExpression", + "title": "Top", + "description": "The number of answers you want to retrieve.", + "default": 3 + }, + "isTest": { + "type": "boolean", + "title": "IsTest", + "description": "True, if pointing to Test environment, else false.", + "default": false + }, + "rankerType": { + "$ref": "#/definitions/stringExpression", + "title": "Ranker type", + "description": "Type of Ranker.", + "oneOf": [ + { + "title": "Standard ranker", + "description": "Standard ranker types.", + "enum": [ + "default", + "questionOnly", + "autoSuggestQuestion" + ], + "default": "default" + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "strictFiltersJoinOperator": { + "$ref": "#/definitions/stringExpression", + "title": "StrictFiltersJoinOperator", + "description": "Join operator for Strict Filters.", + "oneOf": [ + { + "title": "Join operator", + "description": "Value of Join Operator to be used as conjunction with Strict Filter values.", + "enum": [ + "AND", + "OR" + ], + "default": "AND" + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.QnAMakerDialog" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.QnAMakerRecognizer": { + "$role": "implements(Microsoft.IRecognizer)", + "title": "QnAMaker recognizer", + "description": "Recognizer for generating QnAMatch intents from a KB.", + "type": "object", + "required": [ + "knowledgeBaseId", + "endpointKey", + "hostname", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.AI.QnA", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional unique id using with RecognizerSet." + }, + "knowledgeBaseId": { + "$ref": "#/definitions/stringExpression", + "title": "KnowledgeBase Id", + "description": "Knowledge base Id of your QnA Maker knowledge base.", + "default": "=settings.qna.knowledgebaseid" + }, + "endpointKey": { + "$ref": "#/definitions/stringExpression", + "title": "Endpoint key", + "description": "Endpoint key for the QnA Maker KB.", + "default": "=settings.qna.endpointkey" + }, + "hostname": { + "$ref": "#/definitions/stringExpression", + "title": "Hostname", + "description": "Hostname for your QnA Maker service.", + "default": "=settings.qna.hostname", + "examples": [ + "https://yourserver.azurewebsites.net/qnamaker" + ] + }, + "threshold": { + "$ref": "#/definitions/numberExpression", + "title": "Threshold", + "description": "Threshold score to filter results.", + "default": 0.3 + }, + "strictFilters": { + "$ref": "#/definitions/arrayExpression", + "title": "Strict filters", + "description": "Metadata filters to use when calling the QnA Maker KB.", + "items": { + "type": "object", + "title": "Metadata filters", + "description": "Metadata filters to use when querying QnA Maker KB.", + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "Name to filter on.", + "maximum": 100 + }, + "value": { + "type": "string", + "title": "Value", + "description": "Value to restrict filter.", + "maximum": 100 + } + } + } + }, + "top": { + "$ref": "#/definitions/numberExpression", + "title": "Top", + "description": "The number of answers you want to retrieve.", + "default": 3 + }, + "isTest": { + "$ref": "#/definitions/booleanExpression", + "title": "Use test environment", + "description": "True, if pointing to Test environment, else false.", + "examples": [ + true, + "=f(x)" + ] + }, + "rankerType": { + "title": "Ranker type", + "description": "Type of Ranker.", + "oneOf": [ + { + "type": "string", + "title": "Ranker type", + "description": "Type of Ranker.", + "enum": [ + "default", + "questionOnly", + "autoSuggestQuestion" + ], + "default": "default" + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "strictFiltersJoinOperator": { + "$ref": "#/definitions/stringExpression", + "title": "StrictFiltersJoinOperator", + "description": "Join operator for Strict Filters.", + "oneOf": [ + { + "title": "Join operator", + "description": "Value of Join Operator to be used as onjuction with Strict Filter values.", + "enum": [ + "AND", + "OR" + ], + "default": "AND" + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "includeDialogNameInMetadata": { + "$ref": "#/definitions/booleanExpression", + "title": "Include dialog name", + "description": "When set to false, the dialog name will not be passed to QnAMaker. (default) is true", + "default": true, + "examples": [ + true, + "=f(x)" + ] + }, + "metadata": { + "$ref": "#/definitions/arrayExpression", + "title": "Metadata filters", + "description": "Metadata filters to use when calling the QnA Maker KB.", + "items": { + "type": "object", + "title": "Metadata filter", + "description": "Metadata filter to use when calling the QnA Maker KB.", + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "Name of value to test." + }, + "value": { + "type": "string", + "title": "Value", + "description": "Value to filter against." + } + } + } + }, + "context": { + "$ref": "#/definitions/objectExpression", + "title": "QnA request context", + "description": "Context to use for ranking." + }, + "qnaId": { + "$ref": "#/definitions/integerExpression", + "title": "QnA Id", + "description": "A number or expression which is the QnAId to paass to QnAMaker API." + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.QnAMakerRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.RandomSelector": { + "$role": "implements(Microsoft.ITriggerSelector)", + "title": "Random rule selector", + "description": "Select most specific true rule.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "seed": { + "type": "integer", + "title": "Random seed", + "description": "Random seed to start random number generation." + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.RandomSelector" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.RecognizerSet": { + "$role": "implements(Microsoft.IRecognizer)", + "title": "Recognizer set", + "description": "Creates the union of the intents and entities of the recognizers in the set.", + "type": "object", + "required": [ + "recognizers", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional unique id using with RecognizerSet. Other recognizers should return 'DeferToRecognizer_{Id}' intent when cross training data for this recognizer." + }, + "recognizers": { + "type": "array", + "title": "Recognizers", + "description": "List of Recognizers defined for this set.", + "items": { + "$kind": "Microsoft.IRecognizer", + "$ref": "#/definitions/Microsoft.IRecognizer" + } + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.RecognizerSet" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.RegexEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Regex entity recognizer", + "description": "Recognizer which recognizes patterns of input based on regex.", + "type": "object", + "required": [ + "name", + "pattern", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "Name of the entity" + }, + "pattern": { + "type": "string", + "title": "Pattern", + "description": "Pattern expressed as regular expression." + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.RegexEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.RegexRecognizer": { + "$role": "implements(Microsoft.IRecognizer)", + "title": "Regex recognizer", + "description": "Use regular expressions to recognize intents and entities from user input.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional unique id using with RecognizerSet. Other recognizers should return 'DeferToRecognizer_{Id}' intent when cross training data for this recognizer." + }, + "intents": { + "type": "array", + "title": "RegEx patterns to intents", + "description": "Collection of patterns to match for an intent.", + "items": { + "type": "object", + "title": "Pattern", + "description": "Intent and regex pattern.", + "properties": { + "intent": { + "type": "string", + "title": "Intent", + "description": "The intent name." + }, + "pattern": { + "type": "string", + "title": "Pattern", + "description": "The regular expression pattern." + } + } + } + }, + "entities": { + "type": "array", + "title": "Entity recognizers", + "description": "Collection of entity recognizers to use.", + "items": { + "$kind": "Microsoft.IEntityRecognizer", + "$ref": "#/definitions/Microsoft.IEntityRecognizer" + } + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.RegexRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.RepeatDialog": { + "$role": "implements(Microsoft.IDialog)", + "type": "object", + "title": "Repeat dialog", + "description": "Repeat current dialog.", + "$policies": { + "lastAction": true + }, + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "allowLoop": { + "$ref": "#/definitions/booleanExpression", + "title": "AllowLoop", + "description": "Optional condition which if true will allow loop of the repeated dialog.", + "examples": [ + "user.age > 3" + ] + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "options": { + "$ref": "#/definitions/objectExpression", + "title": "Options", + "description": "One or more options that are passed to the dialog that is called.", + "additionalProperties": { + "type": "string", + "title": "Options", + "description": "Options for repeating dialog." + } + }, + "activityProcessed": { + "$ref": "#/definitions/booleanExpression", + "title": "Activity processed", + "description": "When set to false, the dialog that is called can process the current activity.", + "default": true + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.RepeatDialog" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.ReplaceDialog": { + "$role": "implements(Microsoft.IDialog)", + "type": "object", + "title": "Replace dialog", + "description": "Replace current dialog with another dialog.", + "$policies": { + "lastAction": true + }, + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "dialog": { + "oneOf": [ + { + "$kind": "Microsoft.IDialog", + "pattern": "^(?!(=)).*", + "title": "Dialog", + "$ref": "#/definitions/Microsoft.IDialog" + }, + { + "$ref": "#/definitions/equalsExpression", + "examples": [ + "=settings.dialogId" + ] + } + ], + "title": "Dialog name", + "description": "Name of the dialog to call." + }, + "options": { + "$ref": "#/definitions/objectExpression", + "title": "Options", + "description": "One or more options that are passed to the dialog that is called.", + "additionalProperties": { + "type": "string", + "title": "Options", + "description": "Options for replacing dialog." + } + }, + "activityProcessed": { + "$ref": "#/definitions/booleanExpression", + "title": "Activity processed", + "description": "When set to false, the dialog that is called can process the current activity.", + "default": true + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.ReplaceDialog" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.ResourceMultiLanguageGenerator": { + "$role": "implements(Microsoft.ILanguageGenerator)", + "title": "Resource multi-language generator", + "description": "MultiLanguage Generator which is bound to resource by resource Id.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional generator ID." + }, + "resourceId": { + "type": "string", + "title": "Resource Id", + "description": "Resource which is the root language generator. Other generaters with the same name and language suffix will be loaded into this generator and used based on the Language Policy.", + "default": "dialog.result" + }, + "languagePolicy": { + "type": "object", + "title": "Language policy", + "description": "Set alternate language policy for this generator. If not set, the global language policy will be used." + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.ResourceMultiLanguageGenerator" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.SendActivity": { + "$role": "implements(Microsoft.IDialog)", + "title": "Send an activity", + "description": "Respond with an activity.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action." + }, + "activity": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Activity", + "description": "Activity to send.", + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.SendActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.SendHandoffActivity": { + "$role": "implements(Microsoft.IDialog)", + "title": "Send a handoff activity", + "description": "Sends a handoff activity to trigger a handoff request.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "context": { + "$ref": "#/definitions/objectExpression", + "title": "Context", + "description": "Context to send with the handoff request" + }, + "transcript": { + "$ref": "#/definitions/objectExpression", + "title": "transcript", + "description": "Transcript to send with the handoff request" + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.SendHandoffActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.SetProperties": { + "$role": "implements(Microsoft.IDialog)", + "title": "Set property", + "description": "Set one or more property values.", + "type": "object", + "required": [ + "assignments", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + true, + "=user.age > 3" + ] + }, + "assignments": { + "type": "array", + "title": "Assignments", + "description": "Property value assignments to set.", + "items": { + "type": "object", + "title": "Assignment", + "description": "Property assignment.", + "properties": { + "property": { + "$ref": "#/definitions/stringExpression", + "title": "Property", + "description": "Property (named location to store information).", + "examples": [ + "user.age" + ] + }, + "value": { + "$ref": "#/definitions/valueExpression", + "title": "Value", + "description": "New value or expression.", + "examples": [ + "='milk'", + "=dialog.favColor", + "=dialog.favColor == 'red'" + ] + } + } + } + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.SetProperties" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.SetProperty": { + "$role": "implements(Microsoft.IDialog)", + "title": "Set property", + "description": "Set property to a value.", + "type": "object", + "required": [ + "property", + "value", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + true, + "=user.age > 3" + ] + }, + "property": { + "$ref": "#/definitions/stringExpression", + "title": "Property", + "description": "Property (named location to store information).", + "examples": [ + "user.age" + ] + }, + "value": { + "$ref": "#/definitions/valueExpression", + "title": "Value", + "description": "New value or expression.", + "examples": [ + "='milk'", + "=dialog.favColor", + "=dialog.favColor == 'red'" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.SetProperty" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.SignOutUser": { + "$role": "implements(Microsoft.IDialog)", + "title": "Sign out user", + "description": "Sign a user out that was logged in previously using OAuthInput.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "userId": { + "$ref": "#/definitions/stringExpression", + "title": "UserId", + "description": "Expression to an user to signout. Default is user.id.", + "default": "=user.id" + }, + "connectionName": { + "$ref": "#/definitions/stringExpression", + "title": "Connection name", + "description": "Connection name that was used with OAuthInput to log a user in." + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + true, + "=user.age > 3" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.SignOutUser" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.StaticActivityTemplate": { + "$role": "implements(Microsoft.IActivityTemplate)", + "title": "Microsoft static activity template", + "description": "This allows you to define a static Activity object", + "type": "object", + "required": [ + "activity", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "activity": { + "$ref": "#/definitions/botframework.json/definitions/Activity", + "title": "Activity", + "description": "A static Activity to used.", + "required": [ + "type" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.StaticActivityTemplate" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.SwitchCondition": { + "$role": "implements(Microsoft.IDialog)", + "title": "Switch condition", + "description": "Execute different actions based on the value of a property.", + "type": "object", + "required": [ + "condition", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "condition": { + "$ref": "#/definitions/stringExpression", + "title": "Condition", + "description": "Property to evaluate.", + "examples": [ + "user.favColor" + ] + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + true, + "=user.age > 3" + ] + }, + "cases": { + "type": "array", + "title": "Cases", + "description": "Actions for each possible condition.", + "items": { + "type": "object", + "title": "Case", + "description": "Case and actions.", + "required": [ + "value" + ], + "properties": { + "value": { + "type": [ + "number", + "integer", + "boolean", + "string" + ], + "title": "Value", + "description": "The value to compare the condition with.", + "examples": [ + "red", + "true", + "13" + ] + }, + "actions": { + "type": "array", + "title": "Actions", + "description": "Actions to execute.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + } + } + } + }, + "default": { + "type": "array", + "title": "Default", + "description": "Actions to execute if none of the cases meet the condition.", + "items": { + "$kind": "Microsoft.IDialog", + "$ref": "#/definitions/Microsoft.IDialog" + } + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.SwitchCondition" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.TelemetryTrackEventAction": { + "$role": "implements(Microsoft.IDialog)", + "type": "object", + "title": "Telemetry - track event", + "description": "Track a custom event using the registered Telemetry Client.", + "required": [ + "url", + "method", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "eventName": { + "$ref": "#/definitions/stringExpression", + "title": "Event name", + "description": "The name of the event to track.", + "examples": [ + "MyEventStarted", + "MyEventCompleted" + ] + }, + "properties": { + "type": "object", + "title": "Properties", + "description": "One or more properties to attach to the event being tracked.", + "additionalProperties": { + "$ref": "#/definitions/stringExpression" + } + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.TelemetryTrackEventAction" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.TemperatureEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Temperature recognizer", + "description": "Recognizer which recognizes temperatures.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.TemperatureEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.TemplateEngineLanguageGenerator": { + "$role": "implements(Microsoft.ILanguageGenerator)", + "title": "Template multi-language generator", + "description": "Template Generator which allows only inline evaluation of templates.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional generator ID." + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.TemplateEngineLanguageGenerator" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.TextInput": { + "$role": [ + "implements(Microsoft.IDialog)", + "extends(Microsoft.InputDialog)" + ], + "type": "object", + "title": "Text input dialog", + "description": "Collection information - Ask for a word or sentence.", + "$policies": { + "interactive": true + }, + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "defaultValue": { + "$ref": "#/definitions/stringExpression", + "title": "Default value", + "description": "'Property' will be set to the value of this expression when max turn count is exceeded.", + "examples": [ + "hello world", + "Hello ${user.name}", + "=concat(user.firstname, user.lastName)" + ] + }, + "value": { + "$ref": "#/definitions/stringExpression", + "title": "Value", + "description": "'Property' will be set to the value of this expression unless it evaluates to null.", + "examples": [ + "hello world", + "Hello ${user.name}", + "=concat(user.firstname, user.lastName)" + ] + }, + "outputFormat": { + "$ref": "#/definitions/stringExpression", + "title": "Output format", + "description": "Expression to format the output.", + "examples": [ + "=toUpper(this.value)", + "${toUpper(this.value)}" + ] + }, + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "default": false, + "examples": [ + false, + "=user.isVip" + ] + }, + "prompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Initial prompt", + "description": "Message to send to collect information.", + "examples": [ + "What is your birth date?" + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "unrecognizedPrompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Unrecognized prompt", + "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value.", + "examples": [ + "Sorry, I'm having trouble understanding you. I will just use {this.options.defaultValue} for now. You can say 'I'm 36 years old' to change it." + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "invalidPrompt": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Invalid prompt", + "description": "Message to send when the user input does not meet any validation expression.", + "examples": [ + "Sorry, '{this.value}' does not work. I need a number between 1-150. What is your age?" + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "defaultValueResponse": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Default value response", + "description": "Message to send when max turn count (if specified) has been exceeded and the default value is selected as the value.", + "examples": [ + "Sorry, I'm having trouble understanding you. I will just use {this.options.defaultValue} for now. You can say 'I'm 36 years old' to change it." + ], + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "maxTurnCount": { + "$ref": "#/definitions/integerExpression", + "title": "Max turn count", + "description": "Maximum number of re-prompt attempts to collect information.", + "default": 3, + "minimum": 0, + "maximum": 2147483647, + "examples": [ + 3, + "=settings.xyz" + ] + }, + "validations": { + "type": "array", + "title": "Validation expressions", + "description": "Expression to validate user input.", + "items": { + "$ref": "#/definitions/condition", + "title": "Condition", + "description": "Expression which needs to met for the input to be considered valid", + "examples": [ + "int(this.value) > 1 && int(this.value) <= 150", + "count(this.value) < 300" + ] + } + }, + "property": { + "$ref": "#/definitions/stringExpression", + "title": "Property", + "description": "Property to store collected information. Input will be skipped if property has value (unless 'Always prompt' is true).", + "examples": [ + "$birthday", + "dialog.${user.name}", + "=f(x)" + ] + }, + "alwaysPrompt": { + "$ref": "#/definitions/booleanExpression", + "title": "Always prompt", + "description": "Collect information even if the specified 'property' is not empty.", + "default": false, + "examples": [ + false, + "=$val" + ] + }, + "allowInterruptions": { + "$ref": "#/definitions/booleanExpression", + "title": "Allow Interruptions", + "description": "A boolean expression that determines whether the parent should be allowed to interrupt the input.", + "default": true, + "examples": [ + true, + "=user.xyz" + ] + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.TextInput" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.TextTemplate": { + "$role": "implements(Microsoft.ITextTemplate)", + "title": "Microsoft TextTemplate", + "description": "Use LG Templates to create text", + "type": "object", + "required": [ + "template", + "$kind" + ], + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "template": { + "title": "Template", + "description": "Language Generator template to evaluate to create the text.", + "type": "string" + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.TextTemplate" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.ThrowException": { + "$role": "implements(Microsoft.IDialog)", + "title": "Throw an exception", + "description": "Throw an exception. Capture this exception with OnError trigger.", + "type": "object", + "required": [ + "errorValue", + "$kind" + ], + "$policies": { + "lastAction": true + }, + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + "user.age > 3" + ] + }, + "errorValue": { + "$ref": "#/definitions/valueExpression", + "title": "Error value", + "description": "Error value to throw." + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.ThrowException" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.TraceActivity": { + "$role": "implements(Microsoft.IDialog)", + "title": "Send a TraceActivity", + "description": "Send a trace activity to the transcript logger and/ or Bot Framework Emulator.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + true, + "=user.age > 3" + ] + }, + "name": { + "$ref": "#/definitions/stringExpression", + "title": "Name", + "description": "Name of the trace activity" + }, + "label": { + "$ref": "#/definitions/stringExpression", + "title": "Label", + "description": "Label for the trace activity (used to identify it in a list of trace activities.)" + }, + "valueType": { + "$ref": "#/definitions/stringExpression", + "title": "Value type", + "description": "Type of value" + }, + "value": { + "$ref": "#/definitions/valueExpression", + "title": "Value", + "description": "Property that holds the value to send as trace activity." + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.TraceActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.TrueSelector": { + "$role": "implements(Microsoft.ITriggerSelector)", + "title": "True trigger selector", + "description": "Selector for all true events", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.TrueSelector" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.UpdateActivity": { + "$role": "implements(Microsoft.IDialog)", + "title": "Update an activity", + "description": "Respond with an activity.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional id for the dialog" + }, + "disabled": { + "$ref": "#/definitions/booleanExpression", + "title": "Disabled", + "description": "Optional condition which if true will disable this action.", + "examples": [ + true, + "=user.age > 3" + ] + }, + "activityId": { + "$ref": "#/definitions/stringExpression", + "title": "Activity Id", + "description": "An string expression with the activity id to update.", + "examples": [ + "=turn.lastresult.id" + ] + }, + "activity": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Activity", + "description": "Activity to send.", + "$ref": "#/definitions/Microsoft.IActivityTemplate" + }, + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.UpdateActivity" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "Microsoft.UrlEntityRecognizer": { + "$role": [ + "implements(Microsoft.IRecognizer)", + "implements(Microsoft.IEntityRecognizer)" + ], + "title": "Url recognizer", + "description": "Recognizer which recognizes urls.", + "type": "object", + "$package": { + "name": "Microsoft.Bot.Builder.Dialogs.Adaptive", + "version": "4.13.2" + }, + "required": [ + "$kind" + ], + "additionalProperties": false, + "patternProperties": { + "^\\$": { + "title": "Tooling property", + "description": "Open ended property for tooling." + } + }, + "properties": { + "$kind": { + "title": "Kind of dialog object", + "description": "Defines the valid properties for the component you are configuring (from a dialog .schema file)", + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.]*$", + "const": "Microsoft.UrlEntityRecognizer" + }, + "$designer": { + "title": "Designer information", + "type": "object", + "description": "Extra information for the Bot Framework Composer." + } + } + }, + "numberExpression": { + "$role": "expression", + "title": "Number or expression", + "description": "Number constant or expression to evaluate.", + "oneOf": [ + { + "type": "number", + "title": "Number", + "description": "Number constant.", + "default": 0, + "examples": [ + 15.5 + ] + }, + { + "$ref": "#/definitions/equalsExpression", + "examples": [ + "=dialog.quantity" + ] + } + ] + }, + "objectExpression": { + "$role": "expression", + "title": "Object or expression", + "description": "Object or expression to evaluate.", + "oneOf": [ + { + "type": "object", + "title": "Object", + "description": "Object constant." + }, + { + "$ref": "#/definitions/equalsExpression" + } + ] + }, + "role": { + "title": "$role", + "description": "Defines the role played in the dialog schema from [expression|interface|implements($kind)|extends($kind)].", + "type": "string", + "pattern": "^((expression)|(interface)|(implements\\([a-zA-Z][a-zA-Z0-9.]*\\))|(extends\\([a-zA-Z][a-zA-Z0-9.]*\\)))$" + }, + "stringExpression": { + "$role": "expression", + "title": "String or expression", + "description": "Interpolated string or expression to evaluate.", + "oneOf": [ + { + "type": "string", + "title": "String", + "description": "Interpolated string", + "pattern": "^(?!(=)).*", + "examples": [ + "Hello ${user.name}" + ] + }, + { + "$ref": "#/definitions/equalsExpression", + "examples": [ + "=concat('x','y','z')" + ] + } + ] + }, + "valueExpression": { + "$role": "expression", + "title": "Any or expression", + "description": "Any constant or expression to evaluate.", + "oneOf": [ + { + "type": "object", + "title": "Object", + "description": "Object constant." + }, + { + "type": "array", + "title": "Array", + "description": "Array constant." + }, + { + "type": "string", + "title": "String", + "description": "Interpolated string.", + "pattern": "^(?!(=)).*", + "examples": [ + "Hello ${user.name}" + ] + }, + { + "type": "boolean", + "title": "Boolean", + "description": "Boolean constant", + "examples": [ + false + ] + }, + { + "type": "number", + "title": "Number", + "description": "Number constant.", + "examples": [ + 15.5 + ] + }, + { + "$ref": "#/definitions/equalsExpression", + "examples": [ + "=..." + ] + } + ] + }, + "schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Core schema meta-schema", + "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/schema" + } + }, + "nonNegativeInteger": { + "type": "integer", + "minimum": 0 + }, + "nonNegativeIntegerDefault0": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "simpleTypes": { + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] + }, + "stringArray": { + "type": "array", + "uniqueItems": true, + "default": [], + "items": { + "type": "string" + } + } + }, + "type": [ + "object", + "boolean" + ], + "default": true, + "properties": { + "$schema": { + "type": "string", + "format": "uri" + }, + "$ref": { + "type": "string", + "format": "uri-reference" + }, + "$comment": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": true, + "readOnly": { + "type": "boolean", + "default": false + }, + "writeOnly": { + "type": "boolean", + "default": false + }, + "examples": { + "type": "array", + "items": true + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "maxLength": { + "$ref": "#/definitions/schema/definitions/nonNegativeInteger" + }, + "minLength": { + "$ref": "#/definitions/schema/definitions/nonNegativeIntegerDefault0" + }, + "pattern": { + "type": "string", + "format": "regex" + }, + "additionalItems": { + "$ref": "#/definitions/schema" + }, + "items": { + "anyOf": [ + { + "$ref": "#/definitions/schema" + }, + { + "$ref": "#/definitions/schema/definitions/schemaArray" + } + ], + "default": true + }, + "maxItems": { + "$ref": "#/definitions/schema/definitions/nonNegativeInteger" + }, + "minItems": { + "$ref": "#/definitions/schema/definitions/nonNegativeIntegerDefault0" + }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "contains": { + "$ref": "#/definitions/schema" + }, + "maxProperties": { + "$ref": "#/definitions/schema/definitions/nonNegativeInteger" + }, + "minProperties": { + "$ref": "#/definitions/schema/definitions/nonNegativeIntegerDefault0" + }, + "required": { + "$ref": "#/definitions/schema/definitions/stringArray" + }, + "additionalProperties": { + "$ref": "#/definitions/schema" + }, + "definitions": { + "type": "object", + "default": {}, + "additionalProperties": { + "$ref": "#/definitions/schema" + } + }, + "properties": { + "type": "object", + "default": {}, + "additionalProperties": { + "$ref": "#/definitions/schema" + } + }, + "patternProperties": { + "type": "object", + "propertyNames": { + "format": "regex" + }, + "default": {}, + "additionalProperties": { + "$ref": "#/definitions/schema" + } + }, + "dependencies": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "$ref": "#/definitions/schema" + }, + { + "$ref": "#/definitions/schema/definitions/stringArray" + } + ] + } + }, + "propertyNames": { + "$ref": "#/definitions/schema" + }, + "const": true, + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": true + }, + "type": { + "anyOf": [ + { + "$ref": "#/definitions/schema/definitions/simpleTypes" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/schema/definitions/simpleTypes" + }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "format": { + "type": "string" + }, + "contentMediaType": { + "type": "string" + }, + "contentEncoding": { + "type": "string" + }, + "if": { + "$ref": "#/definitions/schema" + }, + "then": { + "$ref": "#/definitions/schema" + }, + "else": { + "$ref": "#/definitions/schema" + }, + "allOf": { + "$ref": "#/definitions/schema/definitions/schemaArray" + }, + "anyOf": { + "$ref": "#/definitions/schema/definitions/schemaArray" + }, + "oneOf": { + "$ref": "#/definitions/schema/definitions/schemaArray" + }, + "not": { + "$ref": "#/definitions/schema" + } + } + }, + "botframework.json": { + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "ChannelAccount": { + "description": "Channel account information needed to route a message", + "title": "ChannelAccount", + "type": "object", + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "description": "Channel id for the user or bot on this channel (Example: joe@smith.com, or @joesmith or\n123456)", + "type": "string", + "title": "id" + }, + "name": { + "description": "Display friendly name", + "type": "string", + "title": "name" + }, + "aadObjectId": { + "description": "This account's object ID within Azure Active Directory (AAD)", + "type": "string", + "title": "aadObjectId" + }, + "role": { + "description": "Role of the entity behind the account (Example: User, Bot, etc.). Possible values include:\n'user', 'bot'", + "type": "string", + "title": "role" + } + } + }, + "ConversationAccount": { + "description": "Channel account information for a conversation", + "title": "ConversationAccount", + "type": "object", + "required": [ + "conversationType", + "id", + "isGroup", + "name" + ], + "properties": { + "isGroup": { + "description": "Indicates whether the conversation contains more than two participants at the time the\nactivity was generated", + "type": "boolean", + "title": "isGroup" + }, + "conversationType": { + "description": "Indicates the type of the conversation in channels that distinguish between conversation types", + "type": "string", + "title": "conversationType" + }, + "id": { + "description": "Channel id for the user or bot on this channel (Example: joe@smith.com, or @joesmith or\n123456)", + "type": "string", + "title": "id" + }, + "name": { + "description": "Display friendly name", + "type": "string", + "title": "name" + }, + "aadObjectId": { + "description": "This account's object ID within Azure Active Directory (AAD)", + "type": "string", + "title": "aadObjectId" + }, + "role": { + "description": "Role of the entity behind the account (Example: User, Bot, etc.). Possible values include:\n'user', 'bot'", + "enum": [ + "bot", + "user" + ], + "type": "string", + "title": "role" + } + } + }, + "MessageReaction": { + "description": "Message reaction object", + "title": "MessageReaction", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "description": "Message reaction type. Possible values include: 'like', 'plusOne'", + "type": "string", + "title": "type" + } + } + }, + "CardAction": { + "description": "A clickable action", + "title": "CardAction", + "type": "object", + "required": [ + "title", + "type", + "value" + ], + "properties": { + "type": { + "description": "The type of action implemented by this button. Possible values include: 'openUrl', 'imBack',\n'postBack', 'playAudio', 'playVideo', 'showImage', 'downloadFile', 'signin', 'call',\n'payment', 'messageBack'", + "type": "string", + "title": "type" + }, + "title": { + "description": "Text description which appears on the button", + "type": "string", + "title": "title" + }, + "image": { + "description": "Image URL which will appear on the button, next to text label", + "type": "string", + "title": "image" + }, + "text": { + "description": "Text for this action", + "type": "string", + "title": "text" + }, + "displayText": { + "description": "(Optional) text to display in the chat feed if the button is clicked", + "type": "string", + "title": "displayText" + }, + "value": { + "description": "Supplementary parameter for action. Content of this property depends on the ActionType", + "title": "value" + }, + "channelData": { + "description": "Channel-specific data associated with this action", + "title": "channelData" + } + } + }, + "SuggestedActions": { + "description": "SuggestedActions that can be performed", + "title": "SuggestedActions", + "type": "object", + "required": [ + "actions", + "to" + ], + "properties": { + "to": { + "description": "Ids of the recipients that the actions should be shown to. These Ids are relative to the\nchannelId and a subset of all recipients of the activity", + "type": "array", + "title": "to", + "items": { + "title": "Id", + "description": "Id of recipient.", + "type": "string" + } + }, + "actions": { + "description": "Actions that can be shown to the user", + "type": "array", + "title": "actions", + "items": { + "$ref": "#/definitions/botframework.json/definitions/CardAction" + } + } + } + }, + "Attachment": { + "description": "An attachment within an activity", + "title": "Attachment", + "type": "object", + "required": [ + "contentType" + ], + "properties": { + "contentType": { + "description": "mimetype/Contenttype for the file", + "type": "string", + "title": "contentType" + }, + "contentUrl": { + "description": "Content Url", + "type": "string", + "title": "contentUrl" + }, + "content": { + "type": "object", + "description": "Embedded content", + "title": "content" + }, + "name": { + "description": "(OPTIONAL) The name of the attachment", + "type": "string", + "title": "name" + }, + "thumbnailUrl": { + "description": "(OPTIONAL) Thumbnail associated with attachment", + "type": "string", + "title": "thumbnailUrl" + } + } + }, + "Entity": { + "description": "Metadata object pertaining to an activity", + "title": "Entity", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "description": "Type of this entity (RFC 3987 IRI)", + "type": "string", + "title": "type" + } + } + }, + "ConversationReference": { + "description": "An object relating to a particular point in a conversation", + "title": "ConversationReference", + "type": "object", + "required": [ + "bot", + "channelId", + "conversation", + "serviceUrl" + ], + "properties": { + "activityId": { + "description": "(Optional) ID of the activity to refer to", + "type": "string", + "title": "activityId" + }, + "user": { + "description": "(Optional) User participating in this conversation", + "$ref": "#/definitions/botframework.json/definitions/ChannelAccount", + "title": "user" + }, + "bot": { + "$ref": "#/definitions/botframework.json/definitions/ChannelAccount", + "description": "Bot participating in this conversation", + "title": "bot" + }, + "conversation": { + "$ref": "#/definitions/botframework.json/definitions/ConversationAccount", + "description": "Conversation reference", + "title": "conversation" + }, + "channelId": { + "description": "Channel ID", + "type": "string", + "title": "channelId" + }, + "serviceUrl": { + "description": "Service endpoint where operations concerning the referenced conversation may be performed", + "type": "string", + "title": "serviceUrl" + } + } + }, + "TextHighlight": { + "description": "Refers to a substring of content within another field", + "title": "TextHighlight", + "type": "object", + "required": [ + "occurrence", + "text" + ], + "properties": { + "text": { + "description": "Defines the snippet of text to highlight", + "type": "string", + "title": "text" + }, + "occurrence": { + "description": "Occurrence of the text field within the referenced text, if multiple exist.", + "type": "number", + "title": "occurrence" + } + } + }, + "SemanticAction": { + "description": "Represents a reference to a programmatic action", + "title": "SemanticAction", + "type": "object", + "required": [ + "entities", + "id" + ], + "properties": { + "id": { + "description": "ID of this action", + "type": "string", + "title": "id" + }, + "entities": { + "description": "Entities associated with this action", + "type": "object", + "title": "entities", + "additionalProperties": { + "$ref": "#/definitions/botframework.json/definitions/Entity" + } + } + } + }, + "Activity": { + "description": "An Activity is the basic communication type for the Bot Framework 3.0 protocol.", + "title": "Activity", + "type": "object", + "properties": { + "type": { + "description": "Contains the activity type. Possible values include: 'message', 'contactRelationUpdate',\n'conversationUpdate', 'typing', 'endOfConversation', 'event', 'invoke', 'deleteUserData',\n'messageUpdate', 'messageDelete', 'installationUpdate', 'messageReaction', 'suggestion',\n'trace', 'handoff'", + "type": "string", + "title": "type" + }, + "id": { + "description": "Contains an ID that uniquely identifies the activity on the channel.", + "type": "string", + "title": "id" + }, + "timestamp": { + "description": "Contains the date and time that the message was sent, in UTC, expressed in ISO-8601 format.", + "type": "string", + "format": "date-time", + "title": "timestamp" + }, + "localTimestamp": { + "description": "Contains the date and time that the message was sent, in local time, expressed in ISO-8601\nformat.\nFor example, 2016-09-23T13:07:49.4714686-07:00.", + "type": "string", + "format": "date-time", + "title": "localTimestamp" + }, + "localTimezone": { + "description": "Contains the name of the timezone in which the message, in local time, expressed in IANA Time\nZone database format.\nFor example, America/Los_Angeles.", + "type": "string", + "title": "localTimezone" + }, + "serviceUrl": { + "description": "Contains the URL that specifies the channel's service endpoint. Set by the channel.", + "type": "string", + "title": "serviceUrl" + }, + "channelId": { + "description": "Contains an ID that uniquely identifies the channel. Set by the channel.", + "type": "string", + "title": "channelId" + }, + "from": { + "$ref": "#/definitions/botframework.json/definitions/ChannelAccount", + "description": "Identifies the sender of the message.", + "title": "from" + }, + "conversation": { + "$ref": "#/definitions/botframework.json/definitions/ConversationAccount", + "description": "Identifies the conversation to which the activity belongs.", + "title": "conversation" + }, + "recipient": { + "$ref": "#/definitions/botframework.json/definitions/ChannelAccount", + "description": "Identifies the recipient of the message.", + "title": "recipient" + }, + "textFormat": { + "description": "Format of text fields Default:markdown. Possible values include: 'markdown', 'plain', 'xml'", + "type": "string", + "title": "textFormat" + }, + "attachmentLayout": { + "description": "The layout hint for multiple attachments. Default: list. Possible values include: 'list',\n'carousel'", + "type": "string", + "title": "attachmentLayout" + }, + "membersAdded": { + "description": "The collection of members added to the conversation.", + "type": "array", + "title": "membersAdded", + "items": { + "$ref": "#/definitions/botframework.json/definitions/ChannelAccount" + } + }, + "membersRemoved": { + "description": "The collection of members removed from the conversation.", + "type": "array", + "title": "membersRemoved", + "items": { + "$ref": "#/definitions/botframework.json/definitions/ChannelAccount" + } + }, + "reactionsAdded": { + "description": "The collection of reactions added to the conversation.", + "type": "array", + "title": "reactionsAdded", + "items": { + "$ref": "#/definitions/botframework.json/definitions/MessageReaction" + } + }, + "reactionsRemoved": { + "description": "The collection of reactions removed from the conversation.", + "type": "array", + "title": "reactionsRemoved", + "items": { + "$ref": "#/definitions/botframework.json/definitions/MessageReaction" + } + }, + "topicName": { + "description": "The updated topic name of the conversation.", + "type": "string", + "title": "topicName" + }, + "historyDisclosed": { + "description": "Indicates whether the prior history of the channel is disclosed.", + "type": "boolean", + "title": "historyDisclosed" + }, + "locale": { + "description": "A locale name for the contents of the text field.\nThe locale name is a combination of an ISO 639 two- or three-letter culture code associated\nwith a language\nand an ISO 3166 two-letter subculture code associated with a country or region.\nThe locale name can also correspond to a valid BCP-47 language tag.", + "type": "string", + "title": "locale" + }, + "text": { + "description": "The text content of the message.", + "type": "string", + "title": "text" + }, + "speak": { + "description": "The text to speak.", + "type": "string", + "title": "speak" + }, + "inputHint": { + "description": "Indicates whether your bot is accepting,\nexpecting, or ignoring user input after the message is delivered to the client. Possible\nvalues include: 'acceptingInput', 'ignoringInput', 'expectingInput'", + "type": "string", + "title": "inputHint" + }, + "summary": { + "description": "The text to display if the channel cannot render cards.", + "type": "string", + "title": "summary" + }, + "suggestedActions": { + "description": "The suggested actions for the activity.", + "$ref": "#/definitions/botframework.json/definitions/SuggestedActions", + "title": "suggestedActions" + }, + "attachments": { + "description": "Attachments", + "type": "array", + "title": "attachments", + "items": { + "$ref": "#/definitions/botframework.json/definitions/Attachment" + } + }, + "entities": { + "description": "Represents the entities that were mentioned in the message.", + "type": "array", + "title": "entities", + "items": { + "$ref": "#/definitions/botframework.json/definitions/Entity" + } + }, + "channelData": { + "description": "Contains channel-specific content.", + "title": "channelData" + }, + "action": { + "description": "Indicates whether the recipient of a contactRelationUpdate was added or removed from the\nsender's contact list.", + "type": "string", + "title": "action" + }, + "replyToId": { + "description": "Contains the ID of the message to which this message is a reply.", + "type": "string", + "title": "replyToId" + }, + "label": { + "description": "A descriptive label for the activity.", + "type": "string", + "title": "label" + }, + "valueType": { + "description": "The type of the activity's value object.", + "type": "string", + "title": "valueType" + }, + "value": { + "description": "A value that is associated with the activity.", + "title": "value" + }, + "name": { + "description": "The name of the operation associated with an invoke or event activity.", + "type": "string", + "title": "name" + }, + "relatesTo": { + "description": "A reference to another conversation or activity.", + "$ref": "#/definitions/botframework.json/definitions/ConversationReference", + "title": "relatesTo" + }, + "code": { + "description": "The a code for endOfConversation activities that indicates why the conversation ended.\nPossible values include: 'unknown', 'completedSuccessfully', 'userCancelled', 'botTimedOut',\n'botIssuedInvalidMessage', 'channelFailed'", + "type": "string", + "title": "code" + }, + "expiration": { + "description": "The time at which the activity should be considered to be \"expired\" and should not be\npresented to the recipient.", + "type": "string", + "format": "date-time", + "title": "expiration" + }, + "importance": { + "description": "The importance of the activity. Possible values include: 'low', 'normal', 'high'", + "type": "string", + "title": "importance" + }, + "deliveryMode": { + "description": "A delivery hint to signal to the recipient alternate delivery paths for the activity.\nThe default delivery mode is \"default\". Possible values include: 'normal', 'notification'", + "type": "string", + "title": "deliveryMode" + }, + "listenFor": { + "description": "List of phrases and references that speech and language priming systems should listen for", + "type": "array", + "title": "listenFor", + "items": { + "type": "string", + "title": "Phrase", + "description": "Phrase to listen for." + } + }, + "textHighlights": { + "description": "The collection of text fragments to highlight when the activity contains a ReplyToId value.", + "type": "array", + "title": "textHighlights", + "items": { + "$ref": "#/definitions/botframework.json/definitions/TextHighlight" + } + }, + "semanticAction": { + "$ref": "#/definitions/botframework.json/definitions/SemanticAction", + "description": "An optional programmatic action accompanying this request", + "title": "semanticAction" + } + } + } + } + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/schemas/sdk.uischema b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/schemas/sdk.uischema new file mode 100644 index 0000000000..9cf19c6919 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/schemas/sdk.uischema @@ -0,0 +1,1409 @@ +{ + "$schema": "https://schemas.botframework.com/schemas/ui/v1.0/ui.schema", + "Microsoft.AdaptiveDialog": { + "form": { + "description": "This configures a data driven dialog via a collection of events and actions.", + "helpLink": "https://aka.ms/bf-composer-docs-dialog", + "hidden": [ + "triggers", + "generator", + "selector", + "schema" + ], + "label": "Adaptive dialog", + "order": [ + "recognizer", + "*" + ], + "properties": { + "recognizer": { + "description": "To understand what the user says, your dialog needs a \"Recognizer\"; that includes example words and sentences that users may use.", + "label": "Language Understanding" + } + } + } + }, + "Microsoft.Ask": { + "flow": { + "body": { + "field": "activity", + "widget": "LgWidget" + }, + "footer": { + "description": "= Default operation", + "property": "=action.defaultOperation", + "widget": "PropertyDescription" + }, + "header": { + "colors": { + "icon": "#5C2E91", + "theme": "#EEEAF4" + }, + "icon": "MessageBot", + "widget": "ActionHeader" + }, + "hideFooter": "=!action.defaultOperation", + "widget": "ActionCard" + }, + "form": { + "helpLink": "https://aka.ms/bfc-send-activity", + "label": "Send a response to ask a question", + "order": [ + "activity", + "*" + ], + "subtitle": "Ask Activity" + } + }, + "Microsoft.AttachmentInput": { + "flow": { + "body": "=action.prompt", + "botAsks": { + "body": { + "defaultContent": "", + "field": "prompt", + "widget": "LgWidget" + }, + "header": { + "colors": { + "icon": "#5C2E91", + "theme": "#EEEAF4" + }, + "icon": "MessageBot", + "widget": "ActionHeader" + }, + "widget": "ActionCard" + }, + "nowrap": true, + "userInput": { + "header": { + "colors": { + "icon": "#0078D4", + "theme": "#E5F0FF" + }, + "disableSDKTitle": true, + "icon": "User", + "menu": "none", + "widget": "ActionHeader" + }, + "widget": "ActionCard" + }, + "widget": "PromptWidget" + }, + "form": { + "helpLink": "https://aka.ms/bfc-ask-for-user-input", + "label": "Prompt for a file or an attachment", + "properties": { + "property": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "Attachment Input" + } + }, + "Microsoft.BeginDialog": { + "flow": { + "body": { + "dialog": "=action.dialog", + "widget": "DialogRef" + }, + "footer": { + "description": "= Return value", + "property": "=action.resultProperty", + "widget": "PropertyDescription" + }, + "hideFooter": "=!action.resultProperty", + "widget": "ActionCard" + }, + "form": { + "helpLink": "https://aka.ms/bfc-understanding-dialogs", + "label": "Begin a new dialog", + "order": [ + "dialog", + "options", + "resultProperty", + "*" + ], + "properties": { + "resultProperty": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "Begin Dialog" + } + }, + "Microsoft.BeginSkill": { + "flow": { + "body": { + "operation": "Host", + "resource": "=coalesce(action.skillEndpoint, \"?\")", + "singleline": true, + "widget": "ResourceOperation" + }, + "colors": { + "color": "#FFFFFF", + "icon": "#FFFFFF", + "theme": "#004578" + }, + "footer": { + "description": "= Result", + "property": "=action.resultProperty", + "widget": "PropertyDescription" + }, + "hideFooter": "=!action.resultProperty", + "icon": "Library", + "widget": "ActionCard" + }, + "form": { + "helpLink": "https://aka.ms/bf-composer-docs-connect-skill", + "label": "Connect to a skill", + "properties": { + "resultProperty": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "Skill Dialog" + } + }, + "Microsoft.BreakLoop": { + "form": { + "label": "Break out of loop", + "subtitle": "Break out of loop" + } + }, + "Microsoft.CancelAllDialogs": { + "flow": { + "body": { + "description": "(Event)", + "property": "=coalesce(action.eventName, \"?\")", + "widget": "PropertyDescription" + }, + "widget": "ActionCard" + }, + "form": { + "helpLink": "https://aka.ms/bfc-understanding-dialogs", + "label": "Cancel all active dialogs", + "subtitle": "Cancel All Dialogs" + } + }, + "Microsoft.ChoiceInput": { + "flow": { + "body": "=action.prompt", + "botAsks": { + "body": { + "defaultContent": "", + "field": "prompt", + "widget": "LgWidget" + }, + "header": { + "colors": { + "icon": "#5C2E91", + "theme": "#EEEAF4" + }, + "icon": "MessageBot", + "widget": "ActionHeader" + }, + "widget": "ActionCard" + }, + "nowrap": true, + "userInput": { + "header": { + "colors": { + "icon": "#0078D4", + "theme": "#E5F0FF" + }, + "disableSDKTitle": true, + "icon": "User", + "menu": "none", + "widget": "ActionHeader" + }, + "widget": "ActionCard" + }, + "widget": "PromptWidget" + }, + "form": { + "helpLink": "https://aka.ms/bfc-ask-for-user-input", + "label": "Prompt with multi-choice", + "properties": { + "property": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "Choice Input" + } + }, + "Microsoft.ConfirmInput": { + "flow": { + "body": "=action.prompt", + "botAsks": { + "body": { + "defaultContent": "", + "field": "prompt", + "widget": "LgWidget" + }, + "header": { + "colors": { + "icon": "#5C2E91", + "theme": "#EEEAF4" + }, + "icon": "MessageBot", + "widget": "ActionHeader" + }, + "widget": "ActionCard" + }, + "nowrap": true, + "userInput": { + "header": { + "colors": { + "icon": "#0078D4", + "theme": "#E5F0FF" + }, + "disableSDKTitle": true, + "icon": "User", + "menu": "none", + "widget": "ActionHeader" + }, + "widget": "ActionCard" + }, + "widget": "PromptWidget" + }, + "form": { + "helpLink": "https://aka.ms/bfc-ask-for-user-input", + "label": "Prompt for confirmation", + "properties": { + "property": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "Confirm Input" + } + }, + "Microsoft.ContinueLoop": { + "form": { + "label": "Continue loop", + "subtitle": "Continue loop" + } + }, + "Microsoft.DateTimeInput": { + "flow": { + "body": "=action.prompt", + "botAsks": { + "body": { + "defaultContent": "", + "field": "prompt", + "widget": "LgWidget" + }, + "header": { + "colors": { + "icon": "#5C2E91", + "theme": "#EEEAF4" + }, + "icon": "MessageBot", + "widget": "ActionHeader" + }, + "widget": "ActionCard" + }, + "nowrap": true, + "userInput": { + "header": { + "colors": { + "icon": "#0078D4", + "theme": "#E5F0FF" + }, + "disableSDKTitle": true, + "icon": "User", + "menu": "none", + "widget": "ActionHeader" + }, + "widget": "ActionCard" + }, + "widget": "PromptWidget" + }, + "form": { + "helpLink": "https://aka.ms/bfc-ask-for-user-input", + "label": "Prompt for a date or a time", + "properties": { + "property": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "Date Time Input" + } + }, + "Microsoft.DebugBreak": { + "form": { + "label": "Debug Break" + } + }, + "Microsoft.DeleteProperties": { + "flow": { + "body": { + "items": "=action.properties", + "widget": "ListOverview" + }, + "widget": "ActionCard" + }, + "form": { + "helpLink": "https://aka.ms/bfc-using-memory", + "label": "Delete properties", + "properties": { + "properties": { + "intellisenseScopes": [ + "user-variables" + ] + } + }, + "subtitle": "Delete Properties" + } + }, + "Microsoft.DeleteProperty": { + "flow": { + "body": "=action.property", + "widget": "ActionCard" + }, + "form": { + "helpLink": "https://aka.ms/bfc-using-memory", + "label": "Delete a property", + "properties": { + "property": { + "intellisenseScopes": [ + "user-variables" + ] + } + }, + "subtitle": "Delete Property" + } + }, + "Microsoft.EditActions": { + "flow": { + "body": "=action.changeType", + "widget": "ActionCard" + }, + "form": { + "label": "Modify active dialog", + "subtitle": "Edit Actions" + } + }, + "Microsoft.EditArray": { + "flow": { + "body": { + "operation": "=coalesce(action.changeType, \"?\")", + "resource": "=coalesce(action.itemsProperty, \"?\")", + "widget": "ResourceOperation" + }, + "footer": { + "description": "= Result", + "property": "=action.resultProperty", + "widget": "PropertyDescription" + }, + "hideFooter": "=!action.resultProperty", + "widget": "ActionCard" + }, + "form": { + "helpLink": "https://aka.ms/bfc-using-memory", + "label": "Edit an array property", + "properties": { + "itemsProperty": { + "intellisenseScopes": [ + "user-variables" + ] + }, + "resultProperty": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "Edit Array" + } + }, + "Microsoft.EmitEvent": { + "flow": { + "body": { + "description": "(Event)", + "property": "=coalesce(action.eventName, \"?\")", + "widget": "PropertyDescription" + }, + "widget": "ActionCard" + }, + "form": { + "helpLink": "https://aka.ms/bfc-custom-events", + "label": "Emit a custom event", + "subtitle": "Emit Event" + } + }, + "Microsoft.EndDialog": { + "form": { + "helpLink": "https://aka.ms/bfc-understanding-dialogs", + "label": "End this dialog", + "subtitle": "End Dialog" + } + }, + "Microsoft.EndTurn": { + "form": { + "helpLink": "https://aka.ms/bfc-understanding-dialogs", + "label": "End turn", + "subtitle": "End Turn" + } + }, + "Microsoft.Foreach": { + "flow": { + "loop": { + "body": "=concat(\"Each value in \", coalesce(action.itemsProperty, \"?\"))", + "widget": "ActionCard" + }, + "nowrap": true, + "widget": "ForeachWidget" + }, + "form": { + "helpLink": "https://aka.ms/bfc-controlling-conversation-flow", + "hidden": [ + "actions" + ], + "label": "Loop: For each item", + "order": [ + "itemsProperty", + "*" + ], + "properties": { + "index": { + "intellisenseScopes": [ + "variable-scopes" + ] + }, + "itemsProperty": { + "intellisenseScopes": [ + "user-variables" + ] + }, + "value": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "For Each" + } + }, + "Microsoft.ForeachPage": { + "flow": { + "loop": { + "body": "=concat(\"Each page of \", coalesce(action.pageSize, \"?\"), \" in \", coalesce(action.page, \"?\"))", + "widget": "ActionCard" + }, + "nowrap": true, + "widget": "ForeachWidget" + }, + "form": { + "helpLink": "https://aka.ms/bfc-controlling-conversation-flow", + "hidden": [ + "actions" + ], + "label": "Loop: For each page (multiple items)", + "order": [ + "itemsProperty", + "pageSize", + "*" + ], + "properties": { + "itemsProperty": { + "intellisenseScopes": [ + "user-variables" + ] + }, + "page": { + "intellisenseScopes": [ + "variable-scopes" + ] + }, + "pageIndex": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "For Each Page" + } + }, + "Microsoft.GetActivityMembers": { + "flow": { + "body": { + "description": "= ActivityId", + "property": "=coalesce(action.activityId, \"?\")", + "widget": "PropertyDescription" + }, + "footer": { + "description": "= Result property", + "property": "=coalesce(action.property, \"?\")", + "widget": "PropertyDescription" + }, + "widget": "ActionCard" + } + }, + "Microsoft.GetConversationMembers": { + "flow": { + "footer": { + "description": "= Result property", + "property": "=action.property", + "widget": "PropertyDescription" + }, + "widget": "ActionCard" + } + }, + "Microsoft.HttpRequest": { + "flow": { + "body": { + "operation": "=action.method", + "resource": "=action.url", + "singleline": true, + "widget": "ResourceOperation" + }, + "footer": { + "description": "= Result property", + "property": "=action.resultProperty", + "widget": "PropertyDescription" + }, + "hideFooter": "=!action.resultProperty", + "widget": "ActionCard" + }, + "form": { + "helpLink": "https://aka.ms/bfc-using-http", + "label": "Send an HTTP request", + "order": [ + "method", + "url", + "body", + "headers", + "*" + ], + "properties": { + "resultProperty": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "HTTP Request" + } + }, + "Microsoft.IfCondition": { + "flow": { + "judgement": { + "body": "=coalesce(action.condition, \"\")", + "widget": "ActionCard" + }, + "nowrap": true, + "widget": "IfConditionWidget" + }, + "form": { + "helpLink": "https://aka.ms/bfc-controlling-conversation-flow", + "hidden": [ + "actions", + "elseActions" + ], + "label": "Branch: If/Else", + "subtitle": "If Condition" + } + }, + "Microsoft.LogAction": { + "form": { + "helpLink": "https://aka.ms/composer-telemetry", + "label": "Log to console", + "subtitle": "Log Action" + } + }, + "Microsoft.NumberInput": { + "flow": { + "body": "=action.prompt", + "botAsks": { + "body": { + "defaultContent": "", + "field": "prompt", + "widget": "LgWidget" + }, + "header": { + "colors": { + "icon": "#5C2E91", + "theme": "#EEEAF4" + }, + "icon": "MessageBot", + "widget": "ActionHeader" + }, + "widget": "ActionCard" + }, + "nowrap": true, + "userInput": { + "header": { + "colors": { + "icon": "#0078D4", + "theme": "#E5F0FF" + }, + "disableSDKTitle": true, + "icon": "User", + "menu": "none", + "widget": "ActionHeader" + }, + "widget": "ActionCard" + }, + "widget": "PromptWidget" + }, + "form": { + "helpLink": "https://aka.ms/bfc-ask-for-user-input", + "label": "Prompt for a number", + "properties": { + "property": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "Number Input" + } + }, + "Microsoft.OAuthInput": { + "flow": { + "body": { + "operation": "Connection", + "resource": "=coalesce(action.connectionName, \"?\")", + "singleline": true, + "widget": "ResourceOperation" + }, + "footer": { + "description": "= Token property", + "property": "=action.property", + "widget": "PropertyDescription" + }, + "hideFooter": "=!action.property", + "widget": "ActionCard" + }, + "form": { + "helpLink": "https://aka.ms/bfc-using-oauth", + "label": "OAuth login", + "order": [ + "connectionName", + "*" + ], + "subtitle": "OAuth Input" + } + }, + "Microsoft.OnActivity": { + "form": { + "hidden": [ + "actions" + ], + "label": "Activities", + "order": [ + "condition", + "*" + ], + "subtitle": "Activity received" + }, + "trigger": { + "label": "Activities (Activity received)", + "order": 5.1, + "submenu": { + "label": "Activities", + "placeholder": "Select an activity type", + "prompt": "Which activity type?" + } + } + }, + "Microsoft.OnAssignEntity": { + "form": { + "hidden": [ + "actions" + ], + "label": "Handle a condition when an entity is assigned", + "order": [ + "condition", + "*" + ], + "subtitle": "EntityAssigned activity" + } + }, + "Microsoft.OnBeginDialog": { + "form": { + "hidden": [ + "actions" + ], + "label": "Dialog started", + "order": [ + "condition", + "*" + ], + "subtitle": "Begin dialog event" + }, + "trigger": { + "label": "Dialog started (Begin dialog event)", + "order": 4.1, + "submenu": { + "label": "Dialog events", + "placeholder": "Select an event type", + "prompt": "Which event?" + } + } + }, + "Microsoft.OnCancelDialog": { + "form": { + "hidden": [ + "actions" + ], + "label": "Dialog cancelled", + "order": [ + "condition", + "*" + ], + "subtitle": "Cancel dialog event" + }, + "trigger": { + "label": "Dialog cancelled (Cancel dialog event)", + "order": 4.2, + "submenu": "Dialog events" + } + }, + "Microsoft.OnChooseEntity": { + "form": { + "hidden": [ + "actions" + ], + "order": [ + "condition", + "*" + ] + } + }, + "Microsoft.OnChooseIntent": { + "form": { + "hidden": [ + "actions" + ], + "order": [ + "condition", + "*" + ] + }, + "trigger": { + "label": "Duplicated intents recognized", + "order": 6 + } + }, + "Microsoft.OnCommandActivity": { + "form": { + "hidden": [ + "actions" + ], + "label": "Command received", + "order": [ + "condition", + "*" + ], + "subtitle": "Command activity received" + }, + "trigger": { + "label": "Command received (Command activity received)", + "order": 5.81, + "submenu": "Activities" + } + }, + "Microsoft.OnCommandResultActivity": { + "form": { + "hidden": [ + "actions" + ], + "label": "Command Result received", + "order": [ + "condition", + "*" + ], + "subtitle": "Command Result activity received" + }, + "trigger": { + "label": "Command Result received (Command Result activity received)", + "order": 5.81, + "submenu": "Activities" + } + }, + "Microsoft.OnCondition": { + "form": { + "hidden": [ + "actions" + ], + "label": "Handle a condition", + "order": [ + "condition", + "*" + ], + "subtitle": "Condition" + } + }, + "Microsoft.OnConversationUpdateActivity": { + "form": { + "description": "Handle the events fired when a user begins a new conversation with the bot.", + "helpLink": "https://aka.ms/bf-composer-docs-conversation-update-activity", + "hidden": [ + "actions" + ], + "label": "Greeting", + "order": [ + "condition", + "*" + ], + "subtitle": "ConversationUpdate activity" + }, + "trigger": { + "label": "Greeting (ConversationUpdate activity)", + "order": 5.2, + "submenu": "Activities" + } + }, + "Microsoft.OnDialogEvent": { + "form": { + "hidden": [ + "actions" + ], + "label": "Dialog events", + "order": [ + "condition", + "*" + ], + "subtitle": "Dialog event" + }, + "trigger": { + "label": "Custom events", + "order": 7 + } + }, + "Microsoft.OnEndOfActions": { + "form": { + "hidden": [ + "actions" + ], + "label": "Handle a condition when actions have ended", + "order": [ + "condition", + "*" + ], + "subtitle": "EndOfActions activity" + } + }, + "Microsoft.OnEndOfConversationActivity": { + "form": { + "hidden": [ + "actions" + ], + "label": "Conversation ended", + "order": [ + "condition", + "*" + ], + "subtitle": "EndOfConversation activity" + }, + "trigger": { + "label": "Conversation ended (EndOfConversation activity)", + "order": 5.3, + "submenu": "Activities" + } + }, + "Microsoft.OnError": { + "form": { + "hidden": [ + "actions" + ], + "label": "Error occurred", + "order": [ + "condition", + "*" + ], + "subtitle": "Error event" + }, + "trigger": { + "label": "Error occurred (Error event)", + "order": 4.3, + "submenu": "Dialog events" + } + }, + "Microsoft.OnEventActivity": { + "form": { + "hidden": [ + "actions" + ], + "label": "Event received", + "order": [ + "condition", + "*" + ], + "subtitle": "Event activity" + }, + "trigger": { + "label": "Event received (Event activity)", + "order": 5.4, + "submenu": "Activities" + } + }, + "Microsoft.OnHandoffActivity": { + "form": { + "hidden": [ + "actions" + ], + "label": "Handover to human", + "order": [ + "condition", + "*" + ], + "subtitle": "Handoff activity" + }, + "trigger": { + "label": "Handover to human (Handoff activity)", + "order": 5.5, + "submenu": "Activities" + } + }, + "Microsoft.OnInstallationUpdateActivity": { + "form": { + "hidden": [ + "actions" + ], + "label": "Installation updated", + "order": [ + "condition", + "*" + ], + "subtitle": "Installation updated activity" + } + }, + "Microsoft.OnIntent": { + "form": { + "hidden": [ + "actions" + ], + "label": "Intent recognized", + "order": [ + "intent", + "condition", + "entities", + "*" + ], + "subtitle": "Intent recognized" + }, + "trigger": { + "label": "Intent recognized", + "order": 1 + } + }, + "Microsoft.OnInvokeActivity": { + "form": { + "hidden": [ + "actions" + ], + "label": "Conversation invoked", + "order": [ + "condition", + "*" + ], + "subtitle": "Invoke activity" + }, + "trigger": { + "label": "Conversation invoked (Invoke activity)", + "order": 5.6, + "submenu": "Activities" + } + }, + "Microsoft.OnMessageActivity": { + "form": { + "hidden": [ + "actions" + ], + "label": "Message received", + "order": [ + "condition", + "*" + ], + "subtitle": "Message activity received" + }, + "trigger": { + "label": "Message received (Message activity received)", + "order": 5.81, + "submenu": "Activities" + } + }, + "Microsoft.OnMessageDeleteActivity": { + "form": { + "hidden": [ + "actions" + ], + "label": "Message deleted", + "order": [ + "condition", + "*" + ], + "subtitle": "Message deleted activity" + }, + "trigger": { + "label": "Message deleted (Message deleted activity)", + "order": 5.82, + "submenu": "Activities" + } + }, + "Microsoft.OnMessageReactionActivity": { + "form": { + "hidden": [ + "actions" + ], + "label": "Message reaction", + "order": [ + "condition", + "*" + ], + "subtitle": "Message reaction activity" + }, + "trigger": { + "label": "Message reaction (Message reaction activity)", + "order": 5.83, + "submenu": "Activities" + } + }, + "Microsoft.OnMessageUpdateActivity": { + "form": { + "hidden": [ + "actions" + ], + "label": "Message updated", + "order": [ + "condition", + "*" + ], + "subtitle": "Message updated activity" + }, + "trigger": { + "label": "Message updated (Message updated activity)", + "order": 5.84, + "submenu": "Activities" + } + }, + "Microsoft.OnQnAMatch": { + "trigger": { + "label": "QnA Intent recognized", + "order": 2 + } + }, + "Microsoft.OnRepromptDialog": { + "form": { + "hidden": [ + "actions" + ], + "label": "Re-prompt for input", + "order": [ + "condition", + "*" + ], + "subtitle": "Reprompt dialog event" + }, + "trigger": { + "label": "Re-prompt for input (Reprompt dialog event)", + "order": 4.4, + "submenu": "Dialog events" + } + }, + "Microsoft.OnTypingActivity": { + "form": { + "hidden": [ + "actions" + ], + "label": "User is typing", + "order": [ + "condition", + "*" + ], + "subtitle": "Typing activity" + }, + "trigger": { + "label": "User is typing (Typing activity)", + "order": 5.7, + "submenu": "Activities" + } + }, + "Microsoft.OnUnknownIntent": { + "form": { + "hidden": [ + "actions" + ], + "label": "Unknown intent", + "order": [ + "condition", + "*" + ], + "subtitle": "Unknown intent recognized" + }, + "trigger": { + "label": "Unknown intent", + "order": 3 + } + }, + "Microsoft.QnAMakerDialog": { + "flow": { + "body": "=action.hostname", + "widget": "ActionCard" + } + }, + "Microsoft.RegexRecognizer": { + "form": { + "hidden": [ + "entities" + ] + } + }, + "Microsoft.RepeatDialog": { + "form": { + "helpLink": "https://aka.ms/bfc-understanding-dialogs", + "label": "Repeat this dialog", + "order": [ + "options", + "*" + ], + "subtitle": "Repeat Dialog" + } + }, + "Microsoft.ReplaceDialog": { + "flow": { + "body": { + "dialog": "=action.dialog", + "widget": "DialogRef" + }, + "widget": "ActionCard" + }, + "form": { + "helpLink": "https://aka.ms/bfc-understanding-dialogs", + "label": "Replace this dialog", + "order": [ + "dialog", + "options", + "*" + ], + "subtitle": "Replace Dialog" + } + }, + "Microsoft.SendActivity": { + "flow": { + "body": { + "field": "activity", + "widget": "LgWidget" + }, + "header": { + "colors": { + "icon": "#5C2E91", + "theme": "#EEEAF4" + }, + "icon": "MessageBot", + "widget": "ActionHeader" + }, + "widget": "ActionCard" + }, + "form": { + "helpLink": "https://aka.ms/bfc-send-activity", + "label": "Send a response", + "order": [ + "activity", + "*" + ], + "subtitle": "Send Activity" + } + }, + "Microsoft.SendHandoffActivity": { + "flow": { + "widget": "ActionHeader" + }, + "form": { + "helpLink": "https://aka.ms/bfc-send-handoff-activity", + "label": "Send a handoff request", + "subtitle": "Send Handoff Activity" + }, + "menu": { + "label": "Send Handoff Event", + "submenu": [ + "Access external resources" + ] + } + }, + "Microsoft.SetProperties": { + "flow": { + "body": { + "items": "=foreach(action.assignments, x => concat(coalesce(x.property, \"?\"), \" : \", coalesce(x.value, \"?\")))", + "widget": "ListOverview" + }, + "widget": "ActionCard" + }, + "form": { + "helpLink": "https://aka.ms/bfc-using-memory", + "label": "Set properties", + "properties": { + "assignments": { + "properties": { + "property": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + } + } + }, + "subtitle": "Set Properties" + } + }, + "Microsoft.SetProperty": { + "flow": { + "body": "${coalesce(action.property, \"?\")} : ${coalesce(action.value, \"?\")}", + "widget": "ActionCard" + }, + "form": { + "helpLink": "https://aka.ms/bfc-using-memory", + "label": "Set a property", + "properties": { + "property": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "Set Property" + } + }, + "Microsoft.SignOutUser": { + "form": { + "label": "Sign out user", + "subtitle": "Signout User" + } + }, + "Microsoft.SwitchCondition": { + "flow": { + "judgement": { + "body": "=coalesce(action.condition, \"\")", + "widget": "ActionCard" + }, + "nowrap": true, + "widget": "SwitchConditionWidget" + }, + "form": { + "helpLink": "https://aka.ms/bfc-controlling-conversation-flow", + "hidden": [ + "default" + ], + "label": "Branch: Switch (multiple options)", + "properties": { + "cases": { + "hidden": [ + "actions" + ] + }, + "condition": { + "intellisenseScopes": [ + "user-variables" + ] + } + }, + "subtitle": "Switch Condition" + } + }, + "Microsoft.TextInput": { + "flow": { + "body": "=action.prompt", + "botAsks": { + "body": { + "defaultContent": "", + "field": "prompt", + "widget": "LgWidget" + }, + "header": { + "colors": { + "icon": "#5C2E91", + "theme": "#EEEAF4" + }, + "icon": "MessageBot", + "widget": "ActionHeader" + }, + "widget": "ActionCard" + }, + "nowrap": true, + "userInput": { + "header": { + "colors": { + "icon": "#0078D4", + "theme": "#E5F0FF" + }, + "disableSDKTitle": true, + "icon": "User", + "menu": "none", + "widget": "ActionHeader" + }, + "widget": "ActionCard" + }, + "widget": "PromptWidget" + }, + "form": { + "helpLink": "https://aka.ms/bfc-ask-for-user-input", + "label": "Prompt for text", + "properties": { + "property": { + "intellisenseScopes": [ + "variable-scopes" + ] + } + }, + "subtitle": "Text Input" + } + }, + "Microsoft.ThrowException": { + "flow": { + "body": { + "description": "= ErrorValue", + "property": "=coalesce(action.errorValue, \"?\")", + "widget": "PropertyDescription" + }, + "widget": "ActionCard" + }, + "form": { + "label": "Throw an exception", + "subtitle": "Throw an exception" + } + }, + "Microsoft.TraceActivity": { + "form": { + "helpLink": "https://aka.ms/composer-telemetry", + "label": "Emit a trace event", + "subtitle": "Trace Activity" + } + }, + "Microsoft.UpdateActivity": { + "flow": { + "body": { + "field": "activity", + "widget": "LgWidget" + }, + "header": { + "colors": { + "icon": "#656565", + "theme": "#D7D7D7" + }, + "icon": "MessageBot", + "title": "Update activity", + "widget": "ActionHeader" + }, + "widget": "ActionCard" + }, + "form": { + "label": "Update an activity", + "subtitle": "Update Activity" + } + } +} diff --git a/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/schemas/update-schema.ps1 b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/schemas/update-schema.ps1 new file mode 100644 index 0000000000..67715586e4 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/schemas/update-schema.ps1 @@ -0,0 +1,27 @@ +$SCHEMA_FILE="sdk.schema" +$UISCHEMA_FILE="sdk.uischema" +$BACKUP_SCHEMA_FILE="sdk-backup.schema" +$BACKUP_UISCHEMA_FILE="sdk-backup.uischema" + +Write-Host "Running schema merge." + +if (Test-Path $SCHEMA_FILE -PathType leaf) { Move-Item -Force -Path $SCHEMA_FILE -Destination $BACKUP_SCHEMA_FILE } +if (Test-Path $UISCHEMA_FILE -PathType leaf) { Move-Item -Force -Path $UISCHEMA_FILE -Destination $BACKUP_UISCHEMA_FILE } + +bf dialog:merge "*.schema" "!**/sdk-backup.schema" "*.uischema" "!**/sdk-backup.uischema" "!**/sdk.override.uischema" "!**/generated" "../*.csproj" "../package.json" -o $SCHEMA_FILE + +if (Test-Path $SCHEMA_FILE -PathType leaf) +{ + if (Test-Path $BACKUP_SCHEMA_FILE -PathType leaf) { Remove-Item -Force -Path $BACKUP_SCHEMA_FILE } + if (Test-Path $BACKUP_UISCHEMA_FILE -PathType leaf) { Remove-Item -Force -Path $BACKUP_UISCHEMA_FILE } + + Write-Host "Schema merged succesfully." + if (Test-Path $SCHEMA_FILE -PathType leaf) { Write-Host " Schema: $SCHEMA_FILE" } + if (Test-Path $UISCHEMA_FILE -PathType leaf) { Write-Host " UI Schema: $UISCHEMA_FILE" } +} +else +{ + Write-Host "Schema merge failed. Restoring previous versions." + if (Test-Path $BACKUP_SCHEMA_FILE -PathType leaf) { Move-Item -Force -Path $BACKUP_SCHEMA_FILE -Destination $SCHEMA_FILE } + if (Test-Path $BACKUP_UISCHEMA_FILE -PathType leaf) { Move-Item -Force -Path $BACKUP_UISCHEMA_FILE -Destination $UISCHEMA_FILE } +} diff --git a/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/schemas/update-schema.sh b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/schemas/update-schema.sh new file mode 100644 index 0000000000..50beec9c4c --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/schemas/update-schema.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +SCHEMA_FILE=sdk.schema +UISCHEMA_FILE=sdk.uischema +BACKUP_SCHEMA_FILE=sdk-backup.schema +BACKUP_UISCHEMA_FILE=sdk-backup.uischema + +while [ $# -gt 0 ]; do + if [[ $1 == *"-"* ]]; then + param="${1/-/}" + declare $param="$2" + fi + shift +done + +echo "Running schema merge." +[ -f "$SCHEMA_FILE" ] && mv "./$SCHEMA_FILE" "./$BACKUP_SCHEMA_FILE" +[ -f "$UISCHEMA_FILE" ] && mv "./$UISCHEMA_FILE" "./$BACKUP_UISCHEMA_FILE" + +bf dialog:merge "*.schema" "!**/sdk-backup.schema" "*.uischema" "!**/sdk-backup.uischema" "!**/sdk.override.uischema" "!**/generated" "../*.csproj" "../package.json" -o $SCHEMA_FILE + +if [ -f "$SCHEMA_FILE" ]; then + rm -rf "./$BACKUP_SCHEMA_FILE" + rm -rf "./$BACKUP_UISCHEMA_FILE" + echo "Schema merged succesfully." + [ -f "$SCHEMA_FILE" ] && echo " Schema: $SCHEMA_FILE" + [ -f "$UISCHEMA_FILE" ] && echo " UI Schema: $UISCHEMA_FILE" +else + echo "Schema merge failed. Restoring previous versions." + [ -f "$BACKUP_SCHEMA_FILE" ] && mv "./$BACKUP_SCHEMA_FILE" "./$SCHEMA_FILE" + [ -f "$BACKUP_UISCHEMA_FILE" ] && mv "./$BACKUP_UISCHEMA_FILE" "./$UISCHEMA_FILE" +fi diff --git a/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/settings/appsettings.json b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/settings/appsettings.json new file mode 100644 index 0000000000..531e75ff37 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/settings/appsettings.json @@ -0,0 +1,76 @@ +{ + "customFunctions": [], + "defaultLanguage": "en-us", + "defaultLocale": "en-us", + "importedLibraries": [], + "languages": [ + "en-us" + ], + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "luFeatures": { + "enableCompositeEntities": true, + "enableListEntities": true, + "enableMLEntities": true, + "enablePattern": true, + "enablePhraseLists": true, + "enablePrebuiltEntities": true, + "enableRegexEntities": true + }, + "luis": { + "authoringEndpoint": "", + "authoringRegion": "", + "defaultLanguage": "en-us", + "endpoint": "", + "environment": "composer", + "name": "EchoSkillBotComposer" + }, + "MicrosoftAppId": "", + "publishTargets": [], + "qna": { + "hostname": "", + "knowledgebaseid": "", + "qnaRegion": "westus" + }, + "runtime": { + "command": "dotnet run --project EchoSkillBotComposer.csproj", + "customRuntime": true, + "key": "adaptive-runtime-dotnet-webapp", + "path": "../" + }, + "runtimeSettings": { + "adapters": [], + "features": { + "removeRecipientMentions": false, + "showTyping": false, + "traceTranscript": false, + "useInspection": false, + "setSpeak": { + "voiceFontName": "en-US-AriaNeural", + "fallbackToTextForSpeechIfEmpty": true + } + }, + "components": [], + "skills": { + "allowedCallers": ["*"] + }, + "storage": "", + "telemetry": { + "logActivities": true, + "logPersonalInformation": false, + "options": { + "connectionString": "" + } + } + }, + "downsampling": { + "maxImbalanceRatio": -1 + }, + "skillConfiguration": {}, + "skillHostEndpoint": "http://localhost:35410/api/skills" +} \ No newline at end of file diff --git a/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/wwwroot/default.htm b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/wwwroot/default.htm new file mode 100644 index 0000000000..696762e394 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/wwwroot/default.htm @@ -0,0 +1,364 @@ + + + + + + + EchoSkillBotComposer + + + + + +
+
+
+
EchoSkillBotComposer
+
+
+
+
+
Your bot is ready!
+
You can test your bot in the Bot Framework Emulator
+ by connecting to http://localhost:3978/api/messages.
+ +
Visit Azure + Bot Service to register your bot and add it to
+ various channels. The bot's endpoint URL typically looks + like this:
+
https://your_bots_hostname/api/messages
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/wwwroot/manifests/echoskillbotcomposer-manifest.json b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/wwwroot/manifests/echoskillbotcomposer-manifest.json new file mode 100644 index 0000000000..25205b9286 --- /dev/null +++ b/tests/functional/Bots/DotNet/Skills/Composer/EchoSkillBotComposer/wwwroot/manifests/echoskillbotcomposer-manifest.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://schemas.botframework.com/schemas/skills/v2.1/skill-manifest.json", + "$id": "EchoSkillBotComposerDotNet", + "name": "EchoSkillBotComposerDotNet", + "version": "1.0", + "description": "This is a skill for echoing what the user sent to the bot (using Composer with the dotnet runtime).", + "publisherName": "Microsoft", + "privacyUrl": "https://microsoft.com/privacy", + "copyright": "Copyright (c) Microsoft Corporation. All rights reserved.", + "license": "https://github.com/microsoft/BotFramework-FunctionalTests/blob/main/LICENSE", + "tags": [ + "echo" + ], + "endpoints": [ + { + "name": "default", + "protocol": "BotFrameworkV3", + "description": "Localhost endpoint for the skill (on port 35410)", + "endpointUrl": "http://localhost:35410/api/messages", + "msAppId": "00000000-0000-0000-0000-000000000000" + } + ] +} \ No newline at end of file diff --git a/tests/functional/Bots/JavaScript/.gitignore b/tests/functional/Bots/JavaScript/.gitignore new file mode 100644 index 0000000000..52b57b278e --- /dev/null +++ b/tests/functional/Bots/JavaScript/.gitignore @@ -0,0 +1,3 @@ + +# Ignore web.config files generated to deploy JS to IIS on Windows +**/web.config \ No newline at end of file diff --git a/tests/functional/Bots/JavaScript/Consumers/CodeFirst/SimpleHostBot/.env b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/SimpleHostBot/.env new file mode 100644 index 0000000000..34470cb8a8 --- /dev/null +++ b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/SimpleHostBot/.env @@ -0,0 +1,24 @@ +MicrosoftAppId= +MicrosoftAppPassword= +SkillHostEndpoint=http://localhost:36000/api/skills/ + +skill_EchoSkillBotComposerDotNet_appId= +skill_EchoSkillBotComposerDotNet_endpoint=http://localhost:35410/api/messages + +skill_EchoSkillBotDotNet_appId= +skill_EchoSkillBotDotNet_endpoint=http://localhost:35400/api/messages + +skill_EchoSkillBotDotNet21_appId= +skill_EchoSkillBotDotNet21_endpoint=http://localhost:35405/api/messages + +skill_EchoSkillBotDotNetV3_appId= +skill_EchoSkillBotDotNetV3_endpoint=http://localhost:35407/api/messages + +skill_EchoSkillBotJS_appId= +skill_EchoSkillBotJS_endpoint=http://localhost:36400/api/messages + +skill_EchoSkillBotJSV3_appId= +skill_EchoSkillBotJSV3_endpoint=http://localhost:36407/api/messages + +skill_EchoSkillBotPython_appId= +skill_EchoSkillBotPython_endpoint=http://localhost:37400/api/messages diff --git a/tests/functional/Bots/JavaScript/Consumers/CodeFirst/SimpleHostBot/authentication/allowedSkillsClaimsValidator.js b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/SimpleHostBot/authentication/allowedSkillsClaimsValidator.js new file mode 100644 index 0000000000..39435af7c7 --- /dev/null +++ b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/SimpleHostBot/authentication/allowedSkillsClaimsValidator.js @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const { JwtTokenValidation, SkillValidation } = require('botframework-connector'); +const { SkillsConfiguration } = require('../skillsConfiguration'); + +const skillsConfig = new SkillsConfiguration(); +// Load the appIds for the configured skills (we will only allow responses from skills we have configured). +const allowedSkills = Object.values(skillsConfig.skills).map(skill => skill.appId); + +/** + * Sample claims validator that loads an allowed list from configuration if present + * and checks that responses are coming from configured skills. + */ +const allowedSkillsClaimsValidator = async (claims) => { + if (SkillValidation.isSkillClaim(claims)) { + // Check that the appId claim in the skill request is in the list of skills configured for this bot. + const appId = JwtTokenValidation.getAppIdFromClaims(claims); + if (!allowedSkills.includes(appId)) { + throw new Error(`Received a request from an application with an appID of "${appId}". To enable requests from this skill, add the skill to your configuration file.`); + } + } +}; + +module.exports.allowedSkillsClaimsValidator = allowedSkillsClaimsValidator; diff --git a/tests/functional/Bots/JavaScript/Consumers/CodeFirst/SimpleHostBot/bots/hostBot.js b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/SimpleHostBot/bots/hostBot.js new file mode 100644 index 0000000000..11c5cebf4a --- /dev/null +++ b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/SimpleHostBot/bots/hostBot.js @@ -0,0 +1,168 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const { ActivityHandler, ActivityTypes, DeliveryModes, MessageFactory } = require('botbuilder'); + +class HostBot extends ActivityHandler { + constructor (dialog, conversationState, skillsConfig, skillClient) { + super(); + if (!conversationState) throw new Error('[HostBot]: Missing parameter. conversationState is required'); + if (!skillsConfig) throw new Error('[HostBot]: Missing parameter. skillsConfig is required'); + if (!skillClient) throw new Error('[HostBot]: Missing parameter. skillClient is required'); + + this.conversationState = conversationState; + this.skillsConfig = skillsConfig; + this.skillClient = skillClient; + this.dialog = dialog; + this.dialogStateProperty = this.conversationState.createProperty('DialogState'); + + this.botId = process.env.MicrosoftAppId; + + // Create state property to track the delivery mode and active skill. + this.deliveryModeProperty = this.conversationState.createProperty(HostBot.DeliveryModePropertyName); + this.activeSkillProperty = this.conversationState.createProperty(HostBot.ActiveSkillPropertyName); + + this.onTurn(async (context, next) => { + // Forward all activities except EndOfConversation to the active skill. + if (context.activity.type !== ActivityTypes.EndOfConversation) { + // Try to get the active skill. + const activeSkill = await this.activeSkillProperty.get(context); + + if (activeSkill) { + const deliveryMode = await this.deliveryModeProperty.get(context); + + // Send the activity to the skill + await this.sendToSkill(context, deliveryMode, activeSkill); + return; + } + } + + await next(); + }); + + // See https://aka.ms/about-bot-activity-message to learn more about the message and other activity types. + this.onMessage(async (context, next) => { + const deliveryMode = await this.deliveryModeProperty.get(context); + const selectedSkill = this.skillsConfig.skills[context.activity.text]; + const v3Bots = ['EchoSkillBotDotNetV3', 'EchoSkillBotJSV3']; + + if (selectedSkill && deliveryMode === DeliveryModes.ExpectReplies && v3Bots.includes(selectedSkill.id)) { + const message = MessageFactory.text("V3 Bots do not support 'expectReplies' delivery mode."); + await context.sendActivity(message); + + // Forget delivery mode and skill invocation. + await this.deliveryModeProperty.delete(context); + await this.activeSkillProperty.delete(context); + + // Restart setup dialog + await this.conversationState.delete(context); + } + + await this.dialog.run(context, this.dialogStateProperty); + }); + + this.onEndOfConversation(async (context, next) => { + // Handle EndOfConversation returned by the skill. + await this.EndConversation(context.activity, context); + + // By calling next() you ensure that the next BotHandler is run. + await next(); + }); + + this.onMembersAdded(async (context, next) => { + const membersAdded = context.activity.membersAdded; + for (let cnt = 0; cnt < membersAdded.length; ++cnt) { + if (membersAdded[cnt].id !== context.activity.recipient.id) { + await context.sendActivity('Hello and welcome!'); + await this.dialog.run(context, this.dialogStateProperty); + } + } + + // By calling next() you ensure that the next BotHandler is run. + await next(); + }); + } + + async EndConversation (activity, context) { + if (activity.type === ActivityTypes.EndOfConversation) { + // Forget delivery mode and skill invocation. + await this.deliveryModeProperty.delete(context); + await this.activeSkillProperty.delete(context); + + // Show status message, text and value returned by the skill. + let eocActivityMessage = `Received ${ActivityTypes.EndOfConversation}.\n\nCode: ${activity.code}.`; + if (activity.text) { + eocActivityMessage += `\n\nText: ${activity.text}`; + } + + if (activity.value) { + eocActivityMessage += `\n\nValue: ${activity.value}`; + } + + await context.sendActivity(eocActivityMessage); + + // We are back at the host. + await context.sendActivity('Back in the host bot.'); + + // Restart setup dialog + await this.dialog.run(context, this.dialogStateProperty); + + await this.conversationState.saveChanges(context); + } + } + + async sendToSkill (context, deliveryMode, targetSkill) { + // NOTE: Always SaveChanges() before calling a skill so that any activity generated by the skill + // will have access to current accurate state. + await this.conversationState.saveChanges(context, true); + + if (deliveryMode === DeliveryModes.ExpectReplies) { + // Clone activity and update its delivery mode. + const activity = JSON.parse(JSON.stringify(context.activity)); + activity.deliveryMode = deliveryMode; + + // Route the activity to the skill. + const expectRepliesResponse = await this.skillClient.postToSkill(this.botId, targetSkill, this.skillsConfig.skillHostEndpoint, activity); + + // Check response status. + if (!(expectRepliesResponse.status >= 200 && expectRepliesResponse.status <= 299)) { + throw new Error(`[HostBot]: Error invoking the skill id: "${targetSkill.id}" at "${targetSkill.skillEndpoint}" (status is ${expectRepliesResponse.status}). \r\n ${expectRepliesResponse.body}`); + } + + if (expectRepliesResponse.body && expectRepliesResponse.body.activities) { + // Route response activities back to the channel. + const responseActivities = expectRepliesResponse.body.activities; + + for (let index = 0; index < responseActivities.length; index++) { + if (responseActivities[index].type === ActivityTypes.EndOfConversation) { + await this.EndConversation(responseActivities[index], context); + } else { + await context.sendActivity(responseActivities[index]); + } + } + } + } else { + // Route the activity to the skill. + const response = await this.skillClient.postToSkill(this.botId, targetSkill, this.skillsConfig.skillHostEndpoint, context.activity); + + // Check response status. + if (!(response.status >= 200 && response.status <= 299)) { + throw new Error(`[HostBot]: Error invoking the skill id: "${targetSkill.id}" at "${targetSkill.skillEndpoint}" (status is ${response.status}). \r\n ${response.body}`); + } + } + } + + /** + * Override the ActivityHandler.run() method to save state changes after the bot logic completes. + */ + async run (context) { + await super.run(context); + + // Save any state changes. The load happened during the execution of the Dialog. + await this.conversationState.saveChanges(context, false); + } +} + +module.exports.HostBot = HostBot; +HostBot.ActiveSkillPropertyName = 'activeSkillProperty'; +HostBot.DeliveryModePropertyName = 'deliveryModeProperty'; diff --git a/tests/functional/Bots/JavaScript/Consumers/CodeFirst/SimpleHostBot/dialogs/setupDialog.js b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/SimpleHostBot/dialogs/setupDialog.js new file mode 100644 index 0000000000..77e25eee63 --- /dev/null +++ b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/SimpleHostBot/dialogs/setupDialog.js @@ -0,0 +1,122 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const { InputHints, MessageFactory, DeliveryModes } = require('botbuilder'); +const { ChoicePrompt, ComponentDialog, DialogSet, DialogTurnStatus, WaterfallDialog, ListStyle } = require('botbuilder-dialogs'); +const { HostBot } = require('../bots/hostBot'); + +const SETUP_DIALOG = 'SetupDialog'; +const CHOICE_PROMPT = 'ChoicePrompt'; +const WATERFALL_DIALOG = 'WaterfallDialog'; + +/** + * The setup dialog for this bot. + */ +class SetupDialog extends ComponentDialog { + constructor (conversationState, skillsConfig) { + super(SETUP_DIALOG); + + this.deliveryModeProperty = conversationState.createProperty(HostBot.DeliveryModePropertyName); + this.activeSkillProperty = conversationState.createProperty(HostBot.ActiveSkillPropertyName); + this.skillsConfig = skillsConfig; + this.deliveryMode = ''; + + // Define the setup dialog and its related components. + // Add ChoicePrompt to render available skills. + this.addDialog(new ChoicePrompt(CHOICE_PROMPT)) + // Add main waterfall dialog for this bot. + .addDialog(new WaterfallDialog(WATERFALL_DIALOG, [ + this.selectDeliveryModeStep.bind(this), + this.selectSkillStep.bind(this), + this.finalStep.bind(this) + ])); + + this.initialDialogId = WATERFALL_DIALOG; + } + + /** + * The run method handles the incoming activity (in the form of a TurnContext) and passes it through the dialog system. + * If no dialog is active, it will start the default dialog. + * @param {*} turnContext + * @param {*} accessor + */ + async run (turnContext, accessor) { + const dialogSet = new DialogSet(accessor); + dialogSet.add(this); + + const dialogContext = await dialogSet.createContext(turnContext); + const results = await dialogContext.continueDialog(); + if (results.status === DialogTurnStatus.empty) { + await dialogContext.beginDialog(this.id); + } + } + + /** + * Render a prompt to select the delivery mode to use. + */ + async selectDeliveryModeStep (stepContext) { + // Create the PromptOptions with the delivery modes supported. + const messageText = 'What delivery mode would you like to use?'; + const repromptMessageText = 'That was not a valid choice, please select a valid delivery mode.'; + const options = { + prompt: MessageFactory.text(messageText, messageText, InputHints.ExpectingInput), + retryPrompt: MessageFactory.text(repromptMessageText, repromptMessageText, InputHints.ExpectingInput), + choices: [DeliveryModes.Normal, DeliveryModes.ExpectReplies] + }; + + // Prompt the user to select a delivery mode. + return await stepContext.prompt(CHOICE_PROMPT, options); + } + + /** + * Render a prompt to select the skill to call. + */ + async selectSkillStep (stepContext) { + // Set delivery mode. + this.deliveryMode = stepContext.result.value; + await this.deliveryModeProperty.set(stepContext.context, stepContext.result.value); + + // Create the PromptOptions from the skill configuration which contains the list of configured skills. + const messageText = 'What skill would you like to call?'; + const repromptMessageText = 'That was not a valid choice, please select a valid skill.'; + const options = { + prompt: MessageFactory.text(messageText, messageText, InputHints.ExpectingInput), + retryPrompt: MessageFactory.text(repromptMessageText, repromptMessageText, InputHints.ExpectingInput), + choices: Object.keys(this.skillsConfig.skills), + style: ListStyle.suggestedAction + }; + + // Prompt the user to select a skill. + return await stepContext.prompt(CHOICE_PROMPT, options); + } + + /** + * The SetupDialog has ended, we go back to the HostBot to connect with the selected skill. + */ + async finalStep (stepContext) { + const selectedSkill = this.skillsConfig.skills[stepContext.result.value]; + const v3Bots = ['EchoSkillBotDotNetV3', 'EchoSkillBotJSV3']; + + // Set active skill + await this.activeSkillProperty.set(stepContext.context, selectedSkill); + + if (this.deliveryMode === DeliveryModes.ExpectReplies && v3Bots.includes(selectedSkill.id)) { + const message = MessageFactory.text("V3 Bots do not support 'expectReplies' delivery mode."); + await stepContext.context.sendActivity(message); + + // Forget delivery mode and skill invocation. + await this.deliveryModeProperty.delete(stepContext.context); + await this.activeSkillProperty.delete(stepContext.context); + + // Restart setup dialog + return await stepContext.replaceDialog(this.initialDialogId); + } + + const message = MessageFactory.text('Type anything to send to the skill.', 'Type anything to send to the skill.', InputHints.ExpectingInput); + await stepContext.context.sendActivity(message); + + return await stepContext.endDialog(); + } +} + +module.exports.SetupDialog = SetupDialog; diff --git a/tests/functional/Bots/JavaScript/Consumers/CodeFirst/SimpleHostBot/index.js b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/SimpleHostBot/index.js new file mode 100644 index 0000000000..e6e0a329e0 --- /dev/null +++ b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/SimpleHostBot/index.js @@ -0,0 +1,164 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// index.js is used to setup and configure your bot + +// Import required packages +const http = require('http'); +const https = require('https'); +const path = require('path'); +const restify = require('restify'); + +// Import required bot services. +// See https://aka.ms/bot-services to learn more about the different parts of a bot. +const { BotFrameworkAdapter, TurnContext, ActivityTypes, ChannelServiceRoutes, ConversationState, InputHints, MemoryStorage, SkillHandler, SkillHttpClient, MessageFactory, SkillConversationIdFactory } = require('botbuilder'); +const { AuthenticationConfiguration, SimpleCredentialProvider } = require('botframework-connector'); + +// Import required bot configuration. +const ENV_FILE = path.join(__dirname, '.env'); +require('dotenv').config({ path: ENV_FILE }); + +// This bot's main dialog. +const { HostBot } = require('./bots/hostBot'); +const { SkillsConfiguration } = require('./skillsConfiguration'); +const { allowedSkillsClaimsValidator } = require('./authentication/allowedSkillsClaimsValidator'); +const { SetupDialog } = require('./dialogs/setupDialog'); + +const maxTotalSockets = (preallocatedSnatPorts, procCount = 1, weight = 0.5, overcommit = 1.1) => + Math.min( + Math.floor((preallocatedSnatPorts / procCount) * weight * overcommit), + preallocatedSnatPorts + ); + +// Create adapter. +// See https://aka.ms/about-bot-adapter to learn more about adapters. +const adapter = new BotFrameworkAdapter({ + appId: process.env.MicrosoftAppId, + appPassword: process.env.MicrosoftAppPassword, + authConfig: new AuthenticationConfiguration([], allowedSkillsClaimsValidator), + clientOptions: { + agentSettings: { + http: new http.Agent({ + keepAlive: true, + maxTotalSockets: maxTotalSockets(1024, 4, 0.3) + }), + https: new https.Agent({ + keepAlive: true, + maxTotalSockets: maxTotalSockets(1024, 4, 0.7) + }) + } + } +}); + +// Catch-all for errors. +adapter.onTurnError = async (context, error) => { + // This check writes out errors to console log .vs. app insights. + // NOTE: In production environment, you should consider logging this to Azure + // application insights. + console.error(`\n [onTurnError] unhandled error: ${error}`); + + try { + const { message, stack } = error; + + // Send a message to the user. + let errorMessageText = 'The bot encountered an error or bug.'; + let errorMessage = MessageFactory.text(errorMessageText, errorMessageText, InputHints.IgnoringInput); + errorMessage.value = { message, stack }; + await context.sendActivity(errorMessage); + + await context.sendActivity(`Exception: ${message}`); + await context.sendActivity(stack); + + errorMessageText = 'To continue to run this bot, please fix the bot source code.'; + errorMessage = MessageFactory.text(errorMessageText, errorMessageText, InputHints.ExpectingInput); + await context.sendActivity(errorMessage); + + // Send a trace activity, which will be displayed in Bot Framework Emulator + await context.sendTraceActivity( + 'OnTurnError Trace', + `${error}`, + 'https://www.botframework.com/schemas/error', + 'TurnError' + ); + } catch (err) { + console.error(`\n [onTurnError] Exception caught in onTurnError : ${err}`); + } + + try { + // Inform the active skill that the conversation is ended so that it has + // a chance to clean up. + // Note: ActiveSkillPropertyName is set by the RooBot while messages are being + // forwarded to a Skill. + const activeSkill = await conversationState.createProperty(HostBot.ActiveSkillPropertyName).get(context); + if (activeSkill) { + const botId = process.env.MicrosoftAppId; + + let endOfConversation = { + type: ActivityTypes.EndOfConversation, + code: 'RootSkillError' + }; + endOfConversation = TurnContext.applyConversationReference( + endOfConversation, TurnContext.getConversationReference(context.activity), true); + + await conversationState.saveChanges(context, true); + await skillClient.postToSkill(botId, activeSkill, skillsConfig.skillHostEndpoint, endOfConversation); + } + } catch (err) { + console.error(`\n [onTurnError] Exception caught on attempting to send EndOfConversation : ${err}`); + } + + try { + // Clear out state + await conversationState.delete(context); + } catch (err) { + console.error(`\n [onTurnError] Exception caught on attempting to Delete ConversationState : ${err}`); + } +}; + +// Define a state store for your bot. See https://aka.ms/about-bot-state to learn more about using MemoryStorage. +// A bot requires a state store to persist the dialog and user state between messages. + +// For local development, in-memory storage is used. +// CAUTION: The Memory Storage used here is for local bot debugging only. When the bot +// is restarted, anything stored in memory will be gone. +const memoryStorage = new MemoryStorage(); +const conversationState = new ConversationState(memoryStorage); + +// Create the conversationIdFactory +const conversationIdFactory = new SkillConversationIdFactory(memoryStorage); + +// Load skills configuration +const skillsConfig = new SkillsConfiguration(); + +// Create the credential provider; +const credentialProvider = new SimpleCredentialProvider(process.env.MicrosoftAppId, process.env.MicrosoftAppPassword); + +// Create the skill client +const skillClient = new SkillHttpClient(credentialProvider, conversationIdFactory); + +// Create the main dialog. +const dialog = new SetupDialog(conversationState, skillsConfig); +const bot = new HostBot(dialog, conversationState, skillsConfig, skillClient); + +// Create HTTP server +const server = restify.createServer(); +server.listen(process.env.port || process.env.PORT || 36000, function () { + console.log(`\n${server.name} listening to ${server.url}`); + console.log('\nGet Bot Framework Emulator: https://aka.ms/botframework-emulator'); + console.log('\nTo talk to your bot, open the emulator select "Open Bot"'); +}); + +// Listen for incoming activities and route them to your bot main dialog. +server.post('/api/messages', (req, res) => { + // Route received a request to adapter for processing + adapter.processActivity(req, res, async (turnContext) => { + // route to bot activity handler. + await bot.run(turnContext); + }); +}); + +// Create and initialize the skill classes +const authConfig = new AuthenticationConfiguration([], allowedSkillsClaimsValidator); +const handler = new SkillHandler(adapter, bot, conversationIdFactory, credentialProvider, authConfig); +const skillEndpoint = new ChannelServiceRoutes(handler); +skillEndpoint.register(server, '/api/skills'); diff --git a/tests/functional/Bots/JavaScript/Consumers/CodeFirst/SimpleHostBot/package.json b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/SimpleHostBot/package.json new file mode 100644 index 0000000000..bddc987858 --- /dev/null +++ b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/SimpleHostBot/package.json @@ -0,0 +1,26 @@ +{ + "name": "simple-root-bot", + "version": "1.0.0", + "description": "A simple root bot for testing skills", + "author": "Microsoft", + "license": "MIT", + "main": "index.js", + "scripts": { + "start": "node ./index.js", + "watch": "nodemon ./index.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://github.com" + }, + "dependencies": { + "botbuilder": "~4.13.3", + "botbuilder-dialogs": "~4.13.3", + "dotenv": "~8.2.0", + "restify": "~8.5.1" + }, + "devDependencies": { + "nodemon": "~2.0.4" + } +} diff --git a/tests/functional/Bots/JavaScript/Consumers/CodeFirst/SimpleHostBot/skillsConfiguration.js b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/SimpleHostBot/skillsConfiguration.js new file mode 100644 index 0000000000..69d7bd19fe --- /dev/null +++ b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/SimpleHostBot/skillsConfiguration.js @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +/** + * A helper class that loads Skills information from configuration. + */ +class SkillsConfiguration { + constructor () { + this.skillsData = {}; + + const skillVariables = Object.keys(process.env).filter(prop => prop.startsWith('skill_')); + + for (const val of skillVariables) { + const names = val.split('_'); + const id = names[1]; + const attr = names[2]; + let propName; + if (!(id in this.skillsData)) { + this.skillsData[id] = { id: id }; + } + switch (attr.toLowerCase()) { + case 'appid': + propName = 'appId'; + break; + case 'endpoint': + propName = 'skillEndpoint'; + break; + case 'group': + propName = 'group'; + break; + default: + throw new Error(`[SkillsConfiguration]: Invalid environment variable declaration ${val}`); + } + + this.skillsData[id][propName] = process.env[val]; + } + + this.skillHostEndpointValue = process.env.SkillHostEndpoint; + if (!this.skillHostEndpointValue) { + throw new Error('[SkillsConfiguration]: Missing configuration parameter. SkillHostEndpoint is required'); + } + } + + get skills () { + return this.skillsData; + } + + get skillHostEndpoint () { + return this.skillHostEndpointValue; + } +} + +module.exports.SkillsConfiguration = SkillsConfiguration; diff --git a/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/.env b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/.env new file mode 100644 index 0000000000..cfef59e0a9 --- /dev/null +++ b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/.env @@ -0,0 +1,52 @@ +MicrosoftAppId= +MicrosoftAppPassword= +SkillHostEndpoint=http://localhost:36020/api/skills/ +SsoConnectionName= + +skill_EchoSkillBotDotNet_group=Echo +skill_EchoSkillBotDotNet_appId= +skill_EchoSkillBotDotNet_endpoint=http://localhost:35400/api/messages + +skill_EchoSkillBotDotNet21_group=Echo +skill_EchoSkillBotDotNet21_appId= +skill_EchoSkillBotDotNet21_endpoint=http://localhost:35405/api/messages + +skill_EchoSkillBotDotNetV3_group=Echo +skill_EchoSkillBotDotNetV3_appId= +skill_EchoSkillBotDotNetV3_endpoint=http://localhost:35407/api/messages + +skill_EchoSkillBotJS_group=Echo +skill_EchoSkillBotJS_appId= +skill_EchoSkillBotJS_endpoint=http://localhost:36400/api/messages + +skill_EchoSkillBotJSV3_group=Echo +skill_EchoSkillBotJSV3_appId= +skill_EchoSkillBotJSV3_endpoint=http://localhost:36407/api/messages + +skill_EchoSkillBotPython_group=Echo +skill_EchoSkillBotPython_appId= +skill_EchoSkillBotPython_endpoint=http://localhost:37400/api/messages + +skill_WaterfallSkillBotDotNet_group=Waterfall +skill_WaterfallSkillBotDotNet_appId= +skill_WaterfallSkillBotDotNet_endpoint=http://localhost:35420/api/messages + +skill_WaterfallSkillBotJS_group=Waterfall +skill_WaterfallSkillBotJS_appId= +skill_WaterfallSkillBotJS_endpoint=http://localhost:36420/api/messages + +skill_WaterfallSkillBotPython_group=Waterfall +skill_WaterfallSkillBotPython_appId= +skill_WaterfallSkillBotPython_endpoint=http://localhost:37420/api/messages + +skill_TeamsSkillBotDotNet_group=Teams +skill_TeamsSkillBotDotNet_appId= +skill_TeamsSkillBotDotNet_endpoint=http://localhost:35430/api/messages + +skill_TeamsSkillBotJS_group=Teams +skill_TeamsSkillBotJS_appId= +skill_TeamsSkillBotJS_endpoint=http://localhost:36430/api/messages + +skill_TeamsSkillBotPython_group=Teams +skill_TeamsSkillBotPython_appId= +skill_TeamsSkillBotPython_endpoint=http://localhost:37430/api/messages diff --git a/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/Cards/welcomeCard.json b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/Cards/welcomeCard.json new file mode 100644 index 0000000000..1fc98a2237 --- /dev/null +++ b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/Cards/welcomeCard.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.0", + "body": [ + { + "type": "Image", + "url": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQtB3AwMUeNoq4gUBGe6Ocj8kyh3bXa9ZbV7u1fVKQoyKFHdkqU", + "size": "stretch" + }, + { + "type": "TextBlock", + "spacing": "Medium", + "size": "Medium", + "weight": "Bolder", + "text": "Welcome to the Skill Dialog Sample!", + "wrap": true, + "maxLines": 0, + "color": "Accent" + }, + { + "type": "TextBlock", + "size": "default", + "text": "This sample allows you to connect to a skill using a SkillDialog and invoke several actions.", + "wrap": true, + "maxLines": 0 + } + ] +} \ No newline at end of file diff --git a/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/TokenExchangeSkillHandler.js b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/TokenExchangeSkillHandler.js new file mode 100644 index 0000000000..6f320128f0 --- /dev/null +++ b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/TokenExchangeSkillHandler.js @@ -0,0 +1,123 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const { v4 } = require('uuid'); +const { ActivityEx, ActivityTypes, CardFactory, SkillHandler, tokenExchangeOperationName, TurnContext } = require('botbuilder'); +const { JwtTokenValidation } = require('botframework-connector'); + +const WATERFALL_SKILL_BOT = 'WaterfallSkillBot'; + +/** + * A SkillHandler specialized to support SSO Token exchanges. + */ +class TokenExchangeSkillHandler extends SkillHandler { + constructor (adapter, bot, conversationIdFactory, skillsConfig, skillClient, + credentialProvider, authConfig, channelProvider = null, logger = null) { + super(adapter, bot, conversationIdFactory, credentialProvider, authConfig, channelProvider); + this.adapter = adapter; + this.tokenExchangeProvider = adapter; + + if (!this.tokenExchangeProvider) { + throw new Error(`${adapter} does not support token exchange`); + } + + this.skillsConfig = skillsConfig; + this.skillClient = skillClient; + this.conversationIdFactory = conversationIdFactory; + this.logger = logger; + this.botId = process.env.MicrosoftAppId; + } + + async onSendToConversation (claimsIdentity, conversationId, activity) { + if (await this.interceptOAuthCards(claimsIdentity, activity)) { + return { id: v4() }; + } + + return await super.onSendToConversation(claimsIdentity, conversationId, activity); + } + + async onReplyToActivity (claimsIdentity, conversationId, activityId, activity) { + if (await this.interceptOAuthCards(claimsIdentity, activity)) { + return { id: v4() }; + } + + return await super.onReplyToActivity(claimsIdentity, conversationId, activityId, activity); + } + + getCallingSkill (claimsIdentity) { + const appId = JwtTokenValidation.getAppIdFromClaims(claimsIdentity.claims); + + if (!appId) { + return null; + } + + return Object.values(this.skillsConfig.skills).find(skill => skill.appId === appId); + } + + async interceptOAuthCards (claimsIdentity, activity) { + const oauthCardAttachment = activity.attachments ? activity.attachments.find(attachment => attachment.contentType === CardFactory.contentTypes.oauthCard) : null; + if (oauthCardAttachment) { + const targetSkill = this.getCallingSkill(claimsIdentity); + if (targetSkill) { + const oauthCard = oauthCardAttachment.content; + + if (oauthCard && oauthCard.tokenExchangeResource && oauthCard.tokenExchangeResource.uri) { + const context = new TurnContext(this.adapter, activity); + context.turnState.push('BotIdentity', claimsIdentity); + + // We need to know what connection name to use for the token exchange so we figure that out here + const connectionName = targetSkill.id.includes(WATERFALL_SKILL_BOT) ? process.env.SsoConnectionName : process.env.SsoConnectionNameTeams; + + if (!connectionName) { + throw new Error('The connection name cannot be null.'); + } + + // AAD token exchange + try { + const result = await this.tokenExchangeProvider.exchangeToken( + context, + connectionName, + activity.recipient.id, + { uri: oauthCard.tokenExchangeResource.uri } + ); + + if (result.token) { + // If token above is null, then SSO has failed and hence we return false. + // If not, send an invoke to the skill with the token. + return await this.sendTokenExchangeInvokeToSkill(activity, oauthCard.tokenExchangeResource.id, result.token, oauthCard.connectionName, targetSkill); + } + } catch (exception) { + // Show oauth card if token exchange fails. + this.logger.log('Unable to exchange token.', exception); + return false; + } + } + } + } + + return false; + } + + async sendTokenExchangeInvokeToSkill (incomingActivity, id, token, connectionName, targetSkill) { + const activity = ActivityEx.createReply(incomingActivity); + activity.type = ActivityTypes.Invoke; + activity.name = tokenExchangeOperationName; + activity.value = { + id: id, + token: token, + connectionName: connectionName + }; + + const skillConversationReference = await this.conversationIdFactory.getSkillConversationReference(incomingActivity.conversation.id); + activity.conversation = incomingActivity.conversation; + activity.serviceUrl = skillConversationReference.conversationReference.serviceUrl; + + // Route the activity to the skill + const response = await this.skillClient.postActivity(this.botId, targetSkill.appId, targetSkill.skillEndpoint, this.skillsConfig.skillHostEndpoint, activity.conversation.id, activity); + + // Check response status: true if success, false if failure + return response.status >= 200 && response.status <= 299; + } +} + +module.exports.TokenExchangeSkillHandler = TokenExchangeSkillHandler; diff --git a/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/authentication/allowedSkillsClaimsValidator.js b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/authentication/allowedSkillsClaimsValidator.js new file mode 100644 index 0000000000..39435af7c7 --- /dev/null +++ b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/authentication/allowedSkillsClaimsValidator.js @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const { JwtTokenValidation, SkillValidation } = require('botframework-connector'); +const { SkillsConfiguration } = require('../skillsConfiguration'); + +const skillsConfig = new SkillsConfiguration(); +// Load the appIds for the configured skills (we will only allow responses from skills we have configured). +const allowedSkills = Object.values(skillsConfig.skills).map(skill => skill.appId); + +/** + * Sample claims validator that loads an allowed list from configuration if present + * and checks that responses are coming from configured skills. + */ +const allowedSkillsClaimsValidator = async (claims) => { + if (SkillValidation.isSkillClaim(claims)) { + // Check that the appId claim in the skill request is in the list of skills configured for this bot. + const appId = JwtTokenValidation.getAppIdFromClaims(claims); + if (!allowedSkills.includes(appId)) { + throw new Error(`Received a request from an application with an appID of "${appId}". To enable requests from this skill, add the skill to your configuration file.`); + } + } +}; + +module.exports.allowedSkillsClaimsValidator = allowedSkillsClaimsValidator; diff --git a/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/bots/rootBot.js b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/bots/rootBot.js new file mode 100644 index 0000000000..9fe1b3dbb3 --- /dev/null +++ b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/bots/rootBot.js @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const { ActivityHandler, ActivityTypes, CardFactory, MessageFactory } = require('botbuilder'); +const { runDialog } = require('botbuilder-dialogs'); +const WelcomeCard = require('../cards/welcomeCard.json'); + +class RootBot extends ActivityHandler { + /** + * @param {import('botbuilder').ConversationState} conversationState + * @param {import('botbuilder').SkillHttpClient} skillClient + * @param {import('botbuilder-dialogs').Dialog} mainDialog + */ + constructor (conversationState, skillClient, mainDialog) { + super(); + if (!conversationState) throw new Error('[RootBot]: Missing parameter. conversationState is required'); + if (!mainDialog) throw new Error('[RootBot]: Missing parameter. mainDialog is required'); + + this.conversationState = conversationState; + this.skillClient = skillClient; + this.mainDialog = mainDialog; + this.botId = process.env.MicrosoftAppId; + + // Create state property to track the active skill + this.activeSkillProperty = this.conversationState.createProperty(RootBot.ActiveSkillPropertyName); + + this.onTurn(async (turnContext, next) => { + if (turnContext.activity.type !== ActivityTypes.ConversationUpdate) { + // Run the Dialog with the activity. + await runDialog(this.mainDialog, turnContext, this.conversationState.createProperty('DialogState')); + } + + await next(); + }); + + this.onMembersAdded(async (context, next) => { + const membersAdded = context.activity.membersAdded; + for (const member of membersAdded) { + if (member.id !== context.activity.recipient.id) { + const welcomeCard = CardFactory.adaptiveCard(WelcomeCard); + const activity = MessageFactory.attachment(welcomeCard); + activity.speak = 'Welcome to the waterfall host bot'; + await context.sendActivity(activity); + await runDialog(this.mainDialog, context, conversationState.createProperty('DialogState')); + } + } + + // By calling next() you ensure that the next BotHandler is run. + await next(); + }); + } + + /** + * Override the ActivityHandler.run() method to save state changes after the bot logic completes. + * @param {import('botbuilder').TurnContext} turnContext + */ + async run (context) { + await super.run(context); + + // Save any state changes. The load happened during the execution of the Dialog. + await this.conversationState.saveChanges(context, false); + } + + /** + * @param {import('botbuilder').TurnContext} turnContext + * @param {*} targetSkill + */ + async sendToSkill (context, targetSkill) { + // NOTE: Always SaveChanges() before calling a skill so that any activity generated by the skill + // will have access to current accurate state. + await this.conversationState.saveChanges(context, true); + + // route the activity to the skill + const response = await this.skillClient.postToSkill(this.botId, targetSkill, this.skillsConfig.skillHostEndpoint, context.activity); + + // Check response status + if (!(response.status >= 200 && response.status <= 299)) { + throw new Error(`[RootBot]: Error invoking the skill id: "${targetSkill.id}" at "${targetSkill.skillEndpoint}" (status is ${response.status}). \r\n ${response.body}`); + } + } +} + +module.exports.RootBot = RootBot; diff --git a/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/dialogs/mainDialog.js b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/dialogs/mainDialog.js new file mode 100644 index 0000000000..8f3fb81e4c --- /dev/null +++ b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/dialogs/mainDialog.js @@ -0,0 +1,314 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const { ActivityTypes, DeliveryModes, InputHints, MessageFactory } = require('botbuilder'); +const { ChoicePrompt, ChoiceFactory, ComponentDialog, DialogSet, SkillDialog, WaterfallDialog, DialogTurnStatus, ListStyle } = require('botbuilder-dialogs'); +const { RootBot } = require('../bots/rootBot'); +const { SsoDialog } = require('./sso/ssoDialog'); +const { TangentDialog } = require('./tangentDialog'); + +const MAIN_DIALOG = 'MainDialog'; +const DELIVERY_PROMPT = 'DeliveryModePrompt'; +const SKILL_GROUP_PROMPT = 'SkillGroupPrompt'; +const SKILL_PROMPT = 'SkillPrompt'; +const SKILL_ACTION_PROMPT = 'SkillActionPrompt'; +const TANGENT_DIALOG = 'TangentDialog'; +const WATERFALL_DIALOG = 'WaterfallDialog'; +const SSO_DIALOG_PREFIX = 'Sso'; + +class MainDialog extends ComponentDialog { + /** + * @param {import('botbuilder').ConversationState} conversationState + * @param {import('../skillsConfiguration').SkillsConfiguration} skillsConfig + * @param {import('botbuilder').SkillHttpClient} skillClient + * @param {import('../skillConversationIdFactory').SkillConversationIdFactory} conversationIdFactory + */ + constructor (conversationState, skillsConfig, skillClient, conversationIdFactory) { + super(MAIN_DIALOG); + + const botId = process.env.MicrosoftAppId; + + if (!conversationState) throw new Error('[MainDialog]: Missing parameter \'conversationState\' is required'); + if (!skillsConfig) throw new Error('[MainDialog]: Missing parameter \'skillsConfig\' is required'); + if (!skillClient) throw new Error('[MainDialog]: Missing parameter \'skillClient\' is required'); + if (!conversationIdFactory) throw new Error('[MainDialog]: Missing parameter \'conversationIdFactory\' is required'); + + this.deliveryModeProperty = conversationState.createProperty(RootBot.DeliveryModePropertyName); + this.activeSkillProperty = conversationState.createProperty(RootBot.ActiveSkillPropertyName); + this.skillsConfig = skillsConfig; + this.deliveryMode = ''; + + // Register the tangent dialog for testing tangents and resume. + this.addDialog(new TangentDialog(TANGENT_DIALOG)); + + // Create and add SkillDialog instances for the configured skills. + this.addSkillDialogs(conversationState, conversationIdFactory, skillClient, skillsConfig, botId); + + // Add ChoicePrompt to render available delivery modes. + this.addDialog(new ChoicePrompt(DELIVERY_PROMPT)); + + // Add ChoicePrompt to render available groups of skills. + this.addDialog(new ChoicePrompt(SKILL_GROUP_PROMPT)); + + // Add ChoicePrompt to render available skills. + this.addDialog(new ChoicePrompt(SKILL_PROMPT)); + + // Add ChoicePrompt to render skill actions. + this.addDialog(new ChoicePrompt(SKILL_ACTION_PROMPT)); + + // Special case: register SSO dialogs for skills that support SSO actions. + this.addSsoDialogs(); + + this.addDialog(new WaterfallDialog(WATERFALL_DIALOG, [ + this.selectDeliveryModeStep.bind(this), + this.selectSkillGroupStep.bind(this), + this.selectSkillStep.bind(this), + this.selectSkillActionStep.bind(this), + this.callSkillActionStep.bind(this), + this.finalStep.bind(this) + ])); + + this.initialDialogId = WATERFALL_DIALOG; + } + + /** + * The run method handles the incoming activity (in the form of a TurnContext) and passes it through the dialog system. + * If no dialog is active, it will start the default dialog. + * @param {import('botbuilder').TurnContext} turnContext + * @param {*} accessor + */ + async run (turnContext, accessor) { + const dialogSet = new DialogSet(accessor); + dialogSet.add(this); + + const dialogContext = await dialogSet.createContext(turnContext); + const results = await dialogContext.continueDialog(); + if (results.status === DialogTurnStatus.empty) { + await dialogContext.beginDialog(this.id); + } + } + + /** + * @param {import('botbuilder-dialogs').DialogContext} innerDc + */ + async onContinueDialog (innerDc) { + const activeSkill = await this.activeSkillProperty.get(innerDc.context, () => null); + const activity = innerDc.context.activity; + if (activeSkill != null && activity.type === ActivityTypes.Message && activity.text != null && activity.text.toLowerCase() === 'abort') { + // Cancel all dialogs when the user says abort. + // The SkillDialog automatically sends an EndOfConversation message to the skill to let the + // skill know that it needs to end its current dialogs, too. + await innerDc.cancelAllDialogs(); + return innerDc.replaceDialog(this.initialDialogId, { text: 'Canceled! \n\n What delivery mode would you like to use?' }); + } + // Sample to test a tangent when in the middle of a skill conversation. + if (activeSkill != null && activity.type === ActivityTypes.Message && activity.text != null && activity.text.toLowerCase() === 'tangent') { + // Start tangent. + return innerDc.beginDialog(TANGENT_DIALOG); + } + + return super.onContinueDialog(innerDc); + } + + /** + * Render a prompt to select the delivery mode to use. + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + async selectDeliveryModeStep (stepContext) { + // Create the PromptOptions with the delivery modes supported. + const messageText = stepContext.options && stepContext.options.text ? stepContext.options.text : 'What delivery mode would you like to use?'; + const retryMessageText = 'That was not a valid choice, please select a valid delivery mode.'; + + return stepContext.prompt(DELIVERY_PROMPT, { + prompt: MessageFactory.text(messageText, messageText, InputHints.ExpectingInput), + retryPrompt: MessageFactory.text(retryMessageText, retryMessageText, InputHints.ExpectingInput), + choices: ChoiceFactory.toChoices([DeliveryModes.Normal, DeliveryModes.ExpectReplies]) + }); + } + + /** + * Render a prompt to select the group of skills to use. + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + async selectSkillGroupStep (stepContext) { + // Set delivery mode. + this.deliveryMode = stepContext.result.value; + await this.deliveryModeProperty.set(stepContext.context, stepContext.result.value); + + const messageText = 'What group of skills would you like to use?'; + const retryMessageText = 'That was not a valid choice, please select a valid skill group.'; + + // Get a list of the groups for the skills in skillsConfig. + const groups = Object.values(this.skillsConfig.skills) + .map(skill => skill.group); + // Remove duplicates + const choices = [...new Set(groups)]; + + // Create the PromptOptions from the skill configuration which contains the list of configured skills. + return stepContext.prompt(SKILL_GROUP_PROMPT, { + prompt: MessageFactory.text(messageText, messageText, InputHints.ExpectingInput), + retryPrompt: MessageFactory.text(retryMessageText, retryMessageText, InputHints.ExpectingInput), + choices: ChoiceFactory.toChoices(choices) + }); + } + + /** + * Render a prompt to select the skill to call. + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + async selectSkillStep (stepContext) { + const skillGroup = stepContext.result.value; + + // Create the PromptOptions from the skill configuration which contains the list of configured skills. + const messageText = 'What skill would you like to call?'; + const retryMessageText = 'That was not a valid choice, please select a valid skill.'; + + // Get skills for the selected group. + const choices = Object.entries(this.skillsConfig.skills) + .filter(([, skill]) => skill.group === skillGroup) + .map(([id]) => id); + + return stepContext.prompt(SKILL_PROMPT, { + prompt: MessageFactory.text(messageText, messageText, InputHints.ExpectingInput), + retryPrompt: MessageFactory.text(retryMessageText, retryMessageText, InputHints.ExpectingInput), + choices: ChoiceFactory.toChoices(choices), + style: ListStyle.list + }); + } + + /** + * Render a prompt to select the begin action for the skill. + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + async selectSkillActionStep (stepContext) { + const selectedSkill = this.skillsConfig.skills[stepContext.result.value]; + const v3Bots = ['EchoSkillBotDotNetV3', 'EchoSkillBotJSV3']; + + // Set active skill. + await this.activeSkillProperty.set(stepContext.context, selectedSkill); + + // Exclude v3 bots from ExpectReplies. + if (this.deliveryMode === DeliveryModes.ExpectReplies && v3Bots.includes(selectedSkill.id)) { + await stepContext.context.SendActivityAsync(MessageFactory.text("V3 Bots do not support 'expectReplies' delivery mode.")); + + // Forget delivery mode and skill invocation. + await this.deliveryModeProperty.delete(stepContext.context); + await this.activeSkillProperty.delete(stepContext.context); + + // Restart setup dialog. + return stepContext.replaceDialog(this.initialDialogId); + } + + const skillActionChoices = selectedSkill.getActions(); + + if (skillActionChoices && skillActionChoices.length === 1) { + // The skill only supports one action (e.g. Echo), skip the prompt. + return stepContext.next({ value: skillActionChoices[0] }); + } + + // Create the PromptOptions with the actions supported by the selected skill. + const messageText = `Select an action to send to **${selectedSkill.id}**.`; + + return stepContext.prompt(SKILL_ACTION_PROMPT, { + prompt: MessageFactory.text(messageText, messageText, InputHints.ExpectingInput), + choices: ChoiceFactory.toChoices(skillActionChoices) + }); + } + + /** + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + async callSkillActionStep (stepContext) { + const selectedSkill = await this.activeSkillProperty.get(stepContext.context); + + // Save active skill in state. + await this.activeSkillProperty.set(stepContext.context, selectedSkill); + + // Create the initial activity to call the skill. + const skillActivity = this.skillsConfig.skills[selectedSkill.id].createBeginActivity(stepContext.result.value); + + if (skillActivity.name === 'Sso') { + // Special case, we start the SSO dialog to prepare the host to call the skill. + return stepContext.beginDialog(`${SSO_DIALOG_PREFIX}${selectedSkill.id}`); + } + + // We are manually creating the activity to send to the skill; ensure we add the ChannelData and Properties + // from the original activity so the skill gets them. + // Note: this is not necessary if we are just forwarding the current activity from context. + skillActivity.channelData = stepContext.context.activity.channelData; + skillActivity.properties = stepContext.context.activity.properties; + + // Create the BeginSkillDialogOptions and assign the activity to send. + const skillDialogArgs = { activity: skillActivity }; + + if (this.deliveryMode === DeliveryModes.ExpectReplies) { + skillDialogArgs.activity.deliveryMode = DeliveryModes.ExpectReplies; + } + + // Start the skillDialog instance with the arguments. + return stepContext.beginDialog(selectedSkill.id, skillDialogArgs); + } + + /** + * The SkillDialog has ended, render the results (if any) and restart MainDialog. + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + async finalStep (stepContext) { + const activeSkill = await this.activeSkillProperty.get(stepContext.context); + + if (stepContext.result) { + let message = `Skill "${activeSkill.id}" invocation complete.`; + message += ` Result: ${JSON.SerializeObject(stepContext.result)}`; + await stepContext.context.sendActivity(message); + } + + // Forget delivery mode and skill invocation. + await this.deliveryModeProperty.delete(stepContext.context); + await this.activeSkillProperty.delete(stepContext.context); + + // Restart setup dialog + return stepContext.replaceDialog(this.initialDialogId, { text: `Done with "${activeSkill.id}". \n\n What delivery mode would you like to use?` }); + } + + /** + * Helper method that creates and adds SkillDialog instances for the configured skills. + * @param {import('botbuilder').ConversationState} conversationState + * @param {import('../skillConversationIdFactory').SkillConversationIdFactory} conversationIdFactory + * @param {import('botbuilder').SkillHttpClient} skillClient + * @param {import('../skillsConfiguration').SkillsConfiguration} skillsConfig + * @param {string} botId + */ + addSkillDialogs (conversationState, conversationIdFactory, skillClient, skillsConfig, botId) { + Object.keys(skillsConfig.skills).forEach((skillId) => { + const skillInfo = skillsConfig.skills[skillId]; + + const skillDialogOptions = { + botId: botId, + conversationIdFactory, + conversationState, + skill: skillInfo, + skillHostEndpoint: process.env.SkillHostEndpoint, + skillClient + }; + + // Add a SkillDialog for the selected skill. + this.addDialog(new SkillDialog(skillDialogOptions, skillInfo.id)); + }); + } + + /** + * Special case. + * SSO needs a dialog in the host to allow the user to sign in. + * We create and several SsoDialog instances for each skill that supports SSO. + */ + addSsoDialogs () { + const addDialogs = (name, connectionName) => Object.values(this.dialogs.dialogs) + .filter(({ id }) => id.startsWith(name)) + .forEach(skill => this.addDialog(new SsoDialog(`${SSO_DIALOG_PREFIX}${skill.id}`, skill, connectionName))); + + addDialogs('WaterfallSkillBot', process.env.SsoConnectionName); + addDialogs('TeamsSkillBot', process.env.SsoConnectionNameTeams); + } +} + +module.exports.MainDialog = MainDialog; diff --git a/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/dialogs/sso/ssoDialog.js b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/dialogs/sso/ssoDialog.js new file mode 100644 index 0000000000..061d6ea2ad --- /dev/null +++ b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/dialogs/sso/ssoDialog.js @@ -0,0 +1,137 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const { ActivityTypes, EndOfConversationCodes, InputHints, MessageFactory } = require('botbuilder'); +const { ComponentDialog, ChoicePrompt, ChoiceFactory, DialogTurnStatus, WaterfallDialog } = require('botbuilder-dialogs'); +const { SsoSignInDialog } = require('./ssoSignInDialog'); + +const ACTION_STEP_PROMPT = 'ActionStepPrompt'; +const SSO_SIGNIN_DIALOG = 'SsoSignInDialog'; +const WATERFALL_DIALOG = 'WaterfallDialog'; + +/** + * Helps prepare the host for SSO operations and provides helpers to check the status and invoke the skill. + */ +class SsoDialog extends ComponentDialog { + /** + * @param {string} dialogId + * @param {*} ssoSkillDialog + * @param {string} connectionName + */ + constructor (dialogId, ssoSkillDialog, connectionName) { + super(dialogId); + + this.connectionName = connectionName; + this.skillDialogId = ssoSkillDialog.id; + + this.addDialog(new ChoicePrompt(ACTION_STEP_PROMPT)); + this.addDialog(new SsoSignInDialog(this.connectionName)); + this.addDialog(ssoSkillDialog); + + this.addDialog(new WaterfallDialog(WATERFALL_DIALOG, [ + this.promptActionStep.bind(this), + this.handleActionStep.bind(this), + this.promptFinalStep.bind(this) + ])); + + this.initialDialogId = WATERFALL_DIALOG; + } + + /** + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + async promptActionStep (stepContext) { + const messageText = 'What SSO action do you want to perform?'; + const repromptMessageText = 'That was not a valid choice, please select a valid choice.'; + + return stepContext.prompt(ACTION_STEP_PROMPT, { + prompt: MessageFactory.text(messageText, messageText, InputHints.ExpectingInput), + retryPrompt: MessageFactory.text(repromptMessageText, repromptMessageText, InputHints.ExpectingInput), + choices: await this.getPromptChoices(stepContext) + }); + } + + /** + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + async getPromptChoices (stepContext) { + const promptChoices = []; + const adapter = stepContext.context.adapter; + const token = await adapter.getUserToken(stepContext.context, this.connectionName); + + if (!token) { + promptChoices.push('Login'); + // Token exchange will fail when the host is not logged on and the skill should + // show a regular OAuthPrompt. + promptChoices.push('Call Skill (without SSO)'); + } else { + promptChoices.push('Logout'); + promptChoices.push('Show token'); + promptChoices.push('Call Skill (with SSO)'); + } + + promptChoices.push('Back'); + + return ChoiceFactory.toChoices(promptChoices); + } + + /** + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + async handleActionStep (stepContext) { + const action = stepContext.result.value; + + switch (action.toLowerCase()) { + case 'login': + return stepContext.beginDialog(SSO_SIGNIN_DIALOG); + + case 'logout': { + const adapter = stepContext.context.adapter; + await adapter.signOutUser(stepContext.context, this.connectionName); + await stepContext.context.sendActivity('You have been signed out.'); + return stepContext.next(); + } + + case 'show token': { + const tokenProvider = stepContext.context.adapter; + const token = await tokenProvider.getUserToken(stepContext.context, this.connectionName); + if (!token) { + await stepContext.context.sendActivity('User has no cached token.'); + } else { + await stepContext.context.sendActivity(`Here is your current SSO token: ${token.token}`); + } + return stepContext.next(); + } + + case 'call skill (with sso)': + case 'call skill (without sso)': { + const beginSkillActivity = { + type: ActivityTypes.Event, + name: 'Sso' + }; + return stepContext.beginDialog(this.skillDialogId, { activity: beginSkillActivity }); + } + + case 'back': + await stepContext.context.sendActivity({ + type: ActivityTypes.EndOfConversation, + code: EndOfConversationCodes.CompletedSuccessfully + }); + return { status: DialogTurnStatus.complete }; + + default: + // This should never be hit since the previous prompt validates the choice. + throw new Error(`[SsoDialog]: Unrecognized action: ${action}`); + } + } + + /** + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + async promptFinalStep (stepContext) { + // Restart the dialog (we will exit when the user says end). + return stepContext.replaceDialog(this.initialDialogId); + } +} + +module.exports.SsoDialog = SsoDialog; diff --git a/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/dialogs/sso/ssoSignInDialog.js b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/dialogs/sso/ssoSignInDialog.js new file mode 100644 index 0000000000..205c0db38a --- /dev/null +++ b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/dialogs/sso/ssoSignInDialog.js @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const { ComponentDialog, WaterfallDialog, OAuthPrompt } = require('botbuilder-dialogs'); + +const SSO_SIGNIN_DIALOG = 'SsoSignInDialog'; +const OAUTH_PROMPT = 'OAuthPrompt'; +const WATERFALL_DIALOG = 'WaterfallDialog'; + +/** + * Helps prepare the host for SSO operations and provides helpers to check the status and invoke the skill. + */ +class SsoSignInDialog extends ComponentDialog { + /** + * @param {string} connectionName + */ + constructor (connectionName) { + super(SSO_SIGNIN_DIALOG); + + this.addDialog(new OAuthPrompt(OAUTH_PROMPT, { + connectionName: connectionName, + text: `Sign in to the host bot using AAD for SSO and connection ${connectionName}`, + title: 'Sign In', + timeout: 60000 + })); + + this.addDialog(new WaterfallDialog(WATERFALL_DIALOG, [ + this.signInStep.bind(this), + this.displayTokenStep.bind(this) + ])); + + this.initialDialogId = WATERFALL_DIALOG; + } + + /** + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + async signInStep (stepContext) { + return stepContext.beginDialog(OAUTH_PROMPT); + } + + /** + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + async displayTokenStep (stepContext) { + if (!stepContext.result.token) { + await stepContext.context.sendActivity('No token was provided.'); + } else { + await stepContext.context.sendActivity(`Here is your token: ${stepContext.result.token}`); + } + return stepContext.endDialog(); + } +} + +module.exports.SsoSignInDialog = SsoSignInDialog; diff --git a/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/dialogs/tangentDialog.js b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/dialogs/tangentDialog.js new file mode 100644 index 0000000000..42dc86b6cc --- /dev/null +++ b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/dialogs/tangentDialog.js @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const { InputHints, MessageFactory } = require('botbuilder'); +const { ComponentDialog, DialogSet, TextPrompt, WaterfallDialog, DialogTurnStatus } = require('botbuilder-dialogs'); + +const TANGENT_DIALOG = 'TangentDialog'; +const WATERFALL_DIALOG = 'WaterfallDialog'; +const TEXT_PROMPT = 'TextPrompt'; + +class TangentDialog extends ComponentDialog { + /** + * @param {string} dialogId + */ + constructor (dialogId = TANGENT_DIALOG) { + super(dialogId); + + this.addDialog(new TextPrompt(TEXT_PROMPT)); + this.addDialog(new WaterfallDialog(WATERFALL_DIALOG, [ + this.step1.bind(this), + this.step2.bind(this), + this.endStep.bind(this) + ])); + + this.initialDialogId = WATERFALL_DIALOG; + } + + /** + * The run method handles the incoming activity (in the form of a TurnContext) and passes it through the dialog system. + * If no dialog is active, it will start the default dialog. + * @param {import('botbuilder').TurnContext} turnContext + * @param {*} accessor + */ + async run (turnContext, accessor) { + const dialogSet = new DialogSet(accessor); + dialogSet.add(this); + + const dialogContext = await dialogSet.createContext(turnContext); + const results = await dialogContext.continueDialog(); + if (results.status === DialogTurnStatus.empty) { + await dialogContext.beginDialog(this.id); + } + } + + /** + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + async step1 (stepContext) { + const messageText = 'Tangent step 1 of 2, say something.'; + const promptMessage = MessageFactory.text(messageText, messageText, InputHints.ExpectingInput); + + return stepContext.prompt(TEXT_PROMPT, promptMessage); + } + + /** + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + async step2 (stepContext) { + const messageText = 'Tangent step 2 of 2, say something.'; + const promptMessage = MessageFactory.text(messageText, messageText, InputHints.ExpectingInput); + + return stepContext.prompt(TEXT_PROMPT, promptMessage); + } + + /** + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + async endStep (stepContext) { + return stepContext.endDialog(); + } +} + +module.exports.TangentDialog = TangentDialog; diff --git a/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/index.js b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/index.js new file mode 100644 index 0000000000..0082806e4b --- /dev/null +++ b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/index.js @@ -0,0 +1,204 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// index.js is used to setup and configure your bot + +// Import required packages +const http = require('http'); +const https = require('https'); +const path = require('path'); +const restify = require('restify'); + +// Import required bot services. +// See https://aka.ms/bot-services to learn more about the different parts of a bot. +const { BotFrameworkAdapter, TurnContext, ActivityTypes, ChannelServiceRoutes, ConversationState, InputHints, MemoryStorage, SkillHttpClient, MessageFactory, SkillConversationIdFactory } = require('botbuilder'); +const { AuthenticationConfiguration, SimpleCredentialProvider } = require('botframework-connector'); + +// Import required bot configuration. +const ENV_FILE = path.join(__dirname, '.env'); +require('dotenv').config({ path: ENV_FILE }); + +// This bot's main dialog. +const { RootBot } = require('./bots/rootBot'); +const { SkillsConfiguration } = require('./skillsConfiguration'); +const { allowedSkillsClaimsValidator } = require('./authentication/allowedSkillsClaimsValidator'); +const { MainDialog } = require('./dialogs/mainDialog'); +const { LoggerMiddleware } = require('./middleware/loggerMiddleware'); +const { TokenExchangeSkillHandler } = require('./TokenExchangeSkillHandler'); + +const maxTotalSockets = (preallocatedSnatPorts, procCount = 1, weight = 0.5, overcommit = 1.1) => + Math.min( + Math.floor((preallocatedSnatPorts / procCount) * weight * overcommit), + preallocatedSnatPorts + ); + +// Create adapter. +// See https://aka.ms/about-bot-adapter to learn more about adapters. +const adapter = new BotFrameworkAdapter({ + appId: process.env.MicrosoftAppId, + appPassword: process.env.MicrosoftAppPassword, + authConfig: new AuthenticationConfiguration([], allowedSkillsClaimsValidator), + clientOptions: { + agentSettings: { + http: new http.Agent({ + keepAlive: true, + maxTotalSockets: maxTotalSockets(1024, 4, 0.3) + }), + https: new https.Agent({ + keepAlive: true, + maxTotalSockets: maxTotalSockets(1024, 4, 0.7) + }) + } + } +}); + +// Use the logger middleware to log messages. The default logger argument for LoggerMiddleware is Node's console.log(). +adapter.use(new LoggerMiddleware()); + +// Catch-all for errors. +const onTurnErrorHandler = async (context, error) => { + // This check writes out errors to the console log, instead of to app insights. + // NOTE: In production environment, you should consider logging this to Azure + // application insights. See https://aka.ms/bottelemetry for telemetry + // configuration instructions. + console.error(`\n [onTurnError] unhandled error: ${error}`); + + await sendErrorMessage(context, error); + await endSkillConversation(context); + await clearConversationState(context); +}; + +async function sendErrorMessage (context, error) { + try { + const { message, stack } = error; + + // Send a message to the user. + let errorMessageText = 'The bot encountered an error or bug.'; + let errorMessage = MessageFactory.text(errorMessageText, errorMessageText, InputHints.IgnoringInput); + errorMessage.value = { message, stack }; + await context.sendActivity(errorMessage); + + await context.sendActivity(`Exception: ${message}`); + await context.sendActivity(stack); + + errorMessageText = 'To continue to run this bot, please fix the bot source code.'; + errorMessage = MessageFactory.text(errorMessageText, errorMessageText, InputHints.ExpectingInput); + await context.sendActivity(errorMessage); + + // Send a trace activity, which will be displayed in Bot Framework Emulator. + await context.sendTraceActivity( + 'OnTurnError Trace', + `${error}`, + 'https://www.botframework.com/schemas/error', + 'TurnError' + ); + } catch (err) { + console.error(`\n [onTurnError] Exception caught in sendErrorMessage: ${err}`); + } +} + +async function endSkillConversation (context) { + try { + // Inform the active skill that the conversation is ended so that it has + // a chance to clean up. + // Note: ActiveSkillPropertyName is set by the RooBot while messages are being + // forwarded to a Skill. + const activeSkill = await conversationState.createProperty(RootBot.ActiveSkillPropertyName).get(context); + if (activeSkill) { + const botId = process.env.MicrosoftAppId; + + let endOfConversation = { + type: ActivityTypes.EndOfConversation, + code: 'RootSkillError' + }; + endOfConversation = TurnContext.applyConversationReference( + endOfConversation, TurnContext.getConversationReference(context.activity), true); + + await conversationState.saveChanges(context, true); + await skillClient.postToSkill(botId, activeSkill, skillsConfig.skillHostEndpoint, endOfConversation); + } + } catch (err) { + console.error(`\n [onTurnError] Exception caught on attempting to send EndOfConversation : ${err}`); + } +} + +async function clearConversationState (context) { + try { + // Delete the conversationState for the current conversation to prevent the + // bot from getting stuck in a error-loop caused by being in a bad state. + // ConversationState should be thought of as similar to "cookie-state" in a Web page. + await conversationState.delete(context); + } catch (err) { + console.error(`\n [onTurnError] Exception caught on attempting to Delete ConversationState : ${err}`); + } +} + +// Set the onTurnError for the singleton BotFrameworkAdapter. +adapter.onTurnError = onTurnErrorHandler; + +// Define a state store for your bot. See https://aka.ms/about-bot-state to learn more about using MemoryStorage. +// A bot requires a state store to persist the dialog and user state between messages. + +// For local development, in-memory storage is used. +// CAUTION: The Memory Storage used here is for local bot debugging only. When the bot +// is restarted, anything stored in memory will be gone. +const memoryStorage = new MemoryStorage(); +const conversationState = new ConversationState(memoryStorage); + +// Create the conversationIdFactory +const conversationIdFactory = new SkillConversationIdFactory(memoryStorage); + +// Load skills configuration +const skillsConfig = new SkillsConfiguration(); + +// Create the credential provider; +const credentialProvider = new SimpleCredentialProvider(process.env.MicrosoftAppId, process.env.MicrosoftAppPassword); + +// Create the skill client +const skillClient = new SkillHttpClient(credentialProvider, conversationIdFactory); + +// Create the main dialog. +const mainDialog = new MainDialog(conversationState, skillsConfig, skillClient, conversationIdFactory); +const bot = new RootBot(conversationState, skillClient, mainDialog); + +// Create HTTP server. +// maxParamLength defaults to 100, which is too short for the conversationId created in skillConversationIdFactory. +// See: https://github.com/microsoft/BotBuilder-Samples/issues/2194. +const server = restify.createServer({ maxParamLength: 1000 }); +server.listen(process.env.port || process.env.PORT || 36020, function () { + console.log(`\n${server.name} listening to ${server.url}`); + console.log('\nGet Bot Framework Emulator: https://aka.ms/botframework-emulator'); + console.log('\nTo talk to your bot, open the emulator select "Open Bot"'); +}); + +// Listen for incoming activities and route them to your bot main dialog. +server.post('/api/messages', (req, res) => { + // Route received a request to adapter for processing + adapter.processActivity(req, res, async (turnContext) => { + // route to bot activity handler. + await bot.run(turnContext); + }); +}); + +// Create and initialize the skill classes +const authConfig = new AuthenticationConfiguration([], allowedSkillsClaimsValidator); +const handler = new TokenExchangeSkillHandler(adapter, bot, conversationIdFactory, skillsConfig, skillClient, credentialProvider, authConfig); +const skillEndpoint = new ChannelServiceRoutes(handler); +skillEndpoint.register(server, '/api/skills'); + +// Listen for Upgrade requests for Streaming. +server.on('upgrade', (req, socket, head) => { + // Create an adapter scoped to this WebSocket connection to allow storing session data. + const streamingAdapter = new BotFrameworkAdapter({ + appId: process.env.MicrosoftAppId, + appPassword: process.env.MicrosoftAppPassword + }); + // Set onTurnError for the BotFrameworkAdapter created for each connection. + streamingAdapter.onTurnError = onTurnErrorHandler; + + streamingAdapter.useWebSocket(req, socket, head, async (context) => { + // After connecting via WebSocket, run this logic for every request sent over + // the WebSocket connection. + await bot.run(context); + }); +}); diff --git a/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/middleware/loggerMiddleware.js b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/middleware/loggerMiddleware.js new file mode 100644 index 0000000000..325e331c0d --- /dev/null +++ b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/middleware/loggerMiddleware.js @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const { ActivityTypes } = require('botbuilder'); + +/** + * Logs user and bot messages. It filters out ContinueConversation events coming from skill responses. + */ +class LoggerMiddleware { + // This defaults to using Node's console.log() method if a logger isn't passed in. + constructor (logger = console) { + this.logger = logger; + } + + /** + * @param {import('botbuilder').TurnContext} turnContext + * @param {Function} next + */ + async onTurn (turnContext, next) { + // Note: Skill responses will show as ContinueConversation events; we don't log those. + // We only log incoming messages from users. + if (turnContext.activity.type === ActivityTypes.Event && turnContext.activity.name !== 'continueConversation') { + const message = `User said: "${turnContext.activity.text}" Type: "${turnContext.activity.type}" Name: "${turnContext.activity.name}"`; + this.logger.log(message); + } + + // Register outgoing handler. + turnContext.onSendActivities(this.outgoingHandler.bind(this)); + + // Continue processing messages. + await next(); + } + + /** + * @param {import('botbuilder').TurnContext} turnContext + * @param {Partial[]} activities + * @param {Function} next + */ + async outgoingHandler (turnContext, activities, next) { + activities.forEach((activity) => { + const message = `Bot said: "${activity.text}" Type: "${activity.type}" Name: "${activity.name}"`; + this.logger.log(message); + }); + + await next(); + } +} + +module.exports.LoggerMiddleware = LoggerMiddleware; diff --git a/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/package.json b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/package.json new file mode 100644 index 0000000000..197d3fb53a --- /dev/null +++ b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/package.json @@ -0,0 +1,26 @@ +{ + "name": "waterfall-host-bot", + "version": "1.0.0", + "description": "A simple waterfall root bot for testing skills", + "author": "Microsoft", + "license": "MIT", + "main": "index.js", + "scripts": { + "start": "node ./index.js", + "watch": "nodemon ./index.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://github.com" + }, + "dependencies": { + "botbuilder": "~4.13.3", + "botbuilder-dialogs": "~4.13.3", + "dotenv": "~8.2.0", + "restify": "~8.5.1" + }, + "devDependencies": { + "nodemon": "~2.0.4" + } +} diff --git a/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/skills/echoSkill.js b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/skills/echoSkill.js new file mode 100644 index 0000000000..7c9165a319 --- /dev/null +++ b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/skills/echoSkill.js @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const { ActivityEx } = require('botbuilder-core'); +const { SkillDefinition } = require('./skillDefinition'); + +const SkillAction = { + Message: 'Message' +}; + +class EchoSkill extends SkillDefinition { + getActions () { + return Object.values(SkillAction); + } + + /** + * @param {string} actionId + */ + createBeginActivity (actionId) { + if (!this.getActions().includes(actionId)) { + throw new Error(`[EchoSkill]: Unable to create begin activity for "${actionId}".`); + } + + // We only support one activity for Echo so no further checks are needed + const activity = ActivityEx.createMessageActivity(); + activity.name = actionId; + activity.text = 'Begin the Echo Skill.'; + + return activity; + } +} + +module.exports.EchoSkill = EchoSkill; diff --git a/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/skills/skillDefinition.js b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/skills/skillDefinition.js new file mode 100644 index 0000000000..73f731cbb4 --- /dev/null +++ b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/skills/skillDefinition.js @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +class SkillDefinition { + getActions () { + throw new Error('[SkillDefinition]: Method not implemented'); + } + + createBeginActivity () { + throw new Error('[SkillDefinition]: Method not implemented'); + } +} + +module.exports.SkillDefinition = SkillDefinition; diff --git a/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/skills/teamsSkill.js b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/skills/teamsSkill.js new file mode 100644 index 0000000000..74c4d4d73d --- /dev/null +++ b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/skills/teamsSkill.js @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const { ActivityEx } = require('botbuilder-core'); +const { SkillDefinition } = require('./skillDefinition'); + +const SkillAction = { + TeamsTaskModule: 'TeamsTaskModule', + TeamsCardAction: 'TeamsCardAction', + TeamsConversation: 'TeamsConversation', + Cards: 'Cards', + Proactive: 'Proactive', + Attachment: 'Attachment', + Auth: 'Auth', + Sso: 'Sso', + Echo: 'Echo', + FileUpload: 'FileUpload', + Delete: 'Delete', + Update: 'Update' +}; + +class TeamsSkill extends SkillDefinition { + getActions () { + return Object.values(SkillAction); + } + + /** + * @param {string} actionId + */ + createBeginActivity (actionId) { + if (!this.getActions().includes(actionId)) { + throw new Error(`[TeamsSkill]: Unable to create begin activity for "${actionId}".`); + } + + // We don't support special parameters in these skills so a generic event with the right name + // will do in this case. + const activity = ActivityEx.createEventActivity(); + activity.name = actionId; + + return activity; + } +} + +module.exports.TeamsSkill = TeamsSkill; diff --git a/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/skills/waterfallSkill.js b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/skills/waterfallSkill.js new file mode 100644 index 0000000000..04e14a21d6 --- /dev/null +++ b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/skills/waterfallSkill.js @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const { ActivityEx } = require('botbuilder-core'); +const { SkillDefinition } = require('./skillDefinition'); + +const SkillAction = { + Cards: 'Cards', + Proactive: 'Proactive', + Auth: 'Auth', + MessageWithAttachment: 'MessageWithAttachment', + Sso: 'Sso', + FileUpload: 'FileUpload', + Echo: 'Echo', + Delete: 'Delete', + Update: 'Update' +}; + +class WaterfallSkill extends SkillDefinition { + getActions () { + return Object.values(SkillAction); + } + + /** + * @param {string} actionId + */ + createBeginActivity (actionId) { + if (!this.getActions().includes(actionId)) { + throw new Error(`[WaterfallSkill]: Unable to create begin activity for "${actionId}".`); + } + + // We don't support special parameters in these skills so a generic event with the right name + // will do in this case. + const activity = ActivityEx.createEventActivity(); + activity.name = actionId; + + return activity; + } +} + +module.exports.WaterfallSkill = WaterfallSkill; diff --git a/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/skillsConfiguration.js b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/skillsConfiguration.js new file mode 100644 index 0000000000..e5f0f27644 --- /dev/null +++ b/tests/functional/Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot/skillsConfiguration.js @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const { EchoSkill } = require('./skills/echoSkill'); +const { WaterfallSkill } = require('./skills/waterfallSkill'); +const { TeamsSkill } = require('./skills/teamsSkill'); + +/** + * A helper class that loads Skills information from configuration. + */ +class SkillsConfiguration { + constructor () { + this.skillsData = Object.entries(process.env) + .filter(([key]) => key.startsWith('skill_')) + .reduce((acc, [key, value]) => { + const [, id, attr] = key.split('_'); + acc[id] = acc[id] || {}; + const propName = { appid: 'appId', endpoint: 'skillEndpoint', group: 'group' }[attr.toLowerCase()]; + if (!propName) { throw new Error(`[SkillsConfiguration]: Invalid environment variable declaration ${key}`); } + acc[id][propName] = value; + if (propName === 'group') { + acc[id] = this.createSkillDefinition({ id, ...acc[id] }); + } + return acc; + }, {}); + + this.skillHostEndpointValue = process.env.SkillHostEndpoint; + if (!this.skillHostEndpointValue) { + throw new Error('[SkillsConfiguration]: Missing configuration parameter. SkillHostEndpoint is required'); + } + } + + get skills () { + return this.skillsData; + } + + get skillHostEndpoint () { + return this.skillHostEndpointValue; + } + + createSkillDefinition (skill) { + // Note: we hard code this for now, we should dynamically create instances based on the manifests. + switch (skill.group) { + case 'Echo': + return Object.assign(new EchoSkill(), skill); + + case 'Waterfall': + return Object.assign(new WaterfallSkill(), skill); + + case 'Teams': + return Object.assign(new TeamsSkill(), skill); + + default: + throw new Error(`[SkillsConfiguration]: Unable to find definition class for ${skill.id}`); + } + } +} + +module.exports.SkillsConfiguration = SkillsConfiguration; diff --git a/tests/functional/Bots/JavaScript/Skills/CodeFirst/EchoSkillBot-v3/.env b/tests/functional/Bots/JavaScript/Skills/CodeFirst/EchoSkillBot-v3/.env new file mode 100644 index 0000000000..6a8719bd3a --- /dev/null +++ b/tests/functional/Bots/JavaScript/Skills/CodeFirst/EchoSkillBot-v3/.env @@ -0,0 +1,3 @@ +MicrosoftAppId= +MicrosoftAppPassword= +AllowedCallers=* \ No newline at end of file diff --git a/tests/functional/Bots/JavaScript/Skills/CodeFirst/EchoSkillBot-v3/index.js b/tests/functional/Bots/JavaScript/Skills/CodeFirst/EchoSkillBot-v3/index.js new file mode 100644 index 0000000000..4a3d731311 --- /dev/null +++ b/tests/functional/Bots/JavaScript/Skills/CodeFirst/EchoSkillBot-v3/index.js @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const restify = require('restify'); +const builder = require('botbuilder'); +require('dotenv').config(); + +// Setup Restify Server +const server = restify.createServer(); +server.listen(process.env.port || process.env.PORT || 36407, function () { + console.log('%s listening to %s', server.name, server.url); +}); + +// Expose the manifest +server.get('/manifests/*', restify.plugins.serveStatic({ directory: './manifests', appendRequestPath: false })); + +// Bot Storage: Here we register the state storage for your bot. +// Default store: volatile in-memory store - Only for prototyping! +// We provide adapters for Azure Table, CosmosDb, SQL Azure, or you can implement your own! +// For samples and documentation, see: https://github.com/Microsoft/BotBuilder-Azure +const inMemoryStorage = new builder.MemoryBotStorage(); + +// Create chat connector for communicating with the Bot Framework Service +const connector = new builder.ChatConnector({ + appId: process.env.MicrosoftAppId, + appPassword: process.env.MicrosoftAppPassword, + enableSkills: true, + allowedCallers: [process.env.allowedCallers] +}); + +// Listen for messages from users +server.post('/api/messages', connector.listen()); + +// Create your bot with a function to receive messages from the user +new builder.UniversalBot(connector, function (session) { + session.on('error', function (error) { + const { message, stack } = error; + + // Send a message to the user. + let errorMessageText = 'The skill encountered an error or bug.'; + let activity = new builder.Message() + .text(`${errorMessageText}\r\n${message}\r\n${stack}`) + .speak(errorMessageText) + .inputHint(builder.InputHint.ignoringInput) + .value({ message, stack }); + session.send(activity); + + errorMessageText = 'To continue to run this bot, please fix the bot source code.'; + activity = new builder.Message() + .text(errorMessageText) + .speak(errorMessageText) + .inputHint(builder.InputHint.expectingInput); + session.send(activity); + + activity = new builder.Message() + .code('SkillError') + .text(message); + session.endConversation(activity); + }); + + switch (session.message.text.toLowerCase()) { + case 'end': + case 'stop': + session.say('Ending conversation from the skill...', { + inputHint: builder.InputHint.acceptingInput + }); + session.endConversation(); + break; + default: + session.say('Echo: ' + session.message.text, { + inputHint: builder.InputHint.acceptingInput + }); + session.say('Say "end" or "stop" and I\'ll end the conversation and back to the parent.'); + } +}).set('storage', inMemoryStorage); // Register in memory storage diff --git a/tests/functional/Bots/JavaScript/Skills/CodeFirst/EchoSkillBot-v3/manifests/echoskillbotv3-manifest-1.0.json b/tests/functional/Bots/JavaScript/Skills/CodeFirst/EchoSkillBot-v3/manifests/echoskillbotv3-manifest-1.0.json new file mode 100644 index 0000000000..f99c248eb8 --- /dev/null +++ b/tests/functional/Bots/JavaScript/Skills/CodeFirst/EchoSkillBot-v3/manifests/echoskillbotv3-manifest-1.0.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://schemas.botframework.com/schemas/skills/v2.1/skill-manifest.json", + "$id": "EchoSkillBotJSV3", + "name": "EchoSkillBotJSV3", + "version": "1.0", + "description": "This is a skill for echoing what the user sent to the bot (implemented using JS and BF v3).", + "publisherName": "Microsoft", + "privacyUrl": "https://microsoft.com/privacy", + "copyright": "Copyright (c) Microsoft Corporation. All rights reserved.", + "license": "https://github.com/microsoft/BotFramework-FunctionalTests/blob/main/LICENSE", + "tags": [ + "echo" + ], + "endpoints": [ + { + "name": "default", + "protocol": "BotFrameworkV3", + "description": "Localhost endpoint for the skill (on port 36407)", + "endpointUrl": "http://localhost:36407/api/messages", + "msAppId": "00000000-0000-0000-0000-000000000000" + } + ] +} diff --git a/tests/functional/Bots/JavaScript/Skills/CodeFirst/EchoSkillBot-v3/package.json b/tests/functional/Bots/JavaScript/Skills/CodeFirst/EchoSkillBot-v3/package.json new file mode 100644 index 0000000000..dbc5fe99ae --- /dev/null +++ b/tests/functional/Bots/JavaScript/Skills/CodeFirst/EchoSkillBot-v3/package.json @@ -0,0 +1,29 @@ +{ + "name": "v3-skill-bot", + "version": "1.0.0", + "description": "An example of a V3 bot that can be used as a skill", + "author": "Microsoft Corp.", + "license": "MIT", + "scripts": { + "start": "node ./index.js" + }, + "dependencies": { + "async": "^1.5.2", + "base64url": "^3.0.0", + "botbuilder": "^3.30.0", + "busboy": "^0.2.13", + "chrono-node": "^1.2.5", + "dotenv": "^8.2.0", + "jsonwebtoken": "^8.2.2", + "node-uuid": "^1.4.7", + "promise": "^7.1.1", + "request": "^2.78.0", + "restify": "^8.5.1", + "rsa-pem-from-mod-exp": "^0.8.4", + "sprintf-js": "^1.0.3", + "url-join": "^1.1.0" + }, + "devDependencies": { + "mocha": "^2.4.5" + } +} diff --git a/tests/functional/Bots/JavaScript/Skills/CodeFirst/EchoSkillBot/.env b/tests/functional/Bots/JavaScript/Skills/CodeFirst/EchoSkillBot/.env new file mode 100644 index 0000000000..b48405cf7f --- /dev/null +++ b/tests/functional/Bots/JavaScript/Skills/CodeFirst/EchoSkillBot/.env @@ -0,0 +1,3 @@ +MicrosoftAppId= +MicrosoftAppPassword= +AllowedCallers= diff --git a/tests/functional/Bots/JavaScript/Skills/CodeFirst/EchoSkillBot/authentication/allowedCallersClaimsValidator.js b/tests/functional/Bots/JavaScript/Skills/CodeFirst/EchoSkillBot/authentication/allowedCallersClaimsValidator.js new file mode 100644 index 0000000000..a0146a3aca --- /dev/null +++ b/tests/functional/Bots/JavaScript/Skills/CodeFirst/EchoSkillBot/authentication/allowedCallersClaimsValidator.js @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const { JwtTokenValidation, SkillValidation } = require('botframework-connector'); + +// Load the AppIds for the configured callers (we will only allow responses from skills we have configured). +// process.env.AllowedCallers is the list of parent bot Ids that are allowed to access the skill +// to add a new parent bot simply go to the .env file and add +// the parent bot's Microsoft AppId to the list under AllowedCallers, e.g.: +// AllowedCallers=195bd793-4319-4a84-a800-386770c058b2,38c74e7a-3d01-4295-8e66-43dd358920f8 +const allowedCallers = process.env.AllowedCallers ? process.env.AllowedCallers.split(',') : undefined; + +/** + * Sample claims validator that loads an allowed list from configuration if present + * and checks that requests are coming from allowed parent bots. + * @param claims An array of Claims decoded from the HTTP request's auth header + */ +const allowedCallersClaimsValidator = async (claims) => { + // If allowedCallers is undefined we allow all calls + if (allowedCallers && SkillValidation.isSkillClaim(claims)) { + // Check that the appId claim in the skill request is in the list of skills configured for this bot. + const appId = JwtTokenValidation.getAppIdFromClaims(claims); + if (!allowedCallers.includes(appId)) { + throw new Error(`Received a request from a bot with an app ID of "${appId}". To enable requests from this caller, add the app ID to your configuration file.`); + } + } +}; + +module.exports.allowedCallersClaimsValidator = allowedCallersClaimsValidator; diff --git a/tests/functional/Bots/JavaScript/Skills/CodeFirst/EchoSkillBot/bot.js b/tests/functional/Bots/JavaScript/Skills/CodeFirst/EchoSkillBot/bot.js new file mode 100644 index 0000000000..bf9ce2f986 --- /dev/null +++ b/tests/functional/Bots/JavaScript/Skills/CodeFirst/EchoSkillBot/bot.js @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const { ActivityHandler, ActivityTypes, EndOfConversationCodes } = require('botbuilder'); + +class EchoBot extends ActivityHandler { + constructor () { + super(); + // See https://aka.ms/about-bot-activity-message to learn more about the message and other activity types. + this.onMessage(async (context, next) => { + const activityText = context.activity.text.toLowerCase(); + if (activityText === 'end' || activityText === 'stop') { + await context.sendActivity('Ending conversation from the skill...'); + await context.sendActivity({ + type: ActivityTypes.EndOfConversation, + code: EndOfConversationCodes.CompletedSuccessfully + }); + } else { + await context.sendActivity(`Echo: ${context.activity.text}`); + await context.sendActivity('Say "end" or "stop" and I\'ll end the conversation and back to the parent.'); + } + + // By calling next() you ensure that the next BotHandler is run. + await next(); + }); + + this.onUnrecognizedActivityType(async (context, next) => { + // This will be called if the root bot is ending the conversation. Sending additional messages should be + // avoided as the conversation may have been deleted. + // Perform cleanup of resources if needed. + + // By calling next() you ensure that the next BotHandler is run. + await next(); + }); + } +} + +module.exports.EchoBot = EchoBot; diff --git a/tests/functional/Bots/JavaScript/Skills/CodeFirst/EchoSkillBot/index.js b/tests/functional/Bots/JavaScript/Skills/CodeFirst/EchoSkillBot/index.js new file mode 100644 index 0000000000..dbcf46b4f9 --- /dev/null +++ b/tests/functional/Bots/JavaScript/Skills/CodeFirst/EchoSkillBot/index.js @@ -0,0 +1,109 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const dotenv = require('dotenv'); +const http = require('http'); +const https = require('https'); +const path = require('path'); +const restify = require('restify'); + +// Import required bot services. +// See https://aka.ms/bot-services to learn more about the different parts of a bot. +const { ActivityTypes, BotFrameworkAdapter, InputHints, MessageFactory } = require('botbuilder'); +const { AuthenticationConfiguration } = require('botframework-connector'); + +// Import required bot configuration. +const ENV_FILE = path.join(__dirname, '.env'); +dotenv.config({ path: ENV_FILE }); + +// This bot's main dialog. +const { EchoBot } = require('./bot'); +const { allowedCallersClaimsValidator } = require('./authentication/allowedCallersClaimsValidator'); + +// Create HTTP server +const server = restify.createServer(); +server.listen(process.env.port || process.env.PORT || 36400, () => { + console.log(`\n${server.name} listening to ${server.url}`); + console.log('\nGet Bot Framework Emulator: https://aka.ms/botframework-emulator'); + console.log('\nTo talk to your bot, open the emulator select "Open Bot"'); +}); + +// Expose the manifest +server.get('/manifests/*', restify.plugins.serveStatic({ directory: './manifests', appendRequestPath: false })); + +const maxTotalSockets = (preallocatedSnatPorts, procCount = 1, weight = 0.5, overcommit = 1.1) => + Math.min( + Math.floor((preallocatedSnatPorts / procCount) * weight * overcommit), + preallocatedSnatPorts + ); + +// Create adapter. +// See https://aka.ms/about-bot-adapter to learn more about how bots work. +const adapter = new BotFrameworkAdapter({ + appId: process.env.MicrosoftAppId, + appPassword: process.env.MicrosoftAppPassword, + authConfig: new AuthenticationConfiguration([], allowedCallersClaimsValidator), + clientOptions: { + agentSettings: { + http: new http.Agent({ + keepAlive: true, + maxTotalSockets: maxTotalSockets(1024, 4, 0.3) + }), + https: new https.Agent({ + keepAlive: true, + maxTotalSockets: maxTotalSockets(1024, 4, 0.7) + }) + } + } +}); + +// Catch-all for errors. +adapter.onTurnError = async (context, error) => { + // This check writes out errors to console log .vs. app insights. + // NOTE: In production environment, you should consider logging this to Azure + // application insights. + console.error(`\n [onTurnError] unhandled error: ${error}`); + + try { + const { message, stack } = error; + + // Send a message to the user. + let errorMessageText = 'The skill encountered an error or bug.'; + let errorMessage = MessageFactory.text(`${errorMessageText}\r\n${message}\r\n${stack}`, errorMessageText, InputHints.IgnoringInput); + errorMessage.value = { message, stack }; + await context.sendActivity(errorMessage); + + errorMessageText = 'To continue to run this bot, please fix the bot source code.'; + errorMessage = MessageFactory.text(errorMessageText, errorMessageText, InputHints.ExpectingInput); + await context.sendActivity(errorMessage); + + // Send a trace activity, which will be displayed in Bot Framework Emulator + await context.sendTraceActivity( + 'OnTurnError Trace', + `${error}`, + 'https://www.botframework.com/schemas/error', + 'TurnError' + ); + + // Send and EndOfConversation activity to the skill caller with the error to end the conversation + // and let the caller decide what to do. + await context.sendActivity({ + type: ActivityTypes.EndOfConversation, + code: 'SkillError', + text: error + }); + } catch (err) { + console.error(`\n [onTurnError] Exception caught in onTurnError : ${err}`); + } +}; + +// Create the bot that will handle incoming messages. +const myBot = new EchoBot(); + +// Listen for incoming requests. +server.post('/api/messages', (req, res) => { + adapter.processActivity(req, res, async (context) => { + // Route to main dialog. + await myBot.run(context); + }); +}); diff --git a/tests/functional/Bots/JavaScript/Skills/CodeFirst/EchoSkillBot/manifests/echoskillbot-manifest-1.0.json b/tests/functional/Bots/JavaScript/Skills/CodeFirst/EchoSkillBot/manifests/echoskillbot-manifest-1.0.json new file mode 100644 index 0000000000..3ca4cfd1f2 --- /dev/null +++ b/tests/functional/Bots/JavaScript/Skills/CodeFirst/EchoSkillBot/manifests/echoskillbot-manifest-1.0.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://schemas.botframework.com/schemas/skills/v2.1/skill-manifest.json", + "$id": "EchoSkillBotJS", + "name": "EchoSkillBotJS", + "version": "1.0", + "description": "This is a skill for echoing what the user sent to the bot (implemented using JS).", + "publisherName": "Microsoft", + "privacyUrl": "https://microsoft.com/privacy", + "copyright": "Copyright (c) Microsoft Corporation. All rights reserved.", + "license": "https://github.com/microsoft/BotFramework-FunctionalTests/blob/main/LICENSE", + "tags": [ + "echo" + ], + "endpoints": [ + { + "name": "default", + "protocol": "BotFrameworkV3", + "description": "Localhost endpoint for the skill (on port 36400)", + "endpointUrl": "http://localhost:36400/api/messages", + "msAppId": "00000000-0000-0000-0000-000000000000" + } + ] +} \ No newline at end of file diff --git a/tests/functional/Bots/JavaScript/Skills/CodeFirst/EchoSkillBot/package.json b/tests/functional/Bots/JavaScript/Skills/CodeFirst/EchoSkillBot/package.json new file mode 100644 index 0000000000..ff06ea8e8e --- /dev/null +++ b/tests/functional/Bots/JavaScript/Skills/CodeFirst/EchoSkillBot/package.json @@ -0,0 +1,25 @@ +{ + "name": "echo-skill-bot", + "version": "1.0.0", + "description": "Bot Builder v4 echo skill bot sample", + "author": "Microsoft", + "license": "MIT", + "main": "index.js", + "scripts": { + "start": "node ./index.js", + "watch": "nodemon ./index.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://github.com" + }, + "dependencies": { + "botbuilder": "~4.13.3", + "dotenv": "^8.2.0", + "restify": "~8.5.1" + }, + "devDependencies": { + "nodemon": "~2.0.4" + } +} diff --git a/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/.env b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/.env new file mode 100644 index 0000000000..a9c638cff3 --- /dev/null +++ b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/.env @@ -0,0 +1,11 @@ +MicrosoftAppId= +MicrosoftAppPassword= +ConnectionName=TestOAuthProvider +SsoConnectionName= +ChannelService= +AllowedCallers= +SkillHostEndpoint=http://localhost:36420/api/skills/ + +EchoSkillInfo_id=EchoSkillBot +EchoSkillInfo_appId= +EchoSkillInfo_skillEndpoint=http://localhost:36400/api/messages diff --git a/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/authentication/allowedCallersClaimsValidator.js b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/authentication/allowedCallersClaimsValidator.js new file mode 100644 index 0000000000..a0146a3aca --- /dev/null +++ b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/authentication/allowedCallersClaimsValidator.js @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const { JwtTokenValidation, SkillValidation } = require('botframework-connector'); + +// Load the AppIds for the configured callers (we will only allow responses from skills we have configured). +// process.env.AllowedCallers is the list of parent bot Ids that are allowed to access the skill +// to add a new parent bot simply go to the .env file and add +// the parent bot's Microsoft AppId to the list under AllowedCallers, e.g.: +// AllowedCallers=195bd793-4319-4a84-a800-386770c058b2,38c74e7a-3d01-4295-8e66-43dd358920f8 +const allowedCallers = process.env.AllowedCallers ? process.env.AllowedCallers.split(',') : undefined; + +/** + * Sample claims validator that loads an allowed list from configuration if present + * and checks that requests are coming from allowed parent bots. + * @param claims An array of Claims decoded from the HTTP request's auth header + */ +const allowedCallersClaimsValidator = async (claims) => { + // If allowedCallers is undefined we allow all calls + if (allowedCallers && SkillValidation.isSkillClaim(claims)) { + // Check that the appId claim in the skill request is in the list of skills configured for this bot. + const appId = JwtTokenValidation.getAppIdFromClaims(claims); + if (!allowedCallers.includes(appId)) { + throw new Error(`Received a request from a bot with an app ID of "${appId}". To enable requests from this caller, add the app ID to your configuration file.`); + } + } +}; + +module.exports.allowedCallersClaimsValidator = allowedCallersClaimsValidator; diff --git a/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/bots/skillBot.js b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/bots/skillBot.js new file mode 100644 index 0000000000..51baad225e --- /dev/null +++ b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/bots/skillBot.js @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const { ActivityHandler, ActivityTypes } = require('botbuilder'); +const { runDialog } = require('botbuilder-dialogs'); + +class SkillBot extends ActivityHandler { + /** + * @param {import('botbuilder').ConversationState} conversationState + * @param {import('botbuilder-dialogs').Dialog} dialog + * @param {string} serverUrl + */ + constructor (conversationState, dialog, serverUrl) { + super(); + if (!conversationState) throw new Error('[SkillBot]: Missing parameter. conversationState is required'); + if (!dialog) throw new Error('[SkillBot]: Missing parameter. dialog is required'); + + this.conversationState = conversationState; + this.dialog = dialog; + this.serverUrl = serverUrl; + + this.onTurn(async (turnContext, next) => { + if (turnContext.activity.type !== ActivityTypes.ConversationUpdate) { + await runDialog(this.dialog, turnContext, this.conversationState.createProperty('DialogState')); + } + + await next(); + }); + + this.onMembersAdded(async (turnContext, next) => { + const text = 'Welcome to the waterfall skill bot. \n\nThis is a skill, you will need to call it from another bot to use it.'; + + for (const member of turnContext.activity.membersAdded) { + if (member.id !== turnContext.activity.recipient.id) { + await turnContext.sendActivity({ + type: ActivityTypes.Message, + text, + speak: text.replace('\n\n', '') + }); + await turnContext.sendActivity(`You can check the skill manifest to see what it supports here: ${this.serverUrl}/manifests/waterfallskillbot-manifest-1.0.json`); + } + } + + // By calling next() you ensure that the next BotHandler is run. + await next(); + }); + } + + /** + * Override the ActivityHandler.run() method to save state changes after the bot logic completes. + * @param {import('botbuilder').TurnContext} context + */ + async run (context) { + await super.run(context); + + // Save any state changes. The load happened during the execution of the Dialog. + await this.conversationState.saveChanges(context); + } +} + +module.exports.SkillBot = SkillBot; diff --git a/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/activityRouterDialog.js b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/activityRouterDialog.js new file mode 100644 index 0000000000..881f557a48 --- /dev/null +++ b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/activityRouterDialog.js @@ -0,0 +1,159 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const { ActivityTypes, InputHints, MessageFactory } = require('botbuilder'); +const { DialogTurnStatus, WaterfallDialog, ComponentDialog, SkillDialog } = require('botbuilder-dialogs'); +const { AuthDialog } = require('./auth/authDialog'); +const { CardDialog } = require('./cards/cardDialog'); +const { FileUploadDialog } = require('./fileUpload/fileUploadDialog'); +const { MessageWithAttachmentDialog } = require('./messageWithAttachment/messageWithAttachmentDialog'); +const { WaitForProactiveDialog } = require('./proactive/waitForProactiveDialog'); +const { SsoSkillDialog } = require('./sso/ssoSkillDialog'); +const { DeleteDialog } = require('./delete/deleteDialog'); +const { UpdateDialog } = require('./update/updateDialog'); + +const MAIN_DIALOG = 'ActivityRouterDialog'; +const WATERFALL_DIALOG = 'WaterfallDialog'; +const CARDS_DIALOG = 'Cards'; +const PROACTIVE_DIALOG = 'Proactive'; +const ATTACHMENT_DIALOG = 'MessageWithAttachment'; +const AUTH_DIALOG = 'Auth'; +const SSO_DIALOG = 'Sso'; +const FILE_UPLOAD_DIALOG = 'FileUpload'; +const ECHO_DIALOG = 'Echo'; +const DELETE_DIALOG = 'Delete'; +const UPDATE_DIALOG = 'Update'; + +/** + * A root dialog that can route activities sent to the skill to different sub-dialogs. + */ +class ActivityRouterDialog extends ComponentDialog { + /** + * @param {string} serverUrl + * @param {import('botbuilder').ConversationState} conversationState + * @param {import('../skillConversationIdFactory').SkillConversationIdFactory} conversationIdFactory + * @param {import('botbuilder').SkillHttpClient} skillClient + * @param {Object} continuationParametersStore + */ + constructor (serverUrl, conversationState, conversationIdFactory, skillClient, continuationParametersStore) { + super(MAIN_DIALOG); + + this.addDialog(new CardDialog(CARDS_DIALOG, serverUrl)) + .addDialog(new WaitForProactiveDialog(PROACTIVE_DIALOG, serverUrl, continuationParametersStore)) + .addDialog(new MessageWithAttachmentDialog(ATTACHMENT_DIALOG, serverUrl)) + .addDialog(new AuthDialog(AUTH_DIALOG, process.env)) + .addDialog(new SsoSkillDialog(SSO_DIALOG, process.env)) + .addDialog(new FileUploadDialog(FILE_UPLOAD_DIALOG)) + .addDialog(new DeleteDialog(DELETE_DIALOG)) + .addDialog(new UpdateDialog(UPDATE_DIALOG)) + .addDialog(this.createEchoSkillDialog(ECHO_DIALOG, conversationState, conversationIdFactory, skillClient, process.env)) + .addDialog(new WaterfallDialog(WATERFALL_DIALOG, [ + this.processActivity.bind(this) + ])); + + this.initialDialogId = WATERFALL_DIALOG; + } + + /** + * @param {string} dialogId + * @param {import('botbuilder').ConversationState} conversationState + * @param {import('../skillConversationIdFactory').SkillConversationIdFactory} conversationIdFactory + * @param {import('botbuilder').SkillHttpClient} skillClient + * @param {Object} configuration + */ + createEchoSkillDialog (dialogId, conversationState, conversationIdFactory, skillClient, configuration) { + const skillHostEndpoint = configuration.SkillHostEndpoint; + if (!skillHostEndpoint) { + throw new Error('SkillHostEndpoint is not in configuration'); + } + + const skill = { + id: configuration.EchoSkillInfo_id, + appId: configuration.EchoSkillInfo_appId, + skillEndpoint: configuration.EchoSkillInfo_skillEndpoint + }; + + if (!skill.id || !skill.skillEndpoint) { + throw new Error('EchoSkillInfo_id and EchoSkillInfo_skillEndpoint are not set in configuration'); + } + + return new SkillDialog({ + botId: configuration.MicrosoftAppId, + conversationIdFactory, + skillClient, + skillHostEndpoint, + conversationState, + skill + }, dialogId); + } + + /** + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + async processActivity (stepContext) { + // A skill can send trace activities, if needed. + await stepContext.context.sendActivity({ + type: ActivityTypes.Trace, + timestamp: new Date(), + text: 'ActivityRouterDialog.processActivity()', + label: `Got activityType: ${stepContext.context.activity.type}` + }); + + if (stepContext.context.activity.type === ActivityTypes.Event) { + return this.onEventActivity(stepContext); + } else { + // We didn't get an activity type we can handle. + await stepContext.context.sendActivity( + `Unrecognized ActivityType: "${stepContext.context.activity.type}".`, + undefined, + InputHints.IgnoringInput + ); + return { status: DialogTurnStatus.complete }; + } + } + + /** + * This method performs different tasks based on event name. + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + async onEventActivity (stepContext) { + const activity = stepContext.context.activity; + await stepContext.context.sendActivity({ + type: ActivityTypes.Trace, + timestamp: new Date(), + text: 'ActivityRouterDialog.onEventActivity()', + label: `Name: ${activity.name}, Value: ${JSON.stringify(activity.value)}` + }); + + // Resolve what to execute based on the event name. + switch (activity.name) { + case CARDS_DIALOG: + case PROACTIVE_DIALOG: + case ATTACHMENT_DIALOG: + case AUTH_DIALOG: + case SSO_DIALOG: + case FILE_UPLOAD_DIALOG: + case DELETE_DIALOG: + case UPDATE_DIALOG: + return stepContext.beginDialog(activity.name); + + case ECHO_DIALOG: { + // Start the EchoSkillBot + const messageActivity = MessageFactory.text("I'm the echo skill bot"); + messageActivity.deliveryMode = activity.deliveryMode; + return stepContext.beginDialog(activity.name, { activity: messageActivity }); + } + + default: + // We didn't get an event name we can handle. + await stepContext.context.sendActivity( + `Unrecognized EventName: "${stepContext.context.activity.name}".`, + undefined, + InputHints.IgnoringInput + ); + return { status: DialogTurnStatus.complete }; + } + } +} + +module.exports.ActivityRouterDialog = ActivityRouterDialog; diff --git a/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/auth/authDialog.js b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/auth/authDialog.js new file mode 100644 index 0000000000..573df18620 --- /dev/null +++ b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/auth/authDialog.js @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const { InputHints, MessageFactory } = require('botbuilder'); +const { ComponentDialog, OAuthPrompt, WaterfallDialog, ConfirmPrompt } = require('botbuilder-dialogs'); + +const WATERFALL_DIALOG = 'WaterfallDialog'; +const OAUTH_PROMPT = 'OAuthPrompt'; +const CONFIRM_PROMPT = 'ConfirmPrompt'; + +class AuthDialog extends ComponentDialog { + /** + * @param {string} dialogId + * @param {Object} configuration + */ + constructor (dialogId, configuration) { + super(dialogId); + + this.connectionName = configuration.ConnectionName; + + this.addDialog(new ConfirmPrompt(CONFIRM_PROMPT)) + .addDialog(new OAuthPrompt(OAUTH_PROMPT, { + connectionName: this.connectionName, + text: `Please Sign In to connection: '${this.connectionName}'`, + title: 'Sign In', + timeout: 300000 // User has 5 minutes to login (1000 * 60 * 5) + })); + + this.addDialog(new WaterfallDialog(WATERFALL_DIALOG, [ + this.promptStep.bind(this), + this.loginStep.bind(this), + this.displayToken.bind(this) + ])); + + // The initial child Dialog to run. + this.initialDialogId = WATERFALL_DIALOG; + } + + /** + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + promptStep (stepContext) { + return stepContext.beginDialog(OAUTH_PROMPT); + } + + /** + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + async loginStep (stepContext) { + // Get the token from the previous step. + const tokenResponse = stepContext.result; + + if (tokenResponse) { + stepContext.values.token = tokenResponse.token; + + // Show the token + const loggedInMessage = 'You are now logged in.'; + await stepContext.context.sendActivity(MessageFactory.text(loggedInMessage, loggedInMessage, InputHints.IgnoringInput)); + + return stepContext.prompt(CONFIRM_PROMPT, { + prompt: MessageFactory.text('Would you like to view your token?') + }); + } + + const tryAgainMessage = 'Login was not successful please try again.'; + await stepContext.context.sendActivity(MessageFactory.text(tryAgainMessage, tryAgainMessage, InputHints.IgnoringInput)); + return stepContext.replaceDialog(this.initialDialogId); + } + + /** + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + async displayToken (stepContext) { + const result = stepContext.result; + + if (result) { + const showTokenMessage = 'Here is your token:'; + await stepContext.context.sendActivity(MessageFactory.text(`${showTokenMessage} ${stepContext.values.token}`, showTokenMessage, InputHints.IgnoringInput)); + } + + // Sign out + stepContext.context.adapter.signOutUser(stepContext.context, this.connectionName); + const signOutMessage = 'I have signed you out.'; + await stepContext.context.sendActivity(MessageFactory.text(signOutMessage, signOutMessage, InputHints.IgnoringInput)); + + return stepContext.endDialog(); + } +} + +module.exports.AuthDialog = AuthDialog; diff --git a/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/cardDialog.js b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/cardDialog.js new file mode 100644 index 0000000000..58f61927b0 --- /dev/null +++ b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/cardDialog.js @@ -0,0 +1,223 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const { InputHints, MessageFactory, CardFactory, ActionTypes } = require('botbuilder'); +const { ComponentDialog, WaterfallDialog, ChoicePrompt, ListStyle, ChoiceFactory, DialogTurnStatus } = require('botbuilder-dialogs'); +const fs = require('fs'); +const path = require('path'); +const { CardOptions } = require('./cardOptions'); +const { CardSampleHelper } = require('./cardSampleHelper'); +const { ChannelSupportedCards } = require('./channelSupportedCards'); + +const WATERFALL_DIALOG = 'WaterfallDialog'; +const CARD_PROMPT = 'CardPrompt'; + +class CardDialog extends ComponentDialog { + /** + * @param {string} dialogId + * @param {string} serverUrl + */ + constructor (dialogId, serverUrl) { + super(dialogId); + + this.serverUrl = serverUrl; + this.mindBlownGif = 'https://media3.giphy.com/media/xT0xeJpnrWC4XWblEk/giphy.gif?cid=ecf05e47mye7k75sup6tcmadoom8p1q8u03a7g2p3f76upp9&rid=giphy.gif'; + this.corgiOnCarouselVideo = 'https://www.youtube.com/watch?v=LvqzubPZjHE'; + this.teamsLogoFileName = 'teams-logo.png'; + + this.addDialog(new ChoicePrompt(CARD_PROMPT, this.cardPromptValidator)) + .addDialog(new WaterfallDialog(WATERFALL_DIALOG, [ + this.selectCard.bind(this), + this.displayCard.bind(this) + ])); + + this.initialDialogId = WATERFALL_DIALOG; + } + + /** + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + * @returns {import('botframework-schema').HeroCard} + */ + makeUpdatedHeroCard (stepContext) { + const data = stepContext.context.activity.value; + data.count++; + + const { title, text, cardActions } = { + title: 'Newly updated card.', + text: `Update count - ${data.count}`, + cardActions: [ + { + type: ActionTypes.MessageBack, + title: 'Update Card', + text: 'UpdateCardAction', + value: data + } + ] + }; + + return CardFactory.heroCard(title, text, null, cardActions); + } + + /** + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + async selectCard (stepContext) { + // Create the PromptOptions from the skill configuration which contain the list of configured skills. + const messageText = 'What card do you want?'; + const repromptMessageText = 'This message will be created in the validation code'; + const options = { + prompt: MessageFactory.text(messageText, messageText, InputHints.ExpectingInput), + retryPrompt: MessageFactory.text(repromptMessageText, repromptMessageText, InputHints.ExpectingInput), + choices: ChoiceFactory.toChoices(Object.values(CardOptions)), + style: ListStyle.list + }; + + // Ask the user to enter a card choice. + return stepContext.prompt(CARD_PROMPT, options); + } + + /** + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + async displayCard (stepContext) { + if (stepContext.context.activity.value != null) { + await this.handleSpecialActivity(stepContext); + } else { + // Checks to see if the activity is an adaptive card update or a bot action respose. + const card = stepContext.result.value; + const cardType = Object.keys(CardOptions).find(key => CardOptions[key].toLowerCase() === card.toLowerCase()); + const { channelId } = stepContext.context.activity; + + if (ChannelSupportedCards.isCardSupported(channelId, cardType)) { + switch (cardType) { + case CardOptions.AdaptiveCardBotAction: + await stepContext.context.sendActivity(MessageFactory.attachment(CardSampleHelper.createAdaptiveCardBotAction())); + break; + + case CardOptions.AdaptiveCardTeamsTaskModule: + await stepContext.context.sendActivity(MessageFactory.attachment(CardSampleHelper.createAdaptiveCardTaskModule())); + break; + + case CardOptions.AdaptiveCardSubmitAction: + await stepContext.context.sendActivity(MessageFactory.attachment(CardSampleHelper.createAdaptiveCardSubmit())); + break; + + case CardOptions.Hero: + await stepContext.context.sendActivity(MessageFactory.attachment(CardSampleHelper.createHeroCard())); + break; + + case CardOptions.Thumbnail: + await stepContext.context.sendActivity(MessageFactory.attachment(CardSampleHelper.createThumbnailCard())); + break; + + case CardOptions.Receipt: + await stepContext.context.sendActivity(MessageFactory.attachment(CardSampleHelper.createReceiptCard())); + break; + + case CardOptions.Signin: + await stepContext.context.sendActivity(MessageFactory.attachment(CardSampleHelper.createSigninCard())); + break; + + case CardOptions.Carousel: + // NOTE: if cards are NOT the same height in a carousel, Teams will instead display as AttachmentLayoutTypes.List + await stepContext.context.sendActivity(MessageFactory.carousel([ + CardSampleHelper.createHeroCard(), + CardSampleHelper.createHeroCard(), + CardSampleHelper.createHeroCard() + ])); + break; + + case CardOptions.List: + await stepContext.context.sendActivity(MessageFactory.list([ + CardSampleHelper.createHeroCard(), + CardSampleHelper.createHeroCard(), + CardSampleHelper.createHeroCard() + ])); + break; + + case CardOptions.O365: + await stepContext.context.sendActivity(MessageFactory.attachment(CardSampleHelper.createO365ConnectorCard())); + break; + + case CardOptions.TeamsFileConsent: { + const file = fs.readFileSync(path.resolve(__dirname, 'files', this.teamsLogoFileName)); + await stepContext.context.sendActivity(MessageFactory.attachment(CardSampleHelper.createTeamsFileConsentCard(this.teamsLogoFileName, file.byteLength))); + break; + } + + case CardOptions.Animation: + await stepContext.context.sendActivity(MessageFactory.attachment(CardSampleHelper.createAnimationCard(this.mindBlownGif))); + break; + + case CardOptions.Audio: + await stepContext.context.sendActivity(MessageFactory.attachment(CardSampleHelper.createAudioCard(`${this.serverUrl}/api/music`))); + break; + + case CardOptions.Video: + await stepContext.context.sendActivity(MessageFactory.attachment(CardSampleHelper.createVideoCard(this.corgiOnCarouselVideo))); + break; + + case CardOptions.AdaptiveUpdate: + await stepContext.context.sendActivity(MessageFactory.attachment(CardSampleHelper.createUpdateAdaptiveCard())); + break; + + case CardOptions.End: + return { status: DialogTurnStatus.complete }; + } + } else { + await stepContext.context.sendActivity(MessageFactory.text(`${card} cards are not supported in the ${channelId} channel.`)); + } + } + + return stepContext.replaceDialog(this.initialDialogId); + } + + /** + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + async handleSpecialActivity (stepContext) { + if (!stepContext.context.activity.text) { + await stepContext.context.sendActivity(MessageFactory.Text(`I received an activity with this data in the value field ${stepContext.context.activity.value}`)); + } else { + if (stepContext.context.activity.text.toLowerCase().includes('update')) { + if (!stepContext.context.activity.replyToId) { + await stepContext.context.sendActivity(MessageFactory.Text(`Update activity is not supported in the ${stepContext.context.activity.channelId} channel.`)); + } else { + const heroCard = this.makeUpdatedHeroCard(stepContext); + const activity = MessageFactory.attachment(heroCard); + activity.id = stepContext.context.activity.replyToId; + await stepContext.context.updateActivity(activity); + } + } else { + await stepContext.context.sendActivity(MessageFactory.Text(`I received an activity with this data in the text field ${stepContext.context.activity.text} and this data in the value field ${stepContext.context.activity.value}`)); + } + } + } + + /** + * @param {import('botbuilder-dialogs').PromptValidatorContext} promptContext + */ + async cardPromptValidator (promptContext) { + if (!promptContext.recognized.succeeded) { + // This checks to see if this response is the user clicking the update button on the card + if (promptContext.context.activity.value) { + return true; + } + + if (promptContext.context.activity.attachments) { + return true; + } + + const activityJson = JSON.stringify(promptContext.context.activity, null, 4).replace(/\n/g, '\r\n'); + + // Render the activity so we can assert in tests. + // We may need to simplify the json if it gets too complicated to test. + promptContext.options.retryPrompt.text = `Got ${activityJson}\n\n${promptContext.options.prompt.text}`; + return false; + } + + return true; + } +} + +module.exports.CardDialog = CardDialog; diff --git a/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/cardOptions.js b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/cardOptions.js new file mode 100644 index 0000000000..85a54205a0 --- /dev/null +++ b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/cardOptions.js @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +module.exports.CardOptions = { + /** + * Adaptive card - Bot action + */ + AdaptiveCardBotAction: 'AdaptiveCardBotAction', + /** + * Adaptive card - Task module + */ + AdaptiveCardTeamsTaskModule: 'AdaptiveCardTeamsTaskModule', + /** + * Adaptive card - Submit action + */ + AdaptiveCardSubmitAction: 'AdaptiveCardSubmitAction', + /** + * Hero cards + */ + Hero: 'Hero', + /** + * Thumbnail cards + */ + Thumbnail: 'Thumbnail', + /** + * Receipt cards + */ + Receipt: 'Receipt', + /** + * Signin cards + */ + Signin: 'Signin', + /** + * Carousel cards + */ + Carousel: 'Carousel', + /** + * List cards + */ + List: 'List', + /** + * O365 cards + */ + O365: 'O365', + /** + * File cards + */ + TeamsFileConsent: 'TeamsFileConsent', + /** + * Animation cards + */ + Animation: 'Animation', + /** + * Audio cards + */ + Audio: 'Audio', + /** + * Video cards + */ + Video: 'Video', + /** + * Adaptive update cards + */ + AdaptiveUpdate: 'AdaptiveUpdate', + /** + * Ends the card selection dialog + */ + End: 'End' +}; diff --git a/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/cardSampleHelper.js b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/cardSampleHelper.js new file mode 100644 index 0000000000..36613d78de --- /dev/null +++ b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/cardSampleHelper.js @@ -0,0 +1,514 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const { CardFactory, ActionTypes } = require('botbuilder'); + +class CardSampleHelper { + static createAdaptiveCardBotAction () { + return CardFactory.adaptiveCard({ + type: 'AdaptiveCard', + version: '1.2', + body: [ + { + text: 'Bot Builder actions', + type: 'TextBlock' + } + ], + actions: [ + { + type: 'Action.Submit', + title: 'imBack', + data: { + msteams: { + type: 'imBack', + value: 'text' + } + } + }, + { + type: 'Action.Submit', + title: 'message back', + data: { + msteams: { + type: ActionTypes.MessageBack, + value: { + key: 'value' + } + } + } + }, + { + type: 'Action.Submit', + title: 'message back local echo', + data: { + msteams: { + type: ActionTypes.MessageBack, + text: 'text received by bots', + displayText: 'display text message back', + value: { + key: 'value' + } + } + } + }, + { + type: 'Action.Submit', + title: 'invoke', + data: { + msteams: { + type: 'invoke', + value: { + key: 'value' + } + } + } + } + ] + }); + } + + static createAdaptiveCardTaskModule () { + return CardFactory.adaptiveCard({ + type: 'AdaptiveCard', + version: '1.2', + body: [ + { + type: 'TextBlock', + text: 'Task Module Adaptive Card' + } + ], + actions: [ + { + type: 'Action.Submit', + title: 'Launch Task Module', + data: { + msteams: { + type: 'invoke', + value: '{\r\n "hiddenKey": "hidden value from task module launcher",\r\n "type": "task/fetch"\r\n}' + } + } + } + ] + }); + } + + static createAdaptiveCardSubmit () { + return CardFactory.adaptiveCard({ + type: 'AdaptiveCard', + version: '1.2', + body: [ + { + type: 'TextBlock', + text: 'Bot Builder actions' + }, + { + type: 'Input.Text', + id: 'x' + } + ], + actions: [ + { + type: 'Action.Submit', + title: 'Action.Submit', + data: { + key: 'value' + } + } + ] + }); + } + + static createHeroCard () { + const { title, subtitle, text, images, buttons } = { + title: 'BotFramework Hero Card', + subtitle: 'Microsoft Bot Framework', + text: 'Build and connect intelligent bots to interact with your users naturally wherever they are, from text/sms to Skype, Slack, Office 365 mail and other popular services.', + images: [ + { + url: 'https://sec.ch9.ms/ch9/7ff5/e07cfef0-aa3b-40bb-9baa-7c9ef8ff7ff5/buildreactionbotframework_960.jpg' + } + ], + buttons: [ + { + type: ActionTypes.OpenUrl, + title: 'Get Started', + value: 'https://docs.microsoft.com/bot-framework' + } + ] + }; + + return CardFactory.heroCard(title, images, buttons, { subtitle, text }); + } + + static createThumbnailCard () { + const { title, subtitle, text, images, buttons } = { + title: 'BotFramework Thumbnail Card', + subtitle: 'Microsoft Bot Framework', + text: 'Build and connect intelligent bots to interact with your users naturally wherever they are, from text/sms to Skype, Slack, Office 365 mail and other popular services.', + images: [ + { + url: 'https://sec.ch9.ms/ch9/7ff5/e07cfef0-aa3b-40bb-9baa-7c9ef8ff7ff5/buildreactionbotframework_960.jpg' + } + ], + buttons: [ + { + type: ActionTypes.OpenUrl, + title: 'Get Started', + value: 'https://docs.microsoft.com/bot-framework' + } + ] + }; + + return CardFactory.thumbnailCard(title, images, buttons, { subtitle, text }); + } + + static createReceiptCard () { + return CardFactory.receiptCard({ + title: 'John Doe', + facts: [ + { + key: 'Order Number', + value: '1234' + }, + { + key: 'Payment Method', + value: 'VISA 5555-****' + } + ], + items: [ + { + title: 'Data Transfer', + image: { + url: 'https://github.com/amido/azure-vector-icons/raw/master/renders/traffic-manager.png' + }, + price: '$ 38.45', + quantity: '368' + }, + { + title: 'App Service', + image: { + url: 'https://github.com/amido/azure-vector-icons/raw/master/renders/cloud-service.png' + }, + price: '$ 45.00', + quantity: '720' + } + ], + total: '$ 90.95', + tax: '$ 7.50', + buttons: [ + { + type: ActionTypes.OpenUrl, + title: 'More information', + image: 'https://account.windowsazure.com/content/6.10.1.38-.8225.160809-1618/aux-pre/images/offer-icon-freetrial.png', + value: 'https://azure.microsoft.com/en-us/pricing/' + } + ] + }); + } + + static createSigninCard () { + return CardFactory.signinCard('Sign-in', 'https://login.microsoftonline.com/', 'BotFramework Sign-in Card'); + } + + static createO365ConnectorCard () { + return CardFactory.o365ConnectorCard({ + title: 'card title', + text: 'card text', + summary: 'O365 card summary', + themeColor: '#E67A9E', + sections: [ + { + title: '**section title**', + text: 'section text', + activityTitle: 'activity title', + activitySubtitle: 'activity subtitle', + activityText: 'activity text', + activityImage: 'http://connectorsdemo.azurewebsites.net/images/MSC12_Oscar_002.jpg', + activityImageType: 'avatar', + markdown: true, + facts: [ + { + name: 'Fact name 1', + value: 'Fact value 1' + }, + { + name: 'Fact name 2', + value: 'Fact value 2' + } + ], + images: [ + { + image: 'http://connectorsdemo.azurewebsites.net/images/MicrosoftSurface_024_Cafe_OH-06315_VS_R1c.jpg', + title: 'image 1' + }, + { + image: 'http://connectorsdemo.azurewebsites.net/images/WIN12_Scene_01.jpg', + title: 'image 2' + }, + { + image: 'http://connectorsdemo.azurewebsites.net/images/WIN12_Anthony_02.jpg', + title: 'image 3' + } + ] + } + ], + potentialAction: + [ + { + '@type': 'ActionCard', + inputs: [ + { + '@type': 'MultichoiceInput', + choices: [ + { + display: 'Choice 1', + value: '1' + }, + { + display: 'Choice 2', + value: '2' + }, + { + display: 'Choice 3', + value: '3' + } + ], + style: 'expanded', + isMultiSelect: true, + id: 'list-1', + isRequired: true, + title: 'Pick multiple options' + }, + { + '@type': 'MultichoiceInput', + choices: [ + { + display: 'Choice 4', + value: '4' + }, + { + display: 'Choice 5', + value: '5' + }, + { + display: 'Choice 6', + value: '6' + } + ], + style: 'compact', + isMultiSelect: true, + id: 'list-2', + isRequired: true, + title: 'Pick multiple options' + }, + { + '@type': 'MultichoiceInput', + choices: [ + { + display: 'Choice a', + value: 'a' + }, + { + display: 'Choice b', + value: 'b' + }, + { + display: 'Choice c', + value: 'c' + } + ], + style: 'expanded', + isMultiSelect: false, + id: 'list-3', + isRequired: false, + title: 'Pick an option' + }, + { + '@type': 'MultichoiceInput', + choices: [ + { + display: 'Choice x', + value: 'x' + }, + { + display: 'Choice y', + value: 'y' + }, + { + display: 'Choice z', + value: 'z' + } + ], + style: 'compact', + isMultiSelect: false, + id: 'list-4', + isRequired: false, + title: 'Pick an option' + } + ], + actions: [ + { + '@type': 'HttpPOST', + body: '{"list1":"{{list-1.value}}", "list2":"{{list-2.value}}", "list3":"{{list-3.value}}", "list4":"{{list-4.value}}"}', + name: 'Send', + '@id': 'card-1-btn-1' + } + ], + name: 'Multiple Choice', + '@id': 'card-1' + }, + { + '@type': 'ActionCard', + inputs: [ + { + '@type': 'TextInput', + isMultiline: true, + id: 'text-1', + isRequired: false, + title: 'multiline, no maxLength' + }, + { + '@type': 'TextInput', + isMultiline: false, + id: 'text-2', + isRequired: false, + title: 'single line, no maxLength' + }, + { + '@type': 'TextInput', + isMultiline: true, + maxLength: 10, + id: 'text-3', + isRequired: true, + title: 'multiline, max len = 10, isRequired' + }, + { + '@type': 'TextInput', + isMultiline: false, + maxLength: 10, + id: 'text-4', + isRequired: true, + title: 'single line, max len = 10, isRequired' + } + ], + actions: [ + { + '@type': 'HttpPOST', + body: '{"text1":"{{text-1.value}}", "text2":"{{text-2.value}}", "text3":"{{text-3.value}}", "text4":"{{text-4.value}}"}', + name: 'Send', + '@id': 'card-2-btn-1' + } + ], + name: 'Text Input', + '@id': 'card-2' + }, + { + '@type': 'ActionCard', + inputs: [ + { + '@type': 'DateInput', + includeTime: true, + id: 'date-1', + isRequired: true, + title: 'date with time' + }, + { + '@type': 'DateInput', + includeTime: false, + id: 'date-2', + isRequired: false, + title: 'date only' + } + ], + actions: [ + { + '@type': 'HttpPOST', + body: '{"date1":"{{date-1.value}}", "date2":"{{date-2.value}}"}', + name: 'Send', + '@id': 'card-3-btn-1' + } + ], + name: 'Date Input', + '@id': 'card-3' + }, + { + '@type': 'ViewAction', + target: [ + 'http://microsoft.com' + ], + name: 'View Action' + }, + { + '@type': 'OpenUri', + targets: [ + { + os: 'default', + uri: 'http://microsoft.com' + }, + { + os: 'iOS', + uri: 'http://microsoft.com' + }, + { + os: 'android', + uri: 'http://microsoft.com' + }, + { + os: 'windows', + uri: 'http://microsoft.com' + } + ], + name: 'Open Uri', + '@id': 'open-uri' + } + ] + + }); + } + + static createTeamsFileConsentCard (filename, filesize) { + const consentContext = { filename }; + + return { + contentType: 'application/vnd.microsoft.teams.card.file.consent', + name: filename, + content: { + description: 'This is the file I want to send you', + sizeInbytes: filesize, + acceptContext: consentContext, + declineContext: consentContext + } + }; + } + + static createAnimationCard (url) { + return CardFactory.animationCard('Animation Card', [{ url }], null, { autostart: true }); + } + + static createAudioCard (url) { + return CardFactory.audioCard('Audio Card', [{ url }], null, { autoloop: true }); + } + + static createVideoCard (url) { + return CardFactory.videoCard('Video Card', [{ url }]); + } + + static createUpdateAdaptiveCard () { + const { title, text, buttons } = { + title: 'Update card', + text: 'Update Card Action', + buttons: [ + { + type: ActionTypes.MessageBack, + title: 'Update card title', + text: 'Update card text', + value: { count: 0 } + } + ] + }; + + return CardFactory.heroCard(title, text, null, buttons); + } +} + +module.exports.CardSampleHelper = CardSampleHelper; diff --git a/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/channelSupportedCards.js b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/channelSupportedCards.js new file mode 100644 index 0000000000..31770b6939 --- /dev/null +++ b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/channelSupportedCards.js @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const { Channels } = require('botbuilder'); +const { CardOptions } = require('./cardOptions'); + +/** + * This tracks what cards are not supported in a given channel. + */ +const unsupportedChannelCards = { + [Channels.Emulator]: [ + CardOptions.AdaptiveCardTeamsTaskModule, + CardOptions.AdaptiveUpdate, + CardOptions.TeamsFileConsent, + CardOptions.O365 + ], + [Channels.Directline]: [ + CardOptions.AdaptiveUpdate + ], + [Channels.Telegram]: [ + CardOptions.AdaptiveCardBotAction, + CardOptions.AdaptiveCardTeamsTaskModule, + CardOptions.AdaptiveCardSubmitAction, + CardOptions.List, + CardOptions.TeamsFileConsent + ] +}; + +class ChannelSupportedCards { + /** + * This let's you know if a card is supported in a given channel. + * @param {string} channel Bot Connector Channel. + * @param {keyof CardOptions} type Card Option to be checked. + * @returns A bool if the card is supported in the channel. + */ + static isCardSupported (channel, type) { + const unsupportedChannel = unsupportedChannelCards[channel]; + if (unsupportedChannel) { + return !unsupportedChannel.includes(type); + } + + return true; + } +} + +module.exports.ChannelSupportedCards = ChannelSupportedCards; diff --git a/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/files/buildreactionbotframework.jpg b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/files/buildreactionbotframework.jpg new file mode 100644 index 0000000000..f410fc137e Binary files /dev/null and b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/files/buildreactionbotframework.jpg differ diff --git a/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/files/music.mp3 b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/files/music.mp3 new file mode 100644 index 0000000000..b4ff6ee30f Binary files /dev/null and b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/files/music.mp3 differ diff --git a/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/files/teams-logo.png b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/files/teams-logo.png new file mode 100644 index 0000000000..78b0a0c308 Binary files /dev/null and b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/files/teams-logo.png differ diff --git a/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/delete/deleteDialog.js b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/delete/deleteDialog.js new file mode 100644 index 0000000000..b814ff70f4 --- /dev/null +++ b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/delete/deleteDialog.js @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const { MessageFactory } = require('botbuilder'); +const { ComponentDialog, WaterfallDialog, DialogTurnStatus } = require('botbuilder-dialogs'); +const { Channels } = require('botbuilder-core'); + +const SLEEP_TIMER = 5000; +const WATERFALL_DIALOG = 'WaterfallDialog'; +const DELETE_SUPPORTED = new Set([Channels.Msteams, Channels.Slack, Channels.Telegram]); + +class DeleteDialog extends ComponentDialog { + /** + * @param {string} dialogId + */ + constructor (dialogId) { + super(dialogId); + + this.addDialog(new WaterfallDialog(WATERFALL_DIALOG, [ + this.handleDeleteDialog.bind(this) + ])); + + this.initialDialogId = WATERFALL_DIALOG; + } + + /** + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + async handleDeleteDialog (stepContext) { + const channel = stepContext.context.activity.channelId; + + if (DeleteDialog.isDeleteSupported(channel)) { + const id = await stepContext.context.sendActivity(MessageFactory.text('I will delete this message in 5 seconds')); + await DeleteDialog.sleep(SLEEP_TIMER); + await stepContext.context.deleteActivity(id.id); + } else { + await stepContext.context.sendActivity(MessageFactory.text(`Delete is not supported in the ${channel} channel.`)); + } + + return { status: DialogTurnStatus.complete }; + } + + /** + * @param {number} milliseconds + */ + static sleep (milliseconds) { + return new Promise(resolve => { + setTimeout(resolve, milliseconds); + }); + } + + /** + * @param {string} channel + */ + static isDeleteSupported (channel) { + return DELETE_SUPPORTED.has(channel); + } +} + +module.exports.DeleteDialog = DeleteDialog; diff --git a/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/fileUpload/fileUploadDialog.js b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/fileUpload/fileUploadDialog.js new file mode 100644 index 0000000000..5cc8d38c48 --- /dev/null +++ b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/fileUpload/fileUploadDialog.js @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const { MessageFactory, InputHints } = require('botbuilder'); +const { ComponentDialog, AttachmentPrompt, WaterfallDialog, DialogTurnStatus, ConfirmPrompt } = require('botbuilder-dialogs'); +const fs = require('fs'); +const fetch = require('node-fetch'); +const os = require('os'); +const path = require('path'); +const stream = require('stream'); +const util = require('util'); + +const streamPipeline = util.promisify(stream.pipeline); + +const ATTACHMENT_PROMPT = 'AttachmentPrompt'; +const CONFIRM_PROMPT = 'ConfirmPrompt'; +const WATERFALL_DIALOG = 'WaterfallDialog'; + +class FileUploadDialog extends ComponentDialog { + /** + * @param {string} dialogId + */ + constructor (dialogId) { + super(dialogId); + + this.addDialog(new AttachmentPrompt(ATTACHMENT_PROMPT)) + .addDialog(new ConfirmPrompt(CONFIRM_PROMPT)) + .addDialog(new WaterfallDialog(WATERFALL_DIALOG, [ + this.promptUploadStep.bind(this), + this.handleAttachmentStep.bind(this), + this.finalStep.bind(this) + ])); + + this.initialDialogId = WATERFALL_DIALOG; + } + + /** + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + async promptUploadStep (stepContext) { + return stepContext.prompt(ATTACHMENT_PROMPT, { + prompt: MessageFactory.text('Please upload a file to continue.'), + retryPrompt: MessageFactory.text('You must upload a file.') + }); + } + + /** + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + async handleAttachmentStep (stepContext) { + let fileText = ''; + let fileContent = ''; + + for (const file of stepContext.context.activity.attachments) { + const localFileName = path.resolve(os.tmpdir(), file.name); + const tempFile = fs.createWriteStream(localFileName); + const response = await fetch(file.contentUrl); + await streamPipeline(response.body, tempFile); + + fileContent = fs.readFileSync(localFileName, 'utf8'); + fileText += `Attachment "${file.name}" has been received.\r\n`; + fileText += `File content: ${fileContent}\r\n`; + } + + await stepContext.context.sendActivity(MessageFactory.text(fileText)); + + // Ask to upload another file or end. + const messageText = 'Do you want to upload another file?'; + const repromptMessageText = "That's an invalid choice."; + + return stepContext.prompt(CONFIRM_PROMPT, { + prompt: MessageFactory.text(messageText, messageText, InputHints.ExpectingInput), + retryPrompt: MessageFactory.text(repromptMessageText, repromptMessageText, InputHints.ExpectingInput) + }); + } + + /** + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + async finalStep (stepContext) { + const tryAnother = stepContext.result; + + if (tryAnother) { + return stepContext.replaceDialog(this.initialDialogId); + } + + return { status: DialogTurnStatus.complete }; + } +} + +module.exports.FileUploadDialog = FileUploadDialog; diff --git a/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/messageWithAttachment/messageWithAttachmentDialog.js b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/messageWithAttachment/messageWithAttachmentDialog.js new file mode 100644 index 0000000000..5d242b7d92 --- /dev/null +++ b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/messageWithAttachment/messageWithAttachmentDialog.js @@ -0,0 +1,124 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const { MessageFactory, InputHints } = require('botbuilder'); +const { ComponentDialog, ChoicePrompt, WaterfallDialog, ChoiceFactory, DialogTurnStatus, ConfirmPrompt } = require('botbuilder-dialogs'); +const fs = require('fs'); +const path = require('path'); + +const ATTACHMENT_TYPE_PROMPT = 'AttachmentTypePrompt'; +const CONFIRM_PROMPT = 'ConfirmPrompt'; +const WATERFALL_DIALOG = 'WaterfallDialog'; + +class MessageWithAttachmentDialog extends ComponentDialog { + /** + * @param {string} dialogId + * @param {string} serverUrl + */ + constructor (dialogId, serverUrl) { + super(dialogId); + + this.picture = 'architecture-resize.png'; + this.serverUrl = serverUrl; + + this.addDialog(new ChoicePrompt(ATTACHMENT_TYPE_PROMPT)) + .addDialog(new ConfirmPrompt(CONFIRM_PROMPT)) + .addDialog(new WaterfallDialog(WATERFALL_DIALOG, [ + this.selectAttachmentType.bind(this), + this.sendActivityWithAttachment.bind(this), + this.finalStep.bind(this) + ])); + + this.initialDialogId = WATERFALL_DIALOG; + } + + /** + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + async selectAttachmentType (stepContext) { + const messageText = 'What attachment type do you want?'; + const repromptMessageText = 'That was not a valid choice, please select a valid card type.'; + const options = { + prompt: MessageFactory.text(messageText, messageText, InputHints.ExpectingInput), + retryPrompt: MessageFactory.text(repromptMessageText, repromptMessageText, InputHints.ExpectingInput), + choices: ChoiceFactory.toChoices(['Inline', 'Internet']) + }; + + return stepContext.prompt(ATTACHMENT_TYPE_PROMPT, options); + } + + /** + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + async sendActivityWithAttachment (stepContext) { + const attachmentType = stepContext.result.value.toLowerCase(); + const reply = MessageFactory.text('', '', InputHints.IgnoringInput); + + switch (attachmentType) { + case 'inline': + reply.text = 'This is an inline attachment.'; + reply.attachments = [this.getInlineAttachment()]; + break; + + case 'internet': + reply.text = 'This is an attachment from a HTTP URL.'; + reply.attachments = [this.getInternetAttachment()]; + break; + + default: + throw new Error(`Invalid card type ${attachmentType}`); + } + + await stepContext.context.sendActivity(reply); + + // Ask to submit another or end. + const messageText = 'Do you want another type of attachment?'; + const repromptMessageText = "That's an invalid choice."; + + return stepContext.prompt(CONFIRM_PROMPT, { + prompt: MessageFactory.text(messageText, messageText, InputHints.ExpectingInput), + retryPrompt: MessageFactory.text(repromptMessageText, repromptMessageText, InputHints.ExpectingInput) + }); + } + + /** + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + async finalStep (stepContext) { + const tryAnother = stepContext.result; + + if (tryAnother) { + return stepContext.replaceDialog(this.initialDialogId); + } + + return { status: DialogTurnStatus.complete }; + } + + /** + * Returns an inline attachment. + * @returns {import('botbuilder').Attachment} + */ + getInlineAttachment () { + const filepath = path.resolve(process.cwd(), 'images', this.picture); + const file = fs.readFileSync(filepath, 'base64'); + return { + name: `Files/${this.picture}`, + contentType: 'image/png', + contentUrl: `data:image/png;base64,${file}` + }; + } + + /** + * Returns an attachment to be sent to the user from a HTTPS URL. + * @returns {import('botbuilder').Attachment} + */ + getInternetAttachment () { + return { + name: `Files/${this.picture}`, + contentType: 'image/png', + contentUrl: `${this.serverUrl}/images/architecture-resize.png` + }; + } +} + +module.exports.MessageWithAttachmentDialog = MessageWithAttachmentDialog; diff --git a/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/proactive/continuationParameters.js b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/proactive/continuationParameters.js new file mode 100644 index 0000000000..12b131ef36 --- /dev/null +++ b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/proactive/continuationParameters.js @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +/** + * Stores the information needed to resume a conversation when a proactive message arrives. + */ +class ContinuationParameters { + /** + * @param {string} claimsIdentity + * @param {string} oAuthScope + * @param {import('botbuilder').ConversationReference} conversationReference + */ + constructor (claimsIdentity, oAuthScope, conversationReference) { + this.claimsIdentity = claimsIdentity; + this.oAuthScope = oAuthScope; + this.conversationReference = conversationReference; + } +} + +module.exports.ContinuationParameters = ContinuationParameters; diff --git a/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/proactive/waitForProactiveDialog.js b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/proactive/waitForProactiveDialog.js new file mode 100644 index 0000000000..623bcd0db6 --- /dev/null +++ b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/proactive/waitForProactiveDialog.js @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const { TurnContext, MessageFactory, ActivityTypes, ActivityEventNames, EndOfConversationCodes } = require('botbuilder'); +const { Dialog, DialogTurnStatus } = require('botbuilder-dialogs'); + +class WaitForProactiveDialog extends Dialog { + /** + * @param {string} dialogId + * @param {string} serverUrl + * @param {Object} continuationParametersStore + */ + constructor (dialogId, serverUrl, continuationParametersStore) { + super(dialogId); + this.serverUrl = serverUrl; + this.continuationParametersStore = continuationParametersStore; + } + + /** + * Message to send to users when the bot receives a Conversation Update event + * @param {string} url + * @param {string} id + */ + notifyMessage (url, id) { + return `Navigate to ${url}/api/notify?user=${id} to proactively message the user.`; + } + + /** + * @param {import('botbuilder-dialogs').DialogContext} dc + */ + async beginDialog (dc) { + // Store a reference to the conversation. + this.addOrUpdateContinuationParameters(dc.context); + + // Render message with continuation link. + await dc.context.sendActivity(MessageFactory.text(this.notifyMessage(this.serverUrl, dc.context.activity.from.id))); + return Dialog.EndOfTurn; + } + + /** + * @param {import('botbuilder-dialogs').DialogContext} dc + */ + async continueDialog (dc) { + const { activity } = dc.context; + if (activity.type === ActivityTypes.Event && activity.name === ActivityEventNames.ContinueConversation) { + // We continued the conversation, forget the proactive reference. + this.continuationParametersStore[activity.from.id] = undefined; + + // The continue conversation activity comes from the ProactiveController when the notification is received + await dc.context.sendActivity('We received a proactive message, ending the dialog'); + + // End the dialog so the host gets an EoC + await dc.context.sendActivity({ + type: ActivityTypes.EndOfConversation, + code: EndOfConversationCodes.CompletedSuccessfully + }); + return { status: DialogTurnStatus.complete }; + } + + // Keep waiting for a call to the ProactiveController. + await dc.context.sendActivity(`We are waiting for a proactive message. ${this.notifyMessage(this.serverUrl, activity.from.id)}`); + + return Dialog.EndOfTurn; + } + + /** + * Helper to extract and store parameters we need to continue a conversation from a proactive message. + * @param {import('botbuilder').TurnContext} turnContext + */ + addOrUpdateContinuationParameters (turnContext) { + this.continuationParametersStore[turnContext.activity.from.id] = { + claimsIdentity: turnContext.turnState.get(turnContext.adapter.BotIdentityKey), + conversationReference: TurnContext.getConversationReference(turnContext.activity), + oAuthScope: turnContext.turnState.get(turnContext.adapter.OAuthScopeKey) + }; + } +} + +module.exports.WaitForProactiveDialog = WaitForProactiveDialog; diff --git a/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/sso/ssoSkillDialog.js b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/sso/ssoSkillDialog.js new file mode 100644 index 0000000000..86a357e20a --- /dev/null +++ b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/sso/ssoSkillDialog.js @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const { MessageFactory, InputHints } = require('botbuilder'); +const { ComponentDialog, ChoicePrompt, ChoiceFactory, DialogTurnStatus, WaterfallDialog } = require('botbuilder-dialogs'); +const { SsoSkillSignInDialog } = require('./SsoSkillSignInDialog'); + +const ACTION_PROMPT = 'ActionStepPrompt'; +const WATERFALL_DIALOG = 'WaterfallDialog'; +const SSO_SKILL_DIALOG = 'SsoSkillDialog'; + +class SsoSkillDialog extends ComponentDialog { + /** + * @param {string} dialogId + * @param {Object} configuration + */ + constructor (dialogId, configuration) { + super(dialogId); + + this.connectionName = configuration.SsoConnectionName; + + this.addDialog(new SsoSkillSignInDialog(SSO_SKILL_DIALOG, this.connectionName)) + .addDialog(new ChoicePrompt(ACTION_PROMPT)) + .addDialog(new WaterfallDialog(WATERFALL_DIALOG, [ + this.promptActionStep.bind(this), + this.handleActionStep.bind(this), + this.promptFinalStep.bind(this) + ])); + + this.initialDialogId = WATERFALL_DIALOG; + } + + /** + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + async promptActionStep (stepContext) { + const messageText = 'What SSO action would you like to perform on the skill?'; + const repromptMessageText = 'That was not a valid choice, please select a valid choice.'; + + return stepContext.prompt(ACTION_PROMPT, { + prompt: MessageFactory.text(messageText, messageText, InputHints.ExpectingInput), + retryPrompt: MessageFactory.text(repromptMessageText, repromptMessageText, InputHints.ExpectingInput), + choices: ChoiceFactory.toChoices(await this.getPromptChoices(stepContext)) + }); + } + + /** + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + async getPromptChoices (stepContext) { + const choices = []; + const token = await stepContext.context.adapter.getUserToken(stepContext.context, this.connectionName); + + if (!token) { + choices.push('Login'); + } else { + choices.push('Logout'); + choices.push('Show token'); + } + + choices.push('End'); + + return choices; + } + + /** + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + async handleActionStep (stepContext) { + const action = stepContext.result.value.toLowerCase(); + + switch (action) { + case 'login': + return stepContext.beginDialog(SSO_SKILL_DIALOG); + + case 'logout': + await stepContext.context.adapter.signOutUser(stepContext.context, this.connectionName); + await stepContext.context.sendActivity('You have been signed out.'); + return stepContext.next(); + + case 'show token': { + const token = await stepContext.context.adapter.getUserToken(stepContext.context, this.connectionName); + + if (!token) { + await stepContext.context.sendActivity('User has no cached token.'); + } else { + await stepContext.context.sendActivity(`Here is your current SSO token: ${token.token}`); + } + + return stepContext.next(); + } + + case 'end': + return { status: DialogTurnStatus.complete }; + + default: + // This should never be hit since the previous prompt validates the choice. + throw new Error(`Unrecognized action: ${action}`); + } + } + + /** + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + async promptFinalStep (stepContext) { + // Restart the dialog (we will exit when the user says end). + return stepContext.replaceDialog(this.initialDialogId); + } +} + +module.exports.SsoSkillDialog = SsoSkillDialog; diff --git a/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/sso/ssoSkillSignInDialog.js b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/sso/ssoSkillSignInDialog.js new file mode 100644 index 0000000000..f6312ec17f --- /dev/null +++ b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/sso/ssoSkillSignInDialog.js @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const { ComponentDialog, OAuthPrompt, WaterfallDialog } = require('botbuilder-dialogs'); + +const OAUTH_PROMPT = 'OAuthPrompt'; +const WATERFALL_DIALOG = 'WaterfallDialog'; + +class SsoSkillSignInDialog extends ComponentDialog { + /** + * @param {string} dialogId + * @param {string} connectionName + */ + constructor (dialogId, connectionName) { + super(dialogId); + + this.addDialog(new OAuthPrompt(OAUTH_PROMPT, { + connectionName: connectionName, + text: 'Sign in to the Skill using AAD', + title: 'Sign In' + })); + + this.addDialog(new WaterfallDialog(WATERFALL_DIALOG, [ + this.signInStep.bind(this), + this.displayToken.bind(this) + ])); + + this.initialDialogId = WATERFALL_DIALOG; + } + + /** + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + async signInStep (stepContext) { + return stepContext.beginDialog(OAUTH_PROMPT); + } + + /** + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + async displayToken (stepContext) { + const { result } = stepContext; + if (!result || !result.token) { + await stepContext.context.sendActivity('No token was provided for the skill.'); + } else { + await stepContext.context.sendActivity(`Here is your token for the skill: ${result.token}`); + } + + return stepContext.endDialog(); + } +} + +module.exports.SsoSkillSignInDialog = SsoSkillSignInDialog; diff --git a/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/update/updateDialog.js b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/update/updateDialog.js new file mode 100644 index 0000000000..fc999bf644 --- /dev/null +++ b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/dialogs/update/updateDialog.js @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const { MessageFactory } = require('botbuilder'); +const { ConfirmPrompt, ComponentDialog, WaterfallDialog, DialogTurnStatus } = require('botbuilder-dialogs'); +const { Channels } = require('botbuilder-core'); + +const WATERFALL_DIALOG = 'WaterfallDialog'; +const CONFIRM_PROMPT = 'ConfirmPrompt'; +const UPDATE_SUPPORTED = new Set([Channels.Msteams, Channels.Slack, Channels.Telegram]); + +class UpdateDialog extends ComponentDialog { + /** + * @param {string} dialogId + */ + constructor (dialogId) { + super(dialogId); + + this.updateTracker = {}; + + this.addDialog(new ConfirmPrompt(CONFIRM_PROMPT)); + this.addDialog(new WaterfallDialog(WATERFALL_DIALOG, [ + this.handleUpdateDialog.bind(this), + this.finalStepAsync.bind(this) + ])); + + this.initialDialogId = WATERFALL_DIALOG; + } + + /** + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + async handleUpdateDialog (stepContext) { + const channel = stepContext.context.activity.channelId; + + if (UpdateDialog.isUpdateSupported(channel)) { + const conversationId = stepContext.context.activity.conversation.id; + + if (conversationId in this.updateTracker) { + const tuple = this.updateTracker[conversationId]; + const activity = MessageFactory.text(`This message has been updated ${tuple[1]} time(s).`); + activity.id = tuple[0]; + tuple[1]++; + this.updateTracker[conversationId] = tuple; + await stepContext.context.updateActivity(activity); + } else { + const id = await stepContext.context.sendActivity(MessageFactory.text('Here is the original activity')); + this.updateTracker[conversationId] = [id.id, 1]; + } + } else { + await stepContext.context.sendActivity(MessageFactory.text(`Update is not supported in the ${channel} channel.`)); + return { status: DialogTurnStatus.complete }; + } + + // Ask if we want to update the activity again. + const messageText = 'Do you want to update the activity again?'; + const repromptMessageText = 'Please select a valid answer'; + const options = { + prompt: MessageFactory.text(messageText, messageText), + retryPrompt: MessageFactory.text(repromptMessageText, repromptMessageText) + }; + + // Ask the user to enter a card choice. + return stepContext.prompt(CONFIRM_PROMPT, options); + } + + /** + * @param {import('botbuilder-dialogs').WaterfallStepContext} stepContext + */ + async finalStepAsync (stepContext) { + const tryAnother = stepContext.result; + + if (tryAnother) { + return stepContext.replaceDialog(this.initialDialogId); + } + + this.updateTracker = {}; + return { status: DialogTurnStatus.complete }; + } + + /** + * @param {string} channel + */ + static isUpdateSupported (channel) { + return UPDATE_SUPPORTED.has(channel); + } +} + +module.exports.UpdateDialog = UpdateDialog; diff --git a/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/images/architecture-resize.png b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/images/architecture-resize.png new file mode 100644 index 0000000000..e65f0f7332 Binary files /dev/null and b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/images/architecture-resize.png differ diff --git a/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/index.js b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/index.js new file mode 100644 index 0000000000..8309e60139 --- /dev/null +++ b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/index.js @@ -0,0 +1,216 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const dotenv = require('dotenv'); +const http = require('http'); +const https = require('https'); +const path = require('path'); +const restify = require('restify'); + +// Import required bot configuration. +const ENV_FILE = path.join(__dirname, '.env'); +dotenv.config({ path: ENV_FILE }); + +// Import required bot services. +// See https://aka.ms/bot-services to learn more about the different parts of a bot. +const { ActivityTypes, BotFrameworkAdapter, InputHints, MemoryStorage, ConversationState, SkillHttpClient, SkillHandler, ChannelServiceRoutes, TurnContext, MessageFactory, SkillConversationIdFactory } = require('botbuilder'); +const { AuthenticationConfiguration, SimpleCredentialProvider } = require('botframework-connector'); + +const { SkillBot } = require('./bots/skillBot'); +const { ActivityRouterDialog } = require('./dialogs/activityRouterDialog'); +const { allowedCallersClaimsValidator } = require('./authentication/allowedCallersClaimsValidator'); +const { SsoSaveStateMiddleware } = require('./middleware/ssoSaveStateMiddleware'); + +// Create HTTP server +const server = restify.createServer({ maxParamLength: 1000 }); +server.use(restify.plugins.queryParser()); + +server.listen(process.env.port || process.env.PORT || 36420, () => { + console.log(`\n${server.name} listening to ${server.url}`); + console.log('\nGet Bot Framework Emulator: https://aka.ms/botframework-emulator'); + console.log('\nTo talk to your bot, open the emulator select "Open Bot"'); +}); + +const maxTotalSockets = (preallocatedSnatPorts, procCount = 1, weight = 0.5, overcommit = 1.1) => + Math.min( + Math.floor((preallocatedSnatPorts / procCount) * weight * overcommit), + preallocatedSnatPorts + ); + +const authConfig = new AuthenticationConfiguration([], allowedCallersClaimsValidator); + +// Create adapter. +// See https://aka.ms/about-bot-adapter to learn more about how bots work. +const adapter = new BotFrameworkAdapter({ + appId: process.env.MicrosoftAppId, + appPassword: process.env.MicrosoftAppPassword, + authConfig: authConfig, + clientOptions: { + agentSettings: { + http: new http.Agent({ + keepAlive: true, + maxTotalSockets: maxTotalSockets(1024, 4, 0.3) + }), + https: new https.Agent({ + keepAlive: true, + maxTotalSockets: maxTotalSockets(1024, 4, 0.7) + }) + } + } +}); + +// Catch-all for errors. +adapter.onTurnError = async (context, error) => { + // This check writes out errors to console log .vs. app insights. + // NOTE: In production environment, you should consider logging this to Azure application insights. + console.error(`\n [onTurnError] unhandled error: ${error}`); + + try { + const { message, stack } = error; + + // Send a message to the user. + let errorMessageText = 'The skill encountered an error or bug.'; + let errorMessage = MessageFactory.text(`${errorMessageText}\r\n${message}\r\n${stack}`, errorMessageText, InputHints.IgnoringInput); + errorMessage.value = { message, stack }; + await context.sendActivity(errorMessage); + + errorMessageText = 'To continue to run this bot, please fix the bot source code.'; + errorMessage = MessageFactory.text(errorMessageText, errorMessageText, InputHints.ExpectingInput); + await context.sendActivity(errorMessage); + + // Send a trace activity, which will be displayed in Bot Framework Emulator + await context.sendTraceActivity( + 'OnTurnError Trace', + `${error}`, + 'https://www.botframework.com/schemas/error', + 'TurnError' + ); + + // Send and EndOfConversation activity to the skill caller with the error to end the conversation + // and let the caller decide what to do. + await context.sendActivity({ + type: ActivityTypes.EndOfConversation, + code: 'SkillError', + text: error + }); + } catch (err) { + console.error(`\n [onTurnError] Exception caught in onTurnError : ${err}`); + } +}; + +const continuationParametersStore = {}; + +// Define the state store for your bot. +// See https://aka.ms/about-bot-state to learn more about using MemoryStorage. +// A bot requires a state storage system to persist the dialog and user state between messages. +const memoryStorage = new MemoryStorage(); + +// Create conversation and user state with in-memory storage provider. +const conversationState = new ConversationState(memoryStorage); + +adapter.use(new SsoSaveStateMiddleware(conversationState)); + +// Create the conversationIdFactory +const conversationIdFactory = new SkillConversationIdFactory(memoryStorage); + +// Create the credential provider; +const credentialProvider = new SimpleCredentialProvider(process.env.MicrosoftAppId, process.env.MicrosoftAppPassword); + +// Create the skill client +const skillClient = new SkillHttpClient(credentialProvider, conversationIdFactory); + +// Create the main dialog. +const dialog = new ActivityRouterDialog(server.url, conversationState, conversationIdFactory, skillClient, continuationParametersStore); + +// Create the bot that will handle incoming messages. +const bot = new SkillBot(conversationState, dialog, server.url); + +// Expose the manifest +server.get('/manifests/*', restify.plugins.serveStatic({ directory: './manifests', appendRequestPath: false })); + +// Expose images +server.get('/images/*', restify.plugins.serveStatic({ directory: './images', appendRequestPath: false })); + +// Listen for incoming requests. +server.post('/api/messages', (req, res) => { + adapter.processActivity(req, res, async (context) => { + // Route to main dialog. + await bot.run(context); + }); +}); + +// Create and initialize the skill classes. + +// Workaround for communicating back to the Host without throwing Unauthorized error due to the creation of a new Connector Client in the Adapter when the continueConvesation happens. + +// Uncomment this when resolved. +// const handler = new SkillHandler(adapter, bot, conversationIdFactory, credentialProvider, authConfig); +// const skillEndpoint = new ChannelServiceRoutes(handler); +// skillEndpoint.register(server, '/api/skills'); + +// Remove this when resolved +const handler = new SkillHandler(adapter, bot, conversationIdFactory, credentialProvider, authConfig); +server.post('/api/skills/v3/conversations/:conversationId/activities/:activityId', async (req, res) => { + try { + const authHeader = req.headers.authorization || req.headers.Authorization || ''; + const activity = await ChannelServiceRoutes.readActivity(req); + const ref = await handler.conversationIdFactory.getSkillConversationReference(req.params.conversationId); + const claimsIdentity = await handler.authenticate(authHeader); + + const response = await new Promise(resolve => { + return adapter.continueConversation(ref.conversationReference, ref.oAuthScope, async (context) => { + context.turnState.set(adapter.BotIdentityKey, claimsIdentity); + context.turnState.set(adapter.SkillConversationReferenceKey, ref); + + const newActivity = TurnContext.applyConversationReference(activity, ref.conversationReference); + + if (newActivity.type === ActivityTypes.EndOfConversation) { + await handler.conversationIdFactory.deleteConversationReference(req.params.conversationId); + SkillHandler.applyEoCToTurnContextActivity(context, newActivity); + resolve(await bot.run(context)); + } + + resolve(await context.sendActivity(newActivity)); + }); + }); + + res.status(200); + res.send(response); + res.end(); + } catch (error) { + ChannelServiceRoutes.handleError(error, res); + } +}); + +// Listen for incoming requests. +server.get('/api/music', restify.plugins.serveStatic({ directory: 'dialogs/cards/files', file: 'music.mp3' })); + +// Listen for incoming notifications and send proactive messages to users. +server.get('/api/notify', async (req, res) => { + let error; + const { user } = req.query; + + const continuationParameters = continuationParametersStore[user]; + + if (!continuationParameters) { + res.setHeader('Content-Type', 'text/html'); + res.writeHead(200); + res.write(`

No messages sent


There are no conversations registered to receive proactive messages for ${user}.`); + res.end(); + return; + } + + try { + adapter.continueConversation(continuationParameters.conversationReference, continuationParameters.oAuthScope, async context => { + await context.sendActivity(`Got proactive message for user: ${user}`); + await bot.run(context); + }); + } catch (err) { + error = err; + } + + res.setHeader('Content-Type', 'text/html'); + res.writeHead(200); + res.write(`

Proactive messages have been sent


Timestamp: ${new Date().toISOString()}
Exception: ${error || ''}`); + res.end(); +}); diff --git a/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/manifests/waterfallskillbot-manifest-1.0.json b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/manifests/waterfallskillbot-manifest-1.0.json new file mode 100644 index 0000000000..0e2c4109f1 --- /dev/null +++ b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/manifests/waterfallskillbot-manifest-1.0.json @@ -0,0 +1,105 @@ +{ + "$schema": "https://schemas.botframework.com/schemas/skills/v2.1/skill-manifest.json", + "$id": "WaterfallSkillBotJS", + "name": "WaterfallSkillBotJS", + "version": "1.0", + "description": "This is a skill definition for multiple activity types (implemented using waterfall dialogs).", + "publisherName": "Microsoft", + "privacyUrl": "https://microsoft.com/privacy", + "copyright": "Copyright (c) Microsoft Corporation. All rights reserved.", + "license": "https://github.com/microsoft/BotFramework-FunctionalTests/blob/main/LICENSE", + "tags": [ + "travel", + "weather", + "luis" + ], + "endpoints": [ + { + "name": "default", + "protocol": "BotFrameworkV3", + "description": "Localhost endpoint for the skill (on port 35420)", + "endpointUrl": "http://localhost:35420/api/messages", + "msAppId": "00000000-0000-0000-0000-000000000000" + } + ], + "activities": { + "bookFlight": { + "description": "Books a flight (multi turn).", + "type": "event", + "name": "BookFlight", + "value": { + "$ref": "#/definitions/bookingInfo" + }, + "resultValue": { + "$ref": "#/definitions/bookingInfo" + } + }, + "getWeather": { + "description": "Retrieves and returns the weather for the user's location.", + "type": "event", + "name": "GetWeather", + "value": { + "$ref": "#/definitions/location" + }, + "resultValue": { + "$ref": "#/definitions/weatherReport" + } + }, + "passthroughMessage": { + "type": "message", + "description": "Receives the user's utterance and attempts to resolve it using the skill's LUIS models.", + "value": { + "type": "object" + } + } + }, + "definitions": { + "bookingInfo": { + "type": "object", + "required": [ + "origin" + ], + "properties": { + "origin": { + "type": "string", + "description": "This is the origin city for the flight." + }, + "destination": { + "type": "string", + "description": "This is the destination city for the flight." + }, + "travelDate": { + "type": "string", + "description": "The date for the flight in YYYY-MM-DD format." + } + } + }, + "weatherReport": { + "type": "array", + "description": "Array of forecasts for the next week.", + "items": [ + { + "type": "string" + } + ] + }, + "location": { + "type": "object", + "description": "Location metadata.", + "properties": { + "latitude": { + "type": "number", + "title": "Latitude" + }, + "longitude": { + "type": "number", + "title": "Longitude" + }, + "postalCode": { + "type": "string", + "title": "Postal code" + } + } + } + } +} diff --git a/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/middleware/ssoSaveStateMiddleware.js b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/middleware/ssoSaveStateMiddleware.js new file mode 100644 index 0000000000..e0c1806c4b --- /dev/null +++ b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/middleware/ssoSaveStateMiddleware.js @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const { CardFactory } = require('botbuilder'); + +class SsoSaveStateMiddleware { + /** + * @param {import('botbuilder').ConversationState} conversationState + */ + constructor (conversationState) { + this.conversationState = conversationState; + } + + /** + * @param {import('botbuilder').TurnContext} turnContext + */ + async onTurn (turnContext, next) { + // Register outgoing handler. + turnContext.onSendActivities(this.outgoingHandler.bind(this)); + + // Continue processing messages. + await next(); + } + + /** + * @param {import('botbuilder').TurnContext} turnContext + * @param {Partial[]} activities + * @param {Function} next + */ + async outgoingHandler (turnContext, activities, next) { + for (const activity of activities) { + if (!!activity.attachments && activity.attachments.some(attachment => attachment.contentType === CardFactory.contentTypes.oauthCard)) { + this.conversationState.saveChanges(turnContext); + } + } + + return next(); + } +} + +module.exports.SsoSaveStateMiddleware = SsoSaveStateMiddleware; diff --git a/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/package.json b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/package.json new file mode 100644 index 0000000000..f7990840f7 --- /dev/null +++ b/tests/functional/Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot/package.json @@ -0,0 +1,27 @@ +{ + "name": "waterfall-skill-bot", + "version": "1.0.0", + "description": "A simple waterfall skill bot for testing skills", + "author": "Microsoft", + "license": "MIT", + "main": "index.js", + "scripts": { + "start": "node ./index.js", + "watch": "nodemon ./index.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://github.com" + }, + "dependencies": { + "botbuilder": "~4.13.3", + "botbuilder-dialogs": "~4.13.3", + "dotenv": "^8.2.0", + "node-fetch": "^2.6.1", + "restify": "~8.5.1" + }, + "devDependencies": { + "nodemon": "~2.0.4" + } +} diff --git a/tests/functional/Bots/Python/.gitignore b/tests/functional/Bots/Python/.gitignore new file mode 100644 index 0000000000..01e8a341d5 --- /dev/null +++ b/tests/functional/Bots/Python/.gitignore @@ -0,0 +1,3 @@ + +# Ignore .deployment files generated to deploy Python to linux from VSCode +**/.deployment \ No newline at end of file diff --git a/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/.env b/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/.env new file mode 100644 index 0000000000..11aabb20f4 --- /dev/null +++ b/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/.env @@ -0,0 +1,24 @@ +MicrosoftAppId= +MicrosoftAppPassword= +SkillHostEndpoint=http://localhost:37000/api/skills + +skill_EchoSkillBotComposerDotNet_appId= +skill_EchoSkillBotComposerDotNet_endpoint=http://localhost:35410/api/messages + +skill_EchoSkillBotDotNet_appId= +skill_EchoSkillBotDotNet_endpoint=http://localhost:35400/api/messages + +skill_EchoSkillBotDotNet21_appId= +skill_EchoSkillBotDotNet21_endpoint=http://localhost:35405/api/messages + +skill_EchoSkillBotDotNetV3_appId= +skill_EchoSkillBotDotNetV3_endpoint=http://localhost:35407/api/messages + +skill_EchoSkillBotJS_appId= +skill_EchoSkillBotJS_endpoint=http://localhost:36400/api/messages + +skill_EchoSkillBotJSV3_appId= +skill_EchoSkillBotJSV3_endpoint=http://localhost:36407/api/messages + +skill_EchoSkillBotPython_appId= +skill_EchoSkillBotPython_endpoint=http://localhost:37400/api/messages diff --git a/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/adapter_with_error_handler.py b/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/adapter_with_error_handler.py new file mode 100644 index 0000000000..f8271d257a --- /dev/null +++ b/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/adapter_with_error_handler.py @@ -0,0 +1,141 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +import sys +import traceback + +from botbuilder.core import ( + BotFrameworkAdapter, + BotFrameworkAdapterSettings, + ConversationState, + MessageFactory, + TurnContext, +) +from botbuilder.integration.aiohttp.skills import SkillHttpClient +from botbuilder.schema import ActivityTypes, Activity, InputHints + +from config import DefaultConfig, SkillConfiguration +from bots.host_bot import ACTIVE_SKILL_PROPERTY_NAME + + +class AdapterWithErrorHandler(BotFrameworkAdapter): + def __init__( + self, + settings: BotFrameworkAdapterSettings, + config: DefaultConfig, + conversation_state: ConversationState, + skill_client: SkillHttpClient = None, + skill_config: SkillConfiguration = None, + ): + super().__init__(settings) + self._config = config + + if not conversation_state: + raise TypeError( + "AdapterWithErrorHandler: `conversation_state` argument cannot be None." + ) + self._conversation_state = conversation_state + self._skill_client = skill_client + self._skill_config = skill_config + + self.on_turn_error = self._handle_turn_error + + async def _handle_turn_error(self, turn_context: TurnContext, error: Exception): + # This check writes out errors to console log + # NOTE: In production environment, you should consider logging this to Azure + # application insights. + print(f"\n [on_turn_error] unhandled error: {error}", file=sys.stderr) + traceback.print_exc() + await self._send_error_message(turn_context, error) + await self._end_skill_conversation(turn_context, error) + await self._clear_conversation_state(turn_context) + + async def _send_error_message(self, turn_context: TurnContext, error: Exception): + if not self._skill_client or not self._skill_config: + return + try: + exc_info = sys.exc_info() + stack = traceback.format_exception(*exc_info) + + # Send a message to the user. + error_message_text = "The bot encountered an error or bug." + error_message = MessageFactory.text( + error_message_text, error_message_text, InputHints.ignoring_input + ) + error_message.value = {"message": error, "stack": stack} + await turn_context.send_activity(error_message) + + await turn_context.send_activity(f"Exception: {error}") + await turn_context.send_activity(traceback.format_exc()) + + error_message_text = ( + "To continue to run this bot, please fix the bot source code." + ) + error_message = MessageFactory.text( + error_message_text, error_message_text, InputHints.ignoring_input + ) + await turn_context.send_activity(error_message) + + # Send a trace activity, which will be displayed in Bot Framework Emulator. + await turn_context.send_trace_activity( + label="TurnError", + name="on_turn_error Trace", + value=f"{error}", + value_type="https://www.botframework.com/schemas/error", + ) + except Exception as exception: + print( + f"\n Exception caught on _send_error_message : {exception}", + file=sys.stderr, + ) + traceback.print_exc() + + async def _end_skill_conversation( + self, turn_context: TurnContext, error: Exception + ): + if not self._skill_client or not self._skill_config: + return + + try: + # Inform the active skill that the conversation is ended so that it has a chance to clean up. + # Note: the root bot manages the ActiveSkillPropertyName, which has a value while the root bot + # has an active conversation with a skill. + active_skill = await self._conversation_state.create_property( + ACTIVE_SKILL_PROPERTY_NAME + ).get(turn_context) + + if active_skill: + bot_id = self._config.APP_ID + end_of_conversation = Activity(type=ActivityTypes.end_of_conversation) + end_of_conversation.code = "RootSkillError" + TurnContext.apply_conversation_reference( + end_of_conversation, + TurnContext.get_conversation_reference(turn_context.activity), + True, + ) + + await self._conversation_state.save_changes(turn_context, True) + await self._skill_client.post_activity_to_skill( + bot_id, + active_skill, + self._skill_config.SKILL_HOST_ENDPOINT, + end_of_conversation, + ) + except Exception as exception: + print( + f"\n Exception caught on _end_skill_conversation : {exception}", + file=sys.stderr, + ) + traceback.print_exc() + + async def _clear_conversation_state(self, turn_context: TurnContext): + try: + # Delete the conversationState for the current conversation to prevent the + # bot from getting stuck in a error-loop caused by being in a bad state. + # ConversationState should be thought of as similar to "cookie-state" for a Web page. + await self._conversation_state.delete(turn_context) + except Exception as exception: + print( + f"\n Exception caught on _clear_conversation_state : {exception}", + file=sys.stderr, + ) + traceback.print_exc() diff --git a/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/app.py b/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/app.py new file mode 100644 index 0000000000..283492ff41 --- /dev/null +++ b/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/app.py @@ -0,0 +1,89 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from aiohttp import web +from aiohttp.web import Request, Response +from botbuilder.core import ( + BotFrameworkAdapterSettings, + ConversationState, + MemoryStorage, +) +from botbuilder.core.integration import ( + aiohttp_channel_service_routes, + aiohttp_error_middleware, +) +from botbuilder.core.skills import SkillHandler +from botbuilder.integration.aiohttp.skills import SkillHttpClient +from botbuilder.schema import Activity +from botframework.connector.auth import ( + AuthenticationConfiguration, + SimpleCredentialProvider, +) + +from dialogs import SetupDialog +from skill_conversation_id_factory import SkillConversationIdFactory +from authentication import AllowedSkillsClaimsValidator +from bots import HostBot +from config import DefaultConfig, SkillConfiguration +from adapter_with_error_handler import AdapterWithErrorHandler + +CONFIG = DefaultConfig() +SKILL_CONFIG = SkillConfiguration() + +# Whitelist skills from SKILL_CONFIG +AUTH_CONFIG = AuthenticationConfiguration( + claims_validator=AllowedSkillsClaimsValidator(CONFIG).claims_validator +) +# Create adapter. +# See https://aka.ms/about-bot-adapter to learn more about how bots work. +SETTINGS = BotFrameworkAdapterSettings( + app_id=CONFIG.APP_ID, + app_password=CONFIG.APP_PASSWORD, + auth_configuration=AUTH_CONFIG, +) + +STORAGE = MemoryStorage() +CONVERSATION_STATE = ConversationState(STORAGE) + +ID_FACTORY = SkillConversationIdFactory(STORAGE) +CREDENTIAL_PROVIDER = SimpleCredentialProvider(CONFIG.APP_ID, CONFIG.APP_PASSWORD) +CLIENT = SkillHttpClient(CREDENTIAL_PROVIDER, ID_FACTORY) + +ADAPTER = AdapterWithErrorHandler( + SETTINGS, CONFIG, CONVERSATION_STATE, CLIENT, SKILL_CONFIG +) + +# Create the Bot +DIALOG = SetupDialog(CONVERSATION_STATE, SKILL_CONFIG) +BOT = HostBot(CONVERSATION_STATE, SKILL_CONFIG, CLIENT, CONFIG, DIALOG) + +SKILL_HANDLER = SkillHandler(ADAPTER, BOT, ID_FACTORY, CREDENTIAL_PROVIDER, AUTH_CONFIG) + + +# Listen for incoming requests on /api/messages +async def messages(req: Request) -> Response: + # Main bot message handler. + if "application/json" in req.headers["Content-Type"]: + body = await req.json() + else: + return Response(status=415) + + activity = Activity().deserialize(body) + auth_header = req.headers["Authorization"] if "Authorization" in req.headers else "" + + try: + await ADAPTER.process_activity(activity, auth_header, BOT.on_turn) + return Response(status=201) + except Exception as exception: + raise exception + + +APP = web.Application(middlewares=[aiohttp_error_middleware]) +APP.router.add_post("/api/messages", messages) +APP.router.add_routes(aiohttp_channel_service_routes(SKILL_HANDLER, "/api/skills")) + +if __name__ == "__main__": + try: + web.run_app(APP, host="localhost", port=CONFIG.PORT) + except Exception as error: + raise error diff --git a/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/authentication/__init__.py b/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/authentication/__init__.py new file mode 100644 index 0000000000..550f9b54b6 --- /dev/null +++ b/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/authentication/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .allowed_skills_claims_validator import AllowedSkillsClaimsValidator + +__all__ = ["AllowedSkillsClaimsValidator"] diff --git a/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/authentication/allowed_skills_claims_validator.py b/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/authentication/allowed_skills_claims_validator.py new file mode 100644 index 0000000000..7f0d26f087 --- /dev/null +++ b/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/authentication/allowed_skills_claims_validator.py @@ -0,0 +1,44 @@ +from typing import Awaitable, Callable, Dict, List + +from botframework.connector.auth import JwtTokenValidation, SkillValidation + +from config import DefaultConfig + + +class AllowedSkillsClaimsValidator: + + config_key = "ALLOWED_CALLERS" + + def __init__(self, config: DefaultConfig): + if not config: + raise TypeError( + "AllowedSkillsClaimsValidator: config object cannot be None." + ) + + # ALLOWED_CALLERS is the setting in config.py file + # that consists of the list of parent bot ids that are allowed to access the skill + # to add a new parent bot simply go to the AllowedCallers and add + # the parent bot's microsoft app id to the list + caller_list = getattr(config, self.config_key) + if caller_list is None: + raise TypeError(f'"{self.config_key}" not found in configuration.') + self._allowed_callers = caller_list + + @property + def claims_validator(self) -> Callable[[List[Dict]], Awaitable]: + async def allow_callers_claims_validator(claims: Dict[str, object]): + # if allowed_callers is None we allow all calls + if "*" not in self._allowed_callers and SkillValidation.is_skill_claim( + claims + ): + # Check that the appId claim in the skill request is in the list of skills configured for this bot. + app_id = JwtTokenValidation.get_app_id_from_claims(claims) + if app_id not in self._allowed_callers: + raise PermissionError( + f'Received a request from a bot with an app ID of "{app_id}".' + f" To enable requests from this caller, add the app ID to your configuration file." + ) + + return + + return allow_callers_claims_validator diff --git a/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/bots/__init__.py b/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/bots/__init__.py new file mode 100644 index 0000000000..acb6f75c35 --- /dev/null +++ b/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/bots/__init__.py @@ -0,0 +1,7 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .host_bot import HostBot + + +__all__ = ["HostBot"] diff --git a/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/bots/host_bot.py b/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/bots/host_bot.py new file mode 100644 index 0000000000..ab1e383cc8 --- /dev/null +++ b/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/bots/host_bot.py @@ -0,0 +1,186 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +import copy +from typing import List +from botbuilder.core import ( + ActivityHandler, + ConversationState, + MessageFactory, + TurnContext, +) +from botbuilder.core.skills import BotFrameworkSkill +from botbuilder.dialogs import Dialog +from botbuilder.schema import ( + ActivityTypes, + ChannelAccount, + ExpectedReplies, + DeliveryModes, +) +from botbuilder.integration.aiohttp.skills import SkillHttpClient + +from config import DefaultConfig, SkillConfiguration +from helpers.dialog_helper import DialogHelper + +DELIVERY_MODE_PROPERTY_NAME = "deliveryModeProperty" +ACTIVE_SKILL_PROPERTY_NAME = "activeSkillProperty" + + +class HostBot(ActivityHandler): + def __init__( + self, + conversation_state: ConversationState, + skills_config: SkillConfiguration, + skill_client: SkillHttpClient, + config: DefaultConfig, + dialog: Dialog, + ): + self._bot_id = config.APP_ID + self._skill_client = skill_client + self._skills_config = skills_config + self._conversation_state = conversation_state + self._dialog = dialog + self._dialog_state_property = conversation_state.create_property("DialogState") + + # Create state property to track the delivery mode and active skill. + self._delivery_mode_property = conversation_state.create_property( + DELIVERY_MODE_PROPERTY_NAME + ) + self._active_skill_property = conversation_state.create_property( + ACTIVE_SKILL_PROPERTY_NAME + ) + + async def on_turn(self, turn_context): + # Forward all activities except EndOfConversation to the active skill. + if turn_context.activity.type != ActivityTypes.end_of_conversation: + # Try to get the active skill + active_skill: BotFrameworkSkill = await self._active_skill_property.get( + turn_context + ) + + if active_skill: + delivery_mode: str = await self._delivery_mode_property.get( + turn_context + ) + + # Send the activity to the skill + await self.__send_to_skill(turn_context, delivery_mode, active_skill) + return + + await super().on_turn(turn_context) + # Save any state changes that might have occurred during the turn. + await self._conversation_state.save_changes(turn_context) + + async def on_message_activity(self, turn_context: TurnContext): + if turn_context.activity.text in self._skills_config.SKILLS: + delivery_mode: str = await self._delivery_mode_property.get(turn_context) + selected_skill = self._skills_config.SKILLS[turn_context.activity.text] + v3_bots = ["EchoSkillBotDotNetV3", "EchoSkillBotJSV3"] + + if ( + selected_skill + and delivery_mode == DeliveryModes.expect_replies + and selected_skill.id.lower() in (id.lower() for id in v3_bots) + ): + message = MessageFactory.text( + "V3 Bots do not support 'expectReplies' delivery mode." + ) + await turn_context.send_activity(message) + + # Forget delivery mode and skill invocation. + await self._delivery_mode_property.delete(turn_context) + + # Restart setup dialog + await self._conversation_state.delete(turn_context) + + await DialogHelper.run_dialog( + self._dialog, + turn_context, + self._dialog_state_property, + ) + + async def on_end_of_conversation_activity(self, turn_context: TurnContext): + await self.end_conversation(turn_context.activity, turn_context) + + async def on_members_added_activity( + self, members_added: List[ChannelAccount], turn_context: TurnContext + ): + for member in members_added: + if member.id != turn_context.activity.recipient.id: + await turn_context.send_activity( + MessageFactory.text("Hello and welcome!") + ) + await DialogHelper.run_dialog( + self._dialog, + turn_context, + self._dialog_state_property, + ) + + async def end_conversation(self, activity, turn_context): + # Forget delivery mode and skill invocation. + await self._delivery_mode_property.delete(turn_context) + await self._active_skill_property.delete(turn_context) + + eoc_activity_message = ( + f"Received {ActivityTypes.end_of_conversation}.\n\nCode: {activity.code}." + ) + if activity.text: + eoc_activity_message = eoc_activity_message + f"\n\nText: {activity.text}" + if activity.value: + eoc_activity_message = eoc_activity_message + f"\n\nValue: {activity.value}" + await turn_context.send_activity(eoc_activity_message) + + # We are back + await turn_context.send_activity(MessageFactory.text("Back in the host bot.")) + + # Restart setup dialog. + await DialogHelper.run_dialog( + self._dialog, + turn_context, + self._dialog_state_property, + ) + + await self._conversation_state.save_changes(turn_context) + + async def __send_to_skill( + self, + turn_context: TurnContext, + delivery_mode: str, + target_skill: BotFrameworkSkill, + ): + # NOTE: Always SaveChanges() before calling a skill so that any activity generated by the skill + # will have access to current accurate state. + await self._conversation_state.save_changes(turn_context, force=True) + + if delivery_mode == "expectReplies": + # Clone activity and update its delivery mode. + activity = copy.copy(turn_context.activity) + activity.delivery_mode = delivery_mode + + # Route the activity to the skill. + expect_replies_response = await self._skill_client.post_activity_to_skill( + self._bot_id, + target_skill, + self._skills_config.SKILL_HOST_ENDPOINT, + activity, + ) + + # Route response activities back to the channel. + response_activities: ExpectedReplies = ( + ExpectedReplies().deserialize(expect_replies_response.body).activities + ) + + for response_activity in response_activities: + if response_activity.type == ActivityTypes.end_of_conversation: + await self.end_conversation(response_activity, turn_context) + + else: + await turn_context.send_activity(response_activity) + + else: + # Route the activity to the skill. + await self._skill_client.post_activity_to_skill( + self._bot_id, + target_skill, + self._skills_config.SKILL_HOST_ENDPOINT, + turn_context.activity, + ) diff --git a/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/config.py b/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/config.py new file mode 100644 index 0000000000..6e4da1d2a4 --- /dev/null +++ b/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/config.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import os +from typing import Dict +from botbuilder.core.skills import BotFrameworkSkill +from dotenv import load_dotenv + +""" Bot Configuration """ + + +class DefaultConfig: + """ Bot Configuration """ + + load_dotenv() + + PORT = 37000 + APP_ID = os.getenv("MicrosoftAppId") + APP_PASSWORD = os.getenv("MicrosoftAppPassword") + SKILL_HOST_ENDPOINT = os.getenv("SkillHostEndpoint") + SKILLS = [] + + # Callers to only those specified, '*' allows any caller. + # Example: os.environ.get("AllowedCallers", ["54d3bb6a-3b6d-4ccd-bbfd-cad5c72fb53a"]) + ALLOWED_CALLERS = os.environ.get("AllowedCallers", ["*"]) + + @staticmethod + def configure_skills(): + skills = list() + env_skills = [x for x in os.environ if ((x.lower().startswith("skill_")) and ('group' not in x.lower()))] + + for envKey in env_skills: + keys = envKey.split("_") + bot_id = keys[1] + key = keys[2] + index = -1 + + for i, newSkill in enumerate(skills): + if newSkill["id"] == bot_id: + index = i + + if key.lower() == "appid": + attr = "app_id" + elif key.lower() == "endpoint": + attr = "skill_endpoint" + else: + raise ValueError( + f"[SkillsConfiguration]: Invalid environment variable declaration {key}" + ) + + env_val = os.getenv(envKey) + + if index == -1: + skill = {"id": bot_id, attr: env_val} + skills.append(skill) + else: + skills[index][attr] = env_val + pass + + DefaultConfig.SKILLS = skills + + +DefaultConfig.configure_skills() + + +class SkillConfiguration: + SKILL_HOST_ENDPOINT = DefaultConfig.SKILL_HOST_ENDPOINT + SKILLS: Dict[str, BotFrameworkSkill] = { + skill["id"]: BotFrameworkSkill(**skill) for skill in DefaultConfig.SKILLS + } diff --git a/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/dialogs/__init__.py b/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/dialogs/__init__.py new file mode 100644 index 0000000000..0ea48d3bd7 --- /dev/null +++ b/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/dialogs/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .setup_dialog import SetupDialog + +__all__ = ["SetupDialog"] diff --git a/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/dialogs/setup_dialog.py b/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/dialogs/setup_dialog.py new file mode 100644 index 0000000000..fe87c91941 --- /dev/null +++ b/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/dialogs/setup_dialog.py @@ -0,0 +1,129 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from botbuilder.dialogs import ( + ComponentDialog, + WaterfallDialog, + WaterfallStepContext, + DialogTurnResult, +) +from botbuilder.dialogs.choices.list_style import ListStyle +from botbuilder.dialogs.prompts import ( + TextPrompt, + ChoicePrompt, + PromptOptions, +) +from botbuilder.dialogs.choices import Choice +from botbuilder.core import MessageFactory, ConversationState +from botbuilder.schema import InputHints, DeliveryModes + +from bots.host_bot import ACTIVE_SKILL_PROPERTY_NAME, DELIVERY_MODE_PROPERTY_NAME +from config import SkillConfiguration + + +class SetupDialog(ComponentDialog): + def __init__( + self, conversation_state: ConversationState, skills_config: SkillConfiguration + ): + super(SetupDialog, self).__init__(SetupDialog.__name__) + + self._delivery_mode_property = conversation_state.create_property( + DELIVERY_MODE_PROPERTY_NAME + ) + self._active_skill_property = conversation_state.create_property( + ACTIVE_SKILL_PROPERTY_NAME + ) + self._delivery_mode = "" + + self._skills_config = skills_config + + # Define the setup dialog and its related components. + # Add ChoicePrompt to render available skills. + self.add_dialog(ChoicePrompt(self.select_delivery_mode_step.__name__)) + self.add_dialog(ChoicePrompt(self.select_skill_step.__name__)) + self.add_dialog(TextPrompt(self.final_step.__name__)) + # Add main waterfall dialog for this bot. + self.add_dialog( + WaterfallDialog( + WaterfallDialog.__name__, + [ + self.select_delivery_mode_step, + self.select_skill_step, + self.final_step, + ], + ) + ) + self.initial_dialog_id = WaterfallDialog.__name__ + + # Render a prompt to select the delivery mode to use. + async def select_delivery_mode_step( + self, step_context: WaterfallStepContext + ) -> DialogTurnResult: + # Create the PromptOptions with the delivery modes supported. + message = "What delivery mode would you like to use?" + reprompt_message = ( + "That was not a valid choice, please select a valid delivery mode." + ) + options = PromptOptions( + prompt=MessageFactory.text(message, message, InputHints.expecting_input), + retry_prompt=MessageFactory.text( + reprompt_message, reprompt_message, InputHints.expecting_input + ), + choices=[Choice("normal"), Choice("expectReplies")], + ) + return await step_context.prompt( + self.select_delivery_mode_step.__name__, options + ) + + # Render a prompt to select the skill to call. + async def select_skill_step( + self, step_context: WaterfallStepContext + ) -> DialogTurnResult: + # Set delivery mode. + self._delivery_mode = step_context.result.value + await self._delivery_mode_property.set( + step_context.context, step_context.result.value + ) + + # Create the PromptOptions from the skill configuration which contains the list of configured skills. + message = "What skill would you like to call?" + reprompt_message = "That was not a valid choice, please select a valid skill." + options = PromptOptions( + prompt=MessageFactory.text(message, message, InputHints.expecting_input), + retry_prompt=MessageFactory.text(reprompt_message, reprompt_message), + choices=[ + Choice(value=skill.id) + for _, skill in sorted(self._skills_config.SKILLS.items()) + ], + style=ListStyle.suggested_action + ) + + return await step_context.prompt(self.select_skill_step.__name__, options) + + # The SetupDialog has ended, we go back to the HostBot to connect with the selected skill. + async def final_step(self, step_context: WaterfallStepContext) -> DialogTurnResult: + # Set active skill. + for i in self._skills_config.SKILLS.keys(): + if i.lower() == step_context.result.value.lower(): + selected_skill = self._skills_config.SKILLS.get(i) + + await self._active_skill_property.set(step_context.context, selected_skill) + + v3_bots = ['EchoSkillBotDotNetV3', 'EchoSkillBotJSV3'] + + if self._delivery_mode == DeliveryModes.expect_replies and selected_skill.id.lower() in (id.lower() for id in v3_bots): + message = MessageFactory.text("V3 Bots do not support 'expectReplies' delivery mode.") + await step_context.context.send_activity(message) + + # Forget delivery mode and skill invocation. + await self._delivery_mode_property.delete(step_context.context) + await self._active_skill_property.delete(step_context.context) + + # Restart setup dialog + return await step_context.replace_dialog(self.initial_dialog_id) + + await step_context.context.send_activity( + MessageFactory.text("Type anything to send to the skill.", "Type anything to send to the skill.", InputHints.expecting_input) + ) + + return await step_context.end_dialog() diff --git a/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/helpers/dialog_helper.py b/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/helpers/dialog_helper.py new file mode 100644 index 0000000000..6b2646b0ba --- /dev/null +++ b/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/helpers/dialog_helper.py @@ -0,0 +1,19 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from botbuilder.core import StatePropertyAccessor, TurnContext +from botbuilder.dialogs import Dialog, DialogSet, DialogTurnStatus + + +class DialogHelper: + @staticmethod + async def run_dialog( + dialog: Dialog, turn_context: TurnContext, accessor: StatePropertyAccessor + ): + dialog_set = DialogSet(accessor) + dialog_set.add(dialog) + + dialog_context = await dialog_set.create_context(turn_context) + results = await dialog_context.continue_dialog() + if results.status == DialogTurnStatus.Empty: + await dialog_context.begin_dialog(dialog.id) diff --git a/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/requirements.txt b/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/requirements.txt new file mode 100644 index 0000000000..47a60840f5 --- /dev/null +++ b/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/requirements.txt @@ -0,0 +1,3 @@ +botbuilder-integration-aiohttp>=4.13.0 +botbuilder-dialogs>=4.13.0 +python-dotenv diff --git a/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/skill_conversation_id_factory.py b/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/skill_conversation_id_factory.py new file mode 100644 index 0000000000..8b3ab86d14 --- /dev/null +++ b/tests/functional/Bots/Python/Consumers/CodeFirst/SimpleHostBot/skill_conversation_id_factory.py @@ -0,0 +1,75 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +from typing import Union + +from botbuilder.core import Storage, TurnContext +from botbuilder.core.skills import ( + ConversationIdFactoryBase, + SkillConversationIdFactoryOptions, + SkillConversationReference, +) +from botbuilder.schema import ConversationReference + + +class SkillConversationIdFactory(ConversationIdFactoryBase): + def __init__(self, storage: Storage): + if not storage: + raise TypeError("storage can't be None") + + self._storage = storage + + async def create_skill_conversation_id( + self, + options_or_conversation_reference: Union[ + SkillConversationIdFactoryOptions, ConversationReference + ], + ) -> str: + if not options_or_conversation_reference: + raise TypeError("Need options or conversation reference") + + if not isinstance( + options_or_conversation_reference, SkillConversationIdFactoryOptions + ): + raise TypeError( + "This SkillConversationIdFactory can only handle SkillConversationIdFactoryOptions" + ) + + options = options_or_conversation_reference + + # Create the storage key based on the SkillConversationIdFactoryOptions. + conversation_reference = TurnContext.get_conversation_reference( + options.activity + ) + skill_conversation_id = ( + f"{conversation_reference.conversation.id}" + f"-{options.bot_framework_skill.id}" + f"-{conversation_reference.channel_id}" + f"-skillconvo" + ) + + # Create the SkillConversationReference instance. + skill_conversation_reference = SkillConversationReference( + conversation_reference=conversation_reference, + oauth_scope=options.from_bot_oauth_scope, + ) + + # Store the SkillConversationReference using the skill_conversation_id as a key. + skill_conversation_info = {skill_conversation_id: skill_conversation_reference} + await self._storage.write(skill_conversation_info) + + # Return the generated skill_conversation_id (that will be also used as the conversation ID to call the skill). + return skill_conversation_id + + async def get_conversation_reference( + self, skill_conversation_id: str + ) -> Union[SkillConversationReference, ConversationReference]: + if not skill_conversation_id: + raise TypeError("skill_conversation_id can't be None") + + # Get the SkillConversationReference from storage for the given skill_conversation_id. + skill_conversation_info = await self._storage.read([skill_conversation_id]) + + return skill_conversation_info.get(skill_conversation_id) + + async def delete_conversation_reference(self, skill_conversation_id: str): + await self._storage.delete([skill_conversation_id]) diff --git a/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/.env b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/.env new file mode 100644 index 0000000000..382c7b2790 --- /dev/null +++ b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/.env @@ -0,0 +1,53 @@ +MicrosoftAppId= +MicrosoftAppPassword= +SkillHostEndpoint=http://localhost:37020/api/skills +SsoConnectionName= +SsoConnectionNameTeams= + +skill_EchoSkillBotDotNet_group=Echo +skill_EchoSkillBotDotNet_appId= +skill_EchoSkillBotDotNet_endpoint=http://localhost:35400/api/messages + +skill_EchoSkillBotDotNet21_group=Echo +skill_EchoSkillBotDotNet21_appId= +skill_EchoSkillBotDotNet21_endpoint=http://localhost:35405/api/messages + +skill_EchoSkillBotDotNetV3_group=Echo +skill_EchoSkillBotDotNetV3_appId= +skill_EchoSkillBotDotNetV3_endpoint=http://localhost:35407/api/messages + +skill_EchoSkillBotJS_group=Echo +skill_EchoSkillBotJS_appId= +skill_EchoSkillBotJS_endpoint=http://localhost:36400/api/messages + +skill_EchoSkillBotJSV3_group=Echo +skill_EchoSkillBotJSV3_appId= +skill_EchoSkillBotJSV3_endpoint=http://localhost:36407/api/messages + +skill_EchoSkillBotPython_group=Echo +skill_EchoSkillBotPython_appId= +skill_EchoSkillBotPython_endpoint=http://localhost:37400/api/messages + +skill_WaterfallSkillBotDotNet_group=Waterfall +skill_WaterfallSkillBotDotNet_appId= +skill_WaterfallSkillBotDotNet_endpoint=http://localhost:35420/api/messages + +skill_WaterfallSkillBotJS_group=Waterfall +skill_WaterfallSkillBotJS_appId= +skill_WaterfallSkillBotJS_endpoint=http://localhost:36420/api/messages + +skill_WaterfallSkillBotPython_group=Waterfall +skill_WaterfallSkillBotPython_appId= +skill_WaterfallSkillBotPython_endpoint=http://localhost:37420/api/messages + +skill_TeamsSkillBotDotNet_group=Teams +skill_TeamsSkillBotDotNet_appId= +skill_TeamsSkillBotDotNet_endpoint=http://localhost:35430/api/messages + +skill_TeamsSkillBotJS_group=Teams +skill_TeamsSkillBotJS_appId= +skill_TeamsSkillBotJS_endpoint=http://localhost:36430/api/messages + +skill_TeamsSkillBotPython_group=Teams +skill_TeamsSkillBotPython_appId= +skill_TeamsSkillBotPython_endpoint=http://localhost:37430/api/messages diff --git a/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/.pylintrc b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/.pylintrc new file mode 100644 index 0000000000..0eb8d1d4f6 --- /dev/null +++ b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/.pylintrc @@ -0,0 +1,590 @@ +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-whitelist= + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore= + +# Add files or directories matching the regex patterns to the blacklist. The +# regex matches against base names, not paths. +ignore-patterns= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Specify a configuration file. +#rcfile= + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=print-statement, + parameter-unpacking, + unpacking-in-except, + old-raise-syntax, + backtick, + long-suffix, + old-ne-operator, + old-octal-literal, + import-star-module-level, + non-ascii-bytes-literal, + raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead, + apply-builtin, + basestring-builtin, + buffer-builtin, + cmp-builtin, + coerce-builtin, + execfile-builtin, + file-builtin, + long-builtin, + raw_input-builtin, + reduce-builtin, + standarderror-builtin, + unicode-builtin, + xrange-builtin, + coerce-method, + delslice-method, + getslice-method, + setslice-method, + no-absolute-import, + old-division, + dict-iter-method, + dict-view-method, + next-method-called, + metaclass-assignment, + indexing-exception, + raising-string, + reload-builtin, + oct-method, + hex-method, + nonzero-method, + cmp-method, + input-builtin, + round-builtin, + intern-builtin, + unichr-builtin, + map-builtin-not-iterating, + zip-builtin-not-iterating, + range-builtin-not-iterating, + filter-builtin-not-iterating, + using-cmp-argument, + eq-without-hash, + div-method, + idiv-method, + rdiv-method, + exception-message-attribute, + invalid-str-codec, + sys-max-int, + bad-python3-import, + deprecated-string-function, + deprecated-str-translate-call, + deprecated-itertools-function, + deprecated-types-field, + next-method-defined, + dict-items-not-iterating, + dict-keys-not-iterating, + dict-values-not-iterating, + deprecated-operator-function, + deprecated-urllib-function, + xreadlines-attribute, + deprecated-sys-function, + exception-escape, + comprehension-escape, + bad-continuation, + duplicate-code, + redefined-outer-name, + missing-docstring, + too-many-instance-attributes, + too-few-public-methods, + redefined-builtin, + too-many-arguments, + no-self-use, + fixme, + broad-except, + bare-except, + too-many-public-methods, + cyclic-import, + too-many-locals, + too-many-function-args, + too-many-return-statements, + import-error, + no-name-in-module, + too-many-branches + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[REPORTS] + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +#msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit + + +[LOGGING] + +# Format style used to check logging format string. `old` means using % +# formatting, while `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: none. To make it working +# install python-enchant package.. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to indicated private dictionary in +# --spelling-private-dict-file option instead of raising a message. +spelling-store-unknown-words=no + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis. It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=120 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# List of optional constructs for which whitespace checking is disabled. `dict- +# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. +# `trailing-comma` allows a space between comma and closing bracket: (a, ). +# `empty-line` allows space-only lines. +no-space-check=trailing-comma, + dict-separator + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[SIMILARITIES] + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. +#class-attribute-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. +#variable-rgx= + + +[STRING] + +# This flag controls whether the implicit-str-concat-in-sequence should +# generate a warning on implicit string concatenation in sequences defined over +# several lines. +check-str-concat-over-line-jumps=no + + +[IMPORTS] + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules=optparse,tkinter.tix + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled). +ext-import-graph= + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled). +import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=cls + + +[DESIGN] + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement. +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "BaseException, Exception". +overgeneral-exceptions=BaseException, + Exception diff --git a/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/adapter_with_error_handler.py b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/adapter_with_error_handler.py new file mode 100644 index 0000000000..d2fc861c15 --- /dev/null +++ b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/adapter_with_error_handler.py @@ -0,0 +1,140 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import sys +import traceback + +from botbuilder.core import ( + BotFrameworkAdapter, + BotFrameworkAdapterSettings, + ConversationState, + MessageFactory, + TurnContext, +) +from botbuilder.integration.aiohttp.skills import SkillHttpClient +from botbuilder.schema import ActivityTypes, Activity, InputHints + +from skills_configuration import DefaultConfig, SkillsConfiguration +from bots.root_bot import ACTIVE_SKILL_PROPERTY_NAME + + +class AdapterWithErrorHandler(BotFrameworkAdapter): + def __init__( + self, + settings: BotFrameworkAdapterSettings, + config: DefaultConfig, + conversation_state: ConversationState, + skill_client: SkillHttpClient = None, + skill_config: SkillsConfiguration = None, + ): + super().__init__(settings) + self._config = config + + if not conversation_state: + raise TypeError( + "AdapterWithErrorHandler: `conversation_state` argument cannot be None." + ) + self._conversation_state = conversation_state + self._skill_client = skill_client + self._skill_config = skill_config + + self.on_turn_error = self._handle_turn_error + + async def _handle_turn_error(self, turn_context: TurnContext, error: Exception): + # This check writes out errors to console log + # NOTE: In production environment, you should consider logging this to Azure + # application insights. + print(f"\n [on_turn_error] unhandled error: {error}", file=sys.stderr) + traceback.print_exc() + await self._send_error_message(turn_context, error) + await self._end_skill_conversation(turn_context, error) + await self._clear_conversation_state(turn_context) + + async def _send_error_message(self, turn_context: TurnContext, error: Exception): + if not self._skill_client or not self._skill_config: + return + try: + exc_info = sys.exc_info() + stack = traceback.format_exception(*exc_info) + + # Send a message to the user. + error_message_text = "The bot encountered an error or bug." + error_message = MessageFactory.text( + error_message_text, error_message_text, InputHints.ignoring_input + ) + error_message.value = {"message": error, "stack": stack} + await turn_context.send_activity(error_message) + + await turn_context.send_activity(f"Exception: {error}") + await turn_context.send_activity(traceback.format_exc()) + + error_message_text = ( + "To continue to run this bot, please fix the bot source code." + ) + error_message = MessageFactory.text( + error_message_text, error_message_text, InputHints.ignoring_input + ) + await turn_context.send_activity(error_message) + + # Send a trace activity, which will be displayed in Bot Framework Emulator. + await turn_context.send_trace_activity( + label="TurnError", + name="on_turn_error Trace", + value=f"{error}", + value_type="https://www.botframework.com/schemas/error", + ) + except Exception as exception: + print( + f"\n Exception caught in _send_error_message : {exception}", + file=sys.stderr, + ) + traceback.print_exc() + + async def _end_skill_conversation(self, turn_context: TurnContext): + if not self._skill_client or not self._skill_config: + return + + try: + # Inform the active skill that the conversation is ended so that it has a chance to clean up. + # Note: the root bot manages the ActiveSkillPropertyName, which has a value while the root bot + # has an active conversation with a skill. + active_skill = await self._conversation_state.create_property( + ACTIVE_SKILL_PROPERTY_NAME + ).get(turn_context) + + if active_skill: + bot_id = self._config.APP_ID + end_of_conversation = Activity(type=ActivityTypes.end_of_conversation) + end_of_conversation.code = "RootSkillError" + TurnContext.apply_conversation_reference( + end_of_conversation, + TurnContext.get_conversation_reference(turn_context.activity), + True, + ) + + await self._conversation_state.save_changes(turn_context, True) + await self._skill_client.post_activity_to_skill( + bot_id, + active_skill, + self._skill_config.SKILL_HOST_ENDPOINT, + end_of_conversation, + ) + except Exception as exception: + print( + f"\n Exception caught on _end_skill_conversation : {exception}", + file=sys.stderr, + ) + traceback.print_exc() + + async def _clear_conversation_state(self, turn_context: TurnContext): + try: + # Delete the conversationState for the current conversation to prevent the + # bot from getting stuck in an error-loop caused by being in a bad state. + # ConversationState should be thought of as similar to "cookie-state" for a Web page. + await self._conversation_state.delete(turn_context) + except Exception as exception: + print( + f"\n Exception caught on _clear_conversation_state : {exception}", + file=sys.stderr, + ) + traceback.print_exc() diff --git a/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/app.py b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/app.py new file mode 100644 index 0000000000..63bdfd6db7 --- /dev/null +++ b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/app.py @@ -0,0 +1,101 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from http import HTTPStatus + +from aiohttp import web +from aiohttp.web import Request, Response +from aiohttp.web_response import json_response +from botbuilder.core import ( + BotFrameworkAdapterSettings, + ConversationState, + MemoryStorage, + UserState, +) +from botbuilder.core.integration import ( + aiohttp_channel_service_routes, + aiohttp_error_middleware, +) +from botbuilder.schema import Activity +from botbuilder.integration.aiohttp.skills import SkillHttpClient +from botframework.connector.auth import ( + AuthenticationConfiguration, + SimpleCredentialProvider, +) + +from authentication import AllowedSkillsClaimsValidator +from bots import RootBot +from dialogs import MainDialog +from skills_configuration import DefaultConfig, SkillsConfiguration +from adapter_with_error_handler import AdapterWithErrorHandler +from skill_conversation_id_factory import SkillConversationIdFactory +from token_exchange_skill_handler import TokenExchangeSkillHandler + +CONFIG = DefaultConfig() +SKILL_CONFIG = SkillsConfiguration() + +# Create MemoryStorage, UserState and ConversationState +MEMORY = MemoryStorage() +USER_STATE = UserState(MEMORY) +CONVERSATION_STATE = ConversationState(MEMORY) +ID_FACTORY = SkillConversationIdFactory(MEMORY) + +CREDENTIAL_PROVIDER = SimpleCredentialProvider(CONFIG.APP_ID, CONFIG.APP_PASSWORD) +CLIENT = SkillHttpClient(CREDENTIAL_PROVIDER, ID_FACTORY) + +# Whitelist skills from SKILLS_CONFIG +AUTH_CONFIG = AuthenticationConfiguration( + claims_validator=AllowedSkillsClaimsValidator(SKILL_CONFIG).claims_validator +) + +# Create adapter. +# See https://aka.ms/about-bot-adapter to learn more about how bots work. +SETTINGS = BotFrameworkAdapterSettings(CONFIG.APP_ID, CONFIG.APP_PASSWORD) +ADAPTER = AdapterWithErrorHandler( + SETTINGS, CONFIG, CONVERSATION_STATE, CLIENT, SKILL_CONFIG +) + +DIALOG = MainDialog(CONVERSATION_STATE, ID_FACTORY, CLIENT, SKILL_CONFIG, CONFIG) + +# Create the Bot +BOT = RootBot(CONVERSATION_STATE, DIALOG) + +SKILL_HANDLER = TokenExchangeSkillHandler( + ADAPTER, + BOT, + CONFIG, + ID_FACTORY, + SKILL_CONFIG, + CLIENT, + CREDENTIAL_PROVIDER, + AUTH_CONFIG, +) + + +# Listen for incoming requests on /api/messages +async def messages(req: Request) -> Response: + # Main bot message handler. + if "application/json" in req.headers["Content-Type"]: + body = await req.json() + else: + return Response(status=HTTPStatus.UNSUPPORTED_MEDIA_TYPE) + + activity = Activity().deserialize(body) + auth_header = req.headers["Authorization"] if "Authorization" in req.headers else "" + + invoke_response = await ADAPTER.process_activity(activity, auth_header, BOT.on_turn) + if invoke_response: + return json_response(data=invoke_response.body, status=invoke_response.status) + return Response(status=HTTPStatus.OK) + + +APP = web.Application(middlewares=[aiohttp_error_middleware]) +APP.router.add_post("/api/messages", messages) +APP.router.add_get("/api/messages", messages) +APP.router.add_routes(aiohttp_channel_service_routes(SKILL_HANDLER, "/api/skills")) + +if __name__ == "__main__": + try: + web.run_app(APP, host="localhost", port=CONFIG.PORT) + except Exception as error: + raise error diff --git a/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/authentication/__init__.py b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/authentication/__init__.py new file mode 100644 index 0000000000..550f9b54b6 --- /dev/null +++ b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/authentication/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .allowed_skills_claims_validator import AllowedSkillsClaimsValidator + +__all__ = ["AllowedSkillsClaimsValidator"] diff --git a/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/authentication/allowed_skills_claims_validator.py b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/authentication/allowed_skills_claims_validator.py new file mode 100644 index 0000000000..69bfddabc8 --- /dev/null +++ b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/authentication/allowed_skills_claims_validator.py @@ -0,0 +1,33 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from typing import Awaitable, Callable, Dict, List +from botframework.connector.auth import JwtTokenValidation, SkillValidation +from skills_configuration import SkillsConfiguration + + +class AllowedSkillsClaimsValidator: + def __init__(self, skills_config: SkillsConfiguration): + if not skills_config: + raise TypeError( + "AllowedSkillsClaimsValidator: config object cannot be None." + ) + + skills_list = [skill.app_id for skill in skills_config.SKILLS.values()] + self._allowed_skills = frozenset(skills_list) + + @property + def claims_validator(self) -> Callable[[List[Dict]], Awaitable]: + async def allow_callers_claims_validator(claims: Dict[str, object]): + if SkillValidation.is_skill_claim(claims): + # Check that the appId claim in the skill request is in the list of skills configured for this bot. + app_id = JwtTokenValidation.get_app_id_from_claims(claims) + if app_id not in self._allowed_skills: + raise PermissionError( + f'Received a request from a bot with an app ID of "{app_id}".' + f" To enable requests from this caller, add the app ID to your configuration file." + ) + + return + + return allow_callers_claims_validator diff --git a/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/bots/__init__.py b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/bots/__init__.py new file mode 100644 index 0000000000..6a5060f62f --- /dev/null +++ b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/bots/__init__.py @@ -0,0 +1,7 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .root_bot import RootBot + + +__all__ = ["RootBot"] diff --git a/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/bots/root_bot.py b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/bots/root_bot.py new file mode 100644 index 0000000000..3ad5213c8a --- /dev/null +++ b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/bots/root_bot.py @@ -0,0 +1,84 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import json +import os.path +from typing import List +from botbuilder.core import ( + ActivityHandler, + ConversationState, + MessageFactory, + TurnContext, +) +from botbuilder.dialogs import Dialog, DialogExtensions +from botbuilder.schema import ActivityTypes, ChannelAccount, Attachment + +DELIVERY_MODE_PROPERTY_NAME = "deliveryModeProperty" +ACTIVE_SKILL_PROPERTY_NAME = "activeSkillProperty" + + +class RootBot(ActivityHandler): + def __init__( + self, + conversation_state: ConversationState, + main_dialog: Dialog, + ): + self._conversation_state = conversation_state + self._main_dialog = main_dialog + + self._dialog_state_property = conversation_state.create_property("DialogState") + + # Create state property to track the delivery mode and active skill. + self._delivery_mode_property = conversation_state.create_property( + DELIVERY_MODE_PROPERTY_NAME + ) + self._active_skill_property = conversation_state.create_property( + ACTIVE_SKILL_PROPERTY_NAME + ) + + async def on_turn(self, turn_context: TurnContext): + if turn_context.activity.type != ActivityTypes.conversation_update: + # Run the Dialog with the Activity. + await DialogExtensions.run_dialog( + self._main_dialog, + turn_context, + self._dialog_state_property, + ) + else: + # Let the base class handle the activity. + await super().on_turn(turn_context) + + # Save any state changes that might have occurred during the turn. + await self._conversation_state.save_changes(turn_context) + + async def on_members_added_activity( + self, members_added: List[ChannelAccount], turn_context: TurnContext + ): + # Greet anyone that was not the target (recipient) of this message. + # To learn more about Adaptive Cards, see https://aka.ms/msbot-adaptivecards. + for member in members_added: + if member.id != turn_context.activity.recipient.id: + welcome_card = self._create_adaptive_card_attachment() + activity = MessageFactory.attachment(welcome_card) + activity.speak = "Welcome to the waterfall host bot" + await turn_context.send_activity(activity) + await DialogExtensions.run_dialog( + self._main_dialog, + turn_context, + self._dialog_state_property, + ) + + @staticmethod + def _create_adaptive_card_attachment() -> Attachment: + """ + Load attachment from embedded resource. + """ + + relative_path = os.path.abspath(os.path.dirname(__file__)) + path = os.path.join(relative_path, "../cards/welcomeCard.json") + with open(path) as in_file: + card = json.load(in_file) + + return Attachment( + content_type="application/vnd.microsoft.card.adaptive", content=card + ) diff --git a/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/cards/welcomeCard.json b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/cards/welcomeCard.json new file mode 100644 index 0000000000..0cb25e2570 --- /dev/null +++ b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/cards/welcomeCard.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.0", + "body": [ + { + "type": "Image", + "url": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQtB3AwMUeNoq4gUBGe6Ocj8kyh3bXa9ZbV7u1fVKQoyKFHdkqU", + "size": "stretch" + }, + { + "type": "TextBlock", + "spacing": "Medium", + "size": "Medium", + "weight": "Bolder", + "text": "Welcome to the Skill Dialog Sample!", + "wrap": true, + "maxLines": 0, + "color": "Accent" + }, + { + "type": "TextBlock", + "size": "default", + "text": "This sample allows you to connect to a skill using a SkillDialog and invoke several actions.", + "wrap": true, + "maxLines": 0 + } + ] +} \ No newline at end of file diff --git a/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/dialogs/__init__.py b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/dialogs/__init__.py new file mode 100644 index 0000000000..3f3a5f6f9a --- /dev/null +++ b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/dialogs/__init__.py @@ -0,0 +1,8 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .main_dialog import MainDialog +from .tangent_dialog import TangentDialog +from .sso import SsoDialog, SsoSignInDialog + +__all__ = ["MainDialog", "TangentDialog", "SsoDialog", "SsoSignInDialog"] diff --git a/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/dialogs/main_dialog.py b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/dialogs/main_dialog.py new file mode 100644 index 0000000000..46981efa20 --- /dev/null +++ b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/dialogs/main_dialog.py @@ -0,0 +1,456 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import copy +import json + +from botbuilder.dialogs import ( + ComponentDialog, + DialogContext, + WaterfallDialog, + WaterfallStepContext, + DialogTurnResult, + ListStyle, +) +from botbuilder.dialogs.choices import Choice, FoundChoice +from botbuilder.dialogs.prompts import ( + PromptOptions, + ChoicePrompt, +) +from botbuilder.dialogs.skills import ( + SkillDialogOptions, + SkillDialog, + BeginSkillDialogOptions, +) +from botbuilder.core import ConversationState, MessageFactory, TurnContext +from botbuilder.core.skills import ConversationIdFactoryBase +from botbuilder.schema import Activity, ActivityTypes, InputHints, DeliveryModes +from botbuilder.integration.aiohttp.skills import SkillHttpClient + +from skills_configuration import SkillsConfiguration, DefaultConfig + +from dialogs.tangent_dialog import TangentDialog +from dialogs.sso.sso_dialog import SsoDialog + +SSO_DIALOG_PREFIX = "Sso" +ACTIVE_SKILL_PROPERTY_NAME = "MainDialog.ActiveSkillProperty" +DELIVERY_MODE_NAME = "MainDialog.DeliveryMode" +SELECTED_SKILL_KEY_NAME = "MainDialog.SelectedSkillKey" +JUST_FORWARD_THE_ACTIVITY = "JustForwardTurnContext.Activity" + +DELIVERY_MODE_PROMPT = "DeliveryModePrompt" +SKILL_GROUP_PROMPT = "SkillGroupPrompt" +SKILL_PROMPT = "SkillPrompt" +SKILL_ACTION_PROMPT = "SkillActionPrompt" + + +class MainDialog(ComponentDialog): + def __init__( + self, + conversation_state: ConversationState, + conversation_id_factory: ConversationIdFactoryBase, + skill_client: SkillHttpClient, + skills_config: SkillsConfiguration, + configuration: DefaultConfig, + ): + super().__init__(MainDialog.__name__) + + self._configuration = configuration + if not self._configuration: + raise TypeError( + "[MainDialog]: Missing parameter. configuration is required" + ) + + bot_id = self._configuration.APP_ID + + self._skills_config = skills_config + if not self._skills_config: + raise TypeError( + "[MainDialog]: Missing parameter. skills_config is required" + ) + + if not skill_client: + raise TypeError("[MainDialog]: Missing parameter. skill_client is required") + + if not conversation_state: + raise TypeError( + "[MainDialog]: Missing parameter. conversation_state is required" + ) + + if not conversation_id_factory: + raise TypeError( + "[MainDialog]: Missing parameter. conversation_id_factory is required" + ) + + # Use helper method to add SkillDialog instances for the configured skills. + self._add_skill_dialogs( + conversation_state, + conversation_id_factory, + skill_client, + skills_config, + bot_id, + ) + + # Create state property to track the active skill. + self.active_skill_property = conversation_state.create_property( + ACTIVE_SKILL_PROPERTY_NAME + ) + + # Register the tangent dialog for testing tangents and resume. + self.add_dialog(TangentDialog()) + + # Add ChoicePrompt to render available delivery modes. + self.add_dialog(ChoicePrompt(DELIVERY_MODE_PROMPT)) + + # Add ChoicePrompt to render available types of skill. + self.add_dialog(ChoicePrompt(SKILL_GROUP_PROMPT)) + + # Add ChoicePrompt to render available skills. + self.add_dialog(ChoicePrompt(SKILL_PROMPT)) + + # Add ChoicePrompt to render skill actions. + self.add_dialog(ChoicePrompt(SKILL_ACTION_PROMPT)) + + # Special case: register SSO dialogs for skills that support SSO actions. + self._add_sso_dialogs(self._configuration) + + # Add main waterfall dialog for this bot. + self.add_dialog( + WaterfallDialog( + WaterfallDialog.__name__, + [ + self._select_delivery_mode_step, + self._select_skill_group_step, + self._select_skill_step, + self._select_skill_action_step, + self._call_skill_action_step, + self._final_step, + ], + ) + ) + + self.initial_dialog_id = WaterfallDialog.__name__ + + async def on_continue_dialog(self, inner_dc: DialogContext): + """ + This override is used to test the "abort" command to interrupt skills from the parent and + also to test the "tangent" command to start a tangent and resume a skill. + """ + + # This is an example on how to cancel a SkillDialog that is currently in progress from the parent bot. + active_skill = await self.active_skill_property.get(inner_dc.context) + activity = inner_dc.context.activity + + if ( + active_skill + and activity.type == ActivityTypes.message + and activity.text + and "abort" in activity.text.lower() + ): + # Cancel all dialogs when the user says abort. + # The SkillDialog automatically sends an EndOfConversation message to the skill to let the + # skill know that it needs to end its current dialogs, too. + await inner_dc.cancel_all_dialogs() + return await inner_dc.replace_dialog( + self.initial_dialog_id, + "Canceled! \n\n What delivery mode would you like to use?", + ) + + # Sample to test a tangent when in the middle of a skill conversation. + if ( + active_skill + and activity.type == ActivityTypes.message + and activity.text + and activity.text.lower() == "tangent" + ): + # Start tangent. + return await inner_dc.begin_dialog(TangentDialog.__name__) + + return await super().on_continue_dialog(inner_dc) + + async def _select_delivery_mode_step( + self, step_context: WaterfallStepContext + ) -> DialogTurnResult: + """ + Render a prompt to select the delivery mode to use. + """ + + # Create the PromptOptions with the delivery modes supported. + message_text = ( + str(step_context.options) + if step_context.options + else "What delivery mode would you like to use?" + ) + retry_message_text = ( + "That was not a valid choice, please select a valid delivery mode." + ) + + options = PromptOptions( + prompt=MessageFactory.text( + message_text, message_text, InputHints.expecting_input + ), + retry_prompt=MessageFactory.text( + retry_message_text, retry_message_text, InputHints.expecting_input + ), + choices=[ + Choice(DeliveryModes.normal.value), + Choice(DeliveryModes.expect_replies.value), + ], + ) + + # Prompt the user to select a delivery mode. + return await step_context.prompt(DELIVERY_MODE_PROMPT, options) + + async def _select_skill_group_step(self, step_context: WaterfallStepContext): + """ + Render a prompt to select the group of skills to use. + """ + + # Remember the delivery mode selected by the user. + step_context.values[DELIVERY_MODE_NAME] = step_context.result.value + + # Create the PromptOptions with the types of supported skills. + message_text = "What group of skills would you like to use?" + retry_message_text = ( + "That was not a valid choice, please select a valid skill group." + ) + + choices = [] + groups = [] + for skill in self._skills_config.SKILLS.values(): + if skill.group not in groups: + groups.append(skill.group) + choices.append(Choice(skill.group)) + + options = PromptOptions( + prompt=MessageFactory.text( + message_text, message_text, InputHints.expecting_input + ), + retry_prompt=MessageFactory.text( + retry_message_text, retry_message_text, InputHints.expecting_input + ), + choices=choices, + ) + + # Prompt the user to select a type of skill. + return await step_context.prompt(SKILL_GROUP_PROMPT, options) + + async def _select_skill_step( + self, step_context: WaterfallStepContext + ) -> DialogTurnResult: + """ + Render a prompt to select the skill to call. + """ + + skill_group = step_context.result.value + + # Create the PromptOptions from the skill configuration which contain the list of configured skills. + message_text = "What skill would you like to call?" + retry_message_text = "That was not a valid choice, please select a valid skill." + + options = PromptOptions( + prompt=MessageFactory.text( + message_text, message_text, InputHints.expecting_input + ), + retry_prompt=MessageFactory.text( + retry_message_text, retry_message_text, InputHints.expecting_input + ), + style=ListStyle.list_style, + choices=[ + Choice(skill.id) + for skill in self._skills_config.SKILLS.values() + if skill.group.lower().startswith(skill_group.lower()) + ], + ) + + # Prompt the user to select a skill. + return await step_context.prompt(SKILL_PROMPT, options) + + async def _select_skill_action_step(self, step_context: WaterfallStepContext): + """ + Render a prompt to select the begin action for the skill. + """ + + # Get the skill info based on the selected skill. + selected_skill_id = step_context.result.value + delivery_mode = str(step_context.values[DELIVERY_MODE_NAME]) + v3_bots = ["EchoSkillBotDotNetV3", "EchoSkillBotJSV3"] + + # Exclude v3 bots from ExpectReplies + if ( + delivery_mode == DeliveryModes.expect_replies + and selected_skill_id in v3_bots + ): + await step_context.context.send_activity( + MessageFactory.text( + "V3 Bots do not support 'expectReplies' delivery mode." + ) + ) + + # Restart setup dialog + return await step_context.replace_dialog(self.initial_dialog_id) + + selected_skill = next( + val + for key, val in self._skills_config.SKILLS.items() + if val.id == selected_skill_id + ) + + # Remember the skill selected by the user. + step_context.values[SELECTED_SKILL_KEY_NAME] = selected_skill + + skill_action_choices = [ + Choice(action) for action in selected_skill.get_actions() + ] + if len(skill_action_choices) == 1: + # The skill only supports one action (e.g. Echo), skip the prompt. + return await step_context.next( + FoundChoice(value=skill_action_choices[0].value, index=0, score=0) + ) + + # Create the PromptOptions with the actions supported by the selected skill. + message_text = f"Select an action to send to **{selected_skill.id}**." + + options = PromptOptions( + prompt=MessageFactory.text( + message_text, message_text, InputHints.expecting_input + ), + choices=skill_action_choices, + ) + + # Prompt the user to select a skill action. + return await step_context.prompt(SKILL_ACTION_PROMPT, options) + + async def _call_skill_action_step(self, step_context: WaterfallStepContext): + """ + Starts the SkillDialog based on the user's selections. + """ + + selected_skill = step_context.values[SELECTED_SKILL_KEY_NAME] + + # Save active skill in state. + await self.active_skill_property.set(step_context.context, selected_skill) + + # Create the initial activity to call the skill. + skill_activity = self._create_begin_activity( + step_context.context, selected_skill.id, step_context.result.value + ) + + if skill_activity.name == "Sso": + # Special case, we start the SSO dialog to prepare the host to call the skill. + return await step_context.begin_dialog( + f"{SSO_DIALOG_PREFIX}{selected_skill.id}" + ) + + # We are manually creating the activity to send to the skill; ensure we add the ChannelData and Properties + # from the original activity so the skill gets them. + # Note: this is not necessary if we are just forwarding the current activity from context. + skill_activity.channel_data = step_context.context.activity.channel_data + skill_activity.additional_properties = ( + step_context.context.activity.additional_properties + ) + + # Create the BeginSkillDialogOptions and assign the activity to send. + skill_dialog_args = BeginSkillDialogOptions(activity=skill_activity) + if str(step_context.values[DELIVERY_MODE_NAME]) == DeliveryModes.expect_replies: + skill_dialog_args.activity.delivery_mode = DeliveryModes.expect_replies + + # Start the skillDialog instance with the arguments. + return await step_context.begin_dialog(selected_skill.id, skill_dialog_args) + + async def _final_step(self, step_context: WaterfallStepContext): + """ + The SkillDialog has ended, render the results (if any) and restart MainDialog. + """ + + active_skill = await self.active_skill_property.get(step_context.context) + + # Check if the skill returned any results and display them. + if step_context.result: + message = f'Skill "{active_skill.id}" invocation complete.' + message += f" Result: {json.dumps(step_context.result)}" + await step_context.context.send_activity( + MessageFactory.text(message, message, InputHints.ignoring_input) + ) + + # Clear the delivery mode selected by the user. + step_context.values[DELIVERY_MODE_NAME] = None + + # Clear the skill selected by the user. + step_context.values[SELECTED_SKILL_KEY_NAME] = None + + # Clear active skill in state. + await self.active_skill_property.delete(step_context.context) + + # Restart the main dialog with a different message the second time around. + return await step_context.replace_dialog( + self.initial_dialog_id, + f'Done with "{active_skill.id}". \n\n What delivery mode would you ' + f"like to use?", + ) + + def _add_skill_dialogs( + self, + conversation_state: ConversationState, + conversation_id_factory: ConversationIdFactoryBase, + skill_client: SkillHttpClient, + skills_config: SkillsConfiguration, + bot_id: str, + ): + """ + Helper method that creates and adds SkillDialog instances for the configured skills. + """ + + for skill_info in self._skills_config.SKILLS.values(): + # Create the dialog options. + skill_dialog_options = SkillDialogOptions( + bot_id=bot_id, + conversation_id_factory=conversation_id_factory, + skill_client=skill_client, + skill_host_endpoint=skills_config.SKILL_HOST_ENDPOINT, + conversation_state=conversation_state, + skill=skill_info, + ) + + # Add a SkillDialog for the selected skill. + self.add_dialog(SkillDialog(skill_dialog_options, skill_info.id)) + + def _create_begin_activity( + self, context: TurnContext, skill_id: str, selected_option: str + ): + if selected_option.lower() == JUST_FORWARD_THE_ACTIVITY.lower(): + # Note message activities also support input parameters but we are not using them in this example. + # Return a deep clone of the activity so we don't risk altering the original one + return copy.deepcopy(context.activity) + + # Get the begin activity from the skill instance. + activity: Activity = self._skills_config.SKILLS[skill_id].create_begin_activity( + selected_option + ) + + # We are manually creating the activity to send to the skill; ensure we add the ChannelData and Properties + # from the original activity so the skill gets them. + # Note: this is not necessary if we are just forwarding the current activity from context. + activity.channel_data = context.activity.channel_data + activity.additional_properties = context.activity.additional_properties + + return activity + + # Special case. + # SSO needs a dialog in the host to allow the user to sign in. + # We create and several SsoDialog instances for each skill that supports SSO. + def _add_sso_dialogs(self, configuration: DefaultConfig): + connection_name = configuration.SSO_CONNECTION_NAME + + for sso_skill_dialog in [ + skill_dialog + for skill_dialog in self._dialogs._dialogs.values() # pylint: disable=W0212 + if skill_dialog.id.startswith("WATERFALLSKILL") + ]: + self.add_dialog( + SsoDialog( + f"{SSO_DIALOG_PREFIX}{sso_skill_dialog.id}", + sso_skill_dialog, + connection_name, + ) + ) diff --git a/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/dialogs/sso/__init__.py b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/dialogs/sso/__init__.py new file mode 100644 index 0000000000..f489fd4fae --- /dev/null +++ b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/dialogs/sso/__init__.py @@ -0,0 +1,7 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .sso_dialog import SsoDialog +from .sso_signin_dialog import SsoSignInDialog + +__all__ = ["SsoDialog", "SsoSignInDialog"] diff --git a/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/dialogs/sso/sso_dialog.py b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/dialogs/sso/sso_dialog.py new file mode 100644 index 0000000000..1145358a33 --- /dev/null +++ b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/dialogs/sso/sso_dialog.py @@ -0,0 +1,137 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from botbuilder.dialogs import ( + ComponentDialog, + WaterfallDialog, + WaterfallStepContext, + Choice, + ChoicePrompt, + PromptOptions, + Dialog, + BeginSkillDialogOptions, +) +from botbuilder.core import MessageFactory +from botbuilder.dialogs.dialog_turn_result import DialogTurnResult +from botbuilder.dialogs.dialog_turn_status import DialogTurnStatus +from botbuilder.schema import Activity, ActivityTypes, InputHints + +from .sso_signin_dialog import SsoSignInDialog + + +class SsoDialog(ComponentDialog): + """ + Helps prepare the host for SSO operations and provides helpers to check the status and invoke the skill. + """ + + def __init__(self, dialog_id: str, sso_skill_dialog: Dialog, connection_name): + super().__init__(dialog_id) + + self._connection_name = connection_name + self.skill_dialog_id = sso_skill_dialog.id + + self.add_dialog(ChoicePrompt(ChoicePrompt.__name__)) + self.add_dialog(SsoSignInDialog(self._connection_name)) + self.add_dialog(sso_skill_dialog) + + self.add_dialog( + WaterfallDialog( + WaterfallDialog.__name__, + [ + self.prompt_action_step, + self.handle_action_step, + self.prompt_final_step, + ], + ) + ) + + self.initial_dialog_id = WaterfallDialog.__name__ + + async def prompt_action_step(self, step_context: WaterfallStepContext): + message_text = "What SSO action do you want to perform?" + reprompt_message_text = ( + "That was not a valid choice, please select a valid choice." + ) + + options = PromptOptions( + prompt=MessageFactory.text( + message_text, message_text, InputHints.expecting_input + ), + retry_prompt=MessageFactory.text( + reprompt_message_text, reprompt_message_text, InputHints.expecting_input + ), + choices=await self.get_prompt_choices(step_context), + ) + + # Prompt the user to select an SSO action. + return await step_context.prompt(ChoicePrompt.__name__, options) + + async def get_prompt_choices(self, step_context: WaterfallStepContext): + """ + Create the prompt choices based on the current sign in status + """ + + prompt_choices = list() + token = await step_context.context.adapter.get_user_token( + step_context.context, self._connection_name + ) + + if token is None: + prompt_choices.append(Choice("Login")) + + # Token exchange will fail when the host is not logged on and the skill should + # show a regular OAuthPrompt + prompt_choices.append(Choice("Call Skill (without SSO)")) + else: + prompt_choices.append(Choice("Logout")) + prompt_choices.append(Choice("Show token")) + prompt_choices.append(Choice("Call Skill (with SSO)")) + + prompt_choices.append(Choice("Back")) + + return prompt_choices + + async def handle_action_step(self, step_context: WaterfallStepContext): + action = str(step_context.result.value).lower() + + if action == "login": + return await step_context.begin_dialog(SsoSignInDialog.__name__) + + if action == "logout": + await step_context.context.adapter.sign_out_user( + step_context.context, self._connection_name + ) + await step_context.context.send_activity("You have been signed out.") + return await step_context.next(step_context.result) + + if action == "show token": + token = await step_context.context.adapter.get_user_token( + step_context.context, self._connection_name + ) + + if token is None: + await step_context.context.send_activity("User has no cached token.") + else: + await step_context.context.send_activity( + f"Here is your current SSO token: { token.token }" + ) + + return await step_context.next(step_context.result) + + if action in ["call skill (with sso)", "call skill (without sso)"]: + begin_skill_activity = Activity(type=ActivityTypes.event, name="Sso") + + return await step_context.begin_dialog( + self.skill_dialog_id, + BeginSkillDialogOptions(activity=begin_skill_activity), + ) + + if action == "back": + return DialogTurnResult(DialogTurnStatus.Complete) + + # This should never be hit since the previous prompt validates the choice + raise Exception(f"Unrecognized action: {action}") + + async def prompt_final_step(self, step_context: WaterfallStepContext): + # Restart the dialog (we will exit when the user says end) + return await step_context.replace_dialog(self.initial_dialog_id) diff --git a/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/dialogs/sso/sso_signin_dialog.py b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/dialogs/sso/sso_signin_dialog.py new file mode 100644 index 0000000000..242679dc36 --- /dev/null +++ b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/dialogs/sso/sso_signin_dialog.py @@ -0,0 +1,57 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from botbuilder.dialogs import ( + ComponentDialog, + WaterfallDialog, + WaterfallStepContext, + OAuthPrompt, + OAuthPromptSettings, +) + + +class SsoSignInDialog(ComponentDialog): + def __init__(self, connection_name: str): + super().__init__(SsoSignInDialog.__name__) + + self.add_dialog( + OAuthPrompt( + OAuthPrompt.__name__, + OAuthPromptSettings( + connection_name=connection_name, + text=f"Sign in to the host bot using AAD for SSO and connection {connection_name}", + title="Sign In", + timeout=60000, + ), + ) + ) + + self.add_dialog( + WaterfallDialog( + WaterfallDialog.__name__, + [ + self.signin_step, + self.display_token, + ], + ) + ) + + self.initial_dialog_id = WaterfallDialog.__name__ + + async def signin_step(self, step_context: WaterfallStepContext): + return await step_context.begin_dialog(OAuthPrompt.__name__) + + async def display_token(self, step_context: WaterfallStepContext): + sso_token = step_context.result + if sso_token: + if isinstance(sso_token, dict): + token = sso_token.get("token") + else: + token = sso_token.token + + await step_context.context.send_activity(f"Here is your token: {token}") + + else: + await step_context.context.send_activity("No token was provided.") + + return await step_context.end_dialog() diff --git a/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/dialogs/tangent_dialog.py b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/dialogs/tangent_dialog.py new file mode 100644 index 0000000000..957aba021b --- /dev/null +++ b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/dialogs/tangent_dialog.py @@ -0,0 +1,51 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from botbuilder.dialogs import ( + ComponentDialog, + WaterfallDialog, + WaterfallStepContext, + TextPrompt, + PromptOptions, +) +from botbuilder.core import MessageFactory +from botbuilder.schema import InputHints + + +class TangentDialog(ComponentDialog): + """ + A simple waterfall dialog used to test triggering tangents from "MainDialog". + """ + + def __init__(self): + super().__init__(TangentDialog.__name__) + + self.add_dialog(TextPrompt(TextPrompt.__name__)) + self.add_dialog( + WaterfallDialog( + WaterfallDialog.__name__, [self._step_1, self._step_2, self._end_step] + ) + ) + + self.initial_dialog_id = WaterfallDialog.__name__ + + async def _step_1(self, step_context: WaterfallStepContext): + message_text = "Tangent step 1 of 2, say something." + prompt_message = MessageFactory.text( + message_text, message_text, InputHints.expecting_input + ) + return await step_context.prompt( + TextPrompt.__name__, PromptOptions(prompt=prompt_message) + ) + + async def _step_2(self, step_context: WaterfallStepContext): + message_text = "Tangent step 2 of 2, say something." + prompt_message = MessageFactory.text( + message_text, message_text, InputHints.expecting_input + ) + return await step_context.prompt( + TextPrompt.__name__, PromptOptions(prompt=prompt_message) + ) + + async def _end_step(self, step_context: WaterfallStepContext): + return await step_context.end_dialog() diff --git a/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/helpers/dialog_helper.py b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/helpers/dialog_helper.py new file mode 100644 index 0000000000..6b2646b0ba --- /dev/null +++ b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/helpers/dialog_helper.py @@ -0,0 +1,19 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from botbuilder.core import StatePropertyAccessor, TurnContext +from botbuilder.dialogs import Dialog, DialogSet, DialogTurnStatus + + +class DialogHelper: + @staticmethod + async def run_dialog( + dialog: Dialog, turn_context: TurnContext, accessor: StatePropertyAccessor + ): + dialog_set = DialogSet(accessor) + dialog_set.add(dialog) + + dialog_context = await dialog_set.create_context(turn_context) + results = await dialog_context.continue_dialog() + if results.status == DialogTurnStatus.Empty: + await dialog_context.begin_dialog(dialog.id) diff --git a/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/requirements.txt b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/requirements.txt new file mode 100644 index 0000000000..02460b14f9 --- /dev/null +++ b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/requirements.txt @@ -0,0 +1,3 @@ +botbuilder-integration-aiohttp>=4.13.0 +botbuilder-dialogs>=4.13.0 +python-dotenv~=0.15.0 diff --git a/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/skill_conversation_id_factory.py b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/skill_conversation_id_factory.py new file mode 100644 index 0000000000..06c5e5a2f5 --- /dev/null +++ b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/skill_conversation_id_factory.py @@ -0,0 +1,76 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from typing import Union + +from botbuilder.core import Storage, TurnContext +from botbuilder.core.skills import ( + ConversationIdFactoryBase, + SkillConversationIdFactoryOptions, + SkillConversationReference, +) +from botbuilder.schema import ConversationReference + + +class SkillConversationIdFactory(ConversationIdFactoryBase): + def __init__(self, storage: Storage): + if not storage: + raise TypeError("storage can't be None") + + self._storage = storage + + async def create_skill_conversation_id( + self, + options_or_conversation_reference: Union[ + SkillConversationIdFactoryOptions, ConversationReference + ], + ) -> str: + if not options_or_conversation_reference: + raise TypeError("Need options or conversation reference") + + if not isinstance( + options_or_conversation_reference, SkillConversationIdFactoryOptions + ): + raise TypeError( + "This SkillConversationIdFactory can only handle SkillConversationIdFactoryOptions" + ) + + options = options_or_conversation_reference + + # Create the storage key based on the SkillConversationIdFactoryOptions. + conversation_reference = TurnContext.get_conversation_reference( + options.activity + ) + skill_conversation_id = ( + f"{conversation_reference.conversation.id}" + f"-{options.bot_framework_skill.id}" + f"-{conversation_reference.channel_id}" + f"-skillconvo" + ) + + # Create the SkillConversationReference instance. + skill_conversation_reference = SkillConversationReference( + conversation_reference=conversation_reference, + oauth_scope=options.from_bot_oauth_scope, + ) + + # Store the SkillConversationReference using the skill_conversation_id as a key. + skill_conversation_info = {skill_conversation_id: skill_conversation_reference} + await self._storage.write(skill_conversation_info) + + # Return the generated skill_conversation_id (that will be also used as the conversation ID to call the skill). + return skill_conversation_id + + async def get_conversation_reference( + self, skill_conversation_id: str + ) -> Union[SkillConversationReference, ConversationReference]: + if not skill_conversation_id: + raise TypeError("skill_conversation_id can't be None") + + # Get the SkillConversationReference from storage for the given skill_conversation_id. + skill_conversation_info = await self._storage.read([skill_conversation_id]) + + return skill_conversation_info.get(skill_conversation_id) + + async def delete_conversation_reference(self, skill_conversation_id: str): + await self._storage.delete([skill_conversation_id]) diff --git a/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/skills/echo_skill.py b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/skills/echo_skill.py new file mode 100644 index 0000000000..560f0adb14 --- /dev/null +++ b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/skills/echo_skill.py @@ -0,0 +1,25 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from enum import Enum +from botbuilder.schema import Activity +from skills.skill_definition import SkillDefinition + + +class EchoSkill(SkillDefinition): + class SkillAction(str, Enum): + MESSAGE = "Message" + + def get_actions(self): + return self.SkillAction + + def create_begin_activity(self, action_id: str): + if action_id not in self.SkillAction: + raise Exception(f'Unable to create begin activity for "${action_id}".') + + # We only support one activity for Echo so no further checks are needed + activity = Activity.create_message_activity() + activity.name = self.SkillAction.MESSAGE.value + activity.text = "Begin the Echo Skill" + + return activity diff --git a/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/skills/skill_definition.py b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/skills/skill_definition.py new file mode 100644 index 0000000000..3e0d43e498 --- /dev/null +++ b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/skills/skill_definition.py @@ -0,0 +1,25 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from botbuilder.core.skills import BotFrameworkSkill + + +class SkillDefinition(BotFrameworkSkill): + """ + Extends BotFrameworkSkill and provides methods to return the actions and the begin activity + to start a skill. + This class also exposes a group property to render skill groups and narrow down the available + options. + Remarks: This is just a temporary implementation, ideally, this should be replaced by logic that + parses a manifest and creates what's needed. + """ + + def __init__(self, id: str = None, group: str = None): + super().__init__(id=id) + self.group = group + + def get_actions(self): + raise NotImplementedError("[SkillDefinition]: Method not implemented") + + def create_begin_activity(self, action_id: str): + raise NotImplementedError("[SkillDefinition]: Method not implemented") diff --git a/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/skills/teams_skill.py b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/skills/teams_skill.py new file mode 100644 index 0000000000..6631c869ea --- /dev/null +++ b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/skills/teams_skill.py @@ -0,0 +1,36 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from enum import Enum +from botbuilder.schema import Activity +from skills.skill_definition import SkillDefinition + + +class TeamsSkill(SkillDefinition): + class SkillAction(str, Enum): + TEAMS_TASK_MODULE = "TeamsTaskModule" + TEAMS_CARD_ACTION = "TeamsCardAction" + TEAMS_CONVERSATION = "TeamsConversation" + CARDS = "Cards" + PROACTIVE = "Proactive" + ATTACHMENT = "Attachment" + AUTH = "Auth" + SSO = "Sso" + ECHO = "Echo" + FILE_UPLOAD = "FileUpload" + DELETE = "Delete" + UPDATE = "Update" + + def get_actions(self): + return self.SkillAction + + def create_begin_activity(self, action_id: str): + if action_id not in self.SkillAction: + raise Exception(f'Unable to create begin activity for "${action_id}".') + + # We don't support special parameters in these skills so a generic event with the + # right name will do in this case. + activity = Activity.create_event_activity() + activity.name = action_id + + return activity diff --git a/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/skills/waterfall_skill.py b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/skills/waterfall_skill.py new file mode 100644 index 0000000000..34db3b6abc --- /dev/null +++ b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/skills/waterfall_skill.py @@ -0,0 +1,33 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from enum import Enum +from botbuilder.schema import Activity +from skills.skill_definition import SkillDefinition + + +class WaterfallSkill(SkillDefinition): + class SkillAction(str, Enum): + CARDS = "Cards" + PROACTIVE = "Proactive" + AUTH = "Auth" + MESSAGE_WITH_ATTACHMENT = "MessageWithAttachment" + SSO = "Sso" + FILE_UPLOAD = "FileUpload" + ECHO = "Echo" + DELETE = "Delete" + UPDATE = "Update" + + def get_actions(self): + return self.SkillAction + + def create_begin_activity(self, action_id: str): + if action_id not in self.SkillAction: + raise Exception(f'Unable to create begin activity for "${action_id}".') + + # We don't support special parameters in these skills so a generic event with the + # right name will do in this case. + activity = Activity.create_event_activity() + activity.name = action_id + + return activity diff --git a/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/skills_configuration.py b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/skills_configuration.py new file mode 100644 index 0000000000..1b29374ffb --- /dev/null +++ b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/skills_configuration.py @@ -0,0 +1,88 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import os +from typing import Dict + +from botbuilder.dialogs import ObjectPath +from dotenv import load_dotenv + +from skills.skill_definition import SkillDefinition +from skills.waterfall_skill import WaterfallSkill +from skills.echo_skill import EchoSkill +from skills.teams_skill import TeamsSkill + +load_dotenv() + + +class DefaultConfig: + """ + Bot Default Configuration + """ + + PORT = 37020 + APP_ID = os.getenv("MicrosoftAppId") + APP_PASSWORD = os.getenv("MicrosoftAppPassword") + SSO_CONNECTION_NAME = os.getenv("SsoConnectionName") + SSO_CONNECTION_NAME_TEAMS = os.getenv("SsoConnectionNameTeams") + + +class SkillsConfiguration: + """ + Bot Skills Configuration + A helper class that loads Skills information from configuration + Remarks: This class loads the skill settings from env and casts them into derived + types of SkillDefinition so we can render prompts with the skills and in their + groups. + """ + + SKILL_HOST_ENDPOINT = os.getenv("SkillHostEndpoint") + SKILLS: Dict[str, SkillDefinition] = dict() + + def __init__(self): + skills_data = dict() + skill_variable = [x for x in os.environ if x.lower().startswith("skill_")] + + for val in skill_variable: + names = val.split("_") + bot_id = names[1] + attr = names[2] + + if bot_id not in skills_data: + skills_data[bot_id] = dict() + + if attr.lower() == "appid": + skills_data[bot_id]["app_id"] = os.getenv(val) + elif attr.lower() == "endpoint": + skills_data[bot_id]["skill_endpoint"] = os.getenv(val) + elif attr.lower() == "group": + skills_data[bot_id]["group"] = os.getenv(val) + else: + raise ValueError( + f"[SkillsConfiguration]: Invalid environment variable declaration {attr}" + ) + + for skill_id, skill_value in skills_data.items(): + definition = SkillDefinition(id=skill_id, group=skill_value["group"]) + definition.app_id = skill_value["app_id"] + definition.skill_endpoint = skill_value["skill_endpoint"] + self.SKILLS[skill_id] = self.create_skill_definition(definition) + + # Note: we hard code this for now, we should dynamically create instances based on the manifests. + # For now, this code creates a strong typed version of the SkillDefinition based on the skill group + # and copies the info from env into it. + @staticmethod + def create_skill_definition(skill: SkillDefinition): + if skill.group.lower() == ("echo"): + skill_definition = ObjectPath.assign(EchoSkill(), skill) + + elif skill.group.lower() == ("waterfall"): + skill_definition = ObjectPath.assign(WaterfallSkill(), skill) + + elif skill.group.lower() == ("teams"): + skill_definition = ObjectPath.assign(TeamsSkill(), skill) + + else: + raise Exception(f"Unable to find definition class for {skill.id}.") + + return skill_definition diff --git a/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/token_exchange_skill_handler.py b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/token_exchange_skill_handler.py new file mode 100644 index 0000000000..1da847ef5f --- /dev/null +++ b/tests/functional/Bots/Python/Consumers/CodeFirst/WaterfallHostBot/token_exchange_skill_handler.py @@ -0,0 +1,199 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import traceback +from uuid import uuid4 +from typing import Union + +from botbuilder.core import Bot, TurnContext +from botbuilder.core.card_factory import ContentTypes +from botbuilder.core.skills import ( + SkillHandler, + BotFrameworkSkill, +) +from botbuilder.integration.aiohttp.skills import SkillHttpClient +from botbuilder.schema import ( + ResourceResponse, + ActivityTypes, + SignInConstants, + TokenExchangeInvokeRequest, +) +from botframework.connector.auth import ( + CredentialProvider, + AuthenticationConfiguration, + ClaimsIdentity, + Activity, + JwtTokenValidation, +) +from botframework.connector.token_api.models import TokenExchangeRequest + +from adapter_with_error_handler import AdapterWithErrorHandler +from skill_conversation_id_factory import SkillConversationIdFactory +from skills.skill_definition import SkillDefinition +from skills_configuration import SkillsConfiguration, DefaultConfig + + +class TokenExchangeSkillHandler(SkillHandler): + def __init__( + self, + adapter: AdapterWithErrorHandler, + bot: Bot, + configuration: DefaultConfig, + conversation_id_factory: SkillConversationIdFactory, + skills_config: SkillsConfiguration, + skill_client: SkillHttpClient, + credential_provider: CredentialProvider, + auth_configuration: AuthenticationConfiguration, + ): + super().__init__( + adapter, + bot, + conversation_id_factory, + credential_provider, + auth_configuration, + ) + self._token_exchange_provider = adapter + if not self._token_exchange_provider: + raise ValueError( + f"{self._token_exchange_provider} does not support token exchange" + ) + + self._configuration = configuration + self._skills_config = skills_config + self._skill_client = skill_client + self._conversation_id_factory = conversation_id_factory + self._bot_id = configuration.APP_ID + + async def on_send_to_conversation( + self, claims_identity: ClaimsIdentity, conversation_id: str, activity: Activity, + ) -> ResourceResponse: + if await self._intercept_oauth_cards(claims_identity, activity): + return ResourceResponse(id=str(uuid4())) + + return await super().on_send_to_conversation( + claims_identity, conversation_id, activity + ) + + async def on_reply_to_activity( + self, + claims_identity: ClaimsIdentity, + conversation_id: str, + activity_id: str, + activity: Activity, + ) -> ResourceResponse: + if await self._intercept_oauth_cards(claims_identity, activity): + return ResourceResponse(id=str(uuid4())) + + return await super().on_reply_to_activity( + claims_identity, conversation_id, activity_id, activity + ) + + def _get_calling_skill( + self, claims_identity: ClaimsIdentity + ) -> Union[SkillDefinition, None]: + app_id = JwtTokenValidation.get_app_id_from_claims(claims_identity.claims) + + if not app_id: + return None + + return next( + skill + for skill in self._skills_config.SKILLS.values() + if skill.app_id == app_id + ) + + async def _intercept_oauth_cards( + self, claims_identity: ClaimsIdentity, activity: Activity + ) -> bool: + if activity.attachments: + oauth_card_attachment = next( + ( + attachment + for attachment in activity.attachments + if attachment.content_type == ContentTypes.oauth_card + ), + None, + ) + + if oauth_card_attachment: + target_skill = self._get_calling_skill(claims_identity) + if target_skill: + oauth_card = oauth_card_attachment.content + token_exchange_resource = oauth_card.get( + "TokenExchangeResource" + ) or oauth_card.get("tokenExchangeResource") + if token_exchange_resource: + context = TurnContext(self._adapter, activity) + context.turn_state["BotIdentity"] = claims_identity + + # We need to know what connection name to use for the token exchange so we figure that out here + connection_name = ( + self._configuration.SSO_CONNECTION_NAME + if target_skill.group == "Waterfall" + else self._configuration.SSO_CONNECTION_NAME_TEAMS + ) + + if not connection_name: + raise ValueError("The SSO connection name cannot be null.") + + # AAD token exchange + try: + uri = token_exchange_resource.get("uri") + result = await self._token_exchange_provider.exchange_token( + context, + connection_name, + activity.recipient.id, + TokenExchangeRequest(uri=uri), + ) + + if result.token: + # If token above is null, then SSO has failed and hence we return false. + # If not, send an invoke to the skill with the token. + return await self._send_token_exchange_invoke_to_skill( + incoming_activity=activity, + connection_name=oauth_card.get("connectionName"), + resource_id=token_exchange_resource.get("id"), + token=result.token, + target_skill=target_skill, + ) + + except Exception as exception: + print(f"Unable to exchange token: {exception}") + traceback.print_exc() + return False + + return False + + async def _send_token_exchange_invoke_to_skill( + self, + incoming_activity: Activity, + resource_id: str, + token: str, + connection_name: str, + target_skill: BotFrameworkSkill, + ) -> bool: + activity = incoming_activity.create_reply() + activity.type = ActivityTypes.invoke + activity.name = SignInConstants.token_exchange_operation_name + activity.value = TokenExchangeInvokeRequest( + id=resource_id, token=token, connection_name=connection_name + ) + skill_conversation_reference = await self._conversation_id_factory.get_conversation_reference( + incoming_activity.conversation.id + ) + activity.conversation = ( + skill_conversation_reference.conversation_reference.conversation + ) + activity.service_url = ( + skill_conversation_reference.conversation_reference.service_url + ) + + # Route the activity to the skill + response = await self._skill_client.post_activity_to_skill( + from_bot_id=self._bot_id, + to_skill=target_skill, + service_url=self._skills_config.SKILL_HOST_ENDPOINT, + activity=activity, + ) + + return 200 <= response.status <= 299 diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/EchoSkillBot/app.py b/tests/functional/Bots/Python/Skills/CodeFirst/EchoSkillBot/app.py new file mode 100644 index 0000000000..5a87c33513 --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/EchoSkillBot/app.py @@ -0,0 +1,135 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import json +import sys +import traceback +from datetime import datetime + +from aiohttp import web +from aiohttp.web import Request, Response +from botbuilder.core import ( + BotFrameworkAdapter, + BotFrameworkAdapterSettings, + TurnContext, + MessageFactory, +) +from botbuilder.schema import Activity, ActivityTypes, InputHints +from botframework.connector.auth import AuthenticationConfiguration + +from bots import EchoBot +from config import DefaultConfig +from authentication import AllowedCallersClaimsValidator +from http import HTTPStatus + +CONFIG = DefaultConfig() +CLAIMS_VALIDATOR = AllowedCallersClaimsValidator(frozenset(CONFIG.ALLOWED_CALLERS)) +AUTH_CONFIG = AuthenticationConfiguration( + claims_validator=CLAIMS_VALIDATOR.validate_claims +) +# Create adapter. +# See https://aka.ms/about-bot-adapter to learn more about how bots work. +SETTINGS = BotFrameworkAdapterSettings( + app_id=CONFIG.APP_ID, + app_password=CONFIG.APP_PASSWORD, + auth_configuration=AUTH_CONFIG, +) +ADAPTER = BotFrameworkAdapter(SETTINGS) + +# Catch-all for errors. +async def on_error(context: TurnContext, error: Exception): + # This check writes out errors to console log .vs. app insights. + # NOTE: In production environment, you should consider logging this to Azure + # application insights. + print(f"\n [on_turn_error] unhandled error: {error}", file=sys.stderr) + traceback.print_exc() + + try: + exc_info = sys.exc_info() + stack = traceback.format_exception(*exc_info) + + # Send a message to the user + error_message_text = "The skill encountered an error or bug." + error_message = MessageFactory.text( + f"{error_message_text}\r\n{error}\r\n{stack}", + error_message_text, + InputHints.ignoring_input, + ) + error_message.value = {"message": error, "stack": stack} + await context.send_activity(error_message) + + error_message_text = ( + "To continue to run this bot, please fix the bot source code." + ) + error_message = MessageFactory.text( + error_message_text, error_message_text, InputHints.expecting_input + ) + await context.send_activity(error_message) + + # Send a trace activity, which will be displayed in Bot Framework Emulator + if context.activity.channel_id == "emulator": + # Create a trace activity that contains the error object + trace_activity = Activity( + label="TurnError", + name="on_turn_error Trace", + timestamp=datetime.utcnow(), + type=ActivityTypes.trace, + value=f"{error}", + value_type="https://www.botframework.com/schemas/error", + ) + await context.send_activity(trace_activity) + + # Send and EndOfConversation activity to the skill caller with the error to end the conversation and let the + # caller decide what to do. Send a trace activity if we're talking to the Bot Framework Emulator + end_of_conversation = Activity( + type=ActivityTypes.end_of_conversation, code="SkillError", text=f"{error}" + ) + await context.send_activity(end_of_conversation) + except Exception as exception: + print( + f"\n Exception caught on on_error : {exception}", file=sys.stderr, + ) + traceback.print_exc() + + +ADAPTER.on_turn_error = on_error + +# Create Bot +BOT = EchoBot() + +# Listen for incoming requests on /api/messages +async def messages(req: Request) -> Response: + # Main bot message handler. + if "application/json" in req.headers["Content-Type"]: + body = await req.json() + else: + return Response(status=HTTPStatus.UNSUPPORTED_MEDIA_TYPE) + + activity = Activity().deserialize(body) + auth_header = req.headers["Authorization"] if "Authorization" in req.headers else "" + + try: + response = await ADAPTER.process_activity(activity, auth_header, BOT.on_turn) + # DeliveryMode => Expected Replies + if response: + body = json.dumps(response.body) + return Response(status=response.status, body=body) + + # DeliveryMode => Normal + return Response(status=HTTPStatus.CREATED) + except Exception as exception: + raise exception + + +APP = web.Application() +APP.router.add_post("/api/messages", messages) + +# simple way of exposing the manifest for dev purposes. +APP.router.add_static("/manifests", "./manifests/") + + +if __name__ == "__main__": + try: + web.run_app(APP, host="localhost", port=CONFIG.PORT) + except Exception as error: + raise error diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/EchoSkillBot/authentication/__init__.py b/tests/functional/Bots/Python/Skills/CodeFirst/EchoSkillBot/authentication/__init__.py new file mode 100644 index 0000000000..b6383973c8 --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/EchoSkillBot/authentication/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .allowed_callers_claims_validator import AllowedCallersClaimsValidator + +__all__ = ["AllowedCallersClaimsValidator"] diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/EchoSkillBot/authentication/allowed_callers_claims_validator.py b/tests/functional/Bots/Python/Skills/CodeFirst/EchoSkillBot/authentication/allowed_callers_claims_validator.py new file mode 100644 index 0000000000..d790e36f34 --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/EchoSkillBot/authentication/allowed_callers_claims_validator.py @@ -0,0 +1,29 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from botframework.connector.auth import JwtTokenValidation, SkillValidation + + +class AllowedCallersClaimsValidator(): + """ + Sample claims validator that loads an allowed list from configuration if present and checks + that requests are coming from allowed parent bots. + """ + + def __init__(self, allowed_callers: frozenset): + self.allowed_callers = allowed_callers + + # Load the AppIds for the configured callers (we will only allow responses from parent bots we have configured). + # DefaultConfig.ALLOWED_CALLERS is the list of parent bot Ids that are allowed to access the skill. + # To add a new parent bot simply go to the config.py file and add + # the parent bot's Microsoft AppId to the array under AllowedCallers, e.g.: + # AllowedCallers=["195bd793-4319-4a84-a800-386770c058b2","38c74e7a-3d01-4295-8e66-43dd358920f8"] + async def validate_claims(self, claims: dict): + if SkillValidation.is_skill_claim(claims) and self.allowed_callers: + # Check that the appId claim in the skill request is in the list of skills configured for this bot. + app_id = JwtTokenValidation.get_app_id_from_claims(claims) + if app_id not in self.allowed_callers: + raise ValueError( + f'Received a request from an application with an appID of "{ app_id }". To enable requests' + ' from this bot, add the id to your configuration file.' + ) diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/EchoSkillBot/bots/__init__.py b/tests/functional/Bots/Python/Skills/CodeFirst/EchoSkillBot/bots/__init__.py new file mode 100644 index 0000000000..f95fbbbadd --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/EchoSkillBot/bots/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .echo_bot import EchoBot + +__all__ = ["EchoBot"] diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/EchoSkillBot/bots/echo_bot.py b/tests/functional/Bots/Python/Skills/CodeFirst/EchoSkillBot/bots/echo_bot.py new file mode 100644 index 0000000000..4c69deb366 --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/EchoSkillBot/bots/echo_bot.py @@ -0,0 +1,32 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from botbuilder.core import ActivityHandler, MessageFactory, TurnContext +from botbuilder.schema import Activity, ActivityTypes, EndOfConversationCodes + +class EchoBot(ActivityHandler): + async def on_message_activity(self, turn_context: TurnContext): + if "end" in turn_context.activity.text or "stop" in turn_context.activity.text: + # Send End of conversation at the end. + await turn_context.send_activity( + MessageFactory.text("Ending conversation from the skill...") + ) + + end_of_conversation = Activity(type=ActivityTypes.end_of_conversation) + end_of_conversation.code = EndOfConversationCodes.completed_successfully + await turn_context.send_activity(end_of_conversation) + else: + await turn_context.send_activity( + MessageFactory.text(f"Echo: {turn_context.activity.text}") + ) + await turn_context.send_activity( + MessageFactory.text( + f'Say "end" or "stop" and I\'ll end the conversation and back to the parent.' + ) + ) + + async def on_end_of_conversation_activity(self, turn_context: TurnContext): + # This will be called if the host bot is ending the conversation. Sending additional messages should be + # avoided as the conversation may have been deleted. + # Perform cleanup of resources if needed. + pass diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/EchoSkillBot/config.py b/tests/functional/Bots/Python/Skills/CodeFirst/EchoSkillBot/config.py new file mode 100644 index 0000000000..8de1e0a9b4 --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/EchoSkillBot/config.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import os + +""" Bot Configuration """ + +class DefaultConfig: + """ Bot Configuration """ + + PORT = 37400 + APP_ID = os.environ.get("MicrosoftAppId", "") + APP_PASSWORD = os.environ.get("MicrosoftAppPassword", "") + # If ALLOWED_CALLERS is empty, any bot can call this Skill. Add MicrosoftAppIds to restrict callers to only those specified. + # Example: os.environ.get("AllowedCallers", ["54d3bb6a-3b6d-4ccd-bbfd-cad5c72fb53a", "3851a47b-53ed-4d29-b878-6e941da61e98"]) + ALLOWED_CALLERS = os.environ.get("AllowedCallers", []) diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/EchoSkillBot/manifests/echoskillbot-manifest-1.0.json b/tests/functional/Bots/Python/Skills/CodeFirst/EchoSkillBot/manifests/echoskillbot-manifest-1.0.json new file mode 100644 index 0000000000..9e995c0101 --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/EchoSkillBot/manifests/echoskillbot-manifest-1.0.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://schemas.botframework.com/schemas/skills/v2.1/skill-manifest.json", + "$id": "EchoSkillBotPython", + "name": "EchoSkillBotPython", + "version": "1.0", + "description": "This is a skill for echoing what the user sent to the bot (using Python).", + "publisherName": "Microsoft", + "privacyUrl": "https://microsoft.com/privacy", + "copyright": "Copyright (c) Microsoft Corporation. All rights reserved.", + "license": "https://github.com/microsoft/BotFramework-FunctionalTests/blob/main/LICENSE", + "tags": [ + "echo" + ], + "endpoints": [ + { + "name": "default", + "protocol": "BotFrameworkV3", + "description": "Localhost endpoint for the skill (on port 37400)", + "endpointUrl": "http://localhost:37400/api/messages", + "msAppId": "00000000-0000-0000-0000-000000000000" + } + ] +} diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/EchoSkillBot/requirements.txt b/tests/functional/Bots/Python/Skills/CodeFirst/EchoSkillBot/requirements.txt new file mode 100644 index 0000000000..3658c09bb6 --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/EchoSkillBot/requirements.txt @@ -0,0 +1 @@ +botbuilder-integration-aiohttp>=4.13.0 diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/.env b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/.env new file mode 100644 index 0000000000..e83f3f1d74 --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/.env @@ -0,0 +1,11 @@ +MicrosoftAppId= +MicrosoftAppPassword= +ConnectionName=TestOAuthProvider +SsoConnectionName= +ChannelService= +AllowedCallers=* +SkillHostEndpoint=http://localhost:37420/api/skills + +EchoSkillInfo_id=EchoSkillBot +EchoSkillInfo_appId= +EchoSkillInfo_skillEndpoint=http://localhost:37400/api/messages diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/.pylintrc b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/.pylintrc new file mode 100644 index 0000000000..6e8d5976b6 --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/.pylintrc @@ -0,0 +1,582 @@ +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-whitelist= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Specify a configuration file. +#rcfile= + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=print-statement, + parameter-unpacking, + unpacking-in-except, + old-raise-syntax, + backtick, + long-suffix, + old-ne-operator, + old-octal-literal, + import-star-module-level, + non-ascii-bytes-literal, + raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead, + apply-builtin, + basestring-builtin, + buffer-builtin, + cmp-builtin, + coerce-builtin, + execfile-builtin, + file-builtin, + long-builtin, + raw_input-builtin, + reduce-builtin, + standarderror-builtin, + unicode-builtin, + xrange-builtin, + coerce-method, + delslice-method, + getslice-method, + setslice-method, + no-absolute-import, + old-division, + dict-iter-method, + dict-view-method, + next-method-called, + metaclass-assignment, + indexing-exception, + raising-string, + reload-builtin, + oct-method, + hex-method, + nonzero-method, + cmp-method, + input-builtin, + round-builtin, + intern-builtin, + unichr-builtin, + map-builtin-not-iterating, + zip-builtin-not-iterating, + range-builtin-not-iterating, + filter-builtin-not-iterating, + using-cmp-argument, + eq-without-hash, + div-method, + idiv-method, + rdiv-method, + exception-message-attribute, + invalid-str-codec, + sys-max-int, + bad-python3-import, + deprecated-string-function, + deprecated-str-translate-call, + deprecated-itertools-function, + deprecated-types-field, + next-method-defined, + dict-items-not-iterating, + dict-keys-not-iterating, + dict-values-not-iterating, + deprecated-operator-function, + deprecated-urllib-function, + xreadlines-attribute, + deprecated-sys-function, + exception-escape, + comprehension-escape, + bad-continuation, + duplicate-code, + redefined-outer-name, + missing-docstring, + too-many-instance-attributes, + too-few-public-methods, + redefined-builtin, + too-many-arguments, + no-self-use, + fixme, + broad-except, + bare-except, + too-many-public-methods, + cyclic-import, + too-many-locals, + too-many-function-args, + too-many-return-statements, + import-error, + no-name-in-module, + too-many-branches + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[REPORTS] + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +#msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit + + +[LOGGING] + +# Format style used to check logging format string. `old` means using % +# formatting, while `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: none. To make it working +# install python-enchant package.. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to indicated private dictionary in +# --spelling-private-dict-file option instead of raising a message. +spelling-store-unknown-words=no + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis. It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=120 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# List of optional constructs for which whitespace checking is disabled. `dict- +# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. +# `trailing-comma` allows a space between comma and closing bracket: (a, ). +# `empty-line` allows space-only lines. +no-space-check=trailing-comma, + dict-separator + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[SIMILARITIES] + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. +#class-attribute-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. +#variable-rgx= + + +[STRING] + +# This flag controls whether the implicit-str-concat-in-sequence should +# generate a warning on implicit string concatenation in sequences defined over +# several lines. +check-str-concat-over-line-jumps=no + + +[IMPORTS] + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules=optparse,tkinter.tix + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled). +ext-import-graph= + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled). +import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=cls + + +[DESIGN] + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement. +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "BaseException, Exception". +overgeneral-exceptions=BaseException, + Exception diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/app.py b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/app.py new file mode 100644 index 0000000000..354fe400a8 --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/app.py @@ -0,0 +1,176 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import os +from datetime import datetime +from http import HTTPStatus +from typing import Dict +from aiohttp import web +from aiohttp.web import Request, Response, json_response +from botbuilder.core import ( + BotFrameworkAdapterSettings, + ConversationState, + MemoryStorage, + TurnContext, +) +from botbuilder.core.skills import SkillHandler +from botbuilder.core.integration import ( + aiohttp_channel_service_routes, + aiohttp_error_middleware, +) +from botbuilder.integration.aiohttp.skills import SkillHttpClient +from botbuilder.schema import Activity +from botframework.connector.auth import ( + AuthenticationConfiguration, + SimpleCredentialProvider, +) +from authentication import AllowedCallersClaimsValidator +from bots import SkillBot +from config import DefaultConfig +from dialogs import ActivityRouterDialog +from dialogs.proactive import ContinuationParameters +from middleware import SsoSaveStateMiddleware +from skill_conversation_id_factory import SkillConversationIdFactory +from skill_adapter_with_error_handler import AdapterWithErrorHandler + +CONFIG = DefaultConfig() + +# Create MemoryStorage and ConversationState. +MEMORY = MemoryStorage() +CONVERSATION_STATE = ConversationState(MEMORY) + +# Create the conversationIdFactory. +CONVERSATION_ID_FACTORY = SkillConversationIdFactory(MEMORY) + +# Create the credential provider. +CREDENTIAL_PROVIDER = SimpleCredentialProvider(CONFIG.APP_ID, CONFIG.APP_PASSWORD) + +VALIDATOR = AllowedCallersClaimsValidator(CONFIG).claims_validator +AUTH_CONFIG = AuthenticationConfiguration(claims_validator=VALIDATOR) + +# Create adapter. +# See https://aka.ms/about-bot-adapter to learn more about how bots work. +SETTINGS = BotFrameworkAdapterSettings( + app_id=CONFIG.APP_ID, + app_password=CONFIG.APP_PASSWORD, + auth_configuration=AUTH_CONFIG, +) +ADAPTER = AdapterWithErrorHandler(SETTINGS, CONVERSATION_STATE) + +ADAPTER.use(SsoSaveStateMiddleware(CONVERSATION_STATE)) + +# Create the skill client. +SKILL_CLIENT = SkillHttpClient(CREDENTIAL_PROVIDER, CONVERSATION_ID_FACTORY) + +CONTINUATION_PARAMETERS_STORE: Dict[str, ContinuationParameters] = dict() + +# Create the main dialog. +DIALOG = ActivityRouterDialog( + configuration=CONFIG, + conversation_state=CONVERSATION_STATE, + conversation_id_factory=CONVERSATION_ID_FACTORY, + skill_client=SKILL_CLIENT, + continuation_parameters_store=CONTINUATION_PARAMETERS_STORE, +) + +# Create the bot that will handle incoming messages. +BOT = SkillBot(CONFIG, CONVERSATION_STATE, DIALOG) +SKILL_HANDLER = SkillHandler( + ADAPTER, BOT, CONVERSATION_ID_FACTORY, CREDENTIAL_PROVIDER, AUTH_CONFIG +) + +# Listen for incoming requests on /api/messages +async def messages(req: Request) -> Response: + # Main bot message handler. + if "application/json" in req.headers["Content-Type"]: + body = await req.json() + else: + return Response(status=HTTPStatus.UNSUPPORTED_MEDIA_TYPE) + + activity = Activity().deserialize(body) + auth_header = req.headers["Authorization"] if "Authorization" in req.headers else "" + + try: + website_hostname = os.getenv("WEBSITE_HOSTNAME") + if website_hostname: + CONFIG.SERVER_URL = f"https://{website_hostname}" + else: + CONFIG.SERVER_URL = f"{req.scheme}://{req.host}" + + response = await ADAPTER.process_activity(activity, auth_header, BOT.on_turn) + # DeliveryMode => Expected Replies + if response: + return json_response(data=response.body, status=response.status) + + # DeliveryMode => Normal + return Response(status=HTTPStatus.CREATED) + except Exception as exception: + raise exception + + +# Listen for incoming requests on /api/notify +async def notify(req: Request) -> Response: + error = "" + user = req.query.get("user") + + continuation_parameters = CONTINUATION_PARAMETERS_STORE.get(user) + + if not continuation_parameters: + return Response( + content_type="text/html", + status=HTTPStatus.OK, + body=f"

No messages sent

" + f"
There are no conversations registered to receive proactive messages for { user }.", + ) + + try: + + async def callback(context: TurnContext): + await context.send_activity(f"Got proactive message for user: { user }") + await BOT.on_turn(context) + + await ADAPTER.continue_conversation( + continuation_parameters.conversation_reference, + callback, + CONFIG.APP_ID, + continuation_parameters.claims_identity, + continuation_parameters.oauth_scope, + ) + except Exception as err: + error = err + + return Response( + content_type="text/html", + status=HTTPStatus.OK, + body=f"

Proactive messages have been sent

" + f"
Timestamp: { datetime.utcnow() }
Exception: { error }", + ) + + +# Listen for incoming requests on /api/music +async def music(req: Request) -> web.FileResponse: # pylint: disable=unused-argument + file_path = os.path.join(os.getcwd(), "dialogs/cards/files/music.mp3") + return web.FileResponse(file_path) + + +APP = web.Application(middlewares=[aiohttp_error_middleware]) +APP.router.add_post("/api/messages", messages) +APP.router.add_routes(aiohttp_channel_service_routes(SKILL_HANDLER, "/api/skills")) + +# Simple way of exposing the manifest for dev purposes. +APP.router.add_static("/manifests", "./manifests/") + +# Simple way of exposing images folder. +APP.router.add_static("/images", "./images/") + +# Listen for incoming requests. +APP.router.add_get("/api/music", music) + +# Listen for incoming notifications and send proactive messages to users. +APP.router.add_get("/api/notify", notify) + +if __name__ == "__main__": + try: + web.run_app(APP, host="localhost", port=CONFIG.PORT) + except Exception as error: + raise error diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/authentication/__init__.py b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/authentication/__init__.py new file mode 100644 index 0000000000..b6383973c8 --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/authentication/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .allowed_callers_claims_validator import AllowedCallersClaimsValidator + +__all__ = ["AllowedCallersClaimsValidator"] diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/authentication/allowed_callers_claims_validator.py b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/authentication/allowed_callers_claims_validator.py new file mode 100644 index 0000000000..b2ca42c61f --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/authentication/allowed_callers_claims_validator.py @@ -0,0 +1,50 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from typing import Awaitable, Callable, Dict, List +from botframework.connector.auth import JwtTokenValidation, SkillValidation +from config import DefaultConfig + + +class AllowedCallersClaimsValidator: + """ + Sample claims validator that loads an allowed list from configuration if present + and checks that requests are coming from allowed parent bots. + """ + + config_key = "ALLOWED_CALLERS" + + def __init__(self, config: DefaultConfig): + if not config: + raise TypeError( + "AllowedCallersClaimsValidator: config object cannot be None." + ) + + # AllowedCallers is the setting in the appsettings.json file + # that consists of the list of parent bot IDs that are allowed to access the skill. + # To add a new parent bot, simply edit the AllowedCallers and add + # the parent bot's Microsoft app ID to the list. + # In this sample, we allow all callers if AllowedCallers contains an "*". + caller_list = getattr(config, self.config_key) + if caller_list is None: + raise TypeError(f'"{self.config_key}" not found in configuration.') + self._allowed_callers = frozenset(caller_list) + + @property + def claims_validator(self) -> Callable[[List[Dict]], Awaitable]: + async def allow_callers_claims_validator(claims: Dict[str, object]): + # If _allowed_callers contains an "*", we allow all callers. + if "*" not in self._allowed_callers and SkillValidation.is_skill_claim( + claims + ): + # Check that the appId claim in the skill request is in the list of callers configured for this bot. + app_id = JwtTokenValidation.get_app_id_from_claims(claims) + if app_id not in self._allowed_callers: + raise PermissionError( + f'Received a request from a bot with an app ID of "{app_id}".' + f" To enable requests from this caller, add the app ID to your configuration file." + ) + + return + + return allow_callers_claims_validator diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/bots/__init__.py b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/bots/__init__.py new file mode 100644 index 0000000000..b58a3b0653 --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/bots/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .skill_bot import SkillBot + +__all__ = ["SkillBot"] diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/bots/skill_bot.py b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/bots/skill_bot.py new file mode 100644 index 0000000000..542423fd3a --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/bots/skill_bot.py @@ -0,0 +1,69 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from typing import List +from botbuilder.core import ( + ActivityHandler, + TurnContext, + ConversationState, +) +from botbuilder.dialogs import Dialog, DialogExtensions +from botbuilder.schema import Activity, ActivityTypes, ChannelAccount +from config import DefaultConfig + + +class SkillBot(ActivityHandler): + def __init__( + self, + config: DefaultConfig, + conversation_state: ConversationState, + dialog: Dialog, + ): + if config is None: + raise Exception("[SkillBot]: Missing parameter. config is required") + if conversation_state is None: + raise Exception( + "[SkillBot]: Missing parameter. conversation_state is required" + ) + if dialog is None: + raise Exception("[SkillBot]: Missing parameter. dialog is required") + + self.config = config + self.conversation_state = conversation_state + self.dialog = dialog + + async def on_turn(self, turn_context: TurnContext): + if turn_context.activity.type == ActivityTypes.conversation_update: + await super().on_turn(turn_context) + + else: + await DialogExtensions.run_dialog( + self.dialog, + turn_context, + self.conversation_state.create_property("DialogState"), + ) + + # Save any state changes that might have occurred during the turn. + await self.conversation_state.save_changes(turn_context) + + async def on_members_added_activity( + self, members_added: List[ChannelAccount], turn_context: TurnContext + ): + text = ( + "Welcome to the waterfall skill bot. \n\n" + "This is a skill, you will need to call it from another bot to use it." + ) + + for member in members_added: + if member.id != turn_context.activity.recipient.id: + await turn_context.send_activity( + Activity( + type=ActivityTypes.message, + text=text, + speak=text.replace("\n\n", ""), + ) + ) + await turn_context.send_activity( + f"You can check the skill manifest to see what it supports here: " + f"{self.config.SERVER_URL}/manifests/waterfallskillbot-manifest-1.0.json" + ) diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/config.py b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/config.py new file mode 100644 index 0000000000..c688cb431d --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/config.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import os +from botbuilder.core.skills import BotFrameworkSkill +from dotenv import load_dotenv + +load_dotenv() + + +class DefaultConfig: + """ + Bot Configuration + """ + + SERVER_URL = "" # pylint: disable=invalid-name + PORT = os.getenv("Port", "37420") + APP_ID = os.getenv("MicrosoftAppId") + APP_PASSWORD = os.getenv("MicrosoftAppPassword") + CONNECTION_NAME = os.getenv("ConnectionName") + SSO_CONNECTION_NAME = os.getenv("SsoConnectionName") + CHANNEL_SERVICE = os.getenv("ChannelService") + SKILL_HOST_ENDPOINT = os.getenv("SkillHostEndpoint") + # If ALLOWED_CALLERS is empty, any bot can call this Skill. + # Add MicrosoftAppIds to restrict callers to only those specified. + # Example: + # os.getenv("AllowedCallers", ["54d3bb6a-3b6d-4ccd-bbfd-cad5c72fb53a", "3851a47b-53ed-4d29-b878-6e941da61e98"]) + ALLOWED_CALLERS = os.getenv("AllowedCallers") + ECHO_SKILL_INFO = BotFrameworkSkill( + id=os.getenv("EchoSkillInfo_id"), + app_id=os.getenv("EchoSkillInfo_appId"), + skill_endpoint=os.getenv("EchoSkillInfo_skillEndpoint"), + ) diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/__init__.py b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/__init__.py new file mode 100644 index 0000000000..80d6165e54 --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .activity_router_dialog import ActivityRouterDialog + +__all__ = ["ActivityRouterDialog"] diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/activity_router_dialog.py b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/activity_router_dialog.py new file mode 100644 index 0000000000..1c38f4d7f2 --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/activity_router_dialog.py @@ -0,0 +1,166 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import json +from typing import Dict +from datetime import datetime + +from botbuilder.core import MessageFactory, ConversationState +from botbuilder.dialogs import ( + WaterfallDialog, + WaterfallStepContext, + DialogTurnResult, + DialogTurnStatus, + ComponentDialog, +) +from botbuilder.dialogs.skills import ( + SkillDialogOptions, + SkillDialog, + BeginSkillDialogOptions, +) +from botbuilder.schema import Activity, ActivityTypes, InputHints +from botbuilder.integration.aiohttp.skills import SkillHttpClient +from config import DefaultConfig +from skill_conversation_id_factory import SkillConversationIdFactory +from dialogs.cards import CardDialog +from dialogs.delete import DeleteDialog +from dialogs.proactive import WaitForProactiveDialog +from dialogs.message_with_attachment import MessageWithAttachmentDialog +from dialogs.auth import AuthDialog +from dialogs.sso import SsoSkillDialog +from dialogs.file_upload import FileUploadDialog +from dialogs.update import UpdateDialog + +ECHO_SKILL = "EchoSkill" + + +class ActivityRouterDialog(ComponentDialog): + def __init__( + self, + configuration: DefaultConfig, + conversation_state: ConversationState, + conversation_id_factory: SkillConversationIdFactory, + skill_client: SkillHttpClient, + continuation_parameters_store: Dict, + ): + super().__init__(ActivityRouterDialog.__name__) + + self.add_dialog(CardDialog(configuration)) + self.add_dialog(MessageWithAttachmentDialog(configuration)) + + self.add_dialog( + WaitForProactiveDialog(configuration, continuation_parameters_store) + ) + + self.add_dialog(AuthDialog(configuration)) + self.add_dialog(SsoSkillDialog(configuration)) + self.add_dialog(FileUploadDialog()) + self.add_dialog(DeleteDialog()) + self.add_dialog(UpdateDialog()) + + self.add_dialog( + self.create_echo_skill_dialog( + configuration, conversation_state, conversation_id_factory, skill_client + ) + ) + self.add_dialog( + WaterfallDialog(WaterfallDialog.__name__, [self.process_activity]) + ) + + self.initial_dialog_id = WaterfallDialog.__name__ + + def create_echo_skill_dialog( + self, + configuration: DefaultConfig, + conversation_state: ConversationState, + conversation_id_factory: SkillConversationIdFactory, + skill_client: SkillHttpClient, + ) -> SkillDialog: + if configuration.SKILL_HOST_ENDPOINT is None: + raise Exception("SkillHostEndpoint is not in configuration") + + if configuration.ECHO_SKILL_INFO is None: + raise Exception("EchoSkillInfo is not set in configuration") + + options = SkillDialogOptions( + bot_id=configuration.APP_ID, + conversation_id_factory=conversation_id_factory, + skill_client=skill_client, + skill_host_endpoint=configuration.SKILL_HOST_ENDPOINT, + conversation_state=conversation_state, + skill=configuration.ECHO_SKILL_INFO, + ) + + return SkillDialog(options, ECHO_SKILL) + + async def process_activity(self, step_context: WaterfallStepContext): + # A skill can send trace activities, if needed. + await step_context.context.send_activity( + Activity( + type=ActivityTypes.trace, + timestamp=datetime.utcnow(), + name="ActivityRouterDialog.process_activity()", + label=f"Got ActivityType: {step_context.context.activity.type}", + ) + ) + + if step_context.context.activity.type == ActivityTypes.event: + return await self.on_event_activity(step_context) + + # We didn't get an activity type we can handle. + await step_context.context.send_activity( + activity_or_text=f'Unrecognized ActivityType: "{step_context.context.activity.type}".', + input_hint=InputHints.ignoring_input, + ) + return DialogTurnResult(DialogTurnStatus.Complete) + + async def on_event_activity(self, step_context: WaterfallStepContext): + activity = step_context.context.activity + await step_context.context.send_activity( + Activity( + type=ActivityTypes.trace, + timestamp=datetime.utcnow(), + name="ActivityRouterDialog.on_event_activity()", + label=f"Name: {activity.name}. Value: {json.dumps(activity.value)}", + ) + ) + + if activity.name == "Cards": + return await step_context.begin_dialog(CardDialog.__name__) + + if activity.name == "Proactive": + return await step_context.begin_dialog(WaitForProactiveDialog.__name__) + + if activity.name == "MessageWithAttachment": + return await step_context.begin_dialog(MessageWithAttachmentDialog.__name__) + + if activity.name == "Auth": + return await step_context.begin_dialog(AuthDialog.__name__) + + if activity.name == "Sso": + return await step_context.begin_dialog(SsoSkillDialog.__name__) + + if activity.name == "FileUpload": + return await step_context.begin_dialog(FileUploadDialog.__name__) + + if activity.name == "Echo": + # Start the EchoSkillBot + message_activity = MessageFactory.text("I'm the echo skill bot") + message_activity.delivery_mode = activity.delivery_mode + dialog = await self.find_dialog(ECHO_SKILL) + return await step_context.begin_dialog( + dialog.id, BeginSkillDialogOptions(activity=message_activity) + ) + + if activity.name == "Delete": + return await step_context.begin_dialog(DeleteDialog.__name__) + + if activity.name == "Update": + return await step_context.begin_dialog(UpdateDialog.__name__) + + # We didn't get an event name we can handle. + await step_context.context.send_activity( + activity_or_text=f'Unrecognized EventName: "{step_context.context.activity.name}".', + input_hint=InputHints.ignoring_input, + ) + return DialogTurnResult(DialogTurnStatus.Complete) diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/auth/__init__.py b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/auth/__init__.py new file mode 100644 index 0000000000..938f3d16ea --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/auth/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .auth_dialog import AuthDialog + +__all__ = ["AuthDialog"] diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/auth/auth_dialog.py b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/auth/auth_dialog.py new file mode 100644 index 0000000000..2957371462 --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/auth/auth_dialog.py @@ -0,0 +1,106 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from botbuilder.core import MessageFactory +from botbuilder.dialogs import ( + WaterfallDialog, + WaterfallStepContext, + PromptOptions, + ComponentDialog, +) +from botbuilder.dialogs.prompts import OAuthPrompt, OAuthPromptSettings, ConfirmPrompt +from botbuilder.schema import InputHints +from config import DefaultConfig + + +class AuthDialog(ComponentDialog): + def __init__(self, configuration: DefaultConfig): + super().__init__(AuthDialog.__name__) + + self.connection_name = configuration.CONNECTION_NAME + + self.add_dialog(ConfirmPrompt(ConfirmPrompt.__name__)) + self.add_dialog( + OAuthPrompt( + OAuthPrompt.__name__, + OAuthPromptSettings( + connection_name=self.connection_name, + text=f"Please Sign In to connection: '{self.connection_name}'", + title="Sign In", + timeout=300000, # User has 5 minutes to login (1000 * 60 * 5) + ), + ) + ) + + self.add_dialog( + WaterfallDialog( + WaterfallDialog.__name__, + [ + self.prompt_step, + self.login_step, + self.display_token, + ], + ) + ) + + # The initial child Dialog to run. + self.initial_dialog_id = WaterfallDialog.__name__ + + async def prompt_step(self, step_context: WaterfallStepContext): + return await step_context.begin_dialog(OAuthPrompt.__name__) + + async def login_step(self, step_context: WaterfallStepContext): + # Get the token from the previous step. + token_response = step_context.result + + if token_response: + # Workaround, step_context.result value using DirectLine returns a 'dict' instead of TokenResponse + if isinstance(token_response, dict): + step_context.values["token"] = token_response.get("token") + else: + step_context.values["token"] = token_response.token + + # Show the token + logged_in_message = "You are now logged in." + await step_context.context.send_activity( + MessageFactory.text( + logged_in_message, logged_in_message, InputHints.ignoring_input + ) + ) + + options = PromptOptions( + prompt=MessageFactory.text("Would you like to view your token?") + ) + return await step_context.prompt(ConfirmPrompt.__name__, options) + + try_again_message = "Login was not successful please try again." + await step_context.context.send_activity( + MessageFactory.text( + try_again_message, try_again_message, InputHints.ignoring_input + ) + ) + return await step_context.replace_dialog(self.initial_dialog_id) + + async def display_token(self, step_context: WaterfallStepContext): + if step_context.result: + show_token_message = "Here is your token:" + await step_context.context.send_activity( + MessageFactory.text( + f'{show_token_message} {step_context.values["token"]}', + show_token_message, + InputHints.ignoring_input, + ) + ) + + # Sign out + await step_context.context.adapter.sign_out_user( + step_context.context, self.connection_name + ) + sign_out_message = "I have signed you out." + await step_context.context.send_activity( + MessageFactory.text( + sign_out_message, sign_out_message, InputHints.ignoring_input + ) + ) + + return await step_context.end_dialog() diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/__init__.py b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/__init__.py new file mode 100644 index 0000000000..465fe91fee --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/__init__.py @@ -0,0 +1,9 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .card_dialog import CardDialog +from .card_options import CardOptions +from .card_sample_helper import CardSampleHelper +from .channel_supported_cards import ChannelSupportedCards + +__all__ = ["CardDialog", "CardOptions", "CardSampleHelper", "ChannelSupportedCards"] diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/card_dialog.py b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/card_dialog.py new file mode 100644 index 0000000000..519bc77fb7 --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/card_dialog.py @@ -0,0 +1,282 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import json + +from botbuilder.core import MessageFactory, CardFactory +from botbuilder.dialogs import ( + ComponentDialog, + DialogTurnResult, + DialogTurnStatus, + WaterfallDialog, + WaterfallStepContext, + Choice, + ListStyle, +) +from botbuilder.dialogs.prompts import ( + ChoicePrompt, + PromptOptions, + PromptValidatorContext, +) +from botbuilder.schema import ( + InputHints, + HeroCard, + CardAction, + ActionTypes, +) +from config import DefaultConfig +from dialogs.cards.card_options import CardOptions +from dialogs.cards.channel_supported_cards import ChannelSupportedCards +from dialogs.cards.card_sample_helper import CardSampleHelper + + +CORGI_ON_CAROUSEL_VIDEO = "https://www.youtube.com/watch?v=LvqzubPZjHE" +MIND_BLOWN_GIF = ( + "https://media3.giphy.com/media/xT0xeJpnrWC4XWblEk/giphy.gif?" + "cid=ecf05e47mye7k75sup6tcmadoom8p1q8u03a7g2p3f76upp9&rid=giphy.gif" +) +MUSIC_API = "api/music" +TEAMS_LOGO_FILE_NAME = "teams-logo.png" + + +class CardDialog(ComponentDialog): + def __init__(self, configuration: DefaultConfig): + super().__init__(CardDialog.__name__) + self.configuration = configuration + + self.add_dialog(ChoicePrompt(ChoicePrompt.__name__, self.card_prompt_validator)) + self.add_dialog( + WaterfallDialog( + WaterfallDialog.__name__, + [self.select_card_step, self.display_card_step], + ) + ) + + self.initial_dialog_id = WaterfallDialog.__name__ + + def make_update_hero_card(self, step_context: WaterfallStepContext): + hero_card = HeroCard(title="Newly updated card.", buttons=[]) + + data = step_context.context.activity.value + data["count"] = data["count"].value + 1 + hero_card.text = f"Update count - {data['count'].value}" + + hero_card.buttons.push( + CardAction( + type=ActionTypes.message_back, + title="Update Card", + text="UpdateCardAction", + value=data, + ) + ) + + return CardFactory.hero_card(hero_card) + + async def select_card_step(self, step_context: WaterfallStepContext): + # Create the PromptOptions from the skill configuration which contain the list of configured skills. + message_text = "What card do you want?" + reprompt_message_text = "This message will be created in the validation code" + + options = PromptOptions( + prompt=MessageFactory.text( + message_text, message_text, InputHints.expecting_input + ), + retry_prompt=MessageFactory.text( + reprompt_message_text, reprompt_message_text, InputHints.expecting_input + ), + choices=[Choice(card.value) for card in CardOptions], + style=ListStyle.list_style, + ) + + return await step_context.prompt(ChoicePrompt.__name__, options) + + async def display_card_step(self, step_context: WaterfallStepContext): + if step_context.context.activity.value is not None: + await self.handle_special_activity(step_context) + else: + # Check to see if the activity is an adaptive card or a bot action response + card_type = CardOptions(step_context.result.value) + + if ChannelSupportedCards.is_card_supported( + step_context.context.activity.channel_id, card_type + ): + if card_type == CardOptions.ADAPTIVE_CARD_BOT_ACTION: + await step_context.context.send_activity( + MessageFactory.attachment( + CardSampleHelper.create_adaptive_card_bot_action() + ) + ) + + elif card_type == CardOptions.ADAPTIVE_CARD_TEAMS_TASK_MODULE: + await step_context.context.send_activity( + MessageFactory.attachment( + CardSampleHelper.create_adaptive_card_task_module() + ) + ) + + elif card_type == CardOptions.ADAPTIVE_CARD_SUBMIT_ACTION: + await step_context.context.send_activity( + MessageFactory.attachment( + CardSampleHelper.create_adaptive_card_submit() + ) + ) + + elif card_type == CardOptions.HERO: + await step_context.context.send_activity( + MessageFactory.attachment(CardSampleHelper.create_hero_card()) + ) + + elif card_type == CardOptions.THUMBNAIL: + await step_context.context.send_activity( + MessageFactory.attachment( + CardSampleHelper.create_thumbnail_card() + ) + ) + + elif card_type == CardOptions.RECEIPT: + await step_context.context.send_activity( + MessageFactory.attachment( + CardSampleHelper.create_receipt_card() + ) + ) + + elif card_type == CardOptions.SIGN_IN: + await step_context.context.send_activity( + MessageFactory.attachment(CardSampleHelper.create_signin_card()) + ) + + elif card_type == CardOptions.CAROUSEL: + # NOTE if cards are NOT the same height in a carousel, + # Teams will instead display as AttachmentLayoutTypes.List + await step_context.context.send_activity( + MessageFactory.carousel( + [ + CardSampleHelper.create_hero_card(), + CardSampleHelper.create_hero_card(), + CardSampleHelper.create_hero_card(), + ] + ) + ) + + elif card_type == CardOptions.LIST: + await step_context.context.send_activity( + MessageFactory.list( + [ + CardSampleHelper.create_hero_card(), + CardSampleHelper.create_hero_card(), + CardSampleHelper.create_hero_card(), + ] + ) + ) + + elif card_type == CardOptions.O365: + await step_context.context.send_activity( + MessageFactory.attachment( + CardSampleHelper.create_o365_connector_card() + ) + ) + + elif card_type == CardOptions.TEAMS_FILE_CONSENT: + await step_context.context.send_activity( + MessageFactory.attachment( + CardSampleHelper.create_teams_file_consent_card( + TEAMS_LOGO_FILE_NAME + ) + ) + ) + + elif card_type == CardOptions.ANIMATION: + await step_context.context.send_activity( + MessageFactory.attachment( + CardSampleHelper.create_animation_card(MIND_BLOWN_GIF) + ) + ) + + elif card_type == CardOptions.AUDIO: + await step_context.context.send_activity( + MessageFactory.attachment( + CardSampleHelper.create_audio_card( + f"{self.configuration.SERVER_URL}/{MUSIC_API}" + ) + ) + ) + + elif card_type == CardOptions.VIDEO: + await step_context.context.send_activity( + MessageFactory.attachment( + CardSampleHelper.create_video_card(CORGI_ON_CAROUSEL_VIDEO) + ) + ) + + elif card_type == CardOptions.ADAPTIVE_UPDATE: + await step_context.context.send_activity( + MessageFactory.attachment( + CardSampleHelper.create_adaptive_update_card() + ) + ) + + elif card_type == CardOptions.END: + return DialogTurnResult(DialogTurnStatus.Complete) + + else: + await step_context.context.send_activity( + f"{card_type.value} cards are not supported in the " + f"{step_context.context.activity.channel_id} channel." + ) + + return await step_context.replace_dialog( + self.initial_dialog_id, "What card would you want?" + ) + + async def handle_special_activity(self, step_context: WaterfallStepContext): + if step_context.context.activity.text is None: + await step_context.context.send_activity( + MessageFactory.text( + f"I received an activity with this data in the value field {step_context.context.activity.value}" + ) + ) + else: + if "update" in step_context.context.activity.text.lower(): + if step_context.context.activity.reply_to_id is None: + await step_context.context.send_activity( + MessageFactory.text( + f"Update activity is not supported in the " + f"{step_context.context.activity.channel_id} channel" + ) + ) + else: + hero_card = self.make_update_hero_card(step_context) + + activity = MessageFactory.attachment(hero_card) + activity.id = step_context.context.activity.reply_to_id + + await step_context.context.update_activity(activity) + + else: + await step_context.context.send_activity( + MessageFactory.text( + f"I received an activity with this data in the text field {step_context.context.activity.text} " + f"and this data in the value field {step_context.context.activity.value}" + ) + ) + + @staticmethod + async def card_prompt_validator(prompt_context: PromptValidatorContext) -> bool: + if not prompt_context.recognized.succeeded: + # This checks to see if this response is the user clicking the update button on the card + if prompt_context.context.activity.value is not None: + return True + + if prompt_context.context.activity.attachments: + return True + + # Render the activity so we can assert in tests. + # We may need to simplify the json if it gets too complicated to test. + activity_json = json.dumps( + prompt_context.context.activity.__dict__, indent=4, default=str + ).replace("\n", "\r\n") + prompt_context.options.retry_prompt.text = ( + f"Got {activity_json}\n\n{prompt_context.options.prompt.text}" + ) + return False + return True diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/card_options.py b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/card_options.py new file mode 100644 index 0000000000..99b98e5b66 --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/card_options.py @@ -0,0 +1,23 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from enum import Enum + + +class CardOptions(str, Enum): + ADAPTIVE_CARD_BOT_ACTION = "AdaptiveCardBotAction" + ADAPTIVE_CARD_TEAMS_TASK_MODULE = "AdaptiveCardTeamsTaskModule" + ADAPTIVE_CARD_SUBMIT_ACTION = "AdaptiveCardSubmitAction" + HERO = "Hero" + THUMBNAIL = "Thumbnail" + RECEIPT = "Receipt" + SIGN_IN = "Signin" + CAROUSEL = "Carousel" + LIST = "List" + O365 = "O365" + TEAMS_FILE_CONSENT = "TeamsFileConsent" + ANIMATION = "Animation" + AUDIO = "Audio" + VIDEO = "Video" + ADAPTIVE_UPDATE = "AdaptiveUpdate" + END = "End" diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/card_sample_helper.py b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/card_sample_helper.py new file mode 100644 index 0000000000..8ef19fe7ae --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/card_sample_helper.py @@ -0,0 +1,557 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import os +from botbuilder.core import CardFactory +from botbuilder.schema.teams import ( + FileConsentCard, + O365ConnectorCard, + O365ConnectorCardActionCard, + O365ConnectorCardDateInput, + O365ConnectorCardFact, + O365ConnectorCardHttpPOST, + O365ConnectorCardImage, + O365ConnectorCardMultichoiceInput, + O365ConnectorCardMultichoiceInputChoice, + O365ConnectorCardOpenUri, + O365ConnectorCardOpenUriTarget, + O365ConnectorCardSection, + O365ConnectorCardTextInput, + O365ConnectorCardViewAction, +) +from botbuilder.schema import ( + ActionTypes, + Attachment, + AnimationCard, + AudioCard, + HeroCard, + VideoCard, + ReceiptCard, + SigninCard, + ThumbnailCard, + MediaUrl, + CardAction, + CardImage, + Fact, + ReceiptItem, +) +from botbuilder.schema.teams.additional_properties import ContentType + + +class CardSampleHelper: + @staticmethod + def create_adaptive_card_bot_action(): + return CardFactory.adaptive_card( + { + "type": "AdaptiveCard", + "version": "1.2", + "body": [ + { + "text": "Bot Builder actions", + "type": "TextBlock", + } + ], + "actions": [ + { + "type": "Action.Submit", + "title": "imBack", + "data": { + "msteams": { + "type": ActionTypes.im_back.value, + "value": "text", + } + }, + }, + { + "type": "Action.Submit", + "title": "message back", + "data": { + "msteams": { + "type": ActionTypes.message_back.value, + "value": {"key": "value"}, + } + }, + }, + { + "type": "Action.Submit", + "title": "message back local echo", + "data": { + "msteams": { + "type": ActionTypes.message_back.value, + "text": "text received by bots", + "displayText": "display text message back", + "value": {"key": "value"}, + } + }, + }, + { + "type": "Action.Submit", + "title": "invoke", + "data": { + "msteams": {"type": "invoke", "value": {"key": "value"}} + }, + }, + ], + } + ) + + @staticmethod + def create_adaptive_card_task_module(): + return CardFactory.adaptive_card( + { + "type": "AdaptiveCard", + "version": "1.2", + "body": [ + { + "type": "TextBlock", + "text": "Task Module Adaptive Card", + } + ], + "actions": [ + { + "type": "Action.Submit", + "title": "Launch Task Module", + "data": { + "msteams": { + "type": "invoke", + "value": '{\r\n "hiddenKey": ' + '"hidden value from task module launcher",\r\n "type": "task/fetch"\r\n}', + } + }, + } + ], + } + ) + + @staticmethod + def create_adaptive_card_submit(): + return CardFactory.adaptive_card( + { + "type": "AdaptiveCard", + "version": "1.2", + "body": [ + { + "type": "TextBlock", + "text": "Bot Builder actions", + }, + { + "type": "Input.Text", + "id": "x", + }, + ], + "actions": [ + { + "type": "Action.Submit", + "title": "Action.Submit", + "data": { + "key": "value", + }, + } + ], + } + ) + + @staticmethod + def create_adaptive_update_card() -> Attachment: + card = HeroCard(title="Update card", text="Update Card Action", buttons=[]) + + action = CardAction( + type=ActionTypes.message_back, + title="Update card title", + text="Update card text", + value={"count": 0}, + ) + + card.buttons.push(action) + + return CardFactory.hero_card(card) + + @staticmethod + def create_hero_card() -> Attachment: + card = HeroCard( + title="BotFramework Hero Card", + subtitle="Microsoft Bot Framework", + text="Build and connect intelligent bots to interact with your users naturally wherever they are, " + "from text/sms to Skype, Slack, Office 365 mail and other popular services.", + images=[ + CardImage( + url="https://sec.ch9.ms/ch9/7ff5/e07cfef0-aa3b-40bb-9baa-7c9ef8ff7ff5/" + "buildreactionbotframework_960.jpg", + ) + ], + buttons=[ + CardAction( + type=ActionTypes.open_url, + title="Get Started", + value="https://docs.microsoft.com/bot-framework", + ) + ], + ) + return CardFactory.hero_card(card) + + @staticmethod + def create_thumbnail_card() -> Attachment: + card = ThumbnailCard( + title="BotFramework Thumbnail Card", + subtitle="Microsoft Bot Framework", + text="Build and connect intelligent bots to interact with your users naturally wherever they are, " + "from text/sms to Skype, Slack, Office 365 mail and other popular services.", + images=[ + CardImage( + url="https://sec.ch9.ms/ch9/7ff5/e07cfef0-aa3b-40bb-9baa-7c9ef8ff7ff5/" + "buildreactionbotframework_960.jpg", + ) + ], + buttons=[ + CardAction( + type=ActionTypes.open_url, + title="Get Started", + value="https://docs.microsoft.com/bot-framework", + ) + ], + ) + return CardFactory.thumbnail_card(card) + + @staticmethod + def create_receipt_card() -> Attachment: + card = ReceiptCard( + title="John Doe", + facts=[ + Fact( + key="Order Number", + value="1234", + ), + Fact( + key="Payment Method", + value="VISA 5555-****", + ), + ], + items=[ + ReceiptItem( + title="Data Transfer", + image=CardImage( + url="https://github.com/amido/azure-vector-icons/raw/master/renders/traffic-manager.png", + ), + price="$ 38.45", + quantity="368", + ), + ReceiptItem( + title="App Service", + image=CardImage( + url="https://github.com/amido/azure-vector-icons/raw/master/renders/cloud-service.png", + ), + price="$ 45.00", + quantity="720", + ), + ], + total="$ 90.95", + tax="$ 7.50", + buttons=[ + CardAction( + type=ActionTypes.open_url, + title="More information", + image="https://account.windowsazure.com/content/6.10.1.38-.8225.160809-1618/aux-pre/" + "images/offer-icon-freetrial.png", + value="https://azure.microsoft.com/en-us/pricing/", + ) + ], + ) + return CardFactory.receipt_card(card) + + @staticmethod + def create_signin_card() -> Attachment: + card = SigninCard( + text="BotFramework Sign-in Card", + buttons=[ + CardAction( + type=ActionTypes.signin, + title="Sign-in", + value="https://login.microsoftonline.com/", + ) + ], + ) + + return CardFactory.signin_card(card) + + @staticmethod + def create_o365_connector_card() -> Attachment: + section = O365ConnectorCardSection( + title="**section title**", + text="section text", + activity_title="activity title", + activity_subtitle="activity subtitle", + activity_text="activity text", + activity_image="http://connectorsdemo.azurewebsites.net/images/MSC12_Oscar_002.jpg", + activity_image_type="avatar", + markdown=True, + facts=[ + O365ConnectorCardFact(name="Fact name 1", value="Fact value 1"), + O365ConnectorCardFact(name="Fact name 2", value="Fact value 2"), + ], + images=[ + O365ConnectorCardImage( + image="http://connectorsdemo.azurewebsites.net/images/" + "MicrosoftSurface_024_Cafe_OH-06315_VS_R1c.jpg", + title="image 1", + ), + O365ConnectorCardImage( + image="http://connectorsdemo.azurewebsites.net/images/WIN12_Scene_01.jpg", + title="image 2", + ), + O365ConnectorCardImage( + image="http://connectorsdemo.azurewebsites.net/images/WIN12_Anthony_02.jpg", + title="image 3", + ), + ], + ) + + action_card1 = O365ConnectorCardActionCard( + type="ActionCard", + name="Multiple Choice", + id="card-1", + inputs=[ + O365ConnectorCardMultichoiceInput( + type="MultichoiceInput", + id="list-1", + is_required=True, + title="Pick multiple options", + choices=[ + O365ConnectorCardMultichoiceInputChoice( + display="Choice 1", value="1" + ), + O365ConnectorCardMultichoiceInputChoice( + display="Choice 2", value="2" + ), + O365ConnectorCardMultichoiceInputChoice( + display="Choice 3", value="3" + ), + ], + style="expanded", + is_multi_select=True, + ), + O365ConnectorCardMultichoiceInput( + type="MultichoiceInput", + id="list-2", + is_required=True, + title="Pick multiple options", + choices=[ + O365ConnectorCardMultichoiceInputChoice( + display="Choice 4", value="4" + ), + O365ConnectorCardMultichoiceInputChoice( + display="Choice 5", value="5" + ), + O365ConnectorCardMultichoiceInputChoice( + display="Choice 6", value="6" + ), + ], + style="compact", + is_multi_select=True, + ), + O365ConnectorCardMultichoiceInput( + type="MultichoiceInput", + id="list-3", + is_required=False, + title="Pick an option", + choices=[ + O365ConnectorCardMultichoiceInputChoice( + display="Choice a", value="a" + ), + O365ConnectorCardMultichoiceInputChoice( + display="Choice b", value="b" + ), + O365ConnectorCardMultichoiceInputChoice( + display="Choice c", value="c" + ), + ], + style="expanded", + is_multi_select=False, + ), + O365ConnectorCardMultichoiceInput( + type="MultichoiceInput", + id="list-4", + is_required=False, + title="Pick an option", + choices=[ + O365ConnectorCardMultichoiceInputChoice( + display="Choice x", value="x" + ), + O365ConnectorCardMultichoiceInputChoice( + display="Choice y", value="y" + ), + O365ConnectorCardMultichoiceInputChoice( + display="Choice z", value="z" + ), + ], + style="compact", + is_multi_select=False, + ), + ], + actions=[ + O365ConnectorCardHttpPOST( + type="HttpPOST", + name="Send", + id="card-1-btn-1", + body='{"list1":"{{list-1.value}}", "list2":"{{list-2.value}}", ' + '"list3":"{{list-3.value}}", "list4":"{{list-4.value}}"}', + ) + ], + ) + + action_card2 = O365ConnectorCardActionCard( + type="ActionCard", + name="Text Input", + id="card-2", + inputs=[ + O365ConnectorCardTextInput( + type="TextInput", + id="text-1", + is_required=False, + title="multiline, no maxLength", + is_multiline=True, + ), + O365ConnectorCardTextInput( + type="TextInput", + id="text-2", + is_required=False, + title="single line, no maxLength", + is_multiline=False, + ), + O365ConnectorCardTextInput( + type="TextInput", + id="text-3", + is_required=True, + title="multiline, max len = 10, isRequired", + is_multiline=True, + max_length=10, + ), + O365ConnectorCardTextInput( + type="TextInput", + id="text-4", + is_required=True, + title="single line, max len = 10, isRequired", + is_multiline=False, + max_length=10, + ), + ], + actions=[ + O365ConnectorCardHttpPOST( + type="HttpPOST", + name="Send", + id="card-2-btn-1", + body='{"text1":"{{text-1.value}}", "text2":"{{text-2.value}}", ' + '"text3":"{{text-3.value}}", "text4":"{{text-4.value}}"}', + ) + ], + ) + + action_card3 = O365ConnectorCardActionCard( + type="ActionCard", + name="Date Input", + id="card-3", + inputs=[ + O365ConnectorCardDateInput( + type="DateInput", + id="date-1", + is_required=True, + title="date with time", + include_time=True, + ), + O365ConnectorCardDateInput( + type="DateInput", + id="date-2", + is_required=False, + title="date only", + include_time=False, + ), + ], + actions=[ + O365ConnectorCardHttpPOST( + type="HttpPOST", + name="Send", + id="card-3-btn-1", + body='{"date1":"{{date-1.value}}", "date2":"{{date-2.value}}"}', + ) + ], + ) + + card = O365ConnectorCard( + summary="O365 card summary", + theme_color="#E67A9E", + title="card title", + text="card text", + sections=[section], + potential_action=[ + action_card1, + action_card2, + action_card3, + O365ConnectorCardViewAction( + type="ViewAction", + name="View Action", + target=["http://microsoft.com"], + ), + O365ConnectorCardOpenUri( + type="OpenUri", + name="Open Uri", + id="open-uri", + targets=[ + O365ConnectorCardOpenUriTarget( + os="default", uri="http://microsoft.com" + ), + O365ConnectorCardOpenUriTarget( + os="iOS", uri="http://microsoft.com" + ), + O365ConnectorCardOpenUriTarget( + os="android", uri="http://microsoft.com" + ), + O365ConnectorCardOpenUriTarget( + os="windows", uri="http://microsoft.com" + ), + ], + ), + ], + ) + + return Attachment(content=card, content_type=ContentType.O365_CONNECTOR_CARD) + + @staticmethod + def create_teams_file_consent_card(file_name: str): + file_path = os.path.join(os.getcwd(), "Dialogs/Cards/Files", file_name) + file_size = os.path.getsize(file_path) + + consent_context = {{"filename", file_name}} + + file_card = FileConsentCard( + description="This is the file I want to send you", + size_in_bytes=file_size, + accept_context=consent_context, + decline_context=consent_context, + ) + + return Attachment( + content=file_card, + content_type=ContentType.FILE_CONSENT_CARD, + name=file_name, + ) + + @staticmethod + def create_animation_card(url: str) -> Attachment: + card = AnimationCard( + title="Animation Card", + media=[MediaUrl(url=url)], + autostart=True, + ) + return CardFactory.animation_card(card) + + @staticmethod + def create_audio_card(url: str) -> Attachment: + card = AudioCard(title="Audio Card", media=[MediaUrl(url=url)], autoloop=True) + return CardFactory.audio_card(card) + + @staticmethod + def create_video_card(url: str) -> Attachment: + card = VideoCard( + title="Video Card", + media=[MediaUrl(url=url)], + ) + return CardFactory.video_card(card) diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/channel_supported_cards.py b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/channel_supported_cards.py new file mode 100644 index 0000000000..13cbffa194 --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/channel_supported_cards.py @@ -0,0 +1,33 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from botframework.connector import Channels + +from dialogs.cards.card_options import CardOptions + + +UNSUPPORTED_CHANNEL_CARDS = { + Channels.emulator.value: [ + CardOptions.ADAPTIVE_CARD_TEAMS_TASK_MODULE, + CardOptions.ADAPTIVE_UPDATE, + CardOptions.O365, + CardOptions.TEAMS_FILE_CONSENT, + ], + Channels.direct_line.value: [CardOptions.ADAPTIVE_UPDATE], + Channels.telegram.value: [ + CardOptions.ADAPTIVE_CARD_BOT_ACTION, + CardOptions.ADAPTIVE_CARD_TEAMS_TASK_MODULE, + CardOptions.ADAPTIVE_CARD_SUBMIT_ACTION, + CardOptions.LIST, + CardOptions.TEAMS_FILE_CONSENT, + ], +} + + +class ChannelSupportedCards: + @staticmethod + def is_card_supported(channel: str, card_type: CardOptions): + if channel in UNSUPPORTED_CHANNEL_CARDS: + if card_type in UNSUPPORTED_CHANNEL_CARDS[channel]: + return False + return True diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/files/buildreactionbotframework.jpg b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/files/buildreactionbotframework.jpg new file mode 100644 index 0000000000..f410fc137e Binary files /dev/null and b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/files/buildreactionbotframework.jpg differ diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/files/music.mp3 b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/files/music.mp3 new file mode 100644 index 0000000000..b4ff6ee30f Binary files /dev/null and b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/files/music.mp3 differ diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/files/teams-logo.png b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/files/teams-logo.png new file mode 100644 index 0000000000..78b0a0c308 Binary files /dev/null and b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/cards/files/teams-logo.png differ diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/delete/__init__.py b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/delete/__init__.py new file mode 100644 index 0000000000..414664e1eb --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/delete/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .delete_dialog import DeleteDialog + +__all__ = ["DeleteDialog"] diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/delete/delete_dialog.py b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/delete/delete_dialog.py new file mode 100644 index 0000000000..216701f750 --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/delete/delete_dialog.py @@ -0,0 +1,44 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import time +from botbuilder.core import MessageFactory +from botbuilder.dialogs import ( + ComponentDialog, + WaterfallDialog, + WaterfallStepContext, + DialogTurnResult, + DialogTurnStatus, +) +from botframework.connector import Channels + + +class DeleteDialog(ComponentDialog): + def __init__(self): + super().__init__(DeleteDialog.__name__) + self._delete_supported = [Channels.ms_teams, Channels.slack, Channels.telegram] + + self.add_dialog( + WaterfallDialog(WaterfallDialog.__name__, [self.handle_delete_dialog_step]) + ) + + self.initial_dialog_id = WaterfallDialog.__name__ + + async def handle_delete_dialog_step(self, step_context: WaterfallStepContext): + channel = step_context.context.activity.channel_id + + if channel in self._delete_supported: + response = await step_context.context.send_activity( + MessageFactory.text("I will delete this message in 5 seconds") + ) + time.sleep(5) + await step_context.context.delete_activity(response.id) + + else: + await step_context.context.send_activity( + MessageFactory.text( + f"Delete is not supported in the {channel} channel." + ) + ) + + return DialogTurnResult(DialogTurnStatus.Complete) diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/file_upload/__init__.py b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/file_upload/__init__.py new file mode 100644 index 0000000000..b665b303db --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/file_upload/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .file_upload_dialog import FileUploadDialog + +__all__ = ["FileUploadDialog"] diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/file_upload/file_upload_dialog.py b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/file_upload/file_upload_dialog.py new file mode 100644 index 0000000000..374a1906a6 --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/file_upload/file_upload_dialog.py @@ -0,0 +1,86 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import tempfile +import os +import urllib.request +import shutil +from botbuilder.core import MessageFactory +from botbuilder.dialogs import ( + ComponentDialog, + DialogTurnResult, + DialogTurnStatus, + WaterfallDialog, + WaterfallStepContext, + ConfirmPrompt, +) +from botbuilder.dialogs.prompts import PromptOptions, AttachmentPrompt +from botbuilder.schema import InputHints + + +class FileUploadDialog(ComponentDialog): + def __init__(self): + super().__init__(FileUploadDialog.__name__) + + self.add_dialog(AttachmentPrompt(AttachmentPrompt.__name__)) + self.add_dialog(ConfirmPrompt(ConfirmPrompt.__name__)) + self.add_dialog( + WaterfallDialog( + WaterfallDialog.__name__, + [self.prompt_upload_step, self.handle_attachment_step, self.final_step], + ) + ) + + self.initial_dialog_id = WaterfallDialog.__name__ + + async def prompt_upload_step(self, step_context: WaterfallStepContext): + return await step_context.prompt( + AttachmentPrompt.__name__, + PromptOptions( + prompt=MessageFactory.text( + "Please upload a file to continue.", input_hint=InputHints.accepting_input + ), + retry_prompt=MessageFactory.text("You must upload a file."), + ), + ) + + async def handle_attachment_step(self, step_context: WaterfallStepContext): + file_text = "" + file_content = "" + + for file in step_context.context.activity.attachments: + remote_file_url = file.content_url + local_file_name = os.path.join(tempfile.gettempdir(), file.name) + with urllib.request.urlopen(remote_file_url) as response, open( + local_file_name, "wb" + ) as out_file: + shutil.copyfileobj(response, out_file) + + file_content = open(local_file_name, "r").read() + file_text += f'Attachment "{ file.name }" has been received.\r\n' + file_text += f'File content: { file_content }\r\n' + + await step_context.context.send_activity(MessageFactory.text(file_text)) + + # Ask to upload another file or end. + message_text = "Do you want to upload another file?" + reprompt_message_text = "That's an invalid choice." + + options = PromptOptions( + prompt=MessageFactory.text( + message_text, message_text, InputHints.expecting_input + ), + retry_prompt=MessageFactory.text( + reprompt_message_text, reprompt_message_text, InputHints.expecting_input + ), + ) + + return await step_context.prompt(ConfirmPrompt.__name__, options) + + async def final_step(self, step_context: WaterfallStepContext): + try_another = step_context.result + + if try_another: + return await step_context.replace_dialog(self.initial_dialog_id) + + return DialogTurnResult(DialogTurnStatus.Complete) diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/message_with_attachment/__init__.py b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/message_with_attachment/__init__.py new file mode 100644 index 0000000000..efe3140792 --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/message_with_attachment/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .message_with_attachment_dialog import MessageWithAttachmentDialog + +__all__ = ["MessageWithAttachmentDialog"] diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/message_with_attachment/message_with_attachment_dialog.py b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/message_with_attachment/message_with_attachment_dialog.py new file mode 100644 index 0000000000..419fc3cbe4 --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/message_with_attachment/message_with_attachment_dialog.py @@ -0,0 +1,125 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import os +import base64 +from botbuilder.core import MessageFactory +from botbuilder.dialogs import ( + ComponentDialog, + ConfirmPrompt, + ChoicePrompt, + DialogTurnResult, + DialogTurnStatus, + WaterfallDialog, + WaterfallStepContext, + Choice, +) +from botbuilder.dialogs.prompts import PromptOptions +from botbuilder.schema import ( + Attachment, + InputHints +) + +from config import DefaultConfig + + +class MessageWithAttachmentDialog(ComponentDialog): + def __init__(self, configuration: DefaultConfig): + super().__init__(MessageWithAttachmentDialog.__name__) + + self._picture = "architecture-resize.png" + self.configuration = configuration + + self.add_dialog(ChoicePrompt(ChoicePrompt.__name__)) + self.add_dialog(ConfirmPrompt(ConfirmPrompt.__name__)) + self.add_dialog( + WaterfallDialog( + WaterfallDialog.__name__, + [ + self.select_attachment_type_step, + self.send_activity_with_attachment_step, + self.final_step, + ], + ) + ) + + self.initial_dialog_id = WaterfallDialog.__name__ + + async def select_attachment_type_step(self, step_context: WaterfallStepContext): + # Create the PromptOptions from the skill configuration which contain the list of configured skills. + message_text = "What attachment type do you want?" + reprompt_message_text = ( + "That was not a valid choice, please select a valid card type." + ) + + options = PromptOptions( + prompt=MessageFactory.text( + message_text, message_text, InputHints.expecting_input + ), + retry_prompt=MessageFactory.text( + reprompt_message_text, reprompt_message_text, InputHints.expecting_input + ), + choices=[Choice("Inline"), Choice("Internet")], + ) + + return await step_context.prompt(ChoicePrompt.__name__, options) + + async def send_activity_with_attachment_step( + self, step_context: WaterfallStepContext + ): + attachment_type = str(step_context.result.value).lower() + reply = MessageFactory.text("", input_hint=InputHints.ignoring_input) + + if attachment_type == "inline": + reply.text = "This is an inline attachment." + reply.attachments = [await self.get_inline_attachment()] + + elif attachment_type == "internet": + reply.text = "This is an attachment from a HTTP URL." + reply.attachments = [await self.get_internet_attachment()] + + else: + raise TypeError(f"Invalid card type {attachment_type}") + + await step_context.context.send_activity(reply) + + message_text = "Do you want another type of attachment?" + reprompt_message_text = "That's an invalid choice." + + options = PromptOptions( + prompt=MessageFactory.text( + message_text, message_text, InputHints.expecting_input + ), + retry_prompt=MessageFactory.text( + reprompt_message_text, reprompt_message_text, InputHints.expecting_input + ), + ) + + return await step_context.prompt(ConfirmPrompt.__name__, options) + + async def final_step(self, step_context: WaterfallStepContext): + try_another = step_context.result + + if try_another: + return await step_context.replace_dialog(self.initial_dialog_id) + + return DialogTurnResult(DialogTurnStatus.Complete) + + async def get_inline_attachment(self): + file_path = os.path.join(os.getcwd(), "images", self._picture) + + with open(file_path, "rb") as in_file: + file = base64.b64encode(in_file.read()).decode() + + return Attachment( + name=f"Files/{ self._picture }", + content_type="image/png", + content_url=f"data:image/png;base64,{file}", + ) + + async def get_internet_attachment(self): + return Attachment( + name=f"Files/{ self._picture }", + content_type="image/png", + content_url=f"{self.configuration.SERVER_URL}/images/{self._picture}", + ) diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/proactive/__init__.py b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/proactive/__init__.py new file mode 100644 index 0000000000..9f8161fa73 --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/proactive/__init__.py @@ -0,0 +1,7 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .continuation_parameters import ContinuationParameters +from .wait_for_proactive_dialog import WaitForProactiveDialog + +__all__ = ["ContinuationParameters", "WaitForProactiveDialog"] diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/proactive/continuation_parameters.py b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/proactive/continuation_parameters.py new file mode 100644 index 0000000000..0ab78fb9e3 --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/proactive/continuation_parameters.py @@ -0,0 +1,16 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from botbuilder.schema import ConversationReference + + +class ContinuationParameters: + def __init__( + self, + claims_identity: str, + oauth_scope: str, + conversation_reference: ConversationReference, + ): + self.claims_identity = claims_identity + self.oauth_scope = oauth_scope + self.conversation_reference = conversation_reference diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/proactive/wait_for_proactive_dialog.py b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/proactive/wait_for_proactive_dialog.py new file mode 100644 index 0000000000..845295d6dd --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/proactive/wait_for_proactive_dialog.py @@ -0,0 +1,77 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from typing import Dict +from botbuilder.core import MessageFactory, BotAdapter, TurnContext +from botbuilder.dialogs import Dialog, DialogContext, DialogTurnResult, DialogTurnStatus +from botbuilder.schema import ( + ActivityTypes, + ActivityEventNames +) +from config import DefaultConfig +from .continuation_parameters import ContinuationParameters + + +class WaitForProactiveDialog(Dialog): + def __init__( + self, + configuration: DefaultConfig, + continuation_parameters_store: Dict[str, ContinuationParameters], + ): + super().__init__(WaitForProactiveDialog.__name__) + self.configuration = configuration + self.continuation_parameters_store = continuation_parameters_store + + def notify_message(self, url: str, user_id: str): + return f"Navigate to { url }/api/notify?user={ user_id } to proactively message the user." + + async def begin_dialog(self, dialog_context: DialogContext, options: object = None): + # Store a reference to the conversation. + self.add_or_update_continuation_parameters(dialog_context.context) + + # Render message with continuation link. + await dialog_context.context.send_activity( + MessageFactory.text( + self.notify_message( + self.configuration.SERVER_URL, + dialog_context.context.activity.from_property.id, + ) + ) + ) + return Dialog.end_of_turn + + async def continue_dialog(self, dialog_context: DialogContext): + activity = dialog_context.context.activity + if ( + activity.type == ActivityTypes.event + and activity.name == ActivityEventNames.continue_conversation + ): + # We continued the conversation, forget the proactive reference. + self.continuation_parameters_store[activity.id] = None + + # The continue conversation activity comes from the ProactiveController when the notification is received + await dialog_context.context.send_activity( + "We received a proactive message, ending the dialog" + ) + + # End the dialog so the host gets an EoC + return DialogTurnResult(DialogTurnStatus.Complete) + + # Keep waiting for a call to the ProactiveController. + await dialog_context.context.send_activity( + f"We are waiting for a proactive message. " + f"{self.notify_message(self.configuration, activity.from_property.id)}" + ) + + return Dialog.end_of_turn + + def add_or_update_continuation_parameters(self, context: TurnContext): + self.continuation_parameters_store[ + context.activity.from_property.id + ] = ContinuationParameters( + claims_identity=context.turn_state.get(BotAdapter.BOT_IDENTITY_KEY), + conversation_reference=TurnContext.get_conversation_reference( + context.activity + ), + oauth_scope=context.turn_state.get(BotAdapter.BOT_OAUTH_SCOPE_KEY), + ) diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/sso/__init__.py b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/sso/__init__.py new file mode 100644 index 0000000000..fc79378126 --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/sso/__init__.py @@ -0,0 +1,7 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .sso_skill_dialog import SsoSkillDialog +from .sso_skill_signin_dialog import SsoSkillSignInDialog + +__all__ = ["SsoSkillDialog", "SsoSkillSignInDialog"] diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/sso/sso_skill_dialog.py b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/sso/sso_skill_dialog.py new file mode 100644 index 0000000000..042c00c0e8 --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/sso/sso_skill_dialog.py @@ -0,0 +1,112 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from botbuilder.core import MessageFactory +from botbuilder.dialogs import ( + DialogTurnResult, + DialogTurnStatus, + ChoicePrompt, + PromptOptions, + WaterfallDialog, + WaterfallStepContext, + ComponentDialog, + Choice, +) +from botbuilder.schema import InputHints +from config import DefaultConfig +from .sso_skill_signin_dialog import SsoSkillSignInDialog + + +class SsoSkillDialog(ComponentDialog): + def __init__(self, configuration: DefaultConfig): + super().__init__(SsoSkillDialog.__name__) + + self.connection_name = configuration.SSO_CONNECTION_NAME + + self.add_dialog(SsoSkillSignInDialog(self.connection_name)) + self.add_dialog(ChoicePrompt(ChoicePrompt.__name__)) + + self.add_dialog( + WaterfallDialog( + WaterfallDialog.__name__, + [ + self.prompt_action_step, + self.handle_action_step, + self.prompt_final_step, + ], + ) + ) + + self.initial_dialog_id = WaterfallDialog.__name__ + + async def prompt_action_step(self, step_context: WaterfallStepContext): + message_text = "What SSO action would you like to perform on the skill?" + reprompt_message_text = ( + "That was not a valid choice, please select a valid choice." + ) + + options = PromptOptions( + prompt=MessageFactory.text( + message_text, message_text, InputHints.expecting_input + ), + retry_prompt=MessageFactory.text( + reprompt_message_text, reprompt_message_text, InputHints.expecting_input + ), + choices=await self.get_prompt_choices(step_context), + ) + + return await step_context.prompt(ChoicePrompt.__name__, options) + + async def get_prompt_choices(self, step_context: WaterfallStepContext): + choices = list() + token = await step_context.context.adapter.get_user_token( + step_context.context, self.connection_name + ) + + if token is None: + choices.append(Choice("Login")) + + else: + choices.append(Choice("Logout")) + choices.append(Choice("Show token")) + + choices.append(Choice("End")) + + return choices + + async def handle_action_step(self, step_context: WaterfallStepContext): + action = str(step_context.result.value).lower() + + if action == "login": + return await step_context.begin_dialog(SsoSkillSignInDialog.__name__) + + if action == "logout": + await step_context.context.adapter.sign_out_user( + step_context.context, self.connection_name + ) + await step_context.context.send_activity("You have been signed out.") + return await step_context.next(None) + + if action == "show token": + token = await step_context.context.adapter.get_user_token( + step_context.context, self.connection_name + ) + + if token is None: + await step_context.context.send_activity("User has no cached token.") + else: + await step_context.context.send_activity( + f"Here is your current SSO token: { token.token }" + ) + + return await step_context.next(None) + + if action == "end": + return DialogTurnResult(DialogTurnStatus.Complete) + + # This should never be hit since the previous prompt validates the choice. + raise Exception(f"Unrecognized action: { action }") + + async def prompt_final_step(self, step_context: WaterfallStepContext): + # Restart the dialog (we will exit when the user says end). + return await step_context.replace_dialog(self.initial_dialog_id) diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/sso/sso_skill_signin_dialog.py b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/sso/sso_skill_signin_dialog.py new file mode 100644 index 0000000000..4d974f4703 --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/sso/sso_skill_signin_dialog.py @@ -0,0 +1,56 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from botbuilder.dialogs import ( + ComponentDialog, + OAuthPrompt, + OAuthPromptSettings, + WaterfallDialog, + WaterfallStepContext, +) + + +class SsoSkillSignInDialog(ComponentDialog): + def __init__(self, connection_name: str): + super().__init__(SsoSkillSignInDialog.__name__) + + self.add_dialog( + OAuthPrompt( + OAuthPrompt.__name__, + OAuthPromptSettings( + connection_name=connection_name, + text="Sign in to the Skill using AAD", + title="Sign In", + ), + ) + ) + + self.add_dialog( + WaterfallDialog( + WaterfallDialog.__name__, [self.signin_step, self.display_token] + ) + ) + + self.initial_dialog_id = WaterfallDialog.__name__ + + async def signin_step(self, step_context: WaterfallStepContext): + return await step_context.begin_dialog(OAuthPrompt.__name__) + + async def display_token(self, step_context: WaterfallStepContext): + sso_token = step_context.result + if sso_token: + if isinstance(sso_token, dict): + token = sso_token.get("token") + else: + token = sso_token.token + + await step_context.context.send_activity( + f"Here is your token for the skill: {token}" + ) + + else: + await step_context.context.send_activity( + "No token was provided for the skill." + ) + + return await step_context.end_dialog() diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/update/__init__.py b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/update/__init__.py new file mode 100644 index 0000000000..3f01584aaa --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/update/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .update_dialog import UpdateDialog + +__all__ = ["UpdateDialog"] diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/update/update_dialog.py b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/update/update_dialog.py new file mode 100644 index 0000000000..30c4907d04 --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/dialogs/update/update_dialog.py @@ -0,0 +1,90 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from typing import Dict + +from botbuilder.core import MessageFactory +from botbuilder.dialogs import ( + ComponentDialog, + ConfirmPrompt, + WaterfallDialog, + WaterfallStepContext, + DialogTurnResult, + DialogTurnStatus, + PromptOptions, +) +from botframework.connector import Channels + + +class UpdateDialog(ComponentDialog): + def __init__(self): + super().__init__(UpdateDialog.__name__) + + self._update_supported = [Channels.ms_teams, Channels.slack, Channels.telegram] + + self._update_tracker: Dict[str, (str, int)] = {} + + self.add_dialog(ConfirmPrompt(ConfirmPrompt.__name__)) + self.add_dialog( + WaterfallDialog( + WaterfallDialog.__name__, + [self.handle_update_dialog_step, self.final_step], + ) + ) + + self.initial_dialog_id = WaterfallDialog.__name__ + + async def handle_update_dialog_step(self, step_context: WaterfallStepContext): + channel = step_context.context.activity.channel_id + if channel in self._update_supported: + if step_context.context.activity.conversation.id in self._update_tracker: + conversation_id = step_context.context.activity.conversation.id + tracked_tuple = self._update_tracker[conversation_id] + activity = MessageFactory.text( + f"This message has been updated {tracked_tuple.item2} time(s)." + ) + + tracked_tuple.item2 += 1 + activity.id = tracked_tuple.item1 + self._update_tracker[conversation_id] = tracked_tuple + + await step_context.context.update_activity(activity) + + else: + response = await step_context.context.send_activity( + MessageFactory.text("Here is the original activity.") + ) + self._update_tracker[step_context.context.activity.conversation.id] = ( + response.id, + 1, + ) + + else: + await step_context.context.send_activity( + MessageFactory.text( + f"Delete is not supported in the {channel} channel." + ) + ) + + return DialogTurnResult(DialogTurnStatus.Complete) + + # Ask if we want to update the activity again. + message_text = "Do you want to update the activity again?" + reprompt_message_text = "Please select a valid answer" + options = PromptOptions( + prompt=MessageFactory.text(message_text, message_text), + retry_prompt=MessageFactory.text( + reprompt_message_text, reprompt_message_text + ), + ) + + return await step_context.prompt(ConfirmPrompt.__name__, options) + + async def final_step(self, step_context: WaterfallStepContext): + try_another = step_context.result + if try_another: + return await step_context.replace_dialog(self.initial_dialog_id) + + self._update_tracker.pop(step_context.context.activity.conversation.id) + + return DialogTurnResult(DialogTurnStatus.Complete) diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/images/architecture-resize.png b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/images/architecture-resize.png new file mode 100644 index 0000000000..e65f0f7332 Binary files /dev/null and b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/images/architecture-resize.png differ diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/manifests/waterfallskillbot-manifest-1.0.json b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/manifests/waterfallskillbot-manifest-1.0.json new file mode 100644 index 0000000000..92544e7621 --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/manifests/waterfallskillbot-manifest-1.0.json @@ -0,0 +1,105 @@ +{ + "$schema": "https://schemas.botframework.com/schemas/skills/v2.1/skill-manifest.json", + "$id": "WaterfallSkillBotPython", + "name": "WaterfallSkillBotPython", + "version": "1.0", + "description": "This is a skill definition for multiple activity types (implemented using waterfall dialogs).", + "publisherName": "Microsoft", + "privacyUrl": "https://microsoft.com/privacy", + "copyright": "Copyright (c) Microsoft Corporation. All rights reserved.", + "license": "https://github.com/microsoft/BotFramework-FunctionalTests/blob/main/LICENSE", + "tags": [ + "travel", + "weather", + "luis" + ], + "endpoints": [ + { + "name": "default", + "protocol": "BotFrameworkV3", + "description": "Localhost endpoint for the skill (on port 37420)", + "endpointUrl": "http://localhost:37420/api/messages", + "msAppId": "00000000-0000-0000-0000-000000000000" + } + ], + "activities": { + "bookFlight": { + "description": "Books a flight (multi turn).", + "type": "event", + "name": "BookFlight", + "value": { + "$ref": "#/definitions/bookingInfo" + }, + "resultValue": { + "$ref": "#/definitions/bookingInfo" + } + }, + "getWeather": { + "description": "Retrieves and returns the weather for the user's location.", + "type": "event", + "name": "GetWeather", + "value": { + "$ref": "#/definitions/location" + }, + "resultValue": { + "$ref": "#/definitions/weatherReport" + } + }, + "passthroughMessage": { + "type": "message", + "description": "Receives the user's utterance and attempts to resolve it using the skill's LUIS models.", + "value": { + "type": "object" + } + } + }, + "definitions": { + "bookingInfo": { + "type": "object", + "required": [ + "origin" + ], + "properties": { + "origin": { + "type": "string", + "description": "This is the origin city for the flight." + }, + "destination": { + "type": "string", + "description": "This is the destination city for the flight." + }, + "travelDate": { + "type": "string", + "description": "The date for the flight in YYYY-MM-DD format." + } + } + }, + "weatherReport": { + "type": "array", + "description": "Array of forecasts for the next week.", + "items": [ + { + "type": "string" + } + ] + }, + "location": { + "type": "object", + "description": "Location metadata.", + "properties": { + "latitude": { + "type": "number", + "title": "Latitude" + }, + "longitude": { + "type": "number", + "title": "Longitude" + }, + "postalCode": { + "type": "string", + "title": "Postal code" + } + } + } + } +} diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/middleware/__init__.py b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/middleware/__init__.py new file mode 100644 index 0000000000..00162b84e1 --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/middleware/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .sso_save_state_middleware import SsoSaveStateMiddleware + +__all__ = ["SsoSaveStateMiddleware"] diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/middleware/sso_save_state_middleware.py b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/middleware/sso_save_state_middleware.py new file mode 100644 index 0000000000..b3645acdce --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/middleware/sso_save_state_middleware.py @@ -0,0 +1,30 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from typing import Awaitable, Callable, List +from botbuilder.core import Middleware, TurnContext, ConversationState, CardFactory +from botbuilder.schema import Activity + + +class SsoSaveStateMiddleware(Middleware): + def __init__(self, conversation_state: ConversationState): + self.conversation_state = conversation_state + + async def on_turn( + self, context: TurnContext, logic: Callable[[TurnContext], Awaitable] + ): + # Register outgoing handler. + context.on_send_activities(self._outgoing_handler) + return await logic() + + async def _outgoing_handler( + self, context: TurnContext, activities: List[Activity], next: Callable + ): + for activity in activities: + if activity.attachments is not None and any( + x.content_type == CardFactory.content_types.oauth_card + for x in activity.attachments + ): + await self.conversation_state.save_changes(context) + + return await next() diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/requirements.txt b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/requirements.txt new file mode 100644 index 0000000000..47a60840f5 --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/requirements.txt @@ -0,0 +1,3 @@ +botbuilder-integration-aiohttp>=4.13.0 +botbuilder-dialogs>=4.13.0 +python-dotenv diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/skill_adapter_with_error_handler.py b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/skill_adapter_with_error_handler.py new file mode 100644 index 0000000000..ab2e30a88e --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/skill_adapter_with_error_handler.py @@ -0,0 +1,103 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import sys +import traceback +from botbuilder.core import ( + BotFrameworkAdapter, + BotFrameworkAdapterSettings, + ConversationState, + MessageFactory, + TurnContext, +) +from botbuilder.schema import Activity, ActivityTypes, InputHints + + +class AdapterWithErrorHandler(BotFrameworkAdapter): + def __init__( + self, + settings: BotFrameworkAdapterSettings, + conversation_state: ConversationState, + ): + super().__init__(settings) + self.conversation_state = conversation_state + self.on_turn_error = self._handle_turn_error + + async def _handle_turn_error(self, context: TurnContext, error: Exception): + # This check writes out errors to console log + # NOTE: In production environment, you should consider logging this to Azure + # application insights. + print(f"\n [on_turn_error] unhandled error: {error}", file=sys.stderr) + traceback.print_exc() + await self._send_error_message(context, error) + await self._send_eoc_to_parent(context, error) + await self._clear_conversation_state(context) + + async def _send_error_message(self, context: TurnContext, error: Exception): + try: + exc_info = sys.exc_info() + stack = traceback.format_exception(*exc_info) + + # Send a message to the user. + error_message_text = "The skill encountered an error or bug." + error_message = MessageFactory.text( + f"{error_message_text}\r\n{error}\r\n{stack}", + error_message_text, + InputHints.ignoring_input, + ) + error_message.value = { "message": error, "stack": stack } + await context.send_activity(error_message) + + error_message_text = ( + "To continue to run this bot, please fix the bot source code." + ) + error_message = MessageFactory.text( + error_message_text, error_message_text, InputHints.ignoring_input + ) + await context.send_activity(error_message) + + # Send a trace activity, which will be displayed in the BotFramework Emulator. + # Note: we return the entire exception in the value property to help the developer; + # this should not be done in production. + await context.send_trace_activity( + label="TurnError", + name="on_turn_error Trace", + value=f"{error}", + value_type="https://www.botframework.com/schemas/error", + ) + except Exception as exception: + print( + f"\n Exception caught on _send_error_message : {exception}", + file=sys.stderr, + ) + traceback.print_exc() + + async def _send_eoc_to_parent(self, context: TurnContext, error: Exception): + try: + # Send an EndOfConversation activity to the skill caller with the error to end the conversation, + # and let the caller decide what to do. + end_of_conversation = Activity(type=ActivityTypes.end_of_conversation) + end_of_conversation.code = "SkillError" + end_of_conversation.text = str(error) + + await context.send_activity(end_of_conversation) + except Exception as exception: + print( + f"\n Exception caught on _send_eoc_to_parent : {exception}", + file=sys.stderr, + ) + traceback.print_exc() + + async def _clear_conversation_state(self, context: TurnContext): + if self.conversation_state: + try: + # Delete the conversationState for the current conversation to prevent the + # bot from getting stuck in a error-loop caused by being in a bad state. + # ConversationState should be thought of as similar to "cookie-state" for a Web page. + await self.conversation_state.delete(context) + except Exception as exception: + print( + f"\n Exception caught on _clear_conversation_state : {exception}", + file=sys.stderr, + ) + traceback.print_exc() diff --git a/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/skill_conversation_id_factory.py b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/skill_conversation_id_factory.py new file mode 100644 index 0000000000..e10942db14 --- /dev/null +++ b/tests/functional/Bots/Python/Skills/CodeFirst/WaterfallSkillBot/skill_conversation_id_factory.py @@ -0,0 +1,75 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from typing import Union +from botbuilder.core import Storage, TurnContext +from botbuilder.core.skills import ( + ConversationIdFactoryBase, + SkillConversationIdFactoryOptions, + SkillConversationReference, +) +from botbuilder.schema import ConversationReference + + +class SkillConversationIdFactory(ConversationIdFactoryBase): + def __init__(self, storage: Storage): + if not storage: + raise TypeError("storage can't be None") + + self._storage = storage + + async def create_skill_conversation_id( + self, + options_or_conversation_reference: Union[ + SkillConversationIdFactoryOptions, ConversationReference + ], + ) -> str: + if not options_or_conversation_reference: + raise TypeError("Need options or conversation reference") + + if not isinstance( + options_or_conversation_reference, SkillConversationIdFactoryOptions + ): + raise TypeError( + "This SkillConversationIdFactory can only handle SkillConversationIdFactoryOptions" + ) + + options = options_or_conversation_reference + + # Create the storage key based on the SkillConversationIdFactoryOptions. + conversation_reference = TurnContext.get_conversation_reference( + options.activity + ) + skill_conversation_id = ( + f"{conversation_reference.conversation.id}" + f"-{options.bot_framework_skill.id}" + f"-{conversation_reference.channel_id}" + f"-skillconvo" + ) + + # Create the SkillConversationReference instance. + skill_conversation_reference = SkillConversationReference( + conversation_reference=conversation_reference, + oauth_scope=options.from_bot_oauth_scope, + ) + + # Store the SkillConversationReference using the skill_conversation_id as a key. + skill_conversation_info = {skill_conversation_id: skill_conversation_reference} + await self._storage.write(skill_conversation_info) + + # Return the generated skill_conversation_id (that will be also used as the conversation ID to call the skill). + return skill_conversation_id + + async def get_conversation_reference( + self, skill_conversation_id: str + ) -> Union[SkillConversationReference, ConversationReference]: + if not skill_conversation_id: + raise TypeError("skill_conversation_id can't be None") + + # Get the SkillConversationReference from storage for the given skill_conversation_id. + skill_conversation_info = await self._storage.read([skill_conversation_id]) + + return skill_conversation_info.get(skill_conversation_id) + + async def delete_conversation_reference(self, skill_conversation_id: str): + await self._storage.delete([skill_conversation_id]) diff --git a/tests/functional/CODE_OF_CONDUCT.md b/tests/functional/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..f9ba8cf65f --- /dev/null +++ b/tests/functional/CODE_OF_CONDUCT.md @@ -0,0 +1,9 @@ +# Microsoft Open Source Code of Conduct + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). + +Resources: + +- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) +- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) +- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns diff --git a/tests/functional/Directory.Build.props b/tests/functional/Directory.Build.props new file mode 100644 index 0000000000..3e44d6df83 --- /dev/null +++ b/tests/functional/Directory.Build.props @@ -0,0 +1,34 @@ + + + + latest + + + + $(MSBuildThisFileDirectory)BotFramework-FunctionalTests.ruleset + + + + + + + Microsoft + + + + + + Microsoft + Microsoft Bot Framework SDK Functional Tests + © Microsoft Corporation. All rights reserved. + + + + https://github.com/microsoft/BotFramework-FunctionalTests + https://github.com/microsoft/BotFramework-FunctionalTests/blob/main/LICENSE + + en-US + true + + + diff --git a/tests/functional/Docs/README.md b/tests/functional/Docs/README.md new file mode 100644 index 0000000000..89cbf102ae --- /dev/null +++ b/tests/functional/Docs/README.md @@ -0,0 +1,12 @@ +# Index + +## Main documents + +- [Pipelines](./pipelines.md) + +## Guides + +- [Add a Service Connection to an Azure Resource Manager](./addARMServiceConnection.md) +- [Create WebApp Deployment credentials](./createWebAppDeploymentCredentials.md) +- [Get the Service Principal's Object ID](./getServicePrincipalObjectID.md) +- [Setup Pipelines](./setupPipelines.md) diff --git a/tests/functional/Docs/addARMServiceConnection.md b/tests/functional/Docs/addARMServiceConnection.md new file mode 100644 index 0000000000..5218a7937b --- /dev/null +++ b/tests/functional/Docs/addARMServiceConnection.md @@ -0,0 +1,24 @@ +# How to add a Service Connection to an Azure Resource Manager + +The following steps will guide you on how to add a Service Connection to an Azure Resource Manager in your DevOps organization. This is a required step to connect your Azure Subscription to the pipelines and deploy resources to Azure. + +**Note: The name you use for the resource will be used as `AzureSubscription` in the pipeline's variables.** + +Go to [Service connections](https://docs.microsoft.com/en-us/azure/devops/pipelines/library/service-endpoints?view=azure-devops&tabs=yaml) for more information. + +## Requirements + +- An [Azure DevOps Organization](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/create-organization?view=azure-devops). +- Access to an active [Azure Resource Manager](https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/overview). + +## Steps + +- From your Azure DevOps project, go to `Project settings -> Service Connections -> New Service connection`. + + ![addARMServiceConnection1](./media/addARMServiceConnection1.png) + +- Select `Azure Resource Manager -> Service principal (manual)` and fill out the form. Check the `Grant access permission to all pipelines` option. The name you use for the Subscription Name field will be your `AzureSubscription` variable in the pipelines. + + ![addARMServiceConnection2](./media/addARMServiceConnection2.png) + +- Click on `Verify and save` when done. diff --git a/tests/functional/Docs/createWebAppDeploymentCredentials.md b/tests/functional/Docs/createWebAppDeploymentCredentials.md new file mode 100644 index 0000000000..11075f1c2d --- /dev/null +++ b/tests/functional/Docs/createWebAppDeploymentCredentials.md @@ -0,0 +1,18 @@ +# How to create WebApp Deployment credentials + +The following steps will guide you on how to create WebApp Deployment credentials, required for Linux-based deployments. + +Go to [Configure deployment credentials for Azure App Service](https://docs.microsoft.com/en-us/azure/app-service/deploy-configure-credentials#in-the-cloud-shell) for more information. + +## Requirements + +- An active [Azure Subscription](https://azure.microsoft.com/en-us/free/). +- [az cli](https://docs.microsoft.com/en-us/cli/azure/) installed. + +## Steps + +- From a terminal where [az cli](https://docs.microsoft.com/en-us/cli/azure/) is accessible, run `az login` (in case you're not logged in), then execute the following command: + + `az webapp deployment user set --user-name [name] --password [password]` + + **Note: Special characters at the beginning of the user name will work when creating the credentials, but break later on. We advise to avoid special characters for these credentials altogether.** diff --git a/tests/functional/Docs/getServicePrincipalObjectID.md b/tests/functional/Docs/getServicePrincipalObjectID.md new file mode 100644 index 0000000000..d7173e142d --- /dev/null +++ b/tests/functional/Docs/getServicePrincipalObjectID.md @@ -0,0 +1,14 @@ +# Hot to get the Service Principal's Object ID + +The following steps will guide you on how to get the Service Principal's Object ID. + +## Requirements + +- Access to a [Service Principal](https://docs.microsoft.com/en-us/azure/active-directory/develop/app-objects-and-service-principals). + +## Steps + +- Go to `Azure subscription -> Access control -> Role assignments` and select the service principal used to configure the service connection. +- Copy the value of the `Object ID`. + + ![getServicePrincipalObjectID](./media/getServicePrincipalObjectID.png) diff --git a/tests/functional/Docs/media/CreateAppRegistrations.ps1 b/tests/functional/Docs/media/CreateAppRegistrations.ps1 new file mode 100644 index 0000000000..93ce20e028 --- /dev/null +++ b/tests/functional/Docs/media/CreateAppRegistrations.ps1 @@ -0,0 +1,39 @@ +Write-Host "You will be prompted to login into your Azure account to create App Registrations" +az login + +$defaultPrefix = "generic-app-registration" +$defaultAmount = 14 +$appRegistrations = @() +do { + $prefix = Read-Host -Prompt "App Registrations prefix ('$defaultPrefix' by default)" + if($prefix -eq ""){ + $prefix = $defaultPrefix + } + $amount = Read-Host -Prompt "Amount to create ($defaultAmount by default)" + if($amount -eq ""){ + $amount = $defaultAmount + } + + For ($index = 1; $index -le $amount; $index++) { + $name = "$prefix-$index" + Write-Host "Creating $name..." + $id = (az ad app create --display-name $name --available-to-other-tenants | ConvertFrom-Json).appId + $password = (az ad app credential reset --id $id | ConvertFrom-Json).password + $appRegistration = @{ + name = $name + id = $id + password = $password + } + Write-Host ($appRegistration | ConvertTo-Json) "`n" + $appRegistrations += $appRegistration + } +} while ((Read-Host -Prompt 'Batch completed. Create another batch? y/n').ToUpper() -eq "Y") + +if((Read-Host -Prompt "Output App Registrations to a file? y/n").ToUpper() -eq "Y"){ + $defaultPath = ".\appRegistrations.json" + $path = Read-Host -Prompt "Where? ('$defaultPath' by default)" + if($path -eq ""){ + $path = $defaultPath + } + $appRegistrations | ConvertTo-Json | Out-File $path +} diff --git a/tests/functional/Docs/media/addARMServiceConnection1.png b/tests/functional/Docs/media/addARMServiceConnection1.png new file mode 100644 index 0000000000..9265312902 Binary files /dev/null and b/tests/functional/Docs/media/addARMServiceConnection1.png differ diff --git a/tests/functional/Docs/media/addARMServiceConnection2.png b/tests/functional/Docs/media/addARMServiceConnection2.png new file mode 100644 index 0000000000..99b829d2ec Binary files /dev/null and b/tests/functional/Docs/media/addARMServiceConnection2.png differ diff --git a/tests/functional/Docs/media/getServicePrincipalObjectID.png b/tests/functional/Docs/media/getServicePrincipalObjectID.png new file mode 100644 index 0000000000..d4ae01ab8f Binary files /dev/null and b/tests/functional/Docs/media/getServicePrincipalObjectID.png differ diff --git a/tests/functional/Docs/media/setupAppRegistrations1.png b/tests/functional/Docs/media/setupAppRegistrations1.png new file mode 100644 index 0000000000..9ae7045552 Binary files /dev/null and b/tests/functional/Docs/media/setupAppRegistrations1.png differ diff --git a/tests/functional/Docs/media/setupAppRegistrations2.png b/tests/functional/Docs/media/setupAppRegistrations2.png new file mode 100644 index 0000000000..b6d15cad17 Binary files /dev/null and b/tests/functional/Docs/media/setupAppRegistrations2.png differ diff --git a/tests/functional/Docs/media/setupAppRegistrations3.png b/tests/functional/Docs/media/setupAppRegistrations3.png new file mode 100644 index 0000000000..be9ed13301 Binary files /dev/null and b/tests/functional/Docs/media/setupAppRegistrations3.png differ diff --git a/tests/functional/Docs/media/setupAppRegistrations4.png b/tests/functional/Docs/media/setupAppRegistrations4.png new file mode 100644 index 0000000000..db8a2951a9 Binary files /dev/null and b/tests/functional/Docs/media/setupAppRegistrations4.png differ diff --git a/tests/functional/Docs/media/setupAppRegistrations5.png b/tests/functional/Docs/media/setupAppRegistrations5.png new file mode 100644 index 0000000000..f5bc4dfb71 Binary files /dev/null and b/tests/functional/Docs/media/setupAppRegistrations5.png differ diff --git a/tests/functional/Docs/media/setupPipelines1.png b/tests/functional/Docs/media/setupPipelines1.png new file mode 100644 index 0000000000..8d991855e2 Binary files /dev/null and b/tests/functional/Docs/media/setupPipelines1.png differ diff --git a/tests/functional/Docs/media/setupPipelines2.png b/tests/functional/Docs/media/setupPipelines2.png new file mode 100644 index 0000000000..f8465e9783 Binary files /dev/null and b/tests/functional/Docs/media/setupPipelines2.png differ diff --git a/tests/functional/Docs/media/setupPipelines3.png b/tests/functional/Docs/media/setupPipelines3.png new file mode 100644 index 0000000000..3c40597486 Binary files /dev/null and b/tests/functional/Docs/media/setupPipelines3.png differ diff --git a/tests/functional/Docs/media/setupPipelines4.png b/tests/functional/Docs/media/setupPipelines4.png new file mode 100644 index 0000000000..1c17ed1b2c Binary files /dev/null and b/tests/functional/Docs/media/setupPipelines4.png differ diff --git a/tests/functional/Docs/media/setupPipelines5.png b/tests/functional/Docs/media/setupPipelines5.png new file mode 100644 index 0000000000..cea9352e99 Binary files /dev/null and b/tests/functional/Docs/media/setupPipelines5.png differ diff --git a/tests/functional/Docs/media/setupPipelines6.png b/tests/functional/Docs/media/setupPipelines6.png new file mode 100644 index 0000000000..07f561587d Binary files /dev/null and b/tests/functional/Docs/media/setupPipelines6.png differ diff --git a/tests/functional/Docs/pipelines.md b/tests/functional/Docs/pipelines.md new file mode 100644 index 0000000000..c5f2dbd3bd --- /dev/null +++ b/tests/functional/Docs/pipelines.md @@ -0,0 +1,129 @@ +# Pipelines + +## 01 - Create Shared Resources Pipeline + +- **Description**: Creates all the long-term resources required. +- **Schedule**: Quarterly or on demand. +- **YAML**: [build\yaml\sharedResources\createSharedResources.yml](../build/yaml/sharedResources/createSharedResources.yml) + +| Variable Name | Source | Description | +| - | - | - | +| **AzureSubscription** | Azure DevOps | Name of the Azure Resource Manager Service Connection configured in the DevOps organization. Click [here](./addARMServiceConnection.md) to see how to set it up. | +| **KeyVaultObjectId** | Azure | Suscription's Object Id to create the keyvault to store App Registrations in Azure. Click [here](./getServicePrincipalObjectID.md) to see how to get it. | +| **AppServicePlanPricingTier** | User | (optional) Pricing Tier for App Service Plans. **Default value is F1.** | +| **ResourceGroupName** | User | (optional) Name for the resource group that will contain the shared resources. | +| **ResourceSuffix** | User | (optional) Suffix to add to the resources' name to avoid collisions. | + +## 02 - Deploy Bot Resources Pipeline + +- **Description:** Creates the test bot resources to be used in the functional tests, separated in a Resource Group for each language (DotNet, JS, and Python) +- **Schedule**: Nightly or on-demand. +- **YAML**: [build\yaml\deployBotResources\deployBotResources.yml](../build/yaml/deployBotResources/deployBotResources.yml) + +| Variable Name | Source | Description | +| - | - | - | +| **AzureSubscription** | Azure DevOps | Name of the Azure Resource Manager Service Connection configured in the DevOps organization. Click [here](./addARMServiceConnection.md) to see how to set it up. | +| **AppServicePlanGroup** | Create Shared Resources | (optional) Name of the Resource Group where the Windows App Service Plan is located. | +| **AppServicePlanGroupLinux** | Create Shared Resources | (optional) Name of the Resource Group where the Linux App Service Plan is located. | +| **AppServicePlanDotNetName** | Create Shared Resources | (optional) Name of the DotNet App Service Plan. | +| **AppServicePlanJSName** | Create Shared Resources | (optional) Name of the JavaScript App Service Plan. | +| **AppServicePlanPythonName** | Create Shared Resources | (optional) Name of the Python App Service Plan. | +| **BotPricingTier** | User | (optional) Pricing tier for the Web App resources. ***Default value is F0.** | +| **ResourceGroup** | User | (optional) Name of the Resource Group where the bots will be deployed. | +| **ResourceSuffix** | Create Shared Resources | (optional) Suffix to add to the resources' name to avoid collisions. | +| **[BotName](#botnames) + AppId** | [App Registration Portal](https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade) | (optional) App ID to use. If not configured, will be retrieved from the key vault. | +| **[BotName](#botnames) + AppSecret** | [App Registration Portal](https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade) | (optional) App Secret to use. If not configured, will be retrieved from the key vault. | + +The following parameters will be displayed in the run pipeline blade. + +| Parameter Name | Source | Description | +| - | - | - | +| **[Language](#dependency-variables-language) Hosts Registry** | User | (optional) Source from which the Bot Builder dependencies will be downloaded for selected host bots. [**More info**](#dependency-variables-language) | +| **[Language](#dependency-variables-language) Skills Registry** | User | (optional) Source from which the Bot Builder dependencies will be downloaded for selected skill bots. [**More info**](#dependency-variables-language) | +| **[Language](#dependency-variables-language) Skills V3 Registry** | User | (optional) Source from which the Bot Builder dependencies will be downloaded for selected V3 skill bots. [**More info**](#dependency-variables-language) | +| **[Language](#dependency-variables-language) Hosts Version** | User | (optional) Bot Builder dependency version to use for selected host bots. **Possible values are: Latest (default), Stable, or specific version numbers.** | +| **[Language](#dependency-variables-language) Skills Version** | User | (optional) Bot Builder dependency version to use for selected skill bots. **Possible values are: Latest (default), Stable, or specific version numbers.** | +| **[Language](#dependency-variables-language) Skills V3 Version** | User | (optional) Bot Builder dependency version to use for selected V3 skill bots. **Possible values are: Latest (default), Stable, or specific version numbers.** | + +## 03 - Run Test Scenarios Pipeline + +- **Description:** Configures and executes the test scenarios. +- **Schedule**: Nightly (after Deploy Bot Resources) or on demand. +- **YAML**: [build\yaml\testScenarios\runTestScenarios.yml](../build/yaml/testScenarios/runTestScenarios.yml) + +| Variable Name | Source | Description | +| - | - | - | +| **AzureSubscription** | Azure DevOps | Name of the Azure Resource Manager Service Connection configured in the DevOps organization. Click [here](./addARMServiceConnection.md) to see how to set it up. | +| **ResourceGroup** | User | (optional) Name of the Resource Group where the bots are deployed. | +| **ResourceSuffix** | Create Shared Resources | (optional) Suffix to add to the resources' name to avoid collitions. | +| **[BotName](#botnames) + AppId** | [App Registration Portal](https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade) | (optional) App ID to use. If not configured, it will be retrieved from the key vault. | +| **ResourceSuffix** | Create Bot Resources | (optional) Deploy Bot Resources pipeline GUID. | + +## 04 - Cleanup Resources Pipeline + +- **Description:** Removes all resources, including all the shared resources, bots, and app registrations. +- **Schedule**: Quarterly or on demand. +- **YAML**: [build\yaml\cleanupResources\cleanupResources.yml](../build/yaml/cleanupResources/cleanupResources.yml) + +| Variable Name | Source | Description | +| - | - | - | +| **AzureSubscription** | Azure DevOps | Name of the Azure Resource Manager Service Connection configured in the DevOps organization. Click [here](./addARMServiceConnection.md) to see how to set it up. | +| **DeployResourceGroup** | Deploy Bot Resources | (optional) Name of the Resource Group containing the bots. | +| **ResourceSuffix** | Create Shared Resources | (optional) Suffix to add to the resources' name to avoid collitions. | +| **SharedResourceGroup** | Create Shared Resources | (optional) Name of the Resource Group containing the shared resources. | + +### Dependency Variables + +These are the available languages for the dependencies registry and version variables: + +You can choose between one of the following options to select the package's feed. + +- DotNet + - Artifacts (default) + - MyGet (default for V3 skill) + - NuGet +- JS + - MyGet (default) + - Npm +- Python (Not available for SkillsV3) + - Artifacts (default) + - Pypi + - Test.Pypi + +The version parameters support LATEST (default), STABLE, or a specific version. + +Note: Npm and NuGet feeds only support stable versions, fill the corresponding variable with a specific version or set it to `stable`. + +### BotNames + +As of now, these are the bots available. This list will be expanded in the future. + +- DotNet + - Consumers + - BffnSimpleHostBotDotNet + - BffnSimpleHostBotDotNet21 + - BffnSimpleComposerHostBotDotNet + - BffnWaterfallHostBotDotNet + - Skills + - BffnEchoSkillBotDotNet + - BffnEchoSkillBotDotNet21 + - BffnEchoSkillBotDotNetV3 + - BffnEchoComposerSkillBotDotNet + - BffnWaterfallSkillBotDotNet + +- JS + - Consumers + - BffnSimpleHostBotJS + - BffnWaterfallHostBotJS + - Skills + - BffnEchoSkillBotJS + - BffnEchoSkillBotJSV3 + - BffnWaterfallSkillBotJS + +- Python + - Consumers + - BffnSimpleHostBotPython + - BffnWaterfallHostBotPython + - Skills + - BffnEchoSkillBotPython + - BffnWaterfallSkillBotPython diff --git a/tests/functional/Docs/setupAppRegistrations.md b/tests/functional/Docs/setupAppRegistrations.md new file mode 100644 index 0000000000..cd4fcee8d8 --- /dev/null +++ b/tests/functional/Docs/setupAppRegistrations.md @@ -0,0 +1,88 @@ +# How to setup up App Registrations + +The following steps will guide you on how to manually set up App Registrations. +By default, App Registrations are created and used automatically across the [pipelines](./pipelines.md), but the possibility of setting them up manually is available. + +## Requirements + +- An active [Azure Subscription](https://azure.microsoft.com/en-us/free/). + +## (optional) Skip the automatic creation/deletion of App Registrations at runtime + +The Key Vault and App Registration creation/deletion stages can be skipped at runtime on the [Create Shared Resources](../build/yaml/sharedResources/createSharedResources.yml) and [Cleanup Resources](../build/yaml/cleanupResources/cleanupResources.yml). Running these stages without having assigned the required Service Principal permissions can lead to errors (although these errors won't interrupt the rest of the processes, so this step is optional). +To skip stages, simple click on Stages to run, deselect them, and click Use selected stages before running the pipeline (see picture below). + +![setupAppRegistrations1](media/setupAppRegistrations1.png) + +## Create App Registrations + +App Registration credentials must be created for each bot to be deployed and tested (there are fourteen currently, but the number is expected to increase in the future). +To do this, you could head to [App Registrations](https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade) on Azure and create them one by one, but we created a PowerShell script to create registration batches automatically. The only requirement to run it is to have access to an active Azure account. + +Click [here](./media/CreateAppRegistrations.ps1) to go to the script. + +Executing it will prompt you to log into your account with your default browser, then to define a prefix for the registration names, and finally for the amount of registrations to create. Both have default values (triggered by pressing Enter) to accommodate for the creation of fourteen credentials with generic names. +The creation process will start, displaying each registration's name, ID, and password created while running. + +![setupAppRegistrations2](media/setupAppRegistrations2.png) + +Once all the credentials are created, the script will ask if you'd like to save them to a file for easier access. It will be saved in the same directory as the script in a file named AppRegistrations.json by default. + +![setupAppRegistrations3](media/setupAppRegistrations3.png) + +## Assign credentials in the pipelines + +You'll have to create variables for each bot in the [Deploy Bot Resources](../build/yaml/deployBotResources/deployBotResources.yml) and [Run Test Scenarios](../build/yaml/testScenarios/runTestScenarios.yml) pipelines. +Alternatively, you could set the variables into a Key Vault or library, but we'll focus on the more straight-forward method of setting them up directly into the pipelines as it requires the least amount of permissions possible. +Head to your [Deploy Bot Resources](../build/yaml/deployBotResources/deployBotResources.yml) pipeline overview (click [here](./setupPipelines.md) for instructions on how to set pipelines up), and click on `Edit` (left of the Run pipeline button on the top right corner). +You will be redirected to the *Edit view*, here click on `Variables` (left of the Run button on the top right corner), and the *Variables panel* will pop up. +If you didn't have any variables set up already, it will look like the picture on the left; if you had, it will look like the one on the right. Click on `New variable` or the plus button respectively to start adding variables. + +![setupAppRegistrations4](media/setupAppRegistrations4.png) + +The following list contains all current variable names to be created, each credential pair can be populated with any of the ones created in the [Create App Registrations](#create-app-registrations) step. + +- BffnEchoSkillBotComposerDotNetAppId +- BffnEchoSkillBotComposerDotNetAppSecret +- BffnEchoSkillBotDotNet21AppId +- BffnEchoSkillBotDotNet21AppSecret +- BffnEchoSkillBotDotNetAppId +- BffnEchoSkillBotDotNetAppSecret +- BffnEchoSkillBotDotNetV3AppId +- BffnEchoSkillBotDotNetV3AppSecret +- BffnEchoSkillBotJSAppId +- BffnEchoSkillBotJSAppSecret +- BffnEchoSkillBotJSV3AppId +- BffnEchoSkillBotJSV3AppSecret +- BffnEchoSkillBotPythonAppId +- BffnEchoSkillBotPythonAppSecret +- BffnSimpleHostBotComposerDotNetAppId +- BffnSimpleHostBotComposerDotNetAppSecret +- BffnSimpleHostBotDotNet21AppId +- BffnSimpleHostBotDotNet21AppSecret +- BffnSimpleHostBotDotNetAppId +- BffnSimpleHostBotDotNetAppSecret +- BffnSimpleHostBotJSAppId +- BffnSimpleHostBotJSAppSecret +- BffnSimpleHostBotPythonAppId +- BffnSimpleHostBotPythonAppSecret +- BffnWaterfallHostBotDotNetAppId +- BffnWaterfallHostBotDotNetAppSecret +- BffnWaterfallSkillBotDotNetAppId +- BffnWaterfallSkillBotDotNetAppSecret + +Don't forget to click on SAVE once you've finished setting up every variable. + +![setupAppRegistrations5](media/setupAppRegistrations5.png) + +Lastly, you'll have to create new variables for the [Run Test Scenarios](../build/yaml/testScenarios/runTestScenarios.yml), same steps apply. +This pipeline requires only the App Registration IDs of the skill bots, so the list is shorter. Just be sure to use the same IDs as with the [Deploy Bot Resources](../build/yaml/deployBotResources/deployBotResources.yml) pipeline. + +- BffnEchoSkillBotComposerDotnetAppId +- BffnEchoSkillBotDotNet21AppId +- BffnEchoSkillBotDotNetAppId +- BffnEchoSkillBotDotNetV3AppId +- BffnEchoSkillBotJSAppId +- BffnEchoSkillBotJSV3AppId +- BffnEchoSkillBotPythonAppId +- BffnWaterfallSkillBotDotNetAppId diff --git a/tests/functional/Docs/setupPipelines.md b/tests/functional/Docs/setupPipelines.md new file mode 100644 index 0000000000..2db02d7a0b --- /dev/null +++ b/tests/functional/Docs/setupPipelines.md @@ -0,0 +1,34 @@ +# How to setup Pipelines + +The following steps will guide you through the creation of a pipeline from a YAML file. + +## Requirements + +- An [Azure DevOps Organization](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/create-organization?view=azure-devops). +- Access to the repository where the pipeline definition YAML file is stored. + +## Steps + +- Go to the pipeline section of your Azure DevOps organization and create a new pipeline using the classic editor. + + ![setupPipelines1](./media/setupPipelines1.png) + +- Configure the repository and branch. In the configuration as code section, click on Apply for the YAML file option. + + ![setupPipelines2](./media/setupPipelines2.png) + +- In the YAML tab, write the pipeline's name and select the YAML file you want to use. + + ![setupPipelines3](./media/setupPipelines3.png) + +- Go to the variables tab to add all the variables that will be used in the pipeline. + + ![setupPipelines4](./media/setupPipelines4.png) + +- In the triggers tab you can configure the triggers and schedules for each pipeline, all pipelines are configured to not trigger by default. + + ![setupPipelines5](./media/setupPipelines5.png) + +- When you are done configuring the pipeline, save the changes. + + ![setupPipelines6](./media/setupPipelines6.png) diff --git a/tests/functional/LICENSE b/tests/functional/LICENSE new file mode 100644 index 0000000000..9e841e7a26 --- /dev/null +++ b/tests/functional/LICENSE @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE diff --git a/tests/functional/Libraries/Directory.Build.props b/tests/functional/Libraries/Directory.Build.props new file mode 100644 index 0000000000..d9e265fcaa --- /dev/null +++ b/tests/functional/Libraries/Directory.Build.props @@ -0,0 +1,24 @@ + + + + true + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + \ No newline at end of file diff --git a/tests/functional/Libraries/TranscriptConverter/ConvertTranscriptHandler.cs b/tests/functional/Libraries/TranscriptConverter/ConvertTranscriptHandler.cs new file mode 100644 index 0000000000..49712f7b94 --- /dev/null +++ b/tests/functional/Libraries/TranscriptConverter/ConvertTranscriptHandler.cs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.CommandLine; +using System.CommandLine.Invocation; +using System.IO; +using Newtonsoft.Json; + +namespace TranscriptConverter +{ + public class ConvertTranscriptHandler + { + /// + /// Creates the convert command. + /// + /// The created command. + public Command Create() + { + var cmd = new Command("convert", "Converts transcript files into test script to be executed by the TranscriptTestRunner"); + + cmd.AddArgument(new Argument("source")); + cmd.AddOption(new Option("--target", "Path to the target test script file.")); + + cmd.Handler = CommandHandler.Create((source, target) => + { + try + { + Console.WriteLine("{0}: {1}", "Converting source transcript", source); + + var testScript = Converter.ConvertTranscript(source); + + Console.WriteLine("Finished conversion"); + + var targetPath = string.IsNullOrEmpty(target) ? source.Replace(".transcript", ".json", StringComparison.InvariantCulture) : target; + + WriteTestScript(testScript, targetPath); + + Console.WriteLine("{0}: {1}", "Test script saved as", targetPath); + } + catch (FileNotFoundException e) + { + Console.WriteLine("{0}: {1}", "Error", e.Message); + } + catch (DirectoryNotFoundException e) + { + Console.WriteLine("{0}: {1}", "Error", e.Message); + } + }); + return cmd; + } + + /// + /// Writes the test script content to the path set in the target argument. + /// + private static void WriteTestScript(TestScript testScript, string targetScript) + { + var json = JsonConvert.SerializeObject( + testScript, + Formatting.Indented, + new JsonSerializerSettings + { + NullValueHandling = NullValueHandling.Ignore + }); + + using var streamWriter = new StreamWriter(Path.GetFullPath(targetScript)); + streamWriter.Write(json); + } + } +} diff --git a/tests/functional/Libraries/TranscriptConverter/Converter.cs b/tests/functional/Libraries/TranscriptConverter/Converter.cs new file mode 100644 index 0000000000..c275c1852d --- /dev/null +++ b/tests/functional/Libraries/TranscriptConverter/Converter.cs @@ -0,0 +1,254 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Text.RegularExpressions; +using Microsoft.Bot.Schema; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace TranscriptConverter +{ + public static class Converter + { + /// + /// Converts the transcript into a test script. + /// + /// The path to the transcript file. + /// The test script created. + public static TestScript ConvertTranscript(string transcriptPath) + { + using var reader = new StreamReader(Path.GetFullPath(transcriptPath)); + + var transcript = reader.ReadToEnd(); + + var cleanedTranscript = RemoveUndesiredFields(transcript); + + return CreateTestScript(cleanedTranscript); + } + + /// + /// Recursively goes through the json content removing the undesired elements. + /// + /// The JToken element to process. + /// The recursive function to iterate over the JToken. + private static void RemoveFields(JToken token, Func callback) + { + if (!(token is JContainer container)) + { + return; + } + + var removeList = new List(); + + foreach (var element in container.Children()) + { + if (element is JProperty prop && callback(prop)) + { + removeList.Add(element); + } + + RemoveFields(element, callback); + } + + foreach (var element in removeList) + { + element.Remove(); + } + } + + /// + /// Creates the test script based on the transcript's activities. + /// + /// Json containing the transcript's activities. + /// The test script created. + private static TestScript CreateTestScript(string json) + { + var activities = JsonConvert.DeserializeObject>(json); + var testScript = new TestScript(); + + foreach (var activity in activities) + { + var scriptItem = new TestScriptItem + { + Type = activity.Type, + Role = activity.From.Role, + Text = activity.Text + }; + + if (scriptItem.Role == "bot") + { + var assertionsList = CreateAssertionsList(activity); + + foreach (var assertion in assertionsList) + { + scriptItem.Assertions.Add(assertion); + } + } + + testScript.Items.Add(scriptItem); + } + + return testScript; + } + + /// + /// Creates a list of assertions with the activity's properties. + /// + /// The activity to parse as assertions. + /// The list of assertions. + private static IEnumerable CreateAssertionsList(IActivity activity) + { + var json = JsonConvert.SerializeObject( + activity, + Formatting.None, + new JsonSerializerSettings + { + NullValueHandling = NullValueHandling.Ignore + }); + + var token = JToken.Parse(json); + var assertionsList = new List(); + + AddAssertions(token, assertionsList); + + return assertionsList; + } + + /// + /// Goes over the Jtoken object adding assertions to the list for each property found. + /// + /// The JToken object containing the properties. + /// The list of assertions. + private static void AddAssertions(JToken token, ICollection assertionsList) + { + foreach (var property in token) + { + if (property is JProperty prop && !IsJsonObject(prop.Value.ToString())) + { + if (prop.Path == "from.name") + { + continue; + } + + var value = prop.Value.Type == JTokenType.String + ? $"'{prop.Value.ToString().Replace("'", "\\'", StringComparison.InvariantCulture)}'" + : prop.Value; + + assertionsList.Add($"{prop.Path} == {value}"); + } + else + { + AddAssertions(property, assertionsList); + } + } + } + + /// + /// Checks if a string is a GUID value. + /// + /// The string to check. + /// True if the string is a GUID, otherwise, returns false. + private static bool IsGuid(string guid) + { + var guidMatch = Regex.Match( + guid, + @"([a-z0-9]{8}[-][a-z0-9]{4}[-][a-z0-9]{4}[-][a-z0-9]{4}[-][a-z0-9]{12})", + RegexOptions.IgnoreCase); + return guidMatch.Success; + } + + /// + /// Checks if a string is an ID value. + /// + /// The string to check. + /// True if the string is an ID, otherwise, returns false. + private static bool IsId(string id) + { + var idMatch = Regex.Match( + id, + @"([a-z0-9]{23})", + RegexOptions.IgnoreCase); + return idMatch.Success; + } + + /// + /// Checks if a string is a service url value. + /// + /// The string to check. + /// True if the string is an url, otherwise, returns false. + private static bool IsServiceUrl(string url) + { + var idMatch = Regex.Match( + url, + @"https://([a-z0-9]{12})", + RegexOptions.IgnoreCase); + return idMatch.Success; + } + + /// + /// Checks if a string is a channel ID value. + /// + /// The string to check. + /// True if the string is a channel ID (Emulator), otherwise, returns false. + private static bool IsChannelId(string value) + { + return value.ToUpper(CultureInfo.InvariantCulture) == "EMULATOR"; + } + + /// + /// Checks if a string is a JSON Object. + /// + /// The string to check. + /// True if the string is a JSON Object, otherwise, returns false. + private static bool IsJsonObject(string value) + { + try + { + JsonConvert.DeserializeObject(value); + return true; + } + catch (JsonException) + { + return false; + } + } + + /// + /// Removes variable fields like IDs, Dates and urls from the transcript file. + /// + /// The transcript file content. + /// The transcript content without the undesired fields. + private static string RemoveUndesiredFields(string transcript) + { + var token = JToken.Parse(transcript); + + RemoveFields(token, (attr) => + { + var value = attr.Value.ToString(); + + if (IsJsonObject(value)) + { + return false; + } + + return IsGuid(value) || IsDateTime(value) || IsId(value) || IsServiceUrl(value) || IsChannelId(value); + }); + + return token.ToString(); + } + + /// + /// Checks if a string is a DateTime value. + /// + /// The string to check. + /// True if the string is a DateTime, otherwise, returns false. + private static bool IsDateTime(string datetime) + { + return DateTime.TryParse(datetime, out _); + } + } +} diff --git a/tests/functional/Libraries/TranscriptConverter/Program.cs b/tests/functional/Libraries/TranscriptConverter/Program.cs new file mode 100644 index 0000000000..938a956369 --- /dev/null +++ b/tests/functional/Libraries/TranscriptConverter/Program.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.CommandLine.Builder; +using System.CommandLine.Parsing; + +namespace TranscriptConverter +{ + public class Program + { + private static int Main(string[] args) + { + var cmd = new CommandLineBuilder() + .AddCommand(new ConvertTranscriptHandler().Create()) + .UseDefaults() + .Build(); + + return cmd.Invoke(args); + } + } +} diff --git a/tests/functional/Libraries/TranscriptConverter/TestScript.cs b/tests/functional/Libraries/TranscriptConverter/TestScript.cs new file mode 100644 index 0000000000..87999deee5 --- /dev/null +++ b/tests/functional/Libraries/TranscriptConverter/TestScript.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace TranscriptConverter +{ + /// + /// A Test Script that can be used for functional testing of bots. + /// + public class TestScript + { + /// + /// Gets the test script items. + /// + /// + /// The sequence of test scripts to perform to validate the bots behavior. + /// + [JsonProperty("items")] + public List Items { get; } = new List(); + } +} diff --git a/tests/functional/Libraries/TranscriptConverter/TestScriptItem.cs b/tests/functional/Libraries/TranscriptConverter/TestScriptItem.cs new file mode 100644 index 0000000000..ace9299487 --- /dev/null +++ b/tests/functional/Libraries/TranscriptConverter/TestScriptItem.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace TranscriptConverter +{ + /// + /// TestRunner's representation of an activity. + /// + public class TestScriptItem + { + /// + /// Gets or sets the activity type. + /// + /// + /// The activity type. + /// + [JsonProperty("type")] + public string Type { get; set; } + + /// + /// Gets or sets the activity role. + /// + /// + /// The activity role. + /// + [JsonProperty("role")] + public string Role { get; set; } + + /// + /// Gets or sets the activity text. + /// + /// + /// The activity text. + /// + [JsonProperty("text")] + public string Text { get; set; } + + /// + /// Gets the activity assertion collection. + /// + /// + /// The activity assertion collection. + /// + [JsonProperty("assertions")] + public List Assertions { get; } = new List(); + + /// + /// Prevents the serializer from creating the assertions collection if its empty. + /// + /// true if the assertions collection should be serialized. + public bool ShouldSerializeAssertions() + { + return Assertions.Count > 0; + } + } +} diff --git a/tests/functional/Libraries/TranscriptConverter/TranscriptConverter.csproj b/tests/functional/Libraries/TranscriptConverter/TranscriptConverter.csproj new file mode 100644 index 0000000000..aa4817caff --- /dev/null +++ b/tests/functional/Libraries/TranscriptConverter/TranscriptConverter.csproj @@ -0,0 +1,16 @@ + + + + Exe + netcoreapp3.1 + true + btc + + + + + + + + + diff --git a/tests/functional/Libraries/TranscriptTestRunner/Authentication/Session.cs b/tests/functional/Libraries/TranscriptTestRunner/Authentication/Session.cs new file mode 100644 index 0000000000..3f3f5698a8 --- /dev/null +++ b/tests/functional/Libraries/TranscriptTestRunner/Authentication/Session.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Newtonsoft.Json; + +namespace TranscriptTestRunner.Authentication +{ + /// + /// Session definition. + /// + public class Session + { + /// + /// Gets or sets the session ID. + /// + /// + /// The session ID. + /// + [JsonProperty("sessionId")] + public string SessionId { get; set; } + } +} diff --git a/tests/functional/Libraries/TranscriptTestRunner/Authentication/SessionInfo.cs b/tests/functional/Libraries/TranscriptTestRunner/Authentication/SessionInfo.cs new file mode 100644 index 0000000000..5d96e14302 --- /dev/null +++ b/tests/functional/Libraries/TranscriptTestRunner/Authentication/SessionInfo.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Net; + +namespace TranscriptTestRunner.Authentication +{ + /// + /// Session information definition. + /// + public class SessionInfo + { + /// + /// Gets or sets the session ID. + /// + /// + /// The session ID. + /// + public string SessionId { get; set; } + + /// + /// Gets or sets the session cookie. + /// + /// + /// The session cookie. + /// + public Cookie Cookie { get; set; } + } +} diff --git a/tests/functional/Libraries/TranscriptTestRunner/Authentication/TestClientAuthentication.cs b/tests/functional/Libraries/TranscriptTestRunner/Authentication/TestClientAuthentication.cs new file mode 100644 index 0000000000..6ea517ed43 --- /dev/null +++ b/tests/functional/Libraries/TranscriptTestRunner/Authentication/TestClientAuthentication.cs @@ -0,0 +1,119 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace TranscriptTestRunner.Authentication +{ + /// + /// Authentication class for the Test Client. + /// + public static class TestClientAuthentication + { + /// + /// Signs in to the bot. + /// + /// The sign in Url. + /// The Origin Header with key and value. + /// The Session information definition. + /// True, if SignIn is successful; otherwise false. + public static async Task SignInAsync(string url, KeyValuePair originHeader, SessionInfo sessionInfo) + { + var cookieContainer = new CookieContainer(); + using var handler = new HttpClientHandler + { + AllowAutoRedirect = false, + CookieContainer = cookieContainer + }; + + // We have a sign in url, which will produce multiple HTTP 302 for redirects + // This will path + // token service -> other services -> auth provider -> token service (post sign in)-> response with token + // When we receive the post sign in redirect, we add the cookie passed in the session info + // to test enhanced authentication. This in the scenarios happens by itself since browsers do this for us. + using var client = new HttpClient(handler); + client.DefaultRequestHeaders.Add(originHeader.Key, originHeader.Value); + + while (!string.IsNullOrEmpty(url)) + { + using var response = await client.GetAsync(new Uri(url)).ConfigureAwait(false); + var text = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + + url = response.StatusCode == HttpStatusCode.Redirect + ? response.Headers.Location.OriginalString + : null; + + // Once the redirects are done, there is no more url. + // This means we did the entire loop. + if (url == null) + { + if (!response.IsSuccessStatusCode || !text.Contains("You are now signed in and can close this window.")) + { + throw new InvalidOperationException("An error occurred signing in"); + } + + return true; + } + + // If this is the post sign in callback, add the cookie and code challenge + // so that the token service gets the verification. + // Here we are simulating what WebChat does along with the browser cookies. + if (url.StartsWith("https://token.botframework.com/api/oauth/PostSignInCallback", StringComparison.Ordinal)) + { + url += $"&code_challenge={sessionInfo.SessionId}"; + cookieContainer.Add(sessionInfo.Cookie); + } + } + + throw new InvalidOperationException("Sign in did not succeed. Set a breakpoint in TestClientAuthentication.SignInAsync() to debug the redirect sequence."); + } + + /// + /// Obtains the from an URL endpoint with a token. + /// + /// The URL endpoint to obtain the from. + /// The token to use for authorization. + /// The Origin Header with key and value. + /// The Task. + public static async Task GetSessionInfoAsync(string url, string token, KeyValuePair originHeader) + { + // Set up cookie container to obtain response cookie + var cookies = new CookieContainer(); + using var handler = new HttpClientHandler { CookieContainer = cookies }; + + using var client = new HttpClient(handler); + + using var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); + + // We want to add the Origins header to this client as well + client.DefaultRequestHeaders.Add(originHeader.Key, originHeader.Value); + + using var response = await client.SendAsync(request).ConfigureAwait(false); + if (response.IsSuccessStatusCode) + { + // Extract cookie from cookies + var cookie = cookies.GetCookies(new Uri(url)).Cast().FirstOrDefault(c => c.Name == "webchat_session_v2"); + + // Extract session info from body + var body = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + var session = JsonConvert.DeserializeObject(body); + + return new SessionInfo + { + SessionId = session.SessionId, + Cookie = cookie + }; + } + + throw new InvalidOperationException("Failed to obtain session id"); + } + } +} diff --git a/tests/functional/Libraries/TranscriptTestRunner/Authentication/TokenInfo.cs b/tests/functional/Libraries/TranscriptTestRunner/Authentication/TokenInfo.cs new file mode 100644 index 0000000000..3415d629a4 --- /dev/null +++ b/tests/functional/Libraries/TranscriptTestRunner/Authentication/TokenInfo.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Newtonsoft.Json; + +namespace TranscriptTestRunner.Authentication +{ + /// + /// Token definition. + /// + public class TokenInfo + { + /// + /// Gets or sets the token string. + /// + /// + /// The token string. + /// + [JsonProperty("token")] + public string Token { get; set; } + + /// + /// Gets or sets the conversation ID. + /// + /// + /// The conversation ID. + /// + [JsonProperty("conversationId")] + public string ConversationId { get; set; } + } +} diff --git a/tests/functional/Libraries/TranscriptTestRunner/README.md b/tests/functional/Libraries/TranscriptTestRunner/README.md new file mode 100644 index 0000000000..2d4a054bc8 --- /dev/null +++ b/tests/functional/Libraries/TranscriptTestRunner/README.md @@ -0,0 +1,196 @@ +# Transcript Test Runner + +## Summary + +Transcript Test Runner aims to simplify complex conversation scenarios against bots. +Starting from a transcript file containing the user-bot interactions to be tested, the Transcript Test Runner converts the transcript into a test script used to replicate the messages sent by the user and evaluate the answers from the bot. + +The Transcript Test Runner also offers the possibility of running the tests directly from a [test script](testscript.schema), adding flexibility to the assertions for the bot's messages. + +The Test Runner supports different formats of transcript files (Emulator, Teams, Slack, etc.) and +can be connected to the bots using different channels (*): +- DirectLineClient +- TeamsClient +- SlackClient +- FacebookClient + +(*) _This first version implements the DirectLineClient and uses Emulator transcript files. Stay tuned for the next features._ + +## User Step-by-step Guide +This step-by-step guide shows how to run a test with the `TestRunner` configuring a `DirectLine` client to communicate with the bots, and starting from an Emulator transcript file. + +### Using the TestRunner +1- Open your test project and install the `TranscriptTestRunner` package. + +2- In the `appsettings.json` file add the following variables: +```json +{ + "DIRECTLINE": "", + "BOTID": "" +} +``` + +> **Note:** For more information on how to obtain the bot `DIRECTLINE` secret key, follow [this guide](https://docs.microsoft.com/en-us/azure/bot-service/bot-service-channel-connect-directline). + +3- Add the `Emulator Transcript` file in a folder on your test project. To create a transcript file, follow [these steps](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-debug-transcript?view=azure-bot-service-4.0#creatingstoring-a-bot-transcript-file). + +4- Add the `TestRunner` to your test. + +```csharp +// Create a DirectLine client with the `TestClientFactory`. +var directLineClient = new TestClientFactory(ClientType.DirectLine).GetTestClient(); + +// Instantiate the TestRunner and set up the DirectLine client. +var runner = new TestRunner(directLineClient); + +// Run the test recorded in the transcript file. +await runner.RunTestAsync(""); +``` +The `TestRunner` will convert the transcript file into a Test Script with the messages the user sends to the bot and the assertions that should be made to the bot's answers. + +The `TestRunner` also allows you to run the test from a custom Test Script file. + +### Using a Test Script file +A Test Script is basically a JSON file with an array of [TestScriptItem](TestScriptItem.cs) that will be used by the `TestRunner` as a test input. + +Create a test script file using this [JSON schema](testscript.schema). +```json +[ + { + "type": "conversationUpdate", + "role": "user" + }, + { + "type": "message", + "role": "bot", + "text": "Hello and welcome!", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'Hello and welcome!'", + "inputHint == 'acceptingInput'" + ] + }, + { + "type": "message", + "role": "user", + "text": "Hi" + } +] +``` +> **Note:** The JSON Schema is still a work in progress. + +Once the Test Script file is created, store it in a folder on your test project and pass it to the _RunTestAsync_ method of the TestRunner instead of the transcript file. + +### Creating tests programmatically +The `TestRunner` allows you to run tests in a programmatic way, sending Activities to the bot and Assert its reply. + +The following sample shows how to create a simple test programmatically. + +```csharp +// Create a DirectLine client instance that will be used to communicate with your bot. +var directLineClient = new TestClientFactory(ClientType.DirectLine).GetTestClient(); + +// Instantiate the TestRunner and set up the DirectLine client. +var runner = new TestRunner(directLineClient); + +// Send an Activity to the bot. +await runner.SendActivityAsync(new Activity(ActivityTypes.ConversationUpdate)); + +// Asserts the reply received from the bot. +await runner.AssertReplyAsync(activity => +{ + // Sample asserting Activity's type and text with xUnit. + Assert.Equal(ActivityTypes.Message, activity.Type); + Assert.Equal("Hello and welcome!", activity.Text); +}); +``` + +**Methods** +- **SendActivityAsync:** Used to send an Activity to the bot. +- **AssertReplyAsync:** Used to create custom assertions to an expected reply when the bot responds. +- **ClientSignInAsync:** Used to sign in to the bot. + +**ClientSignInAsync** + +This method is used when your bot has an authentication implementation and you want to sign in. + +The following sample shows how to use the `ClientSignInAsync` method to sign in. + +```csharp +// Create a DirectLine client instance that will be used to communicate with your bot. +var directLineClient = new TestClientFactory(ClientType.DirectLine).GetTestClient(); +// Instantiate the TestRunner and set up the DirectLine client. +var runner = new TestRunner(directLineClient); +var signInUrl = string.Empty; + +// Sends an Activity to the bot. +await runner.SendActivityAsync(new Activity { Type = ActivityTypes.Message, Text = "auth" }); + +// Obtain the sign in url. +await runner.AssertReplyAsync(activity => +{ + Assert.Equal(ActivityTypes.Message, activity.Type); + Assert.True(activity.Attachments.Count > 0); + + var attachment = JsonConvert.SerializeObject(activity.Attachments.FirstOrDefault().Content); + var card = JsonConvert.DeserializeObject(attachment); + signInUrl = card.Buttons[0].Value?.ToString(); + + Assert.False(string.IsNullOrEmpty(signInUrl)); +}); + +// Execute the sign in. +await runner.ClientSignInAsync(signInUrl); + +// If the sign in succeeded you can continue to execute the rest of the conversation +// either programmatically or with a test file. +``` + +### Setting up a Logger (Optional). +TestRunner uses [ILogger](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.ilogger) when it comes to display an output for each interaction between the test and the bot. + +The following sample shows how to create and pass an `ILogger` to the `TestRunner` with a `LoggerFactory`. + +```csharp +// Create a new Logger Factory. +var loggerFactory = LoggerFactory.Create(builder => +{ + // Add new Providers to tell the Logger where to output the data. + builder + // This provider will output the logs in the console. + .AddConsole() + // This provider will output the logs in the Debug output window. + .AddDebug(); +}); + +// Create the `Logger` instance with a Category name. +var logger = loggerFactory.CreateLogger("Category"); + +// Create a DirectLine client instance that will be used to communicate with your bot. +var directLineClient = new TestClientFactory(ClientType.DirectLine).GetTestClient(); +// Instantiate the TestRunner, set up the DirectLine client, and send the created `Logger`. +var runner = new TestRunner(directLineClient, logger); +``` + +### Extend TestRunner functionality +TestRunner has an Xunit extention to allow the users that prefer this test framework, to override the `AssertActivityAsync` with Xunit assertions. + +```csharp +public class XUnitTestRunner : TestRunner +{ + public XUnitTestRunner(TestClientBase client, ILogger logger = null) + : base(client, logger) + { + } + + protected override Task AssertActivityAsync(TestScriptItem expectedActivity, Activity actualActivity, CancellationToken cancellationToken = default) + { + Assert.Equal(expectedActivity.Type, actualActivity.Type); + Assert.Equal(expectedActivity.Text, actualActivity.Text); + + return Task.CompletedTask; + } +} +``` diff --git a/tests/functional/Libraries/TranscriptTestRunner/TestClientBase.cs b/tests/functional/Libraries/TranscriptTestRunner/TestClientBase.cs new file mode 100644 index 0000000000..3a6bd63b2a --- /dev/null +++ b/tests/functional/Libraries/TranscriptTestRunner/TestClientBase.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Schema; + +namespace TranscriptTestRunner +{ + /// + /// Base class for test clients. + /// + /// + /// Test clients act as intermediaries between tests and bots. + /// + public abstract class TestClientBase + { + /// + /// Sends an to the bot. + /// + /// to send. + /// A that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + public abstract Task SendActivityAsync(Activity activity, CancellationToken cancellationToken); + + /// + /// Gets the next reply from the bot. + /// + /// A that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + public abstract Task GetNextReplyAsync(CancellationToken cancellationToken); + + /// + /// Signs in to the bot. + /// + /// The sign in Url. + /// True, if SignIn is successful; otherwise false. + public abstract Task SignInAsync(string signInUrl); + + /// + /// Uploads a file. + /// + /// The file to be uploaded. + /// A that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + public abstract Task UploadAsync(Stream file, CancellationToken cancellationToken); + } +} diff --git a/tests/functional/Libraries/TranscriptTestRunner/TestClientFactory.cs b/tests/functional/Libraries/TranscriptTestRunner/TestClientFactory.cs new file mode 100644 index 0000000000..c8a752e6a3 --- /dev/null +++ b/tests/functional/Libraries/TranscriptTestRunner/TestClientFactory.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.ComponentModel; +using Microsoft.Bot.Connector; +using Microsoft.Extensions.Logging; +using TranscriptTestRunner.TestClients; + +namespace TranscriptTestRunner +{ + /// + /// Factory class to create instances of . + /// + public class TestClientFactory + { + private readonly TestClientBase _testClientBase; + + /// + /// Initializes a new instance of the class. + /// + /// The type of channel to create based on the class. + /// The options to create the client. + /// An optional instance. + public TestClientFactory(string channel, DirectLineTestClientOptions options, ILogger logger) + { + switch (channel) + { + case Channels.Directline: + _testClientBase = new DirectLineTestClient(options, logger); + break; + case Channels.Emulator: + break; + case Channels.Msteams: + break; + case Channels.Facebook: + break; + case Channels.Slack: + break; + default: + throw new InvalidEnumArgumentException($"Invalid client type ({channel})"); + } + } + + /// + /// Gets the test client. + /// + /// The test client. + public TestClientBase GetTestClient() + { + return _testClientBase; + } + } +} diff --git a/tests/functional/Libraries/TranscriptTestRunner/TestClients/DirectLineTestClient.cs b/tests/functional/Libraries/TranscriptTestRunner/TestClients/DirectLineTestClient.cs new file mode 100644 index 0000000000..50f668d37a --- /dev/null +++ b/tests/functional/Libraries/TranscriptTestRunner/TestClients/DirectLineTestClient.cs @@ -0,0 +1,448 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Net.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Connector.DirectLine; +using Microsoft.Bot.Schema; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Rest.TransientFaultHandling; +using Newtonsoft.Json; +using TranscriptTestRunner.Authentication; +using Activity = Microsoft.Bot.Connector.DirectLine.Activity; +using ActivityTypes = Microsoft.Bot.Schema.ActivityTypes; +using Attachment = Microsoft.Bot.Connector.DirectLine.Attachment; +using BotActivity = Microsoft.Bot.Schema.Activity; +using BotChannelAccount = Microsoft.Bot.Schema.ChannelAccount; +using ChannelAccount = Microsoft.Bot.Connector.DirectLine.ChannelAccount; + +namespace TranscriptTestRunner.TestClients +{ + /// + /// DirectLine implementation of . + /// + public class DirectLineTestClient : TestClientBase, IDisposable + { + // DL client sample: https://github.com/microsoft/BotFramework-DirectLine-DotNet/tree/main/samples/core-DirectLine/DirectLineClient + // Stores the activities received from the bot + private readonly SortedDictionary _activityQueue = new SortedDictionary(); + + // Stores the activities received from the bot that don't immediately correlate with the last activity we received (an activity was skipped) + private readonly SortedDictionary _futureQueue = new SortedDictionary(); + + // Used to lock access to the internal lists + private readonly object _listLock = new object(); + + // Tracks the index of the last activity received + private int _lastActivityIndex = -1; + + private readonly KeyValuePair _originHeader = new KeyValuePair("Origin", $"https://botframework.test.com/{Guid.NewGuid()}"); + private readonly string _user = $"TestUser-{Guid.NewGuid()}"; + private Conversation _conversation; + private CancellationTokenSource _webSocketClientCts; + + // To detect redundant calls to dispose + private bool _disposed; + private DirectLineClient _dlClient; + private ClientWebSocket _webSocketClient; + private readonly ILogger _logger; + private readonly DirectLineTestClientOptions _options; + + /// + /// Initializes a new instance of the class. + /// + /// Options for the client configuration. + /// The logger. + public DirectLineTestClient(DirectLineTestClientOptions options, ILogger logger = null) + { + if (string.IsNullOrWhiteSpace(options.BotId)) + { + throw new ArgumentException("BotId not set."); + } + + if (string.IsNullOrWhiteSpace(options.DirectLineSecret)) + { + throw new ArgumentException("DirectLineSecret not set."); + } + + _options = options; + + _logger = logger ?? NullLogger.Instance; + } + + /// + public override async Task SendActivityAsync(BotActivity activity, CancellationToken cancellationToken) + { + if (_conversation == null) + { + await StartConversationAsync().ConfigureAwait(false); + + if (activity.Type == ActivityTypes.ConversationUpdate) + { + // StartConversationAsync sends a ConversationUpdate automatically. + // Ignore the activity sent if it is the first one we are sending to the bot and it is a ConversationUpdate. + // This can happen with recorded scripts where we get a conversation update from the transcript that we don't + // want to use. + return; + } + } + + var attachments = new List(); + + if (activity.Attachments != null && activity.Attachments.Any()) + { + foreach (var item in activity.Attachments) + { + attachments.Add(new Attachment + { + ContentType = item.ContentType, + ContentUrl = item.ContentUrl, + Content = item.Content, + Name = item.Name + }); + } + } + + var activityPost = new Activity + { + From = new ChannelAccount(_user), + Text = activity.Text, + Type = activity.Type, + Attachments = attachments, + }; + + _logger.LogDebug($"{DateTime.Now} Sending activity to conversation {_conversation.ConversationId}"); + _logger.LogDebug(JsonConvert.SerializeObject(activityPost, Formatting.Indented)); + + await _dlClient.Conversations.PostActivityAsync(_conversation.ConversationId, activityPost, cancellationToken).ConfigureAwait(false); + } + + /// + public override async Task GetNextReplyAsync(CancellationToken cancellationToken) + { + if (_conversation == null) + { + await StartConversationAsync().ConfigureAwait(false); + } + + // lock the list while work with it. + lock (_listLock) + { + if (_activityQueue.Any()) + { + // Return the first activity in the queue (if any) + var keyValuePair = _activityQueue.First(); + _activityQueue.Remove(keyValuePair.Key); + _logger.LogDebug($"{DateTime.Now} Popped ID {keyValuePair.Key} from queue (Activity ID is {keyValuePair.Value.Id}. Queue length is now: {_activityQueue.Count}."); + + return keyValuePair.Value; + } + } + + // No activities in the queue + return null; + } + + /// + public override async Task SignInAsync(string url) + { + const string sessionUrl = "https://directline.botframework.com/v3/directline/session/getsessionid"; + var directLineSession = await TestClientAuthentication.GetSessionInfoAsync(sessionUrl, _conversation.Token, _originHeader).ConfigureAwait(false); + return await TestClientAuthentication.SignInAsync(url, _originHeader, directLineSession).ConfigureAwait(false); + } + + /// + /// Frees resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + public override async Task UploadAsync(Stream file, CancellationToken cancellationToken) + { + await _dlClient.Conversations.UploadAsync(_conversation.ConversationId, file, _user, cancellationToken: cancellationToken).ConfigureAwait(false); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + /// Boolean value that determines whether to free resources or not. + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + // Dispose managed objects owned by the class here. + _dlClient?.Dispose(); + + _webSocketClientCts?.Cancel(); + _webSocketClientCts?.Dispose(); + _webSocketClient?.Dispose(); + } + + _disposed = true; + } + + private async Task StartConversationAsync() + { + var tryCount = 0; + var maxTries = _options.StartConversationMaxAttempts; + while (tryCount < maxTries && !_activityQueue.Any() && !_futureQueue.Any()) + { + using var startConversationCts = new CancellationTokenSource(_options.StartConversationTimeout); + tryCount++; + try + { + _logger.LogDebug($"{DateTime.Now} Attempting to start conversation (try {tryCount} of {maxTries})."); + + // Obtain a token using the Direct Line secret + var tokenInfo = await GetDirectLineTokenAsync().ConfigureAwait(false); + + // Ensure we dispose the client after the retries (this helps us make sure we get a new conversation ID on each try) + _dlClient?.Dispose(); + + // Create directLine client from token and initialize settings. + _dlClient = new DirectLineClient(tokenInfo.Token); + _dlClient.SetRetryPolicy(new RetryPolicy(new HttpStatusCodeErrorDetectionStrategy(), 0)); + + // From now on, we'll add an Origin header in directLine calls, with + // the trusted origin we sent when acquiring the token as value. + _dlClient.HttpClient.DefaultRequestHeaders.Add(_originHeader.Key, _originHeader.Value); + + // Start the conversation now (this will send a ConversationUpdate to the bot) + _conversation = await _dlClient.Conversations.StartConversationAsync(startConversationCts.Token).ConfigureAwait(false); + _logger.LogDebug($"{DateTime.Now} Got conversation ID {_conversation.ConversationId} from direct line client."); + _logger.LogTrace($"{DateTime.Now} {Environment.NewLine}{JsonConvert.SerializeObject(_conversation, Formatting.Indented)}"); + + // Ensure we dispose the _webSocketClient after the retries. + _webSocketClient?.Dispose(); + + // Initialize web socket client and listener + _webSocketClient = new ClientWebSocket(); + await _webSocketClient.ConnectAsync(new Uri(_conversation.StreamUrl), startConversationCts.Token).ConfigureAwait(false); + + _logger.LogDebug($"{DateTime.Now} Connected to websocket, state is {_webSocketClient.State}."); + + // Block and wait for the first response to come in. + ActivitySet activitySet = null; + while (activitySet == null) + { + activitySet = await ReceiveActivityAsync(startConversationCts).ConfigureAwait(false); + if (activitySet != null) + { + ProcessActivitySet(activitySet); + } + else + { + _logger.LogDebug($"{DateTime.Now} Got empty ActivitySet while attempting to start the conversation."); + } + } + } + catch (Exception ex) + { + if (tryCount < maxTries) + { + _logger.LogDebug($"{DateTime.Now} Failed to start conversation (attempt {tryCount} of {maxTries}), retrying...{Environment.NewLine}Exception{Environment.NewLine}{ex}"); + } + else + { + _logger.LogCritical($"{DateTime.Now} Failed to start conversation after {maxTries} attempts.{Environment.NewLine}Exception{Environment.NewLine}{ex}"); + throw; + } + } + } + + // We have started a conversation and got at least one activity back. + // Start long running background task to read activities from the socket. + _webSocketClientCts = new CancellationTokenSource(); + _ = Task.Factory.StartNew(() => ListenAsync(_webSocketClientCts), _webSocketClientCts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); + } + + /// + /// This method is invoked as a background task and lists to directline websocket. + /// + /// The cancellation token for the operation. + private async Task ListenAsync(CancellationTokenSource cancellationToken) + { + try + { + while (!cancellationToken.IsCancellationRequested) + { + var activitySet = await ReceiveActivityAsync(cancellationToken).ConfigureAwait(false); + if (activitySet != null) + { + ProcessActivitySet(activitySet); + } + else + { + _logger.LogDebug($"{DateTime.Now} got empty ActivitySet."); + } + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error in ListenAsync"); + throw; + } + } + + private void ProcessActivitySet(ActivitySet activitySet) + { + // lock the list while work with it. + lock (_listLock) + { + foreach (var dlActivity in activitySet.Activities) + { + // Convert the DL Activity object to a BF activity object. + var botActivity = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(dlActivity)); + var activityIndex = int.Parse(botActivity.Id.Split('|')[1], CultureInfo.InvariantCulture); + if (activityIndex == _lastActivityIndex + 1) + { + ProcessActivity(botActivity, activityIndex); + _lastActivityIndex = activityIndex; + } + else + { + // Activities come out of sequence in some situations. + // put the activity in the future queue so we can process it once we fill in the gaps. + _futureQueue.Add(activityIndex, botActivity); + } + } + + // Process the future queue and append the activities if we filled in the gaps. + var queueCopy = new KeyValuePair[_futureQueue.Count]; + _futureQueue.CopyTo(queueCopy, 0); + foreach (var kvp in queueCopy) + { + if (kvp.Key == _lastActivityIndex + 1) + { + ProcessActivity(kvp.Value, kvp.Key); + _futureQueue.Remove(kvp.Key); + _lastActivityIndex = kvp.Key; + } + else + { + break; + } + } + } + } + + private async Task ReceiveActivityAsync(CancellationTokenSource cancellationToken) + { + var rcvBytes = new byte[16384]; + var rcvBuffer = new ArraySegment(rcvBytes); + + // Read messages from the socket. + var rcvMsg = new StringBuilder(); + WebSocketReceiveResult rcvResult; + do + { + _logger.LogDebug($"{DateTime.Now} Listening to web socket...."); + rcvResult = await _webSocketClient.ReceiveAsync(rcvBuffer, cancellationToken.Token).ConfigureAwait(false); + _logger.LogTrace($"{DateTime.Now} Received data from socket.{Environment.NewLine}Buffer offset: {rcvBuffer.Offset}.{Environment.NewLine}Buffer count: {rcvBuffer.Count}{Environment.NewLine}{JsonConvert.SerializeObject(rcvResult, Formatting.Indented)}"); + + if (rcvBuffer.Array != null) + { + rcvMsg.Append(Encoding.UTF8.GetString(rcvBuffer.Array, rcvBuffer.Offset, rcvResult.Count)); + } + else + { + _logger.LogDebug($"{DateTime.Now} Received data but the array was empty."); + } + } + while (!rcvResult.EndOfMessage); + + var message = rcvMsg.ToString(); + _logger.LogDebug($"{DateTime.Now} Activity received"); + _logger.LogDebug(message); + + var activitySet = JsonConvert.DeserializeObject(message); + return activitySet; + } + + private void ProcessActivity(BotActivity botActivity, int activitySeq) + { + if (botActivity.From.Id.StartsWith(_options.BotId, StringComparison.CurrentCultureIgnoreCase)) + { + botActivity.From.Role = RoleTypes.Bot; + botActivity.Recipient = new BotChannelAccount(role: RoleTypes.User); + + _activityQueue.Add(activitySeq, botActivity); + _logger.LogDebug($"{DateTime.Now} Added activity to queue (key is {activitySeq} activity ID is {botActivity.Id}. Activity queue length: {_activityQueue.Count} - Future activities queue length: {_futureQueue.Count}"); + } + } + + /// + /// Exchanges the directLine secret by an auth token. + /// + /// + /// Instead of generating a vanilla DirectLineClient with secret, + /// we obtain a directLine token with the secrets and then we use + /// that token to create the directLine client. + /// What this gives us is the ability to pass TrustedOrigins when obtaining the token, + /// which tests the enhanced authentication. + /// This endpoint is unfortunately not supported by the directLine client which is + /// why we add this custom code. + /// + /// A instance. + private async Task GetDirectLineTokenAsync() + { + using var client = new HttpClient(); + using var request = new HttpRequestMessage(HttpMethod.Post, "https://directline.botframework.com/v3/directline/tokens/generate"); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _options.DirectLineSecret); + request.Content = new StringContent( + JsonConvert.SerializeObject(new + { + User = new { Id = _user }, + TrustedOrigins = new[] { _originHeader.Value } + }), Encoding.UTF8, + "application/json"); + + using var response = await client.SendAsync(request).ConfigureAwait(false); + try + { + response.EnsureSuccessStatusCode(); + } + catch + { + // Log headers and body to help troubleshoot issues (the exception itself will be handled upstream). + var sb = new StringBuilder(); + sb.AppendLine($"Failed to get a directline token (response status was: {response.StatusCode})"); + sb.AppendLine("Response headers:"); + sb.AppendLine(JsonConvert.SerializeObject(response.Headers, Formatting.Indented)); + sb.AppendLine("Response body:"); + sb.AppendLine(response.Content.ReadAsStringAsync().GetAwaiter().GetResult()); + _logger.LogWarning(sb.ToString()); + throw; + } + + // Extract token from response + var body = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); + var tokenInfo = JsonConvert.DeserializeObject(body); + if (string.IsNullOrWhiteSpace(tokenInfo?.Token)) + { + throw new InvalidOperationException("Failed to acquire directLine token"); + } + + return tokenInfo; + } + } +} diff --git a/tests/functional/Libraries/TranscriptTestRunner/TestClients/DirectLineTestClientOptions.cs b/tests/functional/Libraries/TranscriptTestRunner/TestClients/DirectLineTestClientOptions.cs new file mode 100644 index 0000000000..a65cbb2741 --- /dev/null +++ b/tests/functional/Libraries/TranscriptTestRunner/TestClients/DirectLineTestClientOptions.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace TranscriptTestRunner.TestClients +{ + /// + /// Class with the options needed to run a test against a TesClient. + /// + public class DirectLineTestClientOptions + { + /// + /// Gets or sets the Id of the host bot. + /// + /// The Id of the host bot. + public string BotId { get; set; } + + /// + /// Gets or sets the secret for the connection with the test client. + /// + /// The secret for the connection with the test client. + public string DirectLineSecret { get; set; } + + /// + /// Gets or sets the timeout for starting a conversation in milliseconds (default is 120000). + /// + /// + /// The timeout for starting a conversation (in milliseconds). + /// + public int StartConversationTimeout { get; set; } = 120000; + + /// + /// Gets or sets the maximum number of attempts to start a conversation (default is 3). + /// + /// + /// The maximum number of attempts to start a conversation. + /// + public int StartConversationMaxAttempts { get; set; } = 3; + } +} diff --git a/tests/functional/Libraries/TranscriptTestRunner/TestRunner.cs b/tests/functional/Libraries/TranscriptTestRunner/TestRunner.cs new file mode 100644 index 0000000000..211dda897d --- /dev/null +++ b/tests/functional/Libraries/TranscriptTestRunner/TestRunner.cs @@ -0,0 +1,324 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using AdaptiveExpressions; +using Microsoft.Bot.Schema; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Newtonsoft.Json; +using Activity = Microsoft.Bot.Schema.Activity; + +namespace TranscriptTestRunner +{ + /// + /// Test runner implementation. + /// + public class TestRunner + { + private readonly ILogger _logger; + private readonly int _replyTimeout; + private readonly TestClientBase _testClient; + private Stopwatch _stopwatch; + private string _testScriptPath; + + /// + /// Initializes a new instance of the class. + /// + /// Test client to use. + /// The timeout for waiting for replies (in seconds). Default is 180. + /// Optional. Instance of to use. + public TestRunner(TestClientBase client, int replyTimeout = 180, ILogger logger = null) + { + _testClient = client; + _replyTimeout = replyTimeout; + _logger = logger ?? NullLogger.Instance; + } + + private Stopwatch Stopwatch + { + get + { + if (_stopwatch == null) + { + _stopwatch = new Stopwatch(); + _stopwatch.Start(); + } + + return _stopwatch; + } + } + + /// + /// Executes a test script with the test steps. + /// + /// Path to the file to use. + /// Optional. Parameter dictionary, every key surrounded by brackets as such: ${key} + /// found in the script will be replaced by its value. + /// Optional. The name of the method caller. + /// Optional. A that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + public async Task RunTestAsync(string testScriptPath, Dictionary scriptParams = null, [CallerMemberName] string callerName = "", CancellationToken cancellationToken = default) + { + var testFileName = $"{callerName} - {Path.GetFileNameWithoutExtension(testScriptPath)}"; + + _logger.LogInformation($"======== Running script: {testScriptPath} ========"); + + _testScriptPath = testScriptPath; + + await ExecuteTestScriptAsync(testFileName, cancellationToken, scriptParams).ConfigureAwait(false); + } + + /// + /// Sends an to the bot through the test client. + /// + /// to send. + /// Optional. A that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + public async Task SendActivityAsync(Activity sendActivity, CancellationToken cancellationToken = default) + { + _logger.LogInformation("Elapsed Time: {Elapsed}, User sends: {Text}", Stopwatch.Elapsed, sendActivity.Text); + await _testClient.SendActivityAsync(sendActivity, cancellationToken).ConfigureAwait(false); + } + + /// + /// Uploads a file through the test client. + /// + /// The file to upload. + /// Optional. A that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + public async Task UploadAsync(Stream file, CancellationToken cancellationToken = default) + { + _logger.LogInformation("Elapsed Time: {Elapsed}, Uploading file", Stopwatch.Elapsed); + await _testClient.UploadAsync(file, cancellationToken).ConfigureAwait(false); + } + + /// + /// Gets the next reply from the bot through the test client. + /// + /// Optional. A that can be used by other objects + /// or threads to receive notice of cancellation. + /// The reply Activity from the bot. + public async Task GetNextReplyAsync(CancellationToken cancellationToken = default) + { + var timeoutCheck = new Stopwatch(); + timeoutCheck.Start(); + while (true) + { + // Try to get the next activity. + var activity = await _testClient.GetNextReplyAsync(cancellationToken).ConfigureAwait(false); + if (activity != null && activity.Type != ActivityTypes.Trace && activity.Type != ActivityTypes.Typing) + { + _logger.LogInformation("Elapsed Time: {Elapsed}, Bot Responds: {Text}", Stopwatch.Elapsed, activity.Text); + + if (activity.Attachments != null && activity.Attachments.Any()) + { + foreach (var attachment in activity.Attachments) + { + _logger.LogInformation("Elapsed Time: {Elapsed}, Attachment included: {Type} - {Attachment}", Stopwatch.Elapsed, attachment.ContentType, attachment.Content); + } + } + + return activity; + } + + // Check timeout. + if (timeoutCheck.ElapsedMilliseconds > _replyTimeout * 1000) + { + throw new TimeoutException($"Operation timed out while waiting for a response from the bot after {timeoutCheck.ElapsedMilliseconds} milliseconds (current timeout is set to {_replyTimeout * 1000} milliseconds)."); + } + + // Wait a bit for the bot + await Task.Delay(TimeSpan.FromMilliseconds(100), cancellationToken).ConfigureAwait(false); + } + } + + /// + /// Validates the reply from the bot according to the validateAction parameter. + /// + /// The to validate the reply from the bot. + /// Optional. A that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + public async Task AssertReplyAsync(Action validateAction, CancellationToken cancellationToken = default) + { + var nextReply = await GetNextReplyAsync(cancellationToken).ConfigureAwait(false); + validateAction(nextReply); + } + + /// + /// Signs in to the bot through the test client. + /// + /// The sign in Url. + /// A task that represents the work queued to execute. + public async Task ClientSignInAsync(string signInUrl) + { + if (string.IsNullOrEmpty(signInUrl)) + { + throw new ArgumentNullException(signInUrl); + } + + if (!signInUrl.StartsWith("https://", StringComparison.Ordinal)) + { + throw new ArgumentException($"Sign in url is badly formatted. Url received: {signInUrl}"); + } + + await _testClient.SignInAsync(signInUrl).ConfigureAwait(false); + } + + /// + /// Validates an according to an expected activity . + /// + /// The expected activity of type . + /// The actual response received. + /// Optional. A that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + protected virtual Task AssertActivityAsync(TestScriptItem expectedActivity, Activity actualActivity, CancellationToken cancellationToken = default) + { + var templateRegex = new Regex(@"\{\{[\w\s]*\}\}"); + + foreach (var assertion in expectedActivity.Assertions) + { + var template = templateRegex.Match(assertion); + + if (template.Success) + { + ValidateVariable(template.Value, actualActivity); + } + + var (result, error) = Expression.Parse(assertion).TryEvaluate(actualActivity); + + if (!result) + { + throw new InvalidOperationException($"Assertion failed: {assertion}."); + } + + if (error != null) + { + throw new InvalidOperationException(error); + } + } + + return Task.CompletedTask; + } + + /// + /// Validates the variable date in the bots message with the value between double curly braces. + /// + /// The assertion containing the variable. + /// The activity with the message containing the date. + protected void ValidateVariable(string value, Activity actualActivity) + { + var dateRegex = new Regex(@"(\d{1,4}([.\-/])\d{1,2}([.\-/])\d{1,4})"); + var wordRegex = new Regex(@"[\w]+"); + + var dateMatch = dateRegex.Match(actualActivity.Text); + var resultExpression = string.Empty; + var expectedExpression = wordRegex.Match(value).Value; + var dateValue = string.Empty; + + if (dateMatch.Success) + { + dateValue = dateMatch.Value; + var date = Convert.ToDateTime(dateMatch.Value, CultureInfo.InvariantCulture); + resultExpression = EvaluateDate(date); + } + + if (resultExpression != expectedExpression) + { + throw new InvalidOperationException($"Assertion failed. The variable '{expectedExpression}' does not match with the value {dateValue}."); + } + + actualActivity.Text = actualActivity.Text.Replace(dateMatch.Value, value); + } + + private static string EvaluateDate(DateTime date) + { + var currentDate = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss.fffZ", CultureInfo.InvariantCulture); + var inputDate = date.ToString("yyyy-MM-ddTHH:mm:ss.fffZ", CultureInfo.InvariantCulture); + var expression = $"dateReadBack('{currentDate}', '{inputDate}')"; + var parsed = Expression.Parse(expression); + + if (parsed == null) + { + throw new InvalidOperationException("Null parsed expression"); + } + + // String.Empty is used to get the result of the prebuilt function in the parsed expression. + var (result, msg) = parsed.TryEvaluate(string.Empty); + + if (msg != null) + { + throw new InvalidOperationException("An error has occurred while evaluating the date"); + } + + return result.ToString(); + } + + private async Task ExecuteTestScriptAsync(string callerName, CancellationToken cancellationToken, Dictionary scriptParams = null) + { + _logger.LogInformation($"\n------ Starting test {callerName} ----------"); + + using var reader = new StreamReader(_testScriptPath); + var plainTestScript = await reader.ReadToEndAsync().ConfigureAwait(false); + + if (scriptParams != null && scriptParams.Any()) + { + var replacement = string.Join("|", scriptParams.Keys.Select(k => $@"\$\{{\s?{k}\s?\}}").ToArray()); + plainTestScript = Regex.Replace(plainTestScript, replacement, m => scriptParams[m.Value.Trim(new char[] { '$', '{', '}' })]); + } + + var testScript = JsonConvert.DeserializeObject(plainTestScript); + + foreach (var scriptActivity in testScript.Items) + { + switch (scriptActivity.Role) + { + case RoleTypes.User: + // Send the activity. + var sendActivity = new Activity + { + Type = scriptActivity.Type, + Text = scriptActivity.Text + }; + + await SendActivityAsync(sendActivity, cancellationToken).ConfigureAwait(false); + break; + + case RoleTypes.Bot: + if (IgnoreScriptActivity(scriptActivity)) + { + break; + } + + var nextReply = await GetNextReplyAsync(cancellationToken).ConfigureAwait(false); + await AssertActivityAsync(scriptActivity, nextReply, cancellationToken).ConfigureAwait(false); + break; + + default: + throw new InvalidOperationException($"Invalid script activity type {scriptActivity.Role}."); + } + } + + _logger.LogInformation($"======== Finished running script: {Stopwatch.Elapsed} =============\n"); + } + + private bool IgnoreScriptActivity(TestScriptItem activity) + { + return activity.Type == ActivityTypes.Trace || activity.Type == ActivityTypes.Typing; + } + } +} diff --git a/tests/functional/Libraries/TranscriptTestRunner/TestScript.cs b/tests/functional/Libraries/TranscriptTestRunner/TestScript.cs new file mode 100644 index 0000000000..0682a98c80 --- /dev/null +++ b/tests/functional/Libraries/TranscriptTestRunner/TestScript.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace TranscriptTestRunner +{ + /// + /// A Test Script that can be used for functional testing of bots. + /// + public class TestScript + { + /// + /// Gets the test script items. + /// + /// + /// The sequence of test scripts to perform to validate the bots behavior. + /// + [JsonProperty("items")] + public List Items { get; } = new List(); + } +} diff --git a/tests/functional/Libraries/TranscriptTestRunner/TestScriptItem.cs b/tests/functional/Libraries/TranscriptTestRunner/TestScriptItem.cs new file mode 100644 index 0000000000..24171c0912 --- /dev/null +++ b/tests/functional/Libraries/TranscriptTestRunner/TestScriptItem.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace TranscriptTestRunner +{ + /// + /// representation of an activity. + /// + public class TestScriptItem + { + /// + /// Gets or sets the activity type. + /// + /// + /// The activity type. + /// + [JsonProperty("type")] + public string Type { get; set; } + + /// + /// Gets or sets the activity role. + /// + /// + /// The activity role. + /// + [JsonProperty("role")] + public string Role { get; set; } + + /// + /// Gets or sets the activity text. + /// + /// + /// The activity text. + /// + [JsonProperty("text")] + public string Text { get; set; } + + /// + /// Gets the activity assertion collection. + /// + /// + /// The activity assertion collection. + /// + [JsonProperty("assertions")] + public List Assertions { get; } = new List(); + + /// + /// Prevents the serializer from creating the assertions collection if its empty. + /// + /// true if the assertions collection should be serialized. + public bool ShouldSerializeAssertions() + { + return Assertions.Count > 0; + } + } +} diff --git a/tests/functional/Libraries/TranscriptTestRunner/TranscriptTestRunner.csproj b/tests/functional/Libraries/TranscriptTestRunner/TranscriptTestRunner.csproj new file mode 100644 index 0000000000..357cf4ee10 --- /dev/null +++ b/tests/functional/Libraries/TranscriptTestRunner/TranscriptTestRunner.csproj @@ -0,0 +1,24 @@ + + + + Library + netstandard2.0 + latest + + + + bin\$(Configuration)\netstandard2.0\TranscriptTestRunner.xml + + + + + + + + + + + + + + diff --git a/tests/functional/Libraries/TranscriptTestRunner/XUnit/XUnitTestRunner.cs b/tests/functional/Libraries/TranscriptTestRunner/XUnit/XUnitTestRunner.cs new file mode 100644 index 0000000000..25add7bf40 --- /dev/null +++ b/tests/functional/Libraries/TranscriptTestRunner/XUnit/XUnitTestRunner.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using AdaptiveExpressions; +using Microsoft.Bot.Schema; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Xunit; + +namespace TranscriptTestRunner.XUnit +{ + /// + /// XUnit extension of . + /// + public class XUnitTestRunner : TestRunner + { + /// + /// Initializes a new instance of the class. + /// + /// Test client to use. + /// The timeout for waiting for replies (in seconds). Default is 180. + /// Optional. Instance of to use. + public XUnitTestRunner(TestClientBase client, int replyTimeout = 180, ILogger logger = null) + : base(client, replyTimeout, logger) + { + } + + /// + /// Validates an according to an expected activity using XUnit. + /// + /// The expected activity of type . + /// The actual response received. + /// Optional. A that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + protected override Task AssertActivityAsync(TestScriptItem expectedActivity, Activity actualActivity, CancellationToken cancellationToken = default) + { + var templateRegex = new Regex(@"\{\{[\w\s]*\}\}"); + + foreach (var assertion in expectedActivity.Assertions) + { + var template = templateRegex.Match(assertion); + + if (template.Success) + { + ValidateVariable(template.Value, actualActivity); + } + + var (result, _) = Expression.Parse(assertion).TryEvaluate(actualActivity); + + Assert.True(result, $"The bot's response was different than expected. The assertion: \"{assertion}\" was evaluated as false.\nExpected Activity:\n{JsonConvert.SerializeObject(expectedActivity, Formatting.Indented)}\nActual Activity:\n{JsonConvert.SerializeObject(actualActivity, Formatting.Indented)}"); + } + + return Task.CompletedTask; + } + } +} diff --git a/tests/functional/Libraries/TranscriptTestRunner/testscript.schema b/tests/functional/Libraries/TranscriptTestRunner/testscript.schema new file mode 100644 index 0000000000..f45715c6a1 --- /dev/null +++ b/tests/functional/Libraries/TranscriptTestRunner/testscript.schema @@ -0,0 +1,171 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "type": "object", + "description": "Schema for TestScript items.", + "additionalProperties": false, + "definitions": { + "TestScriptItem": { + "type": "object", + "description": "TestScript item.", + "title": "TestScriptItem", + "additionalProperties": false, + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "description": "The activity type.", + "title": "type", + "oneOf": [ + { + "const": "message", + "description": "The type value for message activities.", + "title": "message" + }, + { + "const": "contactRelationUpdate", + "description": "The type value for contact relation update activities.", + "title": "contactRelationUpdate" + }, + { + "const": "conversationUpdate", + "description": "The type value for conversation update activities.", + "title": "conversationUpdate" + }, + { + "const": "typing", + "description": "The type value for typing activities.", + "title": "typing" + }, + { + "const": "endOfConversation", + "description": "The type value for end of conversation activities.", + "title": "endOfConversation" + }, + { + "const": "event", + "description": "The type value for event activities.", + "title": "event" + }, + { + "const": "invoke", + "description": "The type value for invoke activities.", + "title": "invoke" + }, + { + "const": "deleteUserData", + "description": "The type value for delete user data activities.", + "title": "deleteUserData" + }, + { + "const": "messageUpdate", + "description": "The type value for message update activities.", + "title": "messageUpdate" + }, + { + "const": "messageDelete", + "description": "The type value for message delete activities.", + "title": "messageDelete" + }, + { + "const": "installationUpdate", + "description": "The type value for installation update activities.", + "title": "installationUpdate" + }, + { + "const": "messageReaction", + "description": "The type value for message reaction activities.", + "title": "messageReaction" + }, + { + "const": "suggestion", + "description": "The type value for suggestion activities.", + "title": "suggestion" + }, + { + "const": "trace", + "description": "The type value for trace activities.", + "title": "trace" + }, + { + "const": "handoff", + "description": "The type value for handoff activities.", + "title": "handoff" + } + ] + }, + "role": { + "type": "string", + "description": "Role of the entity behind the account.", + "title": "role", + "oneOf": [ + { + "const": "user", + "description": "User" + }, + { + "const": "bot", + "description": "Bot" + } + ] + }, + "text": { + "type": "string", + "description": "The text content of the message.", + "title": "text" + }, + "assertions": { + "type": "array", + "title": "Assertions to perform to validate Activity.", + "description": "Sequence of expressions which must evaluate to true.", + "items": { + "$ref": "#/definitions/condition", + "title": "Assertion", + "description": "Assertion as an expression, which must evaluate to true or it will fail the test script." + } + } + } + }, + "condition": { + "$role": "expression", + "description": "Boolean constant or expression to evaluate.", + "title": "condition", + "oneOf": [ + { + "$ref": "#/definitions/expression" + } + ] + }, + "expression": { + "$role": "expression", + "type": "string", + "description": "Expression to evaluate. More information at\n https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-adaptive-expressions?view=azure-bot-service-4.0&tabs=comparison#operators", + "title": "expression", + "examples": [ + "type", + "recipient.role", + "from.name", + "from.role", + "text", + "inputHint" + ] + } + }, + "properties": { + "$schema": { + "type": "string", + "description": "Schema.", + "title": "$schema" + }, + "items": { + "type": "array", + "description": "List of TestScript items.", + "title": "items", + "additionalItems": false, + "items": { + "$ref": "#/definitions/TestScriptItem" + } + } + } +} diff --git a/tests/functional/PackageReferences.props b/tests/functional/PackageReferences.props new file mode 100644 index 0000000000..8bae20ab44 --- /dev/null +++ b/tests/functional/PackageReferences.props @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/tests/functional/README.md b/tests/functional/README.md new file mode 100644 index 0000000000..2f12fba0b2 --- /dev/null +++ b/tests/functional/README.md @@ -0,0 +1,25 @@ +# Bot Framework Functional Tests + +This repository contains functional tests for the different [Bot Framework SDK](https://github.com/microsoft/botframework-sdk) tools. + +- [Skill functional tests](Tests/SkillFunctionalTests) aims to automate the testing of Host and Skill interactions through several scenarios in all available programming languages. +- [Transcript Test Runner](Libraries/TranscriptTestRunner) aims to simplify complex conversation scenarios against bots, starting from a transcript file with a user-bot recorded conversation. +- [Transcript Converter](Libraries/TranscriptConverter) takes a conversation transcript and converts it into JSON to be consumed by other projects. + +Head to [Docs](./Docs/) directory for more information. + +## Build Status + + | Branch | Description | Deploy Bot Resources Status | Run Test Scenarios Status | + |--------|-------------|-----------------------------|---------------------------| + | Main | 4.14.* Nightly Run | [![Build Status](https://dev.azure.com/FuseLabs/SDK_v4/_apis/build/status/FunctionalTests/02.A.%20Deploy%20skill%20bots%20(daily)?branchName=main)](https://dev.azure.com/FuseLabs/SDK_v4/_build/latest?definitionId=1229&branchName=main) | [![Build Status](https://dev.azure.com/FuseLabs/SDK_v4/_apis/build/status/FunctionalTests/02.B.%20Run%20skill%20test%20scenarios%20(daily)?branchName=main)](https://dev.azure.com/FuseLabs/SDK_v4/_build/latest?definitionId=1237&branchName=main)| + +## Contributing + +This project welcomes contributions and suggestions. Most contributions require you to agree to a +Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit . + +When you submit a pull request, a CLA bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA. + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). +For more information, see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. diff --git a/tests/functional/SECURITY.md b/tests/functional/SECURITY.md new file mode 100644 index 0000000000..e0dfff56a9 --- /dev/null +++ b/tests/functional/SECURITY.md @@ -0,0 +1,41 @@ + + +## Security + +Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). + +If you believe you have found a security vulnerability in any Microsoft-owned repository that meets Microsoft's [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)) of a security vulnerability, please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). + +If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). + +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + + * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. + +## Preferred Languages + +We prefer all communications to be in English. + +## Policy + +Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). + + diff --git a/tests/functional/SkillFunctionalTests.sln b/tests/functional/SkillFunctionalTests.sln new file mode 100644 index 0000000000..5c48eeb48d --- /dev/null +++ b/tests/functional/SkillFunctionalTests.sln @@ -0,0 +1,59 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29806.167 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SkillFunctionalTests", "Tests\SkillFunctionalTests\SkillFunctionalTests.csproj", "{DE0231D9-A9BE-4D4C-A830-7ADCF28EA5D6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TranscriptTestRunner", "Libraries\TranscriptTestRunner\TranscriptTestRunner.csproj", "{65C3A2E0-FDC5-4132-9980-3BAE230E9F2E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{BCBFF5A2-F2CD-4F82-BCD1-ED8E7950D82F}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + BotFramework-FunctionalTests.ruleset = BotFramework-FunctionalTests.ruleset + Directory.Build.props = Directory.Build.props + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TranscriptConverter", "Libraries\TranscriptConverter\TranscriptConverter.csproj", "{EF963904-79CB-4222-811A-E24B73E5DC1A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Libraries", "Libraries", "{B5E35C94-0880-4AEB-8F3C-940FECA7A8C7}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{5A0C68F1-6CA7-43D7-8F01-70A484D8D412}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TranscriptTestRunnerTests", "Tests\TranscriptTestRunnerTests\TranscriptTestRunnerTests.csproj", "{5CAE9F80-9BD3-480C-8504-552B31B265E0}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DE0231D9-A9BE-4D4C-A830-7ADCF28EA5D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DE0231D9-A9BE-4D4C-A830-7ADCF28EA5D6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DE0231D9-A9BE-4D4C-A830-7ADCF28EA5D6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DE0231D9-A9BE-4D4C-A830-7ADCF28EA5D6}.Release|Any CPU.Build.0 = Release|Any CPU + {65C3A2E0-FDC5-4132-9980-3BAE230E9F2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {65C3A2E0-FDC5-4132-9980-3BAE230E9F2E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {65C3A2E0-FDC5-4132-9980-3BAE230E9F2E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {65C3A2E0-FDC5-4132-9980-3BAE230E9F2E}.Release|Any CPU.Build.0 = Release|Any CPU + {EF963904-79CB-4222-811A-E24B73E5DC1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EF963904-79CB-4222-811A-E24B73E5DC1A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EF963904-79CB-4222-811A-E24B73E5DC1A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EF963904-79CB-4222-811A-E24B73E5DC1A}.Release|Any CPU.Build.0 = Release|Any CPU + {5CAE9F80-9BD3-480C-8504-552B31B265E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5CAE9F80-9BD3-480C-8504-552B31B265E0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5CAE9F80-9BD3-480C-8504-552B31B265E0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5CAE9F80-9BD3-480C-8504-552B31B265E0}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {DE0231D9-A9BE-4D4C-A830-7ADCF28EA5D6} = {5A0C68F1-6CA7-43D7-8F01-70A484D8D412} + {65C3A2E0-FDC5-4132-9980-3BAE230E9F2E} = {B5E35C94-0880-4AEB-8F3C-940FECA7A8C7} + {5CAE9F80-9BD3-480C-8504-552B31B265E0} = {5A0C68F1-6CA7-43D7-8F01-70A484D8D412} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {CF79F565-B676-4679-83A1-DDE5CB729723} + EndGlobalSection +EndGlobal diff --git a/tests/functional/Tests/Directory.Build.props b/tests/functional/Tests/Directory.Build.props new file mode 100644 index 0000000000..83fc7a9e36 --- /dev/null +++ b/tests/functional/Tests/Directory.Build.props @@ -0,0 +1,16 @@ + + + + true + + + + + + $(NoWarn);SA0001;CS1573;CS1591;CS1712;SX1309 + + + + + + \ No newline at end of file diff --git a/tests/functional/Tests/SkillFunctionalTests/CardActions/CardActionsTests.cs b/tests/functional/Tests/SkillFunctionalTests/CardActions/CardActionsTests.cs new file mode 100644 index 0000000000..25391c6fa0 --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/CardActions/CardActionsTests.cs @@ -0,0 +1,120 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Microsoft.Bot.Connector; +using Microsoft.Bot.Schema; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using SkillFunctionalTests.Common; +using TranscriptTestRunner; +using TranscriptTestRunner.XUnit; +using Xunit; +using Xunit.Abstractions; + +namespace SkillFunctionalTests.CardActions +{ + [Trait("TestCategory", "CardActions")] + public class CardActionsTests : ScriptTestBase + { + private readonly string _testScriptsFolder = Directory.GetCurrentDirectory() + @"/CardActions/TestScripts"; + + public CardActionsTests(ITestOutputHelper output) + : base(output) + { + } + + public static IEnumerable TestCases() + { + var channelIds = new List { Channels.Directline }; + + var deliverModes = new List + { + DeliveryModes.Normal, + DeliveryModes.ExpectReplies + }; + + var hostBots = new List + { + HostBot.WaterfallHostBotDotNet, + HostBot.WaterfallHostBotJS, + HostBot.WaterfallHostBotPython, + + // TODO: Enable this when the port to composer is ready + //HostBot.ComposerHostBotDotNet + }; + + var targetSkills = new List + { + SkillBotNames.WaterfallSkillBotDotNet, + SkillBotNames.WaterfallSkillBotJS, + SkillBotNames.WaterfallSkillBotPython, + + // TODO: Enable this when the port to composer is ready + //SkillBotNames.ComposerSkillBotDotNet + }; + + var scripts = new List + { + "BotAction.json", + "TaskModule.json", + "SubmitAction.json", + "Hero.json", + "Thumbnail.json", + "Receipt.json", + "SignIn.json", + "Carousel.json", + "List.json", + "O365.json", + "Animation.json", + "Audio.json", + "Video.json" + }; + + var testCaseBuilder = new TestCaseBuilder(); + + // This local function is used to exclude ExpectReplies, O365 and WaterfallSkillBotPython test cases + static bool ShouldExclude(TestCase testCase) + { + if (testCase.Script == "O365.json") + { + // BUG: O365 fails with ExpectReplies for WaterfallSkillBotPython (remove when https://github.com/microsoft/BotFramework-FunctionalTests/issues/328 is fixed). + if (testCase.TargetSkill == SkillBotNames.WaterfallSkillBotPython && testCase.DeliveryMode == DeliveryModes.ExpectReplies) + { + return true; + } + } + + return false; + } + + var testCases = testCaseBuilder.BuildTestCases(channelIds, deliverModes, hostBots, targetSkills, scripts, ShouldExclude); + foreach (var testCase in testCases) + { + yield return testCase; + } + } + + [Theory] + [MemberData(nameof(TestCases))] + public async Task RunTestCases(TestCaseDataObject testData) + { + var testCase = testData.GetObject(); + Logger.LogInformation(JsonConvert.SerializeObject(testCase, Formatting.Indented)); + + var options = TestClientOptions[testCase.HostBot]; + var runner = new XUnitTestRunner(new TestClientFactory(testCase.ChannelId, options, Logger).GetTestClient(), TestRequestTimeout, Logger); + + var testParams = new Dictionary + { + { "DeliveryMode", testCase.DeliveryMode }, + { "TargetSkill", testCase.TargetSkill } + }; + + await runner.RunTestAsync(Path.Combine(_testScriptsFolder, "WaterfallGreeting.json"), testParams); + await runner.RunTestAsync(Path.Combine(_testScriptsFolder, testCase.Script), testParams); + } + } +} diff --git a/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Animation.json b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Animation.json new file mode 100644 index 0000000000..237082091f --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Animation.json @@ -0,0 +1,58 @@ +{ + "items": [ + { + "type": "message", + "role": "user", + "text": "Animation" + }, + { + "type": "message", + "role": "bot", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "attachmentLayout == 'list'", + "inputHint == 'acceptingInput'", + "attachments[0].contentType == 'application/vnd.microsoft.card.animation'", + "attachments[0].content.title == 'Animation Card'", + "attachments[0].content.autostart == True" + ] + }, + { + "type": "message", + "role": "bot", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "speak == 'What card do you want?'", + "inputHint == 'expectingInput'" + ] + }, + { + "type": "message", + "role": "user", + "text": "end" + }, + { + "type": "message", + "role": "bot", + "text": "Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?'", + "speak == 'Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?'", + "inputHint == 'expectingInput'", + "suggestedActions.actions[0].type == 'imBack'", + "suggestedActions.actions[0].title == 'normal'", + "suggestedActions.actions[0].value == 'normal'", + "suggestedActions.actions[1].type == 'imBack'", + "suggestedActions.actions[1].title == 'expectReplies'", + "suggestedActions.actions[1].value == 'expectReplies'" + ] + } + ] +} \ No newline at end of file diff --git a/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Animation.transcript b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Animation.transcript new file mode 100644 index 0000000000..a2c1586a72 --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Animation.transcript @@ -0,0 +1,165 @@ +[ + { + "channelData": { + "clientActivityID": "1611839814734xukiykq7r6", + "clientTimestamp": "2021-01-28T13:16:54.734Z" + }, + "text": "Animation", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "04e743bc-b422-4dc5-8a8a-6d006bfc6661", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T13:16:54.799Z", + "conversation": { + "id": "063c4460-616b-11eb-881a-afe376da3863|livechat" + }, + "id": "1776d1f0-616b-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:16:54-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "063c4460-616b-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "04e743bc-b422-4dc5-8a8a-6d006bfc6661", + "role": "user" + }, + "attachmentLayout": "list", + "locale": "", + "inputHint": "acceptingInput", + "attachments": [ + { + "contentType": "application/vnd.microsoft.card.animation", + "content": { + "title": "Animation Card", + "media": [ + { + "url": "https://media3.giphy.com/media/xT0xeJpnrWC4XWblEk/giphy.gif?cid=ecf05e47mye7k75sup6tcmadoom8p1q8u03a7g2p3f76upp9&rid=giphy.gif" + } + ], + "autostart": true + } + } + ], + "entities": [], + "replyToId": "1776d1f0-616b-11eb-b46e-c3c3213a748e", + "id": "183d0820-616b-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:16:56-03:00", + "timestamp": "2021-01-28T13:16:56.097Z" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "063c4460-616b-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "04e743bc-b422-4dc5-8a8a-6d006bfc6661", + "role": "user" + }, + "locale": "", + "text": "What card do you want?\n\n 1. AdaptiveCardBotAction\n 2. AdaptiveCardTaskModule\n 3. AdaptiveCardSumbitAction\n 4. Hero\n 5. Thumbnail\n 6. Receipt\n 7. Signin\n 8. Carousel\n 9. List\n 10. O365\n 11. TeamsFileConsent\n 12. Animation\n 13. Audio\n 14. Video\n 15. End", + "speak": "What card do you want?", + "inputHint": "expectingInput", + "attachments": [], + "entities": [], + "replyToId": "1776d1f0-616b-11eb-b46e-c3c3213a748e", + "id": "18699660-616b-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:16:56-03:00", + "timestamp": "2021-01-28T13:16:56.390Z" + }, + { + "channelData": { + "clientActivityID": "16118398192100re8kcctazwj", + "clientTimestamp": "2021-01-28T13:16:59.210Z" + }, + "text": "end", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "04e743bc-b422-4dc5-8a8a-6d006bfc6661", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T13:16:59.271Z", + "conversation": { + "id": "063c4460-616b-11eb-881a-afe376da3863|livechat" + }, + "id": "1a213170-616b-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:16:59-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "063c4460-616b-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "04e743bc-b422-4dc5-8a8a-6d006bfc6661", + "role": "user" + }, + "locale": "", + "text": "Done with \"WaterfallSkillBotDotNet\". \n\n What delivery mode would you like to use?", + "speak": "Done with \"WaterfallSkillBotDotNet\". \n\n What delivery mode would you like to use?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "normal", + "value": "normal" + }, + { + "type": "imBack", + "title": "expectReplies", + "value": "expectReplies" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "1a213170-616b-11eb-b46e-c3c3213a748e", + "id": "1adc9230-616b-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:17:00-03:00", + "timestamp": "2021-01-28T13:17:00.498Z" + } +] diff --git a/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Audio.json b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Audio.json new file mode 100644 index 0000000000..2f7f16a576 --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Audio.json @@ -0,0 +1,58 @@ +{ + "items": [ + { + "type": "message", + "role": "user", + "text": "Audio" + }, + { + "type": "message", + "role": "bot", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "attachmentLayout == 'list'", + "inputHint == 'acceptingInput'", + "attachments[0].contentType == 'application/vnd.microsoft.card.audio'", + "attachments[0].content.title == 'Audio Card'", + "attachments[0].content.autoloop == True" + ] + }, + { + "type": "message", + "role": "bot", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "speak == 'What card do you want?'", + "inputHint == 'expectingInput'" + ] + }, + { + "type": "message", + "role": "user", + "text": "end" + }, + { + "type": "message", + "role": "bot", + "text": "Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?'", + "speak == 'Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?'", + "inputHint == 'expectingInput'", + "suggestedActions.actions[0].type == 'imBack'", + "suggestedActions.actions[0].title == 'normal'", + "suggestedActions.actions[0].value == 'normal'", + "suggestedActions.actions[1].type == 'imBack'", + "suggestedActions.actions[1].title == 'expectReplies'", + "suggestedActions.actions[1].value == 'expectReplies'" + ] + } + ] +} \ No newline at end of file diff --git a/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Audio.transcript b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Audio.transcript new file mode 100644 index 0000000000..4b83bab546 --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Audio.transcript @@ -0,0 +1,165 @@ +[ + { + "channelData": { + "clientActivityID": "1611839872412avts1ortbzo", + "clientTimestamp": "2021-01-28T13:17:52.412Z" + }, + "text": "Audio", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "e0681749-455e-4df6-ac1d-cce532da5262", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T13:17:52.463Z", + "conversation": { + "id": "2f090f90-616b-11eb-881a-afe376da3863|livechat" + }, + "id": "39d5a5f0-616b-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:17:52-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "2f090f90-616b-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "e0681749-455e-4df6-ac1d-cce532da5262", + "role": "user" + }, + "attachmentLayout": "list", + "locale": "", + "inputHint": "acceptingInput", + "attachments": [ + { + "contentType": "application/vnd.microsoft.card.audio", + "content": { + "title": "Audio Card", + "media": [ + { + "url": "https://bffnwaterfallskillbotdotnet.azurewebsites.net/api/music" + } + ], + "autoloop": true + } + } + ], + "entities": [], + "replyToId": "39d5a5f0-616b-11eb-b46e-c3c3213a748e", + "id": "3a6c1990-616b-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:17:53-03:00", + "timestamp": "2021-01-28T13:17:53.449Z" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "2f090f90-616b-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "e0681749-455e-4df6-ac1d-cce532da5262", + "role": "user" + }, + "locale": "", + "text": "What card do you want?\n\n 1. AdaptiveCardBotAction\n 2. AdaptiveCardTaskModule\n 3. AdaptiveCardSumbitAction\n 4. Hero\n 5. Thumbnail\n 6. Receipt\n 7. Signin\n 8. Carousel\n 9. List\n 10. O365\n 11. TeamsFileConsent\n 12. Animation\n 13. Audio\n 14. Video\n 15. End", + "speak": "What card do you want?", + "inputHint": "expectingInput", + "attachments": [], + "entities": [], + "replyToId": "39d5a5f0-616b-11eb-b46e-c3c3213a748e", + "id": "3a98f5f0-616b-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:17:53-03:00", + "timestamp": "2021-01-28T13:17:53.743Z" + }, + { + "channelData": { + "clientActivityID": "16118398766380tfc3r379ha", + "clientTimestamp": "2021-01-28T13:17:56.638Z" + }, + "text": "end", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "e0681749-455e-4df6-ac1d-cce532da5262", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T13:17:56.693Z", + "conversation": { + "id": "2f090f90-616b-11eb-881a-afe376da3863|livechat" + }, + "id": "3c5b3f60-616b-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:17:56-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "2f090f90-616b-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "e0681749-455e-4df6-ac1d-cce532da5262", + "role": "user" + }, + "locale": "", + "text": "Done with \"WaterfallSkillBotDotNet\". \n\n What delivery mode would you like to use?", + "speak": "Done with \"WaterfallSkillBotDotNet\". \n\n What delivery mode would you like to use?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "normal", + "value": "normal" + }, + { + "type": "imBack", + "title": "expectReplies", + "value": "expectReplies" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "3c5b3f60-616b-11eb-b46e-c3c3213a748e", + "id": "3cfcfda0-616b-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:17:57-03:00", + "timestamp": "2021-01-28T13:17:57.754Z" + } +] diff --git a/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/BotAction.json b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/BotAction.json new file mode 100644 index 0000000000..f3b7f58912 --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/BotAction.json @@ -0,0 +1,77 @@ +{ + "items": [ + { + "type": "message", + "role": "user", + "text": "AdaptiveCardBotAction" + }, + { + "type": "message", + "role": "bot", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "attachmentLayout == 'list'", + "inputHint == 'acceptingInput'", + "attachments[0].contentType == 'application/vnd.microsoft.card.adaptive'", + "attachments[0].content.type == 'AdaptiveCard'", + "attachments[0].content.body[0].type == 'TextBlock'", + "attachments[0].content.body[0].text == 'Bot Builder actions'", + "attachments[0].content.actions[0].type == 'Action.Submit'", + "attachments[0].content.actions[0].data.msteams.type == 'imBack'", + "attachments[0].content.actions[0].data.msteams.value == 'text'", + "attachments[0].content.actions[0].title == 'imBack'", + "attachments[0].content.actions[1].type == 'Action.Submit'", + "attachments[0].content.actions[1].data.msteams.type == 'messageBack'", + "attachments[0].content.actions[1].data.msteams.value.key == 'value'", + "attachments[0].content.actions[1].title == 'message back'", + "attachments[0].content.actions[2].type == 'Action.Submit'", + "attachments[0].content.actions[2].data.msteams.type == 'messageBack'", + "attachments[0].content.actions[2].data.msteams.text == 'text received by bots'", + "attachments[0].content.actions[2].data.msteams.displayText == 'display text message back'", + "attachments[0].content.actions[2].data.msteams.value.key == 'value'", + "attachments[0].content.actions[2].title == 'message back local echo'", + "attachments[0].content.actions[3].type == 'Action.Submit'", + "attachments[0].content.actions[3].data.msteams.type == 'invoke'", + "attachments[0].content.actions[3].data.msteams.value.key == 'value'", + "attachments[0].content.actions[3].title == 'invoke'" + ] + }, + { + "type": "message", + "role": "bot", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "speak == 'What card do you want?'", + "inputHint == 'expectingInput'" + ] + }, + { + "type": "message", + "role": "user", + "text": "end" + }, + { + "type": "message", + "role": "bot", + "text": "Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?'", + "speak == 'Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?'", + "inputHint == 'expectingInput'", + "suggestedActions.actions[0].type == 'imBack'", + "suggestedActions.actions[0].title == 'normal'", + "suggestedActions.actions[0].value == 'normal'", + "suggestedActions.actions[1].type == 'imBack'", + "suggestedActions.actions[1].title == 'expectReplies'", + "suggestedActions.actions[1].value == 'expectReplies'" + ] + } + ] +} diff --git a/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/BotAction.transcript b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/BotAction.transcript new file mode 100644 index 0000000000..089ea60de3 --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/BotAction.transcript @@ -0,0 +1,216 @@ +[ + { + "channelData": { + "clientActivityID": "1611838440775fk6vbo5p5nw", + "clientTimestamp": "2021-01-28T12:54:00.775Z" + }, + "text": "AdaptiveCardBotAction", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "7da4d5a3-9aaf-4a03-bd62-1495536c852e", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T12:54:00.810Z", + "conversation": { + "id": "d5f4ad40-6167-11eb-881a-afe376da3863|livechat" + }, + "id": "e480bca0-6167-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T09:54:00-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "d5f4ad40-6167-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "7da4d5a3-9aaf-4a03-bd62-1495536c852e", + "role": "user" + }, + "attachmentLayout": "list", + "locale": "", + "inputHint": "acceptingInput", + "attachments": [ + { + "contentType": "application/vnd.microsoft.card.adaptive", + "content": { + "type": "AdaptiveCard", + "version": "1.2", + "body": [ + { + "type": "TextBlock", + "text": "Bot Builder actions" + } + ], + "actions": [ + { + "type": "Action.Submit", + "data": { + "msteams": { + "type": "imBack", + "value": "text" + } + }, + "title": "imBack" + }, + { + "type": "Action.Submit", + "data": { + "msteams": { + "type": "messageBack", + "value": { + "key": "value" + } + } + }, + "title": "message back" + }, + { + "type": "Action.Submit", + "data": { + "msteams": { + "type": "messageBack", + "text": "text received by bots", + "displayText": "display text message back", + "value": { + "key": "value" + } + } + }, + "title": "message back local echo" + }, + { + "type": "Action.Submit", + "data": { + "msteams": { + "type": "invoke", + "value": { + "key": "value" + } + } + }, + "title": "invoke" + } + ] + } + } + ], + "entities": [], + "replyToId": "e480bca0-6167-11eb-b46e-c3c3213a748e", + "id": "e59fc130-6167-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T09:54:02-03:00", + "timestamp": "2021-01-28T12:54:02.691Z" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "d5f4ad40-6167-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "7da4d5a3-9aaf-4a03-bd62-1495536c852e", + "role": "user" + }, + "locale": "", + "text": "What card do you want?\n\n 1. AdaptiveCardBotAction\n 2. AdaptiveCardTaskModule\n 3. AdaptiveCardSumbitAction\n 4. Hero\n 5. Thumbnail\n 6. Receipt\n 7. Signin\n 8. Carousel\n 9. List\n 10. O365\n 11. TeamsFileConsent\n 12. Animation\n 13. Audio\n 14. Video\n 15. End", + "speak": "What card do you want?", + "inputHint": "expectingInput", + "attachments": [], + "entities": [], + "replyToId": "e480bca0-6167-11eb-b46e-c3c3213a748e", + "id": "e5cb3e00-6167-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T09:54:02-03:00", + "timestamp": "2021-01-28T12:54:02.976Z" + }, + { + "channelData": { + "clientActivityID": "1611838581343cy25vk7jmo9", + "clientTimestamp": "2021-01-28T12:56:21.344Z" + }, + "text": "end", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "7da4d5a3-9aaf-4a03-bd62-1495536c852e", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T12:56:21.384Z", + "conversation": { + "id": "d5f4ad40-6167-11eb-881a-afe376da3863|livechat" + }, + "id": "384aa080-6168-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T09:56:21-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "d5f4ad40-6167-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "7da4d5a3-9aaf-4a03-bd62-1495536c852e", + "role": "user" + }, + "locale": "", + "text": "Done with \"WaterfallSkillBotDotNet\". \n\n What delivery mode would you like to use?", + "speak": "Done with \"WaterfallSkillBotDotNet\". \n\n What delivery mode would you like to use?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "normal", + "value": "normal" + }, + { + "type": "imBack", + "title": "expectReplies", + "value": "expectReplies" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "384aa080-6168-11eb-b46e-c3c3213a748e", + "id": "3948af90-6168-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T09:56:23-03:00", + "timestamp": "2021-01-28T12:56:23.049Z" + } +] diff --git a/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Carousel.json b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Carousel.json new file mode 100644 index 0000000000..1bf2d60fcd --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Carousel.json @@ -0,0 +1,76 @@ +{ + "items": [ + { + "type": "message", + "role": "user", + "text": "Carousel" + }, + { + "type": "message", + "role": "bot", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "attachmentLayout == 'carousel'", + "inputHint == 'acceptingInput'", + "attachments[0].contentType == 'application/vnd.microsoft.card.hero'", + "attachments[0].content.title == 'BotFramework Hero Card'", + "attachments[0].content.subtitle == 'Microsoft Bot Framework'", + "attachments[0].content.text == 'Build and connect intelligent bots to interact with your users naturally wherever they are, from text/sms to Skype, Slack, Office 365 mail and other popular services.'", + "attachments[0].content.buttons[0].type == 'openUrl'", + "attachments[0].content.buttons[0].title == 'Get Started'", + "attachments[0].content.buttons[0].value == 'https://docs.microsoft.com/bot-framework'", + "attachments[1].contentType == 'application/vnd.microsoft.card.hero'", + "attachments[1].content.title == 'BotFramework Hero Card'", + "attachments[1].content.subtitle == 'Microsoft Bot Framework'", + "attachments[1].content.text == 'Build and connect intelligent bots to interact with your users naturally wherever they are, from text/sms to Skype, Slack, Office 365 mail and other popular services.'", + "attachments[1].content.buttons[0].type == 'openUrl'", + "attachments[1].content.buttons[0].title == 'Get Started'", + "attachments[1].content.buttons[0].value == 'https://docs.microsoft.com/bot-framework'", + "attachments[2].contentType == 'application/vnd.microsoft.card.hero'", + "attachments[2].content.title == 'BotFramework Hero Card'", + "attachments[2].content.subtitle == 'Microsoft Bot Framework'", + "attachments[2].content.text == 'Build and connect intelligent bots to interact with your users naturally wherever they are, from text/sms to Skype, Slack, Office 365 mail and other popular services.'", + "attachments[2].content.buttons[0].type == 'openUrl'", + "attachments[2].content.buttons[0].title == 'Get Started'", + "attachments[2].content.buttons[0].value == 'https://docs.microsoft.com/bot-framework'" + ] + }, + { + "type": "message", + "role": "bot", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "speak == 'What card do you want?'", + "inputHint == 'expectingInput'" + ] + }, + { + "type": "message", + "role": "user", + "text": "end" + }, + { + "type": "message", + "role": "bot", + "text": "Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?'", + "speak == 'Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?'", + "inputHint == 'expectingInput'", + "suggestedActions.actions[0].type == 'imBack'", + "suggestedActions.actions[0].title == 'normal'", + "suggestedActions.actions[0].value == 'normal'", + "suggestedActions.actions[1].type == 'imBack'", + "suggestedActions.actions[1].title == 'expectReplies'", + "suggestedActions.actions[1].value == 'expectReplies'" + ] + } + ] +} \ No newline at end of file diff --git a/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Carousel.transcript b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Carousel.transcript new file mode 100644 index 0000000000..1a9de47a96 --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Carousel.transcript @@ -0,0 +1,213 @@ +[ + { + "channelData": { + "clientActivityID": "1611839522508zaakfbpcfw", + "clientTimestamp": "2021-01-28T13:12:02.508Z" + }, + "text": "Carousel", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "43ff7934-8228-49a8-9925-6d469752f983", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T13:12:02.562Z", + "conversation": { + "id": "60a4e8e0-616a-11eb-881a-afe376da3863|livechat" + }, + "id": "6946fe20-616a-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:12:02-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "60a4e8e0-616a-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "43ff7934-8228-49a8-9925-6d469752f983", + "role": "user" + }, + "attachmentLayout": "carousel", + "locale": "", + "inputHint": "acceptingInput", + "attachments": [ + { + "contentType": "application/vnd.microsoft.card.hero", + "content": { + "title": "BotFramework Hero Card", + "subtitle": "Microsoft Bot Framework", + "text": "Build and connect intelligent bots to interact with your users naturally wherever they are, from text/sms to Skype, Slack, Office 365 mail and other popular services.", + "images": [ + { + "url": "https://sec.ch9.ms/ch9/7ff5/e07cfef0-aa3b-40bb-9baa-7c9ef8ff7ff5/buildreactionbotframework_960.jpg" + } + ], + "buttons": [ + { + "type": "openUrl", + "title": "Get Started", + "value": "https://docs.microsoft.com/bot-framework" + } + ] + } + }, + { + "contentType": "application/vnd.microsoft.card.hero", + "content": { + "title": "BotFramework Hero Card", + "subtitle": "Microsoft Bot Framework", + "text": "Build and connect intelligent bots to interact with your users naturally wherever they are, from text/sms to Skype, Slack, Office 365 mail and other popular services.", + "images": [ + { + "url": "https://sec.ch9.ms/ch9/7ff5/e07cfef0-aa3b-40bb-9baa-7c9ef8ff7ff5/buildreactionbotframework_960.jpg" + } + ], + "buttons": [ + { + "type": "openUrl", + "title": "Get Started", + "value": "https://docs.microsoft.com/bot-framework" + } + ] + } + }, + { + "contentType": "application/vnd.microsoft.card.hero", + "content": { + "title": "BotFramework Hero Card", + "subtitle": "Microsoft Bot Framework", + "text": "Build and connect intelligent bots to interact with your users naturally wherever they are, from text/sms to Skype, Slack, Office 365 mail and other popular services.", + "images": [ + { + "url": "https://sec.ch9.ms/ch9/7ff5/e07cfef0-aa3b-40bb-9baa-7c9ef8ff7ff5/buildreactionbotframework_960.jpg" + } + ], + "buttons": [ + { + "type": "openUrl", + "title": "Get Started", + "value": "https://docs.microsoft.com/bot-framework" + } + ] + } + } + ], + "entities": [], + "replyToId": "6946fe20-616a-11eb-b46e-c3c3213a748e", + "id": "69fedc70-616a-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:12:03-03:00", + "timestamp": "2021-01-28T13:12:03.767Z" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "60a4e8e0-616a-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "43ff7934-8228-49a8-9925-6d469752f983", + "role": "user" + }, + "locale": "", + "text": "What card do you want?\n\n 1. AdaptiveCardBotAction\n 2. AdaptiveCardTaskModule\n 3. AdaptiveCardSumbitAction\n 4. Hero\n 5. Thumbnail\n 6. Receipt\n 7. Signin\n 8. Carousel\n 9. List\n 10. O365\n 11. TeamsFileConsent\n 12. Animation\n 13. Audio\n 14. Video\n 15. End", + "speak": "What card do you want?", + "inputHint": "expectingInput", + "attachments": [], + "entities": [], + "replyToId": "6946fe20-616a-11eb-b46e-c3c3213a748e", + "id": "6a2af580-616a-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:12:04-03:00", + "timestamp": "2021-01-28T13:12:04.056Z" + }, + { + "channelData": { + "clientActivityID": "1611839526614stj36f82vy", + "clientTimestamp": "2021-01-28T13:12:06.614Z" + }, + "text": "end", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "43ff7934-8228-49a8-9925-6d469752f983", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T13:12:06.671Z", + "conversation": { + "id": "60a4e8e0-616a-11eb-881a-afe376da3863|livechat" + }, + "id": "6bb9f9f0-616a-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:12:06-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "60a4e8e0-616a-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "43ff7934-8228-49a8-9925-6d469752f983", + "role": "user" + }, + "locale": "", + "text": "Done with \"WaterfallSkillBotDotNet\". \n\n What delivery mode would you like to use?", + "speak": "Done with \"WaterfallSkillBotDotNet\". \n\n What delivery mode would you like to use?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "normal", + "value": "normal" + }, + { + "type": "imBack", + "title": "expectReplies", + "value": "expectReplies" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "6bb9f9f0-616a-11eb-b46e-c3c3213a748e", + "id": "6c559db0-616a-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:12:07-03:00", + "timestamp": "2021-01-28T13:12:07.691Z" + } +] diff --git a/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Hero.json b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Hero.json new file mode 100644 index 0000000000..d913abad6a --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Hero.json @@ -0,0 +1,62 @@ +{ + "items": [ + { + "type": "message", + "role": "user", + "text": "Hero" + }, + { + "type": "message", + "role": "bot", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "attachmentLayout == 'list'", + "inputHint == 'acceptingInput'", + "attachments[0].contentType == 'application/vnd.microsoft.card.hero'", + "attachments[0].content.title == 'BotFramework Hero Card'", + "attachments[0].content.subtitle == 'Microsoft Bot Framework'", + "attachments[0].content.text == 'Build and connect intelligent bots to interact with your users naturally wherever they are, from text/sms to Skype, Slack, Office 365 mail and other popular services.'", + "attachments[0].content.buttons[0].type == 'openUrl'", + "attachments[0].content.buttons[0].title == 'Get Started'", + "attachments[0].content.buttons[0].value == 'https://docs.microsoft.com/bot-framework'" + ] + }, + { + "type": "message", + "role": "bot", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "speak == 'What card do you want?'", + "inputHint == 'expectingInput'" + ] + }, + { + "type": "message", + "role": "user", + "text": "end" + }, + { + "type": "message", + "role": "bot", + "text": "Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?'", + "speak == 'Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?'", + "inputHint == 'expectingInput'", + "suggestedActions.actions[0].type == 'imBack'", + "suggestedActions.actions[0].title == 'normal'", + "suggestedActions.actions[0].value == 'normal'", + "suggestedActions.actions[1].type == 'imBack'", + "suggestedActions.actions[1].title == 'expectReplies'", + "suggestedActions.actions[1].value == 'expectReplies'" + ] + } + ] +} \ No newline at end of file diff --git a/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Hero.transcript b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Hero.transcript new file mode 100644 index 0000000000..9a95ed0474 --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Hero.transcript @@ -0,0 +1,173 @@ +[ + { + "channelData": { + "clientActivityID": "1611839251501nwivvhmw61i", + "clientTimestamp": "2021-01-28T13:07:31.501Z" + }, + "text": "Hero", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "f748dcdb-2d9b-4540-a2dc-341070aaf910", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T13:07:31.554Z", + "conversation": { + "id": "bd2d1200-6169-11eb-881a-afe376da3863|livechat" + }, + "id": "c7be7420-6169-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:07:31-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "bd2d1200-6169-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "f748dcdb-2d9b-4540-a2dc-341070aaf910", + "role": "user" + }, + "attachmentLayout": "list", + "locale": "", + "inputHint": "acceptingInput", + "attachments": [ + { + "contentType": "application/vnd.microsoft.card.hero", + "content": { + "title": "BotFramework Hero Card", + "subtitle": "Microsoft Bot Framework", + "text": "Build and connect intelligent bots to interact with your users naturally wherever they are, from text/sms to Skype, Slack, Office 365 mail and other popular services.", + "images": [ + { + "url": "https://sec.ch9.ms/ch9/7ff5/e07cfef0-aa3b-40bb-9baa-7c9ef8ff7ff5/buildreactionbotframework_960.jpg" + } + ], + "buttons": [ + { + "type": "openUrl", + "title": "Get Started", + "value": "https://docs.microsoft.com/bot-framework" + } + ] + } + } + ], + "entities": [], + "replyToId": "c7be7420-6169-11eb-b46e-c3c3213a748e", + "id": "c87519f0-6169-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:07:32-03:00", + "timestamp": "2021-01-28T13:07:32.750Z" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "bd2d1200-6169-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "f748dcdb-2d9b-4540-a2dc-341070aaf910", + "role": "user" + }, + "locale": "", + "text": "What card do you want?\n\n 1. AdaptiveCardBotAction\n 2. AdaptiveCardTaskModule\n 3. AdaptiveCardSumbitAction\n 4. Hero\n 5. Thumbnail\n 6. Receipt\n 7. Signin\n 8. Carousel\n 9. List\n 10. O365\n 11. TeamsFileConsent\n 12. Animation\n 13. Audio\n 14. Video\n 15. End", + "speak": "What card do you want?", + "inputHint": "expectingInput", + "attachments": [], + "entities": [], + "replyToId": "c7be7420-6169-11eb-b46e-c3c3213a748e", + "id": "c8e67960-6169-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:07:33-03:00", + "timestamp": "2021-01-28T13:07:33.494Z" + }, + { + "channelData": { + "clientActivityID": "1611839255359ffrgmvahfew", + "clientTimestamp": "2021-01-28T13:07:35.359Z" + }, + "text": "end", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "f748dcdb-2d9b-4540-a2dc-341070aaf910", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T13:07:35.435Z", + "conversation": { + "id": "bd2d1200-6169-11eb-881a-afe376da3863|livechat" + }, + "id": "ca0ea5b0-6169-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:07:35-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "bd2d1200-6169-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "f748dcdb-2d9b-4540-a2dc-341070aaf910", + "role": "user" + }, + "locale": "", + "text": "Done with \"WaterfallSkillBotDotNet\". \n\n What delivery mode would you like to use?", + "speak": "Done with \"WaterfallSkillBotDotNet\". \n\n What delivery mode would you like to use?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "normal", + "value": "normal" + }, + { + "type": "imBack", + "title": "expectReplies", + "value": "expectReplies" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "ca0ea5b0-6169-11eb-b46e-c3c3213a748e", + "id": "cab03ce0-6169-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:07:36-03:00", + "timestamp": "2021-01-28T13:07:36.494Z" + } +] diff --git a/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/List.json b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/List.json new file mode 100644 index 0000000000..d34f09b8de --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/List.json @@ -0,0 +1,76 @@ +{ + "items": [ + { + "type": "message", + "role": "user", + "text": "List" + }, + { + "type": "message", + "role": "bot", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "attachmentLayout == 'list'", + "inputHint == 'acceptingInput'", + "attachments[0].contentType == 'application/vnd.microsoft.card.hero'", + "attachments[0].content.title == 'BotFramework Hero Card'", + "attachments[0].content.subtitle == 'Microsoft Bot Framework'", + "attachments[0].content.text == 'Build and connect intelligent bots to interact with your users naturally wherever they are, from text/sms to Skype, Slack, Office 365 mail and other popular services.'", + "attachments[0].content.buttons[0].type == 'openUrl'", + "attachments[0].content.buttons[0].title == 'Get Started'", + "attachments[0].content.buttons[0].value == 'https://docs.microsoft.com/bot-framework'", + "attachments[1].contentType == 'application/vnd.microsoft.card.hero'", + "attachments[1].content.title == 'BotFramework Hero Card'", + "attachments[1].content.subtitle == 'Microsoft Bot Framework'", + "attachments[1].content.text == 'Build and connect intelligent bots to interact with your users naturally wherever they are, from text/sms to Skype, Slack, Office 365 mail and other popular services.'", + "attachments[1].content.buttons[0].type == 'openUrl'", + "attachments[1].content.buttons[0].title == 'Get Started'", + "attachments[1].content.buttons[0].value == 'https://docs.microsoft.com/bot-framework'", + "attachments[2].contentType == 'application/vnd.microsoft.card.hero'", + "attachments[2].content.title == 'BotFramework Hero Card'", + "attachments[2].content.subtitle == 'Microsoft Bot Framework'", + "attachments[2].content.text == 'Build and connect intelligent bots to interact with your users naturally wherever they are, from text/sms to Skype, Slack, Office 365 mail and other popular services.'", + "attachments[2].content.buttons[0].type == 'openUrl'", + "attachments[2].content.buttons[0].title == 'Get Started'", + "attachments[2].content.buttons[0].value == 'https://docs.microsoft.com/bot-framework'" + ] + }, + { + "type": "message", + "role": "bot", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "speak == 'What card do you want?'", + "inputHint == 'expectingInput'" + ] + }, + { + "type": "message", + "role": "user", + "text": "end" + }, + { + "type": "message", + "role": "bot", + "text": "Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?'", + "speak == 'Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?'", + "inputHint == 'expectingInput'", + "suggestedActions.actions[0].type == 'imBack'", + "suggestedActions.actions[0].title == 'normal'", + "suggestedActions.actions[0].value == 'normal'", + "suggestedActions.actions[1].type == 'imBack'", + "suggestedActions.actions[1].title == 'expectReplies'", + "suggestedActions.actions[1].value == 'expectReplies'" + ] + } + ] +} \ No newline at end of file diff --git a/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/List.transcript b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/List.transcript new file mode 100644 index 0000000000..ccad6421be --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/List.transcript @@ -0,0 +1,213 @@ +[ + { + "channelData": { + "clientActivityID": "1611839575098y19me2gcp5b", + "clientTimestamp": "2021-01-28T13:12:55.098Z" + }, + "text": "List", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "59cdcc89-e282-4417-9822-4d725675243b", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T13:12:55.143Z", + "conversation": { + "id": "7e64e060-616a-11eb-881a-afe376da3863|livechat" + }, + "id": "889e3770-616a-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:12:55-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "7e64e060-616a-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "59cdcc89-e282-4417-9822-4d725675243b", + "role": "user" + }, + "attachmentLayout": "list", + "locale": "", + "inputHint": "acceptingInput", + "attachments": [ + { + "contentType": "application/vnd.microsoft.card.hero", + "content": { + "title": "BotFramework Hero Card", + "subtitle": "Microsoft Bot Framework", + "text": "Build and connect intelligent bots to interact with your users naturally wherever they are, from text/sms to Skype, Slack, Office 365 mail and other popular services.", + "images": [ + { + "url": "https://sec.ch9.ms/ch9/7ff5/e07cfef0-aa3b-40bb-9baa-7c9ef8ff7ff5/buildreactionbotframework_960.jpg" + } + ], + "buttons": [ + { + "type": "openUrl", + "title": "Get Started", + "value": "https://docs.microsoft.com/bot-framework" + } + ] + } + }, + { + "contentType": "application/vnd.microsoft.card.hero", + "content": { + "title": "BotFramework Hero Card", + "subtitle": "Microsoft Bot Framework", + "text": "Build and connect intelligent bots to interact with your users naturally wherever they are, from text/sms to Skype, Slack, Office 365 mail and other popular services.", + "images": [ + { + "url": "https://sec.ch9.ms/ch9/7ff5/e07cfef0-aa3b-40bb-9baa-7c9ef8ff7ff5/buildreactionbotframework_960.jpg" + } + ], + "buttons": [ + { + "type": "openUrl", + "title": "Get Started", + "value": "https://docs.microsoft.com/bot-framework" + } + ] + } + }, + { + "contentType": "application/vnd.microsoft.card.hero", + "content": { + "title": "BotFramework Hero Card", + "subtitle": "Microsoft Bot Framework", + "text": "Build and connect intelligent bots to interact with your users naturally wherever they are, from text/sms to Skype, Slack, Office 365 mail and other popular services.", + "images": [ + { + "url": "https://sec.ch9.ms/ch9/7ff5/e07cfef0-aa3b-40bb-9baa-7c9ef8ff7ff5/buildreactionbotframework_960.jpg" + } + ], + "buttons": [ + { + "type": "openUrl", + "title": "Get Started", + "value": "https://docs.microsoft.com/bot-framework" + } + ] + } + } + ], + "entities": [], + "replyToId": "889e3770-616a-11eb-b46e-c3c3213a748e", + "id": "89448990-616a-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:12:56-03:00", + "timestamp": "2021-01-28T13:12:56.233Z" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "7e64e060-616a-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "59cdcc89-e282-4417-9822-4d725675243b", + "role": "user" + }, + "locale": "", + "text": "What card do you want?\n\n 1. AdaptiveCardBotAction\n 2. AdaptiveCardTaskModule\n 3. AdaptiveCardSumbitAction\n 4. Hero\n 5. Thumbnail\n 6. Receipt\n 7. Signin\n 8. Carousel\n 9. List\n 10. O365\n 11. TeamsFileConsent\n 12. Animation\n 13. Audio\n 14. Video\n 15. End", + "speak": "What card do you want?", + "inputHint": "expectingInput", + "attachments": [], + "entities": [], + "replyToId": "889e3770-616a-11eb-b46e-c3c3213a748e", + "id": "8970a2a0-616a-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:12:56-03:00", + "timestamp": "2021-01-28T13:12:56.522Z" + }, + { + "channelData": { + "clientActivityID": "1611839578738d2iy2wsylg", + "clientTimestamp": "2021-01-28T13:12:58.738Z" + }, + "text": "end", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "59cdcc89-e282-4417-9822-4d725675243b", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T13:12:58.788Z", + "conversation": { + "id": "7e64e060-616a-11eb-881a-afe376da3863|livechat" + }, + "id": "8aca6640-616a-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:12:58-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "7e64e060-616a-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "59cdcc89-e282-4417-9822-4d725675243b", + "role": "user" + }, + "locale": "", + "text": "Done with \"WaterfallSkillBotDotNet\". \n\n What delivery mode would you like to use?", + "speak": "Done with \"WaterfallSkillBotDotNet\". \n\n What delivery mode would you like to use?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "normal", + "value": "normal" + }, + { + "type": "imBack", + "title": "expectReplies", + "value": "expectReplies" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "8aca6640-616a-11eb-b46e-c3c3213a748e", + "id": "8b62d5b0-616a-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:12:59-03:00", + "timestamp": "2021-01-28T13:12:59.787Z" + } +] diff --git a/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/O365.json b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/O365.json new file mode 100644 index 0000000000..a36b000dfe --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/O365.json @@ -0,0 +1,181 @@ +{ + "items": [ + { + "type": "message", + "role": "user", + "text": "o365" + }, + { + "type": "message", + "role": "bot", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "attachmentLayout == 'list'", + "inputHint == 'acceptingInput'", + "attachments[0].contentType == 'application/vnd.microsoft.teams.card.o365connector'", + "attachments[0].content.title == 'card title'", + "attachments[0].content.text == 'card text'", + "attachments[0].content.summary == 'O365 card summary'", + "attachments[0].content.themeColor == '#E67A9E'", + "attachments[0].content.sections[0].title == '**section title**'", + "attachments[0].content.sections[0].text == 'section text'", + "attachments[0].content.sections[0].activityTitle == 'activity title'", + "attachments[0].content.sections[0].activitySubtitle == 'activity subtitle'", + "attachments[0].content.sections[0].activityText == 'activity text'", + "attachments[0].content.sections[0].activityImage == 'http://connectorsdemo.azurewebsites.net/images/MSC12_Oscar_002.jpg'", + "attachments[0].content.sections[0].activityImageType == 'avatar'", + "attachments[0].content.sections[0].markdown == True", + "attachments[0].content.sections[0].facts[0].name == 'Fact name 1'", + "attachments[0].content.sections[0].facts[0].value == 'Fact value 1'", + "attachments[0].content.sections[0].facts[1].name == 'Fact name 2'", + "attachments[0].content.sections[0].facts[1].value == 'Fact value 2'", + "attachments[0].content.sections[0].images[0].image == 'http://connectorsdemo.azurewebsites.net/images/MicrosoftSurface_024_Cafe_OH-06315_VS_R1c.jpg'", + "attachments[0].content.sections[0].images[0].title == 'image 1'", + "attachments[0].content.sections[0].images[1].image == 'http://connectorsdemo.azurewebsites.net/images/WIN12_Scene_01.jpg'", + "attachments[0].content.sections[0].images[1].title == 'image 2'", + "attachments[0].content.sections[0].images[2].image == 'http://connectorsdemo.azurewebsites.net/images/WIN12_Anthony_02.jpg'", + "attachments[0].content.sections[0].images[2].title == 'image 3'", + "attachments[0].content.potentialAction[0].@type == 'ActionCard'", + "attachments[0].content.potentialAction[0].inputs[0].@type == 'MultichoiceInput'", + "attachments[0].content.potentialAction[0].inputs[0].choices[0].display == 'Choice 1'", + "attachments[0].content.potentialAction[0].inputs[0].choices[1].display == 'Choice 2'", + "attachments[0].content.potentialAction[0].inputs[0].choices[2].display == 'Choice 3'", + "attachments[0].content.potentialAction[0].inputs[0].style == 'expanded'", + "attachments[0].content.potentialAction[0].inputs[0].isMultiSelect == True", + "attachments[0].content.potentialAction[0].inputs[0].id == 'list-1'", + "attachments[0].content.potentialAction[0].inputs[0].isRequired == True", + "attachments[0].content.potentialAction[0].inputs[0].title == 'Pick multiple options'", + "attachments[0].content.potentialAction[0].inputs[1].@type == 'MultichoiceInput'", + "attachments[0].content.potentialAction[0].inputs[1].choices[0].display == 'Choice 4'", + "attachments[0].content.potentialAction[0].inputs[1].choices[1].display == 'Choice 5'", + "attachments[0].content.potentialAction[0].inputs[1].choices[2].display == 'Choice 6'", + "attachments[0].content.potentialAction[0].inputs[1].style == 'compact'", + "attachments[0].content.potentialAction[0].inputs[1].isMultiSelect == True", + "attachments[0].content.potentialAction[0].inputs[1].id == 'list-2'", + "attachments[0].content.potentialAction[0].inputs[1].isRequired == True", + "attachments[0].content.potentialAction[0].inputs[1].title == 'Pick multiple options'", + "attachments[0].content.potentialAction[0].inputs[2].@type == 'MultichoiceInput'", + "attachments[0].content.potentialAction[0].inputs[2].choices[0].display == 'Choice a'", + "attachments[0].content.potentialAction[0].inputs[2].choices[0].value == 'a'", + "attachments[0].content.potentialAction[0].inputs[2].choices[1].display == 'Choice b'", + "attachments[0].content.potentialAction[0].inputs[2].choices[1].value == 'b'", + "attachments[0].content.potentialAction[0].inputs[2].choices[2].display == 'Choice c'", + "attachments[0].content.potentialAction[0].inputs[2].choices[2].value == 'c'", + "attachments[0].content.potentialAction[0].inputs[2].style == 'expanded'", + "attachments[0].content.potentialAction[0].inputs[2].isMultiSelect == False", + "attachments[0].content.potentialAction[0].inputs[2].id == 'list-3'", + "attachments[0].content.potentialAction[0].inputs[2].isRequired == False", + "attachments[0].content.potentialAction[0].inputs[2].title == 'Pick an option'", + "attachments[0].content.potentialAction[0].inputs[3].@type == 'MultichoiceInput'", + "attachments[0].content.potentialAction[0].inputs[3].choices[0].display == 'Choice x'", + "attachments[0].content.potentialAction[0].inputs[3].choices[0].value == 'x'", + "attachments[0].content.potentialAction[0].inputs[3].choices[1].display == 'Choice y'", + "attachments[0].content.potentialAction[0].inputs[3].choices[1].value == 'y'", + "attachments[0].content.potentialAction[0].inputs[3].choices[2].display == 'Choice z'", + "attachments[0].content.potentialAction[0].inputs[3].choices[2].value == 'z'", + "attachments[0].content.potentialAction[0].inputs[3].style == 'compact'", + "attachments[0].content.potentialAction[0].inputs[3].isMultiSelect == False", + "attachments[0].content.potentialAction[0].inputs[3].id == 'list-4'", + "attachments[0].content.potentialAction[0].inputs[3].isRequired == False", + "attachments[0].content.potentialAction[0].inputs[3].title == 'Pick an option'", + "attachments[0].content.potentialAction[0].actions[0].@type == 'HttpPOST'", + "attachments[0].content.potentialAction[0].actions[0].name == 'Send'", + "attachments[0].content.potentialAction[0].actions[0].@id == 'card-1-btn-1'", + "attachments[0].content.potentialAction[0].name == 'Multiple Choice'", + "attachments[0].content.potentialAction[0].@id == 'card-1'", + "attachments[0].content.potentialAction[1].@type == 'ActionCard'", + "attachments[0].content.potentialAction[1].inputs[0].@type == 'TextInput'", + "attachments[0].content.potentialAction[1].inputs[0].isMultiline == True", + "attachments[0].content.potentialAction[1].inputs[0].id == 'text-1'", + "attachments[0].content.potentialAction[1].inputs[0].isRequired == False", + "attachments[0].content.potentialAction[1].inputs[0].title == 'multiline, no maxLength'", + "attachments[0].content.potentialAction[1].inputs[1].@type == 'TextInput'", + "attachments[0].content.potentialAction[1].inputs[1].isMultiline == False", + "attachments[0].content.potentialAction[1].inputs[1].id == 'text-2'", + "attachments[0].content.potentialAction[1].inputs[1].isRequired == False", + "attachments[0].content.potentialAction[1].inputs[1].title == 'single line, no maxLength'", + "attachments[0].content.potentialAction[1].inputs[2].@type == 'TextInput'", + "attachments[0].content.potentialAction[1].inputs[2].isMultiline == True", + "attachments[0].content.potentialAction[1].inputs[2].id == 'text-3'", + "attachments[0].content.potentialAction[1].inputs[2].isRequired == True", + "attachments[0].content.potentialAction[1].inputs[2].title == 'multiline, max len = 10, isRequired'", + "attachments[0].content.potentialAction[1].inputs[3].@type == 'TextInput'", + "attachments[0].content.potentialAction[1].inputs[3].isMultiline == False", + "attachments[0].content.potentialAction[1].inputs[3].id == 'text-4'", + "attachments[0].content.potentialAction[1].inputs[3].isRequired == True", + "attachments[0].content.potentialAction[1].inputs[3].title == 'single line, max len = 10, isRequired'", + "attachments[0].content.potentialAction[1].actions[0].@type == 'HttpPOST'", + "attachments[0].content.potentialAction[1].actions[0].name == 'Send'", + "attachments[0].content.potentialAction[1].actions[0].@id == 'card-2-btn-1'", + "attachments[0].content.potentialAction[1].name == 'Text Input'", + "attachments[0].content.potentialAction[1].@id == 'card-2'", + "attachments[0].content.potentialAction[2].@type == 'ActionCard'", + "attachments[0].content.potentialAction[2].inputs[0].@type == 'DateInput'", + "attachments[0].content.potentialAction[2].inputs[0].includeTime == True", + "attachments[0].content.potentialAction[2].inputs[0].id == 'date-1'", + "attachments[0].content.potentialAction[2].inputs[0].isRequired == True", + "attachments[0].content.potentialAction[2].inputs[0].title == 'date with time'", + "attachments[0].content.potentialAction[2].inputs[1].@type == 'DateInput'", + "attachments[0].content.potentialAction[2].inputs[1].includeTime == False", + "attachments[0].content.potentialAction[2].inputs[1].id == 'date-2'", + "attachments[0].content.potentialAction[2].inputs[1].isRequired == False", + "attachments[0].content.potentialAction[2].inputs[1].title == 'date only'", + "attachments[0].content.potentialAction[2].actions[0].@type == 'HttpPOST'", + "attachments[0].content.potentialAction[2].actions[0].name == 'Send'", + "attachments[0].content.potentialAction[2].actions[0].@id == 'card-3-btn-1'", + "attachments[0].content.potentialAction[2].name == 'Date Input'", + "attachments[0].content.potentialAction[2].@id == 'card-3'", + "attachments[0].content.potentialAction[3].@type == 'ViewAction'", + "attachments[0].content.potentialAction[3].name == 'View Action'", + "attachments[0].content.potentialAction[4].@type == 'OpenUri'", + "attachments[0].content.potentialAction[4].targets[0].os == 'default'", + "attachments[0].content.potentialAction[4].targets[0].uri == 'http://microsoft.com'", + "attachments[0].content.potentialAction[4].targets[1].os == 'iOS'", + "attachments[0].content.potentialAction[4].targets[1].uri == 'http://microsoft.com'", + "attachments[0].content.potentialAction[4].targets[2].os == 'android'", + "attachments[0].content.potentialAction[4].targets[2].uri == 'http://microsoft.com'", + "attachments[0].content.potentialAction[4].targets[3].os == 'windows'", + "attachments[0].content.potentialAction[4].targets[3].uri == 'http://microsoft.com'", + "attachments[0].content.potentialAction[4].name == 'Open Uri'", + "attachments[0].content.potentialAction[4].@id == 'open-uri'" + ] + }, + { + "type": "message", + "role": "bot", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "speak == 'What card do you want?'", + "inputHint == 'expectingInput'" + ] + }, + { + "type": "message", + "role": "user", + "text": "end" + }, + { + "type": "message", + "role": "bot", + "text": "Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?'", + "speak == 'Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?'", + "inputHint == 'expectingInput'", + "suggestedActions.actions[0].type == 'imBack'", + "suggestedActions.actions[0].title == 'normal'", + "suggestedActions.actions[0].value == 'normal'", + "suggestedActions.actions[1].type == 'imBack'", + "suggestedActions.actions[1].title == 'expectReplies'", + "suggestedActions.actions[1].value == 'expectReplies'" + ] + } + ] +} \ No newline at end of file diff --git a/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/O365.transcript b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/O365.transcript new file mode 100644 index 0000000000..35486a16ed --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/O365.transcript @@ -0,0 +1,407 @@ +[ + { + "channelData": { + "clientActivityID": "16097853343944p9zoiqzu9d", + "clientTimestamp": "2021-01-04T18:35:34.394Z" + }, + "text": "o365", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "9dbd973e-a896-4b91-9d00-e98bb8dec6a2", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-04T18:35:34.423Z", + "conversation": { + "id": "9a3c8340-4ebb-11eb-9fc9-afbd749ec1ea|livechat" + }, + "id": "a1bbea70-4ebb-11eb-8029-87a6e92fcaab", + "localTimestamp": "2021-01-04T15:35:34-03:00", + "recipient": { + "id": "fce93a20-4e9c-11eb-9fc9-afbd749ec1ea", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://0e141a8d6e98.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://0e141a8d6e98.ngrok.io", + "channelId": "emulator", + "from": { + "id": "fce93a20-4e9c-11eb-9fc9-afbd749ec1ea", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "9a3c8340-4ebb-11eb-9fc9-afbd749ec1ea|livechat" + }, + "recipient": { + "id": "9dbd973e-a896-4b91-9d00-e98bb8dec6a2", + "role": "user" + }, + "attachmentLayout": "list", + "locale": "", + "inputHint": "acceptingInput", + "attachments": [ + { + "contentType": "application/vnd.microsoft.teams.card.o365connector", + "content": { + "title": "card title", + "text": "card text", + "summary": "O365 card summary", + "themeColor": "#E67A9E", + "sections": [ + { + "title": "**section title**", + "text": "section text", + "activityTitle": "activity title", + "activitySubtitle": "activity subtitle", + "activityText": "activity text", + "activityImage": "http://connectorsdemo.azurewebsites.net/images/MSC12_Oscar_002.jpg", + "activityImageType": "avatar", + "markdown": true, + "facts": [ + { + "name": "Fact name 1", + "value": "Fact value 1" + }, + { + "name": "Fact name 2", + "value": "Fact value 2" + } + ], + "images": [ + { + "image": "http://connectorsdemo.azurewebsites.net/images/MicrosoftSurface_024_Cafe_OH-06315_VS_R1c.jpg", + "title": "image 1" + }, + { + "image": "http://connectorsdemo.azurewebsites.net/images/WIN12_Scene_01.jpg", + "title": "image 2" + }, + { + "image": "http://connectorsdemo.azurewebsites.net/images/WIN12_Anthony_02.jpg", + "title": "image 3" + } + ] + } + ], + "potentialAction": [ + { + "@type": "ActionCard", + "inputs": [ + { + "@type": "MultichoiceInput", + "choices": [ + { + "display": "Choice 1", + "value": "1" + }, + { + "display": "Choice 2", + "value": "2" + }, + { + "display": "Choice 3", + "value": "3" + } + ], + "style": "expanded", + "isMultiSelect": true, + "id": "list-1", + "isRequired": true, + "title": "Pick multiple options" + }, + { + "@type": "MultichoiceInput", + "choices": [ + { + "display": "Choice 4", + "value": "4" + }, + { + "display": "Choice 5", + "value": "5" + }, + { + "display": "Choice 6", + "value": "6" + } + ], + "style": "compact", + "isMultiSelect": true, + "id": "list-2", + "isRequired": true, + "title": "Pick multiple options" + }, + { + "@type": "MultichoiceInput", + "choices": [ + { + "display": "Choice a", + "value": "a" + }, + { + "display": "Choice b", + "value": "b" + }, + { + "display": "Choice c", + "value": "c" + } + ], + "style": "expanded", + "isMultiSelect": false, + "id": "list-3", + "isRequired": false, + "title": "Pick an option" + }, + { + "@type": "MultichoiceInput", + "choices": [ + { + "display": "Choice x", + "value": "x" + }, + { + "display": "Choice y", + "value": "y" + }, + { + "display": "Choice z", + "value": "z" + } + ], + "style": "compact", + "isMultiSelect": false, + "id": "list-4", + "isRequired": false, + "title": "Pick an option" + } + ], + "actions": [ + { + "@type": "HttpPOST", + "body": "{\"list1\":\"{{list-1.value}}\", \"list2\":\"{{list-2.value}}\", \"list3\":\"{{list-3.value}}\", \"list4\":\"{{list-4.value}}\"}", + "name": "Send", + "@id": "card-1-btn-1" + } + ], + "name": "Multiple Choice", + "@id": "card-1" + }, + { + "@type": "ActionCard", + "inputs": [ + { + "@type": "TextInput", + "isMultiline": true, + "id": "text-1", + "isRequired": false, + "title": "multiline, no maxLength" + }, + { + "@type": "TextInput", + "isMultiline": false, + "id": "text-2", + "isRequired": false, + "title": "single line, no maxLength" + }, + { + "@type": "TextInput", + "isMultiline": true, + "maxLength": 10, + "id": "text-3", + "isRequired": true, + "title": "multiline, max len = 10, isRequired" + }, + { + "@type": "TextInput", + "isMultiline": false, + "maxLength": 10, + "id": "text-4", + "isRequired": true, + "title": "single line, max len = 10, isRequired" + } + ], + "actions": [ + { + "@type": "HttpPOST", + "body": "{\"text1\":\"{{text-1.value}}\", \"text2\":\"{{text-2.value}}\", \"text3\":\"{{text-3.value}}\", \"text4\":\"{{text-4.value}}\"}", + "name": "Send", + "@id": "card-2-btn-1" + } + ], + "name": "Text Input", + "@id": "card-2" + }, + { + "@type": "ActionCard", + "inputs": [ + { + "@type": "DateInput", + "includeTime": true, + "id": "date-1", + "isRequired": true, + "title": "date with time" + }, + { + "@type": "DateInput", + "includeTime": false, + "id": "date-2", + "isRequired": false, + "title": "date only" + } + ], + "actions": [ + { + "@type": "HttpPOST", + "body": "{\"date1\":\"{{date-1.value}}\", \"date2\":\"{{date-2.value}}\"}", + "name": "Send", + "@id": "card-3-btn-1" + } + ], + "name": "Date Input", + "@id": "card-3" + }, + { + "@type": "ViewAction", + "target": [ + "http://microsoft.com" + ], + "name": "View Action" + }, + { + "@type": "OpenUri", + "targets": [ + { + "os": "default", + "uri": "http://microsoft.com" + }, + { + "os": "iOS", + "uri": "http://microsoft.com" + }, + { + "os": "android", + "uri": "http://microsoft.com" + }, + { + "os": "windows", + "uri": "http://microsoft.com" + } + ], + "name": "Open Uri", + "@id": "open-uri" + } + ] + } + } + ], + "entities": [], + "replyToId": "a1bbea70-4ebb-11eb-8029-87a6e92fcaab", + "id": "a2567cc0-4ebb-11eb-8029-87a6e92fcaab", + "localTimestamp": "2021-01-04T15:35:35-03:00", + "timestamp": "2021-01-04T18:35:35.436Z" + }, + { + "type": "message", + "serviceUrl": "https://a3c0daa166e8.ngrok.io", + "channelId": "emulator", + "from": { + "id": "e6f62e00-5a57-11eb-856d-5fc1719d27ce", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "5cd758c0-5a5c-11eb-856d-5fc1719d27ce|livechat" + }, + "recipient": { + "id": "967d23e5-491c-4039-9edd-923a53617734", + "role": "user" + }, + "locale": "", + "text": "What card do you want?\n\n 1. AdaptiveCardBotAction\n 2. AdaptiveCardTaskModule\n 3. AdaptiveCardSumbitAction\n 4. Hero\n 5. Thumbnail\n 6. Receipt\n 7. Signin\n 8. Carousel\n 9. List\n 10. O365\n 11. TeamsFileConsent\n 12. Animation\n 13. Audio\n 14. Video\n 15. End", + "speak": "What card do you want?", + "inputHint": "expectingInput", + "attachments": [], + "entities": [], + "replyToId": "6511ab80-5a5c-11eb-9f42-afe5fddb4d6c", + "id": "6603aca0-5a5c-11eb-9f42-afe5fddb4d6c", + "localTimestamp": "2021-01-19T10:44:05-03:00", + "timestamp": "2021-01-19T13:44:05.994Z" + }, + { + "channelData": { + "clientActivityID": "1609785433694xe3les8ksd", + "clientTimestamp": "2021-01-04T18:37:13.694Z" + }, + "text": "end", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "9bdd62a5-6bf7-4c0c-a7b6-e954be30f2f0", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-04T18:37:13.730Z", + "conversation": { + "id": "cee77be0-4ebb-11eb-9fc9-afbd749ec1ea|livechat" + }, + "id": "dcecf620-4ebb-11eb-8029-87a6e92fcaab", + "localTimestamp": "2021-01-04T15:37:13-03:00", + "recipient": { + "id": "fce93a20-4e9c-11eb-9fc9-afbd749ec1ea", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://0e141a8d6e98.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://0e141a8d6e98.ngrok.io", + "channelId": "emulator", + "from": { + "id": "fce93a20-4e9c-11eb-9fc9-afbd749ec1ea", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "cee77be0-4ebb-11eb-9fc9-afbd749ec1ea|livechat" + }, + "recipient": { + "id": "9bdd62a5-6bf7-4c0c-a7b6-e954be30f2f0", + "role": "user" + }, + "locale": "", + "text": "Done with \"WaterfallSkillBotDotNet\". \n\n What delivery mode would you like to use?", + "speak": "Done with \"WaterfallSkillBotDotNet\". \n\n What delivery mode would you like to use?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "normal", + "value": "normal" + }, + { + "type": "imBack", + "title": "expectReplies", + "value": "expectReplies" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "dcecf620-4ebb-11eb-8029-87a6e92fcaab", + "id": "ddb8aa90-4ebb-11eb-8029-87a6e92fcaab", + "localTimestamp": "2021-01-04T15:37:15-03:00", + "timestamp": "2021-01-04T18:37:15.065Z" + } +] diff --git a/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Receipt.json b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Receipt.json new file mode 100644 index 0000000000..1709f44ccd --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Receipt.json @@ -0,0 +1,72 @@ +{ + "items": [ + { + "type": "message", + "role": "user", + "text": "Receipt" + }, + { + "type": "message", + "role": "bot", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "attachmentLayout == 'list'", + "inputHint == 'acceptingInput'", + "attachments[0].contentType == 'application/vnd.microsoft.card.receipt'", + "attachments[0].content.title == 'John Doe'", + "attachments[0].content.facts[0].key == 'Order Number'", + "attachments[0].content.facts[1].key == 'Payment Method'", + "attachments[0].content.facts[1].value == 'VISA 5555-****'", + "attachments[0].content.items[0].title == 'Data Transfer'", + "attachments[0].content.items[0].image.url == 'https://github.com/amido/azure-vector-icons/raw/master/renders/traffic-manager.png'", + "attachments[0].content.items[0].price == '$ 38.45'", + "attachments[0].content.items[1].title == 'App Service'", + "attachments[0].content.items[1].image.url == 'https://github.com/amido/azure-vector-icons/raw/master/renders/cloud-service.png'", + "attachments[0].content.items[1].price == '$ 45.00'", + "attachments[0].content.total == '$ 90.95'", + "attachments[0].content.tax == '$ 7.50'", + "attachments[0].content.buttons[0].type == 'openUrl'", + "attachments[0].content.buttons[0].title == 'More information'", + "attachments[0].content.buttons[0].image == 'https://account.windowsazure.com/content/6.10.1.38-.8225.160809-1618/aux-pre/images/offer-icon-freetrial.png'", + "attachments[0].content.buttons[0].value == 'https://azure.microsoft.com/en-us/pricing/'" + ] + }, + { + "type": "message", + "role": "bot", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "speak == 'What card do you want?'", + "inputHint == 'expectingInput'" + ] + }, + { + "type": "message", + "role": "user", + "text": "end" + }, + { + "type": "message", + "role": "bot", + "text": "Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?'", + "speak == 'Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?'", + "inputHint == 'expectingInput'", + "suggestedActions.actions[0].type == 'imBack'", + "suggestedActions.actions[0].title == 'normal'", + "suggestedActions.actions[0].value == 'normal'", + "suggestedActions.actions[1].type == 'imBack'", + "suggestedActions.actions[1].title == 'expectReplies'", + "suggestedActions.actions[1].value == 'expectReplies'" + ] + } + ] +} \ No newline at end of file diff --git a/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Receipt.transcript b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Receipt.transcript new file mode 100644 index 0000000000..e5205b5dc7 --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Receipt.transcript @@ -0,0 +1,197 @@ +[ + { + "channelData": { + "clientActivityID": "1611839371599wsbxze20iv", + "clientTimestamp": "2021-01-28T13:09:31.599Z" + }, + "text": "Receipt", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "3eb35847-111c-4ec7-86de-a54438c4a976", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T13:09:31.650Z", + "conversation": { + "id": "06cce110-616a-11eb-881a-afe376da3863|livechat" + }, + "id": "0f53a620-616a-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:09:31-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "06cce110-616a-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "3eb35847-111c-4ec7-86de-a54438c4a976", + "role": "user" + }, + "attachmentLayout": "list", + "locale": "", + "inputHint": "acceptingInput", + "attachments": [ + { + "contentType": "application/vnd.microsoft.card.receipt", + "content": { + "title": "John Doe", + "facts": [ + { + "key": "Order Number", + "value": "1234" + }, + { + "key": "Payment Method", + "value": "VISA 5555-****" + } + ], + "items": [ + { + "title": "Data Transfer", + "image": { + "url": "https://github.com/amido/azure-vector-icons/raw/master/renders/traffic-manager.png" + }, + "price": "$ 38.45", + "quantity": "368" + }, + { + "title": "App Service", + "image": { + "url": "https://github.com/amido/azure-vector-icons/raw/master/renders/cloud-service.png" + }, + "price": "$ 45.00", + "quantity": "720" + } + ], + "total": "$ 90.95", + "tax": "$ 7.50", + "buttons": [ + { + "type": "openUrl", + "title": "More information", + "image": "https://account.windowsazure.com/content/6.10.1.38-.8225.160809-1618/aux-pre/images/offer-icon-freetrial.png", + "value": "https://azure.microsoft.com/en-us/pricing/" + } + ] + } + } + ], + "entities": [], + "replyToId": "0f53a620-616a-11eb-b46e-c3c3213a748e", + "id": "0ff42be0-616a-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:09:32-03:00", + "timestamp": "2021-01-28T13:09:32.702Z" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "06cce110-616a-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "3eb35847-111c-4ec7-86de-a54438c4a976", + "role": "user" + }, + "locale": "", + "text": "What card do you want?\n\n 1. AdaptiveCardBotAction\n 2. AdaptiveCardTaskModule\n 3. AdaptiveCardSumbitAction\n 4. Hero\n 5. Thumbnail\n 6. Receipt\n 7. Signin\n 8. Carousel\n 9. List\n 10. O365\n 11. TeamsFileConsent\n 12. Animation\n 13. Audio\n 14. Video\n 15. End", + "speak": "What card do you want?", + "inputHint": "expectingInput", + "attachments": [], + "entities": [], + "replyToId": "0f53a620-616a-11eb-b46e-c3c3213a748e", + "id": "101fcfc0-616a-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:09:32-03:00", + "timestamp": "2021-01-28T13:09:32.988Z" + }, + { + "channelData": { + "clientActivityID": "1611839374573ek608u3396m", + "clientTimestamp": "2021-01-28T13:09:34.573Z" + }, + "text": "end", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "3eb35847-111c-4ec7-86de-a54438c4a976", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T13:09:34.793Z", + "conversation": { + "id": "06cce110-616a-11eb-881a-afe376da3863|livechat" + }, + "id": "11333b90-616a-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:09:34-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "06cce110-616a-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "3eb35847-111c-4ec7-86de-a54438c4a976", + "role": "user" + }, + "locale": "", + "text": "Done with \"WaterfallSkillBotDotNet\". \n\n What delivery mode would you like to use?", + "speak": "Done with \"WaterfallSkillBotDotNet\". \n\n What delivery mode would you like to use?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "normal", + "value": "normal" + }, + { + "type": "imBack", + "title": "expectReplies", + "value": "expectReplies" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "11333b90-616a-11eb-b46e-c3c3213a748e", + "id": "11ca2460-616a-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:09:35-03:00", + "timestamp": "2021-01-28T13:09:35.782Z" + } +] diff --git a/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/SignIn.json b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/SignIn.json new file mode 100644 index 0000000000..74415aeb59 --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/SignIn.json @@ -0,0 +1,60 @@ +{ + "items": [ + { + "type": "message", + "role": "user", + "text": "Signin" + }, + { + "type": "message", + "role": "bot", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "attachmentLayout == 'list'", + "inputHint == 'acceptingInput'", + "attachments[0].contentType == 'application/vnd.microsoft.card.signin'", + "attachments[0].content.text == 'BotFramework Sign-in Card'", + "attachments[0].content.buttons[0].type == 'signin'", + "attachments[0].content.buttons[0].title == 'Sign-in'", + "attachments[0].content.buttons[0].value == 'https://login.microsoftonline.com/'" + ] + }, + { + "type": "message", + "role": "bot", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "speak == 'What card do you want?'", + "inputHint == 'expectingInput'" + ] + }, + { + "type": "message", + "role": "user", + "text": "end" + }, + { + "type": "message", + "role": "bot", + "text": "Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?'", + "speak == 'Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?'", + "inputHint == 'expectingInput'", + "suggestedActions.actions[0].type == 'imBack'", + "suggestedActions.actions[0].title == 'normal'", + "suggestedActions.actions[0].value == 'normal'", + "suggestedActions.actions[1].type == 'imBack'", + "suggestedActions.actions[1].title == 'expectReplies'", + "suggestedActions.actions[1].value == 'expectReplies'" + ] + } + ] +} \ No newline at end of file diff --git a/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/SignIn.transcript b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/SignIn.transcript new file mode 100644 index 0000000000..93337e2ddc --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/SignIn.transcript @@ -0,0 +1,166 @@ +[ + { + "channelData": { + "clientActivityID": "1611839446716rvo7eet9ri", + "clientTimestamp": "2021-01-28T13:10:46.716Z" + }, + "text": "Signin", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "1eb07a8c-c293-47ab-aecf-0bf4201e7f43", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T13:10:46.812Z", + "conversation": { + "id": "32bac261-616a-11eb-881a-afe376da3863|livechat" + }, + "id": "3c209cd0-616a-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:10:46-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "32bac261-616a-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "1eb07a8c-c293-47ab-aecf-0bf4201e7f43", + "role": "user" + }, + "attachmentLayout": "list", + "locale": "", + "inputHint": "acceptingInput", + "attachments": [ + { + "contentType": "application/vnd.microsoft.card.signin", + "content": { + "text": "BotFramework Sign-in Card", + "buttons": [ + { + "type": "signin", + "title": "Sign-in", + "value": "https://login.microsoftonline.com/" + } + ] + } + } + ], + "entities": [], + "replyToId": "3c209cd0-616a-11eb-b46e-c3c3213a748e", + "id": "3cc47df0-616a-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:10:47-03:00", + "timestamp": "2021-01-28T13:10:47.887Z" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "32bac261-616a-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "1eb07a8c-c293-47ab-aecf-0bf4201e7f43", + "role": "user" + }, + "locale": "", + "text": "What card do you want?\n\n 1. AdaptiveCardBotAction\n 2. AdaptiveCardTaskModule\n 3. AdaptiveCardSumbitAction\n 4. Hero\n 5. Thumbnail\n 6. Receipt\n 7. Signin\n 8. Carousel\n 9. List\n 10. O365\n 11. TeamsFileConsent\n 12. Animation\n 13. Audio\n 14. Video\n 15. End", + "speak": "What card do you want?", + "inputHint": "expectingInput", + "attachments": [], + "entities": [], + "replyToId": "3c209cd0-616a-11eb-b46e-c3c3213a748e", + "id": "3cf0be10-616a-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:10:48-03:00", + "timestamp": "2021-01-28T13:10:48.177Z" + }, + { + "channelData": { + "clientActivityID": "16118394507003s21693ifj8", + "clientTimestamp": "2021-01-28T13:10:50.700Z" + }, + "text": "end", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "1eb07a8c-c293-47ab-aecf-0bf4201e7f43", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T13:10:50.748Z", + "conversation": { + "id": "32bac261-616a-11eb-881a-afe376da3863|livechat" + }, + "id": "3e790bc0-616a-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:10:50-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "32bac261-616a-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "1eb07a8c-c293-47ab-aecf-0bf4201e7f43", + "role": "user" + }, + "locale": "", + "text": "Done with \"WaterfallSkillBotDotNet\". \n\n What delivery mode would you like to use?", + "speak": "Done with \"WaterfallSkillBotDotNet\". \n\n What delivery mode would you like to use?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "normal", + "value": "normal" + }, + { + "type": "imBack", + "title": "expectReplies", + "value": "expectReplies" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "3e790bc0-616a-11eb-b46e-c3c3213a748e", + "id": "3f13c520-616a-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:10:51-03:00", + "timestamp": "2021-01-28T13:10:51.762Z" + } +] diff --git a/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/SubmitAction.json b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/SubmitAction.json new file mode 100644 index 0000000000..8509242e40 --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/SubmitAction.json @@ -0,0 +1,64 @@ +{ + "items": [ + { + "type": "message", + "role": "user", + "text": "AdaptiveCardSubmitAction" + }, + { + "type": "message", + "role": "bot", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "attachmentLayout == 'list'", + "inputHint == 'acceptingInput'", + "attachments[0].contentType == 'application/vnd.microsoft.card.adaptive'", + "attachments[0].content.type == 'AdaptiveCard'", + "attachments[0].content.body[0].type == 'TextBlock'", + "attachments[0].content.body[0].text == 'Bot Builder actions'", + "attachments[0].content.body[1].type == 'Input.Text'", + "attachments[0].content.body[1].id == 'x'", + "attachments[0].content.actions[0].type == 'Action.Submit'", + "attachments[0].content.actions[0].data.key == 'value'", + "attachments[0].content.actions[0].title == 'Action.Submit'" + ] + }, + { + "type": "message", + "role": "bot", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "speak == 'What card do you want?'", + "inputHint == 'expectingInput'" + ] + }, + { + "type": "message", + "role": "user", + "text": "end" + }, + { + "type": "message", + "role": "bot", + "text": "Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?'", + "speak == 'Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?'", + "inputHint == 'expectingInput'", + "suggestedActions.actions[0].type == 'imBack'", + "suggestedActions.actions[0].title == 'normal'", + "suggestedActions.actions[0].value == 'normal'", + "suggestedActions.actions[1].type == 'imBack'", + "suggestedActions.actions[1].title == 'expectReplies'", + "suggestedActions.actions[1].value == 'expectReplies'" + ] + } + ] +} \ No newline at end of file diff --git a/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/SubmitAction.transcript b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/SubmitAction.transcript new file mode 100644 index 0000000000..35ffbdf33c --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/SubmitAction.transcript @@ -0,0 +1,179 @@ +[ + { + "channelData": { + "clientActivityID": "1611839977312o3qfq3addts", + "clientTimestamp": "2021-01-28T13:19:37.312Z" + }, + "text": "AdaptiveCardSumbitAction", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "4ea03367-4ce0-46a8-901f-efcdb37418d3", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T13:19:37.385Z", + "conversation": { + "id": "6bc8f8a0-616b-11eb-881a-afe376da3863|livechat" + }, + "id": "785f7990-616b-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:19:37-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "6bc8f8a0-616b-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "4ea03367-4ce0-46a8-901f-efcdb37418d3", + "role": "user" + }, + "attachmentLayout": "list", + "locale": "", + "inputHint": "acceptingInput", + "attachments": [ + { + "contentType": "application/vnd.microsoft.card.adaptive", + "content": { + "type": "AdaptiveCard", + "version": "1.2", + "body": [ + { + "type": "TextBlock", + "text": "Bot Builder actions" + }, + { + "type": "Input.Text", + "id": "x" + } + ], + "actions": [ + { + "type": "Action.Submit", + "data": { + "key": "value" + }, + "title": "Action.Submit" + } + ] + } + } + ], + "entities": [], + "replyToId": "785f7990-616b-11eb-b46e-c3c3213a748e", + "id": "78f5ed30-616b-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:19:38-03:00", + "timestamp": "2021-01-28T13:19:38.371Z" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "6bc8f8a0-616b-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "4ea03367-4ce0-46a8-901f-efcdb37418d3", + "role": "user" + }, + "locale": "", + "text": "What card do you want?\n\n 1. AdaptiveCardBotAction\n 2. AdaptiveCardTaskModule\n 3. AdaptiveCardSumbitAction\n 4. Hero\n 5. Thumbnail\n 6. Receipt\n 7. Signin\n 8. Carousel\n 9. List\n 10. O365\n 11. TeamsFileConsent\n 12. Animation\n 13. Audio\n 14. Video\n 15. End", + "speak": "What card do you want?", + "inputHint": "expectingInput", + "attachments": [], + "entities": [], + "replyToId": "785f7990-616b-11eb-b46e-c3c3213a748e", + "id": "79227b70-616b-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:19:38-03:00", + "timestamp": "2021-01-28T13:19:38.663Z" + }, + { + "channelData": { + "clientActivityID": "1611839981867xrs9cyi53o9", + "clientTimestamp": "2021-01-28T13:19:41.867Z" + }, + "text": "end", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "4ea03367-4ce0-46a8-901f-efcdb37418d3", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T13:19:41.920Z", + "conversation": { + "id": "6bc8f8a0-616b-11eb-881a-afe376da3863|livechat" + }, + "id": "7b137600-616b-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:19:41-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "6bc8f8a0-616b-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "4ea03367-4ce0-46a8-901f-efcdb37418d3", + "role": "user" + }, + "locale": "", + "text": "Done with \"WaterfallSkillBotDotNet\". \n\n What delivery mode would you like to use?", + "speak": "Done with \"WaterfallSkillBotDotNet\". \n\n What delivery mode would you like to use?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "normal", + "value": "normal" + }, + { + "type": "imBack", + "title": "expectReplies", + "value": "expectReplies" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "7b137600-616b-11eb-b46e-c3c3213a748e", + "id": "7bbb27b0-616b-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:19:43-03:00", + "timestamp": "2021-01-28T13:19:43.019Z" + } +] diff --git a/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/TaskModule.json b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/TaskModule.json new file mode 100644 index 0000000000..d821b94cfd --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/TaskModule.json @@ -0,0 +1,62 @@ +{ + "items": [ + { + "type": "message", + "role": "user", + "text": "AdaptiveCardTeamsTaskModule" + }, + { + "type": "message", + "role": "bot", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "attachmentLayout == 'list'", + "inputHint == 'acceptingInput'", + "attachments[0].contentType == 'application/vnd.microsoft.card.adaptive'", + "attachments[0].content.type == 'AdaptiveCard'", + "attachments[0].content.body[0].type == 'TextBlock'", + "attachments[0].content.body[0].text == 'Task Module Adaptive Card'", + "attachments[0].content.actions[0].type == 'Action.Submit'", + "attachments[0].content.actions[0].data.msteams.type == 'invoke'", + "attachments[0].content.actions[0].title == 'Launch Task Module'" + ] + }, + { + "type": "message", + "role": "bot", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "speak == 'What card do you want?'", + "inputHint == 'expectingInput'" + ] + }, + { + "type": "message", + "role": "user", + "text": "end" + }, + { + "type": "message", + "role": "bot", + "text": "Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?'", + "speak == 'Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?'", + "inputHint == 'expectingInput'", + "suggestedActions.actions[0].type == 'imBack'", + "suggestedActions.actions[0].title == 'normal'", + "suggestedActions.actions[0].value == 'normal'", + "suggestedActions.actions[1].type == 'imBack'", + "suggestedActions.actions[1].title == 'expectReplies'", + "suggestedActions.actions[1].value == 'expectReplies'" + ] + } + ] +} \ No newline at end of file diff --git a/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/TaskModule.transcript b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/TaskModule.transcript new file mode 100644 index 0000000000..21df36d5ed --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/TaskModule.transcript @@ -0,0 +1,178 @@ +[ + { + "channelData": { + "clientActivityID": "16118391929936yytoz4eh0g", + "clientTimestamp": "2021-01-28T13:06:32.993Z" + }, + "text": "AdaptiveCardTaskModule", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "8402a6ae-fe88-4f1b-91cf-6026ed39d32a", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T13:06:33.044Z", + "conversation": { + "id": "9a706690-6169-11eb-881a-afe376da3863|livechat" + }, + "id": "a4de8940-6169-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:06:33-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "9a706690-6169-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "8402a6ae-fe88-4f1b-91cf-6026ed39d32a", + "role": "user" + }, + "attachmentLayout": "list", + "locale": "", + "inputHint": "acceptingInput", + "attachments": [ + { + "contentType": "application/vnd.microsoft.card.adaptive", + "content": { + "type": "AdaptiveCard", + "version": "1.2", + "body": [ + { + "type": "TextBlock", + "text": "Task Module Adaptive Card" + } + ], + "actions": [ + { + "type": "Action.Submit", + "data": { + "msteams": { + "type": "invoke", + "value": "{\r\n \"hiddenKey\": \"hidden value from task module launcher\",\r\n \"type\": \"task/fetch\"\r\n}" + } + }, + "title": "Launch Task Module" + } + ] + } + } + ], + "entities": [], + "replyToId": "a4de8940-6169-11eb-b46e-c3c3213a748e", + "id": "a5a115f0-6169-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:06:34-03:00", + "timestamp": "2021-01-28T13:06:34.319Z" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "9a706690-6169-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "8402a6ae-fe88-4f1b-91cf-6026ed39d32a", + "role": "user" + }, + "locale": "", + "text": "What card do you want?\n\n 1. AdaptiveCardBotAction\n 2. AdaptiveCardTaskModule\n 3. AdaptiveCardSumbitAction\n 4. Hero\n 5. Thumbnail\n 6. Receipt\n 7. Signin\n 8. Carousel\n 9. List\n 10. O365\n 11. TeamsFileConsent\n 12. Animation\n 13. Audio\n 14. Video\n 15. End", + "speak": "What card do you want?", + "inputHint": "expectingInput", + "attachments": [], + "entities": [], + "replyToId": "a4de8940-6169-11eb-b46e-c3c3213a748e", + "id": "a5cd07f0-6169-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:06:34-03:00", + "timestamp": "2021-01-28T13:06:34.607Z" + }, + { + "channelData": { + "clientActivityID": "1611839197644k7d79gt7vgr", + "clientTimestamp": "2021-01-28T13:06:37.644Z" + }, + "text": "end", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "8402a6ae-fe88-4f1b-91cf-6026ed39d32a", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T13:06:37.717Z", + "conversation": { + "id": "9a706690-6169-11eb-881a-afe376da3863|livechat" + }, + "id": "a7a79450-6169-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:06:37-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "9a706690-6169-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "8402a6ae-fe88-4f1b-91cf-6026ed39d32a", + "role": "user" + }, + "locale": "", + "text": "Done with \"WaterfallSkillBotDotNet\". \n\n What delivery mode would you like to use?", + "speak": "Done with \"WaterfallSkillBotDotNet\". \n\n What delivery mode would you like to use?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "normal", + "value": "normal" + }, + { + "type": "imBack", + "title": "expectReplies", + "value": "expectReplies" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "a7a79450-6169-11eb-b46e-c3c3213a748e", + "id": "a84e0d80-6169-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:06:38-03:00", + "timestamp": "2021-01-28T13:06:38.808Z" + } +] diff --git a/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Thumbnail.json b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Thumbnail.json new file mode 100644 index 0000000000..abf37c0aff --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Thumbnail.json @@ -0,0 +1,62 @@ +{ + "items": [ + { + "type": "message", + "role": "user", + "text": "Thumbnail" + }, + { + "type": "message", + "role": "bot", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "attachmentLayout == 'list'", + "inputHint == 'acceptingInput'", + "attachments[0].contentType == 'application/vnd.microsoft.card.thumbnail'", + "attachments[0].content.title == 'BotFramework Thumbnail Card'", + "attachments[0].content.subtitle == 'Microsoft Bot Framework'", + "attachments[0].content.text == 'Build and connect intelligent bots to interact with your users naturally wherever they are, from text/sms to Skype, Slack, Office 365 mail and other popular services.'", + "attachments[0].content.buttons[0].type == 'openUrl'", + "attachments[0].content.buttons[0].title == 'Get Started'", + "attachments[0].content.buttons[0].value == 'https://docs.microsoft.com/bot-framework'" + ] + }, + { + "type": "message", + "role": "bot", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "speak == 'What card do you want?'", + "inputHint == 'expectingInput'" + ] + }, + { + "type": "message", + "role": "user", + "text": "end" + }, + { + "type": "message", + "role": "bot", + "text": "Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?'", + "speak == 'Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?'", + "inputHint == 'expectingInput'", + "suggestedActions.actions[0].type == 'imBack'", + "suggestedActions.actions[0].title == 'normal'", + "suggestedActions.actions[0].value == 'normal'", + "suggestedActions.actions[1].type == 'imBack'", + "suggestedActions.actions[1].title == 'expectReplies'", + "suggestedActions.actions[1].value == 'expectReplies'" + ] + } + ] +} \ No newline at end of file diff --git a/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Thumbnail.transcript b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Thumbnail.transcript new file mode 100644 index 0000000000..dbae756d1d --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Thumbnail.transcript @@ -0,0 +1,173 @@ +[ + { + "channelData": { + "clientActivityID": "1611839317022nw5evtm02lt", + "clientTimestamp": "2021-01-28T13:08:37.022Z" + }, + "text": "Thumbnail", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "341b7ecf-7777-43c0-83a2-2882c6dc6a78", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T13:08:37.084Z", + "conversation": { + "id": "e57bac80-6169-11eb-881a-afe376da3863|livechat" + }, + "id": "eecd89c0-6169-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:08:37-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "e57bac80-6169-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "341b7ecf-7777-43c0-83a2-2882c6dc6a78", + "role": "user" + }, + "attachmentLayout": "list", + "locale": "", + "inputHint": "acceptingInput", + "attachments": [ + { + "contentType": "application/vnd.microsoft.card.thumbnail", + "content": { + "title": "BotFramework Thumbnail Card", + "subtitle": "Microsoft Bot Framework", + "text": "Build and connect intelligent bots to interact with your users naturally wherever they are, from text/sms to Skype, Slack, Office 365 mail and other popular services.", + "images": [ + { + "url": "https://sec.ch9.ms/ch9/7ff5/e07cfef0-aa3b-40bb-9baa-7c9ef8ff7ff5/buildreactionbotframework_960.jpg" + } + ], + "buttons": [ + { + "type": "openUrl", + "title": "Get Started", + "value": "https://docs.microsoft.com/bot-framework" + } + ] + } + } + ], + "entities": [], + "replyToId": "eecd89c0-6169-11eb-b46e-c3c3213a748e", + "id": "ef633a10-6169-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:08:38-03:00", + "timestamp": "2021-01-28T13:08:38.064Z" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "e57bac80-6169-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "341b7ecf-7777-43c0-83a2-2882c6dc6a78", + "role": "user" + }, + "locale": "", + "text": "What card do you want?\n\n 1. AdaptiveCardBotAction\n 2. AdaptiveCardTaskModule\n 3. AdaptiveCardSumbitAction\n 4. Hero\n 5. Thumbnail\n 6. Receipt\n 7. Signin\n 8. Carousel\n 9. List\n 10. O365\n 11. TeamsFileConsent\n 12. Animation\n 13. Audio\n 14. Video\n 15. End", + "speak": "What card do you want?", + "inputHint": "expectingInput", + "attachments": [], + "entities": [], + "replyToId": "eecd89c0-6169-11eb-b46e-c3c3213a748e", + "id": "ef8eb6e0-6169-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:08:38-03:00", + "timestamp": "2021-01-28T13:08:38.350Z" + }, + { + "channelData": { + "clientActivityID": "1611839320481tz2ppzyepo9", + "clientTimestamp": "2021-01-28T13:08:40.481Z" + }, + "text": "end", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "341b7ecf-7777-43c0-83a2-2882c6dc6a78", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T13:08:40.529Z", + "conversation": { + "id": "e57bac80-6169-11eb-881a-afe376da3863|livechat" + }, + "id": "f0db3410-6169-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:08:40-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "e57bac80-6169-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "341b7ecf-7777-43c0-83a2-2882c6dc6a78", + "role": "user" + }, + "locale": "", + "text": "Done with \"WaterfallSkillBotDotNet\". \n\n What delivery mode would you like to use?", + "speak": "Done with \"WaterfallSkillBotDotNet\". \n\n What delivery mode would you like to use?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "normal", + "value": "normal" + }, + { + "type": "imBack", + "title": "expectReplies", + "value": "expectReplies" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "f0db3410-6169-11eb-b46e-c3c3213a748e", + "id": "f18e5770-6169-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:08:41-03:00", + "timestamp": "2021-01-28T13:08:41.703Z" + } +] diff --git a/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Video.json b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Video.json new file mode 100644 index 0000000000..2e96bb8001 --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Video.json @@ -0,0 +1,58 @@ +{ + "items": [ + { + "type": "message", + "role": "user", + "text": "Video" + }, + { + "type": "message", + "role": "bot", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "attachmentLayout == 'list'", + "inputHint == 'acceptingInput'", + "attachments[0].contentType == 'application/vnd.microsoft.card.video'", + "attachments[0].content.title == 'Video Card'", + "attachments[0].content.media[0].url == 'https://www.youtube.com/watch?v=LvqzubPZjHE'" + ] + }, + { + "type": "message", + "role": "bot", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "speak == 'What card do you want?'", + "inputHint == 'expectingInput'" + ] + }, + { + "type": "message", + "role": "user", + "text": "end" + }, + { + "type": "message", + "role": "bot", + "text": "Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?'", + "speak == 'Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?'", + "inputHint == 'expectingInput'", + "suggestedActions.actions[0].type == 'imBack'", + "suggestedActions.actions[0].title == 'normal'", + "suggestedActions.actions[0].value == 'normal'", + "suggestedActions.actions[1].type == 'imBack'", + "suggestedActions.actions[1].title == 'expectReplies'", + "suggestedActions.actions[1].value == 'expectReplies'" + ] + } + ] +} \ No newline at end of file diff --git a/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Video.transcript b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Video.transcript new file mode 100644 index 0000000000..884aa68788 --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/Video.transcript @@ -0,0 +1,164 @@ +[ + { + "channelData": { + "clientActivityID": "1611839916459etjxoqosqtr", + "clientTimestamp": "2021-01-28T13:18:36.459Z" + }, + "text": "Video", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "7563d50d-a9b1-48fd-aaf9-e0d4d59fc0e2", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T13:18:36.513Z", + "conversation": { + "id": "4b43edb0-616b-11eb-881a-afe376da3863|livechat" + }, + "id": "54172510-616b-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:18:36-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "4b43edb0-616b-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "7563d50d-a9b1-48fd-aaf9-e0d4d59fc0e2", + "role": "user" + }, + "attachmentLayout": "list", + "locale": "", + "inputHint": "acceptingInput", + "attachments": [ + { + "contentType": "application/vnd.microsoft.card.video", + "content": { + "title": "Video Card", + "media": [ + { + "url": "https://www.youtube.com/watch?v=LvqzubPZjHE" + } + ] + } + } + ], + "entities": [], + "replyToId": "54172510-616b-11eb-b46e-c3c3213a748e", + "id": "54b86e20-616b-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:18:37-03:00", + "timestamp": "2021-01-28T13:18:37.570Z" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "4b43edb0-616b-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "7563d50d-a9b1-48fd-aaf9-e0d4d59fc0e2", + "role": "user" + }, + "locale": "", + "text": "What card do you want?\n\n 1. AdaptiveCardBotAction\n 2. AdaptiveCardTaskModule\n 3. AdaptiveCardSumbitAction\n 4. Hero\n 5. Thumbnail\n 6. Receipt\n 7. Signin\n 8. Carousel\n 9. List\n 10. O365\n 11. TeamsFileConsent\n 12. Animation\n 13. Audio\n 14. Video\n 15. End", + "speak": "What card do you want?", + "inputHint": "expectingInput", + "attachments": [], + "entities": [], + "replyToId": "54172510-616b-11eb-b46e-c3c3213a748e", + "id": "54e4d550-616b-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:18:37-03:00", + "timestamp": "2021-01-28T13:18:37.861Z" + }, + { + "channelData": { + "clientActivityID": "1611839921730zsw4gsfltfm", + "clientTimestamp": "2021-01-28T13:18:41.731Z" + }, + "text": "end", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "7563d50d-a9b1-48fd-aaf9-e0d4d59fc0e2", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T13:18:41.787Z", + "conversation": { + "id": "4b43edb0-616b-11eb-881a-afe376da3863|livechat" + }, + "id": "573be4b0-616b-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:18:41-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "4b43edb0-616b-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "7563d50d-a9b1-48fd-aaf9-e0d4d59fc0e2", + "role": "user" + }, + "locale": "", + "text": "Done with \"WaterfallSkillBotDotNet\". \n\n What delivery mode would you like to use?", + "speak": "Done with \"WaterfallSkillBotDotNet\". \n\n What delivery mode would you like to use?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "normal", + "value": "normal" + }, + { + "type": "imBack", + "title": "expectReplies", + "value": "expectReplies" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "573be4b0-616b-11eb-b46e-c3c3213a748e", + "id": "57d31ba0-616b-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:18:42-03:00", + "timestamp": "2021-01-28T13:18:42.778Z" + } +] diff --git a/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/WaterfallGreeting.json b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/WaterfallGreeting.json new file mode 100644 index 0000000000..af4438a1de --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/WaterfallGreeting.json @@ -0,0 +1,148 @@ +{ + "items": [ + { + "type": "conversationUpdate", + "role": "user" + }, + { + "type": "message", + "role": "bot", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "attachmentLayout == 'list'", + "speak == 'Welcome to the waterfall host bot'", + "inputHint == 'acceptingInput'", + "attachments[0].contentType == 'application/vnd.microsoft.card.adaptive'", + "attachments[0].content.type == 'AdaptiveCard'", + "attachments[0].content.body[0].type == 'Image'", + "attachments[0].content.body[0].size == 'stretch'", + "attachments[0].content.body[1].type == 'TextBlock'", + "attachments[0].content.body[1].spacing == 'Medium'", + "attachments[0].content.body[1].size == 'Medium'", + "attachments[0].content.body[1].weight == 'Bolder'", + "attachments[0].content.body[1].text == 'Welcome to the Skill Dialog Sample!'", + "attachments[0].content.body[1].wrap == True", + "attachments[0].content.body[1].color == 'Accent'", + "attachments[0].content.body[2].type == 'TextBlock'", + "attachments[0].content.body[2].text == 'This sample allows you to connect to a skill using a SkillDialog and invoke several actions.'", + "attachments[0].content.body[2].wrap == True" + ] + }, + { + "type": "message", + "role": "bot", + "text": "What delivery mode would you like to use?", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'What delivery mode would you like to use?'", + "speak == 'What delivery mode would you like to use?'", + "inputHint == 'expectingInput'", + "suggestedActions.actions[0].type == 'imBack'", + "suggestedActions.actions[0].title == 'normal'", + "suggestedActions.actions[0].value == 'normal'", + "suggestedActions.actions[1].type == 'imBack'", + "suggestedActions.actions[1].title == 'expectReplies'", + "suggestedActions.actions[1].value == 'expectReplies'" + ] + }, + { + "type": "message", + "role": "user", + "text": "${DeliveryMode}" + }, + { + "type": "message", + "role": "bot", + "text": "What group of skills would you like to use?", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'What group of skills would you like to use?'", + "speak == 'What group of skills would you like to use?'", + "inputHint == 'expectingInput'", + "suggestedActions.actions[0].type == 'imBack'", + "suggestedActions.actions[0].title == 'Echo' || suggestedActions.actions[0].title == 'Waterfall' || suggestedActions.actions[0].title == 'Teams'", + "suggestedActions.actions[0].value == 'Echo' || suggestedActions.actions[0].title == 'Waterfall' || suggestedActions.actions[0].title == 'Teams'" + ] + }, + { + "type": "message", + "role": "user", + "text": "Waterfall" + }, + { + "type": "message", + "role": "bot", + "text": "What skill would you like to call?\n\n 1. WaterfallSkillBotDotNet\n 2. WaterfallSkillBotJS\n 3. WaterfallSkillBotPython", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "startsWith(text, 'What skill would you like to call?')", + "speak == 'What skill would you like to call?'", + "inputHint == 'expectingInput'" + ] + }, + { + "type": "message", + "role": "user", + "text": "${TargetSkill}" + }, + { + "type": "message", + "role": "bot", + "text": "Select an action # to send to **${TargetSkill}**.\n\n 1. Cards\n 2. Proactive\n 3. Auth\n 4. MessageWithAttachment\n 5. Sso\n 6. FileUpload\n 7. Echo\n 8. Delete\n 9. Update", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'Select an action to send to **${TargetSkill}**.\n\n 1. Cards\n 2. Proactive\n 3. Auth\n 4. MessageWithAttachment\n 5. Sso\n 6. FileUpload\n 7. Echo\n 8. Delete\n 9. Update'", + "speak == 'Select an action to send to **${TargetSkill}**.'", + "inputHint == 'expectingInput'" + ] + }, + { + "type": "message", + "role": "user", + "text": "Cards" + }, + { + "type": "trace", + "role": "bot", + "assertions": [ + "type == 'trace'", + "from.role == 'bot'", + "recipient.role == 'user'", + "label == 'Got ActivityType: event'", + "name == 'ActivityRouterDialog.ProcessActivityAsync()'" + ] + }, + { + "type": "trace", + "role": "bot", + "assertions": [ + "type == 'trace'", + "from.role == 'bot'", + "recipient.role == 'user'", + "label == 'Name: Cards. Value: '", + "name == 'ActivityRouterDialog.OnEventActivityAsync()'" + ] + }, + { + "type": "message", + "role": "bot", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "speak == 'What card do you want?'", + "inputHint == 'expectingInput'" + ] + } + ] +} diff --git a/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/WaterfallGreeting.transcript b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/WaterfallGreeting.transcript new file mode 100644 index 0000000000..aae2fbe486 --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/CardActions/TestScripts/WaterfallGreeting.transcript @@ -0,0 +1,435 @@ +[ + { + "type": "conversationUpdate", + "membersAdded": [ + { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot" + }, + { + "id": "7da4d5a3-9aaf-4a03-bd62-1495536c852e", + "name": "User" + } + ], + "membersRemoved": [], + "channelId": "emulator", + "conversation": { + "id": "d5f4ad40-6167-11eb-881a-afe376da3863|livechat" + }, + "id": "d6088360-6167-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T09:53:36-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "timestamp": "2021-01-28T12:53:36.534Z", + "from": { + "id": "7da4d5a3-9aaf-4a03-bd62-1495536c852e", + "name": "User", + "role": "user" + }, + "locale": "", + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "d5f4ad40-6167-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "7da4d5a3-9aaf-4a03-bd62-1495536c852e", + "role": "user" + }, + "attachmentLayout": "list", + "locale": "", + "speak": "Welcome to the waterfall host bot", + "inputHint": "acceptingInput", + "attachments": [ + { + "contentType": "application/vnd.microsoft.card.adaptive", + "content": { + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.0", + "body": [ + { + "type": "Image", + "url": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQtB3AwMUeNoq4gUBGe6Ocj8kyh3bXa9ZbV7u1fVKQoyKFHdkqU", + "size": "stretch" + }, + { + "type": "TextBlock", + "spacing": "Medium", + "size": "Medium", + "weight": "Bolder", + "text": "Welcome to the Skill Dialog Sample!", + "wrap": true, + "maxLines": 0, + "color": "Accent" + }, + { + "type": "TextBlock", + "size": "default", + "text": "This sample allows you to connect to a skill using a SkillDialog and invoke several actions.", + "wrap": true, + "maxLines": 0 + } + ] + } + } + ], + "entities": [], + "replyToId": "d6088360-6167-11eb-b46e-c3c3213a748e", + "id": "d71ba110-6167-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T09:53:38-03:00", + "timestamp": "2021-01-28T12:53:38.337Z" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "d5f4ad40-6167-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "7da4d5a3-9aaf-4a03-bd62-1495536c852e", + "role": "user" + }, + "locale": "", + "text": "What delivery mode would you like to use?", + "speak": "What delivery mode would you like to use?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "normal", + "value": "normal" + }, + { + "type": "imBack", + "title": "expectReplies", + "value": "expectReplies" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "d6088360-6167-11eb-b46e-c3c3213a748e", + "id": "d75f60d0-6167-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T09:53:38-03:00", + "timestamp": "2021-01-28T12:53:38.781Z" + }, + { + "channelData": { + "clientActivityID": "1611838423833wj35v7pu64g", + "clientTimestamp": "2021-01-28T12:53:43.833Z" + }, + "text": "normal", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "7da4d5a3-9aaf-4a03-bd62-1495536c852e", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T12:53:43.851Z", + "entities": [ + { + "requiresBotState": true, + "supportsListening": true, + "supportsTts": true, + "type": "ClientCapabilities" + } + ], + "conversation": { + "id": "d5f4ad40-6167-11eb-881a-afe376da3863|livechat" + }, + "id": "da64ffb0-6167-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T09:53:43-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "d5f4ad40-6167-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "7da4d5a3-9aaf-4a03-bd62-1495536c852e", + "role": "user" + }, + "locale": "", + "text": "What type of skill would you like to use?", + "speak": "What type of skill would you like to use?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "EchoSkill", + "value": "EchoSkill" + }, + { + "type": "imBack", + "title": "WaterfallSkill", + "value": "WaterfallSkill" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "da64ffb0-6167-11eb-b46e-c3c3213a748e", + "id": "db167560-6167-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T09:53:45-03:00", + "timestamp": "2021-01-28T12:53:45.014Z" + }, + { + "channelData": { + "clientActivityID": "1611838427337752w1bylwms", + "clientTimestamp": "2021-01-28T12:53:47.337Z" + }, + "text": "WaterfallSkill", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "7da4d5a3-9aaf-4a03-bd62-1495536c852e", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T12:53:47.402Z", + "conversation": { + "id": "d5f4ad40-6167-11eb-881a-afe376da3863|livechat" + }, + "id": "dc82d6a0-6167-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T09:53:47-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "d5f4ad40-6167-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "7da4d5a3-9aaf-4a03-bd62-1495536c852e", + "role": "user" + }, + "locale": "", + "text": "What skill would you like to call?", + "speak": "What skill would you like to call?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "WaterfallSkillBotDotNet", + "value": "WaterfallSkillBotDotNet" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "dc82d6a0-6167-11eb-b46e-c3c3213a748e", + "id": "dd207630-6167-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T09:53:48-03:00", + "timestamp": "2021-01-28T12:53:48.435Z" + }, + { + "channelData": { + "clientActivityID": "1611838429939ntmad398ck7", + "clientTimestamp": "2021-01-28T12:53:49.939Z" + }, + "text": "WaterfallSkillBotDotNet", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "7da4d5a3-9aaf-4a03-bd62-1495536c852e", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T12:53:49.965Z", + "conversation": { + "id": "d5f4ad40-6167-11eb-881a-afe376da3863|livechat" + }, + "id": "de0a12e0-6167-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T09:53:49-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "d5f4ad40-6167-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "7da4d5a3-9aaf-4a03-bd62-1495536c852e", + "role": "user" + }, + "locale": "", + "text": "Select an action # to send to **WaterfallSkillBotDotNet**.\n\nOr just type in a message and it will be forwarded to the skill as a message activity.\n\n 1. Cards\n 2. Proactive\n 3. Auth\n 4. MessageWithAttachment\n 5. Sso\n 6. FileUpload\n 7. Echo", + "speak": "Select an action # to send to **WaterfallSkillBotDotNet**.\n\nOr just type in a message and it will be forwarded to the skill as a message activity.", + "inputHint": "expectingInput", + "attachments": [], + "entities": [], + "replyToId": "de0a12e0-6167-11eb-b46e-c3c3213a748e", + "id": "dea31e90-6167-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T09:53:50-03:00", + "timestamp": "2021-01-28T12:53:50.969Z" + }, + { + "channelData": { + "clientActivityID": "16118384346899i989qque7t", + "clientTimestamp": "2021-01-28T12:53:54.689Z" + }, + "text": "Cards", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "7da4d5a3-9aaf-4a03-bd62-1495536c852e", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T12:53:54.717Z", + "conversation": { + "id": "d5f4ad40-6167-11eb-881a-afe376da3863|livechat" + }, + "id": "e0df04d0-6167-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T09:53:54-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "trace", + "timestamp": "2021-01-28T12:53:55.817Z", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "d5f4ad40-6167-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "7da4d5a3-9aaf-4a03-bd62-1495536c852e", + "role": "user" + }, + "locale": "", + "replyToId": "e0df04d0-6167-11eb-b46e-c3c3213a748e", + "label": "Got ActivityType: event", + "name": "ActivityRouterDialog.ProcessActivityAsync()", + "id": "e186dd90-6167-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T09:53:55-03:00" + }, + { + "type": "trace", + "timestamp": "2021-01-28T12:53:56.114Z", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "d5f4ad40-6167-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "7da4d5a3-9aaf-4a03-bd62-1495536c852e", + "role": "user" + }, + "locale": "", + "replyToId": "e0df04d0-6167-11eb-b46e-c3c3213a748e", + "label": "Name: Cards. Value: ", + "name": "ActivityRouterDialog.OnEventActivityAsync()", + "id": "e1b42f20-6167-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T09:53:56-03:00" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "d5f4ad40-6167-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "7da4d5a3-9aaf-4a03-bd62-1495536c852e", + "role": "user" + }, + "locale": "", + "text": "What card do you want?\n\n 1. AdaptiveCardBotAction\n 2. AdaptiveCardTaskModule\n 3. AdaptiveCardSumbitAction\n 4. Hero\n 5. Thumbnail\n 6. Receipt\n 7. Signin\n 8. Carousel\n 9. List\n 10. O365\n 11. TeamsFileConsent\n 12. Animation\n 13. Audio\n 14. Video\n 15. End", + "speak": "What card do you want?", + "inputHint": "expectingInput", + "attachments": [], + "entities": [], + "replyToId": "e0df04d0-6167-11eb-b46e-c3c3213a748e", + "id": "e1e26b10-6167-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T09:53:56-03:00", + "timestamp": "2021-01-28T12:53:56.417Z" + }, +] diff --git a/tests/functional/Tests/SkillFunctionalTests/Common/HostBot.cs b/tests/functional/Tests/SkillFunctionalTests/Common/HostBot.cs new file mode 100644 index 0000000000..d2aff8acd7 --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/Common/HostBot.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace SkillFunctionalTests.Common +{ + public enum HostBot + { + /// + /// Simple host implemented using Composer and the dotnet runtime. + /// + SimpleHostBotComposerDotNet, + + /// + /// Simple host implemented using dotnet 3.1. + /// + SimpleHostBotDotNet, + + /// + /// Simple host implemented using dotnet 2.1. + /// + SimpleHostBotDotNet21, + + /// + /// Simple host implemented using JS. + /// + SimpleHostBotJS, + + /// + /// Simple host implemented using Python. + /// + SimpleHostBotPython, + + /// + /// Host implemented using dotnet and waterfall dialogs. + /// + WaterfallHostBotDotNet, + + /// + /// Host implemented using JS and waterfall dialogs. + /// + WaterfallHostBotJS, + + /// + /// Host implemented using Python and waterfall dialogs. + /// + WaterfallHostBotPython, + + /// + /// Host implemented for LegacyTests. + /// + EchoHostBot + } +} diff --git a/tests/functional/Tests/SkillFunctionalTests/Common/SkillBotNames.cs b/tests/functional/Tests/SkillFunctionalTests/Common/SkillBotNames.cs new file mode 100644 index 0000000000..2b1422ca76 --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/Common/SkillBotNames.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace SkillFunctionalTests.Common +{ + public static class SkillBotNames + { + public const string EchoSkillBotComposerDotNet = "EchoSkillBotComposerDotNet"; + public const string EchoSkillBotDotNet = "EchoSkillBotDotNet"; + public const string EchoSkillBotDotNet21 = "EchoSkillBotDotNet21"; + public const string EchoSkillBotDotNetV3 = "EchoSkillBotDotNetV3"; + public const string EchoSkillBotJS = "EchoSkillBotJS"; + public const string EchoSkillBotJSV3 = "EchoSkillBotJSV3"; + public const string EchoSkillBotPython = "EchoSkillBotPython"; + + public const string WaterfallSkillBotDotNet = "WaterfallSkillBotDotNet"; + public const string WaterfallSkillBotJS = "WaterfallSkillBotJS"; + public const string WaterfallSkillBotPython = "WaterfallSkillBotPython"; + + public const string EchoSkillBot = "EchoSkillBot"; + } +} diff --git a/tests/functional/Tests/SkillFunctionalTests/Common/TestCase.cs b/tests/functional/Tests/SkillFunctionalTests/Common/TestCase.cs new file mode 100644 index 0000000000..990135daca --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/Common/TestCase.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Bot.Connector; +using TranscriptTestRunner; + +namespace SkillFunctionalTests.Common +{ + public class TestCase + { + public string Description { get; set; } + + public string ChannelId { get; set; } + + public string DeliveryMode { get; set; } + + public HostBot HostBot { get; set; } + + public string TargetSkill { get; set; } + + public string Script { get; set; } + } +} diff --git a/tests/functional/Tests/SkillFunctionalTests/Common/TestCaseBuilder.cs b/tests/functional/Tests/SkillFunctionalTests/Common/TestCaseBuilder.cs new file mode 100644 index 0000000000..16b422d5f7 --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/Common/TestCaseBuilder.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using TranscriptTestRunner; + +namespace SkillFunctionalTests.Common +{ + public class TestCaseBuilder + { + public IEnumerable BuildTestCases(List channelIds, List deliveryModes, List hostBots, List targetSkills, List scripts, Func shouldExclude = null) + { + var testCases = new List(); + var count = 1; + foreach (var channelId in channelIds) + { + foreach (var script in scripts) + { + foreach (var deliveryMode in deliveryModes) + { + foreach (var hostBot in hostBots) + { + foreach (var targetSkill in targetSkills) + { + var testCase = new TestCase + { + Description = $"{script}, {hostBot}, {targetSkill}, {channelId}, {deliveryMode}", + ChannelId = channelId, + DeliveryMode = deliveryMode, + HostBot = hostBot, + TargetSkill = targetSkill, + Script = script + }; + + if (!ExcludeTestCase(shouldExclude, testCase)) + { + testCases.Add(new object[] { new TestCaseDataObject(testCase) }); + count++; + } + } + } + } + } + } + + return testCases; + } + + private bool ExcludeTestCase(Func shouldExclude, TestCase testCase) + { + if (shouldExclude == null) + { + return false; + } + + return shouldExclude(testCase); + } + } +} diff --git a/tests/functional/Tests/SkillFunctionalTests/Common/TestCaseDataObject.cs b/tests/functional/Tests/SkillFunctionalTests/Common/TestCaseDataObject.cs new file mode 100644 index 0000000000..422264211a --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/Common/TestCaseDataObject.cs @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Newtonsoft.Json; +using Xunit.Abstractions; + +namespace SkillFunctionalTests.Common +{ + public class TestCaseDataObject : IXunitSerializable + { + private const string TestObjectKey = "TestObjectKey"; + private string _testObject; + + /// + /// Initializes a new instance of the class. + /// + public TestCaseDataObject() + { + // Note: This empty constructor is needed by the serializer. + } + + /// + /// Initializes a new instance of the class. + /// + /// An object with the data to be used in the test. + public TestCaseDataObject(object testData) + { + _testObject = JsonConvert.SerializeObject(testData); + } + + /// + /// Used by XUnit.net for deserialization. + /// + /// A parameter used by XUnit.net. + public void Deserialize(IXunitSerializationInfo serializationInfo) + { + _testObject = serializationInfo.GetValue(TestObjectKey); + } + + /// + /// Used by XUnit.net for serialization. + /// + /// A parameter used by XUnit.net. + public void Serialize(IXunitSerializationInfo serializationInfo) + { + serializationInfo.AddValue(TestObjectKey, _testObject); + } + + /// + /// Gets the test data object for the specified .Net type. + /// + /// The type of the object to be returned. + /// The test object instance. + public T GetObject() + { + return JsonConvert.DeserializeObject(_testObject); + } + + public override string ToString() + { + try + { + var testCase = GetObject(); + return testCase.Description; + } + catch (Exception) + { + return base.ToString(); + } + } + } +} diff --git a/tests/functional/Tests/SkillFunctionalTests/FileUpload/FileUploadTests.cs b/tests/functional/Tests/SkillFunctionalTests/FileUpload/FileUploadTests.cs new file mode 100644 index 0000000000..084da720be --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/FileUpload/FileUploadTests.cs @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Microsoft.Bot.Connector; +using Microsoft.Bot.Schema; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using SkillFunctionalTests.Common; +using TranscriptTestRunner; +using TranscriptTestRunner.XUnit; +using Xunit; +using Xunit.Abstractions; + +namespace SkillFunctionalTests.FileUpload +{ + [Trait("TestCategory", "FileUpload")] + public class FileUploadTests : ScriptTestBase + { + private readonly string _testScriptsFolder = Directory.GetCurrentDirectory() + @"/FileUpload/TestScripts"; + + public FileUploadTests(ITestOutputHelper output) + : base(output) + { + } + + public static IEnumerable TestCases() + { + var channelIds = new List { Channels.Directline }; + + var deliverModes = new List + { + DeliveryModes.Normal, + DeliveryModes.ExpectReplies + }; + + var hostBots = new List + { + HostBot.WaterfallHostBotDotNet, + HostBot.WaterfallHostBotJS, + HostBot.WaterfallHostBotPython + + // TODO: Enable these when the port to composer is ready + //HostBotNames.ComposerHostBotDotNet + }; + + var targetSkills = new List + { + SkillBotNames.WaterfallSkillBotDotNet, + SkillBotNames.WaterfallSkillBotJS, + SkillBotNames.WaterfallSkillBotPython + + // TODO: Enable these when the port to composer is ready + //SkillBotNames.ComposerSkillBotDotNet + }; + + var scripts = new List + { + "FileUpload1.json" + }; + + var testCaseBuilder = new TestCaseBuilder(); + var testCases = testCaseBuilder.BuildTestCases(channelIds, deliverModes, hostBots, targetSkills, scripts); + foreach (var testCase in testCases) + { + yield return testCase; + } + } + + [Theory] + [MemberData(nameof(TestCases))] + public async Task RunTestCases(TestCaseDataObject testData) + { + const string fileName = "TestFile.txt"; + var testGuid = Guid.NewGuid().ToString(); + var testCase = testData.GetObject(); + Logger.LogInformation(JsonConvert.SerializeObject(testCase, Formatting.Indented)); + + var options = TestClientOptions[testCase.HostBot]; + var runner = new XUnitTestRunner(new TestClientFactory(testCase.ChannelId, options, Logger).GetTestClient(), TestRequestTimeout, Logger); + + // Execute the first part of the conversation. + var testParams = new Dictionary + { + { "DeliveryMode", testCase.DeliveryMode }, + { "TargetSkill", testCase.TargetSkill }, + { "FileName", fileName }, + { "TestGuid", testGuid } + }; + + await runner.RunTestAsync(Path.Combine(_testScriptsFolder, testCase.Script), testParams); + + // Create or Update testFile. + await using var stream = File.OpenWrite(Directory.GetCurrentDirectory() + $"/FileUpload/Media/{fileName}"); + await using var writer = new StreamWriter(stream); + await writer.WriteLineAsync($"GUID:{testGuid}"); + writer.Close(); + + // Upload file. + await using var file = File.OpenRead(Directory.GetCurrentDirectory() + $"/FileUpload/Media/{fileName}"); + await runner.UploadAsync(file); + + // Execute the rest of the conversation. + await runner.RunTestAsync(Path.Combine(_testScriptsFolder, "FileUpload2.json"), testParams); + } + } +} diff --git a/tests/functional/Tests/SkillFunctionalTests/FileUpload/Media/TestFile.txt b/tests/functional/Tests/SkillFunctionalTests/FileUpload/Media/TestFile.txt new file mode 100644 index 0000000000..af27ff4986 --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/FileUpload/Media/TestFile.txt @@ -0,0 +1 @@ +This is a test file. \ No newline at end of file diff --git a/tests/functional/Tests/SkillFunctionalTests/FileUpload/TestScripts/FileUpload1.json b/tests/functional/Tests/SkillFunctionalTests/FileUpload/TestScripts/FileUpload1.json new file mode 100644 index 0000000000..45098d7133 --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/FileUpload/TestScripts/FileUpload1.json @@ -0,0 +1,148 @@ +{ + "items": [ + { + "type": "conversationUpdate", + "role": "user" + }, + { + "type": "message", + "role": "bot", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "attachmentLayout == 'list'", + "speak == 'Welcome to the waterfall host bot'", + "inputHint == 'acceptingInput'", + "attachments[0].contentType == 'application/vnd.microsoft.card.adaptive'", + "attachments[0].content.type == 'AdaptiveCard'", + "attachments[0].content.body[0].type == 'Image'", + "attachments[0].content.body[0].size == 'stretch'", + "attachments[0].content.body[1].type == 'TextBlock'", + "attachments[0].content.body[1].spacing == 'Medium'", + "attachments[0].content.body[1].size == 'Medium'", + "attachments[0].content.body[1].weight == 'Bolder'", + "attachments[0].content.body[1].text == 'Welcome to the Skill Dialog Sample!'", + "attachments[0].content.body[1].wrap == true", + "attachments[0].content.body[1].color == 'Accent'", + "attachments[0].content.body[2].type == 'TextBlock'", + "attachments[0].content.body[2].text == 'This sample allows you to connect to a skill using a SkillDialog and invoke several actions.'", + "attachments[0].content.body[2].wrap == true" + ] + }, + { + "type": "message", + "role": "bot", + "text": "What delivery mode would you like to use?", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'What delivery mode would you like to use?'", + "speak == 'What delivery mode would you like to use?'", + "inputHint == 'expectingInput'", + "suggestedActions.actions[0].type == 'imBack'", + "suggestedActions.actions[0].title == 'normal'", + "suggestedActions.actions[0].value == 'normal'", + "suggestedActions.actions[1].type == 'imBack'", + "suggestedActions.actions[1].title == 'expectReplies'", + "suggestedActions.actions[1].value == 'expectReplies'" + ] + }, + { + "type": "message", + "role": "user", + "text": "${DeliveryMode}" + }, + { + "type": "message", + "role": "bot", + "text": "What group of skills would you like to use?", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'What group of skills would you like to use?'", + "speak == 'What group of skills would you like to use?'", + "inputHint == 'expectingInput'", + "suggestedActions.actions[0].type == 'imBack'", + "suggestedActions.actions[0].title == 'Echo' || suggestedActions.actions[0].title == 'Waterfall' || suggestedActions.actions[0].title == 'Teams'", + "suggestedActions.actions[0].value == 'Echo' || suggestedActions.actions[0].title == 'Waterfall' || suggestedActions.actions[0].title == 'Teams'" + ] + }, + { + "type": "message", + "role": "user", + "text": "Waterfall" + }, + { + "type": "message", + "role": "bot", + "text": "What skill would you like to call?\n\n 1. WaterfallSkillBotDotNet\n 2. WaterfallSkillBotJS\n 3. WaterfallSkillBotPython", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "startsWith(text, 'What skill would you like to call?')", + "speak == 'What skill would you like to call?'", + "inputHint == 'expectingInput'" + ] + }, + { + "type": "message", + "role": "user", + "text": "${TargetSkill}" + }, + { + "type": "message", + "role": "bot", + "text": "Select an action to send to **${TargetSkill}**.\n\n 1. Cards\n 2. Proactive\n 3. Auth\n 4. MessageWithAttachment\n 5. Sso\n 6. FileUpload\n 7. Echo\n 8. Delete\n 9. Update", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'Select an action to send to **${TargetSkill}**.\n\n 1. Cards\n 2. Proactive\n 3. Auth\n 4. MessageWithAttachment\n 5. Sso\n 6. FileUpload\n 7. Echo\n 8. Delete\n 9. Update'", + "speak == 'Select an action to send to **${TargetSkill}**.'", + "inputHint == 'expectingInput'" + ] + }, + { + "type": "message", + "role": "user", + "text": "FileUpload" + }, + { + "type": "trace", + "role": "bot", + "assertions": [ + "type == 'trace'", + "from.role == 'bot'", + "recipient.role == 'user'", + "label == 'Got ActivityType: event'", + "name == 'ActivityRouterDialog.ProcessActivityAsync()'" + ] + }, + { + "type": "trace", + "role": "bot", + "assertions": [ + "type == 'trace'", + "from.role == 'bot'", + "recipient.role == 'user'", + "label == 'Name: FileUpload. Value: '", + "name == 'ActivityRouterDialog.OnEventActivityAsync()'" + ] + }, + { + "type": "message", + "role": "bot", + "text": "Please upload a file to continue.", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'Please upload a file to continue.'" + ] + } + ] +} diff --git a/tests/functional/Tests/SkillFunctionalTests/FileUpload/TestScripts/FileUpload1.transcript b/tests/functional/Tests/SkillFunctionalTests/FileUpload/TestScripts/FileUpload1.transcript new file mode 100644 index 0000000000..cc5270a21e --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/FileUpload/TestScripts/FileUpload1.transcript @@ -0,0 +1,434 @@ +[ + { + "type": "conversationUpdate", + "membersAdded": [ + { + "id": "cc657090-60b1-11eb-8abe-e3b86321e5ec", + "name": "Bot" + }, + { + "id": "ff961b1d-815c-4e98-87fd-ac1db5ea78a3", + "name": "User" + } + ], + "membersRemoved": [], + "channelId": "emulator", + "conversation": { + "id": "ccf941c0-60d5-11eb-8abe-e3b86321e5ec|livechat" + }, + "id": "cd00e2e0-60d5-11eb-9a74-bb9adba325d2", + "localTimestamp": "2021-01-27T16:28:14-03:00", + "recipient": { + "id": "cc657090-60b1-11eb-8abe-e3b86321e5ec", + "name": "Bot", + "role": "bot" + }, + "timestamp": "2021-01-27T19:28:14.862Z", + "from": { + "id": "ff961b1d-815c-4e98-87fd-ac1db5ea78a3", + "name": "User", + "role": "user" + }, + "locale": "en-US", + "serviceUrl": "https://506512dd91f3.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://506512dd91f3.ngrok.io", + "channelId": "emulator", + "from": { + "id": "cc657090-60b1-11eb-8abe-e3b86321e5ec", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "ccf941c0-60d5-11eb-8abe-e3b86321e5ec|livechat" + }, + "recipient": { + "id": "ff961b1d-815c-4e98-87fd-ac1db5ea78a3", + "role": "user" + }, + "attachmentLayout": "list", + "locale": "en-US", + "speak": "Welcome to the waterfall host bot", + "inputHint": "acceptingInput", + "attachments": [ + { + "contentType": "application/vnd.microsoft.card.adaptive", + "content": { + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.0", + "body": [ + { + "type": "Image", + "url": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQtB3AwMUeNoq4gUBGe6Ocj8kyh3bXa9ZbV7u1fVKQoyKFHdkqU", + "size": "stretch" + }, + { + "type": "TextBlock", + "spacing": "Medium", + "size": "Medium", + "weight": "Bolder", + "text": "Welcome to the Skill Dialog Sample!", + "wrap": true, + "maxLines": 0, + "color": "Accent" + }, + { + "type": "TextBlock", + "size": "default", + "text": "This sample allows you to connect to a skill using a SkillDialog and invoke several actions.", + "wrap": true, + "maxLines": 0 + } + ] + } + } + ], + "entities": [], + "replyToId": "cd00e2e0-60d5-11eb-9a74-bb9adba325d2", + "id": "cdb9f9b0-60d5-11eb-9a74-bb9adba325d2", + "localTimestamp": "2021-01-27T16:28:16-03:00", + "timestamp": "2021-01-27T19:28:16.075Z" + }, + { + "type": "message", + "serviceUrl": "https://506512dd91f3.ngrok.io", + "channelId": "emulator", + "from": { + "id": "cc657090-60b1-11eb-8abe-e3b86321e5ec", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "ccf941c0-60d5-11eb-8abe-e3b86321e5ec|livechat" + }, + "recipient": { + "id": "ff961b1d-815c-4e98-87fd-ac1db5ea78a3", + "role": "user" + }, + "locale": "en-US", + "text": "What delivery mode would you like to use?", + "speak": "What delivery mode would you like to use?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "normal", + "value": "normal" + }, + { + "type": "imBack", + "title": "expectReplies", + "value": "expectReplies" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "cd00e2e0-60d5-11eb-9a74-bb9adba325d2", + "id": "cde04660-60d5-11eb-9a74-bb9adba325d2", + "localTimestamp": "2021-01-27T16:28:16-03:00", + "timestamp": "2021-01-27T19:28:16.326Z" + }, + { + "channelData": { + "clientActivityID": "16117756974587xlkqssva0r", + "clientTimestamp": "2021-01-27T19:28:17.458Z" + }, + "text": "normal", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "ff961b1d-815c-4e98-87fd-ac1db5ea78a3", + "name": "User", + "role": "user" + }, + "locale": "en-US", + "timestamp": "2021-01-27T19:28:17.472Z", + "entities": [ + { + "requiresBotState": true, + "supportsListening": true, + "supportsTts": true, + "type": "ClientCapabilities" + } + ], + "conversation": { + "id": "ccf941c0-60d5-11eb-8abe-e3b86321e5ec|livechat" + }, + "id": "ce8f2400-60d5-11eb-9a74-bb9adba325d2", + "localTimestamp": "2021-01-27T16:28:17-03:00", + "recipient": { + "id": "cc657090-60b1-11eb-8abe-e3b86321e5ec", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://506512dd91f3.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://506512dd91f3.ngrok.io", + "channelId": "emulator", + "from": { + "id": "cc657090-60b1-11eb-8abe-e3b86321e5ec", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "ccf941c0-60d5-11eb-8abe-e3b86321e5ec|livechat" + }, + "recipient": { + "id": "ff961b1d-815c-4e98-87fd-ac1db5ea78a3", + "role": "user" + }, + "locale": "en-US", + "text": "What type of skill would you like to use?", + "speak": "What type of skill would you like to use?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "EchoSkill", + "value": "EchoSkill" + }, + { + "type": "imBack", + "title": "WaterfallSkill", + "value": "WaterfallSkill" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "ce8f2400-60d5-11eb-9a74-bb9adba325d2", + "id": "cf180310-60d5-11eb-9a74-bb9adba325d2", + "localTimestamp": "2021-01-27T16:28:18-03:00", + "timestamp": "2021-01-27T19:28:18.369Z" + }, + { + "channelData": { + "clientActivityID": "1611775699426cx57468bwb6", + "clientTimestamp": "2021-01-27T19:28:19.427Z" + }, + "text": "WaterfallSkill", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "ff961b1d-815c-4e98-87fd-ac1db5ea78a3", + "name": "User", + "role": "user" + }, + "locale": "en-US", + "timestamp": "2021-01-27T19:28:19.441Z", + "conversation": { + "id": "ccf941c0-60d5-11eb-8abe-e3b86321e5ec|livechat" + }, + "id": "cfbb9610-60d5-11eb-9a74-bb9adba325d2", + "localTimestamp": "2021-01-27T16:28:19-03:00", + "recipient": { + "id": "cc657090-60b1-11eb-8abe-e3b86321e5ec", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://506512dd91f3.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://506512dd91f3.ngrok.io", + "channelId": "emulator", + "from": { + "id": "cc657090-60b1-11eb-8abe-e3b86321e5ec", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "ccf941c0-60d5-11eb-8abe-e3b86321e5ec|livechat" + }, + "recipient": { + "id": "ff961b1d-815c-4e98-87fd-ac1db5ea78a3", + "role": "user" + }, + "locale": "en-US", + "text": "What skill would you like to call?", + "speak": "What skill would you like to call?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "WaterfallSkillBotDotNet", + "value": "WaterfallSkillBotDotNet" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "cfbb9610-60d5-11eb-9a74-bb9adba325d2", + "id": "d04363b0-60d5-11eb-9a74-bb9adba325d2", + "localTimestamp": "2021-01-27T16:28:20-03:00", + "timestamp": "2021-01-27T19:28:20.331Z" + }, + { + "channelData": { + "clientActivityID": "1611775701238btqor6clvrg", + "clientTimestamp": "2021-01-27T19:28:21.238Z" + }, + "text": "WaterfallSkillBotDotNet", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "ff961b1d-815c-4e98-87fd-ac1db5ea78a3", + "name": "User", + "role": "user" + }, + "locale": "en-US", + "timestamp": "2021-01-27T19:28:21.255Z", + "conversation": { + "id": "ccf941c0-60d5-11eb-8abe-e3b86321e5ec|livechat" + }, + "id": "d0d06170-60d5-11eb-9a74-bb9adba325d2", + "localTimestamp": "2021-01-27T16:28:21-03:00", + "recipient": { + "id": "cc657090-60b1-11eb-8abe-e3b86321e5ec", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://506512dd91f3.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://506512dd91f3.ngrok.io", + "channelId": "emulator", + "from": { + "id": "cc657090-60b1-11eb-8abe-e3b86321e5ec", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "ccf941c0-60d5-11eb-8abe-e3b86321e5ec|livechat" + }, + "recipient": { + "id": "ff961b1d-815c-4e98-87fd-ac1db5ea78a3", + "role": "user" + }, + "locale": "en-US", + "text": "Select an action # to send to **WaterfallSkillBotDotNet**.\n\nOr just type in a message and it will be forwarded to the skill as a message activity.\n\n 1. Cards\n 2. Proactive\n 3. Auth\n 4. MessageWithAttachment\n 5. Sso\n 6. FileUpload\n 7. Echo", + "speak": "Select an action # to send to **WaterfallSkillBotDotNet**.\n\nOr just type in a message and it will be forwarded to the skill as a message activity.", + "inputHint": "expectingInput", + "attachments": [], + "entities": [], + "replyToId": "d0d06170-60d5-11eb-9a74-bb9adba325d2", + "id": "d15a7900-60d5-11eb-9a74-bb9adba325d2", + "localTimestamp": "2021-01-27T16:28:22-03:00", + "timestamp": "2021-01-27T19:28:22.159Z" + }, + { + "channelData": { + "clientActivityID": "1611775705642g7ss96aqemf", + "clientTimestamp": "2021-01-27T19:28:25.642Z" + }, + "text": "FileUpload", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "ff961b1d-815c-4e98-87fd-ac1db5ea78a3", + "name": "User", + "role": "user" + }, + "locale": "en-US", + "timestamp": "2021-01-27T19:28:25.677Z", + "conversation": { + "id": "ccf941c0-60d5-11eb-8abe-e3b86321e5ec|livechat" + }, + "id": "d3731fd0-60d5-11eb-9a74-bb9adba325d2", + "localTimestamp": "2021-01-27T16:28:25-03:00", + "recipient": { + "id": "cc657090-60b1-11eb-8abe-e3b86321e5ec", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://506512dd91f3.ngrok.io" + }, + { + "type": "trace", + "timestamp": "2021-01-27T19:28:26.806Z", + "serviceUrl": "https://506512dd91f3.ngrok.io", + "channelId": "emulator", + "from": { + "id": "cc657090-60b1-11eb-8abe-e3b86321e5ec", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "ccf941c0-60d5-11eb-8abe-e3b86321e5ec|livechat" + }, + "recipient": { + "id": "ff961b1d-815c-4e98-87fd-ac1db5ea78a3", + "role": "user" + }, + "locale": "en-US", + "replyToId": "d3731fd0-60d5-11eb-9a74-bb9adba325d2", + "label": "Got ActivityType: event", + "name": "ActivityRouterDialog.ProcessActivityAsync()", + "id": "d41f6560-60d5-11eb-9a74-bb9adba325d2", + "localTimestamp": "2021-01-27T16:28:26-03:00" + }, + { + "type": "trace", + "timestamp": "2021-01-27T19:28:27.070Z", + "serviceUrl": "https://506512dd91f3.ngrok.io", + "channelId": "emulator", + "from": { + "id": "cc657090-60b1-11eb-8abe-e3b86321e5ec", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "ccf941c0-60d5-11eb-8abe-e3b86321e5ec|livechat" + }, + "recipient": { + "id": "ff961b1d-815c-4e98-87fd-ac1db5ea78a3", + "role": "user" + }, + "locale": "en-US", + "replyToId": "d3731fd0-60d5-11eb-9a74-bb9adba325d2", + "label": "Name: FileUpload. Value: ", + "name": "ActivityRouterDialog.OnEventActivityAsync()", + "id": "d447ade0-60d5-11eb-9a74-bb9adba325d2", + "localTimestamp": "2021-01-27T16:28:27-03:00" + }, + { + "type": "message", + "serviceUrl": "https://506512dd91f3.ngrok.io", + "channelId": "emulator", + "from": { + "id": "cc657090-60b1-11eb-8abe-e3b86321e5ec", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "ccf941c0-60d5-11eb-8abe-e3b86321e5ec|livechat" + }, + "recipient": { + "id": "ff961b1d-815c-4e98-87fd-ac1db5ea78a3", + "role": "user" + }, + "locale": "en-US", + "text": "Please upload a file to continue.", + "inputHint": "acceptingInput", + "attachments": [], + "entities": [], + "replyToId": "d3731fd0-60d5-11eb-9a74-bb9adba325d2", + "id": "d46e6fc0-60d5-11eb-9a74-bb9adba325d2", + "localTimestamp": "2021-01-27T16:28:27-03:00", + "timestamp": "2021-01-27T19:28:27.324Z" + } +] diff --git a/tests/functional/Tests/SkillFunctionalTests/FileUpload/TestScripts/FileUpload2.json b/tests/functional/Tests/SkillFunctionalTests/FileUpload/TestScripts/FileUpload2.json new file mode 100644 index 0000000000..49696744d4 --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/FileUpload/TestScripts/FileUpload2.json @@ -0,0 +1,59 @@ +{ + "items": [ + { + "type": "message", + "role": "bot", + "text": "Attachment \"${FileName}\" has been received.\r\nFile content: GUID:${TestGuid}\r\n\r\n", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "startsWith(text, 'Attachment \"${FileName}\" has been received.\r\nFile content: GUID:${TestGuid}')", + "inputHint == 'acceptingInput'" + ] + }, + { + "type": "message", + "role": "bot", + "text": "Do you want to upload another file?", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'Do you want to upload another file?'", + "speak == 'Do you want to upload another file?'", + "inputHint == 'expectingInput'", + "suggestedActions.actions[0].type == 'imBack'", + "suggestedActions.actions[0].title == 'Yes'", + "suggestedActions.actions[0].value == 'Yes'", + "suggestedActions.actions[1].type == 'imBack'", + "suggestedActions.actions[1].title == 'No'", + "suggestedActions.actions[1].value == 'No'" + ] + }, + { + "type": "message", + "role": "user", + "text": "No" + }, + { + "type": "message", + "role": "bot", + "text": "Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?'", + "speak == 'Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?'", + "inputHint == 'expectingInput'", + "suggestedActions.actions[0].type == 'imBack'", + "suggestedActions.actions[0].title == 'normal'", + "suggestedActions.actions[0].value == 'normal'", + "suggestedActions.actions[1].type == 'imBack'", + "suggestedActions.actions[1].title == 'expectReplies'", + "suggestedActions.actions[1].value == 'expectReplies'" + ] + } + ] +} diff --git a/tests/functional/Tests/SkillFunctionalTests/FileUpload/TestScripts/FileUpload2.transcript b/tests/functional/Tests/SkillFunctionalTests/FileUpload/TestScripts/FileUpload2.transcript new file mode 100644 index 0000000000..d785b4b3ff --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/FileUpload/TestScripts/FileUpload2.transcript @@ -0,0 +1,138 @@ +[ + { + "type": "message", + "serviceUrl": "https://7e981c20ff5a.ngrok.io", + "channelId": "emulator", + "from": { + "id": "19b99450-a209-11eb-9135-ffc715768685", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "19c30a30-a209-11eb-8b44-53ccf8d65f4d|livechat" + }, + "recipient": { + "id": "0c3116ae-0d26-4a5f-8483-1082c9ab6c7f", + "role": "user" + }, + "locale": "", + "text": "Attachment \"test2.txt\" has been received.\r\nFile content: GUID:1234\r\n", + "inputHint": "acceptingInput", + "attachments": [], + "entities": [], + "replyToId": "2df8af51-a209-11eb-8b44-53ccf8d65f4d", + "id": "2ed3cd10-a209-11eb-8b44-53ccf8d65f4d", + "localTimestamp": "2021-04-20T15:49:48-03:00", + "timestamp": "2021-04-20T18:49:48.897Z" + }, + { + "type": "message", + "serviceUrl": "https://7e981c20ff5a.ngrok.io", + "channelId": "emulator", + "from": { + "id": "19b99450-a209-11eb-9135-ffc715768685", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "19c30a30-a209-11eb-8b44-53ccf8d65f4d|livechat" + }, + "recipient": { + "id": "0c3116ae-0d26-4a5f-8483-1082c9ab6c7f", + "role": "user" + }, + "locale": "", + "text": "Do you want to upload another file?", + "speak": "Do you want to upload another file?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "Yes", + "value": "Yes" + }, + { + "type": "imBack", + "title": "No", + "value": "No" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "2df8af51-a209-11eb-8b44-53ccf8d65f4d", + "id": "2f160630-a209-11eb-8b44-53ccf8d65f4d", + "localTimestamp": "2021-04-20T15:49:49-03:00", + "timestamp": "2021-04-20T18:49:49.331Z" + }, + { + "channelData": { + "clientActivityID": "1618944597322y3mgzbre63", + "clientTimestamp": "2021-04-20T18:49:57.322Z" + }, + "text": "No", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "0c3116ae-0d26-4a5f-8483-1082c9ab6c7f", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-04-20T18:49:57.359Z", + "conversation": { + "id": "19c30a30-a209-11eb-8b44-53ccf8d65f4d|livechat" + }, + "id": "33defff0-a209-11eb-8b44-53ccf8d65f4d", + "localTimestamp": "2021-04-20T15:49:57-03:00", + "recipient": { + "id": "19b99450-a209-11eb-9135-ffc715768685", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://7e981c20ff5a.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://7e981c20ff5a.ngrok.io", + "channelId": "emulator", + "from": { + "id": "19b99450-a209-11eb-9135-ffc715768685", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "19c30a30-a209-11eb-8b44-53ccf8d65f4d|livechat" + }, + "recipient": { + "id": "0c3116ae-0d26-4a5f-8483-1082c9ab6c7f", + "role": "user" + }, + "locale": "", + "text": "Done with \"WaterfallSkillBotDotNet\". \n\n What delivery mode would you like to use?", + "speak": "Done with \"WaterfallSkillBotDotNet\". \n\n What delivery mode would you like to use?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "normal", + "value": "normal" + }, + { + "type": "imBack", + "title": "expectReplies", + "value": "expectReplies" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "33defff0-a209-11eb-8b44-53ccf8d65f4d", + "id": "34157940-a209-11eb-8b44-53ccf8d65f4d", + "localTimestamp": "2021-04-20T15:49:57-03:00", + "timestamp": "2021-04-20T18:49:57.716Z" + } +] \ No newline at end of file diff --git a/tests/functional/Tests/SkillFunctionalTests/MessageWithAttachment/MessageWithAttachmentTests.cs b/tests/functional/Tests/SkillFunctionalTests/MessageWithAttachment/MessageWithAttachmentTests.cs new file mode 100644 index 0000000000..c7c045e792 --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/MessageWithAttachment/MessageWithAttachmentTests.cs @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Microsoft.Bot.Connector; +using Microsoft.Bot.Schema; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using SkillFunctionalTests.Common; +using TranscriptTestRunner; +using TranscriptTestRunner.XUnit; +using Xunit; +using Xunit.Abstractions; + +namespace SkillFunctionalTests.MessageWithAttachment +{ + [Trait("TestCategory", "Attachments")] + public class MessageWithAttachmentTests : ScriptTestBase + { + private readonly string _testScriptsFolder = Directory.GetCurrentDirectory() + @"/MessageWithAttachment/TestScripts"; + + public MessageWithAttachmentTests(ITestOutputHelper output) + : base(output) + { + } + + public static IEnumerable TestCases() + { + var channelIds = new List { Channels.Directline }; + var deliverModes = new List + { + DeliveryModes.Normal, + DeliveryModes.ExpectReplies + }; + + var hostBots = new List + { + HostBot.WaterfallHostBotDotNet, + HostBot.WaterfallHostBotJS, + HostBot.WaterfallHostBotPython, + + // TODO: Enable this when the port to composer is ready + //HostBot.ComposerHostBotDotNet + }; + + var targetSkills = new List + { + SkillBotNames.WaterfallSkillBotDotNet, + SkillBotNames.WaterfallSkillBotJS, + SkillBotNames.WaterfallSkillBotPython, + + // TODO: Enable this when the port to composer is ready + //SkillBotNames.ComposerSkillBotDotNet + }; + + var scripts = new List + { + "MessageWithAttachment.json", + }; + + var testCaseBuilder = new TestCaseBuilder(); + + var testCases = testCaseBuilder.BuildTestCases(channelIds, deliverModes, hostBots, targetSkills, scripts); + foreach (var testCase in testCases) + { + yield return testCase; + } + } + + [Theory] + [MemberData(nameof(TestCases))] + public async Task RunTestCases(TestCaseDataObject testData) + { + var testCase = testData.GetObject(); + Logger.LogInformation(JsonConvert.SerializeObject(testCase, Formatting.Indented)); + + var options = TestClientOptions[testCase.HostBot]; + var runner = new XUnitTestRunner(new TestClientFactory(testCase.ChannelId, options, Logger).GetTestClient(), TestRequestTimeout, Logger); + + var testParams = new Dictionary + { + { "DeliveryMode", testCase.DeliveryMode }, + { "TargetSkill", testCase.TargetSkill } + }; + + await runner.RunTestAsync(Path.Combine(_testScriptsFolder, testCase.Script), testParams); + } + } +} diff --git a/tests/functional/Tests/SkillFunctionalTests/MessageWithAttachment/TestScripts/MessageWithAttachment.json b/tests/functional/Tests/SkillFunctionalTests/MessageWithAttachment/TestScripts/MessageWithAttachment.json new file mode 100644 index 0000000000..3efa3c468d --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/MessageWithAttachment/TestScripts/MessageWithAttachment.json @@ -0,0 +1,281 @@ +{ + "items": [ + { + "type": "conversationUpdate", + "role": "user" + }, + { + "type": "message", + "role": "bot", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "attachmentLayout == 'list'", + "speak == 'Welcome to the waterfall host bot'", + "inputHint == 'acceptingInput'", + "attachments[0].contentType == 'application/vnd.microsoft.card.adaptive'", + "attachments[0].content.type == 'AdaptiveCard'", + "attachments[0].content.body[0].type == 'Image'", + "attachments[0].content.body[0].size == 'stretch'", + "attachments[0].content.body[1].type == 'TextBlock'", + "attachments[0].content.body[1].spacing == 'Medium'", + "attachments[0].content.body[1].size == 'Medium'", + "attachments[0].content.body[1].weight == 'Bolder'", + "attachments[0].content.body[1].text == 'Welcome to the Skill Dialog Sample!'", + "attachments[0].content.body[1].wrap == True", + "attachments[0].content.body[1].color == 'Accent'", + "attachments[0].content.body[2].type == 'TextBlock'", + "attachments[0].content.body[2].text == 'This sample allows you to connect to a skill using a SkillDialog and invoke several actions.'", + "attachments[0].content.body[2].wrap == True" + ] + }, + { + "type": "message", + "role": "bot", + "text": "What delivery mode would you like to use?", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'What delivery mode would you like to use?'", + "speak == 'What delivery mode would you like to use?'", + "inputHint == 'expectingInput'", + "suggestedActions.actions[0].type == 'imBack'", + "suggestedActions.actions[0].title == 'normal'", + "suggestedActions.actions[0].value == 'normal'", + "suggestedActions.actions[1].type == 'imBack'", + "suggestedActions.actions[1].title == 'expectReplies'", + "suggestedActions.actions[1].value == 'expectReplies'" + ] + }, + { + "type": "message", + "role": "user", + "text": "${DeliveryMode}" + }, + { + "type": "message", + "role": "bot", + "text": "What group of skills would you like to use?", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'What group of skills would you like to use?'", + "speak == 'What group of skills would you like to use?'", + "inputHint == 'expectingInput'", + "suggestedActions.actions[0].type == 'imBack'", + "suggestedActions.actions[0].title == 'Echo' || suggestedActions.actions[0].title == 'Waterfall' || suggestedActions.actions[0].title == 'Teams'", + "suggestedActions.actions[0].value == 'Echo' || suggestedActions.actions[0].title == 'Waterfall' || suggestedActions.actions[0].title == 'Teams'" + ] + }, + { + "type": "message", + "role": "user", + "text": "Waterfall" + }, + { + "type": "message", + "role": "bot", + "text": "What skill would you like to call?\n\n 1. WaterfallSkillBotDotNet\n 2. WaterfallSkillBotJS\n 3. WaterfallSkillBotPython", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "startsWith(text, 'What skill would you like to call?')", + "speak == 'What skill would you like to call?'", + "inputHint == 'expectingInput'" + ] + }, + { + "type": "message", + "role": "user", + "text": "${TargetSkill}" + }, + { + "type": "message", + "role": "bot", + "text": "Select an action # to send to **${TargetSkill}**.\n\n 1. Cards\n 2. Proactive\n 3. Auth\n 4. MessageWithAttachment\n 5. Sso\n 6. FileUpload\n 7. Echo\n 8. Delete\n 9. Update", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'Select an action to send to **${TargetSkill}**.\n\n 1. Cards\n 2. Proactive\n 3. Auth\n 4. MessageWithAttachment\n 5. Sso\n 6. FileUpload\n 7. Echo\n 8. Delete\n 9. Update'", + "speak == 'Select an action to send to **${TargetSkill}**.'", + "inputHint == 'expectingInput'" + ] + }, + { + "type": "message", + "role": "user", + "text": "MessageWithAttachment" + }, + { + "type": "trace", + "role": "bot", + "assertions": [ + "type == 'trace'", + "from.role == 'bot'", + "recipient.role == 'user'", + "label == 'Got ActivityType: event'", + "name == 'ActivityRouterDialog.ProcessActivityAsync()'" + ] + }, + { + "type": "trace", + "role": "bot", + "assertions": [ + "type == 'trace'", + "from.role == 'bot'", + "recipient.role == 'user'", + "label == 'Name: Attachment. Value: '", + "name == 'ActivityRouterDialog.OnEventActivityAsync()'" + ] + }, + { + "type": "message", + "role": "bot", + "text": "What attachment type do you want?", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'What attachment type do you want?'", + "speak == 'What attachment type do you want?'", + "inputHint == 'expectingInput'", + "suggestedActions.actions[0].type == 'imBack'", + "suggestedActions.actions[0].title == 'Inline'", + "suggestedActions.actions[0].value == 'Inline'", + "suggestedActions.actions[1].type == 'imBack'", + "suggestedActions.actions[1].title == 'Internet'", + "suggestedActions.actions[1].value == 'Internet'" + ] + }, + { + "type": "message", + "role": "user", + "text": "Inline" + }, + { + "type": "message", + "role": "bot", + "text": "This is an inline attachment.", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'This is an inline attachment.'", + "inputHint == 'ignoringInput'", + "attachments[0].contentType == 'image/png'", + "attachments[0].name == 'Files/architecture-resize.png'" + ] + }, + { + "type": "message", + "role": "bot", + "text": "Do you want another type of attachment?", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'Do you want another type of attachment?'", + "speak == 'Do you want another type of attachment?'", + "inputHint == 'expectingInput'", + "suggestedActions.actions[0].type == 'imBack'", + "suggestedActions.actions[0].title == 'Yes'", + "suggestedActions.actions[0].value == 'Yes'", + "suggestedActions.actions[1].type == 'imBack'", + "suggestedActions.actions[1].title == 'No'", + "suggestedActions.actions[1].value == 'No'" + ] + }, + { + "type": "message", + "role": "user", + "text": "Yes" + }, + { + "type": "message", + "role": "bot", + "text": "What attachment type do you want?", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'What attachment type do you want?'", + "speak == 'What attachment type do you want?'", + "inputHint == 'expectingInput'", + "suggestedActions.actions[0].type == 'imBack'", + "suggestedActions.actions[0].title == 'Inline'", + "suggestedActions.actions[0].value == 'Inline'", + "suggestedActions.actions[1].type == 'imBack'", + "suggestedActions.actions[1].title == 'Internet'", + "suggestedActions.actions[1].value == 'Internet'" + ] + }, + { + "type": "message", + "role": "user", + "text": "Internet" + }, + { + "type": "message", + "role": "bot", + "text": "This is an attachment from a HTTP URL.", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'This is an attachment from a HTTP URL.'", + "inputHint == 'ignoringInput'", + "attachments[0].contentType == 'image/png'", + "endsWith(attachments[0].contentUrl, 'images/architecture-resize.png')", + "attachments[0].name == 'Files/architecture-resize.png'" + ] + }, + { + "type": "message", + "role": "bot", + "text": "Do you want another type of attachment?", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'Do you want another type of attachment?'", + "speak == 'Do you want another type of attachment?'", + "inputHint == 'expectingInput'", + "suggestedActions.actions[0].type == 'imBack'", + "suggestedActions.actions[0].title == 'Yes'", + "suggestedActions.actions[0].value == 'Yes'", + "suggestedActions.actions[1].type == 'imBack'", + "suggestedActions.actions[1].title == 'No'", + "suggestedActions.actions[1].value == 'No'" + ] + }, + { + "type": "message", + "role": "user", + "text": "No" + }, + { + "type": "message", + "role": "bot", + "text": "Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?'", + "speak == 'Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?'", + "inputHint == 'expectingInput'", + "suggestedActions.actions[0].type == 'imBack'", + "suggestedActions.actions[0].title == 'normal'", + "suggestedActions.actions[0].value == 'normal'", + "suggestedActions.actions[1].type == 'imBack'", + "suggestedActions.actions[1].title == 'expectReplies'", + "suggestedActions.actions[1].value == 'expectReplies'" + ] + } + ] +} diff --git a/tests/functional/Tests/SkillFunctionalTests/MessageWithAttachment/TestScripts/MessageWithAttachment.transcript b/tests/functional/Tests/SkillFunctionalTests/MessageWithAttachment/TestScripts/MessageWithAttachment.transcript new file mode 100644 index 0000000000..112a5cde62 --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/MessageWithAttachment/TestScripts/MessageWithAttachment.transcript @@ -0,0 +1,789 @@ +[ + { + "type": "conversationUpdate", + "membersAdded": [ + { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot" + }, + { + "id": "0db3a1e0-bca8-4e54-af83-13efe060d8dd", + "name": "User" + } + ], + "membersRemoved": [], + "channelId": "emulator", + "conversation": { + "id": "94732b31-616c-11eb-881a-afe376da3863|livechat" + }, + "id": "9496dfd0-616c-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:27:34-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "timestamp": "2021-01-28T13:27:34.221Z", + "from": { + "id": "0db3a1e0-bca8-4e54-af83-13efe060d8dd", + "name": "User", + "role": "user" + }, + "locale": "", + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "94732b31-616c-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "0db3a1e0-bca8-4e54-af83-13efe060d8dd", + "role": "user" + }, + "attachmentLayout": "list", + "locale": "", + "speak": "Welcome to the waterfall host bot", + "inputHint": "acceptingInput", + "attachments": [ + { + "contentType": "application/vnd.microsoft.card.adaptive", + "content": { + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.0", + "body": [ + { + "type": "Image", + "url": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQtB3AwMUeNoq4gUBGe6Ocj8kyh3bXa9ZbV7u1fVKQoyKFHdkqU", + "size": "stretch" + }, + { + "type": "TextBlock", + "spacing": "Medium", + "size": "Medium", + "weight": "Bolder", + "text": "Welcome to the Skill Dialog Sample!", + "wrap": true, + "maxLines": 0, + "color": "Accent" + }, + { + "type": "TextBlock", + "size": "default", + "text": "This sample allows you to connect to a skill using a SkillDialog and invoke several actions.", + "wrap": true, + "maxLines": 0 + } + ] + } + } + ], + "entities": [], + "replyToId": "9496dfd0-616c-11eb-b46e-c3c3213a748e", + "id": "9544fa20-616c-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:27:35-03:00", + "timestamp": "2021-01-28T13:27:35.362Z" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "94732b31-616c-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "0db3a1e0-bca8-4e54-af83-13efe060d8dd", + "role": "user" + }, + "locale": "", + "text": "What delivery mode would you like to use?", + "speak": "What delivery mode would you like to use?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "normal", + "value": "normal" + }, + { + "type": "imBack", + "title": "expectReplies", + "value": "expectReplies" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "9496dfd0-616c-11eb-b46e-c3c3213a748e", + "id": "956e2d00-616c-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:27:35-03:00", + "timestamp": "2021-01-28T13:27:35.632Z" + }, + { + "channelData": { + "clientActivityID": "1611840456587v84hgz17ny8", + "clientTimestamp": "2021-01-28T13:27:36.587Z" + }, + "text": "normal", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "0db3a1e0-bca8-4e54-af83-13efe060d8dd", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T13:27:36.608Z", + "entities": [ + { + "requiresBotState": true, + "supportsListening": true, + "supportsTts": true, + "type": "ClientCapabilities" + } + ], + "conversation": { + "id": "94732b31-616c-11eb-881a-afe376da3863|livechat" + }, + "id": "96031a00-616c-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:27:36-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "94732b31-616c-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "0db3a1e0-bca8-4e54-af83-13efe060d8dd", + "role": "user" + }, + "locale": "", + "text": "What type of skill would you like to use?", + "speak": "What type of skill would you like to use?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "EchoSkill", + "value": "EchoSkill" + }, + { + "type": "imBack", + "title": "WaterfallSkill", + "value": "WaterfallSkill" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "96031a00-616c-11eb-b46e-c3c3213a748e", + "id": "96980700-616c-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:27:37-03:00", + "timestamp": "2021-01-28T13:27:37.584Z" + }, + { + "channelData": { + "clientActivityID": "16118404585657p5vbek9bxa", + "clientTimestamp": "2021-01-28T13:27:38.565Z" + }, + "text": "WaterfallSkill", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "0db3a1e0-bca8-4e54-af83-13efe060d8dd", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T13:27:38.588Z", + "conversation": { + "id": "94732b31-616c-11eb-881a-afe376da3863|livechat" + }, + "id": "973139c0-616c-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:27:38-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "94732b31-616c-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "0db3a1e0-bca8-4e54-af83-13efe060d8dd", + "role": "user" + }, + "locale": "", + "text": "What skill would you like to call?", + "speak": "What skill would you like to call?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "WaterfallSkillBotDotNet", + "value": "WaterfallSkillBotDotNet" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "973139c0-616c-11eb-b46e-c3c3213a748e", + "id": "97cf2770-616c-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:27:39-03:00", + "timestamp": "2021-01-28T13:27:39.623Z" + }, + { + "channelData": { + "clientActivityID": "1611840461032eejmg7hkwgc", + "clientTimestamp": "2021-01-28T13:27:41.032Z" + }, + "text": "WaterfallSkillBotDotNet", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "0db3a1e0-bca8-4e54-af83-13efe060d8dd", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T13:27:41.053Z", + "conversation": { + "id": "94732b31-616c-11eb-881a-afe376da3863|livechat" + }, + "id": "98a95ad0-616c-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:27:41-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "94732b31-616c-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "0db3a1e0-bca8-4e54-af83-13efe060d8dd", + "role": "user" + }, + "locale": "", + "text": "Select an action # to send to **WaterfallSkillBotDotNet**.\n\nOr just type in a message and it will be forwarded to the skill as a message activity.\n\n 1. Cards\n 2. Proactive\n 3. Auth\n 4. MessageWithAttachment\n 5. Sso\n 6. FileUpload\n 7. Echo", + "speak": "Select an action # to send to **WaterfallSkillBotDotNet**.\n\nOr just type in a message and it will be forwarded to the skill as a message activity.", + "inputHint": "expectingInput", + "attachments": [], + "entities": [], + "replyToId": "98a95ad0-616c-11eb-b46e-c3c3213a748e", + "id": "993e6ee0-616c-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:27:42-03:00", + "timestamp": "2021-01-28T13:27:42.029Z" + }, + { + "channelData": { + "clientActivityID": "1611840465174eu3jly2odgv", + "clientTimestamp": "2021-01-28T13:27:45.174Z" + }, + "text": "4", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "0db3a1e0-bca8-4e54-af83-13efe060d8dd", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T13:27:45.203Z", + "conversation": { + "id": "94732b31-616c-11eb-881a-afe376da3863|livechat" + }, + "id": "9b229830-616c-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:27:45-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "trace", + "timestamp": "2021-01-28T13:27:46.200Z", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "94732b31-616c-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "0db3a1e0-bca8-4e54-af83-13efe060d8dd", + "role": "user" + }, + "locale": "", + "replyToId": "9b229830-616c-11eb-b46e-c3c3213a748e", + "label": "Got ActivityType: event", + "name": "ActivityRouterDialog.ProcessActivityAsync()", + "id": "9bbab980-616c-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:27:46-03:00" + }, + { + "type": "trace", + "timestamp": "2021-01-28T13:27:46.485Z", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "94732b31-616c-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "0db3a1e0-bca8-4e54-af83-13efe060d8dd", + "role": "user" + }, + "locale": "", + "replyToId": "9b229830-616c-11eb-b46e-c3c3213a748e", + "label": "Name: MessageWithAttachment. Value: ", + "name": "ActivityRouterDialog.OnEventActivityAsync()", + "id": "9be63650-616c-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:27:46-03:00" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "94732b31-616c-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "0db3a1e0-bca8-4e54-af83-13efe060d8dd", + "role": "user" + }, + "locale": "", + "text": "What card do you want?", + "speak": "What card do you want?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "Inline", + "value": "Inline" + }, + { + "type": "imBack", + "title": "Internet", + "value": "Internet" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "9b229830-616c-11eb-b46e-c3c3213a748e", + "id": "9c10efd0-616c-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:27:46-03:00", + "timestamp": "2021-01-28T13:27:46.765Z" + }, + { + "channelData": { + "clientActivityID": "16118405051429uurdklppdu", + "clientTimestamp": "2021-01-28T13:28:25.142Z" + }, + "text": "Inline", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "0db3a1e0-bca8-4e54-af83-13efe060d8dd", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T13:28:25.198Z", + "conversation": { + "id": "94732b31-616c-11eb-881a-afe376da3863|livechat" + }, + "id": "b2f958e0-616c-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:28:25-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "94732b31-616c-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "0db3a1e0-bca8-4e54-af83-13efe060d8dd", + "role": "user" + }, + "locale": "", + "text": "This is an inline attachment.", + "inputHint": "acceptingInput", + "attachments": [ + { + "contentType": "image/png", + "contentUrl": "data:image/png;base64,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", + "name": "Files\\architecture-resize.png" + } + ], + "entities": [], + "replyToId": "b2f958e0-616c-11eb-b46e-c3c3213a748e", + "id": "b57b96f0-616c-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:28:29-03:00", + "timestamp": "2021-01-28T13:28:29.407Z" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "94732b31-616c-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "0db3a1e0-bca8-4e54-af83-13efe060d8dd", + "role": "user" + }, + "locale": "", + "text": "Do you want another type of attachment?", + "speak": "Do you want another type of attachment?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "Yes", + "value": "Yes" + }, + { + "type": "imBack", + "title": "No", + "value": "No" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "b2f958e0-616c-11eb-b46e-c3c3213a748e", + "id": "b5a82530-616c-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:28:29-03:00", + "timestamp": "2021-01-28T13:28:29.699Z" + }, + { + "channelData": { + "clientActivityID": "16118405370383nul2pf807g", + "clientTimestamp": "2021-01-28T13:28:57.038Z" + }, + "text": "Yes", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "0db3a1e0-bca8-4e54-af83-13efe060d8dd", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T13:28:57.116Z", + "conversation": { + "id": "94732b31-616c-11eb-881a-afe376da3863|livechat" + }, + "id": "c5ffa5c0-616c-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:28:57-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "94732b31-616c-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "0db3a1e0-bca8-4e54-af83-13efe060d8dd", + "role": "user" + }, + "locale": "", + "text": "What card do you want?", + "speak": "What card do you want?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "Inline", + "value": "Inline" + }, + { + "type": "imBack", + "title": "Internet", + "value": "Internet" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "c5ffa5c0-616c-11eb-b46e-c3c3213a748e", + "id": "c6c4f190-616c-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:28:58-03:00", + "timestamp": "2021-01-28T13:28:58.409Z" + }, + { + "channelData": { + "clientActivityID": "16118405437189fvw0nchfd", + "clientTimestamp": "2021-01-28T13:29:03.718Z" + }, + "text": "Internet", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "0db3a1e0-bca8-4e54-af83-13efe060d8dd", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T13:29:03.775Z", + "conversation": { + "id": "94732b31-616c-11eb-881a-afe376da3863|livechat" + }, + "id": "c9f7baf0-616c-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:29:03-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "94732b31-616c-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "0db3a1e0-bca8-4e54-af83-13efe060d8dd", + "role": "user" + }, + "locale": "", + "text": "This is an attachment from a HTTP URL.", + "inputHint": "acceptingInput", + "attachments": [ + { + "contentType": "image/png", + "contentUrl": "https://docs.microsoft.com/en-us/bot-framework/media/how-it-works/architecture-resize.png", + "name": "Files\\architecture-resize.png" + } + ], + "entities": [], + "replyToId": "c9f7baf0-616c-11eb-b46e-c3c3213a748e", + "id": "caae87d0-616c-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:29:04-03:00", + "timestamp": "2021-01-28T13:29:04.973Z" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "94732b31-616c-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "0db3a1e0-bca8-4e54-af83-13efe060d8dd", + "role": "user" + }, + "locale": "", + "text": "Do you want another type of attachment?", + "speak": "Do you want another type of attachment?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "Yes", + "value": "Yes" + }, + { + "type": "imBack", + "title": "No", + "value": "No" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "c9f7baf0-616c-11eb-b46e-c3c3213a748e", + "id": "cad9dd90-616c-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:29:05-03:00", + "timestamp": "2021-01-28T13:29:05.257Z" + }, + { + "channelData": { + "clientActivityID": "1611840562624thw0ki5mdtf", + "clientTimestamp": "2021-01-28T13:29:22.624Z" + }, + "text": "No", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "0db3a1e0-bca8-4e54-af83-13efe060d8dd", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T13:29:22.702Z", + "conversation": { + "id": "94732b31-616c-11eb-881a-afe376da3863|livechat" + }, + "id": "d53fc2e0-616c-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:29:22-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "94732b31-616c-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "0db3a1e0-bca8-4e54-af83-13efe060d8dd", + "role": "user" + }, + "locale": "", + "text": "Done with \"WaterfallSkillBotDotNet\". \n\n What delivery mode would you like to use?", + "speak": "Done with \"WaterfallSkillBotDotNet\". \n\n What delivery mode would you like to use?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "normal", + "value": "normal" + }, + { + "type": "imBack", + "title": "expectReplies", + "value": "expectReplies" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "d53fc2e0-616c-11eb-b46e-c3c3213a748e", + "id": "d5dffa80-616c-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:29:23-03:00", + "timestamp": "2021-01-28T13:29:23.752Z" + } +] diff --git a/tests/functional/Tests/SkillFunctionalTests/ProactiveMessages/ProactiveTests.cs b/tests/functional/Tests/SkillFunctionalTests/ProactiveMessages/ProactiveTests.cs new file mode 100644 index 0000000000..cf5fb00613 --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/ProactiveMessages/ProactiveTests.cs @@ -0,0 +1,121 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.Bot.Connector; +using Microsoft.Bot.Schema; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using SkillFunctionalTests.Common; +using TranscriptTestRunner; +using TranscriptTestRunner.XUnit; +using Xunit; +using Xunit.Abstractions; + +namespace SkillFunctionalTests.ProactiveMessages +{ + [Trait("TestCategory", "ProactiveMessages")] + public class ProactiveTests : ScriptTestBase + { + private readonly string _testScriptsFolder = Directory.GetCurrentDirectory() + @"/ProactiveMessages/TestScripts"; + + public ProactiveTests(ITestOutputHelper output) + : base(output) + { + } + + public static IEnumerable TestCases() + { + var channelIds = new List { Channels.Directline }; + var deliverModes = new List + { + DeliveryModes.Normal, + DeliveryModes.ExpectReplies + }; + + var hostBots = new List + { + HostBot.WaterfallHostBotDotNet, + HostBot.WaterfallHostBotJS, + HostBot.WaterfallHostBotPython, + + // TODO: Enable this when the port to composer is ready + //HostBot.ComposerHostBotDotNet + }; + + var targetSkills = new List + { + SkillBotNames.WaterfallSkillBotDotNet, + SkillBotNames.WaterfallSkillBotPython, + + // TODO: Enable these when the ports to JS, and composer are ready + //SkillBotNames.WaterfallSkillBotJS, + //SkillBotNames.ComposerSkillBotDotNet + }; + + var scripts = new List + { + "ProactiveStart.json", + }; + + var testCaseBuilder = new TestCaseBuilder(); + + var testCases = testCaseBuilder.BuildTestCases(channelIds, deliverModes, hostBots, targetSkills, scripts); + foreach (var testCase in testCases) + { + yield return testCase; + } + } + + [Theory] + [MemberData(nameof(TestCases))] + public async Task RunTestCases(TestCaseDataObject testData) + { + var userId = string.Empty; + var url = string.Empty; + + var testCase = testData.GetObject(); + Logger.LogInformation(JsonConvert.SerializeObject(testCase, Formatting.Indented)); + + var options = TestClientOptions[testCase.HostBot]; + var runner = new XUnitTestRunner(new TestClientFactory(testCase.ChannelId, options, Logger).GetTestClient(), TestRequestTimeout, Logger); + + var testParamsStart = new Dictionary + { + { "DeliveryMode", testCase.DeliveryMode }, + { "TargetSkill", testCase.TargetSkill } + }; + + // Execute the first part of the conversation. + await runner.RunTestAsync(Path.Combine(_testScriptsFolder, testCase.Script), testParamsStart); + + await runner.AssertReplyAsync(activity => + { + Assert.Equal(ActivityTypes.Message, activity.Type); + Assert.Contains("Navigate to http", activity.Text); + + var message = activity.Text.Split(" "); + url = message[2]; + userId = url.Split("user=")[1]; + }); + + // Send a get request to the message's url to continue the conversation. + using (var client = new HttpClient()) + { + await client.GetAsync(url).ConfigureAwait(false); + } + + var testParamsEnd = new Dictionary + { + { "UserId", userId }, + { "TargetSkill", testCase.TargetSkill } + }; + + // Execute the rest of the conversation passing the messageId. + await runner.RunTestAsync(Path.Combine(_testScriptsFolder, "ProactiveEnd.json"), testParamsEnd); + } + } +} diff --git a/tests/functional/Tests/SkillFunctionalTests/ProactiveMessages/TestScripts/ProactiveEnd.json b/tests/functional/Tests/SkillFunctionalTests/ProactiveMessages/TestScripts/ProactiveEnd.json new file mode 100644 index 0000000000..9e6671b8d0 --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/ProactiveMessages/TestScripts/ProactiveEnd.json @@ -0,0 +1,47 @@ +{ + "items": [ + { + "type": "message", + "role": "bot", + "text": "Got proactive message for user: ${UserId}", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'Got proactive message for user: ${UserId}'", + "inputHint == 'acceptingInput'" + ] + }, + { + "type": "message", + "role": "bot", + "text": "We received a proactive message, ending the dialog", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'We received a proactive message, ending the dialog'", + "inputHint == 'acceptingInput'" + ] + }, + { + "type": "message", + "role": "bot", + "text": "Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?'", + "speak == 'Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?'", + "inputHint == 'expectingInput'", + "suggestedActions.actions[0].type == 'imBack'", + "suggestedActions.actions[0].title == 'normal'", + "suggestedActions.actions[0].value == 'normal'", + "suggestedActions.actions[1].type == 'imBack'", + "suggestedActions.actions[1].title == 'expectReplies'", + "suggestedActions.actions[1].value == 'expectReplies'" + ] + } + ] +} \ No newline at end of file diff --git a/tests/functional/Tests/SkillFunctionalTests/ProactiveMessages/TestScripts/ProactiveEnd.transcript b/tests/functional/Tests/SkillFunctionalTests/ProactiveMessages/TestScripts/ProactiveEnd.transcript new file mode 100644 index 0000000000..0ee0498b53 --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/ProactiveMessages/TestScripts/ProactiveEnd.transcript @@ -0,0 +1,91 @@ +[ + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "980966c0-616b-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "bc95d182-9d74-4d9d-82ec-d650cb5e83d8", + "role": "user" + }, + "locale": "", + "text": "Got proactive message for user: ${UserId}", + "inputHint": "acceptingInput", + "replyToId": "5dfebb53-9851-449a-a86f-dff9a9155816", + "id": "f44c7b20-616b-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:23:05-03:00", + "timestamp": "2021-01-28T13:23:05.298Z" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "980966c0-616b-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "bc95d182-9d74-4d9d-82ec-d650cb5e83d8", + "role": "user" + }, + "locale": "", + "text": "We received a proactive message, ending the dialog", + "inputHint": "acceptingInput", + "replyToId": "5dfebb53-9851-449a-a86f-dff9a9155816", + "id": "f4789430-616b-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:23:05-03:00", + "timestamp": "2021-01-28T13:23:05.587Z" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "980966c0-616b-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "bc95d182-9d74-4d9d-82ec-d650cb5e83d8", + "role": "user" + }, + "locale": "", + "text": "Done with \"WaterfallSkillBotDotNet\". \n\n What delivery mode would you like to use?", + "speak": "Done with \"WaterfallSkillBotDotNet\". \n\n What delivery mode would you like to use?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "normal", + "value": "normal" + }, + { + "type": "imBack", + "title": "expectReplies", + "value": "expectReplies" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "5dfebb53-9851-449a-a86f-dff9a9155816", + "id": "f4a4fb60-616b-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:23:05-03:00", + "timestamp": "2021-01-28T13:23:05.878Z" + } +] diff --git a/tests/functional/Tests/SkillFunctionalTests/ProactiveMessages/TestScripts/ProactiveStart.json b/tests/functional/Tests/SkillFunctionalTests/ProactiveMessages/TestScripts/ProactiveStart.json new file mode 100644 index 0000000000..1f1b1108d8 --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/ProactiveMessages/TestScripts/ProactiveStart.json @@ -0,0 +1,137 @@ +{ + "items": [ + { + "type": "conversationUpdate", + "role": "user" + }, + { + "type": "message", + "role": "bot", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "attachmentLayout == 'list'", + "speak == 'Welcome to the waterfall host bot'", + "inputHint == 'acceptingInput'", + "attachments[0].contentType == 'application/vnd.microsoft.card.adaptive'", + "attachments[0].content.type == 'AdaptiveCard'", + "attachments[0].content.body[0].type == 'Image'", + "attachments[0].content.body[0].size == 'stretch'", + "attachments[0].content.body[1].type == 'TextBlock'", + "attachments[0].content.body[1].spacing == 'Medium'", + "attachments[0].content.body[1].size == 'Medium'", + "attachments[0].content.body[1].weight == 'Bolder'", + "attachments[0].content.body[1].text == 'Welcome to the Skill Dialog Sample!'", + "attachments[0].content.body[1].wrap == True", + "attachments[0].content.body[1].color == 'Accent'", + "attachments[0].content.body[2].type == 'TextBlock'", + "attachments[0].content.body[2].text == 'This sample allows you to connect to a skill using a SkillDialog and invoke several actions.'", + "attachments[0].content.body[2].wrap == True" + ] + }, + { + "type": "message", + "role": "bot", + "text": "What delivery mode would you like to use?", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'What delivery mode would you like to use?'", + "speak == 'What delivery mode would you like to use?'", + "inputHint == 'expectingInput'", + "suggestedActions.actions[0].type == 'imBack'", + "suggestedActions.actions[0].title == 'normal'", + "suggestedActions.actions[0].value == 'normal'", + "suggestedActions.actions[1].type == 'imBack'", + "suggestedActions.actions[1].title == 'expectReplies'", + "suggestedActions.actions[1].value == 'expectReplies'" + ] + }, + { + "type": "message", + "role": "user", + "text": "${DeliveryMode}" + }, + { + "type": "message", + "role": "bot", + "text": "What group of skills would you like to use?", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'What group of skills would you like to use?'", + "speak == 'What group of skills would you like to use?'", + "inputHint == 'expectingInput'", + "suggestedActions.actions[0].type == 'imBack'", + "suggestedActions.actions[0].title == 'Echo' || suggestedActions.actions[0].title == 'Waterfall' || suggestedActions.actions[0].title == 'Teams'", + "suggestedActions.actions[0].value == 'Echo' || suggestedActions.actions[0].title == 'Waterfall' || suggestedActions.actions[0].title == 'Teams'" + ] + }, + { + "type": "message", + "role": "user", + "text": "Waterfall" + }, + { + "type": "message", + "role": "bot", + "text": "What skill would you like to call?\n\n 1. WaterfallSkillBotDotNet\n 2. WaterfallSkillBotJS\n 3. WaterfallSkillBotPython", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "startsWith(text, 'What skill would you like to call?')", + "speak == 'What skill would you like to call?'", + "inputHint == 'expectingInput'" + ] + }, + { + "type": "message", + "role": "user", + "text": "${TargetSkill}" + }, + { + "type": "message", + "role": "bot", + "text": "Select an action # to send to **${TargetSkill}**.\n\n 1. Cards\n 2. Proactive\n 3. Auth\n 4. MessageWithAttachment\n 5. Sso\n 6. FileUpload\n 7. Echo\n 8. Delete\n 9. Update", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'Select an action to send to **${TargetSkill}**.\n\n 1. Cards\n 2. Proactive\n 3. Auth\n 4. MessageWithAttachment\n 5. Sso\n 6. FileUpload\n 7. Echo\n 8. Delete\n 9. Update'", + "speak == 'Select an action to send to **${TargetSkill}**.'", + "inputHint == 'expectingInput'" + ] + }, + { + "type": "message", + "role": "user", + "text": "Proactive" + }, + { + "type": "trace", + "role": "bot", + "assertions": [ + "type == 'trace'", + "from.role == 'bot'", + "recipient.role == 'user'", + "label == 'Got ActivityType: event'", + "name == 'ActivityRouterDialog.ProcessActivityAsync()'" + ] + }, + { + "type": "trace", + "role": "bot", + "assertions": [ + "type == 'trace'", + "from.role == 'bot'", + "recipient.role == 'user'", + "label == 'Name: Proactive. Value: '", + "name == 'ActivityRouterDialog.OnEventActivityAsync()'" + ] + } + ] +} diff --git a/tests/functional/Tests/SkillFunctionalTests/ProactiveMessages/TestScripts/ProactiveStart.transcript b/tests/functional/Tests/SkillFunctionalTests/ProactiveMessages/TestScripts/ProactiveStart.transcript new file mode 100644 index 0000000000..ef34517760 --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/ProactiveMessages/TestScripts/ProactiveStart.transcript @@ -0,0 +1,408 @@ +[ + { + "type": "conversationUpdate", + "membersAdded": [ + { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot" + }, + { + "id": "bc95d182-9d74-4d9d-82ec-d650cb5e83d8", + "name": "User" + } + ], + "membersRemoved": [], + "channelId": "emulator", + "conversation": { + "id": "980966c0-616b-11eb-881a-afe376da3863|livechat" + }, + "id": "981e0030-616b-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:20:30-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "timestamp": "2021-01-28T13:20:30.643Z", + "from": { + "id": "bc95d182-9d74-4d9d-82ec-d650cb5e83d8", + "name": "User", + "role": "user" + }, + "locale": "", + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "980966c0-616b-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "bc95d182-9d74-4d9d-82ec-d650cb5e83d8", + "role": "user" + }, + "attachmentLayout": "list", + "locale": "", + "speak": "Welcome to the waterfall host bot", + "inputHint": "acceptingInput", + "attachments": [ + { + "contentType": "application/vnd.microsoft.card.adaptive", + "content": { + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.0", + "body": [ + { + "type": "Image", + "url": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQtB3AwMUeNoq4gUBGe6Ocj8kyh3bXa9ZbV7u1fVKQoyKFHdkqU", + "size": "stretch" + }, + { + "type": "TextBlock", + "spacing": "Medium", + "size": "Medium", + "weight": "Bolder", + "text": "Welcome to the Skill Dialog Sample!", + "wrap": true, + "maxLines": 0, + "color": "Accent" + }, + { + "type": "TextBlock", + "size": "default", + "text": "This sample allows you to connect to a skill using a SkillDialog and invoke several actions.", + "wrap": true, + "maxLines": 0 + } + ] + } + } + ], + "entities": [], + "replyToId": "981e0030-616b-11eb-b46e-c3c3213a748e", + "id": "98f26730-616b-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:20:32-03:00", + "timestamp": "2021-01-28T13:20:32.035Z" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "980966c0-616b-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "bc95d182-9d74-4d9d-82ec-d650cb5e83d8", + "role": "user" + }, + "locale": "", + "text": "What delivery mode would you like to use?", + "speak": "What delivery mode would you like to use?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "normal", + "value": "normal" + }, + { + "type": "imBack", + "title": "expectReplies", + "value": "expectReplies" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "981e0030-616b-11eb-b46e-c3c3213a748e", + "id": "991c0f40-616b-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:20:32-03:00", + "timestamp": "2021-01-28T13:20:32.308Z" + }, + { + "channelData": { + "clientActivityID": "1611840033447rjp2jvkf9jk", + "clientTimestamp": "2021-01-28T13:20:33.447Z" + }, + "text": "normal", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "bc95d182-9d74-4d9d-82ec-d650cb5e83d8", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T13:20:33.472Z", + "entities": [ + { + "requiresBotState": true, + "supportsListening": true, + "supportsTts": true, + "type": "ClientCapabilities" + } + ], + "conversation": { + "id": "980966c0-616b-11eb-881a-afe376da3863|livechat" + }, + "id": "99cdac00-616b-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:20:33-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "980966c0-616b-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "bc95d182-9d74-4d9d-82ec-d650cb5e83d8", + "role": "user" + }, + "locale": "", + "text": "What type of skill would you like to use?", + "speak": "What type of skill would you like to use?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "EchoSkill", + "value": "EchoSkill" + }, + { + "type": "imBack", + "title": "WaterfallSkill", + "value": "WaterfallSkill" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "99cdac00-616b-11eb-b46e-c3c3213a748e", + "id": "9a618790-616b-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:20:34-03:00", + "timestamp": "2021-01-28T13:20:34.441Z" + }, + { + "channelData": { + "clientActivityID": "1611840035554f72iioqm5u6", + "clientTimestamp": "2021-01-28T13:20:35.554Z" + }, + "text": "WaterfallSkill", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "bc95d182-9d74-4d9d-82ec-d650cb5e83d8", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T13:20:35.584Z", + "conversation": { + "id": "980966c0-616b-11eb-881a-afe376da3863|livechat" + }, + "id": "9b0ff000-616b-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:20:35-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "980966c0-616b-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "bc95d182-9d74-4d9d-82ec-d650cb5e83d8", + "role": "user" + }, + "locale": "", + "text": "What skill would you like to call?", + "speak": "What skill would you like to call?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "WaterfallSkillBotDotNet", + "value": "WaterfallSkillBotDotNet" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "9b0ff000-616b-11eb-b46e-c3c3213a748e", + "id": "9babe1e0-616b-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:20:36-03:00", + "timestamp": "2021-01-28T13:20:36.606Z" + }, + { + "channelData": { + "clientActivityID": "1611840037630e91nk7asij4", + "clientTimestamp": "2021-01-28T13:20:37.630Z" + }, + "text": "WaterfallSkillBotDotNet", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "bc95d182-9d74-4d9d-82ec-d650cb5e83d8", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T13:20:37.663Z", + "conversation": { + "id": "980966c0-616b-11eb-881a-afe376da3863|livechat" + }, + "id": "9c4d2af0-616b-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:20:37-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "980966c0-616b-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "bc95d182-9d74-4d9d-82ec-d650cb5e83d8", + "role": "user" + }, + "locale": "", + "text": "Select an action # to send to **WaterfallSkillBotDotNet**.\n\nOr just type in a message and it will be forwarded to the skill as a message activity.\n\n 1. Cards\n 2. Proactive\n 3. Auth\n 4. MessageWithAttachment\n 5. Sso\n 6. FileUpload\n 7. Echo", + "speak": "Select an action # to send to **WaterfallSkillBotDotNet**.\n\nOr just type in a message and it will be forwarded to the skill as a message activity.", + "inputHint": "expectingInput", + "attachments": [], + "entities": [], + "replyToId": "9c4d2af0-616b-11eb-b46e-c3c3213a748e", + "id": "9ce30250-616b-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:20:38-03:00", + "timestamp": "2021-01-28T13:20:38.645Z" + }, + { + "channelData": { + "clientActivityID": "16118400442741u2eexpiomv", + "clientTimestamp": "2021-01-28T13:20:44.274Z" + }, + "text": "Proactive", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "bc95d182-9d74-4d9d-82ec-d650cb5e83d8", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-28T13:20:44.313Z", + "conversation": { + "id": "980966c0-616b-11eb-881a-afe376da3863|livechat" + }, + "id": "a043e090-616b-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:20:44-03:00", + "recipient": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://9ed82cccfc76.ngrok.io" + }, + { + "type": "trace", + "timestamp": "2021-01-28T13:20:45.553Z", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "980966c0-616b-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "bc95d182-9d74-4d9d-82ec-d650cb5e83d8", + "role": "user" + }, + "locale": "", + "replyToId": "a043e090-616b-11eb-b46e-c3c3213a748e", + "label": "Got ActivityType: event", + "name": "ActivityRouterDialog.ProcessActivityAsync()", + "id": "a1011610-616b-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:20:45-03:00" + }, + { + "type": "trace", + "timestamp": "2021-01-28T13:20:45.845Z", + "serviceUrl": "https://9ed82cccfc76.ngrok.io", + "channelId": "emulator", + "from": { + "id": "48224d50-60a5-11eb-881a-afe376da3863", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "980966c0-616b-11eb-881a-afe376da3863|livechat" + }, + "recipient": { + "id": "bc95d182-9d74-4d9d-82ec-d650cb5e83d8", + "role": "user" + }, + "locale": "", + "replyToId": "a043e090-616b-11eb-b46e-c3c3213a748e", + "label": "Name: Proactive. Value: ", + "name": "ActivityRouterDialog.OnEventActivityAsync()", + "id": "a12da450-616b-11eb-b46e-c3c3213a748e", + "localTimestamp": "2021-01-28T10:20:45-03:00" + } +] diff --git a/tests/functional/Tests/SkillFunctionalTests/README.md b/tests/functional/Tests/SkillFunctionalTests/README.md new file mode 100644 index 0000000000..7de681fca3 --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/README.md @@ -0,0 +1,7 @@ +# Skills Functional Tests + +## Summary + +Skill functional testing aims to automate the testing of Host and Skill interactions through several scenarios in all available programming languages. + +Head to [Docs](../../Docs/) directory for more information. diff --git a/tests/functional/Tests/SkillFunctionalTests/ScriptTestBase.cs b/tests/functional/Tests/SkillFunctionalTests/ScriptTestBase.cs new file mode 100644 index 0000000000..59ae89e379 --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/ScriptTestBase.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.IO; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using SkillFunctionalTests.Common; +using TranscriptTestRunner.TestClients; +using Xunit.Abstractions; + +namespace SkillFunctionalTests +{ + public class ScriptTestBase + { + public ScriptTestBase(ITestOutputHelper output) + { + var configuration = new ConfigurationBuilder() + .AddJsonFile("appsettings.json") + .AddJsonFile("appsettings.Development.json", true, true) + .AddEnvironmentVariables() + .Build(); + + var loggerFactory = LoggerFactory.Create(builder => + { + builder + .AddConfiguration(configuration.GetSection("Logging")) + .AddConsole() + .AddDebug() + .AddFile(Directory.GetCurrentDirectory() + @"/Logs/Log.json", isJson: true) + .AddXunit(output); + }); + + Logger = loggerFactory.CreateLogger(); + + TestRequestTimeout = int.Parse(configuration["TestRequestTimeout"]); + TestClientOptions = configuration.GetSection("HostBotClientOptions").Get>(); + } + + public Dictionary TestClientOptions { get; } + + public ILogger Logger { get; } + + public int TestRequestTimeout { get; } + } +} diff --git a/tests/functional/Tests/SkillFunctionalTests/SignIn/SignInTests.cs b/tests/functional/Tests/SkillFunctionalTests/SignIn/SignInTests.cs new file mode 100644 index 0000000000..abbb3582c7 --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/SignIn/SignInTests.cs @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Bot.Connector; +using Microsoft.Bot.Schema; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using SkillFunctionalTests.Common; +using TranscriptTestRunner; +using TranscriptTestRunner.XUnit; +using Xunit; +using Xunit.Abstractions; + +namespace SkillFunctionalTests.SignIn +{ + [Trait("TestCategory", "SignIn")] + public class SignInTests : ScriptTestBase + { + private readonly string _testScriptsFolder = Directory.GetCurrentDirectory() + @"/SignIn/TestScripts"; + + public SignInTests(ITestOutputHelper output) + : base(output) + { + } + + public static IEnumerable TestCases() + { + var channelIds = new List { Channels.Directline }; + var deliverModes = new List + { + DeliveryModes.Normal, + DeliveryModes.ExpectReplies, + }; + + var hostBots = new List + { + HostBot.WaterfallHostBotDotNet, + HostBot.WaterfallHostBotJS, + HostBot.WaterfallHostBotPython, + + // TODO: Enable this when the port to composer is ready + //HostBot.ComposerHostBotDotNet + }; + + var targetSkills = new List + { + SkillBotNames.WaterfallSkillBotDotNet, + SkillBotNames.WaterfallSkillBotJS, + SkillBotNames.WaterfallSkillBotPython, + + // TODO: Enable this when the port to composer is ready + //SkillBotNames.ComposerSkillBotDotNet + }; + + var scripts = new List + { + "SignIn1.json" + }; + + var testCaseBuilder = new TestCaseBuilder(); + + var testCases = testCaseBuilder.BuildTestCases(channelIds, deliverModes, hostBots, targetSkills, scripts); + foreach (var testCase in testCases) + { + yield return testCase; + } + } + + [Theory] + [MemberData(nameof(TestCases))] + public async Task RunTestCases(TestCaseDataObject testData) + { + var signInUrl = string.Empty; + var testCase = testData.GetObject(); + Logger.LogInformation(JsonConvert.SerializeObject(testCase, Formatting.Indented)); + + var options = TestClientOptions[testCase.HostBot]; + var runner = new XUnitTestRunner(new TestClientFactory(testCase.ChannelId, options, Logger).GetTestClient(), TestRequestTimeout, Logger); + + var testParams = new Dictionary + { + { "DeliveryMode", testCase.DeliveryMode }, + { "TargetSkill", testCase.TargetSkill } + }; + + // Execute the first part of the conversation. + await runner.RunTestAsync(Path.Combine(_testScriptsFolder, testCase.Script), testParams); + + await runner.AssertReplyAsync(activity => + { + Assert.Equal(ActivityTypes.Message, activity.Type); + Assert.True(activity.Attachments.Count > 0); + + var card = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(activity.Attachments.FirstOrDefault().Content)); + signInUrl = card.Buttons[0].Value?.ToString(); + + Assert.False(string.IsNullOrEmpty(signInUrl)); + }); + + // Execute the SignIn. + await runner.ClientSignInAsync(signInUrl); + + // Execute the rest of the conversation passing the messageId. + await runner.RunTestAsync(Path.Combine(_testScriptsFolder, "SignIn2.json"), testParams); + } + } +} diff --git a/tests/functional/Tests/SkillFunctionalTests/SignIn/TestScripts/SignIn1.json b/tests/functional/Tests/SkillFunctionalTests/SignIn/TestScripts/SignIn1.json new file mode 100644 index 0000000000..75f761289a --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/SignIn/TestScripts/SignIn1.json @@ -0,0 +1,112 @@ +{ + "items": [ + { + "type": "conversationUpdate", + "role": "user" + }, + { + "type": "message", + "role": "bot", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "attachmentLayout == 'list'", + "speak == 'Welcome to the waterfall host bot'", + "inputHint == 'acceptingInput'", + "attachments[0].contentType == 'application/vnd.microsoft.card.adaptive'", + "attachments[0].content.type == 'AdaptiveCard'", + "attachments[0].content.body[0].type == 'Image'", + "attachments[0].content.body[0].size == 'stretch'", + "attachments[0].content.body[1].type == 'TextBlock'", + "attachments[0].content.body[1].spacing == 'Medium'", + "attachments[0].content.body[1].size == 'Medium'", + "attachments[0].content.body[1].weight == 'Bolder'", + "attachments[0].content.body[1].text == 'Welcome to the Skill Dialog Sample!'", + "attachments[0].content.body[1].wrap == True", + "attachments[0].content.body[1].color == 'Accent'", + "attachments[0].content.body[2].type == 'TextBlock'", + "attachments[0].content.body[2].text == 'This sample allows you to connect to a skill using a SkillDialog and invoke several actions.'", + "attachments[0].content.body[2].wrap == True" + ] + }, + { + "type": "message", + "role": "bot", + "text": "What delivery mode would you like to use?", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'What delivery mode would you like to use?'", + "speak == 'What delivery mode would you like to use?'", + "inputHint == 'expectingInput'", + "suggestedActions.actions[0].type == 'imBack'", + "suggestedActions.actions[0].title == 'normal'", + "suggestedActions.actions[0].value == 'normal'", + "suggestedActions.actions[1].type == 'imBack'", + "suggestedActions.actions[1].title == 'expectReplies'", + "suggestedActions.actions[1].value == 'expectReplies'" + ] + }, + { + "type": "message", + "role": "user", + "text": "${DeliveryMode}" + }, + { + "type": "message", + "role": "bot", + "text": "What group of skills would you like to use?", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'What group of skills would you like to use?'", + "speak == 'What group of skills would you like to use?'", + "inputHint == 'expectingInput'", + "suggestedActions.actions[0].type == 'imBack'", + "suggestedActions.actions[0].title == 'Echo' || suggestedActions.actions[0].title == 'Waterfall' || suggestedActions.actions[0].title == 'Teams'", + "suggestedActions.actions[0].value == 'Echo' || suggestedActions.actions[0].title == 'Waterfall' || suggestedActions.actions[0].title == 'Teams'" + ] + }, + { + "type": "message", + "role": "user", + "text": "Waterfall" + }, + { + "type": "message", + "role": "bot", + "text": "What skill would you like to call?\n\n 1. WaterfallSkillBotDotNet\n 2. WaterfallSkillBotJS\n 3. WaterfallSkillBotPython", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "startsWith(text, 'What skill would you like to call?')", + "speak == 'What skill would you like to call?'", + "inputHint == 'expectingInput'" + ] + }, + { + "type": "message", + "role": "user", + "text": "${TargetSkill}" + }, + { + "type": "message", + "role": "bot", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "inputHint == 'expectingInput'" + ] + }, + { + "type": "message", + "role": "user", + "text": "Auth" + } + ] +} diff --git a/tests/functional/Tests/SkillFunctionalTests/SignIn/TestScripts/SignIn1.transcript b/tests/functional/Tests/SkillFunctionalTests/SignIn/TestScripts/SignIn1.transcript new file mode 100644 index 0000000000..71181719c3 --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/SignIn/TestScripts/SignIn1.transcript @@ -0,0 +1,360 @@ +[ + { + "type": "conversationUpdate", + "membersAdded": [ + { + "id": "73573150-60c5-11eb-b881-018622be8a2a", + "name": "Bot" + }, + { + "id": "b5d6e07f-f8f9-491d-b767-10a0817f89f2", + "name": "User" + } + ], + "membersRemoved": [], + "channelId": "emulator", + "conversation": { + "id": "da522520-60d1-11eb-b881-018622be8a2a|livechat" + }, + "id": "da5e8130-60d1-11eb-8b98-b971494822a6", + "localTimestamp": "2021-01-27T15:59:59-03:00", + "recipient": { + "id": "73573150-60c5-11eb-b881-018622be8a2a", + "name": "Bot", + "role": "bot" + }, + "timestamp": "2021-01-27T18:59:59.299Z", + "from": { + "id": "b5d6e07f-f8f9-491d-b767-10a0817f89f2", + "name": "User", + "role": "user" + }, + "locale": "", + "serviceUrl": "https://259039676c61.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://259039676c61.ngrok.io", + "channelId": "emulator", + "from": { + "id": "73573150-60c5-11eb-b881-018622be8a2a", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "da522520-60d1-11eb-b881-018622be8a2a|livechat" + }, + "recipient": { + "id": "b5d6e07f-f8f9-491d-b767-10a0817f89f2", + "role": "user" + }, + "attachmentLayout": "list", + "locale": "", + "speak": "Welcome to the waterfall host bot", + "inputHint": "acceptingInput", + "attachments": [ + { + "contentType": "application/vnd.microsoft.card.adaptive", + "content": { + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.0", + "body": [ + { + "type": "Image", + "url": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQtB3AwMUeNoq4gUBGe6Ocj8kyh3bXa9ZbV7u1fVKQoyKFHdkqU", + "size": "stretch" + }, + { + "type": "TextBlock", + "spacing": "Medium", + "size": "Medium", + "weight": "Bolder", + "text": "Welcome to the Skill Dialog Sample!", + "wrap": true, + "maxLines": 0, + "color": "Accent" + }, + { + "type": "TextBlock", + "size": "default", + "text": "This sample allows you to connect to a skill using a SkillDialog and invoke several actions.", + "wrap": true, + "maxLines": 0 + } + ] + } + } + ], + "entities": [], + "replyToId": "da5e8130-60d1-11eb-8b98-b971494822a6", + "id": "db473380-60d1-11eb-8b98-b971494822a6", + "localTimestamp": "2021-01-27T16:00:00-03:00", + "timestamp": "2021-01-27T19:00:00.824Z" + }, + { + "type": "message", + "serviceUrl": "https://259039676c61.ngrok.io", + "channelId": "emulator", + "from": { + "id": "73573150-60c5-11eb-b881-018622be8a2a", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "da522520-60d1-11eb-b881-018622be8a2a|livechat" + }, + "recipient": { + "id": "b5d6e07f-f8f9-491d-b767-10a0817f89f2", + "role": "user" + }, + "locale": "", + "text": "What delivery mode would you like to use?", + "speak": "What delivery mode would you like to use?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "normal", + "value": "normal" + }, + { + "type": "imBack", + "title": "expectReplies", + "value": "expectReplies" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "da5e8130-60d1-11eb-8b98-b971494822a6", + "id": "db7129b0-60d1-11eb-8b98-b971494822a6", + "localTimestamp": "2021-01-27T16:00:01-03:00", + "timestamp": "2021-01-27T19:00:01.099Z" + }, + { + "channelData": { + "clientActivityID": "16117740025163t1zr5cif", + "clientTimestamp": "2021-01-27T19:00:02.516Z" + }, + "text": "normal", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "b5d6e07f-f8f9-491d-b767-10a0817f89f2", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-27T19:00:02.528Z", + "entities": [ + { + "requiresBotState": true, + "supportsListening": true, + "supportsTts": true, + "type": "ClientCapabilities" + } + ], + "conversation": { + "id": "da522520-60d1-11eb-b881-018622be8a2a|livechat" + }, + "id": "dc4b3600-60d1-11eb-8b98-b971494822a6", + "localTimestamp": "2021-01-27T16:00:02-03:00", + "recipient": { + "id": "73573150-60c5-11eb-b881-018622be8a2a", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://259039676c61.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://259039676c61.ngrok.io", + "channelId": "emulator", + "from": { + "id": "73573150-60c5-11eb-b881-018622be8a2a", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "da522520-60d1-11eb-b881-018622be8a2a|livechat" + }, + "recipient": { + "id": "b5d6e07f-f8f9-491d-b767-10a0817f89f2", + "role": "user" + }, + "locale": "", + "text": "What type of skill would you like to use?", + "speak": "What type of skill would you like to use?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "EchoSkill", + "value": "EchoSkill" + }, + { + "type": "imBack", + "title": "WaterfallSkill", + "value": "WaterfallSkill" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "dc4b3600-60d1-11eb-8b98-b971494822a6", + "id": "dcd65f00-60d1-11eb-8b98-b971494822a6", + "localTimestamp": "2021-01-27T16:00:03-03:00", + "timestamp": "2021-01-27T19:00:03.440Z" + }, + { + "channelData": { + "clientActivityID": "1611774004677b31ih46f32l", + "clientTimestamp": "2021-01-27T19:00:04.677Z" + }, + "text": "WaterfallSkill", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "b5d6e07f-f8f9-491d-b767-10a0817f89f2", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-27T19:00:04.692Z", + "conversation": { + "id": "da522520-60d1-11eb-b881-018622be8a2a|livechat" + }, + "id": "dd956940-60d1-11eb-8b98-b971494822a6", + "localTimestamp": "2021-01-27T16:00:04-03:00", + "recipient": { + "id": "73573150-60c5-11eb-b881-018622be8a2a", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://259039676c61.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://259039676c61.ngrok.io", + "channelId": "emulator", + "from": { + "id": "73573150-60c5-11eb-b881-018622be8a2a", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "da522520-60d1-11eb-b881-018622be8a2a|livechat" + }, + "recipient": { + "id": "b5d6e07f-f8f9-491d-b767-10a0817f89f2", + "role": "user" + }, + "locale": "", + "text": "What skill would you like to call?", + "speak": "What skill would you like to call?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "WaterfallSkillBotDotNet", + "value": "WaterfallSkillBotDotNet" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "dd956940-60d1-11eb-8b98-b971494822a6", + "id": "de24d800-60d1-11eb-8b98-b971494822a6", + "localTimestamp": "2021-01-27T16:00:05-03:00", + "timestamp": "2021-01-27T19:00:05.632Z" + }, + { + "channelData": { + "clientActivityID": "1611774006749prlvxjdfr2b", + "clientTimestamp": "2021-01-27T19:00:06.749Z" + }, + "text": "WaterfallSkillBotDotNet", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "b5d6e07f-f8f9-491d-b767-10a0817f89f2", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-27T19:00:06.776Z", + "conversation": { + "id": "da522520-60d1-11eb-b881-018622be8a2a|livechat" + }, + "id": "ded36780-60d1-11eb-8b98-b971494822a6", + "localTimestamp": "2021-01-27T16:00:06-03:00", + "recipient": { + "id": "73573150-60c5-11eb-b881-018622be8a2a", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://259039676c61.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://259039676c61.ngrok.io", + "channelId": "emulator", + "from": { + "id": "73573150-60c5-11eb-b881-018622be8a2a", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "da522520-60d1-11eb-b881-018622be8a2a|livechat" + }, + "recipient": { + "id": "b5d6e07f-f8f9-491d-b767-10a0817f89f2", + "role": "user" + }, + "locale": "", + "text": "Select an action # to send to **WaterfallSkillBotDotNet**.\n\nOr just type in a message and it will be forwarded to the skill as a message activity.\n\n 1. Cards\n 2. Proactive\n 3. Auth\n 4. MessageWithAttachment\n 5. Sso\n 6. FileUpload\n 7. Echo", + "speak": "Select an action # to send to **WaterfallSkillBotDotNet**.\n\nOr just type in a message and it will be forwarded to the skill as a message activity.", + "inputHint": "expectingInput", + "attachments": [], + "entities": [], + "replyToId": "ded36780-60d1-11eb-8b98-b971494822a6", + "id": "df60da70-60d1-11eb-8b98-b971494822a6", + "localTimestamp": "2021-01-27T16:00:07-03:00", + "timestamp": "2021-01-27T19:00:07.703Z" + }, + { + "channelData": { + "clientActivityID": "1611774010842ks0wa2td8kl", + "clientTimestamp": "2021-01-27T19:00:10.842Z" + }, + "text": "Auth", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "b5d6e07f-f8f9-491d-b767-10a0817f89f2", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-27T19:00:10.866Z", + "conversation": { + "id": "da522520-60d1-11eb-b881-018622be8a2a|livechat" + }, + "id": "e1437d20-60d1-11eb-8b98-b971494822a6", + "localTimestamp": "2021-01-27T16:00:10-03:00", + "recipient": { + "id": "73573150-60c5-11eb-b881-018622be8a2a", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://259039676c61.ngrok.io" + } +] \ No newline at end of file diff --git a/tests/functional/Tests/SkillFunctionalTests/SignIn/TestScripts/SignIn2.json b/tests/functional/Tests/SkillFunctionalTests/SignIn/TestScripts/SignIn2.json new file mode 100644 index 0000000000..6382f436e7 --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/SignIn/TestScripts/SignIn2.json @@ -0,0 +1,77 @@ +{ + "items": [ + { + "type": "message", + "role": "bot", + "text": "You are now logged in.", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'You are now logged in.'", + "speak == 'You are now logged in.'", + "inputHint == 'ignoringInput'" + ] + }, + { + "type": "message", + "role": "bot", + "text": "Would you like to view your token?", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'Would you like to view your token?'", + "inputHint == 'acceptingInput'" + ] + }, + { + "type": "message", + "role": "user", + "text": "yes" + }, + { + "type": "message", + "role": "bot", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "speak == 'Here is your token:'", + "inputHint == 'ignoringInput'" + ] + }, + { + "type": "message", + "role": "bot", + "text": "I have signed you out.", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'I have signed you out.'", + "speak == 'I have signed you out.'", + "inputHint == 'ignoringInput'" + ] + }, + { + "type": "message", + "role": "bot", + "text": "Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?'", + "speak == 'Done with \"${TargetSkill}\". \n\n What delivery mode would you like to use?'", + "inputHint == 'expectingInput'", + "suggestedActions.actions[0].type == 'imBack'", + "suggestedActions.actions[0].title == 'normal'", + "suggestedActions.actions[0].value == 'normal'", + "suggestedActions.actions[1].type == 'imBack'", + "suggestedActions.actions[1].title == 'expectReplies'", + "suggestedActions.actions[1].value == 'expectReplies'" + ] + } + ] +} \ No newline at end of file diff --git a/tests/functional/Tests/SkillFunctionalTests/SignIn/TestScripts/signin2.transcript b/tests/functional/Tests/SkillFunctionalTests/SignIn/TestScripts/signin2.transcript new file mode 100644 index 0000000000..2d8cf8ee19 --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/SignIn/TestScripts/signin2.transcript @@ -0,0 +1,124 @@ +[ + { + "type": "message", + "serviceUrl": "https://259039676c61.ngrok.io", + "channelId": "emulator", + "from": { + "id": "73573150-60c5-11eb-b881-018622be8a2a", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "da522520-60d1-11eb-b881-018622be8a2a|livechat" + }, + "recipient": { + "id": "b5d6e07f-f8f9-491d-b767-10a0817f89f2", + "role": "user" + }, + "locale": "", + "text": "You are now logged in.", + "speak": "You are now logged in.", + "inputHint": "ignoringInput", + "attachments": [], + "entities": [], + "replyToId": "05fa4ae0-60d2-11eb-8b98-b971494822a6", + "id": "07013390-60d2-11eb-8b98-b971494822a6", + "localTimestamp": "2021-01-27T16:01:14-03:00", + "timestamp": "2021-01-27T19:01:14.185Z" + }, + { + "type": "message", + "serviceUrl": "https://259039676c61.ngrok.io", + "channelId": "emulator", + "from": { + "id": "73573150-60c5-11eb-b881-018622be8a2a", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "da522520-60d1-11eb-b881-018622be8a2a|livechat" + }, + "recipient": { + "id": "b5d6e07f-f8f9-491d-b767-10a0817f89f2", + "role": "user" + }, + "locale": "", + "text": "Here is your token: 37f0ba3c-24bd-485c-9a11-7151fe604b95", + "speak": "Here is your token:", + "inputHint": "ignoringInput", + "attachments": [], + "entities": [], + "replyToId": "05fa4ae0-60d2-11eb-8b98-b971494822a6", + "id": "072c6240-60d2-11eb-8b98-b971494822a6", + "localTimestamp": "2021-01-27T16:01:14-03:00", + "timestamp": "2021-01-27T19:01:14.468Z" + }, + { + "type": "message", + "serviceUrl": "https://259039676c61.ngrok.io", + "channelId": "emulator", + "from": { + "id": "73573150-60c5-11eb-b881-018622be8a2a", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "da522520-60d1-11eb-b881-018622be8a2a|livechat" + }, + "recipient": { + "id": "b5d6e07f-f8f9-491d-b767-10a0817f89f2", + "role": "user" + }, + "locale": "", + "text": "I have signed you out.", + "speak": "I have signed you out.", + "inputHint": "ignoringInput", + "attachments": [], + "entities": [], + "replyToId": "05fa4ae0-60d2-11eb-8b98-b971494822a6", + "id": "077e2bc0-60d2-11eb-8b98-b971494822a6", + "localTimestamp": "2021-01-27T16:01:15-03:00", + "timestamp": "2021-01-27T19:01:15.004Z" + }, + { + "type": "message", + "serviceUrl": "https://259039676c61.ngrok.io", + "channelId": "emulator", + "from": { + "id": "73573150-60c5-11eb-b881-018622be8a2a", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "da522520-60d1-11eb-b881-018622be8a2a|livechat" + }, + "recipient": { + "id": "b5d6e07f-f8f9-491d-b767-10a0817f89f2", + "role": "user" + }, + "locale": "", + "text": "Done with \"WaterfallSkillBotDotNet\". \n\n What delivery mode would you like to use?", + "speak": "Done with \"WaterfallSkillBotDotNet\". \n\n What delivery mode would you like to use?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "normal", + "value": "normal" + }, + { + "type": "imBack", + "title": "expectReplies", + "value": "expectReplies" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "05fa4ae0-60d2-11eb-8b98-b971494822a6", + "id": "07aa92f0-60d2-11eb-8b98-b971494822a6", + "localTimestamp": "2021-01-27T16:01:15-03:00", + "timestamp": "2021-01-27T19:01:15.295Z" + } +] \ No newline at end of file diff --git a/tests/functional/Tests/SkillFunctionalTests/SingleTurn/EchoTests.cs b/tests/functional/Tests/SkillFunctionalTests/SingleTurn/EchoTests.cs new file mode 100644 index 0000000000..8ba52a0f71 --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/SingleTurn/EchoTests.cs @@ -0,0 +1,104 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Microsoft.Bot.Connector; +using Microsoft.Bot.Schema; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using SkillFunctionalTests.Common; +using TranscriptTestRunner; +using TranscriptTestRunner.XUnit; +using Xunit; +using Xunit.Abstractions; + +namespace SkillFunctionalTests.SingleTurn +{ + [Trait("TestCategory", "SingleTurn")] + public class EchoTests : ScriptTestBase + { + private readonly string _testScriptsFolder = Directory.GetCurrentDirectory() + @"/SingleTurn/TestScripts"; + + public EchoTests(ITestOutputHelper output) + : base(output) + { + } + + public static IEnumerable TestCases() + { + var channelIds = new List { Channels.Directline }; + var deliverModes = new List + { + DeliveryModes.Normal, + DeliveryModes.ExpectReplies + }; + + var hostBots = new List + { + HostBot.SimpleHostBotComposerDotNet, + HostBot.SimpleHostBotDotNet, + HostBot.SimpleHostBotDotNet21, + HostBot.SimpleHostBotJS, + HostBot.SimpleHostBotPython, + }; + + var targetSkills = new List + { + SkillBotNames.EchoSkillBotComposerDotNet, + SkillBotNames.EchoSkillBotDotNet, + SkillBotNames.EchoSkillBotDotNet21, + SkillBotNames.EchoSkillBotDotNetV3, + SkillBotNames.EchoSkillBotJS, + SkillBotNames.EchoSkillBotJSV3, + SkillBotNames.EchoSkillBotPython + }; + + var scripts = new List { "EchoMultiSkill.json" }; + + var testCaseBuilder = new TestCaseBuilder(); + + // This local function is used to exclude ExpectReplies test cases for v3 bots + static bool ShouldExclude(TestCase testCase) + { + if (testCase.DeliveryMode == DeliveryModes.ExpectReplies) + { + // Note: ExpectReplies is not supported by DotNetV3 and JSV3 skills. + if (testCase.TargetSkill == SkillBotNames.EchoSkillBotDotNetV3 || testCase.TargetSkill == SkillBotNames.EchoSkillBotJSV3) + { + return true; + } + } + + return false; + } + + var testCases = testCaseBuilder.BuildTestCases(channelIds, deliverModes, hostBots, targetSkills, scripts, ShouldExclude); + foreach (var testCase in testCases) + { + yield return testCase; + } + } + + [Theory] + [MemberData(nameof(TestCases))] + public async Task RunTestCases(TestCaseDataObject testData) + { + var testCase = testData.GetObject(); + Logger.LogInformation(JsonConvert.SerializeObject(testCase, Formatting.Indented)); + + var options = TestClientOptions[testCase.HostBot]; + + var runner = new XUnitTestRunner(new TestClientFactory(testCase.ChannelId, options, Logger).GetTestClient(), TestRequestTimeout, Logger); + + var testParams = new Dictionary + { + { "DeliveryMode", testCase.DeliveryMode }, + { "TargetSkill", testCase.TargetSkill } + }; + + await runner.RunTestAsync(Path.Combine(_testScriptsFolder, testCase.Script), testParams); + } + } +} diff --git a/tests/functional/Tests/SkillFunctionalTests/SingleTurn/TestScripts/EchoMultiSkill.json b/tests/functional/Tests/SkillFunctionalTests/SingleTurn/TestScripts/EchoMultiSkill.json new file mode 100644 index 0000000000..1b3c06348e --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/SingleTurn/TestScripts/EchoMultiSkill.json @@ -0,0 +1,163 @@ +{ + "items": [ + { + "type": "conversationUpdate", + "role": "user" + }, + { + "type": "message", + "role": "bot", + "text": "Hello and welcome!", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'Hello and welcome!'", + "inputHint == 'acceptingInput'" + ] + }, + { + "type": "message", + "role": "bot", + "text": "What delivery mode would you like to use?", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'What delivery mode would you like to use?'", + "speak == 'What delivery mode would you like to use?'", + "inputHint == 'expectingInput'", + "suggestedActions.actions[0].type == 'imBack'", + "suggestedActions.actions[0].title == 'normal'", + "suggestedActions.actions[0].value == 'normal'", + "suggestedActions.actions[1].type == 'imBack'", + "suggestedActions.actions[1].title == 'expectReplies'", + "suggestedActions.actions[1].value == 'expectReplies'" + ] + }, + { + "type": "message", + "role": "user", + "text": "${DeliveryMode}" + }, + { + "type": "message", + "role": "bot", + "text": "What skill would you like to call?", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'What skill would you like to call?'", + "speak == 'What skill would you like to call?'", + "inputHint == 'expectingInput'" + ] + }, + { + "type": "message", + "role": "user", + "text": "${TargetSkill}" + }, + { + "type": "message", + "role": "bot", + "text": "Type anything to send to the skill.", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'Type anything to send to the skill.'", + "inputHint == 'expectingInput'" + ] + }, + { + "type": "message", + "role": "user", + "text": "Hello" + }, + { + "type": "message", + "role": "bot", + "text": "Echo: Hello", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'Echo: Hello'", + "inputHint == 'acceptingInput'" + ] + }, + { + "type": "message", + "role": "bot", + "text": "Say \"end\" or \"stop\" and I'll end the conversation and back to the parent.", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'Say \"end\" or \"stop\" and I\\'ll end the conversation and back to the parent.'", + "inputHint == 'acceptingInput'" + ] + }, + { + "type": "message", + "role": "user", + "text": "end" + }, + { + "type": "message", + "role": "bot", + "text": "Ending conversation from the skill...", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'Ending conversation from the skill...'", + "inputHint == 'acceptingInput'" + ] + }, + { + "type": "message", + "role": "bot", + "text": "Received endOfConversation.\n\nCode: completedSuccessfully.", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'Received endOfConversation.\n\nCode: completedSuccessfully.'", + "inputHint == 'acceptingInput'" + ] + }, + { + "type": "message", + "role": "bot", + "text": "Back in the host bot.", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'Back in the host bot.'", + "inputHint == 'acceptingInput'" + ] + }, + { + "type": "message", + "role": "bot", + "text": "What delivery mode would you like to use?", + "assertions": [ + "type == 'message'", + "from.role == 'bot'", + "recipient.role == 'user'", + "text == 'What delivery mode would you like to use?'", + "speak == 'What delivery mode would you like to use?'", + "inputHint == 'expectingInput'", + "suggestedActions.actions[0].type == 'imBack'", + "suggestedActions.actions[0].title == 'normal'", + "suggestedActions.actions[0].value == 'normal'", + "suggestedActions.actions[1].type == 'imBack'", + "suggestedActions.actions[1].title == 'expectReplies'", + "suggestedActions.actions[1].value == 'expectReplies'" + ] + } + ] +} \ No newline at end of file diff --git a/tests/functional/Tests/SkillFunctionalTests/SingleTurn/TestScripts/EchoMultiSkill.transcript b/tests/functional/Tests/SkillFunctionalTests/SingleTurn/TestScripts/EchoMultiSkill.transcript new file mode 100644 index 0000000000..f5569cf169 --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/SingleTurn/TestScripts/EchoMultiSkill.transcript @@ -0,0 +1,480 @@ +[ + { + "type": "conversationUpdate", + "membersAdded": [ + { + "id": "56b5e650-4f58-11eb-aad9-1dbdd1591611", + "name": "Bot" + }, + { + "id": "c13a9ba2-8ba2-4364-85eb-5dd06d9c9331", + "name": "User" + } + ], + "membersRemoved": [], + "channelId": "emulator", + "conversation": { + "id": "67c4f710-4f58-11eb-aad9-1dbdd1591611|livechat" + }, + "id": "67d08fd0-4f58-11eb-80df-41840e87027c", + "localTimestamp": "2021-01-05T10:17:48-03:00", + "recipient": { + "id": "56b5e650-4f58-11eb-aad9-1dbdd1591611", + "name": "Bot", + "role": "bot" + }, + "timestamp": "2021-01-05T13:17:48.237Z", + "from": { + "id": "c13a9ba2-8ba2-4364-85eb-5dd06d9c9331", + "name": "User", + "role": "user" + }, + "locale": "", + "serviceUrl": "https://60ecb8345374.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://60ecb8345374.ngrok.io", + "channelId": "emulator", + "from": { + "id": "56b5e650-4f58-11eb-aad9-1dbdd1591611", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "67c4f710-4f58-11eb-aad9-1dbdd1591611|livechat" + }, + "recipient": { + "id": "c13a9ba2-8ba2-4364-85eb-5dd06d9c9331", + "role": "user" + }, + "locale": "", + "text": "Hello and welcome!", + "inputHint": "acceptingInput", + "attachments": [], + "entities": [], + "replyToId": "67d08fd0-4f58-11eb-80df-41840e87027c", + "id": "68722700-4f58-11eb-80df-41840e87027c", + "localTimestamp": "2021-01-05T10:17:49-03:00", + "timestamp": "2021-01-05T13:17:49.296Z" + }, + { + "type": "message", + "serviceUrl": "https://60ecb8345374.ngrok.io", + "channelId": "emulator", + "from": { + "id": "56b5e650-4f58-11eb-aad9-1dbdd1591611", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "67c4f710-4f58-11eb-aad9-1dbdd1591611|livechat" + }, + "recipient": { + "id": "c13a9ba2-8ba2-4364-85eb-5dd06d9c9331", + "role": "user" + }, + "locale": "", + "text": "What delivery mode would you like to use?", + "speak": "What delivery mode would you like to use?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "normal", + "value": "normal" + }, + { + "type": "imBack", + "title": "expectReplies", + "value": "expectReplies" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "67d08fd0-4f58-11eb-80df-41840e87027c", + "id": "689ae4b0-4f58-11eb-80df-41840e87027c", + "localTimestamp": "2021-01-05T10:17:49-03:00", + "timestamp": "2021-01-05T13:17:49.563Z" + }, + { + "channelData": { + "clientActivityID": "1609852670737zdf39h87ze9", + "clientTimestamp": "2021-01-05T13:17:50.737Z" + }, + "text": "expectReplies", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "c13a9ba2-8ba2-4364-85eb-5dd06d9c9331", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-05T13:17:50.760Z", + "entities": [ + { + "requiresBotState": true, + "supportsListening": true, + "supportsTts": true, + "type": "ClientCapabilities" + } + ], + "conversation": { + "id": "67c4f710-4f58-11eb-aad9-1dbdd1591611|livechat" + }, + "id": "69518a80-4f58-11eb-80df-41840e87027c", + "localTimestamp": "2021-01-05T10:17:50-03:00", + "recipient": { + "id": "56b5e650-4f58-11eb-aad9-1dbdd1591611", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://60ecb8345374.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://60ecb8345374.ngrok.io", + "channelId": "emulator", + "from": { + "id": "56b5e650-4f58-11eb-aad9-1dbdd1591611", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "67c4f710-4f58-11eb-aad9-1dbdd1591611|livechat" + }, + "recipient": { + "id": "c13a9ba2-8ba2-4364-85eb-5dd06d9c9331", + "role": "user" + }, + "locale": "", + "text": "What skill would you like to call?", + "speak": "What skill would you like to call?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "EchoSkillBotDotNet", + "value": "EchoSkillBotDotNet" + }, + { + "type": "imBack", + "title": "EchoSkillBotDotNet21", + "value": "EchoSkillBotDotNet21" + }, + { + "type": "imBack", + "title": "EchoSkillBotDotNetV3", + "value": "EchoSkillBotDotNetV3" + }, + { + "type": "imBack", + "title": "EchoSkillBotJS", + "value": "EchoSkillBotJS" + }, + { + "type": "imBack", + "title": "EchoSkillBotJSV3", + "value": "EchoSkillBotJSV3" + }, + { + "type": "imBack", + "title": "EchoSkillBotPython", + "value": "EchoSkillBotPython" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "69518a80-4f58-11eb-80df-41840e87027c", + "id": "69f93c30-4f58-11eb-80df-41840e87027c", + "localTimestamp": "2021-01-05T10:17:51-03:00", + "timestamp": "2021-01-05T13:17:51.859Z" + }, + { + "channelData": { + "clientActivityID": "16098526780751zl7loernbj", + "clientTimestamp": "2021-01-05T13:17:58.075Z" + }, + "text": "EchoSkillBotDotNet", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "c13a9ba2-8ba2-4364-85eb-5dd06d9c9331", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-05T13:17:58.102Z", + "conversation": { + "id": "67c4f710-4f58-11eb-aad9-1dbdd1591611|livechat" + }, + "id": "6db1d760-4f58-11eb-80df-41840e87027c", + "localTimestamp": "2021-01-05T10:17:58-03:00", + "recipient": { + "id": "56b5e650-4f58-11eb-aad9-1dbdd1591611", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://60ecb8345374.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://60ecb8345374.ngrok.io", + "channelId": "emulator", + "from": { + "id": "56b5e650-4f58-11eb-aad9-1dbdd1591611", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "67c4f710-4f58-11eb-aad9-1dbdd1591611|livechat" + }, + "recipient": { + "id": "c13a9ba2-8ba2-4364-85eb-5dd06d9c9331", + "role": "user" + }, + "locale": "", + "text": "Type anything to send to the skill.", + "inputHint": "expectingInput", + "attachments": [], + "entities": [], + "replyToId": "6db1d760-4f58-11eb-80df-41840e87027c", + "id": "6e4ae310-4f58-11eb-80df-41840e87027c", + "localTimestamp": "2021-01-05T10:17:59-03:00", + "timestamp": "2021-01-05T13:17:59.105Z" + }, + { + "channelData": { + "clientActivityID": "1609852686824zhcy1hqt9dd", + "clientTimestamp": "2021-01-05T13:18:06.824Z" + }, + "text": "Hello", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "c13a9ba2-8ba2-4364-85eb-5dd06d9c9331", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-05T13:18:06.848Z", + "conversation": { + "id": "67c4f710-4f58-11eb-aad9-1dbdd1591611|livechat" + }, + "id": "72e86000-4f58-11eb-80df-41840e87027c", + "localTimestamp": "2021-01-05T10:18:06-03:00", + "recipient": { + "id": "56b5e650-4f58-11eb-aad9-1dbdd1591611", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://60ecb8345374.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://60ecb8345374.ngrok.io", + "channelId": "emulator", + "from": { + "id": "56b5e650-4f58-11eb-aad9-1dbdd1591611", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "67c4f710-4f58-11eb-aad9-1dbdd1591611|livechat" + }, + "recipient": { + "id": "c13a9ba2-8ba2-4364-85eb-5dd06d9c9331", + "role": "user" + }, + "locale": "", + "text": "Echo: Hello", + "inputHint": "acceptingInput", + "attachments": [], + "entities": [], + "replyToId": "72e86000-4f58-11eb-80df-41840e87027c", + "id": "740cbbc0-4f58-11eb-80df-41840e87027c", + "localTimestamp": "2021-01-05T10:18:08-03:00", + "timestamp": "2021-01-05T13:18:08.764Z" + }, + { + "type": "message", + "serviceUrl": "https://60ecb8345374.ngrok.io", + "channelId": "emulator", + "from": { + "id": "56b5e650-4f58-11eb-aad9-1dbdd1591611", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "67c4f710-4f58-11eb-aad9-1dbdd1591611|livechat" + }, + "recipient": { + "id": "c13a9ba2-8ba2-4364-85eb-5dd06d9c9331", + "role": "user" + }, + "locale": "", + "text": "Say \"end\" or \"stop\" and I'll end the conversation and back to the parent.", + "inputHint": "acceptingInput", + "attachments": [], + "entities": [], + "replyToId": "72e86000-4f58-11eb-80df-41840e87027c", + "id": "74411230-4f58-11eb-80df-41840e87027c", + "localTimestamp": "2021-01-05T10:18:09-03:00", + "timestamp": "2021-01-05T13:18:09.107Z" + }, + { + "channelData": { + "clientActivityID": "16098526945962f14ibp83pt", + "clientTimestamp": "2021-01-05T13:18:14.596Z" + }, + "text": "end", + "textFormat": "plain", + "type": "message", + "channelId": "emulator", + "from": { + "id": "c13a9ba2-8ba2-4364-85eb-5dd06d9c9331", + "name": "User", + "role": "user" + }, + "locale": "", + "timestamp": "2021-01-05T13:18:14.627Z", + "conversation": { + "id": "67c4f710-4f58-11eb-aad9-1dbdd1591611|livechat" + }, + "id": "778b5b30-4f58-11eb-80df-41840e87027c", + "localTimestamp": "2021-01-05T10:18:14-03:00", + "recipient": { + "id": "56b5e650-4f58-11eb-aad9-1dbdd1591611", + "name": "Bot", + "role": "bot" + }, + "serviceUrl": "https://60ecb8345374.ngrok.io" + }, + { + "type": "message", + "serviceUrl": "https://60ecb8345374.ngrok.io", + "channelId": "emulator", + "from": { + "id": "56b5e650-4f58-11eb-aad9-1dbdd1591611", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "67c4f710-4f58-11eb-aad9-1dbdd1591611|livechat" + }, + "recipient": { + "id": "c13a9ba2-8ba2-4364-85eb-5dd06d9c9331", + "role": "user" + }, + "locale": "", + "text": "Ending conversation from the skill...", + "inputHint": "acceptingInput", + "attachments": [], + "entities": [], + "replyToId": "778b5b30-4f58-11eb-80df-41840e87027c", + "id": "7839c3a0-4f58-11eb-80df-41840e87027c", + "localTimestamp": "2021-01-05T10:18:15-03:00", + "timestamp": "2021-01-05T13:18:15.770Z" + }, + { + "type": "message", + "serviceUrl": "https://60ecb8345374.ngrok.io", + "channelId": "emulator", + "from": { + "id": "56b5e650-4f58-11eb-aad9-1dbdd1591611", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "67c4f710-4f58-11eb-aad9-1dbdd1591611|livechat" + }, + "recipient": { + "id": "c13a9ba2-8ba2-4364-85eb-5dd06d9c9331", + "role": "user" + }, + "locale": "", + "text": "Received endOfConversation.\n\nCode: completedSuccessfully", + "inputHint": "acceptingInput", + "attachments": [], + "entities": [], + "replyToId": "778b5b30-4f58-11eb-80df-41840e87027c", + "id": "78684db0-4f58-11eb-80df-41840e87027c", + "localTimestamp": "2021-01-05T10:18:16-03:00", + "timestamp": "2021-01-05T13:18:16.075Z" + }, + { + "type": "message", + "serviceUrl": "https://60ecb8345374.ngrok.io", + "channelId": "emulator", + "from": { + "id": "56b5e650-4f58-11eb-aad9-1dbdd1591611", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "67c4f710-4f58-11eb-aad9-1dbdd1591611|livechat" + }, + "recipient": { + "id": "c13a9ba2-8ba2-4364-85eb-5dd06d9c9331", + "role": "user" + }, + "locale": "", + "text": "Back in the host bot.", + "inputHint": "acceptingInput", + "attachments": [], + "entities": [], + "replyToId": "778b5b30-4f58-11eb-80df-41840e87027c", + "id": "7898faa0-4f58-11eb-80df-41840e87027c", + "localTimestamp": "2021-01-05T10:18:16-03:00", + "timestamp": "2021-01-05T13:18:16.394Z" + }, + { + "type": "message", + "serviceUrl": "https://60ecb8345374.ngrok.io", + "channelId": "emulator", + "from": { + "id": "56b5e650-4f58-11eb-aad9-1dbdd1591611", + "name": "Bot", + "role": "bot" + }, + "conversation": { + "id": "67c4f710-4f58-11eb-aad9-1dbdd1591611|livechat" + }, + "recipient": { + "id": "c13a9ba2-8ba2-4364-85eb-5dd06d9c9331", + "role": "user" + }, + "locale": "", + "text": "What delivery mode would you like to use?", + "speak": "What delivery mode would you like to use?", + "inputHint": "expectingInput", + "suggestedActions": { + "actions": [ + { + "type": "imBack", + "title": "normal", + "value": "normal" + }, + { + "type": "imBack", + "title": "expectReplies", + "value": "expectReplies" + } + ] + }, + "attachments": [], + "entities": [], + "replyToId": "778b5b30-4f58-11eb-80df-41840e87027c", + "id": "78c317e0-4f58-11eb-80df-41840e87027c", + "localTimestamp": "2021-01-05T10:18:16-03:00", + "timestamp": "2021-01-05T13:18:16.670Z" + } +] diff --git a/tests/functional/Tests/SkillFunctionalTests/SkillFunctionalTests.csproj b/tests/functional/Tests/SkillFunctionalTests/SkillFunctionalTests.csproj new file mode 100644 index 0000000000..c9208c26ac --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/SkillFunctionalTests.csproj @@ -0,0 +1,60 @@ + + + + netcoreapp3.1 + false + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + Always + + + + + + Always + + + + + + Always + + + + + + Always + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/functional/Tests/SkillFunctionalTests/Sso/SsoTests.cs b/tests/functional/Tests/SkillFunctionalTests/Sso/SsoTests.cs new file mode 100644 index 0000000000..151306b8b4 --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/Sso/SsoTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Microsoft.Bot.Connector; +using Microsoft.Bot.Schema; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using SkillFunctionalTests.Common; +using TranscriptTestRunner; +using TranscriptTestRunner.XUnit; +using Xunit; +using Xunit.Abstractions; + +namespace SkillFunctionalTests.Sso +{ + public class SsoTests : ScriptTestBase + { + private readonly string _testScriptsFolder = Directory.GetCurrentDirectory() + @"/Sso/TestScripts"; + + public SsoTests(ITestOutputHelper output) + : base(output) + { + } + + public static IEnumerable TestCases() + { + var channelIds = new List { Channels.Directline }; + var deliverModes = new List + { + DeliveryModes.Normal, + DeliveryModes.ExpectReplies, + }; + + var hostBots = new List + { + HostBot.WaterfallHostBotDotNet, + HostBot.WaterfallHostBotJS, + HostBot.WaterfallHostBotPython + }; + + var targetSkills = new List + { + SkillBotNames.WaterfallSkillBotDotNet, + SkillBotNames.WaterfallSkillBotJS, + SkillBotNames.WaterfallSkillBotPython + }; + + var scripts = new List + { + "Sso.json", + }; + + var testCaseBuilder = new TestCaseBuilder(); + + var testCases = testCaseBuilder.BuildTestCases(channelIds, deliverModes, hostBots, targetSkills, scripts); + foreach (var testCase in testCases) + { + yield return testCase; + } + } + + [Theory] + [MemberData(nameof(TestCases))] + public Task RunTestCases(TestCaseDataObject testData) + { + var testCase = testData.GetObject(); + Logger.LogInformation(JsonConvert.SerializeObject(testCase, Formatting.Indented)); + + // TODO: Implement tests and scripts + //var runner = new XUnitTestRunner(new TestClientFactory(testCase.ChannelId).GetTestClient(), Logger); + //await runner.RunTestAsync(Path.Combine(_testScriptsFolder, testCase.Script)); + + // TODO: remove this line once we implement the test and we change the method to public async task + return Task.CompletedTask; + } + } +} diff --git a/tests/functional/Tests/SkillFunctionalTests/appsettings.json b/tests/functional/Tests/SkillFunctionalTests/appsettings.json new file mode 100644 index 0000000000..447bc08b9a --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/appsettings.json @@ -0,0 +1,45 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "Microsoft": "Warning" + } + }, + + "TestRequestTimeout": 180, + + "HostBotClientOptions": { + "SimpleHostBotComposerDotNet": { + "BotId": "bffnsimplehostbotcomposerdotnet", + "DirectLineSecret": "" + }, + "SimpleHostBotDotNet": { + "BotId": "bffnsimplehostbotdotnet", + "DirectLineSecret": "" + }, + "SimpleHostBotDotNet21": { + "BotId": "bffnsimplehostbotdotnet21", + "DirectLineSecret": "" + }, + "SimpleHostBotJS": { + "BotId": "bffnsimplehostbotjs", + "DirectLineSecret": "" + }, + "SimpleHostBotPython": { + "BotId": "bffnsimplehostbotpython", + "DirectLineSecret": "" + }, + "WaterfallHostBotDotNet": { + "BotId": "bffnwaterfallhostbotdotnet", + "DirectLineSecret": "" + }, + "WaterfallHostBotJS": { + "BotId": "bffnwaterfallhostbotjs", + "DirectLineSecret": "" + }, + "WaterfallHostBotPython": { + "BotId": "bffnwaterfallhostbotpython", + "DirectLineSecret": "" + } + } +} diff --git a/tests/functional/Tests/SkillFunctionalTests/xunit.runner.json b/tests/functional/Tests/SkillFunctionalTests/xunit.runner.json new file mode 100644 index 0000000000..285c70b81a --- /dev/null +++ b/tests/functional/Tests/SkillFunctionalTests/xunit.runner.json @@ -0,0 +1,3 @@ +{ + "parallelizeAssembly": true +} \ No newline at end of file diff --git a/tests/functional/Tests/TranscriptTestRunnerTests/TestRunnerTests.cs b/tests/functional/Tests/TranscriptTestRunnerTests/TestRunnerTests.cs new file mode 100644 index 0000000000..2bbdd79c9c --- /dev/null +++ b/tests/functional/Tests/TranscriptTestRunnerTests/TestRunnerTests.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Moq; +using TranscriptTestRunner; +using Xunit; + +namespace TranscriptTestRunnerTests +{ + public class TestRunnerTests + { + [Fact] + public void CreateTestRunnerTest() + { + var testClientMock = new Mock(); + var testRunner = new TestRunner(testClientMock.Object); + + Assert.NotNull(testRunner); + } + } +} diff --git a/tests/functional/Tests/TranscriptTestRunnerTests/TranscriptTestRunnerTests.csproj b/tests/functional/Tests/TranscriptTestRunnerTests/TranscriptTestRunnerTests.csproj new file mode 100644 index 0000000000..14d32b4f57 --- /dev/null +++ b/tests/functional/Tests/TranscriptTestRunnerTests/TranscriptTestRunnerTests.csproj @@ -0,0 +1,21 @@ + + + + netcoreapp3.1 + + false + + + + + + + + + + + + + + + diff --git a/tests/functional/build/templates/template-app-insights-resources.json b/tests/functional/build/templates/template-app-insights-resources.json new file mode 100644 index 0000000000..640b82e924 --- /dev/null +++ b/tests/functional/build/templates/template-app-insights-resources.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appInsightsName": { + "type": "string", + "defaultValue": "bffnappinsights", + "metadata": { + "description": "The name of the Application Insights instance." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Specifies the Azure location where the key vault should be created." + } + } + }, + "resources": [ + { + "type": "Microsoft.Insights/components", + "apiVersion": "2020-02-02-preview", + "name": "[parameters('appInsightsName')]", + "location": "[parameters('location')]", + "kind": "web", + "properties": { + "Application_Type":"web", + "IngestionMode": "ApplicationInsights", + "publicNetworkAccessForIngestion": "Enabled", + "publicNetworkAccessForQuery": "Enabled" + } + } + ] +} diff --git a/tests/functional/build/templates/template-bot-resources.json b/tests/functional/build/templates/template-bot-resources.json new file mode 100644 index 0000000000..de5faada36 --- /dev/null +++ b/tests/functional/build/templates/template-bot-resources.json @@ -0,0 +1,210 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." + } + }, + "botName": { + "type": "string" + }, + "botLocation": { + "type": "string" + }, + "appInsightsName": { + "type": "string", + "defaultValue": "" + }, + "appServicePlanName": { + "type": "string" + }, + "appServicePlanResourceGroup": { + "type": "string" + }, + "botSku": { + "type": "string", + "defaultValue": "F0", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + } + }, + "variables": { + "siteHost": "[concat(parameters('botName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('botName'))]" + }, + "resources": [ + { + "type": "Microsoft.Web/sites", + "apiVersion": "2020-09-01", + "name": "[parameters('botName')]", + "location": "[parameters('botLocation')]", + "kind": "app", + "properties": { + "enabled": true, + "hostNameSslStates": [ + { + "name": "[concat(parameters('botName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('botName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[concat('/subscriptions/', subscription().id,'/resourcegroups/', parameters('appServicePlanResourceGroup'), '/providers/Microsoft.Web/serverfarms/', parameters('appServicePlanName'))]", + "reserved": true, + "hyperV": false, + "siteConfig": { + "appSettings": [ + { + "name": "WEBSITE_NODE_DEFAULT_VERSION", + "value": "10.14.1" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + }, + { + "name": "APPINSIGHTS_INSTRUMENTATIONKEY", + "value": "[if(empty(parameters('appInsightsName')), '', reference(resourceId(parameters('appServicePlanResourceGroup'),'Microsoft.Insights/components', parameters('appInsightsName')), '2015-05-01', 'Full').properties.InstrumentationKey)]" + }, + { + "name": "APPLICATIONINSIGHTS_CONNECTION_STRING", + "value": "[if(empty(parameters('appInsightsName')), '', reference(resourceId(parameters('appServicePlanResourceGroup'),'Microsoft.Insights/components', parameters('appInsightsName')), '2015-05-01', 'Full').properties.ConnectionString)]" + }, + { + "name": "ApplicationInsightsAgent_EXTENSION_VERSION", + "value": "~2" + } + ], + "webSocketsEnabled": true, + "scmType": "None", + "use32BitWorkerProcess": true, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ], + "supportCredentials": false + }, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "phpVersion": "5.6", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": true, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]" + }, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "hostNamesDisabled": false, + "clientCertEnabled": false, + "httpsOnly": false, + "redundancyMode": "None" + } + }, + { + "type": "Microsoft.Web/sites/hostNameBindings", + "apiVersion": "2020-09-01", + "name": "[concat(parameters('botName'), '/', parameters('botName'), '.azurewebsites.net')]", + "location": "[parameters('botLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites', parameters('botName'))]" + ], + "properties": { + "siteName": "[parameters('botName')]", + "hostNameType": "Verified" + } + }, + { + "type": "Microsoft.BotService/botServices", + "apiVersion": "2020-06-02", + "name": "[parameters('botName')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites', parameters('botName'))]" + ], + "properties": { + "name": "[parameters('botName')]", + "displayName": "[parameters('botName')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": "", + "developerAppInsightKey": "", + "publishingCredentials": null, + "storageResourceId": null + } + } + ] +} \ No newline at end of file diff --git a/tests/functional/build/templates/template-cosmosdb-resources.json b/tests/functional/build/templates/template-cosmosdb-resources.json new file mode 100644 index 0000000000..152fb99a29 --- /dev/null +++ b/tests/functional/build/templates/template-cosmosdb-resources.json @@ -0,0 +1,77 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "accountName": { + "type": "string", + "metadata": { + "description": "Cosmos DB account name" + } + }, + "databaseName": { + "type": "string", + "metadata": { + "description": "The name for the Core (SQL) database" + } + } + }, + "variables": { + "accountName": "[toLower(parameters('accountName'))]" + }, + "resources": [ + { + "apiVersion": "2020-04-01", + "kind": "GlobalDocumentDB", + "type": "Microsoft.DocumentDB/databaseAccounts", + "name": "[parameters('accountName')]", + "location": "westus", + "properties": { + "databaseAccountOfferType": "Standard", + "locations": [ + { + "id": "[concat(variables('accountName'), '-westus')]", + "failoverPriority": 0, + "locationName": "West US" + } + ], + "backupPolicy": { + "type": "Periodic", + "periodicModeProperties": { + "backupIntervalInMinutes": 240, + "backupRetentionIntervalInHours": 8 + } + }, + "isVirtualNetworkFilterEnabled": false, + "virtualNetworkRules": [], + "ipRules": [], + "dependsOn": [], + "enableMultipleWriteLocations": false, + "capabilities": [], + "enableFreeTier": false + }, + "tags": { + "defaultExperience": "Core (SQL)", + "hidden-cosmos-mmspecial": "", + "CosmosAccountType": "Non-Production" + } + }, + { + "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases", + "apiVersion": "2020-03-01", + "name": "[concat(variables('accountName'), '/', parameters('databaseName'))]", + "dependsOn": [ + "[resourceId('Microsoft.DocumentDB/databaseAccounts', variables('accountName'))]" + ], + "properties": { + "mode": "Complete", + "resource": { + "id": "[parameters('databaseName')]" + }, + "options": { + "throughput": "400" + } + } + } + ], + "outputs": {} +} diff --git a/tests/functional/build/templates/template-key-vault-resources.json b/tests/functional/build/templates/template-key-vault-resources.json new file mode 100644 index 0000000000..3c3849c5f3 --- /dev/null +++ b/tests/functional/build/templates/template-key-vault-resources.json @@ -0,0 +1,129 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Specifies the name of the key vault." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Specifies the Azure location where the key vault should be created." + } + }, + "enabledForDeployment": { + "type": "bool", + "defaultValue": false, + "allowedValues": [ + true, + false + ], + "metadata": { + "description": "Specifies whether Azure Virtual Machines are permitted to retrieve certificates stored as secrets from the key vault." + } + }, + "enabledForDiskEncryption": { + "type": "bool", + "defaultValue": false, + "allowedValues": [ + true, + false + ], + "metadata": { + "description": "Specifies whether Azure Disk Encryption is permitted to retrieve secrets from the vault and unwrap keys." + } + }, + "enabledForTemplateDeployment": { + "type": "bool", + "defaultValue": true, + "allowedValues": [ + true, + false + ], + "metadata": { + "description": "Specifies whether Azure Resource Manager is permitted to retrieve secrets from the key vault." + } + }, + "tenantId": { + "type": "string", + "defaultValue": "[subscription().tenantId]", + "metadata": { + "description": "Specifies the Azure Active Directory tenant ID that should be used for authenticating requests to the key vault. Get it by using Get-AzSubscription cmdlet." + } + }, + "objectId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Specifies the object ID of a user, service principal or security group in the Azure Active Directory tenant for the vault. The object ID must be unique for the list of access policies. Get it by using Get-AzADUser or Get-AzADServicePrincipal cmdlets." + } + }, + "keysPermissions": { + "type": "array", + "defaultValue": [ + "list" + ], + "metadata": { + "description": "Specifies the permissions to keys in the vault. Valid values are: all, encrypt, decrypt, wrapKey, unwrapKey, sign, verify, get, list, create, update, import, delete, backup, restore, recover, and purge." + } + }, + "secretsPermissions": { + "type": "array", + "defaultValue": [ + "list", + "set", + "get" + ], + "metadata": { + "description": "Specifies the permissions to secrets in the vault. Valid values are: all, get, list, set, delete, backup, restore, recover, and purge." + } + }, + "skuName": { + "type": "string", + "defaultValue": "Standard", + "allowedValues": [ + "Standard", + "Premium" + ], + "metadata": { + "description": "Specifies whether the key vault is a standard vault or a premium vault." + } + } + }, + "resources": [ + { + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2019-09-01", + "name": "[parameters('keyVaultName')]", + "location": "[parameters('location')]", + "properties": { + "enabledForDeployment": "[parameters('enabledForDeployment')]", + "enabledForDiskEncryption": "[parameters('enabledForDiskEncryption')]", + "enabledForTemplateDeployment": "[parameters('enabledForTemplateDeployment')]", + "tenantId": "[parameters('tenantId')]", + "accessPolicies": [ + { + "objectId": "[parameters('objectId')]", + "tenantId": "[parameters('tenantId')]", + "permissions": { + "keys": "[parameters('keysPermissions')]", + "secrets": "[parameters('secretsPermissions')]" + } + } + ], + "sku": { + "name": "[parameters('skuName')]", + "family": "A" + }, + "networkAcls": { + "defaultAction": "Allow", + "bypass": "AzureServices" + } + } + } + ] +} diff --git a/tests/functional/build/templates/template-python-bot-resources.json b/tests/functional/build/templates/template-python-bot-resources.json new file mode 100644 index 0000000000..d704aabd80 --- /dev/null +++ b/tests/functional/build/templates/template-python-bot-resources.json @@ -0,0 +1,195 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." + } + }, + "botName": { + "type": "string" + }, + "botLocation": { + "type": "string" + }, + "appServicePlanName": { + "type": "string" + }, + "appServicePlanResourceGroup": { + "type": "string" + }, + "botSku": { + "type": "string", + "defaultValue": "F0", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + } + }, + "variables": { + "siteHost": "[concat(parameters('botName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('botName'))]" + }, + "resources": [ + { + "type": "Microsoft.Web/sites", + "apiVersion": "2020-09-01", + "name": "[parameters('botName')]", + "location": "[parameters('botLocation')]", + "kind": "app,linux", + "properties": { + "enabled": true, + "hostNameSslStates": [ + { + "name": "[concat(parameters('botName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('botName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[concat('/subscriptions/', subscription().id,'/resourcegroups/', parameters('appServicePlanResourceGroup'), '/providers/Microsoft.Web/serverfarms/', parameters('appServicePlanName'))]", + "reserved": true, + "hyperV": false, + "siteConfig": { + "appSettings": [ + { + "name": "WEBSITE_RUN_FROM_PACKAGE", + "value": "1" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "webSocketsEnabled": true, + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "PYTHON|3.8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": true, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "alwaysOn": true, + "appCommandLine": "gunicorn --bind 0.0.0.0 --worker-class aiohttp.worker.GunicornWebWorker --timeout 600 app:APP", + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ], + "supportCredentials": false + }, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed" + }, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "hostNamesDisabled": false, + "clientCertEnabled": false, + "httpsOnly": false, + "redundancyMode": "None" + } + }, + { + "type": "Microsoft.Web/sites/hostNameBindings", + "apiVersion": "2020-09-01", + "name": "[concat(parameters('botName'), '/', parameters('botName'), '.azurewebsites.net')]", + "location": "[parameters('botLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites', parameters('botName'))]" + ], + "properties": { + "siteName": "[parameters('botName')]", + "hostNameType": "Verified" + } + }, + { + "type": "Microsoft.BotService/botServices", + "apiVersion": "2020-06-02", + "name": "[parameters('botName')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites', parameters('botName'))]" + ], + "properties": { + "name": "[parameters('botName')]", + "displayName": "[parameters('botName')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": "", + "developerAppInsightKey": "", + "publishingCredentials": null, + "storageResourceId": null + } + } + ] +} \ No newline at end of file diff --git a/tests/functional/build/templates/template-service-plan-linux-resources.json b/tests/functional/build/templates/template-service-plan-linux-resources.json new file mode 100644 index 0000000000..f52af0cec3 --- /dev/null +++ b/tests/functional/build/templates/template-service-plan-linux-resources.json @@ -0,0 +1,61 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "newAppServicePlanName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the new App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "string", + "defaultValue": "F1", + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "appServicePlanLocation": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "The location of the App Service Plan." + } + }, + "existingAppServicePlan": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the existing App Service Plan used to create the Web App for the bot." + } + } + }, + "variables": { + "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", + "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]" + }, + "resources": [ + { + "type": "Microsoft.Web/serverfarms", + "apiVersion": "2018-02-01", + "name": "[variables('servicePlanName')]", + "location": "[variables('resourcesLocation')]", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "condition": "[not(variables('useExistingAppServicePlan'))]", + "sku": { + "name": "[parameters('newAppServicePlanSku')]" + }, + "kind": "linux", + "properties": { + "name": "[variables('servicePlanName')]", + "perSiteScaling": false, + "reserved": true, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + } + ] +} diff --git a/tests/functional/build/templates/template-service-plan-windows-resources.json b/tests/functional/build/templates/template-service-plan-windows-resources.json new file mode 100644 index 0000000000..996fc7d078 --- /dev/null +++ b/tests/functional/build/templates/template-service-plan-windows-resources.json @@ -0,0 +1,61 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "newAppServicePlanName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the new App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "string", + "defaultValue": "F1", + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "appServicePlanLocation": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "The location of the App Service Plan." + } + }, + "existingAppServicePlan": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the existing App Service Plan used to create the Web App for the bot." + } + } + }, + "variables": { + "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", + "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]" + }, + "resources": [ + { + "type": "Microsoft.Web/serverfarms", + "apiVersion": "2018-02-01", + "name": "[variables('servicePlanName')]", + "location": "[variables('resourcesLocation')]", + "comments": "Create a new Windows App Service Plan if no existing App Service Plan name was passed in.", + "condition": "[not(variables('useExistingAppServicePlan'))]", + "sku": { + "name": "[parameters('newAppServicePlanSku')]" + }, + "kind": "app", + "properties": { + "name": "[variables('servicePlanName')]", + "perSiteScaling": false, + "reserved": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + } + ] +} diff --git a/tests/functional/build/yaml/cleanupResources/cleanupResources.yml b/tests/functional/build/yaml/cleanupResources/cleanupResources.yml new file mode 100644 index 0000000000..fb0fd3d206 --- /dev/null +++ b/tests/functional/build/yaml/cleanupResources/cleanupResources.yml @@ -0,0 +1,261 @@ +# +# Deletes resources from Deploy & Shared Pipelines for the Skills Functional Tests. +# + +name: $(BUILD.BUILDID) +trigger: none +pr: none + +variables: + ## Azure Resources (Define these variables in Azure) + # AzureSubscription: Service Connection Name to Manage Azure resources. + # DeployResourceGroup: (optional) Name of the Resource Group containing the bots. + # ResourceSuffix: (optional) Suffix to add to the resources' name to avoid collitions. + # SharedResourceGroup: (optional) Name of the Resource Group containing the shared resources. + + ## Internal variables + InternalAppInsightsName: "bffnappinsights$($env:RESOURCESUFFIX)" + InternalAppServicePlanDotNetName: "bffnbotsappservicedotnet$($env:RESOURCESUFFIX)" + InternalAppServicePlanJSName: "bffnbotsappservicejs$($env:RESOURCESUFFIX)" + InternalAppServicePlanPythonName: "bffnbotsappservicepython$($env:RESOURCESUFFIX)" + InternalCosmosDBName: "bffnbotstatedb$($env:RESOURCESUFFIX)" + InternalKeyVaultName: "bffnbotkeyvault$($env:RESOURCESUFFIX)" + InternalBotResourceGroupName: $[coalesce(variables['DEPLOYRESOURCEGROUP'], 'bffnbots')] + InternalSharedResourceGroupName: $[coalesce(variables['SHAREDRESOURCEGROUP'], 'bffnshared')] + +pool: + vmImage: "windows-2019" + +stages: +- stage: "Delete_DotNet_Resource_Group" + displayName: "Delete DotNet's Resource Group" + dependsOn: [] + jobs: + - job: "Delete" + displayName: "Delete steps" + steps: + - template: ../common/deleteResourceGroup.yml + parameters: + azureSubscription: $(AZURESUBSCRIPTION) + name: "$(INTERNALBOTRESOURCEGROUPNAME)-DotNet" + +- stage: "Delete_JS_Resource_Group" + displayName: "Delete JS's Resource Group" + dependsOn: [] + jobs: + - job: "Delete" + displayName: "Delete steps" + steps: + - template: ../common/deleteResourceGroup.yml + parameters: + azureSubscription: $(AZURESUBSCRIPTION) + name: "$(INTERNALBOTRESOURCEGROUPNAME)-JS" + +- stage: "Delete_Python_Resource_Group" + displayName: "Delete Python's Resource Group" + dependsOn: [] + jobs: + - job: "Delete" + displayName: "Delete steps" + steps: + - template: ../common/deleteResourceGroup.yml + parameters: + azureSubscription: $(AZURESUBSCRIPTION) + name: "$(INTERNALBOTRESOURCEGROUPNAME)-Python" + +- stage: "Delete_App_Service_Plan_DotNet" + displayName: "Delete App Service Plan (DotNet)" + dependsOn: + - Delete_DotNet_Resource_Group + jobs: + - job: "Delete" + displayName: "Delete steps" + steps: + - task: AzureCLI@2 + displayName: "Delete App Service Plan (DotNet)" + inputs: + azureSubscription: $(AZURESUBSCRIPTION) + scriptType: pscore + scriptLocation: inlineScript + inlineScript: | + Write-Host "Looking for $(INTERNALAPPSERVICEPLANDOTNETNAME)..." + $exists = az appservice plan show --name "$(INTERNALAPPSERVICEPLANDOTNETNAME)" --resource-group "$(INTERNALSHAREDRESOURCEGROUPNAME)" + if ($exists) { + Write-Host "Deleting $(INTERNALAPPSERVICEPLANDOTNETNAME)..." + az appservice plan delete --name "$(INTERNALAPPSERVICEPLANDOTNETNAME)" --resource-group "$(INTERNALSHAREDRESOURCEGROUPNAME)" --yes + } else { + Write-Host "No pre-existing $(INTERNALAPPSERVICEPLANDOTNETNAME) resource found." + } + +- stage: "Delete_App_Service_Plan_JS" + displayName: "Delete App Service Plan (JS)" + dependsOn: + - Delete_JS_Resource_Group + jobs: + - job: "Delete" + displayName: "Delete steps" + steps: + - task: AzureCLI@2 + displayName: "Delete App Service Plan (JS)" + inputs: + azureSubscription: $(AZURESUBSCRIPTION) + scriptType: pscore + scriptLocation: inlineScript + inlineScript: | + Write-Host "Looking for $(INTERNALAPPSERVICEPLANJSNAME)..." + $exists = az appservice plan show --name "$(INTERNALAPPSERVICEPLANJSNAME)" --resource-group "$(INTERNALSHAREDRESOURCEGROUPNAME)" + if ($exists) { + Write-Host "Deleting $(INTERNALAPPSERVICEPLANJSNAME)..." + az appservice plan delete --name "$(INTERNALAPPSERVICEPLANJSNAME)" --resource-group "$(INTERNALSHAREDRESOURCEGROUPNAME)" --yes + } else { + Write-Host "No pre-existing $(INTERNALAPPSERVICEPLANJSNAME) resource found." + } + +- stage: "Delete_App_Service_Plan_Python" + displayName: "Delete App Service Plan (Python)" + dependsOn: + - Delete_Python_Resource_Group + jobs: + - job: "Delete" + displayName: "Delete steps" + steps: + - task: AzureCLI@2 + displayName: "Delete App Service Plan (Python)" + inputs: + azureSubscription: $(AZURESUBSCRIPTION) + scriptType: pscore + scriptLocation: inlineScript + inlineScript: | + Write-Host "Looking for $(INTERNALAPPSERVICEPLANPYTHONNAME)..." + $exists = az appservice plan show --name "$(INTERNALAPPSERVICEPLANPYTHONNAME)" --resource-group "$(INTERNALSHAREDRESOURCEGROUPNAME)-linux" + if ($exists) { + Write-Host "Deleting $(INTERNALAPPSERVICEPLANPYTHONNAME)..." + az appservice plan delete --name "$(INTERNALAPPSERVICEPLANPYTHONNAME)" --resource-group "$(INTERNALSHAREDRESOURCEGROUPNAME)-linux" --yes + } else { + Write-Host "No pre-existing $(INTERNALAPPSERVICEPLANPYTHONNAME) resource found." + } + +- stage: "Delete_App_Registrations" + displayName: "Delete App Registrations" + dependsOn: [] + jobs: + - job: "Delete" + displayName: "Delete steps" + steps: + - template: deleteAppRegistrations.yml + parameters: + azureSubscription: "$(AZURESUBSCRIPTION)" + keyVault: "$(INTERNALKEYVAULTNAME)" + resourceGroup: "$(INTERNALSHAREDRESOURCEGROUPNAME)" + +- stage: "Delete_Key_Vault" + displayName: "Delete Key Vault" + dependsOn: + - Delete_App_Registrations + jobs: + - job: "Delete" + displayName: "Delete steps" + steps: + - task: AzureCLI@2 + displayName: "Delete Key Vault" + inputs: + azureSubscription: $(AZURESUBSCRIPTION) + scriptType: pscore + scriptLocation: inlineScript + inlineScript: | + Write-Host "Looking for $(INTERNALKEYVAULTNAME)..." + $exists = az keyvault list --resource-group "$(INTERNALSHAREDRESOURCEGROUPNAME)" --query "[?name == '$(INTERNALKEYVAULTNAME)']" | ConvertFrom-Json + if ($exists) { + Write-Host "Deleting $(INTERNALKEYVAULTNAME)..." + az keyvault delete --name "$(INTERNALKEYVAULTNAME)" --resource-group "$(INTERNALSHAREDRESOURCEGROUPNAME)" + az keyvault purge --name "$(INTERNALKEYVAULTNAME)" + } else { + Write-Host "No pre-existing $(INTERNALKEYVAULTNAME) resource found." + } + +- stage: "Delete_App_Insights" + displayName: "Delete App Insights" + dependsOn: [] + jobs: + - job: "Delete" + displayName: "Delete steps" + steps: + - task: AzureCLI@2 + displayName: "Delete App Insights" + inputs: + azureSubscription: $(AZURESUBSCRIPTION) + scriptType: pscore + scriptLocation: inlineScript + inlineScript: | + az config set extension.use_dynamic_install=yes_without_prompt + az feature register --name AIWorkspacePreview --namespace microsoft.insights + Write-Host "Looking for $(INTERNALAPPINSIGHTSNAME)..." + if ((az group exists -n "$(INTERNALSHAREDRESOURCEGROUPNAME)") -eq "true") { + $exists = az monitor app-insights component show --app "$(INTERNALAPPINSIGHTSNAME)" --resource-group "$(INTERNALSHAREDRESOURCEGROUPNAME)" + if ($exists) { + Write-Host "Deleting $(INTERNALAPPINSIGHTSNAME)..." + az monitor app-insights component delete --app "$(INTERNALAPPINSIGHTSNAME)" --resource-group "$(INTERNALSHAREDRESOURCEGROUPNAME)" + } else { + Write-Host "No pre-existing $(INTERNALAPPINSIGHTSNAME) resource found." + } + } else { + Write-Host "No pre-existing $(INTERNALSHAREDRESOURCEGROUPNAME) group found." + } + +- stage: "Delete_CosmosDB" + displayName: "Delete CosmosDB" + dependsOn: [] + jobs: + - job: "Delete" + displayName: "Delete steps" + steps: + - task: AzureCLI@2 + displayName: "Delete CosmosDB" + inputs: + azureSubscription: $(AZURESUBSCRIPTION) + scriptType: pscore + scriptLocation: inlineScript + inlineScript: | + Write-Host "Looking for $(INTERNALCOSMOSDBNAME)..." + $exists = az cosmosdb check-name-exists --name "$(INTERNALCOSMOSDBNAME)" + if ($exists -eq $true) { + Write-Host "Deleting $(INTERNALCOSMOSDBNAME)..." + az cosmosdb delete --name "$(INTERNALCOSMOSDBNAME)" --resource-group "$(INTERNALSHAREDRESOURCEGROUPNAME)" --yes + } else { + Write-Host "No pre-existing $(INTERNALCOSMOSDBNAME) resource found." + } + +- stage: "Delete_Resource_Group_Windows" + displayName: "Delete Resource Group (Windows)" + dependsOn: + - Delete_DotNet_Resource_Group + - Delete_JS_Resource_Group + - Delete_App_Service_Plan_DotNet + - Delete_App_Service_Plan_JS + - Delete_App_Insights + - Delete_CosmosDB + - Delete_App_Registrations + - Delete_Key_Vault + jobs: + - job: "Delete" + displayName: "Delete steps" + steps: + - template: ../common/deleteResourceGroup.yml + parameters: + azureSubscription: $(AZURESUBSCRIPTION) + name: "$(INTERNALSHAREDRESOURCEGROUPNAME)" + +- stage: "Delete_Resource_Group_Linux" + displayName: "Delete Resource Group (Linux)" + dependsOn: + - Delete_Python_Resource_Group + - Delete_App_Service_Plan_Python + jobs: + - job: "Delete" + displayName: "Delete Resource Group (Linux)" + steps: + - template: ../common/deleteResourceGroup.yml + parameters: + azureSubscription: $(AZURESUBSCRIPTION) + name: "$(INTERNALSHAREDRESOURCEGROUPNAME)-linux" + diff --git a/tests/functional/build/yaml/cleanupResources/deleteAppRegistrations.yml b/tests/functional/build/yaml/cleanupResources/deleteAppRegistrations.yml new file mode 100644 index 0000000000..6236117a2d --- /dev/null +++ b/tests/functional/build/yaml/cleanupResources/deleteAppRegistrations.yml @@ -0,0 +1,62 @@ +parameters: + - name: azureSubscription + displayName: Azure Service Connection + type: string + + - name: keyVault + displayName: Key Vault name + type: string + + - name: resourceGroup + displayName: Resource Group + type: string + +steps: + - task: AzureCLI@2 + displayName: "Delete Bots App Registrations" + inputs: + azureSubscription: "${{ parameters.azureSubscription }}" + addSpnToEnvironment: true + scriptType: pscore + scriptLocation: inlineScript + inlineScript: | + # Using Microsoft Graph REST API to create App Registrations (https://docs.microsoft.com/en-us/graph/api/application-post-applications) instead of Azure CLI due to Azure Active Directory Graph API has been deprecated and still in a migration process to Microsoft Graph API, more information can be found in this link (https://github.com/Azure/azure-cli/issues/12946). + + function GetToken() { + # Get Token + + $body = @{ + grant_type = "client_credentials"; + scope = "https://graph.microsoft.com/.default"; + client_id = $env:SERVICEPRINCIPALID; + client_secret = $env:SERVICEPRINCIPALKEY; + } + + Invoke-WebRequest -Uri "https://login.microsoftonline.com/$($env:TENANTID)/oauth2/v2.0/token" -Method "POST" -Body $body | ConvertFrom-Json + } + + function DeleteAppRegistration($token, $objectId) { + # Delete App Registration + + $headers = @{ + Authorization = "Bearer " + $token.access_token + } + + Invoke-WebRequest -Uri "https://graph.microsoft.com/v1.0/applications/$objectId" -Method "DELETE" -Headers $headers | ConvertFrom-Json + } + + Write-Host "Looking for ${{ parameters.keyVault }}..." + $exists = az keyvault list --resource-group "${{ parameters.resourceGroup }}" --query "[?name == '${{ parameters.keyVault }}']" | ConvertFrom-Json + if ($exists) { + $entries = az keyvault secret list --vault-name "${{ parameters.keyVault }}" --query "[?ends_with(name, 'AppObjectId')]" | ConvertFrom-Json + $token = GetToken + + foreach ($entry in $entries) { + $name = $entry.name -replace "AppObjectId" + Write-Host "Deleting $name secrets..." + $secretVault = az keyvault secret show --id $entry.id | ConvertFrom-Json + DeleteAppRegistration $token $secretVault.value + } + } else { + Write-Host "No pre-existing ${{ parameters.keyVault }} resource found." + } diff --git a/tests/functional/build/yaml/common/deleteResourceGroup.yml b/tests/functional/build/yaml/common/deleteResourceGroup.yml new file mode 100644 index 0000000000..8ef7f4cc2f --- /dev/null +++ b/tests/functional/build/yaml/common/deleteResourceGroup.yml @@ -0,0 +1,37 @@ +parameters: +- name: azureSubscription + displayName: Azure Service Connection + type: string + +- name: name + displayName: WebApp name + type: string + +steps: + - task: AzureCLI@2 + displayName: "Delete pre-existing Resource Group" + inputs: + azureSubscription: "${{ parameters.azureSubscription }}" + scriptType: pscore + scriptLocation: inlineScript + inlineScript: | + Write-Host "Looking for ${{ parameters.name }}..." + if ((az group exists -n "${{ parameters.name }}") -eq "true") { + Write-Host "Found pre-existing resource group ${{ parameters.name }}." + Write-Host "Starting resource cleanup..." + + $webapps = az webapp list --resource-group "${{ parameters.name }}" | ConvertFrom-Json + + foreach ($webapp in $webapps) { + Write-Host ("Deleting '" + $webapp.name + "'...") + az webapp delete --name $webapp.name --resource-group "${{ parameters.name }}" --keep-empty-plan + } + + Write-Host "Deleting ${{ parameters.name }}..." + az group delete -n "${{ parameters.name }}" --yes --no-wait + az group wait --deleted --interval 15 --timeout 600 --resource-group "${{ parameters.name }}" + + Write-Host "Pre-existing resource group ${{ parameters.name }} deleted." + } else { + Write-Host "No pre-existing resource group found." + } diff --git a/tests/functional/build/yaml/deployBotResources/common/configureOAuth.yml b/tests/functional/build/yaml/deployBotResources/common/configureOAuth.yml new file mode 100644 index 0000000000..6d33840271 --- /dev/null +++ b/tests/functional/build/yaml/deployBotResources/common/configureOAuth.yml @@ -0,0 +1,45 @@ +parameters: + - name: appId + displayName: Bot's App Registration Id + type: string + + - name: appSecret + displayName: Bot's App Registration Secret + type: string + + - name: azureSubscription + displayName: Azure Service Connection + type: string + + - name: botName + displayName: Bot Name + type: string + + - name: botGroup + displayName: Bot's Resource Group + type: string + + - name: connectionName + displayName: OAuth Connection Name + type: string + + - name: resourceSuffix + displayName: Azure resources' name suffix + type: string + +steps: + - task: AzureCLI@2 + displayName: "Configure OAuth connection" + inputs: + azureSubscription: "${{ parameters.azureSubscription }}" + scriptType: pscore + scriptLocation: inlineScript + inlineScript: | + $connectionName = if([string]::IsNullOrEmpty("${{ parameters.connectionName }}")) { "TestOAuthProvider" } Else { "${{ parameters.connectionName }}" }; + + # Create OAuth Connection. + az bot show -g "${{ parameters.botGroup }}" -n "${{ parameters.botName }}${{ parameters.resourceSuffix }}-$(BUILD.BUILDID)" + az bot authsetting create -g "${{ parameters.botGroup }}" -n "${{ parameters.botName }}${{ parameters.resourceSuffix }}-$(BUILD.BUILDID)" -c $connectionName --client-id="${{ parameters.appId }}" --client-secret="${{ parameters.appSecret }}" --service "oauth2" --provider-scope-string '""' --parameters authorizationUrl=https://webjobs.botframework.com/api/testauthprovider/authorize tokenUrl=https://webjobs.botframework.com/api/testauthprovider/token refreshUrl=https://webjobs.botframework.com/api/testauthprovider/refresh clientId="${{ parameters.appId }}" clientSecret="${{ parameters.appSecret }}" + + # Add ConnectionName to Azure AppSettings. + az webapp config appsettings set --name "${{ parameters.botName }}${{ parameters.resourceSuffix }}-$(BUILD.BUILDID)" --resource-group ${{ parameters.botGroup }} --settings "ConnectionName=$connectionName" --output none diff --git a/tests/functional/build/yaml/deployBotResources/common/createAppService.yml b/tests/functional/build/yaml/deployBotResources/common/createAppService.yml new file mode 100644 index 0000000000..7b3b6fc5ed --- /dev/null +++ b/tests/functional/build/yaml/deployBotResources/common/createAppService.yml @@ -0,0 +1,62 @@ +parameters: + - name: appId + displayName: Bot's App Registration Id + type: string + + - name: appInsight + displayName: Azure Application Insight name + type: string + default: "" + + - name: appSecret + displayName: Bot's App Registration Secret + type: string + + - name: appServicePlan + displayName: App Service Plan name + type: string + + - name: appServicePlanRG + displayName: App Service Plan Resource Group + type: string + + - name: azureSubscription + displayName: Azure Service Connection + type: string + + - name: botGroup + displayName: Bot's Resource Group + type: string + + - name: botName + displayName: Bot Name + type: string + + - name: botPricingTier + displayName: Bot Pricing Tier + type: string + + - name: templateFile + displayName: Template File Location + type: string + + - name: resourceSuffix + displayName: Azure resources' name suffix + type: string + +steps: + - task: AzureCLI@2 + displayName: "Create resources" + inputs: + azureSubscription: "${{ parameters.azureSubscription }}" + scriptType: pscore + scriptLocation: inlineScript + inlineScript: | + Set-PSDebug -Trace 1 + + $botPricingTier = if(-not ([string]::IsNullOrEmpty("${{ parameters.botPricingTier }}"))) { "botSku=${{ parameters.botPricingTier }}" }; + $appInsights = if(-not ([string]::IsNullOrEmpty("${{ parameters.appInsight }}"))) { "appInsightsName=${{ parameters.appInsight }}" } + + az deployment group create --resource-group "${{ parameters.botGroup }}" --name "${{ parameters.botName }}${{ parameters.resourceSuffix }}-$(BUILD.BUILDID)" --template-file ${{ parameters.templateFile }} --parameters $botPricingTier botLocation="westus" appId="${{ parameters.appId }}" appSecret="${{ parameters.appSecret }}" botName="${{ parameters.botName }}${{ parameters.resourceSuffix }}-$(BUILD.BUILDID)" appServicePlanName="${{ parameters.appServicePlan }}" appServicePlanResourceGroup="${{ parameters.appServicePlanRG }}" $appInsights; + + Set-PSDebug -Trace 0 diff --git a/tests/functional/build/yaml/deployBotResources/common/createDirectLine.yml b/tests/functional/build/yaml/deployBotResources/common/createDirectLine.yml new file mode 100644 index 0000000000..2fdd1aa044 --- /dev/null +++ b/tests/functional/build/yaml/deployBotResources/common/createDirectLine.yml @@ -0,0 +1,26 @@ +parameters: + - name: azureSubscription + displayName: Azure Service Connection + type: string + + - name: botGroup + displayName: Bot's Resource Group + type: string + + - name: botName + displayName: Bot Name + type: string + + - name: resourceSuffix + displayName: Azure resources' name suffix + type: string + +steps: +- task: AzureCLI@2 + displayName: "Create DirectLine Channel" + inputs: + azureSubscription: "${{ parameters.azureSubscription }}" + scriptType: pscore + scriptLocation: inlineScript + inlineScript: | + az bot directline create -n "${{ parameters.botName }}${{ parameters.resourceSuffix }}-$(BUILD.BUILDID)" -g "${{ parameters.botGroup }}" diff --git a/tests/functional/build/yaml/deployBotResources/common/deleteResources.yml b/tests/functional/build/yaml/deployBotResources/common/deleteResources.yml new file mode 100644 index 0000000000..21e9a7b408 --- /dev/null +++ b/tests/functional/build/yaml/deployBotResources/common/deleteResources.yml @@ -0,0 +1,40 @@ +parameters: + - name: azureSubscription + displayName: Azure Service Connection + type: string + + - name: resourceGroup + displayName: Resource Group + type: string + + - name: resourceName + displayName: Resource Name + type: string + + - name: resourceSuffix + displayName: Azure resources' name suffix + type: string + +steps: + - task: AzureCLI@2 + displayName: "Delete pre-existing bot resources" + inputs: + azureSubscription: "${{ parameters.azureSubscription }}" + scriptType: pscore + scriptLocation: inlineScript + inlineScript: | + Write-Host "Looking for ${{ parameters.resourceGroup }}..." + if ((az group exists -n "${{ parameters.resourceGroup }}") -eq "true") { + Write-Host "Found pre-existing resource group ${{ parameters.resourceGroup }}." + Write-Host "Starting resource cleanup..." + + Write-Host ("Deleting '" + "${{ parameters.resourceName }}${{ parameters.resourceSuffix }}-$(BUILD.BUILDID)" + "'...") + az webapp delete --name "${{ parameters.resourceName }}${{ parameters.resourceSuffix }}-$(BUILD.BUILDID)" --resource-group ${{ parameters.resourceGroup }} --keep-empty-plan + az bot delete --name "${{ parameters.resourceName }}${{ parameters.resourceSuffix }}-$(BUILD.BUILDID)" --resource-group ${{ parameters.resourceGroup }} + + Write-Host "Pre-existing bot resources for '${{ parameters.resourceName }}${{ parameters.resourceSuffix }}-$(BUILD.BUILDID)' deleted." + } else { + Write-Host "Resource group not found." + Write-Host "##vso[task.complete result=Failed;]" + return + } diff --git a/tests/functional/build/yaml/deployBotResources/common/getAppRegistration.yml b/tests/functional/build/yaml/deployBotResources/common/getAppRegistration.yml new file mode 100644 index 0000000000..039721c08a --- /dev/null +++ b/tests/functional/build/yaml/deployBotResources/common/getAppRegistration.yml @@ -0,0 +1,62 @@ +parameters: + - name: appId + displayName: Bot's App Registration Id + type: string + + - name: appSecret + displayName: Bot's App Registration Secret + type: string + + - name: azureSubscription + displayName: Azure Service Connection + type: string + + - name: botName + displayName: Bot Name + type: string + + - name: keyVault + displayName: KeyVault name + type: string + +steps: + - task: AzureCLI@2 + displayName: "Get AppId & AppSecret" + inputs: + azureSubscription: "${{ parameters.azureSubscription }}" + scriptType: pscore + failOnStandardError: true + scriptLocation: inlineScript + inlineScript: | + $secret = New-Object -TypeName psobject + $source = "" + + if ([string]::IsNullOrEmpty("${{ parameters.appId }}")) { + $source = "KeyVault ${{ parameters.keyVault }}"; + $entries = az keyvault secret list --vault-name "${{ parameters.keyVault }}" | ConvertFrom-Json | Where-Object {$_.name -like "${{ parameters.botName }}*"}; + + foreach ($entry in $entries) { + $secretVault = az keyvault secret show --id $entry.id | ConvertFrom-Json + $secret | Add-Member -MemberType NoteProperty -Name ($secretVault.name -replace "${{ parameters.botName }}", "") -Value "$($secretVault.value)" + } + } else { + $source = "Pipeline Variables" + $secret | Add-Member -MemberType NoteProperty -Name AppId -Value "${{ parameters.appId }}" + $secret | Add-Member -MemberType NoteProperty -Name AppSecret -Value "${{ parameters.appSecret }}" + } + + if ([string]::IsNullOrEmpty($secret.AppId)) { + Write-Host "##vso[task.LogIssue type=error;]AppId is Null or Empty" + Write-Host "##vso[task.complete result=Failed;]DONE" + } + + if ([string]::IsNullOrEmpty($secret.AppSecret)) { + Write-Host "##vso[task.LogIssue type=error;]AppSecret is Null or Empty" + Write-Host "##vso[task.complete result=Failed;]DONE" + } + + Write-Host "Source: $source;" + Write-Host "AppId: $($secret.AppId);" + + Write-Host "##vso[task.setvariable variable=AppId]$($secret.AppId)" + Write-Host "##vso[task.setvariable variable=AppSecret]$($secret.AppSecret)" diff --git a/tests/functional/build/yaml/deployBotResources/common/prepareResources.yml b/tests/functional/build/yaml/deployBotResources/common/prepareResources.yml new file mode 100644 index 0000000000..e8f687de44 --- /dev/null +++ b/tests/functional/build/yaml/deployBotResources/common/prepareResources.yml @@ -0,0 +1,29 @@ +parameters: + - name: azureSubscription + displayName: Azure Service Connection + type: string + + - name: resourceGroups + displayName: Resource Groups + type: object + +stages: +- ${{ each resourceGroup in parameters.resourceGroups }}: + - stage: "${{ resourceGroup.id }}" + displayName: "${{ resourceGroup.displayName }}" + dependsOn: [] # Makes this run in parallel + jobs: + - job: "Prepare" + displayName: "Prepare steps" + steps: + - template: ../../common/deleteResourceGroup.yml + parameters: + azureSubscription: "${{ parameters.azureSubscription }}" + name: "${{ resourceGroup.name }}" + + - task: AzureCLI@1 + displayName: "Create Resource Group" + inputs: + azureSubscription: "${{ parameters.azureSubscription }}" + scriptLocation: inlineScript + inlineScript: az group create --location westus --name "${{ resourceGroup.name }}" diff --git a/tests/functional/build/yaml/deployBotResources/common/tagBotBuilderVersion.yml b/tests/functional/build/yaml/deployBotResources/common/tagBotBuilderVersion.yml new file mode 100644 index 0000000000..dd3107eb0a --- /dev/null +++ b/tests/functional/build/yaml/deployBotResources/common/tagBotBuilderVersion.yml @@ -0,0 +1,15 @@ +parameters: + - name: botName + displayName: Bot Name + type: string + + - name: version + displayName: Version number + type: string + +steps: + - task: colinsalmcorner.colinsalmcorner-buildtasks.tag-build-task.tagBuildOrRelease@0 + displayName: "Tag BotBuilder Version" + inputs: + tags: "${{ parameters.botName }} Version = ${{ parameters.version }}" + continueOnError: true diff --git a/tests/functional/build/yaml/deployBotResources/deployBotResources.yml b/tests/functional/build/yaml/deployBotResources/deployBotResources.yml new file mode 100644 index 0000000000..61f31b8c58 --- /dev/null +++ b/tests/functional/build/yaml/deployBotResources/deployBotResources.yml @@ -0,0 +1,510 @@ +# +# Deploys the bot resources needed for the Skills Functional Tests. +# + +name: "$(BUILD.BUILDID)" +trigger: none +pr: none + +pool: + vmImage: "windows-2019" + +parameters: + - name: dependenciesVersionDotNetHosts + displayName: DotNet Hosts Version + type: string + default: $env:DEPENDENCIESVERSIONDOTNETHOSTS + + - name: dependenciesRegistryDotNetHosts + displayName: DotNet Hosts Registry + type: string + default: Artifacts + values: + - Artifacts + - MyGet + - NuGet + + - name: dependenciesVersionDotNetSkills + displayName: DotNet Skills Version + type: string + default: $env:DEPENDENCIESVERSIONDOTNETSKILLS + + - name: dependenciesRegistryDotNetSkills + displayName: DotNet Skills Registry + type: string + default: Artifacts + values: + - Artifacts + - MyGet + - NuGet + + - name: dependenciesVersionDotNetSkillsV3 + displayName: DotNet Skills V3 Version + type: string + default: $env:DEPENDENCIESVERSIONDOTNETSKILLSV3 + + - name: dependenciesRegistryDotNetSkillsV3 + displayName: DotNet Skills V3 Registry + type: string + default: MyGet + values: + - MyGet + - NuGet + + - name: dependenciesVersionJSHosts + displayName: JS Hosts Version + type: string + default: $env:DEPENDENCIESVERSIONJSHOSTS + + - name: dependenciesRegistryJSHosts + displayName: JS Hosts Registry + type: string + default: MyGet + values: + - MyGet + - Npm + + - name: dependenciesVersionJSSkills + displayName: JS Skills Version + type: string + default: $env:DEPENDENCIESVERSIONJSSKILLS + + - name: dependenciesRegistryJSSkills + displayName: JS Skills Registry + type: string + default: MyGet + values: + - MyGet + - Npm + + - name: dependenciesVersionJSSkillsV3 + displayName: JS Skills V3 Version + type: string + default: $env:DEPENDENCIESVERSIONJSSKILLSV3 + + - name: dependenciesRegistryJSSkillsV3 + displayName: JS Skills V3 Registry + type: string + default: MyGet + values: + - MyGet + - Npm + + - name: dependenciesVersionPythonHosts + displayName: Python Hosts Version + type: string + default: $env:DEPENDENCIESVERSIONPYTHONHOSTS + + - name: dependenciesRegistryPythonHosts + displayName: Python Hosts Registry + type: string + default: Artifacts + values: + - Artifacts + - Pypi + - Test.Pypi + + - name: dependenciesVersionPythonSkills + displayName: Python Skills Version + type: string + default: $env:DEPENDENCIESVERSIONPYTHONSKILLS + + - name: dependenciesRegistryPythonSkills + displayName: Python Skills Registry + type: string + default: Artifacts + values: + - Artifacts + - Pypi + - Test.Pypi + +variables: + BuildConfiguration: "Debug" + BuildPlatform: "AnyCPU" + ## Azure Resources (Define these variables in Azure) + # AzureSubscription: Service Connection Name to Manage Azure resources. + # AppServicePlanGroup: (optional) Name of the Resource Group where the Windows App Service Plan is located. + # AppServicePlanGroupLinux: (optional) Name of the Resource Group where the Linux App Service Plan is located. + # AppServicePlanDotNetName: (optional) Name of the DotNet App Service Plan. + # AppServicePlanJSName: (optional) Name of the JavaScript App Service Plan. + # AppServicePlanPythonName: (optional) Name of the Python App Service Plan. + # BotPricingTier: (optional) Pricing Tier for the bots, default F0. + # ResourceGroup: (optional) Name of the Resource Group where the bots will be deployed. + # ResourceSuffix: (optional) Suffix to add to the resources' name to avoid collitions. + + ## Bots Configuration (Define these variables in Azure) + # BffnEchoSkillBotComposerDotNetAppId: (optional) App Id for BffnEchoSkillBotComposerDotNet bot. + # BffnEchoSkillBotComposerDotNetAppSecret: (optional) App Secret for BffnEchoSkillBotComposerDotNet bot. + # BffnEchoSkillBotDotNet21AppId: (optional) App Id for BffnEchoSkillBotDotNet21 bot. + # BffnEchoSkillBotDotNet21AppSecret: (optional) App Secret for BffnEchoSkillBotDotNet21 bot. + # BffnEchoSkillBotDotNetAppId: (optional) App Id for BffnEchoSkillBotDotNet bot. + # BffnEchoSkillBotDotNetAppSecret: (optional) App Secret for BffnEchoSkillBotDotNet bot. + # BffnEchoSkillBotDotNetV3AppId: (optional) App Id for BffnEchoSkillBotDotNetV3 bot. + # BffnEchoSkillBotDotNetV3AppSecret: (optional) App Secret for BffnEchoSkillBotDotNetV3 bot. + # BffnEchoSkillBotJSAppId: (optional) App Id for BffnEchoSkillBotJS bot. + # BffnEchoSkillBotJSAppSecret: (optional) App Secret for BffnEchoSkillBotJS bot. + # BffnEchoSkillBotJSV3AppId: (optional) App Id for BffnEchoSkillBotJSV3 bot. + # BffnEchoSkillBotJSV3AppSecret: (optional) App Secret for BffnEchoSkillBotJSV3 bot. + # BffnEchoSkillBotPythonAppId: (optional) App Id for BffnEchoSkillBotPython bot. + # BffnEchoSkillBotPythonAppSecret: (optional) App Secret for BffnEchoSkillBotPython bot. + # BffnSimpleHostBotComposerDotNetAppId: (optional) App Id for BffnSimpleHostBotComposerDotNet bot. + # BffnSimpleHostBotComposerDotNetAppSecret: (optional) App Secret for BffnSimpleHostBotComposerDotNet bot. + # BffnSimpleHostBotDotNet21AppId: (optional) App Id for BffnSimpleHostBotDotNet21 bot. + # BffnSimpleHostBotDotNet21AppSecret: (optional) App Secret for BffnSimpleHostBotDotNet21 bot. + # BffnSimpleHostBotDotNetAppId: (optional) App Id for BffnSimpleHostBotDotNet bot. + # BffnSimpleHostBotDotNetAppSecret: (optional) App Secret for BffnSimpleHostBotDotNet bot. + # BffnSimpleHostBotJSAppId: (optional) App Id for BffnSimpleHostBotJS bot. + # BffnSimpleHostBotJSAppSecret: (optional) App Secret for BffnSimpleHostBotJS bot. + # BffnSimpleHostBotPythonAppId: (optional) App Id for BffnSimpleHostBotPython bot. + # BffnSimpleHostBotPythonAppSecret: (optional) App Secret for BffnSimpleHostBotPython bot. + # BffnWaterfallHostBotDotNetAppId: (optional) App Id for BffnWaterfallHostBotDotNet bot. + # BffnWaterfallHostBotDotNetAppSecret: (optional) App Secret for BffnWaterfallHostBotDotNet bot. + # BffnWaterfallSkillBotDotNetAppId: (optional) App Id for BffnWaterfallSkillBotDotNet bot. + # BffnWaterfallSkillBotDotNetAppSecret: (optional) App Secret for BffnWaterfallSkillBotDotNet bot. + # BffnWaterfallHostBotJSAppId: (optional) App Id for BffnWaterfallHostBotJS bot. + # BffnWaterfallHostBotJSAppSecret: (optional) App Secret for BffnWaterfallHostBotJS bot. + # BffnWaterfallSkillBotJSAppId: (optional) App Id for BffnWaterfallSkillBotJS bot. + # BffnWaterfallSkillBotJSAppSecret: (optional) App Secret for BffnWaterfallSkillBotJS bot. + # BffnWaterfallHostBotPythonAppId: (optional) App Id for BffnWaterfallHostBotPython bot. + # BffnWaterfallHostBotPythonAppSecret: (optional) App Secret for BffnWaterfallHostBotPython bot. + # BffnWaterfallSkillBotPythonAppId: (optional) App Id for BffnWaterfallSkillBotPython bot. + # BffnWaterfallSkillBotPythonAppSecret: (optional) App Secret for BffnWaterfallSkillBotPython bot. + # ConnectionName: (optional) Name for the OAuth connection to use in the skill bots. + + ## DependenciesVersion (Define these variables in Azure) Possible values are: Latest (default), Stable, or specific version numbers. + # DependenciesVersionDotNetHosts: (optional) Bot Builder dependency version to use for DotNet host bots. + # DependenciesVersionDotNetSkills: (optional) Bot Builder dependency version to use for DotNet skill bots. + # DependenciesVersionDotNetSkillsV3: (optional) Bot Builder dependency version to use for DotNet skill V3 bots. + # DependenciesVersionJSHosts: (optional) Bot Builder dependency version to use for JS host bots. + # DependenciesVersionJSSkills: (optional) Bot Builder dependency version to use for JS skill bots. + # DependenciesVersionJSSkillsV3: (optional) Bot Builder dependency version to use for JS skill V3 bots. + # DependenciesVersionPythonHosts: (optional) Bot Builder dependency version to use for Python host bots. + # DependenciesVersionPythonSkills: (optional) Bot Builder dependency version to use for Python skill bots. + + ## Internal variables + InternalAppInsightsName: 'bffnappinsights$(INTERNALRESOURCESUFFIX)' + InternalAppServicePlanWindowsResourceGroup: $[coalesce(variables['APPSERVICEPLANGROUP'], 'bffnshared')] + InternalAppServicePlanLinuxResourceGroup: $[coalesce(variables['APPSERVICEPLANGROUPLINUX'], 'bffnshared-linux')] + InternalAppServicePlanDotNetName: $[coalesce(variables['APPSERVICEPLANDOTNETNAME'], 'bffnbotsappservicedotnet$(INTERNALRESOURCESUFFIX)')] + InternalAppServicePlanJSName: $[coalesce(variables['APPSERVICEPLANJSNAME'], 'bffnbotsappservicejs$(INTERNALRESOURCESUFFIX)')] + InternalAppServicePlanPythonName: $[coalesce(variables['APPSERVICEPLANPYTHONNAME'], 'bffnbotsappservicepython$(INTERNALRESOURCESUFFIX)')] + InternalKeyVaultName: 'bffnbotkeyvault$(INTERNALRESOURCESUFFIX)' + InternalResourceGroupName: $[coalesce(variables['RESOURCEGROUP'], 'bffnbots')] + InternalResourceSuffix: $[coalesce(variables['RESOURCESUFFIX'], '')] + +stages: +# Resource Groups + - template: common/prepareResources.yml + parameters: + azureSubscription: "$(AZURESUBSCRIPTION)" + resourceGroups: + - id: "Prepare_DotNetGroup" + name: "$(INTERNALRESOURCEGROUPNAME)-DotNet" + displayName: "Prepare DotNet's Resource Group" + + - id: "Prepare_JSGroup" + name: "$(INTERNALRESOURCEGROUPNAME)-JS" + displayName: "Prepare JS's Resource Group" + + - id: "Prepare_PythonGroup" + name: "$(INTERNALRESOURCEGROUPNAME)-Python" + displayName: "Prepare Python's Resource Group" + +# DotNet + - template: dotnet/deploy.yml + parameters: + appInsight: "$(INTERNALAPPINSIGHTSNAME)" + appServicePlan: "$(INTERNALAPPSERVICEPLANDOTNETNAME)" + appServicePlanRG: "$(INTERNALAPPSERVICEPLANWINDOWSRESOURCEGROUP)" + azureSubscription: "$(AZURESUBSCRIPTION)" + botPricingTier: $env:BOTPRICINGTIER + connectionName: $env:CONNECTIONNAME + keyVault: "$(INTERNALKEYVAULTNAME)" + resourceGroup: "$(INTERNALRESOURCEGROUPNAME)-DotNet" + resourceSuffix: $(INTERNALRESOURCESUFFIX) + bots: + - name: "bffnsimplehostbotdotnet" + dependsOn: "Prepare_DotNetGroup" + type: "Host" + displayName: "DotNet Simple Host Bot" + appId: $(BFFNSIMPLEHOSTBOTDOTNETAPPID) + appSecret: $(BFFNSIMPLEHOSTBOTDOTNETAPPSECRET) + project: + directory: 'Bots/DotNet/Consumers/CodeFirst/SimpleHostBot' + name: "SimpleHostBot.csproj" + netCoreVersion: "3.1.x" + dependency: + registry: ${{ parameters.dependenciesRegistryDotNetHosts }} + version: ${{ parameters.dependenciesVersionDotNetHosts }} + + - name: "bffnsimplehostbotdotnet21" + dependsOn: "Prepare_DotNetGroup" + type: "Host" + displayName: "DotNet Simple Host Bot 2.1" + appId: $(BFFNSIMPLEHOSTBOTDOTNET21APPID) + appSecret: $(BFFNSIMPLEHOSTBOTDOTNET21APPSECRET) + project: + directory: 'Bots/DotNet/Consumers/CodeFirst/SimpleHostBot-2.1' + name: "SimpleHostBot-2.1.csproj" + netCoreVersion: "2.1.x" + dependency: + registry: ${{ parameters.dependenciesRegistryDotNetHosts }} + version: ${{ parameters.dependenciesVersionDotNetHosts }} + + - name: "bffnechoskillbotdotnet" + dependsOn: "Prepare_DotNetGroup" + type: "Skill" + displayName: "DotNet Echo Skill Bot" + appId: $(BFFNECHOSKILLBOTDOTNETAPPID) + appSecret: $(BFFNECHOSKILLBOTDOTNETAPPSECRET) + project: + directory: 'Bots/DotNet/Skills/CodeFirst/EchoSkillBot' + name: "EchoSkillBot.csproj" + netCoreVersion: "3.1.x" + dependency: + registry: ${{ parameters.dependenciesRegistryDotNetSkills }} + version: ${{ parameters.dependenciesVersionDotNetSkills }} + + - name: "bffnechoskillbotdotnet21" + dependsOn: "Prepare_DotNetGroup" + type: "Skill" + displayName: "DotNet Echo Skill Bot 2.1" + appId: $(BFFNECHOSKILLBOTDOTNET21APPID) + appSecret: $(BFFNECHOSKILLBOTDOTNET21APPSECRET) + project: + directory: 'Bots/DotNet/Skills/CodeFirst/EchoSkillBot-2.1' + name: "EchoSkillBot-2.1.csproj" + netCoreVersion: "2.1.x" + dependency: + registry: ${{ parameters.dependenciesRegistryDotNetSkills }} + version: ${{ parameters.dependenciesVersionDotNetSkills }} + + - name: "bffnechoskillbotdotnetv3" + dependsOn: "Prepare_DotNetGroup" + type: "SkillV3" + displayName: "DotNet Echo Skill Bot v3" + appId: $(BFFNECHOSKILLBOTDOTNETV3APPID) + appSecret: $(BFFNECHOSKILLBOTDOTNETV3APPSECRET) + project: + directory: 'Bots/DotNet/Skills/CodeFirst/EchoSkillBot-v3' + name: "EchoSkillBot-v3.csproj" + dependency: + registry: ${{ parameters.dependenciesRegistryDotNetSkillsV3 }} + version: ${{ parameters.dependenciesVersionDotNetSkillsV3 }} + + - name: "bffnwaterfallhostbotdotnet" + dependsOn: "Deploy_bffnsimplehostbotdotnet" + type: "Host" + displayName: "DotNet Waterfall Host Bot" + appId: $(BFFNWATERFALLHOSTBOTDOTNETAPPID) + appSecret: $(BFFNWATERFALLHOSTBOTDOTNETAPPSECRET) + project: + directory: 'Bots/DotNet/Consumers/CodeFirst/WaterfallHostBot' + name: "WaterfallHostBot.csproj" + netCoreVersion: "3.1.x" + dependency: + registry: ${{ parameters.dependenciesRegistryDotNetHosts }} + version: ${{ parameters.dependenciesVersionDotNetHosts }} + + - name: "bffnwaterfallskillbotdotnet" + dependsOn: "Deploy_bffnsimplehostbotdotnet21" + type: "Skill" + displayName: "DotNet Waterfall Skill Bot" + appId: $(BFFNWATERFALLSKILLBOTDOTNETAPPID) + appSecret: $(BFFNWATERFALLSKILLBOTDOTNETAPPSECRET) + project: + directory: 'Bots/DotNet/Skills/CodeFirst/WaterfallSkillBot' + name: "WaterfallSkillBot.csproj" + netCoreVersion: "3.1.x" + dependency: + registry: ${{ parameters.dependenciesRegistryDotNetSkills }} + version: ${{ parameters.dependenciesVersionDotNetSkills }} + +# DotNet Composer + - template: dotnet/deployComposer.yml + parameters: + appInsight: "$(INTERNALAPPINSIGHTSNAME)" + appServicePlan: "$(INTERNALAPPSERVICEPLANDOTNETNAME)" + appServicePlanRG: "$(INTERNALAPPSERVICEPLANWINDOWSRESOURCEGROUP)" + azureSubscription: "$(AZURESUBSCRIPTION)" + botPricingTier: $env:BOTPRICINGTIER + keyVault: "$(INTERNALKEYVAULTNAME)" + resourceGroup: "$(INTERNALRESOURCEGROUPNAME)-DotNet" + resourceSuffix: $(INTERNALRESOURCESUFFIX) + bots: + - name: "bffnsimplehostbotcomposerdotnet" + dependsOn: "Deploy_bffnechoskillbotdotnet" + type: "Host" + displayName: "DotNet Simple Composer Host Bot" + appId: $(BFFNSIMPLEHOSTBOTCOMPOSERDOTNETAPPID) + appSecret: $(BFFNSIMPLEHOSTBOTCOMPOSERDOTNETAPPSECRET) + project: + directory: 'Bots/DotNet/Consumers/Composer/SimpleHostBotComposer' + name: "SimpleHostBotComposer.csproj" + netCoreVersion: "3.1.x" + dependency: + registry: ${{ parameters.dependenciesRegistryDotNetHosts }} + version: ${{ parameters.dependenciesVersionDotNetHosts }} + + - name: "bffnechoskillbotcomposerdotnet" + dependsOn: "Deploy_bffnechoskillbotdotnet21" + type: "Skill" + displayName: "DotNet Echo Composer Skill Bot" + appId: $(BFFNECHOSKILLBOTCOMPOSERDOTNETAPPID) + appSecret: $(BFFNECHOSKILLBOTCOMPOSERDOTNETAPPSECRET) + project: + directory: 'Bots/DotNet/Skills/Composer/EchoSkillBotComposer' + name: "EchoSkillBotComposer.csproj" + netCoreVersion: "3.1.x" + dependency: + registry: ${{ parameters.dependenciesRegistryDotNetSkills }} + version: ${{ parameters.dependenciesVersionDotNetSkills }} + +# JS + - template: js/deploy.yml + parameters: + appInsight: "$(INTERNALAPPINSIGHTSNAME)" + appServicePlan: "$(INTERNALAPPSERVICEPLANJSNAME)" + appServicePlanRG: "$(INTERNALAPPSERVICEPLANWINDOWSRESOURCEGROUP)" + azureSubscription: "$(AZURESUBSCRIPTION)" + botPricingTier: $env:BOTPRICINGTIER + connectionName: $env:CONNECTIONNAME + dependsOn: "Prepare_JSGroup" + keyVault: "$(INTERNALKEYVAULTNAME)" + resourceGroup: "$(INTERNALRESOURCEGROUPNAME)-JS" + resourceSuffix: $(INTERNALRESOURCESUFFIX) + bots: + - name: "bffnsimplehostbotjs" + type: "Host" + displayName: "JS Simple Host Bot" + appId: $(BFFNSIMPLEHOSTBOTJSAPPID) + appSecret: $(BFFNSIMPLEHOSTBOTJSAPPSECRET) + project: + directory: 'Bots/JavaScript/Consumers/CodeFirst/SimpleHostBot' + dependency: + registry: ${{ parameters.dependenciesRegistryJSHosts }} + version: ${{ parameters.dependenciesVersionJSHosts }} + + - name: "bffnechoskillbotjs" + type: "Skill" + displayName: "JS Echo Skill Bot" + appId: $(BFFNECHOSKILLBOTJSAPPID) + appSecret: $(BFFNECHOSKILLBOTJSAPPSECRET) + project: + directory: 'Bots/JavaScript/Skills/CodeFirst/EchoSkillBot' + dependency: + registry: ${{ parameters.dependenciesRegistryJSSkills }} + version: ${{ parameters.dependenciesVersionJSSkills }} + + - name: "bffnechoskillbotjsv3" + type: "SkillV3" + displayName: "JS Echo Skill Bot v3" + appId: $(BFFNECHOSKILLBOTJSV3APPID) + appSecret: $(BFFNECHOSKILLBOTJSV3APPSECRET) + project: + directory: 'Bots/JavaScript/Skills/CodeFirst/EchoSkillBot-v3' + dependency: + registry: ${{ parameters.dependenciesRegistryJSSkillsV3 }} + version: ${{ parameters.dependenciesVersionJSSkillsV3 }} + + - name: "bffnwaterfallhostbotjs" + type: "Host" + displayName: "JS Waterfall Host Bot" + appId: $(BFFNWATERFALLHOSTBOTJSAPPID) + appSecret: $(BFFNWATERFALLHOSTBOTJSAPPSECRET) + project: + directory: 'Bots/JavaScript/Consumers/CodeFirst/WaterfallHostBot' + dependency: + registry: ${{ parameters.dependenciesRegistryJSHosts }} + version: ${{ parameters.dependenciesVersionJSHosts }} + + - name: "bffnwaterfallskillbotjs" + type: "Skill" + displayName: "JS Waterfall Skill Bot" + appId: $(BFFNWATERFALLSKILLBOTJSAPPID) + appSecret: $(BFFNWATERFALLSKILLBOTJSAPPSECRET) + project: + directory: 'Bots/JavaScript/Skills/CodeFirst/WaterfallSkillBot' + dependency: + registry: ${{ parameters.dependenciesRegistryJSSkills }} + version: ${{ parameters.dependenciesVersionJSSkills }} + +# Python + - template: python/deploy.yml + parameters: + appServicePlan: "$(INTERNALAPPSERVICEPLANPYTHONNAME)" + appServicePlanRG: "$(INTERNALAPPSERVICEPLANLINUXRESOURCEGROUP)" + azureSubscription: "$(AZURESUBSCRIPTION)" + botPricingTier: $env:BOTPRICINGTIER + connectionName: $env:CONNECTIONNAME + dependsOn: "Prepare_PythonGroup" + keyVault: "$(INTERNALKEYVAULTNAME)" + resourceGroup: "$(INTERNALRESOURCEGROUPNAME)-Python" + resourceSuffix: "$(INTERNALRESOURCESUFFIX)" + bots: + - name: "bffnsimplehostbotpython" + type: "Host" + displayName: "Python Simple Host Bot" + appId: $(BFFNSIMPLEHOSTBOTPYTHONAPPID) + appSecret: $(BFFNSIMPLEHOSTBOTPYTHONAPPSECRET) + project: + directory: 'Bots/Python/Consumers/CodeFirst/SimpleHostBot' + dependency: + registry: ${{ parameters.dependenciesRegistryPythonHosts }} + version: ${{ parameters.dependenciesVersionPythonHosts }} + + - name: "bffnechoskillbotpython" + type: "Skill" + displayName: "Python Echo Skill Bot" + appId: $(BFFNECHOSKILLBOTPYTHONAPPID) + appSecret: $(BFFNECHOSKILLBOTPYTHONAPPSECRET) + project: + directory: 'Bots/Python/Skills/CodeFirst/EchoSkillBot' + dependency: + registry: ${{ parameters.dependenciesRegistryPythonSkills }} + version: ${{ parameters.dependenciesVersionPythonSkills }} + + - name: "bffnwaterfallhostbotpython" + type: "Host" + displayName: "Python Waterfall Host Bot" + appId: $(BFFNWATERFALLHOSTBOTPYTHONAPPID) + appSecret: $(BFFNWATERFALLHOSTBOTPYTHONAPPSECRET) + project: + directory: 'Bots/Python/Consumers/CodeFirst/WaterfallHostBot' + dependency: + registry: ${{ parameters.dependenciesRegistryPythonHosts }} + version: ${{ parameters.dependenciesVersionPythonHosts }} + + - name: "bffnwaterfallskillbotpython" + type: "Skill" + displayName: "Python Waterfall Skill Bot" + appId: $(BFFNWATERFALLSKILLBOTPYTHONAPPID) + appSecret: $(BFFNWATERFALLSKILLBOTPYTHONAPPSECRET) + project: + directory: 'Bots/Python/Skills/CodeFirst/WaterfallSkillBot' + dependency: + registry: ${{ parameters.dependenciesRegistryPythonSkills }} + version: ${{ parameters.dependenciesVersionPythonSkills }} + +# Publish variables + - stage: "Publish_Variables" + displayName: "Publish Variables" + dependsOn: [] + jobs: + - job: "Publish_Variables" + displayName: "Publish Variables" + steps: + - powershell: | + $variables = @{ + deploymentBuildSuffix = "$(BUILD.BUILDID)" + } + Write-Host $variables + New-Item -Path "$(SYSTEM.DEFAULTWORKINGDIRECTORY)" -Name "variables" -ItemType "directory" + $variables | ConvertTo-Json | Out-File "$(SYSTEM.DEFAULTWORKINGDIRECTORY)/Variables/variables.json" + displayName: "Create Variables file" + + - task: PublishPipelineArtifact@1 + displayName: "Publish Variables as artifact" + inputs: + targetPath: "$(SYSTEM.DEFAULTWORKINGDIRECTORY)/variables" + artifactName: Variables diff --git a/tests/functional/build/yaml/deployBotResources/dotnet/deploy.yml b/tests/functional/build/yaml/deployBotResources/dotnet/deploy.yml new file mode 100644 index 0000000000..9c85c0cd42 --- /dev/null +++ b/tests/functional/build/yaml/deployBotResources/dotnet/deploy.yml @@ -0,0 +1,260 @@ +parameters: + - name: appInsight + displayName: Azure Application Insight name + type: string + + - name: appServicePlan + displayName: App Service Plan name + type: string + + - name: appServicePlanRG + displayName: App Service Plan Resource Group + type: string + + - name: azureSubscription + displayName: Azure Service Connection + type: string + + - name: botPricingTier + displayName: Bot Pricing Tier + type: string + + - name: bots + displayName: Bots + type: object + + - name: buildFolder + displayName: Build Folder + type: string + default: "build-dotnet" + + - name: connectionName + displayName: OAuth Connection Name + type: string + + - name: keyVault + displayName: Key Vault name + type: string + + - name: resourceGroup + displayName: Resource Group + type: string + + - name: resourceSuffix + displayName: Azure resources' name suffix + type: string + +stages: +- ${{ each bot in parameters.bots }}: + - stage: "Deploy_${{ bot.name }}" + ${{ if eq(bot.displayName, '') }}: + displayName: "${{ bot.name }}" + ${{ if ne(bot.displayName, '') }}: + displayName: "${{ bot.displayName }}" + dependsOn: "${{ bot.dependsOn }}" + jobs: + - job: "Deploy" + ${{ if eq(bot.type, 'SkillV3') }}: + variables: + SolutionDir: "$(BUILD.SOURCESDIRECTORY)/Bots/DotNet/" + displayName: "Deploy steps" + steps: + # Delete Bot Resources + - template: ../common/deleteResources.yml + parameters: + azureSubscription: "${{ parameters.azureSubscription }}" + resourceGroup: "${{ parameters.resourceGroup }}" + resourceName: "${{ bot.name }}" + resourceSuffix: "${{ parameters.resourceSuffix }}" + + # Gets Bot App Registration credentials from KeyVault or Pipeline Variables + - template: ../common/getAppRegistration.yml + parameters: + appId: ${{ bot.appId }} + appSecret: ${{ bot.appSecret }} + azureSubscription: "${{ parameters.azureSubscription }}" + botName: "${{ bot.name }}" + keyVault: "${{ parameters.keyVault }}" + + # Use Net Core version + - ${{ if ne(bot.project.netCoreVersion, '') }}: + - task: UseDotNet@2 + displayName: "Use NetCore v${{ bot.project.netCoreVersion }}" + inputs: + version: "${{ bot.project.netCoreVersion }}" + + # Use NuGet + - task: NuGetToolInstaller@1 + displayName: "Use NuGet" + + # Prepare appsettings.json file, deleting all the declared skills, so it uses only the settings define in Azure + - ${{ if eq(bot.type, 'Host') }}: + - task: PowerShell@2 + displayName: 'Prepare App Settings' + inputs: + targetType: inline + workingDirectory: '$(SYSTEM.DEFAULTWORKINGDIRECTORY)/${{ bot.project.directory }}' + failOnStderr: true + script: | + $file = "./appsettings.json" + $content = Get-Content -Raw $file | ConvertFrom-Json + $content.BotFrameworkSkills = @() + $content | ConvertTo-Json | Set-Content $file + + # Run NuGet restore SkillV3 + - ${{ if eq(bot.type, 'SkillV3') }}: + - task: NuGetCommand@2 + displayName: "NuGet restore" + inputs: + restoreSolution: "${{ bot.project.directory }}/${{ bot.project.name }}" + restoreDirectory: "$(SOLUTIONDIR)packages" + + # Evaluate dependencies source and version + - template: evaluateDependenciesVariables.yml + parameters: + botType: "${{ bot.type }}" + registry: "${{ bot.dependency.registry }}" + version: "${{ bot.dependency.version }}" + + # Start of DotNet Install & Build + - ${{ if in(bot.type, 'Host', 'Skill') }}: + # Install dependencies + - template: installDependencies.yml + parameters: + project: "${{ bot.project }}" + registry: "$(DEPENDENCIESSOURCE)" + version: "$(DEPENDENCIESVERSIONNUMBER)" + packages: + Microsoft.Bot.Builder.Dialogs + Microsoft.Bot.Builder.Integration.AspNet.Core + + # Build Bot + - task: DotNetCoreCLI@2 + displayName: "Build" + inputs: + command: publish + publishWebProjects: false + projects: "${{ bot.project.directory }}/${{ bot.project.name }}" + arguments: "--output $(SYSTEM.DEFAULTWORKINGDIRECTORY)/${{ parameters.buildFolder }}/${{ bot.name }}" + modifyOutputPath: false + + # Get BotBuilder package version + - task: PowerShell@2 + displayName: 'Get BotBuilder Version' + inputs: + targetType: inline + workingDirectory: '$(SYSTEM.DEFAULTWORKINGDIRECTORY)/${{ bot.project.directory }}' + failOnStderr: true + script: | + [XML]$data = Get-Content "./${{ bot.project.name }}" + $package = $data.Project.ItemGroup.PackageReference | Where-Object { $_.Include -eq "Microsoft.Bot.Builder.Integration.AspNet.Core" } + Write-Host "##vso[task.setvariable variable=BotBuilderVersionNumber]$($package.version)" + + # End of DotNet Install & Build + + # Start DotNet v3 Install, Build + - ${{ if eq(bot.type, 'SkillV3') }}: + # Install dependencies + - template: installDependenciesV3.yml + parameters: + registry: "$(DEPENDENCIESSOURCE)" + version: "$(DEPENDENCIESVERSIONNUMBER)" + project: "${{ bot.project }}" + packages: + Microsoft.Bot.Builder + Microsoft.Bot.Builder.Azure + Microsoft.Bot.Builder.History + + # Build bot + - task: MSBuild@1 + displayName: "Build" + inputs: + solution: "${{ bot.project.directory }}/${{ bot.project.name }}" + vsVersion: 16.0 + platform: "$(BUILDPLATFORM)" + configuration: "$(BUILDCONFIGURATION)" + + # Get BotBuilder version + - task: PowerShell@2 + displayName: 'Get BotBuilder Version' + inputs: + targetType: inline + failOnStderr: true + script: | + $result = @(Get-ChildItem "$(SOLUTIONDIR)packages\Microsoft.Bot.Builder.[0-9]*" -directory | Sort LastWriteTime -Descending) + $version = $result[0].Name.Replace("Microsoft.Bot.Builder.", "") + Write-Host "##vso[task.setvariable variable=BotBuilderVersionNumber]$($version)" + + # Zip bot + - task: ArchiveFiles@2 + displayName: 'Zip bot' + inputs: + rootFolderOrFile: '$(SYSTEM.DEFAULTWORKINGDIRECTORY)/${{ bot.project.directory }}' + includeRootFolder: false + archiveType: 'zip' + archiveFile: '$(SYSTEM.DEFAULTWORKINGDIRECTORY)/${{ parameters.buildFolder }}/${{ bot.name }}/${{ bot.name }}.zip' + replaceExistingArchive: true + verbose: true + # End of DotNet v3 Install, Build + + # Tag BotBuilder package version + - template: ../common/tagBotBuilderVersion.yml + parameters: + ${{ if eq(bot.displayName, '') }}: + botName: "${{ bot.name }}" + ${{ if ne(bot.displayName, '') }}: + botName: "${{ bot.displayName }}" + version: "$(BOTBUILDERVERSIONNUMBER)" + + # Upload zip to artifacts in case we want to debug it + - task: PublishBuildArtifacts@1 + displayName: 'Publish zip package' + inputs: + pathToPublish: "$(SYSTEM.DEFAULTWORKINGDIRECTORY)/${{ parameters.buildFolder }}/${{ bot.name }}/${{ bot.name }}.zip" + artifactName: dotnet-$(BUILD.BUILDID) + + # Create App Service and Bot Channel Registration + - template: ../common/createAppService.yml + parameters: + appId: $(APPID) + appInsight: "${{ parameters.appInsight }}" + appSecret: $(APPSECRET) + appServicePlan: "${{ parameters.appServicePlan }}" + appServicePlanRG: "${{ parameters.appServicePlanRG }}" + azureSubscription: "${{ parameters.azureSubscription }}" + botGroup: "${{ parameters.resourceGroup }}" + botName: "${{ bot.name }}" + botPricingTier: "${{ parameters.botPricingTier }}" + resourceSuffix: "${{ parameters.resourceSuffix }}" + templateFile: "build/templates/template-bot-resources.json" + + # Deploy bot + - task: AzureWebApp@1 + displayName: 'Deploy Azure Web App : ${{ bot.name }}-$(BUILD.BUILDID)' + inputs: + azureSubscription: "${{ parameters.azureSubscription }}" + appName: '${{ bot.name }}${{ parameters.resourceSuffix }}-$(BUILD.BUILDID)' + resourceGroupName: '${{ parameters.resourceGroup }}' + package: '$(SYSTEM.DEFAULTWORKINGDIRECTORY)/${{ parameters.buildFolder }}/${{ bot.name }}/${{ bot.name }}.zip' + deploymentMethod: runFromPackage + + # Configure OAuth + - ${{ if eq(bot.type, 'Skill') }}: + - template: ../common/configureOAuth.yml + parameters: + appId: $(APPID) + appSecret: $(APPSECRET) + azureSubscription: "${{ parameters.azureSubscription }}" + botGroup: "${{ parameters.resourceGroup }}" + botName: "${{ bot.name }}" + connectionName: "${{ parameters.connectionName }}" + resourceSuffix: "${{ parameters.resourceSuffix }}" + + # Create DirectLine Channel Hosts + - ${{ if eq(bot.type, 'Host') }}: + - template: ../common/createDirectLine.yml + parameters: + azureSubscription: "${{ parameters.azureSubscription }}" + botGroup: "${{ parameters.resourceGroup }}" + botName: "${{ bot.name }}" + resourceSuffix: "${{ parameters.resourceSuffix }}" diff --git a/tests/functional/build/yaml/deployBotResources/dotnet/deployComposer.yml b/tests/functional/build/yaml/deployBotResources/dotnet/deployComposer.yml new file mode 100644 index 0000000000..5e20c68afe --- /dev/null +++ b/tests/functional/build/yaml/deployBotResources/dotnet/deployComposer.yml @@ -0,0 +1,167 @@ +parameters: + - name: appInsight + displayName: Azure Application Insight name + type: string + + - name: appServicePlan + displayName: App Service Plan name + type: string + + - name: appServicePlanRG + displayName: App Service Plan Resource Group + type: string + + - name: azureSubscription + displayName: Azure Service Connection + type: string + + - name: botPricingTier + displayName: Bot Pricing Tier + type: string + + - name: bots + displayName: Bots + type: object + + - name: buildFolder + displayName: Build Folder + type: string + default: "build-composer" + + - name: keyVault + displayName: Key Vault name + type: string + + - name: resourceGroup + displayName: Resource Group + type: string + + - name: resourceSuffix + displayName: Azure resources' name suffix + type: string + +stages: +- ${{ each bot in parameters.bots }}: + - stage: "Deploy_${{ bot.name }}" + ${{ if eq(bot.displayName, '') }}: + displayName: "${{ bot.name }}" + ${{ if ne(bot.displayName, '') }}: + displayName: "${{ bot.displayName }}" + dependsOn: "${{ bot.dependsOn }}" + jobs: + - job: "Deploy" + displayName: "Deploy steps" + steps: + # Delete Bot Resources + - template: ../common/deleteResources.yml + parameters: + azureSubscription: "${{ parameters.azureSubscription }}" + resourceGroup: "${{ parameters.resourceGroup }}" + resourceName: "${{ bot.name }}" + resourceSuffix: "${{ parameters.resourceSuffix }}" + + # Gets Bot App Registration credentials from KeyVault or Pipeline Variables + - template: ../common/getAppRegistration.yml + parameters: + appId: ${{ bot.appId }} + appSecret: ${{ bot.appSecret }} + azureSubscription: "${{ parameters.azureSubscription }}" + botName: "${{ bot.name }}" + keyVault: "${{ parameters.keyVault }}" + + # Use Net Core version + - ${{ if ne(bot.project.netCoreVersion, '') }}: + - task: UseDotNet@2 + displayName: "Use NetCore v${{ bot.project.netCoreVersion }}" + inputs: + version: "${{ bot.project.netCoreVersion }}" + + # Evaluate dependencies source and version + - template: evaluateDependenciesVariables.yml + parameters: + botType: "${{ bot.type }}" + registry: "${{ bot.dependency.registry }}" + version: "${{ bot.dependency.version }}" + + # Start of DotNet Install & Build + - template: installDependencies.yml + parameters: + project: "${{ bot.project }}" + registry: "$(DEPENDENCIESSOURCE)" + version: "$(DEPENDENCIESVERSIONNUMBER)" + packages: + Microsoft.Bot.Builder.AI.Luis + Microsoft.Bot.Builder.AI.QnA + Microsoft.Bot.Builder.Dialogs.Adaptive.Runtime + + # Build Bot + - task: DotNetCoreCLI@2 + displayName: "Build" + inputs: + command: publish + publishWebProjects: false + projects: "${{ bot.project.directory }}/${{ bot.project.name }}" + arguments: "--output $(SYSTEM.DEFAULTWORKINGDIRECTORY)/${{ parameters.buildFolder }}/${{ bot.name }}" + modifyOutputPath: false + + # Get BotBuilder package version + - task: PowerShell@2 + displayName: 'Get BotBuilder Version' + inputs: + targetType: inline + workingDirectory: '$(SYSTEM.DEFAULTWORKINGDIRECTORY)/${{ bot.project.directory }}' + failOnStderr: true + script: | + [XML]$data = Get-Content "./${{ bot.project.name }}" + $package = $data.Project.ItemGroup.PackageReference | Where-Object { $_.Include -eq "Microsoft.Bot.Builder.Dialogs.Adaptive.Runtime" } + Write-Host "##vso[task.setvariable variable=BotBuilderVersionNumber]$($package.version)" + + # Tag BotBuilder package version + - template: ../common/tagBotBuilderVersion.yml + parameters: + ${{ if eq(bot.displayName, '') }}: + botName: "${{ bot.name }}" + ${{ if ne(bot.displayName, '') }}: + botName: "${{ bot.displayName }}" + version: "$(BOTBUILDERVERSIONNUMBER)" + + # Upload zip to artifacts in case we want to debug it + - task: PublishBuildArtifacts@1 + displayName: 'Publish zip package' + inputs: + pathToPublish: "$(SYSTEM.DEFAULTWORKINGDIRECTORY)/${{ parameters.buildFolder }}/${{ bot.name }}/${{ bot.name }}.zip" + artifactName: dotnet-$(BUILD.BUILDID) + + # Create App Service and Bot Channel Registration + - template: ../common/createAppService.yml + parameters: + appId: $(APPID) + appInsight: "${{ parameters.appInsight }}" + appSecret: $(APPSECRET) + appServicePlan: "${{ parameters.appServicePlan }}" + appServicePlanRG: "${{ parameters.appServicePlanRG }}" + azureSubscription: "${{ parameters.azureSubscription }}" + botGroup: "${{ parameters.resourceGroup }}" + botName: "${{ bot.name }}" + botPricingTier: "${{ parameters.botPricingTier }}" + resourceSuffix: "${{ parameters.resourceSuffix }}" + templateFile: "build/templates/template-bot-resources.json" + + # Deploy bot + - task: AzureWebApp@1 + displayName: 'Deploy Azure Web App : ${{ bot.name }}-$(BUILD.BUILDID)' + inputs: + azureSubscription: "${{ parameters.azureSubscription }}" + appName: '${{ bot.name }}${{ parameters.resourceSuffix }}-$(BUILD.BUILDID)' + resourceGroupName: '${{ parameters.resourceGroup }}' + package: '$(SYSTEM.DEFAULTWORKINGDIRECTORY)/${{ parameters.buildFolder }}/${{ bot.name }}/${{ bot.name }}.zip' + deploymentMethod: runFromPackage + + # Create DirectLine Channel Hosts + - ${{ if eq(bot.type, 'Host') }}: + - template: ../common/createDirectLine.yml + parameters: + azureSubscription: "${{ parameters.azureSubscription }}" + botGroup: "${{ parameters.resourceGroup }}" + botName: "${{ bot.name }}" + resourceSuffix: "${{ parameters.resourceSuffix }}" diff --git a/tests/functional/build/yaml/deployBotResources/dotnet/evaluateDependenciesVariables.yml b/tests/functional/build/yaml/deployBotResources/dotnet/evaluateDependenciesVariables.yml new file mode 100644 index 0000000000..161b04723b --- /dev/null +++ b/tests/functional/build/yaml/deployBotResources/dotnet/evaluateDependenciesVariables.yml @@ -0,0 +1,67 @@ +parameters: + - name: botType + displayName: Bot type + type: string + + - name: registry + displayName: Registry source + type: string + + - name: version + displayName: Version number + type: string + +steps: + - task: PowerShell@2 + displayName: 'Evaluate source & version' + inputs: + targetType: inline + failOnStderr: true + script: | + # Get Source + $sourceDotNetv3MyGet = "https://botbuilder.myget.org/F/botbuilder-v3-dotnet-daily/api/v3/index.json" + $sourceDotNetArtifacts = "https://pkgs.dev.azure.com/ConversationalAI/BotFramework/_packaging/SDK/nuget/v3/index.json" + $sourceDotNetMyGet = "https://botbuilder.myget.org/F/botbuilder-v4-dotnet-daily/api/v3/index.json" + switch -regex ("${{ parameters.registry }}") { + "^($null|)$" { + switch ("${{ parameters.botType }}") { + "SkillV3" { $source = $sourceDotNetv3MyGet } + default { $source = $sourceDotNetArtifacts } + } + } + "Artifacts" { $source = $sourceDotNetArtifacts } + "MyGet" { + switch ("${{ parameters.botType }}") { + "SkillV3" { $source = $sourceDotNetv3MyGet } + default { $source = $sourceDotNetMyGet } + } + } + "NuGet" { $source = "" } + default { $source = "${{ parameters.registry }}" } + } + Write-Host "Source: $source" + + # Get Version Number + switch -regex ("${{ parameters.version }}") { + "^($null||LATEST)$" { + if ("${{ parameters.registry }}".ToUpper() -in "NUGET") { + [Console]::ForegroundColor = "red" + [Console]::Error.WriteLine("Preview versions of BotBuilder are not available for this source.") + [Console]::ResetColor() + exit 1 # Force exit + } + if ("${{ parameters.botType }}" -in "Host", "Skill") { + $PackageList = nuget list Microsoft.Bot.Builder.Integration.AspNet.Core -Source "$source" -PreRelease + $versionNumber = $PackageList.Split(" ")[-1] + } elseif ("${{ parameters.botType }}" -in "SkillV3") { + $versionNumber = "" + } + } + STABLE { $versionNumber = "" } + default { $versionNumber = "${{ parameters.version }}" } + } + Write-Host "Version Number: $versionNumber" + + # Set environment variables + Write-Host "##vso[task.setvariable variable=DependenciesSource]$source" + Write-Host "##vso[task.setvariable variable=DependenciesVersionNumber]$versionNumber" diff --git a/tests/functional/build/yaml/deployBotResources/dotnet/installDependencies.yml b/tests/functional/build/yaml/deployBotResources/dotnet/installDependencies.yml new file mode 100644 index 0000000000..4fc5ef882e --- /dev/null +++ b/tests/functional/build/yaml/deployBotResources/dotnet/installDependencies.yml @@ -0,0 +1,60 @@ +parameters: + - name: packages + displayName: Dependency Packages + type: object + + - name: project + displayName: Project + type: object + + - name: registry + displayName: Registry source + type: string + + - name: version + displayName: Version number + type: string + +steps: + - task: PowerShell@2 + displayName: 'Install dependencies for ${{ parameters.project.name }}' + inputs: + targetType: inline + workingDirectory: '$(SYSTEM.DEFAULTWORKINGDIRECTORY)/${{ parameters.project.directory }}' + failOnStderr: true + script: | + $version = "" + $source = "" + + if (-not ([string]::IsNullOrEmpty("${{ parameters.version }}"))) { + $version = "--version ${{ parameters.version }}" + } + + if (-not ([string]::IsNullOrEmpty("${{ parameters.registry }}"))) { + $source = "--source ${{ parameters.registry }}" + } + + foreach ($package in "${{ parameters.packages }}".Split()) { + if ($package -eq "Microsoft.Bot.Builder.Dialogs.Debugging") { + $versionAux = $version + if ($version -Match "rc") { + $version = "$version.preview" + } elseif ($version -Match "daily") { + $version = $version.replace("daily", "daily.preview") + } else { + $version = "$version-preview" + } + } + + Invoke-Expression "dotnet add ""./${{ parameters.project.name }}"" package $version $source $package" + + if (-not ([string]::IsNullOrEmpty("$versionAux"))) { + $version = $versionAux + $versionAux = "" + } + } + + write-host " `nPackages:" + foreach ($package in "${{ parameters.packages }}".Split()) { + write-host " - $package" + } diff --git a/tests/functional/build/yaml/deployBotResources/dotnet/installDependenciesV3.yml b/tests/functional/build/yaml/deployBotResources/dotnet/installDependenciesV3.yml new file mode 100644 index 0000000000..62bb916f5a --- /dev/null +++ b/tests/functional/build/yaml/deployBotResources/dotnet/installDependenciesV3.yml @@ -0,0 +1,44 @@ +parameters: + - name: packages + displayName: Dependency Packages + type: object + + - name: project + displayName: Project + type: object + + - name: registry + displayName: Registry source + type: string + + - name: version + displayName: Version number + type: string + +steps: + - task: PowerShell@2 + displayName: 'Install dependencies' + inputs: + targetType: inline + workingDirectory: '$(System.DefaultWorkingDirectory)/${{ parameters.project.directory }}' + failOnStderr: true + script: | + $version = "" + $source = "" + + if (-not ([string]::IsNullOrEmpty("${{ parameters.version }}"))) { + $version = "-Version ""${{ parameters.version }}""" + } + + if (-not ([string]::IsNullOrEmpty("${{ parameters.registry }}"))) { + $source = "-Source ""${{ parameters.registry }}""" + } + + foreach ($package in "${{ parameters.packages }}".Split()) { + Invoke-Expression "nuget update ""./packages.config"" -Id $package $version $source" + } + + write-host "`nPackages:" + foreach ($package in "${{ parameters.packages }}".Split()) { + write-host " - $package " + } diff --git a/tests/functional/build/yaml/deployBotResources/js/deploy.yml b/tests/functional/build/yaml/deployBotResources/js/deploy.yml new file mode 100644 index 0000000000..497994cda4 --- /dev/null +++ b/tests/functional/build/yaml/deployBotResources/js/deploy.yml @@ -0,0 +1,218 @@ +parameters: + - name: appInsight + displayName: Azure Application Insight name + type: string + + - name: appServicePlan + displayName: App Service Plan name + type: string + + - name: appServicePlanRG + displayName: App Service Plan Resource Group + type: string + + - name: azureSubscription + displayName: Azure Service Connection + type: string + + - name: bots + displayName: Bots + type: object + + - name: botPricingTier + displayName: Bot Pricing Tier + type: string + + - name: connectionName + displayName: OAuth Connection Name + type: string + + - name: dependsOn + displayName: Depends On + type: string + + - name: keyVault + displayName: Key Vault name + type: string + + - name: resourceGroup + displayName: Resource Group + type: string + + - name: resourceSuffix + displayName: Azure resources' name suffix + type: string + +stages: +- ${{ each bot in parameters.bots }}: + - stage: "Deploy_${{ bot.name }}" + ${{ if eq(bot.displayName, '') }}: + displayName: "${{ bot.name }}" + ${{ if ne(bot.displayName, '') }}: + displayName: "${{ bot.displayName }}" + dependsOn: "${{ parameters.dependsOn }}" + jobs: + - job: "Deploy" + displayName: "Deploy steps" + steps: + # Delete Bot Resources + - template: ../common/deleteResources.yml + parameters: + azureSubscription: "${{ parameters.azureSubscription }}" + resourceGroup: "${{ parameters.resourceGroup }}" + resourceName: "${{ bot.name }}" + resourceSuffix: "${{ parameters.resourceSuffix }}" + + # Gets Bot App Registration credentials from KeyVault or Pipeline Variables + - template: ../common/getAppRegistration.yml + parameters: + appId: ${{ bot.appId }} + appSecret: ${{ bot.appSecret }} + azureSubscription: "${{ parameters.azureSubscription }}" + botName: "${{ bot.name }}" + keyVault: "${{ parameters.keyVault }}" + + # Prepare .env file, deleting all the declared skills, so it uses only the settings define in Azure + - ${{ if eq(bot.type, 'Host') }}: + - task: PowerShell@2 + displayName: 'Prepare .env file' + inputs: + targetType: inline + workingDirectory: '$(SYSTEM.DEFAULTWORKINGDIRECTORY)/${{ bot.project.directory }}' + failOnStderr: true + script: | + $file = "./.env" + $content = Get-Content $file + $content | ForEach-Object { + $line = $_ + if ($line.Trim().Length -gt 0 -and -not $line.Trim().ToLower().StartsWith('skill_')) { + $line + } + } | Set-Content $file; + + # Evaluate dependencies source and version + - template: evaluateDependenciesVariables.yml + parameters: + botType: "${{ bot.type }}" + registry: "${{ bot.dependency.registry }}" + version: "${{ bot.dependency.version }}" + + # Tag BotBuilder version + - template: ../common/tagBotBuilderVersion.yml + parameters: + ${{ if eq(bot.displayName, '') }}: + botName: "${{ bot.name }}" + ${{ if ne(bot.displayName, '') }}: + botName: "${{ bot.displayName }}" + version: "$(DEPENDENCIESVERSIONNUMBER)" + + # Set BotBuilder source and version + - task: PowerShell@2 + displayName: 'Set BotBuilder source and version' + inputs: + targetType: inline + workingDirectory: '$(SYSTEM.DEFAULTWORKINGDIRECTORY)/${{ bot.project.directory }}' + failOnStderr: true + script: | + $registry = "$(DEPENDENCIESSOURCE)"; + $version = "$(DEPENDENCIESVERSIONNUMBER)"; + $packagejsonPath = "./package.json"; + + $packagejson = Get-Content $packagejsonPath | ConvertFrom-Json + $dependencies = @{} + + $packagejson.dependencies.psobject.properties | ForEach-Object { + $dependencies[$_.Name] = if ($_.Name -match "botbuilder*") { $version } else { $_.Value } + } + + $packagejson.dependencies = $dependencies + + Set-Content $packagejsonPath -Value ($packagejson | ConvertTo-Json) + + Write-Host "Updated BotBuilder packages version:"; + $dependencies + + Write-Host "`nUsing the registry source: $registry"; + New-Item -Path . -Name ".npmrc" -ItemType "file" -Value "registry=$registry"; + + # Install Packages + - task: Npm@1 + displayName: 'Install dependencies' + inputs: + command: 'install' + workingDir: '$(SYSTEM.DEFAULTWORKINGDIRECTORY)/${{ bot.project.directory }}' + customRegistries: 'useNpmrc' + verbose: true + + # Prepate bot + - task: AzureCLI@2 + displayName: 'Prepare Bot' + inputs: + azureSubscription: "${{ parameters.azureSubscription }}" + scriptType: pscore + scriptLocation: inlineScript + inlineScript: 'az bot prepare-deploy --code-dir "${{ bot.project.directory }}" --lang Javascript' + + # Zip bot + - task: ArchiveFiles@2 + displayName: 'Zip bot' + inputs: + rootFolderOrFile: '$(SYSTEM.DEFAULTWORKINGDIRECTORY)/${{ bot.project.directory }}' + includeRootFolder: false + archiveType: 'zip' + archiveFile: '$(SYSTEM.DEFAULTWORKINGDIRECTORY)/build/${{ bot.name }}.zip' + replaceExistingArchive: true + verbose: true + + # Upload zip to artifacts in case we want to debug it + - task: PublishBuildArtifacts@1 + displayName: 'Publish zip package' + inputs: + pathToPublish: '$(SYSTEM.DEFAULTWORKINGDIRECTORY)/build/${{ bot.name }}.zip' + artifactName: javascript-$(BUILD.BUILDID) + + # Create App Service and Bot Channel Registration + - template: ../common/createAppService.yml + parameters: + appId: $(APPID) + appInsight: "${{ parameters.appInsight }}" + appSecret: $(APPSECRET) + appServicePlan: "${{ parameters.appServicePlan }}" + appServicePlanRG: "${{ parameters.appServicePlanRG }}" + azureSubscription: "${{ parameters.azureSubscription }}" + botGroup: "${{ parameters.resourceGroup }}" + botName: "${{ bot.name }}" + botPricingTier: "${{ parameters.botPricingTier }}" + resourceSuffix: "${{ parameters.resourceSuffix }}" + templateFile: "build/templates/template-bot-resources.json" + + # Deploy bot + - task: AzureWebApp@1 + displayName: 'Deploy Azure Web App : ${{ bot.name }}-$(BUILD.BUILDID)' + inputs: + azureSubscription: "${{ parameters.azureSubscription }}" + appName: '${{ bot.name }}${{ parameters.resourceSuffix }}-$(BUILD.BUILDID)' + resourceGroupName: '${{ parameters.resourceGroup }}' + package: '$(SYSTEM.DEFAULTWORKINGDIRECTORY)/build/${{ bot.name }}.zip' + deploymentMethod: runFromPackage + + # Configure OAuth + - ${{ if eq(bot.type, 'Skill') }}: + - template: ../common/configureOAuth.yml + parameters: + appId: $(APPID) + appSecret: $(APPSECRET) + azureSubscription: "${{ parameters.azureSubscription }}" + botGroup: "${{ parameters.resourceGroup }}" + botName: "${{ bot.name }}" + connectionName: "${{ parameters.connectionName }}" + resourceSuffix: "${{ parameters.resourceSuffix }}" + + # Create DirectLine Channel Hosts + - ${{ if eq(bot.type, 'Host') }}: + - template: ../common/createDirectLine.yml + parameters: + azureSubscription: "${{ parameters.azureSubscription }}" + botGroup: "${{ parameters.resourceGroup }}" + botName: "${{ bot.name }}" + resourceSuffix: "${{ parameters.resourceSuffix }}" diff --git a/tests/functional/build/yaml/deployBotResources/js/evaluateDependenciesVariables.yml b/tests/functional/build/yaml/deployBotResources/js/evaluateDependenciesVariables.yml new file mode 100644 index 0000000000..fc47b0302e --- /dev/null +++ b/tests/functional/build/yaml/deployBotResources/js/evaluateDependenciesVariables.yml @@ -0,0 +1,69 @@ +parameters: + - name: botType + displayName: Bot type + type: string + + - name: registry + displayName: Registry source + type: string + + - name: version + displayName: Version number + type: string + +steps: + - task: PowerShell@2 + displayName: 'Evaluate source & version' + inputs: + targetType: inline + failOnStderr: true + script: | + # Get Source + $sourceJSMyGet = "https://botbuilder.myget.org/F/botbuilder-v4-js-daily/npm/" + $sourceJSv3MyGet = "https://botbuilder.myget.org/F/botbuilder-v3-js-daily/npm/" + $sourceJSNpm = "https://registry.npmjs.com/" + switch -regex ("${{ parameters.registry }}") { + "^($null|MyGet)$" { + switch ("${{ parameters.botType }}") { + "SkillV3" { $source = $sourceJSv3MyGet } + default { $source = $sourceJSMyGet } + } + } + "Npm" { $source = $sourceJSNpm } + default { $source = "${{ parameters.registry }}" } + } + Write-Host "Source: $source" + npm config set registry $source + + # Get Version Number + switch -regex ("${{ parameters.version }}") { + "^($null||LATEST)$" { + if ("${{ parameters.registry }}".ToUpper() -in "NPM") { + [Console]::ForegroundColor = 'red' + [Console]::Error.WriteLine("Preview versions of BotBuilder are not available for this source.") + [Console]::ResetColor() + exit 1 # Force exit + } + + if ($source -eq $sourceJSMyGet) { + $versionNumber = npm show botbuilder@next version | Out-String + } else { + $versionNumber = npm show botbuilder@latest version | Out-String + } + } + STABLE { + if ("${{ parameters.botType }}" -in "Host", "Skill") { + $PackageList = npm show botbuilder@* version | Out-String; + } + elseif ("${{ parameters.botType }}" -in "SkillV3") { + $PackageList = npm show botbuilder@3.* version | Out-String; + } + $versionNumber = ($PackageList.Split(" ")[-1]).Trim().TrimStart("'").TrimEnd("'"); + } + default { $versionNumber = "${{ parameters.version }}" } + } + Write-Host "Version Number: $versionNumber" + + # Set environment variables + Write-Host "##vso[task.setvariable variable=DependenciesSource]$source" + Write-Host "##vso[task.setvariable variable=DependenciesVersionNumber]$versionNumber" diff --git a/tests/functional/build/yaml/deployBotResources/python/deploy.yml b/tests/functional/build/yaml/deployBotResources/python/deploy.yml new file mode 100644 index 0000000000..b0f721a61e --- /dev/null +++ b/tests/functional/build/yaml/deployBotResources/python/deploy.yml @@ -0,0 +1,183 @@ +parameters: + - name: appServicePlan + displayName: App Service Plan name + type: string + + - name: appServicePlanRG + displayName: App Service Plan Resource Group + type: string + + - name: azureSubscription + displayName: Azure Service Connection + type: string + + - name: bots + displayName: Bots + type: object + + - name: botPricingTier + displayName: Bot Pricing Tier + type: string + + - name: connectionName + displayName: OAuth Connection Name + type: string + + - name: dependsOn + displayName: Depends On + type: string + + - name: keyVault + displayName: Key Vault name + type: string + + - name: resourceGroup + displayName: Resource Group + type: string + + - name: resourceSuffix + displayName: Azure resources' name suffix + type: string + +stages: +- ${{ each bot in parameters.bots }}: + - stage: "Deploy_${{ bot.name }}" + ${{ if eq(bot.displayName, '') }}: + displayName: "${{ bot.name }}" + ${{ if ne(bot.displayName, '') }}: + displayName: "${{ bot.displayName }}" + dependsOn: "${{ parameters.dependsOn }}" + jobs: + - job: "Deploy" + pool: + vmImage: "ubuntu-latest" + displayName: "Deploy steps" + steps: + # Delete Bot Resources + - template: ../common/deleteResources.yml + parameters: + azureSubscription: "${{ parameters.azureSubscription }}" + resourceGroup: "${{ parameters.resourceGroup }}" + resourceName: "${{ bot.name }}" + resourceSuffix: "${{ parameters.resourceSuffix }}" + + # Gets Bot App Registration credentials from KeyVault or Pipeline Variables + - template: ../common/getAppRegistration.yml + parameters: + appId: ${{ bot.appId }} + appSecret: ${{ bot.appSecret }} + azureSubscription: "${{ parameters.azureSubscription }}" + botName: "${{ bot.name }}" + keyVault: "${{ parameters.keyVault }}" + + # Prepare .env file, deleting all the declared skills, so it uses only the settings define in Azure + - ${{ if eq(bot.type, 'Host') }}: + - task: PowerShell@2 + displayName: "Prepare .env file" + inputs: + targetType: inline + script: | + $file = "${{ bot.project.directory }}/.env" + $content = Get-Content $file + $content | ForEach-Object { + $line = $_ + if ($line.Trim().Length -gt 0 -and -not $line.Trim().ToLower().StartsWith("skill_")) { + $line + } + } | Set-Content $file + + # Evaluate dependencies source and version + - template: evaluateDependenciesVariables.yml + parameters: + botType: "${{ bot.type }}" + source: "${{ bot.project.directory }}" + registry: "${{ bot.dependency.registry }}" + version: "${{ bot.dependency.version }}" + + # Tag BotBuilder package version + - template: ../common/tagBotBuilderVersion.yml + parameters: + ${{ if eq(bot.displayName, '') }}: + botName: "${{ bot.name }}" + ${{ if ne(bot.displayName, '') }}: + botName: "${{ bot.displayName }}" + version: "$(DEPENDENCIESVERSIONNUMBER)" + + # Create App Service and Bot Channel Registration + - template: ../common/createAppService.yml + parameters: + appId: $(APPID) + appSecret: $(APPSECRET) + appServicePlan: "${{ parameters.appServicePlan }}" + appServicePlanRG: "${{ parameters.appServicePlanRG }}" + azureSubscription: "${{ parameters.azureSubscription }}" + botName: "${{ bot.name }}" + botGroup: "${{ parameters.resourceGroup }}" + botPricingTier: "${{ parameters.botPricingTier }}" + resourceSuffix: "${{ parameters.resourceSuffix }}" + templateFile: "build/templates/template-python-bot-resources.json" + + # Configure OAuth + - ${{ if eq(bot.type, 'Skill') }}: + - template: ../common/configureOAuth.yml + parameters: + appId: $(APPID) + appSecret: $(APPSECRET) + azureSubscription: "${{ parameters.azureSubscription }}" + botGroup: "${{ parameters.resourceGroup }}" + botName: "${{ bot.name }}" + connectionName: "${{ parameters.connectionName }}" + resourceSuffix: "${{ parameters.resourceSuffix }}" + + # Set Python version + - task: UsePythonVersion@0 + displayName: "Set python version to 3.8.x" + inputs: + versionSpec: "3.8.x" + + # Build Python app (linux only) + - script: | + python -m venv antenv + source antenv/bin/activate + python -m pip install --upgrade pip + pip install -r requirements.txt + workingDirectory: $(SYSTEM.DEFAULTWORKINGDIRECTORY)/${{ bot.project.directory }} + displayName: "Build Python app" + + # Zip bot + - task: ArchiveFiles@2 + displayName: "Zip bot" + inputs: + rootFolderOrFile: "$(SYSTEM.DEFAULTWORKINGDIRECTORY)/${{ bot.project.directory }}" + includeRootFolder: false + archiveType: "zip" + archiveFile: "$(SYSTEM.DEFAULTWORKINGDIRECTORY)/build/${{ bot.name }}.zip" + replaceExistingArchive: true + verbose: true + + # Upload zip to artifacts in case we want to debug it + - task: PublishBuildArtifacts@1 + displayName: "Publish zip package" + inputs: + pathToPublish: "$(SYSTEM.DEFAULTWORKINGDIRECTORY)/build/${{ bot.name }}.zip" + artifactName: python-$(BUILD.BUILDID) + + # Deploy bot + - task: AzureWebApp@1 + displayName: "Deploy Azure Web App : ${{ bot.name }}-$(BUILD.BUILDID)" + inputs: + azureSubscription: "${{ parameters.azureSubscription }}" + appName: "${{ bot.name }}${{ parameters.resourceSuffix }}-$(BUILD.BUILDID)" + appType: webAppLinux + resourceGroupName: "${{ parameters.resourceGroup }}" + package: "$(System.DefaultWorkingDirectory)/build/${{ bot.name }}.zip" + deploymentMethod: runFromPackage + + # Create DirectLine Channel Hosts + - ${{ if eq(bot.type, 'Host') }}: + - template: ../common/createDirectLine.yml + parameters: + azureSubscription: "${{ parameters.azureSubscription }}" + botGroup: "${{ parameters.resourceGroup }}" + botName: "${{ bot.name }}" + resourceSuffix: "${{ parameters.resourceSuffix }}" diff --git a/tests/functional/build/yaml/deployBotResources/python/evaluateDependenciesVariables.yml b/tests/functional/build/yaml/deployBotResources/python/evaluateDependenciesVariables.yml new file mode 100644 index 0000000000..aa8874fa4e --- /dev/null +++ b/tests/functional/build/yaml/deployBotResources/python/evaluateDependenciesVariables.yml @@ -0,0 +1,110 @@ +parameters: + - name: botType + displayName: Bot type + type: string + + - name: source + displayName: Bot Source Location + type: string + + - name: registry + displayName: Registry source + type: string + + - name: version + displayName: Version number + type: string + +steps: + - task: PowerShell@2 + displayName: "Evaluate source & version" + inputs: + targetType: inline + script: | + # Get Source + $sourcePypi = "https://pypi.org/simple/" + $sourceTestPypi = "https://test.pypi.org/simple/" + $sourceArtifacts = "https://pkgs.dev.azure.com/ConversationalAI/BotFramework/_packaging/SDK/pypi/simple/" + + switch -regex ("${{ parameters.registry }}") { + "^($null||Artifacts)$" { + $source = $sourceArtifacts + $extraSource = $sourcePypi + } + "Pypi" { + $source = $sourcePypi + $extraSource = $sourceArtifacts + } + "Test.Pypi" { + $source = $sourceTestPypi + $extraSource = $sourcePypi + } + default { + $source = "${{ parameters.registry }}" + $extraSource = $sourcePypi + } + } + + # Get Version Number + switch -regex ("${{ parameters.version }}") { + "^($null||LATEST)$" { + if ("${{ parameters.registry }}".ToUpper() -in "PYPI") { + [Console]::ForegroundColor = "red" + [Console]::Error.WriteLine("Preview versions of BotBuilder are not available for this source.") + [Console]::ResetColor() + exit 1 # Force exit + } + $versionNumber = "" + $preFag = "--pre" + } + STABLE { + $versionNumber = "" + } + default { + $versionNumber = "${{ parameters.version }}" + $condition = "==" + } + } + + #Add the $source source at the beginning of requirements + $file = "${{ parameters.source }}/requirements.txt" + $content = @(Get-Content $file) + $line = "$preFag --index-url $source --extra-index-url $extraSource".Trim() + Set-Content -Path $file -Value $line + Add-Content -Path $file -Value $content + + function UpdatePackageVersion($package) { + #Set Package version to empty value + $content = @(Get-Content $file) + $matchinfo = Select-String -Path $file -Pattern $package + + $script = "$package $condition $versionNumber" + + if ($matchinfo.LineNumber -gt 0) { + $content[$matchinfo.LineNumber - 1] = $script + Set-Content -Path $file -Value $content + } else { + Add-Content -Path $file -Value $script + } + } + + UpdatePackageVersion "botbuilder-dialogs" + UpdatePackageVersion "botbuilder-integration-aiohttp" + + if (-not $versionNumber) { + Invoke-Expression "pip install botbuilder-integration-aiohttp $line --quiet" + $packageVersion = pip show botbuilder-integration-aiohttp | Where-Object { $_ -match "^Version" } + $versionNumber = $packageVersion.Split(" ")[1] + } + + Write-Host "`nSource: $source" + Write-Host "Extra Source: $extraSource" + Write-Host "Version Number: $versionNumber" + + $content = @(Get-Content $file) + Write-Host "`nrequirements.txt file:" + $content + + # Set environment variables + Write-Host "##vso[task.setvariable variable=DependenciesSource]$source" + Write-Host "##vso[task.setvariable variable=DependenciesVersionNumber]$versionNumber" diff --git a/tests/functional/build/yaml/dotnetBotsBuild-CI.yml b/tests/functional/build/yaml/dotnetBotsBuild-CI.yml new file mode 100644 index 0000000000..a5d9e29280 --- /dev/null +++ b/tests/functional/build/yaml/dotnetBotsBuild-CI.yml @@ -0,0 +1,53 @@ +parameters: + - name: buildConfiguration + displayName: Build Configuration + type: string + + - name: buildPlatform + displayName: Build Platfrom + type: string + + - name: solutionDir + displayName: Solution Directory + type: string + +steps: +- task: UseDotNet@2 + displayName: "Use .Net Core sdk 2.1.x" + inputs: + version: 2.1.x + +- task: UseDotNet@2 + displayName: "Use .Net Core sdk 3.1.x" + inputs: + version: 3.1.x + +- task: NuGetToolInstaller@1 + displayName: "Use NuGet" + +- task: NuGetCommand@2 + displayName: "NuGet restore" + inputs: + restoreSolution: "Bots/DotNet/FunctionalTestsBots.sln" + restoreDirectory: "${{ parameters.solutionDir }}packages" + +- task: MSBuild@1 + displayName: "Build" + inputs: + solution: "Bots/DotNet/FunctionalTestsBots.sln" + vsVersion: 16.0 + platform: "${{ parameters.buildPlatform }}" + configuration: "${{ parameters.buildConfiguration }}" + +- task: PublishBuildArtifacts@1 + displayName: "Publish Artifact: build folder" + inputs: + PathtoPublish: build + ArtifactName: build + +- powershell: | + cd .. + ls -R + displayName: "Dir workspace" + continueOnError: true + condition: succeededOrFailed() diff --git a/tests/functional/build/yaml/functionalTests-CI.yml b/tests/functional/build/yaml/functionalTests-CI.yml new file mode 100644 index 0000000000..a7bd678a83 --- /dev/null +++ b/tests/functional/build/yaml/functionalTests-CI.yml @@ -0,0 +1,31 @@ +# This YAML runs all the CI pipelines Dot Net, python, JS in parallel +variables: + BuildConfiguration: "Debug" + BuildPlatform: "Any CPU" + +jobs: + - job: "BuildJSBots" + steps: + - template: jsBotsBuildCI.yml + + - job: "BuildPythonBots" + steps: + - template: pythonBotsBuild-CI.yml + + - job: "BuildDotnetBots" + steps: + - template: dotnetBotsBuild-CI.yml + parameters: + buildConfiguration: "$(BUILDCONFIGURATION)" + buildPlatform: "$(BUILDPLATFORM)" + solutionDir: "$(BUILD.SOURCESDIRECTORY)/Bots/DotNet/" + + - job: "BuildSkillsFunctionalTests" + steps: + - template: functionalTestsBuild-CI.yml + parameters: + buildConfiguration: "$(BUILDCONFIGURATION)" + buildPlatform: "$(BUILDPLATFORM)" + +pool: + vmImage: "windows-2019" diff --git a/tests/functional/build/yaml/functionalTestsBuild-CI.yml b/tests/functional/build/yaml/functionalTestsBuild-CI.yml new file mode 100644 index 0000000000..89bff9a0cd --- /dev/null +++ b/tests/functional/build/yaml/functionalTestsBuild-CI.yml @@ -0,0 +1,56 @@ +parameters: + - name: buildConfiguration + displayName: Build Configuration + type: string + + - name: buildPlatform + displayName: Build Platform + type: string + +steps: +- task: UseDotNet@2 + displayName: "Use .Net Core sdk 2.1.x" + inputs: + version: 2.1.x + +- task: UseDotNet@2 + displayName: "Use .Net Core sdk 3.1.x" + inputs: + version: 3.1.x + +- task: NuGetToolInstaller@1 + displayName: "Use NuGet" + +- task: NuGetCommand@2 + displayName: "NuGet restore" + inputs: + restoreSolution: SkillFunctionalTests.sln + +- task: MSBuild@1 + displayName: "Build" + inputs: + solution: "SkillFunctionalTests.sln" + vsVersion: 16.0 + platform: "${{ parameters.buildPlatform }}" + configuration: "${{ parameters.buildConfiguration }}" + +- task: DotNetCoreCLI@2 + displayName: 'Run Unit Tests' + inputs: + command: test + projects: 'Tests/TranscriptTestRunnerTests/TranscriptTestRunnerTests.csproj' + testRunTitle: 'FunctionalTests-CI-Results-$(BUILD.BUILDNUMBER)' + arguments: '-v n --configuration ${{ parameters.buildConfiguration }} --no-build --no-restore --collect "Code Coverage" --logger "trx;LogFileName=FunctionalTests-CI-Results-$(BUILD.BUILDNUMBER).trx"' + +- task: PublishBuildArtifacts@1 + displayName: "Publish Artifact: build folder" + inputs: + PathtoPublish: build + ArtifactName: build + +- powershell: | + cd .. + ls -R + displayName: "Dir workspace" + continueOnError: true + condition: succeededOrFailed() diff --git a/tests/functional/build/yaml/jsBotsBuildCI.yml b/tests/functional/build/yaml/jsBotsBuildCI.yml new file mode 100644 index 0000000000..264b80a4c5 --- /dev/null +++ b/tests/functional/build/yaml/jsBotsBuildCI.yml @@ -0,0 +1,6 @@ +steps: +- script: yarn + displayName: "yarn install" + +- script: yarn lint + displayName: "yarn lint" diff --git a/tests/functional/build/yaml/pythonBotsBuild-CI.yml b/tests/functional/build/yaml/pythonBotsBuild-CI.yml new file mode 100644 index 0000000000..816153ba31 --- /dev/null +++ b/tests/functional/build/yaml/pythonBotsBuild-CI.yml @@ -0,0 +1,14 @@ + steps: + - task: UsePythonVersion@0 + displayName: "Use Python version" + inputs: + versionSpec: "3.8.x" + + - task: Bash@3 + displayName: "Install dependencies" + inputs: + targetType: "inline" + script: | + python -m pip install pip==21.0.1; + find ./Bots -type f -name "requirements.txt" -exec pip install pip==21.0.1 -r "{}" \; + diff --git a/tests/functional/build/yaml/sharedResources/createAppRegistrations.yml b/tests/functional/build/yaml/sharedResources/createAppRegistrations.yml new file mode 100644 index 0000000000..3edd645750 --- /dev/null +++ b/tests/functional/build/yaml/sharedResources/createAppRegistrations.yml @@ -0,0 +1,132 @@ +parameters: +- name: azureSubscription + displayName: Azure Service Connection + type: string + +- name: keyVault + displayName: KeyVault name + type: string + +- name: resourceSuffix + displayName: Azure resources' name suffix + type: string + +- name: servicePrincipalId + displayName: Service Principal Id + type: string + +- name: servicePrincipalKey + displayName: Service Principal Key + type: string + +- name: tenantId + displayName: Subscription Tenant Id + type: string + +steps: + - task: AzureCLI@2 + displayName: "Create App Registrations & Store into KeyVault" + inputs: + azureSubscription: "${{ parameters.azureSubscription }}" + addSpnToEnvironment: true + failOnStandardError: true + scriptType: pscore + scriptLocation: inlineScript + inlineScript: | + # Using Microsoft Graph REST API to create App Registrations (https://docs.microsoft.com/en-us/graph/api/application-post-applications) instead of Azure CLI due to Azure Active Directory Graph API has been deprecated and still in a migration process to Microsoft Graph API, more information can be found in this link (https://github.com/Azure/azure-cli/issues/12946). + + function GetToken() { + # Get Token + + $body = @{ + grant_type = "client_credentials"; + scope = "https://graph.microsoft.com/.default"; + client_id = ${{ parameters.servicePrincipalId }}; + client_secret = ${{ parameters.servicePrincipalKey }}; + } + + Invoke-WebRequest -Uri "https://login.microsoftonline.com/${{ parameters.tenantId }}/oauth2/v2.0/token" -Method "POST" -Body $body | ConvertFrom-Json + } + + function CreateAppRegistration($token, $appName) { + # Create App Registration + + $headers = @{ + Authorization = "Bearer " + $token.access_token + } + + $body = @{ + displayName = $appName; + signInAudience = "AzureADandPersonalMicrosoftAccount" + } | ConvertTo-Json + + $app = Invoke-WebRequest -Uri "https://graph.microsoft.com/v1.0/applications" -Method "POST" -Headers $headers -Body $body -ContentType "application/json" | ConvertFrom-Json; + + # Assign a secret to the App Registration + + $body = @{ + displayName = $app.appId; + } | ConvertTo-Json + + $secret = Invoke-WebRequest -Uri "https://graph.microsoft.com/v1.0/applications/$($app.id)/addPassword" -Method "POST" -Headers $headers -Body $body -ContentType "application/json" | ConvertFrom-Json + + $app | Add-Member -MemberType NoteProperty -Name secret -Value $secret.secretText; + + $app + } + + function SaveAppRegistrationIntoKeyVault($vaultName, $bot, $app) { + # Store secrets into KeyVault + + $secrets = @( + @{ name = $bot.objectId; value = $app.id }, + @{ name = $bot.appId; value = $app.appId }, + @{ name = $bot.appSecret; value = $app.secret } + ) + + foreach ($secret in $secrets) { + az keyvault secret set --vault-name $vaultName --name $secret.name --value="$($secret.value)" --output none + } + } + + $bots = @( + @{ appName = "bffnsimplehostbotdotnet"; variables = @{ appId = "BffnSimpleHostBotDotNetAppId"; appSecret = "BffnSimpleHostBotDotNetAppSecret"; objectId = "BffnSimpleHostBotDotNetAppObjectId" }}, + @{ appName = "bffnsimplehostbotdotnet21"; variables = @{ appId = "BffnSimpleHostBotDotNet21AppId"; appSecret = "BffnSimpleHostBotDotNet21AppSecret"; objectId = "BffnSimpleHostBotDotNet21AppObjectId" }}, + @{ appName = "bffnechoskillbotdotnet"; variables = @{ appId = "BffnEchoSkillBotDotNetAppId"; appSecret = "BffnEchoSkillBotDotNetAppSecret"; objectId = "BffnEchoSkillBotDotNetAppObjectId" }}, + @{ appName = "bffnechoskillbotdotnet21"; variables = @{ appId = "BffnEchoSkillBotDotNet21AppId"; appSecret = "BffnEchoSkillBotDotNet21AppSecret"; objectId = "BffnEchoSkillBotDotNet21AppObjectId" }}, + @{ appName = "bffnechoskillbotdotnetv3"; variables = @{ appId = "BffnEchoSkillBotDotNetV3AppId"; appSecret = "BffnEchoSkillBotDotNetV3AppSecret"; objectId = "BffnEchoSkillBotDotNetV3AppObjectId" }}, + @{ appName = "bffnsimplehostbotcomposerdotnet"; variables = @{ appId = "BffnSimpleHostBotComposerDotNetAppId"; appSecret = "BffnSimpleHostBotComposerDotNetAppSecret"; objectId = "BffnSimpleHostBotComposerDotNetAppObjectId" }}, + @{ appName = "bffnechoskillbotcomposerdotnet"; variables = @{ appId = "BffnEchoSkillBotComposerDotNetAppId"; appSecret = "BffnEchoSkillBotComposerDotNetAppSecret"; objectId = "BffnEchoSkillBotComposerDotNetAppObjectId" }}, + @{ appName = "bffnwaterfallhostbotdotnet"; variables = @{ appId = "BffnWaterfallHostBotDotNetAppId"; appSecret = "BffnWaterfallHostBotDotNetAppSecret"; objectId = "BffnWaterfallHostBotDotNetAppObjectId" }}, + @{ appName = "bffnwaterfallskillbotdotnet"; variables = @{ appId = "BffnWaterfallSkillBotDotNetAppId"; appSecret = "BffnWaterfallSkillBotDotNetAppSecret"; objectId = "BffnWaterfallSkillBotDotNetAppObjectId" }}, + @{ appName = "bffnsimplehostbotjs"; variables = @{ appId = "BffnSimpleHostBotJSAppId"; appSecret = "BffnSimpleHostBotJSAppSecret"; objectId = "BffnSimpleHostBotJSAppObjectId" }}, + @{ appName = "bffnechoskillbotjs"; variables = @{ appId = "BffnEchoSkillBotJSAppId"; appSecret = "BffnEchoSkillBotJSAppSecret"; objectId = "BffnEchoSkillBotJSAppObjectId" }}, + @{ appName = "bffnechoskillbotjsv3"; variables = @{ appId = "BffnEchoSkillBotJSV3AppId"; appSecret = "BffnEchoSkillBotJSV3AppSecret"; objectId = "BffnEchoSkillBotJSV3AppObjectId" }}, + @{ appName = "bffnwaterfallhostbotjs"; variables = @{ appId = "BffnWaterfallHostBotJSAppId"; appSecret = "BffnWaterfallHostBotJSAppSecret"; objectId = "BffnWaterfallHostBotJSAppObjectId" }}, + @{ appName = "bffnwaterfallskillbotjs"; variables = @{ appId = "BffnWaterfallSkillBotJSAppId"; appSecret = "BffnWaterfallSkillBotJSAppSecret"; objectId = "BffnWaterfallSkillBotJSAppObjectId" }}, + @{ appName = "bffnsimplehostbotpython"; variables = @{ appId = "BffnSimpleHostBotPythonAppId"; appSecret = "BffnSimpleHostBotPythonAppSecret"; objectId = "BffnSimpleHostBotPythonAppObjectId" }}, + @{ appName = "bffnechoskillbotpython"; variables = @{ appId = "BffnEchoSkillBotPythonAppId"; appSecret = "BffnEchoSkillBotPythonAppSecret"; objectId = "BffnEchoSkillBotPythonAppObjectId" }}, + @{ appName = "bffnwaterfallhostbotpython"; variables = @{ appId = "BffnWaterfallHostBotPythonAppId"; appSecret = "BffnWaterfallHostBotPythonAppSecret"; objectId = "BffnWaterfallHostBotPythonAppObjectId" }}, + @{ appName = "bffnwaterfallskillbotpython"; variables = @{ appId = "BffnWaterfallSkillBotPythonAppId"; appSecret = "BffnWaterfallSkillBotPythonAppSecret"; objectId = "BffnWaterfallSkillBotPythonAppObjectId" }} + ) + + $token = GetToken + + foreach ($bot in $bots) { + $botName = "$($bot.appName)${{ parameters.resourceSuffix }}" + Write-Host "`n[$botName] Starting" + Write-Host "Creating App Registration ..." + + $app = CreateAppRegistration $token $botName + Write-Host " + App Registration: + Name: $botName + Variables: + $($bot.variables.objectId): $($app.id) + $($bot.variables.appId): $($app.appId) + " + + Write-Host "Storing App Registration into the KeyVault (${{ parameters.keyVault }}) ..." + SaveAppRegistrationIntoKeyVault "${{ parameters.keyVault }}" $bot.variables $app + Write-Host "[$botName] Ending" + } diff --git a/tests/functional/build/yaml/sharedResources/createSharedResources.yml b/tests/functional/build/yaml/sharedResources/createSharedResources.yml new file mode 100644 index 0000000000..cae6a936aa --- /dev/null +++ b/tests/functional/build/yaml/sharedResources/createSharedResources.yml @@ -0,0 +1,172 @@ +# +# Creates the shared resources needed for the Skills Functional Tests. +# + +name: $(BUILD.BUILDID) +trigger: none +pr: none + +pool: + vmImage: "windows-2019" + +variables: + ## Azure Resources (Define these variables in Azure) + # AzureSubscription: Service Connection Name to Manage Azure resources. + # KeyVaultObjectId: Suscription's Object Id to create the keyvault to store App Registrations. + # AppServicePlanPricingTier: (optional) Pricing Tier for App Service Plans, default F1. + # ResourceGroupName: (optional) Name of the Resource Group for the shared resources. + # ResourceSuffix: (optional) Suffix to add to the resources' name to avoid collitions. + + ## Internal variables + InternalAppInsightsName: "bffnappinsights$($env:RESOURCESUFFIX)" + InternalAppServicePlanDotNetName: "bffnbotsappservicedotnet$($env:RESOURCESUFFIX)" + InternalAppServicePlanJSName: "bffnbotsappservicejs$($env:RESOURCESUFFIX)" + InternalAppServicePlanPythonName: "bffnbotsappservicepython$($env:RESOURCESUFFIX)" + InternalCosmosDBName: "bffnbotstatedb$($env:RESOURCESUFFIX)" + InternalKeyVaultName: "bffnbotkeyvault$($env:RESOURCESUFFIX)" + InternalResourceGroupName: $[coalesce(variables['RESOURCEGROUPNAME'], 'bffnshared')] + +stages: +- stage: Create_Resource_Group_Windows + displayName: "Create Resource Group (Windows)" + jobs: + - job: Create_Resource_Group_Windows + displayName: "Create steps" + steps: + - checkout: none + - task: AzureCLI@2 + displayName: "Create $(INTERNALRESOURCEGROUPNAME)" + inputs: + azureSubscription: $(AZURESUBSCRIPTION) + scriptType: pscore + scriptLocation: inlineScript + inlineScript: "az group create --name $(INTERNALRESOURCEGROUPNAME) --location westus" + +- stage: Create_Resource_Group_Linux + displayName: "Create Resource Group (Linux)" + dependsOn: [] + jobs: + - job: Create_Resource_Group_Linux + displayName: "Create steps" + steps: + - checkout: none + - task: AzureCLI@2 + displayName: "Create $(INTERNALRESOURCEGROUPNAME)-linux" + inputs: + azureSubscription: $(AZURESUBSCRIPTION) + scriptType: pscore + scriptLocation: inlineScript + inlineScript: "az group create --name $(INTERNALRESOURCEGROUPNAME)-linux --location westus" + +- stage: Create_CosmosDB + displayName: "Create CosmosDB" + dependsOn: Create_Resource_Group_Windows + jobs: + - job: Deploy_Cosmos_DB + displayName: "Deploy steps" + steps: + - task: AzureCLI@2 + displayName: "Deploy CosmosDB" + inputs: + azureSubscription: $(AZURESUBSCRIPTION) + scriptType: pscore + scriptLocation: inlineScript + inlineScript: "az deployment group create --name $(INTERNALCOSMOSDBNAME) --resource-group $(INTERNALRESOURCEGROUPNAME) --template-file build/templates/template-cosmosdb-resources.json --parameters accountName=$(INTERNALCOSMOSDBNAME) databaseName=$(INTERNALCOSMOSDBNAME)" + +- stage: Create_Key_Vault + displayName: "Create Key Vault" + dependsOn: Create_Resource_Group_Windows + jobs: + - job: Deploy_Key_Vault + displayName: "Deploy steps" + steps: + - task: AzureCLI@2 + displayName: "Deploy Key Vault" + inputs: + azureSubscription: $(AZURESUBSCRIPTION) + scriptType: pscore + scriptLocation: inlineScript + inlineScript: "az deployment group create --name $(INTERNALKEYVAULTNAME) --resource-group $(INTERNALRESOURCEGROUPNAME) --template-file build/templates/template-key-vault-resources.json --parameters keyVaultName=$(INTERNALKEYVAULTNAME) objectId=$(KEYVAULTOBJECTID)" + +- stage: Create_App_Registrations + displayName: "Create App Registrations" + dependsOn: Create_Key_Vault + jobs: + - job: Create_App_Registrations + displayName: "Create steps" + steps: + - checkout: none + - template: createAppRegistrations.yml + parameters: + azureSubscription: $(AZURESUBSCRIPTION) + keyVault: "$(INTERNALKEYVAULTNAME)" + resourceSuffix: $env:RESOURCESUFFIX + servicePrincipalId: $env:SERVICEPRINCIPALID + servicePrincipalKey: $env:SERVICEPRINCIPALKEY + tenantId: $env:TENANTID + +- stage: Create_App_Service_Plan_DotNet + displayName: "Create App Service Plan (DotNet)" + dependsOn: Create_Resource_Group_Windows + jobs: + - job: Deploy_App_Service_Plan_DotNet + displayName: "Deploy steps" + steps: + - task: AzureCLI@2 + displayName: "Deploy App Service Plan (DotNet)" + inputs: + azureSubscription: $(AZURESUBSCRIPTION) + scriptType: pscore + scriptLocation: inlineScript + inlineScript: | + $appServicePlanPricingTier = if ($env:APPSERVICEPLANPRICINGTIER) { "newAppServicePlanSku=$env:APPSERVICEPLANPRICINGTIER" } + az deployment group create --name "$(INTERNALAPPSERVICEPLANDOTNETNAME)" --resource-group "$(INTERNALRESOURCEGROUPNAME)" --template-file build/templates/template-service-plan-windows-resources.json --parameters $appServicePlanPricingTier newAppServicePlanName="$(INTERNALAPPSERVICEPLANDOTNETNAME)" + +- stage: Create_App_Service_Plan_JS + displayName: "Create App Service Plan (JS)" + dependsOn: Create_Resource_Group_Windows + jobs: + - job: Deploy_App_Service_Plan_JS + displayName: "Deploy steps" + steps: + - task: AzureCLI@2 + displayName: "Deploy App Service Plan (JS)" + inputs: + azureSubscription: $(AZURESUBSCRIPTION) + scriptType: pscore + scriptLocation: inlineScript + inlineScript: | + $appServicePlanPricingTier = if ($env:APPSERVICEPLANPRICINGTIER) { "newAppServicePlanSku=$env:APPSERVICEPLANPRICINGTIER" } + az deployment group create --name "$(INTERNALAPPSERVICEPLANJSNAME)" --resource-group "$(INTERNALRESOURCEGROUPNAME)" --template-file build/templates/template-service-plan-windows-resources.json --parameters $appServicePlanPricingTier newAppServicePlanName="$(INTERNALAPPSERVICEPLANJSNAME)" + +- stage: Create_App_Service_Plan_Python + displayName: "Create App Service Plan (Python)" + dependsOn: Create_Resource_Group_Linux + jobs: + - job: Deploy_App_Service_Plan_Python + displayName: "Deploy steps" + steps: + - task: AzureCLI@2 + displayName: "Deploy App Service Plan (Python)" + inputs: + azureSubscription: $(AZURESUBSCRIPTION) + scriptType: pscore + scriptLocation: inlineScript + inlineScript: | + $appServicePlanPricingTier = if ($env:APPSERVICEPLANPRICINGTIER) { "newAppServicePlanSku=$env:APPSERVICEPLANPRICINGTIER" } + az deployment group create --name "$(INTERNALAPPSERVICEPLANPYTHONNAME)" --resource-group "$(INTERNALRESOURCEGROUPNAME)-linux" --template-file build/templates/template-service-plan-linux-resources.json --parameters $appServicePlanPricingTier newAppServicePlanName="$(INTERNALAPPSERVICEPLANPYTHONNAME)" + +- stage: Create_App_Insights + displayName: "Create App Insights" + dependsOn: Create_Resource_Group_Windows + jobs: + - job: Deploy_App_Insights + displayName: "Deploy steps" + steps: + - task: AzureCLI@2 + displayName: "Deploy App Insights" + inputs: + azureSubscription: $(AZURESUBSCRIPTION) + scriptType: pscore + scriptLocation: inlineScript + inlineScript: "az deployment group create --name $(INTERNALAPPINSIGHTSNAME) --resource-group $(INTERNALRESOURCEGROUPNAME) --template-file build/templates/template-app-insights-resources.json --parameters appInsightsName=$(INTERNALAPPINSIGHTSNAME)" diff --git a/tests/functional/build/yaml/testScenarios/configureConsumers.yml b/tests/functional/build/yaml/testScenarios/configureConsumers.yml new file mode 100644 index 0000000000..0cf5eadbee --- /dev/null +++ b/tests/functional/build/yaml/testScenarios/configureConsumers.yml @@ -0,0 +1,618 @@ +parameters: +- name: appIds + displayName: Bot's App Registration Ids + type: object + default: + EchoSkillBotComposerDotNet: "" + EchoSkillBotDotNet: "" + EchoSkillBotDotNet21: "" + EchoSkillBotDotNetV3: "" + EchoSkillBotJS: "" + EchoSkillBotJSV3: "" + EchoSkillBotPython: "" + WaterfallSkillBotDotNet: "" + WaterfallSkillBotJS: "" + WaterfallSkillBotPython: "" + +- name: azureSubscription + displayName: Azure Service Connection + type: string + +- name: keyVault + displayName: KeyVault name + type: string + +- name: resourceGroup + displayName: Resource Group name + type: string + +- name: resourceSuffix + displayName: Azure resources' name suffix + type: string + +- name: scenario + displayName: Test scenario + type: object + +steps: + - task: AzureCLI@2 + displayName: "Set Consumers AppSettings" + inputs: + azureSubscription: ${{ parameters.azureSubscription }} + scriptType: pscore + scriptLocation: inlineScript + failOnStderr: true + inlineScript: | + # Global Variables + $scenario = "${{ parameters.scenario }}"; + $resourceGroup = "${{ parameters.resourceGroup }}"; + $keyVault = "${{ parameters.keyVault }}"; + $suffix = "${{ parameters.resourceSuffix }}"; + + # Helper Functions. + + $noBotsFoundMessage = "No bots were found in the configuration."; + + function AddTimeStamp { + param($text) + return "$("[{0:MM/dd/yy} {0:HH:mm:ss}]" -f (Get-Date)): $text"; + } + + function AddBotsSuffix { + param($bots, $suffix) + # Add a suffix for each bot. + + if (-not $bots) { + Write-Host $(AddTimeStamp -text $noBotsFoundMessage); + return $bots; + } + + return $bots | ForEach-Object { + $bot = $_; + $bot.resourceBotName = $bot.botName + $suffix; + return $bot; + } + } + + function AddBotsAppIdFromKeyVault { + param($bots, $keyVault) + # Load AppIds from KeyVault. + + if (-not $bots) { + Write-Host $(AddTimeStamp -text $noBotsFoundMessage); + return $bots; + } + + $AddTimeStampDef = $function:AddTimeStamp.ToString(); + + return $bots | ForEach-Object -Parallel { + $bot = $_; + $function:AddTimeStamp = $using:AddTimeStampDef + $keyVault = $using:keyVault + + if ([string]::IsNullOrEmpty($bot.appId)) { + Write-Host $(AddTimeStamp -text "$($bot.key): Unable to find the AppId in the Pipeline Variables, proceeding to search in the KeyVault '$keyVault'."); + + $entry = az keyvault secret list --vault-name $keyVault --query "[?name == 'Bffn$($bot.key)AppId']" | ConvertFrom-Json; + + if ($entry) { + $secretVault = az keyvault secret show --id $entry.id | ConvertFrom-Json; + $bot.appId = $secretVault.value; + } + else { + Write-Host $(AddTimeStamp -text "$($bot.key): Unable to find the AppId in the KeyVault '$keyVault'."); + } + } + else { + Write-Host $(AddTimeStamp -text "$($bot.key): Using AppId from the Pipeline Variable."); + } + + return $bot; + } + } + + function FilterBotsByScenario { + param($bots, $scenarios, $scenario) + # Filter bots by a specific test scenario. + + if (-not $bots) { + Write-Host $(AddTimeStamp -text $noBotsFoundMessage); + return $bots; + } + + $scenarioSelected = $scenarios | Where-Object { $_.name -eq $scenario } + + if (-not $scenarioSelected) { + Write-Host $(AddTimeStamp -text "$($scenario): Unable to find the Test Scenario."); + return @(); + } + + return $bots | Where-Object { + $bot = $_; + + $scenarioBots = $scenarioSelected.consumers + $scenarioSelected.skills; + return $scenarioBots -contains $bot.key; + } + } + + function FilterResourceGroupsByExistence { + param($groups) + # Filter created resource groups. + + $AddTimeStampDef = $function:AddTimeStamp.ToString(); + + return $groups.GetEnumerator() | ForEach-Object -Parallel { + $function:AddTimeStamp = $using:AddTimeStampDef + $group = $_; + + $exists = (az group exists -n $group.Value) -eq "true"; + if ($exists) { + Write-Host $(AddTimeStamp -text "$($group.Value): Resource Group found."); + return $group; + } + else { + Write-Host $(AddTimeStamp -text "$($group.Value): Unable to find the Resource Group."); + } + } + } + + function FilterBotsByResourceExistence { + param($groups, $bots) + # Filter bots only if their resource exists in Azure. + + if (-not $bots) { + Write-Host $(AddTimeStamp -text $noBotsFoundMessage); + return $bots; + } + + $AddTimeStampDef = $function:AddTimeStamp.ToString(); + + return $bots | ForEach-Object -Parallel { + $groups = $using:groups + $function:AddTimeStamp = $using:AddTimeStampDef + $bot = $_; + + if ($groups.Value -contains $bot.resourceGroup) { + $enabled = (az webapp show --name $bot.resourceBotName --resource-group $bot.resourceGroup 2>$null | ConvertFrom-Json).enabled; + + if ($enabled) { + Write-Host $(AddTimeStamp -text "$($bot.key): Resource '$($bot.resourceBotName)' found."); + return $bot; + } + else { + Write-Host $(AddTimeStamp -text "$($bot.key): Unable to find the resource '$($bot.resourceBotName)'."); + } + } + }; + } + + function FilterBotsWithAppId { + param($bots) + # Filter bots that have an AppId. + + if (-not $bots) { + Write-Host $(AddTimeStamp -text $noBotsFoundMessage); + return $bots; + } + + return @($bots | Where-Object { + $bot = $_; + + if ($bot.appId.Trim().Length -eq 0) { + Write-Host $(AddTimeStamp -text "$($bot.key): AppId not found in the configuration, Skiping ..."); + return $false; + } + + return $true; + }) + } + + function AddAzureAppSettings { + param($consumers, $skills) + # Add Azure AppSettings to each Consumer. + + if (-not $consumers) { + Write-Host $(AddTimeStamp -text $noBotsFoundMessage); + return $consumers; + } + + return $consumers | Sort-Object { $_.key } | ForEach-Object { + $consumer = $_; + $consumer.appSettings = @( + @{name = "SkillHostEndpoint"; value = "https://$($consumer.resourceBotName).azurewebsites.net/api/skills" }; + ); + + $orderedSkills = $skills | Sort-Object { $_.key } + + for ($index = 0; $index -lt $orderedSkills.Count; $index++) { + $skill = $orderedSkills[$index]; + + switch ($consumer.configType) { + $types.Appsettings { + $consumer.appSettings += @{name = "BotFrameworkSkills:$($index):Id"; value = "$($skill.key)" }; + $consumer.appSettings += @{name = "BotFrameworkSkills:$($index):AppId"; value = "$($skill.appId)" }; + $consumer.appSettings += @{name = "BotFrameworkSkills:$($index):SkillEndpoint"; value = "https://$($skill.resourceBotName).azurewebsites.net/api/messages" }; + $consumer.appSettings += @{name = "BotFrameworkSkills:$($index):Group"; value = "$($skill.group)" }; + } + $types.Env { + $consumer.appSettings += @{name = "skill_$($skill.key)_appId"; value = "$($skill.appId)" }; + $consumer.appSettings += @{name = "skill_$($skill.key)_endpoint"; value = "https://$($skill.resourceBotName).azurewebsites.net/api/messages" }; + $consumer.appSettings += @{name = "skill_$($skill.key)_group"; value = "$($skill.group)" }; + } + $types.Composer { + # NOTE: Composer uses different capitalization for the skill keys. + $consumer.appSettings += @{name = "skill__$($skill.keyComposer)__msAppId"; value = "$($skill.appId)" }; + $consumer.appSettings += @{name = "skill__$($skill.keyComposer)__endpointUrl"; value = "https://$($skill.resourceBotName).azurewebsites.net/api/messages" }; + } + } + } + + return $consumer; + } + } + + function ConfigureTestProjectAppSettings { + param($bots, $appSettingsPath) + # Save each bot direct line into the Test Project AppSettings file. + + if (-not $bots) { + Write-Host $(AddTimeStamp -text $noBotsFoundMessage); + return $bots; + } + + $appSettings = Get-Content -Raw $appSettingsPath | ConvertFrom-Json; + $appSettings.HostBotClientOptions = @{} + + $AddTimeStampDef = $function:AddTimeStamp.ToString(); + + $bots | ForEach-Object -Parallel { + # Gets the Bot DirectLine + $function:AddTimeStamp = $using:AddTimeStampDef + $appSettings = $using:appSettings + $bot = $_; + + Write-Host $(AddTimeStamp -text "$($bot.key): Getting the DirectLine secret."); + $tries = 3; + $directLine = ""; + + while($tries -gt 0) { + $directLine = (az bot directline show --name $bot.resourceBotName --resource-group $bot.resourceGroup --with-secrets true 2>$null | ConvertFrom-Json).properties.properties.sites.key; + if (-not [string]::IsNullOrEmpty($directLine)) { + $appSettings.HostBotClientOptions[$bot.key] = @{ + DirectLineSecret = $directLine + BotId = $bot.botName + } + break; + } + $tries--; + } + } + + if ($appSettings.HostBotClientOptions.count -ne $bots.length) { + Write-Host "##vso[task.logissue type=error]Some host bots' DirectLine secrets couldn't be retrieved from Azure." + $config = $appSettings.HostBotClientOptions | Out-String + Write-Host "##vso[task.logissue type=error]$config" + exit 1 # Force exit + } + + $appSettings | ConvertTo-Json | Set-Content $appsettingsPath; + + Write-Host $(AddTimeStamp -text "Test Project AppSettings saved:"); + $appSettings.HostBotClientOptions; + Write-Host ""; # Separator + } + + function ConfigureConsumers { + param($consumers, $skills) + # Configure Consumers with all the Skills to connect to. + + $AddTimeStampDef = $function:AddTimeStamp.ToString(); + + Write-Host $(AddTimeStamp -text "Waiting for configuration to finish ..."); + $consumers | ForEach-Object -Parallel { + $function:AddTimeStamp = $using:AddTimeStampDef + $skills = $using:skills + $types = $using:types + + $consumer = $_; + $output = @(); + + $conditions = @( + "SkillHostEndpoint" + "BotFrameworkSkills*" + "skill_*" + ) + + $output += AddTimeStamp -text "$($consumer.key): Looking for existing Azure AppSettings ..."; + + $json = (az webapp config appsettings list --name $consumer.resourceBotName --resource-group $consumer.resourceGroup) | ConvertFrom-Json + $appSettings = @($json | Where-Object { $_.name -match ($conditions -join "|") }) + + $settings = @{ + toSet = [System.Collections.ArrayList]$consumer.appSettings; + toRemove = [System.Collections.ArrayList]@(); + } + + # Lookup for Azure AppSettings that are needed to be added/updated, otherwise, skip. + foreach ($appSetting in $appSettings) { + $setting = $settings.toSet | Where-Object { $_.name -eq $appSetting.name } | Select-Object -Unique + if ($setting) { + if ($setting.value -eq $appSetting.value) { + $settings.toSet.Remove($setting); + } + } + else { + $settings.toRemove.Add($appSetting); + } + } + + if ($settings.toRemove) { + $output += AddTimeStamp -text "$($consumer.key): Removing unnecessary Azure AppSettings ..."; + + $config = $settings.toRemove | ForEach-Object { $_.name } + az webapp config appsettings delete --name $consumer.resourceBotName --resource-group $consumer.resourceGroup --setting-names $config --output none + + $output += AddTimeStamp -text "$($consumer.key): Azure AppSettings removed:"; + $output += $config | ForEach-Object { [PSCustomObject]@{ Name = $_ } } | Format-Table -AutoSize; + } + + if ($settings.toSet) { + $output += AddTimeStamp -text "$($consumer.key): Adding new Azure AppSettings ..."; + + $config = $settings.toSet | ForEach-Object { "$($_.name)=$($_.value)" } + az webapp config appsettings set --name $consumer.resourceBotName --resource-group $consumer.resourceGroup --settings $config --output none + + $output += AddTimeStamp -text "$($consumer.key): Azure AppSettings added:"; + # Format output + $output += $settings.toSet | ForEach-Object { + $setting = $_; + + if ($setting.name.ToLower().EndsWith("appid")) { + $setting.value = $setting.value.Substring(0, 3) + "***" + } + + return [PSCustomObject]@{ + Name = $setting.name + Value = $setting.value + } + } | Format-Table -AutoSize + } + + if (-not $settings.toSet -and -not $settings.toRemove) { + $output += AddTimeStamp -text "$($consumer.key): Azure AppSettings are up to date."; + } + + $output; + } + } + + # Configuration + + # Type of setting to use for the AppSettings variables. + $types = @{ + Appsettings = 0 + Env = 1 + Composer = 2 + } + + # Bots Resource Groups + $groups = @{ + DotNet = "$resourceGroup-DotNet" + JS = "$resourceGroup-JS" + Python = "$resourceGroup-Python" + } + + # Bots Settings + $consumers = @( + @{ + key = "SimpleHostBotDotNet" + botName = "bffnsimplehostbotdotnet" + resourceGroup = $groups.DotNet + configType = $types.Appsettings + } + @{ + key = "SimpleHostBotDotNet21" + botName = "bffnsimplehostbotdotnet21" + resourceGroup = $groups.DotNet + configType = $types.Appsettings + } + @{ + key = "SimpleHostBotComposerDotNet" + botName = "bffnsimplehostbotcomposerdotnet" + resourceGroup = $groups.DotNet + configType = $types.Composer + } + @{ + key = "WaterfallHostBotDotNet" + botName = "bffnwaterfallhostbotdotnet" + resourceGroup = $groups.DotNet + configType = $types.Appsettings + } + @{ + key = "SimpleHostBotJS" + botName = "bffnsimplehostbotjs" + resourceGroup = $groups.JS + configType = $types.Env + } + @{ + key = "WaterfallHostBotJS" + botName = "bffnwaterfallhostbotjs" + resourceGroup = $groups.JS + configType = $types.Env + } + @{ + key = "SimpleHostBotPython" + botName = "bffnsimplehostbotpython" + resourceGroup = $groups.Python + configType = $types.Env + } + @{ + key = "WaterfallHostBotPython" + botName = "bffnwaterfallhostbotpython" + resourceGroup = $groups.Python + configType = $types.Env + } + ) + + $skills = @( + @{ + key = "EchoSkillBotDotNet" + keyComposer = "echoSkillBotDotNet" + botName = "bffnechoskillbotdotnet" + appId = "${{ parameters.appIds.EchoSkillBotDotNet }}" + resourceGroup = $groups.DotNet + group = "Echo" + } + @{ + key = "EchoSkillBotDotNet21" + keyComposer = "echoSkillBotDotNet21" + botName = "bffnechoskillbotdotnet21" + appId = "${{ parameters.appIds.EchoSkillBotDotNet21 }}" + resourceGroup = $groups.DotNet + group = "Echo" + } + @{ + key = "EchoSkillBotDotNetV3" + keyComposer = "echoSkillBotDotNetV3" + botName = "bffnechoskillbotdotnetv3" + appId = "${{ parameters.appIds.EchoSkillBotDotNetV3 }}" + resourceGroup = $groups.DotNet + group = "Echo" + } + @{ + key = "EchoSkillBotComposerDotNet" + keyComposer = "echoSkillBotComposerDotNet" + botName = "bffnechoskillbotcomposerdotnet" + appId = "${{ parameters.appIds.EchoSkillBotComposerDotNet }}" + resourceGroup = $groups.DotNet + group = "Echo" + } + @{ + key = "WaterfallSkillBotDotNet" + keyComposer = "waterfallSkillBotDotNet" + botName = "bffnwaterfallskillbotdotnet" + appId = "${{ parameters.appIds.WaterfallSkillBotDotNet }}" + resourceGroup = $groups.DotNet + group = "Waterfall" + } + @{ + key = "EchoSkillBotJS" + keyComposer = "echoSkillBotJs" + botName = "bffnechoskillbotjs" + appId = "${{ parameters.appIds.EchoSkillBotJS }}" + resourceGroup = $groups.JS + group = "Echo" + } + @{ + key = "EchoSkillBotJSV3" + keyComposer = "echoSkillBotJsV3" + botName = "bffnechoskillbotjsv3" + appId = "${{ parameters.appIds.EchoSkillBotJSV3 }}" + resourceGroup = $groups.JS + group = "Echo" + } + @{ + key = "WaterfallSkillBotJS" + keyComposer = "waterfallSkillBotJS" + botName = "bffnwaterfallskillbotjs" + appId = "${{ parameters.appIds.WaterfallSkillBotJS }}" + resourceGroup = $groups.JS + group = "Waterfall" + } + @{ + key = "EchoSkillBotPython" + keyComposer = "echoSkillBotPython" + botName = "bffnechoskillbotpython" + appId = "${{ parameters.appIds.EchoSkillBotPython }}" + resourceGroup = $groups.Python + group = "Echo" + } + @{ + key = "WaterfallSkillBotPython" + keyComposer = "waterfallSkillBotPython" + botName = "bffnwaterfallskillbotpython" + appId = "${{ parameters.appIds.WaterfallSkillBotPython }}" + resourceGroup = $groups.Python + group = "Waterfall" + } + ) + + # Bots Test Scenarios + $scenarios = @( + @{ + name = "SingleTurn"; + consumers = @( + "SimpleHostBotComposerDotNet", + "SimpleHostBotDotNet", + "SimpleHostBotDotNet21", + "SimpleHostBotJS", + "SimpleHostBotPython" + ); + skills = @( + "EchoSkillBotComposerDotNet", + "EchoSkillBotDotNet", + "EchoSkillBotDotNet21", + "EchoSkillBotDotNetV3", + "EchoSkillBotJS", + "EchoSkillBotJSV3", + "EchoSkillBotPython" + ); + } + @{ + name = "Waterfall"; + consumers = @( + "WaterfallHostBotDotNet", + "WaterfallHostBotJS", + "WaterfallHostBotPython" + ); + skills = @( + "WaterfallSkillBotDotNet", + "WaterfallSkillBotJS", + "WaterfallSkillBotPython" + ); + } + ) + + # Pre-configure and filter bots. + Write-Host $(AddTimeStamp -text "Filtering bots by '$scenario' scenario ..."); + $consumersToConfigure = FilterBotsByScenario -bots $consumers -scenarios $scenarios -scenario $scenario; + $skillsToConfigure = FilterBotsByScenario -bots $skills -scenarios $scenarios -scenario $scenario; + + Write-Host $(AddTimeStamp -text "Loading the Skills AppIds from the KeyVault '$keyVault' when no Pipeline Variable is provided."); + $skillsToConfigure = AddBotsAppIdFromKeyVault -bots $skillsToConfigure -keyVault $keyVault + + Write-Host $(AddTimeStamp -text "Filtering bots that have an AppId assigned ..."); + $skillsToConfigure = FilterBotsWithAppId -bots $skillsToConfigure + + Write-Host $(AddTimeStamp -text "Adding the suffix '$suffix' to the bot resources ..."); + $consumersToConfigure = AddBotsSuffix -bots $consumersToConfigure -suffix $suffix + $skillsToConfigure = AddBotsSuffix -bots $skillsToConfigure -suffix $suffix + + Write-Host $(AddTimeStamp -text "Filtering existing Resource Groups ..."); + $resourceGroups = FilterResourceGroupsByExistence -groups $groups + + Write-Host $(AddTimeStamp -text "Filtering deployed bots in Azure ..."); + $consumersToConfigure = FilterBotsByResourceExistence -groups $resourceGroups -bots $consumersToConfigure + $skillsToConfigure = FilterBotsByResourceExistence -groups $resourceGroups -bots $skillsToConfigure + + Write-Host $(AddTimeStamp -text "Adding Azure AppSettings to Consumers' configuration."); + $consumersToConfigure = AddAzureAppSettings -consumers $consumersToConfigure -skills $skillsToConfigure + + if (-not $consumersToConfigure) { + Write-Error $(AddTimeStamp -text "No Consumers were found to configure. Cancelling the configuration ..."); + return; + } + + if (-not $skillsToConfigure) { + Write-Error $(AddTimeStamp -text "No Skills were found to configure each Consumer. Cancelling the configuration ..."); + return; + } + + # Configure steps. + Write-Host $(AddTimeStamp -text "Configuring the Test Project."); + ConfigureTestProjectAppSettings -bots $consumersToConfigure -appSettingsPath "tests/SkillFunctionalTests/appsettings.json"; + + Write-Host $(AddTimeStamp -text "Configuring the Consumer bots App Settings in Azure."); + ConfigureConsumers -consumers $consumersToConfigure -skills $skillsToConfigure + + Write-Host $(AddTimeStamp -text "Process Finished!"); diff --git a/tests/functional/build/yaml/testScenarios/runScenario.yml b/tests/functional/build/yaml/testScenarios/runScenario.yml new file mode 100644 index 0000000000..bfa72bd232 --- /dev/null +++ b/tests/functional/build/yaml/testScenarios/runScenario.yml @@ -0,0 +1,83 @@ +parameters: +- name: appIds + displayName: Bot's App Registration Ids + type: object + default: + EchoSkillBotComposerDotNet: "" + EchoSkillBotDotNet: "" + EchoSkillBotDotNet21: "" + EchoSkillBotDotNetV3: "" + EchoSkillBotJS: "" + EchoSkillBotJSV3: "" + EchoSkillBotPython: "" + WaterfallSkillBotDotNet: "" + WaterfallSkillBotJS: "" + WaterfallSkillBotPython: "" + +- name: azureSubscription + displayName: Azure Service Connection + type: string + +- name: buildConfiguration + displayName: Build Configuration + type: string + +- name: buildIdSuffix + displayName: Build Id Suffix + type: string + +- name: keyVault + displayName: KeyVault name + type: string + +- name: resourceGroup + displayName: Resource Group name + type: string + +- name: resourceSuffix + displayName: Azure resources' name suffix + type: string + +- name: scenarios + displayName: Test Scenarios + type: object + +stages: + - ${{ each scenario in parameters.scenarios }}: + - stage: "${{ scenario.name }}" + displayName: "Test ${{ scenario.name }} Scenario" + dependsOn: "${{ scenario.dependsOn }}" + jobs: + - job: Test + variables: + BuildIdSuffix: "${{ parameters.buildIdSuffix }}" + steps: + - template: configureConsumers.yml + parameters: + appIds: "${{ parameters.appIds }}" + azureSubscription: "${{ parameters.azureSubscription }}" + keyVault: "${{ parameters.keyVault }}" + resourceGroup: "${{ parameters.resourceGroup }}" + resourceSuffix: "${{ parameters.resourceSuffix }}$(BUILDIDSUFFIX)" + scenario: "${{ scenario.name }}" + + - task: UseDotNet@2 + displayName: "Use .Net Core sdk 3.1.x" + inputs: + version: 3.1.x + + - task: DotNetCoreCLI@2 + displayName: "Build" + inputs: + command: build + publishWebProjects: false + projects: "Tests/SkillFunctionalTests/SkillFunctionalTests.csproj" + arguments: "-v n --configuration ${{ parameters.buildConfiguration }}" + + - task: DotNetCoreCLI@2 + displayName: "DotNet Test" + inputs: + command: test + testRunTitle: "SkillFunctionalTests-${{ scenario.name }}-$(BUILD.BUILDNUMBER)" + projects: "Tests/SkillFunctionalTests/SkillFunctionalTests.csproj" + arguments: "-v n --configuration ${{ parameters.buildConfiguration }} --no-build --no-restore --logger trx;LogFileName=SkillFunctionalTests-${{ scenario.name }}-$(BUILD.BUILDNUMBER).trx --filter TestCategory!=IgnoreInAutomatedBuild&TestCategory=${{ join('|TestCategory=', scenario.testCategories) }}" diff --git a/tests/functional/build/yaml/testScenarios/runTestScenarios.yml b/tests/functional/build/yaml/testScenarios/runTestScenarios.yml new file mode 100644 index 0000000000..ebb6a14b2d --- /dev/null +++ b/tests/functional/build/yaml/testScenarios/runTestScenarios.yml @@ -0,0 +1,102 @@ +# +# Executes the test scenarios. +# + +# "name" here defines the build number format. Build number is accessed via $(Build.BuildNumber) +name: $(BUILD.BUILDID) +trigger: none +pr: none + +variables: + BuildConfiguration: "Debug" + + ## Azure Resources (Define these variables in Azure) + # AzureSubscription: Service Connection Name to Manage Azure resources. + # ResourceGroup: (optional) Name of the Resource Group where the bots are deployed. + # ResourceSuffix: (optional) Suffix to add to the resources' name to avoid collitions. + + ## Bots Configuration (Define these variables in Azure) + # BffnEchoSkillBotComposerDotNetAppId: (optional) App Id for BffnEchoSkillBotComposerDotNet bot. + # BffnEchoSkillBotDotNet21AppId: (optional) App Id for BffnEchoSkillBotDotNet21 bot. + # BffnEchoSkillBotDotNetAppId: (optional) App Id for BffnEchoSkillBotDotNet bot. + # BffnEchoSkillBotDotNetV3AppId: (optional) App Id for BffnEchoSkillBotDotNetV3 bot. + # BffnEchoSkillBotJSAppId: (optional) App Id for BffnEchoSkillBotJS bot. + # BffnEchoSkillBotJSV3AppId: (optional) App Id for BffnEchoSkillBotJSV3 bot. + # BffnEchoSkillBotPythonAppId: (optional) App Id for BffnEchoSkillBotPython bot. + # BffnWaterfallSkillBotDotNetAppId: (optional) App Id for BffnWaterfallSkillBotDotNet bot. + # BffnWaterfallSkillBotJSAppId: (optional) App Id for BffnWaterfallSkillBotJS bot. + # BffnWaterfallSkillBotPythonAppId: (optional) App Id for BffnWaterfallSkillBotPython bot. + # DeployBotResourcesGuid: (optional) Deploy Bot Resources pipeline GUID. + + ## Internal variables + InternalKeyVaultName: "bffnbotkeyvault$(INTERNALRESOURCESUFFIX)" + InternalResourceGroupName: $[coalesce(variables['RESOURCEGROUP'], 'bffnbots')] + InternalResourceSuffix: $[coalesce(variables['RESOURCESUFFIX'], '')] + +pool: + vmImage: "windows-2019" + +stages: + - stage: "Download_Variables" + displayName: "Download Variables" + jobs: + - job: "Download_Variables" + displayName: "Download Variables" + steps: + - powershell: | + $pipelineGuid = if ([string]::IsNullOrEmpty("$env:DEPLOYBOTRESOURCESGUID")) { "02 - Deploy Bot Resources" } else { "$(DEPLOYBOTRESOURCESGUID)" } + Write-Host "Deploy Bot Resources Pipeline GUID: " $pipelineGuid + Write-Host "##vso[task.setvariable variable=PipelineGuid]$pipelineGuid" + displayName: "Set Deploy Bot Resources GUID" + + - task: DownloadPipelineArtifact@2 + displayName: "Download Variables artifact" + inputs: + source: "specific" + project: "$(SYSTEM.TEAMPROJECT)" + pipeline: "$(PIPELINEGUID)" + allowPartiallySucceededBuilds: true + allowFailedBuilds: true + artifact: "Variables" + path: "$(SYSTEM.DEFAULTWORKINGDIRECTORY)/Variables/" + + - powershell: | + $deploymentBuildSuffix = (Get-Content -Path "$(SYSTEM.DEFAULTWORKINGDIRECTORY)/Variables/variables.json" | ConvertFrom-Json).deploymentBuildSuffix + Write-Host "Deployment Build Suffix: " $deploymentBuildSuffix + Write-Host "##vso[task.setvariable variable=DeploymentBuildSuffix;isOutput=true]-$deploymentBuildSuffix" + displayName: "Set variables" + name: "Set_Variables" + + - template: runScenario.yml + parameters: + appIds: + EchoSkillBotComposerDotNet: "$(BFFNECHOSKILLBOTCOMPOSERDOTNETAPPID)" + EchoSkillBotDotNet: "$(BFFNECHOSKILLBOTDOTNETAPPID)" + EchoSkillBotDotNet21: "$(BFFNECHOSKILLBOTDOTNET21APPID)" + EchoSkillBotDotNetV3: "$(BFFNECHOSKILLBOTDOTNETV3APPID)" + EchoSkillBotJS: "$(BFFNECHOSKILLBOTJSAPPID)" + EchoSkillBotJSV3: "$(BFFNECHOSKILLBOTJSV3APPID)" + EchoSkillBotPython: "$(BFFNECHOSKILLBOTPYTHONAPPID)" + WaterfallSkillBotDotNet: "$(BFFNWATERFALLSKILLBOTDOTNETAPPID)" + WaterfallSkillBotJS: "$(BFFNWATERFALLSKILLBOTJSAPPID)" + WaterfallSkillBotPython: "$(BFFNWATERFALLSKILLBOTPYTHONAPPID)" + azureSubscription: "$(AZURESUBSCRIPTION)" + buildConfiguration: "$(BUILDCONFIGURATION)" + buildIdSuffix: $[stageDependencies.Download_Variables.Download_Variables.outputs['Set_Variables.DeploymentBuildSuffix']] + keyVault: "$(INTERNALKEYVAULTNAME)" + resourceGroup: "$(INTERNALRESOURCEGROUPNAME)" + resourceSuffix: "$(INTERNALRESOURCESUFFIX)" + scenarios: + - name: Waterfall + dependsOn: [Download_Variables] + testCategories: + - Attachments + - CardActions + - ProactiveMessages + - FileUpload + - SignIn + + - name: SingleTurn + dependsOn: [Download_Variables] + testCategories: + - SingleTurn diff --git a/tests/functional/package.json b/tests/functional/package.json new file mode 100644 index 0000000000..e42329bcdd --- /dev/null +++ b/tests/functional/package.json @@ -0,0 +1,15 @@ +{ + "name": "botframework-functional-tests", + "private": true, + "workspaces": { + "packages": [ + "Bots/JavaScript/*/*/*" + ] + }, + "devDependencies": { + "semistandard": "^16.0.0" + }, + "scripts": { + "lint": "semistandard" + } +} diff --git a/tests/functional/specs/TransciptTestRunner.md b/tests/functional/specs/TransciptTestRunner.md new file mode 100644 index 0000000000..595652c2b9 --- /dev/null +++ b/tests/functional/specs/TransciptTestRunner.md @@ -0,0 +1,59 @@ +# Transcript based test runner (DRAFT) + +## Summary + +Transcript based functional tests aim to allow us to test complex conversation flows against a bot and related skills end to end without having to worry about how the bot is implemented. + +## Contents + +- [Requirements](#requirements) +- [Design notes](#design-notes) +- [Implementation notes](#implementation-notes) +- [Other considerations and TODOs](#other-considerations-and-todos) + +## Requirements + +1. I can run functional tests from visual studio +2. I can run functional tests from the command line +3. I can run functional tests from a CI/CD pipeline in Azure DevOps +4. I can take a transcript created using the Emulator and run it against a deployed bot +5. I can emulate a channel from my tests (Emulator, Teams, WebChat) +6. I get clear messages describing where my test failed so I can debug it +7. I can debug a test step by step from the IDE +8. I should be able to use expressions in my expected bot responses to be able to deal with variable data +9. I should be able to run my tests against a bot running in localhost +10. We should write unit tests to make sure the runner classes and be easy to maintain + +## Design notes + +Here is a high level class diagram to get started + +![Class Diagram](media/TestRunnerClassDiagram2.png) + +- `TestRunner`: responsible for executing the `TestScript` using the desired `TestClientBase` +- `TranscriptConverter`: responsible for reading a transcript, converting it into a sequence of steps and returning the steps to be executed. Input: an emulator transcript file. Output: a "Test Script" file. +- `TestClientFactory`: creates an instance of a TestClient implementation. +- `TestClientBase`(abstract): base class for implementing channel specific test clients +- `_XYZ_Client`: specialized client that knows how to interact with a specific channel (it should be capable of generating channel OAuthTokens, handle responses coming from that specific channel, etc.) +- `ClientType` (enum): types are: DirectLine, Emulator, Teams + +## Implementation notes + +1. The first version of the runner will be in C# targeting DotNet Core 3.1 +2. We will rely on XUnit to write the tests but let's try to keep it out of the base classes (if possible) +3. The test runner should be able to load its settings from configuration using IConfiguration +4. The code should follow the SyleCop and FxCop ruleset used by the dotnet SDK +5. (Not a P0), we may be able to refactor some of the code in the test runner and make it part of the SDK testing package in the future + +## Other considerations and TODOs + +- Can we use Adaptive expressions to create asserts? +- Do we create a tool to convert a transcript into a test script that removes some of the noise in the transcript and makes test easier to read and write? +- Do we implement _XYZ_Options classes to configure the runner, the test script and the test client? +- Possible feature - chaining test scripts, consider we have a welcome part that we do over and over again and want to combine with the book a flight or get weather. The welcome portion and assertions can be written once and then we can append the other scenarios. + +## Test Script file + +It is generated by the TranscriptConverter from an Emulator transcript. The test script file contains the list of Activities found in its corresponding Emulator transcript with the following properties removed: +* All IDs +* All timestamps \ No newline at end of file diff --git a/tests/functional/specs/media/TestRunnerClassDiagram.png b/tests/functional/specs/media/TestRunnerClassDiagram.png new file mode 100644 index 0000000000..1f1f4a5d72 Binary files /dev/null and b/tests/functional/specs/media/TestRunnerClassDiagram.png differ diff --git a/tests/functional/specs/media/TestRunnerClassDiagram2.png b/tests/functional/specs/media/TestRunnerClassDiagram2.png new file mode 100644 index 0000000000..627aaf21bc Binary files /dev/null and b/tests/functional/specs/media/TestRunnerClassDiagram2.png differ diff --git a/tests/functional/specs/media/src/TestRunnerDiagrams.vsdx b/tests/functional/specs/media/src/TestRunnerDiagrams.vsdx new file mode 100644 index 0000000000..9755494c79 Binary files /dev/null and b/tests/functional/specs/media/src/TestRunnerDiagrams.vsdx differ diff --git a/tests/functional/yarn.lock b/tests/functional/yarn.lock new file mode 100644 index 0000000000..50dd5fb796 --- /dev/null +++ b/tests/functional/yarn.lock @@ -0,0 +1,2032 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 4 + cacheKey: 7 + +"@babel/code-frame@npm:^7.0.0": + version: 7.12.13 + resolution: "@babel/code-frame@npm:7.12.13" + dependencies: + "@babel/highlight": ^7.12.13 + checksum: 471532bb7cb4a300bd1a3201e75e7c0c83ebfb4e0e6610fdb53270521505d7efe0961258de61e7b1970ef3092a97ed675248ee1a44597912a1f61f903d85ef41 + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^7.12.11": + version: 7.12.11 + resolution: "@babel/helper-validator-identifier@npm:7.12.11" + checksum: 18de432203264b501db2690b53370a4289dc56084f5a2c66de624b159ee28b8abaeb402b2b7584296d9261645d91ddb6bfd21125d3ffd9bf02e9262e77baf3d2 + languageName: node + linkType: hard + +"@babel/highlight@npm:^7.12.13": + version: 7.13.10 + resolution: "@babel/highlight@npm:7.13.10" + dependencies: + "@babel/helper-validator-identifier": ^7.12.11 + chalk: ^2.0.0 + js-tokens: ^4.0.0 + checksum: 8f23d3b728422713bfab45bee1e7584f2a3d2e20c9c4d6153312b898c82e776bdc5b1b2afaf9433fddb21d70417f5b477c0bb1a48613324fd761117e19a5702b + languageName: node + linkType: hard + +"@eslint/eslintrc@npm:^0.2.1": + version: 0.2.2 + resolution: "@eslint/eslintrc@npm:0.2.2" + dependencies: + ajv: ^6.12.4 + debug: ^4.1.1 + espree: ^7.3.0 + globals: ^12.1.0 + ignore: ^4.0.6 + import-fresh: ^3.2.1 + js-yaml: ^3.13.1 + lodash: ^4.17.19 + minimatch: ^3.0.4 + strip-json-comments: ^3.1.1 + checksum: 049c98898934100aa5ad4912242ecbec136454787c00776a236e5321c73176dbc813917445a3d50d230a5a84531a99ec0e2885dd7a6838be68b46b6720fa1459 + languageName: node + linkType: hard + +"@types/json5@npm:^0.0.29": + version: 0.0.29 + resolution: "@types/json5@npm:0.0.29" + checksum: 66e9ac0143ec521522c7bb670301e9836ee886207eeed1aab6d4854a1b19b404ab3a54cd8d449f9b1f13acc357f540be96f8ac2d1e86e301eab52ae0f9a4066f + languageName: node + linkType: hard + +"acorn-jsx@npm:^5.3.1": + version: 5.3.1 + resolution: "acorn-jsx@npm:5.3.1" + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: 5925bc5d79a2821a8f7250b6de2b02bb86c0470dcb78cf68a603855291c5e50602b9eaf294aba2efbf3ee7063c80a9074b520b2330cc1aef80b849bfc7a041c0 + languageName: node + linkType: hard + +"acorn@npm:^7.4.0": + version: 7.4.1 + resolution: "acorn@npm:7.4.1" + bin: + acorn: bin/acorn + checksum: 2bde98c28c1be9a08e41e581179b776b43396c9486ce52b2b9848d73c53df38c516b7edba4bacdc84cabc9d7a3299f3b46ef240ae261c38dbf8ddd89f635bd32 + languageName: node + linkType: hard + +"ajv@npm:^6.10.0, ajv@npm:^6.10.2, ajv@npm:^6.12.4": + version: 6.12.6 + resolution: "ajv@npm:6.12.6" + dependencies: + fast-deep-equal: ^3.1.1 + fast-json-stable-stringify: ^2.0.0 + json-schema-traverse: ^0.4.1 + uri-js: ^4.2.2 + checksum: 19a8f3b0a06001eb68e6268f4f9f04424b32baadd5df6ba8292cd473e22e5f4019ed9ab17c3e3510394178ed8bef9b42ad0bdb5c675d65f042421a774780ce1a + languageName: node + linkType: hard + +"ansi-colors@npm:^4.1.1": + version: 4.1.1 + resolution: "ansi-colors@npm:4.1.1" + checksum: 50d8dfbce25602caea1b170ecf4c71c4c9c58d2d1e3186fb5712848c0610d05fe60b8bb6a9eaebd9b54f1db3baf6f603e04214cce597cc7799bc9f47fd9a797a + languageName: node + linkType: hard + +"ansi-regex@npm:^4.1.0": + version: 4.1.0 + resolution: "ansi-regex@npm:4.1.0" + checksum: 53b6fe447cf92ee59739379de637af6f86b3b8a9537fbfe36a66f946f1d9d34afc3efe664ac31bcc7c3af042d43eabcfcfd3f790316d474bbc7b19a4b1d132dd + languageName: node + linkType: hard + +"ansi-regex@npm:^5.0.0": + version: 5.0.0 + resolution: "ansi-regex@npm:5.0.0" + checksum: cbd9b5c9dbbb4a949c2a6e93f1c6cc19f0683d8a4724d08d2158627be6d373f0f3ba1f4ada01dce7ee141f2ba2628fbbd29932c7d49926e3b630c7f329f3178b + languageName: node + linkType: hard + +"ansi-styles@npm:^3.2.0, ansi-styles@npm:^3.2.1": + version: 3.2.1 + resolution: "ansi-styles@npm:3.2.1" + dependencies: + color-convert: ^1.9.0 + checksum: 456e1c23d9277512a47718da75e7fbb0a5ee215ef893c2f76d6b3efe8fceabc861121b80b0362146f5f995d21a1633f05a19bbf6283ae66ac11dc3b9c0bed779 + languageName: node + linkType: hard + +"ansi-styles@npm:^4.1.0": + version: 4.3.0 + resolution: "ansi-styles@npm:4.3.0" + dependencies: + color-convert: ^2.0.1 + checksum: ea02c0179f3dd089a161f5fdd7ccd89dd84f31d82b68869f1134bf5c5b9e1313dadd2ff9edb02b44f46243f285ef5b785f6cb61c84a293694221417c42934407 + languageName: node + linkType: hard + +"argparse@npm:^1.0.7": + version: 1.0.10 + resolution: "argparse@npm:1.0.10" + dependencies: + sprintf-js: ~1.0.2 + checksum: 435adaef5f6671c3ef1478a22be6fd54bdb99fdbbce8f5561b9cbbb05068ccce87b7df3b9f3322ff52a6ebb9cab2b427cbedac47a07611690a9beaa5184093e2 + languageName: node + linkType: hard + +"array-includes@npm:^3.1.1, array-includes@npm:^3.1.2": + version: 3.1.3 + resolution: "array-includes@npm:3.1.3" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.3 + es-abstract: ^1.18.0-next.2 + get-intrinsic: ^1.1.1 + is-string: ^1.0.5 + checksum: 0610b361162071ef8749827f3a6e171da03ac14a518d4d45a513b6c22a7c5017c55dcbea4d34d699ef006f1f148aa52e6d437e0101c93996da736d0055add173 + languageName: node + linkType: hard + +"array.prototype.flat@npm:^1.2.3": + version: 1.2.4 + resolution: "array.prototype.flat@npm:1.2.4" + dependencies: + call-bind: ^1.0.0 + define-properties: ^1.1.3 + es-abstract: ^1.18.0-next.1 + checksum: b4936e68f8bb4ed8d6bf12eff4e19e93f263ee6ff66b0e394be275c0b168e2a4889740f105799ec1d19631e93020fba528534ca34dd0538e24d2a90043ebc6b0 + languageName: node + linkType: hard + +"array.prototype.flatmap@npm:^1.2.3": + version: 1.2.4 + resolution: "array.prototype.flatmap@npm:1.2.4" + dependencies: + call-bind: ^1.0.0 + define-properties: ^1.1.3 + es-abstract: ^1.18.0-next.1 + function-bind: ^1.1.1 + checksum: 2718f73b91e5f377fb230765c1ab1a67063d11f7d14b1edfa6283ba4129f710b2d2416003dc8c9fc25595d921a76430ed8a379f66e58d237e5f1f300b6465ece + languageName: node + linkType: hard + +"astral-regex@npm:^1.0.0": + version: 1.0.0 + resolution: "astral-regex@npm:1.0.0" + checksum: 08e37f599604eb3894af4ec5f9845caec7a45d10c1b57b04c4fc21cc669091803f8386efc52957ec3c7ae8c3af60b933018926aab156e5696a7aab393d6e0874 + languageName: node + linkType: hard + +"balanced-match@npm:^1.0.0": + version: 1.0.0 + resolution: "balanced-match@npm:1.0.0" + checksum: f515a605fe1b59f476f7477c5e1d53ad55b4f42982fca1d57b6701906f4ad1f1ac90fd6587d92cc1af2edb43eecf979214dd847ee410a6de9db4ebf0dd128d62 + languageName: node + linkType: hard + +"botframework-functional-tests@workspace:.": + version: 0.0.0-use.local + resolution: "botframework-functional-tests@workspace:." + dependencies: + semistandard: ^16.0.0 + languageName: unknown + linkType: soft + +"brace-expansion@npm:^1.1.7": + version: 1.1.11 + resolution: "brace-expansion@npm:1.1.11" + dependencies: + balanced-match: ^1.0.0 + concat-map: 0.0.1 + checksum: 4c878e25e4858baf801945dfd63eb68feab2e502cf1122f25f3915c0e3bf397af3a93ff6bef0798db41c0d81ef28c08e55daac38058710f749a3b96eee6b8f40 + languageName: node + linkType: hard + +"call-bind@npm:^1.0.0, call-bind@npm:^1.0.2": + version: 1.0.2 + resolution: "call-bind@npm:1.0.2" + dependencies: + function-bind: ^1.1.1 + get-intrinsic: ^1.0.2 + checksum: 18cc6107a1f028247f2b505dae73ad1c63b737addfcd43ff75159f072c5c827300c1fb66f26ee0ec70fc2fdd005ce68d65c05a2a34b74bab08c3b1921954ada9 + languageName: node + linkType: hard + +"callsites@npm:^3.0.0": + version: 3.1.0 + resolution: "callsites@npm:3.1.0" + checksum: f726bf10d752901174cae348e69c2e58206404d5eebcea485b3fedbcf7fcffdb397e10919fdf6ee2c8adb4be52a64eea2365d52583611939bfecd109260451c9 + languageName: node + linkType: hard + +"chalk@npm:^2.0.0": + version: 2.4.2 + resolution: "chalk@npm:2.4.2" + dependencies: + ansi-styles: ^3.2.1 + escape-string-regexp: ^1.0.5 + supports-color: ^5.3.0 + checksum: 22c7b7b5bc761c882bb6516454a1a671923f1c53ff972860065aa0b28a195f230163c1d46ee88bcc7a03e5539177d896457d8bc727de7f244c6e87032743038e + languageName: node + linkType: hard + +"chalk@npm:^4.0.0": + version: 4.1.0 + resolution: "chalk@npm:4.1.0" + dependencies: + ansi-styles: ^4.1.0 + supports-color: ^7.1.0 + checksum: f860285b419f9e925c2db0f45ffa88aa8794c14b80cc5d01ff30930bcfc384996606362706f0829cf557f6d36152a5fb2d227ad63c4bc90e2ec9e9dbed4a3c07 + languageName: node + linkType: hard + +"color-convert@npm:^1.9.0": + version: 1.9.3 + resolution: "color-convert@npm:1.9.3" + dependencies: + color-name: 1.1.3 + checksum: 5f244daa3d1fe1f216d48878c550465067d15268688308554e613b7640a068f96588096d51f0b98b68f15d6ff6bb8ad24e172582ac8c0ad43fa4d3da60fd1b79 + languageName: node + linkType: hard + +"color-convert@npm:^2.0.1": + version: 2.0.1 + resolution: "color-convert@npm:2.0.1" + dependencies: + color-name: ~1.1.4 + checksum: 3d5d8a011a43012ca11b6d739049ecf2055d95582fd16ec44bf1e685eb0baa5cc652002be8a1dc92b429c8d87418287d0528266a7595cb1ad8a7f4f1d3049df2 + languageName: node + linkType: hard + +"color-name@npm:1.1.3": + version: 1.1.3 + resolution: "color-name@npm:1.1.3" + checksum: d8b91bb90aefc05b6ff568cf8889566dcc6269824df6f3c9b8ca842b18d7f4d089c07dc166808d33f22092d4a79167aa56a96a5ff0d21efab548bf44614db43b + languageName: node + linkType: hard + +"color-name@npm:~1.1.4": + version: 1.1.4 + resolution: "color-name@npm:1.1.4" + checksum: 3e1c9a4dee12eada307436f61614dd11fe300469db2b83f80c8b7a7cd8a1015f0f18dd13403f018927b249003777ff60baba4a03c65f12e6bddc0dfd9642021f + languageName: node + linkType: hard + +"concat-map@npm:0.0.1": + version: 0.0.1 + resolution: "concat-map@npm:0.0.1" + checksum: 554e28d9ee5aa6e061795473ee092cb3d3a2cbdb76c35416e0bb6e03f136d7d07676da387b2ed0ec4106cedbb6534080d9abc48ecc4a92b76406cf2d0c3c0c4b + languageName: node + linkType: hard + +"contains-path@npm:^0.1.0": + version: 0.1.0 + resolution: "contains-path@npm:0.1.0" + checksum: 59920a59a0c7d1244235d76b8cfd2b2e7a8dcc463fa578ef9d4d5a5a73eeb14d75dada6b21188e0b35f2474ae9efd10c3698372e674db9c6a904b281998b97d6 + languageName: node + linkType: hard + +"cross-spawn@npm:^7.0.2": + version: 7.0.3 + resolution: "cross-spawn@npm:7.0.3" + dependencies: + path-key: ^3.1.0 + shebang-command: ^2.0.0 + which: ^2.0.1 + checksum: 51f10036f5f1de781be98f4738d58b50c6d44f4f471069b8ab075b21605893ba1548654880f7310a29a732d6fc7cd481da6026169b9f0831cab0148a62fb397a + languageName: node + linkType: hard + +"debug@npm:^2.6.9": + version: 2.6.9 + resolution: "debug@npm:2.6.9" + dependencies: + ms: 2.0.0 + checksum: 559f44f98cf25e2ee489022aec173afbff746564cb108c4493becb95bc3c017a67bdaa25a0ff64801fd32c35051d00af0e56cc7f762ae2c3bc089496e5a1c31b + languageName: node + linkType: hard + +"debug@npm:^4.0.1, debug@npm:^4.1.1": + version: 4.3.1 + resolution: "debug@npm:4.3.1" + dependencies: + ms: 2.1.2 + peerDependenciesMeta: + supports-color: + optional: true + checksum: 0d41ba5177510e8b388dfd7df143ab0f9312e4abdaba312595461511dac88e9ef8101939d33b4e6d37e10341af6a5301082e4d7d6f3deb4d57bc05fc7d296fad + languageName: node + linkType: hard + +"deep-is@npm:^0.1.3": + version: 0.1.3 + resolution: "deep-is@npm:0.1.3" + checksum: 3de58f86af4dec86c8be531a5abaf2e6d8ea98fa2f1d81a3a778d0d8df920ee282043a6ef05bfb4eb699c8551df9ac1b808d4dc71d54cc40ab1efa5ce8792943 + languageName: node + linkType: hard + +"define-properties@npm:^1.1.3": + version: 1.1.3 + resolution: "define-properties@npm:1.1.3" + dependencies: + object-keys: ^1.0.12 + checksum: b69c48c1b1dacb61f0b1cea367707c3bb214e3c47818aff18e6f20a7f88cbfa33d4cbdfd9ff79e56faba95ddca3d78ff10fbf2f02ecfad6f3e13b256e76b1212 + languageName: node + linkType: hard + +"doctrine@npm:1.5.0": + version: 1.5.0 + resolution: "doctrine@npm:1.5.0" + dependencies: + esutils: ^2.0.2 + isarray: ^1.0.0 + checksum: aaffea02f963b8b07a78b1e27d7cef29be65d31be2c6681cb2872c2fb3781e14615bd05d4dff6036f75dcf3f191216058409fbfec805d3a7277a8647cd5bdee1 + languageName: node + linkType: hard + +"doctrine@npm:^2.1.0": + version: 2.1.0 + resolution: "doctrine@npm:2.1.0" + dependencies: + esutils: ^2.0.2 + checksum: 4aa55e46757cc11bff8efa67cdb679dd89e87c954ea9d88fad5a9198cfe0a73748085503d29bebcb143487d720a759a6bbe81d6848c94da46a55c7a366b9834e + languageName: node + linkType: hard + +"doctrine@npm:^3.0.0": + version: 3.0.0 + resolution: "doctrine@npm:3.0.0" + dependencies: + esutils: ^2.0.2 + checksum: 2eae469bd2889ceee9892083a67340b3622568fe5290edce620e5d5ddab23d644b2a780e9a7c68ad9c8a62716a70c5e484402ac93a398fa78b54b7505592aa7f + languageName: node + linkType: hard + +"emoji-regex@npm:^7.0.1": + version: 7.0.3 + resolution: "emoji-regex@npm:7.0.3" + checksum: e3a504cf5242061d9b3c78a88ce787d6beee37a5d21287c6ccdddf1fe665d5ef3eddfdda663d0baf683df8e7d354210eeb1458a7d9afdf0d7a28d48cbb9975e1 + languageName: node + linkType: hard + +"enquirer@npm:^2.3.5": + version: 2.3.6 + resolution: "enquirer@npm:2.3.6" + dependencies: + ansi-colors: ^4.1.1 + checksum: e249bb97bf7d5a91d51081547ea5aa1d849604e5de74feff2c48f7174fc6c9dfcfeea42ef5536e9a3be58964a248c322d6897269ae7bba3e1b6d24f152d9d685 + languageName: node + linkType: hard + +"error-ex@npm:^1.2.0, error-ex@npm:^1.3.1": + version: 1.3.2 + resolution: "error-ex@npm:1.3.2" + dependencies: + is-arrayish: ^0.2.1 + checksum: 6c6c9187429ae867d145bc64c682c7c137b1f8373a406dc3b605c0d92f15b85bfcea02b461dc55ae11b10d013377e1eaf3d469d2861b2f94703c743620a9c08c + languageName: node + linkType: hard + +"es-abstract@npm:^1.18.0-next.1, es-abstract@npm:^1.18.0-next.2": + version: 1.18.0 + resolution: "es-abstract@npm:1.18.0" + dependencies: + call-bind: ^1.0.2 + es-to-primitive: ^1.2.1 + function-bind: ^1.1.1 + get-intrinsic: ^1.1.1 + has: ^1.0.3 + has-symbols: ^1.0.2 + is-callable: ^1.2.3 + is-negative-zero: ^2.0.1 + is-regex: ^1.1.2 + is-string: ^1.0.5 + object-inspect: ^1.9.0 + object-keys: ^1.1.1 + object.assign: ^4.1.2 + string.prototype.trimend: ^1.0.4 + string.prototype.trimstart: ^1.0.4 + unbox-primitive: ^1.0.0 + checksum: 019fa7c51e10532cd07ca3aa9b76e4c6ad6f421e15064205d144da08da54f8fc057edc262f6f95775e0b249ecbb753b497050dd75ab69a3c1c89cb9b734e42ca + languageName: node + linkType: hard + +"es-to-primitive@npm:^1.2.1": + version: 1.2.1 + resolution: "es-to-primitive@npm:1.2.1" + dependencies: + is-callable: ^1.1.4 + is-date-object: ^1.0.1 + is-symbol: ^1.0.2 + checksum: d20b7be268b84662469972ec7265a57d4d6a65b9bf2b73f040d75e14f9f6dbe266a1a88579162e11349f9cb70eaa17640efb515c90dab19745a904b680b14be3 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^1.0.5": + version: 1.0.5 + resolution: "escape-string-regexp@npm:1.0.5" + checksum: f9484b8b4c8827d816e0fd905c25ed4b561376a9c220e1430403ea84619bf680c76a883a48cff8b8e091daf55d6a497e37479f9787b9f15f3c421b6054289744 + languageName: node + linkType: hard + +"eslint-config-semistandard@npm:15.0.1": + version: 15.0.1 + resolution: "eslint-config-semistandard@npm:15.0.1" + peerDependencies: + eslint: ">=6.0.1" + eslint-config-standard: ">=14.1.0" + eslint-plugin-import: ">=2.18.0" + eslint-plugin-node: ">=9.1.0" + eslint-plugin-promise: ">=4.2.1" + eslint-plugin-standard: ">=4.0.0" + checksum: 2550dfd3f9f4151f52cbbc072dc835d0fbc7582e83715f47f743d31bceabcaffd616dbb8facbcbe804181ea3c387511a4ad608efe109508867e05404a5fcd3c7 + languageName: node + linkType: hard + +"eslint-config-standard-jsx@npm:10.0.0": + version: 10.0.0 + resolution: "eslint-config-standard-jsx@npm:10.0.0" + peerDependencies: + eslint: ^7.12.1 + eslint-plugin-react: ^7.21.5 + checksum: 1808aa6f198b1fcebfc9590363e6c9cc6b00fb1c40a2f9eb66fe50ff4ab5fbc6b5f6ca070f7ea8e3afc15331a05c8e38b5f3fdb1c70949839eacd3da9a2237b8 + languageName: node + linkType: hard + +"eslint-config-standard@npm:16.0.0": + version: 16.0.0 + resolution: "eslint-config-standard@npm:16.0.0" + peerDependencies: + eslint: ^7.12.1 + eslint-plugin-import: ^2.22.1 + eslint-plugin-node: ^11.1.0 + eslint-plugin-promise: ^4.2.1 + checksum: 385e702d59929f4eeae471d47a51da138c25bb0a263adb935623ac0b9f25f54869e5eb2ea0a6f9658c4c52caeb6cfa282ac9335ff6eb39b8ff8e998997af73e8 + languageName: node + linkType: hard + +"eslint-import-resolver-node@npm:^0.3.4": + version: 0.3.4 + resolution: "eslint-import-resolver-node@npm:0.3.4" + dependencies: + debug: ^2.6.9 + resolve: ^1.13.1 + checksum: 825e34e662c988ece8229e6956a95f12d2fa19265b429e3e3db14e58bfe72e270c999cda0cfc690793ed6e6a3e49ffa8df0e0a8842d668a1f0f7de5ae1aa36f9 + languageName: node + linkType: hard + +"eslint-module-utils@npm:^2.6.0": + version: 2.6.0 + resolution: "eslint-module-utils@npm:2.6.0" + dependencies: + debug: ^2.6.9 + pkg-dir: ^2.0.0 + checksum: f584af176480a702eedcdb3f610797f8b8d1293c3835ed71fadb579ec28400b91ded5283729418f63d48dc27c6358bd66f2bd839614d565a1b78d3c3440ee8f7 + languageName: node + linkType: hard + +"eslint-plugin-es@npm:^3.0.0": + version: 3.0.1 + resolution: "eslint-plugin-es@npm:3.0.1" + dependencies: + eslint-utils: ^2.0.0 + regexpp: ^3.0.0 + peerDependencies: + eslint: ">=4.19.1" + checksum: 11b3229c3c7126d24dea1df90ec6b7966f8a269fc95fa24ae1b868fb676474cdb7e5144076aefca99cb1d3e9c89e6d0ae72d4874242d0987f210e0ee7e7f6896 + languageName: node + linkType: hard + +"eslint-plugin-import@npm:~2.22.1": + version: 2.22.1 + resolution: "eslint-plugin-import@npm:2.22.1" + dependencies: + array-includes: ^3.1.1 + array.prototype.flat: ^1.2.3 + contains-path: ^0.1.0 + debug: ^2.6.9 + doctrine: 1.5.0 + eslint-import-resolver-node: ^0.3.4 + eslint-module-utils: ^2.6.0 + has: ^1.0.3 + minimatch: ^3.0.4 + object.values: ^1.1.1 + read-pkg-up: ^2.0.0 + resolve: ^1.17.0 + tsconfig-paths: ^3.9.0 + peerDependencies: + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 + checksum: 35ae09ceae6f0fe239f6b72e134d58d74762ad1ed0f57aa989affb856354e46bc082bb6df9399b624989107efb9ab9af2c91c08f03c0c70c5cb46a37676591ec + languageName: node + linkType: hard + +"eslint-plugin-node@npm:~11.1.0": + version: 11.1.0 + resolution: "eslint-plugin-node@npm:11.1.0" + dependencies: + eslint-plugin-es: ^3.0.0 + eslint-utils: ^2.0.0 + ignore: ^5.1.1 + minimatch: ^3.0.4 + resolve: ^1.10.1 + semver: ^6.1.0 + peerDependencies: + eslint: ">=5.16.0" + checksum: 634e03613a4aa0ffd5ccf0807a375840a915797781a4e5d44fb1c4d331cd2a5e6b3da41924a0d6a2123c1a51626db032a038210153fbba6a0012e0b7c7f26499 + languageName: node + linkType: hard + +"eslint-plugin-promise@npm:~4.2.1": + version: 4.2.1 + resolution: "eslint-plugin-promise@npm:4.2.1" + checksum: 8c233a0b5f5646e08709e999aa75973481ee46c45255ec4b4d1577915f68d79ae52f6f84e361af5b761971294bdb38ec86021515fa4cff178f57c989226dd671 + languageName: node + linkType: hard + +"eslint-plugin-react@npm:~7.21.5": + version: 7.21.5 + resolution: "eslint-plugin-react@npm:7.21.5" + dependencies: + array-includes: ^3.1.1 + array.prototype.flatmap: ^1.2.3 + doctrine: ^2.1.0 + has: ^1.0.3 + jsx-ast-utils: ^2.4.1 || ^3.0.0 + object.entries: ^1.1.2 + object.fromentries: ^2.0.2 + object.values: ^1.1.1 + prop-types: ^15.7.2 + resolve: ^1.18.1 + string.prototype.matchall: ^4.0.2 + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 + checksum: a7123f6feb287ad7e15d36b1c9fda8f78a0f0d39b9ee2d6479a5f0ad0effb8f60f639ef7449cecabb711095060e487f0950b69478b22768547b909c0de4f3afd + languageName: node + linkType: hard + +"eslint-plugin-standard@npm:~4.0.2": + version: 4.0.2 + resolution: "eslint-plugin-standard@npm:4.0.2" + peerDependencies: + eslint: ">=5.0.0" + checksum: 1d1e4e5e25e39fd2e645e204d3c407d098b5d179730ed1bb9f156ecc8fa3828b11246b8eff03d4af95f33abdc91efa103255b3dacc311479edb585fea7d3b65b + languageName: node + linkType: hard + +"eslint-scope@npm:^5.1.1": + version: 5.1.1 + resolution: "eslint-scope@npm:5.1.1" + dependencies: + esrecurse: ^4.3.0 + estraverse: ^4.1.1 + checksum: 79465cf5082f4216176f6d49c7d088de89ee890f912eb87b831f23ee9a5e17ed0f3f2ab6108fb8fefa0474ba5ebeaa9bdefbe49ba704bd879b73f2445e23ee10 + languageName: node + linkType: hard + +"eslint-utils@npm:^2.0.0, eslint-utils@npm:^2.1.0": + version: 2.1.0 + resolution: "eslint-utils@npm:2.1.0" + dependencies: + eslint-visitor-keys: ^1.1.0 + checksum: a43892372a4205374982ac9d4c8edc5fe180cba76535ab9184c48f18a3d931b7ffdd6862cb2f8ca4305c47eface0e614e39884a75fbf169fcc55a6131af2ec48 + languageName: node + linkType: hard + +"eslint-visitor-keys@npm:^1.1.0, eslint-visitor-keys@npm:^1.3.0": + version: 1.3.0 + resolution: "eslint-visitor-keys@npm:1.3.0" + checksum: 58ab7a0107621d8a0fe19142a5e1306fd527c0f36b65d5c79033639e80278d8060264804f42c56f68e5541c4cc83d9175f9143083774cec8222f6cd5a695306e + languageName: node + linkType: hard + +"eslint-visitor-keys@npm:^2.0.0": + version: 2.0.0 + resolution: "eslint-visitor-keys@npm:2.0.0" + checksum: 429dabdcab3c1cf5e65d44843afc513398d4ee32a37f93edc93bb5ba59a12b78fa67d87ff23c752c170b5e4f9085050f45b3c036cdfb23d40a724f2614048140 + languageName: node + linkType: hard + +"eslint@npm:~7.12.1": + version: 7.12.1 + resolution: "eslint@npm:7.12.1" + dependencies: + "@babel/code-frame": ^7.0.0 + "@eslint/eslintrc": ^0.2.1 + ajv: ^6.10.0 + chalk: ^4.0.0 + cross-spawn: ^7.0.2 + debug: ^4.0.1 + doctrine: ^3.0.0 + enquirer: ^2.3.5 + eslint-scope: ^5.1.1 + eslint-utils: ^2.1.0 + eslint-visitor-keys: ^2.0.0 + espree: ^7.3.0 + esquery: ^1.2.0 + esutils: ^2.0.2 + file-entry-cache: ^5.0.1 + functional-red-black-tree: ^1.0.1 + glob-parent: ^5.0.0 + globals: ^12.1.0 + ignore: ^4.0.6 + import-fresh: ^3.0.0 + imurmurhash: ^0.1.4 + is-glob: ^4.0.0 + js-yaml: ^3.13.1 + json-stable-stringify-without-jsonify: ^1.0.1 + levn: ^0.4.1 + lodash: ^4.17.19 + minimatch: ^3.0.4 + natural-compare: ^1.4.0 + optionator: ^0.9.1 + progress: ^2.0.0 + regexpp: ^3.1.0 + semver: ^7.2.1 + strip-ansi: ^6.0.0 + strip-json-comments: ^3.1.0 + table: ^5.2.3 + text-table: ^0.2.0 + v8-compile-cache: ^2.0.3 + bin: + eslint: bin/eslint.js + checksum: b3d11ea516d2932db5854ea942a546b711e6ee8735306627042bb9e2539e96844cc62cdb5826039f5d0509202936045c2cd7b84b457004c891a326f8eccae333 + languageName: node + linkType: hard + +"espree@npm:^7.3.0": + version: 7.3.1 + resolution: "espree@npm:7.3.1" + dependencies: + acorn: ^7.4.0 + acorn-jsx: ^5.3.1 + eslint-visitor-keys: ^1.3.0 + checksum: ff8e0f73939e1e76529b630cba65b6128e4d18ed7bf0b16af62022cadc73ddb950c7e5aa629cca74e8abebdf76f6dcd1cf873dbc819f10599827c6019e2f8e91 + languageName: node + linkType: hard + +"esprima@npm:^4.0.0": + version: 4.0.1 + resolution: "esprima@npm:4.0.1" + bin: + esparse: ./bin/esparse.js + esvalidate: ./bin/esvalidate.js + checksum: 5df45a3d9c95c36800d028ba76d8d4e04e199932b58c2939f462f859fd583e7d39b4a12d3f97986cf272a28a5fe5948ee6e49e36ef63f67b5b48d82a635c5081 + languageName: node + linkType: hard + +"esquery@npm:^1.2.0": + version: 1.4.0 + resolution: "esquery@npm:1.4.0" + dependencies: + estraverse: ^5.1.0 + checksum: 3293ecc1507a8cec8d2da8a4707275c2ccf5413e7a3c771fe41c16cee603cacd193bb7383a6e219d1f7d2449156ef575ffd66c839073d4a8058f72856a15f622 + languageName: node + linkType: hard + +"esrecurse@npm:^4.3.0": + version: 4.3.0 + resolution: "esrecurse@npm:4.3.0" + dependencies: + estraverse: ^5.2.0 + checksum: 2c96302dd5c4e6d07154d0ce6baee9e829ebf77e21c50c5ca4f24d6d0006fe4a4582364624a01f5667a3633b3e39bbce1a8191924f8419fb71584bb45bf7bb81 + languageName: node + linkType: hard + +"estraverse@npm:^4.1.1": + version: 4.3.0 + resolution: "estraverse@npm:4.3.0" + checksum: 1e4c627da9e9af07bf7b2817320f606841808fb2ec0cbd81097b30d5f90d8613288b3e523153babe04615d59b54ef876d98f0ca27488b6c0934dacd725a8d338 + languageName: node + linkType: hard + +"estraverse@npm:^5.1.0, estraverse@npm:^5.2.0": + version: 5.2.0 + resolution: "estraverse@npm:5.2.0" + checksum: 7dc1b027aebf937bab10c3254d9d73ed21672d7382518c9ddb9dc45560cb2f4e6548cc8ff1a07b7f431e94bd0fb0bf5da75b602e2473f966fea141c4c31b31d6 + languageName: node + linkType: hard + +"esutils@npm:^2.0.2": + version: 2.0.3 + resolution: "esutils@npm:2.0.3" + checksum: 590b04533177f8f6f0f352b3ac7da6c1c1e3d8375d8973972fba9c94558ca168685fd38319c3c6f4c37ba256df7494a7f15d8e761df1655af8a8f0027d988f8f + languageName: node + linkType: hard + +"fast-deep-equal@npm:^3.1.1": + version: 3.1.3 + resolution: "fast-deep-equal@npm:3.1.3" + checksum: 451526766b219503131d11e823eaadd1533080b0be4860e316670b039dcaf31cd1007c2fe036a9b922abba7c040dfad5e942ed79d21f2ff849e50049f36e0fb7 + languageName: node + linkType: hard + +"fast-json-stable-stringify@npm:^2.0.0": + version: 2.1.0 + resolution: "fast-json-stable-stringify@npm:2.1.0" + checksum: 7df3fabfe445d65953b2d9d9d3958bd895438b215a40fb87dae8b2165c5169a897785eb5d51e6cf0eb03523af756e3d82ea01083f6ac6341fe16db532fee3016 + languageName: node + linkType: hard + +"fast-levenshtein@npm:^2.0.6": + version: 2.0.6 + resolution: "fast-levenshtein@npm:2.0.6" + checksum: a2d03af3088b0397633e007fb3010ecfa4f91cae2116d2385653c59396a1b31467641afa672a79e6f82218518670dc144128378124e711e35dbf90bc82846f22 + languageName: node + linkType: hard + +"file-entry-cache@npm:^5.0.1": + version: 5.0.1 + resolution: "file-entry-cache@npm:5.0.1" + dependencies: + flat-cache: ^2.0.1 + checksum: 7140588becf15f05ee956cfb359b5f23e0c73acbbd38ad14c7a76a0097342e6bfc0a8151cd2e481ea3cbb735190ba9a0df4b69055ebb5b0389c62339b1a2f86b + languageName: node + linkType: hard + +"find-up@npm:^2.0.0, find-up@npm:^2.1.0": + version: 2.1.0 + resolution: "find-up@npm:2.1.0" + dependencies: + locate-path: ^2.0.0 + checksum: 9dedb89f936b572f7c9fda3f66ebe146b0000fe9ef16fad94a77c25ce9585962e910bb32c1e08bab9b423985ff20221d2af4b7e4130b27c0f5f60c1aad3f6a7f + languageName: node + linkType: hard + +"find-up@npm:^3.0.0": + version: 3.0.0 + resolution: "find-up@npm:3.0.0" + dependencies: + locate-path: ^3.0.0 + checksum: c5422fc7231820421cff6f6e3a5d00a11a79fd16625f2af779c6aedfbaad66764fd149c1b84017aa44e85f86395eb25c31188ad273fc468a981b529eaa59a424 + languageName: node + linkType: hard + +"flat-cache@npm:^2.0.1": + version: 2.0.1 + resolution: "flat-cache@npm:2.0.1" + dependencies: + flatted: ^2.0.0 + rimraf: 2.6.3 + write: 1.0.3 + checksum: a36ba407553064be4a571cdee4021a50290f6179a0827df1d076a2e33cd84e543d0274cb15dbeb551c2ae6d53e611e3c02564a93f0d527563d0f560be7a14b0d + languageName: node + linkType: hard + +"flatted@npm:^2.0.0": + version: 2.0.2 + resolution: "flatted@npm:2.0.2" + checksum: a3e5fb71ad3c4f0661cd3899864812bcf3f64bdf6aa5f33f967c9c2a8a5f0c7219707e864c0602115fef40e093415f76a43e77afd0a86990904e2217ddb44eb4 + languageName: node + linkType: hard + +"fs.realpath@npm:^1.0.0": + version: 1.0.0 + resolution: "fs.realpath@npm:1.0.0" + checksum: 698a91b1695e3926185c9e5b0dd57cf687dceb4eb73799af91e6b2ab741735e2962c366c5af6403ffddae2619914193bd339efa706fdc984d0ffc74b7a3603f4 + languageName: node + linkType: hard + +"function-bind@npm:^1.1.1": + version: 1.1.1 + resolution: "function-bind@npm:1.1.1" + checksum: ffad86e7d2010ba179aaa6a3987d2cc0ed48fa92d27f1ed84bfa06d14f77deeed5bfbae7f00bdebc0c54218392cab2b18ecc080e2c72f592431927b87a27d42b + languageName: node + linkType: hard + +"functional-red-black-tree@npm:^1.0.1": + version: 1.0.1 + resolution: "functional-red-black-tree@npm:1.0.1" + checksum: 477ecaf62d4f8d788876099b35ed4b97586b331e729d2d28d0df96b598863d21c18b8a45a6cbecb6c2bf7f5e5ef1e82a053570583ef9a0ff8336683ab42b8d14 + languageName: node + linkType: hard + +"get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.0, get-intrinsic@npm:^1.1.1": + version: 1.1.1 + resolution: "get-intrinsic@npm:1.1.1" + dependencies: + function-bind: ^1.1.1 + has: ^1.0.3 + has-symbols: ^1.0.1 + checksum: acf1506f25a32a194cfc5c19d33835756080d970eb6e29a8a3852380106df981acef7bb9ac2002689437235221f24bcbdc1e3532b9bcacd7ff3621091fafe607 + languageName: node + linkType: hard + +"get-stdin@npm:^8.0.0": + version: 8.0.0 + resolution: "get-stdin@npm:8.0.0" + checksum: 009a4c42484cc30fe0f353d0a8c593dda0cdef46f4fb8c1668860d8e2fd6ca53faa2a08a85fdf3a4a56dfbe38f940772fd8b66e0ecba333a8f055c56cda72537 + languageName: node + linkType: hard + +"glob-parent@npm:^5.0.0": + version: 5.1.2 + resolution: "glob-parent@npm:5.1.2" + dependencies: + is-glob: ^4.0.1 + checksum: 82fcaa4ce102a0ae01370ed8fd5299ca32184af0418e1c1b613ed851240160558c0cc9712868eb9ca1924f687b07cd9c70c25f303f39f9f376d9a32f94f28e76 + languageName: node + linkType: hard + +"glob@npm:^7.1.3": + version: 7.1.6 + resolution: "glob@npm:7.1.6" + dependencies: + fs.realpath: ^1.0.0 + inflight: ^1.0.4 + inherits: 2 + minimatch: ^3.0.4 + once: ^1.3.0 + path-is-absolute: ^1.0.0 + checksum: 789977b52432865bd63846da5c75a6efc2c56abdc0cb5ffcdb8e91eeb67a58fa5594c1195d18b2b4aff99675b0739ed6bd61024b26562e0cca18c8f993efdc82 + languageName: node + linkType: hard + +"globals@npm:^12.1.0": + version: 12.4.0 + resolution: "globals@npm:12.4.0" + dependencies: + type-fest: ^0.8.1 + checksum: 0b9764bdeab0bc9762dea8954a0d4c5db029420bd8bf693df9098ce7e045ccaf9b2d259185396fd048b051d42fdc8dc7ab02af62e3dbeb2324a78a05aac8d33c + languageName: node + linkType: hard + +"graceful-fs@npm:^4.1.15, graceful-fs@npm:^4.1.2": + version: 4.2.6 + resolution: "graceful-fs@npm:4.2.6" + checksum: 84d39c7756892553da990a9db7e45f844b3309b37b5a00174cbb4748476f2250c54f24594d4d252f64f085c65c2fdac7c809419bf6d55f0e6e42eb07ac0f5bf2 + languageName: node + linkType: hard + +"has-bigints@npm:^1.0.0": + version: 1.0.1 + resolution: "has-bigints@npm:1.0.1" + checksum: 1074b644f5f2c319fc31af00fe2f81b6e21e204bb46da70ff7b970fe65c56f504e697fe6b41823ba679bd4111840482a83327d3432b8d670a684da4087ed074b + languageName: node + linkType: hard + +"has-flag@npm:^3.0.0": + version: 3.0.0 + resolution: "has-flag@npm:3.0.0" + checksum: 63aade480d27aeedb3b5b63a2e069d47d0006bf182338d662e7941cdc024e68a28418e0efa8dc5df30db9c4ee2407f39e6ea3f16cfbc6b83848b450826a28aa0 + languageName: node + linkType: hard + +"has-flag@npm:^4.0.0": + version: 4.0.0 + resolution: "has-flag@npm:4.0.0" + checksum: 2e5391139d3d287231ccb58659702392f6e3abeac3296fb4721afaff46493f3d9b99a9329ae015dfe973aa206ed5c75f43e86aec0267dce79aa5c2b6e811b3ad + languageName: node + linkType: hard + +"has-symbols@npm:^1.0.0, has-symbols@npm:^1.0.1, has-symbols@npm:^1.0.2": + version: 1.0.2 + resolution: "has-symbols@npm:1.0.2" + checksum: 1b73928752fa9ca993fa48f7b3832c95ea408c0ec635b2d6cbaf011b94a7e6a704a9254ae6d8ecc913d4dd92f2ff760dc43aad7c7e790ddb3f627005614d8e28 + languageName: node + linkType: hard + +"has@npm:^1.0.3": + version: 1.0.3 + resolution: "has@npm:1.0.3" + dependencies: + function-bind: ^1.1.1 + checksum: c686e15300d41364486c099a9259d9c418022c294244843dcd712c4c286ff839d4f23a25413baa28c4d2c1e828afc2aaab70f685400b391533980223c71fa1ca + languageName: node + linkType: hard + +"hosted-git-info@npm:^2.1.4": + version: 2.8.8 + resolution: "hosted-git-info@npm:2.8.8" + checksum: 3ecc389dc6ecbd5463fada7e04461e96f3c817fe2f989ca41e9dd3b503745a0bfa26fba405861b2831ca64edc1abc5d2fbc97ee977303f89650dac4fbfdc2d7a + languageName: node + linkType: hard + +"ignore@npm:^4.0.6": + version: 4.0.6 + resolution: "ignore@npm:4.0.6" + checksum: 8f7b7f7c261d110604aed4340771933b0a42ffd2075e87bf8b4229ceb679659c5384c99e25c059f53a2b0e16cebaa4c49f7e837d1f374d1abf91fea46ccddd1a + languageName: node + linkType: hard + +"ignore@npm:^5.1.1": + version: 5.1.8 + resolution: "ignore@npm:5.1.8" + checksum: b08e3d5b5d94eca13475f29a5d47d221060e9cdd7e38d7647088e29d90130669a970fecbc4cdb41b8fa295c6673740c729d3dc05dadc381f593efb42282cbf9f + languageName: node + linkType: hard + +"import-fresh@npm:^3.0.0, import-fresh@npm:^3.2.1": + version: 3.3.0 + resolution: "import-fresh@npm:3.3.0" + dependencies: + parent-module: ^1.0.0 + resolve-from: ^4.0.0 + checksum: 3ff624f00140850a2878eb7630d635daaad556cfa5a0e23191e9b65ab4fec8cc23f929f03bc9b3c4251b497a434f459bf3e7a8aa547a400ad140f431a1b0e4d6 + languageName: node + linkType: hard + +"imurmurhash@npm:^0.1.4": + version: 0.1.4 + resolution: "imurmurhash@npm:0.1.4" + checksum: 34d414d789286f6ef4d2b954c76c7df40dd7cabffef9b9959c8bd148677e98151f4fa5344aae2e3ad2b62308555ccbba3022e535a3e24288c9babb1308e35532 + languageName: node + linkType: hard + +"inflight@npm:^1.0.4": + version: 1.0.6 + resolution: "inflight@npm:1.0.6" + dependencies: + once: ^1.3.0 + wrappy: 1 + checksum: 17c53fc42cbe7f7f471d2bc41b97a0cde4b79a74d5ff59997d3f75210566fa278e17596da526d43de2bd07e222706240ce50e60097e54f2cde2e64cbbb372638 + languageName: node + linkType: hard + +"inherits@npm:2": + version: 2.0.4 + resolution: "inherits@npm:2.0.4" + checksum: 98426da247ddfc3dcd7d7daedd90c3ca32d5b08deca08949726f12d49232aef94772a07b36cf4ff833e105ae2ef931777f6de4a6dd8245a216b9299ad4a50bea + languageName: node + linkType: hard + +"internal-slot@npm:^1.0.3": + version: 1.0.3 + resolution: "internal-slot@npm:1.0.3" + dependencies: + get-intrinsic: ^1.1.0 + has: ^1.0.3 + side-channel: ^1.0.4 + checksum: 2465f832aa80c3740f2cfc5c75e74c727b4a45b8d80e295bb66dbb59435de536b9951b7f4d1a8075d5bb90054bd30ff22a37356a247fba3608987c7765569345 + languageName: node + linkType: hard + +"is-arrayish@npm:^0.2.1": + version: 0.2.1 + resolution: "is-arrayish@npm:0.2.1" + checksum: fc2bbe14dbcb27b490e63b7fbf0e3b0aae843e5e1fa96d79450bb9617797615a575c78c454ffc8e027c3ad50d63d83e85a7387784979dcd46686d2eb5f412db0 + languageName: node + linkType: hard + +"is-bigint@npm:^1.0.1": + version: 1.0.1 + resolution: "is-bigint@npm:1.0.1" + checksum: dd132ab80f389d6968315d491706c5dbb3f6c4bf35b64085d74895e7f3516123ab1bcf6a9a83a63cfede688f44550a08713ed37f3ae9153afe8d0cf569a8b956 + languageName: node + linkType: hard + +"is-boolean-object@npm:^1.1.0": + version: 1.1.0 + resolution: "is-boolean-object@npm:1.1.0" + dependencies: + call-bind: ^1.0.0 + checksum: 1d6047a022aa49cdf8580ac8b3d6d25da0d33a65ae00142bec2ba95c6c889de84693a0ef5acc9eabb59ba9e66fb473f47fa589ec22dd8e7ef8d88b6774e3adc6 + languageName: node + linkType: hard + +"is-callable@npm:^1.1.4, is-callable@npm:^1.2.3": + version: 1.2.3 + resolution: "is-callable@npm:1.2.3" + checksum: 8180a1c4e227e204e199ff355c4f24a80f74536898e16716583aa6a09167f2cceecc188cea750a2f3ae3b163577691595ae8d22bf7bb94b4bbb9fbdfea1bc5c3 + languageName: node + linkType: hard + +"is-core-module@npm:^2.2.0": + version: 2.2.0 + resolution: "is-core-module@npm:2.2.0" + dependencies: + has: ^1.0.3 + checksum: 2344744de98a3bc22e2bb30895f307d7889f09e963f9bcb1cc321788f508c8b463f75e0a9ca009eeeb8eb9465181b5c15f1ec9299a6bb6921cfbb2423892e0ba + languageName: node + linkType: hard + +"is-date-object@npm:^1.0.1": + version: 1.0.2 + resolution: "is-date-object@npm:1.0.2" + checksum: 0e322699464a99da638c8a583b74dfb791732b6bc9c102bc0b7ac6303d83c86b9935f19b8d2ed4de52092241190c8826b099cb31972dea49a99b755293c0b1cf + languageName: node + linkType: hard + +"is-extglob@npm:^2.1.1": + version: 2.1.1 + resolution: "is-extglob@npm:2.1.1" + checksum: ca623e2c56c893714a237aff645ec7caa8fea4d78868682af8d6803d7f0780323f8d566311e0dc6f942c886e81cbfa517597e48fcada7f3bf78a4d099eeecdd3 + languageName: node + linkType: hard + +"is-fullwidth-code-point@npm:^2.0.0": + version: 2.0.0 + resolution: "is-fullwidth-code-point@npm:2.0.0" + checksum: e1e5284f848ab6885665967cd768292a75022304d4401e78937a68f423047c29bfe87a43a9cdb67a3210fff7bcd5da51469122a0eff59b03261c379e58dbe921 + languageName: node + linkType: hard + +"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1": + version: 4.0.1 + resolution: "is-glob@npm:4.0.1" + dependencies: + is-extglob: ^2.1.1 + checksum: 98cd4f715f0fb81da34aa6c8be4a5ef02d8cfac3ebc885153012abc2a0410df5a572f9d0393134fcba9192c7a845da96142c5f74a3c02787efe178ed798615e6 + languageName: node + linkType: hard + +"is-negative-zero@npm:^2.0.1": + version: 2.0.1 + resolution: "is-negative-zero@npm:2.0.1" + checksum: e2160af9a6fad7027bbd513e1efe9a99c780bb6af688e61e6b71084b5893f976241ca081e1ed8c18222d391ea3c1c0771cd23ab322be107150b66faf03d6ecbd + languageName: node + linkType: hard + +"is-number-object@npm:^1.0.4": + version: 1.0.4 + resolution: "is-number-object@npm:1.0.4" + checksum: 5bae52129f0e097485da25cbe89307dd46cf5ce7640edb6fcf40350d59c9f909039713d35fbeb0f1de1df817da6ec6e88aceca41b01e5ac989f6fdfc15c438a7 + languageName: node + linkType: hard + +"is-regex@npm:^1.1.2": + version: 1.1.2 + resolution: "is-regex@npm:1.1.2" + dependencies: + call-bind: ^1.0.2 + has-symbols: ^1.0.1 + checksum: 5e2f80f495f5297d1295730820a4be31f3848ca92357cfef1b2a61c09fe0fcd3f68c34f3042a5b81885e249cd50eac8efac472ad6da7ecb497bb2d7bad402a9a + languageName: node + linkType: hard + +"is-string@npm:^1.0.5": + version: 1.0.5 + resolution: "is-string@npm:1.0.5" + checksum: c64c791eb75935db9055291bc598edc22f03d3879b8a050b2955ba8087642d006338a1dedf7ac414c95f985c77c2d6fce655498d33c0df248fa92228a9945720 + languageName: node + linkType: hard + +"is-symbol@npm:^1.0.2, is-symbol@npm:^1.0.3": + version: 1.0.3 + resolution: "is-symbol@npm:1.0.3" + dependencies: + has-symbols: ^1.0.1 + checksum: 753aa0cf95069387521b110c6646df4e0b5cce76cf604521c26b4f5d30a997a95036ed5930c0cca9e850ac6fccb04de551cc95aab71df471ee88e04ed1a96f21 + languageName: node + linkType: hard + +"isarray@npm:^1.0.0": + version: 1.0.0 + resolution: "isarray@npm:1.0.0" + checksum: b0ff31a290e783f7b3fb73f2951ee7fc2946dc197b05f73577dc77f87dc3be2e0f66007bedf069123d4e5c4b691e7c89a241f6ca06f0c0f4765cdac5aa4b4047 + languageName: node + linkType: hard + +"isexe@npm:^2.0.0": + version: 2.0.0 + resolution: "isexe@npm:2.0.0" + checksum: 7b437980bb77881a146fba85cfbdf01edc2b148673e9c2722a1e49661fea73adf524430a80fdbfb8ce9f60d43224e682c657c45030482bd39e0c488fc29b4afe + languageName: node + linkType: hard + +"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": + version: 4.0.0 + resolution: "js-tokens@npm:4.0.0" + checksum: 1fc4e4667ac2d972aba65148b9cbf9c17566b2394d3504238d8492bbd3e68f496c657eab06b26b40b17db5cac0a34d153a12130e2d2d2bb6dc2cdc8a4764eb1b + languageName: node + linkType: hard + +"js-yaml@npm:^3.13.1": + version: 3.14.1 + resolution: "js-yaml@npm:3.14.1" + dependencies: + argparse: ^1.0.7 + esprima: ^4.0.0 + bin: + js-yaml: bin/js-yaml.js + checksum: 46b61f889796a20d16b0b64580a01b6a02b2e45c1a2744906346da54d07e14cde764e887ab6d1512d8b2541c63711bd4b75859c28eb99605baf59fa173fc38c2 + languageName: node + linkType: hard + +"json-parse-better-errors@npm:^1.0.1": + version: 1.0.2 + resolution: "json-parse-better-errors@npm:1.0.2" + checksum: b4c4f0e43b43892af887db742b26f9aa6302b09cd5f6e655ead49fca9f47f3cdd300dcf98cf5218778262be51d7b29859221206fc98b87a1a61c5af7618dae89 + languageName: node + linkType: hard + +"json-schema-traverse@npm:^0.4.1": + version: 0.4.1 + resolution: "json-schema-traverse@npm:0.4.1" + checksum: 6f71bddba38aa043cf9c05ff9cf37158a6657909f1dd37032ba164b76923da47a17bb4592ee4f7f9c029dfaf26965b821ac214c1f991bb3bd038c9cfea2da50b + languageName: node + linkType: hard + +"json-stable-stringify-without-jsonify@npm:^1.0.1": + version: 1.0.1 + resolution: "json-stable-stringify-without-jsonify@npm:1.0.1" + checksum: a01b6c65413b2d0dd6797004ace6166bb6f8a0a2a77c742966021c74233cebe48de3c33223f003a9e8e4a241bb882fe92141e538e7e1dad58afd32649444e269 + languageName: node + linkType: hard + +"json5@npm:^1.0.1": + version: 1.0.1 + resolution: "json5@npm:1.0.1" + dependencies: + minimist: ^1.2.0 + bin: + json5: lib/cli.js + checksum: df41624f9f40bfacc546f779eef6d161a3312fbb6ec1dbd69f8c4388e9807af653b753371ab19b6d2bab22af2ca7dde62fe03c791596acf76915e1fc4ee6fd88 + languageName: node + linkType: hard + +"jsx-ast-utils@npm:^2.4.1 || ^3.0.0": + version: 3.2.0 + resolution: "jsx-ast-utils@npm:3.2.0" + dependencies: + array-includes: ^3.1.2 + object.assign: ^4.1.2 + checksum: 2a8033e63234d04e6ed77ac91222e2dff527f64cf70c10d2f26fda6f35dc9b78d5e3a43fc3d28df7fe0dab45294b94c9c90e58ab242ecf14e58cd39691ee0ed4 + languageName: node + linkType: hard + +"levn@npm:^0.4.1": + version: 0.4.1 + resolution: "levn@npm:0.4.1" + dependencies: + prelude-ls: ^1.2.1 + type-check: ~0.4.0 + checksum: 2f6ddfb0b956f2cb6b1608253a62b0c30e7392dd3c7b4cf284dfe2889b44d8385eaa81597646e253752c312a960ccb5e4d596968e476d5f6614f4ca60e5218e9 + languageName: node + linkType: hard + +"load-json-file@npm:^2.0.0": + version: 2.0.0 + resolution: "load-json-file@npm:2.0.0" + dependencies: + graceful-fs: ^4.1.2 + parse-json: ^2.2.0 + pify: ^2.0.0 + strip-bom: ^3.0.0 + checksum: c6ea93d36099dd6e778c6c018c9e184ad65d278a9538c2280f959b040b1a9a756d8856bdaf8a38c8f1454eca19bf4798ea59f79ccd8bb1c33aa8b7ecbe157f0c + languageName: node + linkType: hard + +"load-json-file@npm:^5.2.0": + version: 5.3.0 + resolution: "load-json-file@npm:5.3.0" + dependencies: + graceful-fs: ^4.1.15 + parse-json: ^4.0.0 + pify: ^4.0.1 + strip-bom: ^3.0.0 + type-fest: ^0.3.0 + checksum: c45b21cf66cb3a5948ef1ab12db94f9bf8d298c713014c8d9b6667062413916b57eb3c8ca365e1e84d422014c8c4d749ceb3e7335d2400e3e062e4009314eae7 + languageName: node + linkType: hard + +"locate-path@npm:^2.0.0": + version: 2.0.0 + resolution: "locate-path@npm:2.0.0" + dependencies: + p-locate: ^2.0.0 + path-exists: ^3.0.0 + checksum: ee5a888d686f8d555ebfa6c4f6f3b7c5cdfa5f382dee17e0b3fde7456fc68301ddb6a79790a412659d1e067f2f58fd74c683b203fc20368deaed45fb985b4fda + languageName: node + linkType: hard + +"locate-path@npm:^3.0.0": + version: 3.0.0 + resolution: "locate-path@npm:3.0.0" + dependencies: + p-locate: ^3.0.0 + path-exists: ^3.0.0 + checksum: 0b6bf0c1bb09021499f6198ed6a4ae367e8224e2493a74cc7bc5f4e6eca9ed880a5f7fdfb4d57b7e21d3e289c3abfe152cd510cacb1d03049f9d81d9a7d302ca + languageName: node + linkType: hard + +"lodash@npm:^4.17.14, lodash@npm:^4.17.19": + version: 4.17.21 + resolution: "lodash@npm:4.17.21" + checksum: 4983720b9abca930a4a46f18db163d7dad8dd00dbed6db0cc7b499b33b717cce69f80928b27bbb1ff2cbd3b19d251ee90669a8b5ea466072ca81c2ebe91e7468 + languageName: node + linkType: hard + +"loose-envify@npm:^1.4.0": + version: 1.4.0 + resolution: "loose-envify@npm:1.4.0" + dependencies: + js-tokens: ^3.0.0 || ^4.0.0 + bin: + loose-envify: cli.js + checksum: 5c3b47bbe5f597a3889fb001a3a98aaea2a3fafa48089c19034de1e0121bf57dbee609d184478514d74d5c5a7e9cfa3d846343455e5123b060040d46c39e91dc + languageName: node + linkType: hard + +"lru-cache@npm:^6.0.0": + version: 6.0.0 + resolution: "lru-cache@npm:6.0.0" + dependencies: + yallist: ^4.0.0 + checksum: b8b78353d2391c0f135cdc245c4744ad41c2efb1a6d98f31bc57a2cf48ebf02de96e4876657c3026673576bf1f1f61fc3fdd77ab00ad1ead737537bf17d8019d + languageName: node + linkType: hard + +"minimatch@npm:^3.0.4": + version: 3.0.4 + resolution: "minimatch@npm:3.0.4" + dependencies: + brace-expansion: ^1.1.7 + checksum: 47eab9263962cacd5733e274ecad2d8e54b0f8e124ba35ae69189e296058f634a4967b87a98954f86fa5c830ff177caf827ce0136d28717ed3232951fb4fae62 + languageName: node + linkType: hard + +"minimist@npm:^1.2.0, minimist@npm:^1.2.5": + version: 1.2.5 + resolution: "minimist@npm:1.2.5" + checksum: b77b8590147a4e217ff34266236bc39de23b52e6e33054076991ff674c7397a1380a7bde11111916f16f003a94aaa7e4f3d92595a32189644ff607fabc65a5b6 + languageName: node + linkType: hard + +"mkdirp@npm:^0.5.1": + version: 0.5.5 + resolution: "mkdirp@npm:0.5.5" + dependencies: + minimist: ^1.2.5 + bin: + mkdirp: bin/cmd.js + checksum: 9dd9792e891927b14ca02226dbe1daeb717b9517a001620d5e2658bbc72c5e4f06887b6cbcbb60595fa5a56e701073cf250f1ed69c1988a6b89faf9fd6a4d049 + languageName: node + linkType: hard + +"ms@npm:2.0.0": + version: 2.0.0 + resolution: "ms@npm:2.0.0" + checksum: 1a230340cc7f322fbe916783d8c8d60455407c6b7fb7f901d6ee34eb272402302c5c7f070a97b8531245cbb4ca6a0a623f6a128d7e5a5440cefa2c669c0b35bb + languageName: node + linkType: hard + +"ms@npm:2.1.2": + version: 2.1.2 + resolution: "ms@npm:2.1.2" + checksum: 9b65fb709bc30c0c07289dcbdb61ca032acbb9ea5698b55fa62e2cebb04c5953f1876a1f3f7f4bc2e91d4bf4d86003f3e207c3bc6ee2f716f99827e62389cd0e + languageName: node + linkType: hard + +"natural-compare@npm:^1.4.0": + version: 1.4.0 + resolution: "natural-compare@npm:1.4.0" + checksum: 2daf93d9bb516eddb06e2e80657a605af2e494d47c65d090ba43691aaffbc41f520840f1c9d3b7b641977af950217a4ab6ffb85bafcd5dfa8ba6fe4e68c43b53 + languageName: node + linkType: hard + +"normalize-package-data@npm:^2.3.2": + version: 2.5.0 + resolution: "normalize-package-data@npm:2.5.0" + dependencies: + hosted-git-info: ^2.1.4 + resolve: ^1.10.0 + semver: 2 || 3 || 4 || 5 + validate-npm-package-license: ^3.0.1 + checksum: 97d4d6b061cab51425ddb05c38d126d7a1a2a6f2c9949bef2b5ad7ef19c005df12099ea442e4cb09190929b7770008f94f87b10342a66f739acf92a7ebb9d9f2 + languageName: node + linkType: hard + +"object-assign@npm:^4.1.1": + version: 4.1.1 + resolution: "object-assign@npm:4.1.1" + checksum: 66cf021898fc1b13ea573ea8635fbd5a76533f50cecbc2fcd5eee1e8029af41bcebe7023788b6d0e06cbe4401ecea075d972f78ec74467cdc571a0f1a4d1a081 + languageName: node + linkType: hard + +"object-inspect@npm:^1.9.0": + version: 1.9.0 + resolution: "object-inspect@npm:1.9.0" + checksum: 63b412167d716e332b3233090a9e8cc7951479a6971629fb8a3d00135a2329136c697fbd2f56e48bb132928f01bd0f8c5fe2d7386222f217228ca697b8c3932a + languageName: node + linkType: hard + +"object-keys@npm:^1.0.12, object-keys@npm:^1.1.1": + version: 1.1.1 + resolution: "object-keys@npm:1.1.1" + checksum: 30d72d768b7f3f42144cee517b80e70c40cf39bb76f100557ffac42779613c591780135c54d8133894a78d2c0ae817e24a5891484722c6019a5cd5b58c745c66 + languageName: node + linkType: hard + +"object.assign@npm:^4.1.2": + version: 4.1.2 + resolution: "object.assign@npm:4.1.2" + dependencies: + call-bind: ^1.0.0 + define-properties: ^1.1.3 + has-symbols: ^1.0.1 + object-keys: ^1.1.1 + checksum: a5855cc6db3f64606c41ceb97cb9847e667d8240889d771d65638244be1d35c2e2ccb5762f437bb76abf4e98ab4634a9d302380398121cee288a44dce5028f54 + languageName: node + linkType: hard + +"object.entries@npm:^1.1.2": + version: 1.1.3 + resolution: "object.entries@npm:1.1.3" + dependencies: + call-bind: ^1.0.0 + define-properties: ^1.1.3 + es-abstract: ^1.18.0-next.1 + has: ^1.0.3 + checksum: 89eec6d43bf8440dfd800ad76486d76a900ff7e3e20b560abb4cba8494bdc8524af8cf49a897739da776fe434f8091569b2422333075378f9fd5a50d599601eb + languageName: node + linkType: hard + +"object.fromentries@npm:^2.0.2": + version: 2.0.4 + resolution: "object.fromentries@npm:2.0.4" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.3 + es-abstract: ^1.18.0-next.2 + has: ^1.0.3 + checksum: 9e02d109f6f63dda78715e43fcbd80941491e56ee771a5d21da93e271859f43b0db15e26e0b945989a6a6ee0ba480ca57b047cd331a71e4c4251d44517e0649c + languageName: node + linkType: hard + +"object.values@npm:^1.1.1": + version: 1.1.3 + resolution: "object.values@npm:1.1.3" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.3 + es-abstract: ^1.18.0-next.2 + has: ^1.0.3 + checksum: 31111fe8b8dfe7c3326ae8729eae542dc32d5705339b9b63d89d4a2f766641bfe8989744bd4771c65a7ca0dff281800e99640262c2e82daa97079143a86b3e0b + languageName: node + linkType: hard + +"once@npm:^1.3.0": + version: 1.4.0 + resolution: "once@npm:1.4.0" + dependencies: + wrappy: 1 + checksum: 57afc246536cf6494437f982b26475f22bee860f8b77ce8eb1543f42a8bffe04b2c66ddfea9a16cb25ccb80943f8ee4fc639367ef97b7a6a4f2672eb573963f5 + languageName: node + linkType: hard + +"optionator@npm:^0.9.1": + version: 0.9.1 + resolution: "optionator@npm:0.9.1" + dependencies: + deep-is: ^0.1.3 + fast-levenshtein: ^2.0.6 + levn: ^0.4.1 + prelude-ls: ^1.2.1 + type-check: ^0.4.0 + word-wrap: ^1.2.3 + checksum: bdf5683f986d00e173e6034837b7b6a9e68c7e1a37d7684b240adf1758db9076cfb04c9f64be29327881bb06c5017afb8b65012c5f02d07b180e9f6f42595ffd + languageName: node + linkType: hard + +"p-limit@npm:^1.1.0": + version: 1.3.0 + resolution: "p-limit@npm:1.3.0" + dependencies: + p-try: ^1.0.0 + checksum: 579cbd3d6c606058aa624c464e2cb3c4b56d04ed4cbafdb705633cbe62ba36d77ba2c4289023335ba382f4fbf32c15709465eea18a0e1547c5ebc4b887f2a7da + languageName: node + linkType: hard + +"p-limit@npm:^2.0.0": + version: 2.3.0 + resolution: "p-limit@npm:2.3.0" + dependencies: + p-try: ^2.0.0 + checksum: 5f20492a25c5f93fca2930dbbf41fa1bee46ef70eaa6b49ad1f7b963f309e599bc40507e0a3a531eee4bcd10fec4dd4a63291d0e3b2d84ac97d7403d43d271a9 + languageName: node + linkType: hard + +"p-locate@npm:^2.0.0": + version: 2.0.0 + resolution: "p-locate@npm:2.0.0" + dependencies: + p-limit: ^1.1.0 + checksum: b6dabbd855fba9bfa74b77882f96d0eac6c25d9966e61ab0ed7bf3d19f2e3b766f290ded1aada1ac4ce2627217b00342cf7a1d36482bada59ba6789be412dad7 + languageName: node + linkType: hard + +"p-locate@npm:^3.0.0": + version: 3.0.0 + resolution: "p-locate@npm:3.0.0" + dependencies: + p-limit: ^2.0.0 + checksum: 3ee9e3ed0b1b543f8148ef0981d33013d82a21c338b117a2d15650456f8dc888c19eb8a98484e7e159276c3ad9219c3e2a00b63228cab46bf29aeaaae096b1d6 + languageName: node + linkType: hard + +"p-try@npm:^1.0.0": + version: 1.0.0 + resolution: "p-try@npm:1.0.0" + checksum: 85739d77b3e9f6a52a8545f1adc53621fb5df4d6ef9b59a3f54f3f3159b45c4100d4e63128a1e790e9ff8ff8b86213ace314ff6d2d327c3edcceea18891baa42 + languageName: node + linkType: hard + +"p-try@npm:^2.0.0": + version: 2.2.0 + resolution: "p-try@npm:2.2.0" + checksum: 20983f3765466c1ab617ed153cb53b70ac5df828d854a3334d185e20b37f436e9096f12bc1b7fc96d8908dc927a3685172d3d89e755774f57b7103460c54dcc5 + languageName: node + linkType: hard + +"parent-module@npm:^1.0.0": + version: 1.0.1 + resolution: "parent-module@npm:1.0.1" + dependencies: + callsites: ^3.0.0 + checksum: 58714b9699f8e84340aaf0781b7cbd82f1c357f6ce9c035c151d0e8c1e9b869c51b95b680882f0d21b4751e817a6c936d4bb2952a1a1d9d9fb27e5a84baec2aa + languageName: node + linkType: hard + +"parse-json@npm:^2.2.0": + version: 2.2.0 + resolution: "parse-json@npm:2.2.0" + dependencies: + error-ex: ^1.2.0 + checksum: 920582196a8edebb3d3c4623b2f057987218272b35ae4d2d310c00bc1bd7e89b87c79358d7e009d54f047ca2eea82eab8d7e1b14e1f7cbbb345ef29fcda29731 + languageName: node + linkType: hard + +"parse-json@npm:^4.0.0": + version: 4.0.0 + resolution: "parse-json@npm:4.0.0" + dependencies: + error-ex: ^1.3.1 + json-parse-better-errors: ^1.0.1 + checksum: fa9d23708f562c447f2077c6007938334a16e772c5a9b25a6eb1853d792bc34560b483bb6079143040bc89e5476288dd2edd5a60024722986e3e434d326218c9 + languageName: node + linkType: hard + +"path-exists@npm:^3.0.0": + version: 3.0.0 + resolution: "path-exists@npm:3.0.0" + checksum: 09683e92bafb5657838217cce04e4f2f0530c274bc357c995c3231461030566e9f322b9a8bcc1ea810996e250d9a293ca36dd78dbdd6bfbee42e85a94772d6d5 + languageName: node + linkType: hard + +"path-is-absolute@npm:^1.0.0": + version: 1.0.1 + resolution: "path-is-absolute@npm:1.0.1" + checksum: 907e1e3e6ac0aef6e65adffd75b3892191d76a5b94c5cf26b43667c4240531d11872ca6979c209b2e5e1609f7f579d02f64ba9936b48bb59d36cc529f0d965ed + languageName: node + linkType: hard + +"path-key@npm:^3.1.0": + version: 3.1.1 + resolution: "path-key@npm:3.1.1" + checksum: e44aa3ca9faed0440994883050143b1214fffb907bf3a7bbdba15dc84f60821617c0d84e4cc74e1d84e9274003da50427f54d739b0b47636bcbaff4ec71b9b86 + languageName: node + linkType: hard + +"path-parse@npm:^1.0.6": + version: 1.0.6 + resolution: "path-parse@npm:1.0.6" + checksum: 2eee4b93fb3ae13600e3fca18390d9933bbbcf725a624f6b8df020d87515a74872ff6c58072190d6dc75a5584a683dc6ae5c385ad4e4f4efb6e66af040d56c67 + languageName: node + linkType: hard + +"path-type@npm:^2.0.0": + version: 2.0.0 + resolution: "path-type@npm:2.0.0" + dependencies: + pify: ^2.0.0 + checksum: d028f828dffe48a0062dc4370d5118a0c45f5fb075b013a1dfb13eadd1426eba0c8c2a13fa78f19fc4fd8771ef2012e9d062f8f970c8e56df36d4fbbe5073b26 + languageName: node + linkType: hard + +"pify@npm:^2.0.0": + version: 2.3.0 + resolution: "pify@npm:2.3.0" + checksum: d5758aa570bbd5969c62b5f745065006827ef4859b32af302e3df2bb5978e6c1e50c2360d7ffefa102e451084f4530115c84570c185ba5153ee9871c977fe278 + languageName: node + linkType: hard + +"pify@npm:^4.0.1": + version: 4.0.1 + resolution: "pify@npm:4.0.1" + checksum: 786486a8c94a7e1980ea56c59dcc05ebf0793740b71df9b9f273e48032e6301c5ecc5cc237c5a9ff45b13db27678b4d71aa37a2777bc11473c1310718b648e98 + languageName: node + linkType: hard + +"pkg-conf@npm:^3.1.0": + version: 3.1.0 + resolution: "pkg-conf@npm:3.1.0" + dependencies: + find-up: ^3.0.0 + load-json-file: ^5.2.0 + checksum: e0567c5b0f7d7abe3d9f87650436ee8f7175b6ed55027e72fc186fed124b05b2563233a5ad4d2f0468730439687c8e75128fa220b4ba0d05e186149b8788337e + languageName: node + linkType: hard + +"pkg-dir@npm:^2.0.0": + version: 2.0.0 + resolution: "pkg-dir@npm:2.0.0" + dependencies: + find-up: ^2.1.0 + checksum: f8ae3a151714c61283aeb24385b10355a238732fab822a560145c670c21350da2024f01918231222bcdfce53ec5d69056681be2c2cffe3f3a06e462b9ef2ac29 + languageName: node + linkType: hard + +"prelude-ls@npm:^1.2.1": + version: 1.2.1 + resolution: "prelude-ls@npm:1.2.1" + checksum: bc1649f521e8928cde0e1b349b224de2e6f00b71361a4a44f2e4a615342b6e1ae30366c32d26412dabe74d999a40f79c0ae044ae6b17cf19af935e74d12ea4fa + languageName: node + linkType: hard + +"progress@npm:^2.0.0": + version: 2.0.3 + resolution: "progress@npm:2.0.3" + checksum: c46ef5a1de4d527dfd32fe56a7df0c1c8b420a4c02617196813bf7f10ac7c2a929afc265d44fdd68f5c439a7e7cb3d70d569716c82d6b4148ec72089860a1312 + languageName: node + linkType: hard + +"prop-types@npm:^15.7.2": + version: 15.7.2 + resolution: "prop-types@npm:15.7.2" + dependencies: + loose-envify: ^1.4.0 + object-assign: ^4.1.1 + react-is: ^16.8.1 + checksum: a440dd406c5cf53bf39f3e898d2c65178511d34ca3c8c789b30c177992408b9e4273969726b274719aa69ccce5ab34b2fd8caa60b90f23cd2e910cdcf682de52 + languageName: node + linkType: hard + +"punycode@npm:^2.1.0": + version: 2.1.1 + resolution: "punycode@npm:2.1.1" + checksum: 0202dc191cb35bfd88870ac99a1e824b03486d4cee20b543ef337a6dee8d8b11017da32a3e4c40b69b19976e982c030b62bd72bba42884acb691bc5ef91354c8 + languageName: node + linkType: hard + +"react-is@npm:^16.8.1": + version: 16.13.1 + resolution: "react-is@npm:16.13.1" + checksum: 11bcf1267a314a522615f626f3ce3727a3a24cdbf61c4d452add3550a7875326669631326cfb1ba3e92b6f72244c32ffecf93ad21c0cad8455d3e169d0e3f060 + languageName: node + linkType: hard + +"read-pkg-up@npm:^2.0.0": + version: 2.0.0 + resolution: "read-pkg-up@npm:2.0.0" + dependencies: + find-up: ^2.0.0 + read-pkg: ^2.0.0 + checksum: f35e4cb4577b994fc9497886672c748de766ab034e24f029111b6bbbfe757b2e27b6d2b82a28a38f45d9d89ea8a9b1d3c04854e5f991d5deed48f4c9ff7baeb9 + languageName: node + linkType: hard + +"read-pkg@npm:^2.0.0": + version: 2.0.0 + resolution: "read-pkg@npm:2.0.0" + dependencies: + load-json-file: ^2.0.0 + normalize-package-data: ^2.3.2 + path-type: ^2.0.0 + checksum: ddf911317fba54abb447b1d76dd1785c37e1360f7b1e39d83201f6f3807572391ab7392f11727a9c4d90600ebc6616d22e72514d2291688c89ebd440148840b4 + languageName: node + linkType: hard + +"regexp.prototype.flags@npm:^1.3.1": + version: 1.3.1 + resolution: "regexp.prototype.flags@npm:1.3.1" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.3 + checksum: 967e462a83cdfd6f226aa9337bda6f739e3fba72a49f3d3f4ed16b60d5a811ba576ef22f01e37b9230022ba715c6207c082ca117160b304b6503e4a6557628f5 + languageName: node + linkType: hard + +"regexpp@npm:^3.0.0, regexpp@npm:^3.1.0": + version: 3.1.0 + resolution: "regexpp@npm:3.1.0" + checksum: 69d0ce6b449cf35d3732d6341a1e70850360ffc619f8eef10629871c462e614853fffb80d3f00fc17cd0bb5b8f34b0cde5be4b434e72c0eb3fbba2360c8b5ac4 + languageName: node + linkType: hard + +"resolve-from@npm:^4.0.0": + version: 4.0.0 + resolution: "resolve-from@npm:4.0.0" + checksum: 87a4357c0c1c2d165012ec04a3b2aa58931c0c0be257890806760b627bad36c9bceb6f9b2a3726f8570c67f2c9ff3ecc9507fe65cc3ad8d45cdab015245c649f + languageName: node + linkType: hard + +"resolve@^1.10.0, resolve@^1.10.1, resolve@^1.13.1, resolve@^1.17.0, resolve@^1.18.1": + version: 1.20.0 + resolution: "resolve@npm:1.20.0" + dependencies: + is-core-module: ^2.2.0 + path-parse: ^1.0.6 + checksum: 0f5206d454b30e74d9b2d575b5f8aedf443c4d8b90b84cdf79474ade29bb459075220da3127b682896872a16022ed65cc4db09e0f23849654144d3d75c65cd1b + languageName: node + linkType: hard + +"resolve@patch:resolve@^1.10.0#builtin, resolve@patch:resolve@^1.10.1#builtin, resolve@patch:resolve@^1.13.1#builtin, resolve@patch:resolve@^1.17.0#builtin, resolve@patch:resolve@^1.18.1#builtin": + version: 1.20.0 + resolution: "resolve@patch:resolve@npm%3A1.20.0#builtin::version=1.20.0&hash=3388aa" + dependencies: + is-core-module: ^2.2.0 + path-parse: ^1.0.6 + checksum: c4a515b76026806b5b26513fc7bdb80458c532bc91c02ef45ac928d1025585f93bec0b904be39c02131118a37ff7e3f9258f1526850b025d2ec0948bb5fd03d0 + languageName: node + linkType: hard + +"rimraf@npm:2.6.3": + version: 2.6.3 + resolution: "rimraf@npm:2.6.3" + dependencies: + glob: ^7.1.3 + bin: + rimraf: ./bin.js + checksum: c9ce1854f19327606934558f4729b0f7aa7b9f1a3e7ca292d56261cce1074e20b0a0b16689166da6d8ab24ed9c30f7c71fba0df38e1d37f0233b6f48307c5c7a + languageName: node + linkType: hard + +"semistandard@npm:^16.0.0": + version: 16.0.0 + resolution: "semistandard@npm:16.0.0" + dependencies: + eslint: ~7.12.1 + eslint-config-semistandard: 15.0.1 + eslint-config-standard: 16.0.0 + eslint-config-standard-jsx: 10.0.0 + eslint-plugin-import: ~2.22.1 + eslint-plugin-node: ~11.1.0 + eslint-plugin-promise: ~4.2.1 + eslint-plugin-react: ~7.21.5 + eslint-plugin-standard: ~4.0.2 + standard-engine: ^14.0.0 + bin: + semistandard: bin/cmd.js + checksum: a9b525a9747327561a1cf81cb4d0b9a9589753cf92c61e7086e420d985c1d6c2cf52ae2807c22f04477f9aeb8d7c0fd4f9e47bfd80ec329f12b5bec4feebc884 + languageName: node + linkType: hard + +"semver@npm:2 || 3 || 4 || 5": + version: 5.7.1 + resolution: "semver@npm:5.7.1" + bin: + semver: ./bin/semver + checksum: 06ff0ed753ebf741b7602be8faad620d6e160a2cb3f61019d00d919c8bca141638aa23c34da779b8595afdc9faa3678bfbb5f60366b6a4f65f98cf86605bbcdb + languageName: node + linkType: hard + +"semver@npm:^6.1.0": + version: 6.3.0 + resolution: "semver@npm:6.3.0" + bin: + semver: ./bin/semver.js + checksum: f0d155c06a67cc7e500c92d929339f1c6efd4ce9fe398aee6acc00a2333489cca0f5b4e76ee7292beba237fcca4b5a3d4a6153471f105f56299801bdab37289f + languageName: node + linkType: hard + +"semver@npm:^7.2.1": + version: 7.3.4 + resolution: "semver@npm:7.3.4" + dependencies: + lru-cache: ^6.0.0 + bin: + semver: bin/semver.js + checksum: f2c7f9aeb976d1484b2f39aa7afc8332a1d21fd32ca4a6fbf650e1423455ebf3e7029f6e2e7ba0cd71935b85942521f1ec25b6cc2c031b953c8ca4ff2d7a823d + languageName: node + linkType: hard + +"shebang-command@npm:^2.0.0": + version: 2.0.0 + resolution: "shebang-command@npm:2.0.0" + dependencies: + shebang-regex: ^3.0.0 + checksum: 85aa394d8cedeedf2e03524d6defef67a2b07d3a17d7ee50d4281d62d3fca898f26ebe7aa7bf674d51b80f197aa1d346bc1a10e8efb04377b534f4322c621012 + languageName: node + linkType: hard + +"shebang-regex@npm:^3.0.0": + version: 3.0.0 + resolution: "shebang-regex@npm:3.0.0" + checksum: ea18044ffaf18129ced5a246660a9171a7dff98999aaa9de8abb237d8a7711d8a1f76e16881399994ee429156717ce1c6a50c665bb18a4d55a7f80b9125b1f7d + languageName: node + linkType: hard + +"side-channel@npm:^1.0.4": + version: 1.0.4 + resolution: "side-channel@npm:1.0.4" + dependencies: + call-bind: ^1.0.0 + get-intrinsic: ^1.0.2 + object-inspect: ^1.9.0 + checksum: 84258ce3edb1ad35810ca17eccd52fd504b5d4da59447a6829cfd1ae8e3cff97b7df2a14f9a45b7aaa3b507ded95626cf20a500735d3b797e9ffb1eba3cfa9e7 + languageName: node + linkType: hard + +"slice-ansi@npm:^2.1.0": + version: 2.1.0 + resolution: "slice-ansi@npm:2.1.0" + dependencies: + ansi-styles: ^3.2.0 + astral-regex: ^1.0.0 + is-fullwidth-code-point: ^2.0.0 + checksum: 7578393cac91c28f8cb5fa5df36b826ad62c9e66313d2547770db8401570fa8f4aa20cd84ef9244fa054d8e9cc6bfc02578784bb89b238d384b99f2728a35a6d + languageName: node + linkType: hard + +"spdx-correct@npm:^3.0.0": + version: 3.1.1 + resolution: "spdx-correct@npm:3.1.1" + dependencies: + spdx-expression-parse: ^3.0.0 + spdx-license-ids: ^3.0.0 + checksum: f3413eb225ef9f13aa2ec05230ff7669bffad055a7f62ec85164dd27f00a9f1e19880554a8fa5350fc434764ff895836c207f98813511a0180b0e929581bfe01 + languageName: node + linkType: hard + +"spdx-exceptions@npm:^2.1.0": + version: 2.3.0 + resolution: "spdx-exceptions@npm:2.3.0" + checksum: 3cbd2498897dc384158666a9dd7435e3b42ece5da42fd967b218b790e248381d001ec77a676d13d1f4e8da317d97b7bc0ebf4fff37bfbb95923d49b024030c96 + languageName: node + linkType: hard + +"spdx-expression-parse@npm:^3.0.0": + version: 3.0.1 + resolution: "spdx-expression-parse@npm:3.0.1" + dependencies: + spdx-exceptions: ^2.1.0 + spdx-license-ids: ^3.0.0 + checksum: f0211cada3fa7cd9db2243143fb0e66e28a46d72d8268f38ad2196aac49408d87892cda6e5600d43d6b05ed2707cb2f4148deb27b092aafabc50a67038f4cbf5 + languageName: node + linkType: hard + +"spdx-license-ids@npm:^3.0.0": + version: 3.0.7 + resolution: "spdx-license-ids@npm:3.0.7" + checksum: 21e38ec5dd970643f78d37700b6c6ebd42d68c0e4618db914a56cabd2fe4cc1608404ce6abc7535d5165c6555560e821553d06edf6af6ae439617883cf932c0e + languageName: node + linkType: hard + +"sprintf-js@npm:~1.0.2": + version: 1.0.3 + resolution: "sprintf-js@npm:1.0.3" + checksum: 51df1bce9e577287f56822d79ac5bd94f6c634fccf193895f2a1d2db2e975b6aa7bc97afae9cf11d49b7c37fe4afc188ff5c4878be91f2c86eabd11c5df8b62c + languageName: node + linkType: hard + +"standard-engine@npm:^14.0.0": + version: 14.0.1 + resolution: "standard-engine@npm:14.0.1" + dependencies: + get-stdin: ^8.0.0 + minimist: ^1.2.5 + pkg-conf: ^3.1.0 + xdg-basedir: ^4.0.0 + checksum: 671824cd48a890936e2815757284c138da3aa49b9743f88726add53866b67fc97200e6a7f8ce72dfeb8111208feaefcdd05b6d3c5ddc1b38f4221589f9f8eb67 + languageName: node + linkType: hard + +"string-width@npm:^3.0.0": + version: 3.1.0 + resolution: "string-width@npm:3.1.0" + dependencies: + emoji-regex: ^7.0.1 + is-fullwidth-code-point: ^2.0.0 + strip-ansi: ^5.1.0 + checksum: 54c5d1842dc122d8e0251ad50e00e91c06368f1aca44f41a67cd5ce013c4ba8f5a26f1b7f72a3e1644f38c62092a82c86b646aff514073894faf84b9564a38a0 + languageName: node + linkType: hard + +"string.prototype.matchall@npm:^4.0.2": + version: 4.0.4 + resolution: "string.prototype.matchall@npm:4.0.4" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.3 + es-abstract: ^1.18.0-next.2 + has-symbols: ^1.0.1 + internal-slot: ^1.0.3 + regexp.prototype.flags: ^1.3.1 + side-channel: ^1.0.4 + checksum: e19b26a14adc6f6957248ae0ce3a054ddfb5b783b7528a9dd7ee2635d95a977486805c7b2182dca17fd60e1a40e1be1e02f0a9fee32e19c29d2f1850564d96d1 + languageName: node + linkType: hard + +"string.prototype.trimend@npm:^1.0.4": + version: 1.0.4 + resolution: "string.prototype.trimend@npm:1.0.4" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.3 + checksum: ea8793bee1104362587e6a0fab2cb48e76548423d8ac95847284f9f6ef6a11338cf47114e8ec1c2a9519cce55cfa8d19fc8e26413937c3e804a768ec43ebe38e + languageName: node + linkType: hard + +"string.prototype.trimstart@npm:^1.0.4": + version: 1.0.4 + resolution: "string.prototype.trimstart@npm:1.0.4" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.3 + checksum: dd2c994af9b9194c7ce9d94e30b8f8bbe30ec95ada94534a71d63df2964a200c8d2264378252a5047a5f1cf805e8216911d78d16b22d5db7b0abcdbbb2d24b4a + languageName: node + linkType: hard + +"strip-ansi@npm:^5.1.0": + version: 5.2.0 + resolution: "strip-ansi@npm:5.2.0" + dependencies: + ansi-regex: ^4.1.0 + checksum: 44a0d0d354f5f7b15f83323879a9112ea746daae7bef0b68238a27626ee757d9a04ce6590433841e14b325e8e7c5d62b8442885e50497e21b7cbca6da40d54ea + languageName: node + linkType: hard + +"strip-ansi@npm:^6.0.0": + version: 6.0.0 + resolution: "strip-ansi@npm:6.0.0" + dependencies: + ansi-regex: ^5.0.0 + checksum: 10568c91cadbef182a807c38dfa718dce15a35b12fcc97b96b6b2029d0508ef66ca93fabddeb49482d9b027495d1e18591858e80f27ad26861c4967c60fd207f + languageName: node + linkType: hard + +"strip-bom@npm:^3.0.0": + version: 3.0.0 + resolution: "strip-bom@npm:3.0.0" + checksum: 361dd1dd08ae626940061570d20bcf73909d0459734b8880eb3d14176aa28f41cf85d13af036c323ce739e04ef3930a71b516950c5985b318bae3757ecb2974c + languageName: node + linkType: hard + +"strip-json-comments@npm:^3.1.0, strip-json-comments@npm:^3.1.1": + version: 3.1.1 + resolution: "strip-json-comments@npm:3.1.1" + checksum: f16719ce25abc58a55ef82b1c27f541dcfa5d544f17158f62d10be21ff9bd22fde45a53c592b29d80ad3c97ccb67b7451c4833913fdaeadb508a40f5e0a9c206 + languageName: node + linkType: hard + +"supports-color@npm:^5.3.0": + version: 5.5.0 + resolution: "supports-color@npm:5.5.0" + dependencies: + has-flag: ^3.0.0 + checksum: edacee6425498440744c418be94b0660181aad2a1828bcf2be85c42bd385da2fd8b2b358d9b62b0c5b03ff5cd3e992458d7b8f879d9fb42f2201fe05a4848a29 + languageName: node + linkType: hard + +"supports-color@npm:^7.1.0": + version: 7.2.0 + resolution: "supports-color@npm:7.2.0" + dependencies: + has-flag: ^4.0.0 + checksum: 8e57067c39216f3c2ffce7cc14ca934d54746192571203aa9c9922d97d2d55cc1bdaa9e41a11f91e620670b5a74ebdec6b548a885d8cc2dea7cab59e21416029 + languageName: node + linkType: hard + +"table@npm:^5.2.3": + version: 5.4.6 + resolution: "table@npm:5.4.6" + dependencies: + ajv: ^6.10.2 + lodash: ^4.17.14 + slice-ansi: ^2.1.0 + string-width: ^3.0.0 + checksum: 38877a196c0a57b955e4965fa3ff1cede38649b6e1f6286aa5435579dfd01663fdf8d19c87510e67a79474d75ae0144a0819f2054d654c45d7f525270aafe56b + languageName: node + linkType: hard + +"text-table@npm:^0.2.0": + version: 0.2.0 + resolution: "text-table@npm:0.2.0" + checksum: 373904ce70524ba11ec7e1905c44fb92671132d5e0b0aba2fb48057161f8bf9cbf7f6178f0adf31810150cf44fb52c7b912dc722bff3fddf9688378596dbeb56 + languageName: node + linkType: hard + +"tsconfig-paths@npm:^3.9.0": + version: 3.9.0 + resolution: "tsconfig-paths@npm:3.9.0" + dependencies: + "@types/json5": ^0.0.29 + json5: ^1.0.1 + minimist: ^1.2.0 + strip-bom: ^3.0.0 + checksum: 5383ba626b3ac70e08094b9dfd1e30ce82878407b6c8db8cd84279cc7c7340d5f53f67dbeb8174a233c082a068322a6b00ec8514b96d9a80a453e0476dc116d2 + languageName: node + linkType: hard + +"type-check@npm:^0.4.0, type-check@npm:~0.4.0": + version: 0.4.0 + resolution: "type-check@npm:0.4.0" + dependencies: + prelude-ls: ^1.2.1 + checksum: 6c2e1ce339567e122504f0c729cfa35d877fb2da293b99110f0819eca81e6ed8d3ba9bb36c0bc0ee4904d5340dbe678f8642a395c1c67b1d0f69f081efb47f4a + languageName: node + linkType: hard + +"type-fest@npm:^0.3.0": + version: 0.3.1 + resolution: "type-fest@npm:0.3.1" + checksum: 508923061144ff7ebc69d4f49bc812c7b8a81c633d10e89191092efb5746531ee6c4dd912db1447e954a766186ed48eee0dcfa53047c55a7646076a76640ff43 + languageName: node + linkType: hard + +"type-fest@npm:^0.8.1": + version: 0.8.1 + resolution: "type-fest@npm:0.8.1" + checksum: f8c4b4249f52e8bea7a4fc55b3653c96c2d547240e4c772e001d02b7cc38b8c3eb493ab9fbe985a76a203cd1aa7044776b728a71ba12bf36e7131f989597885b + languageName: node + linkType: hard + +"unbox-primitive@npm:^1.0.0": + version: 1.0.0 + resolution: "unbox-primitive@npm:1.0.0" + dependencies: + function-bind: ^1.1.1 + has-bigints: ^1.0.0 + has-symbols: ^1.0.0 + which-boxed-primitive: ^1.0.1 + checksum: 25e82f99bb40981f30615644305c757ecefff43d2ef2ac1b80e24f304f3002cd637eecb672bdd07f5fb858a265d96a4b2e983c714cba65498715acf7af23e86b + languageName: node + linkType: hard + +"uri-js@npm:^4.2.2": + version: 4.4.1 + resolution: "uri-js@npm:4.4.1" + dependencies: + punycode: ^2.1.0 + checksum: 7d8ae8e2d7b82480d7d337f3e53c9a89ffdc7ebb1c31f212da3df6349f2fd1e6a4361f5fb27369ecab33fa37aa85edc53aec6eb7c9a7c3207a9e0944e8c48802 + languageName: node + linkType: hard + +"v8-compile-cache@npm:^2.0.3": + version: 2.3.0 + resolution: "v8-compile-cache@npm:2.3.0" + checksum: b56f83d9ff14187562badc4955dadeef53ff3abde478ce60759539dd8d5472a91fce9db6083fc2450e54cef6f2110c1a28d8c12162dbf575a6cfcb846986904b + languageName: node + linkType: hard + +"validate-npm-package-license@npm:^3.0.1": + version: 3.0.4 + resolution: "validate-npm-package-license@npm:3.0.4" + dependencies: + spdx-correct: ^3.0.0 + spdx-expression-parse: ^3.0.0 + checksum: 940899bd4eacfa012ceecb10a5814ba0e8103da5243aa74d0d62f1f8a405efcd23e034fb7193e2d05b392870c53aabcb1f66439b062075cdcb28bc5d562a8ff6 + languageName: node + linkType: hard + +"which-boxed-primitive@npm:^1.0.1": + version: 1.0.2 + resolution: "which-boxed-primitive@npm:1.0.2" + dependencies: + is-bigint: ^1.0.1 + is-boolean-object: ^1.1.0 + is-number-object: ^1.0.4 + is-string: ^1.0.5 + is-symbol: ^1.0.3 + checksum: 771ef43357afbba9febf2da4867b2971ada0a5126227f9b7926751525e3721f7f5f3722f8c60af67881714d9a82a98ed686f1768490cfb2cd40518df5f2e056e + languageName: node + linkType: hard + +"which@npm:^2.0.1": + version: 2.0.2 + resolution: "which@npm:2.0.2" + dependencies: + isexe: ^2.0.0 + bin: + node-which: ./bin/node-which + checksum: ea9b1db1266b08f7880717cf70dd9012dd523e5a317f10fbe4d5e8c1a761c5fd237f88642f2ba33b23f973ff4002c9b26648d63084ab208d8ecef36497315f6e + languageName: node + linkType: hard + +"word-wrap@npm:^1.2.3": + version: 1.2.3 + resolution: "word-wrap@npm:1.2.3" + checksum: 6526abd75d4409c76d1989cf2fbf6080b903db29824be3d17d0a0b8f6221486c76a021174eda2616cf311199787983c34bae3c5e7b51d2ad7476f2066cddb75a + languageName: node + linkType: hard + +"wrappy@npm:1": + version: 1.0.2 + resolution: "wrappy@npm:1.0.2" + checksum: 519fcda0fcdf0c16327be2de9d98646742307bc830277e8868529fcf7566f2b330a6453c233e0cdcb767d5838dd61a90984a02ecc983bcddebea5ad0833bbf98 + languageName: node + linkType: hard + +"write@npm:1.0.3": + version: 1.0.3 + resolution: "write@npm:1.0.3" + dependencies: + mkdirp: ^0.5.1 + checksum: e8f8fddefea3eaaf4c8bacf072161f82d5e05c5fb8f003e1bae52673b94b88a4954d97688c7403a20301d2f6e9f61363b1affe74286b499b39bc0c42f17a56cb + languageName: node + linkType: hard + +"xdg-basedir@npm:^4.0.0": + version: 4.0.0 + resolution: "xdg-basedir@npm:4.0.0" + checksum: 928953cb7dda8e2475932f748847a3aae751f44864a0067b03e5ca66820a36e1e9ffb647f9b06fb68ebcb0b9d06d5aee630717a1d2501be14cec99f82efa2fe6 + languageName: node + linkType: hard + +"yallist@npm:^4.0.0": + version: 4.0.0 + resolution: "yallist@npm:4.0.0" + checksum: a2960ef879af6ee67a76cae29bac9d8bffeb6e9e366c217dbd21464e7fce071933705544724f47e90ba5209cf9c83c17d5582dd04415d86747a826b2a231efb8 + languageName: node + linkType: hard diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/ActionTests.cs b/tests/packages/Microsoft.Bot.Components.Teams.Tests/ActionTests.cs deleted file mode 100644 index c4f5ff73f7..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/ActionTests.cs +++ /dev/null @@ -1,592 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Bot.Builder; -using Microsoft.Bot.Builder.Dialogs; -using Microsoft.Bot.Builder.Dialogs.Adaptive; -using Microsoft.Bot.Builder.Dialogs.Adaptive.Testing; -using Microsoft.Bot.Builder.Dialogs.Declarative; -using Microsoft.Bot.Builder.Dialogs.Declarative.Obsolete; -using Microsoft.Bot.Connector; -using Microsoft.Bot.Connector.Authentication; -using Microsoft.Bot.Schema; -using Microsoft.Bot.Schema.Teams; -using Newtonsoft.Json.Linq; -using Xunit; - -namespace Microsoft.Bot.Components.Teams.Tests -{ - [CollectionDefinition("Dialogs.Adaptive")] - public class ActionTests : IClassFixture - { - private readonly ResourceExplorerFixture _resourceExplorerFixture; - - public ActionTests(ResourceExplorerFixture resourceExplorerFixture) - { - ComponentRegistration.Add(new DeclarativeComponentRegistration()); - ComponentRegistration.Add(new DialogsComponentRegistration()); - ComponentRegistration.Add(new AdaptiveComponentRegistration()); - ComponentRegistration.Add(new LanguageGenerationComponentRegistration()); - ComponentRegistration.Add(new AdaptiveTestingComponentRegistration()); - ComponentRegistration.Add(new DeclarativeComponentRegistrationBridge()); - - _resourceExplorerFixture = resourceExplorerFixture.Initialize(nameof(ActionTests)); - } - - [Fact] - public async Task Action_GetMeetingParticipant() - { - var participantResult = GetParticipant().ToString(); - - var uriToContent = new Dictionary() - { - { "/v1/meetings/meeting-id-1/participants/participant-aad-id-1?tenantId=tenant-id-1", participantResult }, - { "/v1/meetings/customMeetingId/participants/customParticipantId?tenantId=customTenantId", participantResult } - }; - - var teamsMiddleware = GetTeamsMiddleware(uriToContent); - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer, middleware: new[] { teamsMiddleware }); - } - - [Fact] - public async Task Action_GetMeetingParticipantError() - { - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer, adapterChannel: Channels.Test); - } - - [Fact] - public async Task Action_GetMeetingParticipantErrorWithAdapter() - { - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer); - } - - [Fact] - public async Task Action_GetMember() - { - var participantResult = GetParticipant().ToString(); - var uriToContent = new Dictionary() - { - { "/v3/conversations/Action_GetMember/members/member-id", participantResult }, - { "/v3/conversations/Action_GetMember/members/customMemberId", participantResult } - }; - - var teamsMiddleware = GetTeamsMiddleware(uriToContent); - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer, middleware: new[] { teamsMiddleware }); - } - - [Fact] - public async Task Action_GetMemberError() - { - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer, adapterChannel: Channels.Test); - } - - [Fact] - public async Task Action_GetMemberErrorWithAdapter() - { - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer); - } - - [Fact] - public async Task Action_GetPagedMembers() - { - var threeMembers = GenerateTeamMembers(3); - threeMembers.ContinuationToken = "customToken"; - - var twoMembers = GenerateTeamMembers(2); - twoMembers.ContinuationToken = "token"; - - var uriToContent = new Dictionary() - { - { "/v3/conversations/Action_GetPagedMembers/pagedmembers", JObject.FromObject(threeMembers).ToString() }, - { "/v3/conversations/Action_GetPagedMembers/pagedmembers?pageSize=2&continuationToken=token", JObject.FromObject(twoMembers).ToString() } - }; - - var teamsMiddleware = GetTeamsMiddleware(uriToContent); - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer, middleware: new[] { teamsMiddleware }); - } - - [Fact] - public async Task Action_GetPagedMembersError() - { - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer, adapterChannel: Channels.Test); - } - - [Fact] - public async Task Action_GetPagedTeamMembers() - { - var threeMembers = GenerateTeamMembers(3); - threeMembers.ContinuationToken = "token"; - - var twoMembers = GenerateTeamMembers(2); - twoMembers.ContinuationToken = "token"; - - var uriToContent = new Dictionary() - { - { "/v3/conversations/team-id-1/pagedmembers", JObject.FromObject(threeMembers).ToString() }, - { "/v3/conversations/team-id-1/pagedmembers?pageSize=2&continuationToken=token", JObject.FromObject(twoMembers).ToString() } - }; - - var teamsMiddleware = GetTeamsMiddleware(uriToContent); - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer, middleware: new[] { teamsMiddleware }); - } - - [Fact] - public async Task Action_GetPagedTeamMembersError() - { - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer, adapterChannel: Channels.Test); - } - - [Fact] - public async Task Action_GetTeamChannels() - { - var conversations = JObject.FromObject(new ConversationList - { - Conversations = new List() - { - new ChannelInfo { Id = "19:ChannelIdgeneralChannelId@thread.skype", Name = "Testing0" }, - new ChannelInfo { Id = "19:somechannelId2e5ab3df9ae9b594bdb@thread.skype", Name = "Testing1" }, - new ChannelInfo { Id = "19:somechannelId388ade16aa4dd375e69@thread.skype", Name = "Testing2" }, - } - }).ToString(); - - var uriToContent = new Dictionary() - { - { "/v3/teams/team-id-1/conversations", conversations }, - { "/v3/teams/customTeamId/conversations", conversations } - }; - - var teamsMiddleware = GetTeamsMiddleware(uriToContent); - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer, middleware: new[] { teamsMiddleware }); - } - - [Fact] - public async Task Action_GetTeamChannelsError() - { - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer, adapterChannel: Channels.Test); - } - - [Fact] - public async Task Action_GetTeamDetails() - { - var details = JObject.FromObject(new TeamDetails - { - Id = "19:generalChannelIdgeneralChannelId@thread.skype", - Name = "TeamName", - AadGroupId = "Team-aadGroupId" - }).ToString(); - - var uriToContent = new Dictionary() - { - { "/v3/teams/team-id-1/team-id-1", details }, - { "/v3/teams/customTeamId", details } - }; - - var teamsMiddleware = GetTeamsMiddleware(uriToContent); - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer, middleware: new[] { teamsMiddleware }); - } - - [Fact] - public async Task Action_GetTeamDetailsError() - { - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer, adapterChannel: Channels.Test); - } - - [Fact] - public async Task Action_GetTeamMember() - { - var member = JObject.FromObject(GenerateTeamMembers(1).Members.First()).ToString(); - - var uriToContent = new Dictionary() - { - { "/v3/conversations/team-id-1/members/user1", member }, - { "/v3/conversations/customTeamId/members/customMemberId", member } - }; - - var teamsMiddleware = GetTeamsMiddleware(uriToContent); - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer, middleware: new[] { teamsMiddleware }); - } - - [Fact] - public async Task Action_GetTeamMemberError() - { - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer, adapterChannel: Channels.Test); - } - - [Fact] - public async Task Action_GetTeamMemberErrorWithAdapter() - { - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer); - } - - [Fact] - public async Task Action_SendAppBasedLinkQueryResponse() - { - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer); - } - - [Fact] - public async Task Action_SendAppBasedLinkQueryResponseError() - { - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer, adapterChannel: Channels.Test); - } - - //[Fact] - //public async Task Action_SendMessageToTeamsChannel() - //{ - // NOTE: Current test adapter is not a BotFrameworkAdapter, - // and does not support mocking SendMessageToTeamsChannel - // var teamsMiddleware = GetTeamsMiddleware(new JObject(), "/v3/conversations"); - // await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer, middleware: new[] { teamsMiddleware }); - //} - - [Fact] - public async Task Action_SendMessageToTeamsChannelError() - { - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer, adapterChannel: Channels.Test); - } - - [Fact] - public async Task Action_SendMEActionResponse() - { - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer); - } - - [Fact] - public async Task Action_SendMEActionResponseError() - { - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer); - } - - [Fact] - public async Task Action_SendMEAttachmentsResponse() - { - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer); - } - - [Fact] - public async Task Action_SendMEAttachmentsResponseError() - { - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer, adapterChannel: Channels.Test); - } - - [Fact] - public async Task Action_SendMEAuthResponse() - { - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer); - } - - //[Fact] - //public async Task Action_SendMEAuthResponseError() - //{ - // NOTE: Current test adapter is not a BotFrameworkAdapter, - // await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer); - //} - - [Fact] - public async Task Action_SendMEAuthResponseErrorWithAdapter() - { - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer, adapterChannel: Channels.Test); - } - - [Fact] - public async Task Action_SendMEBotMessagePreviewResponse() - { - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer); - } - - [Fact] - public async Task Action_SendMEBotMessagePreviewResponseError() - { - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer, adapterChannel: Channels.Test); - } - - [Fact] - public async Task Action_SendMEConfigQuerySettingUrlResponse() - { - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer); - } - - [Fact] - public async Task Action_SendMEConfigQuerySettingUrlResponseError() - { - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer, adapterChannel: Channels.Test); - } - - [Fact] - public async Task Action_SendMEMessageResponse() - { - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer); - } - - [Fact] - public async Task Action_SendMEMessageResponseError() - { - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer, adapterChannel: Channels.Test); - } - - [Fact] - public async Task Action_SendMESelectItemResponse() - { - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer); - } - - [Fact] - public async Task Action_SendMESelectItemResponseError() - { - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer, adapterChannel: Channels.Test); - } - - [Fact] - public async Task Action_SendTaskModuleCardResponse() - { - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer); - } - - [Fact] - public async Task Action_SendTaskModuleCardResponseError() - { - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer, adapterChannel: Channels.Test); - } - - [Fact] - public async Task Action_SendTaskModuleMessageResponse() - { - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer); - } - - [Fact] - public async Task Action_SendTaskModuleUrlResponse() - { - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer); - } - - [Fact] - public async Task Action_SendTaskModuleUrlResponseError() - { - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer); - } - - [Fact] - public async Task Action_SendTabCardResponseError() - { - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer); - } - - [Fact] - public async Task Action_SendTabCardResponse() - { - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer); - } - - [Fact] - public async Task Action_SendTabAuthResponseErrorWithAdapter() - { - await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer); - } - - //[Fact] - //public async Task Action_SendTabAuthResponseError() - //{ - // NOTE: Current test adapter is not a BotFrameworkAdapter, - // await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer); - //} - - private IMiddleware GetErrorTeamsMiddleware(string exception) - { - // Create a connector client, setup with a custom httpclient which will - // throw an exception when the connectorclient's outgoing pipeline's SendAsync - // is called - var messageHandler = new ErrorHttpMessageHandler(exception); - return GetTestConnectorClientMiddleware(messageHandler); - } - - private ConversationReference GetGroupConversation() - { - return new ConversationReference - { - ChannelId = Channels.Msteams, - User = new ChannelAccount - { - Id = "29:User-Id", - Name = "User Name", - AadObjectId = "participant-aad-id" - }, - Conversation = new ConversationAccount - { - ConversationType = "groupChat", - TenantId = "tenantId-Guid", - Name = "group", - IsGroup = true, - Id = "19:groupChatId@thread.v2" - } - }; - } - - private TeamsPagedMembersResult GenerateTeamMembers(int total) - { - var accounts = new List(); - - for (int count = 0; count < total; count++) - { - accounts.Add(new TeamsChannelAccount - { - Id = $"29:User-Id-{count}", - Name = $"User Name-{count}", - AadObjectId = $"User-{count}-Object-Id", - Surname = $"Surname-{count}", - Email = $"User.{count}@microsoft.com", - UserPrincipalName = $"user{count}@microsoft.com", - TenantId = "tenant-id-1", - GivenName = "User" - }); - } - - return new TeamsPagedMembersResult() { Members = accounts }; - } - - private JObject GetParticipant(bool groupConversation = false) - { - return JObject.FromObject(new - { - id = "29:User-Id-0", - objectId = "User-0-Object-Id", - name = "User Name-0", - meeting = new { role = "Organizer" }, - surname = "Surname-0", - tenantId = "tenant-id-1", - userPrincipalName = "user0@microsoft.com", - user = new - { - userPrincipalName = "userPrincipalName-1", - }, - email = "User.0@microsoft.com", - givenName = "User", - conversation = new - { - id = groupConversation ? "19:groupChatId@thread.v2" : "a:oneOnOneConversationId", - name = groupConversation ? "group" : "oneOnOne", - tenantId = "tenantId-Guid", - conversationType = groupConversation ? "groupChat" : "personal", - isGroup = groupConversation, - } - }); - } - - private IMiddleware GetTeamsMiddleware(JObject result, string path = null) - { - // Create a connector client, setup with a custom httpclient which will return - // the desired result through the TestHttpMessageHandler. - TestsHttpMessageHandler messageHandler; - if (!string.IsNullOrEmpty(path)) - { - messageHandler = new TestsHttpMessageHandler(path, result.ToString()); - } - else - { - messageHandler = new TestsHttpMessageHandler(result.ToString()); - } - - return GetTestConnectorClientMiddleware(messageHandler); - } - - private IMiddleware GetTeamsMiddleware(Dictionary results) - { - // Create a connector client, setup with a custom httpclient which will return - // the desired result through the TestHttpMessageHandler. - var messageHandler = new TestsHttpMessageHandler(results); - return GetTestConnectorClientMiddleware(messageHandler); - } - - private TestConnectorClientMiddleware GetTestConnectorClientMiddleware(HttpMessageHandler messageHandler) - { - var testHttpClient = new HttpClient(messageHandler); - testHttpClient.BaseAddress = new Uri("https://localhost.coffee"); - var testConnectorClient = new ConnectorClient(new Uri("http://localhost.coffee/"), new MicrosoftAppCredentials(string.Empty, string.Empty), testHttpClient); - return new TestConnectorClientMiddleware(testConnectorClient); - } - - // This middleware sets the turnstate's connector client, - // so it will be found by the adapter, and a new one not created. - private class TestConnectorClientMiddleware : IMiddleware - { - private IConnectorClient _connectorClient; - - public TestConnectorClientMiddleware(IConnectorClient connectorClient) - { - _connectorClient = connectorClient; - } - - public Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken = default) - { - turnContext.TurnState.Add(_connectorClient); - return next(cancellationToken); - } - } - - private class ErrorHttpMessageHandler : HttpMessageHandler - { - private readonly string _error; - - public ErrorHttpMessageHandler(string error) - { - _error = error; - } - - protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - throw new Exception(_error); - } - } - - // Message handler to mock returning a specific object when requested from a specified path. - private class TestsHttpMessageHandler : HttpMessageHandler - { - private Dictionary _uriToContent; - private string _content; - - public TestsHttpMessageHandler(string url, string content) - : this(new Dictionary() { { url, content } }) - { - } - - public TestsHttpMessageHandler(string content) - { - _content = content; - } - - public TestsHttpMessageHandler(Dictionary uriToContent) - { - _uriToContent = uriToContent; - } - - protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - var response = new HttpResponseMessage(HttpStatusCode.OK); - if (!string.IsNullOrEmpty(_content)) - { - response.Content = new StringContent(_content); - } - else - { - var path = request.RequestUri.PathAndQuery; - foreach (var urlAndContent in _uriToContent) - { - if (urlAndContent.Key.Contains(path, System.StringComparison.OrdinalIgnoreCase)) - { - response.Content = new StringContent(urlAndContent.Value); - } - } - } - - return Task.FromResult(response); - } - } - } -} diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/ConditionalTests.cs b/tests/packages/Microsoft.Bot.Components.Teams.Tests/ConditionalTests.cs deleted file mode 100644 index 8c744a42d9..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/ConditionalTests.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System.IO; -using System.Threading.Tasks; -using Microsoft.Bot.Builder; -using Microsoft.Bot.Builder.Dialogs; -using Microsoft.Bot.Builder.Dialogs.Adaptive; -using Microsoft.Bot.Builder.Dialogs.Adaptive.Testing; -using Microsoft.Bot.Builder.Dialogs.Declarative; -using Microsoft.Bot.Builder.Dialogs.Declarative.Obsolete; -using Microsoft.Bot.Builder.Dialogs.Declarative.Resources; -using Xunit; - -namespace Microsoft.Bot.Components.Teams.Tests -{ - public class ConditionalTests - { - public ConditionalTests() - { - ComponentRegistration.Add(new DeclarativeComponentRegistration()); - ComponentRegistration.Add(new DialogsComponentRegistration()); - ComponentRegistration.Add(new AdaptiveComponentRegistration()); - ComponentRegistration.Add(new LanguageGenerationComponentRegistration()); - ComponentRegistration.Add(new AdaptiveTestingComponentRegistration()); - ComponentRegistration.Add(new DeclarativeComponentRegistrationBridge()); - - ResourceExplorer = new ResourceExplorer() - .AddFolder(Path.Combine(TestUtils.GetProjectPath(), "Tests", nameof(ConditionalTests)), monitorChanges: false); - } - - public static ResourceExplorer ResourceExplorer { get; set; } - - [Fact] - public async Task ConditionalsTests_OnTeamsActivityTypes() - { - await TestUtils.RunTestScript(ResourceExplorer); - } - } -} diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Microsoft.Bot.Components.Teams.Tests.csproj b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Microsoft.Bot.Components.Teams.Tests.csproj deleted file mode 100644 index c49768951b..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Microsoft.Bot.Components.Teams.Tests.csproj +++ /dev/null @@ -1,35 +0,0 @@ - - - - netcoreapp2.1 - netcoreapp3.1 - netcoreapp2.1;netcoreapp3.1 - false - Debug;Release - latest - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - Always - - - - diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/ResourceExplorerFixture.cs b/tests/packages/Microsoft.Bot.Components.Teams.Tests/ResourceExplorerFixture.cs deleted file mode 100644 index 7b2b192b71..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/ResourceExplorerFixture.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.IO; -using Microsoft.Bot.Builder.Dialogs.Declarative.Resources; - -namespace Microsoft.Bot.Components.Teams.Tests -{ - public class ResourceExplorerFixture : IDisposable - { - private string _folderPath = string.Empty; - - public ResourceExplorerFixture() - { - ResourceExplorer = new ResourceExplorer(); - } - - public ResourceExplorer ResourceExplorer { get; private set; } - - public ResourceExplorerFixture Initialize(string resourceFolder) - { - if (_folderPath.Length == 0) - { - _folderPath = Path.Combine(TestUtils.GetProjectPath(), "Tests", resourceFolder); - ResourceExplorer = ResourceExplorer.AddFolder(_folderPath, monitorChanges: false); - } - - return this; - } - - public void Dispose() - { - _folderPath = string.Empty; - ResourceExplorer.Dispose(); - } - } -} diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/TestUtils.cs b/tests/packages/Microsoft.Bot.Components.Teams.Tests/TestUtils.cs deleted file mode 100644 index f8f136dc1b..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/TestUtils.cs +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; -using Microsoft.Bot.Builder; -using Microsoft.Bot.Builder.Adapters; -using Microsoft.Bot.Builder.Dialogs.Adaptive.Testing; -using Microsoft.Bot.Builder.Dialogs.Declarative.Resources; -using Microsoft.Bot.Connector; -using Microsoft.Bot.Schema; -using Microsoft.Extensions.Configuration; - -namespace Microsoft.Bot.Components.Teams.Tests -{ - public class TestUtils - { - public static IConfiguration DefaultConfiguration { get; set; } = new ConfigurationBuilder().AddInMemoryCollection().Build(); - - public static string RootFolder { get; set; } = GetProjectPath(); - - public static IEnumerable GetTestScripts(string relativeFolder) - { - var testFolder = Path.GetFullPath(Path.Combine(RootFolder, PathUtils.NormalizePath(relativeFolder))); - return Directory.EnumerateFiles(testFolder, "*.test.dialog", SearchOption.AllDirectories).Select(s => new object[] { Path.GetFileName(s) }).ToArray(); - } - - public static async Task RunTestScript(ResourceExplorer resourceExplorer, string resourceId = null, IConfiguration configuration = null, [CallerMemberName] string testName = null, IEnumerable middleware = null, string adapterChannel = Channels.Msteams) - { - var storage = new MemoryStorage(); - var convoState = new ConversationState(storage); - var userState = new UserState(storage); - - var adapter = (TestAdapter)new TestAdapter(CreateConversation(adapterChannel, testName)); - - if (middleware != null) - { - foreach (var m in middleware) - { - adapter.Use(m); - } - } - - adapter.Use(new RegisterClassMiddleware(DefaultConfiguration)) - .UseStorage(storage) - .UseBotState(userState, convoState) - .Use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger(traceActivity: false))); - - adapter.OnTurnError += async (context, err) => - { - if (err.Message.EndsWith("MemoryAssertion failed")) - { - throw err; - } - - await context.SendActivityAsync(err.Message); - }; - - var script = resourceExplorer.LoadType(resourceId ?? $"{testName}.test.dialog"); - script.Configuration = configuration ?? new ConfigurationBuilder().AddInMemoryCollection().Build(); - script.Description ??= resourceId; - await script.ExecuteAsync(adapter: adapter, testName: testName, resourceExplorer: resourceExplorer).ConfigureAwait(false); - } - - public static string GetProjectPath() - { - var parent = Environment.CurrentDirectory; - while (!string.IsNullOrEmpty(parent)) - { - if (Directory.EnumerateFiles(parent, "*proj").Any()) - { - break; - } - - parent = Path.GetDirectoryName(parent); - } - - return parent; - } - - public static ConversationReference CreateConversation(string channel, string conversationName) - { - return new ConversationReference - { - ChannelId = channel ?? Channels.Test, - ServiceUrl = "https://test.com", - User = new ChannelAccount("user1", "User1"), - Bot = new ChannelAccount("bot", "Bot"), - Conversation = new ConversationAccount(false, "personal", conversationName), - Locale = "en-US", - }; - } - } -} diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetMeetingParticipant.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetMeetingParticipant.test.dialog deleted file mode 100644 index a770c1cc0c..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetMeetingParticipant.test.dialog +++ /dev/null @@ -1,66 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnUnknownIntent", - "actions": [ - { - "$kind": "Teams.GetMeetingParticipant", - "property": "conversation.result" - }, - { - "$kind": "Teams.GetMeetingParticipant", - "property": "conversation.resultWithCustomProperties", - "meetingId": "customMeetingId", - "participantId": "customParticipantId", - "tenantId": "customTenantId" - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "hi", - "from": { - "id": "participant-id", - "aadObjectId": "participant-aad-id-1" - }, - "channelData": { - "tenant": { - "id": "tenant-id-1" - }, - "meeting": { - "id": "meeting-id-1" - } - } - } - }, - { - "$kind": "Microsoft.Test.MemoryAssertions", - "assertions": [ - "conversation.result.conversation.conversationType == 'personal'", - "conversation.result.conversation.id == 'a:oneOnOneConversationId'", - "conversation.result.conversation.isGroup == false", - "conversation.result.conversation.name == 'oneOnOne'", - "conversation.result.conversation.tenantId == 'tenantId-Guid'", - "conversation.result.meeting.role == 'Organizer'", - "conversation.result.user.userPrincipalName == 'userPrincipalName-1'", - "conversation.resultWithCustomProperties.conversation.conversationType == 'personal'", - "conversation.resultWithCustomProperties.conversation.id == 'a:oneOnOneConversationId'", - "conversation.resultWithCustomProperties.conversation.isGroup == false", - "conversation.resultWithCustomProperties.conversation.name == 'oneOnOne'", - "conversation.resultWithCustomProperties.conversation.tenantId == 'tenantId-Guid'", - "conversation.resultWithCustomProperties.meeting.role == 'Organizer'", - "conversation.resultWithCustomProperties.user.userPrincipalName == 'userPrincipalName-1'" - ] - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetMeetingParticipantError.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetMeetingParticipantError.test.dialog deleted file mode 100644 index e4d8551cdd..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetMeetingParticipantError.test.dialog +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnUnknownIntent", - "actions": [ - { - "$kind": "Teams.GetMeetingParticipant", - "property": "$result" - }, - { - "$kind": "Microsoft.SendActivity", - "activity": "${$result}" - } - ] - }, - { - "$kind": "Microsoft.OnError", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "${turn.dialogEvent.value.message}" - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserSays", - "text": "hi" - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "Teams.GetMeetingParticipant works only on the Teams channel." - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetMeetingParticipantErrorWithAdapter.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetMeetingParticipantErrorWithAdapter.test.dialog deleted file mode 100644 index eab01c2edc..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetMeetingParticipantErrorWithAdapter.test.dialog +++ /dev/null @@ -1,43 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnUnknownIntent", - "actions": [ - { - "$kind": "Teams.GetMeetingParticipant", - "property": "$result", - "participantId": "=turn.channelData.doesNotExist" - }, - { - "$kind": "Microsoft.SendActivity", - "activity": "${$result}" - } - ] - }, - { - "$kind": "Microsoft.OnError", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "${turn.dialogEvent.value.message}" - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserSays", - "text": "hi" - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "Teams.GetMeetingParticipant could not determine the participant id by expression value provided. participantId is required." - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetMember.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetMember.test.dialog deleted file mode 100644 index 90d44b865c..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetMember.test.dialog +++ /dev/null @@ -1,65 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnUnknownIntent", - "actions": [ - { - "$kind": "Teams.GetMember", - "property": "conversation.result" - }, - { - "$kind": "Teams.GetMember", - "property": "conversation.resultWithCustomProperties", - "memberId": "customMemberId" - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "hi", - "from": { - "id": "member-id" - }, - "channelData": { - "tenant": { - "id": "tenant-id-1" - }, - "meeting": { - "id": "meeting-id-1" - } - } - } - }, - { - "$kind": "Microsoft.Test.MemoryAssertions", - "assertions": [ - "conversation.result.email == 'User.0@microsoft.com'", - "conversation.result.givenName == 'User'", - "conversation.result.id == '29:User-Id-0'", - "conversation.result.name == 'User Name-0'", - "conversation.result.objectId == 'User-0-Object-Id'", - "conversation.result.surname == 'Surname-0'", - "conversation.result.tenantId == 'tenant-id-1'", - "conversation.result.userPrincipalName == 'user0@microsoft.com'", - "conversation.resultWithCustomProperties.email == 'User.0@microsoft.com'", - "conversation.resultWithCustomProperties.givenName == 'User'", - "conversation.resultWithCustomProperties.id == '29:User-Id-0'", - "conversation.resultWithCustomProperties.name == 'User Name-0'", - "conversation.resultWithCustomProperties.objectId == 'User-0-Object-Id'", - "conversation.resultWithCustomProperties.surname == 'Surname-0'", - "conversation.resultWithCustomProperties.tenantId == 'tenant-id-1'", - "conversation.resultWithCustomProperties.userPrincipalName == 'user0@microsoft.com'" - ] - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetMemberError.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetMemberError.test.dialog deleted file mode 100644 index 03ed714e8c..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetMemberError.test.dialog +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnUnknownIntent", - "actions": [ - { - "$kind": "Teams.GetMember", - "property": "$result" - }, - { - "$kind": "Microsoft.SendActivity", - "activity": "${$result}" - } - ] - }, - { - "$kind": "Microsoft.OnError", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "${turn.dialogEvent.value.message}" - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserSays", - "text": "hi" - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "Teams.GetMember works only on the Teams channel." - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetMemberErrorWithAdapter.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetMemberErrorWithAdapter.test.dialog deleted file mode 100644 index 70b25fc571..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetMemberErrorWithAdapter.test.dialog +++ /dev/null @@ -1,43 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnUnknownIntent", - "actions": [ - { - "$kind": "Teams.GetMember", - "property": "$result", - "memberId": "=turn.activity.channelData.doesNotExist" - }, - { - "$kind": "Microsoft.SendActivity", - "activity": "${$result}" - } - ] - }, - { - "$kind": "Microsoft.OnError", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "${turn.dialogEvent.value.message}" - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserSays", - "text": "hi" - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "Missing MemberId in Teams.GetMember." - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetPagedMembers.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetPagedMembers.test.dialog deleted file mode 100644 index 07dcae5c4f..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetPagedMembers.test.dialog +++ /dev/null @@ -1,68 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnUnknownIntent", - "actions": [ - { - "$kind": "Teams.GetPagedMembers", - "property": "conversation.result" - }, - { - "$kind": "Teams.GetPagedMembers", - "property": "conversation.resultWithCustomProperties", - "continuationToken": "token", - "pageSize": 2 - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "hi", - "from": { - "id": "member-id" - }, - "channelData": { - "tenant": { - "id": "tenant-id-1" - }, - "meeting": { - "id": "meeting-id-1" - } - } - } - }, - { - "$kind": "Microsoft.Test.MemoryAssertions", - "assertions": [ - "conversation.result.continuationToken == 'token'", - "conversation.result.members[0].email == 'User.0@microsoft.com'", - "conversation.result.members[0].id == '29:User-Id-0'", - "conversation.result.members[0].objectId == 'User-0-Object-Id'", - "conversation.result.members[1].email == 'User.1@microsoft.com'", - "conversation.result.members[1].id == '29:User-Id-1'", - "conversation.result.members[1].objectId == 'User-1-Object-Id'", - "conversation.result.members[2].email == 'User.2@microsoft.com'", - "conversation.result.members[2].id == '29:User-Id-2'", - "conversation.result.members[2].objectId == 'User-2-Object-Id'", - "conversation.resultWithCustomProperties.continuationToken == 'customToken'", - "conversation.resultWithCustomProperties.members[0].email == 'User.0@microsoft.com'", - "conversation.resultWithCustomProperties.members[0].id == '29:User-Id-0'", - "conversation.resultWithCustomProperties.members[0].objectId == 'User-0-Object-Id'", - "conversation.resultWithCustomProperties.members[1].email == 'User.1@microsoft.com'", - "conversation.resultWithCustomProperties.members[1].id == '29:User-Id-1'", - "conversation.resultWithCustomProperties.members[1].objectId == 'User-1-Object-Id'", - "not(exists(conversation.resultWithCustomProperties.members[2]))" - ] - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetPagedMembersError.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetPagedMembersError.test.dialog deleted file mode 100644 index 2f2f705eb2..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetPagedMembersError.test.dialog +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnUnknownIntent", - "actions": [ - { - "$kind": "Teams.GetPagedMembers", - "property": "$result" - }, - { - "$kind": "Microsoft.SendActivity", - "activity": "${$result}" - } - ] - }, - { - "$kind": "Microsoft.OnError", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "${turn.dialogEvent.value.message}" - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserSays", - "text": "hi" - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "Teams.GetPagedMembers works only on the Teams channel." - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetPagedTeamMembers.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetPagedTeamMembers.test.dialog deleted file mode 100644 index 559b99576c..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetPagedTeamMembers.test.dialog +++ /dev/null @@ -1,65 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnUnknownIntent", - "actions": [ - { - "$kind": "Teams.GetPagedTeamMembers", - "property": "conversation.result" - }, - { - "$kind": "Teams.GetPagedTeamMembers", - "property": "conversation.resultWithCustomProperties", - "continuationToken": "token", - "pageSize": 2 - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "hi", - "channelData": { - "tenant": { - "id": "tenant-id-1" - }, - "team": { - "id": "team-id-1" - } - } - } - }, - { - "$kind": "Microsoft.Test.MemoryAssertions", - "assertions": [ - "conversation.result.continuationToken == 'token'", - "conversation.result.members[0].email == 'User.0@microsoft.com'", - "conversation.result.members[0].id == '29:User-Id-0'", - "conversation.result.members[0].objectId == 'User-0-Object-Id'", - "conversation.result.members[1].email == 'User.1@microsoft.com'", - "conversation.result.members[1].id == '29:User-Id-1'", - "conversation.result.members[1].objectId == 'User-1-Object-Id'", - "conversation.result.members[2].email == 'User.2@microsoft.com'", - "conversation.result.members[2].id == '29:User-Id-2'", - "conversation.result.members[2].objectId == 'User-2-Object-Id'", - "conversation.resultWithCustomProperties.continuationToken == 'customToken'", - "conversation.resultWithCustomProperties.members[0].email == 'User.0@microsoft.com'", - "conversation.resultWithCustomProperties.members[0].id == '29:User-Id-0'", - "conversation.resultWithCustomProperties.members[0].objectId == 'User-0-Object-Id'", - "conversation.resultWithCustomProperties.members[1].email == 'User.1@microsoft.com'", - "conversation.resultWithCustomProperties.members[1].id == '29:User-Id-1'", - "conversation.resultWithCustomProperties.members[1].objectId == 'User-1-Object-Id'", - "not(exists(conversation.resultWithCustomProperties.members[2]))" - ] - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetPagedTeamMembersError.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetPagedTeamMembersError.test.dialog deleted file mode 100644 index f8b11b4486..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetPagedTeamMembersError.test.dialog +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnUnknownIntent", - "actions": [ - { - "$kind": "Teams.GetPagedTeamMembers", - "property": "$result" - }, - { - "$kind": "Microsoft.SendActivity", - "activity": "${$result}" - } - ] - }, - { - "$kind": "Microsoft.OnError", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "${turn.dialogEvent.value.message}" - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserSays", - "text": "hi" - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "Teams.GetPagedTeamMembers works only on the Teams channel." - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetTeamChannels.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetTeamChannels.test.dialog deleted file mode 100644 index a5e4697ba9..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetTeamChannels.test.dialog +++ /dev/null @@ -1,58 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnUnknownIntent", - "actions": [ - { - "$kind": "Teams.GetTeamChannels", - "property": "conversation.result" - }, - { - "$kind": "Teams.GetTeamChannels", - "property": "conversation.resultWithCustomProperties", - "teamId": "customTeamId" - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "hi", - "channelData": { - "tenant": { - "id": "tenant-id-1" - }, - "team": { - "id": "team-id-1" - } - } - } - }, - { - "$kind": "Microsoft.Test.MemoryAssertions", - "assertions": [ - "conversation.result[0].id == '19:ChannelIdgeneralChannelId@thread.skype'", - "conversation.result[0].name == 'Testing0'", - "conversation.result[1].id == '19:somechannelId2e5ab3df9ae9b594bdb@thread.skype'", - "conversation.result[1].name == 'Testing1'", - "conversation.result[2].id == '19:somechannelId388ade16aa4dd375e69@thread.skype'", - "conversation.result[2].name == 'Testing2'", - "conversation.resultWithCustomProperties[0].id == '19:ChannelIdgeneralChannelId@thread.skype'", - "conversation.resultWithCustomProperties[0].name == 'Testing0'", - "conversation.resultWithCustomProperties[1].id == '19:somechannelId2e5ab3df9ae9b594bdb@thread.skype'", - "conversation.resultWithCustomProperties[1].name == 'Testing1'", - "conversation.resultWithCustomProperties[2].id == '19:somechannelId388ade16aa4dd375e69@thread.skype'", - "conversation.resultWithCustomProperties[2].name == 'Testing2'" - ] - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetTeamChannelsError.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetTeamChannelsError.test.dialog deleted file mode 100644 index 7d6735ab0f..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetTeamChannelsError.test.dialog +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnUnknownIntent", - "actions": [ - { - "$kind": "Teams.GetTeamChannels", - "property": "$result" - }, - { - "$kind": "Microsoft.SendActivity", - "activity": "${$result}" - } - ] - }, - { - "$kind": "Microsoft.OnError", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "${turn.dialogEvent.value.message}" - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserSays", - "text": "hi" - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "Teams.GetTeamChannels works only on the Teams channel." - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetTeamDetails.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetTeamDetails.test.dialog deleted file mode 100644 index 49d9f9068e..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetTeamDetails.test.dialog +++ /dev/null @@ -1,52 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnUnknownIntent", - "actions": [ - { - "$kind": "Teams.GetTeamDetails", - "property": "conversation.result" - }, - { - "$kind": "Teams.GetTeamDetails", - "property": "conversation.resultWithCustomProperties", - "teamId": "customTeamId" - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "hi", - "channelData": { - "tenant": { - "id": "tenant-id-1" - }, - "team": { - "id": "team-id-1" - } - } - } - }, - { - "$kind": "Microsoft.Test.MemoryAssertions", - "assertions": [ - "conversation.result.aadGroupId == 'Team-aadGroupId'", - "conversation.result.id == '19:generalChannelIdgeneralChannelId@thread.skype'", - "conversation.result.name == 'TeamName'", - "conversation.resultWithCustomProperties.aadGroupId == 'Team-aadGroupId'", - "conversation.resultWithCustomProperties.id == '19:generalChannelIdgeneralChannelId@thread.skype'", - "conversation.resultWithCustomProperties.name == 'TeamName'" - ] - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetTeamDetailsError.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetTeamDetailsError.test.dialog deleted file mode 100644 index a501cfdbef..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetTeamDetailsError.test.dialog +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnUnknownIntent", - "actions": [ - { - "$kind": "Teams.GetTeamDetails", - "property": "$result" - }, - { - "$kind": "Microsoft.SendActivity", - "activity": "${$result}" - } - ] - }, - { - "$kind": "Microsoft.OnError", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "${turn.dialogEvent.value.message}" - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserSays", - "text": "hi" - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "Teams.GetTeamDetails works only on the Teams channel." - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetTeamMember.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetTeamMember.test.dialog deleted file mode 100644 index 4a45b57cf6..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetTeamMember.test.dialog +++ /dev/null @@ -1,64 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "enableTrace": true, - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnUnknownIntent", - "actions": [ - { - "$kind": "Teams.GetTeamMember", - "property": "conversation.result" - }, - { - "$kind": "Teams.GetTeamMember", - "property": "conversation.resultWithCustomProperties", - "memberId": "customMemberId", - "teamId": "customTeamId" - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "hi", - "channelData": { - "tenant": { - "id": "tenant-id-1" - }, - "team": { - "id": "team-id-1" - } - } - } - }, - { - "$kind": "Microsoft.Test.MemoryAssertions", - "assertions": [ - "conversation.result.email == 'User.0@microsoft.com'", - "conversation.result.givenName == 'User'", - "conversation.result.id == '29:User-Id-0'", - "conversation.result.name == 'User Name-0'", - "conversation.result.objectId == 'User-0-Object-Id'", - "conversation.result.surname == 'Surname-0'", - "conversation.result.tenantId == 'tenant-id-1'", - "conversation.result.userPrincipalName == 'user0@microsoft.com'", - "conversation.resultWithCustomProperties.email == 'User.0@microsoft.com'", - "conversation.resultWithCustomProperties.givenName == 'User'", - "conversation.resultWithCustomProperties.id == '29:User-Id-0'", - "conversation.resultWithCustomProperties.name == 'User Name-0'", - "conversation.resultWithCustomProperties.objectId == 'User-0-Object-Id'", - "conversation.resultWithCustomProperties.surname == 'Surname-0'", - "conversation.resultWithCustomProperties.tenantId == 'tenant-id-1'", - "conversation.resultWithCustomProperties.userPrincipalName == 'user0@microsoft.com'" - ] - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetTeamMemberError.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetTeamMemberError.test.dialog deleted file mode 100644 index 6f97c8bc51..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetTeamMemberError.test.dialog +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnUnknownIntent", - "actions": [ - { - "$kind": "Teams.GetTeamMember", - "property": "$result" - }, - { - "$kind": "Microsoft.SendActivity", - "activity": "${$result}" - } - ] - }, - { - "$kind": "Microsoft.OnError", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "${turn.dialogEvent.value.message}" - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserSays", - "text": "hi" - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "Teams.GetTeamMember works only on the Teams channel." - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetTeamMemberErrorWithAdapter.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetTeamMemberErrorWithAdapter.test.dialog deleted file mode 100644 index ad843a82c7..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_GetTeamMemberErrorWithAdapter.test.dialog +++ /dev/null @@ -1,43 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnUnknownIntent", - "actions": [ - { - "$kind": "Teams.GetTeamMember", - "property": "$result", - "memberId": "=turn.activity.channelData.doesNotExist" - }, - { - "$kind": "Microsoft.SendActivity", - "activity": "${$result}" - } - ] - }, - { - "$kind": "Microsoft.OnError", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "${turn.dialogEvent.value.message}" - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserSays", - "text": "hi" - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "Missing MemberId in Teams.GetTeamMember." - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendAppBasedLinkQueryResponse.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendAppBasedLinkQueryResponse.test.dialog deleted file mode 100644 index 188d548469..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendAppBasedLinkQueryResponse.test.dialog +++ /dev/null @@ -1,50 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnUnknownIntent", - "actions": [ - { - "$kind": "Teams.SendAppBasedLinkQueryResponse", - "card": { - "type": "message", - "attachments": [ - { - "contentType": "application/vnd.microsoft.card.thumbnail", - "content": { - "title": "card-title" - } - } - ] - } - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "hi", - "name": "composeExtension/queryLink" - } - }, - { - "$kind": "Microsoft.Test.AssertReplyActivity", - "assertions": [ - "type == 'invokeResponse'", - "conversation.id == 'Action_SendAppBasedLinkQueryResponse'", - "value.body.composeExtension.attachmentLayout == 'list'", - "value.body.composeExtension.type == 'result'", - "value.body.composeExtension.attachments[0].contentType == 'application/vnd.microsoft.card.thumbnail'", - "value.body.composeExtension.attachments[0].content.title == 'card-title'" - ] - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendAppBasedLinkQueryResponseError.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendAppBasedLinkQueryResponseError.test.dialog deleted file mode 100644 index 2323a1cb05..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendAppBasedLinkQueryResponseError.test.dialog +++ /dev/null @@ -1,79 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "recognizer": { - "$kind": "Microsoft.RegexRecognizer", - "intents": [ - { - "intent": "NoCard", - "pattern": "NoCard" - }, - { - "intent": "NoAttachments", - "pattern": "NoAttachments" - } - ] - }, - "triggers": [ - { - "$kind": "Microsoft.OnIntent", - "intent": "NoCard", - "actions": [ - { - "$kind": "Teams.SendAppBasedLinkQueryResponse" - } - ] - }, - { - "$kind": "Microsoft.OnIntent", - "intent": "NoAttachments", - "actions": [ - { - "$kind": "Teams.SendAppBasedLinkQueryResponse", - "card": { - "type": "message" - } - } - ] - }, - { - "$kind": "Microsoft.OnError", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "${turn.dialogEvent.value.message}" - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "NoCard", - "name": "composeExtension/queryLink" - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "An activity with attachments is required for Teams.SendAppBasedLinkQueryResponse." - }, - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "NoAttachments", - "name": "composeExtension/queryLink" - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "Invalid activity. An attachment is required for Teams.SendAppBasedLinkQueryResponse." - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEActionResponse.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEActionResponse.test.dialog deleted file mode 100644 index 4f83b08266..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEActionResponse.test.dialog +++ /dev/null @@ -1,56 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnUnknownIntent", - "actions": [ - { - "$kind": "Teams.SendMEActionResponse", - "title": "some title", - "height": 1, - "width": 2, - "completionBotId": "someBotId", - "card": { - "type": "message", - "attachments": [ - { - "contentType": "application/vnd.microsoft.card.thumbnail", - "content": { - "title": "card-title" - } - } - ] - } - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "hi", - "name": "composeExtension/queryLink" - } - }, - { - "$kind": "Microsoft.Test.AssertReplyActivity", - "assertions": [ - "type == 'invokeResponse'", - "conversation.id == 'Action_SendMEActionResponse'", - "value.body.task.value.completionBotId == 'someBotId'", - "value.body.task.value.height == 1", - "value.body.task.value.title == 'some title'", - "value.body.task.value.width == 2", - "value.body.task.value.card.contentType == 'application/vnd.microsoft.card.thumbnail'", - "value.body.task.value.card.content.title == 'card-title'" - ] - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEActionResponseError.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEActionResponseError.test.dialog deleted file mode 100644 index 8fd4f9bb21..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEActionResponseError.test.dialog +++ /dev/null @@ -1,54 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "recognizer": { - "$kind": "Microsoft.RegexRecognizer", - "intents": [ - { - "intent": "NoAttachments", - "pattern": "NoAttachments" - } - ] - }, - "triggers": [ - { - "$kind": "Microsoft.OnIntent", - "intent": "NoAttachments", - "actions": [ - { - "$kind": "Teams.SendMEActionResponse", - "card": { - "type": "message" - } - } - ] - }, - { - "$kind": "Microsoft.OnError", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "${turn.dialogEvent.value.message}" - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "NoAttachments", - "name": "composeExtension/queryLink" - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "Missing attachments in Teams.SendMEActionResponse." - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEAttachmentsResponse.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEAttachmentsResponse.test.dialog deleted file mode 100644 index 5ae8adb325..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEAttachmentsResponse.test.dialog +++ /dev/null @@ -1,50 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnUnknownIntent", - "actions": [ - { - "$kind": "Teams.SendMEAttachmentsResponse", - "attachments": { - "type": "message", - "attachments": [ - { - "contentType": "application/vnd.microsoft.card.thumbnail", - "content": { - "title": "card-title" - } - } - ] - } - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "hi", - "name": "composeExtension/queryLink" - } - }, - { - "$kind": "Microsoft.Test.AssertReplyActivity", - "assertions": [ - "type == 'invokeResponse'", - "conversation.id == 'Action_SendMEAttachmentsResponse'", - "value.body.composeExtension.attachmentLayout == 'list'", - "value.body.composeExtension.type == 'result'", - "value.body.composeExtension.attachments[0].contentType == 'application/vnd.microsoft.card.thumbnail'", - "value.body.composeExtension.attachments[0].content.title == 'card-title'" - ] - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEAttachmentsResponseError.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEAttachmentsResponseError.test.dialog deleted file mode 100644 index ed4f18b0b0..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEAttachmentsResponseError.test.dialog +++ /dev/null @@ -1,44 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnUnknownIntent", - "actions": [ - { - "$kind": "Teams.SendMEAttachmentsResponse", - "attachments": { - "type": "message" - } - } - ] - }, - { - "$kind": "Microsoft.OnError", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "${turn.dialogEvent.value.message}" - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "hi", - "name": "composeExtension/queryLink" - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "Missing attachments in Teams.SendMEAttachmentsResponse." - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEAuthResponse.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEAuthResponse.test.dialog deleted file mode 100644 index 6acfa74080..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEAuthResponse.test.dialog +++ /dev/null @@ -1,41 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnUnknownIntent", - "actions": [ - { - "$kind": "Teams.SendMEAuthResponse", - "connectionName": "testConnection", - "title": "test title" - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "hi", - "name": "composeExtension/queryLink" - } - }, - { - "$kind": "Microsoft.Test.AssertReplyActivity", - "assertions": [ - "type == 'invokeResponse'", - "conversation.id == 'Action_SendMEAuthResponse'", - "value.body.composeExtension.type == 'auth'", - "value.body.composeExtension.suggestedActions.actions[0].title == 'test title'", - "value.body.composeExtension.suggestedActions.actions[0].type == 'openUrl'", - "startsWith(value.body.composeExtension.suggestedActions.actions[0].value, 'https://fake.com/oauthsignin/testConnection')" - ] - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEAuthResponseError.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEAuthResponseError.test.dialog deleted file mode 100644 index b860a92226..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEAuthResponseError.test.dialog +++ /dev/null @@ -1,43 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnUnknownIntent", - "actions": [ - { - "$kind": "Teams.SendMEAuthResponse", - "connectionName": "someConnection", - "title": "someTitle" - } - ] - }, - { - "$kind": "Microsoft.OnError", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "${turn.dialogEvent.value.message}" - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "NoConnectionName", - "name": "composeExtension/queryLink" - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "Teams.SendMEAuthResponse: not supported by the current adapter." - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEAuthResponseErrorWithAdapter.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEAuthResponseErrorWithAdapter.test.dialog deleted file mode 100644 index 7d7425ba9b..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEAuthResponseErrorWithAdapter.test.dialog +++ /dev/null @@ -1,88 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "recognizer": { - "$kind": "Microsoft.RegexRecognizer", - "intents": [ - { - "intent": "NoConnectionName", - "pattern": "NoConnectionName" - }, - { - "intent": "NoTitle", - "pattern": "NoTitle" - } - ] - }, - "triggers": [ - { - "$kind": "Microsoft.OnIntent", - "intent": "NoConnectionName", - "actions": [ - { - "$kind": "Teams.SendMEAuthResponse", - "connectionName": "=turn.channelData.DoesNotExist" - } - ] - }, - { - "$kind": "Microsoft.OnIntent", - "intent": "NoTitle", - "actions": [ - { - "$kind": "Teams.SendMEAuthResponse", - "connectionName": "testConnection", - "title": "=turn.channelData.doesNotExist" - } - ] - }, - { - "$kind": "Microsoft.OnIntent", - "intent": "NoAttachments", - "actions": [ - { - "$kind": "Teams.SendMEAuthResponse" - } - ] - }, - { - "$kind": "Microsoft.OnError", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "${turn.dialogEvent.value.message}" - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "NoConnectionName", - "name": "composeExtension/queryLink" - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "A valid ConnectionName is required for auth responses." - }, - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "NoTitle", - "name": "composeExtension/queryLink" - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "A valid Title is required for auth responses." - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEBotMessagePreviewResponse.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEBotMessagePreviewResponse.test.dialog deleted file mode 100644 index 74343b9138..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEBotMessagePreviewResponse.test.dialog +++ /dev/null @@ -1,52 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnUnknownIntent", - "actions": [ - { - "$kind": "Teams.SendMEBotMessagePreviewResponse", - "card": { - "type": "message", - "attachments": [ - { - "contentType": "application/vnd.microsoft.card.thumbnail", - "content": { - "title": "card-title" - } - } - ] - } - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "hi", - "name": "composeExtension/queryLink" - } - }, - { - "$kind": "Microsoft.Test.AssertReplyActivity", - "assertions": [ - "type == 'invokeResponse'", - "conversation.id == 'Action_SendMEBotMessagePreviewResponse'", - "value.body.composeExtension.type == 'botMessagePreview'", - "value.body.composeExtension.activityPreview.attachmentLayout == 'list'", - "value.body.composeExtension.activityPreview.inputHint == 'acceptingInput'", - "value.body.composeExtension.activityPreview.type == 'message'", - "value.body.composeExtension.activityPreview.attachments[0].contentType == 'application/vnd.microsoft.card.thumbnail'", - "value.body.composeExtension.activityPreview.attachments[0].content.title == 'card-title'" - ] - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEBotMessagePreviewResponseError.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEBotMessagePreviewResponseError.test.dialog deleted file mode 100644 index 3ef5fd3bac..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEBotMessagePreviewResponseError.test.dialog +++ /dev/null @@ -1,79 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "recognizer": { - "$kind": "Microsoft.RegexRecognizer", - "intents": [ - { - "intent": "NoCard", - "pattern": "NoCard" - }, - { - "intent": "NoAttachments", - "pattern": "NoAttachments" - } - ] - }, - "triggers": [ - { - "$kind": "Microsoft.OnIntent", - "intent": "NoCard", - "actions": [ - { - "$kind": "Teams.SendMEBotMessagePreviewResponse" - } - ] - }, - { - "$kind": "Microsoft.OnIntent", - "intent": "NoAttachments", - "actions": [ - { - "$kind": "Teams.SendMEBotMessagePreviewResponse", - "card": { - "type": "message" - } - } - ] - }, - { - "$kind": "Microsoft.OnError", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "${turn.dialogEvent.value.message}" - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "NoCard", - "name": "composeExtension/queryLink" - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "A valid Card is required for Teams.SendMEBotMessagePreviewResponse." - }, - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "NoAttachments", - "name": "composeExtension/queryLink" - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "Invalid activity. An attachment is required for Teams.SendMEBotMessagePreviewResponse." - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEConfigQuerySettingUrlResponse.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEConfigQuerySettingUrlResponse.test.dialog deleted file mode 100644 index 65d8447602..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEConfigQuerySettingUrlResponse.test.dialog +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnUnknownIntent", - "actions": [ - { - "$kind": "Teams.SendMEConfigQuerySettingUrlResponse", - "configUrl": "someBaseUrl.com/somePage.html" - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "hi", - "name": "composeExtension/queryLink" - } - }, - { - "$kind": "Microsoft.Test.AssertReplyActivity", - "assertions": [ - "type == 'invokeResponse'", - "conversation.id == 'Action_SendMEConfigQuerySettingUrlResponse'", - "value.body.composeExtension.type == 'config'", - "value.body.composeExtension.suggestedActions.actions[0].type == 'openUrl'", - "value.body.composeExtension.suggestedActions.actions[0].value == 'someBaseUrl.com/somePage.html'" - ] - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEConfigQuerySettingUrlResponseError.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEConfigQuerySettingUrlResponseError.test.dialog deleted file mode 100644 index 6245df449d..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEConfigQuerySettingUrlResponseError.test.dialog +++ /dev/null @@ -1,32 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnUnknownIntent", - "actions": [ - { - "$kind": "Teams.SendMEConfigQuerySettingUrlResponse" - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "hi", - "name": "composeExtension/queryLink" - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "ConfigUrl is required for Teams.SendMEConfigQuerySettingUrlResponse." - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEMessageResponse.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEMessageResponse.test.dialog deleted file mode 100644 index 0efa1dfde0..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEMessageResponse.test.dialog +++ /dev/null @@ -1,38 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnUnknownIntent", - "actions": [ - { - "$kind": "Teams.SendMEMessageResponse", - "message": "i want to send this to the user" - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "hi", - "name": "composeExtension/queryLink" - } - }, - { - "$kind": "Microsoft.Test.AssertReplyActivity", - "assertions": [ - "type == 'invokeResponse'", - "conversation.id == 'Action_SendMEMessageResponse'", - "value.body.composeExtension.type == 'message'", - "value.body.composeExtension.text == 'i want to send this to the user'" - ] - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEMessageResponseError.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEMessageResponseError.test.dialog deleted file mode 100644 index 6f4f595c4b..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMEMessageResponseError.test.dialog +++ /dev/null @@ -1,32 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnUnknownIntent", - "actions": [ - { - "$kind": "Teams.SendMEMessageResponse" - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "hi", - "name": "composeExtension/queryLink" - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "A Message is required for Teams.SendMEMessageResponse." - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMESelectItemResponse.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMESelectItemResponse.test.dialog deleted file mode 100644 index b9a8f8ddb2..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMESelectItemResponse.test.dialog +++ /dev/null @@ -1,50 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnUnknownIntent", - "actions": [ - { - "$kind": "Teams.SendMESelectItemResponse", - "card": { - "type": "message", - "attachments": [ - { - "contentType": "application/vnd.microsoft.card.thumbnail", - "content": { - "title": "card-title" - } - } - ] - } - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "hi", - "name": "composeExtension/queryLink" - } - }, - { - "$kind": "Microsoft.Test.AssertReplyActivity", - "assertions": [ - "type == 'invokeResponse'", - "conversation.id == 'Action_SendMESelectItemResponse'", - "value.body.composeExtension.type == 'result'", - "value.body.composeExtension.attachmentLayout == 'list'", - "value.body.composeExtension.attachments[0].contentType == 'application/vnd.microsoft.card.thumbnail'", - "value.body.composeExtension.attachments[0].content.title == 'card-title'" - ] - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMESelectItemResponseError.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMESelectItemResponseError.test.dialog deleted file mode 100644 index a78eb61c61..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMESelectItemResponseError.test.dialog +++ /dev/null @@ -1,79 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "recognizer": { - "$kind": "Microsoft.RegexRecognizer", - "intents": [ - { - "intent": "NoCard", - "pattern": "NoCard" - }, - { - "intent": "NoAttachments", - "pattern": "NoAttachments" - } - ] - }, - "triggers": [ - { - "$kind": "Microsoft.OnIntent", - "intent": "NoCard", - "actions": [ - { - "$kind": "Teams.SendMESelectItemResponse" - } - ] - }, - { - "$kind": "Microsoft.OnIntent", - "intent": "NoAttachments", - "actions": [ - { - "$kind": "Teams.SendMESelectItemResponse", - "card": { - "type": "message" - } - } - ] - }, - { - "$kind": "Microsoft.OnError", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "${turn.dialogEvent.value.message}" - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "NoCard", - "name": "composeExtension/queryLink" - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "A valid card is required for Teams.SendMESelectItemResponse." - }, - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "NoAttachments", - "name": "composeExtension/queryLink" - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "Invalid activity. An attachment is required for Teams.SendMESelectItemResponse." - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMessageToTeamsChannel.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMessageToTeamsChannel.test.dialog deleted file mode 100644 index 6996effedb..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMessageToTeamsChannel.test.dialog +++ /dev/null @@ -1,63 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnUnknownIntent", - "actions": [ - { - "$kind": "Teams.SendMessageToTeamsChannel", - "conversationReferenceProperty": "conversation.result", - "activity": { - "type": "message", - "text": "This is a message to a Teams Channel" - } - }, - { - "$kind": "Teams.SendMessageToTeamsChannel", - "conversationReferenceProperty": "conversation.resultWithCustomProperties", - "activity": { - "type": "message", - "text": "This is a message to a Teams Channel with custom properties" - }, - "teamsChannelId": "customTeamsChannel" - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "hi", - "channelData": { - "channel": { - "id": "fakeTeamsChannelToSendTo" - } - } - } - }, - { - "$kind": "Microsoft.Test.MemoryAssertions", - "assertions": [ - "conversation.result.activityId == '0'", - "conversation.result.channelId == 'msteams'", - "conversation.result.conversation.conversationType == 'groupChat'", - "conversation.result.conversation.isGroup == true", - "conversation.result.conversation.name == 'group'", - "conversation.result.conversation.tenantId == 'tenantId-Guid'", - "conversation.resultWithCustomProperties.activityId == '0'", - "conversation.resultWithCustomProperties.channelId == 'msteams'", - "conversation.resultWithCustomProperties.conversation.conversationType == 'groupChat'", - "conversation.resultWithCustomProperties.conversation.isGroup == true", - "conversation.resultWithCustomProperties.conversation.name == 'group'", - "conversation.resultWithCustomProperties.conversation.tenantId == 'tenantId-Guid'" - ] - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMessageToTeamsChannelError.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMessageToTeamsChannelError.test.dialog deleted file mode 100644 index 19419f3b1c..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendMessageToTeamsChannelError.test.dialog +++ /dev/null @@ -1,37 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnUnknownIntent", - "actions": [ - { - "$kind": "Teams.SendMessageToTeamsChannel" - } - ] - }, - { - "$kind": "Microsoft.OnError", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "${turn.dialogEvent.value.message}" - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserSays", - "text": "hi" - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "Teams.SendMessageToTeamsChannel works only on the Teams channel." - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendTabAuthResponseError.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendTabAuthResponseError.test.dialog deleted file mode 100644 index b5e40d7263..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendTabAuthResponseError.test.dialog +++ /dev/null @@ -1,43 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnUnknownIntent", - "actions": [ - { - "$kind": "Teams.SendTabAuthResponse", - "connectionName": "someConnection", - "title": "someTitle" - } - ] - }, - { - "$kind": "Microsoft.OnError", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "${turn.dialogEvent.value.message}" - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "NoConnectionName", - "name": "composeExtension/queryLink" - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "Teams.SendTabAuthResponse: not supported by the current adapter." - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendTabAuthResponseErrorWithAdapter.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendTabAuthResponseErrorWithAdapter.test.dialog deleted file mode 100644 index 8172f1d27c..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendTabAuthResponseErrorWithAdapter.test.dialog +++ /dev/null @@ -1,88 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "recognizer": { - "$kind": "Microsoft.RegexRecognizer", - "intents": [ - { - "intent": "NoConnectionName", - "pattern": "NoConnectionName" - }, - { - "intent": "NoTitle", - "pattern": "NoTitle" - } - ] - }, - "triggers": [ - { - "$kind": "Microsoft.OnIntent", - "intent": "NoConnectionName", - "actions": [ - { - "$kind": "Teams.SendTabAuthResponse", - "connectionName": "=turn.channelData.DoesNotExist" - } - ] - }, - { - "$kind": "Microsoft.OnIntent", - "intent": "NoTitle", - "actions": [ - { - "$kind": "Teams.SendTabAuthResponse", - "connectionName": "testConnection", - "title": "=turn.channelData.doesNotExist" - } - ] - }, - { - "$kind": "Microsoft.OnIntent", - "intent": "NoAttachments", - "actions": [ - { - "$kind": "Teams.SendTabAuthResponse" - } - ] - }, - { - "$kind": "Microsoft.OnError", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "${turn.dialogEvent.value.message}" - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "NoConnectionName", - "name": "tab/fetch" - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "A valid ConnectionName is required for auth responses." - }, - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "NoTitle", - "name": "tab/fetch" - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "A valid Title is required for auth responses." - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendTabCardResponse.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendTabCardResponse.test.dialog deleted file mode 100644 index 7e06e3ecde..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendTabCardResponse.test.dialog +++ /dev/null @@ -1,66 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnUnknownIntent", - "actions": [ - { - "$kind": "Teams.SendTabCardResponse", - "cards": { - "type": "message", - "attachments": [ - { - "contentType": "application/vnd.microsoft.card.adaptive", - "content": { - "type": "AdaptiveCard", - "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", - "version": "1.2", - "body": [ - { - "type": "Container", - "items": [ - { - "type": "RichTextBlock", - "inlines": [ - { - "type": "TextRun", - "text": "Success!" - } - ] - } - ] - } - ] - } - } - ] - } - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "hi", - "name": "tab/fetch" - } - }, - { - "$kind": "Microsoft.Test.AssertReplyActivity", - "assertions": [ - "type == 'invokeResponse'", - "conversation.id == 'Action_SendTabCardResponse'", - "value.body.tab.type == 'continue'", - "value.body.tab.value.cards[0].card.body[0].items[0].inlines[0].text == 'Success!'" - ] - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendTabCardResponseError.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendTabCardResponseError.test.dialog deleted file mode 100644 index 1e8a6bdc8c..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendTabCardResponseError.test.dialog +++ /dev/null @@ -1,79 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "recognizer": { - "$kind": "Microsoft.RegexRecognizer", - "intents": [ - { - "intent": "NoCard", - "pattern": "NoCard" - }, - { - "intent": "NoAttachments", - "pattern": "NoAttachments" - } - ] - }, - "triggers": [ - { - "$kind": "Microsoft.OnIntent", - "intent": "NoCard", - "actions": [ - { - "$kind": "Teams.SendTabCardResponse" - } - ] - }, - { - "$kind": "Microsoft.OnIntent", - "intent": "NoAttachments", - "actions": [ - { - "$kind": "Teams.SendTabCardResponse", - "cards": { - - } - } - ] - }, - { - "$kind": "Microsoft.OnError", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "${turn.dialogEvent.value.message}" - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "NoCard", - "name": "tab/fetch" - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "Valid Cards are required for Teams.SendTabCardResponse." - }, - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "NoAttachments", - "name": "tab/fetch" - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "Invalid activity. Attachment(s) are required for Teams.SendTabCardResponse." - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendTaskModuleCardResponse.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendTaskModuleCardResponse.test.dialog deleted file mode 100644 index d3c0c285fd..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendTaskModuleCardResponse.test.dialog +++ /dev/null @@ -1,56 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnUnknownIntent", - "actions": [ - { - "$kind": "Teams.SendTaskModuleCardResponse", - "title": "some title", - "height": 1, - "width": 2, - "completionBotId": "someBotId", - "card": { - "type": "message", - "attachments": [ - { - "contentType": "application/vnd.microsoft.card.thumbnail", - "content": { - "title": "card-title" - } - } - ] - } - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "hi", - "name": "composeExtension/queryLink" - } - }, - { - "$kind": "Microsoft.Test.AssertReplyActivity", - "assertions": [ - "type == 'invokeResponse'", - "conversation.id == 'Action_SendTaskModuleCardResponse'", - "value.body.task.value.completionBotId == 'someBotId'", - "value.body.task.value.height == 1", - "value.body.task.value.title == 'some title'", - "value.body.task.value.width == 2", - "value.body.task.value.card.contentType == 'application/vnd.microsoft.card.thumbnail'", - "value.body.task.value.card.content.title == 'card-title'" - ] - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendTaskModuleCardResponseError.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendTaskModuleCardResponseError.test.dialog deleted file mode 100644 index 20e353e108..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendTaskModuleCardResponseError.test.dialog +++ /dev/null @@ -1,79 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "recognizer": { - "$kind": "Microsoft.RegexRecognizer", - "intents": [ - { - "intent": "NoCard", - "pattern": "NoCard" - }, - { - "intent": "NoAttachments", - "pattern": "NoAttachments" - } - ] - }, - "triggers": [ - { - "$kind": "Microsoft.OnIntent", - "intent": "NoCard", - "actions": [ - { - "$kind": "Teams.SendTaskModuleCardResponse" - } - ] - }, - { - "$kind": "Microsoft.OnIntent", - "intent": "NoAttachments", - "actions": [ - { - "$kind": "Teams.SendTaskModuleCardResponse", - "card": { - "type": "message" - } - } - ] - }, - { - "$kind": "Microsoft.OnError", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "${turn.dialogEvent.value.message}" - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "NoCard", - "name": "composeExtension/queryLink" - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "A valid Card is required for Teams.SendTaskModuleCardResponse." - }, - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "NoAttachments", - "name": "composeExtension/queryLink" - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "Invalid activity. An attachment is required for Teams.SendTaskModuleCardResponse." - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendTaskModuleMessageResponse.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendTaskModuleMessageResponse.test.dialog deleted file mode 100644 index 5819cd6331..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendTaskModuleMessageResponse.test.dialog +++ /dev/null @@ -1,37 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnUnknownIntent", - "actions": [ - { - "$kind": "Teams.SendTaskModuleMessageResponse", - "message": "i want to send this to the user" - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "hi", - "name": "composeExtension/queryLink" - } - }, - { - "$kind": "Microsoft.Test.AssertReplyActivity", - "assertions": [ - "type == 'invokeResponse'", - "conversation.id == 'Action_SendTaskModuleMessageResponse'", - "value.body.task.value == 'i want to send this to the user'" - ] - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendTaskModuleUrlResponse.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendTaskModuleUrlResponse.test.dialog deleted file mode 100644 index 53a2d07eac..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendTaskModuleUrlResponse.test.dialog +++ /dev/null @@ -1,47 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnUnknownIntent", - "actions": [ - { - "$kind": "Teams.SendTaskModuleUrlResponse", - "title": "some title", - "height": 1, - "width": 2, - "completionBotId": "someBotId", - "url": "http://thisIsTheMainUrl.com", - "fallbackUrl": "http://thisIsTheFallbackUrl.net/okay/falling/back_now" - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "hi", - "name": "composeExtension/queryLink" - } - }, - { - "$kind": "Microsoft.Test.AssertReplyActivity", - "assertions": [ - "type == 'invokeResponse'", - "conversation.id == 'Action_SendTaskModuleUrlResponse'", - "value.body.task.value.completionBotId == 'someBotId'", - "value.body.task.value.height == 1", - "value.body.task.value.title == 'some title'", - "value.body.task.value.width == 2", - "value.body.task.value.url == 'http://thisIsTheMainUrl.com'", - "value.body.task.value.fallbackUrl == 'http://thisIsTheFallbackUrl.net/okay/falling/back_now'" - ] - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendTaskModuleUrlResponseError.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendTaskModuleUrlResponseError.test.dialog deleted file mode 100644 index e30217656b..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ActionTests/Action_SendTaskModuleUrlResponseError.test.dialog +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnUnknownIntent", - "actions": [ - { - "$kind": "Teams.SendTaskModuleUrlResponse", - "url": "=turn.channelData.doesNotExist" - } - ] - }, - { - "$kind": "Microsoft.OnError", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "${turn.dialogEvent.value.message}" - } - ] - } - ] - }, - "script": [ - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "message", - "text": "hi", - "name": "composeExtension/queryLink" - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "Missing Url for Teams.SendTaskModuleUrlResponse." - } - ] -} \ No newline at end of file diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ConditionalTests/ConditionalsTests_OnTeamsActivityTypes.test.dialog b/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ConditionalTests/ConditionalsTests_OnTeamsActivityTypes.test.dialog deleted file mode 100644 index ef348c61ac..0000000000 --- a/tests/packages/Microsoft.Bot.Components.Teams.Tests/Tests/ConditionalTests/ConditionalsTests_OnTeamsActivityTypes.test.dialog +++ /dev/null @@ -1,616 +0,0 @@ -{ - "$schema": "../../../tests.schema", - "$kind": "Microsoft.Test.Script", - "dialog": { - "$kind": "Microsoft.AdaptiveDialog", - "id": "planningTest", - "triggers": [ - { - "$kind": "Microsoft.OnInvokeActivity", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "${turn.activity.text}" - } - ] - }, - { - "$kind": "Microsoft.OnConversationUpdateActivity", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "${turn.activity.text}" - } - ] - }, - { - "$kind": "Teams.OnAppBasedLinkQuery", - "condition": "turn.activity.text == 'OnAppBasedLinkQuery'", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "OnAppBasedLinkQuery" - } - ] - }, - { - "$kind": "Teams.OnCardAction", - "condition": "turn.activity.text == 'OnCardAction'", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "OnCardAction" - } - ] - }, - { - "$kind": "Teams.OnChannelCreated", - "condition": "turn.activity.text == 'OnChannelCreated'", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "OnChannelCreated" - } - ] - }, - { - "$kind": "Teams.OnChannelDeleted", - "condition": "turn.activity.text == 'OnChannelDeleted'", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "OnChannelDeleted" - } - ] - }, - { - "$kind": "Teams.OnChannelRenamed", - "condition": "turn.activity.text == 'OnChannelRenamed'", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "OnChannelRenamed" - } - ] - }, - { - "$kind": "Teams.OnChannelRestored", - "condition": "turn.activity.text == 'OnChannelRestored'", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "OnChannelRestored" - } - ] - }, - { - "$kind": "Teams.OnFileConsent", - "condition": "turn.activity.text == 'OnFileConsent'", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "OnFileConsent" - } - ] - }, - { - "$kind": "Teams.OnMECardButtonClicked", - "condition": "turn.activity.text == 'OnCardButtonClicked'", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "OnCardButtonClicked" - } - ] - }, - { - "$kind": "Teams.OnMEConfigQuerySettingUrl", - "condition": "turn.activity.text == 'OnConfigurationQuerySettingUrl'", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "OnConfigurationQuerySettingUrl" - } - ] - }, - { - "$kind": "Teams.OnMEConfigSetting", - "condition": "turn.activity.text == 'OnConfigurationSetting'", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "OnConfigurationSetting" - } - ] - }, - { - "$kind": "Teams.OnMEFetchTask", - "condition": "turn.activity.text == 'OnFetchTask'", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "OnFetchTask" - } - ] - }, - { - "$kind": "Teams.OnMEQuery", - "condition": "turn.activity.text == 'OnQuery'", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "OnQuery" - } - ] - }, - { - "$kind": "Teams.OnMESelectItem", - "condition": "turn.activity.text == 'OnSelectItem'", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "OnSelectItem" - } - ] - }, - { - "$kind": "Teams.OnMESubmitAction", - "condition": "turn.activity.text == 'OnSubmitAction'", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "OnSubmitAction" - } - ] - }, - { - "$kind": "Teams.OnO365ConnectorCardAction", - "condition": "turn.activity.text == 'OnO365ConnectorCardAction'", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "OnO365ConnectorCardAction" - } - ] - }, - { - "$kind": "Teams.OnTaskModuleFetch", - "condition": "turn.activity.text == 'OnTaskModuleFetch'", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "OnTaskModuleFetch" - } - ] - }, - { - "$kind": "Teams.OnTabFetch", - "condition": "turn.activity.text == 'OnTabFetch'", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "OnTabFetch" - } - ] - }, - { - "$kind": "Teams.OnTaskModuleSubmit", - "condition": "turn.activity.text == 'OnTaskModuleSubmit'", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "OnTaskModuleSubmit" - } - ] - }, - { - "$kind": "Teams.OnTabSubmit", - "condition": "turn.activity.text == 'OnTabSubmit'", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "OnTabSubmit" - } - ] - }, - { - "$kind": "Teams.OnTeamArchived", - "condition": "turn.activity.text == 'OnTeamArchived'", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "OnTeamArchived" - } - ] - }, - { - "$kind": "Teams.OnTeamDeleted", - "condition": "turn.activity.text == 'OnTeamDeleted'", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "OnTeamDeleted" - } - ] - }, - { - "$kind": "Teams.OnTeamHardDeleted", - "condition": "turn.activity.text == 'OnTeamHardDeleted'", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "OnTeamHardDeleted" - } - ] - }, - { - "$kind": "Teams.OnTeamRenamed", - "condition": "turn.activity.text == 'OnTeamRenamed'", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "OnTeamRenamed" - } - ] - }, - { - "$kind": "Teams.OnTeamRestored", - "condition": "turn.activity.text == 'OnTeamRestored'", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "OnTeamRestored" - } - ] - }, - { - "$kind": "Teams.OnTeamUnarchived", - "condition": "turn.activity.text == 'OnTeamUnarchived'", - "actions": [ - { - "$kind": "Microsoft.SendActivity", - "activity": "OnTeamUnarchived" - } - ] - } - ], - "autoEndDialog": false, - "defaultResultProperty": "dialog.result" - }, - "script": [ - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "invoke", - "text": "OnAppBasedLinkQuery", - "value": {}, - "name": "composeExtension/queryLink" - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "OnAppBasedLinkQuery" - }, - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "invoke", - "text": "OnCardAction", - "value": {} - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "OnCardAction" - }, - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "conversationUpdate", - "text": "OnChannelCreated", - "channelData": { - "eventType": "channelCreated" - } - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "OnChannelCreated" - }, - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "conversationUpdate", - "text": "OnChannelDeleted", - "channelData": { - "eventType": "channelDeleted" - } - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "OnChannelDeleted" - }, - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "conversationUpdate", - "text": "OnChannelRenamed", - "channelData": { - "eventType": "channelRenamed" - } - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "OnChannelRenamed" - }, - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "conversationUpdate", - "text": "OnChannelRestored", - "channelData": { - "eventType": "channelRestored" - } - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "OnChannelRestored" - }, - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "invoke", - "text": "OnFileConsent", - "value": {}, - "name": "fileConsent/invoke" - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "OnFileConsent" - }, - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "invoke", - "text": "OnCardButtonClicked", - "value": {}, - "name": "composeExtension/onCardButtonClicked" - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "OnCardButtonClicked" - }, - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "invoke", - "text": "OnConfigurationQuerySettingUrl", - "value": {}, - "name": "composeExtension/querySettingUrl" - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "OnConfigurationQuerySettingUrl" - }, - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "invoke", - "text": "OnConfigurationSetting", - "value": {}, - "name": "composeExtension/setting" - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "OnConfigurationSetting" - }, - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "invoke", - "text": "OnFetchTask", - "value": {}, - "name": "composeExtension/fetchTask" - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "OnFetchTask" - }, - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "invoke", - "text": "OnQuery", - "value": {}, - "name": "composeExtension/query" - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "OnQuery" - }, - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "invoke", - "text": "OnSelectItem", - "value": {}, - "name": "composeExtension/selectItem" - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "OnSelectItem" - }, - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "invoke", - "text": "OnSubmitAction", - "value": {}, - "name": "composeExtension/submitAction" - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "OnSubmitAction" - }, - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "invoke", - "text": "OnO365ConnectorCardAction", - "value": {}, - "name": "actionableMessage/executeAction" - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "OnO365ConnectorCardAction" - }, - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "invoke", - "text": "OnTabFetch", - "value": {}, - "name": "tab/fetch" - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "OnTabFetch" - }, - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "invoke", - "text": "OnTaskModuleFetch", - "value": {}, - "name": "task/fetch" - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "OnTaskModuleFetch" - }, - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "invoke", - "text": "OnTabSubmit", - "value": {}, - "name": "tab/submit" - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "OnTabSubmit" - }, - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "invoke", - "text": "OnTaskModuleSubmit", - "value": {}, - "name": "task/submit" - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "OnTaskModuleSubmit" - }, - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "conversationUpdate", - "text": "OnTeamArchived", - "channelData": { - "eventType": "teamArchived" - } - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "OnTeamArchived" - }, - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "conversationUpdate", - "text": "OnTeamDeleted", - "channelData": { - "eventType": "teamDeleted" - } - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "OnTeamDeleted" - }, - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "conversationUpdate", - "text": "OnTeamHardDeleted", - "channelData": { - "eventType": "teamHardDeleted" - } - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "OnTeamHardDeleted" - }, - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "conversationUpdate", - "text": "OnTeamRenamed", - "channelData": { - "eventType": "teamRenamed" - } - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "OnTeamRenamed" - }, - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "conversationUpdate", - "text": "OnTeamRestored", - "channelData": { - "eventType": "teamRestored" - } - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "OnTeamRestored" - }, - { - "$kind": "Microsoft.Test.UserActivity", - "activity": { - "type": "conversationUpdate", - "text": "OnTeamUnarchived", - "channelData": { - "eventType": "teamUnarchived" - } - } - }, - { - "$kind": "Microsoft.Test.AssertReply", - "text": "OnTeamUnarchived" - } - ] -} \ No newline at end of file diff --git a/tests/skills/common/NuGet.Config b/tests/skills/common/NuGet.Config deleted file mode 100644 index 16e6dfff88..0000000000 --- a/tests/skills/common/NuGet.Config +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/tests/Microsoft.Bot.Components.Tests.sln b/tests/unit/Microsoft.Bot.Components.Tests.sln similarity index 94% rename from tests/Microsoft.Bot.Components.Tests.sln rename to tests/unit/Microsoft.Bot.Components.Tests.sln index c9ad6216f0..74801b67a6 100644 --- a/tests/Microsoft.Bot.Components.Tests.sln +++ b/tests/unit/Microsoft.Bot.Components.Tests.sln @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.31112.23 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Components.Teams", "..\packages\Teams\dotnet\Microsoft.Bot.Components.Teams.csproj", "{1C09226C-3DE0-48BF-BEB7-68F0691908EA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Components.Teams", "..\..\packages\Teams\dotnet\Microsoft.Bot.Components.Teams.csproj", "{1C09226C-3DE0-48BF-BEB7-68F0691908EA}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "skills", "skills", "{31CBAEDA-E319-49AE-A662-AA0739205C82}" EndProject @@ -19,7 +19,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "teams", "teams", "{61708441 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Components.Teams.Tests", "packages\Microsoft.Bot.Components.Teams.Tests\Microsoft.Bot.Components.Teams.Tests.csproj", "{F6E872DE-52B7-4372-B546-B18B5A495C73}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Components.Graph", "..\packages\Graph\Microsoft.Bot.Components.Graph.csproj", "{9561BF32-D0EE-464F-981B-7E4298ACDDFB}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Components.Graph", "..\..\packages\Graph\Microsoft.Bot.Components.Graph.csproj", "{9561BF32-D0EE-464F-981B-7E4298ACDDFB}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/tests/skills/calendar/AvailabilityTests.cs b/tests/unit/skills/calendar/AvailabilityTests.cs similarity index 100% rename from tests/skills/calendar/AvailabilityTests.cs rename to tests/unit/skills/calendar/AvailabilityTests.cs diff --git a/tests/skills/calendar/CalendarSkillTestBase.cs b/tests/unit/skills/calendar/CalendarSkillTestBase.cs similarity index 100% rename from tests/skills/calendar/CalendarSkillTestBase.cs rename to tests/unit/skills/calendar/CalendarSkillTestBase.cs diff --git a/tests/skills/calendar/CreateEventTests.cs b/tests/unit/skills/calendar/CreateEventTests.cs similarity index 100% rename from tests/skills/calendar/CreateEventTests.cs rename to tests/unit/skills/calendar/CreateEventTests.cs diff --git a/tests/skills/calendar/CustomActionTests.cs b/tests/unit/skills/calendar/CustomActionTests.cs similarity index 100% rename from tests/skills/calendar/CustomActionTests.cs rename to tests/unit/skills/calendar/CustomActionTests.cs diff --git a/tests/skills/calendar/GetEventTests.cs b/tests/unit/skills/calendar/GetEventTests.cs similarity index 100% rename from tests/skills/calendar/GetEventTests.cs rename to tests/unit/skills/calendar/GetEventTests.cs diff --git a/tests/skills/calendar/Microsoft.Bot.Dialogs.Tests.Skills.Calendar.csproj b/tests/unit/skills/calendar/Microsoft.Bot.Dialogs.Tests.Skills.Calendar.csproj similarity index 91% rename from tests/skills/calendar/Microsoft.Bot.Dialogs.Tests.Skills.Calendar.csproj rename to tests/unit/skills/calendar/Microsoft.Bot.Dialogs.Tests.Skills.Calendar.csproj index 2aa48c5aa1..e98d945c2c 100644 --- a/tests/skills/calendar/Microsoft.Bot.Dialogs.Tests.Skills.Calendar.csproj +++ b/tests/unit/skills/calendar/Microsoft.Bot.Dialogs.Tests.Skills.Calendar.csproj @@ -32,7 +32,7 @@ Always - + Always diff --git a/tests/packages/Microsoft.Bot.Components.Teams.Tests/NuGet.Config b/tests/unit/skills/calendar/NuGet.Config similarity index 100% rename from tests/packages/Microsoft.Bot.Components.Teams.Tests/NuGet.Config rename to tests/unit/skills/calendar/NuGet.Config diff --git a/tests/skills/calendar/RespondToEventTests.cs b/tests/unit/skills/calendar/RespondToEventTests.cs similarity index 100% rename from tests/skills/calendar/RespondToEventTests.cs rename to tests/unit/skills/calendar/RespondToEventTests.cs diff --git a/tests/skills/calendar/TestAssets/AvailabilityTests/GetAvailabilityBreaks.test.dialog b/tests/unit/skills/calendar/TestAssets/AvailabilityTests/GetAvailabilityBreaks.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/AvailabilityTests/GetAvailabilityBreaks.test.dialog rename to tests/unit/skills/calendar/TestAssets/AvailabilityTests/GetAvailabilityBreaks.test.dialog diff --git a/tests/skills/calendar/TestAssets/AvailabilityTests/GetAvailabilityFirst.test.dialog b/tests/unit/skills/calendar/TestAssets/AvailabilityTests/GetAvailabilityFirst.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/AvailabilityTests/GetAvailabilityFirst.test.dialog rename to tests/unit/skills/calendar/TestAssets/AvailabilityTests/GetAvailabilityFirst.test.dialog diff --git a/tests/skills/calendar/TestAssets/AvailabilityTests/GetAvailabilityLast.test.dialog b/tests/unit/skills/calendar/TestAssets/AvailabilityTests/GetAvailabilityLast.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/AvailabilityTests/GetAvailabilityLast.test.dialog rename to tests/unit/skills/calendar/TestAssets/AvailabilityTests/GetAvailabilityLast.test.dialog diff --git a/tests/skills/calendar/TestAssets/AvailabilityTests/HttpRequestMock/findMeetingTimes.mock.dialog b/tests/unit/skills/calendar/TestAssets/AvailabilityTests/HttpRequestMock/findMeetingTimes.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/AvailabilityTests/HttpRequestMock/findMeetingTimes.mock.dialog rename to tests/unit/skills/calendar/TestAssets/AvailabilityTests/HttpRequestMock/findMeetingTimes.mock.dialog diff --git a/tests/skills/calendar/TestAssets/AvailabilityTests/HttpRequestMock/getContact.mock.dialog b/tests/unit/skills/calendar/TestAssets/AvailabilityTests/HttpRequestMock/getContact.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/AvailabilityTests/HttpRequestMock/getContact.mock.dialog rename to tests/unit/skills/calendar/TestAssets/AvailabilityTests/HttpRequestMock/getContact.mock.dialog diff --git a/tests/skills/calendar/TestAssets/AvailabilityTests/HttpRequestMock/getEvent.mock.dialog b/tests/unit/skills/calendar/TestAssets/AvailabilityTests/HttpRequestMock/getEvent.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/AvailabilityTests/HttpRequestMock/getEvent.mock.dialog rename to tests/unit/skills/calendar/TestAssets/AvailabilityTests/HttpRequestMock/getEvent.mock.dialog diff --git a/tests/skills/calendar/TestAssets/AvailabilityTests/HttpRequestMock/getEvents.mock.dialog b/tests/unit/skills/calendar/TestAssets/AvailabilityTests/HttpRequestMock/getEvents.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/AvailabilityTests/HttpRequestMock/getEvents.mock.dialog rename to tests/unit/skills/calendar/TestAssets/AvailabilityTests/HttpRequestMock/getEvents.mock.dialog diff --git a/tests/skills/calendar/TestAssets/AvailabilityTests/HttpRequestMock/getPeople.mock.dialog b/tests/unit/skills/calendar/TestAssets/AvailabilityTests/HttpRequestMock/getPeople.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/AvailabilityTests/HttpRequestMock/getPeople.mock.dialog rename to tests/unit/skills/calendar/TestAssets/AvailabilityTests/HttpRequestMock/getPeople.mock.dialog diff --git a/tests/skills/calendar/TestAssets/AvailabilityTests/HttpRequestMock/getProfile.mock.dialog b/tests/unit/skills/calendar/TestAssets/AvailabilityTests/HttpRequestMock/getProfile.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/AvailabilityTests/HttpRequestMock/getProfile.mock.dialog rename to tests/unit/skills/calendar/TestAssets/AvailabilityTests/HttpRequestMock/getProfile.mock.dialog diff --git a/tests/skills/calendar/TestAssets/AvailabilityTests/HttpRequestMock/getWorkingHours.mock.dialog b/tests/unit/skills/calendar/TestAssets/AvailabilityTests/HttpRequestMock/getWorkingHours.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/AvailabilityTests/HttpRequestMock/getWorkingHours.mock.dialog rename to tests/unit/skills/calendar/TestAssets/AvailabilityTests/HttpRequestMock/getWorkingHours.mock.dialog diff --git a/tests/skills/calendar/TestAssets/AvailabilityTests/UserTokenMock/nomagiccode.mock.dialog b/tests/unit/skills/calendar/TestAssets/AvailabilityTests/UserTokenMock/nomagiccode.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/AvailabilityTests/UserTokenMock/nomagiccode.mock.dialog rename to tests/unit/skills/calendar/TestAssets/AvailabilityTests/UserTokenMock/nomagiccode.mock.dialog diff --git a/tests/skills/calendar/TestAssets/AvailabilityTests/cachedResponses/Calendar_en_us_lu/1322656655.json b/tests/unit/skills/calendar/TestAssets/AvailabilityTests/cachedResponses/Calendar_en_us_lu/1322656655.json similarity index 100% rename from tests/skills/calendar/TestAssets/AvailabilityTests/cachedResponses/Calendar_en_us_lu/1322656655.json rename to tests/unit/skills/calendar/TestAssets/AvailabilityTests/cachedResponses/Calendar_en_us_lu/1322656655.json diff --git a/tests/skills/calendar/TestAssets/AvailabilityTests/cachedResponses/Calendar_en_us_lu/565125802.json b/tests/unit/skills/calendar/TestAssets/AvailabilityTests/cachedResponses/Calendar_en_us_lu/565125802.json similarity index 100% rename from tests/skills/calendar/TestAssets/AvailabilityTests/cachedResponses/Calendar_en_us_lu/565125802.json rename to tests/unit/skills/calendar/TestAssets/AvailabilityTests/cachedResponses/Calendar_en_us_lu/565125802.json diff --git a/tests/skills/calendar/TestAssets/AvailabilityTests/cachedResponses/Calendar_en_us_lu/686230913.json b/tests/unit/skills/calendar/TestAssets/AvailabilityTests/cachedResponses/Calendar_en_us_lu/686230913.json similarity index 100% rename from tests/skills/calendar/TestAssets/AvailabilityTests/cachedResponses/Calendar_en_us_lu/686230913.json rename to tests/unit/skills/calendar/TestAssets/AvailabilityTests/cachedResponses/Calendar_en_us_lu/686230913.json diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent.test.dialog b/tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent.test.dialog rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent.test.dialog diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_multiple.test.dialog b/tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_multiple.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_multiple.test.dialog rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_multiple.test.dialog diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_setAttendeeAdd.test.dialog b/tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_setAttendeeAdd.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_setAttendeeAdd.test.dialog rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_setAttendeeAdd.test.dialog diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_setAttendeeRemove.test.dialog b/tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_setAttendeeRemove.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_setAttendeeRemove.test.dialog rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_setAttendeeRemove.test.dialog diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_setDateTime.test.dialog b/tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_setDateTime.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_setDateTime.test.dialog rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_setDateTime.test.dialog diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_setDescription.test.dialog b/tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_setDescription.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_setDescription.test.dialog rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_setDescription.test.dialog diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_setDuration.test.dialog b/tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_setDuration.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_setDuration.test.dialog rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_setDuration.test.dialog diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_setLocation.test.dialog b/tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_setLocation.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_setLocation.test.dialog rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_setLocation.test.dialog diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_setOnlineMeetingAdd.test.dialog b/tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_setOnlineMeetingAdd.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_setOnlineMeetingAdd.test.dialog rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_setOnlineMeetingAdd.test.dialog diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_setOnlineMeetingRemove.test.dialog b/tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_setOnlineMeetingRemove.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_setOnlineMeetingRemove.test.dialog rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_setOnlineMeetingRemove.test.dialog diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_setTitle.test.dialog b/tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_setTitle.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_setTitle.test.dialog rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_setTitle.test.dialog diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_skip.test.dialog b/tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_skip.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_skip.test.dialog rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent_interruption_skip.test.dialog diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent_noEntities.test.dialog b/tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent_noEntities.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent_noEntities.test.dialog rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent_noEntities.test.dialog diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent_withEntity_contact.test.dialog b/tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent_withEntity_contact.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent_withEntity_contact.test.dialog rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent_withEntity_contact.test.dialog diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent_withEntity_datetime.test.dialog b/tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent_withEntity_datetime.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent_withEntity_datetime.test.dialog rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent_withEntity_datetime.test.dialog diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent_withEntity_location.test.dialog b/tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent_withEntity_location.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent_withEntity_location.test.dialog rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent_withEntity_location.test.dialog diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent_withEntity_title.test.dialog b/tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent_withEntity_title.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent_withEntity_title.test.dialog rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent_withEntity_title.test.dialog diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent_withEntity_title_contact.test.dialog b/tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent_withEntity_title_contact.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent_withEntity_title_contact.test.dialog rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent_withEntity_title_contact.test.dialog diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent_withEntity_title_contact_datetime.test.dialog b/tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent_withEntity_title_contact_datetime.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/CreateEvent_withEntity_title_contact_datetime.test.dialog rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/CreateEvent_withEntity_title_contact_datetime.test.dialog diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/HttpRequestMock/createEvent.mock.dialog b/tests/unit/skills/calendar/TestAssets/CreateEventTests/HttpRequestMock/createEvent.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/HttpRequestMock/createEvent.mock.dialog rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/HttpRequestMock/createEvent.mock.dialog diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/HttpRequestMock/findMeetingTimes.mock.dialog b/tests/unit/skills/calendar/TestAssets/CreateEventTests/HttpRequestMock/findMeetingTimes.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/HttpRequestMock/findMeetingTimes.mock.dialog rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/HttpRequestMock/findMeetingTimes.mock.dialog diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/HttpRequestMock/getContact.mock.dialog b/tests/unit/skills/calendar/TestAssets/CreateEventTests/HttpRequestMock/getContact.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/HttpRequestMock/getContact.mock.dialog rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/HttpRequestMock/getContact.mock.dialog diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/HttpRequestMock/getPeople.mock.dialog b/tests/unit/skills/calendar/TestAssets/CreateEventTests/HttpRequestMock/getPeople.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/HttpRequestMock/getPeople.mock.dialog rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/HttpRequestMock/getPeople.mock.dialog diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/HttpRequestMock/getProfile.mock.dialog b/tests/unit/skills/calendar/TestAssets/CreateEventTests/HttpRequestMock/getProfile.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/HttpRequestMock/getProfile.mock.dialog rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/HttpRequestMock/getProfile.mock.dialog diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/HttpRequestMock/getWorkingHours.mock.dialog b/tests/unit/skills/calendar/TestAssets/CreateEventTests/HttpRequestMock/getWorkingHours.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/HttpRequestMock/getWorkingHours.mock.dialog rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/HttpRequestMock/getWorkingHours.mock.dialog diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/UserTokenMock/nomagiccode.mock.dialog b/tests/unit/skills/calendar/TestAssets/CreateEventTests/UserTokenMock/nomagiccode.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/UserTokenMock/nomagiccode.mock.dialog rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/UserTokenMock/nomagiccode.mock.dialog diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/1039170905.json b/tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/1039170905.json similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/1039170905.json rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/1039170905.json diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/1082755164.json b/tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/1082755164.json similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/1082755164.json rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/1082755164.json diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/1127709374.json b/tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/1127709374.json similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/1127709374.json rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/1127709374.json diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/2937641126.json b/tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/2937641126.json similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/2937641126.json rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/2937641126.json diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/3617381462.json b/tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/3617381462.json similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/3617381462.json rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/3617381462.json diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/3672480022.json b/tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/3672480022.json similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/3672480022.json rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/3672480022.json diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/3897990621.json b/tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/3897990621.json similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/3897990621.json rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/3897990621.json diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/4011733239.json b/tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/4011733239.json similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/4011733239.json rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/4011733239.json diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/4107782572.json b/tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/4107782572.json similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/4107782572.json rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/4107782572.json diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/503597261.json b/tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/503597261.json similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/503597261.json rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/503597261.json diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/565993014.json b/tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/565993014.json similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/565993014.json rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/565993014.json diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/641530942.json b/tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/641530942.json similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/641530942.json rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/Calendar_en_us_lu/641530942.json diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/1039170905.json b/tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/1039170905.json similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/1039170905.json rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/1039170905.json diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/1134332707.json b/tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/1134332707.json similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/1134332707.json rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/1134332707.json diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/1413756758.json b/tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/1413756758.json similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/1413756758.json rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/1413756758.json diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/1556687899.json b/tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/1556687899.json similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/1556687899.json rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/1556687899.json diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/1695466276.json b/tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/1695466276.json similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/1695466276.json rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/1695466276.json diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/1847842841.json b/tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/1847842841.json similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/1847842841.json rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/1847842841.json diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/1869746525.json b/tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/1869746525.json similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/1869746525.json rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/1869746525.json diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/2540704622.json b/tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/2540704622.json similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/2540704622.json rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/2540704622.json diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/2542755548.json b/tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/2542755548.json similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/2542755548.json rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/2542755548.json diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/2553583282.json b/tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/2553583282.json similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/2553583282.json rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/2553583282.json diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/2564825304.json b/tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/2564825304.json similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/2564825304.json rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/2564825304.json diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/2603715459.json b/tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/2603715459.json similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/2603715459.json rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/2603715459.json diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/2937641126.json b/tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/2937641126.json similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/2937641126.json rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/2937641126.json diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/3081695755.json b/tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/3081695755.json similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/3081695755.json rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/3081695755.json diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/3430243042.json b/tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/3430243042.json similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/3430243042.json rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/3430243042.json diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/3665878675.json b/tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/3665878675.json similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/3665878675.json rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/3665878675.json diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/3672480022.json b/tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/3672480022.json similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/3672480022.json rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/3672480022.json diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/3886812449.json b/tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/3886812449.json similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/3886812449.json rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/3886812449.json diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/428143476.json b/tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/428143476.json similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/428143476.json rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/CreateEventDialog_en_us_lu/428143476.json diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/GetContactsDialog_en_us_lu/1392353970.json b/tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/GetContactsDialog_en_us_lu/1392353970.json similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/GetContactsDialog_en_us_lu/1392353970.json rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/GetContactsDialog_en_us_lu/1392353970.json diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/GetContactsDialog_en_us_lu/1484328397.json b/tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/GetContactsDialog_en_us_lu/1484328397.json similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/GetContactsDialog_en_us_lu/1484328397.json rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/GetContactsDialog_en_us_lu/1484328397.json diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/GetContactsDialog_en_us_lu/1542622736.json b/tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/GetContactsDialog_en_us_lu/1542622736.json similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/GetContactsDialog_en_us_lu/1542622736.json rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/GetContactsDialog_en_us_lu/1542622736.json diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/GetContactsDialog_en_us_lu/4085864143.json b/tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/GetContactsDialog_en_us_lu/4085864143.json similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/GetContactsDialog_en_us_lu/4085864143.json rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/GetContactsDialog_en_us_lu/4085864143.json diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/GetContactsDialog_en_us_lu/430240736.json b/tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/GetContactsDialog_en_us_lu/430240736.json similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/GetContactsDialog_en_us_lu/430240736.json rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/GetContactsDialog_en_us_lu/430240736.json diff --git a/tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/ListDialog_en_us_lu/3723765839.json b/tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/ListDialog_en_us_lu/3723765839.json similarity index 100% rename from tests/skills/calendar/TestAssets/CreateEventTests/cachedResponses/ListDialog_en_us_lu/3723765839.json rename to tests/unit/skills/calendar/TestAssets/CreateEventTests/cachedResponses/ListDialog_en_us_lu/3723765839.json diff --git a/tests/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_AcceptEvent.test.dialog b/tests/unit/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_AcceptEvent.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_AcceptEvent.test.dialog rename to tests/unit/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_AcceptEvent.test.dialog diff --git a/tests/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_CreateEvent.test.dialog b/tests/unit/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_CreateEvent.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_CreateEvent.test.dialog rename to tests/unit/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_CreateEvent.test.dialog diff --git a/tests/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_DeclineEvent.test.dialog b/tests/unit/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_DeclineEvent.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_DeclineEvent.test.dialog rename to tests/unit/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_DeclineEvent.test.dialog diff --git a/tests/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_DeleteEvent.test.dialog b/tests/unit/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_DeleteEvent.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_DeleteEvent.test.dialog rename to tests/unit/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_DeleteEvent.test.dialog diff --git a/tests/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_FindMeetingTimes.test.dialog b/tests/unit/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_FindMeetingTimes.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_FindMeetingTimes.test.dialog rename to tests/unit/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_FindMeetingTimes.test.dialog diff --git a/tests/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_GetContacts.test.dialog b/tests/unit/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_GetContacts.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_GetContacts.test.dialog rename to tests/unit/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_GetContacts.test.dialog diff --git a/tests/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_GetEvents.test.dialog b/tests/unit/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_GetEvents.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_GetEvents.test.dialog rename to tests/unit/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_GetEvents.test.dialog diff --git a/tests/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_GetProfile.test.dialog b/tests/unit/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_GetProfile.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_GetProfile.test.dialog rename to tests/unit/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_GetProfile.test.dialog diff --git a/tests/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_GetWorkingHours.test.dialog b/tests/unit/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_GetWorkingHours.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_GetWorkingHours.test.dialog rename to tests/unit/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_GetWorkingHours.test.dialog diff --git a/tests/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_SettingsTest.test.dialog b/tests/unit/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_SettingsTest.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_SettingsTest.test.dialog rename to tests/unit/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_SettingsTest.test.dialog diff --git a/tests/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_TentativelyAcceptEvent.test.dialog b/tests/unit/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_TentativelyAcceptEvent.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_TentativelyAcceptEvent.test.dialog rename to tests/unit/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_TentativelyAcceptEvent.test.dialog diff --git a/tests/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_UpdateEvent.test.dialog b/tests/unit/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_UpdateEvent.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_UpdateEvent.test.dialog rename to tests/unit/skills/calendar/TestAssets/CustomActionTests/CalendarSkillTests_UpdateEvent.test.dialog diff --git a/tests/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/acceptEvent.mock.dialog b/tests/unit/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/acceptEvent.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/acceptEvent.mock.dialog rename to tests/unit/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/acceptEvent.mock.dialog diff --git a/tests/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/createEvent.mock.dialog b/tests/unit/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/createEvent.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/createEvent.mock.dialog rename to tests/unit/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/createEvent.mock.dialog diff --git a/tests/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/declineEvent.mock.dialog b/tests/unit/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/declineEvent.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/declineEvent.mock.dialog rename to tests/unit/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/declineEvent.mock.dialog diff --git a/tests/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/deleteEvent.mock.dialog b/tests/unit/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/deleteEvent.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/deleteEvent.mock.dialog rename to tests/unit/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/deleteEvent.mock.dialog diff --git a/tests/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/findMeetingTimes.mock.dialog b/tests/unit/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/findMeetingTimes.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/findMeetingTimes.mock.dialog rename to tests/unit/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/findMeetingTimes.mock.dialog diff --git a/tests/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/getContact.mock.dialog b/tests/unit/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/getContact.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/getContact.mock.dialog rename to tests/unit/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/getContact.mock.dialog diff --git a/tests/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/getEvents.mock.dialog b/tests/unit/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/getEvents.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/getEvents.mock.dialog rename to tests/unit/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/getEvents.mock.dialog diff --git a/tests/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/getPeople.mock.dialog b/tests/unit/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/getPeople.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/getPeople.mock.dialog rename to tests/unit/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/getPeople.mock.dialog diff --git a/tests/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/getProfile.mock.dialog b/tests/unit/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/getProfile.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/getProfile.mock.dialog rename to tests/unit/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/getProfile.mock.dialog diff --git a/tests/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/getWorkingHours.mock.dialog b/tests/unit/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/getWorkingHours.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/getWorkingHours.mock.dialog rename to tests/unit/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/getWorkingHours.mock.dialog diff --git a/tests/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/tentativelyAcceptEvent.mock.dialog b/tests/unit/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/tentativelyAcceptEvent.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/tentativelyAcceptEvent.mock.dialog rename to tests/unit/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/tentativelyAcceptEvent.mock.dialog diff --git a/tests/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/updateEvent.mock.dialog b/tests/unit/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/updateEvent.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/updateEvent.mock.dialog rename to tests/unit/skills/calendar/TestAssets/CustomActionTests/HttpRequestMock/updateEvent.mock.dialog diff --git a/tests/skills/calendar/TestAssets/CustomActionTests/UserTokenMock/nomagiccode.mock.dialog b/tests/unit/skills/calendar/TestAssets/CustomActionTests/UserTokenMock/nomagiccode.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/CustomActionTests/UserTokenMock/nomagiccode.mock.dialog rename to tests/unit/skills/calendar/TestAssets/CustomActionTests/UserTokenMock/nomagiccode.mock.dialog diff --git a/tests/skills/calendar/TestAssets/GetEventTests/GetEventAttendees.test.dialog b/tests/unit/skills/calendar/TestAssets/GetEventTests/GetEventAttendees.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/GetEventTests/GetEventAttendees.test.dialog rename to tests/unit/skills/calendar/TestAssets/GetEventTests/GetEventAttendees.test.dialog diff --git a/tests/skills/calendar/TestAssets/GetEventTests/GetEventDateTime.test.dialog b/tests/unit/skills/calendar/TestAssets/GetEventTests/GetEventDateTime.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/GetEventTests/GetEventDateTime.test.dialog rename to tests/unit/skills/calendar/TestAssets/GetEventTests/GetEventDateTime.test.dialog diff --git a/tests/skills/calendar/TestAssets/GetEventTests/GetEventLocation.test.dialog b/tests/unit/skills/calendar/TestAssets/GetEventTests/GetEventLocation.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/GetEventTests/GetEventLocation.test.dialog rename to tests/unit/skills/calendar/TestAssets/GetEventTests/GetEventLocation.test.dialog diff --git a/tests/skills/calendar/TestAssets/GetEventTests/GetEvents_multipleResults.test.dialog b/tests/unit/skills/calendar/TestAssets/GetEventTests/GetEvents_multipleResults.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/GetEventTests/GetEvents_multipleResults.test.dialog rename to tests/unit/skills/calendar/TestAssets/GetEventTests/GetEvents_multipleResults.test.dialog diff --git a/tests/skills/calendar/TestAssets/GetEventTests/GetEvents_noResults.test.dialog b/tests/unit/skills/calendar/TestAssets/GetEventTests/GetEvents_noResults.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/GetEventTests/GetEvents_noResults.test.dialog rename to tests/unit/skills/calendar/TestAssets/GetEventTests/GetEvents_noResults.test.dialog diff --git a/tests/skills/calendar/TestAssets/GetEventTests/GetEvents_singleResult.test.dialog b/tests/unit/skills/calendar/TestAssets/GetEventTests/GetEvents_singleResult.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/GetEventTests/GetEvents_singleResult.test.dialog rename to tests/unit/skills/calendar/TestAssets/GetEventTests/GetEvents_singleResult.test.dialog diff --git a/tests/skills/calendar/TestAssets/GetEventTests/GetEvents_withEntity_contact.test.dialog b/tests/unit/skills/calendar/TestAssets/GetEventTests/GetEvents_withEntity_contact.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/GetEventTests/GetEvents_withEntity_contact.test.dialog rename to tests/unit/skills/calendar/TestAssets/GetEventTests/GetEvents_withEntity_contact.test.dialog diff --git a/tests/skills/calendar/TestAssets/GetEventTests/GetEvents_withEntity_datetime.test.dialog b/tests/unit/skills/calendar/TestAssets/GetEventTests/GetEvents_withEntity_datetime.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/GetEventTests/GetEvents_withEntity_datetime.test.dialog rename to tests/unit/skills/calendar/TestAssets/GetEventTests/GetEvents_withEntity_datetime.test.dialog diff --git a/tests/skills/calendar/TestAssets/GetEventTests/HttpRequestMock/getContact.mock.dialog b/tests/unit/skills/calendar/TestAssets/GetEventTests/HttpRequestMock/getContact.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/GetEventTests/HttpRequestMock/getContact.mock.dialog rename to tests/unit/skills/calendar/TestAssets/GetEventTests/HttpRequestMock/getContact.mock.dialog diff --git a/tests/skills/calendar/TestAssets/GetEventTests/HttpRequestMock/getEvent.mock.dialog b/tests/unit/skills/calendar/TestAssets/GetEventTests/HttpRequestMock/getEvent.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/GetEventTests/HttpRequestMock/getEvent.mock.dialog rename to tests/unit/skills/calendar/TestAssets/GetEventTests/HttpRequestMock/getEvent.mock.dialog diff --git a/tests/skills/calendar/TestAssets/GetEventTests/HttpRequestMock/getEvents.mock.dialog b/tests/unit/skills/calendar/TestAssets/GetEventTests/HttpRequestMock/getEvents.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/GetEventTests/HttpRequestMock/getEvents.mock.dialog rename to tests/unit/skills/calendar/TestAssets/GetEventTests/HttpRequestMock/getEvents.mock.dialog diff --git a/tests/skills/calendar/TestAssets/GetEventTests/HttpRequestMock/getEventsNotOrganizer.mock.dialog b/tests/unit/skills/calendar/TestAssets/GetEventTests/HttpRequestMock/getEventsNotOrganizer.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/GetEventTests/HttpRequestMock/getEventsNotOrganizer.mock.dialog rename to tests/unit/skills/calendar/TestAssets/GetEventTests/HttpRequestMock/getEventsNotOrganizer.mock.dialog diff --git a/tests/skills/calendar/TestAssets/GetEventTests/HttpRequestMock/getEventsOnlineMeeting.mock.dialog b/tests/unit/skills/calendar/TestAssets/GetEventTests/HttpRequestMock/getEventsOnlineMeeting.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/GetEventTests/HttpRequestMock/getEventsOnlineMeeting.mock.dialog rename to tests/unit/skills/calendar/TestAssets/GetEventTests/HttpRequestMock/getEventsOnlineMeeting.mock.dialog diff --git a/tests/skills/calendar/TestAssets/GetEventTests/HttpRequestMock/getEvents_noResults.mock.dialog b/tests/unit/skills/calendar/TestAssets/GetEventTests/HttpRequestMock/getEvents_noResults.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/GetEventTests/HttpRequestMock/getEvents_noResults.mock.dialog rename to tests/unit/skills/calendar/TestAssets/GetEventTests/HttpRequestMock/getEvents_noResults.mock.dialog diff --git a/tests/skills/calendar/TestAssets/GetEventTests/HttpRequestMock/getPeople.mock.dialog b/tests/unit/skills/calendar/TestAssets/GetEventTests/HttpRequestMock/getPeople.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/GetEventTests/HttpRequestMock/getPeople.mock.dialog rename to tests/unit/skills/calendar/TestAssets/GetEventTests/HttpRequestMock/getPeople.mock.dialog diff --git a/tests/skills/calendar/TestAssets/GetEventTests/HttpRequestMock/getProfile.mock.dialog b/tests/unit/skills/calendar/TestAssets/GetEventTests/HttpRequestMock/getProfile.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/GetEventTests/HttpRequestMock/getProfile.mock.dialog rename to tests/unit/skills/calendar/TestAssets/GetEventTests/HttpRequestMock/getProfile.mock.dialog diff --git a/tests/skills/calendar/TestAssets/GetEventTests/HttpRequestMock/getWorkingHours.mock.dialog b/tests/unit/skills/calendar/TestAssets/GetEventTests/HttpRequestMock/getWorkingHours.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/GetEventTests/HttpRequestMock/getWorkingHours.mock.dialog rename to tests/unit/skills/calendar/TestAssets/GetEventTests/HttpRequestMock/getWorkingHours.mock.dialog diff --git a/tests/skills/calendar/TestAssets/GetEventTests/UserTokenMock/nomagiccode.mock.dialog b/tests/unit/skills/calendar/TestAssets/GetEventTests/UserTokenMock/nomagiccode.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/GetEventTests/UserTokenMock/nomagiccode.mock.dialog rename to tests/unit/skills/calendar/TestAssets/GetEventTests/UserTokenMock/nomagiccode.mock.dialog diff --git a/tests/skills/calendar/TestAssets/GetEventTests/cachedResponses/Calendar_en_us_lu/1526748840.json b/tests/unit/skills/calendar/TestAssets/GetEventTests/cachedResponses/Calendar_en_us_lu/1526748840.json similarity index 100% rename from tests/skills/calendar/TestAssets/GetEventTests/cachedResponses/Calendar_en_us_lu/1526748840.json rename to tests/unit/skills/calendar/TestAssets/GetEventTests/cachedResponses/Calendar_en_us_lu/1526748840.json diff --git a/tests/skills/calendar/TestAssets/GetEventTests/cachedResponses/Calendar_en_us_lu/1662191128.json b/tests/unit/skills/calendar/TestAssets/GetEventTests/cachedResponses/Calendar_en_us_lu/1662191128.json similarity index 100% rename from tests/skills/calendar/TestAssets/GetEventTests/cachedResponses/Calendar_en_us_lu/1662191128.json rename to tests/unit/skills/calendar/TestAssets/GetEventTests/cachedResponses/Calendar_en_us_lu/1662191128.json diff --git a/tests/skills/calendar/TestAssets/GetEventTests/cachedResponses/Calendar_en_us_lu/1938006685.json b/tests/unit/skills/calendar/TestAssets/GetEventTests/cachedResponses/Calendar_en_us_lu/1938006685.json similarity index 100% rename from tests/skills/calendar/TestAssets/GetEventTests/cachedResponses/Calendar_en_us_lu/1938006685.json rename to tests/unit/skills/calendar/TestAssets/GetEventTests/cachedResponses/Calendar_en_us_lu/1938006685.json diff --git a/tests/skills/calendar/TestAssets/GetEventTests/cachedResponses/Calendar_en_us_lu/2117736667.json b/tests/unit/skills/calendar/TestAssets/GetEventTests/cachedResponses/Calendar_en_us_lu/2117736667.json similarity index 100% rename from tests/skills/calendar/TestAssets/GetEventTests/cachedResponses/Calendar_en_us_lu/2117736667.json rename to tests/unit/skills/calendar/TestAssets/GetEventTests/cachedResponses/Calendar_en_us_lu/2117736667.json diff --git a/tests/skills/calendar/TestAssets/GetEventTests/cachedResponses/Calendar_en_us_lu/3989091574.json b/tests/unit/skills/calendar/TestAssets/GetEventTests/cachedResponses/Calendar_en_us_lu/3989091574.json similarity index 100% rename from tests/skills/calendar/TestAssets/GetEventTests/cachedResponses/Calendar_en_us_lu/3989091574.json rename to tests/unit/skills/calendar/TestAssets/GetEventTests/cachedResponses/Calendar_en_us_lu/3989091574.json diff --git a/tests/skills/calendar/TestAssets/GetEventTests/cachedResponses/Calendar_en_us_lu/904626561.json b/tests/unit/skills/calendar/TestAssets/GetEventTests/cachedResponses/Calendar_en_us_lu/904626561.json similarity index 100% rename from tests/skills/calendar/TestAssets/GetEventTests/cachedResponses/Calendar_en_us_lu/904626561.json rename to tests/unit/skills/calendar/TestAssets/GetEventTests/cachedResponses/Calendar_en_us_lu/904626561.json diff --git a/tests/skills/calendar/TestAssets/GetEventTests/cachedResponses/ListDialog_en_us_lu/3723765839.json b/tests/unit/skills/calendar/TestAssets/GetEventTests/cachedResponses/ListDialog_en_us_lu/3723765839.json similarity index 100% rename from tests/skills/calendar/TestAssets/GetEventTests/cachedResponses/ListDialog_en_us_lu/3723765839.json rename to tests/unit/skills/calendar/TestAssets/GetEventTests/cachedResponses/ListDialog_en_us_lu/3723765839.json diff --git a/tests/skills/calendar/TestAssets/RespondToEventTests/CancelEvent_asAttendee.test.dialog b/tests/unit/skills/calendar/TestAssets/RespondToEventTests/CancelEvent_asAttendee.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/RespondToEventTests/CancelEvent_asAttendee.test.dialog rename to tests/unit/skills/calendar/TestAssets/RespondToEventTests/CancelEvent_asAttendee.test.dialog diff --git a/tests/skills/calendar/TestAssets/RespondToEventTests/CancelEvent_asOrganizer.test.dialog b/tests/unit/skills/calendar/TestAssets/RespondToEventTests/CancelEvent_asOrganizer.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/RespondToEventTests/CancelEvent_asOrganizer.test.dialog rename to tests/unit/skills/calendar/TestAssets/RespondToEventTests/CancelEvent_asOrganizer.test.dialog diff --git a/tests/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/acceptEvent.mock.dialog b/tests/unit/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/acceptEvent.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/acceptEvent.mock.dialog rename to tests/unit/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/acceptEvent.mock.dialog diff --git a/tests/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/declineEvent.mock.dialog b/tests/unit/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/declineEvent.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/declineEvent.mock.dialog rename to tests/unit/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/declineEvent.mock.dialog diff --git a/tests/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/deleteEvent.mock.dialog b/tests/unit/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/deleteEvent.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/deleteEvent.mock.dialog rename to tests/unit/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/deleteEvent.mock.dialog diff --git a/tests/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/getContact.mock.dialog b/tests/unit/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/getContact.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/getContact.mock.dialog rename to tests/unit/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/getContact.mock.dialog diff --git a/tests/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/getEvent.mock.dialog b/tests/unit/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/getEvent.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/getEvent.mock.dialog rename to tests/unit/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/getEvent.mock.dialog diff --git a/tests/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/getEvents.mock.dialog b/tests/unit/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/getEvents.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/getEvents.mock.dialog rename to tests/unit/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/getEvents.mock.dialog diff --git a/tests/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/getEventsNotOrganizer.mock.dialog b/tests/unit/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/getEventsNotOrganizer.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/getEventsNotOrganizer.mock.dialog rename to tests/unit/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/getEventsNotOrganizer.mock.dialog diff --git a/tests/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/getPeople.mock.dialog b/tests/unit/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/getPeople.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/getPeople.mock.dialog rename to tests/unit/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/getPeople.mock.dialog diff --git a/tests/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/getProfile.mock.dialog b/tests/unit/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/getProfile.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/getProfile.mock.dialog rename to tests/unit/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/getProfile.mock.dialog diff --git a/tests/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/getWorkingHours.mock.dialog b/tests/unit/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/getWorkingHours.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/getWorkingHours.mock.dialog rename to tests/unit/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/getWorkingHours.mock.dialog diff --git a/tests/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/tentativelyAcceptEvent.mock.dialog b/tests/unit/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/tentativelyAcceptEvent.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/tentativelyAcceptEvent.mock.dialog rename to tests/unit/skills/calendar/TestAssets/RespondToEventTests/HttpRequestMock/tentativelyAcceptEvent.mock.dialog diff --git a/tests/skills/calendar/TestAssets/RespondToEventTests/RespondToEvent_Accept.test.dialog b/tests/unit/skills/calendar/TestAssets/RespondToEventTests/RespondToEvent_Accept.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/RespondToEventTests/RespondToEvent_Accept.test.dialog rename to tests/unit/skills/calendar/TestAssets/RespondToEventTests/RespondToEvent_Accept.test.dialog diff --git a/tests/skills/calendar/TestAssets/RespondToEventTests/RespondToEvent_Decline.test.dialog b/tests/unit/skills/calendar/TestAssets/RespondToEventTests/RespondToEvent_Decline.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/RespondToEventTests/RespondToEvent_Decline.test.dialog rename to tests/unit/skills/calendar/TestAssets/RespondToEventTests/RespondToEvent_Decline.test.dialog diff --git a/tests/skills/calendar/TestAssets/RespondToEventTests/RespondToEvent_TentativelyAccept.test.dialog b/tests/unit/skills/calendar/TestAssets/RespondToEventTests/RespondToEvent_TentativelyAccept.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/RespondToEventTests/RespondToEvent_TentativelyAccept.test.dialog rename to tests/unit/skills/calendar/TestAssets/RespondToEventTests/RespondToEvent_TentativelyAccept.test.dialog diff --git a/tests/skills/calendar/TestAssets/RespondToEventTests/UserTokenMock/nomagiccode.mock.dialog b/tests/unit/skills/calendar/TestAssets/RespondToEventTests/UserTokenMock/nomagiccode.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/RespondToEventTests/UserTokenMock/nomagiccode.mock.dialog rename to tests/unit/skills/calendar/TestAssets/RespondToEventTests/UserTokenMock/nomagiccode.mock.dialog diff --git a/tests/skills/calendar/TestAssets/RespondToEventTests/cachedResponses/Calendar_en_us_lu/1290152274.json b/tests/unit/skills/calendar/TestAssets/RespondToEventTests/cachedResponses/Calendar_en_us_lu/1290152274.json similarity index 100% rename from tests/skills/calendar/TestAssets/RespondToEventTests/cachedResponses/Calendar_en_us_lu/1290152274.json rename to tests/unit/skills/calendar/TestAssets/RespondToEventTests/cachedResponses/Calendar_en_us_lu/1290152274.json diff --git a/tests/skills/calendar/TestAssets/RespondToEventTests/cachedResponses/Calendar_en_us_lu/2454555038.json b/tests/unit/skills/calendar/TestAssets/RespondToEventTests/cachedResponses/Calendar_en_us_lu/2454555038.json similarity index 100% rename from tests/skills/calendar/TestAssets/RespondToEventTests/cachedResponses/Calendar_en_us_lu/2454555038.json rename to tests/unit/skills/calendar/TestAssets/RespondToEventTests/cachedResponses/Calendar_en_us_lu/2454555038.json diff --git a/tests/skills/calendar/TestAssets/RespondToEventTests/cachedResponses/Calendar_en_us_lu/2818238401.json b/tests/unit/skills/calendar/TestAssets/RespondToEventTests/cachedResponses/Calendar_en_us_lu/2818238401.json similarity index 100% rename from tests/skills/calendar/TestAssets/RespondToEventTests/cachedResponses/Calendar_en_us_lu/2818238401.json rename to tests/unit/skills/calendar/TestAssets/RespondToEventTests/cachedResponses/Calendar_en_us_lu/2818238401.json diff --git a/tests/skills/calendar/TestAssets/RespondToEventTests/cachedResponses/Calendar_en_us_lu/3653033598.json b/tests/unit/skills/calendar/TestAssets/RespondToEventTests/cachedResponses/Calendar_en_us_lu/3653033598.json similarity index 100% rename from tests/skills/calendar/TestAssets/RespondToEventTests/cachedResponses/Calendar_en_us_lu/3653033598.json rename to tests/unit/skills/calendar/TestAssets/RespondToEventTests/cachedResponses/Calendar_en_us_lu/3653033598.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/HttpRequestMock/getContact.mock.dialog b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/HttpRequestMock/getContact.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/HttpRequestMock/getContact.mock.dialog rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/HttpRequestMock/getContact.mock.dialog diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/HttpRequestMock/getEvent.mock.dialog b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/HttpRequestMock/getEvent.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/HttpRequestMock/getEvent.mock.dialog rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/HttpRequestMock/getEvent.mock.dialog diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/HttpRequestMock/getEvents.mock.dialog b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/HttpRequestMock/getEvents.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/HttpRequestMock/getEvents.mock.dialog rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/HttpRequestMock/getEvents.mock.dialog diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/HttpRequestMock/getEventsNotOrganizer.mock.dialog b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/HttpRequestMock/getEventsNotOrganizer.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/HttpRequestMock/getEventsNotOrganizer.mock.dialog rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/HttpRequestMock/getEventsNotOrganizer.mock.dialog diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/HttpRequestMock/getEventsOnlineMeeting.mock.dialog b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/HttpRequestMock/getEventsOnlineMeeting.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/HttpRequestMock/getEventsOnlineMeeting.mock.dialog rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/HttpRequestMock/getEventsOnlineMeeting.mock.dialog diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/HttpRequestMock/getPeople.mock.dialog b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/HttpRequestMock/getPeople.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/HttpRequestMock/getPeople.mock.dialog rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/HttpRequestMock/getPeople.mock.dialog diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/HttpRequestMock/getProfile.mock.dialog b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/HttpRequestMock/getProfile.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/HttpRequestMock/getProfile.mock.dialog rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/HttpRequestMock/getProfile.mock.dialog diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/HttpRequestMock/getWorkingHours.mock.dialog b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/HttpRequestMock/getWorkingHours.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/HttpRequestMock/getWorkingHours.mock.dialog rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/HttpRequestMock/getWorkingHours.mock.dialog diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/HttpRequestMock/updateEvent.mock.dialog b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/HttpRequestMock/updateEvent.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/HttpRequestMock/updateEvent.mock.dialog rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/HttpRequestMock/updateEvent.mock.dialog diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_basic_attendees.test.dialog b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_basic_attendees.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_basic_attendees.test.dialog rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_basic_attendees.test.dialog diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_basic_datetime.test.dialog b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_basic_datetime.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_basic_datetime.test.dialog rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_basic_datetime.test.dialog diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_basic_description.test.dialog b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_basic_description.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_basic_description.test.dialog rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_basic_description.test.dialog diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_basic_duration.test.dialog b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_basic_duration.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_basic_duration.test.dialog rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_basic_duration.test.dialog diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_basic_location.test.dialog b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_basic_location.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_basic_location.test.dialog rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_basic_location.test.dialog diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_basic_onlineMeeting.test.dialog b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_basic_onlineMeeting.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_basic_onlineMeeting.test.dialog rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_basic_onlineMeeting.test.dialog diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_basic_title.test.dialog b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_basic_title.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_basic_title.test.dialog rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_basic_title.test.dialog diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_interruption_setAttendeesAdd.test.dialog b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_interruption_setAttendeesAdd.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_interruption_setAttendeesAdd.test.dialog rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_interruption_setAttendeesAdd.test.dialog diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_interruption_setAttendeesRemove.test.dialog b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_interruption_setAttendeesRemove.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_interruption_setAttendeesRemove.test.dialog rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_interruption_setAttendeesRemove.test.dialog diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_interruption_setDateTime.test.dialog b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_interruption_setDateTime.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_interruption_setDateTime.test.dialog rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_interruption_setDateTime.test.dialog diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_interruption_setDescription.test.dialog b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_interruption_setDescription.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_interruption_setDescription.test.dialog rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_interruption_setDescription.test.dialog diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_interruption_setDuration.test.dialog b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_interruption_setDuration.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_interruption_setDuration.test.dialog rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_interruption_setDuration.test.dialog diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_interruption_setLocation.test.dialog b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_interruption_setLocation.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_interruption_setLocation.test.dialog rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_interruption_setLocation.test.dialog diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_interruption_setOnlineMeeting.test.dialog b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_interruption_setOnlineMeeting.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_interruption_setOnlineMeeting.test.dialog rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_interruption_setOnlineMeeting.test.dialog diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_interruption_setTitle.test.dialog b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_interruption_setTitle.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_interruption_setTitle.test.dialog rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_interruption_setTitle.test.dialog diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_trigger_setAttendeesAdd.test.dialog b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_trigger_setAttendeesAdd.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_trigger_setAttendeesAdd.test.dialog rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_trigger_setAttendeesAdd.test.dialog diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_trigger_setAttendeesRemove.test.dialog b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_trigger_setAttendeesRemove.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_trigger_setAttendeesRemove.test.dialog rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_trigger_setAttendeesRemove.test.dialog diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_trigger_setDateTime.test.dialog b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_trigger_setDateTime.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_trigger_setDateTime.test.dialog rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_trigger_setDateTime.test.dialog diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_trigger_setDescription.test.dialog b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_trigger_setDescription.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_trigger_setDescription.test.dialog rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_trigger_setDescription.test.dialog diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_trigger_setDuration.test.dialog b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_trigger_setDuration.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_trigger_setDuration.test.dialog rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_trigger_setDuration.test.dialog diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_trigger_setLocation.test.dialog b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_trigger_setLocation.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_trigger_setLocation.test.dialog rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_trigger_setLocation.test.dialog diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_trigger_setOnlineMeeting.test.dialog b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_trigger_setOnlineMeeting.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_trigger_setOnlineMeeting.test.dialog rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_trigger_setOnlineMeeting.test.dialog diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_trigger_setTitle.test.dialog b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_trigger_setTitle.test.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_trigger_setTitle.test.dialog rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/UpdateEvent_trigger_setTitle.test.dialog diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/UserTokenMock/nomagiccode.mock.dialog b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/UserTokenMock/nomagiccode.mock.dialog similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/UserTokenMock/nomagiccode.mock.dialog rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/UserTokenMock/nomagiccode.mock.dialog diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/1145996603.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/1145996603.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/1145996603.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/1145996603.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/1544134286.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/1544134286.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/1544134286.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/1544134286.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/1664405117.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/1664405117.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/1664405117.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/1664405117.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/2122886558.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/2122886558.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/2122886558.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/2122886558.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/2359587634.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/2359587634.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/2359587634.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/2359587634.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/2392124786.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/2392124786.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/2392124786.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/2392124786.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/274526507.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/274526507.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/274526507.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/274526507.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/2860054276.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/2860054276.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/2860054276.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/2860054276.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/2937641126.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/2937641126.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/2937641126.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/2937641126.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/4148905213.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/4148905213.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/4148905213.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/4148905213.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/463684775.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/463684775.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/463684775.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/463684775.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/493543230.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/493543230.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/493543230.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/493543230.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/717479241.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/717479241.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/717479241.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/Calendar_en_us_lu/717479241.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/GetContactsDialog_en_us_lu/1392353970.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/GetContactsDialog_en_us_lu/1392353970.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/GetContactsDialog_en_us_lu/1392353970.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/GetContactsDialog_en_us_lu/1392353970.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/GetContactsDialog_en_us_lu/4085864143.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/GetContactsDialog_en_us_lu/4085864143.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/GetContactsDialog_en_us_lu/4085864143.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/GetContactsDialog_en_us_lu/4085864143.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/GetEventsDialog_en_us_lu/2993157681.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/GetEventsDialog_en_us_lu/2993157681.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/GetEventsDialog_en_us_lu/2993157681.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/GetEventsDialog_en_us_lu/2993157681.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/ListDialog_en_us_lu/3723765839.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/ListDialog_en_us_lu/3723765839.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/ListDialog_en_us_lu/3723765839.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/ListDialog_en_us_lu/3723765839.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/1145996603.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/1145996603.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/1145996603.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/1145996603.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/1392353970.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/1392353970.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/1392353970.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/1392353970.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/1490330477.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/1490330477.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/1490330477.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/1490330477.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/1544134286.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/1544134286.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/1544134286.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/1544134286.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/1639066482.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/1639066482.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/1639066482.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/1639066482.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/1704462493.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/1704462493.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/1704462493.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/1704462493.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/2250432073.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/2250432073.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/2250432073.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/2250432073.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/2338442885.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/2338442885.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/2338442885.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/2338442885.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/2359587634.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/2359587634.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/2359587634.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/2359587634.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/2395873221.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/2395873221.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/2395873221.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/2395873221.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/2464902023.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/2464902023.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/2464902023.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/2464902023.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/2564825304.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/2564825304.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/2564825304.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/2564825304.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/2903480491.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/2903480491.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/2903480491.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/2903480491.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/2937641126.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/2937641126.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/2937641126.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/2937641126.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/3216421296.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/3216421296.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/3216421296.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/3216421296.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/3255472636.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/3255472636.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/3255472636.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/3255472636.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/3386169890.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/3386169890.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/3386169890.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/3386169890.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/3558312640.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/3558312640.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/3558312640.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/3558312640.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/3857112431.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/3857112431.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/3857112431.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/3857112431.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/4278021066.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/4278021066.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/4278021066.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/4278021066.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/428143476.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/428143476.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/428143476.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/428143476.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/457420302.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/457420302.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/457420302.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/457420302.json diff --git a/tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/766092102.json b/tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/766092102.json similarity index 100% rename from tests/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/766092102.json rename to tests/unit/skills/calendar/TestAssets/UpdateEventTests/cachedResponses/UpdateEventDialog_en_us_lu/766092102.json diff --git a/tests/skills/calendar/UpdateEventTests.cs b/tests/unit/skills/calendar/UpdateEventTests.cs similarity index 100% rename from tests/skills/calendar/UpdateEventTests.cs rename to tests/unit/skills/calendar/UpdateEventTests.cs diff --git a/tests/skills/calendar/app.schema b/tests/unit/skills/calendar/app.schema similarity index 100% rename from tests/skills/calendar/app.schema rename to tests/unit/skills/calendar/app.schema diff --git a/tests/skills/calendar/testsettings.json b/tests/unit/skills/calendar/testsettings.json similarity index 100% rename from tests/skills/calendar/testsettings.json rename to tests/unit/skills/calendar/testsettings.json diff --git a/tests/skills/common/GraphComponentRegistration.cs b/tests/unit/skills/common/GraphComponentRegistration.cs similarity index 100% rename from tests/skills/common/GraphComponentRegistration.cs rename to tests/unit/skills/common/GraphComponentRegistration.cs diff --git a/tests/skills/common/Microsoft.Bot.Dialogs.Tests.Common.csproj b/tests/unit/skills/common/Microsoft.Bot.Dialogs.Tests.Common.csproj similarity index 90% rename from tests/skills/common/Microsoft.Bot.Dialogs.Tests.Common.csproj rename to tests/unit/skills/common/Microsoft.Bot.Dialogs.Tests.Common.csproj index d385a3800d..05c521c926 100644 --- a/tests/skills/common/Microsoft.Bot.Dialogs.Tests.Common.csproj +++ b/tests/unit/skills/common/Microsoft.Bot.Dialogs.Tests.Common.csproj @@ -17,7 +17,7 @@ - + diff --git a/tests/skills/calendar/NuGet.Config b/tests/unit/skills/common/NuGet.Config similarity index 100% rename from tests/skills/calendar/NuGet.Config rename to tests/unit/skills/common/NuGet.Config diff --git a/tests/skills/common/PbxDialogTestBase.cs b/tests/unit/skills/common/PbxDialogTestBase.cs similarity index 100% rename from tests/skills/common/PbxDialogTestBase.cs rename to tests/unit/skills/common/PbxDialogTestBase.cs diff --git a/tests/skills/people/GetCollaboratorsTests.cs b/tests/unit/skills/people/GetCollaboratorsTests.cs similarity index 100% rename from tests/skills/people/GetCollaboratorsTests.cs rename to tests/unit/skills/people/GetCollaboratorsTests.cs diff --git a/tests/skills/people/GetDirectReportsTests.cs b/tests/unit/skills/people/GetDirectReportsTests.cs similarity index 100% rename from tests/skills/people/GetDirectReportsTests.cs rename to tests/unit/skills/people/GetDirectReportsTests.cs diff --git a/tests/skills/people/GetManagerTests.cs b/tests/unit/skills/people/GetManagerTests.cs similarity index 100% rename from tests/skills/people/GetManagerTests.cs rename to tests/unit/skills/people/GetManagerTests.cs diff --git a/tests/skills/people/GetPeersTests.cs b/tests/unit/skills/people/GetPeersTests.cs similarity index 100% rename from tests/skills/people/GetPeersTests.cs rename to tests/unit/skills/people/GetPeersTests.cs diff --git a/tests/skills/people/GetProfileTests.cs b/tests/unit/skills/people/GetProfileTests.cs similarity index 100% rename from tests/skills/people/GetProfileTests.cs rename to tests/unit/skills/people/GetProfileTests.cs diff --git a/tests/skills/people/Microsoft.Bot.Dialogs.Tests.Skills.People.csproj b/tests/unit/skills/people/Microsoft.Bot.Dialogs.Tests.Skills.People.csproj similarity index 86% rename from tests/skills/people/Microsoft.Bot.Dialogs.Tests.Skills.People.csproj rename to tests/unit/skills/people/Microsoft.Bot.Dialogs.Tests.Skills.People.csproj index b0624527cf..02c6c2f4e2 100644 --- a/tests/skills/people/Microsoft.Bot.Dialogs.Tests.Skills.People.csproj +++ b/tests/unit/skills/people/Microsoft.Bot.Dialogs.Tests.Skills.People.csproj @@ -18,7 +18,7 @@ - + @@ -33,7 +33,7 @@ Always - + Always diff --git a/tests/skills/people/TestAssets/GetCollaboratorsTests/GetCollaborators_CollaboratorNotFound.test.dialog b/tests/unit/skills/people/TestAssets/GetCollaboratorsTests/GetCollaborators_CollaboratorNotFound.test.dialog similarity index 100% rename from tests/skills/people/TestAssets/GetCollaboratorsTests/GetCollaborators_CollaboratorNotFound.test.dialog rename to tests/unit/skills/people/TestAssets/GetCollaboratorsTests/GetCollaborators_CollaboratorNotFound.test.dialog diff --git a/tests/skills/people/TestAssets/GetCollaboratorsTests/GetCollaborators_MoreThanOneFound.test.dialog b/tests/unit/skills/people/TestAssets/GetCollaboratorsTests/GetCollaborators_MoreThanOneFound.test.dialog similarity index 100% rename from tests/skills/people/TestAssets/GetCollaboratorsTests/GetCollaborators_MoreThanOneFound.test.dialog rename to tests/unit/skills/people/TestAssets/GetCollaboratorsTests/GetCollaborators_MoreThanOneFound.test.dialog diff --git a/tests/skills/people/TestAssets/GetCollaboratorsTests/GetCollaborators_OneFound.test.dialog b/tests/unit/skills/people/TestAssets/GetCollaboratorsTests/GetCollaborators_OneFound.test.dialog similarity index 100% rename from tests/skills/people/TestAssets/GetCollaboratorsTests/GetCollaborators_OneFound.test.dialog rename to tests/unit/skills/people/TestAssets/GetCollaboratorsTests/GetCollaborators_OneFound.test.dialog diff --git a/tests/skills/people/TestAssets/GetCollaboratorsTests/UserTokenMock/nomagiccode.mock.dialog b/tests/unit/skills/people/TestAssets/GetCollaboratorsTests/UserTokenMock/nomagiccode.mock.dialog similarity index 100% rename from tests/skills/people/TestAssets/GetCollaboratorsTests/UserTokenMock/nomagiccode.mock.dialog rename to tests/unit/skills/people/TestAssets/GetCollaboratorsTests/UserTokenMock/nomagiccode.mock.dialog diff --git a/tests/skills/people/TestAssets/GetCollaboratorsTests/cachedResponses/ListDialog_en_us_lu/1176839528.json b/tests/unit/skills/people/TestAssets/GetCollaboratorsTests/cachedResponses/ListDialog_en_us_lu/1176839528.json similarity index 100% rename from tests/skills/people/TestAssets/GetCollaboratorsTests/cachedResponses/ListDialog_en_us_lu/1176839528.json rename to tests/unit/skills/people/TestAssets/GetCollaboratorsTests/cachedResponses/ListDialog_en_us_lu/1176839528.json diff --git a/tests/skills/people/TestAssets/GetCollaboratorsTests/cachedResponses/ListDialog_en_us_lu/3723765839.json b/tests/unit/skills/people/TestAssets/GetCollaboratorsTests/cachedResponses/ListDialog_en_us_lu/3723765839.json similarity index 100% rename from tests/skills/people/TestAssets/GetCollaboratorsTests/cachedResponses/ListDialog_en_us_lu/3723765839.json rename to tests/unit/skills/people/TestAssets/GetCollaboratorsTests/cachedResponses/ListDialog_en_us_lu/3723765839.json diff --git a/tests/skills/people/TestAssets/GetCollaboratorsTests/cachedResponses/people_en_us_lu/1916631299.json b/tests/unit/skills/people/TestAssets/GetCollaboratorsTests/cachedResponses/people_en_us_lu/1916631299.json similarity index 100% rename from tests/skills/people/TestAssets/GetCollaboratorsTests/cachedResponses/people_en_us_lu/1916631299.json rename to tests/unit/skills/people/TestAssets/GetCollaboratorsTests/cachedResponses/people_en_us_lu/1916631299.json diff --git a/tests/skills/people/TestAssets/GetDirectReportsTests/GetDirectReports_DirectReportsNotFound.test.dialog b/tests/unit/skills/people/TestAssets/GetDirectReportsTests/GetDirectReports_DirectReportsNotFound.test.dialog similarity index 100% rename from tests/skills/people/TestAssets/GetDirectReportsTests/GetDirectReports_DirectReportsNotFound.test.dialog rename to tests/unit/skills/people/TestAssets/GetDirectReportsTests/GetDirectReports_DirectReportsNotFound.test.dialog diff --git a/tests/skills/people/TestAssets/GetDirectReportsTests/GetDirectReports_MoreThanOneFound.test.dialog b/tests/unit/skills/people/TestAssets/GetDirectReportsTests/GetDirectReports_MoreThanOneFound.test.dialog similarity index 100% rename from tests/skills/people/TestAssets/GetDirectReportsTests/GetDirectReports_MoreThanOneFound.test.dialog rename to tests/unit/skills/people/TestAssets/GetDirectReportsTests/GetDirectReports_MoreThanOneFound.test.dialog diff --git a/tests/skills/people/TestAssets/GetDirectReportsTests/GetDirectReports_OneFound.test.dialog b/tests/unit/skills/people/TestAssets/GetDirectReportsTests/GetDirectReports_OneFound.test.dialog similarity index 100% rename from tests/skills/people/TestAssets/GetDirectReportsTests/GetDirectReports_OneFound.test.dialog rename to tests/unit/skills/people/TestAssets/GetDirectReportsTests/GetDirectReports_OneFound.test.dialog diff --git a/tests/skills/people/TestAssets/GetDirectReportsTests/GetDirectReports_UserNotFound.test.dialog b/tests/unit/skills/people/TestAssets/GetDirectReportsTests/GetDirectReports_UserNotFound.test.dialog similarity index 100% rename from tests/skills/people/TestAssets/GetDirectReportsTests/GetDirectReports_UserNotFound.test.dialog rename to tests/unit/skills/people/TestAssets/GetDirectReportsTests/GetDirectReports_UserNotFound.test.dialog diff --git a/tests/skills/people/TestAssets/GetDirectReportsTests/UserTokenMock/nomagiccode.mock.dialog b/tests/unit/skills/people/TestAssets/GetDirectReportsTests/UserTokenMock/nomagiccode.mock.dialog similarity index 100% rename from tests/skills/people/TestAssets/GetDirectReportsTests/UserTokenMock/nomagiccode.mock.dialog rename to tests/unit/skills/people/TestAssets/GetDirectReportsTests/UserTokenMock/nomagiccode.mock.dialog diff --git a/tests/skills/people/TestAssets/GetDirectReportsTests/cachedResponses/ListDialog_en_us_lu/1221289675.json b/tests/unit/skills/people/TestAssets/GetDirectReportsTests/cachedResponses/ListDialog_en_us_lu/1221289675.json similarity index 100% rename from tests/skills/people/TestAssets/GetDirectReportsTests/cachedResponses/ListDialog_en_us_lu/1221289675.json rename to tests/unit/skills/people/TestAssets/GetDirectReportsTests/cachedResponses/ListDialog_en_us_lu/1221289675.json diff --git a/tests/skills/people/TestAssets/GetDirectReportsTests/cachedResponses/ListDialog_en_us_lu/3723765839.json b/tests/unit/skills/people/TestAssets/GetDirectReportsTests/cachedResponses/ListDialog_en_us_lu/3723765839.json similarity index 100% rename from tests/skills/people/TestAssets/GetDirectReportsTests/cachedResponses/ListDialog_en_us_lu/3723765839.json rename to tests/unit/skills/people/TestAssets/GetDirectReportsTests/cachedResponses/ListDialog_en_us_lu/3723765839.json diff --git a/tests/skills/people/TestAssets/GetDirectReportsTests/cachedResponses/people_en_us_lu/1541877400.json b/tests/unit/skills/people/TestAssets/GetDirectReportsTests/cachedResponses/people_en_us_lu/1541877400.json similarity index 100% rename from tests/skills/people/TestAssets/GetDirectReportsTests/cachedResponses/people_en_us_lu/1541877400.json rename to tests/unit/skills/people/TestAssets/GetDirectReportsTests/cachedResponses/people_en_us_lu/1541877400.json diff --git a/tests/skills/people/TestAssets/GetDirectReportsTests/cachedResponses/people_en_us_lu/1968135638.json b/tests/unit/skills/people/TestAssets/GetDirectReportsTests/cachedResponses/people_en_us_lu/1968135638.json similarity index 100% rename from tests/skills/people/TestAssets/GetDirectReportsTests/cachedResponses/people_en_us_lu/1968135638.json rename to tests/unit/skills/people/TestAssets/GetDirectReportsTests/cachedResponses/people_en_us_lu/1968135638.json diff --git a/tests/skills/people/TestAssets/GetDirectReportsTests/cachedResponses/people_en_us_lu/2599657509.json b/tests/unit/skills/people/TestAssets/GetDirectReportsTests/cachedResponses/people_en_us_lu/2599657509.json similarity index 100% rename from tests/skills/people/TestAssets/GetDirectReportsTests/cachedResponses/people_en_us_lu/2599657509.json rename to tests/unit/skills/people/TestAssets/GetDirectReportsTests/cachedResponses/people_en_us_lu/2599657509.json diff --git a/tests/skills/people/TestAssets/GetManagerTests/GetManager_HappyPath.test.dialog b/tests/unit/skills/people/TestAssets/GetManagerTests/GetManager_HappyPath.test.dialog similarity index 100% rename from tests/skills/people/TestAssets/GetManagerTests/GetManager_HappyPath.test.dialog rename to tests/unit/skills/people/TestAssets/GetManagerTests/GetManager_HappyPath.test.dialog diff --git a/tests/skills/people/TestAssets/GetManagerTests/GetManager_NoManager.test.dialog b/tests/unit/skills/people/TestAssets/GetManagerTests/GetManager_NoManager.test.dialog similarity index 100% rename from tests/skills/people/TestAssets/GetManagerTests/GetManager_NoManager.test.dialog rename to tests/unit/skills/people/TestAssets/GetManagerTests/GetManager_NoManager.test.dialog diff --git a/tests/skills/people/TestAssets/GetManagerTests/GetManager_UserNotFound.test.dialog b/tests/unit/skills/people/TestAssets/GetManagerTests/GetManager_UserNotFound.test.dialog similarity index 100% rename from tests/skills/people/TestAssets/GetManagerTests/GetManager_UserNotFound.test.dialog rename to tests/unit/skills/people/TestAssets/GetManagerTests/GetManager_UserNotFound.test.dialog diff --git a/tests/skills/people/TestAssets/GetManagerTests/UserTokenMock/nomagiccode.mock.dialog b/tests/unit/skills/people/TestAssets/GetManagerTests/UserTokenMock/nomagiccode.mock.dialog similarity index 100% rename from tests/skills/people/TestAssets/GetManagerTests/UserTokenMock/nomagiccode.mock.dialog rename to tests/unit/skills/people/TestAssets/GetManagerTests/UserTokenMock/nomagiccode.mock.dialog diff --git a/tests/skills/people/TestAssets/GetManagerTests/cachedResponses/people_en_us_lu/1541877400.json b/tests/unit/skills/people/TestAssets/GetManagerTests/cachedResponses/people_en_us_lu/1541877400.json similarity index 100% rename from tests/skills/people/TestAssets/GetManagerTests/cachedResponses/people_en_us_lu/1541877400.json rename to tests/unit/skills/people/TestAssets/GetManagerTests/cachedResponses/people_en_us_lu/1541877400.json diff --git a/tests/skills/people/TestAssets/GetManagerTests/cachedResponses/people_en_us_lu/2918515466.json b/tests/unit/skills/people/TestAssets/GetManagerTests/cachedResponses/people_en_us_lu/2918515466.json similarity index 100% rename from tests/skills/people/TestAssets/GetManagerTests/cachedResponses/people_en_us_lu/2918515466.json rename to tests/unit/skills/people/TestAssets/GetManagerTests/cachedResponses/people_en_us_lu/2918515466.json diff --git a/tests/skills/people/TestAssets/GetManagerTests/cachedResponses/people_en_us_lu/780636099.json b/tests/unit/skills/people/TestAssets/GetManagerTests/cachedResponses/people_en_us_lu/780636099.json similarity index 100% rename from tests/skills/people/TestAssets/GetManagerTests/cachedResponses/people_en_us_lu/780636099.json rename to tests/unit/skills/people/TestAssets/GetManagerTests/cachedResponses/people_en_us_lu/780636099.json diff --git a/tests/skills/people/TestAssets/GetPeersTests/GetPeers_MoreThanOneFound.test.dialog b/tests/unit/skills/people/TestAssets/GetPeersTests/GetPeers_MoreThanOneFound.test.dialog similarity index 100% rename from tests/skills/people/TestAssets/GetPeersTests/GetPeers_MoreThanOneFound.test.dialog rename to tests/unit/skills/people/TestAssets/GetPeersTests/GetPeers_MoreThanOneFound.test.dialog diff --git a/tests/skills/people/TestAssets/GetPeersTests/GetPeers_OneFound.test.dialog b/tests/unit/skills/people/TestAssets/GetPeersTests/GetPeers_OneFound.test.dialog similarity index 100% rename from tests/skills/people/TestAssets/GetPeersTests/GetPeers_OneFound.test.dialog rename to tests/unit/skills/people/TestAssets/GetPeersTests/GetPeers_OneFound.test.dialog diff --git a/tests/skills/people/TestAssets/GetPeersTests/GetPeers_PeersNotFound.test.dialog b/tests/unit/skills/people/TestAssets/GetPeersTests/GetPeers_PeersNotFound.test.dialog similarity index 100% rename from tests/skills/people/TestAssets/GetPeersTests/GetPeers_PeersNotFound.test.dialog rename to tests/unit/skills/people/TestAssets/GetPeersTests/GetPeers_PeersNotFound.test.dialog diff --git a/tests/skills/people/TestAssets/GetPeersTests/GetPeers_UserNotFound.test.dialog b/tests/unit/skills/people/TestAssets/GetPeersTests/GetPeers_UserNotFound.test.dialog similarity index 100% rename from tests/skills/people/TestAssets/GetPeersTests/GetPeers_UserNotFound.test.dialog rename to tests/unit/skills/people/TestAssets/GetPeersTests/GetPeers_UserNotFound.test.dialog diff --git a/tests/skills/people/TestAssets/GetPeersTests/UserTokenMock/nomagiccode.mock.dialog b/tests/unit/skills/people/TestAssets/GetPeersTests/UserTokenMock/nomagiccode.mock.dialog similarity index 100% rename from tests/skills/people/TestAssets/GetPeersTests/UserTokenMock/nomagiccode.mock.dialog rename to tests/unit/skills/people/TestAssets/GetPeersTests/UserTokenMock/nomagiccode.mock.dialog diff --git a/tests/skills/people/TestAssets/GetPeersTests/cachedResponses/ListDialog_en_us_lu/1221289675.json b/tests/unit/skills/people/TestAssets/GetPeersTests/cachedResponses/ListDialog_en_us_lu/1221289675.json similarity index 100% rename from tests/skills/people/TestAssets/GetPeersTests/cachedResponses/ListDialog_en_us_lu/1221289675.json rename to tests/unit/skills/people/TestAssets/GetPeersTests/cachedResponses/ListDialog_en_us_lu/1221289675.json diff --git a/tests/skills/people/TestAssets/GetPeersTests/cachedResponses/ListDialog_en_us_lu/3723765839.json b/tests/unit/skills/people/TestAssets/GetPeersTests/cachedResponses/ListDialog_en_us_lu/3723765839.json similarity index 100% rename from tests/skills/people/TestAssets/GetPeersTests/cachedResponses/ListDialog_en_us_lu/3723765839.json rename to tests/unit/skills/people/TestAssets/GetPeersTests/cachedResponses/ListDialog_en_us_lu/3723765839.json diff --git a/tests/skills/people/TestAssets/GetPeersTests/cachedResponses/people_en_us_lu/1175234241.json b/tests/unit/skills/people/TestAssets/GetPeersTests/cachedResponses/people_en_us_lu/1175234241.json similarity index 100% rename from tests/skills/people/TestAssets/GetPeersTests/cachedResponses/people_en_us_lu/1175234241.json rename to tests/unit/skills/people/TestAssets/GetPeersTests/cachedResponses/people_en_us_lu/1175234241.json diff --git a/tests/skills/people/TestAssets/GetPeersTests/cachedResponses/people_en_us_lu/2875849222.json b/tests/unit/skills/people/TestAssets/GetPeersTests/cachedResponses/people_en_us_lu/2875849222.json similarity index 100% rename from tests/skills/people/TestAssets/GetPeersTests/cachedResponses/people_en_us_lu/2875849222.json rename to tests/unit/skills/people/TestAssets/GetPeersTests/cachedResponses/people_en_us_lu/2875849222.json diff --git a/tests/skills/people/TestAssets/GetPeersTests/cachedResponses/people_en_us_lu/3817055125.json b/tests/unit/skills/people/TestAssets/GetPeersTests/cachedResponses/people_en_us_lu/3817055125.json similarity index 100% rename from tests/skills/people/TestAssets/GetPeersTests/cachedResponses/people_en_us_lu/3817055125.json rename to tests/unit/skills/people/TestAssets/GetPeersTests/cachedResponses/people_en_us_lu/3817055125.json diff --git a/tests/skills/people/TestAssets/GetProfileTests/GetProfile_HappyPath.test.dialog b/tests/unit/skills/people/TestAssets/GetProfileTests/GetProfile_HappyPath.test.dialog similarity index 100% rename from tests/skills/people/TestAssets/GetProfileTests/GetProfile_HappyPath.test.dialog rename to tests/unit/skills/people/TestAssets/GetProfileTests/GetProfile_HappyPath.test.dialog diff --git a/tests/skills/people/TestAssets/GetProfileTests/GetProfile_HappyPath_MultipleUsers.test.dialog b/tests/unit/skills/people/TestAssets/GetProfileTests/GetProfile_HappyPath_MultipleUsers.test.dialog similarity index 100% rename from tests/skills/people/TestAssets/GetProfileTests/GetProfile_HappyPath_MultipleUsers.test.dialog rename to tests/unit/skills/people/TestAssets/GetProfileTests/GetProfile_HappyPath_MultipleUsers.test.dialog diff --git a/tests/skills/people/TestAssets/GetProfileTests/GetProfile_UserNotFound.test.dialog b/tests/unit/skills/people/TestAssets/GetProfileTests/GetProfile_UserNotFound.test.dialog similarity index 100% rename from tests/skills/people/TestAssets/GetProfileTests/GetProfile_UserNotFound.test.dialog rename to tests/unit/skills/people/TestAssets/GetProfileTests/GetProfile_UserNotFound.test.dialog diff --git a/tests/skills/people/TestAssets/GetProfileTests/UserTokenMock/nomagiccode.mock.dialog b/tests/unit/skills/people/TestAssets/GetProfileTests/UserTokenMock/nomagiccode.mock.dialog similarity index 100% rename from tests/skills/people/TestAssets/GetProfileTests/UserTokenMock/nomagiccode.mock.dialog rename to tests/unit/skills/people/TestAssets/GetProfileTests/UserTokenMock/nomagiccode.mock.dialog diff --git a/tests/skills/people/TestAssets/GetProfileTests/cachedResponses/ListDialog_en_us_lu/3914016862.json b/tests/unit/skills/people/TestAssets/GetProfileTests/cachedResponses/ListDialog_en_us_lu/3914016862.json similarity index 100% rename from tests/skills/people/TestAssets/GetProfileTests/cachedResponses/ListDialog_en_us_lu/3914016862.json rename to tests/unit/skills/people/TestAssets/GetProfileTests/cachedResponses/ListDialog_en_us_lu/3914016862.json diff --git a/tests/skills/people/TestAssets/GetProfileTests/cachedResponses/people_en_us_lu/1667363801.json b/tests/unit/skills/people/TestAssets/GetProfileTests/cachedResponses/people_en_us_lu/1667363801.json similarity index 100% rename from tests/skills/people/TestAssets/GetProfileTests/cachedResponses/people_en_us_lu/1667363801.json rename to tests/unit/skills/people/TestAssets/GetProfileTests/cachedResponses/people_en_us_lu/1667363801.json diff --git a/tests/skills/people/TestAssets/GetProfileTests/cachedResponses/people_en_us_lu/2122046538.json b/tests/unit/skills/people/TestAssets/GetProfileTests/cachedResponses/people_en_us_lu/2122046538.json similarity index 100% rename from tests/skills/people/TestAssets/GetProfileTests/cachedResponses/people_en_us_lu/2122046538.json rename to tests/unit/skills/people/TestAssets/GetProfileTests/cachedResponses/people_en_us_lu/2122046538.json diff --git a/tests/skills/people/TestAssets/GetProfileTests/cachedResponses/people_en_us_lu/2126032743.json b/tests/unit/skills/people/TestAssets/GetProfileTests/cachedResponses/people_en_us_lu/2126032743.json similarity index 100% rename from tests/skills/people/TestAssets/GetProfileTests/cachedResponses/people_en_us_lu/2126032743.json rename to tests/unit/skills/people/TestAssets/GetProfileTests/cachedResponses/people_en_us_lu/2126032743.json diff --git a/tests/skills/people/WhoSkillTestBase.cs b/tests/unit/skills/people/WhoSkillTestBase.cs similarity index 100% rename from tests/skills/people/WhoSkillTestBase.cs rename to tests/unit/skills/people/WhoSkillTestBase.cs diff --git a/tests/skills/people/app.schema b/tests/unit/skills/people/app.schema similarity index 100% rename from tests/skills/people/app.schema rename to tests/unit/skills/people/app.schema diff --git a/tests/skills/people/testsettings.json b/tests/unit/skills/people/testsettings.json similarity index 100% rename from tests/skills/people/testsettings.json rename to tests/unit/skills/people/testsettings.json