diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000..d023a0d
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,2 @@
+# Default code owners for all files
+* @jongio
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..bcce2be
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,14 @@
+version: 2
+updates:
+ - package-ecosystem: "gomod"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ commit-message:
+ prefix: "deps"
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ commit-message:
+ prefix: "ci"
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6caacb1..3fd81c6 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -9,7 +9,7 @@ on:
workflow_dispatch:
concurrency:
- group: ci-${{ github.ref }}
+ group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
defaults:
@@ -27,17 +27,17 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@v4
+ uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Set up Go
- uses: actions/setup-go@v5
+ uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version: '${{ env.GO_VERSION }}'
cache: true
cache-dependency-path: cli/go.sum
- name: Cache Go tools
- uses: actions/cache@v4
+ uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4
with:
path: ~/go/bin
key: go-tools-${{ runner.os }}-${{ env.GO_VERSION }}
@@ -73,10 +73,10 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@v4
+ uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Set up Go
- uses: actions/setup-go@v5
+ uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version: '${{ env.GO_VERSION }}'
cache: true
@@ -97,7 +97,7 @@ jobs:
- name: Upload coverage to Codecov
if: github.repository == 'jongio/azd-exec'
- uses: codecov/codecov-action@v4
+ uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4
with:
file: coverage/coverage.out
flags: unittests
@@ -119,22 +119,8 @@ jobs:
COVERAGE=$(go tool cover -func=../coverage/coverage.out | grep total | awk '{print $3}')
echo "**Total Coverage: $COVERAGE**" >> $GITHUB_STEP_SUMMARY
- build:
- name: Build
- runs-on: ubuntu-latest
- needs: [preflight, test]
- timeout-minutes: 30
-
- defaults:
- run:
- working-directory: cli
-
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
-
- name: Set up Go
- uses: actions/setup-go@v5
+ uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version: '${{ env.GO_VERSION }}'
cache: true
@@ -149,7 +135,7 @@ jobs:
GOOS=darwin GOARCH=arm64 go build -o bin/darwin-arm64/exec ./src/cmd/exec
- name: Upload artifacts
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: binaries
path: cli/bin/
@@ -166,17 +152,17 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@v4
+ uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Set up Go
- uses: actions/setup-go@v5
+ uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version: '${{ env.GO_VERSION }}'
cache: true
cache-dependency-path: cli/go.sum
- name: Set up Python
- uses: actions/setup-python@v5
+ uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: '3.11'
@@ -198,7 +184,7 @@ jobs:
- name: Upload test logs on failure
if: failure()
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: integration-test-logs-${{ matrix.os }}
path: |
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 26549c3..6f30a59 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -12,6 +12,10 @@ on:
schedule:
- cron: '0 0 * * 0' # Weekly on Sundays
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
jobs:
analyze:
name: Analyze
@@ -29,19 +33,19 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@v4
+ uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Initialize CodeQL
- uses: github/codeql-action/init@v3
+ uses: github/codeql-action/init@9792ccaef0455e446c567163589397e8c3ac2e0d # v3
with:
languages: ${{ matrix.language }}
queries: security-extended,security-and-quality
- name: Autobuild
- uses: github/codeql-action/autobuild@v3
+ uses: github/codeql-action/autobuild@9792ccaef0455e446c567163589397e8c3ac2e0d # v3
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v3
+ uses: github/codeql-action/analyze@9792ccaef0455e446c567163589397e8c3ac2e0d # v3
continue-on-error: true
with:
category: "/language:${{matrix.language}}"
diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml
new file mode 100644
index 0000000..dabad49
--- /dev/null
+++ b/.github/workflows/govulncheck.yml
@@ -0,0 +1,38 @@
+name: Go Vulnerability Check
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+ schedule:
+ - cron: '0 0 * * 0' # Weekly on Sundays at midnight UTC
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+permissions:
+ contents: read
+
+jobs:
+ govulncheck:
+ name: Run govulncheck
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
+
+ - name: Set up Go
+ uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
+ with:
+ go-version-file: cli/go.mod
+ cache-dependency-path: cli/go.sum
+
+ - name: Install govulncheck
+ run: go install golang.org/x/vuln/cmd/govulncheck@latest
+
+ - name: Run govulncheck
+ working-directory: cli
+ run: govulncheck ./...
diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml
index 5e7e31b..7d4b70b 100644
--- a/.github/workflows/pr-build.yml
+++ b/.github/workflows/pr-build.yml
@@ -22,7 +22,7 @@ on:
type: number
concurrency:
- group: pr-build-${{ github.event.pull_request.number || github.ref }}
+ group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
defaults:
@@ -57,7 +57,7 @@ jobs:
steps:
- name: Check if build is allowed
id: check
- uses: actions/github-script@v7
+ uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
with:
script: |
let allowed = false;
@@ -168,7 +168,7 @@ jobs:
steps:
- name: Get PR details
id: pr
- uses: actions/github-script@v7
+ uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
with:
script: |
const prNumber = '${{ needs.check-permission.outputs.pr_number }}' || context.payload.pull_request.number;
@@ -184,12 +184,12 @@ jobs:
core.setOutput('title', pr.data.title);
- name: Checkout code
- uses: actions/checkout@v4
+ uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
ref: ${{ steps.pr.outputs.sha }}
- name: Set up Go
- uses: actions/setup-go@v5
+ uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version: '${{ env.GO_VERSION }}'
cache: true
@@ -334,7 +334,7 @@ jobs:
EOF
- name: Comment on PR
- uses: actions/github-script@v7
+ uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
with:
script: |
const fs = require('fs');
diff --git a/.github/workflows/release-test.yml b/.github/workflows/release-test.yml
index 975af0d..34b9b3a 100644
--- a/.github/workflows/release-test.yml
+++ b/.github/workflows/release-test.yml
@@ -31,12 +31,12 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
- name: Set up Go
- uses: actions/setup-go@v5
+ uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version: '${{ env.GO_VERSION }}'
cache: true
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 8a9b024..c8f20c4 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -33,17 +33,17 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@v4
+ uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Set up Go
- uses: actions/setup-go@v5
+ uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version: '${{ env.GO_VERSION }}'
cache: true
cache-dependency-path: cli/go.sum
- name: Cache Go tools
- uses: actions/cache@v4
+ uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4
with:
path: ~/go/bin
key: go-tools-${{ runner.os }}-${{ env.GO_VERSION }}
@@ -85,10 +85,10 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@v4
+ uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Set up Go
- uses: actions/setup-go@v5
+ uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version: '${{ env.GO_VERSION }}'
cache: true
@@ -109,7 +109,7 @@ jobs:
- name: Upload coverage to Codecov
if: github.repository == 'jongio/azd-exec'
- uses: codecov/codecov-action@v4
+ uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4
with:
file: coverage/coverage.out
flags: unittests
@@ -118,22 +118,8 @@ jobs:
fail_ci_if_error: false
verbose: true
- build:
- name: Build
- runs-on: ubuntu-latest
- needs: [preflight, test]
- timeout-minutes: 30
-
- defaults:
- run:
- working-directory: cli
-
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
-
- name: Set up Go
- uses: actions/setup-go@v5
+ uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version: '${{ env.GO_VERSION }}'
cache: true
@@ -148,7 +134,7 @@ jobs:
GOOS=darwin GOARCH=arm64 go build -o bin/darwin-arm64/exec ./src/cmd/exec
- name: Upload artifacts
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: binaries
path: cli/bin/
@@ -161,15 +147,16 @@ jobs:
permissions:
contents: write
pull-requests: write
+ id-token: write # Required for cosign OIDC signing
steps:
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
- name: Set up Go
- uses: actions/setup-go@v5
+ uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version: '${{ env.GO_VERSION }}'
cache: true
@@ -311,6 +298,52 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ - name: Install cosign
+ uses: sigstore/cosign-installer@3454372be43e8ddeec39df0f73bb47954da3a1f1 # v3
+
+ - name: Install syft
+ uses: anchore/sbom-action/download-syft@e11c554f704a0b820cbf8c51673f6945e0731532 # v0
+
+ - name: Sign release artifacts with cosign
+ working-directory: cli
+ run: |
+ VERSION="${{ steps.version.outputs.next }}"
+ echo "Signing release artifacts for v${VERSION}..."
+
+ # Download release assets
+ gh release download "v${VERSION}" --dir /tmp/release-assets --repo "${{ github.repository }}" || true
+
+ # Sign each artifact
+ if [ -d /tmp/release-assets ]; then
+ for file in /tmp/release-assets/*; do
+ if [ -f "$file" ]; then
+ echo "Signing $(basename $file)..."
+ cosign sign-blob --yes "$file" --output-signature "${file}.sig" --output-certificate "${file}.pem"
+ fi
+ done
+
+ # Upload signatures and certificates to the release
+ gh release upload "v${VERSION}" /tmp/release-assets/*.sig /tmp/release-assets/*.pem --repo "${{ github.repository }}" || true
+ echo "✅ All artifacts signed with cosign (keyless/OIDC)"
+ fi
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Generate SBOM
+ working-directory: cli
+ run: |
+ VERSION="${{ steps.version.outputs.next }}"
+ echo "Generating SBOM for v${VERSION}..."
+
+ # Generate SBOM for the Go module
+ syft . -o spdx-json=/tmp/sbom-spdx.json -o cyclonedx-json=/tmp/sbom-cyclonedx.json
+
+ # Upload SBOM to the release
+ gh release upload "v${VERSION}" /tmp/sbom-spdx.json /tmp/sbom-cyclonedx.json --repo "${{ github.repository }}" || true
+ echo "✅ SBOM generated and uploaded (SPDX + CycloneDX)"
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
- name: Update registry
working-directory: cli
run: |
diff --git a/.github/workflows/update-azd-core.yml b/.github/workflows/update-azd-core.yml
index e3f0398..03ab5d5 100644
--- a/.github/workflows/update-azd-core.yml
+++ b/.github/workflows/update-azd-core.yml
@@ -63,7 +63,7 @@ jobs:
working-directory: .
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
ref: ${{ steps.inputs.outputs.branch || 'main' }}
fetch-depth: 0
@@ -83,7 +83,7 @@ jobs:
working-directory: .
- name: Set up Go
- uses: actions/setup-go@v5
+ uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version: '${{ env.GO_VERSION }}'
cache: true
diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml
index 9444a53..d882727 100644
--- a/.github/workflows/website.yml
+++ b/.github/workflows/website.yml
@@ -32,15 +32,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Setup pnpm
- uses: pnpm/action-setup@v4
+ uses: pnpm/action-setup@5b4374b04084dc1f9032b52464284b769ac5059e # v4
with:
version: 9
- name: Setup Node.js
- uses: actions/setup-node@v4
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '20'
cache: 'pnpm'
@@ -71,7 +71,7 @@ jobs:
- name: Upload production artifact
if: github.event_name != 'pull_request'
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: production-build
path: web/dist
@@ -79,7 +79,7 @@ jobs:
- name: Upload PR preview artifact
if: github.event_name == 'pull_request'
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: pr-preview-${{ github.event.pull_request.number }}
path: web/dist
@@ -92,13 +92,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout gh-pages
- uses: actions/checkout@v4
+ uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
ref: gh-pages
fetch-depth: 0
- name: Download production artifact
- uses: actions/download-artifact@v4
+ uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: production-build
path: _new_build
@@ -130,13 +130,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
ref: gh-pages
fetch-depth: 0
- name: Download PR preview artifact
- uses: actions/download-artifact@v4
+ uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: pr-preview-${{ github.event.pull_request.number }}
path: pr/${{ github.event.pull_request.number }}
@@ -159,7 +159,7 @@ jobs:
git push origin gh-pages
- name: Comment PR with preview URL
- uses: actions/github-script@v7
+ uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
with:
script: |
const prNumber = context.payload.pull_request.number;
@@ -209,7 +209,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout gh-pages
- uses: actions/checkout@v4
+ uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
ref: gh-pages
fetch-depth: 0
@@ -232,7 +232,7 @@ jobs:
fi
- name: Update PR comment
- uses: actions/github-script@v7
+ uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
with:
script: |
const prNumber = context.payload.pull_request.number;
diff --git a/README.md b/README.md
index 8a6864a..2aac1a3 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,12 @@ Execute any script with full access to your Azure Developer CLI environment vari
[](https://github.com/jongio/azd-exec/actions/workflows/ci.yml)
[](https://github.com/jongio/azd-exec/actions/workflows/codeql.yml)
[](https://opensource.org/licenses/MIT)
+[](https://goreportcard.com/report/github.com/jongio/azd-exec/cli)
+[](https://pkg.go.dev/github.com/jongio/azd-exec/cli)
+[](https://github.com/jongio/azd-exec/actions/workflows/govulncheck.yml)
+[](https://github.com/jongio/azd-exec/actions/workflows/ci.yml)
+[](https://go.dev/)
+[](https://github.com/jongio/azd-exec)
@@ -55,7 +61,7 @@ That's it. Your script has access to all azd environment variables, Azure creden
Automatically detects and runs bash, sh, zsh, PowerShell, pwsh, and cmd scripts based on file extension or shebang.
### 🎯 Script Arguments
-Pass arguments to your scripts seamlessly with the `--` separator for clean parameter handling.
+Pass arguments to your scripts using the `--` separator for clean parameter handling.
### 🌍 Full Azure Context
Access all azd environment variables including subscription, tenant, location, and custom variables.
diff --git a/cli/.golangci.yml b/cli/.golangci.yml
index 144d22a..fdbb654 100644
--- a/cli/.golangci.yml
+++ b/cli/.golangci.yml
@@ -25,12 +25,21 @@ linters:
- unconvert # Remove unnecessary type conversions
- goconst # Find repeated strings that could be constants
- dupl # Code clone detection
- - godot # Check for proper comment punctuation
- - goprintffuncname # Check printf-like function names
- nolintlint # Ensure nolint directives are properly formatted
- gosec # Security checker for Go code
- bodyclose # Check for HTTP response body close
- gocritic # Comprehensive Go source code linter
+ # Dispatch-level linters
+ - errname # Check error type naming conventions
+ - exhaustive # Check exhaustiveness of enum switch statements
+ - forcetypeassert # Find forced type assertions
+ - makezero # Find slice declarations not initialized with zero length
+ - nilerr # Find code returning nil error incorrectly
+ - noctx # Find HTTP requests without context
+ - prealloc # Find slice pre-allocation opportunities
+ - predeclared # Find shadowed predeclared identifiers
+ - tparallel # Detect inappropriate t.Parallel() usage
+ - wastedassign # Find wasted assignments
settings:
errcheck:
check-type-assertions: true
@@ -44,10 +53,8 @@ linters:
- G306 # File permissions - we need executable scripts
govet:
- enable-all: true
disable:
- - shadow # Disable shadow checking as it can be noisy
- - fieldalignment # Disable struct field alignment - minor optimization
+ - fieldalignment
revive:
rules:
@@ -65,16 +72,33 @@ linters:
goconst:
min-len: 3
min-occurrences: 3
+ exhaustive:
+ default-signifies-exhaustive: true
+ gocritic:
+ disabled-checks:
+ - ifElseChain
+ - appendAssign
+ - elseif
exclusions:
rules:
- path: '(.+)_test\.go'
linters:
- dupl
+ - errcheck
- goconst
+ - unparam
+ - forcetypeassert
+ - noctx
+ - exhaustive
+ - prealloc
+ - gosec
+ - revive
+ - path: 'magefile\.go'
+ linters:
+ - unparam
+ - errcheck
output:
formats:
- colored-line-number:
+ text:
path: stdout
- print-issued-lines: true
- print-linter-name: true
diff --git a/cli/magefile.go b/cli/magefile.go
index f114bcf..b717228 100644
--- a/cli/magefile.go
+++ b/cli/magefile.go
@@ -384,6 +384,23 @@ func Lint() error {
return nil
}
+// preflightCrossGOOSLint runs golangci-lint with GOOS=linux to catch cross-platform issues.
+func preflightCrossGOOSLint() error {
+ if runtime.GOOS == "linux" {
+ fmt.Println(" ⏭️ Already on Linux — skipping cross-OS lint")
+ return nil
+ }
+ cmd := exec.Command("golangci-lint", "run", "./...")
+ cmd.Env = append(os.Environ(), "GOOS=linux")
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ if err := cmd.Run(); err != nil {
+ return fmt.Errorf("cross-OS lint (GOOS=linux) failed: %w", err)
+ }
+ fmt.Println(" ✅ Cross-OS lint passed (GOOS=linux)")
+ return nil
+}
+
// Clean removes build artifacts and coverage reports.
func Clean() error {
fmt.Println("Cleaning build artifacts...")
@@ -463,9 +480,56 @@ func SpellCheck() error {
return nil
}
+// preflightGofumpt checks that all Go files are formatted with gofumpt (stricter than gofmt).
+func preflightGofumpt() error {
+ if _, err := exec.LookPath("gofumpt"); err != nil {
+ fmt.Println(" ⚠️ gofumpt not installed — skipping strict format check")
+ fmt.Println(" Install with: go install mvdan.cc/gofumpt@latest")
+ return nil
+ }
+ output, err := sh.Output("gofumpt", "-l", ".")
+ if err != nil {
+ return fmt.Errorf("gofumpt check failed: %w", err)
+ }
+ if strings.TrimSpace(output) != "" {
+ fmt.Println(" Files not formatted with gofumpt:")
+ for _, f := range strings.Split(strings.TrimSpace(output), "\n") {
+ fmt.Printf(" • %s\n", f)
+ }
+ return fmt.Errorf("code is not gofumpt-formatted. Run 'gofumpt -w .' to fix")
+ }
+ fmt.Println(" ✅ Code is gofumpt-formatted")
+ return nil
+}
+
+// preflightDeadcode checks for unreachable functions using golang.org/x/tools deadcode analyzer.
+func preflightDeadcode() error {
+ if _, err := exec.LookPath("deadcode"); err != nil {
+ fmt.Println(" ⚠️ deadcode not installed — skipping dead code check")
+ fmt.Println(" Install with: go install golang.org/x/tools/cmd/deadcode@latest")
+ return nil
+ }
+ output, err := sh.Output("deadcode", "-test", "./...")
+ if err != nil {
+ fmt.Println(" ⚠️ Dead code found:")
+ fmt.Println(output)
+ // Non-fatal for now — report but don't fail
+ fmt.Println(" ⚠️ Dead code check completed with findings (non-fatal)")
+ return nil
+ }
+ if strings.TrimSpace(output) != "" {
+ fmt.Println(" ⚠️ Potential dead code found:")
+ fmt.Println(output)
+ } else {
+ fmt.Println(" ✅ No dead code detected")
+ }
+ return nil
+}
+
// Preflight runs all checks before shipping: format, build, lint, tests, and coverage.
func Preflight() error {
- fmt.Println("🚀 Running preflight checks...\n")
+ fmt.Println("🚀 Running preflight checks...")
+ fmt.Println()
checks := []struct {
name string
@@ -474,6 +538,9 @@ func Preflight() error {
{"Format check", Fmt},
{"Spell check", SpellCheck},
{"Linting", Lint},
+ {"Checking gofumpt formatting", preflightGofumpt},
+ {"Checking for dead code", preflightDeadcode},
+ {"Cross-OS lint (GOOS=linux)", preflightCrossGOOSLint},
{"Unit tests", Test},
{"Integration tests", TestIntegration},
{"Coverage report", TestCoverage},
diff --git a/cli/src/cmd/exec/commands/mcp.go b/cli/src/cmd/exec/commands/mcp.go
index 565d862..4e5f97e 100644
--- a/cli/src/cmd/exec/commands/mcp.go
+++ b/cli/src/cmd/exec/commands/mcp.go
@@ -248,7 +248,7 @@ func handleListShells(_ context.Context, _ azdext.ToolArgs) (*mcp.CallToolResult
shellutil.ShellCmd,
}
- var results []shellInfo
+ results := make([]shellInfo, 0, len(shells))
for _, sh := range shells {
_, err := exec.LookPath(sh)
results = append(results, shellInfo{
@@ -385,14 +385,16 @@ func buildShellArgs(shell, scriptOrCmd string, isInline bool, extraArgs []string
if isInline {
return []string{"cmd", "/c", scriptOrCmd}
}
- args := []string{"cmd", "/c", scriptOrCmd}
+ args := make([]string, 0, 3+len(extraArgs))
+ args = append(args, "cmd", "/c", scriptOrCmd)
args = append(args, extraArgs...)
return args
case "powershell", "pwsh":
if isInline {
return []string{shellLower, "-NoProfile", "-Command", scriptOrCmd}
}
- args := []string{shellLower, "-NoProfile", "-File", scriptOrCmd}
+ args := make([]string, 0, 4+len(extraArgs))
+ args = append(args, shellLower, "-NoProfile", "-File", scriptOrCmd)
args = append(args, extraArgs...)
return args
default:
@@ -400,7 +402,8 @@ func buildShellArgs(shell, scriptOrCmd string, isInline bool, extraArgs []string
if isInline {
return []string{shellLower, "-c", scriptOrCmd}
}
- args := []string{shellLower, scriptOrCmd}
+ args := make([]string, 0, 2+len(extraArgs))
+ args = append(args, shellLower, scriptOrCmd)
args = append(args, extraArgs...)
return args
}
diff --git a/cli/src/internal/executor/command_builder.go b/cli/src/internal/executor/command_builder.go
index 511597a..605813d 100644
--- a/cli/src/internal/executor/command_builder.go
+++ b/cli/src/internal/executor/command_builder.go
@@ -75,7 +75,7 @@ func (e *Executor) buildCommand(shell, scriptOrPath string, isInline bool) *exec
cmdArgs = append(cmdArgs, e.config.Args...)
}
- return exec.Command(cmdArgs[0], cmdArgs[1:]...) // #nosec G204 - cmdArgs are controlled by caller
+ return exec.Command(cmdArgs[0], cmdArgs[1:]...) //nolint:noctx // CLI command builder has no context available; #nosec G204
}
// buildPowerShellInlineCommand joins the inline script with its arguments into a single
diff --git a/cspell.json b/cspell.json
index 7c6b23f..b9f6e3c 100644
--- a/cspell.json
+++ b/cspell.json
@@ -33,6 +33,8 @@
"gochecknoinits",
"gocognit",
"gofmt",
+ "noctx",
+ "nolint",
"goimports",
"golangci",
"gopls",
@@ -83,7 +85,22 @@
"waitgroups",
"winget",
"wrapcheck",
- "squoted"
+ "squoted",
+ "cliout",
+ "CONNSTR",
+ "copilotskills",
+ "coreversion",
+ "deadcode",
+ "eastus",
+ "gofumpt",
+ "MCPJSON",
+ "mvdan",
+ "myenv",
+ "anothersecret",
+ "pkill",
+ "PYTHONPATH",
+ "shellutil",
+ "testutil"
],
"ignorePaths": [
"node_modules",
diff --git a/web/src/pages/index.astro b/web/src/pages/index.astro
index fb660ba..daf2192 100644
--- a/web/src/pages/index.astro
+++ b/web/src/pages/index.astro
@@ -61,7 +61,7 @@ const description = "azd exec - Run any script with azd environment and Azure cr