swallowkit 1.0.0-beta.2 → 1.0.0-beta.21

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 (191) hide show
  1. package/LICENSE +21 -21
  2. package/README.ja.md +312 -215
  3. package/README.md +369 -216
  4. package/dist/__tests__/fixtures.d.ts +22 -0
  5. package/dist/__tests__/fixtures.d.ts.map +1 -0
  6. package/dist/__tests__/fixtures.js +146 -0
  7. package/dist/__tests__/fixtures.js.map +1 -0
  8. package/dist/cli/commands/add-auth.d.ts +10 -0
  9. package/dist/cli/commands/add-auth.d.ts.map +1 -0
  10. package/dist/cli/commands/add-auth.js +444 -0
  11. package/dist/cli/commands/add-auth.js.map +1 -0
  12. package/dist/cli/commands/add-connector.d.ts +20 -0
  13. package/dist/cli/commands/add-connector.d.ts.map +1 -0
  14. package/dist/cli/commands/add-connector.js +163 -0
  15. package/dist/cli/commands/add-connector.js.map +1 -0
  16. package/dist/cli/commands/create-model.d.ts +1 -4
  17. package/dist/cli/commands/create-model.d.ts.map +1 -1
  18. package/dist/cli/commands/create-model.js +21 -82
  19. package/dist/cli/commands/create-model.js.map +1 -1
  20. package/dist/cli/commands/dev-seeds.d.ts +35 -0
  21. package/dist/cli/commands/dev-seeds.d.ts.map +1 -0
  22. package/dist/cli/commands/dev-seeds.js +292 -0
  23. package/dist/cli/commands/dev-seeds.js.map +1 -0
  24. package/dist/cli/commands/dev.d.ts +19 -0
  25. package/dist/cli/commands/dev.d.ts.map +1 -1
  26. package/dist/cli/commands/dev.js +476 -117
  27. package/dist/cli/commands/dev.js.map +1 -1
  28. package/dist/cli/commands/index.d.ts +1 -0
  29. package/dist/cli/commands/index.d.ts.map +1 -1
  30. package/dist/cli/commands/index.js +3 -1
  31. package/dist/cli/commands/index.js.map +1 -1
  32. package/dist/cli/commands/init.d.ts +13 -0
  33. package/dist/cli/commands/init.d.ts.map +1 -1
  34. package/dist/cli/commands/init.js +2627 -1708
  35. package/dist/cli/commands/init.js.map +1 -1
  36. package/dist/cli/commands/scaffold.d.ts +3 -0
  37. package/dist/cli/commands/scaffold.d.ts.map +1 -1
  38. package/dist/cli/commands/scaffold.js +617 -129
  39. package/dist/cli/commands/scaffold.js.map +1 -1
  40. package/dist/cli/index.d.ts +5 -1
  41. package/dist/cli/index.d.ts.map +1 -1
  42. package/dist/cli/index.js +164 -42
  43. package/dist/cli/index.js.map +1 -1
  44. package/dist/core/config.d.ts +8 -2
  45. package/dist/core/config.d.ts.map +1 -1
  46. package/dist/core/config.js +90 -4
  47. package/dist/core/config.js.map +1 -1
  48. package/dist/core/mock/connector-mock-server.d.ts +101 -0
  49. package/dist/core/mock/connector-mock-server.d.ts.map +1 -0
  50. package/dist/core/mock/connector-mock-server.js +480 -0
  51. package/dist/core/mock/connector-mock-server.js.map +1 -0
  52. package/dist/core/mock/zod-mock-generator.d.ts +14 -0
  53. package/dist/core/mock/zod-mock-generator.d.ts.map +1 -0
  54. package/dist/core/mock/zod-mock-generator.js +163 -0
  55. package/dist/core/mock/zod-mock-generator.js.map +1 -0
  56. package/dist/core/operations/create-model.d.ts +15 -0
  57. package/dist/core/operations/create-model.d.ts.map +1 -0
  58. package/dist/core/operations/create-model.js +171 -0
  59. package/dist/core/operations/create-model.js.map +1 -0
  60. package/dist/core/operations/runtime.d.ts +32 -0
  61. package/dist/core/operations/runtime.d.ts.map +1 -0
  62. package/dist/core/operations/runtime.js +225 -0
  63. package/dist/core/operations/runtime.js.map +1 -0
  64. package/dist/core/operations/scaffold-machine.d.ts +16 -0
  65. package/dist/core/operations/scaffold-machine.d.ts.map +1 -0
  66. package/dist/core/operations/scaffold-machine.js +63 -0
  67. package/dist/core/operations/scaffold-machine.js.map +1 -0
  68. package/dist/core/project/manifest.d.ts +92 -0
  69. package/dist/core/project/manifest.d.ts.map +1 -0
  70. package/dist/core/project/manifest.js +321 -0
  71. package/dist/core/project/manifest.js.map +1 -0
  72. package/dist/core/project/validation.d.ts +20 -0
  73. package/dist/core/project/validation.d.ts.map +1 -0
  74. package/dist/core/project/validation.js +204 -0
  75. package/dist/core/project/validation.js.map +1 -0
  76. package/dist/core/scaffold/auth-generator.d.ts +38 -0
  77. package/dist/core/scaffold/auth-generator.d.ts.map +1 -0
  78. package/dist/core/scaffold/auth-generator.js +1244 -0
  79. package/dist/core/scaffold/auth-generator.js.map +1 -0
  80. package/dist/core/scaffold/connector-functions-generator.d.ts +41 -0
  81. package/dist/core/scaffold/connector-functions-generator.d.ts.map +1 -0
  82. package/dist/core/scaffold/connector-functions-generator.js +1027 -0
  83. package/dist/core/scaffold/connector-functions-generator.js.map +1 -0
  84. package/dist/core/scaffold/functions-generator.d.ts +7 -1
  85. package/dist/core/scaffold/functions-generator.d.ts.map +1 -1
  86. package/dist/core/scaffold/functions-generator.js +920 -213
  87. package/dist/core/scaffold/functions-generator.js.map +1 -1
  88. package/dist/core/scaffold/model-parser.d.ts +20 -1
  89. package/dist/core/scaffold/model-parser.d.ts.map +1 -1
  90. package/dist/core/scaffold/model-parser.js +329 -135
  91. package/dist/core/scaffold/model-parser.js.map +1 -1
  92. package/dist/core/scaffold/nextjs-generator.d.ts +8 -0
  93. package/dist/core/scaffold/nextjs-generator.d.ts.map +1 -1
  94. package/dist/core/scaffold/nextjs-generator.js +314 -182
  95. package/dist/core/scaffold/nextjs-generator.js.map +1 -1
  96. package/dist/core/scaffold/openapi-generator.d.ts +3 -0
  97. package/dist/core/scaffold/openapi-generator.d.ts.map +1 -0
  98. package/dist/core/scaffold/openapi-generator.js +190 -0
  99. package/dist/core/scaffold/openapi-generator.js.map +1 -0
  100. package/dist/core/scaffold/ui-generator.d.ts +10 -4
  101. package/dist/core/scaffold/ui-generator.d.ts.map +1 -1
  102. package/dist/core/scaffold/ui-generator.js +768 -663
  103. package/dist/core/scaffold/ui-generator.js.map +1 -1
  104. package/dist/database/base-model.d.ts +3 -3
  105. package/dist/database/base-model.js +3 -3
  106. package/dist/index.d.ts +2 -2
  107. package/dist/index.d.ts.map +1 -1
  108. package/dist/index.js +2 -1
  109. package/dist/index.js.map +1 -1
  110. package/dist/machine/contracts.d.ts +16 -0
  111. package/dist/machine/contracts.d.ts.map +1 -0
  112. package/dist/machine/contracts.js +3 -0
  113. package/dist/machine/contracts.js.map +1 -0
  114. package/dist/machine/errors.d.ts +11 -0
  115. package/dist/machine/errors.d.ts.map +1 -0
  116. package/dist/machine/errors.js +34 -0
  117. package/dist/machine/errors.js.map +1 -0
  118. package/dist/machine/index.d.ts +3 -0
  119. package/dist/machine/index.d.ts.map +1 -0
  120. package/dist/machine/index.js +156 -0
  121. package/dist/machine/index.js.map +1 -0
  122. package/dist/mcp/index.d.ts +25 -0
  123. package/dist/mcp/index.d.ts.map +1 -0
  124. package/dist/mcp/index.js +184 -0
  125. package/dist/mcp/index.js.map +1 -0
  126. package/dist/types/index.d.ts +65 -0
  127. package/dist/types/index.d.ts.map +1 -1
  128. package/dist/utils/package-manager.d.ts +109 -0
  129. package/dist/utils/package-manager.d.ts.map +1 -0
  130. package/dist/utils/package-manager.js +215 -0
  131. package/dist/utils/package-manager.js.map +1 -0
  132. package/package.json +85 -73
  133. package/src/__tests__/__snapshots__/functions-generator.test.ts.snap +1139 -0
  134. package/src/__tests__/__snapshots__/nextjs-generator.test.ts.snap +194 -0
  135. package/src/__tests__/__snapshots__/ui-generator.test.ts.snap +532 -0
  136. package/src/__tests__/auth.test.ts +654 -0
  137. package/src/__tests__/config.test.ts +263 -0
  138. package/src/__tests__/connector-functions-generator.test.ts +288 -0
  139. package/src/__tests__/connector-mock-server.test.ts +439 -0
  140. package/src/__tests__/connector-model-bff.test.ts +162 -0
  141. package/src/__tests__/dev-seeds.test.ts +112 -0
  142. package/src/__tests__/dev.test.ts +154 -0
  143. package/src/__tests__/fixtures.ts +144 -0
  144. package/src/__tests__/functions-generator.test.ts +237 -0
  145. package/src/__tests__/init.test.ts +80 -0
  146. package/src/__tests__/machine.test.ts +212 -0
  147. package/src/__tests__/mcp.test.ts +56 -0
  148. package/src/__tests__/model-parser.test.ts +72 -0
  149. package/src/__tests__/nextjs-generator.test.ts +97 -0
  150. package/src/__tests__/openapi-generator.test.ts +43 -0
  151. package/src/__tests__/package-manager.test.ts +189 -0
  152. package/src/__tests__/scaffold.test.ts +39 -0
  153. package/src/__tests__/string-utils.test.ts +75 -0
  154. package/src/__tests__/ui-generator.test.ts +144 -0
  155. package/src/__tests__/zod-mock-generator.test.ts +132 -0
  156. package/src/cli/commands/add-auth.ts +500 -0
  157. package/src/cli/commands/add-connector.ts +158 -0
  158. package/src/cli/commands/create-model.ts +62 -0
  159. package/src/cli/commands/dev-seeds.ts +358 -0
  160. package/src/cli/commands/dev.ts +962 -0
  161. package/src/cli/commands/index.ts +9 -0
  162. package/src/cli/commands/init.ts +3371 -0
  163. package/src/cli/commands/provision.ts +193 -0
  164. package/src/cli/commands/scaffold.ts +1211 -0
  165. package/src/cli/index.ts +193 -0
  166. package/src/core/config.ts +308 -0
  167. package/src/core/mock/connector-mock-server.ts +555 -0
  168. package/src/core/mock/zod-mock-generator.ts +205 -0
  169. package/src/core/operations/create-model.ts +174 -0
  170. package/src/core/operations/runtime.ts +235 -0
  171. package/src/core/operations/scaffold-machine.ts +91 -0
  172. package/src/core/project/manifest.ts +402 -0
  173. package/src/core/project/validation.ts +221 -0
  174. package/src/core/scaffold/auth-generator.ts +1284 -0
  175. package/src/core/scaffold/connector-functions-generator.ts +1128 -0
  176. package/src/core/scaffold/functions-generator.ts +970 -0
  177. package/src/core/scaffold/model-parser.ts +841 -0
  178. package/src/core/scaffold/nextjs-generator.ts +370 -0
  179. package/src/core/scaffold/openapi-generator.ts +212 -0
  180. package/src/core/scaffold/ui-generator.ts +1061 -0
  181. package/src/database/base-model.ts +184 -0
  182. package/src/database/client.ts +140 -0
  183. package/src/database/repository.ts +104 -0
  184. package/src/database/runtime-check.ts +25 -0
  185. package/src/index.ts +27 -0
  186. package/src/machine/contracts.ts +17 -0
  187. package/src/machine/errors.ts +34 -0
  188. package/src/machine/index.ts +173 -0
  189. package/src/mcp/index.ts +185 -0
  190. package/src/types/index.ts +134 -0
  191. package/src/utils/package-manager.ts +229 -0
