swallowkit 1.0.0-beta.9 → 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 +69 -1
  32. package/dist/cli/commands/init.d.ts.map +1 -1
  33. package/dist/cli/commands/init.js +788 -766
  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 +366 -2
  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 +851 -786
  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
@@ -35,25 +35,119 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.devCommand = void 0;
37
37
  exports.buildFunctionsStartArgs = buildFunctionsStartArgs;
38
+ exports.parseCoreToolsVersion = parseCoreToolsVersion;
39
+ exports.compareVersionNumbers = compareVersionNumbers;
40
+ exports.buildFunctionsCoreToolsCommand = buildFunctionsCoreToolsCommand;
38
41
  exports.buildNextDevArgs = buildNextDevArgs;
42
+ exports.buildFunctionsBaseUrl = buildFunctionsBaseUrl;
43
+ exports.buildSwaStartArgs = buildSwaStartArgs;
44
+ exports.getSwaCliInstallCommand = getSwaCliInstallCommand;
45
+ exports.getFunctionsReadinessTimeoutMs = getFunctionsReadinessTimeoutMs;
46
+ exports.waitForHttpServerReady = waitForHttpServerReady;
39
47
  exports.getPythonVirtualEnvPaths = getPythonVirtualEnvPaths;
48
+ exports.getCSharpFunctionsBuildArtifactPaths = getCSharpFunctionsBuildArtifactPaths;
40
49
  exports.buildPythonFunctionsEnv = buildPythonFunctionsEnv;
50
+ exports.buildFunctionsCosmosEnvironment = buildFunctionsCosmosEnvironment;
51
+ exports.buildDevCommand = buildDevCommand;
41
52
  const commander_1 = require("commander");
42
53
  const child_process_1 = require("child_process");
54
+ const http = __importStar(require("http"));
55
+ const https = __importStar(require("https"));
43
56
  const path = __importStar(require("path"));
44
57
  const fs = __importStar(require("fs"));
45
58
  const os = __importStar(require("os"));
59
+ const net = __importStar(require("net"));
46
60
  const cosmos_1 = require("@azure/cosmos");
47
61
  const config_1 = require("../../core/config");
48
62
  const dev_seeds_1 = require("./dev-seeds");
49
63
  const package_manager_1 = require("../../utils/package-manager");
64
+ const python_uv_1 = require("../../utils/python-uv");
65
+ const connector_mock_server_1 = require("../../core/mock/connector-mock-server");
66
+ const MINIMUM_CSHARP_CORE_TOOLS_VERSION = '4.6.0';
67
+ const NPM_CORE_TOOLS_PACKAGE = 'azure-functions-core-tools@4';
68
+ function normalizeParsedDevOptions(options) {
69
+ return {
70
+ ...options,
71
+ noFunctions: options.noFunctions ?? options.functions === false,
72
+ noSwa: options.noSwa ?? options.swa === false,
73
+ };
74
+ }
50
75
  function buildFunctionsStartArgs(functionsPort) {
51
76
  return ['start', '--port', functionsPort];
52
77
  }
78
+ function parseCoreToolsVersion(output) {
79
+ const match = output.match(/\d+\.\d+\.\d+/);
80
+ return match ? match[0] : null;
81
+ }
82
+ function compareVersionNumbers(left, right) {
83
+ const leftParts = left.split('.').map((value) => Number.parseInt(value, 10));
84
+ const rightParts = right.split('.').map((value) => Number.parseInt(value, 10));
85
+ const length = Math.max(leftParts.length, rightParts.length);
86
+ for (let index = 0; index < length; index += 1) {
87
+ const leftPart = leftParts[index] ?? 0;
88
+ const rightPart = rightParts[index] ?? 0;
89
+ if (leftPart !== rightPart) {
90
+ return leftPart - rightPart;
91
+ }
92
+ }
93
+ return 0;
94
+ }
95
+ function buildFunctionsCoreToolsCommand(backendLanguage, installedVersion) {
96
+ if (backendLanguage === 'csharp' &&
97
+ (!installedVersion || compareVersionNumbers(installedVersion, MINIMUM_CSHARP_CORE_TOOLS_VERSION) < 0)) {
98
+ const reason = installedVersion
99
+ ? `installed func ${installedVersion} is too old for C# isolated`
100
+ : 'func is not installed';
101
+ return {
102
+ command: 'npm',
103
+ argsPrefix: ['exec', '--yes', NPM_CORE_TOOLS_PACKAGE, '--'],
104
+ label: `npm exec ${NPM_CORE_TOOLS_PACKAGE} (${reason})`,
105
+ };
106
+ }
107
+ return {
108
+ command: 'func',
109
+ argsPrefix: [],
110
+ label: installedVersion ? `func ${installedVersion}` : 'func',
111
+ };
112
+ }
53
113
  function buildNextDevArgs(pm, port) {
54
114
  const baseArgs = ['next', 'dev', '--port', port, '--webpack'];
55
115
  return pm === 'pnpm' ? ['exec', ...baseArgs] : baseArgs;
56
116
  }
