@atlashub/smartstack-cli 4.38.0 → 4.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/templates/agents/efcore/migration.md +17 -18
- package/templates/agents/efcore/rebase-snapshot.md +106 -23
- package/templates/agents/efcore/squash.md +131 -33
- package/templates/agents/gitflow/cleanup.md +17 -6
- package/templates/skills/dev-start/SKILL.md +53 -24
- package/templates/skills/efcore/SKILL.md +55 -12
- package/templates/skills/efcore/references/database-operations.md +2 -3
- package/templates/skills/efcore/references/seed-methods.md +1 -1
- package/templates/skills/efcore/references/shared-init-functions.md +8 -8
- package/templates/skills/efcore/references/zero-downtime-patterns.md +2 -0
- package/templates/skills/gitflow/phases/cleanup.md +33 -3
package/package.json
CHANGED
|
@@ -47,6 +47,7 @@ SmartStack uses two separate DbContexts with separate migration histories:
|
|
|
47
47
|
| `extensions` | ExtensionsDbContext | `extensions` | `extensions.__EFMigrationsHistory` | Client-specific entities |
|
|
48
48
|
|
|
49
49
|
**Detection Logic:**
|
|
50
|
+
0. If `--context` flag provided → use specified DbContext (skip auto-detection)
|
|
50
51
|
1. If project contains `SmartStack.Domain` → use `CoreDbContext`
|
|
51
52
|
2. If project contains `{ClientName}.Domain` with SmartStack NuGet → use `ExtensionsDbContext`
|
|
52
53
|
3. If unclear → ask user which context to use
|
|
@@ -80,6 +81,8 @@ MCP returns the fully compliant name: `{context}_v{version}_{sequence}_{Descript
|
|
|
80
81
|
|
|
81
82
|
## Commands
|
|
82
83
|
|
|
84
|
+
> **IMPORTANT:** Project detection, migration search, and migration creation should run in a single Bash call to preserve shell variables (`$INFRA_PROJECT`, `$MIGRATIONS_DIR`, `$STARTUP_PROJECT`).
|
|
85
|
+
|
|
83
86
|
**Prerequisite — ensure `dotnet ef` is on PATH (MANDATORY before any command):**
|
|
84
87
|
|
|
85
88
|
```bash
|
|
@@ -96,23 +99,29 @@ fi
|
|
|
96
99
|
> NEVER rely on `$HOME` alone — in WSL it resolves to `/home/{user}` where .NET SDK is absent.
|
|
97
100
|
|
|
98
101
|
```bash
|
|
102
|
+
# Detect project paths
|
|
103
|
+
INFRA_PROJECT=$(find src -name "*Infrastructure.csproj" | head -1)
|
|
104
|
+
INFRA_DIR=$(dirname "$INFRA_PROJECT")
|
|
105
|
+
MIGRATIONS_DIR=$(find "$INFRA_DIR" -type d -name "Migrations" -path "*/Persistence/*" | head -1)
|
|
106
|
+
STARTUP_PROJECT=$(find src -name "*.Api.csproj" | head -1)
|
|
107
|
+
|
|
99
108
|
# Existing migrations for Core
|
|
100
|
-
find
|
|
109
|
+
find "$MIGRATIONS_DIR" -name "core_*.cs" | grep -v Designer | grep -v Snapshot
|
|
101
110
|
|
|
102
111
|
# Existing migrations for Extensions
|
|
103
|
-
find
|
|
112
|
+
find "$MIGRATIONS_DIR" -name "ext_*.cs" | grep -v Designer | grep -v Snapshot
|
|
104
113
|
|
|
105
|
-
# Create Core migration (use
|
|
106
|
-
dotnet ef migrations add $
|
|
114
|
+
# Create Core migration (use MCP_MIGRATION_NAME from suggest_migration)
|
|
115
|
+
dotnet ef migrations add "$MCP_MIGRATION_NAME" --context CoreDbContext -o Persistence/Migrations --project "$INFRA_PROJECT" --startup-project "$STARTUP_PROJECT"
|
|
107
116
|
|
|
108
|
-
# Create Extensions migration (use
|
|
109
|
-
dotnet ef migrations add $
|
|
117
|
+
# Create Extensions migration (use MCP_MIGRATION_NAME from suggest_migration)
|
|
118
|
+
dotnet ef migrations add "$MCP_MIGRATION_NAME" --context ExtensionsDbContext -o Persistence/Migrations --project "$INFRA_PROJECT" --startup-project "$STARTUP_PROJECT"
|
|
110
119
|
|
|
111
120
|
# Delete existing migration
|
|
112
|
-
rm
|
|
121
|
+
rm -f "$MIGRATIONS_DIR"/*${OLD_NAME}*.cs
|
|
113
122
|
```
|
|
114
123
|
|
|
115
|
-
> **Note:** `$
|
|
124
|
+
> **Note:** `$MCP_MIGRATION_NAME` is obtained via MCP call, never computed locally.
|
|
116
125
|
|
|
117
126
|
## SQL Objects (NO injection needed)
|
|
118
127
|
|
|
@@ -186,16 +195,6 @@ After rebase on develop:
|
|
|
186
195
|
3. Report the error with dotnet output
|
|
187
196
|
4. Suggest corrective action
|
|
188
197
|
|
|
189
|
-
## SQL in Migrations (STRICT RULE)
|
|
190
|
-
|
|
191
|
-
**FORBIDDEN:** Raw SQL inline in migrations (e.g., `migrationBuilder.Sql("CREATE FUNCTION ...")`)
|
|
192
|
-
|
|
193
|
-
**ONLY** SQL objects defined in `src/{Project}.Infrastructure/Persistence/SqlObjects/` are authorized.
|
|
194
|
-
|
|
195
|
-
- All SQL objects (functions, views, procedures, TVFs) MUST be `.sql` files in `Persistence/SqlObjects/`
|
|
196
|
-
- SQL objects are applied **automatically at startup** via `SqlObjectHelper.ApplyAllAsync()` — no migration injection needed
|
|
197
|
-
- **Never** write raw SQL directly inside migration `Up()` or `Down()` methods
|
|
198
|
-
|
|
199
198
|
## Important Notes
|
|
200
199
|
|
|
201
200
|
- **Core migrations** go to `CoreDbContextModelSnapshot.cs`
|
|
@@ -21,12 +21,9 @@ if [ -f .git ] && grep -q '^gitdir: [A-Za-z]:' .git 2>/dev/null && grep -qi micr
|
|
|
21
21
|
fi
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
## SQL
|
|
24
|
+
## SQL Objects
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
**ONLY** SQL objects defined in `src/{Project}.Infrastructure/Persistence/SqlObjects/` are authorized.
|
|
29
|
-
SQL objects are applied **automatically at startup** via `SqlObjectHelper.ApplyAllAsync()` — no migration injection needed.
|
|
26
|
+
SQL objects (TVFs, views, SPs) are applied **at startup** via `SqlObjectHelper.ApplyAllAsync()` — never inject in migrations.
|
|
30
27
|
|
|
31
28
|
## Workflow
|
|
32
29
|
|
|
@@ -36,42 +33,128 @@ SQL objects are applied **automatically at startup** via `SqlObjectHelper.ApplyA
|
|
|
36
33
|
4. **Regenerate** consolidated migration
|
|
37
34
|
5. **Validate** build OK
|
|
38
35
|
|
|
36
|
+
## Shared Functions
|
|
37
|
+
|
|
38
|
+
> **Reference:** See `references/shared-init-functions.md` for `ensure_dotnet_ef()`, `detect_efcore_project()`, `detect_dbcontext()`, `determine_base_branch()`.
|
|
39
|
+
|
|
39
40
|
## Key Commands
|
|
40
41
|
|
|
42
|
+
> **IMPORTANT:** Steps 1-2 MUST run in a single Bash call to preserve shell variables. Step 2b (confirm) is a separate interactive call. Step 3 requires variables from steps 1-2.
|
|
43
|
+
|
|
44
|
+
### Step 1: Initialize
|
|
45
|
+
|
|
41
46
|
```bash
|
|
42
|
-
#
|
|
43
|
-
|
|
44
|
-
|
|
47
|
+
# Determine parent branch
|
|
48
|
+
CURRENT_BRANCH=$(git branch --show-current)
|
|
49
|
+
case "$CURRENT_BRANCH" in
|
|
50
|
+
feature/*) BASE_BRANCH="develop"; BRANCH_TYPE="feature" ;;
|
|
51
|
+
release/*) BASE_BRANCH="main"; BRANCH_TYPE="release" ;;
|
|
52
|
+
hotfix/*) BASE_BRANCH="main"; BRANCH_TYPE="hotfix" ;;
|
|
53
|
+
develop) BASE_BRANCH="main"; BRANCH_TYPE="develop" ;;
|
|
54
|
+
main|master) echo "BLOCKED: Cannot rebase on main/master"; exit 1 ;;
|
|
55
|
+
*) BASE_BRANCH="develop"; BRANCH_TYPE="unknown" ;;
|
|
56
|
+
esac
|
|
57
|
+
|
|
58
|
+
# Detect project
|
|
59
|
+
INFRA_PROJECT=$(find src -name "*Infrastructure.csproj" | head -1)
|
|
60
|
+
INFRA_DIR=$(dirname "$INFRA_PROJECT")
|
|
61
|
+
MIGRATIONS_DIR=$(find "$INFRA_DIR" -type d -name "Migrations" -path "*/Persistence/*" | head -1)
|
|
62
|
+
MIGRATIONS_REL=$(realpath --relative-to="$(git rev-parse --show-toplevel)" "$MIGRATIONS_DIR")
|
|
63
|
+
STARTUP_PROJECT=$(find src -name "*.Api.csproj" | head -1)
|
|
64
|
+
|
|
65
|
+
# Detect DbContext (override via --context flag, else auto-detect)
|
|
66
|
+
CONTEXT_OVERRIDE="${CONTEXT:-}"
|
|
67
|
+
if [ "$CONTEXT_OVERRIDE" = "CoreDbContext" ] || [ "$CONTEXT_OVERRIDE" = "core" ]; then
|
|
68
|
+
DBCONTEXT="CoreDbContext"; DBCONTEXT_TYPE="core"
|
|
69
|
+
elif [ "$CONTEXT_OVERRIDE" = "ExtensionsDbContext" ] || [ "$CONTEXT_OVERRIDE" = "extensions" ]; then
|
|
70
|
+
DBCONTEXT="ExtensionsDbContext"; DBCONTEXT_TYPE="extensions"
|
|
71
|
+
elif find src -type d -name "SmartStack.Domain" | grep -q .; then
|
|
72
|
+
DBCONTEXT="CoreDbContext"; DBCONTEXT_TYPE="core"
|
|
73
|
+
else
|
|
74
|
+
DBCONTEXT="ExtensionsDbContext"; DBCONTEXT_TYPE="extensions"
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
echo "Branch: $CURRENT_BRANCH → Base: $BASE_BRANCH"
|
|
78
|
+
echo "Migrations: $MIGRATIONS_DIR"
|
|
79
|
+
echo "DbContext: $DBCONTEXT"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Step 2: Backup
|
|
45
83
|
|
|
46
|
-
|
|
84
|
+
```bash
|
|
47
85
|
BACKUP_DIR=".claude/gitflow/backup/migrations/rebase_$(date +%Y%m%d_%H%M%S)"
|
|
48
86
|
mkdir -p "$BACKUP_DIR"
|
|
49
|
-
cp
|
|
87
|
+
cp "$MIGRATIONS_DIR"/*.cs "$BACKUP_DIR/"
|
|
88
|
+
echo "Backup: $BACKUP_DIR"
|
|
89
|
+
```
|
|
50
90
|
|
|
51
|
-
|
|
91
|
+
### Step 2b: Confirm (skip if `--force`)
|
|
92
|
+
|
|
93
|
+
If `--force` flag is NOT set:
|
|
94
|
+
|
|
95
|
+
```javascript
|
|
96
|
+
AskUserQuestion({
|
|
97
|
+
question: "Rebase snapshot will:\n1. Reset ModelSnapshot to " + BASE_BRANCH + "\n2. Delete branch-specific migrations\n3. Regenerate consolidated migration\n\nBranch-specific migrations to recreate:\n" + BRANCH_ONLY_MIGS + "\n\nProceed?",
|
|
98
|
+
header: "EF Core Rebase-Snapshot",
|
|
99
|
+
options: [
|
|
100
|
+
{ label: "Yes, rebase", description: "Backup + rebase snapshot" },
|
|
101
|
+
{ label: "Cancel", description: "Do nothing" }
|
|
102
|
+
]
|
|
103
|
+
})
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Step 3: Identify branch-specific migrations + Reset
|
|
107
|
+
|
|
108
|
+
```bash
|
|
52
109
|
git fetch origin "$BASE_BRANCH"
|
|
53
|
-
|
|
110
|
+
|
|
111
|
+
# List parent branch migrations (names only)
|
|
112
|
+
BASE_MIGS=$(git ls-tree -r --name-only "origin/$BASE_BRANCH" -- "$MIGRATIONS_REL/" 2>/dev/null | grep "\.cs$" | grep -v "Designer\|Snapshot" | xargs -I{} basename {} .cs)
|
|
113
|
+
|
|
114
|
+
# List local migrations (names only)
|
|
115
|
+
LOCAL_MIGS=$(find "$MIGRATIONS_DIR" -maxdepth 1 -name "*.cs" ! -name "*Designer*" ! -name "*Snapshot*" | xargs -I{} basename {} .cs)
|
|
116
|
+
|
|
117
|
+
# Calculate branch-specific migrations (in local but NOT in base)
|
|
118
|
+
BRANCH_ONLY_MIGS=""
|
|
119
|
+
for mig in $LOCAL_MIGS; do
|
|
120
|
+
if ! echo "$BASE_MIGS" | grep -qx "$mig"; then
|
|
121
|
+
BRANCH_ONLY_MIGS="$BRANCH_ONLY_MIGS $mig"
|
|
122
|
+
fi
|
|
123
|
+
done
|
|
124
|
+
BRANCH_ONLY_MIGS=$(echo "$BRANCH_ONLY_MIGS" | xargs)
|
|
125
|
+
|
|
126
|
+
echo "Branch-specific migrations to recreate:"
|
|
127
|
+
for mig in $BRANCH_ONLY_MIGS; do echo " - $mig"; done
|
|
128
|
+
|
|
129
|
+
# Reset snapshot to parent branch
|
|
130
|
+
git checkout "origin/$BASE_BRANCH" -- "$MIGRATIONS_REL"/*ModelSnapshot.cs
|
|
54
131
|
|
|
55
132
|
# Retrieve parent branch migrations
|
|
56
|
-
BASE_MIGS=$(git ls-tree -r --name-only "origin/$BASE_BRANCH" -- Migrations/ | grep "\.cs$" | grep -v "Designer\|Snapshot")
|
|
57
133
|
for base_mig in $BASE_MIGS; do
|
|
58
|
-
|
|
59
|
-
git checkout "origin/$BASE_BRANCH" -- "
|
|
60
|
-
|
|
134
|
+
git checkout "origin/$BASE_BRANCH" -- "$MIGRATIONS_REL/${base_mig}.cs" 2>/dev/null || true
|
|
135
|
+
git checkout "origin/$BASE_BRANCH" -- "$MIGRATIONS_REL/${base_mig}.Designer.cs" 2>/dev/null || true
|
|
136
|
+
done
|
|
137
|
+
|
|
138
|
+
# Delete branch-specific migrations by exact name (NOT by branch type pattern)
|
|
139
|
+
for mig in $BRANCH_ONLY_MIGS; do
|
|
140
|
+
rm -f "$MIGRATIONS_DIR"/*"${mig}"*.cs
|
|
141
|
+
echo " Deleted: $mig"
|
|
61
142
|
done
|
|
143
|
+
```
|
|
62
144
|
|
|
63
|
-
|
|
64
|
-
rm -f Migrations/*${BRANCH_TYPE}_*.cs
|
|
65
|
-
rm -f Migrations/*${BRANCH_TYPE}_*.Designer.cs
|
|
145
|
+
### Step 4: Regenerate + Validate
|
|
66
146
|
|
|
147
|
+
```bash
|
|
67
148
|
# Regenerate with MCP naming - MANDATORY
|
|
68
149
|
mcp__smartstack__suggest_migration({ description: "...", context: DBCONTEXT_TYPE })
|
|
69
|
-
# Result: core_v1.7.0_001_UserAuthConsolidated
|
|
70
150
|
|
|
71
|
-
dotnet ef migrations add "$
|
|
151
|
+
dotnet ef migrations add "$MCP_MIGRATION_NAME" \
|
|
152
|
+
--context "$DBCONTEXT" \
|
|
153
|
+
--project "$INFRA_PROJECT" \
|
|
154
|
+
--startup-project "$STARTUP_PROJECT" \
|
|
155
|
+
-o Persistence/Migrations
|
|
72
156
|
|
|
73
|
-
# NOTE: SQL objects
|
|
74
|
-
# via SqlObjectHelper.ApplyAllAsync() — no injection needed in migrations.
|
|
157
|
+
# NOTE: SQL objects are applied automatically at startup — no injection needed.
|
|
75
158
|
|
|
76
159
|
# Validate
|
|
77
160
|
dotnet build
|
|
@@ -21,12 +21,9 @@ if [ -f .git ] && grep -q '^gitdir: [A-Za-z]:' .git 2>/dev/null && grep -qi micr
|
|
|
21
21
|
fi
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
## SQL
|
|
24
|
+
## SQL Objects
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
**ONLY** SQL objects defined in `src/{Project}.Infrastructure/Persistence/SqlObjects/` are authorized.
|
|
29
|
-
SQL objects are applied **automatically at startup** via `SqlObjectHelper.ApplyAllAsync()` — no migration injection needed.
|
|
26
|
+
SQL objects (TVFs, views, SPs) are applied **at startup** via `SqlObjectHelper.ApplyAllAsync()` — never inject in migrations.
|
|
30
27
|
|
|
31
28
|
## Core Principle
|
|
32
29
|
|
|
@@ -53,57 +50,158 @@ WARNING: NEVER retrieve only the snapshot without the migrations!
|
|
|
53
50
|
7. **Create**: Create consolidated migration (MCP mandatory)
|
|
54
51
|
8. **Validate**: Build + script OK
|
|
55
52
|
|
|
53
|
+
## Shared Functions
|
|
54
|
+
|
|
55
|
+
> **Reference:** See `references/shared-init-functions.md` for `ensure_dotnet_ef()`, `detect_efcore_project()`, `detect_dbcontext()`, `determine_base_branch()`, `block_production()`.
|
|
56
|
+
|
|
56
57
|
## Key Commands
|
|
57
58
|
|
|
59
|
+
> **IMPORTANT:** Steps 1-3 MUST run in a single Bash call to preserve shell variables ($BASE_BRANCH, $MIGRATIONS_DIR, $BRANCH_ONLY_MIGS, etc.). Step 4 (guard) and Step 5 (confirm) are separate calls. Step 6 requires variables from steps 1-3.
|
|
60
|
+
|
|
61
|
+
### Step 1: Initialize
|
|
62
|
+
|
|
58
63
|
```bash
|
|
59
|
-
# Determine parent branch
|
|
60
|
-
|
|
61
|
-
|
|
64
|
+
# Determine parent branch
|
|
65
|
+
CURRENT_BRANCH=$(git branch --show-current)
|
|
66
|
+
case "$CURRENT_BRANCH" in
|
|
67
|
+
feature/*) BASE_BRANCH="develop"; BRANCH_TYPE="feature" ;;
|
|
68
|
+
release/*) BASE_BRANCH="main"; BRANCH_TYPE="release" ;;
|
|
69
|
+
hotfix/*) BASE_BRANCH="main"; BRANCH_TYPE="hotfix" ;;
|
|
70
|
+
develop) BASE_BRANCH="main"; BRANCH_TYPE="develop" ;;
|
|
71
|
+
main|master) echo "BLOCKED: Cannot squash on main/master"; exit 1 ;;
|
|
72
|
+
*) BASE_BRANCH="develop"; BRANCH_TYPE="unknown" ;;
|
|
73
|
+
esac
|
|
74
|
+
echo "Branch: $CURRENT_BRANCH ($BRANCH_TYPE) → Base: $BASE_BRANCH"
|
|
75
|
+
|
|
76
|
+
# Context override from --context flag (passed via agent prompt)
|
|
77
|
+
CONTEXT_OVERRIDE="${CONTEXT:-}"
|
|
78
|
+
```
|
|
62
79
|
|
|
63
|
-
|
|
64
|
-
BASE_MIGS=$(git ls-tree -r --name-only "origin/$BASE_BRANCH" -- Migrations/ | grep "\.cs$" | grep -v "Designer\|Snapshot")
|
|
65
|
-
LOCAL_MIGS=$(find Migrations -name "*.cs" | grep -v Designer | grep -v Snapshot)
|
|
80
|
+
### Step 2: Detect project and migrations directory
|
|
66
81
|
|
|
82
|
+
```bash
|
|
83
|
+
# Find Infrastructure project and migrations directory
|
|
84
|
+
INFRA_PROJECT=$(find src -name "*Infrastructure.csproj" | head -1)
|
|
85
|
+
INFRA_DIR=$(dirname "$INFRA_PROJECT")
|
|
86
|
+
MIGRATIONS_DIR=$(find "$INFRA_DIR" -type d -name "Migrations" -path "*/Persistence/*" | head -1)
|
|
87
|
+
|
|
88
|
+
# Detect DbContext (override via --context flag, else auto-detect)
|
|
89
|
+
if [ "$CONTEXT_OVERRIDE" = "CoreDbContext" ] || [ "$CONTEXT_OVERRIDE" = "core" ]; then
|
|
90
|
+
DBCONTEXT="CoreDbContext"; DBCONTEXT_TYPE="core"
|
|
91
|
+
elif [ "$CONTEXT_OVERRIDE" = "ExtensionsDbContext" ] || [ "$CONTEXT_OVERRIDE" = "extensions" ]; then
|
|
92
|
+
DBCONTEXT="ExtensionsDbContext"; DBCONTEXT_TYPE="extensions"
|
|
93
|
+
elif find src -type d -name "SmartStack.Domain" | grep -q .; then
|
|
94
|
+
DBCONTEXT="CoreDbContext"; DBCONTEXT_TYPE="core"
|
|
95
|
+
else
|
|
96
|
+
DBCONTEXT="ExtensionsDbContext"; DBCONTEXT_TYPE="extensions"
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
echo "Migrations: $MIGRATIONS_DIR"
|
|
100
|
+
echo "DbContext: $DBCONTEXT"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Step 3: Identify branch-specific migrations
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
git fetch origin "$BASE_BRANCH"
|
|
107
|
+
|
|
108
|
+
# Get migration file paths RELATIVE TO GIT ROOT
|
|
109
|
+
MIGRATIONS_REL=$(realpath --relative-to="$(git rev-parse --show-toplevel)" "$MIGRATIONS_DIR")
|
|
110
|
+
|
|
111
|
+
# List parent branch migrations (names only, no Designer/Snapshot)
|
|
112
|
+
BASE_MIGS=$(git ls-tree -r --name-only "origin/$BASE_BRANCH" -- "$MIGRATIONS_REL/" 2>/dev/null | grep "\.cs$" | grep -v "Designer\|Snapshot" | xargs -I{} basename {} .cs)
|
|
113
|
+
|
|
114
|
+
# List local migrations (names only)
|
|
115
|
+
LOCAL_MIGS=$(find "$MIGRATIONS_DIR" -maxdepth 1 -name "*.cs" ! -name "*Designer*" ! -name "*Snapshot*" | xargs -I{} basename {} .cs)
|
|
116
|
+
|
|
117
|
+
# Calculate branch-specific migrations (in local but NOT in base)
|
|
118
|
+
BRANCH_ONLY_MIGS=""
|
|
119
|
+
for mig in $LOCAL_MIGS; do
|
|
120
|
+
if ! echo "$BASE_MIGS" | grep -qx "$mig"; then
|
|
121
|
+
BRANCH_ONLY_MIGS="$BRANCH_ONLY_MIGS $mig"
|
|
122
|
+
fi
|
|
123
|
+
done
|
|
124
|
+
BRANCH_ONLY_MIGS=$(echo "$BRANCH_ONLY_MIGS" | xargs) # trim
|
|
125
|
+
|
|
126
|
+
BRANCH_COUNT=$(echo "$BRANCH_ONLY_MIGS" | wc -w)
|
|
127
|
+
BASE_COUNT=$(echo "$BASE_MIGS" | wc -w)
|
|
128
|
+
|
|
129
|
+
echo ""
|
|
130
|
+
echo "Parent ($BASE_BRANCH): $BASE_COUNT migration(s)"
|
|
131
|
+
echo "Branch-specific: $BRANCH_COUNT migration(s)"
|
|
132
|
+
for mig in $BRANCH_ONLY_MIGS; do echo " - $mig"; done
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Step 4: Guard — nothing to squash
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
if [ "$BRANCH_COUNT" -eq 0 ]; then
|
|
139
|
+
echo "Nothing to squash — no branch-specific migrations found."
|
|
140
|
+
exit 0
|
|
141
|
+
fi
|
|
142
|
+
if [ "$BRANCH_COUNT" -eq 1 ]; then
|
|
143
|
+
echo "Only 1 branch-specific migration — squash not needed."
|
|
144
|
+
exit 0
|
|
145
|
+
fi
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Step 5: Confirm (skip if `--force`)
|
|
149
|
+
|
|
150
|
+
If `--force` flag is NOT set, display the plan and ask confirmation:
|
|
151
|
+
|
|
152
|
+
```javascript
|
|
153
|
+
AskUserQuestion({
|
|
154
|
+
question: "Squash will consolidate " + BRANCH_COUNT + " migrations into 1.\n\nMigrations to squash:\n" + BRANCH_ONLY_MIGS_LIST + "\n\nParent: " + BASE_BRANCH + " (" + BASE_COUNT + " migrations will be preserved)\n\nProceed?",
|
|
155
|
+
header: "EF Core Squash",
|
|
156
|
+
options: [
|
|
157
|
+
{ label: "Yes, squash", description: "Backup + consolidate" },
|
|
158
|
+
{ label: "Cancel", description: "Do nothing" }
|
|
159
|
+
]
|
|
160
|
+
})
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Step 6: Backup + Fetch + Delete + Create
|
|
164
|
+
|
|
165
|
+
```bash
|
|
67
166
|
# Backup
|
|
68
167
|
BACKUP_DIR=".claude/gitflow/backup/migrations/squash_$(date +%Y%m%d_%H%M%S)"
|
|
69
|
-
mkdir -p "$BACKUP_DIR" && cp
|
|
168
|
+
mkdir -p "$BACKUP_DIR" && cp "$MIGRATIONS_DIR"/*.cs "$BACKUP_DIR/"
|
|
169
|
+
echo "Backup: $BACKUP_DIR"
|
|
70
170
|
|
|
71
171
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
72
172
|
# CRUCIAL: Retrieve SNAPSHOT + MIGRATIONS from parent branch
|
|
73
173
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
74
|
-
git fetch origin "$BASE_BRANCH"
|
|
75
174
|
|
|
76
175
|
# 1. Retrieve ModelSnapshot
|
|
77
|
-
git checkout "origin/$BASE_BRANCH" --
|
|
176
|
+
git checkout "origin/$BASE_BRANCH" -- "$MIGRATIONS_REL"/*ModelSnapshot.cs
|
|
78
177
|
echo "Snapshot retrieved from origin/$BASE_BRANCH"
|
|
79
178
|
|
|
80
179
|
# 2. Retrieve ALL migrations from parent branch
|
|
81
|
-
# MANDATORY: Without this, develop/main migrations will be lost!
|
|
82
180
|
for base_mig in $BASE_MIGS; do
|
|
83
|
-
|
|
84
|
-
git checkout "origin/$BASE_BRANCH" -- "
|
|
85
|
-
git checkout "origin/$BASE_BRANCH" -- "Migrations/${base_name}.Designer.cs" 2>/dev/null || true
|
|
86
|
-
echo " $base_name retrieved"
|
|
181
|
+
git checkout "origin/$BASE_BRANCH" -- "$MIGRATIONS_REL/${base_mig}.cs" 2>/dev/null || true
|
|
182
|
+
git checkout "origin/$BASE_BRANCH" -- "$MIGRATIONS_REL/${base_mig}.Designer.cs" 2>/dev/null || true
|
|
87
183
|
done
|
|
88
|
-
echo "$(
|
|
184
|
+
echo "$BASE_COUNT migration(s) retrieved from origin/$BASE_BRANCH"
|
|
89
185
|
|
|
90
|
-
# Delete branch-specific migrations only
|
|
91
|
-
for mig in $
|
|
92
|
-
rm -f "
|
|
186
|
+
# 3. Delete branch-specific migrations only
|
|
187
|
+
for mig in $BRANCH_ONLY_MIGS; do
|
|
188
|
+
rm -f "$MIGRATIONS_DIR"/*"${mig}"*.cs
|
|
189
|
+
echo " Deleted: $mig"
|
|
93
190
|
done
|
|
94
191
|
|
|
95
|
-
# Create consolidated migration - MCP MANDATORY
|
|
96
|
-
# $DBCONTEXT_TYPE = "core" or "extensions" (from detect_efcore_project)
|
|
192
|
+
# 4. Create consolidated migration - MCP MANDATORY
|
|
97
193
|
mcp__smartstack__suggest_migration({ description: "...", context: DBCONTEXT_TYPE })
|
|
98
|
-
#
|
|
99
|
-
|
|
100
|
-
|
|
194
|
+
# Use the name returned by MCP in $MCP_MIGRATION_NAME:
|
|
195
|
+
dotnet ef migrations add "$MCP_MIGRATION_NAME" \
|
|
196
|
+
--context "$DBCONTEXT" \
|
|
197
|
+
--project "$INFRA_PROJECT" \
|
|
198
|
+
--startup-project "$(find src -name "*.Api.csproj" | head -1)" \
|
|
199
|
+
-o Persistence/Migrations
|
|
101
200
|
|
|
102
|
-
# NOTE: SQL objects
|
|
103
|
-
# via SqlObjectHelper.ApplyAllAsync() — no injection needed in migrations.
|
|
201
|
+
# NOTE: SQL objects are applied automatically at startup — no injection needed.
|
|
104
202
|
|
|
105
|
-
# Validate
|
|
106
|
-
dotnet build && dotnet ef migrations script --idempotent > /dev/null
|
|
203
|
+
# 5. Validate
|
|
204
|
+
dotnet build && dotnet ef migrations script --idempotent --context "$DBCONTEXT" > /dev/null
|
|
107
205
|
```
|
|
108
206
|
|
|
109
207
|
## Safety Checks
|
|
@@ -130,7 +228,7 @@ SQUASH - {current_branch}
|
|
|
130
228
|
|
|
131
229
|
Consolidated:
|
|
132
230
|
Before: {M} migrations (branch-specific)
|
|
133
|
-
After: 1 migration ({
|
|
231
|
+
After: 1 migration ({MCP_MIGRATION_NAME} via MCP)
|
|
134
232
|
|
|
135
233
|
Backup: .claude/gitflow/backup/migrations/squash_{timestamp}/
|
|
136
234
|
|
|
@@ -43,9 +43,9 @@ fi
|
|
|
43
43
|
| Categorie | Condition | Action |
|
|
44
44
|
|-----------|-----------|--------|
|
|
45
45
|
| Permanent | main, develop | Protege - jamais supprime |
|
|
46
|
-
| Actif | Branche existe + recent | Conserver |
|
|
47
|
-
| Orphelin |
|
|
48
|
-
| Stale |
|
|
46
|
+
| Actif | Branche existe + non mergee + recent | Conserver |
|
|
47
|
+
| Orphelin | Repertoire manquant ou branche supprimee | Supprimer (worktree + local + remote + parent dir) |
|
|
48
|
+
| Stale | Remote supprime OU branche mergee dans develop/main | Supprimer (worktree + local + remote + parent dir) |
|
|
49
49
|
| Dirty | Modifications non commitees | Warning - confirmation requise |
|
|
50
50
|
|
|
51
51
|
## Commandes
|
|
@@ -64,9 +64,20 @@ git branch --list $BRANCH
|
|
|
64
64
|
# Derniere activite
|
|
65
65
|
git log -1 --format=%ci $BRANCH
|
|
66
66
|
|
|
67
|
-
# Supprimer worktree
|
|
68
|
-
git worktree remove "$WORKTREE_PATH" --force
|
|
69
|
-
git
|
|
67
|
+
# Supprimer worktree (3 etapes obligatoires)
|
|
68
|
+
git worktree remove "$WORKTREE_PATH" --force # 1. Remove worktree
|
|
69
|
+
git push origin --delete "$BRANCH" 2>/dev/null # 2. Delete remote branch
|
|
70
|
+
git branch -D "$BRANCH" 2>/dev/null # 3. Delete local branch
|
|
71
|
+
git worktree prune # 4. Prune stale refs
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Post-cleanup: empty parent directories
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# Remove empty parent directories (features/, releases/, hotfixes/)
|
|
78
|
+
for dir in "$FEATURES_DIR" "$RELEASES_DIR" "$HOTFIXES_DIR"; do
|
|
79
|
+
[ -d "$dir" ] && [ -z "$(ls -A "$dir" 2>/dev/null)" ] && rm -rf "$dir"
|
|
80
|
+
done
|
|
70
81
|
```
|
|
71
82
|
|
|
72
83
|
## Output Format
|
|
@@ -3,7 +3,7 @@ name: dev-start
|
|
|
3
3
|
description: Launch SmartStack dev environment (backend + frontend + admin credentials)
|
|
4
4
|
argument-hint: "[--stop] [--reset] [--backend-only] [--frontend-only]"
|
|
5
5
|
model: haiku
|
|
6
|
-
allowed-tools: Read, Bash, Glob, Write
|
|
6
|
+
allowed-tools: Read, Bash, Glob, Write, TaskOutput
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
## Arguments
|
|
@@ -106,9 +106,11 @@ If `--stop` argument → skip this step entirely (--stop uses netstat only).
|
|
|
106
106
|
|
|
107
107
|
**Skip if CACHE_HIT=true (values loaded from cache in Step 0).**
|
|
108
108
|
|
|
109
|
-
Find the API and Web directories:
|
|
110
|
-
|
|
111
|
-
-
|
|
109
|
+
Find the API and Web directories using bash (Glob doesn't match directories):
|
|
110
|
+
```bash
|
|
111
|
+
API_DIR=$(ls -d src/*.Api 2>/dev/null | head -1)
|
|
112
|
+
WEB_DIR=$(ls -d web/*-web 2>/dev/null | head -1)
|
|
113
|
+
```
|
|
112
114
|
|
|
113
115
|
If neither found, display error and exit.
|
|
114
116
|
|
|
@@ -129,6 +131,11 @@ Check if `appsettings.Local.json` exists in the API directory:
|
|
|
129
131
|
- **Dev backend port**: Default `5142`
|
|
130
132
|
- **Dev frontend port**: Default `6173`
|
|
131
133
|
|
|
134
|
+
**IMPORTANT: Read config files in parallel.** Use a single message with multiple Read tool calls for:
|
|
135
|
+
- `{API_DIR}/appsettings.json` (or `appsettings.Local.json`)
|
|
136
|
+
- `{WEB_DIR}/.env.development` (or `.env.standalone`)
|
|
137
|
+
- `{API_DIR}/Properties/launchSettings.json`
|
|
138
|
+
|
|
132
139
|
## Step 3: Validate config coherence
|
|
133
140
|
|
|
134
141
|
**Skip if CACHE_HIT=true (values loaded from cache in Step 0).**
|
|
@@ -242,7 +249,7 @@ Config Coherence Check:
|
|
|
242
249
|
3. Write `.claude/config/dev-run.json` using the Write tool with all detected values:
|
|
243
250
|
```json
|
|
244
251
|
{
|
|
245
|
-
"version":
|
|
252
|
+
"version": 2,
|
|
246
253
|
"api_dir": "{API_DIR}",
|
|
247
254
|
"web_dir": "{WEB_DIR}",
|
|
248
255
|
"mode": "{MODE}",
|
|
@@ -250,6 +257,7 @@ Config Coherence Check:
|
|
|
250
257
|
"web_port": {WEB_PORT},
|
|
251
258
|
"backend_cmd": "{BACKEND_CMD}",
|
|
252
259
|
"frontend_cmd": "{FRONTEND_CMD}",
|
|
260
|
+
"connection_string": "{CONN_STR}",
|
|
253
261
|
"sql_server": "{SQL_SERVER}",
|
|
254
262
|
"sql_database": "{SQL_DATABASE}",
|
|
255
263
|
"sql_status": "{ok|unreachable|db_missing|skipped}",
|
|
@@ -277,7 +285,20 @@ If NOT already running and NOT `--frontend-only`:
|
|
|
277
285
|
cd {API_DIR} && dotnet run {LAUNCH_ARGS}
|
|
278
286
|
```
|
|
279
287
|
|
|
280
|
-
Use `Bash(run_in_background=true)` so it runs in background.
|
|
288
|
+
Use `Bash(run_in_background=true)` so it runs in background. **Save the task ID** returned by the background command.
|
|
289
|
+
|
|
290
|
+
### Fail-fast crash detection
|
|
291
|
+
|
|
292
|
+
After launching, wait 5 seconds then check if the process crashed:
|
|
293
|
+
```bash
|
|
294
|
+
sleep 5
|
|
295
|
+
```
|
|
296
|
+
Then call `TaskOutput(task_id="{TASK_ID}", block=false)` to read the background output.
|
|
297
|
+
|
|
298
|
+
**If the output contains a stack trace, "Unhandled exception", or the process has already exited:**
|
|
299
|
+
1. Display the error output to the user
|
|
300
|
+
2. Display: `[FAIL] Backend crashed on startup. Fix the error above and retry /dev-start`
|
|
301
|
+
3. **STOP immediately. Do NOT attempt to diagnose, fix, rebuild, or retry.**
|
|
281
302
|
|
|
282
303
|
## Step 6: Launch frontend
|
|
283
304
|
|
|
@@ -294,25 +315,29 @@ Use `Bash(run_in_background=true)` so it runs in background.
|
|
|
294
315
|
|
|
295
316
|
1. If `.claude/dev-session.json` exists and contains a password and `--reset` NOT specified -> reuse stored credentials
|
|
296
317
|
2. If `--reset` OR no stored password:
|
|
297
|
-
a. If backend was just launched, wait for it to be UP first
|
|
318
|
+
a. If backend was just launched, wait for it to be UP first.
|
|
319
|
+
Run the full wait in a **single bash command** to avoid multiple tool calls:
|
|
298
320
|
```bash
|
|
299
321
|
# Phase 1: Wait for port to be bound (up to 30s)
|
|
322
|
+
echo "Waiting for backend port ${API_PORT}..."
|
|
300
323
|
for i in $(seq 1 10); do
|
|
301
|
-
netstat.exe -ano | grep ":${API_PORT} .*LISTENING" && break
|
|
324
|
+
netstat.exe -ano | grep ":${API_PORT} .*LISTENING" > /dev/null 2>&1 && echo "Port bound." && break
|
|
302
325
|
sleep 3
|
|
303
326
|
done
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
# Phase 2: Wait for API to actually respond to HTTP requests (up to 30s)
|
|
307
|
-
# Kestrel binds the port BEFORE the app is fully initialized.
|
|
308
|
-
# We must confirm the API actually responds before resetting the password.
|
|
327
|
+
# Phase 2: Wait for API to respond (up to 30s) — accept 200 or 302
|
|
328
|
+
echo "Waiting for API to respond..."
|
|
309
329
|
for i in $(seq 1 10); do
|
|
310
|
-
curl -s -o /dev/null -w "%{http_code}" "http://localhost:${API_PORT}/scalar" 2>/dev/null
|
|
330
|
+
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:${API_PORT}/scalar" 2>/dev/null)
|
|
331
|
+
[ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "302" ] && echo "API ready (HTTP $HTTP_CODE)." && break
|
|
311
332
|
sleep 3
|
|
312
333
|
done
|
|
313
334
|
```
|
|
314
|
-
If `/scalar` does not
|
|
315
|
-
b. Run
|
|
335
|
+
If `/scalar` does not respond after 30s, warn the user and attempt the reset anyway.
|
|
336
|
+
b. Run admin reset using the connection string extracted from appsettings (Step 3):
|
|
337
|
+
```bash
|
|
338
|
+
smartstack admin reset --connection "{CONN_STR}" --force --json
|
|
339
|
+
```
|
|
340
|
+
**IMPORTANT:** Always pass `--connection` with the full connection string. Without it, the CLI shows an interactive environment picker that blocks execution.
|
|
316
341
|
c. Parse JSON output to extract email and password
|
|
317
342
|
d. Write `.claude/dev-session.json`:
|
|
318
343
|
```json
|
|
@@ -352,14 +377,18 @@ Use `Bash(run_in_background=true)` so it runs in background.
|
|
|
352
377
|
|
|
353
378
|
<important_notes>
|
|
354
379
|
|
|
355
|
-
1. **
|
|
356
|
-
2. **
|
|
357
|
-
3. **
|
|
358
|
-
4. **
|
|
359
|
-
5. **
|
|
360
|
-
6. **
|
|
361
|
-
7. **
|
|
362
|
-
8. **
|
|
380
|
+
1. **NEVER debug or fix code** - This skill ONLY starts processes. If the backend or frontend fails to start, display the error and STOP. Do NOT attempt to: clean/restore/rebuild, upgrade packages, inspect DLLs, clear NuGet cache, read .csproj files, or any other diagnostic/repair action. The user will fix the issue themselves.
|
|
381
|
+
2. **Fail-fast on crash** - After launching backend in background, wait 5s then check TaskOutput. If the process already exited or shows errors, report and STOP immediately.
|
|
382
|
+
3. **Backend must be READY before admin reset** - First poll the port (netstat), then poll the `/scalar` endpoint (HTTP 200/302) to confirm the API is fully initialized. Kestrel binds the port before the app finishes starting, so port-only checks are insufficient.
|
|
383
|
+
4. **Admin reset requires --connection** - Always pass `--connection "{CONN_STR}"` to avoid the interactive environment picker.
|
|
384
|
+
5. **Read config files in parallel** - When reading appsettings, .env, and launchSettings, use multiple Read calls in a single message.
|
|
385
|
+
6. **Cross-worktree support** - Detect the correct API directory regardless of which worktree we're in.
|
|
386
|
+
7. **No MCP required** - This skill only uses bash commands and the `smartstack` CLI.
|
|
387
|
+
8. **Config coherence is critical** - Mismatched ports between backend/frontend is the #1 cause of "it doesn't work" issues.
|
|
388
|
+
9. **Idempotent** - Running twice should detect already-running processes and reuse stored credentials.
|
|
389
|
+
10. **dev-session.json contains secrets** - Must be in `.gitignore`.
|
|
390
|
+
11. **Cache** - Config detection is cached in `.claude/config/dev-run.json`. Cache is invalidated when config files change (mtime-based hash). Use `--reset` to force re-detection.
|
|
391
|
+
12. **SQL check is non-blocking** - A failed SQL check displays a warning but does not prevent launch. AutoMigrate may create the database on startup.
|
|
363
392
|
|
|
364
393
|
</important_notes>
|
|
365
394
|
|
|
@@ -24,6 +24,7 @@ Parse `$ARGUMENTS` to determine the command:
|
|
|
24
24
|
| `migration` | **Agent** `efcore-migration` | sonnet |
|
|
25
25
|
| `squash` | **Agent** `efcore-squash` | sonnet |
|
|
26
26
|
| `rebase-snapshot` | **Agent** `efcore-rebase-snapshot` | sonnet |
|
|
27
|
+
| (none) | **Inline** `<command-help>` | — |
|
|
27
28
|
|
|
28
29
|
**Inline commands:** Execute directly following the matching `<command-*>` section below.
|
|
29
30
|
**Agent commands:** Delegate via Agent tool:
|
|
@@ -31,11 +32,19 @@ Parse `$ARGUMENTS` to determine the command:
|
|
|
31
32
|
Agent(
|
|
32
33
|
subagent_type: "{agent_name}",
|
|
33
34
|
model: "sonnet",
|
|
34
|
-
prompt: "Execute /efcore {command}
|
|
35
|
+
prompt: "Execute /efcore {command}.
|
|
36
|
+
Working directory: {cwd}
|
|
37
|
+
Branch: {branch}
|
|
38
|
+
Context: {--context value if provided, else 'auto-detect'}
|
|
39
|
+
Force: {true if --force flag, else false}
|
|
40
|
+
Description: {user description if provided}
|
|
41
|
+
"
|
|
35
42
|
)
|
|
36
43
|
```
|
|
37
44
|
|
|
38
|
-
**Flags:**
|
|
45
|
+
**Flags:**
|
|
46
|
+
- `--context <name>` — Force DbContext (`CoreDbContext` or `ExtensionsDbContext`), skip auto-detection
|
|
47
|
+
- `--force` — Skip all confirmations (backup still created)
|
|
39
48
|
|
|
40
49
|
</routing>
|
|
41
50
|
|
|
@@ -91,6 +100,8 @@ Pattern: `{context}_v{version}_{sequence}_{Description}`
|
|
|
91
100
|
|
|
92
101
|
**READ-ONLY: Never modify database or migrations.**
|
|
93
102
|
|
|
103
|
+
> **Troubleshooting:** See `references/troubleshooting.md` for common errors.
|
|
104
|
+
|
|
94
105
|
1. Call MCP `mcp__smartstack__check_migrations` with `branch: {current_branch}`
|
|
95
106
|
2. If MCP unavailable, fallback: `dotnet ef migrations list --no-build --context {Context}` per context
|
|
96
107
|
3. Display compact summary
|
|
@@ -117,11 +128,14 @@ BRANCH: {branch}
|
|
|
117
128
|
|
|
118
129
|
**ONLY runs `dotnet ef database update`. Never creates/deletes migrations.**
|
|
119
130
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
131
|
+
> **Reference:** See `references/database-operations.md` for connection test and migration count utilities.
|
|
132
|
+
|
|
133
|
+
1. Detect projects: `INFRA_PROJECT=$(find src -name "*Infrastructure.csproj" | head -1)` and `STARTUP_PROJECT=$(find src -name "*.Api.csproj" | head -1)`
|
|
134
|
+
2. Verify `appsettings.Local.json` exists in the API project
|
|
135
|
+
3. Apply Core: `dotnet ef database update --context CoreDbContext --project "$INFRA_PROJECT" --startup-project "$STARTUP_PROJECT" --verbose`
|
|
136
|
+
4. Apply Extensions: `dotnet ef database update --context ExtensionsDbContext --project "$INFRA_PROJECT" --startup-project "$STARTUP_PROJECT" --verbose`
|
|
123
137
|
|
|
124
|
-
If connection fails: suggest verifying SQL Server, appsettings.Local.json, or `/efcore db-reset`.
|
|
138
|
+
If connection fails: suggest verifying SQL Server, appsettings.Local.json, or `/efcore db-reset`. See `references/troubleshooting.md`.
|
|
125
139
|
|
|
126
140
|
```
|
|
127
141
|
DB DEPLOY
|
|
@@ -138,15 +152,18 @@ DB DEPLOY
|
|
|
138
152
|
|
|
139
153
|
**ONLY operates on database. Never creates/deletes migration files.**
|
|
140
154
|
|
|
141
|
-
|
|
155
|
+
> **Reference:** See `references/reset-operations.md` for drop/recreate commands.
|
|
156
|
+
|
|
157
|
+
1. Detect projects: `INFRA_PROJECT=$(find src -name "*Infrastructure.csproj" | head -1)` and `STARTUP_PROJECT=$(find src -name "*.Api.csproj" | head -1)`
|
|
158
|
+
2. **Confirm** (MANDATORY unless `--force`):
|
|
142
159
|
```
|
|
143
160
|
AskUserQuestion({ question: "DELETE the database? All data will be lost.", options: ["Yes, delete", "No, cancel"] })
|
|
144
161
|
```
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
162
|
+
3. **Backup** (optional): `sqlcmd -S "$SERVER" -E -Q "BACKUP DATABASE [$DB] TO DISK='$FILE' WITH FORMAT, INIT, COMPRESSION"`
|
|
163
|
+
4. **Drop**: `dotnet ef database drop --force --project "$INFRA_PROJECT" --startup-project "$STARTUP_PROJECT"`
|
|
164
|
+
5. **Recreate Core**: `dotnet ef database update --context CoreDbContext --project "$INFRA_PROJECT" --startup-project "$STARTUP_PROJECT"`
|
|
165
|
+
6. **Recreate Extensions**: `dotnet ef database update --context ExtensionsDbContext --project "$INFRA_PROJECT" --startup-project "$STARTUP_PROJECT"`
|
|
166
|
+
7. **Ask** if user wants `/efcore db-seed`
|
|
150
167
|
|
|
151
168
|
Block if ASPNETCORE_ENVIRONMENT=Production.
|
|
152
169
|
|
|
@@ -167,6 +184,8 @@ Next: /efcore db-status, /efcore db-seed
|
|
|
167
184
|
|
|
168
185
|
**Never drop or recreate database.**
|
|
169
186
|
|
|
187
|
+
> **Reference:** See `references/seed-methods.md` for detection and execution details.
|
|
188
|
+
|
|
170
189
|
1. **Detect** seeding method:
|
|
171
190
|
- `HasData()` in EF configs → apply via `dotnet ef database update --context {Context}`
|
|
172
191
|
- `IDataSeeder` / `--seed` CLI arg → `dotnet run --project {startup} -- --seed`
|
|
@@ -250,3 +269,27 @@ If conflict:
|
|
|
250
269
|
```
|
|
251
270
|
|
|
252
271
|
</command-conflicts>
|
|
272
|
+
|
|
273
|
+
<command-help>
|
|
274
|
+
|
|
275
|
+
## (no command) — Show available commands
|
|
276
|
+
|
|
277
|
+
Display the list of available `/efcore` commands:
|
|
278
|
+
|
|
279
|
+
```
|
|
280
|
+
EF CORE COMMANDS
|
|
281
|
+
────────────────
|
|
282
|
+
db-status Migration state check
|
|
283
|
+
db-deploy Apply pending migrations
|
|
284
|
+
db-reset Drop + recreate database
|
|
285
|
+
db-seed Populate test data
|
|
286
|
+
scan Cross-branch migration scanner
|
|
287
|
+
conflicts Migration conflict analyzer
|
|
288
|
+
migration Create/recreate migration
|
|
289
|
+
squash Consolidate migrations
|
|
290
|
+
rebase-snapshot Resync ModelSnapshot with parent
|
|
291
|
+
|
|
292
|
+
Usage: /efcore <command> [--context <name>] [--force]
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
</command-help>
|
|
@@ -7,8 +7,7 @@
|
|
|
7
7
|
```bash
|
|
8
8
|
# Validate required variables from step-00-init
|
|
9
9
|
for VAR_NAME in DBCONTEXT DBCONTEXT_TYPE INFRA_PROJECT STARTUP_PROJECT SELECTED_ENV; do
|
|
10
|
-
|
|
11
|
-
if [ -z "$VAR_VALUE" ]; then
|
|
10
|
+
if [ -z "${!VAR_NAME}" ]; then
|
|
12
11
|
echo "ERROR: Required variable $VAR_NAME is not set"
|
|
13
12
|
echo "Run step-00-init first"
|
|
14
13
|
exit 1
|
|
@@ -38,7 +37,7 @@ fi
|
|
|
38
37
|
## Connection Test
|
|
39
38
|
|
|
40
39
|
```bash
|
|
41
|
-
CONNECTION_TEST=$(dotnet ef
|
|
40
|
+
CONNECTION_TEST=$(dotnet ef migrations list \
|
|
42
41
|
--context "$DBCONTEXT" \
|
|
43
42
|
--project "$INFRA_PROJECT" \
|
|
44
43
|
--startup-project "$STARTUP_PROJECT" 2>&1)
|
|
@@ -27,7 +27,7 @@ if grep -q "\-\-seed" ./src/*/Program.cs 2>/dev/null; then
|
|
|
27
27
|
fi
|
|
28
28
|
|
|
29
29
|
# WARNING: SQL scripts detected
|
|
30
|
-
if [ -f "./scripts/seed.sql" ] || find
|
|
30
|
+
if [ -f "./scripts/seed.sql" ] || find src -name "seed*.sql" -not -path "*/bin/*" -not -path "*/obj/*" 2>/dev/null | grep -q .; then
|
|
31
31
|
echo ""
|
|
32
32
|
echo "WARNING: SQL seed scripts detected - FORBIDDEN by conventions"
|
|
33
33
|
echo "Migrate to HasData() or IDataSeeder"
|
|
@@ -58,7 +58,7 @@ the .NET SDK may not be found even if `dotnet-ef` is. Use `$USERPROFILE` first o
|
|
|
58
58
|
```bash
|
|
59
59
|
detect_efcore_project() {
|
|
60
60
|
# Find project with EF Core reference
|
|
61
|
-
CSPROJ=$(find
|
|
61
|
+
CSPROJ=$(find src-name "*.csproj" -exec grep -l "Microsoft.EntityFrameworkCore" {} \; | head -1)
|
|
62
62
|
|
|
63
63
|
if [ -z "$CSPROJ" ]; then
|
|
64
64
|
echo "ERROR: No EF Core project found"
|
|
@@ -70,8 +70,8 @@ detect_efcore_project() {
|
|
|
70
70
|
MIGRATIONS_DIR="$PROJECT_DIR/Persistence/Migrations"
|
|
71
71
|
|
|
72
72
|
# Find startup and infrastructure projects
|
|
73
|
-
STARTUP_PROJECT=$(find
|
|
74
|
-
INFRA_PROJECT=$(find
|
|
73
|
+
STARTUP_PROJECT=$(find src-name "*.Api.csproj" -o -name "*Web.csproj" | head -1)
|
|
74
|
+
INFRA_PROJECT=$(find src-name "*Infrastructure.csproj" | head -1)
|
|
75
75
|
[ -z "$INFRA_PROJECT" ] && INFRA_PROJECT="$CSPROJ"
|
|
76
76
|
|
|
77
77
|
echo "Project: $PROJECT_NAME"
|
|
@@ -84,7 +84,7 @@ detect_efcore_project() {
|
|
|
84
84
|
```bash
|
|
85
85
|
detect_dbcontext() {
|
|
86
86
|
# Priority 1: SmartStack.Domain exists → SmartStack source project (Core only)
|
|
87
|
-
if find
|
|
87
|
+
if find src-type d -name "SmartStack.Domain" | grep -q .; then
|
|
88
88
|
DBCONTEXT="CoreDbContext"
|
|
89
89
|
DBCONTEXT_TYPE="core"
|
|
90
90
|
SCHEMA="core"
|
|
@@ -94,7 +94,7 @@ detect_dbcontext() {
|
|
|
94
94
|
fi
|
|
95
95
|
|
|
96
96
|
# Priority 2: Client project with SmartStack NuGet → BOTH contexts
|
|
97
|
-
if find
|
|
97
|
+
if find src-name "*.csproj" -exec grep -ql "PackageReference.*SmartStack" {} \; 2>/dev/null; then
|
|
98
98
|
DBCONTEXT="CoreDbContext"
|
|
99
99
|
DBCONTEXT_TYPE="both"
|
|
100
100
|
SCHEMA="core"
|
|
@@ -105,8 +105,8 @@ detect_dbcontext() {
|
|
|
105
105
|
fi
|
|
106
106
|
|
|
107
107
|
# Priority 3: Scan for DbContext in code
|
|
108
|
-
CORE_CTX=$(find
|
|
109
|
-
EXT_CTX=$(find
|
|
108
|
+
CORE_CTX=$(find src-name "*.cs" -exec grep -l "CoreDbContext" {} \; 2>/dev/null | head -1)
|
|
109
|
+
EXT_CTX=$(find src-name "*.cs" -exec grep -l "ExtensionsDbContext" {} \; 2>/dev/null | head -1)
|
|
110
110
|
|
|
111
111
|
if [ -n "$CORE_CTX" ] && [ -n "$EXT_CTX" ]; then
|
|
112
112
|
DBCONTEXT="CoreDbContext"
|
|
@@ -137,7 +137,7 @@ detect_dbcontext() {
|
|
|
137
137
|
|
|
138
138
|
```bash
|
|
139
139
|
detect_environment() {
|
|
140
|
-
API_DIR=$(find
|
|
140
|
+
API_DIR=$(find src-type d -name "*.Api" | head -1)
|
|
141
141
|
[ -z "$API_DIR" ] && API_DIR="src/SmartStack.Api"
|
|
142
142
|
|
|
143
143
|
# Priority: --env flag > appsettings.Local.json > error
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Zero-downtime migration patterns for EF Core. Use when a migration contains destructive operations (column rename, type change, column drop) that could cause downtime during deployment.
|
|
3
3
|
|
|
4
4
|
These patterns apply to production deployments where the application runs continuously. For development environments, standard migrations are sufficient.
|
|
5
|
+
|
|
6
|
+
> **Exception to "no raw SQL" rule:** `migrationBuilder.Sql(...)` is authorized ONLY for data backfill operations within zero-downtime migration patterns (e.g., `UPDATE ... SET NewCol = OldCol`). Never use it for DDL (CREATE/ALTER/DROP) — those must go through EF Core Fluent API or `SqlObjects/`.
|
|
5
7
|
</overview>
|
|
6
8
|
|
|
7
9
|
<when_to_use>
|
|
@@ -60,9 +60,21 @@ while read -r line; do
|
|
|
60
60
|
continue
|
|
61
61
|
fi
|
|
62
62
|
|
|
63
|
+
# Skip permanent branches
|
|
64
|
+
[[ "$WT_BRANCH" == "$GF_MAIN_BRANCH" ]] || [[ "$WT_BRANCH" == "$GF_DEVELOP_BRANCH" ]] && {
|
|
65
|
+
VALID+=("$WT_PATH:$WT_BRANCH")
|
|
66
|
+
continue
|
|
67
|
+
}
|
|
68
|
+
|
|
63
69
|
# Check if branch still exists on remote
|
|
64
70
|
REMOTE_EXISTS=$(git branch -r --list "origin/$WT_BRANCH" | wc -l)
|
|
65
|
-
|
|
71
|
+
|
|
72
|
+
# Check if branch is merged into develop or main
|
|
73
|
+
MERGED_INTO_DEVELOP=$(git branch --merged "$GF_DEVELOP_BRANCH" | grep -c "$WT_BRANCH" 2>/dev/null || echo 0)
|
|
74
|
+
MERGED_INTO_MAIN=$(git branch --merged "$GF_MAIN_BRANCH" | grep -c "$WT_BRANCH" 2>/dev/null || echo 0)
|
|
75
|
+
|
|
76
|
+
# Stale if: no remote OR already merged
|
|
77
|
+
if [ "$REMOTE_EXISTS" -eq 0 ] || [ "$MERGED_INTO_DEVELOP" -gt 0 ] || [ "$MERGED_INTO_MAIN" -gt 0 ]; then
|
|
66
78
|
STALE+=("$WT_PATH:$WT_BRANCH")
|
|
67
79
|
continue
|
|
68
80
|
fi
|
|
@@ -166,10 +178,14 @@ AskUserQuestion:
|
|
|
166
178
|
# Remove orphaned
|
|
167
179
|
for item in "${ORPHANED[@]}"; do
|
|
168
180
|
WT_PATH=$(echo "$item" | cut -d':' -f1)
|
|
181
|
+
WT_BRANCH=$(echo "$item" | cut -d':' -f2)
|
|
169
182
|
git worktree remove "$WT_PATH" --force 2>/dev/null || rm -rf "$WT_PATH"
|
|
183
|
+
# Delete remote branch if exists
|
|
184
|
+
git push origin --delete "$WT_BRANCH" 2>/dev/null
|
|
185
|
+
git branch -D "$WT_BRANCH" 2>/dev/null
|
|
170
186
|
done
|
|
171
187
|
|
|
172
|
-
# Prune
|
|
188
|
+
# Prune stale worktree references
|
|
173
189
|
git worktree prune
|
|
174
190
|
|
|
175
191
|
# Remove stale
|
|
@@ -178,15 +194,29 @@ for item in "${STALE[@]}"; do
|
|
|
178
194
|
WT_BRANCH=$(echo "$item" | cut -d':' -f2)
|
|
179
195
|
|
|
180
196
|
git worktree remove "$WT_PATH" --force 2>/dev/null || rm -rf "$WT_PATH"
|
|
197
|
+
# Delete remote branch if exists
|
|
198
|
+
git push origin --delete "$WT_BRANCH" 2>/dev/null
|
|
181
199
|
git branch -D "$WT_BRANCH" 2>/dev/null
|
|
182
200
|
done
|
|
201
|
+
|
|
202
|
+
# Final prune after all removals
|
|
203
|
+
git worktree prune
|
|
183
204
|
```
|
|
184
205
|
|
|
185
206
|
**Branches:**
|
|
186
207
|
```bash
|
|
187
|
-
# Delete merged branches
|
|
208
|
+
# Delete merged branches (local + remote)
|
|
188
209
|
for branch in "${MERGED_BRANCHES[@]}"; do
|
|
189
210
|
git branch -d "$branch"
|
|
211
|
+
git push origin --delete "$branch" 2>/dev/null
|
|
212
|
+
done
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**Empty parent directories:**
|
|
216
|
+
```bash
|
|
217
|
+
# Clean up empty worktree parent directories
|
|
218
|
+
for dir in "$GF_FEATURES_DIR" "$GF_RELEASES_DIR" "$GF_HOTFIXES_DIR"; do
|
|
219
|
+
[ -d "$dir" ] && [ -z "$(ls -A "$dir" 2>/dev/null)" ] && rm -rf "$dir"
|
|
190
220
|
done
|
|
191
221
|
```
|
|
192
222
|
|