{"id":36473901,"url":"https://github.com/jongio/azd-core","last_synced_at":"2026-03-03T19:04:07.856Z","repository":{"id":331992161,"uuid":"1130805618","full_name":"jongio/azd-core","owner":"jongio","description":"Reusable Go utilities for building Azure Developer CLI extensions and tooling. ","archived":false,"fork":false,"pushed_at":"2026-02-23T05:05:40.000Z","size":418,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-23T13:52:45.939Z","etag":null,"topics":["azd","azd-extension","azure","microsoft"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jongio.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-09T03:26:19.000Z","updated_at":"2026-02-23T05:05:42.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jongio/azd-core","commit_stats":null,"previous_names":["jongio/azd-core"],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/jongio/azd-core","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jongio%2Fazd-core","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jongio%2Fazd-core/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jongio%2Fazd-core/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jongio%2Fazd-core/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jongio","download_url":"https://codeload.github.com/jongio/azd-core/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jongio%2Fazd-core/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30056056,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-03T18:21:05.932Z","status":"ssl_error","status_checked_at":"2026-03-03T18:20:59.341Z","response_time":61,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["azd","azd-extension","azure","microsoft"],"created_at":"2026-01-12T00:44:51.442Z","updated_at":"2026-03-03T19:04:07.840Z","avatar_url":"https://github.com/jongio.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# azd-core\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/jongio/azd-core.svg)](https://pkg.go.dev/github.com/jongio/azd-core)\n[![Go Report Card](https://goreportcard.com/badge/github.com/jongio/azd-core)](https://goreportcard.com/report/github.com/jongio/azd-core)\n[![CI](https://github.com/jongio/azd-core/workflows/CI/badge.svg)](https://github.com/jongio/azd-core/actions/workflows/ci.yml)\n[![codecov](https://codecov.io/gh/jongio/azd-core/branch/main/graph/badge.svg)](https://codecov.io/gh/jongio/azd-core)\n\nCommon reusable Go modules for building Azure Developer CLI (azd) extensions and tooling.\n\n## Overview\n\n`azd-core` provides shared utilities extracted from the Azure Developer CLI to support building azd extensions, custom CLI tools, and automation scripts. The goal is to enable developers to create azd-compatible tools without duplicating common logic or pulling in the entire azd runtime.\n\nThis library includes:\n- **URL Validation**: RFC-compliant HTTP/HTTPS URL validation and parsing\n- **Environment Management**: Environment variable resolution, pattern extraction, and Key Vault integration\n- **File System Utilities**: Atomic writes, JSON handling, secure file operations\n- **Path Management**: Tool discovery, PATH manipulation, installation suggestions\n- **Process Utilities**: Cross-platform process detection and management\n- **Shell Detection**: Script type detection from extensions, shebangs, and OS defaults\n- **Copilot Skill Installation**: Version-aware installation of agentskills.io SKILL.md files\n- **Browser Launching**: Secure cross-platform URL opening\n- **Security Validation**: Path traversal prevention, input sanitization, permission checks\n\n## Installation\n\n```bash\ngo get github.com/jongio/azd-core\n```\n\nOr add specific packages to your `go.mod`:\n\n```bash\ngo get github.com/jongio/azd-core/urlutil\ngo get github.com/jongio/azd-core/testutil\ngo get github.com/jongio/azd-core/cliout\ngo get github.com/jongio/azd-core/env\ngo get github.com/jongio/azd-core/keyvault\ngo get github.com/jongio/azd-core/fileutil\ngo get github.com/jongio/azd-core/pathutil\ngo get github.com/jongio/azd-core/browser\ngo get github.com/jongio/azd-core/security\ngo get github.com/jongio/azd-core/procutil\ngo get github.com/jongio/azd-core/shellutil\ngo get github.com/jongio/azd-core/copilotskills\n```\n\n## Documentation\n\nFull API documentation is available at [pkg.go.dev/github.com/jongio/azd-core](https://pkg.go.dev/github.com/jongio/azd-core).\n\n**Extension Development:**\n- [Extension Patterns Guide](docs/extension-patterns.md) - Comprehensive patterns and best practices for building azd extensions\n\n**Migration Guides:**\n- [URL Validation and Environment Patterns Migration](docs/migration-urlutil-env.md) - Migrate from custom validation to azd-core utilities\n\n## Packages\n\n### `urlutil`\nURL validation and parsing utilities with RFC-compliant validation.\n\n**Key Functions:**\n- `Validate` - Comprehensive HTTP/HTTPS URL validation using `net/url.Parse`\n- `ValidateHTTPSOnly` - Enforce HTTPS-only for production (allows localhost HTTP)\n- `Parse` - Parse and normalize URLs with validation\n- `NormalizeScheme` - Ensure URL has http:// or https:// prefix\n\n**Validation Rules:**\n- Protocol must be http:// or https:// (rejects ftp://, file://, javascript://, etc.)\n- URL must have a valid host/domain (rejects \"http://\", \"https://\")\n- URL must not exceed 2048 characters (RFC 2616 practical limit)\n- Uses `net/url.Parse` for RFC 3986 compliant parsing\n- Whitespace is trimmed before validation\n\n**Security Features:**\n- Prevents protocol injection (javascript:, file:, data: URLs)\n- Validates host presence to prevent malformed URLs\n- Length limits prevent DoS via extremely long URLs\n- HTTPS enforcement for production with localhost exception\n\n**Example:**\n```go\nimport \"github.com/jongio/azd-core/urlutil\"\n\n// Validate custom URL from configuration\nif err := urlutil.Validate(customURL); err != nil {\n    return fmt.Errorf(\"invalid custom URL: %w\", err)\n}\n\n// Enforce HTTPS for production endpoints (allows localhost HTTP)\nif err := urlutil.ValidateHTTPSOnly(apiEndpoint); err != nil {\n    return fmt.Errorf(\"production endpoint must use HTTPS: %w\", err)\n}\n\n// Parse and normalize URL\nparsed, err := urlutil.Parse(userProvidedURL)\nif err != nil {\n    return err\n}\nfmt.Printf(\"Accessing: %s://%s\\n\", parsed.Scheme, parsed.Host)\n\n// Add default scheme if missing\nnormalized := urlutil.NormalizeScheme(\"example.com\", \"https\")\n// Returns: \"https://example.com\"\n```\n\n### `testutil`\nCommon testing utilities for writing reliable tests in azd extensions.\n\n**Key Functions:**\n- `CaptureOutput` - Capture stdout during function execution for testing CLI commands\n- `FindTestData` - Locate test fixture directories with flexible path searching\n- `TempDir` - Create temporary directories with automatic cleanup via t.Cleanup()\n- `Contains` - Convenience helper for string containment checks\n\n**Features:**\n- Proper test line reporting via t.Helper() in all functions\n- Automatic cleanup of temporary resources\n- Cross-platform path handling\n- Reliable stdout capture with goroutine-based reading\n\n**Example:**\n```go\nimport \"github.com/jongio/azd-core/testutil\"\n\nfunc TestCLICommand(t *testing.T) {\n    // Capture command output\n    output := testutil.CaptureOutput(t, func() error {\n        return runCommand()\n    })\n    \n    if !testutil.Contains(output, \"success\") {\n        t.Error(\"expected success message\")\n    }\n}\n\nfunc TestWithFixtures(t *testing.T) {\n    // Find test data directory\n    fixturesDir := testutil.FindTestData(t, \"tests\", \"fixtures\")\n    \n    // Create temporary directory for outputs\n    tmpDir := testutil.TempDir(t)\n    // Automatically cleaned up after test\n}\n```\n\n### `cliout`\nStructured CLI output formatting with cross-platform terminal support and multiple output formats.\n\n**Key Functions:**\n- `Success` / `Error` / `Warning` / `Info` - Colored status messages with icons\n- `Header` / `Section` - Formatted section headers\n- `Table` - Simple table rendering with automatic column width calculation\n- `ProgressBar` - Visual progress indicators\n- `Confirm` - Interactive yes/no prompts (non-interactive in JSON mode)\n- `Print` - Hybrid output (JSON or formatted text)\n\n**Output Formats:**\n- `FormatDefault` - Human-readable text with ANSI colors and Unicode symbols\n- `FormatJSON` - Structured JSON for automation and scripting\n\n**Example:**\n```go\nimport \"github.com/jongio/azd-core/cliout\"\n\n// Set output format\nif err := cliout.SetFormat(\"json\"); err != nil {\n    log.Fatal(err)\n}\n\n// Print status messages\ncliout.Success(\"Deployment completed successfully\")\ncliout.Error(\"Failed to connect: %s\", err)\ncliout.Warning(\"This feature is deprecated\")\ncliout.Info(\"Processing %d items\", count)\n\n// Create tables\nheaders := []string{\"Name\", \"Status\", \"Port\"}\nrows := []cliout.TableRow{\n    {\"Name\": \"web\", \"Status\": \"running\", \"Port\": \"8080\"},\n    {\"Name\": \"api\", \"Status\": \"stopped\", \"Port\": \"3000\"},\n}\ncliout.Table(headers, rows)\n\n// Hybrid output (JSON mode or formatted)\ndata := map[string]interface{}{\"status\": \"success\", \"count\": 42}\ncliout.Print(data, func() {\n    cliout.Success(\"Processed %d items\", 42)\n})\n\n// Interactive prompts\nif cliout.Confirm(\"Do you want to continue?\") {\n    // User confirmed (always true in JSON mode)\n}\n\n// Orchestration mode for subcommands\ncliout.SetOrchestrated(true)\n// Now CommandHeader() calls are skipped\n```\n\n### `env`\nEnvironment variable utilities for converting between maps and slices, resolving references, and applying transformations.\n\n**Key Functions:**\n- `ResolveMap` / `ResolveSlice` - Resolve Key Vault references in environment variables\n- `MapToSlice` / `SliceToMap` - Convert between map and slice formats\n- `HasKeyVaultReferences` - Detect Key Vault references in environment data\n- `FilterByPrefix` / `FilterByPrefixSlice` - Filter environment variables by prefix (case-insensitive)\n- `ExtractPattern` - Extract environment variables matching prefix/suffix with key transformation\n- `NormalizeServiceName` - Convert environment variable naming to service naming (MY_API → my-api)\n\n**Pattern Extraction Features:**\n- Case-insensitive prefix/suffix matching\n- Optional prefix/suffix trimming from result keys\n- Custom key transformation functions\n- Value validation with callback functions\n- Useful for extracting service URLs, Azure variables, custom domain configs\n\n**Example:**\n```go\nimport \"github.com/jongio/azd-core/env\"\n\n// Filter by prefix (case-insensitive)\nenvVars := map[string]string{\n    \"AZURE_TENANT_ID\": \"xyz\",\n    \"AZURE_CLIENT_ID\": \"abc\",\n    \"DATABASE_URL\": \"postgres://...\",\n}\nazureVars := env.FilterByPrefix(envVars, \"AZURE_\")\n// Returns: {\"AZURE_TENANT_ID\": \"xyz\", \"AZURE_CLIENT_ID\": \"abc\"}\n\n// Extract service URLs with normalization\nserviceEnv := map[string]string{\n    \"SERVICE_MY_API_URL\": \"https://api.example.com\",\n    \"SERVICE_WEB_APP_URL\": \"https://web.example.com\",\n    \"SERVICE_DB_HOST\": \"db.example.com\",\n}\nurls, _ := env.ExtractPattern(serviceEnv, env.PatternOptions{\n    Prefix:       \"SERVICE_\",\n    Suffix:       \"_URL\",\n    TrimPrefix:   true,\n    TrimSuffix:   true,\n    KeyTransform: env.NormalizeServiceName, // MY_API → my-api\n})\n// Returns: {\"my-api\": \"https://api.example.com\", \"web-app\": \"https://web.example.com\"}\n```\n\n**Key Vault Resolution:**\n\n### `keyvault`\nAzure Key Vault reference detection and resolution for environment variables.\n\n**Supported Formats:**\n- `@Microsoft.KeyVault(SecretUri=https://...)`\n- `@Microsoft.KeyVault(VaultName=...;SecretName=...;SecretVersion=...)`\n- `akvs://\u003csubscription-id\u003e/\u003cvault-name\u003e/\u003csecret-name\u003e[/\u003cversion\u003e]`\n\n**Features:**\n- Uses `azidentity.DefaultAzureCredential` for authentication\n- Thread-safe client caching\n- Configurable error handling (fail-fast or graceful degradation)\n- SSRF protection and validation\n\n### `fileutil`\nFile system utilities with atomic operations, JSON handling, and secure file detection.\n\n**Key Functions:**\n- `AtomicWriteJSON` / `AtomicWriteFile` - Write files atomically with retry logic\n- `ReadJSON` - Read JSON with graceful missing file handling\n- `EnsureDir` - Create directories with secure permissions (0750)\n- `FileExists` / `FileExistsAny` / `FilesExistAll` - File existence checks\n- `HasFileWithExt` / `HasAnyFileWithExts` - Extension-based file detection\n- `ContainsText` / `ContainsTextInFile` - Search file contents\n\n**Features:**\n- Atomic writes prevent partial/corrupt files\n- Retry logic for transient filesystem errors\n- Secure permissions (directories: 0750, files: 0644)\n- Path traversal protection via `security.ValidatePath`\n\n### `pathutil`\nPATH environment variable management and tool discovery utilities.\n\n**Key Functions:**\n- `RefreshPATH` - Refresh PATH from system (Windows registry, Unix environment)\n- `FindToolInPath` - Search PATH for executables (auto .exe handling on Windows)\n- `SearchToolInSystemPath` - Search common installation directories\n- `GetInstallSuggestion` - Get installation URLs for 22+ popular tools\n\n**Features:**\n- Cross-platform PATH refresh (Windows PowerShell registry read, Unix environment)\n- Automatic .exe extension handling on Windows\n- Common install directory search (Program Files, /usr/local/bin, Homebrew, etc.)\n- Installation suggestions for npm, python, docker, azd, and more\n\n### `browser`\nCross-platform browser launching with URL validation and timeout support.\n\n**Key Functions:**\n- `Launch` - Open URL in system default browser (non-blocking)\n- `ResolveTarget` - Resolve browser target (default, system, none)\n- `ValidTargets` / `IsValid` - Target validation\n- `GetTargetDisplayName` / `FormatValidTargets` - Display formatting\n\n**Features:**\n- Cross-platform support (Windows cmd/start, macOS open, Linux xdg-open)\n- URL validation (http/https only for security)\n- Non-blocking launch with configurable timeout\n- Context-based cancellation\n- Graceful error handling (warnings only, non-critical)\n\n### `security`\nSecurity validation utilities for path traversal prevention, input sanitization, and permission checks.\n\n**Key Functions:**\n- `ValidatePath` - Prevent path traversal attacks (detects `..`, resolves symlinks)\n- `ValidateServiceName` - Validate service names (DNS-safe, container-safe)\n- `ValidatePackageManager` - Allowlist-based package manager validation\n- `SanitizeScriptName` - Detect shell metacharacters\n- `IsContainerEnvironment` - Detect Codespaces, Dev Containers, Docker, Kubernetes\n- `ValidateFilePermissions` - Detect world-writable files (Unix only)\n\n**Features:**\n- Path traversal attack prevention\n- Symbolic link resolution and validation\n- Service name validation (alphanumeric start, DNS label limits)\n- Shell metacharacter detection\n- Container environment detection\n- World-writable file detection (security warning)\n\n### `procutil`\nCross-platform process detection utilities using gopsutil for reliable cross-platform behavior.\n\n**Key Functions:**\n- `IsProcessRunning` - Check if process with given PID is running\n\n**Features:**\n- Cross-platform support (Windows, Linux, macOS, BSD, Solaris, AIX)\n- Reliable Windows process detection (no stale PID issues)\n- Uses platform-native APIs (Windows: OpenProcess, Linux: /proc, macOS: sysctl)\n- Powered by github.com/shirou/gopsutil/v4\n- Uses Signal(0) on Unix for accurate detection\n- Windows fallback with documented limitations (stale PID may return true)\n- Invalid PID handling (≤0 returns false)\n\n### `copilotskills`\nInstalls agentskills.io-compliant SKILL.md files from an embedded filesystem to `~/.copilot/skills/{name}/`.\n\n**Key Functions:**\n- `Install` - Write embedded skill files to `~/.copilot/skills/{name}/` with version-based skip logic\n\n**Features:**\n- Version-based skip: reads `.version` file and skips if it matches (no unnecessary I/O)\n- Atomic file writes via `fileutil.AtomicWriteFile`\n- Name validation per agentskills.io spec (lowercase, hyphens, digits only)\n- Walks embedded `embed.FS` under a configurable root directory\n\n**Example:**\n```go\nimport \"github.com/jongio/azd-core/copilotskills\"\n\n//go:embed skills/my-extension\nvar skillFS embed.FS\n\nfunc installSkills(version string) error {\n    return copilotskills.Install(\"my-extension\", version, skillFS, \"skills/my-extension\")\n}\n```\n\n### `shellutil`\nShell detection from file extensions, shebangs, and OS defaults.\n\n**Key Functions:**\n- `DetectShell` - Auto-detect shell from extension, shebang, or OS default\n- `ReadShebang` - Parse shebang line to extract interpreter\n\n**Shell Constants:**\n- `ShellBash`, `ShellSh`, `ShellZsh` - Unix shells\n- `ShellPwsh`, `ShellPowerShell` - PowerShell variants\n- `ShellCmd` - Windows Command Prompt\n\n**Features:**\n- Extension detection (.ps1 → pwsh, .sh → bash, .cmd → cmd, etc.)\n- Shebang parsing (#!/bin/bash, #!/usr/bin/env python3, etc.)\n- OS-specific defaults (Windows: cmd, Unix: bash)\n- Graceful error handling (falls back to OS default)\n\n## Usage Examples\n\n### Resolve Key Vault References in Environment\n\n```go\npackage main\n\nimport (\n    \"context\"\n    \"os\"\n\n    \"github.com/jongio/azd-core/env\"\n    \"github.com/jongio/azd-core/keyvault\"\n)\n\nfunc main() {\n    // Create resolver\n    resolver, err := keyvault.NewKeyVaultResolver()\n    if err != nil {\n        panic(err)\n    }\n\n    // Resolve from environment map\n    envMap := map[string]string{\n        \"DATABASE_PASSWORD\": \"@Microsoft.KeyVault(VaultName=myvault;SecretName=db-pass)\",\n        \"API_ENDPOINT\":      \"https://api.example.com\",\n    }\n\n    resolved, warnings, err := env.ResolveMap(\n        context.Background(),\n        envMap,\n        resolver,\n        keyvault.ResolveEnvironmentOptions{},\n    )\n    if err != nil {\n        panic(err)\n    }\n\n    // Handle warnings\n    for _, w := range warnings {\n        os.Stderr.WriteString(\"warning: \" + w.Err.Error() + \"\\n\")\n    }\n\n    // Use resolved environment\n    os.Setenv(\"DATABASE_PASSWORD\", resolved[\"DATABASE_PASSWORD\"])\n}\n```\n\n### Atomic File Writing\n\n```go\nimport \"github.com/jongio/azd-core/fileutil\"\n\n// Write JSON atomically (prevents partial/corrupt files)\ndata := map[string]interface{}{\n    \"version\": \"1.0\",\n    \"config\":  map[string]string{\"key\": \"value\"},\n}\nerr := fileutil.AtomicWriteJSON(\"config.json\", data)\n```\n\n### Tool Discovery\n\n```go\nimport (\n    \"fmt\"\n    \"github.com/jongio/azd-core/pathutil\"\n)\n\n// Find a tool in PATH\nif nodePath := pathutil.FindToolInPath(\"node\"); nodePath != \"\" {\n    fmt.Printf(\"Node.js found at: %s\\n\", nodePath)\n} else {\n    fmt.Println(pathutil.GetInstallSuggestion(\"node\"))\n}\n\n// Search common system directories\nif dockerPath := pathutil.SearchToolInSystemPath(\"docker\"); dockerPath != \"\" {\n    fmt.Printf(\"Docker found at: %s\\n\", dockerPath)\n}\n```\n\n### Secure Path Validation\n\n```go\nimport \"github.com/jongio/azd-core/security\"\n\n// Validate user-provided path (prevents path traversal)\nif err := security.ValidatePath(userPath); err != nil {\n    return fmt.Errorf(\"invalid path: %w\", err)\n}\n\n// Validate service name (DNS-safe, container-safe)\nif err := security.ValidateServiceName(name, false); err != nil {\n    return fmt.Errorf(\"invalid service name: %w\", err)\n}\n```\n\n### Shell Detection\n\n```go\nimport \"github.com/jongio/azd-core/shellutil\"\n\n// Auto-detect shell from script\nshell := shellutil.DetectShell(\"deploy.sh\")  // Returns \"bash\"\nshell = shellutil.DetectShell(\"setup.ps1\")   // Returns \"pwsh\" or \"powershell\"\nshell = shellutil.DetectShell(\"build.cmd\")   // Returns \"cmd\"\n\n// Read shebang to detect interpreter\nif shebang := shellutil.ReadShebang(\"script.py\"); shebang != \"\" {\n    fmt.Printf(\"Interpreter: %s\\n\", shebang)  // \"python3\"\n}\n```\n\n### Browser Launch\n\n```go\nimport (\n    \"github.com/jongio/azd-core/browser\"\n    \"time\"\n)\n\n// Open URL in default browser\nerr := browser.Launch(browser.LaunchOptions{\n    URL:     \"https://example.com\",\n    Target:  browser.TargetDefault,\n    Timeout: 5 * time.Second,\n})\n```\n\n### Process Detection\n\n```go\nimport \"github.com/jongio/azd-core/procutil\"\n\n// Check if process is running\nif procutil.IsProcessRunning(pid) {\n    fmt.Println(\"Process is running\")\n}\n```\n\n## Authentication\n\nThe `keyvault` package uses `azidentity.DefaultAzureCredential`, supporting:\n- Environment variables (`AZURE_TENANT_ID`, `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`)\n- Managed identity (Azure VM, App Service, Container Apps, etc.)\n- Azure CLI (`az login`)\n- Azure PowerShell\n- Interactive browser authentication\n\nNo global state is maintained, and client caching is thread-safe.\n\n## Testing\n\n```bash\n# Run all tests\ngo test ./...\n\n# Run with coverage\ngo test -cover ./...\n\n# Generate coverage report\ngo test -coverprofile=coverage.out ./...\ngo tool cover -func=coverage.out\ngo tool cover -html=coverage.out\n```\n\nTests are offline-only and use mocks for Azure SDK interactions.\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on contributing to this project.\n\n## Security\n\nSee [SECURITY.md](SECURITY.md) for information on reporting security vulnerabilities.\n\n## License\n\nThis project is licensed under the MIT License. See [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjongio%2Fazd-core","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjongio%2Fazd-core","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjongio%2Fazd-core/lists"}