swallowkit 1.0.0-beta.8 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (272) hide show
  1. package/README.ja.md +87 -181
  2. package/README.md +87 -238
  3. package/dist/__tests__/fixtures.d.ts +12 -0
  4. package/dist/__tests__/fixtures.d.ts.map +1 -1
  5. package/dist/__tests__/fixtures.js +101 -0
  6. package/dist/__tests__/fixtures.js.map +1 -1
  7. package/dist/cli/commands/add-auth.d.ts +15 -0
  8. package/dist/cli/commands/add-auth.d.ts.map +1 -0
  9. package/dist/cli/commands/add-auth.js +790 -0
  10. package/dist/cli/commands/add-auth.js.map +1 -0
  11. package/dist/cli/commands/add-connector.d.ts +20 -0
  12. package/dist/cli/commands/add-connector.d.ts.map +1 -0
  13. package/dist/cli/commands/add-connector.js +164 -0
  14. package/dist/cli/commands/add-connector.js.map +1 -0
  15. package/dist/cli/commands/cosmos-worktree-isolation.d.ts +25 -0
  16. package/dist/cli/commands/cosmos-worktree-isolation.d.ts.map +1 -0
  17. package/dist/cli/commands/cosmos-worktree-isolation.js +124 -0
  18. package/dist/cli/commands/cosmos-worktree-isolation.js.map +1 -0
  19. package/dist/cli/commands/create-model.d.ts +1 -4
  20. package/dist/cli/commands/create-model.d.ts.map +1 -1
  21. package/dist/cli/commands/create-model.js +20 -82
  22. package/dist/cli/commands/create-model.js.map +1 -1
  23. package/dist/cli/commands/dev-seeds.d.ts +27 -0
  24. package/dist/cli/commands/dev-seeds.d.ts.map +1 -1
  25. package/dist/cli/commands/dev-seeds.js +188 -6
  26. package/dist/cli/commands/dev-seeds.js.map +1 -1
  27. package/dist/cli/commands/dev.d.ts +30 -0
  28. package/dist/cli/commands/dev.d.ts.map +1 -1
  29. package/dist/cli/commands/dev.js +495 -129
  30. package/dist/cli/commands/dev.js.map +1 -1
  31. package/dist/cli/commands/init.d.ts +70 -0
  32. package/dist/cli/commands/init.d.ts.map +1 -1
  33. package/dist/cli/commands/init.js +797 -768
  34. package/dist/cli/commands/init.js.map +1 -1
  35. package/dist/cli/commands/scaffold.d.ts +24 -3
  36. package/dist/cli/commands/scaffold.d.ts.map +1 -1
  37. package/dist/cli/commands/scaffold.js +408 -189
  38. package/dist/cli/commands/scaffold.js.map +1 -1
  39. package/dist/cli/index.d.ts +5 -1
  40. package/dist/cli/index.d.ts.map +1 -1
  41. package/dist/cli/index.js +335 -58
  42. package/dist/cli/index.js.map +1 -1
  43. package/dist/core/config.d.ts +13 -3
  44. package/dist/core/config.d.ts.map +1 -1
  45. package/dist/core/config.js +151 -8
  46. package/dist/core/config.js.map +1 -1
  47. package/dist/core/mock/connector-mock-server.d.ts +101 -0
  48. package/dist/core/mock/connector-mock-server.d.ts.map +1 -0
  49. package/dist/core/mock/connector-mock-server.js +480 -0
  50. package/dist/core/mock/connector-mock-server.js.map +1 -0
  51. package/dist/core/mock/zod-mock-generator.d.ts +14 -0
  52. package/dist/core/mock/zod-mock-generator.d.ts.map +1 -0
  53. package/dist/core/mock/zod-mock-generator.js +163 -0
  54. package/dist/core/mock/zod-mock-generator.js.map +1 -0
  55. package/dist/core/operations/auth-operations.d.ts +61 -0
  56. package/dist/core/operations/auth-operations.d.ts.map +1 -0
  57. package/dist/core/operations/auth-operations.js +272 -0
  58. package/dist/core/operations/auth-operations.js.map +1 -0
  59. package/dist/core/operations/create-model.d.ts +15 -0
  60. package/dist/core/operations/create-model.d.ts.map +1 -0
  61. package/dist/core/operations/create-model.js +185 -0
  62. package/dist/core/operations/create-model.js.map +1 -0
  63. package/dist/core/operations/file-session.d.ts +56 -0
  64. package/dist/core/operations/file-session.d.ts.map +1 -0
  65. package/dist/core/operations/file-session.js +203 -0
  66. package/dist/core/operations/file-session.js.map +1 -0
  67. package/dist/core/operations/provision-operations.d.ts +52 -0
  68. package/dist/core/operations/provision-operations.d.ts.map +1 -0
  69. package/dist/core/operations/provision-operations.js +211 -0
  70. package/dist/core/operations/provision-operations.js.map +1 -0
  71. package/dist/core/operations/runtime.d.ts +37 -0
  72. package/dist/core/operations/runtime.d.ts.map +1 -0
  73. package/dist/core/operations/runtime.js +248 -0
  74. package/dist/core/operations/runtime.js.map +1 -0
  75. package/dist/core/operations/scaffold-apply.d.ts +21 -0
  76. package/dist/core/operations/scaffold-apply.d.ts.map +1 -0
  77. package/dist/core/operations/scaffold-apply.js +57 -0
  78. package/dist/core/operations/scaffold-apply.js.map +1 -0
  79. package/dist/core/operations/scaffold-machine.d.ts +16 -0
  80. package/dist/core/operations/scaffold-machine.d.ts.map +1 -0
  81. package/dist/core/operations/scaffold-machine.js +51 -0
  82. package/dist/core/operations/scaffold-machine.js.map +1 -0
  83. package/dist/core/operations/scaffold-plan.d.ts +43 -0
  84. package/dist/core/operations/scaffold-plan.d.ts.map +1 -0
  85. package/dist/core/operations/scaffold-plan.js +187 -0
  86. package/dist/core/operations/scaffold-plan.js.map +1 -0
  87. package/dist/core/project/agent-instructions.d.ts +29 -0
  88. package/dist/core/project/agent-instructions.d.ts.map +1 -0
  89. package/dist/core/project/agent-instructions.js +160 -0
  90. package/dist/core/project/agent-instructions.js.map +1 -0
  91. package/dist/core/project/artifacts.d.ts +58 -0
  92. package/dist/core/project/artifacts.d.ts.map +1 -0
  93. package/dist/core/project/artifacts.js +179 -0
  94. package/dist/core/project/artifacts.js.map +1 -0
  95. package/dist/core/project/boundaries.d.ts +50 -0
  96. package/dist/core/project/boundaries.d.ts.map +1 -0
  97. package/dist/core/project/boundaries.js +117 -0
  98. package/dist/core/project/boundaries.js.map +1 -0
  99. package/dist/core/project/capabilities.d.ts +58 -0
  100. package/dist/core/project/capabilities.d.ts.map +1 -0
  101. package/dist/core/project/capabilities.js +135 -0
  102. package/dist/core/project/capabilities.js.map +1 -0
  103. package/dist/core/project/drift.d.ts +29 -0
  104. package/dist/core/project/drift.d.ts.map +1 -0
  105. package/dist/core/project/drift.js +188 -0
  106. package/dist/core/project/drift.js.map +1 -0
  107. package/dist/core/project/infra.d.ts +49 -0
  108. package/dist/core/project/infra.d.ts.map +1 -0
  109. package/dist/core/project/infra.js +167 -0
  110. package/dist/core/project/infra.js.map +1 -0
  111. package/dist/core/project/manifest.d.ts +92 -0
  112. package/dist/core/project/manifest.d.ts.map +1 -0
  113. package/dist/core/project/manifest.js +321 -0
  114. package/dist/core/project/manifest.js.map +1 -0
  115. package/dist/core/project/state.d.ts +15 -0
  116. package/dist/core/project/state.d.ts.map +1 -0
  117. package/dist/core/project/state.js +106 -0
  118. package/dist/core/project/state.js.map +1 -0
  119. package/dist/core/project/validation.d.ts +20 -0
  120. package/dist/core/project/validation.d.ts.map +1 -0
  121. package/dist/core/project/validation.js +274 -0
  122. package/dist/core/project/validation.js.map +1 -0
  123. package/dist/core/project/workflows.d.ts +43 -0
  124. package/dist/core/project/workflows.d.ts.map +1 -0
  125. package/dist/core/project/workflows.js +299 -0
  126. package/dist/core/project/workflows.js.map +1 -0
  127. package/dist/core/scaffold/auth-generator.d.ts +63 -0
  128. package/dist/core/scaffold/auth-generator.d.ts.map +1 -0
  129. package/dist/core/scaffold/auth-generator.js +1774 -0
  130. package/dist/core/scaffold/auth-generator.js.map +1 -0
  131. package/dist/core/scaffold/connector-functions-generator.d.ts +41 -0
  132. package/dist/core/scaffold/connector-functions-generator.d.ts.map +1 -0
  133. package/dist/core/scaffold/connector-functions-generator.js +1029 -0
  134. package/dist/core/scaffold/connector-functions-generator.js.map +1 -0
  135. package/dist/core/scaffold/functions-generator.d.ts +4 -3
  136. package/dist/core/scaffold/functions-generator.d.ts.map +1 -1
  137. package/dist/core/scaffold/functions-generator.js +405 -134
  138. package/dist/core/scaffold/functions-generator.js.map +1 -1
  139. package/dist/core/scaffold/model-parser.d.ts +20 -0
  140. package/dist/core/scaffold/model-parser.d.ts.map +1 -1
  141. package/dist/core/scaffold/model-parser.js +229 -78
  142. package/dist/core/scaffold/model-parser.js.map +1 -1
  143. package/dist/core/scaffold/native-schema-generator.d.ts +19 -0
  144. package/dist/core/scaffold/native-schema-generator.d.ts.map +1 -0
  145. package/dist/core/scaffold/native-schema-generator.js +753 -0
  146. package/dist/core/scaffold/native-schema-generator.js.map +1 -0
  147. package/dist/core/scaffold/nextjs-generator.d.ts +8 -0
  148. package/dist/core/scaffold/nextjs-generator.d.ts.map +1 -1
  149. package/dist/core/scaffold/nextjs-generator.js +152 -8
  150. package/dist/core/scaffold/nextjs-generator.js.map +1 -1
  151. package/dist/core/scaffold/ui-generator.d.ts +17 -4
  152. package/dist/core/scaffold/ui-generator.d.ts.map +1 -1
  153. package/dist/core/scaffold/ui-generator.js +174 -34
  154. package/dist/core/scaffold/ui-generator.js.map +1 -1
  155. package/dist/core/verify/index.d.ts +69 -0
  156. package/dist/core/verify/index.d.ts.map +1 -0
  157. package/dist/core/verify/index.js +412 -0
  158. package/dist/core/verify/index.js.map +1 -0
  159. package/dist/machine/contracts.d.ts +24 -0
  160. package/dist/machine/contracts.d.ts.map +1 -0
  161. package/dist/machine/contracts.js +3 -0
  162. package/dist/machine/contracts.js.map +1 -0
  163. package/dist/machine/errors.d.ts +14 -0
  164. package/dist/machine/errors.d.ts.map +1 -0
  165. package/dist/machine/errors.js +42 -0
  166. package/dist/machine/errors.js.map +1 -0
  167. package/dist/machine/index.d.ts +3 -0
  168. package/dist/machine/index.d.ts.map +1 -0
  169. package/dist/machine/index.js +566 -0
  170. package/dist/machine/index.js.map +1 -0
  171. package/dist/mcp/index.d.ts +25 -0
  172. package/dist/mcp/index.d.ts.map +1 -0
  173. package/dist/mcp/index.js +425 -0
  174. package/dist/mcp/index.js.map +1 -0
  175. package/dist/types/index.d.ts +105 -0
  176. package/dist/types/index.d.ts.map +1 -1
  177. package/dist/utils/package-manager.d.ts +7 -11
  178. package/dist/utils/package-manager.d.ts.map +1 -1
  179. package/dist/utils/package-manager.js +42 -17
  180. package/dist/utils/package-manager.js.map +1 -1
  181. package/dist/utils/python-uv.d.ts +21 -0
  182. package/dist/utils/python-uv.d.ts.map +1 -0
  183. package/dist/utils/python-uv.js +111 -0
  184. package/dist/utils/python-uv.js.map +1 -0
  185. package/dist/version.d.ts +2 -0
  186. package/dist/version.d.ts.map +1 -0
  187. package/dist/version.js +49 -0
  188. package/dist/version.js.map +1 -0
  189. package/package.json +15 -17
  190. package/src/__tests__/__snapshots__/functions-generator.test.ts.snap +716 -22
  191. package/src/__tests__/__snapshots__/nextjs-generator.test.ts.snap +19 -7
  192. package/src/__tests__/__snapshots__/ui-generator.test.ts.snap +20 -12
  193. package/src/__tests__/agent-instructions.test.ts +237 -0
  194. package/src/__tests__/agent-loop-phase34.test.ts +513 -0
  195. package/src/__tests__/auth.test.ts +770 -0
  196. package/src/__tests__/bff-swa-auth.test.ts +275 -0
  197. package/src/__tests__/config.test.ts +176 -3
  198. package/src/__tests__/connector-functions-generator.test.ts +288 -0
  199. package/src/__tests__/connector-mock-server.test.ts +459 -0
  200. package/src/__tests__/connector-model-bff.test.ts +162 -0
  201. package/src/__tests__/cosmos-worktree-isolation.test.ts +149 -0
  202. package/src/__tests__/dev-seeds.test.ts +124 -0
  203. package/src/__tests__/dev.test.ts +254 -0
  204. package/src/__tests__/file-session.test.ts +98 -0
  205. package/src/__tests__/fixtures.ts +101 -0
  206. package/src/__tests__/functions-generator.test.ts +137 -1
  207. package/src/__tests__/init.test.ts +386 -1
  208. package/src/__tests__/jest-globals.d.ts +8 -0
  209. package/src/__tests__/machine.test.ts +371 -0
  210. package/src/__tests__/mcp.test.ts +253 -0
  211. package/src/__tests__/model-parser.test.ts +142 -0
  212. package/src/__tests__/multi-auth.test.ts +160 -0
  213. package/src/__tests__/nextjs-generator.test.ts +8 -0
  214. package/src/__tests__/package-manager.test.ts +59 -4
  215. package/src/__tests__/plan-apply.test.ts +499 -0
  216. package/src/__tests__/python-uv.test.ts +48 -0
  217. package/src/__tests__/scaffold.test.ts +287 -24
  218. package/src/__tests__/tsconfig.json +15 -0
  219. package/src/__tests__/ui-generator.test.ts +104 -0
  220. package/src/__tests__/zod-mock-generator.test.ts +132 -0
  221. package/src/cli/commands/add-auth.ts +853 -0
  222. package/src/cli/commands/add-connector.ts +159 -0
  223. package/src/cli/commands/cosmos-worktree-isolation.ts +130 -0
  224. package/src/cli/commands/create-model.ts +21 -100
  225. package/src/cli/commands/dev-seeds.ts +278 -7
  226. package/src/cli/commands/dev.ts +607 -160
  227. package/src/cli/commands/init.ts +880 -808
  228. package/src/cli/commands/scaffold.ts +531 -236
  229. package/src/cli/index.ts +338 -60
  230. package/src/core/config.ts +145 -9
  231. package/src/core/mock/connector-mock-server.ts +555 -0
  232. package/src/core/mock/zod-mock-generator.ts +205 -0
  233. package/src/core/operations/auth-operations.ts +360 -0
  234. package/src/core/operations/create-model.ts +191 -0
  235. package/src/core/operations/file-session.ts +222 -0
  236. package/src/core/operations/provision-operations.ts +297 -0
  237. package/src/core/operations/runtime.ts +258 -0
  238. package/src/core/operations/scaffold-apply.ts +106 -0
  239. package/src/core/operations/scaffold-machine.ts +77 -0
  240. package/src/core/operations/scaffold-plan.ts +215 -0
  241. package/src/core/project/agent-instructions.ts +167 -0
  242. package/src/core/project/agent-templates/AGENTS.md +504 -0
  243. package/src/core/project/agent-templates/CLAUDE.md +31 -0
  244. package/src/core/project/agent-templates/azure-functions.instructions.md +22 -0
  245. package/src/core/project/agent-templates/bff-routes.instructions.md +18 -0
  246. package/src/core/project/agent-templates/copilot-instructions.md +27 -0
  247. package/src/core/project/agent-templates/shared-models.instructions.md +72 -0
  248. package/src/core/project/artifacts.ts +205 -0
  249. package/src/core/project/boundaries.ts +170 -0
  250. package/src/core/project/capabilities.ts +193 -0
  251. package/src/core/project/drift.ts +202 -0
  252. package/src/core/project/infra.ts +196 -0
  253. package/src/core/project/manifest.ts +402 -0
  254. package/src/core/project/state.ts +77 -0
  255. package/src/core/project/validation.ts +295 -0
  256. package/src/core/project/workflows.ts +295 -0
  257. package/src/core/scaffold/auth-generator.ts +1824 -0
  258. package/src/core/scaffold/connector-functions-generator.ts +1133 -0
  259. package/src/core/scaffold/functions-generator.ts +432 -134
  260. package/src/core/scaffold/model-parser.ts +244 -90
  261. package/src/core/scaffold/native-schema-generator.ts +904 -0
  262. package/src/core/scaffold/nextjs-generator.ts +174 -9
  263. package/src/core/scaffold/ui-generator.ts +191 -36
  264. package/src/core/verify/index.ts +468 -0
  265. package/src/machine/contracts.ts +33 -0
  266. package/src/machine/errors.ts +45 -0
  267. package/src/machine/index.ts +690 -0
  268. package/src/mcp/index.ts +423 -0
  269. package/src/types/index.ts +137 -0
  270. package/src/utils/package-manager.ts +41 -19
  271. package/src/utils/python-uv.ts +96 -0
  272. package/src/version.ts +13 -0
