@polymorphism-tech/morph-spec 2.4.0 → 3.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 +158 -26
- package/LICENSE +72 -72
- package/bin/detect-agents.js +225 -225
- package/bin/morph-spec.js +8 -0
- package/bin/render-template.js +302 -302
- package/bin/semantic-detect-agents.js +246 -246
- package/bin/validate-agents-skills.js +251 -251
- package/bin/validate-agents.js +69 -69
- package/bin/validate-phase.js +263 -263
- package/content/.azure/README.md +293 -293
- package/content/.azure/docs/azure-devops-setup.md +454 -454
- package/content/.azure/docs/branch-strategy.md +398 -398
- package/content/.azure/docs/local-development.md +515 -515
- package/content/.azure/pipelines/pipeline-variables.yml +34 -34
- package/content/.azure/pipelines/prod-pipeline.yml +319 -319
- package/content/.azure/pipelines/staging-pipeline.yml +234 -234
- package/content/.azure/pipelines/templates/build-dotnet.yml +75 -75
- package/content/.azure/pipelines/templates/deploy-app-service.yml +94 -94
- package/content/.azure/pipelines/templates/deploy-container-app.yml +120 -120
- package/content/.azure/pipelines/templates/infra-deploy.yml +90 -90
- package/content/.claude/commands/morph-archive.md +79 -79
- package/content/.claude/commands/morph-deploy.md +529 -0
- package/content/.claude/commands/morph-infra.md +209 -209
- package/content/.claude/commands/morph-preflight.md +227 -227
- package/content/.claude/commands/morph-troubleshoot.md +122 -122
- package/content/.claude/settings.local.json +15 -15
- package/content/.claude/skills/infra/azure-deploy-specialist.md +699 -0
- package/content/.claude/skills/level-0-meta/README.md +7 -0
- package/content/.claude/skills/{checklists → level-0-meta}/morph-checklist.md +117 -117
- package/content/.claude/skills/level-1-workflows/README.md +7 -0
- package/content/.claude/skills/{workflows → level-1-workflows}/morph-replicate.md +213 -213
- package/content/.claude/skills/{workflows → level-1-workflows}/phase-clarify.md +131 -131
- package/content/.claude/skills/{workflows → level-1-workflows}/phase-design.md +213 -205
- package/content/.claude/skills/{workflows → level-1-workflows}/phase-setup.md +106 -92
- package/content/.claude/skills/{workflows → level-1-workflows}/phase-tasks.md +164 -164
- package/content/.claude/skills/{workflows → level-1-workflows}/phase-uiux.md +169 -138
- package/content/.claude/skills/level-2-domains/README.md +14 -0
- package/content/.claude/skills/{specialists → level-2-domains/quality}/testing-specialist.md +126 -126
- package/content/.claude/skills/level-3-technologies/README.md +7 -0
- package/content/.claude/skills/level-4-patterns/README.md +7 -0
- package/content/.claude/skills/specialists/prompt-engineer.md +189 -0
- package/content/.claude/skills/specialists/seo-growth-hacker.md +320 -0
- package/content/.morph/.morphversion +5 -5
- package/content/.morph/archive/.gitkeep +25 -25
- package/content/.morph/config/agents.json +742 -358
- package/content/.morph/config/config.template.json +33 -0
- package/content/.morph/docs/STORY-DRIVEN-DEVELOPMENT.md +392 -392
- package/content/.morph/docs/workflows/enforcement-pipeline.md +668 -0
- package/content/.morph/examples/api-nextjs/README.md +241 -241
- package/content/.morph/examples/api-nextjs/contracts.ts +307 -307
- package/content/.morph/examples/api-nextjs/spec.md +399 -399
- package/content/.morph/examples/api-nextjs/tasks.md +168 -168
- package/content/.morph/examples/micro-saas/README.md +125 -125
- package/content/.morph/examples/micro-saas/contracts.cs +358 -358
- package/content/.morph/examples/micro-saas/decisions.md +246 -246
- package/content/.morph/examples/micro-saas/spec.md +236 -236
- package/content/.morph/examples/micro-saas/tasks.md +150 -150
- package/content/.morph/examples/multi-agent/README.md +309 -309
- package/content/.morph/examples/multi-agent/contracts.cs +433 -433
- package/content/.morph/examples/multi-agent/spec.md +479 -479
- package/content/.morph/examples/multi-agent/tasks.md +185 -185
- package/content/.morph/examples/scheduled-reports/decisions.md +158 -158
- package/content/.morph/examples/scheduled-reports/proposal.md +95 -95
- package/content/.morph/examples/scheduled-reports/spec.md +267 -267
- package/content/.morph/examples/state-v3.json +188 -188
- package/content/.morph/features/.gitkeep +25 -25
- package/content/.morph/hooks/README.md +158 -0
- package/content/.morph/hooks/pre-commit-all.sh +48 -48
- package/content/.morph/hooks/pre-commit-specs.sh +49 -49
- package/content/.morph/hooks/pre-commit-tests.sh +60 -60
- package/content/.morph/hooks/task-completed.js +73 -0
- package/content/.morph/hooks/teammate-idle.js +68 -0
- package/content/.morph/project.md +160 -160
- package/content/.morph/schemas/agent.schema.json +296 -296
- package/content/.morph/schemas/tasks.schema.json +220 -220
- package/content/.morph/specs/.gitkeep +20 -20
- package/content/.morph/standards/agent-teams-workflow.md +474 -0
- package/content/.morph/standards/coding.md +377 -377
- package/content/.morph/standards/fluent-ui-setup.md +590 -590
- package/content/.morph/standards/migration-guide.md +514 -514
- package/content/.morph/standards/passkeys-auth.md +423 -423
- package/content/.morph/standards/vector-search-rag.md +536 -536
- package/content/.morph/state.json +17 -17
- package/content/.morph/templates/CONTEXT-FEATURE.md +276 -0
- package/content/.morph/templates/CONTEXT.md +170 -0
- package/content/.morph/templates/FluentDesignTheme.cs +149 -149
- package/content/.morph/templates/MudTheme.cs +281 -281
- package/content/.morph/templates/clarify-questions.md +159 -159
- package/content/.morph/templates/component.razor +239 -239
- package/content/.morph/templates/contracts/Commands.cs +74 -74
- package/content/.morph/templates/contracts/Entities.cs +25 -25
- package/content/.morph/templates/contracts/Queries.cs +74 -74
- package/content/.morph/templates/contracts/README.md +74 -74
- package/content/.morph/templates/contracts.cs +217 -217
- package/content/.morph/templates/design-system.css +226 -226
- package/content/.morph/templates/infra/.dockerignore.example +89 -89
- package/content/.morph/templates/infra/Dockerfile.example +82 -82
- package/content/.morph/templates/infra/README.md +286 -286
- package/content/.morph/templates/infra/app-insights.bicep +63 -63
- package/content/.morph/templates/infra/app-service.bicep +164 -164
- package/content/.morph/templates/infra/azure-pipelines-deploy.yml +480 -0
- package/content/.morph/templates/infra/container-app-env.bicep +49 -49
- package/content/.morph/templates/infra/container-app.bicep +156 -156
- package/content/.morph/templates/infra/deploy-checklist.md +426 -426
- package/content/.morph/templates/infra/deploy.ps1 +229 -229
- package/content/.morph/templates/infra/deploy.sh +208 -208
- package/content/.morph/templates/infra/key-vault.bicep +91 -91
- package/content/.morph/templates/infra/main.bicep +189 -189
- package/content/.morph/templates/infra/parameters.dev.json +29 -29
- package/content/.morph/templates/infra/parameters.prod.json +29 -29
- package/content/.morph/templates/infra/parameters.staging.json +29 -29
- package/content/.morph/templates/infra/sql-database.bicep +103 -103
- package/content/.morph/templates/infra/storage.bicep +106 -106
- package/content/.morph/templates/integrations/asaas-client.cs +387 -387
- package/content/.morph/templates/integrations/asaas-webhook.cs +351 -351
- package/content/.morph/templates/integrations/azure-identity-config.cs +288 -288
- package/content/.morph/templates/integrations/clerk-config.cs +258 -258
- package/content/.morph/templates/job.cs +171 -171
- package/content/.morph/templates/migration.cs +83 -83
- package/content/.morph/templates/repository.cs +141 -141
- package/content/.morph/templates/saas/subscription.cs +347 -347
- package/content/.morph/templates/saas/tenant.cs +338 -338
- package/content/.morph/templates/service.cs +139 -139
- package/content/.morph/templates/sprint-status.yaml +68 -68
- package/content/.morph/templates/story.md +143 -143
- package/content/.morph/templates/test.cs +239 -239
- package/content/.morph/templates/ui-design-system.md +286 -286
- package/content/.morph/templates/ui-flows.md +336 -336
- package/content/.morph/templates/ui-mockups.md +133 -133
- package/content/.morph/test-infra/example.bicep +59 -59
- package/content/README.md +79 -79
- package/detectors/config-detector.js +223 -223
- package/detectors/conversation-analyzer.js +163 -163
- package/detectors/index.js +84 -84
- package/detectors/standards-generator.js +275 -275
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +977 -977
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1048 -1048
- package/docs/api/scripts/collapse.js +38 -38
- package/docs/api/scripts/commonNav.js +28 -28
- package/docs/api/scripts/linenumber.js +25 -25
- package/docs/api/scripts/nav.js +12 -12
- package/docs/api/scripts/polyfill.js +3 -3
- package/docs/api/scripts/prettify/Apache-License-2.0.txt +202 -202
- package/docs/api/scripts/prettify/lang-css.js +2 -2
- package/docs/api/scripts/prettify/prettify.js +28 -28
- package/docs/api/scripts/search.js +98 -98
- package/docs/api/styles/jsdoc.css +776 -776
- package/docs/api/styles/prettify.css +80 -80
- package/docs/examples.md +328 -328
- package/docs/templates.md +418 -418
- package/package.json +1 -1
- package/scripts/postinstall.js +132 -132
- package/src/commands/advance-phase.js +83 -0
- package/src/commands/analyze-blazor-concurrency.js +193 -193
- package/src/commands/create-story.js +351 -351
- package/src/commands/deploy.js +780 -0
- package/src/commands/detect-agents.js +34 -6
- package/src/commands/detect.js +104 -104
- package/src/commands/generate-context.js +40 -0
- package/src/commands/generate.js +149 -149
- package/src/commands/lint-fluent.js +352 -352
- package/src/commands/rollback-phase.js +185 -185
- package/src/commands/session-summary.js +291 -291
- package/src/commands/shard-spec.js +224 -224
- package/src/commands/sprint-status.js +250 -250
- package/src/commands/state.js +333 -333
- package/src/commands/sync.js +167 -167
- package/src/commands/troubleshoot.js +222 -222
- package/src/commands/validate-blazor-state.js +210 -210
- package/src/commands/validate-blazor.js +156 -156
- package/src/commands/validate-css.js +84 -84
- package/src/commands/validate-phase.js +221 -221
- package/src/lib/blazor-concurrency-analyzer.js +288 -288
- package/src/lib/blazor-state-validator.js +291 -291
- package/src/lib/blazor-validator.js +374 -374
- package/src/lib/context-generator.js +513 -0
- package/src/lib/css-validator.js +352 -352
- package/src/lib/design-system-detector.js +187 -0
- package/src/lib/design-system-generator.js +298 -298
- package/src/lib/design-system-scaffolder.js +299 -0
- package/src/lib/hook-executor.js +256 -0
- package/src/lib/learning-system.js +520 -520
- package/src/lib/mockup-generator.js +366 -366
- package/src/lib/spec-validator.js +258 -0
- package/src/lib/standards-context-injector.js +287 -0
- package/src/lib/team-orchestrator.js +322 -0
- package/src/lib/troubleshoot-grep.js +194 -194
- package/src/lib/troubleshoot-index.js +144 -144
- package/src/lib/ui-detector.js +350 -350
- package/src/lib/validation-runner.js +65 -13
- package/src/lib/validators/architecture-validator.js +387 -387
- package/src/lib/validators/design-system-validator.js +231 -0
- package/src/lib/validators/package-validator.js +360 -360
- package/src/lib/validators/ui-contrast-validator.js +422 -422
- package/src/utils/file-copier.js +9 -1
- package/src/utils/logger.js +32 -32
- package/src/utils/version-checker.js +175 -175
- /package/content/.claude/skills/{checklists → level-0-meta}/code-review.md +0 -0
- /package/content/.claude/skills/{checklists → level-0-meta}/simulation-checklist.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/ai-agents}/ai-system-architect.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/architecture}/po-pm-advisor.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/architecture}/standards-architect.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/backend}/dotnet-senior.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/backend}/ef-modeler.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/backend}/hangfire-orchestrator.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/backend}/ms-agent-expert.md +0 -0
- /package/content/.claude/skills/{stacks/dotnet-blazor.md → level-2-domains/frontend/blazor-builder.md} +0 -0
- /package/content/.claude/skills/{stacks/dotnet-nextjs.md → level-2-domains/frontend/nextjs-expert.md} +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/frontend}/ui-ux-designer.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/infrastructure}/azure-architect.md +0 -0
- /package/content/.claude/skills/{infra → level-2-domains/infrastructure}/bicep-architect.md +0 -0
- /package/content/.claude/skills/{infra → level-2-domains/infrastructure}/container-specialist.md +0 -0
- /package/content/.claude/skills/{infra → level-2-domains/infrastructure}/devops-engineer.md +0 -0
- /package/content/.claude/skills/{integrations → level-2-domains/integrations}/asaas-financial.md +0 -0
- /package/content/.claude/skills/{integrations → level-2-domains/integrations}/azure-identity.md +0 -0
- /package/content/.claude/skills/{integrations → level-2-domains/integrations}/clerk-auth.md +0 -0
- /package/content/.claude/skills/{integrations → level-2-domains/integrations}/resend-email.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/quality}/code-analyzer.md +0 -0
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# Azure DevOps Pipeline - MORPH-SPEC Deploy
|
|
3
|
+
# ============================================================================
|
|
4
|
+
# Generated by MORPH-SPEC Azure Deploy Specialist
|
|
5
|
+
#
|
|
6
|
+
# This pipeline provides:
|
|
7
|
+
# - Multi-stage deployment (Build -> Dev -> Staging -> Prod)
|
|
8
|
+
# - Automatic deployment to dev on develop branch
|
|
9
|
+
# - Manual approval for staging and prod
|
|
10
|
+
# - Rollback support via MORPH-SPEC CLI
|
|
11
|
+
# - Cost validation before deployment
|
|
12
|
+
#
|
|
13
|
+
# Prerequisites:
|
|
14
|
+
# 1. Service connection 'azure-service-connection' with subscription access
|
|
15
|
+
# 2. Service connection 'acr-service-connection' for Container Registry
|
|
16
|
+
# 3. Variable group 'deploy-secrets-{env}' for each environment
|
|
17
|
+
# 4. Environment 'dev', 'staging', 'prod' configured in Azure DevOps
|
|
18
|
+
# ============================================================================
|
|
19
|
+
|
|
20
|
+
trigger:
|
|
21
|
+
branches:
|
|
22
|
+
include:
|
|
23
|
+
- main
|
|
24
|
+
- develop
|
|
25
|
+
paths:
|
|
26
|
+
exclude:
|
|
27
|
+
- '*.md'
|
|
28
|
+
- 'docs/**'
|
|
29
|
+
- '.morph/**'
|
|
30
|
+
|
|
31
|
+
pr:
|
|
32
|
+
branches:
|
|
33
|
+
include:
|
|
34
|
+
- main
|
|
35
|
+
- develop
|
|
36
|
+
|
|
37
|
+
variables:
|
|
38
|
+
# Project Configuration
|
|
39
|
+
- name: projectName
|
|
40
|
+
value: '{{PROJECT_NAME}}'
|
|
41
|
+
- name: acrName
|
|
42
|
+
value: '{{ACR_NAME}}'
|
|
43
|
+
- name: dockerfilePath
|
|
44
|
+
value: './Dockerfile'
|
|
45
|
+
- name: resourceGroupPrefix
|
|
46
|
+
value: 'rg-{{PROJECT_NAME}}'
|
|
47
|
+
|
|
48
|
+
# Azure Configuration
|
|
49
|
+
- name: azureSubscription
|
|
50
|
+
value: 'azure-service-connection'
|
|
51
|
+
- name: acrServiceConnection
|
|
52
|
+
value: 'acr-service-connection'
|
|
53
|
+
- name: azureLocation
|
|
54
|
+
value: 'brazilsouth'
|
|
55
|
+
|
|
56
|
+
# Build Configuration
|
|
57
|
+
- name: imageTag
|
|
58
|
+
value: '$(Build.BuildId)'
|
|
59
|
+
- name: imageRepository
|
|
60
|
+
value: '$(projectName)'
|
|
61
|
+
|
|
62
|
+
# ============================================================================
|
|
63
|
+
# STAGES
|
|
64
|
+
# ============================================================================
|
|
65
|
+
|
|
66
|
+
stages:
|
|
67
|
+
# --------------------------------------------------------------------------
|
|
68
|
+
# Stage: Build
|
|
69
|
+
# --------------------------------------------------------------------------
|
|
70
|
+
- stage: Build
|
|
71
|
+
displayName: 'Build and Push Docker Image'
|
|
72
|
+
jobs:
|
|
73
|
+
- job: BuildAndPush
|
|
74
|
+
displayName: 'Build Docker Image'
|
|
75
|
+
pool:
|
|
76
|
+
vmImage: 'ubuntu-latest'
|
|
77
|
+
steps:
|
|
78
|
+
# Checkout code
|
|
79
|
+
- checkout: self
|
|
80
|
+
fetchDepth: 1
|
|
81
|
+
|
|
82
|
+
# Install Node.js for MORPH-SPEC CLI
|
|
83
|
+
- task: NodeTool@0
|
|
84
|
+
displayName: 'Install Node.js'
|
|
85
|
+
inputs:
|
|
86
|
+
versionSpec: '20.x'
|
|
87
|
+
|
|
88
|
+
# Install MORPH-SPEC CLI
|
|
89
|
+
- script: npm install -g @polymorphism-tech/morph-spec
|
|
90
|
+
displayName: 'Install MORPH-SPEC CLI'
|
|
91
|
+
|
|
92
|
+
# Validate Bicep costs
|
|
93
|
+
- script: |
|
|
94
|
+
if [ -f "infra/main.bicep" ]; then
|
|
95
|
+
npx morph-spec cost infra/main.bicep --strict
|
|
96
|
+
else
|
|
97
|
+
echo "No Bicep files found, skipping cost validation"
|
|
98
|
+
fi
|
|
99
|
+
displayName: 'Validate Infrastructure Costs'
|
|
100
|
+
continueOnError: false
|
|
101
|
+
|
|
102
|
+
# Build and push Docker image
|
|
103
|
+
- task: Docker@2
|
|
104
|
+
displayName: 'Build and Push Image'
|
|
105
|
+
inputs:
|
|
106
|
+
containerRegistry: '$(acrServiceConnection)'
|
|
107
|
+
repository: '$(imageRepository)'
|
|
108
|
+
command: 'buildAndPush'
|
|
109
|
+
Dockerfile: '$(dockerfilePath)'
|
|
110
|
+
tags: |
|
|
111
|
+
$(imageTag)
|
|
112
|
+
latest
|
|
113
|
+
|
|
114
|
+
# Save image tag as artifact
|
|
115
|
+
- script: |
|
|
116
|
+
echo "$(imageTag)" > $(Build.ArtifactStagingDirectory)/imagetag.txt
|
|
117
|
+
displayName: 'Save Image Tag'
|
|
118
|
+
|
|
119
|
+
- task: PublishBuildArtifacts@1
|
|
120
|
+
displayName: 'Publish Artifacts'
|
|
121
|
+
inputs:
|
|
122
|
+
pathToPublish: '$(Build.ArtifactStagingDirectory)'
|
|
123
|
+
artifactName: 'build-artifacts'
|
|
124
|
+
|
|
125
|
+
# --------------------------------------------------------------------------
|
|
126
|
+
# Stage: Deploy to Dev
|
|
127
|
+
# --------------------------------------------------------------------------
|
|
128
|
+
- stage: DeployDev
|
|
129
|
+
displayName: 'Deploy to Development'
|
|
130
|
+
dependsOn: Build
|
|
131
|
+
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop'))
|
|
132
|
+
variables:
|
|
133
|
+
- group: 'deploy-secrets-dev'
|
|
134
|
+
- name: environment
|
|
135
|
+
value: 'dev'
|
|
136
|
+
jobs:
|
|
137
|
+
- deployment: DeployToDev
|
|
138
|
+
displayName: 'Deploy to Dev Environment'
|
|
139
|
+
pool:
|
|
140
|
+
vmImage: 'ubuntu-latest'
|
|
141
|
+
environment: 'dev'
|
|
142
|
+
strategy:
|
|
143
|
+
runOnce:
|
|
144
|
+
deploy:
|
|
145
|
+
steps:
|
|
146
|
+
- download: current
|
|
147
|
+
artifact: 'build-artifacts'
|
|
148
|
+
|
|
149
|
+
- task: AzureCLI@2
|
|
150
|
+
displayName: 'Deploy Infrastructure'
|
|
151
|
+
inputs:
|
|
152
|
+
azureSubscription: '$(azureSubscription)'
|
|
153
|
+
scriptType: 'bash'
|
|
154
|
+
scriptLocation: 'inlineScript'
|
|
155
|
+
inlineScript: |
|
|
156
|
+
# Create resource group if not exists
|
|
157
|
+
az group create \
|
|
158
|
+
--name $(resourceGroupPrefix)-$(environment) \
|
|
159
|
+
--location $(azureLocation) \
|
|
160
|
+
--tags environment=$(environment) project=$(projectName)
|
|
161
|
+
|
|
162
|
+
# Deploy Bicep (if exists)
|
|
163
|
+
if [ -f "infra/main.bicep" ]; then
|
|
164
|
+
az deployment group create \
|
|
165
|
+
--resource-group $(resourceGroupPrefix)-$(environment) \
|
|
166
|
+
--template-file infra/main.bicep \
|
|
167
|
+
--parameters @infra/parameters.$(environment).json \
|
|
168
|
+
--name "deploy-$(Build.BuildId)"
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
- task: AzureCLI@2
|
|
172
|
+
displayName: 'Deploy Application'
|
|
173
|
+
inputs:
|
|
174
|
+
azureSubscription: '$(azureSubscription)'
|
|
175
|
+
scriptType: 'bash'
|
|
176
|
+
scriptLocation: 'inlineScript'
|
|
177
|
+
inlineScript: |
|
|
178
|
+
IMAGE_TAG=$(cat $(Pipeline.Workspace)/build-artifacts/imagetag.txt)
|
|
179
|
+
|
|
180
|
+
# Get ACR credentials
|
|
181
|
+
ACR_PASSWORD=$(az acr credential show \
|
|
182
|
+
--name $(acrName) \
|
|
183
|
+
--query "passwords[0].value" -o tsv)
|
|
184
|
+
|
|
185
|
+
# Update Container App
|
|
186
|
+
az containerapp update \
|
|
187
|
+
--name $(projectName)-$(environment)-app \
|
|
188
|
+
--resource-group $(resourceGroupPrefix)-$(environment) \
|
|
189
|
+
--image $(acrName).azurecr.io/$(imageRepository):$IMAGE_TAG \
|
|
190
|
+
--set-env-vars \
|
|
191
|
+
"ConnectionStrings__DefaultConnection=$(SqlConnectionString)" \
|
|
192
|
+
"ASPNETCORE_ENVIRONMENT=Development"
|
|
193
|
+
|
|
194
|
+
# Enable sticky sessions for Blazor Server
|
|
195
|
+
az containerapp ingress sticky-sessions set \
|
|
196
|
+
--name $(projectName)-$(environment)-app \
|
|
197
|
+
--resource-group $(resourceGroupPrefix)-$(environment) \
|
|
198
|
+
--affinity sticky
|
|
199
|
+
|
|
200
|
+
- task: AzureCLI@2
|
|
201
|
+
displayName: 'Verify Deployment'
|
|
202
|
+
inputs:
|
|
203
|
+
azureSubscription: '$(azureSubscription)'
|
|
204
|
+
scriptType: 'bash'
|
|
205
|
+
scriptLocation: 'inlineScript'
|
|
206
|
+
inlineScript: |
|
|
207
|
+
# Wait for healthy state
|
|
208
|
+
for i in {1..30}; do
|
|
209
|
+
HEALTH=$(az containerapp revision list \
|
|
210
|
+
--name $(projectName)-$(environment)-app \
|
|
211
|
+
--resource-group $(resourceGroupPrefix)-$(environment) \
|
|
212
|
+
--query "[0].properties.healthState" -o tsv)
|
|
213
|
+
|
|
214
|
+
if [ "$HEALTH" == "Healthy" ]; then
|
|
215
|
+
echo "Deployment verified: Healthy"
|
|
216
|
+
exit 0
|
|
217
|
+
fi
|
|
218
|
+
|
|
219
|
+
echo "Waiting for healthy state... ($i/30)"
|
|
220
|
+
sleep 10
|
|
221
|
+
done
|
|
222
|
+
|
|
223
|
+
echo "Deployment verification timeout"
|
|
224
|
+
exit 1
|
|
225
|
+
|
|
226
|
+
# --------------------------------------------------------------------------
|
|
227
|
+
# Stage: Deploy to Staging
|
|
228
|
+
# --------------------------------------------------------------------------
|
|
229
|
+
- stage: DeployStaging
|
|
230
|
+
displayName: 'Deploy to Staging'
|
|
231
|
+
dependsOn: Build
|
|
232
|
+
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
|
|
233
|
+
variables:
|
|
234
|
+
- group: 'deploy-secrets-staging'
|
|
235
|
+
- name: environment
|
|
236
|
+
value: 'staging'
|
|
237
|
+
jobs:
|
|
238
|
+
- deployment: DeployToStaging
|
|
239
|
+
displayName: 'Deploy to Staging Environment'
|
|
240
|
+
pool:
|
|
241
|
+
vmImage: 'ubuntu-latest'
|
|
242
|
+
environment: 'staging'
|
|
243
|
+
strategy:
|
|
244
|
+
runOnce:
|
|
245
|
+
deploy:
|
|
246
|
+
steps:
|
|
247
|
+
- download: current
|
|
248
|
+
artifact: 'build-artifacts'
|
|
249
|
+
|
|
250
|
+
- task: AzureCLI@2
|
|
251
|
+
displayName: 'Deploy Infrastructure'
|
|
252
|
+
inputs:
|
|
253
|
+
azureSubscription: '$(azureSubscription)'
|
|
254
|
+
scriptType: 'bash'
|
|
255
|
+
scriptLocation: 'inlineScript'
|
|
256
|
+
inlineScript: |
|
|
257
|
+
az group create \
|
|
258
|
+
--name $(resourceGroupPrefix)-$(environment) \
|
|
259
|
+
--location $(azureLocation) \
|
|
260
|
+
--tags environment=$(environment) project=$(projectName)
|
|
261
|
+
|
|
262
|
+
if [ -f "infra/main.bicep" ]; then
|
|
263
|
+
az deployment group create \
|
|
264
|
+
--resource-group $(resourceGroupPrefix)-$(environment) \
|
|
265
|
+
--template-file infra/main.bicep \
|
|
266
|
+
--parameters @infra/parameters.$(environment).json \
|
|
267
|
+
--name "deploy-$(Build.BuildId)"
|
|
268
|
+
fi
|
|
269
|
+
|
|
270
|
+
- task: AzureCLI@2
|
|
271
|
+
displayName: 'Deploy Application'
|
|
272
|
+
inputs:
|
|
273
|
+
azureSubscription: '$(azureSubscription)'
|
|
274
|
+
scriptType: 'bash'
|
|
275
|
+
scriptLocation: 'inlineScript'
|
|
276
|
+
inlineScript: |
|
|
277
|
+
IMAGE_TAG=$(cat $(Pipeline.Workspace)/build-artifacts/imagetag.txt)
|
|
278
|
+
|
|
279
|
+
ACR_PASSWORD=$(az acr credential show \
|
|
280
|
+
--name $(acrName) \
|
|
281
|
+
--query "passwords[0].value" -o tsv)
|
|
282
|
+
|
|
283
|
+
az containerapp update \
|
|
284
|
+
--name $(projectName)-$(environment)-app \
|
|
285
|
+
--resource-group $(resourceGroupPrefix)-$(environment) \
|
|
286
|
+
--image $(acrName).azurecr.io/$(imageRepository):$IMAGE_TAG \
|
|
287
|
+
--set-env-vars \
|
|
288
|
+
"ConnectionStrings__DefaultConnection=$(SqlConnectionString)" \
|
|
289
|
+
"ASPNETCORE_ENVIRONMENT=Staging"
|
|
290
|
+
|
|
291
|
+
az containerapp ingress sticky-sessions set \
|
|
292
|
+
--name $(projectName)-$(environment)-app \
|
|
293
|
+
--resource-group $(resourceGroupPrefix)-$(environment) \
|
|
294
|
+
--affinity sticky
|
|
295
|
+
|
|
296
|
+
- task: AzureCLI@2
|
|
297
|
+
displayName: 'Verify Deployment'
|
|
298
|
+
inputs:
|
|
299
|
+
azureSubscription: '$(azureSubscription)'
|
|
300
|
+
scriptType: 'bash'
|
|
301
|
+
scriptLocation: 'inlineScript'
|
|
302
|
+
inlineScript: |
|
|
303
|
+
for i in {1..30}; do
|
|
304
|
+
HEALTH=$(az containerapp revision list \
|
|
305
|
+
--name $(projectName)-$(environment)-app \
|
|
306
|
+
--resource-group $(resourceGroupPrefix)-$(environment) \
|
|
307
|
+
--query "[0].properties.healthState" -o tsv)
|
|
308
|
+
|
|
309
|
+
if [ "$HEALTH" == "Healthy" ]; then
|
|
310
|
+
echo "Deployment verified: Healthy"
|
|
311
|
+
exit 0
|
|
312
|
+
fi
|
|
313
|
+
|
|
314
|
+
echo "Waiting for healthy state... ($i/30)"
|
|
315
|
+
sleep 10
|
|
316
|
+
done
|
|
317
|
+
|
|
318
|
+
echo "Deployment verification timeout"
|
|
319
|
+
exit 1
|
|
320
|
+
|
|
321
|
+
# --------------------------------------------------------------------------
|
|
322
|
+
# Stage: Deploy to Production
|
|
323
|
+
# --------------------------------------------------------------------------
|
|
324
|
+
- stage: DeployProd
|
|
325
|
+
displayName: 'Deploy to Production'
|
|
326
|
+
dependsOn: DeployStaging
|
|
327
|
+
condition: succeeded()
|
|
328
|
+
variables:
|
|
329
|
+
- group: 'deploy-secrets-prod'
|
|
330
|
+
- name: environment
|
|
331
|
+
value: 'prod'
|
|
332
|
+
jobs:
|
|
333
|
+
- deployment: DeployToProd
|
|
334
|
+
displayName: 'Deploy to Production Environment'
|
|
335
|
+
pool:
|
|
336
|
+
vmImage: 'ubuntu-latest'
|
|
337
|
+
environment: 'prod'
|
|
338
|
+
strategy:
|
|
339
|
+
runOnce:
|
|
340
|
+
deploy:
|
|
341
|
+
steps:
|
|
342
|
+
- download: current
|
|
343
|
+
artifact: 'build-artifacts'
|
|
344
|
+
|
|
345
|
+
# Save previous revision for rollback
|
|
346
|
+
- task: AzureCLI@2
|
|
347
|
+
displayName: 'Save Previous Revision'
|
|
348
|
+
inputs:
|
|
349
|
+
azureSubscription: '$(azureSubscription)'
|
|
350
|
+
scriptType: 'bash'
|
|
351
|
+
scriptLocation: 'inlineScript'
|
|
352
|
+
inlineScript: |
|
|
353
|
+
PREVIOUS_REVISION=$(az containerapp revision list \
|
|
354
|
+
--name $(projectName)-$(environment)-app \
|
|
355
|
+
--resource-group $(resourceGroupPrefix)-$(environment) \
|
|
356
|
+
--query "[0].name" -o tsv 2>/dev/null || echo "none")
|
|
357
|
+
|
|
358
|
+
echo "##vso[task.setvariable variable=previousRevision]$PREVIOUS_REVISION"
|
|
359
|
+
echo "Previous revision: $PREVIOUS_REVISION"
|
|
360
|
+
|
|
361
|
+
- task: AzureCLI@2
|
|
362
|
+
displayName: 'Deploy Infrastructure'
|
|
363
|
+
inputs:
|
|
364
|
+
azureSubscription: '$(azureSubscription)'
|
|
365
|
+
scriptType: 'bash'
|
|
366
|
+
scriptLocation: 'inlineScript'
|
|
367
|
+
inlineScript: |
|
|
368
|
+
az group create \
|
|
369
|
+
--name $(resourceGroupPrefix)-$(environment) \
|
|
370
|
+
--location $(azureLocation) \
|
|
371
|
+
--tags environment=$(environment) project=$(projectName)
|
|
372
|
+
|
|
373
|
+
if [ -f "infra/main.bicep" ]; then
|
|
374
|
+
az deployment group create \
|
|
375
|
+
--resource-group $(resourceGroupPrefix)-$(environment) \
|
|
376
|
+
--template-file infra/main.bicep \
|
|
377
|
+
--parameters @infra/parameters.$(environment).json \
|
|
378
|
+
--name "deploy-$(Build.BuildId)"
|
|
379
|
+
fi
|
|
380
|
+
|
|
381
|
+
- task: AzureCLI@2
|
|
382
|
+
displayName: 'Deploy Application'
|
|
383
|
+
inputs:
|
|
384
|
+
azureSubscription: '$(azureSubscription)'
|
|
385
|
+
scriptType: 'bash'
|
|
386
|
+
scriptLocation: 'inlineScript'
|
|
387
|
+
inlineScript: |
|
|
388
|
+
IMAGE_TAG=$(cat $(Pipeline.Workspace)/build-artifacts/imagetag.txt)
|
|
389
|
+
|
|
390
|
+
ACR_PASSWORD=$(az acr credential show \
|
|
391
|
+
--name $(acrName) \
|
|
392
|
+
--query "passwords[0].value" -o tsv)
|
|
393
|
+
|
|
394
|
+
az containerapp update \
|
|
395
|
+
--name $(projectName)-$(environment)-app \
|
|
396
|
+
--resource-group $(resourceGroupPrefix)-$(environment) \
|
|
397
|
+
--image $(acrName).azurecr.io/$(imageRepository):$IMAGE_TAG \
|
|
398
|
+
--set-env-vars \
|
|
399
|
+
"ConnectionStrings__DefaultConnection=$(SqlConnectionString)" \
|
|
400
|
+
"ASPNETCORE_ENVIRONMENT=Production"
|
|
401
|
+
|
|
402
|
+
az containerapp ingress sticky-sessions set \
|
|
403
|
+
--name $(projectName)-$(environment)-app \
|
|
404
|
+
--resource-group $(resourceGroupPrefix)-$(environment) \
|
|
405
|
+
--affinity sticky
|
|
406
|
+
|
|
407
|
+
- task: AzureCLI@2
|
|
408
|
+
displayName: 'Verify Deployment'
|
|
409
|
+
inputs:
|
|
410
|
+
azureSubscription: '$(azureSubscription)'
|
|
411
|
+
scriptType: 'bash'
|
|
412
|
+
scriptLocation: 'inlineScript'
|
|
413
|
+
inlineScript: |
|
|
414
|
+
for i in {1..30}; do
|
|
415
|
+
HEALTH=$(az containerapp revision list \
|
|
416
|
+
--name $(projectName)-$(environment)-app \
|
|
417
|
+
--resource-group $(resourceGroupPrefix)-$(environment) \
|
|
418
|
+
--query "[0].properties.healthState" -o tsv)
|
|
419
|
+
|
|
420
|
+
if [ "$HEALTH" == "Healthy" ]; then
|
|
421
|
+
echo "Deployment verified: Healthy"
|
|
422
|
+
|
|
423
|
+
# Get app URL
|
|
424
|
+
APP_URL=$(az containerapp show \
|
|
425
|
+
--name $(projectName)-$(environment)-app \
|
|
426
|
+
--resource-group $(resourceGroupPrefix)-$(environment) \
|
|
427
|
+
--query "properties.configuration.ingress.fqdn" -o tsv)
|
|
428
|
+
|
|
429
|
+
echo "Application URL: https://$APP_URL"
|
|
430
|
+
exit 0
|
|
431
|
+
fi
|
|
432
|
+
|
|
433
|
+
echo "Waiting for healthy state... ($i/30)"
|
|
434
|
+
sleep 10
|
|
435
|
+
done
|
|
436
|
+
|
|
437
|
+
# Rollback on failure
|
|
438
|
+
echo "Deployment failed! Initiating rollback..."
|
|
439
|
+
if [ "$(previousRevision)" != "none" ]; then
|
|
440
|
+
az containerapp revision activate \
|
|
441
|
+
--name $(projectName)-$(environment)-app \
|
|
442
|
+
--resource-group $(resourceGroupPrefix)-$(environment) \
|
|
443
|
+
--revision $(previousRevision)
|
|
444
|
+
echo "Rolled back to $(previousRevision)"
|
|
445
|
+
fi
|
|
446
|
+
exit 1
|
|
447
|
+
|
|
448
|
+
# ============================================================================
|
|
449
|
+
# USAGE INSTRUCTIONS
|
|
450
|
+
# ============================================================================
|
|
451
|
+
#
|
|
452
|
+
# 1. Replace placeholders:
|
|
453
|
+
# - {{PROJECT_NAME}}: Your project name (e.g., "myapp")
|
|
454
|
+
# - {{ACR_NAME}}: Your Azure Container Registry name
|
|
455
|
+
#
|
|
456
|
+
# 2. Create Service Connections in Azure DevOps:
|
|
457
|
+
# - azure-service-connection: Azure Resource Manager connection
|
|
458
|
+
# - acr-service-connection: Docker Registry connection to ACR
|
|
459
|
+
#
|
|
460
|
+
# 3. Create Variable Groups:
|
|
461
|
+
# - deploy-secrets-dev: Variables for dev environment
|
|
462
|
+
# - deploy-secrets-staging: Variables for staging environment
|
|
463
|
+
# - deploy-secrets-prod: Variables for production environment
|
|
464
|
+
#
|
|
465
|
+
# Required variables in each group:
|
|
466
|
+
# - SqlConnectionString: Full SQL connection string
|
|
467
|
+
# - (Add other secrets as needed)
|
|
468
|
+
#
|
|
469
|
+
# 4. Create Environments in Azure DevOps:
|
|
470
|
+
# - dev: No approvals (auto-deploy on develop branch)
|
|
471
|
+
# - staging: Optional approval
|
|
472
|
+
# - prod: Required approval
|
|
473
|
+
#
|
|
474
|
+
# 5. Ensure Bicep templates exist:
|
|
475
|
+
# - infra/main.bicep
|
|
476
|
+
# - infra/parameters.dev.json
|
|
477
|
+
# - infra/parameters.staging.json
|
|
478
|
+
# - infra/parameters.prod.json
|
|
479
|
+
#
|
|
480
|
+
# ============================================================================
|
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
// ==============================================================================
|
|
2
|
-
// MORPH-SPEC - Container Apps Environment
|
|
3
|
-
// Azure Container Apps managed environment
|
|
4
|
-
// ==============================================================================
|
|
5
|
-
|
|
6
|
-
@description('Environment name')
|
|
7
|
-
param name string
|
|
8
|
-
|
|
9
|
-
@description('Location')
|
|
10
|
-
param location string
|
|
11
|
-
|
|
12
|
-
@description('Tags')
|
|
13
|
-
param tags object = {}
|
|
14
|
-
|
|
15
|
-
@description('Log Analytics Workspace ID')
|
|
16
|
-
param logAnalyticsWorkspaceId string
|
|
17
|
-
|
|
18
|
-
// ==============================================================================
|
|
19
|
-
// CONTAINER APPS ENVIRONMENT
|
|
20
|
-
// ==============================================================================
|
|
21
|
-
|
|
22
|
-
resource containerAppEnv 'Microsoft.App/managedEnvironments@2023-05-01' = {
|
|
23
|
-
name: name
|
|
24
|
-
location: location
|
|
25
|
-
tags: tags
|
|
26
|
-
properties: {
|
|
27
|
-
appLogsConfiguration: {
|
|
28
|
-
destination: 'log-analytics'
|
|
29
|
-
logAnalyticsConfiguration: {
|
|
30
|
-
customerId: reference(logAnalyticsWorkspaceId, '2022-10-01').customerId
|
|
31
|
-
sharedKey: listKeys(logAnalyticsWorkspaceId, '2022-10-01').primarySharedKey
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
zoneRedundant: false
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// ==============================================================================
|
|
39
|
-
// OUTPUTS
|
|
40
|
-
// ==============================================================================
|
|
41
|
-
|
|
42
|
-
@description('Container Apps Environment ID')
|
|
43
|
-
output id string = containerAppEnv.id
|
|
44
|
-
|
|
45
|
-
@description('Container Apps Environment name')
|
|
46
|
-
output name string = containerAppEnv.name
|
|
47
|
-
|
|
48
|
-
@description('Default domain')
|
|
49
|
-
output defaultDomain string = containerAppEnv.properties.defaultDomain
|
|
1
|
+
// ==============================================================================
|
|
2
|
+
// MORPH-SPEC - Container Apps Environment
|
|
3
|
+
// Azure Container Apps managed environment
|
|
4
|
+
// ==============================================================================
|
|
5
|
+
|
|
6
|
+
@description('Environment name')
|
|
7
|
+
param name string
|
|
8
|
+
|
|
9
|
+
@description('Location')
|
|
10
|
+
param location string
|
|
11
|
+
|
|
12
|
+
@description('Tags')
|
|
13
|
+
param tags object = {}
|
|
14
|
+
|
|
15
|
+
@description('Log Analytics Workspace ID')
|
|
16
|
+
param logAnalyticsWorkspaceId string
|
|
17
|
+
|
|
18
|
+
// ==============================================================================
|
|
19
|
+
// CONTAINER APPS ENVIRONMENT
|
|
20
|
+
// ==============================================================================
|
|
21
|
+
|
|
22
|
+
resource containerAppEnv 'Microsoft.App/managedEnvironments@2023-05-01' = {
|
|
23
|
+
name: name
|
|
24
|
+
location: location
|
|
25
|
+
tags: tags
|
|
26
|
+
properties: {
|
|
27
|
+
appLogsConfiguration: {
|
|
28
|
+
destination: 'log-analytics'
|
|
29
|
+
logAnalyticsConfiguration: {
|
|
30
|
+
customerId: reference(logAnalyticsWorkspaceId, '2022-10-01').customerId
|
|
31
|
+
sharedKey: listKeys(logAnalyticsWorkspaceId, '2022-10-01').primarySharedKey
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
zoneRedundant: false
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ==============================================================================
|
|
39
|
+
// OUTPUTS
|
|
40
|
+
// ==============================================================================
|
|
41
|
+
|
|
42
|
+
@description('Container Apps Environment ID')
|
|
43
|
+
output id string = containerAppEnv.id
|
|
44
|
+
|
|
45
|
+
@description('Container Apps Environment name')
|
|
46
|
+
output name string = containerAppEnv.name
|
|
47
|
+
|
|
48
|
+
@description('Default domain')
|
|
49
|
+
output defaultDomain string = containerAppEnv.properties.defaultDomain
|