@polymorphism-tech/morph-spec 1.0.2 → 2.0.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.
Files changed (152) hide show
  1. package/CLAUDE.md +1381 -0
  2. package/LICENSE +72 -0
  3. package/README.md +114 -12
  4. package/bin/detect-agents.js +225 -0
  5. package/bin/morph-spec.js +120 -0
  6. package/bin/render-template.js +302 -0
  7. package/bin/semantic-detect-agents.js +246 -0
  8. package/bin/validate-agents-skills.js +239 -0
  9. package/bin/validate-agents.js +69 -0
  10. package/bin/validate-phase.js +263 -0
  11. package/content/.azure/README.md +293 -0
  12. package/content/.azure/docs/azure-devops-setup.md +454 -0
  13. package/content/.azure/docs/branch-strategy.md +398 -0
  14. package/content/.azure/docs/local-development.md +515 -0
  15. package/content/.azure/pipelines/pipeline-variables.yml +34 -0
  16. package/content/.azure/pipelines/prod-pipeline.yml +319 -0
  17. package/content/.azure/pipelines/staging-pipeline.yml +234 -0
  18. package/content/.azure/pipelines/templates/build-dotnet.yml +75 -0
  19. package/content/.azure/pipelines/templates/deploy-app-service.yml +94 -0
  20. package/content/.azure/pipelines/templates/deploy-container-app.yml +120 -0
  21. package/content/.azure/pipelines/templates/infra-deploy.yml +90 -0
  22. package/content/.claude/commands/morph-apply.md +118 -26
  23. package/content/.claude/commands/morph-archive.md +9 -9
  24. package/content/.claude/commands/morph-clarify.md +184 -0
  25. package/content/.claude/commands/morph-design.md +275 -0
  26. package/content/.claude/commands/morph-proposal.md +56 -15
  27. package/content/.claude/commands/morph-setup.md +100 -0
  28. package/content/.claude/commands/morph-status.md +47 -32
  29. package/content/.claude/commands/morph-tasks.md +319 -0
  30. package/content/.claude/commands/morph-uiux.md +211 -0
  31. package/content/.claude/skills/specialists/ai-system-architect.md +604 -0
  32. package/content/.claude/skills/specialists/ms-agent-expert.md +143 -89
  33. package/content/.claude/skills/specialists/ui-ux-designer.md +744 -9
  34. package/content/.claude/skills/stacks/dotnet-blazor.md +244 -8
  35. package/content/.claude/skills/stacks/dotnet-nextjs.md +2 -2
  36. package/content/.morph/.morphversion +5 -0
  37. package/content/.morph/config/agents.json +101 -8
  38. package/content/.morph/config/azure-pricing.json +70 -0
  39. package/content/.morph/config/azure-pricing.schema.json +50 -0
  40. package/content/.morph/config/config.template.json +15 -3
  41. package/content/.morph/docs/STORY-DRIVEN-DEVELOPMENT.md +392 -0
  42. package/content/.morph/hooks/README.md +239 -0
  43. package/content/.morph/hooks/pre-commit-agents.sh +24 -0
  44. package/content/.morph/hooks/pre-commit-all.sh +48 -0
  45. package/content/.morph/hooks/pre-commit-costs.sh +91 -0
  46. package/content/.morph/hooks/pre-commit-specs.sh +49 -0
  47. package/content/.morph/hooks/pre-commit-tests.sh +60 -0
  48. package/content/.morph/project.md +5 -4
  49. package/content/.morph/schemas/agent.schema.json +296 -0
  50. package/content/.morph/standards/agent-framework-setup.md +453 -0
  51. package/content/.morph/standards/architecture.md +142 -7
  52. package/content/.morph/standards/azure.md +218 -23
  53. package/content/.morph/standards/coding.md +47 -12
  54. package/content/.morph/standards/dotnet10-migration.md +494 -0
  55. package/content/.morph/standards/fluent-ui-setup.md +590 -0
  56. package/content/.morph/standards/migration-guide.md +514 -0
  57. package/content/.morph/standards/passkeys-auth.md +423 -0
  58. package/content/.morph/standards/vector-search-rag.md +536 -0
  59. package/content/.morph/state.json +18 -0
  60. package/content/.morph/templates/FluentDesignTheme.cs +149 -0
  61. package/content/.morph/templates/MudTheme.cs +281 -0
  62. package/content/.morph/templates/contracts.cs +55 -55
  63. package/content/.morph/templates/decisions.md +4 -4
  64. package/content/.morph/templates/design-system.css +226 -0
  65. package/content/.morph/templates/infra/.dockerignore.example +89 -0
  66. package/content/.morph/templates/infra/Dockerfile.example +82 -0
  67. package/content/.morph/templates/infra/README.md +286 -0
  68. package/content/.morph/templates/infra/app-service.bicep +164 -0
  69. package/content/.morph/templates/infra/deploy.ps1 +229 -0
  70. package/content/.morph/templates/infra/deploy.sh +208 -0
  71. package/content/.morph/templates/infra/main.bicep +41 -7
  72. package/content/.morph/templates/infra/parameters.dev.json +6 -0
  73. package/content/.morph/templates/infra/parameters.prod.json +6 -0
  74. package/content/.morph/templates/infra/parameters.staging.json +29 -0
  75. package/content/.morph/templates/proposal.md +3 -3
  76. package/content/.morph/templates/recap.md +3 -3
  77. package/content/.morph/templates/spec.md +9 -8
  78. package/content/.morph/templates/sprint-status.yaml +68 -0
  79. package/content/.morph/templates/state.template.json +222 -0
  80. package/content/.morph/templates/story.md +143 -0
  81. package/content/.morph/templates/tasks.md +1 -1
  82. package/content/.morph/templates/ui-components.md +276 -0
  83. package/content/.morph/templates/ui-design-system.md +286 -0
  84. package/content/.morph/templates/ui-flows.md +336 -0
  85. package/content/.morph/templates/ui-mockups.md +133 -0
  86. package/content/.morph/test-infra/example.bicep +59 -0
  87. package/content/CLAUDE.md +124 -0
  88. package/content/README.md +79 -0
  89. package/detectors/config-detector.js +223 -0
  90. package/detectors/conversation-analyzer.js +163 -0
  91. package/detectors/index.js +84 -0
  92. package/detectors/standards-generator.js +275 -0
  93. package/detectors/structure-detector.js +221 -0
  94. package/docs/README.md +149 -0
  95. package/docs/api/cost-calculator.js.html +513 -0
  96. package/docs/api/design-system-generator.js.html +382 -0
  97. package/docs/api/fonts/Montserrat/Montserrat-Bold.eot +0 -0
  98. package/docs/api/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
  99. package/docs/api/fonts/Montserrat/Montserrat-Bold.woff +0 -0
  100. package/docs/api/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
  101. package/docs/api/fonts/Montserrat/Montserrat-Regular.eot +0 -0
  102. package/docs/api/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
  103. package/docs/api/fonts/Montserrat/Montserrat-Regular.woff +0 -0
  104. package/docs/api/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
  105. package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
  106. package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +978 -0
  107. package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
  108. package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
  109. package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
  110. package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
  111. package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1049 -0
  112. package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
  113. package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
  114. package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
  115. package/docs/api/global.html +5263 -0
  116. package/docs/api/index.html +96 -0
  117. package/docs/api/scripts/collapse.js +39 -0
  118. package/docs/api/scripts/commonNav.js +28 -0
  119. package/docs/api/scripts/linenumber.js +25 -0
  120. package/docs/api/scripts/nav.js +12 -0
  121. package/docs/api/scripts/polyfill.js +4 -0
  122. package/docs/api/scripts/prettify/Apache-License-2.0.txt +202 -0
  123. package/docs/api/scripts/prettify/lang-css.js +2 -0
  124. package/docs/api/scripts/prettify/prettify.js +28 -0
  125. package/docs/api/scripts/search.js +99 -0
  126. package/docs/api/state-manager.js.html +423 -0
  127. package/docs/api/styles/jsdoc.css +776 -0
  128. package/docs/api/styles/prettify.css +80 -0
  129. package/docs/examples.md +328 -0
  130. package/docs/getting-started.md +302 -0
  131. package/docs/installation.md +361 -0
  132. package/docs/templates.md +418 -0
  133. package/docs/validation-checklist.md +266 -0
  134. package/package.json +39 -12
  135. package/src/commands/cost.js +181 -0
  136. package/src/commands/create-story.js +283 -0
  137. package/src/commands/detect.js +104 -0
  138. package/src/commands/doctor.js +67 -0
  139. package/src/commands/generate.js +149 -0
  140. package/src/commands/init.js +71 -46
  141. package/src/commands/shard-spec.js +224 -0
  142. package/src/commands/sprint-status.js +250 -0
  143. package/src/commands/state.js +333 -0
  144. package/src/commands/sync.js +167 -0
  145. package/src/commands/update-pricing.js +206 -0
  146. package/src/commands/update.js +88 -13
  147. package/src/lib/complexity-analyzer.js +292 -0
  148. package/src/lib/cost-calculator.js +429 -0
  149. package/src/lib/design-system-generator.js +298 -0
  150. package/src/lib/state-manager.js +340 -0
  151. package/src/utils/file-copier.js +63 -0
  152. package/src/utils/version-checker.js +175 -0
