swallowkit 1.0.0-beta.3 → 1.0.0-beta.31

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 (201) hide show
  1. package/LICENSE +21 -21
  2. package/README.ja.md +353 -215
  3. package/README.md +406 -216
  4. package/dist/__tests__/fixtures.d.ts +22 -0
  5. package/dist/__tests__/fixtures.d.ts.map +1 -0
  6. package/dist/__tests__/fixtures.js +146 -0
  7. package/dist/__tests__/fixtures.js.map +1 -0
  8. package/dist/cli/commands/add-auth.d.ts +10 -0
  9. package/dist/cli/commands/add-auth.d.ts.map +1 -0
  10. package/dist/cli/commands/add-auth.js +444 -0
  11. package/dist/cli/commands/add-auth.js.map +1 -0
  12. package/dist/cli/commands/add-connector.d.ts +20 -0
  13. package/dist/cli/commands/add-connector.d.ts.map +1 -0
  14. package/dist/cli/commands/add-connector.js +163 -0
  15. package/dist/cli/commands/add-connector.js.map +1 -0
  16. package/dist/cli/commands/create-model.d.ts +1 -4
  17. package/dist/cli/commands/create-model.d.ts.map +1 -1
  18. package/dist/cli/commands/create-model.js +21 -82
  19. package/dist/cli/commands/create-model.js.map +1 -1
  20. package/dist/cli/commands/dev-seeds.d.ts +57 -0
  21. package/dist/cli/commands/dev-seeds.d.ts.map +1 -0
  22. package/dist/cli/commands/dev-seeds.js +470 -0
  23. package/dist/cli/commands/dev-seeds.js.map +1 -0
  24. package/dist/cli/commands/dev.d.ts +33 -0
  25. package/dist/cli/commands/dev.d.ts.map +1 -1
  26. package/dist/cli/commands/dev.js +628 -146
  27. package/dist/cli/commands/dev.js.map +1 -1
  28. package/dist/cli/commands/index.d.ts +1 -0
  29. package/dist/cli/commands/index.d.ts.map +1 -1
  30. package/dist/cli/commands/index.js +3 -1
  31. package/dist/cli/commands/index.js.map +1 -1
  32. package/dist/cli/commands/init.d.ts +15 -0
  33. package/dist/cli/commands/init.d.ts.map +1 -1
  34. package/dist/cli/commands/init.js +2696 -1706
  35. package/dist/cli/commands/init.js.map +1 -1
  36. package/dist/cli/commands/scaffold.d.ts.map +1 -1
  37. package/dist/cli/commands/scaffold.js +448 -129
  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 +200 -42
  42. package/dist/cli/index.js.map +1 -1
  43. package/dist/core/config.d.ts +8 -2
  44. package/dist/core/config.d.ts.map +1 -1
  45. package/dist/core/config.js +94 -5
  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/create-model.d.ts +15 -0
  56. package/dist/core/operations/create-model.d.ts.map +1 -0
  57. package/dist/core/operations/create-model.js +171 -0
  58. package/dist/core/operations/create-model.js.map +1 -0
  59. package/dist/core/operations/runtime.d.ts +32 -0
  60. package/dist/core/operations/runtime.d.ts.map +1 -0
  61. package/dist/core/operations/runtime.js +225 -0
  62. package/dist/core/operations/runtime.js.map +1 -0
  63. package/dist/core/operations/scaffold-machine.d.ts +16 -0
  64. package/dist/core/operations/scaffold-machine.d.ts.map +1 -0
  65. package/dist/core/operations/scaffold-machine.js +63 -0
  66. package/dist/core/operations/scaffold-machine.js.map +1 -0
  67. package/dist/core/project/manifest.d.ts +92 -0
  68. package/dist/core/project/manifest.d.ts.map +1 -0
  69. package/dist/core/project/manifest.js +321 -0
  70. package/dist/core/project/manifest.js.map +1 -0
  71. package/dist/core/project/validation.d.ts +20 -0
  72. package/dist/core/project/validation.d.ts.map +1 -0
  73. package/dist/core/project/validation.js +209 -0
  74. package/dist/core/project/validation.js.map +1 -0
  75. package/dist/core/scaffold/auth-generator.d.ts +38 -0
  76. package/dist/core/scaffold/auth-generator.d.ts.map +1 -0
  77. package/dist/core/scaffold/auth-generator.js +1244 -0
  78. package/dist/core/scaffold/auth-generator.js.map +1 -0
  79. package/dist/core/scaffold/connector-functions-generator.d.ts +41 -0
  80. package/dist/core/scaffold/connector-functions-generator.d.ts.map +1 -0
  81. package/dist/core/scaffold/connector-functions-generator.js +1027 -0
  82. package/dist/core/scaffold/connector-functions-generator.js.map +1 -0
  83. package/dist/core/scaffold/functions-generator.d.ts +7 -1
  84. package/dist/core/scaffold/functions-generator.d.ts.map +1 -1
  85. package/dist/core/scaffold/functions-generator.js +920 -213
  86. package/dist/core/scaffold/functions-generator.js.map +1 -1
  87. package/dist/core/scaffold/model-parser.d.ts +20 -1
  88. package/dist/core/scaffold/model-parser.d.ts.map +1 -1
  89. package/dist/core/scaffold/model-parser.js +328 -135
  90. package/dist/core/scaffold/model-parser.js.map +1 -1
  91. package/dist/core/scaffold/native-schema-generator.d.ts +13 -0
  92. package/dist/core/scaffold/native-schema-generator.d.ts.map +1 -0
  93. package/dist/core/scaffold/native-schema-generator.js +677 -0
  94. package/dist/core/scaffold/native-schema-generator.js.map +1 -0
  95. package/dist/core/scaffold/nextjs-generator.d.ts +8 -0
  96. package/dist/core/scaffold/nextjs-generator.d.ts.map +1 -1
  97. package/dist/core/scaffold/nextjs-generator.js +314 -182
  98. package/dist/core/scaffold/nextjs-generator.js.map +1 -1
  99. package/dist/core/scaffold/openapi-generator.d.ts +3 -0
  100. package/dist/core/scaffold/openapi-generator.d.ts.map +1 -0
  101. package/dist/core/scaffold/openapi-generator.js +190 -0
  102. package/dist/core/scaffold/openapi-generator.js.map +1 -0
  103. package/dist/core/scaffold/ui-generator.d.ts +10 -4
  104. package/dist/core/scaffold/ui-generator.d.ts.map +1 -1
  105. package/dist/core/scaffold/ui-generator.js +768 -663
  106. package/dist/core/scaffold/ui-generator.js.map +1 -1
  107. package/dist/database/base-model.d.ts +3 -3
  108. package/dist/database/base-model.js +3 -3
  109. package/dist/index.d.ts +2 -2
  110. package/dist/index.d.ts.map +1 -1
  111. package/dist/index.js +2 -1
  112. package/dist/index.js.map +1 -1
  113. package/dist/machine/contracts.d.ts +16 -0
  114. package/dist/machine/contracts.d.ts.map +1 -0
  115. package/dist/machine/contracts.js +3 -0
  116. package/dist/machine/contracts.js.map +1 -0
  117. package/dist/machine/errors.d.ts +11 -0
  118. package/dist/machine/errors.d.ts.map +1 -0
  119. package/dist/machine/errors.js +34 -0
  120. package/dist/machine/errors.js.map +1 -0
  121. package/dist/machine/index.d.ts +3 -0
  122. package/dist/machine/index.d.ts.map +1 -0
  123. package/dist/machine/index.js +156 -0
  124. package/dist/machine/index.js.map +1 -0
  125. package/dist/mcp/index.d.ts +25 -0
  126. package/dist/mcp/index.d.ts.map +1 -0
  127. package/dist/mcp/index.js +184 -0
  128. package/dist/mcp/index.js.map +1 -0
  129. package/dist/types/index.d.ts +65 -0
  130. package/dist/types/index.d.ts.map +1 -1
  131. package/dist/utils/package-manager.d.ts +109 -0
  132. package/dist/utils/package-manager.d.ts.map +1 -0
  133. package/dist/utils/package-manager.js +215 -0
  134. package/dist/utils/package-manager.js.map +1 -0
  135. package/dist/utils/python-uv.d.ts +21 -0
  136. package/dist/utils/python-uv.d.ts.map +1 -0
  137. package/dist/utils/python-uv.js +111 -0
  138. package/dist/utils/python-uv.js.map +1 -0
  139. package/package.json +85 -73
  140. package/src/__tests__/__snapshots__/functions-generator.test.ts.snap +1139 -0
  141. package/src/__tests__/__snapshots__/nextjs-generator.test.ts.snap +194 -0
  142. package/src/__tests__/__snapshots__/ui-generator.test.ts.snap +532 -0
  143. package/src/__tests__/auth.test.ts +654 -0
  144. package/src/__tests__/config.test.ts +274 -0
  145. package/src/__tests__/connector-functions-generator.test.ts +288 -0
  146. package/src/__tests__/connector-mock-server.test.ts +439 -0
  147. package/src/__tests__/connector-model-bff.test.ts +162 -0
  148. package/src/__tests__/dev-seeds.test.ts +173 -0
  149. package/src/__tests__/dev.test.ts +252 -0
  150. package/src/__tests__/fixtures.ts +144 -0
  151. package/src/__tests__/functions-generator.test.ts +237 -0
  152. package/src/__tests__/init.test.ts +115 -0
  153. package/src/__tests__/machine.test.ts +251 -0
  154. package/src/__tests__/mcp.test.ts +117 -0
  155. package/src/__tests__/model-parser.test.ts +52 -0
  156. package/src/__tests__/nextjs-generator.test.ts +97 -0
  157. package/src/__tests__/openapi-generator.test.ts +43 -0
  158. package/src/__tests__/package-manager.test.ts +189 -0
  159. package/src/__tests__/python-uv.test.ts +48 -0
  160. package/src/__tests__/scaffold.test.ts +67 -0
  161. package/src/__tests__/string-utils.test.ts +75 -0
  162. package/src/__tests__/ui-generator.test.ts +144 -0
  163. package/src/__tests__/zod-mock-generator.test.ts +132 -0
  164. package/src/cli/commands/add-auth.ts +500 -0
  165. package/src/cli/commands/add-connector.ts +158 -0
  166. package/src/cli/commands/create-model.ts +62 -0
  167. package/src/cli/commands/dev-seeds.ts +614 -0
  168. package/src/cli/commands/dev.ts +1134 -0
  169. package/src/cli/commands/index.ts +9 -0
  170. package/src/cli/commands/init.ts +3480 -0
  171. package/src/cli/commands/provision.ts +193 -0
  172. package/src/cli/commands/scaffold.ts +1001 -0
  173. package/src/cli/index.ts +196 -0
  174. package/src/core/config.ts +312 -0
  175. package/src/core/mock/connector-mock-server.ts +555 -0
  176. package/src/core/mock/zod-mock-generator.ts +205 -0
  177. package/src/core/operations/create-model.ts +174 -0
  178. package/src/core/operations/runtime.ts +235 -0
  179. package/src/core/operations/scaffold-machine.ts +91 -0
  180. package/src/core/project/manifest.ts +402 -0
  181. package/src/core/project/validation.ts +229 -0
  182. package/src/core/scaffold/auth-generator.ts +1284 -0
  183. package/src/core/scaffold/connector-functions-generator.ts +1128 -0
  184. package/src/core/scaffold/functions-generator.ts +970 -0
  185. package/src/core/scaffold/model-parser.ts +841 -0
  186. package/src/core/scaffold/native-schema-generator.ts +798 -0
  187. package/src/core/scaffold/nextjs-generator.ts +370 -0
  188. package/src/core/scaffold/openapi-generator.ts +212 -0
  189. package/src/core/scaffold/ui-generator.ts +1061 -0
  190. package/src/database/base-model.ts +184 -0
  191. package/src/database/client.ts +140 -0
  192. package/src/database/repository.ts +104 -0
  193. package/src/database/runtime-check.ts +25 -0
  194. package/src/index.ts +27 -0
  195. package/src/machine/contracts.ts +17 -0
  196. package/src/machine/errors.ts +34 -0
  197. package/src/machine/index.ts +173 -0
  198. package/src/mcp/index.ts +185 -0
  199. package/src/types/index.ts +134 -0
  200. package/src/utils/package-manager.ts +229 -0
  201. package/src/utils/python-uv.ts +96 -0
