@polymorphism-tech/morph-spec 1.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 (83) hide show
  1. package/README.md +279 -0
  2. package/bin/morph-spec.js +53 -0
  3. package/content/.claude/commands/morph-apply.md +66 -0
  4. package/content/.claude/commands/morph-archive.md +79 -0
  5. package/content/.claude/commands/morph-costs.md +206 -0
  6. package/content/.claude/commands/morph-infra.md +209 -0
  7. package/content/.claude/commands/morph-proposal.md +60 -0
  8. package/content/.claude/commands/morph-status.md +71 -0
  9. package/content/.claude/settings.local.json +15 -0
  10. package/content/.claude/skills/infra/bicep-architect.md +419 -0
  11. package/content/.claude/skills/infra/container-specialist.md +437 -0
  12. package/content/.claude/skills/infra/devops-engineer.md +405 -0
  13. package/content/.claude/skills/integrations/asaas-financial.md +333 -0
  14. package/content/.claude/skills/integrations/azure-identity.md +309 -0
  15. package/content/.claude/skills/integrations/clerk-auth.md +290 -0
  16. package/content/.claude/skills/specialists/azure-architect.md +142 -0
  17. package/content/.claude/skills/specialists/cost-guardian.md +110 -0
  18. package/content/.claude/skills/specialists/ef-modeler.md +200 -0
  19. package/content/.claude/skills/specialists/hangfire-orchestrator.md +245 -0
  20. package/content/.claude/skills/specialists/ms-agent-expert.md +209 -0
  21. package/content/.claude/skills/specialists/po-pm-advisor.md +197 -0
  22. package/content/.claude/skills/specialists/standards-architect.md +78 -0
  23. package/content/.claude/skills/specialists/ui-ux-designer.md +325 -0
  24. package/content/.claude/skills/stacks/dotnet-blazor.md +352 -0
  25. package/content/.claude/skills/stacks/dotnet-nextjs.md +402 -0
  26. package/content/.claude/skills/stacks/shopify.md +445 -0
  27. package/content/.morph/archive/.gitkeep +25 -0
  28. package/content/.morph/config/agents.json +149 -0
  29. package/content/.morph/config/config.template.json +96 -0
  30. package/content/.morph/examples/api-nextjs/README.md +241 -0
  31. package/content/.morph/examples/api-nextjs/contracts.ts +307 -0
  32. package/content/.morph/examples/api-nextjs/spec.md +399 -0
  33. package/content/.morph/examples/api-nextjs/tasks.md +168 -0
  34. package/content/.morph/examples/micro-saas/README.md +125 -0
  35. package/content/.morph/examples/micro-saas/contracts.cs +358 -0
  36. package/content/.morph/examples/micro-saas/decisions.md +246 -0
  37. package/content/.morph/examples/micro-saas/spec.md +236 -0
  38. package/content/.morph/examples/micro-saas/tasks.md +150 -0
  39. package/content/.morph/examples/multi-agent/README.md +309 -0
  40. package/content/.morph/examples/multi-agent/contracts.cs +433 -0
  41. package/content/.morph/examples/multi-agent/spec.md +479 -0
  42. package/content/.morph/examples/multi-agent/tasks.md +185 -0
  43. package/content/.morph/features/.gitkeep +25 -0
  44. package/content/.morph/project.md +159 -0
  45. package/content/.morph/specs/.gitkeep +20 -0
  46. package/content/.morph/standards/architecture.md +190 -0
  47. package/content/.morph/standards/azure.md +184 -0
  48. package/content/.morph/standards/coding.md +342 -0
  49. package/content/.morph/templates/agent.cs +172 -0
  50. package/content/.morph/templates/component.razor +239 -0
  51. package/content/.morph/templates/contracts.cs +217 -0
  52. package/content/.morph/templates/decisions.md +106 -0
  53. package/content/.morph/templates/infra/app-insights.bicep +63 -0
  54. package/content/.morph/templates/infra/container-app-env.bicep +49 -0
  55. package/content/.morph/templates/infra/container-app.bicep +156 -0
  56. package/content/.morph/templates/infra/key-vault.bicep +91 -0
  57. package/content/.morph/templates/infra/main.bicep +155 -0
  58. package/content/.morph/templates/infra/parameters.dev.json +23 -0
  59. package/content/.morph/templates/infra/parameters.prod.json +23 -0
  60. package/content/.morph/templates/infra/sql-database.bicep +103 -0
  61. package/content/.morph/templates/infra/storage.bicep +106 -0
  62. package/content/.morph/templates/integrations/asaas-client.cs +387 -0
  63. package/content/.morph/templates/integrations/asaas-webhook.cs +351 -0
  64. package/content/.morph/templates/integrations/azure-identity-config.cs +288 -0
  65. package/content/.morph/templates/integrations/clerk-config.cs +258 -0
  66. package/content/.morph/templates/job.cs +171 -0
  67. package/content/.morph/templates/migration.cs +83 -0
  68. package/content/.morph/templates/proposal.md +155 -0
  69. package/content/.morph/templates/recap.md +105 -0
  70. package/content/.morph/templates/repository.cs +141 -0
  71. package/content/.morph/templates/saas/subscription.cs +347 -0
  72. package/content/.morph/templates/saas/tenant.cs +338 -0
  73. package/content/.morph/templates/service.cs +139 -0
  74. package/content/.morph/templates/spec.md +147 -0
  75. package/content/.morph/templates/tasks.md +235 -0
  76. package/content/.morph/templates/test.cs +239 -0
  77. package/content/CLAUDE.md +318 -0
  78. package/package.json +50 -0
  79. package/src/commands/doctor.js +132 -0
  80. package/src/commands/init.js +121 -0
  81. package/src/commands/update.js +84 -0
  82. package/src/utils/file-copier.js +50 -0
  83. package/src/utils/logger.js +32 -0
