swallowkit 1.0.0-beta.2 → 1.0.0-beta.21

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 (191) hide show
  1. package/LICENSE +21 -21
  2. package/README.ja.md +312 -215
  3. package/README.md +369 -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 +35 -0
  21. package/dist/cli/commands/dev-seeds.d.ts.map +1 -0
  22. package/dist/cli/commands/dev-seeds.js +292 -0
  23. package/dist/cli/commands/dev-seeds.js.map +1 -0
  24. package/dist/cli/commands/dev.d.ts +19 -0
  25. package/dist/cli/commands/dev.d.ts.map +1 -1
  26. package/dist/cli/commands/dev.js +476 -117
  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 +13 -0
  33. package/dist/cli/commands/init.d.ts.map +1 -1
  34. package/dist/cli/commands/init.js +2627 -1708
  35. package/dist/cli/commands/init.js.map +1 -1
  36. package/dist/cli/commands/scaffold.d.ts +3 -0
  37. package/dist/cli/commands/scaffold.d.ts.map +1 -1
  38. package/dist/cli/commands/scaffold.js +617 -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 +164 -42
  43. package/dist/cli/index.js.map +1 -1
  44. package/dist/core/config.d.ts +8 -2
  45. package/dist/core/config.d.ts.map +1 -1
  46. package/dist/core/config.js +90 -4
  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 +204 -0
  75. package/dist/core/project/validation.js.map +1 -0
  76. package/dist/core/scaffold/auth-generator.d.ts +38 -0
  77. package/dist/core/scaffold/auth-generator.d.ts.map +1 -0
  78. package/dist/core/scaffold/auth-generator.js +1244 -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 +1027 -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 +920 -213
  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 +329 -135
  91. package/dist/core/scaffold/model-parser.js.map +1 -1
  92. package/dist/core/scaffold/nextjs-generator.d.ts +8 -0
  93. package/dist/core/scaffold/nextjs-generator.d.ts.map +1 -1
  94. package/dist/core/scaffold/nextjs-generator.js +314 -182
  95. package/dist/core/scaffold/nextjs-generator.js.map +1 -1
  96. package/dist/core/scaffold/openapi-generator.d.ts +3 -0
  97. package/dist/core/scaffold/openapi-generator.d.ts.map +1 -0
  98. package/dist/core/scaffold/openapi-generator.js +190 -0
  99. package/dist/core/scaffold/openapi-generator.js.map +1 -0
  100. package/dist/core/scaffold/ui-generator.d.ts +10 -4
  101. package/dist/core/scaffold/ui-generator.d.ts.map +1 -1
  102. package/dist/core/scaffold/ui-generator.js +768 -663
  103. package/dist/core/scaffold/ui-generator.js.map +1 -1
  104. package/dist/database/base-model.d.ts +3 -3
  105. package/dist/database/base-model.js +3 -3
  106. package/dist/index.d.ts +2 -2
  107. package/dist/index.d.ts.map +1 -1
  108. package/dist/index.js +2 -1
  109. package/dist/index.js.map +1 -1
  110. package/dist/machine/contracts.d.ts +16 -0
  111. package/dist/machine/contracts.d.ts.map +1 -0
  112. package/dist/machine/contracts.js +3 -0
  113. package/dist/machine/contracts.js.map +1 -0
  114. package/dist/machine/errors.d.ts +11 -0
  115. package/dist/machine/errors.d.ts.map +1 -0
  116. package/dist/machine/errors.js +34 -0
  117. package/dist/machine/errors.js.map +1 -0
  118. package/dist/machine/index.d.ts +3 -0
  119. package/dist/machine/index.d.ts.map +1 -0
  120. package/dist/machine/index.js +156 -0
  121. package/dist/machine/index.js.map +1 -0
  122. package/dist/mcp/index.d.ts +25 -0
  123. package/dist/mcp/index.d.ts.map +1 -0
  124. package/dist/mcp/index.js +184 -0
  125. package/dist/mcp/index.js.map +1 -0
  126. package/dist/types/index.d.ts +65 -0
  127. package/dist/types/index.d.ts.map +1 -1
  128. package/dist/utils/package-manager.d.ts +109 -0
  129. package/dist/utils/package-manager.d.ts.map +1 -0
  130. package/dist/utils/package-manager.js +215 -0
  131. package/dist/utils/package-manager.js.map +1 -0
  132. package/package.json +85 -73
  133. package/src/__tests__/__snapshots__/functions-generator.test.ts.snap +1139 -0
  134. package/src/__tests__/__snapshots__/nextjs-generator.test.ts.snap +194 -0
  135. package/src/__tests__/__snapshots__/ui-generator.test.ts.snap +532 -0
  136. package/src/__tests__/auth.test.ts +654 -0
  137. package/src/__tests__/config.test.ts +263 -0
  138. package/src/__tests__/connector-functions-generator.test.ts +288 -0
  139. package/src/__tests__/connector-mock-server.test.ts +439 -0
  140. package/src/__tests__/connector-model-bff.test.ts +162 -0
  141. package/src/__tests__/dev-seeds.test.ts +112 -0
  142. package/src/__tests__/dev.test.ts +154 -0
  143. package/src/__tests__/fixtures.ts +144 -0
  144. package/src/__tests__/functions-generator.test.ts +237 -0
  145. package/src/__tests__/init.test.ts +80 -0
  146. package/src/__tests__/machine.test.ts +212 -0
  147. package/src/__tests__/mcp.test.ts +56 -0
  148. package/src/__tests__/model-parser.test.ts +72 -0
  149. package/src/__tests__/nextjs-generator.test.ts +97 -0
  150. package/src/__tests__/openapi-generator.test.ts +43 -0
  151. package/src/__tests__/package-manager.test.ts +189 -0
  152. package/src/__tests__/scaffold.test.ts +39 -0
  153. package/src/__tests__/string-utils.test.ts +75 -0
  154. package/src/__tests__/ui-generator.test.ts +144 -0
  155. package/src/__tests__/zod-mock-generator.test.ts +132 -0
  156. package/src/cli/commands/add-auth.ts +500 -0
  157. package/src/cli/commands/add-connector.ts +158 -0
  158. package/src/cli/commands/create-model.ts +62 -0
  159. package/src/cli/commands/dev-seeds.ts +358 -0
  160. package/src/cli/commands/dev.ts +962 -0
  161. package/src/cli/commands/index.ts +9 -0
  162. package/src/cli/commands/init.ts +3371 -0
  163. package/src/cli/commands/provision.ts +193 -0
  164. package/src/cli/commands/scaffold.ts +1211 -0
  165. package/src/cli/index.ts +193 -0
  166. package/src/core/config.ts +308 -0
  167. package/src/core/mock/connector-mock-server.ts +555 -0
  168. package/src/core/mock/zod-mock-generator.ts +205 -0
  169. package/src/core/operations/create-model.ts +174 -0
  170. package/src/core/operations/runtime.ts +235 -0
  171. package/src/core/operations/scaffold-machine.ts +91 -0
  172. package/src/core/project/manifest.ts +402 -0
  173. package/src/core/project/validation.ts +221 -0
  174. package/src/core/scaffold/auth-generator.ts +1284 -0
  175. package/src/core/scaffold/connector-functions-generator.ts +1128 -0
  176. package/src/core/scaffold/functions-generator.ts +970 -0
  177. package/src/core/scaffold/model-parser.ts +841 -0
  178. package/src/core/scaffold/nextjs-generator.ts +370 -0
  179. package/src/core/scaffold/openapi-generator.ts +212 -0
  180. package/src/core/scaffold/ui-generator.ts +1061 -0
  181. package/src/database/base-model.ts +184 -0
  182. package/src/database/client.ts +140 -0
  183. package/src/database/repository.ts +104 -0
  184. package/src/database/runtime-check.ts +25 -0
  185. package/src/index.ts +27 -0
  186. package/src/machine/contracts.ts +17 -0
  187. package/src/machine/errors.ts +34 -0
  188. package/src/machine/index.ts +173 -0
  189. package/src/mcp/index.ts +185 -0
  190. package/src/types/index.ts +134 -0
  191. package/src/utils/package-manager.ts +229 -0