@@ -34,19 +34,151 @@ 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.getFunctionsReadinessTimeoutMs = getFunctionsReadinessTimeoutMs;
44
+ exports.waitForHttpServerReady = waitForHttpServerReady;
45
+ exports.getPythonVirtualEnvPaths = getPythonVirtualEnvPaths;
46
+ exports.getCSharpFunctionsBuildArtifactPaths = getCSharpFunctionsBuildArtifactPaths;
47
+ exports.buildPythonFunctionsEnv = buildPythonFunctionsEnv;
48
+ exports.buildDevCommand = buildDevCommand;
37
49
  const commander_1 = require("commander");
38
50
  const child_process_1 = require("child_process");
51
+ const http = __importStar(require("http"));
52
+ const https = __importStar(require("https"));
39
53
  const path = __importStar(require("path"));
40
54
  const fs = __importStar(require("fs"));
55
+ const os = __importStar(require("os"));
56
+ const net = __importStar(require("net"));
41
57
  const cosmos_1 = require("@azure/cosmos");
42
58
  const config_1 = require("../../core/config");
59
+ const dev_seeds_1 = require("./dev-seeds");
60
+ const package_manager_1 = require("../../utils/package-manager");
61
+ const python_uv_1 = require("../../utils/python-uv");
62
+ const connector_mock_server_1 = require("../../core/mock/connector-mock-server");
63
+ const MINIMUM_CSHARP_CORE_TOOLS_VERSION = '4.6.0';
64
+ const NPM_CORE_TOOLS_PACKAGE = 'azure-functions-core-tools@4';
65
+ function normalizeParsedDevOptions(options) {
66
+ return {
67
+ ...options,
68
+ noFunctions: options.noFunctions ?? options.functions === false,
69
+ };
70
+ }
71
+ function buildFunctionsStartArgs(functionsPort) {
72
+ return ['start', '--port', functionsPort];
73
+ }
74
+ function parseCoreToolsVersion(output) {
75
+ const match = output.match(/\d+\.\d+\.\d+/);
76
+ return match ? match[0] : null;
77
+ }
78
+ function compareVersionNumbers(left, right) {
79
+ const leftParts = left.split('.').map((value) => Number.parseInt(value, 10));
80
+ const rightParts = right.split('.').map((value) => Number.parseInt(value, 10));
81
+ const length = Math.max(leftParts.length, rightParts.length);
82
+ for (let index = 0; index < length; index += 1) {
83
+ const leftPart = leftParts[index] ?? 0;
84
+ const rightPart = rightParts[index] ?? 0;
85
+ if (leftPart !== rightPart) {
86
+ return leftPart - rightPart;
87
+ }
88
+ }
89
+ return 0;
90
+ }
91
+ function buildFunctionsCoreToolsCommand(backendLanguage, installedVersion) {
92
+ if (backendLanguage === 'csharp' &&
93
+ (!installedVersion || compareVersionNumbers(installedVersion, MINIMUM_CSHARP_CORE_TOOLS_VERSION) < 0)) {
94
+ const reason = installedVersion
95
+ ? `installed func ${installedVersion} is too old for C# isolated`
96
+ : 'func is not installed';
97
+ return {
98
+ command: 'npm',
99
+ argsPrefix: ['exec', '--yes', NPM_CORE_TOOLS_PACKAGE, '--'],
100
+ label: `npm exec ${NPM_CORE_TOOLS_PACKAGE} (${reason})`,
101
+ };
102
+ }
103
+ return {
104
+ command: 'func',
105
+ argsPrefix: [],
106
+ label: installedVersion ? `func ${installedVersion}` : 'func',
107
+ };
108
+ }
109
+ function buildNextDevArgs(pm, port) {
110
+ const baseArgs = ['next', 'dev', '--port', port, '--webpack'];
111
+ return pm === 'pnpm' ? ['exec', ...baseArgs] : baseArgs;
112
+ }
113
+ function buildFunctionsBaseUrl(host, functionsPort) {
114
+ return `http://${host || 'localhost'}:${functionsPort}`;
115
+ }
116
+ function getFunctionsReadinessTimeoutMs(backendLanguage) {
117
+ return backendLanguage === 'csharp' ? 90000 : 30000;
118
+ }
119
+ async function waitForHttpServerReady(url, timeoutMs = 30000, intervalMs = 500) {
120
+ const deadline = Date.now() + timeoutMs;
121
+ while (Date.now() <= deadline) {
122
+ if (await probeHttpServer(url)) {
123
+ return true;
124
+ }
125
+ if (Date.now() + intervalMs > deadline) {
126
+ break;
127
+ }
128
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
129
+ }
130
+ return probeHttpServer(url);
131
+ }
132
+ function getPythonVirtualEnvPaths(functionsDir) {
133
+ const venvDir = path.join(functionsDir, '.venv');
134
+ const binDir = process.platform === 'win32'
135
+ ? path.join(venvDir, 'Scripts')
136
+ : path.join(venvDir, 'bin');
137
+ const pythonExecutable = process.platform === 'win32'
138
+ ? path.join(binDir, 'python.exe')
139
+ : path.join(binDir, 'python');
140
+ return { venvDir, binDir, pythonExecutable };
141
+ }
142
+ function getCSharpFunctionsBuildArtifactPaths(functionsDir) {
143
+ return [
144
+ path.join(functionsDir, 'bin'),
145
+ path.join(functionsDir, 'obj'),
146
+ ];
147
+ }
148
+ function buildPythonFunctionsEnv(baseEnv, functionsDir) {
149
+ const { venvDir, binDir, pythonExecutable } = getPythonVirtualEnvPaths(functionsDir);
150
+ const pathKey = getPathEnvKey(baseEnv);
151
+ const currentPath = baseEnv[pathKey] || '';
152
+ return {
153
+ ...baseEnv,
154
+ [pathKey]: currentPath ? `${binDir}${path.delimiter}${currentPath}` : binDir,
155
+ VIRTUAL_ENV: venvDir,
156
+ languageWorkers__python__defaultExecutablePath: pythonExecutable,
157
+ };
158
+ }
159
+ function getPathEnvKey(env) {
160
+ return Object.keys(env).find((key) => key.toUpperCase() === 'PATH') || 'PATH';
161
+ }
162
+ function prependToPathEnv(env, entry) {
163
+ const pathKey = getPathEnvKey(env);
164
+ const currentPath = env[pathKey] || '';
165
+ return {
166
+ ...env,
167
+ [pathKey]: currentPath ? `${entry}${path.delimiter}${currentPath}` : entry,
168
+ };
169
+ }
43
170
  /**
44
171
  * Check if Azure Functions Core Tools is installed
45
172
  */
