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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. package/LICENSE +21 -21
  2. package/README.ja.md +153 -242
  3. package/README.md +153 -243
  4. package/dist/__tests__/fixtures.d.ts +22 -0
  5. package/dist/__tests__/fixtures.d.ts.map +1 -0
  6. package/dist/__tests__/fixtures.js +146 -0
  7. package/dist/__tests__/fixtures.js.map +1 -0
  8. package/dist/cli/commands/add-auth.d.ts +13 -0
  9. package/dist/cli/commands/add-auth.d.ts.map +1 -0
  10. package/dist/cli/commands/add-auth.js +759 -0
  11. package/dist/cli/commands/add-auth.js.map +1 -0
  12. package/dist/cli/commands/add-connector.d.ts +20 -0
  13. package/dist/cli/commands/add-connector.d.ts.map +1 -0
  14. package/dist/cli/commands/add-connector.js +163 -0
  15. package/dist/cli/commands/add-connector.js.map +1 -0
  16. package/dist/cli/commands/create-model.d.ts +1 -4
  17. package/dist/cli/commands/create-model.d.ts.map +1 -1
  18. package/dist/cli/commands/create-model.js +19 -81
  19. package/dist/cli/commands/create-model.js.map +1 -1
  20. package/dist/cli/commands/dev-seeds.d.ts +57 -0
  21. package/dist/cli/commands/dev-seeds.d.ts.map +1 -0
  22. package/dist/cli/commands/dev-seeds.js +470 -0
  23. package/dist/cli/commands/dev-seeds.js.map +1 -0
  24. package/dist/cli/commands/dev.d.ts +37 -0
  25. package/dist/cli/commands/dev.d.ts.map +1 -1
  26. package/dist/cli/commands/dev.js +720 -156
  27. package/dist/cli/commands/dev.js.map +1 -1
  28. package/dist/cli/commands/index.d.ts +1 -0
  29. package/dist/cli/commands/index.d.ts.map +1 -1
  30. package/dist/cli/commands/index.js +3 -1
  31. package/dist/cli/commands/index.js.map +1 -1
  32. package/dist/cli/commands/init.d.ts +67 -0
  33. package/dist/cli/commands/init.d.ts.map +1 -1
  34. package/dist/cli/commands/init.js +3080 -1771
  35. package/dist/cli/commands/init.js.map +1 -1
  36. package/dist/cli/commands/scaffold.d.ts +13 -0
  37. package/dist/cli/commands/scaffold.d.ts.map +1 -1
  38. package/dist/cli/commands/scaffold.js +513 -129
  39. package/dist/cli/commands/scaffold.js.map +1 -1
  40. package/dist/cli/index.d.ts +5 -1
  41. package/dist/cli/index.d.ts.map +1 -1
  42. package/dist/cli/index.js +206 -55
  43. package/dist/cli/index.js.map +1 -1
  44. package/dist/core/config.d.ts +14 -3
  45. package/dist/core/config.d.ts.map +1 -1
  46. package/dist/core/config.js +172 -5
  47. package/dist/core/config.js.map +1 -1
  48. package/dist/core/mock/connector-mock-server.d.ts +101 -0
  49. package/dist/core/mock/connector-mock-server.d.ts.map +1 -0
  50. package/dist/core/mock/connector-mock-server.js +480 -0
  51. package/dist/core/mock/connector-mock-server.js.map +1 -0
  52. package/dist/core/mock/zod-mock-generator.d.ts +14 -0
  53. package/dist/core/mock/zod-mock-generator.d.ts.map +1 -0
  54. package/dist/core/mock/zod-mock-generator.js +163 -0
  55. package/dist/core/mock/zod-mock-generator.js.map +1 -0
  56. package/dist/core/operations/create-model.d.ts +15 -0
  57. package/dist/core/operations/create-model.d.ts.map +1 -0
  58. package/dist/core/operations/create-model.js +171 -0
  59. package/dist/core/operations/create-model.js.map +1 -0
  60. package/dist/core/operations/runtime.d.ts +32 -0
  61. package/dist/core/operations/runtime.d.ts.map +1 -0
  62. package/dist/core/operations/runtime.js +225 -0
  63. package/dist/core/operations/runtime.js.map +1 -0
  64. package/dist/core/operations/scaffold-machine.d.ts +16 -0
  65. package/dist/core/operations/scaffold-machine.d.ts.map +1 -0
  66. package/dist/core/operations/scaffold-machine.js +63 -0
  67. package/dist/core/operations/scaffold-machine.js.map +1 -0
  68. package/dist/core/project/manifest.d.ts +92 -0
  69. package/dist/core/project/manifest.d.ts.map +1 -0
  70. package/dist/core/project/manifest.js +321 -0
  71. package/dist/core/project/manifest.js.map +1 -0
  72. package/dist/core/project/validation.d.ts +20 -0
  73. package/dist/core/project/validation.d.ts.map +1 -0
  74. package/dist/core/project/validation.js +226 -0
  75. package/dist/core/project/validation.js.map +1 -0
  76. package/dist/core/scaffold/auth-generator.d.ts +63 -0
  77. package/dist/core/scaffold/auth-generator.d.ts.map +1 -0
  78. package/dist/core/scaffold/auth-generator.js +1714 -0
  79. package/dist/core/scaffold/auth-generator.js.map +1 -0
  80. package/dist/core/scaffold/connector-functions-generator.d.ts +41 -0
  81. package/dist/core/scaffold/connector-functions-generator.d.ts.map +1 -0
  82. package/dist/core/scaffold/connector-functions-generator.js +1029 -0
  83. package/dist/core/scaffold/connector-functions-generator.js.map +1 -0
  84. package/dist/core/scaffold/functions-generator.d.ts +7 -1
  85. package/dist/core/scaffold/functions-generator.d.ts.map +1 -1
  86. package/dist/core/scaffold/functions-generator.js +922 -220
  87. package/dist/core/scaffold/functions-generator.js.map +1 -1
  88. package/dist/core/scaffold/model-parser.d.ts +20 -1
  89. package/dist/core/scaffold/model-parser.d.ts.map +1 -1
  90. package/dist/core/scaffold/model-parser.js +333 -139
  91. package/dist/core/scaffold/model-parser.js.map +1 -1
  92. package/dist/core/scaffold/native-schema-generator.d.ts +13 -0
  93. package/dist/core/scaffold/native-schema-generator.d.ts.map +1 -0
  94. package/dist/core/scaffold/native-schema-generator.js +696 -0
  95. package/dist/core/scaffold/native-schema-generator.js.map +1 -0
  96. package/dist/core/scaffold/nextjs-generator.d.ts +8 -0
  97. package/dist/core/scaffold/nextjs-generator.d.ts.map +1 -1
  98. package/dist/core/scaffold/nextjs-generator.js +326 -182
  99. package/dist/core/scaffold/nextjs-generator.js.map +1 -1
  100. package/dist/core/scaffold/openapi-generator.d.ts +3 -0
  101. package/dist/core/scaffold/openapi-generator.d.ts.map +1 -0
  102. package/dist/core/scaffold/openapi-generator.js +190 -0
  103. package/dist/core/scaffold/openapi-generator.js.map +1 -0
  104. package/dist/core/scaffold/ui-generator.d.ts +13 -4
  105. package/dist/core/scaffold/ui-generator.d.ts.map +1 -1
  106. package/dist/core/scaffold/ui-generator.js +772 -663
  107. package/dist/core/scaffold/ui-generator.js.map +1 -1
  108. package/dist/database/base-model.d.ts +3 -3
  109. package/dist/database/base-model.js +3 -3
  110. package/dist/index.d.ts +2 -2
  111. package/dist/index.d.ts.map +1 -1
  112. package/dist/index.js +2 -1
  113. package/dist/index.js.map +1 -1
  114. package/dist/machine/contracts.d.ts +16 -0
  115. package/dist/machine/contracts.d.ts.map +1 -0
  116. package/dist/machine/contracts.js +3 -0
  117. package/dist/machine/contracts.js.map +1 -0
  118. package/dist/machine/errors.d.ts +11 -0
  119. package/dist/machine/errors.d.ts.map +1 -0
  120. package/dist/machine/errors.js +34 -0
  121. package/dist/machine/errors.js.map +1 -0
  122. package/dist/machine/index.d.ts +3 -0
  123. package/dist/machine/index.d.ts.map +1 -0
  124. package/dist/machine/index.js +156 -0
  125. package/dist/machine/index.js.map +1 -0
  126. package/dist/mcp/index.d.ts +25 -0
  127. package/dist/mcp/index.d.ts.map +1 -0
  128. package/dist/mcp/index.js +202 -0
  129. package/dist/mcp/index.js.map +1 -0
  130. package/dist/types/index.d.ts +95 -0
  131. package/dist/types/index.d.ts.map +1 -1
  132. package/dist/utils/package-manager.d.ts +5 -2
  133. package/dist/utils/package-manager.d.ts.map +1 -1
  134. package/dist/utils/package-manager.js +34 -13
  135. package/dist/utils/package-manager.js.map +1 -1
  136. package/dist/utils/python-uv.d.ts +21 -0
  137. package/dist/utils/python-uv.d.ts.map +1 -0
  138. package/dist/utils/python-uv.js +111 -0
  139. package/dist/utils/python-uv.js.map +1 -0
  140. package/dist/version.d.ts +2 -0
  141. package/dist/version.d.ts.map +1 -0
  142. package/dist/version.js +49 -0
  143. package/dist/version.js.map +1 -0
  144. package/package.json +84 -74
  145. package/src/__tests__/__snapshots__/functions-generator.test.ts.snap +1139 -0
  146. package/src/__tests__/__snapshots__/nextjs-generator.test.ts.snap +206 -0
  147. package/src/__tests__/__snapshots__/ui-generator.test.ts.snap +532 -0
  148. package/src/__tests__/auth.test.ts +761 -0
  149. package/src/__tests__/config.test.ts +295 -0
  150. package/src/__tests__/connector-functions-generator.test.ts +288 -0
  151. package/src/__tests__/connector-mock-server.test.ts +439 -0
  152. package/src/__tests__/connector-model-bff.test.ts +162 -0
  153. package/src/__tests__/dev-seeds.test.ts +173 -0
  154. package/src/__tests__/dev.test.ts +287 -0
  155. package/src/__tests__/fixtures.ts +144 -0
  156. package/src/__tests__/functions-generator.test.ts +237 -0
  157. package/src/__tests__/init.test.ts +327 -0
  158. package/src/__tests__/jest-globals.d.ts +8 -0
  159. package/src/__tests__/machine.test.ts +356 -0
  160. package/src/__tests__/mcp.test.ts +145 -0
  161. package/src/__tests__/model-parser.test.ts +52 -0
  162. package/src/__tests__/multi-auth.test.ts +133 -0
  163. package/src/__tests__/nextjs-generator.test.ts +105 -0
  164. package/src/__tests__/openapi-generator.test.ts +43 -0
  165. package/src/__tests__/package-manager.test.ts +227 -0
  166. package/src/__tests__/python-uv.test.ts +48 -0
  167. package/src/__tests__/scaffold.test.ts +191 -0
  168. package/src/__tests__/string-utils.test.ts +75 -0
  169. package/src/__tests__/tsconfig.json +15 -0
  170. package/src/__tests__/ui-generator.test.ts +144 -0
  171. package/src/__tests__/zod-mock-generator.test.ts +132 -0
  172. package/src/cli/commands/add-auth.ts +815 -0
  173. package/src/cli/commands/add-connector.ts +158 -0
  174. package/src/cli/commands/create-model.ts +62 -0
  175. package/src/cli/commands/dev-seeds.ts +614 -0
  176. package/src/cli/commands/dev.ts +1234 -0
  177. package/src/cli/commands/index.ts +9 -0
  178. package/src/cli/commands/init.ts +3902 -0
  179. package/src/cli/commands/provision.ts +193 -0
  180. package/src/cli/commands/scaffold.ts +1067 -0
  181. package/src/cli/index.ts +202 -0
  182. package/src/core/config.ts +379 -0
  183. package/src/core/mock/connector-mock-server.ts +555 -0
  184. package/src/core/mock/zod-mock-generator.ts +205 -0
  185. package/src/core/operations/create-model.ts +174 -0
  186. package/src/core/operations/runtime.ts +235 -0
  187. package/src/core/operations/scaffold-machine.ts +91 -0
  188. package/src/core/project/manifest.ts +402 -0
  189. package/src/core/project/validation.ts +245 -0
  190. package/src/core/scaffold/auth-generator.ts +1760 -0
  191. package/src/core/scaffold/connector-functions-generator.ts +1133 -0
  192. package/src/core/scaffold/functions-generator.ts +972 -0
  193. package/src/core/scaffold/model-parser.ts +847 -0
  194. package/src/core/scaffold/native-schema-generator.ts +822 -0
  195. package/src/core/scaffold/nextjs-generator.ts +382 -0
  196. package/src/core/scaffold/openapi-generator.ts +212 -0
  197. package/src/core/scaffold/ui-generator.ts +1068 -0
  198. package/src/database/base-model.ts +184 -0
  199. package/src/database/client.ts +140 -0
  200. package/src/database/repository.ts +104 -0
  201. package/src/database/runtime-check.ts +25 -0
  202. package/src/index.ts +27 -0
  203. package/src/machine/contracts.ts +17 -0
  204. package/src/machine/errors.ts +34 -0
  205. package/src/machine/index.ts +173 -0
  206. package/src/mcp/index.ts +209 -0
  207. package/src/types/index.ts +165 -0
  208. package/src/utils/package-manager.ts +246 -0
  209. package/src/utils/python-uv.ts +96 -0
  210. package/src/version.ts +13 -0
  211. package/dist/cli/commands/build.d.ts +0 -6
  212. package/dist/cli/commands/build.d.ts.map +0 -1
  213. package/dist/cli/commands/build.js +0 -177
  214. package/dist/cli/commands/build.js.map +0 -1
  215. package/dist/cli/commands/deploy.d.ts +0 -3
  216. package/dist/cli/commands/deploy.d.ts.map +0 -1
  217. package/dist/cli/commands/deploy.js +0 -147
  218. package/dist/cli/commands/deploy.js.map +0 -1
  219. package/dist/cli/commands/setup.d.ts +0 -6
  220. package/dist/cli/commands/setup.d.ts.map +0 -1
  221. package/dist/cli/commands/setup.js +0 -254
  222. package/dist/cli/commands/setup.js.map +0 -1