@@ -38,21 +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.getOpenApiGeneratorArgs = getOpenApiGeneratorArgs;
42
- exports.getCSharpSchemaArtifactPruneTargets = getCSharpSchemaArtifactPruneTargets;
41
+ exports.resolveAuthPolicy = resolveAuthPolicy;
42
+ exports.resolveModelPath = resolveModelPath;
43
+ exports.createUIAuthOptions = createUIAuthOptions;
44
+ exports.generateCosmosContainer = generateCosmosContainer;
45
+ exports.generateUIComponents = generateUIComponents;
43
46
  const fs = __importStar(require("fs"));
44
47
  const path = __importStar(require("path"));
45
48
  const child_process_1 = require("child_process");
46
49
  const config_1 = require("../../core/config");
47
50
  const model_parser_1 = require("../../core/scaffold/model-parser");
48
51
  const functions_generator_1 = require("../../core/scaffold/functions-generator");
52
+ const connector_functions_generator_1 = require("../../core/scaffold/connector-functions-generator");
49
53
  const nextjs_generator_1 = require("../../core/scaffold/nextjs-generator");
50
- const openapi_generator_1 = require("../../core/scaffold/openapi-generator");
51
54
  const ui_generator_1 = require("../../core/scaffold/ui-generator");
55
+ const native_schema_generator_1 = require("../../core/scaffold/native-schema-generator");
52
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
+ }
53
78
  async function scaffoldCommand(options) {
54
79
  // SwallowKit プロジェクトディレクトリかどうかを検証
55
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");
56
86
  console.log("🏗️ SwallowKit Scaffold: Generating CRUD operations...\n");
57
87
  try {
58
88
  // 1. Resolve model file path
@@ -64,6 +94,12 @@ async function scaffoldCommand(options) {
64
94
  console.log(`✅ Model parsed: ${modelInfo.name} (${modelInfo.schemaName})`);
65
95
  const backendLanguage = (0, config_1.getBackendLanguage)();
66
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
+ }
67
103
  // ネストスキーマ参照があれば表示
68
104
  if (modelInfo.nestedSchemaRefs.length > 0) {
69
105
  console.log(`🔗 Nested schema references detected:`);
@@ -83,22 +119,61 @@ async function scaffoldCommand(options) {
83
119
  const relatedModels = backendLanguage === "typescript"
84
120
  ? [modelInfo]
85
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
+ }
86
132
  // 5. Generate BFF callFunction helper
87
- await generateCallFunctionHelper();
133
+ await generateCallFunctionHelper(session);
88
134
  // 6. Generate Azure Functions code
89
- await generateFunctionsCode(modelInfo, functionsDir, sharedPackageName, backendLanguage);
90
- if (backendLanguage !== "typescript") {
91
- await generateLanguageSchemaArtifacts(relatedModels, modelInfo, functionsDir, backendLanguage);
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
+ }
92
145
  }
93
146
  // 7. Generate Next.js BFF API Routes
94
147
  const apiDir = options.apiDir || "app/api";
95
- await generateBFFRoutes(modelInfo, apiDir, sharedPackageName);
96
- // 8. Generate Cosmos DB container Bicep file
97
- 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
+ }
98
158
  // 9. Generate UI components (unless --api-only)
99
159
  if (!options.apiOnly) {
100
- await generateUIComponents(modelInfo, sharedPackageName);
101
- 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)();
102
177
  }
