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.
- 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/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__/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/machine/index.ts +7 -1
- package/src/mcp/index.ts +8 -2
|
@@ -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
|
+
}
|