@@ -0,0 +1,164 @@
1
+ // ==============================================================================
2
+ // MORPH-SPEC - App Service
3
+ // Azure App Service with Free F1 tier support
4
+ // ==============================================================================
5
+
6
+ @description('App Service name')
7
+ param name string
8
+
9
+ @description('Location')
10
+ param location string
11
+
12
+ @description('Tags')
13
+ param tags object = {}
14
+
15
+ @description('App Service Plan SKU (F1, B1, S1, P1v2)')
16
+ @allowed(['F1', 'B1', 'S1', 'P1v2', 'P2v2'])
17
+ param sku string = 'F1'
18
+
19
+ @description('App Insights connection string')
20
+ param appInsightsConnectionString string = ''
21
+
22
+ @description('.NET version')
23
+ @allowed(['v6.0', 'v7.0', 'v8.0'])
24
+ param dotnetVersion string = 'v8.0'
25
+
26
+ @description('Always On (not available on Free tier)')
27
+ param alwaysOn bool = false
28
+
29
+ @description('Environment variables')
30
+ param envVars object = {}
31
+
32
+ // ==============================================================================
33
+ // VARIABLES
34
+ // ==============================================================================
35
+
36
+ var skuTiers = {
37
+ F1: 'Free'
38
+ B1: 'Basic'
39
+ S1: 'Standard'
40
+ P1v2: 'PremiumV2'
41
+ P2v2: 'PremiumV2'
42
+ }
43
+
44
+ var planName = 'plan-${name}'
45
+
46
+ // Merge default env vars with custom ones
47
+ var defaultEnvVars = {
48
+ ASPNETCORE_ENVIRONMENT: 'Production'
49
+ APPLICATIONINSIGHTS_CONNECTION_STRING: appInsightsConnectionString
50
+ }
51
+
52
+ var mergedEnvVars = union(defaultEnvVars, envVars)
53
+
54
+ // ==============================================================================
55
+ // APP SERVICE PLAN
56
+ // ==============================================================================
57
+
58
+ resource appServicePlan 'Microsoft.Web/serverfarms@2022-09-01' = {
59
+ name: planName
60
+ location: location
61
+ tags: tags
62
+ sku: {
63
+ name: sku
64
+ tier: skuTiers[sku]
65
+ }
66
+ kind: 'linux'
67
+ properties: {
68
+ reserved: true // Required for Linux
69
+ }
70
+ }
71
+
72
+ // ==============================================================================
73
+ // APP SERVICE
74
+ // ==============================================================================
75
+
76
+ resource appService 'Microsoft.Web/sites@2022-09-01' = {
77
+ name: name
78
+ location: location
79
+ tags: tags
80
+ kind: 'app,linux'
81
+ identity: {
82
+ type: 'SystemAssigned'
83
+ }
84
+ properties: {
85
+ serverFarmId: appServicePlan.id
86
+ httpsOnly: true
87
+ siteConfig: {
88
+ linuxFxVersion: 'DOTNETCORE|${dotnetVersion}'
89
+ alwaysOn: alwaysOn
90
+ minTlsVersion: '1.2'
91
+ ftpsState: 'Disabled'
92
+ http20Enabled: true
93
+ healthCheckPath: '/health'
94
+ appSettings: [for key in objectKeys(mergedEnvVars): {
95
+ name: key
96
+ value: mergedEnvVars[key]
97
+ }]
98
+ cors: {
99
+ allowedOrigins: [
100
+ 'https://portal.azure.com'
101
+ ]
102
+ supportCredentials: false
103
+ }
104
+ }
105
+ clientAffinityEnabled: false // Recommended for stateless apps
106
+ }
107
+ }
108
+
109
+ // ==============================================================================
110
+ // DEPLOYMENT SLOT (Only for non-Free tiers)
111
+ // ==============================================================================
112
+
113
+ resource stagingSlot 'Microsoft.Web/sites/slots@2022-09-01' = if (sku != 'F1') {
114
+ name: 'staging'
115
+ parent: appService
116
+ location: location
117
+ tags: tags
118
+ kind: 'app,linux'
119
+ identity: {
120
+ type: 'SystemAssigned'
121
+ }
122
+ properties: {
123
+ serverFarmId: appServicePlan.id
124
+ httpsOnly: true
125
+ siteConfig: {
126
+ linuxFxVersion: 'DOTNETCORE|${dotnetVersion}'
127
+ alwaysOn: alwaysOn
128
+ minTlsVersion: '1.2'
129
+ ftpsState: 'Disabled'
130
+ http20Enabled: true
131
+ healthCheckPath: '/health'
132
+ appSettings: [for key in objectKeys(mergedEnvVars): {
133
+ name: key
134
+ value: mergedEnvVars[key]
135
+ }]
136
+ }
137
+ clientAffinityEnabled: false
138
+ }
139
+ }
140
+
141
+ // ==============================================================================
142
+ // OUTPUTS
143
+ // ==============================================================================
144
+
145
+ @description('App Service ID')
146
+ output id string = appService.id
147
+
148
+ @description('App Service name')
149
+ output name string = appService.name
150
+
151
+ @description('App Service URL')
152
+ output url string = 'https://${appService.properties.defaultHostName}'
153
+
154
+ @description('App Service default hostname')
155
+ output defaultHostName string = appService.properties.defaultHostName
156
+
157
+ @description('App Service Plan ID')
158
+ output planId string = appServicePlan.id
159
+
160
+ @description('App Service Managed Identity Principal ID')
161
+ output principalId string = appService.identity.principalId
162
+
163
+ @description('Staging slot URL (if available)')
164
+ output stagingUrl string = sku != 'F1' ? 'https://${stagingSlot.properties.defaultHostName}' : ''
@@ -0,0 +1,229 @@
1
+ # ==============================================================================
2
+ # MORPH-SPEC - Deploy Script (PowerShell)
3
+ # Automated deployment of Azure infrastructure
4
+ # ==============================================================================
5
+
6
+ [CmdletBinding()]
7
+ param(
8
+ [Parameter(Mandatory=$false)]
9
+ [string]$AppName = "myapp",
10
+
11
+ [Parameter(Mandatory=$false)]
12
+ [ValidateSet("dev", "staging", "prod")]
13
+ [string]$Environment = "dev",
14
+
15
+ [Parameter(Mandatory=$false)]
16
+ [string]$Location = "eastus",
17
+
18
+ [Parameter(Mandatory=$false)]
19
+ [string]$SubscriptionId,
20
+
21
+ [Parameter(Mandatory=$false)]
22
+ [ValidateSet("appservice", "containerapp")]
23
+ [string]$HostingType = "appservice",
24
+
25
+ [Parameter(Mandatory=$false)]
26
+ [ValidateSet("F1", "B1", "S1", "P1v2")]
27
+ [string]$AppServiceSku = "F1",
28
+
29
+ [Parameter(Mandatory=$false)]
30
+ [string]$ContainerImage = "mcr.microsoft.com/hello-world:latest"
31
+ )
32
+
33
+ # ==============================================================================
34
+ # CONFIGURATION
35
+ # ==============================================================================
36
+
37
+ $ErrorActionPreference = "Stop"
38
+
39
+ $ResourceGroup = "rg-$AppName-$Environment"
40
+ $DeploymentName = "deploy-$AppName-$(Get-Date -Format 'yyyyMMdd-HHmmss')"
41
+
42
+ # ==============================================================================
43
+ # FUNCTIONS
44
+ # ==============================================================================
45
+
46
+ function Write-InfoLog {
47
+ param([string]$Message)
48
+ Write-Host "ℹ️ $Message" -ForegroundColor Blue
49
+ }
50
+
51
+ function Write-SuccessLog {
52
+ param([string]$Message)
53
+ Write-Host "✅ $Message" -ForegroundColor Green
54
+ }
55
+
56
+ function Write-WarningLog {
57
+ param([string]$Message)
58
+ Write-Host "⚠️ $Message" -ForegroundColor Yellow
59
+ }
60
+
61
+ function Write-ErrorLog {
62
+ param([string]$Message)
63
+ Write-Host "❌ $Message" -ForegroundColor Red
64
+ }
65
+
66
+ function Test-Prerequisites {
67
+ Write-InfoLog "Checking prerequisites..."
68
+
69
+ # Check Azure CLI
70
+ $azCliInstalled = Get-Command az -ErrorAction SilentlyContinue
71
+ if (-not $azCliInstalled) {
72
+ Write-ErrorLog "Azure CLI not found. Install from: https://aka.ms/azure-cli"
73
+ exit 1
74
+ }
75
+
76
+ # Check login
77
+ try {
78
+ $null = az account show 2>$null
79
+ }
80
+ catch {
81
+ Write-ErrorLog "Not logged in to Azure. Run: az login"
82
+ exit 1
83
+ }
84
+
85
+ # Set subscription if provided
86
+ if ($SubscriptionId) {
87
+ Write-InfoLog "Setting subscription to: $SubscriptionId"
88
+ az account set --subscription $SubscriptionId
89
+ }
90
+
91
+ Write-SuccessLog "Prerequisites checked"
92
+ }
93
+
94
+ function New-ResourceGroupIfNotExists {
95
+ Write-InfoLog "Creating resource group: $ResourceGroup"
96
+
97
+ $exists = az group exists -n $ResourceGroup
98
+ if ($exists -eq "true") {
99
+ Write-WarningLog "Resource group already exists"
100
+ }
101
+ else {
102
+ az group create `
103
+ --name $ResourceGroup `
104
+ --location $Location `
105
+ --tags `
106
+ environment=$Environment `
107
+ application=$AppName `
108
+ managedBy=bicep `
109
+ framework=morph-spec
110
+
111
+ Write-SuccessLog "Resource group created"
112
+ }
113
+ }
114
+
115
+ function New-SqlPassword {
116
+ # Generate secure random password
117
+ $bytes = New-Object byte[] 32
118
+ [Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($bytes)
119
+ $password = [Convert]::ToBase64String($bytes) -replace '[=+/]', '' | Select-Object -First 25
120
+
121
+ Write-SuccessLog "SQL password generated (stored in Key Vault after deploy)"
122
+ return $password
123
+ }
124
+
125
+ function Deploy-Infrastructure {
126
+ param([string]$SqlPassword)
127
+
128
+ Write-InfoLog "Deploying infrastructure..."
129
+ Write-InfoLog " App Name: $AppName"
130
+ Write-InfoLog " Environment: $Environment"
131
+ Write-InfoLog " Hosting Type: $HostingType"
132
+ Write-InfoLog " Location: $Location"
133
+
134
+ # Prepare parameters file
135
+ $paramsFile = "parameters.$Environment.json"
136
+
137
+ if (-not (Test-Path $paramsFile)) {
138
+ Write-ErrorLog "Parameters file not found: $paramsFile"
139
+ exit 1
140
+ }
141
+
142
+ # Deploy
143
+ az deployment group create `
144
+ --resource-group $ResourceGroup `
145
+ --name $DeploymentName `
146
+ --template-file main.bicep `
147
+ --parameters "@$paramsFile" `
148
+ --parameters `
149
+ appName=$AppName `
150
+ environment=$Environment `
151
+ location=$Location `
152
+ hostingType=$HostingType `
153
+ appServiceSku=$AppServiceSku `
154
+ containerImage=$ContainerImage `
155
+ sqlAdminPassword=$SqlPassword `
156
+ --output table
157
+
158
+ Write-SuccessLog "Infrastructure deployed"
159
+ }
160
+
161
+ function Show-Outputs {
162
+ Write-InfoLog "Retrieving deployment outputs..."
163
+
164
+ $appUrl = az deployment group show `
165
+ -g $ResourceGroup `
166
+ -n $DeploymentName `
167
+ --query properties.outputs.appUrl.value -o tsv
168
+
169
+ $sqlConnection = az deployment group show `
170
+ -g $ResourceGroup `
171
+ -n $DeploymentName `
172
+ --query properties.outputs.sqlConnectionString.value -o tsv
173
+
174
+ $appInsightsConnection = az deployment group show `
175
+ -g $ResourceGroup `
176
+ -n $DeploymentName `
177
+ --query properties.outputs.appInsightsConnectionString.value -o tsv
178
+
179
+ Write-Host ""
180
+ Write-Host "╔════════════════════════════════════════════════════════════════╗" -ForegroundColor Green
181
+ Write-Host "║ DEPLOYMENT SUCCESSFUL ║" -ForegroundColor Green
182
+ Write-Host "╚════════════════════════════════════════════════════════════════╝" -ForegroundColor Green
183
+ Write-Host ""
184
+ Write-Host "🌐 Application URL:" -ForegroundColor Cyan
185
+ Write-Host " $appUrl"
186
+ Write-Host ""
187
+ Write-Host "🗄️ SQL Connection String:" -ForegroundColor Cyan
188
+ Write-Host " $sqlConnection"
189
+ Write-Host ""
190
+ Write-Host "📊 App Insights Connection String:" -ForegroundColor Cyan
191
+ Write-Host " $appInsightsConnection"
192
+ Write-Host ""
193
+ Write-Host "💡 Next steps:" -ForegroundColor Yellow
194
+
195
+ if ($HostingType -eq "appservice") {
196
+ Write-Host " 1. Deploy your code: az webapp up --name app-$AppName-$Environment"
197
+ Write-Host " 2. View logs: az webapp log tail --name app-$AppName-$Environment -g $ResourceGroup"
198
+ }
199
+ else {
200
+ Write-Host " 1. Build and push container: az acr build --registry <ACR> --image ${AppName}:latest ."
201
+ Write-Host " 2. Update container app: az containerapp update -n ca-$AppName-$Environment -g $ResourceGroup --image <IMAGE>"
202
+ Write-Host " 3. View logs: az containerapp logs show -n ca-$AppName-$Environment -g $ResourceGroup --follow"
203
+ }
204
+
205
+ Write-Host ""
206
+ }
207
+
208
+ # ==============================================================================
209
+ # MAIN
210
+ # ==============================================================================
211
+
212
+ function Main {
213
+ Write-Host ""
214
+ Write-Host "╔════════════════════════════════════════════════════════════════╗" -ForegroundColor Cyan
215
+ Write-Host "║ MORPH-SPEC - Azure Infrastructure Deploy ║" -ForegroundColor Cyan
216
+ Write-Host "╚════════════════════════════════════════════════════════════════╝" -ForegroundColor Cyan
217
+ Write-Host ""
218
+
219
+ Test-Prerequisites
220
+ New-ResourceGroupIfNotExists
221
+ $sqlPassword = New-SqlPassword
222
+ Deploy-Infrastructure -SqlPassword $sqlPassword
223
+ Show-Outputs
224
+
225
+ Write-SuccessLog "Deployment complete! 🚀"
226
+ }
227
+
228
+ # Run main function
229
+ Main
@@ -0,0 +1,208 @@
1
+ #!/bin/bash
2
+ # ==============================================================================
3
+ # MORPH-SPEC - Deploy Script
4
+ # Automated deployment of Azure infrastructure
5
+ # ==============================================================================
6
+
7
+ set -e # Exit on error
8
+
9
+ # ==============================================================================
10
+ # CONFIGURATION
11
+ # ==============================================================================
12
+
13
+ # Required variables (override via environment)
14
+ APP_NAME="${APP_NAME:-myapp}"
15
+ ENVIRONMENT="${ENVIRONMENT:-dev}"
16
+ LOCATION="${LOCATION:-eastus}"
17
+ SUBSCRIPTION_ID="${SUBSCRIPTION_ID:-}"
18
+
19
+ # Optional variables
20
+ HOSTING_TYPE="${HOSTING_TYPE:-appservice}" # appservice or containerapp
21
+ APP_SERVICE_SKU="${APP_SERVICE_SKU:-F1}"
22
+ CONTAINER_IMAGE="${CONTAINER_IMAGE:-mcr.microsoft.com/hello-world:latest}"
23
+
24
+ # Derived variables
25
+ RESOURCE_GROUP="rg-${APP_NAME}-${ENVIRONMENT}"
26
+ DEPLOYMENT_NAME="deploy-${APP_NAME}-$(date +%Y%m%d-%H%M%S)"
27
+
28
+ # ==============================================================================
29
+ # COLORS
30
+ # ==============================================================================
31
+
32
+ RED='\033[0;31m'
33
+ GREEN='\033[0;32m'
34
+ YELLOW='\033[1;33m'
35
+ BLUE='\033[0;34m'
36
+ NC='\033[0m' # No Color
37
+
38
+ # ==============================================================================
39
+ # FUNCTIONS
40
+ # ==============================================================================
41
+
42
+ log_info() {
43
+ echo -e "${BLUE}ℹ️ $1${NC}"
44
+ }
45
+
46
+ log_success() {
47
+ echo -e "${GREEN}✅ $1${NC}"
48
+ }
49
+
50
+ log_warning() {
51
+ echo -e "${YELLOW}⚠️ $1${NC}"
52
+ }
53
+
54
+ log_error() {
55
+ echo -e "${RED}❌ $1${NC}"
56
+ }
57
+
58
+ check_prerequisites() {
59
+ log_info "Checking prerequisites..."
60
+
61
+ # Check Azure CLI
62
+ if ! command -v az &> /dev/null; then
63
+ log_error "Azure CLI not found. Install from: https://aka.ms/azure-cli"
64
+ exit 1
65
+ fi
66
+
67
+ # Check login
68
+ if ! az account show &> /dev/null; then
69
+ log_error "Not logged in to Azure. Run: az login"
70
+ exit 1
71
+ fi
72
+
73
+ # Set subscription if provided
74
+ if [ -n "$SUBSCRIPTION_ID" ]; then
75
+ log_info "Setting subscription to: $SUBSCRIPTION_ID"
76
+ az account set --subscription "$SUBSCRIPTION_ID"
77
+ fi
78
+
79
+ log_success "Prerequisites checked"
80
+ }
81
+
82
+ create_resource_group() {
83
+ log_info "Creating resource group: $RESOURCE_GROUP"
84
+
85
+ if az group exists -n "$RESOURCE_GROUP" | grep -q true; then
86
+ log_warning "Resource group already exists"
87
+ else
88
+ az group create \
89
+ --name "$RESOURCE_GROUP" \
90
+ --location "$LOCATION" \
91
+ --tags \
92
+ environment="$ENVIRONMENT" \
93
+ application="$APP_NAME" \
94
+ managedBy="bicep" \
95
+ framework="morph-spec"
96
+
97
+ log_success "Resource group created"
98
+ fi
99
+ }
100
+
101
+ generate_sql_password() {
102
+ # Generate secure random password
103
+ SQL_PASSWORD=$(openssl rand -base64 32 | tr -d "=+/" | cut -c1-25)
104
+ log_success "SQL password generated (stored in Key Vault after deploy)"
105
+ }
106
+
107
+ deploy_infrastructure() {
108
+ log_info "Deploying infrastructure..."
109
+ log_info " App Name: $APP_NAME"
110
+ log_info " Environment: $ENVIRONMENT"
111
+ log_info " Hosting Type: $HOSTING_TYPE"
112
+ log_info " Location: $LOCATION"
113
+
114
+ # Prepare parameters file
115
+ PARAMS_FILE="parameters.${ENVIRONMENT}.json"
116
+
117
+ if [ ! -f "$PARAMS_FILE" ]; then
118
+ log_error "Parameters file not found: $PARAMS_FILE"
119
+ exit 1
120
+ fi
121
+
122
+ # Deploy
123
+ az deployment group create \
124
+ --resource-group "$RESOURCE_GROUP" \
125
+ --name "$DEPLOYMENT_NAME" \
126
+ --template-file main.bicep \
127
+ --parameters "@$PARAMS_FILE" \
128
+ --parameters \
129
+ appName="$APP_NAME" \
130
+ environment="$ENVIRONMENT" \
131
+ location="$LOCATION" \
132
+ hostingType="$HOSTING_TYPE" \
133
+ appServiceSku="$APP_SERVICE_SKU" \
134
+ containerImage="$CONTAINER_IMAGE" \
135
+ sqlAdminPassword="$SQL_PASSWORD" \
136
+ --output table
137
+
138
+ log_success "Infrastructure deployed"
139
+ }
140
+
141
+ show_outputs() {
142
+ log_info "Retrieving deployment outputs..."
143
+
144
+ APP_URL=$(az deployment group show \
145
+ -g "$RESOURCE_GROUP" \
146
+ -n "$DEPLOYMENT_NAME" \
147
+ --query properties.outputs.appUrl.value -o tsv)
148
+
149
+ SQL_CONNECTION=$(az deployment group show \
150
+ -g "$RESOURCE_GROUP" \
151
+ -n "$DEPLOYMENT_NAME" \
152
+ --query properties.outputs.sqlConnectionString.value -o tsv)
153
+
154
+ APPINSIGHTS_CONNECTION=$(az deployment group show \
155
+ -g "$RESOURCE_GROUP" \
156
+ -n "$DEPLOYMENT_NAME" \
157
+ --query properties.outputs.appInsightsConnectionString.value -o tsv)
158
+
159
+ echo ""
160
+ echo "╔════════════════════════════════════════════════════════════════╗"
161
+ echo "║ DEPLOYMENT SUCCESSFUL ║"
162
+ echo "╚════════════════════════════════════════════════════════════════╝"
163
+ echo ""
164
+ echo "🌐 Application URL:"
165
+ echo " $APP_URL"
166
+ echo ""
167
+ echo "🗄️ SQL Connection String:"
168
+ echo " $SQL_CONNECTION"
169
+ echo ""
170
+ echo "📊 App Insights Connection String:"
171
+ echo " $APPINSIGHTS_CONNECTION"
172
+ echo ""
173
+ echo "💡 Next steps:"
174
+
175
+ if [ "$HOSTING_TYPE" = "appservice" ]; then
176
+ echo " 1. Deploy your code: az webapp up --name app-${APP_NAME}-${ENVIRONMENT}"
177
+ echo " 2. View logs: az webapp log tail --name app-${APP_NAME}-${ENVIRONMENT} -g $RESOURCE_GROUP"
178
+ else
179
+ echo " 1. Build and push container: az acr build --registry <ACR> --image ${APP_NAME}:latest ."
180
+ echo " 2. Update container app: az containerapp update -n ca-${APP_NAME}-${ENVIRONMENT} -g $RESOURCE_GROUP --image <IMAGE>"
181
+ echo " 3. View logs: az containerapp logs show -n ca-${APP_NAME}-${ENVIRONMENT} -g $RESOURCE_GROUP --follow"
182
+ fi
183
+
184
+ echo ""
185
+ }
186
+
187
+ # ==============================================================================
188
+ # MAIN
189
+ # ==============================================================================
190
+
191
+ main() {
192
+ echo ""
193
+ echo "╔════════════════════════════════════════════════════════════════╗"
194
+ echo "║ MORPH-SPEC - Azure Infrastructure Deploy ║"
195
+ echo "╚════════════════════════════════════════════════════════════════╝"
196
+ echo ""
197
+
198
+ check_prerequisites
199
+ create_resource_group
200
+ generate_sql_password
201
+ deploy_infrastructure
202
+ show_outputs
203
+
204
+ log_success "Deployment complete! 🚀"
205
+ }
206
+
207
+ # Run main function
208
+ main
@@ -25,9 +25,17 @@ param appName string
25
25
  @secure()
26
26
  param sqlAdminPassword string
27
27
 
28
- @description('Container image to deploy')
28
+ @description('Container image to deploy (only for Container Apps)')
29
29
  param containerImage string = 'mcr.microsoft.com/hello-world:latest'
30
30
 
31
+ @description('Hosting type: appservice or containerapp')
32
+ @allowed(['appservice', 'containerapp'])
33
+ param hostingType string = 'appservice'
34
+
35
+ @description('App Service SKU (only for App Service hosting)')
36
+ @allowed(['F1', 'B1', 'S1', 'P1v2'])
37
+ param appServiceSku string = 'F1'
38
+
31
39
  // ==============================================================================
32
40
  // VARIABLES
33
41
  // ==============================================================================
@@ -106,8 +114,28 @@ module sqlDatabase 'sql-database.bicep' = {
106
114
  }
107
115
  }
108
116
 
117
+ // ==============================================================================
118
+ // HOSTING - App Service (Conditional)
119
+ // ==============================================================================
120
+
121
+ module appService 'app-service.bicep' = if (hostingType == 'appservice') {
122
+ name: 'appService-${uniqueString(resourceGroup().id)}'
123
+ params: {
124
+ name: 'app-${resourcePrefix}'
125
+ location: location
126
+ tags: tags
127
+ sku: appServiceSku
128
+ appInsightsConnectionString: appInsights.outputs.connectionString
129
+ alwaysOn: appServiceSku != 'F1' // Only available on paid tiers
130
+ }
131
+ }
132
+
133
+ // ==============================================================================
134
+ // HOSTING - Container Apps (Conditional)
135
+ // ==============================================================================
136
+
109
137
  // Container Apps Environment
110
- module containerAppEnv 'container-app-env.bicep' = {
138
+ module containerAppEnv 'container-app-env.bicep' = if (hostingType == 'containerapp') {
111
139
  name: 'containerAppEnv-${uniqueString(resourceGroup().id)}'
112
140
  params: {
113
141
  name: '${resourcePrefix}-env'
@@ -118,13 +146,13 @@ module containerAppEnv 'container-app-env.bicep' = {
118
146
  }
119
147
 
120
148
  // Container App
121
- module containerApp 'container-app.bicep' = {
149
+ module containerApp 'container-app.bicep' = if (hostingType == 'containerapp') {
122
150
  name: 'containerApp-${uniqueString(resourceGroup().id)}'
123
151
  params: {
124
- name: resourcePrefix
152
+ name: 'ca-${resourcePrefix}'
125
153
  location: location
126
154
  tags: tags
127
- environmentId: containerAppEnv.outputs.id
155
+ environmentId: hostingType == 'containerapp' ? containerAppEnv.outputs.id : ''
128
156
  containerImage: containerImage
129
157
  appInsightsConnectionString: appInsights.outputs.connectionString
130
158
  minReplicas: environment == 'prod' ? 1 : 0
@@ -136,8 +164,11 @@ module containerApp 'container-app.bicep' = {
136
164
  // OUTPUTS
137
165
  // ==============================================================================
138
166
 
139
- @description('Container App URL')
140
- output containerAppUrl string = containerApp.outputs.url
167
+ @description('Application URL')
168
+ output appUrl string = hostingType == 'appservice' ? appService.outputs.url : containerApp.outputs.url
169
+
170
+ @description('Hosting Type')
171
+ output hostingType string = hostingType
141
172
 
142
173
  @description('SQL Server connection string')
143
174
  output sqlConnectionString string = sqlDatabase.outputs.connectionString
@@ -153,3 +184,6 @@ output appInsightsConnectionString string = appInsights.outputs.connectionString
153
184
 
154
185
  @description('Log Analytics Workspace ID')
155
186
  output logAnalyticsWorkspaceId string = logAnalytics.id
187
+
188
+ @description('App Service Principal ID (for Managed Identity)')
189
+ output appPrincipalId string = hostingType == 'appservice' ? appService.outputs.principalId : ''
@@ -16,6 +16,12 @@
16
16
  "secretName": "sql-admin-password"
17
17
  }
18
18
  },
19
+ "hostingType": {
20
+ "value": "appservice"
21
+ },
22
+ "appServiceSku": {
23
+ "value": "F1"
24
+ },
19
25
  "containerImage": {
20
26
  "value": "{{ACR_NAME}}.azurecr.io/{{APP_NAME}}:latest"
21
27
  }