@@ -0,0 +1,156 @@
1
+ // ==============================================================================
2
+ // MORPH-SPEC - Container App
3
+ // Azure Container Apps with ingress and auto-scaling
4
+ // ==============================================================================
5
+
6
+ @description('App name')
7
+ param name string
8
+
9
+ @description('Location')
10
+ param location string
11
+
12
+ @description('Tags')
13
+ param tags object = {}
14
+
15
+ @description('Container App Environment ID')
16
+ param environmentId string
17
+
18
+ @description('Container image')
19
+ param containerImage string
20
+
21
+ @description('App Insights connection string')
22
+ param appInsightsConnectionString string = ''
23
+
24
+ @description('CPU cores (0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0)')
25
+ param cpu string = '0.25'
26
+
27
+ @description('Memory (0.5Gi, 1Gi, 1.5Gi, 2Gi, 3Gi, 4Gi)')
28
+ param memory string = '0.5Gi'
29
+
30
+ @description('Minimum replicas (0 = scale to zero)')
31
+ param minReplicas int = 0
32
+
33
+ @description('Maximum replicas')
34
+ param maxReplicas int = 3
35
+
36
+ @description('Target port')
37
+ param targetPort int = 8080
38
+
39
+ @description('Environment variables')
40
+ param envVars array = []
41
+
42
+ // ==============================================================================
43
+ // CONTAINER APP
44
+ // ==============================================================================
45
+
46
+ var secrets = appInsightsConnectionString != '' ? [
47
+ {
48
+ name: 'appinsights-connection-string'
49
+ value: appInsightsConnectionString
50
+ }
51
+ ] : []
52
+
53
+ var defaultEnvVars = appInsightsConnectionString != '' ? [
54
+ {
55
+ name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
56
+ secretRef: 'appinsights-connection-string'
57
+ }
58
+ {
59
+ name: 'ASPNETCORE_ENVIRONMENT'
60
+ value: 'Production'
61
+ }
62
+ ] : [
63
+ {
64
+ name: 'ASPNETCORE_ENVIRONMENT'
65
+ value: 'Production'
66
+ }
67
+ ]
68
+
69
+ resource containerApp 'Microsoft.App/containerApps@2023-05-01' = {
70
+ name: name
71
+ location: location
72
+ tags: tags
73
+ properties: {
74
+ managedEnvironmentId: environmentId
75
+ configuration: {
76
+ ingress: {
77
+ external: true
78
+ targetPort: targetPort
79
+ transport: 'http'
80
+ allowInsecure: false
81
+ traffic: [
82
+ {
83
+ latestRevision: true
84
+ weight: 100
85
+ }
86
+ ]
87
+ }
88
+ secrets: secrets
89
+ }
90
+ template: {
91
+ containers: [
92
+ {
93
+ name: name
94
+ image: containerImage
95
+ resources: {
96
+ cpu: json(cpu)
97
+ memory: memory
98
+ }
99
+ env: concat(defaultEnvVars, envVars)
100
+ probes: [
101
+ {
102
+ type: 'Liveness'
103
+ httpGet: {
104
+ path: '/health'
105
+ port: targetPort
106
+ }
107
+ initialDelaySeconds: 10
108
+ periodSeconds: 30
109
+ failureThreshold: 3
110
+ }
111
+ {
112
+ type: 'Readiness'
113
+ httpGet: {
114
+ path: '/health/ready'
115
+ port: targetPort
116
+ }
117
+ initialDelaySeconds: 5
118
+ periodSeconds: 10
119
+ failureThreshold: 3
120
+ }
121
+ ]
122
+ }
123
+ ]
124
+ scale: {
125
+ minReplicas: minReplicas
126
+ maxReplicas: maxReplicas
127
+ rules: [
128
+ {
129
+ name: 'http-scale'
130
+ http: {
131
+ metadata: {
132
+ concurrentRequests: '100'
133
+ }
134
+ }
135
+ }
136
+ ]
137
+ }
138
+ }
139
+ }
140
+ }
141
+
142
+ // ==============================================================================
143
+ // OUTPUTS
144
+ // ==============================================================================
145
+
146
+ @description('Container App ID')
147
+ output id string = containerApp.id
148
+
149
+ @description('Container App name')
150
+ output name string = containerApp.name
151
+
152
+ @description('Container App URL')
153
+ output url string = 'https://${containerApp.properties.configuration.ingress.fqdn}'
154
+
155
+ @description('Container App FQDN')
156
+ output fqdn string = containerApp.properties.configuration.ingress.fqdn
@@ -0,0 +1,91 @@
1
+ // ==============================================================================
2
+ // MORPH-SPEC - Key Vault
3
+ // Azure Key Vault for secrets management
4
+ // ==============================================================================
5
+
6
+ @description('Key Vault name')
7
+ @minLength(3)
8
+ @maxLength(24)
9
+ param name string
10
+
11
+ @description('Location')
12
+ param location string
13
+
14
+ @description('Tags')
15
+ param tags object = {}
16
+
17
+ @description('Enable soft delete')
18
+ param enableSoftDelete bool = true
19
+
20
+ @description('Soft delete retention days')
21
+ @minValue(7)
22
+ @maxValue(90)
23
+ param softDeleteRetentionDays int = 30
24
+
25
+ @description('Enable purge protection')
26
+ param enablePurgeProtection bool = false
27
+
28
+ @description('Object IDs to grant access (optional)')
29
+ param accessPoliciesObjectIds array = []
30
+
31
+ // ==============================================================================
32
+ // KEY VAULT
33
+ // ==============================================================================
34
+
35
+ resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' = {
36
+ name: name
37
+ location: location
38
+ tags: tags
39
+ properties: {
40
+ tenantId: subscription().tenantId
41
+ sku: {
42
+ family: 'A'
43
+ name: 'standard'
44
+ }
45
+ enabledForDeployment: true
46
+ enabledForDiskEncryption: false
47
+ enabledForTemplateDeployment: true
48
+ enableSoftDelete: enableSoftDelete
49
+ softDeleteRetentionInDays: softDeleteRetentionDays
50
+ enablePurgeProtection: enablePurgeProtection ? true : null
51
+ enableRbacAuthorization: true
52
+ publicNetworkAccess: 'Enabled'
53
+ networkAcls: {
54
+ defaultAction: 'Allow'
55
+ bypass: 'AzureServices'
56
+ }
57
+ }
58
+ }
59
+
60
+ // ==============================================================================
61
+ // ACCESS POLICIES (Optional - if not using RBAC)
62
+ // ==============================================================================
63
+
64
+ resource accessPolicies 'Microsoft.KeyVault/vaults/accessPolicies@2023-07-01' = if (length(accessPoliciesObjectIds) > 0) {
65
+ parent: keyVault
66
+ name: 'add'
67
+ properties: {
68
+ accessPolicies: [for objectId in accessPoliciesObjectIds: {
69
+ tenantId: subscription().tenantId
70
+ objectId: objectId
71
+ permissions: {
72
+ secrets: ['get', 'list', 'set', 'delete']
73
+ keys: ['get', 'list', 'create', 'delete']
74
+ certificates: ['get', 'list', 'create', 'delete']
75
+ }
76
+ }]
77
+ }
78
+ }
79
+
80
+ // ==============================================================================
81
+ // OUTPUTS
82
+ // ==============================================================================
83
+
84
+ @description('Key Vault ID')
85
+ output id string = keyVault.id
86
+
87
+ @description('Key Vault name')
88
+ output name string = keyVault.name
89
+
90
+ @description('Key Vault URI')
91
+ output uri string = keyVault.properties.vaultUri
@@ -0,0 +1,155 @@
1
+ // ==============================================================================
2
+ // MORPH-SPEC - Main Bicep Template
3
+ // Entry point para infraestrutura Azure
4
+ // ==============================================================================
5
+
6
+ targetScope = 'resourceGroup'
7
+
8
+ // ==============================================================================
9
+ // PARAMETERS
10
+ // ==============================================================================
11
+
12
+ @description('Environment name (dev, staging, prod)')
13
+ @allowed(['dev', 'staging', 'prod'])
14
+ param environment string = 'dev'
15
+
16
+ @description('Location for all resources')
17
+ param location string = resourceGroup().location
18
+
19
+ @description('Application name (used for naming resources)')
20
+ @minLength(3)
21
+ @maxLength(15)
22
+ param appName string
23
+
24
+ @description('SQL Server administrator password')
25
+ @secure()
26
+ param sqlAdminPassword string
27
+
28
+ @description('Container image to deploy')
29
+ param containerImage string = 'mcr.microsoft.com/hello-world:latest'
30
+
31
+ // ==============================================================================
32
+ // VARIABLES
33
+ // ==============================================================================
34
+
35
+ var resourcePrefix = '${appName}-${environment}'
36
+ var tags = {
37
+ environment: environment
38
+ application: appName
39
+ managedBy: 'bicep'
40
+ framework: 'morph-spec'
41
+ }
42
+
43
+ // ==============================================================================
44
+ // LOG ANALYTICS WORKSPACE
45
+ // Required for Container Apps and Application Insights
46
+ // ==============================================================================
47
+
48
+ resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2022-10-01' = {
49
+ name: '${resourcePrefix}-logs'
50
+ location: location
51
+ tags: tags
52
+ properties: {
53
+ sku: {
54
+ name: 'PerGB2018'
55
+ }
56
+ retentionInDays: environment == 'prod' ? 90 : 30
57
+ }
58
+ }
59
+
60
+ // ==============================================================================
61
+ // MODULES
62
+ // ==============================================================================
63
+
64
+ // Application Insights
65
+ module appInsights 'app-insights.bicep' = {
66
+ name: 'appInsights-${uniqueString(resourceGroup().id)}'
67
+ params: {
68
+ name: '${resourcePrefix}-insights'
69
+ location: location
70
+ tags: tags
71
+ logAnalyticsWorkspaceId: logAnalytics.id
72
+ }
73
+ }
74
+
75
+ // Key Vault
76
+ module keyVault 'key-vault.bicep' = {
77
+ name: 'keyVault-${uniqueString(resourceGroup().id)}'
78
+ params: {
79
+ name: '${resourcePrefix}-kv'
80
+ location: location
81
+ tags: tags
82
+ }
83
+ }
84
+
85
+ // Storage Account
86
+ module storage 'storage.bicep' = {
87
+ name: 'storage-${uniqueString(resourceGroup().id)}'
88
+ params: {
89
+ name: replace('${resourcePrefix}st', '-', '')
90
+ location: location
91
+ tags: tags
92
+ sku: environment == 'prod' ? 'Standard_GRS' : 'Standard_LRS'
93
+ }
94
+ }
95
+
96
+ // SQL Database
97
+ module sqlDatabase 'sql-database.bicep' = {
98
+ name: 'sqlDatabase-${uniqueString(resourceGroup().id)}'
99
+ params: {
100
+ serverName: '${resourcePrefix}-sql'
101
+ databaseName: appName
102
+ location: location
103
+ tags: tags
104
+ adminPassword: sqlAdminPassword
105
+ useFree: environment == 'dev'
106
+ }
107
+ }
108
+
109
+ // Container Apps Environment
110
+ module containerAppEnv 'container-app-env.bicep' = {
111
+ name: 'containerAppEnv-${uniqueString(resourceGroup().id)}'
112
+ params: {
113
+ name: '${resourcePrefix}-env'
114
+ location: location
115
+ tags: tags
116
+ logAnalyticsWorkspaceId: logAnalytics.id
117
+ }
118
+ }
119
+
120
+ // Container App
121
+ module containerApp 'container-app.bicep' = {
122
+ name: 'containerApp-${uniqueString(resourceGroup().id)}'
123
+ params: {
124
+ name: resourcePrefix
125
+ location: location
126
+ tags: tags
127
+ environmentId: containerAppEnv.outputs.id
128
+ containerImage: containerImage
129
+ appInsightsConnectionString: appInsights.outputs.connectionString
130
+ minReplicas: environment == 'prod' ? 1 : 0
131
+ maxReplicas: environment == 'prod' ? 10 : 3
132
+ }
133
+ }
134
+
135
+ // ==============================================================================
136
+ // OUTPUTS
137
+ // ==============================================================================
138
+
139
+ @description('Container App URL')
140
+ output containerAppUrl string = containerApp.outputs.url
141
+
142
+ @description('SQL Server connection string')
143
+ output sqlConnectionString string = sqlDatabase.outputs.connectionString
144
+
145
+ @description('Key Vault URI')
146
+ output keyVaultUri string = keyVault.outputs.uri
147
+
148
+ @description('Storage Account connection string')
149
+ output storageConnectionString string = storage.outputs.connectionString
150
+
151
+ @description('Application Insights connection string')
152
+ output appInsightsConnectionString string = appInsights.outputs.connectionString
153
+
154
+ @description('Log Analytics Workspace ID')
155
+ output logAnalyticsWorkspaceId string = logAnalytics.id
@@ -0,0 +1,23 @@
1
+ {
2
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
3
+ "contentVersion": "1.0.0.0",
4
+ "parameters": {
5
+ "environment": {
6
+ "value": "dev"
7
+ },
8
+ "appName": {
9
+ "value": "{{APP_NAME}}"
10
+ },
11
+ "sqlAdminPassword": {
12
+ "reference": {
13
+ "keyVault": {
14
+ "id": "/subscriptions/{{SUBSCRIPTION_ID}}/resourceGroups/{{RESOURCE_GROUP}}/providers/Microsoft.KeyVault/vaults/{{KEY_VAULT_NAME}}"
15
+ },
16
+ "secretName": "sql-admin-password"
17
+ }
18
+ },
19
+ "containerImage": {
20
+ "value": "{{ACR_NAME}}.azurecr.io/{{APP_NAME}}:latest"
21
+ }
22
+ }
23
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
3
+ "contentVersion": "1.0.0.0",
4
+ "parameters": {
5
+ "environment": {
6
+ "value": "prod"
7
+ },
8
+ "appName": {
9
+ "value": "{{APP_NAME}}"
10
+ },
11
+ "sqlAdminPassword": {
12
+ "reference": {
13
+ "keyVault": {
14
+ "id": "/subscriptions/{{SUBSCRIPTION_ID}}/resourceGroups/{{RESOURCE_GROUP}}/providers/Microsoft.KeyVault/vaults/{{KEY_VAULT_NAME}}"
15
+ },
16
+ "secretName": "sql-admin-password"
17
+ }
18
+ },
19
+ "containerImage": {
20
+ "value": "{{ACR_NAME}}.azurecr.io/{{APP_NAME}}:{{VERSION}}"
21
+ }
22
+ }
23
+ }
@@ -0,0 +1,103 @@
1
+ // ==============================================================================
2
+ // MORPH-SPEC - SQL Database
3
+ // Azure SQL Server with Database (supports Free tier)
4
+ // ==============================================================================
5
+
6
+ @description('SQL Server name')
7
+ param serverName string
8
+
9
+ @description('Database name')
10
+ param databaseName string
11
+
12
+ @description('Location')
13
+ param location string
14
+
15
+ @description('Tags')
16
+ param tags object = {}
17
+
18
+ @description('Admin username')
19
+ param adminUsername string = 'sqladmin'
20
+
21
+ @description('Admin password')
22
+ @secure()
23
+ param adminPassword string
24
+
25
+ @description('Use free tier (32GB, limited DTUs)')
26
+ param useFree bool = true
27
+
28
+ // ==============================================================================
29
+ // SQL SERVER
30
+ // ==============================================================================
31
+
32
+ resource sqlServer 'Microsoft.Sql/servers@2023-05-01-preview' = {
33
+ name: serverName
34
+ location: location
35
+ tags: tags
36
+ properties: {
37
+ administratorLogin: adminUsername
38
+ administratorLoginPassword: adminPassword
39
+ version: '12.0'
40
+ minimalTlsVersion: '1.2'
41
+ publicNetworkAccess: 'Enabled'
42
+ }
43
+ }
44
+
45
+ // ==============================================================================
46
+ // SQL DATABASE
47
+ // ==============================================================================
48
+
49
+ resource sqlDatabase 'Microsoft.Sql/servers/databases@2023-05-01-preview' = {
50
+ parent: sqlServer
51
+ name: databaseName
52
+ location: location
53
+ tags: tags
54
+ sku: useFree ? {
55
+ name: 'Free'
56
+ tier: 'Free'
57
+ } : {
58
+ name: 'Basic'
59
+ tier: 'Basic'
60
+ capacity: 5
61
+ }
62
+ properties: {
63
+ collation: 'SQL_Latin1_General_CP1_CI_AS'
64
+ maxSizeBytes: useFree ? 32212254720 : 2147483648 // 32GB free, 2GB basic
65
+ catalogCollation: 'SQL_Latin1_General_CP1_CI_AS'
66
+ zoneRedundant: false
67
+ readScale: 'Disabled'
68
+ requestedBackupStorageRedundancy: 'Local'
69
+ }
70
+ }
71
+
72
+ // ==============================================================================
73
+ // FIREWALL RULES
74
+ // ==============================================================================
75
+
76
+ // Allow Azure services
77
+ resource firewallAzure 'Microsoft.Sql/servers/firewallRules@2023-05-01-preview' = {
78
+ parent: sqlServer
79
+ name: 'AllowAllAzureIps'
80
+ properties: {
81
+ startIpAddress: '0.0.0.0'
82
+ endIpAddress: '0.0.0.0'
83
+ }
84
+ }
85
+
86
+ // ==============================================================================
87
+ // OUTPUTS
88
+ // ==============================================================================
89
+
90
+ @description('SQL Server ID')
91
+ output serverId string = sqlServer.id
92
+
93
+ @description('SQL Server FQDN')
94
+ output serverFqdn string = sqlServer.properties.fullyQualifiedDomainName
95
+
96
+ @description('Database ID')
97
+ output databaseId string = sqlDatabase.id
98
+
99
+ @description('Connection string (password placeholder)')
100
+ output connectionString string = 'Server=tcp:${sqlServer.properties.fullyQualifiedDomainName},1433;Database=${databaseName};User ID=${adminUsername};Password=${adminPassword};Encrypt=true;TrustServerCertificate=false;Connection Timeout=30;'
101
+
102
+ @description('Connection string template (no password)')
103
+ output connectionStringTemplate string = 'Server=tcp:${sqlServer.properties.fullyQualifiedDomainName},1433;Database=${databaseName};User ID=${adminUsername};Password={your_password};Encrypt=true;TrustServerCertificate=false;Connection Timeout=30;'
@@ -0,0 +1,106 @@
1
+ // ==============================================================================
2
+ // MORPH-SPEC - Storage Account
3
+ // Azure Storage Account with Blob containers
4
+ // ==============================================================================
5
+
6
+ @description('Storage account name (lowercase, no hyphens, 3-24 chars)')
7
+ @minLength(3)
8
+ @maxLength(24)
9
+ param name string
10
+
11
+ @description('Location')
12
+ param location string
13
+
14
+ @description('Tags')
15
+ param tags object = {}
16
+
17
+ @description('SKU (Standard_LRS, Standard_GRS, Standard_ZRS)')
18
+ @allowed(['Standard_LRS', 'Standard_GRS', 'Standard_ZRS', 'Premium_LRS'])
19
+ param sku string = 'Standard_LRS'
20
+
21
+ @description('Create default blob containers')
22
+ param createContainers bool = true
23
+
24
+ // ==============================================================================
25
+ // STORAGE ACCOUNT
26
+ // ==============================================================================
27
+
28
+ resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
29
+ name: name
30
+ location: location
31
+ tags: tags
32
+ kind: 'StorageV2'
33
+ sku: {
34
+ name: sku
35
+ }
36
+ properties: {
37
+ accessTier: 'Hot'
38
+ allowBlobPublicAccess: false
39
+ allowSharedKeyAccess: true
40
+ minimumTlsVersion: 'TLS1_2'
41
+ supportsHttpsTrafficOnly: true
42
+ networkAcls: {
43
+ defaultAction: 'Allow'
44
+ bypass: 'AzureServices'
45
+ }
46
+ }
47
+ }
48
+
49
+ // ==============================================================================
50
+ // BLOB SERVICE
51
+ // ==============================================================================
52
+
53
+ resource blobService 'Microsoft.Storage/storageAccounts/blobServices@2023-01-01' = {
54
+ parent: storageAccount
55
+ name: 'default'
56
+ properties: {
57
+ deleteRetentionPolicy: {
58
+ enabled: true
59
+ days: 7
60
+ }
61
+ }
62
+ }
63
+
64
+ // ==============================================================================
65
+ // DEFAULT CONTAINERS
66
+ // ==============================================================================
67
+
68
+ resource uploadsContainer 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-01-01' = if (createContainers) {
69
+ parent: blobService
70
+ name: 'uploads'
71
+ properties: {
72
+ publicAccess: 'None'
73
+ }
74
+ }
75
+
76
+ resource reportsContainer 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-01-01' = if (createContainers) {
77
+ parent: blobService
78
+ name: 'reports'
79
+ properties: {
80
+ publicAccess: 'None'
81
+ }
82
+ }
83
+
84
+ resource backupsContainer 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-01-01' = if (createContainers) {
85
+ parent: blobService
86
+ name: 'backups'
87
+ properties: {
88
+ publicAccess: 'None'
89
+ }
90
+ }
91
+
92
+ // ==============================================================================
93
+ // OUTPUTS
94
+ // ==============================================================================
95
+
96
+ @description('Storage Account ID')
97
+ output id string = storageAccount.id
98
+
99
+ @description('Storage Account name')
100
+ output name string = storageAccount.name
101
+
102
+ @description('Primary blob endpoint')
103
+ output primaryBlobEndpoint string = storageAccount.properties.primaryEndpoints.blob
104
+
105
+ @description('Connection string')
106
+ output connectionString string = 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};AccountKey=${storageAccount.listKeys().keys[0].value};EndpointSuffix=core.windows.net'