swallowkit 1.0.0-beta.9 → 1.0.1

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 (272) hide show
  1. package/README.ja.md +87 -181
  2. package/README.md +87 -238
  3. package/dist/__tests__/fixtures.d.ts +12 -0
  4. package/dist/__tests__/fixtures.d.ts.map +1 -1
  5. package/dist/__tests__/fixtures.js +101 -0
  6. package/dist/__tests__/fixtures.js.map +1 -1
  7. package/dist/cli/commands/add-auth.d.ts +15 -0
  8. package/dist/cli/commands/add-auth.d.ts.map +1 -0
  9. package/dist/cli/commands/add-auth.js +790 -0
  10. package/dist/cli/commands/add-auth.js.map +1 -0
  11. package/dist/cli/commands/add-connector.d.ts +20 -0
  12. package/dist/cli/commands/add-connector.d.ts.map +1 -0
  13. package/dist/cli/commands/add-connector.js +164 -0
  14. package/dist/cli/commands/add-connector.js.map +1 -0
  15. package/dist/cli/commands/cosmos-worktree-isolation.d.ts +25 -0
  16. package/dist/cli/commands/cosmos-worktree-isolation.d.ts.map +1 -0
  17. package/dist/cli/commands/cosmos-worktree-isolation.js +124 -0
  18. package/dist/cli/commands/cosmos-worktree-isolation.js.map +1 -0
  19. package/dist/cli/commands/create-model.d.ts +1 -4
  20. package/dist/cli/commands/create-model.d.ts.map +1 -1
  21. package/dist/cli/commands/create-model.js +20 -82
  22. package/dist/cli/commands/create-model.js.map +1 -1
  23. package/dist/cli/commands/dev-seeds.d.ts +27 -0
  24. package/dist/cli/commands/dev-seeds.d.ts.map +1 -1
  25. package/dist/cli/commands/dev-seeds.js +188 -6
  26. package/dist/cli/commands/dev-seeds.js.map +1 -1
  27. package/dist/cli/commands/dev.d.ts +30 -0
  28. package/dist/cli/commands/dev.d.ts.map +1 -1
  29. package/dist/cli/commands/dev.js +495 -129
  30. package/dist/cli/commands/dev.js.map +1 -1
  31. package/dist/cli/commands/init.d.ts +69 -1
  32. package/dist/cli/commands/init.d.ts.map +1 -1
  33. package/dist/cli/commands/init.js +788 -766
  34. package/dist/cli/commands/init.js.map +1 -1
  35. package/dist/cli/commands/scaffold.d.ts +24 -3
  36. package/dist/cli/commands/scaffold.d.ts.map +1 -1
  37. package/dist/cli/commands/scaffold.js +408 -189
  38. package/dist/cli/commands/scaffold.js.map +1 -1
  39. package/dist/cli/index.d.ts +5 -1
  40. package/dist/cli/index.d.ts.map +1 -1
  41. package/dist/cli/index.js +335 -58
  42. package/dist/cli/index.js.map +1 -1
  43. package/dist/core/config.d.ts +13 -3
  44. package/dist/core/config.d.ts.map +1 -1
  45. package/dist/core/config.js +151 -8
  46. package/dist/core/config.js.map +1 -1
  47. package/dist/core/mock/connector-mock-server.d.ts +101 -0
  48. package/dist/core/mock/connector-mock-server.d.ts.map +1 -0
  49. package/dist/core/mock/connector-mock-server.js +480 -0
  50. package/dist/core/mock/connector-mock-server.js.map +1 -0
  51. package/dist/core/mock/zod-mock-generator.d.ts +14 -0
  52. package/dist/core/mock/zod-mock-generator.d.ts.map +1 -0
  53. package/dist/core/mock/zod-mock-generator.js +163 -0
  54. package/dist/core/mock/zod-mock-generator.js.map +1 -0
  55. package/dist/core/operations/auth-operations.d.ts +61 -0
  56. package/dist/core/operations/auth-operations.d.ts.map +1 -0
  57. package/dist/core/operations/auth-operations.js +272 -0
  58. package/dist/core/operations/auth-operations.js.map +1 -0
  59. package/dist/core/operations/create-model.d.ts +15 -0
  60. package/dist/core/operations/create-model.d.ts.map +1 -0
  61. package/dist/core/operations/create-model.js +185 -0
  62. package/dist/core/operations/create-model.js.map +1 -0
  63. package/dist/core/operations/file-session.d.ts +56 -0
  64. package/dist/core/operations/file-session.d.ts.map +1 -0
  65. package/dist/core/operations/file-session.js +203 -0
  66. package/dist/core/operations/file-session.js.map +1 -0
  67. package/dist/core/operations/provision-operations.d.ts +52 -0
  68. package/dist/core/operations/provision-operations.d.ts.map +1 -0
  69. package/dist/core/operations/provision-operations.js +211 -0
  70. package/dist/core/operations/provision-operations.js.map +1 -0
  71. package/dist/core/operations/runtime.d.ts +37 -0
  72. package/dist/core/operations/runtime.d.ts.map +1 -0
  73. package/dist/core/operations/runtime.js +248 -0
  74. package/dist/core/operations/runtime.js.map +1 -0
  75. package/dist/core/operations/scaffold-apply.d.ts +21 -0
  76. package/dist/core/operations/scaffold-apply.d.ts.map +1 -0
  77. package/dist/core/operations/scaffold-apply.js +57 -0
  78. package/dist/core/operations/scaffold-apply.js.map +1 -0
  79. package/dist/core/operations/scaffold-machine.d.ts +16 -0
  80. package/dist/core/operations/scaffold-machine.d.ts.map +1 -0
  81. package/dist/core/operations/scaffold-machine.js +51 -0
  82. package/dist/core/operations/scaffold-machine.js.map +1 -0
  83. package/dist/core/operations/scaffold-plan.d.ts +43 -0
  84. package/dist/core/operations/scaffold-plan.d.ts.map +1 -0
  85. package/dist/core/operations/scaffold-plan.js +187 -0
  86. package/dist/core/operations/scaffold-plan.js.map +1 -0
  87. package/dist/core/project/agent-instructions.d.ts +29 -0
  88. package/dist/core/project/agent-instructions.d.ts.map +1 -0
  89. package/dist/core/project/agent-instructions.js +160 -0
  90. package/dist/core/project/agent-instructions.js.map +1 -0
  91. package/dist/core/project/artifacts.d.ts +58 -0
  92. package/dist/core/project/artifacts.d.ts.map +1 -0
  93. package/dist/core/project/artifacts.js +179 -0
  94. package/dist/core/project/artifacts.js.map +1 -0
  95. package/dist/core/project/boundaries.d.ts +50 -0
  96. package/dist/core/project/boundaries.d.ts.map +1 -0
  97. package/dist/core/project/boundaries.js +117 -0
  98. package/dist/core/project/boundaries.js.map +1 -0
  99. package/dist/core/project/capabilities.d.ts +58 -0
  100. package/dist/core/project/capabilities.d.ts.map +1 -0
  101. package/dist/core/project/capabilities.js +135 -0
  102. package/dist/core/project/capabilities.js.map +1 -0
  103. package/dist/core/project/drift.d.ts +29 -0
  104. package/dist/core/project/drift.d.ts.map +1 -0
  105. package/dist/core/project/drift.js +190 -0
  106. package/dist/core/project/drift.js.map +1 -0
  107. package/dist/core/project/infra.d.ts +49 -0
  108. package/dist/core/project/infra.d.ts.map +1 -0
  109. package/dist/core/project/infra.js +167 -0
  110. package/dist/core/project/infra.js.map +1 -0
  111. package/dist/core/project/manifest.d.ts +92 -0
  112. package/dist/core/project/manifest.d.ts.map +1 -0
  113. package/dist/core/project/manifest.js +321 -0
  114. package/dist/core/project/manifest.js.map +1 -0
  115. package/dist/core/project/state.d.ts +15 -0
  116. package/dist/core/project/state.d.ts.map +1 -0
  117. package/dist/core/project/state.js +106 -0
  118. package/dist/core/project/state.js.map +1 -0
  119. package/dist/core/project/validation.d.ts +20 -0
  120. package/dist/core/project/validation.d.ts.map +1 -0
  121. package/dist/core/project/validation.js +274 -0
  122. package/dist/core/project/validation.js.map +1 -0
  123. package/dist/core/project/workflows.d.ts +43 -0
  124. package/dist/core/project/workflows.d.ts.map +1 -0
  125. package/dist/core/project/workflows.js +299 -0
  126. package/dist/core/project/workflows.js.map +1 -0
  127. package/dist/core/scaffold/auth-generator.d.ts +63 -0
  128. package/dist/core/scaffold/auth-generator.d.ts.map +1 -0
  129. package/dist/core/scaffold/auth-generator.js +1774 -0
  130. package/dist/core/scaffold/auth-generator.js.map +1 -0
  131. package/dist/core/scaffold/connector-functions-generator.d.ts +41 -0
  132. package/dist/core/scaffold/connector-functions-generator.d.ts.map +1 -0
  133. package/dist/core/scaffold/connector-functions-generator.js +1029 -0
  134. package/dist/core/scaffold/connector-functions-generator.js.map +1 -0
  135. package/dist/core/scaffold/functions-generator.d.ts +4 -3
  136. package/dist/core/scaffold/functions-generator.d.ts.map +1 -1
  137. package/dist/core/scaffold/functions-generator.js +405 -134
  138. package/dist/core/scaffold/functions-generator.js.map +1 -1
  139. package/dist/core/scaffold/model-parser.d.ts +20 -0
  140. package/dist/core/scaffold/model-parser.d.ts.map +1 -1
  141. package/dist/core/scaffold/model-parser.js +229 -78
  142. package/dist/core/scaffold/model-parser.js.map +1 -1
  143. package/dist/core/scaffold/native-schema-generator.d.ts +19 -0
  144. package/dist/core/scaffold/native-schema-generator.d.ts.map +1 -0
  145. package/dist/core/scaffold/native-schema-generator.js +753 -0
  146. package/dist/core/scaffold/native-schema-generator.js.map +1 -0
  147. package/dist/core/scaffold/nextjs-generator.d.ts +8 -0
  148. package/dist/core/scaffold/nextjs-generator.d.ts.map +1 -1
  149. package/dist/core/scaffold/nextjs-generator.js +152 -8
  150. package/dist/core/scaffold/nextjs-generator.js.map +1 -1
  151. package/dist/core/scaffold/ui-generator.d.ts +17 -4
  152. package/dist/core/scaffold/ui-generator.d.ts.map +1 -1
  153. package/dist/core/scaffold/ui-generator.js +174 -34
  154. package/dist/core/scaffold/ui-generator.js.map +1 -1
  155. package/dist/core/verify/index.d.ts +69 -0
  156. package/dist/core/verify/index.d.ts.map +1 -0
  157. package/dist/core/verify/index.js +412 -0
  158. package/dist/core/verify/index.js.map +1 -0
  159. package/dist/machine/contracts.d.ts +24 -0
  160. package/dist/machine/contracts.d.ts.map +1 -0
  161. package/dist/machine/contracts.js +3 -0
  162. package/dist/machine/contracts.js.map +1 -0
  163. package/dist/machine/errors.d.ts +14 -0
  164. package/dist/machine/errors.d.ts.map +1 -0
  165. package/dist/machine/errors.js +42 -0
  166. package/dist/machine/errors.js.map +1 -0
  167. package/dist/machine/index.d.ts +3 -0
  168. package/dist/machine/index.d.ts.map +1 -0
  169. package/dist/machine/index.js +566 -0
  170. package/dist/machine/index.js.map +1 -0
  171. package/dist/mcp/index.d.ts +25 -0
  172. package/dist/mcp/index.d.ts.map +1 -0
  173. package/dist/mcp/index.js +425 -0
  174. package/dist/mcp/index.js.map +1 -0
  175. package/dist/types/index.d.ts +105 -0
  176. package/dist/types/index.d.ts.map +1 -1
  177. package/dist/utils/package-manager.d.ts +7 -11
  178. package/dist/utils/package-manager.d.ts.map +1 -1
  179. package/dist/utils/package-manager.js +42 -17
  180. package/dist/utils/package-manager.js.map +1 -1
  181. package/dist/utils/python-uv.d.ts +21 -0
  182. package/dist/utils/python-uv.d.ts.map +1 -0
  183. package/dist/utils/python-uv.js +111 -0
  184. package/dist/utils/python-uv.js.map +1 -0
  185. package/dist/version.d.ts +2 -0
  186. package/dist/version.d.ts.map +1 -0
  187. package/dist/version.js +49 -0
  188. package/dist/version.js.map +1 -0
  189. package/package.json +15 -17
  190. package/src/__tests__/__snapshots__/functions-generator.test.ts.snap +716 -22
  191. package/src/__tests__/__snapshots__/nextjs-generator.test.ts.snap +19 -7
  192. package/src/__tests__/__snapshots__/ui-generator.test.ts.snap +20 -12
  193. package/src/__tests__/agent-instructions.test.ts +237 -0
  194. package/src/__tests__/agent-loop-phase34.test.ts +619 -0
  195. package/src/__tests__/auth.test.ts +770 -0
  196. package/src/__tests__/bff-swa-auth.test.ts +275 -0
  197. package/src/__tests__/config.test.ts +176 -3
  198. package/src/__tests__/connector-functions-generator.test.ts +288 -0
  199. package/src/__tests__/connector-mock-server.test.ts +459 -0
  200. package/src/__tests__/connector-model-bff.test.ts +162 -0
  201. package/src/__tests__/cosmos-worktree-isolation.test.ts +149 -0
  202. package/src/__tests__/dev-seeds.test.ts +124 -0
  203. package/src/__tests__/dev.test.ts +254 -0
  204. package/src/__tests__/file-session.test.ts +98 -0
  205. package/src/__tests__/fixtures.ts +101 -0
  206. package/src/__tests__/functions-generator.test.ts +137 -1
  207. package/src/__tests__/init.test.ts +366 -2
  208. package/src/__tests__/jest-globals.d.ts +8 -0
  209. package/src/__tests__/machine.test.ts +371 -0
  210. package/src/__tests__/mcp.test.ts +253 -0
  211. package/src/__tests__/model-parser.test.ts +142 -0
  212. package/src/__tests__/multi-auth.test.ts +160 -0
  213. package/src/__tests__/nextjs-generator.test.ts +8 -0
  214. package/src/__tests__/package-manager.test.ts +59 -4
  215. package/src/__tests__/plan-apply.test.ts +499 -0
  216. package/src/__tests__/python-uv.test.ts +48 -0
  217. package/src/__tests__/scaffold.test.ts +287 -24
  218. package/src/__tests__/tsconfig.json +15 -0
  219. package/src/__tests__/ui-generator.test.ts +104 -0
  220. package/src/__tests__/zod-mock-generator.test.ts +132 -0
  221. package/src/cli/commands/add-auth.ts +853 -0
  222. package/src/cli/commands/add-connector.ts +159 -0
  223. package/src/cli/commands/cosmos-worktree-isolation.ts +130 -0
  224. package/src/cli/commands/create-model.ts +21 -100
  225. package/src/cli/commands/dev-seeds.ts +278 -7
  226. package/src/cli/commands/dev.ts +607 -160
  227. package/src/cli/commands/init.ts +851 -786
  228. package/src/cli/commands/scaffold.ts +531 -236
  229. package/src/cli/index.ts +338 -60
  230. package/src/core/config.ts +145 -9
  231. package/src/core/mock/connector-mock-server.ts +555 -0
  232. package/src/core/mock/zod-mock-generator.ts +205 -0
  233. package/src/core/operations/auth-operations.ts +360 -0
  234. package/src/core/operations/create-model.ts +191 -0
  235. package/src/core/operations/file-session.ts +222 -0
  236. package/src/core/operations/provision-operations.ts +297 -0
  237. package/src/core/operations/runtime.ts +258 -0
  238. package/src/core/operations/scaffold-apply.ts +106 -0
  239. package/src/core/operations/scaffold-machine.ts +77 -0
  240. package/src/core/operations/scaffold-plan.ts +215 -0
  241. package/src/core/project/agent-instructions.ts +167 -0
  242. package/src/core/project/agent-templates/AGENTS.md +504 -0
  243. package/src/core/project/agent-templates/CLAUDE.md +31 -0
  244. package/src/core/project/agent-templates/azure-functions.instructions.md +22 -0
  245. package/src/core/project/agent-templates/bff-routes.instructions.md +18 -0
  246. package/src/core/project/agent-templates/copilot-instructions.md +27 -0
  247. package/src/core/project/agent-templates/shared-models.instructions.md +72 -0
  248. package/src/core/project/artifacts.ts +205 -0
  249. package/src/core/project/boundaries.ts +170 -0
  250. package/src/core/project/capabilities.ts +193 -0
  251. package/src/core/project/drift.ts +203 -0
  252. package/src/core/project/infra.ts +196 -0
  253. package/src/core/project/manifest.ts +402 -0
  254. package/src/core/project/state.ts +77 -0
  255. package/src/core/project/validation.ts +295 -0
  256. package/src/core/project/workflows.ts +295 -0
  257. package/src/core/scaffold/auth-generator.ts +1824 -0
  258. package/src/core/scaffold/connector-functions-generator.ts +1133 -0
  259. package/src/core/scaffold/functions-generator.ts +432 -134
  260. package/src/core/scaffold/model-parser.ts +244 -90
  261. package/src/core/scaffold/native-schema-generator.ts +904 -0
  262. package/src/core/scaffold/nextjs-generator.ts +174 -9
  263. package/src/core/scaffold/ui-generator.ts +191 -36
  264. package/src/core/verify/index.ts +468 -0
  265. package/src/machine/contracts.ts +33 -0
  266. package/src/machine/errors.ts +45 -0
  267. package/src/machine/index.ts +690 -0
  268. package/src/mcp/index.ts +423 -0
  269. package/src/types/index.ts +137 -0
  270. package/src/utils/package-manager.ts +41 -19
  271. package/src/utils/python-uv.ts +96 -0
  272. package/src/version.ts +13 -0