103
178
  console.log("\n✅ Scaffold completed successfully!");
104
179
  console.log("\n📝 Next steps:");
@@ -108,17 +183,68 @@ async function scaffoldCommand(options) {
108
183
  console.log(" 3. Navigate to the model from the homepage menu");
109
184
  }
110
185
  if (backendLanguage !== "typescript") {
111
- console.log(` ${options.apiOnly ? "2" : "4"}. Review generated OpenAPI assets in ${functionsDir}/openapi/ and ${functionsDir}/generated/`);
186
+ console.log(` ${options.apiOnly ? "2" : "4"}. Review generated OpenAPI export and native schema assets in ${functionsDir}/openapi/ and ${functionsDir}/generated/`);
112
187
  }
113
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`);
114
189
  console.log(` ${options.apiOnly ? (backendLanguage === "typescript" ? "3" : "4") : (backendLanguage === "typescript" ? "5" : "6")}. Configure CosmosDBConnection in functions/local.settings.json`);
115
- 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`);
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`);
116
191
  }
117
192
  catch (error) {
118
193
  console.error("\n❌ Scaffold failed:", error.message);
119
194
  process.exit(1);
120
195
  }
121
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
+ }
122
248
  /**
123
249
  * モデルファイルのパスを解決
124
250
  */
@@ -176,50 +302,254 @@ function readSharedPackageName() {
176
302
  /**
177
303
  * Generate BFF callFunction helper (lib/api/call-function.ts)
178
304
  */
179
- async function generateCallFunctionHelper() {
305
+ async function generateCallFunctionHelper(session) {
180
306
  console.log("\n📦 Generating BFF callFunction helper...");
181
307
  const cwd = process.cwd();
182
308
  const helperDir = path.join(cwd, "lib", "api");
183
309
  const helperPath = path.join(helperDir, "call-function.ts");
184
- if (!fs.existsSync(helperDir)) {
185
- fs.mkdirSync(helperDir, { recursive: true });
186
- }
187
- const helperCode = (0, nextjs_generator_1.generateBFFCallFunction)();
188
- fs.writeFileSync(helperPath, helperCode, "utf-8");
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)();
323
+ }
324
+ session.writeFile(helperPath, helperCode, { ownership: "managed", generator: SCAFFOLD_GENERATOR });
189
325
  console.log(`✅ Created: ${helperPath}`);
190
326
  }
191
327
  /**
192
328
  * Generate Azure Functions CRUD code
193
329
  */
194
- async function generateFunctionsCode(modelInfo, functionsDir, sharedPackageName, backendLanguage) {
330
+ async function generateFunctionsCode(modelInfo, functionsDir, sharedPackageName, backendLanguage, session) {
195
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
+ }
196
338
  const modelKebab = (0, model_parser_1.toKebabCase)(modelInfo.name);
339
+ const managedMeta = { ownership: "managed", generator: SCAFFOLD_GENERATOR, sourceModel: modelInfo.name };
197
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
+ }
198
346
  const functionFilePath = path.join(process.cwd(), functionsDir, "src", `${modelKebab}.ts`);
199
- const functionDir = path.dirname(functionFilePath);
200
- if (!fs.existsSync(functionDir)) {
201
- fs.mkdirSync(functionDir, { recursive: true });
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;
351
+ }
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 });
202
357
  }
203
- const code = (0, functions_generator_1.generateCompactAzureFunctionsCRUD)(modelInfo, sharedPackageName);
204
- fs.writeFileSync(functionFilePath, code, "utf-8");
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);
205
424
  console.log(`✅ Created: ${functionFilePath}`);
206
425
  return;
207
426
  }
208
427
  if (backendLanguage === "csharp") {
209
- const functionFilePath = path.join(process.cwd(), functionsDir, "Crud", `${modelInfo.name}Functions.cs`);
210
- fs.mkdirSync(path.dirname(functionFilePath), { recursive: true });
211
- fs.writeFileSync(functionFilePath, (0, functions_generator_1.generateCSharpAzureFunctionsCRUD)(modelInfo), "utf-8");
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);
212
437
  console.log(`✅ Created: ${functionFilePath}`);
213
438
  return;
214
439
  }
440
+ // Python
215
441
  const blueprintsDir = path.join(process.cwd(), functionsDir, "blueprints");
216
442
  const blueprintPath = path.join(blueprintsDir, `${modelKebab.replace(/-/g, "_")}.py`);
217
- fs.mkdirSync(blueprintsDir, { recursive: true });
218
- const { blueprint, registration } = (0, functions_generator_1.generatePythonAzureFunctionsCRUD)(modelInfo);
219
- fs.writeFileSync(blueprintPath, blueprint, "utf-8");
220
- updatePythonFunctionRegistrations(path.join(process.cwd(), functionsDir, "function_app.py"), registration);
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);
221
452
  console.log(`✅ Created: ${blueprintPath}`);
222
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
+ }
223
553
  async function collectModelGraph(modelPath, seen = new Map()) {
224
554
  const resolvedPath = path.resolve(modelPath);
225
555
  if (seen.has(resolvedPath)) {
@@ -249,102 +579,16 @@ function describeFunctionsOutputPath(functionsDir, backendLanguage) {
249
579
  }
250
580
  return `${functionsDir}/blueprints/`;
251
581
  }
252
- async function generateLanguageSchemaArtifacts(models, rootModel, functionsDir, backendLanguage) {
253
- console.log("\n🧬 Generating OpenAPI-backed schema artifacts...");
254
- const openApiDir = path.join(process.cwd(), functionsDir, "openapi");
255
- fs.mkdirSync(openApiDir, { recursive: true });
256
- const specPath = path.join(openApiDir, `${(0, model_parser_1.toKebabCase)(rootModel.name)}.openapi.json`);
257
- fs.writeFileSync(specPath, (0, openapi_generator_1.generateOpenApiDocument)(models, rootModel), "utf-8");
258
- console.log(`✅ Created: ${specPath}`);
259
- const outputDir = path.join(process.cwd(), functionsDir, "generated", backendLanguage === "csharp" ? "csharp-models" : "python-models");
260
- fs.mkdirSync(outputDir, { recursive: true });
261
- await runOpenApiGenerator(specPath, outputDir, backendLanguage);
262
- if (backendLanguage === "csharp") {
263
- pruneGeneratedCSharpArtifacts(outputDir);
264
- }
265
- console.log(`✅ Generated ${backendLanguage} schema assets: ${outputDir}`);
266
- }
267
- async function runOpenApiGenerator(specPath, outputDir, backendLanguage) {
268
- const pm = (0, package_manager_1.detectFromProject)();
269
- const command = pm === "pnpm" ? "pnpm" : "npx";
270
- const args = pm === "pnpm"
271
- ? ["exec", "openapi-generator-cli", ...getOpenApiGeneratorArgs(specPath, outputDir, backendLanguage)]
272
- : ["openapi-generator-cli", ...getOpenApiGeneratorArgs(specPath, outputDir, backendLanguage)];
273
- await new Promise((resolve, reject) => {
274
- const child = (0, child_process_1.spawn)(command, args, {
275
- cwd: process.cwd(),
276
- shell: true,
277
- stdio: "inherit",
278
- });
279
- child.on("close", (code) => {
280
- if (code === 0) {
281
- resolve();
282
- return;
283
- }
284
- reject(new Error(`OpenAPI generator exited with code ${code}`));
285
- });
286
- child.on("error", reject);
287
- });
288
- }
289
- function getOpenApiGeneratorArgs(specPath, outputDir, backendLanguage) {
290
- const globalProperty = backendLanguage === "csharp"
291
- ? "models,supportingFiles,apis=false,modelDocs=false,modelTests=false"
292
- : "models,apis=false,supportingFiles=false,modelDocs=false,modelTests=false";
293
- const baseArgs = [
294
- "generate",
295
- "-i",
296
- specPath,
297
- "-o",
298
- outputDir,
299
- "--global-property",
300
- globalProperty,
301
- ];
302
- if (backendLanguage === "csharp") {
303
- return [
304
- ...baseArgs,
305
- "-g",
306
- "csharp",
307
- "--additional-properties",
308
- "packageName=SwallowKitBackendModels,targetFramework=net8.0,nullableReferenceTypes=true",
309
- ];
310
- }
311
- return [
312
- ...baseArgs,
313
- "-g",
314
- "python",
315
- "--additional-properties",
316
- "packageName=backend_models,projectName=backend-models",
317
- ];
318
- }
319
- function getCSharpSchemaArtifactPruneTargets(outputDir) {
320
- return [
321
- path.join(outputDir, "src", "SwallowKitBackendModels.Test"),
322
- path.join(outputDir, "src", "SwallowKitBackendModels", "Api"),
323
- path.join(outputDir, "src", "SwallowKitBackendModels", "Extensions"),
324
- ];
325
- }
326
- function pruneGeneratedCSharpArtifacts(outputDir) {
327
- for (const target of getCSharpSchemaArtifactPruneTargets(outputDir)) {
328
- if (fs.existsSync(target)) {
329
- fs.rmSync(target, { recursive: true, force: true });
330
- }
582
+ function updatePythonFunctionRegistrations(functionAppPath, registration, session) {
583
+ if (!session.fileExists(functionAppPath)) {
584
+ throw new Error(`Python Functions entrypoint not found: ${functionAppPath}`);
331
585
  }
332
- const clientDir = path.join(outputDir, "src", "SwallowKitBackendModels", "Client");
333
- if (!fs.existsSync(clientDir)) {
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)) {
334
590
  return;
335
591
  }
336
- for (const entry of fs.readdirSync(clientDir, { withFileTypes: true })) {
337
- if (!entry.isFile() || entry.name === "Option.cs") {
338
- continue;
339
- }
340
- fs.rmSync(path.join(clientDir, entry.name), { force: true });
341
- }
342
- }
343
- function updatePythonFunctionRegistrations(functionAppPath, registration) {
344
- if (!fs.existsSync(functionAppPath)) {
345
- throw new Error(`Python Functions entrypoint not found: ${functionAppPath}`);
346
- }
347
- const content = fs.readFileSync(functionAppPath, "utf-8");
348
592
  if (content.includes(registration)) {
349
593
  return;
350
594
  }
@@ -353,40 +597,31 @@ function updatePythonFunctionRegistrations(functionAppPath, registration) {
353
597
  throw new Error(`Could not find scaffold registration marker in ${functionAppPath}`);
354
598
  }
355
599
  const updated = content.replace(marker, `${registration}\n${marker}`);
356
- fs.writeFileSync(functionAppPath, updated, "utf-8");
600
+ session.writeFile(functionAppPath, updated, { ownership: "extension-point", generator: SCAFFOLD_GENERATOR });
357
601
  }
358
602
  /**
359
603
  * Next.js BFF API Routes を生成
360
604
  */
361
- async function generateBFFRoutes(modelInfo, apiDir, sharedPackageName) {
605
+ async function generateBFFRoutes(modelInfo, apiDir, sharedPackageName, session) {
362
606
  console.log("\n🔨 Generating Next.js BFF API routes...");
363
- const modelKebab = (0, model_parser_1.toKebabCase)(modelInfo.name);
364
607
  const modelCamel = modelInfo.name.charAt(0).toLowerCase() + modelInfo.name.slice(1);
365
608
  // List route: app/api/[model]/route.ts
366
609
  const listRoutePath = path.join(process.cwd(), apiDir, modelCamel, "route.ts");
367
610
  // Detail route: app/api/[model]/[id]/route.ts
368
611
  const detailRoutePath = path.join(process.cwd(), apiDir, modelCamel, "[id]", "route.ts");
369
- // ディレクトリを作成
370
- const listRouteDir = path.dirname(listRoutePath);
371
- const detailRouteDir = path.dirname(detailRoutePath);
372
- if (!fs.existsSync(listRouteDir)) {
373
- fs.mkdirSync(listRouteDir, { recursive: true });
374
- }
375
- if (!fs.existsSync(detailRouteDir)) {
376
- fs.mkdirSync(detailRouteDir, { recursive: true });
377
- }
378
612
  // コードを生成
379
613
  const routes = (0, nextjs_generator_1.generateCompactBFFRoutes)(modelInfo, sharedPackageName);
380
614
  // ファイルに書き込み
381
- fs.writeFileSync(listRoutePath, routes.listRoute, "utf-8");
382
- 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);
383
618
  console.log(`✅ Created: ${listRoutePath}`);
384
619
  console.log(`✅ Created: ${detailRoutePath}`);
385
620
  }
386
621
  /**
387
622
  * Generate Cosmos DB container Bicep file
388
623
  */
389
- async function generateCosmosContainer(modelInfo) {
624
+ async function generateCosmosContainer(modelInfo, session = new file_session_1.FileOperationSession("commit")) {
390
625
  console.log("\n🗄️ Generating Cosmos DB container Bicep file...");
391
626
  const modelKebab = (0, model_parser_1.toKebabCase)(modelInfo.name);
392
627
  const modelPascal = (0, model_parser_1.toPascalCase)(modelInfo.name);
@@ -397,12 +632,8 @@ async function generateCosmosContainer(modelInfo) {
397
632
  console.log("ℹ️ infra directory not found. Skipping Cosmos DB container generation.");
398
633
  return;
399
634
  }
400
- // Check if containers directory exists, if not create it
401
- const containersDir = path.join(infraDir, "containers");
402
- if (!fs.existsSync(containersDir)) {
403
- fs.mkdirSync(containersDir, { recursive: true });
404
- }
405
635
  // Generate container Bicep file
636
+ const containersDir = path.join(infraDir, "containers");
406
637
  const containerFileName = `${modelKebab}-container.bicep`;
407
638
  const containerFilePath = path.join(containersDir, containerFileName);
408
639
  const bicepContent = `@description('Cosmos DB account name')
