swallowkit 1.0.3 → 1.1.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 (72) hide show
  1. package/dist/cli/commands/create-migration.d.ts +3 -0
  2. package/dist/cli/commands/create-migration.d.ts.map +1 -0
  3. package/dist/cli/commands/create-migration.js +16 -0
  4. package/dist/cli/commands/create-migration.js.map +1 -0
  5. package/dist/cli/commands/index.d.ts +2 -0
  6. package/dist/cli/commands/index.d.ts.map +1 -1
  7. package/dist/cli/commands/index.js +5 -1
  8. package/dist/cli/commands/index.js.map +1 -1
  9. package/dist/cli/commands/init.d.ts.map +1 -1
  10. package/dist/cli/commands/init.js +2 -0
  11. package/dist/cli/commands/init.js.map +1 -1
  12. package/dist/cli/commands/migrations.d.ts +3 -0
  13. package/dist/cli/commands/migrations.d.ts.map +1 -0
  14. package/dist/cli/commands/migrations.js +22 -0
  15. package/dist/cli/commands/migrations.js.map +1 -0
  16. package/dist/cli/commands/provision.d.ts +1 -1
  17. package/dist/cli/commands/provision.d.ts.map +1 -1
  18. package/dist/cli/commands/provision.js +95 -185
  19. package/dist/cli/commands/provision.js.map +1 -1
  20. package/dist/cli/commands/scaffold.d.ts.map +1 -1
  21. package/dist/cli/commands/scaffold.js +6 -130
  22. package/dist/cli/commands/scaffold.js.map +1 -1
  23. package/dist/cli/index.d.ts.map +1 -1
  24. package/dist/cli/index.js +4 -0
  25. package/dist/cli/index.js.map +1 -1
  26. package/dist/core/operations/migration-generator.d.ts +10 -0
  27. package/dist/core/operations/migration-generator.d.ts.map +1 -0
  28. package/dist/core/operations/migration-generator.js +194 -0
  29. package/dist/core/operations/migration-generator.js.map +1 -0
  30. package/dist/core/operations/provision-operations.d.ts +19 -0
  31. package/dist/core/operations/provision-operations.d.ts.map +1 -1
  32. package/dist/core/operations/provision-operations.js +299 -30
  33. package/dist/core/operations/provision-operations.js.map +1 -1
  34. package/dist/core/project/capabilities.js +1 -1
  35. package/dist/core/project/capabilities.js.map +1 -1
  36. package/dist/core/project/infra.d.ts +4 -0
  37. package/dist/core/project/infra.d.ts.map +1 -1
  38. package/dist/core/project/infra.js +19 -1
  39. package/dist/core/project/infra.js.map +1 -1
  40. package/dist/core/project/migrations.d.ts +53 -0
  41. package/dist/core/project/migrations.d.ts.map +1 -0
  42. package/dist/core/project/migrations.js +262 -0
  43. package/dist/core/project/migrations.js.map +1 -0
  44. package/dist/core/project/workflows.d.ts.map +1 -1
  45. package/dist/core/project/workflows.js +7 -3
  46. package/dist/core/project/workflows.js.map +1 -1
  47. package/dist/machine/index.d.ts.map +1 -1
  48. package/dist/machine/index.js +6 -0
  49. package/dist/machine/index.js.map +1 -1
  50. package/dist/mcp/index.d.ts.map +1 -1
  51. package/dist/mcp/index.js +11 -2
  52. package/dist/mcp/index.js.map +1 -1
  53. package/package.json +1 -1
  54. package/src/__tests__/agent-loop-phase34.test.ts +139 -0
  55. package/src/__tests__/init.test.ts +8 -0
  56. package/src/__tests__/migration.test.ts +116 -0
  57. package/src/__tests__/scaffold.test.ts +7 -4
  58. package/src/cli/commands/create-migration.ts +13 -0
  59. package/src/cli/commands/index.ts +2 -0
  60. package/src/cli/commands/init.ts +2 -0
  61. package/src/cli/commands/migrations.ts +20 -0
  62. package/src/cli/commands/provision.ts +115 -184
  63. package/src/cli/commands/scaffold.ts +6 -145
  64. package/src/cli/index.ts +4 -0
  65. package/src/core/operations/migration-generator.ts +188 -0
  66. package/src/core/operations/provision-operations.ts +391 -44
  67. package/src/core/project/capabilities.ts +1 -1
  68. package/src/core/project/infra.ts +23 -1
  69. package/src/core/project/migrations.ts +274 -0
  70. package/src/core/project/workflows.ts +7 -3
  71. package/src/machine/index.ts +7 -1
  72. package/src/mcp/index.ts +8 -2
