swallowkit 1.1.2 → 2.0.0-alpha.10

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 (262) hide show
  1. package/README.ja.md +61 -7
  2. package/README.md +61 -7
  3. package/dist/cli/commands/add-auth.d.ts.map +1 -1
  4. package/dist/cli/commands/add-auth.js +103 -27
  5. package/dist/cli/commands/add-auth.js.map +1 -1
  6. package/dist/cli/commands/configure-mcp.d.ts +12 -0
  7. package/dist/cli/commands/configure-mcp.d.ts.map +1 -0
  8. package/dist/cli/commands/configure-mcp.js +42 -0
  9. package/dist/cli/commands/configure-mcp.js.map +1 -0
  10. package/dist/cli/commands/dev.d.ts +5 -2
  11. package/dist/cli/commands/dev.d.ts.map +1 -1
  12. package/dist/cli/commands/dev.js +143 -67
  13. package/dist/cli/commands/dev.js.map +1 -1
  14. package/dist/cli/commands/init.d.ts +14 -7
  15. package/dist/cli/commands/init.d.ts.map +1 -1
  16. package/dist/cli/commands/init.js +149 -170
  17. package/dist/cli/commands/init.js.map +1 -1
  18. package/dist/cli/commands/migrations.d.ts.map +1 -1
  19. package/dist/cli/commands/migrations.js +25 -0
  20. package/dist/cli/commands/migrations.js.map +1 -1
  21. package/dist/cli/commands/provision.d.ts +5 -0
  22. package/dist/cli/commands/provision.d.ts.map +1 -1
  23. package/dist/cli/commands/provision.js +278 -55
  24. package/dist/cli/commands/provision.js.map +1 -1
  25. package/dist/cli/commands/scaffold.d.ts +5 -1
  26. package/dist/cli/commands/scaffold.d.ts.map +1 -1
  27. package/dist/cli/commands/scaffold.js +123 -18
  28. package/dist/cli/commands/scaffold.js.map +1 -1
  29. package/dist/cli/index.d.ts +2 -0
  30. package/dist/cli/index.d.ts.map +1 -1
  31. package/dist/cli/index.js +49 -8
  32. package/dist/cli/index.js.map +1 -1
  33. package/dist/core/config.d.ts +2 -1
  34. package/dist/core/config.d.ts.map +1 -1
  35. package/dist/core/config.js +19 -0
  36. package/dist/core/config.js.map +1 -1
  37. package/dist/core/operations/create-model.d.ts +2 -0
  38. package/dist/core/operations/create-model.d.ts.map +1 -1
  39. package/dist/core/operations/create-model.js +60 -8
  40. package/dist/core/operations/create-model.js.map +1 -1
  41. package/dist/core/operations/migration-generator.d.ts.map +1 -1
  42. package/dist/core/operations/migration-generator.js +12 -1
  43. package/dist/core/operations/migration-generator.js.map +1 -1
  44. package/dist/core/operations/model-migration.d.ts +22 -0
  45. package/dist/core/operations/model-migration.d.ts.map +1 -0
  46. package/dist/core/operations/model-migration.js +36 -0
  47. package/dist/core/operations/model-migration.js.map +1 -0
  48. package/dist/core/operations/provision-deployment.d.ts +52 -0
  49. package/dist/core/operations/provision-deployment.d.ts.map +1 -0
  50. package/dist/core/operations/provision-deployment.js +164 -0
  51. package/dist/core/operations/provision-deployment.js.map +1 -0
  52. package/dist/core/operations/provision-operations.d.ts +62 -6
  53. package/dist/core/operations/provision-operations.d.ts.map +1 -1
  54. package/dist/core/operations/provision-operations.js +653 -115
  55. package/dist/core/operations/provision-operations.js.map +1 -1
  56. package/dist/core/operations/scaffold-apply.d.ts +1 -0
  57. package/dist/core/operations/scaffold-apply.d.ts.map +1 -1
  58. package/dist/core/operations/scaffold-apply.js +1 -0
  59. package/dist/core/operations/scaffold-apply.js.map +1 -1
  60. package/dist/core/operations/scaffold-machine.d.ts +1 -0
  61. package/dist/core/operations/scaffold-machine.d.ts.map +1 -1
  62. package/dist/core/operations/scaffold-machine.js.map +1 -1
  63. package/dist/core/operations/scaffold-plan.d.ts +2 -0
  64. package/dist/core/operations/scaffold-plan.d.ts.map +1 -1
  65. package/dist/core/operations/scaffold-plan.js +3 -1
  66. package/dist/core/operations/scaffold-plan.js.map +1 -1
  67. package/dist/core/project/agent-instructions.d.ts +2 -1
  68. package/dist/core/project/agent-instructions.d.ts.map +1 -1
  69. package/dist/core/project/agent-instructions.js +15 -0
  70. package/dist/core/project/agent-instructions.js.map +1 -1
  71. package/dist/core/project/boundaries.d.ts +5 -2
  72. package/dist/core/project/boundaries.d.ts.map +1 -1
  73. package/dist/core/project/boundaries.js +51 -20
  74. package/dist/core/project/boundaries.js.map +1 -1
  75. package/dist/core/project/capabilities.d.ts +17 -0
  76. package/dist/core/project/capabilities.d.ts.map +1 -1
  77. package/dist/core/project/capabilities.js +45 -6
  78. package/dist/core/project/capabilities.js.map +1 -1
  79. package/dist/core/project/home-page.d.ts +4 -0
  80. package/dist/core/project/home-page.d.ts.map +1 -0
  81. package/dist/core/project/home-page.js +113 -0
  82. package/dist/core/project/home-page.js.map +1 -0
  83. package/dist/core/project/manifest.d.ts +3 -1
  84. package/dist/core/project/manifest.d.ts.map +1 -1
  85. package/dist/core/project/manifest.js +47 -25
  86. package/dist/core/project/manifest.js.map +1 -1
  87. package/dist/core/project/mcp-config.d.ts +26 -0
  88. package/dist/core/project/mcp-config.d.ts.map +1 -0
  89. package/dist/core/project/mcp-config.js +117 -0
  90. package/dist/core/project/mcp-config.js.map +1 -0
  91. package/dist/core/project/react-project.d.ts +26 -0
  92. package/dist/core/project/react-project.d.ts.map +1 -0
  93. package/dist/core/project/react-project.js +443 -0
  94. package/dist/core/project/react-project.js.map +1 -0
  95. package/dist/core/project/state.d.ts +7 -0
  96. package/dist/core/project/state.d.ts.map +1 -1
  97. package/dist/core/project/state.js +86 -0
  98. package/dist/core/project/state.js.map +1 -1
  99. package/dist/core/project/validation.d.ts.map +1 -1
  100. package/dist/core/project/validation.js +32 -10
  101. package/dist/core/project/validation.js.map +1 -1
  102. package/dist/core/project/workflows.d.ts.map +1 -1
  103. package/dist/core/project/workflows.js +53 -7
  104. package/dist/core/project/workflows.js.map +1 -1
  105. package/dist/core/scaffold/auth-generator.d.ts.map +1 -1
  106. package/dist/core/scaffold/auth-generator.js +26 -27
  107. package/dist/core/scaffold/auth-generator.js.map +1 -1
  108. package/dist/core/scaffold/model-parser.d.ts +23 -0
  109. package/dist/core/scaffold/model-parser.d.ts.map +1 -1
  110. package/dist/core/scaffold/model-parser.js +407 -137
  111. package/dist/core/scaffold/model-parser.js.map +1 -1
  112. package/dist/core/scaffold/native-schema-generator.d.ts +13 -0
  113. package/dist/core/scaffold/native-schema-generator.d.ts.map +1 -1
  114. package/dist/core/scaffold/native-schema-generator.js +294 -91
  115. package/dist/core/scaffold/native-schema-generator.js.map +1 -1
  116. package/dist/core/scaffold/openapi-generator.d.ts +5 -0
  117. package/dist/core/scaffold/openapi-generator.d.ts.map +1 -1
  118. package/dist/core/scaffold/openapi-generator.js +53 -3
  119. package/dist/core/scaffold/openapi-generator.js.map +1 -1
  120. package/dist/core/scaffold/react-bff-generator.d.ts +10 -0
  121. package/dist/core/scaffold/react-bff-generator.d.ts.map +1 -0
  122. package/dist/core/scaffold/react-bff-generator.js +252 -0
  123. package/dist/core/scaffold/react-bff-generator.js.map +1 -0
  124. package/dist/core/scaffold/react-ui-generator.d.ts +6 -0
  125. package/dist/core/scaffold/react-ui-generator.d.ts.map +1 -0
  126. package/dist/core/scaffold/react-ui-generator.js +41 -0
  127. package/dist/core/scaffold/react-ui-generator.js.map +1 -0
  128. package/dist/core/scaffold/ui-generator.d.ts +0 -3
  129. package/dist/core/scaffold/ui-generator.d.ts.map +1 -1
  130. package/dist/core/scaffold/ui-generator.js +68 -7
  131. package/dist/core/scaffold/ui-generator.js.map +1 -1
  132. package/dist/core/verify/index.d.ts +4 -3
  133. package/dist/core/verify/index.d.ts.map +1 -1
  134. package/dist/core/verify/index.js +122 -5
  135. package/dist/core/verify/index.js.map +1 -1
  136. package/dist/index.d.ts +3 -3
  137. package/dist/index.d.ts.map +1 -1
  138. package/dist/index.js +3 -2
  139. package/dist/index.js.map +1 -1
  140. package/dist/machine/contracts.d.ts +1 -0
  141. package/dist/machine/contracts.d.ts.map +1 -1
  142. package/dist/machine/errors.d.ts +3 -2
  143. package/dist/machine/errors.d.ts.map +1 -1
  144. package/dist/machine/errors.js +2 -1
  145. package/dist/machine/errors.js.map +1 -1
  146. package/dist/machine/index.d.ts.map +1 -1
  147. package/dist/machine/index.js +61 -30
  148. package/dist/machine/index.js.map +1 -1
  149. package/dist/mcp/index.d.ts +6 -4
  150. package/dist/mcp/index.d.ts.map +1 -1
  151. package/dist/mcp/index.js +210 -25
  152. package/dist/mcp/index.js.map +1 -1
  153. package/dist/mcp/version-selection.d.ts +55 -0
  154. package/dist/mcp/version-selection.d.ts.map +1 -0
  155. package/dist/mcp/version-selection.js +152 -0
  156. package/dist/mcp/version-selection.js.map +1 -0
  157. package/dist/types/index.d.ts +4 -0
  158. package/dist/types/index.d.ts.map +1 -1
  159. package/dist/utils/azure-cli.d.ts +56 -0
  160. package/dist/utils/azure-cli.d.ts.map +1 -0
  161. package/dist/utils/azure-cli.js +326 -0
  162. package/dist/utils/azure-cli.js.map +1 -0
  163. package/dist/utils/azure-diagnostics.d.ts +16 -0
  164. package/dist/utils/azure-diagnostics.d.ts.map +1 -0
  165. package/dist/utils/azure-diagnostics.js +68 -0
  166. package/dist/utils/azure-diagnostics.js.map +1 -0
  167. package/dist/utils/package-manager.d.ts +2 -2
  168. package/dist/utils/package-manager.d.ts.map +1 -1
  169. package/dist/utils/package-manager.js +6 -3
  170. package/dist/utils/package-manager.js.map +1 -1
  171. package/dist/utils/python-uv.d.ts +5 -7
  172. package/dist/utils/python-uv.d.ts.map +1 -1
  173. package/dist/utils/python-uv.js +50 -24
  174. package/dist/utils/python-uv.js.map +1 -1
  175. package/dist/utils/react-dev.d.ts +4 -0
  176. package/dist/utils/react-dev.d.ts.map +1 -0
  177. package/dist/utils/react-dev.js +35 -0
  178. package/dist/utils/react-dev.js.map +1 -0
  179. package/dist/utils/semver.d.ts +14 -0
  180. package/dist/utils/semver.d.ts.map +1 -0
  181. package/dist/utils/semver.js +61 -0
  182. package/dist/utils/semver.js.map +1 -0
  183. package/package.json +7 -4
  184. package/src/__tests__/__snapshots__/boundaries.test.ts.snap +154 -0
  185. package/src/__tests__/agent-instructions.test.ts +6 -1
  186. package/src/__tests__/agent-loop-phase34.test.ts +143 -8
  187. package/src/__tests__/auth.test.ts +9 -0
  188. package/src/__tests__/azure-cli.test.ts +202 -0
  189. package/src/__tests__/boundaries.test.ts +42 -0
  190. package/src/__tests__/csharp-schema.test.ts +261 -0
  191. package/src/__tests__/dev.test.ts +13 -6
  192. package/src/__tests__/fixtures/az wrapper.cmd +2 -0
  193. package/src/__tests__/fixtures/azure-cli.cjs +153 -0
  194. package/src/__tests__/fixtures/provision-runner.cjs +28 -0
  195. package/src/__tests__/generated-project-matrix.test.ts +789 -0
  196. package/src/__tests__/home-page.test.ts +50 -0
  197. package/src/__tests__/init.test.ts +145 -5
  198. package/src/__tests__/mcp-version.test.ts +262 -0
  199. package/src/__tests__/mcp.test.ts +120 -3
  200. package/src/__tests__/multi-auth.test.ts +115 -4
  201. package/src/__tests__/package-manager.test.ts +17 -2
  202. package/src/__tests__/plan-apply.test.ts +179 -0
  203. package/src/__tests__/provision-runtime.test.ts +722 -0
  204. package/src/__tests__/python-uv.test.ts +50 -14
  205. package/src/__tests__/python-venv.test.ts +48 -0
  206. package/src/__tests__/react-dev.test.ts +72 -0
  207. package/src/__tests__/react.test.ts +838 -0
  208. package/src/__tests__/release-metadata.test.ts +121 -0
  209. package/src/__tests__/schema-analysis.test.ts +329 -0
  210. package/src/cli/commands/add-auth.ts +103 -29
  211. package/src/cli/commands/configure-mcp.ts +54 -0
  212. package/src/cli/commands/dev.ts +145 -73
  213. package/src/cli/commands/init.ts +147 -172
  214. package/src/cli/commands/migrations.ts +22 -0
  215. package/src/cli/commands/provision.ts +294 -56
  216. package/src/cli/commands/scaffold.ts +145 -22
  217. package/src/cli/index.ts +46 -10
  218. package/src/core/config.ts +21 -1
  219. package/src/core/operations/create-model.ts +51 -13
  220. package/src/core/operations/migration-generator.ts +14 -1
  221. package/src/core/operations/model-migration.ts +52 -0
  222. package/src/core/operations/provision-deployment.ts +183 -0
  223. package/src/core/operations/provision-operations.ts +662 -154
  224. package/src/core/operations/scaffold-apply.ts +2 -0
  225. package/src/core/operations/scaffold-machine.ts +1 -0
  226. package/src/core/operations/scaffold-plan.ts +5 -1
  227. package/src/core/project/agent-instructions.ts +17 -1
  228. package/src/core/project/agent-templates/AGENTS.md +113 -15
  229. package/src/core/project/agent-templates/CLAUDE.md +14 -1
  230. package/src/core/project/agent-templates/bff-routes.instructions.md +5 -3
  231. package/src/core/project/agent-templates/copilot-instructions.md +15 -1
  232. package/src/core/project/agent-templates/shared-models.instructions.md +11 -3
  233. package/src/core/project/boundaries.ts +62 -20
  234. package/src/core/project/capabilities.ts +63 -6
  235. package/src/core/project/home-page.ts +114 -0
  236. package/src/core/project/manifest.ts +52 -27
  237. package/src/core/project/mcp-config.ts +84 -0
  238. package/src/core/project/react-project.ts +529 -0
  239. package/src/core/project/state.ts +70 -0
  240. package/src/core/project/validation.ts +32 -10
  241. package/src/core/project/workflows.ts +53 -7
  242. package/src/core/scaffold/auth-generator.ts +26 -27
  243. package/src/core/scaffold/model-parser.ts +413 -129
  244. package/src/core/scaffold/native-schema-generator.ts +319 -103
  245. package/src/core/scaffold/openapi-generator.ts +63 -5
  246. package/src/core/scaffold/react-bff-generator.ts +292 -0
  247. package/src/core/scaffold/react-ui-generator.ts +51 -0
  248. package/src/core/scaffold/ui-generator.ts +70 -8
  249. package/src/core/verify/index.ts +118 -6
  250. package/src/index.ts +3 -1
  251. package/src/machine/contracts.ts +1 -0
  252. package/src/machine/errors.ts +4 -2
  253. package/src/machine/index.ts +67 -36
  254. package/src/mcp/index.ts +221 -31
  255. package/src/mcp/version-selection.ts +163 -0
  256. package/src/types/index.ts +4 -0
  257. package/src/utils/azure-cli.ts +315 -0
  258. package/src/utils/azure-diagnostics.ts +75 -0
  259. package/src/utils/package-manager.ts +7 -4
  260. package/src/utils/python-uv.ts +53 -33
  261. package/src/utils/react-dev.ts +31 -0
  262. package/src/utils/semver.ts +59 -0