@@ -415,13 +646,7 @@ param databaseName string
415
646
  param containerName string = '${modelPascal}s'
416
647
 
417
648
  @description('Partition key path')
418
- param partitionKeyPath string = '/id'
419
-
420
- @description('Throughput (RU/s) - only used for Free Tier')
421
- param throughput int = 400
422
-
423
- @description('Cosmos DB mode: freetier or serverless')
424
- param cosmosDbMode string
649
+ param partitionKeyPath string = '${modelInfo.partitionKey}'
425
650
 
426
651
  // Reference existing Cosmos DB account
427
652
  resource cosmosAccount 'Microsoft.DocumentDB/databaseAccounts@2023-11-15' existing = {
@@ -462,33 +687,30 @@ resource container 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/container
462
687
  ]
463
688
  }
464
689
  }
465
- options: cosmosDbMode == 'freetier' ? {
466
- throughput: throughput
467
- } : {}
468
690
  }
469
691
  }
470
692
 
471
693
  output containerName string = container.name
472
694
  `;
473
695
  // Write Bicep file (overwrite if exists)
474
- fs.writeFileSync(containerFilePath, bicepContent, "utf-8");
696
+ session.writeFile(containerFilePath, bicepContent, { ownership: "managed", generator: SCAFFOLD_GENERATOR, sourceModel: modelInfo.name });
475
697
  console.log(`✅ Created: ${containerFilePath}`);
476
698
  // Update main.bicep to include this container module
477
- await updateMainBicepWithContainer(modelKebab, modelPascal);
699
+ await updateMainBicepWithContainer(modelKebab, modelPascal, session);
478
700
  }
479
701
  /**
480
702
  * Update main.bicep to include new container module
481
703
  */
482
- async function updateMainBicepWithContainer(modelKebab, modelPascal) {
704
+ async function updateMainBicepWithContainer(modelKebab, modelPascal, session) {
483
705
  const cwd = process.cwd();
484
706
  const mainBicepPath = path.join(cwd, "infra", "main.bicep");
485
- if (!fs.existsSync(mainBicepPath)) {
707
+ if (!session.fileExists(mainBicepPath)) {
486
708
  console.log("ℹ️ main.bicep not found. Please manually add the container module.");
487
709
  return;
488
710
  }
489
- let mainBicepContent = fs.readFileSync(mainBicepPath, "utf-8");
711
+ let mainBicepContent = session.readFile(mainBicepPath);
490
712
  // Check if container module already exists
491
- const containerModuleName = `${modelKebab.replace(/-/g, '')}Container`;
713
+ const containerModuleName = `${modelPascal.charAt(0).toLowerCase()}${modelPascal.slice(1)}Container`;
492
714
  if (mainBicepContent.includes(`module ${containerModuleName}`)) {
493
715
  console.log(`ℹ️ Container module '${containerModuleName}' already exists in main.bicep`);
494
716
  return;
@@ -523,26 +745,21 @@ module ${containerModuleName} 'containers/${modelKebab}-container.bicep' = {
523
745
  name: '${modelKebab}-container'
524
746
  params: {
525
747
  cosmosAccountName: cosmosDbMode == 'freetier' ? cosmosDbFreeTier.outputs.accountName : cosmosDbServerless.outputs.accountName
526
- databaseName: '$` + `{projectName}Database'
527
- cosmosDbMode: cosmosDbMode
748
+ databaseName: '\${projectName}Database'
528
749
  }
529
- dependsOn: [
530
- cosmosDbFreeTier
531
- cosmosDbServerless
532
- ]
533
750
  }`;
