@atlashub/smartstack-cli 4.37.0 → 4.38.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/dist/mcp-entry.mjs +3 -17
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +1 -1
- package/templates/agents/efcore/migration.md +4 -42
- package/templates/agents/efcore/rebase-snapshot.md +4 -38
- package/templates/agents/efcore/squash.md +6 -41
- package/templates/project/claude-md/infrastructure.CLAUDE.md.template +1 -1
- package/templates/skills/application/references/migration-checklist-troubleshooting.md +3 -15
- package/templates/skills/application/steps/step-06-migration.md +0 -4
- package/templates/skills/application/templates-seed.md +33 -27
- package/templates/skills/dev-start/SKILL.md +142 -20
- package/templates/skills/efcore/references/sql-objects-injection.md +12 -54
- package/templates/skills/gitflow/references/commit-migration-validation.md +4 -0
package/package.json
CHANGED
|
@@ -114,48 +114,11 @@ rm Persistence/Migrations/*${OLD_NAME}*.cs
|
|
|
114
114
|
|
|
115
115
|
> **Note:** `$MCP_NAME` is obtained via MCP call, never computed locally.
|
|
116
116
|
|
|
117
|
-
## SQL Objects
|
|
117
|
+
## SQL Objects (NO injection needed)
|
|
118
118
|
|
|
119
|
-
|
|
119
|
+
SQL objects (TVFs, views, stored procedures) are applied **automatically at application startup** via `SqlObjectHelper.ApplyAllAsync(dbContext)` in `SmartStackExtensions.InitializeSmartStackAsync()`, right after `MigrateAsync()`.
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
# Detect the generated migration file (the main .cs, not .Designer.cs)
|
|
123
|
-
MIGRATION_FILE=$(find "$MIGRATIONS_DIR" -name "*${MCP_NAME}.cs" ! -name "*.Designer.cs" | head -1)
|
|
124
|
-
SQL_OBJECTS_DIR="$INFRA_PROJECT_DIR/Persistence/SqlObjects"
|
|
125
|
-
SQL_FILES=$(find "$SQL_OBJECTS_DIR" -name "*.sql" 2>/dev/null | wc -l)
|
|
126
|
-
|
|
127
|
-
if [ "$SQL_FILES" -gt 0 ]; then
|
|
128
|
-
echo "Found $SQL_FILES SQL object(s) — injecting SqlObjectHelper.ApplyAll()..."
|
|
129
|
-
|
|
130
|
-
# Add using directive if not present
|
|
131
|
-
if ! grep -q "using SmartStack.Infrastructure.Persistence.SqlObjects;" "$MIGRATION_FILE"; then
|
|
132
|
-
sed -i '1s/^/using SmartStack.Infrastructure.Persistence.SqlObjects;\n/' "$MIGRATION_FILE"
|
|
133
|
-
fi
|
|
134
|
-
|
|
135
|
-
# Add SqlObjectHelper.ApplyAll at the end of Up() method
|
|
136
|
-
# Use Edit tool to insert before the closing brace of Up()
|
|
137
|
-
# Target: the last " }" before "protected override void Down"
|
|
138
|
-
sed -i '/protected override void Down/i\
|
|
139
|
-
\ // Apply SQL objects (TVF, Views, SP) from embedded resources\
|
|
140
|
-
\ SqlObjectHelper.ApplyAll(migrationBuilder);' "$MIGRATION_FILE"
|
|
141
|
-
|
|
142
|
-
# VERIFY injection succeeded — FAIL if not
|
|
143
|
-
if ! grep -q "SqlObjectHelper.ApplyAll" "$MIGRATION_FILE"; then
|
|
144
|
-
echo "ERROR: Auto-injection failed. Use Edit tool to manually add:"
|
|
145
|
-
echo " 1. Add 'using SmartStack.Infrastructure.Persistence.SqlObjects;' at top"
|
|
146
|
-
echo " 2. Add 'SqlObjectHelper.ApplyAll(migrationBuilder);' at end of Up() method"
|
|
147
|
-
echo " File: $MIGRATION_FILE"
|
|
148
|
-
# DO NOT CONTINUE — fix this before proceeding
|
|
149
|
-
else
|
|
150
|
-
echo " SqlObjectHelper.ApplyAll(migrationBuilder) injected successfully"
|
|
151
|
-
find "$SQL_OBJECTS_DIR" -name "*.sql" -exec basename {} \; | while read f; do echo " - $f"; done
|
|
152
|
-
fi
|
|
153
|
-
else
|
|
154
|
-
echo "No SQL objects found in SqlObjects/ — skipping injection"
|
|
155
|
-
fi
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
> **CRITICAL:** If the bash `sed` injection fails (indentation mismatch), use the **Edit tool** to manually insert the lines. NEVER skip this step — it causes cascading 500 errors at runtime.
|
|
121
|
+
**You do NOT need to add `SqlObjectHelper.ApplyAll(migrationBuilder)` in migrations.** The startup process handles this with `CREATE OR ALTER` (idempotent). Modifying a `.sql` file only requires an application restart — no new migration needed.
|
|
159
122
|
|
|
160
123
|
## Context Detection
|
|
161
124
|
|
|
@@ -230,8 +193,7 @@ After rebase on develop:
|
|
|
230
193
|
**ONLY** SQL objects defined in `src/{Project}.Infrastructure/Persistence/SqlObjects/` are authorized.
|
|
231
194
|
|
|
232
195
|
- All SQL objects (functions, views, procedures, TVFs) MUST be `.sql` files in `Persistence/SqlObjects/`
|
|
233
|
-
-
|
|
234
|
-
- If `Persistence/SqlObjects/*.sql` files exist, inject the call automatically after migration creation
|
|
196
|
+
- SQL objects are applied **automatically at startup** via `SqlObjectHelper.ApplyAllAsync()` — no migration injection needed
|
|
235
197
|
- **Never** write raw SQL directly inside migration `Up()` or `Down()` methods
|
|
236
198
|
|
|
237
199
|
## Important Notes
|
|
@@ -26,7 +26,7 @@ fi
|
|
|
26
26
|
**FORBIDDEN:** Raw SQL inline in migrations (e.g., `migrationBuilder.Sql("CREATE FUNCTION ...")`)
|
|
27
27
|
|
|
28
28
|
**ONLY** SQL objects defined in `src/{Project}.Infrastructure/Persistence/SqlObjects/` are authorized.
|
|
29
|
-
|
|
29
|
+
SQL objects are applied **automatically at startup** via `SqlObjectHelper.ApplyAllAsync()` — no migration injection needed.
|
|
30
30
|
|
|
31
31
|
## Workflow
|
|
32
32
|
|
|
@@ -70,41 +70,8 @@ mcp__smartstack__suggest_migration({ description: "...", context: DBCONTEXT_TYPE
|
|
|
70
70
|
|
|
71
71
|
dotnet ef migrations add "$MIGRATION_NAME" --context "$DBCONTEXT"
|
|
72
72
|
|
|
73
|
-
#
|
|
74
|
-
#
|
|
75
|
-
# Without this, TVFs (fn_GetUserGroupHierarchy etc.) won't exist in DB
|
|
76
|
-
# and ALL endpoints will return 500 at runtime
|
|
77
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
78
|
-
MIGRATION_FILE=$(find Migrations -name "*${MIGRATION_NAME}.cs" ! -name "*.Designer.cs" | head -1)
|
|
79
|
-
SQL_OBJECTS_DIR="$INFRA_PROJECT_DIR/Persistence/SqlObjects"
|
|
80
|
-
SQL_FILES=$(find "$SQL_OBJECTS_DIR" -name "*.sql" 2>/dev/null | wc -l)
|
|
81
|
-
|
|
82
|
-
if [ "$SQL_FILES" -gt 0 ]; then
|
|
83
|
-
echo "Found $SQL_FILES SQL object(s) — injecting SqlObjectHelper.ApplyAll()..."
|
|
84
|
-
|
|
85
|
-
# Add using directive if not present
|
|
86
|
-
if ! grep -q "using SmartStack.Infrastructure.Persistence.SqlObjects;" "$MIGRATION_FILE"; then
|
|
87
|
-
sed -i '1s/^/using SmartStack.Infrastructure.Persistence.SqlObjects;\n/' "$MIGRATION_FILE"
|
|
88
|
-
fi
|
|
89
|
-
|
|
90
|
-
# Inject SqlObjectHelper.ApplyAll before Down() method
|
|
91
|
-
sed -i '/protected override void Down/i\
|
|
92
|
-
\ // Apply SQL objects (TVF, Views, SP) from embedded resources\
|
|
93
|
-
\ SqlObjectHelper.ApplyAll(migrationBuilder);' "$MIGRATION_FILE"
|
|
94
|
-
|
|
95
|
-
# VERIFY — FAIL if injection didn't work
|
|
96
|
-
if ! grep -q "SqlObjectHelper.ApplyAll" "$MIGRATION_FILE"; then
|
|
97
|
-
echo "ERROR: Auto-injection failed. Use Edit tool to manually add:"
|
|
98
|
-
echo " 1. 'using SmartStack.Infrastructure.Persistence.SqlObjects;' at top"
|
|
99
|
-
echo " 2. 'SqlObjectHelper.ApplyAll(migrationBuilder);' at end of Up()"
|
|
100
|
-
echo " File: $MIGRATION_FILE"
|
|
101
|
-
else
|
|
102
|
-
echo " SqlObjectHelper.ApplyAll(migrationBuilder) injected"
|
|
103
|
-
find "$SQL_OBJECTS_DIR" -name "*.sql" -exec basename {} \; | while read f; do echo " - $f"; done
|
|
104
|
-
fi
|
|
105
|
-
else
|
|
106
|
-
echo "No SQL objects found — skipping injection"
|
|
107
|
-
fi
|
|
73
|
+
# NOTE: SQL objects (TVFs, views, SPs) are applied automatically at startup
|
|
74
|
+
# via SqlObjectHelper.ApplyAllAsync() — no injection needed in migrations.
|
|
108
75
|
|
|
109
76
|
# Validate
|
|
110
77
|
dotnet build
|
|
@@ -130,7 +97,6 @@ core_v1.7.0_001_ReleaseInitial
|
|
|
130
97
|
- [ ] Backup created
|
|
131
98
|
- [ ] Build OK after rebase
|
|
132
99
|
- [ ] SQL script can be generated
|
|
133
|
-
- [ ] SQL objects injected (if `Persistence/SqlObjects/*.sql` exists)
|
|
134
100
|
|
|
135
101
|
## Error Recovery
|
|
136
102
|
|
|
@@ -140,7 +106,7 @@ core_v1.7.0_001_ReleaseInitial
|
|
|
140
106
|
| MCP unavailable | Check `.claude/mcp-status.json`, run `/mcp healthcheck` |
|
|
141
107
|
| Snapshot fetch fails | Verify `origin/$BASE_BRANCH` exists: `git fetch origin $BASE_BRANCH` |
|
|
142
108
|
| Migration creation fails | Restore backup, check `dotnet build` output |
|
|
143
|
-
| SQL objects
|
|
109
|
+
| SQL objects missing at runtime | SQL objects are applied at startup — restart the application |
|
|
144
110
|
|
|
145
111
|
**Automatic recovery:** On ANY failure after backup:
|
|
146
112
|
1. Restore all files from backup directory
|
|
@@ -3,7 +3,7 @@ name: efcore-squash
|
|
|
3
3
|
description: EF Core migration squasher - combine multiple migrations into one
|
|
4
4
|
color: magenta
|
|
5
5
|
model: sonnet
|
|
6
|
-
tools: Bash, Glob, Read
|
|
6
|
+
tools: Bash, Glob, Read, Edit
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# EF Core Squash Agent
|
|
@@ -26,7 +26,7 @@ fi
|
|
|
26
26
|
**FORBIDDEN:** Raw SQL inline in migrations (e.g., `migrationBuilder.Sql("CREATE FUNCTION ...")`)
|
|
27
27
|
|
|
28
28
|
**ONLY** SQL objects defined in `src/{Project}.Infrastructure/Persistence/SqlObjects/` are authorized.
|
|
29
|
-
|
|
29
|
+
SQL objects are applied **automatically at startup** via `SqlObjectHelper.ApplyAllAsync()` — no migration injection needed.
|
|
30
30
|
|
|
31
31
|
## Core Principle
|
|
32
32
|
|
|
@@ -51,8 +51,7 @@ WARNING: NEVER retrieve only the snapshot without the migrations!
|
|
|
51
51
|
5. **Fetch**: Retrieve **ModelSnapshot AND migrations** from parent branch (CRUCIAL!)
|
|
52
52
|
6. **Delete**: Remove branch-specific migrations ONLY
|
|
53
53
|
7. **Create**: Create consolidated migration (MCP mandatory)
|
|
54
|
-
8. **
|
|
55
|
-
9. **Validate**: Build + script OK
|
|
54
|
+
8. **Validate**: Build + script OK
|
|
56
55
|
|
|
57
56
|
## Key Commands
|
|
58
57
|
|
|
@@ -100,41 +99,8 @@ mcp__smartstack__suggest_migration({ description: "...", context: DBCONTEXT_TYPE
|
|
|
100
99
|
|
|
101
100
|
dotnet ef migrations add "$MIGRATION_NAME_FROM_MCP" --context "$DBCONTEXT"
|
|
102
101
|
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
# Without this, TVFs (fn_GetUserGroupHierarchy etc.) won't exist in DB
|
|
106
|
-
# and ALL endpoints will return 500 at runtime
|
|
107
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
108
|
-
MIGRATION_FILE=$(find Migrations -name "*${MIGRATION_NAME_FROM_MCP}.cs" ! -name "*.Designer.cs" | head -1)
|
|
109
|
-
SQL_OBJECTS_DIR="$INFRA_PROJECT_DIR/Persistence/SqlObjects"
|
|
110
|
-
SQL_FILES=$(find "$SQL_OBJECTS_DIR" -name "*.sql" 2>/dev/null | wc -l)
|
|
111
|
-
|
|
112
|
-
if [ "$SQL_FILES" -gt 0 ]; then
|
|
113
|
-
echo "Found $SQL_FILES SQL object(s) — injecting SqlObjectHelper.ApplyAll()..."
|
|
114
|
-
|
|
115
|
-
# Add using directive if not present
|
|
116
|
-
if ! grep -q "using SmartStack.Infrastructure.Persistence.SqlObjects;" "$MIGRATION_FILE"; then
|
|
117
|
-
sed -i '1s/^/using SmartStack.Infrastructure.Persistence.SqlObjects;\n/' "$MIGRATION_FILE"
|
|
118
|
-
fi
|
|
119
|
-
|
|
120
|
-
# Inject SqlObjectHelper.ApplyAll before Down() method
|
|
121
|
-
sed -i '/protected override void Down/i\
|
|
122
|
-
\ // Apply SQL objects (TVF, Views, SP) from embedded resources\
|
|
123
|
-
\ SqlObjectHelper.ApplyAll(migrationBuilder);' "$MIGRATION_FILE"
|
|
124
|
-
|
|
125
|
-
# VERIFY — FAIL if injection didn't work
|
|
126
|
-
if ! grep -q "SqlObjectHelper.ApplyAll" "$MIGRATION_FILE"; then
|
|
127
|
-
echo "ERROR: Auto-injection failed. Use Edit tool to manually add:"
|
|
128
|
-
echo " 1. 'using SmartStack.Infrastructure.Persistence.SqlObjects;' at top"
|
|
129
|
-
echo " 2. 'SqlObjectHelper.ApplyAll(migrationBuilder);' at end of Up()"
|
|
130
|
-
echo " File: $MIGRATION_FILE"
|
|
131
|
-
else
|
|
132
|
-
echo " SqlObjectHelper.ApplyAll(migrationBuilder) injected"
|
|
133
|
-
find "$SQL_OBJECTS_DIR" -name "*.sql" -exec basename {} \; | while read f; do echo " - $f"; done
|
|
134
|
-
fi
|
|
135
|
-
else
|
|
136
|
-
echo "No SQL objects found — skipping injection"
|
|
137
|
-
fi
|
|
102
|
+
# NOTE: SQL objects (TVFs, views, SPs) are applied automatically at startup
|
|
103
|
+
# via SqlObjectHelper.ApplyAllAsync() — no injection needed in migrations.
|
|
138
104
|
|
|
139
105
|
# Validate
|
|
140
106
|
dotnet build && dotnet ef migrations script --idempotent > /dev/null
|
|
@@ -149,7 +115,6 @@ dotnet build && dotnet ef migrations script --idempotent > /dev/null
|
|
|
149
115
|
- [ ] **Parent snapshot retrieved** (origin/$BASE_BRANCH)
|
|
150
116
|
- [ ] **Parent migrations retrieved** (.cs AND .Designer.cs files)
|
|
151
117
|
- [ ] Branch-specific migrations deleted (not inherited ones!)
|
|
152
|
-
- [ ] **SQL Objects injected** (if `Persistence/SqlObjects/*.sql` exists)
|
|
153
118
|
- [ ] Build OK after squash
|
|
154
119
|
- [ ] Script can be generated
|
|
155
120
|
|
|
@@ -189,7 +154,7 @@ Next: /efcore db-reset, /efcore db-deploy
|
|
|
189
154
|
| MCP unavailable | Check `.claude/mcp-status.json`, run `/mcp healthcheck` |
|
|
190
155
|
| Snapshot fetch fails | Verify `origin/$BASE_BRANCH` exists: `git fetch origin $BASE_BRANCH` |
|
|
191
156
|
| Script generation fails | Restore backup, then investigate migration content |
|
|
192
|
-
| SQL objects
|
|
157
|
+
| SQL objects missing at runtime | SQL objects are applied at startup — restart the application |
|
|
193
158
|
|
|
194
159
|
**Automatic recovery:** On ANY failure after backup:
|
|
195
160
|
1. Restore all files from backup directory
|
|
@@ -152,7 +152,7 @@ dotnet ef migrations remove --context ExtensionsDbContext -p src/{{ProjectName}}
|
|
|
152
152
|
3. **Repositories** implement interfaces from Application
|
|
153
153
|
4. **DbContext** implements `IExtensionsDbContext` from Application
|
|
154
154
|
5. **Migrations** stay in this project under `Persistence/Migrations/`
|
|
155
|
-
6. **SQL objects via SqlObjectHelper** - TVFs/views use `SqlObjectHelper.
|
|
155
|
+
6. **SQL objects via SqlObjectHelper** - TVFs/views use `SqlObjectHelper.ApplyAllAsync()` at startup and embedded `.sql` files (no migration injection needed)
|
|
156
156
|
7. **NO sequential/deterministic GUIDs in seed data** - All GUIDs must be generated via `[guid]::NewGuid()`
|
|
157
157
|
8. **Schema prefix pattern** - Table names use format `{prefix}_{EntityName}` with schema `extensions`. Use `SchemaConstants.Extensions` constant.
|
|
158
158
|
|
|
@@ -34,24 +34,13 @@ The migration should include:
|
|
|
34
34
|
- RolePermissions (role assignments)
|
|
35
35
|
- Domain entity table
|
|
36
36
|
- Indexes and constraints
|
|
37
|
-
- SQL Objects
|
|
37
|
+
- SQL Objects: applied automatically at startup (no migration injection needed)
|
|
38
38
|
|
|
39
39
|
---
|
|
40
40
|
|
|
41
|
-
## SQL Objects
|
|
41
|
+
## SQL Objects
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
```
|
|
45
|
-
Glob: **/Persistence/SqlObjects/Functions/*.sql
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
If SQL files are found, the migration's `Up()` method should include a call to re-apply all SQL objects:
|
|
49
|
-
```csharp
|
|
50
|
-
// At the end of the Up() method:
|
|
51
|
-
SqlObjectHelper.ApplyAll(migrationBuilder);
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
This ensures SQL functions/views are re-applied with each migration (`CREATE OR ALTER` = idempotent).
|
|
43
|
+
SQL objects (TVFs, views, stored procedures) are applied **automatically at application startup** via `SqlObjectHelper.ApplyAllAsync()` in `SmartStackExtensions.InitializeSmartStackAsync()`. No injection into migrations is needed — just restart the application after modifying `.sql` files.
|
|
55
44
|
|
|
56
45
|
---
|
|
57
46
|
|
|
@@ -92,7 +81,6 @@ dotnet ef database update --context CoreDbContext
|
|
|
92
81
|
- [ ] Migration name suggested via MCP
|
|
93
82
|
- [ ] Migration created with correct context
|
|
94
83
|
- [ ] Migration includes navigation, permissions, and entity table
|
|
95
|
-
- [ ] SQL objects injected (if applicable)
|
|
96
84
|
- [ ] Migration applied successfully
|
|
97
85
|
- [ ] Database updated without errors
|
|
98
86
|
- [ ] DbSet registered in DbContext
|
|
@@ -86,10 +86,6 @@ dotnet ef migrations add {suggested_migration_name} --context ExtensionsDbContex
|
|
|
86
86
|
> NEVER create subdirectories under `Migrations/` (e.g., `Migrations/Extensions/`).
|
|
87
87
|
> All migrations for a given DbContext MUST share the same namespace to avoid EF Core errors.
|
|
88
88
|
|
|
89
|
-
### 2b. Inject SQL Objects (if any exist)
|
|
90
|
-
|
|
91
|
-
See [references/migration-checklist-troubleshooting.md](../references/migration-checklist-troubleshooting.md) for SQL Objects injection pattern.
|
|
92
|
-
|
|
93
89
|
### 3. Verify Migration Content
|
|
94
90
|
|
|
95
91
|
See [references/migration-checklist-troubleshooting.md](../references/migration-checklist-troubleshooting.md) for complete migration content verification checklist.
|
|
@@ -955,7 +955,7 @@ Level 4: RESOURCE (finest granularity)
|
|
|
955
955
|
## SQL OBJECTS INFRASTRUCTURE
|
|
956
956
|
|
|
957
957
|
> **Usage:** SQL Server functions (TVFs, scalar), views, and stored procedures managed as embedded resources
|
|
958
|
-
> **Pattern:** Same as SmartStack.app — `CREATE OR ALTER` for idempotency, applied
|
|
958
|
+
> **Pattern:** Same as SmartStack.app — `CREATE OR ALTER` for idempotency, applied automatically at startup
|
|
959
959
|
> **Location:** `Infrastructure/Persistence/SqlObjects/`
|
|
960
960
|
|
|
961
961
|
### Directory Structure
|
|
@@ -983,7 +983,7 @@ namespace {BaseNamespace}.Infrastructure.Persistence.SqlObjects;
|
|
|
983
983
|
|
|
984
984
|
/// <summary>
|
|
985
985
|
/// Helper for applying SQL objects from embedded resources.
|
|
986
|
-
/// SQL files use CREATE OR ALTER for idempotency —
|
|
986
|
+
/// SQL files use CREATE OR ALTER for idempotency — applied automatically at startup.
|
|
987
987
|
/// </summary>
|
|
988
988
|
public static class SqlObjectHelper
|
|
989
989
|
{
|
|
@@ -991,8 +991,19 @@ public static class SqlObjectHelper
|
|
|
991
991
|
private const string ResourcePrefix = "{BaseNamespace}.Infrastructure.Persistence.SqlObjects.";
|
|
992
992
|
|
|
993
993
|
/// <summary>
|
|
994
|
-
/// Applies all SQL objects
|
|
995
|
-
///
|
|
994
|
+
/// Applies all SQL objects directly to the database via DbContext.
|
|
995
|
+
/// Called at startup after MigrateAsync() — ensures SQL objects are always up-to-date
|
|
996
|
+
/// without requiring a new migration when a .sql file changes.
|
|
997
|
+
/// </summary>
|
|
998
|
+
public static async Task ApplyAllAsync(DbContext dbContext)
|
|
999
|
+
{
|
|
1000
|
+
await ApplyCategoryToDbAsync(dbContext, "Functions");
|
|
1001
|
+
// Future: await ApplyCategoryToDbAsync(dbContext, "Views");
|
|
1002
|
+
// Future: await ApplyCategoryToDbAsync(dbContext, "StoredProcedures");
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
/// <summary>
|
|
1006
|
+
/// Applies all SQL objects via MigrationBuilder (legacy, kept for backward compatibility).
|
|
996
1007
|
/// </summary>
|
|
997
1008
|
public static void ApplyAll(MigrationBuilder migrationBuilder)
|
|
998
1009
|
{
|
|
@@ -1004,12 +1015,6 @@ public static class SqlObjectHelper
|
|
|
1004
1015
|
ApplyCategory(migrationBuilder, "Functions");
|
|
1005
1016
|
}
|
|
1006
1017
|
|
|
1007
|
-
public static void ApplyOne(MigrationBuilder migrationBuilder, string resourceName)
|
|
1008
|
-
{
|
|
1009
|
-
var sql = ReadSqlObject(resourceName);
|
|
1010
|
-
migrationBuilder.Sql(sql);
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
1018
|
public static string ReadSqlObject(string resourceName)
|
|
1014
1019
|
{
|
|
1015
1020
|
var fullName = ResourcePrefix + resourceName;
|
|
@@ -1037,6 +1042,22 @@ public static class SqlObjectHelper
|
|
|
1037
1042
|
migrationBuilder.Sql(reader.ReadToEnd());
|
|
1038
1043
|
}
|
|
1039
1044
|
}
|
|
1045
|
+
|
|
1046
|
+
private static async Task ApplyCategoryToDbAsync(DbContext dbContext, string category)
|
|
1047
|
+
{
|
|
1048
|
+
var prefix = ResourcePrefix + category + ".";
|
|
1049
|
+
var resources = Assembly.GetManifestResourceNames()
|
|
1050
|
+
.Where(n => n.StartsWith(prefix, StringComparison.Ordinal)
|
|
1051
|
+
&& n.EndsWith(".sql", StringComparison.Ordinal))
|
|
1052
|
+
.OrderBy(n => n, StringComparer.Ordinal);
|
|
1053
|
+
|
|
1054
|
+
foreach (var resource in resources)
|
|
1055
|
+
{
|
|
1056
|
+
using var stream = Assembly.GetManifestResourceStream(resource)!;
|
|
1057
|
+
using var reader = new StreamReader(stream);
|
|
1058
|
+
await dbContext.Database.ExecuteSqlRawAsync(reader.ReadToEnd());
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1040
1061
|
}
|
|
1041
1062
|
```
|
|
1042
1063
|
|
|
@@ -1073,24 +1094,9 @@ RETURN
|
|
|
1073
1094
|
);
|
|
1074
1095
|
```
|
|
1075
1096
|
|
|
1076
|
-
###
|
|
1097
|
+
### Startup Integration
|
|
1077
1098
|
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
```csharp
|
|
1081
|
-
protected override void Up(MigrationBuilder migrationBuilder)
|
|
1082
|
-
{
|
|
1083
|
-
// ... table creation, indexes, etc.
|
|
1084
|
-
|
|
1085
|
-
// Apply all SQL objects (idempotent — CREATE OR ALTER)
|
|
1086
|
-
SqlObjectHelper.ApplyAll(migrationBuilder);
|
|
1087
|
-
}
|
|
1088
|
-
```
|
|
1089
|
-
|
|
1090
|
-
Or apply a single function:
|
|
1091
|
-
```csharp
|
|
1092
|
-
SqlObjectHelper.ApplyOne(migrationBuilder, "Functions.fn_GetEntityHierarchy.sql");
|
|
1093
|
-
```
|
|
1099
|
+
SQL objects are applied **automatically at application startup** via `SqlObjectHelper.ApplyAllAsync(dbContext)`, called in `SmartStackExtensions.InitializeSmartStackAsync()` right after `MigrateAsync()`. No migration injection needed — just add/modify `.sql` files and restart the application.
|
|
1094
1100
|
|
|
1095
1101
|
### When to Use SQL Objects
|
|
1096
1102
|
|
|
@@ -11,25 +11,21 @@ $ARGUMENTS
|
|
|
11
11
|
|
|
12
12
|
## Current state (auto-injected)
|
|
13
13
|
|
|
14
|
+
- Dev cache: !`cat .claude/config/dev-run.json 2>/dev/null || echo "no cache"`
|
|
14
15
|
- Dev session: !`cat .claude/dev-session.json 2>/dev/null || echo "no session"`
|
|
15
|
-
-
|
|
16
|
-
- Frontend port check: !`netstat.exe -ano 2>/dev/null | grep -E ":(6173|3000) .*LISTENING" | head -5 || echo "no frontend listening"`
|
|
17
|
-
- API directory: !`ls -d src/*.Api 2>/dev/null || echo "no API found"`
|
|
18
|
-
- Web directory: !`ls -d web/*-web 2>/dev/null || echo "no web found"`
|
|
19
|
-
- Local config: !`cat src/*.Api/appsettings.Local.json 2>/dev/null || echo "no local config"`
|
|
20
|
-
- Frontend env standalone: !`cat web/*-web/.env.standalone 2>/dev/null || echo "no .env.standalone"`
|
|
21
|
-
- Frontend env development: !`cat web/*-web/.env.development 2>/dev/null || echo "no .env.development"`
|
|
16
|
+
- Ports running: !`netstat.exe -ano 2>/dev/null | grep -E "LISTENING" | grep -E ":(5[0-9]{3}|6173|3000) " | head -5 || echo "none"`
|
|
22
17
|
|
|
23
18
|
<objective>
|
|
24
|
-
Launch the SmartStack development environment in one command: detect configuration, validate config coherence, start backend + frontend, and provide admin credentials.
|
|
19
|
+
Launch the SmartStack development environment in one command: detect configuration, validate config coherence, check SQL connectivity, start backend + frontend, and provide admin credentials.
|
|
25
20
|
Idempotent: safe to run multiple times. Detects already-running processes.
|
|
21
|
+
Uses a config cache to skip re-detection on subsequent runs when config files haven't changed.
|
|
26
22
|
</objective>
|
|
27
23
|
|
|
28
24
|
<quick_start>
|
|
29
25
|
```bash
|
|
30
26
|
/dev-start # Launch everything (idempotent)
|
|
31
27
|
/dev-start --stop # Stop backend + frontend
|
|
32
|
-
/dev-start --reset # Force admin password reset
|
|
28
|
+
/dev-start --reset # Force admin password reset + re-detect config
|
|
33
29
|
/dev-start --backend-only # Launch only the backend
|
|
34
30
|
/dev-start --frontend-only # Launch only the frontend
|
|
35
31
|
```
|
|
@@ -41,7 +37,7 @@ Idempotent: safe to run multiple times. Detects already-running processes.
|
|
|
41
37
|
|---------|-------------|
|
|
42
38
|
| `/dev-start` | Launch all (idempotent) + validate configs |
|
|
43
39
|
| `/dev-start --stop` | Stop backend + frontend via taskkill.exe |
|
|
44
|
-
| `/dev-start --reset` | Force reset admin password |
|
|
40
|
+
| `/dev-start --reset` | Force reset admin password + re-detect config |
|
|
45
41
|
| `/dev-start --backend-only` | Launch only backend |
|
|
46
42
|
| `/dev-start --frontend-only` | Launch only frontend |
|
|
47
43
|
|
|
@@ -53,22 +49,63 @@ Idempotent: safe to run multiple times. Detects already-running processes.
|
|
|
53
49
|
|
|
54
50
|
If `--stop` argument detected, execute the stop workflow and exit:
|
|
55
51
|
|
|
56
|
-
1.
|
|
52
|
+
1. Determine ports to stop:
|
|
53
|
+
- If `.claude/config/dev-run.json` exists → read `api_port` and `web_port` from cache
|
|
54
|
+
- Otherwise → scan default ports: 5142, 5290 (API) and 6173, 3000 (Web)
|
|
55
|
+
|
|
56
|
+
2. Find backend PID:
|
|
57
57
|
```bash
|
|
58
58
|
netstat.exe -ano | grep ":${API_PORT} .*LISTENING" | awk '{print $5}' | head -1
|
|
59
59
|
```
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
3. If found: `taskkill.exe /PID $PID /F`
|
|
61
|
+
4. Find frontend PID:
|
|
62
62
|
```bash
|
|
63
63
|
netstat.exe -ano | grep ":${WEB_PORT} .*LISTENING" | awk '{print $5}' | head -1
|
|
64
64
|
```
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
5. If found: `taskkill.exe /PID $PID /F`
|
|
66
|
+
6. Display stopped status and exit.
|
|
67
67
|
|
|
68
68
|
If no processes found, display "Nothing running" and exit.
|
|
69
69
|
|
|
70
|
+
## Step 0: Load cache
|
|
71
|
+
|
|
72
|
+
If `--stop` argument → skip this step entirely (--stop uses netstat only).
|
|
73
|
+
|
|
74
|
+
1. Read cache file:
|
|
75
|
+
```bash
|
|
76
|
+
cat .claude/config/dev-run.json 2>/dev/null
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
2. **If no cache OR `--reset` argument** → CACHE_HIT=false, go to Step 1.
|
|
80
|
+
|
|
81
|
+
3. **If cache exists**, verify config files haven't changed:
|
|
82
|
+
```bash
|
|
83
|
+
API_DIR=$(grep -o '"api_dir": *"[^"]*"' .claude/config/dev-run.json | cut -d'"' -f4)
|
|
84
|
+
WEB_DIR=$(grep -o '"web_dir": *"[^"]*"' .claude/config/dev-run.json | cut -d'"' -f4)
|
|
85
|
+
MODE=$(grep -o '"mode": *"[^"]*"' .claude/config/dev-run.json | cut -d'"' -f4)
|
|
86
|
+
|
|
87
|
+
if [ "$MODE" = "Local" ]; then
|
|
88
|
+
CURRENT_HASH=$(stat -c %Y "$API_DIR/appsettings.Local.json" "$WEB_DIR/.env.standalone" "$API_DIR/Properties/launchSettings.json" 2>/dev/null | md5sum | cut -d' ' -f1)
|
|
89
|
+
else
|
|
90
|
+
CURRENT_HASH=$(stat -c %Y "$API_DIR/appsettings.json" "$WEB_DIR/.env.development" "$API_DIR/Properties/launchSettings.json" 2>/dev/null | md5sum | cut -d' ' -f1)
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
CACHED_HASH=$(grep -o '"config_hash": *"[^"]*"' .claude/config/dev-run.json | cut -d'"' -f4)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
4. **If CURRENT_HASH = CACHED_HASH** → CACHE_HIT=true :
|
|
97
|
+
- Load all values from cache (api_dir, web_dir, mode, ports, commands, sql_status)
|
|
98
|
+
- Display: `[CACHE] Using cached config (from {cached_at})`
|
|
99
|
+
- **Skip directly to Step 4** (check if already running)
|
|
100
|
+
|
|
101
|
+
5. **If hash mismatch** → CACHE_HIT=false :
|
|
102
|
+
- Display: `[CACHE] Config changed, re-detecting...`
|
|
103
|
+
- Continue to Step 1
|
|
104
|
+
|
|
70
105
|
## Step 1: Detect project
|
|
71
106
|
|
|
107
|
+
**Skip if CACHE_HIT=true (values loaded from cache in Step 0).**
|
|
108
|
+
|
|
72
109
|
Find the API and Web directories:
|
|
73
110
|
- API: `src/*.Api` (first match)
|
|
74
111
|
- Web: `web/*-web` (first match)
|
|
@@ -77,6 +114,8 @@ If neither found, display error and exit.
|
|
|
77
114
|
|
|
78
115
|
## Step 2: Detect environment and ports
|
|
79
116
|
|
|
117
|
+
**Skip if CACHE_HIT=true (values loaded from cache in Step 0).**
|
|
118
|
+
|
|
80
119
|
Check if `appsettings.Local.json` exists in the API directory:
|
|
81
120
|
|
|
82
121
|
| Condition | Mode | Backend | Frontend | Backend command | Frontend command |
|
|
@@ -92,6 +131,8 @@ Check if `appsettings.Local.json` exists in the API directory:
|
|
|
92
131
|
|
|
93
132
|
## Step 3: Validate config coherence
|
|
94
133
|
|
|
134
|
+
**Skip if CACHE_HIT=true (values loaded from cache in Step 0).**
|
|
135
|
+
|
|
95
136
|
**CRITICAL: Verify that backend and frontend configs are consistent.**
|
|
96
137
|
|
|
97
138
|
### Rules to check:
|
|
@@ -142,6 +183,81 @@ Config Coherence Check:
|
|
|
142
183
|
-> Creating .env.standalone with correct values...
|
|
143
184
|
```
|
|
144
185
|
|
|
186
|
+
### Rule 5: SQL Server connectivity
|
|
187
|
+
|
|
188
|
+
1. Extract connection string from appsettings:
|
|
189
|
+
- **Local mode**: `appsettings.Local.json` → `ConnectionStrings.DefaultConnection`
|
|
190
|
+
- **Dev mode**: `appsettings.json` → `ConnectionStrings.DefaultConnection`
|
|
191
|
+
|
|
192
|
+
2. Parse server and database:
|
|
193
|
+
```bash
|
|
194
|
+
SQL_SERVER=$(echo "$CONN_STR" | grep -oiP '(?:Server|Data Source)=\K[^;]+')
|
|
195
|
+
SQL_DATABASE=$(echo "$CONN_STR" | grep -oiP '(?:Database|Initial Catalog)=\K[^;]+')
|
|
196
|
+
USE_WIN_AUTH=$(echo "$CONN_STR" | grep -qi 'Integrated Security=true\|Trusted_Connection=true' && echo "true" || echo "false")
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
3. Test connectivity (5s timeout):
|
|
200
|
+
```bash
|
|
201
|
+
# Normalize (local) → .
|
|
202
|
+
SQL_SERVER_NORM=$(echo "$SQL_SERVER" | sed 's/(local)/./')
|
|
203
|
+
|
|
204
|
+
if [ "$USE_WIN_AUTH" = "true" ]; then
|
|
205
|
+
sqlcmd -S "$SQL_SERVER_NORM" -d master -Q "SELECT 1" -t 5 -E -C -h -1 -W 2>/dev/null
|
|
206
|
+
else
|
|
207
|
+
SQL_USER=$(echo "$CONN_STR" | grep -oiP '(?:User Id|Uid)=\K[^;]+')
|
|
208
|
+
SQL_PASS=$(echo "$CONN_STR" | grep -oiP '(?:Password|Pwd)=\K[^;]+')
|
|
209
|
+
sqlcmd -S "$SQL_SERVER_NORM" -d master -Q "SELECT 1" -t 5 -U "$SQL_USER" -P "$SQL_PASS" -C -h -1 -W 2>/dev/null
|
|
210
|
+
fi
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
4. If reachable, check database exists:
|
|
214
|
+
```bash
|
|
215
|
+
sqlcmd -S "$SQL_SERVER_NORM" -Q "SET NOCOUNT ON; SELECT name FROM sys.databases WHERE name = '$SQL_DATABASE'" -t 5 -E -C -h -1 -W 2>/dev/null
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
5. Output format:
|
|
219
|
+
| Scenario | Output | Blocking? |
|
|
220
|
+
|----------|--------|-----------|
|
|
221
|
+
| Server OK + DB exists | `[OK] SQL Server (server) — Database 'db' exists` | No |
|
|
222
|
+
| Server OK + DB missing | `[WARN] Database 'db' not found (AutoMigrate will create it)` | No |
|
|
223
|
+
| Server unreachable | `[FAIL] SQL Server (server) unreachable — Is the service running?` | No |
|
|
224
|
+
| sqlcmd not installed | `[SKIP] sqlcmd not found — SQL check skipped` | No |
|
|
225
|
+
| No connection string | `[SKIP] No connection string — SQL check skipped` | No |
|
|
226
|
+
|
|
227
|
+
## Step 3b: Write cache
|
|
228
|
+
|
|
229
|
+
**Only execute if CACHE_HIT=false (Steps 1-3 were executed).**
|
|
230
|
+
|
|
231
|
+
1. Ensure directory exists: `mkdir -p .claude/config`
|
|
232
|
+
|
|
233
|
+
2. Compute config hash:
|
|
234
|
+
```bash
|
|
235
|
+
if [ "$MODE" = "Local" ]; then
|
|
236
|
+
CONFIG_HASH=$(stat -c %Y "$API_DIR/appsettings.Local.json" "$WEB_DIR/.env.standalone" "$API_DIR/Properties/launchSettings.json" 2>/dev/null | md5sum | cut -d' ' -f1)
|
|
237
|
+
else
|
|
238
|
+
CONFIG_HASH=$(stat -c %Y "$API_DIR/appsettings.json" "$WEB_DIR/.env.development" "$API_DIR/Properties/launchSettings.json" 2>/dev/null | md5sum | cut -d' ' -f1)
|
|
239
|
+
fi
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
3. Write `.claude/config/dev-run.json` using the Write tool with all detected values:
|
|
243
|
+
```json
|
|
244
|
+
{
|
|
245
|
+
"version": 1,
|
|
246
|
+
"api_dir": "{API_DIR}",
|
|
247
|
+
"web_dir": "{WEB_DIR}",
|
|
248
|
+
"mode": "{MODE}",
|
|
249
|
+
"api_port": {API_PORT},
|
|
250
|
+
"web_port": {WEB_PORT},
|
|
251
|
+
"backend_cmd": "{BACKEND_CMD}",
|
|
252
|
+
"frontend_cmd": "{FRONTEND_CMD}",
|
|
253
|
+
"sql_server": "{SQL_SERVER}",
|
|
254
|
+
"sql_database": "{SQL_DATABASE}",
|
|
255
|
+
"sql_status": "{ok|unreachable|db_missing|skipped}",
|
|
256
|
+
"config_hash": "{CONFIG_HASH}",
|
|
257
|
+
"cached_at": "{ISO_TIMESTAMP}"
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
145
261
|
## Step 4: Check if already running
|
|
146
262
|
|
|
147
263
|
```bash
|
|
@@ -218,15 +334,17 @@ Use `Bash(run_in_background=true)` so it runs in background.
|
|
|
218
334
|
SMARTSTACK DEV ENVIRONMENT
|
|
219
335
|
================================================================
|
|
220
336
|
|
|
221
|
-
Backend:
|
|
222
|
-
Frontend:
|
|
223
|
-
Swagger:
|
|
337
|
+
Backend: http://localhost:{API_PORT} [RUNNING|STARTING]
|
|
338
|
+
Frontend: http://localhost:{WEB_PORT} [RUNNING|STARTING]
|
|
339
|
+
Swagger: http://localhost:{API_PORT}/scalar
|
|
340
|
+
SQL Server: {SQL_SERVER}/{SQL_DATABASE} [OK|WARN|FAIL|SKIP]
|
|
224
341
|
|
|
225
|
-
Email:
|
|
226
|
-
Password:
|
|
342
|
+
Email: local.admin@smartstack.local
|
|
343
|
+
Password: xxxxxxxxxxxxxx
|
|
227
344
|
|
|
228
345
|
Environment: {Development|Local}
|
|
229
346
|
Config: OK | WARNING (details)
|
|
347
|
+
Cache: HIT (from {cached_at}) | MISS (re-detected)
|
|
230
348
|
================================================================
|
|
231
349
|
```
|
|
232
350
|
|
|
@@ -240,6 +358,8 @@ Use `Bash(run_in_background=true)` so it runs in background.
|
|
|
240
358
|
4. **Config coherence is critical** - Mismatched ports between backend/frontend is the #1 cause of "it doesn't work" issues
|
|
241
359
|
5. **Idempotent** - Running twice should detect already-running processes and reuse stored credentials
|
|
242
360
|
6. **dev-session.json contains secrets** - Must be in `.gitignore`
|
|
361
|
+
7. **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.
|
|
362
|
+
8. **SQL check is non-blocking** - A failed SQL check displays a warning but does not prevent launch. AutoMigrate may create the database on startup.
|
|
243
363
|
|
|
244
364
|
</important_notes>
|
|
245
365
|
|
|
@@ -249,4 +369,6 @@ Use `Bash(run_in_background=true)` so it runs in background.
|
|
|
249
369
|
- Admin credentials available (reused or freshly reset)
|
|
250
370
|
- Connection info displayed in clear format
|
|
251
371
|
- No errors on repeated invocations (idempotent)
|
|
372
|
+
- Cache written on first run, reused on subsequent runs
|
|
373
|
+
- SQL Server connectivity reported
|
|
252
374
|
</success_criteria>
|