swallowkit 1.0.0-beta.6 → 1.0.0-beta.61

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 (296) hide show
  1. package/README.ja.md +87 -175
  2. package/README.md +87 -176
  3. package/dist/__tests__/fixtures.d.ts +12 -0
  4. package/dist/__tests__/fixtures.d.ts.map +1 -1
  5. package/dist/__tests__/fixtures.js +101 -0
  6. package/dist/__tests__/fixtures.js.map +1 -1
  7. package/dist/cli/commands/add-auth.d.ts +15 -0
  8. package/dist/cli/commands/add-auth.d.ts.map +1 -0
  9. package/dist/cli/commands/add-auth.js +790 -0
  10. package/dist/cli/commands/add-auth.js.map +1 -0
  11. package/dist/cli/commands/add-connector.d.ts +20 -0
  12. package/dist/cli/commands/add-connector.d.ts.map +1 -0
  13. package/dist/cli/commands/add-connector.js +164 -0
  14. package/dist/cli/commands/add-connector.js.map +1 -0
  15. package/dist/cli/commands/cosmos-worktree-isolation.d.ts +25 -0
  16. package/dist/cli/commands/cosmos-worktree-isolation.d.ts.map +1 -0
  17. package/dist/cli/commands/cosmos-worktree-isolation.js +124 -0
  18. package/dist/cli/commands/cosmos-worktree-isolation.js.map +1 -0
  19. package/dist/cli/commands/create-model.d.ts +1 -4
  20. package/dist/cli/commands/create-model.d.ts.map +1 -1
  21. package/dist/cli/commands/create-model.js +20 -82
  22. package/dist/cli/commands/create-model.js.map +1 -1
  23. package/dist/cli/commands/dev-seeds.d.ts +62 -0
  24. package/dist/cli/commands/dev-seeds.d.ts.map +1 -0
  25. package/dist/cli/commands/dev-seeds.js +474 -0
  26. package/dist/cli/commands/dev-seeds.js.map +1 -0
  27. package/dist/cli/commands/dev.d.ts +38 -0
  28. package/dist/cli/commands/dev.d.ts.map +1 -1
  29. package/dist/cli/commands/dev.js +733 -155
  30. package/dist/cli/commands/dev.js.map +1 -1
  31. package/dist/cli/commands/index.d.ts +1 -0
  32. package/dist/cli/commands/index.d.ts.map +1 -1
  33. package/dist/cli/commands/index.js +3 -1
  34. package/dist/cli/commands/index.js.map +1 -1
  35. package/dist/cli/commands/init.d.ts +74 -0
  36. package/dist/cli/commands/init.d.ts.map +1 -1
  37. package/dist/cli/commands/init.js +1376 -930
  38. package/dist/cli/commands/init.js.map +1 -1
  39. package/dist/cli/commands/scaffold.d.ts +24 -0
  40. package/dist/cli/commands/scaffold.d.ts.map +1 -1
  41. package/dist/cli/commands/scaffold.js +473 -90
  42. package/dist/cli/commands/scaffold.js.map +1 -1
  43. package/dist/cli/index.d.ts +5 -1
  44. package/dist/cli/index.d.ts.map +1 -1
  45. package/dist/cli/index.js +335 -55
  46. package/dist/cli/index.js.map +1 -1
  47. package/dist/core/config.d.ts +14 -3
  48. package/dist/core/config.d.ts.map +1 -1
  49. package/dist/core/config.js +179 -8
  50. package/dist/core/config.js.map +1 -1
  51. package/dist/core/mock/connector-mock-server.d.ts +101 -0
  52. package/dist/core/mock/connector-mock-server.d.ts.map +1 -0
  53. package/dist/core/mock/connector-mock-server.js +480 -0
  54. package/dist/core/mock/connector-mock-server.js.map +1 -0
  55. package/dist/core/mock/zod-mock-generator.d.ts +14 -0
  56. package/dist/core/mock/zod-mock-generator.d.ts.map +1 -0
  57. package/dist/core/mock/zod-mock-generator.js +163 -0
  58. package/dist/core/mock/zod-mock-generator.js.map +1 -0
  59. package/dist/core/operations/auth-operations.d.ts +61 -0
  60. package/dist/core/operations/auth-operations.d.ts.map +1 -0
  61. package/dist/core/operations/auth-operations.js +272 -0
  62. package/dist/core/operations/auth-operations.js.map +1 -0
  63. package/dist/core/operations/create-model.d.ts +15 -0
  64. package/dist/core/operations/create-model.d.ts.map +1 -0
  65. package/dist/core/operations/create-model.js +185 -0
  66. package/dist/core/operations/create-model.js.map +1 -0
  67. package/dist/core/operations/file-session.d.ts +56 -0
  68. package/dist/core/operations/file-session.d.ts.map +1 -0
  69. package/dist/core/operations/file-session.js +203 -0
  70. package/dist/core/operations/file-session.js.map +1 -0
  71. package/dist/core/operations/provision-operations.d.ts +52 -0
  72. package/dist/core/operations/provision-operations.d.ts.map +1 -0
  73. package/dist/core/operations/provision-operations.js +211 -0
  74. package/dist/core/operations/provision-operations.js.map +1 -0
  75. package/dist/core/operations/runtime.d.ts +37 -0
  76. package/dist/core/operations/runtime.d.ts.map +1 -0
  77. package/dist/core/operations/runtime.js +248 -0
  78. package/dist/core/operations/runtime.js.map +1 -0
  79. package/dist/core/operations/scaffold-apply.d.ts +21 -0
  80. package/dist/core/operations/scaffold-apply.d.ts.map +1 -0
  81. package/dist/core/operations/scaffold-apply.js +57 -0
  82. package/dist/core/operations/scaffold-apply.js.map +1 -0
  83. package/dist/core/operations/scaffold-machine.d.ts +16 -0
  84. package/dist/core/operations/scaffold-machine.d.ts.map +1 -0
  85. package/dist/core/operations/scaffold-machine.js +51 -0
  86. package/dist/core/operations/scaffold-machine.js.map +1 -0
  87. package/dist/core/operations/scaffold-plan.d.ts +43 -0
  88. package/dist/core/operations/scaffold-plan.d.ts.map +1 -0
  89. package/dist/core/operations/scaffold-plan.js +187 -0
  90. package/dist/core/operations/scaffold-plan.js.map +1 -0
  91. package/dist/core/project/agent-instructions.d.ts +29 -0
  92. package/dist/core/project/agent-instructions.d.ts.map +1 -0
  93. package/dist/core/project/agent-instructions.js +160 -0
  94. package/dist/core/project/agent-instructions.js.map +1 -0
  95. package/dist/core/project/artifacts.d.ts +58 -0
  96. package/dist/core/project/artifacts.d.ts.map +1 -0
  97. package/dist/core/project/artifacts.js +179 -0
  98. package/dist/core/project/artifacts.js.map +1 -0
  99. package/dist/core/project/boundaries.d.ts +50 -0
  100. package/dist/core/project/boundaries.d.ts.map +1 -0
  101. package/dist/core/project/boundaries.js +117 -0
  102. package/dist/core/project/boundaries.js.map +1 -0
  103. package/dist/core/project/capabilities.d.ts +58 -0
  104. package/dist/core/project/capabilities.d.ts.map +1 -0
  105. package/dist/core/project/capabilities.js +135 -0
  106. package/dist/core/project/capabilities.js.map +1 -0
  107. package/dist/core/project/drift.d.ts +29 -0
  108. package/dist/core/project/drift.d.ts.map +1 -0
  109. package/dist/core/project/drift.js +188 -0
  110. package/dist/core/project/drift.js.map +1 -0
  111. package/dist/core/project/infra.d.ts +49 -0
  112. package/dist/core/project/infra.d.ts.map +1 -0
  113. package/dist/core/project/infra.js +167 -0
  114. package/dist/core/project/infra.js.map +1 -0
  115. package/dist/core/project/manifest.d.ts +92 -0
  116. package/dist/core/project/manifest.d.ts.map +1 -0
  117. package/dist/core/project/manifest.js +321 -0
  118. package/dist/core/project/manifest.js.map +1 -0
  119. package/dist/core/project/state.d.ts +15 -0
  120. package/dist/core/project/state.d.ts.map +1 -0
  121. package/dist/core/project/state.js +106 -0
  122. package/dist/core/project/state.js.map +1 -0
  123. package/dist/core/project/validation.d.ts +20 -0
  124. package/dist/core/project/validation.d.ts.map +1 -0
  125. package/dist/core/project/validation.js +274 -0
  126. package/dist/core/project/validation.js.map +1 -0
  127. package/dist/core/project/workflows.d.ts +43 -0
  128. package/dist/core/project/workflows.d.ts.map +1 -0
  129. package/dist/core/project/workflows.js +299 -0
  130. package/dist/core/project/workflows.js.map +1 -0
  131. package/dist/core/scaffold/auth-generator.d.ts +63 -0
  132. package/dist/core/scaffold/auth-generator.d.ts.map +1 -0
  133. package/dist/core/scaffold/auth-generator.js +1774 -0
  134. package/dist/core/scaffold/auth-generator.js.map +1 -0
  135. package/dist/core/scaffold/connector-functions-generator.d.ts +41 -0
  136. package/dist/core/scaffold/connector-functions-generator.d.ts.map +1 -0
  137. package/dist/core/scaffold/connector-functions-generator.js +1029 -0
  138. package/dist/core/scaffold/connector-functions-generator.js.map +1 -0
  139. package/dist/core/scaffold/functions-generator.d.ts +7 -1
  140. package/dist/core/scaffold/functions-generator.d.ts.map +1 -1
  141. package/dist/core/scaffold/functions-generator.js +771 -69
  142. package/dist/core/scaffold/functions-generator.js.map +1 -1
  143. package/dist/core/scaffold/model-parser.d.ts +21 -1
  144. package/dist/core/scaffold/model-parser.d.ts.map +1 -1
  145. package/dist/core/scaffold/model-parser.js +230 -79
  146. package/dist/core/scaffold/model-parser.js.map +1 -1
  147. package/dist/core/scaffold/native-schema-generator.d.ts +19 -0
  148. package/dist/core/scaffold/native-schema-generator.d.ts.map +1 -0
  149. package/dist/core/scaffold/native-schema-generator.js +753 -0
  150. package/dist/core/scaffold/native-schema-generator.js.map +1 -0
  151. package/dist/core/scaffold/nextjs-generator.d.ts +8 -0
  152. package/dist/core/scaffold/nextjs-generator.d.ts.map +1 -1
  153. package/dist/core/scaffold/nextjs-generator.js +153 -9
  154. package/dist/core/scaffold/nextjs-generator.js.map +1 -1
  155. package/dist/core/scaffold/openapi-generator.d.ts +3 -0
  156. package/dist/core/scaffold/openapi-generator.d.ts.map +1 -0
  157. package/dist/core/scaffold/openapi-generator.js +190 -0
  158. package/dist/core/scaffold/openapi-generator.js.map +1 -0
  159. package/dist/core/scaffold/ui-generator.d.ts +17 -4
  160. package/dist/core/scaffold/ui-generator.d.ts.map +1 -1
  161. package/dist/core/scaffold/ui-generator.js +174 -34
  162. package/dist/core/scaffold/ui-generator.js.map +1 -1
  163. package/dist/core/verify/index.d.ts +69 -0
  164. package/dist/core/verify/index.d.ts.map +1 -0
  165. package/dist/core/verify/index.js +412 -0
  166. package/dist/core/verify/index.js.map +1 -0
  167. package/dist/database/base-model.d.ts +3 -3
  168. package/dist/database/base-model.js +3 -3
  169. package/dist/index.d.ts +2 -2
  170. package/dist/index.d.ts.map +1 -1
  171. package/dist/index.js +2 -1
  172. package/dist/index.js.map +1 -1
  173. package/dist/machine/contracts.d.ts +24 -0
  174. package/dist/machine/contracts.d.ts.map +1 -0
  175. package/dist/machine/contracts.js +3 -0
  176. package/dist/machine/contracts.js.map +1 -0
  177. package/dist/machine/errors.d.ts +14 -0
  178. package/dist/machine/errors.d.ts.map +1 -0
  179. package/dist/machine/errors.js +42 -0
  180. package/dist/machine/errors.js.map +1 -0
  181. package/dist/machine/index.d.ts +3 -0
  182. package/dist/machine/index.d.ts.map +1 -0
  183. package/dist/machine/index.js +566 -0
  184. package/dist/machine/index.js.map +1 -0
  185. package/dist/mcp/index.d.ts +25 -0
  186. package/dist/mcp/index.d.ts.map +1 -0
  187. package/dist/mcp/index.js +425 -0
  188. package/dist/mcp/index.js.map +1 -0
  189. package/dist/types/index.d.ts +109 -0
  190. package/dist/types/index.d.ts.map +1 -1
  191. package/dist/utils/package-manager.d.ts +9 -12
  192. package/dist/utils/package-manager.d.ts.map +1 -1
  193. package/dist/utils/package-manager.js +51 -22
  194. package/dist/utils/package-manager.js.map +1 -1
  195. package/dist/utils/python-uv.d.ts +21 -0
  196. package/dist/utils/python-uv.d.ts.map +1 -0
  197. package/dist/utils/python-uv.js +111 -0
  198. package/dist/utils/python-uv.js.map +1 -0
  199. package/dist/version.d.ts +2 -0
  200. package/dist/version.d.ts.map +1 -0
  201. package/dist/version.js +49 -0
  202. package/dist/version.js.map +1 -0
  203. package/package.json +16 -17
  204. package/src/__tests__/__snapshots__/functions-generator.test.ts.snap +1139 -0
  205. package/src/__tests__/__snapshots__/nextjs-generator.test.ts.snap +206 -0
  206. package/src/__tests__/__snapshots__/ui-generator.test.ts.snap +532 -0
  207. package/src/__tests__/agent-instructions.test.ts +237 -0
  208. package/src/__tests__/agent-loop-phase34.test.ts +513 -0
  209. package/src/__tests__/auth.test.ts +770 -0
  210. package/src/__tests__/bff-swa-auth.test.ts +275 -0
  211. package/src/__tests__/config.test.ts +295 -0
  212. package/src/__tests__/connector-functions-generator.test.ts +288 -0
  213. package/src/__tests__/connector-mock-server.test.ts +459 -0
  214. package/src/__tests__/connector-model-bff.test.ts +162 -0
  215. package/src/__tests__/cosmos-worktree-isolation.test.ts +149 -0
  216. package/src/__tests__/dev-seeds.test.ts +236 -0
  217. package/src/__tests__/dev.test.ts +296 -0
  218. package/src/__tests__/file-session.test.ts +98 -0
  219. package/src/__tests__/fixtures.ts +184 -0
  220. package/src/__tests__/functions-generator.test.ts +237 -0
  221. package/src/__tests__/init.test.ts +363 -0
  222. package/src/__tests__/jest-globals.d.ts +8 -0
  223. package/src/__tests__/machine.test.ts +371 -0
  224. package/src/__tests__/mcp.test.ts +253 -0
  225. package/src/__tests__/model-parser.test.ts +142 -0
  226. package/src/__tests__/multi-auth.test.ts +160 -0
  227. package/src/__tests__/nextjs-generator.test.ts +105 -0
  228. package/src/__tests__/openapi-generator.test.ts +43 -0
  229. package/src/__tests__/package-manager.test.ts +244 -0
  230. package/src/__tests__/plan-apply.test.ts +499 -0
  231. package/src/__tests__/python-uv.test.ts +48 -0
  232. package/src/__tests__/scaffold.test.ts +302 -0
  233. package/src/__tests__/string-utils.test.ts +75 -0
  234. package/src/__tests__/tsconfig.json +15 -0
  235. package/src/__tests__/ui-generator.test.ts +248 -0
  236. package/src/__tests__/zod-mock-generator.test.ts +132 -0
  237. package/src/cli/commands/add-auth.ts +853 -0
  238. package/src/cli/commands/add-connector.ts +159 -0
  239. package/src/cli/commands/cosmos-worktree-isolation.ts +130 -0
  240. package/src/cli/commands/create-model.ts +62 -0
  241. package/src/cli/commands/dev-seeds.ts +629 -0
  242. package/src/cli/commands/dev.ts +1252 -0
  243. package/src/cli/commands/index.ts +9 -0
  244. package/src/cli/commands/init.ts +3414 -0
  245. package/src/cli/commands/provision.ts +193 -0
  246. package/src/cli/commands/scaffold.ts +1081 -0
  247. package/src/cli/index.ts +352 -0
  248. package/src/core/config.ts +380 -0
  249. package/src/core/mock/connector-mock-server.ts +555 -0
  250. package/src/core/mock/zod-mock-generator.ts +205 -0
  251. package/src/core/operations/auth-operations.ts +360 -0
  252. package/src/core/operations/create-model.ts +191 -0
  253. package/src/core/operations/file-session.ts +222 -0
  254. package/src/core/operations/provision-operations.ts +297 -0
  255. package/src/core/operations/runtime.ts +258 -0
  256. package/src/core/operations/scaffold-apply.ts +106 -0
  257. package/src/core/operations/scaffold-machine.ts +77 -0
  258. package/src/core/operations/scaffold-plan.ts +215 -0
  259. package/src/core/project/agent-instructions.ts +167 -0
  260. package/src/core/project/agent-templates/AGENTS.md +504 -0
  261. package/src/core/project/agent-templates/CLAUDE.md +31 -0
  262. package/src/core/project/agent-templates/azure-functions.instructions.md +22 -0
  263. package/src/core/project/agent-templates/bff-routes.instructions.md +18 -0
  264. package/src/core/project/agent-templates/copilot-instructions.md +27 -0
  265. package/src/core/project/agent-templates/shared-models.instructions.md +72 -0
  266. package/src/core/project/artifacts.ts +205 -0
  267. package/src/core/project/boundaries.ts +170 -0
  268. package/src/core/project/capabilities.ts +193 -0
  269. package/src/core/project/drift.ts +202 -0
  270. package/src/core/project/infra.ts +196 -0
  271. package/src/core/project/manifest.ts +402 -0
  272. package/src/core/project/state.ts +77 -0
  273. package/src/core/project/validation.ts +295 -0
  274. package/src/core/project/workflows.ts +295 -0
  275. package/src/core/scaffold/auth-generator.ts +1824 -0
  276. package/src/core/scaffold/connector-functions-generator.ts +1133 -0
  277. package/src/core/scaffold/functions-generator.ts +972 -0
  278. package/src/core/scaffold/model-parser.ts +781 -0
  279. package/src/core/scaffold/native-schema-generator.ts +904 -0
  280. package/src/core/scaffold/nextjs-generator.ts +382 -0
  281. package/src/core/scaffold/openapi-generator.ts +212 -0
  282. package/src/core/scaffold/ui-generator.ts +1100 -0
  283. package/src/core/verify/index.ts +468 -0
  284. package/src/database/base-model.ts +184 -0
  285. package/src/database/client.ts +140 -0
  286. package/src/database/repository.ts +104 -0
  287. package/src/database/runtime-check.ts +25 -0
  288. package/src/index.ts +27 -0
  289. package/src/machine/contracts.ts +33 -0
  290. package/src/machine/errors.ts +45 -0
  291. package/src/machine/index.ts +690 -0
  292. package/src/mcp/index.ts +423 -0
  293. package/src/types/index.ts +182 -0
  294. package/src/utils/package-manager.ts +251 -0
  295. package/src/utils/python-uv.ts +96 -0
  296. package/src/version.ts +13 -0