534
751
  // Insert the module
535
752
  mainBicepContent =
536
753
  mainBicepContent.slice(0, insertPosition) +
537
754
  containerModule +
538
755
  mainBicepContent.slice(insertPosition);
539
- fs.writeFileSync(mainBicepPath, mainBicepContent, "utf-8");
756
+ session.writeFile(mainBicepPath, mainBicepContent, { ownership: "extension-point", generator: SCAFFOLD_GENERATOR });
540
757
  console.log(`✅ Added container module to main.bicep`);
541
758
  }
542
759
  /**
543
760
  * Generate UI components (list, detail, form, create, edit pages)
544
761
  */
545
- async function generateUIComponents(modelInfo, sharedPackageName) {
762
+ async function generateUIComponents(modelInfo, sharedPackageName, authOptions, session = new file_session_1.FileOperationSession("commit")) {
546
763
  console.log("\n🎨 Generating UI components...");
547
764
  const modelKebab = (0, model_parser_1.toKebabCase)(modelInfo.name);
548
765
  const modelName = modelInfo.name;
@@ -553,44 +770,46 @@ async function generateUIComponents(modelInfo, sharedPackageName) {
553
770
  const newDir = path.join(modelDir, "new");
554
771
  const idDir = path.join(modelDir, "[id]");
555
772
  const editDir = path.join(idDir, "edit");
556
- // Create directories
557
- [componentsDir, newDir, editDir].forEach(dir => {
558
- if (!fs.existsSync(dir)) {
559
- fs.mkdirSync(dir, { recursive: true });
560
- }
561
- });
562
773
  // Generate and write files
563
- const listPage = (0, ui_generator_1.generateListPage)(modelInfo, sharedPackageName);
564
- 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);
565
776
  const formComponent = (0, ui_generator_1.generateFormComponent)(modelInfo, sharedPackageName);
566
- const newPage = (0, ui_generator_1.generateNewPage)(modelInfo);
567
- const editPage = (0, ui_generator_1.generateEditPage)(modelInfo, sharedPackageName);
568
- fs.writeFileSync(path.join(modelDir, "page.tsx"), listPage, "utf-8");
569
- fs.writeFileSync(path.join(idDir, "page.tsx"), detailPage, "utf-8");
570
- fs.writeFileSync(path.join(componentsDir, `${modelName}Form.tsx`), formComponent, "utf-8");
571
- fs.writeFileSync(path.join(newDir, "page.tsx"), newPage, "utf-8");
572
- 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
+ }
573
789
  console.log(`✅ Created: ${path.join(modelDir, "page.tsx")}`);
574
790
  console.log(`✅ Created: ${path.join(idDir, "page.tsx")}`);
575
791
  console.log(`✅ Created: ${path.join(componentsDir, `${modelName}Form.tsx`)}`);
576
792
  console.log(`✅ Created: ${path.join(newDir, "page.tsx")}`);
577
793
  console.log(`✅ Created: ${path.join(editDir, "page.tsx")}`);
794
+ if (authLayout) {
795
+ console.log(`✅ Created: ${path.join(modelDir, "layout.tsx")}`);
796
+ }
578
797
  }
