swallowkit 1.0.0-beta.4 → 1.0.0-beta.40

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