Skip to content

Commit 54839bc

Browse files
tmeschterCopilot
andcommitted
refac: split detect-aspire into presence and gather-info scripts
Separate Aspire detection into two purpose-built scripts: - detect-aspire.{sh,ps1}: presence-only (isAspire + appHostPath) for detection/routing points (scan.md, recipe-selection.md, generate.md, recipes/azd/README.md) - gather-aspire-info.{sh,ps1}: full facts (appHostDir, hasExcludeFromManifest, hasFunctions, secretStorageConfigured) for Aspire-specific reference files (aspire.md, recipes/azd/aspire.md, services/functions/aspire-containerapps.md) Add gather-aspire-info invocations to the two Aspire-specific files that previously referenced no detection script. Update the integration test regex to accept either script name. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 41f3ea2 commit 54839bc

10 files changed

Lines changed: 319 additions & 134 deletions

File tree

plugin/skills/azure-prepare/references/aspire.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,16 @@ Guidance for preparing .NET Aspire applications for Azure deployment.
1919

2020
### Step 1: Detection
2121

22-
When scanning the codebase (per [scan.md](scan.md)), run the detection script ([detect-aspire.sh](scripts/detect-aspire.sh) / [detect-aspire.ps1](scripts/detect-aspire.ps1)). It performs the full deterministic detection sequence in one pass and prints `key=value` lines plus a human-readable summary, so you can branch on the result instead of parsing raw `find`/`grep` output.
22+
When scanning the codebase (per [scan.md](scan.md)), Aspire presence is established with `detect-aspire`. Once confirmed, gather the full set of facts by running [gather-aspire-info.sh](scripts/gather-aspire-info.sh) / [gather-aspire-info.ps1](scripts/gather-aspire-info.ps1). It performs the full deterministic detection sequence in one pass and prints `key=value` lines plus a human-readable summary, so you can branch on the result instead of parsing raw `find`/`grep` output.
2323

2424
**bash:**
2525
```bash
26-
./scripts/detect-aspire.sh [workspace-root]
26+
./scripts/gather-aspire-info.sh [workspace-root]
2727
```
2828

2929
**PowerShell:**
3030
```powershell
31-
./scripts/detect-aspire.ps1 -WorkspaceRoot <workspace-root>
31+
./scripts/gather-aspire-info.ps1 -WorkspaceRoot <workspace-root>
3232
```
3333

3434
`workspace-root` defaults to the current directory. The script reports these fields:
@@ -56,7 +56,7 @@ If `isAspire=false`, this is not an Aspire app — continue with the normal reci
5656

5757
### ⛔ Step 1a: Pre-Check for Custom/Non-Deployable Resources (MANDATORY)
5858

59-
**Before running `azd init --from-code`, understand whether the app may contain local-only custom resources.** The detection script from Step 1 already reports this as the `hasExcludeFromManifest` field — no separate scan is needed.
59+
**Before running `azd init --from-code`, understand whether the app may contain local-only custom resources.** The `gather-aspire-info` run from Step 1 already reports this as the `hasExcludeFromManifest` field — no separate scan is needed.
6060

6161
- `hasExcludeFromManifest=true` → the AppHost source uses `.ExcludeFromManifest()`.
6262
- `hasExcludeFromManifest=false` → no such usage was found.
@@ -177,16 +177,16 @@ This step **MUST** run BEFORE `azd up` or `azd provision`. Skipping it causes a
177177

178178
**1. Detect Azure Functions in the AppHost:**
179179

180-
Use the `hasFunctions` field from the Step 1 detection script. If you have not run it yet (or the workspace changed), re-run it:
180+
Use the `hasFunctions` field from the Step 1 `gather-aspire-info` run. If you have not run it yet (or the workspace changed), re-run it:
181181

182182
**bash:**
183183
```bash
184-
./scripts/detect-aspire.sh [workspace-root]
184+
./scripts/gather-aspire-info.sh [workspace-root]
185185
```
186186

187187
**PowerShell:**
188188
```powershell
189-
./scripts/detect-aspire.ps1 -WorkspaceRoot <workspace-root>
189+
./scripts/gather-aspire-info.ps1 -WorkspaceRoot <workspace-root>
190190
```
191191

192192
**If `hasFunctions=false` → skip this step.**