@@ -34,18 +34,76 @@ 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.buildNextDevArgs = buildNextDevArgs;
39
+ exports.getPythonVirtualEnvPaths = getPythonVirtualEnvPaths;
40
+ exports.buildPythonFunctionsEnv = buildPythonFunctionsEnv;
41
+ exports.buildDevCommand = buildDevCommand;
37
42
  const commander_1 = require("commander");
38
43
  const child_process_1 = require("child_process");
39
44
  const path = __importStar(require("path"));
40
45
  const fs = __importStar(require("fs"));
46
+ const os = __importStar(require("os"));
47
+ const net = __importStar(require("net"));
41
48
  const cosmos_1 = require("@azure/cosmos");
42
49
  const config_1 = require("../../core/config");
50
+ const dev_seeds_1 = require("./dev-seeds");
51
+ const package_manager_1 = require("../../utils/package-manager");
52
+ const connector_mock_server_1 = require("../../core/mock/connector-mock-server");
53
+ function normalizeParsedDevOptions(options) {
54
+ return {
55
+ ...options,
56
+ noFunctions: options.noFunctions ?? options.functions === false,
57
+ };
58
+ }
59
+ function buildFunctionsStartArgs(functionsPort) {
60
+ return ['start', '--port', functionsPort];
61
+ }
62
+ function buildNextDevArgs(pm, port) {
63
+ const baseArgs = ['next', 'dev', '--port', port, '--webpack'];
64
+ return pm === 'pnpm' ? ['exec', ...baseArgs] : baseArgs;
65
+ }
66
+ function getPythonVirtualEnvPaths(functionsDir) {
67
+ const venvDir = path.join(functionsDir, '.venv');
68
+ const binDir = process.platform === 'win32'
69
+ ? path.join(venvDir, 'Scripts')
70
+ : path.join(venvDir, 'bin');
71
+ const pythonExecutable = process.platform === 'win32'
72
+ ? path.join(binDir, 'python.exe')
73
+ : path.join(binDir, 'python');
74
+ return { venvDir, binDir, pythonExecutable };
75
+ }
76
+ function buildPythonFunctionsEnv(baseEnv, functionsDir) {
77
+ const { venvDir, binDir, pythonExecutable } = getPythonVirtualEnvPaths(functionsDir);
78
+ const pathKey = getPathEnvKey(baseEnv);
79
+ const currentPath = baseEnv[pathKey] || '';
80
+ return {
81
+ ...baseEnv,
82
+ [pathKey]: currentPath ? `${binDir}${path.delimiter}${currentPath}` : binDir,
83
+ VIRTUAL_ENV: venvDir,
84
+ languageWorkers__python__defaultExecutablePath: pythonExecutable,
85
+ };
86
+ }
87
+ function getPathEnvKey(env) {
88
+ return Object.keys(env).find((key) => key.toUpperCase() === 'PATH') || 'PATH';
89
+ }
90
+ function prependToPathEnv(env, entry) {
91
+ const pathKey = getPathEnvKey(env);
92
+ const currentPath = env[pathKey] || '';
93
+ return {
94
+ ...env,
95
+ [pathKey]: currentPath ? `${entry}${path.delimiter}${currentPath}` : entry,
96
+ };
97
+ }
43
98
  /**
44
99
  * Check if Azure Functions Core Tools is installed
45
100
  */