@@ -34,20 +34,169 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.devCommand = void 0;
37
+ exports.buildFunctionsStartArgs = buildFunctionsStartArgs;
38
+ exports.parseCoreToolsVersion = parseCoreToolsVersion;
39
+ exports.compareVersionNumbers = compareVersionNumbers;
40
+ exports.buildFunctionsCoreToolsCommand = buildFunctionsCoreToolsCommand;
41
+ exports.buildNextDevArgs = buildNextDevArgs;
42
+ exports.buildFunctionsBaseUrl = buildFunctionsBaseUrl;
43
+ exports.buildSwaStartArgs = buildSwaStartArgs;
44
+ exports.getSwaCliInstallCommand = getSwaCliInstallCommand;
45
+ exports.getFunctionsReadinessTimeoutMs = getFunctionsReadinessTimeoutMs;
46
+ exports.waitForHttpServerReady = waitForHttpServerReady;
47
+ exports.getPythonVirtualEnvPaths = getPythonVirtualEnvPaths;
48
+ exports.getCSharpFunctionsBuildArtifactPaths = getCSharpFunctionsBuildArtifactPaths;
49
+ exports.buildPythonFunctionsEnv = buildPythonFunctionsEnv;
50
+ exports.buildDevCommand = buildDevCommand;
37
51
  const commander_1 = require("commander");
38
52
  const child_process_1 = require("child_process");
53
+ const http = __importStar(require("http"));
54
+ const https = __importStar(require("https"));
39
55
  const path = __importStar(require("path"));
40
56
  const fs = __importStar(require("fs"));
57
+ const os = __importStar(require("os"));
58
+ const net = __importStar(require("net"));
41
59
  const cosmos_1 = require("@azure/cosmos");
42
60
  const config_1 = require("../../core/config");
61
+ const dev_seeds_1 = require("./dev-seeds");
43
62
  const package_manager_1 = require("../../utils/package-manager");