@@ -38,17 +38,51 @@ 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.resolveModelPath = resolveModelPath;
43
+ exports.createUIAuthOptions = createUIAuthOptions;
44
+ exports.generateCosmosContainer = generateCosmosContainer;
45
+ exports.generateUIComponents = generateUIComponents;
41
46
  const fs = __importStar(require("fs"));
42
47
  const path = __importStar(require("path"));
48
+ const child_process_1 = require("child_process");
49
+ const config_1 = require("../../core/config");
43
50
  const model_parser_1 = require("../../core/scaffold/model-parser");
44
51
  const functions_generator_1 = require("../../core/scaffold/functions-generator");
52
+ const connector_functions_generator_1 = require("../../core/scaffold/connector-functions-generator");
45
53
  const nextjs_generator_1 = require("../../core/scaffold/nextjs-generator");
46
54
  const ui_generator_1 = require("../../core/scaffold/ui-generator");
47
- const config_1 = require("../../core/config");
55
+ const native_schema_generator_1 = require("../../core/scaffold/native-schema-generator");
48
56
  const package_manager_1 = require("../../utils/package-manager");
57
+ const manifest_1 = require("../../core/project/manifest");
58
+ const auth_generator_1 = require("../../core/scaffold/auth-generator");
59
+ const file_session_1 = require("../../core/operations/file-session");
60
+ const artifacts_1 = require("../../core/project/artifacts");
61
+ const SCAFFOLD_GENERATOR = "scaffold";
62
+ function getMachineAwareStdio() {
63
+ return process.env.SWALLOWKIT_MACHINE_OUTPUT === "1" ? "pipe" : "inherit";
64
+ }
65
+ function runSpawnSyncCommand(command, args, cwd) {
66
+ const result = (0, child_process_1.spawnSync)(command, args, {
67
+ cwd,
68
+ stdio: getMachineAwareStdio(),
69
+ shell: true,
70
+ });
71
+ if (typeof result.status === "number" && result.status !== 0) {
72
+ throw new Error(`${command} ${args.join(" ")} exited with code ${result.status}`);
73
+ }
74
+ if (result.error) {
75
+ throw result.error;
76
+ }
77
+ }
49
78
  async function scaffoldCommand(options) {
50
79
  // SwallowKit プロジェクトディレクトリかどうかを検証
51
80
  (0, config_1.ensureSwallowKitProject)("scaffold");
81
+ const configValidation = (0, config_1.validateConfig)((0, config_1.getFullConfig)());
82
+ if (!configValidation.valid)
83
+ throw new Error(`Invalid SwallowKit configuration:\n${configValidation.errors.map(error => `- ${error}`).join("\n")}`);
84
+ // Plan(dry-run) や Apply からセッションが供給される。単体実行時は commit セッションを生成。
85
+ const session = (0, file_session_1.getActiveFileSession)() ?? new file_session_1.FileOperationSession("commit");
52
86
  console.log("🏗️ SwallowKit Scaffold: Generating CRUD operations...\n");
53
87
  try {
54
88
  // 1. Resolve model file path
@@ -58,6 +92,14 @@ async function scaffoldCommand(options) {
58
92
  console.log("🔍 Parsing model file...");
59
93
  const modelInfo = await (0, model_parser_1.parseModelFile)(modelPath);
60
94
  console.log(`✅ Model parsed: ${modelInfo.name} (${modelInfo.schemaName})`);
95
+ const backendLanguage = (0, config_1.getBackendLanguage)();
96
+ console.log(`🧠 Backend language: ${backendLanguage}`);
97
+ // コネクタモデルかどうかを判定
98
+ const isConnectorModel = !!modelInfo.connectorConfig;
99
+ if (isConnectorModel) {
100
+ console.log(`🔌 Connector model detected: ${modelInfo.connectorConfig.connector}`);
101
+ console.log(` Operations: ${modelInfo.connectorConfig.operations.join(", ")}`);
102
+ }
61
103
  // ネストスキーマ参照があれば表示
62
104
  if (modelInfo.nestedSchemaRefs.length > 0) {
63
105
  console.log(`🔗 Nested schema references detected:`);
@@ -74,36 +116,135 @@ async function scaffoldCommand(options) {
74
116
  // 4. Read shared package name
75
117
  const functionsDir = options.functionsDir || "functions";
76
118
  const sharedPackageName = readSharedPackageName();
119
+ const relatedModels = backendLanguage === "typescript"
120
+ ? [modelInfo]
121
+ : await collectModelGraph(modelPath);
122
+ for (const relatedModel of relatedModels) {
123
+ session.registerInput(relatedModel.filePath);
124
+ }
125
+ // 4b. 書き込み前に外部 codegen toolchain を検証し、失敗時はプロジェクト無変更で終了する
126
+ if (session.mode === "commit" && backendLanguage !== "typescript" && !isConnectorModel) {
127
+ await (0, native_schema_generator_1.validateNativeSchemaToolchain)(relatedModels, modelInfo, backendLanguage, path.join(process.cwd(), functionsDir));
128
+ }
129
+ if (options.apiOnly) {
130
+ console.log("ℹ️ --api-only skips UI generation only; Functions, BFF routes, OpenAPI, and native schema assets are still updated.");
131
+ }
77
132
  // 5. Generate BFF callFunction helper
78
- await generateCallFunctionHelper();
133
+ await generateCallFunctionHelper(session);
79
134
  // 6. Generate Azure Functions code
80
- await generateFunctionsCode(modelInfo, functionsDir, sharedPackageName);
135
+ if (isConnectorModel) {
136
+ await generateConnectorFunctionsCode(modelInfo, functionsDir, sharedPackageName, backendLanguage, session);
137
+ // 6b. Install connector driver dependencies
138
+ await installConnectorDriverDependencies(modelInfo, functionsDir, backendLanguage, session);
139
+ }
140
+ else {
141
+ await generateFunctionsCode(modelInfo, functionsDir, sharedPackageName, backendLanguage, session);
142
+ if (backendLanguage !== "typescript") {
143
+ await (0, native_schema_generator_1.generateLanguageSchemaArtifacts)(relatedModels, modelInfo, functionsDir, backendLanguage, session);
144
+ }
145
+ }
81
146
  // 7. Generate Next.js BFF API Routes
82
147
  const apiDir = options.apiDir || "app/api";
83
- await generateBFFRoutes(modelInfo, apiDir, sharedPackageName);
84
- // 8. Generate Cosmos DB container Bicep file
85
- await generateCosmosContainer(modelInfo);
148
+ if (isConnectorModel) {
149
+ await generateConnectorBFFRoutesFiles(modelInfo, apiDir, sharedPackageName, session);
150
+ }
151
+ else {
152
+ await generateBFFRoutes(modelInfo, apiDir, sharedPackageName, session);
153
+ }
154
+ // 8. Generate Cosmos DB container Bicep file (skip for connector models)
155
+ if (!isConnectorModel) {
156
+ await generateCosmosContainer(modelInfo, session);
157
+ }
86
158
  // 9. Generate UI components (unless --api-only)
87
159
  if (!options.apiOnly) {
88
- await generateUIComponents(modelInfo, sharedPackageName);
89
- await updateNavigationMenu(modelInfo);
160
+ const uiAuthConfig = (0, config_1.getAuthConfig)();
161
+ const uiAuthPolicy = resolveAuthPolicy(modelInfo, uiAuthConfig);
162
+ const uiAuthOptions = uiAuthPolicy && uiAuthConfig && uiAuthConfig.provider !== 'none'
163
+ ? createUIAuthOptions(uiAuthPolicy, uiAuthConfig)
164
+ : undefined;
165
+ await generateUIComponents(modelInfo, sharedPackageName, uiAuthOptions, session);
166
+ if (uiAuthOptions?.authenticatedFetchImport) {
167
+ applyExternalTokenFetchToGeneratedUi(modelInfo, session, uiAuthOptions.authenticatedFetchImport);
168
+ }
169
+ await updateNavigationMenu(modelInfo, session);
170
+ }
171
+ if (session.mode === "commit") {
172
+ (0, artifacts_1.recordSessionOperations)(session.operations, {
173
+ schemaHash: (0, file_session_1.hashFileIfExists)(modelPath) ?? undefined,
174
+ sourceModel: modelInfo.name,
175
+ });
176
+ await (0, manifest_1.syncProjectManifest)();
90
177
  }
91
178
  console.log("\n✅ Scaffold completed successfully!");
92
179
  console.log("\n📝 Next steps:");
93
- console.log(` 1. Review generated files in ${functionsDir}/src/ and ${apiDir}/`);
180
+ console.log(` 1. Review generated files in ${describeFunctionsOutputPath(functionsDir, backendLanguage)} and ${apiDir}/`);
94
181
  if (!options.apiOnly) {
95
182
  console.log(` 2. Check the generated UI pages in app/${(0, model_parser_1.toKebabCase)(modelInfo.name)}/`);
96
183
  console.log(" 3. Navigate to the model from the homepage menu");
97
184
  }
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`);
185
+ if (backendLanguage !== "typescript") {
186
+ console.log(` ${options.apiOnly ? "2" : "4"}. Review generated OpenAPI export and native schema assets in ${functionsDir}/openapi/ and ${functionsDir}/generated/`);
187
+ }
188
+ console.log(` ${options.apiOnly ? (backendLanguage === "typescript" ? "2" : "3") : (backendLanguage === "typescript" ? "4" : "5")}. Ensure BACKEND_FUNCTIONS_BASE_URL is set in your .env.local file`);
189
+ console.log(` ${options.apiOnly ? (backendLanguage === "typescript" ? "3" : "4") : (backendLanguage === "typescript" ? "5" : "6")}. Configure CosmosDBConnection in functions/local.settings.json`);
190
+ console.log(` ${options.apiOnly ? (backendLanguage === "typescript" ? "4" : "5") : (backendLanguage === "typescript" ? "6" : "7")}. Run '${(0, package_manager_1.getCommands)((0, package_manager_1.detectFromProject)()).exec} swallowkit dev' to test the generated code`);
101
191
  }
102
192
  catch (error) {
103
193
  console.error("\n❌ Scaffold failed:", error.message);
104
194
  process.exit(1);
105
195
  }
106
196
  }
197
+ function applyExternalTokenFetchToGeneratedUi(modelInfo, session, importPath = '@/lib/auth/authenticated-fetch') {
198
+ const uiPrefix = `app/${(0, model_parser_1.toKebabCase)(modelInfo.name)}/`;
199
+ for (const operation of session.operations) {
200
+ if (!operation.path.startsWith(uiPrefix) || !operation.path.endsWith('.tsx') || operation.action === 'delete')
201
+ continue;
202
+ let content = session.readFile(operation.absolutePath);
203
+ if (!content.includes('fetch('))
204
+ continue;
205
+ content = content.replace(/\bfetch\(/g, 'authenticatedFetch(');
206
+ content = content.replace(/(if \(!res\.ok\) throw new Error\([^\n]+\);)/g, `if (res.status === 401) throw new Error('Authentication required');\n $1`);
207
+ if (!content.includes(importPath)) {
208
+ const directive = content.match(/^'use client';\s*\r?\n/)?.[0] ?? '';
209
+ content = `${directive}import { authenticatedFetch } from '${importPath}';\n${content.slice(directive.length)}`;
210
+ }
211
+ session.writeFile(operation.absolutePath, content, {
212
+ ownership: 'managed',
213
+ generator: SCAFFOLD_GENERATOR,
214
+ sourceModel: modelInfo.name,
215
+ });
216
+ }
217
+ }
218
+ /**
219
+ * モデルの authPolicy と auth config から実効的な認可ポリシーを解決
220
+ * - モデルに authPolicy があればそれを使用
221
+ * - なければ auth.authorization.defaultPolicy に従う
222
+ * - auth 設定がなければ undefined(ガードなし)
223
+ */
224
+ function resolveAuthPolicy(modelInfo, authConfig) {
225
+ // モデルに明示的な authPolicy がある場合はそれを使用
226
+ if (modelInfo.authPolicy) {
227
+ const normalized = (0, config_1.normalizeAuthConfig)(authConfig);
228
+ 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))) {
229
+ if (!normalized?.authorization.policies[policyName])
230
+ throw new Error(`Model '${modelInfo.name}' references undefined auth policy '${policyName}'. Define auth.authorization.policies.${policyName}.`);
231
+ }
232
+ return modelInfo.authPolicy;
233
+ }
234
+ // auth 設定がない場合はガードなし
235
+ if (!authConfig || authConfig.provider === 'none') {
236
+ return undefined;
237
+ }
238
+ // defaultPolicy が 'authenticated' なら認証のみ(ロール指定なし)のポリシーを返す
239
+ const defaultPolicy = authConfig.authorization?.defaultPolicy === 'public' ? 'anonymous' : authConfig.authorization?.defaultPolicy ?? 'authenticated';
240
+ if (defaultPolicy === 'authenticated') {
241
+ return {}; // 空のポリシー = 認証のみ、ロール制限なし
242
+ }
243
+ if (defaultPolicy !== 'anonymous')
244
+ return { policy: defaultPolicy };
245
+ // defaultPolicy が 'anonymous' なら認証不要
246
+ return undefined;
247
+ }
107
248
  /**
108
249
  * モデルファイルのパスを解決
109
250
  */
@@ -161,68 +302,326 @@ function readSharedPackageName() {
161
302
  /**
162
303
  * Generate BFF callFunction helper (lib/api/call-function.ts)
163
304
  */
164
- async function generateCallFunctionHelper() {
305
+ async function generateCallFunctionHelper(session) {
165
306
  console.log("\n📦 Generating BFF callFunction helper...");
166
307
  const cwd = process.cwd();
167
308
  const helperDir = path.join(cwd, "lib", "api");
168
309
  const helperPath = path.join(helperDir, "call-function.ts");
169
- if (!fs.existsSync(helperDir)) {
170
- fs.mkdirSync(helperDir, { recursive: true });
310
+ // auth 設定がある場合は Authorization ヘッダー転送版を生成
311
+ const authConfig = (0, config_1.getAuthConfig)();
312
+ const normalizedAuth = (0, config_1.normalizeAuthConfig)(authConfig);
313
+ const hasAuth = Boolean(normalizedAuth && Object.keys(normalizedAuth.schemes).length > 0);
314
+ let helperCode;
315
+ if (hasAuth) {
316
+ const providers = new Set(Object.values(normalizedAuth.schemes).map(scheme => scheme.provider));
317
+ helperCode = normalizedAuth.schemes.default && Object.keys(normalizedAuth.schemes).length === 1
318
+ ? authConfig?.provider === "swa" ? (0, auth_generator_1.generateBFFCallFunctionWithSwaAuth)() : (0, auth_generator_1.generateBFFCallFunctionWithAuth)()
319
+ : providers.has("swa") ? (0, auth_generator_1.generateBFFCallFunctionWithMultipleAuth)() : (0, auth_generator_1.generateBFFCallFunctionWithAuth)();
320
+ }
321
+ else {
322
+ helperCode = (0, nextjs_generator_1.generateBFFCallFunction)();
171
323
  }
172
- const helperCode = (0, nextjs_generator_1.generateBFFCallFunction)();
173
- fs.writeFileSync(helperPath, helperCode, "utf-8");
324
+ session.writeFile(helperPath, helperCode, { ownership: "managed", generator: SCAFFOLD_GENERATOR });
174
325
  console.log(`✅ Created: ${helperPath}`);
175
326
  }
176
327
  /**
177
328
  * Generate Azure Functions CRUD code
178
329
  */
179
- async function generateFunctionsCode(modelInfo, functionsDir, sharedPackageName) {
330
+ async function generateFunctionsCode(modelInfo, functionsDir, sharedPackageName, backendLanguage, session) {
180
331
  console.log("\n🔨 Generating Azure Functions CRUD code...");
332
+ // Resolve auth policy: model-level authPolicy or global defaultPolicy
333
+ const authConfig = (0, config_1.getAuthConfig)();
334
+ const authPolicy = resolveAuthPolicy(modelInfo, authConfig);
335
+ if (authPolicy) {
336
+ console.log(`🔐 Auth policy detected: ${JSON.stringify(authPolicy)}`);
337
+ }
181
338
  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 });
339
+ const managedMeta = { ownership: "managed", generator: SCAFFOLD_GENERATOR, sourceModel: modelInfo.name };
340
+ if (backendLanguage === "typescript") {
341
+ const normalizedAuth = (0, config_1.getNormalizedAuthConfig)();
342
+ if (normalizedAuth && Object.keys(normalizedAuth.schemes).some(name => name !== "default")) {
343
+ const routerPath = path.join(process.cwd(), functionsDir, "src", "auth", "auth-router.ts");
344
+ session.writeFile(routerPath, (0, auth_generator_1.generateNamedAuthRouterTS)(normalizedAuth), { ownership: "managed", generator: SCAFFOLD_GENERATOR });
345
+ }
346
+ const functionFilePath = path.join(process.cwd(), functionsDir, "src", `${modelKebab}.ts`);
347
+ const code = (0, functions_generator_1.generateCompactAzureFunctionsCRUD)(modelInfo, sharedPackageName, authPolicy, authConfig?.provider);
348
+ session.writeFile(functionFilePath, code, managedMeta);
349
+ console.log(`✅ Created: ${functionFilePath}`);
350
+ return;
187
351
  }
188
- // コードを生成
189
- const code = (0, functions_generator_1.generateCompactAzureFunctionsCRUD)(modelInfo, sharedPackageName);
190
- // ファイルに書き込み
191
- fs.writeFileSync(functionFilePath, code, "utf-8");
192
- console.log(`✅ Created: ${functionFilePath}`);
352
+ if (backendLanguage === "csharp") {
353
+ const normalizedAuth = (0, config_1.getNormalizedAuthConfig)();
354
+ if (normalizedAuth && Object.keys(normalizedAuth.schemes).some(name => name !== "default")) {
355
+ const routerPath = path.join(process.cwd(), functionsDir, "Auth", "AuthRouter.cs");
356
+ session.writeFile(routerPath, (0, auth_generator_1.generateNamedAuthRouterCSharp)(normalizedAuth), { ownership: "managed", generator: SCAFFOLD_GENERATOR });
357
+ }
358
+ const crudDir = path.join(process.cwd(), functionsDir, "Crud");
359
+ const functionFilePath = path.join(crudDir, `${modelInfo.name}CrudFunctions.cs`);
360
+ // Remove init-generated template (singular) to avoid route conflicts
361
+ const templatePath = path.join(crudDir, `${modelInfo.name}Function.cs`);
362
+ if (session.fileExists(templatePath)) {
363
+ session.deleteFile(templatePath, managedMeta);
364
+ console.log(`🗑️ Removed template: ${templatePath}`);
365
+ }
366
+ session.writeFile(functionFilePath, (0, functions_generator_1.generateCSharpAzureFunctionsCRUD)(modelInfo, authPolicy), managedMeta);
367
+ console.log(`✅ Created: ${functionFilePath}`);
368
+ return;
369
+ }
370
+ const normalizedAuth = (0, config_1.getNormalizedAuthConfig)();
371
+ if (normalizedAuth && Object.keys(normalizedAuth.schemes).some(name => name !== "default")) {
372
+ const routerPath = path.join(process.cwd(), functionsDir, "auth", "auth_router.py");
373
+ session.writeFile(routerPath, (0, auth_generator_1.generateNamedAuthRouterPython)(normalizedAuth), { ownership: "managed", generator: SCAFFOLD_GENERATOR });
374
+ }
375
+ const blueprintsDir = path.join(process.cwd(), functionsDir, "blueprints");
376
+ const blueprintPath = path.join(blueprintsDir, `${modelKebab.replace(/-/g, "_")}.py`);
377
+ const { blueprint, registration } = (0, functions_generator_1.generatePythonAzureFunctionsCRUD)(modelInfo, authPolicy);
378
+ session.writeFile(blueprintPath, blueprint, managedMeta);
379
+ updatePythonFunctionRegistrations(path.join(process.cwd(), functionsDir, "function_app.py"), registration, session);
380
+ console.log(`✅ Created: ${blueprintPath}`);
381
+ }
382
+ function createUIAuthOptions(policy, authConfig) {
383
+ const normalized = (0, config_1.normalizeAuthConfig)(authConfig);
384
+ const readPolicyName = typeof policy.read === 'string' ? policy.read : policy.policy;
385
+ const writePolicyName = typeof policy.write === 'string' ? policy.write : policy.policy;
386
+ const readNamed = readPolicyName ? normalized?.authorization.policies[readPolicyName] : undefined;
387
+ const named = writePolicyName ? normalized?.authorization.policies[writePolicyName] : undefined;
388
+ const writeRoles = named?.roles ?? (Array.isArray(policy.write) ? policy.write : policy.roles) ?? [];
389
+ const externalScheme = [...(readNamed?.schemes ?? []), ...(named?.schemes ?? [])].find(name => normalized?.schemes[name]?.provider === 'external-token');
390
+ const authScheme = readNamed?.schemes[0] ?? named?.schemes[0];
391
+ const authenticatedFetchImport = externalScheme ? `@/lib/auth/schemes/${(0, model_parser_1.toKebabCase)(externalScheme)}/authenticated-fetch` : authConfig.provider === 'external-token' ? '@/lib/auth/authenticated-fetch' : undefined;
392
+ const authContextImport = authScheme ? `@/lib/auth/schemes/${(0, model_parser_1.toKebabCase)(authScheme)}/auth-context` : '@/lib/auth/auth-context';
393
+ return { authPolicy: policy, writeRoles, authenticatedFetchImport, authContextImport };
394
+ }
395
+ /**
396
+ * コネクタモデル用 Azure Functions コード生成
397
+ */
398
+ async function generateConnectorFunctionsCode(modelInfo, functionsDir, sharedPackageName, backendLanguage, session) {
399
+ console.log("\n🔌 Generating Connector Azure Functions code...");
400
+ const connectorConfig = modelInfo.connectorConfig;
401
+ const connectorDef = (0, config_1.getConnectorDefinition)(connectorConfig.connector);
402
+ if (!connectorDef) {
403
+ throw new Error(`Connector '${connectorConfig.connector}' not found in swallowkit.config.js.\n` +
404
+ ` Please add it to the 'connectors' section of your configuration.`);
405
+ }
406
+ // Resolve auth policy (same logic as Cosmos model scaffolding)
407
+ const authConfig = (0, config_1.getAuthConfig)();
408
+ const authPolicy = resolveAuthPolicy(modelInfo, authConfig);
409
+ if (authPolicy) {
410
+ console.log(`🔐 Auth policy detected: ${JSON.stringify(authPolicy)}`);
411
+ }
412
+ const modelKebab = (0, model_parser_1.toKebabCase)(modelInfo.name);
413
+ const connectorManagedMeta = { ownership: "managed", generator: SCAFFOLD_GENERATOR, sourceModel: modelInfo.name };
414
+ if (backendLanguage === "typescript") {
415
+ const functionFilePath = path.join(process.cwd(), functionsDir, "src", `${modelKebab}.ts`);
416
+ let code;
417
+ if (connectorDef.type === "rdb") {
418
+ code = (0, connector_functions_generator_1.generateRdbConnectorFunctionTS)(modelInfo, sharedPackageName, connectorDef, connectorConfig, authPolicy, authConfig?.provider);
419
+ }
420
+ else {
421
+ code = (0, connector_functions_generator_1.generateApiConnectorFunctionTS)(modelInfo, sharedPackageName, connectorDef, connectorConfig, authPolicy, authConfig?.provider);
422
+ }
423
+ session.writeFile(functionFilePath, code, connectorManagedMeta);
424
+ console.log(`✅ Created: ${functionFilePath}`);
425
+ return;
426
+ }
427
+ if (backendLanguage === "csharp") {
428
+ const functionFilePath = path.join(process.cwd(), functionsDir, "Connectors", `${modelInfo.name}ConnectorFunctions.cs`);
429
+ let code;
430
+ if (connectorDef.type === "rdb") {
431
+ code = (0, connector_functions_generator_1.generateRdbConnectorFunctionCSharp)(modelInfo, connectorDef, connectorConfig);
432
+ }
433
+ else {
434
+ code = (0, connector_functions_generator_1.generateApiConnectorFunctionCSharp)(modelInfo, connectorDef, connectorConfig);
435
+ }
436
+ session.writeFile(functionFilePath, code, connectorManagedMeta);
437
+ console.log(`✅ Created: ${functionFilePath}`);
438
+ return;
439
+ }
440
+ // Python
441
+ const blueprintsDir = path.join(process.cwd(), functionsDir, "blueprints");
442
+ const blueprintPath = path.join(blueprintsDir, `${modelKebab.replace(/-/g, "_")}.py`);
443
+ let result;
444
+ if (connectorDef.type === "rdb") {
445
+ result = (0, connector_functions_generator_1.generateRdbConnectorFunctionPython)(modelInfo, connectorDef, connectorConfig);
446
+ }
447
+ else {
448
+ result = (0, connector_functions_generator_1.generateApiConnectorFunctionPython)(modelInfo, connectorDef, connectorConfig);
449
+ }
450
+ session.writeFile(blueprintPath, result.blueprint, connectorManagedMeta);
451
+ updatePythonFunctionRegistrations(path.join(process.cwd(), functionsDir, "function_app.py"), result.registration, session);
452
+ console.log(`✅ Created: ${blueprintPath}`);
453
+ }
454
+ /**
455
+ * コネクタモデルの scaffold 後に、生成コードが必要とする RDB/API ドライバを
456
+ * functions ディレクトリにインストールする。
457
+ */
458
+ async function installConnectorDriverDependencies(modelInfo, functionsDir, backendLanguage, session) {
459
+ const connectorConfig = modelInfo.connectorConfig;
460
+ const connectorDef = (0, config_1.getConnectorDefinition)(connectorConfig.connector);
461
+ if (!connectorDef || connectorDef.type !== "rdb")
462
+ return;
463
+ const rdbDef = connectorDef;
464
+ const functionsPath = path.join(process.cwd(), functionsDir);
465
+ if (backendLanguage === "typescript") {
466
+ const driverMap = {
467
+ mysql: { deps: ["mysql2"], devDeps: [] },
468
+ postgres: { deps: ["pg"], devDeps: ["@types/pg"] },
469
+ sqlserver: { deps: ["mssql"], devDeps: [] },
470
+ };
471
+ const entry = driverMap[rdbDef.provider];
472
+ if (!entry)
473
+ return;
474
+ // package.json を読んで、既にインストール済みならスキップ
475
+ const pkgJsonPath = path.join(functionsPath, "package.json");
476
+ if (fs.existsSync(pkgJsonPath)) {
477
+ const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
478
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
479
+ const missingDeps = entry.deps.filter(d => !allDeps[d]);
480
+ const missingDevDeps = entry.devDeps.filter(d => !allDeps[d]);
481
+ if (missingDeps.length === 0 && missingDevDeps.length === 0)
482
+ return;
483
+ }
484
+ if (session.mode === "collect") {
485
+ session.addWarning(`external-install: ${rdbDef.provider} driver dependencies (${[...entry.deps, ...entry.devDeps].join(", ")}) will be installed into ${functionsDir}/ on apply.`);
486
+ return;
487
+ }
488
+ console.log(`\n📦 Installing ${rdbDef.provider} driver dependencies...`);
489
+ const pm = (0, package_manager_1.detectFromProject)(functionsPath);
490
+ const cmds = (0, package_manager_1.getCommands)(pm);
491
+ if (entry.deps.length > 0) {
492
+ runSpawnSyncCommand(cmds.name, [pm === "pnpm" ? "add" : "install", ...entry.deps], functionsPath);
493
+ }
494
+ if (entry.devDeps.length > 0) {
495
+ runSpawnSyncCommand(cmds.name, [pm === "pnpm" ? "add" : "install", "-D", ...entry.devDeps], functionsPath);
496
+ }
497
+ console.log(`✅ ${rdbDef.provider} driver installed`);
498
+ return;
499
+ }
500
+ if (backendLanguage === "csharp") {
501
+ const nugetMap = {
502
+ mysql: "MySqlConnector",
503
+ postgres: "Npgsql",
504
+ sqlserver: "Microsoft.Data.SqlClient",
505
+ };
506
+ const pkg = nugetMap[rdbDef.provider];
507
+ if (!pkg)
508
+ return;
509
+ if (session.mode === "collect") {
510
+ session.addWarning(`external-install: NuGet package ${pkg} will be added to ${functionsDir}/functions.csproj on apply.`);
511
+ return;
512
+ }
513
+ console.log(`\n📦 Installing ${rdbDef.provider} NuGet package...`);
514
+ runSpawnSyncCommand("dotnet", ["add", path.join(functionsPath, "functions.csproj"), "package", pkg], functionsPath);
515
+ console.log(`✅ ${pkg} installed`);
516
+ return;
517
+ }
518
+ // Python — requirements.txt に追記
519
+ const pipMap = {
520
+ mysql: "mysql-connector-python",
521
+ postgres: "psycopg2-binary",
522
+ sqlserver: "pymssql",
523
+ };
524
+ const pipPkg = pipMap[rdbDef.provider];
525
+ if (!pipPkg)
526
+ return;
527
+ const reqPath = path.join(functionsPath, "requirements.txt");
528
+ if (session.fileExists(reqPath)) {
529
+ const existing = session.readFile(reqPath);
530
+ if (!existing.includes(pipPkg)) {
531
+ console.log(`\n📦 Adding ${pipPkg} to requirements.txt...`);
532
+ session.writeFile(reqPath, `${existing}\n${pipPkg}\n`, { ownership: "extension-point", generator: SCAFFOLD_GENERATOR });
533
+ console.log(`✅ ${pipPkg} added`);
534
+ }
535
+ }
536
+ }
537
+ /**
538
+ * コネクタモデル用 BFF ルート生成(操作制限に対応)
539
+ */
540
+ async function generateConnectorBFFRoutesFiles(modelInfo, apiDir, sharedPackageName, session) {
541
+ console.log("\n🔌 Generating Connector BFF API routes...");
542
+ const modelCamel = (0, model_parser_1.toCamelCase)(modelInfo.name);
543
+ const operations = modelInfo.connectorConfig.operations;
544
+ const listRoutePath = path.join(process.cwd(), apiDir, modelCamel, "route.ts");
545
+ const detailRoutePath = path.join(process.cwd(), apiDir, modelCamel, "[id]", "route.ts");
546
+ const routes = (0, nextjs_generator_1.generateConnectorBFFRoutes)(modelInfo, sharedPackageName, operations);
547
+ const meta = { ownership: "managed", generator: SCAFFOLD_GENERATOR, sourceModel: modelInfo.name };
548
+ session.writeFile(listRoutePath, routes.listRoute, meta);
549
+ session.writeFile(detailRoutePath, routes.detailRoute, meta);
550
+ console.log(`✅ Created: ${listRoutePath}`);
551
+ console.log(`✅ Created: ${detailRoutePath}`);
552
+ }
553
+ async function collectModelGraph(modelPath, seen = new Map()) {
554
+ const resolvedPath = path.resolve(modelPath);
555
+ if (seen.has(resolvedPath)) {
556
+ return Array.from(seen.values());
557
+ }
558
+ const modelInfo = await (0, model_parser_1.parseModelFile)(resolvedPath);
559
+ seen.set(resolvedPath, modelInfo);
560
+ for (const ref of modelInfo.nestedSchemaRefs) {
561
+ const nestedPath = resolveNestedModelPath(resolvedPath, ref.importPath);
562
+ await collectModelGraph(nestedPath, seen);
563
+ }
564
+ return Array.from(seen.values());
565
+ }
566
+ function resolveNestedModelPath(modelPath, importPath) {
567
+ let resolvedPath = path.resolve(path.dirname(modelPath), importPath);
568
+ if (!resolvedPath.endsWith(".ts")) {
569
+ resolvedPath += ".ts";
570
+ }
571
+ return resolvedPath;
572
+ }
573
+ function describeFunctionsOutputPath(functionsDir, backendLanguage) {
574
+ if (backendLanguage === "typescript") {
575
+ return `${functionsDir}/src/`;
576
+ }
577
+ if (backendLanguage === "csharp") {
578
+ return `${functionsDir}/Crud/`;
579
+ }
580
+ return `${functionsDir}/blueprints/`;
581
+ }
582
+ function updatePythonFunctionRegistrations(functionAppPath, registration, session) {
583
+ if (!session.fileExists(functionAppPath)) {
584
+ throw new Error(`Python Functions entrypoint not found: ${functionAppPath}`);
585
+ }
586
+ const content = session.readFile(functionAppPath);
587
+ // Check if import line already exists (handles init-generated layout)
588
+ const importLine = registration.split("\n").find((l) => l.startsWith("from ") || l.startsWith("import "));
589
+ if (importLine && content.includes(importLine)) {
590
+ return;
591
+ }
592
+ if (content.includes(registration)) {
593
+ return;
594
+ }
595
+ const marker = "# SwallowKit scaffold registrations";
596
+ if (!content.includes(marker)) {
597
+ throw new Error(`Could not find scaffold registration marker in ${functionAppPath}`);
598
+ }
599
+ const updated = content.replace(marker, `${registration}\n${marker}`);
600
+ session.writeFile(functionAppPath, updated, { ownership: "extension-point", generator: SCAFFOLD_GENERATOR });
193
601
  }
194
602
  /**
195
603
  * Next.js BFF API Routes を生成
196
604
  */
197
- async function generateBFFRoutes(modelInfo, apiDir, sharedPackageName) {
605
+ async function generateBFFRoutes(modelInfo, apiDir, sharedPackageName, session) {
198
606
  console.log("\n🔨 Generating Next.js BFF API routes...");
199
- const modelKebab = (0, model_parser_1.toKebabCase)(modelInfo.name);
200
607
  const modelCamel = modelInfo.name.charAt(0).toLowerCase() + modelInfo.name.slice(1);
201
608
  // List route: app/api/[model]/route.ts
202
609
  const listRoutePath = path.join(process.cwd(), apiDir, modelCamel, "route.ts");
203
610
  // Detail route: app/api/[model]/[id]/route.ts
204
611
  const detailRoutePath = path.join(process.cwd(), apiDir, modelCamel, "[id]", "route.ts");
205
- // ディレクトリを作成
206
- const listRouteDir = path.dirname(listRoutePath);
207
- const detailRouteDir = path.dirname(detailRoutePath);
208
- if (!fs.existsSync(listRouteDir)) {
209
- fs.mkdirSync(listRouteDir, { recursive: true });
210
- }
211
- if (!fs.existsSync(detailRouteDir)) {
212
- fs.mkdirSync(detailRouteDir, { recursive: true });
213
- }
214
612
  // コードを生成
215
613
  const routes = (0, nextjs_generator_1.generateCompactBFFRoutes)(modelInfo, sharedPackageName);
216
614
  // ファイルに書き込み
217
- fs.writeFileSync(listRoutePath, routes.listRoute, "utf-8");
218
- fs.writeFileSync(detailRoutePath, routes.detailRoute, "utf-8");
615
+ const meta = { ownership: "managed", generator: SCAFFOLD_GENERATOR, sourceModel: modelInfo.name };
616
+ session.writeFile(listRoutePath, routes.listRoute, meta);
617
+ session.writeFile(detailRoutePath, routes.detailRoute, meta);
219
618
  console.log(`✅ Created: ${listRoutePath}`);
220
619
  console.log(`✅ Created: ${detailRoutePath}`);
221
620
  }
222
621
  /**
223
622
  * Generate Cosmos DB container Bicep file
224
623
  */
225
- async function generateCosmosContainer(modelInfo) {
624
+ async function generateCosmosContainer(modelInfo, session = new file_session_1.FileOperationSession("commit")) {
226
625
  console.log("\n🗄️ Generating Cosmos DB container Bicep file...");
227
626
  const modelKebab = (0, model_parser_1.toKebabCase)(modelInfo.name);
228
627
  const modelPascal = (0, model_parser_1.toPascalCase)(modelInfo.name);
@@ -233,12 +632,8 @@ async function generateCosmosContainer(modelInfo) {
233
632
  console.log("ℹ️ infra directory not found. Skipping Cosmos DB container generation.");
234
633
  return;
235
634
  }
236
- // Check if containers directory exists, if not create it
237
- const containersDir = path.join(infraDir, "containers");
238
- if (!fs.existsSync(containersDir)) {
239
- fs.mkdirSync(containersDir, { recursive: true });
240
- }
241
635
  // Generate container Bicep file
636
+ const containersDir = path.join(infraDir, "containers");
242
637
  const containerFileName = `${modelKebab}-container.bicep`;
243
638
  const containerFilePath = path.join(containersDir, containerFileName);
244
639
  const bicepContent = `@description('Cosmos DB account name')
@@ -251,13 +646,7 @@ param databaseName string
251
646
  param containerName string = '${modelPascal}s'
252
647
 
253
648
  @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
649
+ param partitionKeyPath string = '${modelInfo.partitionKey}'
261
650
 
262
651
  // Reference existing Cosmos DB account
263
652
  resource cosmosAccount 'Microsoft.DocumentDB/databaseAccounts@2023-11-15' existing = {
@@ -298,33 +687,30 @@ resource container 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/container
298
687
  ]
299
688
  }
300
689
  }
301
- options: cosmosDbMode == 'freetier' ? {
302
- throughput: throughput
303
- } : {}
304
690
  }
305
691
  }
306
692
 
307
693
  output containerName string = container.name
308
694
  `;
309
695
  // Write Bicep file (overwrite if exists)
310
- fs.writeFileSync(containerFilePath, bicepContent, "utf-8");
696
+ session.writeFile(containerFilePath, bicepContent, { ownership: "managed", generator: SCAFFOLD_GENERATOR, sourceModel: modelInfo.name });
311
697
  console.log(`✅ Created: ${containerFilePath}`);
312
698
  // Update main.bicep to include this container module
313
- await updateMainBicepWithContainer(modelKebab, modelPascal);
699
+ await updateMainBicepWithContainer(modelKebab, modelPascal, session);
314
700
  }
315
701
  /**
316
702
  * Update main.bicep to include new container module
317
703
  */
318
- async function updateMainBicepWithContainer(modelKebab, modelPascal) {
704
+ async function updateMainBicepWithContainer(modelKebab, modelPascal, session) {
319
705
  const cwd = process.cwd();
320
706
  const mainBicepPath = path.join(cwd, "infra", "main.bicep");
321
- if (!fs.existsSync(mainBicepPath)) {
707
+ if (!session.fileExists(mainBicepPath)) {
322
708
  console.log("ℹ️ main.bicep not found. Please manually add the container module.");
323
709
  return;
324
710
  }
325
- let mainBicepContent = fs.readFileSync(mainBicepPath, "utf-8");
711
+ let mainBicepContent = session.readFile(mainBicepPath);
326
712
  // Check if container module already exists
327
- const containerModuleName = `${modelKebab.replace(/-/g, '')}Container`;
713
+ const containerModuleName = `${modelPascal.charAt(0).toLowerCase()}${modelPascal.slice(1)}Container`;
328
714
  if (mainBicepContent.includes(`module ${containerModuleName}`)) {
329
715
  console.log(`ℹ️ Container module '${containerModuleName}' already exists in main.bicep`);
330
716
  return;
@@ -359,26 +745,21 @@ module ${containerModuleName} 'containers/${modelKebab}-container.bicep' = {
359
745
  name: '${modelKebab}-container'
360
746
  params: {
361
747
  cosmosAccountName: cosmosDbMode == 'freetier' ? cosmosDbFreeTier.outputs.accountName : cosmosDbServerless.outputs.accountName
362
- databaseName: '$` + `{projectName}Database'
363
- cosmosDbMode: cosmosDbMode
748
+ databaseName: '\${projectName}Database'
364
749
  }
365
- dependsOn: [
366
- cosmosDbFreeTier
367
- cosmosDbServerless
368
- ]
369
750
  }`;
370
751
  // Insert the module
371
752
  mainBicepContent =
372
753
  mainBicepContent.slice(0, insertPosition) +
373
754
  containerModule +
374
755
  mainBicepContent.slice(insertPosition);
375
- fs.writeFileSync(mainBicepPath, mainBicepContent, "utf-8");
756
+ session.writeFile(mainBicepPath, mainBicepContent, { ownership: "extension-point", generator: SCAFFOLD_GENERATOR });
376
757
  console.log(`✅ Added container module to main.bicep`);
377
758
  }