@@ -1,193 +1,124 @@
1
- import { Command } from 'commander';
2
- import { execSync } from 'child_process';
3
- import * as fs from 'fs';
4
- import * as path from 'path';
5
- import prompts from 'prompts';
6
- import { ensureSwallowKitProject } from '../../core/config';
7
-
8
- export const provisionCommand = new Command('provision')
9
- .description('Provision Azure resources using Bicep templates')
10
- .requiredOption('-g, --resource-group <name>', 'Resource group name')
11
- .option('--subscription <id>', 'Azure subscription ID (optional)')
12
- .action(async (options) => {
13
- // SwallowKit プロジェクトディレクトリかどうかを検証
1
+ import { Command } from "commander";
2
+ import prompts from "prompts";
3
+ import { ensureSwallowKitProject } from "../../core/config";
4
+ import { applyProvisionOperation, planProvisionOperation } from "../../core/operations/provision-operations";
5
+
6
+ const PRIMARY_REGIONS = [
7
+ { title: "Japan East (japaneast)", value: "japaneast" },
8
+ { title: "Japan West (japanwest)", value: "japanwest" },
9
+ { title: "East Asia (eastasia)", value: "eastasia" },
10
+ { title: "Southeast Asia (southeastasia)", value: "southeastasia" },
11
+ { title: "East US (eastus)", value: "eastus" },
12
+ { title: "East US 2 (eastus2)", value: "eastus2" },
13
+ { title: "West US 2 (westus2)", value: "westus2" },
14
+ { title: "Central US (centralus)", value: "centralus" },
15
+ { title: "West Europe (westeurope)", value: "westeurope" },
16
+ ];
17
+
18
+ const SWA_REGIONS = [
19
+ { title: "East Asia (eastasia) - Recommended for Japan", value: "eastasia" },
20
+ { title: "West US 2 (westus2)", value: "westus2" },
21
+ { title: "Central US (centralus)", value: "centralus" },
22
+ { title: "East US 2 (eastus2)", value: "eastus2" },
23
+ { title: "West Europe (westeurope)", value: "westeurope" },
24
+ ];
25
+
26
+ interface ProvisionCliOptions {
27
+ resourceGroup: string;
28
+ subscription?: string;
29
+ location?: string;
30
+ swaLocation?: string;
31
+ whatIf?: boolean;
32
+ adoptExisting?: boolean;
33
+ baseline?: string;
34
+ reconcile?: boolean;
35
+ approve?: boolean;
36
+ }
37
+
38
+ async function resolveLocations(options: ProvisionCliOptions): Promise<{ location: string; swaLocation: string }> {
39
+ if (options.location && options.swaLocation) {
40
+ return { location: options.location, swaLocation: options.swaLocation };
41
+ }
42
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
43
+ throw new Error("Non-interactive provision requires --location and --swa-location.");
44
+ }
45
+ const answer = await prompts([
46
+ {
47
+ type: options.location ? null : "select",
48
+ name: "location",
49
+ message: "Primary location for Functions and Cosmos DB",
50
+ choices: PRIMARY_REGIONS,
51
+ initial: 0,
52
+ },
53
+ {
54
+ type: options.swaLocation ? null : "select",
55
+ name: "swaLocation",
56
+ message: "Static Web App location",
57
+ choices: SWA_REGIONS,
58
+ initial: 0,
59
+ },
60
+ ]);
61
+ const location = options.location ?? answer.location;
62
+ const swaLocation = options.swaLocation ?? answer.swaLocation;
63
+ if (!location || !swaLocation) throw new Error("Region selection cancelled.");
64
+ return { location, swaLocation };
65
+ }
66
+
67
+ export const provisionCommand = new Command("provision")
68
+ .description("Bootstrap, adopt, migrate, or explicitly reconcile Azure resources")
69
+ .requiredOption("-g, --resource-group <name>", "Resource group name")
70
+ .option("--subscription <id>", "Azure subscription ID")
71
+ .option("--location <region>", "Primary location for Functions and Cosmos DB")
72
+ .option("--swa-location <region>", "Static Web App location")
73
+ .option("--what-if", "Include Azure what-if evidence", false)
74
+ .option("--adopt-existing", "Adopt an existing untagged resource group without deploying main.bicep", false)
75
+ .option("--baseline <version>", "Baseline version for adoption (currently 0)")
76
+ .option("--reconcile", "Explicitly redeploy infra/main.bicep", false)
77
+ .option("--approve", "Apply the displayed plan without an interactive confirmation", false)
78
+ .action(async (options: ProvisionCliOptions) => {
14
79
  ensureSwallowKitProject("provision");
15
-
16
- console.log('🚀 Starting Azure resource provisioning...\n');
17
-
18
- // Check if Azure CLI is installed
19
- try {
20
- execSync('az --version', { stdio: 'ignore' });
21
- } catch {
22
- console.error('❌ Azure CLI is not installed. Please install it first: https://aka.ms/azure-cli');
23
- process.exit(1);
80
+ const locations = await resolveLocations(options);
81
+ const baseline = options.baseline === undefined ? undefined : Number(options.baseline);
82
+ const plan = await planProvisionOperation({
83
+ resourceGroup: options.resourceGroup,
84
+ subscription: options.subscription,
85
+ location: locations.location,
86
+ swaLocation: locations.swaLocation,
87
+ whatIf: options.whatIf,
88
+ adoptExisting: options.adoptExisting,
89
+ baseline,
90
+ reconcile: options.reconcile,
91
+ });
92
+
93
+ console.log(`Provision mode: ${plan.mode}`);
94
+ for (const warning of plan.warnings) console.warn(`Warning: ${warning}`);
95
+ if (plan.pendingMigrations.length > 0) {
96
+ console.log(`Pending migrations: ${plan.pendingMigrations.map((migration) => `${migration.version}-${migration.slug}`).join(", ")}`);
24
97
  }
98
+ for (const command of plan.commands) console.log(` ${command}`);
25
99
 
26
- // Check if Bicep files exist
27
- const infraDir = path.join(process.cwd(), 'infra');
28
- const mainBicepPath = path.join(infraDir, 'main.bicep');
29
- const parametersPath = path.join(infraDir, 'main.parameters.json');
30
-
31
- if (!fs.existsSync(mainBicepPath)) {
32
- console.error('❌ Bicep files not found. Run "swallowkit init" first to generate infrastructure files.');
33
- process.exit(1);
100
+ if (plan.mode === "adoption-required") {
101
+ throw new Error('Existing resource group requires: migrations init --legacy-baseline, then provision --adopt-existing --baseline 0 --what-if.');
34
102
  }
35
103
 
36
- try {
37
- // Prompt for region selection
38
- console.log('📍 Select Azure regions for your resources:\n');
39
-
40
- const regionChoices = await prompts([
41
- {
42
- type: 'select',
43
- name: 'primaryLocation',
44
- message: 'Primary location for Functions and Cosmos DB',
45
- choices: [
46
- { title: 'Japan East (japaneast)', value: 'japaneast' },
47
- { title: 'Japan West (japanwest)', value: 'japanwest' },
48
- { title: 'East Asia (eastasia)', value: 'eastasia' },
49
- { title: 'Southeast Asia (southeastasia)', value: 'southeastasia' },
50
- { title: 'East US (eastus)', value: 'eastus' },
51
- { title: 'East US 2 (eastus2)', value: 'eastus2' },
52
- { title: 'West US 2 (westus2)', value: 'westus2' },
53
- { title: 'Central US (centralus)', value: 'centralus' },
54
- { title: 'West Europe (westeurope)', value: 'westeurope' },
55
- ],
56
- initial: 0, // Default to japaneast
57
- },
58
- {
59
- type: 'select',
60
- name: 'swaLocation',
61
- message: 'Static Web App location (limited availability)',
62
- choices: [
63
- { title: 'East Asia (eastasia) - Recommended for Japan', value: 'eastasia' },
64
- { title: 'West US 2 (westus2)', value: 'westus2' },
65
- { title: 'Central US (centralus)', value: 'centralus' },
66
- { title: 'East US 2 (eastus2)', value: 'eastus2' },
67
- { title: 'West Europe (westeurope)', value: 'westeurope' },
68
- ],
69
- initial: 0, // Default to eastasia
70
- },
71
- ], {
72
- onCancel: () => {
73
- throw new Error('User cancelled');
74
- }
75
- });
76
-
77
- if (!regionChoices.primaryLocation || !regionChoices.swaLocation) {
78
- console.log('\n❌ Region selection cancelled.');
79
- process.exit(1);
104
+ let approved = options.approve === true;
105
+ if (!approved) {
106
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
107
+ throw new Error("Non-interactive provision requires --approve after reviewing the plan.");
80
108
  }
81
-
82
- const primaryLocation = regionChoices.primaryLocation;
83
- const swaLocation = regionChoices.swaLocation;
84
-
85
- console.log(`\n✓ Primary location: ${primaryLocation}`);
86
- console.log(`✓ Static Web App location: ${swaLocation}`);
87
-
88
- // Confirmation prompt to prevent accidental wrong selection
89
- const confirmation = await prompts({
90
- type: 'confirm',
91
- name: 'proceed',
92
- message: `Proceed with deployment to ${primaryLocation} (primary) and ${swaLocation} (SWA)?`,
93
- initial: true
94
- }, {
95
- onCancel: () => {
96
- throw new Error('User cancelled');
97
- }
109
+ const answer = await prompts({
110
+ type: "confirm",
111
+ name: "approved",
112
+ message: `Apply the ${plan.mode} plan?`,
113
+ initial: false,
98
114
  });
99
-
100
- if (!confirmation.proceed) {
101
- console.log('\n❌ Deployment cancelled by user.');
102
- process.exit(0);
103
- }
104
-
105
- console.log('');
106
- // Set subscription if provided
107
- if (options.subscription) {
108
- console.log(`🔧 Setting subscription: ${options.subscription}`);
109
- execSync(`az account set --subscription ${options.subscription}`, { stdio: 'inherit' });
110
- }
111
-
112
- // Create resource group if it doesn't exist (use primary location)
113
- console.log(`🔧 Ensuring resource group exists: ${options.resourceGroup}`);
114
- execSync(
115
- `az group create --name ${options.resourceGroup} --location ${primaryLocation}`,
116
- { stdio: 'inherit' }
117
- );
118
-
119
- // Deploy Bicep template with both locations
120
- console.log('\n📦 Deploying resources (this may take several minutes)...\n');
121
- const deployCommand = `az deployment group create --resource-group ${options.resourceGroup} --template-file "${mainBicepPath}" --parameters "${parametersPath}" --parameters location=${primaryLocation} --parameters swaLocation=${swaLocation}`;
122
-
123
- const output = execSync(deployCommand, { encoding: 'utf-8', stdio: 'pipe' });
124
- const deployment = JSON.parse(output);
125
-
126
- // Display outputs
127
- console.log('\n✅ Deployment completed successfully!\n');
128
- console.log('📋 Resource Information:');
129
-
130
- let swaName = '<swa-name>';
131
- let functionAppName = '<function-name>';
132
- if (deployment.properties?.outputs) {
133
- const outputs = deployment.properties.outputs;
134
- if (outputs.staticWebAppName) {
135
- swaName = outputs.staticWebAppName.value;
136
- console.log(` - Static Web App: ${swaName}`);
137
- console.log(` - URL: https://${outputs.staticWebAppUrl.value}`);
138
- }
139
- if (outputs.functionsAppName) {
140
- functionAppName = outputs.functionsAppName.value;
141
- console.log(` - Function App: ${functionAppName}`);
142
- console.log(` - URL: https://${outputs.functionsAppUrl.value}`);
143
- }
144
- if (outputs.cosmosDbAccountName) {
145
- console.log(` - Cosmos DB: ${outputs.cosmosDbAccountName.value}`);
146
- console.log(` - Database: ${outputs.cosmosDatabaseName.value}`);
147
- }
148
- }
149
-
150
- // Next steps guidance
151
- console.log('\n📝 Next Steps:');
152
- console.log(' 1. Configure CI/CD secrets/variables:\n');
153
-
154
- // Fetch SWA deployment token
155
- console.log(' [AZURE_STATIC_WEB_APPS_API_TOKEN]');
156
- try {
157
- const swaToken = execSync(
158
- `az staticwebapp secrets list --name ${swaName} --resource-group ${options.resourceGroup} --query "properties.apiKey" -o tsv`,
159
- { encoding: 'utf-8', stdio: 'pipe' }
160
- ).trim();
161
- console.log(` ${swaToken}\n`);
162
- } catch {
163
- console.log(' ⚠️ Failed to retrieve. Run manually:');
164
- console.log(` az staticwebapp secrets list --name ${swaName} --resource-group ${options.resourceGroup} --query "properties.apiKey" -o tsv\n`);
165
- }
166
-
167
- // Function App name (already known)
168
- console.log(' [AZURE_FUNCTIONAPP_NAME]');
169
- console.log(` ${functionAppName}\n`);
170
-
171
- // Fetch Function App publish profile
172
- console.log(' [AZURE_FUNCTIONAPP_PUBLISH_PROFILE]');
173
- try {
174
- const publishProfile = execSync(
175
- `az webapp deployment list-publishing-profiles --name ${functionAppName} --resource-group ${options.resourceGroup} --xml`,
176
- { encoding: 'utf-8', stdio: 'pipe' }
177
- ).trim();
178
- console.log(` ${publishProfile}\n`);
179
- } catch {
180
- console.log(' ⚠️ Failed to retrieve. Run manually:');
181
- console.log(` az webapp deployment list-publishing-profiles --name ${functionAppName} --resource-group ${options.resourceGroup} --xml\n`);
182
- }
183
-
184
- console.log(' 2. Set up your CI/CD pipeline (GitHub Actions or Azure Pipelines)');
185
- console.log(' 3. Manually trigger the first deployment in your CI/CD pipeline');
186
- console.log(' (Automatic deployments will run on subsequent pushes)\n');
187
-
188
- } catch (error: any) {
189
- console.error('❌ Deployment failed:');
190
- console.error(error.message);
191
- process.exit(1);
115
+ approved = answer.approved === true;
116
+ }
117
+ if (!approved) {
118
+ console.log("Provision cancelled.");
119
+ return;
192
120
  }
121
+
122
+ const result = await applyProvisionOperation({ planId: plan.planId, approve: true });
123
+ console.log(`Provision completed (${plan.mode}). ${result.executedCommands.length} command(s) executed.`);
193
124
  });
@@ -50,6 +50,7 @@ import {
50
50
  getActiveFileSession,
51
51
  SchemaAnalysisSnapshot,
52
52
  } from "../../core/operations/file-session";
53
+ import { generateModelContainerMigration } from "../../core/operations/migration-generator";
53
54
  import { recordSessionOperations } from "../../core/project/artifacts";
54
55
 
55
56
  const SCAFFOLD_GENERATOR = "scaffold";
@@ -842,155 +843,15 @@ async function generateBFFRoutes(
842
843
  * Generate Cosmos DB container Bicep file
843
844
  */
844
845
  export async function generateCosmosContainer(modelInfo: any, session: FileOperationSession = new FileOperationSession("commit")): Promise<void> {
845
- console.log("\n🗄️ Generating Cosmos DB container Bicep file...");
846
+ console.log("\n🗄️ Generating Cosmos DB container migration...");
846
847
 
847
- const modelKebab = toKebabCase(modelInfo.name);
848
- const modelPascal = toPascalCase(modelInfo.name);
849
- const cwd = process.cwd();
850
-
851
- // Check if infra directory exists
852
- const infraDir = path.join(cwd, "infra");
853
- if (!fs.existsSync(infraDir)) {
848
+ const infraDir = path.join(process.cwd(), "infra");
849
+ if (!session.fileExists(infraDir) && !fs.existsSync(infraDir)) {
854
850
  console.log("ℹ️ infra directory not found. Skipping Cosmos DB container generation.");
855
851
  return;
856
852
  }
857
-
858
- // Generate container Bicep file
859
- const containersDir = path.join(infraDir, "containers");
860
- const containerFileName = `${modelKebab}-container.bicep`;
861
- const containerFilePath = path.join(containersDir, containerFileName);
862
-
863
- const bicepContent = `@description('Cosmos DB account name')
864
- param cosmosAccountName string
865
-
866
- @description('Database name')
867
- param databaseName string
868
-
869
- @description('Container name')
870
- param containerName string = '${modelPascal}s'
871
-
872
- @description('Partition key path')
873
- param partitionKeyPath string = '${modelInfo.partitionKey}'
874
-
875
- // Reference existing Cosmos DB account
876
- resource cosmosAccount 'Microsoft.DocumentDB/databaseAccounts@2023-11-15' existing = {
877
- name: cosmosAccountName
878
- }
879
-
880
- // Reference existing database
881
- resource database 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2023-11-15' existing = {
882
- parent: cosmosAccount
883
- name: databaseName
884
- }
885
-
886
- // Container for ${modelPascal}
887
- resource container 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2023-11-15' = {
888
- parent: database
889
- name: containerName
890
- properties: {
891
- resource: {
892
- id: containerName
893
- partitionKey: {
894
- paths: [
895
- partitionKeyPath
896
- ]
897
- kind: 'Hash'
898
- }
899
- indexingPolicy: {
900
- automatic: true
901
- indexingMode: 'consistent'
902
- includedPaths: [
903
- {
904
- path: '/*'
905
- }
906
- ]
907
- excludedPaths: [
908
- {
909
- path: '/_etag/?'
910
- }
911
- ]
912
- }
913
- }
914
- }
915
- }
916
-
917
- output containerName string = container.name
918
- `;
919
-
920
- // Write Bicep file (overwrite if exists)
921
- session.writeFile(containerFilePath, bicepContent, { ownership: "managed", generator: SCAFFOLD_GENERATOR, sourceModel: modelInfo.name });
922
- console.log(`✅ Created: ${containerFilePath}`);
923
-
924
- // Update main.bicep to include this container module
925
- await updateMainBicepWithContainer(modelKebab, modelPascal, session);
926
- }
927
-
928
- /**
929
- * Update main.bicep to include new container module
930
- */
931
- async function updateMainBicepWithContainer(modelKebab: string, modelPascal: string, session: FileOperationSession): Promise<void> {
932
- const cwd = process.cwd();
933
- const mainBicepPath = path.join(cwd, "infra", "main.bicep");
934
-
935
- if (!session.fileExists(mainBicepPath)) {
936
- console.log("ℹ️ main.bicep not found. Please manually add the container module.");
937
- return;
938
- }
939
-
940
- let mainBicepContent = session.readFile(mainBicepPath);
941
-
942
- // Check if container module already exists
943
- const containerModuleName = `${modelPascal.charAt(0).toLowerCase()}${modelPascal.slice(1)}Container`;
944
- if (mainBicepContent.includes(`module ${containerModuleName}`)) {
945
- console.log(`ℹ️ Container module '${containerModuleName}' already exists in main.bicep`);
946
- return;
947
- }
948
-
949
- // Find the position to insert the container module (after cosmosDb modules)
950
- // Look for the end of both cosmosDb modules (FreeTier and Serverless)
951
- const cosmosModulePattern = /module cosmosDbServerless ['"]modules\/cosmosdb-serverless\.bicep['"] = if \(cosmosDbMode == 'serverless'\) \{[\s\S]*?\n\}/;
952
- const cosmosModuleMatch = mainBicepContent.match(cosmosModulePattern);
953
-
954
- if (!cosmosModuleMatch) {
955
- console.log("⚠️ Could not find Cosmos DB Serverless module in main.bicep. Please manually add the container module:");
956
- console.log(`\nmodule ${containerModuleName} 'containers/${modelKebab}-container.bicep' = {
957
- name: '${modelKebab}-container'
958
- params: {
959
- cosmosAccountName: cosmosDbMode == 'freetier' ? cosmosDbFreeTier.outputs.accountName : cosmosDbServerless.outputs.accountName
960
- databaseName: databaseName
961
- cosmosDbMode: cosmosDbMode
962
- }
963
- dependsOn: [
964
- cosmosDbFreeTier
965
- cosmosDbServerless
966
- ]
967
- }\n`);
968
- return;
969
- }
970
-
971
- // Find the end of the cosmosDbServerless module
972
- const insertPosition = cosmosModuleMatch.index! + cosmosModuleMatch[0].length;
973
-
974
- // Create the container module declaration
975
- const containerModule = `
976
-
977
- // ${modelPascal} Container
978
- module ${containerModuleName} 'containers/${modelKebab}-container.bicep' = {
979
- name: '${modelKebab}-container'
980
- params: {
981
- cosmosAccountName: cosmosDbMode == 'freetier' ? cosmosDbFreeTier.outputs.accountName : cosmosDbServerless.outputs.accountName
982
- databaseName: '\${projectName}Database'
983
- }
984
- }`;
985
-
986
- // Insert the module
987
- mainBicepContent =
988
- mainBicepContent.slice(0, insertPosition) +
989
- containerModule +
990
- mainBicepContent.slice(insertPosition);
991
-
992
- session.writeFile(mainBicepPath, mainBicepContent, { ownership: "extension-point", generator: SCAFFOLD_GENERATOR });
993
- console.log(`✅ Added container module to main.bicep`);
853
+ const migration = generateModelContainerMigration(modelInfo, session);
854
+ console.log(`✅ Created migration ${migration.version}: ${migration.template}`);
994
855
  }
995
856
 
996
857
  /**
package/src/cli/index.ts CHANGED
@@ -6,6 +6,8 @@ import * as path from "path";
6
6
  import { Command } from "commander";
7
7
  import { initCommand, devCommand, devSeedsCommand, scaffoldCommand, createModelCommand } from "./commands";
8
8
  import { provisionCommand } from "./commands/provision";
9
+ import { createMigrationCommand } from "./commands/create-migration";
10
+ import { migrationsCommand } from "./commands/migrations";
9
11
  import { addConnectorCommand } from "./commands/add-connector";
10
12
  import { addAuthCommand } from "./commands/add-auth";
11
13
  import { planScaffoldOperation } from "../core/operations/scaffold-plan";
@@ -133,6 +135,8 @@ export function createProgram(devCommandOverride: Command = devCommand): Command
133
135
  program.addCommand(devSeedsCommand);
134
136
 
135
137
  program.addCommand(provisionCommand);
138
+ program.addCommand(migrationsCommand);
139
+ program.addCommand(createMigrationCommand);
136
140
 
137
141
  program
138
142
  .command("create-model <names...>")