63
+ const python_uv_1 = require("../../utils/python-uv");
64
+ const connector_mock_server_1 = require("../../core/mock/connector-mock-server");
65
+ const MINIMUM_CSHARP_CORE_TOOLS_VERSION = '4.6.0';
66
+ const NPM_CORE_TOOLS_PACKAGE = 'azure-functions-core-tools@4';
67
+ function normalizeParsedDevOptions(options) {
68
+ return {
69
+ ...options,
70
+ noFunctions: options.noFunctions ?? options.functions === false,
71
+ noSwa: options.noSwa ?? options.swa === false,
72
+ };
73
+ }
74
+ function buildFunctionsStartArgs(functionsPort) {
75
+ return ['start', '--port', functionsPort];
76
+ }
77
+ function parseCoreToolsVersion(output) {
78
+ const match = output.match(/\d+\.\d+\.\d+/);
79
+ return match ? match[0] : null;
80
+ }
81
+ function compareVersionNumbers(left, right) {
82
+ const leftParts = left.split('.').map((value) => Number.parseInt(value, 10));
83
+ const rightParts = right.split('.').map((value) => Number.parseInt(value, 10));
84
+ const length = Math.max(leftParts.length, rightParts.length);
85
+ for (let index = 0; index < length; index += 1) {
86
+ const leftPart = leftParts[index] ?? 0;
87
+ const rightPart = rightParts[index] ?? 0;
88
+ if (leftPart !== rightPart) {
89
+ return leftPart - rightPart;
90
+ }
91
+ }
92
+ return 0;
93
+ }
94
+ function buildFunctionsCoreToolsCommand(backendLanguage, installedVersion) {
95
+ if (backendLanguage === 'csharp' &&
96
+ (!installedVersion || compareVersionNumbers(installedVersion, MINIMUM_CSHARP_CORE_TOOLS_VERSION) < 0)) {
97
+ const reason = installedVersion
98
+ ? `installed func ${installedVersion} is too old for C# isolated`
99
+ : 'func is not installed';
100
+ return {
101
+ command: 'npm',
102
+ argsPrefix: ['exec', '--yes', NPM_CORE_TOOLS_PACKAGE, '--'],
103
+ label: `npm exec ${NPM_CORE_TOOLS_PACKAGE} (${reason})`,
104
+ };
105
+ }
106
+ return {
107
+ command: 'func',
108
+ argsPrefix: [],
109
+ label: installedVersion ? `func ${installedVersion}` : 'func',
110
+ };
111
+ }
112
+ function buildNextDevArgs(pm, port) {
113
+ const baseArgs = ['next', 'dev', '--port', port, '--webpack'];
114
+ return pm === 'pnpm' ? ['exec', ...baseArgs] : baseArgs;
115
+ }
116
+ function buildFunctionsBaseUrl(host, functionsPort) {
117
+ return `http://${host || 'localhost'}:${functionsPort}`;
118
+ }
119
+ function buildSwaStartArgs(host, nextPort, swaPort) {
120
+ return [
121
+ 'start',
122
+ `http://${host || 'localhost'}:${nextPort}`,
123
+ '--swa-config-location',
124
+ '.',
125
+ '--port',
126
+ swaPort,
127
+ ];
128
+ }
129
+ function getSwaCliInstallCommand(pm) {
130
+ return pm === 'pnpm'
131
+ ? 'pnpm add -Dw @azure/static-web-apps-cli'
132
+ : 'npm install -D @azure/static-web-apps-cli';
133
+ }
134
+ function getFunctionsReadinessTimeoutMs(backendLanguage) {
135
+ return backendLanguage === 'csharp' ? 90000 : 30000;
136
+ }
137
+ async function waitForHttpServerReady(url, timeoutMs = 30000, intervalMs = 500) {
138
+ const deadline = Date.now() + timeoutMs;
139
+ while (Date.now() <= deadline) {
140
+ if (await probeHttpServer(url)) {
141
+ return true;
142
+ }
143
+ if (Date.now() + intervalMs > deadline) {
144
+ break;
145
+ }
146
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
147
+ }
148
+ return probeHttpServer(url);
149
+ }
150
+ function getPythonVirtualEnvPaths(functionsDir) {
151
+ const venvDir = path.join(functionsDir, '.venv');
152
+ const binDir = process.platform === 'win32'
153
+ ? path.join(venvDir, 'Scripts')
154
+ : path.join(venvDir, 'bin');
155
+ const pythonExecutable = process.platform === 'win32'
156
+ ? path.join(binDir, 'python.exe')
157
+ : path.join(binDir, 'python');
158
+ return { venvDir, binDir, pythonExecutable };
159
+ }
160
+ function getCSharpFunctionsBuildArtifactPaths(functionsDir) {
161
+ return [
162
+ path.join(functionsDir, 'bin'),
163
+ path.join(functionsDir, 'obj'),
164
+ ];
165
+ }
166
+ function buildPythonFunctionsEnv(baseEnv, functionsDir) {
167
+ const { venvDir, binDir, pythonExecutable } = getPythonVirtualEnvPaths(functionsDir);
168
+ const pathKey = getPathEnvKey(baseEnv);
169
+ const currentPath = baseEnv[pathKey] || '';
170
+ return {
171
+ ...baseEnv,
172
+ [pathKey]: currentPath ? `${binDir}${path.delimiter}${currentPath}` : binDir,
173
+ VIRTUAL_ENV: venvDir,
174
+ languageWorkers__python__defaultExecutablePath: pythonExecutable,
175
+ };
176
+ }
177
+ function getPathEnvKey(env) {
178
+ return Object.keys(env).find((key) => key.toUpperCase() === 'PATH') || 'PATH';
179
+ }
180
+ function prependToPathEnv(env, entry) {
181
+ const pathKey = getPathEnvKey(env);
182
+ const currentPath = env[pathKey] || '';
183
+ return {
184
+ ...env,
185
+ [pathKey]: currentPath ? `${entry}${path.delimiter}${currentPath}` : entry,
186
+ };
187
+ }
44
188
  /**
45
189
  * Check if Azure Functions Core Tools is installed
46
190
  */