plugin/skills/azure-prepare/references/generate.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Generate infrastructure and configuration files based on selected recipe.
44

55
## ⛔ CRITICAL: Check for .NET Aspire Projects FIRST
66

7-
**MANDATORY: Before generating any files, always check for .NET Aspire projects** using the detection script ([detect-aspire.sh](scripts/detect-aspire.sh) / [detect-aspire.ps1](scripts/detect-aspire.ps1)). It runs the full detection sequence and prints `key=value` fields plus a summary:
7+
**MANDATORY: Before generating any files, always check for .NET Aspire projects** using the presence script ([detect-aspire.sh](scripts/detect-aspire.sh) / [detect-aspire.ps1](scripts/detect-aspire.ps1)). It reports `isAspire` and `appHostPath`:
88

99
**bash:**
1010
```bash
@@ -16,7 +16,7 @@ Generate infrastructure and configuration files based on selected recipe.
1616
./scripts/detect-aspire.ps1 -WorkspaceRoot <workspace-root>
1717
```
1818

19-
If the result includes `isAspire=true`, treat the project as Aspire. See [aspire.md](aspire.md) Step 1 for the full list of fields the script reports.
19+
If the result includes `isAspire=true`, treat the project as Aspire. See [aspire.md](aspire.md) Step 1 for gathering the full set of Aspire facts with `gather-aspire-info`.
2020

2121
**If Aspire is detected:**
2222
1.**STOP** - Do NOT manually create `azure.yaml`

plugin/skills/azure-prepare/references/recipes/azd/aspire.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
**⛔ MANDATORY: For .NET Aspire projects, NEVER manually create azure.yaml. Use `azd init --from-code` instead.**
44

5+
> 📋 **Gather facts first:** Run [gather-aspire-info.sh](../../scripts/gather-aspire-info.sh) / [gather-aspire-info.ps1](../../scripts/gather-aspire-info.ps1) to capture the `appHostPath`, `hasExcludeFromManifest`, and Azure Functions signals before generating anything. See [aspire.md](../../aspire.md) Step 1 for the full field list.
6+
57
## Workflow
68

79
### ⛔ DO NOT (Wrong Approach)

plugin/skills/azure-prepare/references/scan.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ Before classifying components, grep dependency files for SDKs that require a spe
5656

5757
### .NET Aspire Detection
5858

59-
Always check **.NET Aspire projects** by running the detection script ([detect-aspire.sh](scripts/detect-aspire.sh) / [detect-aspire.ps1](scripts/detect-aspire.ps1)), which reports `isAspire` along with AppHost path, `ExcludeFromManifest`, and Azure Functions signals:
59+
Always check **.NET Aspire projects** by running the presence script ([detect-aspire.sh](scripts/detect-aspire.sh) / [detect-aspire.ps1](scripts/detect-aspire.ps1)), which reports just `isAspire` and `appHostPath` so you can branch quickly. Once Aspire is confirmed, [aspire.md](aspire.md) gathers the deeper facts (`ExcludeFromManifest`, Azure Functions signals) via `gather-aspire-info`:
6060

6161
**bash:**
6262
```bash
@@ -68,13 +68,13 @@ Always check **.NET Aspire projects** by running the detection script ([detect-a
6868
./scripts/detect-aspire.ps1 -WorkspaceRoot <workspace-root>
6969
```
7070

71-
A project is Aspire when the script returns `isAspire=true` (a `*.AppHost.csproj` or an `Aspire.Hosting` / `Aspire.Hosting.AppHost` / `Aspire.AppHost.Sdk` package reference was found). See [aspire.md](aspire.md) Step 1 for the full field list.
71+
A project is Aspire when the script returns `isAspire=true` (a `*.AppHost.csproj` or an `Aspire.Hosting` / `Aspire.Hosting.AppHost` / `Aspire.AppHost.Sdk` package reference was found). See [aspire.md](aspire.md) Step 1 for the full field list gathered by `gather-aspire-info`.
7272

