@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.
- package/CLAUDE.md +1381 -0
- package/LICENSE +72 -0
- package/README.md +114 -12
- package/bin/detect-agents.js +225 -0
- package/bin/morph-spec.js +120 -0
- package/bin/render-template.js +302 -0
- package/bin/semantic-detect-agents.js +246 -0
- package/bin/validate-agents-skills.js +239 -0
- package/bin/validate-agents.js +69 -0
- package/bin/validate-phase.js +263 -0
- package/content/.azure/README.md +293 -0
- package/content/.azure/docs/azure-devops-setup.md +454 -0
- package/content/.azure/docs/branch-strategy.md +398 -0
- package/content/.azure/docs/local-development.md +515 -0
- package/content/.azure/pipelines/pipeline-variables.yml +34 -0
- package/content/.azure/pipelines/prod-pipeline.yml +319 -0
- package/content/.azure/pipelines/staging-pipeline.yml +234 -0
- package/content/.azure/pipelines/templates/build-dotnet.yml +75 -0
- package/content/.azure/pipelines/templates/deploy-app-service.yml +94 -0
- package/content/.azure/pipelines/templates/deploy-container-app.yml +120 -0
- package/content/.azure/pipelines/templates/infra-deploy.yml +90 -0
- package/content/.claude/commands/morph-apply.md +118 -26
- package/content/.claude/commands/morph-archive.md +9 -9
- package/content/.claude/commands/morph-clarify.md +184 -0
- package/content/.claude/commands/morph-design.md +275 -0
- package/content/.claude/commands/morph-proposal.md +56 -15
- package/content/.claude/commands/morph-setup.md +100 -0
- package/content/.claude/commands/morph-status.md +47 -32
- package/content/.claude/commands/morph-tasks.md +319 -0
- package/content/.claude/commands/morph-uiux.md +211 -0
- package/content/.claude/skills/specialists/ai-system-architect.md +604 -0
- package/content/.claude/skills/specialists/ms-agent-expert.md +143 -89
- package/content/.claude/skills/specialists/ui-ux-designer.md +744 -9
- package/content/.claude/skills/stacks/dotnet-blazor.md +244 -8
- package/content/.claude/skills/stacks/dotnet-nextjs.md +2 -2
- package/content/.morph/.morphversion +5 -0
- package/content/.morph/config/agents.json +101 -8
- package/content/.morph/config/azure-pricing.json +70 -0
- package/content/.morph/config/azure-pricing.schema.json +50 -0
- package/content/.morph/config/config.template.json +15 -3
- package/content/.morph/docs/STORY-DRIVEN-DEVELOPMENT.md +392 -0
- package/content/.morph/hooks/README.md +239 -0
- package/content/.morph/hooks/pre-commit-agents.sh +24 -0
- package/content/.morph/hooks/pre-commit-all.sh +48 -0
- package/content/.morph/hooks/pre-commit-costs.sh +91 -0
- package/content/.morph/hooks/pre-commit-specs.sh +49 -0
- package/content/.morph/hooks/pre-commit-tests.sh +60 -0
- package/content/.morph/project.md +5 -4
- package/content/.morph/schemas/agent.schema.json +296 -0
- package/content/.morph/standards/agent-framework-setup.md +453 -0
- package/content/.morph/standards/architecture.md +142 -7
- package/content/.morph/standards/azure.md +218 -23
- package/content/.morph/standards/coding.md +47 -12
- package/content/.morph/standards/dotnet10-migration.md +494 -0
- package/content/.morph/standards/fluent-ui-setup.md +590 -0
- package/content/.morph/standards/migration-guide.md +514 -0
- package/content/.morph/standards/passkeys-auth.md +423 -0
- package/content/.morph/standards/vector-search-rag.md +536 -0
- package/content/.morph/state.json +18 -0
- package/content/.morph/templates/FluentDesignTheme.cs +149 -0
- package/content/.morph/templates/MudTheme.cs +281 -0
- package/content/.morph/templates/contracts.cs +55 -55
- package/content/.morph/templates/decisions.md +4 -4
- package/content/.morph/templates/design-system.css +226 -0
- package/content/.morph/templates/infra/.dockerignore.example +89 -0
- package/content/.morph/templates/infra/Dockerfile.example +82 -0
- package/content/.morph/templates/infra/README.md +286 -0
- package/content/.morph/templates/infra/app-service.bicep +164 -0
- package/content/.morph/templates/infra/deploy.ps1 +229 -0
- package/content/.morph/templates/infra/deploy.sh +208 -0
- package/content/.morph/templates/infra/main.bicep +41 -7
- package/content/.morph/templates/infra/parameters.dev.json +6 -0
- package/content/.morph/templates/infra/parameters.prod.json +6 -0
- package/content/.morph/templates/infra/parameters.staging.json +29 -0
- package/content/.morph/templates/proposal.md +3 -3
- package/content/.morph/templates/recap.md +3 -3
- package/content/.morph/templates/spec.md +9 -8
- package/content/.morph/templates/sprint-status.yaml +68 -0
- package/content/.morph/templates/state.template.json +222 -0
- package/content/.morph/templates/story.md +143 -0
- package/content/.morph/templates/tasks.md +1 -1
- package/content/.morph/templates/ui-components.md +276 -0
- package/content/.morph/templates/ui-design-system.md +286 -0
- package/content/.morph/templates/ui-flows.md +336 -0
- package/content/.morph/templates/ui-mockups.md +133 -0
- package/content/.morph/test-infra/example.bicep +59 -0
- package/content/CLAUDE.md +124 -0
- package/content/README.md +79 -0
- package/detectors/config-detector.js +223 -0
- package/detectors/conversation-analyzer.js +163 -0
- package/detectors/index.js +84 -0
- package/detectors/standards-generator.js +275 -0
- package/detectors/structure-detector.js +221 -0
- package/docs/README.md +149 -0
- package/docs/api/cost-calculator.js.html +513 -0
- package/docs/api/design-system-generator.js.html +382 -0
- package/docs/api/fonts/Montserrat/Montserrat-Bold.eot +0 -0
- package/docs/api/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
- package/docs/api/fonts/Montserrat/Montserrat-Bold.woff +0 -0
- package/docs/api/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
- package/docs/api/fonts/Montserrat/Montserrat-Regular.eot +0 -0
- package/docs/api/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
- package/docs/api/fonts/Montserrat/Montserrat-Regular.woff +0 -0
- package/docs/api/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +978 -0
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1049 -0
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
- package/docs/api/global.html +5263 -0
- package/docs/api/index.html +96 -0
- package/docs/api/scripts/collapse.js +39 -0
- package/docs/api/scripts/commonNav.js +28 -0
- package/docs/api/scripts/linenumber.js +25 -0
- package/docs/api/scripts/nav.js +12 -0
- package/docs/api/scripts/polyfill.js +4 -0
- package/docs/api/scripts/prettify/Apache-License-2.0.txt +202 -0
- package/docs/api/scripts/prettify/lang-css.js +2 -0
- package/docs/api/scripts/prettify/prettify.js +28 -0
- package/docs/api/scripts/search.js +99 -0
- package/docs/api/state-manager.js.html +423 -0
- package/docs/api/styles/jsdoc.css +776 -0
- package/docs/api/styles/prettify.css +80 -0
- package/docs/examples.md +328 -0
- package/docs/getting-started.md +302 -0
- package/docs/installation.md +361 -0
- package/docs/templates.md +418 -0
- package/docs/validation-checklist.md +266 -0
- package/package.json +39 -12
- package/src/commands/cost.js +181 -0
- package/src/commands/create-story.js +283 -0
- package/src/commands/detect.js +104 -0
- package/src/commands/doctor.js +67 -0
- package/src/commands/generate.js +149 -0
- package/src/commands/init.js +71 -46
- package/src/commands/shard-spec.js +224 -0
- package/src/commands/sprint-status.js +250 -0
- package/src/commands/state.js +333 -0
- package/src/commands/sync.js +167 -0
- package/src/commands/update-pricing.js +206 -0
- package/src/commands/update.js +88 -13
- package/src/lib/complexity-analyzer.js +292 -0
- package/src/lib/cost-calculator.js +429 -0
- package/src/lib/design-system-generator.js +298 -0
- package/src/lib/state-manager.js +340 -0
- package/src/utils/file-copier.js +63 -0
- package/src/utils/version-checker.js +175 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
# ==============================================================================
|
|
2
|
+
# MORPH-SPEC - Production Pipeline
|
|
3
|
+
# Deployment to Azure Container Apps with manual approval and always-on
|
|
4
|
+
# ==============================================================================
|
|
5
|
+
|
|
6
|
+
trigger:
|
|
7
|
+
branches:
|
|
8
|
+
include:
|
|
9
|
+
- main
|
|
10
|
+
- master
|
|
11
|
+
|
|
12
|
+
pr: none # No PR builds for prod
|
|
13
|
+
|
|
14
|
+
variables:
|
|
15
|
+
- template: pipeline-variables.yml
|
|
16
|
+
- name: environment
|
|
17
|
+
value: 'prod'
|
|
18
|
+
- name: resourceGroupName
|
|
19
|
+
value: 'rg-$(APP_NAME)-prod'
|
|
20
|
+
- name: containerAppName
|
|
21
|
+
value: 'ca-$(APP_NAME)-prod'
|
|
22
|
+
- name: hostingType
|
|
23
|
+
value: 'containerapp'
|
|
24
|
+
- name: parametersFile
|
|
25
|
+
value: 'content/.morph/templates/infra/parameters.prod.json'
|
|
26
|
+
- name: imageName
|
|
27
|
+
value: '$(APP_NAME)'
|
|
28
|
+
- name: imageFullName
|
|
29
|
+
value: '$(containerRegistry)/$(imageName):$(imageTag)'
|
|
30
|
+
|
|
31
|
+
stages:
|
|
32
|
+
# ===========================================================================
|
|
33
|
+
# STAGE 1: Build & Test
|
|
34
|
+
# ===========================================================================
|
|
35
|
+
- stage: Build
|
|
36
|
+
displayName: 'Build & Test'
|
|
37
|
+
jobs:
|
|
38
|
+
- job: BuildJob
|
|
39
|
+
displayName: 'Build .NET Application'
|
|
40
|
+
pool:
|
|
41
|
+
vmImage: 'ubuntu-latest'
|
|
42
|
+
steps:
|
|
43
|
+
- checkout: self
|
|
44
|
+
fetchDepth: 1
|
|
45
|
+
|
|
46
|
+
- template: templates/build-dotnet.yml
|
|
47
|
+
parameters:
|
|
48
|
+
dotnetVersion: $(dotnetVersion)
|
|
49
|
+
buildConfiguration: $(buildConfiguration)
|
|
50
|
+
runTests: true
|
|
51
|
+
publishArtifact: false
|
|
52
|
+
|
|
53
|
+
# ===========================================================================
|
|
54
|
+
# STAGE 2: Security Scan
|
|
55
|
+
# ===========================================================================
|
|
56
|
+
- stage: SecurityScan
|
|
57
|
+
displayName: 'Security Scan'
|
|
58
|
+
dependsOn: Build
|
|
59
|
+
condition: succeeded()
|
|
60
|
+
jobs:
|
|
61
|
+
- job: SecurityScanJob
|
|
62
|
+
displayName: 'Run Security Scans'
|
|
63
|
+
pool:
|
|
64
|
+
vmImage: 'ubuntu-latest'
|
|
65
|
+
steps:
|
|
66
|
+
- checkout: self
|
|
67
|
+
fetchDepth: 1
|
|
68
|
+
|
|
69
|
+
- task: DotNetCoreCLI@2
|
|
70
|
+
displayName: 'Restore packages'
|
|
71
|
+
inputs:
|
|
72
|
+
command: 'restore'
|
|
73
|
+
|
|
74
|
+
- task: PowerShell@2
|
|
75
|
+
displayName: 'Check for vulnerable packages'
|
|
76
|
+
inputs:
|
|
77
|
+
targetType: 'inline'
|
|
78
|
+
script: |
|
|
79
|
+
Write-Host "๐ Scanning for vulnerable NuGet packages..."
|
|
80
|
+
dotnet list package --vulnerable --include-transitive
|
|
81
|
+
|
|
82
|
+
if ($LASTEXITCODE -ne 0) {
|
|
83
|
+
Write-Warning "โ ๏ธ Vulnerable packages found. Review before deploying to production."
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
# ===========================================================================
|
|
87
|
+
# STAGE 3: Deploy Infrastructure
|
|
88
|
+
# ===========================================================================
|
|
89
|
+
- stage: DeployInfra
|
|
90
|
+
displayName: 'Deploy Infrastructure'
|
|
91
|
+
dependsOn: SecurityScan
|
|
92
|
+
condition: succeeded()
|
|
93
|
+
jobs:
|
|
94
|
+
- deployment: DeployInfraJob
|
|
95
|
+
displayName: 'Deploy Bicep Templates'
|
|
96
|
+
pool:
|
|
97
|
+
vmImage: 'ubuntu-latest'
|
|
98
|
+
environment: 'production' # Requires manual approval in Azure DevOps
|
|
99
|
+
strategy:
|
|
100
|
+
runOnce:
|
|
101
|
+
deploy:
|
|
102
|
+
steps:
|
|
103
|
+
- checkout: self
|
|
104
|
+
fetchDepth: 1
|
|
105
|
+
|
|
106
|
+
- template: templates/infra-deploy.yml
|
|
107
|
+
parameters:
|
|
108
|
+
azureSubscription: 'Azure-Prod-Connection'
|
|
109
|
+
resourceGroupName: $(resourceGroupName)
|
|
110
|
+
location: $(azureLocation)
|
|
111
|
+
environment: $(environment)
|
|
112
|
+
appName: $(APP_NAME)
|
|
113
|
+
hostingType: $(hostingType)
|
|
114
|
+
bicepTemplateFile: $(bicepTemplateFile)
|
|
115
|
+
parametersFile: $(parametersFile)
|
|
116
|
+
|
|
117
|
+
# ===========================================================================
|
|
118
|
+
# STAGE 4: Build and Push Container
|
|
119
|
+
# ===========================================================================
|
|
120
|
+
- stage: BuildContainer
|
|
121
|
+
displayName: 'Build Container'
|
|
122
|
+
dependsOn: DeployInfra
|
|
123
|
+
condition: succeeded()
|
|
124
|
+
jobs:
|
|
125
|
+
- job: BuildContainerJob
|
|
126
|
+
displayName: 'Build and Push Docker Image'
|
|
127
|
+
pool:
|
|
128
|
+
vmImage: 'ubuntu-latest'
|
|
129
|
+
steps:
|
|
130
|
+
- checkout: self
|
|
131
|
+
fetchDepth: 1
|
|
132
|
+
|
|
133
|
+
- task: Docker@2
|
|
134
|
+
displayName: 'Build and push container'
|
|
135
|
+
inputs:
|
|
136
|
+
containerRegistry: 'ACR-Connection'
|
|
137
|
+
repository: '$(imageName)'
|
|
138
|
+
command: 'buildAndPush'
|
|
139
|
+
Dockerfile: '$(dockerfilePath)'
|
|
140
|
+
tags: |
|
|
141
|
+
$(imageTag)
|
|
142
|
+
prod-latest
|
|
143
|
+
$(Build.BuildId)
|
|
144
|
+
|
|
145
|
+
- task: AzureCLI@2
|
|
146
|
+
displayName: 'Scan image for vulnerabilities (Microsoft Defender)'
|
|
147
|
+
continueOnError: false # Fail on vulnerabilities in prod
|
|
148
|
+
inputs:
|
|
149
|
+
azureSubscription: 'Azure-Prod-Connection'
|
|
150
|
+
scriptType: 'bash'
|
|
151
|
+
scriptLocation: 'inlineScript'
|
|
152
|
+
inlineScript: |
|
|
153
|
+
echo "๐ Scanning image for vulnerabilities..."
|
|
154
|
+
# Add your security scanning tool here (e.g., Trivy, Defender for Containers)
|
|
155
|
+
# az acr scan --name $(ACR_NAME) --image $(imageName):$(imageTag)
|
|
156
|
+
|
|
157
|
+
# ===========================================================================
|
|
158
|
+
# STAGE 5: Deploy to Production (Blue-Green)
|
|
159
|
+
# ===========================================================================
|
|
160
|
+
- stage: DeployApp
|
|
161
|
+
displayName: 'Deploy to Production'
|
|
162
|
+
dependsOn: BuildContainer
|
|
163
|
+
condition: succeeded()
|
|
164
|
+
jobs:
|
|
165
|
+
- deployment: DeployAppJob
|
|
166
|
+
displayName: 'Deploy Container App'
|
|
167
|
+
pool:
|
|
168
|
+
vmImage: 'ubuntu-latest'
|
|
169
|
+
environment: 'production' # Requires manual approval
|
|
170
|
+
strategy:
|
|
171
|
+
runOnce:
|
|
172
|
+
deploy:
|
|
173
|
+
steps:
|
|
174
|
+
- checkout: self
|
|
175
|
+
fetchDepth: 1
|
|
176
|
+
|
|
177
|
+
# Create new revision
|
|
178
|
+
- template: templates/deploy-container-app.yml
|
|
179
|
+
parameters:
|
|
180
|
+
azureSubscription: 'Azure-Prod-Connection'
|
|
181
|
+
containerAppName: $(containerAppName)
|
|
182
|
+
resourceGroupName: $(resourceGroupName)
|
|
183
|
+
containerRegistry: $(containerRegistry)
|
|
184
|
+
imageName: $(imageName)
|
|
185
|
+
imageTag: $(imageTag)
|
|
186
|
+
acrServiceConnection: 'ACR-Connection'
|
|
187
|
+
healthCheckUrl: '/health'
|
|
188
|
+
healthCheckTimeout: 300
|
|
189
|
+
|
|
190
|
+
# Wait before activating
|
|
191
|
+
- task: PowerShell@2
|
|
192
|
+
displayName: 'Monitor new revision (5 min)'
|
|
193
|
+
inputs:
|
|
194
|
+
targetType: 'inline'
|
|
195
|
+
script: |
|
|
196
|
+
Write-Host "โณ Monitoring new revision for 5 minutes before activating..."
|
|
197
|
+
Start-Sleep -Seconds 300
|
|
198
|
+
Write-Host "โ
Monitoring period complete"
|
|
199
|
+
|
|
200
|
+
# ===========================================================================
|
|
201
|
+
# STAGE 6: Smoke Tests in Production
|
|
202
|
+
# ===========================================================================
|
|
203
|
+
- stage: SmokeTests
|
|
204
|
+
displayName: 'Production Smoke Tests'
|
|
205
|
+
dependsOn: DeployApp
|
|
206
|
+
condition: succeeded()
|
|
207
|
+
jobs:
|
|
208
|
+
- job: SmokeTestsJob
|
|
209
|
+
displayName: 'Run Production Smoke Tests'
|
|
210
|
+
pool:
|
|
211
|
+
vmImage: 'ubuntu-latest'
|
|
212
|
+
steps:
|
|
213
|
+
- task: AzureCLI@2
|
|
214
|
+
displayName: 'Get Container App URL'
|
|
215
|
+
name: getUrl
|
|
216
|
+
inputs:
|
|
217
|
+
azureSubscription: 'Azure-Prod-Connection'
|
|
218
|
+
scriptType: 'bash'
|
|
219
|
+
scriptLocation: 'inlineScript'
|
|
220
|
+
inlineScript: |
|
|
221
|
+
FQDN=$(az containerapp show \
|
|
222
|
+
--name $(containerAppName) \
|
|
223
|
+
--resource-group $(resourceGroupName) \
|
|
224
|
+
--query properties.configuration.ingress.fqdn -o tsv)
|
|
225
|
+
|
|
226
|
+
APP_URL="https://$FQDN"
|
|
227
|
+
echo "##vso[task.setvariable variable=appUrl]$APP_URL"
|
|
228
|
+
echo "Production URL: $APP_URL"
|
|
229
|
+
|
|
230
|
+
- task: PowerShell@2
|
|
231
|
+
displayName: 'Critical smoke tests'
|
|
232
|
+
inputs:
|
|
233
|
+
targetType: 'inline'
|
|
234
|
+
script: |
|
|
235
|
+
$appUrl = "$(appUrl)"
|
|
236
|
+
|
|
237
|
+
Write-Host "๐งช Running CRITICAL smoke tests in PRODUCTION"
|
|
238
|
+
Write-Host "URL: $appUrl"
|
|
239
|
+
|
|
240
|
+
$criticalEndpoints = @(
|
|
241
|
+
@{Path="/health"; Description="Health Check"},
|
|
242
|
+
@{Path="/health/ready"; Description="Readiness Check"},
|
|
243
|
+
@{Path="/"; Description="Home Page"}
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
$failed = $false
|
|
247
|
+
foreach ($endpoint in $criticalEndpoints) {
|
|
248
|
+
$url = "$appUrl$($endpoint.Path)"
|
|
249
|
+
try {
|
|
250
|
+
$response = Invoke-WebRequest -Uri $url -UseBasicParsing -TimeoutSec 10
|
|
251
|
+
Write-Host "โ
$($endpoint.Description): $($response.StatusCode)"
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
Write-Error "โ $($endpoint.Description) FAILED: $_"
|
|
255
|
+
$failed = $true
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if ($failed) {
|
|
260
|
+
Write-Error "โ CRITICAL: Smoke tests failed in production!"
|
|
261
|
+
exit 1
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
Write-Host "โ
All critical smoke tests passed!"
|
|
265
|
+
|
|
266
|
+
- task: AzureCLI@2
|
|
267
|
+
displayName: 'Monitor metrics'
|
|
268
|
+
inputs:
|
|
269
|
+
azureSubscription: 'Azure-Prod-Connection'
|
|
270
|
+
scriptType: 'bash'
|
|
271
|
+
scriptLocation: 'inlineScript'
|
|
272
|
+
inlineScript: |
|
|
273
|
+
echo "๐ Production Deployment Metrics:"
|
|
274
|
+
|
|
275
|
+
# Get replica count
|
|
276
|
+
REPLICAS=$(az containerapp revision list \
|
|
277
|
+
--name $(containerAppName) \
|
|
278
|
+
--resource-group $(resourceGroupName) \
|
|
279
|
+
--query "[?properties.active].properties.replicas" -o tsv)
|
|
280
|
+
|
|
281
|
+
echo "Active Replicas: $REPLICAS"
|
|
282
|
+
|
|
283
|
+
# Show active revisions
|
|
284
|
+
az containerapp revision list \
|
|
285
|
+
--name $(containerAppName) \
|
|
286
|
+
--resource-group $(resourceGroupName) \
|
|
287
|
+
--query "[?properties.active].{Name:name, Traffic:properties.trafficWeight, Replicas:properties.replicas}" \
|
|
288
|
+
--output table
|
|
289
|
+
|
|
290
|
+
- task: AzureCLI@2
|
|
291
|
+
displayName: 'Deployment summary'
|
|
292
|
+
inputs:
|
|
293
|
+
azureSubscription: 'Azure-Prod-Connection'
|
|
294
|
+
scriptType: 'bash'
|
|
295
|
+
scriptLocation: 'inlineScript'
|
|
296
|
+
inlineScript: |
|
|
297
|
+
echo "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
|
|
298
|
+
echo "โ PRODUCTION DEPLOYMENT SUCCESSFUL โ"
|
|
299
|
+
echo "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
|
|
300
|
+
echo ""
|
|
301
|
+
echo "๐ Application URL: $(appUrl)"
|
|
302
|
+
echo "๐ Environment: $(environment)"
|
|
303
|
+
echo "๐ณ Container Image: $(imageFullName)"
|
|
304
|
+
echo "๐ฐ Hosting: Container Apps (always-on) - ~$10-20/month"
|
|
305
|
+
echo "๐ฆ Resource Group: $(resourceGroupName)"
|
|
306
|
+
echo "๐ท๏ธ Version: $(imageTag)"
|
|
307
|
+
echo ""
|
|
308
|
+
echo "โ ๏ธ IMPORTANT:"
|
|
309
|
+
echo " 1. Monitor Application Insights for errors"
|
|
310
|
+
echo " 2. Watch for performance degradation"
|
|
311
|
+
echo " 3. Have rollback plan ready"
|
|
312
|
+
echo " 4. Monitor costs in Azure Portal"
|
|
313
|
+
echo ""
|
|
314
|
+
echo "๐ Rollback command (if needed):"
|
|
315
|
+
echo " az containerapp revision activate \\"
|
|
316
|
+
echo " --name $(containerAppName) \\"
|
|
317
|
+
echo " --resource-group $(resourceGroupName) \\"
|
|
318
|
+
echo " --revision <PREVIOUS_REVISION_NAME>"
|
|
319
|
+
echo ""
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# ==============================================================================
|
|
2
|
+
# MORPH-SPEC - Staging Pipeline
|
|
3
|
+
# Deployment to Azure Container Apps with scale-to-zero
|
|
4
|
+
# ==============================================================================
|
|
5
|
+
|
|
6
|
+
trigger:
|
|
7
|
+
branches:
|
|
8
|
+
include:
|
|
9
|
+
- staging
|
|
10
|
+
|
|
11
|
+
pr:
|
|
12
|
+
branches:
|
|
13
|
+
include:
|
|
14
|
+
- staging
|
|
15
|
+
|
|
16
|
+
variables:
|
|
17
|
+
- template: pipeline-variables.yml
|
|
18
|
+
- name: environment
|
|
19
|
+
value: 'staging'
|
|
20
|
+
- name: resourceGroupName
|
|
21
|
+
value: 'rg-$(APP_NAME)-staging'
|
|
22
|
+
- name: containerAppName
|
|
23
|
+
value: 'ca-$(APP_NAME)-staging'
|
|
24
|
+
- name: hostingType
|
|
25
|
+
value: 'containerapp'
|
|
26
|
+
- name: parametersFile
|
|
27
|
+
value: 'content/.morph/templates/infra/parameters.staging.json'
|
|
28
|
+
- name: imageName
|
|
29
|
+
value: '$(APP_NAME)'
|
|
30
|
+
- name: imageFullName
|
|
31
|
+
value: '$(containerRegistry)/$(imageName):$(imageTag)'
|
|
32
|
+
|
|
33
|
+
stages:
|
|
34
|
+
# ===========================================================================
|
|
35
|
+
# STAGE 1: Build
|
|
36
|
+
# ===========================================================================
|
|
37
|
+
- stage: Build
|
|
38
|
+
displayName: 'Build & Test'
|
|
39
|
+
jobs:
|
|
40
|
+
- job: BuildJob
|
|
41
|
+
displayName: 'Build .NET Application'
|
|
42
|
+
pool:
|
|
43
|
+
vmImage: 'ubuntu-latest'
|
|
44
|
+
steps:
|
|
45
|
+
- checkout: self
|
|
46
|
+
fetchDepth: 1
|
|
47
|
+
|
|
48
|
+
- template: templates/build-dotnet.yml
|
|
49
|
+
parameters:
|
|
50
|
+
dotnetVersion: $(dotnetVersion)
|
|
51
|
+
buildConfiguration: $(buildConfiguration)
|
|
52
|
+
runTests: true
|
|
53
|
+
publishArtifact: false # Don't need artifact for containers
|
|
54
|
+
|
|
55
|
+
# ===========================================================================
|
|
56
|
+
# STAGE 2: Deploy Infrastructure
|
|
57
|
+
# ===========================================================================
|
|
58
|
+
- stage: DeployInfra
|
|
59
|
+
displayName: 'Deploy Infrastructure'
|
|
60
|
+
dependsOn: Build
|
|
61
|
+
condition: succeeded()
|
|
62
|
+
jobs:
|
|
63
|
+
- deployment: DeployInfraJob
|
|
64
|
+
displayName: 'Deploy Bicep Templates'
|
|
65
|
+
pool:
|
|
66
|
+
vmImage: 'ubuntu-latest'
|
|
67
|
+
environment: 'staging'
|
|
68
|
+
strategy:
|
|
69
|
+
runOnce:
|
|
70
|
+
deploy:
|
|
71
|
+
steps:
|
|
72
|
+
- checkout: self
|
|
73
|
+
fetchDepth: 1
|
|
74
|
+
|
|
75
|
+
- template: templates/infra-deploy.yml
|
|
76
|
+
parameters:
|
|
77
|
+
azureSubscription: 'Azure-Staging-Connection'
|
|
78
|
+
resourceGroupName: $(resourceGroupName)
|
|
79
|
+
location: $(azureLocation)
|
|
80
|
+
environment: $(environment)
|
|
81
|
+
appName: $(APP_NAME)
|
|
82
|
+
hostingType: $(hostingType)
|
|
83
|
+
bicepTemplateFile: $(bicepTemplateFile)
|
|
84
|
+
parametersFile: $(parametersFile)
|
|
85
|
+
|
|
86
|
+
# ===========================================================================
|
|
87
|
+
# STAGE 3: Build and Push Container
|
|
88
|
+
# ===========================================================================
|
|
89
|
+
- stage: BuildContainer
|
|
90
|
+
displayName: 'Build Container'
|
|
91
|
+
dependsOn: DeployInfra
|
|
92
|
+
condition: succeeded()
|
|
93
|
+
jobs:
|
|
94
|
+
- job: BuildContainerJob
|
|
95
|
+
displayName: 'Build and Push Docker Image'
|
|
96
|
+
pool:
|
|
97
|
+
vmImage: 'ubuntu-latest'
|
|
98
|
+
steps:
|
|
99
|
+
- checkout: self
|
|
100
|
+
fetchDepth: 1
|
|
101
|
+
|
|
102
|
+
- task: Docker@2
|
|
103
|
+
displayName: 'Build and push container'
|
|
104
|
+
inputs:
|
|
105
|
+
containerRegistry: 'ACR-Connection'
|
|
106
|
+
repository: '$(imageName)'
|
|
107
|
+
command: 'buildAndPush'
|
|
108
|
+
Dockerfile: '$(dockerfilePath)'
|
|
109
|
+
tags: |
|
|
110
|
+
$(imageTag)
|
|
111
|
+
staging-latest
|
|
112
|
+
$(Build.BuildId)
|
|
113
|
+
|
|
114
|
+
- task: AzureCLI@2
|
|
115
|
+
displayName: 'Scan image for vulnerabilities'
|
|
116
|
+
continueOnError: true
|
|
117
|
+
inputs:
|
|
118
|
+
azureSubscription: 'Azure-Staging-Connection'
|
|
119
|
+
scriptType: 'bash'
|
|
120
|
+
scriptLocation: 'inlineScript'
|
|
121
|
+
inlineScript: |
|
|
122
|
+
echo "๐ Scanning image for vulnerabilities..."
|
|
123
|
+
az acr check-health --name $(ACR_NAME) --yes || true
|
|
124
|
+
|
|
125
|
+
# ===========================================================================
|
|
126
|
+
# STAGE 4: Deploy Container App
|
|
127
|
+
# ===========================================================================
|
|
128
|
+
- stage: DeployApp
|
|
129
|
+
displayName: 'Deploy Container App'
|
|
130
|
+
dependsOn: BuildContainer
|
|
131
|
+
condition: succeeded()
|
|
132
|
+
jobs:
|
|
133
|
+
- deployment: DeployAppJob
|
|
134
|
+
displayName: 'Deploy to Container Apps'
|
|
135
|
+
pool:
|
|
136
|
+
vmImage: 'ubuntu-latest'
|
|
137
|
+
environment: 'staging'
|
|
138
|
+
strategy:
|
|
139
|
+
runOnce:
|
|
140
|
+
deploy:
|
|
141
|
+
steps:
|
|
142
|
+
- checkout: self
|
|
143
|
+
fetchDepth: 1
|
|
144
|
+
|
|
145
|
+
- template: templates/deploy-container-app.yml
|
|
146
|
+
parameters:
|
|
147
|
+
azureSubscription: 'Azure-Staging-Connection'
|
|
148
|
+
containerAppName: $(containerAppName)
|
|
149
|
+
resourceGroupName: $(resourceGroupName)
|
|
150
|
+
containerRegistry: $(containerRegistry)
|
|
151
|
+
imageName: $(imageName)
|
|
152
|
+
imageTag: $(imageTag)
|
|
153
|
+
acrServiceConnection: 'ACR-Connection'
|
|
154
|
+
healthCheckUrl: '/health'
|
|
155
|
+
healthCheckTimeout: 300
|
|
156
|
+
|
|
157
|
+
# ===========================================================================
|
|
158
|
+
# STAGE 5: Integration Tests
|
|
159
|
+
# ===========================================================================
|
|
160
|
+
- stage: IntegrationTests
|
|
161
|
+
displayName: 'Integration Tests'
|
|
162
|
+
dependsOn: DeployApp
|
|
163
|
+
condition: succeeded()
|
|
164
|
+
jobs:
|
|
165
|
+
- job: IntegrationTestsJob
|
|
166
|
+
displayName: 'Run Integration Tests'
|
|
167
|
+
pool:
|
|
168
|
+
vmImage: 'ubuntu-latest'
|
|
169
|
+
steps:
|
|
170
|
+
- task: AzureCLI@2
|
|
171
|
+
displayName: 'Get Container App URL'
|
|
172
|
+
name: getUrl
|
|
173
|
+
inputs:
|
|
174
|
+
azureSubscription: 'Azure-Staging-Connection'
|
|
175
|
+
scriptType: 'bash'
|
|
176
|
+
scriptLocation: 'inlineScript'
|
|
177
|
+
inlineScript: |
|
|
178
|
+
FQDN=$(az containerapp show \
|
|
179
|
+
--name $(containerAppName) \
|
|
180
|
+
--resource-group $(resourceGroupName) \
|
|
181
|
+
--query properties.configuration.ingress.fqdn -o tsv)
|
|
182
|
+
|
|
183
|
+
APP_URL="https://$FQDN"
|
|
184
|
+
echo "##vso[task.setvariable variable=appUrl]$APP_URL"
|
|
185
|
+
echo "Container App URL: $APP_URL"
|
|
186
|
+
|
|
187
|
+
- task: PowerShell@2
|
|
188
|
+
displayName: 'Run integration tests'
|
|
189
|
+
inputs:
|
|
190
|
+
targetType: 'inline'
|
|
191
|
+
script: |
|
|
192
|
+
$appUrl = "$(appUrl)"
|
|
193
|
+
|
|
194
|
+
Write-Host "๐งช Running integration tests against: $appUrl"
|
|
195
|
+
|
|
196
|
+
# Test endpoints
|
|
197
|
+
$endpoints = @("/", "/health", "/health/ready")
|
|
198
|
+
|
|
199
|
+
foreach ($endpoint in $endpoints) {
|
|
200
|
+
$url = "$appUrl$endpoint"
|
|
201
|
+
try {
|
|
202
|
+
$response = Invoke-WebRequest -Uri $url -UseBasicParsing -TimeoutSec 10
|
|
203
|
+
Write-Host "โ
$endpoint : $($response.StatusCode)"
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
Write-Error "โ $endpoint failed: $_"
|
|
207
|
+
exit 1
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
Write-Host "โ
All integration tests passed!"
|
|
212
|
+
|
|
213
|
+
- task: AzureCLI@2
|
|
214
|
+
displayName: 'Show deployment summary'
|
|
215
|
+
inputs:
|
|
216
|
+
azureSubscription: 'Azure-Staging-Connection'
|
|
217
|
+
scriptType: 'bash'
|
|
218
|
+
scriptLocation: 'inlineScript'
|
|
219
|
+
inlineScript: |
|
|
220
|
+
echo "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
|
|
221
|
+
echo "โ STAGING DEPLOYMENT SUCCESSFUL โ"
|
|
222
|
+
echo "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
|
|
223
|
+
echo ""
|
|
224
|
+
echo "๐ Application URL: $(appUrl)"
|
|
225
|
+
echo "๐ Environment: $(environment)"
|
|
226
|
+
echo "๐ณ Container Image: $(imageFullName)"
|
|
227
|
+
echo "๐ฐ Hosting: Container Apps (scale-to-zero) - ~$5-10/month"
|
|
228
|
+
echo "๐ฆ Resource Group: $(resourceGroupName)"
|
|
229
|
+
echo ""
|
|
230
|
+
echo "๐ก Next steps:"
|
|
231
|
+
echo " 1. Run manual QA tests"
|
|
232
|
+
echo " 2. Monitor auto-scaling behavior"
|
|
233
|
+
echo " 3. If stable for 24h, promote to production"
|
|
234
|
+
echo ""
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# ==============================================================================
|
|
2
|
+
# MORPH-SPEC - Build .NET Template
|
|
3
|
+
# Reusable template for building and testing .NET applications
|
|
4
|
+
# ==============================================================================
|
|
5
|
+
|
|
6
|
+
parameters:
|
|
7
|
+
- name: dotnetVersion
|
|
8
|
+
type: string
|
|
9
|
+
default: '8.x'
|
|
10
|
+
- name: buildConfiguration
|
|
11
|
+
type: string
|
|
12
|
+
default: 'Release'
|
|
13
|
+
- name: projectPath
|
|
14
|
+
type: string
|
|
15
|
+
default: '**/*.csproj'
|
|
16
|
+
- name: runTests
|
|
17
|
+
type: boolean
|
|
18
|
+
default: true
|
|
19
|
+
- name: publishArtifact
|
|
20
|
+
type: boolean
|
|
21
|
+
default: true
|
|
22
|
+
|
|
23
|
+
steps:
|
|
24
|
+
- task: UseDotNetVersion@2
|
|
25
|
+
displayName: 'Install .NET SDK ${{ parameters.dotnetVersion }}'
|
|
26
|
+
inputs:
|
|
27
|
+
version: ${{ parameters.dotnetVersion }}
|
|
28
|
+
packageType: sdk
|
|
29
|
+
|
|
30
|
+
- task: DotNetCoreCLI@2
|
|
31
|
+
displayName: 'Restore NuGet packages'
|
|
32
|
+
inputs:
|
|
33
|
+
command: 'restore'
|
|
34
|
+
projects: '${{ parameters.projectPath }}'
|
|
35
|
+
feedsToUse: 'select'
|
|
36
|
+
|
|
37
|
+
- task: DotNetCoreCLI@2
|
|
38
|
+
displayName: 'Build solution'
|
|
39
|
+
inputs:
|
|
40
|
+
command: 'build'
|
|
41
|
+
projects: '${{ parameters.projectPath }}'
|
|
42
|
+
arguments: '--configuration ${{ parameters.buildConfiguration }} --no-restore'
|
|
43
|
+
|
|
44
|
+
- ${{ if eq(parameters.runTests, true) }}:
|
|
45
|
+
- task: DotNetCoreCLI@2
|
|
46
|
+
displayName: 'Run unit tests'
|
|
47
|
+
inputs:
|
|
48
|
+
command: 'test'
|
|
49
|
+
projects: '**/*Tests.csproj'
|
|
50
|
+
arguments: '--configuration ${{ parameters.buildConfiguration }} --no-build --collect:"XPlat Code Coverage" --logger trx'
|
|
51
|
+
publishTestResults: true
|
|
52
|
+
|
|
53
|
+
- task: PublishCodeCoverageResults@1
|
|
54
|
+
displayName: 'Publish code coverage'
|
|
55
|
+
condition: succeededOrFailed()
|
|
56
|
+
inputs:
|
|
57
|
+
codeCoverageTool: 'Cobertura'
|
|
58
|
+
summaryFileLocation: '$(Agent.TempDirectory)/**/*coverage.cobertura.xml'
|
|
59
|
+
|
|
60
|
+
- ${{ if eq(parameters.publishArtifact, true) }}:
|
|
61
|
+
- task: DotNetCoreCLI@2
|
|
62
|
+
displayName: 'Publish application'
|
|
63
|
+
inputs:
|
|
64
|
+
command: 'publish'
|
|
65
|
+
projects: '${{ parameters.projectPath }}'
|
|
66
|
+
arguments: '--configuration ${{ parameters.buildConfiguration }} --output $(Build.ArtifactStagingDirectory) --no-build'
|
|
67
|
+
publishWebProjects: true
|
|
68
|
+
zipAfterPublish: true
|
|
69
|
+
|
|
70
|
+
- task: PublishBuildArtifacts@1
|
|
71
|
+
displayName: 'Publish build artifacts'
|
|
72
|
+
inputs:
|
|
73
|
+
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
|
|
74
|
+
ArtifactName: 'drop'
|
|
75
|
+
publishLocation: 'Container'
|