47
191
  async function checkCoreTools() {
192
+ return checkCommand('func', ['--version']);
193
+ }
194
+ async function checkCommand(command, args = ['--version'], options) {
48
195
  return new Promise((resolve) => {
49
- const checkProcess = (0, child_process_1.spawn)('func', ['--version'], {
50
- shell: true,
196
+ const checkProcess = (0, child_process_1.spawn)(command, args, {
197
+ cwd: options?.cwd,
198
+ env: options?.env ?? process.env,
199
+ shell: options?.shell ?? true,
51
200
  stdio: 'pipe',
52
201
  });
53
202
  checkProcess.on('close', (code) => {
@@ -58,12 +207,173 @@ async function checkCoreTools() {
58
207
  });
59
208
  });
60
209
  }
210
+ async function resolveProjectLocalUvCommand(projectRoot) {
211
+ const uvEnv = (0, python_uv_1.buildProjectLocalUvEnv)(process.env, projectRoot);
212
+ const { localUvExecutable } = (0, python_uv_1.getProjectLocalUvPaths)(projectRoot);
213
+ if (await checkCommand('uv', ['--version'], { shell: false })) {
214
+ return { command: 'uv', env: uvEnv };
215
+ }
216
+ if (fs.existsSync(localUvExecutable) && await checkCommand(localUvExecutable, ['--version'], { shell: false })) {
217
+ return { command: localUvExecutable, env: uvEnv };
218
+ }
219
+ console.log('📦 Installing project-local uv...');
220
+ const installer = (0, python_uv_1.getProjectLocalUvInstallerCommand)();
221
+ await runCommand(installer.command, installer.args, projectRoot, 'uv installation', (0, python_uv_1.buildProjectLocalUvInstallerEnv)(process.env, projectRoot), false);
222
+ if (!(fs.existsSync(localUvExecutable) && await checkCommand(localUvExecutable, ['--version'], { shell: false }))) {
223
+ throw new Error('Failed to install project-local uv.');
224
+ }
225
+ return { command: localUvExecutable, env: uvEnv };
226
+ }
227
+ async function getCommandPath(command) {
228
+ const locator = process.platform === 'win32' ? 'where' : 'which';
229
+ const result = await captureCommandOutput(locator, [command]);
230
+ const firstLine = result
231
+ .split(/\r?\n/)
232
+ .map((line) => line.trim())
233
+ .find(Boolean);
234
+ return firstLine || null;
235
+ }
236
+ async function resolveInstalledCoreToolsVersion() {
237
+ if (!(await checkCoreTools())) {
238
+ return null;
239
+ }
240
+ try {
241
+ return parseCoreToolsVersion(await captureCommandOutput('func', ['--version']));
242
+ }
243
+ catch {
244
+ return null;
245
+ }
246
+ }
247
+ async function captureCommandOutput(command, args, cwd, env) {
248
+ return new Promise((resolve, reject) => {
249
+ const child = (0, child_process_1.spawn)(command, args, {
250
+ cwd,
251
+ env,
252
+ shell: false,
253
+ stdio: ['ignore', 'pipe', 'pipe'],
254
+ });
255
+ let stdout = '';
256
+ let stderr = '';
257
+ child.stdout?.on('data', (data) => {
258
+ stdout += data.toString();
259
+ });
260
+ child.stderr?.on('data', (data) => {
261
+ stderr += data.toString();
262
+ });
263
+ child.on('close', (code) => {
264
+ if (code === 0) {
265
+ resolve(stdout);
266
+ }
267
+ else {
268
+ reject(new Error(stderr || stdout || `${command} exited with code ${code}`));
269
+ }
270
+ });
271
+ child.on('error', reject);
272
+ });
273
+ }
274
+ async function probeHttpServer(url) {
275
+ return new Promise((resolve) => {
276
+ const target = new URL(url);
277
+ const requestFactory = target.protocol === 'https:' ? https.request : http.request;
278
+ let settled = false;
279
+ const finish = (value) => {
280
+ if (!settled) {
281
+ settled = true;
282
+ resolve(value);
283
+ }
284
+ };
285
+ const request = requestFactory({
286
+ hostname: target.hostname,
287
+ port: target.port,
288
+ path: target.pathname || '/',
289
+ method: 'GET',
290
+ timeout: 1000,
291
+ }, (response) => {
292
+ response.resume();
293
+ finish(true);
294
+ });
295
+ request.on('timeout', () => {
296
+ request.destroy();
297
+ finish(false);
298
+ });
299
+ request.on('error', () => finish(false));
300
+ request.end();
301
+ });
302
+ }
303
+ async function resolvePythonRuntimeDetails(functionsDir, env) {
304
+ const { pythonExecutable } = getPythonVirtualEnvPaths(functionsDir);
305
+ const output = await captureCommandOutput(pythonExecutable, [
306
+ '-c',
307
+ 'import platform; import sys; print(str(sys.version_info.major) + "." + str(sys.version_info.minor)); print(platform.machine())',
308
+ ], functionsDir, env);
309
+ const [version, architecture] = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
310
+ if (!version || !architecture) {
311
+ throw new Error('Failed to determine Python runtime details.');
312
+ }
313
+ return { version, architecture };
314
+ }
315
+ async function bridgePythonCoreToolsForWindowsArm64(functionsDir, env) {
316
+ if (process.platform !== 'win32' || process.arch !== 'arm64') {
317
+ return env;
318
+ }
319
+ const funcPath = await getCommandPath('func');
320
+ if (!funcPath) {
321
+ return env;
322
+ }
323
+ const { version, architecture } = await resolvePythonRuntimeDetails(functionsDir, env);
324
+ if (architecture.toUpperCase() !== 'AMD64') {
325
+ return env;
326
+ }
327
+ const coreToolsRoot = path.dirname(funcPath);
328
+ const armWorkerDir = path.join(coreToolsRoot, 'workers', 'python', version, 'WINDOWS', 'Arm64');
329
+ if (fs.existsSync(armWorkerDir)) {
330
+ return env;
331
+ }
332
+ const x64WorkerDir = path.join(coreToolsRoot, 'workers', 'python', version, 'WINDOWS', 'X64');
333
+ if (!fs.existsSync(x64WorkerDir)) {
334
+ return env;
335
+ }
336
+ const patchedRoot = path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'), 'SwallowKit', 'azure-functions-core-tools-python-bridge');
337
+ if (!fs.existsSync(path.join(patchedRoot, 'func.exe'))) {
338
+ console.log('🩹 Creating a local Azure Functions Core Tools bridge for Windows Arm64 Python...');
339
+ fs.mkdirSync(path.dirname(patchedRoot), { recursive: true });
340
+ fs.cpSync(coreToolsRoot, patchedRoot, { recursive: true });
341
+ }
342
+ const patchedArmWorkerDir = path.join(patchedRoot, 'workers', 'python', version, 'WINDOWS', 'Arm64');
343
+ const patchedX64WorkerDir = path.join(patchedRoot, 'workers', 'python', version, 'WINDOWS', 'X64');
344
+ if (!fs.existsSync(patchedArmWorkerDir) && fs.existsSync(patchedX64WorkerDir)) {
345
+ fs.cpSync(patchedX64WorkerDir, patchedArmWorkerDir, { recursive: true });
346
+ }
347
+ console.log(`🩹 Using bridged Azure Functions Core Tools from ${patchedRoot}`);
348
+ return prependToPathEnv(env, patchedRoot);
349
+ }
350
+ async function preparePythonFunctionsEnvironment(functionsDir) {
351
+ const projectRoot = (0, python_uv_1.getPythonProjectRoot)(functionsDir);
352
+ const { command: uvCommand, env: uvEnv } = await resolveProjectLocalUvCommand(projectRoot);
353
+ const { venvDir, pythonExecutable } = getPythonVirtualEnvPaths(functionsDir);
354
+ const hasUsableVirtualEnv = fs.existsSync(pythonExecutable) && await checkCommand(pythonExecutable, ['--version'], {
355
+ cwd: functionsDir,
356
+ env: uvEnv,
357
+ shell: false,
358
+ });
359
+ if (!hasUsableVirtualEnv) {
360
+ const venvArgs = (0, python_uv_1.buildUvVenvArgs)('.venv');
361
+ if (fs.existsSync(venvDir)) {
362
+ venvArgs.push('--clear');
363
+ }
364
+ console.log('📦 Creating Python virtual environment with uv...');
365
+ await runCommand(uvCommand, venvArgs, functionsDir, 'python virtual environment setup', uvEnv, false);
366
+ }
367
+ console.log('📦 Installing Python Azure Functions dependencies with uv...');
368
+ await runCommand(uvCommand, (0, python_uv_1.buildUvPipInstallArgs)(pythonExecutable, 'requirements.txt'), functionsDir, 'python dependency installation', uvEnv, false);
369
+ const pythonEnv = buildPythonFunctionsEnv(uvEnv, functionsDir);
370
+ return bridgePythonCoreToolsForWindowsArm64(functionsDir, pythonEnv);
371
+ }
61
372
  /**
62
373
  * Check if Cosmos DB Emulator is running by checking if port 8081 is open
63
374
  */
64
375
  async function checkCosmosDBEmulator() {
65
376
  return new Promise((resolve) => {
66
- const net = require('net');
67
377
  const socket = new net.Socket();
68
378
  const timeout = setTimeout(() => {
69
379
  socket.destroy();
@@ -81,112 +391,102 @@ async function checkCosmosDBEmulator() {
81
391
  socket.connect(8081, 'localhost');
82
392
  });
83
393
  }
84
- exports.devCommand = new commander_1.Command()
85
- .name('dev')
86
- .description('Start SwallowKit development server (Cosmos DB + Next.js + Azure Functions)')
87
- .option('-p, --port <port>', 'Next.js port', '3000')
88
- .option('-f, --functions-port <port>', 'Azure Functions port', '7071')
89
- .option('--host <host>', 'Host name', 'localhost')
90
- .option('--open', 'Open browser automatically', false)
91
- .option('--verbose', 'Show verbose logs', false)
92
- .option('--no-functions', 'Skip Azure Functions startup', false)
93
- .action(async (options) => {
94
- // SwallowKit プロジェクトディレクトリかどうかを検証
95
- (0, config_1.ensureSwallowKitProject)("dev");
96
- console.log('🚀 Starting SwallowKit development environment...');
97
- if (options.verbose) {
98
- console.log('⚙️ Options:', options);
99
- }
100
- await startDevEnvironment(options);
101
- });
394
+ function buildDevCommand(runDevEnvironment = startDevEnvironment, verifyProject = config_1.ensureSwallowKitProject) {
395
+ return new commander_1.Command()
396
+ .name('dev')
397
+ .description('Start SwallowKit development server (Cosmos DB + Next.js + Azure Functions)')
398
+ .option('-p, --port <port>', 'Next.js port', '3000')
399
+ .option('-f, --functions-port <port>', 'Azure Functions port', '7071')
400
+ .option('--host <host>', 'Host name', 'localhost')
401
+ .option('--open', 'Open browser automatically', false)
402
+ .option('--verbose', 'Show verbose logs', false)
403
+ .option('--no-functions', 'Skip Azure Functions startup')
404
+ .option('--seed-env <environment>', 'Replace Cosmos DB Emulator data from dev-seeds/<environment> before startup')
405
+ .option('--mock-connectors', 'Start mock server for connector models (serves Zod-generated data)', false)
406
+ .option('--swa-port <port>', 'SWA authentication emulator port', '4280')
407
+ .option('--no-swa', 'Skip the SWA authentication emulator')
408
+ .action(async (options) => {
409
+ const normalizedOptions = normalizeParsedDevOptions(options);
410
+ // SwallowKit プロジェクトディレクトリかどうかを検証
411
+ verifyProject("dev");
412
+ console.log('🚀 Starting SwallowKit development environment...');
413
+ if (normalizedOptions.verbose) {
414
+ console.log('⚙️ Options:', normalizedOptions);
415
+ }
416
+ await runDevEnvironment(normalizedOptions);
417
+ });
418
+ }
419
+ exports.devCommand = buildDevCommand();
102
420
  async function initializeCosmosDB(databaseName) {
103
421
  try {
104
- // Read local.settings.json from functions directory
105
- const functionsDir = path.join(process.cwd(), 'functions');
106
- const localSettingsPath = path.join(functionsDir, 'local.settings.json');
107
- if (!fs.existsSync(localSettingsPath)) {
108
- console.log('⚠️ local.settings.json not found. Skipping Cosmos DB initialization.');
109
- return;
110
- }
111
- const localSettings = JSON.parse(fs.readFileSync(localSettingsPath, 'utf-8'));
112
- const connectionString = localSettings.Values?.CosmosDBConnection;
113
- const dbName = localSettings.Values?.COSMOS_DB_DATABASE_NAME || databaseName;
114
- if (!connectionString) {
115
- console.log('⚠️ CosmosDBConnection not found in local.settings.json. Skipping Cosmos DB initialization.');
116
- return;
422
+ const connectionInfoResult = (0, dev_seeds_1.resolveLocalCosmosConnectionInfo)(databaseName);
423
+ if (!connectionInfoResult.ok) {
424
+ if (connectionInfoResult.reason === 'missing-local-settings') {
425
+ console.log('⚠️ local.settings.json not found. Skipping Cosmos DB initialization.');
426
+ }
427
+ else if (connectionInfoResult.reason === 'missing-connection-string') {
428
+ console.log('⚠️ CosmosDBConnection not found in local.settings.json. Skipping Cosmos DB initialization.');
429
+ }
430
+ else {
431
+ console.log('⚠️ Invalid CosmosDB connection string format.');
432
+ }
433
+ return null;
117
434
  }
118
435
  console.log('🗄️ Initializing Cosmos DB...');
119
- // Parse connection string
120
- const endpointMatch = connectionString.match(/AccountEndpoint=([^;]+)/);
121
- const keyMatch = connectionString.match(/AccountKey=([^;]+)/);
122
- if (!endpointMatch || !keyMatch) {
123
- console.log('⚠️ Invalid CosmosDB connection string format.');
124
- return;
125
- }
126
- const endpoint = endpointMatch[1];
436
+ const { endpoint, key, databaseName: dbName } = connectionInfoResult.value;
127
437
  const client = new cosmos_1.CosmosClient({
128
438
  endpoint: endpoint,
129
- key: keyMatch[1]
439
+ key,
130
440
  });
131
441
  // Create database if not exists
132
442
  const { database } = await client.databases.createIfNotExists({ id: dbName });
133
443
  console.log(`✅ Database "${dbName}" ready`);
134
- // Read lib/scaffold-config.ts to get list of models
135
- const scaffoldConfigPath = path.join(process.cwd(), 'lib', 'scaffold-config.ts');
136
- if (fs.existsSync(scaffoldConfigPath)) {
137
- const scaffoldConfigContent = fs.readFileSync(scaffoldConfigPath, 'utf-8');
138
- // Parse TypeScript file to extract models array
139
- const modelsMatch = scaffoldConfigContent.match(/models:\s*\[([\s\S]*?)\]\s*as\s*ScaffoldModel\[\]/);
140
- if (modelsMatch) {
141
- const modelsArrayContent = modelsMatch[1];
142
- // Extract model names from objects like { name: 'Task', path: '/task', label: 'Task' }
143
- const modelMatches = modelsArrayContent.matchAll(/\{\s*name:\s*['"](\w+)['"]/g);
144
- const models = Array.from(modelMatches, m => m[1]);
145
- for (const modelName of models) {
146
- const containerName = `${modelName}s`; // Pluralize model name
147
- // Try creating container with full partition key definition first
148
- let containerCreated = false;
149
- try {
150
- console.log(`🔧 Creating container "${containerName}" with partition key /id...`);
151
- const containerResponse = await database.containers.createIfNotExists({
152
- id: containerName,
153
- partitionKey: {
154
- paths: ['/id'],
155
- kind: cosmos_1.PartitionKeyKind.Hash,
156
- version: 2
157
- }
158
- });
159
- console.log(`✅ Container "${containerName}" ready (status: ${containerResponse.statusCode})`);
160
- containerCreated = true;
161
- }
162
- catch (error) {
163
- console.log(`⚠️ Failed with full partition key definition: ${error.message}`);
164
- console.log(`🔄 Retrying with simple partition key...`);
444
+ const models = await (0, dev_seeds_1.loadProjectModels)();
445
+ for (const model of models) {
446
+ const containerName = (0, dev_seeds_1.getContainerNameForModel)(model);
447
+ // Try creating container with full partition key definition first
448
+ let containerCreated = false;
449
+ try {
450
+ console.log(`🔧 Creating container "${containerName}" with partition key ${model.partitionKey}...`);
451
+ const containerResponse = await database.containers.createIfNotExists({
452
+ id: containerName,
453
+ partitionKey: {
454
+ paths: [model.partitionKey],
455
+ kind: cosmos_1.PartitionKeyKind.Hash,
456
+ version: 2
165
457
  }
166
- // If first attempt failed, try with simple partition key definition
167
- if (!containerCreated) {
168
- try {
169
- const containerResponse = await database.containers.createIfNotExists({
170
- id: containerName,
171
- partitionKey: {
172
- paths: ['/id']
173
- }
174
- });
175
- console.log(`✅ Container "${containerName}" ready (status: ${containerResponse.statusCode})`);
176
- }
177
- catch (containerError) {
178
- console.error(`❌ Failed to create container "${containerName}":`, containerError.message);
179
- console.error(`Error code: ${containerError.code}`);
180
- if (containerError.body) {
181
- console.error(`Response body:`, JSON.stringify(containerError.body, null, 2));
182
- }
183
- // Continue with other containers
458
+ });
459
+ console.log(`✅ Container "${containerName}" ready (status: ${containerResponse.statusCode})`);
460
+ containerCreated = true;
461
+ }
462
+ catch (error) {
463
+ const message = error instanceof Error ? error.message : String(error);
464
+ console.log(`⚠️ Failed with full partition key definition: ${message}`);
465
+ console.log(`🔄 Retrying with simple partition key...`);
466
+ }
467
+ // If first attempt failed, try with simple partition key definition
468
+ if (!containerCreated) {
469
+ try {
470
+ const containerResponse = await database.containers.createIfNotExists({
471
+ id: containerName,
472
+ partitionKey: {
473
+ paths: [model.partitionKey]
184
474
  }
475
+ });
476
+ console.log(`✅ Container "${containerName}" ready (status: ${containerResponse.statusCode})`);
477
+ }
478
+ catch (containerError) {
479
+ console.error(`❌ Failed to create container "${containerName}":`, containerError.message);
480
+ console.error(`Error code: ${containerError.code}`);
481
+ if (containerError.body) {
482
+ console.error(`Response body:`, JSON.stringify(containerError.body, null, 2));
185
483
  }
484
+ // Continue with other containers
186
485
  }
187
486
  }
188
487
  }
189
488
  console.log('✅ Cosmos DB initialization complete\n');
489
+ return { endpoint, key, databaseName: dbName, models };
190
490
  }
191
491
  catch (error) {
192
492
  console.error('⚠️ Cosmos DB initialization failed:', error.message);
@@ -194,19 +494,58 @@ async function initializeCosmosDB(databaseName) {
194
494
  console.error('Stack trace:', error.stack);
195
495
  }
196
496
  console.log('💡 Make sure Cosmos DB Emulator is running');
497
+ return null;
197
498
  }
198
499
  }
199
500
  async function startDevEnvironment(options) {
200
501
  const port = options.port || '3000';
201
502
  const functionsPort = options.functionsPort || '7071';
503
+ const swaPort = options.swaPort || '4280';
202
504
  // Detect package manager from project lockfile
203
505
  const pm = (0, package_manager_1.detectFromProject)();
204
506
  const pmCmd = (0, package_manager_1.getCommands)(pm);
507
+ const backendLanguage = (0, config_1.getBackendLanguage)();
508
+ const authConfig = (0, config_1.getAuthConfig)();
509
+ const normalizedAuth = (0, config_1.normalizeAuthConfig)(authConfig);
510
+ const useSwaEmulator = Boolean(normalizedAuth && Object.values(normalizedAuth.schemes).some(scheme => scheme.provider === 'swa')) && !options.noSwa;
511
+ const swaCommand = useSwaEmulator ? await resolveSwaCliCommand(process.cwd()) : null;
512
+ if (useSwaEmulator && !swaCommand) {
513
+ const installCommand = getSwaCliInstallCommand(pm);
514
+ console.error('❌ SWA authentication requires Azure Static Web Apps CLI for local development.');
515
+ console.error(' Install it in this project and retry:');
516
+ console.error(`\n ${installCommand}\n`);
517
+ process.exitCode = 1;
518
+ return;
519
+ }
205
520
  // プロセスを管理する配列
206
521
  const processes = [];
522
+ let functionsEnv = process.env;
523
+ let mockServer = null;
524
+ let envLocalPath = '';
525
+ let envLocalDefaultUrl = ''; // default Functions URL to restore on shutdown
526
+ const functionsBaseUrl = buildFunctionsBaseUrl(options.host, functionsPort);
527
+ let functionsReadinessPromise = null;
528
+ let functionsReady = !!options.noFunctions;
207
529
  // Cleanup processes on Ctrl+C
208
- process.on('SIGINT', () => {
530
+ process.on('SIGINT', async () => {
209
531
  console.log('\n🛑 Stopping development servers...');
532
+ // Restore .env.local to default Functions port on shutdown
533
+ if (envLocalPath && envLocalDefaultUrl) {
534
+ try {
535
+ if (fs.existsSync(envLocalPath)) {
536
+ const content = fs.readFileSync(envLocalPath, 'utf-8');
537
+ if (content.includes('BACKEND_FUNCTIONS_BASE_URL=') &&
538
+ !content.includes(`BACKEND_FUNCTIONS_BASE_URL=${envLocalDefaultUrl}`)) {
539
+ const restored = content.replace(/^BACKEND_FUNCTIONS_BASE_URL=.*/m, `BACKEND_FUNCTIONS_BASE_URL=${envLocalDefaultUrl}`);
540
+ fs.writeFileSync(envLocalPath, restored, 'utf-8');
541
+ }
542
+ }
543
+ }
544
+ catch { /* ignore */ }
545
+ }
546
+ if (mockServer) {
547
+ await mockServer.stop();
548
+ }
210
549
  processes.forEach((proc) => {
211
550
  if (proc && !proc.killed) {
212
551
  proc.kill();
@@ -230,12 +569,13 @@ async function startDevEnvironment(options) {
230
569
  }
231
570
  // 2. Check if Azure Functions exists
232
571
  const functionsDir = path.join(process.cwd(), 'functions');
233
- const hasFunctions = fs.existsSync(functionsDir) &&
234
- fs.existsSync(path.join(functionsDir, 'package.json'));
572
+ const hasFunctions = fs.existsSync(functionsDir) && hasFunctionsProject(functionsDir, backendLanguage);
573
+ let functionsCoreToolsCommand = null;
574
+ let installedCoreToolsVersion = null;
235
575
  if (hasFunctions && !options.noFunctions) {
236
- // Check if Azure Functions Core Tools is installed
237
- const coreToolsInstalled = await checkCoreTools();
238
- if (!coreToolsInstalled) {
576
+ installedCoreToolsVersion = await resolveInstalledCoreToolsVersion();
577
+ functionsCoreToolsCommand = buildFunctionsCoreToolsCommand(backendLanguage, installedCoreToolsVersion);
578
+ if (functionsCoreToolsCommand.command === 'func' && !installedCoreToolsVersion) {
239
579
  console.log('');
240
580
  console.log('⚠️ Azure Functions Core Tools not found.');
241
581
  console.log('');
@@ -282,6 +622,9 @@ async function startDevEnvironment(options) {
282
622
  options.noFunctions = true;
283
623
  }
284
624
  }
625
+ else if (functionsCoreToolsCommand.command !== 'func') {
626
+ console.log(`ℹ️ Using ${functionsCoreToolsCommand.label}.`);
627
+ }
285
628
  if (!options.noFunctions) {
286
629
  // Check if Cosmos DB Emulator is running
287
630
  const cosmosRunning = await checkCosmosDBEmulator();
@@ -297,69 +640,89 @@ async function startDevEnvironment(options) {
297
640
  process.exit(1);
298
641
  }
299
642
  // Initialize Cosmos DB before starting Functions
300
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
301
- const appName = packageJson.name || 'App';
302
- const databaseName = `${appName.charAt(0).toUpperCase() + appName.slice(1)}Database`;
303
- await initializeCosmosDB(databaseName);
643
+ const databaseName = (0, dev_seeds_1.getDefaultCosmosDatabaseName)();
644
+ const cosmosInitialization = await initializeCosmosDB(databaseName);
645
+ if (options.seedEnv && cosmosInitialization) {
646
+ await (0, dev_seeds_1.applyDevSeedEnvironment)({
647
+ client: new cosmos_1.CosmosClient({
648
+ endpoint: cosmosInitialization.endpoint,
649
+ key: cosmosInitialization.key,
650
+ }),
651
+ databaseName: cosmosInitialization.databaseName,
652
+ environment: options.seedEnv,
653
+ models: cosmosInitialization.models,
654
+ });
655
+ }
304
656
  console.log('');
305
657
  console.log('🚀 Starting Azure Functions...');
306
- // Check if pnpm install has been run in functions directory
307
- const functionsNodeModules = path.join(functionsDir, 'node_modules');
308
- if (!fs.existsSync(functionsNodeModules)) {
309
- console.log('📦 Installing Azure Functions dependencies...');
310
- const depInstall = (0, child_process_1.spawn)(pm, ['install'], {
311
- cwd: functionsDir,
658
+ if (backendLanguage === 'typescript') {
659
+ const functionsNodeModules = path.join(functionsDir, 'node_modules');
660
+ if (!fs.existsSync(functionsNodeModules)) {
661
+ console.log('📦 Installing Azure Functions dependencies...');
662
+ await runCommand(pm, ['install'], functionsDir, `${pm} install`);
663
+ }
664
+ }
665
+ else if (backendLanguage === 'csharp') {
666
+ console.log('ℹ️ C# Azure Functions can take longer on cold start while the worker builds.');
667
+ }
668
+ else {
669
+ functionsEnv = await preparePythonFunctionsEnvironment(functionsDir);
670
+ }
671
+ }
672
+ }
673
+ if (hasFunctions && !options.noFunctions) {
674
+ // Build shared package before starting Functions
675
+ const sharedDir = path.join(process.cwd(), 'shared');
676
+ const sharedPkgPath = path.join(sharedDir, 'package.json');
677
+ if (fs.existsSync(sharedDir) && fs.existsSync(sharedPkgPath)) {
678
+ const sharedPkg = JSON.parse(fs.readFileSync(sharedPkgPath, 'utf-8'));
679
+ if (sharedPkg.scripts?.build) {
680
+ console.log('📦 Building shared package...');
681
+ const filterArgs = pm === 'pnpm'
682
+ ? ['run', '--filter', 'shared', 'build']
683
+ : ['run', '--workspace=shared', 'build'];
684
+ const sharedBuild = (0, child_process_1.spawn)(pm, filterArgs, {
685
+ cwd: process.cwd(),
312
686
  shell: true,
313
687
  stdio: 'inherit',
314
688
  });
315
689
  await new Promise((resolve, reject) => {
316
- depInstall.on('close', (code) => {
690
+ sharedBuild.on('close', (code) => {
317
691
  if (code === 0) {
692
+ console.log('✅ Shared package built successfully');
318
693
  resolve();
319
694
  }
320
695
  else {
321
- reject(new Error(`${pm} install failed with code ${code}`));
696
+ reject(new Error(`Shared package build failed with code ${code}`));
322
697
  }
323
698
  });
324
- depInstall.on('error', reject);
699
+ sharedBuild.on('error', reject);
325
700
  });
326
701
  }
702
+ else {
703
+ console.log('⚠️ Shared package has no build script — skipping build. Run "swallowkit add-auth" to fix.');
704
+ }
327
705
  }
328
- }
329
- if (hasFunctions && !options.noFunctions) {
330
- // Build shared package before starting Functions
331
- const sharedDir = path.join(process.cwd(), 'shared');
332
- if (fs.existsSync(sharedDir) && fs.existsSync(path.join(sharedDir, 'package.json'))) {
333
- console.log('📦 Building shared package...');
334
- const filterArgs = pm === 'pnpm'
335
- ? ['run', '--filter', 'shared', 'build']
336
- : ['run', '--workspace=shared', 'build'];
337
- const sharedBuild = (0, child_process_1.spawn)(pm, filterArgs, {
338
- cwd: process.cwd(),
339
- shell: true,
340
- stdio: 'inherit',
341
- });
342
- await new Promise((resolve, reject) => {
343
- sharedBuild.on('close', (code) => {
344
- if (code === 0) {
345
- console.log('✅ Shared package built successfully');
346
- resolve();
347
- }
348
- else {
349
- reject(new Error(`Shared package build failed with code ${code}`));
350
- }
351
- });
352
- sharedBuild.on('error', reject);
353
- });
706
+ // Build TypeScript functions after shared package (functions import from shared)
707
+ if (backendLanguage === 'typescript') {
708
+ const functionsPkgPath = path.join(functionsDir, 'package.json');
709
+ if (fs.existsSync(functionsPkgPath)) {
710
+ const functionsPkg = JSON.parse(fs.readFileSync(functionsPkgPath, 'utf-8'));
711
+ if (functionsPkg.scripts?.build) {
712
+ console.log('📦 Building TypeScript Azure Functions...');
713
+ await runCommand(pm, ['run', 'build'], functionsDir, `${pm} run build`);
714
+ }
715
+ }
354
716
  }
355
717
  // Azure Functions を起動
356
- const funcEnv = { ...process.env, FUNCTIONS_PORT: functionsPort };
357
- const funcProcess = (0, child_process_1.spawn)(pm, ['start'], {
718
+ const functionsCommand = functionsCoreToolsCommand ?? buildFunctionsCoreToolsCommand(backendLanguage, installedCoreToolsVersion);
719
+ const funcProcess = (0, child_process_1.spawn)(functionsCommand.command, [...functionsCommand.argsPrefix, ...buildFunctionsStartArgs(functionsPort)], {
358
720
  cwd: functionsDir,
359
721
  shell: true,
360
722
  stdio: 'pipe', // Always pipe to capture output
361
- env: funcEnv
723
+ env: functionsEnv
362
724
  });
725
+ let pythonWorkerMissing = false;
363
726
  // Functions の出力をそのまま表示(プレフィックス付き)
364
727
  if (funcProcess.stdout) {
365
728
  funcProcess.stdout.on('data', (data) => {
@@ -367,6 +730,10 @@ async function startDevEnvironment(options) {
367
730
  // 各行にプレフィックスを付けて出力
368
731
  const lines = output.split('\n').filter((line) => line.trim());
369
732
  lines.forEach((line) => {
733
+ if (backendLanguage === 'python' &&
734
+ (line.includes('WorkerConfig for runtime: python not found') || line.includes('DefaultWorkerPath:'))) {
735
+ pythonWorkerMissing = true;
736
+ }
370
737
  console.log(`[Functions] ${line}`);
371
738
  });
372
739
  });
@@ -376,6 +743,10 @@ async function startDevEnvironment(options) {
376
743
  const output = data.toString();
377
744
  const lines = output.split('\n').filter((line) => line.trim());
378
745
  lines.forEach((line) => {
746
+ if (backendLanguage === 'python' &&
747
+ (line.includes('WorkerConfig for runtime: python not found') || line.includes('DefaultWorkerPath:'))) {
748
+ pythonWorkerMissing = true;
749
+ }
379
750
  console.error(`[Functions Error] ${line}`);
380
751
  });
381
752
  });
@@ -389,9 +760,15 @@ async function startDevEnvironment(options) {
389
760
  funcProcess.on('close', (code) => {
390
761
  if (code !== 0) {
391
762
  console.log(`\n⏹️ Azure Functions exited (exit code: ${code})`);
763
+ if (backendLanguage === 'python' && pythonWorkerMissing) {
764
+ console.log('💡 Your Azure Functions Core Tools installation is missing the Python worker for this OS/architecture.');
765
+ console.log(' Reinstall a matching Core Tools v4 package (Windows users should prefer the official x64/x86 MSI for their machine).');
766
+ console.log(' SwallowKit local Python dev uses functions/.venv and requirements.txt, but Core Tools still needs its own bundled Python worker.');
767
+ }
392
768
  }
393
769
  });
394
- console.log(`✅ Azure Functions started (port: ${functionsPort})`);
770
+ console.log(`⏳ Waiting for Azure Functions to accept requests at ${functionsBaseUrl}...`);
771
+ functionsReadinessPromise = waitForHttpServerReady(functionsBaseUrl, getFunctionsReadinessTimeoutMs(backendLanguage));
395
772
  }
396
773
  else if (!hasFunctions) {
397
774
  console.log('');
@@ -403,24 +780,97 @@ async function startDevEnvironment(options) {
403
780
  }
404
781
  console.log('');
405
782
  console.log('🚀 Starting Next.js development server...');
783
+ // Mock connector server — start proxy if --mock-connectors is enabled
784
+ let bffTargetPort = functionsPort;
785
+ if (options.mockConnectors) {
786
+ const allModels = await (0, dev_seeds_1.loadProjectModels)();
787
+ const connectorModels = allModels.filter((m) => m.connectorConfig);
788
+ // Resolve auth config — auth functions use RDB connectors, mocked alongside other models
789
+ let mockAuthConfig;
790
+ if (authConfig?.provider === 'custom-jwt' && authConfig.customJwt) {
791
+ // Read JWT_SECRET from functions/local.settings.json if available
792
+ let jwtSecret = 'dev-jwt-secret-change-in-production-min-32-chars!!';
793
+ try {
794
+ const localSettingsPath = path.join(process.cwd(), 'functions', 'local.settings.json');
795
+ if (fs.existsSync(localSettingsPath)) {
796
+ const localSettings = JSON.parse(fs.readFileSync(localSettingsPath, 'utf-8'));
797
+ jwtSecret = localSettings.Values?.[authConfig.customJwt.jwtSecretEnv || 'JWT_SECRET'] || jwtSecret;
798
+ }
799
+ }
800
+ catch { /* ignore */ }
801
+ mockAuthConfig = {
802
+ jwtSecret,
803
+ tokenExpiry: authConfig.customJwt.tokenExpiry,
804
+ customJwt: {
805
+ userTable: authConfig.customJwt.userTable,
806
+ loginIdColumn: authConfig.customJwt.loginIdColumn,
807
+ passwordHashColumn: authConfig.customJwt.passwordHashColumn,
808
+ rolesColumn: authConfig.customJwt.rolesColumn,
809
+ },
810
+ defaultPolicy: authConfig.authorization?.defaultPolicy === "public" ? "anonymous" :
811
+ authConfig.authorization?.defaultPolicy === "authenticated" || authConfig.authorization?.defaultPolicy === "anonymous"
812
+ ? authConfig.authorization.defaultPolicy : undefined,
813
+ };
814
+ }
815
+ if (connectorModels.length > 0 || mockAuthConfig) {
816
+ const mockPort = parseInt(functionsPort, 10) + 1;
817
+ mockServer = new connector_mock_server_1.ConnectorMockServer({
818
+ port: mockPort,
819
+ functionsTarget: `${options.host || 'localhost'}:${functionsPort}`,
820
+ connectorModels,
821
+ allModels,
822
+ seedEnv: options.seedEnv,
823
+ host: options.host || 'localhost',
824
+ authConfig: mockAuthConfig,
825
+ });
826
+ await mockServer.start();
827
+ bffTargetPort = String(mockPort);
828
+ const modelCount = connectorModels.length + (mockAuthConfig ? 1 : 0);
829
+ console.log('');
830
+ console.log(`🔌 Mock server started (port: ${mockPort}) — ${modelCount} model(s) mocked via Zod/seed data`);
831
+ for (const m of connectorModels) {
832
+ const ops = m.connectorConfig.operations.join(', ');
833
+ console.log(` - ${m.name} [${ops}]`);
834
+ }
835
+ if (mockAuthConfig) {
836
+ console.log(` - auth [login, me, logout]`);
837
+ }
838
+ console.log(` Other routes → proxied to Azure Functions (port: ${functionsPort})`);
839
+ }
840
+ else {
841
+ console.log('');
842
+ console.log('ℹ️ --mock-connectors specified but no connector models found. Skipping mock server.');
843
+ }
844
+ }
406
845
  // 5. Start Next.js development server
407
- const nextArgs = pm === 'pnpm'
408
- ? ['exec', 'next', 'dev', '--port', port]
409
- : ['next', 'dev', '--port', port];
410
- if (options.open) {
411
- // Next.js 14+ deprecated --open option, so we open browser manually
412
- setTimeout(() => {
413
- const url = `http://${options.host || 'localhost'}:${port}`;
414
- console.log(`\n🌐 Opening browser: ${url}`);
415
- const start = process.platform === 'darwin' ? 'open' :
416
- process.platform === 'win32' ? 'start' : 'xdg-open';
417
- (0, child_process_1.spawn)(start, [url], { shell: true });
418
- }, 3000);
846
+ const nextArgs = buildNextDevArgs(pm, port);
847
+ // Ensure .env.local points to bffTargetPort so Next.js reads the correct backend URL.
848
+ // When --mock-connectors is active, bffTargetPort = mock port (7072); otherwise = Functions port (7071).
849
+ // Next.js may load .env.local values that override spawn env vars, so we must keep them in sync.
850
+ envLocalPath = path.join(process.cwd(), '.env.local');
851
+ envLocalDefaultUrl = functionsBaseUrl;
852
+ const bffTargetUrl = buildFunctionsBaseUrl(options.host, bffTargetPort);
853
+ try {
854
+ if (fs.existsSync(envLocalPath)) {
855
+ const envContent = fs.readFileSync(envLocalPath, 'utf-8');
856
+ if (envContent.includes('BACKEND_FUNCTIONS_BASE_URL=') &&
857
+ !envContent.includes(`BACKEND_FUNCTIONS_BASE_URL=${bffTargetUrl}`)) {
858
+ const updated = envContent.replace(/^BACKEND_FUNCTIONS_BASE_URL=.*/m, `BACKEND_FUNCTIONS_BASE_URL=${bffTargetUrl}`);
859
+ fs.writeFileSync(envLocalPath, updated, 'utf-8');
860
+ }
861
+ }
419
862
  }
863
+ catch { /* ignore */ }
864
+ const nextEnv = {
865
+ ...process.env,
866
+ BACKEND_FUNCTIONS_BASE_URL: bffTargetUrl,
867
+ FUNCTIONS_BASE_URL: bffTargetUrl,
868
+ };
420
869
  const nextProcess = (0, child_process_1.spawn)(pm === 'pnpm' ? 'pnpm' : 'npx', nextArgs, {
421
870
  cwd: process.cwd(),
422
871
  shell: true,
423
872
  stdio: options.verbose ? 'inherit' : 'inherit',
873
+ env: nextEnv,
424
874
  });
425
875
  processes.push(nextProcess);
426
876
  nextProcess.on('error', (error) => {
@@ -432,6 +882,9 @@ async function startDevEnvironment(options) {
432
882
  console.log(`\n⏹️ Next.js exited (exit code: ${code})`);
433
883
  }
434
884
  // Exit all processes when Next.js exits
885
+ if (mockServer) {
886
+ mockServer.stop().catch(() => { });
887
+ }
435
888
  processes.forEach((proc) => {
436
889
  if (proc && !proc.killed) {
437
890
  proc.kill();
@@ -439,18 +892,90 @@ async function startDevEnvironment(options) {
439
892
  });
440
893
  process.exit(code || 0);
441
894
  });
895
+ if (functionsReadinessPromise) {
896
+ functionsReady = await functionsReadinessPromise;
897
+ console.log('');
898
+ if (functionsReady) {
899
+ console.log(`✅ Azure Functions ready (port: ${functionsPort})`);
900
+ }
901
+ else {
902
+ console.log(`⚠️ Azure Functions is still starting: ${functionsBaseUrl}`);
903
+ }
904
+ }
905
+ let swaReady = false;
906
+ if (useSwaEmulator && swaCommand) {
907
+ const nextUrl = `http://${options.host || 'localhost'}:${port}`;
908
+ console.log(`⏳ Waiting for Next.js before starting the SWA emulator at ${nextUrl}...`);
909
+ const nextReady = await waitForHttpServerReady(nextUrl);
910
+ if (!nextReady) {
911
+ throw new Error(`Next.js did not become ready at ${nextUrl}`);
912
+ }
913
+ console.log(`🔐 Starting SWA authentication emulator (port: ${swaPort})...`);
914
+ const swaProcess = (0, child_process_1.spawn)(swaCommand, buildSwaStartArgs(options.host, port, swaPort), {
915
+ cwd: process.cwd(),
916
+ shell: true,
917
+ stdio: 'inherit',
918
+ env: process.env,
919
+ });
920
+ processes.push(swaProcess);
921
+ swaProcess.on('error', (error) => {
922
+ console.error('❌ SWA CLI startup error:', error.message);
923
+ });
924
+ const swaUrl = `http://${options.host || 'localhost'}:${swaPort}`;
925
+ swaReady = await waitForHttpServerReady(swaUrl);
926
+ if (!swaReady) {
927
+ throw new Error(`SWA authentication emulator did not become ready at ${swaUrl}`);
928
+ }
929
+ }
442
930
  console.log('');
443
931
  console.log('✅ SwallowKit development environment is running!');
444
932
  console.log('');
445
- console.log(`📱 Next.js: http://${options.host || 'localhost'}:${port}`);
933
+ if (useSwaEmulator) {
934
+ const swaUrl = `http://${options.host || 'localhost'}:${swaPort}`;
935
+ console.log('🔐 SWA authenticated app:');
936
+ console.log(` ${swaUrl}`);
937
+ console.log('');
938
+ console.log('🔑 Local sign-in:');
939
+ console.log(` ${swaUrl}/.auth/login/aad`);
940
+ console.log(' Enter any username and select Login.');
941
+ console.log(' The "authenticated" role is added automatically.');
942
+ console.log('');
943
+ console.log('📱 Next.js direct access (authentication is not emulated):');
944
+ console.log(` http://${options.host || 'localhost'}:${port}`);
945
+ }
946
+ else {
947
+ console.log(`📱 Next.js: http://${options.host || 'localhost'}:${port}`);
948
+ }
446
949
  if (hasFunctions && !options.noFunctions) {
447
- console.log(`⚡ Azure Functions: http://${options.host || 'localhost'}:${functionsPort}`);
950
+ console.log(`${functionsReady ? '⚡ Azure Functions' : '⏳ Azure Functions (starting)'}: ${functionsBaseUrl}`);
951
+ }
952
+ if (mockServer) {
953
+ console.log(`🔌 Mock Proxy: ${bffTargetUrl} (BFF → here)`);
448
954
  }
449
955
  console.log('');
450
- if (hasFunctions && !options.noFunctions) {
956
+ if (hasFunctions && !options.noFunctions && functionsReady) {
451
957
  console.log('💡 Azure Functions and Next.js BFF are connected');
452
958
  }
959
+ else if (hasFunctions && !options.noFunctions) {
960
+ console.log('💡 Azure Functions is still warming up; BFF routes can fail until the backend responds.');
961
+ }
962
+ if (mockServer) {
963
+ console.log('💡 Connector models served from mock server (Zod-generated data)');
964
+ }
965
+ if (swaReady) {
966
+ console.log('💡 Open the app through the SWA port when testing authenticated pages and CRUD APIs.');
967
+ }
453
968
  console.log('');
969
+ if (options.open) {
970
+ const url = useSwaEmulator
971
+ ? `http://${options.host || 'localhost'}:${swaPort}`
972
+ : `http://${options.host || 'localhost'}:${port}`;
973
+ console.log(`🌐 Opening browser: ${url}`);
974
+ const start = process.platform === 'darwin' ? 'open' :
975
+ process.platform === 'win32' ? 'start' : 'xdg-open';
976
+ (0, child_process_1.spawn)(start, [url], { shell: true });
977
+ console.log('');
978
+ }
454
979
  console.log('🛑 Press Ctrl+C to stop');
455
980
  console.log('');
456
981
  }
@@ -464,4 +989,43 @@ async function startDevEnvironment(options) {
464
989
  process.exit(1);
465
990
  }
466
991
  }
992
+ async function resolveSwaCliCommand(projectRoot) {
993
+ const binaryName = process.platform === 'win32' ? 'swa.cmd' : 'swa';
994
+ const localCommand = path.join(projectRoot, 'node_modules', '.bin', binaryName);
995
+ if (fs.existsSync(localCommand) && await checkCommand(localCommand, ['--version'], {
996
+ cwd: projectRoot,
997
+ shell: process.platform === 'win32',
998
+ })) {
999
+ return localCommand;
1000
+ }
1001
+ if (await checkCommand('swa', ['--version'], { cwd: projectRoot })) {
1002
+ return 'swa';
1003
+ }
1004
+ return null;
1005
+ }
1006
+ async function runCommand(command, args, cwd, label, env, shell = true) {
1007
+ await new Promise((resolve, reject) => {
1008
+ const child = (0, child_process_1.spawn)(command, args, {
1009
+ cwd,
1010
+ shell,
1011
+ stdio: 'inherit',
1012
+ env,
1013
+ });
1014
+ child.on('close', (code) => {
1015
+ if (code === 0) {
1016
+ resolve();
1017
+ }
1018
+ else {
1019
+ reject(new Error(`${label} failed with code ${code}`));
1020
+ }
1021
+ });
1022
+ child.on('error', reject);
1023
+ });
1024
+ }
1025
+ function hasFunctionsProject(functionsDir, backendLanguage) {
1026
+ if (backendLanguage === 'typescript') {
1027
+ return fs.existsSync(path.join(functionsDir, 'package.json'));
1028
+ }
1029
+ return fs.existsSync(path.join(functionsDir, 'host.json'));
1030
+ }
467
1031
  //# sourceMappingURL=dev.js.map