swallowkit 1.0.0-beta.5 → 1.0.0-beta.50

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 (222) hide show
  1. package/LICENSE +21 -21
  2. package/README.ja.md +153 -242
  3. package/README.md +153 -243
  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 +13 -0
  9. package/dist/cli/commands/add-auth.d.ts.map +1 -0
  10. package/dist/cli/commands/add-auth.js +759 -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 +19 -81
  19. package/dist/cli/commands/create-model.js.map +1 -1
  20. package/dist/cli/commands/dev-seeds.d.ts +57 -0
  21. package/dist/cli/commands/dev-seeds.d.ts.map +1 -0
  22. package/dist/cli/commands/dev-seeds.js +470 -0
  23. package/dist/cli/commands/dev-seeds.js.map +1 -0
  24. package/dist/cli/commands/dev.d.ts +37 -0
  25. package/dist/cli/commands/dev.d.ts.map +1 -1
  26. package/dist/cli/commands/dev.js +720 -156
  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 +67 -0
  33. package/dist/cli/commands/init.d.ts.map +1 -1
  34. package/dist/cli/commands/init.js +3080 -1771
  35. package/dist/cli/commands/init.js.map +1 -1
  36. package/dist/cli/commands/scaffold.d.ts +13 -0
  37. package/dist/cli/commands/scaffold.d.ts.map +1 -1
  38. package/dist/cli/commands/scaffold.js +513 -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 +206 -55
  43. package/dist/cli/index.js.map +1 -1
  44. package/dist/core/config.d.ts +14 -3
  45. package/dist/core/config.d.ts.map +1 -1
  46. package/dist/core/config.js +172 -5
  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 +226 -0
  75. package/dist/core/project/validation.js.map +1 -0
  76. package/dist/core/scaffold/auth-generator.d.ts +63 -0
  77. package/dist/core/scaffold/auth-generator.d.ts.map +1 -0
  78. package/dist/core/scaffold/auth-generator.js +1714 -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 +1029 -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 +922 -220
  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 +333 -139
  91. package/dist/core/scaffold/model-parser.js.map +1 -1
  92. package/dist/core/scaffold/native-schema-generator.d.ts +13 -0
  93. package/dist/core/scaffold/native-schema-generator.d.ts.map +1 -0
  94. package/dist/core/scaffold/native-schema-generator.js +696 -0
  95. package/dist/core/scaffold/native-schema-generator.js.map +1 -0
  96. package/dist/core/scaffold/nextjs-generator.d.ts +8 -0
  97. package/dist/core/scaffold/nextjs-generator.d.ts.map +1 -1
  98. package/dist/core/scaffold/nextjs-generator.js +326 -182
  99. package/dist/core/scaffold/nextjs-generator.js.map +1 -1
  100. package/dist/core/scaffold/openapi-generator.d.ts +3 -0
  101. package/dist/core/scaffold/openapi-generator.d.ts.map +1 -0
  102. package/dist/core/scaffold/openapi-generator.js +190 -0
  103. package/dist/core/scaffold/openapi-generator.js.map +1 -0
  104. package/dist/core/scaffold/ui-generator.d.ts +13 -4
  105. package/dist/core/scaffold/ui-generator.d.ts.map +1 -1
  106. package/dist/core/scaffold/ui-generator.js +772 -663
  107. package/dist/core/scaffold/ui-generator.js.map +1 -1
  108. package/dist/database/base-model.d.ts +3 -3
  109. package/dist/database/base-model.js +3 -3
  110. package/dist/index.d.ts +2 -2
  111. package/dist/index.d.ts.map +1 -1
  112. package/dist/index.js +2 -1
  113. package/dist/index.js.map +1 -1
  114. package/dist/machine/contracts.d.ts +16 -0
  115. package/dist/machine/contracts.d.ts.map +1 -0
  116. package/dist/machine/contracts.js +3 -0
  117. package/dist/machine/contracts.js.map +1 -0
  118. package/dist/machine/errors.d.ts +11 -0
  119. package/dist/machine/errors.d.ts.map +1 -0
  120. package/dist/machine/errors.js +34 -0
  121. package/dist/machine/errors.js.map +1 -0
  122. package/dist/machine/index.d.ts +3 -0
  123. package/dist/machine/index.d.ts.map +1 -0
  124. package/dist/machine/index.js +156 -0
  125. package/dist/machine/index.js.map +1 -0
  126. package/dist/mcp/index.d.ts +25 -0
  127. package/dist/mcp/index.d.ts.map +1 -0
  128. package/dist/mcp/index.js +202 -0
  129. package/dist/mcp/index.js.map +1 -0
  130. package/dist/types/index.d.ts +95 -0
  131. package/dist/types/index.d.ts.map +1 -1
  132. package/dist/utils/package-manager.d.ts +5 -2
  133. package/dist/utils/package-manager.d.ts.map +1 -1
  134. package/dist/utils/package-manager.js +34 -13
  135. package/dist/utils/package-manager.js.map +1 -1
  136. package/dist/utils/python-uv.d.ts +21 -0
  137. package/dist/utils/python-uv.d.ts.map +1 -0
  138. package/dist/utils/python-uv.js +111 -0
  139. package/dist/utils/python-uv.js.map +1 -0
  140. package/dist/version.d.ts +2 -0
  141. package/dist/version.d.ts.map +1 -0
  142. package/dist/version.js +49 -0
  143. package/dist/version.js.map +1 -0
  144. package/package.json +84 -74
  145. package/src/__tests__/__snapshots__/functions-generator.test.ts.snap +1139 -0
  146. package/src/__tests__/__snapshots__/nextjs-generator.test.ts.snap +206 -0
  147. package/src/__tests__/__snapshots__/ui-generator.test.ts.snap +532 -0
  148. package/src/__tests__/auth.test.ts +761 -0
  149. package/src/__tests__/config.test.ts +295 -0
  150. package/src/__tests__/connector-functions-generator.test.ts +288 -0
  151. package/src/__tests__/connector-mock-server.test.ts +439 -0
  152. package/src/__tests__/connector-model-bff.test.ts +162 -0
  153. package/src/__tests__/dev-seeds.test.ts +173 -0
  154. package/src/__tests__/dev.test.ts +287 -0
  155. package/src/__tests__/fixtures.ts +144 -0
  156. package/src/__tests__/functions-generator.test.ts +237 -0
  157. package/src/__tests__/init.test.ts +327 -0
  158. package/src/__tests__/jest-globals.d.ts +8 -0
  159. package/src/__tests__/machine.test.ts +356 -0
  160. package/src/__tests__/mcp.test.ts +145 -0
  161. package/src/__tests__/model-parser.test.ts +52 -0
  162. package/src/__tests__/multi-auth.test.ts +133 -0
  163. package/src/__tests__/nextjs-generator.test.ts +105 -0
  164. package/src/__tests__/openapi-generator.test.ts +43 -0
  165. package/src/__tests__/package-manager.test.ts +227 -0
  166. package/src/__tests__/python-uv.test.ts +48 -0
  167. package/src/__tests__/scaffold.test.ts +191 -0
  168. package/src/__tests__/string-utils.test.ts +75 -0
  169. package/src/__tests__/tsconfig.json +15 -0
  170. package/src/__tests__/ui-generator.test.ts +144 -0
  171. package/src/__tests__/zod-mock-generator.test.ts +132 -0
  172. package/src/cli/commands/add-auth.ts +815 -0
  173. package/src/cli/commands/add-connector.ts +158 -0
  174. package/src/cli/commands/create-model.ts +62 -0
  175. package/src/cli/commands/dev-seeds.ts +614 -0
  176. package/src/cli/commands/dev.ts +1234 -0
  177. package/src/cli/commands/index.ts +9 -0
  178. package/src/cli/commands/init.ts +3902 -0
  179. package/src/cli/commands/provision.ts +193 -0
  180. package/src/cli/commands/scaffold.ts +1067 -0
  181. package/src/cli/index.ts +202 -0
  182. package/src/core/config.ts +379 -0
  183. package/src/core/mock/connector-mock-server.ts +555 -0
  184. package/src/core/mock/zod-mock-generator.ts +205 -0
  185. package/src/core/operations/create-model.ts +174 -0
  186. package/src/core/operations/runtime.ts +235 -0
  187. package/src/core/operations/scaffold-machine.ts +91 -0
  188. package/src/core/project/manifest.ts +402 -0
  189. package/src/core/project/validation.ts +245 -0
  190. package/src/core/scaffold/auth-generator.ts +1760 -0
  191. package/src/core/scaffold/connector-functions-generator.ts +1133 -0
  192. package/src/core/scaffold/functions-generator.ts +972 -0
  193. package/src/core/scaffold/model-parser.ts +847 -0
  194. package/src/core/scaffold/native-schema-generator.ts +822 -0
  195. package/src/core/scaffold/nextjs-generator.ts +382 -0
  196. package/src/core/scaffold/openapi-generator.ts +212 -0
  197. package/src/core/scaffold/ui-generator.ts +1068 -0
  198. package/src/database/base-model.ts +184 -0
  199. package/src/database/client.ts +140 -0
  200. package/src/database/repository.ts +104 -0
  201. package/src/database/runtime-check.ts +25 -0
  202. package/src/index.ts +27 -0
  203. package/src/machine/contracts.ts +17 -0
  204. package/src/machine/errors.ts +34 -0
  205. package/src/machine/index.ts +173 -0
  206. package/src/mcp/index.ts +209 -0
  207. package/src/types/index.ts +165 -0
  208. package/src/utils/package-manager.ts +246 -0
  209. package/src/utils/python-uv.ts +96 -0
  210. package/src/version.ts +13 -0
  211. package/dist/cli/commands/build.d.ts +0 -6
  212. package/dist/cli/commands/build.d.ts.map +0 -1
  213. package/dist/cli/commands/build.js +0 -177
  214. package/dist/cli/commands/build.js.map +0 -1
  215. package/dist/cli/commands/deploy.d.ts +0 -3
  216. package/dist/cli/commands/deploy.d.ts.map +0 -1
  217. package/dist/cli/commands/deploy.js +0 -147
  218. package/dist/cli/commands/deploy.js.map +0 -1
  219. package/dist/cli/commands/setup.d.ts +0 -6
  220. package/dist/cli/commands/setup.d.ts.map +0 -1
  221. package/dist/cli/commands/setup.js +0 -254
  222. package/dist/cli/commands/setup.js.map +0 -1