46
101
  async function checkCoreTools() {
102
+ return checkCommand('func', ['--version']);
103
+ }
104
+ async function checkCommand(command, args = ['--version']) {
47
105
  return new Promise((resolve) => {
48
- const checkProcess = (0, child_process_1.spawn)('func', ['--version'], {
106
+ const checkProcess = (0, child_process_1.spawn)(command, args, {
49
107
  shell: true,
50
108
  stdio: 'pipe',
51
109
  });
@@ -57,12 +115,136 @@ async function checkCoreTools() {
57
115
  });
58
116
  });
59
117
  }
118
+ async function resolvePythonBootstrapCommand() {
119
+ const candidates = process.platform === 'win32'
120
+ ? [
121
+ { command: 'py', argsPrefix: ['-3.11'], label: 'py -3.11' },
122
+ { command: 'python', argsPrefix: [], label: 'python' },
123
+ ]
124
+ : [
125
+ { command: 'python3', argsPrefix: [], label: 'python3' },
126
+ { command: 'python', argsPrefix: [], label: 'python' },
127
+ ];
128
+ for (const candidate of candidates) {
129
+ if (await checkCommand(candidate.command, [...candidate.argsPrefix, '--version'])) {
130
+ return candidate;
131
+ }
132
+ }
133
+ throw new Error('Python 3.11 was not found. Install Python 3.11 and make sure `python`, `python3`, or `py -3.11` is available.');
134
+ }
135
+ async function getCommandPath(command) {
136
+ const locator = process.platform === 'win32' ? 'where' : 'which';
137
+ const result = await captureCommandOutput(locator, [command]);
138
+ const firstLine = result
139
+ .split(/\r?\n/)
140
+ .map((line) => line.trim())
141
+ .find(Boolean);
142
+ return firstLine || null;
143
+ }
144
+ async function captureCommandOutput(command, args, cwd, env) {
145
+ return new Promise((resolve, reject) => {
146
+ const child = (0, child_process_1.spawn)(command, args, {
147
+ cwd,
148
+ env,
149
+ shell: false,
150
+ stdio: ['ignore', 'pipe', 'pipe'],
151
+ });
152
+ let stdout = '';
153
+ let stderr = '';
154
+ child.stdout?.on('data', (data) => {
155
+ stdout += data.toString();
156
+ });
157
+ child.stderr?.on('data', (data) => {
158
+ stderr += data.toString();
159
+ });
160
+ child.on('close', (code) => {
161
+ if (code === 0) {
162
+ resolve(stdout);
163
+ }
164
+ else {
165
+ reject(new Error(stderr || stdout || `${command} exited with code ${code}`));
166
+ }
167
+ });
168
+ child.on('error', reject);
169
+ });
170
+ }
171
+ async function resolvePythonRuntimeDetails(functionsDir, env) {
172
+ const { pythonExecutable } = getPythonVirtualEnvPaths(functionsDir);
173
+ const output = await captureCommandOutput(pythonExecutable, [
174
+ '-c',
175
+ 'import platform; import sys; print(str(sys.version_info.major) + "." + str(sys.version_info.minor)); print(platform.machine())',
176
+ ], functionsDir, env);
177
+ const [version, architecture] = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
178
+ if (!version || !architecture) {
179
+ throw new Error('Failed to determine Python runtime details.');
180
+ }
181
+ return { version, architecture };
182
+ }
183
+ async function bridgePythonCoreToolsForWindowsArm64(functionsDir, env) {
184
+ if (process.platform !== 'win32' || process.arch !== 'arm64') {
185
+ return env;
186
+ }
187
+ const funcPath = await getCommandPath('func');
188
+ if (!funcPath) {
189
+ return env;
190
+ }
191
+ const { version, architecture } = await resolvePythonRuntimeDetails(functionsDir, env);
192
+ if (architecture.toUpperCase() !== 'AMD64') {
193
+ return env;
194
+ }
195
+ const coreToolsRoot = path.dirname(funcPath);
196
+ const armWorkerDir = path.join(coreToolsRoot, 'workers', 'python', version, 'WINDOWS', 'Arm64');
197
+ if (fs.existsSync(armWorkerDir)) {
198
+ return env;
199
+ }
200
+ const x64WorkerDir = path.join(coreToolsRoot, 'workers', 'python', version, 'WINDOWS', 'X64');
201
+ if (!fs.existsSync(x64WorkerDir)) {
202
+ return env;
203
+ }
204
+ const patchedRoot = path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'), 'SwallowKit', 'azure-functions-core-tools-python-bridge');
205
+ if (!fs.existsSync(path.join(patchedRoot, 'func.exe'))) {
206
+ console.log('🩹 Creating a local Azure Functions Core Tools bridge for Windows Arm64 Python...');
207
+ fs.mkdirSync(path.dirname(patchedRoot), { recursive: true });
208
+ fs.cpSync(coreToolsRoot, patchedRoot, { recursive: true });
209
+ }
210
+ const patchedArmWorkerDir = path.join(patchedRoot, 'workers', 'python', version, 'WINDOWS', 'Arm64');
211
+ const patchedX64WorkerDir = path.join(patchedRoot, 'workers', 'python', version, 'WINDOWS', 'X64');
212
+ if (!fs.existsSync(patchedArmWorkerDir) && fs.existsSync(patchedX64WorkerDir)) {
213
+ fs.cpSync(patchedX64WorkerDir, patchedArmWorkerDir, { recursive: true });
214
+ }
215
+ console.log(`🩹 Using bridged Azure Functions Core Tools from ${patchedRoot}`);
216
+ return prependToPathEnv(env, patchedRoot);
217
+ }
218
+ async function preparePythonFunctionsEnvironment(functionsDir) {
219
+ const { pythonExecutable } = getPythonVirtualEnvPaths(functionsDir);
220
+ const hasUv = await checkCommand('uv', ['--version']);
221
+ if (!fs.existsSync(pythonExecutable)) {
222
+ if (hasUv) {
223
+ console.log('šŸ“¦ Creating Python virtual environment with uv...');
224
+ await runCommand('uv', ['venv', '.venv', '--python', '3.11'], functionsDir, 'python virtual environment setup');
225
+ }
226
+ else {
227
+ const bootstrap = await resolvePythonBootstrapCommand();
228
+ console.log(`šŸ“¦ Creating Python virtual environment with ${bootstrap.label}...`);
229
+ await runCommand(bootstrap.command, [...bootstrap.argsPrefix, '-m', 'venv', '.venv'], functionsDir, 'python virtual environment setup');
230
+ }
231
+ }
232
+ const pythonEnv = buildPythonFunctionsEnv(process.env, functionsDir);
233
+ console.log(`šŸ“¦ Installing Python Azure Functions dependencies${hasUv ? ' with uv' : ''}...`);
234
+ if (hasUv) {
235
+ await runCommand('uv', ['pip', 'install', '--python', pythonExecutable, '-r', 'requirements.txt'], functionsDir, 'python dependency installation', pythonEnv);
236
+ }
237
+ else {
238
+ await runCommand('python', ['-m', 'pip', 'install', '--upgrade', 'pip'], functionsDir, 'python pip upgrade', pythonEnv);
239
+ await runCommand('python', ['-m', 'pip', 'install', '-r', 'requirements.txt'], functionsDir, 'python dependency installation', pythonEnv);
240
+ }
241
+ return bridgePythonCoreToolsForWindowsArm64(functionsDir, pythonEnv);
242
+ }
60
243
  /**
61
244
  * Check if Cosmos DB Emulator is running by checking if port 8081 is open
62
245
  */
63
246
  async function checkCosmosDBEmulator() {
64
247
  return new Promise((resolve) => {
65
- const net = require('net');
66
248
  const socket = new net.Socket();
67
249
  const timeout = setTimeout(() => {
68
250
  socket.destroy();
@@ -80,24 +262,30 @@ async function checkCosmosDBEmulator() {
80
262
  socket.connect(8081, 'localhost');
81
263
  });
82
264
  }
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
- });
265
+ function buildDevCommand(runDevEnvironment = startDevEnvironment, verifyProject = config_1.ensureSwallowKitProject) {
266
+ return new commander_1.Command()
267
+ .name('dev')
268
+ .description('Start SwallowKit development server (Cosmos DB + Next.js + Azure Functions)')
269
+ .option('-p, --port <port>', 'Next.js port', '3000')
270
+ .option('-f, --functions-port <port>', 'Azure Functions port', '7071')
271
+ .option('--host <host>', 'Host name', 'localhost')
272
+ .option('--open', 'Open browser automatically', false)
273
+ .option('--verbose', 'Show verbose logs', false)
274
+ .option('--no-functions', 'Skip Azure Functions startup', false)
275
+ .option('--seed-env <environment>', 'Replace Cosmos DB Emulator data from dev-seeds/<environment> before startup')
276
+ .option('--mock-connectors', 'Start mock server for connector models (serves Zod-generated data)', false)
277
+ .action(async (options) => {
278
+ const normalizedOptions = normalizeParsedDevOptions(options);
279
+ // SwallowKit ćƒ—ćƒ­ć‚øć‚§ć‚Æćƒˆćƒ‡ć‚£ćƒ¬ć‚ÆćƒˆćƒŖć‹ć©ć†ć‹ć‚’ę¤œčØ¼
280
+ verifyProject("dev");
281
+ console.log('šŸš€ Starting SwallowKit development environment...');
282
+ if (normalizedOptions.verbose) {
283
+ console.log('āš™ļø Options:', normalizedOptions);
284
+ }
285
+ await runDevEnvironment(normalizedOptions);
286
+ });
287
+ }
288
+ exports.devCommand = buildDevCommand();
101
289
  async function initializeCosmosDB(databaseName) {
102
290
  try {
103
291
  // Read local.settings.json from functions directory
@@ -105,14 +293,14 @@ async function initializeCosmosDB(databaseName) {
105
293
  const localSettingsPath = path.join(functionsDir, 'local.settings.json');
106
294
  if (!fs.existsSync(localSettingsPath)) {
107
295
  console.log('āš ļø local.settings.json not found. Skipping Cosmos DB initialization.');
108
- return;
296
+ return null;
109
297
  }
110
298
  const localSettings = JSON.parse(fs.readFileSync(localSettingsPath, 'utf-8'));
111
299
  const connectionString = localSettings.Values?.CosmosDBConnection;
112
300
  const dbName = localSettings.Values?.COSMOS_DB_DATABASE_NAME || databaseName;
113
301
  if (!connectionString) {
114
302
  console.log('āš ļø CosmosDBConnection not found in local.settings.json. Skipping Cosmos DB initialization.');
115
- return;
303
+ return null;
116
304
  }
117
305
  console.log('šŸ—„ļø Initializing Cosmos DB...');
118
306
  // Parse connection string
@@ -120,7 +308,7 @@ async function initializeCosmosDB(databaseName) {
120
308
  const keyMatch = connectionString.match(/AccountKey=([^;]+)/);
121
309
  if (!endpointMatch || !keyMatch) {
122
310
  console.log('āš ļø Invalid CosmosDB connection string format.');
123
- return;
311
+ return null;
124
312
  }
125
313
  const endpoint = endpointMatch[1];
126
314
  const client = new cosmos_1.CosmosClient({
@@ -130,62 +318,52 @@ async function initializeCosmosDB(databaseName) {
130
318
  // Create database if not exists
131
319
  const { database } = await client.databases.createIfNotExists({ id: dbName });
132
320
  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...`);
321
+ const models = await (0, dev_seeds_1.loadProjectModels)();
322
+ for (const model of models) {
323
+ const containerName = (0, dev_seeds_1.getContainerNameForModel)(model);
324
+ // Try creating container with full partition key definition first
325
+ let containerCreated = false;
326
+ try {
327
+ console.log(`šŸ”§ Creating container "${containerName}" with partition key ${model.partitionKey}...`);
328
+ const containerResponse = await database.containers.createIfNotExists({
329
+ id: containerName,
330
+ partitionKey: {
331
+ paths: [model.partitionKey],
332
+ kind: cosmos_1.PartitionKeyKind.Hash,
333
+ version: 2
164
334
  }
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
335
+ });
336
+ console.log(`āœ… Container "${containerName}" ready (status: ${containerResponse.statusCode})`);
337
+ containerCreated = true;
338
+ }
339
+ catch (error) {
340
+ const message = error instanceof Error ? error.message : String(error);
341
+ console.log(`āš ļø Failed with full partition key definition: ${message}`);
342
+ console.log(`šŸ”„ Retrying with simple partition key...`);
343
+ }
344
+ // If first attempt failed, try with simple partition key definition
345
+ if (!containerCreated) {
346
+ try {
347
+ const containerResponse = await database.containers.createIfNotExists({
348
+ id: containerName,
349
+ partitionKey: {
350
+ paths: [model.partitionKey]
183
351
  }
352
+ });
353
+ console.log(`āœ… Container "${containerName}" ready (status: ${containerResponse.statusCode})`);
354
+ }
355
+ catch (containerError) {
356
+ console.error(`āŒ Failed to create container "${containerName}":`, containerError.message);
357
+ console.error(`Error code: ${containerError.code}`);
358
+ if (containerError.body) {
359
+ console.error(`Response body:`, JSON.stringify(containerError.body, null, 2));
184
360
  }
361
+ // Continue with other containers
185
362
  }
186
363
  }
187
364
  }
188
365
  console.log('āœ… Cosmos DB initialization complete\n');
366
+ return { endpoint, key: keyMatch[1], databaseName: dbName, models };
189
367
  }
190
368
  catch (error) {
191
369
  console.error('āš ļø Cosmos DB initialization failed:', error.message);
@@ -193,16 +371,42 @@ async function initializeCosmosDB(databaseName) {
193
371
  console.error('Stack trace:', error.stack);
194
372
  }
195
373
  console.log('šŸ’” Make sure Cosmos DB Emulator is running');
374
+ return null;
196
375
  }
197
376
  }
198
377
  async function startDevEnvironment(options) {
199
378
  const port = options.port || '3000';
200
379
  const functionsPort = options.functionsPort || '7071';
380
+ // Detect package manager from project lockfile
381
+ const pm = (0, package_manager_1.detectFromProject)();
382
+ const pmCmd = (0, package_manager_1.getCommands)(pm);
383
+ const backendLanguage = (0, config_1.getBackendLanguage)();
201
384
  // ćƒ—ćƒ­ć‚»ć‚¹ć‚’ē®”ē†ć™ć‚‹é…åˆ—
202
385
  const processes = [];
386
+ let functionsEnv = process.env;
387
+ let mockServer = null;
388
+ let envLocalPath = '';
389
+ let envLocalDefaultUrl = ''; // default Functions URL to restore on shutdown
203
390
  // Cleanup processes on Ctrl+C
204
- process.on('SIGINT', () => {
391
+ process.on('SIGINT', async () => {
205
392
  console.log('\nšŸ›‘ Stopping development servers...');
393
+ // Restore .env.local to default Functions port on shutdown
394
+ if (envLocalPath && envLocalDefaultUrl) {
395
+ try {
396
+ if (fs.existsSync(envLocalPath)) {
397
+ const content = fs.readFileSync(envLocalPath, 'utf-8');
398
+ if (content.includes('BACKEND_FUNCTIONS_BASE_URL=') &&
399
+ !content.includes(`BACKEND_FUNCTIONS_BASE_URL=${envLocalDefaultUrl}`)) {
400
+ const restored = content.replace(/^BACKEND_FUNCTIONS_BASE_URL=.*/m, `BACKEND_FUNCTIONS_BASE_URL=${envLocalDefaultUrl}`);
401
+ fs.writeFileSync(envLocalPath, restored, 'utf-8');
402
+ }
403
+ }
404
+ }
405
+ catch { /* ignore */ }
406
+ }
407
+ if (mockServer) {
408
+ await mockServer.stop();
409
+ }
206
410
  processes.forEach((proc) => {
207
411
  if (proc && !proc.killed) {
208
412
  proc.kill();
@@ -226,8 +430,7 @@ async function startDevEnvironment(options) {
226
430
  }
227
431
  // 2. Check if Azure Functions exists
228
432
  const functionsDir = path.join(process.cwd(), 'functions');
229
- const hasFunctions = fs.existsSync(functionsDir) &&
230
- fs.existsSync(path.join(functionsDir, 'package.json'));
433
+ const hasFunctions = fs.existsSync(functionsDir) && hasFunctionsProject(functionsDir, backendLanguage);
231
434
  if (hasFunctions && !options.noFunctions) {
232
435
  // Check if Azure Functions Core Tools is installed
233
436
  const coreToolsInstalled = await checkCoreTools();
@@ -248,7 +451,7 @@ async function startDevEnvironment(options) {
248
451
  if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
249
452
  console.log('šŸ“¦ Installing Azure Functions Core Tools...');
250
453
  console.log(' This may take a few minutes.');
251
- const installProcess = (0, child_process_1.spawn)('npm', ['install', '-g', 'azure-functions-core-tools@4'], {
454
+ 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
455
  shell: true,
253
456
  stdio: 'inherit',
254
457
  });
@@ -261,7 +464,7 @@ async function startDevEnvironment(options) {
261
464
  else {
262
465
  console.error('āŒ Installation failed.');
263
466
  console.log('šŸ’” Please install manually:');
264
- console.log(' npm install -g azure-functions-core-tools@4');
467
+ console.log(` ${pmCmd.addGlobal} azure-functions-core-tools@4`);
265
468
  reject(new Error(`Installation failed with code ${code}`));
266
469
  }
267
470
  });
@@ -272,7 +475,7 @@ async function startDevEnvironment(options) {
272
475
  console.log('');
273
476
  console.log('ā„¹ļø Skipping Azure Functions startup.');
274
477
  console.log('šŸ’” To install later:');
275
- console.log(' npm install -g azure-functions-core-tools@4');
478
+ console.log(` ${pmCmd.addGlobal} azure-functions-core-tools@4`);
276
479
  console.log('');
277
480
  // Skip Azure Functions startup
278
481
  options.noFunctions = true;
@@ -296,63 +499,88 @@ async function startDevEnvironment(options) {
296
499
  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
297
500
  const appName = packageJson.name || 'App';
298
501
  const databaseName = `${appName.charAt(0).toUpperCase() + appName.slice(1)}Database`;
299
- await initializeCosmosDB(databaseName);
502
+ const cosmosInitialization = await initializeCosmosDB(databaseName);
503
+ if (options.seedEnv && cosmosInitialization) {
504
+ await (0, dev_seeds_1.applyDevSeedEnvironment)({
505
+ client: new cosmos_1.CosmosClient({
506
+ endpoint: cosmosInitialization.endpoint,
507
+ key: cosmosInitialization.key,
508
+ }),
509
+ databaseName: cosmosInitialization.databaseName,
510
+ environment: options.seedEnv,
511
+ models: cosmosInitialization.models,
512
+ });
513
+ }
300
514
  console.log('');
301
515
  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,
516
+ if (backendLanguage === 'typescript') {
517
+ const functionsNodeModules = path.join(functionsDir, 'node_modules');
518
+ if (!fs.existsSync(functionsNodeModules)) {
519
+ console.log('šŸ“¦ Installing Azure Functions dependencies...');
520
+ await runCommand(pm, ['install'], functionsDir, `${pm} install`);
521
+ }
522
+ }
523
+ else if (backendLanguage === 'csharp') {
524
+ console.log('šŸ“¦ Building C# Azure Functions project...');
525
+ await runCommand('dotnet', ['build'], functionsDir, 'dotnet build');
526
+ }
527
+ else {
528
+ functionsEnv = await preparePythonFunctionsEnvironment(functionsDir);
529
+ }
530
+ }
531
+ }
532
+ if (hasFunctions && !options.noFunctions) {
533
+ // Build shared package before starting Functions
534
+ const sharedDir = path.join(process.cwd(), 'shared');
535
+ const sharedPkgPath = path.join(sharedDir, 'package.json');
536
+ if (fs.existsSync(sharedDir) && fs.existsSync(sharedPkgPath)) {
537
+ const sharedPkg = JSON.parse(fs.readFileSync(sharedPkgPath, 'utf-8'));
538
+ if (sharedPkg.scripts?.build) {
539
+ console.log('šŸ“¦ Building shared package...');
540
+ const filterArgs = pm === 'pnpm'
541
+ ? ['run', '--filter', 'shared', 'build']
542
+ : ['run', '--workspace=shared', 'build'];
543
+ const sharedBuild = (0, child_process_1.spawn)(pm, filterArgs, {
544
+ cwd: process.cwd(),
308
545
  shell: true,
309
546
  stdio: 'inherit',
310
547
  });
311
548
  await new Promise((resolve, reject) => {
312
- npmInstall.on('close', (code) => {
549
+ sharedBuild.on('close', (code) => {
313
550
  if (code === 0) {
551
+ console.log('āœ… Shared package built successfully');
314
552
  resolve();
315
553
  }
316
554
  else {
317
- reject(new Error(`npm install failed with code ${code}`));
555
+ reject(new Error(`Shared package build failed with code ${code}`));
318
556
  }
319
557
  });
320
- npmInstall.on('error', reject);
558
+ sharedBuild.on('error', reject);
321
559
  });
322
560
  }
561
+ else {
562
+ console.log('āš ļø Shared package has no build script — skipping build. Run "swallowkit add-auth" to fix.');
563
+ }
323
564
  }
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
- });
565
+ // Build TypeScript functions after shared package (functions import from shared)
566
+ if (backendLanguage === 'typescript') {
567
+ const functionsPkgPath = path.join(functionsDir, 'package.json');
568
+ if (fs.existsSync(functionsPkgPath)) {
569
+ const functionsPkg = JSON.parse(fs.readFileSync(functionsPkgPath, 'utf-8'));
570
+ if (functionsPkg.scripts?.build) {
571
+ console.log('šŸ“¦ Building TypeScript Azure Functions...');
572
+ await runCommand(pm, ['run', 'build'], functionsDir, `${pm} run build`);
573
+ }
574
+ }
347
575
  }
348
576
  // Azure Functions ć‚’čµ·å‹•
349
- const funcEnv = { ...process.env, FUNCTIONS_PORT: functionsPort };
350
- const funcProcess = (0, child_process_1.spawn)('npm', ['start'], {
577
+ const funcProcess = (0, child_process_1.spawn)('func', buildFunctionsStartArgs(functionsPort), {
351
578
  cwd: functionsDir,
352
579
  shell: true,
353
580
  stdio: 'pipe', // Always pipe to capture output
354
- env: funcEnv
581
+ env: functionsEnv
355
582
  });
583
+ let pythonWorkerMissing = false;
356
584
  // Functions ć®å‡ŗåŠ›ć‚’ćć®ć¾ć¾č”Øē¤ŗļ¼ˆćƒ—ćƒ¬ćƒ•ć‚£ćƒƒć‚Æć‚¹ä»˜ćļ¼‰
357
585
  if (funcProcess.stdout) {
358
586
  funcProcess.stdout.on('data', (data) => {
@@ -360,6 +588,10 @@ async function startDevEnvironment(options) {
360
588
  // å„č”Œć«ćƒ—ćƒ¬ćƒ•ć‚£ćƒƒć‚Æć‚¹ć‚’ä»˜ć‘ć¦å‡ŗåŠ›
361
589
  const lines = output.split('\n').filter((line) => line.trim());
362
590
  lines.forEach((line) => {
591
+ if (backendLanguage === 'python' &&
592
+ (line.includes('WorkerConfig for runtime: python not found') || line.includes('DefaultWorkerPath:'))) {
593
+ pythonWorkerMissing = true;
594
+ }
363
595
  console.log(`[Functions] ${line}`);
364
596
  });
365
597
  });
@@ -369,6 +601,10 @@ async function startDevEnvironment(options) {
369
601
  const output = data.toString();
370
602
  const lines = output.split('\n').filter((line) => line.trim());
371
603
  lines.forEach((line) => {
604
+ if (backendLanguage === 'python' &&
605
+ (line.includes('WorkerConfig for runtime: python not found') || line.includes('DefaultWorkerPath:'))) {
606
+ pythonWorkerMissing = true;
607
+ }
372
608
  console.error(`[Functions Error] ${line}`);
373
609
  });
374
610
  });
@@ -377,11 +613,16 @@ async function startDevEnvironment(options) {
377
613
  funcProcess.on('error', (error) => {
378
614
  console.error('āš ļø Azure Functions startup error:', error.message);
379
615
  console.log('šŸ’” Please ensure Azure Functions Core Tools is installed');
380
- console.log(' npm install -g azure-functions-core-tools@4');
616
+ console.log(` ${pmCmd.addGlobal} azure-functions-core-tools@4`);
381
617
  });
382
618
  funcProcess.on('close', (code) => {
383
619
  if (code !== 0) {
384
620
  console.log(`\nā¹ļø Azure Functions exited (exit code: ${code})`);
621
+ if (backendLanguage === 'python' && pythonWorkerMissing) {
622
+ console.log('šŸ’” Your Azure Functions Core Tools installation is missing the Python worker for this OS/architecture.');
623
+ console.log(' Reinstall a matching Core Tools v4 package (Windows users should prefer the official x64/x86 MSI for their machine).');
624
+ console.log(' SwallowKit local Python dev uses functions/.venv and requirements.txt, but Core Tools still needs its own bundled Python worker.');
625
+ }
385
626
  }
386
627
  });
387
628
  console.log(`āœ… Azure Functions started (port: ${functionsPort})`);
@@ -396,8 +637,69 @@ async function startDevEnvironment(options) {
396
637
  }
397
638
  console.log('');
398
639
  console.log('šŸš€ Starting Next.js development server...');
640
+ // Mock connector server — start proxy if --mock-connectors is enabled
641
+ let bffTargetPort = functionsPort;
642
+ if (options.mockConnectors) {
643
+ const allModels = await (0, dev_seeds_1.loadProjectModels)();
644
+ const connectorModels = allModels.filter((m) => m.connectorConfig);
645
+ // Resolve auth config — auth functions use RDB connectors, mocked alongside other models
646
+ const authConfig = (0, config_1.getAuthConfig)();
647
+ let mockAuthConfig;
648
+ if (authConfig?.provider === 'custom-jwt' && authConfig.customJwt) {
649
+ // Read JWT_SECRET from functions/local.settings.json if available
650
+ let jwtSecret = 'dev-jwt-secret-change-in-production-min-32-chars!!';
651
+ try {
652
+ const localSettingsPath = path.join(process.cwd(), 'functions', 'local.settings.json');
653
+ if (fs.existsSync(localSettingsPath)) {
654
+ const localSettings = JSON.parse(fs.readFileSync(localSettingsPath, 'utf-8'));
655
+ jwtSecret = localSettings.Values?.[authConfig.customJwt.jwtSecretEnv || 'JWT_SECRET'] || jwtSecret;
656
+ }
657
+ }
658
+ catch { /* ignore */ }
659
+ mockAuthConfig = {
660
+ jwtSecret,
661
+ tokenExpiry: authConfig.customJwt.tokenExpiry,
662
+ customJwt: {
663
+ userTable: authConfig.customJwt.userTable,
664
+ loginIdColumn: authConfig.customJwt.loginIdColumn,
665
+ passwordHashColumn: authConfig.customJwt.passwordHashColumn,
666
+ rolesColumn: authConfig.customJwt.rolesColumn,
667
+ },
668
+ defaultPolicy: authConfig.authorization?.defaultPolicy,
669
+ };
670
+ }
671
+ if (connectorModels.length > 0 || mockAuthConfig) {
672
+ const mockPort = parseInt(functionsPort, 10) + 1;
673
+ mockServer = new connector_mock_server_1.ConnectorMockServer({
674
+ port: mockPort,
675
+ functionsTarget: `${options.host || 'localhost'}:${functionsPort}`,
676
+ connectorModels,
677
+ allModels,
678
+ seedEnv: options.seedEnv,
679
+ host: options.host || 'localhost',
680
+ authConfig: mockAuthConfig,
681
+ });
682
+ await mockServer.start();
683
+ bffTargetPort = String(mockPort);
684
+ const modelCount = connectorModels.length + (mockAuthConfig ? 1 : 0);
685
+ console.log('');
686
+ console.log(`šŸ”Œ Mock server started (port: ${mockPort}) — ${modelCount} model(s) mocked via Zod/seed data`);
687
+ for (const m of connectorModels) {
688
+ const ops = m.connectorConfig.operations.join(', ');
689
+ console.log(` - ${m.name} [${ops}]`);
690
+ }
691
+ if (mockAuthConfig) {
692
+ console.log(` - auth [login, me, logout]`);
693
+ }
694
+ console.log(` Other routes → proxied to Azure Functions (port: ${functionsPort})`);
695
+ }
696
+ else {
697
+ console.log('');
698
+ console.log('ā„¹ļø --mock-connectors specified but no connector models found. Skipping mock server.');
699
+ }
700
+ }
399
701
  // 5. Start Next.js development server
400
- const nextArgs = ['next', 'dev', '--port', port];
702
+ const nextArgs = buildNextDevArgs(pm, port);
401
703
  if (options.open) {
402
704
  // Next.js 14+ deprecated --open option, so we open browser manually
403
705
  setTimeout(() => {
@@ -408,10 +710,33 @@ async function startDevEnvironment(options) {
408
710
  (0, child_process_1.spawn)(start, [url], { shell: true });
409
711
  }, 3000);
410
712
  }
411
- const nextProcess = (0, child_process_1.spawn)('npx', nextArgs, {
713
+ // Ensure .env.local points to bffTargetPort so Next.js reads the correct backend URL.
714
+ // When --mock-connectors is active, bffTargetPort = mock port (7072); otherwise = Functions port (7071).
715
+ // Next.js may load .env.local values that override spawn env vars, so we must keep them in sync.
716
+ envLocalPath = path.join(process.cwd(), '.env.local');
717
+ envLocalDefaultUrl = `http://${options.host || 'localhost'}:${functionsPort}`;
718
+ const bffTargetUrl = `http://${options.host || 'localhost'}:${bffTargetPort}`;
719
+ try {
720
+ if (fs.existsSync(envLocalPath)) {
721
+ const envContent = fs.readFileSync(envLocalPath, 'utf-8');
722
+ if (envContent.includes('BACKEND_FUNCTIONS_BASE_URL=') &&
723
+ !envContent.includes(`BACKEND_FUNCTIONS_BASE_URL=${bffTargetUrl}`)) {
724
+ const updated = envContent.replace(/^BACKEND_FUNCTIONS_BASE_URL=.*/m, `BACKEND_FUNCTIONS_BASE_URL=${bffTargetUrl}`);
725
+ fs.writeFileSync(envLocalPath, updated, 'utf-8');
726
+ }
727
+ }
728
+ }
729
+ catch { /* ignore */ }
730
+ const nextEnv = {
731
+ ...process.env,
732
+ BACKEND_FUNCTIONS_BASE_URL: `http://${options.host || 'localhost'}:${bffTargetPort}`,
733
+ FUNCTIONS_BASE_URL: `http://${options.host || 'localhost'}:${bffTargetPort}`,
734
+ };
735
+ const nextProcess = (0, child_process_1.spawn)(pm === 'pnpm' ? 'pnpm' : 'npx', nextArgs, {
412
736
  cwd: process.cwd(),
413
737
  shell: true,
414
738
  stdio: options.verbose ? 'inherit' : 'inherit',
739
+ env: nextEnv,
415
740
  });
416
741
  processes.push(nextProcess);
417
742
  nextProcess.on('error', (error) => {
@@ -423,6 +748,9 @@ async function startDevEnvironment(options) {
423
748
  console.log(`\nā¹ļø Next.js exited (exit code: ${code})`);
424
749
  }
425
750
  // Exit all processes when Next.js exits
751
+ if (mockServer) {
752
+ mockServer.stop().catch(() => { });
753
+ }
426
754
  processes.forEach((proc) => {
427
755
  if (proc && !proc.killed) {
428
756
  proc.kill();
@@ -437,10 +765,16 @@ async function startDevEnvironment(options) {
437
765
  if (hasFunctions && !options.noFunctions) {
438
766
  console.log(`⚔ Azure Functions: http://${options.host || 'localhost'}:${functionsPort}`);
439
767
  }
768
+ if (mockServer) {
769
+ console.log(`šŸ”Œ Mock Proxy: http://${options.host || 'localhost'}:${bffTargetPort} (BFF → here)`);
770
+ }
440
771
  console.log('');
441
772
  if (hasFunctions && !options.noFunctions) {
442
773
  console.log('šŸ’” Azure Functions and Next.js BFF are connected');
443
774
  }
775
+ if (mockServer) {
776
+ console.log('šŸ’” Connector models served from mock server (Zod-generated data)');
777
+ }
444
778
  console.log('');
445
779
  console.log('šŸ›‘ Press Ctrl+C to stop');
446
780
  console.log('');
@@ -455,4 +789,29 @@ async function startDevEnvironment(options) {
455
789
  process.exit(1);
456
790
  }
457
791
  }
792
+ async function runCommand(command, args, cwd, label, env) {
793
+ await new Promise((resolve, reject) => {
794
+ const child = (0, child_process_1.spawn)(command, args, {
795
+ cwd,
796
+ shell: true,
797
+ stdio: 'inherit',
798
+ env,
799
+ });
800
+ child.on('close', (code) => {
801
+ if (code === 0) {
802
+ resolve();
803
+ }
804
+ else {
805
+ reject(new Error(`${label} failed with code ${code}`));
806
+ }
807
+ });
808
+ child.on('error', reject);
809
+ });
810
+ }
811
+ function hasFunctionsProject(functionsDir, backendLanguage) {
812
+ if (backendLanguage === 'typescript') {
813
+ return fs.existsSync(path.join(functionsDir, 'package.json'));
814
+ }
815
+ return fs.existsSync(path.join(functionsDir, 'host.json'));
816
+ }
458
817
  //# sourceMappingURL=dev.js.map