117
+ function buildFunctionsBaseUrl(host, functionsPort) {
118
+ return `http://${host || 'localhost'}:${functionsPort}`;
119
+ }
120
+ function buildSwaStartArgs(host, nextPort, swaPort) {
121
+ return [
122
+ 'start',
123
+ `http://${host || 'localhost'}:${nextPort}`,
124
+ '--swa-config-location',
125
+ '.',
126
+ '--port',
127
+ swaPort,
128
+ ];
129
+ }
130
+ function getSwaCliInstallCommand(pm) {
131
+ return pm === 'pnpm'
132
+ ? 'pnpm add -Dw @azure/static-web-apps-cli'
133
+ : 'npm install -D @azure/static-web-apps-cli';
134
+ }
135
+ function getFunctionsReadinessTimeoutMs(backendLanguage) {
136
+ return backendLanguage === 'csharp' ? 90000 : 30000;
137
+ }
138
+ async function waitForHttpServerReady(url, timeoutMs = 30000, intervalMs = 500) {
139
+ const deadline = Date.now() + timeoutMs;
140
+ while (Date.now() <= deadline) {
141
+ if (await probeHttpServer(url)) {
142
+ return true;
143
+ }
144
+ if (Date.now() + intervalMs > deadline) {
145
+ break;
146
+ }
147
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
148
+ }
149
+ return probeHttpServer(url);
150
+ }
57
151
  function getPythonVirtualEnvPaths(functionsDir) {
58
152
  const venvDir = path.join(functionsDir, '.venv');
59
153
  const binDir = process.platform === 'win32'
@@ -64,6 +158,12 @@ function getPythonVirtualEnvPaths(functionsDir) {
64
158
  : path.join(binDir, 'python');
65
159
  return { venvDir, binDir, pythonExecutable };
66
160
  }
161
+ function getCSharpFunctionsBuildArtifactPaths(functionsDir) {
162
+ return [
163
+ path.join(functionsDir, 'bin'),
164
+ path.join(functionsDir, 'obj'),
165
+ ];
166
+ }
67
167
  function buildPythonFunctionsEnv(baseEnv, functionsDir) {
68
168
  const { venvDir, binDir, pythonExecutable } = getPythonVirtualEnvPaths(functionsDir);
69
169
  const pathKey = getPathEnvKey(baseEnv);
@@ -75,6 +175,12 @@ function buildPythonFunctionsEnv(baseEnv, functionsDir) {
75
175
  languageWorkers__python__defaultExecutablePath: pythonExecutable,
76
176
  };
77
177
  }
178
+ function buildFunctionsCosmosEnvironment(baseEnv, databaseName) {
179
+ return {
180
+ ...baseEnv,
181
+ COSMOS_DB_DATABASE_NAME: databaseName,
182
+ };
183
+ }
78
184
  function getPathEnvKey(env) {
79
185
  return Object.keys(env).find((key) => key.toUpperCase() === 'PATH') || 'PATH';
80
186
  }
@@ -92,10 +198,12 @@ function prependToPathEnv(env, entry) {
92
198
  async function checkCoreTools() {
93
199
  return checkCommand('func', ['--version']);
94
200
  }
95
- async function checkCommand(command, args = ['--version']) {
201
+ async function checkCommand(command, args = ['--version'], options) {
96
202
  return new Promise((resolve) => {
97
203
  const checkProcess = (0, child_process_1.spawn)(command, args, {
98
- shell: false,
204
+ cwd: options?.cwd,
205
+ env: options?.env ?? process.env,
206
+ shell: options?.shell ?? true,
99
207
  stdio: 'pipe',
100
208
  });
101
209
  checkProcess.on('close', (code) => {
@@ -106,22 +214,22 @@ async function checkCommand(command, args = ['--version']) {
106
214
  });
107
215
  });
108
216
  }
109
- async function resolvePythonBootstrapCommand() {
110
- const candidates = process.platform === 'win32'
111
- ? [
112
- { command: 'py', argsPrefix: ['-3.11'], label: 'py -3.11' },
113
- { command: 'python', argsPrefix: [], label: 'python' },
114
- ]
115
- : [
116
- { command: 'python3', argsPrefix: [], label: 'python3' },
117
- { command: 'python', argsPrefix: [], label: 'python' },
118
- ];
119
- for (const candidate of candidates) {
120
- if (await checkCommand(candidate.command, [...candidate.argsPrefix, '--version'])) {
121
- return candidate;
122
- }
217
+ async function resolveProjectLocalUvCommand(projectRoot) {
218
+ const uvEnv = (0, python_uv_1.buildProjectLocalUvEnv)(process.env, projectRoot);
219
+ const { localUvExecutable } = (0, python_uv_1.getProjectLocalUvPaths)(projectRoot);
220
+ if (await checkCommand('uv', ['--version'], { shell: false })) {
221
+ return { command: 'uv', env: uvEnv };
222
+ }
223
+ if (fs.existsSync(localUvExecutable) && await checkCommand(localUvExecutable, ['--version'], { shell: false })) {
224
+ return { command: localUvExecutable, env: uvEnv };
123
225
  }
124
- throw new Error('Python 3.11 was not found. Install Python 3.11 and make sure `python`, `python3`, or `py -3.11` is available.');
226
+ console.log('📦 Installing project-local uv...');
227
+ const installer = (0, python_uv_1.getProjectLocalUvInstallerCommand)();
228
+ await runCommand(installer.command, installer.args, projectRoot, 'uv installation', (0, python_uv_1.buildProjectLocalUvInstallerEnv)(process.env, projectRoot), false);
229
+ if (!(fs.existsSync(localUvExecutable) && await checkCommand(localUvExecutable, ['--version'], { shell: false }))) {
230
+ throw new Error('Failed to install project-local uv.');
231
+ }
232
+ return { command: localUvExecutable, env: uvEnv };
125
233
  }
126
234
  async function getCommandPath(command) {
127
235
  const locator = process.platform === 'win32' ? 'where' : 'which';
@@ -132,6 +240,17 @@ async function getCommandPath(command) {
132
240
  .find(Boolean);
133
241
  return firstLine || null;
134
242
  }
243
+ async function resolveInstalledCoreToolsVersion() {
244
+ if (!(await checkCoreTools())) {
245
+ return null;
246
+ }
247
+ try {
248
+ return parseCoreToolsVersion(await captureCommandOutput('func', ['--version']));
249
+ }
250
+ catch {
251
+ return null;
252
+ }
253
+ }
135
254
  async function captureCommandOutput(command, args, cwd, env) {
136
255
  return new Promise((resolve, reject) => {
137
256
  const child = (0, child_process_1.spawn)(command, args, {
@@ -159,6 +278,35 @@ async function captureCommandOutput(command, args, cwd, env) {
159
278
  child.on('error', reject);
160
279
  });
161
280
  }
281
+ async function probeHttpServer(url) {
282
+ return new Promise((resolve) => {
283
+ const target = new URL(url);
284
+ const requestFactory = target.protocol === 'https:' ? https.request : http.request;
285
+ let settled = false;
286
+ const finish = (value) => {
287
+ if (!settled) {
288
+ settled = true;
289
+ resolve(value);
290
+ }
291
+ };
292
+ const request = requestFactory({
293
+ hostname: target.hostname,
294
+ port: target.port,
295
+ path: target.pathname || '/',
296
+ method: 'GET',
297
+ timeout: 1000,
298
+ }, (response) => {
299
+ response.resume();
300
+ finish(true);
301
+ });
302
+ request.on('timeout', () => {
303
+ request.destroy();
304
+ finish(false);
305
+ });
306
+ request.on('error', () => finish(false));
307
+ request.end();
308
+ });
309
+ }
162
310
  async function resolvePythonRuntimeDetails(functionsDir, env) {
163
311
  const { pythonExecutable } = getPythonVirtualEnvPaths(functionsDir);
164
312
  const output = await captureCommandOutput(pythonExecutable, [
@@ -207,28 +355,25 @@ async function bridgePythonCoreToolsForWindowsArm64(functionsDir, env) {
207
355
  return prependToPathEnv(env, patchedRoot);
208
356
  }
209
357
  async function preparePythonFunctionsEnvironment(functionsDir) {
210
- const { pythonExecutable } = getPythonVirtualEnvPaths(functionsDir);
211
- const hasUv = await checkCommand('uv', ['--version']);
212
- if (!fs.existsSync(pythonExecutable)) {
213
- if (hasUv) {
214
- console.log('📦 Creating Python virtual environment with uv...');
215
- await runCommand('uv', ['venv', '.venv', '--python', '3.11'], functionsDir, 'python virtual environment setup');
216
- }
217
- else {
218
- const bootstrap = await resolvePythonBootstrapCommand();
219
- console.log(`📦 Creating Python virtual environment with ${bootstrap.label}...`);
220
- await runCommand(bootstrap.command, [...bootstrap.argsPrefix, '-m', 'venv', '.venv'], functionsDir, 'python virtual environment setup');
358
+ const projectRoot = (0, python_uv_1.getPythonProjectRoot)(functionsDir);
359
+ const { command: uvCommand, env: uvEnv } = await resolveProjectLocalUvCommand(projectRoot);
360
+ const { venvDir, pythonExecutable } = getPythonVirtualEnvPaths(functionsDir);
361
+ const hasUsableVirtualEnv = fs.existsSync(pythonExecutable) && await checkCommand(pythonExecutable, ['--version'], {
362
+ cwd: functionsDir,
363
+ env: uvEnv,
364
+ shell: false,
365
+ });
366
+ if (!hasUsableVirtualEnv) {
367
+ const venvArgs = (0, python_uv_1.buildUvVenvArgs)('.venv');
368
+ if (fs.existsSync(venvDir)) {
369
+ venvArgs.push('--clear');
221
370
  }
371
+ console.log('📦 Creating Python virtual environment with uv...');
372
+ await runCommand(uvCommand, venvArgs, functionsDir, 'python virtual environment setup', uvEnv, false);
222
373
  }
223
- const pythonEnv = buildPythonFunctionsEnv(process.env, functionsDir);
224
- console.log(`📦 Installing Python Azure Functions dependencies${hasUv ? ' with uv' : ''}...`);
225
- if (hasUv) {
226
- await runCommand('uv', ['pip', 'install', '--python', pythonExecutable, '-r', 'requirements.txt'], functionsDir, 'python dependency installation', pythonEnv);
227
- }
228
- else {
229
- await runCommand('python', ['-m', 'pip', 'install', '--upgrade', 'pip'], functionsDir, 'python pip upgrade', pythonEnv);
230
- await runCommand('python', ['-m', 'pip', 'install', '-r', 'requirements.txt'], functionsDir, 'python dependency installation', pythonEnv);
231
- }
374
+ console.log('📦 Installing Python Azure Functions dependencies with uv...');
375
+ await runCommand(uvCommand, (0, python_uv_1.buildUvPipInstallArgs)(pythonExecutable, 'requirements.txt'), functionsDir, 'python dependency installation', uvEnv, false);
376
+ const pythonEnv = buildPythonFunctionsEnv(uvEnv, functionsDir);
232
377
  return bridgePythonCoreToolsForWindowsArm64(functionsDir, pythonEnv);
233
378
  }
234
379
  /**
@@ -236,7 +381,6 @@ async function preparePythonFunctionsEnvironment(functionsDir) {
236
381
  */
237
382
  async function checkCosmosDBEmulator() {
238
383
  return new Promise((resolve) => {
239
- const net = require('net');
240
384
  const socket = new net.Socket();
241
385
  const timeout = setTimeout(() => {
242
386
  socket.destroy();
@@ -254,53 +398,56 @@ async function checkCosmosDBEmulator() {
254
398
  socket.connect(8081, 'localhost');
255
399
  });
256
400
  }
257
- exports.devCommand = new commander_1.Command()
258
- .name('dev')
259
- .description('Start SwallowKit development server (Cosmos DB + Next.js + Azure Functions)')
260
- .option('-p, --port <port>', 'Next.js port', '3000')
261
- .option('-f, --functions-port <port>', 'Azure Functions port', '7071')
262
- .option('--host <host>', 'Host name', 'localhost')
263
- .option('--open', 'Open browser automatically', false)
264
- .option('--verbose', 'Show verbose logs', false)
265
- .option('--no-functions', 'Skip Azure Functions startup', false)
266
- .option('--seed-env <environment>', 'Replace Cosmos DB Emulator data from dev-seeds/<environment> before startup')
267
- .action(async (options) => {
268
- // SwallowKit プロジェクトディレクトリかどうかを検証
269
- (0, config_1.ensureSwallowKitProject)("dev");
270
- console.log('🚀 Starting SwallowKit development environment...');
271
- if (options.verbose) {
272
- console.log('⚙️ Options:', options);
273
- }
274
- await startDevEnvironment(options);
275
- });
401
+ function buildDevCommand(runDevEnvironment = startDevEnvironment, verifyProject = config_1.ensureSwallowKitProject) {
402
+ return new commander_1.Command()
403
+ .name('dev')
404
+ .description('Start SwallowKit development server (Cosmos DB + Next.js + Azure Functions)')
405
+ .option('-p, --port <port>', 'Next.js port', '3000')
406
+ .option('-f, --functions-port <port>', 'Azure Functions port', '7071')
407
+ .option('--host <host>', 'Host name', 'localhost')
408
+ .option('--open', 'Open browser automatically', false)
409
+ .option('--verbose', 'Show verbose logs', false)
410
+ .option('--no-functions', 'Skip Azure Functions startup')
411
+ .option('--seed-env <environment>', 'Replace Cosmos DB Emulator data from dev-seeds/<environment> before startup')
412
+ .option('--mock-connectors', 'Start mock server for connector models (serves Zod-generated data)', false)
413
+ .option('--swa-port <port>', 'SWA authentication emulator port', '4280')
414
+ .option('--no-swa', 'Skip the SWA authentication emulator')
415
+ .action(async (options) => {
416
+ const normalizedOptions = normalizeParsedDevOptions(options);
417
+ // SwallowKit プロジェクトディレクトリかどうかを検証
418
+ verifyProject("dev");
419
+ console.log('🚀 Starting SwallowKit development environment...');
420
+ if (normalizedOptions.verbose) {
421
+ console.log('⚙️ Options:', normalizedOptions);
422
+ }
423
+ await runDevEnvironment(normalizedOptions);
424
+ });
425
+ }
426
+ exports.devCommand = buildDevCommand();
276
427
  async function initializeCosmosDB(databaseName) {
277
428
  try {
278
- // Read local.settings.json from functions directory
279
- const functionsDir = path.join(process.cwd(), 'functions');
280
- const localSettingsPath = path.join(functionsDir, 'local.settings.json');
281
- if (!fs.existsSync(localSettingsPath)) {
282
- console.log('⚠️ local.settings.json not found. Skipping Cosmos DB initialization.');
283
- return null;
284
- }
285
- const localSettings = JSON.parse(fs.readFileSync(localSettingsPath, 'utf-8'));
286
- const connectionString = localSettings.Values?.CosmosDBConnection;
287
- const dbName = localSettings.Values?.COSMOS_DB_DATABASE_NAME || databaseName;
288
- if (!connectionString) {
289
- console.log('⚠️ CosmosDBConnection not found in local.settings.json. Skipping Cosmos DB initialization.');
429
+ const connectionInfoResult = (0, dev_seeds_1.resolveLocalCosmosConnectionInfo)(databaseName);
430
+ if (!connectionInfoResult.ok) {
431
+ if (connectionInfoResult.reason === 'missing-local-settings') {
432
+ console.log('⚠️ local.settings.json not found. Skipping Cosmos DB initialization.');
433
+ }
434
+ else if (connectionInfoResult.reason === 'missing-connection-string') {
435
+ console.log('⚠️ CosmosDBConnection not found in local.settings.json. Skipping Cosmos DB initialization.');
436
+ }
437
+ else {
438
+ console.log('⚠️ Invalid CosmosDB connection string format.');
439
+ }
290
440
  return null;
291
441
  }
292
442
  console.log('🗄️ Initializing Cosmos DB...');
293
- // Parse connection string
294
- const endpointMatch = connectionString.match(/AccountEndpoint=([^;]+)/);
295
- const keyMatch = connectionString.match(/AccountKey=([^;]+)/);
296
- if (!endpointMatch || !keyMatch) {
297
- console.log('⚠️ Invalid CosmosDB connection string format.');
298
- return null;
443
+ const { endpoint, key, databaseName: dbName, worktreeIsolationEnabled } = connectionInfoResult.value;
444
+ console.log(`📦 Cosmos DB database: ${dbName}`);
445
+ if (worktreeIsolationEnabled) {
446
+ console.log('🌳 Worktree isolation: enabled');
299
447
  }
300
- const endpoint = endpointMatch[1];
301
448
  const client = new cosmos_1.CosmosClient({
302
449
  endpoint: endpoint,
303
- key: keyMatch[1]
450
+ key,
304
451
  });
305
452
  // Create database if not exists
306
453
  const { database } = await client.databases.createIfNotExists({ id: dbName });
@@ -311,11 +458,11 @@ async function initializeCosmosDB(databaseName) {
311
458
  // Try creating container with full partition key definition first
312
459
  let containerCreated = false;
313
460
  try {
314
- console.log(`🔧 Creating container "${containerName}" with partition key /id...`);
461
+ console.log(`🔧 Creating container "${containerName}" with partition key ${model.partitionKey}...`);
315
462
  const containerResponse = await database.containers.createIfNotExists({
316
463
  id: containerName,
317
464
  partitionKey: {
318
- paths: ['/id'],
465
+ paths: [model.partitionKey],
319
466
  kind: cosmos_1.PartitionKeyKind.Hash,
320
467
  version: 2
321
468
  }
@@ -334,7 +481,7 @@ async function initializeCosmosDB(databaseName) {
334
481
  const containerResponse = await database.containers.createIfNotExists({
335
482
  id: containerName,
336
483
  partitionKey: {
337
- paths: ['/id']
484
+ paths: [model.partitionKey]
338
485
  }
339
486
  });
340
487
  console.log(`✅ Container "${containerName}" ready (status: ${containerResponse.statusCode})`);
@@ -350,7 +497,7 @@ async function initializeCosmosDB(databaseName) {
350
497
  }
351
498
  }
352
499
  console.log('✅ Cosmos DB initialization complete\n');
353
- return { endpoint, key: keyMatch[1], databaseName: dbName, models };
500
+ return { endpoint, key, databaseName: dbName, models };
354
501
  }
355
502
  catch (error) {
356
503
  console.error('⚠️ Cosmos DB initialization failed:', error.message);
@@ -364,16 +511,52 @@ async function initializeCosmosDB(databaseName) {
364
511
  async function startDevEnvironment(options) {
365
512
  const port = options.port || '3000';
366
513
  const functionsPort = options.functionsPort || '7071';
514
+ const swaPort = options.swaPort || '4280';
367
515
  // Detect package manager from project lockfile
368
516
  const pm = (0, package_manager_1.detectFromProject)();
369
517
  const pmCmd = (0, package_manager_1.getCommands)(pm);
370
518
  const backendLanguage = (0, config_1.getBackendLanguage)();
519
+ const authConfig = (0, config_1.getAuthConfig)();
520
+ const normalizedAuth = (0, config_1.normalizeAuthConfig)(authConfig);
521
+ const useSwaEmulator = Boolean(normalizedAuth && Object.values(normalizedAuth.schemes).some(scheme => scheme.provider === 'swa')) && !options.noSwa;
522
+ const swaCommand = useSwaEmulator ? await resolveSwaCliCommand(process.cwd()) : null;
523
+ if (useSwaEmulator && !swaCommand) {
524
+ const installCommand = getSwaCliInstallCommand(pm);
525
+ console.error('❌ SWA authentication requires Azure Static Web Apps CLI for local development.');
526
+ console.error(' Install it in this project and retry:');
527
+ console.error(`\n ${installCommand}\n`);
528
+ process.exitCode = 1;
529
+ return;
530
+ }
371
531
  // プロセスを管理する配列
372
532
  const processes = [];
373
533
  let functionsEnv = process.env;
534
+ let mockServer = null;
535
+ let envLocalPath = '';
536
+ let envLocalDefaultUrl = ''; // default Functions URL to restore on shutdown
537
+ const functionsBaseUrl = buildFunctionsBaseUrl(options.host, functionsPort);
538
+ let functionsReadinessPromise = null;
539
+ let functionsReady = !!options.noFunctions;
374
540
  // Cleanup processes on Ctrl+C
375
- process.on('SIGINT', () => {
541
+ process.on('SIGINT', async () => {
376
542
  console.log('\n🛑 Stopping development servers...');
543
+ // Restore .env.local to default Functions port on shutdown
544
+ if (envLocalPath && envLocalDefaultUrl) {
545
+ try {
546
+ if (fs.existsSync(envLocalPath)) {
547
+ const content = fs.readFileSync(envLocalPath, 'utf-8');
548
+ if (content.includes('BACKEND_FUNCTIONS_BASE_URL=') &&
549
+ !content.includes(`BACKEND_FUNCTIONS_BASE_URL=${envLocalDefaultUrl}`)) {
550
+ const restored = content.replace(/^BACKEND_FUNCTIONS_BASE_URL=.*/m, `BACKEND_FUNCTIONS_BASE_URL=${envLocalDefaultUrl}`);
551
+ fs.writeFileSync(envLocalPath, restored, 'utf-8');
552
+ }
553
+ }
554
+ }
555
+ catch { /* ignore */ }
556
+ }
557
+ if (mockServer) {
558
+ await mockServer.stop();
559
+ }
377
560
  processes.forEach((proc) => {
378
561
  if (proc && !proc.killed) {
379
562
  proc.kill();
@@ -398,10 +581,12 @@ async function startDevEnvironment(options) {
398
581
  // 2. Check if Azure Functions exists
399
582
  const functionsDir = path.join(process.cwd(), 'functions');
400
583
  const hasFunctions = fs.existsSync(functionsDir) && hasFunctionsProject(functionsDir, backendLanguage);
584
+ let functionsCoreToolsCommand = null;
585
+ let installedCoreToolsVersion = null;
401
586
  if (hasFunctions && !options.noFunctions) {
402
- // Check if Azure Functions Core Tools is installed
403
- const coreToolsInstalled = await checkCoreTools();
404
- if (!coreToolsInstalled) {
587
+ installedCoreToolsVersion = await resolveInstalledCoreToolsVersion();
588
+ functionsCoreToolsCommand = buildFunctionsCoreToolsCommand(backendLanguage, installedCoreToolsVersion);
589
+ if (functionsCoreToolsCommand.command === 'func' && !installedCoreToolsVersion) {
405
590
  console.log('');
406
591
  console.log('⚠️ Azure Functions Core Tools not found.');
407
592
  console.log('');
@@ -448,6 +633,9 @@ async function startDevEnvironment(options) {
448
633
  options.noFunctions = true;
449
634
  }
450
635
  }
636
+ else if (functionsCoreToolsCommand.command !== 'func') {
637
+ console.log(`ℹ️ Using ${functionsCoreToolsCommand.label}.`);
638
+ }
451
639
  if (!options.noFunctions) {
452
640
  // Check if Cosmos DB Emulator is running
453
641
  const cosmosRunning = await checkCosmosDBEmulator();
@@ -463,9 +651,7 @@ async function startDevEnvironment(options) {
463
651
  process.exit(1);
464
652
  }
465
653
  // Initialize Cosmos DB before starting Functions
466
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
467
- const appName = packageJson.name || 'App';
468
- const databaseName = `${appName.charAt(0).toUpperCase() + appName.slice(1)}Database`;
654
+ const databaseName = (0, dev_seeds_1.getDefaultCosmosDatabaseName)();
469
655
  const cosmosInitialization = await initializeCosmosDB(databaseName);
470
656
  if (options.seedEnv && cosmosInitialization) {
471
657
  await (0, dev_seeds_1.applyDevSeedEnvironment)({
@@ -488,42 +674,63 @@ async function startDevEnvironment(options) {
488
674
  }
489
675
  }
490
676
  else if (backendLanguage === 'csharp') {
491
- console.log('📦 Building C# Azure Functions project...');
492
- await runCommand('dotnet', ['build'], functionsDir, 'dotnet build');
677
+ console.log('ℹ️ C# Azure Functions can take longer on cold start while the worker builds.');
493
678
  }
494
679
  else {
495
680
  functionsEnv = await preparePythonFunctionsEnvironment(functionsDir);
496
681
  }
682
+ if (cosmosInitialization) {
683
+ functionsEnv = buildFunctionsCosmosEnvironment(functionsEnv, cosmosInitialization.databaseName);
684
+ }
497
685
  }
498
686
  }
499
687
  if (hasFunctions && !options.noFunctions) {
500
688
  // Build shared package before starting Functions
501
689
  const sharedDir = path.join(process.cwd(), 'shared');
502
- if (fs.existsSync(sharedDir) && fs.existsSync(path.join(sharedDir, 'package.json'))) {
503
- console.log('📦 Building shared package...');
504
- const filterArgs = pm === 'pnpm'
505
- ? ['run', '--filter', 'shared', 'build']
506
- : ['run', '--workspace=shared', 'build'];
507
- const sharedBuild = (0, child_process_1.spawn)(pm, filterArgs, {
508
- cwd: process.cwd(),
509
- shell: true,
510
- stdio: 'inherit',
511
- });
512
- await new Promise((resolve, reject) => {
513
- sharedBuild.on('close', (code) => {
514
- if (code === 0) {
515
- console.log('✅ Shared package built successfully');
516
- resolve();
517
- }
518
- else {
519
- reject(new Error(`Shared package build failed with code ${code}`));
520
- }
690
+ const sharedPkgPath = path.join(sharedDir, 'package.json');
691
+ if (fs.existsSync(sharedDir) && fs.existsSync(sharedPkgPath)) {
692
+ const sharedPkg = JSON.parse(fs.readFileSync(sharedPkgPath, 'utf-8'));
693
+ if (sharedPkg.scripts?.build) {
694
+ console.log('📦 Building shared package...');
695
+ const filterArgs = pm === 'pnpm'
696
+ ? ['run', '--filter', 'shared', 'build']
697
+ : ['run', '--workspace=shared', 'build'];
698
+ const sharedBuild = (0, child_process_1.spawn)(pm, filterArgs, {
699
+ cwd: process.cwd(),
700
+ shell: true,
701
+ stdio: 'inherit',
521
702
  });
522
- sharedBuild.on('error', reject);
523
- });
703
+ await new Promise((resolve, reject) => {
704
+ sharedBuild.on('close', (code) => {
705
+ if (code === 0) {
706
+ console.log('✅ Shared package built successfully');
707
+ resolve();
708
+ }
709
+ else {
710
+ reject(new Error(`Shared package build failed with code ${code}`));
711
+ }
712
+ });
713
+ sharedBuild.on('error', reject);
714
+ });
715
+ }
716
+ else {
717
+ console.log('⚠️ Shared package has no build script — skipping build. Run "swallowkit add-auth" to fix.');
718
+ }
719
+ }
720
+ // Build TypeScript functions after shared package (functions import from shared)
721
+ if (backendLanguage === 'typescript') {
722
+ const functionsPkgPath = path.join(functionsDir, 'package.json');
723
+ if (fs.existsSync(functionsPkgPath)) {
724
+ const functionsPkg = JSON.parse(fs.readFileSync(functionsPkgPath, 'utf-8'));
725
+ if (functionsPkg.scripts?.build) {
726
+ console.log('📦 Building TypeScript Azure Functions...');
727
+ await runCommand(pm, ['run', 'build'], functionsDir, `${pm} run build`);
728
+ }
729
+ }
524
730
  }
525
731
  // Azure Functions を起動
526
- const funcProcess = (0, child_process_1.spawn)('func', buildFunctionsStartArgs(functionsPort), {
732
+ const functionsCommand = functionsCoreToolsCommand ?? buildFunctionsCoreToolsCommand(backendLanguage, installedCoreToolsVersion);
733
+ const funcProcess = (0, child_process_1.spawn)(functionsCommand.command, [...functionsCommand.argsPrefix, ...buildFunctionsStartArgs(functionsPort)], {
527
734
  cwd: functionsDir,
528
735
  shell: true,
529
736
  stdio: 'pipe', // Always pipe to capture output
@@ -574,7 +781,8 @@ async function startDevEnvironment(options) {
574
781
  }
575
782
  }
576
783
  });
577
- console.log(`✅ Azure Functions started (port: ${functionsPort})`);
784
+ console.log(`⏳ Waiting for Azure Functions to accept requests at ${functionsBaseUrl}...`);
785
+ functionsReadinessPromise = waitForHttpServerReady(functionsBaseUrl, getFunctionsReadinessTimeoutMs(backendLanguage));
578
786
  }
579
787
  else if (!hasFunctions) {
580
788
  console.log('');
@@ -586,22 +794,91 @@ async function startDevEnvironment(options) {
586
794
  }
587
795
  console.log('');
588
796
  console.log('🚀 Starting Next.js development server...');
797
+ // Mock connector server — start proxy if --mock-connectors is enabled
798
+ let bffTargetPort = functionsPort;
799
+ if (options.mockConnectors) {
800
+ const allModels = await (0, dev_seeds_1.loadProjectModels)();
801
+ const connectorModels = allModels.filter((m) => m.connectorConfig);
802
+ // Resolve auth config — auth functions use RDB connectors, mocked alongside other models
803
+ let mockAuthConfig;
804
+ if (authConfig?.provider === 'custom-jwt' && authConfig.customJwt) {
805
+ // Read JWT_SECRET from functions/local.settings.json if available
806
+ let jwtSecret = 'dev-jwt-secret-change-in-production-min-32-chars!!';
807
+ try {
808
+ const localSettingsPath = path.join(process.cwd(), 'functions', 'local.settings.json');
809
+ if (fs.existsSync(localSettingsPath)) {
810
+ const localSettings = JSON.parse(fs.readFileSync(localSettingsPath, 'utf-8'));
811
+ jwtSecret = localSettings.Values?.[authConfig.customJwt.jwtSecretEnv || 'JWT_SECRET'] || jwtSecret;
812
+ }
813
+ }
814
+ catch { /* ignore */ }
815
+ mockAuthConfig = {
816
+ jwtSecret,
817
+ tokenExpiry: authConfig.customJwt.tokenExpiry,
818
+ customJwt: {
819
+ userTable: authConfig.customJwt.userTable,
820
+ loginIdColumn: authConfig.customJwt.loginIdColumn,
821
+ passwordHashColumn: authConfig.customJwt.passwordHashColumn,
822
+ rolesColumn: authConfig.customJwt.rolesColumn,
823
+ },
824
+ defaultPolicy: authConfig.authorization?.defaultPolicy === "public" ? "anonymous" :
825
+ authConfig.authorization?.defaultPolicy === "authenticated" || authConfig.authorization?.defaultPolicy === "anonymous"
826
+ ? authConfig.authorization.defaultPolicy : undefined,
827
+ };
828
+ }
829
+ if (connectorModels.length > 0 || mockAuthConfig) {
830
+ const mockPort = parseInt(functionsPort, 10) + 1;
831
+ mockServer = new connector_mock_server_1.ConnectorMockServer({
832
+ port: mockPort,
833
+ functionsTarget: `${options.host || 'localhost'}:${functionsPort}`,
834
+ connectorModels,
835
+ allModels,
836
+ seedEnv: options.seedEnv,
837
+ host: options.host || 'localhost',
838
+ authConfig: mockAuthConfig,
839
+ });
840
+ await mockServer.start();
841
+ bffTargetPort = String(mockPort);
842
+ const modelCount = connectorModels.length + (mockAuthConfig ? 1 : 0);
843
+ console.log('');
844
+ console.log(`🔌 Mock server started (port: ${mockPort}) — ${modelCount} model(s) mocked via Zod/seed data`);
845
+ for (const m of connectorModels) {
846
+ const ops = m.connectorConfig.operations.join(', ');
847
+ console.log(` - ${m.name} [${ops}]`);
848
+ }
849
+ if (mockAuthConfig) {
850
+ console.log(` - auth [login, me, logout]`);
851
+ }
852
+ console.log(` Other routes → proxied to Azure Functions (port: ${functionsPort})`);
853
+ }
854
+ else {
855
+ console.log('');
856
+ console.log('ℹ️ --mock-connectors specified but no connector models found. Skipping mock server.');
857
+ }
858
+ }
589
859
  // 5. Start Next.js development server
590
860
  const nextArgs = buildNextDevArgs(pm, port);
591
- if (options.open) {
592
- // Next.js 14+ deprecated --open option, so we open browser manually
593
- setTimeout(() => {
594
- const url = `http://${options.host || 'localhost'}:${port}`;
595
- console.log(`\n🌐 Opening browser: ${url}`);
596
- const start = process.platform === 'darwin' ? 'open' :
597
- process.platform === 'win32' ? 'start' : 'xdg-open';
598
- (0, child_process_1.spawn)(start, [url], { shell: true });
599
- }, 3000);
861
+ // Ensure .env.local points to bffTargetPort so Next.js reads the correct backend URL.
862
+ // When --mock-connectors is active, bffTargetPort = mock port (7072); otherwise = Functions port (7071).
863
+ // Next.js may load .env.local values that override spawn env vars, so we must keep them in sync.
864
+ envLocalPath = path.join(process.cwd(), '.env.local');
865
+ envLocalDefaultUrl = functionsBaseUrl;
866
+ const bffTargetUrl = buildFunctionsBaseUrl(options.host, bffTargetPort);
867
+ try {
868
+ if (fs.existsSync(envLocalPath)) {
869
+ const envContent = fs.readFileSync(envLocalPath, 'utf-8');
870
+ if (envContent.includes('BACKEND_FUNCTIONS_BASE_URL=') &&
871
+ !envContent.includes(`BACKEND_FUNCTIONS_BASE_URL=${bffTargetUrl}`)) {
872
+ const updated = envContent.replace(/^BACKEND_FUNCTIONS_BASE_URL=.*/m, `BACKEND_FUNCTIONS_BASE_URL=${bffTargetUrl}`);
873
+ fs.writeFileSync(envLocalPath, updated, 'utf-8');
874
+ }
875
+ }
600
876
  }
877
+ catch { /* ignore */ }
601
878
  const nextEnv = {
602
879
  ...process.env,
603
- BACKEND_FUNCTIONS_BASE_URL: `http://${options.host || 'localhost'}:${functionsPort}`,
604
- FUNCTIONS_BASE_URL: `http://${options.host || 'localhost'}:${functionsPort}`,
880
+ BACKEND_FUNCTIONS_BASE_URL: bffTargetUrl,
881
+ FUNCTIONS_BASE_URL: bffTargetUrl,
605
882
  };
606
883
  const nextProcess = (0, child_process_1.spawn)(pm === 'pnpm' ? 'pnpm' : 'npx', nextArgs, {
607
884
  cwd: process.cwd(),
@@ -619,6 +896,9 @@ async function startDevEnvironment(options) {
619
896
  console.log(`\n⏹️ Next.js exited (exit code: ${code})`);
620
897
  }
621
898
  // Exit all processes when Next.js exits
899
+ if (mockServer) {
900
+ mockServer.stop().catch(() => { });
901
+ }
622
902
  processes.forEach((proc) => {
623
903
  if (proc && !proc.killed) {
624
904
  proc.kill();
@@ -626,18 +906,90 @@ async function startDevEnvironment(options) {
626
906
  });
627
907
  process.exit(code || 0);
628
908
  });
909
+ if (functionsReadinessPromise) {
910
+ functionsReady = await functionsReadinessPromise;
911
+ console.log('');
912
+ if (functionsReady) {
913
+ console.log(`✅ Azure Functions ready (port: ${functionsPort})`);
914
+ }
915
+ else {
916
+ console.log(`⚠️ Azure Functions is still starting: ${functionsBaseUrl}`);
917
+ }
918
+ }
919
+ let swaReady = false;
920
+ if (useSwaEmulator && swaCommand) {
921
+ const nextUrl = `http://${options.host || 'localhost'}:${port}`;
922
+ console.log(`⏳ Waiting for Next.js before starting the SWA emulator at ${nextUrl}...`);
923
+ const nextReady = await waitForHttpServerReady(nextUrl);
924
+ if (!nextReady) {
925
+ throw new Error(`Next.js did not become ready at ${nextUrl}`);
926
+ }
927
+ console.log(`🔐 Starting SWA authentication emulator (port: ${swaPort})...`);
928
+ const swaProcess = (0, child_process_1.spawn)(swaCommand, buildSwaStartArgs(options.host, port, swaPort), {
929
+ cwd: process.cwd(),
930
+ shell: true,
931
+ stdio: 'inherit',
932
+ env: process.env,
933
+ });
934
+ processes.push(swaProcess);
935
+ swaProcess.on('error', (error) => {
936
+ console.error('❌ SWA CLI startup error:', error.message);
937
+ });
938
+ const swaUrl = `http://${options.host || 'localhost'}:${swaPort}`;
939
+ swaReady = await waitForHttpServerReady(swaUrl);
940
+ if (!swaReady) {
941
+ throw new Error(`SWA authentication emulator did not become ready at ${swaUrl}`);
942
+ }
943
+ }
629
944
  console.log('');
630
945
  console.log('✅ SwallowKit development environment is running!');
631
946
  console.log('');
632
- console.log(`📱 Next.js: http://${options.host || 'localhost'}:${port}`);
947
+ if (useSwaEmulator) {
948
+ const swaUrl = `http://${options.host || 'localhost'}:${swaPort}`;
949
+ console.log('🔐 SWA authenticated app:');
950
+ console.log(` ${swaUrl}`);
951
+ console.log('');
952
+ console.log('🔑 Local sign-in:');
953
+ console.log(` ${swaUrl}/.auth/login/aad`);
954
+ console.log(' Enter any username and select Login.');
955
+ console.log(' The "authenticated" role is added automatically.');
956
+ console.log('');
957
+ console.log('📱 Next.js direct access (authentication is not emulated):');
958
+ console.log(` http://${options.host || 'localhost'}:${port}`);
959
+ }
960
+ else {
961
+ console.log(`📱 Next.js: http://${options.host || 'localhost'}:${port}`);
962
+ }
633
963
  if (hasFunctions && !options.noFunctions) {
634
- console.log(`⚡ Azure Functions: http://${options.host || 'localhost'}:${functionsPort}`);
964
+ console.log(`${functionsReady ? '⚡ Azure Functions' : '⏳ Azure Functions (starting)'}: ${functionsBaseUrl}`);
965
+ }
966
+ if (mockServer) {
967
+ console.log(`🔌 Mock Proxy: ${bffTargetUrl} (BFF → here)`);
635
968
  }
636
969
  console.log('');
637
- if (hasFunctions && !options.noFunctions) {
970
+ if (hasFunctions && !options.noFunctions && functionsReady) {
638
971
  console.log('💡 Azure Functions and Next.js BFF are connected');
639
972
  }
973
+ else if (hasFunctions && !options.noFunctions) {
974
+ console.log('💡 Azure Functions is still warming up; BFF routes can fail until the backend responds.');
975
+ }
976
+ if (mockServer) {
977
+ console.log('💡 Connector models served from mock server (Zod-generated data)');
978
+ }
979
+ if (swaReady) {
980
+ console.log('💡 Open the app through the SWA port when testing authenticated pages and CRUD APIs.');
981
+ }
640
982
  console.log('');
983
+ if (options.open) {
984
+ const url = useSwaEmulator
985
+ ? `http://${options.host || 'localhost'}:${swaPort}`
986
+ : `http://${options.host || 'localhost'}:${port}`;
987
+ console.log(`🌐 Opening browser: ${url}`);
988
+ const start = process.platform === 'darwin' ? 'open' :
989
+ process.platform === 'win32' ? 'start' : 'xdg-open';
990
+ (0, child_process_1.spawn)(start, [url], { shell: true });
991
+ console.log('');
992
+ }
641
993
  console.log('🛑 Press Ctrl+C to stop');
642
994
  console.log('');
643
995
  }
@@ -651,11 +1003,25 @@ async function startDevEnvironment(options) {
651
1003
  process.exit(1);
652
1004
  }
653
1005
  }
654
- async function runCommand(command, args, cwd, label, env) {
1006
+ async function resolveSwaCliCommand(projectRoot) {
1007
+ const binaryName = process.platform === 'win32' ? 'swa.cmd' : 'swa';
1008
+ const localCommand = path.join(projectRoot, 'node_modules', '.bin', binaryName);
1009
+ if (fs.existsSync(localCommand) && await checkCommand(localCommand, ['--version'], {
1010
+ cwd: projectRoot,
1011
+ shell: process.platform === 'win32',
1012
+ })) {
1013
+ return localCommand;
1014
+ }
1015
+ if (await checkCommand('swa', ['--version'], { cwd: projectRoot })) {
1016
+ return 'swa';
1017
+ }
1018
+ return null;
1019
+ }
1020
+ async function runCommand(command, args, cwd, label, env, shell = true) {
655
1021
  await new Promise((resolve, reject) => {
656
1022
  const child = (0, child_process_1.spawn)(command, args, {
657
1023
  cwd,
658
- shell: true,
1024
+ shell,
659
1025
  stdio: 'inherit',
660
1026
  env,
661
1027
  });