swallowkit 1.0.2 → 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.
- package/dist/cli/commands/create-migration.d.ts +3 -0
- package/dist/cli/commands/create-migration.d.ts.map +1 -0
- package/dist/cli/commands/create-migration.js +16 -0
- package/dist/cli/commands/create-migration.js.map +1 -0
- package/dist/cli/commands/index.d.ts +2 -0
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +5 -1
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +2 -0
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/migrations.d.ts +3 -0
- package/dist/cli/commands/migrations.d.ts.map +1 -0
- package/dist/cli/commands/migrations.js +22 -0
- package/dist/cli/commands/migrations.js.map +1 -0
- package/dist/cli/commands/provision.d.ts +1 -1
- package/dist/cli/commands/provision.d.ts.map +1 -1
- package/dist/cli/commands/provision.js +95 -185
- package/dist/cli/commands/provision.js.map +1 -1
- package/dist/cli/commands/scaffold.d.ts.map +1 -1
- package/dist/cli/commands/scaffold.js +6 -130
- package/dist/cli/commands/scaffold.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +4 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/core/operations/migration-generator.d.ts +10 -0
- package/dist/core/operations/migration-generator.d.ts.map +1 -0
- package/dist/core/operations/migration-generator.js +194 -0
- package/dist/core/operations/migration-generator.js.map +1 -0
- package/dist/core/operations/provision-operations.d.ts +19 -0
- package/dist/core/operations/provision-operations.d.ts.map +1 -1
- package/dist/core/operations/provision-operations.js +299 -30
- package/dist/core/operations/provision-operations.js.map +1 -1
- package/dist/core/project/capabilities.js +1 -1
- package/dist/core/project/capabilities.js.map +1 -1
- package/dist/core/project/infra.d.ts +4 -0
- package/dist/core/project/infra.d.ts.map +1 -1
- package/dist/core/project/infra.js +19 -1
- package/dist/core/project/infra.js.map +1 -1
- package/dist/core/project/migrations.d.ts +53 -0
- package/dist/core/project/migrations.d.ts.map +1 -0
- package/dist/core/project/migrations.js +262 -0
- package/dist/core/project/migrations.js.map +1 -0
- package/dist/core/project/workflows.d.ts.map +1 -1
- package/dist/core/project/workflows.js +7 -3
- package/dist/core/project/workflows.js.map +1 -1
- package/dist/core/scaffold/native-schema-generator.d.ts.map +1 -1
- package/dist/core/scaffold/native-schema-generator.js +61 -13
- package/dist/core/scaffold/native-schema-generator.js.map +1 -1
- package/dist/machine/index.d.ts.map +1 -1
- package/dist/machine/index.js +6 -0
- package/dist/machine/index.js.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +11 -2
- package/dist/mcp/index.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/agent-loop-phase34.test.ts +139 -0
- package/src/__tests__/init.test.ts +8 -0
- package/src/__tests__/migration.test.ts +116 -0
- package/src/__tests__/plan-apply.test.ts +41 -11
- package/src/__tests__/scaffold.test.ts +7 -4
- package/src/cli/commands/create-migration.ts +13 -0
- package/src/cli/commands/index.ts +2 -0
- package/src/cli/commands/init.ts +2 -0
- package/src/cli/commands/migrations.ts +20 -0
- package/src/cli/commands/provision.ts +115 -184
- package/src/cli/commands/scaffold.ts +6 -145
- package/src/cli/index.ts +4 -0
- package/src/core/operations/migration-generator.ts +188 -0
- package/src/core/operations/provision-operations.ts +391 -44
- package/src/core/project/capabilities.ts +1 -1
- package/src/core/project/infra.ts +23 -1
- package/src/core/project/migrations.ts +274 -0
- package/src/core/project/workflows.ts +7 -3
- package/src/core/scaffold/native-schema-generator.ts +69 -13
- package/src/machine/index.ts +7 -1
- package/src/mcp/index.ts +8 -2
|
@@ -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
|
|
846
|
+
console.log("\n🗄️ Generating Cosmos DB container migration...");
|
|
846
847
|
|
|
847
|
-
const
|
|
848
|
-
|
|
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
|
-
|
|
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...>")
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import { MachineCommandError } from "../../machine/errors";
|
|
4
|
+
import { FileOperationSession } from "./file-session";
|
|
5
|
+
import {
|
|
6
|
+
InfrastructureMigration,
|
|
7
|
+
MIGRATION_MANIFEST_PATH,
|
|
8
|
+
MigrationManifest,
|
|
9
|
+
createMigrationManifest,
|
|
10
|
+
loadMigrationManifest,
|
|
11
|
+
saveMigrationManifest,
|
|
12
|
+
validateMigrationManifest,
|
|
13
|
+
} from "../project/migrations";
|
|
14
|
+
|
|
15
|
+
const GENERATOR = "infrastructure-migration";
|
|
16
|
+
|
|
17
|
+
function normalizeSlug(value: string): string {
|
|
18
|
+
const slug = value
|
|
19
|
+
.trim()
|
|
20
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1-$2")
|
|
21
|
+
.replace(/[^A-Za-z0-9]+/g, "-")
|
|
22
|
+
.replace(/^-|-$/g, "")
|
|
23
|
+
.toLowerCase();
|
|
24
|
+
if (!slug) {
|
|
25
|
+
throw new MachineCommandError("invalid-migration-slug", "Migration slug must contain letters or numbers.", undefined, "failed");
|
|
26
|
+
}
|
|
27
|
+
return slug;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function formatVersion(version: number): string {
|
|
31
|
+
return String(version).padStart(4, "0");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function readManifestForSession(session: FileOperationSession): MigrationManifest {
|
|
35
|
+
const manifestPath = path.join(session.rootDirectory, MIGRATION_MANIFEST_PATH);
|
|
36
|
+
if (!session.fileExists(manifestPath)) {
|
|
37
|
+
throw new MachineCommandError(
|
|
38
|
+
"migration-manifest-not-found",
|
|
39
|
+
`Migration manifest not found. Run "swallowkit migrations init --legacy-baseline" before scaffolding new models.`,
|
|
40
|
+
undefined,
|
|
41
|
+
"blocked"
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
const manifest = JSON.parse(session.readFile(manifestPath)) as MigrationManifest;
|
|
46
|
+
validateMigrationManifest(manifest, session.rootDirectory);
|
|
47
|
+
return manifest;
|
|
48
|
+
} catch (error) {
|
|
49
|
+
if (error instanceof MachineCommandError) throw error;
|
|
50
|
+
throw new MachineCommandError("invalid-migration-manifest", `${MIGRATION_MANIFEST_PATH} is not valid JSON.`, undefined, "blocked");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function appendMigration(manifest: MigrationManifest, migration: InfrastructureMigration): MigrationManifest {
|
|
55
|
+
return {
|
|
56
|
+
...manifest,
|
|
57
|
+
migrations: [...manifest.migrations, migration].sort((left, right) => left.version - right.version),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function initializeLegacyMigrations(projectRoot: string = process.cwd()): MigrationManifest {
|
|
62
|
+
const manifest = createMigrationManifest(projectRoot, { legacy: true });
|
|
63
|
+
saveMigrationManifest(manifest, projectRoot);
|
|
64
|
+
return manifest;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function initializeNewProjectMigrations(projectRoot: string): MigrationManifest {
|
|
68
|
+
const manifest = createMigrationManifest(projectRoot, { legacy: false });
|
|
69
|
+
saveMigrationManifest(manifest, projectRoot);
|
|
70
|
+
return manifest;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function createCustomMigration(slugValue: string, projectRoot: string = process.cwd()): InfrastructureMigration {
|
|
74
|
+
const manifest = loadMigrationManifest(projectRoot);
|
|
75
|
+
const slug = normalizeSlug(slugValue);
|
|
76
|
+
if (manifest.migrations.some((migration) => migration.slug === slug)) {
|
|
77
|
+
throw new MachineCommandError("migration-exists", `Migration slug already exists: ${slug}`, undefined, "blocked");
|
|
78
|
+
}
|
|
79
|
+
const version = manifest.migrations.length + 1;
|
|
80
|
+
const directory = path.join("infra", "migrations", `${formatVersion(version)}-${slug}`);
|
|
81
|
+
const template = path.join(directory, "main.bicep").replace(/\\/g, "/");
|
|
82
|
+
const absoluteTemplate = path.join(projectRoot, template);
|
|
83
|
+
if (fs.existsSync(absoluteTemplate)) {
|
|
84
|
+
throw new MachineCommandError("migration-exists", `Migration path already exists: ${template}`, undefined, "blocked");
|
|
85
|
+
}
|
|
86
|
+
const source = `targetScope = 'resourceGroup'
|
|
87
|
+
|
|
88
|
+
@description('SwallowKit project name')
|
|
89
|
+
param projectName string
|
|
90
|
+
|
|
91
|
+
@description('Azure resource location')
|
|
92
|
+
param location string
|
|
93
|
+
|
|
94
|
+
// Add only new or intentionally updated resources in this migration.
|
|
95
|
+
// Reference existing resources with the Bicep "existing" keyword.
|
|
96
|
+
`;
|
|
97
|
+
fs.mkdirSync(path.dirname(absoluteTemplate), { recursive: true });
|
|
98
|
+
fs.writeFileSync(absoluteTemplate, source, "utf-8");
|
|
99
|
+
|
|
100
|
+
const migration: InfrastructureMigration = { version, slug, template, source: "custom" };
|
|
101
|
+
const manifestPath = path.join(projectRoot, MIGRATION_MANIFEST_PATH);
|
|
102
|
+
fs.writeFileSync(manifestPath, `${JSON.stringify(appendMigration(manifest, migration), null, 2)}\n`, "utf-8");
|
|
103
|
+
return migration;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function generateModelContainerMigration(
|
|
107
|
+
model: { name: string; partitionKey: string },
|
|
108
|
+
session: FileOperationSession = new FileOperationSession("commit")
|
|
109
|
+
): InfrastructureMigration {
|
|
110
|
+
const manifest = readManifestForSession(session);
|
|
111
|
+
const existing = manifest.migrations.find((migration) => migration.source === "model" && migration.sourceModel === model.name);
|
|
112
|
+
const slug = normalizeSlug(`create-${model.name}-container`);
|
|
113
|
+
const version = existing?.version ?? manifest.migrations.length + 1;
|
|
114
|
+
const directory = path.join("infra", "migrations", `${formatVersion(version)}-${slug}`);
|
|
115
|
+
const template = (existing?.template ?? path.join(directory, "main.bicep")).replace(/\\/g, "/");
|
|
116
|
+
const containerName = `${model.name}s`;
|
|
117
|
+
const bicep = `targetScope = 'resourceGroup'
|
|
118
|
+
|
|
119
|
+
@description('SwallowKit project name')
|
|
120
|
+
param projectName string
|
|
121
|
+
|
|
122
|
+
@description('Azure resource location')
|
|
123
|
+
param location string
|
|
124
|
+
|
|
125
|
+
resource cosmosAccount 'Microsoft.DocumentDB/databaseAccounts@2023-11-15' existing = {
|
|
126
|
+
name: 'cosmos-\${projectName}'
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
resource database 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2023-11-15' existing = {
|
|
130
|
+
parent: cosmosAccount
|
|
131
|
+
name: '\${projectName}Database'
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
resource container 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2023-11-15' = {
|
|
135
|
+
parent: database
|
|
136
|
+
name: '${containerName}'
|
|
137
|
+
properties: {
|
|
138
|
+
resource: {
|
|
139
|
+
id: '${containerName}'
|
|
140
|
+
partitionKey: {
|
|
141
|
+
paths: [
|
|
142
|
+
'${model.partitionKey}'
|
|
143
|
+
]
|
|
144
|
+
kind: 'Hash'
|
|
145
|
+
}
|
|
146
|
+
indexingPolicy: {
|
|
147
|
+
automatic: true
|
|
148
|
+
indexingMode: 'consistent'
|
|
149
|
+
includedPaths: [
|
|
150
|
+
{ path: '/*' }
|
|
151
|
+
]
|
|
152
|
+
excludedPaths: [
|
|
153
|
+
{ path: '/_etag/?' }
|
|
154
|
+
]
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
output containerName string = container.name
|
|
161
|
+
`;
|
|
162
|
+
const absoluteTemplate = path.join(session.rootDirectory, template);
|
|
163
|
+
if (existing && session.fileExists(absoluteTemplate) && session.readFile(absoluteTemplate) !== bicep) {
|
|
164
|
+
throw new MachineCommandError(
|
|
165
|
+
"applied-migration-immutable",
|
|
166
|
+
`The generated migration for model ${model.name} differs from the current schema. Create a new explicit migration instead of changing container properties.`,
|
|
167
|
+
{ version: existing.version, template },
|
|
168
|
+
"blocked"
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
session.writeFile(absoluteTemplate, bicep, { ownership: "generated-once", generator: GENERATOR, sourceModel: model.name });
|
|
172
|
+
|
|
173
|
+
const migration: InfrastructureMigration = existing ?? {
|
|
174
|
+
version,
|
|
175
|
+
slug,
|
|
176
|
+
template,
|
|
177
|
+
source: "model",
|
|
178
|
+
sourceModel: model.name,
|
|
179
|
+
};
|
|
180
|
+
if (!existing) {
|
|
181
|
+
session.writeFile(
|
|
182
|
+
path.join(session.rootDirectory, MIGRATION_MANIFEST_PATH),
|
|
183
|
+
`${JSON.stringify(appendMigration(manifest, migration), null, 2)}\n`,
|
|
184
|
+
{ ownership: "metadata", generator: GENERATOR, sourceModel: model.name }
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
return migration;
|
|
188
|
+
}
|