579
798
  /**
580
799
  * Update navigation menu on the homepage
581
800
  */
582
- async function updateNavigationMenu(modelInfo) {
801
+ async function updateNavigationMenu(modelInfo, session) {
583
802
  console.log("\n📋 Updating navigation menu...");
584
803
  const modelKebab = (0, model_parser_1.toKebabCase)(modelInfo.name);
585
804
  const cwd = process.cwd();
586
805
  // Update scaffold config
587
806
  const configPath = path.join(cwd, "lib", "scaffold-config.ts");
588
- if (!fs.existsSync(configPath)) {
807
+ if (!session.fileExists(configPath)) {
589
808
  console.log("⚠️ scaffold-config.ts not found. Skipping navigation menu update.");
590
809
  return;
591
810
  }
592
811
  // Read existing config
593
- const configContent = fs.readFileSync(configPath, "utf-8");
812
+ const configContent = session.readFile(configPath);
594
813
  // Parse models array - extract each model entry
595
814
  const models = [];
596
815
  const modelsMatch = configContent.match(/models:\s*\[([\s\S]*?)\]\s*as\s*ScaffoldModel\[\]/);
@@ -631,7 +850,7 @@ ${models.map(m => ` { name: '${m.name}', path: '${m.path}', label: '${m.label
631
850
  ] as ScaffoldModel[]
632
851
  };
633
852
  `;
634
- fs.writeFileSync(configPath, newConfigContent, "utf-8");
853
+ session.writeFile(configPath, newConfigContent, { ownership: "extension-point", generator: SCAFFOLD_GENERATOR });
635
854
  console.log(`✅ Added ${modelInfo.name} to navigation menu`);
636
855
  }
637
856
  //# sourceMappingURL=scaffold.js.map