@@ -0,0 +1,62 @@
1
+ import * as readline from "readline";
2
+ import { createModelOperation } from "../../core/operations/create-model";
3
+ import { detectFromProject, getCommands } from "../../utils/package-manager";
4
+
5
+ interface CreateModelOptions {
6
+ names: string[]; // モデル名のリスト(例: ["todo", "user", "post"])
7
+ modelsDir?: string; // モデルディレクトリ(デフォルト: "shared/models")
8
+ connector?: string; // コネクタ名(例: "mysql", "backlog")
9
+ }
10
+
11
+ /**
12
+ * ユーザーに確認を求める
13
+ */
14
+ function askConfirmation(question: string): Promise<boolean> {
15
+ const rl = readline.createInterface({
16
+ input: process.stdin,
17
+ output: process.stdout,
18
+ });
19
+
20
+ return new Promise((resolve) => {
21
+ rl.question(question, (answer) => {
22
+ rl.close();
23
+ resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
24
+ });
25
+ });
26
+ }
27
+
28
+ /**
29
+ * create-model コマンド
30
+ */
31
+ export async function createModelCommand(options: CreateModelOptions) {
32
+ console.log("🏗️ SwallowKit Create-Model: Generating model templates...\n");
33
+
34
+ const result = await createModelOperation({
35
+ names: options.names,
36
+ modelsDir: options.modelsDir,
37
+ connector: options.connector,
38
+ overwriteMode: "prompt",
39
+ confirmOverwrite: async (filePath) => askConfirmation(`⚠️ File ${filePath} already exists. Overwrite? (y/N): `),
40
+ });
41
+
42
+ // サマリー表示
43
+ console.log("\n📋 Summary:");
44
+ if (result.connectorType && options.connector) {
45
+ console.log(` 🔌 Connector: ${options.connector} (${result.connectorType})`);
46
+ }
47
+ if (result.createdFiles.length > 0) {
48
+ console.log(` ✅ Created ${result.createdFiles.length} model(s): ${result.createdFiles.join(", ")}`);
49
+ }
50
+ if (result.skippedFiles.length > 0) {
51
+ console.log(` ⏭️ Skipped ${result.skippedFiles.length} model(s): ${result.skippedFiles.join(", ")}`);
52
+ }
53
+ if (result.updatedIndex) {
54
+ console.log(" 📦 Updated shared/index.ts");
55
+ }
56
+
57
+ if (result.createdFiles.length > 0) {
58
+ console.log("\n📝 Next steps:");
59
+ console.log(" 1. Customize the generated model fields in shared/models/");
60
+ console.log(` 2. Run '${getCommands(detectFromProject()).dlx} swallowkit scaffold <model>' to generate CRUD code`);
61
+ }
62
+ }
@@ -0,0 +1,358 @@
1
+ import { Command } from "commander";
2
+ import { CosmosClient, Database, PartitionKeyKind } from "@azure/cosmos";
3
+ import * as fs from "fs";
4
+ import * as path from "path";
5
+ import { ensureSwallowKitProject } from "../../core/config";
6
+ import { FieldInfo, ModelInfo, getAllModels, toKebabCase } from "../../core/scaffold/model-parser";
7
+
8
+ export type SeedDocument = Record<string, unknown>;
9
+
10
+ interface GenerateDevSeedTemplatesOptions {
11
+ environment: string;
12
+ modelsDir?: string;
13
+ seedsDir?: string;
14
+ force?: boolean;
15
+ }
16
+
17
+ interface ApplyDevSeedEnvironmentOptions {
18
+ client: CosmosClient;
19
+ databaseName: string;
20
+ environment: string;
21
+ models: ModelInfo[];
22
+ seedsDir?: string;
23
+ }
24
+
25
+ interface LoadedSeedFile {
26
+ model: ModelInfo;
27
+ containerName: string;
28
+ documents: SeedDocument[];
29
+ filePath: string;
30
+ }
31
+
32
+ export function getContainerNameForModel(model: Pick<ModelInfo, "name">): string {
33
+ return model.name.endsWith('s') ? model.name : `${model.name}s`;
34
+ }
35
+
36
+ export function getSeedEnvironmentDir(environment: string, seedsDir: string = "dev-seeds"): string {
37
+ return path.join(process.cwd(), seedsDir, environment);
38
+ }
39
+
40
+ export function normalizeSeedIdentifier(value: string): string {
41
+ return value.toLowerCase().replace(/[^a-z0-9]/g, "");
42
+ }
43
+
44
+ export function parseSeedDocuments(content: string, filePath: string): SeedDocument[] {
45
+ const parsed: unknown = JSON.parse(content);
46
+
47
+ if (Array.isArray(parsed)) {
48
+ return parsed.map((item, index) => {
49
+ if (!isSeedDocument(item)) {
50
+ throw new Error(`${filePath} must contain only JSON objects. Invalid item at index ${index}.`);
51
+ }
52
+
53
+ return item;
54
+ });
55
+ }
56
+
57
+ if (isSeedDocument(parsed)) {
58
+ return [parsed];
59
+ }
60
+
61
+ throw new Error(`${filePath} must contain a JSON object or an array of JSON objects.`);
62
+ }
63
+
64
+ export async function loadProjectModels(modelsDir: string = "shared/models"): Promise<ModelInfo[]> {
65
+ return getAllModels(modelsDir);
66
+ }
67
+
68
+ export function buildSeedTemplateDocument(
69
+ model: ModelInfo,
70
+ allModels: ModelInfo[] = [model],
71
+ seenModels: Set<string> = new Set()
72
+ ): SeedDocument {
73
+ const modelLookup = new Map(allModels.map((candidate) => [candidate.name, candidate]));
74
+ return buildSeedTemplateDocumentInternal(model, modelLookup, seenModels);
75
+ }
76
+
77
+ export async function loadDevSeedFiles(
78
+ environment: string,
79
+ models: ModelInfo[],
80
+ seedsDir: string = "dev-seeds"
81
+ ): Promise<LoadedSeedFile[]> {
82
+ const environmentDir = getSeedEnvironmentDir(environment, seedsDir);
83
+ if (!fs.existsSync(environmentDir)) {
84
+ return [];
85
+ }
86
+
87
+ const modelAliases = buildModelAliasMap(models);
88
+ const seedFiles = fs.readdirSync(environmentDir).filter((entry) => entry.endsWith(".json"));
89
+ const loaded: LoadedSeedFile[] = [];
90
+
91
+ for (const fileName of seedFiles) {
92
+ const filePath = path.join(environmentDir, fileName);
93
+ const fileStem = path.basename(fileName, ".json");
94
+ const model = modelAliases.get(normalizeSeedIdentifier(fileStem));
95
+
96
+ if (!model) {
97
+ console.warn(`⚠️ Skipping seed file without matching schema: ${filePath}`);
98
+ continue;
99
+ }
100
+
101
+ const documents = parseSeedDocuments(fs.readFileSync(filePath, "utf-8"), filePath);
102
+ validateSeedDocuments(documents, filePath);
103
+
104
+ loaded.push({
105
+ model,
106
+ containerName: getContainerNameForModel(model),
107
+ documents,
108
+ filePath,
109
+ });
110
+ }
111
+
112
+ return loaded;
113
+ }
114
+
115
+ export async function applyDevSeedEnvironment({
116
+ client,
117
+ databaseName,
118
+ environment,
119
+ models,
120
+ seedsDir = "dev-seeds",
121
+ }: ApplyDevSeedEnvironmentOptions): Promise<boolean> {
122
+ const environmentDir = getSeedEnvironmentDir(environment, seedsDir);
123
+ if (!fs.existsSync(environmentDir)) {
124
+ console.log(`ℹ️ Seed environment "${environment}" not found at ${environmentDir}. Keeping existing Cosmos DB data.`);
125
+ return false;
126
+ }
127
+
128
+ const seedFiles = await loadDevSeedFiles(environment, models, seedsDir);
129
+ if (seedFiles.length === 0) {
130
+ console.log(`ℹ️ No seed files found for environment "${environment}". Keeping existing Cosmos DB data.`);
131
+ return false;
132
+ }
133
+
134
+ const database = client.database(databaseName);
135
+ console.log(`🧪 Applying Cosmos DB seed data for environment "${environment}"...`);
136
+
137
+ for (const seedFile of seedFiles) {
138
+ await recreateContainer(database, seedFile.containerName, seedFile.model.partitionKey);
139
+ const container = database.container(seedFile.containerName);
140
+
141
+ for (const document of seedFile.documents) {
142
+ await container.items.create(document);
143
+ }
144
+
145
+ console.log(
146
+ `✅ Seeded "${seedFile.containerName}" with ${seedFile.documents.length} item(s) from ${path.basename(seedFile.filePath)}`
147
+ );
148
+ }
149
+
150
+ console.log("✅ Cosmos DB seed replacement complete\n");
151
+ return true;
152
+ }
153
+
154
+ export async function generateDevSeedTemplates({
155
+ environment,
156
+ modelsDir = "shared/models",
157
+ seedsDir = "dev-seeds",
158
+ force = false,
159
+ }: GenerateDevSeedTemplatesOptions): Promise<void> {
160
+ ensureSwallowKitProject("create-dev-seeds");
161
+
162
+ console.log(`🧪 Generating dev seed templates for environment "${environment}"...\n`);
163
+ const models = await loadProjectModels(modelsDir);
164
+
165
+ if (models.length === 0) {
166
+ console.log("⚠️ No schemas found under shared/models. Nothing was generated.");
167
+ return;
168
+ }
169
+
170
+ const environmentDir = getSeedEnvironmentDir(environment, seedsDir);
171
+ fs.mkdirSync(environmentDir, { recursive: true });
172
+
173
+ const writtenFiles: string[] = [];
174
+ const skippedFiles: string[] = [];
175
+
176
+ for (const model of models) {
177
+ const filePath = path.join(environmentDir, `${toKebabCase(model.name)}.json`);
178
+ if (!force && fs.existsSync(filePath)) {
179
+ skippedFiles.push(filePath);
180
+ continue;
181
+ }
182
+
183
+ const content = JSON.stringify([buildSeedTemplateDocument(model, models)], null, 2) + "\n";
184
+ fs.writeFileSync(filePath, content, "utf-8");
185
+ writtenFiles.push(filePath);
186
+ console.log(`✅ Created: ${filePath}`);
187
+ }
188
+
189
+ if (skippedFiles.length > 0) {
190
+ console.log("");
191
+ console.log(`⏭️ Skipped ${skippedFiles.length} existing file(s). Use --force to overwrite them.`);
192
+ }
193
+
194
+ console.log("");
195
+ console.log("📝 Next steps:");
196
+ console.log(` 1. Edit JSON files under ${environmentDir}`);
197
+ console.log(` 2. Run 'swallowkit dev --seed-env ${environment}' to replace emulator data before startup`);
198
+ }
199
+
200
+ export const devSeedsCommand = new Command()
201
+ .name("create-dev-seeds")
202
+ .description("Generate dev seed JSON templates under dev-seeds/<environment> from shared/models schemas")
203
+ .argument("<environment>", "Seed environment name")
204
+ .option("--models-dir <dir>", "Models directory", "shared/models")
205
+ .option("--seeds-dir <dir>", "Dev seeds directory", "dev-seeds")
206
+ .option("--force", "Overwrite existing seed JSON files", false)
207
+ .action(async (environment: string, options: { modelsDir?: string; seedsDir?: string; force?: boolean }) => {
208
+ await generateDevSeedTemplates({
209
+ environment,
210
+ modelsDir: options.modelsDir,
211
+ seedsDir: options.seedsDir,
212
+ force: options.force,
213
+ });
214
+ });
215
+
216
+ function buildSeedTemplateDocumentInternal(
217
+ model: ModelInfo,
218
+ modelLookup: Map<string, ModelInfo>,
219
+ seenModels: Set<string>
220
+ ): SeedDocument {
221
+ const nextSeen = new Set(seenModels);
222
+ nextSeen.add(model.name);
223
+
224
+ const document: SeedDocument = {};
225
+
226
+ for (const field of model.fields) {
227
+ document[field.name] = buildTemplateValueForField(model, field, modelLookup, nextSeen);
228
+ }
229
+
230
+ return document;
231
+ }
232
+
233
+ function buildTemplateValueForField(
234
+ model: ModelInfo,
235
+ field: FieldInfo,
236
+ modelLookup: Map<string, ModelInfo>,
237
+ seenModels: Set<string>
238
+ ): unknown {
239
+ if (field.isNestedSchema && field.nestedModelName) {
240
+ const nestedModel = modelLookup.get(field.nestedModelName);
241
+ if (nestedModel) {
242
+ if (seenModels.has(nestedModel.name)) {
243
+ const fallback = { id: `${toKebabCase(nestedModel.name)}-001` };
244
+ return field.isArray ? [fallback] : fallback;
245
+ }
246
+
247
+ const nestedDocument = buildSeedTemplateDocumentInternal(nestedModel, modelLookup, seenModels);
248
+ return field.isArray ? [nestedDocument] : nestedDocument;
249
+ }
250
+ }
251
+
252
+ if (field.isArray) {
253
+ return [buildScalarTemplateValue(model, field)];
254
+ }
255
+
256
+ return buildScalarTemplateValue(model, field);
257
+ }
258
+
259
+ function buildScalarTemplateValue(model: ModelInfo, field: FieldInfo): unknown {
260
+ if (field.name === "id") {
261
+ return `${toKebabCase(model.name)}-001`;
262
+ }
263
+
264
+ if (field.enumValues && field.enumValues.length > 0) {
265
+ return field.enumValues[0];
266
+ }
267
+
268
+ if (field.type === "number") {
269
+ return 0;
270
+ }
271
+
272
+ if (field.type === "boolean") {
273
+ return false;
274
+ }
275
+
276
+ if (field.type === "date" || field.name.endsWith("At")) {
277
+ return "2026-01-01T00:00:00.000Z";
278
+ }
279
+
280
+ if (field.type === "object") {
281
+ return {};
282
+ }
283
+
284
+ return `${toKebabCase(model.name)}-${toKebabCase(field.name)}-sample`;
285
+ }
286
+
287
+ function buildModelAliasMap(models: ModelInfo[]): Map<string, ModelInfo> {
288
+ const aliases = new Map<string, ModelInfo>();
289
+
290
+ for (const model of models) {
291
+ const candidates = [
292
+ model.name,
293
+ model.schemaName,
294
+ toKebabCase(model.name),
295
+ path.basename(model.filePath, path.extname(model.filePath)),
296
+ ];
297
+
298
+ for (const candidate of candidates) {
299
+ const normalized = normalizeSeedIdentifier(candidate);
300
+ if (!aliases.has(normalized)) {
301
+ aliases.set(normalized, model);
302
+ }
303
+ }
304
+ }
305
+
306
+ return aliases;
307
+ }
308
+
309
+ function isSeedDocument(value: unknown): value is SeedDocument {
310
+ return typeof value === "object" && value !== null && !Array.isArray(value);
311
+ }
312
+
313
+ function validateSeedDocuments(documents: SeedDocument[], filePath: string): void {
314
+ const ids = new Set<string>();
315
+
316
+ documents.forEach((document, index) => {
317
+ if (typeof document.id !== "string" || document.id.trim().length === 0) {
318
+ throw new Error(`${filePath} item at index ${index} must contain a non-empty string id.`);
319
+ }
320
+
321
+ if (ids.has(document.id)) {
322
+ throw new Error(`${filePath} contains duplicate id "${document.id}".`);
323
+ }
324
+
325
+ ids.add(document.id);
326
+ });
327
+ }
328
+
329
+ async function recreateContainer(database: Database, containerName: string, partitionKeyPath: string = '/id'): Promise<void> {
330
+ try {
331
+ await database.container(containerName).delete();
332
+ } catch (error: any) {
333
+ if (error?.code !== 404) {
334
+ throw error;
335
+ }
336
+ }
337
+
338
+ try {
339
+ await database.containers.createIfNotExists({
340
+ id: containerName,
341
+ partitionKey: {
342
+ paths: [partitionKeyPath],
343
+ kind: PartitionKeyKind.Hash,
344
+ version: 2,
345
+ },
346
+ });
347
+ } catch (error: unknown) {
348
+ const message = error instanceof Error ? error.message : String(error);
349
+ console.log(`⚠️ Failed to recreate "${containerName}" with full partition key definition: ${message}`);
350
+ console.log("🔄 Retrying with simple partition key...");
351
+ await database.containers.createIfNotExists({
352
+ id: containerName,
353
+ partitionKey: {
354
+ paths: [partitionKeyPath],
355
+ },
356
+ });
357
+ }
358
+ }