@@ -1,5 +1,5 @@
1
1
  import { Command } from 'commander';
2
- import { spawn, ChildProcess } from 'child_process';
2
+ import { spawn, spawnSync, ChildProcess } from 'child_process';
3
3
  import * as http from 'http';
4
4
  import * as https from 'https';
5
5
  import * as path from 'path';
@@ -7,7 +7,7 @@ import * as fs from 'fs';
7
7
  import * as os from 'os';
8
8
  import * as net from 'net';
9
9
  import { CosmosClient, PartitionKeyKind } from '@azure/cosmos';
10
- import { ensureSwallowKitProject, getBackendLanguage, getAuthConfig, normalizeAuthConfig } from '../../core/config';
10
+ import { ensureSwallowKitProject, getBackendLanguage, getFrontendFramework, getAuthConfig, normalizeAuthConfig } from '../../core/config';
11
11
  import { ModelInfo } from '../../core/scaffold/model-parser';
12
12
  import {
13
13
  applyDevSeedEnvironment,
@@ -19,19 +19,20 @@ import {
19
19
  import { BackendLanguage } from '../../types';
20
20
  import { detectFromProject, getCommands } from '../../utils/package-manager';
21
21
  import {
22
- buildProjectLocalUvEnv,
23
22
  buildProjectLocalUvInstallerEnv,
24
23
  buildUvPipInstallArgs,
25
- buildUvVenvArgs,
24
+ ensureUvVirtualEnvironment,
26
25
  getProjectLocalUvInstallerCommand,
27
26
  getProjectLocalUvPaths,
28
27
  getPythonProjectRoot,
29
28
  } from '../../utils/python-uv';
30
29
  import { ConnectorMockServer } from '../../core/mock/connector-mock-server';
30
+ import { buildReactDevConfigSource } from '../../utils/react-dev';
31
31
 
32
32
  export interface DevOptions {
33
33
  port?: string;
34
34
  functionsPort?: string;
35
+ bffPort?: string;
35
36
  host?: string;
36
37
  open?: boolean;
37
38
  verbose?: boolean;
@@ -64,8 +65,8 @@ function normalizeParsedDevOptions(options: ParsedDevActionOptions): DevOptions
64
65
  };
65
66
  }
66
67
 
67
- export function buildFunctionsStartArgs(functionsPort: string): string[] {
68
- return ['start', '--port', functionsPort];
68
+ export function buildFunctionsStartArgs(functionsPort: string, backendLanguage: BackendLanguage = 'typescript'): string[] {
69
+ return [...(backendLanguage === 'csharp' ? [] : ['start']), '--port', functionsPort];
69
70
  }
70
71
 
71
72
  export function parseCoreToolsVersion(output: string): string | null {
@@ -104,15 +105,15 @@ export function buildFunctionsCoreToolsCommand(
104
105
 
105
106
  return {
106
107
  command: 'npm',
107
- argsPrefix: ['exec', '--yes', NPM_CORE_TOOLS_PACKAGE, '--'],
108
- label: `npm exec ${NPM_CORE_TOOLS_PACKAGE} (${reason})`,
108
+ argsPrefix: ['exec', '--yes', '--package', NPM_CORE_TOOLS_PACKAGE, '--', 'dotnet', 'run', '--no-launch-profile', '--'],
109
+ label: `dotnet run with npm ${NPM_CORE_TOOLS_PACKAGE} (${reason})`,
109
110
  };
110
111
  }
111
112
 
112
113
  return {
113
- command: 'func',
114
- argsPrefix: [],
115
- label: installedVersion ? `func ${installedVersion}` : 'func',
114
+ command: backendLanguage === 'csharp' ? 'dotnet' : 'func',
115
+ argsPrefix: backendLanguage === 'csharp' ? ['run', '--no-launch-profile', '--'] : [],
116
+ label: backendLanguage === 'csharp' ? 'dotnet run' : installedVersion ? `func ${installedVersion}` : 'func',
116
117
  };
117
118
  }
118
119
 
@@ -125,7 +126,7 @@ export function buildFunctionsBaseUrl(host: string | undefined, functionsPort: s
125
126
  return `http://${host || 'localhost'}:${functionsPort}`;
126
127
  }
127
128
 
128
- export function buildSwaStartArgs(host: string | undefined, nextPort: string, swaPort: string): string[] {
129
+ export function buildSwaStartArgs(host: string | undefined, nextPort: string, swaPort: string, bffPort?: string): string[] {
129
130
  return [
130
131
  'start',
131
132
  `http://${host || 'localhost'}:${nextPort}`,
@@ -133,9 +134,15 @@ export function buildSwaStartArgs(host: string | undefined, nextPort: string, sw
133
134
  '.',
134
135
  '--port',
135
136
  swaPort,
137
+ ...(bffPort ? ['--api-devserver-url', buildFunctionsBaseUrl(host, bffPort)] : []),
136
138
  ];
137
139
  }
138
140
 
141
+ export function buildReactDevArgs(pm: string, port: string, host = 'localhost', configPath?: string): string[] {
142
+ const args = ['vite', '--port', port, '--host', host, '--strictPort', ...(configPath ? ['--config', configPath] : [])];
143
+ return pm === 'pnpm' ? ['exec', ...args] : args;
144
+ }
145
+
139
146
  export function getSwaCliInstallCommand(pm: string): string {
140
147
  return pm === 'pnpm'
141
148
  ? 'pnpm add -Dw @azure/static-web-apps-cli'
@@ -263,7 +270,7 @@ async function checkCommand(
263
270
  }
264
271
 
265
272
  async function resolveProjectLocalUvCommand(projectRoot: string): Promise<{ command: string; env: NodeJS.ProcessEnv }> {
266
- const uvEnv = buildProjectLocalUvEnv(process.env, projectRoot);
273
+ const uvEnv = { ...process.env };
267
274
  const { localUvExecutable } = getProjectLocalUvPaths(projectRoot);
268
275
 
269
276
  if (await checkCommand('uv', ['--version'], { shell: false })) {
@@ -464,22 +471,8 @@ async function bridgePythonCoreToolsForWindowsArm64(
464
471
  async function preparePythonFunctionsEnvironment(functionsDir: string): Promise<NodeJS.ProcessEnv> {
465
472
  const projectRoot = getPythonProjectRoot(functionsDir);
466
473
  const { command: uvCommand, env: uvEnv } = await resolveProjectLocalUvCommand(projectRoot);
467
- const { venvDir, pythonExecutable } = getPythonVirtualEnvPaths(functionsDir);
468
- const hasUsableVirtualEnv = fs.existsSync(pythonExecutable) && await checkCommand(pythonExecutable, ['--version'], {
469
- cwd: functionsDir,
470
- env: uvEnv,
471
- shell: false,
472
- });
473
-
474
- if (!hasUsableVirtualEnv) {
475
- const venvArgs = buildUvVenvArgs('.venv');
476
- if (fs.existsSync(venvDir)) {
477
- venvArgs.push('--clear');
478
- }
479
-
480
- console.log('📦 Creating Python virtual environment with uv...');
481
- await runCommand(uvCommand, venvArgs, functionsDir, 'python virtual environment setup', uvEnv, false);
482
- }
474
+ const { venvDir } = getPythonVirtualEnvPaths(functionsDir);
475
+ const pythonExecutable = await ensureUvVirtualEnvironment(uvCommand, venvDir, functionsDir, uvEnv);
483
476
 
484
477
  console.log('📦 Installing Python Azure Functions dependencies with uv...');
485
478
  await runCommand(
@@ -522,23 +515,48 @@ async function checkCosmosDBEmulator(): Promise<boolean> {
522
515
  });
523
516
  }
524
517
 
518
+ export async function assertDevPortAvailable(port: number, host: string): Promise<void> {
519
+ await new Promise<void>((resolve, reject) => {
520
+ const server = net.createServer();
521
+ server.once('error', error => reject(new Error(`Development port ${port} on ${host} is unavailable: ${error.message}`)));
522
+ server.listen({ port, host, exclusive: true }, () => server.close(error => error ? reject(error) : resolve()));
523
+ });
524
+ }
525
+
526
+ function stopDevProcesses(processes: ChildProcess[]): void {
527
+ const active = processes.filter(child => child.pid && !child.killed && child.exitCode === null && child.signalCode === null);
528
+ if (process.platform === 'win32' && active.length) {
529
+ // Windows shell termination alone leaves Vite, Core Tools, and SWA running.
530
+ const ids = active.map(child => child.pid).join(',');
531
+ const result = spawnSync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command',
532
+ `$all = @(Get-CimInstance Win32_Process); function Stop-OwnedTree([int]$id) { foreach ($child in ($all | Where-Object ParentProcessId -eq $id)) { Stop-OwnedTree $child.ProcessId }; Stop-Process -Id $id -Force -ErrorAction SilentlyContinue }; foreach ($id in @(${ids})) { Stop-OwnedTree $id }`,
533
+ ], { windowsHide: true, encoding: 'utf8' });
534
+ if (result.error || result.status !== 0) {
535
+ console.error('❌ Could not stop all development subprocesses:', result.error?.message || result.stderr);
536
+ }
537
+ } else {
538
+ for (const child of active) child.kill();
539
+ }
540
+ }
541
+
525
542
  export function buildDevCommand(
526
543
  runDevEnvironment: (options: DevOptions) => Promise<void> = startDevEnvironment,
527
544
  verifyProject: (commandName: string, projectRoot?: string) => void = ensureSwallowKitProject
528
545
  ): Command {
529
546
  return new Command()
530
547
  .name('dev')
531
- .description('Start SwallowKit development server (Cosmos DB + Next.js + Azure Functions)')
532
- .option('-p, --port <port>', 'Next.js port', '3000')
548
+ .description('Start SwallowKit frontend, BFF, and standalone Azure Functions')
549
+ .option('-p, --port <port>', 'Frontend dev server port', '3000')
533
550
  .option('-f, --functions-port <port>', 'Azure Functions port', '7071')
551
+ .option('--bff-port <port>', 'React managed BFF Functions port', '7073')
534
552
  .option('--host <host>', 'Host name', 'localhost')
535
553
  .option('--open', 'Open browser automatically', false)
536
554
  .option('--verbose', 'Show verbose logs', false)
537
555
  .option('--no-functions', 'Skip Azure Functions startup')
538
556
  .option('--seed-env <environment>', 'Replace Cosmos DB Emulator data from dev-seeds/<environment> before startup')
539
557
  .option('--mock-connectors', 'Start mock server for connector models (serves Zod-generated data)', false)
540
- .option('--swa-port <port>', 'SWA authentication emulator port', '4280')
541
- .option('--no-swa', 'Skip the SWA authentication emulator')
558
+ .option('--swa-port <port>', 'SWA routing/authentication emulator port', '4280')
559
+ .option('--no-swa', 'Skip SWA routing/authentication emulation (React direct access has no same-origin API)')
542
560
  .action(async (options: ParsedDevActionOptions) => {
543
561
  const normalizedOptions = normalizeParsedDevOptions(options);
544
562
 
@@ -656,19 +674,24 @@ async function startDevEnvironment(options: DevOptions) {
656
674
  const port = options.port || '3000';
657
675
  const functionsPort = options.functionsPort || '7071';
658
676
  const swaPort = options.swaPort || '4280';
677
+ const bffPort = options.bffPort || '7073';
659
678
 
660
679
  // Detect package manager from project lockfile
661
680
  const pm = detectFromProject();
662
681
  const pmCmd = getCommands(pm);
663
682
  const backendLanguage = getBackendLanguage();
683
+ const react = getFrontendFramework() === 'react';
684
+ const frontendLabel = react ? 'React/Vite' : 'Next.js';
664
685
  const authConfig = getAuthConfig();
665
686
  const normalizedAuth = normalizeAuthConfig(authConfig);
666
- const useSwaEmulator = Boolean(normalizedAuth && Object.values(normalizedAuth.schemes).some(scheme => scheme.provider === 'swa')) && !options.noSwa;
687
+ const authSchemes = Object.values(normalizedAuth?.schemes ?? {});
688
+ const swaScheme = authSchemes.find(scheme => scheme.provider === 'swa');
689
+ const useSwaEmulator = (react || Boolean(swaScheme)) && !options.noSwa;
667
690
  const swaCommand = useSwaEmulator ? await resolveSwaCliCommand(process.cwd()) : null;
668
691
 
669
692
  if (useSwaEmulator && !swaCommand) {
670
693
  const installCommand = getSwaCliInstallCommand(pm);
671
- console.error('❌ SWA authentication requires Azure Static Web Apps CLI for local development.');
694
+ console.error('❌ SWA routing/authentication requires Azure Static Web Apps CLI for local development.');
672
695
  console.error(' Install it in this project and retry:');
673
696
  console.error(`\n ${installCommand}\n`);
674
697
  process.exitCode = 1;
@@ -684,6 +707,14 @@ async function startDevEnvironment(options: DevOptions) {
684
707
  const functionsBaseUrl = buildFunctionsBaseUrl(options.host, functionsPort);
685
708
  let functionsReadinessPromise: Promise<boolean> | null = null;
686
709
  let functionsReady = !!options.noFunctions;
710
+ let reactDevConfigDir: string | undefined;
711
+ const cleanupReactDevConfig = () => {
712
+ if (reactDevConfigDir) {
713
+ fs.rmSync(reactDevConfigDir, { recursive: true, force: true });
714
+ reactDevConfigDir = undefined;
715
+ }
716
+ };
717
+ process.once('exit', cleanupReactDevConfig);
687
718
 
688
719
  // Cleanup processes on Ctrl+C
689
720
  process.on('SIGINT', async () => {
@@ -707,15 +738,22 @@ async function startDevEnvironment(options: DevOptions) {
707
738
  if (mockServer) {
708
739
  await mockServer.stop();
709
740
  }
710
- processes.forEach((proc) => {
711
- if (proc && !proc.killed) {
712
- proc.kill();
713
- }
714
- });
741
+ stopDevProcesses(processes);
715
742
  process.exit(0);
716
743
  });
717
744
 
718
745
  try {
746
+ if (react) {
747
+ const ports = [port, bffPort, ...(!options.noFunctions ? [functionsPort] : []), ...(useSwaEmulator ? [swaPort] : []), ...(options.mockConnectors ? [String(Number(functionsPort) + 1)] : [])];
748
+ if (ports.some(value => !/^\d+$/.test(value) || Number(value) < 1 || Number(value) > 65535) || new Set(ports).size !== ports.length) {
749
+ throw new Error('Frontend, BFF, standalone Functions, mock, and SWA ports must be valid and distinct');
750
+ }
751
+ for (const value of ports) await assertDevPortAvailable(Number(value), options.host || 'localhost');
752
+ const coreToolsVersion = await resolveInstalledCoreToolsVersion();
753
+ if (!coreToolsVersion || compareVersionNumbers(coreToolsVersion, '4.0.0') < 0) {
754
+ throw new Error('React BFF requires Azure Functions Core Tools v4. Install it explicitly before running dev.');
755
+ }
756
+ }
719
757
  // 1. Check for Next.js project
720
758
  const packageJsonPath = path.join(process.cwd(), 'package.json');
721
759
  const nextConfigPathJs = path.join(process.cwd(), 'next.config.js');
@@ -724,11 +762,11 @@ async function startDevEnvironment(options: DevOptions) {
724
762
 
725
763
  if (!fs.existsSync(packageJsonPath)) {
726
764
  console.log('❌ package.json not found.');
727
- console.log('💡 Please run this command in the root directory of a Next.js project.');
765
+ console.log('💡 Please run this command in the root directory of a SwallowKit project.');
728
766
  process.exit(1);
729
767
  }
730
768
 
731
- if (!fs.existsSync(nextConfigPathJs) && !fs.existsSync(nextConfigPathTs) && !fs.existsSync(nextConfigPathMjs)) {
769
+ if (!react && !fs.existsSync(nextConfigPathJs) && !fs.existsSync(nextConfigPathTs) && !fs.existsSync(nextConfigPathMjs)) {
732
770
  console.log('⚠️ next.config file not found. Is this a Next.js project?');
733
771
  }
734
772
 
@@ -895,7 +933,7 @@ async function startDevEnvironment(options: DevOptions) {
895
933
 
896
934
  // Azure Functions を起動
897
935
  const functionsCommand = functionsCoreToolsCommand ?? buildFunctionsCoreToolsCommand(backendLanguage, installedCoreToolsVersion);
898
- const funcProcess = spawn(functionsCommand.command, [...functionsCommand.argsPrefix, ...buildFunctionsStartArgs(functionsPort)], {
936
+ const funcProcess = spawn(functionsCommand.command, [...functionsCommand.argsPrefix, ...buildFunctionsStartArgs(functionsPort, backendLanguage)], {
899
937
  cwd: functionsDir,
900
938
  shell: true,
901
939
  stdio: 'pipe', // Always pipe to capture output
@@ -943,6 +981,8 @@ async function startDevEnvironment(options: DevOptions) {
943
981
  console.error('⚠️ Azure Functions startup error:', error.message);
944
982
  console.log('💡 Please ensure Azure Functions Core Tools is installed');
945
983
  console.log(` ${pmCmd.addGlobal} azure-functions-core-tools@4`);
984
+ stopDevProcesses(processes);
985
+ process.exit(1);
946
986
  });
947
987
 
948
988
  funcProcess.on('close', (code) => {
@@ -953,6 +993,8 @@ async function startDevEnvironment(options: DevOptions) {
953
993
  console.log(' Reinstall a matching Core Tools v4 package (Windows users should prefer the official x64/x86 MSI for their machine).');
954
994
  console.log(' SwallowKit local Python dev uses functions/.venv and requirements.txt, but Core Tools still needs its own bundled Python worker.');
955
995
  }
996
+ stopDevProcesses(processes);
997
+ process.exit(code || 1);
956
998
  }
957
999
  });
958
1000
 
@@ -963,14 +1005,14 @@ async function startDevEnvironment(options: DevOptions) {
963
1005
  );
964
1006
  } else if (!hasFunctions) {
965
1007
  console.log('');
966
- console.log('ℹ️ functions/ directory not found. Starting Next.js only.');
1008
+ console.log(`ℹ️ functions/ directory not found. Starting ${frontendLabel}${react ? ' and managed BFF' : ''} only.`);
967
1009
  } else if (options.noFunctions) {
968
1010
  console.log('');
969
1011
  console.log('ℹ️ --no-functions specified. Skipping Azure Functions.');
970
1012
  }
971
1013
 
972
1014
  console.log('');
973
- console.log('🚀 Starting Next.js development server...');
1015
+ console.log(`🚀 Starting ${frontendLabel} development server...`);
974
1016
 
975
1017
  // Mock connector server — start proxy if --mock-connectors is enabled
976
1018
  let bffTargetPort = functionsPort;
@@ -1039,7 +1081,17 @@ async function startDevEnvironment(options: DevOptions) {
1039
1081
  }
1040
1082
 
1041
1083
  // 5. Start Next.js development server
1042
- const nextArgs = buildNextDevArgs(pm, port);
1084
+ let reactDevConfigPath: string | undefined;
1085
+ if (react) {
1086
+ const stateDir = path.join(process.cwd(), '.swallowkit');
1087
+ fs.mkdirSync(stateDir, { recursive: true });
1088
+ reactDevConfigDir = fs.mkdtempSync(path.join(stateDir, 'vite-dev-'));
1089
+ reactDevConfigPath = path.join(reactDevConfigDir, 'config.mjs');
1090
+ fs.writeFileSync(reactDevConfigPath, buildReactDevConfigSource());
1091
+ }
1092
+ const nextArgs = react
1093
+ ? buildReactDevArgs(pm, port, options.host, reactDevConfigPath && path.relative(process.cwd(), reactDevConfigPath))
1094
+ : buildNextDevArgs(pm, port);
1043
1095
 
1044
1096
  // Ensure .env.local points to bffTargetPort so Next.js reads the correct backend URL.
1045
1097
  // When --mock-connectors is active, bffTargetPort = mock port (7072); otherwise = Functions port (7071).
@@ -1067,6 +1119,25 @@ async function startDevEnvironment(options: DevOptions) {
1067
1119
  FUNCTIONS_BASE_URL: bffTargetUrl,
1068
1120
  };
1069
1121
 
1122
+ if (react) {
1123
+ await runCommand(pm, ['run', 'build'], path.join(process.cwd(), 'shared'), 'Shared schema build');
1124
+ const bffDir = path.join(process.cwd(), 'bff');
1125
+ await runCommand(pm, ['run', 'build'], bffDir, 'Managed BFF build');
1126
+ const bffProcess = spawn('func', buildFunctionsStartArgs(bffPort), {
1127
+ cwd: bffDir, shell: true, stdio: 'inherit', env: nextEnv,
1128
+ });
1129
+ processes.push(bffProcess);
1130
+ bffProcess.on('error', error => console.error('❌ Managed BFF startup error:', error.message));
1131
+ bffProcess.on('close', code => {
1132
+ console.error(`⏹️ Managed BFF exited (exit code: ${code})`);
1133
+ stopDevProcesses(processes);
1134
+ process.exit(code || 1);
1135
+ });
1136
+ if (!await waitForHttpServerReady(buildFunctionsBaseUrl(options.host, bffPort))) {
1137
+ throw new Error(`Managed BFF did not become ready on port ${bffPort}`);
1138
+ }
1139
+ }
1140
+
1070
1141
  const nextProcess = spawn(pm === 'pnpm' ? 'pnpm' : 'npx', nextArgs, {
1071
1142
  cwd: process.cwd(),
1072
1143
  shell: true,
@@ -1077,23 +1148,20 @@ async function startDevEnvironment(options: DevOptions) {
1077
1148
  processes.push(nextProcess);
1078
1149
 
1079
1150
  nextProcess.on('error', (error) => {
1080
- console.error('❌ Next.js startup error:', error.message);
1151
+ console.error(`❌ ${frontendLabel} startup error:`, error.message);
1152
+ stopDevProcesses(processes);
1081
1153
  process.exit(1);
1082
1154
  });
1083
1155
 
1084
1156
  nextProcess.on('close', (code) => {
1085
1157
  if (code !== 0) {
1086
- console.log(`\n⏹️ Next.js exited (exit code: ${code})`);
1158
+ console.log(`\n⏹️ ${frontendLabel} exited (exit code: ${code})`);
1087
1159
  }
1088
- // Exit all processes when Next.js exits
1160
+ // Exit all owned processes when the frontend exits.
1089
1161
  if (mockServer) {
1090
1162
  mockServer.stop().catch(() => {});
1091
1163
  }
1092
- processes.forEach((proc) => {
1093
- if (proc && !proc.killed) {
1094
- proc.kill();
1095
- }
1096
- });
1164
+ stopDevProcesses(processes);
1097
1165
  process.exit(code || 0);
1098
1166
  });
1099
1167
 
@@ -1110,14 +1178,14 @@ async function startDevEnvironment(options: DevOptions) {
1110
1178
  let swaReady = false;
1111
1179
  if (useSwaEmulator && swaCommand) {
1112
1180
  const nextUrl = `http://${options.host || 'localhost'}:${port}`;
1113
- console.log(`⏳ Waiting for Next.js before starting the SWA emulator at ${nextUrl}...`);
1181
+ console.log(`⏳ Waiting for ${frontendLabel} before starting the SWA emulator at ${nextUrl}...`);
1114
1182
  const nextReady = await waitForHttpServerReady(nextUrl);
1115
1183
  if (!nextReady) {
1116
- throw new Error(`Next.js did not become ready at ${nextUrl}`);
1184
+ throw new Error(`${frontendLabel} did not become ready at ${nextUrl}`);
1117
1185
  }
1118
1186
 
1119
- console.log(`🔐 Starting SWA authentication emulator (port: ${swaPort})...`);
1120
- const swaProcess = spawn(swaCommand, buildSwaStartArgs(options.host, port, swaPort), {
1187
+ console.log(`🔐 Starting SWA routing/authentication emulator (port: ${swaPort})...`);
1188
+ const swaProcess = spawn(swaCommand, buildSwaStartArgs(options.host, port, swaPort, react ? bffPort : undefined), {
1121
1189
  cwd: process.cwd(),
1122
1190
  shell: true,
1123
1191
  stdio: 'inherit',
@@ -1131,7 +1199,7 @@ async function startDevEnvironment(options: DevOptions) {
1131
1199
  const swaUrl = `http://${options.host || 'localhost'}:${swaPort}`;
1132
1200
  swaReady = await waitForHttpServerReady(swaUrl);
1133
1201
  if (!swaReady) {
1134
- throw new Error(`SWA authentication emulator did not become ready at ${swaUrl}`);
1202
+ throw new Error(`SWA routing/authentication emulator did not become ready at ${swaUrl}`);
1135
1203
  }
1136
1204
  }
1137
1205
 
@@ -1140,18 +1208,25 @@ async function startDevEnvironment(options: DevOptions) {
1140
1208
  console.log('');
1141
1209
  if (useSwaEmulator) {
1142
1210
  const swaUrl = `http://${options.host || 'localhost'}:${swaPort}`;
1143
- console.log('🔐 SWA authenticated app:');
1211
+ console.log('🔐 SWA app (same-origin frontend and BFF):');
1144
1212
  console.log(` ${swaUrl}`);
1145
1213
  console.log('');
1146
- console.log('🔑 Local sign-in:');
1147
- console.log(` ${swaUrl}/.auth/login/aad`);
1148
- console.log(' Enter any username and select Login.');
1149
- console.log(' The "authenticated" role is added automatically.');
1150
- console.log('');
1151
- console.log('📱 Next.js direct access (authentication is not emulated):');
1214
+ if (swaScheme) {
1215
+ const provider = swaScheme.swa?.allowedProviders?.[0] || 'aad';
1216
+ console.log('🔑 Local SWA sign-in:');
1217
+ console.log(` ${swaUrl}/.auth/login/${provider}`);
1218
+ console.log(' Enter any username and select Login.');
1219
+ console.log(' The "authenticated" role is added automatically.');
1220
+ console.log('');
1221
+ }
1222
+ if (authSchemes.some(scheme => scheme.provider === 'custom-jwt')) {
1223
+ console.log(`🔑 Application sign-in: ${swaUrl}/login`);
1224
+ console.log('');
1225
+ }
1226
+ console.log(`📱 ${frontendLabel} direct access (SWA routing/authentication is not emulated):`);
1152
1227
  console.log(` http://${options.host || 'localhost'}:${port}`);
1153
1228
  } else {
1154
- console.log(`📱 Next.js: http://${options.host || 'localhost'}:${port}`);
1229
+ console.log(`📱 ${frontendLabel}: http://${options.host || 'localhost'}:${port}`);
1155
1230
  }
1156
1231
  if (hasFunctions && !options.noFunctions) {
1157
1232
  console.log(`${functionsReady ? '⚡ Azure Functions' : '⏳ Azure Functions (starting)'}: ${functionsBaseUrl}`);
@@ -1161,7 +1236,7 @@ async function startDevEnvironment(options: DevOptions) {
1161
1236
  }
1162
1237
  console.log('');
1163
1238
  if (hasFunctions && !options.noFunctions && functionsReady) {
1164
- console.log('💡 Azure Functions and Next.js BFF are connected');
1239
+ console.log(`💡 Azure Functions and ${react ? 'managed Functions' : 'Next.js'} BFF are connected`);
1165
1240
  } else if (hasFunctions && !options.noFunctions) {
1166
1241
  console.log('💡 Azure Functions is still warming up; BFF routes can fail until the backend responds.');
1167
1242
  }
@@ -1171,6 +1246,7 @@ async function startDevEnvironment(options: DevOptions) {
1171
1246
  if (swaReady) {
1172
1247
  console.log('💡 Open the app through the SWA port when testing authenticated pages and CRUD APIs.');
1173
1248
  }
1249
+ if (react) console.log(`🔀 Managed BFF: ${buildFunctionsBaseUrl(options.host, bffPort)} (not the standalone Functions endpoint)`);
1174
1250
  console.log('');
1175
1251
 
1176
1252
  if (options.open) {
@@ -1189,11 +1265,7 @@ async function startDevEnvironment(options: DevOptions) {
1189
1265
 
1190
1266
  } catch (error) {
1191
1267
  console.error('❌ Failed to start development environment:', error instanceof Error ? error.message : error);
1192
- processes.forEach((proc) => {
1193
- if (proc && !proc.killed) {
1194
- proc.kill();
1195
- }
1196
- });
1268
+ stopDevProcesses(processes);
1197
1269
  process.exit(1);
1198
1270
  }
1199
1271
  }