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..77aa955355e --- /dev/null +++ b/cli/azd/extensions/azure.logicappsstandard/CHANGELOG.md @@ -0,0 +1,5 @@ +# Release History + +## 0.1.0 + +- Initial release with support for packaging Logic App Standard projects via `language: logicappsstandard` in `azure.yaml`, and optional custom code projects using `customCodeProject` property. 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..7a85b272938 --- /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 +} 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..2e66dea3138 --- /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 diff --git a/cli/azd/extensions/azure.logicappsstandard/cspell.yaml b/cli/azd/extensions/azure.logicappsstandard/cspell.yaml new file mode 100644 index 00000000000..47fc7877e8f --- /dev/null +++ b/cli/azd/extensions/azure.logicappsstandard/cspell.yaml @@ -0,0 +1,6 @@ +import: ../../.vscode/cspell.yaml +words: + - azurelogicappsstandard + - designtime + - logicapp + - logicappsstandard diff --git a/cli/azd/extensions/azure.logicappsstandard/extension.yaml b/cli/azd/extensions/azure.logicappsstandard/extension.yaml new file mode 100644 index 00000000000..ea177817b3e --- /dev/null +++ b/cli/azd/extensions/azure.logicappsstandard/extension.yaml @@ -0,0 +1,13 @@ +# 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.1.0 +requiredAzdVersion: ">= 1.28.1" +language: go +capabilities: + - framework-service-provider + - metadata diff --git a/cli/azd/extensions/azure.logicappsstandard/go.mod b/cli/azd/extensions/azure.logicappsstandard/go.mod new file mode 100644 index 00000000000..616146927c9 --- /dev/null +++ b/cli/azd/extensions/azure.logicappsstandard/go.mod @@ -0,0 +1,107 @@ +module azurelogicappsstandard + +go 1.26.4 + +require ( + github.com/azure/azure-dev/cli/azd v1.28.1 + github.com/spf13/cobra v1.10.2 + github.com/stretchr/testify v1.11.1 + google.golang.org/protobuf v1.36.11 +) + +require ( + dario.cat/mergo v1.0.2 // indirect + github.com/AlecAivazis/survey/v2 v2.3.7 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.22.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.0 // 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.5.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.27.0 // 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-20260720091843-3eef36eaaa28 // 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/dlclark/regexp2/v2 v2.5.2 // indirect + github.com/drone/envsubst v1.0.3 // indirect + github.com/fatih/color v1.19.0 // indirect + github.com/go-logr/logr v1.4.4 // 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.57.0 // indirect + github.com/mattn/go-colorable v0.1.15 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect + github.com/mattn/go-runewidth v0.0.27 // 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.4.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.4 // indirect + github.com/yuin/goldmark-emoji v1.0.6 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/otel v1.44.0 // indirect + go.opentelemetry.io/otel/metric v1.44.0 // indirect + go.opentelemetry.io/otel/sdk v1.44.0 // indirect + go.opentelemetry.io/otel/trace v1.44.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.6 // indirect + golang.org/x/crypto v0.54.0 // indirect + golang.org/x/exp v0.0.0-20260718201538-764159d718ef // indirect + golang.org/x/net v0.57.0 // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/term v0.45.0 // indirect + golang.org/x/text v0.40.0 // indirect + golang.org/x/time v0.15.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260723215102-3fe39f3c1018 // indirect + google.golang.org/grpc v1.82.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..c4711dbad5f --- /dev/null +++ b/cli/azd/extensions/azure.logicappsstandard/go.sum @@ -0,0 +1,320 @@ +code.cloudfoundry.org/clock v0.0.0-20180518195852-02e53af36e6c/go.mod h1:QD9Lzhd/ux6eNQVUDVRJX/RKTigpewimNYBi7ivZKY8= +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +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.22.0 h1:aokoqcHvaGjiM3VpjKDfMMnF/8epJ+Q1HLJ7CudztqE= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.22.0/go.mod h1:/WYEx9pcM9Y+Dd/APJaNlSvVSvzl54rrMdZT5+Oi2LM= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.0 h1:CU4+EJeJi3TKYWEcYuSdWsjzw0nVsK/H0MSQOiPcymU= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.0/go.mod h1:q0+UTSRvShwUCrR/s5HtyInYphN7Wvxb7snFM3u+SLA= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.4.0 h1:xFaZZ+IubdftrDHnGGwZ6QvQ3KHTtWl2MCK+GMt2vxs= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.4.0/go.mod h1:mCBhUhlMjLLJKr5aqw2TNS/VqJOie8MzWq3DAMJeKso= +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.5.0 h1:aMFOzch6ZJo4Ct9hI4A9Y2fPen5YNRTPmkSBhe5m0ZQ= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.5.0/go.mod h1:Oct8bx+g+DXKngU7i/LzFzYt44rmLdMu4uoofIpooVo= +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.27.0 h1:FodwmyOBgJULFYmDqibcp9pvfDLWdtPRh9v/r5BXYZs= +github.com/alecthomas/chroma/v2 v2.27.0/go.mod h1:NjJ3ciIgrqBNeIkWZ4e46nseoLDslxU1LmfCoL+wcY8= +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.28.1 h1:OO4P0QxCE+rpQ1rYaeJNqyHyucSOc5+ZEvOjQgKPPj4= +github.com/azure/azure-dev/cli/azd v1.28.1/go.mod h1:Ge7QaU9PoJM7i6J0xArDoQCf2tUn6O7OIKkoItxFTA8= +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-20260720091843-3eef36eaaa28 h1:fGQpqto9ryOmNMRHacdlSLxKg+l2E+yX0PpIxoBZAi0= +github.com/charmbracelet/x/exp/slice v0.0.0-20260720091843-3eef36eaaa28/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/dlclark/regexp2/v2 v2.5.2 h1:HAsucWRhsqcDzl6Ua9aR8JwYOTzrZyPrF0/FNxJVAI0= +github.com/dlclark/regexp2/v2 v2.5.2/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU= +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.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= +github.com/go-logr/logr v1.4.4/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.57.0 h1:jzWKyCzdWnwnZt05cvcQQ+ngiUl2RnixXJa7Kj4qP1E= +github.com/mark3labs/mcp-go v0.57.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.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY= +github.com/mattn/go-colorable v0.1.15/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.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= +github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.27 h1:Feg/Oou5zI/wnpgDF6omIU0OokC9GxLC/WRknhVlIR0= +github.com/mattn/go-runewidth v0.0.27/go.mod h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/8= +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.4.0 h1:9qy1OoIAxBL+gBYnkTnTnWle5wlfsXQlwRzIbbpdqPw= +github.com/sethvargo/go-retry v0.4.0/go.mod h1:tvsjdKG6xfiCx4LSiUZ06kcv38xvdVQwv8R6/VnnVWg= +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.4 h1:oat/nd3U6NeQqFEL3xpEJq7d7c86NI+DbSNGAs4xnjA= +github.com/yuin/goldmark v1.8.4/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.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= +go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= +go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= +go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= +go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= +go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= +go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= +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.6 h1:1h7H1ohdUh93/FyE4YaDa1Zh64K6VVbjF4K6WUxMtH4= +go.yaml.in/yaml/v4 v4.0.0-rc.6/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.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= +golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= +golang.org/x/exp v0.0.0-20260718201538-764159d718ef h1:LkZ48HFgy/TvhTI0bcWkjgFkgLyKUwcTbDjS0DUjw+A= +golang.org/x/exp v0.0.0-20260718201538-764159d718ef/go.mod h1:EdfpwwqSu+0Li0mzskwHU6FWDV3t9Q+RZDo3QMUtL3Q= +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.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= +golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= +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/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +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.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.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.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= +golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= +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.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= +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-20260723215102-3fe39f3c1018 h1:yXIvV9x4Vu2wUs2cCW8puVLHAjZkuipNK1MnTCZ0Jo0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260723215102-3fe39f3c1018/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE= +google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA= +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..b051f4303dd --- /dev/null +++ b/cli/azd/extensions/azure.logicappsstandard/internal/project/framework_service_logicappsstandard.go @@ -0,0 +1,256 @@ +// 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" + "google.golang.org/protobuf/types/known/structpb" +) + +// 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{} + +// 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 { + if serviceConfig.Host != "function" { + return fmt.Errorf("Logic Apps Standard requires the host to be 'function', but found '%s'", serviceConfig.Host) + } + + hasCustomCode, err := hasCustomCodeProjectConfigured(serviceConfig) + if err != nil { + return err + } + if hasCustomCode { + 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) { + hasCustomCode, _ := hasCustomCodeProjectConfigured(serviceConfig) + if hasCustomCode { + 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) { + // We always return true for restore & build because Requirements is not evaluated per service. + // The actual restore/build steps will be skipped if no custom code project is configured. + return &azdext.FrameworkRequirements{ + Package: &azdext.FrameworkPackageRequirements{ + RequireRestore: true, + RequireBuild: true, + }, + }, 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) { + hasCustomCode, _ := hasCustomCodeProjectConfigured(serviceConfig) + if hasCustomCode { + 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) { + hasCustomCode, _ := hasCustomCodeProjectConfigured(serviceConfig) + if hasCustomCode { + 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) + } + + customCodeProject, err := getAdditionalProperty(serviceConfig, "customCodeProject") + if err != nil { + return "", fmt.Errorf("getting custom code project path: %w", err) + } + + customCodeProjectPath, err := resolvePathWithinBase( + projectDir, + serviceConfig.RelativePath, + 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, error) { + if serviceConfig == nil { + return "", nil + } + + props := serviceConfig.GetAdditionalProperties() + if props == nil { + return "", nil + } + if v, ok := props.Fields[key]; ok && v != nil { + if _, ok := v.Kind.(*structpb.Value_StringValue); !ok { + return "", fmt.Errorf("additional property %q must be a string, got %T", key, v.Kind) + } + return v.GetStringValue(), nil + } + return "", nil +} + +func hasCustomCodeProjectConfigured(serviceConfig *azdext.ServiceConfig) (bool, error) { + customCodeProject, err := getAdditionalProperty(serviceConfig, "customCodeProject") + if err != nil { + return false, err + } + return customCodeProject != "", nil +} + +// runDotNet executes the dotnet CLI with the given arguments, returning any output as part of the error on failure. +func runDotNet(ctx context.Context, args ...string) error { + cmd := azdext.ExecCommand(ctx, "dotnet", args...) + output, err := cmd.CombinedOutput() + if err != nil && len(output) > 0 { + return fmt.Errorf("%w, output: %s", err, output) + } + return err +} 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..1d07021f1f5 --- /dev/null +++ b/cli/azd/extensions/azure.logicappsstandard/internal/project/framework_service_logicappsstandard_test.go @@ -0,0 +1,444 @@ +// 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) { + value, err := getAdditionalProperty(nil, "customCodeProject") + require.NoError(t, err) + assert.Equal(t, "", value) + }) + + t.Run("returns empty for nil additional properties", func(t *testing.T) { + svc := &azdext.ServiceConfig{} + value, err := getAdditionalProperty(svc, "customCodeProject") + require.NoError(t, err) + assert.Equal(t, "", value) + }) + + t.Run("returns property value when present", func(t *testing.T) { + svc := newServiceConfig("logicApp", "src/logicApp", map[string]any{ + "customCodeProject": "Functions/Functions.csproj", + }) + + value, err := getAdditionalProperty(svc, "customCodeProject") + require.NoError(t, err) + assert.Equal(t, "Functions/Functions.csproj", value) + }) + + t.Run("returns error for non-string values", func(t *testing.T) { + svc := newServiceConfig("logicApp", "src/logicApp", map[string]any{ + "customCodeProject": 42, + }) + + value, err := getAdditionalProperty(svc, "customCodeProject") + require.Error(t, err) + assert.Empty(t, value) + assert.Contains(t, err.Error(), "must be a string") + }) +} + +func TestHasCustomCodeProjectConfigured(t *testing.T) { + t.Run("returns false for nil service config", func(t *testing.T) { + hasCustomCode, err := hasCustomCodeProjectConfigured(nil) + require.NoError(t, err) + assert.False(t, hasCustomCode) + }) + + t.Run("returns false when customCodeProject is absent", func(t *testing.T) { + hasCustomCode, err := hasCustomCodeProjectConfigured(newServiceConfig("logicApp", "src/logicApp", nil)) + require.NoError(t, err) + assert.False(t, hasCustomCode) + }) + + t.Run("returns true when customCodeProject is present", func(t *testing.T) { + hasCustomCode, err := hasCustomCodeProjectConfigured(newServiceConfig("logicApp", "src/logicApp", map[string]any{ + "customCodeProject": "Functions/Functions.csproj", + })) + require.NoError(t, err) + assert.True(t, hasCustomCode) + }) + + t.Run("returns error when customCodeProject is not a string", func(t *testing.T) { + hasCustomCode, err := hasCustomCodeProjectConfigured(newServiceConfig("logicApp", "src/logicApp", map[string]any{ + "customCodeProject": 42, + })) + require.Error(t, err) + assert.False(t, hasCustomCode) + assert.Contains(t, err.Error(), "must be a string") + }) +} + +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("enables restore and build", func(t *testing.T) { + provider := &LogicAppsStandardFrameworkServiceProvider{} + 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("succeeds when host is function", func(t *testing.T) { + provider := &LogicAppsStandardFrameworkServiceProvider{} + svc := newServiceConfig("logicApp", "src/logicApp", nil) + svc.Host = "function" + + err := provider.Initialize(t.Context(), svc) + require.NoError(t, err) + }) + + 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", func(t *testing.T) { + provider := &LogicAppsStandardFrameworkServiceProvider{} + svc := newServiceConfig("logicApp", "src/logicApp", nil) + + err := provider.Initialize(t.Context(), svc) + require.NoError(t, err) + }) + + 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 contains invalid characters", 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") + }) + }) + + t.Run("fails when custom code project is not a string", func(t *testing.T) { + provider := &LogicAppsStandardFrameworkServiceProvider{} + svc := newServiceConfig("logicApp", "src/logicApp", map[string]any{ + "customCodeProject": 42, + }) + + withEnv(t, "AZD_EXEC_PROJECT_DIR", projectDir, func() { + err := provider.Initialize(t.Context(), svc) + require.Error(t, err) + assert.Contains(t, err.Error(), "must be a string") + }) + }) +} + +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\n>>\"%DOTNET_ARGS_LOG%\" echo %*\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..6e8bf73aa55 --- /dev/null +++ b/cli/azd/extensions/azure.logicappsstandard/version.txt @@ -0,0 +1 @@ +0.1.0 diff --git a/eng/pipelines/release-ext-azure-logicappsstandard.yml b/eng/pipelines/release-ext-azure-logicappsstandard.yml new file mode 100644 index 00000000000..508e964c682 --- /dev/null +++ b/eng/pipelines/release-ext-azure-logicappsstandard.yml @@ -0,0 +1,45 @@ +# Continuous deployment trigger +trigger: + branches: + include: + - main + paths: + include: + - cli/azd/extensions/azure.logicappsstandard + - /eng/pipelines/templates/stages/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/templates/stages/release-azd-extension.yml + - eng/pipelines/templates/steps/publish-cli.yml + exclude: + - cli/azd/docs/** + +parameters: + - name: PublishToRegistry + displayName: Publish to registry + type: string + # Scheduled (nightly) runs override this in the shared templates; the runtime + # parameter default must be a literal because it renders before variables exist. + default: stable + values: + - stable + - dev + - nightly + +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 + PublishToRegistry: ${{ parameters.PublishToRegistry }}