@@ -4,6 +4,8 @@ import * as fs from "fs";
4
4
  import * as path from "path";
5
5
  import { ensureSwallowKitProject } from "../../core/config";
6
6
  import { FieldInfo, ModelInfo, getAllModels, toKebabCase } from "../../core/scaffold/model-parser";
7
+ import { resolveCosmosDatabaseNameForWorktree } from "./cosmos-worktree-isolation";
8
+ import type { GitCommandRunner } from "./cosmos-worktree-isolation";
7
9
 
8
10
  export type SeedDocument = Record<string, unknown>;
9
11
 
@@ -12,6 +14,7 @@ interface GenerateDevSeedTemplatesOptions {
12
14
  modelsDir?: string;
13
15
  seedsDir?: string;
14
16
  force?: boolean;
17
+ fromEmulator?: boolean;
15
18
  }
16
19
 
17
20
  interface ApplyDevSeedEnvironmentOptions {
@@ -29,8 +32,31 @@ interface LoadedSeedFile {
29
32
  filePath: string;
30
33
  }
31
34
 
35
+ export interface LocalCosmosConnectionInfo {
36
+ endpoint: string;
37
+ key: string;
38
+ databaseName: string;
39
+ worktreeIsolationEnabled?: boolean;
40
+ localSettingsPath: string;
41
+ }
42
+
43
+ export type ResolveLocalCosmosConnectionResult =
44
+ | { ok: true; value: LocalCosmosConnectionInfo }
45
+ | {
46
+ ok: false;
47
+ reason: "missing-local-settings" | "missing-connection-string" | "invalid-connection-string";
48
+ localSettingsPath: string;
49
+ };
50
+
51
+ interface ExportedContainerDocuments {
52
+ documents: SeedDocument[];
53
+ containerExists: boolean;
54
+ }
55
+
56
+ const COSMOS_SYSTEM_PROPERTIES = new Set(["_rid", "_self", "_etag", "_attachments", "_ts"]);
57
+
32
58
  export function getContainerNameForModel(model: Pick<ModelInfo, "name">): string {
33
- return `${model.name}s`;
59
+ return model.name.endsWith('s') ? model.name : `${model.name}s`;
34
60
  }
35
61
 
36
62
  export function getSeedEnvironmentDir(environment: string, seedsDir: string = "dev-seeds"): string {
@@ -65,6 +91,82 @@ export async function loadProjectModels(modelsDir: string = "shared/models"): Pr
65
91
  return getAllModels(modelsDir);
66
92
  }
67
93
 
94
+ export function buildDefaultCosmosDatabaseName(packageName: string): string {
95
+ const normalizedName = packageName.trim();
96
+ const baseName = normalizedName.length > 0 ? normalizedName : "App";
97
+ return `${baseName.charAt(0).toUpperCase()}${baseName.slice(1)}Database`;
98
+ }
99
+
100
+ export function getDefaultCosmosDatabaseName(projectDir: string = process.cwd()): string {
101
+ const packageJsonPath = path.join(projectDir, "package.json");
102
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")) as { name?: string };
103
+ return buildDefaultCosmosDatabaseName(packageJson.name || "App");
104
+ }
105
+
106
+ export function parseCosmosConnectionString(
107
+ connectionString: string
108
+ ): Pick<LocalCosmosConnectionInfo, "endpoint" | "key"> | null {
109
+ const endpointMatch = connectionString.match(/AccountEndpoint=([^;]+)/);
110
+ const keyMatch = connectionString.match(/AccountKey=([^;]+)/);
111
+
112
+ if (!endpointMatch || !keyMatch) {
113
+ return null;
114
+ }
115
+
116
+ return {
117
+ endpoint: endpointMatch[1],
118
+ key: keyMatch[1],
119
+ };
120
+ }
121
+
122
+ export function resolveLocalCosmosConnectionInfo(
123
+ defaultDatabaseName: string,
124
+ functionsDir: string = path.join(process.cwd(), "functions"),
125
+ worktreeOptions: {
126
+ cwd?: string;
127
+ runGit?: GitCommandRunner;
128
+ } = {}
129
+ ): ResolveLocalCosmosConnectionResult {
130
+ const localSettingsPath = path.join(functionsDir, "local.settings.json");
131
+ if (!fs.existsSync(localSettingsPath)) {
132
+ return { ok: false, reason: "missing-local-settings", localSettingsPath };
133
+ }
134
+
135
+ const localSettings = JSON.parse(fs.readFileSync(localSettingsPath, "utf-8")) as {
136
+ Values?: {
137
+ CosmosDBConnection?: string;
138
+ COSMOS_DB_DATABASE_NAME?: string;
139
+ };
140
+ };
141
+ const connectionString = localSettings.Values?.CosmosDBConnection;
142
+
143
+ if (!connectionString) {
144
+ return { ok: false, reason: "missing-connection-string", localSettingsPath };
145
+ }
146
+
147
+ const parsedConnection = parseCosmosConnectionString(connectionString);
148
+ if (!parsedConnection) {
149
+ return { ok: false, reason: "invalid-connection-string", localSettingsPath };
150
+ }
151
+
152
+ const baseDatabaseName = localSettings.Values?.COSMOS_DB_DATABASE_NAME || defaultDatabaseName;
153
+ const databaseNameResolution = resolveCosmosDatabaseNameForWorktree(
154
+ baseDatabaseName,
155
+ worktreeOptions.cwd,
156
+ worktreeOptions.runGit
157
+ );
158
+
159
+ return {
160
+ ok: true,
161
+ value: {
162
+ ...parsedConnection,
163
+ databaseName: databaseNameResolution.databaseName,
164
+ worktreeIsolationEnabled: databaseNameResolution.worktreeIsolationEnabled,
165
+ localSettingsPath,
166
+ },
167
+ };
168
+ }
169
+
68
170
  export function buildSeedTemplateDocument(
69
171
  model: ModelInfo,
70
172
  allModels: ModelInfo[] = [model],
@@ -135,7 +237,7 @@ export async function applyDevSeedEnvironment({
135
237
  console.log(`🧪 Applying Cosmos DB seed data for environment "${environment}"...`);
136
238
 
137
239
  for (const seedFile of seedFiles) {
138
- await recreateContainer(database, seedFile.containerName);
240
+ await recreateContainer(database, seedFile.containerName, seedFile.model.partitionKey);
139
241
  const container = database.container(seedFile.containerName);
140
242
 
141
243
  for (const document of seedFile.documents) {
@@ -197,14 +299,121 @@ export async function generateDevSeedTemplates({
197
299
  console.log(` 2. Run 'swallowkit dev --seed-env ${environment}' to replace emulator data before startup`);
198
300
  }
199
301
 
302
+ export function stripCosmosSystemProperties(document: SeedDocument): SeedDocument {
303
+ const sanitized: SeedDocument = {};
304
+
305
+ for (const [key, value] of Object.entries(document)) {
306
+ if (!COSMOS_SYSTEM_PROPERTIES.has(key)) {
307
+ sanitized[key] = value;
308
+ }
309
+ }
310
+
311
+ return sanitized;
312
+ }
313
+
314
+ export function prepareSeedDocumentsForExport(documents: SeedDocument[], filePath: string): SeedDocument[] {
315
+ const preparedDocuments = documents
316
+ .map((document) => stripCosmosSystemProperties(document))
317
+ .sort(compareSeedDocumentsForExport);
318
+
319
+ validateSeedDocuments(preparedDocuments, filePath);
320
+ return preparedDocuments;
321
+ }
322
+
323
+ export async function exportDevSeedsFromEmulator({
324
+ environment,
325
+ modelsDir = "shared/models",
326
+ seedsDir = "dev-seeds",
327
+ force = false,
328
+ }: GenerateDevSeedTemplatesOptions): Promise<void> {
329
+ ensureSwallowKitProject("create-dev-seeds");
330
+
331
+ console.log(`🧪 Exporting dev seeds from local Cosmos DB Emulator for environment "${environment}"...\n`);
332
+ const models = await loadProjectModels(modelsDir);
333
+
334
+ if (models.length === 0) {
335
+ console.log("⚠️ No schemas found under shared/models. Nothing was exported.");
336
+ return;
337
+ }
338
+
339
+ const connectionInfoResult = resolveLocalCosmosConnectionInfo(getDefaultCosmosDatabaseName());
340
+ if (!connectionInfoResult.ok) {
341
+ throw new Error(buildLocalCosmosConnectionError(connectionInfoResult));
342
+ }
343
+
344
+ const { endpoint, key, databaseName, localSettingsPath } = connectionInfoResult.value;
345
+ console.log(`🗄️ Using local Cosmos DB settings from ${localSettingsPath}`);
346
+ console.log(`📦 Export source database: "${databaseName}"\n`);
347
+
348
+ const client = new CosmosClient({ endpoint, key });
349
+ const database = client.database(databaseName);
350
+
351
+ try {
352
+ await database.read();
353
+ } catch (error: unknown) {
354
+ if (isCosmosNotFoundError(error)) {
355
+ throw new Error(`Cosmos DB database "${databaseName}" was not found in the local emulator.`);
356
+ }
357
+
358
+ throw error;
359
+ }
360
+
361
+ const environmentDir = getSeedEnvironmentDir(environment, seedsDir);
362
+ fs.mkdirSync(environmentDir, { recursive: true });
363
+
364
+ let writtenFiles = 0;
365
+ let skippedFiles = 0;
366
+ let totalDocuments = 0;
367
+
368
+ for (const model of models) {
369
+ const containerName = getContainerNameForModel(model);
370
+ const filePath = path.join(environmentDir, `${toKebabCase(model.name)}.json`);
371
+
372
+ if (!force && fs.existsSync(filePath)) {
373
+ skippedFiles += 1;
374
+ continue;
375
+ }
376
+
377
+ const exported = await readContainerSeedDocuments(database, containerName, filePath);
378
+ fs.writeFileSync(filePath, JSON.stringify(exported.documents, null, 2) + "\n", "utf-8");
379
+ writtenFiles += 1;
380
+ totalDocuments += exported.documents.length;
381
+
382
+ const detail = exported.containerExists
383
+ ? `${exported.documents.length} item(s)`
384
+ : "container not found; wrote empty seed";
385
+ console.log(`✅ Exported "${containerName}" to ${filePath} (${detail})`);
386
+ }
387
+
388
+ if (skippedFiles > 0) {
389
+ console.log("");
390
+ console.log(`⏭️ Skipped ${skippedFiles} existing file(s). Use --force to overwrite them.`);
391
+ }
392
+
393
+ console.log("");
394
+ console.log(`🧾 Export complete: ${writtenFiles} file(s), ${totalDocuments} item(s).`);
395
+ console.log(`📝 Next step: Run 'swallowkit dev --seed-env ${environment}' to replay the exported data.`);
396
+ }
397
+
200
398
  export const devSeedsCommand = new Command()
201
399
  .name("create-dev-seeds")
202
- .description("Generate dev seed JSON templates under dev-seeds/<environment> from shared/models schemas")
400
+ .description("Generate dev seed JSON files from shared/models schemas or export current local Cosmos DB Emulator data")
203
401
  .argument("<environment>", "Seed environment name")
204
402
  .option("--models-dir <dir>", "Models directory", "shared/models")
205
403
  .option("--seeds-dir <dir>", "Dev seeds directory", "dev-seeds")
206
404
  .option("--force", "Overwrite existing seed JSON files", false)
207
- .action(async (environment: string, options: { modelsDir?: string; seedsDir?: string; force?: boolean }) => {
405
+ .option("--from-emulator", "Export current data from the local Cosmos DB Emulator instead of generating templates", false)
406
+ .action(async (environment: string, options: { modelsDir?: string; seedsDir?: string; force?: boolean; fromEmulator?: boolean }) => {
407
+ if (options.fromEmulator) {
408
+ await exportDevSeedsFromEmulator({
409
+ environment,
410
+ modelsDir: options.modelsDir,
411
+ seedsDir: options.seedsDir,
412
+ force: options.force,
413
+ });
414
+ return;
415
+ }
416
+
208
417
  await generateDevSeedTemplates({
209
418
  environment,
210
419
  modelsDir: options.modelsDir,
@@ -326,7 +535,69 @@ function validateSeedDocuments(documents: SeedDocument[], filePath: string): voi
326
535
  });
327
536
  }
328
537
 
329
- async function recreateContainer(database: Database, containerName: string): Promise<void> {
538
+ function compareSeedDocumentsForExport(left: SeedDocument, right: SeedDocument): number {
539
+ const leftId = typeof left.id === "string" ? left.id : "";
540
+ const rightId = typeof right.id === "string" ? right.id : "";
541
+
542
+ if (leftId.length === 0 && rightId.length === 0) {
543
+ return 0;
544
+ }
545
+
546
+ if (leftId.length === 0) {
547
+ return 1;
548
+ }
549
+
550
+ if (rightId.length === 0) {
551
+ return -1;
552
+ }
553
+
554
+ return leftId.localeCompare(rightId);
555
+ }
556
+
557
+ function buildLocalCosmosConnectionError(
558
+ result: Exclude<ResolveLocalCosmosConnectionResult, { ok: true; value: LocalCosmosConnectionInfo }>
559
+ ): string {
560
+ switch (result.reason) {
561
+ case "missing-local-settings":
562
+ return `local.settings.json not found at ${result.localSettingsPath}. Start from a SwallowKit app with Azure Functions configured.`;
563
+ case "missing-connection-string":
564
+ return `CosmosDBConnection not found in ${result.localSettingsPath}.`;
565
+ case "invalid-connection-string":
566
+ return `Invalid CosmosDBConnection format in ${result.localSettingsPath}.`;
567
+ }
568
+
569
+ throw new Error(`Unhandled local Cosmos connection error: ${JSON.stringify(result)}`);
570
+ }
571
+
572
+ async function readContainerSeedDocuments(
573
+ database: Database,
574
+ containerName: string,
575
+ filePath: string
576
+ ): Promise<ExportedContainerDocuments> {
577
+ const container = database.container(containerName);
578
+
579
+ try {
580
+ await container.read();
581
+ } catch (error: unknown) {
582
+ if (isCosmosNotFoundError(error)) {
583
+ return { documents: [], containerExists: false };
584
+ }
585
+
586
+ throw error;
587
+ }
588
+
589
+ const { resources } = await container.items.query<SeedDocument>("SELECT * FROM c").fetchAll();
590
+ return {
591
+ documents: prepareSeedDocumentsForExport(resources, filePath),
592
+ containerExists: true,
593
+ };
594
+ }
595
+
596
+ function isCosmosNotFoundError(error: unknown): boolean {
597
+ return typeof error === "object" && error !== null && "code" in error && error.code === 404;
598
+ }
599
+
600
+ async function recreateContainer(database: Database, containerName: string, partitionKeyPath: string = '/id'): Promise<void> {
330
601
  try {
331
602
  await database.container(containerName).delete();
332
603
  } catch (error: any) {
@@ -339,7 +610,7 @@ async function recreateContainer(database: Database, containerName: string): Pro
339
610
  await database.containers.createIfNotExists({
340
611
  id: containerName,
341
612
  partitionKey: {
342
- paths: ["/id"],
613
+ paths: [partitionKeyPath],
343
614
  kind: PartitionKeyKind.Hash,
344
615
  version: 2,
345
616
  },
@@ -351,7 +622,7 @@ async function recreateContainer(database: Database, containerName: string): Pro
351
622
  await database.containers.createIfNotExists({
352
623
  id: containerName,
353
624
  partitionKey: {
354
- paths: ["/id"],
625
+ paths: [partitionKeyPath],
355
626
  },
356
627
  });
357
628
  }