378
759
  /**
379
760
  * Generate UI components (list, detail, form, create, edit pages)
380
761
  */
381
- async function generateUIComponents(modelInfo, sharedPackageName) {
762
+ async function generateUIComponents(modelInfo, sharedPackageName, authOptions, session = new file_session_1.FileOperationSession("commit")) {
382
763
  console.log("\n🎨 Generating UI components...");
383
764
  const modelKebab = (0, model_parser_1.toKebabCase)(modelInfo.name);
384
765
  const modelName = modelInfo.name;
@@ -389,44 +770,46 @@ async function generateUIComponents(modelInfo, sharedPackageName) {
389
770
  const newDir = path.join(modelDir, "new");
390
771
  const idDir = path.join(modelDir, "[id]");
391
772
  const editDir = path.join(idDir, "edit");
392
- // Create directories
393
- [componentsDir, newDir, editDir].forEach(dir => {
394
- if (!fs.existsSync(dir)) {
395
- fs.mkdirSync(dir, { recursive: true });
396
- }
397
- });
398
773
  // Generate and write files
399
- const listPage = (0, ui_generator_1.generateListPage)(modelInfo, sharedPackageName);
400
- const detailPage = (0, ui_generator_1.generateDetailPage)(modelInfo, sharedPackageName);
774
+ const listPage = (0, ui_generator_1.generateListPage)(modelInfo, sharedPackageName, authOptions);
775
+ const detailPage = (0, ui_generator_1.generateDetailPage)(modelInfo, sharedPackageName, authOptions);
401
776
  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);
404
- fs.writeFileSync(path.join(modelDir, "page.tsx"), listPage, "utf-8");
405
- fs.writeFileSync(path.join(idDir, "page.tsx"), detailPage, "utf-8");
406
- fs.writeFileSync(path.join(componentsDir, `${modelName}Form.tsx`), formComponent, "utf-8");
407
- fs.writeFileSync(path.join(newDir, "page.tsx"), newPage, "utf-8");
408
- fs.writeFileSync(path.join(editDir, "page.tsx"), editPage, "utf-8");
777
+ const newPage = (0, ui_generator_1.generateNewPage)(modelInfo, authOptions);
778
+ const editPage = (0, ui_generator_1.generateEditPage)(modelInfo, sharedPackageName, authOptions);
779
+ const authLayout = (0, ui_generator_1.generateAuthLayout)(modelInfo, authOptions);
780
+ const meta = { ownership: "managed", generator: SCAFFOLD_GENERATOR, sourceModel: modelInfo.name };
781
+ session.writeFile(path.join(modelDir, "page.tsx"), listPage, meta);
782
+ session.writeFile(path.join(idDir, "page.tsx"), detailPage, meta);
783
+ session.writeFile(path.join(componentsDir, `${modelName}Form.tsx`), formComponent, meta);
784
+ session.writeFile(path.join(newDir, "page.tsx"), newPage, meta);
785
+ session.writeFile(path.join(editDir, "page.tsx"), editPage, meta);
786
+ if (authLayout) {
787
+ session.writeFile(path.join(modelDir, "layout.tsx"), authLayout, meta);
788
+ }
409
789
  console.log(`✅ Created: ${path.join(modelDir, "page.tsx")}`);
410
790
  console.log(`✅ Created: ${path.join(idDir, "page.tsx")}`);
411
791
  console.log(`✅ Created: ${path.join(componentsDir, `${modelName}Form.tsx`)}`);
412
792
  console.log(`✅ Created: ${path.join(newDir, "page.tsx")}`);
413
793
  console.log(`✅ Created: ${path.join(editDir, "page.tsx")}`);
794
+ if (authLayout) {
795
+ console.log(`✅ Created: ${path.join(modelDir, "layout.tsx")}`);
796
+ }
414
797
  }
415
798
  /**
416
799
  * Update navigation menu on the homepage
417
800
  */
418
- async function updateNavigationMenu(modelInfo) {
801
+ async function updateNavigationMenu(modelInfo, session) {
419
802
  console.log("\n📋 Updating navigation menu...");
420
803
  const modelKebab = (0, model_parser_1.toKebabCase)(modelInfo.name);
421
804
  const cwd = process.cwd();
422
805
  // Update scaffold config
423
806
  const configPath = path.join(cwd, "lib", "scaffold-config.ts");
424
- if (!fs.existsSync(configPath)) {
807
+ if (!session.fileExists(configPath)) {
425
808
  console.log("⚠️ scaffold-config.ts not found. Skipping navigation menu update.");
426
809
  return;
427
810
  }
428
811
  // Read existing config
429
- const configContent = fs.readFileSync(configPath, "utf-8");
812
+ const configContent = session.readFile(configPath);
430
813
  // Parse models array - extract each model entry
431
814
  const models = [];
432
815
  const modelsMatch = configContent.match(/models:\s*\[([\s\S]*?)\]\s*as\s*ScaffoldModel\[\]/);
@@ -467,7 +850,7 @@ ${models.map(m => ` { name: '${m.name}', path: '${m.path}', label: '${m.label
467
850
  ] as ScaffoldModel[]
468
851
  };
469
852
  `;
470
- fs.writeFileSync(configPath, newConfigContent, "utf-8");
853
+ session.writeFile(configPath, newConfigContent, { ownership: "extension-point", generator: SCAFFOLD_GENERATOR });
471
854
  console.log(`✅ Added ${modelInfo.name} to navigation menu`);
472
855
  }
473
856
  //# sourceMappingURL=scaffold.js.map