46
173
  async function checkCoreTools() {
174
+ return checkCommand('func', ['--version']);
175
+ }
176
+ async function checkCommand(command, args = ['--version'], options) {
47
177
  return new Promise((resolve) => {
48
- const checkProcess = (0, child_process_1.spawn)('func', ['--version'], {
49
- shell: true,
178
+ const checkProcess = (0, child_process_1.spawn)(command, args, {
179
+ cwd: options?.cwd,
180
+ env: options?.env ?? process.env,
181
+ shell: options?.shell ?? true,
50
182
  stdio: 'pipe',
51
183
  });
52
184
  checkProcess.on('close', (code) => {
@@ -57,12 +189,173 @@ async function checkCoreTools() {
57
189
  });
58
190
  });
59
191
  }
192
+ async function resolveProjectLocalUvCommand(projectRoot) {
193
+ const uvEnv = (0, python_uv_1.buildProjectLocalUvEnv)(process.env, projectRoot);
194
+ const { localUvExecutable } = (0, python_uv_1.getProjectLocalUvPaths)(projectRoot);
195
+ if (await checkCommand('uv', ['--version'], { shell: false })) {
196
+ return { command: 'uv', env: uvEnv };
197
+ }
198
+ if (fs.existsSync(localUvExecutable) && await checkCommand(localUvExecutable, ['--version'], { shell: false })) {
199
+ return { command: localUvExecutable, env: uvEnv };
200
+ }
201
+ console.log('šŸ“¦ Installing project-local uv...');
202
+ const installer = (0, python_uv_1.getProjectLocalUvInstallerCommand)();
203
+ await runCommand(installer.command, installer.args, projectRoot, 'uv installation', (0, python_uv_1.buildProjectLocalUvInstallerEnv)(process.env, projectRoot), false);
204
+ if (!(fs.existsSync(localUvExecutable) && await checkCommand(localUvExecutable, ['--version'], { shell: false }))) {
205
+ throw new Error('Failed to install project-local uv.');
206
+ }
207
+ return { command: localUvExecutable, env: uvEnv };
208
+ }
209
+ async function getCommandPath(command) {
210
+ const locator = process.platform === 'win32' ? 'where' : 'which';
211
+ const result = await captureCommandOutput(locator, [command]);
212
+ const firstLine = result
213
+ .split(/\r?\n/)
214
+ .map((line) => line.trim())
215
+ .find(Boolean);
216
+ return firstLine || null;
217
+ }
218
+ async function resolveInstalledCoreToolsVersion() {
219
+ if (!(await checkCoreTools())) {
220
+ return null;
221
+ }
222
+ try {
223
+ return parseCoreToolsVersion(await captureCommandOutput('func', ['--version']));
224
+ }
225
+ catch {
226
+ return null;
227
+ }
228
+ }
229
+ async function captureCommandOutput(command, args, cwd, env) {
230
+ return new Promise((resolve, reject) => {
231
+ const child = (0, child_process_1.spawn)(command, args, {
232
+ cwd,
233
+ env,
234
+ shell: false,
235
+ stdio: ['ignore', 'pipe', 'pipe'],
236
+ });
237
+ let stdout = '';
238
+ let stderr = '';
239
+ child.stdout?.on('data', (data) => {
240
+ stdout += data.toString();
241
+ });
242
+ child.stderr?.on('data', (data) => {
243
+ stderr += data.toString();
244
+ });
245
+ child.on('close', (code) => {
246
+ if (code === 0) {
247
+ resolve(stdout);
248
+ }
249
+ else {
250
+ reject(new Error(stderr || stdout || `${command} exited with code ${code}`));
251
+ }
252
+ });
253
+ child.on('error', reject);
254
+ });
255
+ }
256
+ async function probeHttpServer(url) {
257
+ return new Promise((resolve) => {
258
+ const target = new URL(url);
259
+ const requestFactory = target.protocol === 'https:' ? https.request : http.request;
260
+ let settled = false;
261
+ const finish = (value) => {
262
+ if (!settled) {
263
+ settled = true;
264
+ resolve(value);
265
+ }
266
+ };
267
+ const request = requestFactory({
268
+ hostname: target.hostname,
269
+ port: target.port,
270
+ path: target.pathname || '/',
271
+ method: 'GET',
272
+ timeout: 1000,
273
+ }, (response) => {
274
+ response.resume();
275
+ finish(true);
276
+ });
277
+ request.on('timeout', () => {
278
+ request.destroy();
279
+ finish(false);
280
+ });
281
+ request.on('error', () => finish(false));
282
+ request.end();
283
+ });
284
+ }
285
+ async function resolvePythonRuntimeDetails(functionsDir, env) {
286
+ const { pythonExecutable } = getPythonVirtualEnvPaths(functionsDir);
287
+ const output = await captureCommandOutput(pythonExecutable, [
288
+ '-c',
289
+ 'import platform; import sys; print(str(sys.version_info.major) + "." + str(sys.version_info.minor)); print(platform.machine())',
290
+ ], functionsDir, env);
291
+ const [version, architecture] = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
292
+ if (!version || !architecture) {
293
+ throw new Error('Failed to determine Python runtime details.');
294
+ }
295
+ return { version, architecture };
296
+ }
297
+ async function bridgePythonCoreToolsForWindowsArm64(functionsDir, env) {
298
+ if (process.platform !== 'win32' || process.arch !== 'arm64') {
299
+ return env;
300
+ }
301
+ const funcPath = await getCommandPath('func');
302
+ if (!funcPath) {
303
+ return env;
304
+ }
305
+ const { version, architecture } = await resolvePythonRuntimeDetails(functionsDir, env);
306
+ if (architecture.toUpperCase() !== 'AMD64') {
307
+ return env;
308
+ }
309
+ const coreToolsRoot = path.dirname(funcPath);
310
+ const armWorkerDir = path.join(coreToolsRoot, 'workers', 'python', version, 'WINDOWS', 'Arm64');
311
+ if (fs.existsSync(armWorkerDir)) {
312
+ return env;
313
+ }
314
+ const x64WorkerDir = path.join(coreToolsRoot, 'workers', 'python', version, 'WINDOWS', 'X64');
315
+ if (!fs.existsSync(x64WorkerDir)) {
316
+ return env;
317
+ }
318
+ const patchedRoot = path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'), 'SwallowKit', 'azure-functions-core-tools-python-bridge');
319
+ if (!fs.existsSync(path.join(patchedRoot, 'func.exe'))) {
320
+ console.log('🩹 Creating a local Azure Functions Core Tools bridge for Windows Arm64 Python...');
321
+ fs.mkdirSync(path.dirname(patchedRoot), { recursive: true });
322
+ fs.cpSync(coreToolsRoot, patchedRoot, { recursive: true });
323
+ }
324
+ const patchedArmWorkerDir = path.join(patchedRoot, 'workers', 'python', version, 'WINDOWS', 'Arm64');
325
+ const patchedX64WorkerDir = path.join(patchedRoot, 'workers', 'python', version, 'WINDOWS', 'X64');
326
+ if (!fs.existsSync(patchedArmWorkerDir) && fs.existsSync(patchedX64WorkerDir)) {
327
+ fs.cpSync(patchedX64WorkerDir, patchedArmWorkerDir, { recursive: true });
328
+ }
329
+ console.log(`🩹 Using bridged Azure Functions Core Tools from ${patchedRoot}`);
330
+ return prependToPathEnv(env, patchedRoot);
331
+ }
332
+ async function preparePythonFunctionsEnvironment(functionsDir) {
333
+ const projectRoot = (0, python_uv_1.getPythonProjectRoot)(functionsDir);
334
+ const { command: uvCommand, env: uvEnv } = await resolveProjectLocalUvCommand(projectRoot);
335
+ const { venvDir, pythonExecutable } = getPythonVirtualEnvPaths(functionsDir);
336
+ const hasUsableVirtualEnv = fs.existsSync(pythonExecutable) && await checkCommand(pythonExecutable, ['--version'], {
337
+ cwd: functionsDir,
338
+ env: uvEnv,
339
+ shell: false,
340
+ });
341
+ if (!hasUsableVirtualEnv) {
342
+ const venvArgs = (0, python_uv_1.buildUvVenvArgs)('.venv');
343
+ if (fs.existsSync(venvDir)) {
344
+ venvArgs.push('--clear');
345
+ }
346
+ console.log('šŸ“¦ Creating Python virtual environment with uv...');
347
+ await runCommand(uvCommand, venvArgs, functionsDir, 'python virtual environment setup', uvEnv, false);
348
+ }
349
+ console.log('šŸ“¦ Installing Python Azure Functions dependencies with uv...');
350
+ await runCommand(uvCommand, (0, python_uv_1.buildUvPipInstallArgs)(pythonExecutable, 'requirements.txt'), functionsDir, 'python dependency installation', uvEnv, false);
351
+ const pythonEnv = buildPythonFunctionsEnv(uvEnv, functionsDir);
352
+ return bridgePythonCoreToolsForWindowsArm64(functionsDir, pythonEnv);
353
+ }
60
354
  /**
61
355
  * Check if Cosmos DB Emulator is running by checking if port 8081 is open
62
356
  */
63
357
  async function checkCosmosDBEmulator() {
64
358
  return new Promise((resolve) => {
65
- const net = require('net');
66
359
  const socket = new net.Socket();
67
360
  const timeout = setTimeout(() => {
68
361
  socket.destroy();
@@ -80,112 +373,100 @@ async function checkCosmosDBEmulator() {
80
373
  socket.connect(8081, 'localhost');
81
374
  });
82
375
  }
83
- exports.devCommand = new commander_1.Command()
84
- .name('dev')
85
- .description('Start SwallowKit development server (Cosmos DB + Next.js + Azure Functions)')
86
- .option('-p, --port <port>', 'Next.js port', '3000')
87
- .option('-f, --functions-port <port>', 'Azure Functions port', '7071')
88
- .option('--host <host>', 'Host name', 'localhost')
89
- .option('--open', 'Open browser automatically', false)
90
- .option('--verbose', 'Show verbose logs', false)
91
- .option('--no-functions', 'Skip Azure Functions startup', false)
92
- .action(async (options) => {
93
- // SwallowKit ćƒ—ćƒ­ć‚øć‚§ć‚Æćƒˆćƒ‡ć‚£ćƒ¬ć‚ÆćƒˆćƒŖć‹ć©ć†ć‹ć‚’ę¤œčØ¼
94
- (0, config_1.ensureSwallowKitProject)("dev");
95
- console.log('šŸš€ Starting SwallowKit development environment...');
96
- if (options.verbose) {
97
- console.log('āš™ļø Options:', options);
98
- }
99
- await startDevEnvironment(options);
100
- });
376
+ function buildDevCommand(runDevEnvironment = startDevEnvironment, verifyProject = config_1.ensureSwallowKitProject) {
377
+ return new commander_1.Command()
378
+ .name('dev')
379
+ .description('Start SwallowKit development server (Cosmos DB + Next.js + Azure Functions)')
380
+ .option('-p, --port <port>', 'Next.js port', '3000')
381
+ .option('-f, --functions-port <port>', 'Azure Functions port', '7071')
382
+ .option('--host <host>', 'Host name', 'localhost')
383
+ .option('--open', 'Open browser automatically', false)
384
+ .option('--verbose', 'Show verbose logs', false)
385
+ .option('--no-functions', 'Skip Azure Functions startup')
386
+ .option('--seed-env <environment>', 'Replace Cosmos DB Emulator data from dev-seeds/<environment> before startup')
387
+ .option('--mock-connectors', 'Start mock server for connector models (serves Zod-generated data)', false)
388
+ .action(async (options) => {
389
+ const normalizedOptions = normalizeParsedDevOptions(options);
390
+ // SwallowKit ćƒ—ćƒ­ć‚øć‚§ć‚Æćƒˆćƒ‡ć‚£ćƒ¬ć‚ÆćƒˆćƒŖć‹ć©ć†ć‹ć‚’ę¤œčØ¼
391
+ verifyProject("dev");
392
+ console.log('šŸš€ Starting SwallowKit development environment...');
393
+ if (normalizedOptions.verbose) {
394
+ console.log('āš™ļø Options:', normalizedOptions);
395
+ }
396
+ await runDevEnvironment(normalizedOptions);
397
+ });
398
+ }
399
+ exports.devCommand = buildDevCommand();
101
400
  async function initializeCosmosDB(databaseName) {
102
401
  try {
103
- // Read local.settings.json from functions directory
104
- const functionsDir = path.join(process.cwd(), 'functions');
105
- const localSettingsPath = path.join(functionsDir, 'local.settings.json');
106
- if (!fs.existsSync(localSettingsPath)) {
107
- console.log('āš ļø local.settings.json not found. Skipping Cosmos DB initialization.');
108
- return;
109
- }
110
- const localSettings = JSON.parse(fs.readFileSync(localSettingsPath, 'utf-8'));
111
- const connectionString = localSettings.Values?.CosmosDBConnection;
112
- const dbName = localSettings.Values?.COSMOS_DB_DATABASE_NAME || databaseName;
113
- if (!connectionString) {
114
- console.log('āš ļø CosmosDBConnection not found in local.settings.json. Skipping Cosmos DB initialization.');
115
- return;
402
+ const connectionInfoResult = (0, dev_seeds_1.resolveLocalCosmosConnectionInfo)(databaseName);
403
+ if (!connectionInfoResult.ok) {
404
+ if (connectionInfoResult.reason === 'missing-local-settings') {
405
+ console.log('āš ļø local.settings.json not found. Skipping Cosmos DB initialization.');
406
+ }
407
+ else if (connectionInfoResult.reason === 'missing-connection-string') {
408
+ console.log('āš ļø CosmosDBConnection not found in local.settings.json. Skipping Cosmos DB initialization.');
409
+ }
410
+ else {
411
+ console.log('āš ļø Invalid CosmosDB connection string format.');
412
+ }
413
+ return null;
116
414
  }
117
415
  console.log('šŸ—„ļø Initializing Cosmos DB...');
118
- // Parse connection string
119
- const endpointMatch = connectionString.match(/AccountEndpoint=([^;]+)/);
120
- const keyMatch = connectionString.match(/AccountKey=([^;]+)/);
121
- if (!endpointMatch || !keyMatch) {
122
- console.log('āš ļø Invalid CosmosDB connection string format.');
123
- return;
124
- }
125
- const endpoint = endpointMatch[1];
416
+ const { endpoint, key, databaseName: dbName } = connectionInfoResult.value;
126
417
  const client = new cosmos_1.CosmosClient({
127
418
  endpoint: endpoint,
128
- key: keyMatch[1]
419
+ key,
129
420
  });
130
421
  // Create database if not exists
131
422
  const { database } = await client.databases.createIfNotExists({ id: dbName });
132
423
  console.log(`āœ… Database "${dbName}" ready`);
133
- // Read lib/scaffold-config.ts to get list of models
134
- const scaffoldConfigPath = path.join(process.cwd(), 'lib', 'scaffold-config.ts');
135
- if (fs.existsSync(scaffoldConfigPath)) {
136
- const scaffoldConfigContent = fs.readFileSync(scaffoldConfigPath, 'utf-8');
137
- // Parse TypeScript file to extract models array
138
- const modelsMatch = scaffoldConfigContent.match(/models:\s*\[([\s\S]*?)\]\s*as\s*ScaffoldModel\[\]/);
139
- if (modelsMatch) {
140
- const modelsArrayContent = modelsMatch[1];
141
- // Extract model names from objects like { name: 'Task', path: '/task', label: 'Task' }
142
- const modelMatches = modelsArrayContent.matchAll(/\{\s*name:\s*['"](\w+)['"]/g);
143
- const models = Array.from(modelMatches, m => m[1]);
144
- for (const modelName of models) {
145
- const containerName = `${modelName}s`; // Pluralize model name
146
- // Try creating container with full partition key definition first
147
- let containerCreated = false;
148
- try {
149
- console.log(`šŸ”§ Creating container "${containerName}" with partition key /id...`);
150
- const containerResponse = await database.containers.createIfNotExists({
151
- id: containerName,
152
- partitionKey: {
153
- paths: ['/id'],
154
- kind: cosmos_1.PartitionKeyKind.Hash,
155
- version: 2
156
- }
157
- });
158
- console.log(`āœ… Container "${containerName}" ready (status: ${containerResponse.statusCode})`);
159
- containerCreated = true;
160
- }
161
- catch (error) {
162
- console.log(`āš ļø Failed with full partition key definition: ${error.message}`);
163
- console.log(`šŸ”„ Retrying with simple partition key...`);
424
+ const models = await (0, dev_seeds_1.loadProjectModels)();
425
+ for (const model of models) {
426
+ const containerName = (0, dev_seeds_1.getContainerNameForModel)(model);
427
+ // Try creating container with full partition key definition first
428
+ let containerCreated = false;
429
+ try {
430
+ console.log(`šŸ”§ Creating container "${containerName}" with partition key ${model.partitionKey}...`);
431
+ const containerResponse = await database.containers.createIfNotExists({
432
+ id: containerName,
433
+ partitionKey: {
434
+ paths: [model.partitionKey],
435
+ kind: cosmos_1.PartitionKeyKind.Hash,
436
+ version: 2
164
437
  }
165
- // If first attempt failed, try with simple partition key definition
166
- if (!containerCreated) {
167
- try {
168
- const containerResponse = await database.containers.createIfNotExists({
169
- id: containerName,
170
- partitionKey: {
171
- paths: ['/id']
172
- }
173
- });
174
- console.log(`āœ… Container "${containerName}" ready (status: ${containerResponse.statusCode})`);
175
- }
176
- catch (containerError) {
177
- console.error(`āŒ Failed to create container "${containerName}":`, containerError.message);
178
- console.error(`Error code: ${containerError.code}`);
179
- if (containerError.body) {
180
- console.error(`Response body:`, JSON.stringify(containerError.body, null, 2));
181
- }
182
- // Continue with other containers
438
+ });
439
+ console.log(`āœ… Container "${containerName}" ready (status: ${containerResponse.statusCode})`);
440
+ containerCreated = true;
441
+ }
442
+ catch (error) {
443
+ const message = error instanceof Error ? error.message : String(error);
444
+ console.log(`āš ļø Failed with full partition key definition: ${message}`);
445
+ console.log(`šŸ”„ Retrying with simple partition key...`);
446
+ }
447
+ // If first attempt failed, try with simple partition key definition
448
+ if (!containerCreated) {
449
+ try {
450
+ const containerResponse = await database.containers.createIfNotExists({
451
+ id: containerName,
452
+ partitionKey: {
453
+ paths: [model.partitionKey]
183
454
  }
455
+ });
456
+ console.log(`āœ… Container "${containerName}" ready (status: ${containerResponse.statusCode})`);
457
+ }
458
+ catch (containerError) {
459
+ console.error(`āŒ Failed to create container "${containerName}":`, containerError.message);
460
+ console.error(`Error code: ${containerError.code}`);
461
+ if (containerError.body) {
462
+ console.error(`Response body:`, JSON.stringify(containerError.body, null, 2));
184
463
  }
464
+ // Continue with other containers
185
465
  }
186
466
  }
187
467
  }
188
468
  console.log('āœ… Cosmos DB initialization complete\n');
469
+ return { endpoint, key, databaseName: dbName, models };
189
470
  }
190
471
  catch (error) {
191
472
  console.error('āš ļø Cosmos DB initialization failed:', error.message);
@@ -193,16 +474,45 @@ async function initializeCosmosDB(databaseName) {
193
474
  console.error('Stack trace:', error.stack);
194
475
  }
195
476
  console.log('šŸ’” Make sure Cosmos DB Emulator is running');
477
+ return null;
196
478
  }
197
479
  }
198
480
  async function startDevEnvironment(options) {
199
481
  const port = options.port || '3000';
200
482
  const functionsPort = options.functionsPort || '7071';
483
+ // Detect package manager from project lockfile
484
+ const pm = (0, package_manager_1.detectFromProject)();
485
+ const pmCmd = (0, package_manager_1.getCommands)(pm);
486
+ const backendLanguage = (0, config_1.getBackendLanguage)();
201
487
  // ćƒ—ćƒ­ć‚»ć‚¹ć‚’ē®”ē†ć™ć‚‹é…åˆ—
202
488
  const processes = [];
489
+ let functionsEnv = process.env;
490
+ let mockServer = null;
491
+ let envLocalPath = '';
492
+ let envLocalDefaultUrl = ''; // default Functions URL to restore on shutdown
493
+ const functionsBaseUrl = buildFunctionsBaseUrl(options.host, functionsPort);
494
+ let functionsReadinessPromise = null;
495
+ let functionsReady = !!options.noFunctions;
203
496
  // Cleanup processes on Ctrl+C
204
- process.on('SIGINT', () => {
497
+ process.on('SIGINT', async () => {
205
498
  console.log('\nšŸ›‘ Stopping development servers...');
499
+ // Restore .env.local to default Functions port on shutdown
500
+ if (envLocalPath && envLocalDefaultUrl) {
501
+ try {
502
+ if (fs.existsSync(envLocalPath)) {
503
+ const content = fs.readFileSync(envLocalPath, 'utf-8');
504
+ if (content.includes('BACKEND_FUNCTIONS_BASE_URL=') &&
505
+ !content.includes(`BACKEND_FUNCTIONS_BASE_URL=${envLocalDefaultUrl}`)) {
506
+ const restored = content.replace(/^BACKEND_FUNCTIONS_BASE_URL=.*/m, `BACKEND_FUNCTIONS_BASE_URL=${envLocalDefaultUrl}`);
507
+ fs.writeFileSync(envLocalPath, restored, 'utf-8');
508
+ }
509
+ }
510
+ }
511
+ catch { /* ignore */ }
512
+ }
513
+ if (mockServer) {
514
+ await mockServer.stop();
515
+ }
206
516
  processes.forEach((proc) => {
207
517
  if (proc && !proc.killed) {
208
518
  proc.kill();
@@ -226,12 +536,13 @@ async function startDevEnvironment(options) {
226
536
  }
227
537
  // 2. Check if Azure Functions exists
228
538
  const functionsDir = path.join(process.cwd(), 'functions');
229
- const hasFunctions = fs.existsSync(functionsDir) &&
230
- fs.existsSync(path.join(functionsDir, 'package.json'));
539
+ const hasFunctions = fs.existsSync(functionsDir) && hasFunctionsProject(functionsDir, backendLanguage);
540
+ let functionsCoreToolsCommand = null;
541
+ let installedCoreToolsVersion = null;
231
542
  if (hasFunctions && !options.noFunctions) {
232
- // Check if Azure Functions Core Tools is installed
233
- const coreToolsInstalled = await checkCoreTools();
234
- if (!coreToolsInstalled) {
543
+ installedCoreToolsVersion = await resolveInstalledCoreToolsVersion();
544
+ functionsCoreToolsCommand = buildFunctionsCoreToolsCommand(backendLanguage, installedCoreToolsVersion);
545
+ if (functionsCoreToolsCommand.command === 'func' && !installedCoreToolsVersion) {
235
546
  console.log('');
236
547
  console.log('āš ļø Azure Functions Core Tools not found.');
237
548
  console.log('');
@@ -248,7 +559,7 @@ async function startDevEnvironment(options) {
248
559
  if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
249
560
  console.log('šŸ“¦ Installing Azure Functions Core Tools...');
250
561
  console.log(' This may take a few minutes.');
251
- const installProcess = (0, child_process_1.spawn)('npm', ['install', '-g', 'azure-functions-core-tools@4'], {
562
+ const installProcess = (0, child_process_1.spawn)(pm, pm === 'pnpm' ? ['add', '-g', 'azure-functions-core-tools@4'] : ['install', '-g', 'azure-functions-core-tools@4'], {
252
563
  shell: true,
253
564
  stdio: 'inherit',
254
565
  });
@@ -261,7 +572,7 @@ async function startDevEnvironment(options) {
261
572
  else {
262
573
  console.error('āŒ Installation failed.');
263
574
  console.log('šŸ’” Please install manually:');
264
- console.log(' npm install -g azure-functions-core-tools@4');
575
+ console.log(` ${pmCmd.addGlobal} azure-functions-core-tools@4`);
265
576
  reject(new Error(`Installation failed with code ${code}`));
266
577
  }
267
578
  });
@@ -272,12 +583,15 @@ async function startDevEnvironment(options) {
272
583
  console.log('');
273
584
  console.log('ā„¹ļø Skipping Azure Functions startup.');
274
585
  console.log('šŸ’” To install later:');
275
- console.log(' npm install -g azure-functions-core-tools@4');
586
+ console.log(` ${pmCmd.addGlobal} azure-functions-core-tools@4`);
276
587
  console.log('');
277
588
  // Skip Azure Functions startup
278
589
  options.noFunctions = true;
279
590
  }
280
591
  }
592
+ else if (functionsCoreToolsCommand.command !== 'func') {
593
+ console.log(`ā„¹ļø Using ${functionsCoreToolsCommand.label}.`);
594
+ }
281
595
  if (!options.noFunctions) {
282
596
  // Check if Cosmos DB Emulator is running
283
597
  const cosmosRunning = await checkCosmosDBEmulator();
@@ -293,66 +607,89 @@ async function startDevEnvironment(options) {
293
607
  process.exit(1);
294
608
  }
295
609
  // Initialize Cosmos DB before starting Functions
296
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
297
- const appName = packageJson.name || 'App';
298
- const databaseName = `${appName.charAt(0).toUpperCase() + appName.slice(1)}Database`;
299
- await initializeCosmosDB(databaseName);
610
+ const databaseName = (0, dev_seeds_1.getDefaultCosmosDatabaseName)();
611
+ const cosmosInitialization = await initializeCosmosDB(databaseName);
612
+ if (options.seedEnv && cosmosInitialization) {
613
+ await (0, dev_seeds_1.applyDevSeedEnvironment)({
614
+ client: new cosmos_1.CosmosClient({
615
+ endpoint: cosmosInitialization.endpoint,
616
+ key: cosmosInitialization.key,
617
+ }),
618
+ databaseName: cosmosInitialization.databaseName,
619
+ environment: options.seedEnv,
620
+ models: cosmosInitialization.models,
621
+ });
622
+ }
300
623
  console.log('');
301
624
  console.log('šŸš€ Starting Azure Functions...');
302
- // Check if npm install has been run in functions directory
303
- const functionsNodeModules = path.join(functionsDir, 'node_modules');
304
- if (!fs.existsSync(functionsNodeModules)) {
305
- console.log('šŸ“¦ Installing Azure Functions dependencies...');
306
- const npmInstall = (0, child_process_1.spawn)('npm', ['install'], {
307
- cwd: functionsDir,
625
+ if (backendLanguage === 'typescript') {
626
+ const functionsNodeModules = path.join(functionsDir, 'node_modules');
627
+ if (!fs.existsSync(functionsNodeModules)) {
628
+ console.log('šŸ“¦ Installing Azure Functions dependencies...');
629
+ await runCommand(pm, ['install'], functionsDir, `${pm} install`);
630
+ }
631
+ }
632
+ else if (backendLanguage === 'csharp') {
633
+ console.log('ā„¹ļø C# Azure Functions can take longer on cold start while the worker builds.');
634
+ }
635
+ else {
636
+ functionsEnv = await preparePythonFunctionsEnvironment(functionsDir);
637
+ }
638
+ }
639
+ }
640
+ if (hasFunctions && !options.noFunctions) {
641
+ // Build shared package before starting Functions
642
+ const sharedDir = path.join(process.cwd(), 'shared');
643
+ const sharedPkgPath = path.join(sharedDir, 'package.json');
644
+ if (fs.existsSync(sharedDir) && fs.existsSync(sharedPkgPath)) {
645
+ const sharedPkg = JSON.parse(fs.readFileSync(sharedPkgPath, 'utf-8'));
646
+ if (sharedPkg.scripts?.build) {
647
+ console.log('šŸ“¦ Building shared package...');
648
+ const filterArgs = pm === 'pnpm'
649
+ ? ['run', '--filter', 'shared', 'build']
650
+ : ['run', '--workspace=shared', 'build'];
651
+ const sharedBuild = (0, child_process_1.spawn)(pm, filterArgs, {
652
+ cwd: process.cwd(),
308
653
  shell: true,
309
654
  stdio: 'inherit',
310
655
  });
311
656
  await new Promise((resolve, reject) => {
312
- npmInstall.on('close', (code) => {
657
+ sharedBuild.on('close', (code) => {
313
658
  if (code === 0) {
659
+ console.log('āœ… Shared package built successfully');
314
660
  resolve();
315
661
  }
316
662
  else {
317
- reject(new Error(`npm install failed with code ${code}`));
663
+ reject(new Error(`Shared package build failed with code ${code}`));
318
664
  }
319
665
  });
320
- npmInstall.on('error', reject);
666
+ sharedBuild.on('error', reject);
321
667
  });
322
668
  }
669
+ else {
670
+ console.log('āš ļø Shared package has no build script — skipping build. Run "swallowkit add-auth" to fix.');
671
+ }
323
672
  }
324
- }
325
- if (hasFunctions && !options.noFunctions) {
326
- // Build shared package before starting Functions
327
- const sharedDir = path.join(process.cwd(), 'shared');
328
- if (fs.existsSync(sharedDir) && fs.existsSync(path.join(sharedDir, 'package.json'))) {
329
- console.log('šŸ“¦ Building shared package...');
330
- const sharedBuild = (0, child_process_1.spawn)('npm', ['run', 'build', '-w', 'shared'], {
331
- cwd: process.cwd(),
332
- shell: true,
333
- stdio: 'inherit',
334
- });
335
- await new Promise((resolve, reject) => {
336
- sharedBuild.on('close', (code) => {
337
- if (code === 0) {
338
- console.log('āœ… Shared package built successfully');
339
- resolve();
340
- }
341
- else {
342
- reject(new Error(`Shared package build failed with code ${code}`));
343
- }
344
- });
345
- sharedBuild.on('error', reject);
346
- });
673
+ // Build TypeScript functions after shared package (functions import from shared)
674
+ if (backendLanguage === 'typescript') {
675
+ const functionsPkgPath = path.join(functionsDir, 'package.json');
676
+ if (fs.existsSync(functionsPkgPath)) {
677
+ const functionsPkg = JSON.parse(fs.readFileSync(functionsPkgPath, 'utf-8'));
678
+ if (functionsPkg.scripts?.build) {
679
+ console.log('šŸ“¦ Building TypeScript Azure Functions...');
680
+ await runCommand(pm, ['run', 'build'], functionsDir, `${pm} run build`);
681
+ }
682
+ }
347
683
  }
348
684
  // Azure Functions ć‚’čµ·å‹•
349
- const funcEnv = { ...process.env, FUNCTIONS_PORT: functionsPort };
350
- const funcProcess = (0, child_process_1.spawn)('npm', ['start'], {
685
+ const functionsCommand = functionsCoreToolsCommand ?? buildFunctionsCoreToolsCommand(backendLanguage, installedCoreToolsVersion);
686
+ const funcProcess = (0, child_process_1.spawn)(functionsCommand.command, [...functionsCommand.argsPrefix, ...buildFunctionsStartArgs(functionsPort)], {
351
687
  cwd: functionsDir,
352
688
  shell: true,
353
689
  stdio: 'pipe', // Always pipe to capture output
354
- env: funcEnv
690
+ env: functionsEnv
355
691
  });
692
+ let pythonWorkerMissing = false;
356
693
  // Functions ć®å‡ŗåŠ›ć‚’ćć®ć¾ć¾č”Øē¤ŗļ¼ˆćƒ—ćƒ¬ćƒ•ć‚£ćƒƒć‚Æć‚¹ä»˜ćļ¼‰
357
694
  if (funcProcess.stdout) {
358
695
  funcProcess.stdout.on('data', (data) => {
@@ -360,6 +697,10 @@ async function startDevEnvironment(options) {
360
697
  // å„č”Œć«ćƒ—ćƒ¬ćƒ•ć‚£ćƒƒć‚Æć‚¹ć‚’ä»˜ć‘ć¦å‡ŗåŠ›
361
698
  const lines = output.split('\n').filter((line) => line.trim());
362
699
  lines.forEach((line) => {
700
+ if (backendLanguage === 'python' &&
701
+ (line.includes('WorkerConfig for runtime: python not found') || line.includes('DefaultWorkerPath:'))) {
702
+ pythonWorkerMissing = true;
703
+ }
363
704
  console.log(`[Functions] ${line}`);
364
705
  });
365
706
  });
@@ -369,6 +710,10 @@ async function startDevEnvironment(options) {
369
710
  const output = data.toString();
370
711
  const lines = output.split('\n').filter((line) => line.trim());
371
712
  lines.forEach((line) => {
713
+ if (backendLanguage === 'python' &&
714
+ (line.includes('WorkerConfig for runtime: python not found') || line.includes('DefaultWorkerPath:'))) {
715
+ pythonWorkerMissing = true;
716
+ }
372
717
  console.error(`[Functions Error] ${line}`);
373
718
  });
374
719
  });
@@ -377,14 +722,20 @@ async function startDevEnvironment(options) {
377
722
  funcProcess.on('error', (error) => {
378
723
  console.error('āš ļø Azure Functions startup error:', error.message);
379
724
  console.log('šŸ’” Please ensure Azure Functions Core Tools is installed');
380
- console.log(' npm install -g azure-functions-core-tools@4');
725
+ console.log(` ${pmCmd.addGlobal} azure-functions-core-tools@4`);
381
726
  });
382
727
  funcProcess.on('close', (code) => {
383
728
  if (code !== 0) {
384
729
  console.log(`\nā¹ļø Azure Functions exited (exit code: ${code})`);
730
+ if (backendLanguage === 'python' && pythonWorkerMissing) {
731
+ console.log('šŸ’” Your Azure Functions Core Tools installation is missing the Python worker for this OS/architecture.');
732
+ console.log(' Reinstall a matching Core Tools v4 package (Windows users should prefer the official x64/x86 MSI for their machine).');
733
+ console.log(' SwallowKit local Python dev uses functions/.venv and requirements.txt, but Core Tools still needs its own bundled Python worker.');
734
+ }
385
735
  }
386
736
  });
387
- console.log(`āœ… Azure Functions started (port: ${functionsPort})`);
737
+ console.log(`ā³ Waiting for Azure Functions to accept requests at ${functionsBaseUrl}...`);
738
+ functionsReadinessPromise = waitForHttpServerReady(functionsBaseUrl, getFunctionsReadinessTimeoutMs(backendLanguage));
388
739
  }
389
740
  else if (!hasFunctions) {
390
741
  console.log('');
@@ -396,8 +747,69 @@ async function startDevEnvironment(options) {
396
747
  }
397
748
  console.log('');
398
749
  console.log('šŸš€ Starting Next.js development server...');
750
+ // Mock connector server — start proxy if --mock-connectors is enabled
751
+ let bffTargetPort = functionsPort;
752
+ if (options.mockConnectors) {
753
+ const allModels = await (0, dev_seeds_1.loadProjectModels)();
754
+ const connectorModels = allModels.filter((m) => m.connectorConfig);
755
+ // Resolve auth config — auth functions use RDB connectors, mocked alongside other models
756
+ const authConfig = (0, config_1.getAuthConfig)();
757
+ let mockAuthConfig;
758
+ if (authConfig?.provider === 'custom-jwt' && authConfig.customJwt) {
759
+ // Read JWT_SECRET from functions/local.settings.json if available
760
+ let jwtSecret = 'dev-jwt-secret-change-in-production-min-32-chars!!';
761
+ try {
762
+ const localSettingsPath = path.join(process.cwd(), 'functions', 'local.settings.json');
763
+ if (fs.existsSync(localSettingsPath)) {
764
+ const localSettings = JSON.parse(fs.readFileSync(localSettingsPath, 'utf-8'));
765
+ jwtSecret = localSettings.Values?.[authConfig.customJwt.jwtSecretEnv || 'JWT_SECRET'] || jwtSecret;
766
+ }
767
+ }
768
+ catch { /* ignore */ }
769
+ mockAuthConfig = {
770
+ jwtSecret,
771
+ tokenExpiry: authConfig.customJwt.tokenExpiry,
772
+ customJwt: {
773
+ userTable: authConfig.customJwt.userTable,
774
+ loginIdColumn: authConfig.customJwt.loginIdColumn,
775
+ passwordHashColumn: authConfig.customJwt.passwordHashColumn,
776
+ rolesColumn: authConfig.customJwt.rolesColumn,
777
+ },
778
+ defaultPolicy: authConfig.authorization?.defaultPolicy,
779
+ };
780
+ }
781
+ if (connectorModels.length > 0 || mockAuthConfig) {
782
+ const mockPort = parseInt(functionsPort, 10) + 1;
783
+ mockServer = new connector_mock_server_1.ConnectorMockServer({
784
+ port: mockPort,
785
+ functionsTarget: `${options.host || 'localhost'}:${functionsPort}`,
786
+ connectorModels,
787
+ allModels,
788
+ seedEnv: options.seedEnv,
789
+ host: options.host || 'localhost',
790
+ authConfig: mockAuthConfig,
791
+ });
792
+ await mockServer.start();
793
+ bffTargetPort = String(mockPort);
794
+ const modelCount = connectorModels.length + (mockAuthConfig ? 1 : 0);
795
+ console.log('');
796
+ console.log(`šŸ”Œ Mock server started (port: ${mockPort}) — ${modelCount} model(s) mocked via Zod/seed data`);
797
+ for (const m of connectorModels) {
798
+ const ops = m.connectorConfig.operations.join(', ');
799
+ console.log(` - ${m.name} [${ops}]`);
800
+ }
801
+ if (mockAuthConfig) {
802
+ console.log(` - auth [login, me, logout]`);
803
+ }
804
+ console.log(` Other routes → proxied to Azure Functions (port: ${functionsPort})`);
805
+ }
806
+ else {
807
+ console.log('');
808
+ console.log('ā„¹ļø --mock-connectors specified but no connector models found. Skipping mock server.');
809
+ }
810
+ }
399
811
  // 5. Start Next.js development server
400
- const nextArgs = ['next', 'dev', '--port', port];
812
+ const nextArgs = buildNextDevArgs(pm, port);
401
813
  if (options.open) {
402
814
  // Next.js 14+ deprecated --open option, so we open browser manually
403
815
  setTimeout(() => {
@@ -408,10 +820,33 @@ async function startDevEnvironment(options) {
408
820
  (0, child_process_1.spawn)(start, [url], { shell: true });
409
821
  }, 3000);
410
822
  }
411
- const nextProcess = (0, child_process_1.spawn)('npx', nextArgs, {
823
+ // Ensure .env.local points to bffTargetPort so Next.js reads the correct backend URL.
824
+ // When --mock-connectors is active, bffTargetPort = mock port (7072); otherwise = Functions port (7071).
825
+ // Next.js may load .env.local values that override spawn env vars, so we must keep them in sync.
826
+ envLocalPath = path.join(process.cwd(), '.env.local');
827
+ envLocalDefaultUrl = functionsBaseUrl;
828
+ const bffTargetUrl = buildFunctionsBaseUrl(options.host, bffTargetPort);
829
+ try {
830
+ if (fs.existsSync(envLocalPath)) {
831
+ const envContent = fs.readFileSync(envLocalPath, 'utf-8');
832
+ if (envContent.includes('BACKEND_FUNCTIONS_BASE_URL=') &&
833
+ !envContent.includes(`BACKEND_FUNCTIONS_BASE_URL=${bffTargetUrl}`)) {
834
+ const updated = envContent.replace(/^BACKEND_FUNCTIONS_BASE_URL=.*/m, `BACKEND_FUNCTIONS_BASE_URL=${bffTargetUrl}`);
835
+ fs.writeFileSync(envLocalPath, updated, 'utf-8');
836
+ }
837
+ }
838
+ }
839
+ catch { /* ignore */ }
840
+ const nextEnv = {
841
+ ...process.env,
842
+ BACKEND_FUNCTIONS_BASE_URL: bffTargetUrl,
843
+ FUNCTIONS_BASE_URL: bffTargetUrl,
844
+ };
845
+ const nextProcess = (0, child_process_1.spawn)(pm === 'pnpm' ? 'pnpm' : 'npx', nextArgs, {
412
846
  cwd: process.cwd(),
413
847
  shell: true,
414
848
  stdio: options.verbose ? 'inherit' : 'inherit',
849
+ env: nextEnv,
415
850
  });
416
851
  processes.push(nextProcess);
417
852
  nextProcess.on('error', (error) => {
@@ -423,6 +858,9 @@ async function startDevEnvironment(options) {
423
858
  console.log(`\nā¹ļø Next.js exited (exit code: ${code})`);
424
859
  }
425
860
  // Exit all processes when Next.js exits
861
+ if (mockServer) {
862
+ mockServer.stop().catch(() => { });
863
+ }
426
864
  processes.forEach((proc) => {
427
865
  if (proc && !proc.killed) {
428
866
  proc.kill();
@@ -430,17 +868,36 @@ async function startDevEnvironment(options) {
430
868
  });
431
869
  process.exit(code || 0);
432
870
  });
871
+ if (functionsReadinessPromise) {
872
+ functionsReady = await functionsReadinessPromise;
873
+ console.log('');
874
+ if (functionsReady) {
875
+ console.log(`āœ… Azure Functions ready (port: ${functionsPort})`);
876
+ }
877
+ else {
878
+ console.log(`āš ļø Azure Functions is still starting: ${functionsBaseUrl}`);
879
+ }
880
+ }
433
881
  console.log('');
434
882
  console.log('āœ… SwallowKit development environment is running!');
435
883
  console.log('');
436
884
  console.log(`šŸ“± Next.js: http://${options.host || 'localhost'}:${port}`);
437
885
  if (hasFunctions && !options.noFunctions) {
438
- console.log(`⚔ Azure Functions: http://${options.host || 'localhost'}:${functionsPort}`);
886
+ console.log(`${functionsReady ? '⚔ Azure Functions' : 'ā³ Azure Functions (starting)'}: ${functionsBaseUrl}`);
887
+ }
888
+ if (mockServer) {
889
+ console.log(`šŸ”Œ Mock Proxy: ${bffTargetUrl} (BFF → here)`);
439
890
  }
440
891
  console.log('');
441
- if (hasFunctions && !options.noFunctions) {
892
+ if (hasFunctions && !options.noFunctions && functionsReady) {
442
893
  console.log('šŸ’” Azure Functions and Next.js BFF are connected');
443
894
  }
895
+ else if (hasFunctions && !options.noFunctions) {
896
+ console.log('šŸ’” Azure Functions is still warming up; BFF routes can fail until the backend responds.');
897
+ }
898
+ if (mockServer) {
899
+ console.log('šŸ’” Connector models served from mock server (Zod-generated data)');
900
+ }
444
901
  console.log('');
445
902
  console.log('šŸ›‘ Press Ctrl+C to stop');
446
903
  console.log('');
@@ -455,4 +912,29 @@ async function startDevEnvironment(options) {
455
912
  process.exit(1);
456
913
  }
457
914
  }
915
+ async function runCommand(command, args, cwd, label, env, shell = true) {
916
+ await new Promise((resolve, reject) => {
917
+ const child = (0, child_process_1.spawn)(command, args, {
918
+ cwd,
919
+ shell,
920
+ stdio: 'inherit',
921
+ env,
922
+ });
923
+ child.on('close', (code) => {
924
+ if (code === 0) {
925
+ resolve();
926
+ }
927
+ else {
928
+ reject(new Error(`${label} failed with code ${code}`));
929
+ }
930
+ });
931
+ child.on('error', reject);
932
+ });
933
+ }
934
+ function hasFunctionsProject(functionsDir, backendLanguage) {
935
+ if (backendLanguage === 'typescript') {
936
+ return fs.existsSync(path.join(functionsDir, 'package.json'));
937
+ }
938
+ return fs.existsSync(path.join(functionsDir, 'host.json'));
939
+ }
458
940
  //# sourceMappingURL=dev.js.map