Repository: aspnet/HttpRepl Branch: main Commit: 079da59615c5 Files: 408 Total size: 1.8 MB Directory structure: gitextract_13swohqi/ ├── .config/ │ └── tsaoptions.json ├── .editorconfig ├── .gitattributes ├── .gitignore ├── CODE-OF-CONDUCT.md ├── CONTRIBUTING.md ├── Directory.Build.props ├── Directory.Build.targets ├── Directory.Packages.props ├── HttpRepl.sln ├── LICENSE.txt ├── NOTICE.txt ├── NuGet.config ├── README.md ├── SECURITY.md ├── azure-pipelines-codeql.yml ├── azure-pipelines-pr.yml ├── azure-pipelines.yml ├── build/ │ └── analyzers/ │ └── rulesets/ │ ├── Default.ruleset │ └── Sdl7.0.ruleset ├── build.cmd ├── build.sh ├── eng/ │ ├── AspNetCore.snk │ ├── Build.props │ ├── Signing.props │ ├── Version.Details.xml │ ├── Versions.props │ └── common/ │ ├── BuildConfiguration/ │ │ └── build-configuration.json │ ├── CIBuild.cmd │ ├── PSScriptAnalyzerSettings.psd1 │ ├── README.md │ ├── SetupNugetSources.ps1 │ ├── SetupNugetSources.sh │ ├── build.cmd │ ├── build.ps1 │ ├── build.sh │ ├── cibuild.sh │ ├── core-templates/ │ │ ├── job/ │ │ │ ├── job.yml │ │ │ ├── onelocbuild.yml │ │ │ ├── publish-build-assets.yml │ │ │ ├── source-build.yml │ │ │ └── source-index-stage1.yml │ │ ├── jobs/ │ │ │ ├── codeql-build.yml │ │ │ ├── jobs.yml │ │ │ └── source-build.yml │ │ ├── post-build/ │ │ │ ├── common-variables.yml │ │ │ ├── post-build.yml │ │ │ └── setup-maestro-vars.yml │ │ ├── steps/ │ │ │ ├── cleanup-microbuild.yml │ │ │ ├── component-governance.yml │ │ │ ├── enable-internal-runtimes.yml │ │ │ ├── enable-internal-sources.yml │ │ │ ├── generate-sbom.yml │ │ │ ├── get-delegation-sas.yml │ │ │ ├── get-federated-access-token.yml │ │ │ ├── install-microbuild.yml │ │ │ ├── publish-build-artifacts.yml │ │ │ ├── publish-logs.yml │ │ │ ├── publish-pipeline-artifacts.yml │ │ │ ├── retain-build.yml │ │ │ ├── send-to-helix.yml │ │ │ ├── source-build.yml │ │ │ └── source-index-stage1-publish.yml │ │ └── variables/ │ │ └── pool-providers.yml │ ├── cross/ │ │ ├── arm/ │ │ │ └── tizen/ │ │ │ └── tizen.patch │ │ ├── arm64/ │ │ │ └── tizen/ │ │ │ └── tizen.patch │ │ ├── armel/ │ │ │ ├── armel.jessie.patch │ │ │ └── tizen/ │ │ │ └── tizen.patch │ │ ├── build-android-rootfs.sh │ │ ├── build-rootfs.sh │ │ ├── install-debs.py │ │ ├── riscv64/ │ │ │ └── tizen/ │ │ │ └── tizen.patch │ │ ├── tizen-build-rootfs.sh │ │ ├── tizen-fetch.sh │ │ ├── toolchain.cmake │ │ └── x86/ │ │ └── tizen/ │ │ └── tizen.patch │ ├── darc-init.ps1 │ ├── darc-init.sh │ ├── dotnet-install.cmd │ ├── dotnet-install.ps1 │ ├── dotnet-install.sh │ ├── enable-cross-org-publishing.ps1 │ ├── generate-locproject.ps1 │ ├── generate-sbom-prep.ps1 │ ├── generate-sbom-prep.sh │ ├── helixpublish.proj │ ├── init-tools-native.cmd │ ├── init-tools-native.ps1 │ ├── init-tools-native.sh │ ├── internal/ │ │ ├── Directory.Build.props │ │ ├── NuGet.config │ │ └── Tools.csproj │ ├── internal-feed-operations.ps1 │ ├── internal-feed-operations.sh │ ├── loc/ │ │ └── P22DotNetHtmlLocalization.lss │ ├── msbuild.ps1 │ ├── msbuild.sh │ ├── native/ │ │ ├── CommonLibrary.psm1 │ │ ├── common-library.sh │ │ ├── init-compiler.sh │ │ ├── init-distro-rid.sh │ │ ├── init-os-and-arch.sh │ │ ├── install-cmake-test.sh │ │ ├── install-cmake.sh │ │ ├── install-dependencies.sh │ │ └── install-tool.ps1 │ ├── pipeline-logging-functions.ps1 │ ├── pipeline-logging-functions.sh │ ├── post-build/ │ │ ├── check-channel-consistency.ps1 │ │ ├── nuget-validation.ps1 │ │ ├── nuget-verification.ps1 │ │ ├── publish-using-darc.ps1 │ │ ├── redact-logs.ps1 │ │ ├── sourcelink-validation.ps1 │ │ └── symbols-validation.ps1 │ ├── retain-build.ps1 │ ├── sdk-task.ps1 │ ├── sdl/ │ │ ├── NuGet.config │ │ ├── configure-sdl-tool.ps1 │ │ ├── execute-all-sdl-tools.ps1 │ │ ├── extract-artifact-archives.ps1 │ │ ├── extract-artifact-packages.ps1 │ │ ├── init-sdl.ps1 │ │ ├── packages.config │ │ ├── run-sdl.ps1 │ │ ├── sdl.ps1 │ │ └── trim-assets-version.ps1 │ ├── template-guidance.md │ ├── templates/ │ │ ├── job/ │ │ │ ├── job.yml │ │ │ ├── onelocbuild.yml │ │ │ ├── publish-build-assets.yml │ │ │ ├── source-build.yml │ │ │ └── source-index-stage1.yml │ │ ├── jobs/ │ │ │ ├── codeql-build.yml │ │ │ ├── jobs.yml │ │ │ └── source-build.yml │ │ ├── post-build/ │ │ │ ├── common-variables.yml │ │ │ ├── post-build.yml │ │ │ └── setup-maestro-vars.yml │ │ ├── steps/ │ │ │ ├── component-governance.yml │ │ │ ├── enable-internal-runtimes.yml │ │ │ ├── enable-internal-sources.yml │ │ │ ├── generate-sbom.yml │ │ │ ├── get-delegation-sas.yml │ │ │ ├── get-federated-access-token.yml │ │ │ ├── publish-build-artifacts.yml │ │ │ ├── publish-logs.yml │ │ │ ├── publish-pipeline-artifacts.yml │ │ │ ├── retain-build.yml │ │ │ ├── send-to-helix.yml │ │ │ ├── source-build.yml │ │ │ └── source-index-stage1-publish.yml │ │ └── variables/ │ │ └── pool-providers.yml │ ├── templates-official/ │ │ ├── job/ │ │ │ ├── job.yml │ │ │ ├── onelocbuild.yml │ │ │ ├── publish-build-assets.yml │ │ │ ├── source-build.yml │ │ │ └── source-index-stage1.yml │ │ ├── jobs/ │ │ │ ├── codeql-build.yml │ │ │ ├── jobs.yml │ │ │ └── source-build.yml │ │ ├── post-build/ │ │ │ ├── common-variables.yml │ │ │ ├── post-build.yml │ │ │ └── setup-maestro-vars.yml │ │ ├── steps/ │ │ │ ├── component-governance.yml │ │ │ ├── enable-internal-runtimes.yml │ │ │ ├── enable-internal-sources.yml │ │ │ ├── generate-sbom.yml │ │ │ ├── get-delegation-sas.yml │ │ │ ├── get-federated-access-token.yml │ │ │ ├── publish-build-artifacts.yml │ │ │ ├── publish-logs.yml │ │ │ ├── publish-pipeline-artifacts.yml │ │ │ ├── retain-build.yml │ │ │ ├── send-to-helix.yml │ │ │ ├── source-build.yml │ │ │ └── source-index-stage1-publish.yml │ │ └── variables/ │ │ ├── pool-providers.yml │ │ └── sdl-variables.yml │ ├── tools.ps1 │ └── tools.sh ├── global.json ├── src/ │ ├── Directory.Build.props │ ├── Microsoft.HttpRepl/ │ │ ├── ApiConnection.cs │ │ ├── Commands/ │ │ │ ├── AddQueryParamCommand.cs │ │ │ ├── BaseHttpCommand.cs │ │ │ ├── ChangeDirectoryCommand.cs │ │ │ ├── ClearCommand.cs │ │ │ ├── ClearQueryParamCommand.cs │ │ │ ├── ConnectCommand.cs │ │ │ ├── DeleteCommand.cs │ │ │ ├── EchoCommand.cs │ │ │ ├── ExitCommand.cs │ │ │ ├── Formatter.cs │ │ │ ├── GetCommand.cs │ │ │ ├── HeadCommand.cs │ │ │ ├── HelpCommand.cs │ │ │ ├── ListCommand.cs │ │ │ ├── OptionsCommand.cs │ │ │ ├── PatchCommand.cs │ │ │ ├── PostCommand.cs │ │ │ ├── PrefCommand.cs │ │ │ ├── PutCommand.cs │ │ │ ├── RunCommand.cs │ │ │ ├── SetHeaderCommand.cs │ │ │ ├── TreeNode.cs │ │ │ └── UICommand.cs │ │ ├── DirectoryStructure.cs │ │ ├── DirectoryStructureExtensions.cs │ │ ├── Extensions/ │ │ │ ├── RequestInfoExtensions.cs │ │ │ └── UrlStringExtensions.cs │ │ ├── FileSystem/ │ │ │ ├── IFileSystem.cs │ │ │ └── RealFileSystem.cs │ │ ├── Formatting/ │ │ │ └── JsonVisitor.cs │ │ ├── HttpState.cs │ │ ├── IDirectoryStructure.cs │ │ ├── IRequestInfo.cs │ │ ├── IUriLauncher.cs │ │ ├── Microsoft.HttpRepl.csproj │ │ ├── OpenApi/ │ │ │ ├── ApiDefinition.cs │ │ │ ├── ApiDefinitionParseResult.cs │ │ │ ├── ApiDefinitionReader.cs │ │ │ ├── EndpointMetadata.cs │ │ │ ├── IApiDefinitionReader.cs │ │ │ ├── IOpenApiSearchPathsProvider.cs │ │ │ ├── OpenApiDotNetApiDefinitionReader.cs │ │ │ └── SchemaDataGenerator.cs │ │ ├── Preferences/ │ │ │ ├── IJsonConfig.cs │ │ │ ├── IPreferences.cs │ │ │ ├── JsonConfig.cs │ │ │ ├── OpenApiSearchPathsProvider.cs │ │ │ ├── RequestConfig.cs │ │ │ ├── RequestOrResponseConfig.cs │ │ │ ├── ResponseConfig.cs │ │ │ ├── UserFolderPreferences.cs │ │ │ └── WellKnownPreference.cs │ │ ├── Program.cs │ │ ├── Properties/ │ │ │ └── AssemblyInfo.cs │ │ ├── Resources/ │ │ │ ├── Strings.Designer.cs │ │ │ └── Strings.resx │ │ ├── Suggestions/ │ │ │ ├── HeaderCompletion.cs │ │ │ └── ServerPathCompletion.cs │ │ ├── Telemetry/ │ │ │ ├── DefaultCommandDispatcherExtensions.cs │ │ │ ├── Events/ │ │ │ │ ├── AddQueryParamEvent.cs │ │ │ │ ├── ClearQueryParamEvent.cs │ │ │ │ ├── CommandExecutedEvent.cs │ │ │ │ ├── ConnectEvent.cs │ │ │ │ ├── HttpCommandEvent.cs │ │ │ │ ├── PreferenceEvent.cs │ │ │ │ ├── SetHeaderEvent.cs │ │ │ │ ├── StartedEvent.cs │ │ │ │ ├── TelemetryEventBase.cs │ │ │ │ └── WebApiF5FixEvent.cs │ │ │ ├── TelemetryCommandWrapper.cs │ │ │ ├── TelemetryConstants.cs │ │ │ └── TelemetryExtensions.cs │ │ ├── UriLauncher.cs │ │ ├── UserProfile/ │ │ │ ├── IUserProfileDirectoryProvider.cs │ │ │ └── UserProfileDirectoryProvider.cs │ │ ├── VersionSensor.cs │ │ └── WellKnownHeaders.cs │ ├── Microsoft.HttpRepl.Telemetry/ │ │ ├── DockerContainerDetectorForTelemetry.cs │ │ ├── EnvironmentHelper.cs │ │ ├── FirstTimeUseNoticeSentinel.cs │ │ ├── IDockerContainerDetector.cs │ │ ├── IFirstTimeUseNoticeSentinel.cs │ │ ├── ITelemetry.cs │ │ ├── IUserLevelCacheWriter.cs │ │ ├── MacAddressGetter.cs │ │ ├── Microsoft.HttpRepl.Telemetry.csproj │ │ ├── Paths.cs │ │ ├── ProcessStartInfoExtensions.cs │ │ ├── Sha256Hasher.cs │ │ ├── StreamForwarder.cs │ │ ├── Telemetry.cs │ │ ├── TelemetryCommonProperties.cs │ │ └── UserLevelCacheWriter.cs │ └── Microsoft.Repl/ │ ├── Commanding/ │ │ ├── CommandHistory.cs │ │ ├── CommandInputLocation.cs │ │ ├── CommandInputProcessingIssue.cs │ │ ├── CommandInputProcessingIssueKind.cs │ │ ├── CommandInputSpecification.cs │ │ ├── CommandInputSpecificationBuilder.cs │ │ ├── CommandOptionSpecification.cs │ │ ├── CommandWithStructuredInputBase.cs │ │ ├── DefaultCommandDispatcher.cs │ │ ├── DefaultCommandInput.cs │ │ ├── ICommand.cs │ │ ├── ICommandDispatcher.cs │ │ ├── ICommandHistory.cs │ │ └── InputElement.cs │ ├── ConsoleHandling/ │ │ ├── AllowedColors.cs │ │ ├── AnsiColorExtensions.cs │ │ ├── AnsiConsole.cs │ │ ├── ConsoleManager.cs │ │ ├── IConsoleManager.cs │ │ ├── IWritable.cs │ │ ├── Point.cs │ │ ├── Reporter.cs │ │ └── Writable.cs │ ├── Disposable.cs │ ├── IShellState.cs │ ├── Input/ │ │ ├── AsyncKeyPressHandler.cs │ │ ├── IInputManager.cs │ │ ├── InputManager.cs │ │ └── KeyHandlers.cs │ ├── Microsoft.Repl.csproj │ ├── Parsing/ │ │ ├── CoreParseResult.cs │ │ ├── CoreParseResultExtensions.cs │ │ ├── CoreParser.cs │ │ ├── ICoreParseResult.cs │ │ └── IParser.cs │ ├── Properties/ │ │ └── AssemblyInfo.cs │ ├── Resources/ │ │ ├── Strings.Designer.cs │ │ └── Strings.resx │ ├── Scripting/ │ │ ├── IScriptExecutor.cs │ │ └── ScriptExecutor.cs │ ├── Shell.cs │ ├── ShellState.cs │ ├── Suggestions/ │ │ ├── FileSystemCompletion.cs │ │ ├── ISuggestionManager.cs │ │ └── SuggestionManager.cs │ └── Utils.cs └── test/ ├── Directory.Build.props ├── Microsoft.HttpRepl.Fakes/ │ ├── ApiDefinitionReaderStub.cs │ ├── FakePreferences.cs │ ├── FileSystemStub.cs │ ├── LoggingConsoleManagerDecorator.cs │ ├── Microsoft.HttpRepl.Fakes.csproj │ ├── MockCommand.cs │ ├── MockConsoleManager.cs │ ├── MockHttpContent.cs │ ├── MockHttpMessageHandler.cs │ ├── MockInputManager.cs │ ├── MockWritable.cs │ ├── MockedFileSystem.cs │ ├── MockedShellState.cs │ ├── NullConsoleManager.cs │ ├── NullPreferences.cs │ ├── NullTelemetry.cs │ └── TelemetryCollector.cs ├── Microsoft.HttpRepl.IntegrationTests/ │ ├── BaseIntegrationTest.cs │ ├── Commands/ │ │ ├── ChangeDirectoryCommandTests.cs │ │ ├── EchoCommandTests.cs │ │ ├── GetCommandTests.cs │ │ ├── HttpCommandsFixture.cs │ │ ├── ListCommandTests.cs │ │ └── SetHeaderCommandTests.cs │ ├── Microsoft.HttpRepl.IntegrationTests.csproj │ ├── SampleApi/ │ │ ├── Controllers/ │ │ │ └── ValuesController.cs │ │ ├── SampleApiServer.cs │ │ └── SampleApiServerConfig.cs │ └── Utilities/ │ └── TestScript.cs ├── Microsoft.HttpRepl.Tests/ │ ├── Commands/ │ │ ├── AddQueryParamCommandTests.cs │ │ ├── ChangeDirectoryCommandTests.cs │ │ ├── ClearCommandTests.cs │ │ ├── ClearQueryParamCommandTests.cs │ │ ├── CommandTestsBase.cs │ │ ├── ConnectCommandTests.cs │ │ ├── CoreParseResultHelper.cs │ │ ├── DeleteCommandsTests.cs │ │ ├── EchoCommandTests.cs │ │ ├── ExitCommandTests.cs │ │ ├── GetCommandTests.cs │ │ ├── HeadCommandTests.cs │ │ ├── HelpCommandTests.cs │ │ ├── ListCommandTests.cs │ │ ├── OptionsCommandTests.cs │ │ ├── PatchCommandTests.cs │ │ ├── PostCommandTests.cs │ │ ├── PrefCommandTests.cs │ │ ├── PutCommandTests.cs │ │ ├── RunCommandTests.cs │ │ ├── SetHeaderCommandTests.cs │ │ ├── TreeNodeTests.cs │ │ └── UICommandTests.cs │ ├── FileSystem/ │ │ └── RealFileSystemTests.cs │ ├── HttpStateTests.cs │ ├── JsonVisitorTests.cs │ ├── Microsoft.HttpRepl.Tests.csproj │ ├── OpenApi/ │ │ ├── ApiDefinitionBuilder.cs │ │ ├── ApiDefinitionReaderTests.cs │ │ ├── OpenApiDotNetApiDefinitionReaderTests.cs │ │ ├── OpenApiDotNetApiDefinitionReaderV2Tests.cs │ │ ├── OpenApiDotNetApiDefinitionReaderV3Tests.cs │ │ └── SchemaDataGeneratorTests.cs │ ├── Preferences/ │ │ ├── OpenApiSearchPathsProviderTests.cs │ │ ├── TestDefaultPreferences.cs │ │ └── UserFolderPreferencesTests.cs │ ├── Resources/ │ │ └── OpenApiDescriptions/ │ │ ├── MicrosoftGraph.PowershellSdk.Analytics.json │ │ ├── MicrosoftGraph.PowershellSdk.Analytics.yml │ │ ├── MicrosoftGraph.PowershellSdk.CloudCommunications.json │ │ ├── MicrosoftGraph.PowershellSdk.CloudCommunications.yml │ │ ├── MicrosoftGraph.PowershellSdk.Subscriptions.json │ │ ├── MicrosoftGraph.PowershellSdk.Subscriptions.yml │ │ ├── xkcd.json │ │ └── xkcd.yml │ ├── Suggestions/ │ │ ├── HeaderCompletionTests.cs │ │ └── ServerPathCompletionTests.cs │ └── Telemetry/ │ └── Events/ │ ├── PreferenceEventTests.cs │ └── SetHeaderEventTests.cs └── Microsoft.Repl.Tests/ ├── ConsoleHandling/ │ └── AnsiColorExtensionsTests.cs ├── DisposableTests.cs ├── InputManagerTests.cs ├── Microsoft.Repl.Tests.csproj ├── ParserTests.cs ├── Parsing/ │ └── CoreParseResultTests.cs └── ShellTests.cs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .config/tsaoptions.json ================================================ { "instanceUrl": "https://dev.azure.com/devdiv/", "template": "TFSDEVDIV", "projectName": "DEVDIV", "areaPath": "DevDiv\\Web Tools\\API\\Http Repl", "iterationPath": "DevDiv", "notificationAliases": [ "webproject@microsoft.com" ], "repositoryName":"HttpRepl", "codebaseName": "HttpRepl", "serviceTreeId": "225159cd-5cda-4eaf-9b48-9bc4ff385b23" } ================================================ FILE: .editorconfig ================================================ # EditorConfig is awesome:http://EditorConfig.org # top-most EditorConfig file root = true [*] # Don't use tabs for indentation. # (Please don't specify an indent_size here; that has too many unintended consequences.) indent_style = space charset = utf-8 # Where supported, trim trailing whitespace on all lines. trim_trailing_whitespace = true # Where supported (e.g. in VS Code but not VS), add a final newline to files. insert_final_newline = true # Code files [*.{cs,csx,vb,vbx}] indent_size = 4 dotnet_sort_system_directives_first = true:warning file_header_template = Licensed to the .NET Foundation under one or more agreements.\nThe .NET Foundation licenses this file to you under the MIT license.\nSee the License.txt file in the project root for more information. # Xml project files [*.{*proj,vcxproj.filters,projitems}] indent_size = 2 # Xml config files [*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct,tasks,xml,yml}] indent_size = 2 # JSON files [*.json] indent_size = 2 # PowerShell [*.{ps1,psm1}] indent_size = 4 # Shell [*.sh] indent_size = 4 end_of_line = lf # Dotnet code style settings: [*.cs] # Sort using and Import directives with System.* appearing first dotnet_sort_system_directives_first = true # Don't use this. qualifier dotnet_style_qualification_for_field = false:suggestion dotnet_style_qualification_for_property = false:suggestion # use int x = .. over Int32 dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion # use int.MaxValue over Int32.MaxValue dotnet_style_predefined_type_for_member_access = true:suggestion # Require var all the time. csharp_style_var_for_built_in_types = false:suggestion csharp_style_var_when_type_is_apparent = false:suggestion csharp_style_var_elsewhere = false:suggestion # Disallow throw expressions. csharp_style_throw_expression = false:suggestion # Newline settings 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 ================================================ FILE: .gitattributes ================================================ * text=auto *.cs diff=csharp *.sh eol=lf *.sln eol=crlf ================================================ FILE: .gitignore ================================================ syntax: glob ### VisualStudio ### # Tools directory /[Tt]ools/ .dotnet/ .packages/ # User-specific files *.suo *.user *.userosscache *.sln.docstates launchSettings.json # Build results artifacts/ [Dd]ebug/ [Rr]elease/ x64/ x86/ !eng/common/cross/x86/ [Bb]in/ [Oo]bj/ msbuild.log msbuild.err msbuild.wrn msbuild.binlog # Visual Studio 2015 .vs/ # Visual Studio 2015 Pre-CTP6 *.sln.ide *.ide/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* #NUNIT *.VisualState.xml TestResult.xml # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # DotCover is a Code Coverage Tool *.dotCover # NuGet Packages *.nuget.props *.nuget.targets *.nupkg **/packages/* ### Windows ### # Windows image file caches Thumbs.db ehthumbs.db # Folder config file Desktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ # Windows Installer files *.cab *.msi *.msm *.msp # Windows shortcuts *.lnk ### Linux ### *~ # KDE directory preferences .directory ### OSX ### .DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear on external disk .Spotlight-V100 .Trashes # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk # vim temporary files [._]*.s[a-w][a-z] [._]s[a-w][a-z] *.un~ Session.vim .netrwhist *~ # Visual Studio Code .vscode/ # Private test configuration and binaries. config.ps1 **/IISApplications # Node.js modules node_modules/ # Python Compile Outputs *.pyc ================================================ FILE: CODE-OF-CONDUCT.md ================================================ # Code of Conduct This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community. For more information, see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct). ================================================ FILE: CONTRIBUTING.md ================================================ # How to contribute One of the easiest ways to contribute is to participate in discussions on GitHub issues. You can also contribute by submitting pull requests with code changes. ## General feedback, bugs, feature requests and discussions? Start a discussion on the [repository issue tracker](https://github.com/aspnet/HttpRepl/issues). ## Reporting security issues and bugs Security issues and bugs should be reported privately, via email, to the Microsoft Security Response Center (MSRC) secure@microsoft.com. 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. Further information, including the MSRC PGP key, can be found in the [Security TechCenter](https://technet.microsoft.com/en-us/security/ff852094.aspx). ## Contributing code and content We accept fixes and features! * Look at the [README](/README.md) to get started on building the source code on your own. * Submit a PR when you're ready and we'll take a look! ### Submitting a pull request You will need to sign a [Contributor License Agreement](https://cla.dotnetfoundation.org/) when submitting your pull request. To complete the Contributor License Agreement (CLA), you will need to follow the instructions provided by the CLA bot when you send the pull request. This needs to only be done once for any .NET Foundation OSS project. If you don't know what a pull request is read this article: https://help.github.com/articles/using-pull-requests. Make sure the repository can build and all tests pass. Familiarize yourself with the project workflow and our coding conventions. The coding, style, and general engineering guidelines are published on the [Engineering guidelines](https://github.com/aspnet/AspNetCore/wiki/Engineering-guidelines) page. ### Tests - Tests need to be provided for every bug/feature that is completed. - Tests only need to be present for issues that need to be verified by QA (for example, not tasks) - If there is a scenario that is far too hard to test there does not need to be a test for it. - "Too hard" is determined by the team as a whole. ### Feedback Your pull request will now go through extensive checks by the subject matter experts on our team. Please be patient; we have hundreds of pull requests across all of our repositories. Update your pull request according to feedback until it is approved by one of the team members. After that, one of our team members may adjust the branch you merge into based on the expected release schedule. ================================================ FILE: Directory.Build.props ================================================ preview true $(MSBuildThisFileDirectory) $(RepoRoot)src $(RepoRoot)test $(CopyrightMicrosoft) MIT ================================================ FILE: Directory.Build.targets ================================================ ================================================ FILE: Directory.Packages.props ================================================ true true $(NoWarn);NU1507 ================================================ FILE: HttpRepl.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.29006.145 MinimumVisualStudioVersion = 15.0.26124.0 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.HttpRepl.Tests", "test\Microsoft.HttpRepl.Tests\Microsoft.HttpRepl.Tests.csproj", "{84E7BEDA-9064-4B11-A84B-3011C6A97A8C}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.HttpRepl", "src\Microsoft.HttpRepl\Microsoft.HttpRepl.csproj", "{798BD900-DFF5-46CD-A422-5B0B2495A943}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Repl.Tests", "test\Microsoft.Repl.Tests\Microsoft.Repl.Tests.csproj", "{218B878C-5856-4B1F-99B7-8D98AB4C1B03}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Repl", "src\Microsoft.Repl\Microsoft.Repl.csproj", "{AC0BF0EE-FF72-4BD8-BF18-3322BE5FA695}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.HttpRepl.IntegrationTests", "test\Microsoft.HttpRepl.IntegrationTests\Microsoft.HttpRepl.IntegrationTests.csproj", "{A1B13133-8F4D-42D5-B470-D6349C71AFD6}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.HttpRepl.Fakes", "test\Microsoft.HttpRepl.Fakes\Microsoft.HttpRepl.Fakes.csproj", "{32DEC29F-4FFB-4AFB-8E9E-A88B44739CC4}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.HttpRepl.Telemetry", "src\Microsoft.HttpRepl.Telemetry\Microsoft.HttpRepl.Telemetry.csproj", "{EE2A1FCB-F93B-4FF1-BD37-95A1E4991AD1}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {84E7BEDA-9064-4B11-A84B-3011C6A97A8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {84E7BEDA-9064-4B11-A84B-3011C6A97A8C}.Debug|Any CPU.Build.0 = Debug|Any CPU {84E7BEDA-9064-4B11-A84B-3011C6A97A8C}.Debug|x64.ActiveCfg = Debug|Any CPU {84E7BEDA-9064-4B11-A84B-3011C6A97A8C}.Debug|x64.Build.0 = Debug|Any CPU {84E7BEDA-9064-4B11-A84B-3011C6A97A8C}.Debug|x86.ActiveCfg = Debug|Any CPU {84E7BEDA-9064-4B11-A84B-3011C6A97A8C}.Debug|x86.Build.0 = Debug|Any CPU {84E7BEDA-9064-4B11-A84B-3011C6A97A8C}.Release|Any CPU.ActiveCfg = Release|Any CPU {84E7BEDA-9064-4B11-A84B-3011C6A97A8C}.Release|Any CPU.Build.0 = Release|Any CPU {84E7BEDA-9064-4B11-A84B-3011C6A97A8C}.Release|x64.ActiveCfg = Release|Any CPU {84E7BEDA-9064-4B11-A84B-3011C6A97A8C}.Release|x64.Build.0 = Release|Any CPU {84E7BEDA-9064-4B11-A84B-3011C6A97A8C}.Release|x86.ActiveCfg = Release|Any CPU {84E7BEDA-9064-4B11-A84B-3011C6A97A8C}.Release|x86.Build.0 = Release|Any CPU {798BD900-DFF5-46CD-A422-5B0B2495A943}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {798BD900-DFF5-46CD-A422-5B0B2495A943}.Debug|Any CPU.Build.0 = Debug|Any CPU {798BD900-DFF5-46CD-A422-5B0B2495A943}.Debug|x64.ActiveCfg = Debug|Any CPU {798BD900-DFF5-46CD-A422-5B0B2495A943}.Debug|x64.Build.0 = Debug|Any CPU {798BD900-DFF5-46CD-A422-5B0B2495A943}.Debug|x86.ActiveCfg = Debug|Any CPU {798BD900-DFF5-46CD-A422-5B0B2495A943}.Debug|x86.Build.0 = Debug|Any CPU {798BD900-DFF5-46CD-A422-5B0B2495A943}.Release|Any CPU.ActiveCfg = Release|Any CPU {798BD900-DFF5-46CD-A422-5B0B2495A943}.Release|Any CPU.Build.0 = Release|Any CPU {798BD900-DFF5-46CD-A422-5B0B2495A943}.Release|x64.ActiveCfg = Release|Any CPU {798BD900-DFF5-46CD-A422-5B0B2495A943}.Release|x64.Build.0 = Release|Any CPU {798BD900-DFF5-46CD-A422-5B0B2495A943}.Release|x86.ActiveCfg = Release|Any CPU {798BD900-DFF5-46CD-A422-5B0B2495A943}.Release|x86.Build.0 = Release|Any CPU {218B878C-5856-4B1F-99B7-8D98AB4C1B03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {218B878C-5856-4B1F-99B7-8D98AB4C1B03}.Debug|Any CPU.Build.0 = Debug|Any CPU {218B878C-5856-4B1F-99B7-8D98AB4C1B03}.Debug|x64.ActiveCfg = Debug|Any CPU {218B878C-5856-4B1F-99B7-8D98AB4C1B03}.Debug|x64.Build.0 = Debug|Any CPU {218B878C-5856-4B1F-99B7-8D98AB4C1B03}.Debug|x86.ActiveCfg = Debug|Any CPU {218B878C-5856-4B1F-99B7-8D98AB4C1B03}.Debug|x86.Build.0 = Debug|Any CPU {218B878C-5856-4B1F-99B7-8D98AB4C1B03}.Release|Any CPU.ActiveCfg = Release|Any CPU {218B878C-5856-4B1F-99B7-8D98AB4C1B03}.Release|Any CPU.Build.0 = Release|Any CPU {218B878C-5856-4B1F-99B7-8D98AB4C1B03}.Release|x64.ActiveCfg = Release|Any CPU {218B878C-5856-4B1F-99B7-8D98AB4C1B03}.Release|x64.Build.0 = Release|Any CPU {218B878C-5856-4B1F-99B7-8D98AB4C1B03}.Release|x86.ActiveCfg = Release|Any CPU {218B878C-5856-4B1F-99B7-8D98AB4C1B03}.Release|x86.Build.0 = Release|Any CPU {AC0BF0EE-FF72-4BD8-BF18-3322BE5FA695}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AC0BF0EE-FF72-4BD8-BF18-3322BE5FA695}.Debug|Any CPU.Build.0 = Debug|Any CPU {AC0BF0EE-FF72-4BD8-BF18-3322BE5FA695}.Debug|x64.ActiveCfg = Debug|Any CPU {AC0BF0EE-FF72-4BD8-BF18-3322BE5FA695}.Debug|x64.Build.0 = Debug|Any CPU {AC0BF0EE-FF72-4BD8-BF18-3322BE5FA695}.Debug|x86.ActiveCfg = Debug|Any CPU {AC0BF0EE-FF72-4BD8-BF18-3322BE5FA695}.Debug|x86.Build.0 = Debug|Any CPU {AC0BF0EE-FF72-4BD8-BF18-3322BE5FA695}.Release|Any CPU.ActiveCfg = Release|Any CPU {AC0BF0EE-FF72-4BD8-BF18-3322BE5FA695}.Release|Any CPU.Build.0 = Release|Any CPU {AC0BF0EE-FF72-4BD8-BF18-3322BE5FA695}.Release|x64.ActiveCfg = Release|Any CPU {AC0BF0EE-FF72-4BD8-BF18-3322BE5FA695}.Release|x64.Build.0 = Release|Any CPU {AC0BF0EE-FF72-4BD8-BF18-3322BE5FA695}.Release|x86.ActiveCfg = Release|Any CPU {AC0BF0EE-FF72-4BD8-BF18-3322BE5FA695}.Release|x86.Build.0 = Release|Any CPU {A1B13133-8F4D-42D5-B470-D6349C71AFD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A1B13133-8F4D-42D5-B470-D6349C71AFD6}.Debug|Any CPU.Build.0 = Debug|Any CPU {A1B13133-8F4D-42D5-B470-D6349C71AFD6}.Debug|x64.ActiveCfg = Debug|Any CPU {A1B13133-8F4D-42D5-B470-D6349C71AFD6}.Debug|x64.Build.0 = Debug|Any CPU {A1B13133-8F4D-42D5-B470-D6349C71AFD6}.Debug|x86.ActiveCfg = Debug|Any CPU {A1B13133-8F4D-42D5-B470-D6349C71AFD6}.Debug|x86.Build.0 = Debug|Any CPU {A1B13133-8F4D-42D5-B470-D6349C71AFD6}.Release|Any CPU.ActiveCfg = Release|Any CPU {A1B13133-8F4D-42D5-B470-D6349C71AFD6}.Release|Any CPU.Build.0 = Release|Any CPU {A1B13133-8F4D-42D5-B470-D6349C71AFD6}.Release|x64.ActiveCfg = Release|Any CPU {A1B13133-8F4D-42D5-B470-D6349C71AFD6}.Release|x64.Build.0 = Release|Any CPU {A1B13133-8F4D-42D5-B470-D6349C71AFD6}.Release|x86.ActiveCfg = Release|Any CPU {A1B13133-8F4D-42D5-B470-D6349C71AFD6}.Release|x86.Build.0 = Release|Any CPU {32DEC29F-4FFB-4AFB-8E9E-A88B44739CC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {32DEC29F-4FFB-4AFB-8E9E-A88B44739CC4}.Debug|Any CPU.Build.0 = Debug|Any CPU {32DEC29F-4FFB-4AFB-8E9E-A88B44739CC4}.Debug|x64.ActiveCfg = Debug|Any CPU {32DEC29F-4FFB-4AFB-8E9E-A88B44739CC4}.Debug|x64.Build.0 = Debug|Any CPU {32DEC29F-4FFB-4AFB-8E9E-A88B44739CC4}.Debug|x86.ActiveCfg = Debug|Any CPU {32DEC29F-4FFB-4AFB-8E9E-A88B44739CC4}.Debug|x86.Build.0 = Debug|Any CPU {32DEC29F-4FFB-4AFB-8E9E-A88B44739CC4}.Release|Any CPU.ActiveCfg = Release|Any CPU {32DEC29F-4FFB-4AFB-8E9E-A88B44739CC4}.Release|Any CPU.Build.0 = Release|Any CPU {32DEC29F-4FFB-4AFB-8E9E-A88B44739CC4}.Release|x64.ActiveCfg = Release|Any CPU {32DEC29F-4FFB-4AFB-8E9E-A88B44739CC4}.Release|x64.Build.0 = Release|Any CPU {32DEC29F-4FFB-4AFB-8E9E-A88B44739CC4}.Release|x86.ActiveCfg = Release|Any CPU {32DEC29F-4FFB-4AFB-8E9E-A88B44739CC4}.Release|x86.Build.0 = Release|Any CPU {EE2A1FCB-F93B-4FF1-BD37-95A1E4991AD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EE2A1FCB-F93B-4FF1-BD37-95A1E4991AD1}.Debug|Any CPU.Build.0 = Debug|Any CPU {EE2A1FCB-F93B-4FF1-BD37-95A1E4991AD1}.Debug|x64.ActiveCfg = Debug|Any CPU {EE2A1FCB-F93B-4FF1-BD37-95A1E4991AD1}.Debug|x64.Build.0 = Debug|Any CPU {EE2A1FCB-F93B-4FF1-BD37-95A1E4991AD1}.Debug|x86.ActiveCfg = Debug|Any CPU {EE2A1FCB-F93B-4FF1-BD37-95A1E4991AD1}.Debug|x86.Build.0 = Debug|Any CPU {EE2A1FCB-F93B-4FF1-BD37-95A1E4991AD1}.Release|Any CPU.ActiveCfg = Release|Any CPU {EE2A1FCB-F93B-4FF1-BD37-95A1E4991AD1}.Release|Any CPU.Build.0 = Release|Any CPU {EE2A1FCB-F93B-4FF1-BD37-95A1E4991AD1}.Release|x64.ActiveCfg = Release|Any CPU {EE2A1FCB-F93B-4FF1-BD37-95A1E4991AD1}.Release|x64.Build.0 = Release|Any CPU {EE2A1FCB-F93B-4FF1-BD37-95A1E4991AD1}.Release|x86.ActiveCfg = Release|Any CPU {EE2A1FCB-F93B-4FF1-BD37-95A1E4991AD1}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {7D2700BF-2B16-46BC-9761-E941C506AC69} EndGlobalSection EndGlobal ================================================ FILE: LICENSE.txt ================================================ The MIT License (MIT) Copyright (c) .NET Foundation and Contributors All rights reserved. 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. ================================================ FILE: NOTICE.txt ================================================ NOTICES AND INFORMATION Do Not Translate or Localize This software incorporates material from third parties. Microsoft makes certain open source code available at https://3rdpartysource.microsoft.com, or you may send a check or money order for US $5.00, including the product name, the open source component name, platform, and version number, to: Source Code Compliance Team Microsoft Corporation One Microsoft Way Redmond, WA 98052 USA Notwithstanding any other terms, you may reverse engineer this software to the extent required to debug changes to any libraries licensed under the GNU Lesser General Public License. --------------------------------------------------------- Microsoft.Extensions.Configuration 2.1.1 - Apache-2.0 (c) Microsoft Corporation. Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --------------------------------------------------------- --------------------------------------------------------- Microsoft.Extensions.Configuration.Abstractions 2.1.1 - Apache-2.0 (c) Microsoft Corporation. Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --------------------------------------------------------- --------------------------------------------------------- Microsoft.Extensions.Configuration.Binder 2.1.1 - Apache-2.0 (c) Microsoft Corporation. Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --------------------------------------------------------- --------------------------------------------------------- Microsoft.Extensions.DependencyInjection 2.1.1 - Apache-2.0 (c) Microsoft Corporation. Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --------------------------------------------------------- --------------------------------------------------------- Microsoft.Extensions.DependencyInjection.Abstractions 2.1.1 - Apache-2.0 (c) Microsoft Corporation. Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --------------------------------------------------------- --------------------------------------------------------- Microsoft.Extensions.Logging 2.1.1 - Apache-2.0 (c) Microsoft Corporation. Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --------------------------------------------------------- --------------------------------------------------------- Microsoft.Extensions.Logging.Abstractions 2.1.1 - Apache-2.0 (c) Microsoft Corporation. Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --------------------------------------------------------- --------------------------------------------------------- Microsoft.Extensions.Options 2.1.1 - Apache-2.0 (c) Microsoft Corporation. Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --------------------------------------------------------- --------------------------------------------------------- Microsoft.Extensions.Primitives 2.1.1 - Apache-2.0 (c) Microsoft Corporation. Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --------------------------------------------------------- --------------------------------------------------------- Microsoft.ApplicationInsights 2.20.0 - MIT (c) Microsoft Corporation MIT License Copyright (c) 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. --------------------------------------------------------- --------------------------------------------------------- Microsoft.ApplicationInsights.DependencyCollector 2.20.0 - MIT MIT License Copyright (c) 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. --------------------------------------------------------- --------------------------------------------------------- Microsoft.ApplicationInsights.EventCounterCollector 2.20.0 - MIT (c) Microsoft Corporation. MIT License Copyright (c) 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. --------------------------------------------------------- --------------------------------------------------------- Microsoft.ApplicationInsights.PerfCounterCollector 2.20.0 - MIT (c) Microsoft Corporation. MIT License Copyright (c) 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. --------------------------------------------------------- --------------------------------------------------------- Microsoft.ApplicationInsights.WindowsServer 2.20.0 - MIT MIT License Copyright (c) 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. --------------------------------------------------------- --------------------------------------------------------- Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel 2.20.0 - MIT (c) Microsoft Corporation. MIT License Copyright (c) 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. --------------------------------------------------------- --------------------------------------------------------- Microsoft.ApplicationInsights.WorkerService 2.20.0 - MIT (c) Microsoft Corporation. MIT License Copyright (c) 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. --------------------------------------------------------- --------------------------------------------------------- Microsoft.DotNet.PlatformAbstractions 3.1.6 - MIT (c) Microsoft Corporation. Copyright (c) Andrew Arnott Copyright (c) 2011, Google Inc. Copyright (c) 1998 Microsoft. To (c) 1997-2005 Sean Eron Anderson. Copyright (c) 2017 Yoshifumi Kawai Copyright (c) Microsoft Corporation Copyright (c) 2012-2014, Yann Collet Copyright (c) 1991-2017 Unicode, Inc. Portions (c) International Organization Copyright (c) 2015 The Chromium Authors. Copyright (c) The Internet Society 1997. Copyright (c) 2004-2006 Intel Corporation Copyright (c) 2013-2017, Milosz Krajewski Copyright (c) .NET Foundation Contributors Copyright (c) The Internet Society (2003). Copyright (c) .NET Foundation and Contributors Copyright (c) 2011 Novell, Inc (http://www.novell.com) Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. Copyright (c) 2003-2005 Hewlett-Packard Development Company, L.P. Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass. Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass. To The MIT License (MIT) Copyright (c) .NET Foundation and Contributors All rights reserved. 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. --------------------------------------------------------- --------------------------------------------------------- Microsoft.Extensions.Logging.ApplicationInsights 2.20.0 - MIT MIT License Copyright (c) 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. --------------------------------------------------------- --------------------------------------------------------- Microsoft.OpenApi 1.2.3 - MIT (c) Microsoft Corporation. Copyright (c) Microsoft Corporation. MIT License Copyright (c) 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. --------------------------------------------------------- --------------------------------------------------------- Microsoft.Win32.SystemEvents 4.7.0 - MIT (c) Microsoft Corporation. Copyright (c) .NET Foundation. Copyright (c) 2011, Google Inc. (c) 1997-2005 Sean Eron Anderson. Copyright (c) 2007 James Newton-King Copyright (c) 1991-2017 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2015-2017, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath Portions (c) International Organization Copyright (c) 2015 The Chromium Authors. Copyright (c) 2004-2006 Intel Corporation Copyright (c) 2016-2017, Matthieu Darbois Copyright (c) .NET Foundation Contributors Copyright (c) .NET Foundation and Contributors Copyright (c) 2011 Novell, Inc (http://www.novell.com) Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS The MIT License (MIT) Copyright (c) .NET Foundation and Contributors All rights reserved. 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. --------------------------------------------------------- --------------------------------------------------------- Newtonsoft.Json 13.0.1 - MIT Copyright James Newton-King 2008 Copyright (c) 2007 James Newton-King Copyright (c) James Newton-King 2008 Copyright James Newton-King 2008 Json.NET The MIT License (MIT) Copyright (c) 2007 James Newton-King 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. --------------------------------------------------------- --------------------------------------------------------- SharpYaml 1.6.5 - MIT MIT License Copyright (c) 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. --------------------------------------------------------- --------------------------------------------------------- System.Configuration.ConfigurationManager 4.7.0 - MIT (c) Microsoft Corporation. Copyright (c) .NET Foundation. Copyright (c) 2011, Google Inc. (c) 1997-2005 Sean Eron Anderson. Copyright (c) 2007 James Newton-King Copyright (c) 1991-2017 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2015-2017, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath Portions (c) International Organization Copyright (c) 2015 The Chromium Authors. Copyright (c) 2004-2006 Intel Corporation Copyright (c) 2016-2017, Matthieu Darbois Copyright (c) .NET Foundation Contributors Copyright (c) .NET Foundation and Contributors Copyright (c) 2011 Novell, Inc (http://www.novell.com) Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS The MIT License (MIT) Copyright (c) .NET Foundation and Contributors All rights reserved. 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. --------------------------------------------------------- --------------------------------------------------------- System.Diagnostics.PerformanceCounter 4.7.0 - MIT (c) Microsoft Corporation. Copyright (c) .NET Foundation. Copyright (c) 2011, Google Inc. (c) 1997-2005 Sean Eron Anderson. Copyright (c) 2007 James Newton-King Copyright (c) 1991-2017 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2015-2017, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath Portions (c) International Organization Copyright (c) 2015 The Chromium Authors. Copyright (c) 2004-2006 Intel Corporation Copyright (c) 2016-2017, Matthieu Darbois Copyright (c) .NET Foundation Contributors Copyright (c) .NET Foundation and Contributors Copyright (c) 2011 Novell, Inc (http://www.novell.com) Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS The MIT License (MIT) Copyright (c) .NET Foundation and Contributors All rights reserved. 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. --------------------------------------------------------- --------------------------------------------------------- System.Drawing.Common 4.7.2 - MIT (c) Microsoft Corporation. Copyright (c) .NET Foundation. Copyright (c) 2011, Google Inc. (c) 1997-2005 Sean Eron Anderson. Copyright (c) 2007 James Newton-King Copyright (c) 1991-2017 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2015-2017, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath Portions (c) International Organization Copyright (c) 2015 The Chromium Authors. Copyright (c) 2004-2006 Intel Corporation Copyright (c) 2016-2017, Matthieu Darbois Copyright (c) .NET Foundation Contributors Copyright (c) .NET Foundation and Contributors Copyright (c) 2011 Novell, Inc (http://www.novell.com) Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS The MIT License (MIT) Copyright (c) .NET Foundation and Contributors All rights reserved. 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. --------------------------------------------------------- --------------------------------------------------------- System.IO.FileSystem.AccessControl 4.7.0 - MIT (c) Microsoft Corporation. Copyright (c) .NET Foundation. Copyright (c) 2011, Google Inc. (c) 1997-2005 Sean Eron Anderson. Copyright (c) 2007 James Newton-King Copyright (c) 1991-2017 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2015-2017, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath Portions (c) International Organization Copyright (c) 2015 The Chromium Authors. Copyright (c) 2004-2006 Intel Corporation Copyright (c) 2016-2017, Matthieu Darbois Copyright (c) .NET Foundation Contributors Copyright (c) .NET Foundation and Contributors Copyright (c) 2011 Novell, Inc (http://www.novell.com) Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS The MIT License (MIT) Copyright (c) .NET Foundation and Contributors All rights reserved. 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. --------------------------------------------------------- --------------------------------------------------------- System.Memory 4.5.1 - MIT (c) Microsoft Corporation Copyright (c) 2011, Google Inc. (c) 1997-2005 Sean Eron Anderson Copyright (c) 1991-2017 Unicode, Inc. Copyright (c) 2015 The Chromium Authors Portions (c) International Organization Copyright (c) 2004-2006 Intel Corporation Copyright (c) .NET Foundation Contributors Copyright (c) .NET Foundation and Contributors Copyright (c) 2011 Novell, Inc (http://www.novell.com) Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers The MIT License (MIT) Copyright (c) .NET Foundation and Contributors All rights reserved. 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. --------------------------------------------------------- --------------------------------------------------------- System.Runtime.CompilerServices.Unsafe 4.5.1 - MIT (c) Microsoft Corporation. Copyright (c) 2011, Google Inc. (c) 1997-2005 Sean Eron Anderson. Copyright (c) 1991-2017 Unicode, Inc. Portions (c) International Organization Copyright (c) 2015 The Chromium Authors. Copyright (c) 2004-2006 Intel Corporation Copyright (c) .NET Foundation Contributors Copyright (c) .NET Foundation and Contributors Copyright (c) 2011 Novell, Inc (http://www.novell.com) Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS The MIT License (MIT) Copyright (c) .NET Foundation and Contributors All rights reserved. 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. --------------------------------------------------------- --------------------------------------------------------- System.Security.Cryptography.ProtectedData 4.7.0 - MIT (c) Microsoft Corporation. Copyright (c) .NET Foundation. Copyright (c) 2011, Google Inc. (c) 1997-2005 Sean Eron Anderson. Copyright (c) 2007 James Newton-King Copyright (c) 1991-2017 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2015-2017, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath Portions (c) International Organization Copyright (c) 2015 The Chromium Authors. Copyright (c) 2004-2006 Intel Corporation Copyright (c) 2016-2017, Matthieu Darbois Copyright (c) .NET Foundation Contributors Copyright (c) .NET Foundation and Contributors Copyright (c) 2011 Novell, Inc (http://www.novell.com) Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS The MIT License (MIT) Copyright (c) .NET Foundation and Contributors All rights reserved. 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. --------------------------------------------------------- --------------------------------------------------------- System.Security.Permissions 4.7.0 - MIT (c) Microsoft Corporation. Copyright (c) .NET Foundation. Copyright (c) 2011, Google Inc. (c) 1997-2005 Sean Eron Anderson. Copyright (c) 2007 James Newton-King Copyright (c) 1991-2017 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2015-2017, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath Portions (c) International Organization Copyright (c) 2015 The Chromium Authors. Copyright (c) 2004-2006 Intel Corporation Copyright (c) 2016-2017, Matthieu Darbois Copyright (c) .NET Foundation Contributors Copyright (c) .NET Foundation and Contributors Copyright (c) 2011 Novell, Inc (http://www.novell.com) Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS The MIT License (MIT) Copyright (c) .NET Foundation and Contributors All rights reserved. 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. --------------------------------------------------------- --------------------------------------------------------- System.Windows.Extensions 4.7.0 - MIT (c) Microsoft Corporation. Copyright (c) .NET Foundation. Copyright (c) 2011, Google Inc. (c) 1997-2005 Sean Eron Anderson. Copyright (c) 2007 James Newton-King Copyright (c) 1991-2017 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2015-2017, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath Portions (c) International Organization Copyright (c) 2015 The Chromium Authors. Copyright (c) 2004-2006 Intel Corporation Copyright (c) 2016-2017, Matthieu Darbois Copyright (c) .NET Foundation Contributors Copyright (c) .NET Foundation and Contributors Copyright (c) 2011 Novell, Inc (http://www.novell.com) Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS The MIT License (MIT) Copyright (c) .NET Foundation and Contributors All rights reserved. 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. --------------------------------------------------------- ================================================ FILE: NuGet.config ================================================ ================================================ FILE: README.md ================================================ HttpRepl ======= # HttpRepl is now deprecated HttpRepl is now deprecated and will not receive any future updates. See https://github.com/dotnet/HttpRepl/issues/701 for more info. # Build Status [![Build Status](https://dev.azure.com/dnceng/public/_apis/build/status/aspnet/HttpRepl/aspnet-HttpRepl-CI?branchName=main)](https://dev.azure.com/dnceng/public/_build/latest?definitionId=538&branchName=main) The HTTP Read-Eval-Print Loop (REPL) is: - A lightweight, cross-platform command-line tool that's supported everywhere .NET Core is supported. - Used for making HTTP requests to test ASP.NET Core web APIs and view their results. ## Installation To install the HttpRepl, run the following command: ``` dotnet tool install -g Microsoft.dotnet-httprepl ``` A [.NET Core Global Tool](https://docs.microsoft.com/dotnet/core/tools/global-tools#install-a-global-tool) is installed from the [Microsoft.dotnet-httprepl](https://www.nuget.org/packages/Microsoft.dotnet-httprepl) NuGet package. ## Usage See the [documentation](https://aka.ms/http-repl-doc) for how to use and configure HttpRepl. ## Telemetry See the [documentation](https://docs.microsoft.com/aspnet/core/web-api/http-repl/telemetry) for information about the usage data collection. ## Building To build this repo, run the `build.cmd` or `build.sh` in the root of this repo. This repo uses the .NET [Arcade toolset](https://github.com/dotnet/arcade). ## Contributing See the [Contributing Guide](/CONTRIBUTING.md) for details on what it means to contribute and how to do so. ## Reporting security issues and bugs Security issues and bugs should be reported privately, via email, to the Microsoft Security Response Center (MSRC) secure@microsoft.com. 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. Further information, including the MSRC PGP key, can be found in the [Security TechCenter](https://technet.microsoft.com/security/ff852094.aspx). ================================================ FILE: SECURITY.md ================================================ ## 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 [many more](https://opensource.microsoft.com/). If you believe you have found a security vulnerability in any Microsoft-owned repository that meets Microsoft's [definition](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). ================================================ FILE: azure-pipelines-codeql.yml ================================================ parameters: # Optionally do not publish to TSA. Useful for e.g. verifying fixes before PR. - name: TSAEnabled displayName: Publish results to TSA type: boolean default: true variables: - template: eng/common/templates/variables/pool-providers.yml # CG is handled in the primary CI pipeline - name: skipComponentGovernanceDetection value: true # Force CodeQL enabled so it may be run on any branch - name: Codeql.Enabled value: true # Do not let CodeQL 3000 Extension gate scan frequency - name: Codeql.Cadence value: 0 # CodeQL needs this plumbed along as a variable to enable TSA - name: Codeql.TSAEnabled value: ${{ parameters.TSAEnabled }} # Build variables - name: _BuildConfig value: Release trigger: none schedules: - cron: 0 12 * * 1 displayName: Weekly Monday CodeQL run branches: include: - main always: true jobs: - job: codeql displayName: CodeQL pool: name: VSEngSS-MicroBuild2022-1ES timeoutInMinutes: 90 steps: - task: UseDotNet@2 inputs: useGlobalJson: true - task: CodeQL3000Init@0 displayName: CodeQL Initialize - script: eng\common\cibuild.cmd -configuration $(_BuildConfig) -prepareMachine /p:Test=false displayName: Windows Build - task: CodeQL3000Finalize@0 displayName: CodeQL Finalize ================================================ FILE: azure-pipelines-pr.yml ================================================ variables: - name: Build.Repository.Clean value: true - name: _TeamName value: AspNetCore - name: DOTNET_SKIP_FIRST_TIME_EXPERIENCE value: true - name: _PublishUsingPipelines value: true - name: _HelixType value: build/product - name: _HelixSource value: pr/dotnet/HttpRepl/$(Build.SourceBranch) resources: containers: - container: LinuxContainer image: mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-14.04-cross-0cd4667-20170319080304 options: --init # This ensures all the stray defunct processes are reaped. pr: branches: include: - "*" paths: include: - / exclude: - CONTRIBUTING.md - README.md - SECURITY.md stages: - stage: build displayName: Build jobs: - template: /eng/common/templates/jobs/jobs.yml parameters: enablePublishBuildArtifacts: true testResultsFormat: xunit enableTelemetry: true helixRepo: dotnet/HttpRepl jobs: - job: Windows pool: name: NetCore-Public demands: ImageOverride -equals windows.vs2022.amd64.open variables: - name: _HelixBuildConfig value: $(_BuildConfig) strategy: matrix: Debug: _BuildConfig: Debug _SignType: test _BuildArgs: /p:DotNetSignType=$(_SignType) /p:TeamName=$(_TeamName) Release: _BuildConfig: Release _SignType: test _BuildArgs: /p:DotNetSignType=$(_SignType) /p:TeamName=$(_TeamName) steps: - checkout: self clean: true - task: NuGetCommand@2 displayName: 'Clear NuGet caches' condition: succeeded() inputs: command: custom arguments: 'locals all -clear' - script: eng\common\cibuild.cmd -configuration $(_BuildConfig) -prepareMachine -integrationTest $(_BuildArgs) /p:DotNetPublishUsingPipelines=$(_PublishUsingPipelines) name: Build displayName: Build condition: succeeded() - task: PublishBuildArtifacts@1 displayName: Publish Packages condition: and(eq(variables['system.pullrequest.isfork'], false), eq(variables['_BuildConfig'], 'Release')) continueOnError: true inputs: artifactName: Packages_$(Agent.Os)_$(Agent.JobName) parallel: true pathtoPublish: '$(Build.SourcesDirectory)/artifacts/packages/$(_BuildConfig)' publishLocation: Container - job: macOS pool: vmImage: macOS-latest strategy: matrix: debug: _BuildConfig: Debug release: _BuildConfig: Release variables: - name: _HelixBuildConfig value: $(_BuildConfig) steps: - checkout: self clean: true - script: eng/common/cibuild.sh --configuration $(_BuildConfig) --prepareMachine --integrationTest name: Build displayName: Build condition: succeeded() - job: Linux pool: vmImage: ubuntu-20.04 container: LinuxContainer strategy: matrix: debug: _BuildConfig: Debug release: _BuildConfig: Release variables: - name: _HelixBuildConfig value: $(_BuildConfig) steps: - checkout: self clean: true - script: eng/common/cibuild.sh --configuration $(_BuildConfig) --prepareMachine --integrationTest name: Build displayName: Build condition: succeeded() ================================================ FILE: azure-pipelines.yml ================================================ variables: - template: /eng/common/templates-official/variables/pool-providers.yml@self - name: Build.Repository.Clean value: true - name: _TeamName value: AspNetCore - name: TeamName value: AspNetCore - name: DOTNET_SKIP_FIRST_TIME_EXPERIENCE value: true - name: _PublishUsingPipelines value: true - name: _HelixType value: build/product - name: _HelixSource value: official/dotnet/HttpRepl/$(Build.SourceBranch) - name: _BuildConfig value: Release - name: _SignType value: real - name: _BuildArgs value: /p:DotNetSignType=$(_SignType) /p:TeamName=$(_TeamName) /p:OfficialBuildId=$(Build.BuildNumber) - name: _HelixBuildConfig value: $(_BuildConfig) resources: repositories: - repository: MicroBuildTemplate type: git name: 1ESPipelineTemplates/MicroBuildTemplate ref: refs/tags/release trigger: branches: include: - main - release/* paths: include: - / exclude: - CONTRIBUTING.md - README.md - SECURITY.md extends: template: azure-pipelines/MicroBuild.1ES.Official.yml@MicroBuildTemplate parameters: sdl: sourceAnalysisPool: name: $(DncEngInternalBuildPool) image: 1es-windows-2022 os: windows codeSignValidation: # We make copies of our pre-signed binaries to the output directory, but we do not ship those. The signed ones # are in the packages folder and pass CSV without issue. We only ship the signed packages, not any individual binaries additionalTargetsGlobPattern: -|**\bin\** policheck: enabled: true tsa: enabled: true configFile: '$(Build.SourcesDirectory)\.config\tsaoptions.json' customBuildTags: - ES365AIMigrationTooling stages: - stage: build displayName: Build jobs: - template: /eng/common/templates-official/jobs/jobs.yml@self parameters: artifacts: publish: artifacts: true enablePublishBuildArtifacts: true testResultsFormat: xunit enableTelemetry: true helixRepo: dotnet/HttpRepl enableMicrobuild: true jobs: - job: Windows pool: name: $(DncEngInternalBuildPool) image: 1es-windows-2022 os: windows steps: - checkout: self clean: true - task: NuGetCommand@2 displayName: 'Clear NuGet caches' condition: succeeded() inputs: command: custom arguments: 'locals all -clear' - script: eng\common\cibuild.cmd -configuration $(_BuildConfig) -prepareMachine -integrationTest $(_BuildArgs) /p:DotNetPublishUsingPipelines=$(_PublishUsingPipelines) name: Build displayName: Build condition: succeeded() ================================================ FILE: build/analyzers/rulesets/Default.ruleset ================================================  ================================================ FILE: build/analyzers/rulesets/Sdl7.0.ruleset ================================================  ================================================ FILE: build.cmd ================================================ @echo off powershell -ExecutionPolicy ByPass -NoProfile -command "& """%~dp0eng\common\Build.ps1""" -build -restore -pack -test %*" exit /b %ErrorLevel% ================================================ FILE: build.sh ================================================ #!/usr/bin/env bash source="${BASH_SOURCE[0]}" # resolve $SOURCE until the file is no longer a symlink while [[ -h $source ]]; do scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" source="$(readlink "$source")" # if $source was a relative symlink, we need to resolve it relative to the path where the # symlink file was located [[ $source != /* ]] && source="$scriptroot/$source" done scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" "$scriptroot/eng/common/build.sh" --pack --build --restore --test $@ ================================================ FILE: eng/Build.props ================================================ ================================================ FILE: eng/Signing.props ================================================ ================================================ FILE: eng/Version.Details.xml ================================================ https://github.com/dotnet/arcade e58820063a8754d418518bce69ca2df0e3b4ac25 ================================================ FILE: eng/Versions.props ================================================ false 8.1.0 preview ================================================ FILE: eng/common/BuildConfiguration/build-configuration.json ================================================ { "RetryCountLimit": 1, "RetryByAnyError": false } ================================================ FILE: eng/common/CIBuild.cmd ================================================ @echo off powershell -ExecutionPolicy ByPass -NoProfile -command "& """%~dp0Build.ps1""" -restore -build -test -sign -pack -publish -ci %*" ================================================ FILE: eng/common/PSScriptAnalyzerSettings.psd1 ================================================ @{ IncludeRules=@('PSAvoidUsingCmdletAliases', 'PSAvoidUsingWMICmdlet', 'PSAvoidUsingPositionalParameters', 'PSAvoidUsingInvokeExpression', 'PSUseDeclaredVarsMoreThanAssignments', 'PSUseCmdletCorrectly', 'PSStandardDSCFunctionsInResource', 'PSUseIdenticalMandatoryParametersForDSC', 'PSUseIdenticalParametersForDSC') } ================================================ FILE: eng/common/README.md ================================================ # Don't touch this folder uuuuuuuuuuuuuuuuuuuu u" uuuuuuuuuuuuuuuuuu "u u" u$$$$$$$$$$$$$$$$$$$$u "u u" u$$$$$$$$$$$$$$$$$$$$$$$$u "u u" u$$$$$$$$$$$$$$$$$$$$$$$$$$$$u "u u" u$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$u "u u" u$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$u "u $ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $ $ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $ $ $$$" ... "$... ...$" ... "$$$ ... "$$$ $ $ $$$u `"$$$$$$$ $$$ $$$$$ $$ $$$ $$$ $ $ $$$$$$uu "$$$$ $$$ $$$$$ $$ """ u$$$ $ $ $$$""$$$ $$$$ $$$u "$$$" u$$ $$$$$$$$ $ $ $$$$....,$$$$$..$$$$$....,$$$$..$$$$$$$$ $ $ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $ "u "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" u" "u "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" u" "u "$$$$$$$$$$$$$$$$$$$$$$$$$$$$" u" "u "$$$$$$$$$$$$$$$$$$$$$$$$" u" "u "$$$$$$$$$$$$$$$$$$$$" u" "u """""""""""""""""" u" """""""""""""""""""" !!! Changes made in this directory are subject to being overwritten by automation !!! The files in this directory are shared by all Arcade repos and managed by automation. If you need to make changes to these files, open an issue or submit a pull request to https://github.com/dotnet/arcade first. ================================================ FILE: eng/common/SetupNugetSources.ps1 ================================================ # This script adds internal feeds required to build commits that depend on internal package sources. For instance, # dotnet6-internal would be added automatically if dotnet6 was found in the nuget.config file. In addition also enables # disabled internal Maestro (darc-int*) feeds. # # Optionally, this script also adds a credential entry for each of the internal feeds if supplied. # # See example call for this script below. # # - task: PowerShell@2 # displayName: Setup Private Feeds Credentials # condition: eq(variables['Agent.OS'], 'Windows_NT') # inputs: # filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.ps1 # arguments: -ConfigFile $(Build.SourcesDirectory)/NuGet.config -Password $Env:Token # env: # Token: $(dn-bot-dnceng-artifact-feeds-rw) # # Note that the NuGetAuthenticate task should be called after SetupNugetSources. # This ensures that: # - Appropriate creds are set for the added internal feeds (if not supplied to the scrupt) # - The credential provider is installed. # # This logic is also abstracted into enable-internal-sources.yml. [CmdletBinding()] param ( [Parameter(Mandatory = $true)][string]$ConfigFile, $Password ) $ErrorActionPreference = "Stop" Set-StrictMode -Version 2.0 [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 . $PSScriptRoot\tools.ps1 # Add source entry to PackageSources function AddPackageSource($sources, $SourceName, $SourceEndPoint, $creds, $Username, $pwd) { $packageSource = $sources.SelectSingleNode("add[@key='$SourceName']") if ($packageSource -eq $null) { $packageSource = $doc.CreateElement("add") $packageSource.SetAttribute("key", $SourceName) $packageSource.SetAttribute("value", $SourceEndPoint) $sources.AppendChild($packageSource) | Out-Null } else { Write-Host "Package source $SourceName already present." } AddCredential -Creds $creds -Source $SourceName -Username $Username -pwd $pwd } # Add a credential node for the specified source function AddCredential($creds, $source, $username, $pwd) { # If no cred supplied, don't do anything. if (!$pwd) { return; } # Looks for credential configuration for the given SourceName. Create it if none is found. $sourceElement = $creds.SelectSingleNode($Source) if ($sourceElement -eq $null) { $sourceElement = $doc.CreateElement($Source) $creds.AppendChild($sourceElement) | Out-Null } # Add the node to the credential if none is found. $usernameElement = $sourceElement.SelectSingleNode("add[@key='Username']") if ($usernameElement -eq $null) { $usernameElement = $doc.CreateElement("add") $usernameElement.SetAttribute("key", "Username") $sourceElement.AppendChild($usernameElement) | Out-Null } $usernameElement.SetAttribute("value", $Username) # Add the to the credential if none is found. # Add it as a clear text because there is no support for encrypted ones in non-windows .Net SDKs. # -> https://github.com/NuGet/Home/issues/5526 $passwordElement = $sourceElement.SelectSingleNode("add[@key='ClearTextPassword']") if ($passwordElement -eq $null) { $passwordElement = $doc.CreateElement("add") $passwordElement.SetAttribute("key", "ClearTextPassword") $sourceElement.AppendChild($passwordElement) | Out-Null } $passwordElement.SetAttribute("value", $pwd) } function InsertMaestroPrivateFeedCredentials($Sources, $Creds, $Username, $pwd) { $maestroPrivateSources = $Sources.SelectNodes("add[contains(@key,'darc-int')]") Write-Host "Inserting credentials for $($maestroPrivateSources.Count) Maestro's private feeds." ForEach ($PackageSource in $maestroPrivateSources) { Write-Host "`tInserting credential for Maestro's feed:" $PackageSource.Key AddCredential -Creds $creds -Source $PackageSource.Key -Username $Username -pwd $pwd } } function EnablePrivatePackageSources($DisabledPackageSources) { $maestroPrivateSources = $DisabledPackageSources.SelectNodes("add[contains(@key,'darc-int')]") ForEach ($DisabledPackageSource in $maestroPrivateSources) { Write-Host "`tEnsuring private source '$($DisabledPackageSource.key)' is enabled by deleting it from disabledPackageSource" # Due to https://github.com/NuGet/Home/issues/10291, we must actually remove the disabled entries $DisabledPackageSources.RemoveChild($DisabledPackageSource) } } if (!(Test-Path $ConfigFile -PathType Leaf)) { Write-PipelineTelemetryError -Category 'Build' -Message "Eng/common/SetupNugetSources.ps1 returned a non-zero exit code. Couldn't find the NuGet config file: $ConfigFile" ExitWithExitCode 1 } # Load NuGet.config $doc = New-Object System.Xml.XmlDocument $filename = (Get-Item $ConfigFile).FullName $doc.Load($filename) # Get reference to or create one if none exist already $sources = $doc.DocumentElement.SelectSingleNode("packageSources") if ($sources -eq $null) { $sources = $doc.CreateElement("packageSources") $doc.DocumentElement.AppendChild($sources) | Out-Null } $creds = $null if ($Password) { # Looks for a node. Create it if none is found. $creds = $doc.DocumentElement.SelectSingleNode("packageSourceCredentials") if ($creds -eq $null) { $creds = $doc.CreateElement("packageSourceCredentials") $doc.DocumentElement.AppendChild($creds) | Out-Null } } # Check for disabledPackageSources; we'll enable any darc-int ones we find there $disabledSources = $doc.DocumentElement.SelectSingleNode("disabledPackageSources") if ($disabledSources -ne $null) { Write-Host "Checking for any darc-int disabled package sources in the disabledPackageSources node" EnablePrivatePackageSources -DisabledPackageSources $disabledSources } $userName = "dn-bot" # Insert credential nodes for Maestro's private feeds InsertMaestroPrivateFeedCredentials -Sources $sources -Creds $creds -Username $userName -pwd $Password # 3.1 uses a different feed url format so it's handled differently here $dotnet31Source = $sources.SelectSingleNode("add[@key='dotnet3.1']") if ($dotnet31Source -ne $null) { AddPackageSource -Sources $sources -SourceName "dotnet3.1-internal" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal/nuget/v2" -Creds $creds -Username $userName -pwd $Password AddPackageSource -Sources $sources -SourceName "dotnet3.1-internal-transport" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-transport/nuget/v2" -Creds $creds -Username $userName -pwd $Password } $dotnetVersions = @('5','6','7','8','9') foreach ($dotnetVersion in $dotnetVersions) { $feedPrefix = "dotnet" + $dotnetVersion; $dotnetSource = $sources.SelectSingleNode("add[@key='$feedPrefix']") if ($dotnetSource -ne $null) { AddPackageSource -Sources $sources -SourceName "$feedPrefix-internal" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/internal/_packaging/$feedPrefix-internal/nuget/v2" -Creds $creds -Username $userName -pwd $Password AddPackageSource -Sources $sources -SourceName "$feedPrefix-internal-transport" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/internal/_packaging/$feedPrefix-internal-transport/nuget/v2" -Creds $creds -Username $userName -pwd $Password } } $doc.Save($filename) ================================================ FILE: eng/common/SetupNugetSources.sh ================================================ #!/usr/bin/env bash # This script adds internal feeds required to build commits that depend on internal package sources. For instance, # dotnet6-internal would be added automatically if dotnet6 was found in the nuget.config file. In addition also enables # disabled internal Maestro (darc-int*) feeds. # # Optionally, this script also adds a credential entry for each of the internal feeds if supplied. # # See example call for this script below. # # - task: Bash@3 # displayName: Setup Internal Feeds # inputs: # filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.sh # arguments: $(Build.SourcesDirectory)/NuGet.config # condition: ne(variables['Agent.OS'], 'Windows_NT') # - task: NuGetAuthenticate@1 # # Note that the NuGetAuthenticate task should be called after SetupNugetSources. # This ensures that: # - Appropriate creds are set for the added internal feeds (if not supplied to the scrupt) # - The credential provider is installed. # # This logic is also abstracted into enable-internal-sources.yml. ConfigFile=$1 CredToken=$2 NL='\n' TB=' ' source="${BASH_SOURCE[0]}" # resolve $source until the file is no longer a symlink while [[ -h "$source" ]]; do scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" source="$(readlink "$source")" # if $source was a relative symlink, we need to resolve it relative to the path where the # symlink file was located [[ $source != /* ]] && source="$scriptroot/$source" done scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" . "$scriptroot/tools.sh" if [ ! -f "$ConfigFile" ]; then Write-PipelineTelemetryError -Category 'Build' "Error: Eng/common/SetupNugetSources.sh returned a non-zero exit code. Couldn't find the NuGet config file: $ConfigFile" ExitWithExitCode 1 fi if [[ `uname -s` == "Darwin" ]]; then NL=$'\\\n' TB='' fi # Ensure there is a ... section. grep -i "" $ConfigFile if [ "$?" != "0" ]; then echo "Adding ... section." ConfigNodeHeader="" PackageSourcesTemplate="${TB}${NL}${TB}" sed -i.bak "s|$ConfigNodeHeader|$ConfigNodeHeader${NL}$PackageSourcesTemplate|" $ConfigFile fi # Ensure there is a ... section. grep -i "" $ConfigFile if [ "$?" != "0" ]; then echo "Adding ... section." PackageSourcesNodeFooter="" PackageSourceCredentialsTemplate="${TB}${NL}${TB}" sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourcesNodeFooter${NL}$PackageSourceCredentialsTemplate|" $ConfigFile fi PackageSources=() # Ensure dotnet3.1-internal and dotnet3.1-internal-transport are in the packageSources if the public dotnet3.1 feeds are present grep -i "" sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourceTemplate${NL}$PackageSourcesNodeFooter|" $ConfigFile fi PackageSources+=('dotnet3.1-internal') grep -i "" $ConfigFile if [ "$?" != "0" ]; then echo "Adding dotnet3.1-internal-transport to the packageSources." PackageSourcesNodeFooter="" PackageSourceTemplate="${TB}" sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourceTemplate${NL}$PackageSourcesNodeFooter|" $ConfigFile fi PackageSources+=('dotnet3.1-internal-transport') fi DotNetVersions=('5' '6' '7' '8' '9') for DotNetVersion in ${DotNetVersions[@]} ; do FeedPrefix="dotnet${DotNetVersion}"; grep -i "" sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourceTemplate${NL}$PackageSourcesNodeFooter|" $ConfigFile fi PackageSources+=("$FeedPrefix-internal") grep -i "" $ConfigFile if [ "$?" != "0" ]; then echo "Adding $FeedPrefix-internal-transport to the packageSources." PackageSourcesNodeFooter="" PackageSourceTemplate="${TB}" sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourceTemplate${NL}$PackageSourcesNodeFooter|" $ConfigFile fi PackageSources+=("$FeedPrefix-internal-transport") fi done # I want things split line by line PrevIFS=$IFS IFS=$'\n' PackageSources+="$IFS" PackageSources+=$(grep -oh '"darc-int-[^"]*"' $ConfigFile | tr -d '"') IFS=$PrevIFS if [ "$CredToken" ]; then for FeedName in ${PackageSources[@]} ; do # Check if there is no existing credential for this FeedName grep -i "<$FeedName>" $ConfigFile if [ "$?" != "0" ]; then echo "Adding credentials for $FeedName." PackageSourceCredentialsNodeFooter="" NewCredential="${TB}${TB}<$FeedName>${NL}${NL}${NL}" sed -i.bak "s|$PackageSourceCredentialsNodeFooter|$NewCredential${NL}$PackageSourceCredentialsNodeFooter|" $ConfigFile fi done fi # Re-enable any entries in disabledPackageSources where the feed name contains darc-int grep -i "" $ConfigFile if [ "$?" == "0" ]; then DisabledDarcIntSources=() echo "Re-enabling any disabled \"darc-int\" package sources in $ConfigFile" DisabledDarcIntSources+=$(grep -oh '"darc-int-[^"]*" value="true"' $ConfigFile | tr -d '"') for DisabledSourceName in ${DisabledDarcIntSources[@]} ; do if [[ $DisabledSourceName == darc-int* ]] then OldDisableValue="" NewDisableValue="" sed -i.bak "s|$OldDisableValue|$NewDisableValue|" $ConfigFile echo "Neutralized disablePackageSources entry for '$DisabledSourceName'" fi done fi ================================================ FILE: eng/common/build.cmd ================================================ @echo off powershell -ExecutionPolicy ByPass -NoProfile -command "& """%~dp0build.ps1""" %*" exit /b %ErrorLevel% ================================================ FILE: eng/common/build.ps1 ================================================ [CmdletBinding(PositionalBinding=$false)] Param( [string][Alias('c')]$configuration = "Debug", [string]$platform = $null, [string] $projects, [string][Alias('v')]$verbosity = "minimal", [string] $msbuildEngine = $null, [bool] $warnAsError = $true, [bool] $nodeReuse = $true, [switch][Alias('r')]$restore, [switch] $deployDeps, [switch][Alias('b')]$build, [switch] $rebuild, [switch] $deploy, [switch][Alias('t')]$test, [switch] $integrationTest, [switch] $performanceTest, [switch] $sign, [switch] $pack, [switch] $publish, [switch] $clean, [switch][Alias('pb')]$productBuild, [switch][Alias('bl')]$binaryLog, [switch][Alias('nobl')]$excludeCIBinarylog, [switch] $ci, [switch] $prepareMachine, [string] $runtimeSourceFeed = '', [string] $runtimeSourceFeedKey = '', [switch] $excludePrereleaseVS, [switch] $nativeToolsOnMachine, [switch] $help, [Parameter(ValueFromRemainingArguments=$true)][String[]]$properties ) # Unset 'Platform' environment variable to avoid unwanted collision in InstallDotNetCore.targets file # some computer has this env var defined (e.g. Some HP) if($env:Platform) { $env:Platform="" } function Print-Usage() { Write-Host "Common settings:" Write-Host " -configuration Build configuration: 'Debug' or 'Release' (short: -c)" Write-Host " -platform Platform configuration: 'x86', 'x64' or any valid Platform value to pass to msbuild" Write-Host " -verbosity Msbuild verbosity: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] (short: -v)" Write-Host " -binaryLog Output binary log (short: -bl)" Write-Host " -help Print help and exit" Write-Host "" Write-Host "Actions:" Write-Host " -restore Restore dependencies (short: -r)" Write-Host " -build Build solution (short: -b)" Write-Host " -rebuild Rebuild solution" Write-Host " -deploy Deploy built VSIXes" Write-Host " -deployDeps Deploy dependencies (e.g. VSIXes for integration tests)" Write-Host " -test Run all unit tests in the solution (short: -t)" Write-Host " -integrationTest Run all integration tests in the solution" Write-Host " -performanceTest Run all performance tests in the solution" Write-Host " -pack Package build outputs into NuGet packages and Willow components" Write-Host " -sign Sign build outputs" Write-Host " -publish Publish artifacts (e.g. symbols)" Write-Host " -clean Clean the solution" Write-Host " -productBuild Build the solution in the way it will be built in the full .NET product (VMR) build (short: -pb)" Write-Host "" Write-Host "Advanced settings:" Write-Host " -projects Semi-colon delimited list of sln/proj's to build. Globbing is supported (*.sln)" Write-Host " -ci Set when running on CI server" Write-Host " -excludeCIBinarylog Don't output binary log (short: -nobl)" Write-Host " -prepareMachine Prepare machine for CI run, clean up processes after build" Write-Host " -warnAsError Sets warnaserror msbuild parameter ('true' or 'false')" Write-Host " -msbuildEngine Msbuild engine to use to run build ('dotnet', 'vs', or unspecified)." Write-Host " -excludePrereleaseVS Set to exclude build engines in prerelease versions of Visual Studio" Write-Host " -nativeToolsOnMachine Sets the native tools on machine environment variable (indicating that the script should use native tools on machine)" Write-Host "" Write-Host "Command line arguments not listed above are passed thru to msbuild." Write-Host "The above arguments can be shortened as much as to be unambiguous (e.g. -co for configuration, -t for test, etc.)." } . $PSScriptRoot\tools.ps1 function InitializeCustomToolset { if (-not $restore) { return } $script = Join-Path $EngRoot 'restore-toolset.ps1' if (Test-Path $script) { . $script } } function Build { $toolsetBuildProj = InitializeToolset InitializeCustomToolset $bl = if ($binaryLog) { '/bl:' + (Join-Path $LogDir 'Build.binlog') } else { '' } $platformArg = if ($platform) { "/p:Platform=$platform" } else { '' } if ($projects) { # Re-assign properties to a new variable because PowerShell doesn't let us append properties directly for unclear reasons. # Explicitly set the type as string[] because otherwise PowerShell would make this char[] if $properties is empty. [string[]] $msbuildArgs = $properties # Resolve relative project paths into full paths $projects = ($projects.Split(';').ForEach({Resolve-Path $_}) -join ';') $msbuildArgs += "/p:Projects=$projects" $properties = $msbuildArgs } MSBuild $toolsetBuildProj ` $bl ` $platformArg ` /p:Configuration=$configuration ` /p:RepoRoot=$RepoRoot ` /p:Restore=$restore ` /p:DeployDeps=$deployDeps ` /p:Build=$build ` /p:Rebuild=$rebuild ` /p:Deploy=$deploy ` /p:Test=$test ` /p:Pack=$pack ` /p:DotNetBuildRepo=$productBuild ` /p:IntegrationTest=$integrationTest ` /p:PerformanceTest=$performanceTest ` /p:Sign=$sign ` /p:Publish=$publish ` @properties } try { if ($clean) { if (Test-Path $ArtifactsDir) { Remove-Item -Recurse -Force $ArtifactsDir Write-Host 'Artifacts directory deleted.' } exit 0 } if ($help -or (($null -ne $properties) -and ($properties.Contains('/help') -or $properties.Contains('/?')))) { Print-Usage exit 0 } if ($ci) { if (-not $excludeCIBinarylog) { $binaryLog = $true } $nodeReuse = $false } if ($nativeToolsOnMachine) { $env:NativeToolsOnMachine = $true } if ($restore) { InitializeNativeTools } Build } catch { Write-Host $_.ScriptStackTrace Write-PipelineTelemetryError -Category 'InitializeToolset' -Message $_ ExitWithExitCode 1 } ExitWithExitCode 0 ================================================ FILE: eng/common/build.sh ================================================ #!/usr/bin/env bash # Stop script if unbound variable found (use ${var:-} if intentional) set -u # Stop script if command returns non-zero exit code. # Prevents hidden errors caused by missing error code propagation. set -e usage() { echo "Common settings:" echo " --configuration Build configuration: 'Debug' or 'Release' (short: -c)" echo " --verbosity Msbuild verbosity: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] (short: -v)" echo " --binaryLog Create MSBuild binary log (short: -bl)" echo " --help Print help and exit (short: -h)" echo "" echo "Actions:" echo " --restore Restore dependencies (short: -r)" echo " --build Build solution (short: -b)" echo " --sourceBuild Source-build the solution (short: -sb)" echo " Will additionally trigger the following actions: --restore, --build, --pack" echo " If --configuration is not set explicitly, will also set it to 'Release'" echo " --productBuild Build the solution in the way it will be built in the full .NET product (VMR) build (short: -pb)" echo " Will additionally trigger the following actions: --restore, --build, --pack" echo " If --configuration is not set explicitly, will also set it to 'Release'" echo " --rebuild Rebuild solution" echo " --test Run all unit tests in the solution (short: -t)" echo " --integrationTest Run all integration tests in the solution" echo " --performanceTest Run all performance tests in the solution" echo " --pack Package build outputs into NuGet packages and Willow components" echo " --sign Sign build outputs" echo " --publish Publish artifacts (e.g. symbols)" echo " --clean Clean the solution" echo "" echo "Advanced settings:" echo " --projects Project or solution file(s) to build" echo " --ci Set when running on CI server" echo " --excludeCIBinarylog Don't output binary log (short: -nobl)" echo " --prepareMachine Prepare machine for CI run, clean up processes after build" echo " --nodeReuse Sets nodereuse msbuild parameter ('true' or 'false')" echo " --warnAsError Sets warnaserror msbuild parameter ('true' or 'false')" echo "" echo "Command line arguments not listed above are passed thru to msbuild." echo "Arguments can also be passed in with a single hyphen." } source="${BASH_SOURCE[0]}" # resolve $source until the file is no longer a symlink while [[ -h "$source" ]]; do scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" source="$(readlink "$source")" # if $source was a relative symlink, we need to resolve it relative to the path where the # symlink file was located [[ $source != /* ]] && source="$scriptroot/$source" done scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" restore=false build=false source_build=false product_build=false rebuild=false test=false integration_test=false performance_test=false pack=false publish=false sign=false public=false ci=false clean=false warn_as_error=true node_reuse=true binary_log=false exclude_ci_binary_log=false pipelines_log=false projects='' configuration='' prepare_machine=false verbosity='minimal' runtime_source_feed='' runtime_source_feed_key='' properties='' while [[ $# > 0 ]]; do opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" case "$opt" in -help|-h) usage exit 0 ;; -clean) clean=true ;; -configuration|-c) configuration=$2 shift ;; -verbosity|-v) verbosity=$2 shift ;; -binarylog|-bl) binary_log=true ;; -excludecibinarylog|-nobl) exclude_ci_binary_log=true ;; -pipelineslog|-pl) pipelines_log=true ;; -restore|-r) restore=true ;; -build|-b) build=true ;; -rebuild) rebuild=true ;; -pack) pack=true ;; -sourcebuild|-sb) build=true source_build=true product_build=true restore=true pack=true ;; -productBuild|-pb) build=true product_build=true restore=true pack=true ;; -test|-t) test=true ;; -integrationtest) integration_test=true ;; -performancetest) performance_test=true ;; -sign) sign=true ;; -publish) publish=true ;; -preparemachine) prepare_machine=true ;; -projects) projects=$2 shift ;; -ci) ci=true ;; -warnaserror) warn_as_error=$2 shift ;; -nodereuse) node_reuse=$2 shift ;; -runtimesourcefeed) runtime_source_feed=$2 shift ;; -runtimesourcefeedkey) runtime_source_feed_key=$2 shift ;; *) properties="$properties $1" ;; esac shift done if [[ -z "$configuration" ]]; then if [[ "$source_build" = true ]]; then configuration="Release"; else configuration="Debug"; fi fi if [[ "$ci" == true ]]; then pipelines_log=true node_reuse=false if [[ "$exclude_ci_binary_log" == false ]]; then binary_log=true fi fi . "$scriptroot/tools.sh" function InitializeCustomToolset { local script="$eng_root/restore-toolset.sh" if [[ -a "$script" ]]; then . "$script" fi } function Build { InitializeToolset InitializeCustomToolset if [[ ! -z "$projects" ]]; then properties="$properties /p:Projects=$projects" fi local bl="" if [[ "$binary_log" == true ]]; then bl="/bl:\"$log_dir/Build.binlog\"" fi MSBuild $_InitializeToolset \ $bl \ /p:Configuration=$configuration \ /p:RepoRoot="$repo_root" \ /p:Restore=$restore \ /p:Build=$build \ /p:DotNetBuildRepo=$product_build \ /p:DotNetBuildSourceOnly=$source_build \ /p:Rebuild=$rebuild \ /p:Test=$test \ /p:Pack=$pack \ /p:IntegrationTest=$integration_test \ /p:PerformanceTest=$performance_test \ /p:Sign=$sign \ /p:Publish=$publish \ $properties ExitWithExitCode 0 } if [[ "$clean" == true ]]; then if [ -d "$artifacts_dir" ]; then rm -rf $artifacts_dir echo "Artifacts directory deleted." fi exit 0 fi if [[ "$restore" == true ]]; then InitializeNativeTools fi Build ================================================ FILE: eng/common/cibuild.sh ================================================ #!/usr/bin/env bash source="${BASH_SOURCE[0]}" # resolve $SOURCE until the file is no longer a symlink while [[ -h $source ]]; do scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" source="$(readlink "$source")" # if $source was a relative symlink, we need to resolve it relative to the path where # the symlink file was located [[ $source != /* ]] && source="$scriptroot/$source" done scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" . "$scriptroot/build.sh" --restore --build --test --pack --publish --ci $@ ================================================ FILE: eng/common/core-templates/job/job.yml ================================================ parameters: # Job schema parameters - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#job cancelTimeoutInMinutes: '' condition: '' container: '' continueOnError: false dependsOn: '' displayName: '' pool: '' steps: [] strategy: '' timeoutInMinutes: '' variables: [] workspace: '' templateContext: {} # Job base template specific parameters # See schema documentation - https://github.com/dotnet/arcade/blob/master/Documentation/AzureDevOps/TemplateSchema.md # publishing defaults artifacts: '' enableMicrobuild: false enableMicrobuildForMacAndLinux: false enablePublishBuildArtifacts: false enablePublishBuildAssets: false enablePublishTestResults: false enablePublishUsingPipelines: false enableBuildRetry: false mergeTestResults: false testRunTitle: '' testResultsFormat: '' name: '' componentGovernanceSteps: [] preSteps: [] artifactPublishSteps: [] runAsPublic: false # 1es specific parameters is1ESPipeline: '' jobs: - job: ${{ parameters.name }} ${{ if ne(parameters.cancelTimeoutInMinutes, '') }}: cancelTimeoutInMinutes: ${{ parameters.cancelTimeoutInMinutes }} ${{ if ne(parameters.condition, '') }}: condition: ${{ parameters.condition }} ${{ if ne(parameters.container, '') }}: container: ${{ parameters.container }} ${{ if ne(parameters.continueOnError, '') }}: continueOnError: ${{ parameters.continueOnError }} ${{ if ne(parameters.dependsOn, '') }}: dependsOn: ${{ parameters.dependsOn }} ${{ if ne(parameters.displayName, '') }}: displayName: ${{ parameters.displayName }} ${{ if ne(parameters.pool, '') }}: pool: ${{ parameters.pool }} ${{ if ne(parameters.strategy, '') }}: strategy: ${{ parameters.strategy }} ${{ if ne(parameters.timeoutInMinutes, '') }}: timeoutInMinutes: ${{ parameters.timeoutInMinutes }} ${{ if ne(parameters.templateContext, '') }}: templateContext: ${{ parameters.templateContext }} variables: - ${{ if ne(parameters.enableTelemetry, 'false') }}: - name: DOTNET_CLI_TELEMETRY_PROFILE value: '$(Build.Repository.Uri)' - ${{ if eq(parameters.enableRichCodeNavigation, 'true') }}: - name: EnableRichCodeNavigation value: 'true' # Retry signature validation up to three times, waiting 2 seconds between attempts. # See https://learn.microsoft.com/en-us/nuget/reference/errors-and-warnings/nu3028#retry-untrusted-root-failures - name: NUGET_EXPERIMENTAL_CHAIN_BUILD_RETRY_POLICY value: 3,2000 - ${{ each variable in parameters.variables }}: # handle name-value variable syntax # example: # - name: [key] # value: [value] - ${{ if ne(variable.name, '') }}: - name: ${{ variable.name }} value: ${{ variable.value }} # handle variable groups - ${{ if ne(variable.group, '') }}: - group: ${{ variable.group }} # handle template variable syntax # example: # - template: path/to/template.yml # parameters: # [key]: [value] - ${{ if ne(variable.template, '') }}: - template: ${{ variable.template }} ${{ if ne(variable.parameters, '') }}: parameters: ${{ variable.parameters }} # handle key-value variable syntax. # example: # - [key]: [value] - ${{ if and(eq(variable.name, ''), eq(variable.group, ''), eq(variable.template, '')) }}: - ${{ each pair in variable }}: - name: ${{ pair.key }} value: ${{ pair.value }} # DotNet-HelixApi-Access provides 'HelixApiAccessToken' for internal builds - ${{ if and(eq(parameters.enableTelemetry, 'true'), eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - group: DotNet-HelixApi-Access ${{ if ne(parameters.workspace, '') }}: workspace: ${{ parameters.workspace }} steps: - ${{ if eq(parameters.is1ESPipeline, '') }}: - 'Illegal entry point, is1ESPipeline is not defined. Repository yaml should not directly reference templates in core-templates folder.': error - ${{ if ne(parameters.preSteps, '') }}: - ${{ each preStep in parameters.preSteps }}: - ${{ preStep }} - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - template: /eng/common/core-templates/steps/install-microbuild.yml parameters: enableMicrobuild: ${{ parameters.enableMicrobuild }} enableMicrobuildForMacAndLinux: ${{ parameters.enableMicrobuildForMacAndLinux }} continueOnError: ${{ parameters.continueOnError }} - ${{ if and(eq(parameters.runAsPublic, 'false'), eq(variables['System.TeamProject'], 'internal')) }}: - task: NuGetAuthenticate@1 - ${{ if and(ne(parameters.artifacts.download, 'false'), ne(parameters.artifacts.download, '')) }}: - task: DownloadPipelineArtifact@2 inputs: buildType: current artifactName: ${{ coalesce(parameters.artifacts.download.name, 'Artifacts_$(Agent.OS)_$(_BuildConfig)') }} targetPath: ${{ coalesce(parameters.artifacts.download.path, 'artifacts') }} itemPattern: ${{ coalesce(parameters.artifacts.download.pattern, '**') }} - ${{ each step in parameters.steps }}: - ${{ step }} - ${{ if eq(parameters.enableRichCodeNavigation, true) }}: - task: RichCodeNavIndexer@0 displayName: RichCodeNav Upload inputs: languages: ${{ coalesce(parameters.richCodeNavigationLanguage, 'csharp') }} environment: ${{ coalesce(parameters.richCodeNavigationEnvironment, 'internal') }} richNavLogOutputDirectory: $(Build.SourcesDirectory)/artifacts/bin uploadRichNavArtifacts: ${{ coalesce(parameters.richCodeNavigationUploadArtifacts, false) }} continueOnError: true - ${{ each step in parameters.componentGovernanceSteps }}: - ${{ step }} - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - template: /eng/common/core-templates/steps/cleanup-microbuild.yml parameters: enableMicrobuild: ${{ parameters.enableMicrobuild }} enableMicrobuildForMacAndLinux: ${{ parameters.enableMicrobuildForMacAndLinux }} continueOnError: ${{ parameters.continueOnError }} # Publish test results - ${{ if or(and(eq(parameters.enablePublishTestResults, 'true'), eq(parameters.testResultsFormat, '')), eq(parameters.testResultsFormat, 'xunit')) }}: - task: PublishTestResults@2 displayName: Publish XUnit Test Results inputs: testResultsFormat: 'xUnit' testResultsFiles: '*.xml' searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)' testRunTitle: ${{ coalesce(parameters.testRunTitle, parameters.name, '$(System.JobName)') }}-xunit mergeTestResults: ${{ parameters.mergeTestResults }} continueOnError: true condition: always() - ${{ if or(and(eq(parameters.enablePublishTestResults, 'true'), eq(parameters.testResultsFormat, '')), eq(parameters.testResultsFormat, 'vstest')) }}: - task: PublishTestResults@2 displayName: Publish TRX Test Results inputs: testResultsFormat: 'VSTest' testResultsFiles: '*.trx' searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)' testRunTitle: ${{ coalesce(parameters.testRunTitle, parameters.name, '$(System.JobName)') }}-trx mergeTestResults: ${{ parameters.mergeTestResults }} continueOnError: true condition: always() # gather artifacts - ${{ if ne(parameters.artifacts.publish, '') }}: - ${{ if and(ne(parameters.artifacts.publish.artifacts, 'false'), ne(parameters.artifacts.publish.artifacts, '')) }}: - task: CopyFiles@2 displayName: Gather binaries for publish to artifacts inputs: SourceFolder: 'artifacts/bin' Contents: '**' TargetFolder: '$(Build.ArtifactStagingDirectory)/artifacts/bin' - task: CopyFiles@2 displayName: Gather packages for publish to artifacts inputs: SourceFolder: 'artifacts/packages' Contents: '**' TargetFolder: '$(Build.ArtifactStagingDirectory)/artifacts/packages' - ${{ if and(ne(parameters.artifacts.publish.logs, 'false'), ne(parameters.artifacts.publish.logs, '')) }}: - task: CopyFiles@2 displayName: Gather logs for publish to artifacts inputs: SourceFolder: 'artifacts/log' Contents: '**' TargetFolder: '$(Build.ArtifactStagingDirectory)/artifacts/log' continueOnError: true condition: always() - ${{ if eq(parameters.enablePublishBuildArtifacts, 'true') }}: - task: CopyFiles@2 displayName: Gather logs for publish to artifacts inputs: SourceFolder: 'artifacts/log/$(_BuildConfig)' Contents: '**' TargetFolder: '$(Build.ArtifactStagingDirectory)/artifacts/log/$(_BuildConfig)' continueOnError: true condition: always() - ${{ if eq(parameters.enableBuildRetry, 'true') }}: - task: CopyFiles@2 displayName: Gather buildconfiguration for build retry inputs: SourceFolder: '$(Build.SourcesDirectory)/eng/common/BuildConfiguration' Contents: '**' TargetFolder: '$(Build.ArtifactStagingDirectory)/eng/common/BuildConfiguration' continueOnError: true condition: always() - ${{ each step in parameters.artifactPublishSteps }}: - ${{ step }} ================================================ FILE: eng/common/core-templates/job/onelocbuild.yml ================================================ parameters: # Optional: dependencies of the job dependsOn: '' # Optional: A defined YAML pool - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#pool pool: '' CeapexPat: $(dn-bot-ceapex-package-r) # PAT for the loc AzDO instance https://dev.azure.com/ceapex GithubPat: $(BotAccount-dotnet-bot-repo-PAT) SourcesDirectory: $(Build.SourcesDirectory) CreatePr: true AutoCompletePr: false ReusePr: true UseLfLineEndings: true UseCheckedInLocProjectJson: false SkipLocProjectJsonGeneration: false LanguageSet: VS_Main_Languages LclSource: lclFilesInRepo LclPackageId: '' RepoType: gitHub GitHubOrg: dotnet MirrorRepo: '' MirrorBranch: main condition: '' JobNameSuffix: '' is1ESPipeline: '' jobs: - job: OneLocBuild${{ parameters.JobNameSuffix }} dependsOn: ${{ parameters.dependsOn }} displayName: OneLocBuild${{ parameters.JobNameSuffix }} variables: - group: OneLocBuildVariables # Contains the CeapexPat and GithubPat - name: _GenerateLocProjectArguments value: -SourcesDirectory ${{ parameters.SourcesDirectory }} -LanguageSet "${{ parameters.LanguageSet }}" -CreateNeutralXlfs - ${{ if eq(parameters.UseCheckedInLocProjectJson, 'true') }}: - name: _GenerateLocProjectArguments value: ${{ variables._GenerateLocProjectArguments }} -UseCheckedInLocProjectJson - template: /eng/common/core-templates/variables/pool-providers.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} ${{ if ne(parameters.pool, '') }}: pool: ${{ parameters.pool }} ${{ if eq(parameters.pool, '') }}: pool: # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: name: AzurePipelines-EO image: 1ESPT-Windows2022 demands: Cmd os: windows # If it's not devdiv, it's dnceng ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}: name: $(DncEngInternalBuildPool) image: 1es-windows-2022 os: windows steps: - ${{ if eq(parameters.is1ESPipeline, '') }}: - 'Illegal entry point, is1ESPipeline is not defined. Repository yaml should not directly reference templates in core-templates folder.': error - ${{ if ne(parameters.SkipLocProjectJsonGeneration, 'true') }}: - task: Powershell@2 inputs: filePath: $(Build.SourcesDirectory)/eng/common/generate-locproject.ps1 arguments: $(_GenerateLocProjectArguments) displayName: Generate LocProject.json condition: ${{ parameters.condition }} - task: OneLocBuild@2 displayName: OneLocBuild env: SYSTEM_ACCESSTOKEN: $(System.AccessToken) inputs: locProj: eng/Localize/LocProject.json outDir: $(Build.ArtifactStagingDirectory) lclSource: ${{ parameters.LclSource }} lclPackageId: ${{ parameters.LclPackageId }} isCreatePrSelected: ${{ parameters.CreatePr }} isAutoCompletePrSelected: ${{ parameters.AutoCompletePr }} ${{ if eq(parameters.CreatePr, true) }}: isUseLfLineEndingsSelected: ${{ parameters.UseLfLineEndings }} ${{ if eq(parameters.RepoType, 'gitHub') }}: isShouldReusePrSelected: ${{ parameters.ReusePr }} packageSourceAuth: patAuth patVariable: ${{ parameters.CeapexPat }} ${{ if eq(parameters.RepoType, 'gitHub') }}: repoType: ${{ parameters.RepoType }} gitHubPatVariable: "${{ parameters.GithubPat }}" ${{ if ne(parameters.MirrorRepo, '') }}: isMirrorRepoSelected: true gitHubOrganization: ${{ parameters.GitHubOrg }} mirrorRepo: ${{ parameters.MirrorRepo }} mirrorBranch: ${{ parameters.MirrorBranch }} condition: ${{ parameters.condition }} - template: /eng/common/core-templates/steps/publish-build-artifacts.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} args: displayName: Publish Localization Files pathToPublish: '$(Build.ArtifactStagingDirectory)/loc' publishLocation: Container artifactName: Loc condition: ${{ parameters.condition }} - template: /eng/common/core-templates/steps/publish-build-artifacts.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} args: displayName: Publish LocProject.json pathToPublish: '$(Build.SourcesDirectory)/eng/Localize/' publishLocation: Container artifactName: Loc condition: ${{ parameters.condition }} ================================================ FILE: eng/common/core-templates/job/publish-build-assets.yml ================================================ parameters: configuration: 'Debug' # Optional: condition for the job to run condition: '' # Optional: 'true' if future jobs should run even if this job fails continueOnError: false # Optional: dependencies of the job dependsOn: '' # Optional: Include PublishBuildArtifacts task enablePublishBuildArtifacts: false # Optional: A defined YAML pool - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#pool pool: {} # Optional: should run as a public build even in the internal project # if 'true', the build won't run any of the internal only steps, even if it is running in non-public projects. runAsPublic: false # Optional: whether the build's artifacts will be published using release pipelines or direct feed publishing publishUsingPipelines: false # Optional: whether the build's artifacts will be published using release pipelines or direct feed publishing publishAssetsImmediately: false artifactsPublishingAdditionalParameters: '' signingValidationAdditionalParameters: '' is1ESPipeline: '' jobs: - job: Asset_Registry_Publish dependsOn: ${{ parameters.dependsOn }} timeoutInMinutes: 150 ${{ if eq(parameters.publishAssetsImmediately, 'true') }}: displayName: Publish Assets ${{ else }}: displayName: Publish to Build Asset Registry variables: - template: /eng/common/core-templates/variables/pool-providers.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - group: Publish-Build-Assets - group: AzureDevOps-Artifact-Feeds-Pats - name: runCodesignValidationInjection value: false # unconditional - needed for logs publishing (redactor tool version) - template: /eng/common/core-templates/post-build/common-variables.yml pool: # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: name: AzurePipelines-EO image: 1ESPT-Windows2022 demands: Cmd os: windows # If it's not devdiv, it's dnceng ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}: name: NetCore1ESPool-Publishing-Internal image: windows.vs2019.amd64 os: windows steps: - ${{ if eq(parameters.is1ESPipeline, '') }}: - 'Illegal entry point, is1ESPipeline is not defined. Repository yaml should not directly reference templates in core-templates folder.': error - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - checkout: self fetchDepth: 3 clean: true - task: DownloadBuildArtifacts@0 displayName: Download artifact inputs: artifactName: AssetManifests downloadPath: '$(Build.StagingDirectory)/Download' checkDownloadedFiles: true condition: ${{ parameters.condition }} continueOnError: ${{ parameters.continueOnError }} - task: NuGetAuthenticate@1 - task: AzureCLI@2 displayName: Publish Build Assets inputs: azureSubscription: "Darc: Maestro Production" scriptType: ps scriptLocation: scriptPath scriptPath: $(Build.SourcesDirectory)/eng/common/sdk-task.ps1 arguments: -task PublishBuildAssets -restore -msbuildEngine dotnet /p:ManifestsPath='$(Build.StagingDirectory)/Download/AssetManifests' /p:MaestroApiEndpoint=https://maestro.dot.net /p:PublishUsingPipelines=${{ parameters.publishUsingPipelines }} /p:OfficialBuildId=$(Build.BuildNumber) condition: ${{ parameters.condition }} continueOnError: ${{ parameters.continueOnError }} - task: powershell@2 displayName: Create ReleaseConfigs Artifact inputs: targetType: inline script: | New-Item -Path "$(Build.StagingDirectory)/ReleaseConfigs" -ItemType Directory -Force $filePath = "$(Build.StagingDirectory)/ReleaseConfigs/ReleaseConfigs.txt" Add-Content -Path $filePath -Value $(BARBuildId) Add-Content -Path $filePath -Value "$(DefaultChannels)" Add-Content -Path $filePath -Value $(IsStableBuild) $symbolExclusionfile = "$(Build.SourcesDirectory)/eng/SymbolPublishingExclusionsFile.txt" if (Test-Path -Path $symbolExclusionfile) { Write-Host "SymbolExclusionFile exists" Copy-Item -Path $symbolExclusionfile -Destination "$(Build.StagingDirectory)/ReleaseConfigs" } - template: /eng/common/core-templates/steps/publish-build-artifacts.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} args: displayName: Publish ReleaseConfigs Artifact pathToPublish: '$(Build.StagingDirectory)/ReleaseConfigs' publishLocation: Container artifactName: ReleaseConfigs - ${{ if eq(parameters.publishAssetsImmediately, 'true') }}: - template: /eng/common/core-templates/post-build/setup-maestro-vars.yml parameters: BARBuildId: ${{ parameters.BARBuildId }} PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} is1ESPipeline: ${{ parameters.is1ESPipeline }} - task: AzureCLI@2 displayName: Publish Using Darc inputs: azureSubscription: "Darc: Maestro Production" scriptType: ps scriptLocation: scriptPath scriptPath: $(Build.SourcesDirectory)/eng/common/post-build/publish-using-darc.ps1 arguments: > -BuildId $(BARBuildId) -PublishingInfraVersion 3 -AzdoToken '$(System.AccessToken)' -WaitPublishingFinish true -ArtifactsPublishingAdditionalParameters '${{ parameters.artifactsPublishingAdditionalParameters }}' -SymbolPublishingAdditionalParameters '${{ parameters.symbolPublishingAdditionalParameters }}' - ${{ if eq(parameters.enablePublishBuildArtifacts, 'true') }}: - template: /eng/common/core-templates/steps/publish-logs.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} JobLabel: 'Publish_Artifacts_Logs' ================================================ FILE: eng/common/core-templates/job/source-build.yml ================================================ parameters: # This template adds arcade-powered source-build to CI. The template produces a server job with a # default ID 'Source_Build_Complete' to put in a dependency list if necessary. # Specifies the prefix for source-build jobs added to pipeline. Use this if disambiguation needed. jobNamePrefix: 'Source_Build' # Defines the platform on which to run the job. By default, a linux-x64 machine, suitable for # managed-only repositories. This is an object with these properties: # # name: '' # The name of the job. This is included in the job ID. # targetRID: '' # The name of the target RID to use, instead of the one auto-detected by Arcade. # portableBuild: false # Enables non-portable mode. This means a more specific RID (e.g. fedora.32-x64 rather than # linux-x64), and compiling against distro-provided packages rather than portable ones. The # default is portable mode. # skipPublishValidation: false # Disables publishing validation. By default, a check is performed to ensure no packages are # published by source-build. # container: '' # A container to use. Runs in docker. # pool: {} # A pool to use. Runs directly on an agent. # buildScript: '' # Specifies the build script to invoke to perform the build in the repo. The default # './build.sh' should work for typical Arcade repositories, but this is customizable for # difficult situations. # jobProperties: {} # A list of job properties to inject at the top level, for potential extensibility beyond # container and pool. platform: {} is1ESPipeline: '' # If set to true and running on a non-public project, # Internal nuget and blob storage locations will be enabled. # This is not enabled by default because many repositories do not need internal sources # and do not need to have the required service connections approved in the pipeline. enableInternalSources: false jobs: - job: ${{ parameters.jobNamePrefix }}_${{ parameters.platform.name }} displayName: Source-Build (${{ parameters.platform.name }}) ${{ each property in parameters.platform.jobProperties }}: ${{ property.key }}: ${{ property.value }} ${{ if ne(parameters.platform.container, '') }}: container: ${{ parameters.platform.container }} ${{ if eq(parameters.platform.pool, '') }}: # The default VM host AzDO pool. This should be capable of running Docker containers: almost all # source-build builds run in Docker, including the default managed platform. # /eng/common/core-templates/variables/pool-providers.yml can't be used here (some customers declare variables already), so duplicate its logic ${{ if eq(parameters.is1ESPipeline, 'true') }}: pool: ${{ if eq(variables['System.TeamProject'], 'public') }}: name: $[replace(replace(eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), True, 'NetCore-Svc-Public' ), False, 'NetCore-Public')] demands: ImageOverride -equals build.ubuntu.2004.amd64 ${{ if eq(variables['System.TeamProject'], 'internal') }}: name: $[replace(replace(eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), True, 'NetCore1ESPool-Svc-Internal'), False, 'NetCore1ESPool-Internal')] image: 1es-mariner-2 os: linux ${{ else }}: pool: ${{ if eq(variables['System.TeamProject'], 'public') }}: name: $[replace(replace(eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), True, 'NetCore-Svc-Public' ), False, 'NetCore-Public')] demands: ImageOverride -equals Build.Ubuntu.2204.Amd64.Open ${{ if eq(variables['System.TeamProject'], 'internal') }}: name: $[replace(replace(eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), True, 'NetCore1ESPool-Svc-Internal'), False, 'NetCore1ESPool-Internal')] demands: ImageOverride -equals Build.Ubuntu.2204.Amd64 ${{ if ne(parameters.platform.pool, '') }}: pool: ${{ parameters.platform.pool }} workspace: clean: all steps: - ${{ if eq(parameters.is1ESPipeline, '') }}: - 'Illegal entry point, is1ESPipeline is not defined. Repository yaml should not directly reference templates in core-templates folder.': error - ${{ if eq(parameters.enableInternalSources, true) }}: - template: /eng/common/core-templates/steps/enable-internal-sources.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} - template: /eng/common/core-templates/steps/enable-internal-runtimes.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} - template: /eng/common/core-templates/steps/source-build.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} platform: ${{ parameters.platform }} ================================================ FILE: eng/common/core-templates/job/source-index-stage1.yml ================================================ parameters: runAsPublic: false sourceIndexBuildCommand: powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "eng/common/build.ps1 -restore -build -binarylog -ci" preSteps: [] binlogPath: artifacts/log/Debug/Build.binlog condition: '' dependsOn: '' pool: '' is1ESPipeline: '' jobs: - job: SourceIndexStage1 dependsOn: ${{ parameters.dependsOn }} condition: ${{ parameters.condition }} variables: - name: BinlogPath value: ${{ parameters.binlogPath }} - template: /eng/common/core-templates/variables/pool-providers.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} ${{ if ne(parameters.pool, '') }}: pool: ${{ parameters.pool }} ${{ if eq(parameters.pool, '') }}: pool: ${{ if eq(variables['System.TeamProject'], 'public') }}: name: $(DncEngPublicBuildPool) image: windows.vs2022.amd64.open ${{ if eq(variables['System.TeamProject'], 'internal') }}: name: $(DncEngInternalBuildPool) image: windows.vs2022.amd64 steps: - ${{ if eq(parameters.is1ESPipeline, '') }}: - 'Illegal entry point, is1ESPipeline is not defined. Repository yaml should not directly reference templates in core-templates folder.': error - ${{ each preStep in parameters.preSteps }}: - ${{ preStep }} - script: ${{ parameters.sourceIndexBuildCommand }} displayName: Build Repository - template: /eng/common/core-templates/steps/source-index-stage1-publish.yml parameters: binLogPath: ${{ parameters.binLogPath }} ================================================ FILE: eng/common/core-templates/jobs/codeql-build.yml ================================================ parameters: # See schema documentation in /Documentation/AzureDevOps/TemplateSchema.md continueOnError: false # Required: A collection of jobs to run - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#job jobs: [] # Optional: if specified, restore and use this version of Guardian instead of the default. overrideGuardianVersion: '' is1ESPipeline: '' jobs: - template: /eng/common/core-templates/jobs/jobs.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} enableMicrobuild: false enablePublishBuildArtifacts: false enablePublishTestResults: false enablePublishBuildAssets: false enablePublishUsingPipelines: false enableTelemetry: true variables: - group: Publish-Build-Assets # The Guardian version specified in 'eng/common/sdl/packages.config'. This value must be kept in # sync with the packages.config file. - name: DefaultGuardianVersion value: 0.109.0 - name: GuardianPackagesConfigFile value: $(Build.SourcesDirectory)\eng\common\sdl\packages.config - name: GuardianVersion value: ${{ coalesce(parameters.overrideGuardianVersion, '$(DefaultGuardianVersion)') }} jobs: ${{ parameters.jobs }} ================================================ FILE: eng/common/core-templates/jobs/jobs.yml ================================================ parameters: # See schema documentation in /Documentation/AzureDevOps/TemplateSchema.md continueOnError: false # Optional: Include PublishBuildArtifacts task enablePublishBuildArtifacts: false # Optional: Enable publishing using release pipelines enablePublishUsingPipelines: false # Optional: Enable running the source-build jobs to build repo from source enableSourceBuild: false # Optional: Parameters for source-build template. # See /eng/common/core-templates/jobs/source-build.yml for options sourceBuildParameters: [] graphFileGeneration: # Optional: Enable generating the graph files at the end of the build enabled: false # Optional: Include toolset dependencies in the generated graph files includeToolset: false # Required: A collection of jobs to run - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#job jobs: [] # Optional: Override automatically derived dependsOn value for "publish build assets" job publishBuildAssetsDependsOn: '' # Optional: Publish the assets as soon as the publish to BAR stage is complete, rather doing so in a separate stage. publishAssetsImmediately: false # Optional: If using publishAssetsImmediately and additional parameters are needed, can be used to send along additional parameters (normally sent to post-build.yml) artifactsPublishingAdditionalParameters: '' signingValidationAdditionalParameters: '' # Optional: should run as a public build even in the internal project # if 'true', the build won't run any of the internal only steps, even if it is running in non-public projects. runAsPublic: false enableSourceIndex: false sourceIndexParams: {} artifacts: {} is1ESPipeline: '' # Internal resources (telemetry, microbuild) can only be accessed from non-public projects, # and some (Microbuild) should only be applied to non-PR cases for internal builds. jobs: - ${{ each job in parameters.jobs }}: - ${{ if eq(parameters.is1ESPipeline, 'true') }}: - template: /eng/common/templates-official/job/job.yml parameters: # pass along parameters ${{ each parameter in parameters }}: ${{ if ne(parameter.key, 'jobs') }}: ${{ parameter.key }}: ${{ parameter.value }} # pass along job properties ${{ each property in job }}: ${{ if ne(property.key, 'job') }}: ${{ property.key }}: ${{ property.value }} name: ${{ job.job }} - ${{ else }}: - template: /eng/common/templates/job/job.yml parameters: # pass along parameters ${{ each parameter in parameters }}: ${{ if ne(parameter.key, 'jobs') }}: ${{ parameter.key }}: ${{ parameter.value }} # pass along job properties ${{ each property in job }}: ${{ if ne(property.key, 'job') }}: ${{ property.key }}: ${{ property.value }} name: ${{ job.job }} - ${{ if eq(parameters.enableSourceBuild, true) }}: - template: /eng/common/core-templates/jobs/source-build.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} allCompletedJobId: Source_Build_Complete ${{ each parameter in parameters.sourceBuildParameters }}: ${{ parameter.key }}: ${{ parameter.value }} - ${{ if eq(parameters.enableSourceIndex, 'true') }}: - template: ../job/source-index-stage1.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} runAsPublic: ${{ parameters.runAsPublic }} ${{ each parameter in parameters.sourceIndexParams }}: ${{ parameter.key }}: ${{ parameter.value }} - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - ${{ if or(eq(parameters.enablePublishBuildAssets, true), eq(parameters.artifacts.publish.manifests, 'true'), ne(parameters.artifacts.publish.manifests, '')) }}: - template: ../job/publish-build-assets.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} continueOnError: ${{ parameters.continueOnError }} dependsOn: - ${{ if ne(parameters.publishBuildAssetsDependsOn, '') }}: - ${{ each job in parameters.publishBuildAssetsDependsOn }}: - ${{ job.job }} - ${{ if eq(parameters.publishBuildAssetsDependsOn, '') }}: - ${{ each job in parameters.jobs }}: - ${{ job.job }} - ${{ if eq(parameters.enableSourceBuild, true) }}: - Source_Build_Complete runAsPublic: ${{ parameters.runAsPublic }} publishUsingPipelines: ${{ parameters.enablePublishUsingPipelines }} publishAssetsImmediately: ${{ parameters.publishAssetsImmediately }} enablePublishBuildArtifacts: ${{ parameters.enablePublishBuildArtifacts }} artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} signingValidationAdditionalParameters: ${{ parameters.signingValidationAdditionalParameters }} ================================================ FILE: eng/common/core-templates/jobs/source-build.yml ================================================ parameters: # This template adds arcade-powered source-build to CI. A job is created for each platform, as # well as an optional server job that completes when all platform jobs complete. # The name of the "join" job for all source-build platforms. If set to empty string, the job is # not included. Existing repo pipelines can use this job depend on all source-build jobs # completing without maintaining a separate list of every single job ID: just depend on this one # server job. By default, not included. Recommended name if used: 'Source_Build_Complete'. allCompletedJobId: '' # See /eng/common/core-templates/job/source-build.yml jobNamePrefix: 'Source_Build' # This is the default platform provided by Arcade, intended for use by a managed-only repo. defaultManagedPlatform: name: 'Managed' container: 'mcr.microsoft.com/dotnet-buildtools/prereqs:centos-stream9' # Defines the platforms on which to run build jobs. One job is created for each platform, and the # object in this array is sent to the job template as 'platform'. If no platforms are specified, # one job runs on 'defaultManagedPlatform'. platforms: [] is1ESPipeline: '' # If set to true and running on a non-public project, # Internal nuget and blob storage locations will be enabled. # This is not enabled by default because many repositories do not need internal sources # and do not need to have the required service connections approved in the pipeline. enableInternalSources: false jobs: - ${{ if ne(parameters.allCompletedJobId, '') }}: - job: ${{ parameters.allCompletedJobId }} displayName: Source-Build Complete pool: server dependsOn: - ${{ each platform in parameters.platforms }}: - ${{ parameters.jobNamePrefix }}_${{ platform.name }} - ${{ if eq(length(parameters.platforms), 0) }}: - ${{ parameters.jobNamePrefix }}_${{ parameters.defaultManagedPlatform.name }} - ${{ each platform in parameters.platforms }}: - template: /eng/common/core-templates/job/source-build.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} jobNamePrefix: ${{ parameters.jobNamePrefix }} platform: ${{ platform }} enableInternalSources: ${{ parameters.enableInternalSources }} - ${{ if eq(length(parameters.platforms), 0) }}: - template: /eng/common/core-templates/job/source-build.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} jobNamePrefix: ${{ parameters.jobNamePrefix }} platform: ${{ parameters.defaultManagedPlatform }} enableInternalSources: ${{ parameters.enableInternalSources }} ================================================ FILE: eng/common/core-templates/post-build/common-variables.yml ================================================ variables: - group: Publish-Build-Assets # Whether the build is internal or not - name: IsInternalBuild value: ${{ and(ne(variables['System.TeamProject'], 'public'), contains(variables['Build.SourceBranch'], 'internal')) }} # Default Maestro++ API Endpoint and API Version - name: MaestroApiEndPoint value: "https://maestro.dot.net" - name: MaestroApiVersion value: "2020-02-20" - name: SourceLinkCLIVersion value: 3.0.0 - name: SymbolToolVersion value: 1.0.1 - name: BinlogToolVersion value: 1.0.11 - name: runCodesignValidationInjection value: false ================================================ FILE: eng/common/core-templates/post-build/post-build.yml ================================================ parameters: # Which publishing infra should be used. THIS SHOULD MATCH THE VERSION ON THE BUILD MANIFEST. # Publishing V1 is no longer supported # Publishing V2 is no longer supported # Publishing V3 is the default - name: publishingInfraVersion displayName: Which version of publishing should be used to promote the build definition? type: number default: 3 values: - 3 - name: BARBuildId displayName: BAR Build Id type: number default: 0 - name: PromoteToChannelIds displayName: Channel to promote BARBuildId to type: string default: '' - name: enableSourceLinkValidation displayName: Enable SourceLink validation type: boolean default: false - name: enableSigningValidation displayName: Enable signing validation type: boolean default: true - name: enableSymbolValidation displayName: Enable symbol validation type: boolean default: false - name: enableNugetValidation displayName: Enable NuGet validation type: boolean default: true - name: publishInstallersAndChecksums displayName: Publish installers and checksums type: boolean default: true - name: requireDefaultChannels displayName: Fail the build if there are no default channel(s) registrations for the current build type: boolean default: false - name: SDLValidationParameters type: object default: enable: false publishGdn: false continueOnError: false params: '' artifactNames: '' downloadArtifacts: true # These parameters let the user customize the call to sdk-task.ps1 for publishing # symbols & general artifacts as well as for signing validation - name: symbolPublishingAdditionalParameters displayName: Symbol publishing additional parameters type: string default: '' - name: artifactsPublishingAdditionalParameters displayName: Artifact publishing additional parameters type: string default: '' - name: signingValidationAdditionalParameters displayName: Signing validation additional parameters type: string default: '' # Which stages should finish execution before post-build stages start - name: validateDependsOn type: object default: - build - name: publishDependsOn type: object default: - Validate # Optional: Call asset publishing rather than running in a separate stage - name: publishAssetsImmediately type: boolean default: false - name: is1ESPipeline type: boolean default: false stages: - ${{ if or(eq( parameters.enableNugetValidation, 'true'), eq(parameters.enableSigningValidation, 'true'), eq(parameters.enableSourceLinkValidation, 'true'), eq(parameters.SDLValidationParameters.enable, 'true')) }}: - stage: Validate dependsOn: ${{ parameters.validateDependsOn }} displayName: Validate Build Assets variables: - template: /eng/common/core-templates/post-build/common-variables.yml - template: /eng/common/core-templates/variables/pool-providers.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} jobs: - job: displayName: NuGet Validation condition: and(succeededOrFailed(), eq( ${{ parameters.enableNugetValidation }}, 'true')) pool: # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: name: AzurePipelines-EO image: 1ESPT-Windows2022 demands: Cmd os: windows # If it's not devdiv, it's dnceng ${{ else }}: ${{ if eq(parameters.is1ESPipeline, true) }}: name: $(DncEngInternalBuildPool) image: windows.vs2022.amd64 os: windows ${{ else }}: name: $(DncEngInternalBuildPool) demands: ImageOverride -equals windows.vs2022.amd64 steps: - template: /eng/common/core-templates/post-build/setup-maestro-vars.yml parameters: BARBuildId: ${{ parameters.BARBuildId }} PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} is1ESPipeline: ${{ parameters.is1ESPipeline }} - task: DownloadBuildArtifacts@0 displayName: Download Package Artifacts inputs: buildType: specific buildVersionToDownload: specific project: $(AzDOProjectName) pipeline: $(AzDOPipelineId) buildId: $(AzDOBuildId) artifactName: PackageArtifacts checkDownloadedFiles: true - task: PowerShell@2 displayName: Validate inputs: filePath: $(Build.SourcesDirectory)/eng/common/post-build/nuget-validation.ps1 arguments: -PackagesPath $(Build.ArtifactStagingDirectory)/PackageArtifacts/ - job: displayName: Signing Validation condition: and( eq( ${{ parameters.enableSigningValidation }}, 'true'), ne( variables['PostBuildSign'], 'true')) pool: # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: name: AzurePipelines-EO image: 1ESPT-Windows2022 demands: Cmd os: windows # If it's not devdiv, it's dnceng ${{ else }}: ${{ if eq(parameters.is1ESPipeline, true) }}: name: $(DncEngInternalBuildPool) image: 1es-windows-2022 os: windows ${{ else }}: name: $(DncEngInternalBuildPool) demands: ImageOverride -equals windows.vs2022.amd64 steps: - template: /eng/common/core-templates/post-build/setup-maestro-vars.yml parameters: BARBuildId: ${{ parameters.BARBuildId }} PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} is1ESPipeline: ${{ parameters.is1ESPipeline }} - task: DownloadBuildArtifacts@0 displayName: Download Package Artifacts inputs: buildType: specific buildVersionToDownload: specific project: $(AzDOProjectName) pipeline: $(AzDOPipelineId) buildId: $(AzDOBuildId) artifactName: PackageArtifacts checkDownloadedFiles: true itemPattern: | ** !**/Microsoft.SourceBuild.Intermediate.*.nupkg # This is necessary whenever we want to publish/restore to an AzDO private feed # Since sdk-task.ps1 tries to restore packages we need to do this authentication here # otherwise it'll complain about accessing a private feed. - task: NuGetAuthenticate@1 displayName: 'Authenticate to AzDO Feeds' # Signing validation will optionally work with the buildmanifest file which is downloaded from # Azure DevOps above. - task: PowerShell@2 displayName: Validate inputs: filePath: eng\common\sdk-task.ps1 arguments: -task SigningValidation -restore -msbuildEngine vs /p:PackageBasePath='$(Build.ArtifactStagingDirectory)/PackageArtifacts' /p:SignCheckExclusionsFile='$(Build.SourcesDirectory)/eng/SignCheckExclusionsFile.txt' ${{ parameters.signingValidationAdditionalParameters }} - template: /eng/common/core-templates/steps/publish-logs.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} StageLabel: 'Validation' JobLabel: 'Signing' BinlogToolVersion: $(BinlogToolVersion) - job: displayName: SourceLink Validation condition: eq( ${{ parameters.enableSourceLinkValidation }}, 'true') pool: # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: name: AzurePipelines-EO image: 1ESPT-Windows2022 demands: Cmd os: windows # If it's not devdiv, it's dnceng ${{ else }}: ${{ if eq(parameters.is1ESPipeline, true) }}: name: $(DncEngInternalBuildPool) image: 1es-windows-2022 os: windows ${{ else }}: name: $(DncEngInternalBuildPool) demands: ImageOverride -equals windows.vs2022.amd64 steps: - template: /eng/common/core-templates/post-build/setup-maestro-vars.yml parameters: BARBuildId: ${{ parameters.BARBuildId }} PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} is1ESPipeline: ${{ parameters.is1ESPipeline }} - task: DownloadBuildArtifacts@0 displayName: Download Blob Artifacts inputs: buildType: specific buildVersionToDownload: specific project: $(AzDOProjectName) pipeline: $(AzDOPipelineId) buildId: $(AzDOBuildId) artifactName: BlobArtifacts checkDownloadedFiles: true - task: PowerShell@2 displayName: Validate inputs: filePath: $(Build.SourcesDirectory)/eng/common/post-build/sourcelink-validation.ps1 arguments: -InputPath $(Build.ArtifactStagingDirectory)/BlobArtifacts/ -ExtractPath $(Agent.BuildDirectory)/Extract/ -GHRepoName $(Build.Repository.Name) -GHCommit $(Build.SourceVersion) -SourcelinkCliVersion $(SourceLinkCLIVersion) continueOnError: true - ${{ if ne(parameters.publishAssetsImmediately, 'true') }}: - stage: publish_using_darc ${{ if or(eq(parameters.enableNugetValidation, 'true'), eq(parameters.enableSigningValidation, 'true'), eq(parameters.enableSourceLinkValidation, 'true'), eq(parameters.SDLValidationParameters.enable, 'true')) }}: dependsOn: ${{ parameters.publishDependsOn }} ${{ else }}: dependsOn: ${{ parameters.validateDependsOn }} displayName: Publish using Darc variables: - template: /eng/common/core-templates/post-build/common-variables.yml - template: /eng/common/core-templates/variables/pool-providers.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} jobs: - job: displayName: Publish Using Darc timeoutInMinutes: 120 pool: # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: name: AzurePipelines-EO image: 1ESPT-Windows2022 demands: Cmd os: windows # If it's not devdiv, it's dnceng ${{ else }}: ${{ if eq(parameters.is1ESPipeline, true) }}: name: NetCore1ESPool-Publishing-Internal image: windows.vs2019.amd64 os: windows ${{ else }}: name: NetCore1ESPool-Publishing-Internal demands: ImageOverride -equals windows.vs2019.amd64 steps: - template: /eng/common/core-templates/post-build/setup-maestro-vars.yml parameters: BARBuildId: ${{ parameters.BARBuildId }} PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} is1ESPipeline: ${{ parameters.is1ESPipeline }} - task: NuGetAuthenticate@1 - task: AzureCLI@2 displayName: Publish Using Darc inputs: azureSubscription: "Darc: Maestro Production" scriptType: ps scriptLocation: scriptPath scriptPath: $(Build.SourcesDirectory)/eng/common/post-build/publish-using-darc.ps1 arguments: > -BuildId $(BARBuildId) -PublishingInfraVersion ${{ parameters.publishingInfraVersion }} -AzdoToken '$(System.AccessToken)' -WaitPublishingFinish true -RequireDefaultChannels ${{ parameters.requireDefaultChannels }} -ArtifactsPublishingAdditionalParameters '${{ parameters.artifactsPublishingAdditionalParameters }}' -SymbolPublishingAdditionalParameters '${{ parameters.symbolPublishingAdditionalParameters }}' ================================================ FILE: eng/common/core-templates/post-build/setup-maestro-vars.yml ================================================ parameters: BARBuildId: '' PromoteToChannelIds: '' is1ESPipeline: '' steps: - ${{ if eq(parameters.is1ESPipeline, '') }}: - 'Illegal entry point, is1ESPipeline is not defined. Repository yaml should not directly reference templates in core-templates folder.': error - ${{ if eq(coalesce(parameters.PromoteToChannelIds, 0), 0) }}: - task: DownloadBuildArtifacts@0 displayName: Download Release Configs inputs: buildType: current artifactName: ReleaseConfigs checkDownloadedFiles: true - task: AzureCLI@2 name: setReleaseVars displayName: Set Release Configs Vars inputs: azureSubscription: "Darc: Maestro Production" scriptType: pscore scriptLocation: inlineScript inlineScript: | try { if (!$Env:PromoteToMaestroChannels -or $Env:PromoteToMaestroChannels.Trim() -eq '') { $Content = Get-Content $(Build.StagingDirectory)/ReleaseConfigs/ReleaseConfigs.txt $BarId = $Content | Select -Index 0 $Channels = $Content | Select -Index 1 $IsStableBuild = $Content | Select -Index 2 $AzureDevOpsProject = $Env:System_TeamProject $AzureDevOpsBuildDefinitionId = $Env:System_DefinitionId $AzureDevOpsBuildId = $Env:Build_BuildId } else { . $(Build.SourcesDirectory)\eng\common\tools.ps1 $darc = Get-Darc $buildInfo = & $darc get-build ` --id ${{ parameters.BARBuildId }} ` --extended ` --output-format json ` --ci ` | convertFrom-Json $BarId = ${{ parameters.BARBuildId }} $Channels = $Env:PromoteToMaestroChannels -split "," $Channels = $Channels -join "][" $Channels = "[$Channels]" $IsStableBuild = $buildInfo.stable $AzureDevOpsProject = $buildInfo.azureDevOpsProject $AzureDevOpsBuildDefinitionId = $buildInfo.azureDevOpsBuildDefinitionId $AzureDevOpsBuildId = $buildInfo.azureDevOpsBuildId } Write-Host "##vso[task.setvariable variable=BARBuildId]$BarId" Write-Host "##vso[task.setvariable variable=TargetChannels]$Channels" Write-Host "##vso[task.setvariable variable=IsStableBuild]$IsStableBuild" Write-Host "##vso[task.setvariable variable=AzDOProjectName]$AzureDevOpsProject" Write-Host "##vso[task.setvariable variable=AzDOPipelineId]$AzureDevOpsBuildDefinitionId" Write-Host "##vso[task.setvariable variable=AzDOBuildId]$AzureDevOpsBuildId" } catch { Write-Host $_ Write-Host $_.Exception Write-Host $_.ScriptStackTrace exit 1 } env: PromoteToMaestroChannels: ${{ parameters.PromoteToChannelIds }} ================================================ FILE: eng/common/core-templates/steps/cleanup-microbuild.yml ================================================ parameters: # Enable cleanup tasks for MicroBuild enableMicrobuild: false # Enable cleanup tasks for MicroBuild on Mac and Linux # Will be ignored if 'enableMicrobuild' is false or 'Agent.Os' is 'Windows_NT' enableMicrobuildForMacAndLinux: false continueOnError: false steps: - ${{ if eq(parameters.enableMicrobuild, 'true') }}: - task: MicroBuildCleanup@1 displayName: Execute Microbuild cleanup tasks condition: and( always(), or( and( eq(variables['Agent.Os'], 'Windows_NT'), in(variables['_SignType'], 'real', 'test') ), and( ${{ eq(parameters.enableMicrobuildForMacAndLinux, true) }}, ne(variables['Agent.Os'], 'Windows_NT'), eq(variables['_SignType'], 'real') ) )) continueOnError: ${{ parameters.continueOnError }} env: TeamName: $(_TeamName) ================================================ FILE: eng/common/core-templates/steps/component-governance.yml ================================================ parameters: disableComponentGovernance: false componentGovernanceIgnoreDirectories: '' is1ESPipeline: false displayName: 'Component Detection' steps: - ${{ if eq(parameters.disableComponentGovernance, 'true') }}: - script: echo "##vso[task.setvariable variable=skipComponentGovernanceDetection]true" displayName: Set skipComponentGovernanceDetection variable - ${{ if ne(parameters.disableComponentGovernance, 'true') }}: - task: ComponentGovernanceComponentDetection@0 continueOnError: true displayName: ${{ parameters.displayName }} inputs: ignoreDirectories: ${{ parameters.componentGovernanceIgnoreDirectories }} ================================================ FILE: eng/common/core-templates/steps/enable-internal-runtimes.yml ================================================ # Obtains internal runtime download credentials and populates the 'dotnetbuilds-internal-container-read-token-base64' # variable with the base64-encoded SAS token, by default parameters: - name: federatedServiceConnection type: string default: 'dotnetbuilds-internal-read' - name: outputVariableName type: string default: 'dotnetbuilds-internal-container-read-token-base64' - name: expiryInHours type: number default: 1 - name: base64Encode type: boolean default: true - name: is1ESPipeline type: boolean default: false steps: - ${{ if ne(variables['System.TeamProject'], 'public') }}: - template: /eng/common/core-templates/steps/get-delegation-sas.yml parameters: federatedServiceConnection: ${{ parameters.federatedServiceConnection }} outputVariableName: ${{ parameters.outputVariableName }} expiryInHours: ${{ parameters.expiryInHours }} base64Encode: ${{ parameters.base64Encode }} storageAccount: dotnetbuilds container: internal permissions: rl is1ESPipeline: ${{ parameters.is1ESPipeline }} ================================================ FILE: eng/common/core-templates/steps/enable-internal-sources.yml ================================================ parameters: # This is the Azure federated service connection that we log into to get an access token. - name: nugetFederatedServiceConnection type: string default: 'dnceng-artifacts-feeds-read' - name: is1ESPipeline type: boolean default: false # Legacy parameters to allow for PAT usage - name: legacyCredential type: string default: '' steps: - ${{ if ne(variables['System.TeamProject'], 'public') }}: - ${{ if ne(parameters.legacyCredential, '') }}: - task: PowerShell@2 displayName: Setup Internal Feeds inputs: filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.ps1 arguments: -ConfigFile $(Build.SourcesDirectory)/NuGet.config -Password $Env:Token env: Token: ${{ parameters.legacyCredential }} # If running on dnceng (internal project), just use the default behavior for NuGetAuthenticate. # If running on DevDiv, NuGetAuthenticate is not really an option. It's scoped to a single feed, and we have many feeds that # may be added. Instead, we'll use the traditional approach (add cred to nuget.config), but use an account token. - ${{ else }}: - ${{ if eq(variables['System.TeamProject'], 'internal') }}: - task: PowerShell@2 displayName: Setup Internal Feeds inputs: filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.ps1 arguments: -ConfigFile $(Build.SourcesDirectory)/NuGet.config - ${{ else }}: - template: /eng/common/templates/steps/get-federated-access-token.yml parameters: federatedServiceConnection: ${{ parameters.nugetFederatedServiceConnection }} outputVariableName: 'dnceng-artifacts-feeds-read-access-token' - task: PowerShell@2 displayName: Setup Internal Feeds inputs: filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.ps1 arguments: -ConfigFile $(Build.SourcesDirectory)/NuGet.config -Password $(dnceng-artifacts-feeds-read-access-token) # This is required in certain scenarios to install the ADO credential provider. # It installed by default in some msbuild invocations (e.g. VS msbuild), but needs to be installed for others # (e.g. dotnet msbuild). - task: NuGetAuthenticate@1 ================================================ FILE: eng/common/core-templates/steps/generate-sbom.yml ================================================ # BuildDropPath - The root folder of the drop directory for which the manifest file will be generated. # PackageName - The name of the package this SBOM represents. # PackageVersion - The version of the package this SBOM represents. # ManifestDirPath - The path of the directory where the generated manifest files will be placed # IgnoreDirectories - Directories to ignore for SBOM generation. This will be passed through to the CG component detector. parameters: PackageVersion: 9.0.0 BuildDropPath: '$(Build.SourcesDirectory)/artifacts' PackageName: '.NET' ManifestDirPath: $(Build.ArtifactStagingDirectory)/sbom IgnoreDirectories: '' sbomContinueOnError: true is1ESPipeline: false # disable publishArtifacts if some other step is publishing the artifacts (like job.yml). publishArtifacts: true steps: - task: PowerShell@2 displayName: Prep for SBOM generation in (Non-linux) condition: or(eq(variables['Agent.Os'], 'Windows_NT'), eq(variables['Agent.Os'], 'Darwin')) inputs: filePath: ./eng/common/generate-sbom-prep.ps1 arguments: ${{parameters.manifestDirPath}} # Chmodding is a workaround for https://github.com/dotnet/arcade/issues/8461 - script: | chmod +x ./eng/common/generate-sbom-prep.sh ./eng/common/generate-sbom-prep.sh ${{parameters.manifestDirPath}} displayName: Prep for SBOM generation in (Linux) condition: eq(variables['Agent.Os'], 'Linux') continueOnError: ${{ parameters.sbomContinueOnError }} - task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0 displayName: 'Generate SBOM manifest' continueOnError: ${{ parameters.sbomContinueOnError }} inputs: PackageName: ${{ parameters.packageName }} BuildDropPath: ${{ parameters.buildDropPath }} PackageVersion: ${{ parameters.packageVersion }} ManifestDirPath: ${{ parameters.manifestDirPath }} ${{ if ne(parameters.IgnoreDirectories, '') }}: AdditionalComponentDetectorArgs: '--IgnoreDirectories ${{ parameters.IgnoreDirectories }}' - ${{ if eq(parameters.publishArtifacts, 'true')}}: - template: /eng/common/core-templates/steps/publish-pipeline-artifacts.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} args: displayName: Publish SBOM manifest continueOnError: ${{parameters.sbomContinueOnError}} targetPath: '${{ parameters.manifestDirPath }}' artifactName: $(ARTIFACT_NAME) ================================================ FILE: eng/common/core-templates/steps/get-delegation-sas.yml ================================================ parameters: - name: federatedServiceConnection type: string - name: outputVariableName type: string - name: expiryInHours type: number default: 1 - name: base64Encode type: boolean default: false - name: storageAccount type: string - name: container type: string - name: permissions type: string default: 'rl' - name: is1ESPipeline type: boolean default: false steps: - task: AzureCLI@2 displayName: 'Generate delegation SAS Token for ${{ parameters.storageAccount }}/${{ parameters.container }}' inputs: azureSubscription: ${{ parameters.federatedServiceConnection }} scriptType: 'pscore' scriptLocation: 'inlineScript' inlineScript: | # Calculate the expiration of the SAS token and convert to UTC $expiry = (Get-Date).AddHours(${{ parameters.expiryInHours }}).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ") $sas = az storage container generate-sas --account-name ${{ parameters.storageAccount }} --name ${{ parameters.container }} --permissions ${{ parameters.permissions }} --expiry $expiry --auth-mode login --as-user -o tsv if ($LASTEXITCODE -ne 0) { Write-Error "Failed to generate SAS token." exit 1 } if ('${{ parameters.base64Encode }}' -eq 'true') { $sas = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($sas)) } Write-Host "Setting '${{ parameters.outputVariableName }}' with the access token value" Write-Host "##vso[task.setvariable variable=${{ parameters.outputVariableName }};issecret=true]$sas" ================================================ FILE: eng/common/core-templates/steps/get-federated-access-token.yml ================================================ parameters: - name: federatedServiceConnection type: string - name: outputVariableName type: string - name: is1ESPipeline type: boolean - name: stepName type: string default: 'getFederatedAccessToken' - name: condition type: string default: '' # Resource to get a token for. Common values include: # - '499b84ac-1321-427f-aa17-267ca6975798' for Azure DevOps # - 'https://storage.azure.com/' for storage # Defaults to Azure DevOps - name: resource type: string default: '499b84ac-1321-427f-aa17-267ca6975798' - name: isStepOutputVariable type: boolean default: false steps: - task: AzureCLI@2 displayName: 'Getting federated access token for feeds' name: ${{ parameters.stepName }} ${{ if ne(parameters.condition, '') }}: condition: ${{ parameters.condition }} inputs: azureSubscription: ${{ parameters.federatedServiceConnection }} scriptType: 'pscore' scriptLocation: 'inlineScript' inlineScript: | $accessToken = az account get-access-token --query accessToken --resource ${{ parameters.resource }} --output tsv if ($LASTEXITCODE -ne 0) { Write-Error "Failed to get access token for resource '${{ parameters.resource }}'" exit 1 } Write-Host "Setting '${{ parameters.outputVariableName }}' with the access token value" Write-Host "##vso[task.setvariable variable=${{ parameters.outputVariableName }};issecret=true;isOutput=${{ parameters.isStepOutputVariable }}]$accessToken" ================================================ FILE: eng/common/core-templates/steps/install-microbuild.yml ================================================ parameters: # Enable install tasks for MicroBuild enableMicrobuild: false # Enable install tasks for MicroBuild on Mac and Linux # Will be ignored if 'enableMicrobuild' is false or 'Agent.Os' is 'Windows_NT' enableMicrobuildForMacAndLinux: false # Location of the MicroBuild output folder microBuildOutputFolder: '$(Agent.TempDirectory)' continueOnError: false steps: - ${{ if eq(parameters.enableMicrobuild, 'true') }}: - ${{ if eq(parameters.enableMicrobuildForMacAndLinux, 'true') }}: # Install Python 3.12.x on when Python > 3.12.x is installed - https://github.com/dotnet/source-build/issues/4802 - script: | version=$(python3 --version | awk '{print $2}') major=$(echo $version | cut -d. -f1) minor=$(echo $version | cut -d. -f2) installPython=false if [ "$major" -gt 3 ] || { [ "$major" -eq 3 ] && [ "$minor" -gt 12 ]; }; then installPython=true fi echo "Python version: $version." echo "Install Python 3.12.x: $installPython." echo "##vso[task.setvariable variable=installPython;isOutput=true]$installPython" name: InstallPython displayName: 'Determine Python installation' condition: and(succeeded(), ne(variables['Agent.Os'], 'Windows_NT')) - task: UsePythonVersion@0 inputs: versionSpec: '3.12.x' displayName: 'Use Python 3.12.x' condition: and(succeeded(), eq(variables['InstallPython.installPython'], 'true'), ne(variables['Agent.Os'], 'Windows_NT')) # Needed to download the MicroBuild plugin nupkgs on Mac and Linux when nuget.exe is unavailable - task: UseDotNet@2 displayName: Install .NET 8.0 SDK for MicroBuild Plugin inputs: packageType: sdk version: 8.0.x installationPath: ${{ parameters.microBuildOutputFolder }}/dotnet workingDirectory: ${{ parameters.microBuildOutputFolder }} condition: and(succeeded(), ne(variables['Agent.Os'], 'Windows_NT')) - task: MicroBuildSigningPlugin@4 displayName: Install MicroBuild plugin inputs: signType: $(_SignType) zipSources: false feedSource: https://dnceng.pkgs.visualstudio.com/_packaging/MicroBuildToolset/nuget/v3/index.json ${{ if and(eq(parameters.enableMicrobuildForMacAndLinux, 'true'), ne(variables['Agent.Os'], 'Windows_NT')) }}: azureSubscription: 'MicroBuild Signing Task (DevDiv)' env: TeamName: $(_TeamName) MicroBuildOutputFolderOverride: ${{ parameters.microBuildOutputFolder }} SYSTEM_ACCESSTOKEN: $(System.AccessToken) continueOnError: ${{ parameters.continueOnError }} condition: and( succeeded(), or( and( eq(variables['Agent.Os'], 'Windows_NT'), in(variables['_SignType'], 'real', 'test') ), and( ${{ eq(parameters.enableMicrobuildForMacAndLinux, true) }}, ne(variables['Agent.Os'], 'Windows_NT'), eq(variables['_SignType'], 'real') ) )) ================================================ FILE: eng/common/core-templates/steps/publish-build-artifacts.yml ================================================ parameters: - name: is1ESPipeline type: boolean default: false - name: args type: object default: {} steps: - ${{ if ne(parameters.is1ESPipeline, true) }}: - template: /eng/common/templates/steps/publish-build-artifacts.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} ${{ each parameter in parameters.args }}: ${{ parameter.key }}: ${{ parameter.value }} - ${{ else }}: - template: /eng/common/templates-official/steps/publish-build-artifacts.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} ${{ each parameter in parameters.args }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/core-templates/steps/publish-logs.yml ================================================ parameters: StageLabel: '' JobLabel: '' CustomSensitiveDataList: '' # A default - in case value from eng/common/core-templates/post-build/common-variables.yml is not passed BinlogToolVersion: '1.0.11' is1ESPipeline: false steps: - task: Powershell@2 displayName: Prepare Binlogs to Upload inputs: targetType: inline script: | New-Item -ItemType Directory $(Build.SourcesDirectory)/PostBuildLogs/${{parameters.StageLabel}}/${{parameters.JobLabel}}/ Move-Item -Path $(Build.SourcesDirectory)/artifacts/log/Debug/* $(Build.SourcesDirectory)/PostBuildLogs/${{parameters.StageLabel}}/${{parameters.JobLabel}}/ continueOnError: true condition: always() - task: PowerShell@2 displayName: Redact Logs inputs: filePath: $(Build.SourcesDirectory)/eng/common/post-build/redact-logs.ps1 # For now this needs to have explicit list of all sensitive data. Taken from eng/publishing/v3/publish.yml # Sensitive data can as well be added to $(Build.SourcesDirectory)/eng/BinlogSecretsRedactionFile.txt' # If the file exists - sensitive data for redaction will be sourced from it # (single entry per line, lines starting with '# ' are considered comments and skipped) arguments: -InputPath '$(Build.SourcesDirectory)/PostBuildLogs' -BinlogToolVersion ${{parameters.BinlogToolVersion}} -TokensFilePath '$(Build.SourcesDirectory)/eng/BinlogSecretsRedactionFile.txt' '$(publishing-dnceng-devdiv-code-r-build-re)' '$(MaestroAccessToken)' '$(dn-bot-all-orgs-artifact-feeds-rw)' '$(akams-client-id)' '$(microsoft-symbol-server-pat)' '$(symweb-symbol-server-pat)' '$(dnceng-symbol-server-pat)' '$(dn-bot-all-orgs-build-rw-code-rw)' '$(System.AccessToken)' ${{parameters.CustomSensitiveDataList}} continueOnError: true condition: always() - task: CopyFiles@2 displayName: Gather post build logs inputs: SourceFolder: '$(Build.SourcesDirectory)/PostBuildLogs' Contents: '**' TargetFolder: '$(Build.ArtifactStagingDirectory)/PostBuildLogs' condition: always() - template: /eng/common/core-templates/steps/publish-build-artifacts.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} args: displayName: Publish Logs pathToPublish: '$(Build.ArtifactStagingDirectory)/PostBuildLogs' publishLocation: Container artifactName: PostBuildLogs continueOnError: true condition: always() ================================================ FILE: eng/common/core-templates/steps/publish-pipeline-artifacts.yml ================================================ parameters: - name: is1ESPipeline type: boolean default: false - name: args type: object default: {} steps: - ${{ if ne(parameters.is1ESPipeline, true) }}: - template: /eng/common/templates/steps/publish-pipeline-artifacts.yml parameters: ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} - ${{ else }}: - template: /eng/common/templates-official/steps/publish-pipeline-artifacts.yml parameters: ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/core-templates/steps/retain-build.yml ================================================ parameters: # Optional azure devops PAT with build execute permissions for the build's organization, # only needed if the build that should be retained ran on a different organization than # the pipeline where this template is executing from Token: '' # Optional BuildId to retain, defaults to the current running build BuildId: '' # Azure devops Organization URI for the build in the https://dev.azure.com/ format. # Defaults to the organization the current pipeline is running on AzdoOrgUri: '$(System.CollectionUri)' # Azure devops project for the build. Defaults to the project the current pipeline is running on AzdoProject: '$(System.TeamProject)' steps: - task: powershell@2 inputs: targetType: 'filePath' filePath: eng/common/retain-build.ps1 pwsh: true arguments: > -AzdoOrgUri: ${{parameters.AzdoOrgUri}} -AzdoProject ${{parameters.AzdoProject}} -Token ${{coalesce(parameters.Token, '$env:SYSTEM_ACCESSTOKEN') }} -BuildId ${{coalesce(parameters.BuildId, '$env:BUILD_ID')}} displayName: Enable permanent build retention env: SYSTEM_ACCESSTOKEN: $(System.AccessToken) BUILD_ID: $(Build.BuildId) ================================================ FILE: eng/common/core-templates/steps/send-to-helix.yml ================================================ # Please remember to update the documentation if you make changes to these parameters! parameters: HelixSource: 'pr/default' # required -- sources must start with pr/, official/, prodcon/, or agent/ HelixType: 'tests/default/' # required -- Helix telemetry which identifies what type of data this is; should include "test" for clarity and must end in '/' HelixBuild: $(Build.BuildNumber) # required -- the build number Helix will use to identify this -- automatically set to the AzDO build number HelixTargetQueues: '' # required -- semicolon-delimited list of Helix queues to test on; see https://helix.dot.net/ for a list of queues HelixAccessToken: '' # required -- access token to make Helix API requests; should be provided by the appropriate variable group HelixProjectPath: 'eng/common/helixpublish.proj' # optional -- path to the project file to build relative to BUILD_SOURCESDIRECTORY HelixProjectArguments: '' # optional -- arguments passed to the build command HelixConfiguration: '' # optional -- additional property attached to a job HelixPreCommands: '' # optional -- commands to run before Helix work item execution HelixPostCommands: '' # optional -- commands to run after Helix work item execution WorkItemDirectory: '' # optional -- a payload directory to zip up and send to Helix; requires WorkItemCommand; incompatible with XUnitProjects WorkItemCommand: '' # optional -- a command to execute on the payload; requires WorkItemDirectory; incompatible with XUnitProjects WorkItemTimeout: '' # optional -- a timeout in TimeSpan.Parse-ready value (e.g. 00:02:00) for the work item command; requires WorkItemDirectory; incompatible with XUnitProjects CorrelationPayloadDirectory: '' # optional -- a directory to zip up and send to Helix as a correlation payload XUnitProjects: '' # optional -- semicolon-delimited list of XUnitProjects to parse and send to Helix; requires XUnitRuntimeTargetFramework, XUnitPublishTargetFramework, XUnitRunnerVersion, and IncludeDotNetCli=true XUnitWorkItemTimeout: '' # optional -- the workitem timeout in seconds for all workitems created from the xUnit projects specified by XUnitProjects XUnitPublishTargetFramework: '' # optional -- framework to use to publish your xUnit projects XUnitRuntimeTargetFramework: '' # optional -- framework to use for the xUnit console runner XUnitRunnerVersion: '' # optional -- version of the xUnit nuget package you wish to use on Helix; required for XUnitProjects IncludeDotNetCli: false # optional -- true will download a version of the .NET CLI onto the Helix machine as a correlation payload; requires DotNetCliPackageType and DotNetCliVersion DotNetCliPackageType: '' # optional -- either 'sdk', 'runtime' or 'aspnetcore-runtime'; determines whether the sdk or runtime will be sent to Helix; see https://raw.githubusercontent.com/dotnet/core/main/release-notes/releases-index.json DotNetCliVersion: '' # optional -- version of the CLI to send to Helix; based on this: https://raw.githubusercontent.com/dotnet/core/main/release-notes/releases-index.json WaitForWorkItemCompletion: true # optional -- true will make the task wait until work items have been completed and fail the build if work items fail. False is "fire and forget." IsExternal: false # [DEPRECATED] -- doesn't do anything, jobs are external if HelixAccessToken is empty and Creator is set HelixBaseUri: 'https://helix.dot.net/' # optional -- sets the Helix API base URI (allows targeting https://helix.int-dot.net ) Creator: '' # optional -- if the build is external, use this to specify who is sending the job DisplayNamePrefix: 'Run Tests' # optional -- rename the beginning of the displayName of the steps in AzDO condition: succeeded() # optional -- condition for step to execute; defaults to succeeded() continueOnError: false # optional -- determines whether to continue the build if the step errors; defaults to false steps: - powershell: 'powershell "$env:BUILD_SOURCESDIRECTORY\eng\common\msbuild.ps1 $env:BUILD_SOURCESDIRECTORY/${{ parameters.HelixProjectPath }} /restore /p:TreatWarningsAsErrors=false ${{ parameters.HelixProjectArguments }} /t:Test /bl:$env:BUILD_SOURCESDIRECTORY\artifacts\log\$env:BuildConfig\SendToHelix.binlog"' displayName: ${{ parameters.DisplayNamePrefix }} (Windows) env: BuildConfig: $(_BuildConfig) HelixSource: ${{ parameters.HelixSource }} HelixType: ${{ parameters.HelixType }} HelixBuild: ${{ parameters.HelixBuild }} HelixConfiguration: ${{ parameters.HelixConfiguration }} HelixTargetQueues: ${{ parameters.HelixTargetQueues }} HelixAccessToken: ${{ parameters.HelixAccessToken }} HelixPreCommands: ${{ parameters.HelixPreCommands }} HelixPostCommands: ${{ parameters.HelixPostCommands }} WorkItemDirectory: ${{ parameters.WorkItemDirectory }} WorkItemCommand: ${{ parameters.WorkItemCommand }} WorkItemTimeout: ${{ parameters.WorkItemTimeout }} CorrelationPayloadDirectory: ${{ parameters.CorrelationPayloadDirectory }} XUnitProjects: ${{ parameters.XUnitProjects }} XUnitWorkItemTimeout: ${{ parameters.XUnitWorkItemTimeout }} XUnitPublishTargetFramework: ${{ parameters.XUnitPublishTargetFramework }} XUnitRuntimeTargetFramework: ${{ parameters.XUnitRuntimeTargetFramework }} XUnitRunnerVersion: ${{ parameters.XUnitRunnerVersion }} IncludeDotNetCli: ${{ parameters.IncludeDotNetCli }} DotNetCliPackageType: ${{ parameters.DotNetCliPackageType }} DotNetCliVersion: ${{ parameters.DotNetCliVersion }} WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }} HelixBaseUri: ${{ parameters.HelixBaseUri }} Creator: ${{ parameters.Creator }} SYSTEM_ACCESSTOKEN: $(System.AccessToken) condition: and(${{ parameters.condition }}, eq(variables['Agent.Os'], 'Windows_NT')) continueOnError: ${{ parameters.continueOnError }} - script: $BUILD_SOURCESDIRECTORY/eng/common/msbuild.sh $BUILD_SOURCESDIRECTORY/${{ parameters.HelixProjectPath }} /restore /p:TreatWarningsAsErrors=false ${{ parameters.HelixProjectArguments }} /t:Test /bl:$BUILD_SOURCESDIRECTORY/artifacts/log/$BuildConfig/SendToHelix.binlog displayName: ${{ parameters.DisplayNamePrefix }} (Unix) env: BuildConfig: $(_BuildConfig) HelixSource: ${{ parameters.HelixSource }} HelixType: ${{ parameters.HelixType }} HelixBuild: ${{ parameters.HelixBuild }} HelixConfiguration: ${{ parameters.HelixConfiguration }} HelixTargetQueues: ${{ parameters.HelixTargetQueues }} HelixAccessToken: ${{ parameters.HelixAccessToken }} HelixPreCommands: ${{ parameters.HelixPreCommands }} HelixPostCommands: ${{ parameters.HelixPostCommands }} WorkItemDirectory: ${{ parameters.WorkItemDirectory }} WorkItemCommand: ${{ parameters.WorkItemCommand }} WorkItemTimeout: ${{ parameters.WorkItemTimeout }} CorrelationPayloadDirectory: ${{ parameters.CorrelationPayloadDirectory }} XUnitProjects: ${{ parameters.XUnitProjects }} XUnitWorkItemTimeout: ${{ parameters.XUnitWorkItemTimeout }} XUnitPublishTargetFramework: ${{ parameters.XUnitPublishTargetFramework }} XUnitRuntimeTargetFramework: ${{ parameters.XUnitRuntimeTargetFramework }} XUnitRunnerVersion: ${{ parameters.XUnitRunnerVersion }} IncludeDotNetCli: ${{ parameters.IncludeDotNetCli }} DotNetCliPackageType: ${{ parameters.DotNetCliPackageType }} DotNetCliVersion: ${{ parameters.DotNetCliVersion }} WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }} HelixBaseUri: ${{ parameters.HelixBaseUri }} Creator: ${{ parameters.Creator }} SYSTEM_ACCESSTOKEN: $(System.AccessToken) condition: and(${{ parameters.condition }}, ne(variables['Agent.Os'], 'Windows_NT')) continueOnError: ${{ parameters.continueOnError }} ================================================ FILE: eng/common/core-templates/steps/source-build.yml ================================================ parameters: # This template adds arcade-powered source-build to CI. # This is a 'steps' template, and is intended for advanced scenarios where the existing build # infra has a careful build methodology that must be followed. For example, a repo # (dotnet/runtime) might choose to clone the GitHub repo only once and store it as a pipeline # artifact for all subsequent jobs to use, to reduce dependence on a strong network connection to # GitHub. Using this steps template leaves room for that infra to be included. # Defines the platform on which to run the steps. See 'eng/common/core-templates/job/source-build.yml' # for details. The entire object is described in the 'job' template for simplicity, even though # the usage of the properties on this object is split between the 'job' and 'steps' templates. platform: {} is1ESPipeline: false steps: # Build. Keep it self-contained for simple reusability. (No source-build-specific job variables.) - script: | set -x df -h # If file changes are detected, set CopyWipIntoInnerSourceBuildRepo to copy the WIP changes into the inner source build repo. internalRestoreArgs= if ! git diff --quiet; then internalRestoreArgs='/p:CopyWipIntoInnerSourceBuildRepo=true' # The 'Copy WIP' feature of source build uses git stash to apply changes from the original repo. # This only works if there is a username/email configured, which won't be the case in most CI runs. git config --get user.email if [ $? -ne 0 ]; then git config user.email dn-bot@microsoft.com git config user.name dn-bot fi fi # If building on the internal project, the internal storage variable may be available (usually only if needed) # In that case, add variables to allow the download of internal runtimes if the specified versions are not found # in the default public locations. internalRuntimeDownloadArgs= if [ '$(dotnetbuilds-internal-container-read-token-base64)' != '$''(dotnetbuilds-internal-container-read-token-base64)' ]; then internalRuntimeDownloadArgs='/p:DotNetRuntimeSourceFeed=https://dotnetbuilds.blob.core.windows.net/internal /p:DotNetRuntimeSourceFeedKey=$(dotnetbuilds-internal-container-read-token-base64) --runtimesourcefeed https://dotnetbuilds.blob.core.windows.net/internal --runtimesourcefeedkey $(dotnetbuilds-internal-container-read-token-base64)' fi buildConfig=Release # Check if AzDO substitutes in a build config from a variable, and use it if so. if [ '$(_BuildConfig)' != '$''(_BuildConfig)' ]; then buildConfig='$(_BuildConfig)' fi officialBuildArgs= if [ '${{ and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}' = 'True' ]; then officialBuildArgs='/p:DotNetPublishUsingPipelines=true /p:OfficialBuildId=$(BUILD.BUILDNUMBER)' fi targetRidArgs= if [ '${{ parameters.platform.targetRID }}' != '' ]; then targetRidArgs='/p:TargetRid=${{ parameters.platform.targetRID }}' fi runtimeOsArgs= if [ '${{ parameters.platform.runtimeOS }}' != '' ]; then runtimeOsArgs='/p:RuntimeOS=${{ parameters.platform.runtimeOS }}' fi baseOsArgs= if [ '${{ parameters.platform.baseOS }}' != '' ]; then baseOsArgs='/p:BaseOS=${{ parameters.platform.baseOS }}' fi publishArgs= if [ '${{ parameters.platform.skipPublishValidation }}' != 'true' ]; then publishArgs='--publish' fi assetManifestFileName=SourceBuild_RidSpecific.xml if [ '${{ parameters.platform.name }}' != '' ]; then assetManifestFileName=SourceBuild_${{ parameters.platform.name }}.xml fi portableBuildArgs= if [ '${{ parameters.platform.portableBuild }}' != '' ]; then portableBuildArgs='/p:PortableBuild=${{ parameters.platform.portableBuild }}' fi ${{ coalesce(parameters.platform.buildScript, './build.sh') }} --ci \ --configuration $buildConfig \ --restore --build --pack $publishArgs -bl \ $officialBuildArgs \ $internalRuntimeDownloadArgs \ $internalRestoreArgs \ $targetRidArgs \ $runtimeOsArgs \ $baseOsArgs \ $portableBuildArgs \ /p:DotNetBuildSourceOnly=true \ /p:DotNetBuildRepo=true \ /p:AssetManifestFileName=$assetManifestFileName displayName: Build # Upload build logs for diagnosis. - task: CopyFiles@2 displayName: Prepare BuildLogs staging directory inputs: SourceFolder: '$(Build.SourcesDirectory)' Contents: | **/*.log **/*.binlog artifacts/sb/prebuilt-report/** TargetFolder: '$(Build.StagingDirectory)/BuildLogs' CleanTargetFolder: true continueOnError: true condition: succeededOrFailed() - template: /eng/common/core-templates/steps/publish-pipeline-artifacts.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} args: displayName: Publish BuildLogs targetPath: '$(Build.StagingDirectory)/BuildLogs' artifactName: BuildLogs_SourceBuild_${{ parameters.platform.name }}_Attempt$(System.JobAttempt) continueOnError: true condition: succeededOrFailed() sbomEnabled: false # we don't need SBOM for logs # Manually inject component detection so that we can ignore the source build upstream cache, which contains # a nupkg cache of input packages (a local feed). # This path must match the upstream cache path in property 'CurrentRepoSourceBuiltNupkgCacheDir' # in src\Microsoft.DotNet.Arcade.Sdk\tools\SourceBuild\SourceBuildArcade.targets - template: /eng/common/core-templates/steps/component-governance.yml parameters: displayName: Component Detection (Exclude upstream cache) is1ESPipeline: ${{ parameters.is1ESPipeline }} componentGovernanceIgnoreDirectories: '$(Build.SourcesDirectory)/artifacts/sb/src/artifacts/obj/source-built-upstream-cache' disableComponentGovernance: ${{ eq(variables['System.TeamProject'], 'public') }} ================================================ FILE: eng/common/core-templates/steps/source-index-stage1-publish.yml ================================================ parameters: sourceIndexUploadPackageVersion: 2.0.0-20240522.1 sourceIndexProcessBinlogPackageVersion: 1.0.1-20240522.1 sourceIndexPackageSource: https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json binlogPath: artifacts/log/Debug/Build.binlog steps: - task: UseDotNet@2 displayName: "Source Index: Use .NET 8 SDK" inputs: packageType: sdk version: 8.0.x installationPath: $(Agent.TempDirectory)/dotnet workingDirectory: $(Agent.TempDirectory) - script: | $(Agent.TempDirectory)/dotnet/dotnet tool install BinLogToSln --version ${{parameters.sourceIndexProcessBinlogPackageVersion}} --add-source ${{parameters.SourceIndexPackageSource}} --tool-path $(Agent.TempDirectory)/.source-index/tools $(Agent.TempDirectory)/dotnet/dotnet tool install UploadIndexStage1 --version ${{parameters.sourceIndexUploadPackageVersion}} --add-source ${{parameters.SourceIndexPackageSource}} --tool-path $(Agent.TempDirectory)/.source-index/tools displayName: "Source Index: Download netsourceindex Tools" # Set working directory to temp directory so 'dotnet' doesn't try to use global.json and use the repo's sdk. workingDirectory: $(Agent.TempDirectory) - script: $(Agent.TempDirectory)/.source-index/tools/BinLogToSln -i ${{parameters.BinlogPath}} -r $(Build.SourcesDirectory) -n $(Build.Repository.Name) -o .source-index/stage1output displayName: "Source Index: Process Binlog into indexable sln" - ${{ if and(ne(parameters.runAsPublic, 'true'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - task: AzureCLI@2 displayName: "Source Index: Upload Source Index stage1 artifacts to Azure" inputs: azureSubscription: 'SourceDotNet Stage1 Publish' addSpnToEnvironment: true scriptType: 'ps' scriptLocation: 'inlineScript' inlineScript: | $(Agent.TempDirectory)/.source-index/tools/UploadIndexStage1 -i .source-index/stage1output -n $(Build.Repository.Name) -s netsourceindexstage1 -b stage1 ================================================ FILE: eng/common/core-templates/variables/pool-providers.yml ================================================ parameters: is1ESPipeline: false variables: - ${{ if eq(parameters.is1ESPipeline, 'true') }}: - template: /eng/common/templates-official/variables/pool-providers.yml - ${{ else }}: - template: /eng/common/templates/variables/pool-providers.yml ================================================ FILE: eng/common/cross/arm/tizen/tizen.patch ================================================ diff -u -r a/usr/lib/libc.so b/usr/lib/libc.so --- a/usr/lib/libc.so 2016-12-30 23:00:08.284951863 +0900 +++ b/usr/lib/libc.so 2016-12-30 23:00:32.140951815 +0900 @@ -2,4 +2,4 @@ Use the shared library, but some functions are only in the static library, so try that secondarily. */ OUTPUT_FORMAT(elf32-littlearm) -GROUP ( /lib/libc.so.6 /usr/lib/libc_nonshared.a AS_NEEDED ( /lib/ld-linux-armhf.so.3 ) ) +GROUP ( libc.so.6 libc_nonshared.a AS_NEEDED ( ld-linux-armhf.so.3 ) ) ================================================ FILE: eng/common/cross/arm64/tizen/tizen.patch ================================================ diff -u -r a/usr/lib/libc.so b/usr/lib/libc.so --- a/usr/lib64/libc.so 2016-12-30 23:00:08.284951863 +0900 +++ b/usr/lib64/libc.so 2016-12-30 23:00:32.140951815 +0900 @@ -2,4 +2,4 @@ Use the shared library, but some functions are only in the static library, so try that secondarily. */ OUTPUT_FORMAT(elf64-littleaarch64) -GROUP ( /lib64/libc.so.6 /usr/lib64/libc_nonshared.a AS_NEEDED ( /lib/ld-linux-aarch64.so.1 ) ) +GROUP ( libc.so.6 libc_nonshared.a AS_NEEDED ( ld-linux-aarch64.so.1 ) ) ================================================ FILE: eng/common/cross/armel/armel.jessie.patch ================================================ diff -u -r a/usr/include/urcu/uatomic/generic.h b/usr/include/urcu/uatomic/generic.h --- a/usr/include/urcu/uatomic/generic.h 2014-10-22 15:00:58.000000000 -0700 +++ b/usr/include/urcu/uatomic/generic.h 2020-10-30 21:38:28.550000000 -0700 @@ -69,10 +69,10 @@ #endif #ifdef UATOMIC_HAS_ATOMIC_SHORT case 2: - return __sync_val_compare_and_swap_2(addr, old, _new); + return __sync_val_compare_and_swap_2((uint16_t*) addr, old, _new); #endif case 4: - return __sync_val_compare_and_swap_4(addr, old, _new); + return __sync_val_compare_and_swap_4((uint32_t*) addr, old, _new); #if (CAA_BITS_PER_LONG == 64) case 8: return __sync_val_compare_and_swap_8(addr, old, _new); @@ -109,7 +109,7 @@ return; #endif case 4: - __sync_and_and_fetch_4(addr, val); + __sync_and_and_fetch_4((uint32_t*) addr, val); return; #if (CAA_BITS_PER_LONG == 64) case 8: @@ -148,7 +148,7 @@ return; #endif case 4: - __sync_or_and_fetch_4(addr, val); + __sync_or_and_fetch_4((uint32_t*) addr, val); return; #if (CAA_BITS_PER_LONG == 64) case 8: @@ -187,7 +187,7 @@ return __sync_add_and_fetch_2(addr, val); #endif case 4: - return __sync_add_and_fetch_4(addr, val); + return __sync_add_and_fetch_4((uint32_t*) addr, val); #if (CAA_BITS_PER_LONG == 64) case 8: return __sync_add_and_fetch_8(addr, val); ================================================ FILE: eng/common/cross/armel/tizen/tizen.patch ================================================ diff -u -r a/usr/lib/libc.so b/usr/lib/libc.so --- a/usr/lib/libc.so 2016-12-30 23:00:08.284951863 +0900 +++ b/usr/lib/libc.so 2016-12-30 23:00:32.140951815 +0900 @@ -2,4 +2,4 @@ Use the shared library, but some functions are only in the static library, so try that secondarily. */ OUTPUT_FORMAT(elf32-littlearm) -GROUP ( /lib/libc.so.6 /usr/lib/libc_nonshared.a AS_NEEDED ( /lib/ld-linux.so.3 ) ) +GROUP ( libc.so.6 libc_nonshared.a AS_NEEDED ( ld-linux.so.3 ) ) ================================================ FILE: eng/common/cross/build-android-rootfs.sh ================================================ #!/usr/bin/env bash set -e __NDK_Version=r21 usage() { echo "Creates a toolchain and sysroot used for cross-compiling for Android." echo echo "Usage: $0 [BuildArch] [ApiLevel] [--ndk NDKVersion]" echo echo "BuildArch is the target architecture of Android. Currently only arm64 is supported." echo "ApiLevel is the target Android API level. API levels usually match to Android releases. See https://source.android.com/source/build-numbers.html" echo "NDKVersion is the version of Android NDK. The default is r21. See https://developer.android.com/ndk/downloads/revision_history" echo echo "By default, the toolchain and sysroot will be generated in cross/android-rootfs/toolchain/[BuildArch]. You can change this behavior" echo "by setting the TOOLCHAIN_DIR environment variable" echo echo "By default, the NDK will be downloaded into the cross/android-rootfs/android-ndk-$__NDK_Version directory. If you already have an NDK installation," echo "you can set the NDK_DIR environment variable to have this script use that installation of the NDK." echo "By default, this script will generate a file, android_platform, in the root of the ROOTFS_DIR directory that contains the RID for the supported and tested Android build: android.28-arm64. This file is to replace '/etc/os-release', which is not available for Android." exit 1 } __ApiLevel=28 # The minimum platform for arm64 is API level 21 but the minimum version that support glob(3) is 28. See $ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/glob.h __BuildArch=arm64 __AndroidArch=aarch64 __AndroidToolchain=aarch64-linux-android while :; do if [[ "$#" -le 0 ]]; then break fi i=$1 lowerI="$(echo $i | tr "[:upper:]" "[:lower:]")" case $lowerI in -?|-h|--help) usage exit 1 ;; arm64) __BuildArch=arm64 __AndroidArch=aarch64 __AndroidToolchain=aarch64-linux-android ;; arm) __BuildArch=arm __AndroidArch=arm __AndroidToolchain=arm-linux-androideabi ;; --ndk) shift __NDK_Version=$1 ;; *[0-9]) __ApiLevel=$i ;; *) __UnprocessedBuildArgs="$__UnprocessedBuildArgs $i" ;; esac shift done if [[ "$__NDK_Version" == "r21" ]] || [[ "$__NDK_Version" == "r22" ]]; then __NDK_File_Arch_Spec=-x86_64 __SysRoot=sysroot else __NDK_File_Arch_Spec= __SysRoot=toolchains/llvm/prebuilt/linux-x86_64/sysroot fi # Obtain the location of the bash script to figure out where the root of the repo is. __ScriptBaseDir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" __CrossDir="$__ScriptBaseDir/../../../.tools/android-rootfs" if [[ ! -f "$__CrossDir" ]]; then mkdir -p "$__CrossDir" fi # Resolve absolute path to avoid `../` in build logs __CrossDir="$( cd "$__CrossDir" && pwd )" __NDK_Dir="$__CrossDir/android-ndk-$__NDK_Version" __lldb_Dir="$__CrossDir/lldb" __ToolchainDir="$__CrossDir/android-ndk-$__NDK_Version" if [[ -n "$TOOLCHAIN_DIR" ]]; then __ToolchainDir=$TOOLCHAIN_DIR fi if [[ -n "$NDK_DIR" ]]; then __NDK_Dir=$NDK_DIR fi echo "Target API level: $__ApiLevel" echo "Target architecture: $__BuildArch" echo "NDK version: $__NDK_Version" echo "NDK location: $__NDK_Dir" echo "Target Toolchain location: $__ToolchainDir" # Download the NDK if required if [ ! -d $__NDK_Dir ]; then echo Downloading the NDK into $__NDK_Dir mkdir -p $__NDK_Dir wget -q --progress=bar:force:noscroll --show-progress https://dl.google.com/android/repository/android-ndk-$__NDK_Version-linux$__NDK_File_Arch_Spec.zip -O $__CrossDir/android-ndk-$__NDK_Version-linux.zip unzip -q $__CrossDir/android-ndk-$__NDK_Version-linux.zip -d $__CrossDir fi if [ ! -d $__lldb_Dir ]; then mkdir -p $__lldb_Dir echo Downloading LLDB into $__lldb_Dir wget -q --progress=bar:force:noscroll --show-progress https://dl.google.com/android/repository/lldb-2.3.3614996-linux-x86_64.zip -O $__CrossDir/lldb-2.3.3614996-linux-x86_64.zip unzip -q $__CrossDir/lldb-2.3.3614996-linux-x86_64.zip -d $__lldb_Dir fi echo "Download dependencies..." __TmpDir=$__CrossDir/tmp/$__BuildArch/ mkdir -p "$__TmpDir" # combined dependencies for coreclr, installer and libraries __AndroidPackages="libicu" __AndroidPackages+=" libandroid-glob" __AndroidPackages+=" liblzma" __AndroidPackages+=" krb5" __AndroidPackages+=" openssl" for path in $(wget -qO- https://packages.termux.dev/termux-main-21/dists/stable/main/binary-$__AndroidArch/Packages |\ grep -A15 "Package: \(${__AndroidPackages// /\\|}\)" | grep -v "static\|tool" | grep Filename); do if [[ "$path" != "Filename:" ]]; then echo "Working on: $path" wget -qO- https://packages.termux.dev/termux-main-21/$path | dpkg -x - "$__TmpDir" fi done cp -R "$__TmpDir/data/data/com.termux/files/usr/"* "$__ToolchainDir/$__SysRoot/usr/" # Generate platform file for build.sh script to assign to __DistroRid echo "Generating platform file..." echo "RID=android.${__ApiLevel}-${__BuildArch}" > $__ToolchainDir/$__SysRoot/android_platform echo "Now to build coreclr, libraries and host; run:" echo ROOTFS_DIR=$(realpath $__ToolchainDir/$__SysRoot) ./build.sh clr+libs+host --cross --arch $__BuildArch ================================================ FILE: eng/common/cross/build-rootfs.sh ================================================ #!/usr/bin/env bash set -e usage() { echo "Usage: $0 [BuildArch] [CodeName] [lldbx.y] [llvmx[.y]] [--skipunmount] --rootfsdir ]" echo "BuildArch can be: arm(default), arm64, armel, armv6, loongarch64, ppc64le, riscv64, s390x, x64, x86" echo "CodeName - optional, Code name for Linux, can be: xenial(default), zesty, bionic, alpine" echo " for alpine can be specified with version: alpineX.YY or alpineedge" echo " for FreeBSD can be: freebsd13, freebsd14" echo " for illumos can be: illumos" echo " for Haiku can be: haiku." echo "lldbx.y - optional, LLDB version, can be: lldb3.9(default), lldb4.0, lldb5.0, lldb6.0 no-lldb. Ignored for alpine and FreeBSD" echo "llvmx[.y] - optional, LLVM version for LLVM related packages." echo "--skipunmount - optional, will skip the unmount of rootfs folder." echo "--skipsigcheck - optional, will skip package signature checks (allowing untrusted packages)." echo "--skipemulation - optional, will skip qemu and debootstrap requirement when building environment for debian based systems." echo "--use-mirror - optional, use mirror URL to fetch resources, when available." echo "--jobs N - optional, restrict to N jobs." exit 1 } __CodeName=xenial __CrossDir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) __BuildArch=arm __AlpineArch=armv7 __FreeBSDArch=arm __FreeBSDMachineArch=armv7 __IllumosArch=arm7 __HaikuArch=arm __QEMUArch=arm __UbuntuArch=armhf __UbuntuRepo= __UbuntuSuites="updates security backports" __DebianSuites= __LLDB_Package="liblldb-3.9-dev" __SkipUnmount=0 # base development support __UbuntuPackages="build-essential" __AlpinePackages="alpine-base" __AlpinePackages+=" build-base" __AlpinePackages+=" linux-headers" __AlpinePackages+=" lldb-dev" __AlpinePackages+=" python3" __AlpinePackages+=" libedit" # symlinks fixer __UbuntuPackages+=" symlinks" # runtime dependencies __UbuntuPackages+=" libicu-dev" __UbuntuPackages+=" liblttng-ust-dev" __UbuntuPackages+=" libunwind8-dev" __AlpinePackages+=" gettext-dev" __AlpinePackages+=" icu-dev" __AlpinePackages+=" libunwind-dev" __AlpinePackages+=" lttng-ust-dev" __AlpinePackages+=" compiler-rt" # runtime libraries' dependencies __UbuntuPackages+=" libcurl4-openssl-dev" __UbuntuPackages+=" libkrb5-dev" __UbuntuPackages+=" libssl-dev" __UbuntuPackages+=" zlib1g-dev" __UbuntuPackages+=" libbrotli-dev" __AlpinePackages+=" curl-dev" __AlpinePackages+=" krb5-dev" __AlpinePackages+=" openssl-dev" __AlpinePackages+=" zlib-dev" __FreeBSDBase="13.4-RELEASE" __FreeBSDPkg="1.21.3" __FreeBSDABI="13" __FreeBSDPackages="libunwind" __FreeBSDPackages+=" icu" __FreeBSDPackages+=" libinotify" __FreeBSDPackages+=" openssl" __FreeBSDPackages+=" krb5" __FreeBSDPackages+=" terminfo-db" __IllumosPackages="icu" __IllumosPackages+=" mit-krb5" __IllumosPackages+=" openssl" __IllumosPackages+=" zlib" __HaikuPackages="gcc_syslibs" __HaikuPackages+=" gcc_syslibs_devel" __HaikuPackages+=" gmp" __HaikuPackages+=" gmp_devel" __HaikuPackages+=" icu[0-9]+" __HaikuPackages+=" icu[0-9]*_devel" __HaikuPackages+=" krb5" __HaikuPackages+=" krb5_devel" __HaikuPackages+=" libiconv" __HaikuPackages+=" libiconv_devel" __HaikuPackages+=" llvm[0-9]*_libunwind" __HaikuPackages+=" llvm[0-9]*_libunwind_devel" __HaikuPackages+=" mpfr" __HaikuPackages+=" mpfr_devel" __HaikuPackages+=" openssl3" __HaikuPackages+=" openssl3_devel" __HaikuPackages+=" zlib" __HaikuPackages+=" zlib_devel" # ML.NET dependencies __UbuntuPackages+=" libomp5" __UbuntuPackages+=" libomp-dev" # Taken from https://github.com/alpinelinux/alpine-chroot-install/blob/6d08f12a8a70dd9b9dc7d997c88aa7789cc03c42/alpine-chroot-install#L85-L133 __AlpineKeys=' 4a6a0840:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1yHJxQgsHQREclQu4Ohe\nqxTxd1tHcNnvnQTu/UrTky8wWvgXT+jpveroeWWnzmsYlDI93eLI2ORakxb3gA2O\nQ0Ry4ws8vhaxLQGC74uQR5+/yYrLuTKydFzuPaS1dK19qJPXB8GMdmFOijnXX4SA\njixuHLe1WW7kZVtjL7nufvpXkWBGjsfrvskdNA/5MfxAeBbqPgaq0QMEfxMAn6/R\nL5kNepi/Vr4S39Xvf2DzWkTLEK8pcnjNkt9/aafhWqFVW7m3HCAII6h/qlQNQKSo\nGuH34Q8GsFG30izUENV9avY7hSLq7nggsvknlNBZtFUcmGoQrtx3FmyYsIC8/R+B\nywIDAQAB 5243ef4b:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvNijDxJ8kloskKQpJdx+\nmTMVFFUGDoDCbulnhZMJoKNkSuZOzBoFC94omYPtxnIcBdWBGnrm6ncbKRlR+6oy\nDO0W7c44uHKCFGFqBhDasdI4RCYP+fcIX/lyMh6MLbOxqS22TwSLhCVjTyJeeH7K\naA7vqk+QSsF4TGbYzQDDpg7+6aAcNzg6InNePaywA6hbT0JXbxnDWsB+2/LLSF2G\nmnhJlJrWB1WGjkz23ONIWk85W4S0XB/ewDefd4Ly/zyIciastA7Zqnh7p3Ody6Q0\nsS2MJzo7p3os1smGjUF158s6m/JbVh4DN6YIsxwl2OjDOz9R0OycfJSDaBVIGZzg\ncQIDAQAB 524d27bb:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr8s1q88XpuJWLCZALdKj\nlN8wg2ePB2T9aIcaxryYE/Jkmtu+ZQ5zKq6BT3y/udt5jAsMrhHTwroOjIsF9DeG\ne8Y3vjz+Hh4L8a7hZDaw8jy3CPag47L7nsZFwQOIo2Cl1SnzUc6/owoyjRU7ab0p\niWG5HK8IfiybRbZxnEbNAfT4R53hyI6z5FhyXGS2Ld8zCoU/R4E1P0CUuXKEN4p0\n64dyeUoOLXEWHjgKiU1mElIQj3k/IF02W89gDj285YgwqA49deLUM7QOd53QLnx+\nxrIrPv3A+eyXMFgexNwCKQU9ZdmWa00MjjHlegSGK8Y2NPnRoXhzqSP9T9i2HiXL\nVQIDAQAB 5261cecb:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwlzMkl7b5PBdfMzGdCT0\ncGloRr5xGgVmsdq5EtJvFkFAiN8Ac9MCFy/vAFmS8/7ZaGOXoCDWbYVLTLOO2qtX\nyHRl+7fJVh2N6qrDDFPmdgCi8NaE+3rITWXGrrQ1spJ0B6HIzTDNEjRKnD4xyg4j\ng01FMcJTU6E+V2JBY45CKN9dWr1JDM/nei/Pf0byBJlMp/mSSfjodykmz4Oe13xB\nCa1WTwgFykKYthoLGYrmo+LKIGpMoeEbY1kuUe04UiDe47l6Oggwnl+8XD1MeRWY\nsWgj8sF4dTcSfCMavK4zHRFFQbGp/YFJ/Ww6U9lA3Vq0wyEI6MCMQnoSMFwrbgZw\nwwIDAQAB 58199dcc:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3v8/ye/V/t5xf4JiXLXa\nhWFRozsnmn3hobON20GdmkrzKzO/eUqPOKTpg2GtvBhK30fu5oY5uN2ORiv2Y2ht\neLiZ9HVz3XP8Fm9frha60B7KNu66FO5P2o3i+E+DWTPqqPcCG6t4Znk2BypILcit\nwiPKTsgbBQR2qo/cO01eLLdt6oOzAaF94NH0656kvRewdo6HG4urbO46tCAizvCR\nCA7KGFMyad8WdKkTjxh8YLDLoOCtoZmXmQAiwfRe9pKXRH/XXGop8SYptLqyVVQ+\ntegOD9wRs2tOlgcLx4F/uMzHN7uoho6okBPiifRX+Pf38Vx+ozXh056tjmdZkCaV\naQIDAQAB 58cbb476:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoSPnuAGKtRIS5fEgYPXD\n8pSGvKAmIv3A08LBViDUe+YwhilSHbYXUEAcSH1KZvOo1WT1x2FNEPBEFEFU1Eyc\n+qGzbA03UFgBNvArurHQ5Z/GngGqE7IarSQFSoqewYRtFSfp+TL9CUNBvM0rT7vz\n2eMu3/wWG+CBmb92lkmyWwC1WSWFKO3x8w+Br2IFWvAZqHRt8oiG5QtYvcZL6jym\nY8T6sgdDlj+Y+wWaLHs9Fc+7vBuyK9C4O1ORdMPW15qVSl4Lc2Wu1QVwRiKnmA+c\nDsH/m7kDNRHM7TjWnuj+nrBOKAHzYquiu5iB3Qmx+0gwnrSVf27Arc3ozUmmJbLj\nzQIDAQAB 58e4f17d:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvBxJN9ErBgdRcPr5g4hV\nqyUSGZEKuvQliq2Z9SRHLh2J43+EdB6A+yzVvLnzcHVpBJ+BZ9RV30EM9guck9sh\nr+bryZcRHyjG2wiIEoduxF2a8KeWeQH7QlpwGhuobo1+gA8L0AGImiA6UP3LOirl\nI0G2+iaKZowME8/tydww4jx5vG132JCOScMjTalRsYZYJcjFbebQQolpqRaGB4iG\nWqhytWQGWuKiB1A22wjmIYf3t96l1Mp+FmM2URPxD1gk/BIBnX7ew+2gWppXOK9j\n1BJpo0/HaX5XoZ/uMqISAAtgHZAqq+g3IUPouxTphgYQRTRYpz2COw3NF43VYQrR\nbQIDAQAB 60ac2099:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwR4uJVtJOnOFGchnMW5Y\nj5/waBdG1u5BTMlH+iQMcV5+VgWhmpZHJCBz3ocD+0IGk2I68S5TDOHec/GSC0lv\n6R9o6F7h429GmgPgVKQsc8mPTPtbjJMuLLs4xKc+viCplXc0Nc0ZoHmCH4da6fCV\ntdpHQjVe6F9zjdquZ4RjV6R6JTiN9v924dGMAkbW/xXmamtz51FzondKC52Gh8Mo\n/oA0/T0KsCMCi7tb4QNQUYrf+Xcha9uus4ww1kWNZyfXJB87a2kORLiWMfs2IBBJ\nTmZ2Fnk0JnHDb8Oknxd9PvJPT0mvyT8DA+KIAPqNvOjUXP4bnjEHJcoCP9S5HkGC\nIQIDAQAB 6165ee59:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAutQkua2CAig4VFSJ7v54\nALyu/J1WB3oni7qwCZD3veURw7HxpNAj9hR+S5N/pNeZgubQvJWyaPuQDm7PTs1+\ntFGiYNfAsiibX6Rv0wci3M+z2XEVAeR9Vzg6v4qoofDyoTbovn2LztaNEjTkB+oK\ntlvpNhg1zhou0jDVYFniEXvzjckxswHVb8cT0OMTKHALyLPrPOJzVtM9C1ew2Nnc\n3848xLiApMu3NBk0JqfcS3Bo5Y2b1FRVBvdt+2gFoKZix1MnZdAEZ8xQzL/a0YS5\nHd0wj5+EEKHfOd3A75uPa/WQmA+o0cBFfrzm69QDcSJSwGpzWrD1ScH3AK8nWvoj\nv7e9gukK/9yl1b4fQQ00vttwJPSgm9EnfPHLAtgXkRloI27H6/PuLoNvSAMQwuCD\nhQRlyGLPBETKkHeodfLoULjhDi1K2gKJTMhtbnUcAA7nEphkMhPWkBpgFdrH+5z4\nLxy+3ek0cqcI7K68EtrffU8jtUj9LFTUC8dERaIBs7NgQ/LfDbDfGh9g6qVj1hZl\nk9aaIPTm/xsi8v3u+0qaq7KzIBc9s59JOoA8TlpOaYdVgSQhHHLBaahOuAigH+VI\nisbC9vmqsThF2QdDtQt37keuqoda2E6sL7PUvIyVXDRfwX7uMDjlzTxHTymvq2Ck\nhtBqojBnThmjJQFgZXocHG8CAwEAAQ== 61666e3f:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlEyxkHggKCXC2Wf5Mzx4\nnZLFZvU2bgcA3exfNPO/g1YunKfQY+Jg4fr6tJUUTZ3XZUrhmLNWvpvSwDS19ZmC\nIXOu0+V94aNgnhMsk9rr59I8qcbsQGIBoHzuAl8NzZCgdbEXkiY90w1skUw8J57z\nqCsMBydAueMXuWqF5nGtYbi5vHwK42PffpiZ7G5Kjwn8nYMW5IZdL6ZnMEVJUWC9\nI4waeKg0yskczYDmZUEAtrn3laX9677ToCpiKrvmZYjlGl0BaGp3cxggP2xaDbUq\nqfFxWNgvUAb3pXD09JM6Mt6HSIJaFc9vQbrKB9KT515y763j5CC2KUsilszKi3mB\nHYe5PoebdjS7D1Oh+tRqfegU2IImzSwW3iwA7PJvefFuc/kNIijfS/gH/cAqAK6z\nbhdOtE/zc7TtqW2Wn5Y03jIZdtm12CxSxwgtCF1NPyEWyIxAQUX9ACb3M0FAZ61n\nfpPrvwTaIIxxZ01L3IzPLpbc44x/DhJIEU+iDt6IMTrHOphD9MCG4631eIdB0H1b\n6zbNX1CXTsafqHRFV9XmYYIeOMggmd90s3xIbEujA6HKNP/gwzO6CDJ+nHFDEqoF\nSkxRdTkEqjTjVKieURW7Swv7zpfu5PrsrrkyGnsRrBJJzXlm2FOOxnbI2iSL1B5F\nrO5kbUxFeZUIDq+7Yv4kLWcCAwEAAQ== 616a9724:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnC+bR4bHf/L6QdU4puhQ\ngl1MHePszRC38bzvVFDUJsmCaMCL2suCs2A2yxAgGb9pu9AJYLAmxQC4mM3jNqhg\n/E7yuaBbek3O02zN/ctvflJ250wZCy+z0ZGIp1ak6pu1j14IwHokl9j36zNfGtfv\nADVOcdpWITFFlPqwq1qt/H3UsKVmtiF3BNWWTeUEQwKvlU8ymxgS99yn0+4OPyNT\nL3EUeS+NQJtDS01unau0t7LnjUXn+XIneWny8bIYOQCuVR6s/gpIGuhBaUqwaJOw\n7jkJZYF2Ij7uPb4b5/R3vX2FfxxqEHqssFSg8FFUNTZz3qNZs0CRVyfA972g9WkJ\nhPfn31pQYil4QGRibCMIeU27YAEjXoqfJKEPh4UWMQsQLrEfdGfb8VgwrPbniGfU\nL3jKJR3VAafL9330iawzVQDlIlwGl6u77gEXMl9K0pfazunYhAp+BMP+9ot5ckK+\nosmrqj11qMESsAj083GeFdfV3pXEIwUytaB0AKEht9DbqUfiE/oeZ/LAXgySMtVC\nsbC4ESmgVeY2xSBIJdDyUap7FR49GGrw0W49NUv9gRgQtGGaNVQQO9oGL2PBC41P\niWF9GLoX30HIz1P8PF/cZvicSSPkQf2Z6TV+t0ebdGNS5DjapdnCrq8m9Z0pyKsQ\nuxAL2a7zX8l5i1CZh1ycUGsCAwEAAQ== 616abc23:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0MfCDrhODRCIxR9Dep1s\neXafh5CE5BrF4WbCgCsevyPIdvTeyIaW4vmO3bbG4VzhogDZju+R3IQYFuhoXP5v\nY+zYJGnwrgz3r5wYAvPnLEs1+dtDKYOgJXQj+wLJBW1mzRDL8FoRXOe5iRmn1EFS\nwZ1DoUvyu7/J5r0itKicZp3QKED6YoilXed+1vnS4Sk0mzN4smuMR9eO1mMCqNp9\n9KTfRDHTbakIHwasECCXCp50uXdoW6ig/xUAFanpm9LtK6jctNDbXDhQmgvAaLXZ\nLvFqoaYJ/CvWkyYCgL6qxvMvVmPoRv7OPcyni4xR/WgWa0MSaEWjgPx3+yj9fiMA\n1S02pFWFDOr5OUF/O4YhFJvUCOtVsUPPfA/Lj6faL0h5QI9mQhy5Zb9TTaS9jB6p\nLw7u0dJlrjFedk8KTJdFCcaGYHP6kNPnOxMylcB/5WcztXZVQD5WpCicGNBxCGMm\nW64SgrV7M07gQfL/32QLsdqPUf0i8hoVD8wfQ3EpbQzv6Fk1Cn90bZqZafg8XWGY\nwddhkXk7egrr23Djv37V2okjzdqoyLBYBxMz63qQzFoAVv5VoY2NDTbXYUYytOvG\nGJ1afYDRVWrExCech1mX5ZVUB1br6WM+psFLJFoBFl6mDmiYt0vMYBddKISsvwLl\nIJQkzDwtXzT2cSjoj3T5QekCAwEAAQ== 616ac3bc:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvaaoSLab+IluixwKV5Od\n0gib2YurjPatGIbn5Ov2DLUFYiebj2oJINXJSwUOO+4WcuHFEqiL/1rya+k5hLZt\nhnPL1tn6QD4rESznvGSasRCQNT2vS/oyZbTYJRyAtFkEYLlq0t3S3xBxxHWuvIf0\nqVxVNYpQWyM3N9RIeYBR/euXKJXileSHk/uq1I5wTC0XBIHWcthczGN0m9wBEiWS\n0m3cnPk4q0Ea8mUJ91Rqob19qETz6VbSPYYpZk3qOycjKosuwcuzoMpwU8KRiMFd\n5LHtX0Hx85ghGsWDVtS0c0+aJa4lOMGvJCAOvDfqvODv7gKlCXUpgumGpLdTmaZ8\n1RwqspAe3IqBcdKTqRD4m2mSg23nVx2FAY3cjFvZQtfooT7q1ItRV5RgH6FhQSl7\n+6YIMJ1Bf8AAlLdRLpg+doOUGcEn+pkDiHFgI8ylH1LKyFKw+eXaAml/7DaWZk1d\ndqggwhXOhc/UUZFQuQQ8A8zpA13PcbC05XxN2hyP93tCEtyynMLVPtrRwDnHxFKa\nqKzs3rMDXPSXRn3ZZTdKH3069ApkEjQdpcwUh+EmJ1Ve/5cdtzT6kKWCjKBFZP/s\n91MlRrX2BTRdHaU5QJkUheUtakwxuHrdah2F94lRmsnQlpPr2YseJu6sIE+Dnx4M\nCfhdVbQL2w54R645nlnohu8CAwEAAQ== 616adfeb:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq0BFD1D4lIxQcsqEpQzU\npNCYM3aP1V/fxxVdT4DWvSI53JHTwHQamKdMWtEXetWVbP5zSROniYKFXd/xrD9X\n0jiGHey3lEtylXRIPxe5s+wXoCmNLcJVnvTcDtwx/ne2NLHxp76lyc25At+6RgE6\nADjLVuoD7M4IFDkAsd8UQ8zM0Dww9SylIk/wgV3ZkifecvgUQRagrNUdUjR56EBZ\nraQrev4hhzOgwelT0kXCu3snbUuNY/lU53CoTzfBJ5UfEJ5pMw1ij6X0r5S9IVsy\nKLWH1hiO0NzU2c8ViUYCly4Fe9xMTFc6u2dy/dxf6FwERfGzETQxqZvSfrRX+GLj\n/QZAXiPg5178hT/m0Y3z5IGenIC/80Z9NCi+byF1WuJlzKjDcF/TU72zk0+PNM/H\nKuppf3JT4DyjiVzNC5YoWJT2QRMS9KLP5iKCSThwVceEEg5HfhQBRT9M6KIcFLSs\nmFjx9kNEEmc1E8hl5IR3+3Ry8G5/bTIIruz14jgeY9u5jhL8Vyyvo41jgt9sLHR1\n/J1TxKfkgksYev7PoX6/ZzJ1ksWKZY5NFoDXTNYUgzFUTOoEaOg3BAQKadb3Qbbq\nXIrxmPBdgrn9QI7NCgfnAY3Tb4EEjs3ON/BNyEhUENcXOH6I1NbcuBQ7g9P73kE4\nVORdoc8MdJ5eoKBpO8Ww8HECAwEAAQ== 616ae350:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyduVzi1mWm+lYo2Tqt/0\nXkCIWrDNP1QBMVPrE0/ZlU2bCGSoo2Z9FHQKz/mTyMRlhNqTfhJ5qU3U9XlyGOPJ\npiM+b91g26pnpXJ2Q2kOypSgOMOPA4cQ42PkHBEqhuzssfj9t7x47ppS94bboh46\nxLSDRff/NAbtwTpvhStV3URYkxFG++cKGGa5MPXBrxIp+iZf9GnuxVdST5PGiVGP\nODL/b69sPJQNbJHVquqUTOh5Ry8uuD2WZuXfKf7/C0jC/ie9m2+0CttNu9tMciGM\nEyKG1/Xhk5iIWO43m4SrrT2WkFlcZ1z2JSf9Pjm4C2+HovYpihwwdM/OdP8Xmsnr\nDzVB4YvQiW+IHBjStHVuyiZWc+JsgEPJzisNY0Wyc/kNyNtqVKpX6dRhMLanLmy+\nf53cCSI05KPQAcGj6tdL+D60uKDkt+FsDa0BTAobZ31OsFVid0vCXtsbplNhW1IF\nHwsGXBTVcfXg44RLyL8Lk/2dQxDHNHzAUslJXzPxaHBLmt++2COa2EI1iWlvtznk\nOk9WP8SOAIj+xdqoiHcC4j72BOVVgiITIJNHrbppZCq6qPR+fgXmXa+sDcGh30m6\n9Wpbr28kLMSHiENCWTdsFij+NQTd5S47H7XTROHnalYDuF1RpS+DpQidT5tUimaT\nJZDr++FjKrnnijbyNF8b98UCAwEAAQ== 616db30d:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnpUpyWDWjlUk3smlWeA0\nlIMW+oJ38t92CRLHH3IqRhyECBRW0d0aRGtq7TY8PmxjjvBZrxTNDpJT6KUk4LRm\na6A6IuAI7QnNK8SJqM0DLzlpygd7GJf8ZL9SoHSH+gFsYF67Cpooz/YDqWrlN7Vw\ntO00s0B+eXy+PCXYU7VSfuWFGK8TGEv6HfGMALLjhqMManyvfp8hz3ubN1rK3c8C\nUS/ilRh1qckdbtPvoDPhSbTDmfU1g/EfRSIEXBrIMLg9ka/XB9PvWRrekrppnQzP\nhP9YE3x/wbFc5QqQWiRCYyQl/rgIMOXvIxhkfe8H5n1Et4VAorkpEAXdsfN8KSVv\nLSMazVlLp9GYq5SUpqYX3KnxdWBgN7BJoZ4sltsTpHQ/34SXWfu3UmyUveWj7wp0\nx9hwsPirVI00EEea9AbP7NM2rAyu6ukcm4m6ATd2DZJIViq2es6m60AE6SMCmrQF\nwmk4H/kdQgeAELVfGOm2VyJ3z69fQuywz7xu27S6zTKi05Qlnohxol4wVb6OB7qG\nLPRtK9ObgzRo/OPumyXqlzAi/Yvyd1ZQk8labZps3e16bQp8+pVPiumWioMFJDWV\nGZjCmyMSU8V6MB6njbgLHoyg2LCukCAeSjbPGGGYhnKLm1AKSoJh3IpZuqcKCk5C\n8CM1S15HxV78s9dFntEqIokCAwEAAQ== 66ba20fe:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtfB12w4ZgqsXWZDfUAV/\n6Y4aHUKIu3q4SXrNZ7CXF9nXoAVYrS7NAxJdAodsY3vPCN0g5O8DFXR+390LdOuQ\n+HsGKCc1k5tX5ZXld37EZNTNSbR0k+NKhd9h6X3u6wqPOx7SIKxwAQR8qeeFq4pP\nrt9GAGlxtuYgzIIcKJPwE0dZlcBCg+GnptCUZXp/38BP1eYC+xTXSL6Muq1etYfg\nodXdb7Yl+2h1IHuOwo5rjgY5kpY7GcAs8AjGk3lDD/av60OTYccknH0NCVSmPoXK\nvrxDBOn0LQRNBLcAfnTKgHrzy0Q5h4TNkkyTgxkoQw5ObDk9nnabTxql732yy9BY\ns+hM9+dSFO1HKeVXreYSA2n1ndF18YAvAumzgyqzB7I4pMHXq1kC/8bONMJxwSkS\nYm6CoXKyavp7RqGMyeVpRC7tV+blkrrUml0BwNkxE+XnwDRB3xDV6hqgWe0XrifD\nYTfvd9ScZQP83ip0r4IKlq4GMv/R5shcCRJSkSZ6QSGshH40JYSoiwJf5FHbj9ND\n7do0UAqebWo4yNx63j/wb2ULorW3AClv0BCFSdPsIrCStiGdpgJDBR2P2NZOCob3\nG9uMj+wJD6JJg2nWqNJxkANXX37Qf8plgzssrhrgOvB0fjjS7GYhfkfmZTJ0wPOw\nA8+KzFseBh4UFGgue78KwgkCAwEAAQ== ' __Keyring= __KeyringFile="/usr/share/keyrings/ubuntu-archive-keyring.gpg" __SkipSigCheck=0 __SkipEmulation=0 __UseMirror=0 __UnprocessedBuildArgs= while :; do if [[ "$#" -le 0 ]]; then break fi lowerI="$(echo "$1" | tr "[:upper:]" "[:lower:]")" case $lowerI in -\?|-h|--help) usage ;; arm) __BuildArch=arm __UbuntuArch=armhf __AlpineArch=armv7 __QEMUArch=arm ;; arm64) __BuildArch=arm64 __UbuntuArch=arm64 __AlpineArch=aarch64 __QEMUArch=aarch64 __FreeBSDArch=arm64 __FreeBSDMachineArch=aarch64 ;; armel) __BuildArch=armel __UbuntuArch=armel __UbuntuRepo="http://ftp.debian.org/debian/" __CodeName=jessie __KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg" ;; armv6) __BuildArch=armv6 __UbuntuArch=armhf __QEMUArch=arm __UbuntuRepo="http://raspbian.raspberrypi.org/raspbian/" __CodeName=buster __KeyringFile="/usr/share/keyrings/raspbian-archive-keyring.gpg" __LLDB_Package="liblldb-6.0-dev" __UbuntuSuites= if [[ -e "$__KeyringFile" ]]; then __Keyring="--keyring $__KeyringFile" fi ;; loongarch64) __BuildArch=loongarch64 __AlpineArch=loongarch64 __QEMUArch=loongarch64 __UbuntuArch=loong64 __UbuntuSuites= __DebianSuites=unreleased __LLDB_Package="liblldb-19-dev" if [[ "$__CodeName" == "sid" ]]; then __UbuntuRepo="http://ftp.ports.debian.org/debian-ports/" fi ;; riscv64) __BuildArch=riscv64 __AlpineArch=riscv64 __AlpinePackages="${__AlpinePackages// lldb-dev/}" __QEMUArch=riscv64 __UbuntuArch=riscv64 __UbuntuPackages="${__UbuntuPackages// libunwind8-dev/}" unset __LLDB_Package ;; ppc64le) __BuildArch=ppc64le __AlpineArch=ppc64le __QEMUArch=ppc64le __UbuntuArch=ppc64el __UbuntuRepo="http://ports.ubuntu.com/ubuntu-ports/" __UbuntuPackages="${__UbuntuPackages// libunwind8-dev/}" __UbuntuPackages="${__UbuntuPackages// libomp-dev/}" __UbuntuPackages="${__UbuntuPackages// libomp5/}" unset __LLDB_Package ;; s390x) __BuildArch=s390x __AlpineArch=s390x __QEMUArch=s390x __UbuntuArch=s390x __UbuntuRepo="http://ports.ubuntu.com/ubuntu-ports/" __UbuntuPackages="${__UbuntuPackages// libunwind8-dev/}" __UbuntuPackages="${__UbuntuPackages// libomp-dev/}" __UbuntuPackages="${__UbuntuPackages// libomp5/}" unset __LLDB_Package ;; x64) __BuildArch=x64 __AlpineArch=x86_64 __UbuntuArch=amd64 __FreeBSDArch=amd64 __FreeBSDMachineArch=amd64 __illumosArch=x86_64 __HaikuArch=x86_64 __UbuntuRepo="http://archive.ubuntu.com/ubuntu/" ;; x86) __BuildArch=x86 __UbuntuArch=i386 __AlpineArch=x86 __UbuntuRepo="http://archive.ubuntu.com/ubuntu/" ;; lldb*) version="$(echo "$lowerI" | tr -d '[:alpha:]-=')" majorVersion="${version%%.*}" [ -z "${version##*.*}" ] && minorVersion="${version#*.}" if [ -z "$minorVersion" ]; then minorVersion=0 fi # for versions > 6.0, lldb has dropped the minor version if [ "$majorVersion" -le 6 ]; then version="$majorVersion.$minorVersion" else version="$majorVersion" fi __LLDB_Package="liblldb-${version}-dev" ;; no-lldb) unset __LLDB_Package ;; llvm*) version="$(echo "$lowerI" | tr -d '[:alpha:]-=')" __LLVM_MajorVersion="${version%%.*}" [ -z "${version##*.*}" ] && __LLVM_MinorVersion="${version#*.}" if [ -z "$__LLVM_MinorVersion" ]; then __LLVM_MinorVersion=0 fi # for versions > 6.0, lldb has dropped the minor version if [ "$__LLVM_MajorVersion" -gt 6 ]; then __LLVM_MinorVersion= fi ;; xenial) # Ubuntu 16.04 if [[ "$__CodeName" != "jessie" ]]; then __CodeName=xenial fi ;; zesty) # Ubuntu 17.04 if [[ "$__CodeName" != "jessie" ]]; then __CodeName=zesty fi ;; bionic) # Ubuntu 18.04 if [[ "$__CodeName" != "jessie" ]]; then __CodeName=bionic fi ;; focal) # Ubuntu 20.04 if [[ "$__CodeName" != "jessie" ]]; then __CodeName=focal fi ;; jammy) # Ubuntu 22.04 if [[ "$__CodeName" != "jessie" ]]; then __CodeName=jammy fi ;; noble) # Ubuntu 24.04 if [[ "$__CodeName" != "jessie" ]]; then __CodeName=noble fi if [[ -n "$__LLDB_Package" ]]; then __LLDB_Package="liblldb-18-dev" fi ;; jessie) # Debian 8 __CodeName=jessie __KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg" if [[ -z "$__UbuntuRepo" ]]; then __UbuntuRepo="http://ftp.debian.org/debian/" fi ;; stretch) # Debian 9 __CodeName=stretch __LLDB_Package="liblldb-6.0-dev" __KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg" if [[ -z "$__UbuntuRepo" ]]; then __UbuntuRepo="http://ftp.debian.org/debian/" fi ;; buster) # Debian 10 __CodeName=buster __LLDB_Package="liblldb-6.0-dev" __KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg" if [[ -z "$__UbuntuRepo" ]]; then __UbuntuRepo="http://ftp.debian.org/debian/" fi ;; bullseye) # Debian 11 __CodeName=bullseye __KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg" if [[ -z "$__UbuntuRepo" ]]; then __UbuntuRepo="http://ftp.debian.org/debian/" fi ;; bookworm) # Debian 12 __CodeName=bookworm __KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg" if [[ -z "$__UbuntuRepo" ]]; then __UbuntuRepo="http://ftp.debian.org/debian/" fi ;; sid) # Debian sid __CodeName=sid __UbuntuSuites= # Debian-Ports architectures need different values case "$__UbuntuArch" in amd64|arm64|armel|armhf|i386|mips64el|ppc64el|riscv64|s390x) __KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg" if [[ -z "$__UbuntuRepo" ]]; then __UbuntuRepo="http://ftp.debian.org/debian/" fi ;; *) __KeyringFile="/usr/share/keyrings/debian-ports-archive-keyring.gpg" if [[ -z "$__UbuntuRepo" ]]; then __UbuntuRepo="http://ftp.ports.debian.org/debian-ports/" fi ;; esac if [[ -e "$__KeyringFile" ]]; then __Keyring="--keyring $__KeyringFile" fi ;; tizen) __CodeName= __UbuntuRepo= __Tizen=tizen ;; alpine*) __CodeName=alpine __UbuntuRepo= if [[ "$lowerI" == "alpineedge" ]]; then __AlpineVersion=edge else version="$(echo "$lowerI" | tr -d '[:alpha:]-=')" __AlpineMajorVersion="${version%%.*}" __AlpineMinorVersion="${version#*.}" __AlpineVersion="$__AlpineMajorVersion.$__AlpineMinorVersion" fi ;; freebsd13) __CodeName=freebsd __SkipUnmount=1 ;; freebsd14) __CodeName=freebsd __FreeBSDBase="14.2-RELEASE" __FreeBSDABI="14" __SkipUnmount=1 ;; illumos) __CodeName=illumos __SkipUnmount=1 ;; haiku) __CodeName=haiku __SkipUnmount=1 ;; --skipunmount) __SkipUnmount=1 ;; --skipsigcheck) __SkipSigCheck=1 ;; --skipemulation) __SkipEmulation=1 ;; --rootfsdir|-rootfsdir) shift __RootfsDir="$1" ;; --use-mirror) __UseMirror=1 ;; --use-jobs) shift MAXJOBS=$1 ;; *) __UnprocessedBuildArgs="$__UnprocessedBuildArgs $1" ;; esac shift done case "$__AlpineVersion" in 3.14) __AlpinePackages+=" llvm11-libs" ;; 3.15) __AlpinePackages+=" llvm12-libs" ;; 3.16) __AlpinePackages+=" llvm13-libs" ;; 3.17) __AlpinePackages+=" llvm15-libs" ;; edge) __AlpineLlvmLibsLookup=1 ;; *) if [[ "$__AlpineArch" =~ s390x|ppc64le ]]; then __AlpineVersion=3.15 # minimum version that supports lldb-dev __AlpinePackages+=" llvm12-libs" elif [[ "$__AlpineArch" == "x86" ]]; then __AlpineVersion=3.17 # minimum version that supports lldb-dev __AlpinePackages+=" llvm15-libs" elif [[ "$__AlpineArch" == "riscv64" || "$__AlpineArch" == "loongarch64" ]]; then __AlpineVersion=3.21 # minimum version that supports lldb-dev __AlpinePackages+=" llvm19-libs" elif [[ -n "$__AlpineMajorVersion" ]]; then # use whichever alpine version is provided and select the latest toolchain libs __AlpineLlvmLibsLookup=1 else __AlpineVersion=3.13 # 3.13 to maximize compatibility __AlpinePackages+=" llvm10-libs" fi esac if [[ "$__AlpineVersion" =~ 3\.1[345] ]]; then # compiler-rt--static was merged in compiler-rt package in alpine 3.16 # for older versions, we need compiler-rt--static, so replace the name __AlpinePackages="${__AlpinePackages/compiler-rt/compiler-rt-static}" fi if [[ "$__BuildArch" == "armel" ]]; then __LLDB_Package="lldb-3.5-dev" fi __UbuntuPackages+=" ${__LLDB_Package:-}" if [[ -z "$__UbuntuRepo" ]]; then __UbuntuRepo="http://ports.ubuntu.com/" fi if [[ -n "$__LLVM_MajorVersion" ]]; then __UbuntuPackages+=" libclang-common-${__LLVM_MajorVersion}${__LLVM_MinorVersion:+.$__LLVM_MinorVersion}-dev" fi if [[ -z "$__RootfsDir" && -n "$ROOTFS_DIR" ]]; then __RootfsDir="$ROOTFS_DIR" fi if [[ -z "$__RootfsDir" ]]; then __RootfsDir="$__CrossDir/../../../.tools/rootfs/$__BuildArch" fi if [[ -d "$__RootfsDir" ]]; then if [[ "$__SkipUnmount" == "0" ]]; then umount "$__RootfsDir"/* || true fi rm -rf "$__RootfsDir" fi mkdir -p "$__RootfsDir" __RootfsDir="$( cd "$__RootfsDir" && pwd )" __hasWget= ensureDownloadTool() { if command -v wget &> /dev/null; then __hasWget=1 elif command -v curl &> /dev/null; then __hasWget=0 else >&2 echo "ERROR: either wget or curl is required by this script." exit 1 fi } if [[ "$__CodeName" == "alpine" ]]; then __ApkToolsVersion=2.12.11 __ApkToolsDir="$(mktemp -d)" __ApkKeysDir="$(mktemp -d)" arch="$(uname -m)" ensureDownloadTool if [[ "$__hasWget" == 1 ]]; then wget -P "$__ApkToolsDir" "https://gitlab.alpinelinux.org/api/v4/projects/5/packages/generic/v$__ApkToolsVersion/$arch/apk.static" else curl -SLO --create-dirs --output-dir "$__ApkToolsDir" "https://gitlab.alpinelinux.org/api/v4/projects/5/packages/generic/v$__ApkToolsVersion/$arch/apk.static" fi if [[ "$arch" == "x86_64" ]]; then __ApkToolsSHA512SUM="53e57b49230da07ef44ee0765b9592580308c407a8d4da7125550957bb72cb59638e04f8892a18b584451c8d841d1c7cb0f0ab680cc323a3015776affaa3be33" elif [[ "$arch" == "aarch64" ]]; then __ApkToolsSHA512SUM="9e2b37ecb2b56c05dad23d379be84fd494c14bd730b620d0d576bda760588e1f2f59a7fcb2f2080577e0085f23a0ca8eadd993b4e61c2ab29549fdb71969afd0" else echo "WARNING: add missing hash for your host architecture. To find the value, use: 'find /tmp -name apk.static -exec sha512sum {} \;'" fi echo "$__ApkToolsSHA512SUM $__ApkToolsDir/apk.static" | sha512sum -c chmod +x "$__ApkToolsDir/apk.static" if [[ "$__AlpineVersion" == "edge" ]]; then version=edge else version="v$__AlpineVersion" fi for line in $__AlpineKeys; do id="${line%%:*}" content="${line#*:}" echo -e "-----BEGIN PUBLIC KEY-----\n$content\n-----END PUBLIC KEY-----" > "$__ApkKeysDir/alpine-devel@lists.alpinelinux.org-$id.rsa.pub" done if [[ "$__SkipSigCheck" == "1" ]]; then __ApkSignatureArg="--allow-untrusted" else __ApkSignatureArg="--keys-dir $__ApkKeysDir" fi if [[ "$__SkipEmulation" == "1" ]]; then __NoEmulationArg="--no-scripts" fi # initialize DB # shellcheck disable=SC2086 "$__ApkToolsDir/apk.static" \ -X "http://dl-cdn.alpinelinux.org/alpine/$version/main" \ -X "http://dl-cdn.alpinelinux.org/alpine/$version/community" \ -U $__ApkSignatureArg --root "$__RootfsDir" --arch "$__AlpineArch" --initdb add if [[ "$__AlpineLlvmLibsLookup" == 1 ]]; then # shellcheck disable=SC2086 __AlpinePackages+=" $("$__ApkToolsDir/apk.static" \ -X "http://dl-cdn.alpinelinux.org/alpine/$version/main" \ -X "http://dl-cdn.alpinelinux.org/alpine/$version/community" \ -U $__ApkSignatureArg --root "$__RootfsDir" --arch "$__AlpineArch" \ search 'llvm*-libs' | grep -E '^llvm' | sort | tail -1 | sed 's/-[^-]*//2g')" fi # install all packages in one go # shellcheck disable=SC2086 "$__ApkToolsDir/apk.static" \ -X "http://dl-cdn.alpinelinux.org/alpine/$version/main" \ -X "http://dl-cdn.alpinelinux.org/alpine/$version/community" \ -U $__ApkSignatureArg --root "$__RootfsDir" --arch "$__AlpineArch" $__NoEmulationArg \ add $__AlpinePackages rm -r "$__ApkToolsDir" elif [[ "$__CodeName" == "freebsd" ]]; then mkdir -p "$__RootfsDir"/usr/local/etc JOBS=${MAXJOBS:="$(getconf _NPROCESSORS_ONLN)"} ensureDownloadTool if [[ "$__hasWget" == 1 ]]; then wget -O- "https://download.freebsd.org/ftp/releases/${__FreeBSDArch}/${__FreeBSDMachineArch}/${__FreeBSDBase}/base.txz" | tar -C "$__RootfsDir" -Jxf - ./lib ./usr/lib ./usr/libdata ./usr/include ./usr/share/keys ./etc ./bin/freebsd-version else curl -SL "https://download.freebsd.org/ftp/releases/${__FreeBSDArch}/${__FreeBSDMachineArch}/${__FreeBSDBase}/base.txz" | tar -C "$__RootfsDir" -Jxf - ./lib ./usr/lib ./usr/libdata ./usr/include ./usr/share/keys ./etc ./bin/freebsd-version fi echo "ABI = \"FreeBSD:${__FreeBSDABI}:${__FreeBSDMachineArch}\"; FINGERPRINTS = \"${__RootfsDir}/usr/share/keys\"; REPOS_DIR = [\"${__RootfsDir}/etc/pkg\"]; REPO_AUTOUPDATE = NO; RUN_SCRIPTS = NO;" > "${__RootfsDir}"/usr/local/etc/pkg.conf echo "FreeBSD: { url: \"pkg+http://pkg.FreeBSD.org/\${ABI}/quarterly\", mirror_type: \"srv\", signature_type: \"fingerprints\", fingerprints: \"/usr/share/keys/pkg\", enabled: yes }" > "${__RootfsDir}"/etc/pkg/FreeBSD.conf mkdir -p "$__RootfsDir"/tmp # get and build package manager if [[ "$__hasWget" == 1 ]]; then wget -O- "https://github.com/freebsd/pkg/archive/${__FreeBSDPkg}.tar.gz" | tar -C "$__RootfsDir"/tmp -zxf - else curl -SL "https://github.com/freebsd/pkg/archive/${__FreeBSDPkg}.tar.gz" | tar -C "$__RootfsDir"/tmp -zxf - fi cd "$__RootfsDir/tmp/pkg-${__FreeBSDPkg}" # needed for install to succeed mkdir -p "$__RootfsDir"/host/etc ./autogen.sh && ./configure --prefix="$__RootfsDir"/host && make -j "$JOBS" && make install rm -rf "$__RootfsDir/tmp/pkg-${__FreeBSDPkg}" # install packages we need. INSTALL_AS_USER=$(whoami) "$__RootfsDir"/host/sbin/pkg -r "$__RootfsDir" -C "$__RootfsDir"/usr/local/etc/pkg.conf update # shellcheck disable=SC2086 INSTALL_AS_USER=$(whoami) "$__RootfsDir"/host/sbin/pkg -r "$__RootfsDir" -C "$__RootfsDir"/usr/local/etc/pkg.conf install --yes $__FreeBSDPackages elif [[ "$__CodeName" == "illumos" ]]; then mkdir "$__RootfsDir/tmp" pushd "$__RootfsDir/tmp" JOBS=${MAXJOBS:="$(getconf _NPROCESSORS_ONLN)"} ensureDownloadTool echo "Downloading sysroot." if [[ "$__hasWget" == 1 ]]; then wget -O- https://github.com/illumos/sysroot/releases/download/20181213-de6af22ae73b-v1/illumos-sysroot-i386-20181213-de6af22ae73b-v1.tar.gz | tar -C "$__RootfsDir" -xzf - else curl -SL https://github.com/illumos/sysroot/releases/download/20181213-de6af22ae73b-v1/illumos-sysroot-i386-20181213-de6af22ae73b-v1.tar.gz | tar -C "$__RootfsDir" -xzf - fi echo "Building binutils. Please wait.." if [[ "$__hasWget" == 1 ]]; then wget -O- https://ftp.gnu.org/gnu/binutils/binutils-2.42.tar.xz | tar -xJf - else curl -SL https://ftp.gnu.org/gnu/binutils/binutils-2.42.tar.xz | tar -xJf - fi mkdir build-binutils && cd build-binutils ../binutils-2.42/configure --prefix="$__RootfsDir" --target="${__illumosArch}-sun-solaris2.11" --program-prefix="${__illumosArch}-illumos-" --with-sysroot="$__RootfsDir" make -j "$JOBS" && make install && cd .. echo "Building gcc. Please wait.." if [[ "$__hasWget" == 1 ]]; then wget -O- https://ftp.gnu.org/gnu/gcc/gcc-13.3.0/gcc-13.3.0.tar.xz | tar -xJf - else curl -SL https://ftp.gnu.org/gnu/gcc/gcc-13.3.0/gcc-13.3.0.tar.xz | tar -xJf - fi CFLAGS="-fPIC" CXXFLAGS="-fPIC" CXXFLAGS_FOR_TARGET="-fPIC" CFLAGS_FOR_TARGET="-fPIC" export CFLAGS CXXFLAGS CXXFLAGS_FOR_TARGET CFLAGS_FOR_TARGET mkdir build-gcc && cd build-gcc ../gcc-13.3.0/configure --prefix="$__RootfsDir" --target="${__illumosArch}-sun-solaris2.11" --program-prefix="${__illumosArch}-illumos-" --with-sysroot="$__RootfsDir" --with-gnu-as \ --with-gnu-ld --disable-nls --disable-libgomp --disable-libquadmath --disable-libssp --disable-libvtv --disable-libcilkrts --disable-libada --disable-libsanitizer \ --disable-libquadmath-support --disable-shared --enable-tls make -j "$JOBS" && make install && cd .. BaseUrl=https://pkgsrc.smartos.org if [[ "$__UseMirror" == 1 ]]; then BaseUrl=https://pkgsrc.smartos.skylime.net fi BaseUrl="$BaseUrl/packages/SmartOS/2019Q4/${__illumosArch}/All" echo "Downloading manifest" if [[ "$__hasWget" == 1 ]]; then wget "$BaseUrl" else curl -SLO "$BaseUrl" fi echo "Downloading dependencies." read -ra array <<<"$__IllumosPackages" for package in "${array[@]}"; do echo "Installing '$package'" # find last occurrence of package in listing and extract its name package="$(sed -En '/.*href="('"$package"'-[0-9].*).tgz".*/h;$!d;g;s//\1/p' All)" echo "Resolved name '$package'" if [[ "$__hasWget" == 1 ]]; then wget "$BaseUrl"/"$package".tgz else curl -SLO "$BaseUrl"/"$package".tgz fi ar -x "$package".tgz tar --skip-old-files -xzf "$package".tmp.tg* -C "$__RootfsDir" 2>/dev/null done echo "Cleaning up temporary files." popd rm -rf "$__RootfsDir"/{tmp,+*} mkdir -p "$__RootfsDir"/usr/include/net mkdir -p "$__RootfsDir"/usr/include/netpacket if [[ "$__hasWget" == 1 ]]; then wget -P "$__RootfsDir"/usr/include/net https://raw.githubusercontent.com/illumos/illumos-gate/master/usr/src/uts/common/io/bpf/net/bpf.h wget -P "$__RootfsDir"/usr/include/net https://raw.githubusercontent.com/illumos/illumos-gate/master/usr/src/uts/common/io/bpf/net/dlt.h wget -P "$__RootfsDir"/usr/include/netpacket https://raw.githubusercontent.com/illumos/illumos-gate/master/usr/src/uts/common/inet/sockmods/netpacket/packet.h wget -P "$__RootfsDir"/usr/include/sys https://raw.githubusercontent.com/illumos/illumos-gate/master/usr/src/uts/common/sys/sdt.h else curl -SLO --create-dirs --output-dir "$__RootfsDir"/usr/include/net https://raw.githubusercontent.com/illumos/illumos-gate/master/usr/src/uts/common/io/bpf/net/bpf.h curl -SLO --create-dirs --output-dir "$__RootfsDir"/usr/include/net https://raw.githubusercontent.com/illumos/illumos-gate/master/usr/src/uts/common/io/bpf/net/dlt.h curl -SLO --create-dirs --output-dir "$__RootfsDir"/usr/include/netpacket https://raw.githubusercontent.com/illumos/illumos-gate/master/usr/src/uts/common/inet/sockmods/netpacket/packet.h curl -SLO --create-dirs --output-dir "$__RootfsDir"/usr/include/sys https://raw.githubusercontent.com/illumos/illumos-gate/master/usr/src/uts/common/sys/sdt.h fi elif [[ "$__CodeName" == "haiku" ]]; then JOBS=${MAXJOBS:="$(getconf _NPROCESSORS_ONLN)"} echo "Building Haiku sysroot for $__HaikuArch" mkdir -p "$__RootfsDir/tmp" pushd "$__RootfsDir/tmp" mkdir "$__RootfsDir/tmp/download" ensureDownloadTool echo "Downloading Haiku package tools" git clone https://github.com/haiku/haiku-toolchains-ubuntu --depth 1 "$__RootfsDir/tmp/script" if [[ "$__hasWget" == 1 ]]; then wget -O "$__RootfsDir/tmp/download/hosttools.zip" "$("$__RootfsDir/tmp/script/fetch.sh" --hosttools)" else curl -SLo "$__RootfsDir/tmp/download/hosttools.zip" "$("$__RootfsDir/tmp/script/fetch.sh" --hosttools)" fi unzip -o "$__RootfsDir/tmp/download/hosttools.zip" -d "$__RootfsDir/tmp/bin" HaikuBaseUrl="https://eu.hpkg.haiku-os.org/haiku/master/$__HaikuArch/current" HaikuPortsBaseUrl="https://eu.hpkg.haiku-os.org/haikuports/master/$__HaikuArch/current" echo "Downloading HaikuPorts package repository index..." if [[ "$__hasWget" == 1 ]]; then wget -P "$__RootfsDir/tmp/download" "$HaikuPortsBaseUrl/repo" else curl -SLO --create-dirs --output-dir "$__RootfsDir/tmp/download" "$HaikuPortsBaseUrl/repo" fi echo "Downloading Haiku packages" read -ra array <<<"$__HaikuPackages" for package in "${array[@]}"; do echo "Downloading $package..." hpkgFilename="$(LD_LIBRARY_PATH="$__RootfsDir/tmp/bin" "$__RootfsDir/tmp/bin/package_repo" list -f "$__RootfsDir/tmp/download/repo" | grep -E "${package}-" | sort -V | tail -n 1 | xargs)" if [ -z "$hpkgFilename" ]; then >&2 echo "ERROR: package $package missing." exit 1 fi echo "Resolved filename: $hpkgFilename..." hpkgDownloadUrl="$HaikuPortsBaseUrl/packages/$hpkgFilename" if [[ "$__hasWget" == 1 ]]; then wget -P "$__RootfsDir/tmp/download" "$hpkgDownloadUrl" else curl -SLO --create-dirs --output-dir "$__RootfsDir/tmp/download" "$hpkgDownloadUrl" fi done for package in haiku haiku_devel; do echo "Downloading $package..." if [[ "$__hasWget" == 1 ]]; then hpkgVersion="$(wget -qO- "$HaikuBaseUrl" | sed -n 's/^.*version: "\([^"]*\)".*$/\1/p')" wget -P "$__RootfsDir/tmp/download" "$HaikuBaseUrl/packages/$package-$hpkgVersion-1-$__HaikuArch.hpkg" else hpkgVersion="$(curl -sSL "$HaikuBaseUrl" | sed -n 's/^.*version: "\([^"]*\)".*$/\1/p')" curl -SLO --create-dirs --output-dir "$__RootfsDir/tmp/download" "$HaikuBaseUrl/packages/$package-$hpkgVersion-1-$__HaikuArch.hpkg" fi done # Set up the sysroot echo "Setting up sysroot and extracting required packages" mkdir -p "$__RootfsDir/boot/system" for file in "$__RootfsDir/tmp/download/"*.hpkg; do echo "Extracting $file..." LD_LIBRARY_PATH="$__RootfsDir/tmp/bin" "$__RootfsDir/tmp/bin/package" extract -C "$__RootfsDir/boot/system" "$file" done # Download buildtools echo "Downloading Haiku buildtools" if [[ "$__hasWget" == 1 ]]; then wget -O "$__RootfsDir/tmp/download/buildtools.zip" "$("$__RootfsDir/tmp/script/fetch.sh" --buildtools --arch=$__HaikuArch)" else curl -SLo "$__RootfsDir/tmp/download/buildtools.zip" "$("$__RootfsDir/tmp/script/fetch.sh" --buildtools --arch=$__HaikuArch)" fi unzip -o "$__RootfsDir/tmp/download/buildtools.zip" -d "$__RootfsDir" # Cleaning up temporary files echo "Cleaning up temporary files" popd rm -rf "$__RootfsDir/tmp" elif [[ -n "$__CodeName" ]]; then if [[ "$__SkipEmulation" == "1" ]]; then if [[ -z "$AR" ]]; then if command -v ar &>/dev/null; then AR="$(command -v ar)" elif command -v llvm-ar &>/dev/null; then AR="$(command -v llvm-ar)" else echo "Unable to find ar or llvm-ar on PATH, add them to PATH or set AR environment variable pointing to the available AR tool" exit 1 fi fi # shellcheck disable=SC2086 suites="$__CodeName $__DebianSuites $(echo $__UbuntuSuites | xargs -n 1 | xargs -I {} echo -n "$__CodeName-{} ")" PYTHON=${PYTHON_EXECUTABLE:-python3} # shellcheck disable=SC2086,SC2046 echo running "$PYTHON" "$__CrossDir/install-debs.py" --arch "$__UbuntuArch" --mirror "$__UbuntuRepo" --rootfsdir "$__RootfsDir" --artool "$AR" \ $(echo $suites | xargs -n 1 | xargs -I {} echo -n "--suite {} ") \ $__UbuntuPackages # shellcheck disable=SC2086,SC2046 "$PYTHON" "$__CrossDir/install-debs.py" --arch "$__UbuntuArch" --mirror "$__UbuntuRepo" --rootfsdir "$__RootfsDir" --artool "$AR" \ $(echo $suites | xargs -n 1 | xargs -I {} echo -n "--suite {} ") \ $__UbuntuPackages exit 0 fi __UpdateOptions= if [[ "$__SkipSigCheck" == "0" ]]; then __Keyring="$__Keyring --force-check-gpg" else __Keyring= __UpdateOptions="--allow-unauthenticated --allow-insecure-repositories" fi # shellcheck disable=SC2086 echo running debootstrap "--variant=minbase" $__Keyring --arch "$__UbuntuArch" "$__CodeName" "$__RootfsDir" "$__UbuntuRepo" # shellcheck disable=SC2086 if ! debootstrap "--variant=minbase" $__Keyring --arch "$__UbuntuArch" "$__CodeName" "$__RootfsDir" "$__UbuntuRepo"; then echo "debootstrap failed! dumping debootstrap.log" cat "$__RootfsDir/debootstrap/debootstrap.log" exit 1 fi rm -rf "$__RootfsDir"/etc/apt/*.{sources,list} "$__RootfsDir"/etc/apt/sources.list.d mkdir -p "$__RootfsDir/etc/apt/sources.list.d/" # shellcheck disable=SC2086 cat > "$__RootfsDir/etc/apt/sources.list.d/$__CodeName.sources" < token2) - (token1 < token2) else: return -1 if isinstance(token1, str) else 1 return len(tokens1) - len(tokens2) def compare_debian_versions(version1, version2): """Compare two Debian package versions.""" epoch1, upstream1, revision1 = parse_debian_version(version1) epoch2, upstream2, revision2 = parse_debian_version(version2) if epoch1 != epoch2: return epoch1 - epoch2 result = compare_upstream_version(upstream1, upstream2) if result != 0: return result return compare_upstream_version(revision1, revision2) def resolve_dependencies(packages, aliases, desired_packages): """Recursively resolves dependencies for the desired packages.""" resolved = [] to_process = deque(desired_packages) while to_process: current = to_process.popleft() resolved_package = current if current in packages else aliases.get(current, [None])[0] if not resolved_package: print(f"Error: Package '{current}' was not found in the available packages.") sys.exit(1) if resolved_package not in resolved: resolved.append(resolved_package) deps = packages.get(resolved_package, {}).get("Depends", "") if deps: deps = [dep.split(' ')[0] for dep in deps.split(', ') if dep] for dep in deps: if dep not in resolved and dep not in to_process and dep in packages: to_process.append(dep) return resolved def parse_package_index(content): """Parses the Packages.gz file and returns package information.""" packages = {} aliases = {} entries = re.split(r'\n\n+', content) for entry in entries: fields = dict(re.findall(r'^(\S+): (.+)$', entry, re.MULTILINE)) if "Package" in fields: package_name = fields["Package"] version = fields.get("Version") filename = fields.get("Filename") depends = fields.get("Depends") provides = fields.get("Provides", None) # Only update if package_name is not in packages or if the new version is higher if package_name not in packages or compare_debian_versions(version, packages[package_name]["Version"]) > 0: packages[package_name] = { "Version": version, "Filename": filename, "Depends": depends } # Update aliases if package provides any alternatives if provides: provides_list = [x.strip() for x in provides.split(",")] for alias in provides_list: # Strip version specifiers alias_name = re.sub(r'\s*\(=.*\)', '', alias) if alias_name not in aliases: aliases[alias_name] = [] if package_name not in aliases[alias_name]: aliases[alias_name].append(package_name) return packages, aliases def install_packages(mirror, packages_info, aliases, tmp_dir, extract_dir, ar_tool, desired_packages): """Downloads .deb files and extracts them.""" resolved_packages = resolve_dependencies(packages_info, aliases, desired_packages) print(f"Resolved packages (including dependencies): {resolved_packages}") packages_to_download = {} for pkg in resolved_packages: if pkg in packages_info: packages_to_download[pkg] = packages_info[pkg] if pkg in aliases: for alias in aliases[pkg]: if alias in packages_info: packages_to_download[alias] = packages_info[alias] asyncio.run(download_deb_files_parallel(mirror, packages_to_download, tmp_dir)) package_to_deb_file_map = {} for pkg in resolved_packages: pkg_info = packages_info.get(pkg) if pkg_info: deb_filename = pkg_info.get("Filename") if deb_filename: deb_file_path = os.path.join(tmp_dir, os.path.basename(deb_filename)) package_to_deb_file_map[pkg] = deb_file_path for pkg in reversed(resolved_packages): deb_file = package_to_deb_file_map.get(pkg) if deb_file and os.path.exists(deb_file): extract_deb_file(deb_file, tmp_dir, extract_dir, ar_tool) print("All done!") def extract_deb_file(deb_file, tmp_dir, extract_dir, ar_tool): """Extract .deb file contents""" os.makedirs(extract_dir, exist_ok=True) with tempfile.TemporaryDirectory(dir=tmp_dir) as tmp_subdir: result = subprocess.run(f"{ar_tool} t {os.path.abspath(deb_file)}", cwd=tmp_subdir, check=True, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) tar_filename = None for line in result.stdout.decode().splitlines(): if line.startswith("data.tar"): tar_filename = line.strip() break if not tar_filename: raise FileNotFoundError(f"Could not find 'data.tar.*' in {deb_file}.") tar_file_path = os.path.join(tmp_subdir, tar_filename) print(f"Extracting {tar_filename} from {deb_file}..") subprocess.run(f"{ar_tool} p {os.path.abspath(deb_file)} {tar_filename} > {tar_file_path}", check=True, shell=True) file_extension = os.path.splitext(tar_file_path)[1].lower() if file_extension == ".xz": mode = "r:xz" elif file_extension == ".gz": mode = "r:gz" elif file_extension == ".zst": # zstd is not supported by standard library yet decompressed_tar_path = tar_file_path.replace(".zst", "") with open(tar_file_path, "rb") as zst_file, open(decompressed_tar_path, "wb") as decompressed_file: dctx = zstandard.ZstdDecompressor() dctx.copy_stream(zst_file, decompressed_file) tar_file_path = decompressed_tar_path mode = "r" else: raise ValueError(f"Unsupported compression format: {file_extension}") with tarfile.open(tar_file_path, mode) as tar: tar.extractall(path=extract_dir, filter='fully_trusted') def finalize_setup(rootfsdir): lib_dir = os.path.join(rootfsdir, 'lib') usr_lib_dir = os.path.join(rootfsdir, 'usr', 'lib') if os.path.exists(lib_dir): if os.path.islink(lib_dir): os.remove(lib_dir) else: os.makedirs(usr_lib_dir, exist_ok=True) for item in os.listdir(lib_dir): src = os.path.join(lib_dir, item) dest = os.path.join(usr_lib_dir, item) if os.path.isdir(src): shutil.copytree(src, dest, dirs_exist_ok=True) else: shutil.copy2(src, dest) shutil.rmtree(lib_dir) os.symlink(usr_lib_dir, lib_dir) if __name__ == "__main__": parser = argparse.ArgumentParser(description="Generate rootfs for .NET runtime on Debian-like OS") parser.add_argument("--distro", required=False, help="Distro name (e.g., debian, ubuntu, etc.)") parser.add_argument("--arch", required=True, help="Architecture (e.g., amd64, loong64, etc.)") parser.add_argument("--rootfsdir", required=True, help="Destination directory.") parser.add_argument('--suite', required=True, action='append', help='Specify one or more repository suites to collect index data.') parser.add_argument("--mirror", required=False, help="Mirror (e.g., http://ftp.debian.org/debian-ports etc.)") parser.add_argument("--artool", required=False, default="ar", help="ar tool to extract debs (e.g., ar, llvm-ar etc.)") parser.add_argument("packages", nargs="+", help="List of package names to be installed.") args = parser.parse_args() if args.mirror is None: if args.distro == "ubuntu": args.mirror = "http://archive.ubuntu.com/ubuntu" if args.arch in ["amd64", "i386"] else "http://ports.ubuntu.com/ubuntu-ports" elif args.distro == "debian": args.mirror = "http://ftp.debian.org/debian-ports" else: raise Exception("Unsupported distro") DESIRED_PACKAGES = args.packages + [ # base packages "dpkg", "busybox", "libc-bin", "base-files", "base-passwd", "debianutils" ] print(f"Creating rootfs. rootfsdir: {args.rootfsdir}, distro: {args.distro}, arch: {args.arch}, suites: {args.suite}, mirror: {args.mirror}") package_index_content = asyncio.run(download_package_index_parallel(args.mirror, args.arch, args.suite)) packages_info, aliases = parse_package_index(package_index_content) with tempfile.TemporaryDirectory() as tmp_dir: install_packages(args.mirror, packages_info, aliases, tmp_dir, args.rootfsdir, args.artool, DESIRED_PACKAGES) finalize_setup(args.rootfsdir) ================================================ FILE: eng/common/cross/riscv64/tizen/tizen.patch ================================================ diff -u -r a/usr/lib/libc.so b/usr/lib/libc.so --- a/usr/lib64/libc.so 2016-12-30 23:00:08.284951863 +0900 +++ b/usr/lib64/libc.so 2016-12-30 23:00:32.140951815 +0900 @@ -2,4 +2,4 @@ Use the shared library, but some functions are only in the static library, so try that secondarily. */ OUTPUT_FORMAT(elf64-littleriscv) -GROUP ( /lib64/libc.so.6 /usr/lib64/libc_nonshared.a AS_NEEDED ( /lib64/ld-linux-riscv64-lp64d.so.1 ) ) +GROUP ( libc.so.6 libc_nonshared.a AS_NEEDED ( ld-linux-riscv64-lp64d.so.1 ) ) ================================================ FILE: eng/common/cross/tizen-build-rootfs.sh ================================================ #!/usr/bin/env bash set -e ARCH=$1 LINK_ARCH=$ARCH case "$ARCH" in arm) TIZEN_ARCH="armv7hl" ;; armel) TIZEN_ARCH="armv7l" LINK_ARCH="arm" ;; arm64) TIZEN_ARCH="aarch64" ;; x86) TIZEN_ARCH="i686" ;; x64) TIZEN_ARCH="x86_64" LINK_ARCH="x86" ;; riscv64) TIZEN_ARCH="riscv64" LINK_ARCH="riscv" ;; *) echo "Unsupported architecture for tizen: $ARCH" exit 1 esac __CrossDir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) __TIZEN_CROSSDIR="$__CrossDir/${ARCH}/tizen" if [[ -z "$ROOTFS_DIR" ]]; then echo "ROOTFS_DIR is not defined." exit 1; fi TIZEN_TMP_DIR=$ROOTFS_DIR/tizen_tmp mkdir -p $TIZEN_TMP_DIR # Download files echo ">>Start downloading files" VERBOSE=1 $__CrossDir/tizen-fetch.sh $TIZEN_TMP_DIR $TIZEN_ARCH echo "<>Start constructing Tizen rootfs" TIZEN_RPM_FILES=`ls $TIZEN_TMP_DIR/*.rpm` cd $ROOTFS_DIR for f in $TIZEN_RPM_FILES; do rpm2cpio $f | cpio -idm --quiet done echo "<>Start configuring Tizen rootfs" ln -sfn asm-${LINK_ARCH} ./usr/include/asm patch -p1 < $__TIZEN_CROSSDIR/tizen.patch if [[ "$TIZEN_ARCH" == "riscv64" ]]; then echo "Fixing broken symlinks in $PWD" rm ./usr/lib64/libresolv.so ln -s ../../lib64/libresolv.so.2 ./usr/lib64/libresolv.so rm ./usr/lib64/libpthread.so ln -s ../../lib64/libpthread.so.0 ./usr/lib64/libpthread.so rm ./usr/lib64/libdl.so ln -s ../../lib64/libdl.so.2 ./usr/lib64/libdl.so rm ./usr/lib64/libutil.so ln -s ../../lib64/libutil.so.1 ./usr/lib64/libutil.so rm ./usr/lib64/libm.so ln -s ../../lib64/libm.so.6 ./usr/lib64/libm.so rm ./usr/lib64/librt.so ln -s ../../lib64/librt.so.1 ./usr/lib64/librt.so rm ./lib/ld-linux-riscv64-lp64d.so.1 ln -s ../lib64/ld-linux-riscv64-lp64d.so.1 ./lib/ld-linux-riscv64-lp64d.so.1 fi echo "</dev/null; then VERBOSE=0 fi Log() { if [ $VERBOSE -ge 1 ]; then echo ${@:2} fi } Inform() { Log 1 -e "\x1B[0;34m$@\x1B[m" } Debug() { Log 2 -e "\x1B[0;32m$@\x1B[m" } Error() { >&2 Log 0 -e "\x1B[0;31m$@\x1B[m" } Fetch() { URL=$1 FILE=$2 PROGRESS=$3 if [ $VERBOSE -ge 1 ] && [ $PROGRESS ]; then CURL_OPT="--progress-bar" else CURL_OPT="--silent" fi curl $CURL_OPT $URL > $FILE } hash curl 2> /dev/null || { Error "Require 'curl' Aborting."; exit 1; } hash xmllint 2> /dev/null || { Error "Require 'xmllint' Aborting."; exit 1; } hash sha256sum 2> /dev/null || { Error "Require 'sha256sum' Aborting."; exit 1; } TMPDIR=$1 if [ ! -d $TMPDIR ]; then TMPDIR=./tizen_tmp Debug "Create temporary directory : $TMPDIR" mkdir -p $TMPDIR fi TIZEN_ARCH=$2 TIZEN_URL=http://download.tizen.org/snapshots/TIZEN/Tizen BUILD_XML=build.xml REPOMD_XML=repomd.xml PRIMARY_XML=primary.xml TARGET_URL="http://__not_initialized" Xpath_get() { XPATH_RESULT='' XPATH=$1 XML_FILE=$2 RESULT=$(xmllint --xpath $XPATH $XML_FILE) if [[ -z ${RESULT// } ]]; then Error "Can not find target from $XML_FILE" Debug "Xpath = $XPATH" exit 1 fi XPATH_RESULT=$RESULT } fetch_tizen_pkgs_init() { TARGET=$1 PROFILE=$2 Debug "Initialize TARGET=$TARGET, PROFILE=$PROFILE" TMP_PKG_DIR=$TMPDIR/tizen_${PROFILE}_pkgs if [ -d $TMP_PKG_DIR ]; then rm -rf $TMP_PKG_DIR; fi mkdir -p $TMP_PKG_DIR PKG_URL=$TIZEN_URL/$PROFILE/latest BUILD_XML_URL=$PKG_URL/$BUILD_XML TMP_BUILD=$TMP_PKG_DIR/$BUILD_XML TMP_REPOMD=$TMP_PKG_DIR/$REPOMD_XML TMP_PRIMARY=$TMP_PKG_DIR/$PRIMARY_XML TMP_PRIMARYGZ=${TMP_PRIMARY}.gz Fetch $BUILD_XML_URL $TMP_BUILD Debug "fetch $BUILD_XML_URL to $TMP_BUILD" TARGET_XPATH="//build/buildtargets/buildtarget[@name=\"$TARGET\"]/repo[@type=\"binary\"]/text()" Xpath_get $TARGET_XPATH $TMP_BUILD TARGET_PATH=$XPATH_RESULT TARGET_URL=$PKG_URL/$TARGET_PATH REPOMD_URL=$TARGET_URL/repodata/repomd.xml PRIMARY_XPATH='string(//*[local-name()="data"][@type="primary"]/*[local-name()="location"]/@href)' Fetch $REPOMD_URL $TMP_REPOMD Debug "fetch $REPOMD_URL to $TMP_REPOMD" Xpath_get $PRIMARY_XPATH $TMP_REPOMD PRIMARY_XML_PATH=$XPATH_RESULT PRIMARY_URL=$TARGET_URL/$PRIMARY_XML_PATH Fetch $PRIMARY_URL $TMP_PRIMARYGZ Debug "fetch $PRIMARY_URL to $TMP_PRIMARYGZ" gunzip $TMP_PRIMARYGZ Debug "unzip $TMP_PRIMARYGZ to $TMP_PRIMARY" } fetch_tizen_pkgs() { ARCH=$1 PACKAGE_XPATH_TPL='string(//*[local-name()="metadata"]/*[local-name()="package"][*[local-name()="name"][text()="_PKG_"]][*[local-name()="arch"][text()="_ARCH_"]]/*[local-name()="location"]/@href)' PACKAGE_CHECKSUM_XPATH_TPL='string(//*[local-name()="metadata"]/*[local-name()="package"][*[local-name()="name"][text()="_PKG_"]][*[local-name()="arch"][text()="_ARCH_"]]/*[local-name()="checksum"]/text())' for pkg in ${@:2} do Inform "Fetching... $pkg" XPATH=${PACKAGE_XPATH_TPL/_PKG_/$pkg} XPATH=${XPATH/_ARCH_/$ARCH} Xpath_get $XPATH $TMP_PRIMARY PKG_PATH=$XPATH_RESULT XPATH=${PACKAGE_CHECKSUM_XPATH_TPL/_PKG_/$pkg} XPATH=${XPATH/_ARCH_/$ARCH} Xpath_get $XPATH $TMP_PRIMARY CHECKSUM=$XPATH_RESULT PKG_URL=$TARGET_URL/$PKG_PATH PKG_FILE=$(basename $PKG_PATH) PKG_PATH=$TMPDIR/$PKG_FILE Debug "Download $PKG_URL to $PKG_PATH" Fetch $PKG_URL $PKG_PATH true echo "$CHECKSUM $PKG_PATH" | sha256sum -c - > /dev/null if [ $? -ne 0 ]; then Error "Fail to fetch $PKG_URL to $PKG_PATH" Debug "Checksum = $CHECKSUM" exit 1 fi done } if [ "$TIZEN_ARCH" == "riscv64" ]; then BASE="Tizen-Base-RISCV" UNIFIED="Tizen-Unified-RISCV" else BASE="Tizen-Base" UNIFIED="Tizen-Unified" fi Inform "Initialize ${TIZEN_ARCH} base" fetch_tizen_pkgs_init standard $BASE Inform "fetch common packages" fetch_tizen_pkgs ${TIZEN_ARCH} gcc gcc-devel-static glibc glibc-devel libicu libicu-devel libatomic linux-glibc-devel keyutils keyutils-devel libkeyutils Inform "fetch coreclr packages" fetch_tizen_pkgs ${TIZEN_ARCH} libgcc libstdc++ libstdc++-devel libunwind libunwind-devel lttng-ust-devel lttng-ust userspace-rcu-devel userspace-rcu if [ "$TIZEN_ARCH" != "riscv64" ]; then fetch_tizen_pkgs ${TIZEN_ARCH} lldb lldb-devel fi Inform "fetch corefx packages" fetch_tizen_pkgs ${TIZEN_ARCH} libcom_err libcom_err-devel zlib zlib-devel libopenssl11 libopenssl1.1-devel krb5 krb5-devel Inform "Initialize standard unified" fetch_tizen_pkgs_init standard $UNIFIED Inform "fetch corefx packages" fetch_tizen_pkgs ${TIZEN_ARCH} gssdp gssdp-devel tizen-release ================================================ FILE: eng/common/cross/toolchain.cmake ================================================ set(CROSS_ROOTFS $ENV{ROOTFS_DIR}) # reset platform variables (e.g. cmake 3.25 sets LINUX=1) unset(LINUX) unset(FREEBSD) unset(ILLUMOS) unset(ANDROID) unset(TIZEN) unset(HAIKU) set(TARGET_ARCH_NAME $ENV{TARGET_BUILD_ARCH}) if(EXISTS ${CROSS_ROOTFS}/bin/freebsd-version) set(CMAKE_SYSTEM_NAME FreeBSD) set(FREEBSD 1) elseif(EXISTS ${CROSS_ROOTFS}/usr/platform/i86pc) set(CMAKE_SYSTEM_NAME SunOS) set(ILLUMOS 1) elseif(EXISTS ${CROSS_ROOTFS}/boot/system/develop/headers/config/HaikuConfig.h) set(CMAKE_SYSTEM_NAME Haiku) set(HAIKU 1) else() set(CMAKE_SYSTEM_NAME Linux) set(LINUX 1) endif() set(CMAKE_SYSTEM_VERSION 1) if(EXISTS ${CROSS_ROOTFS}/etc/tizen-release) set(TIZEN 1) elseif(EXISTS ${CROSS_ROOTFS}/android_platform) set(ANDROID 1) endif() if(TARGET_ARCH_NAME STREQUAL "arm") set(CMAKE_SYSTEM_PROCESSOR armv7l) if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/armv7-alpine-linux-musleabihf) set(TOOLCHAIN "armv7-alpine-linux-musleabihf") elseif(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/armv6-alpine-linux-musleabihf) set(TOOLCHAIN "armv6-alpine-linux-musleabihf") else() set(TOOLCHAIN "arm-linux-gnueabihf") endif() if(TIZEN) set(TIZEN_TOOLCHAIN "armv7hl-tizen-linux-gnueabihf") endif() elseif(TARGET_ARCH_NAME STREQUAL "arm64") set(CMAKE_SYSTEM_PROCESSOR aarch64) if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/aarch64-alpine-linux-musl) set(TOOLCHAIN "aarch64-alpine-linux-musl") elseif(LINUX) set(TOOLCHAIN "aarch64-linux-gnu") if(TIZEN) set(TIZEN_TOOLCHAIN "aarch64-tizen-linux-gnu") endif() elseif(FREEBSD) set(triple "aarch64-unknown-freebsd12") endif() elseif(TARGET_ARCH_NAME STREQUAL "armel") set(CMAKE_SYSTEM_PROCESSOR armv7l) set(TOOLCHAIN "arm-linux-gnueabi") if(TIZEN) set(TIZEN_TOOLCHAIN "armv7l-tizen-linux-gnueabi") endif() elseif(TARGET_ARCH_NAME STREQUAL "armv6") set(CMAKE_SYSTEM_PROCESSOR armv6l) if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/armv6-alpine-linux-musleabihf) set(TOOLCHAIN "armv6-alpine-linux-musleabihf") else() set(TOOLCHAIN "arm-linux-gnueabihf") endif() elseif(TARGET_ARCH_NAME STREQUAL "loongarch64") set(CMAKE_SYSTEM_PROCESSOR "loongarch64") if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/loongarch64-alpine-linux-musl) set(TOOLCHAIN "loongarch64-alpine-linux-musl") else() set(TOOLCHAIN "loongarch64-linux-gnu") endif() elseif(TARGET_ARCH_NAME STREQUAL "ppc64le") set(CMAKE_SYSTEM_PROCESSOR ppc64le) if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/powerpc64le-alpine-linux-musl) set(TOOLCHAIN "powerpc64le-alpine-linux-musl") else() set(TOOLCHAIN "powerpc64le-linux-gnu") endif() elseif(TARGET_ARCH_NAME STREQUAL "riscv64") set(CMAKE_SYSTEM_PROCESSOR riscv64) if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/riscv64-alpine-linux-musl) set(TOOLCHAIN "riscv64-alpine-linux-musl") else() set(TOOLCHAIN "riscv64-linux-gnu") if(TIZEN) set(TIZEN_TOOLCHAIN "riscv64-tizen-linux-gnu") endif() endif() elseif(TARGET_ARCH_NAME STREQUAL "s390x") set(CMAKE_SYSTEM_PROCESSOR s390x) if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/s390x-alpine-linux-musl) set(TOOLCHAIN "s390x-alpine-linux-musl") else() set(TOOLCHAIN "s390x-linux-gnu") endif() elseif(TARGET_ARCH_NAME STREQUAL "x64") set(CMAKE_SYSTEM_PROCESSOR x86_64) if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/x86_64-alpine-linux-musl) set(TOOLCHAIN "x86_64-alpine-linux-musl") elseif(LINUX) set(TOOLCHAIN "x86_64-linux-gnu") if(TIZEN) set(TIZEN_TOOLCHAIN "x86_64-tizen-linux-gnu") endif() elseif(FREEBSD) set(triple "x86_64-unknown-freebsd12") elseif(ILLUMOS) set(TOOLCHAIN "x86_64-illumos") elseif(HAIKU) set(TOOLCHAIN "x86_64-unknown-haiku") endif() elseif(TARGET_ARCH_NAME STREQUAL "x86") set(CMAKE_SYSTEM_PROCESSOR i686) if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/i586-alpine-linux-musl) set(TOOLCHAIN "i586-alpine-linux-musl") else() set(TOOLCHAIN "i686-linux-gnu") endif() if(TIZEN) set(TIZEN_TOOLCHAIN "i586-tizen-linux-gnu") endif() else() message(FATAL_ERROR "Arch is ${TARGET_ARCH_NAME}. Only arm, arm64, armel, armv6, loongarch64, ppc64le, riscv64, s390x, x64 and x86 are supported!") endif() if(DEFINED ENV{TOOLCHAIN}) set(TOOLCHAIN $ENV{TOOLCHAIN}) endif() # Specify include paths if(TIZEN) function(find_toolchain_dir prefix) # Dynamically find the version subdirectory file(GLOB DIRECTORIES "${prefix}/*") list(GET DIRECTORIES 0 FIRST_MATCH) get_filename_component(TOOLCHAIN_VERSION ${FIRST_MATCH} NAME) set(TIZEN_TOOLCHAIN_PATH "${prefix}/${TOOLCHAIN_VERSION}" PARENT_SCOPE) endfunction() if(TARGET_ARCH_NAME MATCHES "^(arm|armel|x86)$") find_toolchain_dir("${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}") else() find_toolchain_dir("${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}") endif() message(STATUS "TIZEN_TOOLCHAIN_PATH set to: ${TIZEN_TOOLCHAIN_PATH}") include_directories(SYSTEM ${TIZEN_TOOLCHAIN_PATH}/include/c++) include_directories(SYSTEM ${TIZEN_TOOLCHAIN_PATH}/include/c++/${TIZEN_TOOLCHAIN}) endif() function(locate_toolchain_exec exec var) set(TOOLSET_PREFIX ${TOOLCHAIN}-) string(TOUPPER ${exec} EXEC_UPPERCASE) if(NOT "$ENV{CLR_${EXEC_UPPERCASE}}" STREQUAL "") set(${var} "$ENV{CLR_${EXEC_UPPERCASE}}" PARENT_SCOPE) return() endif() find_program(EXEC_LOCATION_${exec} NAMES "${TOOLSET_PREFIX}${exec}${CLR_CMAKE_COMPILER_FILE_NAME_VERSION}" "${TOOLSET_PREFIX}${exec}") if (EXEC_LOCATION_${exec} STREQUAL "EXEC_LOCATION_${exec}-NOTFOUND") message(FATAL_ERROR "Unable to find toolchain executable. Name: ${exec}, Prefix: ${TOOLSET_PREFIX}.") endif() set(${var} ${EXEC_LOCATION_${exec}} PARENT_SCOPE) endfunction() if(ANDROID) if(TARGET_ARCH_NAME STREQUAL "arm") set(ANDROID_ABI armeabi-v7a) elseif(TARGET_ARCH_NAME STREQUAL "arm64") set(ANDROID_ABI arm64-v8a) endif() # extract platform number required by the NDK's toolchain file(READ "${CROSS_ROOTFS}/android_platform" RID_FILE_CONTENTS) string(REPLACE "RID=" "" ANDROID_RID "${RID_FILE_CONTENTS}") string(REGEX REPLACE ".*\\.([0-9]+)-.*" "\\1" ANDROID_PLATFORM "${ANDROID_RID}") set(ANDROID_TOOLCHAIN clang) set(FEATURE_EVENT_TRACE 0) # disable event trace as there is no lttng-ust package in termux repository set(CMAKE_SYSTEM_LIBRARY_PATH "${CROSS_ROOTFS}/usr/lib") set(CMAKE_SYSTEM_INCLUDE_PATH "${CROSS_ROOTFS}/usr/include") # include official NDK toolchain script include(${CROSS_ROOTFS}/../build/cmake/android.toolchain.cmake) elseif(FREEBSD) # we cross-compile by instructing clang set(CMAKE_C_COMPILER_TARGET ${triple}) set(CMAKE_CXX_COMPILER_TARGET ${triple}) set(CMAKE_ASM_COMPILER_TARGET ${triple}) set(CMAKE_SYSROOT "${CROSS_ROOTFS}") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=lld") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fuse-ld=lld") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -fuse-ld=lld") elseif(ILLUMOS) set(CMAKE_SYSROOT "${CROSS_ROOTFS}") set(CMAKE_SYSTEM_PREFIX_PATH "${CROSS_ROOTFS}") set(CMAKE_C_STANDARD_LIBRARIES "${CMAKE_C_STANDARD_LIBRARIES} -lssp") set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -lssp") include_directories(SYSTEM ${CROSS_ROOTFS}/include) locate_toolchain_exec(gcc CMAKE_C_COMPILER) locate_toolchain_exec(g++ CMAKE_CXX_COMPILER) elseif(HAIKU) set(CMAKE_SYSROOT "${CROSS_ROOTFS}") set(CMAKE_PROGRAM_PATH "${CMAKE_PROGRAM_PATH};${CROSS_ROOTFS}/cross-tools-x86_64/bin") set(CMAKE_SYSTEM_PREFIX_PATH "${CROSS_ROOTFS}") set(CMAKE_C_STANDARD_LIBRARIES "${CMAKE_C_STANDARD_LIBRARIES} -lssp") set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -lssp") locate_toolchain_exec(gcc CMAKE_C_COMPILER) locate_toolchain_exec(g++ CMAKE_CXX_COMPILER) # let CMake set up the correct search paths include(Platform/Haiku) else() set(CMAKE_SYSROOT "${CROSS_ROOTFS}") set(CMAKE_C_COMPILER_EXTERNAL_TOOLCHAIN "${CROSS_ROOTFS}/usr") set(CMAKE_CXX_COMPILER_EXTERNAL_TOOLCHAIN "${CROSS_ROOTFS}/usr") set(CMAKE_ASM_COMPILER_EXTERNAL_TOOLCHAIN "${CROSS_ROOTFS}/usr") endif() # Specify link flags function(add_toolchain_linker_flag Flag) set(Config "${ARGV1}") set(CONFIG_SUFFIX "") if (NOT Config STREQUAL "") set(CONFIG_SUFFIX "_${Config}") endif() set("CMAKE_EXE_LINKER_FLAGS${CONFIG_SUFFIX}_INIT" "${CMAKE_EXE_LINKER_FLAGS${CONFIG_SUFFIX}_INIT} ${Flag}" PARENT_SCOPE) set("CMAKE_SHARED_LINKER_FLAGS${CONFIG_SUFFIX}_INIT" "${CMAKE_SHARED_LINKER_FLAGS${CONFIG_SUFFIX}_INIT} ${Flag}" PARENT_SCOPE) endfunction() if(LINUX) add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/lib/${TOOLCHAIN}") add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/usr/lib/${TOOLCHAIN}") endif() if(TARGET_ARCH_NAME MATCHES "^(arm|armel)$") if(TIZEN) add_toolchain_linker_flag("-B${TIZEN_TOOLCHAIN_PATH}") add_toolchain_linker_flag("-L${CROSS_ROOTFS}/lib") add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib") add_toolchain_linker_flag("-L${TIZEN_TOOLCHAIN_PATH}") endif() elseif(TARGET_ARCH_NAME MATCHES "^(arm64|x64|riscv64)$") if(TIZEN) add_toolchain_linker_flag("-B${TIZEN_TOOLCHAIN_PATH}") add_toolchain_linker_flag("-L${CROSS_ROOTFS}/lib64") add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib64") add_toolchain_linker_flag("-L${TIZEN_TOOLCHAIN_PATH}") add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/lib64") add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/usr/lib64") add_toolchain_linker_flag("-Wl,--rpath-link=${TIZEN_TOOLCHAIN_PATH}") endif() elseif(TARGET_ARCH_NAME STREQUAL "s390x") add_toolchain_linker_flag("--target=${TOOLCHAIN}") elseif(TARGET_ARCH_NAME STREQUAL "x86") if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/i586-alpine-linux-musl) add_toolchain_linker_flag("--target=${TOOLCHAIN}") add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/usr/lib/gcc/${TOOLCHAIN}") endif() add_toolchain_linker_flag(-m32) if(TIZEN) add_toolchain_linker_flag("-B${TIZEN_TOOLCHAIN_PATH}") add_toolchain_linker_flag("-L${CROSS_ROOTFS}/lib") add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib") add_toolchain_linker_flag("-L${TIZEN_TOOLCHAIN_PATH}") endif() elseif(ILLUMOS) add_toolchain_linker_flag("-L${CROSS_ROOTFS}/lib/amd64") add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/amd64/lib") elseif(HAIKU) add_toolchain_linker_flag("-lnetwork") add_toolchain_linker_flag("-lroot") endif() # Specify compile options if((TARGET_ARCH_NAME MATCHES "^(arm|arm64|armel|armv6|loongarch64|ppc64le|riscv64|s390x|x64|x86)$" AND NOT ANDROID AND NOT FREEBSD) OR ILLUMOS OR HAIKU) set(CMAKE_C_COMPILER_TARGET ${TOOLCHAIN}) set(CMAKE_CXX_COMPILER_TARGET ${TOOLCHAIN}) set(CMAKE_ASM_COMPILER_TARGET ${TOOLCHAIN}) endif() if(TARGET_ARCH_NAME MATCHES "^(arm|armel)$") add_compile_options(-mthumb) if (NOT DEFINED CLR_ARM_FPU_TYPE) set (CLR_ARM_FPU_TYPE vfpv3) endif (NOT DEFINED CLR_ARM_FPU_TYPE) add_compile_options (-mfpu=${CLR_ARM_FPU_TYPE}) if (NOT DEFINED CLR_ARM_FPU_CAPABILITY) set (CLR_ARM_FPU_CAPABILITY 0x7) endif (NOT DEFINED CLR_ARM_FPU_CAPABILITY) add_definitions (-DCLR_ARM_FPU_CAPABILITY=${CLR_ARM_FPU_CAPABILITY}) # persist variables across multiple try_compile passes list(APPEND CMAKE_TRY_COMPILE_PLATFORM_VARIABLES CLR_ARM_FPU_TYPE CLR_ARM_FPU_CAPABILITY) if(TARGET_ARCH_NAME STREQUAL "armel") add_compile_options(-mfloat-abi=softfp) endif() elseif(TARGET_ARCH_NAME STREQUAL "s390x") add_compile_options("--target=${TOOLCHAIN}") elseif(TARGET_ARCH_NAME STREQUAL "x86") if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/i586-alpine-linux-musl) add_compile_options(--target=${TOOLCHAIN}) endif() add_compile_options(-m32) add_compile_options(-Wno-error=unused-command-line-argument) endif() if(TIZEN) if(TARGET_ARCH_NAME MATCHES "^(arm|armel|arm64|x86)$") add_compile_options(-Wno-deprecated-declarations) # compile-time option add_compile_options(-D__extern_always_inline=inline) # compile-time option endif() endif() # Set LLDB include and library paths for builds that need lldb. if(TARGET_ARCH_NAME MATCHES "^(arm|armel|x86)$") if(TARGET_ARCH_NAME STREQUAL "x86") set(LLVM_CROSS_DIR "$ENV{LLVM_CROSS_HOME}") else() # arm/armel case set(LLVM_CROSS_DIR "$ENV{LLVM_ARM_HOME}") endif() if(LLVM_CROSS_DIR) set(WITH_LLDB_LIBS "${LLVM_CROSS_DIR}/lib/" CACHE STRING "") set(WITH_LLDB_INCLUDES "${LLVM_CROSS_DIR}/include" CACHE STRING "") set(LLDB_H "${WITH_LLDB_INCLUDES}" CACHE STRING "") set(LLDB "${LLVM_CROSS_DIR}/lib/liblldb.so" CACHE STRING "") else() if(TARGET_ARCH_NAME STREQUAL "x86") set(WITH_LLDB_LIBS "${CROSS_ROOTFS}/usr/lib/i386-linux-gnu" CACHE STRING "") set(CHECK_LLVM_DIR "${CROSS_ROOTFS}/usr/lib/llvm-3.8/include") if(EXISTS "${CHECK_LLVM_DIR}" AND IS_DIRECTORY "${CHECK_LLVM_DIR}") set(WITH_LLDB_INCLUDES "${CHECK_LLVM_DIR}") else() set(WITH_LLDB_INCLUDES "${CROSS_ROOTFS}/usr/lib/llvm-3.6/include") endif() else() # arm/armel case set(WITH_LLDB_LIBS "${CROSS_ROOTFS}/usr/lib/${TOOLCHAIN}" CACHE STRING "") set(WITH_LLDB_INCLUDES "${CROSS_ROOTFS}/usr/lib/llvm-3.6/include" CACHE STRING "") endif() endif() endif() # Set C++ standard library options if specified set(CLR_CMAKE_CXX_STANDARD_LIBRARY "" CACHE STRING "Standard library flavor to link against. Only supported with the Clang compiler.") if (CLR_CMAKE_CXX_STANDARD_LIBRARY) add_compile_options($<$:--stdlib=${CLR_CMAKE_CXX_STANDARD_LIBRARY}>) add_link_options($<$:--stdlib=${CLR_CMAKE_CXX_STANDARD_LIBRARY}>) endif() option(CLR_CMAKE_CXX_STANDARD_LIBRARY_STATIC "Statically link against the C++ standard library" OFF) if(CLR_CMAKE_CXX_STANDARD_LIBRARY_STATIC) add_link_options($<$:-static-libstdc++>) endif() set(CLR_CMAKE_CXX_ABI_LIBRARY "" CACHE STRING "C++ ABI implementation library to link against. Only supported with the Clang compiler.") if (CLR_CMAKE_CXX_ABI_LIBRARY) # The user may specify the ABI library with the 'lib' prefix, like 'libstdc++'. Strip the prefix here so the linker finds the right library. string(REGEX REPLACE "^lib(.+)" "\\1" CLR_CMAKE_CXX_ABI_LIBRARY ${CLR_CMAKE_CXX_ABI_LIBRARY}) # We need to specify this as a linker-backend option as Clang will filter this option out when linking to libc++. add_link_options("LINKER:-l${CLR_CMAKE_CXX_ABI_LIBRARY}") endif() set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) ================================================ FILE: eng/common/cross/x86/tizen/tizen.patch ================================================ diff -u -r a/usr/lib/libc.so b/usr/lib/libc.so --- a/usr/lib/libc.so 2016-12-30 23:00:08.284951863 +0900 +++ b/usr/lib/libc.so 2016-12-30 23:00:32.140951815 +0900 @@ -2,4 +2,4 @@ Use the shared library, but some functions are only in the static library, so try that secondarily. */ OUTPUT_FORMAT(elf32-i386) -GROUP ( /lib/libc.so.6 /usr/lib/libc_nonshared.a AS_NEEDED ( /lib/ld-linux.so.2 ) ) +GROUP ( libc.so.6 libc_nonshared.a AS_NEEDED ( ld-linux.so.2 ) ) ================================================ FILE: eng/common/darc-init.ps1 ================================================ param ( $darcVersion = $null, $versionEndpoint = 'https://maestro.dot.net/api/assets/darc-version?api-version=2020-02-20', $verbosity = 'minimal', $toolpath = $null ) . $PSScriptRoot\tools.ps1 function InstallDarcCli ($darcVersion, $toolpath) { $darcCliPackageName = 'microsoft.dotnet.darc' $dotnetRoot = InitializeDotNetCli -install:$true $dotnet = "$dotnetRoot\dotnet.exe" $toolList = & "$dotnet" tool list -g if ($toolList -like "*$darcCliPackageName*") { & "$dotnet" tool uninstall $darcCliPackageName -g } # If the user didn't explicitly specify the darc version, # query the Maestro API for the correct version of darc to install. if (-not $darcVersion) { $darcVersion = $(Invoke-WebRequest -Uri $versionEndpoint -UseBasicParsing).Content } $arcadeServicesSource = 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' Write-Host "Installing Darc CLI version $darcVersion..." Write-Host 'You may need to restart your command window if this is the first dotnet tool you have installed.' if (-not $toolpath) { Write-Host "'$dotnet' tool install $darcCliPackageName --version $darcVersion --add-source '$arcadeServicesSource' -v $verbosity -g" & "$dotnet" tool install $darcCliPackageName --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity -g }else { Write-Host "'$dotnet' tool install $darcCliPackageName --version $darcVersion --add-source '$arcadeServicesSource' -v $verbosity --tool-path '$toolpath'" & "$dotnet" tool install $darcCliPackageName --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity --tool-path "$toolpath" } } try { InstallDarcCli $darcVersion $toolpath } catch { Write-Host $_.ScriptStackTrace Write-PipelineTelemetryError -Category 'Darc' -Message $_ ExitWithExitCode 1 } ================================================ FILE: eng/common/darc-init.sh ================================================ #!/usr/bin/env bash source="${BASH_SOURCE[0]}" darcVersion='' versionEndpoint='https://maestro.dot.net/api/assets/darc-version?api-version=2020-02-20' verbosity='minimal' while [[ $# > 0 ]]; do opt="$(echo "$1" | tr "[:upper:]" "[:lower:]")" case "$opt" in --darcversion) darcVersion=$2 shift ;; --versionendpoint) versionEndpoint=$2 shift ;; --verbosity) verbosity=$2 shift ;; --toolpath) toolpath=$2 shift ;; *) echo "Invalid argument: $1" usage exit 1 ;; esac shift done # resolve $source until the file is no longer a symlink while [[ -h "$source" ]]; do scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" source="$(readlink "$source")" # if $source was a relative symlink, we need to resolve it relative to the path where the # symlink file was located [[ $source != /* ]] && source="$scriptroot/$source" done scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" . "$scriptroot/tools.sh" if [ -z "$darcVersion" ]; then darcVersion=$(curl -X GET "$versionEndpoint" -H "accept: text/plain") fi function InstallDarcCli { local darc_cli_package_name="microsoft.dotnet.darc" InitializeDotNetCli true local dotnet_root=$_InitializeDotNetCli if [ -z "$toolpath" ]; then local tool_list=$($dotnet_root/dotnet tool list -g) if [[ $tool_list = *$darc_cli_package_name* ]]; then echo $($dotnet_root/dotnet tool uninstall $darc_cli_package_name -g) fi else local tool_list=$($dotnet_root/dotnet tool list --tool-path "$toolpath") if [[ $tool_list = *$darc_cli_package_name* ]]; then echo $($dotnet_root/dotnet tool uninstall $darc_cli_package_name --tool-path "$toolpath") fi fi local arcadeServicesSource="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json" echo "Installing Darc CLI version $darcVersion..." echo "You may need to restart your command shell if this is the first dotnet tool you have installed." if [ -z "$toolpath" ]; then echo $($dotnet_root/dotnet tool install $darc_cli_package_name --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity -g) else echo $($dotnet_root/dotnet tool install $darc_cli_package_name --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity --tool-path "$toolpath") fi } InstallDarcCli ================================================ FILE: eng/common/dotnet-install.cmd ================================================ @echo off powershell -ExecutionPolicy ByPass -NoProfile -command "& """%~dp0dotnet-install.ps1""" %*" ================================================ FILE: eng/common/dotnet-install.ps1 ================================================ [CmdletBinding(PositionalBinding=$false)] Param( [string] $verbosity = 'minimal', [string] $architecture = '', [string] $version = 'Latest', [string] $runtime = 'dotnet', [string] $RuntimeSourceFeed = '', [string] $RuntimeSourceFeedKey = '' ) . $PSScriptRoot\tools.ps1 $dotnetRoot = Join-Path $RepoRoot '.dotnet' $installdir = $dotnetRoot try { if ($architecture -and $architecture.Trim() -eq 'x86') { $installdir = Join-Path $installdir 'x86' } InstallDotNet $installdir $version $architecture $runtime $true -RuntimeSourceFeed $RuntimeSourceFeed -RuntimeSourceFeedKey $RuntimeSourceFeedKey } catch { Write-Host $_.ScriptStackTrace Write-PipelineTelemetryError -Category 'InitializeToolset' -Message $_ ExitWithExitCode 1 } ExitWithExitCode 0 ================================================ FILE: eng/common/dotnet-install.sh ================================================ #!/usr/bin/env bash source="${BASH_SOURCE[0]}" # resolve $source until the file is no longer a symlink while [[ -h "$source" ]]; do scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" source="$(readlink "$source")" # if $source was a relative symlink, we need to resolve it relative to the path where the # symlink file was located [[ $source != /* ]] && source="$scriptroot/$source" done scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" . "$scriptroot/tools.sh" version='Latest' architecture='' runtime='dotnet' runtimeSourceFeed='' runtimeSourceFeedKey='' while [[ $# > 0 ]]; do opt="$(echo "$1" | tr "[:upper:]" "[:lower:]")" case "$opt" in -version|-v) shift version="$1" ;; -architecture|-a) shift architecture="$1" ;; -runtime|-r) shift runtime="$1" ;; -runtimesourcefeed) shift runtimeSourceFeed="$1" ;; -runtimesourcefeedkey) shift runtimeSourceFeedKey="$1" ;; *) Write-PipelineTelemetryError -Category 'Build' -Message "Invalid argument: $1" exit 1 ;; esac shift done # Use uname to determine what the CPU is, see https://en.wikipedia.org/wiki/Uname#Examples cpuname=$(uname -m) case $cpuname in arm64|aarch64) buildarch=arm64 if [ "$(getconf LONG_BIT)" -lt 64 ]; then # This is 32-bit OS running on 64-bit CPU (for example Raspberry Pi OS) buildarch=arm fi ;; loongarch64) buildarch=loongarch64 ;; amd64|x86_64) buildarch=x64 ;; armv*l) buildarch=arm ;; i[3-6]86) buildarch=x86 ;; riscv64) buildarch=riscv64 ;; *) echo "Unknown CPU $cpuname detected, treating it as x64" buildarch=x64 ;; esac dotnetRoot="${repo_root}.dotnet" if [[ $architecture != "" ]] && [[ $architecture != $buildarch ]]; then dotnetRoot="$dotnetRoot/$architecture" fi InstallDotNet "$dotnetRoot" $version "$architecture" $runtime true $runtimeSourceFeed $runtimeSourceFeedKey || { local exit_code=$? Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "dotnet-install.sh failed (exit code '$exit_code')." >&2 ExitWithExitCode $exit_code } ExitWithExitCode 0 ================================================ FILE: eng/common/enable-cross-org-publishing.ps1 ================================================ param( [string] $token ) . $PSScriptRoot\pipeline-logging-functions.ps1 # Write-PipelineSetVariable will no-op if a variable named $ci is not defined # Since this script is only ever called in AzDO builds, just universally set it $ci = $true Write-PipelineSetVariable -Name 'VSS_NUGET_ACCESSTOKEN' -Value $token -IsMultiJobVariable $false Write-PipelineSetVariable -Name 'VSS_NUGET_URI_PREFIXES' -Value 'https://dnceng.pkgs.visualstudio.com/;https://pkgs.dev.azure.com/dnceng/;https://devdiv.pkgs.visualstudio.com/;https://pkgs.dev.azure.com/devdiv/' -IsMultiJobVariable $false ================================================ FILE: eng/common/generate-locproject.ps1 ================================================ Param( [Parameter(Mandatory=$true)][string] $SourcesDirectory, # Directory where source files live; if using a Localize directory it should live in here [string] $LanguageSet = 'VS_Main_Languages', # Language set to be used in the LocProject.json [switch] $UseCheckedInLocProjectJson, # When set, generates a LocProject.json and compares it to one that already exists in the repo; otherwise just generates one [switch] $CreateNeutralXlfs # Creates neutral xlf files. Only set to false when running locally ) # Generates LocProject.json files for the OneLocBuild task. OneLocBuildTask is described here: # https://ceapex.visualstudio.com/CEINTL/_wiki/wikis/CEINTL.wiki/107/Localization-with-OneLocBuild-Task Set-StrictMode -Version 2.0 $ErrorActionPreference = "Stop" . $PSScriptRoot\pipeline-logging-functions.ps1 $exclusionsFilePath = "$SourcesDirectory\eng\Localize\LocExclusions.json" $exclusions = @{ Exclusions = @() } if (Test-Path -Path $exclusionsFilePath) { $exclusions = Get-Content "$exclusionsFilePath" | ConvertFrom-Json } Push-Location "$SourcesDirectory" # push location for Resolve-Path -Relative to work # Template files $jsonFiles = @() $jsonTemplateFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory" | Where-Object { $_.FullName -Match "\.template\.config\\localize\\.+\.en\.json" } # .NET templating pattern $jsonTemplateFiles | ForEach-Object { $null = $_.Name -Match "(.+)\.[\w-]+\.json" # matches '[filename].[langcode].json $destinationFile = "$($_.Directory.FullName)\$($Matches.1).json" $jsonFiles += Copy-Item "$($_.FullName)" -Destination $destinationFile -PassThru } $jsonWinformsTemplateFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory" | Where-Object { $_.FullName -Match "en\\strings\.json" } # current winforms pattern $wxlFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory" | Where-Object { $_.FullName -Match "\\.+\.wxl" -And -Not( $_.Directory.Name -Match "\d{4}" ) } # localized files live in four digit lang ID directories; this excludes them if (-not $wxlFiles) { $wxlEnFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory" | Where-Object { $_.FullName -Match "\\1033\\.+\.wxl" } # pick up en files (1033 = en) specifically so we can copy them to use as the neutral xlf files if ($wxlEnFiles) { $wxlFiles = @() $wxlEnFiles | ForEach-Object { $destinationFile = "$($_.Directory.Parent.FullName)\$($_.Name)" $wxlFiles += Copy-Item "$($_.FullName)" -Destination $destinationFile -PassThru } } } $macosHtmlEnFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory" | Where-Object { $_.FullName -Match "en\.lproj\\.+\.html$" } # add installer HTML files $macosHtmlFiles = @() if ($macosHtmlEnFiles) { $macosHtmlEnFiles | ForEach-Object { $destinationFile = "$($_.Directory.Parent.FullName)\$($_.Name)" $macosHtmlFiles += Copy-Item "$($_.FullName)" -Destination $destinationFile -PassThru } } $xlfFiles = @() $allXlfFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory\*\*.xlf" $langXlfFiles = @() if ($allXlfFiles) { $null = $allXlfFiles[0].FullName -Match "\.([\w-]+)\.xlf" # matches '[langcode].xlf' $firstLangCode = $Matches.1 $langXlfFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory\*\*.$firstLangCode.xlf" } $langXlfFiles | ForEach-Object { $null = $_.Name -Match "(.+)\.[\w-]+\.xlf" # matches '[filename].[langcode].xlf $destinationFile = "$($_.Directory.FullName)\$($Matches.1).xlf" $xlfFiles += Copy-Item "$($_.FullName)" -Destination $destinationFile -PassThru } $locFiles = $jsonFiles + $jsonWinformsTemplateFiles + $xlfFiles $locJson = @{ Projects = @( @{ LanguageSet = $LanguageSet LocItems = @( $locFiles | ForEach-Object { $outputPath = "$(($_.DirectoryName | Resolve-Path -Relative) + "\")" $continue = $true foreach ($exclusion in $exclusions.Exclusions) { if ($_.FullName.Contains($exclusion)) { $continue = $false } } $sourceFile = ($_.FullName | Resolve-Path -Relative) if (!$CreateNeutralXlfs -and $_.Extension -eq '.xlf') { Remove-Item -Path $sourceFile } if ($continue) { if ($_.Directory.Name -eq 'en' -and $_.Extension -eq '.json') { return @{ SourceFile = $sourceFile CopyOption = "LangIDOnPath" OutputPath = "$($_.Directory.Parent.FullName | Resolve-Path -Relative)\" } } else { return @{ SourceFile = $sourceFile CopyOption = "LangIDOnName" OutputPath = $outputPath } } } } ) }, @{ LanguageSet = $LanguageSet CloneLanguageSet = "WiX_CloneLanguages" LssFiles = @( "wxl_loc.lss" ) LocItems = @( $wxlFiles | ForEach-Object { $outputPath = "$($_.Directory.FullName | Resolve-Path -Relative)\" $continue = $true foreach ($exclusion in $exclusions.Exclusions) { if ($_.FullName.Contains($exclusion)) { $continue = $false } } $sourceFile = ($_.FullName | Resolve-Path -Relative) if ($continue) { return @{ SourceFile = $sourceFile CopyOption = "LangIDOnPath" OutputPath = $outputPath } } } ) }, @{ LanguageSet = $LanguageSet CloneLanguageSet = "VS_macOS_CloneLanguages" LssFiles = @( ".\eng\common\loc\P22DotNetHtmlLocalization.lss" ) LocItems = @( $macosHtmlFiles | ForEach-Object { $outputPath = "$($_.Directory.FullName | Resolve-Path -Relative)\" $continue = $true foreach ($exclusion in $exclusions.Exclusions) { if ($_.FullName.Contains($exclusion)) { $continue = $false } } $sourceFile = ($_.FullName | Resolve-Path -Relative) $lciFile = $sourceFile + ".lci" if ($continue) { $result = @{ SourceFile = $sourceFile CopyOption = "LangIDOnPath" OutputPath = $outputPath } if (Test-Path $lciFile -PathType Leaf) { $result["LciFile"] = $lciFile } return $result } } ) } ) } $json = ConvertTo-Json $locJson -Depth 5 Write-Host "LocProject.json generated:`n`n$json`n`n" Pop-Location if (!$UseCheckedInLocProjectJson) { New-Item "$SourcesDirectory\eng\Localize\LocProject.json" -Force # Need this to make sure the Localize directory is created Set-Content "$SourcesDirectory\eng\Localize\LocProject.json" $json } else { New-Item "$SourcesDirectory\eng\Localize\LocProject-generated.json" -Force # Need this to make sure the Localize directory is created Set-Content "$SourcesDirectory\eng\Localize\LocProject-generated.json" $json if ((Get-FileHash "$SourcesDirectory\eng\Localize\LocProject-generated.json").Hash -ne (Get-FileHash "$SourcesDirectory\eng\Localize\LocProject.json").Hash) { Write-PipelineTelemetryError -Category "OneLocBuild" -Message "Existing LocProject.json differs from generated LocProject.json. Download LocProject-generated.json and compare them." exit 1 } else { Write-Host "Generated LocProject.json and current LocProject.json are identical." } } ================================================ FILE: eng/common/generate-sbom-prep.ps1 ================================================ Param( [Parameter(Mandatory=$true)][string] $ManifestDirPath # Manifest directory where sbom will be placed ) . $PSScriptRoot\pipeline-logging-functions.ps1 Write-Host "Creating dir $ManifestDirPath" # create directory for sbom manifest to be placed if (!(Test-Path -path $ManifestDirPath)) { New-Item -ItemType Directory -path $ManifestDirPath Write-Host "Successfully created directory $ManifestDirPath" } else{ Write-PipelineTelemetryError -category 'Build' "Unable to create sbom folder." } Write-Host "Updating artifact name" $artifact_name = "${env:SYSTEM_STAGENAME}_${env:AGENT_JOBNAME}_SBOM" -replace '["/:<>\\|?@*"() ]', '_' Write-Host "Artifact name $artifact_name" Write-Host "##vso[task.setvariable variable=ARTIFACT_NAME]$artifact_name" ================================================ FILE: eng/common/generate-sbom-prep.sh ================================================ #!/usr/bin/env bash source="${BASH_SOURCE[0]}" # resolve $SOURCE until the file is no longer a symlink while [[ -h $source ]]; do scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" source="$(readlink "$source")" # if $source was a relative symlink, we need to resolve it relative to the path where the # symlink file was located [[ $source != /* ]] && source="$scriptroot/$source" done scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" . $scriptroot/pipeline-logging-functions.sh manifest_dir=$1 if [ ! -d "$manifest_dir" ] ; then mkdir -p "$manifest_dir" echo "Sbom directory created." $manifest_dir else Write-PipelineTelemetryError -category 'Build' "Unable to create sbom folder." fi artifact_name=$SYSTEM_STAGENAME"_"$AGENT_JOBNAME"_SBOM" echo "Artifact name before : "$artifact_name # replace all special characters with _, some builds use special characters like : in Agent.Jobname, that is not a permissible name while uploading artifacts. safe_artifact_name="${artifact_name//["/:<>\\|?@*$" ]/_}" echo "Artifact name after : "$safe_artifact_name export ARTIFACT_NAME=$safe_artifact_name echo "##vso[task.setvariable variable=ARTIFACT_NAME]$safe_artifact_name" exit 0 ================================================ FILE: eng/common/helixpublish.proj ================================================ msbuild %(Identity) $(WorkItemDirectory) $(WorkItemCommand) $(WorkItemTimeout) ================================================ FILE: eng/common/init-tools-native.cmd ================================================ @echo off powershell -NoProfile -NoLogo -ExecutionPolicy ByPass -command "& """%~dp0init-tools-native.ps1""" %*" exit /b %ErrorLevel% ================================================ FILE: eng/common/init-tools-native.ps1 ================================================ <# .SYNOPSIS Entry point script for installing native tools .DESCRIPTION Reads $RepoRoot\global.json file to determine native assets to install and executes installers for those tools .PARAMETER BaseUri Base file directory or Url from which to acquire tool archives .PARAMETER InstallDirectory Directory to install native toolset. This is a command-line override for the default Install directory precedence order: - InstallDirectory command-line override - NETCOREENG_INSTALL_DIRECTORY environment variable - (default) %USERPROFILE%/.netcoreeng/native .PARAMETER Clean Switch specifying to not install anything, but cleanup native asset folders .PARAMETER Force Clean and then install tools .PARAMETER DownloadRetries Total number of retry attempts .PARAMETER RetryWaitTimeInSeconds Wait time between retry attempts in seconds .PARAMETER GlobalJsonFile File path to global.json file .PARAMETER PathPromotion Optional switch to enable either promote native tools specified in the global.json to the path (in Azure Pipelines) or break the build if a native tool is not found on the path (on a local dev machine) .NOTES #> [CmdletBinding(PositionalBinding=$false)] Param ( [string] $BaseUri = 'https://netcorenativeassets.blob.core.windows.net/resource-packages/external', [string] $InstallDirectory, [switch] $Clean = $False, [switch] $Force = $False, [int] $DownloadRetries = 5, [int] $RetryWaitTimeInSeconds = 30, [string] $GlobalJsonFile, [switch] $PathPromotion ) if (!$GlobalJsonFile) { $GlobalJsonFile = Join-Path (Get-Item $PSScriptRoot).Parent.Parent.FullName 'global.json' } Set-StrictMode -version 2.0 $ErrorActionPreference='Stop' . $PSScriptRoot\pipeline-logging-functions.ps1 Import-Module -Name (Join-Path $PSScriptRoot 'native\CommonLibrary.psm1') try { # Define verbose switch if undefined $Verbose = $VerbosePreference -Eq 'Continue' $EngCommonBaseDir = Join-Path $PSScriptRoot 'native\' $NativeBaseDir = $InstallDirectory if (!$NativeBaseDir) { $NativeBaseDir = CommonLibrary\Get-NativeInstallDirectory } $Env:CommonLibrary_NativeInstallDir = $NativeBaseDir $InstallBin = Join-Path $NativeBaseDir 'bin' $InstallerPath = Join-Path $EngCommonBaseDir 'install-tool.ps1' # Process tools list Write-Host "Processing $GlobalJsonFile" If (-Not (Test-Path $GlobalJsonFile)) { Write-Host "Unable to find '$GlobalJsonFile'" exit 0 } $NativeTools = Get-Content($GlobalJsonFile) -Raw | ConvertFrom-Json | Select-Object -Expand 'native-tools' -ErrorAction SilentlyContinue if ($NativeTools) { if ($PathPromotion -eq $True) { $ArcadeToolsDirectory = "$env:SYSTEMDRIVE\arcade-tools" if (Test-Path $ArcadeToolsDirectory) { # if this directory exists, we should use native tools on machine $NativeTools.PSObject.Properties | ForEach-Object { $ToolName = $_.Name $ToolVersion = $_.Value $InstalledTools = @{} if ((Get-Command "$ToolName" -ErrorAction SilentlyContinue) -eq $null) { if ($ToolVersion -eq "latest") { $ToolVersion = "" } $ToolDirectories = (Get-ChildItem -Path "$ArcadeToolsDirectory" -Filter "$ToolName-$ToolVersion*" | Sort-Object -Descending) if ($ToolDirectories -eq $null) { Write-Error "Unable to find directory for $ToolName $ToolVersion; please make sure the tool is installed on this image." exit 1 } $ToolDirectory = $ToolDirectories[0] $BinPathFile = "$($ToolDirectory.FullName)\binpath.txt" if (-not (Test-Path -Path "$BinPathFile")) { Write-Error "Unable to find binpath.txt in '$($ToolDirectory.FullName)' ($ToolName $ToolVersion); artifact is either installed incorrectly or is not a bootstrappable tool." exit 1 } $BinPath = Get-Content "$BinPathFile" $ToolPath = Convert-Path -Path $BinPath Write-Host "Adding $ToolName to the path ($ToolPath)..." Write-Host "##vso[task.prependpath]$ToolPath" $env:PATH = "$ToolPath;$env:PATH" $InstalledTools += @{ $ToolName = $ToolDirectory.FullName } } } return $InstalledTools } else { $NativeTools.PSObject.Properties | ForEach-Object { $ToolName = $_.Name $ToolVersion = $_.Value if ((Get-Command "$ToolName" -ErrorAction SilentlyContinue) -eq $null) { Write-PipelineTelemetryError -Category 'NativeToolsBootstrap' -Message "$ToolName not found on path. Please install $ToolName $ToolVersion before proceeding." Write-PipelineTelemetryError -Category 'NativeToolsBootstrap' -Message "If this is running on a build machine, the arcade-tools directory was not found, which means there's an error with the image." } } exit 0 } } else { $NativeTools.PSObject.Properties | ForEach-Object { $ToolName = $_.Name $ToolVersion = $_.Value $LocalInstallerArguments = @{ ToolName = "$ToolName" } $LocalInstallerArguments += @{ InstallPath = "$InstallBin" } $LocalInstallerArguments += @{ BaseUri = "$BaseUri" } $LocalInstallerArguments += @{ CommonLibraryDirectory = "$EngCommonBaseDir" } $LocalInstallerArguments += @{ Version = "$ToolVersion" } if ($Verbose) { $LocalInstallerArguments += @{ Verbose = $True } } if (Get-Variable 'Force' -ErrorAction 'SilentlyContinue') { if($Force) { $LocalInstallerArguments += @{ Force = $True } } } if ($Clean) { $LocalInstallerArguments += @{ Clean = $True } } Write-Verbose "Installing $ToolName version $ToolVersion" Write-Verbose "Executing '$InstallerPath $($LocalInstallerArguments.Keys.ForEach({"-$_ '$($LocalInstallerArguments.$_)'"}) -join ' ')'" & $InstallerPath @LocalInstallerArguments if ($LASTEXITCODE -Ne "0") { $errMsg = "$ToolName installation failed" if ((Get-Variable 'DoNotAbortNativeToolsInstallationOnFailure' -ErrorAction 'SilentlyContinue') -and $DoNotAbortNativeToolsInstallationOnFailure) { $showNativeToolsWarning = $true if ((Get-Variable 'DoNotDisplayNativeToolsInstallationWarnings' -ErrorAction 'SilentlyContinue') -and $DoNotDisplayNativeToolsInstallationWarnings) { $showNativeToolsWarning = $false } if ($showNativeToolsWarning) { Write-Warning $errMsg } $toolInstallationFailure = $true } else { # We cannot change this to Write-PipelineTelemetryError because of https://github.com/dotnet/arcade/issues/4482 Write-Host $errMsg exit 1 } } } if ((Get-Variable 'toolInstallationFailure' -ErrorAction 'SilentlyContinue') -and $toolInstallationFailure) { # We cannot change this to Write-PipelineTelemetryError because of https://github.com/dotnet/arcade/issues/4482 Write-Host 'Native tools bootstrap failed' exit 1 } } } else { Write-Host 'No native tools defined in global.json' exit 0 } if ($Clean) { exit 0 } if (Test-Path $InstallBin) { Write-Host 'Native tools are available from ' (Convert-Path -Path $InstallBin) Write-Host "##vso[task.prependpath]$(Convert-Path -Path $InstallBin)" return $InstallBin } elseif (-not ($PathPromotion)) { Write-PipelineTelemetryError -Category 'NativeToolsBootstrap' -Message 'Native tools install directory does not exist, installation failed' exit 1 } exit 0 } catch { Write-Host $_.ScriptStackTrace Write-PipelineTelemetryError -Category 'NativeToolsBootstrap' -Message $_ ExitWithExitCode 1 } ================================================ FILE: eng/common/init-tools-native.sh ================================================ #!/usr/bin/env bash source="${BASH_SOURCE[0]}" scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" base_uri='https://netcorenativeassets.blob.core.windows.net/resource-packages/external' install_directory='' clean=false force=false download_retries=5 retry_wait_time_seconds=30 global_json_file="$(dirname "$(dirname "${scriptroot}")")/global.json" declare -a native_assets . $scriptroot/pipeline-logging-functions.sh . $scriptroot/native/common-library.sh while (($# > 0)); do lowerI="$(echo $1 | tr "[:upper:]" "[:lower:]")" case $lowerI in --baseuri) base_uri=$2 shift 2 ;; --installdirectory) install_directory=$2 shift 2 ;; --clean) clean=true shift 1 ;; --force) force=true shift 1 ;; --donotabortonfailure) donotabortonfailure=true shift 1 ;; --donotdisplaywarnings) donotdisplaywarnings=true shift 1 ;; --downloadretries) download_retries=$2 shift 2 ;; --retrywaittimeseconds) retry_wait_time_seconds=$2 shift 2 ;; --help) echo "Common settings:" echo " --installdirectory Directory to install native toolset." echo " This is a command-line override for the default" echo " Install directory precedence order:" echo " - InstallDirectory command-line override" echo " - NETCOREENG_INSTALL_DIRECTORY environment variable" echo " - (default) %USERPROFILE%/.netcoreeng/native" echo "" echo " --clean Switch specifying not to install anything, but cleanup native asset folders" echo " --donotabortonfailure Switch specifiying whether to abort native tools installation on failure" echo " --donotdisplaywarnings Switch specifiying whether to display warnings during native tools installation on failure" echo " --force Clean and then install tools" echo " --help Print help and exit" echo "" echo "Advanced settings:" echo " --baseuri Base URI for where to download native tools from" echo " --downloadretries Number of times a download should be attempted" echo " --retrywaittimeseconds Wait time between download attempts" echo "" exit 0 ;; esac done function ReadGlobalJsonNativeTools { # happy path: we have a proper JSON parsing tool `jq(1)` in PATH! if command -v jq &> /dev/null; then # jq: read each key/value pair under "native-tools" entry and emit: # KEY="" VALUE="" # followed by a null byte. # # bash: read line with null byte delimeter and push to array (for later `eval`uation). while IFS= read -rd '' line; do native_assets+=("$line") done < <(jq -r '. | select(has("native-tools")) | ."native-tools" | keys[] as $k | @sh "KEY=\($k) VALUE=\(.[$k])\u0000"' "$global_json_file") return fi # Warning: falling back to manually parsing JSON, which is not recommended. # Following routine matches the output and escaping logic of jq(1)'s @sh formatter used above. # It has been tested with several weird strings with escaped characters in entries (key and value) # and results were compared with the output of jq(1) in binary representation using xxd(1); # just before the assignment to 'native_assets' array (above and below). # try to capture the section under "native-tools". if [[ ! "$(cat "$global_json_file")" =~ \"native-tools\"[[:space:]\:\{]*([^\}]+) ]]; then return fi section="${BASH_REMATCH[1]}" parseStarted=0 possibleEnd=0 escaping=0 escaped=0 isKey=1 for (( i=0; i<${#section}; i++ )); do char="${section:$i:1}" if ! ((parseStarted)) && [[ "$char" =~ [[:space:],:] ]]; then continue; fi if ! ((escaping)) && [[ "$char" == "\\" ]]; then escaping=1 elif ((escaping)) && ! ((escaped)); then escaped=1 fi if ! ((parseStarted)) && [[ "$char" == "\"" ]]; then parseStarted=1 possibleEnd=0 elif [[ "$char" == "'" ]]; then token="$token'\\\''" possibleEnd=0 elif ((escaping)) || [[ "$char" != "\"" ]]; then token="$token$char" possibleEnd=1 fi if ((possibleEnd)) && ! ((escaping)) && [[ "$char" == "\"" ]]; then # Use printf to unescape token to match jq(1)'s @sh formatting rules. # do not use 'token="$(printf "$token")"' syntax, as $() eats the trailing linefeed. printf -v token "'$token'" if ((isKey)); then KEY="$token" isKey=0 else line="KEY=$KEY VALUE=$token" native_assets+=("$line") isKey=1 fi # reset for next token parseStarted=0 token= elif ((escaping)) && ((escaped)); then escaping=0 escaped=0 fi done } native_base_dir=$install_directory if [[ -z $install_directory ]]; then native_base_dir=$(GetNativeInstallDirectory) fi install_bin="${native_base_dir}/bin" installed_any=false ReadGlobalJsonNativeTools if [[ ${#native_assets[@]} -eq 0 ]]; then echo "No native tools defined in global.json" exit 0; else native_installer_dir="$scriptroot/native" for index in "${!native_assets[@]}"; do eval "${native_assets["$index"]}" installer_path="$native_installer_dir/install-$KEY.sh" installer_command="$installer_path" installer_command+=" --baseuri $base_uri" installer_command+=" --installpath $install_bin" installer_command+=" --version $VALUE" echo $installer_command if [[ $force = true ]]; then installer_command+=" --force" fi if [[ $clean = true ]]; then installer_command+=" --clean" fi if [[ -a $installer_path ]]; then $installer_command if [[ $? != 0 ]]; then if [[ $donotabortonfailure = true ]]; then if [[ $donotdisplaywarnings != true ]]; then Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Execution Failed" fi else Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Execution Failed" exit 1 fi else $installed_any = true fi else if [[ $donotabortonfailure == true ]]; then if [[ $donotdisplaywarnings != true ]]; then Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Execution Failed: no install script" fi else Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Execution Failed: no install script" exit 1 fi fi done fi if [[ $clean = true ]]; then exit 0 fi if [[ -d $install_bin ]]; then echo "Native tools are available from $install_bin" echo "##vso[task.prependpath]$install_bin" else if [[ $installed_any = true ]]; then Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Native tools install directory does not exist, installation failed" exit 1 fi fi exit 0 ================================================ FILE: eng/common/internal/Directory.Build.props ================================================ false false ================================================ FILE: eng/common/internal/NuGet.config ================================================ ================================================ FILE: eng/common/internal/Tools.csproj ================================================ net472 false false https://devdiv.pkgs.visualstudio.com/_packaging/dotnet-core-internal-tooling/nuget/v3/index.json; $(RestoreSources); https://devdiv.pkgs.visualstudio.com/_packaging/VS/nuget/v3/index.json; ================================================ FILE: eng/common/internal-feed-operations.ps1 ================================================ param( [Parameter(Mandatory=$true)][string] $Operation, [string] $AuthToken, [string] $CommitSha, [string] $RepoName, [switch] $IsFeedPrivate ) $ErrorActionPreference = 'Stop' Set-StrictMode -Version 2.0 . $PSScriptRoot\tools.ps1 # Sets VSS_NUGET_EXTERNAL_FEED_ENDPOINTS based on the "darc-int-*" feeds defined in NuGet.config. This is needed # in build agents by CredProvider to authenticate the restore requests to internal feeds as specified in # https://github.com/microsoft/artifacts-credprovider/blob/0f53327cd12fd893d8627d7b08a2171bf5852a41/README.md#environment-variables. This should ONLY be called from identified # internal builds function SetupCredProvider { param( [string] $AuthToken ) # Install the Cred Provider NuGet plugin Write-Host 'Setting up Cred Provider NuGet plugin in the agent...' Write-Host "Getting 'installcredprovider.ps1' from 'https://github.com/microsoft/artifacts-credprovider'..." $url = 'https://raw.githubusercontent.com/microsoft/artifacts-credprovider/master/helpers/installcredprovider.ps1' Write-Host "Writing the contents of 'installcredprovider.ps1' locally..." Invoke-WebRequest $url -OutFile installcredprovider.ps1 Write-Host 'Installing plugin...' .\installcredprovider.ps1 -Force Write-Host "Deleting local copy of 'installcredprovider.ps1'..." Remove-Item .\installcredprovider.ps1 if (-Not("$env:USERPROFILE\.nuget\plugins\netcore")) { Write-PipelineTelemetryError -Category 'Arcade' -Message 'CredProvider plugin was not installed correctly!' ExitWithExitCode 1 } else { Write-Host 'CredProvider plugin was installed correctly!' } # Then, we set the 'VSS_NUGET_EXTERNAL_FEED_ENDPOINTS' environment variable to restore from the stable # feeds successfully $nugetConfigPath = Join-Path $RepoRoot "NuGet.config" if (-Not (Test-Path -Path $nugetConfigPath)) { Write-PipelineTelemetryError -Category 'Build' -Message 'NuGet.config file not found in repo root!' ExitWithExitCode 1 } $endpoints = New-Object System.Collections.ArrayList $nugetConfigPackageSources = Select-Xml -Path $nugetConfigPath -XPath "//packageSources/add[contains(@key, 'darc-int-')]/@value" | foreach{$_.Node.Value} if (($nugetConfigPackageSources | Measure-Object).Count -gt 0 ) { foreach ($stableRestoreResource in $nugetConfigPackageSources) { $trimmedResource = ([string]$stableRestoreResource).Trim() [void]$endpoints.Add(@{endpoint="$trimmedResource"; password="$AuthToken"}) } } if (($endpoints | Measure-Object).Count -gt 0) { $endpointCredentials = @{endpointCredentials=$endpoints} | ConvertTo-Json -Compress # Create the environment variables the AzDo way Write-LoggingCommand -Area 'task' -Event 'setvariable' -Data $endpointCredentials -Properties @{ 'variable' = 'VSS_NUGET_EXTERNAL_FEED_ENDPOINTS' 'issecret' = 'false' } # We don't want sessions cached since we will be updating the endpoints quite frequently Write-LoggingCommand -Area 'task' -Event 'setvariable' -Data 'False' -Properties @{ 'variable' = 'NUGET_CREDENTIALPROVIDER_SESSIONTOKENCACHE_ENABLED' 'issecret' = 'false' } } else { Write-Host 'No internal endpoints found in NuGet.config' } } #Workaround for https://github.com/microsoft/msbuild/issues/4430 function InstallDotNetSdkAndRestoreArcade { $dotnetTempDir = Join-Path $RepoRoot "dotnet" $dotnetSdkVersion="2.1.507" # After experimentation we know this version works when restoring the SDK (compared to 3.0.*) $dotnet = "$dotnetTempDir\dotnet.exe" $restoreProjPath = "$PSScriptRoot\restore.proj" Write-Host "Installing dotnet SDK version $dotnetSdkVersion to restore Arcade SDK..." InstallDotNetSdk "$dotnetTempDir" "$dotnetSdkVersion" '' | Out-File "$restoreProjPath" & $dotnet restore $restoreProjPath Write-Host 'Arcade SDK restored!' if (Test-Path -Path $restoreProjPath) { Remove-Item $restoreProjPath } if (Test-Path -Path $dotnetTempDir) { Remove-Item $dotnetTempDir -Recurse } } try { Push-Location $PSScriptRoot if ($Operation -like 'setup') { SetupCredProvider $AuthToken } elseif ($Operation -like 'install-restore') { InstallDotNetSdkAndRestoreArcade } else { Write-PipelineTelemetryError -Category 'Arcade' -Message "Unknown operation '$Operation'!" ExitWithExitCode 1 } } catch { Write-Host $_.ScriptStackTrace Write-PipelineTelemetryError -Category 'Arcade' -Message $_ ExitWithExitCode 1 } finally { Pop-Location } ================================================ FILE: eng/common/internal-feed-operations.sh ================================================ #!/usr/bin/env bash set -e # Sets VSS_NUGET_EXTERNAL_FEED_ENDPOINTS based on the "darc-int-*" feeds defined in NuGet.config. This is needed # in build agents by CredProvider to authenticate the restore requests to internal feeds as specified in # https://github.com/microsoft/artifacts-credprovider/blob/0f53327cd12fd893d8627d7b08a2171bf5852a41/README.md#environment-variables. # This should ONLY be called from identified internal builds function SetupCredProvider { local authToken=$1 # Install the Cred Provider NuGet plugin echo "Setting up Cred Provider NuGet plugin in the agent..."... echo "Getting 'installcredprovider.ps1' from 'https://github.com/microsoft/artifacts-credprovider'..." local url="https://raw.githubusercontent.com/microsoft/artifacts-credprovider/master/helpers/installcredprovider.sh" echo "Writing the contents of 'installcredprovider.ps1' locally..." local installcredproviderPath="installcredprovider.sh" if command -v curl > /dev/null; then curl $url > "$installcredproviderPath" else wget -q -O "$installcredproviderPath" "$url" fi echo "Installing plugin..." . "$installcredproviderPath" echo "Deleting local copy of 'installcredprovider.sh'..." rm installcredprovider.sh if [ ! -d "$HOME/.nuget/plugins" ]; then Write-PipelineTelemetryError -category 'Build' 'CredProvider plugin was not installed correctly!' ExitWithExitCode 1 else echo "CredProvider plugin was installed correctly!" fi # Then, we set the 'VSS_NUGET_EXTERNAL_FEED_ENDPOINTS' environment variable to restore from the stable # feeds successfully local nugetConfigPath="{$repo_root}NuGet.config" if [ ! "$nugetConfigPath" ]; then Write-PipelineTelemetryError -category 'Build' "NuGet.config file not found in repo's root!" ExitWithExitCode 1 fi local endpoints='[' local nugetConfigPackageValues=`cat "$nugetConfigPath" | grep "key=\"darc-int-"` local pattern="value=\"(.*)\"" for value in $nugetConfigPackageValues do if [[ $value =~ $pattern ]]; then local endpoint="${BASH_REMATCH[1]}" endpoints+="{\"endpoint\": \"$endpoint\", \"password\": \"$authToken\"}," fi done endpoints=${endpoints%?} endpoints+=']' if [ ${#endpoints} -gt 2 ]; then local endpointCredentials="{\"endpointCredentials\": "$endpoints"}" echo "##vso[task.setvariable variable=VSS_NUGET_EXTERNAL_FEED_ENDPOINTS]$endpointCredentials" echo "##vso[task.setvariable variable=NUGET_CREDENTIALPROVIDER_SESSIONTOKENCACHE_ENABLED]False" else echo "No internal endpoints found in NuGet.config" fi } # Workaround for https://github.com/microsoft/msbuild/issues/4430 function InstallDotNetSdkAndRestoreArcade { local dotnetTempDir="$repo_root/dotnet" local dotnetSdkVersion="2.1.507" # After experimentation we know this version works when restoring the SDK (compared to 3.0.*) local restoreProjPath="$repo_root/eng/common/restore.proj" echo "Installing dotnet SDK version $dotnetSdkVersion to restore Arcade SDK..." echo "" > "$restoreProjPath" InstallDotNetSdk "$dotnetTempDir" "$dotnetSdkVersion" local res=`$dotnetTempDir/dotnet restore $restoreProjPath` echo "Arcade SDK restored!" # Cleanup if [ "$restoreProjPath" ]; then rm "$restoreProjPath" fi if [ "$dotnetTempDir" ]; then rm -r $dotnetTempDir fi } source="${BASH_SOURCE[0]}" operation='' authToken='' repoName='' while [[ $# > 0 ]]; do opt="$(echo "$1" | tr "[:upper:]" "[:lower:]")" case "$opt" in --operation) operation=$2 shift ;; --authtoken) authToken=$2 shift ;; *) echo "Invalid argument: $1" usage exit 1 ;; esac shift done while [[ -h "$source" ]]; do scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" source="$(readlink "$source")" # if $source was a relative symlink, we need to resolve it relative to the path where the # symlink file was located [[ $source != /* ]] && source="$scriptroot/$source" done scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" . "$scriptroot/tools.sh" if [ "$operation" = "setup" ]; then SetupCredProvider $authToken elif [ "$operation" = "install-restore" ]; then InstallDotNetSdkAndRestoreArcade else echo "Unknown operation '$operation'!" fi ================================================ FILE: eng/common/loc/P22DotNetHtmlLocalization.lss ================================================ ================================================ FILE: eng/common/msbuild.ps1 ================================================ [CmdletBinding(PositionalBinding=$false)] Param( [string] $verbosity = 'minimal', [bool] $warnAsError = $true, [bool] $nodeReuse = $true, [switch] $ci, [switch] $prepareMachine, [switch] $excludePrereleaseVS, [string] $msbuildEngine = $null, [Parameter(ValueFromRemainingArguments=$true)][String[]]$extraArgs ) . $PSScriptRoot\tools.ps1 try { if ($ci) { $nodeReuse = $false } MSBuild @extraArgs } catch { Write-Host $_.ScriptStackTrace Write-PipelineTelemetryError -Category 'Build' -Message $_ ExitWithExitCode 1 } ExitWithExitCode 0 ================================================ FILE: eng/common/msbuild.sh ================================================ #!/usr/bin/env bash source="${BASH_SOURCE[0]}" # resolve $source until the file is no longer a symlink while [[ -h "$source" ]]; do scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" source="$(readlink "$source")" # if $source was a relative symlink, we need to resolve it relative to the path where the # symlink file was located [[ $source != /* ]] && source="$scriptroot/$source" done scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" verbosity='minimal' warn_as_error=true node_reuse=true prepare_machine=false extra_args='' while (($# > 0)); do lowerI="$(echo $1 | tr "[:upper:]" "[:lower:]")" case $lowerI in --verbosity) verbosity=$2 shift 2 ;; --warnaserror) warn_as_error=$2 shift 2 ;; --nodereuse) node_reuse=$2 shift 2 ;; --ci) ci=true shift 1 ;; --preparemachine) prepare_machine=true shift 1 ;; *) extra_args="$extra_args $1" shift 1 ;; esac done . "$scriptroot/tools.sh" if [[ "$ci" == true ]]; then node_reuse=false fi MSBuild $extra_args ExitWithExitCode 0 ================================================ FILE: eng/common/native/CommonLibrary.psm1 ================================================ <# .SYNOPSIS Helper module to install an archive to a directory .DESCRIPTION Helper module to download and extract an archive to a specified directory .PARAMETER Uri Uri of artifact to download .PARAMETER InstallDirectory Directory to extract artifact contents to .PARAMETER Force Force download / extraction if file or contents already exist. Default = False .PARAMETER DownloadRetries Total number of retry attempts. Default = 5 .PARAMETER RetryWaitTimeInSeconds Wait time between retry attempts in seconds. Default = 30 .NOTES Returns False if download or extraction fail, True otherwise #> function DownloadAndExtract { [CmdletBinding(PositionalBinding=$false)] Param ( [Parameter(Mandatory=$True)] [string] $Uri, [Parameter(Mandatory=$True)] [string] $InstallDirectory, [switch] $Force = $False, [int] $DownloadRetries = 5, [int] $RetryWaitTimeInSeconds = 30 ) # Define verbose switch if undefined $Verbose = $VerbosePreference -Eq "Continue" $TempToolPath = CommonLibrary\Get-TempPathFilename -Path $Uri # Download native tool $DownloadStatus = CommonLibrary\Get-File -Uri $Uri ` -Path $TempToolPath ` -DownloadRetries $DownloadRetries ` -RetryWaitTimeInSeconds $RetryWaitTimeInSeconds ` -Force:$Force ` -Verbose:$Verbose if ($DownloadStatus -Eq $False) { Write-Error "Download failed from $Uri" return $False } # Extract native tool $UnzipStatus = CommonLibrary\Expand-Zip -ZipPath $TempToolPath ` -OutputDirectory $InstallDirectory ` -Force:$Force ` -Verbose:$Verbose if ($UnzipStatus -Eq $False) { # Retry Download one more time with Force=true $DownloadRetryStatus = CommonLibrary\Get-File -Uri $Uri ` -Path $TempToolPath ` -DownloadRetries 1 ` -RetryWaitTimeInSeconds $RetryWaitTimeInSeconds ` -Force:$True ` -Verbose:$Verbose if ($DownloadRetryStatus -Eq $False) { Write-Error "Last attempt of download failed as well" return $False } # Retry unzip again one more time with Force=true $UnzipRetryStatus = CommonLibrary\Expand-Zip -ZipPath $TempToolPath ` -OutputDirectory $InstallDirectory ` -Force:$True ` -Verbose:$Verbose if ($UnzipRetryStatus -Eq $False) { Write-Error "Last attempt of unzip failed as well" # Clean up partial zips and extracts if (Test-Path $TempToolPath) { Remove-Item $TempToolPath -Force } if (Test-Path $InstallDirectory) { Remove-Item $InstallDirectory -Force -Recurse } return $False } } return $True } <# .SYNOPSIS Download a file, retry on failure .DESCRIPTION Download specified file and retry if attempt fails .PARAMETER Uri Uri of file to download. If Uri is a local path, the file will be copied instead of downloaded .PARAMETER Path Path to download or copy uri file to .PARAMETER Force Overwrite existing file if present. Default = False .PARAMETER DownloadRetries Total number of retry attempts. Default = 5 .PARAMETER RetryWaitTimeInSeconds Wait time between retry attempts in seconds Default = 30 #> function Get-File { [CmdletBinding(PositionalBinding=$false)] Param ( [Parameter(Mandatory=$True)] [string] $Uri, [Parameter(Mandatory=$True)] [string] $Path, [int] $DownloadRetries = 5, [int] $RetryWaitTimeInSeconds = 30, [switch] $Force = $False ) $Attempt = 0 if ($Force) { if (Test-Path $Path) { Remove-Item $Path -Force } } if (Test-Path $Path) { Write-Host "File '$Path' already exists, skipping download" return $True } $DownloadDirectory = Split-Path -ErrorAction Ignore -Path "$Path" -Parent if (-Not (Test-Path $DownloadDirectory)) { New-Item -path $DownloadDirectory -force -itemType "Directory" | Out-Null } $TempPath = "$Path.tmp" if (Test-Path -IsValid -Path $Uri) { Write-Verbose "'$Uri' is a file path, copying temporarily to '$TempPath'" Copy-Item -Path $Uri -Destination $TempPath Write-Verbose "Moving temporary file to '$Path'" Move-Item -Path $TempPath -Destination $Path return $? } else { Write-Verbose "Downloading $Uri" # Don't display the console progress UI - it's a huge perf hit $ProgressPreference = 'SilentlyContinue' while($Attempt -Lt $DownloadRetries) { try { Invoke-WebRequest -UseBasicParsing -Uri $Uri -OutFile $TempPath Write-Verbose "Downloaded to temporary location '$TempPath'" Move-Item -Path $TempPath -Destination $Path Write-Verbose "Moved temporary file to '$Path'" return $True } catch { $Attempt++ if ($Attempt -Lt $DownloadRetries) { $AttemptsLeft = $DownloadRetries - $Attempt Write-Warning "Download failed, $AttemptsLeft attempts remaining, will retry in $RetryWaitTimeInSeconds seconds" Start-Sleep -Seconds $RetryWaitTimeInSeconds } else { Write-Error $_ Write-Error $_.Exception } } } } return $False } <# .SYNOPSIS Generate a shim for a native tool .DESCRIPTION Creates a wrapper script (shim) that passes arguments forward to native tool assembly .PARAMETER ShimName The name of the shim .PARAMETER ShimDirectory The directory where shims are stored .PARAMETER ToolFilePath Path to file that shim forwards to .PARAMETER Force Replace shim if already present. Default = False .NOTES Returns $True if generating shim succeeds, $False otherwise #> function New-ScriptShim { [CmdletBinding(PositionalBinding=$false)] Param ( [Parameter(Mandatory=$True)] [string] $ShimName, [Parameter(Mandatory=$True)] [string] $ShimDirectory, [Parameter(Mandatory=$True)] [string] $ToolFilePath, [Parameter(Mandatory=$True)] [string] $BaseUri, [switch] $Force ) try { Write-Verbose "Generating '$ShimName' shim" if (-Not (Test-Path $ToolFilePath)){ Write-Error "Specified tool file path '$ToolFilePath' does not exist" return $False } # WinShimmer is a small .NET Framework program that creates .exe shims to bootstrapped programs # Many of the checks for installed programs expect a .exe extension for Windows tools, rather # than a .bat or .cmd file. # Source: https://github.com/dotnet/arcade/tree/master/src/WinShimmer if (-Not (Test-Path "$ShimDirectory\WinShimmer\winshimmer.exe")) { $InstallStatus = DownloadAndExtract -Uri "$BaseUri/windows/winshimmer/WinShimmer.zip" ` -InstallDirectory $ShimDirectory\WinShimmer ` -Force:$Force ` -DownloadRetries 2 ` -RetryWaitTimeInSeconds 5 ` -Verbose:$Verbose } if ((Test-Path (Join-Path $ShimDirectory "$ShimName.exe"))) { Write-Host "$ShimName.exe already exists; replacing..." Remove-Item (Join-Path $ShimDirectory "$ShimName.exe") } & "$ShimDirectory\WinShimmer\winshimmer.exe" $ShimName $ToolFilePath $ShimDirectory return $True } catch { Write-Host $_ Write-Host $_.Exception return $False } } <# .SYNOPSIS Returns the machine architecture of the host machine .NOTES Returns 'x64' on 64 bit machines Returns 'x86' on 32 bit machines #> function Get-MachineArchitecture { $ProcessorArchitecture = $Env:PROCESSOR_ARCHITECTURE $ProcessorArchitectureW6432 = $Env:PROCESSOR_ARCHITEW6432 if($ProcessorArchitecture -Eq "X86") { if(($ProcessorArchitectureW6432 -Eq "") -Or ($ProcessorArchitectureW6432 -Eq "X86")) { return "x86" } $ProcessorArchitecture = $ProcessorArchitectureW6432 } if (($ProcessorArchitecture -Eq "AMD64") -Or ($ProcessorArchitecture -Eq "IA64") -Or ($ProcessorArchitecture -Eq "ARM64") -Or ($ProcessorArchitecture -Eq "LOONGARCH64") -Or ($ProcessorArchitecture -Eq "RISCV64")) { return "x64" } return "x86" } <# .SYNOPSIS Get the name of a temporary folder under the native install directory #> function Get-TempDirectory { return Join-Path (Get-NativeInstallDirectory) "temp/" } function Get-TempPathFilename { [CmdletBinding(PositionalBinding=$false)] Param ( [Parameter(Mandatory=$True)] [string] $Path ) $TempDir = CommonLibrary\Get-TempDirectory $TempFilename = Split-Path $Path -leaf $TempPath = Join-Path $TempDir $TempFilename return $TempPath } <# .SYNOPSIS Returns the base directory to use for native tool installation .NOTES Returns the value of the NETCOREENG_INSTALL_DIRECTORY if that environment variable is set, or otherwise returns an install directory under the %USERPROFILE% #> function Get-NativeInstallDirectory { $InstallDir = $Env:NETCOREENG_INSTALL_DIRECTORY if (!$InstallDir) { $InstallDir = Join-Path $Env:USERPROFILE ".netcoreeng/native/" } return $InstallDir } <# .SYNOPSIS Unzip an archive .DESCRIPTION Powershell module to unzip an archive to a specified directory .PARAMETER ZipPath (Required) Path to archive to unzip .PARAMETER OutputDirectory (Required) Output directory for archive contents .PARAMETER Force Overwrite output directory contents if they already exist .NOTES - Returns True and does not perform an extraction if output directory already exists but Overwrite is not True. - Returns True if unzip operation is successful - Returns False if Overwrite is True and it is unable to remove contents of OutputDirectory - Returns False if unable to extract zip archive #> function Expand-Zip { [CmdletBinding(PositionalBinding=$false)] Param ( [Parameter(Mandatory=$True)] [string] $ZipPath, [Parameter(Mandatory=$True)] [string] $OutputDirectory, [switch] $Force ) Write-Verbose "Extracting '$ZipPath' to '$OutputDirectory'" try { if ((Test-Path $OutputDirectory) -And (-Not $Force)) { Write-Host "Directory '$OutputDirectory' already exists, skipping extract" return $True } if (Test-Path $OutputDirectory) { Write-Verbose "'Force' is 'True', but '$OutputDirectory' exists, removing directory" Remove-Item $OutputDirectory -Force -Recurse if ($? -Eq $False) { Write-Error "Unable to remove '$OutputDirectory'" return $False } } $TempOutputDirectory = Join-Path "$(Split-Path -Parent $OutputDirectory)" "$(Split-Path -Leaf $OutputDirectory).tmp" if (Test-Path $TempOutputDirectory) { Remove-Item $TempOutputDirectory -Force -Recurse } New-Item -Path $TempOutputDirectory -Force -ItemType "Directory" | Out-Null Add-Type -assembly "system.io.compression.filesystem" [io.compression.zipfile]::ExtractToDirectory("$ZipPath", "$TempOutputDirectory") if ($? -Eq $False) { Write-Error "Unable to extract '$ZipPath'" return $False } Move-Item -Path $TempOutputDirectory -Destination $OutputDirectory } catch { Write-Host $_ Write-Host $_.Exception return $False } return $True } export-modulemember -function DownloadAndExtract export-modulemember -function Expand-Zip export-modulemember -function Get-File export-modulemember -function Get-MachineArchitecture export-modulemember -function Get-NativeInstallDirectory export-modulemember -function Get-TempDirectory export-modulemember -function Get-TempPathFilename export-modulemember -function New-ScriptShim ================================================ FILE: eng/common/native/common-library.sh ================================================ #!/usr/bin/env bash function GetNativeInstallDirectory { local install_dir if [[ -z $NETCOREENG_INSTALL_DIRECTORY ]]; then install_dir=$HOME/.netcoreeng/native/ else install_dir=$NETCOREENG_INSTALL_DIRECTORY fi echo $install_dir return 0 } function GetTempDirectory { echo $(GetNativeInstallDirectory)temp/ return 0 } function ExpandZip { local zip_path=$1 local output_directory=$2 local force=${3:-false} echo "Extracting $zip_path to $output_directory" if [[ -d $output_directory ]] && [[ $force = false ]]; then echo "Directory '$output_directory' already exists, skipping extract" return 0 fi if [[ -d $output_directory ]]; then echo "'Force flag enabled, but '$output_directory' exists. Removing directory" rm -rf $output_directory if [[ $? != 0 ]]; then Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Unable to remove '$output_directory'" return 1 fi fi echo "Creating directory: '$output_directory'" mkdir -p $output_directory echo "Extracting archive" tar -xf $zip_path -C $output_directory if [[ $? != 0 ]]; then Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Unable to extract '$zip_path'" return 1 fi return 0 } function GetCurrentOS { local unameOut="$(uname -s)" case $unameOut in Linux*) echo "Linux";; Darwin*) echo "MacOS";; esac return 0 } function GetFile { local uri=$1 local path=$2 local force=${3:-false} local download_retries=${4:-5} local retry_wait_time_seconds=${5:-30} if [[ -f $path ]]; then if [[ $force = false ]]; then echo "File '$path' already exists. Skipping download" return 0 else rm -rf $path fi fi if [[ -f $uri ]]; then echo "'$uri' is a file path, copying file to '$path'" cp $uri $path return $? fi echo "Downloading $uri" # Use curl if available, otherwise use wget if command -v curl > /dev/null; then curl "$uri" -sSL --retry $download_retries --retry-delay $retry_wait_time_seconds --create-dirs -o "$path" --fail else wget -q -O "$path" "$uri" --tries="$download_retries" fi return $? } function GetTempPathFileName { local path=$1 local temp_dir=$(GetTempDirectory) local temp_file_name=$(basename $path) echo $temp_dir$temp_file_name return 0 } function DownloadAndExtract { local uri=$1 local installDir=$2 local force=${3:-false} local download_retries=${4:-5} local retry_wait_time_seconds=${5:-30} local temp_tool_path=$(GetTempPathFileName $uri) echo "downloading to: $temp_tool_path" # Download file GetFile "$uri" "$temp_tool_path" $force $download_retries $retry_wait_time_seconds if [[ $? != 0 ]]; then Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Failed to download '$uri' to '$temp_tool_path'." return 1 fi # Extract File echo "extracting from $temp_tool_path to $installDir" ExpandZip "$temp_tool_path" "$installDir" $force $download_retries $retry_wait_time_seconds if [[ $? != 0 ]]; then Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Failed to extract '$temp_tool_path' to '$installDir'." return 1 fi return 0 } function NewScriptShim { local shimpath=$1 local tool_file_path=$2 local force=${3:-false} echo "Generating '$shimpath' shim" if [[ -f $shimpath ]]; then if [[ $force = false ]]; then echo "File '$shimpath' already exists." >&2 return 1 else rm -rf $shimpath fi fi if [[ ! -f $tool_file_path ]]; then # try to see if the path is lower cased tool_file_path="$(echo $tool_file_path | tr "[:upper:]" "[:lower:]")" if [[ ! -f $tool_file_path ]]; then Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Specified tool file path:'$tool_file_path' does not exist" return 1 fi fi local shim_contents=$'#!/usr/bin/env bash\n' shim_contents+="SHIMARGS="$'$1\n' shim_contents+="$tool_file_path"$' $SHIMARGS\n' # Write shim file echo "$shim_contents" > $shimpath chmod +x $shimpath echo "Finished generating shim '$shimpath'" return $? } ================================================ FILE: eng/common/native/init-compiler.sh ================================================ #!/bin/sh # # This file detects the C/C++ compiler and exports it to the CC/CXX environment variables # # NOTE: some scripts source this file and rely on stdout being empty, make sure # to not output *anything* here, unless it is an error message that fails the # build. if [ -z "$build_arch" ] || [ -z "$compiler" ]; then echo "Usage..." echo "build_arch= compiler= init-compiler.sh" echo "Specify the target architecture." echo "Specify the name of compiler (clang or gcc)." exit 1 fi case "$compiler" in clang*|-clang*|--clang*) # clangx.y or clang-x.y version="$(echo "$compiler" | tr -d '[:alpha:]-=')" majorVersion="${version%%.*}" # LLVM based on v18 released in early 2024, with two releases per year maxVersion="$((18 + ((($(date +%Y) - 2024) * 12 + $(date +%-m) - 3) / 6)))" compiler=clang ;; gcc*|-gcc*|--gcc*) # gccx.y or gcc-x.y version="$(echo "$compiler" | tr -d '[:alpha:]-=')" majorVersion="${version%%.*}" # GCC based on v14 released in early 2024, with one release per year maxVersion="$((14 + ((($(date +%Y) - 2024) * 12 + $(date +%-m) - 3) / 12)))" compiler=gcc ;; esac cxxCompiler="$compiler++" # clear the existing CC and CXX from environment CC= CXX= LDFLAGS= if [ "$compiler" = "gcc" ]; then cxxCompiler="g++"; fi check_version_exists() { desired_version=-1 # Set up the environment to be used for building with the desired compiler. if command -v "$compiler-$1" > /dev/null; then desired_version="-$1" elif command -v "$compiler$1" > /dev/null; then desired_version="$1" fi echo "$desired_version" } __baseOS="$(uname)" set_compiler_version_from_CC() { if [ "$__baseOS" = "Darwin" ]; then # On Darwin, the versions from -version/-dumpversion refer to Xcode # versions, not llvm versions, so we can't rely on them. return fi version="$("$CC" -dumpversion)" if [ -z "$version" ]; then echo "Error: $CC -dumpversion didn't provide a version" exit 1 fi # gcc and clang often display 3 part versions. However, gcc can show only 1 part in some environments. IFS=. read -r majorVersion _ < /dev/null; then echo "Error: No compatible version of $compiler was found within the range of $minVersion to $maxVersion. Please upgrade your toolchain or specify the compiler explicitly using CLR_CC and CLR_CXX environment variables." exit 1 fi CC="$(command -v "$compiler" 2> /dev/null)" CXX="$(command -v "$cxxCompiler" 2> /dev/null)" set_compiler_version_from_CC fi else desired_version="$(check_version_exists "$majorVersion")" if [ "$desired_version" = "-1" ]; then echo "Error: Could not find specific version of $compiler: $majorVersion." exit 1 fi fi if [ -z "$CC" ]; then CC="$(command -v "$compiler$desired_version" 2> /dev/null)" CXX="$(command -v "$cxxCompiler$desired_version" 2> /dev/null)" if [ -z "$CXX" ]; then CXX="$(command -v "$cxxCompiler" 2> /dev/null)"; fi set_compiler_version_from_CC fi else if [ ! -f "$CLR_CC" ]; then echo "Error: CLR_CC is set but path '$CLR_CC' does not exist" exit 1 fi CC="$CLR_CC" CXX="$CLR_CXX" set_compiler_version_from_CC fi if [ -z "$CC" ]; then echo "Error: Unable to find $compiler." exit 1 fi if [ "$__baseOS" != "Darwin" ]; then # On Darwin, we always want to use the Apple linker. # Only lld version >= 9 can be considered stable. lld supports s390x starting from 18.0. if [ "$compiler" = "clang" ] && [ -n "$majorVersion" ] && [ "$majorVersion" -ge 9 ] && { [ "$build_arch" != "s390x" ] || [ "$majorVersion" -ge 18 ]; }; then if "$CC" -fuse-ld=lld -Wl,--version >/dev/null 2>&1; then LDFLAGS="-fuse-ld=lld" fi fi fi SCAN_BUILD_COMMAND="$(command -v "scan-build$desired_version" 2> /dev/null)" export CC CXX LDFLAGS SCAN_BUILD_COMMAND ================================================ FILE: eng/common/native/init-distro-rid.sh ================================================ #!/bin/sh # getNonPortableDistroRid # # Input: # targetOs: (str) # targetArch: (str) # rootfsDir: (str) # # Return: # non-portable rid getNonPortableDistroRid() { targetOs="$1" targetArch="$2" rootfsDir="$3" nonPortableRid="" if [ "$targetOs" = "linux" ]; then # shellcheck disable=SC1091 if [ -e "${rootfsDir}/etc/os-release" ]; then . "${rootfsDir}/etc/os-release" if echo "${VERSION_ID:-}" | grep -qE '^([[:digit:]]|\.)+$'; then nonPortableRid="${ID}.${VERSION_ID}-${targetArch}" else # Rolling release distros either do not set VERSION_ID, set it as blank or # set it to non-version looking string (such as TEMPLATE_VERSION_ID on ArchLinux); # so omit it here to be consistent with everything else. nonPortableRid="${ID}-${targetArch}" fi elif [ -e "${rootfsDir}/android_platform" ]; then # shellcheck disable=SC1091 . "${rootfsDir}/android_platform" nonPortableRid="$RID" fi fi if [ "$targetOs" = "freebsd" ]; then # $rootfsDir can be empty. freebsd-version is a shell script and should always work. __freebsd_major_version=$("$rootfsDir"/bin/freebsd-version | cut -d'.' -f1) nonPortableRid="freebsd.$__freebsd_major_version-${targetArch}" elif command -v getprop >/dev/null && getprop ro.product.system.model | grep -qi android; then __android_sdk_version=$(getprop ro.build.version.sdk) nonPortableRid="android.$__android_sdk_version-${targetArch}" elif [ "$targetOs" = "illumos" ]; then __uname_version=$(uname -v) nonPortableRid="illumos-${targetArch}" elif [ "$targetOs" = "solaris" ]; then __uname_version=$(uname -v) __solaris_major_version=$(echo "$__uname_version" | cut -d'.' -f1) nonPortableRid="solaris.$__solaris_major_version-${targetArch}" elif [ "$targetOs" = "haiku" ]; then __uname_release="$(uname -r)" nonPortableRid=haiku.r"$__uname_release"-"$targetArch" fi echo "$nonPortableRid" | tr '[:upper:]' '[:lower:]' } # initDistroRidGlobal # # Input: # os: (str) # arch: (str) # rootfsDir?: (nullable:string) # # Return: # None # # Notes: # It is important to note that the function does not return anything, but it # exports the following variables on success: # __DistroRid : Non-portable rid of the target platform. # __PortableTargetOS : OS-part of the portable rid that corresponds to the target platform. initDistroRidGlobal() { targetOs="$1" targetArch="$2" rootfsDir="" if [ $# -ge 3 ]; then rootfsDir="$3" fi if [ -n "${rootfsDir}" ]; then # We may have a cross build. Check for the existence of the rootfsDir if [ ! -e "${rootfsDir}" ]; then echo "Error: rootfsDir has been passed, but the location is not valid." exit 1 fi fi __DistroRid=$(getNonPortableDistroRid "${targetOs}" "${targetArch}" "${rootfsDir}") if [ -z "${__PortableTargetOS:-}" ]; then __PortableTargetOS="$targetOs" STRINGS="$(command -v strings || true)" if [ -z "$STRINGS" ]; then STRINGS="$(command -v llvm-strings || true)" fi # Check for musl-based distros (e.g. Alpine Linux, Void Linux). if "${rootfsDir}/usr/bin/ldd" --version 2>&1 | grep -q musl || ( [ -n "$STRINGS" ] && "$STRINGS" "${rootfsDir}/usr/bin/ldd" 2>&1 | grep -q musl ); then __PortableTargetOS="linux-musl" fi fi export __DistroRid __PortableTargetOS } ================================================ FILE: eng/common/native/init-os-and-arch.sh ================================================ #!/bin/sh # Use uname to determine what the OS is. OSName=$(uname -s | tr '[:upper:]' '[:lower:]') if command -v getprop && getprop ro.product.system.model 2>&1 | grep -qi android; then OSName="android" fi case "$OSName" in freebsd|linux|netbsd|openbsd|sunos|android|haiku) os="$OSName" ;; darwin) os=osx ;; *) echo "Unsupported OS $OSName detected!" exit 1 ;; esac # On Solaris, `uname -m` is discouraged, see https://docs.oracle.com/cd/E36784_01/html/E36870/uname-1.html # and `uname -p` returns processor type (e.g. i386 on amd64). # The appropriate tool to determine CPU is isainfo(1) https://docs.oracle.com/cd/E36784_01/html/E36870/isainfo-1.html. if [ "$os" = "sunos" ]; then if uname -o 2>&1 | grep -q illumos; then os="illumos" else os="solaris" fi CPUName=$(isainfo -n) else # For the rest of the operating systems, use uname(1) to determine what the CPU is. CPUName=$(uname -m) fi case "$CPUName" in arm64|aarch64) arch=arm64 if [ "$(getconf LONG_BIT)" -lt 64 ]; then # This is 32-bit OS running on 64-bit CPU (for example Raspberry Pi OS) arch=arm fi ;; loongarch64) arch=loongarch64 ;; riscv64) arch=riscv64 ;; amd64|x86_64) arch=x64 ;; armv7l|armv8l) # shellcheck disable=SC1091 if (NAME=""; . /etc/os-release; test "$NAME" = "Tizen"); then arch=armel else arch=arm fi ;; armv6l) arch=armv6 ;; i[3-6]86) echo "Unsupported CPU $CPUName detected, build might not succeed!" arch=x86 ;; s390x) arch=s390x ;; ppc64le) arch=ppc64le ;; *) echo "Unknown CPU $CPUName detected!" exit 1 ;; esac ================================================ FILE: eng/common/native/install-cmake-test.sh ================================================ #!/usr/bin/env bash source="${BASH_SOURCE[0]}" scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" . $scriptroot/common-library.sh base_uri= install_path= version= clean=false force=false download_retries=5 retry_wait_time_seconds=30 while (($# > 0)); do lowerI="$(echo $1 | tr "[:upper:]" "[:lower:]")" case $lowerI in --baseuri) base_uri=$2 shift 2 ;; --installpath) install_path=$2 shift 2 ;; --version) version=$2 shift 2 ;; --clean) clean=true shift 1 ;; --force) force=true shift 1 ;; --downloadretries) download_retries=$2 shift 2 ;; --retrywaittimeseconds) retry_wait_time_seconds=$2 shift 2 ;; --help) echo "Common settings:" echo " --baseuri Base file directory or Url wrom which to acquire tool archives" echo " --installpath Base directory to install native tool to" echo " --clean Don't install the tool, just clean up the current install of the tool" echo " --force Force install of tools even if they previously exist" echo " --help Print help and exit" echo "" echo "Advanced settings:" echo " --downloadretries Total number of retry attempts" echo " --retrywaittimeseconds Wait time between retry attempts in seconds" echo "" exit 0 ;; esac done tool_name="cmake-test" tool_os=$(GetCurrentOS) tool_folder="$(echo $tool_os | tr "[:upper:]" "[:lower:]")" tool_arch="x86_64" tool_name_moniker="$tool_name-$version-$tool_os-$tool_arch" tool_install_directory="$install_path/$tool_name/$version" tool_file_path="$tool_install_directory/$tool_name_moniker/bin/$tool_name" shim_path="$install_path/$tool_name.sh" uri="${base_uri}/$tool_folder/$tool_name/$tool_name_moniker.tar.gz" # Clean up tool and installers if [[ $clean = true ]]; then echo "Cleaning $tool_install_directory" if [[ -d $tool_install_directory ]]; then rm -rf $tool_install_directory fi echo "Cleaning $shim_path" if [[ -f $shim_path ]]; then rm -rf $shim_path fi tool_temp_path=$(GetTempPathFileName $uri) echo "Cleaning $tool_temp_path" if [[ -f $tool_temp_path ]]; then rm -rf $tool_temp_path fi exit 0 fi # Install tool if [[ -f $tool_file_path ]] && [[ $force = false ]]; then echo "$tool_name ($version) already exists, skipping install" exit 0 fi DownloadAndExtract $uri $tool_install_directory $force $download_retries $retry_wait_time_seconds if [[ $? != 0 ]]; then Write-PipelineTelemetryError -category 'NativeToolsBootstrap' 'Installation failed' exit 1 fi # Generate Shim # Always rewrite shims so that we are referencing the expected version NewScriptShim $shim_path $tool_file_path true if [[ $? != 0 ]]; then Write-PipelineTelemetryError -category 'NativeToolsBootstrap' 'Shim generation failed' exit 1 fi exit 0 ================================================ FILE: eng/common/native/install-cmake.sh ================================================ #!/usr/bin/env bash source="${BASH_SOURCE[0]}" scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" . $scriptroot/common-library.sh base_uri= install_path= version= clean=false force=false download_retries=5 retry_wait_time_seconds=30 while (($# > 0)); do lowerI="$(echo $1 | tr "[:upper:]" "[:lower:]")" case $lowerI in --baseuri) base_uri=$2 shift 2 ;; --installpath) install_path=$2 shift 2 ;; --version) version=$2 shift 2 ;; --clean) clean=true shift 1 ;; --force) force=true shift 1 ;; --downloadretries) download_retries=$2 shift 2 ;; --retrywaittimeseconds) retry_wait_time_seconds=$2 shift 2 ;; --help) echo "Common settings:" echo " --baseuri Base file directory or Url wrom which to acquire tool archives" echo " --installpath Base directory to install native tool to" echo " --clean Don't install the tool, just clean up the current install of the tool" echo " --force Force install of tools even if they previously exist" echo " --help Print help and exit" echo "" echo "Advanced settings:" echo " --downloadretries Total number of retry attempts" echo " --retrywaittimeseconds Wait time between retry attempts in seconds" echo "" exit 0 ;; esac done tool_name="cmake" tool_os=$(GetCurrentOS) tool_folder="$(echo $tool_os | tr "[:upper:]" "[:lower:]")" tool_arch="x86_64" tool_name_moniker="$tool_name-$version-$tool_os-$tool_arch" tool_install_directory="$install_path/$tool_name/$version" tool_file_path="$tool_install_directory/$tool_name_moniker/bin/$tool_name" shim_path="$install_path/$tool_name.sh" uri="${base_uri}/$tool_folder/$tool_name/$tool_name_moniker.tar.gz" # Clean up tool and installers if [[ $clean = true ]]; then echo "Cleaning $tool_install_directory" if [[ -d $tool_install_directory ]]; then rm -rf $tool_install_directory fi echo "Cleaning $shim_path" if [[ -f $shim_path ]]; then rm -rf $shim_path fi tool_temp_path=$(GetTempPathFileName $uri) echo "Cleaning $tool_temp_path" if [[ -f $tool_temp_path ]]; then rm -rf $tool_temp_path fi exit 0 fi # Install tool if [[ -f $tool_file_path ]] && [[ $force = false ]]; then echo "$tool_name ($version) already exists, skipping install" exit 0 fi DownloadAndExtract $uri $tool_install_directory $force $download_retries $retry_wait_time_seconds if [[ $? != 0 ]]; then Write-PipelineTelemetryError -category 'NativeToolsBootstrap' 'Installation failed' exit 1 fi # Generate Shim # Always rewrite shims so that we are referencing the expected version NewScriptShim $shim_path $tool_file_path true if [[ $? != 0 ]]; then Write-PipelineTelemetryError -category 'NativeToolsBootstrap' 'Shim generation failed' exit 1 fi exit 0 ================================================ FILE: eng/common/native/install-dependencies.sh ================================================ #!/bin/sh set -e # This is a simple script primarily used for CI to install necessary dependencies # # Usage: # # ./install-dependencies.sh os="$(echo "$1" | tr "[:upper:]" "[:lower:]")" if [ -z "$os" ]; then . "$(dirname "$0")"/init-os-and-arch.sh fi case "$os" in linux) if [ -e /etc/os-release ]; then . /etc/os-release fi if [ "$ID" = "debian" ] || [ "$ID_LIKE" = "debian" ]; then apt update apt install -y build-essential gettext locales cmake llvm clang lld lldb liblldb-dev libunwind8-dev libicu-dev liblttng-ust-dev \ libssl-dev libkrb5-dev zlib1g-dev pigz cpio localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 elif [ "$ID" = "fedora" ] || [ "$ID" = "rhel" ]; then dnf install -y cmake llvm lld lldb clang python curl libicu-devel openssl-devel krb5-devel zlib-devel lttng-ust-devel pigz cpio elif [ "$ID" = "alpine" ]; then apk add build-base cmake bash curl clang llvm-dev lld lldb krb5-dev lttng-ust-dev icu-dev zlib-dev openssl-dev pigz cpio else echo "Unsupported distro. distro: $ID" exit 1 fi ;; osx|maccatalyst|ios|iossimulator|tvos|tvossimulator) echo "Installed xcode version: $(xcode-select -p)" export HOMEBREW_NO_INSTALL_CLEANUP=1 export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 # Skip brew update for now, see https://github.com/actions/setup-python/issues/577 # brew update --preinstall brew bundle --no-upgrade --no-lock --file=- < [CmdletBinding(PositionalBinding=$false)] Param ( [Parameter(Mandatory=$True)] [string] $ToolName, [Parameter(Mandatory=$True)] [string] $InstallPath, [Parameter(Mandatory=$True)] [string] $BaseUri, [Parameter(Mandatory=$True)] [string] $Version, [string] $CommonLibraryDirectory = $PSScriptRoot, [switch] $Force = $False, [switch] $Clean = $False, [int] $DownloadRetries = 5, [int] $RetryWaitTimeInSeconds = 30 ) . $PSScriptRoot\..\pipeline-logging-functions.ps1 # Import common library modules Import-Module -Name (Join-Path $CommonLibraryDirectory "CommonLibrary.psm1") try { # Define verbose switch if undefined $Verbose = $VerbosePreference -Eq "Continue" $Arch = CommonLibrary\Get-MachineArchitecture $ToolOs = "win64" if($Arch -Eq "x32") { $ToolOs = "win32" } $ToolNameMoniker = "$ToolName-$Version-$ToolOs-$Arch" $ToolInstallDirectory = Join-Path $InstallPath "$ToolName\$Version\" $Uri = "$BaseUri/windows/$ToolName/$ToolNameMoniker.zip" $ShimPath = Join-Path $InstallPath "$ToolName.exe" if ($Clean) { Write-Host "Cleaning $ToolInstallDirectory" if (Test-Path $ToolInstallDirectory) { Remove-Item $ToolInstallDirectory -Force -Recurse } Write-Host "Cleaning $ShimPath" if (Test-Path $ShimPath) { Remove-Item $ShimPath -Force } $ToolTempPath = CommonLibrary\Get-TempPathFilename -Path $Uri Write-Host "Cleaning $ToolTempPath" if (Test-Path $ToolTempPath) { Remove-Item $ToolTempPath -Force } exit 0 } # Install tool if ((Test-Path $ToolInstallDirectory) -And (-Not $Force)) { Write-Verbose "$ToolName ($Version) already exists, skipping install" } else { $InstallStatus = CommonLibrary\DownloadAndExtract -Uri $Uri ` -InstallDirectory $ToolInstallDirectory ` -Force:$Force ` -DownloadRetries $DownloadRetries ` -RetryWaitTimeInSeconds $RetryWaitTimeInSeconds ` -Verbose:$Verbose if ($InstallStatus -Eq $False) { Write-PipelineTelemetryError "Installation failed" -Category "NativeToolsetBootstrapping" exit 1 } } $ToolFilePath = Get-ChildItem $ToolInstallDirectory -Recurse -Filter "$ToolName.exe" | % { $_.FullName } if (@($ToolFilePath).Length -Gt 1) { Write-Error "There are multiple copies of $ToolName in $($ToolInstallDirectory): `n$(@($ToolFilePath | out-string))" exit 1 } elseif (@($ToolFilePath).Length -Lt 1) { Write-Host "$ToolName was not found in $ToolInstallDirectory." exit 1 } # Generate shim # Always rewrite shims so that we are referencing the expected version $GenerateShimStatus = CommonLibrary\New-ScriptShim -ShimName $ToolName ` -ShimDirectory $InstallPath ` -ToolFilePath "$ToolFilePath" ` -BaseUri $BaseUri ` -Force:$Force ` -Verbose:$Verbose if ($GenerateShimStatus -Eq $False) { Write-PipelineTelemetryError "Generate shim failed" -Category "NativeToolsetBootstrapping" return 1 } exit 0 } catch { Write-Host $_.ScriptStackTrace Write-PipelineTelemetryError -Category "NativeToolsetBootstrapping" -Message $_ exit 1 } ================================================ FILE: eng/common/pipeline-logging-functions.ps1 ================================================ # Source for this file was taken from https://github.com/microsoft/azure-pipelines-task-lib/blob/11c9439d4af17e6475d9fe058e6b2e03914d17e6/powershell/VstsTaskSdk/LoggingCommandFunctions.ps1 and modified. # NOTE: You should not be calling these method directly as they are likely to change. Instead you should be calling the Write-Pipeline* functions defined in tools.ps1 $script:loggingCommandPrefix = '##vso[' $script:loggingCommandEscapeMappings = @( # TODO: WHAT ABOUT "="? WHAT ABOUT "%"? New-Object psobject -Property @{ Token = ';' ; Replacement = '%3B' } New-Object psobject -Property @{ Token = "`r" ; Replacement = '%0D' } New-Object psobject -Property @{ Token = "`n" ; Replacement = '%0A' } New-Object psobject -Property @{ Token = "]" ; Replacement = '%5D' } ) # TODO: BUG: Escape % ??? # TODO: Add test to verify don't need to escape "=". # Specify "-Force" to force pipeline formatted output even if "$ci" is false or not set function Write-PipelineTelemetryError { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$Category, [Parameter(Mandatory = $true)] [string]$Message, [Parameter(Mandatory = $false)] [string]$Type = 'error', [string]$ErrCode, [string]$SourcePath, [string]$LineNumber, [string]$ColumnNumber, [switch]$AsOutput, [switch]$Force) $PSBoundParameters.Remove('Category') | Out-Null if ($Force -Or ((Test-Path variable:ci) -And $ci)) { $Message = "(NETCORE_ENGINEERING_TELEMETRY=$Category) $Message" } $PSBoundParameters.Remove('Message') | Out-Null $PSBoundParameters.Add('Message', $Message) Write-PipelineTaskError @PSBoundParameters } # Specify "-Force" to force pipeline formatted output even if "$ci" is false or not set function Write-PipelineTaskError { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$Message, [Parameter(Mandatory = $false)] [string]$Type = 'error', [string]$ErrCode, [string]$SourcePath, [string]$LineNumber, [string]$ColumnNumber, [switch]$AsOutput, [switch]$Force ) if (!$Force -And (-Not (Test-Path variable:ci) -Or !$ci)) { if ($Type -eq 'error') { Write-Host $Message -ForegroundColor Red return } elseif ($Type -eq 'warning') { Write-Host $Message -ForegroundColor Yellow return } } if (($Type -ne 'error') -and ($Type -ne 'warning')) { Write-Host $Message return } $PSBoundParameters.Remove('Force') | Out-Null if (-not $PSBoundParameters.ContainsKey('Type')) { $PSBoundParameters.Add('Type', 'error') } Write-LogIssue @PSBoundParameters } function Write-PipelineSetVariable { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$Name, [string]$Value, [switch]$Secret, [switch]$AsOutput, [bool]$IsMultiJobVariable = $true) if ((Test-Path variable:ci) -And $ci) { Write-LoggingCommand -Area 'task' -Event 'setvariable' -Data $Value -Properties @{ 'variable' = $Name 'isSecret' = $Secret 'isOutput' = $IsMultiJobVariable } -AsOutput:$AsOutput } } function Write-PipelinePrependPath { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$Path, [switch]$AsOutput) if ((Test-Path variable:ci) -And $ci) { Write-LoggingCommand -Area 'task' -Event 'prependpath' -Data $Path -AsOutput:$AsOutput } } function Write-PipelineSetResult { [CmdletBinding()] param( [ValidateSet("Succeeded", "SucceededWithIssues", "Failed", "Cancelled", "Skipped")] [Parameter(Mandatory = $true)] [string]$Result, [string]$Message) if ((Test-Path variable:ci) -And $ci) { Write-LoggingCommand -Area 'task' -Event 'complete' -Data $Message -Properties @{ 'result' = $Result } } } <######################################## # Private functions. ########################################> function Format-LoggingCommandData { [CmdletBinding()] param([string]$Value, [switch]$Reverse) if (!$Value) { return '' } if (!$Reverse) { foreach ($mapping in $script:loggingCommandEscapeMappings) { $Value = $Value.Replace($mapping.Token, $mapping.Replacement) } } else { for ($i = $script:loggingCommandEscapeMappings.Length - 1 ; $i -ge 0 ; $i--) { $mapping = $script:loggingCommandEscapeMappings[$i] $Value = $Value.Replace($mapping.Replacement, $mapping.Token) } } return $Value } function Format-LoggingCommand { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$Area, [Parameter(Mandatory = $true)] [string]$Event, [string]$Data, [hashtable]$Properties) # Append the preamble. [System.Text.StringBuilder]$sb = New-Object -TypeName System.Text.StringBuilder $null = $sb.Append($script:loggingCommandPrefix).Append($Area).Append('.').Append($Event) # Append the properties. if ($Properties) { $first = $true foreach ($key in $Properties.Keys) { [string]$value = Format-LoggingCommandData $Properties[$key] if ($value) { if ($first) { $null = $sb.Append(' ') $first = $false } else { $null = $sb.Append(';') } $null = $sb.Append("$key=$value") } } } # Append the tail and output the value. $Data = Format-LoggingCommandData $Data $sb.Append(']').Append($Data).ToString() } function Write-LoggingCommand { [CmdletBinding(DefaultParameterSetName = 'Parameters')] param( [Parameter(Mandatory = $true, ParameterSetName = 'Parameters')] [string]$Area, [Parameter(Mandatory = $true, ParameterSetName = 'Parameters')] [string]$Event, [Parameter(ParameterSetName = 'Parameters')] [string]$Data, [Parameter(ParameterSetName = 'Parameters')] [hashtable]$Properties, [Parameter(Mandatory = $true, ParameterSetName = 'Object')] $Command, [switch]$AsOutput) if ($PSCmdlet.ParameterSetName -eq 'Object') { Write-LoggingCommand -Area $Command.Area -Event $Command.Event -Data $Command.Data -Properties $Command.Properties -AsOutput:$AsOutput return } $command = Format-LoggingCommand -Area $Area -Event $Event -Data $Data -Properties $Properties if ($AsOutput) { $command } else { Write-Host $command } } function Write-LogIssue { [CmdletBinding()] param( [ValidateSet('warning', 'error')] [Parameter(Mandatory = $true)] [string]$Type, [string]$Message, [string]$ErrCode, [string]$SourcePath, [string]$LineNumber, [string]$ColumnNumber, [switch]$AsOutput) $command = Format-LoggingCommand -Area 'task' -Event 'logissue' -Data $Message -Properties @{ 'type' = $Type 'code' = $ErrCode 'sourcepath' = $SourcePath 'linenumber' = $LineNumber 'columnnumber' = $ColumnNumber } if ($AsOutput) { return $command } if ($Type -eq 'error') { $foregroundColor = $host.PrivateData.ErrorForegroundColor $backgroundColor = $host.PrivateData.ErrorBackgroundColor if ($foregroundColor -isnot [System.ConsoleColor] -or $backgroundColor -isnot [System.ConsoleColor]) { $foregroundColor = [System.ConsoleColor]::Red $backgroundColor = [System.ConsoleColor]::Black } } else { $foregroundColor = $host.PrivateData.WarningForegroundColor $backgroundColor = $host.PrivateData.WarningBackgroundColor if ($foregroundColor -isnot [System.ConsoleColor] -or $backgroundColor -isnot [System.ConsoleColor]) { $foregroundColor = [System.ConsoleColor]::Yellow $backgroundColor = [System.ConsoleColor]::Black } } Write-Host $command -ForegroundColor $foregroundColor -BackgroundColor $backgroundColor } ================================================ FILE: eng/common/pipeline-logging-functions.sh ================================================ #!/usr/bin/env bash function Write-PipelineTelemetryError { local telemetry_category='' local force=false local function_args=() local message='' while [[ $# -gt 0 ]]; do opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" case "$opt" in -category|-c) telemetry_category=$2 shift ;; -force|-f) force=true ;; -*) function_args+=("$1 $2") shift ;; *) message=$* ;; esac shift done if [[ $force != true ]] && [[ "$ci" != true ]]; then echo "$message" >&2 return fi if [[ $force == true ]]; then function_args+=("-force") fi message="(NETCORE_ENGINEERING_TELEMETRY=$telemetry_category) $message" function_args+=("$message") Write-PipelineTaskError ${function_args[@]} } function Write-PipelineTaskError { local message_type="error" local sourcepath='' local linenumber='' local columnnumber='' local error_code='' local force=false while [[ $# -gt 0 ]]; do opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" case "$opt" in -type|-t) message_type=$2 shift ;; -sourcepath|-s) sourcepath=$2 shift ;; -linenumber|-ln) linenumber=$2 shift ;; -columnnumber|-cn) columnnumber=$2 shift ;; -errcode|-e) error_code=$2 shift ;; -force|-f) force=true ;; *) break ;; esac shift done if [[ $force != true ]] && [[ "$ci" != true ]]; then echo "$@" >&2 return fi local message="##vso[task.logissue" message="$message type=$message_type" if [ -n "$sourcepath" ]; then message="$message;sourcepath=$sourcepath" fi if [ -n "$linenumber" ]; then message="$message;linenumber=$linenumber" fi if [ -n "$columnnumber" ]; then message="$message;columnnumber=$columnnumber" fi if [ -n "$error_code" ]; then message="$message;code=$error_code" fi message="$message]$*" echo "$message" } function Write-PipelineSetVariable { if [[ "$ci" != true ]]; then return fi local name='' local value='' local secret=false local as_output=false local is_multi_job_variable=true while [[ $# -gt 0 ]]; do opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" case "$opt" in -name|-n) name=$2 shift ;; -value|-v) value=$2 shift ;; -secret|-s) secret=true ;; -as_output|-a) as_output=true ;; -is_multi_job_variable|-i) is_multi_job_variable=$2 shift ;; esac shift done value=${value/;/%3B} value=${value/\\r/%0D} value=${value/\\n/%0A} value=${value/]/%5D} local message="##vso[task.setvariable variable=$name;isSecret=$secret;isOutput=$is_multi_job_variable]$value" if [[ "$as_output" == true ]]; then $message else echo "$message" fi } function Write-PipelinePrependPath { local prepend_path='' while [[ $# -gt 0 ]]; do opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" case "$opt" in -path|-p) prepend_path=$2 shift ;; esac shift done export PATH="$prepend_path:$PATH" if [[ "$ci" == true ]]; then echo "##vso[task.prependpath]$prepend_path" fi } function Write-PipelineSetResult { local result='' local message='' while [[ $# -gt 0 ]]; do opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" case "$opt" in -result|-r) result=$2 shift ;; -message|-m) message=$2 shift ;; esac shift done if [[ "$ci" == true ]]; then echo "##vso[task.complete result=$result;]$message" fi } ================================================ FILE: eng/common/post-build/check-channel-consistency.ps1 ================================================ param( [Parameter(Mandatory=$true)][string] $PromoteToChannels, # List of channels that the build should be promoted to [Parameter(Mandatory=$true)][array] $AvailableChannelIds # List of channel IDs available in the YAML implementation ) try { $ErrorActionPreference = 'Stop' Set-StrictMode -Version 2.0 # `tools.ps1` checks $ci to perform some actions. Since the post-build # scripts don't necessarily execute in the same agent that run the # build.ps1/sh script this variable isn't automatically set. $ci = $true $disableConfigureToolsetImport = $true . $PSScriptRoot\..\tools.ps1 if ($PromoteToChannels -eq "") { Write-PipelineTaskError -Type 'warning' -Message "This build won't publish assets as it's not configured to any Maestro channel. If that wasn't intended use Darc to configure a default channel using add-default-channel for this branch or to promote it to a channel using add-build-to-channel. See https://github.com/dotnet/arcade/blob/main/Documentation/Darc.md#assigning-an-individual-build-to-a-channel for more info." ExitWithExitCode 0 } # Check that every channel that Maestro told to promote the build to # is available in YAML $PromoteToChannelsIds = $PromoteToChannels -split "\D" | Where-Object { $_ } $hasErrors = $false foreach ($id in $PromoteToChannelsIds) { if (($id -ne 0) -and ($id -notin $AvailableChannelIds)) { Write-PipelineTaskError -Message "Channel $id is not present in the post-build YAML configuration! This is an error scenario. Please contact @dnceng." $hasErrors = $true } } # The `Write-PipelineTaskError` doesn't error the script and we might report several errors # in the previous lines. The check below makes sure that we return an error state from the # script if we reported any validation error if ($hasErrors) { ExitWithExitCode 1 } Write-Host 'done.' } catch { Write-Host $_ Write-PipelineTelemetryError -Category 'CheckChannelConsistency' -Message "There was an error while trying to check consistency of Maestro default channels for the build and post-build YAML configuration." ExitWithExitCode 1 } ================================================ FILE: eng/common/post-build/nuget-validation.ps1 ================================================ # This script validates NuGet package metadata information using this # tool: https://github.com/NuGet/NuGetGallery/tree/jver-verify/src/VerifyMicrosoftPackage param( [Parameter(Mandatory=$true)][string] $PackagesPath # Path to where the packages to be validated are ) # `tools.ps1` checks $ci to perform some actions. Since the post-build # scripts don't necessarily execute in the same agent that run the # build.ps1/sh script this variable isn't automatically set. $ci = $true $disableConfigureToolsetImport = $true . $PSScriptRoot\..\tools.ps1 try { & $PSScriptRoot\nuget-verification.ps1 ${PackagesPath}\*.nupkg } catch { Write-Host $_.ScriptStackTrace Write-PipelineTelemetryError -Category 'NuGetValidation' -Message $_ ExitWithExitCode 1 } ================================================ FILE: eng/common/post-build/nuget-verification.ps1 ================================================ <# .SYNOPSIS Verifies that Microsoft NuGet packages have proper metadata. .DESCRIPTION Downloads a verification tool and runs metadata validation on the provided NuGet packages. This script writes an error if any of the provided packages fail validation. All arguments provided to this PowerShell script that do not match PowerShell parameters are passed on to the verification tool downloaded during the execution of this script. .PARAMETER NuGetExePath The path to the nuget.exe binary to use. If not provided, nuget.exe will be downloaded into the -DownloadPath directory. .PARAMETER PackageSource The package source to use to download the verification tool. If not provided, nuget.org will be used. .PARAMETER DownloadPath The directory path to download the verification tool and nuget.exe to. If not provided, %TEMP%\NuGet.VerifyNuGetPackage will be used. .PARAMETER args Arguments that will be passed to the verification tool. .EXAMPLE PS> .\verify.ps1 *.nupkg Verifies the metadata of all .nupkg files in the currect working directory. .EXAMPLE PS> .\verify.ps1 --help Displays the help text of the downloaded verifiction tool. .LINK https://github.com/NuGet/NuGetGallery/blob/master/src/VerifyMicrosoftPackage/README.md #> # This script was copied from https://github.com/NuGet/NuGetGallery/blob/3e25ad135146676bcab0050a516939d9958bfa5d/src/VerifyMicrosoftPackage/verify.ps1 [CmdletBinding(PositionalBinding = $false)] param( [string]$NuGetExePath, [string]$PackageSource = "https://api.nuget.org/v3/index.json", [string]$DownloadPath, [Parameter(ValueFromRemainingArguments = $true)] [string[]]$args ) # The URL to download nuget.exe. $nugetExeUrl = "https://dist.nuget.org/win-x86-commandline/v4.9.4/nuget.exe" # The package ID of the verification tool. $packageId = "NuGet.VerifyMicrosoftPackage" # The location that nuget.exe and the verification tool will be downloaded to. if (!$DownloadPath) { $DownloadPath = (Join-Path $env:TEMP "NuGet.VerifyMicrosoftPackage") } $fence = New-Object -TypeName string -ArgumentList '=', 80 # Create the download directory, if it doesn't already exist. if (!(Test-Path $DownloadPath)) { New-Item -ItemType Directory $DownloadPath | Out-Null } Write-Host "Using download path: $DownloadPath" if ($NuGetExePath) { $nuget = $NuGetExePath } else { $downloadedNuGetExe = Join-Path $DownloadPath "nuget.exe" # Download nuget.exe, if it doesn't already exist. if (!(Test-Path $downloadedNuGetExe)) { Write-Host "Downloading nuget.exe from $nugetExeUrl..." $ProgressPreference = 'SilentlyContinue' try { Invoke-WebRequest $nugetExeUrl -OutFile $downloadedNuGetExe $ProgressPreference = 'Continue' } catch { $ProgressPreference = 'Continue' Write-Error $_ Write-Error "nuget.exe failed to download." exit } } $nuget = $downloadedNuGetExe } Write-Host "Using nuget.exe path: $nuget" Write-Host " " # Download the latest version of the verification tool. Write-Host "Downloading the latest version of $packageId from $packageSource..." Write-Host $fence & $nuget install $packageId ` -Prerelease ` -OutputDirectory $DownloadPath ` -Source $PackageSource Write-Host $fence Write-Host " " if ($LASTEXITCODE -ne 0) { Write-Error "nuget.exe failed to fetch the verify tool." exit } # Find the most recently downloaded tool Write-Host "Finding the most recently downloaded verification tool." $verifyProbePath = Join-Path $DownloadPath "$packageId.*" $verifyPath = Get-ChildItem -Path $verifyProbePath -Directory ` | Sort-Object -Property LastWriteTime -Descending ` | Select-Object -First 1 $verify = Join-Path $verifyPath "tools\NuGet.VerifyMicrosoftPackage.exe" Write-Host "Using verification tool: $verify" Write-Host " " # Execute the verification tool. Write-Host "Executing the verify tool..." Write-Host $fence & $verify $args Write-Host $fence Write-Host " " # Respond to the exit code. if ($LASTEXITCODE -ne 0) { Write-Error "The verify tool found some problems." } else { Write-Output "The verify tool succeeded." } ================================================ FILE: eng/common/post-build/publish-using-darc.ps1 ================================================ param( [Parameter(Mandatory=$true)][int] $BuildId, [Parameter(Mandatory=$true)][int] $PublishingInfraVersion, [Parameter(Mandatory=$true)][string] $AzdoToken, [Parameter(Mandatory=$false)][string] $MaestroApiEndPoint = 'https://maestro.dot.net', [Parameter(Mandatory=$true)][string] $WaitPublishingFinish, [Parameter(Mandatory=$false)][string] $ArtifactsPublishingAdditionalParameters, [Parameter(Mandatory=$false)][string] $SymbolPublishingAdditionalParameters, [Parameter(Mandatory=$false)][string] $RequireDefaultChannels ) try { # `tools.ps1` checks $ci to perform some actions. Since the post-build # scripts don't necessarily execute in the same agent that run the # build.ps1/sh script this variable isn't automatically set. $ci = $true $disableConfigureToolsetImport = $true . $PSScriptRoot\..\tools.ps1 $darc = Get-Darc $optionalParams = [System.Collections.ArrayList]::new() if ("" -ne $ArtifactsPublishingAdditionalParameters) { $optionalParams.Add("--artifact-publishing-parameters") | Out-Null $optionalParams.Add($ArtifactsPublishingAdditionalParameters) | Out-Null } if ("" -ne $SymbolPublishingAdditionalParameters) { $optionalParams.Add("--symbol-publishing-parameters") | Out-Null $optionalParams.Add($SymbolPublishingAdditionalParameters) | Out-Null } if ("false" -eq $WaitPublishingFinish) { $optionalParams.Add("--no-wait") | Out-Null } if ("true" -eq $RequireDefaultChannels) { $optionalParams.Add("--default-channels-required") | Out-Null } & $darc add-build-to-channel ` --id $buildId ` --publishing-infra-version $PublishingInfraVersion ` --default-channels ` --source-branch main ` --azdev-pat "$AzdoToken" ` --bar-uri "$MaestroApiEndPoint" ` --ci ` --verbose ` @optionalParams if ($LastExitCode -ne 0) { Write-Host "Problems using Darc to promote build ${buildId} to default channels. Stopping execution..." exit 1 } Write-Host 'done.' } catch { Write-Host $_ Write-PipelineTelemetryError -Category 'PromoteBuild' -Message "There was an error while trying to publish build '$BuildId' to default channels." ExitWithExitCode 1 } ================================================ FILE: eng/common/post-build/redact-logs.ps1 ================================================ [CmdletBinding(PositionalBinding=$False)] param( [Parameter(Mandatory=$true, Position=0)][string] $InputPath, [Parameter(Mandatory=$true)][string] $BinlogToolVersion, [Parameter(Mandatory=$false)][string] $DotnetPath, [Parameter(Mandatory=$false)][string] $PackageFeed = 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public/nuget/v3/index.json', # File with strings to redact - separated by newlines. # For comments start the line with '# ' - such lines are ignored [Parameter(Mandatory=$false)][string] $TokensFilePath, [Parameter(ValueFromRemainingArguments=$true)][String[]]$TokensToRedact ) try { $ErrorActionPreference = 'Stop' Set-StrictMode -Version 2.0 # `tools.ps1` checks $ci to perform some actions. Since the post-build # scripts don't necessarily execute in the same agent that run the # build.ps1/sh script this variable isn't automatically set. $ci = $true $disableConfigureToolsetImport = $true . $PSScriptRoot\..\tools.ps1 $packageName = 'binlogtool' $dotnet = $DotnetPath if (!$dotnet) { $dotnetRoot = InitializeDotNetCli -install:$true $dotnet = "$dotnetRoot\dotnet.exe" } $toolList = & "$dotnet" tool list -g if ($toolList -like "*$packageName*") { & "$dotnet" tool uninstall $packageName -g } $toolPath = "$PSScriptRoot\..\..\..\.tools" $verbosity = 'minimal' New-Item -ItemType Directory -Force -Path $toolPath Push-Location -Path $toolPath try { Write-Host "Installing Binlog redactor CLI..." Write-Host "'$dotnet' new tool-manifest" & "$dotnet" new tool-manifest Write-Host "'$dotnet' tool install $packageName --local --add-source '$PackageFeed' -v $verbosity --version $BinlogToolVersion" & "$dotnet" tool install $packageName --local --add-source "$PackageFeed" -v $verbosity --version $BinlogToolVersion if (Test-Path $TokensFilePath) { Write-Host "Adding additional sensitive data for redaction from file: " $TokensFilePath $TokensToRedact += Get-Content -Path $TokensFilePath | Foreach {$_.Trim()} | Where { $_ -notmatch "^# " } } $optionalParams = [System.Collections.ArrayList]::new() Foreach ($p in $TokensToRedact) { if($p -match '^\$\(.*\)$') { Write-Host ("Ignoring token {0} as it is probably unexpanded AzDO variable" -f $p) } elseif($p) { $optionalParams.Add("-p:" + $p) | Out-Null } } & $dotnet binlogtool redact --input:$InputPath --recurse --in-place ` @optionalParams if ($LastExitCode -ne 0) { Write-PipelineTelemetryError -Category 'Redactor' -Type 'warning' -Message "Problems using Redactor tool (exit code: $LastExitCode). But ignoring them now." } } finally { Pop-Location } Write-Host 'done.' } catch { Write-Host $_ Write-PipelineTelemetryError -Category 'Redactor' -Message "There was an error while trying to redact logs. Error: $_" ExitWithExitCode 1 } ================================================ FILE: eng/common/post-build/sourcelink-validation.ps1 ================================================ param( [Parameter(Mandatory=$true)][string] $InputPath, # Full path to directory where Symbols.NuGet packages to be checked are stored [Parameter(Mandatory=$true)][string] $ExtractPath, # Full path to directory where the packages will be extracted during validation [Parameter(Mandatory=$false)][string] $GHRepoName, # GitHub name of the repo including the Org. E.g., dotnet/arcade [Parameter(Mandatory=$false)][string] $GHCommit, # GitHub commit SHA used to build the packages [Parameter(Mandatory=$true)][string] $SourcelinkCliVersion # Version of SourceLink CLI to use ) $ErrorActionPreference = 'Stop' Set-StrictMode -Version 2.0 # `tools.ps1` checks $ci to perform some actions. Since the post-build # scripts don't necessarily execute in the same agent that run the # build.ps1/sh script this variable isn't automatically set. $ci = $true $disableConfigureToolsetImport = $true . $PSScriptRoot\..\tools.ps1 # Cache/HashMap (File -> Exist flag) used to consult whether a file exist # in the repository at a specific commit point. This is populated by inserting # all files present in the repo at a specific commit point. $global:RepoFiles = @{} # Maximum number of jobs to run in parallel $MaxParallelJobs = 16 $MaxRetries = 5 $RetryWaitTimeInSeconds = 30 # Wait time between check for system load $SecondsBetweenLoadChecks = 10 if (!$InputPath -or !(Test-Path $InputPath)){ Write-Host "No files to validate." ExitWithExitCode 0 } $ValidatePackage = { param( [string] $PackagePath # Full path to a Symbols.NuGet package ) . $using:PSScriptRoot\..\tools.ps1 # Ensure input file exist if (!(Test-Path $PackagePath)) { Write-Host "Input file does not exist: $PackagePath" return [pscustomobject]@{ result = 1 packagePath = $PackagePath } } # Extensions for which we'll look for SourceLink information # For now we'll only care about Portable & Embedded PDBs $RelevantExtensions = @('.dll', '.exe', '.pdb') Write-Host -NoNewLine 'Validating ' ([System.IO.Path]::GetFileName($PackagePath)) '...' $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath) $ExtractPath = Join-Path -Path $using:ExtractPath -ChildPath $PackageId $FailedFiles = 0 Add-Type -AssemblyName System.IO.Compression.FileSystem [System.IO.Directory]::CreateDirectory($ExtractPath) | Out-Null try { $zip = [System.IO.Compression.ZipFile]::OpenRead($PackagePath) $zip.Entries | Where-Object {$RelevantExtensions -contains [System.IO.Path]::GetExtension($_.Name)} | ForEach-Object { $FileName = $_.FullName $Extension = [System.IO.Path]::GetExtension($_.Name) $FakeName = -Join((New-Guid), $Extension) $TargetFile = Join-Path -Path $ExtractPath -ChildPath $FakeName # We ignore resource DLLs if ($FileName.EndsWith('.resources.dll')) { return [pscustomobject]@{ result = 0 packagePath = $PackagePath } } [System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, $TargetFile, $true) $ValidateFile = { param( [string] $FullPath, # Full path to the module that has to be checked [string] $RealPath, [ref] $FailedFiles ) $sourcelinkExe = "$env:USERPROFILE\.dotnet\tools" $sourcelinkExe = Resolve-Path "$sourcelinkExe\sourcelink.exe" $SourceLinkInfos = & $sourcelinkExe print-urls $FullPath | Out-String if ($LASTEXITCODE -eq 0 -and -not ([string]::IsNullOrEmpty($SourceLinkInfos))) { $NumFailedLinks = 0 # We only care about Http addresses $Matches = (Select-String '(http[s]?)(:\/\/)([^\s,]+)' -Input $SourceLinkInfos -AllMatches).Matches if ($Matches.Count -ne 0) { $Matches.Value | ForEach-Object { $Link = $_ $CommitUrl = "https://raw.githubusercontent.com/${using:GHRepoName}/${using:GHCommit}/" $FilePath = $Link.Replace($CommitUrl, "") $Status = 200 $Cache = $using:RepoFiles $attempts = 0 while ($attempts -lt $using:MaxRetries) { if ( !($Cache.ContainsKey($FilePath)) ) { try { $Uri = $Link -as [System.URI] if ($Link -match "submodules") { # Skip submodule links until sourcelink properly handles submodules $Status = 200 } elseif ($Uri.AbsoluteURI -ne $null -and ($Uri.Host -match 'github' -or $Uri.Host -match 'githubusercontent')) { # Only GitHub links are valid $Status = (Invoke-WebRequest -Uri $Link -UseBasicParsing -Method HEAD -TimeoutSec 5).StatusCode } else { # If it's not a github link, we want to break out of the loop and not retry. $Status = 0 $attempts = $using:MaxRetries } } catch { Write-Host $_ $Status = 0 } } if ($Status -ne 200) { $attempts++ if ($attempts -lt $using:MaxRetries) { $attemptsLeft = $using:MaxRetries - $attempts Write-Warning "Download failed, $attemptsLeft attempts remaining, will retry in $using:RetryWaitTimeInSeconds seconds" Start-Sleep -Seconds $using:RetryWaitTimeInSeconds } else { if ($NumFailedLinks -eq 0) { if ($FailedFiles.Value -eq 0) { Write-Host } Write-Host "`tFile $RealPath has broken links:" } Write-Host "`t`tFailed to retrieve $Link" $NumFailedLinks++ } } else { break } } } } if ($NumFailedLinks -ne 0) { $FailedFiles.value++ $global:LASTEXITCODE = 1 } } } &$ValidateFile $TargetFile $FileName ([ref]$FailedFiles) } } catch { Write-Host $_ } finally { $zip.Dispose() } if ($FailedFiles -eq 0) { Write-Host 'Passed.' return [pscustomobject]@{ result = 0 packagePath = $PackagePath } } else { Write-PipelineTelemetryError -Category 'SourceLink' -Message "$PackagePath has broken SourceLink links." return [pscustomobject]@{ result = 1 packagePath = $PackagePath } } } function CheckJobResult( $result, $packagePath, [ref]$ValidationFailures, [switch]$logErrors) { if ($result -ne '0') { if ($logErrors) { Write-PipelineTelemetryError -Category 'SourceLink' -Message "$packagePath has broken SourceLink links." } $ValidationFailures.Value++ } } function ValidateSourceLinkLinks { if ($GHRepoName -ne '' -and !($GHRepoName -Match '^[^\s\/]+/[^\s\/]+$')) { if (!($GHRepoName -Match '^[^\s-]+-[^\s]+$')) { Write-PipelineTelemetryError -Category 'SourceLink' -Message "GHRepoName should be in the format / or -. '$GHRepoName'" ExitWithExitCode 1 } else { $GHRepoName = $GHRepoName -replace '^([^\s-]+)-([^\s]+)$', '$1/$2'; } } if ($GHCommit -ne '' -and !($GHCommit -Match '^[0-9a-fA-F]{40}$')) { Write-PipelineTelemetryError -Category 'SourceLink' -Message "GHCommit should be a 40 chars hexadecimal string. '$GHCommit'" ExitWithExitCode 1 } if ($GHRepoName -ne '' -and $GHCommit -ne '') { $RepoTreeURL = -Join('http://api.github.com/repos/', $GHRepoName, '/git/trees/', $GHCommit, '?recursive=1') $CodeExtensions = @('.cs', '.vb', '.fs', '.fsi', '.fsx', '.fsscript') try { # Retrieve the list of files in the repo at that particular commit point and store them in the RepoFiles hash $Data = Invoke-WebRequest $RepoTreeURL -UseBasicParsing | ConvertFrom-Json | Select-Object -ExpandProperty tree foreach ($file in $Data) { $Extension = [System.IO.Path]::GetExtension($file.path) if ($CodeExtensions.Contains($Extension)) { $RepoFiles[$file.path] = 1 } } } catch { Write-Host "Problems downloading the list of files from the repo. Url used: $RepoTreeURL . Execution will proceed without caching." } } elseif ($GHRepoName -ne '' -or $GHCommit -ne '') { Write-Host 'For using the http caching mechanism both GHRepoName and GHCommit should be informed.' } if (Test-Path $ExtractPath) { Remove-Item $ExtractPath -Force -Recurse -ErrorAction SilentlyContinue } $ValidationFailures = 0 # Process each NuGet package in parallel Get-ChildItem "$InputPath\*.symbols.nupkg" | ForEach-Object { Write-Host "Starting $($_.FullName)" Start-Job -ScriptBlock $ValidatePackage -ArgumentList $_.FullName | Out-Null $NumJobs = @(Get-Job -State 'Running').Count while ($NumJobs -ge $MaxParallelJobs) { Write-Host "There are $NumJobs validation jobs running right now. Waiting $SecondsBetweenLoadChecks seconds to check again." sleep $SecondsBetweenLoadChecks $NumJobs = @(Get-Job -State 'Running').Count } foreach ($Job in @(Get-Job -State 'Completed')) { $jobResult = Wait-Job -Id $Job.Id | Receive-Job CheckJobResult $jobResult.result $jobResult.packagePath ([ref]$ValidationFailures) -LogErrors Remove-Job -Id $Job.Id } } foreach ($Job in @(Get-Job)) { $jobResult = Wait-Job -Id $Job.Id | Receive-Job CheckJobResult $jobResult.result $jobResult.packagePath ([ref]$ValidationFailures) Remove-Job -Id $Job.Id } if ($ValidationFailures -gt 0) { Write-PipelineTelemetryError -Category 'SourceLink' -Message "$ValidationFailures package(s) failed validation." ExitWithExitCode 1 } } function InstallSourcelinkCli { $sourcelinkCliPackageName = 'sourcelink' $dotnetRoot = InitializeDotNetCli -install:$true $dotnet = "$dotnetRoot\dotnet.exe" $toolList = & "$dotnet" tool list --global if (($toolList -like "*$sourcelinkCliPackageName*") -and ($toolList -like "*$sourcelinkCliVersion*")) { Write-Host "SourceLink CLI version $sourcelinkCliVersion is already installed." } else { Write-Host "Installing SourceLink CLI version $sourcelinkCliVersion..." Write-Host 'You may need to restart your command window if this is the first dotnet tool you have installed.' & "$dotnet" tool install $sourcelinkCliPackageName --version $sourcelinkCliVersion --verbosity "minimal" --global } } try { InstallSourcelinkCli foreach ($Job in @(Get-Job)) { Remove-Job -Id $Job.Id } ValidateSourceLinkLinks } catch { Write-Host $_.Exception Write-Host $_.ScriptStackTrace Write-PipelineTelemetryError -Category 'SourceLink' -Message $_ ExitWithExitCode 1 } ================================================ FILE: eng/common/post-build/symbols-validation.ps1 ================================================ param( [Parameter(Mandatory = $true)][string] $InputPath, # Full path to directory where NuGet packages to be checked are stored [Parameter(Mandatory = $true)][string] $ExtractPath, # Full path to directory where the packages will be extracted during validation [Parameter(Mandatory = $true)][string] $DotnetSymbolVersion, # Version of dotnet symbol to use [Parameter(Mandatory = $false)][switch] $CheckForWindowsPdbs, # If we should check for the existence of windows pdbs in addition to portable PDBs [Parameter(Mandatory = $false)][switch] $ContinueOnError, # If we should keep checking symbols after an error [Parameter(Mandatory = $false)][switch] $Clean, # Clean extracted symbols directory after checking symbols [Parameter(Mandatory = $false)][string] $SymbolExclusionFile # Exclude the symbols in the file from publishing to symbol server ) . $PSScriptRoot\..\tools.ps1 # Maximum number of jobs to run in parallel $MaxParallelJobs = 16 # Max number of retries $MaxRetry = 5 # Wait time between check for system load $SecondsBetweenLoadChecks = 10 # Set error codes Set-Variable -Name "ERROR_BADEXTRACT" -Option Constant -Value -1 Set-Variable -Name "ERROR_FILEDOESNOTEXIST" -Option Constant -Value -2 $WindowsPdbVerificationParam = "" if ($CheckForWindowsPdbs) { $WindowsPdbVerificationParam = "--windows-pdbs" } $ExclusionSet = New-Object System.Collections.Generic.HashSet[string]; if (!$InputPath -or !(Test-Path $InputPath)){ Write-Host "No symbols to validate." ExitWithExitCode 0 } #Check if the path exists if ($SymbolExclusionFile -and (Test-Path $SymbolExclusionFile)){ [string[]]$Exclusions = Get-Content "$SymbolExclusionFile" $Exclusions | foreach { if($_ -and $_.Trim()){$ExclusionSet.Add($_)} } } else{ Write-Host "Symbol Exclusion file does not exists. No symbols to exclude." } $CountMissingSymbols = { param( [string] $PackagePath, # Path to a NuGet package [string] $WindowsPdbVerificationParam # If we should check for the existence of windows pdbs in addition to portable PDBs ) Add-Type -AssemblyName System.IO.Compression.FileSystem Write-Host "Validating $PackagePath " # Ensure input file exist if (!(Test-Path $PackagePath)) { Write-PipelineTaskError "Input file does not exist: $PackagePath" return [pscustomobject]@{ result = $using:ERROR_FILEDOESNOTEXIST packagePath = $PackagePath } } # Extensions for which we'll look for symbols $RelevantExtensions = @('.dll', '.exe', '.so', '.dylib') # How many files are missing symbol information $MissingSymbols = 0 $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath) $PackageGuid = New-Guid $ExtractPath = Join-Path -Path $using:ExtractPath -ChildPath $PackageGuid $SymbolsPath = Join-Path -Path $ExtractPath -ChildPath 'Symbols' try { [System.IO.Compression.ZipFile]::ExtractToDirectory($PackagePath, $ExtractPath) } catch { Write-Host "Something went wrong extracting $PackagePath" Write-Host $_ return [pscustomobject]@{ result = $using:ERROR_BADEXTRACT packagePath = $PackagePath } } Get-ChildItem -Recurse $ExtractPath | Where-Object { $RelevantExtensions -contains $_.Extension } | ForEach-Object { $FileName = $_.FullName if ($FileName -Match '\\ref\\') { Write-Host "`t Ignoring reference assembly file " $FileName return } $FirstMatchingSymbolDescriptionOrDefault = { param( [string] $FullPath, # Full path to the module that has to be checked [string] $TargetServerParam, # Parameter to pass to `Symbol Tool` indicating the server to lookup for symbols [string] $WindowsPdbVerificationParam, # Parameter to pass to potential check for windows-pdbs. [string] $SymbolsPath ) $FileName = [System.IO.Path]::GetFileName($FullPath) $Extension = [System.IO.Path]::GetExtension($FullPath) # Those below are potential symbol files that the `dotnet symbol` might # return. Which one will be returned depend on the type of file we are # checking and which type of file was uploaded. # The file itself is returned $SymbolPath = $SymbolsPath + '\' + $FileName # PDB file for the module $PdbPath = $SymbolPath.Replace($Extension, '.pdb') # PDB file for R2R module (created by crossgen) $NGenPdb = $SymbolPath.Replace($Extension, '.ni.pdb') # DBG file for a .so library $SODbg = $SymbolPath.Replace($Extension, '.so.dbg') # DWARF file for a .dylib $DylibDwarf = $SymbolPath.Replace($Extension, '.dylib.dwarf') $dotnetSymbolExe = "$env:USERPROFILE\.dotnet\tools" $dotnetSymbolExe = Resolve-Path "$dotnetSymbolExe\dotnet-symbol.exe" $totalRetries = 0 while ($totalRetries -lt $using:MaxRetry) { # Save the output and get diagnostic output $output = & $dotnetSymbolExe --symbols --modules $WindowsPdbVerificationParam $TargetServerParam $FullPath -o $SymbolsPath --diagnostics | Out-String if ((Test-Path $PdbPath) -and (Test-path $SymbolPath)) { return 'Module and PDB for Module' } elseif ((Test-Path $NGenPdb) -and (Test-Path $PdbPath) -and (Test-Path $SymbolPath)) { return 'Dll, PDB and NGen PDB' } elseif ((Test-Path $SODbg) -and (Test-Path $SymbolPath)) { return 'So and DBG for SO' } elseif ((Test-Path $DylibDwarf) -and (Test-Path $SymbolPath)) { return 'Dylib and Dwarf for Dylib' } elseif (Test-Path $SymbolPath) { return 'Module' } else { $totalRetries++ } } return $null } $FileRelativePath = $FileName.Replace("$ExtractPath\", "") if (($($using:ExclusionSet) -ne $null) -and ($($using:ExclusionSet).Contains($FileRelativePath) -or ($($using:ExclusionSet).Contains($FileRelativePath.Replace("\", "/"))))){ Write-Host "Skipping $FileName from symbol validation" } else { $FileGuid = New-Guid $ExpandedSymbolsPath = Join-Path -Path $SymbolsPath -ChildPath $FileGuid $SymbolsOnMSDL = & $FirstMatchingSymbolDescriptionOrDefault ` -FullPath $FileName ` -TargetServerParam '--microsoft-symbol-server' ` -SymbolsPath "$ExpandedSymbolsPath-msdl" ` -WindowsPdbVerificationParam $WindowsPdbVerificationParam $SymbolsOnSymWeb = & $FirstMatchingSymbolDescriptionOrDefault ` -FullPath $FileName ` -TargetServerParam '--internal-server' ` -SymbolsPath "$ExpandedSymbolsPath-symweb" ` -WindowsPdbVerificationParam $WindowsPdbVerificationParam Write-Host -NoNewLine "`t Checking file " $FileName "... " if ($SymbolsOnMSDL -ne $null -and $SymbolsOnSymWeb -ne $null) { Write-Host "Symbols found on MSDL ($SymbolsOnMSDL) and SymWeb ($SymbolsOnSymWeb)" } else { $MissingSymbols++ if ($SymbolsOnMSDL -eq $null -and $SymbolsOnSymWeb -eq $null) { Write-Host 'No symbols found on MSDL or SymWeb!' } else { if ($SymbolsOnMSDL -eq $null) { Write-Host 'No symbols found on MSDL!' } else { Write-Host 'No symbols found on SymWeb!' } } } } } if ($using:Clean) { Remove-Item $ExtractPath -Recurse -Force } Pop-Location return [pscustomobject]@{ result = $MissingSymbols packagePath = $PackagePath } } function CheckJobResult( $result, $packagePath, [ref]$DupedSymbols, [ref]$TotalFailures) { if ($result -eq $ERROR_BADEXTRACT) { Write-PipelineTelemetryError -Category 'CheckSymbols' -Message "$packagePath has duplicated symbol files" $DupedSymbols.Value++ } elseif ($result -eq $ERROR_FILEDOESNOTEXIST) { Write-PipelineTelemetryError -Category 'CheckSymbols' -Message "$packagePath does not exist" $TotalFailures.Value++ } elseif ($result -gt '0') { Write-PipelineTelemetryError -Category 'CheckSymbols' -Message "Missing symbols for $result modules in the package $packagePath" $TotalFailures.Value++ } else { Write-Host "All symbols verified for package $packagePath" } } function CheckSymbolsAvailable { if (Test-Path $ExtractPath) { Remove-Item $ExtractPath -Force -Recurse -ErrorAction SilentlyContinue } $TotalPackages = 0 $TotalFailures = 0 $DupedSymbols = 0 Get-ChildItem "$InputPath\*.nupkg" | ForEach-Object { $FileName = $_.Name $FullName = $_.FullName # These packages from Arcade-Services include some native libraries that # our current symbol uploader can't handle. Below is a workaround until # we get issue: https://github.com/dotnet/arcade/issues/2457 sorted. if ($FileName -Match 'Microsoft\.DotNet\.Darc\.') { Write-Host "Ignoring Arcade-services file: $FileName" Write-Host return } elseif ($FileName -Match 'Microsoft\.DotNet\.Maestro\.Tasks\.') { Write-Host "Ignoring Arcade-services file: $FileName" Write-Host return } $TotalPackages++ Start-Job -ScriptBlock $CountMissingSymbols -ArgumentList @($FullName,$WindowsPdbVerificationParam) | Out-Null $NumJobs = @(Get-Job -State 'Running').Count while ($NumJobs -ge $MaxParallelJobs) { Write-Host "There are $NumJobs validation jobs running right now. Waiting $SecondsBetweenLoadChecks seconds to check again." sleep $SecondsBetweenLoadChecks $NumJobs = @(Get-Job -State 'Running').Count } foreach ($Job in @(Get-Job -State 'Completed')) { $jobResult = Wait-Job -Id $Job.Id | Receive-Job CheckJobResult $jobResult.result $jobResult.packagePath ([ref]$DupedSymbols) ([ref]$TotalFailures) Remove-Job -Id $Job.Id } Write-Host } foreach ($Job in @(Get-Job)) { $jobResult = Wait-Job -Id $Job.Id | Receive-Job CheckJobResult $jobResult.result $jobResult.packagePath ([ref]$DupedSymbols) ([ref]$TotalFailures) } if ($TotalFailures -gt 0 -or $DupedSymbols -gt 0) { if ($TotalFailures -gt 0) { Write-PipelineTelemetryError -Category 'CheckSymbols' -Message "Symbols missing for $TotalFailures/$TotalPackages packages" } if ($DupedSymbols -gt 0) { Write-PipelineTelemetryError -Category 'CheckSymbols' -Message "$DupedSymbols/$TotalPackages packages had duplicated symbol files and could not be extracted" } ExitWithExitCode 1 } else { Write-Host "All symbols validated!" } } function InstallDotnetSymbol { $dotnetSymbolPackageName = 'dotnet-symbol' $dotnetRoot = InitializeDotNetCli -install:$true $dotnet = "$dotnetRoot\dotnet.exe" $toolList = & "$dotnet" tool list --global if (($toolList -like "*$dotnetSymbolPackageName*") -and ($toolList -like "*$dotnetSymbolVersion*")) { Write-Host "dotnet-symbol version $dotnetSymbolVersion is already installed." } else { Write-Host "Installing dotnet-symbol version $dotnetSymbolVersion..." Write-Host 'You may need to restart your command window if this is the first dotnet tool you have installed.' & "$dotnet" tool install $dotnetSymbolPackageName --version $dotnetSymbolVersion --verbosity "minimal" --global } } try { InstallDotnetSymbol foreach ($Job in @(Get-Job)) { Remove-Job -Id $Job.Id } CheckSymbolsAvailable } catch { Write-Host $_.ScriptStackTrace Write-PipelineTelemetryError -Category 'CheckSymbols' -Message $_ ExitWithExitCode 1 } ================================================ FILE: eng/common/retain-build.ps1 ================================================ Param( [Parameter(Mandatory=$true)][int] $buildId, [Parameter(Mandatory=$true)][string] $azdoOrgUri, [Parameter(Mandatory=$true)][string] $azdoProject, [Parameter(Mandatory=$true)][string] $token ) $ErrorActionPreference = 'Stop' Set-StrictMode -Version 2.0 function Get-AzDOHeaders( [string] $token) { $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":${token}")) $headers = @{"Authorization"="Basic $base64AuthInfo"} return $headers } function Update-BuildRetention( [string] $azdoOrgUri, [string] $azdoProject, [int] $buildId, [string] $token) { $headers = Get-AzDOHeaders -token $token $requestBody = "{ `"keepForever`": `"true`" }" $requestUri = "${azdoOrgUri}/${azdoProject}/_apis/build/builds/${buildId}?api-version=6.0" write-Host "Attempting to retain build using the following URI: ${requestUri} ..." try { Invoke-RestMethod -Uri $requestUri -Method Patch -Body $requestBody -Header $headers -contentType "application/json" Write-Host "Updated retention settings for build ${buildId}." } catch { Write-Error "Failed to update retention settings for build: $_.Exception.Response.StatusDescription" exit 1 } } Update-BuildRetention -azdoOrgUri $azdoOrgUri -azdoProject $azdoProject -buildId $buildId -token $token exit 0 ================================================ FILE: eng/common/sdk-task.ps1 ================================================ [CmdletBinding(PositionalBinding=$false)] Param( [string] $configuration = 'Debug', [string] $task, [string] $verbosity = 'minimal', [string] $msbuildEngine = $null, [switch] $restore, [switch] $prepareMachine, [switch] $help, [Parameter(ValueFromRemainingArguments=$true)][String[]]$properties ) $ci = $true $binaryLog = $true $warnAsError = $true . $PSScriptRoot\tools.ps1 function Print-Usage() { Write-Host "Common settings:" Write-Host " -task Name of Arcade task (name of a project in SdkTasks directory of the Arcade SDK package)" Write-Host " -restore Restore dependencies" Write-Host " -verbosity Msbuild verbosity: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic]" Write-Host " -help Print help and exit" Write-Host "" Write-Host "Advanced settings:" Write-Host " -prepareMachine Prepare machine for CI run" Write-Host " -msbuildEngine Msbuild engine to use to run build ('dotnet', 'vs', or unspecified)." Write-Host "" Write-Host "Command line arguments not listed above are passed thru to msbuild." } function Build([string]$target) { $logSuffix = if ($target -eq 'Execute') { '' } else { ".$target" } $log = Join-Path $LogDir "$task$logSuffix.binlog" $outputPath = Join-Path $ToolsetDir "$task\" MSBuild $taskProject ` /bl:$log ` /t:$target ` /p:Configuration=$configuration ` /p:RepoRoot=$RepoRoot ` /p:BaseIntermediateOutputPath=$outputPath ` /v:$verbosity ` @properties } try { if ($help -or (($null -ne $properties) -and ($properties.Contains('/help') -or $properties.Contains('/?')))) { Print-Usage exit 0 } if ($task -eq "") { Write-PipelineTelemetryError -Category 'Build' -Message "Missing required parameter '-task '" Print-Usage ExitWithExitCode 1 } if( $msbuildEngine -eq "vs") { # Ensure desktop MSBuild is available for sdk tasks. if( -not ($GlobalJson.tools.PSObject.Properties.Name -contains "vs" )) { $GlobalJson.tools | Add-Member -Name "vs" -Value (ConvertFrom-Json "{ `"version`": `"16.5`" }") -MemberType NoteProperty } if( -not ($GlobalJson.tools.PSObject.Properties.Name -match "xcopy-msbuild" )) { $GlobalJson.tools | Add-Member -Name "xcopy-msbuild" -Value "17.12.0" -MemberType NoteProperty } if ($GlobalJson.tools."xcopy-msbuild".Trim() -ine "none") { $xcopyMSBuildToolsFolder = InitializeXCopyMSBuild $GlobalJson.tools."xcopy-msbuild" -install $true } if ($xcopyMSBuildToolsFolder -eq $null) { throw 'Unable to get xcopy downloadable version of msbuild' } $global:_MSBuildExe = "$($xcopyMSBuildToolsFolder)\MSBuild\Current\Bin\MSBuild.exe" } $taskProject = GetSdkTaskProject $task if (!(Test-Path $taskProject)) { Write-PipelineTelemetryError -Category 'Build' -Message "Unknown task: $task" ExitWithExitCode 1 } if ($restore) { Build 'Restore' } Build 'Execute' } catch { Write-Host $_.ScriptStackTrace Write-PipelineTelemetryError -Category 'Build' -Message $_ ExitWithExitCode 1 } ExitWithExitCode 0 ================================================ FILE: eng/common/sdl/NuGet.config ================================================  ================================================ FILE: eng/common/sdl/configure-sdl-tool.ps1 ================================================ Param( [string] $GuardianCliLocation, [string] $WorkingDirectory, [string] $TargetDirectory, [string] $GdnFolder, # The list of Guardian tools to configure. For each object in the array: # - If the item is a [hashtable], it must contain these entries: # - Name = The tool name as Guardian knows it. # - Scenario = (Optional) Scenario-specific name for this configuration entry. It must be unique # among all tool entries with the same Name. # - Args = (Optional) Array of Guardian tool configuration args, like '@("Target > C:\temp")' # - If the item is a [string] $v, it is treated as '@{ Name="$v" }' [object[]] $ToolsList, [string] $GuardianLoggerLevel='Standard', # Optional: Additional params to add to any tool using CredScan. [string[]] $CrScanAdditionalRunConfigParams, # Optional: Additional params to add to any tool using PoliCheck. [string[]] $PoliCheckAdditionalRunConfigParams, # Optional: Additional params to add to any tool using CodeQL/Semmle. [string[]] $CodeQLAdditionalRunConfigParams, # Optional: Additional params to add to any tool using Binskim. [string[]] $BinskimAdditionalRunConfigParams ) $ErrorActionPreference = 'Stop' Set-StrictMode -Version 2.0 $disableConfigureToolsetImport = $true $global:LASTEXITCODE = 0 try { # `tools.ps1` checks $ci to perform some actions. Since the SDL # scripts don't necessarily execute in the same agent that run the # build.ps1/sh script this variable isn't automatically set. $ci = $true . $PSScriptRoot\..\tools.ps1 # Normalize tools list: all in [hashtable] form with defined values for each key. $ToolsList = $ToolsList | ForEach-Object { if ($_ -is [string]) { $_ = @{ Name = $_ } } if (-not ($_['Scenario'])) { $_.Scenario = "" } if (-not ($_['Args'])) { $_.Args = @() } $_ } Write-Host "List of tools to configure:" $ToolsList | ForEach-Object { $_ | Out-String | Write-Host } # We store config files in the r directory of .gdn $gdnConfigPath = Join-Path $GdnFolder 'r' $ValidPath = Test-Path $GuardianCliLocation if ($ValidPath -eq $False) { Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Invalid Guardian CLI Location." ExitWithExitCode 1 } foreach ($tool in $ToolsList) { # Put together the name and scenario to make a unique key. $toolConfigName = $tool.Name if ($tool.Scenario) { $toolConfigName += "_" + $tool.Scenario } Write-Host "=== Configuring $toolConfigName..." $gdnConfigFile = Join-Path $gdnConfigPath "$toolConfigName-configure.gdnconfig" # For some tools, add default and automatic args. switch -Exact ($tool.Name) { 'credscan' { if ($targetDirectory) { $tool.Args += "`"TargetDirectory < $TargetDirectory`"" } $tool.Args += "`"OutputType < pre`"" $tool.Args += $CrScanAdditionalRunConfigParams } 'policheck' { if ($targetDirectory) { $tool.Args += "`"Target < $TargetDirectory`"" } $tool.Args += $PoliCheckAdditionalRunConfigParams } {$_ -in 'semmle', 'codeql'} { if ($targetDirectory) { $tool.Args += "`"SourceCodeDirectory < $TargetDirectory`"" } $tool.Args += $CodeQLAdditionalRunConfigParams } 'binskim' { if ($targetDirectory) { # Binskim crashes due to specific PDBs. GitHub issue: https://github.com/microsoft/binskim/issues/924. # We are excluding all `_.pdb` files from the scan. $tool.Args += "`"Target < $TargetDirectory\**;-:file|$TargetDirectory\**\_.pdb`"" } $tool.Args += $BinskimAdditionalRunConfigParams } } # Create variable pointing to the args array directly so we can use splat syntax later. $toolArgs = $tool.Args # Configure the tool. If args array is provided or the current tool has some default arguments # defined, add "--args" and splat each element on the end. Arg format is "{Arg id} < {Value}", # one per parameter. Doc page for "guardian configure": # https://dev.azure.com/securitytools/SecurityIntegration/_wiki/wikis/Guardian/1395/configure Exec-BlockVerbosely { & $GuardianCliLocation configure ` --working-directory $WorkingDirectory ` --tool $tool.Name ` --output-path $gdnConfigFile ` --logger-level $GuardianLoggerLevel ` --noninteractive ` --force ` $(if ($toolArgs) { "--args" }) @toolArgs Exit-IfNZEC "Sdl" } Write-Host "Created '$toolConfigName' configuration file: $gdnConfigFile" } } catch { Write-Host $_.ScriptStackTrace Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ ExitWithExitCode 1 } ================================================ FILE: eng/common/sdl/execute-all-sdl-tools.ps1 ================================================ Param( [string] $GuardianPackageName, # Required: the name of guardian CLI package (not needed if GuardianCliLocation is specified) [string] $NugetPackageDirectory, # Required: directory where NuGet packages are installed (not needed if GuardianCliLocation is specified) [string] $GuardianCliLocation, # Optional: Direct location of Guardian CLI executable if GuardianPackageName & NugetPackageDirectory are not specified [string] $Repository=$env:BUILD_REPOSITORY_NAME, # Required: the name of the repository (e.g. dotnet/arcade) [string] $BranchName=$env:BUILD_SOURCEBRANCH, # Optional: name of branch or version of gdn settings; defaults to master [string] $SourceDirectory=$env:BUILD_SOURCESDIRECTORY, # Required: the directory where source files are located [string] $ArtifactsDirectory = (Join-Path $env:BUILD_ARTIFACTSTAGINGDIRECTORY ('artifacts')), # Required: the directory where build artifacts are located [string] $AzureDevOpsAccessToken, # Required: access token for dnceng; should be provided via KeyVault # Optional: list of SDL tools to run on source code. See 'configure-sdl-tool.ps1' for tools list # format. [object[]] $SourceToolsList, # Optional: list of SDL tools to run on built artifacts. See 'configure-sdl-tool.ps1' for tools # list format. [object[]] $ArtifactToolsList, # Optional: list of SDL tools to run without automatically specifying a target directory. See # 'configure-sdl-tool.ps1' for tools list format. [object[]] $CustomToolsList, [bool] $TsaPublish=$False, # Optional: true will publish results to TSA; only set to true after onboarding to TSA; TSA is the automated framework used to upload test results as bugs. [string] $TsaBranchName=$env:BUILD_SOURCEBRANCH, # Optional: required for TSA publish; defaults to $(Build.SourceBranchName); TSA is the automated framework used to upload test results as bugs. [string] $TsaRepositoryName=$env:BUILD_REPOSITORY_NAME, # Optional: TSA repository name; will be generated automatically if not submitted; TSA is the automated framework used to upload test results as bugs. [string] $BuildNumber=$env:BUILD_BUILDNUMBER, # Optional: required for TSA publish; defaults to $(Build.BuildNumber) [bool] $UpdateBaseline=$False, # Optional: if true, will update the baseline in the repository; should only be run after fixing any issues which need to be fixed [bool] $TsaOnboard=$False, # Optional: if true, will onboard the repository to TSA; should only be run once; TSA is the automated framework used to upload test results as bugs. [string] $TsaInstanceUrl, # Optional: only needed if TsaOnboard or TsaPublish is true; the instance-url registered with TSA; TSA is the automated framework used to upload test results as bugs. [string] $TsaCodebaseName, # Optional: only needed if TsaOnboard or TsaPublish is true; the name of the codebase registered with TSA; TSA is the automated framework used to upload test results as bugs. [string] $TsaProjectName, # Optional: only needed if TsaOnboard or TsaPublish is true; the name of the project registered with TSA; TSA is the automated framework used to upload test results as bugs. [string] $TsaNotificationEmail, # Optional: only needed if TsaOnboard is true; the email(s) which will receive notifications of TSA bug filings (e.g. alias@microsoft.com); TSA is the automated framework used to upload test results as bugs. [string] $TsaCodebaseAdmin, # Optional: only needed if TsaOnboard is true; the aliases which are admins of the TSA codebase (e.g. DOMAIN\alias); TSA is the automated framework used to upload test results as bugs. [string] $TsaBugAreaPath, # Optional: only needed if TsaOnboard is true; the area path where TSA will file bugs in AzDO; TSA is the automated framework used to upload test results as bugs. [string] $TsaIterationPath, # Optional: only needed if TsaOnboard is true; the iteration path where TSA will file bugs in AzDO; TSA is the automated framework used to upload test results as bugs. [string] $GuardianLoggerLevel='Standard', # Optional: the logger level for the Guardian CLI; options are Trace, Verbose, Standard, Warning, and Error [string[]] $CrScanAdditionalRunConfigParams, # Optional: Additional Params to custom build a CredScan run config in the format @("xyz:abc","sdf:1") [string[]] $PoliCheckAdditionalRunConfigParams, # Optional: Additional Params to custom build a Policheck run config in the format @("xyz:abc","sdf:1") [string[]] $CodeQLAdditionalRunConfigParams, # Optional: Additional Params to custom build a Semmle/CodeQL run config in the format @("xyz < abc","sdf < 1") [string[]] $BinskimAdditionalRunConfigParams, # Optional: Additional Params to custom build a Binskim run config in the format @("xyz < abc","sdf < 1") [bool] $BreakOnFailure=$False # Optional: Fail the build if there were errors during the run ) try { $ErrorActionPreference = 'Stop' Set-StrictMode -Version 2.0 $disableConfigureToolsetImport = $true $global:LASTEXITCODE = 0 # `tools.ps1` checks $ci to perform some actions. Since the SDL # scripts don't necessarily execute in the same agent that run the # build.ps1/sh script this variable isn't automatically set. $ci = $true . $PSScriptRoot\..\tools.ps1 #Replace repo names to the format of org/repo if (!($Repository.contains('/'))) { $RepoName = $Repository -replace '(.*?)-(.*)', '$1/$2'; } else{ $RepoName = $Repository; } if ($GuardianPackageName) { $guardianCliLocation = Join-Path $NugetPackageDirectory (Join-Path $GuardianPackageName (Join-Path 'tools' 'guardian.cmd')) } else { $guardianCliLocation = $GuardianCliLocation } $workingDirectory = (Split-Path $SourceDirectory -Parent) $ValidPath = Test-Path $guardianCliLocation if ($ValidPath -eq $False) { Write-PipelineTelemetryError -Force -Category 'Sdl' -Message 'Invalid Guardian CLI Location.' ExitWithExitCode 1 } Exec-BlockVerbosely { & $(Join-Path $PSScriptRoot 'init-sdl.ps1') -GuardianCliLocation $guardianCliLocation -Repository $RepoName -BranchName $BranchName -WorkingDirectory $workingDirectory -AzureDevOpsAccessToken $AzureDevOpsAccessToken -GuardianLoggerLevel $GuardianLoggerLevel } $gdnFolder = Join-Path $workingDirectory '.gdn' if ($TsaOnboard) { if ($TsaCodebaseName -and $TsaNotificationEmail -and $TsaCodebaseAdmin -and $TsaBugAreaPath) { Exec-BlockVerbosely { & $guardianCliLocation tsa-onboard --codebase-name "$TsaCodebaseName" --notification-alias "$TsaNotificationEmail" --codebase-admin "$TsaCodebaseAdmin" --instance-url "$TsaInstanceUrl" --project-name "$TsaProjectName" --area-path "$TsaBugAreaPath" --iteration-path "$TsaIterationPath" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel } if ($LASTEXITCODE -ne 0) { Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Guardian tsa-onboard failed with exit code $LASTEXITCODE." ExitWithExitCode $LASTEXITCODE } } else { Write-PipelineTelemetryError -Force -Category 'Sdl' -Message 'Could not onboard to TSA -- not all required values ($TsaCodebaseName, $TsaNotificationEmail, $TsaCodebaseAdmin, $TsaBugAreaPath) were specified.' ExitWithExitCode 1 } } # Configure a list of tools with a default target directory. Populates the ".gdn/r" directory. function Configure-ToolsList([object[]] $tools, [string] $targetDirectory) { if ($tools -and $tools.Count -gt 0) { Exec-BlockVerbosely { & $(Join-Path $PSScriptRoot 'configure-sdl-tool.ps1') ` -GuardianCliLocation $guardianCliLocation ` -WorkingDirectory $workingDirectory ` -TargetDirectory $targetDirectory ` -GdnFolder $gdnFolder ` -ToolsList $tools ` -AzureDevOpsAccessToken $AzureDevOpsAccessToken ` -GuardianLoggerLevel $GuardianLoggerLevel ` -CrScanAdditionalRunConfigParams $CrScanAdditionalRunConfigParams ` -PoliCheckAdditionalRunConfigParams $PoliCheckAdditionalRunConfigParams ` -CodeQLAdditionalRunConfigParams $CodeQLAdditionalRunConfigParams ` -BinskimAdditionalRunConfigParams $BinskimAdditionalRunConfigParams if ($BreakOnFailure) { Exit-IfNZEC "Sdl" } } } } # Configure Artifact and Source tools with default Target directories. Configure-ToolsList $ArtifactToolsList $ArtifactsDirectory Configure-ToolsList $SourceToolsList $SourceDirectory # Configure custom tools with no default Target directory. Configure-ToolsList $CustomToolsList $null # At this point, all tools are configured in the ".gdn" directory. Run them all in a single call. # (If we used "run" multiple times, each run would overwrite data from earlier runs.) Exec-BlockVerbosely { & $(Join-Path $PSScriptRoot 'run-sdl.ps1') ` -GuardianCliLocation $guardianCliLocation ` -WorkingDirectory $SourceDirectory ` -UpdateBaseline $UpdateBaseline ` -GdnFolder $gdnFolder } if ($TsaPublish) { if ($TsaBranchName -and $BuildNumber) { if (-not $TsaRepositoryName) { $TsaRepositoryName = "$($Repository)-$($BranchName)" } Exec-BlockVerbosely { & $guardianCliLocation tsa-publish --all-tools --repository-name "$TsaRepositoryName" --branch-name "$TsaBranchName" --build-number "$BuildNumber" --onboard $True --codebase-name "$TsaCodebaseName" --notification-alias "$TsaNotificationEmail" --codebase-admin "$TsaCodebaseAdmin" --instance-url "$TsaInstanceUrl" --project-name "$TsaProjectName" --area-path "$TsaBugAreaPath" --iteration-path "$TsaIterationPath" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel } if ($LASTEXITCODE -ne 0) { Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Guardian tsa-publish failed with exit code $LASTEXITCODE." ExitWithExitCode $LASTEXITCODE } } else { Write-PipelineTelemetryError -Force -Category 'Sdl' -Message 'Could not publish to TSA -- not all required values ($TsaBranchName, $BuildNumber) were specified.' ExitWithExitCode 1 } } if ($BreakOnFailure) { Write-Host "Failing the build in case of breaking results..." Exec-BlockVerbosely { & $guardianCliLocation break --working-directory $workingDirectory --logger-level $GuardianLoggerLevel } } else { Write-Host "Letting the build pass even if there were breaking results..." } } catch { Write-Host $_.ScriptStackTrace Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ exit 1 } ================================================ FILE: eng/common/sdl/extract-artifact-archives.ps1 ================================================ # This script looks for each archive file in a directory and extracts it into the target directory. # For example, the file "$InputPath/bin.tar.gz" extracts to "$ExtractPath/bin.tar.gz.extracted/**". # Uses the "tar" utility added to Windows 10 / Windows 2019 that supports tar.gz and zip. param( # Full path to directory where archives are stored. [Parameter(Mandatory=$true)][string] $InputPath, # Full path to directory to extract archives into. May be the same as $InputPath. [Parameter(Mandatory=$true)][string] $ExtractPath ) $ErrorActionPreference = 'Stop' Set-StrictMode -Version 2.0 $disableConfigureToolsetImport = $true try { # `tools.ps1` checks $ci to perform some actions. Since the SDL # scripts don't necessarily execute in the same agent that run the # build.ps1/sh script this variable isn't automatically set. $ci = $true . $PSScriptRoot\..\tools.ps1 Measure-Command { $jobs = @() # Find archive files for non-Windows and Windows builds. $archiveFiles = @( Get-ChildItem (Join-Path $InputPath "*.tar.gz") Get-ChildItem (Join-Path $InputPath "*.zip") ) foreach ($targzFile in $archiveFiles) { $jobs += Start-Job -ScriptBlock { $file = $using:targzFile $fileName = [System.IO.Path]::GetFileName($file) $extractDir = Join-Path $using:ExtractPath "$fileName.extracted" New-Item $extractDir -ItemType Directory -Force | Out-Null Write-Host "Extracting '$file' to '$extractDir'..." # Pipe errors to stdout to prevent PowerShell detecting them and quitting the job early. # This type of quit skips the catch, so we wouldn't be able to tell which file triggered the # error. Save output so it can be stored in the exception string along with context. $output = tar -xf $file -C $extractDir 2>&1 # Handle NZEC manually rather than using Exit-IfNZEC: we are in a background job, so we # don't have access to the outer scope. if ($LASTEXITCODE -ne 0) { throw "Error extracting '$file': non-zero exit code ($LASTEXITCODE). Output: '$output'" } Write-Host "Extracted to $extractDir" } } Receive-Job $jobs -Wait } } catch { Write-Host $_ Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ ExitWithExitCode 1 } ================================================ FILE: eng/common/sdl/extract-artifact-packages.ps1 ================================================ param( [Parameter(Mandatory=$true)][string] $InputPath, # Full path to directory where artifact packages are stored [Parameter(Mandatory=$true)][string] $ExtractPath # Full path to directory where the packages will be extracted ) $ErrorActionPreference = 'Stop' Set-StrictMode -Version 2.0 $disableConfigureToolsetImport = $true function ExtractArtifacts { if (!(Test-Path $InputPath)) { Write-Host "Input Path does not exist: $InputPath" ExitWithExitCode 0 } $Jobs = @() Get-ChildItem "$InputPath\*.nupkg" | ForEach-Object { $Jobs += Start-Job -ScriptBlock $ExtractPackage -ArgumentList $_.FullName } foreach ($Job in $Jobs) { Wait-Job -Id $Job.Id | Receive-Job } } try { # `tools.ps1` checks $ci to perform some actions. Since the SDL # scripts don't necessarily execute in the same agent that run the # build.ps1/sh script this variable isn't automatically set. $ci = $true . $PSScriptRoot\..\tools.ps1 $ExtractPackage = { param( [string] $PackagePath # Full path to a NuGet package ) if (!(Test-Path $PackagePath)) { Write-PipelineTelemetryError -Category 'Build' -Message "Input file does not exist: $PackagePath" ExitWithExitCode 1 } $RelevantExtensions = @('.dll', '.exe', '.pdb') Write-Host -NoNewLine 'Extracting ' ([System.IO.Path]::GetFileName($PackagePath)) '...' $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath) $ExtractPath = Join-Path -Path $using:ExtractPath -ChildPath $PackageId Add-Type -AssemblyName System.IO.Compression.FileSystem [System.IO.Directory]::CreateDirectory($ExtractPath); try { $zip = [System.IO.Compression.ZipFile]::OpenRead($PackagePath) $zip.Entries | Where-Object {$RelevantExtensions -contains [System.IO.Path]::GetExtension($_.Name)} | ForEach-Object { $TargetPath = Join-Path -Path $ExtractPath -ChildPath (Split-Path -Path $_.FullName) [System.IO.Directory]::CreateDirectory($TargetPath); $TargetFile = Join-Path -Path $ExtractPath -ChildPath $_.FullName [System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, $TargetFile) } } catch { Write-Host $_ Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ ExitWithExitCode 1 } finally { $zip.Dispose() } } Measure-Command { ExtractArtifacts } } catch { Write-Host $_ Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ ExitWithExitCode 1 } ================================================ FILE: eng/common/sdl/init-sdl.ps1 ================================================ Param( [string] $GuardianCliLocation, [string] $Repository, [string] $BranchName='master', [string] $WorkingDirectory, [string] $AzureDevOpsAccessToken, [string] $GuardianLoggerLevel='Standard' ) $ErrorActionPreference = 'Stop' Set-StrictMode -Version 2.0 $disableConfigureToolsetImport = $true $global:LASTEXITCODE = 0 # `tools.ps1` checks $ci to perform some actions. Since the SDL # scripts don't necessarily execute in the same agent that run the # build.ps1/sh script this variable isn't automatically set. $ci = $true . $PSScriptRoot\..\tools.ps1 # Don't display the console progress UI - it's a huge perf hit $ProgressPreference = 'SilentlyContinue' # Construct basic auth from AzDO access token; construct URI to the repository's gdn folder stored in that repository; construct location of zip file $encodedPat = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$AzureDevOpsAccessToken")) $escapedRepository = [Uri]::EscapeDataString("/$Repository/$BranchName/.gdn") $uri = "https://dev.azure.com/dnceng/internal/_apis/git/repositories/sdl-tool-cfg/Items?path=$escapedRepository&versionDescriptor[versionOptions]=0&`$format=zip&api-version=5.0" $zipFile = "$WorkingDirectory/gdn.zip" Add-Type -AssemblyName System.IO.Compression.FileSystem $gdnFolder = (Join-Path $WorkingDirectory '.gdn') try { # if the folder does not exist, we'll do a guardian init and push it to the remote repository Write-Host 'Initializing Guardian...' Write-Host "$GuardianCliLocation init --working-directory $WorkingDirectory --logger-level $GuardianLoggerLevel" & $GuardianCliLocation init --working-directory $WorkingDirectory --logger-level $GuardianLoggerLevel if ($LASTEXITCODE -ne 0) { Write-PipelineTelemetryError -Force -Category 'Build' -Message "Guardian init failed with exit code $LASTEXITCODE." ExitWithExitCode $LASTEXITCODE } # We create the mainbaseline so it can be edited later Write-Host "$GuardianCliLocation baseline --working-directory $WorkingDirectory --name mainbaseline" & $GuardianCliLocation baseline --working-directory $WorkingDirectory --name mainbaseline if ($LASTEXITCODE -ne 0) { Write-PipelineTelemetryError -Force -Category 'Build' -Message "Guardian baseline failed with exit code $LASTEXITCODE." ExitWithExitCode $LASTEXITCODE } ExitWithExitCode 0 } catch { Write-Host $_.ScriptStackTrace Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ ExitWithExitCode 1 } ================================================ FILE: eng/common/sdl/packages.config ================================================ ================================================ FILE: eng/common/sdl/run-sdl.ps1 ================================================ Param( [string] $GuardianCliLocation, [string] $WorkingDirectory, [string] $GdnFolder, [string] $UpdateBaseline, [string] $GuardianLoggerLevel='Standard' ) $ErrorActionPreference = 'Stop' Set-StrictMode -Version 2.0 $disableConfigureToolsetImport = $true $global:LASTEXITCODE = 0 try { # `tools.ps1` checks $ci to perform some actions. Since the SDL # scripts don't necessarily execute in the same agent that run the # build.ps1/sh script this variable isn't automatically set. $ci = $true . $PSScriptRoot\..\tools.ps1 # We store config files in the r directory of .gdn $gdnConfigPath = Join-Path $GdnFolder 'r' $ValidPath = Test-Path $GuardianCliLocation if ($ValidPath -eq $False) { Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Invalid Guardian CLI Location." ExitWithExitCode 1 } $gdnConfigFiles = Get-ChildItem $gdnConfigPath -Recurse -Include '*.gdnconfig' Write-Host "Discovered Guardian config files:" $gdnConfigFiles | Out-String | Write-Host Exec-BlockVerbosely { & $GuardianCliLocation run ` --working-directory $WorkingDirectory ` --baseline mainbaseline ` --update-baseline $UpdateBaseline ` --logger-level $GuardianLoggerLevel ` --config @gdnConfigFiles Exit-IfNZEC "Sdl" } } catch { Write-Host $_.ScriptStackTrace Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ ExitWithExitCode 1 } ================================================ FILE: eng/common/sdl/sdl.ps1 ================================================ function Install-Gdn { param( [Parameter(Mandatory=$true)] [string]$Path, # If omitted, install the latest version of Guardian, otherwise install that specific version. [string]$Version ) $ErrorActionPreference = 'Stop' Set-StrictMode -Version 2.0 $disableConfigureToolsetImport = $true $global:LASTEXITCODE = 0 # `tools.ps1` checks $ci to perform some actions. Since the SDL # scripts don't necessarily execute in the same agent that run the # build.ps1/sh script this variable isn't automatically set. $ci = $true . $PSScriptRoot\..\tools.ps1 $argumentList = @("install", "Microsoft.Guardian.Cli", "-Source https://securitytools.pkgs.visualstudio.com/_packaging/Guardian/nuget/v3/index.json", "-OutputDirectory $Path", "-NonInteractive", "-NoCache") if ($Version) { $argumentList += "-Version $Version" } Start-Process nuget -Verbose -ArgumentList $argumentList -NoNewWindow -Wait $gdnCliPath = Get-ChildItem -Filter guardian.cmd -Recurse -Path $Path if (!$gdnCliPath) { Write-PipelineTelemetryError -Category 'Sdl' -Message 'Failure installing Guardian' } return $gdnCliPath.FullName } ================================================ FILE: eng/common/sdl/trim-assets-version.ps1 ================================================ <# .SYNOPSIS Install and run the 'Microsoft.DotNet.VersionTools.Cli' tool with the 'trim-artifacts-version' command to trim the version from the NuGet assets file name. .PARAMETER InputPath Full path to directory where artifact packages are stored .PARAMETER Recursive Search for NuGet packages recursively #> Param( [string] $InputPath, [bool] $Recursive = $true ) $CliToolName = "Microsoft.DotNet.VersionTools.Cli" function Install-VersionTools-Cli { param( [Parameter(Mandatory=$true)][string]$Version ) Write-Host "Installing the package '$CliToolName' with a version of '$version' ..." $feed = "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" $argumentList = @("tool", "install", "--local", "$CliToolName", "--add-source $feed", "--no-cache", "--version $Version", "--create-manifest-if-needed") Start-Process "$dotnet" -Verbose -ArgumentList $argumentList -NoNewWindow -Wait } # ------------------------------------------------------------------- if (!(Test-Path $InputPath)) { Write-Host "Input Path '$InputPath' does not exist" ExitWithExitCode 1 } $ErrorActionPreference = 'Stop' Set-StrictMode -Version 2.0 $disableConfigureToolsetImport = $true $global:LASTEXITCODE = 0 # `tools.ps1` checks $ci to perform some actions. Since the SDL # scripts don't necessarily execute in the same agent that run the # build.ps1/sh script this variable isn't automatically set. $ci = $true . $PSScriptRoot\..\tools.ps1 try { $dotnetRoot = InitializeDotNetCli -install:$true $dotnet = "$dotnetRoot\dotnet.exe" $toolsetVersion = Read-ArcadeSdkVersion Install-VersionTools-Cli -Version $toolsetVersion $cliToolFound = (& "$dotnet" tool list --local | Where-Object {$_.Split(' ')[0] -eq $CliToolName}) if ($null -eq $cliToolFound) { Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "The '$CliToolName' tool is not installed." ExitWithExitCode 1 } Exec-BlockVerbosely { & "$dotnet" $CliToolName trim-assets-version ` --assets-path $InputPath ` --recursive $Recursive Exit-IfNZEC "Sdl" } } catch { Write-Host $_ Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ ExitWithExitCode 1 } ================================================ FILE: eng/common/template-guidance.md ================================================ # Overview Arcade provides templates for public (`/templates`) and 1ES pipeline templates (`/templates-official`) scenarios. Pipelines which are required to be managed by 1ES pipeline templates should reference `/templates-offical`, all other pipelines may reference `/templates`. ## How to use Basic guidance is: - 1ES Pipeline Template or 1ES Microbuild template runs should reference `eng/common/templates-official`. Any internal production-graded pipeline should use these templates. - All other runs should reference `eng/common/templates`. See [azure-pipelines.yml](../../azure-pipelines.yml) (templates-official example) or [azure-pipelines-pr.yml](../../azure-pipelines-pr.yml) (templates example) for examples. #### The `templateIs1ESManaged` parameter The `templateIs1ESManaged` is available on most templates and affects which of the variants is used for nested templates. See [Development Notes](#development-notes) below for more information on the `templateIs1ESManaged1 parameter. - For templates under `job/`, `jobs/`, `steps`, or `post-build/`, this parameter must be explicitly set. ## Multiple outputs 1ES pipeline templates impose a policy where every publish artifact execution results in additional security scans being injected into your pipeline. When using `templates-official/jobs/jobs.yml`, Arcade reduces the number of additional security injections by gathering all publishing outputs into the [Build.ArtifactStagingDirectory](https://learn.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml#build-variables-devops-services), and utilizing the [outputParentDirectory](https://eng.ms/docs/cloud-ai-platform/devdiv/one-engineering-system-1es/1es-docs/1es-pipeline-templates/features/outputs#multiple-outputs) feature of 1ES pipeline templates. When implementing your pipeline, if you ensure publish artifacts are located in the `$(Build.ArtifactStagingDirectory)`, and utilize the 1ES provided template context, then you can reduce the number of security scans for your pipeline. Example: ``` yaml # azure-pipelines.yml extends: template: azure-pipelines/MicroBuild.1ES.Official.yml@MicroBuildTemplate parameters: stages: - stage: build jobs: - template: /eng/common/templates-official/jobs/jobs.yml@self parameters: # 1ES makes use of outputs to reduce security task injection overhead templateContext: outputs: - output: pipelineArtifact displayName: 'Publish logs from source' continueOnError: true condition: always() targetPath: $(Build.ArtifactStagingDirectory)/artifacts/log artifactName: Logs jobs: - job: Windows steps: - script: echo "friendly neighborhood" > artifacts/marvel/spiderman.txt # copy build outputs to artifact staging directory for publishing - task: CopyFiles@2 displayName: Gather build output inputs: SourceFolder: '$(Build.SourcesDirectory)/artifacts/marvel' Contents: '**' TargetFolder: '$(Build.ArtifactStagingDirectory)/artifacts/marvel' ``` Note: Multiple outputs are ONLY applicable to 1ES PT publishing (only usable when referencing `templates-official`). ## Development notes **Folder / file structure** ``` text eng\common\ [templates || templates-official]\ job\ job.yml (shim + artifact publishing logic) onelocbuild.yml (shim) publish-build-assets.yml (shim) source-build.yml (shim) source-index-stage1.yml (shim) jobs\ codeql-build.yml (shim) jobs.yml (shim) source-build.yml (shim) post-build\ post-build.yml (shim) common-variabls.yml (shim) setup-maestro-vars.yml (shim) steps\ publish-build-artifacts.yml (logic) publish-pipeline-artifacts.yml (logic) component-governance.yml (shim) generate-sbom.yml (shim) publish-logs.yml (shim) retain-build.yml (shim) send-to-helix.yml (shim) source-build.yml (shim) variables\ pool-providers.yml (logic + redirect) # templates/variables/pool-providers.yml will redirect to templates-official/variables/pool-providers.yml if you are running in the internal project sdl-variables.yml (logic) core-templates\ job\ job.yml (logic) onelocbuild.yml (logic) publish-build-assets.yml (logic) source-build.yml (logic) source-index-stage1.yml (logic) jobs\ codeql-build.yml (logic) jobs.yml (logic) source-build.yml (logic) post-build\ common-variabls.yml (logic) post-build.yml (logic) setup-maestro-vars.yml (logic) steps\ component-governance.yml (logic) generate-sbom.yml (logic) publish-build-artifacts.yml (redirect) publish-logs.yml (logic) publish-pipeline-artifacts.yml (redirect) retain-build.yml (logic) send-to-helix.yml (logic) source-build.yml (logic) variables\ pool-providers.yml (redirect) ``` In the table above, a file is designated as "shim", "logic", or "redirect". - shim - represents a yaml file which is an intermediate step between pipeline logic and .Net Core Engineering's templates (`core-templates`) and defines the `is1ESPipeline` parameter value. - logic - represents actual base template logic. - redirect- represents a file in `core-templates` which redirects to the "logic" file in either `templates` or `templates-official`. Logic for Arcade's templates live **primarily** in the `core-templates` folder. The exceptions to the location of the logic files are around artifact publishing, which is handled differently between 1es pipeline templates and standard templates. `templates` and `templates-official` provide shim entry points which redirect to `core-templates` while also defining the `is1ESPipeline` parameter. If a shim is referenced in `templates`, then `is1ESPipeline` is set to `false`. If a shim is referenced in `templates-official`, then `is1ESPipeline` is set to `true`. Within `templates` and `templates-official`, the templates at the "stages", and "jobs" / "job" level have been replaced with shims. Templates at the "steps" and "variables" level are typically too granular to be replaced with shims and instead persist logic which is directly applicable to either scenario. Within `core-templates`, there are a handful of places where logic is dependent on which shim entry point was used. In those places, we redirect back to the respective logic file in `templates` or `templates-official`. ================================================ FILE: eng/common/templates/job/job.yml ================================================ parameters: enablePublishBuildArtifacts: false disableComponentGovernance: '' componentGovernanceIgnoreDirectories: '' # Sbom related params enableSbom: true runAsPublic: false PackageVersion: 9.0.0 BuildDropPath: '$(Build.SourcesDirectory)/artifacts' jobs: - template: /eng/common/core-templates/job/job.yml parameters: is1ESPipeline: false ${{ each parameter in parameters }}: ${{ if and(ne(parameter.key, 'steps'), ne(parameter.key, 'is1ESPipeline')) }}: ${{ parameter.key }}: ${{ parameter.value }} steps: - ${{ each step in parameters.steps }}: - ${{ step }} componentGovernanceSteps: - template: /eng/common/templates/steps/component-governance.yml parameters: ${{ if eq(parameters.disableComponentGovernance, '') }}: ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest'), eq(parameters.runAsPublic, 'false'), or(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), startsWith(variables['Build.SourceBranch'], 'refs/heads/dotnet/'), startsWith(variables['Build.SourceBranch'], 'refs/heads/microsoft/'), eq(variables['Build.SourceBranch'], 'refs/heads/main'))) }}: disableComponentGovernance: false ${{ else }}: disableComponentGovernance: true ${{ else }}: disableComponentGovernance: ${{ parameters.disableComponentGovernance }} componentGovernanceIgnoreDirectories: ${{ parameters.componentGovernanceIgnoreDirectories }} artifactPublishSteps: - ${{ if ne(parameters.artifacts.publish, '') }}: - ${{ if and(ne(parameters.artifacts.publish.artifacts, 'false'), ne(parameters.artifacts.publish.artifacts, '')) }}: - template: /eng/common/core-templates/steps/publish-build-artifacts.yml parameters: is1ESPipeline: false args: displayName: Publish pipeline artifacts pathToPublish: '$(Build.ArtifactStagingDirectory)/artifacts' publishLocation: Container artifactName: ${{ coalesce(parameters.artifacts.publish.artifacts.name , 'Artifacts_$(Agent.Os)_$(_BuildConfig)') }} continueOnError: true condition: always() - ${{ if and(ne(parameters.artifacts.publish.logs, 'false'), ne(parameters.artifacts.publish.logs, '')) }}: - template: /eng/common/core-templates/steps/publish-pipeline-artifacts.yml parameters: is1ESPipeline: false args: targetPath: '$(Build.ArtifactStagingDirectory)/artifacts/log' artifactName: ${{ coalesce(parameters.artifacts.publish.logs.name, 'Logs_Build_$(Agent.Os)_$(_BuildConfig)') }} displayName: 'Publish logs' continueOnError: true condition: always() sbomEnabled: false # we don't need SBOM for logs - ${{ if ne(parameters.enablePublishBuildArtifacts, 'false') }}: - template: /eng/common/core-templates/steps/publish-build-artifacts.yml parameters: is1ESPipeline: false args: displayName: Publish Logs pathToPublish: '$(Build.ArtifactStagingDirectory)/artifacts/log/$(_BuildConfig)' publishLocation: Container artifactName: ${{ coalesce(parameters.enablePublishBuildArtifacts.artifactName, '$(Agent.Os)_$(Agent.JobName)' ) }} continueOnError: true condition: always() - ${{ if eq(parameters.enableBuildRetry, 'true') }}: - template: /eng/common/core-templates/steps/publish-pipeline-artifacts.yml parameters: is1ESPipeline: false args: targetPath: '$(Build.SourcesDirectory)\eng\common\BuildConfiguration' artifactName: 'BuildConfiguration' displayName: 'Publish build retry configuration' continueOnError: true sbomEnabled: false # we don't need SBOM for BuildConfiguration ================================================ FILE: eng/common/templates/job/onelocbuild.yml ================================================ jobs: - template: /eng/common/core-templates/job/onelocbuild.yml parameters: is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/job/publish-build-assets.yml ================================================ jobs: - template: /eng/common/core-templates/job/publish-build-assets.yml parameters: is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/job/source-build.yml ================================================ jobs: - template: /eng/common/core-templates/job/source-build.yml parameters: is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/job/source-index-stage1.yml ================================================ jobs: - template: /eng/common/core-templates/job/source-index-stage1.yml parameters: is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/jobs/codeql-build.yml ================================================ jobs: - template: /eng/common/core-templates/jobs/codeql-build.yml parameters: is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/jobs/jobs.yml ================================================ jobs: - template: /eng/common/core-templates/jobs/jobs.yml parameters: is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/jobs/source-build.yml ================================================ jobs: - template: /eng/common/core-templates/jobs/source-build.yml parameters: is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/post-build/common-variables.yml ================================================ variables: - template: /eng/common/core-templates/post-build/common-variables.yml parameters: # Specifies whether to use 1ES is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/post-build/post-build.yml ================================================ stages: - template: /eng/common/core-templates/post-build/post-build.yml parameters: # Specifies whether to use 1ES is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/post-build/setup-maestro-vars.yml ================================================ steps: - template: /eng/common/core-templates/post-build/setup-maestro-vars.yml parameters: # Specifies whether to use 1ES is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/steps/component-governance.yml ================================================ steps: - template: /eng/common/core-templates/steps/component-governance.yml parameters: is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/steps/enable-internal-runtimes.yml ================================================ # Obtains internal runtime download credentials and populates the 'dotnetbuilds-internal-container-read-token-base64' # variable with the base64-encoded SAS token, by default steps: - template: /eng/common/core-templates/steps/enable-internal-runtimes.yml parameters: is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/steps/enable-internal-sources.yml ================================================ steps: - template: /eng/common/core-templates/steps/enable-internal-sources.yml parameters: is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/steps/generate-sbom.yml ================================================ steps: - template: /eng/common/core-templates/steps/generate-sbom.yml parameters: is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/steps/get-delegation-sas.yml ================================================ steps: - template: /eng/common/core-templates/steps/get-delegation-sas.yml parameters: is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/steps/get-federated-access-token.yml ================================================ steps: - template: /eng/common/core-templates/steps/get-federated-access-token.yml parameters: is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/steps/publish-build-artifacts.yml ================================================ parameters: - name: is1ESPipeline type: boolean default: false - name: displayName type: string default: 'Publish to Build Artifact' - name: condition type: string default: succeeded() - name: artifactName type: string - name: pathToPublish type: string - name: continueOnError type: boolean default: false - name: publishLocation type: string default: 'Container' steps: - ${{ if eq(parameters.is1ESPipeline, true) }}: - 'eng/common/templates cannot be referenced from a 1ES managed template': error - task: PublishBuildArtifacts@1 displayName: ${{ parameters.displayName }} condition: ${{ parameters.condition }} ${{ if parameters.continueOnError }}: continueOnError: ${{ parameters.continueOnError }} inputs: PublishLocation: ${{ parameters.publishLocation }} PathtoPublish: ${{ parameters.pathToPublish }} ${{ if parameters.artifactName }}: ArtifactName: ${{ parameters.artifactName }} ================================================ FILE: eng/common/templates/steps/publish-logs.yml ================================================ steps: - template: /eng/common/core-templates/steps/publish-logs.yml parameters: is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/steps/publish-pipeline-artifacts.yml ================================================ parameters: - name: is1ESPipeline type: boolean default: false - name: args type: object default: {} steps: - ${{ if eq(parameters.is1ESPipeline, true) }}: - 'eng/common/templates cannot be referenced from a 1ES managed template': error - task: PublishPipelineArtifact@1 displayName: ${{ coalesce(parameters.args.displayName, 'Publish to Build Artifact') }} ${{ if parameters.args.condition }}: condition: ${{ parameters.args.condition }} ${{ else }}: condition: succeeded() ${{ if parameters.args.continueOnError }}: continueOnError: ${{ parameters.args.continueOnError }} inputs: targetPath: ${{ parameters.args.targetPath }} ${{ if parameters.args.artifactName }}: artifactName: ${{ parameters.args.artifactName }} ${{ if parameters.args.publishLocation }}: publishLocation: ${{ parameters.args.publishLocation }} ${{ if parameters.args.fileSharePath }}: fileSharePath: ${{ parameters.args.fileSharePath }} ${{ if parameters.args.Parallel }}: parallel: ${{ parameters.args.Parallel }} ${{ if parameters.args.parallelCount }}: parallelCount: ${{ parameters.args.parallelCount }} ${{ if parameters.args.properties }}: properties: ${{ parameters.args.properties }} ================================================ FILE: eng/common/templates/steps/retain-build.yml ================================================ steps: - template: /eng/common/core-templates/steps/retain-build.yml parameters: is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/steps/send-to-helix.yml ================================================ steps: - template: /eng/common/core-templates/steps/send-to-helix.yml parameters: is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/steps/source-build.yml ================================================ steps: - template: /eng/common/core-templates/steps/source-build.yml parameters: is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/steps/source-index-stage1-publish.yml ================================================ steps: - template: /eng/common/core-templates/steps/source-index-stage1-publish.yml parameters: is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/variables/pool-providers.yml ================================================ # Select a pool provider based off branch name. Anything with branch name containing 'release' must go into an -Svc pool, # otherwise it should go into the "normal" pools. This separates out the queueing and billing of released branches. # Motivation: # Once a given branch of a repository's output has been officially "shipped" once, it is then considered to be COGS # (Cost of goods sold) and should be moved to a servicing pool provider. This allows both separation of queueing # (allowing release builds and main PR builds to not intefere with each other) and billing (required for COGS. # Additionally, the pool provider name itself may be subject to change when the .NET Core Engineering Services # team needs to move resources around and create new and potentially differently-named pools. Using this template # file from an Arcade-ified repo helps guard against both having to update one's release/* branches and renaming. # How to use: # This yaml assumes your shipped product branches use the naming convention "release/..." (which many do). # If we find alternate naming conventions in broad usage it can be added to the condition below. # # First, import the template in an arcade-ified repo to pick up the variables, e.g.: # # variables: # - template: /eng/common/templates/variables/pool-providers.yml # # ... then anywhere specifying the pool provider use the runtime variables, # $(DncEngInternalBuildPool) and $ (DncEngPublicBuildPool), e.g.: # # pool: # name: $(DncEngInternalBuildPool) # demands: ImageOverride -equals windows.vs2019.amd64 variables: - ${{ if eq(variables['System.TeamProject'], 'internal') }}: - template: /eng/common/templates-official/variables/pool-providers.yml - ${{ else }}: # Coalesce the target and source branches so we know when a PR targets a release branch # If these variables are somehow missing, fall back to main (tends to have more capacity) # Any new -Svc alternative pools should have variables added here to allow for splitting work - name: DncEngPublicBuildPool value: $[ replace( replace( eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), True, 'NetCore-Svc-Public' ), False, 'NetCore-Public' ) ] - name: DncEngInternalBuildPool value: $[ replace( replace( eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), True, 'NetCore1ESPool-Svc-Internal' ), False, 'NetCore1ESPool-Internal' ) ] ================================================ FILE: eng/common/templates-official/job/job.yml ================================================ parameters: # Sbom related params enableSbom: true runAsPublic: false PackageVersion: 9.0.0 BuildDropPath: '$(Build.SourcesDirectory)/artifacts' jobs: - template: /eng/common/core-templates/job/job.yml parameters: is1ESPipeline: true componentGovernanceSteps: - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest'), eq(parameters.enableSbom, 'true')) }}: - template: /eng/common/templates/steps/generate-sbom.yml parameters: PackageVersion: ${{ parameters.packageVersion }} BuildDropPath: ${{ parameters.buildDropPath }} publishArtifacts: false # publish artifacts # for 1ES managed templates, use the templateContext.output to handle multiple outputs. templateContext: outputParentDirectory: $(Build.ArtifactStagingDirectory) outputs: - ${{ if ne(parameters.artifacts.publish, '') }}: - ${{ if and(ne(parameters.artifacts.publish.artifacts, 'false'), ne(parameters.artifacts.publish.artifacts, '')) }}: - output: buildArtifacts displayName: Publish pipeline artifacts PathtoPublish: '$(Build.ArtifactStagingDirectory)/artifacts' ArtifactName: ${{ coalesce(parameters.artifacts.publish.artifacts.name , 'Artifacts_$(Agent.Os)_$(_BuildConfig)') }} condition: always() continueOnError: true - ${{ if and(ne(parameters.artifacts.publish.logs, 'false'), ne(parameters.artifacts.publish.logs, '')) }}: - output: pipelineArtifact targetPath: '$(Build.ArtifactStagingDirectory)/artifacts/log' artifactName: ${{ coalesce(parameters.artifacts.publish.logs.name, 'Logs_Build_$(Agent.Os)_$(_BuildConfig)_Attempt$(System.JobAttempt)') }} displayName: 'Publish logs' continueOnError: true condition: always() sbomEnabled: false # we don't need SBOM for logs - ${{ if eq(parameters.enablePublishBuildArtifacts, true) }}: - output: buildArtifacts displayName: Publish Logs PathtoPublish: '$(Build.ArtifactStagingDirectory)/artifacts/log/$(_BuildConfig)' publishLocation: Container ArtifactName: ${{ coalesce(parameters.enablePublishBuildArtifacts.artifactName, '$(Agent.Os)_$(Agent.JobName)' ) }} continueOnError: true condition: always() sbomEnabled: false # we don't need SBOM for logs - ${{ if eq(parameters.enableBuildRetry, 'true') }}: - output: pipelineArtifact targetPath: '$(Build.ArtifactStagingDirectory)/artifacts/eng/common/BuildConfiguration' artifactName: 'BuildConfiguration' displayName: 'Publish build retry configuration' continueOnError: true sbomEnabled: false # we don't need SBOM for BuildConfiguration - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest'), eq(parameters.enableSbom, 'true')) }}: - output: pipelineArtifact displayName: Publish SBOM manifest continueOnError: true targetPath: $(Build.ArtifactStagingDirectory)/sbom artifactName: $(ARTIFACT_NAME) # add any outputs provided via root yaml - ${{ if ne(parameters.templateContext.outputs, '') }}: - ${{ each output in parameters.templateContext.outputs }}: - ${{ output }} # add any remaining templateContext properties ${{ each context in parameters.templateContext }}: ${{ if and(ne(context.key, 'outputParentDirectory'), ne(context.key, 'outputs')) }}: ${{ context.key }}: ${{ context.value }} ${{ each parameter in parameters }}: ${{ if and(ne(parameter.key, 'templateContext'), ne(parameter.key, 'is1ESPipeline')) }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/job/onelocbuild.yml ================================================ jobs: - template: /eng/common/core-templates/job/onelocbuild.yml parameters: is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/job/publish-build-assets.yml ================================================ jobs: - template: /eng/common/core-templates/job/publish-build-assets.yml parameters: is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/job/source-build.yml ================================================ jobs: - template: /eng/common/core-templates/job/source-build.yml parameters: is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/job/source-index-stage1.yml ================================================ jobs: - template: /eng/common/core-templates/job/source-index-stage1.yml parameters: is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/jobs/codeql-build.yml ================================================ jobs: - template: /eng/common/core-templates/jobs/codeql-build.yml parameters: is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/jobs/jobs.yml ================================================ jobs: - template: /eng/common/core-templates/jobs/jobs.yml parameters: is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/jobs/source-build.yml ================================================ jobs: - template: /eng/common/core-templates/jobs/source-build.yml parameters: is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/post-build/common-variables.yml ================================================ variables: - template: /eng/common/core-templates/post-build/common-variables.yml parameters: # Specifies whether to use 1ES is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/post-build/post-build.yml ================================================ stages: - template: /eng/common/core-templates/post-build/post-build.yml parameters: # Specifies whether to use 1ES is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/post-build/setup-maestro-vars.yml ================================================ steps: - template: /eng/common/core-templates/post-build/setup-maestro-vars.yml parameters: # Specifies whether to use 1ES is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/steps/component-governance.yml ================================================ steps: - template: /eng/common/core-templates/steps/component-governance.yml parameters: is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/steps/enable-internal-runtimes.yml ================================================ # Obtains internal runtime download credentials and populates the 'dotnetbuilds-internal-container-read-token-base64' # variable with the base64-encoded SAS token, by default steps: - template: /eng/common/core-templates/steps/enable-internal-runtimes.yml parameters: is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/steps/enable-internal-sources.yml ================================================ steps: - template: /eng/common/core-templates/steps/enable-internal-sources.yml parameters: is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/steps/generate-sbom.yml ================================================ steps: - template: /eng/common/core-templates/steps/generate-sbom.yml parameters: is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/steps/get-delegation-sas.yml ================================================ steps: - template: /eng/common/core-templates/steps/get-delegation-sas.yml parameters: is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/steps/get-federated-access-token.yml ================================================ steps: - template: /eng/common/core-templates/steps/get-federated-access-token.yml parameters: is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/steps/publish-build-artifacts.yml ================================================ parameters: - name: displayName type: string default: 'Publish to Build Artifact' - name: condition type: string default: succeeded() - name: artifactName type: string - name: pathToPublish type: string - name: continueOnError type: boolean default: false - name: publishLocation type: string default: 'Container' - name: is1ESPipeline type: boolean default: true steps: - ${{ if ne(parameters.is1ESPipeline, true) }}: - 'eng/common/templates-official cannot be referenced from a non-1ES managed template': error - task: 1ES.PublishBuildArtifacts@1 displayName: ${{ parameters.displayName }} condition: ${{ parameters.condition }} ${{ if parameters.continueOnError }}: continueOnError: ${{ parameters.continueOnError }} inputs: PublishLocation: ${{ parameters.publishLocation }} PathtoPublish: ${{ parameters.pathToPublish }} ${{ if parameters.artifactName }}: ArtifactName: ${{ parameters.artifactName }} ================================================ FILE: eng/common/templates-official/steps/publish-logs.yml ================================================ steps: - template: /eng/common/core-templates/steps/publish-logs.yml parameters: is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/steps/publish-pipeline-artifacts.yml ================================================ parameters: - name: is1ESPipeline type: boolean default: true - name: args type: object default: {} steps: - ${{ if ne(parameters.is1ESPipeline, true) }}: - 'eng/common/templates-official cannot be referenced from a non-1ES managed template': error - task: 1ES.PublishPipelineArtifact@1 displayName: ${{ coalesce(parameters.args.displayName, 'Publish to Build Artifact') }} ${{ if parameters.args.condition }}: condition: ${{ parameters.args.condition }} ${{ else }}: condition: succeeded() ${{ if parameters.args.continueOnError }}: continueOnError: ${{ parameters.args.continueOnError }} inputs: targetPath: ${{ parameters.args.targetPath }} ${{ if parameters.args.artifactName }}: artifactName: ${{ parameters.args.artifactName }} ${{ if parameters.args.properties }}: properties: ${{ parameters.args.properties }} ${{ if parameters.args.sbomEnabled }}: sbomEnabled: ${{ parameters.args.sbomEnabled }} ================================================ FILE: eng/common/templates-official/steps/retain-build.yml ================================================ steps: - template: /eng/common/core-templates/steps/retain-build.yml parameters: is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/steps/send-to-helix.yml ================================================ steps: - template: /eng/common/core-templates/steps/send-to-helix.yml parameters: is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/steps/source-build.yml ================================================ steps: - template: /eng/common/core-templates/steps/source-build.yml parameters: is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/steps/source-index-stage1-publish.yml ================================================ steps: - template: /eng/common/core-templates/steps/source-index-stage1-publish.yml parameters: is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/variables/pool-providers.yml ================================================ # Select a pool provider based off branch name. Anything with branch name containing 'release' must go into an -Svc pool, # otherwise it should go into the "normal" pools. This separates out the queueing and billing of released branches. # Motivation: # Once a given branch of a repository's output has been officially "shipped" once, it is then considered to be COGS # (Cost of goods sold) and should be moved to a servicing pool provider. This allows both separation of queueing # (allowing release builds and main PR builds to not intefere with each other) and billing (required for COGS. # Additionally, the pool provider name itself may be subject to change when the .NET Core Engineering Services # team needs to move resources around and create new and potentially differently-named pools. Using this template # file from an Arcade-ified repo helps guard against both having to update one's release/* branches and renaming. # How to use: # This yaml assumes your shipped product branches use the naming convention "release/..." (which many do). # If we find alternate naming conventions in broad usage it can be added to the condition below. # # First, import the template in an arcade-ified repo to pick up the variables, e.g.: # # variables: # - template: /eng/common/templates-official/variables/pool-providers.yml # # ... then anywhere specifying the pool provider use the runtime variables, # $(DncEngInternalBuildPool) # # pool: # name: $(DncEngInternalBuildPool) # image: 1es-windows-2022 variables: # Coalesce the target and source branches so we know when a PR targets a release branch # If these variables are somehow missing, fall back to main (tends to have more capacity) # Any new -Svc alternative pools should have variables added here to allow for splitting work - name: DncEngInternalBuildPool value: $[ replace( replace( eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), True, 'NetCore1ESPool-Svc-Internal' ), False, 'NetCore1ESPool-Internal' ) ] ================================================ FILE: eng/common/templates-official/variables/sdl-variables.yml ================================================ variables: # The Guardian version specified in 'eng/common/sdl/packages.config'. This value must be kept in # sync with the packages.config file. - name: DefaultGuardianVersion value: 0.109.0 - name: GuardianPackagesConfigFile value: $(Build.SourcesDirectory)\eng\common\sdl\packages.config ================================================ FILE: eng/common/tools.ps1 ================================================ # Initialize variables if they aren't already defined. # These may be defined as parameters of the importing script, or set after importing this script. # CI mode - set to true on CI server for PR validation build or official build. [bool]$ci = if (Test-Path variable:ci) { $ci } else { $false } # Build configuration. Common values include 'Debug' and 'Release', but the repository may use other names. [string]$configuration = if (Test-Path variable:configuration) { $configuration } else { 'Debug' } # Set to true to opt out of outputting binary log while running in CI [bool]$excludeCIBinarylog = if (Test-Path variable:excludeCIBinarylog) { $excludeCIBinarylog } else { $false } # Set to true to output binary log from msbuild. Note that emitting binary log slows down the build. [bool]$binaryLog = if (Test-Path variable:binaryLog) { $binaryLog } else { $ci -and !$excludeCIBinarylog } # Set to true to use the pipelines logger which will enable Azure logging output. # https://github.com/Microsoft/azure-pipelines-tasks/blob/master/docs/authoring/commands.md # This flag is meant as a temporary opt-opt for the feature while validate it across # our consumers. It will be deleted in the future. [bool]$pipelinesLog = if (Test-Path variable:pipelinesLog) { $pipelinesLog } else { $ci } # Turns on machine preparation/clean up code that changes the machine state (e.g. kills build processes). [bool]$prepareMachine = if (Test-Path variable:prepareMachine) { $prepareMachine } else { $false } # True to restore toolsets and dependencies. [bool]$restore = if (Test-Path variable:restore) { $restore } else { $true } # Adjusts msbuild verbosity level. [string]$verbosity = if (Test-Path variable:verbosity) { $verbosity } else { 'minimal' } # Set to true to reuse msbuild nodes. Recommended to not reuse on CI. [bool]$nodeReuse = if (Test-Path variable:nodeReuse) { $nodeReuse } else { !$ci } # Configures warning treatment in msbuild. [bool]$warnAsError = if (Test-Path variable:warnAsError) { $warnAsError } else { $true } # Specifies which msbuild engine to use for build: 'vs', 'dotnet' or unspecified (determined based on presence of tools.vs in global.json). [string]$msbuildEngine = if (Test-Path variable:msbuildEngine) { $msbuildEngine } else { $null } # True to attempt using .NET Core already that meets requirements specified in global.json # installed on the machine instead of downloading one. [bool]$useInstalledDotNetCli = if (Test-Path variable:useInstalledDotNetCli) { $useInstalledDotNetCli } else { $true } # Enable repos to use a particular version of the on-line dotnet-install scripts. # default URL: https://dotnet.microsoft.com/download/dotnet/scripts/v1/dotnet-install.ps1 [string]$dotnetInstallScriptVersion = if (Test-Path variable:dotnetInstallScriptVersion) { $dotnetInstallScriptVersion } else { 'v1' } # True to use global NuGet cache instead of restoring packages to repository-local directory. [bool]$useGlobalNuGetCache = if (Test-Path variable:useGlobalNuGetCache) { $useGlobalNuGetCache } else { !$ci } # True to exclude prerelease versions Visual Studio during build [bool]$excludePrereleaseVS = if (Test-Path variable:excludePrereleaseVS) { $excludePrereleaseVS } else { $false } # An array of names of processes to stop on script exit if prepareMachine is true. $processesToStopOnExit = if (Test-Path variable:processesToStopOnExit) { $processesToStopOnExit } else { @('msbuild', 'dotnet', 'vbcscompiler') } $disableConfigureToolsetImport = if (Test-Path variable:disableConfigureToolsetImport) { $disableConfigureToolsetImport } else { $null } set-strictmode -version 2.0 $ErrorActionPreference = 'Stop' [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 # If specifies, provides an alternate path for getting .NET Core SDKs and Runtimes. This script will still try public sources first. [string]$runtimeSourceFeed = if (Test-Path variable:runtimeSourceFeed) { $runtimeSourceFeed } else { $null } # Base-64 encoded SAS token that has permission to storage container described by $runtimeSourceFeed [string]$runtimeSourceFeedKey = if (Test-Path variable:runtimeSourceFeedKey) { $runtimeSourceFeedKey } else { $null } # True if the build is a product build [bool]$productBuild = if (Test-Path variable:productBuild) { $productBuild } else { $false } [String[]]$properties = if (Test-Path variable:properties) { $properties } else { @() } function Create-Directory ([string[]] $path) { New-Item -Path $path -Force -ItemType 'Directory' | Out-Null } function Unzip([string]$zipfile, [string]$outpath) { Add-Type -AssemblyName System.IO.Compression.FileSystem [System.IO.Compression.ZipFile]::ExtractToDirectory($zipfile, $outpath) } # This will exec a process using the console and return it's exit code. # This will not throw when the process fails. # Returns process exit code. function Exec-Process([string]$command, [string]$commandArgs) { $startInfo = New-Object System.Diagnostics.ProcessStartInfo $startInfo.FileName = $command $startInfo.Arguments = $commandArgs $startInfo.UseShellExecute = $false $startInfo.WorkingDirectory = Get-Location $process = New-Object System.Diagnostics.Process $process.StartInfo = $startInfo $process.Start() | Out-Null $finished = $false try { while (-not $process.WaitForExit(100)) { # Non-blocking loop done to allow ctr-c interrupts } $finished = $true return $global:LASTEXITCODE = $process.ExitCode } finally { # If we didn't finish then an error occurred or the user hit ctrl-c. Either # way kill the process if (-not $finished) { $process.Kill() } } } # Take the given block, print it, print what the block probably references from the current set of # variables using low-effort string matching, then run the block. # # This is intended to replace the pattern of manually copy-pasting a command, wrapping it in quotes, # and printing it using "Write-Host". The copy-paste method is more readable in build logs, but less # maintainable and less reliable. It is easy to make a mistake and modify the command without # properly updating the "Write-Host" line, resulting in misleading build logs. The probability of # this mistake makes the pattern hard to trust when it shows up in build logs. Finding the bug in # existing source code can also be difficult, because the strings are not aligned to each other and # the line may be 300+ columns long. # # By removing the need to maintain two copies of the command, Exec-BlockVerbosely avoids the issues. # # In Bash (or any posix-like shell), "set -x" prints usable verbose output automatically. # "Set-PSDebug" appears to be similar at first glance, but unfortunately, it isn't very useful: it # doesn't print any info about the variables being used by the command, which is normally the # interesting part to diagnose. function Exec-BlockVerbosely([scriptblock] $block) { Write-Host "--- Running script block:" $blockString = $block.ToString().Trim() Write-Host $blockString Write-Host "--- List of variables that might be used:" # For each variable x in the environment, check the block for a reference to x via simple "$x" or # "@x" syntax. This doesn't detect other ways to reference variables ("${x}" nor "$variable:x", # among others). It only catches what this function was originally written for: simple # command-line commands. $variableTable = Get-Variable | Where-Object { $blockString.Contains("`$$($_.Name)") -or $blockString.Contains("@$($_.Name)") } | Format-Table -AutoSize -HideTableHeaders -Wrap | Out-String Write-Host $variableTable.Trim() Write-Host "--- Executing:" & $block Write-Host "--- Done running script block!" } # createSdkLocationFile parameter enables a file being generated under the toolset directory # which writes the sdk's location into. This is only necessary for cmd --> powershell invocations # as dot sourcing isn't possible. function InitializeDotNetCli([bool]$install, [bool]$createSdkLocationFile) { if (Test-Path variable:global:_DotNetInstallDir) { return $global:_DotNetInstallDir } # Don't resolve runtime, shared framework, or SDK from other locations to ensure build determinism $env:DOTNET_MULTILEVEL_LOOKUP=0 # Disable first run since we do not need all ASP.NET packages restored. $env:DOTNET_NOLOGO=1 # Disable telemetry on CI. if ($ci) { $env:DOTNET_CLI_TELEMETRY_OPTOUT=1 } # Find the first path on %PATH% that contains the dotnet.exe if ($useInstalledDotNetCli -and (-not $globalJsonHasRuntimes) -and ($env:DOTNET_INSTALL_DIR -eq $null)) { $dotnetExecutable = GetExecutableFileName 'dotnet' $dotnetCmd = Get-Command $dotnetExecutable -ErrorAction SilentlyContinue if ($dotnetCmd -ne $null) { $env:DOTNET_INSTALL_DIR = Split-Path $dotnetCmd.Path -Parent } } $dotnetSdkVersion = $GlobalJson.tools.dotnet # Use dotnet installation specified in DOTNET_INSTALL_DIR if it contains the required SDK version, # otherwise install the dotnet CLI and SDK to repo local .dotnet directory to avoid potential permission issues. if ((-not $globalJsonHasRuntimes) -and (-not [string]::IsNullOrEmpty($env:DOTNET_INSTALL_DIR)) -and (Test-Path(Join-Path $env:DOTNET_INSTALL_DIR "sdk\$dotnetSdkVersion"))) { $dotnetRoot = $env:DOTNET_INSTALL_DIR } else { $dotnetRoot = Join-Path $RepoRoot '.dotnet' if (-not (Test-Path(Join-Path $dotnetRoot "sdk\$dotnetSdkVersion"))) { if ($install) { InstallDotNetSdk $dotnetRoot $dotnetSdkVersion } else { Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Unable to find dotnet with SDK version '$dotnetSdkVersion'" ExitWithExitCode 1 } } $env:DOTNET_INSTALL_DIR = $dotnetRoot } # Creates a temporary file under the toolset dir. # The following code block is protecting against concurrent access so that this function can # be called in parallel. if ($createSdkLocationFile) { do { $sdkCacheFileTemp = Join-Path $ToolsetDir $([System.IO.Path]::GetRandomFileName()) } until (!(Test-Path $sdkCacheFileTemp)) Set-Content -Path $sdkCacheFileTemp -Value $dotnetRoot try { Move-Item -Force $sdkCacheFileTemp (Join-Path $ToolsetDir 'sdk.txt') } catch { # Somebody beat us Remove-Item -Path $sdkCacheFileTemp } } # Add dotnet to PATH. This prevents any bare invocation of dotnet in custom # build steps from using anything other than what we've downloaded. # It also ensures that VS msbuild will use the downloaded sdk targets. $env:PATH = "$dotnetRoot;$env:PATH" # Make Sure that our bootstrapped dotnet cli is available in future steps of the Azure Pipelines build Write-PipelinePrependPath -Path $dotnetRoot Write-PipelineSetVariable -Name 'DOTNET_MULTILEVEL_LOOKUP' -Value '0' Write-PipelineSetVariable -Name 'DOTNET_NOLOGO' -Value '1' return $global:_DotNetInstallDir = $dotnetRoot } function Retry($downloadBlock, $maxRetries = 5) { $retries = 1 while($true) { try { & $downloadBlock break } catch { Write-PipelineTelemetryError -Category 'InitializeToolset' -Message $_ } if (++$retries -le $maxRetries) { $delayInSeconds = [math]::Pow(2, $retries) - 1 # Exponential backoff Write-Host "Retrying. Waiting for $delayInSeconds seconds before next attempt ($retries of $maxRetries)." Start-Sleep -Seconds $delayInSeconds } else { Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Unable to download file in $maxRetries attempts." break } } } function GetDotNetInstallScript([string] $dotnetRoot) { $installScript = Join-Path $dotnetRoot 'dotnet-install.ps1' if (!(Test-Path $installScript)) { Create-Directory $dotnetRoot $ProgressPreference = 'SilentlyContinue' # Don't display the console progress UI - it's a huge perf hit $uri = "https://dotnet.microsoft.com/download/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.ps1" Retry({ Write-Host "GET $uri" Invoke-WebRequest $uri -OutFile $installScript }) } return $installScript } function InstallDotNetSdk([string] $dotnetRoot, [string] $version, [string] $architecture = '', [switch] $noPath) { InstallDotNet $dotnetRoot $version $architecture '' $false $runtimeSourceFeed $runtimeSourceFeedKey -noPath:$noPath } function InstallDotNet([string] $dotnetRoot, [string] $version, [string] $architecture = '', [string] $runtime = '', [bool] $skipNonVersionedFiles = $false, [string] $runtimeSourceFeed = '', [string] $runtimeSourceFeedKey = '', [switch] $noPath) { $dotnetVersionLabel = "'sdk v$version'" if ($runtime -ne '' -and $runtime -ne 'sdk') { $runtimePath = $dotnetRoot $runtimePath = $runtimePath + "\shared" if ($runtime -eq "dotnet") { $runtimePath = $runtimePath + "\Microsoft.NETCore.App" } if ($runtime -eq "aspnetcore") { $runtimePath = $runtimePath + "\Microsoft.AspNetCore.App" } if ($runtime -eq "windowsdesktop") { $runtimePath = $runtimePath + "\Microsoft.WindowsDesktop.App" } $runtimePath = $runtimePath + "\" + $version $dotnetVersionLabel = "runtime toolset '$runtime/$architecture v$version'" if (Test-Path $runtimePath) { Write-Host " Runtime toolset '$runtime/$architecture v$version' already installed." $installSuccess = $true Exit } } $installScript = GetDotNetInstallScript $dotnetRoot $installParameters = @{ Version = $version InstallDir = $dotnetRoot } if ($architecture) { $installParameters.Architecture = $architecture } if ($runtime) { $installParameters.Runtime = $runtime } if ($skipNonVersionedFiles) { $installParameters.SkipNonVersionedFiles = $skipNonVersionedFiles } if ($noPath) { $installParameters.NoPath = $True } $variations = @() $variations += @($installParameters) $dotnetBuilds = $installParameters.Clone() $dotnetbuilds.AzureFeed = "https://ci.dot.net/public" $variations += @($dotnetBuilds) if ($runtimeSourceFeed) { $runtimeSource = $installParameters.Clone() $runtimeSource.AzureFeed = $runtimeSourceFeed if ($runtimeSourceFeedKey) { $decodedBytes = [System.Convert]::FromBase64String($runtimeSourceFeedKey) $decodedString = [System.Text.Encoding]::UTF8.GetString($decodedBytes) $runtimeSource.FeedCredential = $decodedString } $variations += @($runtimeSource) } $installSuccess = $false foreach ($variation in $variations) { if ($variation | Get-Member AzureFeed) { $location = $variation.AzureFeed } else { $location = "public location"; } Write-Host " Attempting to install $dotnetVersionLabel from $location." try { & $installScript @variation $installSuccess = $true break } catch { Write-Host " Failed to install $dotnetVersionLabel from $location." } } if (-not $installSuccess) { Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Failed to install $dotnetVersionLabel from any of the specified locations." ExitWithExitCode 1 } } # # Locates Visual Studio MSBuild installation. # The preference order for MSBuild to use is as follows: # # 1. MSBuild from an active VS command prompt # 2. MSBuild from a compatible VS installation # 3. MSBuild from the xcopy tool package # # Returns full path to msbuild.exe. # Throws on failure. # function InitializeVisualStudioMSBuild([bool]$install, [object]$vsRequirements = $null) { if (-not (IsWindowsPlatform)) { throw "Cannot initialize Visual Studio on non-Windows" } if (Test-Path variable:global:_MSBuildExe) { return $global:_MSBuildExe } # Minimum VS version to require. $vsMinVersionReqdStr = '17.7' $vsMinVersionReqd = [Version]::new($vsMinVersionReqdStr) # If the version of msbuild is going to be xcopied, # use this version. Version matches a package here: # https://dev.azure.com/dnceng/public/_artifacts/feed/dotnet-eng/NuGet/Microsoft.DotNet.Arcade.MSBuild.Xcopy/versions/17.12.0 $defaultXCopyMSBuildVersion = '17.12.0' if (!$vsRequirements) { if (Get-Member -InputObject $GlobalJson.tools -Name 'vs') { $vsRequirements = $GlobalJson.tools.vs } else { $vsRequirements = New-Object PSObject -Property @{ version = $vsMinVersionReqdStr } } } $vsMinVersionStr = if ($vsRequirements.version) { $vsRequirements.version } else { $vsMinVersionReqdStr } $vsMinVersion = [Version]::new($vsMinVersionStr) # Try msbuild command available in the environment. if ($env:VSINSTALLDIR -ne $null) { $msbuildCmd = Get-Command 'msbuild.exe' -ErrorAction SilentlyContinue if ($msbuildCmd -ne $null) { # Workaround for https://github.com/dotnet/roslyn/issues/35793 # Due to this issue $msbuildCmd.Version returns 0.0.0.0 for msbuild.exe 16.2+ $msbuildVersion = [Version]::new((Get-Item $msbuildCmd.Path).VersionInfo.ProductVersion.Split([char[]]@('-', '+'))[0]) if ($msbuildVersion -ge $vsMinVersion) { return $global:_MSBuildExe = $msbuildCmd.Path } # Report error - the developer environment is initialized with incompatible VS version. throw "Developer Command Prompt for VS $($env:VisualStudioVersion) is not recent enough. Please upgrade to $vsMinVersionStr or build from a plain CMD window" } } # Locate Visual Studio installation or download x-copy msbuild. $vsInfo = LocateVisualStudio $vsRequirements if ($vsInfo -ne $null) { # Ensure vsInstallDir has a trailing slash $vsInstallDir = Join-Path $vsInfo.installationPath "\" $vsMajorVersion = $vsInfo.installationVersion.Split('.')[0] InitializeVisualStudioEnvironmentVariables $vsInstallDir $vsMajorVersion } else { if (Get-Member -InputObject $GlobalJson.tools -Name 'xcopy-msbuild') { $xcopyMSBuildVersion = $GlobalJson.tools.'xcopy-msbuild' $vsMajorVersion = $xcopyMSBuildVersion.Split('.')[0] } else { #if vs version provided in global.json is incompatible (too low) then use the default version for xcopy msbuild download if($vsMinVersion -lt $vsMinVersionReqd){ Write-Host "Using xcopy-msbuild version of $defaultXCopyMSBuildVersion since VS version $vsMinVersionStr provided in global.json is not compatible" $xcopyMSBuildVersion = $defaultXCopyMSBuildVersion $vsMajorVersion = $xcopyMSBuildVersion.Split('.')[0] } else{ # If the VS version IS compatible, look for an xcopy msbuild package # with a version matching VS. # Note: If this version does not exist, then an explicit version of xcopy msbuild # can be specified in global.json. This will be required for pre-release versions of msbuild. $vsMajorVersion = $vsMinVersion.Major $vsMinorVersion = $vsMinVersion.Minor $xcopyMSBuildVersion = "$vsMajorVersion.$vsMinorVersion.0" } } $vsInstallDir = $null if ($xcopyMSBuildVersion.Trim() -ine "none") { $vsInstallDir = InitializeXCopyMSBuild $xcopyMSBuildVersion $install if ($vsInstallDir -eq $null) { throw "Could not xcopy msbuild. Please check that package 'Microsoft.DotNet.Arcade.MSBuild.Xcopy @ $xcopyMSBuildVersion' exists on feed 'dotnet-eng'." } } if ($vsInstallDir -eq $null) { throw 'Unable to find Visual Studio that has required version and components installed' } } $msbuildVersionDir = if ([int]$vsMajorVersion -lt 16) { "$vsMajorVersion.0" } else { "Current" } $local:BinFolder = Join-Path $vsInstallDir "MSBuild\$msbuildVersionDir\Bin" $local:Prefer64bit = if (Get-Member -InputObject $vsRequirements -Name 'Prefer64bit') { $vsRequirements.Prefer64bit } else { $false } if ($local:Prefer64bit -and (Test-Path(Join-Path $local:BinFolder "amd64"))) { $global:_MSBuildExe = Join-Path $local:BinFolder "amd64\msbuild.exe" } else { $global:_MSBuildExe = Join-Path $local:BinFolder "msbuild.exe" } return $global:_MSBuildExe } function InitializeVisualStudioEnvironmentVariables([string] $vsInstallDir, [string] $vsMajorVersion) { $env:VSINSTALLDIR = $vsInstallDir Set-Item "env:VS$($vsMajorVersion)0COMNTOOLS" (Join-Path $vsInstallDir "Common7\Tools\") $vsSdkInstallDir = Join-Path $vsInstallDir "VSSDK\" if (Test-Path $vsSdkInstallDir) { Set-Item "env:VSSDK$($vsMajorVersion)0Install" $vsSdkInstallDir $env:VSSDKInstall = $vsSdkInstallDir } } function InstallXCopyMSBuild([string]$packageVersion) { return InitializeXCopyMSBuild $packageVersion -install $true } function InitializeXCopyMSBuild([string]$packageVersion, [bool]$install) { $packageName = 'Microsoft.DotNet.Arcade.MSBuild.Xcopy' $packageDir = Join-Path $ToolsDir "msbuild\$packageVersion" $packagePath = Join-Path $packageDir "$packageName.$packageVersion.nupkg" if (!(Test-Path $packageDir)) { if (!$install) { return $null } Create-Directory $packageDir Write-Host "Downloading $packageName $packageVersion" $ProgressPreference = 'SilentlyContinue' # Don't display the console progress UI - it's a huge perf hit Retry({ Invoke-WebRequest "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/flat2/$packageName/$packageVersion/$packageName.$packageVersion.nupkg" -OutFile $packagePath }) if (!(Test-Path $packagePath)) { Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "See https://dev.azure.com/dnceng/internal/_wiki/wikis/DNCEng%20Services%20Wiki/1074/Updating-Microsoft.DotNet.Arcade.MSBuild.Xcopy-WAS-RoslynTools.MSBuild-(xcopy-msbuild)-generation?anchor=troubleshooting for help troubleshooting issues with XCopy MSBuild" throw } Unzip $packagePath $packageDir } return Join-Path $packageDir 'tools' } # # Locates Visual Studio instance that meets the minimal requirements specified by tools.vs object in global.json. # # The following properties of tools.vs are recognized: # "version": "{major}.{minor}" # Two part minimal VS version, e.g. "15.9", "16.0", etc. # "components": ["componentId1", "componentId2", ...] # Array of ids of workload components that must be available in the VS instance. # See e.g. https://docs.microsoft.com/en-us/visualstudio/install/workload-component-id-vs-enterprise?view=vs-2017 # # Returns JSON describing the located VS instance (same format as returned by vswhere), # or $null if no instance meeting the requirements is found on the machine. # function LocateVisualStudio([object]$vsRequirements = $null){ if (-not (IsWindowsPlatform)) { throw "Cannot run vswhere on non-Windows platforms." } if (Get-Member -InputObject $GlobalJson.tools -Name 'vswhere') { $vswhereVersion = $GlobalJson.tools.vswhere } else { $vswhereVersion = '2.5.2' } $vsWhereDir = Join-Path $ToolsDir "vswhere\$vswhereVersion" $vsWhereExe = Join-Path $vsWhereDir 'vswhere.exe' if (!(Test-Path $vsWhereExe)) { Create-Directory $vsWhereDir Write-Host 'Downloading vswhere' Retry({ Invoke-WebRequest "https://netcorenativeassets.blob.core.windows.net/resource-packages/external/windows/vswhere/$vswhereVersion/vswhere.exe" -OutFile $vswhereExe }) } if (!$vsRequirements) { $vsRequirements = $GlobalJson.tools.vs } $args = @('-latest', '-format', 'json', '-requires', 'Microsoft.Component.MSBuild', '-products', '*') if (!$excludePrereleaseVS) { $args += '-prerelease' } if (Get-Member -InputObject $vsRequirements -Name 'version') { $args += '-version' $args += $vsRequirements.version } if (Get-Member -InputObject $vsRequirements -Name 'components') { foreach ($component in $vsRequirements.components) { $args += '-requires' $args += $component } } $vsInfo =& $vsWhereExe $args | ConvertFrom-Json if ($lastExitCode -ne 0) { return $null } # use first matching instance return $vsInfo[0] } function InitializeBuildTool() { if (Test-Path variable:global:_BuildTool) { # If the requested msbuild parameters do not match, clear the cached variables. if($global:_BuildTool.Contains('ExcludePrereleaseVS') -and $global:_BuildTool.ExcludePrereleaseVS -ne $excludePrereleaseVS) { Remove-Item variable:global:_BuildTool Remove-Item variable:global:_MSBuildExe } else { return $global:_BuildTool } } if (-not $msbuildEngine) { $msbuildEngine = GetDefaultMSBuildEngine } # Initialize dotnet cli if listed in 'tools' $dotnetRoot = $null if (Get-Member -InputObject $GlobalJson.tools -Name 'dotnet') { $dotnetRoot = InitializeDotNetCli -install:$restore } if ($msbuildEngine -eq 'dotnet') { if (!$dotnetRoot) { Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "/global.json must specify 'tools.dotnet'." ExitWithExitCode 1 } $dotnetPath = Join-Path $dotnetRoot (GetExecutableFileName 'dotnet') $buildTool = @{ Path = $dotnetPath; Command = 'msbuild'; Tool = 'dotnet'; Framework = 'net' } } elseif ($msbuildEngine -eq "vs") { try { $msbuildPath = InitializeVisualStudioMSBuild -install:$restore } catch { Write-PipelineTelemetryError -Category 'InitializeToolset' -Message $_ ExitWithExitCode 1 } $buildTool = @{ Path = $msbuildPath; Command = ""; Tool = "vs"; Framework = "netframework"; ExcludePrereleaseVS = $excludePrereleaseVS } } else { Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Unexpected value of -msbuildEngine: '$msbuildEngine'." ExitWithExitCode 1 } return $global:_BuildTool = $buildTool } function GetDefaultMSBuildEngine() { # Presence of tools.vs indicates the repo needs to build using VS msbuild on Windows. if (Get-Member -InputObject $GlobalJson.tools -Name 'vs') { return 'vs' } if (Get-Member -InputObject $GlobalJson.tools -Name 'dotnet') { return 'dotnet' } Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "-msbuildEngine must be specified, or /global.json must specify 'tools.dotnet' or 'tools.vs'." ExitWithExitCode 1 } function GetNuGetPackageCachePath() { if ($env:NUGET_PACKAGES -eq $null) { # Use local cache on CI to ensure deterministic build. # Avoid using the http cache as workaround for https://github.com/NuGet/Home/issues/3116 # use global cache in dev builds to avoid cost of downloading packages. # For directory normalization, see also: https://github.com/NuGet/Home/issues/7968 if ($useGlobalNuGetCache) { $env:NUGET_PACKAGES = Join-Path $env:UserProfile '.nuget\packages\' } else { $env:NUGET_PACKAGES = Join-Path $RepoRoot '.packages\' $env:RESTORENOHTTPCACHE = $true } } return $env:NUGET_PACKAGES } # Returns a full path to an Arcade SDK task project file. function GetSdkTaskProject([string]$taskName) { return Join-Path (Split-Path (InitializeToolset) -Parent) "SdkTasks\$taskName.proj" } function InitializeNativeTools() { if (-Not (Test-Path variable:DisableNativeToolsetInstalls) -And (Get-Member -InputObject $GlobalJson -Name "native-tools")) { $nativeArgs= @{} if ($ci) { $nativeArgs = @{ InstallDirectory = "$ToolsDir" } } if ($env:NativeToolsOnMachine) { Write-Host "Variable NativeToolsOnMachine detected, enabling native tool path promotion..." $nativeArgs += @{ PathPromotion = $true } } & "$PSScriptRoot/init-tools-native.ps1" @nativeArgs } } function Read-ArcadeSdkVersion() { return $GlobalJson.'msbuild-sdks'.'Microsoft.DotNet.Arcade.Sdk' } function InitializeToolset() { # For Unified Build/Source-build support, check whether the environment variable is # set. If it is, then use this as the toolset build project. if ($env:_InitializeToolset -ne $null) { return $global:_InitializeToolset = $env:_InitializeToolset } if (Test-Path variable:global:_InitializeToolset) { return $global:_InitializeToolset } $nugetCache = GetNuGetPackageCachePath $toolsetVersion = Read-ArcadeSdkVersion $toolsetLocationFile = Join-Path $ToolsetDir "$toolsetVersion.txt" if (Test-Path $toolsetLocationFile) { $path = Get-Content $toolsetLocationFile -TotalCount 1 if (Test-Path $path) { return $global:_InitializeToolset = $path } } if (-not $restore) { Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Toolset version $toolsetVersion has not been restored." ExitWithExitCode 1 } $buildTool = InitializeBuildTool $proj = Join-Path $ToolsetDir 'restore.proj' $bl = if ($binaryLog) { '/bl:' + (Join-Path $LogDir 'ToolsetRestore.binlog') } else { '' } '' | Set-Content $proj MSBuild-Core $proj $bl /t:__WriteToolsetLocation /clp:ErrorsOnly`;NoSummary /p:__ToolsetLocationOutputFile=$toolsetLocationFile $path = Get-Content $toolsetLocationFile -Encoding UTF8 -TotalCount 1 if (!(Test-Path $path)) { throw "Invalid toolset path: $path" } return $global:_InitializeToolset = $path } function ExitWithExitCode([int] $exitCode) { if ($ci -and $prepareMachine) { Stop-Processes } exit $exitCode } # Check if $LASTEXITCODE is a nonzero exit code (NZEC). If so, print a Azure Pipeline error for # diagnostics, then exit the script with the $LASTEXITCODE. function Exit-IfNZEC([string] $category = "General") { Write-Host "Exit code $LASTEXITCODE" if ($LASTEXITCODE -ne 0) { $message = "Last command failed with exit code $LASTEXITCODE." Write-PipelineTelemetryError -Force -Category $category -Message $message ExitWithExitCode $LASTEXITCODE } } function Stop-Processes() { Write-Host 'Killing running build processes...' foreach ($processName in $processesToStopOnExit) { Get-Process -Name $processName -ErrorAction SilentlyContinue | Stop-Process } } # # Executes msbuild (or 'dotnet msbuild') with arguments passed to the function. # The arguments are automatically quoted. # Terminates the script if the build fails. # function MSBuild() { if ($pipelinesLog) { $buildTool = InitializeBuildTool if ($ci -and $buildTool.Tool -eq 'dotnet') { $env:NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS = 20 $env:NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS = 20 Write-PipelineSetVariable -Name 'NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS' -Value '20' Write-PipelineSetVariable -Name 'NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS' -Value '20' } Enable-Nuget-EnhancedRetry $toolsetBuildProject = InitializeToolset $basePath = Split-Path -parent $toolsetBuildProject $possiblePaths = @( # new scripts need to work with old packages, so we need to look for the old names/versions (Join-Path $basePath (Join-Path $buildTool.Framework 'Microsoft.DotNet.ArcadeLogging.dll')), (Join-Path $basePath (Join-Path $buildTool.Framework 'Microsoft.DotNet.Arcade.Sdk.dll')), # This list doesn't need to be updated anymore and can eventually be removed. (Join-Path $basePath (Join-Path net9.0 'Microsoft.DotNet.ArcadeLogging.dll')), (Join-Path $basePath (Join-Path net9.0 'Microsoft.DotNet.Arcade.Sdk.dll')), (Join-Path $basePath (Join-Path net8.0 'Microsoft.DotNet.ArcadeLogging.dll')), (Join-Path $basePath (Join-Path net8.0 'Microsoft.DotNet.Arcade.Sdk.dll')) ) $selectedPath = $null foreach ($path in $possiblePaths) { if (Test-Path $path -PathType Leaf) { $selectedPath = $path break } } if (-not $selectedPath) { Write-PipelineTelemetryError -Category 'Build' -Message 'Unable to find arcade sdk logger assembly.' ExitWithExitCode 1 } $args += "/logger:$selectedPath" } MSBuild-Core @args } # # Executes msbuild (or 'dotnet msbuild') with arguments passed to the function. # The arguments are automatically quoted. # Terminates the script if the build fails. # function MSBuild-Core() { if ($ci) { if (!$binaryLog -and !$excludeCIBinarylog) { Write-PipelineTelemetryError -Category 'Build' -Message 'Binary log must be enabled in CI build, or explicitly opted-out from with the -excludeCIBinarylog switch.' ExitWithExitCode 1 } if ($nodeReuse) { Write-PipelineTelemetryError -Category 'Build' -Message 'Node reuse must be disabled in CI build.' ExitWithExitCode 1 } } Enable-Nuget-EnhancedRetry $buildTool = InitializeBuildTool $cmdArgs = "$($buildTool.Command) /m /nologo /clp:Summary /v:$verbosity /nr:$nodeReuse /p:ContinuousIntegrationBuild=$ci" if ($warnAsError) { $cmdArgs += ' /warnaserror /p:TreatWarningsAsErrors=true' } else { $cmdArgs += ' /p:TreatWarningsAsErrors=false' } foreach ($arg in $args) { if ($null -ne $arg -and $arg.Trim() -ne "") { if ($arg.EndsWith('\')) { $arg = $arg + "\" } $cmdArgs += " `"$arg`"" } } # Be sure quote the path in case there are spaces in the dotnet installation location. $env:ARCADE_BUILD_TOOL_COMMAND = "`"$($buildTool.Path)`" $cmdArgs" $exitCode = Exec-Process $buildTool.Path $cmdArgs if ($exitCode -ne 0) { # We should not Write-PipelineTaskError here because that message shows up in the build summary # The build already logged an error, that's the reason it failed. Producing an error here only adds noise. Write-Host "Build failed with exit code $exitCode. Check errors above." -ForegroundColor Red $buildLog = GetMSBuildBinaryLogCommandLineArgument $args if ($null -ne $buildLog) { Write-Host "See log: $buildLog" -ForegroundColor DarkGray } # When running on Azure Pipelines, override the returned exit code to avoid double logging. # Skip this when the build is a child of the VMR orchestrator build. if ($ci -and $env:SYSTEM_TEAMPROJECT -ne $null -and !$productBuild -and -not($properties -like "*DotNetBuildRepo=true*")) { Write-PipelineSetResult -Result "Failed" -Message "msbuild execution failed." # Exiting with an exit code causes the azure pipelines task to log yet another "noise" error # The above Write-PipelineSetResult will cause the task to be marked as failure without adding yet another error ExitWithExitCode 0 } else { ExitWithExitCode $exitCode } } } function GetMSBuildBinaryLogCommandLineArgument($arguments) { foreach ($argument in $arguments) { if ($argument -ne $null) { $arg = $argument.Trim() if ($arg.StartsWith('/bl:', "OrdinalIgnoreCase")) { return $arg.Substring('/bl:'.Length) } if ($arg.StartsWith('/binaryLogger:', 'OrdinalIgnoreCase')) { return $arg.Substring('/binaryLogger:'.Length) } } } return $null } function GetExecutableFileName($baseName) { if (IsWindowsPlatform) { return "$baseName.exe" } else { return $baseName } } function IsWindowsPlatform() { return [environment]::OSVersion.Platform -eq [PlatformID]::Win32NT } function Get-Darc($version) { $darcPath = "$TempDir\darc\$([guid]::NewGuid())" if ($version -ne $null) { & $PSScriptRoot\darc-init.ps1 -toolpath $darcPath -darcVersion $version | Out-Host } else { & $PSScriptRoot\darc-init.ps1 -toolpath $darcPath | Out-Host } return "$darcPath\darc.exe" } . $PSScriptRoot\pipeline-logging-functions.ps1 $RepoRoot = Resolve-Path (Join-Path $PSScriptRoot '..\..\') $EngRoot = Resolve-Path (Join-Path $PSScriptRoot '..') $ArtifactsDir = Join-Path $RepoRoot 'artifacts' $ToolsetDir = Join-Path $ArtifactsDir 'toolset' $ToolsDir = Join-Path $RepoRoot '.tools' $LogDir = Join-Path (Join-Path $ArtifactsDir 'log') $configuration $TempDir = Join-Path (Join-Path $ArtifactsDir 'tmp') $configuration $GlobalJson = Get-Content -Raw -Path (Join-Path $RepoRoot 'global.json') | ConvertFrom-Json # true if global.json contains a "runtimes" section $globalJsonHasRuntimes = if ($GlobalJson.tools.PSObject.Properties.Name -Match 'runtimes') { $true } else { $false } Create-Directory $ToolsetDir Create-Directory $TempDir Create-Directory $LogDir Write-PipelineSetVariable -Name 'Artifacts' -Value $ArtifactsDir Write-PipelineSetVariable -Name 'Artifacts.Toolset' -Value $ToolsetDir Write-PipelineSetVariable -Name 'Artifacts.Log' -Value $LogDir Write-PipelineSetVariable -Name 'TEMP' -Value $TempDir Write-PipelineSetVariable -Name 'TMP' -Value $TempDir # Import custom tools configuration, if present in the repo. # Note: Import in global scope so that the script set top-level variables without qualification. if (!$disableConfigureToolsetImport) { $configureToolsetScript = Join-Path $EngRoot 'configure-toolset.ps1' if (Test-Path $configureToolsetScript) { . $configureToolsetScript if ((Test-Path variable:failOnConfigureToolsetError) -And $failOnConfigureToolsetError) { if ((Test-Path variable:LastExitCode) -And ($LastExitCode -ne 0)) { Write-PipelineTelemetryError -Category 'Build' -Message 'configure-toolset.ps1 returned a non-zero exit code' ExitWithExitCode $LastExitCode } } } } # # If $ci flag is set, turn on (and log that we did) special environment variables for improved Nuget client retry logic. # function Enable-Nuget-EnhancedRetry() { if ($ci) { Write-Host "Setting NUGET enhanced retry environment variables" $env:NUGET_ENABLE_ENHANCED_HTTP_RETRY = 'true' $env:NUGET_ENHANCED_MAX_NETWORK_TRY_COUNT = 6 $env:NUGET_ENHANCED_NETWORK_RETRY_DELAY_MILLISECONDS = 1000 $env:NUGET_RETRY_HTTP_429 = 'true' Write-PipelineSetVariable -Name 'NUGET_ENABLE_ENHANCED_HTTP_RETRY' -Value 'true' Write-PipelineSetVariable -Name 'NUGET_ENHANCED_MAX_NETWORK_TRY_COUNT' -Value '6' Write-PipelineSetVariable -Name 'NUGET_ENHANCED_NETWORK_RETRY_DELAY_MILLISECONDS' -Value '1000' Write-PipelineSetVariable -Name 'NUGET_RETRY_HTTP_429' -Value 'true' } } ================================================ FILE: eng/common/tools.sh ================================================ #!/usr/bin/env bash # Initialize variables if they aren't already defined. # CI mode - set to true on CI server for PR validation build or official build. ci=${ci:-false} # Set to true to use the pipelines logger which will enable Azure logging output. # https://github.com/Microsoft/azure-pipelines-tasks/blob/master/docs/authoring/commands.md # This flag is meant as a temporary opt-opt for the feature while validate it across # our consumers. It will be deleted in the future. if [[ "$ci" == true ]]; then pipelines_log=${pipelines_log:-true} else pipelines_log=${pipelines_log:-false} fi # Build configuration. Common values include 'Debug' and 'Release', but the repository may use other names. configuration=${configuration:-'Debug'} # Set to true to opt out of outputting binary log while running in CI exclude_ci_binary_log=${exclude_ci_binary_log:-false} if [[ "$ci" == true && "$exclude_ci_binary_log" == false ]]; then binary_log_default=true else binary_log_default=false fi # Set to true to output binary log from msbuild. Note that emitting binary log slows down the build. binary_log=${binary_log:-$binary_log_default} # Turns on machine preparation/clean up code that changes the machine state (e.g. kills build processes). prepare_machine=${prepare_machine:-false} # True to restore toolsets and dependencies. restore=${restore:-true} # Adjusts msbuild verbosity level. verbosity=${verbosity:-'minimal'} # Set to true to reuse msbuild nodes. Recommended to not reuse on CI. if [[ "$ci" == true ]]; then node_reuse=${node_reuse:-false} else node_reuse=${node_reuse:-true} fi # Configures warning treatment in msbuild. warn_as_error=${warn_as_error:-true} # True to attempt using .NET Core already that meets requirements specified in global.json # installed on the machine instead of downloading one. use_installed_dotnet_cli=${use_installed_dotnet_cli:-true} # Enable repos to use a particular version of the on-line dotnet-install scripts. # default URL: https://dotnet.microsoft.com/download/dotnet/scripts/v1/dotnet-install.sh dotnetInstallScriptVersion=${dotnetInstallScriptVersion:-'v1'} # True to use global NuGet cache instead of restoring packages to repository-local directory. if [[ "$ci" == true ]]; then use_global_nuget_cache=${use_global_nuget_cache:-false} else use_global_nuget_cache=${use_global_nuget_cache:-true} fi # Used when restoring .NET SDK from alternative feeds runtime_source_feed=${runtime_source_feed:-''} runtime_source_feed_key=${runtime_source_feed_key:-''} # True if the build is a product build product_build=${product_build:-false} # Resolve any symlinks in the given path. function ResolvePath { local path=$1 while [[ -h $path ]]; do local dir="$( cd -P "$( dirname "$path" )" && pwd )" path="$(readlink "$path")" # if $path was a relative symlink, we need to resolve it relative to the path where the # symlink file was located [[ $path != /* ]] && path="$dir/$path" done # return value _ResolvePath="$path" } # ReadVersionFromJson [json key] function ReadGlobalVersion { local key=$1 if command -v jq &> /dev/null; then _ReadGlobalVersion="$(jq -r ".[] | select(has(\"$key\")) | .\"$key\"" "$global_json_file")" elif [[ "$(cat "$global_json_file")" =~ \"$key\"[[:space:]\:]*\"([^\"]+) ]]; then _ReadGlobalVersion=${BASH_REMATCH[1]} fi if [[ -z "$_ReadGlobalVersion" ]]; then Write-PipelineTelemetryError -category 'Build' "Error: Cannot find \"$key\" in $global_json_file" ExitWithExitCode 1 fi } function InitializeDotNetCli { if [[ -n "${_InitializeDotNetCli:-}" ]]; then return fi local install=$1 # Don't resolve runtime, shared framework, or SDK from other locations to ensure build determinism export DOTNET_MULTILEVEL_LOOKUP=0 # Disable first run since we want to control all package sources export DOTNET_NOLOGO=1 # Disable telemetry on CI if [[ $ci == true ]]; then export DOTNET_CLI_TELEMETRY_OPTOUT=1 fi # LTTNG is the logging infrastructure used by Core CLR. Need this variable set # so it doesn't output warnings to the console. export LTTNG_HOME="$HOME" # Find the first path on $PATH that contains the dotnet.exe if [[ "$use_installed_dotnet_cli" == true && $global_json_has_runtimes == false && -z "${DOTNET_INSTALL_DIR:-}" ]]; then local dotnet_path=`command -v dotnet` if [[ -n "$dotnet_path" ]]; then ResolvePath "$dotnet_path" export DOTNET_INSTALL_DIR=`dirname "$_ResolvePath"` fi fi ReadGlobalVersion "dotnet" local dotnet_sdk_version=$_ReadGlobalVersion local dotnet_root="" # Use dotnet installation specified in DOTNET_INSTALL_DIR if it contains the required SDK version, # otherwise install the dotnet CLI and SDK to repo local .dotnet directory to avoid potential permission issues. if [[ $global_json_has_runtimes == false && -n "${DOTNET_INSTALL_DIR:-}" && -d "$DOTNET_INSTALL_DIR/sdk/$dotnet_sdk_version" ]]; then dotnet_root="$DOTNET_INSTALL_DIR" else dotnet_root="${repo_root}.dotnet" export DOTNET_INSTALL_DIR="$dotnet_root" if [[ ! -d "$DOTNET_INSTALL_DIR/sdk/$dotnet_sdk_version" ]]; then if [[ "$install" == true ]]; then InstallDotNetSdk "$dotnet_root" "$dotnet_sdk_version" else Write-PipelineTelemetryError -category 'InitializeToolset' "Unable to find dotnet with SDK version '$dotnet_sdk_version'" ExitWithExitCode 1 fi fi fi # Add dotnet to PATH. This prevents any bare invocation of dotnet in custom # build steps from using anything other than what we've downloaded. Write-PipelinePrependPath -path "$dotnet_root" Write-PipelineSetVariable -name "DOTNET_MULTILEVEL_LOOKUP" -value "0" Write-PipelineSetVariable -name "DOTNET_NOLOGO" -value "1" # return value _InitializeDotNetCli="$dotnet_root" } function InstallDotNetSdk { local root=$1 local version=$2 local architecture="unset" if [[ $# -ge 3 ]]; then architecture=$3 fi InstallDotNet "$root" "$version" $architecture 'sdk' 'true' $runtime_source_feed $runtime_source_feed_key } function InstallDotNet { local root=$1 local version=$2 local runtime=$4 local dotnetVersionLabel="'$runtime v$version'" if [[ -n "${4:-}" ]] && [ "$4" != 'sdk' ]; then runtimePath="$root" runtimePath="$runtimePath/shared" case "$runtime" in dotnet) runtimePath="$runtimePath/Microsoft.NETCore.App" ;; aspnetcore) runtimePath="$runtimePath/Microsoft.AspNetCore.App" ;; windowsdesktop) runtimePath="$runtimePath/Microsoft.WindowsDesktop.App" ;; *) ;; esac runtimePath="$runtimePath/$version" dotnetVersionLabel="runtime toolset '$runtime/$architecture v$version'" if [ -d "$runtimePath" ]; then echo " Runtime toolset '$runtime/$architecture v$version' already installed." local installSuccess=1 return fi fi GetDotNetInstallScript "$root" local install_script=$_GetDotNetInstallScript local installParameters=(--version $version --install-dir "$root") if [[ -n "${3:-}" ]] && [ "$3" != 'unset' ]; then installParameters+=(--architecture $3) fi if [[ -n "${4:-}" ]] && [ "$4" != 'sdk' ]; then installParameters+=(--runtime $4) fi if [[ "$#" -ge "5" ]] && [[ "$5" != 'false' ]]; then installParameters+=(--skip-non-versioned-files) fi local variations=() # list of variable names with parameter arrays in them local public_location=("${installParameters[@]}") variations+=(public_location) local dotnetbuilds=("${installParameters[@]}" --azure-feed "https://ci.dot.net/public") variations+=(dotnetbuilds) if [[ -n "${6:-}" ]]; then variations+=(private_feed) local private_feed=("${installParameters[@]}" --azure-feed $6) if [[ -n "${7:-}" ]]; then # The 'base64' binary on alpine uses '-d' and doesn't support '--decode' # '-d'. To work around this, do a simple detection and switch the parameter # accordingly. decodeArg="--decode" if base64 --help 2>&1 | grep -q "BusyBox"; then decodeArg="-d" fi decodedFeedKey=`echo $7 | base64 $decodeArg` private_feed+=(--feed-credential $decodedFeedKey) fi fi local installSuccess=0 for variationName in "${variations[@]}"; do local name="$variationName[@]" local variation=("${!name}") echo " Attempting to install $dotnetVersionLabel from $variationName." bash "$install_script" "${variation[@]}" && installSuccess=1 if [[ "$installSuccess" -eq 1 ]]; then break fi echo " Failed to install $dotnetVersionLabel from $variationName." done if [[ "$installSuccess" -eq 0 ]]; then Write-PipelineTelemetryError -category 'InitializeToolset' "Failed to install $dotnetVersionLabel from any of the specified locations." ExitWithExitCode 1 fi } function with_retries { local maxRetries=5 local retries=1 echo "Trying to run '$@' for maximum of $maxRetries attempts." while [[ $((retries++)) -le $maxRetries ]]; do "$@" if [[ $? == 0 ]]; then echo "Ran '$@' successfully." return 0 fi timeout=$((3**$retries-1)) echo "Failed to execute '$@'. Waiting $timeout seconds before next attempt ($retries out of $maxRetries)." 1>&2 sleep $timeout done echo "Failed to execute '$@' for $maxRetries times." 1>&2 return 1 } function GetDotNetInstallScript { local root=$1 local install_script="$root/dotnet-install.sh" local install_script_url="https://dotnet.microsoft.com/download/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.sh" if [[ ! -a "$install_script" ]]; then mkdir -p "$root" echo "Downloading '$install_script_url'" # Use curl if available, otherwise use wget if command -v curl > /dev/null; then # first, try directly, if this fails we will retry with verbose logging curl "$install_script_url" -sSL --retry 10 --create-dirs -o "$install_script" || { if command -v openssl &> /dev/null; then echo "Curl failed; dumping some information about dotnet.microsoft.com for later investigation" echo | openssl s_client -showcerts -servername dotnet.microsoft.com -connect dotnet.microsoft.com:443 || true fi echo "Will now retry the same URL with verbose logging." with_retries curl "$install_script_url" -sSL --verbose --retry 10 --create-dirs -o "$install_script" || { local exit_code=$? Write-PipelineTelemetryError -category 'InitializeToolset' "Failed to acquire dotnet install script (exit code '$exit_code')." ExitWithExitCode $exit_code } } else with_retries wget -v -O "$install_script" "$install_script_url" || { local exit_code=$? Write-PipelineTelemetryError -category 'InitializeToolset' "Failed to acquire dotnet install script (exit code '$exit_code')." ExitWithExitCode $exit_code } fi fi # return value _GetDotNetInstallScript="$install_script" } function InitializeBuildTool { if [[ -n "${_InitializeBuildTool:-}" ]]; then return fi InitializeDotNetCli $restore # return values _InitializeBuildTool="$_InitializeDotNetCli/dotnet" _InitializeBuildToolCommand="msbuild" } # Set RestoreNoHttpCache as a workaround for https://github.com/NuGet/Home/issues/3116 function GetNuGetPackageCachePath { if [[ -z ${NUGET_PACKAGES:-} ]]; then if [[ "$use_global_nuget_cache" == true ]]; then export NUGET_PACKAGES="$HOME/.nuget/packages/" else export NUGET_PACKAGES="$repo_root/.packages/" export RESTORENOHTTPCACHE=true fi fi # return value _GetNuGetPackageCachePath=$NUGET_PACKAGES } function InitializeNativeTools() { if [[ -n "${DisableNativeToolsetInstalls:-}" ]]; then return fi if grep -Fq "native-tools" $global_json_file then local nativeArgs="" if [[ "$ci" == true ]]; then nativeArgs="--installDirectory $tools_dir" fi "$_script_dir/init-tools-native.sh" $nativeArgs fi } function InitializeToolset { if [[ -n "${_InitializeToolset:-}" ]]; then return fi GetNuGetPackageCachePath ReadGlobalVersion "Microsoft.DotNet.Arcade.Sdk" local toolset_version=$_ReadGlobalVersion local toolset_location_file="$toolset_dir/$toolset_version.txt" if [[ -a "$toolset_location_file" ]]; then local path=`cat "$toolset_location_file"` if [[ -a "$path" ]]; then # return value _InitializeToolset="$path" return fi fi if [[ "$restore" != true ]]; then Write-PipelineTelemetryError -category 'InitializeToolset' "Toolset version $toolset_version has not been restored." ExitWithExitCode 2 fi local proj="$toolset_dir/restore.proj" local bl="" if [[ "$binary_log" == true ]]; then bl="/bl:$log_dir/ToolsetRestore.binlog" fi echo '' > "$proj" MSBuild-Core "$proj" $bl /t:__WriteToolsetLocation /clp:ErrorsOnly\;NoSummary /p:__ToolsetLocationOutputFile="$toolset_location_file" local toolset_build_proj=`cat "$toolset_location_file"` if [[ ! -a "$toolset_build_proj" ]]; then Write-PipelineTelemetryError -category 'Build' "Invalid toolset path: $toolset_build_proj" ExitWithExitCode 3 fi # return value _InitializeToolset="$toolset_build_proj" } function ExitWithExitCode { if [[ "$ci" == true && "$prepare_machine" == true ]]; then StopProcesses fi exit $1 } function StopProcesses { echo "Killing running build processes..." pkill -9 "dotnet" || true pkill -9 "vbcscompiler" || true return 0 } function MSBuild { local args=( "$@" ) if [[ "$pipelines_log" == true ]]; then InitializeBuildTool InitializeToolset if [[ "$ci" == true ]]; then export NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS=20 export NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS=20 Write-PipelineSetVariable -name "NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS" -value "20" Write-PipelineSetVariable -name "NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS" -value "20" fi local toolset_dir="${_InitializeToolset%/*}" # new scripts need to work with old packages, so we need to look for the old names/versions local selectedPath= local possiblePaths=() possiblePaths+=( "$toolset_dir/net/Microsoft.DotNet.ArcadeLogging.dll" ) possiblePaths+=( "$toolset_dir/net/Microsoft.DotNet.Arcade.Sdk.dll" ) # This list doesn't need to be updated anymore and can eventually be removed. possiblePaths+=( "$toolset_dir/net9.0/Microsoft.DotNet.ArcadeLogging.dll" ) possiblePaths+=( "$toolset_dir/net9.0/Microsoft.DotNet.Arcade.Sdk.dll" ) possiblePaths+=( "$toolset_dir/net8.0/Microsoft.DotNet.ArcadeLogging.dll" ) possiblePaths+=( "$toolset_dir/net8.0/Microsoft.DotNet.Arcade.Sdk.dll" ) for path in "${possiblePaths[@]}"; do if [[ -f $path ]]; then selectedPath=$path break fi done if [[ -z "$selectedPath" ]]; then Write-PipelineTelemetryError -category 'Build' "Unable to find arcade sdk logger assembly." ExitWithExitCode 1 fi args+=( "-logger:$selectedPath" ) fi MSBuild-Core "${args[@]}" } function MSBuild-Core { if [[ "$ci" == true ]]; then if [[ "$binary_log" != true && "$exclude_ci_binary_log" != true ]]; then Write-PipelineTelemetryError -category 'Build' "Binary log must be enabled in CI build, or explicitly opted-out from with the -noBinaryLog switch." ExitWithExitCode 1 fi if [[ "$node_reuse" == true ]]; then Write-PipelineTelemetryError -category 'Build' "Node reuse must be disabled in CI build." ExitWithExitCode 1 fi fi InitializeBuildTool local warnaserror_switch="" if [[ $warn_as_error == true ]]; then warnaserror_switch="/warnaserror" fi function RunBuildTool { export ARCADE_BUILD_TOOL_COMMAND="$_InitializeBuildTool $@" "$_InitializeBuildTool" "$@" || { local exit_code=$? # We should not Write-PipelineTaskError here because that message shows up in the build summary # The build already logged an error, that's the reason it failed. Producing an error here only adds noise. echo "Build failed with exit code $exit_code. Check errors above." # When running on Azure Pipelines, override the returned exit code to avoid double logging. # Skip this when the build is a child of the VMR orchestrator build. if [[ "$ci" == true && -n ${SYSTEM_TEAMPROJECT:-} && "$product_build" != true && "$properties" != *"DotNetBuildRepo=true"* ]]; then Write-PipelineSetResult -result "Failed" -message "msbuild execution failed." # Exiting with an exit code causes the azure pipelines task to log yet another "noise" error # The above Write-PipelineSetResult will cause the task to be marked as failure without adding yet another error ExitWithExitCode 0 else ExitWithExitCode $exit_code fi } } RunBuildTool "$_InitializeBuildToolCommand" /m /nologo /clp:Summary /v:$verbosity /nr:$node_reuse $warnaserror_switch /p:TreatWarningsAsErrors=$warn_as_error /p:ContinuousIntegrationBuild=$ci "$@" } function GetDarc { darc_path="$temp_dir/darc" version="$1" if [[ -n "$version" ]]; then version="--darcversion $version" fi "$eng_root/common/darc-init.sh" --toolpath "$darc_path" $version } ResolvePath "${BASH_SOURCE[0]}" _script_dir=`dirname "$_ResolvePath"` . "$_script_dir/pipeline-logging-functions.sh" eng_root=`cd -P "$_script_dir/.." && pwd` repo_root=`cd -P "$_script_dir/../.." && pwd` repo_root="${repo_root}/" artifacts_dir="${repo_root}artifacts" toolset_dir="$artifacts_dir/toolset" tools_dir="${repo_root}.tools" log_dir="$artifacts_dir/log/$configuration" temp_dir="$artifacts_dir/tmp/$configuration" global_json_file="${repo_root}global.json" # determine if global.json contains a "runtimes" entry global_json_has_runtimes=false if command -v jq &> /dev/null; then if jq -e '.tools | has("runtimes")' "$global_json_file" &> /dev/null; then global_json_has_runtimes=true fi elif [[ "$(cat "$global_json_file")" =~ \"runtimes\"[[:space:]\:]*\{ ]]; then global_json_has_runtimes=true fi # HOME may not be defined in some scenarios, but it is required by NuGet if [[ -z $HOME ]]; then export HOME="${repo_root}artifacts/.home/" mkdir -p "$HOME" fi mkdir -p "$toolset_dir" mkdir -p "$temp_dir" mkdir -p "$log_dir" Write-PipelineSetVariable -name "Artifacts" -value "$artifacts_dir" Write-PipelineSetVariable -name "Artifacts.Toolset" -value "$toolset_dir" Write-PipelineSetVariable -name "Artifacts.Log" -value "$log_dir" Write-PipelineSetVariable -name "Temp" -value "$temp_dir" Write-PipelineSetVariable -name "TMP" -value "$temp_dir" # Import custom tools configuration, if present in the repo. if [ -z "${disable_configure_toolset_import:-}" ]; then configure_toolset_script="$eng_root/configure-toolset.sh" if [[ -a "$configure_toolset_script" ]]; then . "$configure_toolset_script" fi fi # TODO: https://github.com/dotnet/arcade/issues/1468 # Temporary workaround to avoid breaking change. # Remove once repos are updated. if [[ -n "${useInstalledDotNetCli:-}" ]]; then use_installed_dotnet_cli="$useInstalledDotNetCli" fi ================================================ FILE: global.json ================================================ { "tools": { "dotnet": "10.0.100-alpha.1.24573.1" }, "msbuild-sdks": { "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.25056.1" } } ================================================ FILE: src/Directory.Build.props ================================================ true $(RepoRoot)\build\analyzers\rulesets\Default.ruleset true ================================================ FILE: src/Microsoft.HttpRepl/ApiConnection.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. #nullable enable using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Microsoft.HttpRepl.OpenApi; using Microsoft.HttpRepl.Preferences; using Microsoft.Repl.ConsoleHandling; namespace Microsoft.HttpRepl { internal class ApiConnection { private readonly HttpState _httpState; private readonly IWritable _logger; private readonly bool _logVerboseMessages; private readonly IOpenApiSearchPathsProvider _searchPaths; public Uri? RootUri { get; set; } public bool HasRootUri => RootUri is object; public Uri? BaseUri { get; set; } public bool HasBaseUri => BaseUri is object; public Uri? SwaggerUri { get; set; } public bool HasSwaggerUri => SwaggerUri is object; public string? SwaggerDocument { get; set; } public bool HasSwaggerDocument => SwaggerDocument is object; public bool AllowBaseOverrideBySwagger { get; set; } public ApiConnection(HttpState httpState, IPreferences preferences, IWritable logger, bool logVerboseMessages, IOpenApiSearchPathsProvider? openApiSearchPaths = null) { _httpState = httpState ?? throw new ArgumentNullException(nameof(httpState)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _logVerboseMessages = logVerboseMessages; _searchPaths = openApiSearchPaths ?? new OpenApiSearchPathsProvider(preferences); } private async Task FindSwaggerDoc(HttpClient client, IEnumerable swaggerSearchPaths, CancellationToken cancellationToken) { HashSet checkedUris = new HashSet(); List baseUrisToCheck = new List(); if (HasRootUri) { baseUrisToCheck.Add(RootUri!); } if (HasBaseUri) { baseUrisToCheck.Add(BaseUri!); } foreach (Uri baseUriToCheck in baseUrisToCheck) { foreach (string swaggerSearchPath in swaggerSearchPaths) { if (Uri.TryCreate(baseUriToCheck, swaggerSearchPath, out Uri? swaggerUri) && !checkedUris.Contains(swaggerUri)) { string? document = await GetSwaggerDocAsync(client, swaggerUri, cancellationToken); if (document is not null) { SwaggerUri = swaggerUri; SwaggerDocument = document; return; } checkedUris.Add(swaggerUri); } } } } private async Task GetSwaggerDocAsync(HttpClient client, Uri uri, CancellationToken cancellationToken) { try { WriteVerbose(string.Format(Resources.Strings.ApiConnection_Logging_Checking, uri)); HttpResponseMessage? response = await client.GetAsync(uri, cancellationToken).ConfigureAwait(false); if (cancellationToken.IsCancellationRequested) { _logger.WriteLine(Resources.Strings.ApiConnection_Logging_Cancelled.SetColor(_httpState.ErrorColor)); return null; } if (response.IsSuccessStatusCode) { WriteLineVerbose(Resources.Strings.ApiConnection_Logging_Found.SetColor(AllowedColors.BoldGreen)); #if NET5_0_OR_GREATER string responseString = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); #else string responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); #endif WriteVerbose(Resources.Strings.ApiConnection_Logging_Parsing); ApiDefinitionReader reader = new ApiDefinitionReader(); ApiDefinitionParseResult result = reader.CanHandle(responseString); if (result.Success) { if (result.ValidationMessages.Count == 0) { WriteLineVerbose(Resources.Strings.ApiConnection_Logging_Successful.SetColor(AllowedColors.BoldGreen)); } else { WriteLineVerbose(Resources.Strings.ApiConnection_Logging_SuccessfulWithWarnings.SetColor(_httpState.WarningColor)); foreach (string validationMessage in result.ValidationMessages) { WriteLineVerbose(validationMessage.SetColor(_httpState.WarningColor)); } } return responseString; } else { WriteLineVerbose(Resources.Strings.ApiConnection_Logging_Failed.SetColor(_httpState.ErrorColor)); return null; } } else { int statusCode = (int)response.StatusCode; string statusCodeDescription = response.StatusCode.ToString(); WriteLineVerbose($"{statusCode} {statusCodeDescription}".SetColor(_httpState.ErrorColor)); return null; } } catch (Exception e) { WriteLineVerbose(e.Message.SetColor(_httpState.ErrorColor)); return null; } finally { WriteLineVerbose(); } } public void SetupApiDefinition(HttpState programState) { if (SwaggerDocument is not null && SwaggerUri is not null) { ApiDefinitionReader reader = new ApiDefinitionReader(); ApiDefinitionParseResult parseResult = reader.Read(SwaggerDocument, SwaggerUri); if (parseResult.Success) { programState.ApiDefinition = parseResult.ApiDefinition; if (programState.ApiDefinition is not null) { programState.SwaggerEndpoint = SwaggerUri; } } } } public async Task SetupHttpState(HttpState httpState, bool performAutoDetect, bool persistHeaders, bool persistPath, CancellationToken cancellationToken) { httpState.ResetState(persistHeaders, persistPath); if (HasSwaggerUri) { SwaggerDocument = await GetSwaggerDocAsync(httpState.Client, SwaggerUri!, cancellationToken); } else if (performAutoDetect) { await FindSwaggerDoc(httpState.Client, _searchPaths.GetOpenApiSearchPaths(), cancellationToken); } if (HasSwaggerDocument) { SetupApiDefinition(httpState); } // If there's a base address in the api definition and there was no explicit base address, set the // base address to the first one in the api definition if (httpState.ApiDefinition?.BaseAddresses?.Any() == true && AllowBaseOverrideBySwagger) { httpState.BaseAddress = httpState.ApiDefinition.BaseAddresses[0].Url; } else if (HasBaseUri) { httpState.BaseAddress = BaseUri; } } private void WriteVerbose(string s) { if (_logVerboseMessages) { _logger.Write(s); } } private void WriteLineVerbose(string s) { if (_logVerboseMessages) { _logger.WriteLine(s); } } private void WriteLineVerbose() { if (_logVerboseMessages) { _logger.WriteLine(); } } } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/AddQueryParamCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization.Formatters; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.HttpRepl.Resources; using Microsoft.HttpRepl.Telemetry; using Microsoft.HttpRepl.Telemetry.Events; using Microsoft.Repl; using Microsoft.Repl.Commanding; using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Parsing; namespace Microsoft.HttpRepl.Commands { public class AddQueryParamCommand : ICommand { private const string CommandName = "add"; private const string SubCommand = "query-param"; private readonly ITelemetry _telemetry; public string Name => "addQueryParam"; public AddQueryParamCommand(ITelemetry telemetry) { _telemetry = telemetry; } public bool? CanHandle(IShellState shellState, HttpState programState, ICoreParseResult parseResult) => parseResult.ContainsAtLeast(minimumLength: 3, CommandName, SubCommand) ? (bool?)true : null; public Task ExecuteAsync(IShellState shellState, HttpState programState, ICoreParseResult parseResult, CancellationToken cancellationToken) { parseResult = parseResult ?? throw new ArgumentNullException(nameof(parseResult)); programState = programState ?? throw new ArgumentNullException(nameof(programState)); int sectionCount = parseResult.Sections.Count; bool isValueEmpty; if(sectionCount % 2 == 0) { for(int i = 2; i < sectionCount; i+=2) { if (i + 1 < sectionCount) { if (programState.QueryParam.ContainsKey(parseResult.Sections[i])){ IEnumerable updatedParams = programState.QueryParam[parseResult.Sections[i]].Append(parseResult.Sections[i + 1]); programState.QueryParam[parseResult.Sections[i]] = updatedParams; } else { programState.QueryParam[parseResult.Sections[i]] = Enumerable.Repeat(parseResult.Sections[i + 1], 1); } } } isValueEmpty = false; } else { isValueEmpty = true; shellState.ConsoleManager.WriteLine($"The add query-param command key: {parseResult.Sections[sectionCount - 1]} is missing a value. Please try again with a valid key value pair"); } _telemetry.TrackEvent(new AddQueryParamEvent(parseResult.Sections[2], isValueEmpty)); return Task.CompletedTask; } public string GetHelpDetails(IShellState shellState, HttpState programState, ICoreParseResult parseResult) { if (parseResult.ContainsAtLeast(CommandName, SubCommand)) { StringBuilder helpText = new StringBuilder(); helpText.Append(Strings.Usage.Bold()); helpText.AppendLine("add query-param {name} [value]"); helpText.AppendLine(); helpText.AppendLine(Strings.AddQueryParamCommand_HelpDetails); return helpText.ToString(); } return null; } public string GetHelpSummary(IShellState shellState, HttpState programState) => Strings.AddQueryParamCommand_HelpSummary; public IEnumerable Suggest(IShellState shellState, HttpState programState, ICoreParseResult parseResult) => null; } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/BaseHttpCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Xml.Linq; using Microsoft.HttpRepl.FileSystem; using Microsoft.HttpRepl.Formatting; using Microsoft.HttpRepl.Preferences; using Microsoft.HttpRepl.Resources; using Microsoft.HttpRepl.Suggestions; using Microsoft.HttpRepl.Telemetry; using Microsoft.HttpRepl.Telemetry.Events; using Microsoft.Repl; using Microsoft.Repl.Commanding; using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Parsing; using Microsoft.Repl.Suggestions; using Newtonsoft.Json.Linq; namespace Microsoft.HttpRepl.Commands { public abstract class BaseHttpCommand : CommandWithStructuredInputBase { private const string HeaderOption = nameof(HeaderOption); private const string ResponseHeadersFileOption = nameof(ResponseHeadersFileOption); private const string ResponseBodyFileOption = nameof(ResponseBodyFileOption); private const string BodyFileOption = nameof(BodyFileOption); private const string NoBodyOption = nameof(NoBodyOption); private const string NoFormattingOption = nameof(NoFormattingOption); private const string StreamingOption = nameof(StreamingOption); private const string BodyContentOption = nameof(BodyContentOption); private static readonly char[] HeaderSeparatorChars = new[] { '=', ':' }; private static readonly Dictionary FileExtensionLookup = new Dictionary(StringComparer.OrdinalIgnoreCase) { { "application/json", ".json" }, { "text/json", ".json" }, { "application/xml", ".xml" }, { "text/xml", ".xml" }, }; private CommandInputSpecification _inputSpec; private readonly IFileSystem _fileSystem; private readonly IPreferences _preferences; private readonly ITelemetry _telemetry; public override string Name => Verb; protected abstract string Verb { get; } protected abstract bool RequiresBody { get; } protected BaseHttpCommand(IFileSystem fileSystem, IPreferences preferences, ITelemetry telemetry) { _fileSystem = fileSystem; _preferences = preferences; _telemetry = telemetry; } public override CommandInputSpecification InputSpec { get { if (_inputSpec != null) { return _inputSpec; } CommandInputSpecificationBuilder builder = CommandInputSpecification.Create(Verb) .MaximumArgCount(1) .WithOption(new CommandOptionSpecification(HeaderOption, requiresValue: true, forms: new[] { "--header", "-h" })) .WithOption(new CommandOptionSpecification(ResponseHeadersFileOption, requiresValue: true, maximumOccurrences: 1, forms: new[] { "--response:headers", })) .WithOption(new CommandOptionSpecification(ResponseBodyFileOption, requiresValue: true, maximumOccurrences: 1, forms: new[] { "--response:body", })) .WithOption(new CommandOptionSpecification(NoFormattingOption, maximumOccurrences: 1, forms: new[] { "--no-formatting", "-F" })) .WithOption(new CommandOptionSpecification(StreamingOption, maximumOccurrences: 1, forms: new[] { "--streaming", "-s" })); if (RequiresBody) { builder = builder.WithOption(new CommandOptionSpecification(NoBodyOption, maximumOccurrences: 1, forms: "--no-body")) .WithOption(new CommandOptionSpecification(BodyFileOption, requiresValue: true, maximumOccurrences: 1, forms: new[] { "--file", "-f" })) .WithOption(new CommandOptionSpecification(BodyContentOption, requiresValue: true, maximumOccurrences: 1, forms: new[] { "--content", "-c" })); } _inputSpec = builder.Finish(); return _inputSpec; } } protected override async Task ExecuteAsync(IShellState shellState, HttpState programState, DefaultCommandInput commandInput, ICoreParseResult parseResult, CancellationToken cancellationToken) { programState = programState ?? throw new ArgumentNullException(nameof(programState)); commandInput = commandInput ?? throw new ArgumentNullException(nameof(commandInput)); shellState = shellState ?? throw new ArgumentNullException(nameof(shellState)); if (programState.BaseAddress == null && (commandInput.Arguments.Count == 0 || !Uri.TryCreate(commandInput.Arguments[0].Text, UriKind.Absolute, out _))) { shellState.ConsoleManager.Error.WriteLine(Strings.Error_NoBasePath.SetColor(programState.ErrorColor)); return; } SendTelemetry(commandInput); if (programState.SwaggerEndpoint != null) { await CreateDirectoryStructureForSwaggerEndpointAsync(shellState, programState, cancellationToken).ConfigureAwait(false); } Dictionary thisRequestHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (InputElement header in commandInput.Options[HeaderOption]) { int equalsIndex = header.Text.IndexOfAny(HeaderSeparatorChars); if (equalsIndex < 0) { shellState.ConsoleManager.Error.WriteLine(Strings.BaseHttpCommand_Error_HeaderFormatting.SetColor(programState.ErrorColor)); return; } thisRequestHeaders[header.Text.Substring(0, equalsIndex)] = header.Text.Substring(equalsIndex + 1); } Uri effectivePath = programState.GetEffectivePath(commandInput.Arguments.Count > 0 ? commandInput.Arguments[0].Text : string.Empty); using (HttpRequestMessage request = new HttpRequestMessage(new HttpMethod(Verb.ToUpperInvariant()), effectivePath)) { if (RequiresBody) { if (!HandleRequiresBody(commandInput, shellState, programState, request, thisRequestHeaders)) { // HandleRequiresBody can fail if there is a problem with the specified file or the specified editor, // in which case we should bail out before trying to send the request. return; } } foreach (KeyValuePair> header in programState.Headers) { // We only want to add headers that are not content headers if (!WellKnownHeaders.ContentHeaders.Contains(header.Key, StringComparer.OrdinalIgnoreCase)) { request.Headers.TryAddWithoutValidation(header.Key, header.Value); } } foreach (KeyValuePair header in thisRequestHeaders) { // We only want to add headers that are not content headers if (!WellKnownHeaders.ContentHeaders.Contains(header.Key, StringComparer.OrdinalIgnoreCase)) { request.Headers.TryAddWithoutValidation(header.Key, header.Value); } } InputElement responseHeadersFileOption = commandInput.Options[ResponseHeadersFileOption].Any() ? commandInput.Options[ResponseHeadersFileOption][0] : null; InputElement responseBodyFileOption = commandInput.Options[ResponseBodyFileOption].Any() ? commandInput.Options[ResponseBodyFileOption][0] : null; string headersTarget = responseHeadersFileOption?.Text; string bodyTarget = responseBodyFileOption?.Text; if (!string.IsNullOrWhiteSpace(headersTarget) && string.Equals(headersTarget, bodyTarget, StringComparison.OrdinalIgnoreCase)) { shellState.ConsoleManager.Error.WriteLine(Strings.BaseHttpCommand_Error_SameBodyAndHeaderFileName.SetColor(programState.ErrorColor)); return; } try { HttpResponseMessage response = await programState.Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); await HandleResponseAsync(programState, commandInput, shellState.ConsoleManager, response, programState.EchoRequest, headersTarget, bodyTarget, cancellationToken).ConfigureAwait(false); } catch (HttpRequestException httpRequestException) { shellState.ConsoleManager.Error.WriteLine(httpRequestException.Message.SetColor(programState.ErrorColor)); } catch (OperationCanceledException) { // We just want to eat this exception because the cancellation actually occurs for the entire command, // not just the HTTP Request. So the cancellation is handled further down the stack by inspecting // the CancellationToken.IsCancellationRequested property } } } private async Task CreateDirectoryStructureForSwaggerEndpointAsync(IShellState shellState, HttpState programState, CancellationToken cancellationToken) { string swaggerRequeryBehaviorSetting = _preferences.GetValue(WellKnownPreference.SwaggerRequeryBehavior, "auto"); if (swaggerRequeryBehaviorSetting.StartsWith("auto", StringComparison.OrdinalIgnoreCase)) { ApiConnection apiConnection = new ApiConnection(programState, _preferences, shellState.ConsoleManager, false) { BaseUri = programState.BaseAddress, SwaggerUri = programState.SwaggerEndpoint, AllowBaseOverrideBySwagger = false }; await apiConnection.SetupHttpState(programState, performAutoDetect: false, persistHeaders: true, persistPath: true, cancellationToken).ConfigureAwait(false); } } private bool HandleRequiresBody(DefaultCommandInput commandInput, IShellState shellState, HttpState programState, HttpRequestMessage request, Dictionary requestHeaders) { string filePath = null; string bodyContent = null; bool deleteFile = false; bool noBody = commandInput.Options[NoBodyOption].Count > 0; if (!requestHeaders.TryGetValue("content-type", out string contentType) && programState.Headers.TryGetValue("content-type", out IEnumerable contentTypes)) { contentType = contentTypes.FirstOrDefault(); } if (!noBody) { if (string.IsNullOrEmpty(contentType)) { contentType = "application/json"; } if (commandInput.Options[BodyFileOption].Count > 0) { filePath = commandInput.Options[BodyFileOption][0].Text; if (!_fileSystem.FileExists(filePath)) { shellState.ConsoleManager.Error.WriteLine(string.Format(Strings.BaseHttpCommand_Error_ContentFileDoesNotExist, filePath).SetColor(programState.ErrorColor)); return false; } } else if (commandInput.Options[BodyContentOption].Count > 0) { bodyContent = commandInput.Options[BodyContentOption][0].Text; } else { string defaultEditorCommand = _preferences.GetValue(WellKnownPreference.DefaultEditorCommand, null); if (string.IsNullOrWhiteSpace(defaultEditorCommand)) { shellState.ConsoleManager.Error.WriteLine(string.Format(Strings.BaseHttpCommand_Error_DefaultEditorNotConfigured, WellKnownPreference.DefaultEditorCommand).SetColor(programState.ErrorColor)); return false; } else if (!_fileSystem.FileExists(defaultEditorCommand)) { shellState.ConsoleManager.Error.WriteLine(string.Format(Strings.BaseHttpCommand_Error_DefaultEditorDoesNotExist, defaultEditorCommand).SetColor(programState.ErrorColor)); return false; } deleteFile = true; filePath = _fileSystem.GetTempFileName(GetFileExtensionFromContentType(contentType)); string exampleBody = GetExampleBody(commandInput.Arguments.Count > 0 ? commandInput.Arguments[0].Text : string.Empty, ref contentType, Verb, programState); if (!string.IsNullOrEmpty(exampleBody)) { _fileSystem.WriteAllTextToFile(filePath, exampleBody); } string defaultEditorArguments = _preferences.GetValue(WellKnownPreference.DefaultEditorArguments, null) ?? ""; string original = defaultEditorArguments; string pathString = $"\"{filePath}\""; defaultEditorArguments = defaultEditorArguments.Replace("{filename}", pathString, StringComparison.OrdinalIgnoreCase); if (string.Equals(defaultEditorArguments, original, StringComparison.Ordinal)) { defaultEditorArguments = (defaultEditorArguments + " " + pathString).Trim(); } ProcessStartInfo info = new ProcessStartInfo(defaultEditorCommand, defaultEditorArguments); Process.Start(info)?.WaitForExit(); } } if (string.IsNullOrEmpty(contentType)) { contentType = "application/json"; } byte[] data = noBody ? Array.Empty() : string.IsNullOrEmpty(bodyContent) ? _fileSystem.ReadAllBytesFromFile(filePath) : Encoding.UTF8.GetBytes(bodyContent); HttpContent content = new ByteArrayContent(data); content.Headers.ContentType = new MediaTypeHeaderValue(contentType); request.Content = content; if (deleteFile) { _fileSystem.DeleteFile(filePath); } AddHttpContentHeaders(content, programState, requestHeaders); return true; } private static string GetFileExtensionFromContentType(string contentType) { if (FileExtensionLookup.TryGetValue(contentType, out string extension)) { return extension; } return ".tmp"; } private static void AddHttpContentHeaders(HttpContent content, HttpState programState, Dictionary requestHeaders) { foreach (KeyValuePair> header in programState.Headers) { // We only want to add content headers, except for Content-Type, which is handled elsewhere if (WellKnownHeaders.ContentHeaders.Contains(header.Key, StringComparer.OrdinalIgnoreCase) && !string.Equals(WellKnownHeaders.ContentType, header.Key, StringComparison.OrdinalIgnoreCase)) { content.Headers.TryAddWithoutValidation(header.Key, header.Value); } } foreach (KeyValuePair header in requestHeaders) { // We only want to add content headers, except for Content-Type, which is handled elsewhere if (WellKnownHeaders.ContentHeaders.Contains(header.Key, StringComparer.OrdinalIgnoreCase) && !string.Equals(WellKnownHeaders.ContentType, header.Key, StringComparison.OrdinalIgnoreCase)) { content.Headers.TryAddWithoutValidation(header.Key, header.Value); } } } private async Task HandleResponseAsync(HttpState programState, DefaultCommandInput commandInput, IConsoleManager consoleManager, HttpResponseMessage response, bool echoRequest, string headersTargetFile, string bodyTargetFile, CancellationToken cancellationToken) { string protocolInfo; if (echoRequest) { RequestConfig requestConfig = new RequestConfig(_preferences); string hostString = response.RequestMessage.RequestUri.Scheme + "://" + response.RequestMessage.RequestUri.Host + (!response.RequestMessage.RequestUri.IsDefaultPort ? ":" + response.RequestMessage.RequestUri.Port : ""); await HandleEchoRequest(commandInput, consoleManager, programState, response, requestConfig, hostString, cancellationToken); // Only need to write out this separator if we've echoed the request consoleManager.WriteLine(); consoleManager.WriteLine($"Response from {hostString}...".SetColor(requestConfig.AddressColor)); consoleManager.WriteLine(); } ResponseConfig responseConfig = new ResponseConfig(_preferences); protocolInfo = $"{"HTTP".SetColor(responseConfig.ProtocolNameColor)}{"/".SetColor(responseConfig.ProtocolSeparatorColor)}{response.Version.ToString().SetColor(responseConfig.ProtocolVersionColor)}"; string status = ((int)response.StatusCode).ToString().SetColor(responseConfig.StatusCodeColor) + " " + response.ReasonPhrase.SetColor(responseConfig.StatusReasonPhraseColor); consoleManager.WriteLine($"{protocolInfo} {status}"); IEnumerable>> responseHeaders = response.Headers; if (response.Content != null) { responseHeaders = responseHeaders.Union(response.Content.Headers); } List headerFileOutput = null; List bodyFileOutput = null; if (headersTargetFile != null) { headerFileOutput = new List(); } foreach (KeyValuePair> header in responseHeaders.OrderBy(x => x.Key)) { string headerKey = header.Key.SetColor(responseConfig.HeaderKeyColor); string headerSep = ":".SetColor(responseConfig.HeaderSeparatorColor); string headerValue = string.Join(";".SetColor(responseConfig.HeaderValueSeparatorColor), header.Value.Select(x => x.Trim().SetColor(responseConfig.HeaderValueColor))); consoleManager.WriteLine($"{headerKey}{headerSep} {headerValue}"); headerFileOutput?.Add($"{header.Key}: {string.Join(";", header.Value.Select(x => x.Trim()))}"); } if (bodyTargetFile != null) { bodyFileOutput = new List(); } consoleManager.WriteLine(); if (response.Content != null) { await FormatBodyAsync(commandInput, programState, consoleManager, response.Content, bodyFileOutput, _preferences, cancellationToken).ConfigureAwait(false); } if (headersTargetFile != null && headerFileOutput != null) { _fileSystem.WriteAllLinesToFile(headersTargetFile, headerFileOutput); } if (bodyTargetFile != null && bodyFileOutput != null) { _fileSystem.WriteAllLinesToFile(bodyTargetFile, bodyFileOutput); } consoleManager.WriteLine(); } private async Task HandleEchoRequest(DefaultCommandInput commandInput, IConsoleManager consoleManager, HttpState programState, HttpResponseMessage response, RequestConfig requestConfig, string hostString, CancellationToken cancellationToken) { consoleManager.WriteLine($"Request to {hostString}...".SetColor(requestConfig.AddressColor)); consoleManager.WriteLine(); string method = response.RequestMessage.Method.ToString().ToUpperInvariant().SetColor(requestConfig.MethodColor); string pathAndQuery = response.RequestMessage.RequestUri.PathAndQuery.SetColor(requestConfig.AddressColor); string protocolInfo = $"{"HTTP".SetColor(requestConfig.ProtocolNameColor)}{"/".SetColor(requestConfig.ProtocolSeparatorColor)}{response.Version.ToString().SetColor(requestConfig.ProtocolVersionColor)}"; consoleManager.WriteLine($"{method} {pathAndQuery} {protocolInfo}"); IEnumerable>> requestHeaders = response.RequestMessage.Headers; if (response.RequestMessage.Content != null) { requestHeaders = requestHeaders.Union(response.RequestMessage.Content.Headers); } foreach (KeyValuePair> header in requestHeaders.OrderBy(x => x.Key)) { string headerKey = header.Key.SetColor(requestConfig.HeaderKeyColor); string headerSep = ":".SetColor(requestConfig.HeaderSeparatorColor); string headerValue = string.Join(";".SetColor(requestConfig.HeaderValueSeparatorColor), header.Value.Select(x => x.Trim().SetColor(requestConfig.HeaderValueColor))); consoleManager.WriteLine($"{headerKey}{headerSep} {headerValue}"); } consoleManager.WriteLine(); List responseOutput = new List(); if (response.RequestMessage.Content != null) { await FormatBodyAsync(commandInput, programState, consoleManager, response.RequestMessage.Content, responseOutput, _preferences, cancellationToken).ConfigureAwait(false); } } private static async Task FormatBodyAsync(DefaultCommandInput commandInput, HttpState programState, IConsoleManager consoleManager, HttpContent content, List bodyFileOutput, IPreferences preferences, CancellationToken cancellationToken) { if (commandInput.Options[StreamingOption].Count > 0) { Memory buffer = new Memory(new char[2048]); #if NET5_0_OR_GREATER Stream s = await content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); #else Stream s = await content.ReadAsStreamAsync().ConfigureAwait(false); #endif using (StreamReader reader = new StreamReader(s)) { consoleManager.WriteLine(Resources.Strings.BaseHttpCommand_FormatBodyAsync_Streaming.SetColor(programState.WarningColor)); while (!cancellationToken.IsCancellationRequested) { try { Task readTask = reader.ReadAsync(buffer, cancellationToken).AsTask(); if (await WaitForCompletionAsync(readTask, cancellationToken).ConfigureAwait(false)) { if (readTask.Result == 0) { break; } string str = new string(buffer.Span.Slice(0, readTask.Result)); consoleManager.Write(str); bodyFileOutput.Add(str); } else { break; } } catch (OperationCanceledException) { } } } return; } await FormatResponseContentAsync(commandInput, consoleManager, content, bodyFileOutput, preferences); } private static async Task FormatResponseContentAsync(DefaultCommandInput commandInput, IConsoleManager consoleManager, HttpContent content, List bodyFileOutput, IPreferences preferences) { string contentType = null; if (content.Headers.TryGetValues("Content-Type", out IEnumerable contentTypeValues)) { contentType = contentTypeValues.FirstOrDefault()?.Split(';').FirstOrDefault(); } contentType = contentType?.ToUpperInvariant() ?? "text/plain"; if (commandInput.Options[NoFormattingOption].Count == 0) { if (contentType.EndsWith("/JSON", StringComparison.OrdinalIgnoreCase) || contentType.EndsWith("-JSON", StringComparison.OrdinalIgnoreCase) || contentType.EndsWith("+JSON", StringComparison.OrdinalIgnoreCase) || contentType.EndsWith("/JAVASCRIPT", StringComparison.OrdinalIgnoreCase) || contentType.EndsWith("-JAVASCRIPT", StringComparison.OrdinalIgnoreCase) || contentType.EndsWith("+JAVASCRIPT", StringComparison.OrdinalIgnoreCase)) { if (await FormatJsonAsync(consoleManager, content, bodyFileOutput, preferences)) { return; } } else if (contentType.EndsWith("/HTML", StringComparison.OrdinalIgnoreCase) || contentType.EndsWith("-HTML", StringComparison.OrdinalIgnoreCase) || contentType.EndsWith("+HTML", StringComparison.OrdinalIgnoreCase) || contentType.EndsWith("/XML", StringComparison.OrdinalIgnoreCase) || contentType.EndsWith("-XML", StringComparison.OrdinalIgnoreCase) || contentType.EndsWith("+XML", StringComparison.OrdinalIgnoreCase)) { if (await FormatXmlAsync(consoleManager, content, bodyFileOutput)) { return; } } } string responseContent = await content.ReadAsStringAsync().ConfigureAwait(false); bodyFileOutput?.Add(responseContent); consoleManager.WriteLine(responseContent); } private static async Task WaitForCompletionAsync(Task readTask, CancellationToken cancellationToken) { while (!readTask.IsCompleted && !cancellationToken.IsCancellationRequested && !Console.KeyAvailable) { await Task.Delay(1, cancellationToken).ConfigureAwait(false); } if (Console.KeyAvailable) { Console.ReadKey(false); return false; } return readTask.IsCompleted; } private static async Task FormatXmlAsync(IWritable consoleManager, HttpContent content, List bodyFileOutput) { string responseContent = await content.ReadAsStringAsync().ConfigureAwait(false); try { XDocument body = XDocument.Parse(responseContent); consoleManager.WriteLine(body.ToString()); bodyFileOutput?.Add(body.ToString()); return true; } catch { } return false; } private static async Task FormatJsonAsync(IWritable outputSink, HttpContent content, List bodyFileOutput, IPreferences preferences) { string responseContent = await content.ReadAsStringAsync().ConfigureAwait(false); try { JsonConfig config = new JsonConfig(preferences); string formatted = JsonVisitor.FormatAndColorize(config, responseContent); outputSink.WriteLine(formatted); bodyFileOutput?.Add(JToken.Parse(responseContent).ToString()); return true; } catch { } return false; } protected override string GetHelpDetails(IShellState shellState, HttpState programState, DefaultCommandInput commandInput, ICoreParseResult parseResult) { StringBuilder helpText = new StringBuilder(); helpText.Append(Strings.Usage.Bold()); helpText.AppendLine($"{Verb.ToUpperInvariant()} [Options]"); helpText.AppendLine(); helpText.AppendLine($"Issues a {Verb.ToUpperInvariant()} request."); if (RequiresBody) { helpText.AppendLine("Your default editor will be opened with a sample body if no options are provided."); } return helpText.ToString(); } public override string GetHelpSummary(IShellState shellState, HttpState programState) { #pragma warning disable CA1308 // Normalize strings to uppercase return $"{Verb.ToLowerInvariant()} - Issues a {Verb.ToUpperInvariant()} request"; #pragma warning restore CA1308 // Normalize strings to uppercase } protected override IEnumerable GetArgumentSuggestionsForText(IShellState shellState, HttpState programState, ICoreParseResult parseResult, DefaultCommandInput commandInput, string normalCompletionString) { programState = programState ?? throw new ArgumentNullException(nameof(programState)); List results = new List(); if (programState.Structure is object && programState.BaseAddress is object) { parseResult = parseResult ?? throw new ArgumentNullException(nameof(parseResult)); //If it's an absolute URI, nothing to suggest if (Uri.TryCreate(parseResult.Sections[1], UriKind.Absolute, out Uri _)) { return null; } normalCompletionString = normalCompletionString ?? throw new ArgumentNullException(nameof(normalCompletionString)); string path = normalCompletionString.Replace('\\', '/'); int searchFrom = normalCompletionString.Length - 1; int lastSlash = path.LastIndexOf('/', searchFrom); string prefix; if (lastSlash < 0) { path = string.Empty; prefix = normalCompletionString; } else { path = path.Substring(0, lastSlash + 1); prefix = normalCompletionString.Substring(lastSlash + 1); } IDirectoryStructure s = programState.Structure.TraverseTo(programState.PathSections.Reverse()).TraverseTo(path); foreach (string child in s.DirectoryNames) { if (child.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { results.Add(path + child); } } } return results; } protected override IEnumerable GetOptionValueCompletions(IShellState shellState, HttpState programState, string optionId, DefaultCommandInput commandInput, ICoreParseResult parseResult, string normalizedCompletionText) { if (string.Equals(optionId, BodyFileOption, StringComparison.Ordinal) || string.Equals(optionId, ResponseBodyFileOption, StringComparison.OrdinalIgnoreCase) || string.Equals(optionId, ResponseHeadersFileOption, StringComparison.OrdinalIgnoreCase)) { return FileSystemCompletion.GetCompletions(normalizedCompletionText); } if (string.Equals(optionId, HeaderOption, StringComparison.Ordinal)) { commandInput = commandInput ?? throw new ArgumentNullException(nameof(commandInput)); normalizedCompletionText = normalizedCompletionText ?? throw new ArgumentNullException(nameof(normalizedCompletionText)); HashSet alreadySpecifiedHeaders = new HashSet(StringComparer.Ordinal); IReadOnlyList options = commandInput.Options[HeaderOption]; for (int i = 0; i < options.Count; ++i) { if (options[i] == commandInput.SelectedElement) { continue; } string elementText = options[i].Text; string existingHeaderName = elementText.Split(HeaderSeparatorChars)[0]; alreadySpecifiedHeaders.Add(existingHeaderName); } //Check to see if the selected element is in a header name or value int equalsIndex = normalizedCompletionText.IndexOfAny(HeaderSeparatorChars); string path = commandInput.Arguments.Count > 0 ? commandInput.Arguments[0].Text : string.Empty; if (equalsIndex < 0) { IEnumerable headerNameOptions = HeaderCompletion.GetCompletions(alreadySpecifiedHeaders, normalizedCompletionText); List allSuggestions = new List(); foreach (string suggestion in headerNameOptions.Select(x => x)) { allSuggestions.Add(suggestion + ":"); IEnumerable suggestions = HeaderCompletion.GetValueCompletions(Verb, path, suggestion, string.Empty, programState); if (suggestions != null) { foreach (string valueSuggestion in suggestions) { allSuggestions.Add(suggestion + ":" + valueSuggestion); } } } return allSuggestions; } else { //Didn't exit from the header name check, so must be a value string headerName = normalizedCompletionText.Substring(0, equalsIndex); IEnumerable suggestions = HeaderCompletion.GetValueCompletions(Verb, path, headerName, normalizedCompletionText.Substring(equalsIndex + 1), programState); if (suggestions == null) { return null; } return suggestions.Select(x => normalizedCompletionText.Substring(0, equalsIndex + 1) + x); } } return null; } private static string GetExampleBody(string path, ref string contentType, string method, HttpState httpState) { Uri effectivePath = httpState.GetEffectivePath(path); string rootRelativePath = effectivePath.LocalPath.Substring(httpState.BaseAddress.LocalPath.Length).TrimStart('/'); IDirectoryStructure structure = httpState.Structure?.TraverseTo(rootRelativePath); return structure?.RequestInfo?.GetRequestBodyForContentType(ref contentType, method); } private void SendTelemetry(DefaultCommandInput commandInput) { HttpCommandEvent httpCommandEvent = new HttpCommandEvent( method: Verb.ToUpperInvariant(), isPathSpecified: commandInput.Arguments.Count > 0, isHeaderSpecified: commandInput.Options[HeaderOption].Any(), isResponseHeadersFileSpecified: commandInput.Options[ResponseHeadersFileOption].Any(), isResponseBodyFileSpecified: commandInput.Options[ResponseBodyFileOption].Any(), isNoFormattingSpecified: commandInput.Options[NoFormattingOption].Any(), isStreamingSpecified: commandInput.Options[StreamingOption].Any(), isNoBodySpecified: RequiresBody && commandInput.Options[NoBodyOption].Any(), isRequestBodyFileSpecified: RequiresBody && commandInput.Options[BodyFileOption].Any(), isRequestBodyContentSpecified: RequiresBody && commandInput.Options[BodyContentOption].Any() ); _telemetry.TrackEvent(httpCommandEvent); } } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/ChangeDirectoryCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.HttpRepl.Resources; using Microsoft.HttpRepl.Suggestions; using Microsoft.Repl; using Microsoft.Repl.Commanding; using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Parsing; namespace Microsoft.HttpRepl.Commands { public class ChangeDirectoryCommand : CommandWithStructuredInputBase { public override string Name => "changeDirectory"; protected override Task ExecuteAsync(IShellState shellState, HttpState programState, DefaultCommandInput commandInput, ICoreParseResult parseResult, CancellationToken cancellationToken) { commandInput = commandInput ?? throw new ArgumentNullException(nameof(commandInput)); shellState = shellState ?? throw new ArgumentNullException(nameof(shellState)); programState = programState ?? throw new ArgumentNullException(nameof(programState)); if (commandInput.Arguments.Count == 0 || string.IsNullOrEmpty(commandInput.Arguments[0]?.Text)) { shellState.ConsoleManager.WriteLine(programState.GetRelativePathString()); } else { string[] parts = commandInput.Arguments[0].Text.Replace('\\', '/').Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); if (commandInput.Arguments[0].Text.StartsWith("/", StringComparison.Ordinal)) { programState.PathSections.Clear(); } foreach (string part in parts) { switch (part) { case ".": break; case "..": if (programState.PathSections.Count > 0) { programState.PathSections.Pop(); } break; default: programState.PathSections.Push(part); break; } } // If there's no directory structure, we can't traverse it to find the relevant // metadata and display it. The command still succeeded as far as its impact on // future commands, so we can safely just skip this part. if (programState.Structure != null) { IDirectoryStructure s = programState.Structure.TraverseTo(programState.PathSections.Reverse()); string thisDirMethod = "[]"; bool hasRequestMethods = s.RequestInfo?.Methods?.Count > 0; bool hasDirectoryNames = s.DirectoryNames?.Any() == true; // If there's no RequestInfo/Methods AND there's no (sub)DirectoryNames, we currently // assume this must be an auto-generated directory and not one from a swagger definition if (!hasRequestMethods && !hasDirectoryNames) { string warningMessage = string.Format(Resources.Strings.ChangeDirectoryCommand_Warning_UnknownEndpoint, programState.GetRelativePathString()).SetColor(programState.WarningColor); shellState.ConsoleManager.WriteLine(warningMessage); } else if (hasRequestMethods) { thisDirMethod = s.RequestInfo.GetDirectoryMethodListing(); } shellState.ConsoleManager.WriteLine($"{programState.GetRelativePathString()} {thisDirMethod}"); } } return Task.CompletedTask; } public override CommandInputSpecification InputSpec { get; } = CommandInputSpecification.Create("cd") .MaximumArgCount(1) .Finish(); protected override string GetHelpDetails(IShellState shellState, HttpState programState, DefaultCommandInput commandInput, ICoreParseResult parseResult) { var help = new StringBuilder(); help.Append(Strings.Usage.Bold()); help.AppendLine("cd [directory]"); help.AppendLine(); help.AppendLine("Prints the current directory if no argument is specified, otherwise changes to the specified directory"); return help.ToString(); } public override string GetHelpSummary(IShellState shellState, HttpState programState) { return Resources.Strings.ChangeDirectoryCommand_HelpSummary; } protected override IEnumerable GetArgumentSuggestionsForText(IShellState shellState, HttpState programState, ICoreParseResult parseResult, DefaultCommandInput commandInput, string normalCompletionString) { return ServerPathCompletion.GetCompletions(programState, normalCompletionString); } } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/ClearCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.Repl; using Microsoft.Repl.Commanding; using Microsoft.Repl.Parsing; namespace Microsoft.HttpRepl.Commands { public class ClearCommand : ICommand { private const string AlternateName = "cls"; public string Name => "clear"; public bool? CanHandle(IShellState shellState, object programState, ICoreParseResult parseResult) { return parseResult.ContainsExactly(Name) || parseResult.ContainsExactly(AlternateName) ? (bool?) true : null; } public Task ExecuteAsync(IShellState shellState, object programState, ICoreParseResult parseResult, CancellationToken cancellationToken) { shellState = shellState ?? throw new ArgumentNullException(nameof(shellState)); shellState.ConsoleManager.Clear(); shellState.CommandDispatcher.OnReady(shellState); return Task.CompletedTask; } public string GetHelpDetails(IShellState shellState, object programState, ICoreParseResult parseResult) { if (parseResult.ContainsExactly(Name) || parseResult.ContainsExactly(AlternateName)) { return "Clears the shell"; } return null; } public string GetHelpSummary(IShellState shellState, object programState) { return Resources.Strings.ClearCommand_HelpSummary; } public IEnumerable Suggest(IShellState shellState, object programState, ICoreParseResult parseResult) { parseResult = parseResult ?? throw new ArgumentNullException(nameof(parseResult)); if (parseResult.SelectedSection == 0) { bool nameMatch = string.IsNullOrEmpty(parseResult.Sections[parseResult.SelectedSection]) || Name.StartsWith(parseResult.Sections[0].Substring(0, parseResult.CaretPositionWithinSelectedSection), StringComparison.OrdinalIgnoreCase); bool alternateNameMatch = string.IsNullOrEmpty(parseResult.Sections[parseResult.SelectedSection]) || AlternateName.StartsWith(parseResult.Sections[0].Substring(0, parseResult.CaretPositionWithinSelectedSection), StringComparison.OrdinalIgnoreCase); if (nameMatch && alternateNameMatch) { return new[] { Name, AlternateName }; } else if (nameMatch) { return new[] { Name }; } else if (alternateNameMatch) { return new[] { AlternateName }; } } return null; } } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/ClearQueryParamCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.HttpRepl.Resources; using Microsoft.HttpRepl.Telemetry; using Microsoft.HttpRepl.Telemetry.Events; using Microsoft.Repl; using Microsoft.Repl.Commanding; using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Parsing; namespace Microsoft.HttpRepl.Commands { public class ClearQueryParamCommand : ICommand { public string Name => "clearQueryParam"; private const string CommandName = "clear"; private const string SubCommand = "query-param"; public static string Description => Strings.ClearQueryParamCommand_HelpDetails; private readonly ITelemetry _telemetry; public ClearQueryParamCommand(ITelemetry telemetry) { _telemetry = telemetry; } public bool? CanHandle(IShellState shellState, HttpState programState, ICoreParseResult parseResult) => parseResult.ContainsAtLeast(minimumLength: 2, CommandName, SubCommand) ? (bool?)true : null; public Task ExecuteAsync(IShellState shellState, HttpState programState, ICoreParseResult parseResult, CancellationToken cancellationToken) { parseResult = parseResult ?? throw new ArgumentNullException(nameof(parseResult)); programState = programState ?? throw new ArgumentNullException(nameof(programState)); int sectionCount = parseResult.Sections.Count; bool isValueEmpty; if (sectionCount == 2) { programState.QueryParam.Clear(); isValueEmpty = true; } else { isValueEmpty = false; } _telemetry.TrackEvent(new ClearQueryParamEvent(parseResult.Sections[1], isValueEmpty)); return Task.CompletedTask; } public string GetHelpDetails(IShellState shellState, HttpState programState, ICoreParseResult parseResult) { if (parseResult.ContainsAtLeast(CommandName, SubCommand)) { StringBuilder helpText = new StringBuilder(); helpText.Append(Strings.Usage.Bold()); helpText.AppendLine("clear query-param"); helpText.AppendLine(); helpText.AppendLine(Strings.ClearQueryParamCommand_HelpDetails); return helpText.ToString(); } return null; } public string GetHelpSummary(IShellState shellState, HttpState programState) => Description; public IEnumerable Suggest(IShellState shellState, HttpState programState, ICoreParseResult parseResult) => null; } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/ConnectCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.HttpRepl.Preferences; using Microsoft.HttpRepl.Telemetry; using Microsoft.HttpRepl.Telemetry.Events; using Microsoft.Repl; using Microsoft.Repl.Commanding; using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Parsing; namespace Microsoft.HttpRepl.Commands { public class ConnectCommand : CommandWithStructuredInputBase { private const string BaseAddressOption = nameof(BaseAddressOption); private const string SwaggerAddressOption = nameof(SwaggerAddressOption); private const string VerbosityOption = nameof(VerbosityOption); private const string PersistHeadersOption = nameof(PersistHeadersOption); private const string PersistPathOption = nameof(PersistPathOption); public override string Name => "connect"; private const string WebApiDefaultPathSuffix = "/swagger/"; private readonly IPreferences _preferences; private readonly ITelemetry _telemetry; public ConnectCommand(IPreferences preferences, ITelemetry telemetry) { _preferences = preferences; _telemetry = telemetry; } public override CommandInputSpecification InputSpec => CommandInputSpecification.Create("connect") .MinimumArgCount(0) .MaximumArgCount(1) .WithOption(new CommandOptionSpecification(id: BaseAddressOption, requiresValue: true, minimumOccurrences: 0, maximumOccurrences: 1, forms: new[] { "--base", "-b" })) .WithOption(new CommandOptionSpecification(id: SwaggerAddressOption, requiresValue: true, minimumOccurrences: 0, maximumOccurrences: 1, forms: new[] { "--openapi", "-o", "--swagger", "-s" })) .WithOption(new CommandOptionSpecification(id: VerbosityOption, acceptsValue: false, minimumOccurrences: 0, maximumOccurrences: 1, forms: new[] { "--verbose", "-v" })) .WithOption(new CommandOptionSpecification(id: PersistHeadersOption, maximumOccurrences: 1, forms: new[] { "--persist-headers"})) .WithOption(new CommandOptionSpecification(id: PersistPathOption, maximumOccurrences: 1, forms: new[] { "--persist-path"})) .Finish(); public override string GetHelpSummary(IShellState shellState, HttpState programState) { return Resources.Strings.ConnectCommand_Description; } protected override string GetHelpDetails(IShellState shellState, HttpState programState, DefaultCommandInput commandInput, ICoreParseResult parseResult) { if (parseResult.ContainsAtLeast(Name)) { StringBuilder helpText = new StringBuilder(); helpText.Append(Resources.Strings.Usage.Bold()); helpText.AppendLine("connect [rootAddress] [--base baseAddress] [--openapi openApiDescriptionAddress] [--verbose] [--persist-headers] [--persist-paths]"); helpText.AppendLine(); helpText.AppendLine(Resources.Strings.ConnectCommand_HelpDetails_Line1); helpText.AppendLine(); helpText.AppendLine(Resources.Strings.ConnectCommand_HelpDetails_Line2); helpText.AppendLine(Resources.Strings.ConnectCommand_HelpDetails_Line3); helpText.AppendLine(Resources.Strings.ConnectCommand_HelpDetails_Line4); helpText.AppendLine(Resources.Strings.ConnectCommand_HelpDetails_Line5); helpText.AppendLine(Resources.Strings.ConnectCommand_HelpDetails_Line6); return helpText.ToString(); } return null; } protected override async Task ExecuteAsync(IShellState shellState, HttpState programState, DefaultCommandInput commandInput, ICoreParseResult parseResult, CancellationToken cancellationToken) { commandInput = commandInput ?? throw new ArgumentNullException(nameof(commandInput)); shellState = shellState ?? throw new ArgumentNullException(nameof(shellState)); programState = programState ?? throw new ArgumentNullException(nameof(programState)); string rootAddress = commandInput.Arguments.SingleOrDefault()?.Text?.EnsureTrailingSlash(); string baseAddress = GetBaseAddressFromCommand(commandInput)?.EnsureTrailingSlash(); string swaggerAddress = GetSwaggerAddressFromCommand(commandInput); bool isVerbosityEnabled = GetOptionExistsFromCommand(commandInput, VerbosityOption); bool persistHeaders = GetOptionExistsFromCommand(commandInput, PersistHeadersOption); bool persistPath = GetOptionExistsFromCommand(commandInput, PersistPathOption); ApiConnection connectionInfo = GetConnectionInfo(shellState, programState, rootAddress, baseAddress, swaggerAddress, _preferences, isVerbosityEnabled); bool rootSpecified = !string.IsNullOrWhiteSpace(rootAddress); bool baseSpecified = !string.IsNullOrWhiteSpace(baseAddress); bool openApiSpecified = !string.IsNullOrWhiteSpace(swaggerAddress); if (connectionInfo is null) { _telemetry.TrackEvent(new ConnectEvent(baseSpecified, rootSpecified, openApiSpecified, openApiFound: false)); return; } await connectionInfo.SetupHttpState(programState, performAutoDetect: true, persistHeaders, persistPath, cancellationToken); bool openApiFound = connectionInfo?.HasSwaggerDocument == true; _telemetry.TrackEvent(new ConnectEvent(baseSpecified, rootSpecified, openApiSpecified, openApiFound)); WriteStatus(shellState, programState); } private static void WriteStatus(IShellState shellState, HttpState programState) { if (programState.BaseAddress is null) { shellState.ConsoleManager.WriteLine(Resources.Strings.ConnectCommand_Status_NoBase.SetColor(programState.WarningColor)); } else { shellState.ConsoleManager.WriteLine(string.Format(Resources.Strings.ConnectCommand_Status_Base, programState.BaseAddress)); } if (programState.SwaggerEndpoint is null) { shellState.ConsoleManager.WriteLine(Resources.Strings.ConnectCommand_Status_NoSwagger.SetColor(programState.WarningColor)); } else { shellState.ConsoleManager.WriteLine(string.Format(Resources.Strings.ConnectCommand_Status_Swagger, programState.SwaggerEndpoint)); } // Always show help link after connecting shellState.ConsoleManager.WriteLine(Resources.Strings.HelpCommand_Core_Details_Line2.Bold().Cyan()); } private ApiConnection GetConnectionInfo(IShellState shellState, HttpState programState, string rootAddress, string baseAddress, string swaggerAddress, IPreferences preferences, bool isVerbosityEnabled) { rootAddress = rootAddress?.Trim(); baseAddress = baseAddress?.Trim(); swaggerAddress = swaggerAddress?.Trim(); if (string.IsNullOrWhiteSpace(rootAddress) && string.IsNullOrWhiteSpace(baseAddress) && string.IsNullOrWhiteSpace(swaggerAddress)) { shellState.ConsoleManager.Error.WriteLine(Resources.Strings.ConnectCommand_Error_NothingSpecified); return null; } if (!string.IsNullOrWhiteSpace(rootAddress) && !Uri.IsWellFormedUriString(rootAddress, UriKind.Absolute)) { shellState.ConsoleManager.Error.WriteLine(Resources.Strings.ConnectCommand_Error_RootAddressNotValid); return null; } // Even if verbosity is not enabled, we still want to be verbose about finding OpenAPI Descriptions // if they specified one directly. bool logVerboseMessages = isVerbosityEnabled || !string.IsNullOrWhiteSpace(swaggerAddress); ApiConnection apiConnection = new ApiConnection(programState, preferences, shellState.ConsoleManager, logVerboseMessages); if (!string.IsNullOrWhiteSpace(rootAddress)) { // The `dotnet new webapi` template now has a default start url of `swagger`. Because // the default Swashbuckle-generated OpenAPI description doesn't contain a Servers element // this will put HttpRepl users into a pit of failure by having a base address of // https://localhost:{port}/swagger/, even though the API is, by default, based at the root. // Since it is unlikely a user would put their API inside the /swagger path, we will // special-case this scenario and remove that from the url. We will give the user an escape // hatch via the preference if they do put their API under that path. if (rootAddress.EndsWith(WebApiDefaultPathSuffix, StringComparison.OrdinalIgnoreCase)) { WebApiF5FixEvent fixEvent; if (preferences.GetBoolValue(WellKnownPreference.ConnectCommandSkipRootFix)) { fixEvent = new WebApiF5FixEvent(skippedByPreference: true); } else { rootAddress = rootAddress.Substring(0, rootAddress.Length - WebApiDefaultPathSuffix.Length); fixEvent = new WebApiF5FixEvent(); } _telemetry.TrackEvent(fixEvent); } apiConnection.RootUri = new Uri(rootAddress, UriKind.Absolute); } if (!SetupBaseAddress(shellState, baseAddress, apiConnection) || !SetupSwaggerAddress(shellState, swaggerAddress, apiConnection)) { return null; } apiConnection.AllowBaseOverrideBySwagger = !apiConnection.HasBaseUri; if (apiConnection.HasRootUri && !apiConnection.HasBaseUri) { apiConnection.BaseUri = apiConnection.RootUri; } return apiConnection; } private static bool SetupSwaggerAddress(IShellState shellState, string swaggerAddress, ApiConnection connectionInfo) { if (!string.IsNullOrWhiteSpace(swaggerAddress)) { if (!connectionInfo.HasRootUri && !Uri.IsWellFormedUriString(swaggerAddress, UriKind.Absolute)) { shellState.ConsoleManager.Error.WriteLine(Resources.Strings.ConnectCommand_Error_NoRootNoAbsoluteSwagger); return false; } else if (connectionInfo.HasRootUri && !Uri.IsWellFormedUriString(swaggerAddress, UriKind.RelativeOrAbsolute)) { shellState.ConsoleManager.Error.WriteLine(Resources.Strings.ConnectCommand_Error_InvalidSwagger); return false; } if (Uri.IsWellFormedUriString(swaggerAddress, UriKind.Absolute)) { connectionInfo.SwaggerUri = new Uri(swaggerAddress, UriKind.Absolute); } else if (Uri.IsWellFormedUriString(swaggerAddress, UriKind.Relative)) { connectionInfo.SwaggerUri = new Uri(connectionInfo.RootUri, swaggerAddress); } } return true; } private static bool SetupBaseAddress(IShellState shellState, string baseAddress, ApiConnection connectionInfo) { if (!string.IsNullOrWhiteSpace(baseAddress)) { if (!connectionInfo.HasRootUri && !Uri.IsWellFormedUriString(baseAddress, UriKind.Absolute)) { shellState.ConsoleManager.Error.WriteLine(Resources.Strings.ConnectCommand_Error_NoRootNoAbsoluteBase); return false; } else if (connectionInfo.HasRootUri && !Uri.IsWellFormedUriString(baseAddress, UriKind.RelativeOrAbsolute)) { shellState.ConsoleManager.Error.WriteLine(Resources.Strings.ConnectCommand_Error_InvalidBase); return false; } if (Uri.IsWellFormedUriString(baseAddress, UriKind.Absolute)) { connectionInfo.BaseUri = new Uri(baseAddress, UriKind.Absolute); } else if (Uri.IsWellFormedUriString(baseAddress, UriKind.Relative)) { connectionInfo.BaseUri = new Uri(connectionInfo.RootUri, baseAddress); } } return true; } private static string GetBaseAddressFromCommand(DefaultCommandInput commandInput) { return GetOptionValueFromCommand(commandInput, BaseAddressOption); } private static string GetSwaggerAddressFromCommand(DefaultCommandInput commandInput) { return GetOptionValueFromCommand(commandInput, SwaggerAddressOption); } private static string GetOptionValueFromCommand(DefaultCommandInput commandInput, string optionId) { if (commandInput.Options.TryGetValue(optionId, out IReadOnlyList inputElements)) { InputElement inputElement = inputElements.Any() ? inputElements[0] : null; return inputElement?.Text; } return null; } private static bool GetOptionExistsFromCommand(DefaultCommandInput commandInput, string optionId) { return commandInput.Options[optionId].Count > 0; } } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/DeleteCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using Microsoft.HttpRepl.FileSystem; using Microsoft.HttpRepl.Preferences; using Microsoft.HttpRepl.Telemetry; namespace Microsoft.HttpRepl.Commands { public class DeleteCommand : BaseHttpCommand { public DeleteCommand(IFileSystem fileSystem, IPreferences preferences, ITelemetry telemetry) : base(fileSystem, preferences, telemetry) { } protected override string Verb => "delete"; protected override bool RequiresBody => false; } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/EchoCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Repl; using Microsoft.Repl.Commanding; using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Parsing; namespace Microsoft.HttpRepl.Commands { public class EchoCommand : CommandWithStructuredInputBase { public override string Name => "echo"; private readonly HashSet _allowedModes = new HashSet(StringComparer.OrdinalIgnoreCase) {"on", "off"}; protected override bool CanHandle(IShellState shellState, HttpState programState, DefaultCommandInput commandInput) { commandInput = commandInput ?? throw new ArgumentNullException(nameof(commandInput)); shellState = shellState ?? throw new ArgumentNullException(nameof(shellState)); programState = programState ?? throw new ArgumentNullException(nameof(programState)); if (commandInput.Arguments.Count == 0 || !_allowedModes.Contains(commandInput.Arguments[0]?.Text)) { shellState.ConsoleManager.Error.WriteLine(Resources.Strings.EchoCommand_Error_AllowedModes.SetColor(programState.ErrorColor)); return false; } return true; } protected override Task ExecuteAsync(IShellState shellState, HttpState programState, DefaultCommandInput commandInput, ICoreParseResult parseResult, CancellationToken cancellationToken) { shellState = shellState ?? throw new ArgumentNullException(nameof(shellState)); commandInput = commandInput ?? throw new ArgumentNullException(nameof(commandInput)); programState = programState ?? throw new ArgumentNullException(nameof(programState)); bool turnOn = string.Equals(commandInput.Arguments[0].Text, "on", StringComparison.OrdinalIgnoreCase); programState.EchoRequest = turnOn; shellState.ConsoleManager.WriteLine("Request echoing is " + (turnOn ? "on" : "off")); return Task.CompletedTask; } public override CommandInputSpecification InputSpec { get; } = CommandInputSpecification.Create("echo").ExactArgCount(1).Finish(); protected override string GetHelpDetails(IShellState shellState, HttpState programState, DefaultCommandInput commandInput, ICoreParseResult parseResult) { var helpText = new StringBuilder(); helpText.Append(Resources.Strings.Usage.Bold()); helpText.AppendLine($"echo [on|off]"); helpText.AppendLine(); helpText.AppendLine($"Turns request echoing on or off. When request echoing is on we will display a text representation of requests made by the CLI."); return helpText.ToString(); } public override string GetHelpSummary(IShellState shellState, HttpState programState) { return Resources.Strings.EchoCommand_HelpSummary; } protected override IEnumerable GetArgumentSuggestionsForText(IShellState shellState, HttpState programState, ICoreParseResult parseResult, DefaultCommandInput commandInput, string normalCompletionString) { List result = _allowedModes.Where(x => x.StartsWith(normalCompletionString, StringComparison.OrdinalIgnoreCase)).ToList(); return result.Count > 0 ? result : null; } } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/ExitCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Repl; using Microsoft.Repl.Commanding; using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Parsing; namespace Microsoft.HttpRepl.Commands { public class ExitCommand : CommandWithStructuredInputBase { public override string Name => "exit"; protected override Task ExecuteAsync(IShellState shellState, object programState, DefaultCommandInput commandInput, ICoreParseResult parseResult, CancellationToken cancellationToken) { shellState = shellState ?? throw new ArgumentNullException(nameof(shellState)); shellState.IsExiting = true; return Task.CompletedTask; } public override CommandInputSpecification InputSpec { get; } = CommandInputSpecification.Create("exit").ExactArgCount(0).Finish(); protected override string GetHelpDetails(IShellState shellState, object programState, DefaultCommandInput commandInput, ICoreParseResult parseResult) { var helpText = new StringBuilder(); helpText.Append(Resources.Strings.Usage.Bold()); helpText.AppendLine($"exit"); helpText.AppendLine(); helpText.AppendLine($"Exits the shell"); return helpText.ToString(); } public override string GetHelpSummary(IShellState shellState, object programState) { return Resources.Strings.ExitCommand_HelpSummary; } } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/Formatter.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. namespace Microsoft.HttpRepl.Commands { public class Formatter { private int _prefix; private int _maxDepth; public void RegisterEntry(int prefixLength, int depth) { if (depth > _maxDepth) { _maxDepth = depth; } if (prefixLength > _prefix) { _prefix = prefixLength; } } public string Format(string prefix, string entry, int level) { string indent = "".PadRight(level * 4); return (indent + prefix).PadRight(_prefix + 3 + _maxDepth * 4) + entry; } } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/GetCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using Microsoft.HttpRepl.FileSystem; using Microsoft.HttpRepl.Preferences; using Microsoft.HttpRepl.Telemetry; namespace Microsoft.HttpRepl.Commands { public class GetCommand : BaseHttpCommand { public GetCommand(IFileSystem fileSystem, IPreferences preferences, ITelemetry telemetry) : base(fileSystem, preferences, telemetry) { } protected override string Verb => "get"; protected override bool RequiresBody => false; } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/HeadCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using Microsoft.HttpRepl.FileSystem; using Microsoft.HttpRepl.Preferences; using Microsoft.HttpRepl.Telemetry; namespace Microsoft.HttpRepl.Commands { public class HeadCommand : BaseHttpCommand { public HeadCommand(IFileSystem fileSystem, IPreferences preferences, ITelemetry telemetry) : base(fileSystem, preferences, telemetry) { } protected override string Verb => "head"; protected override bool RequiresBody => false; } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/HelpCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.HttpRepl.Resources; using Microsoft.HttpRepl.Suggestions; using Microsoft.HttpRepl.Telemetry; using Microsoft.Repl; using Microsoft.Repl.Commanding; using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Parsing; namespace Microsoft.HttpRepl.Commands { public class HelpCommand : ICommand { public string Name => "help"; public HelpCommand() { } public bool? CanHandle(IShellState shellState, HttpState programState, ICoreParseResult parseResult) { return parseResult.ContainsAtLeast(Name) ? (bool?)true : null; } public Task ExecuteAsync(IShellState shellState, HttpState programState, ICoreParseResult parseResult, CancellationToken cancellationToken) { shellState = shellState ?? throw new ArgumentNullException(nameof(shellState)); parseResult = parseResult ?? throw new ArgumentNullException(nameof(parseResult)); programState = programState ?? throw new ArgumentNullException(nameof(programState)); if (shellState.CommandDispatcher is ICommandDispatcher dispatcher) { if (parseResult.Sections.Count == 1) { CoreGetHelp(shellState, dispatcher, programState); } else { bool anyHelp = false; StringBuilder output = new StringBuilder(); if (parseResult.Slice(1) is ICoreParseResult continuationParseResult) { foreach (ICommand command in dispatcher.Commands) { string help = command.GetHelpDetails(shellState, programState, continuationParseResult); if (!string.IsNullOrEmpty(help)) { anyHelp = true; output.AppendLine(); output.AppendLine(help); CommandWithStructuredInputBase structuredCommand = GetStructuredCommand(command); if (structuredCommand is not null && structuredCommand.InputSpec.Options.Any()) { output.AppendLine(); output.AppendLine(Strings.Options.Bold()); foreach (CommandOptionSpecification option in structuredCommand.InputSpec.Options) { string optionText = string.Empty; foreach (string form in option.Forms) { if (!string.IsNullOrEmpty(optionText)) { optionText += "|"; } optionText += form; } output.AppendLine($" {optionText}"); } } break; } } } if (!anyHelp) { output.AppendLine(Strings.HelpCommand_Error_UnableToLocateHelpInfo); } shellState.ConsoleManager.Write(output.ToString()); } } return Task.CompletedTask; } public string GetHelpDetails(IShellState shellState, HttpState programState, ICoreParseResult parseResult) { parseResult = parseResult ?? throw new ArgumentNullException(nameof(parseResult)); if (parseResult.ContainsAtLeast(Name)) { if (parseResult.Sections.Count > 1) { return "Gets help about " + parseResult.Slice(1).CommandText; } else { return "Gets help"; } } return null; } public string GetHelpSummary(IShellState shellState, HttpState programState) { return "help - Gets help"; } public IEnumerable Suggest(IShellState shellState, HttpState programState, ICoreParseResult parseResult) { parseResult = parseResult ?? throw new ArgumentNullException(nameof(parseResult)); shellState = shellState ?? throw new ArgumentNullException(nameof(shellState)); if (parseResult.SelectedSection == 0 && (string.IsNullOrEmpty(parseResult.Sections[parseResult.SelectedSection]) || Name.StartsWith(parseResult.Sections[0].Substring(0, parseResult.CaretPositionWithinSelectedSection), StringComparison.OrdinalIgnoreCase))) { return new[] { Name }; } else if (parseResult.ContainsAtLeast(minimumLength: 2, Name)) { if (shellState.CommandDispatcher is ICommandDispatcher dispatcher && parseResult.Slice(1) is ICoreParseResult continuationParseResult) { HashSet suggestions = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (ICommand command in dispatcher.Commands) { IEnumerable commandSuggestions = command.Suggest(shellState, programState, continuationParseResult); if (commandSuggestions != null) { suggestions.UnionWith(commandSuggestions); } } if (continuationParseResult.SelectedSection == 0) { string normalizedCompletionText = continuationParseResult.Sections[0].Substring(0, continuationParseResult.CaretPositionWithinSelectedSection); IEnumerable completions = ServerPathCompletion.GetCompletions(programState, normalizedCompletionText); if (completions != null) { suggestions.UnionWith(completions); } } return suggestions.OrderBy(x => x, StringComparer.OrdinalIgnoreCase).ToList(); } } return null; } public static void CoreGetHelp(IShellState shellState, ICommandDispatcher dispatcher, HttpState programState) { shellState = shellState ?? throw new ArgumentNullException(nameof(shellState)); dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); const int navCommandColumn = -15; StringBuilder output = new StringBuilder(); output.AppendLine(); output.AppendLine(Strings.HelpCommand_Core_SetupCommands.Bold().Cyan()); output.AppendLine(Strings.HelpCommand_Core_SetupCommands_Description); output.AppendLine(); output.AppendLine($"{"connect",navCommandColumn}{GetCommand(dispatcher).GetHelpSummary(shellState, programState)}"); output.AppendLine($"{"set header",navCommandColumn}{GetCommand(dispatcher).GetHelpSummary(shellState, programState)}"); output.AppendLine($"{"add query-param",navCommandColumn}{GetCommand(dispatcher).GetHelpSummary(shellState, programState)}"); output.AppendLine($"{"clear query-param",navCommandColumn}{GetCommand(dispatcher).GetHelpSummary(shellState, programState)}"); output.AppendLine(); output.AppendLine(Strings.HelpCommand_Core_HttpCommands.Bold().Cyan()); output.AppendLine(Strings.HelpCommand_Core_HttpCommands_Description); output.AppendLine(); output.AppendLine($"{"GET",navCommandColumn}{GetCommand(dispatcher).GetHelpSummary(shellState, programState)}"); output.AppendLine($"{"POST",navCommandColumn}{GetCommand(dispatcher).GetHelpSummary(shellState, programState)}"); output.AppendLine($"{"PUT",navCommandColumn}{GetCommand(dispatcher).GetHelpSummary(shellState, programState)}"); output.AppendLine($"{"DELETE",navCommandColumn}{GetCommand(dispatcher).GetHelpSummary(shellState, programState)}"); output.AppendLine($"{"PATCH",navCommandColumn}{GetCommand(dispatcher).GetHelpSummary(shellState, programState)}"); output.AppendLine($"{"HEAD",navCommandColumn}{GetCommand(dispatcher).GetHelpSummary(shellState, programState)}"); output.AppendLine($"{"OPTIONS",navCommandColumn}{GetCommand(dispatcher).GetHelpSummary(shellState, programState)}"); output.AppendLine(); output.AppendLine(Strings.HelpCommand_Core_NavigationCommands.Bold().Cyan()); output.AppendLine(Strings.HelpCommand_Core_NavigationCommands_Description); output.AppendLine(); output.AppendLine($"{"ls",navCommandColumn}{GetCommand(dispatcher).GetHelpSummary(shellState, programState)}"); output.AppendLine($"{"cd",navCommandColumn}{GetCommand(dispatcher).GetHelpSummary(shellState, programState)}"); output.AppendLine(); output.AppendLine(Strings.HelpCommand_Core_ShellCommands.Bold().Cyan()); output.AppendLine(Strings.HelpCommand_Core_ShellCommands_Description); output.AppendLine(); output.AppendLine($"{"clear",navCommandColumn}{GetCommand(dispatcher).GetHelpSummary(shellState, programState)}"); output.AppendLine($"{"echo [on/off]",navCommandColumn}{GetCommand(dispatcher).GetHelpSummary(shellState, programState)}"); output.AppendLine($"{"exit",navCommandColumn}{GetCommand(dispatcher).GetHelpSummary(shellState, programState)}"); output.AppendLine(); output.AppendLine(Strings.HelpCommand_Core_CustomizationCommands.Bold().Cyan()); output.AppendLine(Strings.HelpCommand_Core_CustomizationCommands_Description); output.AppendLine(); output.AppendLine($"{"pref [get/set]",navCommandColumn}{GetCommand(dispatcher).GetHelpSummary(shellState, programState)}"); output.AppendLine($"{"run",navCommandColumn}{GetCommand(dispatcher).GetHelpSummary(shellState, programState)}"); output.AppendLine($"{"ui",navCommandColumn}{GetCommand(dispatcher).GetHelpSummary(shellState, programState)}"); output.AppendLine(); output.AppendLine(Strings.HelpCommand_Core_Details_Line1.Bold().Cyan()); output.AppendLine(Strings.HelpCommand_Core_Details_Line2.Bold().Cyan()); output.AppendLine(); shellState.ConsoleManager.Write(output.ToString()); } private static CommandWithStructuredInputBase GetStructuredCommand(ICommand rawCommand) { return rawCommand switch { CommandWithStructuredInputBase structuredCommand => structuredCommand, TelemetryCommandWrapper telemetryWrapper when telemetryWrapper.Command is CommandWithStructuredInputBase structuredCommand => structuredCommand, _ => null }; } private static ICommand GetCommand(ICommandDispatcher dispatcher) where T : ICommand { foreach (ICommand command in dispatcher.Commands) { switch (command) { case T directMatch: return directMatch; case TelemetryCommandWrapper telemetryWrapper when telemetryWrapper.Command?.GetType() == typeof(T): return telemetryWrapper; } } return null; } } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/ListCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.HttpRepl.Preferences; using Microsoft.Repl; using Microsoft.Repl.Commanding; using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Parsing; namespace Microsoft.HttpRepl.Commands { public class ListCommand : CommandWithStructuredInputBase { private const string RecursiveOption = nameof(RecursiveOption); private const string VerboseOption = nameof(VerboseOption); private readonly IPreferences _preferences; public override string Name => "list"; public ListCommand(IPreferences preferences) { _preferences = preferences; } protected override async Task ExecuteAsync(IShellState shellState, HttpState programState, DefaultCommandInput commandInput, ICoreParseResult parseResult, CancellationToken cancellationToken) { programState = programState ?? throw new ArgumentNullException(nameof(programState)); shellState = shellState ?? throw new ArgumentNullException(nameof(shellState)); commandInput = commandInput ?? throw new ArgumentNullException(nameof(commandInput)); if (programState.SwaggerEndpoint != null) { string swaggerRequeryBehaviorSetting = _preferences.GetValue(WellKnownPreference.SwaggerRequeryBehavior, "auto"); if (swaggerRequeryBehaviorSetting.StartsWith("auto", StringComparison.OrdinalIgnoreCase)) { ApiConnection apiConnection = new ApiConnection(programState, _preferences, shellState.ConsoleManager, logVerboseMessages: false) { BaseUri = programState.BaseAddress, SwaggerUri = programState.SwaggerEndpoint, AllowBaseOverrideBySwagger = false }; await apiConnection.SetupHttpState(programState, performAutoDetect: false, persistHeaders: true, persistPath: true, cancellationToken).ConfigureAwait(false); } } if (programState.BaseAddress is null) { shellState.ConsoleManager.WriteLine(Resources.Strings.ListCommand_Error_NoBaseAddress.SetColor(programState.WarningColor)); return; } if (programState.SwaggerEndpoint is null || programState.Structure is null) { shellState.ConsoleManager.WriteLine(Resources.Strings.ListCommand_Error_NoDirectoryStructure.SetColor(programState.WarningColor)); return; } string path = commandInput.Arguments.Count > 0 ? commandInput.Arguments[0].Text : string.Empty; //If it's an absolute URI, nothing to suggest if (Uri.TryCreate(path, UriKind.Absolute, out Uri _)) { return; } IDirectoryStructure s = programState.Structure.TraverseTo(programState.PathSections.Reverse()).TraverseTo(path); string thisDirMethod = s.RequestInfo.GetDirectoryMethodListing(); List roots = new List(); Formatter formatter = new Formatter(); roots.Add(new TreeNode(formatter, ".", thisDirMethod)); if (s.Parent != null) { string parentDirMethod = s.Parent.RequestInfo.GetDirectoryMethodListing(); roots.Add(new TreeNode(formatter, "..", parentDirMethod)); } int recursionDepth = 1; if (commandInput.Options[RecursiveOption].Count > 0) { if (string.IsNullOrEmpty(commandInput.Options[RecursiveOption][0]?.Text)) { recursionDepth = int.MaxValue; } else if (int.TryParse(commandInput.Options[RecursiveOption][0].Text, NumberStyles.Integer, CultureInfo.InvariantCulture, out int rd) && rd > 1) { recursionDepth = rd; } } foreach (string child in s.DirectoryNames) { IDirectoryStructure dir = s.GetChildDirectory(child); string methods = dir.RequestInfo.GetDirectoryMethodListing(); TreeNode dirNode = new TreeNode(formatter, child, methods); roots.Add(dirNode); Recurse(dirNode, dir, recursionDepth - 1); } foreach (TreeNode node in roots) { shellState.ConsoleManager.WriteLine(node.ToString()); } bool hasVerboseOption = commandInput.Options[VerboseOption].Count > 0; bool hasRequestMethods = s.RequestInfo?.Methods?.Count > 0; if (hasVerboseOption && hasRequestMethods) { shellState.ConsoleManager.WriteLine(); shellState.ConsoleManager.WriteLine(Resources.Strings.ListCommand_AvailableMethods); foreach (string method in s.RequestInfo.Methods) { shellState.ConsoleManager.WriteLine(" " + method.ToUpperInvariant()); IReadOnlyList accepts = s.RequestInfo.ContentTypesByMethod[method]; string acceptsString = string.Join(", ", accepts.Where(x => !string.IsNullOrEmpty(x))); if (!string.IsNullOrEmpty(acceptsString)) { shellState.ConsoleManager.WriteLine($" {Resources.Strings.ListCommand_Accepts} " + acceptsString); } } } } private static void Recurse(TreeNode parentNode, IDirectoryStructure parent, int remainingDepth) { if (remainingDepth <= 0) { return; } foreach (string child in parent.DirectoryNames) { IDirectoryStructure dir = parent.GetChildDirectory(child); string methods = dir.RequestInfo?.GetDirectoryMethodListing(); TreeNode node = parentNode.AddChild(child, methods); Recurse(node, dir, remainingDepth - 1); } } public override CommandInputSpecification InputSpec { get; } = CommandInputSpecification.Create("ls").AlternateName("dir") .MaximumArgCount(1) .WithOption(new CommandOptionSpecification(RecursiveOption, acceptsValue: true, maximumOccurrences: 1, forms: new[] {"-r", "--recursive"})) .WithOption(new CommandOptionSpecification(VerboseOption, acceptsValue: false, maximumOccurrences: 1, forms: new[] { "-v", "--verbose"})) .Finish(); protected override string GetHelpDetails(IShellState shellState, HttpState programState, DefaultCommandInput commandInput, ICoreParseResult parseResult) { var helpText = new StringBuilder(); helpText.Append(Resources.Strings.Usage.Bold()); helpText.AppendLine($"ls [Options]"); helpText.AppendLine(); helpText.AppendLine($"Displays the known routes at the current location. Requires a Swagger document to be set."); return helpText.ToString(); } public override string GetHelpSummary(IShellState shellState, HttpState programState) { return Resources.Strings.ListCommand_HelpSummary; } protected override IEnumerable GetArgumentSuggestionsForText(IShellState shellState, HttpState programState, ICoreParseResult parseResult, DefaultCommandInput commandInput, string normalCompletionString) { programState = programState ?? throw new ArgumentNullException(nameof(programState)); if (programState.Structure == null || programState.BaseAddress == null) { return null; } //If it's an absolute URI, nothing to suggest if (Uri.TryCreate(normalCompletionString, UriKind.Absolute, out Uri _)) { return null; } normalCompletionString = normalCompletionString ?? throw new ArgumentNullException(nameof(normalCompletionString)); string path = normalCompletionString.Replace('\\', '/'); int searchFrom = normalCompletionString.Length - 1; int lastSlash = path.LastIndexOf('/', searchFrom); string prefix; if (lastSlash < 0) { path = string.Empty; prefix = normalCompletionString; } else { path = path.Substring(0, lastSlash + 1); prefix = normalCompletionString.Substring(lastSlash + 1); } IDirectoryStructure s = programState.Structure.TraverseTo(programState.PathSections.Reverse()).TraverseTo(path); List results = new List(); foreach (string child in s.DirectoryNames) { if (child.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { results.Add(path + child); } } return results; } } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/OptionsCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using Microsoft.HttpRepl.FileSystem; using Microsoft.HttpRepl.Preferences; using Microsoft.HttpRepl.Telemetry; namespace Microsoft.HttpRepl.Commands { public class OptionsCommand : BaseHttpCommand { public OptionsCommand(IFileSystem fileSystem, IPreferences preferences, ITelemetry telemetry) : base(fileSystem, preferences, telemetry) { } protected override string Verb => "options"; protected override bool RequiresBody => false; } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/PatchCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using Microsoft.HttpRepl.FileSystem; using Microsoft.HttpRepl.Preferences; using Microsoft.HttpRepl.Telemetry; namespace Microsoft.HttpRepl.Commands { public class PatchCommand : BaseHttpCommand { public PatchCommand(IFileSystem fileSystem, IPreferences preferences, ITelemetry telemetry) : base(fileSystem, preferences, telemetry) { } protected override string Verb => "patch"; protected override bool RequiresBody => true; } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/PostCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using Microsoft.HttpRepl.FileSystem; using Microsoft.HttpRepl.Preferences; using Microsoft.HttpRepl.Telemetry; namespace Microsoft.HttpRepl.Commands { public class PostCommand : BaseHttpCommand { public PostCommand(IFileSystem fileSystem, IPreferences preferences, ITelemetry telemetry) : base(fileSystem, preferences, telemetry) { } protected override string Verb => "post"; protected override bool RequiresBody => true; } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/PrefCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.HttpRepl.Preferences; using Microsoft.HttpRepl.Telemetry; using Microsoft.HttpRepl.Telemetry.Events; using Microsoft.Repl; using Microsoft.Repl.Commanding; using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Parsing; namespace Microsoft.HttpRepl.Commands { public class PrefCommand : CommandWithStructuredInputBase { private const string _CommandSyntax = "pref [get/set] {setting} [{value}]"; private const string _GetCommandSyntax = "pref get [{setting}]"; private const string _SetCommandSyntax = "pref set {setting} [{value}]"; private readonly HashSet _allowedSubcommands = new HashSet(StringComparer.OrdinalIgnoreCase) {"get", "set"}; private readonly IPreferences _preferences; private readonly ITelemetry _telemetry; public override string Name => "pref"; public PrefCommand(IPreferences preferences, ITelemetry telemetry) { _preferences = preferences; _telemetry = telemetry; } public override string GetHelpSummary(IShellState shellState, HttpState programState) { return string.Format(Resources.Strings.PrefCommand_HelpSummary, _CommandSyntax); } protected override bool CanHandle(IShellState shellState, HttpState programState, DefaultCommandInput commandInput) { commandInput = commandInput ?? throw new ArgumentNullException(nameof(commandInput)); shellState = shellState ?? throw new ArgumentNullException(nameof(shellState)); if (commandInput.Arguments.Count == 0 || !_allowedSubcommands.Contains(commandInput.Arguments[0]?.Text)) { shellState.ConsoleManager.Error.WriteLine(Resources.Strings.PrefCommand_Error_NoGetOrSet); return false; } if (!string.Equals("get", commandInput.Arguments[0].Text, StringComparison.OrdinalIgnoreCase) && (commandInput.Arguments.Count < 2 || string.IsNullOrEmpty(commandInput.Arguments[1]?.Text))) { shellState.ConsoleManager.Error.WriteLine(Resources.Strings.PrefCommand_Error_NoPreferenceName); return false; } return true; } protected override string GetHelpDetails(IShellState shellState, HttpState programState, DefaultCommandInput commandInput, ICoreParseResult parseResult) { var helpText = new StringBuilder(); helpText.Append(Resources.Strings.Help_Usage.Bold()); commandInput = commandInput ?? throw new ArgumentNullException(nameof(commandInput)); if (commandInput.Arguments.Count == 0 || !_allowedSubcommands.Contains(commandInput.Arguments[0]?.Text)) { helpText.AppendFormat(Resources.Strings.PrefCommand_HelpDetails_Syntax, _CommandSyntax); } else if (string.Equals(commandInput.Arguments[0].Text, "get", StringComparison.OrdinalIgnoreCase)) { helpText.AppendFormat(Resources.Strings.PrefCommand_HelpDetails_GetSyntax, _GetCommandSyntax); } else { helpText.AppendFormat(Resources.Strings.PrefCommand_HelpDetails_SetSyntax, _SetCommandSyntax); } helpText.AppendLine(); helpText.AppendLine(Resources.Strings.PrefCommand_HelpDetails_DefaultPreferences); foreach (var pref in _preferences.DefaultPreferences) { var val = pref.Value; if (pref.Key.Contains("colors", StringComparison.OrdinalIgnoreCase)) { val = GetColor(val); } helpText.AppendLine($"{pref.Key,-50}{val}"); } helpText.AppendLine(); helpText.AppendLine(Resources.Strings.PrefCommand_HelpDetails_CurrentPreferences); foreach (var pref in _preferences.CurrentPreferences) { var val = pref.Value; if (pref.Key.Contains("colors", StringComparison.OrdinalIgnoreCase)) { val = GetColor(val); } helpText.AppendLine($"{pref.Key,-50}{val}"); } return helpText.ToString(); } private static string GetColor(string value) { if (value.Contains("Bold", StringComparison.OrdinalIgnoreCase)) { value = value.Bold(); } if (value.Contains("Yellow", StringComparison.OrdinalIgnoreCase)) { value = value.Yellow(); } if (value.Contains("Cyan", StringComparison.OrdinalIgnoreCase)) { value = value.Cyan(); } if (value.Contains("Magenta", StringComparison.OrdinalIgnoreCase)) { value = value.Magenta(); } if (value.Contains("Green", StringComparison.OrdinalIgnoreCase)) { value = value.Green(); } if (value.Contains("White", StringComparison.OrdinalIgnoreCase)) { value = value.White(); } if (value.Contains("Black", StringComparison.OrdinalIgnoreCase)) { value = value.Black(); } return value; } protected override Task ExecuteAsync(IShellState shellState, HttpState programState, DefaultCommandInput commandInput, ICoreParseResult parseResult, CancellationToken cancellationToken) { commandInput = commandInput ?? throw new ArgumentNullException(nameof(commandInput)); shellState = shellState ?? throw new ArgumentNullException(nameof(shellState)); programState = programState ?? throw new ArgumentNullException(nameof(programState)); if (string.Equals(commandInput.Arguments[0].Text, "get", StringComparison.OrdinalIgnoreCase)) { GetSetting(shellState, programState, commandInput); } else { SetSetting(shellState, programState, commandInput); } return Task.CompletedTask; } private void SetSetting(IShellState shellState, HttpState programState, DefaultCommandInput commandInput) { string prefName = commandInput.Arguments[1].Text; string prefValue = commandInput.Arguments.Count > 2 ? commandInput.Arguments[2]?.Text : null; _telemetry.TrackEvent(new PreferenceEvent("Set", prefName)); if (!_preferences.SetValue(prefName, prefValue)) { shellState.ConsoleManager.Error.WriteLine(Resources.Strings.PrefCommand_Error_Saving.SetColor(programState.ErrorColor)); } else { // If we think they're configurating HttpRepl to use Visual Studio Code as their editor, we should // warn them that for best integration, they should also pass the `-w` or `--wait` arguments to // Visual Studio Code. if (string.Equals(prefName, WellKnownPreference.DefaultEditorCommand, StringComparison.Ordinal)) { if (IsVSCode(prefValue)) { shellState.ConsoleManager.WriteLine(string.Format(Resources.Strings.PrefCommand_Set_VSCode, WellKnownPreference.DefaultEditorArguments).SetColor(programState.WarningColor)); } } } } private void GetSetting(IShellState shellState, HttpState programState, DefaultCommandInput commandInput) { string preferenceName = commandInput.Arguments.Count > 1 ? commandInput.Arguments[1]?.Text : null; _telemetry.TrackEvent(new PreferenceEvent("Get", preferenceName)); //If there's a particular setting to get the value of if (!string.IsNullOrEmpty(preferenceName)) { if (_preferences.TryGetValue(preferenceName, out string value)) { shellState.ConsoleManager.WriteLine(string.Format(Resources.Strings.PrefCommand_Get_ConfiguredValue, value)); } else { shellState.ConsoleManager.Error.WriteLine(string.Format(Resources.Strings.PrefCommand_Error_NoConfiguredValue, commandInput.Arguments[1].Text).SetColor(programState.ErrorColor)); } } else { foreach (KeyValuePair entry in _preferences.CurrentPreferences.OrderBy(x => x.Key)) { shellState.ConsoleManager.WriteLine($"{entry.Key}={entry.Value}"); } } } public override CommandInputSpecification InputSpec { get; } = CommandInputSpecification.Create("pref") .MinimumArgCount(1) .MaximumArgCount(3) .Finish(); protected override IEnumerable GetArgumentSuggestionsForText(IShellState shellState, HttpState programState, ICoreParseResult parseResult, DefaultCommandInput commandInput, string normalCompletionString) { parseResult = parseResult ?? throw new ArgumentNullException(nameof(parseResult)); if (parseResult.SelectedSection == 1) { return _allowedSubcommands.Where(x => x.StartsWith(normalCompletionString, StringComparison.OrdinalIgnoreCase)); } if (parseResult.SelectedSection == 2) { string prefix = parseResult.Sections.Count > 2 ? normalCompletionString : string.Empty; List matchingProperties = new List(); foreach (string val in WellKnownPreference.Catalog.Names) { if (val.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { matchingProperties.Add(val); } } return matchingProperties; } if (parseResult.SelectedSection == 3 && parseResult.Sections[2].StartsWith("colors.", StringComparison.OrdinalIgnoreCase)) { string prefix = parseResult.Sections.Count > 3 ? normalCompletionString : string.Empty; List matchingProperties = new List(); foreach (string val in Enum.GetNames(typeof(AllowedColors))) { if (val.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { matchingProperties.Add(val); } } return matchingProperties; } return null; } private static bool IsVSCode(string path) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return path.Contains("Code.exe", StringComparison.OrdinalIgnoreCase) || path.Contains("Code - Insiders.exe", StringComparison.OrdinalIgnoreCase); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { return path.Contains("Visual Studio Code.app", StringComparison.Ordinal) || path.Contains("Visual Studio Code - Insiders.app", StringComparison.Ordinal); } else // Linux { return string.Equals(path, "/usr/bin/code", StringComparison.Ordinal) || string.Equals(path, "/usr/bin/code-insiders", StringComparison.Ordinal); } } } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/PutCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using Microsoft.HttpRepl.FileSystem; using Microsoft.HttpRepl.Preferences; using Microsoft.HttpRepl.Telemetry; namespace Microsoft.HttpRepl.Commands { public class PutCommand : BaseHttpCommand { public PutCommand(IFileSystem fileSystem, IPreferences preferences, ITelemetry telemetry) : base(fileSystem, preferences, telemetry) { } protected override string Verb => "put"; protected override bool RequiresBody => true; } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/RunCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.HttpRepl.FileSystem; using Microsoft.HttpRepl.Resources; using Microsoft.Repl; using Microsoft.Repl.Commanding; using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Parsing; using Microsoft.Repl.Scripting; using Microsoft.Repl.Suggestions; namespace Microsoft.HttpRepl.Commands { public class RunCommand : ICommand { public string Name => "run"; private readonly IFileSystem _fileSystem; public RunCommand(IFileSystem fileSystem) { _fileSystem = fileSystem; } public bool? CanHandle(IShellState shellState, HttpState programState, ICoreParseResult parseResult) { parseResult = parseResult ?? throw new ArgumentNullException(nameof(parseResult)); return parseResult.ContainsAtLeast(minimumLength: 2, Name) && parseResult.Sections.Count < 4 ? (bool?)true : null; } public async Task ExecuteAsync(IShellState shellState, HttpState programState, ICoreParseResult parseResult, CancellationToken cancellationToken) { parseResult = parseResult ?? throw new ArgumentNullException(nameof(parseResult)); shellState = shellState ?? throw new ArgumentNullException(nameof(shellState)); if (!_fileSystem.FileExists(parseResult.Sections[1])) { shellState.ConsoleManager.Error.WriteLine(string.Format(Strings.RunCommand_CouldNotFindScriptFile, parseResult.Sections[1])); return; } bool suppressScriptLinesInHistory = true; if (parseResult.Sections.Count == 3) { suppressScriptLinesInHistory = !string.Equals(parseResult.Sections[2], "+history", StringComparison.OrdinalIgnoreCase); } string[] lines = _fileSystem.ReadAllLinesFromFile(parseResult.Sections[1]); IScriptExecutor scriptExecutor = new ScriptExecutor(suppressScriptLinesInHistory); await scriptExecutor.ExecuteScriptAsync(shellState, lines, cancellationToken).ConfigureAwait(false); } public string GetHelpDetails(IShellState shellState, HttpState programState, ICoreParseResult parseResult) { if (parseResult.ContainsAtLeast(Name)) { StringBuilder helpText = new StringBuilder(); helpText.Append(Strings.Usage.Bold()); helpText.AppendLine(Strings.RunCommand_HelpDetails); return helpText.ToString(); } return null; } public string GetHelpSummary(IShellState shellState, HttpState programState) { return Resources.Strings.RunCommand_HelpSummary; } public IEnumerable Suggest(IShellState shellState, HttpState programState, ICoreParseResult parseResult) { parseResult = parseResult ?? throw new ArgumentNullException(nameof(parseResult)); if (parseResult.SelectedSection == 0 && (string.IsNullOrEmpty(parseResult.Sections[parseResult.SelectedSection]) || Name.StartsWith(parseResult.Sections[0].Substring(0, parseResult.CaretPositionWithinSelectedSection), StringComparison.OrdinalIgnoreCase))) { return new[] { Name }; } if (parseResult.SelectedSection == 1 && string.Equals(parseResult.Sections[0], Name, StringComparison.OrdinalIgnoreCase)) { return FileSystemCompletion.GetCompletions(parseResult.Sections[1].Substring(0, parseResult.CaretPositionWithinSelectedSection)); } return null; } } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/SetHeaderCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.HttpRepl.Resources; using Microsoft.HttpRepl.Suggestions; using Microsoft.HttpRepl.Telemetry; using Microsoft.HttpRepl.Telemetry.Events; using Microsoft.Repl; using Microsoft.Repl.Commanding; using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Parsing; namespace Microsoft.HttpRepl.Commands { public class SetHeaderCommand : ICommand { private const string CommandName = "set"; private const string SubCommand = "header"; private readonly ITelemetry _telemetry; public string Name => "setHeader"; public static string Description => Strings.SetHeaderCommand_HelpSummary; public SetHeaderCommand(ITelemetry telemetry) { _telemetry = telemetry; } public bool? CanHandle(IShellState shellState, HttpState programState, ICoreParseResult parseResult) { return parseResult.ContainsAtLeast(minimumLength: 3, CommandName, SubCommand) ? (bool?)true : null; } public Task ExecuteAsync(IShellState shellState, HttpState programState, ICoreParseResult parseResult, CancellationToken cancellationToken) { parseResult = parseResult ?? throw new ArgumentNullException(nameof(parseResult)); programState = programState ?? throw new ArgumentNullException(nameof(programState)); bool isValueEmpty; if (parseResult.Sections.Count == 3) { programState.Headers.Remove(parseResult.Sections[2]); isValueEmpty = true; } else { programState.Headers[parseResult.Sections[2]] = parseResult.Sections.Skip(3).ToList(); isValueEmpty = false; } _telemetry.TrackEvent(new SetHeaderEvent(parseResult.Sections[2], isValueEmpty)); return Task.CompletedTask; } public string GetHelpDetails(IShellState shellState, HttpState programState, ICoreParseResult parseResult) { if (parseResult.ContainsAtLeast(CommandName, SubCommand)) { StringBuilder helpText = new StringBuilder(); helpText.Append(Strings.Usage.Bold()); helpText.AppendLine("set header {name} [value]"); helpText.AppendLine(); helpText.AppendLine(Strings.SetHeaderCommand_HelpDetails); return helpText.ToString(); } return null; } public string GetHelpSummary(IShellState shellState, HttpState programState) { return Description; } public IEnumerable Suggest(IShellState shellState, HttpState programState, ICoreParseResult parseResult) { parseResult = parseResult ?? throw new ArgumentNullException(nameof(parseResult)); if (parseResult.Sections.Count == 0) { return new[] { CommandName }; } if (parseResult.Sections.Count > 0 && parseResult.SelectedSection == 0 && CommandName.StartsWith(parseResult.Sections[0].Substring(0, parseResult.CaretPositionWithinSelectedSection), StringComparison.OrdinalIgnoreCase)) { return new[] { CommandName }; } if (string.Equals(CommandName, parseResult.Sections[0], StringComparison.OrdinalIgnoreCase) && parseResult.SelectedSection == 1 && (parseResult.Sections.Count < 2 || SubCommand.StartsWith(parseResult.Sections[1].Substring(0, parseResult.CaretPositionWithinSelectedSection), StringComparison.OrdinalIgnoreCase))) { return new[] { SubCommand }; } if (parseResult.Sections.Count > 2 && string.Equals(CommandName, parseResult.Sections[0], StringComparison.OrdinalIgnoreCase) && string.Equals(SubCommand, parseResult.Sections[1], StringComparison.OrdinalIgnoreCase) && parseResult.SelectedSection == 2) { string prefix = parseResult.Sections[2].Substring(0, parseResult.CaretPositionWithinSelectedSection); return HeaderCompletion.GetCompletions(null, prefix); } if (parseResult.Sections.Count > 3 && string.Equals(CommandName, parseResult.Sections[0], StringComparison.OrdinalIgnoreCase) && string.Equals(SubCommand, parseResult.Sections[1], StringComparison.OrdinalIgnoreCase) && parseResult.SelectedSection == 3) { string prefix = parseResult.Sections[3].Substring(0, parseResult.CaretPositionWithinSelectedSection); return HeaderCompletion.GetValueCompletions(null, string.Empty, parseResult.Sections[2], prefix, programState); } return null; } } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/TreeNode.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; namespace Microsoft.HttpRepl.Commands { public class TreeNode { private readonly int _depth; private readonly Formatter _formatter; private readonly string _prefix; private readonly string _entry; private readonly List _children = new List(); public IReadOnlyList Children => _children; public TreeNode(Formatter formatter, string prefix, string entry) : this(formatter, prefix, entry, 0) { } private TreeNode(Formatter formatter, string prefix, string entry, int depth) { _formatter = formatter ?? throw new ArgumentNullException(nameof(formatter)); _prefix = prefix ?? throw new ArgumentNullException(nameof(prefix)); formatter.RegisterEntry(prefix.Length, depth); _entry = entry; _depth = depth; } public TreeNode AddChild(string prefix, string entry) { TreeNode child = new TreeNode(_formatter, prefix, entry, _depth + 1); _children.Add(child); return child; } public override string ToString() { string self = _formatter.Format(_prefix, _entry, _depth); if (_children.Count == 0) { return self; } return self + Environment.NewLine + string.Join(Environment.NewLine, _children); } } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/UICommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.HttpRepl.Preferences; using Microsoft.HttpRepl.Resources; using Microsoft.Repl; using Microsoft.Repl.Commanding; using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Parsing; namespace Microsoft.HttpRepl.Commands { public class UICommand : ICommand { public string Name => "ui"; private readonly IUriLauncher _uriLauncher; private readonly IPreferences _preferences; public UICommand(IUriLauncher uriLauncher, IPreferences preferences) { _uriLauncher = uriLauncher ?? throw new ArgumentNullException(nameof(uriLauncher)); _preferences = preferences ?? throw new ArgumentNullException(nameof(preferences)); } public bool? CanHandle(IShellState shellState, HttpState programState, ICoreParseResult parseResult) { return parseResult.ContainsAtLeast(Name) ? (bool?)true : null; } public Task ExecuteAsync(IShellState shellState, HttpState programState, ICoreParseResult parseResult, CancellationToken cancellationToken) { parseResult = parseResult ?? throw new ArgumentNullException(nameof(parseResult)); programState = programState ?? throw new ArgumentNullException(nameof(programState)); shellState = shellState ?? throw new ArgumentNullException(nameof(shellState)); if (programState.BaseAddress == null) { shellState.ConsoleManager.Error.WriteLine(Strings.UICommand_NotConnectedToServerError.SetColor(programState.ErrorColor)); return Task.CompletedTask; } Uri uri = null; // Try to use the parameter first, if there was one. if (parseResult.Sections.Count > 1) { string parameter = parseResult.Sections[1]; if (Uri.IsWellFormedUriString(parameter, UriKind.Absolute)) { uri = new Uri(parameter, UriKind.Absolute); } else if (!Uri.TryCreate(programState.BaseAddress, parameter, out uri)) { uri = null; } // If they specified a parameter and it failed, bail out if (uri is null) { shellState.ConsoleManager.Error.WriteLine(string.Format(Strings.UICommand_InvalidParameter, parameter).SetColor(programState.ErrorColor)); return Task.CompletedTask; } } // If no parameter specified, check the preferences or use the default if (uri is null) { string uiEndpoint = _preferences.GetValue(WellKnownPreference.SwaggerUIEndpoint, "swagger"); if (Uri.IsWellFormedUriString(uiEndpoint, UriKind.Absolute)) { uri = new Uri(uiEndpoint, UriKind.Absolute); } else { uri = new Uri(programState.BaseAddress, uiEndpoint); } } return _uriLauncher.LaunchUriAsync(uri); } private string _HelpText; private string HelpText { get { if (_HelpText is null) { string parameter = "{swaggerUIAddress}"; string defaultAddress = "[BaseAddress]/swagger"; StringBuilder helpText = new StringBuilder(); helpText.Append(Strings.Usage.Bold()); helpText.AppendLine("ui [{swaggerUIAddress}]"); helpText.AppendLine(); helpText.AppendLine(Strings.UICommand_Description); helpText.AppendLine(); helpText.AppendLine(string.Format(Strings.UICommand_HelpText_Line1, Name)); helpText.AppendLine(string.Format(Strings.UICommand_HelpText_Line2, parameter)); helpText.AppendLine(string.Format(Strings.UICommand_HelpText_Line3, WellKnownPreference.SwaggerUIEndpoint)); helpText.AppendLine(string.Format(Strings.UICommand_HelpText_Line4, defaultAddress)); _HelpText = helpText.ToString(); } return _HelpText; } } public string GetHelpDetails(IShellState shellState, HttpState programState, ICoreParseResult parseResult) { if (parseResult.ContainsAtLeast(Name)) { return HelpText; } return null; } public string GetHelpSummary(IShellState shellState, HttpState programState) { return Resources.Strings.UICommand_HelpSummary; } public IEnumerable Suggest(IShellState shellState, HttpState programState, ICoreParseResult parseResult) { parseResult = parseResult ?? throw new ArgumentNullException(nameof(parseResult)); if (parseResult.SelectedSection == 0 && (string.IsNullOrEmpty(parseResult.Sections[parseResult.SelectedSection]) || Name.StartsWith(parseResult.Sections[0].Substring(0, parseResult.CaretPositionWithinSelectedSection), StringComparison.OrdinalIgnoreCase))) { return new[] { Name }; } return null; } } } ================================================ FILE: src/Microsoft.HttpRepl/DirectoryStructure.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; namespace Microsoft.HttpRepl { public class DirectoryStructure : IDirectoryStructure { private readonly Dictionary _childDirectories = new Dictionary(StringComparer.OrdinalIgnoreCase); public DirectoryStructure(IDirectoryStructure parent) { Parent = parent; } public IEnumerable DirectoryNames => _childDirectories.Keys; public IDirectoryStructure Parent { get; } public DirectoryStructure DeclareDirectory(string name) { if (_childDirectories.TryGetValue(name, out DirectoryStructure existing)) { return existing; } return _childDirectories[name] = new DirectoryStructure(this); } public IDirectoryStructure GetChildDirectory(string name) { if (_childDirectories.TryGetValue(name, out DirectoryStructure result)) { return result; } IDirectoryStructure parameterizedTarget = _childDirectories.FirstOrDefault(x => x.Key.StartsWith('{') && x.Key.EndsWith('}')).Value; if (parameterizedTarget is object) { return parameterizedTarget; } return new DirectoryStructure(this); } public IRequestInfo RequestInfo { get; set; } } public class RequestInfo : IRequestInfo { private readonly HashSet _methods = new HashSet(StringComparer.OrdinalIgnoreCase); private readonly Dictionary> _requestBodiesByMethodByContentType = new Dictionary>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary _fallbackBodyStringsByMethod = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly Dictionary _fallbackContentTypeStringsByMethod = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly Dictionary> _contentTypesByMethod = new Dictionary>(StringComparer.OrdinalIgnoreCase); public IReadOnlyList Methods => _methods.ToList(); public IReadOnlyDictionary> ContentTypesByMethod => _contentTypesByMethod; public string GetRequestBodyForContentType(ref string contentType, string method) { if (_requestBodiesByMethodByContentType.TryGetValue(method, out Dictionary bodiesByContentType) && bodiesByContentType.TryGetValue(contentType, out string body)) { return body; } if (_fallbackBodyStringsByMethod.TryGetValue(method, out body)) { if (_fallbackContentTypeStringsByMethod.TryGetValue(method, out string newContentType)) { contentType = newContentType; } return body; } return null; } public void SetRequestBody(string method, string contentType, string body) { if (!_requestBodiesByMethodByContentType.TryGetValue(method, out Dictionary bodiesByContentType)) { _requestBodiesByMethodByContentType[method] = bodiesByContentType = new Dictionary(StringComparer.OrdinalIgnoreCase); } if (!_contentTypesByMethod.TryGetValue(method, out IReadOnlyList contentTypesRaw)) { _contentTypesByMethod[method] = contentTypesRaw = new List(); } List contentTypes = (List)contentTypesRaw; contentTypes.Add(contentType); bodiesByContentType[contentType] = body; } public void AddMethod(string method) { _methods.Add(method); } public void SetFallbackRequestBody(string method, string contentType, string fallbackBodyString) { _fallbackBodyStringsByMethod[method] = fallbackBodyString; _fallbackContentTypeStringsByMethod[method] = contentType; } } } ================================================ FILE: src/Microsoft.HttpRepl/DirectoryStructureExtensions.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; namespace Microsoft.HttpRepl { public static class DirectoryStructureExtensions { public static IEnumerable GetDirectoryListingAtPath(this IDirectoryStructure structure, string path) { structure = structure ?? throw new ArgumentNullException(nameof(structure)); return structure.TraverseTo(path).DirectoryNames; } public static IDirectoryStructure TraverseTo(this IDirectoryStructure structure, string path) { structure = structure ?? throw new ArgumentNullException(nameof(structure)); path = path ?? throw new ArgumentNullException(nameof(path)); string[] parts = path.Replace('\\', '/').Split('/'); return structure.TraverseTo(parts); } public static IDirectoryStructure TraverseTo(this IDirectoryStructure structure, IEnumerable pathParts) { structure = structure ?? throw new ArgumentNullException(nameof(structure)); IDirectoryStructure s = structure; if (pathParts is null) { return s; } IReadOnlyList parts = pathParts.ToList(); if (parts.Count == 0) { return s; } if (parts[0].Length == 0 && parts.Count > 1) { while (s.Parent != null) { s = s.Parent; } } foreach (string part in parts) { if (part == ".") { continue; } if (part == "..") { s = s?.Parent ?? s; } else if (!string.IsNullOrEmpty(part) && s is object) { s = s.GetChildDirectory(part); } } return s; } } } ================================================ FILE: src/Microsoft.HttpRepl/Extensions/RequestInfoExtensions.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Collections.Generic; using System.Linq; namespace Microsoft.HttpRepl { public static class RequestInfoExtensions { public static string GetDirectoryMethodListing(this IRequestInfo requestInfo) { if (requestInfo is null || requestInfo.Methods is null || requestInfo.Methods.Count == 0) { return "[]"; } IEnumerable upperCaseMethods = requestInfo.Methods.Select(s => s?.ToUpperInvariant()); return "[" + string.Join("|", upperCaseMethods) + "]"; } } } ================================================ FILE: src/Microsoft.HttpRepl/Extensions/UrlStringExtensions.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; namespace Microsoft.HttpRepl { public static class UrlStringExtensions { public static string EnsureTrailingSlash(this string url) { url = url ?? throw new ArgumentNullException(nameof(url)); if (!url.EndsWith("/", StringComparison.Ordinal)) { url += "/"; } return url; } } } ================================================ FILE: src/Microsoft.HttpRepl/FileSystem/IFileSystem.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Collections.Generic; namespace Microsoft.HttpRepl.FileSystem { public interface IFileSystem { void DeleteFile(string path); bool FileExists(string path); string GetTempFileName(string fileExtension); byte[] ReadAllBytesFromFile(string path); string[] ReadAllLinesFromFile(string path); void WriteAllTextToFile(string path, string contents); void WriteAllLinesToFile(string path, IEnumerable contents); } } ================================================ FILE: src/Microsoft.HttpRepl/FileSystem/RealFileSystem.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.IO; using Microsoft.HttpRepl.Resources; namespace Microsoft.HttpRepl.FileSystem { internal class RealFileSystem : IFileSystem { public bool FileExists(string path) { return File.Exists(path); } public void WriteAllTextToFile(string path, string contents) { VerifyDirectoryExists(path); File.WriteAllText(path, contents); } public byte[] ReadAllBytesFromFile(string path) { return File.ReadAllBytes(path); } public string[] ReadAllLinesFromFile(string path) { return File.ReadAllLines(path); } public void WriteAllLinesToFile(string path, IEnumerable contents) { VerifyDirectoryExists(path); File.WriteAllLines(path, contents); } public void DeleteFile(string path) { File.Delete(path); } public string GetTempFileName(string fileExtension) { fileExtension = fileExtension ?? throw new ArgumentNullException(nameof(fileExtension)); if (!fileExtension.StartsWith(".", StringComparison.Ordinal) || fileExtension.Length < 2) { throw new ArgumentException(string.Format(Strings.RealFileSystem_Error_InvalidExtension, nameof(fileExtension)), nameof(fileExtension)); } string tempFileName = Path.Combine(Path.GetTempPath(), GetRandomFileName(fileExtension)); return tempFileName; } private static string GetRandomFileName(string fileExtension) { // Start it with HttpRepl so we can easily find it if necessary for debugging, etc // Use a GUID to make it unique enough return "HttpRepl." + Guid.NewGuid().ToString() + fileExtension; } private static void VerifyDirectoryExists(string path) { Directory.CreateDirectory(Path.GetDirectoryName(path)); } } } ================================================ FILE: src/Microsoft.HttpRepl/Formatting/JsonVisitor.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Text; using Microsoft.HttpRepl.Preferences; using Microsoft.Repl.ConsoleHandling; using Newtonsoft.Json; namespace Microsoft.HttpRepl.Formatting { public static class JsonVisitor { [SuppressMessage("Design", "CA1308:Normalize strings to uppercase", Justification = "JSON is case-sensitive.")] public static string FormatAndColorize(IJsonConfig config, string jsonData) { config = config ?? throw new ArgumentNullException(nameof(config)); if (jsonData == null) { return string.Empty; } StringBuilder result = new StringBuilder(); using (JsonTextReader reader = new JsonTextReader(new StringReader(jsonData))) { bool isValuePosition = false; bool isTerminalValue = false; bool isFirstToken = true; while (reader.Read()) { if (!isValuePosition) { //If we're about to write an end object/array, we shouldn't have a comma if (reader.TokenType != JsonToken.EndArray && reader.TokenType != JsonToken.EndObject && isTerminalValue) { result.Append(",".SetColor(config.CommaColor)); } if (!isFirstToken) { result.AppendLine(); } } isFirstToken = false; if (!isValuePosition) { result.Append("".PadLeft(reader.Depth * config.IndentSize)); } isTerminalValue = false; isValuePosition = false; JsonToken type = reader.TokenType; switch (type) { case JsonToken.StartObject: result.Append("{".SetColor(config.ObjectBraceColor)); break; case JsonToken.EndObject: result.Append("}".SetColor(config.ObjectBraceColor)); isTerminalValue = true; break; case JsonToken.StartArray: result.Append("[".SetColor(config.ArrayBraceColor)); break; case JsonToken.EndArray: result.Append("]".SetColor(config.ArrayBraceColor)); isTerminalValue = true; break; case JsonToken.PropertyName: result.Append((reader.QuoteChar.ToString() + reader.Value + reader.QuoteChar).SetColor(config.NameColor) + ": ".SetColor(config.NameSeparatorColor)); isValuePosition = true; break; case JsonToken.Boolean: result.Append(reader.Value.ToString().ToLowerInvariant().SetColor(config.BoolColor)); isTerminalValue = true; break; case JsonToken.Integer: case JsonToken.Float: result.Append(reader.Value.ToString().ToLowerInvariant().SetColor(config.NumericColor)); isTerminalValue = true; break; case JsonToken.Null: result.Append("null".SetColor(config.NullColor)); isTerminalValue = true; break; case JsonToken.Comment: result.Append(("//" + reader.Value).SetColor(config.NumericColor)); break; case JsonToken.String: result.Append((reader.QuoteChar.ToString() + reader.Value + reader.QuoteChar.ToString()).SetColor(config.StringColor)); isTerminalValue = true; break; case JsonToken.Raw: case JsonToken.Date: case JsonToken.Bytes: case JsonToken.Undefined: case JsonToken.None: result.Append(reader.Value.ToString().SetColor(config.DefaultColor)); isTerminalValue = true; break; case JsonToken.EndConstructor: case JsonToken.StartConstructor: default: result.Append(reader.Value.ToString().SetColor(config.DefaultColor)); break; } } } return result.ToString(); } } } ================================================ FILE: src/Microsoft.HttpRepl/HttpState.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using Microsoft.HttpRepl.OpenApi; using Microsoft.HttpRepl.Preferences; using System.Net; using Microsoft.Repl.ConsoleHandling; namespace Microsoft.HttpRepl { public class HttpState { private readonly IPreferences _preferences; public HttpClient Client { get; } public AllowedColors ErrorColor => _preferences.GetColorValue(WellKnownPreference.ErrorColor, AllowedColors.BoldRed); public AllowedColors WarningColor => _preferences.GetColorValue(WellKnownPreference.WarningColor, AllowedColors.BoldYellow); public Stack PathSections { get; } public ApiDefinition ApiDefinition { get; set; } public Uri BaseAddress { get; set; } public IDirectoryStructure Structure => ApiDefinition?.DirectoryStructure; public bool EchoRequest { get; set; } public Dictionary> Headers { get; } public Dictionary> QueryParam { get; set; } = new(); public Uri SwaggerEndpoint { get; set; } public HttpState(IPreferences preferences, HttpClient httpClient) { preferences = preferences ?? throw new ArgumentNullException(nameof(preferences)); _preferences = preferences; Client = httpClient; PathSections = new Stack(); Headers = new Dictionary>(StringComparer.OrdinalIgnoreCase); AddDefaultHeaders(); } public string GetPrompt() { return $"{GetEffectivePathForPrompt()?.ToString() ?? "(Disconnected)"}> "; } public IEnumerable GetApplicableContentTypes(string method, string path) { if (BaseAddress is null && !Uri.IsWellFormedUriString(path, UriKind.Absolute)) { return null; } Uri effectivePath = GetEffectivePathWithoutQueryParam(path); string rootRelativePath = effectivePath.LocalPath.Substring(BaseAddress.LocalPath.Length).TrimStart('/'); IDirectoryStructure structure = Structure?.TraverseTo(rootRelativePath); IReadOnlyDictionary> contentTypesByMethod = structure?.RequestInfo?.ContentTypesByMethod; if (contentTypesByMethod != null) { if (method is null) { return contentTypesByMethod.Values.SelectMany(x => x).Distinct(StringComparer.OrdinalIgnoreCase); } if (contentTypesByMethod.TryGetValue(method, out IReadOnlyList contentTypes)) { return contentTypes; } } return null; } internal Uri GetEffectivePathWithoutQueryParam(string commandSpecifiedPath) => GetEffectivePath(BaseAddress, string.Join('/', PathSections.Reverse()), commandSpecifiedPath, queryParam: null); public Uri GetEffectivePath(string commandSpecifiedPath) => GetEffectivePath(BaseAddress, string.Join('/', PathSections.Reverse()), commandSpecifiedPath, QueryParam); internal static Uri GetEffectivePath(Uri baseAddress, string pathSections, string commandSpecifiedPath, Dictionary> queryParam) { // If an absolute uri string was already specified, just return that. if (Uri.IsWellFormedUriString(commandSpecifiedPath, UriKind.Absolute)) { return new Uri(commandSpecifiedPath, UriKind.Absolute); } // If it wasn't, and there also isn't a base address, throw an exception else if (baseAddress == null) { throw new ArgumentNullException(nameof(baseAddress), string.Format(Resources.Strings.HttpState_Error_NoAbsoluteUriNoBaseAddress, nameof(commandSpecifiedPath), nameof(baseAddress))); } pathSections = pathSections ?? throw new ArgumentNullException(nameof(pathSections)); UriBuilder builder = GetUriBuilderFromBaseAddressAndPath(baseAddress, pathSections, out string baseAndPathQuery); UpdateUriBuilderForSpecifiedPath(builder, commandSpecifiedPath, out string commandQuery); AppendQueryToBuilder(builder, baseAndPathQuery); AppendQueryToBuilder(builder, commandQuery); if (queryParam is not null) { foreach (KeyValuePair> tuple in queryParam) { foreach (var singleValue in tuple.Value) { AppendQueryToBuilder(builder, $"{WebUtility.UrlEncode(tuple.Key)}={WebUtility.UrlEncode(singleValue)}"); } } } return builder.Uri; } public Uri GetEffectivePathForPrompt() { if (BaseAddress == null) { return null; } return GetEffectivePath(BaseAddress, string.Join('/', PathSections.Reverse()), "", queryParam: null); } public string GetRelativePathString() { string pathString = "/"; if (PathSections != null && PathSections.Count > 0) { pathString += string.Join("/", PathSections.Reverse()); } return pathString; } private static UriBuilder GetUriBuilderFromBaseAddressAndPath(Uri baseAddress, string pathSections, out string query) { // Get a builder for the base address UriBuilder builder = new UriBuilder(baseAddress); // Get everything beyond the BaseAddress for the current location string path = pathSections; // Split that off into the path and the query string parameters (if any) string[] parts = path.Split('?'); query = null; // If there are some query string parameters, split the path off so it doesn't // contain them and leave the query string parameters in query if (parts.Length > 1) { path = parts[0]; query = string.Join('?', parts.Skip(1)); } if (path.StartsWith('/')) { // Set the builder path to the current path builder.Path = path; } else { // Add the current path to the builder path builder.Path += path; } return builder; } private static void UpdateUriBuilderForSpecifiedPath(UriBuilder builder, string commandSpecifiedPath, out string query) { query = null; // If the parameter has a non-empty value if (!string.IsNullOrEmpty(commandSpecifiedPath)) { // If the parameter doesn't start with a slash if (commandSpecifiedPath[0] != '/') { // If the current builder path doesn't end with a slash, then add one to the parameter string argPath = commandSpecifiedPath; if (builder.Path.Length > 0 && builder.Path[builder.Path.Length - 1] != '/') { argPath = "/" + argPath; } // Split the parameter between the path and the query string int queryIndex = argPath.IndexOf('?', StringComparison.Ordinal); string path = argPath; if (queryIndex > -1) { query = argPath.Substring(queryIndex + 1); path = argPath.Substring(0, queryIndex); } // Add just the path part of the parameter to the current builder path builder.Path += path; } // if the parameter does start with a slash else { // Split the parameter between the path and the query string int queryIndex = commandSpecifiedPath.IndexOf('?', StringComparison.Ordinal); string path = commandSpecifiedPath; if (queryIndex > -1) { query = commandSpecifiedPath.Substring(queryIndex + 1); path = commandSpecifiedPath.Substring(0, queryIndex); } // Set the builder path to just the path part of the parameter builder.Path = path; } } } private static void AppendQueryToBuilder(UriBuilder builder, string query) { if (query != null) { if (!string.IsNullOrEmpty(builder.Query)) { query = "&" + query; } builder.Query += query; } } private void AddDefaultHeaders() { Headers["User-Agent"] = new[] { _preferences.GetValue(WellKnownPreference.HttpClientUserAgent, "HTTP-REPL") }; } public void ResetState(bool persistHeaders = false, bool persistPath = false) { BaseAddress = null; SwaggerEndpoint = null; if (!persistHeaders) { Headers.Clear(); AddDefaultHeaders(); } if (!persistPath) { PathSections.Clear(); } } } } ================================================ FILE: src/Microsoft.HttpRepl/IDirectoryStructure.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Collections.Generic; namespace Microsoft.HttpRepl { public interface IDirectoryStructure { IEnumerable DirectoryNames { get; } IDirectoryStructure Parent { get; } IDirectoryStructure GetChildDirectory(string name); IRequestInfo RequestInfo { get; } } } ================================================ FILE: src/Microsoft.HttpRepl/IRequestInfo.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Collections.Generic; namespace Microsoft.HttpRepl { public interface IRequestInfo { IReadOnlyDictionary> ContentTypesByMethod { get; } IReadOnlyList Methods { get; } string GetRequestBodyForContentType(ref string contentType, string method); } } ================================================ FILE: src/Microsoft.HttpRepl/IUriLauncher.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Threading.Tasks; namespace Microsoft.HttpRepl { public interface IUriLauncher { Task LaunchUriAsync(Uri uri); } } ================================================ FILE: src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj ================================================ Exe net8.0 Major httprepl The HTTP Read-Eval-Print Loop (REPL) is a lightweight, cross-platform command-line tool that's supported everywhere .NET Core is supported and is used for making HTTP requests to test ASP.NET Core web APIs and view their results. Microsoft.dotnet-httprepl dotnet;http;httprepl https://aka.ms/http-repl-doc true true win-x64;win-x86 True True Strings.resx ResXFileCodeGenerator Strings.Designer.cs ================================================ FILE: src/Microsoft.HttpRepl/OpenApi/ApiDefinition.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace Microsoft.HttpRepl.OpenApi { public class ApiDefinition { public IList BaseAddresses { get; } = new List(); public IDirectoryStructure DirectoryStructure { get; set; } [SuppressMessage("Design", "CA1724:Type names should not match namespaces", Justification = "This is a valid name for this type.")] public class Server { public Uri Url { get; set; } public string Description { get; set; } } } } ================================================ FILE: src/Microsoft.HttpRepl/OpenApi/ApiDefinitionParseResult.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. #nullable enable using System; using System.Collections.Generic; namespace Microsoft.HttpRepl.OpenApi { public class ApiDefinitionParseResult { public static ApiDefinitionParseResult Failed { get; } = new ApiDefinitionParseResult(false, null, null); public bool Success { get; private set; } public IReadOnlyCollection ValidationMessages { get; private set; } public ApiDefinition? ApiDefinition { get; private set; } public ApiDefinitionParseResult(bool success, ApiDefinition? apiDefinition, IEnumerable? validationMessages) { Success = success; ApiDefinition = apiDefinition; ValidationMessages = validationMessages is null ? Array.Empty() : new List(validationMessages); } } } ================================================ FILE: src/Microsoft.HttpRepl/OpenApi/ApiDefinitionReader.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. #nullable enable using System; using System.Collections.Generic; namespace Microsoft.HttpRepl.OpenApi { internal class ApiDefinitionReader { private readonly List _readers = new List { new OpenApiDotNetApiDefinitionReader(), }; internal void RegisterReader(IApiDefinitionReader reader) { _readers.Add(reader); } public ApiDefinitionParseResult CanHandle(string document) { if (document is null) { return ApiDefinitionParseResult.Failed; } foreach (IApiDefinitionReader reader in _readers) { ApiDefinitionParseResult result = reader.CanHandle(document); if (result.Success) { return result; } } return ApiDefinitionParseResult.Failed; } public ApiDefinitionParseResult Read(string document, Uri? swaggerUri) { foreach (IApiDefinitionReader reader in _readers) { ApiDefinitionParseResult parseResult = reader.CanHandle(document); if (parseResult.Success) { ApiDefinitionParseResult result = reader.ReadDefinition(document, swaggerUri); return result; } } return ApiDefinitionParseResult.Failed; } public static void FillDirectoryInfo(DirectoryStructure parent, EndpointMetadata entry) { entry = entry ?? throw new ArgumentNullException(nameof(entry)); parent = parent ?? throw new ArgumentNullException(nameof(parent)); string[] parts = entry.Path.Split('/'); foreach (string part in parts) { if (!string.IsNullOrEmpty(part) && parent is object) { parent = parent.DeclareDirectory(part); } } RequestInfo dirRequestInfo = new RequestInfo(); foreach (RequestMetadata requestMetadata in entry.AvailableRequests) { string method = requestMetadata.Operation.ToString(); foreach (RequestContentMetadata content in requestMetadata.Content) { if (string.IsNullOrWhiteSpace(content.ContentType)) { dirRequestInfo.SetFallbackRequestBody(method, content.ContentType, SchemaDataGenerator.GetBodyString(content.BodySchema)); } dirRequestInfo.SetRequestBody(method, content.ContentType, SchemaDataGenerator.GetBodyString(content.BodySchema)); } dirRequestInfo.AddMethod(requestMetadata.Operation.ToString()); } if (dirRequestInfo.Methods.Count > 0 && parent is object) { parent.RequestInfo = dirRequestInfo; } } } } ================================================ FILE: src/Microsoft.HttpRepl/OpenApi/EndpointMetadata.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Collections.Generic; using Microsoft.OpenApi.Models; namespace Microsoft.HttpRepl.OpenApi { internal class EndpointMetadata { public EndpointMetadata(string path) { Path = path; } public string Path { get; } public ICollection AvailableRequests { get; } = new List(); } internal class RequestMetadata { public RequestMetadata(OperationType operation) { Operation = operation; } public OperationType Operation { get; } public ICollection Content { get; } = new List(); public ICollection Parameters { get; } = new List(); } internal class RequestContentMetadata { public RequestContentMetadata(string contentType, bool isRequired, OpenApiSchema bodySchema) { ContentType = contentType; IsRequired = isRequired; BodySchema = bodySchema; } public string ContentType { get; } public bool IsRequired { get; } public OpenApiSchema BodySchema { get; } } } ================================================ FILE: src/Microsoft.HttpRepl/OpenApi/IApiDefinitionReader.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. #nullable enable using System; namespace Microsoft.HttpRepl.OpenApi { public interface IApiDefinitionReader { ApiDefinitionParseResult CanHandle(string document); ApiDefinitionParseResult ReadDefinition(string document, Uri? sourceUri); } } ================================================ FILE: src/Microsoft.HttpRepl/OpenApi/IOpenApiSearchPathsProvider.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. #nullable enable using System.Collections.Generic; namespace Microsoft.HttpRepl.OpenApi { internal interface IOpenApiSearchPathsProvider { IEnumerable GetOpenApiSearchPaths(); } } ================================================ FILE: src/Microsoft.HttpRepl/OpenApi/OpenApiDotNetApiDefinitionReader.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. #nullable enable using System; using System.Collections.Generic; using System.Linq; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers; namespace Microsoft.HttpRepl.OpenApi { internal class OpenApiDotNetApiDefinitionReader : IApiDefinitionReader { public ApiDefinitionParseResult CanHandle(string document) { OpenApiStringReader reader = new(); try { OpenApiDocument openApiDocument = reader.Read(document, out OpenApiDiagnostic diagnostic); IEnumerable messages = diagnostic.Errors.Select(e => e.ToString()); return new ApiDefinitionParseResult(true, null, messages); } catch { return ApiDefinitionParseResult.Failed; } } public ApiDefinitionParseResult ReadDefinition(string document, Uri? sourceUri) { OpenApiStringReader reader = new(); OpenApiDocument openApiDocument = reader.Read(document, out OpenApiDiagnostic diagnostic); ApiDefinition apiDefinition = new ApiDefinition(); ReadServers(apiDefinition, sourceUri, openApiDocument); IList metadata = ReadPaths(openApiDocument); apiDefinition.DirectoryStructure = BuildDirectoryStructure(metadata); return new ApiDefinitionParseResult(true, apiDefinition, diagnostic.Errors.Select(e => e.ToString())); } private static void ReadServers(ApiDefinition apiDefinition, Uri? sourceUri, OpenApiDocument openApiDocument) { foreach (OpenApiServer server in openApiDocument.Servers) { string? url = server.Url?.EnsureTrailingSlash(); string description = server.Description; if (url is null) { continue; } if (Uri.IsWellFormedUriString(url, UriKind.Absolute) && Uri.TryCreate(url, UriKind.Absolute, out Uri? absoluteServerUri)) { apiDefinition.BaseAddresses.Add(new ApiDefinition.Server() { Url = absoluteServerUri, Description = description }); } else if (Uri.TryCreate(sourceUri, url, out Uri? relativeServerUri)) { apiDefinition.BaseAddresses.Add(new ApiDefinition.Server() { Url = relativeServerUri, Description = description }); } } } private static IList ReadPaths(OpenApiDocument openApiDocument) { List metadata = new List(); if (openApiDocument.Paths is not null) { foreach (KeyValuePair path in openApiDocument.Paths) { string relativeUrl = path.Key; EndpointMetadata endpointMetadata = ReadOperations(relativeUrl, path.Value.Operations); metadata.Add(endpointMetadata); } } return metadata; } private static EndpointMetadata ReadOperations(string path, IDictionary operations) { EndpointMetadata endpointMetadata = new EndpointMetadata(path); if (operations is not null) { foreach (KeyValuePair operation in operations) { RequestMetadata requestMetadata = new RequestMetadata(operation.Key); foreach (OpenApiParameter parameter in operation.Value.Parameters) { requestMetadata.Parameters.Add(parameter); } if (operation.Value.RequestBody?.Content is not null) { foreach (KeyValuePair content in operation.Value.RequestBody.Content) { string contentType = content.Key; bool isRequired = operation.Value.RequestBody.Required; OpenApiSchema? schema = content.Value?.Schema; requestMetadata.Content.Add(new RequestContentMetadata(contentType, isRequired, schema)); } } endpointMetadata.AvailableRequests.Add(requestMetadata); } } return endpointMetadata; } private static DirectoryStructure BuildDirectoryStructure(IList metadata) { DirectoryStructure d = new DirectoryStructure(null); if (metadata is not null) { for (int index = 0; index < metadata.Count; index++) { System.Diagnostics.Debug.WriteLine(index); EndpointMetadata entry = metadata[index]; ApiDefinitionReader.FillDirectoryInfo(d, entry); } } return d; } } } ================================================ FILE: src/Microsoft.HttpRepl/OpenApi/SchemaDataGenerator.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using Microsoft.OpenApi.Models; using Newtonsoft.Json.Linq; namespace Microsoft.HttpRepl.OpenApi { internal static class SchemaDataGenerator { // TODO: Make this a setting? private const int MaxExampleDataDepth = 3; public static string GetBodyString(OpenApiSchema schema) { if (schema is not null) { JToken result = GenerateData(schema); return result?.ToString() ?? "{\n}"; } return null; } internal static JToken GenerateData(OpenApiSchema schema, int depth = 0) { if (schema is null) { return null; } if (schema.Example is not null) { return JToken.FromObject(schema.Example); } if (schema.Default is not null) { return JToken.FromObject(schema.Default); } if (schema.Type is null) { if (schema.Properties is not null || schema.AdditionalProperties is not null || schema.MinProperties.HasValue || schema.MaxProperties.HasValue) { schema.Type = "OBJECT"; } else if (schema.Items is not null || schema.MinItems.HasValue || schema.MaxItems.HasValue) { schema.Type = "ARRAY"; } else if (schema.Minimum.HasValue || schema.Maximum.HasValue || schema.MultipleOf.HasValue) { schema.Type = "INTEGER"; } } switch (schema.Type?.ToUpperInvariant()) { case null: case "STRING": if (string.Equals(schema.Format, "date-time", StringComparison.OrdinalIgnoreCase)) { return DateTimeOffset.Now.ToString("o"); } return ""; case "NUMBER": if (schema.Minimum.HasValue) { if (schema.Maximum.HasValue) { return (schema.Maximum.Value + schema.Minimum.Value) / 2; } if (schema.ExclusiveMinimum is true) { return schema.Minimum.Value + 1; } return schema.Minimum.Value; } return 1.1; case "INTEGER": if (schema.Minimum.HasValue) { if (schema.Maximum.HasValue) { return (int)((schema.Maximum.Value + schema.Minimum.Value) / 2); } if (schema.ExclusiveMinimum is true) { return schema.Minimum.Value + 1; } return schema.Minimum.Value; } return 0; case "BOOLEAN": return true; case "ARRAY": JArray container = new JArray(); if (depth < MaxExampleDataDepth) { JToken item = GenerateData(schema.Items, depth + 1) ?? ""; int count = schema.MinItems.GetValueOrDefault(); count = Math.Max(1, count); for (int i = 0; i < count; ++i) { container.Add(item.DeepClone()); } } return container; case "OBJECT": if (schema.Properties is not null) { JObject obj = new JObject(); if (depth < MaxExampleDataDepth) { foreach (KeyValuePair property in schema.Properties) { if (property.Value.ReadOnly) { continue; } JToken data = GenerateData(property.Value, depth + 1) ?? ""; obj[property.Key] = data; } } return obj; } return null; } return null; } } } ================================================ FILE: src/Microsoft.HttpRepl/Preferences/IJsonConfig.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using Microsoft.Repl.ConsoleHandling; namespace Microsoft.HttpRepl.Preferences { public interface IJsonConfig { int IndentSize { get; } AllowedColors DefaultColor { get; } AllowedColors ArrayBraceColor { get; } AllowedColors ObjectBraceColor { get; } AllowedColors CommaColor { get; } AllowedColors NameColor { get; } AllowedColors NameSeparatorColor { get; } AllowedColors BoolColor { get; } AllowedColors NumericColor { get; } AllowedColors StringColor { get; } AllowedColors NullColor { get; } } } ================================================ FILE: src/Microsoft.HttpRepl/Preferences/IPreferences.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Collections.Generic; using Microsoft.Repl.ConsoleHandling; namespace Microsoft.HttpRepl.Preferences { public interface IPreferences { AllowedColors GetColorValue(string preference, AllowedColors defaultValue = AllowedColors.None); int GetIntValue(string preference, int defaultValue = default); bool GetBoolValue(string preference, bool defaultValue = default); string GetValue(string preference, string defaultValue = default); bool TryGetValue(string preference, out string value); bool SetValue(string preference, string value); IReadOnlyDictionary DefaultPreferences { get; } IReadOnlyDictionary CurrentPreferences { get; } } } ================================================ FILE: src/Microsoft.HttpRepl/Preferences/JsonConfig.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using Microsoft.Repl.ConsoleHandling; namespace Microsoft.HttpRepl.Preferences { public class JsonConfig : IJsonConfig { private readonly IPreferences _preferences; public int IndentSize => _preferences.GetIntValue(WellKnownPreference.JsonIndentSize, 2); public AllowedColors DefaultColor => _preferences.GetColorValue(WellKnownPreference.JsonColor); private AllowedColors DefaultBraceColor => _preferences.GetColorValue(WellKnownPreference.JsonBraceColor, DefaultSyntaxColor); private AllowedColors DefaultSyntaxColor => _preferences.GetColorValue(WellKnownPreference.JsonSyntaxColor, DefaultColor); private AllowedColors DefaultLiteralColor => _preferences.GetColorValue(WellKnownPreference.JsonLiteralColor, DefaultColor); public AllowedColors ArrayBraceColor => _preferences.GetColorValue(WellKnownPreference.JsonArrayBraceColor, DefaultBraceColor); public AllowedColors ObjectBraceColor => _preferences.GetColorValue(WellKnownPreference.JsonObjectBraceColor, DefaultBraceColor); public AllowedColors CommaColor => _preferences.GetColorValue(WellKnownPreference.JsonCommaColor, DefaultSyntaxColor); public AllowedColors NameColor => _preferences.GetColorValue(WellKnownPreference.JsonNameColor, StringColor); public AllowedColors NameSeparatorColor => _preferences.GetColorValue(WellKnownPreference.JsonNameSeparatorColor, DefaultSyntaxColor); public AllowedColors BoolColor => _preferences.GetColorValue(WellKnownPreference.JsonBoolColor, DefaultLiteralColor); public AllowedColors NumericColor => _preferences.GetColorValue(WellKnownPreference.JsonNumericColor, DefaultLiteralColor); public AllowedColors StringColor => _preferences.GetColorValue(WellKnownPreference.JsonStringColor, DefaultLiteralColor); public AllowedColors NullColor => _preferences.GetColorValue(WellKnownPreference.JsonNullColor, DefaultLiteralColor); public JsonConfig(IPreferences preferences) { _preferences = preferences; } } } ================================================ FILE: src/Microsoft.HttpRepl/Preferences/OpenApiSearchPathsProvider.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. #nullable enable using System; using System.Collections.Generic; using System.Linq; using Microsoft.HttpRepl.OpenApi; namespace Microsoft.HttpRepl.Preferences { internal sealed class OpenApiSearchPathsProvider : IOpenApiSearchPathsProvider { // OpenAPI description search paths are appended to the base url to // attempt to find the description document. A search path is a // relative url that is appended to the base url using Uri.TryCreate, // so the semantics of relative urls matter here. // Example: Base path https://localhost/v1/ and search path openapi.json // will result in https://localhost/v1/openapi.json being tested. // Example: Base path https://localhost/v1/ and search path /openapi.json // will result in https://localhost/openapi.json being tested. internal static IEnumerable DefaultSearchPaths { get; } = new[] { "swagger.json", "/swagger.json", "swagger/v1/swagger.json", "/swagger/v1/swagger.json", "openapi.json", "/openapi.json", }; private readonly IPreferences _preferences; public OpenApiSearchPathsProvider(IPreferences preferences) { _preferences = preferences ?? throw new ArgumentNullException(nameof(preferences)); } public IEnumerable GetOpenApiSearchPaths() { string[] configSearchPaths = Split(_preferences.GetValue(WellKnownPreference.SwaggerSearchPaths)); if (configSearchPaths.Length > 0) { return configSearchPaths; } string[] addToSearchPaths = Split(_preferences.GetValue(WellKnownPreference.SwaggerAddToSearchPaths)); string[] removeFromSearchPaths = Split(_preferences.GetValue(WellKnownPreference.SwaggerRemoveFromSearchPaths)); return DefaultSearchPaths.Union(addToSearchPaths).Except(removeFromSearchPaths); } private static string[] Split(string searchPaths) { if (string.IsNullOrWhiteSpace(searchPaths)) { return Array.Empty(); } else { return searchPaths.Split('|', StringSplitOptions.RemoveEmptyEntries); } } } } ================================================ FILE: src/Microsoft.HttpRepl/Preferences/RequestConfig.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using Microsoft.Repl.ConsoleHandling; namespace Microsoft.HttpRepl.Preferences { public class RequestConfig : RequestOrResponseConfig { public RequestConfig(IPreferences preferences) : base(preferences) { } public override AllowedColors BodyColor => Preferences.GetColorValue(WellKnownPreference.RequestBodyColor, base.BodyColor); public override AllowedColors SchemeColor => Preferences.GetColorValue(WellKnownPreference.RequestSchemeColor, base.SchemeColor); public override AllowedColors HeaderKeyColor => Preferences.GetColorValue(WellKnownPreference.RequestHeaderKeyColor, base.HeaderKeyColor); public override AllowedColors HeaderSeparatorColor => Preferences.GetColorValue(WellKnownPreference.RequestHeaderSeparatorColor, base.HeaderSeparatorColor); public override AllowedColors HeaderValueSeparatorColor => Preferences.GetColorValue(WellKnownPreference.RequestHeaderValueSeparatorColor, base.HeaderValueSeparatorColor); public override AllowedColors HeaderValueColor => Preferences.GetColorValue(WellKnownPreference.RequestHeaderValueColor, base.HeaderValueColor); public override AllowedColors HeaderColor => Preferences.GetColorValue(WellKnownPreference.RequestHeaderColor, base.HeaderColor); public override AllowedColors GeneralColor => Preferences.GetColorValue(WellKnownPreference.RequestColor, base.GeneralColor); public override AllowedColors ProtocolColor => Preferences.GetColorValue(WellKnownPreference.RequestProtocolColor, base.ProtocolColor); public override AllowedColors ProtocolNameColor => Preferences.GetColorValue(WellKnownPreference.RequestProtocolNameColor, base.ProtocolNameColor); public override AllowedColors ProtocolVersionColor => Preferences.GetColorValue(WellKnownPreference.RequestProtocolVersionColor, base.ProtocolVersionColor); public override AllowedColors ProtocolSeparatorColor => Preferences.GetColorValue(WellKnownPreference.RequestProtocolSeparatorColor, base.ProtocolSeparatorColor); public override AllowedColors StatusColor => Preferences.GetColorValue(WellKnownPreference.RequestStatusColor, base.StatusColor); public override AllowedColors StatusCodeColor => Preferences.GetColorValue(WellKnownPreference.RequestStatusCodeColor, base.StatusCodeColor); public override AllowedColors StatusReasonPhraseColor => Preferences.GetColorValue(WellKnownPreference.RequestStatusReaseonPhraseColor, base.StatusReasonPhraseColor); public AllowedColors MethodColor => Preferences.GetColorValue(WellKnownPreference.RequestMethodColor, GeneralColor); public AllowedColors AddressColor => Preferences.GetColorValue(WellKnownPreference.RequestAddressColor, GeneralColor); } } ================================================ FILE: src/Microsoft.HttpRepl/Preferences/RequestOrResponseConfig.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using Microsoft.Repl.ConsoleHandling; namespace Microsoft.HttpRepl.Preferences { public abstract class RequestOrResponseConfig { protected IPreferences Preferences { get; } protected RequestOrResponseConfig(IPreferences preferences) { Preferences = preferences; } public virtual AllowedColors BodyColor => Preferences.GetColorValue(WellKnownPreference.BodyColor, GeneralColor); public virtual AllowedColors SchemeColor => Preferences.GetColorValue(WellKnownPreference.SchemeColor, GeneralColor); public virtual AllowedColors HeaderKeyColor => Preferences.GetColorValue(WellKnownPreference.HeaderKeyColor, HeaderColor); public virtual AllowedColors HeaderSeparatorColor => Preferences.GetColorValue(WellKnownPreference.HeaderSeparatorColor, HeaderColor); public virtual AllowedColors HeaderValueSeparatorColor => Preferences.GetColorValue(WellKnownPreference.HeaderValueSeparatorColor, HeaderSeparatorColor); public virtual AllowedColors HeaderValueColor => Preferences.GetColorValue(WellKnownPreference.HeaderValueColor, HeaderColor); public virtual AllowedColors HeaderColor => Preferences.GetColorValue(WellKnownPreference.HeaderColor, GeneralColor); public virtual AllowedColors GeneralColor => Preferences.GetColorValue(WellKnownPreference.RequestOrResponseColor); public virtual AllowedColors ProtocolColor => Preferences.GetColorValue(WellKnownPreference.ProtocolColor, GeneralColor); public virtual AllowedColors ProtocolNameColor => Preferences.GetColorValue(WellKnownPreference.ProtocolNameColor, ProtocolColor); public virtual AllowedColors ProtocolVersionColor => Preferences.GetColorValue(WellKnownPreference.ProtocolVersionColor, ProtocolColor); public virtual AllowedColors ProtocolSeparatorColor => Preferences.GetColorValue(WellKnownPreference.ProtocolSeparatorColor, ProtocolColor); public virtual AllowedColors StatusColor => Preferences.GetColorValue(WellKnownPreference.StatusColor, GeneralColor); public virtual AllowedColors StatusCodeColor => Preferences.GetColorValue(WellKnownPreference.StatusCodeColor, StatusColor); public virtual AllowedColors StatusReasonPhraseColor => Preferences.GetColorValue(WellKnownPreference.StatusReaseonPhraseColor, StatusColor); } } ================================================ FILE: src/Microsoft.HttpRepl/Preferences/ResponseConfig.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using Microsoft.Repl.ConsoleHandling; namespace Microsoft.HttpRepl.Preferences { public class ResponseConfig : RequestOrResponseConfig { public ResponseConfig(IPreferences preferences) : base(preferences) { } public override AllowedColors BodyColor => Preferences.GetColorValue(WellKnownPreference.ResponseBodyColor, base.BodyColor); public override AllowedColors SchemeColor => Preferences.GetColorValue(WellKnownPreference.ResponseSchemeColor, base.SchemeColor); public override AllowedColors HeaderKeyColor => Preferences.GetColorValue(WellKnownPreference.ResponseHeaderKeyColor, base.HeaderKeyColor); public override AllowedColors HeaderSeparatorColor => Preferences.GetColorValue(WellKnownPreference.ResponseHeaderSeparatorColor, base.HeaderSeparatorColor); public override AllowedColors HeaderValueSeparatorColor => Preferences.GetColorValue(WellKnownPreference.ResponseHeaderValueSeparatorColor, base.HeaderValueSeparatorColor); public override AllowedColors HeaderValueColor => Preferences.GetColorValue(WellKnownPreference.ResponseHeaderValueColor, base.HeaderValueColor); public override AllowedColors HeaderColor => Preferences.GetColorValue(WellKnownPreference.ResponseHeaderColor, base.HeaderColor); public override AllowedColors GeneralColor => Preferences.GetColorValue(WellKnownPreference.ResponseColor, base.GeneralColor); public override AllowedColors ProtocolColor => Preferences.GetColorValue(WellKnownPreference.ResponseProtocolColor, base.ProtocolColor); public override AllowedColors ProtocolNameColor => Preferences.GetColorValue(WellKnownPreference.ResponseProtocolNameColor, base.ProtocolNameColor); public override AllowedColors ProtocolVersionColor => Preferences.GetColorValue(WellKnownPreference.ResponseProtocolVersionColor, base.ProtocolVersionColor); public override AllowedColors ProtocolSeparatorColor => Preferences.GetColorValue(WellKnownPreference.ResponseProtocolSeparatorColor, base.ProtocolSeparatorColor); public override AllowedColors StatusColor => Preferences.GetColorValue(WellKnownPreference.ResponseStatusColor, base.StatusColor); public override AllowedColors StatusCodeColor => Preferences.GetColorValue(WellKnownPreference.ResponseStatusCodeColor, base.StatusCodeColor); public override AllowedColors StatusReasonPhraseColor => Preferences.GetColorValue(WellKnownPreference.ResponseStatusReaseonPhraseColor, base.StatusReasonPhraseColor); } } ================================================ FILE: src/Microsoft.HttpRepl/Preferences/UserFolderPreferences.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Linq; using Microsoft.HttpRepl.FileSystem; using Microsoft.HttpRepl.UserProfile; using Microsoft.Repl.ConsoleHandling; namespace Microsoft.HttpRepl.Preferences { public class UserFolderPreferences : IPreferences { private string _prefsFilePath; private readonly IFileSystem _fileSystem; private readonly IUserProfileDirectoryProvider _userProfileDirectoryProvider; private Dictionary _preferences; private Dictionary Preferences { get { if (_preferences == null) { _preferences = ReadPreferences(DefaultPreferences); } return _preferences; } } public IReadOnlyDictionary DefaultPreferences { get; } public string PreferencesFilePath { get { if (_prefsFilePath == null) { string userProfileDirectory = _userProfileDirectoryProvider.GetUserProfileDirectory(); _prefsFilePath = Path.Combine(userProfileDirectory, ".httpreplprefs"); } return _prefsFilePath; } } public UserFolderPreferences(IFileSystem fileSystem, IUserProfileDirectoryProvider userProfileDirectoryProvider, IDictionary defaultPreferences) { _fileSystem = fileSystem; _userProfileDirectoryProvider = userProfileDirectoryProvider; DefaultPreferences = SetupDefaults(defaultPreferences); } public AllowedColors GetColorValue(string preference, AllowedColors defaultValue = AllowedColors.None) { if (!Preferences.TryGetValue(preference, out string preferenceValueString) || !Enum.TryParse(preferenceValueString, true, out AllowedColors result)) { result = defaultValue; } return result; } public int GetIntValue(string preference, int defaultValue) { if (!Preferences.TryGetValue(preference, out string preferenceValueString) || !int.TryParse(preferenceValueString, out int result)) { result = defaultValue; } return result; } public bool GetBoolValue(string preference, bool defaultValue) { if (!Preferences.TryGetValue(preference, out string preferenceValueString) || !bool.TryParse(preferenceValueString, out bool result)) { result = defaultValue; } return result; } public string GetValue(string preference, string defaultValue = default) { if (!Preferences.TryGetValue(preference, out string result)) { result = defaultValue; } return result; } public bool TryGetValue(string preference, out string value) { return Preferences.TryGetValue(preference, out value); } public bool SetValue(string preference, string value) { if (string.IsNullOrEmpty(value)) { if (!DefaultPreferences.TryGetValue(preference, out string defaultValue)) { Preferences.Remove(preference); } else { Preferences[preference] = defaultValue; } } else { Preferences[preference] = value; } return WritePreferences(Preferences, DefaultPreferences); } public IReadOnlyDictionary CurrentPreferences { get { return new ReadOnlyDictionary(Preferences); } } private Dictionary ReadPreferences(IReadOnlyDictionary defaultPreferences) { Dictionary preferences = new Dictionary(defaultPreferences); if (_fileSystem.FileExists(PreferencesFilePath)) { string[] prefsFile = _fileSystem.ReadAllLinesFromFile(PreferencesFilePath); foreach (string line in prefsFile) { int equalsIndex = line.IndexOf('=', StringComparison.Ordinal); // If there's no = or = is the first character on the line // (meaning no preference name), move to the next line if (equalsIndex <= 0) { continue; } preferences[line.Substring(0, equalsIndex)] = line.Substring(equalsIndex + 1); } } return preferences; } private bool WritePreferences(Dictionary preferences, IReadOnlyDictionary defaultPreferences) { List lines = new List(); foreach (KeyValuePair entry in preferences.OrderBy(x => x.Key)) { //If the value didn't exist in the defaults or the value's different, include it in the user's preferences file if (!defaultPreferences.TryGetValue(entry.Key, out string value) || !string.Equals(value, entry.Value, StringComparison.Ordinal)) { lines.Add($"{entry.Key}={entry.Value}"); } } try { _fileSystem.WriteAllLinesToFile(PreferencesFilePath, lines); return true; } catch { return false; } } private static IReadOnlyDictionary SetupDefaults(IDictionary defaults) { Dictionary tempDictionary = new Dictionary(); if (defaults != null) { foreach (KeyValuePair kvp in defaults) { tempDictionary.Add(kvp.Key, kvp.Value); } } return new ReadOnlyDictionary(tempDictionary); } } } ================================================ FILE: src/Microsoft.HttpRepl/Preferences/WellKnownPreference.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Collections.Generic; using System.Reflection; namespace Microsoft.HttpRepl.Preferences { public static class WellKnownPreference { public static class Catalog { private static IReadOnlyList _names; public static IReadOnlyList Names { get { if (_names != null) { return _names; } List matchingProperties = new List(); foreach (PropertyInfo property in typeof(WellKnownPreference).GetProperties(BindingFlags.Public | BindingFlags.Static)) { if (property.PropertyType == typeof(string) && property.GetMethod != null && property.GetValue(null) is string val) { matchingProperties.Add(val); } } return _names = matchingProperties; } } } public static string JsonArrayBraceColor { get; } = "colors.json.arrayBrace"; public static string JsonObjectBraceColor { get; } = "colors.json.objectBrace"; public static string JsonNameColor { get; } = "colors.json.name"; public static string JsonNameSeparatorColor { get; } = "colors.json.nameSeparator"; public static string JsonIndentSize { get; } = "formatting.json.indentSize"; public static string JsonCommaColor { get; } = "colors.json.comma"; public static string JsonLiteralColor { get; } = "colors.json.literal"; public static string JsonNullColor { get; } = "colors.json.null"; public static string JsonBoolColor { get; } = "colors.json.bool"; public static string JsonNumericColor { get; } = "colors.json.numeric"; public static string JsonStringColor { get; } = "colors.json.string"; public static string JsonColor { get; } = "colors.json"; public static string JsonSyntaxColor { get; } = "colors.json.syntax"; public static string JsonBraceColor { get; } = "colors.json.brace"; public static string RequestColor { get; } = "colors.request"; public static string RequestBodyColor { get; } = "colors.request.body"; public static string RequestSchemeColor { get; } = "colors.request.scheme"; public static string RequestHeaderKeyColor { get; } = "colors.request.header.key"; public static string RequestHeaderSeparatorColor { get; } = "colors.request.header.separator"; public static string RequestHeaderValueSeparatorColor { get; } = "colors.request.header.valueSeparator"; public static string RequestHeaderValueColor { get; } = "colors.request.header.value"; public static string RequestHeaderColor { get; } = "colors.request.header"; public static string RequestProtocolColor { get; } = "colors.request.protocol"; public static string RequestProtocolNameColor { get; } = "colors.request.protocol.name"; public static string RequestProtocolSeparatorColor { get; } = "colors.request.protocol.separator"; public static string RequestProtocolVersionColor { get; } = "colors.request.protocol.version"; public static string RequestStatusColor { get; } = "colors.request.status"; public static string RequestStatusCodeColor { get; } = "colors.request.status.code"; public static string RequestStatusReaseonPhraseColor { get; } = "colors.request.status.reasonPhrase"; public static string RequestMethodColor { get; } = "colors.request.method"; public static string RequestAddressColor { get; } = "colors.request.address"; public static string ResponseColor { get; } = "colors.response"; public static string ResponseBodyColor { get; } = "colors.response.body"; public static string ResponseSchemeColor { get; } = "colors.response.scheme"; public static string ResponseHeaderKeyColor { get; } = "colors.response.header.key"; public static string ResponseHeaderSeparatorColor { get; } = "colors.response.header.separator"; public static string ResponseHeaderValueSeparatorColor { get; } = "colors.response.header.valueSeparator"; public static string ResponseHeaderValueColor { get; } = "colors.response.header.value"; public static string ResponseHeaderColor { get; } = "colors.response.header"; public static string ResponseProtocolColor { get; } = "colors.response.protocol"; public static string ResponseProtocolNameColor { get; } = "colors.response.protocol.name"; public static string ResponseProtocolSeparatorColor { get; } = "colors.response.protocol.separator"; public static string ResponseProtocolVersionColor { get; } = "colors.response.protocol.version"; public static string ResponseStatusColor { get; } = "colors.response.status"; public static string ResponseStatusCodeColor { get; } = "colors.response.status.code"; public static string ResponseStatusReaseonPhraseColor { get; } = "colors.response.status.reasonPhrase"; public static string RequestOrResponseColor { get; } = "colors.requestOrResponse"; public static string ErrorColor { get; } = "colors.error"; public static string WarningColor { get; } = "colors.warning"; public static string BodyColor { get; } = "colors.body"; public static string SchemeColor { get; } = "colors.scheme"; public static string HeaderKeyColor { get; } = "colors.header.key"; public static string HeaderSeparatorColor { get; } = "colors.header.separator"; public static string HeaderValueSeparatorColor { get; } = "colors.header.valueSeparator"; public static string HeaderValueColor { get; } = "colors.header.value"; public static string HeaderColor { get; } = "colors.header"; public static string ProtocolColor { get; } = "colors.protocol"; public static string ProtocolNameColor { get; } = "colors.protocol.name"; public static string ProtocolSeparatorColor { get; } = "colors.protocol.separator"; public static string ProtocolVersionColor { get; } = "colors.protocol.version"; public static string StatusColor { get; } = "colors.status"; public static string StatusCodeColor { get; } = "colors.status.code"; public static string StatusReaseonPhraseColor { get; } = "colors.status.reasonPhrase"; public static string DefaultEditorCommand { get; } = "editor.command.default"; public static string DefaultEditorArguments { get; } = "editor.command.default.arguments"; public static string SwaggerRequeryBehavior { get; } = "swagger.requery"; public static string SwaggerSearchPaths { get; } = "swagger.searchPaths"; public static string SwaggerUIEndpoint { get; } = "swagger.uiEndpoint"; public static string SwaggerRemoveFromSearchPaths => "swagger.removeFromSearchPaths"; public static string SwaggerAddToSearchPaths => "swagger.addToSearchPaths"; public static string UseDefaultCredentials { get; } = "httpClient.useDefaultCredentials"; public static string HttpClientUserAgent { get; } = "httpClient.userAgent"; public static string ProxyUseDefaultCredentials => "httpClient.proxy.useDefaultCredentials"; public static string ConnectCommandSkipRootFix => "connectCommand.skipRootFix"; } } ================================================ FILE: src/Microsoft.HttpRepl/Program.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.HttpRepl.Commands; using Microsoft.HttpRepl.FileSystem; using Microsoft.HttpRepl.Preferences; using Microsoft.HttpRepl.Telemetry; using Microsoft.HttpRepl.UserProfile; using Microsoft.Repl; using Microsoft.Repl.Commanding; using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Parsing; namespace Microsoft.HttpRepl { public static class Program { public static async Task Main(string[] args) { await Start(args); } public static async Task Start(string[] args, IConsoleManager consoleManager = null, IPreferences preferences = null, ITelemetry telemetry = null) { args = args ?? throw new ArgumentNullException(nameof(args)); RegisterEncodingProviders(); ComposeDependencies(ref consoleManager, ref preferences, ref telemetry, out HttpState state, out Shell shell); if (!telemetry.FirstTimeUseNoticeSentinel.Exists() && !Telemetry.Telemetry.SkipFirstTimeExperience) { Reporter.Output.WriteLine(string.Format(Resources.Strings.Telemetry_WelcomeMessage, VersionSensor.AssemblyVersion.ToString(2))); telemetry.FirstTimeUseNoticeSentinel.CreateIfNotExists(); } if (Console.IsOutputRedirected && !consoleManager.AllowOutputRedirection) { telemetry.TrackStartedEvent(withOutputRedirection: true); Reporter.Error.WriteLine(Resources.Strings.Error_OutputRedirected.SetColor(preferences.GetColorValue(WellKnownPreference.ErrorColor))); return; } using (CancellationTokenSource source = new CancellationTokenSource()) { shell.ShellState.ConsoleManager.AddBreakHandler(() => source.Cancel()); if (args.Length > 0) { if (string.Equals(args[0], "--help", StringComparison.OrdinalIgnoreCase) || string.Equals(args[0], "-h", StringComparison.OrdinalIgnoreCase)) { telemetry.TrackStartedEvent(withHelp: true); shell.ShellState.ConsoleManager.WriteLine(Resources.Strings.Help_Usage); shell.ShellState.ConsoleManager.WriteLine(" httprepl [] [options]"); shell.ShellState.ConsoleManager.WriteLine(); shell.ShellState.ConsoleManager.WriteLine(Resources.Strings.Help_Arguments); shell.ShellState.ConsoleManager.WriteLine(string.Format(Resources.Strings.Help_BaseAddress, "")); shell.ShellState.ConsoleManager.WriteLine(); shell.ShellState.ConsoleManager.WriteLine(Resources.Strings.Help_Options); shell.ShellState.ConsoleManager.WriteLine(string.Format(Resources.Strings.Help_Help, "-h|--help")); shell.ShellState.ConsoleManager.WriteLine(); shell.ShellState.ConsoleManager.WriteLine(Resources.Strings.Help_REPLCommands); HelpCommand.CoreGetHelp(shell.ShellState, (ICommandDispatcher)shell.ShellState.CommandDispatcher, state); return; } // allow running a script file directly. if (string.Equals(args[0], "run", StringComparison.OrdinalIgnoreCase)) { telemetry.TrackStartedEvent(withRun: true); shell.ShellState.CommandDispatcher.OnReady(shell.ShellState); shell.ShellState.InputManager.SetInput(shell.ShellState, string.Join(' ', args)); await shell.ShellState.CommandDispatcher.ExecuteCommandAsync(shell.ShellState, CancellationToken.None).ConfigureAwait(false); return; } telemetry.TrackStartedEvent(withOtherArgs: args.Length > 0); string combinedArgs = string.Join(' ', args); shell.ShellState.CommandDispatcher.OnReady(shell.ShellState); shell.ShellState.InputManager.SetInput(shell.ShellState, $"connect {combinedArgs}"); await shell.ShellState.CommandDispatcher.ExecuteCommandAsync(shell.ShellState, CancellationToken.None).ConfigureAwait(false); } else { telemetry.TrackStartedEvent(); } await shell.RunAsync(source.Token).ConfigureAwait(false); } } private static void ComposeDependencies(ref IConsoleManager consoleManager, ref IPreferences preferences, ref ITelemetry telemetry, out HttpState state, out Shell shell) { consoleManager ??= new ConsoleManager(); IFileSystem fileSystem = new RealFileSystem(); preferences ??= new UserFolderPreferences(fileSystem, new UserProfileDirectoryProvider(), CreateDefaultPreferences()); telemetry ??= new Telemetry.Telemetry(VersionSensor.AssemblyInformationalVersion); HttpClient httpClient = GetHttpClientWithPreferences(preferences); state = new HttpState(preferences, httpClient); DefaultCommandDispatcher dispatcher = DefaultCommandDispatcher.Create(state.GetPrompt, state); dispatcher.AddCommandWithTelemetry(telemetry, new ChangeDirectoryCommand()); dispatcher.AddCommandWithTelemetry(telemetry, new ClearCommand()); dispatcher.AddCommandWithTelemetry(telemetry, new ClearQueryParamCommand(telemetry)); dispatcher.AddCommandWithTelemetry(telemetry, new ConnectCommand(preferences, telemetry)); dispatcher.AddCommandWithTelemetry(telemetry, new DeleteCommand(fileSystem, preferences, telemetry)); dispatcher.AddCommandWithTelemetry(telemetry, new EchoCommand()); dispatcher.AddCommandWithTelemetry(telemetry, new ExitCommand()); dispatcher.AddCommandWithTelemetry(telemetry, new HeadCommand(fileSystem, preferences, telemetry)); dispatcher.AddCommandWithTelemetry(telemetry, new HelpCommand()); dispatcher.AddCommandWithTelemetry(telemetry, new GetCommand(fileSystem, preferences, telemetry)); dispatcher.AddCommandWithTelemetry(telemetry, new ListCommand(preferences)); dispatcher.AddCommandWithTelemetry(telemetry, new OptionsCommand(fileSystem, preferences, telemetry)); dispatcher.AddCommandWithTelemetry(telemetry, new PatchCommand(fileSystem, preferences, telemetry)); dispatcher.AddCommandWithTelemetry(telemetry, new PrefCommand(preferences, telemetry)); dispatcher.AddCommandWithTelemetry(telemetry, new PostCommand(fileSystem, preferences, telemetry)); dispatcher.AddCommandWithTelemetry(telemetry, new PutCommand(fileSystem, preferences, telemetry)); dispatcher.AddCommandWithTelemetry(telemetry, new RunCommand(fileSystem)); dispatcher.AddCommandWithTelemetry(telemetry, new SetHeaderCommand(telemetry)); dispatcher.AddCommandWithTelemetry(telemetry, new AddQueryParamCommand(telemetry)); dispatcher.AddCommandWithTelemetry(telemetry, new UICommand(new UriLauncher(), preferences)); shell = new Shell(dispatcher, consoleManager: consoleManager); } internal static Dictionary CreateDefaultPreferences() { return new Dictionary { { WellKnownPreference.ProtocolColor, "BoldGreen" }, { WellKnownPreference.StatusColor, "BoldYellow" }, { WellKnownPreference.JsonArrayBraceColor, "BoldCyan" }, { WellKnownPreference.JsonCommaColor, "BoldYellow" }, { WellKnownPreference.JsonNameColor, "BoldMagenta" }, { WellKnownPreference.JsonNameSeparatorColor, "BoldWhite" }, { WellKnownPreference.JsonObjectBraceColor, "Cyan" }, { WellKnownPreference.JsonColor, "Green" } }; } private static HttpClient GetHttpClientWithPreferences(IPreferences preferences) { bool useDefaultCredentials = preferences.GetBoolValue(WellKnownPreference.UseDefaultCredentials); bool proxyUseDefaultCredentials = preferences.GetBoolValue(WellKnownPreference.ProxyUseDefaultCredentials); if (useDefaultCredentials || proxyUseDefaultCredentials) { #pragma warning disable CA2000 // Dispose objects before losing scope HttpClientHandler handler = new HttpClientHandler() { UseDefaultCredentials = useDefaultCredentials, DefaultProxyCredentials = proxyUseDefaultCredentials ? CredentialCache.DefaultCredentials : null }; return new HttpClient(handler); #pragma warning restore CA2000 // Dispose objects before losing scope } return new HttpClient(); } private static void RegisterEncodingProviders() { // Adds Windows-1252, among others Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); } } } ================================================ FILE: src/Microsoft.HttpRepl/Properties/AssemblyInfo.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Microsoft.HttpRepl.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "This is done commonly throughout the codebase to catch unexpected errors.") ] ================================================ FILE: src/Microsoft.HttpRepl/Resources/Strings.Designer.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Microsoft.HttpRepl.Resources { using System; /// /// A strongly-typed resource class, for looking up localized strings, etc. /// // This class was auto-generated by the StronglyTypedResourceBuilder // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Strings { private static global::System.Resources.ResourceManager resourceMan; private static global::System.Globalization.CultureInfo resourceCulture; [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal Strings() { } /// /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.HttpRepl.Resources.Strings", typeof(Strings).Assembly); resourceMan = temp; } return resourceMan; } } /// /// Overrides the current thread's CurrentUICulture property for all /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } set { resourceCulture = value; } } /// /// Looks up a localized string similar to Adds a key and value pair to the query string. The key and value will be UrlEncoded. Multiple values may be mapped to the same key.. /// internal static string AddQueryParamCommand_HelpDetails { get { return ResourceManager.GetString("AddQueryParamCommand_HelpDetails", resourceCulture); } } /// /// Looks up a localized string similar to Adds a key and value pair to the query string. /// internal static string AddQueryParamCommand_HelpSummary { get { return ResourceManager.GetString("AddQueryParamCommand_HelpSummary", resourceCulture); } } /// /// Looks up a localized string similar to Cancelled. /// internal static string ApiConnection_Logging_Cancelled { get { return ResourceManager.GetString("ApiConnection_Logging_Cancelled", resourceCulture); } } /// /// Looks up a localized string similar to Checking {0}... . /// internal static string ApiConnection_Logging_Checking { get { return ResourceManager.GetString("ApiConnection_Logging_Checking", resourceCulture); } } /// /// Looks up a localized string similar to Failed!. /// internal static string ApiConnection_Logging_Failed { get { return ResourceManager.GetString("ApiConnection_Logging_Failed", resourceCulture); } } /// /// Looks up a localized string similar to Found. /// internal static string ApiConnection_Logging_Found { get { return ResourceManager.GetString("ApiConnection_Logging_Found", resourceCulture); } } /// /// Looks up a localized string similar to Parsing... . /// internal static string ApiConnection_Logging_Parsing { get { return ResourceManager.GetString("ApiConnection_Logging_Parsing", resourceCulture); } } /// /// Looks up a localized string similar to Successful. /// internal static string ApiConnection_Logging_Successful { get { return ResourceManager.GetString("ApiConnection_Logging_Successful", resourceCulture); } } /// /// Looks up a localized string similar to Successful (with warnings). /// internal static string ApiConnection_Logging_SuccessfulWithWarnings { get { return ResourceManager.GetString("ApiConnection_Logging_SuccessfulWithWarnings", resourceCulture); } } /// /// Looks up a localized string similar to The specified content file, "{0}", does not exist.. /// internal static string BaseHttpCommand_Error_ContentFileDoesNotExist { get { return ResourceManager.GetString("BaseHttpCommand_Error_ContentFileDoesNotExist", resourceCulture); } } /// /// Looks up a localized string similar to The specified default editor path, "{0}", does not exist.. /// internal static string BaseHttpCommand_Error_DefaultEditorDoesNotExist { get { return ResourceManager.GetString("BaseHttpCommand_Error_DefaultEditorDoesNotExist", resourceCulture); } } /// /// Looks up a localized string similar to The default editor must be configured using the command `pref set {0} "{{commandLine}}"`.. /// internal static string BaseHttpCommand_Error_DefaultEditorNotConfigured { get { return ResourceManager.GetString("BaseHttpCommand_Error_DefaultEditorNotConfigured", resourceCulture); } } /// /// Looks up a localized string similar to Headers must be formatted as {header}={value} or {header}:{value}. /// internal static string BaseHttpCommand_Error_HeaderFormatting { get { return ResourceManager.GetString("BaseHttpCommand_Error_HeaderFormatting", resourceCulture); } } /// /// Looks up a localized string similar to The output files for the headers and the body must be two different files. /// internal static string BaseHttpCommand_Error_SameBodyAndHeaderFileName { get { return ResourceManager.GetString("BaseHttpCommand_Error_SameBodyAndHeaderFileName", resourceCulture); } } /// /// Looks up a localized string similar to Streaming the response, press any key to stop.... /// internal static string BaseHttpCommand_FormatBodyAsync_Streaming { get { return ResourceManager.GetString("BaseHttpCommand_FormatBodyAsync_Streaming", resourceCulture); } } /// /// Looks up a localized string similar to Append the given directory to the currently selected path, or move up a path when using `cd ..`. /// internal static string ChangeDirectoryCommand_HelpSummary { get { return ResourceManager.GetString("ChangeDirectoryCommand_HelpSummary", resourceCulture); } } /// /// Looks up a localized string similar to Warning: The '{0}' endpoint is not present in the OpenAPI description. /// internal static string ChangeDirectoryCommand_Warning_UnknownEndpoint { get { return ResourceManager.GetString("ChangeDirectoryCommand_Warning_UnknownEndpoint", resourceCulture); } } /// /// Looks up a localized string similar to Removes all text from the shell. /// internal static string ClearCommand_HelpSummary { get { return ResourceManager.GetString("ClearCommand_HelpSummary", resourceCulture); } } /// /// Looks up a localized string similar to Clears the query string of all key and values. /// internal static string ClearQueryParamCommand_HelpDetails { get { return ResourceManager.GetString("ClearQueryParamCommand_HelpDetails", resourceCulture); } } /// /// Looks up a localized string similar to Clears the query string for all requests. /// internal static string ClearQueryParamCommand_HelpSummary { get { return ResourceManager.GetString("ClearQueryParamCommand_HelpSummary", resourceCulture); } } /// /// Looks up a localized string similar to Configures the directory structure and base address of the api server. /// internal static string ConnectCommand_Description { get { return ResourceManager.GetString("ConnectCommand_Description", resourceCulture); } } /// /// Looks up a localized string similar to The base address must be a valid absolute url or relative url. If it is a relative url, the root address must be specified. /// internal static string ConnectCommand_Error_InvalidBase { get { return ResourceManager.GetString("ConnectCommand_Error_InvalidBase", resourceCulture); } } /// /// Looks up a localized string similar to The OpenAPI description address must be a valid absolute url or relative url. If it is a relative url, the root address must be specified. /// internal static string ConnectCommand_Error_InvalidSwagger { get { return ResourceManager.GetString("ConnectCommand_Error_InvalidSwagger", resourceCulture); } } /// /// Looks up a localized string similar to If no root address is specified, the base address must be an absolute url, including scheme. /// internal static string ConnectCommand_Error_NoRootNoAbsoluteBase { get { return ResourceManager.GetString("ConnectCommand_Error_NoRootNoAbsoluteBase", resourceCulture); } } /// /// Looks up a localized string similar to If no root address is specified, the OpenAPI description address must be an absolute url, including scheme. /// internal static string ConnectCommand_Error_NoRootNoAbsoluteSwagger { get { return ResourceManager.GetString("ConnectCommand_Error_NoRootNoAbsoluteSwagger", resourceCulture); } } /// /// Looks up a localized string similar to You must specify either a root address or a base address and an OpenAPI description address. /// internal static string ConnectCommand_Error_NothingSpecified { get { return ResourceManager.GetString("ConnectCommand_Error_NothingSpecified", resourceCulture); } } /// /// Looks up a localized string similar to If specified, the root address must be a valid absolute url, including scheme. /// internal static string ConnectCommand_Error_RootAddressNotValid { get { return ResourceManager.GetString("ConnectCommand_Error_RootAddressNotValid", resourceCulture); } } /// /// Looks up a localized string similar to Configures the directory structure and base address of the api server based on the arguments and options specified. At least one of [rootAddress], [--base baseAddress] or [--openapi openApiDescriptionAddress] must be specified /// ///By default, existing headers set via the `set header` command and path segments beyond the base address are cleared when this command is executed. Use [--persist-headers] and [--persist-paths] to change those defaults. /// internal static string ConnectCommand_HelpDetails_Line1 { get { return ResourceManager.GetString("ConnectCommand_HelpDetails_Line1", resourceCulture); } } /// /// Looks up a localized string similar to [rootAddress] will be used to automatically determine the base address and OpenAPI description address. /// internal static string ConnectCommand_HelpDetails_Line2 { get { return ResourceManager.GetString("ConnectCommand_HelpDetails_Line2", resourceCulture); } } /// /// Looks up a localized string similar to [--base baseAddress] and [--openapi openApiDescriptionAddress] allow you to explicitly set those addresses and skip auto detection. /// internal static string ConnectCommand_HelpDetails_Line3 { get { return ResourceManager.GetString("ConnectCommand_HelpDetails_Line3", resourceCulture); } } /// /// Looks up a localized string similar to [--verbose] provides more detail about OpenAPI Description discovery and parsing. /// internal static string ConnectCommand_HelpDetails_Line4 { get { return ResourceManager.GetString("ConnectCommand_HelpDetails_Line4", resourceCulture); } } /// /// Looks up a localized string similar to [--persist-headers] leaves existing session headers in place when connecting to a new API. /// internal static string ConnectCommand_HelpDetails_Line5 { get { return ResourceManager.GetString("ConnectCommand_HelpDetails_Line5", resourceCulture); } } /// /// Looks up a localized string similar to [--persist-path] leaves existing path segments in place when connecting to a new API. /// internal static string ConnectCommand_HelpDetails_Line6 { get { return ResourceManager.GetString("ConnectCommand_HelpDetails_Line6", resourceCulture); } } /// /// Looks up a localized string similar to Using a base address of {0}. /// internal static string ConnectCommand_Status_Base { get { return ResourceManager.GetString("ConnectCommand_Status_Base", resourceCulture); } } /// /// Looks up a localized string similar to Unable to determine a base address. /// internal static string ConnectCommand_Status_NoBase { get { return ResourceManager.GetString("ConnectCommand_Status_NoBase", resourceCulture); } } /// /// Looks up a localized string similar to Unable to find an OpenAPI description. /// internal static string ConnectCommand_Status_NoSwagger { get { return ResourceManager.GetString("ConnectCommand_Status_NoSwagger", resourceCulture); } } /// /// Looks up a localized string similar to Using OpenAPI description at {0}. /// internal static string ConnectCommand_Status_Swagger { get { return ResourceManager.GetString("ConnectCommand_Status_Swagger", resourceCulture); } } /// /// Looks up a localized string similar to Allowed echo modes are 'on' and 'off'. /// internal static string EchoCommand_Error_AllowedModes { get { return ResourceManager.GetString("EchoCommand_Error_AllowedModes", resourceCulture); } } /// /// Looks up a localized string similar to Turns request echoing on or off, show the request that was made when using request commands. /// internal static string EchoCommand_HelpSummary { get { return ResourceManager.GetString("EchoCommand_HelpSummary", resourceCulture); } } /// /// Looks up a localized string similar to The base address must be set before issuing requests to a relative path. ///Use the `connect` command to set the base address and then try again. ///Type `help connect` for more information on the `connect` command.. /// internal static string Error_NoBasePath { get { return ResourceManager.GetString("Error_NoBasePath", resourceCulture); } } /// /// Looks up a localized string similar to Cannot start the REPL when output is being redirected. /// internal static string Error_OutputRedirected { get { return ResourceManager.GetString("Error_OutputRedirected", resourceCulture); } } /// /// Looks up a localized string similar to Exit the shell. /// internal static string ExitCommand_HelpSummary { get { return ResourceManager.GetString("ExitCommand_HelpSummary", resourceCulture); } } /// /// Looks up a localized string similar to Arguments:. /// internal static string Help_Arguments { get { return ResourceManager.GetString("Help_Arguments", resourceCulture); } } /// /// Looks up a localized string similar to {0} - The initial base address for the REPL.. /// internal static string Help_BaseAddress { get { return ResourceManager.GetString("Help_BaseAddress", resourceCulture); } } /// /// Looks up a localized string similar to {0} - Show help information.. /// internal static string Help_Help { get { return ResourceManager.GetString("Help_Help", resourceCulture); } } /// /// Looks up a localized string similar to Options:. /// internal static string Help_Options { get { return ResourceManager.GetString("Help_Options", resourceCulture); } } /// /// Looks up a localized string similar to Once the REPL starts, these commands are valid:. /// internal static string Help_REPLCommands { get { return ResourceManager.GetString("Help_REPLCommands", resourceCulture); } } /// /// Looks up a localized string similar to Usage: . /// internal static string Help_Usage { get { return ResourceManager.GetString("Help_Usage", resourceCulture); } } /// /// Looks up a localized string similar to REPL Customization Commands:. /// internal static string HelpCommand_Core_CustomizationCommands { get { return ResourceManager.GetString("HelpCommand_Core_CustomizationCommands", resourceCulture); } } /// /// Looks up a localized string similar to Use these commands to customize the REPL behavior. /// internal static string HelpCommand_Core_CustomizationCommands_Description { get { return ResourceManager.GetString("HelpCommand_Core_CustomizationCommands_Description", resourceCulture); } } /// /// Looks up a localized string similar to Use `help <COMMAND>` for more detail on an individual command. e.g. `help get`. /// internal static string HelpCommand_Core_Details_Line1 { get { return ResourceManager.GetString("HelpCommand_Core_Details_Line1", resourceCulture); } } /// /// Looks up a localized string similar to For detailed tool info, see https://aka.ms/http-repl-doc. /// internal static string HelpCommand_Core_Details_Line2 { get { return ResourceManager.GetString("HelpCommand_Core_Details_Line2", resourceCulture); } } /// /// Looks up a localized string similar to HTTP Commands:. /// internal static string HelpCommand_Core_HttpCommands { get { return ResourceManager.GetString("HelpCommand_Core_HttpCommands", resourceCulture); } } /// /// Looks up a localized string similar to Use these commands to execute requests against your application. /// internal static string HelpCommand_Core_HttpCommands_Description { get { return ResourceManager.GetString("HelpCommand_Core_HttpCommands_Description", resourceCulture); } } /// /// Looks up a localized string similar to Navigation Commands:. /// internal static string HelpCommand_Core_NavigationCommands { get { return ResourceManager.GetString("HelpCommand_Core_NavigationCommands", resourceCulture); } } /// /// Looks up a localized string similar to The REPL allows you to navigate your URL space and focus on specific APIs that you are working on. /// internal static string HelpCommand_Core_NavigationCommands_Description { get { return ResourceManager.GetString("HelpCommand_Core_NavigationCommands_Description", resourceCulture); } } /// /// Looks up a localized string similar to Setup Commands:. /// internal static string HelpCommand_Core_SetupCommands { get { return ResourceManager.GetString("HelpCommand_Core_SetupCommands", resourceCulture); } } /// /// Looks up a localized string similar to Use these commands to configure the tool for your API server. /// internal static string HelpCommand_Core_SetupCommands_Description { get { return ResourceManager.GetString("HelpCommand_Core_SetupCommands_Description", resourceCulture); } } /// /// Looks up a localized string similar to Shell Commands:. /// internal static string HelpCommand_Core_ShellCommands { get { return ResourceManager.GetString("HelpCommand_Core_ShellCommands", resourceCulture); } } /// /// Looks up a localized string similar to Use these commands to interact with the REPL shell. /// internal static string HelpCommand_Core_ShellCommands_Description { get { return ResourceManager.GetString("HelpCommand_Core_ShellCommands_Description", resourceCulture); } } /// /// Looks up a localized string similar to Unable to locate any help information for the specified command. /// internal static string HelpCommand_Error_UnableToLocateHelpInfo { get { return ResourceManager.GetString("HelpCommand_Error_UnableToLocateHelpInfo", resourceCulture); } } /// /// Looks up a localized string similar to If {0} is not an absolute URI, {1} must be specified.. /// internal static string HttpState_Error_NoAbsoluteUriNoBaseAddress { get { return ResourceManager.GetString("HttpState_Error_NoAbsoluteUriNoBaseAddress", resourceCulture); } } /// /// Looks up a localized string similar to Accepts:. /// internal static string ListCommand_Accepts { get { return ResourceManager.GetString("ListCommand_Accepts", resourceCulture); } } /// /// Looks up a localized string similar to Available methods:. /// internal static string ListCommand_AvailableMethods { get { return ResourceManager.GetString("ListCommand_AvailableMethods", resourceCulture); } } /// /// Looks up a localized string similar to No base address has been set, so there is nothing to list. ///Use the `connect` command to set the base address and then try again. ///Type `help connect` for more information on the `connect` command.. /// internal static string ListCommand_Error_NoBaseAddress { get { return ResourceManager.GetString("ListCommand_Error_NoBaseAddress", resourceCulture); } } /// /// Looks up a localized string similar to No directory structure has been set, so there is nothing to list. Use the "connect" command to set a directory structure based on an OpenAPI description.. /// internal static string ListCommand_Error_NoDirectoryStructure { get { return ResourceManager.GetString("ListCommand_Error_NoDirectoryStructure", resourceCulture); } } /// /// Looks up a localized string similar to Show all endpoints for the current path. /// internal static string ListCommand_HelpSummary { get { return ResourceManager.GetString("ListCommand_HelpSummary", resourceCulture); } } /// /// Looks up a localized string similar to Options:. /// internal static string Options { get { return ResourceManager.GetString("Options", resourceCulture); } } /// /// Looks up a localized string similar to {0} does not have a configured value. /// internal static string PrefCommand_Error_NoConfiguredValue { get { return ResourceManager.GetString("PrefCommand_Error_NoConfiguredValue", resourceCulture); } } /// /// Looks up a localized string similar to Whether to get or set a preference must be specified. /// internal static string PrefCommand_Error_NoGetOrSet { get { return ResourceManager.GetString("PrefCommand_Error_NoGetOrSet", resourceCulture); } } /// /// Looks up a localized string similar to The preference to set must be specified. /// internal static string PrefCommand_Error_NoPreferenceName { get { return ResourceManager.GetString("PrefCommand_Error_NoPreferenceName", resourceCulture); } } /// /// Looks up a localized string similar to Error saving preferences. /// internal static string PrefCommand_Error_Saving { get { return ResourceManager.GetString("PrefCommand_Error_Saving", resourceCulture); } } /// /// Looks up a localized string similar to Configured value: {0}. /// internal static string PrefCommand_Get_ConfiguredValue { get { return ResourceManager.GetString("PrefCommand_Get_ConfiguredValue", resourceCulture); } } /// /// Looks up a localized string similar to Current Preferences:. /// internal static string PrefCommand_HelpDetails_CurrentPreferences { get { return ResourceManager.GetString("PrefCommand_HelpDetails_CurrentPreferences", resourceCulture); } } /// /// Looks up a localized string similar to Current Default Preferences:. /// internal static string PrefCommand_HelpDetails_DefaultPreferences { get { return ResourceManager.GetString("PrefCommand_HelpDetails_DefaultPreferences", resourceCulture); } } /// /// Looks up a localized string similar to {0} - Gets the value of the specified preference or lists all preferences if no preference is specified. /// internal static string PrefCommand_HelpDetails_GetSyntax { get { return ResourceManager.GetString("PrefCommand_HelpDetails_GetSyntax", resourceCulture); } } /// /// Looks up a localized string similar to {0} - Sets (or clears if value is not specified) the value of the specified preference. /// internal static string PrefCommand_HelpDetails_SetSyntax { get { return ResourceManager.GetString("PrefCommand_HelpDetails_SetSyntax", resourceCulture); } } /// /// Looks up a localized string similar to {0} - Get or sets a preference to a particular value. /// internal static string PrefCommand_HelpDetails_Syntax { get { return ResourceManager.GetString("PrefCommand_HelpDetails_Syntax", resourceCulture); } } /// /// Looks up a localized string similar to Allows viewing or changing preferences, e.g. 'pref set editor.command.default 'C:\\Program Files\\Microsoft VS Code\\Code.exe'`. /// internal static string PrefCommand_HelpSummary { get { return ResourceManager.GetString("PrefCommand_HelpSummary", resourceCulture); } } /// /// Looks up a localized string similar to If your default editor is Visual Studio Code, you should set the default command arguments (`{0}`) to include `-w` or `--wait` to ensure proper integration between HttpRepl and Visual Studio Code.. /// internal static string PrefCommand_Set_VSCode { get { return ResourceManager.GetString("PrefCommand_Set_VSCode", resourceCulture); } } /// /// Looks up a localized string similar to If specified, {0} must begin with a period and have at least one character after the period.. /// internal static string RealFileSystem_Error_InvalidExtension { get { return ResourceManager.GetString("RealFileSystem_Error_InvalidExtension", resourceCulture); } } /// /// Looks up a localized string similar to Could not find script file {0}. /// internal static string RunCommand_CouldNotFindScriptFile { get { return ResourceManager.GetString("RunCommand_CouldNotFindScriptFile", resourceCulture); } } /// /// Looks up a localized string similar to run {path to script} /// ///Runs the specified script. ///A script is a text file containing one CLI command per line. Each line will be run as if it was typed into the CLI. /// ///When +history option is specified, commands specified in the text file will be added to command history.. /// internal static string RunCommand_HelpDetails { get { return ResourceManager.GetString("RunCommand_HelpDetails", resourceCulture); } } /// /// Looks up a localized string similar to Runs the script at the given path. A script is a set of commands that can be typed with one command per line. /// internal static string RunCommand_HelpSummary { get { return ResourceManager.GetString("RunCommand_HelpSummary", resourceCulture); } } /// /// Looks up a localized string similar to Sets or clears a header. When [value] is empty the header is cleared.. /// internal static string SetHeaderCommand_HelpDetails { get { return ResourceManager.GetString("SetHeaderCommand_HelpDetails", resourceCulture); } } /// /// Looks up a localized string similar to Sets or clears a header for all requests. e.g. `set header content-type application/json`. /// internal static string SetHeaderCommand_HelpSummary { get { return ResourceManager.GetString("SetHeaderCommand_HelpSummary", resourceCulture); } } /// /// Looks up a localized string similar to Welcome to HttpRepl {0}! ///------------------------ /// ///Telemetry ///--------- ///The .NET tools collect usage data in order to help us improve your experience. The data is collected by Microsoft and shared with the community. You can opt-out of telemetry by setting the DOTNET_HTTPREPL_TELEMETRY_OPTOUT environment variable to '1' or 'true' using your favorite shell. /// ///Read more about HttpRepl telemetry: https://aka.ms/httprepl-telemetry ///Read more about .NET CLI Tools telemetry: https://aka.ms/dotnet-cli-telemet [rest of string was truncated]";. /// internal static string Telemetry_WelcomeMessage { get { return ResourceManager.GetString("Telemetry_WelcomeMessage", resourceCulture); } } /// /// Looks up a localized string similar to Launches the Swagger UI page (if available) in the default browser. /// internal static string UICommand_Description { get { return ResourceManager.GetString("UICommand_Description", resourceCulture); } } /// /// Looks up a localized string similar to Displays the Swagger UI page, if available, in the default browser. /// internal static string UICommand_HelpSummary { get { return ResourceManager.GetString("UICommand_HelpSummary", resourceCulture); } } /// /// Looks up a localized string similar to The {0} command uses multiple sources to determine the Swagger UI endpoint. In order of precedence:. /// internal static string UICommand_HelpText_Line1 { get { return ResourceManager.GetString("UICommand_HelpText_Line1", resourceCulture); } } /// /// Looks up a localized string similar to 1. The {0} parameter, if specified. /// internal static string UICommand_HelpText_Line2 { get { return ResourceManager.GetString("UICommand_HelpText_Line2", resourceCulture); } } /// /// Looks up a localized string similar to 2. The {0} preference, if set. /// internal static string UICommand_HelpText_Line3 { get { return ResourceManager.GetString("UICommand_HelpText_Line3", resourceCulture); } } /// /// Looks up a localized string similar to 3. A default URL of {0}. /// internal static string UICommand_HelpText_Line4 { get { return ResourceManager.GetString("UICommand_HelpText_Line4", resourceCulture); } } /// /// Looks up a localized string similar to The parameter '{0}' could not be converted into a valid uri.. /// internal static string UICommand_InvalidParameter { get { return ResourceManager.GetString("UICommand_InvalidParameter", resourceCulture); } } /// /// Looks up a localized string similar to Must be connected to a server to launch Swagger UI. /// internal static string UICommand_NotConnectedToServerError { get { return ResourceManager.GetString("UICommand_NotConnectedToServerError", resourceCulture); } } /// /// Looks up a localized string similar to Unable to launch {0}. /// internal static string UICommand_UnableToLaunchUriError { get { return ResourceManager.GetString("UICommand_UnableToLaunchUriError", resourceCulture); } } /// /// Looks up a localized string similar to Usage: . /// internal static string Usage { get { return ResourceManager.GetString("Usage", resourceCulture); } } } } ================================================ FILE: src/Microsoft.HttpRepl/Resources/Strings.resx ================================================ text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Append the given directory to the currently selected path, or move up a path when using `cd ..` Warning: The '{0}' endpoint is not present in the OpenAPI description {0} is the path to the referenced endpoint Removes all text from the shell Turns request echoing on or off, show the request that was made when using request commands The base address must be set before issuing requests to a relative path. Use the `connect` command to set the base address and then try again. Type `help connect` for more information on the `connect` command. Error shown in console when issuing an HTTP command without first setting the base url Cannot start the REPL when output is being redirected Exit the shell Arguments: {0} - The initial base address for the REPL. {0} is the <BASE_ADDRESS> syntax specifier {0} - Show help information. {0} is the --help syntax specifier Options: Once the REPL starts, these commands are valid: Usage: Show all endpoints for the current path {0} does not have a configured value {0} is the name of the requested preference Whether to get or set a preference must be specified The preference to set must be specified Error saving preferences Configured value: {0} {0} is the value of the specified preference Current Preferences: Current Default Preferences: {0} - Gets the value of the specified preference or lists all preferences if no preference is specified {0} is the get command syntax {0} - Sets (or clears if value is not specified) the value of the specified preference {0} is the set command syntax {0} - Get or sets a preference to a particular value {0} is the overall command syntax Allows viewing or changing preferences, e.g. 'pref set editor.command.default 'C:\\Program Files\\Microsoft VS Code\\Code.exe'` Runs the script at the given path. A script is a set of commands that can be typed with one command per line Sets or clears a header for all requests. e.g. `set header content-type application/json` Adds a key and value pair to the query string. The key and value will be UrlEncoded. Multiple values may be mapped to the same key. Displays the Swagger UI page, if available, in the default browser Launches the Swagger UI page (if available) in the default browser Must be connected to a server to launch Swagger UI Unable to launch {0} {0} indicates a uri Usage: Sets or clears a header. When [value] is empty the header is cleared. run {path to script} Runs the specified script. A script is a text file containing one CLI command per line. Each line will be run as if it was typed into the CLI. When +history option is specified, commands specified in the text file will be added to command history. Could not find script file {0} {0} indicates path to provided script file If {0} is not an absolute URI, {1} must be specified. {0} is the name of the commandSpecifiedPath parameter, {1} is the name of the baseAddress uri parameter No base address has been set, so there is nothing to list. Use the `connect` command to set the base address and then try again. Type `help connect` for more information on the `connect` command. No directory structure has been set, so there is nothing to list. Use the "connect" command to set a directory structure based on an OpenAPI description. The base address must be a valid absolute url or relative url. If it is a relative url, the root address must be specified The OpenAPI description address must be a valid absolute url or relative url. If it is a relative url, the root address must be specified If no root address is specified, the base address must be an absolute url, including scheme If no root address is specified, the OpenAPI description address must be an absolute url, including scheme You must specify either a root address or a base address and an OpenAPI description address If specified, the root address must be a valid absolute url, including scheme Using a base address of {0} {0} indicates a uri Unable to determine a base address Using OpenAPI description at {0} {0} indicates a uri Unable to find an OpenAPI description Configures the directory structure and base address of the api server Configures the directory structure and base address of the api server based on the arguments and options specified. At least one of [rootAddress], [--base baseAddress] or [--openapi openApiDescriptionAddress] must be specified By default, existing headers set via the `set header` command and path segments beyond the base address are cleared when this command is executed. Use [--persist-headers] and [--persist-paths] to change those defaults [rootAddress] will be used to automatically determine the base address and OpenAPI description address [--base baseAddress] and [--openapi openApiDescriptionAddress] allow you to explicitly set those addresses and skip auto detection REPL Customization Commands: Use these commands to customize the REPL behavior Use `help <COMMAND>` for more detail on an individual command. e.g. `help get` For detailed tool info, see https://aka.ms/http-repl-doc HTTP Commands: Use these commands to execute requests against your application Navigation Commands: The REPL allows you to navigate your URL space and focus on specific APIs that you are working on Setup Commands: Use these commands to configure the tool for your API server Shell Commands: Use these commands to interact with the REPL shell If specified, {0} must begin with a period and have at least one character after the period. {0} is the parameter name The parameter '{0}' could not be converted into a valid uri. {0} is a string parameter to the UI command The {0} command uses multiple sources to determine the Swagger UI endpoint. In order of precedence: {0} is the name of the ui command (ui) 1. The {0} parameter, if specified {0} is the syntax for the parameter, e.g. {swaggerUIAddress} 2. The {0} preference, if set {0} is the name of the preference, e.g. swagger.uiEndpoint 3. A default URL of {0} {0} is the default address, e.g. [BaseAddress]/swagger Headers must be formatted as {header}={value} or {header}:{value} Streaming the response, press any key to stop... Allowed echo modes are 'on' and 'off' Options: The specified content file, "{0}", does not exist. {0} is the user supplied content file path The specified default editor path, "{0}", does not exist. {0} is the user supplied default editor preference value The default editor must be configured using the command `pref set {0} "{{commandLine}}"`. {0} is the name of the default editor preference Welcome to HttpRepl {0}! ------------------------ Telemetry --------- The .NET tools collect usage data in order to help us improve your experience. The data is collected by Microsoft and shared with the community. You can opt-out of telemetry by setting the DOTNET_HTTPREPL_TELEMETRY_OPTOUT environment variable to '1' or 'true' using your favorite shell. Read more about HttpRepl telemetry: https://aka.ms/httprepl-telemetry Read more about .NET CLI Tools telemetry: https://aka.ms/dotnet-cli-telemetry {0} is the major.minor version of the current build Accepts: Available methods: If your default editor is Visual Studio Code, you should set the default command arguments (`{0}`) to include `-w` or `--wait` to ensure proper integration between HttpRepl and Visual Studio Code. {0} indicates the preference name for the default editor arguments Unable to locate any help information for the specified command [--verbose] provides more detail about OpenAPI Description discovery and parsing Cancelled Checking {0}... {0} indicates a uri Failed! Found Parsing... Successful The output files for the headers and the body must be two different files Successful (with warnings) [--persist-headers] leaves existing session headers in place when connecting to a new API [--persist-path] leaves existing path segments in place when connecting to a new API Adds a key and value pair to the query string Clears the query string of all key and values Clears the query string for all requests ================================================ FILE: src/Microsoft.HttpRepl/Suggestions/HeaderCompletion.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; namespace Microsoft.HttpRepl.Suggestions { public static class HeaderCompletion { /// /// Gets a collection of HTTP header names which starts with the prefix value passed in, except the ones present in existingHeaders. /// /// A collection of existing headers. /// The prefix value to get completion suggestion items for. /// public static IEnumerable GetCompletions(IReadOnlyCollection existingHeaders, string prefix) { return WellKnownHeaders.CommonHeaders.Where(x => x.StartsWith(prefix, StringComparison.OrdinalIgnoreCase) && existingHeaders?.Contains(x) != true); } public static IEnumerable GetValueCompletions(string method, string path, string header, string prefix, HttpState programState) { header = header ?? throw new ArgumentNullException(nameof(header)); programState = programState ?? throw new ArgumentNullException(nameof(programState)); switch (header.ToUpperInvariant()) { case "CONTENT-TYPE": IEnumerable results = programState.GetApplicableContentTypes(method, path); return results?.Where(x => !string.IsNullOrEmpty(x) && x.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)); default: return null; } } } } ================================================ FILE: src/Microsoft.HttpRepl/Suggestions/ServerPathCompletion.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; namespace Microsoft.HttpRepl.Suggestions { public static class ServerPathCompletion { public static IEnumerable GetCompletions(HttpState programState, string normalCompletionString) { //If it's an absolute URI, nothing to suggest if (Uri.IsWellFormedUriString(normalCompletionString, UriKind.Absolute)) { return null; } programState = programState ?? throw new ArgumentNullException(nameof(programState)); normalCompletionString = normalCompletionString ?? throw new ArgumentNullException(nameof(normalCompletionString)); if (programState.Structure is null) { return null; } string path = normalCompletionString.Replace('\\', '/'); int searchFrom = normalCompletionString.Length - 1; int lastSlash = path.LastIndexOf('/', searchFrom); string prefix; if (lastSlash < 0) { path = string.Empty; prefix = normalCompletionString; } else { path = path.Substring(0, lastSlash + 1); prefix = normalCompletionString.Substring(lastSlash + 1); } IDirectoryStructure s = programState.Structure.TraverseTo(programState.PathSections.Reverse()).TraverseTo(path); if (s?.DirectoryNames == null) { return null; } List results = new List(); foreach (string child in s.DirectoryNames) { if (child.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { results.Add(path + child); } } return results; } } } ================================================ FILE: src/Microsoft.HttpRepl/Telemetry/DefaultCommandDispatcherExtensions.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using Microsoft.Repl.Commanding; using Microsoft.Repl.Parsing; namespace Microsoft.HttpRepl.Telemetry { internal static class DefaultCommandDispatcherExtensions { public static void AddCommandWithTelemetry(this DefaultCommandDispatcher defaultCommandDispatcher, ITelemetry telemetry, ICommand command) { defaultCommandDispatcher.AddCommand(new TelemetryCommandWrapper(telemetry, command)); } } } ================================================ FILE: src/Microsoft.HttpRepl/Telemetry/Events/AddQueryParamEvent.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. namespace Microsoft.HttpRepl.Telemetry.Events { internal class AddQueryParamEvent : TelemetryEventBase { public AddQueryParamEvent(string key, bool isValueEmpty) : base(TelemetryEventNames.AddQueryParam) { SetProperty(TelemetryPropertyNames.AddQueryParam_Key, SanitizeKey(key)); SetProperty(TelemetryPropertyNames.AddQueryParam_IsValueEmpty, isValueEmpty); } private static string SanitizeKey(string headerName) { if (string.IsNullOrEmpty(headerName)) { return headerName; } return Sha256Hasher.Hash(headerName); } } } ================================================ FILE: src/Microsoft.HttpRepl/Telemetry/Events/ClearQueryParamEvent.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. namespace Microsoft.HttpRepl.Telemetry.Events { internal class ClearQueryParamEvent: TelemetryEventBase { public ClearQueryParamEvent(string key, bool isValueEmpty) : base(TelemetryEventNames.ClearQueryParam) { SetProperty(TelemetryPropertyNames.ClearQueryParam_Key, SanitizeKey(key)); SetProperty(TelemetryPropertyNames.ClearQueryParam_IsValueEmpty, isValueEmpty); } private static string SanitizeKey(string headerName) { if (string.IsNullOrEmpty(headerName)) { return headerName; } return Sha256Hasher.Hash(headerName); } } } ================================================ FILE: src/Microsoft.HttpRepl/Telemetry/Events/CommandExecutedEvent.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. namespace Microsoft.HttpRepl.Telemetry.Events { internal class CommandExecutedEvent : TelemetryEventBase { public CommandExecutedEvent(string commandName, bool wasSuccessful) : base(TelemetryEventNames.CommandExecuted) { SetProperty(TelemetryPropertyNames.CommandExecuted_CommandName, commandName); SetProperty(TelemetryPropertyNames.CommandExecuted_WasSuccessful, wasSuccessful); } } } ================================================ FILE: src/Microsoft.HttpRepl/Telemetry/Events/ConnectEvent.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. namespace Microsoft.HttpRepl.Telemetry.Events { internal class ConnectEvent : TelemetryEventBase { public ConnectEvent(bool baseSpecified, bool rootSpecified, bool openApiSpecified, bool openApiFound) : base(TelemetryEventNames.Connect) { SetProperty(TelemetryPropertyNames.Connect_BaseSpecified, baseSpecified); SetProperty(TelemetryPropertyNames.Connect_RootSpecified, rootSpecified); SetProperty(TelemetryPropertyNames.Connect_OpenApiSpecified, openApiSpecified); SetProperty(TelemetryPropertyNames.Connect_OpenApiFound, openApiFound); } } } ================================================ FILE: src/Microsoft.HttpRepl/Telemetry/Events/HttpCommandEvent.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. namespace Microsoft.HttpRepl.Telemetry.Events { internal class HttpCommandEvent : TelemetryEventBase { public HttpCommandEvent(string method, bool isPathSpecified, bool isHeaderSpecified, bool isResponseHeadersFileSpecified, bool isResponseBodyFileSpecified, bool isNoFormattingSpecified, bool isStreamingSpecified, bool isNoBodySpecified, bool isRequestBodyFileSpecified, bool isRequestBodyContentSpecified) : base(TelemetryEventNames.HttpCommand) { SetProperty(TelemetryPropertyNames.HttpCommand_Method, method); SetProperty(TelemetryPropertyNames.HttpCommand_PathSpecified, isPathSpecified); SetProperty(TelemetryPropertyNames.HttpCommand_HeaderSpecified, isHeaderSpecified); SetProperty(TelemetryPropertyNames.HttpCommand_ResponseHeadersFileSpecified, isResponseHeadersFileSpecified); SetProperty(TelemetryPropertyNames.HttpCommand_ResponseBodyFileSpecified, isResponseBodyFileSpecified); SetProperty(TelemetryPropertyNames.HttpCommand_NoFormattingSpecified, isNoFormattingSpecified); SetProperty(TelemetryPropertyNames.HttpCommand_StreamingSpecified, isStreamingSpecified); SetProperty(TelemetryPropertyNames.HttpCommand_NoBodySpecified, isNoBodySpecified); SetProperty(TelemetryPropertyNames.HttpCommand_RequestBodyFileSpecified, isRequestBodyFileSpecified); SetProperty(TelemetryPropertyNames.HttpCommand_RequestBodyContentSpecified, isRequestBodyContentSpecified); } } } ================================================ FILE: src/Microsoft.HttpRepl/Telemetry/Events/PreferenceEvent.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Linq; using Microsoft.HttpRepl.Preferences; namespace Microsoft.HttpRepl.Telemetry.Events { internal class PreferenceEvent : TelemetryEventBase { public PreferenceEvent(string getOrSet, string preferenceName) : base(TelemetryEventNames.Preference) { SetProperty(TelemetryPropertyNames.Preference_GetOrSet, getOrSet); SetProperty(TelemetryPropertyNames.Preference_PreferenceName, SanitizePreferenceName(preferenceName)); } private static string SanitizePreferenceName(string preferenceName) { if (string.IsNullOrEmpty(preferenceName) || WellKnownPreference.Catalog.Names.Contains(preferenceName, StringComparer.OrdinalIgnoreCase)) { return preferenceName; } return Sha256Hasher.Hash(preferenceName); } } } ================================================ FILE: src/Microsoft.HttpRepl/Telemetry/Events/SetHeaderEvent.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Linq; namespace Microsoft.HttpRepl.Telemetry.Events { internal class SetHeaderEvent : TelemetryEventBase { public SetHeaderEvent(string headerName, bool isValueEmpty) : base(TelemetryEventNames.SetHeader) { SetProperty(TelemetryPropertyNames.SetHeader_HeaderName, SanitizeHeaderName(headerName)); SetProperty(TelemetryPropertyNames.SetHeader_IsValueEmpty, isValueEmpty); } private static string SanitizeHeaderName(string headerName) { if (string.IsNullOrEmpty(headerName) || WellKnownHeaders.CommonHeaders.Contains(headerName, StringComparer.OrdinalIgnoreCase)) { return headerName; } return Sha256Hasher.Hash(headerName); } } } ================================================ FILE: src/Microsoft.HttpRepl/Telemetry/Events/StartedEvent.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. namespace Microsoft.HttpRepl.Telemetry.Events { internal class StartedEvent : TelemetryEventBase { public StartedEvent(bool withHelp, bool withRun, bool withOtherArgs, bool withOutputRedirection) : base(TelemetryEventNames.Started) { SetProperty(TelemetryPropertyNames.Started_WithHelp, withHelp); SetProperty(TelemetryPropertyNames.Started_WithRun, withRun); SetProperty(TelemetryPropertyNames.Started_WithOtherArgs, withOtherArgs); SetProperty(TelemetryPropertyNames.Started_WithOutputRedirection, withOutputRedirection); } } } ================================================ FILE: src/Microsoft.HttpRepl/Telemetry/Events/TelemetryEventBase.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Collections.Generic; namespace Microsoft.HttpRepl.Telemetry.Events { internal abstract class TelemetryEventBase { private readonly string _name; private readonly Dictionary _properties = new Dictionary(); private readonly Dictionary _measurements = new Dictionary(); public string Name => _name; public IReadOnlyDictionary Properties => _properties; public IReadOnlyDictionary Measurements => _measurements; public TelemetryEventBase(string name) { _name = name; } protected void SetProperty(string name, bool value) => SetProperty(name, value.ToString()); protected void SetProperty(string name, string value) => _properties[name] = value; protected string GetProperty(string name, string defaultValue = default) { if (_properties.TryGetValue(name, out string value)) { return value; } return defaultValue; } protected void SetMeasurement(string name, double value) => _measurements[name] = value; protected double GetMeasurement(string name, double defaultValue = default) { if (_measurements.TryGetValue(name, out double value)) { return value; } return defaultValue; } } } ================================================ FILE: src/Microsoft.HttpRepl/Telemetry/Events/WebApiF5FixEvent.cs ================================================ namespace Microsoft.HttpRepl.Telemetry.Events { internal class WebApiF5FixEvent : TelemetryEventBase { public WebApiF5FixEvent(bool skippedByPreference = false) : base(TelemetryEventNames.WebApiF5Fix) { SetProperty(TelemetryPropertyNames.WebApiF5Fix_SkippedByPreference, skippedByPreference); } } } ================================================ FILE: src/Microsoft.HttpRepl/Telemetry/TelemetryCommandWrapper.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.HttpRepl.Telemetry.Events; using Microsoft.Repl; using Microsoft.Repl.Commanding; using Microsoft.Repl.Parsing; namespace Microsoft.HttpRepl.Telemetry { internal class TelemetryCommandWrapper : ICommand { private readonly ICommand _command; private readonly ITelemetry _telemetry; public string Name => _command.Name; public ICommand Command => _command; public TelemetryCommandWrapper(ITelemetry telemetry, ICommand command) { _telemetry = telemetry; _command = command; } public bool? CanHandle(IShellState shellState, HttpState programState, ICoreParseResult parseResult) { return _command.CanHandle(shellState, programState, parseResult); } public Task ExecuteAsync(IShellState shellState, HttpState programState, ICoreParseResult parseResult, CancellationToken cancellationToken) { bool wasSuccessful = true; try { return _command.ExecuteAsync(shellState, programState, parseResult, cancellationToken); } catch { wasSuccessful = false; throw; } finally { _telemetry.TrackEvent(new CommandExecutedEvent(_command.Name, wasSuccessful)); } } public string GetHelpDetails(IShellState shellState, HttpState programState, ICoreParseResult parseResult) { return _command.GetHelpDetails(shellState, programState, parseResult); } public string GetHelpSummary(IShellState shellState, HttpState programState) { return _command.GetHelpSummary(shellState, programState); } public IEnumerable Suggest(IShellState shellState, HttpState programState, ICoreParseResult parseResult) { return _command.Suggest(shellState, programState, parseResult); } } } ================================================ FILE: src/Microsoft.HttpRepl/Telemetry/TelemetryConstants.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. namespace Microsoft.HttpRepl.Telemetry { internal class TelemetryEventNames { public const string CommandExecuted = nameof(CommandExecuted); public const string Connect = nameof(Connect); public const string HttpCommand = nameof(HttpCommand); public const string Preference = nameof(Preference); public const string SetHeader = nameof(SetHeader); public const string AddQueryParam = nameof(AddQueryParam); public const string ClearQueryParam = nameof(ClearQueryParam); public const string Started = nameof(Started); public const string WebApiF5Fix = nameof(WebApiF5Fix); } internal class TelemetryPropertyNames { public const string CommandExecuted_CommandName = "CommandName"; public const string CommandExecuted_WasSuccessful = "WasSuccessful"; public const string ClearQueryParam_Key = "QueryParamKey"; public const string ClearQueryParam_IsValueEmpty = "IsValueEmpty"; public const string Connect_BaseSpecified = "BaseSpecified"; public const string Connect_OpenApiFound = "OpenApiFound"; public const string Connect_OpenApiSpecified = "OpenApiSpecified"; public const string Connect_RootSpecified = "RootSpecified"; public const string HttpCommand_HeaderSpecified = "HeaderSpecified"; public const string HttpCommand_Method = "Method"; public const string HttpCommand_NoBodySpecified = "NoBodySpecified"; public const string HttpCommand_NoFormattingSpecified = "NoFormattingSpecified"; public const string HttpCommand_PathSpecified = "PathSpecified"; public const string HttpCommand_RequestBodyContentSpecified = "RequestBodyContentSpecified"; public const string HttpCommand_RequestBodyFileSpecified = "RequestBodyFileSpecified"; public const string HttpCommand_ResponseHeadersFileSpecified = "ResponseHeadersFileSpecified"; public const string HttpCommand_ResponseBodyFileSpecified = "ResponseBodyFileSpecified"; public const string HttpCommand_StreamingSpecified = "StreamingSpecified"; public const string Preference_GetOrSet = "GetOrSet"; public const string Preference_PreferenceName = "PreferenceName"; public const string SetHeader_HeaderName = "HeaderName"; public const string SetHeader_IsValueEmpty = "IsValueEmpty"; public const string AddQueryParam_Key = "QueryParamKey"; public const string AddQueryParam_IsValueEmpty = "IsValueEmpty"; public const string Started_WithHelp = "WithHelp"; public const string Started_WithOtherArgs = "WithOtherArgs"; public const string Started_WithOutputRedirection = "WithOutputRedirection"; public const string Started_WithRun = "WithRun"; public const string WebApiF5Fix_SkippedByPreference = "SkippedByPreference"; } } ================================================ FILE: src/Microsoft.HttpRepl/Telemetry/TelemetryExtensions.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using Microsoft.HttpRepl.Telemetry.Events; namespace Microsoft.HttpRepl.Telemetry { internal static class TelemetryExtensions { public static void TrackEvent(this ITelemetry telemetry, TelemetryEventBase telemetryEvent) { telemetry.TrackEvent(telemetryEvent.Name, telemetryEvent.Properties, telemetryEvent.Measurements); } public static void TrackStartedEvent(this ITelemetry telemetry, bool withHelp = false, bool withRun = false, bool withOtherArgs = false, bool withOutputRedirection = false) { telemetry.TrackEvent(new StartedEvent(withHelp, withRun, withOtherArgs, withOutputRedirection)); } } } ================================================ FILE: src/Microsoft.HttpRepl/UriLauncher.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Diagnostics; using System.Runtime.InteropServices; using System.Threading.Tasks; using Microsoft.HttpRepl.Resources; namespace Microsoft.HttpRepl { internal class UriLauncher : IUriLauncher { public Task LaunchUriAsync(Uri uri) { string agent; string agentParam; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { agent = "cmd"; agentParam = $"/c start {uri.AbsoluteUri}"; } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { agent = "open"; agentParam = uri.AbsoluteUri; } else // Linux { agent = "xdg-open"; agentParam = uri.AbsoluteUri; } Process process = Process.Start(new ProcessStartInfo(agent, agentParam) { CreateNoWindow = true }); if (process != null) { return Task.CompletedTask; } else { string uriLaunchErrorMessage = string.Format(Strings.UICommand_UnableToLaunchUriError, uri); return Task.FromException(new InvalidOperationException(uriLaunchErrorMessage)); } } } } ================================================ FILE: src/Microsoft.HttpRepl/UserProfile/IUserProfileDirectoryProvider.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. namespace Microsoft.HttpRepl.UserProfile { public interface IUserProfileDirectoryProvider { string GetUserProfileDirectory(); } } ================================================ FILE: src/Microsoft.HttpRepl/UserProfile/UserProfileDirectoryProvider.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Runtime.InteropServices; namespace Microsoft.HttpRepl.UserProfile { public class UserProfileDirectoryProvider : IUserProfileDirectoryProvider { public string GetUserProfileDirectory() { bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); string profileDir = Environment.GetEnvironmentVariable(isWindows ? "USERPROFILE" : "HOME"); return profileDir; } } } ================================================ FILE: src/Microsoft.HttpRepl/VersionSensor.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Reflection; namespace Microsoft.HttpRepl { internal class VersionSensor { private static readonly Lazy buildInfo = new Lazy(() => { Assembly assembly = typeof(VersionSensor).GetTypeInfo().Assembly; BuildInfo buildInfo = new BuildInfo() { AssemblyInformationalVersion = assembly.GetCustomAttribute() .InformationalVersion, AssemblyVersion = assembly.GetName().Version }; return buildInfo; }); public static string AssemblyInformationalVersion => buildInfo.Value.AssemblyInformationalVersion; public static Version AssemblyVersion => buildInfo.Value.AssemblyVersion; private class BuildInfo { public string AssemblyInformationalVersion { get; internal set; } public Version AssemblyVersion { get; internal set; } } } } ================================================ FILE: src/Microsoft.HttpRepl/WellKnownHeaders.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Collections.Generic; namespace Microsoft.HttpRepl { public static class WellKnownHeaders { public static readonly string ContentType = "Content-Type"; public static readonly IEnumerable CommonHeaders = new[] { "A-IM", "Accept", "Accept-Charset", "Accept-Encoding", "Accept-Language", "Accept-Datetime", "Access-Control-Request-Method", "Access-Control-Request-Headers", "Allow", "Authorization", "Cache-Control", "Connection", "Content-Disposition", "Content-Encoding", "Content-Language", "Content-Length", "Content-Location", "Content-MD5", "Content-Range", ContentType, "Cookie", "Date", "Expect", "Expires", "Forwarded", "From", "Host", "If-Match", "If-Modified-Since", "If-None-Match", "If-Range", "If-Unmodified-Since", "Last-Modified", "Max-Forwards", "Origin", "Pragma", "Proxy-Authentication", "Range", "Referer", "TE", "User-Agent", "Upgrade", "Via", "Warning", //Non-standard "Upgrade-Insecure-Requests", "X-Requested-With", "DNT", "X-Forwarded-For", "X-Forwarded-Host", "X-Forwarded-Proto", "Front-End-Https", "X-Http-Method-Override", "X-ATT-DeviceId", "X-Wap-Profile", "Proxy-Connection", "X-UIDH", "X-Csrf-Token", "X-Request-ID", "X-Correlation-ID" }; public static readonly IEnumerable ContentHeaders = new [] { "Allow", "Content-Disposition", "Content-Encoding", "Content-Language", "Content-Length", "Content-Location", "Content-MD5", "Content-Range", ContentType, "Expires", "Last-Modified", }; } } ================================================ FILE: src/Microsoft.HttpRepl.Telemetry/DockerContainerDetectorForTelemetry.cs ================================================ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.IO; using System.Runtime.InteropServices; using System.Security; using Microsoft.Win32; namespace Microsoft.HttpRepl.Telemetry { internal class DockerContainerDetectorForTelemetry : IDockerContainerDetector { public IsDockerContainer IsDockerContainer() { if (OperatingSystem.IsWindows()) { try { using (RegistryKey subkey = Registry.LocalMachine.OpenSubKey("System\\CurrentControlSet\\Control")) { return subkey?.GetValue("ContainerType") != null ? HttpRepl.Telemetry.IsDockerContainer.True : HttpRepl.Telemetry.IsDockerContainer.False; } } catch (SecurityException) { return HttpRepl.Telemetry.IsDockerContainer.Unknown; } } else if (OperatingSystem.IsLinux()) { try { bool isDocker = File .ReadAllText("/proc/1/cgroup") .Contains("/docker/", StringComparison.Ordinal); return isDocker ? HttpRepl.Telemetry.IsDockerContainer.True : HttpRepl.Telemetry.IsDockerContainer.False; } catch (Exception ex) when (ex is IOException || ex.InnerException is IOException) { // in some environments (restricted docker container, shared hosting etc.), // procfs is not accessible and we get UnauthorizedAccessException while the // inner exception is set to IOException. Ignore and continue when that happens. } } else if (OperatingSystem.IsMacOS()) { return HttpRepl.Telemetry.IsDockerContainer.False; } return HttpRepl.Telemetry.IsDockerContainer.Unknown; } } internal enum IsDockerContainer { True, False, Unknown } } ================================================ FILE: src/Microsoft.HttpRepl.Telemetry/EnvironmentHelper.cs ================================================ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; namespace Microsoft.HttpRepl.Telemetry { internal static class EnvironmentHelper { public static bool GetEnvironmentVariableAsBool(string name, bool defaultValue = false) { var str = Environment.GetEnvironmentVariable(name); if (string.IsNullOrEmpty(str)) { return defaultValue; } switch (str.ToUpperInvariant()) { case "TRUE": case "1": case "YES": return true; case "FALSE": case "0": case "NO": return false; default: return defaultValue; } } } } ================================================ FILE: src/Microsoft.HttpRepl.Telemetry/FirstTimeUseNoticeSentinel.cs ================================================ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.IO; namespace Microsoft.HttpRepl.Telemetry { public sealed class FirstTimeUseNoticeSentinel : IFirstTimeUseNoticeSentinel { public const string SkipFirstTimeExperienceEnvironmentVariableName = "DOTNET_HTTPREPL_SKIP_FIRST_TIME_EXPERIENCE"; private readonly string _sentinel; private readonly string _dotnetTryUserProfileFolderPath; private readonly Func _fileExists; private readonly Func _directoryExists; private readonly Action _createDirectory; private readonly Action _createEmptyFile; private string SentinelPath => Path.Combine(_dotnetTryUserProfileFolderPath, _sentinel); public FirstTimeUseNoticeSentinel(string productVersion) : this( productVersion, Paths.DotnetUserProfileFolderPath, File.Exists, Directory.Exists, path => Directory.CreateDirectory(path), path => File.WriteAllBytes(path, Array.Empty())) { } public FirstTimeUseNoticeSentinel( string productVersion, string dotnetTryUserProfileFolderPath, Func fileExists, Func directoryExists, Action createDirectory, Action createEmptyFile) { _sentinel = $"{productVersion}.dotnetHttpReplFirstUseSentinel"; _dotnetTryUserProfileFolderPath = dotnetTryUserProfileFolderPath; _fileExists = fileExists; _directoryExists = directoryExists; _createDirectory = createDirectory; _createEmptyFile = createEmptyFile; } public bool Exists() { return _fileExists(SentinelPath); } public void CreateIfNotExists() { if (!Exists()) { if (!_directoryExists(_dotnetTryUserProfileFolderPath)) { _createDirectory(_dotnetTryUserProfileFolderPath); } _createEmptyFile(SentinelPath); } } } } ================================================ FILE: src/Microsoft.HttpRepl.Telemetry/IDockerContainerDetector.cs ================================================ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. namespace Microsoft.HttpRepl.Telemetry { internal interface IDockerContainerDetector { IsDockerContainer IsDockerContainer(); } } ================================================ FILE: src/Microsoft.HttpRepl.Telemetry/IFirstTimeUseNoticeSentinel.cs ================================================ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. namespace Microsoft.HttpRepl.Telemetry { public interface IFirstTimeUseNoticeSentinel { bool Exists(); void CreateIfNotExists(); } } ================================================ FILE: src/Microsoft.HttpRepl.Telemetry/ITelemetry.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Collections.Generic; namespace Microsoft.HttpRepl.Telemetry { public interface ITelemetry { bool Enabled { get; } void TrackEvent(string eventName, IReadOnlyDictionary properties, IReadOnlyDictionary measurements); IFirstTimeUseNoticeSentinel FirstTimeUseNoticeSentinel { get; } } } ================================================ FILE: src/Microsoft.HttpRepl.Telemetry/IUserLevelCacheWriter.cs ================================================ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; namespace Microsoft.HttpRepl.Telemetry { public interface IUserLevelCacheWriter { string RunWithCache( string cacheKey, Func getValueToCache); } } ================================================ FILE: src/Microsoft.HttpRepl.Telemetry/MacAddressGetter.cs ================================================ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Net.NetworkInformation; using System.Runtime.InteropServices; using System.Text.RegularExpressions; namespace Microsoft.HttpRepl.Telemetry { internal static class MacAddressGetter { private const string InvalidMacAddress = "00-00-00-00-00-00"; private const string MacRegex = @"(?:[a-z0-9]{2}[:\-]){5}[a-z0-9]{2}"; private const string ZeroRegex = @"(?:00[:\-]){5}00"; private const int ErrorFileNotFound = 0x2; [SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We don't want any errors in telemetry to cause failures in the product.")] public static string GetMacAddress() { try { var macAddress = GetMacAddressCore(); if (string.IsNullOrWhiteSpace(macAddress) || macAddress.Equals(InvalidMacAddress, StringComparison.OrdinalIgnoreCase)) { return GetMacAddressByNetworkInterface(); } else { return macAddress; } } catch { return null; } } private static string GetMacAddressCore() { try { var shelloutput = GetShellOutMacAddressOutput(); if (string.IsNullOrWhiteSpace(shelloutput)) { return null; } return ParseMACAddress(shelloutput); } catch (Win32Exception e) { if (e.NativeErrorCode == ErrorFileNotFound) { return GetMacAddressByNetworkInterface(); } else { throw; } } } private static string ParseMACAddress(string shelloutput) { foreach (Match match in Regex.Matches(shelloutput, MacRegex, RegexOptions.IgnoreCase)) { if (!Regex.IsMatch(match.Value, ZeroRegex)) { return match.Value; } } return null; } private static string GetIpCommandOutput() { var ipResult = new ProcessStartInfo { FileName = "ip", Arguments = "link", UseShellExecute = false }.ExecuteAndCaptureOutput(out string ipStdOut, out _); if (ipResult == 0) { return ipStdOut; } else { return null; } } private static string GetShellOutMacAddressOutput() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { var result = new ProcessStartInfo { FileName = "getmac.exe", UseShellExecute = false }.ExecuteAndCaptureOutput(out string stdOut, out _); if (result == 0) { return stdOut; } else { return null; } } else { try { var ifconfigResult = new ProcessStartInfo { FileName = "ifconfig", Arguments = "-a", UseShellExecute = false }.ExecuteAndCaptureOutput(out string ifconfigStdOut, out string ifconfigStdErr); if (ifconfigResult == 0) { return ifconfigStdOut; } else { return GetIpCommandOutput(); } } catch (Win32Exception e) { if (e.NativeErrorCode == ErrorFileNotFound) { return GetIpCommandOutput(); } else { throw; } } } } private static string GetMacAddressByNetworkInterface() { return GetMacAddressesByNetworkInterface().Where(x => !x.Equals(InvalidMacAddress, StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); } private static List GetMacAddressesByNetworkInterface() { NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces(); var macs = new List(); if (nics == null || nics.Length < 1) { macs.Add(string.Empty); return macs; } foreach (NetworkInterface adapter in nics) { IPInterfaceProperties properties = adapter.GetIPProperties(); PhysicalAddress address = adapter.GetPhysicalAddress(); byte[] bytes = address.GetAddressBytes(); macs.Add(string.Join("-", bytes.Select(x => x.ToString("X2")))); if (macs.Count >= 10) { break; } } return macs; } } } ================================================ FILE: src/Microsoft.HttpRepl.Telemetry/Microsoft.HttpRepl.Telemetry.csproj ================================================ net8.0 ================================================ FILE: src/Microsoft.HttpRepl.Telemetry/Paths.cs ================================================ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.IO; using System.Runtime.InteropServices; namespace Microsoft.HttpRepl.Telemetry { internal static class Paths { private const string DotnetHomeVariableName = "DOTNET_CLI_HOME"; private const string DotnetProfileDirectoryName = ".dotnet"; private const string ToolsShimFolderName = "tools"; static Paths() { UserProfile = Environment.GetEnvironmentVariable( RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "USERPROFILE" : "HOME"); DotnetToolsPath = Path.Combine(UserProfile, DotnetProfileDirectoryName, ToolsShimFolderName); var nugetPackagesEnvironmentVariable = Environment.GetEnvironmentVariable("NUGET_PACKAGES"); NugetCache = string.IsNullOrWhiteSpace(nugetPackagesEnvironmentVariable) ? Path.Combine(UserProfile, ".nuget", "packages") : nugetPackagesEnvironmentVariable; } public static string DotnetUserProfileFolderPath => Path.Combine(DotnetHomePath, DotnetProfileDirectoryName); public static string DotnetHomePath { get { var home = Environment.GetEnvironmentVariable(DotnetHomeVariableName); if (string.IsNullOrEmpty(home)) { home = UserProfile; if (string.IsNullOrEmpty(home)) { throw new DirectoryNotFoundException(); } } return home; } } public static string DotnetToolsPath { get; } public static string UserProfile { get; } public static string NugetCache { get; } public static readonly string InstallDirectory = Path.GetDirectoryName(typeof(Paths).Assembly.Location); } } ================================================ FILE: src/Microsoft.HttpRepl.Telemetry/ProcessStartInfoExtensions.cs ================================================ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Diagnostics; namespace Microsoft.HttpRepl.Telemetry { internal static class ProcessStartInfoExtensions { public static int ExecuteAndCaptureOutput(this ProcessStartInfo startInfo, out string stdOut, out string stdErr) { using var outStream = new StreamForwarder(); using var errStream = new StreamForwarder(); outStream.Capture(); errStream.Capture(); startInfo.RedirectStandardOutput = true; startInfo.RedirectStandardError = true; using var process = new Process { StartInfo = startInfo }; process.EnableRaisingEvents = true; process.Start(); var taskOut = outStream.BeginRead(process.StandardOutput); var taskErr = errStream.BeginRead(process.StandardError); process.WaitForExit(); taskOut.Wait(); taskErr.Wait(); stdOut = outStream.CapturedOutput; stdErr = errStream.CapturedOutput; return process.ExitCode; } } } ================================================ FILE: src/Microsoft.HttpRepl.Telemetry/Sha256Hasher.cs ================================================ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Security.Cryptography; using System.Text; namespace Microsoft.HttpRepl.Telemetry { public static class Sha256Hasher { /// /// The hashed mac address needs to be the same hashed value as produced by the other distinct sources given the same input. (e.g. VsCode) /// public static string Hash(string text) { using var sha256 = SHA256.Create(); return HashInFormat(sha256, text); } private static string HashInFormat(SHA256 sha256, string text) { byte[] bytes = Encoding.UTF8.GetBytes(text); byte[] hash = sha256.ComputeHash(bytes); StringBuilder hashString = new StringBuilder(); foreach (byte x in hash) { hashString.AppendFormat("{0:x2}", x); } return hashString.ToString(); } } } ================================================ FILE: src/Microsoft.HttpRepl.Telemetry/StreamForwarder.cs ================================================ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Microsoft.HttpRepl.Telemetry { [SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Not doing localization for telemetry yet.")] public sealed class StreamForwarder : IDisposable { private static readonly char[] s_ignoreCharacters = new char[] { '\r' }; private static readonly char s_flushBuilderCharacter = '\n'; private StringBuilder _builder; private StringWriter _capture; private Action _writeLine; public string CapturedOutput { get { return _capture?.GetStringBuilder()?.ToString(); } } public StreamForwarder Capture() { ThrowIfCaptureSet(); _capture = new StringWriter(); return this; } public StreamForwarder ForwardTo(Action writeLine) { ThrowIfNull(writeLine); ThrowIfForwarderSet(); _writeLine = writeLine; return this; } public Task BeginRead(TextReader reader) { return Task.Run(() => Read(reader)); } [SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "CA1062 doesn't understand `is not null`.")] public void Read(TextReader reader) { var bufferSize = 1; char currentCharacter; var buffer = new char[bufferSize]; _builder = new StringBuilder(); if (reader is not null) { // Using Read with buffer size 1 to prevent looping endlessly // like we would when using Read() with no buffer while (reader.Read(buffer, 0, bufferSize) > 0) { currentCharacter = buffer[0]; if (currentCharacter == s_flushBuilderCharacter) { WriteBuilder(); } else if (!s_ignoreCharacters.Contains(currentCharacter)) { _builder.Append(currentCharacter); } } } // Flush anything else when the stream is closed // Which should only happen if someone used console.Write WriteBuilder(); } private void WriteBuilder() { if (_builder.Length == 0) { return; } WriteLine(_builder.ToString()); _builder.Clear(); } private void WriteLine(string str) { if (_capture != null) { _capture.WriteLine(str); } if (_writeLine != null) { _writeLine(str); } } private void ThrowIfNull(object obj) { if (obj == null) { throw new ArgumentNullException(nameof(obj)); } } private void ThrowIfForwarderSet() { if (_writeLine != null) { throw new InvalidOperationException("WriteLine forwarder set previously"); // TODO: Localize this? } } private void ThrowIfCaptureSet() { if (_capture != null) { throw new InvalidOperationException("Already capturing stream!"); // TODO: Localize this? } } public void Dispose() { _capture?.Dispose(); _capture = null; } } } ================================================ FILE: src/Microsoft.HttpRepl.Telemetry/Telemetry.cs ================================================ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights; using Microsoft.DotNet.PlatformAbstractions; namespace Microsoft.HttpRepl.Telemetry { [SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We don't want any errors in telemetry to cause failures in the product.")] [SuppressMessage("Naming", "CA1724: Type names should not match namespaces", Justification = "Keeping it consistent with source implementations.")] public sealed class Telemetry : ITelemetry { internal static string CurrentSessionId; private TelemetryClient _client; private Dictionary _commonProperties; private Dictionary _commonMeasurements; private Task _trackEventTask; private const string InstrumentationKey = "469489a6-628b-4bb9-80db-ec670f70d874"; public const string TelemetryOptout = "DOTNET_HTTPREPL_TELEMETRY_OPTOUT"; public Telemetry( string productVersion, IFirstTimeUseNoticeSentinel sentinel = null, string sessionId = null, bool blockThreadInitialization = false) { FirstTimeUseNoticeSentinel = sentinel ?? new FirstTimeUseNoticeSentinel(productVersion); Enabled = !EnvironmentHelper.GetEnvironmentVariableAsBool(TelemetryOptout) && PermissionExists(FirstTimeUseNoticeSentinel); if (!Enabled) { return; } // Store the session ID in a static field so that it can be reused CurrentSessionId = sessionId ?? Guid.NewGuid().ToString(); if (blockThreadInitialization) { InitializeTelemetry(productVersion); } else { //initialize in task to offload to parallel thread _trackEventTask = Task.Factory.StartNew(() => InitializeTelemetry(productVersion), CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default); } } public bool Enabled { get; } public IFirstTimeUseNoticeSentinel FirstTimeUseNoticeSentinel { get; } public static bool SkipFirstTimeExperience => EnvironmentHelper.GetEnvironmentVariableAsBool(HttpRepl.Telemetry.FirstTimeUseNoticeSentinel.SkipFirstTimeExperienceEnvironmentVariableName, false); private bool PermissionExists(IFirstTimeUseNoticeSentinel sentinel) { if (sentinel == null) { return false; } return sentinel.Exists(); } public void TrackEvent(string eventName, IReadOnlyDictionary properties, IReadOnlyDictionary measurements) { if (!Enabled) { return; } //continue task in existing parallel thread _trackEventTask = _trackEventTask.ContinueWith( x => TrackEventTask(eventName, properties, measurements), TaskScheduler.Default ); } private void ThreadBlockingTrackEvent(string eventName, IReadOnlyDictionary properties, IReadOnlyDictionary measurements) { if (!Enabled) { return; } TrackEventTask(eventName, properties, measurements); } private void InitializeTelemetry(string productVersion) { try { #pragma warning disable CS0618 // Type or member is obsolete _client = new TelemetryClient(); #pragma warning restore CS0618 // Type or member is obsolete _client.InstrumentationKey = InstrumentationKey; _client.Context.Session.Id = CurrentSessionId; _client.Context.Device.OperatingSystem = RuntimeEnvironment.OperatingSystem; _commonProperties = new TelemetryCommonProperties(productVersion).GetTelemetryCommonProperties(); _commonMeasurements = new Dictionary(); } catch (Exception e) { _client = null; // we dont want to fail the tool if telemetry fails. Debug.Fail(e.ToString()); } } private void TrackEventTask( string eventName, IReadOnlyDictionary properties, IReadOnlyDictionary measurements) { if (_client == null) { return; } try { Dictionary eventProperties = GetEventProperties(properties); Dictionary eventMeasurements = GetEventMeasures(measurements); _client.TrackEvent(PrependProducerNamespace(eventName), eventProperties, eventMeasurements); _client.Flush(); } catch (Exception e) { Debug.Fail(e.ToString()); } } private static string PrependProducerNamespace(string eventName) { return "dotnet/httprepl/" + eventName; } private Dictionary GetEventMeasures(IReadOnlyDictionary measurements) { Dictionary eventMeasurements = new Dictionary(_commonMeasurements); if (measurements != null) { foreach (KeyValuePair measurement in measurements) { eventMeasurements[measurement.Key] = measurement.Value; } } return eventMeasurements; } private Dictionary GetEventProperties(IReadOnlyDictionary properties) { if (properties != null) { var eventProperties = new Dictionary(_commonProperties); foreach (KeyValuePair property in properties) { eventProperties[property.Key] = property.Value; } return eventProperties; } else { return _commonProperties; } } } } ================================================ FILE: src/Microsoft.HttpRepl.Telemetry/TelemetryCommonProperties.cs ================================================ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.Collections.Generic; using System.Runtime.InteropServices; using RuntimeEnvironment = Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment; namespace Microsoft.HttpRepl.Telemetry { internal class TelemetryCommonProperties { private readonly string _productVersion; public TelemetryCommonProperties( string productVersion, Func hasher = null, Func getMACAddress = null, IDockerContainerDetector dockerContainerDetector = null, IUserLevelCacheWriter userLevelCacheWriter = null) { _productVersion = productVersion; _hasher = hasher ?? Sha256Hasher.Hash; _getMACAddress = getMACAddress ?? MacAddressGetter.GetMacAddress; _dockerContainerDetector = dockerContainerDetector ?? new DockerContainerDetectorForTelemetry(); _userLevelCacheWriter = userLevelCacheWriter ?? new UserLevelCacheWriter(productVersion); } private readonly IUserLevelCacheWriter _userLevelCacheWriter; private readonly IDockerContainerDetector _dockerContainerDetector; private readonly Func _hasher; private readonly Func _getMACAddress; private const string OSVersion = "OS Version"; private const string OSPlatform = "OS Platform"; private const string RuntimeId = "Runtime Id"; private const string ProductVersion = "Product Version"; private const string DockerContainer = "Docker Container"; private const string MachineId = "Machine ID"; private const string KernelVersion = "Kernel Version"; private const string MachineIdCacheKey = "MachineId"; private const string IsDockerContainerCacheKey = "IsDockerContainer"; public Dictionary GetTelemetryCommonProperties() { return new Dictionary { {OSVersion, RuntimeEnvironment.OperatingSystemVersion}, {OSPlatform, RuntimeEnvironment.OperatingSystemPlatform.ToString()}, {RuntimeId, RuntimeEnvironment.GetRuntimeIdentifier()}, {ProductVersion, _productVersion}, {DockerContainer, IsDockerContainer()}, {MachineId, GetMachineId()}, {KernelVersion, GetKernelVersion()} }; } private string GetMachineId() { return _userLevelCacheWriter.RunWithCache(MachineIdCacheKey, () => { var macAddress = _getMACAddress(); if (macAddress != null) { return _hasher(macAddress); } else { return Guid.NewGuid().ToString(); } }); } private string IsDockerContainer() { return _userLevelCacheWriter.RunWithCache(IsDockerContainerCacheKey, () => { return _dockerContainerDetector.IsDockerContainer().ToString("G"); }); } /// /// Returns a string identifying the OS kernel. /// For Unix this currently comes from "uname -srv". /// For Windows this currently comes from RtlGetVersion(). /// private static string GetKernelVersion() { return RuntimeInformation.OSDescription; } } } ================================================ FILE: src/Microsoft.HttpRepl.Telemetry/UserLevelCacheWriter.cs ================================================ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.IO; namespace Microsoft.HttpRepl.Telemetry { public sealed class UserLevelCacheWriter : IUserLevelCacheWriter { private readonly string _productVersion; private readonly string _dotnetHttpReplUserProfileFolderPath; private readonly Func _fileExists; private readonly Func _directoryExists; private readonly Action _createDirectory; private readonly Action _writeAllText; private readonly Func _readAllText; public UserLevelCacheWriter(string productVersion) : this( productVersion, Paths.DotnetUserProfileFolderPath, File.Exists, Directory.Exists, path => Directory.CreateDirectory(path), File.WriteAllText, File.ReadAllText) { } public UserLevelCacheWriter( string productVersion, string dotnetHttpReplUserProfileFolderPath, Func fileExists, Func directoryExists, Action createDirectory, Action writeAllText, Func readAllText) { _productVersion = productVersion; _dotnetHttpReplUserProfileFolderPath = dotnetHttpReplUserProfileFolderPath; _fileExists = fileExists; _directoryExists = directoryExists; _createDirectory = createDirectory; _writeAllText = writeAllText; _readAllText = readAllText; } public string RunWithCache(string cacheKey, Func getValueToCache) { _ = getValueToCache ?? throw new ArgumentNullException(nameof(getValueToCache)); var cacheFilepath = GetCacheFilePath(cacheKey); try { if (!_fileExists(cacheFilepath)) { if (!_directoryExists(_dotnetHttpReplUserProfileFolderPath)) { _createDirectory(_dotnetHttpReplUserProfileFolderPath); } var runResult = getValueToCache(); _writeAllText(cacheFilepath, runResult); return runResult; } else { return _readAllText(cacheFilepath); } } catch (Exception ex) { if (ex is UnauthorizedAccessException || ex is PathTooLongException || ex is IOException) { return getValueToCache(); } throw; } } private string GetCacheFilePath(string cacheKey) { return Path.Combine(_dotnetHttpReplUserProfileFolderPath, $"{_productVersion}_{cacheKey}.dotnetHttpReplUserLevelCache"); } } } ================================================ FILE: src/Microsoft.Repl/Commanding/CommandHistory.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; namespace Microsoft.Repl.Commanding { public class CommandHistory : ICommandHistory { private readonly int _maxEntries; private readonly List _commandLines = new List(); private int _currentCommand = -1; private int _suspensionDepth; public CommandHistory(int maxEntries = 50) { _maxEntries = maxEntries; } public void AddCommand(string command) { if (_suspensionDepth > 0) { return; } _commandLines.Add(command); if (_commandLines.Count > _maxEntries) { _commandLines.RemoveAt(0); } _currentCommand = -1; } public string GetNextCommand() { if (_commandLines.Count == 0) { return string.Empty; } if (_currentCommand == -1 || _currentCommand >= _commandLines.Count - 1) { _currentCommand = -1; return string.Empty; } return _commandLines[++_currentCommand]; } public string GetPreviousCommand() { if (_commandLines.Count == 0) { return string.Empty; } if (_currentCommand == -1) { _currentCommand = _commandLines.Count; } if (_currentCommand > 0) { return _commandLines[--_currentCommand]; } return _commandLines[0]; } public IDisposable SuspendHistory() { ++_suspensionDepth; return new Disposable(() => --_suspensionDepth); } } } ================================================ FILE: src/Microsoft.Repl/Commanding/CommandInputLocation.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. namespace Microsoft.Repl.Commanding { public enum CommandInputLocation { CommandName, Argument, OptionName, OptionValue } } ================================================ FILE: src/Microsoft.Repl/Commanding/CommandInputProcessingIssue.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. namespace Microsoft.Repl.Commanding { public class CommandInputProcessingIssue { public CommandInputProcessingIssueKind Kind { get; } public string Text { get; } public CommandInputProcessingIssue(CommandInputProcessingIssueKind kind, string text) { Kind = kind; Text = text; } } } ================================================ FILE: src/Microsoft.Repl/Commanding/CommandInputProcessingIssueKind.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. namespace Microsoft.Repl.Commanding { public enum CommandInputProcessingIssueKind { CommandMismatch, ArgumentCountOutOfRange, UnknownOption, OptionUseCountOutOfRange, MissingRequiredOptionInput, } } ================================================ FILE: src/Microsoft.Repl/Commanding/CommandInputSpecification.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Collections.Generic; namespace Microsoft.Repl.Commanding { public class CommandInputSpecification { public IReadOnlyList> CommandName { get; } public char OptionPreamble { get; } public int MinimumArguments { get; } public int MaximumArguments { get; } public IReadOnlyList Options { get; } public CommandInputSpecification(IReadOnlyList> name, char optionPreamble, IReadOnlyList options, int minimumArgs, int maximumArgs) { CommandName = name; OptionPreamble = optionPreamble; MinimumArguments = minimumArgs; MaximumArguments = maximumArgs; if (MinimumArguments < 0) { MinimumArguments = 0; } if (MaximumArguments < MinimumArguments) { MaximumArguments = MinimumArguments; } Options = options; } public static CommandInputSpecificationBuilder Create(string baseName, params string[] additionalNameParts) { List nameParts = new List {baseName}; nameParts.AddRange(additionalNameParts); return new CommandInputSpecificationBuilder(nameParts); } } } ================================================ FILE: src/Microsoft.Repl/Commanding/CommandInputSpecificationBuilder.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; namespace Microsoft.Repl.Commanding { public class CommandInputSpecificationBuilder { private readonly List> _name; private char _optionPreamble; private int _minimumArgs; private int _maximumArgs; private readonly List _options = new List(); public CommandInputSpecificationBuilder(IReadOnlyList name) { _name = new List> { name }; _optionPreamble = '-'; } public CommandInputSpecificationBuilder WithOptionPreamble(char optionChar) { _optionPreamble = optionChar; return this; } public CommandInputSpecificationBuilder ExactArgCount(int count) { _minimumArgs = count; _maximumArgs = count; return this; } public CommandInputSpecificationBuilder MinimumArgCount(int count) { _minimumArgs = count; if (_maximumArgs < count) { _maximumArgs = count; } return this; } public CommandInputSpecificationBuilder MaximumArgCount(int count) { _maximumArgs = count; if (_minimumArgs > count) { _minimumArgs = count; } return this; } public CommandInputSpecificationBuilder WithOption(CommandOptionSpecification option) { _options.Add(option); return this; } public CommandInputSpecification Finish() { return new CommandInputSpecification(_name, _optionPreamble, _options, _minimumArgs, _maximumArgs); } public CommandInputSpecificationBuilder AlternateName(string baseName, params string[] additionalNameParts) { List nameParts = new List { baseName }; nameParts.AddRange(additionalNameParts); _name.Add(nameParts); return this; } } } ================================================ FILE: src/Microsoft.Repl/Commanding/CommandOptionSpecification.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Collections.Generic; namespace Microsoft.Repl.Commanding { public class CommandOptionSpecification { public string Id { get; } public IReadOnlyList Forms { get; } public int MaximumOccurrences { get; } public int MinimumOccurrences { get; } public bool AcceptsValue { get; } public bool RequiresValue { get; } public CommandOptionSpecification(string id, bool acceptsValue = false, bool requiresValue = false, int minimumOccurrences = 0, int maximumOccurrences = int.MaxValue, params string[] forms) { Id = id; Forms = forms; MinimumOccurrences = minimumOccurrences; MaximumOccurrences = maximumOccurrences > minimumOccurrences ? maximumOccurrences : minimumOccurrences; RequiresValue = requiresValue; AcceptsValue = RequiresValue || acceptsValue; } } } ================================================ FILE: src/Microsoft.Repl/Commanding/CommandWithStructuredInputBase.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Repl.Parsing; namespace Microsoft.Repl.Commanding { public abstract class CommandWithStructuredInputBase : ICommand where TParseResult : ICoreParseResult { public abstract string Name { get; } public abstract string GetHelpSummary(IShellState shellState, TProgramState programState); public string GetHelpDetails(IShellState shellState, TProgramState programState, TParseResult parseResult) { if (!DefaultCommandInput.TryProcess(InputSpec, parseResult, out DefaultCommandInput commandInput, out IReadOnlyList processingIssues) && processingIssues.Any(x => x.Kind == CommandInputProcessingIssueKind.CommandMismatch)) { //If this is the right command, just not the right syntax, report the usage errors return null; } return GetHelpDetails(shellState, programState, commandInput, parseResult); } protected abstract string GetHelpDetails(IShellState shellState, TProgramState programState, DefaultCommandInput commandInput, TParseResult parseResult); public IEnumerable Suggest(IShellState shellState, TProgramState programState, TParseResult parseResult) { DefaultCommandInput.TryProcess(InputSpec, parseResult, out DefaultCommandInput commandInput, out IReadOnlyList _); string normalCompletionString = parseResult.SelectedSection == parseResult.Sections.Count ? string.Empty : parseResult.Sections[parseResult.SelectedSection].Substring(0, parseResult.CaretPositionWithinSelectedSection); // Check the various command name permutations to see if we might be completing one of them IReadOnlyList commandName = null; for (int j = 0; j < InputSpec.CommandName.Count; ++j) { IReadOnlyList currentCommandNameParts = InputSpec.CommandName[j]; //If we're completing in a name position, offer completion for the command name if (parseResult.SelectedSection < currentCommandNameParts.Count) { bool success = true; for (int i = 0; i < parseResult.SelectedSection; ++i) { string currentCommandNamePart = currentCommandNameParts[i]; string currentParseSection = parseResult.Sections[i]; if (!string.Equals(currentCommandNamePart, currentParseSection, StringComparison.OrdinalIgnoreCase)) { success = false; break; } } if (success) { commandName = currentCommandNameParts; break; } } } if (commandName?.Count > parseResult.SelectedSection && commandName[parseResult.SelectedSection].StartsWith(normalCompletionString, StringComparison.OrdinalIgnoreCase)) { return new[] { commandName[parseResult.SelectedSection] }; } if (commandInput is null) { return null; } if (normalCompletionString.StartsWith(InputSpec.OptionPreamble.ToString(), StringComparison.OrdinalIgnoreCase)) { return GetOptionCompletions(commandInput, normalCompletionString); } IEnumerable completions = Enumerable.Empty(); CommandInputLocation? inputLocation = commandInput.SelectedElement?.Location; if (inputLocation != CommandInputLocation.OptionValue && commandInput.Arguments.Count < InputSpec.MaximumArguments) { IEnumerable results = GetArgumentSuggestionsForText(shellState, programState, parseResult, commandInput, normalCompletionString); if (results != null) { completions = results; } } switch (inputLocation) { case CommandInputLocation.OptionName: { IEnumerable results = GetOptionCompletions(commandInput, normalCompletionString); if (results != null) { completions = completions.Union(results); } break; } case CommandInputLocation.OptionValue: { IEnumerable results = GetOptionValueCompletions(shellState, programState, commandInput.SelectedElement.Owner.NormalizedText, commandInput, parseResult, normalCompletionString); if (results != null) { completions = completions.Union(results); } break; } case CommandInputLocation.Argument: { IEnumerable argumentResults = GetArgumentSuggestionsForText(shellState, programState, parseResult, commandInput, normalCompletionString); if (argumentResults != null) { completions = completions.Union(argumentResults); } if (string.IsNullOrEmpty(normalCompletionString)) { IEnumerable results = GetOptionCompletions(commandInput, normalCompletionString); if (results != null) { completions = completions.Union(results); } } break; } } return completions; } protected virtual IEnumerable GetOptionValueCompletions(IShellState shellState, TProgramState programState, string optionId, DefaultCommandInput commandInput, TParseResult parseResult, string normalizedCompletionText) { return null; } protected virtual IEnumerable GetArgumentSuggestionsForText(IShellState shellState, TProgramState programState, TParseResult parseResult, DefaultCommandInput commandInput, string normalCompletionString) { return null; } private IEnumerable GetOptionCompletions(DefaultCommandInput commandInput, string normalCompletionString) { return InputSpec.Options.Where(x => commandInput.Options[x.Id].Count < x.MaximumOccurrences) .SelectMany(x => x.Forms) .Where(x => x.StartsWith(normalCompletionString, StringComparison.OrdinalIgnoreCase)); } public bool? CanHandle(IShellState shellState, TProgramState programState, TParseResult parseResult) { shellState = shellState ?? throw new ArgumentNullException(nameof(shellState)); if (!DefaultCommandInput.TryProcess(InputSpec, parseResult, out DefaultCommandInput commandInput, out IReadOnlyList processingIssues)) { //If this is the right command, just not the right syntax, report the usage errors if (processingIssues.All(x => x.Kind != CommandInputProcessingIssueKind.CommandMismatch)) { foreach (CommandInputProcessingIssue issue in processingIssues) { shellState.ConsoleManager.Error.WriteLine(GetStringForIssue(issue)); } string help = GetHelpDetails(shellState, programState, parseResult); shellState.ConsoleManager.WriteLine(help); return false; } //If there was a mismatch in the command name, this isn't our input to handle return null; } return CanHandle(shellState, programState, commandInput); } protected virtual bool CanHandle(IShellState shellState, TProgramState programState, DefaultCommandInput commandInput) { return true; } protected virtual string GetStringForIssue(CommandInputProcessingIssue issue) { issue = issue ?? throw new ArgumentNullException(nameof(issue)); //TODO: Make this nicer return issue.Kind + " -- " + issue.Text; } public Task ExecuteAsync(IShellState shellState, TProgramState programState, TParseResult parseResult, CancellationToken cancellationToken) { if (!DefaultCommandInput.TryProcess(InputSpec, parseResult, out DefaultCommandInput commandInput, out IReadOnlyList _)) { return Task.CompletedTask; } return ExecuteAsync(shellState, programState, commandInput, parseResult, cancellationToken); } protected abstract Task ExecuteAsync(IShellState shellState, TProgramState programState, DefaultCommandInput commandInput, TParseResult parseResult, CancellationToken cancellationToken); public abstract CommandInputSpecification InputSpec { get; } } } ================================================ FILE: src/Microsoft.Repl/Commanding/DefaultCommandDispatcher.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Parsing; namespace Microsoft.Repl.Commanding { public static class DefaultCommandDispatcher { public static DefaultCommandDispatcher Create(Func getPrompt, TProgramState programState) { return new DefaultCommandDispatcher(getPrompt, programState); } public static DefaultCommandDispatcher Create(Action onReady, TProgramState programState) { return new DefaultCommandDispatcher(onReady, programState); } public static DefaultCommandDispatcher Create(Func getPrompt, TProgramState programState, IParser parser) where TParseResult : ICoreParseResult { return new DefaultCommandDispatcher(getPrompt, programState, parser); } public static DefaultCommandDispatcher Create(Action onReady, TProgramState programState, IParser parser) where TParseResult : ICoreParseResult { return new DefaultCommandDispatcher(onReady, programState, parser); } } public class DefaultCommandDispatcher : DefaultCommandDispatcher { public DefaultCommandDispatcher(Func getPrompt, TProgramState programState) : base(getPrompt, programState, new CoreParser()) { } public DefaultCommandDispatcher(Action onReady, TProgramState programState) : base(onReady, programState, new CoreParser()) { } } public class DefaultCommandDispatcher : ICommandDispatcher where TParseResult : ICoreParseResult { private readonly Action _onReady; private readonly TProgramState _programState; private readonly IParser _parser; private readonly HashSet> _commands = new HashSet>(); private bool _isReady; public DefaultCommandDispatcher(Func getPrompt, TProgramState programState, IParser parser) : this(s => s.ConsoleManager.Write(getPrompt()), programState, parser) { } public DefaultCommandDispatcher(Action onReady, TProgramState programState, IParser parser) { _onReady = onReady; _programState = programState; _parser = parser; } public void AddCommand(ICommand command) { _commands.Add(command); } public IEnumerable> Commands => _commands; public IParser Parser => _parser; public IReadOnlyList CollectSuggestions(IShellState shellState) { shellState = shellState ?? throw new ArgumentNullException(nameof(shellState)); string line = shellState.InputManager.GetCurrentBuffer(); TParseResult parseResult = _parser.Parse(line, shellState.InputManager.CaretPosition); HashSet suggestions = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (ICommand command in _commands) { IEnumerable commandSuggestions = command.Suggest(shellState, _programState, parseResult); if (commandSuggestions != null) { suggestions.UnionWith(commandSuggestions); } } return suggestions.OrderBy(x => x, StringComparer.OrdinalIgnoreCase).ToList(); } public async Task ExecuteCommandAsync(IShellState shellState, CancellationToken cancellationToken) { shellState = shellState ?? throw new ArgumentNullException(nameof(shellState)); _isReady = false; shellState.ConsoleManager.WriteLine(); string commandText = shellState.InputManager.GetCurrentBuffer(); if (!string.IsNullOrWhiteSpace(commandText)) { shellState.CommandHistory.AddCommand(shellState.InputManager.GetCurrentBuffer()); try { await ExecuteCommandInternalAsync(shellState, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { shellState.ConsoleManager.Error.WriteLine(ex.ToString().Bold().Red()); } if (cancellationToken.IsCancellationRequested) { shellState.ConsoleManager.Error.WriteLine(Resources.Strings.DefaultCommandDispatcher_Error_ExecutionWasCancelled.Bold().Red()); } } if (!_isReady && !shellState.IsExiting) { shellState.ConsoleManager.WriteLine(); OnReady(shellState); } shellState.InputManager.ResetInput(); } private async Task ExecuteCommandInternalAsync(IShellState shellState, CancellationToken cancellationToken) { string line = shellState.InputManager.GetCurrentBuffer(); TParseResult parseResult = _parser.Parse(line, shellState.InputManager.CaretPosition); if (!string.IsNullOrWhiteSpace(parseResult.CommandText)) { foreach (ICommand command in _commands) { bool? result = command.CanHandle(shellState, _programState, parseResult); if (result.HasValue) { if (result.Value) { await command.ExecuteAsync(shellState, _programState, parseResult, cancellationToken); } //If the handler returned non-null, the input would be directed to it, but it's not valid input return; } } shellState.ConsoleManager.Error.WriteLine(Resources.Strings.DefaultCommandDispatcher_Error_NoMatchingCommand.Red().Bold()); shellState.ConsoleManager.Error.WriteLine(Resources.Strings.DefaultCommandDispatcher_Error_SeeHelp.Red().Bold()); } } public void OnReady(IShellState shellState) { shellState = shellState ?? throw new ArgumentNullException(nameof(shellState)); if (!_isReady && !shellState.IsExiting) { _onReady(shellState); shellState.InputManager.ResetInput(); _isReady = true; } } } } ================================================ FILE: src/Microsoft.Repl/Commanding/DefaultCommandInput.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using Microsoft.Repl.Parsing; namespace Microsoft.Repl.Commanding { public class DefaultCommandInput where TParseResult : ICoreParseResult { public DefaultCommandInput(IReadOnlyList commandName, IReadOnlyList arguments, IReadOnlyDictionary> options, InputElement selectedElement) { CommandName = commandName; Arguments = arguments; Options = options; SelectedElement = selectedElement; } public static bool TryProcess(CommandInputSpecification spec, TParseResult parseResult, out DefaultCommandInput result, out IReadOnlyList processingIssues) { spec = spec ?? throw new ArgumentNullException(nameof(spec)); List issues = null; List commandNameElements = null; foreach (IReadOnlyList commandName in spec.CommandName) { if (TryProcessCommandName(commandName, parseResult, out List nameElements, out issues)) { commandNameElements = nameElements; break; } } if (commandNameElements is null) { result = null; processingIssues = issues; return false; } List arguments = new List(); Dictionary options = new Dictionary(); InputElement currentOption = null; CommandOptionSpecification currentOptionSpec = null; InputElement selectedElement = null; for (int i = commandNameElements.Count; i < parseResult.Sections.Count; ++i) { //If we're not looking at an option name if (!parseResult.Sections[i].StartsWith(spec.OptionPreamble.ToString(), StringComparison.OrdinalIgnoreCase) || parseResult.IsQuotedSection(i)) { if (currentOption is null) { InputElement currentElement = new InputElement(CommandInputLocation.Argument, parseResult.Sections[i], parseResult.Sections[i], i); if (i == parseResult.SelectedSection) { selectedElement = currentElement; } arguments.Add(currentElement); } else { //If the option isn't a defined one or it is and indicates that it accepts a value, add the section as an option value, // otherwise add it as an argument if (currentOptionSpec?.AcceptsValue ?? true) { InputElement currentElement = new InputElement(currentOption, CommandInputLocation.OptionValue, parseResult.Sections[i], parseResult.Sections[i], i); if (i == parseResult.SelectedSection) { selectedElement = currentElement; } options[currentOption] = currentElement; currentOption = null; currentOptionSpec = null; } else { InputElement currentElement = new InputElement(CommandInputLocation.Argument, parseResult.Sections[i], parseResult.Sections[i], i); if (i == parseResult.SelectedSection) { selectedElement = currentElement; } arguments.Add(currentElement); } } } //If we are looking at an option name else { //Otherwise, check to see whether the previous option had a required argument before committing it if (currentOption is object) { options[currentOption] = null; if (currentOptionSpec?.RequiresValue ?? false) { issues.Add(new CommandInputProcessingIssue(CommandInputProcessingIssueKind.MissingRequiredOptionInput, currentOption.Text)); } } CommandOptionSpecification optionSpec = spec.Options.FirstOrDefault(x => x.Forms.Any(y => string.Equals(y, parseResult.Sections[i], StringComparison.Ordinal))); if (optionSpec is null) { issues.Add(new CommandInputProcessingIssue(CommandInputProcessingIssueKind.UnknownOption, parseResult.Sections[i])); } currentOption = new InputElement(CommandInputLocation.OptionName, parseResult.Sections[i], optionSpec?.Id, i); if (i == parseResult.SelectedSection) { selectedElement = currentOption; } currentOptionSpec = optionSpec; } } //Clear any option in progress if (currentOption is object) { options[currentOption] = null; if (currentOptionSpec?.RequiresValue ?? false) { issues.Add(new CommandInputProcessingIssue(CommandInputProcessingIssueKind.MissingRequiredOptionInput, currentOption.Text)); } } //Check to make sure our argument count is in range, if not add an issue if (arguments.Count > spec.MaximumArguments || arguments.Count < spec.MinimumArguments) { issues.Add(new CommandInputProcessingIssue(CommandInputProcessingIssueKind.ArgumentCountOutOfRange, arguments.Count.ToString())); } //Build up the dictionary of options by normal form, then validate counts for every option in the spec Dictionary> optionsByNormalForm = new Dictionary>(StringComparer.Ordinal); foreach (KeyValuePair entry in options) { if (entry.Key.NormalizedText is null) { continue; } if (!optionsByNormalForm.TryGetValue(entry.Key.NormalizedText, out IReadOnlyList rawBucket)) { optionsByNormalForm[entry.Key.NormalizedText] = rawBucket = new List(); } List bucket = (List) rawBucket; bucket.Add(entry.Value); } foreach (CommandOptionSpecification optionSpec in spec.Options) { if (!optionsByNormalForm.TryGetValue(optionSpec.Id, out IReadOnlyList values)) { optionsByNormalForm[optionSpec.Id] = values = new List(); } if (values.Count < optionSpec.MinimumOccurrences || values.Count > optionSpec.MaximumOccurrences) { issues.Add(new CommandInputProcessingIssue(CommandInputProcessingIssueKind.OptionUseCountOutOfRange, values.Count.ToString())); } } result = new DefaultCommandInput(commandNameElements, arguments, optionsByNormalForm, selectedElement); processingIssues = issues; return issues.Count == 0; } private static bool TryProcessCommandName(IReadOnlyList commandName, TParseResult parseResult, out List nameElements, out List processingIssues) { List issues = new List(); List commandNameElements = new List(); if (commandName.Count > parseResult.Sections.Count) { issues.Add(new CommandInputProcessingIssue(CommandInputProcessingIssueKind.CommandMismatch, commandName[parseResult.Sections.Count])); } for (int i = 0; i < commandName.Count && i < parseResult.Sections.Count; ++i) { if (!string.Equals(commandName[i], parseResult.Sections[i], StringComparison.OrdinalIgnoreCase)) { issues.Add(new CommandInputProcessingIssue(CommandInputProcessingIssueKind.CommandMismatch, parseResult.Sections[i])); } commandNameElements.Add(new InputElement(CommandInputLocation.CommandName, parseResult.Sections[i], commandName[i], i)); } processingIssues = issues; //If we have a command name mismatch, no point in continuing if (issues.Count > 0) { nameElements = null; return false; } nameElements = commandNameElements; return true; } public InputElement SelectedElement { get; } public IReadOnlyList CommandName { get; } public IReadOnlyList Arguments { get; } public IReadOnlyDictionary> Options { get; } } } ================================================ FILE: src/Microsoft.Repl/Commanding/ICommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.Repl.Parsing; namespace Microsoft.Repl.Commanding { public interface ICommand where TParseResult : ICoreParseResult { /// /// Identifies the command in telemetry events. /// string Name { get; } string GetHelpSummary(IShellState shellState, TProgramState programState); string GetHelpDetails(IShellState shellState, TProgramState programState, TParseResult parseResult); IEnumerable Suggest(IShellState shellState, TProgramState programState, TParseResult parseResult); bool? CanHandle(IShellState shellState, TProgramState programState, TParseResult parseResult); Task ExecuteAsync(IShellState shellState, TProgramState programState, TParseResult parseResult, CancellationToken cancellationToken); } } ================================================ FILE: src/Microsoft.Repl/Commanding/ICommandDispatcher.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.Repl.Parsing; namespace Microsoft.Repl.Commanding { public interface ICommandDispatcher { IParser Parser { get; } IReadOnlyList CollectSuggestions(IShellState shellState); void OnReady(IShellState shellState); Task ExecuteCommandAsync(IShellState shellState, CancellationToken cancellationToken); } public interface ICommandDispatcher : ICommandDispatcher where TParseResult : ICoreParseResult { IEnumerable> Commands { get; } } } ================================================ FILE: src/Microsoft.Repl/Commanding/ICommandHistory.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; namespace Microsoft.Repl.Commanding { public interface ICommandHistory { string GetPreviousCommand(); string GetNextCommand(); void AddCommand(string command); IDisposable SuspendHistory(); } } ================================================ FILE: src/Microsoft.Repl/Commanding/InputElement.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. namespace Microsoft.Repl.Commanding { public class InputElement { public CommandInputLocation Location { get; } public string Text { get; } public string NormalizedText { get; } public InputElement Owner { get; } public int ParseResultSectionIndex { get; } public InputElement(CommandInputLocation location, string text, string normalizedText, int sectionIndex) : this(null, location, text, normalizedText, sectionIndex) { } public InputElement(InputElement owner, CommandInputLocation location, string text, string normalizedText, int sectionIndex) { Owner = owner; Location = location; Text = text; NormalizedText = normalizedText; ParseResultSectionIndex = sectionIndex; } } } ================================================ FILE: src/Microsoft.Repl/ConsoleHandling/AllowedColors.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; namespace Microsoft.Repl.ConsoleHandling { // The values for the non-bold colors come from the ANSI escape sequences, // specifically the SGR foreground codes, which can be referenced here: // https://en.wikipedia.org/wiki/ANSI_escape_code#Colors [Flags] public enum AllowedColors { None = 0x0, Black = 0x1E, BoldBlack = Bold | Black, Red = 0x1F, BoldRed = Bold | Red, Green = 0x20, BoldGreen = Bold | Green, Yellow = 0x21, BoldYellow = Bold | Yellow, Blue = 0x22, BoldBlue = Bold | Blue, Magenta = 0x23, BoldMagenta = Bold | Magenta, Cyan = 0x24, BoldCyan = Bold | Cyan, White = 0x25, BoldWhite = White | Bold, Bold = 0x100 } } ================================================ FILE: src/Microsoft.Repl/ConsoleHandling/AnsiColorExtensions.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. namespace Microsoft.Repl.ConsoleHandling { public static class AnsiColorExtensions { // For reference on these codes and values, see: // https://en.wikipedia.org/wiki/ANSI_escape_code#Escape_sequences private const string _ansiControlSequenceIntroducer = "\x1B["; private const string _ansiSgrCode = "m"; private static readonly string _ansiSgrDefaultForegroundColor = $"{_ansiControlSequenceIntroducer}39{_ansiSgrCode}"; private static readonly string _ansiSgrBold = $"{_ansiControlSequenceIntroducer}1{_ansiSgrCode}"; public static string Black(this string text) { return SetColorInternal(text, AllowedColors.Black); } public static string Red(this string text) { return SetColorInternal(text, AllowedColors.Red); } public static string Green(this string text) { return SetColorInternal(text, AllowedColors.Green); } public static string Yellow(this string text) { return SetColorInternal(text, AllowedColors.Yellow); } public static string Blue(this string text) { return SetColorInternal(text, AllowedColors.Blue); } public static string Magenta(this string text) { return SetColorInternal(text, AllowedColors.Magenta); } public static string Cyan(this string text) { return SetColorInternal(text, AllowedColors.Cyan); } public static string White(this string text) { return SetColorInternal(text, AllowedColors.White); } public static string Bold(this string text) { return $"{_ansiSgrBold}{text}{_ansiSgrDefaultForegroundColor}"; } private static string SetColorInternal(string text, AllowedColors color) { int sgrParameter = (int)color; return $"{_ansiControlSequenceIntroducer}{sgrParameter}{_ansiSgrCode}{text}{_ansiSgrDefaultForegroundColor}"; } public static string SetColor(this string textToColor, AllowedColors color) { if (color.HasFlag(AllowedColors.Bold)) { textToColor = textToColor.Bold(); color &= ~AllowedColors.Bold; } switch (color) { case AllowedColors.Black: case AllowedColors.Red: case AllowedColors.Green: case AllowedColors.Yellow: case AllowedColors.Blue: case AllowedColors.Magenta: case AllowedColors.Cyan: case AllowedColors.White: return SetColorInternal(textToColor, color); default: return textToColor; } } } } ================================================ FILE: src/Microsoft.Repl/ConsoleHandling/AnsiConsole.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.IO; namespace Microsoft.Repl.ConsoleHandling { public class AnsiConsole { private AnsiConsole(TextWriter writer) { Writer = writer; OriginalForegroundColor = Console.ForegroundColor; } private int _boldRecursion; public static AnsiConsole GetOutput() { return new AnsiConsole(Console.Out); } public static AnsiConsole GetError() { return new AnsiConsole(Console.Error); } public TextWriter Writer { get; } public ConsoleColor OriginalForegroundColor { get; } private void SetColor(ConsoleColor color) { const int light = 0x08; int c = (int)color; Console.ForegroundColor = c < 0 ? color : // unknown, just use it _boldRecursion > 0 ? (ConsoleColor)(c | light) : // ensure color is light (ConsoleColor)(c & ~light); // ensure color is dark } private void SetBold(bool bold) { _boldRecursion += bold ? 1 : -1; if (_boldRecursion > 1 || (_boldRecursion == 1 && !bold)) { return; } // switches on _boldRecursion to handle boldness SetColor(Console.ForegroundColor); } public void WriteLine(string message) { Write(message); Writer.WriteLine(); } public void Write(char message) { Writer.Write(message); } public void Write(string message) { if (message is null) { return; } var escapeScan = 0; for (; ; ) { var escapeIndex = message.IndexOf("\x1b[", escapeScan, StringComparison.Ordinal); if (escapeIndex == -1) { var text = message.Substring(escapeScan); Writer.Write(text); break; } else { var startIndex = escapeIndex + 2; var endIndex = startIndex; while (endIndex != message.Length && message[endIndex] >= 0x20 && message[endIndex] <= 0x3f) { endIndex += 1; } var text = message.Substring(escapeScan, escapeIndex - escapeScan); Writer.Write(text); if (endIndex == message.Length) { break; } switch (message[endIndex]) { case 'm': if (int.TryParse(message.Substring(startIndex, endIndex - startIndex), out int value)) { switch (value) { case 1: SetBold(true); break; case 22: SetBold(false); break; case 30: SetColor(ConsoleColor.Black); break; case 31: SetColor(ConsoleColor.Red); break; case 32: SetColor(ConsoleColor.Green); break; case 33: SetColor(ConsoleColor.Yellow); break; case 34: SetColor(ConsoleColor.Blue); break; case 35: SetColor(ConsoleColor.Magenta); break; case 36: SetColor(ConsoleColor.Cyan); break; case 37: SetColor(ConsoleColor.Gray); break; case 39: Console.ForegroundColor = OriginalForegroundColor; break; } } break; } escapeScan = endIndex + 1; } } } } } ================================================ FILE: src/Microsoft.Repl/ConsoleHandling/ConsoleManager.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using System.Threading; namespace Microsoft.Repl.ConsoleHandling { public class ConsoleManager : IConsoleManager { private readonly List _breakHandlers = new List(); public Point Caret => new Point(Console.CursorLeft, Console.CursorTop); public bool IsKeyAvailable => Console.KeyAvailable; public bool IsCaretVisible { get => Reporter.Output.IsCaretVisible; set => Reporter.Output.IsCaretVisible = value; } public ConsoleManager() { Error = new Writable(Reporter.Error); Console.CancelKeyPress += OnCancelKeyPress; } public void Clear() { Console.Clear(); } public void MoveCaret(int positions) { if (positions == 0) { return; } int bufferWidth = Console.BufferWidth; int cursorTop = Console.CursorTop; int cursorLeft = Console.CursorLeft; while (positions < 0) { if (-positions > bufferWidth) { if (cursorTop == 0) { cursorLeft = 0; positions = 0; } else { positions += bufferWidth; --cursorTop; } } else { int remaining = cursorLeft + positions; if (remaining >= 0) { cursorLeft = remaining; } else if (cursorTop == 0) { cursorLeft = 0; } else { --cursorTop; cursorLeft = bufferWidth + remaining; } positions = 0; } } while (positions > 0) { if (positions > bufferWidth) { positions -= bufferWidth; ++cursorTop; } else { int spaceLeftOnLine = bufferWidth - cursorLeft - 1; if (positions > spaceLeftOnLine) { ++cursorTop; cursorLeft = positions - spaceLeftOnLine - 1; } else { cursorLeft += positions; } positions = 0; } } Console.SetCursorPosition(cursorLeft, cursorTop); } public ConsoleKeyInfo ReadKey(CancellationToken cancellationToken) { while (!Console.KeyAvailable && !cancellationToken.IsCancellationRequested) { Thread.Sleep(2); } if (cancellationToken.IsCancellationRequested) { return default; } else { return Console.ReadKey(true); } } public void Write(char c) { Reporter.Output.Write(c); } public void Write(string s) { Reporter.Output.Write(s); } public void WriteLine() { Reporter.Output.WriteLine(); } public void WriteLine(string s) { if (s is null) { return; } Reporter.Output.WriteLine(s); } public IDisposable AddBreakHandler(Action onBreak) { Disposable result = new Disposable(() => ReleaseBreakHandler(onBreak)); _breakHandlers.Add(onBreak); return result; } private void OnCancelKeyPress(object sender, ConsoleCancelEventArgs e) { e.Cancel = true; Action handler = _breakHandlers.LastOrDefault(); handler?.Invoke(); } private void ReleaseBreakHandler(Action handler) { _breakHandlers.Remove(handler); } public IWritable Error { get; } public bool AllowOutputRedirection => false; } } ================================================ FILE: src/Microsoft.Repl/ConsoleHandling/IConsoleManager.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Threading; namespace Microsoft.Repl.ConsoleHandling { public interface IConsoleManager : IWritable { Point Caret { get; } #pragma warning disable CA1716 // Identifiers should not match keywords IWritable Error { get; } #pragma warning restore CA1716 // Identifiers should not match keywords bool IsKeyAvailable { get; } void Clear(); void MoveCaret(int positions); ConsoleKeyInfo ReadKey(CancellationToken cancellationToken); IDisposable AddBreakHandler(Action onBreak); bool AllowOutputRedirection { get; } } } ================================================ FILE: src/Microsoft.Repl/ConsoleHandling/IWritable.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. namespace Microsoft.Repl.ConsoleHandling { public interface IWritable { void Write(char c); void Write(string s); void WriteLine(); void WriteLine(string s); bool IsCaretVisible { get; set; } } } ================================================ FILE: src/Microsoft.Repl/ConsoleHandling/Point.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; namespace Microsoft.Repl.ConsoleHandling { public struct Point : IEquatable { public int X { get; } public int Y { get; } public Point(int x, int y) { X = x; Y = y; } public override bool Equals(object obj) { if (obj is null) { return false; } if (!(obj is Point other)) { return false; } else { return Equals(other); } } public override int GetHashCode() { int hash = 27; hash = (13 * hash) + X.GetHashCode(); hash = (13 * hash) + Y.GetHashCode(); return hash; } public static bool operator ==(Point left, Point right) { return left.Equals(right); } public static bool operator !=(Point left, Point right) { return !(left == right); } public bool Equals(Point other) { return X == other.X && Y == other.Y; } } } ================================================ FILE: src/Microsoft.Repl/ConsoleHandling/Reporter.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; namespace Microsoft.Repl.ConsoleHandling { public class Reporter : IWritable { private static readonly Reporter NullReporter = new Reporter(null); private static readonly object Sync = new object(); private readonly AnsiConsole _console; static Reporter() { Reset(); } private Reporter(AnsiConsole console) { _console = console; } public static Reporter Output { get; private set; } public static Reporter Error { get; private set; } public static Reporter Verbose { get; private set; } /// /// Resets the Reporters to write to the current Console Out/Error. /// public static void Reset() { lock (Sync) { Output = new Reporter(AnsiConsole.GetOutput()); Error = new Reporter(AnsiConsole.GetError()); Verbose = IsVerbose ? new Reporter(AnsiConsole.GetOutput()) : NullReporter; } } public void WriteLine(string s) { if (s is null) { return; } lock (Sync) { if (ShouldPassAnsiCodesThrough) { _console?.Writer?.WriteLine(s); } else { _console?.WriteLine(s); } } } public void WriteLine() { lock (Sync) { _console?.Writer?.WriteLine(); } } public void Write(char c) { lock (Sync) { if (ShouldPassAnsiCodesThrough) { _console?.Writer?.Write(c); } else { _console?.Write(c); } } } public void Write(string s) { lock (Sync) { if (ShouldPassAnsiCodesThrough) { _console?.Writer?.Write(s); } else { _console?.Write(s); } } } private static bool IsVerbose => bool.TryParse(Environment.GetEnvironmentVariable("DOTNET_CLI_CONTEXT_VERBOSE") ?? "false", out bool value) && value; private static bool ShouldPassAnsiCodesThrough => bool.TryParse(Environment.GetEnvironmentVariable("DOTNET_CLI_CONTEXT_ANSI_PASS_THRU") ?? "false", out bool value) && value; private bool _isCaretVisible = true; public bool IsCaretVisible { get => _isCaretVisible; set { Console.CursorVisible = value; _isCaretVisible = value; } } } } ================================================ FILE: src/Microsoft.Repl/ConsoleHandling/Writable.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. namespace Microsoft.Repl.ConsoleHandling { internal class Writable : IWritable { private readonly Reporter _reporter; public Writable(Reporter reporter) { _reporter = reporter; } public bool IsCaretVisible { get => _reporter.IsCaretVisible; set => _reporter.IsCaretVisible = value; } public void Write(char c) { _reporter.Write(c); } public void Write(string s) { _reporter.Write(s); } public void WriteLine() { _reporter.WriteLine(); } public void WriteLine(string s) { _reporter.WriteLine(s); } } } ================================================ FILE: src/Microsoft.Repl/Disposable.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; namespace Microsoft.Repl { public class Disposable : IDisposable { private Action _onDispose; public Disposable(Action onDispose) { _onDispose = onDispose; } public virtual void Dispose() { _onDispose?.Invoke(); _onDispose = null; GC.SuppressFinalize(this); } } public class Disposable : Disposable where T : class { public Disposable(T value, Action onDispose) : base (onDispose) { Value = value; } public T Value { get; private set; } public override void Dispose() { if (Value is IDisposable d) { d.Dispose(); Value = null; } base.Dispose(); GC.SuppressFinalize(this); } } } ================================================ FILE: src/Microsoft.Repl/IShellState.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using Microsoft.Repl.Commanding; using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Input; using Microsoft.Repl.Suggestions; namespace Microsoft.Repl { public interface IShellState { IInputManager InputManager { get; } ICommandHistory CommandHistory { get; } IConsoleManager ConsoleManager { get; } ICommandDispatcher CommandDispatcher { get; } ISuggestionManager SuggestionManager { get; } bool IsExiting { get; set; } void MoveCarets(int positions); } } ================================================ FILE: src/Microsoft.Repl/Input/AsyncKeyPressHandler.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Threading; using System.Threading.Tasks; namespace Microsoft.Repl.Input { public delegate Task AsyncKeyPressHandler(ConsoleKeyInfo keyInfo, IShellState state, CancellationToken cancellationToken); } ================================================ FILE: src/Microsoft.Repl/Input/IInputManager.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Threading; using System.Threading.Tasks; namespace Microsoft.Repl.Input { public interface IInputManager { bool IsOverwriteMode { get; set; } int CaretPosition { get; } IInputManager RegisterKeyHandler(ConsoleKey key, AsyncKeyPressHandler handler); IInputManager RegisterKeyHandler(ConsoleKey key, ConsoleModifiers modifiers, AsyncKeyPressHandler handler); void ResetInput(); Task StartAsync(IShellState state, CancellationToken cancellationToken); void SetInput(IShellState state, string input); string GetCurrentBuffer(); void RemovePreviousCharacter(IShellState state); void RemoveCurrentCharacter(IShellState state); void Clear(IShellState state); void MoveCaret(int positions); } } ================================================ FILE: src/Microsoft.Repl/Input/InputManager.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; namespace Microsoft.Repl.Input { public class InputManager : IInputManager { private readonly Dictionary> _handlers = new Dictionary>(); private readonly List _inputBuffer = new List(); public InputManager() { } /// /// For testing purposes only /// internal InputManager(string initialInput, int initialPosition) { _inputBuffer.AddRange(initialInput); CaretPosition = initialPosition; } public bool IsOverwriteMode { get; set; } public int CaretPosition { get; private set; } public void MoveCaret(int positions) { if (CaretPosition + positions < 0) { CaretPosition = 0; } else if (CaretPosition + positions > _inputBuffer.Count) { CaretPosition = _inputBuffer.Count; } else { CaretPosition += positions; } } public void Clear(IShellState state) { SetInput(state, string.Empty); } public string GetCurrentBuffer() { return _inputBuffer.Stringify(); } public IInputManager RegisterKeyHandler(ConsoleKey key, AsyncKeyPressHandler handler) { if (!_handlers.TryGetValue(key, out Dictionary handlers)) { _handlers[key] = handlers = new Dictionary(); } if (handler == null) { handlers.Remove(default); } else { handlers[default] = handler; } return this; } public IInputManager RegisterKeyHandler(ConsoleKey key, ConsoleModifiers modifiers, AsyncKeyPressHandler handler) { if (!_handlers.TryGetValue(key, out Dictionary handlers)) { _handlers[key] = handlers = new Dictionary(); } if (handler == null) { handlers.Remove(modifiers); } else { handlers[modifiers] = handler; } return this; } public void RemoveCurrentCharacter(IShellState state) { state = state ?? throw new ArgumentNullException(nameof(state)); int caret = CaretPosition; if (caret == _inputBuffer.Count) { return; } List update = _inputBuffer.ToList(); update.RemoveAt(caret); state.ConsoleManager.IsCaretVisible = false; SetInput(state, update); state.MoveCarets(caret - CaretPosition); state.ConsoleManager.IsCaretVisible = true; } public void RemovePreviousCharacter(IShellState state) { state = state ?? throw new ArgumentNullException(nameof(state)); int caret = CaretPosition; if (caret == 0) { return; } List update = _inputBuffer.ToList(); update.RemoveAt(caret - 1); state.ConsoleManager.IsCaretVisible = false; SetInput(state, update); state.MoveCarets(caret - CaretPosition - 1); state.ConsoleManager.IsCaretVisible = true; } public void SetInput(IShellState state, string input) { state = state ?? throw new ArgumentNullException(nameof(state)); input = input ?? throw new ArgumentNullException(nameof(input)); SetInput(state, input.ToCharArray()); } public void ResetInput() { _inputBuffer.Clear(); CaretPosition = 0; } private string _ttyState; private void StashEchoState() { string sttyFlags = null; if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { _ttyState = GetTtyState(); sttyFlags = "gfmt1:erase=08:werase=08 -echo -icanon"; } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { _ttyState = GetTtyState(); sttyFlags = "erase 0x08 werase 0x08 -echo -icanon"; } if (!string.IsNullOrEmpty(sttyFlags)) { ProcessStartInfo psi = new ProcessStartInfo("stty", sttyFlags); Process p = Process.Start(psi); p?.WaitForExit(); } } private static string GetTtyState() { ProcessStartInfo psi = new ProcessStartInfo("stty", "-g") { RedirectStandardOutput = true }; Process p = Process.Start(psi); p?.WaitForExit(); string result = p?.StandardOutput.ReadToEnd().Trim(); return result; } private void RestoreTtyState() { if (!string.IsNullOrEmpty(_ttyState)) { ProcessStartInfo psi = new ProcessStartInfo("stty", _ttyState); Process p = Process.Start(psi); p?.WaitForExit(); } } private void SetInput(IShellState state, IReadOnlyList input) { bool oldCaretVisibility = state.ConsoleManager.IsCaretVisible; state.ConsoleManager.IsCaretVisible = false; int lastCommonPosition = 0; for (; lastCommonPosition < input.Count && lastCommonPosition < _inputBuffer.Count && _inputBuffer[lastCommonPosition] == input[lastCommonPosition]; ++lastCommonPosition) { } state.ConsoleManager.MoveCaret(-CaretPosition + lastCommonPosition); string str = new string(input.Skip(lastCommonPosition).ToArray()); int trailing = _inputBuffer.Count - input.Count; if (trailing > 0) { str = str.PadRight(trailing + str.Length); } state.ConsoleManager.Write(str); _inputBuffer.Clear(); _inputBuffer.AddRange(input); if (trailing > 0) { state.ConsoleManager.MoveCaret(-trailing); } CaretPosition = _inputBuffer.Count; if (oldCaretVisibility) { state.ConsoleManager.IsCaretVisible = true; } } public async Task StartAsync(IShellState state, CancellationToken cancellationToken) { _ = state ?? throw new ArgumentNullException(nameof(state)); StashEchoState(); try { List presses = null; while (!state.IsExiting && !cancellationToken.IsCancellationRequested) { ConsoleKeyInfo keyPress = state.ConsoleManager.ReadKey(cancellationToken); if (_handlers.TryGetValue(keyPress.Key, out Dictionary handlerLookup) && handlerLookup.TryGetValue(keyPress.Modifiers, out AsyncKeyPressHandler handler)) { using (CancellationTokenSource source = new CancellationTokenSource()) using (state.ConsoleManager.AddBreakHandler(() => source.Cancel())) { if (presses != null) { FlushInput(state, ref presses); } await handler(keyPress, state, source.Token).ConfigureAwait(false); } } else if (!string.IsNullOrEmpty(_ttyState) && keyPress.Modifiers == ConsoleModifiers.Control) { if (presses != null) { FlushInput(state, ref presses); } //TODO: Verify on a mac whether these are still needed if (keyPress.Key == ConsoleKey.A) { state.ConsoleManager.MoveCaret(-CaretPosition); CaretPosition = 0; } else if (keyPress.Key == ConsoleKey.E) { state.ConsoleManager.MoveCaret(_inputBuffer.Count - CaretPosition); CaretPosition = _inputBuffer.Count; } } //TODO: Register these like regular commands else if (!string.IsNullOrEmpty(_ttyState) && keyPress.Modifiers == ConsoleModifiers.Alt) { if (presses != null) { FlushInput(state, ref presses); } //Move back a word if (keyPress.Key == ConsoleKey.B) { int i = CaretPosition - 1; if (i < 0) { continue; } bool letterMode = char.IsLetterOrDigit(_inputBuffer[i]); for (; i > 0 && (char.IsLetterOrDigit(_inputBuffer[i]) == letterMode); --i) { } if (letterMode && i > 0) { ++i; } if (i > -1) { state.ConsoleManager.MoveCaret(i - CaretPosition); CaretPosition = i; } } //Move forward a word else if (keyPress.Key == ConsoleKey.F) { int i = CaretPosition + 1; if (i >= _inputBuffer.Count) { continue; } bool letterMode = char.IsLetterOrDigit(_inputBuffer[i]); for (; i < _inputBuffer.Count && (char.IsLetterOrDigit(_inputBuffer[i]) == letterMode); ++i) { } if (letterMode && i < _inputBuffer.Count - 1 && i > 0) { --i; } state.ConsoleManager.MoveCaret(i - CaretPosition); CaretPosition = i; } } else { // If we got here, we've processed all handlers we know for // key combinations. So anything else should have a valid // character or be the null character. If its the latter, // we just want to ignore it. if (keyPress.KeyChar == '\0') { continue; } if (state.ConsoleManager.IsKeyAvailable) { if (presses == null) { presses = new List(); } presses.Add(keyPress); continue; } if (presses != null) { presses.Add(keyPress); FlushInput(state, ref presses); continue; } if (CaretPosition == _inputBuffer.Count) { _inputBuffer.Add(keyPress.KeyChar); state.ConsoleManager.Write(keyPress.KeyChar); MoveCaret(1); } else if (IsOverwriteMode) { _inputBuffer[CaretPosition] = keyPress.KeyChar; state.ConsoleManager.Write(keyPress.KeyChar); MoveCaret(1); } else { state.ConsoleManager.IsCaretVisible = false; _inputBuffer.Insert(CaretPosition, keyPress.KeyChar); string s = new string(_inputBuffer.ToArray(), CaretPosition, _inputBuffer.Count - CaretPosition); state.ConsoleManager.Write(s); // Since we're "inserting", move the console cursor back by one fewer // than the length of the string just written to the console state.ConsoleManager.MoveCaret(-1 * (s.Length - 1)); state.ConsoleManager.IsCaretVisible = true; MoveCaret(1); } } } } finally { RestoreTtyState(); } } private void FlushInput(IShellState state, ref List presses) { string str = new string(presses.Select(x => x.KeyChar).ToArray()); if (CaretPosition == _inputBuffer.Count) { _inputBuffer.AddRange(str); state.ConsoleManager.Write(str); } else if (IsOverwriteMode) { for (int i = 0; i < str.Length; ++i) { if (CaretPosition + i < _inputBuffer.Count) { _inputBuffer[CaretPosition + i] = str[i]; } else { _inputBuffer.AddRange(str.Skip(i)); break; } } state.ConsoleManager.Write(str); } else { state.ConsoleManager.IsCaretVisible = false; _inputBuffer.InsertRange(CaretPosition, str); int currentCaretPosition = CaretPosition; string s = new string(_inputBuffer.ToArray(), CaretPosition, _inputBuffer.Count - CaretPosition); state.ConsoleManager.Write(s); state.ConsoleManager.MoveCaret(currentCaretPosition - CaretPosition + str.Length); state.ConsoleManager.IsCaretVisible = true; } MoveCaret(str.Length); presses = null; } } } ================================================ FILE: src/Microsoft.Repl/Input/KeyHandlers.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Threading; using System.Threading.Tasks; using Microsoft.Repl.Parsing; namespace Microsoft.Repl.Input { public static class KeyHandlers { public static void RegisterDefaultKeyHandlers(IInputManager inputManager) { inputManager = inputManager ?? throw new ArgumentNullException(nameof(inputManager)); //Navigation in line inputManager.RegisterKeyHandler(ConsoleKey.LeftArrow, LeftArrow); inputManager.RegisterKeyHandler(ConsoleKey.LeftArrow, ConsoleModifiers.Control, LeftArrow); inputManager.RegisterKeyHandler(ConsoleKey.RightArrow, RightArrow); inputManager.RegisterKeyHandler(ConsoleKey.RightArrow, ConsoleModifiers.Control, RightArrow); inputManager.RegisterKeyHandler(ConsoleKey.Home, Home); inputManager.RegisterKeyHandler(ConsoleKey.A, ConsoleModifiers.Control, Home); inputManager.RegisterKeyHandler(ConsoleKey.End, End); inputManager.RegisterKeyHandler(ConsoleKey.E, ConsoleModifiers.Control, End); //Command history inputManager.RegisterKeyHandler(ConsoleKey.UpArrow, UpArrow); inputManager.RegisterKeyHandler(ConsoleKey.DownArrow, DownArrow); //Completion inputManager.RegisterKeyHandler(ConsoleKey.Tab, Tab); inputManager.RegisterKeyHandler(ConsoleKey.Tab, ConsoleModifiers.Shift, Tab); //Input manipulation inputManager.RegisterKeyHandler(ConsoleKey.Escape, Escape); inputManager.RegisterKeyHandler(ConsoleKey.U, ConsoleModifiers.Control, Escape); inputManager.RegisterKeyHandler(ConsoleKey.Delete, Delete); inputManager.RegisterKeyHandler(ConsoleKey.Backspace, Backspace); //Insert/Overwrite mode inputManager.RegisterKeyHandler(ConsoleKey.Insert, Insert); //Execute command inputManager.RegisterKeyHandler(ConsoleKey.Enter, Enter); //Map non-printable keys that aren't handled by default inputManager.RegisterKeyHandler(ConsoleKey.F1, Unhandled); inputManager.RegisterKeyHandler(ConsoleKey.F2, Unhandled); inputManager.RegisterKeyHandler(ConsoleKey.F3, Unhandled); inputManager.RegisterKeyHandler(ConsoleKey.F4, Unhandled); inputManager.RegisterKeyHandler(ConsoleKey.F5, Unhandled); inputManager.RegisterKeyHandler(ConsoleKey.F6, Unhandled); inputManager.RegisterKeyHandler(ConsoleKey.F7, Unhandled); inputManager.RegisterKeyHandler(ConsoleKey.F8, Unhandled); inputManager.RegisterKeyHandler(ConsoleKey.F9, Unhandled); inputManager.RegisterKeyHandler(ConsoleKey.F10, Unhandled); inputManager.RegisterKeyHandler(ConsoleKey.F11, Unhandled); inputManager.RegisterKeyHandler(ConsoleKey.F12, Unhandled); inputManager.RegisterKeyHandler(ConsoleKey.F13, Unhandled); inputManager.RegisterKeyHandler(ConsoleKey.F14, Unhandled); inputManager.RegisterKeyHandler(ConsoleKey.F15, Unhandled); inputManager.RegisterKeyHandler(ConsoleKey.F16, Unhandled); inputManager.RegisterKeyHandler(ConsoleKey.F17, Unhandled); inputManager.RegisterKeyHandler(ConsoleKey.F18, Unhandled); inputManager.RegisterKeyHandler(ConsoleKey.F19, Unhandled); inputManager.RegisterKeyHandler(ConsoleKey.F20, Unhandled); inputManager.RegisterKeyHandler(ConsoleKey.F21, Unhandled); inputManager.RegisterKeyHandler(ConsoleKey.F22, Unhandled); inputManager.RegisterKeyHandler(ConsoleKey.F23, Unhandled); inputManager.RegisterKeyHandler(ConsoleKey.F24, Unhandled); inputManager.RegisterKeyHandler(ConsoleKey.Clear, Unhandled); inputManager.RegisterKeyHandler(ConsoleKey.Execute, Unhandled); inputManager.RegisterKeyHandler(ConsoleKey.Help, Unhandled); inputManager.RegisterKeyHandler(ConsoleKey.PageDown, Unhandled); inputManager.RegisterKeyHandler(ConsoleKey.PageUp, Unhandled); inputManager.RegisterKeyHandler(ConsoleKey.Pause, Unhandled); inputManager.RegisterKeyHandler(ConsoleKey.Print, Unhandled); inputManager.RegisterKeyHandler(ConsoleKey.PrintScreen, Unhandled); inputManager.RegisterKeyHandler(ConsoleKey.Select, Unhandled); inputManager.RegisterKeyHandler(ConsoleKey.Separator, Unhandled); inputManager.RegisterKeyHandler(ConsoleKey.Sleep, Unhandled); inputManager.RegisterKeyHandler(ConsoleKey.LeftWindows, Unhandled); inputManager.RegisterKeyHandler(ConsoleKey.RightWindows, Unhandled); inputManager.RegisterKeyHandler(ConsoleKey.Applications, Unhandled); } private static Task End(ConsoleKeyInfo keyInfo, IShellState state, CancellationToken cancellationToken) { state.MoveCarets(state.InputManager.GetCurrentBuffer().Length - state.InputManager.CaretPosition); return Task.CompletedTask; } public static Task Home(ConsoleKeyInfo keyInfo, IShellState state, CancellationToken cancellationToken) { state = state ?? throw new ArgumentNullException(nameof(state)); state.MoveCarets(-state.InputManager.CaretPosition); return Task.CompletedTask; } public static Task LeftArrow(ConsoleKeyInfo keyInfo, IShellState state, CancellationToken cancellationToken) { state = state ?? throw new ArgumentNullException(nameof(state)); if (state.InputManager.CaretPosition > 0) { if (!keyInfo.Modifiers.HasFlag(ConsoleModifiers.Control)) { state.MoveCarets(-1); } else { string line = state.InputManager.GetCurrentBuffer(); ICoreParseResult parseResult = state.CommandDispatcher.Parser.Parse(line, state.InputManager.CaretPosition); int targetSection = parseResult.SelectedSection - (parseResult.CaretPositionWithinSelectedSection > 0 ? 0 : 1); if (targetSection < 0) { targetSection = 0; } int desiredPosition = parseResult.SectionStartLookup[targetSection]; state.MoveCarets(desiredPosition - state.InputManager.CaretPosition); } } return Task.CompletedTask; } public static Task RightArrow(ConsoleKeyInfo keyInfo, IShellState state, CancellationToken cancellationToken) { state = state ?? throw new ArgumentNullException(nameof(state)); string line = state.InputManager.GetCurrentBuffer(); if (state.InputManager.CaretPosition < line.Length) { if (!keyInfo.Modifiers.HasFlag(ConsoleModifiers.Control)) { state.MoveCarets(1); } else { ICoreParseResult parseResult = state.CommandDispatcher.Parser.Parse(line, state.InputManager.CaretPosition); int targetSection = parseResult.SelectedSection + 1; if (targetSection >= parseResult.Sections.Count) { state.MoveCarets(line.Length - state.InputManager.CaretPosition); } else { int desiredPosition = parseResult.SectionStartLookup[targetSection]; state.MoveCarets(desiredPosition - state.InputManager.CaretPosition); } } } return Task.CompletedTask; } public static Task UpArrow(ConsoleKeyInfo keyInfo, IShellState state, CancellationToken cancellationToken) { state = state ?? throw new ArgumentNullException(nameof(state)); string line = state.CommandHistory.GetPreviousCommand(); state.InputManager.SetInput(state, line); return Task.CompletedTask; } public static Task DownArrow(ConsoleKeyInfo keyInfo, IShellState state, CancellationToken cancellationToken) { state = state ?? throw new ArgumentNullException(nameof(state)); string line = state.CommandHistory.GetNextCommand(); state.InputManager.SetInput(state, line); return Task.CompletedTask; } public static Task Enter(ConsoleKeyInfo keyInfo, IShellState state, CancellationToken cancellationToken) { state = state ?? throw new ArgumentNullException(nameof(state)); return state.CommandDispatcher.ExecuteCommandAsync(state, cancellationToken); } public static Task Backspace(ConsoleKeyInfo keyInfo, IShellState state, CancellationToken cancellationToken) { state = state ?? throw new ArgumentNullException(nameof(state)); state.InputManager.RemovePreviousCharacter(state); return Task.CompletedTask; } public static Task Unhandled(ConsoleKeyInfo keyInfo, IShellState state, CancellationToken cancellationToken) { return Task.CompletedTask; } public static Task Escape(ConsoleKeyInfo keyInfo, IShellState state, CancellationToken cancellationToken) { state = state ?? throw new ArgumentNullException(nameof(state)); state.InputManager.SetInput(state, string.Empty); return Task.CompletedTask; } public static Task Tab(ConsoleKeyInfo keyInfo, IShellState state, CancellationToken cancellationToken) { state = state ?? throw new ArgumentNullException(nameof(state)); if (keyInfo.Modifiers.HasFlag(ConsoleModifiers.Shift)) { state.SuggestionManager.PreviousSuggestion(state); } else { state.SuggestionManager.NextSuggestion(state); } return Task.CompletedTask; } public static Task Delete(ConsoleKeyInfo keyInfo, IShellState state, CancellationToken cancellationToken) { state = state ?? throw new ArgumentNullException(nameof(state)); state.InputManager.RemoveCurrentCharacter(state); return Task.CompletedTask; } public static Task Insert(ConsoleKeyInfo keyInfo, IShellState state, CancellationToken cancellationToken) { state = state ?? throw new ArgumentNullException(nameof(state)); state.InputManager.IsOverwriteMode = !state.InputManager.IsOverwriteMode; return Task.CompletedTask; } } } ================================================ FILE: src/Microsoft.Repl/Microsoft.Repl.csproj ================================================ net8.0 A framework for creating REPLs in .NET Standard. dotnet;repl Microsoft.Repl true false True True Strings.resx ResXFileCodeGenerator Strings.Designer.cs ================================================ FILE: src/Microsoft.Repl/Parsing/CoreParseResult.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; namespace Microsoft.Repl.Parsing { public class CoreParseResult : ICoreParseResult { public CoreParseResult(int caretPositionWithinCommandText, int caretPositionWithinSelectedSection, string commandText, IReadOnlyList sections, int selectedSection, IReadOnlyDictionary sectionStartLookup, HashSet quotedSections) { CaretPositionWithinCommandText = caretPositionWithinCommandText; CaretPositionWithinSelectedSection = caretPositionWithinSelectedSection; CommandText = commandText; Sections = sections; SelectedSection = selectedSection; SectionStartLookup = sectionStartLookup; _quotedSections = quotedSections; } public int CaretPositionWithinCommandText { get; } public int CaretPositionWithinSelectedSection { get; } public string CommandText { get; } public IReadOnlyList Sections { get; } public int SelectedSection { get; } public IReadOnlyDictionary SectionStartLookup { get; } private readonly HashSet _quotedSections; public bool IsQuotedSection(int index) { return _quotedSections.Contains(index); } public virtual ICoreParseResult Slice(int numberOfLeadingSectionsToRemove) { if (numberOfLeadingSectionsToRemove == 0) { return this; } if (numberOfLeadingSectionsToRemove >= Sections.Count) { return new CoreParseResult(0, 0, string.Empty, new[] { string.Empty }, 0, new Dictionary { { 0, 0 } }, new HashSet()); } string commandText = CommandText.Substring(SectionStartLookup[numberOfLeadingSectionsToRemove]); int caretPositionWithinCommandText = CaretPositionWithinCommandText - SectionStartLookup[numberOfLeadingSectionsToRemove]; if (caretPositionWithinCommandText < 0) { caretPositionWithinCommandText = 0; } Dictionary sectionStartLookup = new Dictionary(); List sections = new List(); for (int i = 0; i < Sections.Count - numberOfLeadingSectionsToRemove; ++i) { sectionStartLookup[i] = SectionStartLookup[numberOfLeadingSectionsToRemove + i] - SectionStartLookup[numberOfLeadingSectionsToRemove]; sections.Add(Sections[numberOfLeadingSectionsToRemove + i]); } int selectedSection = SelectedSection - numberOfLeadingSectionsToRemove; if (selectedSection < 0) { selectedSection = 0; } HashSet quotedSections = new HashSet(_quotedSections.Where(x => x > 0).Select(x => x - 1)); return new CoreParseResult(caretPositionWithinCommandText, CaretPositionWithinSelectedSection, commandText, sections, selectedSection, sectionStartLookup, quotedSections); } } } ================================================ FILE: src/Microsoft.Repl/Parsing/CoreParseResultExtensions.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; namespace Microsoft.Repl.Parsing { public static class CoreParseResultExtensions { public static bool ContainsExactly(this ICoreParseResult parseResult, int length, StringComparison stringComparison, params string[] sections) { parseResult = parseResult ?? throw new ArgumentNullException(nameof(parseResult)); if (parseResult.Sections.Count != length || parseResult.Sections.Count < sections.Length) { return false; } return CompareSections(parseResult.Sections, sections, stringComparison); } public static bool ContainsExactly(this ICoreParseResult parseResult, int length, params string[] sections) { return ContainsExactly(parseResult, length, StringComparison.OrdinalIgnoreCase, sections); } public static bool ContainsExactly(this ICoreParseResult parseResult, params string[] sections) { return ContainsExactly(parseResult, sections.Length, sections); } public static bool ContainsAtLeast(this ICoreParseResult parseResult, int minimumLength, StringComparison stringComparison, params string[] sections) { parseResult = parseResult ?? throw new ArgumentNullException(nameof(parseResult)); if (parseResult.Sections.Count < minimumLength || parseResult.Sections.Count < sections.Length) { return false; } return CompareSections(parseResult.Sections, sections, stringComparison); } public static bool ContainsAtLeast(this ICoreParseResult parseResult, int minimumLength, params string[] sections) { return ContainsAtLeast(parseResult, minimumLength, StringComparison.OrdinalIgnoreCase, sections); } public static bool ContainsAtLeast(this ICoreParseResult parseResult, params string[] sections) { return ContainsAtLeast(parseResult, minimumLength: sections.Length, sections); } private static bool CompareSections(IReadOnlyList parseSections, string[] sections, StringComparison stringComparison) { for (int index = 0; index < sections.Length; index++) { if (!string.Equals(parseSections[index], sections[index], stringComparison)) { return false; } } return true; } } } ================================================ FILE: src/Microsoft.Repl/Parsing/CoreParser.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; namespace Microsoft.Repl.Parsing { public class CoreParser : IParser { public ICoreParseResult Parse(string commandText, int caretPosition) { commandText = commandText ?? throw new ArgumentNullException(nameof(commandText)); List sections = commandText.Split(' ').ToList(); // We can't use StringSplitOptions.RemoveEmptyEntries because it // is more aggressive than we need, so we need to do it ourselves. RemoveEmptyEntries(sections); Dictionary sectionStartLookup = new Dictionary(); HashSet quotedSections = new HashSet(); int runningIndex = 0; int selectedSection = -1; int caretPositionWithinSelectedSection = 0; bool isInQuotedSection = false; for (int i = 0; i < sections.Count; ++i) { int thisSectionLength = sections[i].Length; bool isLastSection = i == sections.Count - 1; //If currently in a quoted section, combine with the previous section, check to see if this section closes the quotes if (isInQuotedSection) { //Combine with the previous section sections[i - 1] += " " + sections[i]; sections.RemoveAt(i--); //Check for the closing quote int sectionLength = sections[i].Length; bool justOneCharacter = sectionLength == 1; bool endsWithDoubleQuote = sections[i][sectionLength - 1] == '"'; bool lastCharacterIsEscaped = !justOneCharacter && sections[i][sectionLength - 2] == '\\'; if (endsWithDoubleQuote && !lastCharacterIsEscaped) { isInQuotedSection = false; } } //Not in a quoted section, check to see if we're starting one (and not finishing it at the same time) else { sectionStartLookup[i] = runningIndex; if (thisSectionLength > 0) { bool startsWithDoubleQuote = sections[i][0] == '"'; bool justOneCharacter = thisSectionLength == 1; bool endsWithDoubleQuote = sections[i][thisSectionLength - 1] == '"'; bool lastCharacterIsEscaped = !justOneCharacter && sections[i][thisSectionLength - 2] == '\\'; if (startsWithDoubleQuote && (!endsWithDoubleQuote || lastCharacterIsEscaped)) { isInQuotedSection = true; } } } //Update the running index, adding one for all but the last element to account for the spaces between the sections runningIndex += thisSectionLength + (isLastSection ? 0 : 1); //If the selected section hasn't been determined yet, and the end of the text is past the caret, set the selected // section to the current section and set the initial value for the caret position within the selected section. // Note that the caret position within the selected section, unlike the other positions, accounts for escape // sequences and must be fixed up when escape sequences are removed if (selectedSection == -1 && runningIndex > caretPosition) { selectedSection = i; caretPositionWithinSelectedSection = caretPosition - sectionStartLookup[i]; } } //Unescape the sections // Note that this isn't combined with the above loop to avoid additional complexity in the quoted section case for (int i = 0; i < sections.Count; ++i) { string s = sections[i]; //Trim quotes if needed if (s.Length > 1) { if (s[0] == s[s.Length - 1] && s[0] == '"') { s = s.Substring(1, s.Length - 2); quotedSections.Add(i); //Fix up the caret position in the text if (selectedSection == i) { //If the caret was on the closing quote, back up to the last character of the section if (caretPositionWithinSelectedSection == s.Length - 1) { caretPositionWithinSelectedSection -= 2; } //If the caret was after the opening quote, back up one else if (caretPositionWithinSelectedSection > 0) { --caretPositionWithinSelectedSection; } } } } for (int j = 0; j < s.Length - 1; ++j) { if (s[j] == '\\') { if (s[j + 1] == '\\' || s[j + 1] == '"') { s = s.Substring(0, j) + s.Substring(j + 1); //If we're changing the selected section, and we're removing a character // from before the caret position, back the caret position up to account for it if (selectedSection == i && j < caretPositionWithinSelectedSection) { --caretPositionWithinSelectedSection; } } } } sections[i] = s; } if (selectedSection == -1) { selectedSection = sections.Count - 1; caretPositionWithinSelectedSection = sections[selectedSection].Length; } return new CoreParseResult(caretPosition, caretPositionWithinSelectedSection, commandText, sections, selectedSection, sectionStartLookup, quotedSections); } private static void RemoveEmptyEntries(List sections) { if (sections.Count < 2) { return; } // We want to remove empty spaces from the beginning, and from the middle // but not from the end. for (int index = 0; index < sections.Count - 1; index++) { if (sections[index].Length == 0) { sections.RemoveAt(index); } } } } } ================================================ FILE: src/Microsoft.Repl/Parsing/ICoreParseResult.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Collections.Generic; namespace Microsoft.Repl.Parsing { public interface ICoreParseResult { int CaretPositionWithinCommandText { get; } int CaretPositionWithinSelectedSection { get; } string CommandText { get; } IReadOnlyList Sections { get; } bool IsQuotedSection(int index); int SelectedSection { get; } IReadOnlyDictionary SectionStartLookup { get; } ICoreParseResult Slice(int numberOfLeadingSectionsToRemove); } } ================================================ FILE: src/Microsoft.Repl/Parsing/IParser.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. namespace Microsoft.Repl.Parsing { public interface IParser { ICoreParseResult Parse(string commandText, int caretPosition); } public interface IParser : IParser { new TParseResult Parse(string commandText, int caretPosition); } } ================================================ FILE: src/Microsoft.Repl/Properties/AssemblyInfo.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Microsoft.Repl.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "This is done commonly throughout the codebase to catch unexpected errors.")] ================================================ FILE: src/Microsoft.Repl/Resources/Strings.Designer.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Microsoft.Repl.Resources { using System; /// /// A strongly-typed resource class, for looking up localized strings, etc. /// // This class was auto-generated by the StronglyTypedResourceBuilder // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Strings { private static global::System.Resources.ResourceManager resourceMan; private static global::System.Globalization.CultureInfo resourceCulture; [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal Strings() { } /// /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Repl.Resources.Strings", typeof(Strings).Assembly); resourceMan = temp; } return resourceMan; } } /// /// Overrides the current thread's CurrentUICulture property for all /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } set { resourceCulture = value; } } /// /// Looks up a localized string similar to Command execution cancelled. /// internal static string DefaultCommandDispatcher_Error_ExecutionWasCancelled { get { return ResourceManager.GetString("DefaultCommandDispatcher_Error_ExecutionWasCancelled", resourceCulture); } } /// /// Looks up a localized string similar to No matching command found. /// internal static string DefaultCommandDispatcher_Error_NoMatchingCommand { get { return ResourceManager.GetString("DefaultCommandDispatcher_Error_NoMatchingCommand", resourceCulture); } } /// /// Looks up a localized string similar to Execute 'help' to see available commands. /// internal static string DefaultCommandDispatcher_Error_SeeHelp { get { return ResourceManager.GetString("DefaultCommandDispatcher_Error_SeeHelp", resourceCulture); } } } } ================================================ FILE: src/Microsoft.Repl/Resources/Strings.resx ================================================ text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Command execution cancelled No matching command found Execute 'help' to see available commands ================================================ FILE: src/Microsoft.Repl/Scripting/IScriptExecutor.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; namespace Microsoft.Repl.Scripting { public interface IScriptExecutor { Task ExecuteScriptAsync(IShellState shellState, IEnumerable commandTexts, CancellationToken cancellationToken); } } ================================================ FILE: src/Microsoft.Repl/Scripting/ScriptExecutor.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.Repl.Commanding; using Microsoft.Repl.Parsing; namespace Microsoft.Repl.Scripting { public class ScriptExecutor : IScriptExecutor where TParseResult : ICoreParseResult { private readonly bool _hideScriptLinesFromHistory; public ScriptExecutor(bool hideScriptLinesFromHistory = true) { _hideScriptLinesFromHistory = hideScriptLinesFromHistory; } public async Task ExecuteScriptAsync(IShellState shellState, IEnumerable commandTexts, CancellationToken cancellationToken) { shellState = shellState ?? throw new ArgumentNullException(nameof(shellState)); commandTexts = commandTexts ?? throw new ArgumentNullException(nameof(commandTexts)); if (shellState.CommandDispatcher is ICommandDispatcher dispatcher) { IDisposable suppressor = _hideScriptLinesFromHistory ? shellState.CommandHistory.SuspendHistory() : null; using (suppressor) { foreach (string commandText in commandTexts) { if (string.IsNullOrWhiteSpace(commandText)) { continue; } if (cancellationToken.IsCancellationRequested) { break; } dispatcher.OnReady(shellState); shellState.InputManager.SetInput(shellState, commandText); await dispatcher.ExecuteCommandAsync(shellState, cancellationToken).ConfigureAwait(false); } } } } } } ================================================ FILE: src/Microsoft.Repl/Shell.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Threading; using System.Threading.Tasks; using Microsoft.Repl.Commanding; using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Input; using Microsoft.Repl.Suggestions; namespace Microsoft.Repl { public class Shell { public Shell(IShellState shellState) { shellState = shellState ?? throw new ArgumentNullException(nameof(shellState)); KeyHandlers.RegisterDefaultKeyHandlers(shellState.InputManager); ShellState = shellState; } public Shell(ICommandDispatcher dispatcher, ISuggestionManager suggestionManager = null, IConsoleManager consoleManager = null) : this(new ShellState(dispatcher, suggestionManager, consoleManager: consoleManager)) { } public IShellState ShellState { get; } public Task RunAsync(CancellationToken cancellationToken) { ShellState.CommandDispatcher.OnReady(ShellState); return ShellState.InputManager.StartAsync(ShellState, cancellationToken); } } } ================================================ FILE: src/Microsoft.Repl/ShellState.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using Microsoft.Repl.Commanding; using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Input; using Microsoft.Repl.Suggestions; namespace Microsoft.Repl { public class ShellState : IShellState { public ShellState(ICommandDispatcher commandDispatcher, ISuggestionManager suggestionManager = null, IInputManager inputManager = null, ICommandHistory commandHistory = null, IConsoleManager consoleManager = null) { InputManager = inputManager ?? new InputManager(); CommandHistory = commandHistory ?? new CommandHistory(); ConsoleManager = consoleManager ?? new ConsoleManager(); CommandDispatcher = commandDispatcher; SuggestionManager = suggestionManager ?? new SuggestionManager(); } public IInputManager InputManager { get; } public ICommandHistory CommandHistory { get; } public IConsoleManager ConsoleManager { get; } public ICommandDispatcher CommandDispatcher { get; } public bool IsExiting { get; set; } public ISuggestionManager SuggestionManager { get; } public void MoveCarets(int positions) { ConsoleManager.MoveCaret(positions); InputManager.MoveCaret(positions); } } } ================================================ FILE: src/Microsoft.Repl/Suggestions/FileSystemCompletion.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.IO; using System.Linq; namespace Microsoft.Repl.Suggestions { public static class FileSystemCompletion { public static IEnumerable GetCompletions(string prefix) { prefix = prefix ?? throw new ArgumentNullException(nameof(prefix)); if (prefix.StartsWith("\"", StringComparison.Ordinal)) { prefix = prefix.Substring(1); int lastQuote = prefix.LastIndexOf('\"'); if (lastQuote > -1) { prefix = prefix.Remove(lastQuote, 1); } while (prefix.EndsWith($"{Path.DirectorySeparatorChar}{Path.DirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase)) { prefix = prefix.Substring(0, prefix.Length - 1); } } int lastPathIndex = prefix.LastIndexOfAny(new[] { '\\', '/' }); if (lastPathIndex < 0) { return null; } string dir = prefix.Substring(0, lastPathIndex + 1); if (dir.IndexOfAny(Path.GetInvalidPathChars()) > -1) { return null; } string partPrefix = prefix.Substring(lastPathIndex + 1); if (Directory.Exists(dir)) { return Directory.EnumerateDirectories(dir).Where(x => Path.GetFileName(x).StartsWith(partPrefix, StringComparison.OrdinalIgnoreCase)) .Union(Directory.EnumerateFiles(dir).Where(x => Path.GetFileName(x).StartsWith(partPrefix, StringComparison.OrdinalIgnoreCase))).Select(x => x.IndexOf(' ', StringComparison.Ordinal) > -1 ? $"\"{x}\"" : x); } return null; } } } ================================================ FILE: src/Microsoft.Repl/Suggestions/ISuggestionManager.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. namespace Microsoft.Repl.Suggestions { public interface ISuggestionManager { void NextSuggestion(IShellState shellState); void PreviousSuggestion(IShellState shellState); } } ================================================ FILE: src/Microsoft.Repl/Suggestions/SuggestionManager.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using Microsoft.Repl.Parsing; namespace Microsoft.Repl.Suggestions { public class SuggestionManager : ISuggestionManager { private int _currentSuggestion; private IReadOnlyList _suggestions; private ICoreParseResult _expected; public void NextSuggestion(IShellState shellState) { shellState = shellState ?? throw new ArgumentNullException(nameof(shellState)); string line = shellState.InputManager.GetCurrentBuffer(); ICoreParseResult parseResult = shellState.CommandDispatcher.Parser.Parse(line, shellState.InputManager.CaretPosition); string currentSuggestion; //Check to see if we're continuing before querying for suggestions again if (_expected != null && string.Equals(_expected.CommandText, parseResult.CommandText, StringComparison.Ordinal) && _expected.SelectedSection == parseResult.SelectedSection && _expected.CaretPositionWithinSelectedSection == parseResult.CaretPositionWithinSelectedSection) { if (_suggestions == null || _suggestions.Count == 0) { return; } _currentSuggestion = (_currentSuggestion + 1) % _suggestions.Count; currentSuggestion = _suggestions[_currentSuggestion]; } else { _currentSuggestion = 0; _suggestions = shellState.CommandDispatcher.CollectSuggestions(shellState); if (_suggestions == null || _suggestions.Count == 0) { return; } currentSuggestion = _suggestions[0]; } //We now have a suggestion, take the command text leading up to the section being suggested for, // concatenate that and the suggestion text, turn it in to keys, submit it to the input manager, // reset the caret, store the parse result of the new text as what's expected for a continuation string newText = parseResult.CommandText.Substring(0, parseResult.SectionStartLookup[parseResult.SelectedSection]) + currentSuggestion; _expected = shellState.CommandDispatcher.Parser.Parse(newText, newText.Length); shellState.InputManager.SetInput(shellState, newText); } public void PreviousSuggestion(IShellState shellState) { shellState = shellState ?? throw new ArgumentNullException(nameof(shellState)); string line = shellState.InputManager.GetCurrentBuffer(); ICoreParseResult parseResult = shellState.CommandDispatcher.Parser.Parse(line, shellState.InputManager.CaretPosition); string currentSuggestion; //Check to see if we're continuing before querying for suggestions again if (_expected != null && string.Equals(_expected.CommandText, parseResult.CommandText, StringComparison.Ordinal) && _expected.SelectedSection == parseResult.SelectedSection && _expected.CaretPositionWithinSelectedSection == parseResult.CaretPositionWithinSelectedSection) { if (_suggestions == null || _suggestions.Count == 0) { return; } _currentSuggestion = (_currentSuggestion - 1 + _suggestions.Count) % _suggestions.Count; currentSuggestion = _suggestions[_currentSuggestion]; } else { _suggestions = shellState.CommandDispatcher.CollectSuggestions(shellState); _currentSuggestion = _suggestions.Count - 1; if (_suggestions == null || _suggestions.Count == 0) { return; } currentSuggestion = _suggestions[_suggestions.Count - 1]; } //We now have a suggestion, take the command text leading up to the section being suggested for, // concatenate that and the suggestion text, turn it in to keys, submit it to the input manager, // reset the caret, store the parse result of the new text as what's expected for a continuation string newText = parseResult.CommandText.Substring(0, parseResult.SectionStartLookup[parseResult.SelectedSection]) + currentSuggestion; _expected = shellState.CommandDispatcher.Parser.Parse(newText, newText.Length); shellState.InputManager.SetInput(shellState, newText); } } } ================================================ FILE: src/Microsoft.Repl/Utils.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Collections.Generic; namespace Microsoft.Repl { public static class Utils { public static string Stringify(this IReadOnlyList keys) { return string.Join("", keys); } } } ================================================ FILE: test/Directory.Build.props ================================================ net9.0 Major ================================================ FILE: test/Microsoft.HttpRepl.Fakes/ApiDefinitionReaderStub.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.IO; using Microsoft.HttpRepl.OpenApi; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace Microsoft.HttpRepl.Fakes { public class ApiDefinitionReaderStub : IApiDefinitionReader { private ApiDefinition _apiDefinition; public ApiDefinitionReaderStub(ApiDefinition apiDefinition) { _apiDefinition = apiDefinition; } public ApiDefinitionParseResult CanHandle(string document) { JObject doc; using (StringReader stringReader = new StringReader(document)) { JsonSerializer serializer = new JsonSerializer(); doc = (JObject)serializer.Deserialize(stringReader, typeof(JObject)); } return (doc["fakeApi"]?.ToString() ?? "").StartsWith("1.", StringComparison.Ordinal) ? new ApiDefinitionParseResult(true, null, null) : ApiDefinitionParseResult.Failed; } public ApiDefinitionParseResult ReadDefinition(string document, Uri sourceUri) { return new ApiDefinitionParseResult(true, _apiDefinition, null); } } } ================================================ FILE: test/Microsoft.HttpRepl.Fakes/FakePreferences.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using Microsoft.HttpRepl.Preferences; using Microsoft.Repl.ConsoleHandling; namespace Microsoft.HttpRepl.Fakes { public sealed class FakePreferences : IPreferences { private readonly Dictionary _currentPreferences; public FakePreferences() { DefaultPreferences = new Dictionary(); _currentPreferences = new(); } public IReadOnlyDictionary DefaultPreferences { get; } public IReadOnlyDictionary CurrentPreferences => _currentPreferences; public bool GetBoolValue(string preference, bool defaultValue = false) { if (CurrentPreferences.TryGetValue(preference, out string value) && bool.TryParse(value, out bool result)) { return result; } return defaultValue; } public AllowedColors GetColorValue(string preference, AllowedColors defaultValue = AllowedColors.None) { if (CurrentPreferences.TryGetValue(preference, out string value) && Enum.TryParse(value, true, out AllowedColors result)) { return result; } return defaultValue; } public int GetIntValue(string preference, int defaultValue = 0) { if (CurrentPreferences.TryGetValue(preference, out string value) && int.TryParse(value, out int result)) { return result; } return defaultValue; } public string GetValue(string preference, string defaultValue = null) { if (CurrentPreferences.TryGetValue(preference, out string value)) { return value; } return defaultValue; } public bool SetValue(string preference, string value) { _currentPreferences[preference] = value; return true; } public bool TryGetValue(string preference, out string value) => CurrentPreferences.TryGetValue(preference, out value); } } ================================================ FILE: test/Microsoft.HttpRepl.Fakes/FileSystemStub.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Collections.Generic; using Microsoft.HttpRepl.FileSystem; namespace Microsoft.HttpRepl.Fakes { public class FileSystemStub : IFileSystem { public void DeleteFile(string path) { } public bool FileExists(string path) { return default; } public string GetTempFileName(string fileExtension) { return default; } public byte[] ReadAllBytesFromFile(string path) { return default; } public string[] ReadAllLinesFromFile(string path) { return default; } public void WriteAllLinesToFile(string path, IEnumerable contents) { } public void WriteAllTextToFile(string path, string contents) { } } } ================================================ FILE: test/Microsoft.HttpRepl.Fakes/LoggingConsoleManagerDecorator.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Text; using System.Threading; using Microsoft.Repl.ConsoleHandling; namespace Microsoft.HttpRepl.Fakes { public class LoggingConsoleManagerDecorator : IConsoleManager { private readonly IConsoleManager _baseConsole; private readonly StringBuilder _log; public LoggingConsoleManagerDecorator(IConsoleManager console) { _baseConsole = console; _log = new StringBuilder(); } public string LoggedOutput => _log.ToString(); public bool WasClearCalled { get; private set; } #region IConsoleManager public Point Caret => _baseConsole.Caret; public IWritable Error => _baseConsole.Error; public bool IsKeyAvailable => _baseConsole.IsKeyAvailable; public bool IsCaretVisible { get => _baseConsole.IsCaretVisible; set => _baseConsole.IsCaretVisible = value; } public bool AllowOutputRedirection => _baseConsole.AllowOutputRedirection; public IDisposable AddBreakHandler(Action onBreak) { return _baseConsole.AddBreakHandler(onBreak); } public void Clear() { _baseConsole.Clear(); WasClearCalled = true; } public void MoveCaret(int positions) { _baseConsole.MoveCaret(positions); } public ConsoleKeyInfo ReadKey(CancellationToken cancellationToken) { return _baseConsole.ReadKey(cancellationToken); } public void Write(char c) { _log.Append(c); _baseConsole.Write(c); } public void Write(string s) { _log.Append(s); _baseConsole.Write(s); } public void WriteLine() { _log.AppendLine(); _baseConsole.WriteLine(); } public void WriteLine(string s) { _log.AppendLine(s); _baseConsole.WriteLine(s); } #endregion } } ================================================ FILE: test/Microsoft.HttpRepl.Fakes/Microsoft.HttpRepl.Fakes.csproj ================================================ $(TestProjectTargetFramework) false ================================================ FILE: test/Microsoft.HttpRepl.Fakes/MockCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.Repl; using Microsoft.Repl.Commanding; using Microsoft.Repl.Parsing; namespace Microsoft.HttpRepl.Fakes { public class MockCommand : ICommand { public string Name { get; } public MockCommand(string commandName) { Name = commandName; } public bool? CanHandle(IShellState shellState, object programState, ICoreParseResult parseResult) { return (bool?)true; } public Task ExecuteAsync(IShellState shellState, object programState, ICoreParseResult parseResult, CancellationToken cancellationToken) { return Task.CompletedTask; } public string GetHelpDetails(IShellState shellState, object programState, ICoreParseResult parseResult) { return null; } public string GetHelpSummary(IShellState shellState, object programState) { return null; } public IEnumerable Suggest(IShellState shellState, object programState, ICoreParseResult parseResult) { return new[] { Name }; } } } ================================================ FILE: test/Microsoft.HttpRepl.Fakes/MockConsoleManager.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Text; using System.Threading; using Microsoft.Repl.ConsoleHandling; namespace Microsoft.HttpRepl.Fakes { public class MockConsoleManager : IConsoleManager { private CancellationTokenSource _cancellationTokenSource; private List _consoleKeyInfo; private int _nextKeyIndex; private StringBuilder _outputTracking = new StringBuilder(); public MockConsoleManager(IEnumerable consoleKeyInfo, CancellationTokenSource cancellationTokenSource) { _cancellationTokenSource = cancellationTokenSource; _consoleKeyInfo = new List(consoleKeyInfo); } public MockConsoleManager() { _cancellationTokenSource = new CancellationTokenSource(); } public string Output => _outputTracking.ToString(); public Point Caret => throw new NotImplementedException(); public Point CommandStart => throw new NotImplementedException(); public int CaretPosition { get; set; } public IWritable Error => new MockWritable(); public bool IsKeyAvailable => _nextKeyIndex < _consoleKeyInfo.Count; public void Clear() { } public void MoveCaret(int offset) { CaretPosition += offset; } public ConsoleKeyInfo ReadKey(CancellationToken cancellationToken) { ConsoleKeyInfo currentKeyInfo = _nextKeyIndex < _consoleKeyInfo.Count ? _consoleKeyInfo[_nextKeyIndex] : new ConsoleKeyInfo(); _nextKeyIndex++; if (_nextKeyIndex >= _consoleKeyInfo.Count) { _cancellationTokenSource.Cancel(); } return currentKeyInfo; } public void ResetCommandStart() { } public IDisposable AddBreakHandler(Action onBreak) { return null; } public void Write(char c) => _outputTracking.Append(c); public void Write(string s) => _outputTracking.Append(s); public void WriteLine() => _outputTracking.AppendLine(); public void WriteLine(string s) => _outputTracking.AppendLine(s); public bool IsCaretVisible { get => true; } bool IWritable.IsCaretVisible { get => true; set => value = true; } public bool AllowOutputRedirection => true; } } ================================================ FILE: test/Microsoft.HttpRepl.Fakes/MockHttpContent.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.IO; using System.Net; using System.Net.Http; using System.Text; using System.Threading.Tasks; namespace Microsoft.HttpRepl.Fakes { public class MockHttpContent : HttpContent { public string Content { get; } public MockHttpContent(string content) { Content = content ?? string.Empty; } protected async override Task SerializeToStreamAsync(Stream stream, TransportContext context) { byte[] byteArray = Encoding.ASCII.GetBytes(Content); await stream.WriteAsync(byteArray, 0, byteArray.Length); } protected override bool TryComputeLength(out long length) { length = Content.Length; return true; } } } ================================================ FILE: test/Microsoft.HttpRepl.Fakes/MockHttpMessageHandler.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; namespace Microsoft.HttpRepl.Fakes { public class MockHttpMessageHandler : HttpMessageHandler { private readonly string _contentType; private readonly string _fileContents; private readonly string _header; private readonly bool _readFromFile; private readonly IDictionary _urlsWithResponse; public MockHttpMessageHandler(IDictionary urlsWithResponse, string header, bool readFromFile, string fileContents, string contentType) { _contentType = contentType; _fileContents = fileContents; _header = header; _readFromFile = readFromFile; _urlsWithResponse = urlsWithResponse; } protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { string absoluteUri = request.RequestUri.AbsoluteUri; _urlsWithResponse.TryGetValue(absoluteUri, out string responseContent); HttpResponseMessage httpResponseMessage = new HttpResponseMessage(HttpStatusCode.OK); httpResponseMessage.RequestMessage = request; if (_readFromFile) { httpResponseMessage.Content = new MockHttpContent(_fileContents); } else if (!string.IsNullOrEmpty(_header)) { httpResponseMessage.Headers.Add(_header, responseContent); } else { httpResponseMessage.Content = new MockHttpContent(responseContent); } if (!string.IsNullOrEmpty(_contentType) && httpResponseMessage.Content != null) { httpResponseMessage.Content.Headers.Add("Content-Type", _contentType); } return Task.FromResult(httpResponseMessage); } } } ================================================ FILE: test/Microsoft.HttpRepl.Fakes/MockInputManager.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Threading; using System.Threading.Tasks; using Microsoft.Repl; using Microsoft.Repl.Input; namespace Microsoft.HttpRepl.Fakes { public class MockInputManager : IInputManager { private string _inputBuffer; public int CaretPosition { get; private set; } public MockInputManager(string inputBuffer) { _inputBuffer = inputBuffer; } public void MoveCaret(int positions) { } public bool IsOverwriteMode { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } public IInputManager RegisterKeyHandler(ConsoleKey key, AsyncKeyPressHandler handler) { return new InputManager(); } public IInputManager RegisterKeyHandler(ConsoleKey key, ConsoleModifiers modifiers, AsyncKeyPressHandler handler) { return new InputManager(); } public void ResetInput() { } public Task StartAsync(IShellState state, CancellationToken cancellationToken) { throw new NotImplementedException(); } public void SetInput(IShellState state, string input) { _inputBuffer = input; } public string GetCurrentBuffer() { return _inputBuffer; } public void RemovePreviousCharacter(IShellState state) { } public void RemoveCurrentCharacter(IShellState state) { } public void Clear(IShellState state) { } } } ================================================ FILE: test/Microsoft.HttpRepl.Fakes/MockWritable.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using Microsoft.Repl.ConsoleHandling; namespace Microsoft.HttpRepl.Fakes { internal class MockWritable : IWritable { public void Write(char c) { } public void Write(string s) { } public void WriteLine() { } public void WriteLine(string s) { } public bool IsCaretVisible { get => true; set => value = true; } } } ================================================ FILE: test/Microsoft.HttpRepl.Fakes/MockedFileSystem.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.IO; using System.Security.Cryptography; using System.Text; using Microsoft.HttpRepl.FileSystem; namespace Microsoft.HttpRepl.Fakes { public class MockedFileSystem : IFileSystem { private readonly Dictionary _files = new Dictionary(); public MockedFileSystem AddFile(string path, string contents) { _files[path] = contents; return this; } public string ReadFile(string path) { return _files[path]; } public void DeleteFile(string path) { if (_files.ContainsKey(path)) { _files.Remove(path); } } public bool FileExists(string path) { if (string.IsNullOrWhiteSpace(path)) { return false; } return _files.ContainsKey(path); } public byte[] ReadAllBytesFromFile(string path) { if (path == null) { throw new ArgumentNullException(path); } if (!FileExists(path)) { throw new FileNotFoundException(); } return Encoding.UTF8.GetBytes(_files[path]); } public string[] ReadAllLinesFromFile(string path) { string alltext = _files[path]; return alltext.Split(new string[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); } public void WriteAllLinesToFile(string path, IEnumerable contents) { _files[path] = string.Join(Environment.NewLine, contents); } public void WriteAllTextToFile(string path, string contents) { _files[path] = contents; } public string GetTempFileName(string fileExtension) { string path = GetRandomFileName(); _files[path] = ""; return path; } private string GetRandomFileName() { byte[] bytes = RandomNumberGenerator.GetBytes(9); string path = Convert.ToBase64String(bytes); return path; } } } ================================================ FILE: test/Microsoft.HttpRepl.Fakes/MockedShellState.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.Repl; using Microsoft.Repl.Commanding; using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Input; using Microsoft.Repl.Suggestions; using Moq; namespace Microsoft.HttpRepl.Fakes { public class MockedShellState : IShellState { private readonly ShellState _shellState; private readonly StringBuilder _output = new StringBuilder(); public MockedShellState(IInputManager inputManager = null) { DefaultCommandDispatcher defaultCommandDispatcher = DefaultCommandDispatcher.Create(x => { }, new object()); Mock mockedConsoleManager = new Mock(); Mock mockedErrorWritable = new Mock(); mockedErrorWritable.Setup(x => x.WriteLine(It.IsAny())).Callback((string s) => ErrorMessage = s); mockedConsoleManager.Setup(x => x.Error).Returns(mockedErrorWritable.Object); mockedConsoleManager.Setup(x => x.Write(It.IsAny())).Callback((string s) => _output.Append(s)); mockedConsoleManager.Setup(x => x.WriteLine(It.IsAny())).Callback((string s) => _output.AppendLine(s)); _shellState = new ShellState(defaultCommandDispatcher, inputManager: inputManager, consoleManager: mockedConsoleManager.Object); } public string ErrorMessage { get; private set; } public List Output => _output.ToString().Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries).ToList(); public IInputManager InputManager => _shellState.InputManager; public ICommandHistory CommandHistory => _shellState.CommandHistory; public IConsoleManager ConsoleManager => _shellState.ConsoleManager; public ICommandDispatcher CommandDispatcher => _shellState.CommandDispatcher; public ISuggestionManager SuggestionManager => _shellState.SuggestionManager; public bool IsExiting { get => _shellState.IsExiting; set => _shellState.IsExiting = value; } public void MoveCarets(int positions) { ConsoleManager?.MoveCaret(positions); InputManager?.MoveCaret(positions); } } } ================================================ FILE: test/Microsoft.HttpRepl.Fakes/NullConsoleManager.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Threading; using Microsoft.Repl.ConsoleHandling; namespace Microsoft.HttpRepl.Fakes { public class NullConsoleManager : IConsoleManager { public Point Caret => default(Point); public Point CommandStart => default(Point); public int CaretPosition => default(int); public IWritable Error => default(IWritable); public bool IsKeyAvailable => default; public bool IsCaretVisible { get => default; set => _ = value; } public bool AllowOutputRedirection => true; public IDisposable AddBreakHandler(Action onBreak) { return default; } public void Clear() { } public void MoveCaret(int positions) { } public ConsoleKeyInfo ReadKey(CancellationToken cancellationToken) { return default; } public void ResetCommandStart() { } public void Write(char c) { } public void Write(string s) { } public void WriteLine() { } public void WriteLine(string s) { } } } ================================================ FILE: test/Microsoft.HttpRepl.Fakes/NullPreferences.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Collections.Generic; using Microsoft.HttpRepl.Preferences; using Microsoft.Repl.ConsoleHandling; namespace Microsoft.HttpRepl.Fakes { public class NullPreferences : IPreferences { public IReadOnlyDictionary CurrentPreferences => null; public IReadOnlyDictionary DefaultPreferences => null; public AllowedColors GetColorValue(string preference, AllowedColors defaultValue = default) { return default; } public int GetIntValue(string preference, int defaultValue = default) { return defaultValue; } public bool GetBoolValue(string preference, bool defaultValue = false) { return defaultValue; } public string GetValue(string preference, string defaultValue = default) { return defaultValue; } public bool SetValue(string preference, string value) { return false; } public bool TryGetValue(string preference, out string value) { value = null; return false; } } } ================================================ FILE: test/Microsoft.HttpRepl.Fakes/NullTelemetry.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Collections.Generic; using Microsoft.HttpRepl.Telemetry; namespace Microsoft.HttpRepl.Fakes { public class NullTelemetry : ITelemetry { private readonly IFirstTimeUseNoticeSentinel _firstTimeUseNoticeSentinel = new NullFirstTimeUseNoticeSentinel(); public bool Enabled => false; public IFirstTimeUseNoticeSentinel FirstTimeUseNoticeSentinel => _firstTimeUseNoticeSentinel; public void TrackEvent(string eventName, IReadOnlyDictionary properties, IReadOnlyDictionary measurements) { } } public class NullFirstTimeUseNoticeSentinel : IFirstTimeUseNoticeSentinel { public void CreateIfNotExists() { } public bool Exists() => true; } } ================================================ FILE: test/Microsoft.HttpRepl.Fakes/TelemetryCollector.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Collections.Generic; using Microsoft.HttpRepl.Telemetry; namespace Microsoft.HttpRepl.Fakes { public class TelemetryCollector : ITelemetry { private List _telemetry = new List(); public bool Enabled => true; public IFirstTimeUseNoticeSentinel FirstTimeUseNoticeSentinel => null; public void TrackEvent(string eventName, IReadOnlyDictionary properties, IReadOnlyDictionary measurements) { _telemetry.Add(new CollectedTelemetry(eventName, properties, measurements)); } public IReadOnlyList Telemetry => _telemetry; public class CollectedTelemetry { public string EventName { get; } public IReadOnlyDictionary Properties { get; } public IReadOnlyDictionary Measurements { get; } public CollectedTelemetry(string eventName, IReadOnlyDictionary properties, IReadOnlyDictionary measurements) { EventName = eventName; Properties = properties; Measurements = measurements; } } } } ================================================ FILE: test/Microsoft.HttpRepl.IntegrationTests/BaseIntegrationTest.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.HttpRepl.Fakes; using Microsoft.HttpRepl.IntegrationTests.Utilities; using Microsoft.HttpRepl.Preferences; using Microsoft.HttpRepl.Telemetry; namespace Microsoft.HttpRepl.IntegrationTests { public class BaseIntegrationTest { private static readonly Regex _dateRegex; private static readonly string _dateReplacement; static BaseIntegrationTest() { if (Environment.NewLine == "\r\n") { _dateRegex = new Regex("^Date: [A-Za-z]{3}, \\d{2} [A-Za-z]{3} \\d{4} \\d{2}:\\d{2}:\\d{2} GMT\r$", RegexOptions.Compiled | RegexOptions.Multiline); _dateReplacement = "Date: [Date]\r"; } else { _dateRegex = new Regex("^Date: [A-Za-z]{3}, \\d{2} [A-Za-z]{3} \\d{4} \\d{2}:\\d{2}:\\d{2} GMT$", RegexOptions.Compiled | RegexOptions.Multiline); _dateReplacement = "Date: [Date]"; } } protected static string NormalizeOutput(string output, string baseUrl) { // The console implementation uses trailing whitespace when a new line's text is shorter than the previous // line. For example (the trailing * represent spaces): // Line 1: (Disconnected)> run C:\path\to\a\test\script\file.txt // Line 2: (Disconnected)> set base http://localhost:12345****** // This having this whitespace makes it harder to read/write test baselines, so here we'll trim each line string result = string.Join(Environment.NewLine, output.Split(Environment.NewLine).Select(l => l.TrimEnd())); // next, normalize the base URL from the test fixture if (!string.IsNullOrEmpty(baseUrl)) { result = result.Replace(baseUrl, "[BaseUrl]"); } // next, normalize the date result = _dateRegex.Replace(result, _dateReplacement); // strip ansi begin/end formatting (bold, color) result = Regex.Replace(result, @"\u001b\[[0-9]*m", string.Empty); return result; } protected static async Task RunTestScript(string scriptText, string baseAddress, IPreferences preferences = null, ITelemetry telemetry = null) { LoggingConsoleManagerDecorator console = new LoggingConsoleManagerDecorator(new NullConsoleManager()); preferences ??= new NullPreferences(); telemetry ??= new NullTelemetry(); using (var script = new TestScript(scriptText)) { await Program.Start($"run {script.FilePath}".Split(' '), console, preferences, telemetry); } string output = console.LoggedOutput; // remove the first line because it has the randomly generated script file name. output = output.Substring(output.IndexOf(Environment.NewLine) + Environment.NewLine.Length); output = NormalizeOutput(output, baseAddress); return output; } } } ================================================ FILE: test/Microsoft.HttpRepl.IntegrationTests/Commands/ChangeDirectoryCommandTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Threading.Tasks; using Microsoft.HttpRepl.IntegrationTests.SampleApi; using Xunit; namespace Microsoft.HttpRepl.IntegrationTests.Commands { public class ChangeDirectoryCommandTests : BaseIntegrationTest, IClassFixture> { private readonly SampleApiServerConfig _serverConfig; public ChangeDirectoryCommandTests(HttpCommandsFixture fixture) { _serverConfig = fixture.Config; } [Fact] public async Task WithSwagger_MethodsAreUppercase() { string scriptText = $@"connect {_serverConfig.BaseAddress} ls cd api ls cd Values"; string output = await RunTestScript(scriptText, _serverConfig.BaseAddress); // make sure to normalize newlines in the expected output string expected = NormalizeOutput(@"(Disconnected)> connect [BaseUrl] Using a base address of [BaseUrl]/ Using OpenAPI description at [BaseUrl]/swagger/v1/swagger.json For detailed tool info, see https://aka.ms/http-repl-doc [BaseUrl]/> ls . [] api [] [BaseUrl]/> cd api /api [] [BaseUrl]/api> ls . [] .. [] Values [GET|POST] [BaseUrl]/api> cd Values /api/Values [GET|POST] [BaseUrl]/api/Values>", null); Assert.Equal(expected, output); } } } ================================================ FILE: test/Microsoft.HttpRepl.IntegrationTests/Commands/EchoCommandTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Threading.Tasks; using Microsoft.HttpRepl.IntegrationTests.SampleApi; using Xunit; namespace Microsoft.HttpRepl.IntegrationTests.Commands { public class EchoCommandTests : BaseIntegrationTest, IClassFixture> { private readonly SampleApiServerConfig _serverConfig; public EchoCommandTests(HttpCommandsFixture fixture) { _serverConfig = fixture.Config; } [Fact] public async Task WithEchoOn_ShowsCorrectOutput() { string scriptText = $@"connect --base {_serverConfig.BaseAddress} echo on"; string output = await RunTestScript(scriptText, _serverConfig.BaseAddress); string expected = NormalizeOutput(@"(Disconnected)> connect --base [BaseUrl] Using a base address of [BaseUrl]/ Using OpenAPI description at [BaseUrl]/swagger/v1/swagger.json For detailed tool info, see https://aka.ms/http-repl-doc [BaseUrl]/> echo on Request echoing is on [BaseUrl]/>", null); Assert.Equal(expected, output); } [Fact] public async Task WithEchoOff_ShowsCorrectOutput() { string scriptText = $@"connect --base {_serverConfig.BaseAddress} echo off"; string output = await RunTestScript(scriptText, _serverConfig.BaseAddress); string expected = NormalizeOutput(@"(Disconnected)> connect --base [BaseUrl] Using a base address of [BaseUrl]/ Using OpenAPI description at [BaseUrl]/swagger/v1/swagger.json For detailed tool info, see https://aka.ms/http-repl-doc [BaseUrl]/> echo off Request echoing is off [BaseUrl]/>", null); Assert.Equal(expected, output); } } } ================================================ FILE: test/Microsoft.HttpRepl.IntegrationTests/Commands/GetCommandTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Threading.Tasks; using Microsoft.HttpRepl.IntegrationTests.SampleApi; using Xunit; namespace Microsoft.HttpRepl.IntegrationTests.Commands { public class GetCommandTests : BaseIntegrationTest, IClassFixture> { private readonly SampleApiServerConfig _serverConfig; public GetCommandTests(HttpCommandsFixture fixture) { _serverConfig = fixture.Config; } [Fact] public async Task WithoutParameter_ShowsCorrectOutput() { string scriptText = $@"connect {_serverConfig.BaseAddress} cd api/values get"; string output = await RunTestScript(scriptText, _serverConfig.BaseAddress); string expected = NormalizeOutput(@"(Disconnected)> connect [BaseUrl] Using a base address of [BaseUrl]/ Using OpenAPI description at [BaseUrl]/swagger/v1/swagger.json For detailed tool info, see https://aka.ms/http-repl-doc [BaseUrl]/> cd api/values /api/values [GET|POST] [BaseUrl]/api/values> get HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 Date: [Date] Server: Kestrel Transfer-Encoding: chunked [ ""value1"", ""value2"" ] [BaseUrl]/api/values>", null); Assert.Equal(expected, output); } [Fact] public async Task WithParameter_ShowsCorrectOutput() { string scriptText = $@"connect {_serverConfig.BaseAddress} cd api/values get 5"; string output = await RunTestScript(scriptText, _serverConfig.BaseAddress); string expected = NormalizeOutput(@"(Disconnected)> connect [BaseUrl] Using a base address of [BaseUrl]/ Using OpenAPI description at [BaseUrl]/swagger/v1/swagger.json For detailed tool info, see https://aka.ms/http-repl-doc [BaseUrl]/> cd api/values /api/values [GET|POST] [BaseUrl]/api/values> get 5 HTTP/1.1 200 OK Content-Type: text/plain; charset=utf-8 Date: [Date] Server: Kestrel Transfer-Encoding: chunked value [BaseUrl]/api/values>", null); Assert.Equal(expected, output); } [Fact] public async Task InvalidPath_ShowsNotFoundMessage() { string scriptText = $@"connect {_serverConfig.BaseAddress} cd api/invalidpath get"; string output = await RunTestScript(scriptText, _serverConfig.BaseAddress); string expected = NormalizeOutput(@"(Disconnected)> connect [BaseUrl] Using a base address of [BaseUrl]/ Using OpenAPI description at [BaseUrl]/swagger/v1/swagger.json For detailed tool info, see https://aka.ms/http-repl-doc [BaseUrl]/> cd api/invalidpath Warning: The '/api/invalidpath' endpoint is not present in the OpenAPI description /api/invalidpath [] [BaseUrl]/api/invalidpath> get HTTP/1.1 404 Not Found Content-Length: 0 Date: [Date] Server: Kestrel [BaseUrl]/api/invalidpath>", null); Assert.Equal(expected, output); } [Fact] public async Task WithNoSwaggerAndAbsoluteUrl_ShowsCorrectOutput() { string scriptText = $@"get {_serverConfig.BaseAddress}/api/values"; string output = await RunTestScript(scriptText, _serverConfig.BaseAddress); string expected = NormalizeOutput(@"(Disconnected)> get [BaseUrl]/api/values HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 Date: [Date] Server: Kestrel Transfer-Encoding: chunked [ ""value1"", ""value2"" ] (Disconnected)>", null); Assert.Equal(expected, output); } } } ================================================ FILE: test/Microsoft.HttpRepl.IntegrationTests/Commands/HttpCommandsFixture.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using Microsoft.HttpRepl.IntegrationTests.SampleApi; namespace Microsoft.HttpRepl.IntegrationTests.Commands { public class HttpCommandsFixture : IDisposable where T : SampleApiServerConfig, new() { private readonly SampleApiServer _testWebServer; public T Config { get; } = new T(); public HttpCommandsFixture() { _testWebServer = new SampleApiServer(Config); _testWebServer.Start(); } public void Dispose() { _testWebServer.Stop(); } } public class DualHttpCommandsFixture : IDisposable where T : SampleApiServerConfig, new() { private readonly SampleApiServer _swaggerServer; private readonly SampleApiServer _nonSwaggerServer; public T SwaggerConfig { get; } = new T(); public T NonSwaggerConfig { get; } = new T() { EnableSwagger = false }; public DualHttpCommandsFixture() { _swaggerServer = new SampleApiServer(SwaggerConfig); _swaggerServer.Start(); _nonSwaggerServer = new SampleApiServer(NonSwaggerConfig); _nonSwaggerServer.Start(); } public void Dispose() { _swaggerServer.Stop(); _nonSwaggerServer.Stop(); } } } ================================================ FILE: test/Microsoft.HttpRepl.IntegrationTests/Commands/ListCommandTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Threading.Tasks; using Microsoft.HttpRepl.IntegrationTests.SampleApi; using Xunit; namespace Microsoft.HttpRepl.IntegrationTests.Commands { public class ListCommandTests : BaseIntegrationTest, IClassFixture> { private readonly SampleApiServerConfig _swaggerServerConfig; private readonly SampleApiServerConfig _nonSwaggerServerConfig; public ListCommandTests(DualHttpCommandsFixture fixture) { _swaggerServerConfig = fixture.SwaggerConfig; _nonSwaggerServerConfig = fixture.NonSwaggerConfig; } [Fact] public async Task WithSwagger_ShowsAvailableSubpaths() { string scriptText = $@"connect {_swaggerServerConfig.BaseAddress} ls cd api ls"; string output = await RunTestScript(scriptText, _swaggerServerConfig.BaseAddress); // make sure to normalize newlines in the expected output string expected = NormalizeOutput(@"(Disconnected)> connect [BaseUrl] Using a base address of [BaseUrl]/ Using OpenAPI description at [BaseUrl]/swagger/v1/swagger.json For detailed tool info, see https://aka.ms/http-repl-doc [BaseUrl]/> ls . [] api [] [BaseUrl]/> cd api /api [] [BaseUrl]/api> ls . [] .. [] Values [GET|POST] [BaseUrl]/api>", null); Assert.Equal(expected, output); } [Fact] public async Task WithSwagger_ShowsControllerActionsWithHttpVerbs() { string scriptText = $@"connect {_swaggerServerConfig.BaseAddress} cd api/Values ls"; string output = await RunTestScript(scriptText, _swaggerServerConfig.BaseAddress); string expected = NormalizeOutput(@"(Disconnected)> connect [BaseUrl] Using a base address of [BaseUrl]/ Using OpenAPI description at [BaseUrl]/swagger/v1/swagger.json For detailed tool info, see https://aka.ms/http-repl-doc [BaseUrl]/> cd api/Values /api/Values [GET|POST] [BaseUrl]/api/Values> ls . [GET|POST] .. [] {id} [GET|PUT|DELETE] [BaseUrl]/api/Values>", null); Assert.Equal(expected, output); } [Fact] public async Task WithoutSwagger_ShowsNoSubpaths() { string scriptText = $@"connect --base {_nonSwaggerServerConfig.BaseAddress} ls cd api ls"; string output = await RunTestScript(scriptText, _nonSwaggerServerConfig.BaseAddress); // make sure to normalize newlines in the expected output string expected = NormalizeOutput(@"(Disconnected)> connect --base [BaseUrl] Using a base address of [BaseUrl]/ Unable to find an OpenAPI description For detailed tool info, see https://aka.ms/http-repl-doc [BaseUrl]/> ls No directory structure has been set, so there is nothing to list. Use the ""connect"" command to set a directory structure based on an OpenAPI description. [BaseUrl]/> cd api [BaseUrl]/api> ls No directory structure has been set, so there is nothing to list. Use the ""connect"" command to set a directory structure based on an OpenAPI description. [BaseUrl]/api>", null); Assert.Equal(expected, output); } [Fact] public async Task WithoutSwagger_ShowsNoActionsOrVerbs() { string scriptText = $@"connect --base {_nonSwaggerServerConfig.BaseAddress} cd api/Values ls"; string output = await RunTestScript(scriptText, _nonSwaggerServerConfig.BaseAddress); string expected = NormalizeOutput(@"(Disconnected)> connect --base [BaseUrl] Using a base address of [BaseUrl]/ Unable to find an OpenAPI description For detailed tool info, see https://aka.ms/http-repl-doc [BaseUrl]/> cd api/Values [BaseUrl]/api/Values> ls No directory structure has been set, so there is nothing to list. Use the ""connect"" command to set a directory structure based on an OpenAPI description. [BaseUrl]/api/Values>", null); Assert.Equal(expected, output); } } } ================================================ FILE: test/Microsoft.HttpRepl.IntegrationTests/Commands/SetHeaderCommandTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Threading.Tasks; using Microsoft.HttpRepl.IntegrationTests.SampleApi; using Xunit; namespace Microsoft.HttpRepl.IntegrationTests.Commands { public class SetHeaderCommandTests : BaseIntegrationTest, IClassFixture> { private readonly SampleApiServerConfig _serverConfig; public SetHeaderCommandTests(HttpCommandsFixture fixture) { _serverConfig = fixture.Config; } [Fact] public async Task WithNameAndValueSpecified_AddsNewHeaderToListOfHeaders() { string scriptText = $@"connect {_serverConfig.BaseAddress} cd api/values echo on set header Accept application/json get"; string output = await RunTestScript(scriptText, _serverConfig.BaseAddress); string expected = NormalizeOutput(@"(Disconnected)> connect [BaseUrl] Using a base address of [BaseUrl]/ Using OpenAPI description at [BaseUrl]/swagger/v1/swagger.json For detailed tool info, see https://aka.ms/http-repl-doc [BaseUrl]/> cd api/values /api/values [GET|POST] [BaseUrl]/api/values> echo on Request echoing is on [BaseUrl]/api/values> set header Accept application/json [BaseUrl]/api/values> get Request to [BaseUrl]... GET /api/values HTTP/1.1 Accept: application/json User-Agent: HTTP-REPL Response from [BaseUrl]... HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 Date: [Date] Server: Kestrel Transfer-Encoding: chunked [ ""value1"", ""value2"" ] [BaseUrl]/api/values>", null); Assert.Equal(expected, output); } [Fact] public async Task WithEmptyValue_ClearsHeader() { string scriptText = $@"connect {_serverConfig.BaseAddress} cd api/values echo on set header User-Agent get"; string output = await RunTestScript(scriptText, _serverConfig.BaseAddress); string expected = NormalizeOutput(@"(Disconnected)> connect [BaseUrl] Using a base address of [BaseUrl]/ Using OpenAPI description at [BaseUrl]/swagger/v1/swagger.json For detailed tool info, see https://aka.ms/http-repl-doc [BaseUrl]/> cd api/values /api/values [GET|POST] [BaseUrl]/api/values> echo on Request echoing is on [BaseUrl]/api/values> set header User-Agent [BaseUrl]/api/values> get Request to [BaseUrl]... GET /api/values HTTP/1.1 Response from [BaseUrl]... HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 Date: [Date] Server: Kestrel Transfer-Encoding: chunked [ ""value1"", ""value2"" ] [BaseUrl]/api/values>", null); Assert.Equal(expected, output); } } } ================================================ FILE: test/Microsoft.HttpRepl.IntegrationTests/Microsoft.HttpRepl.IntegrationTests.csproj ================================================ $(TestProjectTargetFramework) false ================================================ FILE: test/Microsoft.HttpRepl.IntegrationTests/SampleApi/Controllers/ValuesController.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; namespace Microsoft.HttpRepl.IntegrationTests.SampleApi.Controllers { [Route("api/[controller]")] [ApiController] public class ValuesController : ControllerBase { // GET api/values [HttpGet] public ActionResult> Get() { return new string[] { "value1", "value2" }; } // GET api/values/5 [HttpGet("{id}")] public ActionResult Get([FromQuery] int id) { return "value"; } // POST api/values [HttpPost] public void Post([FromBody] string value) { } // PUT api/values/5 [HttpPut("{id}")] public void Put(int id, [FromBody] string value) { } // DELETE api/values/5 [HttpDelete("{id}")] public void Delete(int id) { } } } ================================================ FILE: test/Microsoft.HttpRepl.IntegrationTests/SampleApi/SampleApiServer.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using Microsoft.AspNetCore; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.OpenApi.Models; namespace Microsoft.HttpRepl.IntegrationTests.SampleApi { public class SampleApiServer { private readonly IWebHost _Host; public SampleApiServer(SampleApiServerConfig config) { _Host = WebHost.CreateDefaultBuilder() .UseKestrel(options => { options.ListenLocalhost(config.Port.Value, (listenOptions) => { listenOptions.DisableAltSvcHeader = true; }); }) .ConfigureServices(services => { services.AddControllers(); if (config.EnableSwagger) { services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" }); }); } }) .Configure(app => { app.UseDeveloperExceptionPage(); app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); if (config.EnableSwagger) { app.UseSwagger(); } }) .Build(); } public void Start() { _Host.RunAsync(); } public void Stop() { _Host.StopAsync(); } } } ================================================ FILE: test/Microsoft.HttpRepl.IntegrationTests/SampleApi/SampleApiServerConfig.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.ObjectModel; using System.Net; using System.Net.Sockets; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; namespace Microsoft.HttpRepl.IntegrationTests.SampleApi { public class SampleApiServerConfig { public string BaseAddress => $"http://localhost:{Port}"; public Lazy Port { get; set; } = new Lazy(() => FindFreeTcpPort()); /// /// Turns Swagger on or off for the SampleApiServer instance. /// public bool EnableSwagger { get; set; } = true; public Collection Routes { get; } = new Collection(); private static int FindFreeTcpPort() { TcpListener l = new TcpListener(IPAddress.Loopback, 0); l.Start(); int port = ((IPEndPoint)l.LocalEndpoint).Port; l.Stop(); return port; } } public abstract class SampleApiServerRoute { public string Verb { get; set; } = "GET"; public string Route { get; set; } public abstract Task Execute(HttpContext context); } public sealed class StaticSampleApiServerRoute : SampleApiServerRoute { public string Result { get; set; } public StaticSampleApiServerRoute(string verb, string route, string result) { Verb = verb; Route = route; Result = result; } public async override Task Execute(HttpContext context) { await context.Response.WriteAsync(Result); } } public sealed class DynamicSampleApiServerRoute : SampleApiServerRoute { public Func ResultFunction { get; set; } public DynamicSampleApiServerRoute(string verb, string route, Func resultFunction) { Verb = verb; Route = route; ResultFunction = resultFunction; } public async override Task Execute(HttpContext context) { await ResultFunction(context); } } } ================================================ FILE: test/Microsoft.HttpRepl.IntegrationTests/Utilities/TestScript.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.IO; namespace Microsoft.HttpRepl.IntegrationTests.Utilities { public class TestScript : IDisposable { public string FilePath { get; } public TestScript(string content) { FilePath = Path.GetTempFileName(); File.WriteAllText(FilePath, content); } public void Dispose() { if(File.Exists(FilePath)) { File.Delete(FilePath); } } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/Commands/AddQueryParamCommandTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.HttpRepl.Commands; using Microsoft.HttpRepl.Fakes; using Microsoft.HttpRepl.Telemetry; using Microsoft.Repl.Parsing; using Xunit; namespace Microsoft.HttpRepl.Tests.Commands { public class AddQueryParamCommandTests : CommandTestsBase { [Fact] public void CanHandle_WithParseResultSectionsLessThanTwo_ReturnsNull() { ArrangeInputs(parseResultSections: "add", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); AddQueryParamCommand addQueryParamCommand= new AddQueryParamCommand(new NullTelemetry()); bool? result = addQueryParamCommand.CanHandle(shellState, httpState, parseResult); Assert.Null(result); } [Fact] public void CanHandle_WithFirstParseResultSectionNotEqualToName_ReturnsNull() { ArrangeInputs(parseResultSections: "test query-param name", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); AddQueryParamCommand addQueryParamCommand = new AddQueryParamCommand(new NullTelemetry()); bool? result = addQueryParamCommand.CanHandle(shellState, httpState, parseResult); Assert.Null(result); } [Fact] public void CanHandle_WithSecondParseResultSectionNotEqualToSubCommand_ReturnsNull() { ArrangeInputs(parseResultSections: "add base name", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); AddQueryParamCommand addQueryParamCommand = new AddQueryParamCommand(new NullTelemetry()); bool? result = addQueryParamCommand.CanHandle(shellState, httpState, parseResult); Assert.Null(result); } [Fact] public void CanHandle_WithValidInput_ReturnsTrue() { ArrangeInputs(parseResultSections: "add query-param name value", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); AddQueryParamCommand setQueryParamCommand = new AddQueryParamCommand(new NullTelemetry()); bool? result = setQueryParamCommand.CanHandle(shellState, httpState, parseResult); Assert.True(result); } [Fact] public void GetHelpDetails_ReturnsHelpDetails() { ArrangeInputs(parseResultSections: "add query-param test", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); string expected = "\u001b[1mUsage: \u001b[39madd query-param {name} [value]" + Environment.NewLine + Environment.NewLine + "Adds a key and value pair to the query string. " + "The key and value will be UrlEncoded. Multiple values may be mapped to the same key." + Environment.NewLine; AddQueryParamCommand addQueryParamCommand = new AddQueryParamCommand(new NullTelemetry()); string result = addQueryParamCommand.GetHelpDetails(shellState, httpState, parseResult); Assert.Equal(expected, result); } [Fact] public async Task ExecuteAsync_ValidCommandInvalidInput_CommandOutput() { ArrangeInputs(parseResultSections: "add query-param test", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); AddQueryParamCommand addQueryParamCommand = new AddQueryParamCommand(new NullTelemetry()); await addQueryParamCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); string expectedOutput = "The add query-param command key: test is missing a value. " + "Please try again with a valid key value pair"; Assert.Equal(shellState.Output.Last(), expectedOutput); } [Fact] public async Task ExecuteAsync_WithMultipleValuesMapped() { ArrangeInputs(parseResultSections: "add query-param name value1 name value2", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); AddQueryParamCommand addQueryParamCommand = new AddQueryParamCommand(new NullTelemetry()); await addQueryParamCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Dictionary> queryParam = httpState.QueryParam; Assert.Single(httpState.QueryParam); Assert.True(queryParam.ContainsKey("name")); queryParam.TryGetValue("name", out IEnumerable nameHeaderValues); Assert.Contains("value1", nameHeaderValues); Assert.Contains("value2", nameHeaderValues); ArrangeInputs(parseResultSections: "add query-param name value3", out MockedShellState shellStateTwo, out HttpState httpStateTwo, out ICoreParseResult parseResultTwo); await addQueryParamCommand.ExecuteAsync(shellStateTwo, httpState, parseResultTwo, CancellationToken.None); queryParam = httpState.QueryParam; Assert.Single(httpState.QueryParam); Assert.True(queryParam.ContainsKey("name")); queryParam.TryGetValue("name", out IEnumerable nameHeaderValuesTwo); Assert.Contains("value3", nameHeaderValuesTwo); } [Fact] public async Task ExecuteAsync_ValidCommandWithSpace() { ArrangeInputs(parseResultSections: "add query-param test ", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); AddQueryParamCommand addQueryParamCommand = new AddQueryParamCommand(new NullTelemetry()); await addQueryParamCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Dictionary> queryParam = httpState.QueryParam; Assert.Single(httpState.QueryParam); Assert.True(queryParam.ContainsKey("test")); queryParam.TryGetValue("test", out IEnumerable nameHeaderValue); Assert.Contains("", nameHeaderValue); } [Fact] public async Task ExecuteAsync__SendsTelemetryWithHashedHeaderName() { ArrangeInputs(parseResultSections: "add query-param name value", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); TelemetryCollector telemetry = new TelemetryCollector(); AddQueryParamCommand addQueryParamCommand = new AddQueryParamCommand(telemetry); await addQueryParamCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.Single(telemetry.Telemetry); TelemetryCollector.CollectedTelemetry collectedTelemetry = telemetry.Telemetry[0]; Assert.Equal("AddQueryParam", collectedTelemetry.EventName); Assert.Equal(Sha256Hasher.Hash("name"), collectedTelemetry.Properties["QueryParamKey"]); Assert.Equal("False", collectedTelemetry.Properties["IsValueEmpty"]); } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/Commands/ChangeDirectoryCommandTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Net.Http; using System.Threading; using Microsoft.HttpRepl.Commands; using Microsoft.HttpRepl.Fakes; using Microsoft.HttpRepl.FileSystem; using Microsoft.HttpRepl.OpenApi; using Microsoft.HttpRepl.Preferences; using Microsoft.HttpRepl.UserProfile; using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Parsing; using Xunit; namespace Microsoft.HttpRepl.Tests.Commands { public class ChangeDirectoryCommandTests { [Fact] public void ExecuteAsync_UnknownEndpoint_DisplaysWarning() { ChangeDirectoryCommand command = new ChangeDirectoryCommand(); Setup(commandText: "cd NotAnEndpoint", out MockedShellState mockedShellState, out HttpState httpState, out ICoreParseResult parseResult); ApiDefinition apiDefinition = new ApiDefinition() { DirectoryStructure = new DirectoryStructure(null) }; httpState.ApiDefinition = apiDefinition; string expectedFirstLine = string.Format(Resources.Strings.ChangeDirectoryCommand_Warning_UnknownEndpoint, "/NotAnEndpoint").SetColor(httpState.WarningColor); string expectedSecondLine = "/NotAnEndpoint []"; command.ExecuteAsync(mockedShellState, httpState, parseResult, CancellationToken.None); Assert.Equal(2, mockedShellState.Output.Count); Assert.Equal(expectedFirstLine, mockedShellState.Output[0]); Assert.Equal(expectedSecondLine, mockedShellState.Output[1]); } [Fact] public void ExecuteAsync_KnownEndpointWithRequestMethods_NoWarning() { ChangeDirectoryCommand command = new ChangeDirectoryCommand(); Setup(commandText: "cd AnEndpoint", out MockedShellState mockedShellState, out HttpState httpState, out ICoreParseResult parseResult); DirectoryStructure directoryStructure = new DirectoryStructure(null); DirectoryStructure childDirectory = directoryStructure.DeclareDirectory("AnEndpoint"); RequestInfo childRequestInfo = new RequestInfo(); childRequestInfo.AddMethod("GET"); childDirectory.RequestInfo = childRequestInfo; ApiDefinition apiDefinition = new ApiDefinition() { DirectoryStructure = directoryStructure }; httpState.ApiDefinition = apiDefinition; string expectedOutput = "/AnEndpoint [GET]"; command.ExecuteAsync(mockedShellState, httpState, parseResult, CancellationToken.None); Assert.Single(mockedShellState.Output); Assert.Equal(expectedOutput, mockedShellState.Output[0]); } [Fact] public void ExecuteAsync_KnownEndpointWithSubdirectory_NoWarning() { ChangeDirectoryCommand command = new ChangeDirectoryCommand(); Setup(commandText: "cd AnEndpoint", out MockedShellState mockedShellState, out HttpState httpState, out ICoreParseResult parseResult); DirectoryStructure directoryStructure = new DirectoryStructure(null); DirectoryStructure childDirectory = directoryStructure.DeclareDirectory("AnEndpoint"); DirectoryStructure grandchildDirectory = childDirectory.DeclareDirectory("AnotherEndpoint"); ApiDefinition apiDefinition = new ApiDefinition() { DirectoryStructure = directoryStructure }; httpState.ApiDefinition = apiDefinition; string expectedOutput = "/AnEndpoint []"; command.ExecuteAsync(mockedShellState, httpState, parseResult, CancellationToken.None); Assert.Single(mockedShellState.Output); Assert.Equal(expectedOutput, mockedShellState.Output[0]); } private void Setup(string commandText, out MockedShellState mockedShellState, out HttpState httpState, out ICoreParseResult parseResult) { mockedShellState = new MockedShellState(); IFileSystem fileSystem = new RealFileSystem(); IUserProfileDirectoryProvider userProfileDirectoryProvider = new UserProfileDirectoryProvider(); IPreferences preferences = new UserFolderPreferences(fileSystem, userProfileDirectoryProvider, null); parseResult = CoreParseResultHelper.Create(commandText); HttpClient httpClient = new HttpClient(); httpState = new HttpState(preferences, httpClient); } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/Commands/ClearCommandTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.HttpRepl.Commands; using Microsoft.HttpRepl.Fakes; using Microsoft.Repl; using Microsoft.Repl.Commanding; using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Parsing; using Xunit; namespace Microsoft.HttpRepl.Tests.Commands { public class ClearCommandTests : CommandTestsBase { [Theory] [InlineData("clear", true)] [InlineData("cls", true)] [InlineData("cd", null)] [InlineData("clearing", null)] public void CanHandle_ReturnsCorrectResult(string command, bool? expectedResult) { ArrangeInputs(parseResultSections: command, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); ClearCommand clearCommand = new ClearCommand(); bool? result = clearCommand.CanHandle(shellState, httpState, parseResult); Assert.Equal(expectedResult, result); } [Fact] public void Suggest_Cl_ReturnsBoth() { ArrangeInputs(parseResultSections: "cl", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); ClearCommand clearCommand = new ClearCommand(); IEnumerable result = clearCommand.Suggest(shellState, httpState, parseResult); Assert.NotNull(result); List resultList = result.ToList(); Assert.Equal(2, resultList.Count); Assert.Equal("clear", resultList[0]); Assert.Equal("cls", resultList[1]); } [Fact] public void Suggest_Cle_ReturnsClear() { ArrangeInputs(parseResultSections: "cle", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); ClearCommand clearCommand = new ClearCommand(); IEnumerable result = clearCommand.Suggest(shellState, httpState, parseResult); Assert.NotNull(result); List resultList = result.ToList(); Assert.Single(resultList); Assert.Equal("clear", resultList[0]); } [Fact] public void Suggest_Cls_ReturnsCls() { ArrangeInputs(parseResultSections: "cls", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); ClearCommand clearCommand = new ClearCommand(); IEnumerable result = clearCommand.Suggest(shellState, httpState, parseResult); Assert.NotNull(result); List resultList = result.ToList(); Assert.Single(resultList); Assert.Equal("cls", resultList[0]); } [Fact] public async Task ExecuteAsync_CallsConsoleManagerClear() { HttpState httpState = GetHttpState(out _, out _); ICoreParseResult parseResult = CreateCoreParseResult("clear"); DefaultCommandDispatcher dispatcher = DefaultCommandDispatcher.Create((ss) => { }, httpState); LoggingConsoleManagerDecorator consoleManager = new LoggingConsoleManagerDecorator(new NullConsoleManager()); IShellState shellState = new ShellState(dispatcher, consoleManager: consoleManager); ClearCommand clearCommand = new ClearCommand(); await clearCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.True(consoleManager.WasClearCalled); } [Fact] public async Task ExecuteAsync_CallsDispatcherOnReady() { bool wasOnReadyCalled = false; HttpState httpState = GetHttpState(out _, out _); ICoreParseResult parseResult = CreateCoreParseResult("clear"); DefaultCommandDispatcher dispatcher = DefaultCommandDispatcher.Create((ss) => wasOnReadyCalled = true, httpState); IConsoleManager consoleManager = new LoggingConsoleManagerDecorator(new NullConsoleManager()); IShellState shellState = new ShellState(dispatcher, consoleManager: consoleManager); ClearCommand clearCommand = new ClearCommand(); await clearCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.True(wasOnReadyCalled); } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/Commands/ClearQueryParamCommandTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.HttpRepl.Commands; using Microsoft.HttpRepl.Fakes; using Microsoft.Repl.Parsing; using Xunit; namespace Microsoft.HttpRepl.Tests.Commands { public class ClearQueryParamCommandTests: CommandTestsBase { [Fact] public void CanHandle_WithFirstParseResultSectionNotEqualToName_ReturnsNull() { ArrangeInputs(parseResultSections: "test query-param name", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); ClearQueryParamCommand clearQueryParamCommand = new ClearQueryParamCommand(new NullTelemetry()); bool? result = clearQueryParamCommand.CanHandle(shellState, httpState, parseResult); Assert.Null(result); } [Fact] public void CanHandle_WithSecondParseResultSectionNotEqualToSubCommand_ReturnsNull() { ArrangeInputs(parseResultSections: "set base name", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); ClearQueryParamCommand clearQueryParamCommand = new ClearQueryParamCommand(new NullTelemetry()); bool? result = clearQueryParamCommand.CanHandle(shellState, httpState, parseResult); Assert.Null(result); } [Fact] public void CanHandle_WithValidInput_ReturnsTrue() { ArrangeInputs(parseResultSections: "clear query-param", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); ClearQueryParamCommand clearQueryParamCommand = new ClearQueryParamCommand(new NullTelemetry()); bool? result = clearQueryParamCommand.CanHandle(shellState, httpState, parseResult); Assert.True(result); } [Fact] public void GetHelpSummary_ReturnsDescription() { ArrangeInputs(parseResultSections: string.Empty, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult _); ClearQueryParamCommand clearQueryParamCommand = new ClearQueryParamCommand(new NullTelemetry()); string result = clearQueryParamCommand.GetHelpSummary(shellState, httpState); Assert.Equal(ClearQueryParamCommand.Description, result); } [Fact] public void GetHelpDetails_ReturnsHelpDetails() { ArrangeInputs(parseResultSections: "clear query-param", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); string expected = "\u001b[1mUsage: \u001b[39mclear query-param" + Environment.NewLine + Environment.NewLine + "Clears the query string of all key and values" + Environment.NewLine; ClearQueryParamCommand clearQueryParamCommand = new ClearQueryParamCommand(new NullTelemetry()); string result = clearQueryParamCommand.GetHelpDetails(shellState, httpState, parseResult); Assert.Equal(expected, result); } [Fact] public async Task ClearQueryParamCommandWorkingAsync() { ArrangeInputs(parseResultSections: "add query-param limit 5", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); AddQueryParamCommand addQueryParamCommand = new AddQueryParamCommand(new NullTelemetry()); await addQueryParamCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Dictionary> queryParamAfterAdd = httpState.QueryParam; Assert.True(queryParamAfterAdd.ContainsKey("limit")); ClearQueryParamCommand clearQueryParamCommand = new ClearQueryParamCommand(new NullTelemetry()); await clearQueryParamCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Dictionary> queryParamAfterClear = httpState.QueryParam; Assert.True(queryParamAfterClear.ContainsKey("limit")); } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/Commands/CommandTestsBase.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Net.Http; using Microsoft.HttpRepl.Fakes; using Microsoft.HttpRepl.Preferences; using Microsoft.Repl; using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Parsing; using Moq; namespace Microsoft.HttpRepl.Tests.Commands { public class CommandTestsBase { protected static void ArrangeInputs(string parseResultSections, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, string baseAddress = "", int caretPosition = -1, string responseContent = "") { parseResult = CoreParseResultHelper.Create(parseResultSections, caretPosition); shellState = new MockedShellState(); IDictionary urlsWithResponse = new Dictionary(); urlsWithResponse.Add(baseAddress, responseContent); httpState = GetHttpState(out _, out _, urlsWithResponse: urlsWithResponse); } protected void ArrangeInputs(string commandText, string baseAddress, string path, IDictionary urlsWithResponse, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences, string header = "", bool readBodyFromFile = false, string fileContents = "", string contentType = "") { parseResult = CoreParseResultHelper.Create(commandText); shellState = new MockedShellState(); httpState = GetHttpState(out fileSystem, out preferences, baseAddress, header, path, urlsWithResponse, readBodyFromFile, fileContents, contentType); } protected void ArrangeInputsWithOptional(string commandText, string baseAddress, string path, IDictionary urlsWithResponse, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, ref MockedFileSystem fileSystem, ref IPreferences preferences, string header = "", bool readBodyFromFile = false, string fileContents = "", string contentType = "") { parseResult = CoreParseResultHelper.Create(commandText); shellState = new MockedShellState(); httpState = GetHttpStateWithOptional(ref fileSystem, ref preferences, baseAddress, header, path, urlsWithResponse, readBodyFromFile, fileContents, contentType); } protected static void VerifyErrorMessageWasWrittenToConsoleManagerError(IShellState shellState) { Mock error = Mock.Get(shellState.ConsoleManager.Error); error.Verify(s => s.WriteLine(It.IsAny()), Times.Once); } protected static HttpState GetHttpStateWithOptional(ref MockedFileSystem fileSystem, ref IPreferences preferences, string baseAddress = "", string header = "", string path = "", IDictionary urlsWithResponse = null, bool readFromFile = false, string fileContents = "", string contentType = "") { HttpResponseMessage responseMessage = new HttpResponseMessage(); responseMessage.Content = new MockHttpContent(string.Empty); MockHttpMessageHandler messageHandler = new MockHttpMessageHandler(urlsWithResponse, header, readFromFile, fileContents, contentType); HttpClient httpClient = new HttpClient(messageHandler); fileSystem ??= new MockedFileSystem(); preferences ??= new NullPreferences(); HttpState httpState = new HttpState(preferences, httpClient); if (!string.IsNullOrWhiteSpace(baseAddress)) { httpState.BaseAddress = new Uri(baseAddress); } if (!string.IsNullOrWhiteSpace(path)) { httpState.BaseAddress = new Uri(baseAddress); string[] pathParts = path.Split('/'); foreach (string pathPart in pathParts) { httpState.PathSections.Push(pathPart); } } return httpState; } protected static HttpState GetHttpState(out MockedFileSystem fileSystem, out IPreferences preferences, string baseAddress = "", string header = "", string path = "", IDictionary urlsWithResponse = null, bool readFromFile = false, string fileContents = "", string contentType = "") { fileSystem = new MockedFileSystem(); preferences = new NullPreferences(); return GetHttpStateWithOptional(ref fileSystem, ref preferences, baseAddress, header, path, urlsWithResponse, readFromFile, fileContents, contentType); } protected ICoreParseResult CreateCoreParseResult(string commandText) { return CoreParseResultHelper.Create(commandText); } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/Commands/ConnectCommandTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.HttpRepl.Commands; using Microsoft.HttpRepl.Fakes; using Microsoft.HttpRepl.Preferences; using Microsoft.HttpRepl.UserProfile; using Microsoft.Repl.Parsing; using Xunit; namespace Microsoft.HttpRepl.Tests.Commands { public class ConnectCommandTests : CommandTestsBase { [Fact] public async Task ExecuteAsync_WithNothingSpecified_ShowsError() { ArrangeInputs("connect", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out IPreferences preferences); ConnectCommand connectCommand = new ConnectCommand(preferences, new NullTelemetry()); await connectCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.Equal(Resources.Strings.ConnectCommand_Error_NothingSpecified, shellState.ErrorMessage); } [Fact] public async Task ExecuteAsync_WithInvalidRoot_ShowsError() { ArrangeInputs("connect example.com", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out IPreferences preferences); ConnectCommand connectCommand = new ConnectCommand(preferences, new NullTelemetry()); await connectCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.Equal(Resources.Strings.ConnectCommand_Error_RootAddressNotValid, shellState.ErrorMessage); } [Fact] public async Task ExecuteAsync_WithNoRootAndRelativeBase_ShowsError() { ArrangeInputs("connect --base /v1", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out IPreferences preferences); ConnectCommand connectCommand = new ConnectCommand(preferences, new NullTelemetry()); await connectCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.Equal(Resources.Strings.ConnectCommand_Error_NoRootNoAbsoluteBase, shellState.ErrorMessage); } [Fact] public async Task ExecuteAsync_WithNoRootAndRelativeSwagger_ShowsError() { ArrangeInputs("connect --swagger /v1/swagger.json", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out IPreferences preferences); ConnectCommand connectCommand = new ConnectCommand(preferences, new NullTelemetry()); await connectCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.Equal(Resources.Strings.ConnectCommand_Error_NoRootNoAbsoluteSwagger, shellState.ErrorMessage); } [Fact] public async Task ExecuteAsync_WithNoRootAndRelativeOpenApi_ShowsError() { ArrangeInputs("connect --openapi /v1/openapi.json", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out IPreferences preferences); ConnectCommand connectCommand = new ConnectCommand(preferences, new NullTelemetry()); await connectCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.Equal(Resources.Strings.ConnectCommand_Error_NoRootNoAbsoluteSwagger, shellState.ErrorMessage); } [Fact] public async Task ExecuteAsync_WithRootAndNoBase_SetsBaseToRoot() { string rootAddress = "https://localhost/"; ArrangeInputs($"connect {rootAddress}", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out IPreferences preferences, fileContents: ""); ConnectCommand connectCommand = new ConnectCommand(preferences, new NullTelemetry()); await connectCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.Equal(rootAddress, httpState.BaseAddress?.ToString()); } [Fact] public async Task ExecuteAsync_WithJustRoot_SetsBaseAndDefinition() { string rootAddress = "https://localhost/"; string swaggerAddress = "https://localhost/swagger.json"; string swaggerContent = @"{ ""swagger"": ""2.0"", ""info"": { ""title"": ""OpenAPI v2 Spec"", ""version"": ""v1"" }, ""paths"": { ""/api/Values"": { ""post"": { } } } }"; ArrangeInputs(commandText: $"connect {rootAddress}", baseAddress: null, path: null, urlsWithResponse: new Dictionary() { { swaggerAddress, swaggerContent } }, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out _, out IPreferences preferences); ConnectCommand connectCommand = new ConnectCommand(preferences, new NullTelemetry()); await connectCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.NotNull(httpState.BaseAddress); Assert.Equal(rootAddress, httpState.BaseAddress.ToString()); Assert.NotNull(httpState.SwaggerEndpoint); Assert.Equal(swaggerAddress, httpState.SwaggerEndpoint.ToString()); Assert.NotNull(httpState.ApiDefinition); } [Fact] public async Task ExecuteAsync_WithJustBase_FindsSwaggerSetsDefinition() { string baseAddress = "https://localhost/"; string swaggerAddress = "https://localhost/swagger.json"; string swaggerContent = @"{ ""swagger"": ""2.0"", ""info"": { ""title"": ""OpenAPI v2 Spec"", ""version"": ""v1"" }, ""paths"": { ""/api/Values"": { ""post"": { } } } }"; ArrangeInputs(commandText: $"connect --base {baseAddress}", baseAddress: null, path: null, urlsWithResponse: new Dictionary() { { swaggerAddress, swaggerContent } }, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out _, out IPreferences preferences); ConnectCommand connectCommand = new ConnectCommand(preferences, new NullTelemetry()); await connectCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.NotNull(httpState.BaseAddress); Assert.Equal(baseAddress, httpState.BaseAddress.ToString()); Assert.NotNull(httpState.SwaggerEndpoint); Assert.Equal(swaggerAddress, httpState.SwaggerEndpoint.ToString()); Assert.NotNull(httpState.ApiDefinition); } [Fact] public async Task ExecuteAsync_WithJustSwagger_SetsBaseAndDefinition() { string baseAddress = "https://localhost/"; string swaggerAddress = "https://localhost/swagger.json"; string swaggerContent = @"{ ""openapi"": ""3.0.0"", ""info"": { ""title"": ""OpenAPI v3 Spec"", ""version"": ""v1"" }, ""servers"": [ { ""url"": """ + baseAddress + @""", ""description"": ""First Server Address"" } ], ""paths"": { ""/pets"": { } } }"; ArrangeInputs(commandText: $"connect --swagger {swaggerAddress}", baseAddress: null, path: null, urlsWithResponse: new Dictionary() { { swaggerAddress, swaggerContent } }, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out _, out IPreferences preferences); ConnectCommand connectCommand = new ConnectCommand(preferences, new NullTelemetry()); await connectCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.NotNull(httpState.BaseAddress); Assert.Equal(baseAddress, httpState.BaseAddress.ToString()); Assert.NotNull(httpState.SwaggerEndpoint); Assert.Equal(swaggerAddress, httpState.SwaggerEndpoint.ToString()); Assert.NotNull(httpState.ApiDefinition); } [Fact] public async Task ExecuteAsync_WithBaseAndSwagger_SetsBaseAndDefinitionIgnoresSwaggerBase() { string baseAddress = "https://localhost/"; string swaggerAddress = "https://localhost/swagger.json"; string swaggerContent = @"{ ""openapi"": ""3.0.0"", ""info"": { ""title"": ""OpenAPI v3 Spec"", ""version"": ""v1"" }, ""servers"": [ { ""url"": ""https://example.com/"", ""description"": ""First Server Address"" } ], ""paths"": { ""/pets"": { } } }"; ArrangeInputs(commandText: $"connect --base {baseAddress} --swagger {swaggerAddress}", baseAddress: null, path: null, urlsWithResponse: new Dictionary() { { swaggerAddress, swaggerContent } }, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out _, out IPreferences preferences); ConnectCommand connectCommand = new ConnectCommand(preferences, new NullTelemetry()); await connectCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.NotNull(httpState.BaseAddress); Assert.Equal(baseAddress, httpState.BaseAddress.ToString()); Assert.NotNull(httpState.SwaggerEndpoint); Assert.Equal(swaggerAddress, httpState.SwaggerEndpoint.ToString()); Assert.NotNull(httpState.ApiDefinition); } [Fact] public async Task ExecuteAsync_WithRootAndBase_FindsSwaggerFromRoot() { string rootAddress = "https://localhost/"; string baseAddress = "https://localhost/v2/"; string swaggerAddress = "https://localhost/swagger.json"; string swaggerContent = @"{ ""openapi"": ""3.0.0"", ""info"": { ""title"": ""OpenAPI v3 Spec"", ""version"": ""v1"" }, ""servers"": [ { ""url"": ""https://example.com/"", ""description"": ""First Server Address"" } ], ""paths"": { ""/pets"": { } } }"; ArrangeInputs(commandText: $"connect {rootAddress} --base {baseAddress}", baseAddress: null, path: null, urlsWithResponse: new Dictionary() { { swaggerAddress, swaggerContent } }, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out _, out IPreferences preferences); ConnectCommand connectCommand = new ConnectCommand(preferences, new NullTelemetry()); await connectCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.NotNull(httpState.BaseAddress); Assert.Equal(baseAddress, httpState.BaseAddress.ToString()); Assert.NotNull(httpState.SwaggerEndpoint); Assert.Equal(swaggerAddress, httpState.SwaggerEndpoint.ToString()); Assert.NotNull(httpState.ApiDefinition); } [Fact] public async Task ExecuteAsync_WithRootAndBase_FindsSwaggerFromBase() { string rootAddress = "https://localhost/"; string baseAddress = "https://localhost/v2/"; string swaggerAddress = "https://localhost/v2/swagger.json"; string swaggerContent = @"{ ""openapi"": ""3.0.0"", ""info"": { ""title"": ""OpenAPI v3 Spec"", ""version"": ""v1"" }, ""servers"": [ { ""url"": ""https://example.com/"", ""description"": ""First Server Address"" } ], ""paths"": { ""/pets"": { } } }"; ArrangeInputs(commandText: $"connect {rootAddress} --base {baseAddress}", baseAddress: null, path: null, urlsWithResponse: new Dictionary() { { swaggerAddress, swaggerContent } }, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out _, out IPreferences preferences); ConnectCommand connectCommand = new ConnectCommand(preferences, new NullTelemetry()); await connectCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.NotNull(httpState.BaseAddress); Assert.Equal(baseAddress, httpState.BaseAddress.ToString()); Assert.NotNull(httpState.SwaggerEndpoint); Assert.Equal(swaggerAddress, httpState.SwaggerEndpoint.ToString()); Assert.NotNull(httpState.ApiDefinition); } [Fact] public async Task ExecuteAsync_WithRootAndBase_FindsOpenApiFromRoot() { string rootAddress = "https://localhost/"; string baseAddress = "https://localhost/v2/"; string swaggerAddress = "https://localhost/openapi.json"; string swaggerContent = @"{ ""openapi"": ""3.0.0"", ""info"": { ""title"": ""OpenAPI v3 Spec"", ""version"": ""v1"" }, ""servers"": [ { ""url"": ""https://example.com/"", ""description"": ""First Server Address"" } ], ""paths"": { ""/pets"": { } } }"; ArrangeInputs(commandText: $"connect {rootAddress} --base {baseAddress}", baseAddress: null, path: null, urlsWithResponse: new Dictionary() { { swaggerAddress, swaggerContent } }, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out _, out IPreferences preferences); ConnectCommand connectCommand = new ConnectCommand(preferences, new NullTelemetry()); await connectCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.NotNull(httpState.BaseAddress); Assert.Equal(baseAddress, httpState.BaseAddress.ToString()); Assert.NotNull(httpState.SwaggerEndpoint); Assert.Equal(swaggerAddress, httpState.SwaggerEndpoint.ToString()); Assert.NotNull(httpState.ApiDefinition); } [Fact] public async Task ExecuteAsync_WithRootAndBase_FindsOpenApiFromBase() { string rootAddress = "https://localhost/"; string baseAddress = "https://localhost/v2/"; string swaggerAddress = "https://localhost/v2/openapi.json"; string swaggerContent = @"{ ""openapi"": ""3.0.0"", ""info"": { ""title"": ""OpenAPI v3 Spec"", ""version"": ""v1"" }, ""servers"": [ { ""url"": ""https://example.com/"", ""description"": ""First Server Address"" } ], ""paths"": { ""/pets"": { } } }"; ArrangeInputs(commandText: $"connect {rootAddress} --base {baseAddress}", baseAddress: null, path: null, urlsWithResponse: new Dictionary() { { swaggerAddress, swaggerContent } }, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out _, out IPreferences preferences); ConnectCommand connectCommand = new ConnectCommand(preferences, new NullTelemetry()); await connectCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.NotNull(httpState.BaseAddress); Assert.Equal(baseAddress, httpState.BaseAddress.ToString()); Assert.NotNull(httpState.SwaggerEndpoint); Assert.Equal(swaggerAddress, httpState.SwaggerEndpoint.ToString()); Assert.NotNull(httpState.ApiDefinition); } [Fact] public async Task ExecuteAsync_WithRootAndSwaggerWithoutBase_SetsBaseToRoot() { string rootAddress = "https://localhost/"; string swaggerAddress = "https://localhost/v2/swagger.json"; string swaggerContent = @"{ ""openapi"": ""3.0.0"", ""info"": { ""title"": ""OpenAPI v3 Spec"", ""version"": ""v1"" }, ""paths"": { ""/pets"": { } } }"; ArrangeInputs(commandText: $"connect {rootAddress} --swagger {swaggerAddress}", baseAddress: null, path: null, urlsWithResponse: new Dictionary() { { swaggerAddress, swaggerContent } }, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out _, out IPreferences preferences); ConnectCommand connectCommand = new ConnectCommand(preferences, new NullTelemetry()); await connectCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.NotNull(httpState.BaseAddress); Assert.Equal(rootAddress, httpState.BaseAddress.ToString()); Assert.NotNull(httpState.SwaggerEndpoint); Assert.Equal(swaggerAddress, httpState.SwaggerEndpoint.ToString()); Assert.NotNull(httpState.ApiDefinition); } [Fact] public async Task ExecuteAsync_WithRootAndSwaggerWithBase_SetsBaseToSwaggerBase() { string rootAddress = "https://localhost/"; string baseAddress = "https://localhost/v2/"; string swaggerAddress = "https://localhost/v2/swagger.json"; string swaggerContent = @"{ ""openapi"": ""3.0.0"", ""info"": { ""title"": ""OpenAPI v3 Spec"", ""version"": ""v1"" }, ""servers"": [ { ""url"": """ + baseAddress + @""", ""description"": ""First Server Address"" } ], ""paths"": { ""/pets"": { } } }"; ArrangeInputs(commandText: $"connect {rootAddress} --swagger {swaggerAddress}", baseAddress: null, path: null, urlsWithResponse: new Dictionary() { { swaggerAddress, swaggerContent } }, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out _, out IPreferences preferences); ConnectCommand connectCommand = new ConnectCommand(preferences, new NullTelemetry()); await connectCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.NotNull(httpState.BaseAddress); Assert.Equal(baseAddress, httpState.BaseAddress.ToString()); Assert.NotNull(httpState.SwaggerEndpoint); Assert.Equal(swaggerAddress, httpState.SwaggerEndpoint.ToString()); Assert.NotNull(httpState.ApiDefinition); } [Fact] public async Task ExecuteAsync_SwaggerOnlyWithNoBase_ShowsWarning() { string swaggerAddress = "https://localhost/v2/swagger.json"; string swaggerContent = @"{ ""openapi"": ""3.0.0"", ""info"": { ""title"": ""OpenAPI v3 Spec"", ""version"": ""v1"" }, ""paths"": { ""/pets"": { } } }"; ArrangeInputs(commandText: $"connect --swagger {swaggerAddress}", baseAddress: null, path: null, urlsWithResponse: new Dictionary() { { swaggerAddress, swaggerContent } }, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out _, out IPreferences preferences); ConnectCommand connectCommand = new ConnectCommand(preferences, new NullTelemetry()); await connectCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.Null(httpState.BaseAddress); Assert.Contains(Resources.Strings.ConnectCommand_Status_NoBase, shellState.Output, StringComparer.Ordinal); } [Fact] public async Task ExecuteAsync_RootOnlyNoSwaggerFound_ShowsWarning() { string rootAddress = "https://localhost/"; ArrangeInputs(commandText: $"connect {rootAddress}", baseAddress: null, path: null, urlsWithResponse: null, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out _, out IPreferences preferences); ConnectCommand connectCommand = new ConnectCommand(preferences, new NullTelemetry()); await connectCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.Null(httpState.SwaggerEndpoint); Assert.Contains(Resources.Strings.ConnectCommand_Status_NoSwagger, shellState.Output, StringComparer.Ordinal); } [Fact] public async Task ExecuteAsync_SwaggerOnlyWithBase_ShowsBaseAndSwaggerResult() { string baseAddress = "https://localhost/v2/"; string swaggerAddress = "https://localhost/v2/swagger.json"; string swaggerContent = @"{ ""openapi"": ""3.0.0"", ""info"": { ""title"": ""OpenAPI v3 Spec"", ""version"": ""v1"" }, ""servers"": [ { ""url"": """ + baseAddress + @""", ""description"": ""First Server Address"" } ], ""paths"": { ""/pets"": { } } }"; ArrangeInputs(commandText: $"connect --swagger {swaggerAddress}", baseAddress: null, path: null, urlsWithResponse: new Dictionary() { { swaggerAddress, swaggerContent } }, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out _, out IPreferences preferences); ConnectCommand connectCommand = new ConnectCommand(preferences, new NullTelemetry()); await connectCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.NotNull(httpState.BaseAddress); Assert.Contains(string.Format(Resources.Strings.ConnectCommand_Status_Base, httpState.BaseAddress), shellState.Output, StringComparer.Ordinal); Assert.NotNull(httpState.SwaggerEndpoint); Assert.Contains(string.Format(Resources.Strings.ConnectCommand_Status_Swagger, httpState.SwaggerEndpoint), shellState.Output, StringComparer.Ordinal); } [Fact] public async Task ExecuteAsync_RootOnlyWithVerbose_OutputContainsAttempts() { string rootAddress = "https://localhost/v2"; ArrangeInputs(commandText: $"connect {rootAddress} --verbose", baseAddress: null, path: null, urlsWithResponse: new Dictionary(), out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out _, out IPreferences preferences); ConnectCommand connectCommand = new ConnectCommand(preferences, new NullTelemetry()); await connectCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.Contains(Resources.Strings.ApiConnection_Logging_Parsing + Resources.Strings.ApiConnection_Logging_Failed, shellState.Output, StringComparer.Ordinal); } [Fact] public async Task ExecuteAsync_RootOnlyWithoutVerbose_OutputDoesNotContainAttempts() { string rootAddress = "https://localhost/v2"; ArrangeInputs(commandText: $"connect {rootAddress}", baseAddress: null, path: null, urlsWithResponse: new Dictionary(), out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out _, out IPreferences preferences); ConnectCommand connectCommand = new ConnectCommand(preferences, new NullTelemetry()); await connectCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.DoesNotContain(Resources.Strings.ApiConnection_Logging_Parsing + Resources.Strings.ApiConnection_Logging_Failed, shellState.Output, StringComparer.Ordinal); } [Fact] public async Task ExecuteAsync_SwaggerOnlyWithoutVerbose_OutputContainsAttempts() { string openApiDescriptionUrl = "https://localhost/v2/swagger.json"; ArrangeInputs(commandText: $"connect --openapi {openApiDescriptionUrl}", baseAddress: null, path: null, urlsWithResponse: new Dictionary(), out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out _, out IPreferences preferences); ConnectCommand connectCommand = new ConnectCommand(preferences, new NullTelemetry()); await connectCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.Contains(Resources.Strings.ApiConnection_Logging_Parsing + Resources.Strings.ApiConnection_Logging_Failed, shellState.Output, StringComparer.Ordinal); } [Fact] public async Task ExecuteAsync_RootWithSwaggerSuffix_FixesBase() { string rootAddress = "https://localhost:44368/swagger"; string expectedBaseAddress = "https://localhost:44368/"; string expectedSwaggerAddress = "https://localhost:44368/swagger/v1/swagger.json"; string swaggerContent = @"{ ""openapi"": ""3.0.0"", ""info"": { ""title"": ""OpenAPI v3 Spec"", ""version"": ""v1"" }, ""paths"": { ""/WeatherForecast"": { } } }"; ArrangeInputs(commandText: $"connect {rootAddress}", baseAddress: null, path: null, urlsWithResponse: new Dictionary() { { expectedSwaggerAddress, swaggerContent } }, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out _, out IPreferences preferences); ConnectCommand connectCommand = new ConnectCommand(preferences, new NullTelemetry()); await connectCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.Contains(string.Format(Resources.Strings.ConnectCommand_Status_Base, expectedBaseAddress), shellState.Output, StringComparer.Ordinal); Assert.Contains(string.Format(Resources.Strings.ConnectCommand_Status_Swagger, expectedSwaggerAddress), shellState.Output, StringComparer.Ordinal); } [Fact] public async Task ExecuteAsync_RootWithSwaggerSuffixAndOverride_DoesNotFixBase() { string rootAddress = "https://localhost:44368/swagger"; string expectedBaseAddress = rootAddress + "/"; string expectedSwaggerAddress = "https://localhost:44368/swagger/v1/swagger.json"; string swaggerContent = @"{ ""openapi"": ""3.0.0"", ""info"": { ""title"": ""OpenAPI v3 Spec"", ""version"": ""v1"" }, ""paths"": { ""/WeatherForecast"": { } } }"; MockedFileSystem fileSystem = new MockedFileSystem(); UserProfileDirectoryProvider userProfileDirectoryProvider = new UserProfileDirectoryProvider(); IPreferences preferences = new UserFolderPreferences(fileSystem, userProfileDirectoryProvider, new Dictionary { { WellKnownPreference.ConnectCommandSkipRootFix, "true"}}); ArrangeInputsWithOptional(commandText: $"connect {rootAddress}", baseAddress: null, path: null, urlsWithResponse: new Dictionary() { { expectedSwaggerAddress, swaggerContent } }, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, ref fileSystem, ref preferences); ConnectCommand connectCommand = new ConnectCommand(preferences, new NullTelemetry()); await connectCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.Contains(string.Format(Resources.Strings.ConnectCommand_Status_Base, expectedBaseAddress), shellState.Output, StringComparer.Ordinal); Assert.Contains(string.Format(Resources.Strings.ConnectCommand_Status_Swagger, expectedSwaggerAddress), shellState.Output, StringComparer.Ordinal); } [Fact] public async Task ExecuteAsync_WithOnlyRoot_SendsTelemetry() { string rootAddress = "https://localhost/"; ArrangeInputs($"connect {rootAddress}", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out IPreferences preferences, fileContents: ""); TelemetryCollector telemetry = new TelemetryCollector(); ConnectCommand connectCommand = new ConnectCommand(preferences, telemetry); await connectCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.Single(telemetry.Telemetry); TelemetryCollector.CollectedTelemetry collectedTelemetry = telemetry.Telemetry[0]; Assert.Equal("connect", collectedTelemetry.EventName, ignoreCase: true); Assert.Equal("True", collectedTelemetry.Properties["RootSpecified"]); Assert.Equal("False", collectedTelemetry.Properties["BaseSpecified"]); Assert.Equal("False", collectedTelemetry.Properties["OpenApiSpecified"]); Assert.Equal("False", collectedTelemetry.Properties["OpenApiFound"]); } [Fact] public async Task ExecuteAsync_WithoutPersistHeaders_DoesNotPersistNonDefaultHeaders() { string rootAddress = "https://localhost/"; ArrangeInputs($"connect {rootAddress}", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out IPreferences preferences, fileContents: ""); httpState.Headers["TestHeaderName"] = new[] { "TestHeaderValue" }; ConnectCommand connectCommand = new(preferences, new NullTelemetry()); await connectCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.Single(httpState.Headers); Assert.True(httpState.Headers.ContainsKey("User-Agent")); } [Fact] public async Task ExecuteAsync_WithoutPersistPaths_DoesNotPersistPaths() { string rootAddress = "https://localhost/"; ArrangeInputs($"connect {rootAddress}", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out IPreferences preferences, fileContents: ""); httpState.PathSections.Push("dir1"); ConnectCommand connectCommand = new(preferences, new NullTelemetry()); await connectCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.Empty(httpState.PathSections); } [Fact] public async Task ExecuteAsync_WithPersistHeaders_PersistsHeaders() { string expectedHeaderName = "TestHeaderName"; string expectedHeaderValue = "TestHeaderValue"; string rootAddress = "https://localhost/"; ArrangeInputs($"connect {rootAddress} --persist-headers", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out IPreferences preferences, fileContents: ""); httpState.Headers[expectedHeaderName] = new[] { expectedHeaderValue }; ConnectCommand connectCommand = new(preferences, new NullTelemetry()); await connectCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.True(httpState.Headers.ContainsKey(expectedHeaderName)); Assert.Equal(expectedHeaderValue, httpState.Headers[expectedHeaderName].Single()); } [Fact] public async Task ExecuteAsync_WithPersistPaths_PersistsPaths() { string expectedPathSection = "dir1"; string rootAddress = "https://localhost/"; ArrangeInputs($"connect {rootAddress} --persist-paths", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out IPreferences preferences, fileContents: ""); httpState.PathSections.Push(expectedPathSection); ConnectCommand connectCommand = new(preferences, new NullTelemetry()); await connectCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.Single(httpState.PathSections); Assert.Equal(httpState.PathSections.Peek(), expectedPathSection); } private void ArrangeInputs(string commandText, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out IPreferences preferences, string fileContents = null) { ArrangeInputs(commandText, baseAddress: null, path: null, urlsWithResponse: null, out shellState, out httpState, out parseResult, out _, out preferences, readBodyFromFile: fileContents is object, fileContents: fileContents); } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/Commands/CoreParseResultHelper.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using Microsoft.Repl.Parsing; namespace Microsoft.HttpRepl.Tests.Commands { internal static class CoreParseResultHelper { public static ICoreParseResult Create(string commandText, int caretPosition = -1) { if (commandText == null) { throw new ArgumentNullException(nameof(commandText)); } if (caretPosition == -1) { caretPosition = commandText.Length; } CoreParser coreParser = new CoreParser(); ICoreParseResult parseResult = coreParser.Parse(commandText, caretPosition); return parseResult; } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/Commands/DeleteCommandsTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.HttpRepl.Commands; using Microsoft.HttpRepl.Fakes; using Microsoft.HttpRepl.Preferences; using Microsoft.HttpRepl.Resources; using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Parsing; using Xunit; namespace Microsoft.HttpRepl.Tests.Commands { public class DeleteCommandTests : CommandTestsBase { private string _baseAddress; private string _path; private IDictionary _urlsWithResponse = new Dictionary(); public DeleteCommandTests() { _baseAddress = "http://localhost:5050/"; _path = "a/file/path.txt"; _urlsWithResponse.Add(_baseAddress, "Root delete received successfully."); _urlsWithResponse.Add(_baseAddress + _path, "File path delete received successfully."); } [Fact] public async Task ExecuteAsync_WithNoBasePath_VerifyError() { ArrangeInputs(commandText: "DELETE", baseAddress: null, path: null, urlsWithResponse: null, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences); string expectedErrorMessage = Strings.Error_NoBasePath.SetColor(httpState.ErrorColor); DeleteCommand deleteCommand = new DeleteCommand(fileSystem, preferences, new NullTelemetry()); await deleteCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.Equal(expectedErrorMessage, shellState.ErrorMessage); } [Fact] public async Task ExecuteAsync_WithMultipartRoute_VerifyOutput() { ArrangeInputs(commandText: "DELETE", baseAddress: _baseAddress, path: _path, urlsWithResponse: _urlsWithResponse, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences); DeleteCommand deleteCommand = new DeleteCommand(fileSystem, preferences, new NullTelemetry()); await deleteCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); string expectedResponse = "File path delete received successfully."; List result = shellState.Output; Assert.Equal(2, result.Count); Assert.Contains("HTTP/1.1 200 OK", result); Assert.Contains(expectedResponse, result); } [Fact] public async Task ExecuteAsync_WithOnlyBaseAddress_VerifyOutput() { ArrangeInputs(commandText: "DELETE", baseAddress: _baseAddress, path: null, urlsWithResponse: _urlsWithResponse, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences); DeleteCommand deleteCommand = new DeleteCommand(fileSystem, preferences, new NullTelemetry()); await deleteCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); string expectedResponse = "Root delete received successfully."; List result = shellState.Output; Assert.Equal(2, result.Count); Assert.Contains("HTTP/1.1 200 OK", result); Assert.Contains(expectedResponse, result); } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/Commands/EchoCommandTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.HttpRepl.Commands; using Microsoft.HttpRepl.Fakes; using Microsoft.HttpRepl.Preferences; using Microsoft.Repl.Parsing; using Xunit; namespace Microsoft.HttpRepl.Tests.Commands { public class EchoCommandTests : CommandTestsBase { [Theory] [InlineData("echo ON", true)] [InlineData("echo on", true)] [InlineData("echo oN", true)] [InlineData("echo OFF", true)] [InlineData("echo off", true)] [InlineData("echo oFf", true)] [InlineData("echo no", false)] [InlineData("echo yes", false)] public void CanHandle(string commandText, bool expected) { ArrangeInputs(commandText, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); EchoCommand echoCommand = new EchoCommand(); bool? result = echoCommand.CanHandle(shellState, httpState, parseResult); Assert.Equal(expected, result); } [Theory] [InlineData("echo o", "on", "off")] [InlineData("echo O", "on", "off")] [InlineData("echo on", "on")] [InlineData("echo of", "off")] [InlineData("echo off", "off")] public void GetArgumentSuggestionsForText(string commandText, params string[] expectedResults) { ArrangeInputs(commandText, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); EchoCommand echoCommand = new EchoCommand(); IEnumerable result = echoCommand.Suggest(shellState, httpState, parseResult); Assert.NotNull(result); List resultList = result.ToList(); Assert.Equal(expectedResults.Length, resultList.Count); for (int index = 0; index < expectedResults.Length; index++) { Assert.Contains(expectedResults[index], resultList, StringComparer.OrdinalIgnoreCase); } } [Fact] public async Task PostCommand_WithEchoOn_OnlyPrintsRequestBodyOnce() { string baseAddress = "https://localhost/"; string path = "values/5"; string content = "Test Post Body"; ArrangeInputs(commandText: $"POST --content \"{content}\"", baseAddress: baseAddress, path: path, urlsWithResponse: new Dictionary() { { "https://localhost/values/5", "" } }, out var shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences); httpState.EchoRequest = true; PostCommand postCommand = new PostCommand(fileSystem, preferences, new NullTelemetry()); await postCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); List result = shellState.Output; int countOfOccurrences = result.Count(s => s?.Contains(content, StringComparison.Ordinal) == true); Assert.Equal(1, countOfOccurrences); } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/Commands/ExitCommandTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Threading; using System.Threading.Tasks; using Microsoft.HttpRepl.Commands; using Microsoft.HttpRepl.Fakes; using Microsoft.Repl.Parsing; using Xunit; namespace Microsoft.HttpRepl.Tests.Commands { public class ExitCommandTests : CommandTestsBase { [Fact] public async Task ExecuteAsync_Always_IsExitingIsTrue() { ArrangeInputs("exit", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); ExitCommand exitCommand = new ExitCommand(); await exitCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.True(shellState.IsExiting); } [Fact] public async Task ExecuteAsync_Always_DoesNotWritePrompt() { ArrangeInputs("exit", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); ExitCommand exitCommand = new ExitCommand(); await exitCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.Empty(shellState.Output); } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/Commands/GetCommandTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.HttpRepl.Commands; using Microsoft.HttpRepl.Fakes; using Microsoft.HttpRepl.Preferences; using Microsoft.HttpRepl.Resources; using Microsoft.HttpRepl.Telemetry; using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Parsing; using Xunit; namespace Microsoft.HttpRepl.Tests.Commands { public class GetCommandTests : CommandTestsBase { private string _baseAddress; private string _path; private IDictionary _urlsWithResponse = new Dictionary(); public GetCommandTests() { _baseAddress = "http://localhost:5050/"; _path = "this/is/a/test/route"; _urlsWithResponse.Add(_baseAddress, "This is a response from the root."); _urlsWithResponse.Add(_baseAddress + _path, "This is a test response."); } [Fact] public async Task ExecuteAsync_WithNoBasePath_VerifyError() { ArrangeInputs(commandText: "GET", baseAddress: null, path: null, urlsWithResponse: null, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences); string expectedErrorMessage = Strings.Error_NoBasePath.SetColor(httpState.ErrorColor); GetCommand getCommand = new GetCommand(fileSystem, preferences, new NullTelemetry()); await getCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.Equal(expectedErrorMessage, shellState.ErrorMessage); } [Fact] public async Task ExecuteAsync_WithMultipartRoute_VerifyOutput() { ArrangeInputs(commandText: "GET", baseAddress: _baseAddress, path: _path, urlsWithResponse: _urlsWithResponse, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences); GetCommand getCommand = new GetCommand(fileSystem, preferences, new NullTelemetry()); await getCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); string expectedResponse = "This is a test response."; List result = shellState.Output; Assert.Equal(2, result.Count); Assert.Equal("HTTP/1.1 200 OK", result[0]); Assert.Equal(expectedResponse, result[1]); } [Fact] public async Task ExecuteAsync_WithOnlyBaseAddress_VerifyOutput() { ArrangeInputs(commandText: "GET", baseAddress: _baseAddress, path: null, urlsWithResponse: _urlsWithResponse, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences); GetCommand getCommand = new GetCommand(fileSystem, preferences, new NullTelemetry()); await getCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); string expectedResponse = "This is a response from the root."; List result = shellState.Output; Assert.Equal(2, result.Count); Assert.Equal("HTTP/1.1 200 OK", result[0]); Assert.Equal(expectedResponse, result[1]); } [Fact] public async Task ExecuteAsync_WithJsonContentTypeInHeader_FormatsResponseContent() { string json = @"{ ""swagger"": ""2.0"", ""info"": { ""version"": ""v1"" }, ""paths"": { ""/api/Employees"": { } } }"; string path = "this/is/a/test/route/for/formatting"; _urlsWithResponse.Add(_baseAddress + path, json); ArrangeInputs(commandText: "GET", baseAddress: _baseAddress, path: path, urlsWithResponse: _urlsWithResponse, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences, contentType: "application/json"); GetCommand getCommand = new GetCommand(fileSystem, preferences, new NullTelemetry()); await getCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); int expectedHeaderLength = 2; string expectedResponse = @"{ ""swagger"": ""2.0"", ""info"": { ""version"": ""v1"" }, ""paths"": { ""/api/Employees"": { } } }"; string[] expectedResponseLines = expectedResponse.Split(Environment.NewLine); List result = shellState.Output; Assert.Equal(expectedHeaderLength + expectedResponseLines.Length, result.Count); Assert.Equal("HTTP/1.1 200 OK", result[0]); Assert.Equal("Content-Type: application/json", result[1]); for (int expectedIndex = 0; expectedIndex < expectedResponseLines.Length; expectedIndex++) { Assert.Equal(expectedResponseLines[expectedIndex], result[expectedIndex + expectedHeaderLength], StringComparer.Ordinal); } } [Fact] public async Task ExecuteAsync_WithTextContentTypeInHeader_DoesNotFormatResponseContent() { string unformattedResponse = "This is an unformatted response. "; string path = "this/is/a/test/route/for/formatting"; _urlsWithResponse.Add(_baseAddress + path, unformattedResponse); ArrangeInputs(commandText: "GET", baseAddress: _baseAddress, path: path, urlsWithResponse: _urlsWithResponse, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences, contentType: "text/plain"); GetCommand getCommand = new GetCommand(fileSystem, preferences, new NullTelemetry()); await getCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); List result = shellState.Output; Assert.Equal(3, result.Count); Assert.Equal("HTTP/1.1 200 OK", result[0]); Assert.Equal("Content-Type: text/plain", result[1]); Assert.Equal(unformattedResponse, result[2]); } [Fact] public async Task ExecuteAsync_WithHeaderOptionAndEchoOn_VerifyOutputContainsRequestAndReaponseHeaders() { string unformattedResponse = "This is a test response."; string path = "this/is/a/test/route/for/formatting"; _urlsWithResponse.Add(_baseAddress + path, unformattedResponse); ArrangeInputs(commandText: "GET --header Accept=text/plain", baseAddress: _baseAddress, path: path, urlsWithResponse: _urlsWithResponse, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences, contentType: "text/plain"); httpState.EchoRequest = true; GetCommand getCommand = new GetCommand(fileSystem, preferences, new NullTelemetry()); await getCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); List result = shellState.Output; Assert.Equal(8, result.Count); Assert.Equal("Request to http://localhost:5050...", result[0]); Assert.Equal("GET /this/is/a/test/route/for/formatting HTTP/1.1", result[1]); Assert.Equal("Accept: text/plain", result[2]); Assert.Equal("User-Agent: HTTP-REPL", result[3]); Assert.Equal("Response from http://localhost:5050...", result[4]); Assert.Equal("HTTP/1.1 200 OK", result[5]); Assert.Equal("Content-Type: text/plain", result[6]); Assert.Equal(unformattedResponse, result[7]); } [Fact] public async Task ExecuteAsync_WithSameHeadersAndBodyPaths_VerifyError() { // Arrange string fileName = "\"/myfile.txt\""; ArrangeInputs(commandText: $"GET --response:headers {fileName} --response:body {fileName}", baseAddress: _baseAddress, path: null, urlsWithResponse: null, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences); string expectedErrorMessage = Strings.BaseHttpCommand_Error_SameBodyAndHeaderFileName.SetColor(httpState.ErrorColor); GetCommand getCommand = new GetCommand(fileSystem, preferences, new NullTelemetry()); // Act await getCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); // Assert Assert.Equal(expectedErrorMessage, shellState.ErrorMessage); } [Fact] public async Task ExecuteAsync_WithPathAndOptions_SendsTelemetry() { // Arrange string expectedPath = "/path"; string expectedMethod = "GET"; ArrangeInputs(commandText: $"{expectedMethod} {expectedPath} --no-formatting --header Content-Length=20", baseAddress: _baseAddress, path: _path, urlsWithResponse: _urlsWithResponse, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences); TelemetryCollector telemetry = new TelemetryCollector(); GetCommand getCommand = new GetCommand(fileSystem, preferences, telemetry); // Act await getCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); // Assert Assert.Single(telemetry.Telemetry); TelemetryCollector.CollectedTelemetry collectedTelemetry = telemetry.Telemetry[0]; Assert.Equal(TelemetryEventNames.HttpCommand, collectedTelemetry.EventName, ignoreCase: true); Assert.Equal(expectedMethod, collectedTelemetry.Properties[TelemetryPropertyNames.HttpCommand_Method]); Assert.Equal("True", collectedTelemetry.Properties[TelemetryPropertyNames.HttpCommand_PathSpecified]); Assert.Equal("True", collectedTelemetry.Properties[TelemetryPropertyNames.HttpCommand_NoFormattingSpecified]); Assert.Equal("True", collectedTelemetry.Properties[TelemetryPropertyNames.HttpCommand_HeaderSpecified]); Assert.Equal("False", collectedTelemetry.Properties[TelemetryPropertyNames.HttpCommand_ResponseHeadersFileSpecified]); Assert.Equal("False", collectedTelemetry.Properties[TelemetryPropertyNames.HttpCommand_StreamingSpecified]); Assert.Equal("False", collectedTelemetry.Properties[TelemetryPropertyNames.HttpCommand_NoBodySpecified]); Assert.Equal("False", collectedTelemetry.Properties[TelemetryPropertyNames.HttpCommand_RequestBodyContentSpecified]); Assert.Equal("False", collectedTelemetry.Properties[TelemetryPropertyNames.HttpCommand_RequestBodyFileSpecified]); Assert.Equal("False", collectedTelemetry.Properties[TelemetryPropertyNames.HttpCommand_ResponseBodyFileSpecified]); } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/Commands/HeadCommandTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.HttpRepl.Commands; using Microsoft.HttpRepl.Fakes; using Microsoft.HttpRepl.Preferences; using Microsoft.HttpRepl.Resources; using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Parsing; using Xunit; namespace Microsoft.HttpRepl.Tests.Commands { public class HeadCommandTests : CommandTestsBase { private string _baseAddress; private string _path; private IDictionary _urlsWithResponse = new Dictionary(); public HeadCommandTests() { _baseAddress = "http://localhost:5050/"; _path = "this/is/a/test/route"; _urlsWithResponse.Add(_baseAddress, "Header value for root HEAD request."); _urlsWithResponse.Add(_baseAddress + _path, "Header value for HEAD request with route."); } [Fact] public async Task ExecuteAsync_WithNoBasePath_VerifyError() { ArrangeInputs(commandText: "HEAD", baseAddress: null, path: null, urlsWithResponse: null, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences); string expectedErrorMessage = Strings.Error_NoBasePath.SetColor(httpState.ErrorColor); HeadCommand headCommand = new HeadCommand(fileSystem, preferences, new NullTelemetry()); await headCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.Equal(expectedErrorMessage, shellState.ErrorMessage); } [Fact] public async Task ExecuteAsync_WithMultipartRoute_VerifyHeaders() { ArrangeInputs(commandText: "HEAD", baseAddress: _baseAddress, path: _path, urlsWithResponse: _urlsWithResponse, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences, header: "X-HTTPREPL-TESTHEADER"); HeadCommand headCommand = new HeadCommand(fileSystem, preferences, new NullTelemetry()); await headCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); string expectedHeader = "X-HTTPREPL-TESTHEADER: Header value for HEAD request with route."; List result = shellState.Output; Assert.True(result.Count >= 2); Assert.Contains("HTTP/1.1 200 OK", result); Assert.Contains(expectedHeader, result); } [Fact] public async Task ExecuteAsync_WithOnlyBaseAddress_VerifyHeaders() { ArrangeInputs(commandText: "HEAD", baseAddress: _baseAddress, path: null, urlsWithResponse: _urlsWithResponse, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences, header: "X-HTTPREPL-TESTHEADER"); HeadCommand headCommand = new HeadCommand(fileSystem, preferences, new NullTelemetry()); await headCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); string expectedHeader = "X-HTTPREPL-TESTHEADER: Header value for root HEAD request."; List result = shellState.Output; Assert.True(result.Count >= 2); Assert.Contains("HTTP/1.1 200 OK", result); Assert.Contains(expectedHeader, result); } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/Commands/HelpCommandTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using Microsoft.HttpRepl.Commands; using Microsoft.HttpRepl.Fakes; using Microsoft.HttpRepl.Preferences; using Microsoft.Repl; using Microsoft.Repl.Commanding; using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Parsing; using Xunit; namespace Microsoft.HttpRepl.Tests.Commands { public class HelpCommandTests : CommandTestsBase { [Theory] [InlineData("help", true)] [InlineData("HELP", true)] [InlineData("hElP", true)] [InlineData("hep", null)] public void CanHandle(string commandText, bool? expected) { HttpState httpState = GetHttpState(out _, out _); ICoreParseResult parseResult = CreateCoreParseResult(commandText); IShellState shellState = new MockedShellState(); HelpCommand helpCommand = new HelpCommand(); bool? result = helpCommand.CanHandle(shellState, httpState, parseResult); Assert.Equal(expected, result); } [Theory] [InlineData("help c", "clear", "cls", "cd")] [InlineData("help r", "run")] [InlineData("help z")] public void Suggest(string commandText, params string[] expectedResults) { HttpState httpState = GetHttpState(out MockedFileSystem fileSystem, out _); ICoreParseResult parseResult = CreateCoreParseResult(commandText); IConsoleManager consoleManager = new LoggingConsoleManagerDecorator(new NullConsoleManager()); DefaultCommandDispatcher commandDispatcher = DefaultCommandDispatcher.Create((ss) => { }, httpState); commandDispatcher.AddCommand(new ClearCommand()); commandDispatcher.AddCommand(new ChangeDirectoryCommand()); commandDispatcher.AddCommand(new RunCommand(fileSystem)); IShellState shellState = new ShellState(commandDispatcher, consoleManager: consoleManager); HelpCommand helpCommand = new HelpCommand(); IEnumerable result = helpCommand.Suggest(shellState, httpState, parseResult); Assert.NotNull(result); List resultList = result.ToList(); Assert.Equal(expectedResults.Length, resultList.Count); for (int index = 0; index < expectedResults.Length; index++) { Assert.Contains(expectedResults[index], resultList, StringComparer.OrdinalIgnoreCase); } } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/Commands/ListCommandTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.HttpRepl.Commands; using Microsoft.HttpRepl.Fakes; using Microsoft.HttpRepl.OpenApi; using Microsoft.HttpRepl.Preferences; using Microsoft.Repl.Commanding; using Microsoft.Repl.Parsing; using Xunit; namespace Microsoft.HttpRepl.Tests.Commands { public class ListCommandTests : CommandTestsBase { [Fact] public async Task ExecuteAsync_NoBaseAddressOnHttpState_ShowsWarning() { ArrangeInputs(commandText: "ls", baseAddress: "http://localhost/", path: "/", urlsWithResponse: null, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out _, out IPreferences preferences); httpState.BaseAddress = null; httpState.SwaggerEndpoint = new Uri("http://localhost/swagger.json"); ApiDefinition apiDefinition = new ApiDefinition() { DirectoryStructure = new DirectoryStructure(null) }; httpState.ApiDefinition = apiDefinition; ListCommand listCommand = new ListCommand(preferences); await listCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); string actualOutput = string.Join(Environment.NewLine, shellState.Output); Assert.Equal(Resources.Strings.ListCommand_Error_NoBaseAddress, actualOutput); } [Fact] public async Task ExecuteAsync_NoSwagger_ShowsWarning() { ArrangeInputs(commandText: "ls", baseAddress: "http://localhost/", path: "/", urlsWithResponse: null, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out _, out IPreferences preferences); httpState.SwaggerEndpoint = null; ApiDefinition apiDefinition = new ApiDefinition() { DirectoryStructure = new DirectoryStructure(null) }; httpState.ApiDefinition = apiDefinition; ListCommand listCommand = new ListCommand(preferences); await listCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); string actualOutput = string.Join(Environment.NewLine, shellState.Output); Assert.Equal(Resources.Strings.ListCommand_Error_NoDirectoryStructure, actualOutput); } [Fact] public async Task ExecuteAsync_NoStructure_ShowsWarning() { ArrangeInputs(commandText: "ls", baseAddress: "http://localhost/", path: "/", urlsWithResponse: null, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out _, out IPreferences preferences); httpState.SwaggerEndpoint = new Uri("http://localhost/swagger.json"); httpState.ApiDefinition = null; ListCommand listCommand = new ListCommand(preferences); await listCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); string actualOutput = string.Join(Environment.NewLine, shellState.Output); Assert.Equal(Resources.Strings.ListCommand_Error_NoDirectoryStructure, actualOutput); } [Fact] public async Task ExecuteAsync_WithBaseAddressSwaggerAndStructure_NoWarning() { string response = @"{ ""swagger"": ""2.0"", ""info"": { ""title"": ""OpenAPI v2 Spec"", ""version"": ""v1"" }, ""paths"": { ""/api"": { ""get"": { ""tags"": [ ""Employees"" ], ""operationId"": ""GetEmployee"", ""consumes"": [], ""produces"": [ ""text/plain"", ""application/json"", ""text/json"" ], ""parameters"": [], ""responses"": { ""200"": { ""description"": ""Success"", ""schema"": { ""uniqueItems"": false, ""type"": ""array"" } } } } } } }"; ArrangeInputs(commandText: "ls", baseAddress: "http://localhost/", path: "/", urlsWithResponse: new Dictionary() { { "http://localhost/swagger.json", response } }, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out _, out IPreferences preferences); httpState.SwaggerEndpoint = new Uri("http://localhost/swagger.json"); ListCommand listCommand = new ListCommand(preferences); await listCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); string actualOutput = string.Join(Environment.NewLine, shellState.Output); Assert.DoesNotContain(Resources.Strings.ListCommand_Error_NoBaseAddress, actualOutput, StringComparison.Ordinal); Assert.DoesNotContain(Resources.Strings.ListCommand_Error_NoDirectoryStructure, actualOutput, StringComparison.Ordinal); } [Fact] public void Suggest_WithBlankSecondParameter_NoExceptionAndCorrectSuggestions() { // Arrange ArrangeInputs(commandText: "dir ", baseAddress: "https://localhost/", path: "/", urlsWithResponse: null, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out _, out IPreferences preferences); ListCommand listCommand = new ListCommand(preferences); // Act List suggestions = listCommand.Suggest(shellState, httpState, parseResult).ToList(); // Assert for (int optionIndex = 0; optionIndex < listCommand.InputSpec.Options.Count; optionIndex++) { CommandOptionSpecification option = listCommand.InputSpec.Options[optionIndex]; for (int formIndex = 0; formIndex < option.Forms.Count; formIndex++) { Assert.Contains(option.Forms[formIndex], suggestions, StringComparer.Ordinal); } } } [Fact] public async Task ExecuteAsync_WithMethods_MethodsAreUppercase() { string response = @"{ ""swagger"": ""2.0"", ""info"": { ""title"": ""OpenAPI v2 Spec"", ""version"": ""v1"" }, ""paths"": { ""/api"": { ""get"": { }, ""post"": { } } } }"; ArrangeInputs(commandText: "ls", baseAddress: "http://localhost/", path: "/api", urlsWithResponse: new Dictionary() { { "http://localhost/swagger.json", response } }, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out _, out IPreferences preferences); httpState.SwaggerEndpoint = new Uri("http://localhost/swagger.json"); ListCommand listCommand = new ListCommand(preferences); await listCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); string actualOutput = string.Join(Environment.NewLine, shellState.Output); Assert.Contains("[GET|POST]", actualOutput, StringComparison.Ordinal); } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/Commands/OptionsCommandTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.HttpRepl.Commands; using Microsoft.HttpRepl.Fakes; using Microsoft.HttpRepl.Preferences; using Microsoft.HttpRepl.Resources; using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Parsing; using Xunit; namespace Microsoft.HttpRepl.Tests.Commands { public class OptionsCommandTests : CommandTestsBase { private string _baseAddress; private string _path; private IDictionary _urlsWithResponse = new Dictionary(); public OptionsCommandTests() { _baseAddress = "http://localhost:5050/"; _path = "this/is/a/test/route"; _urlsWithResponse.Add(_baseAddress, "Header value for root OPTIONS request."); _urlsWithResponse.Add(_baseAddress + _path, "Header value for OPTIONS request with route."); } [Fact] public async Task ExecuteAsync_WithNoBasePath_VerifyError() { ArrangeInputs(commandText: "OPTIONS", baseAddress: null, path: null, urlsWithResponse: null, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences); string expectedErrorMessage = Strings.Error_NoBasePath.SetColor(httpState.ErrorColor); OptionsCommand optionsCommand = new OptionsCommand(fileSystem, preferences, new NullTelemetry()); await optionsCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.Equal(expectedErrorMessage, shellState.ErrorMessage); } [Fact] public async Task ExecuteAsync_WithMultipartRoute_VerifyOutput() { ArrangeInputs(commandText: "OPTIONS", baseAddress: _baseAddress, path: _path, urlsWithResponse: _urlsWithResponse, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences, header: "X-HTTPREPL-TESTHEADER"); OptionsCommand optionsCommand = new OptionsCommand(fileSystem, preferences, new NullTelemetry()); await optionsCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); string expectedHeader = "X-HTTPREPL-TESTHEADER: Header value for OPTIONS request with route."; List result = shellState.Output; Assert.True(result.Count >= 2); Assert.Contains("HTTP/1.1 200 OK", result); Assert.Contains(expectedHeader, result); } [Fact] public async Task ExecuteAsync_WithOnlyBaseAddress_VerifyOutput() { ArrangeInputs(commandText: "Options", baseAddress: _baseAddress, path: null, urlsWithResponse: _urlsWithResponse, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences, header: "X-HTTPREPL-TESTHEADER"); OptionsCommand optionsCommand = new OptionsCommand(fileSystem, preferences, new NullTelemetry()); await optionsCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); string expectedHeader = "X-HTTPREPL-TESTHEADER: Header value for root OPTIONS request."; List result = shellState.Output; Assert.True(result.Count >= 2); Assert.Contains("HTTP/1.1 200 OK", result); Assert.Contains(expectedHeader, result); } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/Commands/PatchCommandTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.HttpRepl.Commands; using Microsoft.HttpRepl.Fakes; using Microsoft.HttpRepl.Preferences; using Microsoft.HttpRepl.Resources; using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Parsing; using Xunit; namespace Microsoft.HttpRepl.Tests.Commands { public class PatchCommandTests : CommandTestsBase { private string _baseAddress; private string _testPath; private string _noBodyRequiredPath; private IDictionary _urlsWithResponse = new Dictionary(); public PatchCommandTests() { _baseAddress = "http://localhost:5050/"; _testPath = "this/is/a/test/route"; _noBodyRequiredPath = "no/body/required"; _urlsWithResponse.Add(_baseAddress, "This is a test response from a PATCH: \"Test Patch Body\""); _urlsWithResponse.Add(_baseAddress + _testPath, "This is a test response from a PATCH: \"Test Patch Body\""); _urlsWithResponse.Add(_baseAddress + _noBodyRequiredPath, "This is a test response from a PATCH: \"\""); } [Fact] public async Task ExecuteAsync_WithNoBasePath_VerifyError() { ArrangeInputs(commandText: "PATCH", baseAddress: null, path: null, urlsWithResponse: null, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences); string expectedErrorMessage = Strings.Error_NoBasePath.SetColor(httpState.ErrorColor); PatchCommand patchCommand = new PatchCommand(fileSystem, preferences, new NullTelemetry()); await patchCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.Equal(expectedErrorMessage, shellState.ErrorMessage); } [Fact] public async Task ExecuteAsync_OnlyBaseAddressWithInlineContent_VerifyResponse() { ArrangeInputs(commandText: "PATCH --content \"Test Patch Body\"", baseAddress: _baseAddress, path: null, urlsWithResponse: _urlsWithResponse, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences); PatchCommand patchCommand = new PatchCommand(fileSystem, preferences, new NullTelemetry()); await patchCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); string expectedResponse = "This is a test response from a PATCH: \"Test Patch Body\""; List result = shellState.Output; Assert.Equal(2, result.Count); Assert.Contains("HTTP/1.1 200 OK", result); Assert.Contains(expectedResponse, result); } [Fact] public async Task ExecuteAsync_MultiPartRouteWithInlineContent_VerifyResponse() { ArrangeInputs(commandText: "PATCH --content \"Test Patch Body\"", baseAddress: _baseAddress, path: _testPath, urlsWithResponse: _urlsWithResponse, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences); PatchCommand patchCommand = new PatchCommand(fileSystem, preferences, new NullTelemetry()); await patchCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); string expectedResponse = "This is a test response from a PATCH: \"Test Patch Body\""; List result = shellState.Output; Assert.Equal(2, result.Count); Assert.Contains("HTTP/1.1 200 OK", result); Assert.Contains(expectedResponse, result); } [Fact] public async Task ExecuteAsync_MultiPartRouteWithNoBodyRequired_VerifyResponse() { ArrangeInputs(commandText: "PATCH --no-body", baseAddress: _baseAddress, path: _noBodyRequiredPath, urlsWithResponse: _urlsWithResponse, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences); PatchCommand patchCommand = new PatchCommand(fileSystem, preferences, new NullTelemetry()); await patchCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); string expectedResponse = "This is a test response from a PATCH: \"\""; List result = shellState.Output; Assert.Equal(2, result.Count); Assert.Contains("HTTP/1.1 200 OK", result); Assert.Contains(expectedResponse, result); } [Fact] public async Task ExecuteAsync_MultiPartRouteWithBodyFromFile_VerifyResponse() { string filePath = "someFilePath.txt"; string fileContents = "This is a test response from a PATCH: \"Test Patch Body From File\""; ArrangeInputs(commandText: $"PATCH --file " + filePath, baseAddress: _baseAddress, path: _testPath, urlsWithResponse: _urlsWithResponse, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences, readBodyFromFile: true, fileContents: fileContents); fileSystem.AddFile(filePath, "Test Patch Body From File"); PatchCommand patchCommand = new PatchCommand(fileSystem, preferences, new NullTelemetry()); await patchCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); List result = shellState.Output; Assert.Equal(2, result.Count); Assert.Contains("HTTP/1.1 200 OK", result); Assert.Contains(fileContents, result); } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/Commands/PostCommandTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Microsoft.HttpRepl.Commands; using Microsoft.HttpRepl.Fakes; using Microsoft.HttpRepl.FileSystem; using Microsoft.HttpRepl.Preferences; using Microsoft.HttpRepl.Resources; using Microsoft.HttpRepl.UserProfile; using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Parsing; using Xunit; namespace Microsoft.HttpRepl.Tests.Commands { public class PostCommandTests : CommandTestsBase { private string _baseAddress; private string _testPath; private string _noBodyRequiredPath; private IDictionary _urlsWithResponse = new Dictionary(); public PostCommandTests() { _baseAddress = "http://localhost:5050/"; _testPath = "this/is/a/test/route"; _noBodyRequiredPath = "no/body/required"; _urlsWithResponse.Add(_baseAddress, "This is a test response from a POST: \"Test Post Body\""); _urlsWithResponse.Add(_baseAddress + _testPath, "This is a test response from a POST: \"Test Post Body\""); _urlsWithResponse.Add(_baseAddress + _noBodyRequiredPath, "This is a test response from a POST: \"\""); } [Fact] public async Task ExecuteAsync_WithNoBasePath_VerifyError() { ArrangeInputs(commandText: "POST", baseAddress: null, path: null, urlsWithResponse: null, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences); string expectedErrorMessage = Strings.Error_NoBasePath.SetColor(httpState.ErrorColor); PostCommand postCommand = new PostCommand(fileSystem, preferences, new NullTelemetry()); await postCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.Equal(expectedErrorMessage, shellState.ErrorMessage); } [Fact] public async Task ExecuteAsync_OnlyBaseAddressWithInlineContent_VerifyResponse() { ArrangeInputs(commandText: "POST --content \"Test Post Body\"", baseAddress: _baseAddress, path: _testPath, urlsWithResponse: _urlsWithResponse, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences); PostCommand postCommand = new PostCommand(fileSystem, preferences, new NullTelemetry()); await postCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); string expectedResponse = "This is a test response from a POST: \"Test Post Body\""; List result = shellState.Output; Assert.Equal(2, result.Count); Assert.Contains("HTTP/1.1 200 OK", result); Assert.Contains(expectedResponse, result); } [Fact] public async Task ExecuteAsync_WithContentTypeHeader_NotDuplicated() { string expectedHeader = "Content-Type: application/test"; ArrangeInputs(commandText: "POST --header content-type:application/test --content \"Test Post Body\"", baseAddress: _baseAddress, path: _testPath, urlsWithResponse: _urlsWithResponse, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences); httpState.EchoRequest = true; PostCommand postCommand = new PostCommand(fileSystem, preferences, new NullTelemetry()); await postCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); List result = shellState.Output; Assert.Equal(8, result.Count); Assert.Contains("HTTP/1.1 200 OK", result); Assert.Single(result, s => string.Equals(s, expectedHeader, System.StringComparison.Ordinal)); } [Fact] public async Task ExecuteAsync_WithOtherContentHeader_NotDuplicated() { string expectedHeader = "Content-Disposition: inline"; ArrangeInputs(commandText: "POST --header content-disposition:inline --content \"Test Post Body\"", baseAddress: _baseAddress, path: _testPath, urlsWithResponse: _urlsWithResponse, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences); httpState.EchoRequest = true; PostCommand postCommand = new PostCommand(fileSystem, preferences, new NullTelemetry()); await postCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); List result = shellState.Output; Assert.Equal(9, result.Count); Assert.Contains("HTTP/1.1 200 OK", result); Assert.Single(result, s => string.Equals(s, expectedHeader, System.StringComparison.Ordinal)); } [Fact] public async Task ExecuteAsync_WithNonContentHeader_NotDuplicated() { string expectedHeader = "TE: compress"; ArrangeInputs(commandText: "POST --header te:compress --content \"Test Post Body\"", baseAddress: _baseAddress, path: _testPath, urlsWithResponse: _urlsWithResponse, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences); httpState.EchoRequest = true; PostCommand postCommand = new PostCommand(fileSystem, preferences, new NullTelemetry()); await postCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); List result = shellState.Output; Assert.Equal(9, result.Count); Assert.Contains("HTTP/1.1 200 OK", result); Assert.Single(result, s => string.Equals(s, expectedHeader, System.StringComparison.Ordinal)); } [Fact] public async Task ExecuteAsync_MultiPartRouteWithInlineContent_VerifyResponse() { ArrangeInputs(commandText: "POST --content \"Test Post Body\"", baseAddress: _baseAddress, path: _testPath, urlsWithResponse: _urlsWithResponse, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences); PostCommand postCommand = new PostCommand(fileSystem, preferences, new NullTelemetry()); await postCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); string expectedResponse = "This is a test response from a POST: \"Test Post Body\""; List result = shellState.Output; Assert.Equal(2, result.Count); Assert.Contains("HTTP/1.1 200 OK", result); Assert.Contains(expectedResponse, result); } [Fact] public async Task ExecuteAsync_MultiPartRouteWithNoBodyRequired_VerifyResponse() { ArrangeInputs(commandText: "POST --no-body", baseAddress: _baseAddress, path: _noBodyRequiredPath, urlsWithResponse: _urlsWithResponse, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences); PostCommand postCommand = new PostCommand(fileSystem, preferences, new NullTelemetry()); await postCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); string expectedResponse = "This is a test response from a POST: \"\""; List result = shellState.Output; Assert.Equal(2, result.Count); Assert.Contains("HTTP/1.1 200 OK", result); Assert.Contains(expectedResponse, result); } [Fact] public async Task ExecuteAsync_MultiPartRouteWithBodyFromFile_VerifyResponse() { string filePath = "someFilePath.txt"; string fileContents = "This is a test response from a POST: \"Test Post Body From File\""; ArrangeInputs(commandText: $"POST --file " + filePath, baseAddress: _baseAddress, path: _testPath, urlsWithResponse: _urlsWithResponse, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences, readBodyFromFile: true, fileContents: fileContents); fileSystem.AddFile(filePath, "Test Post Body From File"); PostCommand postCommand = new PostCommand(fileSystem, preferences, new NullTelemetry()); await postCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); List result = shellState.Output; Assert.Equal(2, result.Count); Assert.Contains("HTTP/1.1 200 OK", result); Assert.Contains(fileContents, result); } [Fact] public async Task ExecuteAsync_NonExistentContentFile_VerifyResponse() { string filePath = "someFilePath.txt"; string fileContents = "This is a test response from a POST: \"Test Post Body From File\""; ArrangeInputs(commandText: $"POST --file " + filePath, baseAddress: _baseAddress, path: _testPath, urlsWithResponse: _urlsWithResponse, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences, readBodyFromFile: true, fileContents: fileContents); PostCommand postCommand = new PostCommand(fileSystem, preferences, new NullTelemetry()); await postCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.Empty(shellState.Output); Assert.Contains(string.Format(Strings.BaseHttpCommand_Error_ContentFileDoesNotExist, filePath), shellState.ErrorMessage); } [Fact] public async Task ExecuteAsync_WithEditorNotConfigured_VerifyResponse() { ArrangeInputs(commandText: $"POST", baseAddress: _baseAddress, path: _testPath, urlsWithResponse: _urlsWithResponse, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences); preferences.SetValue(WellKnownPreference.DefaultEditorCommand, ""); PostCommand postCommand = new PostCommand(fileSystem, preferences, new NullTelemetry()); await postCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.Empty(shellState.Output); Assert.Contains(string.Format(Strings.BaseHttpCommand_Error_DefaultEditorNotConfigured, WellKnownPreference.DefaultEditorCommand), shellState.ErrorMessage); } [Fact] public async Task ExecuteAsync_WithEditorDoesNotExist_VerifyResponse() { // Arrange string editorPath = "FileThatDoesNotExist.exe"; string commandText = "POST https://localhost/"; MockedShellState shellState = new MockedShellState(); IFileSystem fileSystem = new MockedFileSystem(); IUserProfileDirectoryProvider userProfileDirectoryProvider = new UserProfileDirectoryProvider(); IPreferences preferences = new UserFolderPreferences(fileSystem, userProfileDirectoryProvider, null); ICoreParseResult parseResult = CoreParseResultHelper.Create(commandText); HttpClient httpClient = new HttpClient(); HttpState httpState = new HttpState(preferences, httpClient); PostCommand postCommand = new PostCommand(fileSystem, preferences, new NullTelemetry()); preferences.SetValue(WellKnownPreference.DefaultEditorCommand, editorPath); // Act await postCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); // Execute Assert.Empty(shellState.Output); Assert.Contains(string.Format(Strings.BaseHttpCommand_Error_DefaultEditorDoesNotExist, editorPath), shellState.ErrorMessage); } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/Commands/PrefCommandTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Net.Http; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Microsoft.HttpRepl.Commands; using Microsoft.HttpRepl.Fakes; using Microsoft.HttpRepl.FileSystem; using Microsoft.HttpRepl.Preferences; using Microsoft.HttpRepl.Telemetry; using Microsoft.HttpRepl.Tests.Preferences; using Microsoft.HttpRepl.UserProfile; using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Parsing; using Xunit; namespace Microsoft.HttpRepl.Tests.Commands { public class PrefCommandTests { [Fact] public async Task ExecuteAsync_UppercaseGetCommand_CorrectOutput() { await ValidateOutput(commandText: $"pref GET {WellKnownPreference.ProtocolColor}", expectedOutputLines: 1, expectedOutputOnLastLineCallback: (preferences) => $"Configured value: {preferences.CurrentPreferences[WellKnownPreference.ProtocolColor]}"); } [Fact] public async Task ExecuteAsync_LowercaseGetCommand_CorrectOutput() { await ValidateOutput(commandText: $"pref get {WellKnownPreference.ProtocolColor}", expectedOutputLines: 1, expectedOutputOnLastLineCallback: (preferences) => $"Configured value: {preferences.CurrentPreferences[WellKnownPreference.ProtocolColor]}"); } [Fact] public async Task ExecuteAsync_MixedCaseGetCommand_CorrectOutput() { await ValidateOutput(commandText: $"pref gEt {WellKnownPreference.ProtocolColor}", expectedOutputLines: 1, expectedOutputOnLastLineCallback: (preferences) => $"Configured value: {preferences.CurrentPreferences[WellKnownPreference.ProtocolColor]}"); } [Fact] public async Task ExecuteAsync_UppercaseSetCommand_PreferenceSet() { string expectedValue = "BoldMagenta"; await ValidatePreference(commandText: $"pref SET {WellKnownPreference.ProtocolColor} {expectedValue}", preferenceName: WellKnownPreference.ProtocolColor, expectedValueCallback: (httpState) => expectedValue); } [Fact] public async Task ExecuteAsync_LowercaseSetCommand_PreferenceSet() { string expectedValue = "BoldMagenta"; await ValidatePreference(commandText: $"pref set {WellKnownPreference.ProtocolColor} {expectedValue}", preferenceName: WellKnownPreference.ProtocolColor, expectedValueCallback: (httpState) => expectedValue); } [Fact] public async Task ExecuteAsync_MixedCaseSetCommand_PreferenceSet() { string expectedValue = "BoldMagenta"; await ValidatePreference(commandText: $"pref sEt {WellKnownPreference.ProtocolColor} {expectedValue}", preferenceName: WellKnownPreference.ProtocolColor, expectedValueCallback: (httpState) => expectedValue); } [Fact] public async Task ExecuteAsync_SetCommandNoValue_SetToDefault() { IFileSystem fileSystem = new MockedFileSystem(); IUserProfileDirectoryProvider userProfileDirectoryProvider = new UserProfileDirectoryProvider(); UserFolderPreferences preferences = new UserFolderPreferences(fileSystem, userProfileDirectoryProvider, TestDefaultPreferences.GetDefaultPreferences()); HttpClient httpClient = new HttpClient(); HttpState httpState = new HttpState(preferences, httpClient); MockedShellState shellState = new MockedShellState(); PrefCommand command = new PrefCommand(preferences, new NullTelemetry()); // First, set it to something other than the default and make sure that works. string firstCommandExpectedValue = "BoldMagenta"; string firstCommandText = $"pref set {WellKnownPreference.ProtocolColor} {firstCommandExpectedValue}"; ICoreParseResult firstParseResult = CoreParseResultHelper.Create(firstCommandText); await command.ExecuteAsync(shellState, httpState, firstParseResult, CancellationToken.None); Assert.Empty(shellState.Output); Assert.Equal(firstCommandExpectedValue, preferences.CurrentPreferences[WellKnownPreference.ProtocolColor]); // Then, set it to nothing and make sure it goes back to the default string secondCommandText = $"pref set {WellKnownPreference.ProtocolColor}"; ICoreParseResult secondParseResult = CoreParseResultHelper.Create(secondCommandText); await command.ExecuteAsync(shellState, httpState, secondParseResult, CancellationToken.None); Assert.Empty(shellState.Output); Assert.Equal(preferences.DefaultPreferences[WellKnownPreference.ProtocolColor], preferences.CurrentPreferences[WellKnownPreference.ProtocolColor]); } [Fact] public void CanHandle_NoArguments_ReturnsNull() { string commandText = ""; Arrange(commandText, out HttpState httpState, out MockedShellState shellState, out ICoreParseResult parseResult, out PrefCommand command, out _); bool? canHandle = command.CanHandle(shellState, httpState, parseResult); Assert.Null(canHandle); } [Fact] public void CanHandle_InvalidFirstArgument_ReturnsNull() { string commandText = "preferences set colors.protocol"; Arrange(commandText, out HttpState httpState, out MockedShellState shellState, out ICoreParseResult parseResult, out PrefCommand command, out _); bool? canHandle = command.CanHandle(shellState, httpState, parseResult); Assert.Null(canHandle); } [Fact] public void CanHandle_SetWithNoName_DisplaysError() { string commandText = "pref set"; Arrange(commandText, out HttpState httpState, out MockedShellState shellState, out ICoreParseResult parseResult, out PrefCommand command, out _); bool? canHandle = command.CanHandle(shellState, httpState, parseResult); Assert.False(canHandle); Assert.Equal(Resources.Strings.PrefCommand_Error_NoPreferenceName, shellState.ErrorMessage); } [Fact] public void CanHandle_SetWithBlankName_DisplaysError() { string commandText = "pref set "; Arrange(commandText, out HttpState httpState, out MockedShellState shellState, out ICoreParseResult parseResult, out PrefCommand command, out _); bool? canHandle = command.CanHandle(shellState, httpState, parseResult); Assert.False(canHandle); Assert.Equal(Resources.Strings.PrefCommand_Error_NoPreferenceName, shellState.ErrorMessage); } [Fact] public void GetHelpDetails_NoSubCommands_DisplaysCommandSyntax() { string commandText = "pref"; Arrange(commandText, out HttpState httpState, out MockedShellState shellState, out ICoreParseResult parseResult, out PrefCommand command, out _); string output = command.GetHelpDetails(shellState, httpState, parseResult); Assert.Contains("pref [get/set] {setting} [{value}]", output); } [Fact] public void GetHelpDetails_Get_DisplaysCommandSyntax() { string commandText = "pref get"; Arrange(commandText, out HttpState httpState, out MockedShellState shellState, out ICoreParseResult parseResult, out PrefCommand command, out _); string output = command.GetHelpDetails(shellState, httpState, parseResult); Assert.Contains("pref get [{setting}]", output); } [Fact] public void GetHelpDetails_Set_DisplaysCommandSyntax() { string commandText = "pref set"; Arrange(commandText, out HttpState httpState, out MockedShellState shellState, out ICoreParseResult parseResult, out PrefCommand command, out _); string output = command.GetHelpDetails(shellState, httpState, parseResult); Assert.Contains("pref set {setting} [{value}]", output); } [Fact] public async Task ExecuteAsync_WithGet_SendsTelemetry() { Arrange($"pref get {WellKnownPreference.DefaultEditorCommand}", out HttpState httpState, out MockedShellState shellState, out ICoreParseResult parseResult, out UserFolderPreferences preferences); TelemetryCollector telemetry = new TelemetryCollector(); PrefCommand command = new PrefCommand(preferences, telemetry); await command.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.Single(telemetry.Telemetry); TelemetryCollector.CollectedTelemetry collectedTelemetry = telemetry.Telemetry[0]; Assert.Equal("Preference", collectedTelemetry.EventName); Assert.Equal("Get", collectedTelemetry.Properties["GetOrSet"]); Assert.Equal(WellKnownPreference.DefaultEditorCommand, collectedTelemetry.Properties["PreferenceName"]); } [Fact] public async Task ExecuteAsync_WithSet_SendsTelemetry() { Arrange($"pref set {WellKnownPreference.DefaultEditorCommand} value", out HttpState httpState, out MockedShellState shellState, out ICoreParseResult parseResult, out UserFolderPreferences preferences); TelemetryCollector telemetry = new TelemetryCollector(); PrefCommand command = new PrefCommand(preferences, telemetry); await command.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.Single(telemetry.Telemetry); TelemetryCollector.CollectedTelemetry collectedTelemetry = telemetry.Telemetry[0]; Assert.Equal("Preference", collectedTelemetry.EventName); Assert.Equal("Set", collectedTelemetry.Properties["GetOrSet"]); Assert.Equal(WellKnownPreference.DefaultEditorCommand, collectedTelemetry.Properties["PreferenceName"]); } [Fact] public async Task ExecuteAsync_WithGetAndUnknownName_SendsTelemetryWithHashedName() { Arrange("pref set preferenceName value", out HttpState httpState, out MockedShellState shellState, out ICoreParseResult parseResult, out UserFolderPreferences preferences); TelemetryCollector telemetry = new TelemetryCollector(); PrefCommand command = new PrefCommand(preferences, telemetry); await command.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.Single(telemetry.Telemetry); TelemetryCollector.CollectedTelemetry collectedTelemetry = telemetry.Telemetry[0]; Assert.Equal("Preference", collectedTelemetry.EventName); Assert.Equal("Set", collectedTelemetry.Properties["GetOrSet"]); Assert.Equal(Sha256Hasher.Hash("preferenceName"), collectedTelemetry.Properties["PreferenceName"]); } [Theory] [MemberData(nameof(ExecuteAsync_SetDefaultEditorToVSCode_ShowsWarning_Data))] public async Task ExecuteAsync_SetDefaultEditorToVSCode_ShowsWarning(string commandText, OSPlatform intendedPlatform) { // Arrange Arrange($"pref set {WellKnownPreference.DefaultEditorCommand} \"{commandText}\"", out HttpState httpState, out MockedShellState shellState, out ICoreParseResult parseResult, out UserFolderPreferences preferences); PrefCommand command = new PrefCommand(preferences, new NullTelemetry()); string expectedWarning = string.Format(Resources.Strings.PrefCommand_Set_VSCode, WellKnownPreference.DefaultEditorArguments).SetColor(httpState.WarningColor); // Act await command.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); // Assert if (RuntimeInformation.IsOSPlatform(intendedPlatform)) { Assert.Contains(expectedWarning, shellState.Output, StringComparer.CurrentCulture); } else { Assert.DoesNotContain(expectedWarning, shellState.Output, StringComparer.CurrentCulture); } } public static IEnumerable ExecuteAsync_SetDefaultEditorToVSCode_ShowsWarning_Data { get; } = new List() { new object[] { "c:\\users\\username\\appdata\\local\\programs\\Microsoft VS Code\\Code.exe", OSPlatform.Windows }, new object[] { "c:\\users\\username\\appdata\\local\\programs\\Microsoft VS Code Insiders\\Code - Insiders.exe", OSPlatform.Windows }, new object[] { "C:\\Program Files\\Microsoft VS Code\\Code.exe", OSPlatform.Windows }, new object[] { "/usr/bin/code", OSPlatform.Linux }, new object[] { "/usr/bin/code-insiders", OSPlatform.Linux }, new object[] { "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code", OSPlatform.OSX }, new object[] { "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code-insiders", OSPlatform.OSX }, }; private static async Task ValidatePreference(string commandText, string preferenceName, Func expectedValueCallback) { Arrange(commandText, out HttpState httpState, out MockedShellState shellState, out ICoreParseResult parseResult, out PrefCommand command, out UserFolderPreferences preferences); await command.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.Empty(shellState.Output); Assert.Equal(expectedValueCallback(httpState), preferences.CurrentPreferences[preferenceName]); } private static async Task ValidateOutput(string commandText, int expectedOutputLines, Func expectedOutputOnLastLineCallback) { Arrange(commandText, out HttpState httpState, out MockedShellState shellState, out ICoreParseResult parseResult, out PrefCommand command, out UserFolderPreferences preferences); await command.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.Single(shellState.Output); Assert.Equal(expectedOutputOnLastLineCallback(preferences), shellState.Output[0]); } private static void Arrange(string commandText, out HttpState httpState, out MockedShellState shellState, out ICoreParseResult parseResult, out PrefCommand command, out UserFolderPreferences preferences) { IFileSystem fileSystem = new MockedFileSystem(); IUserProfileDirectoryProvider userProfileDirectoryProvider = new UserProfileDirectoryProvider(); preferences = new UserFolderPreferences(fileSystem, userProfileDirectoryProvider, TestDefaultPreferences.GetDefaultPreferences()); HttpClient httpClient = new HttpClient(); httpState = new HttpState(preferences, httpClient); shellState = new MockedShellState(); parseResult = CoreParseResultHelper.Create(commandText); command = new PrefCommand(preferences, new NullTelemetry()); } private static void Arrange(string commandText, out HttpState httpState, out MockedShellState shellState, out ICoreParseResult parseResult, out UserFolderPreferences preferences) { Arrange(commandText, out httpState, out shellState, out parseResult, out _, out preferences); } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/Commands/PutCommandTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.HttpRepl.Commands; using Microsoft.HttpRepl.Fakes; using Microsoft.HttpRepl.Preferences; using Microsoft.HttpRepl.Resources; using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Parsing; using Xunit; namespace Microsoft.HttpRepl.Tests.Commands { public class PutCommandTests : CommandTestsBase { private string _baseAddress; private string _testPath; private string _noBodyRequiredPath; private IDictionary _urlsWithResponse = new Dictionary(); public PutCommandTests() { _baseAddress = "http://localhost:5050/"; _testPath = "this/is/a/test/route"; _noBodyRequiredPath = "no/body/required"; _urlsWithResponse.Add(_baseAddress, "This is a test response from a PUT: \"Test Put Body\""); _urlsWithResponse.Add(_baseAddress + _testPath, "This is a test response from a PUT: \"Test Put Body\""); _urlsWithResponse.Add(_baseAddress + _noBodyRequiredPath, "This is a test response from a PUT: \"\""); } [Fact] public async Task ExecuteAsync_WithNoBasePath_VerifyError() { ArrangeInputs(commandText: "PUT", baseAddress: null, path: null, urlsWithResponse: null, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences); string expectedErrorMessage = Strings.Error_NoBasePath.SetColor(httpState.ErrorColor); PutCommand putCommand = new PutCommand(fileSystem, preferences, new NullTelemetry()); await putCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.Equal(expectedErrorMessage, shellState.ErrorMessage); } [Fact] public async Task ExecuteAsync_OnlyBaseAddressWithInlineContent_VerifyResponse() { ArrangeInputs(commandText: "PUT --content \"Test Put Body\"", baseAddress: _baseAddress, path: _testPath, urlsWithResponse: _urlsWithResponse, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences); PutCommand putCommand = new PutCommand(fileSystem, preferences, new NullTelemetry()); await putCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); string expectedResponse = "This is a test response from a PUT: \"Test Put Body\""; List result = shellState.Output; Assert.Equal(2, result.Count); Assert.Contains("HTTP/1.1 200 OK", result); Assert.Contains(expectedResponse, result); } [Fact] public async Task ExecuteAsync_MultiPartRouteWithInlineContent_VerifyResponse() { ArrangeInputs(commandText: "PUT --content \"Test Put Body\"", baseAddress: _baseAddress, path: _testPath, urlsWithResponse: _urlsWithResponse, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences); PutCommand putCommand = new PutCommand(fileSystem, preferences, new NullTelemetry()); await putCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); string expectedResponse = "This is a test response from a PUT: \"Test Put Body\""; List result = shellState.Output; Assert.Equal(2, result.Count); Assert.Contains("HTTP/1.1 200 OK", result); Assert.Contains(expectedResponse, result); } [Fact] public async Task ExecuteAsync_MultiPartRouteWithNoBodyRequired_VerifyResponse() { ArrangeInputs(commandText: "PUT --no-body", baseAddress: _baseAddress, path: _noBodyRequiredPath, urlsWithResponse: _urlsWithResponse, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences); PutCommand putCommand = new PutCommand(fileSystem, preferences, new NullTelemetry()); await putCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); string expectedResponse = "This is a test response from a PUT: \"\""; List result = shellState.Output; Assert.Equal(2, result.Count); Assert.Contains("HTTP/1.1 200 OK", result); Assert.Contains(expectedResponse, result); } [Fact] public async Task ExecuteAsync_MultiPartRouteWithBodyFromFile_VerifyResponse() { string filePath = "someFilePath.txt"; string fileContents = "This is a test response from a PUT: \"Test Put Body From File\""; ArrangeInputs(commandText: $"PUT --file " + filePath, baseAddress: _baseAddress, path: _testPath, urlsWithResponse: _urlsWithResponse, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out MockedFileSystem fileSystem, out IPreferences preferences, readBodyFromFile: true, fileContents: fileContents); fileSystem.AddFile(filePath, "Test Put Body From File"); PutCommand putCommand = new PutCommand(fileSystem, preferences, new NullTelemetry()); await putCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); List result = shellState.Output; Assert.Equal(2, result.Count); Assert.Contains("HTTP/1.1 200 OK", result); Assert.Contains(fileContents, result); } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/Commands/RunCommandTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.HttpRepl.Commands; using Microsoft.HttpRepl.Fakes; using Microsoft.HttpRepl.Resources; using Microsoft.Repl; using Microsoft.Repl.Commanding; using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Parsing; using Moq; using Xunit; namespace Microsoft.HttpRepl.Tests.Commands { public class RunCommandTests : CommandTestsBase, IDisposable { private string _pathToScript = Path.Combine(Directory.GetCurrentDirectory(), "InputFileForRunCommand.txt"); [Fact] public void CanHandle_WithNoParseResultSections_ReturnsNull() { ArrangeInputs(parseResultSections: string.Empty, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); RunCommand runCommand = new RunCommand(new MockedFileSystem()); bool? result = runCommand.CanHandle(shellState, httpState, parseResult); Assert.Null(result); } [Fact] public void CanHandle_WithFirstParseResultSectionNotEqualToName_ReturnsNull() { ArrangeInputs(parseResultSections: "test name.txt", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); RunCommand runCommand = new RunCommand(new MockedFileSystem()); bool? result = runCommand.CanHandle(shellState, httpState, parseResult); Assert.Null(result); } [Fact] public void CanHandle_WithValidInput_ReturnsTrue() { ArrangeInputs(parseResultSections: "run InputFileForRunCommand.txt", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); RunCommand runCommand = new RunCommand(new MockedFileSystem()); bool? result = runCommand.CanHandle(shellState, httpState, parseResult); Assert.True(result); } [Fact] public void GetHelpSummary_ReturnsDescription() { ArrangeInputs(parseResultSections: string.Empty, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult _); RunCommand runCommand = new RunCommand(new MockedFileSystem()); string result = runCommand.GetHelpSummary(shellState, httpState); Assert.Equal(Strings.RunCommand_HelpSummary, result); } [Fact] public void GetHelpDetails_WithEmptyParseResultSection_ReturnsNull() { ArrangeInputs(parseResultSections: string.Empty, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); RunCommand runCommand = new RunCommand(new MockedFileSystem()); string result = runCommand.GetHelpDetails(shellState, httpState, parseResult); Assert.Null(result); } [Fact] public void GetHelpDetails_WithFirstParseResultSectionNotEqualToName_ReturnsNull() { ArrangeInputs(parseResultSections: "section1 section2 section3", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); RunCommand runCommand = new RunCommand(new MockedFileSystem()); string result = runCommand.GetHelpDetails(shellState, httpState, parseResult); Assert.Null(result); } [Fact] public void GetHelpDetails_WithValidInput_HelpDetails() { ArrangeInputs(parseResultSections: "run InputFileForRunCommand.txt", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); string expected = "\u001b[1mUsage: \u001b[39mrun {path to script}" + Environment.NewLine + Environment.NewLine + "Runs the specified script." + Environment.NewLine + "A script is a text file containing one CLI command per line. Each line will be run as if it was typed into the CLI." + Environment.NewLine + Environment.NewLine + "When +history option is specified, commands specified in the text file will be added to command history." + Environment.NewLine; RunCommand runCommand = new RunCommand(new MockedFileSystem()); string result = runCommand.GetHelpDetails(shellState, httpState, parseResult); Assert.Equal(expected, result); } [Fact] public async Task ExecuteAsync_IfFileDoesNotExist_WritesToConsoleManagerError() { ArrangeInputs(parseResultSections: "run InputFileForRunCommand.txt", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); RunCommand runCommand = new RunCommand(new MockedFileSystem()); await runCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); VerifyErrorMessageWasWrittenToConsoleManagerError(shellState); } [Fact] public async Task ExecuteAsync_WithValidInput_ExecutesTheCommandsInTheScript() { string commands = @"set header name value1 value2"; if (!File.Exists(_pathToScript)) { File.WriteAllText(_pathToScript, commands); } string parseResultSections = "run " + _pathToScript; ArrangeInputs(parseResultSections: parseResultSections, out MockedShellState _, out HttpState httpState, out ICoreParseResult parseResult); IShellState shellState = GetShellState(commands, httpState); MockedFileSystem mockedFileSystem = new MockedFileSystem(); mockedFileSystem.AddFile(_pathToScript, commands); RunCommand runCommand = new RunCommand(mockedFileSystem); await runCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Dictionary> headers = httpState.Headers; KeyValuePair> firstHeader = headers.First(); KeyValuePair> secondHeader = headers.ElementAt(1); Assert.Equal(2, httpState.Headers.Count); Assert.Equal("User-Agent", firstHeader.Key); Assert.Equal("HTTP-REPL", firstHeader.Value.First()); Assert.Equal("name", secondHeader.Key); Assert.Equal("value1", secondHeader.Value.First()); Assert.Equal("value2", secondHeader.Value.ElementAt(1)); } [Fact] public async Task ExecuteAsync_WithHistoryOption_AddsCommandsExecutedFromScriptToCommandHistory() { string commands = @"set header name value1 value2"; if (!File.Exists(_pathToScript)) { File.WriteAllText(_pathToScript, commands); } string parseResultSections = "run " + _pathToScript + " +history"; ArrangeInputs(parseResultSections: parseResultSections, out MockedShellState _, out HttpState httpState, out ICoreParseResult parseResult); IShellState shellState = GetShellState(commands, httpState); MockedFileSystem mockedFileSystem = new MockedFileSystem(); mockedFileSystem.AddFile(_pathToScript, commands); RunCommand runCommand = new RunCommand(mockedFileSystem); await runCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); string previousCommand = shellState.CommandHistory.GetPreviousCommand(); Assert.Equal(commands, previousCommand); } [Fact] public async Task ExecuteAsync_WithoutHistoryOption_AvoidsAddingCommandsExecutedFromScriptToCommandHistory() { string commands = @"set header name value1 value2"; if (!File.Exists(_pathToScript)) { File.WriteAllText(_pathToScript, commands); } string parseResultSections = "run " + _pathToScript; ArrangeInputs(parseResultSections: parseResultSections, out MockedShellState _, out HttpState httpState, out ICoreParseResult parseResult); IShellState shellState = GetShellState(commands, httpState); MockedFileSystem mockedFileSystem = new MockedFileSystem(); mockedFileSystem.AddFile(_pathToScript, commands); RunCommand runCommand = new RunCommand(mockedFileSystem); await runCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); string previousCommand = shellState.CommandHistory.GetPreviousCommand(); Assert.True(string.IsNullOrEmpty(previousCommand)); } [Fact] public void Suggest_WithSelectedSectionAtZeroAndEmptyParseResultSection_ReturnsName() { ArrangeInputs(parseResultSections: string.Empty, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, caretPosition: 0); string expected = "run"; RunCommand runCommand = new RunCommand(new MockedFileSystem()); IEnumerable result = runCommand.Suggest(shellState, httpState, parseResult); Assert.Single(result); Assert.Equal(expected, result.First()); } [Fact] public void Suggest_WithSelectedSectionAtZeroAndParseResultSectionStartsWithName_ReturnsName() { ArrangeInputs(parseResultSections: "r", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, caretPosition: 0); string expected = "run"; RunCommand runCommand = new RunCommand(new MockedFileSystem()); IEnumerable result = runCommand.Suggest(shellState, httpState, parseResult); Assert.Single(result); Assert.Equal(expected, result.First()); } [Fact] public void Suggest_WithSelectedSectionAtOneAndValidParseResultSection_ReturnsCompletionEntries() { string pathToScript = Path.Combine(Directory.GetCurrentDirectory(), "InputFileForRunCommand.txt"); string parseResultSections = "run " + pathToScript; ArrangeInputs(parseResultSections: parseResultSections, out MockedShellState _, out HttpState httpState, out ICoreParseResult parseResult, caretPosition: 7); IShellState shellState = GetShellState(string.Empty, httpState); MockedFileSystem mockedFileSystem = new MockedFileSystem(); mockedFileSystem.AddFile(pathToScript, string.Empty); RunCommand runCommand = new RunCommand(mockedFileSystem); IEnumerable result = runCommand.Suggest(shellState, httpState, parseResult); Assert.NotEmpty(result); } private IShellState GetShellState(string inputBuffer, HttpState httpState) { DefaultCommandDispatcher defaultCommandDispatcher = DefaultCommandDispatcher.Create(x => { }, httpState); defaultCommandDispatcher.AddCommand(new SetHeaderCommand(new NullTelemetry())); Mock mockConsoleManager = new Mock(); MockInputManager mockInputManager = new MockInputManager(inputBuffer); ShellState shellState = new ShellState(defaultCommandDispatcher, consoleManager: mockConsoleManager.Object, commandHistory: new CommandHistory(), inputManager: mockInputManager); Shell shell = new Shell(shellState); return shell.ShellState; } public void Dispose() { if (File.Exists(_pathToScript)) { File.Delete(_pathToScript); } } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/Commands/SetHeaderCommandTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.HttpRepl.Commands; using Microsoft.HttpRepl.Fakes; using Microsoft.HttpRepl.OpenApi; using Microsoft.HttpRepl.Telemetry; using Microsoft.Repl.Parsing; using Xunit; namespace Microsoft.HttpRepl.Tests.Commands { public class SetHeaderCommandTests : CommandTestsBase { [Fact] public void CanHandle_WithParseResultSectionsLessThanTwo_ReturnsNull() { ArrangeInputs(parseResultSections: "set", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); SetHeaderCommand setHeaderCommand = new SetHeaderCommand(new NullTelemetry()); bool? result = setHeaderCommand.CanHandle(shellState, httpState, parseResult); Assert.Null(result); } [Fact] public void CanHandle_WithFirstParseResultSectionNotEqualToName_ReturnsNull() { ArrangeInputs(parseResultSections: "test header name", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); SetHeaderCommand setHeaderCommand = new SetHeaderCommand(new NullTelemetry()); bool? result = setHeaderCommand.CanHandle(shellState, httpState, parseResult); Assert.Null(result); } [Fact] public void CanHandle_WithSecondParseResultSectionNotEqualToSubCommand_ReturnsNull() { ArrangeInputs(parseResultSections: "set base name", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); SetHeaderCommand setHeaderCommand = new SetHeaderCommand(new NullTelemetry()); bool? result = setHeaderCommand.CanHandle(shellState, httpState, parseResult); Assert.Null(result); } [Fact] public void CanHandle_WithValidInput_ReturnsTrue() { ArrangeInputs(parseResultSections: "set header name", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); SetHeaderCommand setHeaderCommand = new SetHeaderCommand(new NullTelemetry()); bool? result = setHeaderCommand.CanHandle(shellState, httpState, parseResult); Assert.True(result); } [Fact] public void GetHelpSummary_ReturnsDescription() { ArrangeInputs(parseResultSections: string.Empty, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult _); SetHeaderCommand setHeaderCommand = new SetHeaderCommand(new NullTelemetry()); string result = setHeaderCommand.GetHelpSummary(shellState, httpState); Assert.Equal(SetHeaderCommand.Description, result); } [Fact] public void GetHelpDetails_ReturnsHelpDetails() { ArrangeInputs(parseResultSections: "set header", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); string expected = "\u001b[1mUsage: \u001b[39mset header {name} [value]" + Environment.NewLine + Environment.NewLine + "Sets or clears a header. When [value] is empty the header is cleared." + Environment.NewLine; SetHeaderCommand setHeaderCommand = new SetHeaderCommand(new NullTelemetry()); string result = setHeaderCommand.GetHelpDetails(shellState, httpState, parseResult); Assert.Equal(expected, result); } [Fact] public async Task ExecuteAsync_WithExactlyThreeValidParseResultSections_DoesNotUpdateHeaders() { ArrangeInputs(parseResultSections: "set header test", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); SetHeaderCommand setHeaderCommand = new SetHeaderCommand(new NullTelemetry()); await setHeaderCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Dictionary> headers = httpState.Headers; KeyValuePair> firstHeader = headers.First(); Assert.Single(httpState.Headers); Assert.Equal("User-Agent", firstHeader.Key); Assert.Equal("HTTP-REPL", firstHeader.Value.First()); } [Fact] public async Task ExecuteAsync_WithMoreThanThreeValidParseResultSections_AddsEntryToHeaders() { ArrangeInputs(parseResultSections: "set header name value1 value2", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); SetHeaderCommand setHeaderCommand = new SetHeaderCommand(new NullTelemetry()); await setHeaderCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Dictionary> headers = httpState.Headers; Assert.Equal(2, httpState.Headers.Count); Assert.True(headers.ContainsKey("User-Agent")); Assert.True(headers.ContainsKey("name")); headers.TryGetValue("User-Agent", out IEnumerable userAgentHeaderValues); headers.TryGetValue("name", out IEnumerable nameHeaderValues); Assert.Contains("HTTP-REPL", userAgentHeaderValues); Assert.Contains("value1", nameHeaderValues); Assert.Contains("value2", nameHeaderValues); } [Fact] public void Suggest_WithNoParseResultSections_ReturnsName() { ArrangeInputs(parseResultSections: string.Empty, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); SetHeaderCommand setHeaderCommand = new SetHeaderCommand(new NullTelemetry()); IEnumerable suggestions = setHeaderCommand.Suggest(shellState, httpState, parseResult); Assert.Single(suggestions); Assert.Equal("set", suggestions.First()); } [Fact] public void Suggest_WithOneParseResultSectionAndSelectedSectionGreaterAtZero_ReturnsName() { ArrangeInputs(parseResultSections: "set", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, caretPosition: 0); SetHeaderCommand setHeaderCommand = new SetHeaderCommand(new NullTelemetry()); IEnumerable suggestions = setHeaderCommand.Suggest(shellState, httpState, parseResult); Assert.Single(suggestions); Assert.Equal("set", suggestions.First()); } [Fact] public void Suggest_WithOneParseResultSectionAndSelectedSectionGreaterThanZero_ReturnsName() { ArrangeInputs(parseResultSections: "set", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, caretPosition: 3); SetHeaderCommand setHeaderCommand = new SetHeaderCommand(new NullTelemetry()); IEnumerable suggestions = setHeaderCommand.Suggest(shellState, httpState, parseResult); Assert.Single(suggestions); Assert.Equal("set", suggestions.First()); } [Fact] public void Suggest_WithMoreThanOneParseResultSectionAndSelectedSectionGreaterThanZero_ReturnsName() { ArrangeInputs(parseResultSections: "set header", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, caretPosition: 10); SetHeaderCommand setHeaderCommand = new SetHeaderCommand(new NullTelemetry()); IEnumerable suggestions = setHeaderCommand.Suggest(shellState, httpState, parseResult); Assert.Single(suggestions); Assert.Equal("header", suggestions.First()); } [Fact] public void Suggest_WithMoreThanTwoParseResultSectionsAndSelectedSectionGreaterThanTwo_ReturnsHeaderCompletion() { ArrangeInputs(parseResultSections: "set header O", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, caretPosition: 12); SetHeaderCommand setHeaderCommand = new SetHeaderCommand(new NullTelemetry()); IEnumerable suggestions = setHeaderCommand.Suggest(shellState, httpState, parseResult); Assert.Single(suggestions); Assert.Equal("Origin", suggestions.First()); } [Fact] public void Suggest_WithMoreThanThreeParseResultSectionsAndSelectedSectionAtThree_ReturnsValueCompletion() { ArrangeInputs(parseResultSections: "set header CONTENT-TYPE t", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, caretPosition: 25); IDirectoryStructure directoryStructure = GetDirectoryStructure("testMethod", "testContentType", "testBody"); ApiDefinition apiDefinition = new ApiDefinition(); apiDefinition.DirectoryStructure = directoryStructure; httpState.ApiDefinition = apiDefinition; httpState.BaseAddress = new Uri("http://localhost:5050/"); SetHeaderCommand setHeaderCommand = new SetHeaderCommand(new NullTelemetry()); List suggestions = setHeaderCommand.Suggest(shellState, httpState, parseResult).ToList(); Assert.Single(suggestions); Assert.Equal("testContentType", suggestions.First()); } [Fact] public void Suggest_WithMoreThanThreeParseResultSectionsAndNoMatchingCompletions_ReturnsNothing() { ArrangeInputs(parseResultSections: "set header CONTENT-TYPE z", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, caretPosition: 25); IDirectoryStructure directoryStructure = GetDirectoryStructure("testMethod", "testContentType", "testBody"); ApiDefinition apiDefinition = new ApiDefinition(); apiDefinition.DirectoryStructure = directoryStructure; httpState.ApiDefinition = apiDefinition; httpState.BaseAddress = new Uri("http://localhost:5050/"); SetHeaderCommand setHeaderCommand = new SetHeaderCommand(new NullTelemetry()); IEnumerable suggestions = setHeaderCommand.Suggest(shellState, httpState, parseResult); Assert.Empty(suggestions); } [Fact] public async Task ExecuteAsync_WithKnownHeader_SendsTelemetryWithHeaderName() { ArrangeInputs(parseResultSections: "set header Authorization value", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); TelemetryCollector telemetry = new TelemetryCollector(); SetHeaderCommand setHeaderCommand = new SetHeaderCommand(telemetry); await setHeaderCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.Single(telemetry.Telemetry); TelemetryCollector.CollectedTelemetry collectedTelemetry = telemetry.Telemetry[0]; Assert.Equal("SetHeader", collectedTelemetry.EventName); Assert.Equal("Authorization", collectedTelemetry.Properties["HeaderName"]); Assert.Equal("False", collectedTelemetry.Properties["IsValueEmpty"]); } [Fact] public async Task ExecuteAsync_WithUnknownHeader_SendsTelemetryWithHashedHeaderName() { ArrangeInputs(parseResultSections: "set header name value", out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult); TelemetryCollector telemetry = new TelemetryCollector(); SetHeaderCommand setHeaderCommand = new SetHeaderCommand(telemetry); await setHeaderCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.Single(telemetry.Telemetry); TelemetryCollector.CollectedTelemetry collectedTelemetry = telemetry.Telemetry[0]; Assert.Equal("SetHeader", collectedTelemetry.EventName); Assert.Equal(Sha256Hasher.Hash("name"), collectedTelemetry.Properties["HeaderName"]); Assert.Equal("False", collectedTelemetry.Properties["IsValueEmpty"]); } private IDirectoryStructure GetDirectoryStructure(string method, string contentType, string body) { RequestInfo requestInfo = new RequestInfo(); requestInfo.SetRequestBody(method, contentType, body); DirectoryStructure directoryStructure = new DirectoryStructure(null); DirectoryStructure childDirectoryStructure = directoryStructure.DeclareDirectory(contentType); childDirectoryStructure.RequestInfo = requestInfo; return childDirectoryStructure; } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/Commands/TreeNodeTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using Microsoft.HttpRepl.Commands; using Xunit; namespace Microsoft.HttpRepl.Tests.Commands { public class TreeNodeTests { [Fact] public void Constructor_WithNullFormatter_ThrowsArgumentNullException() { Formatter formatter = null; string prefix = ""; string entry = ""; Assert.Throws(() => new TreeNode(formatter, prefix, entry)); } [Fact] public void Constructor_WithNullPrefix_ThrowsArgumentNullException() { Formatter formatter = new Formatter(); string prefix = null; string entry = ""; Assert.Throws(() => new TreeNode(formatter, prefix, entry)); } [Fact] public void AddChild_WithNullPrefix_ThrowsArgumentNullException() { Formatter parentFormatter = new Formatter(); string parentPrefix = ""; string parentEntry = ""; TreeNode treeNode = new TreeNode(parentFormatter, parentPrefix, parentEntry); string childPrefix = null; string childEntry = ""; Assert.Throws(() => treeNode.AddChild(childPrefix, childEntry)); } [Fact] public void AddChild_Valid_ReturnedChildAddedToChildren() { Formatter parentFormatter = new Formatter(); string parentPrefix = ""; string parentEntry = ""; TreeNode treeNode = new TreeNode(parentFormatter, parentPrefix, parentEntry); string childPrefix = ""; string childEntry = ""; TreeNode childTreeNode = treeNode.AddChild(childPrefix, childEntry); Assert.Contains(childTreeNode, treeNode.Children); } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/Commands/UICommandTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Microsoft.HttpRepl.Commands; using Microsoft.HttpRepl.Fakes; using Microsoft.HttpRepl.Preferences; using Microsoft.HttpRepl.Resources; using Microsoft.HttpRepl.UserProfile; using Microsoft.Repl.Parsing; using Moq; using Xunit; namespace Microsoft.HttpRepl.Tests.Commands { public class UICommandTests : CommandTestsBase { [Fact] public void CanHandle_WithNoParseResultSections_ReturnsNull() { ArrangeInputs(commandText: string.Empty, baseAddress: null, path: null, urlsWithResponse: null, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out _, out IPreferences preferences); UICommand uiCommand = new UICommand(new UriLauncher(), preferences); bool? result = uiCommand.CanHandle(shellState, httpState, parseResult); Assert.Null(result); } [Fact] public void CanHandle_WithMoreThanOneParseResultSections_ReturnsTrue() { ArrangeInputs(commandText: "ui test", baseAddress: null, path: null, urlsWithResponse: null, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out _, out IPreferences preferences); UICommand uiCommand = new UICommand(new UriLauncher(), preferences); bool? result = uiCommand.CanHandle(shellState, httpState, parseResult); Assert.True(result); } [Fact] public void CanHandle_WithInvalidName_ReturnsNull() { ArrangeInputs(commandText: "test", baseAddress: null, path: null, urlsWithResponse: null, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out _, out IPreferences preferences); UICommand uiCommand = new UICommand(new UriLauncher(), preferences); bool? result = uiCommand.CanHandle(shellState, httpState, parseResult); Assert.Null(result); } [Fact] public void CanHandle_WithValidName_ReturnsTrue() { ArrangeInputs(commandText: "ui", baseAddress: null, path: null, urlsWithResponse: null, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out _, out IPreferences preferences); UICommand uiCommand = new UICommand(new UriLauncher(), preferences); bool? result = uiCommand.CanHandle(shellState, httpState, parseResult); Assert.True(result); } [Fact] public void GetHelpSummary_ReturnsHelpSummary() { ArrangeInputs(commandText: string.Empty, baseAddress: null, path: null, urlsWithResponse: null, out MockedShellState shellState, out HttpState httpState, out _, out _, out IPreferences preferences); UICommand uiCommand = new UICommand(new UriLauncher(), preferences); string result = uiCommand.GetHelpSummary(shellState, httpState); Assert.Equal(Strings.UICommand_HelpSummary, result); } [Fact] public void GetHelpDetails_WithInvalidParseResultSection_ReturnsNull() { ArrangeInputs(commandText: "section1", baseAddress: null, path: null, urlsWithResponse: null, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out _, out IPreferences preferences); UICommand uiCommand = new UICommand(new UriLauncher(), preferences); string result = uiCommand.GetHelpDetails(shellState, httpState, parseResult); Assert.Null(result); } [Fact] public async Task ExecuteAsync_WithHttpStateBaseAddressSetToNull_WritesToConsoleManagerError() { MockedShellState shellState = new MockedShellState(); ICoreParseResult parseResult = CoreParseResultHelper.Create("ui"); HttpState httpState = GetHttpState(out _, out IPreferences preferences); httpState.BaseAddress = null; Mock mockLauncher = new Mock(); UICommand uiCommand = new UICommand(mockLauncher.Object, preferences); await uiCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); VerifyErrorMessageWasWrittenToConsoleManagerError(shellState); } [Fact] public async Task ExecuteAsync_WithValidHttpStateBaseAddress_VerifyLaunchUriAsyncWasCalledOnce() { ArrangeInputs(commandText: "ui", baseAddress: null, path: null, urlsWithResponse: null, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out _, out IPreferences preferences); Uri uri = new Uri("https://localhost:44366/"); httpState.BaseAddress = uri; Mock mockLauncher = new Mock(); UICommand uiCommand = new UICommand(mockLauncher.Object, preferences); mockLauncher.Setup(s => s.LaunchUriAsync(It.IsAny())) .Returns(Task.CompletedTask); await uiCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); mockLauncher.Verify(l => l.LaunchUriAsync(It.Is(u => u.AbsoluteUri == "https://localhost:44366/swagger")), Times.Once()); } [Fact] public async Task ExecuteAsync_WithLaunchUriFailure_ThrowsException() { ArrangeInputs(commandText: "ui", baseAddress: null, path: null, urlsWithResponse: null, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out _, out IPreferences preferences); Uri uri = new Uri("https://localhost:44366/"); httpState.BaseAddress = uri; Mock mockLauncher = new Mock(); string expectedErrorMessage = "Unable to launch https://localhost:44366/swagger"; mockLauncher.Setup(s => s.LaunchUriAsync(It.IsAny())) .Returns(Task.FromException(new Exception(expectedErrorMessage))); UICommand uiCommand = new UICommand(mockLauncher.Object, preferences); var exception = await Record.ExceptionAsync(async () => await uiCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None)); Assert.NotNull(exception); Assert.Equal(expectedErrorMessage, exception.Message); } [Fact] public async Task ExecuteAsync_WithRelativeParameter_VerifyLaunchUriAsyncWasCalledOnce() { ArrangeInputs(commandText: "ui /mySwaggerPath", baseAddress: "https://localhost:44366/", path: null, urlsWithResponse: null, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out _, out IPreferences preferences); Mock mockLauncher = new Mock(); UICommand uiCommand = new UICommand(mockLauncher.Object, preferences); mockLauncher.Setup(s => s.LaunchUriAsync(It.IsAny())) .Returns(Task.CompletedTask); await uiCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); mockLauncher.Verify(l => l.LaunchUriAsync(It.Is(u => u.AbsoluteUri == "https://localhost:44366/mySwaggerPath")), Times.Once()); } [Fact] public async Task ExecuteAsync_WithAbsoluteParameter_VerifyLaunchUriAsyncWasCalledOnce() { ArrangeInputs(commandText: "ui https://localhost:12345/mySwaggerPath", baseAddress: "https://localhost:44366/", path: null, urlsWithResponse: null, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out _, out IPreferences preferences); Mock mockLauncher = new Mock(); UICommand uiCommand = new UICommand(mockLauncher.Object, preferences); mockLauncher.Setup(s => s.LaunchUriAsync(It.IsAny())) .Returns(Task.CompletedTask); await uiCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); mockLauncher.Verify(l => l.LaunchUriAsync(It.Is(u => u.AbsoluteUri == "https://localhost:12345/mySwaggerPath")), Times.Once()); } [Fact] public async Task ExecuteAsync_WithNoParameterAndNoPreference_VerifyLaunchUriAsyncWasCalledOnce() { ArrangeInputs(commandText: "ui", baseAddress: "https://localhost:44366/", path: null, urlsWithResponse: null, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out _, out IPreferences preferences); Mock mockLauncher = new Mock(); UICommand uiCommand = new UICommand(mockLauncher.Object, preferences); mockLauncher.Setup(s => s.LaunchUriAsync(It.IsAny())) .Returns(Task.CompletedTask); await uiCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); mockLauncher.Verify(l => l.LaunchUriAsync(It.Is(u => u.AbsoluteUri == "https://localhost:44366/swagger")), Times.Once()); } [Fact] public async Task ExecuteAsync_WithNoParameterAndRelativePreference_VerifyLaunchUriAsyncWasCalledOnce() { string commandText = "ui"; ICoreParseResult parseResult = CoreParseResultHelper.Create(commandText); MockedShellState shellState = new MockedShellState(); MockedFileSystem fileSystem = new MockedFileSystem(); UserFolderPreferences preferences = new UserFolderPreferences(fileSystem, new UserProfileDirectoryProvider(), null); preferences.SetValue(WellKnownPreference.SwaggerUIEndpoint, "/mySwaggerPath"); HttpState httpState = new HttpState(preferences, new HttpClient()); httpState.BaseAddress = new Uri("https://localhost:44366", UriKind.Absolute); Mock mockLauncher = new Mock(); UICommand uiCommand = new UICommand(mockLauncher.Object, preferences); mockLauncher.Setup(s => s.LaunchUriAsync(It.IsAny())) .Returns(Task.CompletedTask); await uiCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); mockLauncher.Verify(l => l.LaunchUriAsync(It.Is(u => u.AbsoluteUri == "https://localhost:44366/mySwaggerPath")), Times.Once()); } [Fact] public async Task ExecuteAsync_WithNoParameterAndAbsolutePreference_VerifyLaunchUriAsyncWasCalledOnce() { string commandText = "ui"; ICoreParseResult parseResult = CoreParseResultHelper.Create(commandText); MockedShellState shellState = new MockedShellState(); MockedFileSystem fileSystem = new MockedFileSystem(); UserFolderPreferences preferences = new UserFolderPreferences(fileSystem, new UserProfileDirectoryProvider(), null); preferences.SetValue(WellKnownPreference.SwaggerUIEndpoint, "https://localhost:12345/mySwaggerPath"); HttpState httpState = new HttpState(preferences, new HttpClient()); httpState.BaseAddress = new Uri("https://localhost:44366", UriKind.Absolute); Mock mockLauncher = new Mock(); UICommand uiCommand = new UICommand(mockLauncher.Object, preferences); mockLauncher.Setup(s => s.LaunchUriAsync(It.IsAny())) .Returns(Task.CompletedTask); await uiCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); mockLauncher.Verify(l => l.LaunchUriAsync(It.Is(u => u.AbsoluteUri == "https://localhost:12345/mySwaggerPath")), Times.Once()); } [Fact] public async Task ExecuteAsync_WithInvalidParameterAndNoPreference_DisplaysError() { string invalidParameter = "https:///localhost/swagger"; string expectedError = string.Format(Strings.UICommand_InvalidParameter, invalidParameter); ArrangeInputs(commandText: $"ui {invalidParameter}", baseAddress: "https://localhost:44366/", path: null, urlsWithResponse: null, out MockedShellState shellState, out HttpState httpState, out ICoreParseResult parseResult, out _, out IPreferences preferences); Mock mockLauncher = new Mock(); UICommand uiCommand = new UICommand(mockLauncher.Object, preferences); await uiCommand.ExecuteAsync(shellState, httpState, parseResult, CancellationToken.None); Assert.Equal(expectedError, shellState.ErrorMessage, StringComparer.Ordinal); } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/FileSystem/RealFileSystemTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.IO; using Microsoft.HttpRepl.FileSystem; using Xunit; namespace Microsoft.HttpRepl.Tests.FileSystem { public class RealFileSystemTests { [Theory] [InlineData(".json")] [InlineData(".xml")] [InlineData(".tmp")] [InlineData(".a")] public void GetTempFileName_WithValidInput_ReturnsFileNameWithExtension(string extension) { RealFileSystem realFileSystem = new RealFileSystem(); string fullName = realFileSystem.GetTempFileName(extension); Assert.NotNull(fullName); Assert.EndsWith(extension, fullName, StringComparison.OrdinalIgnoreCase); } [Theory] [InlineData(".json")] [InlineData(".xml")] [InlineData(".tmp")] [InlineData(".a")] public void GetTempFileName_WithValidInput_ReturnsFileInTempPath(string extension) { RealFileSystem realFileSystem = new RealFileSystem(); string expectedPath = Path.GetTempPath(); string actualPath = realFileSystem.GetTempFileName(extension); Assert.StartsWith(expectedPath, actualPath, StringComparison.OrdinalIgnoreCase); } [Theory] [InlineData(".json")] [InlineData(".xml")] [InlineData(".tmp")] [InlineData(".a")] public void GetTempFileName_WithValidInput_ReturnsFileThatStartsWithHttpRepl(string extension) { RealFileSystem realFileSystem = new RealFileSystem(); string expectedStart = "HttpRepl."; string fullName = realFileSystem.GetTempFileName(extension); string actualFileName = Path.GetFileName(fullName); Assert.StartsWith(expectedStart, actualFileName, StringComparison.OrdinalIgnoreCase); } [Fact] public void GetTempFileName_WithNullExtension_ThrowsArgumentNullException() { RealFileSystem realFileSystem = new RealFileSystem(); Assert.Throws(() => realFileSystem.GetTempFileName(null)); } [Theory] [InlineData("")] [InlineData(".")] [InlineData(",a")] public void GetTEmpFileName_WithInvalidInput_ThrowsArgumentException(string extension) { RealFileSystem realFileSystem = new RealFileSystem(); Assert.Throws(() => realFileSystem.GetTempFileName(extension)); } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/HttpStateTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using Microsoft.HttpRepl.Fakes; using Microsoft.HttpRepl.FileSystem; using Microsoft.HttpRepl.OpenApi; using Microsoft.HttpRepl.Preferences; using Microsoft.HttpRepl.UserProfile; using Xunit; namespace Microsoft.HttpRepl.Tests { public class HttpStateTests { [Fact] public void GetRelativePathString_EmptyPathSections_Slash() { string expected = "/"; HttpState state = SetupHttpState(); state.PathSections.Clear(); string result = state.GetRelativePathString(); Assert.Equal(expected, result); } [Fact] public void GetRelativePathString_SinglePathSection_CorrectString() { string expected = "/FirstDirectory"; HttpState state = SetupHttpState(); state.PathSections.Push("FirstDirectory"); string result = state.GetRelativePathString(); Assert.Equal(expected, result); } [Fact] public void GetRelativePathString_MultiplePathSections_CorrectString() { string expected = "/FirstDirectory/SecondDirectory"; HttpState state = SetupHttpState(); state.PathSections.Push("FirstDirectory"); state.PathSections.Push("SecondDirectory"); string result = state.GetRelativePathString(); Assert.Equal(expected, result); } [Fact] public void GetApplicableContentTypes_NoBaseAddress_ReturnsNull() { HttpState httpState = SetupHttpState(); httpState.BaseAddress = null; IEnumerable result = httpState.GetApplicableContentTypes(null, string.Empty); Assert.Null(result); } [Fact] public void GetApplicableContentTypes_NoStructure_ReturnsNull() { HttpState httpState = SetupHttpState(); httpState.BaseAddress = new Uri("https://localhost/"); httpState.ApiDefinition = null; IEnumerable result = httpState.GetApplicableContentTypes(null, string.Empty); Assert.Null(result); } [Fact] public void GetApplicableContentTypes_NoMethod_ReturnsAll() { DirectoryStructure directoryStructure = new DirectoryStructure(null); RequestInfo requestInfo = new RequestInfo(); requestInfo.SetRequestBody("GET", "application/json", ""); requestInfo.SetRequestBody("PUT", "application/xml", ""); directoryStructure.RequestInfo = requestInfo; HttpState httpState = SetupHttpState(); httpState.BaseAddress = new Uri("https://localhost/"); ApiDefinition apiDefinition = new ApiDefinition(); apiDefinition.DirectoryStructure = directoryStructure; httpState.ApiDefinition = apiDefinition; IEnumerable result = httpState.GetApplicableContentTypes(null, ""); Assert.NotNull(result); Assert.Equal(2, result.Count()); Assert.Contains("application/json", result, StringComparer.OrdinalIgnoreCase); Assert.Contains("application/xml", result, StringComparer.OrdinalIgnoreCase); } [Fact] public void GetApplicableContentTypes_GetMethod_ReturnsCorrectOne() { DirectoryStructure directoryStructure = new DirectoryStructure(null); RequestInfo requestInfo = new RequestInfo(); requestInfo.SetRequestBody("GET", "application/json", ""); requestInfo.SetRequestBody("PUT", "application/xml", ""); directoryStructure.RequestInfo = requestInfo; HttpState httpState = SetupHttpState(); httpState.BaseAddress = new Uri("https://localhost/"); ApiDefinition apiDefinition = new ApiDefinition(); apiDefinition.DirectoryStructure = directoryStructure; httpState.ApiDefinition = apiDefinition; IEnumerable result = httpState.GetApplicableContentTypes("GET", ""); Assert.Single(result); Assert.Contains("application/json", result, StringComparer.OrdinalIgnoreCase); } [Fact] public void GetApplicableContentTypes_WithPath_ReturnsCorrectOne() { DirectoryStructure parentDirectoryStructure = new DirectoryStructure(null); RequestInfo parentRequestInfo = new RequestInfo(); parentRequestInfo.SetRequestBody("GET", "application/json", ""); parentDirectoryStructure.RequestInfo = parentRequestInfo; DirectoryStructure childDirectoryStructure = parentDirectoryStructure.DeclareDirectory("child"); RequestInfo childRequestInfo = new RequestInfo(); childRequestInfo.SetRequestBody("GET", "application/xml", ""); childDirectoryStructure.RequestInfo = childRequestInfo; HttpState httpState = SetupHttpState(); httpState.BaseAddress = new Uri("https://localhost/"); ApiDefinition apiDefinition = new ApiDefinition(); apiDefinition.DirectoryStructure = parentDirectoryStructure; httpState.ApiDefinition = apiDefinition; IEnumerable result = httpState.GetApplicableContentTypes("GET", "child"); Assert.Single(result); Assert.Contains("application/xml", result, StringComparer.OrdinalIgnoreCase); } [Fact] public void GetEffectivePath_NoBaseAddressOrAbsoluteUri_ThrowsArgumentNullException() { Assert.Throws(() => HttpState.GetEffectivePath(null, "", "/NotAnAbsoluteUri", queryParam: null)); } [Theory] [InlineData("https://github.com/", "", "https://localhost/pets", "https://localhost/pets")] [InlineData("https://localhost/", "dir1", "dir2", "https://localhost/dir1/dir2")] [InlineData("https://localhost/", "dir1?q=5&r=6", "dir2?s=7", "https://localhost/dir1/dir2?q=5&r=6&s=7")] [InlineData("https://petstore.swagger.io/v2/", "pet", "", "https://petstore.swagger.io/v2/pet")] [InlineData("https://petstore.swagger.io/v2/", "/pet", "", "https://petstore.swagger.io/pet")] [InlineData("https://petstore.swagger.io/v2/", "", "pet", "https://petstore.swagger.io/v2/pet")] [InlineData("https://petstore.swagger.io/v2/", "", "/pet", "https://petstore.swagger.io/pet")] [InlineData("https://petstore.swagger.io/v2/", "/pet", "/buy", "https://petstore.swagger.io/buy")] [InlineData("https://petstore.swagger.io/", "pet", "", "https://petstore.swagger.io/pet")] [InlineData("https://petstore.swagger.io/", "/pet", "", "https://petstore.swagger.io/pet")] [InlineData("https://petstore.swagger.io/", "", "pet", "https://petstore.swagger.io/pet")] [InlineData("https://petstore.swagger.io/", "", "/pet", "https://petstore.swagger.io/pet")] public void GetEffectivePath_ProperConcatenation(string baseUriString, string pathSections, string specifiedPath, string expectedResult) { Uri baseUri = new Uri(baseUriString); Uri result = HttpState.GetEffectivePath(baseUri, pathSections, specifiedPath, queryParam: null); Assert.Equal(expectedResult, result.ToString(), StringComparer.OrdinalIgnoreCase); } [Fact] public void GetEffectivePath_AppendQueryString_SingleCase() { string baseUriString = "https://github.com/"; string pathSections = "dir1"; string specifiedPath = "dir2"; var queryString = new Dictionary>() { ["key"] = new List() { "value"} }; Uri baseUri = new Uri(baseUriString); Uri result = HttpState.GetEffectivePath(baseUri, pathSections, specifiedPath, queryString); Assert.Equal("https://github.com/dir1/dir2?key=value", result.ToString(), StringComparer.OrdinalIgnoreCase); } [Fact] public void GetEffectivePath_AppendQueryString_MultipleCase() { string baseUriString = "https://github.com/"; string pathSections = "dir1"; string specifiedPath = "dir2"; var queryString = new Dictionary>() { ["key1"] = new List() { "value" }, ["key2"] = new List() { "value" }, }; Uri baseUri = new Uri(baseUriString); Uri result = HttpState.GetEffectivePath(baseUri, pathSections, specifiedPath, queryString); Assert.Equal("https://github.com/dir1/dir2?key1=value&key2=value", result.ToString(), StringComparer.OrdinalIgnoreCase); } [Fact] public void GetEffectivePath_AppendQueryString_Multiple_SameKey() { string baseUriString = "https://github.com/"; string pathSections = "dir1"; string specifiedPath = "dir2"; var queryString = new Dictionary>() { ["key1"] = new List() { "value", "anotherValue" }, }; Uri baseUri = new Uri(baseUriString); Uri result = HttpState.GetEffectivePath(baseUri, pathSections, specifiedPath, queryString); Assert.Equal("https://github.com/dir1/dir2?key1=value&key1=anotherValue", result.ToString(), StringComparer.OrdinalIgnoreCase); } [Fact] public void GetEffectivePath_AppendQueryString_EncodeUriValues () { string baseUriString = "https://github.com/"; string pathSections = "dir1"; string specifiedPath = "dir2"; var queryString = new Dictionary>() { ["key1"] = new List() { "a+b=c" }, }; Uri baseUri = new Uri(baseUriString); Uri result = HttpState.GetEffectivePath(baseUri, pathSections, specifiedPath, queryString); Assert.Equal("https://github.com/dir1/dir2?key1=a%2Bb%3Dc", result.ToString(), StringComparer.OrdinalIgnoreCase); } [Fact] public void GetEffectivePath_AppendQueryString_EncodeUriKeys () { string baseUriString = "https://github.com/"; string pathSections = "dir1"; string specifiedPath = "dir2"; var queryString = new Dictionary>() { ["a+b=c"] = new List() { "value1" }, }; Uri baseUri = new Uri(baseUriString); Uri result = HttpState.GetEffectivePath(baseUri, pathSections, specifiedPath, queryString); Assert.Equal("https://github.com/dir1/dir2?a%2Bb%3Dc=value1", result.ToString(), StringComparer.OrdinalIgnoreCase); } [Fact] public void GetEffectivePath_NullBaseAddressAndNoPath_Throws() { HttpState httpState = SetupHttpState(); httpState.BaseAddress = null; Assert.Throws("baseAddress", () => httpState.GetEffectivePathWithoutQueryParam("")); } [Fact] public void GetEffectivePathForPrompt_NullBaseAddress_ReturnsNull() { HttpState httpState = SetupHttpState(); httpState.BaseAddress = null; Uri result = httpState.GetEffectivePathForPrompt(); Assert.Null(result); } [Fact] public void HeaderSetup_WithDefaultUserAgent_UsesHttpRepl() { HttpState httpState = SetupHttpState(preferencesFileContent: ""); Assert.Equal("HTTP-REPL", httpState.Headers["User-Agent"].Single(), StringComparer.Ordinal); } [Fact] public void HeaderSetup_WithCustomUserAgent_UsesCustom() { string differentUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3875.0 Safari/537.36 Edg/78.0.245.0"; HttpState httpState = SetupHttpState(preferencesFileContent: $"{WellKnownPreference.HttpClientUserAgent}={differentUserAgent}"); Assert.Equal(differentUserAgent, httpState.Headers["User-Agent"].Single(), StringComparer.Ordinal); } private static HttpState SetupHttpState(string preferencesFileContent = null) { UserProfileDirectoryProvider userProfileDirectoryProvider = new UserProfileDirectoryProvider(); IFileSystem fileSystem; if (preferencesFileContent != null) { fileSystem = new MockedFileSystem(); } else { fileSystem = new FileSystemStub(); } UserFolderPreferences preferences = new UserFolderPreferences(fileSystem, userProfileDirectoryProvider, null); if (preferencesFileContent != null) { ((MockedFileSystem)fileSystem).AddFile(preferences.PreferencesFilePath, preferencesFileContent); } HttpClient client = new HttpClient(); HttpState state = new HttpState(preferences, client); return state; } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/JsonVisitorTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using Microsoft.HttpRepl.Formatting; using Microsoft.HttpRepl.Preferences; using Microsoft.Repl.ConsoleHandling; using Xunit; namespace Microsoft.HttpRepl.Tests { public class JsonVisitorTests { [Fact] public void JsonVisitor_ObjectWithComments() { string testData = @"[ { //Object 1 ""property"": ""value"", ""and"": ""again"" }, { //Object 2 }, [ //An array ], null, 1, 3.2, ""test"", false ]"; string formatted = JsonVisitor.FormatAndColorize(new MockJsonConfig(), testData); } private class MockJsonConfig : IJsonConfig { public int IndentSize => 2; public AllowedColors DefaultColor => AllowedColors.None; public AllowedColors ArrayBraceColor => AllowedColors.None; public AllowedColors ObjectBraceColor => AllowedColors.None; public AllowedColors CommaColor => AllowedColors.None; public AllowedColors NameColor => AllowedColors.None; public AllowedColors NameSeparatorColor => AllowedColors.None; public AllowedColors BoolColor => AllowedColors.None; public AllowedColors NumericColor => AllowedColors.None; public AllowedColors StringColor => AllowedColors.None; public AllowedColors NullColor => AllowedColors.None; } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/Microsoft.HttpRepl.Tests.csproj ================================================ $(TestProjectTargetFramework) false ================================================ FILE: test/Microsoft.HttpRepl.Tests/OpenApi/ApiDefinitionBuilder.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using Microsoft.HttpRepl.OpenApi; namespace Microsoft.HttpRepl.Tests.OpenApi { internal class ApiDefinitionBuilder { private readonly List _baseAddresses = new(); private readonly List> _directories = new(); private ApiDefinitionBuilder() { } public static ApiDefinitionBuilder Start() => new ApiDefinitionBuilder(); public ApiDefinitionBuilder AddBaseAddress(string url, string description) { _baseAddresses.Add(new ApiDefinitionBaseAddress(url, description)); return this; } public DirectoryBuilder AddDirectory(string directory) { DirectoryBuilder directoryBuilder = new(this, directory); _directories.Add(directoryBuilder); return directoryBuilder; } public ApiDefinition Build() { ApiDefinition apiDefinition = new(); apiDefinition.DirectoryStructure = new DirectoryStructure(null); foreach (ApiDefinitionBaseAddress baseAddress in _baseAddresses) { apiDefinition.BaseAddresses.Add(new ApiDefinition.Server() { Url = baseAddress.Url, Description = baseAddress.Description }); } foreach (DirectoryBuilder subDirectoryBuilder in _directories) { DirectoryStructure subdirectory = ((DirectoryStructure)apiDefinition.DirectoryStructure).DeclareDirectory(subDirectoryBuilder.Name); BuildDirectory(subDirectoryBuilder, subdirectory); } return apiDefinition; } private void BuildDirectory(DirectoryBuilder directoryBuilder, DirectoryStructure directoryStructure) { if (directoryBuilder.Methods.Count > 0) { RequestInfo requestInfo = new(); foreach (string method in directoryBuilder.Methods) { requestInfo.AddMethod(method); } directoryStructure.RequestInfo = requestInfo; } foreach (DirectoryBuilder> subDirectoryBuilder in directoryBuilder.Directories) { DirectoryStructure subdirectory = directoryStructure.DeclareDirectory(subDirectoryBuilder.Name); BuildDirectory(subDirectoryBuilder, subdirectory); } } private class ApiDefinitionBaseAddress { public ApiDefinitionBaseAddress(string url, string description) { Url = new Uri(url); Description = description; } public Uri Url { get; } public string Description { get; } } internal class DirectoryBuilder { private T _parent; private List>> _directories = new(); private List _methods = new(); public DirectoryBuilder(T parent, string name) { _parent = parent; Name = name; } public IReadOnlyList>> Directories => _directories; public IReadOnlyList Methods => _methods; public string Name { get; } public DirectoryBuilder> AddDirectory(string directory) { DirectoryBuilder> directoryBuilder = new(this, directory); _directories.Add(directoryBuilder); return directoryBuilder; } public DirectoryBuilder AddMethod(string method) { _methods.Add(method); return this; } public DirectoryBuilder WithGet() { return AddMethod("Get"); } public DirectoryBuilder WithPost() { return AddMethod("Post"); } public DirectoryBuilder WithPatch() { return AddMethod("Patch"); } public DirectoryBuilder WithDelete() { return AddMethod("Delete"); } public T Finalize() { return _parent; } } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/OpenApi/ApiDefinitionReaderTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using Microsoft.HttpRepl.Fakes; using Microsoft.HttpRepl.OpenApi; using Xunit; namespace Microsoft.HttpRepl.Tests.OpenApi { public class ApiDefinitionReaderTests { [Fact] public void Read_WithJObjectFormatNotSupportedByAnyExistingReader_ReturnsNull() { string json = @"{ ""info"": { ""version"": ""v1"", ""title"": ""My API"" } }"; ApiDefinitionReader apiDefinitionReader = new ApiDefinitionReader(); ApiDefinitionParseResult result = apiDefinitionReader.Read(json, null); Assert.False(result.Success); } [Fact] public void RegisterReader_AddNewReader_VerifyReadReturnsApiDefinitionWithStructure() { string json = @"{ ""fakeApi"": ""1.0.0"", ""info"": { ""version"": ""v1"" } }"; ApiDefinition apiDefinition = new ApiDefinition() { DirectoryStructure = new DirectoryStructure(null) }; ApiDefinitionReaderStub apiDefinitionReaderStub = new ApiDefinitionReaderStub(apiDefinition); ApiDefinitionReader reader = new ApiDefinitionReader(); reader.RegisterReader(apiDefinitionReaderStub); ApiDefinitionParseResult result = reader.Read(json, null); Assert.Same(apiDefinition, result.ApiDefinition); } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/OpenApi/OpenApiDotNetApiDefinitionReaderTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Collections.Generic; using System.IO; using System.Reflection; using System.Threading.Tasks; using Microsoft.HttpRepl.OpenApi; using Xunit; namespace Microsoft.HttpRepl.Tests.OpenApi { public class OpenApiDotNetApiDefinitionReaderTests { [Theory] [MemberData(nameof(GetYamlResourcePaths), MemberType = typeof(OpenApiDotNetApiDefinitionReaderTests))] [MemberData(nameof(GetJsonResourcePaths), MemberType = typeof(OpenApiDotNetApiDefinitionReaderTests))] public async Task CanHandle_RealOpenApiDescriptions_ReturnsNotNull(string resourcePath) { // Arrange string content = await GetResourceContent(resourcePath); OpenApiDotNetApiDefinitionReader apiDefinitionReader = new(); // Act ApiDefinitionParseResult actual = apiDefinitionReader.CanHandle(content); // Assert Assert.True(actual.Success); } [Theory] [MemberData(nameof(GetYamlTestSetups), MemberType = typeof(OpenApiDotNetApiDefinitionReaderTests))] [MemberData(nameof(GetJsonTestSetups), MemberType = typeof(OpenApiDotNetApiDefinitionReaderTests))] public async Task ReadDefinition_RealOpenApiDescriptions_ReturnsExpectedDocument(string resourcePath, ApiDefinition expected) { // Arrange string content = await GetResourceContent(resourcePath); OpenApiDotNetApiDefinitionReader apiDefinitionReader = new(); // Act ApiDefinitionParseResult actual = apiDefinitionReader.ReadDefinition(content, null); // Assert AssertDefinition(expected, actual.ApiDefinition); } [Fact] public void CanHandle_WithMissingInfoAndPaths_ReturnsValidationMessages() { // Arrange string json = @" { ""openapi"": ""3.0.0"" } "; OpenApiDotNetApiDefinitionReader apiDefinitionReader = new(); // Act ApiDefinitionParseResult result = apiDefinitionReader.CanHandle(json); // Assert Assert.True(result.Success); Assert.NotEmpty(result.ValidationMessages); } private void AssertDefinition(ApiDefinition expected, ApiDefinition actual) { // Core properties Assert.NotNull(actual); Assert.NotNull(actual.BaseAddresses); Assert.NotNull(actual.DirectoryStructure); // Base Addresses/Servers IEnumerator expectedBaseAddressEnumerator = expected.BaseAddresses.GetEnumerator(); IEnumerator actualBaseAddressEnumerator = actual.BaseAddresses.GetEnumerator(); int index = 0; while (expectedBaseAddressEnumerator.MoveNext()) { Assert.True(actualBaseAddressEnumerator.MoveNext(), $"Missing BaseAddress: {expectedBaseAddressEnumerator.Current}"); Assert.True(expectedBaseAddressEnumerator.Current.Url == actualBaseAddressEnumerator.Current.Url, $"BaseAddress[{index}].Url does not match."); Assert.True(string.Equals(expectedBaseAddressEnumerator.Current.Description, actualBaseAddressEnumerator.Current.Description, System.StringComparison.InvariantCulture), $"BaseAddress[{index}].Description does not match."); index++; } Assert.False(actualBaseAddressEnumerator.MoveNext(), $"Extra BaseAddress: {actualBaseAddressEnumerator.Current}"); // Directory Structure AssertDirectoryStructure(expected.DirectoryStructure, actual.DirectoryStructure, "/"); } private void AssertDirectoryStructure(IDirectoryStructure expected, IDirectoryStructure actual, string path) { // Request Info/Methods if (expected.RequestInfo is null) { Assert.True(actual.RequestInfo is null, $"RequestInfo should be null on {path}"); } else { AssertRequestInfo(expected.RequestInfo, actual.RequestInfo, path); } // Subdirectories IEnumerator expectedDirectoriesEnumerator = expected.DirectoryNames.GetEnumerator(); IEnumerator actualDirectoriesEnumerator = actual.DirectoryNames.GetEnumerator(); int directoryIndex = 0; while (expectedDirectoriesEnumerator.MoveNext()) { Assert.True(actualDirectoriesEnumerator.MoveNext(), $"Missing subdirectory in {path}: {expectedDirectoriesEnumerator.Current}"); Assert.True(string.Equals(expectedDirectoriesEnumerator.Current, actualDirectoriesEnumerator.Current, System.StringComparison.Ordinal), $"Expected Directory \"{expectedDirectoriesEnumerator.Current}\" does not match Actual Directory \"{actualDirectoriesEnumerator.Current}\" at index {directoryIndex} in {path}."); AssertDirectoryStructure(expected.TraverseTo(expectedDirectoriesEnumerator.Current), actual.TraverseTo(actualDirectoriesEnumerator.Current), $"{path}{expectedDirectoriesEnumerator.Current}/"); directoryIndex++; } // Something changed in recent .NET SDKs such that one of these is happening: // 1) The message is being calculated eagerly when it was not before (thus calculating it even when MoveNext returns false) // 2) Current now throws if there is no current, whereas before it did not // #2 is more likely, but I can't prove it in the sources. We'll break out the Assert.False and do the if check // and message construction separately to avoid it. //Assert.False(actualDirectoriesEnumerator.MoveNext(), $"Extra subdirectory in {path}: {actualDirectoriesEnumerator.Current}"); if (actualDirectoriesEnumerator.MoveNext()) { Assert.Fail($"Extra subdirectory in {path}: {actualDirectoriesEnumerator.Current}"); } } private void AssertRequestInfo(IRequestInfo expected, IRequestInfo actual, string path) { // Core object Assert.True(actual is not null, $"RequestInfo should NOT be null on {path}"); // Methods IEnumerator expectedMethodsEnumerator = expected.Methods.GetEnumerator(); IEnumerator actualMethodsEnumerator = actual.Methods.GetEnumerator(); int methodIndex = 0; while (expectedMethodsEnumerator.MoveNext()) { Assert.True(actualMethodsEnumerator.MoveNext(), $"Missing method in {path}: {expectedMethodsEnumerator.Current}"); Assert.True(string.Equals(expectedMethodsEnumerator.Current, actualMethodsEnumerator.Current, System.StringComparison.Ordinal), $"Expected Method \"{expectedMethodsEnumerator.Current}\" does not match Actual Method \"{actualMethodsEnumerator.Current}\" at index {methodIndex} in {path}."); methodIndex++; } Assert.False(actualMethodsEnumerator.MoveNext(), $"Extra method in {path}: {actualMethodsEnumerator.Current}"); } private async Task GetResourceContent(string filePath) { Assembly assembly = Assembly.GetExecutingAssembly(); string resourcePath = assembly.GetName().Name + ".Resources." + filePath.Replace('\\', '.'); using (Stream resourceStream = assembly.GetManifestResourceStream(resourcePath)) using (StreamReader reader = new(resourceStream)) { return await reader.ReadToEndAsync(); } } public static IEnumerable GetJsonTestSetups() { return GetTestSetups(".json"); } public static IEnumerable GetYamlTestSetups() { return GetTestSetups(".yml"); } public static IEnumerable GetJsonResourcePaths() { return GetResourcePaths(".json"); } public static IEnumerable GetYamlResourcePaths() { return GetResourcePaths(".yml"); } private static IEnumerable GetResourcePaths(string extension) { foreach (string resourcePath in ResourcePaths) { yield return new[] { resourcePath + extension }; } } private static string[] ResourcePaths = new[] { "OpenApiDescriptions\\MicrosoftGraph.PowershellSdk.Subscriptions", "OpenApiDescriptions\\MicrosoftGraph.PowershellSdk.Analytics", "OpenApiDescriptions\\MicrosoftGraph.PowershellSdk.CloudCommunications", "OpenApiDescriptions\\xkcd", }; private static IEnumerable GetTestSetups(string extension) { int x = 0; yield return new object[] { ResourcePaths[x++] + extension, ApiDefinitionBuilder.Start() .AddBaseAddress("https://graph.microsoft.com/v1.0/", "Core") .AddDirectory("subscriptions") .WithGet() .WithPost() .AddDirectory("{subscription-id}") .WithGet() .WithPatch() .WithDelete() .Finalize() .Finalize() .Build() }; yield return new object[] { ResourcePaths[x++] + extension, ApiDefinitionBuilder.Start() .AddBaseAddress("https://graph.microsoft.com/v1.0/", "Core") .AddDirectory("users") .AddDirectory("{user-id}") .AddDirectory("insights") .WithGet() .WithPatch() .AddDirectory("shared") .WithGet() .WithPost() .AddDirectory("{sharedInsight-id}") .WithGet() .WithPatch() .AddDirectory("lastSharedMethod") .WithGet() .Finalize() .AddDirectory("resource") .WithGet() .Finalize() .Finalize() .Finalize() .AddDirectory("trending") .WithGet() .WithPost() .AddDirectory("{trending-id}") .WithGet() .WithPatch() .AddDirectory("resource") .WithGet() .Finalize() .Finalize() .Finalize() .AddDirectory("used") .WithGet() .WithPost() .AddDirectory("{usedInsight-id}") .WithGet() .WithPatch() .AddDirectory("resource") .WithGet() .Finalize() .Finalize() .Finalize() .Finalize() .Finalize() .Finalize() .Build() }; yield return new object[] { ResourcePaths[x++] + extension, ApiDefinitionBuilder.Start() .AddBaseAddress("https://graph.microsoft.com/v1.0/", "Core") .AddDirectory("communications") .WithGet() .WithPatch() .AddDirectory("calls") .WithGet() .WithPost() .AddDirectory("{call-id}") .WithGet() .WithPatch() .AddDirectory("microsoft.graph.answer") .WithPost() .Finalize() .AddDirectory("microsoft.graph.changeScreenSharingRole") .WithPost() .Finalize() .AddDirectory("microsoft.graph.keepAlive") .WithPost() .Finalize() .AddDirectory("microsoft.graph.mute") .WithPost() .Finalize() .AddDirectory("microsoft.graph.playPrompt") .WithPost() .Finalize() .AddDirectory("microsoft.graph.recordResponse") .WithPost() .Finalize() .AddDirectory("microsoft.graph.redirect") .WithPost() .Finalize() .AddDirectory("microsoft.graph.reject") .WithPost() .Finalize() .AddDirectory("microsoft.graph.subscribeToTone") .WithPost() .Finalize() .AddDirectory("microsoft.graph.transfer") .WithPost() .Finalize() .AddDirectory("microsoft.graph.unmute") .WithPost() .Finalize() .AddDirectory("microsoft.graph.updateRecordingStatus") .WithPost() .Finalize() .AddDirectory("operations") .WithGet() .WithPost() .AddDirectory("{commsOperation-id}") .WithGet() .WithPatch() .Finalize() .Finalize() .AddDirectory("participants") .WithGet() .WithPost() .AddDirectory("{participant-id}") .WithGet() .WithPatch() .AddDirectory("microsoft.graph.mute") .WithPost() .Finalize() .Finalize() .AddDirectory("microsoft.graph.invite") .WithPost() .Finalize() .Finalize() .Finalize() .AddDirectory("microsoft.graph.logTeleconferenceDeviceQuality") .WithPost() .Finalize() .Finalize() .AddDirectory("onlineMeetings") .WithGet() .WithPost() .AddDirectory("{onlineMeeting-id}") .WithGet() .WithPatch() .Finalize() .Finalize() .Finalize() .Build() }; yield return new object[] { ResourcePaths[x++] + extension, ApiDefinitionBuilder.Start() .AddBaseAddress("http://xkcd.com", null) .AddDirectory("info.0.json") .WithGet() .Finalize() .AddDirectory("{comicId}") .AddDirectory("info.0.json") .WithGet() .Finalize() .Finalize() .Build() }; } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/OpenApi/OpenApiDotNetApiDefinitionReaderV2Tests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Linq; using Microsoft.HttpRepl.OpenApi; using Xunit; namespace Microsoft.HttpRepl.Tests.OpenApi { public class OpenApiDotNetApiDefinitionReaderV2Tests { [Fact] public void ReadMetadata_WithNoPaths_ReturnsApiDefinitionWithNoDirectories() { string json = @"{ ""swagger"": ""2.0"", ""info"": { ""version"": ""v1"" } }"; OpenApiDotNetApiDefinitionReader swaggerV2ApiDefinitionReader = new OpenApiDotNetApiDefinitionReader(); ApiDefinitionParseResult result = swaggerV2ApiDefinitionReader.ReadDefinition(json, null); Assert.NotNull(result.ApiDefinition?.DirectoryStructure); Assert.Empty(result.ApiDefinition.DirectoryStructure.DirectoryNames); } [Fact] public void ReadMetadata_WithNoProperties_ReturnsApiDefinitionWithNoDirectories() { string json = @"{ ""swagger"": ""2.0"", ""info"": { ""version"": ""v1"" }, ""paths"": { } }"; OpenApiDotNetApiDefinitionReader swaggerV2ApiDefinitionReader = new OpenApiDotNetApiDefinitionReader(); ApiDefinitionParseResult result = swaggerV2ApiDefinitionReader.ReadDefinition(json, null); Assert.NotNull(result.ApiDefinition?.DirectoryStructure); Assert.Empty(result.ApiDefinition.DirectoryStructure.DirectoryNames); } [Fact] public void ReadMetadata_WithNoRequestMethods_ReturnsApiDefinitionWithNullRequestInfo() { string json = @"{ ""swagger"": ""2.0"", ""info"": { ""version"": ""v1"" }, ""paths"": { ""/api/Employees"": { } } }"; OpenApiDotNetApiDefinitionReader swaggerV2ApiDefinitionReader = new OpenApiDotNetApiDefinitionReader(); ApiDefinitionParseResult result = swaggerV2ApiDefinitionReader.ReadDefinition(json, null); IDirectoryStructure subDirectory = result.ApiDefinition.DirectoryStructure.TraverseTo("/api/Employees"); Assert.Null(subDirectory.RequestInfo); } [Fact] public void ReadMetadata_WithNoRequestMethods_ReturnsApiDefinitionWithStructure() { string json = @"{ ""swagger"": ""2.0"", ""info"": { ""version"": ""v1"" }, ""paths"": { ""/api/Employees"": { } } }"; OpenApiDotNetApiDefinitionReader swaggerV2ApiDefinitionReader = new OpenApiDotNetApiDefinitionReader(); ApiDefinitionParseResult result = swaggerV2ApiDefinitionReader.ReadDefinition(json, null); Assert.NotNull(result.ApiDefinition?.DirectoryStructure); } [Fact] public void ReadMetadata_WithValidInput_ReturnsApiDefinition() { string json = @"{ ""swagger"": ""2.0"", ""info"": { ""version"": ""v1"" }, ""paths"": { ""/api/Employees"": { ""get"": { ""tags"": [ ""Employees"" ], ""operationId"": ""GetEmployee"", ""consumes"": [], ""produces"": [ ""text/plain"" ], ""parameters"": [], ""responses"": { ""200"": { ""description"": ""Success"" } } }, ""post"": { ""tags"": [ ""Employees"" ], ""operationId"": ""put"", ""consumes"": [], ""produces"": [ ""text/plain"" ], ""parameters"": [ { ""name"": ""id"", ""in"": ""path"" } ], ""responses"": { ""200"": { ""description"": ""Success"" } } } } } }"; OpenApiDotNetApiDefinitionReader swaggerV2ApiDefinitionReader = new OpenApiDotNetApiDefinitionReader(); ApiDefinitionParseResult result = swaggerV2ApiDefinitionReader.ReadDefinition(json, null); Assert.NotNull(result.ApiDefinition?.DirectoryStructure); Assert.Single(result.ApiDefinition.DirectoryStructure.DirectoryNames); Assert.Equal("api", result.ApiDefinition.DirectoryStructure.DirectoryNames.Single()); IDirectoryStructure subDirectory = result.ApiDefinition.DirectoryStructure.TraverseTo("/api/Employees"); Assert.Equal(2, subDirectory.RequestInfo.Methods.Count); Assert.Contains("Get", subDirectory.RequestInfo.Methods, StringComparer.Ordinal); Assert.Contains("Post", subDirectory.RequestInfo.Methods, StringComparer.Ordinal); } [Fact] public void CanHandle_WithNoSwaggerVersionKeyInDocument_ReturnsFalse() { string json = @"{ ""info"": { ""title"": ""OpenAPI v? Spec"", ""version"": ""v1"" }, ""paths"": { } }"; OpenApiDotNetApiDefinitionReader swaggerV2ApiDefinitionReader = new OpenApiDotNetApiDefinitionReader(); ApiDefinitionParseResult result = swaggerV2ApiDefinitionReader.CanHandle(json); Assert.False(result.Success); } [Fact] public void CanHandle_WithValidSwaggerVersionKeyInDocument_ReturnsTrue() { string json = @"{ ""swagger"": ""2.0"", ""info"": { ""title"": ""OpenAPI v2 Spec"", ""version"": ""v1"" }, ""paths"": { } }"; OpenApiDotNetApiDefinitionReader swaggerV2ApiDefinitionReader = new OpenApiDotNetApiDefinitionReader(); ApiDefinitionParseResult result = swaggerV2ApiDefinitionReader.CanHandle(json); Assert.True(result.Success); } [Fact] public void ReadDefinition_WithNoHost_BaseAddressesIsEmpty() { string json = @"{ ""swagger"": ""2.0"", ""info"": { ""version"": ""v1"" }, ""paths"": { } }"; OpenApiDotNetApiDefinitionReader swaggerV2ApiDefinitionReader = new OpenApiDotNetApiDefinitionReader(); ApiDefinitionParseResult result = swaggerV2ApiDefinitionReader.ReadDefinition(json, new Uri("http://localhost/swagger.json")); Assert.NotNull(result.ApiDefinition?.BaseAddresses); Assert.Empty(result.ApiDefinition.BaseAddresses); } [Fact] public void ReadDefinition_WithHostAndOneScheme_BaseAddressesHasOneEntry() { string json = @"{ ""swagger"": ""2.0"", ""info"": { ""version"": ""v1"" }, ""host"": ""localhost"", ""schemes"": [ ""https"" ], ""paths"": { } }"; OpenApiDotNetApiDefinitionReader swaggerV2ApiDefinitionReader = new OpenApiDotNetApiDefinitionReader(); ApiDefinitionParseResult result = swaggerV2ApiDefinitionReader.ReadDefinition(json, new Uri("http://localhost/swagger.json")); Assert.NotNull(result.ApiDefinition?.BaseAddresses); Assert.Single(result.ApiDefinition.BaseAddresses); Assert.Equal("https://localhost/", result.ApiDefinition.BaseAddresses[0].Url.ToString(), StringComparer.Ordinal); } [Fact] public void ReadDefinition_WithHostAndTwoSchemes_BaseAddressesHasTwoEntries() { string json = @"{ ""swagger"": ""2.0"", ""info"": { ""version"": ""v1"" }, ""host"": ""localhost"", ""schemes"": [ ""https"", ""http"" ], ""paths"": { } }"; OpenApiDotNetApiDefinitionReader swaggerV2ApiDefinitionReader = new OpenApiDotNetApiDefinitionReader(); ApiDefinitionParseResult result = swaggerV2ApiDefinitionReader.ReadDefinition(json, new Uri("http://localhost/swagger.json")); Assert.NotNull(result.ApiDefinition?.BaseAddresses); Assert.Equal(2, result.ApiDefinition.BaseAddresses.Count); Assert.Equal("https://localhost/", result.ApiDefinition.BaseAddresses[0].Url.ToString(), StringComparer.Ordinal); Assert.Equal("http://localhost/", result.ApiDefinition.BaseAddresses[1].Url.ToString(), StringComparer.Ordinal); } [Fact] public void ReadDefinition_WithHostAndNoScheme_BaseAddressesHasOneEntryWithSchemeFromSourceUri() { string json = @"{ ""swagger"": ""2.0"", ""info"": { ""version"": ""v1"" }, ""host"": ""localhost"", ""paths"": { } }"; OpenApiDotNetApiDefinitionReader swaggerV2ApiDefinitionReader = new OpenApiDotNetApiDefinitionReader(); ApiDefinitionParseResult result = swaggerV2ApiDefinitionReader.ReadDefinition(json, new Uri("https://localhost/swagger.json")); Assert.NotNull(result.ApiDefinition?.BaseAddresses); Assert.Single(result.ApiDefinition.BaseAddresses); Assert.Equal("https://localhost/", result.ApiDefinition.BaseAddresses[0].Url.ToString(), StringComparer.Ordinal); result = swaggerV2ApiDefinitionReader.ReadDefinition(json, new Uri("http://localhost/swagger.json")); Assert.NotNull(result.ApiDefinition?.BaseAddresses); Assert.Single(result.ApiDefinition.BaseAddresses); Assert.Equal("http://localhost/", result.ApiDefinition.BaseAddresses[0].Url.ToString(), StringComparer.Ordinal); } [Fact] public void ReadDefinition_WithHostAndBaseAndScheme_BaseAddressesHasOneEntry() { string json = @"{ ""swagger"": ""2.0"", ""info"": { ""version"": ""v1"" }, ""host"": ""localhost"", ""basePath"": ""/api/v2"", ""schemes"": [ ""https"" ], ""paths"": { } }"; OpenApiDotNetApiDefinitionReader swaggerV2ApiDefinitionReader = new OpenApiDotNetApiDefinitionReader(); ApiDefinitionParseResult result = swaggerV2ApiDefinitionReader.ReadDefinition(json, new Uri("http://localhost/swagger.json")); Assert.NotNull(result.ApiDefinition?.BaseAddresses); Assert.Single(result.ApiDefinition.BaseAddresses); Assert.Equal("https://localhost/api/v2/", result.ApiDefinition.BaseAddresses[0].Url.ToString(), StringComparer.Ordinal); } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/OpenApi/OpenApiDotNetApiDefinitionReaderV3Tests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Linq; using Microsoft.HttpRepl.OpenApi; using Xunit; namespace Microsoft.HttpRepl.Tests.OpenApi { public class OpenApiDotNetApiDefinitionReaderV3Tests { [Fact] public void ReadMetadata_WithNoPaths_ReturnsEmptyDirectoryStructure() { string json = @"{ ""openapi"": ""3.0.0"", ""info"": { ""version"": ""v1"" } }"; OpenApiDotNetApiDefinitionReader openApiV3ApiDefinitionReader = new OpenApiDotNetApiDefinitionReader(); ApiDefinitionParseResult result = openApiV3ApiDefinitionReader.ReadDefinition(json, null); Assert.NotNull(result.ApiDefinition?.DirectoryStructure); Assert.Empty(result.ApiDefinition.DirectoryStructure.DirectoryNames); } [Fact] public void ReadMetadata_WithNoProperties_ReturnsNullDirectoryStructure() { string json = @"{ ""openapi"": ""3.0.0"", ""info"": { ""version"": ""v1"" }, ""paths"": { } }"; OpenApiDotNetApiDefinitionReader openApiV3ApiDefinitionReader = new OpenApiDotNetApiDefinitionReader(); ApiDefinitionParseResult result = openApiV3ApiDefinitionReader.ReadDefinition(json, null); Assert.NotNull(result.ApiDefinition?.DirectoryStructure); Assert.Empty(result.ApiDefinition.DirectoryStructure.DirectoryNames); } [Fact] public void ReadMetadata_WithNoResponses_ReturnsApiDefinition() { string json = @"{ ""openapi"": ""3.0.0"", ""paths"": { ""/pets"": { ""post"": { ""summary"": ""Create a pet"", ""operationId"": ""createPets"", ""requestBody"": { ""content"": { } } } } } }"; OpenApiDotNetApiDefinitionReader openApiV3ApiDefinitionReader = new OpenApiDotNetApiDefinitionReader(); ApiDefinitionParseResult result = openApiV3ApiDefinitionReader.ReadDefinition(json, null); Assert.NotNull(result.ApiDefinition?.DirectoryStructure); Assert.Single(result.ApiDefinition.DirectoryStructure.DirectoryNames); Assert.Equal("pets", result.ApiDefinition.DirectoryStructure.DirectoryNames.Single()); IDirectoryStructure subDirectory = result.ApiDefinition.DirectoryStructure.TraverseTo("/pets"); Assert.Single(subDirectory.RequestInfo.Methods); Assert.Contains("Post", subDirectory.RequestInfo.Methods, StringComparer.Ordinal); } [Fact] public void ReadMetadata_WithNoMethods_ReturnsApiDefinitionWithStructure() { string json = @"{ ""openapi"": ""3.0.0"", ""paths"": { ""/pets"": { } } }"; OpenApiDotNetApiDefinitionReader openApiV3ApiDefinitionReader = new OpenApiDotNetApiDefinitionReader(); ApiDefinitionParseResult result = openApiV3ApiDefinitionReader.ReadDefinition(json, null); Assert.NotNull(result.ApiDefinition?.DirectoryStructure); } [Fact] public void ReadMetadata_WithNoResponses_ReturnsApiDefinitionWithNoRequestInfo() { string json = @"{ ""openapi"": ""3.0.0"", ""paths"": { ""/pets"": { } } }"; OpenApiDotNetApiDefinitionReader openApiV3ApiDefinitionReader = new OpenApiDotNetApiDefinitionReader(); ApiDefinitionParseResult result = openApiV3ApiDefinitionReader.ReadDefinition(json, null); IDirectoryStructure subDirectory = result.ApiDefinition.DirectoryStructure.TraverseTo("/pets"); Assert.Null(subDirectory.RequestInfo); } [Theory] [InlineData("Get", true)] [InlineData("Post", true)] [InlineData("Put", true)] [InlineData("Delete", true)] [InlineData("Options", true)] [InlineData("Head", true)] [InlineData("Patch", true)] [InlineData("Trace", true)] [InlineData("$ref", false)] [InlineData("summary", false)] [InlineData("description", false)] [InlineData("servers", false)] [InlineData("parameters", false)] [InlineData("", false)] public void ReadMetadata_WithSpecifiedMethodName_ReturnsApiDefinitionWithCorrectNumberOfRequestMethods(string method, bool shouldHaveRequest) { // The method must be lowercase to be valid in the json string methodForJson = method.ToLower(); string json = @"{ ""openapi"": ""3.0.0"", ""info"": { ""title"": ""OpenAPI v3 Spec"", ""version"": ""v1"" }, ""paths"": { ""/pets"": { """ + methodForJson + @""": { ""summary"": ""Do something"", ""operationId"": ""doSomething"", ""responses"": { ""200"": { ""description"": ""Null response"" } }, ""requestBody"": { ""description"": ""A Request Body"", ""required"": false } } } } }"; OpenApiDotNetApiDefinitionReader openApiV3ApiDefinitionReader = new OpenApiDotNetApiDefinitionReader(); ApiDefinitionParseResult result = openApiV3ApiDefinitionReader.ReadDefinition(json, null); Assert.NotNull(result.ApiDefinition?.DirectoryStructure); Assert.Single(result.ApiDefinition.DirectoryStructure.DirectoryNames); Assert.Equal("pets", result.ApiDefinition.DirectoryStructure.DirectoryNames.Single()); IDirectoryStructure subDirectory = result.ApiDefinition.DirectoryStructure.TraverseTo("/pets"); if (shouldHaveRequest) { Assert.Single(subDirectory.RequestInfo.Methods); Assert.Contains(method, subDirectory.RequestInfo.Methods, StringComparer.OrdinalIgnoreCase); } else { Assert.Null(subDirectory.RequestInfo); } } [Fact] public void ReadMetadata_WithNoContent_ReturnsApiDefinitionWithRequestMethodButNoContentTypes() { string json = @"{ ""openapi"": ""3.0.0"", ""info"": { ""title"": ""OpenAPI v3 Spec"", ""version"": ""v1"" }, ""paths"": { ""/pets"": { ""post"": { ""summary"": ""Create a pet"", ""operationId"": ""createPets"", ""responses"": { ""201"": { ""description"": ""Null response"" } }, ""requestBody"": { ""description"": ""A Request Body"", ""required"": false } } } } }"; OpenApiDotNetApiDefinitionReader openApiV3ApiDefinitionReader = new OpenApiDotNetApiDefinitionReader(); ApiDefinitionParseResult result = openApiV3ApiDefinitionReader.ReadDefinition(json, null); Assert.NotNull(result.ApiDefinition?.DirectoryStructure); Assert.Single(result.ApiDefinition.DirectoryStructure.DirectoryNames); Assert.Equal("pets", result.ApiDefinition.DirectoryStructure.DirectoryNames.Single()); IDirectoryStructure subDirectory = result.ApiDefinition.DirectoryStructure.TraverseTo("/pets"); Assert.Single(subDirectory.RequestInfo.Methods); Assert.Contains("Post", subDirectory.RequestInfo.Methods, StringComparer.Ordinal); Assert.DoesNotContain("post", subDirectory.RequestInfo.ContentTypesByMethod.Keys, StringComparer.Ordinal); } [Fact] public void ReadMetadata_WithContentAndOneContentType_ReturnsApiDefinitionWithContentType() { string json = @"{ ""openapi"": ""3.0.0"", ""paths"": { ""/pets"": { ""post"": { ""summary"": ""Create a pet"", ""operationId"": ""createPets"", ""responses"": { ""201"": { ""description"": ""Null response"" } }, ""requestBody"": { ""description"": ""A Request Body"", ""required"": false, ""content"": { ""application/json"": { } } } } } } }"; OpenApiDotNetApiDefinitionReader openApiV3ApiDefinitionReader = new OpenApiDotNetApiDefinitionReader(); ApiDefinitionParseResult result = openApiV3ApiDefinitionReader.ReadDefinition(json, null); Assert.NotNull(result.ApiDefinition?.DirectoryStructure); Assert.Single(result.ApiDefinition.DirectoryStructure.DirectoryNames); Assert.Equal("pets", result.ApiDefinition.DirectoryStructure.DirectoryNames.Single()); IDirectoryStructure subDirectory = result.ApiDefinition.DirectoryStructure.TraverseTo("/pets"); Assert.Single(subDirectory.RequestInfo.Methods); Assert.Contains("Post", subDirectory.RequestInfo.Methods, StringComparer.Ordinal); Assert.Single(subDirectory.RequestInfo.ContentTypesByMethod["post"]); Assert.Contains("application/json", subDirectory.RequestInfo.ContentTypesByMethod["post"]); } [Fact] public void ReadMetadata_WithContentAndMultipleContentTypes_ReturnsApiDefinitionWithContentTypes() { string json = @"{ ""openapi"": ""3.0.0"", ""paths"": { ""/pets"": { ""post"": { ""summary"": ""Create a pet"", ""operationId"": ""createPets"", ""responses"": { ""201"": { ""description"": ""Null response"" } }, ""requestBody"": { ""description"": ""A Request Body"", ""required"": false, ""content"": { ""application/json"": { }, ""text/plain"": { } } } } } } }"; OpenApiDotNetApiDefinitionReader openApiV3ApiDefinitionReader = new OpenApiDotNetApiDefinitionReader(); ApiDefinitionParseResult result = openApiV3ApiDefinitionReader.ReadDefinition(json, null); Assert.NotNull(result.ApiDefinition?.DirectoryStructure); Assert.Single(result.ApiDefinition.DirectoryStructure.DirectoryNames); Assert.Equal("pets", result.ApiDefinition.DirectoryStructure.DirectoryNames.Single()); IDirectoryStructure subDirectory = result.ApiDefinition.DirectoryStructure.TraverseTo("/pets"); Assert.Single(subDirectory.RequestInfo.Methods); Assert.Contains("Post", subDirectory.RequestInfo.Methods, StringComparer.Ordinal); Assert.Equal(2, subDirectory.RequestInfo.ContentTypesByMethod["post"].Count); Assert.Contains("application/json", subDirectory.RequestInfo.ContentTypesByMethod["post"]); Assert.Contains("text/plain", subDirectory.RequestInfo.ContentTypesByMethod["post"]); } [Fact] public void ReadMetadata_WithValidInput_ReturnsApiDefinition() { string json = @"{ ""openapi"": ""3.0.0"", ""paths"": { ""/pets"": { ""get"": { ""summary"": ""List all pets"", ""operationId"": ""listPets"", ""parameters"": [ { ""name"": ""limit"", ""in"": ""query"", ""required"": false, ""schema"": { ""type"": ""integer"", ""format"": ""int32"" } } ], ""responses"": { ""200"": { ""description"": ""An paged array of pets"" } } }, ""post"": { ""summary"": ""Create a pet"", ""operationId"": ""createPets"", ""responses"": { ""201"": { ""description"": ""Null response"" } }, ""requestBody"": { ""content"": { } } } } } }"; OpenApiDotNetApiDefinitionReader openApiV3ApiDefinitionReader = new OpenApiDotNetApiDefinitionReader(); ApiDefinitionParseResult result = openApiV3ApiDefinitionReader.ReadDefinition(json, null); Assert.NotNull(result.ApiDefinition?.DirectoryStructure); Assert.Single(result.ApiDefinition.DirectoryStructure.DirectoryNames); Assert.Equal("pets", result.ApiDefinition.DirectoryStructure.DirectoryNames.Single()); IDirectoryStructure subDirectory = result.ApiDefinition.DirectoryStructure.TraverseTo("/pets"); Assert.Equal(2, subDirectory.RequestInfo.Methods.Count); Assert.Contains("Get", subDirectory.RequestInfo.Methods, StringComparer.Ordinal); Assert.Contains("Post", subDirectory.RequestInfo.Methods, StringComparer.Ordinal); } [Fact] public void ReadMetadata_WithNoRequestBody_ReturnsApiDefinition() { string json = @"{ ""openapi"": ""3.0.0"", ""paths"": { ""/pets"": { ""get"": { ""responses"": { ""200"": { ""description"": ""Success"" } } }, ""post"": { ""summary"": ""Create a pet"", ""operationId"": ""createPets"", ""responses"": { ""201"": { ""description"": ""Null response"" } }, ""requestBody"": { ""content"": { } } } } } }"; OpenApiDotNetApiDefinitionReader openApiV3ApiDefinitionReader = new OpenApiDotNetApiDefinitionReader(); ApiDefinitionParseResult result = openApiV3ApiDefinitionReader.ReadDefinition(json, null); Assert.NotNull(result.ApiDefinition?.DirectoryStructure); Assert.Single(result.ApiDefinition.DirectoryStructure.DirectoryNames); Assert.Equal("pets", result.ApiDefinition.DirectoryStructure.DirectoryNames.Single()); IDirectoryStructure subDirectory = result.ApiDefinition.DirectoryStructure.TraverseTo("/pets"); Assert.Equal(2, subDirectory.RequestInfo.Methods.Count); Assert.Contains("Get", subDirectory.RequestInfo.Methods, StringComparer.Ordinal); Assert.Contains("Post", subDirectory.RequestInfo.Methods, StringComparer.Ordinal); } [Fact] public void CanHandle_WithNoOpenApiKeyInDocument_ReturnsFalse() { string json = @"{ ""info"": { ""title"": ""OpenAPI v? Spec"", ""version"": ""v1"" }, ""paths"": { } }"; OpenApiDotNetApiDefinitionReader openApiV3ApiDefinitionReader = new OpenApiDotNetApiDefinitionReader(); ApiDefinitionParseResult result = openApiV3ApiDefinitionReader.CanHandle(json); Assert.False(result.Success); } [Fact] public void CanHandle_WithValidOpenApiVersionInDocument_ReturnsTrue() { string json = @"{ ""openapi"": ""3.0.0"", ""info"": { ""title"": ""OpenAPI v3 Spec"", ""version"": ""v1"" }, ""paths"": { } }"; OpenApiDotNetApiDefinitionReader openApiV3ApiDefinitionReader = new OpenApiDotNetApiDefinitionReader(); ApiDefinitionParseResult result = openApiV3ApiDefinitionReader.CanHandle(json); Assert.True(result.Success); } [Fact] public void CanHandle_WithOpenApiVersionGreaterThanThree_ReturnsFalse() { string json = @"{ ""openapi"": ""4.0.0"", ""info"": { ""title"": ""OpenAPI v4 Spec"", ""version"": ""v1"" }, ""paths"": { } }"; OpenApiDotNetApiDefinitionReader openApiV3ApiDefinitionReader = new OpenApiDotNetApiDefinitionReader(); ApiDefinitionParseResult result = openApiV3ApiDefinitionReader.CanHandle(json); Assert.False(result.Success); } [Fact] public void ReadDefinition_WithNoServers_BaseAddressesIsEmpty() { string json = @"{ ""openapi"": ""3.0.0"", ""info"": { ""version"": ""v1"" }, ""paths"": { ""/pets"": { } } }"; OpenApiDotNetApiDefinitionReader openApiV3ApiDefinitionReader = new OpenApiDotNetApiDefinitionReader(); ApiDefinitionParseResult result = openApiV3ApiDefinitionReader.ReadDefinition(json, null); Assert.NotNull(result.ApiDefinition?.BaseAddresses); Assert.Empty(result.ApiDefinition.BaseAddresses); } [Fact] public void ReadDefinition_WithOneServer_BaseAddressesHasOneEntry() { string json = @"{ ""openapi"": ""3.0.0"", ""info"": { ""version"": ""v1"" }, ""servers"": [ { ""url"": ""https://localhost/"", ""description"": ""First Server Address"" } ], ""paths"": { ""/pets"": { } } }"; OpenApiDotNetApiDefinitionReader openApiV3ApiDefinitionReader = new OpenApiDotNetApiDefinitionReader(); ApiDefinitionParseResult result = openApiV3ApiDefinitionReader.ReadDefinition(json, null); Assert.NotNull(result.ApiDefinition?.BaseAddresses); Assert.Single(result.ApiDefinition.BaseAddresses); Assert.Equal("https://localhost/", result.ApiDefinition.BaseAddresses[0].Url.ToString()); Assert.Equal("First Server Address", result.ApiDefinition.BaseAddresses[0].Description); } [Fact] public void ReadDefinition_WithTwoServers_BaseAddressesHasTwoEntries() { string json = @"{ ""openapi"": ""3.0.0"", ""info"": { ""version"": ""v1"" }, ""servers"": [ { ""url"": ""https://petstore.swagger.io/"", ""description"": ""Production Server Address"" }, { ""url"": ""https://localhost/"", ""description"": ""Local Development Server Address"" } ], ""paths"": { ""/pets"": { } } }"; OpenApiDotNetApiDefinitionReader openApiV3ApiDefinitionReader = new OpenApiDotNetApiDefinitionReader(); ApiDefinitionParseResult result = openApiV3ApiDefinitionReader.ReadDefinition(json, null); Assert.NotNull(result.ApiDefinition?.BaseAddresses); Assert.Equal(2, result.ApiDefinition.BaseAddresses.Count); Assert.Equal("https://petstore.swagger.io/", result.ApiDefinition.BaseAddresses[0].Url.ToString()); Assert.Equal("Production Server Address", result.ApiDefinition.BaseAddresses[0].Description); Assert.Equal("https://localhost/", result.ApiDefinition.BaseAddresses[1].Url.ToString()); Assert.Equal("Local Development Server Address", result.ApiDefinition.BaseAddresses[1].Description); } [Fact] public void ReadDefinition_WithRelativeServer_BaseAddressesCorrectEntry() { string json = @"{ ""openapi"": ""3.0.0"", ""info"": { ""version"": ""v1"" }, ""servers"": [ { ""url"": ""/api/v2"", ""description"": ""First Server Address"" } ], ""paths"": { ""/pets"": { } } }"; OpenApiDotNetApiDefinitionReader openApiV3ApiDefinitionReader = new OpenApiDotNetApiDefinitionReader(); ApiDefinitionParseResult result = openApiV3ApiDefinitionReader.ReadDefinition(json, new Uri("https://localhost/swagger.json")); Assert.NotNull(result.ApiDefinition?.BaseAddresses); Assert.Single(result.ApiDefinition.BaseAddresses); Assert.Equal("https://localhost/api/v2/", result.ApiDefinition.BaseAddresses[0].Url.ToString()); Assert.Equal("First Server Address", result.ApiDefinition.BaseAddresses[0].Description); } [Fact] public void ReadDefinition_WithRequestBody_SchemaIsIncluded() { string contentType = "application/json"; string json = @"{ ""openapi"": ""3.0.0"", ""info"": { ""version"": ""v1"" }, ""paths"": { ""/pets"": { ""post"": { ""requestBody"": { ""content"": { """ + contentType + @""": { ""schema"": { ""type"": ""object"", ""properties"": { ""date"": { ""type"": ""string"", ""format"": ""date-time"" } } } } } } } } } }"; OpenApiDotNetApiDefinitionReader openApiV3ApiDefinitionReader = new OpenApiDotNetApiDefinitionReader(); ApiDefinitionParseResult result = openApiV3ApiDefinitionReader.ReadDefinition(json, new Uri("https://localhost/swagger.json")); IDirectoryStructure pets = result.ApiDefinition.DirectoryStructure.TraverseTo("pets"); string requestBody = pets.RequestInfo.GetRequestBodyForContentType(ref contentType, "post"); Assert.NotNull(requestBody); } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/OpenApi/SchemaDataGeneratorTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using Microsoft.HttpRepl.OpenApi; using Microsoft.OpenApi; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Writers; using Newtonsoft.Json.Linq; using Xunit; namespace Microsoft.HttpRepl.Tests.OpenApi { public class SchemaDataGeneratorTests { [Fact] public void GenerateData_WithNull_ReturnsNull() { JToken result = SchemaDataGenerator.GenerateData(null); Assert.Null(result); } [Fact] public void GenerateData_WithExample_ReturnsJTokenBasedOnExample() { string stringValue = "string value"; int intValue = 5; OpenApiSchema schema = new OpenApiSchema(); schema.Example = new StringAndIntClass() { StringProperty = stringValue, IntProperty = intValue,}; JToken result = SchemaDataGenerator.GenerateData(schema); Assert.NotNull(result); Assert.NotNull(result["StringProperty"]); Assert.Equal(stringValue, result["StringProperty"].Value()); Assert.NotNull(result["IntProperty"]); Assert.Equal(intValue, result["IntProperty"].Value()); } [Fact] public void GenerateData_WithDefault_ReturnsJTokenBasedOnDefault() { string stringValue = "string value"; int intValue = 5; OpenApiSchema schema = new OpenApiSchema(); schema.Default = new StringAndIntClass() { StringProperty = stringValue, IntProperty = intValue }; JToken result = SchemaDataGenerator.GenerateData(schema); Assert.NotNull(result); Assert.NotNull(result["StringProperty"]); Assert.Equal(stringValue, result["StringProperty"].Value()); Assert.NotNull(result["IntProperty"]); Assert.Equal(intValue, result["IntProperty"].Value()); } [Fact] public void GenerateData_WithExampleAndDefault_ReturnsJTokenBasedOnExample() { string stringValue = "string value"; int intValue = 5; OpenApiSchema schema = new OpenApiSchema(); schema.Example = new StringAndIntClass() { StringProperty = stringValue, IntProperty = intValue }; schema.Default = new StringAndIntClass() { StringProperty = "a different string value", IntProperty = 7 }; JToken result = SchemaDataGenerator.GenerateData(schema); Assert.NotNull(result); Assert.NotNull(result["StringProperty"]); Assert.Equal(stringValue, result["StringProperty"].Value()); Assert.NotNull(result["IntProperty"]); Assert.Equal(intValue, result["IntProperty"].Value()); } [Fact] public void GenerateData_WithStringNoFormat_ReturnsEmptyString() { OpenApiSchema schema = new OpenApiSchema(); schema.Type = "string"; schema.Format = null; JToken result = SchemaDataGenerator.GenerateData(schema); Assert.NotNull(result); Assert.True(result is JValue); JValue jValue = (JValue)result; Assert.Equal(string.Empty, jValue.Value); } [Fact] public void GenerateData_WithStringDateTimeFormat_ReturnsDateTimeString() { OpenApiSchema schema = new OpenApiSchema(); schema.Type = "string"; schema.Format = "date-time"; // Format we are looking for is ISO8601 - YYYY-MM-DDTHH:MM:SS.MMMMMMM-HH:MM string iso8601RegEx = "^([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(\\.[0-9]+)(Z|([+-](2[0-3]|[01][0-9]):([0-5][0-9])))$"; JToken result = SchemaDataGenerator.GenerateData(schema); Assert.NotNull(result); Assert.True(result is JValue); JValue jValue = (JValue)result; string stringValue = jValue.Value(); Assert.Matches(iso8601RegEx, stringValue); } [Fact] public void GenerateData_WithObjectWithReadOnlyProperty_DoesNotIncludeReadOnlyProperty() { OpenApiSchema rootSchema = new OpenApiSchema() { Type = "object", Properties = new Dictionary() }; OpenApiSchema readOnlySchema = new OpenApiSchema() { Type = "integer", ReadOnly = true }; OpenApiSchema writeableSchema = new OpenApiSchema() { Type = "string" }; rootSchema.Properties.Add("Writeable", writeableSchema); rootSchema.Properties.Add("ReadOnly", readOnlySchema); JToken result = SchemaDataGenerator.GenerateData(rootSchema); Assert.NotNull(result); Assert.True(result is JObject); JObject jObject = (JObject)result; IEnumerable propertyNames = jObject.Properties().Select(j => j.Name); Assert.Contains("Writeable", propertyNames, StringComparer.Ordinal); Assert.DoesNotContain("ReadOnly", propertyNames, StringComparer.Ordinal); } private class StringAndIntClass : IOpenApiAny { public AnyType AnyType => AnyType.Object; public string StringProperty { get; set; } public int IntProperty { get; set; } public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) { // Nothing to do here } } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/Preferences/OpenApiSearchPathsProviderTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using Microsoft.HttpRepl.Fakes; using Microsoft.HttpRepl.Preferences; using Xunit; namespace Microsoft.HttpRepl.Tests.Preferences { public class OpenApiSearchPathsProviderTests { [Fact] public void WithNoOverrides_ReturnsDefault() { // Arrange NullPreferences preferences = new(); OpenApiSearchPathsProvider provider = new(preferences); IEnumerable expectedPaths = OpenApiSearchPathsProvider.DefaultSearchPaths; // Act IEnumerable paths = provider.GetOpenApiSearchPaths(); // Assert AssertPathLists(expectedPaths, paths); } [Fact] public void WithFullOverride_ReturnsConfiguredOverride() { // Arrange string searchPathOverrides = "/red|/green|/blue"; FakePreferences preferences = new(); preferences.SetValue(WellKnownPreference.SwaggerSearchPaths, searchPathOverrides); OpenApiSearchPathsProvider provider = new(preferences); string[] expectedPaths = searchPathOverrides.Split('|'); // Act IEnumerable paths = provider.GetOpenApiSearchPaths(); // Assert AssertPathLists(expectedPaths, paths); } [Fact] public void WithAdditions_ReturnsDefaultPlusAdditions() { // Arrange string[] searchPathAdditions = new[] { "/red", "/green", "/blue" }; FakePreferences preferences = new(); preferences.SetValue(WellKnownPreference.SwaggerAddToSearchPaths, string.Join('|', searchPathAdditions)); OpenApiSearchPathsProvider provider = new(preferences); IEnumerable expectedPaths = OpenApiSearchPathsProvider.DefaultSearchPaths.Union(searchPathAdditions); // Act IEnumerable paths = provider.GetOpenApiSearchPaths(); // Assert AssertPathLists(expectedPaths, paths); } [Fact] public void WithRemovals_ReturnsDefaultMinusRemovals() { // Arrange string[] searchPathRemovals = new[] { "swagger.json", "/swagger.json", "swagger/v1/swagger.json", "/swagger/v1/swagger.json" }; FakePreferences preferences = new(); preferences.SetValue(WellKnownPreference.SwaggerRemoveFromSearchPaths, string.Join('|', searchPathRemovals)); OpenApiSearchPathsProvider provider = new(preferences); IEnumerable expectedPaths = OpenApiSearchPathsProvider.DefaultSearchPaths.Except(searchPathRemovals); // Act IEnumerable paths = provider.GetOpenApiSearchPaths(); // Assert AssertPathLists(expectedPaths, paths); } [Fact] public void WithAdditionsAndRemovals_ReturnsCorrectSet() { // Arrange string[] searchPathAdditions = new[] { "/red", "/green", "/blue" }; string[] searchPathRemovals = new[] { "swagger.json", "/swagger.json", "swagger/v1/swagger.json", "/swagger/v1/swagger.json" }; FakePreferences preferences = new(); preferences.SetValue(WellKnownPreference.SwaggerAddToSearchPaths, string.Join('|', searchPathAdditions)); preferences.SetValue(WellKnownPreference.SwaggerRemoveFromSearchPaths, string.Join('|', searchPathRemovals)); OpenApiSearchPathsProvider provider = new(preferences); IEnumerable expectedPaths = OpenApiSearchPathsProvider.DefaultSearchPaths.Union(searchPathAdditions).Except(searchPathRemovals); // Act IEnumerable paths = provider.GetOpenApiSearchPaths(); // Assert AssertPathLists(expectedPaths, paths); } private static void AssertPathLists(IEnumerable expectedPaths, IEnumerable paths) { Assert.NotNull(expectedPaths); Assert.NotNull(paths); IEnumerator expectedPathsEnumerator = expectedPaths.GetEnumerator(); IEnumerator pathsEnumerator = paths.GetEnumerator(); while (expectedPathsEnumerator.MoveNext()) { Assert.True(pathsEnumerator.MoveNext(), $"Missing path \"{expectedPathsEnumerator.Current}\""); Assert.Equal(expectedPathsEnumerator.Current, pathsEnumerator.Current, StringComparer.Ordinal); } if (pathsEnumerator.MoveNext()) { // We can't do a one-liner here like the Missing path version above because // the order the second parameter is evaluated regardless of the result of the // evaluation of the first parameter. Assert.Fail($"Extra path \"{pathsEnumerator.Current}\""); } } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/Preferences/TestDefaultPreferences.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Collections.Generic; namespace Microsoft.HttpRepl.Tests.Preferences { internal static class TestDefaultPreferences { internal static Dictionary GetDefaultPreferences() { // For now, we'll just use the same defaults as used by the app. return Program.CreateDefaultPreferences(); } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/Preferences/UserFolderPreferencesTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using Microsoft.HttpRepl.Fakes; using Microsoft.HttpRepl.Preferences; using Microsoft.HttpRepl.UserProfile; using Microsoft.Repl.ConsoleHandling; using Xunit; namespace Microsoft.HttpRepl.Tests.Preferences { public class UserFolderPreferencesTests { [Fact] public void ReadPreferences_NoPreferencesFile_AllDefaults() { SetupPreferences(out UserFolderPreferences preferences, out _); ConfirmAllPreferencesAreDefaults(preferences); } [Fact] public void ReadPreferences_BlankPreferencesFile_AllDefaults() { SetupPreferences(out UserFolderPreferences preferences, out MockedFileSystem fileSystem); fileSystem.AddFile(preferences.PreferencesFilePath, ""); ConfirmAllPreferencesAreDefaults(preferences); } [Fact] public void ReadPreferences_InvalidPreferencesFile_AllDefaults() { SetupPreferences(out UserFolderPreferences preferences, out MockedFileSystem fileSystem); fileSystem.AddFile(preferences.PreferencesFilePath, "This is not a valid preferences file."); ConfirmAllPreferencesAreDefaults(preferences); } [Fact] public void ReadPreferences_PartiallyInvalidPreferencesFile_ValidPrefsAreSet() { SetupPreferences(out UserFolderPreferences preferences, out MockedFileSystem fileSystem); string settingName = WellKnownPreference.DefaultEditorCommand; string expectedValue = "Code.exe"; string prefsFileContent = $@"This first line is invalid for a prefs file. {settingName}={expectedValue} This third line is invalid as well"; fileSystem.AddFile(preferences.PreferencesFilePath, prefsFileContent); Assert.Equal(expectedValue, preferences.CurrentPreferences[settingName]); } [Fact] public void WritePreferences_SomeDefault_OnlyWritesNonDefaultValues() { string defaultEditor = "Code.exe"; string errorColor = "BoldMagenta"; string expected = $@"{WellKnownPreference.ErrorColor}={errorColor} {WellKnownPreference.DefaultEditorCommand}={defaultEditor}"; SetupPreferences(out UserFolderPreferences preferences, out MockedFileSystem fileSystem); // Add one and change one // Only the changes should be written, not any of the defaults. bool succeeded = preferences.SetValue(WellKnownPreference.DefaultEditorCommand, defaultEditor); Assert.True(succeeded); succeeded = preferences.SetValue(WellKnownPreference.ErrorColor, errorColor); Assert.True(succeeded); Assert.Equal(expected, fileSystem.ReadFile(preferences.PreferencesFilePath)); } [Fact] public void WritePreferences_ChangeNonDefaultToDefault_RemovesDefaultValue() { string originalValue = "BoldMagenta"; string defaultValue = "Red"; IDictionary defaultPreferences = new Dictionary { { WellKnownPreference.ProtocolColor, defaultValue } }; MockedFileSystem fileSystem = new MockedFileSystem(); IUserProfileDirectoryProvider userProfileDirectoryProvider = new UserProfileDirectoryProvider(); UserFolderPreferences preferences = new UserFolderPreferences(fileSystem, userProfileDirectoryProvider, defaultPreferences); // Create a file with a non-default value, read it from the file system and // validate that it was read correctly fileSystem.AddFile(preferences.PreferencesFilePath, $"{WellKnownPreference.ProtocolColor}={originalValue}"); Assert.Equal(originalValue, preferences.CurrentPreferences[WellKnownPreference.ProtocolColor]); // Now change it to the default value, write it back to the file system and // validate that it was removed from the file bool succeeded = preferences.SetValue(WellKnownPreference.ProtocolColor, defaultPreferences[WellKnownPreference.ProtocolColor]); Assert.True(succeeded); Assert.Equal(string.Empty, fileSystem.ReadFile(preferences.PreferencesFilePath)); } [Fact] public void SetValue_NoValueNoDefault_PreferenceIsRemoved() { string initialValue = "BoldRed"; SetupPreferencesWithFileContent($"{WellKnownPreference.JsonBraceColor}={initialValue}", out UserFolderPreferences preferences); Assert.Equal("BoldRed", preferences.GetValue(WellKnownPreference.JsonBraceColor)); // JsonBraceColor has no default, so this should remove the preference preferences.SetValue(WellKnownPreference.JsonBraceColor, ""); bool found = preferences.TryGetValue(WellKnownPreference.JsonBraceColor, out _); Assert.False(found); } [Theory] [MemberData(nameof(GetStringValuesTestData))] public void GetValue_CorrectOutput(string expected, string fileContent, string preferenceName, string defaultValue) { SetupPreferencesWithFileContent(fileContent, out UserFolderPreferences preferences); string result = preferences.GetValue(preferenceName, defaultValue); Assert.Equal(expected, result, StringComparer.OrdinalIgnoreCase); } [Theory] [MemberData(nameof(GetColorValuesTestData))] public void GetColorValue_CorrectOutput(AllowedColors expected, string fileContent, string preferenceName, AllowedColors defaultValue) { SetupPreferencesWithFileContent(fileContent, out UserFolderPreferences preferences); AllowedColors result = preferences.GetColorValue(preferenceName, defaultValue); Assert.Equal(expected, result); } [Theory] [MemberData(nameof(GetIntValuesTestData))] public void GetIntValue_CorrectOutput(int expected, string fileContent, string preferenceName, int defaultValue) { SetupPreferencesWithFileContent(fileContent, out UserFolderPreferences preferences); int result = preferences.GetIntValue(preferenceName, defaultValue); Assert.Equal(expected, result); } [Theory] [MemberData(nameof(GetBoolValuesTestData))] public void GetBoolValue_CorrectOutput(bool expected, string fileContent, string preferenceName, bool defaultValue) { SetupPreferencesWithFileContent(fileContent, out UserFolderPreferences preferences); bool result = preferences.GetBoolValue(preferenceName, defaultValue); Assert.Equal(expected, result); } public static IEnumerable GetStringValuesTestData() { // Empty/blank preferences file falls back to the passed in default. yield return new object[] { "code.exe", string.Empty, WellKnownPreference.DefaultEditorCommand, "code.exe" }; // Actual value is returned yield return new object[] { "code.exe", $"{WellKnownPreference.DefaultEditorCommand}=code.exe", WellKnownPreference.DefaultEditorCommand, "notepad.exe" }; } public static IEnumerable GetColorValuesTestData() { // Empty/blank preferences file falls back to the passed in default yield return new object[] { AllowedColors.Magenta, string.Empty, WellKnownPreference.ErrorColor, AllowedColors.Magenta }; // Preference value that isn't an actual color falls back to the passed in default yield return new object[] { AllowedColors.Magenta, $"{WellKnownPreference.ErrorColor}=ThisIsGibberish", WellKnownPreference.ErrorColor, AllowedColors.Magenta }; // Actual value is returned yield return new object[] { AllowedColors.BoldRed, $"{WellKnownPreference.ErrorColor}=BoldRed", WellKnownPreference.ErrorColor, AllowedColors.Cyan }; } public static IEnumerable GetIntValuesTestData() { // Empty/blank preferences file falls back to the passed in default yield return new object[] { 5, string.Empty, WellKnownPreference.JsonIndentSize, 5 }; // Preference value that isn't an int falls back to the passed in default yield return new object[] { 5, $"{WellKnownPreference.JsonIndentSize}=ThisIsGibberish", WellKnownPreference.JsonIndentSize, 5 }; // Actual value is returned yield return new object[] { 5, $"{WellKnownPreference.JsonIndentSize}=5", WellKnownPreference.JsonIndentSize, 42 }; } public static IEnumerable GetBoolValuesTestData() { // Empty/blank preferences file falls back to the passed in default yield return new object[] { false, string.Empty, WellKnownPreference.UseDefaultCredentials, false }; // Preference value that isn't a bool falls back to the passed in default yield return new object[] { false, $"{WellKnownPreference.UseDefaultCredentials}=ThisIsGibberish", WellKnownPreference.UseDefaultCredentials, false }; // Actual value is returned yield return new object[] { true, $"{WellKnownPreference.UseDefaultCredentials}=true", WellKnownPreference.UseDefaultCredentials, false }; } private void SetupPreferences(out UserFolderPreferences preferences, out MockedFileSystem fileSystem) { fileSystem = new MockedFileSystem(); IUserProfileDirectoryProvider userProfileDirectoryProvider = new UserProfileDirectoryProvider(); preferences = new UserFolderPreferences(fileSystem, userProfileDirectoryProvider, TestDefaultPreferences.GetDefaultPreferences()); } private void SetupPreferencesWithFileContent(string fileContent, out UserFolderPreferences preferences) { SetupPreferences(out preferences, out MockedFileSystem fileSystem); fileSystem.AddFile(preferences.PreferencesFilePath, fileContent); } private void ConfirmAllPreferencesAreDefaults(IPreferences preferences) { var defaultPreferences = TestDefaultPreferences.GetDefaultPreferences(); var currentPreferences = preferences.CurrentPreferences; Assert.Equal(defaultPreferences.Count, currentPreferences.Count); foreach (KeyValuePair kvp in defaultPreferences) { Assert.True(currentPreferences.ContainsKey(kvp.Key)); Assert.Equal(kvp.Value, currentPreferences[kvp.Key]); } } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/Resources/OpenApiDescriptions/MicrosoftGraph.PowershellSdk.Analytics.json ================================================ { "openapi": "3.0.1", "info": { "title": "Analytics", "version": "v1.0" }, "servers": [ { "url": "https://graph.microsoft.com/v1.0/", "description": "Core" } ], "paths": { "/users/{user-id}/insights": { "get": { "tags": [ "users.officeGraphInsights" ], "summary": "Get insights from users", "operationId": "users_GetInsights", "parameters": [ { "name": "user-id", "in": "path", "description": "key: user-id of user", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "user" }, { "name": "$select", "in": "query", "description": "Select properties to be returned", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "id", "trending", "shared", "used" ], "type": "string" } } }, { "name": "$expand", "in": "query", "description": "Expand related entities", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "*", "trending", "shared", "used" ], "type": "string" } } } ], "responses": { "200": { "description": "Retrieved navigation property", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.officeGraphInsights" } } }, "links": { "trending": { "operationId": "user.insights.GetTrending", "parameters": { "user-id": "$request.path.user-id", "trending-id": "$response.body#/id" } }, "shared": { "operationId": "user.insights.GetShared", "parameters": { "user-id": "$request.path.user-id", "sharedInsight-id": "$response.body#/id" } }, "used": { "operationId": "user.insights.GetUsed", "parameters": { "user-id": "$request.path.user-id", "usedInsight-id": "$response.body#/id" } } } }, "default": { "$ref": "#/components/responses/error" } } }, "patch": { "tags": [ "users.officeGraphInsights" ], "summary": "Update the navigation property insights in users", "operationId": "users_UpdateInsights", "parameters": [ { "name": "user-id", "in": "path", "description": "key: user-id of user", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "user" } ], "requestBody": { "description": "New navigation property values", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.officeGraphInsights" } } }, "required": true }, "responses": { "204": { "description": "Success" }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-docs-operation-type": "operation" } }, "/users/{user-id}/insights/shared": { "get": { "tags": [ "users.officeGraphInsights" ], "summary": "Get shared from users", "operationId": "users.insights_ListShared", "parameters": [ { "name": "user-id", "in": "path", "description": "key: user-id of user", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "user" }, { "$ref": "#/components/parameters/top" }, { "$ref": "#/components/parameters/skip" }, { "$ref": "#/components/parameters/search" }, { "$ref": "#/components/parameters/filter" }, { "$ref": "#/components/parameters/count" }, { "name": "$orderby", "in": "query", "description": "Order items by property values", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "id", "id desc", "lastShared", "lastShared desc", "sharingHistory", "sharingHistory desc", "resourceVisualization", "resourceVisualization desc", "resourceReference", "resourceReference desc" ], "type": "string" } } }, { "name": "$select", "in": "query", "description": "Select properties to be returned", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "id", "lastShared", "sharingHistory", "resourceVisualization", "resourceReference", "lastSharedMethod", "resource" ], "type": "string" } } }, { "name": "$expand", "in": "query", "description": "Expand related entities", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "*", "lastSharedMethod", "resource" ], "type": "string" } } } ], "responses": { "200": { "description": "Retrieved navigation property", "content": { "application/json": { "schema": { "title": "Collection of sharedInsight", "type": "object", "properties": { "value": { "type": "array", "items": { "$ref": "#/components/schemas/microsoft.graph.sharedInsight" } }, "@odata.nextLink": { "type": "string" } } } } } }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-pageable": { "nextLinkName": "@odata.nextLink", "operationName": "listMore" }, "x-ms-docs-operation-type": "operation" }, "post": { "tags": [ "users.officeGraphInsights" ], "summary": "Create new navigation property to shared for users", "operationId": "users.insights_CreateShared", "parameters": [ { "name": "user-id", "in": "path", "description": "key: user-id of user", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "user" } ], "requestBody": { "description": "New navigation property", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.sharedInsight" } } }, "required": true }, "responses": { "201": { "description": "Created navigation property.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.sharedInsight" } } } }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-docs-operation-type": "operation" } }, "/users/{user-id}/insights/shared/{sharedInsight-id}": { "get": { "tags": [ "users.officeGraphInsights" ], "summary": "Get shared from users", "operationId": "users.insights_GetShared", "parameters": [ { "name": "user-id", "in": "path", "description": "key: user-id of user", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "user" }, { "name": "sharedInsight-id", "in": "path", "description": "key: sharedInsight-id of sharedInsight", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "sharedInsight" }, { "name": "$select", "in": "query", "description": "Select properties to be returned", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "id", "lastShared", "sharingHistory", "resourceVisualization", "resourceReference", "lastSharedMethod", "resource" ], "type": "string" } } }, { "name": "$expand", "in": "query", "description": "Expand related entities", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "*", "lastSharedMethod", "resource" ], "type": "string" } } } ], "responses": { "200": { "description": "Retrieved navigation property", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.sharedInsight" } } }, "links": { "lastSharedMethod": { "operationId": "officeGraphInsights.shared.GetLastSharedMethod", "parameters": { "user-id": "$request.path.user-id", "sharedInsight-id": "$request.path.sharedInsight-id", "entity-id": "$response.body#/id" } }, "resource": { "operationId": "officeGraphInsights.shared.GetResource", "parameters": { "user-id": "$request.path.user-id", "sharedInsight-id": "$request.path.sharedInsight-id", "entity-id": "$response.body#/id" } } } }, "default": { "$ref": "#/components/responses/error" } } }, "patch": { "tags": [ "users.officeGraphInsights" ], "summary": "Update the navigation property shared in users", "operationId": "users.insights_UpdateShared", "parameters": [ { "name": "user-id", "in": "path", "description": "key: user-id of user", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "user" }, { "name": "sharedInsight-id", "in": "path", "description": "key: sharedInsight-id of sharedInsight", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "sharedInsight" } ], "requestBody": { "description": "New navigation property values", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.sharedInsight" } } }, "required": true }, "responses": { "204": { "description": "Success" }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-docs-operation-type": "operation" } }, "/users/{user-id}/insights/shared/{sharedInsight-id}/lastSharedMethod": { "get": { "tags": [ "users.officeGraphInsights" ], "summary": "Get lastSharedMethod from users", "operationId": "users.insights.shared_GetLastSharedMethod", "parameters": [ { "name": "user-id", "in": "path", "description": "key: user-id of user", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "user" }, { "name": "sharedInsight-id", "in": "path", "description": "key: sharedInsight-id of sharedInsight", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "sharedInsight" }, { "name": "$select", "in": "query", "description": "Select properties to be returned", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "id" ], "type": "string" } } }, { "name": "$expand", "in": "query", "description": "Expand related entities", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "*" ], "type": "string" } } } ], "responses": { "200": { "description": "Retrieved navigation property", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.entity" } } } }, "default": { "$ref": "#/components/responses/error" } } } }, "/users/{user-id}/insights/shared/{sharedInsight-id}/resource": { "get": { "tags": [ "users.officeGraphInsights" ], "summary": "Get resource from users", "operationId": "users.insights.shared_GetResource", "parameters": [ { "name": "user-id", "in": "path", "description": "key: user-id of user", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "user" }, { "name": "sharedInsight-id", "in": "path", "description": "key: sharedInsight-id of sharedInsight", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "sharedInsight" }, { "name": "$select", "in": "query", "description": "Select properties to be returned", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "id" ], "type": "string" } } }, { "name": "$expand", "in": "query", "description": "Expand related entities", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "*" ], "type": "string" } } } ], "responses": { "200": { "description": "Retrieved navigation property", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.entity" } } } }, "default": { "$ref": "#/components/responses/error" } } } }, "/users/{user-id}/insights/trending": { "get": { "tags": [ "users.officeGraphInsights" ], "summary": "Get trending from users", "operationId": "users.insights_ListTrending", "parameters": [ { "name": "user-id", "in": "path", "description": "key: user-id of user", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "user" }, { "$ref": "#/components/parameters/top" }, { "$ref": "#/components/parameters/skip" }, { "$ref": "#/components/parameters/search" }, { "$ref": "#/components/parameters/filter" }, { "$ref": "#/components/parameters/count" }, { "name": "$orderby", "in": "query", "description": "Order items by property values", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "id", "id desc", "weight", "weight desc", "resourceVisualization", "resourceVisualization desc", "resourceReference", "resourceReference desc", "lastModifiedDateTime", "lastModifiedDateTime desc" ], "type": "string" } } }, { "name": "$select", "in": "query", "description": "Select properties to be returned", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "id", "weight", "resourceVisualization", "resourceReference", "lastModifiedDateTime", "resource" ], "type": "string" } } }, { "name": "$expand", "in": "query", "description": "Expand related entities", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "*", "resource" ], "type": "string" } } } ], "responses": { "200": { "description": "Retrieved navigation property", "content": { "application/json": { "schema": { "title": "Collection of trending", "type": "object", "properties": { "value": { "type": "array", "items": { "$ref": "#/components/schemas/microsoft.graph.trending" } }, "@odata.nextLink": { "type": "string" } } } } } }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-pageable": { "nextLinkName": "@odata.nextLink", "operationName": "listMore" }, "x-ms-docs-operation-type": "operation" }, "post": { "tags": [ "users.officeGraphInsights" ], "summary": "Create new navigation property to trending for users", "operationId": "users.insights_CreateTrending", "parameters": [ { "name": "user-id", "in": "path", "description": "key: user-id of user", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "user" } ], "requestBody": { "description": "New navigation property", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.trending" } } }, "required": true }, "responses": { "201": { "description": "Created navigation property.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.trending" } } } }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-docs-operation-type": "operation" } }, "/users/{user-id}/insights/trending/{trending-id}": { "get": { "tags": [ "users.officeGraphInsights" ], "summary": "Get trending from users", "operationId": "users.insights_GetTrending", "parameters": [ { "name": "user-id", "in": "path", "description": "key: user-id of user", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "user" }, { "name": "trending-id", "in": "path", "description": "key: trending-id of trending", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "trending" }, { "name": "$select", "in": "query", "description": "Select properties to be returned", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "id", "weight", "resourceVisualization", "resourceReference", "lastModifiedDateTime", "resource" ], "type": "string" } } }, { "name": "$expand", "in": "query", "description": "Expand related entities", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "*", "resource" ], "type": "string" } } } ], "responses": { "200": { "description": "Retrieved navigation property", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.trending" } } }, "links": { "resource": { "operationId": "officeGraphInsights.trending.GetResource", "parameters": { "user-id": "$request.path.user-id", "trending-id": "$request.path.trending-id", "entity-id": "$response.body#/id" } } } }, "default": { "$ref": "#/components/responses/error" } } }, "patch": { "tags": [ "users.officeGraphInsights" ], "summary": "Update the navigation property trending in users", "operationId": "users.insights_UpdateTrending", "parameters": [ { "name": "user-id", "in": "path", "description": "key: user-id of user", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "user" }, { "name": "trending-id", "in": "path", "description": "key: trending-id of trending", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "trending" } ], "requestBody": { "description": "New navigation property values", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.trending" } } }, "required": true }, "responses": { "204": { "description": "Success" }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-docs-operation-type": "operation" } }, "/users/{user-id}/insights/trending/{trending-id}/resource": { "get": { "tags": [ "users.officeGraphInsights" ], "summary": "Get resource from users", "operationId": "users.insights.trending_GetResource", "parameters": [ { "name": "user-id", "in": "path", "description": "key: user-id of user", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "user" }, { "name": "trending-id", "in": "path", "description": "key: trending-id of trending", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "trending" }, { "name": "$select", "in": "query", "description": "Select properties to be returned", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "id" ], "type": "string" } } }, { "name": "$expand", "in": "query", "description": "Expand related entities", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "*" ], "type": "string" } } } ], "responses": { "200": { "description": "Retrieved navigation property", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.entity" } } } }, "default": { "$ref": "#/components/responses/error" } } } }, "/users/{user-id}/insights/used": { "get": { "tags": [ "users.officeGraphInsights" ], "summary": "Get used from users", "operationId": "users.insights_ListUsed", "parameters": [ { "name": "user-id", "in": "path", "description": "key: user-id of user", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "user" }, { "$ref": "#/components/parameters/top" }, { "$ref": "#/components/parameters/skip" }, { "$ref": "#/components/parameters/search" }, { "$ref": "#/components/parameters/filter" }, { "$ref": "#/components/parameters/count" }, { "name": "$orderby", "in": "query", "description": "Order items by property values", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "id", "id desc", "lastUsed", "lastUsed desc", "resourceVisualization", "resourceVisualization desc", "resourceReference", "resourceReference desc" ], "type": "string" } } }, { "name": "$select", "in": "query", "description": "Select properties to be returned", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "id", "lastUsed", "resourceVisualization", "resourceReference", "resource" ], "type": "string" } } }, { "name": "$expand", "in": "query", "description": "Expand related entities", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "*", "resource" ], "type": "string" } } } ], "responses": { "200": { "description": "Retrieved navigation property", "content": { "application/json": { "schema": { "title": "Collection of usedInsight", "type": "object", "properties": { "value": { "type": "array", "items": { "$ref": "#/components/schemas/microsoft.graph.usedInsight" } }, "@odata.nextLink": { "type": "string" } } } } } }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-pageable": { "nextLinkName": "@odata.nextLink", "operationName": "listMore" }, "x-ms-docs-operation-type": "operation" }, "post": { "tags": [ "users.officeGraphInsights" ], "summary": "Create new navigation property to used for users", "operationId": "users.insights_CreateUsed", "parameters": [ { "name": "user-id", "in": "path", "description": "key: user-id of user", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "user" } ], "requestBody": { "description": "New navigation property", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.usedInsight" } } }, "required": true }, "responses": { "201": { "description": "Created navigation property.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.usedInsight" } } } }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-docs-operation-type": "operation" } }, "/users/{user-id}/insights/used/{usedInsight-id}": { "get": { "tags": [ "users.officeGraphInsights" ], "summary": "Get used from users", "operationId": "users.insights_GetUsed", "parameters": [ { "name": "user-id", "in": "path", "description": "key: user-id of user", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "user" }, { "name": "usedInsight-id", "in": "path", "description": "key: usedInsight-id of usedInsight", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "usedInsight" }, { "name": "$select", "in": "query", "description": "Select properties to be returned", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "id", "lastUsed", "resourceVisualization", "resourceReference", "resource" ], "type": "string" } } }, { "name": "$expand", "in": "query", "description": "Expand related entities", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "*", "resource" ], "type": "string" } } } ], "responses": { "200": { "description": "Retrieved navigation property", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.usedInsight" } } }, "links": { "resource": { "operationId": "officeGraphInsights.used.GetResource", "parameters": { "user-id": "$request.path.user-id", "usedInsight-id": "$request.path.usedInsight-id", "entity-id": "$response.body#/id" } } } }, "default": { "$ref": "#/components/responses/error" } } }, "patch": { "tags": [ "users.officeGraphInsights" ], "summary": "Update the navigation property used in users", "operationId": "users.insights_UpdateUsed", "parameters": [ { "name": "user-id", "in": "path", "description": "key: user-id of user", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "user" }, { "name": "usedInsight-id", "in": "path", "description": "key: usedInsight-id of usedInsight", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "usedInsight" } ], "requestBody": { "description": "New navigation property values", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.usedInsight" } } }, "required": true }, "responses": { "204": { "description": "Success" }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-docs-operation-type": "operation" } }, "/users/{user-id}/insights/used/{usedInsight-id}/resource": { "get": { "tags": [ "users.officeGraphInsights" ], "summary": "Get resource from users", "operationId": "users.insights.used_GetResource", "parameters": [ { "name": "user-id", "in": "path", "description": "key: user-id of user", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "user" }, { "name": "usedInsight-id", "in": "path", "description": "key: usedInsight-id of usedInsight", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "usedInsight" }, { "name": "$select", "in": "query", "description": "Select properties to be returned", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "id" ], "type": "string" } } }, { "name": "$expand", "in": "query", "description": "Expand related entities", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "*" ], "type": "string" } } } ], "responses": { "200": { "description": "Retrieved navigation property", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.entity" } } } }, "default": { "$ref": "#/components/responses/error" } } } } }, "components": { "schemas": { "microsoft.graph.officeGraphInsights": { "allOf": [ { "$ref": "#/components/schemas/microsoft.graph.entity" }, { "title": "officeGraphInsights", "type": "object", "properties": { "trending": { "type": "array", "items": { "$ref": "#/components/schemas/microsoft.graph.trending" }, "description": "Calculated relationship identifying documents trending around a user. Trending documents are calculated based on activity of the user's closest network of people and include files stored in OneDrive for Business and SharePoint. Trending insights help the user to discover potentially useful content that the user has access to, but has never viewed before." }, "shared": { "type": "array", "items": { "$ref": "#/components/schemas/microsoft.graph.sharedInsight" }, "description": "Calculated relationship identifying documents shared with or by the user. This includes URLs, file attachments, and reference attachments to OneDrive for Business and SharePoint files found in Outlook messages and meetings. This also includes URLs and reference attachments to Teams conversations. Ordered by recency of share." }, "used": { "type": "array", "items": { "$ref": "#/components/schemas/microsoft.graph.usedInsight" }, "description": "Calculated relationship identifying the latest documents viewed or modified by a user, including OneDrive for Business and SharePoint documents, ranked by recency of use." } } } ], "example": { "id": "string (identifier)", "trending": [ { "@odata.type": "microsoft.graph.trending" } ], "shared": [ { "@odata.type": "microsoft.graph.sharedInsight" } ], "used": [ { "@odata.type": "microsoft.graph.usedInsight" } ] } }, "microsoft.graph.sharedInsight": { "allOf": [ { "$ref": "#/components/schemas/microsoft.graph.entity" }, { "title": "sharedInsight", "type": "object", "properties": { "lastShared": { "$ref": "#/components/schemas/microsoft.graph.sharingDetail" }, "sharingHistory": { "type": "array", "items": { "$ref": "#/components/schemas/microsoft.graph.sharingDetail" } }, "resourceVisualization": { "$ref": "#/components/schemas/microsoft.graph.resourceVisualization" }, "resourceReference": { "$ref": "#/components/schemas/microsoft.graph.resourceReference" }, "lastSharedMethod": { "$ref": "#/components/schemas/microsoft.graph.entity" }, "resource": { "$ref": "#/components/schemas/microsoft.graph.entity" } } } ], "example": { "id": "string (identifier)", "lastShared": { "@odata.type": "microsoft.graph.sharingDetail" }, "sharingHistory": [ { "@odata.type": "microsoft.graph.sharingDetail" } ], "resourceVisualization": { "@odata.type": "microsoft.graph.resourceVisualization" }, "resourceReference": { "@odata.type": "microsoft.graph.resourceReference" }, "lastSharedMethod": { "@odata.type": "microsoft.graph.entity" }, "resource": { "@odata.type": "microsoft.graph.entity" } } }, "microsoft.graph.entity": { "title": "entity", "type": "object", "properties": { "id": { "type": "string", "description": "Read-only." } }, "example": { "id": "string (identifier)" } }, "microsoft.graph.trending": { "allOf": [ { "$ref": "#/components/schemas/microsoft.graph.entity" }, { "title": "trending", "type": "object", "properties": { "weight": { "type": "number", "description": "Value indicating how much the document is currently trending. The larger the number, the more the document is currently trending around the user (the more relevant it is). Returned documents are sorted by this value.", "format": "double" }, "resourceVisualization": { "$ref": "#/components/schemas/microsoft.graph.resourceVisualization" }, "resourceReference": { "$ref": "#/components/schemas/microsoft.graph.resourceReference" }, "lastModifiedDateTime": { "pattern": "^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$", "type": "string", "format": "date-time", "nullable": true }, "resource": { "$ref": "#/components/schemas/microsoft.graph.entity" } } } ], "example": { "id": "string (identifier)", "weight": "double", "resourceVisualization": { "@odata.type": "microsoft.graph.resourceVisualization" }, "resourceReference": { "@odata.type": "microsoft.graph.resourceReference" }, "lastModifiedDateTime": "string (timestamp)", "resource": { "@odata.type": "microsoft.graph.entity" } } }, "microsoft.graph.usedInsight": { "allOf": [ { "$ref": "#/components/schemas/microsoft.graph.entity" }, { "title": "usedInsight", "type": "object", "properties": { "lastUsed": { "$ref": "#/components/schemas/microsoft.graph.usageDetails" }, "resourceVisualization": { "$ref": "#/components/schemas/microsoft.graph.resourceVisualization" }, "resourceReference": { "$ref": "#/components/schemas/microsoft.graph.resourceReference" }, "resource": { "$ref": "#/components/schemas/microsoft.graph.entity" } } } ], "example": { "id": "string (identifier)", "lastUsed": { "@odata.type": "microsoft.graph.usageDetails" }, "resourceVisualization": { "@odata.type": "microsoft.graph.resourceVisualization" }, "resourceReference": { "@odata.type": "microsoft.graph.resourceReference" }, "resource": { "@odata.type": "microsoft.graph.entity" } } }, "microsoft.graph.sharingDetail": { "title": "sharingDetail", "type": "object", "properties": { "sharedBy": { "$ref": "#/components/schemas/microsoft.graph.insightIdentity" }, "sharedDateTime": { "pattern": "^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$", "type": "string", "description": "The date and time the file was last shared. The timestamp represents date and time information using ISO 8601 format and is always in UTC time. For example, midnight UTC on Jan 1, 2014 would look like this: 2014-01-01T00:00:00Z. Read-only.", "format": "date-time", "nullable": true }, "sharingSubject": { "type": "string", "description": "The subject with which the document was shared.", "nullable": true }, "sharingType": { "type": "string", "description": "Determines the way the document was shared, can be by a 'Link', 'Attachment', 'Group', 'Site'.", "nullable": true }, "sharingReference": { "$ref": "#/components/schemas/microsoft.graph.resourceReference" } }, "example": { "sharedBy": { "@odata.type": "microsoft.graph.insightIdentity" }, "sharedDateTime": "string (timestamp)", "sharingSubject": "string", "sharingType": "string", "sharingReference": { "@odata.type": "microsoft.graph.resourceReference" } } }, "microsoft.graph.resourceVisualization": { "title": "resourceVisualization", "type": "object", "properties": { "title": { "type": "string", "description": "The item's title text.", "nullable": true }, "type": { "type": "string", "description": "The item's media type. Can be used for filtering for a specific file based on a specific type. See below for supported types.", "nullable": true }, "mediaType": { "type": "string", "description": "The item's media type. Can be used for filtering for a specific type of file based on supported IANA Media Mime Types. Note that not all Media Mime Types are supported.", "nullable": true }, "previewImageUrl": { "type": "string", "description": "A URL leading to the preview image for the item.", "nullable": true }, "previewText": { "type": "string", "description": "A preview text for the item.", "nullable": true }, "containerWebUrl": { "type": "string", "description": "A path leading to the folder in which the item is stored.", "nullable": true }, "containerDisplayName": { "type": "string", "description": "A string describing where the item is stored. For example, the name of a SharePoint site or the user name identifying the owner of the OneDrive storing the item.", "nullable": true }, "containerType": { "type": "string", "description": "Can be used for filtering by the type of container in which the file is stored. Such as Site or OneDriveBusiness.", "nullable": true } }, "example": { "title": "string", "type": "string", "mediaType": "string", "previewImageUrl": "string", "previewText": "string", "containerWebUrl": "string", "containerDisplayName": "string", "containerType": "string" } }, "microsoft.graph.resourceReference": { "title": "resourceReference", "type": "object", "properties": { "webUrl": { "type": "string", "description": "A URL leading to the referenced item.", "nullable": true }, "id": { "type": "string", "description": "The item's unique identifier.", "nullable": true }, "type": { "type": "string", "description": "A string value that can be used to classify the item, such as 'microsoft.graph.driveItem'", "nullable": true } }, "example": { "webUrl": "string", "id": "string", "type": "string" } }, "microsoft.graph.usageDetails": { "title": "usageDetails", "type": "object", "properties": { "lastAccessedDateTime": { "pattern": "^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$", "type": "string", "description": "The date and time the resource was last accessed by the user. The timestamp represents date and time information using ISO 8601 format and is always in UTC time. For example, midnight UTC on Jan 1, 2014 would look like this: 2014-01-01T00:00:00Z. Read-only.", "format": "date-time", "nullable": true }, "lastModifiedDateTime": { "pattern": "^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$", "type": "string", "description": "The date and time the resource was last modified by the user. The timestamp represents date and time information using ISO 8601 format and is always in UTC time. For example, midnight UTC on Jan 1, 2014 would look like this: 2014-01-01T00:00:00Z. Read-only.", "format": "date-time", "nullable": true } }, "example": { "lastAccessedDateTime": "string (timestamp)", "lastModifiedDateTime": "string (timestamp)" } }, "odata.error": { "required": [ "error" ], "type": "object", "properties": { "error": { "$ref": "#/components/schemas/odata.error.main" } } }, "microsoft.graph.insightIdentity": { "title": "insightIdentity", "type": "object", "properties": { "displayName": { "type": "string", "description": "The display name of the user who shared the item.", "nullable": true }, "id": { "type": "string", "description": "The id of the user who shared the item.", "nullable": true }, "address": { "type": "string", "description": "The email address of the user who shared the item.", "nullable": true } }, "example": { "displayName": "string", "id": "string", "address": "string" } }, "odata.error.main": { "required": [ "code", "message" ], "type": "object", "properties": { "code": { "type": "string" }, "message": { "type": "string" }, "target": { "type": "string" }, "details": { "type": "array", "items": { "$ref": "#/components/schemas/odata.error.detail" } }, "innererror": { "type": "object", "description": "The structure of this object is service-specific" } } }, "odata.error.detail": { "required": [ "code", "message" ], "type": "object", "properties": { "code": { "type": "string" }, "message": { "type": "string" }, "target": { "type": "string" } } } }, "responses": { "error": { "description": "error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/odata.error" } } } } }, "parameters": { "top": { "name": "$top", "in": "query", "description": "Show only the first n items", "schema": { "minimum": 0, "type": "integer" }, "example": 50 }, "skip": { "name": "$skip", "in": "query", "description": "Skip the first n items", "schema": { "minimum": 0, "type": "integer" } }, "search": { "name": "$search", "in": "query", "description": "Search items by search phrases", "schema": { "type": "string" } }, "filter": { "name": "$filter", "in": "query", "description": "Filter items by property values", "schema": { "type": "string" } }, "count": { "name": "$count", "in": "query", "description": "Include count of items", "schema": { "type": "boolean" } } }, "securitySchemes": { "azureaadv2": { "type": "oauth2", "flows": { "authorizationCode": { "authorizationUrl": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize", "tokenUrl": "https://login.microsoftonline.com/common/oauth2/v2.0/token", "scopes": {} } } } } }, "security": [ { "azureaadv2": [] } ] } ================================================ FILE: test/Microsoft.HttpRepl.Tests/Resources/OpenApiDescriptions/MicrosoftGraph.PowershellSdk.Analytics.yml ================================================ # Originally from https://github.com/microsoftgraph/msgraph-sdk-powershell/blob/dev/openApiDocs/v1.0/Analytics.yml openapi: 3.0.1 info: title: Analytics version: v1.0 servers: - url: https://graph.microsoft.com/v1.0/ description: Core paths: '/users/{user-id}/insights': get: tags: - users.officeGraphInsights summary: Get insights from users operationId: users_GetInsights parameters: - name: user-id in: path description: 'key: user-id of user' required: true schema: type: string x-ms-docs-key-type: user - name: $select in: query description: Select properties to be returned style: form explode: false schema: uniqueItems: true type: array items: enum: - id - trending - shared - used type: string - name: $expand in: query description: Expand related entities style: form explode: false schema: uniqueItems: true type: array items: enum: - '*' - trending - shared - used type: string responses: '200': description: Retrieved navigation property content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.officeGraphInsights' links: trending: operationId: user.insights.GetTrending parameters: user-id: $request.path.user-id trending-id: $response.body#/id shared: operationId: user.insights.GetShared parameters: user-id: $request.path.user-id sharedInsight-id: $response.body#/id used: operationId: user.insights.GetUsed parameters: user-id: $request.path.user-id usedInsight-id: $response.body#/id default: $ref: '#/components/responses/error' patch: tags: - users.officeGraphInsights summary: Update the navigation property insights in users operationId: users_UpdateInsights parameters: - name: user-id in: path description: 'key: user-id of user' required: true schema: type: string x-ms-docs-key-type: user requestBody: description: New navigation property values content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.officeGraphInsights' required: true responses: '204': description: Success default: $ref: '#/components/responses/error' x-ms-docs-operation-type: operation '/users/{user-id}/insights/shared': get: tags: - users.officeGraphInsights summary: Get shared from users operationId: users.insights_ListShared parameters: - name: user-id in: path description: 'key: user-id of user' required: true schema: type: string x-ms-docs-key-type: user - $ref: '#/components/parameters/top' - $ref: '#/components/parameters/skip' - $ref: '#/components/parameters/search' - $ref: '#/components/parameters/filter' - $ref: '#/components/parameters/count' - name: $orderby in: query description: Order items by property values style: form explode: false schema: uniqueItems: true type: array items: enum: - id - id desc - lastShared - lastShared desc - sharingHistory - sharingHistory desc - resourceVisualization - resourceVisualization desc - resourceReference - resourceReference desc type: string - name: $select in: query description: Select properties to be returned style: form explode: false schema: uniqueItems: true type: array items: enum: - id - lastShared - sharingHistory - resourceVisualization - resourceReference - lastSharedMethod - resource type: string - name: $expand in: query description: Expand related entities style: form explode: false schema: uniqueItems: true type: array items: enum: - '*' - lastSharedMethod - resource type: string responses: '200': description: Retrieved navigation property content: application/json: schema: title: Collection of sharedInsight type: object properties: value: type: array items: $ref: '#/components/schemas/microsoft.graph.sharedInsight' '@odata.nextLink': type: string default: $ref: '#/components/responses/error' x-ms-pageable: nextLinkName: '@odata.nextLink' operationName: listMore x-ms-docs-operation-type: operation post: tags: - users.officeGraphInsights summary: Create new navigation property to shared for users operationId: users.insights_CreateShared parameters: - name: user-id in: path description: 'key: user-id of user' required: true schema: type: string x-ms-docs-key-type: user requestBody: description: New navigation property content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.sharedInsight' required: true responses: '201': description: Created navigation property. content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.sharedInsight' default: $ref: '#/components/responses/error' x-ms-docs-operation-type: operation '/users/{user-id}/insights/shared/{sharedInsight-id}': get: tags: - users.officeGraphInsights summary: Get shared from users operationId: users.insights_GetShared parameters: - name: user-id in: path description: 'key: user-id of user' required: true schema: type: string x-ms-docs-key-type: user - name: sharedInsight-id in: path description: 'key: sharedInsight-id of sharedInsight' required: true schema: type: string x-ms-docs-key-type: sharedInsight - name: $select in: query description: Select properties to be returned style: form explode: false schema: uniqueItems: true type: array items: enum: - id - lastShared - sharingHistory - resourceVisualization - resourceReference - lastSharedMethod - resource type: string - name: $expand in: query description: Expand related entities style: form explode: false schema: uniqueItems: true type: array items: enum: - '*' - lastSharedMethod - resource type: string responses: '200': description: Retrieved navigation property content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.sharedInsight' links: lastSharedMethod: operationId: officeGraphInsights.shared.GetLastSharedMethod parameters: user-id: $request.path.user-id sharedInsight-id: $request.path.sharedInsight-id entity-id: $response.body#/id resource: operationId: officeGraphInsights.shared.GetResource parameters: user-id: $request.path.user-id sharedInsight-id: $request.path.sharedInsight-id entity-id: $response.body#/id default: $ref: '#/components/responses/error' patch: tags: - users.officeGraphInsights summary: Update the navigation property shared in users operationId: users.insights_UpdateShared parameters: - name: user-id in: path description: 'key: user-id of user' required: true schema: type: string x-ms-docs-key-type: user - name: sharedInsight-id in: path description: 'key: sharedInsight-id of sharedInsight' required: true schema: type: string x-ms-docs-key-type: sharedInsight requestBody: description: New navigation property values content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.sharedInsight' required: true responses: '204': description: Success default: $ref: '#/components/responses/error' x-ms-docs-operation-type: operation '/users/{user-id}/insights/shared/{sharedInsight-id}/lastSharedMethod': get: tags: - users.officeGraphInsights summary: Get lastSharedMethod from users operationId: users.insights.shared_GetLastSharedMethod parameters: - name: user-id in: path description: 'key: user-id of user' required: true schema: type: string x-ms-docs-key-type: user - name: sharedInsight-id in: path description: 'key: sharedInsight-id of sharedInsight' required: true schema: type: string x-ms-docs-key-type: sharedInsight - name: $select in: query description: Select properties to be returned style: form explode: false schema: uniqueItems: true type: array items: enum: - id type: string - name: $expand in: query description: Expand related entities style: form explode: false schema: uniqueItems: true type: array items: enum: - '*' type: string responses: '200': description: Retrieved navigation property content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.entity' default: $ref: '#/components/responses/error' '/users/{user-id}/insights/shared/{sharedInsight-id}/resource': get: tags: - users.officeGraphInsights summary: Get resource from users operationId: users.insights.shared_GetResource parameters: - name: user-id in: path description: 'key: user-id of user' required: true schema: type: string x-ms-docs-key-type: user - name: sharedInsight-id in: path description: 'key: sharedInsight-id of sharedInsight' required: true schema: type: string x-ms-docs-key-type: sharedInsight - name: $select in: query description: Select properties to be returned style: form explode: false schema: uniqueItems: true type: array items: enum: - id type: string - name: $expand in: query description: Expand related entities style: form explode: false schema: uniqueItems: true type: array items: enum: - '*' type: string responses: '200': description: Retrieved navigation property content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.entity' default: $ref: '#/components/responses/error' '/users/{user-id}/insights/trending': get: tags: - users.officeGraphInsights summary: Get trending from users operationId: users.insights_ListTrending parameters: - name: user-id in: path description: 'key: user-id of user' required: true schema: type: string x-ms-docs-key-type: user - $ref: '#/components/parameters/top' - $ref: '#/components/parameters/skip' - $ref: '#/components/parameters/search' - $ref: '#/components/parameters/filter' - $ref: '#/components/parameters/count' - name: $orderby in: query description: Order items by property values style: form explode: false schema: uniqueItems: true type: array items: enum: - id - id desc - weight - weight desc - resourceVisualization - resourceVisualization desc - resourceReference - resourceReference desc - lastModifiedDateTime - lastModifiedDateTime desc type: string - name: $select in: query description: Select properties to be returned style: form explode: false schema: uniqueItems: true type: array items: enum: - id - weight - resourceVisualization - resourceReference - lastModifiedDateTime - resource type: string - name: $expand in: query description: Expand related entities style: form explode: false schema: uniqueItems: true type: array items: enum: - '*' - resource type: string responses: '200': description: Retrieved navigation property content: application/json: schema: title: Collection of trending type: object properties: value: type: array items: $ref: '#/components/schemas/microsoft.graph.trending' '@odata.nextLink': type: string default: $ref: '#/components/responses/error' x-ms-pageable: nextLinkName: '@odata.nextLink' operationName: listMore x-ms-docs-operation-type: operation post: tags: - users.officeGraphInsights summary: Create new navigation property to trending for users operationId: users.insights_CreateTrending parameters: - name: user-id in: path description: 'key: user-id of user' required: true schema: type: string x-ms-docs-key-type: user requestBody: description: New navigation property content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.trending' required: true responses: '201': description: Created navigation property. content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.trending' default: $ref: '#/components/responses/error' x-ms-docs-operation-type: operation '/users/{user-id}/insights/trending/{trending-id}': get: tags: - users.officeGraphInsights summary: Get trending from users operationId: users.insights_GetTrending parameters: - name: user-id in: path description: 'key: user-id of user' required: true schema: type: string x-ms-docs-key-type: user - name: trending-id in: path description: 'key: trending-id of trending' required: true schema: type: string x-ms-docs-key-type: trending - name: $select in: query description: Select properties to be returned style: form explode: false schema: uniqueItems: true type: array items: enum: - id - weight - resourceVisualization - resourceReference - lastModifiedDateTime - resource type: string - name: $expand in: query description: Expand related entities style: form explode: false schema: uniqueItems: true type: array items: enum: - '*' - resource type: string responses: '200': description: Retrieved navigation property content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.trending' links: resource: operationId: officeGraphInsights.trending.GetResource parameters: user-id: $request.path.user-id trending-id: $request.path.trending-id entity-id: $response.body#/id default: $ref: '#/components/responses/error' patch: tags: - users.officeGraphInsights summary: Update the navigation property trending in users operationId: users.insights_UpdateTrending parameters: - name: user-id in: path description: 'key: user-id of user' required: true schema: type: string x-ms-docs-key-type: user - name: trending-id in: path description: 'key: trending-id of trending' required: true schema: type: string x-ms-docs-key-type: trending requestBody: description: New navigation property values content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.trending' required: true responses: '204': description: Success default: $ref: '#/components/responses/error' x-ms-docs-operation-type: operation '/users/{user-id}/insights/trending/{trending-id}/resource': get: tags: - users.officeGraphInsights summary: Get resource from users operationId: users.insights.trending_GetResource parameters: - name: user-id in: path description: 'key: user-id of user' required: true schema: type: string x-ms-docs-key-type: user - name: trending-id in: path description: 'key: trending-id of trending' required: true schema: type: string x-ms-docs-key-type: trending - name: $select in: query description: Select properties to be returned style: form explode: false schema: uniqueItems: true type: array items: enum: - id type: string - name: $expand in: query description: Expand related entities style: form explode: false schema: uniqueItems: true type: array items: enum: - '*' type: string responses: '200': description: Retrieved navigation property content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.entity' default: $ref: '#/components/responses/error' '/users/{user-id}/insights/used': get: tags: - users.officeGraphInsights summary: Get used from users operationId: users.insights_ListUsed parameters: - name: user-id in: path description: 'key: user-id of user' required: true schema: type: string x-ms-docs-key-type: user - $ref: '#/components/parameters/top' - $ref: '#/components/parameters/skip' - $ref: '#/components/parameters/search' - $ref: '#/components/parameters/filter' - $ref: '#/components/parameters/count' - name: $orderby in: query description: Order items by property values style: form explode: false schema: uniqueItems: true type: array items: enum: - id - id desc - lastUsed - lastUsed desc - resourceVisualization - resourceVisualization desc - resourceReference - resourceReference desc type: string - name: $select in: query description: Select properties to be returned style: form explode: false schema: uniqueItems: true type: array items: enum: - id - lastUsed - resourceVisualization - resourceReference - resource type: string - name: $expand in: query description: Expand related entities style: form explode: false schema: uniqueItems: true type: array items: enum: - '*' - resource type: string responses: '200': description: Retrieved navigation property content: application/json: schema: title: Collection of usedInsight type: object properties: value: type: array items: $ref: '#/components/schemas/microsoft.graph.usedInsight' '@odata.nextLink': type: string default: $ref: '#/components/responses/error' x-ms-pageable: nextLinkName: '@odata.nextLink' operationName: listMore x-ms-docs-operation-type: operation post: tags: - users.officeGraphInsights summary: Create new navigation property to used for users operationId: users.insights_CreateUsed parameters: - name: user-id in: path description: 'key: user-id of user' required: true schema: type: string x-ms-docs-key-type: user requestBody: description: New navigation property content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.usedInsight' required: true responses: '201': description: Created navigation property. content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.usedInsight' default: $ref: '#/components/responses/error' x-ms-docs-operation-type: operation '/users/{user-id}/insights/used/{usedInsight-id}': get: tags: - users.officeGraphInsights summary: Get used from users operationId: users.insights_GetUsed parameters: - name: user-id in: path description: 'key: user-id of user' required: true schema: type: string x-ms-docs-key-type: user - name: usedInsight-id in: path description: 'key: usedInsight-id of usedInsight' required: true schema: type: string x-ms-docs-key-type: usedInsight - name: $select in: query description: Select properties to be returned style: form explode: false schema: uniqueItems: true type: array items: enum: - id - lastUsed - resourceVisualization - resourceReference - resource type: string - name: $expand in: query description: Expand related entities style: form explode: false schema: uniqueItems: true type: array items: enum: - '*' - resource type: string responses: '200': description: Retrieved navigation property content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.usedInsight' links: resource: operationId: officeGraphInsights.used.GetResource parameters: user-id: $request.path.user-id usedInsight-id: $request.path.usedInsight-id entity-id: $response.body#/id default: $ref: '#/components/responses/error' patch: tags: - users.officeGraphInsights summary: Update the navigation property used in users operationId: users.insights_UpdateUsed parameters: - name: user-id in: path description: 'key: user-id of user' required: true schema: type: string x-ms-docs-key-type: user - name: usedInsight-id in: path description: 'key: usedInsight-id of usedInsight' required: true schema: type: string x-ms-docs-key-type: usedInsight requestBody: description: New navigation property values content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.usedInsight' required: true responses: '204': description: Success default: $ref: '#/components/responses/error' x-ms-docs-operation-type: operation '/users/{user-id}/insights/used/{usedInsight-id}/resource': get: tags: - users.officeGraphInsights summary: Get resource from users operationId: users.insights.used_GetResource parameters: - name: user-id in: path description: 'key: user-id of user' required: true schema: type: string x-ms-docs-key-type: user - name: usedInsight-id in: path description: 'key: usedInsight-id of usedInsight' required: true schema: type: string x-ms-docs-key-type: usedInsight - name: $select in: query description: Select properties to be returned style: form explode: false schema: uniqueItems: true type: array items: enum: - id type: string - name: $expand in: query description: Expand related entities style: form explode: false schema: uniqueItems: true type: array items: enum: - '*' type: string responses: '200': description: Retrieved navigation property content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.entity' default: $ref: '#/components/responses/error' components: schemas: microsoft.graph.officeGraphInsights: allOf: - $ref: '#/components/schemas/microsoft.graph.entity' - title: officeGraphInsights type: object properties: trending: type: array items: $ref: '#/components/schemas/microsoft.graph.trending' description: 'Calculated relationship identifying documents trending around a user. Trending documents are calculated based on activity of the user''s closest network of people and include files stored in OneDrive for Business and SharePoint. Trending insights help the user to discover potentially useful content that the user has access to, but has never viewed before.' shared: type: array items: $ref: '#/components/schemas/microsoft.graph.sharedInsight' description: 'Calculated relationship identifying documents shared with or by the user. This includes URLs, file attachments, and reference attachments to OneDrive for Business and SharePoint files found in Outlook messages and meetings. This also includes URLs and reference attachments to Teams conversations. Ordered by recency of share.' used: type: array items: $ref: '#/components/schemas/microsoft.graph.usedInsight' description: 'Calculated relationship identifying the latest documents viewed or modified by a user, including OneDrive for Business and SharePoint documents, ranked by recency of use.' example: id: string (identifier) trending: - '@odata.type': microsoft.graph.trending shared: - '@odata.type': microsoft.graph.sharedInsight used: - '@odata.type': microsoft.graph.usedInsight microsoft.graph.sharedInsight: allOf: - $ref: '#/components/schemas/microsoft.graph.entity' - title: sharedInsight type: object properties: lastShared: $ref: '#/components/schemas/microsoft.graph.sharingDetail' sharingHistory: type: array items: $ref: '#/components/schemas/microsoft.graph.sharingDetail' resourceVisualization: $ref: '#/components/schemas/microsoft.graph.resourceVisualization' resourceReference: $ref: '#/components/schemas/microsoft.graph.resourceReference' lastSharedMethod: $ref: '#/components/schemas/microsoft.graph.entity' resource: $ref: '#/components/schemas/microsoft.graph.entity' example: id: string (identifier) lastShared: '@odata.type': microsoft.graph.sharingDetail sharingHistory: - '@odata.type': microsoft.graph.sharingDetail resourceVisualization: '@odata.type': microsoft.graph.resourceVisualization resourceReference: '@odata.type': microsoft.graph.resourceReference lastSharedMethod: '@odata.type': microsoft.graph.entity resource: '@odata.type': microsoft.graph.entity microsoft.graph.entity: title: entity type: object properties: id: type: string description: Read-only. example: id: string (identifier) microsoft.graph.trending: allOf: - $ref: '#/components/schemas/microsoft.graph.entity' - title: trending type: object properties: weight: type: number description: 'Value indicating how much the document is currently trending. The larger the number, the more the document is currently trending around the user (the more relevant it is). Returned documents are sorted by this value.' format: double resourceVisualization: $ref: '#/components/schemas/microsoft.graph.resourceVisualization' resourceReference: $ref: '#/components/schemas/microsoft.graph.resourceReference' lastModifiedDateTime: pattern: '^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$' type: string format: date-time nullable: true resource: $ref: '#/components/schemas/microsoft.graph.entity' example: id: string (identifier) weight: double resourceVisualization: '@odata.type': microsoft.graph.resourceVisualization resourceReference: '@odata.type': microsoft.graph.resourceReference lastModifiedDateTime: string (timestamp) resource: '@odata.type': microsoft.graph.entity microsoft.graph.usedInsight: allOf: - $ref: '#/components/schemas/microsoft.graph.entity' - title: usedInsight type: object properties: lastUsed: $ref: '#/components/schemas/microsoft.graph.usageDetails' resourceVisualization: $ref: '#/components/schemas/microsoft.graph.resourceVisualization' resourceReference: $ref: '#/components/schemas/microsoft.graph.resourceReference' resource: $ref: '#/components/schemas/microsoft.graph.entity' example: id: string (identifier) lastUsed: '@odata.type': microsoft.graph.usageDetails resourceVisualization: '@odata.type': microsoft.graph.resourceVisualization resourceReference: '@odata.type': microsoft.graph.resourceReference resource: '@odata.type': microsoft.graph.entity microsoft.graph.sharingDetail: title: sharingDetail type: object properties: sharedBy: $ref: '#/components/schemas/microsoft.graph.insightIdentity' sharedDateTime: pattern: '^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$' type: string description: 'The date and time the file was last shared. The timestamp represents date and time information using ISO 8601 format and is always in UTC time. For example, midnight UTC on Jan 1, 2014 would look like this: 2014-01-01T00:00:00Z. Read-only.' format: date-time nullable: true sharingSubject: type: string description: The subject with which the document was shared. nullable: true sharingType: type: string description: 'Determines the way the document was shared, can be by a ''Link'', ''Attachment'', ''Group'', ''Site''.' nullable: true sharingReference: $ref: '#/components/schemas/microsoft.graph.resourceReference' example: sharedBy: '@odata.type': microsoft.graph.insightIdentity sharedDateTime: string (timestamp) sharingSubject: string sharingType: string sharingReference: '@odata.type': microsoft.graph.resourceReference microsoft.graph.resourceVisualization: title: resourceVisualization type: object properties: title: type: string description: The item's title text. nullable: true type: type: string description: The item's media type. Can be used for filtering for a specific file based on a specific type. See below for supported types. nullable: true mediaType: type: string description: The item's media type. Can be used for filtering for a specific type of file based on supported IANA Media Mime Types. Note that not all Media Mime Types are supported. nullable: true previewImageUrl: type: string description: A URL leading to the preview image for the item. nullable: true previewText: type: string description: A preview text for the item. nullable: true containerWebUrl: type: string description: A path leading to the folder in which the item is stored. nullable: true containerDisplayName: type: string description: 'A string describing where the item is stored. For example, the name of a SharePoint site or the user name identifying the owner of the OneDrive storing the item.' nullable: true containerType: type: string description: Can be used for filtering by the type of container in which the file is stored. Such as Site or OneDriveBusiness. nullable: true example: title: string type: string mediaType: string previewImageUrl: string previewText: string containerWebUrl: string containerDisplayName: string containerType: string microsoft.graph.resourceReference: title: resourceReference type: object properties: webUrl: type: string description: A URL leading to the referenced item. nullable: true id: type: string description: The item's unique identifier. nullable: true type: type: string description: 'A string value that can be used to classify the item, such as ''microsoft.graph.driveItem''' nullable: true example: webUrl: string id: string type: string microsoft.graph.usageDetails: title: usageDetails type: object properties: lastAccessedDateTime: pattern: '^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$' type: string description: 'The date and time the resource was last accessed by the user. The timestamp represents date and time information using ISO 8601 format and is always in UTC time. For example, midnight UTC on Jan 1, 2014 would look like this: 2014-01-01T00:00:00Z. Read-only.' format: date-time nullable: true lastModifiedDateTime: pattern: '^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$' type: string description: 'The date and time the resource was last modified by the user. The timestamp represents date and time information using ISO 8601 format and is always in UTC time. For example, midnight UTC on Jan 1, 2014 would look like this: 2014-01-01T00:00:00Z. Read-only.' format: date-time nullable: true example: lastAccessedDateTime: string (timestamp) lastModifiedDateTime: string (timestamp) odata.error: required: - error type: object properties: error: $ref: '#/components/schemas/odata.error.main' microsoft.graph.insightIdentity: title: insightIdentity type: object properties: displayName: type: string description: The display name of the user who shared the item. nullable: true id: type: string description: The id of the user who shared the item. nullable: true address: type: string description: The email address of the user who shared the item. nullable: true example: displayName: string id: string address: string odata.error.main: required: - code - message type: object properties: code: type: string message: type: string target: type: string details: type: array items: $ref: '#/components/schemas/odata.error.detail' innererror: type: object description: The structure of this object is service-specific odata.error.detail: required: - code - message type: object properties: code: type: string message: type: string target: type: string responses: error: description: error content: application/json: schema: $ref: '#/components/schemas/odata.error' parameters: top: name: $top in: query description: Show only the first n items schema: minimum: 0 type: integer example: 50 skip: name: $skip in: query description: Skip the first n items schema: minimum: 0 type: integer search: name: $search in: query description: Search items by search phrases schema: type: string filter: name: $filter in: query description: Filter items by property values schema: type: string count: name: $count in: query description: Include count of items schema: type: boolean securitySchemes: azureaadv2: type: oauth2 flows: authorizationCode: authorizationUrl: https://login.microsoftonline.com/common/oauth2/v2.0/authorize tokenUrl: https://login.microsoftonline.com/common/oauth2/v2.0/token scopes: { } security: - azureaadv2: [ ] ================================================ FILE: test/Microsoft.HttpRepl.Tests/Resources/OpenApiDescriptions/MicrosoftGraph.PowershellSdk.CloudCommunications.json ================================================ { "openapi": "3.0.1", "info": { "title": "CloudCommunications", "version": "v1.0" }, "servers": [ { "url": "https://graph.microsoft.com/v1.0/", "description": "Core" } ], "paths": { "/communications": { "get": { "tags": [ "communications.cloudCommunications" ], "summary": "Get communications", "operationId": "communications.cloudCommunications_GetCloudCommunications", "parameters": [ { "name": "$select", "in": "query", "description": "Select properties to be returned", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "id", "calls", "onlineMeetings" ], "type": "string" } } }, { "name": "$expand", "in": "query", "description": "Expand related entities", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "*", "calls", "onlineMeetings" ], "type": "string" } } } ], "responses": { "200": { "description": "Retrieved entity", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.cloudCommunications" } } }, "links": { "calls": { "operationId": "communications.GetCalls", "parameters": { "call-id": "$response.body#/id" } }, "onlineMeetings": { "operationId": "communications.GetOnlineMeetings", "parameters": { "onlineMeeting-id": "$response.body#/id" } } } }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-docs-operation-type": "operation" }, "patch": { "tags": [ "communications.cloudCommunications" ], "summary": "Update communications", "operationId": "communications.cloudCommunications_UpdateCloudCommunications", "requestBody": { "description": "New property values", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.cloudCommunications" } } }, "required": true }, "responses": { "204": { "description": "Success" }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-docs-operation-type": "operation" } }, "/communications/calls": { "get": { "tags": [ "communications.call" ], "summary": "Get calls from communications", "operationId": "communications_ListCalls", "parameters": [ { "$ref": "#/components/parameters/top" }, { "$ref": "#/components/parameters/skip" }, { "$ref": "#/components/parameters/search" }, { "$ref": "#/components/parameters/filter" }, { "$ref": "#/components/parameters/count" }, { "name": "$orderby", "in": "query", "description": "Order items by property values", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "id", "id desc", "state", "state desc", "mediaState", "mediaState desc", "resultInfo", "resultInfo desc", "direction", "direction desc", "subject", "subject desc", "callbackUri", "callbackUri desc", "callRoutes", "callRoutes desc", "source", "source desc", "targets", "targets desc", "requestedModalities", "requestedModalities desc", "mediaConfig", "mediaConfig desc", "chatInfo", "chatInfo desc", "callOptions", "callOptions desc", "meetingInfo", "meetingInfo desc", "tenantId", "tenantId desc", "myParticipantId", "myParticipantId desc", "toneInfo", "toneInfo desc", "callChainId", "callChainId desc", "incomingContext", "incomingContext desc" ], "type": "string" } } }, { "name": "$select", "in": "query", "description": "Select properties to be returned", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "id", "state", "mediaState", "resultInfo", "direction", "subject", "callbackUri", "callRoutes", "source", "targets", "requestedModalities", "mediaConfig", "chatInfo", "callOptions", "meetingInfo", "tenantId", "myParticipantId", "toneInfo", "callChainId", "incomingContext", "participants", "operations" ], "type": "string" } } }, { "name": "$expand", "in": "query", "description": "Expand related entities", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "*", "participants", "operations" ], "type": "string" } } } ], "responses": { "200": { "description": "Retrieved navigation property", "content": { "application/json": { "schema": { "title": "Collection of call", "type": "object", "properties": { "value": { "type": "array", "items": { "$ref": "#/components/schemas/microsoft.graph.call" } }, "@odata.nextLink": { "type": "string" } } } } } }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-pageable": { "nextLinkName": "@odata.nextLink", "operationName": "listMore" }, "x-ms-docs-operation-type": "operation" }, "post": { "tags": [ "communications.call" ], "summary": "Create new navigation property to calls for communications", "operationId": "communications_CreateCalls", "requestBody": { "description": "New navigation property", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.call" } } }, "required": true }, "responses": { "201": { "description": "Created navigation property.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.call" } } } }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-docs-operation-type": "operation" } }, "/communications/calls/{call-id}": { "get": { "tags": [ "communications.call" ], "summary": "Get calls from communications", "operationId": "communications_GetCalls", "parameters": [ { "name": "call-id", "in": "path", "description": "key: call-id of call", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "call" }, { "name": "$select", "in": "query", "description": "Select properties to be returned", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "id", "state", "mediaState", "resultInfo", "direction", "subject", "callbackUri", "callRoutes", "source", "targets", "requestedModalities", "mediaConfig", "chatInfo", "callOptions", "meetingInfo", "tenantId", "myParticipantId", "toneInfo", "callChainId", "incomingContext", "participants", "operations" ], "type": "string" } } }, { "name": "$expand", "in": "query", "description": "Expand related entities", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "*", "participants", "operations" ], "type": "string" } } } ], "responses": { "200": { "description": "Retrieved navigation property", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.call" } } }, "links": { "participants": { "operationId": "cloudCommunications.calls.GetParticipants", "parameters": { "call-id": "$request.path.call-id", "participant-id": "$response.body#/id" } }, "operations": { "operationId": "cloudCommunications.calls.GetOperations", "parameters": { "call-id": "$request.path.call-id", "commsOperation-id": "$response.body#/id" } } } }, "default": { "$ref": "#/components/responses/error" } } }, "patch": { "tags": [ "communications.call" ], "summary": "Update the navigation property calls in communications", "operationId": "communications_UpdateCalls", "parameters": [ { "name": "call-id", "in": "path", "description": "key: call-id of call", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "call" } ], "requestBody": { "description": "New navigation property values", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.call" } } }, "required": true }, "responses": { "204": { "description": "Success" }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-docs-operation-type": "operation" } }, "/communications/calls/{call-id}/microsoft.graph.answer": { "post": { "tags": [ "communications.Actions" ], "summary": "Invoke action answer", "operationId": "communications.calls_answer", "parameters": [ { "name": "call-id", "in": "path", "description": "key: call-id of call", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "call" } ], "requestBody": { "description": "Action parameters", "content": { "application/json": { "schema": { "type": "object", "properties": { "callbackUri": { "type": "string" }, "mediaConfig": { "$ref": "#/components/schemas/microsoft.graph.mediaConfig" }, "acceptedModalities": { "type": "array", "items": { "$ref": "#/components/schemas/microsoft.graph.modality" } } } } } }, "required": true }, "responses": { "204": { "description": "Success" }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-docs-operation-type": "action" } }, "/communications/calls/{call-id}/microsoft.graph.changeScreenSharingRole": { "post": { "tags": [ "communications.Actions" ], "summary": "Invoke action changeScreenSharingRole", "operationId": "communications.calls_changeScreenSharingRole", "parameters": [ { "name": "call-id", "in": "path", "description": "key: call-id of call", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "call" } ], "requestBody": { "description": "Action parameters", "content": { "application/json": { "schema": { "type": "object", "properties": { "role": { "$ref": "#/components/schemas/microsoft.graph.screenSharingRole" } } } } }, "required": true }, "responses": { "204": { "description": "Success" }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-docs-operation-type": "action" } }, "/communications/calls/{call-id}/microsoft.graph.keepAlive": { "post": { "tags": [ "communications.Actions" ], "summary": "Invoke action keepAlive", "operationId": "communications.calls_keepAlive", "parameters": [ { "name": "call-id", "in": "path", "description": "key: call-id of call", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "call" } ], "responses": { "204": { "description": "Success" }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-docs-operation-type": "action" } }, "/communications/calls/{call-id}/microsoft.graph.mute": { "post": { "tags": [ "communications.Actions" ], "summary": "Invoke action mute", "operationId": "communications.calls_mute", "parameters": [ { "name": "call-id", "in": "path", "description": "key: call-id of call", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "call" } ], "requestBody": { "description": "Action parameters", "content": { "application/json": { "schema": { "type": "object", "properties": { "clientContext": { "type": "string", "nullable": true } } } } }, "required": true }, "responses": { "200": { "description": "Success", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.muteParticipantOperation" } } } }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-docs-operation-type": "action" } }, "/communications/calls/{call-id}/microsoft.graph.playPrompt": { "post": { "tags": [ "communications.Actions" ], "summary": "Invoke action playPrompt", "operationId": "communications.calls_playPrompt", "parameters": [ { "name": "call-id", "in": "path", "description": "key: call-id of call", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "call" } ], "requestBody": { "description": "Action parameters", "content": { "application/json": { "schema": { "type": "object", "properties": { "prompts": { "type": "array", "items": { "$ref": "#/components/schemas/microsoft.graph.prompt" } }, "clientContext": { "type": "string", "nullable": true } } } } }, "required": true }, "responses": { "200": { "description": "Success", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.playPromptOperation" } } } }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-docs-operation-type": "action" } }, "/communications/calls/{call-id}/microsoft.graph.recordResponse": { "post": { "tags": [ "communications.Actions" ], "summary": "Invoke action recordResponse", "operationId": "communications.calls_recordResponse", "parameters": [ { "name": "call-id", "in": "path", "description": "key: call-id of call", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "call" } ], "requestBody": { "description": "Action parameters", "content": { "application/json": { "schema": { "type": "object", "properties": { "prompts": { "type": "array", "items": { "$ref": "#/components/schemas/microsoft.graph.prompt" } }, "bargeInAllowed": { "type": "boolean", "default": false, "nullable": true }, "initialSilenceTimeoutInSeconds": { "maximum": 2147483647, "minimum": -2147483648, "type": "integer", "format": "int32", "nullable": true }, "maxSilenceTimeoutInSeconds": { "maximum": 2147483647, "minimum": -2147483648, "type": "integer", "format": "int32", "nullable": true }, "maxRecordDurationInSeconds": { "maximum": 2147483647, "minimum": -2147483648, "type": "integer", "format": "int32", "nullable": true }, "playBeep": { "type": "boolean", "default": false, "nullable": true }, "stopTones": { "type": "array", "items": { "type": "string", "nullable": true } }, "clientContext": { "type": "string", "nullable": true } } } } }, "required": true }, "responses": { "200": { "description": "Success", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.recordOperation" } } } }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-docs-operation-type": "action" } }, "/communications/calls/{call-id}/microsoft.graph.redirect": { "post": { "tags": [ "communications.Actions" ], "summary": "Invoke action redirect", "operationId": "communications.calls_redirect", "parameters": [ { "name": "call-id", "in": "path", "description": "key: call-id of call", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "call" } ], "requestBody": { "description": "Action parameters", "content": { "application/json": { "schema": { "type": "object", "properties": { "targets": { "type": "array", "items": { "$ref": "#/components/schemas/microsoft.graph.invitationParticipantInfo" } }, "timeout": { "maximum": 2147483647, "minimum": -2147483648, "type": "integer", "format": "int32", "nullable": true }, "callbackUri": { "type": "string", "nullable": true } } } } }, "required": true }, "responses": { "204": { "description": "Success" }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-docs-operation-type": "action" } }, "/communications/calls/{call-id}/microsoft.graph.reject": { "post": { "tags": [ "communications.Actions" ], "summary": "Invoke action reject", "operationId": "communications.calls_reject", "parameters": [ { "name": "call-id", "in": "path", "description": "key: call-id of call", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "call" } ], "requestBody": { "description": "Action parameters", "content": { "application/json": { "schema": { "type": "object", "properties": { "reason": { "$ref": "#/components/schemas/microsoft.graph.rejectReason" }, "callbackUri": { "type": "string", "nullable": true } } } } }, "required": true }, "responses": { "204": { "description": "Success" }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-docs-operation-type": "action" } }, "/communications/calls/{call-id}/microsoft.graph.subscribeToTone": { "post": { "tags": [ "communications.Actions" ], "summary": "Invoke action subscribeToTone", "operationId": "communications.calls_subscribeToTone", "parameters": [ { "name": "call-id", "in": "path", "description": "key: call-id of call", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "call" } ], "requestBody": { "description": "Action parameters", "content": { "application/json": { "schema": { "type": "object", "properties": { "clientContext": { "type": "string", "nullable": true } } } } }, "required": true }, "responses": { "200": { "description": "Success", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.subscribeToToneOperation" } } } }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-docs-operation-type": "action" } }, "/communications/calls/{call-id}/microsoft.graph.transfer": { "post": { "tags": [ "communications.Actions" ], "summary": "Invoke action transfer", "operationId": "communications.calls_transfer", "parameters": [ { "name": "call-id", "in": "path", "description": "key: call-id of call", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "call" } ], "requestBody": { "description": "Action parameters", "content": { "application/json": { "schema": { "type": "object", "properties": { "transferTarget": { "$ref": "#/components/schemas/microsoft.graph.invitationParticipantInfo" } } } } }, "required": true }, "responses": { "204": { "description": "Success" }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-docs-operation-type": "action" } }, "/communications/calls/{call-id}/microsoft.graph.unmute": { "post": { "tags": [ "communications.Actions" ], "summary": "Invoke action unmute", "operationId": "communications.calls_unmute", "parameters": [ { "name": "call-id", "in": "path", "description": "key: call-id of call", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "call" } ], "requestBody": { "description": "Action parameters", "content": { "application/json": { "schema": { "type": "object", "properties": { "clientContext": { "type": "string", "nullable": true } } } } }, "required": true }, "responses": { "200": { "description": "Success", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.unmuteParticipantOperation" } } } }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-docs-operation-type": "action" } }, "/communications/calls/{call-id}/microsoft.graph.updateRecordingStatus": { "post": { "tags": [ "communications.Actions" ], "summary": "Invoke action updateRecordingStatus", "operationId": "communications.calls_updateRecordingStatus", "parameters": [ { "name": "call-id", "in": "path", "description": "key: call-id of call", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "call" } ], "requestBody": { "description": "Action parameters", "content": { "application/json": { "schema": { "type": "object", "properties": { "status": { "$ref": "#/components/schemas/microsoft.graph.recordingStatus" }, "clientContext": { "type": "string", "nullable": true } } } } }, "required": true }, "responses": { "200": { "description": "Success", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.updateRecordingStatusOperation" } } } }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-docs-operation-type": "action" } }, "/communications/calls/{call-id}/operations": { "get": { "tags": [ "communications.call" ], "summary": "Get operations from communications", "operationId": "communications.calls_ListOperations", "parameters": [ { "name": "call-id", "in": "path", "description": "key: call-id of call", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "call" }, { "$ref": "#/components/parameters/top" }, { "$ref": "#/components/parameters/skip" }, { "$ref": "#/components/parameters/search" }, { "$ref": "#/components/parameters/filter" }, { "$ref": "#/components/parameters/count" }, { "name": "$orderby", "in": "query", "description": "Order items by property values", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "id", "id desc", "status", "status desc", "clientContext", "clientContext desc", "resultInfo", "resultInfo desc" ], "type": "string" } } }, { "name": "$select", "in": "query", "description": "Select properties to be returned", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "id", "status", "clientContext", "resultInfo" ], "type": "string" } } }, { "name": "$expand", "in": "query", "description": "Expand related entities", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "*" ], "type": "string" } } } ], "responses": { "200": { "description": "Retrieved navigation property", "content": { "application/json": { "schema": { "title": "Collection of commsOperation", "type": "object", "properties": { "value": { "type": "array", "items": { "$ref": "#/components/schemas/microsoft.graph.commsOperation" } }, "@odata.nextLink": { "type": "string" } } } } } }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-pageable": { "nextLinkName": "@odata.nextLink", "operationName": "listMore" }, "x-ms-docs-operation-type": "operation" }, "post": { "tags": [ "communications.call" ], "summary": "Create new navigation property to operations for communications", "operationId": "communications.calls_CreateOperations", "parameters": [ { "name": "call-id", "in": "path", "description": "key: call-id of call", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "call" } ], "requestBody": { "description": "New navigation property", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.commsOperation" } } }, "required": true }, "responses": { "201": { "description": "Created navigation property.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.commsOperation" } } } }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-docs-operation-type": "operation" } }, "/communications/calls/{call-id}/operations/{commsOperation-id}": { "get": { "tags": [ "communications.call" ], "summary": "Get operations from communications", "operationId": "communications.calls_GetOperations", "parameters": [ { "name": "call-id", "in": "path", "description": "key: call-id of call", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "call" }, { "name": "commsOperation-id", "in": "path", "description": "key: commsOperation-id of commsOperation", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "commsOperation" }, { "name": "$select", "in": "query", "description": "Select properties to be returned", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "id", "status", "clientContext", "resultInfo" ], "type": "string" } } }, { "name": "$expand", "in": "query", "description": "Expand related entities", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "*" ], "type": "string" } } } ], "responses": { "200": { "description": "Retrieved navigation property", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.commsOperation" } } } }, "default": { "$ref": "#/components/responses/error" } } }, "patch": { "tags": [ "communications.call" ], "summary": "Update the navigation property operations in communications", "operationId": "communications.calls_UpdateOperations", "parameters": [ { "name": "call-id", "in": "path", "description": "key: call-id of call", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "call" }, { "name": "commsOperation-id", "in": "path", "description": "key: commsOperation-id of commsOperation", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "commsOperation" } ], "requestBody": { "description": "New navigation property values", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.commsOperation" } } }, "required": true }, "responses": { "204": { "description": "Success" }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-docs-operation-type": "operation" } }, "/communications/calls/{call-id}/participants": { "get": { "tags": [ "communications.call" ], "summary": "Get participants from communications", "operationId": "communications.calls_ListParticipants", "parameters": [ { "name": "call-id", "in": "path", "description": "key: call-id of call", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "call" }, { "$ref": "#/components/parameters/top" }, { "$ref": "#/components/parameters/skip" }, { "$ref": "#/components/parameters/search" }, { "$ref": "#/components/parameters/filter" }, { "$ref": "#/components/parameters/count" }, { "name": "$orderby", "in": "query", "description": "Order items by property values", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "id", "id desc", "info", "info desc", "recordingInfo", "recordingInfo desc", "mediaStreams", "mediaStreams desc", "isMuted", "isMuted desc", "isInLobby", "isInLobby desc" ], "type": "string" } } }, { "name": "$select", "in": "query", "description": "Select properties to be returned", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "id", "info", "recordingInfo", "mediaStreams", "isMuted", "isInLobby" ], "type": "string" } } }, { "name": "$expand", "in": "query", "description": "Expand related entities", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "*" ], "type": "string" } } } ], "responses": { "200": { "description": "Retrieved navigation property", "content": { "application/json": { "schema": { "title": "Collection of participant", "type": "object", "properties": { "value": { "type": "array", "items": { "$ref": "#/components/schemas/microsoft.graph.participant" } }, "@odata.nextLink": { "type": "string" } } } } } }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-pageable": { "nextLinkName": "@odata.nextLink", "operationName": "listMore" }, "x-ms-docs-operation-type": "operation" }, "post": { "tags": [ "communications.call" ], "summary": "Create new navigation property to participants for communications", "operationId": "communications.calls_CreateParticipants", "parameters": [ { "name": "call-id", "in": "path", "description": "key: call-id of call", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "call" } ], "requestBody": { "description": "New navigation property", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.participant" } } }, "required": true }, "responses": { "201": { "description": "Created navigation property.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.participant" } } } }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-docs-operation-type": "operation" } }, "/communications/calls/{call-id}/participants/{participant-id}": { "get": { "tags": [ "communications.call" ], "summary": "Get participants from communications", "operationId": "communications.calls_GetParticipants", "parameters": [ { "name": "call-id", "in": "path", "description": "key: call-id of call", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "call" }, { "name": "participant-id", "in": "path", "description": "key: participant-id of participant", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "participant" }, { "name": "$select", "in": "query", "description": "Select properties to be returned", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "id", "info", "recordingInfo", "mediaStreams", "isMuted", "isInLobby" ], "type": "string" } } }, { "name": "$expand", "in": "query", "description": "Expand related entities", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "*" ], "type": "string" } } } ], "responses": { "200": { "description": "Retrieved navigation property", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.participant" } } } }, "default": { "$ref": "#/components/responses/error" } } }, "patch": { "tags": [ "communications.call" ], "summary": "Update the navigation property participants in communications", "operationId": "communications.calls_UpdateParticipants", "parameters": [ { "name": "call-id", "in": "path", "description": "key: call-id of call", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "call" }, { "name": "participant-id", "in": "path", "description": "key: participant-id of participant", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "participant" } ], "requestBody": { "description": "New navigation property values", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.participant" } } }, "required": true }, "responses": { "204": { "description": "Success" }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-docs-operation-type": "operation" } }, "/communications/calls/{call-id}/participants/{participant-id}/microsoft.graph.mute": { "post": { "tags": [ "communications.Actions" ], "summary": "Invoke action mute", "operationId": "communications.calls.participants_mute", "parameters": [ { "name": "call-id", "in": "path", "description": "key: call-id of call", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "call" }, { "name": "participant-id", "in": "path", "description": "key: participant-id of participant", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "participant" } ], "requestBody": { "description": "Action parameters", "content": { "application/json": { "schema": { "type": "object", "properties": { "clientContext": { "type": "string", "nullable": true } } } } }, "required": true }, "responses": { "200": { "description": "Success", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.muteParticipantOperation" } } } }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-docs-operation-type": "action" } }, "/communications/calls/{call-id}/participants/microsoft.graph.invite": { "post": { "tags": [ "communications.Actions" ], "summary": "Invoke action invite", "operationId": "communications.calls.participants_invite", "parameters": [ { "name": "call-id", "in": "path", "description": "key: call-id of call", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "call" } ], "requestBody": { "description": "Action parameters", "content": { "application/json": { "schema": { "type": "object", "properties": { "participants": { "type": "array", "items": { "$ref": "#/components/schemas/microsoft.graph.invitationParticipantInfo" } }, "clientContext": { "type": "string", "nullable": true } } } } }, "required": true }, "responses": { "200": { "description": "Success", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.inviteParticipantsOperation" } } } }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-docs-operation-type": "action" } }, "/communications/calls/microsoft.graph.logTeleconferenceDeviceQuality": { "post": { "tags": [ "communications.Actions" ], "summary": "Invoke action logTeleconferenceDeviceQuality", "operationId": "communications.calls_logTeleconferenceDeviceQuality", "requestBody": { "description": "Action parameters", "content": { "application/json": { "schema": { "type": "object", "properties": { "quality": { "$ref": "#/components/schemas/microsoft.graph.teleconferenceDeviceQuality" } } } } }, "required": true }, "responses": { "204": { "description": "Success" }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-docs-operation-type": "action" } }, "/communications/onlineMeetings": { "get": { "tags": [ "communications.onlineMeeting" ], "summary": "Get onlineMeetings from communications", "operationId": "communications_ListOnlineMeetings", "parameters": [ { "$ref": "#/components/parameters/top" }, { "$ref": "#/components/parameters/skip" }, { "$ref": "#/components/parameters/search" }, { "$ref": "#/components/parameters/filter" }, { "$ref": "#/components/parameters/count" }, { "name": "$orderby", "in": "query", "description": "Order items by property values", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "id", "id desc", "creationDateTime", "creationDateTime desc", "startDateTime", "startDateTime desc", "endDateTime", "endDateTime desc", "joinWebUrl", "joinWebUrl desc", "subject", "subject desc", "participants", "participants desc", "audioConferencing", "audioConferencing desc", "chatInfo", "chatInfo desc", "videoTeleconferenceId", "videoTeleconferenceId desc" ], "type": "string" } } }, { "name": "$select", "in": "query", "description": "Select properties to be returned", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "id", "creationDateTime", "startDateTime", "endDateTime", "joinWebUrl", "subject", "participants", "audioConferencing", "chatInfo", "videoTeleconferenceId" ], "type": "string" } } }, { "name": "$expand", "in": "query", "description": "Expand related entities", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "*" ], "type": "string" } } } ], "responses": { "200": { "description": "Retrieved navigation property", "content": { "application/json": { "schema": { "title": "Collection of onlineMeeting", "type": "object", "properties": { "value": { "type": "array", "items": { "$ref": "#/components/schemas/microsoft.graph.onlineMeeting" } }, "@odata.nextLink": { "type": "string" } } } } } }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-pageable": { "nextLinkName": "@odata.nextLink", "operationName": "listMore" }, "x-ms-docs-operation-type": "operation" }, "post": { "tags": [ "communications.onlineMeeting" ], "summary": "Create new navigation property to onlineMeetings for communications", "operationId": "communications_CreateOnlineMeetings", "requestBody": { "description": "New navigation property", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.onlineMeeting" } } }, "required": true }, "responses": { "201": { "description": "Created navigation property.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.onlineMeeting" } } } }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-docs-operation-type": "operation" } }, "/communications/onlineMeetings/{onlineMeeting-id}": { "get": { "tags": [ "communications.onlineMeeting" ], "summary": "Get onlineMeetings from communications", "operationId": "communications_GetOnlineMeetings", "parameters": [ { "name": "onlineMeeting-id", "in": "path", "description": "key: onlineMeeting-id of onlineMeeting", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "onlineMeeting" }, { "name": "$select", "in": "query", "description": "Select properties to be returned", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "id", "creationDateTime", "startDateTime", "endDateTime", "joinWebUrl", "subject", "participants", "audioConferencing", "chatInfo", "videoTeleconferenceId" ], "type": "string" } } }, { "name": "$expand", "in": "query", "description": "Expand related entities", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "*" ], "type": "string" } } } ], "responses": { "200": { "description": "Retrieved navigation property", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.onlineMeeting" } } } }, "default": { "$ref": "#/components/responses/error" } } }, "patch": { "tags": [ "communications.onlineMeeting" ], "summary": "Update the navigation property onlineMeetings in communications", "operationId": "communications_UpdateOnlineMeetings", "parameters": [ { "name": "onlineMeeting-id", "in": "path", "description": "key: onlineMeeting-id of onlineMeeting", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "onlineMeeting" } ], "requestBody": { "description": "New navigation property values", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.onlineMeeting" } } }, "required": true }, "responses": { "204": { "description": "Success" }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-docs-operation-type": "operation" } } }, "components": { "schemas": { "microsoft.graph.cloudCommunications": { "allOf": [ { "$ref": "#/components/schemas/microsoft.graph.entity" }, { "title": "cloudCommunications", "type": "object", "properties": { "calls": { "type": "array", "items": { "$ref": "#/components/schemas/microsoft.graph.call" } }, "onlineMeetings": { "type": "array", "items": { "$ref": "#/components/schemas/microsoft.graph.onlineMeeting" } } } } ], "example": { "id": "string (identifier)", "calls": [ { "@odata.type": "microsoft.graph.call" } ], "onlineMeetings": [ { "@odata.type": "microsoft.graph.onlineMeeting" } ] } }, "microsoft.graph.call": { "allOf": [ { "$ref": "#/components/schemas/microsoft.graph.entity" }, { "title": "call", "type": "object", "properties": { "state": { "$ref": "#/components/schemas/microsoft.graph.callState" }, "mediaState": { "$ref": "#/components/schemas/microsoft.graph.callMediaState" }, "resultInfo": { "$ref": "#/components/schemas/microsoft.graph.resultInfo" }, "direction": { "$ref": "#/components/schemas/microsoft.graph.callDirection" }, "subject": { "type": "string", "description": "The subject of the conversation.", "nullable": true }, "callbackUri": { "type": "string", "description": "The callback URL on which callbacks will be delivered. Must be https." }, "callRoutes": { "type": "array", "items": { "$ref": "#/components/schemas/microsoft.graph.callRoute" }, "description": "The routing information on how the call was retargeted. Read-only." }, "source": { "$ref": "#/components/schemas/microsoft.graph.participantInfo" }, "targets": { "type": "array", "items": { "$ref": "#/components/schemas/microsoft.graph.invitationParticipantInfo" }, "description": "The targets of the call. Required information for creating peer to peer call." }, "requestedModalities": { "type": "array", "items": { "$ref": "#/components/schemas/microsoft.graph.modality" }, "description": "The list of requested modalities. Possible values are: unknown, audio, video, videoBasedScreenSharing, data." }, "mediaConfig": { "$ref": "#/components/schemas/microsoft.graph.mediaConfig" }, "chatInfo": { "$ref": "#/components/schemas/microsoft.graph.chatInfo" }, "callOptions": { "$ref": "#/components/schemas/microsoft.graph.callOptions" }, "meetingInfo": { "$ref": "#/components/schemas/microsoft.graph.meetingInfo" }, "tenantId": { "type": "string", "nullable": true }, "myParticipantId": { "type": "string", "description": "Read-only.", "nullable": true }, "toneInfo": { "$ref": "#/components/schemas/microsoft.graph.toneInfo" }, "callChainId": { "type": "string", "description": "A unique identifier for all the participant calls in a conference or a unique identifier for two participant calls in a P2P call. This needs to be copied over from Microsoft.Graph.Call.CallChainId.", "nullable": true }, "incomingContext": { "$ref": "#/components/schemas/microsoft.graph.incomingContext" }, "participants": { "type": "array", "items": { "$ref": "#/components/schemas/microsoft.graph.participant" }, "description": "Read-only. Nullable." }, "operations": { "type": "array", "items": { "$ref": "#/components/schemas/microsoft.graph.commsOperation" }, "description": "Read-only. Nullable." } } } ], "example": { "id": "string (identifier)", "state": { "@odata.type": "microsoft.graph.callState" }, "mediaState": { "@odata.type": "microsoft.graph.callMediaState" }, "resultInfo": { "@odata.type": "microsoft.graph.resultInfo" }, "direction": { "@odata.type": "microsoft.graph.callDirection" }, "subject": "string", "callbackUri": "string", "callRoutes": [ { "@odata.type": "microsoft.graph.callRoute" } ], "source": { "@odata.type": "microsoft.graph.participantInfo" }, "targets": [ { "@odata.type": "microsoft.graph.invitationParticipantInfo" } ], "requestedModalities": [ { "@odata.type": "microsoft.graph.modality" } ], "mediaConfig": { "@odata.type": "microsoft.graph.mediaConfig" }, "chatInfo": { "@odata.type": "microsoft.graph.chatInfo" }, "callOptions": { "@odata.type": "microsoft.graph.callOptions" }, "meetingInfo": { "@odata.type": "microsoft.graph.meetingInfo" }, "tenantId": "string", "myParticipantId": "string", "toneInfo": { "@odata.type": "microsoft.graph.toneInfo" }, "callChainId": "string", "incomingContext": { "@odata.type": "microsoft.graph.incomingContext" }, "participants": [ { "@odata.type": "microsoft.graph.participant" } ], "operations": [ { "@odata.type": "microsoft.graph.commsOperation" } ] } }, "microsoft.graph.mediaConfig": { "title": "mediaConfig", "type": "object" }, "microsoft.graph.modality": { "title": "modality", "enum": [ "audio", "video", "videoBasedScreenSharing", "data", "unknownFutureValue" ], "type": "string" }, "microsoft.graph.screenSharingRole": { "title": "screenSharingRole", "enum": [ "viewer", "sharer" ], "type": "string" }, "microsoft.graph.muteParticipantOperation": { "allOf": [ { "$ref": "#/components/schemas/microsoft.graph.commsOperation" }, { "title": "muteParticipantOperation", "type": "object" } ], "example": { "id": "string (identifier)", "status": { "@odata.type": "microsoft.graph.operationStatus" }, "clientContext": "string", "resultInfo": { "@odata.type": "microsoft.graph.resultInfo" } } }, "microsoft.graph.prompt": { "title": "prompt", "type": "object" }, "microsoft.graph.playPromptOperation": { "allOf": [ { "$ref": "#/components/schemas/microsoft.graph.commsOperation" }, { "title": "playPromptOperation", "type": "object" } ], "example": { "id": "string (identifier)", "status": { "@odata.type": "microsoft.graph.operationStatus" }, "clientContext": "string", "resultInfo": { "@odata.type": "microsoft.graph.resultInfo" } } }, "microsoft.graph.recordOperation": { "allOf": [ { "$ref": "#/components/schemas/microsoft.graph.commsOperation" }, { "title": "recordOperation", "type": "object", "properties": { "recordingLocation": { "type": "string", "description": "The location where the recording is located.", "nullable": true }, "recordingAccessToken": { "type": "string", "description": "The access token required to retrieve the recording.", "nullable": true } } } ], "example": { "id": "string (identifier)", "status": { "@odata.type": "microsoft.graph.operationStatus" }, "clientContext": "string", "resultInfo": { "@odata.type": "microsoft.graph.resultInfo" }, "recordingLocation": "string", "recordingAccessToken": "string" } }, "microsoft.graph.invitationParticipantInfo": { "title": "invitationParticipantInfo", "type": "object", "properties": { "identity": { "$ref": "#/components/schemas/microsoft.graph.identitySet" }, "replacesCallId": { "type": "string", "description": "Optional. The call which the target identity is currently a part of. This call will be dropped once the participant is added.", "nullable": true } }, "example": { "identity": { "@odata.type": "microsoft.graph.identitySet" }, "replacesCallId": "string" } }, "microsoft.graph.rejectReason": { "title": "rejectReason", "enum": [ "none", "busy", "forbidden", "unknownFutureValue" ], "type": "string" }, "microsoft.graph.subscribeToToneOperation": { "allOf": [ { "$ref": "#/components/schemas/microsoft.graph.commsOperation" }, { "title": "subscribeToToneOperation", "type": "object" } ], "example": { "id": "string (identifier)", "status": { "@odata.type": "microsoft.graph.operationStatus" }, "clientContext": "string", "resultInfo": { "@odata.type": "microsoft.graph.resultInfo" } } }, "microsoft.graph.unmuteParticipantOperation": { "allOf": [ { "$ref": "#/components/schemas/microsoft.graph.commsOperation" }, { "title": "unmuteParticipantOperation", "type": "object" } ], "example": { "id": "string (identifier)", "status": { "@odata.type": "microsoft.graph.operationStatus" }, "clientContext": "string", "resultInfo": { "@odata.type": "microsoft.graph.resultInfo" } } }, "microsoft.graph.recordingStatus": { "title": "recordingStatus", "enum": [ "unknown", "notRecording", "recording", "failed", "unknownFutureValue" ], "type": "string" }, "microsoft.graph.updateRecordingStatusOperation": { "allOf": [ { "$ref": "#/components/schemas/microsoft.graph.commsOperation" }, { "title": "updateRecordingStatusOperation", "type": "object" } ], "example": { "id": "string (identifier)", "status": { "@odata.type": "microsoft.graph.operationStatus" }, "clientContext": "string", "resultInfo": { "@odata.type": "microsoft.graph.resultInfo" } } }, "microsoft.graph.commsOperation": { "allOf": [ { "$ref": "#/components/schemas/microsoft.graph.entity" }, { "title": "commsOperation", "type": "object", "properties": { "status": { "$ref": "#/components/schemas/microsoft.graph.operationStatus" }, "clientContext": { "type": "string", "description": "Unique Client Context string. Max limit is 256 chars.", "nullable": true }, "resultInfo": { "$ref": "#/components/schemas/microsoft.graph.resultInfo" } } } ], "example": { "id": "string (identifier)", "status": { "@odata.type": "microsoft.graph.operationStatus" }, "clientContext": "string", "resultInfo": { "@odata.type": "microsoft.graph.resultInfo" } } }, "microsoft.graph.participant": { "allOf": [ { "$ref": "#/components/schemas/microsoft.graph.entity" }, { "title": "participant", "type": "object", "properties": { "info": { "$ref": "#/components/schemas/microsoft.graph.participantInfo" }, "recordingInfo": { "$ref": "#/components/schemas/microsoft.graph.recordingInfo" }, "mediaStreams": { "type": "array", "items": { "$ref": "#/components/schemas/microsoft.graph.mediaStream" }, "description": "The list of media streams." }, "isMuted": { "type": "boolean", "description": "true if the participant is muted (client or server muted)." }, "isInLobby": { "type": "boolean", "description": "true if the participant is in lobby." } } } ], "example": { "id": "string (identifier)", "info": { "@odata.type": "microsoft.graph.participantInfo" }, "recordingInfo": { "@odata.type": "microsoft.graph.recordingInfo" }, "mediaStreams": [ { "@odata.type": "microsoft.graph.mediaStream" } ], "isMuted": true, "isInLobby": true } }, "microsoft.graph.inviteParticipantsOperation": { "allOf": [ { "$ref": "#/components/schemas/microsoft.graph.commsOperation" }, { "title": "inviteParticipantsOperation", "type": "object", "properties": { "participants": { "type": "array", "items": { "$ref": "#/components/schemas/microsoft.graph.invitationParticipantInfo" }, "description": "The participants to invite." } } } ], "example": { "id": "string (identifier)", "status": { "@odata.type": "microsoft.graph.operationStatus" }, "clientContext": "string", "resultInfo": { "@odata.type": "microsoft.graph.resultInfo" }, "participants": [ { "@odata.type": "microsoft.graph.invitationParticipantInfo" } ] } }, "microsoft.graph.teleconferenceDeviceQuality": { "title": "teleconferenceDeviceQuality", "type": "object", "properties": { "callChainId": { "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", "type": "string", "description": "A unique identifier for all the participant calls in a conference or a unique identifier for two participant calls in P2P call. This needs to be copied over from Microsoft.Graph.Call.CallChainId.", "format": "uuid" }, "participantId": { "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", "type": "string", "description": "A unique identifier for a specific participant in a conference. The CVI partner needs to copy over Call.MyParticipantId to this property.", "format": "uuid" }, "mediaLegId": { "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", "type": "string", "description": "A unique identifier for a specific media leg of a participant in a conference. One participant can have multiple media leg identifiers if retargeting happens. CVI partner assigns this value.", "format": "uuid" }, "deviceName": { "type": "string", "description": "The user media agent name, such as Cisco SX80." }, "deviceDescription": { "type": "string", "description": "Any additional description, such as VTC Bldg 30/21." }, "cloudServiceName": { "type": "string", "description": "The Azure deployed cloud service name, such as contoso.cloudapp.net.", "nullable": true }, "cloudServiceInstanceName": { "type": "string", "description": "The Azure deployed cloud service instance name, such as FrontEnd_IN_3.", "nullable": true }, "cloudServiceDeploymentId": { "type": "string", "description": "A unique deployment identifier assigned by Azure.", "nullable": true }, "cloudServiceDeploymentEnvironment": { "type": "string", "description": "A geo-region where the service is deployed, such as ProdNoam.", "nullable": true }, "mediaQualityList": { "type": "array", "items": { "$ref": "#/components/schemas/microsoft.graph.teleconferenceDeviceMediaQuality" }, "description": "The list of media qualities in a media session (call), such as audio quality, video quality, and/or screen sharing quality." } }, "example": { "callChainId": "string", "participantId": "string", "mediaLegId": "string", "deviceName": "string", "deviceDescription": "string", "cloudServiceName": "string", "cloudServiceInstanceName": "string", "cloudServiceDeploymentId": "string", "cloudServiceDeploymentEnvironment": "string", "mediaQualityList": [ { "@odata.type": "microsoft.graph.teleconferenceDeviceMediaQuality" } ] } }, "microsoft.graph.onlineMeeting": { "allOf": [ { "$ref": "#/components/schemas/microsoft.graph.entity" }, { "title": "onlineMeeting", "type": "object", "properties": { "creationDateTime": { "pattern": "^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$", "type": "string", "description": "The meeting creation time in UTC. Read-only.", "format": "date-time", "nullable": true }, "startDateTime": { "pattern": "^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$", "type": "string", "description": "The meeting start time in UTC.", "format": "date-time", "nullable": true }, "endDateTime": { "pattern": "^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$", "type": "string", "description": "The meeting end time in UTC.", "format": "date-time", "nullable": true }, "joinWebUrl": { "type": "string", "description": "The join URL of the online meeting. Read-only.", "nullable": true }, "subject": { "type": "string", "description": "The subject of the online meeting.", "nullable": true }, "participants": { "$ref": "#/components/schemas/microsoft.graph.meetingParticipants" }, "audioConferencing": { "$ref": "#/components/schemas/microsoft.graph.audioConferencing" }, "chatInfo": { "$ref": "#/components/schemas/microsoft.graph.chatInfo" }, "videoTeleconferenceId": { "type": "string", "description": "The video teleconferencing ID. Read-only.", "nullable": true } } } ], "example": { "id": "string (identifier)", "creationDateTime": "string (timestamp)", "startDateTime": "string (timestamp)", "endDateTime": "string (timestamp)", "joinWebUrl": "string", "subject": "string", "participants": { "@odata.type": "microsoft.graph.meetingParticipants" }, "audioConferencing": { "@odata.type": "microsoft.graph.audioConferencing" }, "chatInfo": { "@odata.type": "microsoft.graph.chatInfo" }, "videoTeleconferenceId": "string" } }, "microsoft.graph.entity": { "title": "entity", "type": "object", "properties": { "id": { "type": "string", "description": "Read-only." } }, "example": { "id": "string (identifier)" } }, "microsoft.graph.callState": { "title": "callState", "enum": [ "incoming", "establishing", "established", "hold", "transferring", "transferAccepted", "redirecting", "terminating", "terminated", "unknownFutureValue" ], "type": "string" }, "microsoft.graph.callMediaState": { "title": "callMediaState", "type": "object", "properties": { "audio": { "$ref": "#/components/schemas/microsoft.graph.mediaState" } }, "example": { "audio": { "@odata.type": "microsoft.graph.mediaState" } } }, "microsoft.graph.resultInfo": { "title": "resultInfo", "type": "object", "properties": { "code": { "maximum": 2147483647, "minimum": -2147483648, "type": "integer", "description": "The result code.", "format": "int32" }, "subcode": { "maximum": 2147483647, "minimum": -2147483648, "type": "integer", "description": "The result sub-code.", "format": "int32" }, "message": { "type": "string", "description": "The message.", "nullable": true } }, "example": { "code": "integer", "subcode": "integer", "message": "string" } }, "microsoft.graph.callDirection": { "title": "callDirection", "enum": [ "incoming", "outgoing" ], "type": "string" }, "microsoft.graph.callRoute": { "title": "callRoute", "type": "object", "properties": { "routingType": { "$ref": "#/components/schemas/microsoft.graph.routingType" }, "original": { "$ref": "#/components/schemas/microsoft.graph.identitySet" }, "final": { "$ref": "#/components/schemas/microsoft.graph.identitySet" } }, "example": { "routingType": { "@odata.type": "microsoft.graph.routingType" }, "original": { "@odata.type": "microsoft.graph.identitySet" }, "final": { "@odata.type": "microsoft.graph.identitySet" } } }, "microsoft.graph.participantInfo": { "title": "participantInfo", "type": "object", "properties": { "identity": { "$ref": "#/components/schemas/microsoft.graph.identitySet" }, "endpointType": { "$ref": "#/components/schemas/microsoft.graph.endpointType" }, "region": { "type": "string", "description": "The home region of the participant. This can be a country, a continent, or a larger geographic region. This does not change based on the participant's current physical location. Read-only.", "nullable": true }, "languageId": { "type": "string", "description": "The language culture string. Read-only.", "nullable": true }, "countryCode": { "type": "string", "description": "The ISO 3166-1 Alpha-2 country code of the participant's best estimated physical location at the start of the call. Read-only.", "nullable": true } }, "example": { "identity": { "@odata.type": "microsoft.graph.identitySet" }, "endpointType": { "@odata.type": "microsoft.graph.endpointType" }, "region": "string", "languageId": "string", "countryCode": "string" } }, "microsoft.graph.chatInfo": { "title": "chatInfo", "type": "object", "properties": { "threadId": { "type": "string", "description": "The unique identifier for a thread in Microsoft Teams.", "nullable": true }, "messageId": { "type": "string", "description": "The unique identifier of a message in a Microsoft Teams channel.", "nullable": true }, "replyChainMessageId": { "type": "string", "description": "The ID of the reply message.", "nullable": true } }, "example": { "threadId": "string", "messageId": "string", "replyChainMessageId": "string" } }, "microsoft.graph.callOptions": { "title": "callOptions", "type": "object" }, "microsoft.graph.meetingInfo": { "title": "meetingInfo", "type": "object" }, "microsoft.graph.toneInfo": { "title": "toneInfo", "type": "object", "properties": { "sequenceId": { "type": "integer", "description": "An incremental identifier used for ordering DTMF events.", "format": "int64" }, "tone": { "$ref": "#/components/schemas/microsoft.graph.tone" } }, "example": { "sequenceId": "integer", "tone": { "@odata.type": "microsoft.graph.tone" } } }, "microsoft.graph.incomingContext": { "title": "incomingContext", "type": "object", "properties": { "sourceParticipantId": { "type": "string", "description": "The ID of the participant that triggered the incoming call. Read-only.", "nullable": true }, "observedParticipantId": { "type": "string", "description": "The ID of the participant that is under observation. Read-only.", "nullable": true }, "onBehalfOf": { "$ref": "#/components/schemas/microsoft.graph.identitySet" }, "transferor": { "$ref": "#/components/schemas/microsoft.graph.identitySet" } }, "example": { "sourceParticipantId": "string", "observedParticipantId": "string", "onBehalfOf": { "@odata.type": "microsoft.graph.identitySet" }, "transferor": { "@odata.type": "microsoft.graph.identitySet" } } }, "microsoft.graph.identitySet": { "title": "identitySet", "type": "object", "properties": { "application": { "$ref": "#/components/schemas/microsoft.graph.identity" }, "device": { "$ref": "#/components/schemas/microsoft.graph.identity" }, "user": { "$ref": "#/components/schemas/microsoft.graph.identity" } }, "example": { "application": { "@odata.type": "microsoft.graph.identity" }, "device": { "@odata.type": "microsoft.graph.identity" }, "user": { "@odata.type": "microsoft.graph.identity" } } }, "microsoft.graph.operationStatus": { "title": "operationStatus", "enum": [ "NotStarted", "Running", "Completed", "Failed" ], "type": "string" }, "microsoft.graph.recordingInfo": { "title": "recordingInfo", "type": "object", "properties": { "recordingStatus": { "$ref": "#/components/schemas/microsoft.graph.recordingStatus" }, "initiator": { "$ref": "#/components/schemas/microsoft.graph.identitySet" } }, "example": { "recordingStatus": { "@odata.type": "microsoft.graph.recordingStatus" }, "initiator": { "@odata.type": "microsoft.graph.identitySet" } } }, "microsoft.graph.mediaStream": { "title": "mediaStream", "type": "object", "properties": { "mediaType": { "$ref": "#/components/schemas/microsoft.graph.modality" }, "label": { "type": "string", "description": "The media stream label.", "nullable": true }, "sourceId": { "type": "string", "description": "The source ID." }, "direction": { "$ref": "#/components/schemas/microsoft.graph.mediaDirection" }, "serverMuted": { "type": "boolean", "description": "If the media is muted by the server." } }, "example": { "mediaType": { "@odata.type": "microsoft.graph.modality" }, "label": "string", "sourceId": "string", "direction": { "@odata.type": "microsoft.graph.mediaDirection" }, "serverMuted": true } }, "microsoft.graph.teleconferenceDeviceMediaQuality": { "title": "teleconferenceDeviceMediaQuality", "type": "object", "properties": { "channelIndex": { "maximum": 2147483647, "minimum": -2147483648, "type": "integer", "description": "The channel index of media. Indexing begins with 1. If a media session contains 3 video modalities, channel indexes will be 1, 2, and 3.", "format": "int32" }, "mediaDuration": { "pattern": "^-?P([0-9]+D)?(T([0-9]+H)?([0-9]+M)?([0-9]+([.][0-9]+)?S)?)?$", "type": "string", "description": "The total modality duration. If the media enabled and disabled multiple times, MediaDuration will the summation of all of the durations.", "format": "duration", "nullable": true }, "networkLinkSpeedInBytes": { "type": "integer", "description": "The network link speed in bytes", "format": "int64", "nullable": true }, "localIPAddress": { "type": "string", "description": "the local IP address for the media session.", "nullable": true }, "localPort": { "maximum": 2147483647, "minimum": -2147483648, "type": "integer", "description": "The local media port.", "format": "int32", "nullable": true }, "remoteIPAddress": { "type": "string", "description": "The remote IP address for the media session.", "nullable": true }, "remotePort": { "maximum": 2147483647, "minimum": -2147483648, "type": "integer", "description": "The remote media port.", "format": "int32", "nullable": true }, "inboundPackets": { "type": "integer", "description": "The total number of the inbound packets.", "format": "int64", "nullable": true }, "outboundPackets": { "type": "integer", "description": "The total number of the outbound packets.", "format": "int64", "nullable": true }, "averageInboundPacketLossRateInPercentage": { "type": "number", "description": "The average inbound stream packet loss rate in percentage (0-100). For example, 0.01 means 0.01%.", "format": "double", "nullable": true }, "averageOutboundPacketLossRateInPercentage": { "type": "number", "description": "The average outbound stream packet loss rate in percentage (0-100). For example, 0.01 means 0.01%.", "format": "double", "nullable": true }, "maximumInboundPacketLossRateInPercentage": { "type": "number", "description": "The maximum inbound stream packet loss rate in percentage (0-100). For example, 0.01 means 0.01%.", "format": "double", "nullable": true }, "maximumOutboundPacketLossRateInPercentage": { "type": "number", "description": "The maximum outbound stream packet loss rate in percentage (0-100). For example, 0.01 means 0.01%.", "format": "double", "nullable": true }, "averageInboundRoundTripDelay": { "pattern": "^-?P([0-9]+D)?(T([0-9]+H)?([0-9]+M)?([0-9]+([.][0-9]+)?S)?)?$", "type": "string", "description": "The average inbound stream network round trip delay.", "format": "duration", "nullable": true }, "averageOutboundRoundTripDelay": { "pattern": "^-?P([0-9]+D)?(T([0-9]+H)?([0-9]+M)?([0-9]+([.][0-9]+)?S)?)?$", "type": "string", "description": "The average outbound stream network round trip delay.", "format": "duration", "nullable": true }, "maximumInboundRoundTripDelay": { "pattern": "^-?P([0-9]+D)?(T([0-9]+H)?([0-9]+M)?([0-9]+([.][0-9]+)?S)?)?$", "type": "string", "description": "The maximum inbound stream network round trip delay.", "format": "duration", "nullable": true }, "maximumOutboundRoundTripDelay": { "pattern": "^-?P([0-9]+D)?(T([0-9]+H)?([0-9]+M)?([0-9]+([.][0-9]+)?S)?)?$", "type": "string", "description": "The maximum outbound stream network round trip delay.", "format": "duration", "nullable": true }, "averageInboundJitter": { "pattern": "^-?P([0-9]+D)?(T([0-9]+H)?([0-9]+M)?([0-9]+([.][0-9]+)?S)?)?$", "type": "string", "description": "The average inbound stream network jitter.", "format": "duration", "nullable": true }, "averageOutboundJitter": { "pattern": "^-?P([0-9]+D)?(T([0-9]+H)?([0-9]+M)?([0-9]+([.][0-9]+)?S)?)?$", "type": "string", "description": "The average outbound stream network jitter.", "format": "duration", "nullable": true }, "maximumInboundJitter": { "pattern": "^-?P([0-9]+D)?(T([0-9]+H)?([0-9]+M)?([0-9]+([.][0-9]+)?S)?)?$", "type": "string", "description": "The maximum inbound stream network jitter.", "format": "duration", "nullable": true }, "maximumOutboundJitter": { "pattern": "^-?P([0-9]+D)?(T([0-9]+H)?([0-9]+M)?([0-9]+([.][0-9]+)?S)?)?$", "type": "string", "description": "The maximum outbound stream network jitter.", "format": "duration", "nullable": true } }, "example": { "channelIndex": "integer", "mediaDuration": "string", "networkLinkSpeedInBytes": "integer", "localIPAddress": "string", "localPort": "integer", "remoteIPAddress": "string", "remotePort": "integer", "inboundPackets": "integer", "outboundPackets": "integer", "averageInboundPacketLossRateInPercentage": "double", "averageOutboundPacketLossRateInPercentage": "double", "maximumInboundPacketLossRateInPercentage": "double", "maximumOutboundPacketLossRateInPercentage": "double", "averageInboundRoundTripDelay": "string", "averageOutboundRoundTripDelay": "string", "maximumInboundRoundTripDelay": "string", "maximumOutboundRoundTripDelay": "string", "averageInboundJitter": "string", "averageOutboundJitter": "string", "maximumInboundJitter": "string", "maximumOutboundJitter": "string" } }, "microsoft.graph.meetingParticipants": { "title": "meetingParticipants", "type": "object", "properties": { "organizer": { "$ref": "#/components/schemas/microsoft.graph.meetingParticipantInfo" }, "attendees": { "type": "array", "items": { "$ref": "#/components/schemas/microsoft.graph.meetingParticipantInfo" } } }, "example": { "organizer": { "@odata.type": "microsoft.graph.meetingParticipantInfo" }, "attendees": [ { "@odata.type": "microsoft.graph.meetingParticipantInfo" } ] } }, "microsoft.graph.audioConferencing": { "title": "audioConferencing", "type": "object", "properties": { "conferenceId": { "type": "string", "nullable": true }, "tollNumber": { "type": "string", "description": "The toll number that connects to the Audio Conference Provider.", "nullable": true }, "tollFreeNumber": { "type": "string", "description": "The toll-free number that connects to the Audio Conference Provider.", "nullable": true }, "dialinUrl": { "type": "string", "description": "A URL to the externally-accessible web page that contains dial-in information.", "nullable": true } }, "example": { "conferenceId": "string", "tollNumber": "string", "tollFreeNumber": "string", "dialinUrl": "string" } }, "odata.error": { "required": [ "error" ], "type": "object", "properties": { "error": { "$ref": "#/components/schemas/odata.error.main" } } }, "microsoft.graph.mediaState": { "title": "mediaState", "enum": [ "active", "inactive", "unknownFutureValue" ], "type": "string" }, "microsoft.graph.routingType": { "title": "routingType", "enum": [ "forwarded", "lookup", "selfFork", "unknownFutureValue" ], "type": "string" }, "microsoft.graph.endpointType": { "title": "endpointType", "enum": [ "default", "voicemail", "skypeForBusiness", "skypeForBusinessVoipPhone", "unknownFutureValue" ], "type": "string" }, "microsoft.graph.tone": { "title": "tone", "enum": [ "tone0", "tone1", "tone2", "tone3", "tone4", "tone5", "tone6", "tone7", "tone8", "tone9", "star", "pound", "a", "b", "c", "d", "flash" ], "type": "string" }, "microsoft.graph.identity": { "title": "identity", "type": "object", "properties": { "displayName": { "type": "string", "description": "The identity's display name. Note that this may not always be available or up to date. For example, if a user changes their display name, the API may show the new value in a future response, but the items associated with the user won't show up as having changed when using delta.", "nullable": true }, "id": { "type": "string", "description": "Unique identifier for the identity.", "nullable": true } }, "example": { "displayName": "string", "id": "string" } }, "microsoft.graph.mediaDirection": { "title": "mediaDirection", "enum": [ "inactive", "sendOnly", "receiveOnly", "sendReceive" ], "type": "string" }, "microsoft.graph.meetingParticipantInfo": { "title": "meetingParticipantInfo", "type": "object", "properties": { "identity": { "$ref": "#/components/schemas/microsoft.graph.identitySet" }, "upn": { "type": "string", "description": "User principal name of the participant.", "nullable": true } }, "example": { "identity": { "@odata.type": "microsoft.graph.identitySet" }, "upn": "string" } }, "odata.error.main": { "required": [ "code", "message" ], "type": "object", "properties": { "code": { "type": "string" }, "message": { "type": "string" }, "target": { "type": "string" }, "details": { "type": "array", "items": { "$ref": "#/components/schemas/odata.error.detail" } }, "innererror": { "type": "object", "description": "The structure of this object is service-specific" } } }, "odata.error.detail": { "required": [ "code", "message" ], "type": "object", "properties": { "code": { "type": "string" }, "message": { "type": "string" }, "target": { "type": "string" } } } }, "responses": { "error": { "description": "error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/odata.error" } } } } }, "parameters": { "top": { "name": "$top", "in": "query", "description": "Show only the first n items", "schema": { "minimum": 0, "type": "integer" }, "example": 50 }, "skip": { "name": "$skip", "in": "query", "description": "Skip the first n items", "schema": { "minimum": 0, "type": "integer" } }, "search": { "name": "$search", "in": "query", "description": "Search items by search phrases", "schema": { "type": "string" } }, "filter": { "name": "$filter", "in": "query", "description": "Filter items by property values", "schema": { "type": "string" } }, "count": { "name": "$count", "in": "query", "description": "Include count of items", "schema": { "type": "boolean" } } }, "securitySchemes": { "azureaadv2": { "type": "oauth2", "flows": { "authorizationCode": { "authorizationUrl": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize", "tokenUrl": "https://login.microsoftonline.com/common/oauth2/v2.0/token", "scopes": {} } } } } }, "security": [ { "azureaadv2": [] } ] } ================================================ FILE: test/Microsoft.HttpRepl.Tests/Resources/OpenApiDescriptions/MicrosoftGraph.PowershellSdk.CloudCommunications.yml ================================================ # Originally from https://github.com/microsoftgraph/msgraph-sdk-powershell/blob/dev/openApiDocs/v1.0/CloudCommunications.yml openapi: 3.0.1 info: title: CloudCommunications version: v1.0 servers: - url: https://graph.microsoft.com/v1.0/ description: Core paths: /communications: get: tags: - communications.cloudCommunications summary: Get communications operationId: communications.cloudCommunications_GetCloudCommunications parameters: - name: $select in: query description: Select properties to be returned style: form explode: false schema: uniqueItems: true type: array items: enum: - id - calls - onlineMeetings type: string - name: $expand in: query description: Expand related entities style: form explode: false schema: uniqueItems: true type: array items: enum: - '*' - calls - onlineMeetings type: string responses: '200': description: Retrieved entity content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.cloudCommunications' links: calls: operationId: communications.GetCalls parameters: call-id: $response.body#/id onlineMeetings: operationId: communications.GetOnlineMeetings parameters: onlineMeeting-id: $response.body#/id default: $ref: '#/components/responses/error' x-ms-docs-operation-type: operation patch: tags: - communications.cloudCommunications summary: Update communications operationId: communications.cloudCommunications_UpdateCloudCommunications requestBody: description: New property values content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.cloudCommunications' required: true responses: '204': description: Success default: $ref: '#/components/responses/error' x-ms-docs-operation-type: operation /communications/calls: get: tags: - communications.call summary: Get calls from communications operationId: communications_ListCalls parameters: - $ref: '#/components/parameters/top' - $ref: '#/components/parameters/skip' - $ref: '#/components/parameters/search' - $ref: '#/components/parameters/filter' - $ref: '#/components/parameters/count' - name: $orderby in: query description: Order items by property values style: form explode: false schema: uniqueItems: true type: array items: enum: - id - id desc - state - state desc - mediaState - mediaState desc - resultInfo - resultInfo desc - direction - direction desc - subject - subject desc - callbackUri - callbackUri desc - callRoutes - callRoutes desc - source - source desc - targets - targets desc - requestedModalities - requestedModalities desc - mediaConfig - mediaConfig desc - chatInfo - chatInfo desc - callOptions - callOptions desc - meetingInfo - meetingInfo desc - tenantId - tenantId desc - myParticipantId - myParticipantId desc - toneInfo - toneInfo desc - callChainId - callChainId desc - incomingContext - incomingContext desc type: string - name: $select in: query description: Select properties to be returned style: form explode: false schema: uniqueItems: true type: array items: enum: - id - state - mediaState - resultInfo - direction - subject - callbackUri - callRoutes - source - targets - requestedModalities - mediaConfig - chatInfo - callOptions - meetingInfo - tenantId - myParticipantId - toneInfo - callChainId - incomingContext - participants - operations type: string - name: $expand in: query description: Expand related entities style: form explode: false schema: uniqueItems: true type: array items: enum: - '*' - participants - operations type: string responses: '200': description: Retrieved navigation property content: application/json: schema: title: Collection of call type: object properties: value: type: array items: $ref: '#/components/schemas/microsoft.graph.call' '@odata.nextLink': type: string default: $ref: '#/components/responses/error' x-ms-pageable: nextLinkName: '@odata.nextLink' operationName: listMore x-ms-docs-operation-type: operation post: tags: - communications.call summary: Create new navigation property to calls for communications operationId: communications_CreateCalls requestBody: description: New navigation property content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.call' required: true responses: '201': description: Created navigation property. content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.call' default: $ref: '#/components/responses/error' x-ms-docs-operation-type: operation '/communications/calls/{call-id}': get: tags: - communications.call summary: Get calls from communications operationId: communications_GetCalls parameters: - name: call-id in: path description: 'key: call-id of call' required: true schema: type: string x-ms-docs-key-type: call - name: $select in: query description: Select properties to be returned style: form explode: false schema: uniqueItems: true type: array items: enum: - id - state - mediaState - resultInfo - direction - subject - callbackUri - callRoutes - source - targets - requestedModalities - mediaConfig - chatInfo - callOptions - meetingInfo - tenantId - myParticipantId - toneInfo - callChainId - incomingContext - participants - operations type: string - name: $expand in: query description: Expand related entities style: form explode: false schema: uniqueItems: true type: array items: enum: - '*' - participants - operations type: string responses: '200': description: Retrieved navigation property content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.call' links: participants: operationId: cloudCommunications.calls.GetParticipants parameters: call-id: $request.path.call-id participant-id: $response.body#/id operations: operationId: cloudCommunications.calls.GetOperations parameters: call-id: $request.path.call-id commsOperation-id: $response.body#/id default: $ref: '#/components/responses/error' patch: tags: - communications.call summary: Update the navigation property calls in communications operationId: communications_UpdateCalls parameters: - name: call-id in: path description: 'key: call-id of call' required: true schema: type: string x-ms-docs-key-type: call requestBody: description: New navigation property values content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.call' required: true responses: '204': description: Success default: $ref: '#/components/responses/error' x-ms-docs-operation-type: operation '/communications/calls/{call-id}/microsoft.graph.answer': post: tags: - communications.Actions summary: Invoke action answer operationId: communications.calls_answer parameters: - name: call-id in: path description: 'key: call-id of call' required: true schema: type: string x-ms-docs-key-type: call requestBody: description: Action parameters content: application/json: schema: type: object properties: callbackUri: type: string mediaConfig: $ref: '#/components/schemas/microsoft.graph.mediaConfig' acceptedModalities: type: array items: $ref: '#/components/schemas/microsoft.graph.modality' required: true responses: '204': description: Success default: $ref: '#/components/responses/error' x-ms-docs-operation-type: action '/communications/calls/{call-id}/microsoft.graph.changeScreenSharingRole': post: tags: - communications.Actions summary: Invoke action changeScreenSharingRole operationId: communications.calls_changeScreenSharingRole parameters: - name: call-id in: path description: 'key: call-id of call' required: true schema: type: string x-ms-docs-key-type: call requestBody: description: Action parameters content: application/json: schema: type: object properties: role: $ref: '#/components/schemas/microsoft.graph.screenSharingRole' required: true responses: '204': description: Success default: $ref: '#/components/responses/error' x-ms-docs-operation-type: action '/communications/calls/{call-id}/microsoft.graph.keepAlive': post: tags: - communications.Actions summary: Invoke action keepAlive operationId: communications.calls_keepAlive parameters: - name: call-id in: path description: 'key: call-id of call' required: true schema: type: string x-ms-docs-key-type: call responses: '204': description: Success default: $ref: '#/components/responses/error' x-ms-docs-operation-type: action '/communications/calls/{call-id}/microsoft.graph.mute': post: tags: - communications.Actions summary: Invoke action mute operationId: communications.calls_mute parameters: - name: call-id in: path description: 'key: call-id of call' required: true schema: type: string x-ms-docs-key-type: call requestBody: description: Action parameters content: application/json: schema: type: object properties: clientContext: type: string nullable: true required: true responses: '200': description: Success content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.muteParticipantOperation' default: $ref: '#/components/responses/error' x-ms-docs-operation-type: action '/communications/calls/{call-id}/microsoft.graph.playPrompt': post: tags: - communications.Actions summary: Invoke action playPrompt operationId: communications.calls_playPrompt parameters: - name: call-id in: path description: 'key: call-id of call' required: true schema: type: string x-ms-docs-key-type: call requestBody: description: Action parameters content: application/json: schema: type: object properties: prompts: type: array items: $ref: '#/components/schemas/microsoft.graph.prompt' clientContext: type: string nullable: true required: true responses: '200': description: Success content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.playPromptOperation' default: $ref: '#/components/responses/error' x-ms-docs-operation-type: action '/communications/calls/{call-id}/microsoft.graph.recordResponse': post: tags: - communications.Actions summary: Invoke action recordResponse operationId: communications.calls_recordResponse parameters: - name: call-id in: path description: 'key: call-id of call' required: true schema: type: string x-ms-docs-key-type: call requestBody: description: Action parameters content: application/json: schema: type: object properties: prompts: type: array items: $ref: '#/components/schemas/microsoft.graph.prompt' bargeInAllowed: type: boolean default: false nullable: true initialSilenceTimeoutInSeconds: maximum: 2147483647 minimum: -2147483648 type: integer format: int32 nullable: true maxSilenceTimeoutInSeconds: maximum: 2147483647 minimum: -2147483648 type: integer format: int32 nullable: true maxRecordDurationInSeconds: maximum: 2147483647 minimum: -2147483648 type: integer format: int32 nullable: true playBeep: type: boolean default: false nullable: true stopTones: type: array items: type: string nullable: true clientContext: type: string nullable: true required: true responses: '200': description: Success content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.recordOperation' default: $ref: '#/components/responses/error' x-ms-docs-operation-type: action '/communications/calls/{call-id}/microsoft.graph.redirect': post: tags: - communications.Actions summary: Invoke action redirect operationId: communications.calls_redirect parameters: - name: call-id in: path description: 'key: call-id of call' required: true schema: type: string x-ms-docs-key-type: call requestBody: description: Action parameters content: application/json: schema: type: object properties: targets: type: array items: $ref: '#/components/schemas/microsoft.graph.invitationParticipantInfo' timeout: maximum: 2147483647 minimum: -2147483648 type: integer format: int32 nullable: true callbackUri: type: string nullable: true required: true responses: '204': description: Success default: $ref: '#/components/responses/error' x-ms-docs-operation-type: action '/communications/calls/{call-id}/microsoft.graph.reject': post: tags: - communications.Actions summary: Invoke action reject operationId: communications.calls_reject parameters: - name: call-id in: path description: 'key: call-id of call' required: true schema: type: string x-ms-docs-key-type: call requestBody: description: Action parameters content: application/json: schema: type: object properties: reason: $ref: '#/components/schemas/microsoft.graph.rejectReason' callbackUri: type: string nullable: true required: true responses: '204': description: Success default: $ref: '#/components/responses/error' x-ms-docs-operation-type: action '/communications/calls/{call-id}/microsoft.graph.subscribeToTone': post: tags: - communications.Actions summary: Invoke action subscribeToTone operationId: communications.calls_subscribeToTone parameters: - name: call-id in: path description: 'key: call-id of call' required: true schema: type: string x-ms-docs-key-type: call requestBody: description: Action parameters content: application/json: schema: type: object properties: clientContext: type: string nullable: true required: true responses: '200': description: Success content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.subscribeToToneOperation' default: $ref: '#/components/responses/error' x-ms-docs-operation-type: action '/communications/calls/{call-id}/microsoft.graph.transfer': post: tags: - communications.Actions summary: Invoke action transfer operationId: communications.calls_transfer parameters: - name: call-id in: path description: 'key: call-id of call' required: true schema: type: string x-ms-docs-key-type: call requestBody: description: Action parameters content: application/json: schema: type: object properties: transferTarget: $ref: '#/components/schemas/microsoft.graph.invitationParticipantInfo' required: true responses: '204': description: Success default: $ref: '#/components/responses/error' x-ms-docs-operation-type: action '/communications/calls/{call-id}/microsoft.graph.unmute': post: tags: - communications.Actions summary: Invoke action unmute operationId: communications.calls_unmute parameters: - name: call-id in: path description: 'key: call-id of call' required: true schema: type: string x-ms-docs-key-type: call requestBody: description: Action parameters content: application/json: schema: type: object properties: clientContext: type: string nullable: true required: true responses: '200': description: Success content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.unmuteParticipantOperation' default: $ref: '#/components/responses/error' x-ms-docs-operation-type: action '/communications/calls/{call-id}/microsoft.graph.updateRecordingStatus': post: tags: - communications.Actions summary: Invoke action updateRecordingStatus operationId: communications.calls_updateRecordingStatus parameters: - name: call-id in: path description: 'key: call-id of call' required: true schema: type: string x-ms-docs-key-type: call requestBody: description: Action parameters content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/microsoft.graph.recordingStatus' clientContext: type: string nullable: true required: true responses: '200': description: Success content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.updateRecordingStatusOperation' default: $ref: '#/components/responses/error' x-ms-docs-operation-type: action '/communications/calls/{call-id}/operations': get: tags: - communications.call summary: Get operations from communications operationId: communications.calls_ListOperations parameters: - name: call-id in: path description: 'key: call-id of call' required: true schema: type: string x-ms-docs-key-type: call - $ref: '#/components/parameters/top' - $ref: '#/components/parameters/skip' - $ref: '#/components/parameters/search' - $ref: '#/components/parameters/filter' - $ref: '#/components/parameters/count' - name: $orderby in: query description: Order items by property values style: form explode: false schema: uniqueItems: true type: array items: enum: - id - id desc - status - status desc - clientContext - clientContext desc - resultInfo - resultInfo desc type: string - name: $select in: query description: Select properties to be returned style: form explode: false schema: uniqueItems: true type: array items: enum: - id - status - clientContext - resultInfo type: string - name: $expand in: query description: Expand related entities style: form explode: false schema: uniqueItems: true type: array items: enum: - '*' type: string responses: '200': description: Retrieved navigation property content: application/json: schema: title: Collection of commsOperation type: object properties: value: type: array items: $ref: '#/components/schemas/microsoft.graph.commsOperation' '@odata.nextLink': type: string default: $ref: '#/components/responses/error' x-ms-pageable: nextLinkName: '@odata.nextLink' operationName: listMore x-ms-docs-operation-type: operation post: tags: - communications.call summary: Create new navigation property to operations for communications operationId: communications.calls_CreateOperations parameters: - name: call-id in: path description: 'key: call-id of call' required: true schema: type: string x-ms-docs-key-type: call requestBody: description: New navigation property content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.commsOperation' required: true responses: '201': description: Created navigation property. content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.commsOperation' default: $ref: '#/components/responses/error' x-ms-docs-operation-type: operation '/communications/calls/{call-id}/operations/{commsOperation-id}': get: tags: - communications.call summary: Get operations from communications operationId: communications.calls_GetOperations parameters: - name: call-id in: path description: 'key: call-id of call' required: true schema: type: string x-ms-docs-key-type: call - name: commsOperation-id in: path description: 'key: commsOperation-id of commsOperation' required: true schema: type: string x-ms-docs-key-type: commsOperation - name: $select in: query description: Select properties to be returned style: form explode: false schema: uniqueItems: true type: array items: enum: - id - status - clientContext - resultInfo type: string - name: $expand in: query description: Expand related entities style: form explode: false schema: uniqueItems: true type: array items: enum: - '*' type: string responses: '200': description: Retrieved navigation property content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.commsOperation' default: $ref: '#/components/responses/error' patch: tags: - communications.call summary: Update the navigation property operations in communications operationId: communications.calls_UpdateOperations parameters: - name: call-id in: path description: 'key: call-id of call' required: true schema: type: string x-ms-docs-key-type: call - name: commsOperation-id in: path description: 'key: commsOperation-id of commsOperation' required: true schema: type: string x-ms-docs-key-type: commsOperation requestBody: description: New navigation property values content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.commsOperation' required: true responses: '204': description: Success default: $ref: '#/components/responses/error' x-ms-docs-operation-type: operation '/communications/calls/{call-id}/participants': get: tags: - communications.call summary: Get participants from communications operationId: communications.calls_ListParticipants parameters: - name: call-id in: path description: 'key: call-id of call' required: true schema: type: string x-ms-docs-key-type: call - $ref: '#/components/parameters/top' - $ref: '#/components/parameters/skip' - $ref: '#/components/parameters/search' - $ref: '#/components/parameters/filter' - $ref: '#/components/parameters/count' - name: $orderby in: query description: Order items by property values style: form explode: false schema: uniqueItems: true type: array items: enum: - id - id desc - info - info desc - recordingInfo - recordingInfo desc - mediaStreams - mediaStreams desc - isMuted - isMuted desc - isInLobby - isInLobby desc type: string - name: $select in: query description: Select properties to be returned style: form explode: false schema: uniqueItems: true type: array items: enum: - id - info - recordingInfo - mediaStreams - isMuted - isInLobby type: string - name: $expand in: query description: Expand related entities style: form explode: false schema: uniqueItems: true type: array items: enum: - '*' type: string responses: '200': description: Retrieved navigation property content: application/json: schema: title: Collection of participant type: object properties: value: type: array items: $ref: '#/components/schemas/microsoft.graph.participant' '@odata.nextLink': type: string default: $ref: '#/components/responses/error' x-ms-pageable: nextLinkName: '@odata.nextLink' operationName: listMore x-ms-docs-operation-type: operation post: tags: - communications.call summary: Create new navigation property to participants for communications operationId: communications.calls_CreateParticipants parameters: - name: call-id in: path description: 'key: call-id of call' required: true schema: type: string x-ms-docs-key-type: call requestBody: description: New navigation property content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.participant' required: true responses: '201': description: Created navigation property. content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.participant' default: $ref: '#/components/responses/error' x-ms-docs-operation-type: operation '/communications/calls/{call-id}/participants/{participant-id}': get: tags: - communications.call summary: Get participants from communications operationId: communications.calls_GetParticipants parameters: - name: call-id in: path description: 'key: call-id of call' required: true schema: type: string x-ms-docs-key-type: call - name: participant-id in: path description: 'key: participant-id of participant' required: true schema: type: string x-ms-docs-key-type: participant - name: $select in: query description: Select properties to be returned style: form explode: false schema: uniqueItems: true type: array items: enum: - id - info - recordingInfo - mediaStreams - isMuted - isInLobby type: string - name: $expand in: query description: Expand related entities style: form explode: false schema: uniqueItems: true type: array items: enum: - '*' type: string responses: '200': description: Retrieved navigation property content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.participant' default: $ref: '#/components/responses/error' patch: tags: - communications.call summary: Update the navigation property participants in communications operationId: communications.calls_UpdateParticipants parameters: - name: call-id in: path description: 'key: call-id of call' required: true schema: type: string x-ms-docs-key-type: call - name: participant-id in: path description: 'key: participant-id of participant' required: true schema: type: string x-ms-docs-key-type: participant requestBody: description: New navigation property values content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.participant' required: true responses: '204': description: Success default: $ref: '#/components/responses/error' x-ms-docs-operation-type: operation '/communications/calls/{call-id}/participants/{participant-id}/microsoft.graph.mute': post: tags: - communications.Actions summary: Invoke action mute operationId: communications.calls.participants_mute parameters: - name: call-id in: path description: 'key: call-id of call' required: true schema: type: string x-ms-docs-key-type: call - name: participant-id in: path description: 'key: participant-id of participant' required: true schema: type: string x-ms-docs-key-type: participant requestBody: description: Action parameters content: application/json: schema: type: object properties: clientContext: type: string nullable: true required: true responses: '200': description: Success content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.muteParticipantOperation' default: $ref: '#/components/responses/error' x-ms-docs-operation-type: action '/communications/calls/{call-id}/participants/microsoft.graph.invite': post: tags: - communications.Actions summary: Invoke action invite operationId: communications.calls.participants_invite parameters: - name: call-id in: path description: 'key: call-id of call' required: true schema: type: string x-ms-docs-key-type: call requestBody: description: Action parameters content: application/json: schema: type: object properties: participants: type: array items: $ref: '#/components/schemas/microsoft.graph.invitationParticipantInfo' clientContext: type: string nullable: true required: true responses: '200': description: Success content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.inviteParticipantsOperation' default: $ref: '#/components/responses/error' x-ms-docs-operation-type: action /communications/calls/microsoft.graph.logTeleconferenceDeviceQuality: post: tags: - communications.Actions summary: Invoke action logTeleconferenceDeviceQuality operationId: communications.calls_logTeleconferenceDeviceQuality requestBody: description: Action parameters content: application/json: schema: type: object properties: quality: $ref: '#/components/schemas/microsoft.graph.teleconferenceDeviceQuality' required: true responses: '204': description: Success default: $ref: '#/components/responses/error' x-ms-docs-operation-type: action /communications/onlineMeetings: get: tags: - communications.onlineMeeting summary: Get onlineMeetings from communications operationId: communications_ListOnlineMeetings parameters: - $ref: '#/components/parameters/top' - $ref: '#/components/parameters/skip' - $ref: '#/components/parameters/search' - $ref: '#/components/parameters/filter' - $ref: '#/components/parameters/count' - name: $orderby in: query description: Order items by property values style: form explode: false schema: uniqueItems: true type: array items: enum: - id - id desc - creationDateTime - creationDateTime desc - startDateTime - startDateTime desc - endDateTime - endDateTime desc - joinWebUrl - joinWebUrl desc - subject - subject desc - participants - participants desc - audioConferencing - audioConferencing desc - chatInfo - chatInfo desc - videoTeleconferenceId - videoTeleconferenceId desc type: string - name: $select in: query description: Select properties to be returned style: form explode: false schema: uniqueItems: true type: array items: enum: - id - creationDateTime - startDateTime - endDateTime - joinWebUrl - subject - participants - audioConferencing - chatInfo - videoTeleconferenceId type: string - name: $expand in: query description: Expand related entities style: form explode: false schema: uniqueItems: true type: array items: enum: - '*' type: string responses: '200': description: Retrieved navigation property content: application/json: schema: title: Collection of onlineMeeting type: object properties: value: type: array items: $ref: '#/components/schemas/microsoft.graph.onlineMeeting' '@odata.nextLink': type: string default: $ref: '#/components/responses/error' x-ms-pageable: nextLinkName: '@odata.nextLink' operationName: listMore x-ms-docs-operation-type: operation post: tags: - communications.onlineMeeting summary: Create new navigation property to onlineMeetings for communications operationId: communications_CreateOnlineMeetings requestBody: description: New navigation property content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.onlineMeeting' required: true responses: '201': description: Created navigation property. content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.onlineMeeting' default: $ref: '#/components/responses/error' x-ms-docs-operation-type: operation '/communications/onlineMeetings/{onlineMeeting-id}': get: tags: - communications.onlineMeeting summary: Get onlineMeetings from communications operationId: communications_GetOnlineMeetings parameters: - name: onlineMeeting-id in: path description: 'key: onlineMeeting-id of onlineMeeting' required: true schema: type: string x-ms-docs-key-type: onlineMeeting - name: $select in: query description: Select properties to be returned style: form explode: false schema: uniqueItems: true type: array items: enum: - id - creationDateTime - startDateTime - endDateTime - joinWebUrl - subject - participants - audioConferencing - chatInfo - videoTeleconferenceId type: string - name: $expand in: query description: Expand related entities style: form explode: false schema: uniqueItems: true type: array items: enum: - '*' type: string responses: '200': description: Retrieved navigation property content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.onlineMeeting' default: $ref: '#/components/responses/error' patch: tags: - communications.onlineMeeting summary: Update the navigation property onlineMeetings in communications operationId: communications_UpdateOnlineMeetings parameters: - name: onlineMeeting-id in: path description: 'key: onlineMeeting-id of onlineMeeting' required: true schema: type: string x-ms-docs-key-type: onlineMeeting requestBody: description: New navigation property values content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.onlineMeeting' required: true responses: '204': description: Success default: $ref: '#/components/responses/error' x-ms-docs-operation-type: operation components: schemas: microsoft.graph.cloudCommunications: allOf: - $ref: '#/components/schemas/microsoft.graph.entity' - title: cloudCommunications type: object properties: calls: type: array items: $ref: '#/components/schemas/microsoft.graph.call' onlineMeetings: type: array items: $ref: '#/components/schemas/microsoft.graph.onlineMeeting' example: id: string (identifier) calls: - '@odata.type': microsoft.graph.call onlineMeetings: - '@odata.type': microsoft.graph.onlineMeeting microsoft.graph.call: allOf: - $ref: '#/components/schemas/microsoft.graph.entity' - title: call type: object properties: state: $ref: '#/components/schemas/microsoft.graph.callState' mediaState: $ref: '#/components/schemas/microsoft.graph.callMediaState' resultInfo: $ref: '#/components/schemas/microsoft.graph.resultInfo' direction: $ref: '#/components/schemas/microsoft.graph.callDirection' subject: type: string description: The subject of the conversation. nullable: true callbackUri: type: string description: The callback URL on which callbacks will be delivered. Must be https. callRoutes: type: array items: $ref: '#/components/schemas/microsoft.graph.callRoute' description: The routing information on how the call was retargeted. Read-only. source: $ref: '#/components/schemas/microsoft.graph.participantInfo' targets: type: array items: $ref: '#/components/schemas/microsoft.graph.invitationParticipantInfo' description: The targets of the call. Required information for creating peer to peer call. requestedModalities: type: array items: $ref: '#/components/schemas/microsoft.graph.modality' description: 'The list of requested modalities. Possible values are: unknown, audio, video, videoBasedScreenSharing, data.' mediaConfig: $ref: '#/components/schemas/microsoft.graph.mediaConfig' chatInfo: $ref: '#/components/schemas/microsoft.graph.chatInfo' callOptions: $ref: '#/components/schemas/microsoft.graph.callOptions' meetingInfo: $ref: '#/components/schemas/microsoft.graph.meetingInfo' tenantId: type: string nullable: true myParticipantId: type: string description: Read-only. nullable: true toneInfo: $ref: '#/components/schemas/microsoft.graph.toneInfo' callChainId: type: string description: A unique identifier for all the participant calls in a conference or a unique identifier for two participant calls in a P2P call. This needs to be copied over from Microsoft.Graph.Call.CallChainId. nullable: true incomingContext: $ref: '#/components/schemas/microsoft.graph.incomingContext' participants: type: array items: $ref: '#/components/schemas/microsoft.graph.participant' description: Read-only. Nullable. operations: type: array items: $ref: '#/components/schemas/microsoft.graph.commsOperation' description: Read-only. Nullable. example: id: string (identifier) state: '@odata.type': microsoft.graph.callState mediaState: '@odata.type': microsoft.graph.callMediaState resultInfo: '@odata.type': microsoft.graph.resultInfo direction: '@odata.type': microsoft.graph.callDirection subject: string callbackUri: string callRoutes: - '@odata.type': microsoft.graph.callRoute source: '@odata.type': microsoft.graph.participantInfo targets: - '@odata.type': microsoft.graph.invitationParticipantInfo requestedModalities: - '@odata.type': microsoft.graph.modality mediaConfig: '@odata.type': microsoft.graph.mediaConfig chatInfo: '@odata.type': microsoft.graph.chatInfo callOptions: '@odata.type': microsoft.graph.callOptions meetingInfo: '@odata.type': microsoft.graph.meetingInfo tenantId: string myParticipantId: string toneInfo: '@odata.type': microsoft.graph.toneInfo callChainId: string incomingContext: '@odata.type': microsoft.graph.incomingContext participants: - '@odata.type': microsoft.graph.participant operations: - '@odata.type': microsoft.graph.commsOperation microsoft.graph.mediaConfig: title: mediaConfig type: object microsoft.graph.modality: title: modality enum: - audio - video - videoBasedScreenSharing - data - unknownFutureValue type: string microsoft.graph.screenSharingRole: title: screenSharingRole enum: - viewer - sharer type: string microsoft.graph.muteParticipantOperation: allOf: - $ref: '#/components/schemas/microsoft.graph.commsOperation' - title: muteParticipantOperation type: object example: id: string (identifier) status: '@odata.type': microsoft.graph.operationStatus clientContext: string resultInfo: '@odata.type': microsoft.graph.resultInfo microsoft.graph.prompt: title: prompt type: object microsoft.graph.playPromptOperation: allOf: - $ref: '#/components/schemas/microsoft.graph.commsOperation' - title: playPromptOperation type: object example: id: string (identifier) status: '@odata.type': microsoft.graph.operationStatus clientContext: string resultInfo: '@odata.type': microsoft.graph.resultInfo microsoft.graph.recordOperation: allOf: - $ref: '#/components/schemas/microsoft.graph.commsOperation' - title: recordOperation type: object properties: recordingLocation: type: string description: The location where the recording is located. nullable: true recordingAccessToken: type: string description: The access token required to retrieve the recording. nullable: true example: id: string (identifier) status: '@odata.type': microsoft.graph.operationStatus clientContext: string resultInfo: '@odata.type': microsoft.graph.resultInfo recordingLocation: string recordingAccessToken: string microsoft.graph.invitationParticipantInfo: title: invitationParticipantInfo type: object properties: identity: $ref: '#/components/schemas/microsoft.graph.identitySet' replacesCallId: type: string description: Optional. The call which the target identity is currently a part of. This call will be dropped once the participant is added. nullable: true example: identity: '@odata.type': microsoft.graph.identitySet replacesCallId: string microsoft.graph.rejectReason: title: rejectReason enum: - none - busy - forbidden - unknownFutureValue type: string microsoft.graph.subscribeToToneOperation: allOf: - $ref: '#/components/schemas/microsoft.graph.commsOperation' - title: subscribeToToneOperation type: object example: id: string (identifier) status: '@odata.type': microsoft.graph.operationStatus clientContext: string resultInfo: '@odata.type': microsoft.graph.resultInfo microsoft.graph.unmuteParticipantOperation: allOf: - $ref: '#/components/schemas/microsoft.graph.commsOperation' - title: unmuteParticipantOperation type: object example: id: string (identifier) status: '@odata.type': microsoft.graph.operationStatus clientContext: string resultInfo: '@odata.type': microsoft.graph.resultInfo microsoft.graph.recordingStatus: title: recordingStatus enum: - unknown - notRecording - recording - failed - unknownFutureValue type: string microsoft.graph.updateRecordingStatusOperation: allOf: - $ref: '#/components/schemas/microsoft.graph.commsOperation' - title: updateRecordingStatusOperation type: object example: id: string (identifier) status: '@odata.type': microsoft.graph.operationStatus clientContext: string resultInfo: '@odata.type': microsoft.graph.resultInfo microsoft.graph.commsOperation: allOf: - $ref: '#/components/schemas/microsoft.graph.entity' - title: commsOperation type: object properties: status: $ref: '#/components/schemas/microsoft.graph.operationStatus' clientContext: type: string description: Unique Client Context string. Max limit is 256 chars. nullable: true resultInfo: $ref: '#/components/schemas/microsoft.graph.resultInfo' example: id: string (identifier) status: '@odata.type': microsoft.graph.operationStatus clientContext: string resultInfo: '@odata.type': microsoft.graph.resultInfo microsoft.graph.participant: allOf: - $ref: '#/components/schemas/microsoft.graph.entity' - title: participant type: object properties: info: $ref: '#/components/schemas/microsoft.graph.participantInfo' recordingInfo: $ref: '#/components/schemas/microsoft.graph.recordingInfo' mediaStreams: type: array items: $ref: '#/components/schemas/microsoft.graph.mediaStream' description: The list of media streams. isMuted: type: boolean description: true if the participant is muted (client or server muted). isInLobby: type: boolean description: true if the participant is in lobby. example: id: string (identifier) info: '@odata.type': microsoft.graph.participantInfo recordingInfo: '@odata.type': microsoft.graph.recordingInfo mediaStreams: - '@odata.type': microsoft.graph.mediaStream isMuted: true isInLobby: true microsoft.graph.inviteParticipantsOperation: allOf: - $ref: '#/components/schemas/microsoft.graph.commsOperation' - title: inviteParticipantsOperation type: object properties: participants: type: array items: $ref: '#/components/schemas/microsoft.graph.invitationParticipantInfo' description: The participants to invite. example: id: string (identifier) status: '@odata.type': microsoft.graph.operationStatus clientContext: string resultInfo: '@odata.type': microsoft.graph.resultInfo participants: - '@odata.type': microsoft.graph.invitationParticipantInfo microsoft.graph.teleconferenceDeviceQuality: title: teleconferenceDeviceQuality type: object properties: callChainId: pattern: '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$' type: string description: A unique identifier for all the participant calls in a conference or a unique identifier for two participant calls in P2P call. This needs to be copied over from Microsoft.Graph.Call.CallChainId. format: uuid participantId: pattern: '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$' type: string description: A unique identifier for a specific participant in a conference. The CVI partner needs to copy over Call.MyParticipantId to this property. format: uuid mediaLegId: pattern: '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$' type: string description: A unique identifier for a specific media leg of a participant in a conference. One participant can have multiple media leg identifiers if retargeting happens. CVI partner assigns this value. format: uuid deviceName: type: string description: 'The user media agent name, such as Cisco SX80.' deviceDescription: type: string description: 'Any additional description, such as VTC Bldg 30/21.' cloudServiceName: type: string description: 'The Azure deployed cloud service name, such as contoso.cloudapp.net.' nullable: true cloudServiceInstanceName: type: string description: 'The Azure deployed cloud service instance name, such as FrontEnd_IN_3.' nullable: true cloudServiceDeploymentId: type: string description: A unique deployment identifier assigned by Azure. nullable: true cloudServiceDeploymentEnvironment: type: string description: 'A geo-region where the service is deployed, such as ProdNoam.' nullable: true mediaQualityList: type: array items: $ref: '#/components/schemas/microsoft.graph.teleconferenceDeviceMediaQuality' description: 'The list of media qualities in a media session (call), such as audio quality, video quality, and/or screen sharing quality.' example: callChainId: string participantId: string mediaLegId: string deviceName: string deviceDescription: string cloudServiceName: string cloudServiceInstanceName: string cloudServiceDeploymentId: string cloudServiceDeploymentEnvironment: string mediaQualityList: - '@odata.type': microsoft.graph.teleconferenceDeviceMediaQuality microsoft.graph.onlineMeeting: allOf: - $ref: '#/components/schemas/microsoft.graph.entity' - title: onlineMeeting type: object properties: creationDateTime: pattern: '^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$' type: string description: The meeting creation time in UTC. Read-only. format: date-time nullable: true startDateTime: pattern: '^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$' type: string description: The meeting start time in UTC. format: date-time nullable: true endDateTime: pattern: '^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$' type: string description: The meeting end time in UTC. format: date-time nullable: true joinWebUrl: type: string description: The join URL of the online meeting. Read-only. nullable: true subject: type: string description: The subject of the online meeting. nullable: true participants: $ref: '#/components/schemas/microsoft.graph.meetingParticipants' audioConferencing: $ref: '#/components/schemas/microsoft.graph.audioConferencing' chatInfo: $ref: '#/components/schemas/microsoft.graph.chatInfo' videoTeleconferenceId: type: string description: The video teleconferencing ID. Read-only. nullable: true example: id: string (identifier) creationDateTime: string (timestamp) startDateTime: string (timestamp) endDateTime: string (timestamp) joinWebUrl: string subject: string participants: '@odata.type': microsoft.graph.meetingParticipants audioConferencing: '@odata.type': microsoft.graph.audioConferencing chatInfo: '@odata.type': microsoft.graph.chatInfo videoTeleconferenceId: string microsoft.graph.entity: title: entity type: object properties: id: type: string description: Read-only. example: id: string (identifier) microsoft.graph.callState: title: callState enum: - incoming - establishing - established - hold - transferring - transferAccepted - redirecting - terminating - terminated - unknownFutureValue type: string microsoft.graph.callMediaState: title: callMediaState type: object properties: audio: $ref: '#/components/schemas/microsoft.graph.mediaState' example: audio: '@odata.type': microsoft.graph.mediaState microsoft.graph.resultInfo: title: resultInfo type: object properties: code: maximum: 2147483647 minimum: -2147483648 type: integer description: The result code. format: int32 subcode: maximum: 2147483647 minimum: -2147483648 type: integer description: The result sub-code. format: int32 message: type: string description: The message. nullable: true example: code: integer subcode: integer message: string microsoft.graph.callDirection: title: callDirection enum: - incoming - outgoing type: string microsoft.graph.callRoute: title: callRoute type: object properties: routingType: $ref: '#/components/schemas/microsoft.graph.routingType' original: $ref: '#/components/schemas/microsoft.graph.identitySet' final: $ref: '#/components/schemas/microsoft.graph.identitySet' example: routingType: '@odata.type': microsoft.graph.routingType original: '@odata.type': microsoft.graph.identitySet final: '@odata.type': microsoft.graph.identitySet microsoft.graph.participantInfo: title: participantInfo type: object properties: identity: $ref: '#/components/schemas/microsoft.graph.identitySet' endpointType: $ref: '#/components/schemas/microsoft.graph.endpointType' region: type: string description: 'The home region of the participant. This can be a country, a continent, or a larger geographic region. This does not change based on the participant''s current physical location. Read-only.' nullable: true languageId: type: string description: The language culture string. Read-only. nullable: true countryCode: type: string description: The ISO 3166-1 Alpha-2 country code of the participant's best estimated physical location at the start of the call. Read-only. nullable: true example: identity: '@odata.type': microsoft.graph.identitySet endpointType: '@odata.type': microsoft.graph.endpointType region: string languageId: string countryCode: string microsoft.graph.chatInfo: title: chatInfo type: object properties: threadId: type: string description: The unique identifier for a thread in Microsoft Teams. nullable: true messageId: type: string description: The unique identifier of a message in a Microsoft Teams channel. nullable: true replyChainMessageId: type: string description: The ID of the reply message. nullable: true example: threadId: string messageId: string replyChainMessageId: string microsoft.graph.callOptions: title: callOptions type: object microsoft.graph.meetingInfo: title: meetingInfo type: object microsoft.graph.toneInfo: title: toneInfo type: object properties: sequenceId: type: integer description: An incremental identifier used for ordering DTMF events. format: int64 tone: $ref: '#/components/schemas/microsoft.graph.tone' example: sequenceId: integer tone: '@odata.type': microsoft.graph.tone microsoft.graph.incomingContext: title: incomingContext type: object properties: sourceParticipantId: type: string description: The ID of the participant that triggered the incoming call. Read-only. nullable: true observedParticipantId: type: string description: The ID of the participant that is under observation. Read-only. nullable: true onBehalfOf: $ref: '#/components/schemas/microsoft.graph.identitySet' transferor: $ref: '#/components/schemas/microsoft.graph.identitySet' example: sourceParticipantId: string observedParticipantId: string onBehalfOf: '@odata.type': microsoft.graph.identitySet transferor: '@odata.type': microsoft.graph.identitySet microsoft.graph.identitySet: title: identitySet type: object properties: application: $ref: '#/components/schemas/microsoft.graph.identity' device: $ref: '#/components/schemas/microsoft.graph.identity' user: $ref: '#/components/schemas/microsoft.graph.identity' example: application: '@odata.type': microsoft.graph.identity device: '@odata.type': microsoft.graph.identity user: '@odata.type': microsoft.graph.identity microsoft.graph.operationStatus: title: operationStatus enum: - NotStarted - Running - Completed - Failed type: string microsoft.graph.recordingInfo: title: recordingInfo type: object properties: recordingStatus: $ref: '#/components/schemas/microsoft.graph.recordingStatus' initiator: $ref: '#/components/schemas/microsoft.graph.identitySet' example: recordingStatus: '@odata.type': microsoft.graph.recordingStatus initiator: '@odata.type': microsoft.graph.identitySet microsoft.graph.mediaStream: title: mediaStream type: object properties: mediaType: $ref: '#/components/schemas/microsoft.graph.modality' label: type: string description: The media stream label. nullable: true sourceId: type: string description: The source ID. direction: $ref: '#/components/schemas/microsoft.graph.mediaDirection' serverMuted: type: boolean description: If the media is muted by the server. example: mediaType: '@odata.type': microsoft.graph.modality label: string sourceId: string direction: '@odata.type': microsoft.graph.mediaDirection serverMuted: true microsoft.graph.teleconferenceDeviceMediaQuality: title: teleconferenceDeviceMediaQuality type: object properties: channelIndex: maximum: 2147483647 minimum: -2147483648 type: integer description: 'The channel index of media. Indexing begins with 1. If a media session contains 3 video modalities, channel indexes will be 1, 2, and 3.' format: int32 mediaDuration: pattern: '^-?P([0-9]+D)?(T([0-9]+H)?([0-9]+M)?([0-9]+([.][0-9]+)?S)?)?$' type: string description: 'The total modality duration. If the media enabled and disabled multiple times, MediaDuration will the summation of all of the durations.' format: duration nullable: true networkLinkSpeedInBytes: type: integer description: The network link speed in bytes format: int64 nullable: true localIPAddress: type: string description: the local IP address for the media session. nullable: true localPort: maximum: 2147483647 minimum: -2147483648 type: integer description: The local media port. format: int32 nullable: true remoteIPAddress: type: string description: The remote IP address for the media session. nullable: true remotePort: maximum: 2147483647 minimum: -2147483648 type: integer description: The remote media port. format: int32 nullable: true inboundPackets: type: integer description: The total number of the inbound packets. format: int64 nullable: true outboundPackets: type: integer description: The total number of the outbound packets. format: int64 nullable: true averageInboundPacketLossRateInPercentage: type: number description: 'The average inbound stream packet loss rate in percentage (0-100). For example, 0.01 means 0.01%.' format: double nullable: true averageOutboundPacketLossRateInPercentage: type: number description: 'The average outbound stream packet loss rate in percentage (0-100). For example, 0.01 means 0.01%.' format: double nullable: true maximumInboundPacketLossRateInPercentage: type: number description: 'The maximum inbound stream packet loss rate in percentage (0-100). For example, 0.01 means 0.01%.' format: double nullable: true maximumOutboundPacketLossRateInPercentage: type: number description: 'The maximum outbound stream packet loss rate in percentage (0-100). For example, 0.01 means 0.01%.' format: double nullable: true averageInboundRoundTripDelay: pattern: '^-?P([0-9]+D)?(T([0-9]+H)?([0-9]+M)?([0-9]+([.][0-9]+)?S)?)?$' type: string description: The average inbound stream network round trip delay. format: duration nullable: true averageOutboundRoundTripDelay: pattern: '^-?P([0-9]+D)?(T([0-9]+H)?([0-9]+M)?([0-9]+([.][0-9]+)?S)?)?$' type: string description: The average outbound stream network round trip delay. format: duration nullable: true maximumInboundRoundTripDelay: pattern: '^-?P([0-9]+D)?(T([0-9]+H)?([0-9]+M)?([0-9]+([.][0-9]+)?S)?)?$' type: string description: The maximum inbound stream network round trip delay. format: duration nullable: true maximumOutboundRoundTripDelay: pattern: '^-?P([0-9]+D)?(T([0-9]+H)?([0-9]+M)?([0-9]+([.][0-9]+)?S)?)?$' type: string description: The maximum outbound stream network round trip delay. format: duration nullable: true averageInboundJitter: pattern: '^-?P([0-9]+D)?(T([0-9]+H)?([0-9]+M)?([0-9]+([.][0-9]+)?S)?)?$' type: string description: The average inbound stream network jitter. format: duration nullable: true averageOutboundJitter: pattern: '^-?P([0-9]+D)?(T([0-9]+H)?([0-9]+M)?([0-9]+([.][0-9]+)?S)?)?$' type: string description: The average outbound stream network jitter. format: duration nullable: true maximumInboundJitter: pattern: '^-?P([0-9]+D)?(T([0-9]+H)?([0-9]+M)?([0-9]+([.][0-9]+)?S)?)?$' type: string description: The maximum inbound stream network jitter. format: duration nullable: true maximumOutboundJitter: pattern: '^-?P([0-9]+D)?(T([0-9]+H)?([0-9]+M)?([0-9]+([.][0-9]+)?S)?)?$' type: string description: The maximum outbound stream network jitter. format: duration nullable: true example: channelIndex: integer mediaDuration: string networkLinkSpeedInBytes: integer localIPAddress: string localPort: integer remoteIPAddress: string remotePort: integer inboundPackets: integer outboundPackets: integer averageInboundPacketLossRateInPercentage: double averageOutboundPacketLossRateInPercentage: double maximumInboundPacketLossRateInPercentage: double maximumOutboundPacketLossRateInPercentage: double averageInboundRoundTripDelay: string averageOutboundRoundTripDelay: string maximumInboundRoundTripDelay: string maximumOutboundRoundTripDelay: string averageInboundJitter: string averageOutboundJitter: string maximumInboundJitter: string maximumOutboundJitter: string microsoft.graph.meetingParticipants: title: meetingParticipants type: object properties: organizer: $ref: '#/components/schemas/microsoft.graph.meetingParticipantInfo' attendees: type: array items: $ref: '#/components/schemas/microsoft.graph.meetingParticipantInfo' example: organizer: '@odata.type': microsoft.graph.meetingParticipantInfo attendees: - '@odata.type': microsoft.graph.meetingParticipantInfo microsoft.graph.audioConferencing: title: audioConferencing type: object properties: conferenceId: type: string nullable: true tollNumber: type: string description: The toll number that connects to the Audio Conference Provider. nullable: true tollFreeNumber: type: string description: The toll-free number that connects to the Audio Conference Provider. nullable: true dialinUrl: type: string description: A URL to the externally-accessible web page that contains dial-in information. nullable: true example: conferenceId: string tollNumber: string tollFreeNumber: string dialinUrl: string odata.error: required: - error type: object properties: error: $ref: '#/components/schemas/odata.error.main' microsoft.graph.mediaState: title: mediaState enum: - active - inactive - unknownFutureValue type: string microsoft.graph.routingType: title: routingType enum: - forwarded - lookup - selfFork - unknownFutureValue type: string microsoft.graph.endpointType: title: endpointType enum: - default - voicemail - skypeForBusiness - skypeForBusinessVoipPhone - unknownFutureValue type: string microsoft.graph.tone: title: tone enum: - tone0 - tone1 - tone2 - tone3 - tone4 - tone5 - tone6 - tone7 - tone8 - tone9 - star - pound - a - b - c - d - flash type: string microsoft.graph.identity: title: identity type: object properties: displayName: type: string description: 'The identity''s display name. Note that this may not always be available or up to date. For example, if a user changes their display name, the API may show the new value in a future response, but the items associated with the user won''t show up as having changed when using delta.' nullable: true id: type: string description: Unique identifier for the identity. nullable: true example: displayName: string id: string microsoft.graph.mediaDirection: title: mediaDirection enum: - inactive - sendOnly - receiveOnly - sendReceive type: string microsoft.graph.meetingParticipantInfo: title: meetingParticipantInfo type: object properties: identity: $ref: '#/components/schemas/microsoft.graph.identitySet' upn: type: string description: User principal name of the participant. nullable: true example: identity: '@odata.type': microsoft.graph.identitySet upn: string odata.error.main: required: - code - message type: object properties: code: type: string message: type: string target: type: string details: type: array items: $ref: '#/components/schemas/odata.error.detail' innererror: type: object description: The structure of this object is service-specific odata.error.detail: required: - code - message type: object properties: code: type: string message: type: string target: type: string responses: error: description: error content: application/json: schema: $ref: '#/components/schemas/odata.error' parameters: top: name: $top in: query description: Show only the first n items schema: minimum: 0 type: integer example: 50 skip: name: $skip in: query description: Skip the first n items schema: minimum: 0 type: integer search: name: $search in: query description: Search items by search phrases schema: type: string filter: name: $filter in: query description: Filter items by property values schema: type: string count: name: $count in: query description: Include count of items schema: type: boolean securitySchemes: azureaadv2: type: oauth2 flows: authorizationCode: authorizationUrl: https://login.microsoftonline.com/common/oauth2/v2.0/authorize tokenUrl: https://login.microsoftonline.com/common/oauth2/v2.0/token scopes: { } security: - azureaadv2: [ ] ================================================ FILE: test/Microsoft.HttpRepl.Tests/Resources/OpenApiDescriptions/MicrosoftGraph.PowershellSdk.Subscriptions.json ================================================ { "openapi": "3.0.1", "info": { "title": "Subscriptions", "version": "v1.0" }, "servers": [ { "url": "https://graph.microsoft.com/v1.0/", "description": "Core" } ], "paths": { "/subscriptions": { "get": { "tags": [ "subscriptions.subscription" ], "summary": "Get entities from subscriptions", "operationId": "subscriptions.subscription_ListSubscription", "parameters": [ { "$ref": "#/components/parameters/top" }, { "$ref": "#/components/parameters/skip" }, { "$ref": "#/components/parameters/search" }, { "$ref": "#/components/parameters/filter" }, { "$ref": "#/components/parameters/count" }, { "name": "$orderby", "in": "query", "description": "Order items by property values", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "id", "id desc", "resource", "resource desc", "changeType", "changeType desc", "clientState", "clientState desc", "notificationUrl", "notificationUrl desc", "expirationDateTime", "expirationDateTime desc", "applicationId", "applicationId desc", "creatorId", "creatorId desc", "latestSupportedTlsVersion", "latestSupportedTlsVersion desc" ], "type": "string" } } }, { "name": "$select", "in": "query", "description": "Select properties to be returned", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "id", "resource", "changeType", "clientState", "notificationUrl", "expirationDateTime", "applicationId", "creatorId", "latestSupportedTlsVersion" ], "type": "string" } } }, { "name": "$expand", "in": "query", "description": "Expand related entities", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "*" ], "type": "string" } } } ], "responses": { "200": { "description": "Retrieved entities", "content": { "application/json": { "schema": { "title": "Collection of subscription", "type": "object", "properties": { "value": { "type": "array", "items": { "$ref": "#/components/schemas/microsoft.graph.subscription" } }, "@odata.nextLink": { "type": "string" } } } } } }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-pageable": { "nextLinkName": "@odata.nextLink", "operationName": "listMore" }, "x-ms-docs-operation-type": "operation" }, "post": { "tags": [ "subscriptions.subscription" ], "summary": "Add new entity to subscriptions", "operationId": "subscriptions.subscription_CreateSubscription", "requestBody": { "description": "New entity", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.subscription" } } }, "required": true }, "responses": { "201": { "description": "Created entity", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.subscription" } } } }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-docs-operation-type": "operation" } }, "/subscriptions/{subscription-id}": { "get": { "tags": [ "subscriptions.subscription" ], "summary": "Get entity from subscriptions by key", "operationId": "subscriptions.subscription_GetSubscription", "parameters": [ { "name": "subscription-id", "in": "path", "description": "key: subscription-id of subscription", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "subscription" }, { "name": "$select", "in": "query", "description": "Select properties to be returned", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "id", "resource", "changeType", "clientState", "notificationUrl", "expirationDateTime", "applicationId", "creatorId", "latestSupportedTlsVersion" ], "type": "string" } } }, { "name": "$expand", "in": "query", "description": "Expand related entities", "style": "form", "explode": false, "schema": { "uniqueItems": true, "type": "array", "items": { "enum": [ "*" ], "type": "string" } } } ], "responses": { "200": { "description": "Retrieved entity", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.subscription" } } } }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-docs-operation-type": "operation" }, "patch": { "tags": [ "subscriptions.subscription" ], "summary": "Update entity in subscriptions", "operationId": "subscriptions.subscription_UpdateSubscription", "parameters": [ { "name": "subscription-id", "in": "path", "description": "key: subscription-id of subscription", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "subscription" } ], "requestBody": { "description": "New property values", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/microsoft.graph.subscription" } } }, "required": true }, "responses": { "204": { "description": "Success" }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-docs-operation-type": "operation" }, "delete": { "tags": [ "subscriptions.subscription" ], "summary": "Delete entity from subscriptions", "operationId": "subscriptions.subscription_DeleteSubscription", "parameters": [ { "name": "subscription-id", "in": "path", "description": "key: subscription-id of subscription", "required": true, "schema": { "type": "string" }, "x-ms-docs-key-type": "subscription" }, { "name": "If-Match", "in": "header", "description": "ETag", "schema": { "type": "string" } } ], "responses": { "204": { "description": "Success" }, "default": { "$ref": "#/components/responses/error" } }, "x-ms-docs-operation-type": "operation" } } }, "components": { "schemas": { "microsoft.graph.subscription": { "allOf": [ { "$ref": "#/components/schemas/microsoft.graph.entity" }, { "title": "subscription", "type": "object", "properties": { "resource": { "type": "string", "description": "Required. Specifies the resource that will be monitored for changes. Do not include the base URL (https://graph.microsoft.com/v1.0/). See the possible resource path values for each supported resource." }, "changeType": { "type": "string", "description": "Required. Indicates the type of change in the subscribed resource that will raise a change notification. The supported values are: created, updated, deleted. Multiple values can be combined using a comma-separated list.Note: Drive root item and list change notifications support only the updated changeType. User and group change notifications support updated and deleted changeType." }, "clientState": { "type": "string", "description": "Optional. Specifies the value of the clientState property sent by the service in each change notification. The maximum length is 128 characters. The client can check that the change notification came from the service by comparing the value of the clientState property sent with the subscription with the value of the clientState property received with each change notification.", "nullable": true }, "notificationUrl": { "type": "string", "description": "Required. The URL of the endpoint that will receive the change notifications. This URL must make use of the HTTPS protocol." }, "expirationDateTime": { "pattern": "^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$", "type": "string", "description": "Required. Specifies the date and time when the webhook subscription expires. The time is in UTC, and can be an amount of time from subscription creation that varies for the resource subscribed to. See the table below for maximum supported subscription length of time.", "format": "date-time" }, "applicationId": { "type": "string", "description": "Identifier of the application used to create the subscription. Read-only.", "nullable": true }, "creatorId": { "type": "string", "description": "Identifier of the user or service principal that created the subscription. If the app used delegated permissions to create the subscription, this field contains the id of the signed-in user the app called on behalf of. If the app used application permissions, this field contains the id of the service principal corresponding to the app. Read-only.", "nullable": true }, "latestSupportedTlsVersion": { "type": "string", "description": "Specifies the latest version of Transport Layer Security (TLS) that the notification endpoint, specified by notificationUrl, supports. The possible values are: v1_0, v1_1, v1_2, v1_3. For subscribers whose notification endpoint supports a version lower than the currently recommended version (TLS 1.2), specifying this property by a set timeline allows them to temporarily use their deprecated version of TLS before completing their upgrade to TLS 1.2. For these subscribers, not setting this property per the timeline would result in subscription operations failing. For subscribers whose notification endpoint already supports TLS 1.2, setting this property is optional. In such cases, Microsoft Graph defaults the property to v1_2.", "nullable": true } } } ], "example": { "id": "string (identifier)", "resource": "string", "changeType": "string", "clientState": "string", "notificationUrl": "string", "expirationDateTime": "string (timestamp)", "applicationId": "string", "creatorId": "string", "latestSupportedTlsVersion": "string" } }, "microsoft.graph.entity": { "title": "entity", "type": "object", "properties": { "id": { "type": "string", "description": "Read-only." } }, "example": { "id": "string (identifier)" } }, "odata.error": { "required": [ "error" ], "type": "object", "properties": { "error": { "$ref": "#/components/schemas/odata.error.main" } } }, "odata.error.main": { "required": [ "code", "message" ], "type": "object", "properties": { "code": { "type": "string" }, "message": { "type": "string" }, "target": { "type": "string" }, "details": { "type": "array", "items": { "$ref": "#/components/schemas/odata.error.detail" } }, "innererror": { "type": "object", "description": "The structure of this object is service-specific" } } }, "odata.error.detail": { "required": [ "code", "message" ], "type": "object", "properties": { "code": { "type": "string" }, "message": { "type": "string" }, "target": { "type": "string" } } } }, "responses": { "error": { "description": "error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/odata.error" } } } } }, "parameters": { "top": { "name": "$top", "in": "query", "description": "Show only the first n items", "schema": { "minimum": 0, "type": "integer" }, "example": 50 }, "skip": { "name": "$skip", "in": "query", "description": "Skip the first n items", "schema": { "minimum": 0, "type": "integer" } }, "search": { "name": "$search", "in": "query", "description": "Search items by search phrases", "schema": { "type": "string" } }, "filter": { "name": "$filter", "in": "query", "description": "Filter items by property values", "schema": { "type": "string" } }, "count": { "name": "$count", "in": "query", "description": "Include count of items", "schema": { "type": "boolean" } } }, "securitySchemes": { "azureaadv2": { "type": "oauth2", "flows": { "authorizationCode": { "authorizationUrl": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize", "tokenUrl": "https://login.microsoftonline.com/common/oauth2/v2.0/token", "scopes": {} } } } } }, "security": [ { "azureaadv2": [] } ] } ================================================ FILE: test/Microsoft.HttpRepl.Tests/Resources/OpenApiDescriptions/MicrosoftGraph.PowershellSdk.Subscriptions.yml ================================================ # Originally from https://github.com/microsoftgraph/msgraph-sdk-powershell/tree/dev/openApiDocs/v1.0 openapi: 3.0.1 info: title: Subscriptions version: v1.0 servers: - url: https://graph.microsoft.com/v1.0/ description: Core paths: /subscriptions: get: tags: - subscriptions.subscription summary: Get entities from subscriptions operationId: subscriptions.subscription_ListSubscription parameters: - $ref: '#/components/parameters/top' - $ref: '#/components/parameters/skip' - $ref: '#/components/parameters/search' - $ref: '#/components/parameters/filter' - $ref: '#/components/parameters/count' - name: $orderby in: query description: Order items by property values style: form explode: false schema: uniqueItems: true type: array items: enum: - id - id desc - resource - resource desc - changeType - changeType desc - clientState - clientState desc - notificationUrl - notificationUrl desc - expirationDateTime - expirationDateTime desc - applicationId - applicationId desc - creatorId - creatorId desc - latestSupportedTlsVersion - latestSupportedTlsVersion desc type: string - name: $select in: query description: Select properties to be returned style: form explode: false schema: uniqueItems: true type: array items: enum: - id - resource - changeType - clientState - notificationUrl - expirationDateTime - applicationId - creatorId - latestSupportedTlsVersion type: string - name: $expand in: query description: Expand related entities style: form explode: false schema: uniqueItems: true type: array items: enum: - '*' type: string responses: '200': description: Retrieved entities content: application/json: schema: title: Collection of subscription type: object properties: value: type: array items: $ref: '#/components/schemas/microsoft.graph.subscription' '@odata.nextLink': type: string default: $ref: '#/components/responses/error' x-ms-pageable: nextLinkName: '@odata.nextLink' operationName: listMore x-ms-docs-operation-type: operation post: tags: - subscriptions.subscription summary: Add new entity to subscriptions operationId: subscriptions.subscription_CreateSubscription requestBody: description: New entity content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.subscription' required: true responses: '201': description: Created entity content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.subscription' default: $ref: '#/components/responses/error' x-ms-docs-operation-type: operation '/subscriptions/{subscription-id}': get: tags: - subscriptions.subscription summary: Get entity from subscriptions by key operationId: subscriptions.subscription_GetSubscription parameters: - name: subscription-id in: path description: 'key: subscription-id of subscription' required: true schema: type: string x-ms-docs-key-type: subscription - name: $select in: query description: Select properties to be returned style: form explode: false schema: uniqueItems: true type: array items: enum: - id - resource - changeType - clientState - notificationUrl - expirationDateTime - applicationId - creatorId - latestSupportedTlsVersion type: string - name: $expand in: query description: Expand related entities style: form explode: false schema: uniqueItems: true type: array items: enum: - '*' type: string responses: '200': description: Retrieved entity content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.subscription' default: $ref: '#/components/responses/error' x-ms-docs-operation-type: operation patch: tags: - subscriptions.subscription summary: Update entity in subscriptions operationId: subscriptions.subscription_UpdateSubscription parameters: - name: subscription-id in: path description: 'key: subscription-id of subscription' required: true schema: type: string x-ms-docs-key-type: subscription requestBody: description: New property values content: application/json: schema: $ref: '#/components/schemas/microsoft.graph.subscription' required: true responses: '204': description: Success default: $ref: '#/components/responses/error' x-ms-docs-operation-type: operation delete: tags: - subscriptions.subscription summary: Delete entity from subscriptions operationId: subscriptions.subscription_DeleteSubscription parameters: - name: subscription-id in: path description: 'key: subscription-id of subscription' required: true schema: type: string x-ms-docs-key-type: subscription - name: If-Match in: header description: ETag schema: type: string responses: '204': description: Success default: $ref: '#/components/responses/error' x-ms-docs-operation-type: operation components: schemas: microsoft.graph.subscription: allOf: - $ref: '#/components/schemas/microsoft.graph.entity' - title: subscription type: object properties: resource: type: string description: Required. Specifies the resource that will be monitored for changes. Do not include the base URL (https://graph.microsoft.com/v1.0/). See the possible resource path values for each supported resource. changeType: type: string description: 'Required. Indicates the type of change in the subscribed resource that will raise a change notification. The supported values are: created, updated, deleted. Multiple values can be combined using a comma-separated list.Note: Drive root item and list change notifications support only the updated changeType. User and group change notifications support updated and deleted changeType.' clientState: type: string description: Optional. Specifies the value of the clientState property sent by the service in each change notification. The maximum length is 128 characters. The client can check that the change notification came from the service by comparing the value of the clientState property sent with the subscription with the value of the clientState property received with each change notification. nullable: true notificationUrl: type: string description: Required. The URL of the endpoint that will receive the change notifications. This URL must make use of the HTTPS protocol. expirationDateTime: pattern: '^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$' type: string description: 'Required. Specifies the date and time when the webhook subscription expires. The time is in UTC, and can be an amount of time from subscription creation that varies for the resource subscribed to. See the table below for maximum supported subscription length of time.' format: date-time applicationId: type: string description: Identifier of the application used to create the subscription. Read-only. nullable: true creatorId: type: string description: 'Identifier of the user or service principal that created the subscription. If the app used delegated permissions to create the subscription, this field contains the id of the signed-in user the app called on behalf of. If the app used application permissions, this field contains the id of the service principal corresponding to the app. Read-only.' nullable: true latestSupportedTlsVersion: type: string description: 'Specifies the latest version of Transport Layer Security (TLS) that the notification endpoint, specified by notificationUrl, supports. The possible values are: v1_0, v1_1, v1_2, v1_3. For subscribers whose notification endpoint supports a version lower than the currently recommended version (TLS 1.2), specifying this property by a set timeline allows them to temporarily use their deprecated version of TLS before completing their upgrade to TLS 1.2. For these subscribers, not setting this property per the timeline would result in subscription operations failing. For subscribers whose notification endpoint already supports TLS 1.2, setting this property is optional. In such cases, Microsoft Graph defaults the property to v1_2.' nullable: true example: id: string (identifier) resource: string changeType: string clientState: string notificationUrl: string expirationDateTime: string (timestamp) applicationId: string creatorId: string latestSupportedTlsVersion: string microsoft.graph.entity: title: entity type: object properties: id: type: string description: Read-only. example: id: string (identifier) odata.error: required: - error type: object properties: error: $ref: '#/components/schemas/odata.error.main' odata.error.main: required: - code - message type: object properties: code: type: string message: type: string target: type: string details: type: array items: $ref: '#/components/schemas/odata.error.detail' innererror: type: object description: The structure of this object is service-specific odata.error.detail: required: - code - message type: object properties: code: type: string message: type: string target: type: string responses: error: description: error content: application/json: schema: $ref: '#/components/schemas/odata.error' parameters: top: name: $top in: query description: Show only the first n items schema: minimum: 0 type: integer example: 50 skip: name: $skip in: query description: Skip the first n items schema: minimum: 0 type: integer search: name: $search in: query description: Search items by search phrases schema: type: string filter: name: $filter in: query description: Filter items by property values schema: type: string count: name: $count in: query description: Include count of items schema: type: boolean securitySchemes: azureaadv2: type: oauth2 flows: authorizationCode: authorizationUrl: https://login.microsoftonline.com/common/oauth2/v2.0/authorize tokenUrl: https://login.microsoftonline.com/common/oauth2/v2.0/token scopes: { } security: - azureaadv2: [ ] ================================================ FILE: test/Microsoft.HttpRepl.Tests/Resources/OpenApiDescriptions/xkcd.json ================================================ { "openapi": "3.0.0", "servers": [ { "url": "http://xkcd.com/" } ], "info": { "description": "Webcomic of romance, sarcasm, math, and language.", "title": "XKCD", "version": "1.0.0", "x-apisguru-categories": [ "media" ], "x-logo": { "url": "https://api.apis.guru/v2/cache/logo/http_imgs.xkcd.com_static_terrible_small_logo.png" }, "x-origin": [ { "format": "openapi", "url": "https://raw.githubusercontent.com/APIs-guru/unofficial_openapi_specs/master/xkcd.com/1.0.0/openapi.yaml", "version": "3.0" } ], "x-preferred": true, "x-providerName": "xkcd.com", "x-tags": [ "humor", "comics" ], "x-unofficialSpec": true }, "externalDocs": { "url": "https://xkcd.com/json.html" }, "paths": { "/info.0.json": { "get": { "description": "Fetch current comic and metadata.\n", "responses": { "200": { "content": { "*/*": { "schema": { "$ref": "#/components/schemas/comic" } } }, "description": "OK" } } } }, "/{comicId}/info.0.json": { "get": { "description": "Fetch comics and metadata by comic id.\n", "parameters": [ { "in": "path", "name": "comicId", "required": true, "schema": { "type": "number" } } ], "responses": { "200": { "content": { "*/*": { "schema": { "$ref": "#/components/schemas/comic" } } }, "description": "OK" } } } } }, "components": { "schemas": { "comic": { "properties": { "alt": { "type": "string" }, "day": { "type": "string" }, "img": { "type": "string" }, "link": { "type": "string" }, "month": { "type": "string" }, "news": { "type": "string" }, "num": { "type": "number" }, "safe_title": { "type": "string" }, "title": { "type": "string" }, "transcript": { "type": "string" }, "year": { "type": "string" } }, "type": "object" } } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/Resources/OpenApiDescriptions/xkcd.yml ================================================ # Originally from https://api.apis.guru/v2/specs/xkcd.com/1.0.0/openapi.yaml openapi: 3.0.0 servers: - url: 'http://xkcd.com/' info: description: 'Webcomic of romance, sarcasm, math, and language.' title: XKCD version: 1.0.0 x-apisguru-categories: - media x-logo: url: 'https://api.apis.guru/v2/cache/logo/http_imgs.xkcd.com_static_terrible_small_logo.png' x-origin: - format: openapi url: 'https://raw.githubusercontent.com/APIs-guru/unofficial_openapi_specs/master/xkcd.com/1.0.0/openapi.yaml' version: '3.0' x-preferred: true x-providerName: xkcd.com x-tags: - humor - comics x-unofficialSpec: true externalDocs: url: 'https://xkcd.com/json.html' paths: /info.0.json: get: description: | Fetch current comic and metadata. responses: '200': content: '*/*': schema: $ref: '#/components/schemas/comic' description: OK '/{comicId}/info.0.json': get: description: | Fetch comics and metadata by comic id. parameters: - in: path name: comicId required: true schema: type: number responses: '200': content: '*/*': schema: $ref: '#/components/schemas/comic' description: OK components: schemas: comic: properties: alt: type: string day: type: string img: type: string link: type: string month: type: string news: type: string num: type: number safe_title: type: string title: type: string transcript: type: string year: type: string type: object ================================================ FILE: test/Microsoft.HttpRepl.Tests/Suggestions/HeaderCompletionTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Net.Http; using Microsoft.HttpRepl.Fakes; using Microsoft.HttpRepl.FileSystem; using Microsoft.HttpRepl.OpenApi; using Microsoft.HttpRepl.Preferences; using Microsoft.HttpRepl.Suggestions; using Microsoft.HttpRepl.UserProfile; using Xunit; namespace Microsoft.HttpRepl.Tests.Suggestions { public class HeaderCompletionTests { [Fact] public void GetCompletions_NullExistingHeaders_ProperResults() { IEnumerable result = HeaderCompletion.GetCompletions(null, "U"); Assert.Equal(3, result.Count()); Assert.Contains("Upgrade", result, StringComparer.OrdinalIgnoreCase); Assert.Contains("Upgrade-Insecure-Requests", result, StringComparer.OrdinalIgnoreCase); Assert.Contains("User-Agent", result, StringComparer.OrdinalIgnoreCase); } [Fact] public void GetCompletions_ExistingHeader_ProperResults() { IReadOnlyCollection existingHeaders = new Collection() { "User-Agent" }; IEnumerable result = HeaderCompletion.GetCompletions(existingHeaders, "U"); Assert.Equal(2, result.Count()); Assert.Contains("Upgrade", result, StringComparer.OrdinalIgnoreCase); Assert.Contains("Upgrade-Insecure-Requests", result, StringComparer.OrdinalIgnoreCase); } [Fact] public void GetCompletions_NotAnExistingHeader_ProperResults() { IReadOnlyCollection existingHeaders = new Collection() { "Content-Type" }; IEnumerable result = HeaderCompletion.GetCompletions(existingHeaders, "U"); Assert.Equal(3, result.Count()); Assert.Contains("Upgrade", result, StringComparer.OrdinalIgnoreCase); Assert.Contains("Upgrade-Insecure-Requests", result, StringComparer.OrdinalIgnoreCase); Assert.Contains("User-Agent", result, StringComparer.OrdinalIgnoreCase); } [Fact] public void GetCompletions_NoResults_ReturnsEmpty() { IEnumerable result = HeaderCompletion.GetCompletions(null, "Not-A-Real-Header"); Assert.Empty(result); } [Theory] [InlineData("Accept")] [InlineData("Content-Length")] [InlineData("X-Requested-With")] public void GetValueCompletions_NoHeaderMatch_ReturnsNull(string header) { HttpState httpState = SetupHttpState(); IEnumerable result = HeaderCompletion.GetValueCompletions(method: null, path: null, header, prefix: null, httpState); Assert.Null(result); } [Fact] public void GetValueCompletions_NoMatch_ReturnsNull() { HttpState httpState = SetupHttpState(); IEnumerable result = HeaderCompletion.GetValueCompletions(method: "GET", path: "", header: "Content-Type", "", httpState); Assert.Null(result); } [Fact] public void GetValueCompletions_OneMatch_ReturnsMatch() { DirectoryStructure directoryStructure = new DirectoryStructure(null); RequestInfo requestInfo = new RequestInfo(); requestInfo.SetRequestBody("GET", "application/json", ""); requestInfo.SetRequestBody("PUT", "application/xml", ""); directoryStructure.RequestInfo = requestInfo; HttpState httpState = SetupHttpState(); httpState.BaseAddress = new Uri("https://localhost/"); ApiDefinition apiDefinition = new ApiDefinition(); apiDefinition.DirectoryStructure = directoryStructure; httpState.ApiDefinition = apiDefinition; IEnumerable result = HeaderCompletion.GetValueCompletions(method: "GET", path: "", header: "Content-Type", "", httpState); Assert.Single(result); Assert.Contains("application/json", result, StringComparer.OrdinalIgnoreCase); } [Fact] public void GetValueCompletions_MultipleMatches_ReturnsCorrectMatches() { DirectoryStructure directoryStructure = new DirectoryStructure(null); RequestInfo requestInfo = new RequestInfo(); requestInfo.SetRequestBody("GET", "application/json", ""); requestInfo.SetRequestBody("GET", "text/plain", ""); requestInfo.SetRequestBody("PUT", "application/xml", ""); directoryStructure.RequestInfo = requestInfo; HttpState httpState = SetupHttpState(); httpState.BaseAddress = new Uri("https://localhost/"); ApiDefinition apiDefinition = new ApiDefinition(); apiDefinition.DirectoryStructure = directoryStructure; httpState.ApiDefinition = apiDefinition; IEnumerable result = HeaderCompletion.GetValueCompletions(method: "GET", path: "", header: "Content-Type", "", httpState); Assert.Equal(2, result.Count()); Assert.Contains("application/json", result, StringComparer.OrdinalIgnoreCase); Assert.Contains("text/plain", result, StringComparer.OrdinalIgnoreCase); } [Fact] public void GetValueCompletions_NoMethod_ReturnsAll() { DirectoryStructure directoryStructure = new DirectoryStructure(null); RequestInfo requestInfo = new RequestInfo(); requestInfo.SetRequestBody("GET", "application/json", ""); requestInfo.SetRequestBody("GET", "text/plain", ""); requestInfo.SetRequestBody("PUT", "application/xml", ""); directoryStructure.RequestInfo = requestInfo; HttpState httpState = SetupHttpState(); httpState.BaseAddress = new Uri("https://localhost/"); ApiDefinition apiDefinition = new ApiDefinition(); apiDefinition.DirectoryStructure = directoryStructure; httpState.ApiDefinition = apiDefinition; IEnumerable result = HeaderCompletion.GetValueCompletions(method: null, path: "", header: "Content-Type", "", httpState); Assert.Equal(3, result.Count()); Assert.Contains("application/json", result, StringComparer.OrdinalIgnoreCase); Assert.Contains("text/plain", result, StringComparer.OrdinalIgnoreCase); Assert.Contains("application/xml", result, StringComparer.OrdinalIgnoreCase); } [Fact] public void GetValueCompletions_WithPrefix_ReturnsMatch() { DirectoryStructure directoryStructure = new DirectoryStructure(null); RequestInfo requestInfo = new RequestInfo(); requestInfo.SetRequestBody("GET", "application/json", ""); requestInfo.SetRequestBody("GET", "text/plain", ""); requestInfo.SetRequestBody("PUT", "application/xml", ""); directoryStructure.RequestInfo = requestInfo; HttpState httpState = SetupHttpState(); httpState.BaseAddress = new Uri("https://localhost/"); ApiDefinition apiDefinition = new ApiDefinition(); apiDefinition.DirectoryStructure = directoryStructure; httpState.ApiDefinition = apiDefinition; IEnumerable result = HeaderCompletion.GetValueCompletions(method: "GET", path: "", header: "Content-Type", "a", httpState); Assert.Single(result); Assert.Contains("application/json", result, StringComparer.OrdinalIgnoreCase); } [Fact] public void GetValueCompletions_EmptyContentType_Skips() { DirectoryStructure directoryStructure = new DirectoryStructure(null); RequestInfo requestInfo = new RequestInfo(); requestInfo.SetRequestBody("GET", "application/json", ""); requestInfo.SetRequestBody("GET", "", ""); directoryStructure.RequestInfo = requestInfo; HttpState httpState = SetupHttpState(); httpState.BaseAddress = new Uri("https://localhost/"); ApiDefinition apiDefinition = new ApiDefinition(); apiDefinition.DirectoryStructure = directoryStructure; httpState.ApiDefinition = apiDefinition; IEnumerable result = HeaderCompletion.GetValueCompletions(method: "GET", path: "", header: "Content-Type", "", httpState); Assert.Single(result); Assert.Contains("application/json", result, StringComparer.OrdinalIgnoreCase); } private static HttpState SetupHttpState() { IFileSystem fileSystem = new FileSystemStub(); IUserProfileDirectoryProvider userProfileDirectoryProvider = new UserProfileDirectoryProvider(); IPreferences preferences = new UserFolderPreferences(fileSystem, userProfileDirectoryProvider, null); HttpClient httpClient = new HttpClient(); return new HttpState(preferences, httpClient); } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/Suggestions/ServerPathCompletionTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using Microsoft.HttpRepl.Fakes; using Microsoft.HttpRepl.FileSystem; using Microsoft.HttpRepl.OpenApi; using Microsoft.HttpRepl.Preferences; using Microsoft.HttpRepl.Suggestions; using Microsoft.HttpRepl.UserProfile; using Xunit; namespace Microsoft.HttpRepl.Tests.Suggestions { public class ServerPathCompletionTests { [Fact] public void GetCompletions_AbsoluteUri_ReturnsNull() { HttpState httpState = SetupHttpState(); IEnumerable result = ServerPathCompletion.GetCompletions(httpState, "https://github.com/"); Assert.Null(result); } [Fact] public void GetCompletions_WithBackslashes_ProperResults() { HttpState httpState = SetupHttpState(); IEnumerable result = ServerPathCompletion.GetCompletions(httpState, "child1\\g"); Assert.Equal(2, result.Count()); Assert.Contains("child1/grandchild1", result, StringComparer.OrdinalIgnoreCase); Assert.Contains("child1/grandchild2", result, StringComparer.OrdinalIgnoreCase); } [Fact] public void GetCompletions_MixedCase_ProperResults() { HttpState httpState = SetupHttpState(); IEnumerable result = ServerPathCompletion.GetCompletions(httpState, "cHiLd1/gRaNd"); Assert.Equal(2, result.Count()); Assert.Contains("child1/grandchild1", result, StringComparer.OrdinalIgnoreCase); Assert.Contains("child1/grandchild2", result, StringComparer.OrdinalIgnoreCase); } [Fact] public void GetCompletions_OnlyRoot_ProperResults() { HttpState httpState = SetupHttpState(); IEnumerable result = ServerPathCompletion.GetCompletions(httpState, "ch"); Assert.Equal(2, result.Count()); Assert.Contains("child1", result, StringComparer.OrdinalIgnoreCase); Assert.Contains("child2", result, StringComparer.OrdinalIgnoreCase); } [Fact] public void GetCompletions_EmptyDirectory_ReturnsEmpty() { HttpState httpState = SetupHttpState(); IEnumerable result = ServerPathCompletion.GetCompletions(httpState, "child1/grandchild1/g"); Assert.Empty(result); } [Fact] public void GetCompletions_NoMatches_ReturnsEmpty() { HttpState httpState = SetupHttpState(); IEnumerable result = ServerPathCompletion.GetCompletions(httpState, "child1/abc"); Assert.Empty(result); } [Fact] public void GetCompletions_NullStructure_ReturnsNull() { HttpState httpState = SetupHttpState(); httpState.ApiDefinition = null; IEnumerable result = ServerPathCompletion.GetCompletions(httpState, "c"); Assert.Null(result); } private static HttpState SetupHttpState() { IFileSystem fileSystem = new FileSystemStub(); IUserProfileDirectoryProvider userProfileDirectoryProvider = new UserProfileDirectoryProvider(); IPreferences preferences = new UserFolderPreferences(fileSystem, userProfileDirectoryProvider, null); HttpClient httpClient = new HttpClient(); HttpState httpState = new HttpState(preferences, httpClient); DirectoryStructure structure = new DirectoryStructure(null); DirectoryStructure child1 = structure.DeclareDirectory("child1"); structure.DeclareDirectory("child2"); child1.DeclareDirectory("grandchild1"); child1.DeclareDirectory("grandchild2"); ApiDefinition apiDefinition = new ApiDefinition(); apiDefinition.DirectoryStructure = structure; httpState.ApiDefinition = apiDefinition; return httpState; } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/Telemetry/Events/PreferenceEventTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using Microsoft.HttpRepl.Telemetry.Events; using Xunit; namespace Microsoft.HttpRepl.Tests.Telemetry.Events { public class PreferenceEventTests { [Theory] [InlineData("get", null)] [InlineData("get", "")] [InlineData("get", " ")] [InlineData("get", "editor.command.default")] [InlineData("get", "not.really.a.preference")] [InlineData("set", null)] [InlineData("set", "")] [InlineData("set", " ")] [InlineData("set", "editor.command.default")] [InlineData("set", "not.really.a.preference")] public void Constructor_DoesNotThrow(string getOrSet, string preferenceName) { PreferenceEvent preferenceEvent = new PreferenceEvent(getOrSet, preferenceName); } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/Telemetry/Events/SetHeaderEventTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using Microsoft.HttpRepl.Telemetry.Events; using Xunit; namespace Microsoft.HttpRepl.Tests.Telemetry.Events { public class SetHeaderEventTests { [Theory] [InlineData(null, false)] [InlineData("", false)] [InlineData(" ", false)] [InlineData("Content-Type", false)] [InlineData("X-Custom-Header", false)] [InlineData(null, true)] [InlineData("", true)] [InlineData(" ", true)] [InlineData("Content-Type", true)] [InlineData("X-Custom-Header", true)] public void Constructor_DoesNotThrow(string headerName, bool isValueEmpty) { SetHeaderEvent preferenceEvent = new SetHeaderEvent(headerName, isValueEmpty); } } } ================================================ FILE: test/Microsoft.Repl.Tests/ConsoleHandling/AnsiColorExtensionsTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using Microsoft.Repl.ConsoleHandling; using Xunit; namespace Microsoft.Repl.Tests.ConsoleHandling { public class AnsiColorExtensionsTests { [Theory] [InlineData("BlackText", AllowedColors.BoldBlack, "\x1B[30m\x1B[1mBlackText\x1B[39m\x1B[39m")] [InlineData("RedText", AllowedColors.BoldRed, "\x1B[31m\x1B[1mRedText\x1B[39m\x1B[39m")] [InlineData("GreenText", AllowedColors.BoldGreen, "\x1B[32m\x1B[1mGreenText\x1B[39m\x1B[39m")] [InlineData("YellowText", AllowedColors.BoldYellow, "\x1B[33m\x1B[1mYellowText\x1B[39m\x1B[39m")] [InlineData("BlueText", AllowedColors.BoldBlue, "\x1B[34m\x1B[1mBlueText\x1B[39m\x1B[39m")] [InlineData("MagentaText", AllowedColors.BoldMagenta, "\x1B[35m\x1B[1mMagentaText\x1B[39m\x1B[39m")] [InlineData("CyanText", AllowedColors.BoldCyan, "\x1B[36m\x1B[1mCyanText\x1B[39m\x1B[39m")] [InlineData("WhiteText", AllowedColors.BoldWhite, "\x1B[37m\x1B[1mWhiteText\x1B[39m\x1B[39m")] public void SetColor_WithBold_ResponseIncludesColorAndBold(string text, AllowedColors color, string expected) { var result = text.SetColor(color); Assert.Equal(expected, result); } [Theory] [InlineData("BlackText", AllowedColors.Black, "\x1B[30mBlackText\x1B[39m")] [InlineData("RedText", AllowedColors.Red, "\x1B[31mRedText\x1B[39m")] [InlineData("GreenText", AllowedColors.Green, "\x1B[32mGreenText\x1B[39m")] [InlineData("YellowText", AllowedColors.Yellow, "\x1B[33mYellowText\x1B[39m")] [InlineData("BlueText", AllowedColors.Blue, "\x1B[34mBlueText\x1B[39m")] [InlineData("MagentaText", AllowedColors.Magenta, "\x1B[35mMagentaText\x1B[39m")] [InlineData("CyanText", AllowedColors.Cyan, "\x1B[36mCyanText\x1B[39m")] [InlineData("WhiteText", AllowedColors.White, "\x1B[37mWhiteText\x1B[39m")] public void SetColor_WithoutBold_ResponseIncludesJustColor(string text, AllowedColors color, string expected) { var result = text.SetColor(color); Assert.Equal(expected, result); } [Theory] [InlineData("None", AllowedColors.None, "None")] [InlineData("InvalidColor", (AllowedColors)0x50, "InvalidColor")] public void SetColor_NoneOrInvalidColor_ReturnsText(string text, AllowedColors color, string expected) { var result = text.SetColor(color); Assert.Equal(expected, result); } } } ================================================ FILE: test/Microsoft.Repl.Tests/DisposableTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System; using Xunit; namespace Microsoft.Repl.Tests { public class DisposableTests { [Fact] public void NonGeneric_Dispose_WithNullAction_DoesNotCrash() { using (Disposable disposable = new Disposable(null)) { } } [Fact] public void Generic_Dispose_WithNullProperty_DoesNotCrash() { using (Disposable disposable = new Disposable(null, null)) { } } [Fact] public void NonGeneric_Dispose_WithAction_CallsOnDispose() { bool onDisposeWasCalled = false; Action onDispose = () => onDisposeWasCalled = true; using (Disposable disposable = new Disposable(onDispose)) { } Assert.True(onDisposeWasCalled); } [Fact] public void Generic_Dispose_WithAction_CallsOnDispose() { bool onDisposeWasCalled = false; Action onDispose = () => onDisposeWasCalled = true; using (Disposable disposable = new Disposable(new ClassStub(), onDispose)) { } Assert.True(onDisposeWasCalled); } [Fact] public void Generic_Dispose_WithDisposable_CallsDispose() { DisposableStub disposableStub = new DisposableStub(); using (Disposable disposable = new Disposable(disposableStub, () => { })) { } Assert.True(disposableStub.DisposeWasCalled); } public class ClassStub { } public class DisposableStub : IDisposable { public bool DisposeWasCalled { get; private set; } = false; public void Dispose() { DisposeWasCalled = true; } } } } ================================================ FILE: test/Microsoft.Repl.Tests/InputManagerTests.cs ================================================ using Microsoft.HttpRepl.Fakes; using Microsoft.Repl.Input; using Xunit; namespace Microsoft.Repl.Tests { public class InputManagerTests { [Fact] public void RemovePreviousCharacter_AtBeginning_DoesNothing() { // Arrange string initialInput = "echo on"; int initialPosition = 0; InputManager inputManager = new(initialInput, initialPosition); MockedShellState mockedShellState = new(inputManager); // Act inputManager.RemovePreviousCharacter(mockedShellState); // Assert Assert.Equal(initialPosition, inputManager.CaretPosition); Assert.Equal(initialInput, inputManager.GetCurrentBuffer()); } [Fact] public void RemovePreviousCharacter_AtEnd_RemovesLastCharacter() { // Arrange string initialInput = "echo on"; int initialPosition = 7; string expectedInput = "echo o"; int expectedPosition = 6; InputManager inputManager = new(initialInput, initialPosition); MockedShellState mockedShellState = new(inputManager); // Act inputManager.RemovePreviousCharacter(mockedShellState); // Assert Assert.Equal(expectedPosition, inputManager.CaretPosition); Assert.Equal(expectedInput, inputManager.GetCurrentBuffer()); } [Fact] public void RemovePreviousCharacter_InMiddle_RemovesProperCharacter() { // Arrange string initialInput = "echo on"; int initialPosition = 4; string expectedInput = "ech on"; int expectedPosition = 3; InputManager inputManager = new(initialInput, initialPosition); MockedShellState mockedShellState = new(inputManager); // Act inputManager.RemovePreviousCharacter(mockedShellState); // Assert Assert.Equal(expectedPosition, inputManager.CaretPosition); Assert.Equal(expectedInput, inputManager.GetCurrentBuffer()); } [Fact] public void RemoveCurrentCharacter_AtEnd_DoesNothing() { // Arrange string initialInput = "echo on"; int initialPosition = 7; InputManager inputManager = new(initialInput, initialPosition); MockedShellState mockedShellState = new(inputManager); // Act inputManager.RemoveCurrentCharacter(mockedShellState); // Assert Assert.Equal(initialPosition, inputManager.CaretPosition); Assert.Equal(initialInput, inputManager.GetCurrentBuffer()); } [Fact] public void RemoveCurrentCharacter_AtBeginning_RemovesFirstCharacter() { // Arrange string initialInput = "echo on"; int initialPosition = 0; string expectedInput = "cho on"; int expectedPosition = 0; InputManager inputManager = new(initialInput, initialPosition); MockedShellState mockedShellState = new(inputManager); // Act inputManager.RemoveCurrentCharacter(mockedShellState); // Assert Assert.Equal(expectedPosition, inputManager.CaretPosition); Assert.Equal(expectedInput, inputManager.GetCurrentBuffer()); } [Fact] public void RemoveCurrentCharacter_InMiddle_RemovesProperCharacter() { // Arrange string initialInput = "echo on"; int initialPosition = 4; string expectedInput = "echoon"; int expectedPosition = 4; InputManager inputManager = new(initialInput, initialPosition); MockedShellState mockedShellState = new(inputManager); // Act inputManager.RemoveCurrentCharacter(mockedShellState); // Assert Assert.Equal(expectedPosition, inputManager.CaretPosition); Assert.Equal(expectedInput, inputManager.GetCurrentBuffer()); } } } ================================================ FILE: test/Microsoft.Repl.Tests/Microsoft.Repl.Tests.csproj ================================================ $(TestProjectTargetFramework) ================================================ FILE: test/Microsoft.Repl.Tests/ParserTests.cs ================================================ using Microsoft.Repl.Parsing; using Xunit; namespace Microsoft.Repl.Tests { public class ParserTests { [Fact] public void ParserTests_Basic() { string testString = "\"this is a test\" of \"Escape\\\\Sequences\\\"\""; CoreParser parser = new CoreParser(); ICoreParseResult result = parser.Parse(testString, 29); Assert.Equal(3, result.Sections.Count); Assert.Equal(2, result.SelectedSection); Assert.Equal(0, result.SectionStartLookup[0]); Assert.Equal(17, result.SectionStartLookup[1]); Assert.Equal(20, result.SectionStartLookup[2]); Assert.Equal(7, result.CaretPositionWithinSelectedSection); Assert.Equal(29, result.CaretPositionWithinCommandText); Assert.Equal(testString, result.CommandText); } } } ================================================ FILE: test/Microsoft.Repl.Tests/Parsing/CoreParseResultTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the License.txt file in the project root for more information. using System.Linq; using Microsoft.Repl.Parsing; using Xunit; namespace Microsoft.Repl.Tests.Parsing { public class CoreParseResultTests { [Fact] public void Slice_WithZero_ReturnsThis() { CoreParser parser = new CoreParser(); string commandText = "set header content-type application/json"; ICoreParseResult parseResult = parser.Parse(commandText, commandText.Length); ICoreParseResult result = parseResult.Slice(0); Assert.Same(parseResult, result); } [Fact] public void Slice_WithTooMany_ReturnsDefaultResult() { CoreParser parser = new CoreParser(); string commandText = "set header content-type application/json"; ICoreParseResult parseResult = parser.Parse(commandText, commandText.Length); ICoreParseResult result = parseResult.Slice(100); Assert.Equal(0, result.CaretPositionWithinCommandText); Assert.Equal(0, result.CaretPositionWithinSelectedSection); Assert.Equal(string.Empty, result.CommandText); Assert.Single(result.Sections); Assert.Contains(string.Empty, result.Sections); Assert.Equal(0, result.SelectedSection); Assert.Single(result.SectionStartLookup); Assert.Equal(0, result.SectionStartLookup.Single().Key); Assert.Equal(0, result.SectionStartLookup.Single().Value); } [Theory] [InlineData("set header content-type application/json", 1, "header content-type application/json")] [InlineData("set header content-type application/json", 2, "content-type application/json")] [InlineData("set header content-type application/json", 3, "application/json")] public void Slice_WithVariousSliceLengths_CorrectCommandTextOutput(string commandText, int toRemove, string expectedCommandText) { CoreParser parser = new CoreParser(); ICoreParseResult parseResult = parser.Parse(commandText, commandText.Length); ICoreParseResult result = parseResult.Slice(toRemove); Assert.Equal(expectedCommandText, result.CommandText); } [Fact] public void Slice_WithCaretInSlicedRegion_CaretIsZero() { CoreParser parser = new CoreParser(); string commandText = "set header content-type application/json"; ICoreParseResult parseResult = parser.Parse(commandText, 5); ICoreParseResult result = parseResult.Slice(2); Assert.Equal(0, result.CaretPositionWithinCommandText); } [Theory] [InlineData("set header content-type application/json", 1, 26, 2)] [InlineData("set header content-type application/json", 2, 26, 1)] [InlineData("set header content-type application/json", 3, 26, 0)] public void Slice_WithSelectedSection_SelectedSectionMoves(string commandText, int toRemove, int caretPosition, int expectedSelectedSection) { CoreParser parser = new CoreParser(); ICoreParseResult parseResult = parser.Parse(commandText, caretPosition); ICoreParseResult result = parseResult.Slice(toRemove); Assert.Equal(expectedSelectedSection, result.SelectedSection); } [Theory] [InlineData("set base https://localhost", 3)] [InlineData("run c:\\script.txt", 2)] [InlineData("help connect", 2)] [InlineData("set base ", 3)] [InlineData("", 1)] [InlineData(" set base https://localhost", 3)] public void Parse_WithDoubleSpace_RemovesEmptySections(string commandText, int expectedSectionCount) { CoreParser parser = new CoreParser(); ICoreParseResult parseResult = parser.Parse(commandText, commandText.Length + 1); Assert.Equal(expectedSectionCount, parseResult.Sections.Count); } [Fact] public void Parse_WithQuotesButNoSpaces_DoesNotCombineSections() { // Arrange string commandText = "GET --response:headers \"file.txt\" --response:body \"file.txt\""; int caretPosition = commandText.Length + 1; CoreParser parser = new CoreParser(); int expectedSectionCount = 5; // Act ICoreParseResult parseResult = parser.Parse(commandText, caretPosition); // Assert Assert.Equal(expectedSectionCount, parseResult.Sections.Count); } } } ================================================ FILE: test/Microsoft.Repl.Tests/ShellTests.cs ================================================ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.HttpRepl.Fakes; using Microsoft.Repl.Commanding; using Moq; using Xunit; namespace Microsoft.Repl.Tests { public class ShellTests { [Fact] public async Task RunAsync_WithUpArrowKeyPress_UpdatesCurrentBufferWithPreviousCommand() { string previousCommand = "set base \"https://localhost:44366/\""; ConsoleKeyInfo consoleKeyInfo = new ConsoleKeyInfo(keyChar: '\0', key: ConsoleKey.UpArrow, shift: false, alt: false, control: false); Shell shell = CreateShell(consoleKeyInfo, previousCommand: previousCommand, nextCommand: null, out CancellationTokenSource cancellationTokenSource); await shell.RunAsync(cancellationTokenSource.Token); // Verify the input buffer has previous command after the UpArrow key press event Assert.Equal(previousCommand, shell.ShellState.InputManager.GetCurrentBuffer()); Assert.Equal(previousCommand.Length, shell.ShellState.InputManager.CaretPosition); } [Fact] public async Task RunAsync_WithUpArrowKeyPressWithLongPreviousCommand_UpdatesCurrentBufferWithPreviousCommand() { string previousCommand = "connect \"https://localhost:44366/\" --base \"https://localhost:44366/api/v2/\" --openapi \"https://localhost:44366/openapi/v2/openapi.yaml\""; ConsoleKeyInfo consoleKeyInfo = new ConsoleKeyInfo(keyChar: '\0', key: ConsoleKey.UpArrow, shift: false, alt: false, control: false); Shell shell = CreateShell(consoleKeyInfo, previousCommand: previousCommand, nextCommand: null, out CancellationTokenSource cancellationTokenSource); await shell.RunAsync(cancellationTokenSource.Token); // Verify the input buffer has previous command after the UpArrow key press event Assert.Equal(previousCommand, shell.ShellState.InputManager.GetCurrentBuffer()); Assert.Equal(previousCommand.Length, shell.ShellState.InputManager.CaretPosition); } [Fact] public async Task RunAsync_WithUpArrowKeyPress_VerifyInputBufferContentsBeforeAndAfterKeyPressEvent() { string previousCommand = "set base \"https://localhost:44366/\""; ConsoleKeyInfo consoleKeyInfo = new ConsoleKeyInfo(keyChar: '\0', key: ConsoleKey.UpArrow, shift: false, alt: false, control: false); Shell shell = CreateShell(consoleKeyInfo, previousCommand: previousCommand, nextCommand: null, out CancellationTokenSource cancellationTokenSource); // Verify the input buffer is empty before the UpArrow key press event Assert.Equal(string.Empty, shell.ShellState.InputManager.GetCurrentBuffer()); Assert.Equal(0, shell.ShellState.InputManager.CaretPosition); await shell.RunAsync(cancellationTokenSource.Token); // Verify the input buffer has previous command after the UpArrow key press event Assert.Equal(previousCommand, shell.ShellState.InputManager.GetCurrentBuffer()); Assert.Equal(previousCommand.Length, shell.ShellState.InputManager.CaretPosition); } [Fact] public async Task RunAsync_WithDownArrowKeyPress_UpdatesCurrentBufferWithNextCommand() { string nextCommand = "get"; ConsoleKeyInfo consoleKeyInfo = new ConsoleKeyInfo(keyChar: '\0', key: ConsoleKey.DownArrow, shift: false, alt: false, control: false); Shell shell = CreateShell(consoleKeyInfo, previousCommand: null, nextCommand: nextCommand, out CancellationTokenSource cancellationTokenSource); await shell.RunAsync(cancellationTokenSource.Token); // Verify the input buffer has next command after the DownArrow key press event Assert.Equal(nextCommand, shell.ShellState.InputManager.GetCurrentBuffer()); Assert.Equal(nextCommand.Length, shell.ShellState.InputManager.CaretPosition); } [Fact] public async Task RunAsync_WithDeleteKeyPress_DeletesCurrentCharacterInTheInputBuffer() { ConsoleKeyInfo[] keys = new[] { GetConsoleKeyInfo('g'), GetConsoleKeyInfo('e'), GetConsoleKeyInfo('t'), GetConsoleKeyInfo(ConsoleKey.LeftArrow), GetConsoleKeyInfo(ConsoleKey.Delete) }; Shell shell = CreateShell(keys, previousCommand: null, nextCommand: null, out CancellationTokenSource cancellationTokenSource); string inputBufferTextAfterKeyPress = "ge"; await shell.RunAsync(cancellationTokenSource.Token); // Verify the input buffer contents after Delete key press event Assert.Equal(inputBufferTextAfterKeyPress, shell.ShellState.InputManager.GetCurrentBuffer()); } [Fact] public async Task RunAsync_WithBackspaceKeyPress_DeletesPreviousCharacterInTheInputBuffer() { ConsoleKeyInfo[] keys = new[] { GetConsoleKeyInfo('g'), GetConsoleKeyInfo('e'), GetConsoleKeyInfo('t'), GetConsoleKeyInfo(ConsoleKey.LeftArrow), GetConsoleKeyInfo(ConsoleKey.Backspace) }; Shell shell = CreateShell(keys, previousCommand: null, nextCommand: null, out CancellationTokenSource cancellationTokenSource); string inputBufferTextAfterKeyPress = "gt"; await shell.RunAsync(cancellationTokenSource.Token); // Verify the input buffer contents after Backspace key press event Assert.Equal(inputBufferTextAfterKeyPress, shell.ShellState.InputManager.GetCurrentBuffer()); } [Fact] public async Task RunAsync_WithEscapeKeyPress_UpdatesInputBufferWithEmptyString() { ConsoleKeyInfo[] keys = new[] { GetConsoleKeyInfo('g'), GetConsoleKeyInfo('e'), GetConsoleKeyInfo('t'), GetConsoleKeyInfo(ConsoleKey.Escape), }; Shell shell = CreateShell(keys, previousCommand: null, nextCommand: null, out CancellationTokenSource cancellationTokenSource); string inputBufferTextAfterKeyPress = string.Empty; await shell.RunAsync(cancellationTokenSource.Token); // Verify the input buffer contents after Escape key press event Assert.Equal(inputBufferTextAfterKeyPress, shell.ShellState.InputManager.GetCurrentBuffer()); } [Fact] public async Task RunAsync_WithCtrlUKeyPress_UpdatesInputBufferWithEmptyString() { ConsoleKeyInfo[] keys = new[] { GetConsoleKeyInfo('g'), GetConsoleKeyInfo('e'), GetConsoleKeyInfo('t'), new(keyChar: '\0', key: ConsoleKey.U, shift: false, alt: false, control: true), }; Shell shell = CreateShell(keys, previousCommand: null, nextCommand: null, out CancellationTokenSource cancellationTokenSource); string inputBufferTextAfterKeyPress = string.Empty; await shell.RunAsync(cancellationTokenSource.Token); // Verify the input buffer contents after Ctrl + U key press event Assert.Equal(inputBufferTextAfterKeyPress, shell.ShellState.InputManager.GetCurrentBuffer()); } [Fact] public async Task RunAsync_WithInsertKeyPress_FlipsIsOverwriteModeInInputManager() { ConsoleKeyInfo consoleKeyInfo = new ConsoleKeyInfo(keyChar: '\0', key: ConsoleKey.Insert, shift: false, alt: false, control: false); Shell shell = CreateShell(consoleKeyInfo, previousCommand: null, nextCommand: null, out CancellationTokenSource cancellationTokenSource); await shell.RunAsync(cancellationTokenSource.Token); // Verify IsOverwriteMode flag in input manager is set to false after Insert key press event Assert.True(shell.ShellState.InputManager.IsOverwriteMode); } [Fact] public async Task RunAsync_WithUnhandledKeyPress_DoesNothing() { ConsoleKeyInfo[] keys = new[] { GetConsoleKeyInfo('g'), GetConsoleKeyInfo('e'), GetConsoleKeyInfo('t'), GetConsoleKeyInfo(ConsoleKey.F1), }; Shell shell = CreateShell(keys, previousCommand: null, nextCommand: null, out CancellationTokenSource cancellationTokenSource); string inputBufferText = "get"; await shell.RunAsync(cancellationTokenSource.Token); // Verify the input buffer contents after F1 key press event Assert.Equal(inputBufferText, shell.ShellState.InputManager.GetCurrentBuffer()); } [Fact] public async Task RunAsync_WithTabKeyPress_UpdatesInputBufferWithFirstEntryFromSuggestionList() { ConsoleKeyInfo[] keys = new[] { GetConsoleKeyInfo('c'), GetConsoleKeyInfo(ConsoleKey.Tab), }; Shell shell = CreateShell(keys, previousCommand: null, nextCommand: null, out CancellationTokenSource cancellationTokenSource); string inputBufferTextAfterKeyPress = "cd"; DefaultCommandDispatcher defaultCommandDispatcher = shell.ShellState.CommandDispatcher as DefaultCommandDispatcher; string cdCommandName = "cd"; defaultCommandDispatcher.AddCommand(new MockCommand(cdCommandName)); string clearCommandName = "clear"; defaultCommandDispatcher.AddCommand(new MockCommand(clearCommandName)); await shell.RunAsync(cancellationTokenSource.Token); // Verify the input buffer contents after tab key press event Assert.Equal(inputBufferTextAfterKeyPress, shell.ShellState.InputManager.GetCurrentBuffer()); Assert.Equal(inputBufferTextAfterKeyPress.Length, shell.ShellState.InputManager.CaretPosition); } [Fact] public async Task RunAsync_WithShiftTabKeyPress_UpdatesInputBufferWithLastEntryFromSuggestionList() { ConsoleKeyInfo[] keys = new[] { GetConsoleKeyInfo('c'), new(keyChar: '\0', key: ConsoleKey.Tab, shift: true, alt: false, control: false), }; Shell shell = CreateShell(keys, previousCommand: null, nextCommand: null, out CancellationTokenSource cancellationTokenSource); string inputBufferTextAfterKeyPress = "clear"; DefaultCommandDispatcher defaultCommandDispatcher = shell.ShellState.CommandDispatcher as DefaultCommandDispatcher; string cdCommandName = "cd"; defaultCommandDispatcher.AddCommand(new MockCommand(cdCommandName)); string clearCommandName = "clear"; defaultCommandDispatcher.AddCommand(new MockCommand(clearCommandName)); await shell.RunAsync(cancellationTokenSource.Token); // Verify the input buffer contents after Shift + Tab key press event Assert.Equal(inputBufferTextAfterKeyPress, shell.ShellState.InputManager.GetCurrentBuffer()); Assert.Equal(inputBufferTextAfterKeyPress.Length, shell.ShellState.InputManager.CaretPosition); } [Fact] public async Task RunAsync_WithTabKeyPressAndNoSuggestions_DoesNothing() { ConsoleKeyInfo[] keys = new[] { GetConsoleKeyInfo('z'), GetConsoleKeyInfo(ConsoleKey.Tab), }; Shell shell = CreateShell(keys, previousCommand: null, nextCommand: null, out CancellationTokenSource cancellationTokenSource); string inputBufferTextAfterKeyPress = "z"; await shell.RunAsync(cancellationTokenSource.Token); // Verify the input buffer contents after tab key press event Assert.Equal(inputBufferTextAfterKeyPress, shell.ShellState.InputManager.GetCurrentBuffer()); } [Fact] public async Task RunAsync_WithShiftTabKeyPressAndNoSuggestions_DoesNothing() { ConsoleKeyInfo[] keys = new[] { GetConsoleKeyInfo('z'), new(keyChar: '\0', key: ConsoleKey.Tab, shift: true, alt: false, control: false) }; Shell shell = CreateShell(keys, previousCommand: null, nextCommand: null, out CancellationTokenSource cancellationTokenSource); string inputBufferTextAfterKeyPress = "z"; await shell.RunAsync(cancellationTokenSource.Token); // Verify the input buffer contents after Shift + Tab key press event Assert.Equal(inputBufferTextAfterKeyPress, shell.ShellState.InputManager.GetCurrentBuffer()); } [Fact] public async Task RunAsync_WithEnterKeyPress_UpdatesInputBufferWithEmptyString() { string input = "set base \"https://localhost:44366/\""; List keys = GetConsoleKeyInfo(input); keys.Add(new(keyChar: '\0', key: ConsoleKey.Enter, shift: false, alt: false, control: false)); Shell shell = CreateShell(keys, previousCommand: null, nextCommand: null, out CancellationTokenSource cancellationTokenSource); await shell.RunAsync(cancellationTokenSource.Token); Assert.Equal(string.Empty, shell.ShellState.InputManager.GetCurrentBuffer()); Assert.Equal(0, shell.ShellState.InputManager.CaretPosition); } [Fact] public async Task RunAsync_WithLeftArrowKeyPress_VerifyCaretPositionWasUpdated() { string input = "set base \"https://localhost:44366/\""; List keys = GetConsoleKeyInfo(input); keys.Add(new(keyChar: '\0', key: ConsoleKey.LeftArrow, shift: false, alt: false, control: false)); Shell shell = CreateShell(keys, previousCommand: null, nextCommand: null, out CancellationTokenSource cancellationTokenSource); IShellState shellState = shell.ShellState; await shell.RunAsync(cancellationTokenSource.Token); Assert.Equal(input.Length - 1, shellState.InputManager.CaretPosition); } [Fact] public async Task RunAsync_WithControlLeftArrowKeyPress_VerifyCaretPositionWasUpdated() { string input = "set base \"https://localhost:44366/\""; List keys = GetConsoleKeyInfo(input); keys.Add(new(keyChar: '\0', key: ConsoleKey.LeftArrow, shift: false, alt: false, control: true)); Shell shell = CreateShell(keys, previousCommand: null, nextCommand: null, out CancellationTokenSource cancellationTokenSource); IShellState shellState = shell.ShellState; await shell.RunAsync(cancellationTokenSource.Token); Assert.Equal(9, shellState.InputManager.CaretPosition); } [Fact] public async Task RunAsync_WithRightArrowKeyPress_VerifyCaretPositionWasUpdated() { string input = "set base \"https://localhost:44366/\""; List keys = GetConsoleKeyInfo(input); keys.Add(new(keyChar: '\0', key: ConsoleKey.LeftArrow, shift: false, alt: false, control: false)); keys.Add(new(keyChar: '\0', key: ConsoleKey.LeftArrow, shift: false, alt: false, control: false)); keys.Add(new(keyChar: '\0', key: ConsoleKey.LeftArrow, shift: false, alt: false, control: false)); keys.Add(new(keyChar: '\0', key: ConsoleKey.RightArrow, shift: false, alt: false, control: false)); Shell shell = CreateShell(keys, previousCommand: null, nextCommand: null, out CancellationTokenSource cancellationTokenSource); IShellState shellState = shell.ShellState; await shell.RunAsync(cancellationTokenSource.Token); Assert.Equal(input.Length - 2, shellState.InputManager.CaretPosition); } [Fact] public async Task RunAsync_WithControlRightArrowKeyPress_VerifyCaretPositionWasUpdated() { string input = "set base \"https://localhost:44366/\""; List keys = GetConsoleKeyInfo(input); keys.Add(new(keyChar: '\0', key: ConsoleKey.LeftArrow, shift: false, alt: false, control: false)); keys.Add(new(keyChar: '\0', key: ConsoleKey.LeftArrow, shift: false, alt: false, control: false)); keys.Add(new(keyChar: '\0', key: ConsoleKey.LeftArrow, shift: false, alt: false, control: false)); keys.Add(new(keyChar: '\0', key: ConsoleKey.RightArrow, shift: false, alt: false, control: true)); Shell shell = CreateShell(keys, previousCommand: null, nextCommand: null, out CancellationTokenSource cancellationTokenSource); IShellState shellState = shell.ShellState; await shell.RunAsync(cancellationTokenSource.Token); Assert.Equal(input.Length, shellState.InputManager.CaretPosition); } [Fact] public async Task RunAsync_WithHomeKeyPress_VerifyCaretPositionWasUpdated() { string input = "set base \"https://localhost:44366/\""; List keys = GetConsoleKeyInfo(input); keys.Add(new(keyChar: '\0', key: ConsoleKey.Home, shift: false, alt: false, control: false)); Shell shell = CreateShell(keys, previousCommand: null, nextCommand: null, out CancellationTokenSource cancellationTokenSource); IShellState shellState = shell.ShellState; await shell.RunAsync(cancellationTokenSource.Token); Assert.Equal(0, shellState.InputManager.CaretPosition); } [Fact] public async Task RunAsync_WithCtrlAKeyPress_VerifyCaretPositionWasUpdated() { string input = "set base \"https://localhost:44366/\""; List keys = GetConsoleKeyInfo(input); keys.Add(new(keyChar: '\0', key: ConsoleKey.A, shift: false, alt: false, control: true)); Shell shell = CreateShell(keys, previousCommand: null, nextCommand: null, out CancellationTokenSource cancellationTokenSource); IShellState shellState = shell.ShellState; await shell.RunAsync(cancellationTokenSource.Token); Assert.Equal(0, shellState.InputManager.CaretPosition); } [Fact] public async Task RunAsync_WithEndKeyPress_VerifyCaretPositionWasUpdated() { string input = "set base \"https://localhost:44366/\""; List keys = GetConsoleKeyInfo(input); keys.Add(new(keyChar: '\0', key: ConsoleKey.LeftArrow, shift: false, alt: false, control: false)); keys.Add(new(keyChar: '\0', key: ConsoleKey.LeftArrow, shift: false, alt: false, control: false)); keys.Add(new(keyChar: '\0', key: ConsoleKey.LeftArrow, shift: false, alt: false, control: false)); keys.Add(new(keyChar: '\0', key: ConsoleKey.End, shift: false, alt: false, control: false)); Shell shell = CreateShell(keys, previousCommand: null, nextCommand: null, out CancellationTokenSource cancellationTokenSource); IShellState shellState = shell.ShellState; await shell.RunAsync(cancellationTokenSource.Token); Assert.Equal(input.Length, shellState.InputManager.CaretPosition); } [Fact] public async Task RunAsync_WithCtrlEKeyPress_VerifyCaretPositionWasUpdated() { string input = "set base \"https://localhost:44366/\""; List keys = GetConsoleKeyInfo(input); keys.Add(new(keyChar: '\0', key: ConsoleKey.LeftArrow, shift: false, alt: false, control: false)); keys.Add(new(keyChar: '\0', key: ConsoleKey.LeftArrow, shift: false, alt: false, control: false)); keys.Add(new(keyChar: '\0', key: ConsoleKey.LeftArrow, shift: false, alt: false, control: false)); keys.Add(new(keyChar: '\0', key: ConsoleKey.E, shift: false, alt: false, control: true)); Shell shell = CreateShell(keys, previousCommand: null, nextCommand: null, out CancellationTokenSource cancellationTokenSource); await shell.RunAsync(cancellationTokenSource.Token); Assert.Equal(input.Length, shell.ShellState.InputManager.CaretPosition); } [Fact] public async Task RunAsync_WithInvalidCommand_VerifyInputBufferIsCleared() { string input = "this is an invalid command\n"; List keys = GetConsoleKeyInfo(input); Shell shell = CreateShell(keys, previousCommand: null, nextCommand: null, out CancellationTokenSource cancellationTokenSource); await shell.RunAsync(cancellationTokenSource.Token); Assert.Empty(shell.ShellState.InputManager.GetCurrentBuffer()); Assert.Equal(0, shell.ShellState.InputManager.CaretPosition); } [Fact] public async Task RunAsync_WithTwoInvalidCommandsAndTwoUpArrows_VerifyInputBufferIsCorrect() { string commandOne = "longer invalid command text"; string commandTwo = "small invalid cmd"; string input = $"{commandOne}\n{commandTwo}\n"; List keys = GetConsoleKeyInfo(input); keys.Add(GetConsoleKeyInfo(ConsoleKey.UpArrow)); keys.Add(GetConsoleKeyInfo(ConsoleKey.UpArrow)); Shell shell = CreateShell(keys, out CancellationTokenSource cancellationTokenSource); await shell.RunAsync(cancellationTokenSource.Token); Assert.Equal(commandOne,shell.ShellState.InputManager.GetCurrentBuffer()); Assert.Equal(commandOne.Length, shell.ShellState.InputManager.CaretPosition); } [Fact] public async Task RunAsync_WithNullCharacters_NullCharactersIgnored() { // Arrange string command = "Command With \0 Character"; string expectedCommand = command.Replace("\0", ""); List keys = GetConsoleKeyInfo(command); Shell shell = CreateShell(keys, out CancellationTokenSource cancellationTokenSource); // Act await shell.RunAsync(cancellationTokenSource.Token); // Assert Assert.Equal(expectedCommand, shell.ShellState.InputManager.GetCurrentBuffer()); Assert.Equal(expectedCommand.Length, shell.ShellState.InputManager.CaretPosition); } [Fact] public async Task RunAsync_WithNullCharactersAndBackspaces_NullCharactersIgnored() { // Arrange string command = "Command With \0 "; string expectedCommand = "Command Wit"; List keys = GetConsoleKeyInfo(command); keys.Add(GetConsoleKeyInfo(ConsoleKey.Backspace)); keys.Add(GetConsoleKeyInfo(ConsoleKey.Backspace)); keys.Add(GetConsoleKeyInfo(ConsoleKey.Backspace)); Shell shell = CreateShell(keys, out CancellationTokenSource cancellationTokenSource); // Act await shell.RunAsync(cancellationTokenSource.Token); // Assert Assert.Equal(expectedCommand, shell.ShellState.InputManager.GetCurrentBuffer()); Assert.Equal(expectedCommand.Length, shell.ShellState.InputManager.CaretPosition); } private static Shell CreateShell(IEnumerable consoleKeyInfo, string previousCommand, string nextCommand, out CancellationTokenSource cancellationTokenSource) { DefaultCommandDispatcher defaultCommandDispatcher = DefaultCommandDispatcher.Create(x => { }, new object()); cancellationTokenSource = new CancellationTokenSource(); MockConsoleManager mockConsoleManager = new MockConsoleManager(consoleKeyInfo, cancellationTokenSource); Mock mockCommandHistory = new Mock(); mockCommandHistory.Setup(s => s.GetPreviousCommand()) .Returns(previousCommand); mockCommandHistory.Setup(s => s.GetNextCommand()) .Returns(nextCommand); ShellState shellState = new ShellState(defaultCommandDispatcher, consoleManager: mockConsoleManager, commandHistory: mockCommandHistory.Object); return new Shell(shellState); } private static Shell CreateShell(IEnumerable consoleKeyInfo, out CancellationTokenSource cancellationTokenSource) { DefaultCommandDispatcher defaultCommandDispatcher = DefaultCommandDispatcher.Create(x => { }, new object()); cancellationTokenSource = new CancellationTokenSource(); MockConsoleManager mockConsoleManager = new MockConsoleManager(consoleKeyInfo, cancellationTokenSource); ShellState shellState = new ShellState(defaultCommandDispatcher, consoleManager: mockConsoleManager); return new Shell(shellState); } private static Shell CreateShell(ConsoleKeyInfo consoleKeyInfo, string previousCommand, string nextCommand, out CancellationTokenSource cancellationTokenSource) { return CreateShell(new ConsoleKeyInfo[] { consoleKeyInfo }, previousCommand, nextCommand, out cancellationTokenSource); } /// /// Converts a string into the series of ConsoleKeyInfo instances that would be used /// to type the string in the console. /// private static List GetConsoleKeyInfo(string text) { List keys = new(); text = text.Replace("\r\n", "\n"); foreach (char c in text) { ConsoleKeyInfo consoleKeyInfo = GetConsoleKeyInfo(c); keys.Add(consoleKeyInfo); } return keys; } /// /// Builds a ConsoleKeyInfo object with the specified console key, the null keyChar ('\0') and no modifiers. /// Intended for non-printable keystrokes. /// private static ConsoleKeyInfo GetConsoleKeyInfo(ConsoleKey key) => new('\0', key, shift: false, alt: false, control: false); /// /// Builds a ConsoleKeyInfo that could produce the specified character /// private static ConsoleKeyInfo GetConsoleKeyInfo(char keyChar) { return keyChar switch { >= 'a' and <= 'z' => new(keyChar: keyChar, key: (ConsoleKey)(keyChar - 32), shift: false, alt: false, control: false), >= 'A' and <= 'Z' => new(keyChar: keyChar, key: (ConsoleKey)keyChar, shift: true, alt: false, control: false), >= '0' and <= '9' or ' ' => new(keyChar: keyChar, key: (ConsoleKey)keyChar, shift: false, alt: false, control: false), ':' => new(keyChar: keyChar, key: ConsoleKey.Oem1, shift: true, alt: false, control: false), '/' => new(keyChar: keyChar, key: ConsoleKey.Oem2, shift: false, alt: false, control: false), '"' => new(keyChar: keyChar, key: ConsoleKey.Oem7, shift: true, alt: false, control: false), '\n' => new(keyChar: '\r', key: ConsoleKey.Enter, shift: false, alt: false, control: false), '\0' => new(keyChar: keyChar, key: ConsoleKey.Oem102, shift: false, alt: false, control: false), _ => throw new InvalidOperationException($"Test setup does not support '{keyChar}' yet."), }; } } }