@@ -38,17 +38,43 @@ var __importStar = (this && this.__importStar) || (function () {
38
38
  })();
39
39
  Object.defineProperty(exports, "__esModule", { value: true });
40
40
  exports.scaffoldCommand = scaffoldCommand;
41
+ exports.resolveAuthPolicy = resolveAuthPolicy;
42
+ exports.generateCosmosContainer = generateCosmosContainer;
41
43
  const fs = __importStar(require("fs"));
42
44
  const path = __importStar(require("path"));
45
+ const child_process_1 = require("child_process");
46
+ const config_1 = require("../../core/config");
43
47
  const model_parser_1 = require("../../core/scaffold/model-parser");
44
48
  const functions_generator_1 = require("../../core/scaffold/functions-generator");
49
+ const connector_functions_generator_1 = require("../../core/scaffold/connector-functions-generator");
45
50
  const nextjs_generator_1 = require("../../core/scaffold/nextjs-generator");
46
51
  const ui_generator_1 = require("../../core/scaffold/ui-generator");
47
- const config_1 = require("../../core/config");
52
+ const native_schema_generator_1 = require("../../core/scaffold/native-schema-generator");
48
53
  const package_manager_1 = require("../../utils/package-manager");
54
+ const manifest_1 = require("../../core/project/manifest");
55
+ const auth_generator_1 = require("../../core/scaffold/auth-generator");
56
+ function getMachineAwareStdio() {
57
+ return process.env.SWALLOWKIT_MACHINE_OUTPUT === "1" ? "pipe" : "inherit";
58
+ }
59
+ function runSpawnSyncCommand(command, args, cwd) {
60
+ const result = (0, child_process_1.spawnSync)(command, args, {
61
+ cwd,
62
+ stdio: getMachineAwareStdio(),
63
+ shell: true,
64
+ });
65
+ if (typeof result.status === "number" && result.status !== 0) {
66
+ throw new Error(`${command} ${args.join(" ")} exited with code ${result.status}`);
67
+ }
68
+ if (result.error) {
69
+ throw result.error;
70
+ }
71
+ }
49
72
  async function scaffoldCommand(options) {
50
73
  // SwallowKit プロジェクトディレクトリかどうかを検証
51
74
  (0, config_1.ensureSwallowKitProject)("scaffold");
75
+ const configValidation = (0, config_1.validateConfig)((0, config_1.getFullConfig)());
76
+ if (!configValidation.valid)
77
+ throw new Error(`Invalid SwallowKit configuration:\n${configValidation.errors.map(error => `- ${error}`).join("\n")}`);
52
78
  console.log("🏗️ SwallowKit Scaffold: Generating CRUD operations...\n");
53
79
  try {
54
80
  // 1. Resolve model file path
@@ -58,6 +84,14 @@ async function scaffoldCommand(options) {
58
84
  console.log("🔍 Parsing model file...");
59
85
  const modelInfo = await (0, model_parser_1.parseModelFile)(modelPath);
60
86
  console.log(`✅ Model parsed: ${modelInfo.name} (${modelInfo.schemaName})`);
87
+ const backendLanguage = (0, config_1.getBackendLanguage)();
88
+ console.log(`🧠 Backend language: ${backendLanguage}`);
89
+ // コネクタモデルかどうかを判定
90
+ const isConnectorModel = !!modelInfo.connectorConfig;
91
+ if (isConnectorModel) {
92
+ console.log(`🔌 Connector model detected: ${modelInfo.connectorConfig.connector}`);
93
+ console.log(` Operations: ${modelInfo.connectorConfig.operations.join(", ")}`);
94
+ }
61
95
  // ネストスキーマ参照があれば表示
62
96
  if (modelInfo.nestedSchemaRefs.length > 0) {
63
97
  console.log(`🔗 Nested schema references detected:`);
@@ -74,36 +108,128 @@ async function scaffoldCommand(options) {
74
108
  // 4. Read shared package name
75
109
  const functionsDir = options.functionsDir || "functions";
76
110
  const sharedPackageName = readSharedPackageName();
111
+ const relatedModels = backendLanguage === "typescript"
112
+ ? [modelInfo]
113
+ : await collectModelGraph(modelPath);
114
+ if (options.apiOnly) {
115
+ console.log("ℹ️ --api-only skips UI generation only; Functions, BFF routes, OpenAPI, and native schema assets are still updated.");
116
+ }
77
117
  // 5. Generate BFF callFunction helper
78
118
  await generateCallFunctionHelper();
79
119
  // 6. Generate Azure Functions code
80
- await generateFunctionsCode(modelInfo, functionsDir, sharedPackageName);
120
+ if (isConnectorModel) {
121
+ await generateConnectorFunctionsCode(modelInfo, functionsDir, sharedPackageName, backendLanguage);
122
+ // 6b. Install connector driver dependencies
123
+ await installConnectorDriverDependencies(modelInfo, functionsDir, backendLanguage);
124
+ }
125
+ else {
126
+ await generateFunctionsCode(modelInfo, functionsDir, sharedPackageName, backendLanguage);
127
+ if (backendLanguage !== "typescript") {
128
+ await (0, native_schema_generator_1.generateLanguageSchemaArtifacts)(relatedModels, modelInfo, functionsDir, backendLanguage);
129
+ }
130
+ }
81
131
  // 7. Generate Next.js BFF API Routes
82
132
  const apiDir = options.apiDir || "app/api";
83
- await generateBFFRoutes(modelInfo, apiDir, sharedPackageName);
84
- // 8. Generate Cosmos DB container Bicep file
85
- await generateCosmosContainer(modelInfo);
133
+ if (isConnectorModel) {
134
+ await generateConnectorBFFRoutesFiles(modelInfo, apiDir, sharedPackageName);
135
+ }
136
+ else {
137
+ await generateBFFRoutes(modelInfo, apiDir, sharedPackageName);
138
+ }
139
+ // 8. Generate Cosmos DB container Bicep file (skip for connector models)
140
+ if (!isConnectorModel) {
141
+ await generateCosmosContainer(modelInfo);
142
+ }
86
143
  // 9. Generate UI components (unless --api-only)
87
144
  if (!options.apiOnly) {
88
- await generateUIComponents(modelInfo, sharedPackageName);
145
+ const uiAuthConfig = (0, config_1.getAuthConfig)();
146
+ const uiAuthPolicy = resolveAuthPolicy(modelInfo, uiAuthConfig);
147
+ const uiAuthOptions = uiAuthPolicy && uiAuthConfig && uiAuthConfig.provider !== 'none'
148
+ ? createUIAuthOptions(uiAuthPolicy, uiAuthConfig)
149
+ : undefined;
150
+ await generateUIComponents(modelInfo, sharedPackageName, uiAuthOptions);
151
+ if (uiAuthOptions?.authenticatedFetchImport) {
152
+ applyExternalTokenFetchToGeneratedUi(modelInfo, uiAuthOptions.authenticatedFetchImport);
153
+ }
89
154
  await updateNavigationMenu(modelInfo);
90
155
  }
156
+ await (0, manifest_1.syncProjectManifest)();
91
157
  console.log("\n✅ Scaffold completed successfully!");
92
158
  console.log("\n📝 Next steps:");
93
- console.log(` 1. Review generated files in ${functionsDir}/src/ and ${apiDir}/`);
159
+ console.log(` 1. Review generated files in ${describeFunctionsOutputPath(functionsDir, backendLanguage)} and ${apiDir}/`);
94
160
  if (!options.apiOnly) {
95
161
  console.log(` 2. Check the generated UI pages in app/${(0, model_parser_1.toKebabCase)(modelInfo.name)}/`);
96
162
  console.log(" 3. Navigate to the model from the homepage menu");
97
163
  }
98
- console.log(` ${options.apiOnly ? '2' : '4'}. Ensure FUNCTIONS_BASE_URL is set in your .env.local file`);
99
- console.log(` ${options.apiOnly ? '3' : '5'}. Configure CosmosDBConnection in functions/local.settings.json`);
100
- console.log(` ${options.apiOnly ? '4' : '6'}. Run '${(0, package_manager_1.getCommands)((0, package_manager_1.detectFromProject)()).dlx} swallowkit dev' to test the generated code`);
164
+ if (backendLanguage !== "typescript") {
165
+ console.log(` ${options.apiOnly ? "2" : "4"}. Review generated OpenAPI export and native schema assets in ${functionsDir}/openapi/ and ${functionsDir}/generated/`);
166
+ }
167
+ console.log(` ${options.apiOnly ? (backendLanguage === "typescript" ? "2" : "3") : (backendLanguage === "typescript" ? "4" : "5")}. Ensure BACKEND_FUNCTIONS_BASE_URL is set in your .env.local file`);
168
+ console.log(` ${options.apiOnly ? (backendLanguage === "typescript" ? "3" : "4") : (backendLanguage === "typescript" ? "5" : "6")}. Configure CosmosDBConnection in functions/local.settings.json`);
169
+ console.log(` ${options.apiOnly ? (backendLanguage === "typescript" ? "4" : "5") : (backendLanguage === "typescript" ? "6" : "7")}. Run '${(0, package_manager_1.getCommands)((0, package_manager_1.detectFromProject)()).dlx} swallowkit dev' to test the generated code`);
101
170
  }
102
171
  catch (error) {
103
172
  console.error("\n❌ Scaffold failed:", error.message);
104
173
  process.exit(1);
105
174
  }
106
175
  }
176
+ function applyExternalTokenFetchToGeneratedUi(modelInfo, importPath = '@/lib/auth/authenticated-fetch') {
177
+ const uiRoot = path.join(process.cwd(), 'app', (0, model_parser_1.toKebabCase)(modelInfo.name));
178
+ if (!fs.existsSync(uiRoot))
179
+ return;
180
+ const visit = (dir) => {
181
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
182
+ const target = path.join(dir, entry.name);
183
+ if (entry.isDirectory()) {
184
+ visit(target);
185
+ continue;
186
+ }
187
+ if (!entry.name.endsWith('.tsx'))
188
+ continue;
189
+ let content = fs.readFileSync(target, 'utf-8');
190
+ if (!content.includes('fetch('))
191
+ continue;
192
+ content = content.replace(/\bfetch\(/g, 'authenticatedFetch(');
193
+ content = content.replace(/(if \(!res\.ok\) throw new Error\([^\n]+\);)/g, `if (res.status === 401) throw new Error('Authentication required');\n $1`);
194
+ if (!content.includes(importPath)) {
195
+ const directive = content.match(/^'use client';\s*\r?\n/)?.[0] ?? '';
196
+ content = `${directive}import { authenticatedFetch } from '${importPath}';\n${content.slice(directive.length)}`;
197
+ }
198
+ fs.writeFileSync(target, content, 'utf-8');
199
+ }
200
+ };
201
+ visit(uiRoot);
202
+ }
203
+ /**
204
+ * モデルの authPolicy と auth config から実効的な認可ポリシーを解決
205
+ * - モデルに authPolicy があればそれを使用
206
+ * - なければ auth.authorization.defaultPolicy に従う
207
+ * - auth 設定がなければ undefined(ガードなし)
208
+ */
209
+ function resolveAuthPolicy(modelInfo, authConfig) {
210
+ // モデルに明示的な authPolicy がある場合はそれを使用
211
+ if (modelInfo.authPolicy) {
212
+ const normalized = (0, config_1.normalizeAuthConfig)(authConfig);
213
+ for (const policyName of [modelInfo.authPolicy.policy, typeof modelInfo.authPolicy.read === 'string' ? modelInfo.authPolicy.read : undefined, typeof modelInfo.authPolicy.write === 'string' ? modelInfo.authPolicy.write : undefined].filter((value) => Boolean(value))) {
214
+ if (!normalized?.authorization.policies[policyName])
215
+ throw new Error(`Model '${modelInfo.name}' references undefined auth policy '${policyName}'. Define auth.authorization.policies.${policyName}.`);
216
+ }
217
+ return modelInfo.authPolicy;
218
+ }
219
+ // auth 設定がない場合はガードなし
220
+ if (!authConfig || authConfig.provider === 'none') {
221
+ return undefined;
222
+ }
223
+ // defaultPolicy が 'authenticated' なら認証のみ(ロール指定なし)のポリシーを返す
224
+ const defaultPolicy = authConfig.authorization?.defaultPolicy === 'public' ? 'anonymous' : authConfig.authorization?.defaultPolicy ?? 'authenticated';
225
+ if (defaultPolicy === 'authenticated') {
226
+ return {}; // 空のポリシー = 認証のみ、ロール制限なし
227
+ }
228
+ if (defaultPolicy !== 'anonymous')
229
+ return { policy: defaultPolicy };
230
+ // defaultPolicy が 'anonymous' なら認証不要
231
+ return undefined;
232
+ }
107
233
  /**
108
234
  * モデルファイルのパスを解決
109
235
  */
@@ -169,34 +295,306 @@ async function generateCallFunctionHelper() {
169
295
  if (!fs.existsSync(helperDir)) {
170
296
  fs.mkdirSync(helperDir, { recursive: true });
171
297
  }
172
- const helperCode = (0, nextjs_generator_1.generateBFFCallFunction)();
298
+ // auth 設定がある場合は Authorization ヘッダー転送版を生成
299
+ const authConfig = (0, config_1.getAuthConfig)();
300
+ const normalizedAuth = (0, config_1.normalizeAuthConfig)(authConfig);
301
+ const hasAuth = Boolean(normalizedAuth && Object.keys(normalizedAuth.schemes).length > 0);
302
+ let helperCode;
303
+ if (hasAuth) {
304
+ const providers = new Set(Object.values(normalizedAuth.schemes).map(scheme => scheme.provider));
305
+ helperCode = normalizedAuth.schemes.default && Object.keys(normalizedAuth.schemes).length === 1
306
+ ? authConfig?.provider === "swa" ? (0, auth_generator_1.generateBFFCallFunctionWithSwaAuth)() : (0, auth_generator_1.generateBFFCallFunctionWithAuth)()
307
+ : providers.has("swa") ? (0, auth_generator_1.generateBFFCallFunctionWithMultipleAuth)() : (0, auth_generator_1.generateBFFCallFunctionWithAuth)();
308
+ }
309
+ else {
310
+ helperCode = (0, nextjs_generator_1.generateBFFCallFunction)();
311
+ }
173
312
  fs.writeFileSync(helperPath, helperCode, "utf-8");
174
313
  console.log(`✅ Created: ${helperPath}`);
175
314
  }
176
315
  /**
177
316
  * Generate Azure Functions CRUD code
178
317
  */
179
- async function generateFunctionsCode(modelInfo, functionsDir, sharedPackageName) {
318
+ async function generateFunctionsCode(modelInfo, functionsDir, sharedPackageName, backendLanguage) {
180
319
  console.log("\n🔨 Generating Azure Functions CRUD code...");
320
+ // Resolve auth policy: model-level authPolicy or global defaultPolicy
321
+ const authConfig = (0, config_1.getAuthConfig)();
322
+ const authPolicy = resolveAuthPolicy(modelInfo, authConfig);
323
+ if (authPolicy) {
324
+ console.log(`🔐 Auth policy detected: ${JSON.stringify(authPolicy)}`);
325
+ }
181
326
  const modelKebab = (0, model_parser_1.toKebabCase)(modelInfo.name);
182
- const functionFilePath = path.join(process.cwd(), functionsDir, "src", `${modelKebab}.ts`);
183
- // ディレクトリを作成
184
- const functionDir = path.dirname(functionFilePath);
185
- if (!fs.existsSync(functionDir)) {
186
- fs.mkdirSync(functionDir, { recursive: true });
327
+ if (backendLanguage === "typescript") {
328
+ const normalizedAuth = (0, config_1.getNormalizedAuthConfig)();
329
+ if (normalizedAuth && Object.keys(normalizedAuth.schemes).some(name => name !== "default")) {
330
+ const routerPath = path.join(process.cwd(), functionsDir, "src", "auth", "auth-router.ts");
331
+ fs.mkdirSync(path.dirname(routerPath), { recursive: true });
332
+ fs.writeFileSync(routerPath, (0, auth_generator_1.generateNamedAuthRouterTS)(normalizedAuth), "utf-8");
333
+ }
334
+ const functionFilePath = path.join(process.cwd(), functionsDir, "src", `${modelKebab}.ts`);
335
+ const functionDir = path.dirname(functionFilePath);
336
+ if (!fs.existsSync(functionDir)) {
337
+ fs.mkdirSync(functionDir, { recursive: true });
338
+ }
339
+ const code = (0, functions_generator_1.generateCompactAzureFunctionsCRUD)(modelInfo, sharedPackageName, authPolicy, authConfig?.provider);
340
+ fs.writeFileSync(functionFilePath, code, "utf-8");
341
+ console.log(`✅ Created: ${functionFilePath}`);
342
+ return;
187
343
  }
188
- // コードを生成
189
- const code = (0, functions_generator_1.generateCompactAzureFunctionsCRUD)(modelInfo, sharedPackageName);
190
- // ファイルに書き込み
191
- fs.writeFileSync(functionFilePath, code, "utf-8");
192
- console.log(`✅ Created: ${functionFilePath}`);
344
+ if (backendLanguage === "csharp") {
345
+ const normalizedAuth = (0, config_1.getNormalizedAuthConfig)();
346
+ if (normalizedAuth && Object.keys(normalizedAuth.schemes).some(name => name !== "default")) {
347
+ const routerPath = path.join(process.cwd(), functionsDir, "Auth", "AuthRouter.cs");
348
+ fs.mkdirSync(path.dirname(routerPath), { recursive: true });
349
+ fs.writeFileSync(routerPath, (0, auth_generator_1.generateNamedAuthRouterCSharp)(normalizedAuth), "utf-8");
350
+ }
351
+ const crudDir = path.join(process.cwd(), functionsDir, "Crud");
352
+ const functionFilePath = path.join(crudDir, `${modelInfo.name}CrudFunctions.cs`);
353
+ fs.mkdirSync(crudDir, { recursive: true });
354
+ // Remove init-generated template (singular) to avoid route conflicts
355
+ const templatePath = path.join(crudDir, `${modelInfo.name}Function.cs`);
356
+ if (fs.existsSync(templatePath)) {
357
+ fs.unlinkSync(templatePath);
358
+ console.log(`🗑️ Removed template: ${templatePath}`);
359
+ }
360
+ fs.writeFileSync(functionFilePath, (0, functions_generator_1.generateCSharpAzureFunctionsCRUD)(modelInfo, authPolicy), "utf-8");
361
+ console.log(`✅ Created: ${functionFilePath}`);
362
+ return;
363
+ }
364
+ const normalizedAuth = (0, config_1.getNormalizedAuthConfig)();
365
+ if (normalizedAuth && Object.keys(normalizedAuth.schemes).some(name => name !== "default")) {
366
+ const routerPath = path.join(process.cwd(), functionsDir, "auth", "auth_router.py");
367
+ fs.mkdirSync(path.dirname(routerPath), { recursive: true });
368
+ fs.writeFileSync(routerPath, (0, auth_generator_1.generateNamedAuthRouterPython)(normalizedAuth), "utf-8");
369
+ }
370
+ const blueprintsDir = path.join(process.cwd(), functionsDir, "blueprints");
371
+ const blueprintPath = path.join(blueprintsDir, `${modelKebab.replace(/-/g, "_")}.py`);
372
+ fs.mkdirSync(blueprintsDir, { recursive: true });
373
+ const { blueprint, registration } = (0, functions_generator_1.generatePythonAzureFunctionsCRUD)(modelInfo, authPolicy);
374
+ fs.writeFileSync(blueprintPath, blueprint, "utf-8");
375
+ updatePythonFunctionRegistrations(path.join(process.cwd(), functionsDir, "function_app.py"), registration);
376
+ console.log(`✅ Created: ${blueprintPath}`);
377
+ }
378
+ function createUIAuthOptions(policy, authConfig) {
379
+ const normalized = (0, config_1.normalizeAuthConfig)(authConfig);
380
+ const readPolicyName = typeof policy.read === 'string' ? policy.read : policy.policy;
381
+ const writePolicyName = typeof policy.write === 'string' ? policy.write : policy.policy;
382
+ const readNamed = readPolicyName ? normalized?.authorization.policies[readPolicyName] : undefined;
383
+ const named = writePolicyName ? normalized?.authorization.policies[writePolicyName] : undefined;
384
+ const writeRoles = named?.roles ?? (Array.isArray(policy.write) ? policy.write : policy.roles) ?? [];
385
+ const externalScheme = [...(readNamed?.schemes ?? []), ...(named?.schemes ?? [])].find(name => normalized?.schemes[name]?.provider === 'external-token');
386
+ const authScheme = readNamed?.schemes[0] ?? named?.schemes[0];
387
+ const authenticatedFetchImport = externalScheme ? `@/lib/auth/schemes/${(0, model_parser_1.toKebabCase)(externalScheme)}/authenticated-fetch` : authConfig.provider === 'external-token' ? '@/lib/auth/authenticated-fetch' : undefined;
388
+ const authContextImport = authScheme ? `@/lib/auth/schemes/${(0, model_parser_1.toKebabCase)(authScheme)}/auth-context` : '@/lib/auth/auth-context';
389
+ return { authPolicy: policy, writeRoles, authenticatedFetchImport, authContextImport };
390
+ }
391
+ /**
392
+ * コネクタモデル用 Azure Functions コード生成
393
+ */
394
+ async function generateConnectorFunctionsCode(modelInfo, functionsDir, sharedPackageName, backendLanguage) {
395
+ console.log("\n🔌 Generating Connector Azure Functions code...");
396
+ const connectorConfig = modelInfo.connectorConfig;
397
+ const connectorDef = (0, config_1.getConnectorDefinition)(connectorConfig.connector);
398
+ if (!connectorDef) {
399
+ throw new Error(`Connector '${connectorConfig.connector}' not found in swallowkit.config.js.\n` +
400
+ ` Please add it to the 'connectors' section of your configuration.`);
401
+ }
402
+ // Resolve auth policy (same logic as Cosmos model scaffolding)
403
+ const authConfig = (0, config_1.getAuthConfig)();
404
+ const authPolicy = resolveAuthPolicy(modelInfo, authConfig);
405
+ if (authPolicy) {
406
+ console.log(`🔐 Auth policy detected: ${JSON.stringify(authPolicy)}`);
407
+ }
408
+ const modelKebab = (0, model_parser_1.toKebabCase)(modelInfo.name);
409
+ if (backendLanguage === "typescript") {
410
+ const functionFilePath = path.join(process.cwd(), functionsDir, "src", `${modelKebab}.ts`);
411
+ fs.mkdirSync(path.dirname(functionFilePath), { recursive: true });
412
+ let code;
413
+ if (connectorDef.type === "rdb") {
414
+ code = (0, connector_functions_generator_1.generateRdbConnectorFunctionTS)(modelInfo, sharedPackageName, connectorDef, connectorConfig, authPolicy, authConfig?.provider);
415
+ }
416
+ else {
417
+ code = (0, connector_functions_generator_1.generateApiConnectorFunctionTS)(modelInfo, sharedPackageName, connectorDef, connectorConfig, authPolicy, authConfig?.provider);
418
+ }
419
+ fs.writeFileSync(functionFilePath, code, "utf-8");
420
+ console.log(`✅ Created: ${functionFilePath}`);
421
+ return;
422
+ }
423
+ if (backendLanguage === "csharp") {
424
+ const functionFilePath = path.join(process.cwd(), functionsDir, "Connectors", `${modelInfo.name}ConnectorFunctions.cs`);
425
+ fs.mkdirSync(path.dirname(functionFilePath), { recursive: true });
426
+ let code;
427
+ if (connectorDef.type === "rdb") {
428
+ code = (0, connector_functions_generator_1.generateRdbConnectorFunctionCSharp)(modelInfo, connectorDef, connectorConfig);
429
+ }
430
+ else {
431
+ code = (0, connector_functions_generator_1.generateApiConnectorFunctionCSharp)(modelInfo, connectorDef, connectorConfig);
432
+ }
433
+ fs.writeFileSync(functionFilePath, code, "utf-8");
434
+ console.log(`✅ Created: ${functionFilePath}`);
435
+ return;
436
+ }
437
+ // Python
438
+ const blueprintsDir = path.join(process.cwd(), functionsDir, "blueprints");
439
+ const blueprintPath = path.join(blueprintsDir, `${modelKebab.replace(/-/g, "_")}.py`);
440
+ fs.mkdirSync(blueprintsDir, { recursive: true });
441
+ let result;
442
+ if (connectorDef.type === "rdb") {
443
+ result = (0, connector_functions_generator_1.generateRdbConnectorFunctionPython)(modelInfo, connectorDef, connectorConfig);
444
+ }
445
+ else {
446
+ result = (0, connector_functions_generator_1.generateApiConnectorFunctionPython)(modelInfo, connectorDef, connectorConfig);
447
+ }
448
+ fs.writeFileSync(blueprintPath, result.blueprint, "utf-8");
449
+ updatePythonFunctionRegistrations(path.join(process.cwd(), functionsDir, "function_app.py"), result.registration);
450
+ console.log(`✅ Created: ${blueprintPath}`);
451
+ }
452
+ /**
453
+ * コネクタモデルの scaffold 後に、生成コードが必要とする RDB/API ドライバを
454
+ * functions ディレクトリにインストールする。
455
+ */
456
+ async function installConnectorDriverDependencies(modelInfo, functionsDir, backendLanguage) {
457
+ const connectorConfig = modelInfo.connectorConfig;
458
+ const connectorDef = (0, config_1.getConnectorDefinition)(connectorConfig.connector);
459
+ if (!connectorDef || connectorDef.type !== "rdb")
460
+ return;
461
+ const rdbDef = connectorDef;
462
+ const functionsPath = path.join(process.cwd(), functionsDir);
463
+ if (backendLanguage === "typescript") {
464
+ const driverMap = {
465
+ mysql: { deps: ["mysql2"], devDeps: [] },
466
+ postgres: { deps: ["pg"], devDeps: ["@types/pg"] },
467
+ sqlserver: { deps: ["mssql"], devDeps: [] },
468
+ };
469
+ const entry = driverMap[rdbDef.provider];
470
+ if (!entry)
471
+ return;
472
+ // package.json を読んで、既にインストール済みならスキップ
473
+ const pkgJsonPath = path.join(functionsPath, "package.json");
474
+ if (fs.existsSync(pkgJsonPath)) {
475
+ const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
476
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
477
+ const missingDeps = entry.deps.filter(d => !allDeps[d]);
478
+ const missingDevDeps = entry.devDeps.filter(d => !allDeps[d]);
479
+ if (missingDeps.length === 0 && missingDevDeps.length === 0)
480
+ return;
481
+ }
482
+ console.log(`\n📦 Installing ${rdbDef.provider} driver dependencies...`);
483
+ const pm = (0, package_manager_1.detectFromProject)(functionsPath);
484
+ const cmds = (0, package_manager_1.getCommands)(pm);
485
+ if (entry.deps.length > 0) {
486
+ runSpawnSyncCommand(cmds.name, [pm === "pnpm" ? "add" : "install", ...entry.deps], functionsPath);
487
+ }
488
+ if (entry.devDeps.length > 0) {
489
+ runSpawnSyncCommand(cmds.name, [pm === "pnpm" ? "add" : "install", "-D", ...entry.devDeps], functionsPath);
490
+ }
491
+ console.log(`✅ ${rdbDef.provider} driver installed`);
492
+ return;
493
+ }
494
+ if (backendLanguage === "csharp") {
495
+ const nugetMap = {
496
+ mysql: "MySqlConnector",
497
+ postgres: "Npgsql",
498
+ sqlserver: "Microsoft.Data.SqlClient",
499
+ };
500
+ const pkg = nugetMap[rdbDef.provider];
501
+ if (!pkg)
502
+ return;
503
+ console.log(`\n📦 Installing ${rdbDef.provider} NuGet package...`);
504
+ runSpawnSyncCommand("dotnet", ["add", path.join(functionsPath, "functions.csproj"), "package", pkg], functionsPath);
505
+ console.log(`✅ ${pkg} installed`);
506
+ return;
507
+ }
508
+ // Python — requirements.txt に追記
509
+ const pipMap = {
510
+ mysql: "mysql-connector-python",
511
+ postgres: "psycopg2-binary",
512
+ sqlserver: "pymssql",
513
+ };
514
+ const pipPkg = pipMap[rdbDef.provider];
515
+ if (!pipPkg)
516
+ return;
517
+ const reqPath = path.join(functionsPath, "requirements.txt");
518
+ if (fs.existsSync(reqPath)) {
519
+ const existing = fs.readFileSync(reqPath, "utf-8");
520
+ if (!existing.includes(pipPkg)) {
521
+ console.log(`\n📦 Adding ${pipPkg} to requirements.txt...`);
522
+ fs.appendFileSync(reqPath, `\n${pipPkg}\n`);
523
+ console.log(`✅ ${pipPkg} added`);
524
+ }
525
+ }
526
+ }
527
+ /**
528
+ * コネクタモデル用 BFF ルート生成(操作制限に対応)
529
+ */
530
+ async function generateConnectorBFFRoutesFiles(modelInfo, apiDir, sharedPackageName) {
531
+ console.log("\n🔌 Generating Connector BFF API routes...");
532
+ const modelCamel = (0, model_parser_1.toCamelCase)(modelInfo.name);
533
+ const operations = modelInfo.connectorConfig.operations;
534
+ const listRoutePath = path.join(process.cwd(), apiDir, modelCamel, "route.ts");
535
+ const detailRoutePath = path.join(process.cwd(), apiDir, modelCamel, "[id]", "route.ts");
536
+ fs.mkdirSync(path.dirname(listRoutePath), { recursive: true });
537
+ fs.mkdirSync(path.dirname(detailRoutePath), { recursive: true });
538
+ const routes = (0, nextjs_generator_1.generateConnectorBFFRoutes)(modelInfo, sharedPackageName, operations);
539
+ fs.writeFileSync(listRoutePath, routes.listRoute, "utf-8");
540
+ fs.writeFileSync(detailRoutePath, routes.detailRoute, "utf-8");
541
+ console.log(`✅ Created: ${listRoutePath}`);
542
+ console.log(`✅ Created: ${detailRoutePath}`);
543
+ }
544
+ async function collectModelGraph(modelPath, seen = new Map()) {
545
+ const resolvedPath = path.resolve(modelPath);
546
+ if (seen.has(resolvedPath)) {
547
+ return Array.from(seen.values());
548
+ }
549
+ const modelInfo = await (0, model_parser_1.parseModelFile)(resolvedPath);
550
+ seen.set(resolvedPath, modelInfo);
551
+ for (const ref of modelInfo.nestedSchemaRefs) {
552
+ const nestedPath = resolveNestedModelPath(resolvedPath, ref.importPath);
553
+ await collectModelGraph(nestedPath, seen);
554
+ }
555
+ return Array.from(seen.values());
556
+ }
557
+ function resolveNestedModelPath(modelPath, importPath) {
558
+ let resolvedPath = path.resolve(path.dirname(modelPath), importPath);
559
+ if (!resolvedPath.endsWith(".ts")) {
560
+ resolvedPath += ".ts";
561
+ }
562
+ return resolvedPath;
563
+ }
564
+ function describeFunctionsOutputPath(functionsDir, backendLanguage) {
565
+ if (backendLanguage === "typescript") {
566
+ return `${functionsDir}/src/`;
567
+ }
568
+ if (backendLanguage === "csharp") {
569
+ return `${functionsDir}/Crud/`;
570
+ }
571
+ return `${functionsDir}/blueprints/`;
572
+ }
573
+ function updatePythonFunctionRegistrations(functionAppPath, registration) {
574
+ if (!fs.existsSync(functionAppPath)) {
575
+ throw new Error(`Python Functions entrypoint not found: ${functionAppPath}`);
576
+ }
577
+ const content = fs.readFileSync(functionAppPath, "utf-8");
578
+ // Check if import line already exists (handles init-generated layout)
579
+ const importLine = registration.split("\n").find((l) => l.startsWith("from ") || l.startsWith("import "));
580
+ if (importLine && content.includes(importLine)) {
581
+ return;
582
+ }
583
+ if (content.includes(registration)) {
584
+ return;
585
+ }
586
+ const marker = "# SwallowKit scaffold registrations";
587
+ if (!content.includes(marker)) {
588
+ throw new Error(`Could not find scaffold registration marker in ${functionAppPath}`);
589
+ }
590
+ const updated = content.replace(marker, `${registration}\n${marker}`);
591
+ fs.writeFileSync(functionAppPath, updated, "utf-8");
193
592
  }
194
593
  /**
195
594
  * Next.js BFF API Routes を生成
196
595
  */
197
596
  async function generateBFFRoutes(modelInfo, apiDir, sharedPackageName) {
198
597
  console.log("\n🔨 Generating Next.js BFF API routes...");
199
- const modelKebab = (0, model_parser_1.toKebabCase)(modelInfo.name);
200
598
  const modelCamel = modelInfo.name.charAt(0).toLowerCase() + modelInfo.name.slice(1);
201
599
  // List route: app/api/[model]/route.ts
202
600
  const listRoutePath = path.join(process.cwd(), apiDir, modelCamel, "route.ts");
@@ -241,70 +639,61 @@ async function generateCosmosContainer(modelInfo) {
241
639
  // Generate container Bicep file
242
640
  const containerFileName = `${modelKebab}-container.bicep`;
243
641
  const containerFilePath = path.join(containersDir, containerFileName);
244
- const bicepContent = `@description('Cosmos DB account name')
245
- param cosmosAccountName string
246
-
247
- @description('Database name')
248
- param databaseName string
249
-
250
- @description('Container name')
251
- param containerName string = '${modelPascal}s'
252
-
253
- @description('Partition key path')
254
- param partitionKeyPath string = '/id'
255
-
256
- @description('Throughput (RU/s) - only used for Free Tier')
257
- param throughput int = 400
258
-
259
- @description('Cosmos DB mode: freetier or serverless')
260
- param cosmosDbMode string
261
-
262
- // Reference existing Cosmos DB account
263
- resource cosmosAccount 'Microsoft.DocumentDB/databaseAccounts@2023-11-15' existing = {
264
- name: cosmosAccountName
265
- }
266
-
267
- // Reference existing database
268
- resource database 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2023-11-15' existing = {
269
- parent: cosmosAccount
270
- name: databaseName
271
- }
272
-
273
- // Container for ${modelPascal}
274
- resource container 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2023-11-15' = {
275
- parent: database
276
- name: containerName
277
- properties: {
278
- resource: {
279
- id: containerName
280
- partitionKey: {
281
- paths: [
282
- partitionKeyPath
283
- ]
284
- kind: 'Hash'
285
- }
286
- indexingPolicy: {
287
- automatic: true
288
- indexingMode: 'consistent'
289
- includedPaths: [
290
- {
291
- path: '/*'
292
- }
293
- ]
294
- excludedPaths: [
295
- {
296
- path: '/_etag/?'
297
- }
298
- ]
299
- }
300
- }
301
- options: cosmosDbMode == 'freetier' ? {
302
- throughput: throughput
303
- } : {}
304
- }
305
- }
306
-
307
- output containerName string = container.name
642
+ const bicepContent = `@description('Cosmos DB account name')
643
+ param cosmosAccountName string
644
+
645
+ @description('Database name')
646
+ param databaseName string
647
+
648
+ @description('Container name')
649
+ param containerName string = '${modelPascal}s'
650
+
651
+ @description('Partition key path')
652
+ param partitionKeyPath string = '${modelInfo.partitionKey}'
653
+
654
+ // Reference existing Cosmos DB account
655
+ resource cosmosAccount 'Microsoft.DocumentDB/databaseAccounts@2023-11-15' existing = {
656
+ name: cosmosAccountName
657
+ }
658
+
659
+ // Reference existing database
660
+ resource database 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2023-11-15' existing = {
661
+ parent: cosmosAccount
662
+ name: databaseName
663
+ }
664
+
665
+ // Container for ${modelPascal}
666
+ resource container 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2023-11-15' = {
667
+ parent: database
668
+ name: containerName
669
+ properties: {
670
+ resource: {
671
+ id: containerName
672
+ partitionKey: {
673
+ paths: [
674
+ partitionKeyPath
675
+ ]
676
+ kind: 'Hash'
677
+ }
678
+ indexingPolicy: {
679
+ automatic: true
680
+ indexingMode: 'consistent'
681
+ includedPaths: [
682
+ {
683
+ path: '/*'
684
+ }
685
+ ]
686
+ excludedPaths: [
687
+ {
688
+ path: '/_etag/?'
689
+ }
690
+ ]
691
+ }
692
+ }
693
+ }
694
+ }
695
+
696
+ output containerName string = container.name
308
697
  `;
309
698
  // Write Bicep file (overwrite if exists)
310
699
  fs.writeFileSync(containerFilePath, bicepContent, "utf-8");
@@ -324,7 +713,7 @@ async function updateMainBicepWithContainer(modelKebab, modelPascal) {
324
713
  }
325
714
  let mainBicepContent = fs.readFileSync(mainBicepPath, "utf-8");
326
715
  // Check if container module already exists
327
- const containerModuleName = `${modelKebab.replace(/-/g, '')}Container`;
716
+ const containerModuleName = `${modelPascal.charAt(0).toLowerCase()}${modelPascal.slice(1)}Container`;
328
717
  if (mainBicepContent.includes(`module ${containerModuleName}`)) {
329
718
  console.log(`ℹ️ Container module '${containerModuleName}' already exists in main.bicep`);
330
719
  return;
@@ -335,37 +724,32 @@ async function updateMainBicepWithContainer(modelKebab, modelPascal) {
335
724
  const cosmosModuleMatch = mainBicepContent.match(cosmosModulePattern);
336
725
  if (!cosmosModuleMatch) {
337
726
  console.log("⚠️ Could not find Cosmos DB Serverless module in main.bicep. Please manually add the container module:");
338
- console.log(`\nmodule ${containerModuleName} 'containers/${modelKebab}-container.bicep' = {
339
- name: '${modelKebab}-container'
340
- params: {
341
- cosmosAccountName: cosmosDbMode == 'freetier' ? cosmosDbFreeTier.outputs.accountName : cosmosDbServerless.outputs.accountName
342
- databaseName: databaseName
343
- cosmosDbMode: cosmosDbMode
344
- }
345
- dependsOn: [
346
- cosmosDbFreeTier
347
- cosmosDbServerless
348
- ]
727
+ console.log(`\nmodule ${containerModuleName} 'containers/${modelKebab}-container.bicep' = {
728
+ name: '${modelKebab}-container'
729
+ params: {
730
+ cosmosAccountName: cosmosDbMode == 'freetier' ? cosmosDbFreeTier.outputs.accountName : cosmosDbServerless.outputs.accountName
731
+ databaseName: databaseName
732
+ cosmosDbMode: cosmosDbMode
733
+ }
734
+ dependsOn: [
735
+ cosmosDbFreeTier
736
+ cosmosDbServerless
737
+ ]
349
738
  }\n`);
350
739
  return;
351
740
  }
352
741
  // Find the end of the cosmosDbServerless module
353
742
  const insertPosition = cosmosModuleMatch.index + cosmosModuleMatch[0].length;
354
743
  // Create the container module declaration
355
- const containerModule = `
356
-
357
- // ${modelPascal} Container
358
- module ${containerModuleName} 'containers/${modelKebab}-container.bicep' = {
359
- name: '${modelKebab}-container'
360
- params: {
361
- cosmosAccountName: cosmosDbMode == 'freetier' ? cosmosDbFreeTier.outputs.accountName : cosmosDbServerless.outputs.accountName
362
- databaseName: '$` + `{projectName}Database'
363
- cosmosDbMode: cosmosDbMode
364
- }
365
- dependsOn: [
366
- cosmosDbFreeTier
367
- cosmosDbServerless
368
- ]
744
+ const containerModule = `
745
+
746
+ // ${modelPascal} Container
747
+ module ${containerModuleName} 'containers/${modelKebab}-container.bicep' = {
748
+ name: '${modelKebab}-container'
749
+ params: {
750
+ cosmosAccountName: cosmosDbMode == 'freetier' ? cosmosDbFreeTier.outputs.accountName : cosmosDbServerless.outputs.accountName
751
+ databaseName: '\${projectName}Database'
752
+ }
369
753
  }`;
370
754
  // Insert the module
371
755
  mainBicepContent =
@@ -378,7 +762,7 @@ module ${containerModuleName} 'containers/${modelKebab}-container.bicep' = {
378
762
  /**
379
763
  * Generate UI components (list, detail, form, create, edit pages)
380
764
  */
381
- async function generateUIComponents(modelInfo, sharedPackageName) {
765
+ async function generateUIComponents(modelInfo, sharedPackageName, authOptions) {
382
766
  console.log("\n🎨 Generating UI components...");
383
767
  const modelKebab = (0, model_parser_1.toKebabCase)(modelInfo.name);
384
768
  const modelName = modelInfo.name;
@@ -396,11 +780,11 @@ async function generateUIComponents(modelInfo, sharedPackageName) {
396
780
  }
397
781
  });
398
782
  // Generate and write files
399
- const listPage = (0, ui_generator_1.generateListPage)(modelInfo, sharedPackageName);
400
- const detailPage = (0, ui_generator_1.generateDetailPage)(modelInfo, sharedPackageName);
783
+ const listPage = (0, ui_generator_1.generateListPage)(modelInfo, sharedPackageName, authOptions);
784
+ const detailPage = (0, ui_generator_1.generateDetailPage)(modelInfo, sharedPackageName, authOptions);
401
785
  const formComponent = (0, ui_generator_1.generateFormComponent)(modelInfo, sharedPackageName);
402
- const newPage = (0, ui_generator_1.generateNewPage)(modelInfo);
403
- const editPage = (0, ui_generator_1.generateEditPage)(modelInfo, sharedPackageName);
786
+ const newPage = (0, ui_generator_1.generateNewPage)(modelInfo, authOptions);
787
+ const editPage = (0, ui_generator_1.generateEditPage)(modelInfo, sharedPackageName, authOptions);
404
788
  fs.writeFileSync(path.join(modelDir, "page.tsx"), listPage, "utf-8");
405
789
  fs.writeFileSync(path.join(idDir, "page.tsx"), detailPage, "utf-8");
406
790
  fs.writeFileSync(path.join(componentsDir, `${modelName}Form.tsx`), formComponent, "utf-8");
@@ -455,17 +839,17 @@ async function updateNavigationMenu(modelInfo) {
455
839
  label: modelInfo.displayName
456
840
  });
457
841
  // Generate new scaffold-config.ts content
458
- const newConfigContent = `export interface ScaffoldModel {
459
- name: string;
460
- path: string;
461
- label: string;
462
- }
463
-
464
- export const scaffoldConfig = {
465
- models: [
466
- ${models.map(m => ` { name: '${m.name}', path: '${m.path}', label: '${m.label}' },`).join('\n')}
467
- ] as ScaffoldModel[]
468
- };
842
+ const newConfigContent = `export interface ScaffoldModel {
843
+ name: string;
844
+ path: string;
845
+ label: string;
846
+ }
847
+
848
+ export const scaffoldConfig = {
849
+ models: [
850
+ ${models.map(m => ` { name: '${m.name}', path: '${m.path}', label: '${m.label}' },`).join('\n')}
851
+ ] as ScaffoldModel[]
852
+ };
469
853
  `;
470
854
  fs.writeFileSync(configPath, newConfigContent, "utf-8");
471
855
  console.log(`✅ Added ${modelInfo.name} to navigation menu`);