7373
**When Aspire is detected:**
7474
- Use `azd init --from-code -e <environment-name>` instead of manual azure.yaml creation
7575
- The `--from-code` flag automatically detects the AppHost and generates appropriate configuration
7676
- The `-e` flag is **required** for non-interactive environments (agents, CI/CD)
77-
- ⚠️ **CRITICAL:** If the script reports `hasFunctions=true` and `secretStorageConfigured=false`, you **MUST** add `.WithEnvironment("AzureWebJobsSecretStorageType", "Files")` to the Functions builder chain BEFORE deployment. Without this, Functions will fail at startup with `Secret initialization from Blob storage failed`. See [aspire.md](aspire.md) Step 4b for the complete fix procedure.
77+
- ⚠️ **CRITICAL:** Run `gather-aspire-info` (per [aspire.md](aspire.md) Step 1). If it reports `hasFunctions=true` and `secretStorageConfigured=false`, you **MUST** add `.WithEnvironment("AzureWebJobsSecretStorageType", "Files")` to the Functions builder chain BEFORE deployment. Without this, Functions will fail at startup with `Secret initialization from Blob storage failed`. See [aspire.md](aspire.md) Step 4b for the complete fix procedure.
7878
- See [aspire.md](aspire.md) for detailed Aspire-specific guidance
7979

