diff --git a/.github/workflows/lint-ext-azure-logicappsstandard.yml b/.github/workflows/lint-ext-azure-logicappsstandard.yml new file mode 100644 index 00000000000..57f8dd4dabb --- /dev/null +++ b/.github/workflows/lint-ext-azure-logicappsstandard.yml @@ -0,0 +1,22 @@ +name: ext-azure-logicappsstandard-ci + +on: + pull_request: + paths: + - "cli/azd/extensions/azure.logicappsstandard/**" + - ".github/workflows/lint-ext-azure-logicappsstandard.yml" + branches: [main] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: true + +permissions: + contents: read + pull-requests: write + +jobs: + lint: + uses: ./.github/workflows/lint-go.yml + with: + working-directory: cli/azd/extensions/azure.logicappsstandard diff --git a/cli/azd/extensions/azure.logicappsstandard/.golangci.yaml b/cli/azd/extensions/azure.logicappsstandard/.golangci.yaml new file mode 100644 index 00000000000..b88a74c6a0b --- /dev/null +++ b/cli/azd/extensions/azure.logicappsstandard/.golangci.yaml @@ -0,0 +1,17 @@ +version: "2" + +linters: + default: none + enable: + - gosec + - lll + - unused + - errorlint + settings: + lll: + line-length: 220 + tab-width: 4 + +formatters: + enable: + - gofmt diff --git a/cli/azd/extensions/azure.logicappsstandard/CHANGELOG.md b/cli/azd/extensions/azure.logicappsstandard/CHANGELOG.md new file mode 100644 index 00000000000..f942c9989b3 --- /dev/null +++ b/cli/azd/extensions/azure.logicappsstandard/CHANGELOG.md @@ -0,0 +1,5 @@ +# Release History + +## 0.0.1 + +- Initial release with support for packaging Logic App Standard projects via `language: logicappsstandard` in `azure.yaml`, and optional custom code projects using `customCodeProject` property. \ No newline at end of file diff --git a/cli/azd/extensions/azure.logicappsstandard/README.md b/cli/azd/extensions/azure.logicappsstandard/README.md new file mode 100644 index 00000000000..4c3dc0d51af --- /dev/null +++ b/cli/azd/extensions/azure.logicappsstandard/README.md @@ -0,0 +1,155 @@ +# Azure Logic Apps Standard extension + +This azd extension makes it possible to package Logic Apps Standard projects and includes support for custom code projects. + + +## Installing + +Assuming 'azd' is in your path, run the following commands to install the extension for the first time: + +```shell +azd ext install azure.logicappsstandard +``` + +Or, if you already have the `azure.logicappsstandard` extension installed, and you want to upgrade to the latest version: + +```shell +azd ext upgrade azure.logicappsstandard +``` + +## Usage + +This extension introduces the `logicappsstandard` language which can package Logic Apps Standard projects. + +For example, if your template has a Logic App Standard project with the following structure: + +``` +└── src + └── logicapp + ├── .vscode + ├── artifacts + ├── lib + ├── workflow1 + │ └── workflow.json + ├── workflow2 + │ └── workflow.json + ├── workflow-designtime + ├── .funcignore + ├── .gitignore + ├── host.json + └── local.settings.json +``` + +Use the following snippet in your `azure.yaml` file to configure the Logic App: + +```yaml +services: + logicapp: + project: ./src/logicapp + host: function + language: logicappsstandard +``` + +This will package everything under the `src/logicapp` folder in a .zip file. Because `function` is used as the host, the exclusions in `.funcignore` are respected and only the relevant files are packaged. + +The extension also supports Logic App Standard projects with a .NET 8 or .NET Framework custom code project. For example, if your template has a Logic App Standard project with custom code project following this structure: + +``` +└── src + └── logicapp + ├── Functions + │ ├── MyFunctions.cs + │ ├── Functions.csproj + │ └── ... + └── Workflows + ├── workflow1 + │ └── workflow.json + ├── workflow2 + │ └── workflow.json + ├── host.json + └── ... +``` + +You can use the following snippet in your `azure.yaml` file to configure the Logic App: + +```yaml +services: + logicapp: + project: ./src/logicapp + dist: Workflows + host: function + language: logicappsstandard + customCodeProject: Functions/Functions.csproj +``` + +This will first build the custom code project and then package the Logic App Standard artifacts. + +> [!NOTE] +> When using `customCodeProject`, make sure the required build toolchain is installed: .NET 8 SDK for .NET 8 projects, or .NET Framework/MSBuild tools for .NET Framework projects. + +- The `project` property contains the root folder of the Logic App Standard project. +- The `dist` property is the relative path to the folder with the Logic App Standard files that will be packaged. +- The `customCodeProject` property is the path to the custom code project's `.csproj` file. + + +## Troubleshooting + +### Language 'logicappsstandard' is not supported + +If you see the following error while packaging a Logic App Standard project, azd could not find an installed extension that provides the `logicappsstandard` language. Make sure to install the `azure.logicappsstandard` extension. + +``` +ERROR: initializing service '...', getting framework service: language 'logicappsstandard' is not supported by built-in framework services and no extensions are currently providing it +``` + +## Local Development + +### Prerequisites + +1. **Install developer kit extension** (if not already installed): + ```bash + azd ext install microsoft.azd.extensions + ``` + + > **Note**: If you encounter an error about the extension not being in the registry, verify you have the default source configured: + > ```bash + > azd ext source list + > ``` + > If missing, add it: + > ```bash + > azd ext source add -n azd -t url -l "https://aka.ms/azd/extensions/registry" + > ``` + +### Building and Installing + +1. **Navigate to the extension directory**: + ```bash + cd cli/azd/extensions/azure.logicappsstandard + ``` + +2. **Initial setup** (first time only): + ```bash + azd x build + azd x pack + azd x publish + ``` + +3. **Install the extension**: + ```bash + azd ext install azure.logicappsstandard + ``` + +4. **For subsequent development** (after initial setup): + ```bash + azd x watch + ``` + This automatically watches for file changes, rebuilds, and installs updates locally. + + Or for manual builds: + ```bash + azd x build + ``` + This builds and automatically installs the updated extension. + +> [!NOTE] +> The `pack` and `publish` steps are only required for the first time setup. For ongoing development, `azd x watch` or `azd x build` handles all updates automatically. diff --git a/cli/azd/extensions/azure.logicappsstandard/build.ps1 b/cli/azd/extensions/azure.logicappsstandard/build.ps1 new file mode 100644 index 00000000000..7e3727a9671 --- /dev/null +++ b/cli/azd/extensions/azure.logicappsstandard/build.ps1 @@ -0,0 +1,78 @@ +# Ensure script fails on any error +$ErrorActionPreference = 'Stop' + +# Get the directory of the script +$EXTENSION_DIR = Split-Path -Parent $MyInvocation.MyCommand.Path + +# Change to the script directory +Set-Location -Path $EXTENSION_DIR + +# Create a safe version of EXTENSION_ID replacing dots with dashes +$EXTENSION_ID_SAFE = $env:EXTENSION_ID -replace '\.', '-' + +# Define output directory +$OUTPUT_DIR = if ($env:OUTPUT_DIR) { $env:OUTPUT_DIR } else { Join-Path $EXTENSION_DIR "bin" } + +# Create output directory if it doesn't exist +if (-not (Test-Path -Path $OUTPUT_DIR)) { + New-Item -ItemType Directory -Path $OUTPUT_DIR | Out-Null +} + +# Get Git commit hash and build date +$COMMIT = git rev-parse HEAD +if ($LASTEXITCODE -ne 0) { + Write-Host "Error: Failed to get git commit hash" + exit 1 +} +$BUILD_DATE = (Get-Date -Format "yyyy-MM-ddTHH:mm:ssZ") + +# List of OS and architecture combinations +if ($env:EXTENSION_PLATFORM) { + $PLATFORMS = @($env:EXTENSION_PLATFORM) +} +else { + $PLATFORMS = @( + "windows/amd64", + "windows/arm64", + "darwin/amd64", + "darwin/arm64", + "linux/amd64", + "linux/arm64" + ) +} + +$VERSION_PATH = "azurelogicappsstandard/internal/version" + +# Loop through platforms and build +foreach ($PLATFORM in $PLATFORMS) { + $OS, $ARCH = $PLATFORM -split '/' + + $OUTPUT_NAME = Join-Path $OUTPUT_DIR "$EXTENSION_ID_SAFE-$OS-$ARCH" + + if ($OS -eq "windows") { + $OUTPUT_NAME += ".exe" + } + + Write-Host "Building for $OS/$ARCH..." + + # Delete the output file if it already exists + if (Test-Path -Path $OUTPUT_NAME) { + Remove-Item -Path $OUTPUT_NAME -Force + } + + # Set environment variables for Go build + $env:GOOS = $OS + $env:GOARCH = $ARCH + + go build ` + -ldflags="-X '$VERSION_PATH.Version=$env:EXTENSION_VERSION' -X '$VERSION_PATH.Commit=$COMMIT' -X '$VERSION_PATH.BuildDate=$BUILD_DATE'" ` + -o $OUTPUT_NAME + + if ($LASTEXITCODE -ne 0) { + Write-Host "An error occurred while building for $OS/$ARCH" + exit 1 + } +} + +Write-Host "Build completed successfully!" +Write-Host "Binaries are located in the $OUTPUT_DIR directory." diff --git a/cli/azd/extensions/azure.logicappsstandard/build.sh b/cli/azd/extensions/azure.logicappsstandard/build.sh new file mode 100644 index 00000000000..5e48da51567 --- /dev/null +++ b/cli/azd/extensions/azure.logicappsstandard/build.sh @@ -0,0 +1,66 @@ +#!/bin/bash + +# Get the directory of the script +EXTENSION_DIR="$(cd "$(dirname "$0")" && pwd)" + +# Change to the script directory +cd "$EXTENSION_DIR" || exit + +# Create a safe version of EXTENSION_ID replacing dots with dashes +EXTENSION_ID_SAFE="${EXTENSION_ID//./-}" + +# Define output directory +OUTPUT_DIR="${OUTPUT_DIR:-$EXTENSION_DIR/bin}" + +# Create output and target directories if they don't exist +mkdir -p "$OUTPUT_DIR" + +# Get Git commit hash and build date +COMMIT=$(git rev-parse HEAD) +BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) + +# List of OS and architecture combinations +if [ -n "$EXTENSION_PLATFORM" ]; then + PLATFORMS=("$EXTENSION_PLATFORM") +else + PLATFORMS=( + "windows/amd64" + "windows/arm64" + "darwin/amd64" + "darwin/arm64" + "linux/amd64" + "linux/arm64" + ) +fi + +VERSION_PATH="azurelogicappsstandard/internal/version" + +# Loop through platforms and build +for PLATFORM in "${PLATFORMS[@]}"; do + OS=$(echo "$PLATFORM" | cut -d'/' -f1) + ARCH=$(echo "$PLATFORM" | cut -d'/' -f2) + + OUTPUT_NAME="$OUTPUT_DIR/$EXTENSION_ID_SAFE-$OS-$ARCH" + + if [ "$OS" = "windows" ]; then + OUTPUT_NAME+='.exe' + fi + + echo "Building for $OS/$ARCH..." + + # Delete the output file if it already exists + [ -f "$OUTPUT_NAME" ] && rm -f "$OUTPUT_NAME" + + # Set environment variables for Go build + GOOS=$OS GOARCH=$ARCH go build \ + -ldflags="-X '$VERSION_PATH.Version=$EXTENSION_VERSION' -X '$VERSION_PATH.Commit=$COMMIT' -X '$VERSION_PATH.BuildDate=$BUILD_DATE'" \ + -o "$OUTPUT_NAME" + + if [ $? -ne 0 ]; then + echo "An error occurred while building for $OS/$ARCH" + exit 1 + fi +done + +echo "Build completed successfully!" +echo "Binaries are located in the $OUTPUT_DIR directory." diff --git a/cli/azd/extensions/azure.logicappsstandard/ci-build.ps1 b/cli/azd/extensions/azure.logicappsstandard/ci-build.ps1 new file mode 100644 index 00000000000..8d5753bb803 --- /dev/null +++ b/cli/azd/extensions/azure.logicappsstandard/ci-build.ps1 @@ -0,0 +1,142 @@ +param( + [string] $Version = (Get-Content "$PSScriptRoot/version.txt"), + [string] $SourceVersion = (git rev-parse HEAD), + [switch] $CodeCoverageEnabled, + [switch] $BuildRecordMode, + [string] $MSYS2Shell, # path to msys2_shell.cmd + [string] $OutputFileName +) + +# Remove any previously built binaries +go clean + +if ($LASTEXITCODE) { + Write-Host "Error running go clean" + exit $LASTEXITCODE +} + +# Run `go help build` to obtain detailed information about `go build` flags. +$buildFlags = @( + # remove all file system paths from the resulting executable. + # Instead of absolute file system paths, the recorded file names + # will begin either a module path@version (when using modules), + # or a plain import path (when using the standard library, or GOPATH). + "-trimpath", + + # Use buildmode=pie (Position Independent Executable) for enhanced security across platforms + # against memory corruption exploits across all major platforms. + # + # On Windows, the -buildmode=pie flag enables Address Space Layout + # Randomization (ASLR) and automatically sets DYNAMICBASE and HIGH-ENTROPY-VA flags in the PE header. + "-buildmode=pie" +) + +if ($CodeCoverageEnabled) { + $buildFlags += "-cover" +} + +# Build constraint tags +# cfi: Enable Control Flow Integrity (CFI), +# cfg: Enable Control Flow Guard (CFG), +# osusergo: Optimize for OS user accounts +$tagsFlag = "-tags=cfi,cfg,osusergo" + +# ld linker flags +# -s: Omit symbol table and debug information +# -w: Omit DWARF symbol table +# -X: Set variable at link time. Used to set the version in source. + +$ldFlag = "-ldflags=-s -w -X 'azurelogicappsstandard/internal/version.Version=$Version' -X 'azurelogicappsstandard/internal/version.Commit=$SourceVersion' -X 'azurelogicappsstandard/internal/version.BuildDate=$(Get-Date -Format o)' " + +if ($IsWindows) { + $msg = "Building for Windows" + Write-Host $msg +} +elseif ($IsLinux) { + Write-Host "Building for linux" +} +elseif ($IsMacOS) { + Write-Host "Building for macOS" +} + +# Add output file flag based on specified output file name +$outputFlag = "-o=$OutputFileName" + +# collect flags +$buildFlags += @( + $tagsFlag, + $ldFlag, + $outputFlag +) + +function PrintFlags() { + param( + [string] $flags + ) + + # Attempt to format flags so that they are easily copy-pastable to be ran inside pwsh + $i = 0 + foreach ($buildFlag in $buildFlags) { + # If the flag has a value, wrap it in quotes. This is not required when invoking directly below, + # but when repasted into a shell for execution, the quotes can help escape special characters such as ','. + $argWithValue = $buildFlag.Split('=', 2) + if ($argWithValue.Length -eq 2 -and !$argWithValue[1].StartsWith("`"")) { + $buildFlag = "$($argWithValue[0])=`"$($argWithValue[1])`"" + } + + # Write each flag on a newline with '`' acting as the multiline separator + if ($i -eq $buildFlags.Length - 1) { + Write-Host " $buildFlag" + } + else { + Write-Host " $buildFlag ``" + } + $i++ + } +} + +$oldGOEXPERIMENT = $env:GOEXPERIMENT +# Enable the loopvar experiment, which makes the loop variable for go loops like `range` behave as most folks would expect. +# the go team is exploring making this default in the future, and we'd like to opt into the behavior now. +$env:GOEXPERIMENT = "loopvar" + +try { + Write-Host "Running: go build ``" + PrintFlags -flags $buildFlags + go build @buildFlags + if ($LASTEXITCODE) { + Write-Host "Error running go build" + exit $LASTEXITCODE + } + + if ($BuildRecordMode) { + # Modify build tags to include record + $recordTagPatched = $false + for ($i = 0; $i -lt $buildFlags.Length; $i++) { + if ($buildFlags[$i].StartsWith("-tags=")) { + $buildFlags[$i] += ",record" + $recordTagPatched = $true + } + } + if (-not $recordTagPatched) { + $buildFlags += "-tags=record" + } + # Add output file flag for record mode + $recordOutput = "-o=$OutputFileName-record" + if ($IsWindows) { $recordOutput += ".exe" } + $buildFlags += $recordOutput + + Write-Host "Running: go build (record) ``" + PrintFlags -flags $buildFlags + go build @buildFlags + if ($LASTEXITCODE) { + Write-Host "Error running go build (record)" + exit $LASTEXITCODE + } + } + + Write-Host "go build succeeded" +} +finally { + $env:GOEXPERIMENT = $oldGOEXPERIMENT +} \ No newline at end of file diff --git a/cli/azd/extensions/azure.logicappsstandard/ci-test.ps1 b/cli/azd/extensions/azure.logicappsstandard/ci-test.ps1 new file mode 100644 index 00000000000..cfb399136f3 --- /dev/null +++ b/cli/azd/extensions/azure.logicappsstandard/ci-test.ps1 @@ -0,0 +1,27 @@ +$gopath = go env GOPATH +$gotestsumBinary = "gotestsum" +if ($IsWindows) { + $gotestsumBinary += ".exe" +} +$gotestsum = Join-Path $gopath "bin" $gotestsumBinary + +Write-Host "Running unit tests..." + +if (Test-Path $gotestsum) { + # Use gotestsum for better output formatting and summary + & $gotestsum --format testname -- ./... -count=1 +} else { + # Fallback to go test if gotestsum is not installed + Write-Host "gotestsum not found, using go test..." -ForegroundColor Yellow + go test ./... -v -count=1 +} + +if ($LASTEXITCODE -ne 0) { + Write-Host "" + Write-Host "Tests failed with exit code: $LASTEXITCODE" -ForegroundColor Red + exit $LASTEXITCODE +} + +Write-Host "" +Write-Host "All tests passed!" -ForegroundColor Green +exit 0 \ No newline at end of file diff --git a/cli/azd/extensions/azure.logicappsstandard/cspell.yaml b/cli/azd/extensions/azure.logicappsstandard/cspell.yaml new file mode 100644 index 00000000000..4d8530cabe7 --- /dev/null +++ b/cli/azd/extensions/azure.logicappsstandard/cspell.yaml @@ -0,0 +1,7 @@ +import: ../../.vscode/cspell.yaml +words: + - azurelogicappsstandard + - designtime + - logicapp + - logicappsstandard + \ No newline at end of file diff --git a/cli/azd/extensions/azure.logicappsstandard/extension.yaml b/cli/azd/extensions/azure.logicappsstandard/extension.yaml new file mode 100644 index 00000000000..f6663a3210c --- /dev/null +++ b/cli/azd/extensions/azure.logicappsstandard/extension.yaml @@ -0,0 +1,11 @@ +# yaml-language-server: $schema=../extension.schema.json +id: azure.logicappsstandard +namespace: logicappsstandard +displayName: Azure Logic Apps Standard +description: Extension for packaging Logic Apps Standard projects, including support for custom code projects +usage: Use logicappsstandard as language in your service configuration. If your Logic App has a custom code project, configure that path to the .csproj file using the customCodeProject property. +# NOTE: Make sure version.txt is in sync with this version. +version: 0.0.1 +language: go +capabilities: + - framework-service-provider \ No newline at end of file diff --git a/cli/azd/extensions/azure.logicappsstandard/go.mod b/cli/azd/extensions/azure.logicappsstandard/go.mod new file mode 100644 index 00000000000..d6c8ffb1842 --- /dev/null +++ b/cli/azd/extensions/azure.logicappsstandard/go.mod @@ -0,0 +1,104 @@ +module azurelogicappsstandard + +go 1.26.1 + +require ( + github.com/azure/azure-dev/cli/azd v1.25.1 + github.com/spf13/cobra v1.10.2 + github.com/stretchr/testify v1.11.1 + google.golang.org/protobuf v1.36.11 +) + +require ( + github.com/AlecAivazis/survey/v2 v2.3.7 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault v1.5.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions v1.3.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.7.2 // indirect + github.com/Masterminds/semver/v3 v3.5.0 // indirect + github.com/adam-lavrik/go-imath v0.0.0-20210910152346-265a42a96f0b // indirect + github.com/alecthomas/chroma/v2 v2.24.1 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/aymerick/douceur v0.2.0 // indirect + github.com/bahlo/generic-list-go v0.2.0 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/braydonk/yaml v0.9.0 // indirect + github.com/buger/goterm v1.0.4 // indirect + github.com/buger/jsonparser v1.2.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/charmbracelet/colorprofile v0.4.3 // indirect + github.com/charmbracelet/glamour v1.0.0 // indirect + github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 // indirect + github.com/charmbracelet/x/ansi v0.11.7 // indirect + github.com/charmbracelet/x/cellbuf v0.0.15 // indirect + github.com/charmbracelet/x/exp/slice v0.0.0-20260519012233-798e623c8447 // indirect + github.com/charmbracelet/x/term v0.2.2 // indirect + github.com/cli/browser v1.3.0 // indirect + github.com/clipperhouse/displaywidth v0.11.0 // indirect + github.com/clipperhouse/uax29/v2 v2.7.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/dlclark/regexp2 v1.12.0 // indirect + github.com/drone/envsubst v1.0.3 // indirect + github.com/fatih/color v1.19.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/gofrs/flock v0.13.0 // indirect + github.com/golang-jwt/jwt/v5 v5.3.1 // indirect + github.com/golobby/container/v3 v3.3.2 // indirect + github.com/google/jsonschema-go v0.4.3 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/css v1.0.1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/invopop/jsonschema v0.14.0 // indirect + github.com/jmespath-community/go-jmespath v1.1.1 // indirect + github.com/joho/godotenv v1.5.1 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/lucasb-eyer/go-colorful v1.4.0 // indirect + github.com/mark3labs/mcp-go v0.54.0 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.22 // indirect + github.com/mattn/go-runewidth v0.0.23 // indirect + github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect + github.com/microcosm-cc/bluemonday v1.0.27 // indirect + github.com/microsoft/ApplicationInsights-Go v0.4.4 // indirect + github.com/microsoft/go-deviceid v1.0.0 // indirect + github.com/muesli/reflow v0.3.0 // indirect + github.com/muesli/termenv v0.16.0 // indirect + github.com/nathan-fiscaletti/consolesize-go v0.0.0-20260406063853-3bac975de715 // indirect + github.com/pb33f/ordered-map/v2 v2.3.1 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect + github.com/sethvargo/go-retry v0.3.0 // indirect + github.com/spf13/cast v1.10.0 // indirect + github.com/spf13/pflag v1.0.10 // indirect + github.com/theckman/yacspin v0.13.12 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + github.com/yosida95/uritemplate/v3 v3.0.2 // indirect + github.com/yuin/goldmark v1.8.2 // indirect + github.com/yuin/goldmark-emoji v1.0.6 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/otel v1.43.0 // indirect + go.opentelemetry.io/otel/metric v1.43.0 // indirect + go.opentelemetry.io/otel/sdk v1.43.0 // indirect + go.opentelemetry.io/otel/trace v1.43.0 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.yaml.in/yaml/v4 v4.0.0-rc.4 // indirect + golang.org/x/crypto v0.51.0 // indirect + golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a // indirect + golang.org/x/net v0.54.0 // indirect + golang.org/x/sys v0.44.0 // indirect + golang.org/x/term v0.43.0 // indirect + golang.org/x/text v0.37.0 // indirect + golang.org/x/time v0.15.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260519071638-aa98bba5eb94 // indirect + google.golang.org/grpc v1.81.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/cli/azd/extensions/azure.logicappsstandard/go.sum b/cli/azd/extensions/azure.logicappsstandard/go.sum new file mode 100644 index 00000000000..f6b13b080f8 --- /dev/null +++ b/cli/azd/extensions/azure.logicappsstandard/go.sum @@ -0,0 +1,314 @@ +code.cloudfoundry.org/clock v0.0.0-20180518195852-02e53af36e6c/go.mod h1:QD9Lzhd/ux6eNQVUDVRJX/RKTigpewimNYBi7ivZKY8= +github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= +github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1 h1:jHb/wfvRikGdxMXYV3QG/SzUOPYN9KEUUuC0Yd0/vC0= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1/go.mod h1:pzBXCYn05zvYIrwLgtK8Ap8QcjRg+0i76tMQdWN6wOk= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 h1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0/go.mod h1:7dCRMLwisfRH3dBupKeNCioWYUZ4SS09Z14H+7i8ZoY= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v2 v2.0.0 h1:PTFGRSlMKCQelWwxUyYVEUqseBJVemLyqWJjvMyt0do= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v2 v2.0.0/go.mod h1:LRr2FzBTQlONPPa5HREE5+RjSCTXl7BwOvYOaWTqCaI= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.1.0 h1:2qsIIvxVT+uE6yrNldntJKlLRgxGbZ85kgtz5SNBhMw= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.1.0/go.mod h1:AW8VEadnhw9xox+VaVd9sP7NjzOAnaZBLRH6Tq3cJ38= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault v1.5.0 h1:nnQ9vXH039UrEFxi08pPuZBE7VfqSJt343uJLw0rhWI= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault v1.5.0/go.mod h1:4YIVtzMFVsPwBvitCDX7J9sqthSj43QD1sP6fYc1egc= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0 h1:Dd+RhdJn0OTtVGaeDLZpcumkIVCtA/3/Fo42+eoYvVM= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0/go.mod h1:5kakwfW5CjC9KK+Q4wjXAg+ShuIm2mBMua0ZFj2C8PE= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions v1.3.0 h1:wxQx2Bt4xzPIKvW59WQf1tJNx/ZZKPfN+EhPX3Z6CYY= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions v1.3.0/go.mod h1:TpiwjwnW/khS0LKs4vW5UmmT9OWcxaveS8U7+tlknzo= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 h1:/g8S6wk65vfC6m3FIxJ+i5QDyN9JWwXI8Hb0Img10hU= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0/go.mod h1:gpl+q95AzZlKVI3xSoseF9QPrypk0hQqBiJYeB/cR/I= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 h1:nCYfgcSyHZXJI8J0IWE5MsCGlb2xp9fJiXyxWgmOFg4= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0/go.mod h1:ucUjca2JtSZboY8IoUqyQyuuXvwbMBVwFOm0vdQPNhA= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= +github.com/AzureAD/microsoft-authentication-library-for-go v1.7.2 h1:RHK7bS+HQMslb1sZpAokUt+zTVmue0hKSs2C791hhzU= +github.com/AzureAD/microsoft-authentication-library-for-go v1.7.2/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= +github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE= +github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= +github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= +github.com/adam-lavrik/go-imath v0.0.0-20210910152346-265a42a96f0b h1:g9SuFmxM/WucQFKTMSP+irxyf5m0RiUJreBDhGI6jSA= +github.com/adam-lavrik/go-imath v0.0.0-20210910152346-265a42a96f0b/go.mod h1:XjvqMUpGd3Xn9Jtzk/4GEBCSoBX0eB2RyriXgne0IdM= +github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= +github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/chroma/v2 v2.24.1 h1:m5ffpfZbIb++k8AqFEKy9uVgY12xIQtBsQlc6DfZJQM= +github.com/alecthomas/chroma/v2 v2.24.1/go.mod h1:l+ohZ9xRXIbGe7cIW+YZgOGbvuVLjMps/FYN/CwuabI= +github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs= +github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= +github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= +github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= +github.com/azure/azure-dev/cli/azd v1.25.1 h1:FLU+KVGwQBmSwADN8GuKQvYdFLpclc8SX6c4md+5b0Y= +github.com/azure/azure-dev/cli/azd v1.25.1/go.mod h1:Q8S2cdn6ArpzaHTjwRFLbGFgS6rU09rOF7k5gsBTTso= +github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= +github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= +github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= +github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= +github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= +github.com/braydonk/yaml v0.9.0 h1:ewGMrVmEVpsm3VwXQDR388sLg5+aQ8Yihp6/hc4m+h4= +github.com/braydonk/yaml v0.9.0/go.mod h1:hcm3h581tudlirk8XEUPDBAimBPbmnL0Y45hCRl47N4= +github.com/buger/goterm v1.0.4 h1:Z9YvGmOih81P0FbVtEYTFF6YsSgxSUKEhf/f9bTMXbY= +github.com/buger/goterm v1.0.4/go.mod h1:HiFWV3xnkolgrBV3mY8m0X0Pumt4zg4QhbdOzQtB8tE= +github.com/buger/jsonparser v1.2.0 h1:4EFcvK1kD4jyj6YqNK6skK6w+y7FHHBR+XBCtxwu/6g= +github.com/buger/jsonparser v1.2.0/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q= +github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q= +github.com/charmbracelet/glamour v1.0.0 h1:AWMLOVFHTsysl4WV8T8QgkQ0s/ZNZo7CiE4WKhk8l08= +github.com/charmbracelet/glamour v1.0.0/go.mod h1:DSdohgOBkMr2ZQNhw4LZxSGpx3SvpeujNoXrQyH2hxo= +github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE= +github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA= +github.com/charmbracelet/x/ansi v0.11.7 h1:kzv1kJvjg2S3r9KHo8hDdHFQLEqn4RBCb39dAYC84jI= +github.com/charmbracelet/x/ansi v0.11.7/go.mod h1:9qGpnAVYz+8ACONkZBUWPtL7lulP9No6p1epAihUZwQ= +github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI= +github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q= +github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30= +github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= +github.com/charmbracelet/x/exp/slice v0.0.0-20260519012233-798e623c8447 h1:dZNZoFaaoQYXmtAOz4ovm0kISwcKX5Xt29ZLqhNYQKc= +github.com/charmbracelet/x/exp/slice v0.0.0-20260519012233-798e623c8447/go.mod h1:vqEfX6xzqW1pKKZUUiFOKg0OQ7bCh54Q2vR/tserrRA= +github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= +github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= +github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo= +github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk= +github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8= +github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0= +github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= +github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI= +github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dlclark/regexp2 v1.12.0 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz8= +github.com/dlclark/regexp2 v1.12.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g= +github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g= +github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= +github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= +github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0= +github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golobby/container/v3 v3.3.2 h1:7u+RgNnsdVlhGoS8gY4EXAG601vpMMzLZlYqSp77Quw= +github.com/golobby/container/v3 v3.3.2/go.mod h1:RDdKpnKpV1Of11PFBe7Dxc2C1k2KaLE4FD47FflAmj0= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/jsonschema-go v0.4.3 h1:/DBOLZTfDow7pe2GmaJNhltueGTtDKICi8V8p+DQPd0= +github.com/google/jsonschema-go v0.4.3/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= +github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= +github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/invopop/jsonschema v0.14.0 h1:MHQqLhvpNUZfw+hM3AZDYK7jxO8FZoQeQM77g8iyZjg= +github.com/invopop/jsonschema v0.14.0/go.mod h1:ygm6C2EaVNMBDPpaPlnOA2pFAxBnxGjFlMZABxm9n2I= +github.com/jmespath-community/go-jmespath v1.1.1 h1:bFikPhsi/FdmlZhVgSCd2jj1e7G/rw+zyQfyg5UF+L4= +github.com/jmespath-community/go-jmespath v1.1.1/go.mod h1:4gOyFJsR/Gk+05RgTKYrifT7tBPWD8Lubtb5jRrfy9I= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= +github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4= +github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mark3labs/mcp-go v0.54.0 h1:PZhQvd+5xrT43cUoiaKn/hDcvLUhcLc1twSEKYPTcTA= +github.com/mark3labs/mcp-go v0.54.0/go.mod h1:+8WclSK1ZUweCP3hvktSji8n8ABG/95QaEkeVE/Uwas= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4= +github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw= +github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= +github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= +github.com/microsoft/ApplicationInsights-Go v0.4.4 h1:G4+H9WNs6ygSCe6sUyxRc2U81TI5Es90b2t/MwX5KqY= +github.com/microsoft/ApplicationInsights-Go v0.4.4/go.mod h1:fKRUseBqkw6bDiXTs3ESTiU/4YTIHsQS4W3fP2ieF4U= +github.com/microsoft/go-deviceid v1.0.0 h1:i5AQ654Xk9kfvwJeKQm3w2+eT1+ImBDVEpAR0AjpP40= +github.com/microsoft/go-deviceid v1.0.0/go.mod h1:KY13FeVdHkzD8gy+6T8+kVmD/7RMpTaWW75K+T4uZWg= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= +github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= +github.com/nathan-fiscaletti/consolesize-go v0.0.0-20260406063853-3bac975de715 h1:FiSC3sz/k2SYB32YJ1bLlBVB7Li0N7n6jcYa9Knq/60= +github.com/nathan-fiscaletti/consolesize-go v0.0.0-20260406063853-3bac975de715/go.mod h1:DQpEqvLUU3b35AJUVu5P6LFn/RY3qj4WtMjvrcE8gto= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pb33f/ordered-map/v2 v2.3.1 h1:5319HDO0aw4DA4gzi+zv4FXU9UlSs3xGZ40wcP1nBjY= +github.com/pb33f/ordered-map/v2 v2.3.1/go.mod h1:qxFQgd0PkVUtOMCkTapqotNgzRhMPL7VvaHKbd1HnmQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= +github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= +github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tedsuo/ifrit v0.0.0-20180802180643-bea94bb476cc/go.mod h1:eyZnKCc955uh98WQvzOm0dgAeLnf2O0Rz0LPoC5ze+0= +github.com/theckman/yacspin v0.13.12 h1:CdZ57+n0U6JMuh2xqjnjRq5Haj6v1ner2djtLQRzJr4= +github.com/theckman/yacspin v0.13.12/go.mod h1:Rd2+oG2LmQi5f3zC3yeZAOl245z8QOvrH4OPOJNZxLg= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= +github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE= +github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= +github.com/yuin/goldmark-emoji v1.0.6 h1:QWfF2FYaXwL74tfGOW5izeiZepUDroDJfWubQI9HTHs= +github.com/yuin/goldmark-emoji v1.0.6/go.mod h1:ukxJDKFpdFb5x0a5HqbdlcKtebh086iJpI31LTKmWuA= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +go.yaml.in/yaml/v4 v4.0.0-rc.4 h1:UP4+v6fFrBIb1l934bDl//mmnoIZEDK0idg1+AIvX5U= +go.yaml.in/yaml/v4 v4.0.0-rc.4/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= +golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= +golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a h1:+3jdDGGB8NGb1Zktc737jlt3/A5f6UlwSzmvqUuufxw= +golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= +golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= +golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= +golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260519071638-aa98bba5eb94 h1:eZCjr/aAF8c5ccm5pb6T4EXgIei5MlAAPWPJk+5ArfY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260519071638-aa98bba5eb94/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= +google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/cli/azd/extensions/azure.logicappsstandard/internal/cmd/listen.go b/cli/azd/extensions/azure.logicappsstandard/internal/cmd/listen.go new file mode 100644 index 00000000000..cf37704f18f --- /dev/null +++ b/cli/azd/extensions/azure.logicappsstandard/internal/cmd/listen.go @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package cmd + +import ( + "github.com/azure/azure-dev/cli/azd/pkg/azdext" + + "azurelogicappsstandard/internal/project" +) + +// configureListen is called by NewListenCommand to register event handlers. +func configureListen(host *azdext.ExtensionHost) { + host.WithFrameworkService("logicappsstandard", func() azdext.FrameworkServiceProvider { + return project.NewLogicAppsStandardFrameworkServiceProvider() + }) +} diff --git a/cli/azd/extensions/azure.logicappsstandard/internal/cmd/root.go b/cli/azd/extensions/azure.logicappsstandard/internal/cmd/root.go new file mode 100644 index 00000000000..f6b1120a3be --- /dev/null +++ b/cli/azd/extensions/azure.logicappsstandard/internal/cmd/root.go @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package cmd + +import ( + "github.com/azure/azure-dev/cli/azd/pkg/azdext" + "github.com/spf13/cobra" + + "azurelogicappsstandard/internal/version" +) + +const ( + extensionID = "azure.logicappsstandard" +) + +// NewRootCommand creates the root command for the Logic Apps Standard extension. +func NewRootCommand() *cobra.Command { + rootCmd, extCtx := azdext.NewExtensionRootCommand(azdext.ExtensionCommandOptions{ + Name: extensionID, + Version: version.Version, + Short: "Extension for packaging Logic Apps Standard projects, including support for custom code projects", + }) + + // Standard lifecycle, metadata, and version commands + rootCmd.AddCommand(azdext.NewListenCommand(configureListen)) + rootCmd.AddCommand(azdext.NewMetadataCommand("1.0", extensionID, NewRootCommand)) + rootCmd.AddCommand(azdext.NewVersionCommand(extensionID, version.Version, &extCtx.OutputFormat)) + + return rootCmd +} diff --git a/cli/azd/extensions/azure.logicappsstandard/internal/project/framework_service_logicappsstandard.go b/cli/azd/extensions/azure.logicappsstandard/internal/project/framework_service_logicappsstandard.go new file mode 100644 index 00000000000..42275feda79 --- /dev/null +++ b/cli/azd/extensions/azure.logicappsstandard/internal/project/framework_service_logicappsstandard.go @@ -0,0 +1,237 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package project + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/azure/azure-dev/cli/azd/pkg/azdext" +) + +// Ensure LogicAppsStandardFrameworkServiceProvider implements FrameworkServiceProvider interface +var _ azdext.FrameworkServiceProvider = &LogicAppsStandardFrameworkServiceProvider{} + +// LogicAppsStandardFrameworkServiceProvider introduces the custom language 'logicappsstandard', +// which makes it possible to package Logic Apps Standard projects, including those with custom code components. +type LogicAppsStandardFrameworkServiceProvider struct { + serviceConfig *azdext.ServiceConfig +} + +// NewLogicAppsStandardFrameworkServiceProvider creates a Logic Apps Standard framework service provider. +func NewLogicAppsStandardFrameworkServiceProvider() azdext.FrameworkServiceProvider { + return &LogicAppsStandardFrameworkServiceProvider{} +} + +// Initialize initializes the framework service provider with service configuration +func (p *LogicAppsStandardFrameworkServiceProvider) Initialize( + ctx context.Context, + serviceConfig *azdext.ServiceConfig, +) error { + p.serviceConfig = serviceConfig + + if serviceConfig.Host != "function" { + return fmt.Errorf("Logic Apps Standard requires the host to be 'function', but found '%s'", serviceConfig.Host) + } + + if hasCustomCodeProjectConfigured(serviceConfig) { + csProjPath, err := p.resolveCustomCodeProjectPath(serviceConfig) + if err != nil { + return err + } + projectInfo, err := os.Stat(csProjPath) + if err != nil { + return fmt.Errorf("custom code project not found at '%s': %w", csProjPath, err) + } + if projectInfo.IsDir() { + return fmt.Errorf("custom code project path '%s' must point to a file", csProjPath) + } + } + + return nil +} + +// Returns dotnet as required external tool if a custom code project is configured +func (p *LogicAppsStandardFrameworkServiceProvider) RequiredExternalTools( + ctx context.Context, + serviceConfig *azdext.ServiceConfig, +) ([]*azdext.ExternalTool, error) { + if hasCustomCodeProjectConfigured(serviceConfig) { + return []*azdext.ExternalTool{ + { + Name: "dotnet", + InstallUrl: "https://dotnet.microsoft.com/download", + }, + }, nil + } + return nil, nil +} + +// Requirements returns the framework requirements (whether restore/build are needed) +func (p *LogicAppsStandardFrameworkServiceProvider) Requirements() (*azdext.FrameworkRequirements, error) { + hasCustomCodeProject := p.serviceConfig != nil && hasCustomCodeProjectConfigured(p.serviceConfig) + return &azdext.FrameworkRequirements{ + Package: &azdext.FrameworkPackageRequirements{ + RequireRestore: hasCustomCodeProject, + RequireBuild: hasCustomCodeProject, + }, + }, nil +} + +// Restores the dependencies for the custom code project if specified in the service configuration. +func (p *LogicAppsStandardFrameworkServiceProvider) Restore( + ctx context.Context, + serviceConfig *azdext.ServiceConfig, + serviceContext *azdext.ServiceContext, + progress azdext.ProgressReporter, +) (*azdext.ServiceRestoreResult, error) { + if hasCustomCodeProjectConfigured(serviceConfig) { + progress("Restoring .NET project dependencies") + csProjPath, err := p.resolveCustomCodeProjectPath(serviceConfig) + if err != nil { + return nil, err + } + if err := runDotNet(ctx, "restore", csProjPath); err != nil { + return nil, fmt.Errorf("restoring custom code project '%s': %w", csProjPath, err) + } + } + return &azdext.ServiceRestoreResult{}, nil +} + +// Builds the custom code project if specified in the service configuration. +func (p *LogicAppsStandardFrameworkServiceProvider) Build( + ctx context.Context, + serviceConfig *azdext.ServiceConfig, + serviceContext *azdext.ServiceContext, + progress azdext.ProgressReporter, +) (*azdext.ServiceBuildResult, error) { + if hasCustomCodeProjectConfigured(serviceConfig) { + progress("Building .NET project") + csProjPath, err := p.resolveCustomCodeProjectPath(serviceConfig) + if err != nil { + return nil, err + } + if err := runDotNet(ctx, "build", csProjPath, "--configuration", "Release"); err != nil { + return nil, fmt.Errorf("building custom code project '%s': %w", csProjPath, err) + } + } + return &azdext.ServiceBuildResult{}, nil +} + +// Packages the Logic Apps Standard project, including any custom code components, into a deployable artifact. +func (p *LogicAppsStandardFrameworkServiceProvider) Package( + ctx context.Context, + serviceConfig *azdext.ServiceConfig, + serviceContext *azdext.ServiceContext, + progress azdext.ProgressReporter, +) (*azdext.ServicePackageResult, error) { + progress("Creating Logic Apps Standard deployment package") + + projectDir, err := azdext.GetProjectDir() + if err != nil { + return nil, fmt.Errorf("getting project directory: %w", err) + } + + packageParts := []string{serviceConfig.RelativePath} + + // If an output path is specified, append it to the package path. + // This allows for subdirectories like "Workflows" to be the root of the zip. + if serviceConfig.OutputPath != "" { + packageParts = append(packageParts, serviceConfig.OutputPath) + } + + packagePath, err := resolvePathWithinBase(projectDir, packageParts...) + if err != nil { + return nil, fmt.Errorf("resolving package path: %w", err) + } + + // Return a DIRECTORY artifact pointing to the resolved package directory for the service. + // azd's packaging pipeline will handle creating the zip archive from this directory. + // By specifying an absolute path, azd will use the host specific ignore file (e.g. .funcignore) for exclusions. + return &azdext.ServicePackageResult{ + Artifacts: []*azdext.Artifact{ + { + Kind: azdext.ArtifactKind_ARTIFACT_KIND_DIRECTORY, + Location: packagePath, + LocationKind: azdext.LocationKind_LOCATION_KIND_LOCAL, + }, + }, + }, nil +} + +func (p *LogicAppsStandardFrameworkServiceProvider) resolveCustomCodeProjectPath( + serviceConfig *azdext.ServiceConfig, +) (string, error) { + projectDir, err := azdext.GetProjectDir() + if err != nil { + return "", fmt.Errorf("getting project directory: %w", err) + } + + customCodeProjectPath, err := resolvePathWithinBase( + projectDir, + serviceConfig.RelativePath, + getAdditionalProperty(serviceConfig, "customCodeProject"), + ) + if err != nil { + return "", fmt.Errorf("resolving custom code project path: %w", err) + } + + return customCodeProjectPath, nil +} + +// resolvePathWithinBase joins path parts and validates the resolved path remains under baseDir. +func resolvePathWithinBase(baseDir string, pathParts ...string) (string, error) { + baseAbs, err := filepath.Abs(baseDir) + if err != nil { + return "", fmt.Errorf("resolving base directory '%s': %w", baseDir, err) + } + + resolvedPath := filepath.Join(append([]string{baseAbs}, pathParts...)...) + resolvedAbs, err := filepath.Abs(resolvedPath) + if err != nil { + return "", fmt.Errorf("resolving path '%s': %w", resolvedPath, err) + } + + rel, err := filepath.Rel(baseAbs, resolvedAbs) + if err != nil { + return "", fmt.Errorf("getting relative path from '%s' to '%s': %w", baseAbs, resolvedAbs, err) + } + + if rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) { + return "", fmt.Errorf("path '%s' is outside project directory '%s'", resolvedAbs, baseAbs) + } + + return resolvedAbs, nil +} + +// getAdditionalProperty retrieves a custom property from the service configuration's additional properties. +func getAdditionalProperty(serviceConfig *azdext.ServiceConfig, key string) string { + if serviceConfig == nil { + return "" + } + + props := serviceConfig.GetAdditionalProperties() + if props == nil { + return "" + } + if v, ok := props.Fields[key]; ok && v != nil { + return v.GetStringValue() + } + return "" +} + +func hasCustomCodeProjectConfigured(serviceConfig *azdext.ServiceConfig) bool { + return getAdditionalProperty(serviceConfig, "customCodeProject") != "" +} + +// runDotNet executes the dotnet CLI with the given arguments, forwarding output to stdout/stderr. +func runDotNet(ctx context.Context, args ...string) error { + cmd := azdext.ExecCommand(ctx, "dotnet", args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} diff --git a/cli/azd/extensions/azure.logicappsstandard/internal/project/framework_service_logicappsstandard_test.go b/cli/azd/extensions/azure.logicappsstandard/internal/project/framework_service_logicappsstandard_test.go new file mode 100644 index 00000000000..dbdd8d04edd --- /dev/null +++ b/cli/azd/extensions/azure.logicappsstandard/internal/project/framework_service_logicappsstandard_test.go @@ -0,0 +1,413 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package project + +import ( + "os" + "path/filepath" + "runtime" + "strings" + "testing" + + "github.com/azure/azure-dev/cli/azd/pkg/azdext" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/structpb" +) + +func TestGetAdditionalProperty(t *testing.T) { + t.Run("returns empty for nil service config", func(t *testing.T) { + assert.Equal(t, "", getAdditionalProperty(nil, "customCodeProject")) + }) + + t.Run("returns empty for nil additional properties", func(t *testing.T) { + svc := &azdext.ServiceConfig{} + assert.Equal(t, "", getAdditionalProperty(svc, "customCodeProject")) + }) + + t.Run("returns property value when present", func(t *testing.T) { + svc := newServiceConfig("logicApp", "src/logicApp", map[string]any{ + "customCodeProject": "Functions/Functions.csproj", + }) + + assert.Equal(t, "Functions/Functions.csproj", getAdditionalProperty(svc, "customCodeProject")) + }) +} + +func TestHasCustomCodeProjectConfigured(t *testing.T) { + t.Run("returns false for nil service config", func(t *testing.T) { + assert.False(t, hasCustomCodeProjectConfigured(nil)) + }) + + t.Run("returns false when customCodeProject is absent", func(t *testing.T) { + assert.False(t, hasCustomCodeProjectConfigured(newServiceConfig("logicApp", "src/logicApp", nil))) + }) + + t.Run("returns true when customCodeProject is present", func(t *testing.T) { + assert.True(t, hasCustomCodeProjectConfigured(newServiceConfig("logicApp", "src/logicApp", map[string]any{ + "customCodeProject": "Functions/Functions.csproj", + }))) + }) +} + +func TestRequiredExternalTools(t *testing.T) { + provider := &LogicAppsStandardFrameworkServiceProvider{} + + t.Run("returns nil when custom code is not configured", func(t *testing.T) { + tools, err := provider.RequiredExternalTools(t.Context(), newServiceConfig("logicApp", "src/logicApp", nil)) + require.NoError(t, err) + assert.Nil(t, tools) + }) + + t.Run("returns dotnet when custom code is configured", func(t *testing.T) { + tools, err := provider.RequiredExternalTools( + t.Context(), + newServiceConfig("logicApp", "src/logicApp", map[string]any{ + "customCodeProject": "Functions/Functions.csproj", + })) + require.NoError(t, err) + require.Len(t, tools, 1) + assert.Equal(t, "dotnet", tools[0].Name) + assert.Equal(t, "https://dotnet.microsoft.com/download", tools[0].InstallUrl) + }) +} + +func TestRequirements(t *testing.T) { + t.Run("disables restore and build when service config is not initialized", func(t *testing.T) { + provider := &LogicAppsStandardFrameworkServiceProvider{} + reqs, err := provider.Requirements() + require.NoError(t, err) + require.NotNil(t, reqs.Package) + assert.False(t, reqs.Package.RequireRestore) + assert.False(t, reqs.Package.RequireBuild) + }) + + t.Run("disables restore and build when initialized without customCodeProject", func(t *testing.T) { + provider := &LogicAppsStandardFrameworkServiceProvider{} + provider.serviceConfig = newServiceConfig("logicApp", "src/logicApp", nil) + reqs, err := provider.Requirements() + require.NoError(t, err) + require.NotNil(t, reqs.Package) + assert.False(t, reqs.Package.RequireRestore) + assert.False(t, reqs.Package.RequireBuild) + }) + + t.Run("enables restore and build when customCodeProject is configured", func(t *testing.T) { + provider := &LogicAppsStandardFrameworkServiceProvider{} + provider.serviceConfig = newServiceConfig("logicApp", "src/logicApp", map[string]any{ + "customCodeProject": "Functions/Functions.csproj", + }) + reqs, err := provider.Requirements() + require.NoError(t, err) + require.NotNil(t, reqs.Package) + assert.True(t, reqs.Package.RequireRestore) + assert.True(t, reqs.Package.RequireBuild) + }) +} + +func TestInitializeValidatesCustomCodeProjectPath(t *testing.T) { + projectDir := t.TempDir() + createFile(t, filepath.Join(projectDir, "azure.yaml"), "name: test-project\n") + + t.Run("fails when host is not function", func(t *testing.T) { + provider := &LogicAppsStandardFrameworkServiceProvider{} + svc := newServiceConfig("logicApp", "src/logicApp", nil) + svc.Host = "appservice" + + err := provider.Initialize(t.Context(), svc) + require.Error(t, err) + assert.Equal(t, "Logic Apps Standard requires the host to be 'function', but found 'appservice'", err.Error()) + }) + + t.Run("succeeds without customCodeProject and sets serviceConfig", func(t *testing.T) { + provider := &LogicAppsStandardFrameworkServiceProvider{} + svc := newServiceConfig("logicApp", "src/logicApp", nil) + + err := provider.Initialize(t.Context(), svc) + require.NoError(t, err) + assert.Equal(t, svc, provider.serviceConfig) + }) + + t.Run("succeeds when custom code project file exists", func(t *testing.T) { + provider := &LogicAppsStandardFrameworkServiceProvider{} + svc := newServiceConfig("logicApp", "src/logicApp", map[string]any{ + "customCodeProject": "Functions/Functions.csproj", + }) + + createFile(t, filepath.Join(projectDir, "src/logicApp/Functions/Functions.csproj"), "\n") + + withEnv(t, "AZD_EXEC_PROJECT_DIR", projectDir, func() { + err := provider.Initialize(t.Context(), svc) + require.NoError(t, err) + }) + }) + + t.Run("fails when custom code project is a directory", func(t *testing.T) { + provider := &LogicAppsStandardFrameworkServiceProvider{} + svc := newServiceConfig("logicApp", "src/logicApp", map[string]any{ + "customCodeProject": "Functions", + }) + + err := os.MkdirAll(filepath.Join(projectDir, "src/logicApp/Functions"), 0o750) + require.NoError(t, err) + + withEnv(t, "AZD_EXEC_PROJECT_DIR", projectDir, func() { + err := provider.Initialize(t.Context(), svc) + require.Error(t, err) + assert.Contains(t, err.Error(), "must point to a file") + }) + }) + + t.Run("fails when custom code project file is missing", func(t *testing.T) { + provider := &LogicAppsStandardFrameworkServiceProvider{} + svc := newServiceConfig("logicApp", "src/logicApp", map[string]any{ + "customCodeProject": "Functions/Missing.csproj", + }) + + withEnv(t, "AZD_EXEC_PROJECT_DIR", projectDir, func() { + err := provider.Initialize(t.Context(), svc) + require.Error(t, err) + assert.Contains(t, err.Error(), "custom code project not found") + }) + }) + + t.Run("fails when custom code project escapes project directory", func(t *testing.T) { + provider := &LogicAppsStandardFrameworkServiceProvider{} + svc := newServiceConfig("logicApp", "src/logicApp", map[string]any{ + "customCodeProject": "../../../outside.csproj", + }) + + withEnv(t, "AZD_EXEC_PROJECT_DIR", projectDir, func() { + err := provider.Initialize(t.Context(), svc) + require.Error(t, err) + assert.Contains(t, err.Error(), "outside project directory") + }) + }) + + t.Run("fails when custom code project path is invalid", func(t *testing.T) { + provider := &LogicAppsStandardFrameworkServiceProvider{} + svc := newServiceConfig("logicApp", "src/logicApp", map[string]any{ + "customCodeProject": "Functions/Bad\x00Name.csproj", + }) + + withEnv(t, "AZD_EXEC_PROJECT_DIR", projectDir, func() { + err := provider.Initialize(t.Context(), svc) + require.Error(t, err) + assert.Contains(t, err.Error(), "custom code project") + }) + }) +} + +func TestPackageUsesProjectAndOutputPaths(t *testing.T) { + provider := &LogicAppsStandardFrameworkServiceProvider{} + projectDir := t.TempDir() + createFile(t, filepath.Join(projectDir, "azure.yaml"), "name: test-project\n") + + withEnv(t, "AZD_EXEC_PROJECT_DIR", projectDir, func() { + result, err := provider.Package(t.Context(), newServiceConfig("logicApp", "src/logicApp", nil), nil, func(string) {}) + require.NoError(t, err) + require.Len(t, result.Artifacts, 1) + + artifact := result.Artifacts[0] + expectedPath := filepath.Join(projectDir, "src/logicApp") + assert.Equal(t, expectedPath, artifact.Location) + assert.Equal(t, azdext.ArtifactKind_ARTIFACT_KIND_DIRECTORY, artifact.Kind) + assert.Equal(t, azdext.LocationKind_LOCATION_KIND_LOCAL, artifact.LocationKind) + }) + + withEnv(t, "AZD_EXEC_PROJECT_DIR", projectDir, func() { + svc := newServiceConfig("logicApp", "src/logicApp", nil) + svc.OutputPath = "Workflows" + + result, err := provider.Package(t.Context(), svc, nil, func(string) {}) + require.NoError(t, err) + expectedPath := filepath.Join(projectDir, "src/logicApp", "Workflows") + require.NotEmpty(t, result.Artifacts) + assert.Equal(t, expectedPath, result.Artifacts[0].Location) + }) + + withEnv(t, "AZD_EXEC_PROJECT_DIR", projectDir, func() { + svc := newServiceConfig("logicApp", "src/logicApp", nil) + svc.OutputPath = "../../../outside" + + _, err := provider.Package(t.Context(), svc, nil, func(string) {}) + require.Error(t, err) + assert.Contains(t, err.Error(), "outside project directory") + }) +} + +func TestRestoreAndBuildInvokeDotNetForCustomCodeProject(t *testing.T) { + projectDir := t.TempDir() + createFile(t, filepath.Join(projectDir, "azure.yaml"), "name: test-project\n") + csprojPath := filepath.Join(projectDir, "src/logicApp/Functions/Functions.csproj") + createFile(t, csprojPath, "\n") + + logFile := filepath.Join(t.TempDir(), "dotnet.log") + fakeBinDir := t.TempDir() + createFakeDotnetStub(t, fakeBinDir) + + svc := newServiceConfig("logicApp", "src/logicApp", map[string]any{ + "customCodeProject": "Functions/Functions.csproj", + }) + provider := &LogicAppsStandardFrameworkServiceProvider{} + + withEnv(t, "AZD_EXEC_PROJECT_DIR", projectDir, func() { + withEnv(t, "DOTNET_ARGS_LOG", logFile, func() { + withEnv(t, "PATH", fakeBinDir+string(os.PathListSeparator)+os.Getenv("PATH"), func() { + _, err := provider.Restore(t.Context(), svc, nil, func(string) {}) + require.NoError(t, err) + _, err = provider.Build(t.Context(), svc, nil, func(string) {}) + require.NoError(t, err) + }) + }) + }) + + // #nosec G304 -- logFile points to a test-controlled file under t.TempDir(). + contents, err := os.ReadFile(logFile) + require.NoError(t, err) + normalized := strings.ReplaceAll(string(contents), "\r\n", "\n") + logLines := strings.Split(strings.TrimSpace(normalized), "\n") + require.Len(t, logLines, 2, "expected two dotnet invocations: %q", string(contents)) + + expectedRestore := "restore " + csprojPath + assert.Equal(t, expectedRestore, logLines[0]) + + expectedBuild := "build " + csprojPath + " --configuration Release" + assert.Equal(t, expectedBuild, logLines[1]) +} + +func TestRestoreAndBuildSkipDotNetWhenNoCustomCodeProject(t *testing.T) { + logFile := filepath.Join(t.TempDir(), "dotnet.log") + fakeBinDir := t.TempDir() + createFakeDotnetStub(t, fakeBinDir) + + provider := &LogicAppsStandardFrameworkServiceProvider{} + svc := newServiceConfig("logicApp", "src/logicApp", nil) + + withEnv(t, "DOTNET_ARGS_LOG", logFile, func() { + withEnv(t, "PATH", fakeBinDir+string(os.PathListSeparator)+os.Getenv("PATH"), func() { + _, err := provider.Restore(t.Context(), svc, nil, func(string) {}) + require.NoError(t, err) + _, err = provider.Build(t.Context(), svc, nil, func(string) {}) + require.NoError(t, err) + }) + }) + + if _, statErr := os.Stat(logFile); statErr == nil { + // #nosec G304 -- logFile points to a test-controlled file under t.TempDir(). + contents, readErr := os.ReadFile(logFile) + require.NoError(t, readErr) + assert.Empty( + t, + strings.TrimSpace(string(contents)), + "dotnet should not be invoked when customCodeProject is not configured") + } else { + require.ErrorIs(t, statErr, os.ErrNotExist) + } +} + +func TestRestoreAndBuildReturnErrorWhenDotNetFails(t *testing.T) { + projectDir := t.TempDir() + createFile(t, filepath.Join(projectDir, "azure.yaml"), "name: test-project\n") + csprojPath := filepath.Join(projectDir, "src/logicApp/Functions/Functions.csproj") + createFile(t, csprojPath, "\n") + + fakeBinDir := t.TempDir() + createFailingFakeDotnetStub(t, fakeBinDir) + + svc := newServiceConfig("logicApp", "src/logicApp", map[string]any{ + "customCodeProject": "Functions/Functions.csproj", + }) + provider := &LogicAppsStandardFrameworkServiceProvider{} + + withEnv(t, "AZD_EXEC_PROJECT_DIR", projectDir, func() { + withEnv(t, "PATH", fakeBinDir+string(os.PathListSeparator)+os.Getenv("PATH"), func() { + _, err := provider.Restore(t.Context(), svc, nil, func(string) {}) + require.Error(t, err) + assert.Contains(t, err.Error(), "restoring custom code project") + assert.Contains(t, err.Error(), csprojPath) + + _, err = provider.Build(t.Context(), svc, nil, func(string) {}) + require.Error(t, err) + assert.Contains(t, err.Error(), "building custom code project") + assert.Contains(t, err.Error(), csprojPath) + }) + }) +} + +func newServiceConfig(name, relativePath string, additionalProps map[string]any) *azdext.ServiceConfig { + svc := &azdext.ServiceConfig{ + Name: name, + RelativePath: relativePath, + Host: "function", + } + + if additionalProps != nil { + props, err := structpb.NewStruct(additionalProps) + if err != nil { + panic(err) + } + svc.AdditionalProperties = props + } + + return svc +} + +func withEnv(t *testing.T, key, value string, fn func()) { + t.Helper() + original, existed := os.LookupEnv(key) + err := os.Setenv(key, value) + require.NoError(t, err, "failed to set %s", key) + t.Cleanup(func() { + if !existed { + _ = os.Unsetenv(key) + return + } + _ = os.Setenv(key, original) + }) + + fn() +} + +// createFakeDotnetStub places a fake dotnet executable in fakeBinDir. +// On Windows it creates dotnet.cmd (a batch file) so that cmd.exe can find +// and execute it via PATHEXT; on Unix it creates a dotnet shell script. +// Both stubs append their arguments to the file named by DOTNET_ARGS_LOG. +func createFakeDotnetStub(t *testing.T, fakeBinDir string) { + t.Helper() + stubName := "dotnet" + stubContent := "#!/bin/sh\nprintf '%s\n' \"$*\" >> \"$DOTNET_ARGS_LOG\"\n" + stubMode := os.FileMode(0o755) + if runtime.GOOS == "windows" { + stubName = "dotnet.cmd" + stubContent = "@echo off\r\necho %* >> %DOTNET_ARGS_LOG%\r\n" + stubMode = 0o644 + } + stubPath := filepath.Join(fakeBinDir, stubName) + err := os.WriteFile(stubPath, []byte(stubContent), stubMode) + require.NoError(t, err, "failed writing fake dotnet stub %q", stubPath) +} + +func createFailingFakeDotnetStub(t *testing.T, fakeBinDir string) { + t.Helper() + stubName := "dotnet" + stubContent := "#!/bin/sh\nexit 1\n" + stubMode := os.FileMode(0o755) + if runtime.GOOS == "windows" { + stubName = "dotnet.cmd" + stubContent = "@echo off\r\nexit /b 1\r\n" + stubMode = 0o644 + } + stubPath := filepath.Join(fakeBinDir, stubName) + err := os.WriteFile(stubPath, []byte(stubContent), stubMode) + require.NoError(t, err, "failed writing failing fake dotnet stub %q", stubPath) +} + +func createFile(t *testing.T, filePath, content string) { + t.Helper() + err := os.MkdirAll(filepath.Dir(filePath), 0o750) + require.NoError(t, err, "failed creating directory %q", filepath.Dir(filePath)) + err = os.WriteFile(filePath, []byte(content), 0o600) + require.NoError(t, err, "failed writing file %q", filePath) +} diff --git a/cli/azd/extensions/azure.logicappsstandard/internal/version/version.go b/cli/azd/extensions/azure.logicappsstandard/internal/version/version.go new file mode 100644 index 00000000000..52188c90118 --- /dev/null +++ b/cli/azd/extensions/azure.logicappsstandard/internal/version/version.go @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package version + +var ( + // Populated at build time + Version = "dev" // Default value for development builds + Commit = "none" + BuildDate = "unknown" +) diff --git a/cli/azd/extensions/azure.logicappsstandard/main.go b/cli/azd/extensions/azure.logicappsstandard/main.go new file mode 100644 index 00000000000..1166cd3800e --- /dev/null +++ b/cli/azd/extensions/azure.logicappsstandard/main.go @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package main + +import ( + "github.com/azure/azure-dev/cli/azd/pkg/azdext" + + "azurelogicappsstandard/internal/cmd" +) + +func main() { + azdext.Run(cmd.NewRootCommand()) +} diff --git a/cli/azd/extensions/azure.logicappsstandard/version.txt b/cli/azd/extensions/azure.logicappsstandard/version.txt new file mode 100644 index 00000000000..8acdd82b765 --- /dev/null +++ b/cli/azd/extensions/azure.logicappsstandard/version.txt @@ -0,0 +1 @@ +0.0.1 diff --git a/eng/pipelines/release-ext-azure-logicappsstandard.yml b/eng/pipelines/release-ext-azure-logicappsstandard.yml new file mode 100644 index 00000000000..7987664d997 --- /dev/null +++ b/eng/pipelines/release-ext-azure-logicappsstandard.yml @@ -0,0 +1,33 @@ +# Continuous deployment trigger +trigger: + branches: + include: + - main + paths: + include: + - go.mod + - cli/azd/extensions/azure.logicappsstandard + - eng/pipelines/release-azd-extension.yml + - /eng/pipelines/templates/jobs/build-azd-extension.yml + - /eng/pipelines/templates/jobs/cross-build-azd-extension.yml + - /eng/pipelines/templates/variables/image.yml + +pr: + paths: + include: + - cli/azd/extensions/azure.logicappsstandard + - eng/pipelines/release-ext-azure-logicappsstandard.yml + - eng/pipelines/release-azd-extension.yml + - eng/pipelines/templates/steps/publish-cli.yml + exclude: + - cli/azd/docs/** + +extends: + template: /eng/pipelines/templates/stages/1es-redirect.yml + parameters: + stages: + - template: /eng/pipelines/templates/stages/release-azd-extension.yml + parameters: + AzdExtensionId: azure.logicappsstandard + SanitizedExtensionId: azure-logicappsstandard + AzdExtensionDirectory: cli/azd/extensions/azure.logicappsstandard