8080
## Output
Lines changed: 8 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,17 @@
11
<#
22
.SYNOPSIS
3-
Detects whether a workspace is a .NET Aspire application and gathers the
4-
facts the azure-prepare skill needs to plan deployment.
3+
Presence check: determines whether a workspace is a .NET Aspire application.
4+
Use this at detection/routing points where you only need a yes/no answer.
5+
To gather the deeper deployment facts (ExcludeFromManifest, Azure Functions,
6+
secret storage, AppHost source dir), use gather-aspire-info.ps1 instead.
57
68
.DESCRIPTION
7-
Runs the full deterministic detection sequence in one pass:
9+
Runs the minimal deterministic presence sequence:
810
1. Find the AppHost project (*.AppHost.csproj)
911
2. Confirm Aspire.Hosting or Aspire.AppHost.Sdk package references
10-
3. Derive the AppHost source directory
11-
4. Scan the AppHost *.cs for ExcludeFromManifest (informational)
12-
5. Scan for AddAzureFunctionsProject, and if present, check whether
13-
AzureWebJobsSecretStorageType is already configured
1412
15-
Output: key=value lines the agent can branch on, followed by a
16-
human-readable summary. The remediation decision (whether/how to add
17-
.WithEnvironment("AzureWebJobsSecretStorageType", "Files")) stays with the agent.
13+
Output: key=value lines (isAspire, appHostPath) followed by a short
14+
human-readable summary.
1815
1916
.PARAMETER WorkspaceRoot
2017
Workspace root directory to scan. Defaults to the current directory.
@@ -42,10 +39,6 @@ if (-not (Test-Path -LiteralPath $WorkspaceRoot -PathType Container)) {
4239
# Defaults (emitted when the workspace is not an Aspire app)
4340
$isAspire = $false
4441
$appHostPath = ""
45-
$appHostDir = ""
46-
$hasExcludeFromManifest = $false
47-
$hasFunctions = $false
48-
$secretStorageConfigured = $false
4942

5043
# Step 1: Find the AppHost project (sorted for deterministic selection)
5144
$appHostProject = @(Get-ChildItem -LiteralPath $WorkspaceRoot -Recurse -Filter "*.AppHost.csproj" -File -ErrorAction SilentlyContinue |
@@ -72,27 +65,6 @@ if ($appHostProject) {
7265
} finally {
7366
Pop-Location
7467
}
75-
76-
# Step 3: Derive the AppHost source directory
77-
$appHostDir = $appHostPath -replace '/[^/]+$', ''
78-
$appHostDirFull = $appHostProject.DirectoryName
79-
80-
# Scan AppHost *.cs, excluding bin/ and obj/ build output
81-
$appHostCs = Get-ChildItem -LiteralPath $appHostDirFull -Recurse -Filter "*.cs" -File -ErrorAction SilentlyContinue |
82-
Where-Object { $_.FullName -notmatch '[\\/](bin|obj)[\\/]' }
83-
84-
# Step 4: Scan the AppHost source for ExcludeFromManifest (informational)
85-
if ($appHostCs -and ($appHostCs | Select-String -Pattern "ExcludeFromManifest" -SimpleMatch -List -ErrorAction SilentlyContinue)) {
86-
$hasExcludeFromManifest = $true
87-
}
88-
89-
# Step 5: Detect Azure Functions and secret-storage configuration
90-
if ($appHostCs -and ($appHostCs | Select-String -Pattern "AddAzureFunctionsProject" -SimpleMatch -List -ErrorAction SilentlyContinue)) {
91-
$hasFunctions = $true
92-
if ($appHostCs | Select-String -Pattern "AzureWebJobsSecretStorageType" -SimpleMatch -List -ErrorAction SilentlyContinue) {
93-
$secretStorageConfigured = $true
94-
}
95-
}
9668
}
9769

9870
function ConvertTo-Lower([bool]$value) {
@@ -102,10 +74,6 @@ function ConvertTo-Lower([bool]$value) {
10274
# Machine-readable result
10375
Write-Output "isAspire=$(ConvertTo-Lower $isAspire)"
10476
Write-Output "appHostPath=$appHostPath"
105-
Write-Output "appHostDir=$appHostDir"
106-
Write-Output "hasExcludeFromManifest=$(ConvertTo-Lower $hasExcludeFromManifest)"
107-
Write-Output "hasFunctions=$(ConvertTo-Lower $hasFunctions)"
108-
Write-Output "secretStorageConfigured=$(ConvertTo-Lower $secretStorageConfigured)"
10977

11078
# Human-readable summary
11179
Write-Output ""
@@ -117,23 +85,7 @@ if (-not $isAspire) {
11785

11886
if ($appHostPath) {
11987
Write-Output "- .NET Aspire app detected. AppHost project: $appHostPath"
120-
Write-Output "- AppHost source directory: $appHostDir"
12188
} else {
12289
Write-Output "- Aspire.Hosting / Aspire.AppHost.Sdk package reference found, but no *.AppHost.csproj was located."
12390
}
124-
125-
if ($hasExcludeFromManifest) {
126-
Write-Output "- ExcludeFromManifest found in AppHost source (informational): the app may contain local-only resources."
127-
} else {
128-
Write-Output "- No ExcludeFromManifest usage found in AppHost source."
129-
}
130-
131-
if ($hasFunctions) {
132-
if ($secretStorageConfigured) {
133-
Write-Output "- AddAzureFunctionsProject found; AzureWebJobsSecretStorageType is configured in the AppHost source."
134-
} else {
135-
Write-Output "- AddAzureFunctionsProject found; AzureWebJobsSecretStorageType is not configured in the AppHost source."
136-
}
137-
} else {
138-
Write-Output "- AddAzureFunctionsProject not found in AppHost source."
139-
}
91+
Write-Output "- Run gather-aspire-info.ps1 for AppHost source dir, ExcludeFromManifest, and Azure Functions details."
Lines changed: 8 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
11
#!/usr/bin/env bash
22
# detect-aspire.sh
3-
# Detects whether a workspace is a .NET Aspire application and gathers the
4-
# facts the azure-prepare skill needs to plan deployment.
3+
# Presence check: determines whether a workspace is a .NET Aspire application.
4+
# Use this at detection/routing points where you only need a yes/no answer.
5+
# To gather the deeper deployment facts (ExcludeFromManifest, Azure Functions,
6+
# secret storage, AppHost source dir), use gather-aspire-info.sh instead.
57
#
6-
# It runs the full deterministic detection sequence in one pass:
8+
# It runs the minimal deterministic presence sequence:
79
# 1. Find the AppHost project (*.AppHost.csproj)
810
# 2. Confirm Aspire.Hosting or Aspire.AppHost.Sdk package references
9-
# 3. Derive the AppHost source directory
10-
# 4. Scan the AppHost *.cs for ExcludeFromManifest (informational)
11-
# 5. Scan for AddAzureFunctionsProject, and if present, check whether
12-
# AzureWebJobsSecretStorageType is already configured
1311
#
14-
# Output: key=value lines the agent can branch on, followed by a human-readable
15-
# summary. The remediation decision (whether/how to add
16-
# .WithEnvironment("AzureWebJobsSecretStorageType", "Files")) stays with the agent.
12+
# Output: key=value lines (isAspire, appHostPath) followed by a short
13+
# human-readable summary.
1714
#
1815
# Usage:
1916
# ./detect-aspire.sh [workspace-root]
@@ -49,21 +46,9 @@ csproj_match() {
4946
[ -n "$(find "$dir" -type f -name '*.csproj' -exec grep -lE "$pattern" {} + 2>/dev/null)" ]
5047
}
5148

52-
# Portable recursive match over *.cs files, pruning bin/ and obj/ build output.
53-
# Returns 0 if the extended-regex pattern is found in any *.cs under the directory.
54-
cs_match() {
55-
local dir="$1" pattern="$2"
56-
[ -n "$(find "$dir" -type d \( -name bin -o -name obj \) -prune -o \
57-
-type f -name '*.cs' -exec grep -lE "$pattern" {} + 2>/dev/null)" ]
58-
}
59-
6049
# Defaults (emitted when the workspace is not an Aspire app)
6150
IS_ASPIRE="false"
6251
APPHOST_PATH=""
63-
APPHOST_DIR=""
64-
HAS_EXCLUDE_FROM_MANIFEST="false"
65-
HAS_FUNCTIONS="false"
66-
SECRET_STORAGE_CONFIGURED="false"
6752

6853
# Step 1: Find the AppHost project (case-insensitive sort for deterministic,
6954
# cross-shell-consistent selection)
@@ -81,32 +66,11 @@ fi
8166

8267
if [ -n "$APPHOST_RAW" ]; then
8368
APPHOST_PATH=$(to_relative "$APPHOST_RAW")
84-
85-
# Step 3: Derive the AppHost source directory (scan the real path on disk)
86-
APPHOST_DIR=$(dirname "$APPHOST_PATH")
87-
APPHOST_DIR_RAW=$(dirname "$APPHOST_RAW")
88-
89-
# Step 4: Scan the AppHost source for ExcludeFromManifest (informational)
90-
if cs_match "$APPHOST_DIR_RAW" "ExcludeFromManifest"; then
91-
HAS_EXCLUDE_FROM_MANIFEST="true"
92-
fi
93-
94-
# Step 5: Detect Azure Functions and secret-storage configuration
95-
if cs_match "$APPHOST_DIR_RAW" "AddAzureFunctionsProject"; then
96-
HAS_FUNCTIONS="true"
97-
if cs_match "$APPHOST_DIR_RAW" "AzureWebJobsSecretStorageType"; then
98-
SECRET_STORAGE_CONFIGURED="true"
99-
fi
100-
fi
10169
fi
10270

10371
# Machine-readable result
10472
echo "isAspire=$IS_ASPIRE"
10573
echo "appHostPath=$APPHOST_PATH"
106-
echo "appHostDir=$APPHOST_DIR"
107-
echo "hasExcludeFromManifest=$HAS_EXCLUDE_FROM_MANIFEST"
108-
echo "hasFunctions=$HAS_FUNCTIONS"
109-
echo "secretStorageConfigured=$SECRET_STORAGE_CONFIGURED"
11074

11175
# Human-readable summary
11276
echo ""
@@ -118,23 +82,7 @@ fi
11882

11983
if [ -n "$APPHOST_PATH" ]; then
12084
echo "- .NET Aspire app detected. AppHost project: $APPHOST_PATH"
121-
echo "- AppHost source directory: $APPHOST_DIR"
12285
else
12386
echo "- Aspire.Hosting / Aspire.AppHost.Sdk package reference found, but no *.AppHost.csproj was located."
12487
fi
125-
126-
if [ "$HAS_EXCLUDE_FROM_MANIFEST" = "true" ]; then
127-
echo "- ExcludeFromManifest found in AppHost source (informational): the app may contain local-only resources."
128-
else
129-
echo "- No ExcludeFromManifest usage found in AppHost source."
130-
fi
131-
132-
if [ "$HAS_FUNCTIONS" = "true" ]; then
133-
if [ "$SECRET_STORAGE_CONFIGURED" = "true" ]; then
134-
echo "- AddAzureFunctionsProject found; AzureWebJobsSecretStorageType is configured in the AppHost source."
135-
else
136-
echo "- AddAzureFunctionsProject found; AzureWebJobsSecretStorageType is not configured in the AppHost source."
137-
fi
138-
else
139-
echo "- AddAzureFunctionsProject not found in AppHost source."
140-
fi
88+
echo "- Run gather-aspire-info.sh for AppHost source dir, ExcludeFromManifest, and Azure Functions details."

0 commit comments

Comments
 (0)