swallowkit 1.0.0-beta.8 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (272) hide show
  1. package/README.ja.md +87 -181
  2. package/README.md +87 -238
  3. package/dist/__tests__/fixtures.d.ts +12 -0
  4. package/dist/__tests__/fixtures.d.ts.map +1 -1
  5. package/dist/__tests__/fixtures.js +101 -0
  6. package/dist/__tests__/fixtures.js.map +1 -1
  7. package/dist/cli/commands/add-auth.d.ts +15 -0
  8. package/dist/cli/commands/add-auth.d.ts.map +1 -0
  9. package/dist/cli/commands/add-auth.js +790 -0
  10. package/dist/cli/commands/add-auth.js.map +1 -0
  11. package/dist/cli/commands/add-connector.d.ts +20 -0
  12. package/dist/cli/commands/add-connector.d.ts.map +1 -0
  13. package/dist/cli/commands/add-connector.js +164 -0
  14. package/dist/cli/commands/add-connector.js.map +1 -0
  15. package/dist/cli/commands/cosmos-worktree-isolation.d.ts +25 -0
  16. package/dist/cli/commands/cosmos-worktree-isolation.d.ts.map +1 -0
  17. package/dist/cli/commands/cosmos-worktree-isolation.js +124 -0
  18. package/dist/cli/commands/cosmos-worktree-isolation.js.map +1 -0
  19. package/dist/cli/commands/create-model.d.ts +1 -4
  20. package/dist/cli/commands/create-model.d.ts.map +1 -1
  21. package/dist/cli/commands/create-model.js +20 -82
  22. package/dist/cli/commands/create-model.js.map +1 -1
  23. package/dist/cli/commands/dev-seeds.d.ts +27 -0
  24. package/dist/cli/commands/dev-seeds.d.ts.map +1 -1
  25. package/dist/cli/commands/dev-seeds.js +188 -6
  26. package/dist/cli/commands/dev-seeds.js.map +1 -1
  27. package/dist/cli/commands/dev.d.ts +30 -0
  28. package/dist/cli/commands/dev.d.ts.map +1 -1
  29. package/dist/cli/commands/dev.js +495 -129
  30. package/dist/cli/commands/dev.js.map +1 -1
  31. package/dist/cli/commands/init.d.ts +70 -0
  32. package/dist/cli/commands/init.d.ts.map +1 -1
  33. package/dist/cli/commands/init.js +797 -768
  34. package/dist/cli/commands/init.js.map +1 -1
  35. package/dist/cli/commands/scaffold.d.ts +24 -3
  36. package/dist/cli/commands/scaffold.d.ts.map +1 -1
  37. package/dist/cli/commands/scaffold.js +408 -189
  38. package/dist/cli/commands/scaffold.js.map +1 -1
  39. package/dist/cli/index.d.ts +5 -1
  40. package/dist/cli/index.d.ts.map +1 -1
  41. package/dist/cli/index.js +335 -58
  42. package/dist/cli/index.js.map +1 -1
  43. package/dist/core/config.d.ts +13 -3
  44. package/dist/core/config.d.ts.map +1 -1
  45. package/dist/core/config.js +151 -8
  46. package/dist/core/config.js.map +1 -1
  47. package/dist/core/mock/connector-mock-server.d.ts +101 -0
  48. package/dist/core/mock/connector-mock-server.d.ts.map +1 -0
  49. package/dist/core/mock/connector-mock-server.js +480 -0
  50. package/dist/core/mock/connector-mock-server.js.map +1 -0
  51. package/dist/core/mock/zod-mock-generator.d.ts +14 -0
  52. package/dist/core/mock/zod-mock-generator.d.ts.map +1 -0
  53. package/dist/core/mock/zod-mock-generator.js +163 -0
  54. package/dist/core/mock/zod-mock-generator.js.map +1 -0
  55. package/dist/core/operations/auth-operations.d.ts +61 -0
  56. package/dist/core/operations/auth-operations.d.ts.map +1 -0
  57. package/dist/core/operations/auth-operations.js +272 -0
  58. package/dist/core/operations/auth-operations.js.map +1 -0
  59. package/dist/core/operations/create-model.d.ts +15 -0
  60. package/dist/core/operations/create-model.d.ts.map +1 -0
  61. package/dist/core/operations/create-model.js +185 -0
  62. package/dist/core/operations/create-model.js.map +1 -0
  63. package/dist/core/operations/file-session.d.ts +56 -0
  64. package/dist/core/operations/file-session.d.ts.map +1 -0
  65. package/dist/core/operations/file-session.js +203 -0
  66. package/dist/core/operations/file-session.js.map +1 -0
  67. package/dist/core/operations/provision-operations.d.ts +52 -0
  68. package/dist/core/operations/provision-operations.d.ts.map +1 -0
  69. package/dist/core/operations/provision-operations.js +211 -0
  70. package/dist/core/operations/provision-operations.js.map +1 -0
  71. package/dist/core/operations/runtime.d.ts +37 -0
  72. package/dist/core/operations/runtime.d.ts.map +1 -0
  73. package/dist/core/operations/runtime.js +248 -0
  74. package/dist/core/operations/runtime.js.map +1 -0
  75. package/dist/core/operations/scaffold-apply.d.ts +21 -0
  76. package/dist/core/operations/scaffold-apply.d.ts.map +1 -0
  77. package/dist/core/operations/scaffold-apply.js +57 -0
  78. package/dist/core/operations/scaffold-apply.js.map +1 -0
  79. package/dist/core/operations/scaffold-machine.d.ts +16 -0
  80. package/dist/core/operations/scaffold-machine.d.ts.map +1 -0
  81. package/dist/core/operations/scaffold-machine.js +51 -0
  82. package/dist/core/operations/scaffold-machine.js.map +1 -0
  83. package/dist/core/operations/scaffold-plan.d.ts +43 -0
  84. package/dist/core/operations/scaffold-plan.d.ts.map +1 -0
  85. package/dist/core/operations/scaffold-plan.js +187 -0
  86. package/dist/core/operations/scaffold-plan.js.map +1 -0
  87. package/dist/core/project/agent-instructions.d.ts +29 -0
  88. package/dist/core/project/agent-instructions.d.ts.map +1 -0
  89. package/dist/core/project/agent-instructions.js +160 -0
  90. package/dist/core/project/agent-instructions.js.map +1 -0
  91. package/dist/core/project/artifacts.d.ts +58 -0
  92. package/dist/core/project/artifacts.d.ts.map +1 -0
  93. package/dist/core/project/artifacts.js +179 -0
  94. package/dist/core/project/artifacts.js.map +1 -0
  95. package/dist/core/project/boundaries.d.ts +50 -0
  96. package/dist/core/project/boundaries.d.ts.map +1 -0
  97. package/dist/core/project/boundaries.js +117 -0
  98. package/dist/core/project/boundaries.js.map +1 -0
  99. package/dist/core/project/capabilities.d.ts +58 -0
  100. package/dist/core/project/capabilities.d.ts.map +1 -0
  101. package/dist/core/project/capabilities.js +135 -0
  102. package/dist/core/project/capabilities.js.map +1 -0
  103. package/dist/core/project/drift.d.ts +29 -0
  104. package/dist/core/project/drift.d.ts.map +1 -0
  105. package/dist/core/project/drift.js +188 -0
  106. package/dist/core/project/drift.js.map +1 -0
  107. package/dist/core/project/infra.d.ts +49 -0
  108. package/dist/core/project/infra.d.ts.map +1 -0
  109. package/dist/core/project/infra.js +167 -0
  110. package/dist/core/project/infra.js.map +1 -0
  111. package/dist/core/project/manifest.d.ts +92 -0
  112. package/dist/core/project/manifest.d.ts.map +1 -0
  113. package/dist/core/project/manifest.js +321 -0
  114. package/dist/core/project/manifest.js.map +1 -0
  115. package/dist/core/project/state.d.ts +15 -0
  116. package/dist/core/project/state.d.ts.map +1 -0
  117. package/dist/core/project/state.js +106 -0
  118. package/dist/core/project/state.js.map +1 -0
  119. package/dist/core/project/validation.d.ts +20 -0
  120. package/dist/core/project/validation.d.ts.map +1 -0
  121. package/dist/core/project/validation.js +274 -0
  122. package/dist/core/project/validation.js.map +1 -0
  123. package/dist/core/project/workflows.d.ts +43 -0
  124. package/dist/core/project/workflows.d.ts.map +1 -0
  125. package/dist/core/project/workflows.js +299 -0
  126. package/dist/core/project/workflows.js.map +1 -0
  127. package/dist/core/scaffold/auth-generator.d.ts +63 -0
  128. package/dist/core/scaffold/auth-generator.d.ts.map +1 -0
  129. package/dist/core/scaffold/auth-generator.js +1774 -0
  130. package/dist/core/scaffold/auth-generator.js.map +1 -0
  131. package/dist/core/scaffold/connector-functions-generator.d.ts +41 -0
  132. package/dist/core/scaffold/connector-functions-generator.d.ts.map +1 -0
  133. package/dist/core/scaffold/connector-functions-generator.js +1029 -0
  134. package/dist/core/scaffold/connector-functions-generator.js.map +1 -0
  135. package/dist/core/scaffold/functions-generator.d.ts +4 -3
  136. package/dist/core/scaffold/functions-generator.d.ts.map +1 -1
  137. package/dist/core/scaffold/functions-generator.js +405 -134
  138. package/dist/core/scaffold/functions-generator.js.map +1 -1
  139. package/dist/core/scaffold/model-parser.d.ts +20 -0
  140. package/dist/core/scaffold/model-parser.d.ts.map +1 -1
  141. package/dist/core/scaffold/model-parser.js +229 -78
  142. package/dist/core/scaffold/model-parser.js.map +1 -1
  143. package/dist/core/scaffold/native-schema-generator.d.ts +19 -0
  144. package/dist/core/scaffold/native-schema-generator.d.ts.map +1 -0
  145. package/dist/core/scaffold/native-schema-generator.js +753 -0
  146. package/dist/core/scaffold/native-schema-generator.js.map +1 -0
  147. package/dist/core/scaffold/nextjs-generator.d.ts +8 -0
  148. package/dist/core/scaffold/nextjs-generator.d.ts.map +1 -1
  149. package/dist/core/scaffold/nextjs-generator.js +152 -8
  150. package/dist/core/scaffold/nextjs-generator.js.map +1 -1
  151. package/dist/core/scaffold/ui-generator.d.ts +17 -4
  152. package/dist/core/scaffold/ui-generator.d.ts.map +1 -1
  153. package/dist/core/scaffold/ui-generator.js +174 -34
  154. package/dist/core/scaffold/ui-generator.js.map +1 -1
  155. package/dist/core/verify/index.d.ts +69 -0
  156. package/dist/core/verify/index.d.ts.map +1 -0
  157. package/dist/core/verify/index.js +412 -0
  158. package/dist/core/verify/index.js.map +1 -0
  159. package/dist/machine/contracts.d.ts +24 -0
  160. package/dist/machine/contracts.d.ts.map +1 -0
  161. package/dist/machine/contracts.js +3 -0
  162. package/dist/machine/contracts.js.map +1 -0
  163. package/dist/machine/errors.d.ts +14 -0
  164. package/dist/machine/errors.d.ts.map +1 -0
  165. package/dist/machine/errors.js +42 -0
  166. package/dist/machine/errors.js.map +1 -0
  167. package/dist/machine/index.d.ts +3 -0
  168. package/dist/machine/index.d.ts.map +1 -0
  169. package/dist/machine/index.js +566 -0
  170. package/dist/machine/index.js.map +1 -0
  171. package/dist/mcp/index.d.ts +25 -0
  172. package/dist/mcp/index.d.ts.map +1 -0
  173. package/dist/mcp/index.js +425 -0
  174. package/dist/mcp/index.js.map +1 -0
  175. package/dist/types/index.d.ts +105 -0
  176. package/dist/types/index.d.ts.map +1 -1
  177. package/dist/utils/package-manager.d.ts +7 -11
  178. package/dist/utils/package-manager.d.ts.map +1 -1
  179. package/dist/utils/package-manager.js +42 -17
  180. package/dist/utils/package-manager.js.map +1 -1
  181. package/dist/utils/python-uv.d.ts +21 -0
  182. package/dist/utils/python-uv.d.ts.map +1 -0
  183. package/dist/utils/python-uv.js +111 -0
  184. package/dist/utils/python-uv.js.map +1 -0
  185. package/dist/version.d.ts +2 -0
  186. package/dist/version.d.ts.map +1 -0
  187. package/dist/version.js +49 -0
  188. package/dist/version.js.map +1 -0
  189. package/package.json +15 -17
  190. package/src/__tests__/__snapshots__/functions-generator.test.ts.snap +716 -22
  191. package/src/__tests__/__snapshots__/nextjs-generator.test.ts.snap +19 -7
  192. package/src/__tests__/__snapshots__/ui-generator.test.ts.snap +20 -12
  193. package/src/__tests__/agent-instructions.test.ts +237 -0
  194. package/src/__tests__/agent-loop-phase34.test.ts +513 -0
  195. package/src/__tests__/auth.test.ts +770 -0
  196. package/src/__tests__/bff-swa-auth.test.ts +275 -0
  197. package/src/__tests__/config.test.ts +176 -3
  198. package/src/__tests__/connector-functions-generator.test.ts +288 -0
  199. package/src/__tests__/connector-mock-server.test.ts +459 -0
  200. package/src/__tests__/connector-model-bff.test.ts +162 -0
  201. package/src/__tests__/cosmos-worktree-isolation.test.ts +149 -0
  202. package/src/__tests__/dev-seeds.test.ts +124 -0
  203. package/src/__tests__/dev.test.ts +254 -0
  204. package/src/__tests__/file-session.test.ts +98 -0
  205. package/src/__tests__/fixtures.ts +101 -0
  206. package/src/__tests__/functions-generator.test.ts +137 -1
  207. package/src/__tests__/init.test.ts +386 -1
  208. package/src/__tests__/jest-globals.d.ts +8 -0
  209. package/src/__tests__/machine.test.ts +371 -0
  210. package/src/__tests__/mcp.test.ts +253 -0
  211. package/src/__tests__/model-parser.test.ts +142 -0
  212. package/src/__tests__/multi-auth.test.ts +160 -0
  213. package/src/__tests__/nextjs-generator.test.ts +8 -0
  214. package/src/__tests__/package-manager.test.ts +59 -4
  215. package/src/__tests__/plan-apply.test.ts +499 -0
  216. package/src/__tests__/python-uv.test.ts +48 -0
  217. package/src/__tests__/scaffold.test.ts +287 -24
  218. package/src/__tests__/tsconfig.json +15 -0
  219. package/src/__tests__/ui-generator.test.ts +104 -0
  220. package/src/__tests__/zod-mock-generator.test.ts +132 -0
  221. package/src/cli/commands/add-auth.ts +853 -0
  222. package/src/cli/commands/add-connector.ts +159 -0
  223. package/src/cli/commands/cosmos-worktree-isolation.ts +130 -0
  224. package/src/cli/commands/create-model.ts +21 -100
  225. package/src/cli/commands/dev-seeds.ts +278 -7
  226. package/src/cli/commands/dev.ts +607 -160
  227. package/src/cli/commands/init.ts +880 -808
  228. package/src/cli/commands/scaffold.ts +531 -236
  229. package/src/cli/index.ts +338 -60
  230. package/src/core/config.ts +145 -9
  231. package/src/core/mock/connector-mock-server.ts +555 -0
  232. package/src/core/mock/zod-mock-generator.ts +205 -0
  233. package/src/core/operations/auth-operations.ts +360 -0
  234. package/src/core/operations/create-model.ts +191 -0
  235. package/src/core/operations/file-session.ts +222 -0
  236. package/src/core/operations/provision-operations.ts +297 -0
  237. package/src/core/operations/runtime.ts +258 -0
  238. package/src/core/operations/scaffold-apply.ts +106 -0
  239. package/src/core/operations/scaffold-machine.ts +77 -0
  240. package/src/core/operations/scaffold-plan.ts +215 -0
  241. package/src/core/project/agent-instructions.ts +167 -0
  242. package/src/core/project/agent-templates/AGENTS.md +504 -0
  243. package/src/core/project/agent-templates/CLAUDE.md +31 -0
  244. package/src/core/project/agent-templates/azure-functions.instructions.md +22 -0
  245. package/src/core/project/agent-templates/bff-routes.instructions.md +18 -0
  246. package/src/core/project/agent-templates/copilot-instructions.md +27 -0
  247. package/src/core/project/agent-templates/shared-models.instructions.md +72 -0
  248. package/src/core/project/artifacts.ts +205 -0
  249. package/src/core/project/boundaries.ts +170 -0
  250. package/src/core/project/capabilities.ts +193 -0
  251. package/src/core/project/drift.ts +202 -0
  252. package/src/core/project/infra.ts +196 -0
  253. package/src/core/project/manifest.ts +402 -0
  254. package/src/core/project/state.ts +77 -0
  255. package/src/core/project/validation.ts +295 -0
  256. package/src/core/project/workflows.ts +295 -0
  257. package/src/core/scaffold/auth-generator.ts +1824 -0
  258. package/src/core/scaffold/connector-functions-generator.ts +1133 -0
  259. package/src/core/scaffold/functions-generator.ts +432 -134
  260. package/src/core/scaffold/model-parser.ts +244 -90
  261. package/src/core/scaffold/native-schema-generator.ts +904 -0
  262. package/src/core/scaffold/nextjs-generator.ts +174 -9
  263. package/src/core/scaffold/ui-generator.ts +191 -36
  264. package/src/core/verify/index.ts +468 -0
  265. package/src/machine/contracts.ts +33 -0
  266. package/src/machine/errors.ts +45 -0
  267. package/src/machine/index.ts +690 -0
  268. package/src/mcp/index.ts +423 -0
  269. package/src/types/index.ts +137 -0
  270. package/src/utils/package-manager.ts +41 -19
  271. package/src/utils/python-uv.ts +96 -0
  272. package/src/version.ts +13 -0
package/src/cli/index.ts CHANGED
@@ -1,74 +1,352 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // Ensure UTF-8 console output on Windows (fixes emoji/Unicode garbling in PowerShell 5.1)
4
- if (process.platform === 'win32') {
5
- try {
6
- require('child_process').execSync('chcp 65001', { stdio: 'ignore' });
7
- } catch { /* ignore — non-critical */ }
8
- }
9
-
3
+ import { execSync } from "child_process";
4
+ import { readFileSync } from "fs";
5
+ import * as path from "path";
10
6
  import { Command } from "commander";
11
7
  import { initCommand, devCommand, devSeedsCommand, scaffoldCommand, createModelCommand } from "./commands";
12
8
  import { provisionCommand } from "./commands/provision";
9
+ import { addConnectorCommand } from "./commands/add-connector";
10
+ import { addAuthCommand } from "./commands/add-auth";
11
+ import { planScaffoldOperation } from "../core/operations/scaffold-plan";
12
+ import { inspectArtifacts } from "../core/project/artifacts";
13
+ import { detectDrift } from "../core/project/drift";
14
+ import { runVerify } from "../core/verify";
15
+ import { isMachineCommand, runMachineCli } from "../machine";
16
+
17
+ const packageJsonPath = path.resolve(__dirname, "../../package.json");
18
+ export const CLI_VERSION = JSON.parse(readFileSync(packageJsonPath, "utf8")).version as string;
19
+
20
+ const DEV_OPTION_ARITY = new Map<string, 0 | 1>([
21
+ ["-p", 1],
22
+ ["--port", 1],
23
+ ["-f", 1],
24
+ ["--functions-port", 1],
25
+ ["--host", 1],
26
+ ["--seed-env", 1],
27
+ ["-o", 0],
28
+ ["--open", 0],
29
+ ["-v", 0],
30
+ ["--verbose", 0],
31
+ ["--no-functions", 0],
32
+ ["--mock-connectors", 0],
33
+ ["--swa-port", 1],
34
+ ["--no-swa", 0],
35
+ ]);
36
+
37
+ function ensureUtf8ConsoleOnWindows(): void {
38
+ if (process.platform === 'win32') {
39
+ try {
40
+ execSync('chcp 65001', { stdio: 'ignore' });
41
+ } catch {
42
+ // ignore — non-critical
43
+ }
44
+ }
45
+ }
46
+
47
+ function isDevOptionSequence(tokens: string[]): boolean {
48
+ if (tokens.length === 0) {
49
+ return false;
50
+ }
51
+
52
+ for (let index = 0; index < tokens.length; index += 1) {
53
+ const token = tokens[index];
54
+ const arity = DEV_OPTION_ARITY.get(token);
55
+
56
+ if (arity === undefined) {
57
+ return false;
58
+ }
59
+
60
+ if (arity === 1) {
61
+ const value = tokens[index + 1];
62
+ if (value === undefined || value === "dev") {
63
+ return false;
64
+ }
65
+ index += 1;
66
+ }
67
+ }
68
+
69
+ return true;
70
+ }
71
+
72
+ export function normalizeDevCommandArgv(argv: string[]): string[] {
73
+ if (argv.length <= 3) {
74
+ return argv;
75
+ }
76
+
77
+ const prefix = argv.slice(0, 2);
78
+ const args = argv.slice(2);
79
+ const devIndex = args.indexOf("dev");
80
+
81
+ if (devIndex <= 0) {
82
+ return argv;
83
+ }
84
+
85
+ const leadingTokens = args.slice(0, devIndex);
86
+ if (!isDevOptionSequence(leadingTokens)) {
87
+ return argv;
88
+ }
13
89
 
14
- const program = new Command();
15
-
16
- program
17
- .name("swallowkit")
18
- .description("Next.js framework optimized for Azure deployment - Automatically splits SSR into individual Azure Functions")
19
- .version("1.0.0-beta.8");
20
-
21
- // Register commands
22
- program
23
- .command("init [project-name]")
24
- .description("Initialize a new SwallowKit project")
25
- .option("--template <template>", "Template to use", "default")
26
- .option("--next-version <version>", "Next.js version to install (e.g., 16.0.7, latest)", "latest")
27
- .option("--cicd <provider>", "CI/CD provider: github | azure | skip")
28
- .option("--backend-language <language>", "Azure Functions backend language: typescript | csharp | python")
29
- .option("--cosmos-db-mode <mode>", "Cosmos DB mode: freetier | serverless")
30
- .option("--vnet <option>", "Network security: outbound | none")
31
- .action((projectName, options) => {
32
- initCommand({
33
- name: projectName || "swallowkit-app",
34
- template: options.template,
35
- nextVersion: options.nextVersion,
36
- cicd: options.cicd,
37
- backendLanguage: options.backendLanguage,
38
- cosmosDbMode: options.cosmosDbMode,
39
- vnet: options.vnet,
90
+ return [
91
+ ...prefix,
92
+ "dev",
93
+ ...leadingTokens,
94
+ ...args.slice(devIndex + 1),
95
+ ];
96
+ }
97
+
98
+ export function createProgram(devCommandOverride: Command = devCommand): Command {
99
+ const program = new Command();
100
+
101
+ program
102
+ .name("swallowkit")
103
+ .description("Next.js framework optimized for Azure deployment - Automatically splits SSR into individual Azure Functions")
104
+ .version(CLI_VERSION);
105
+
106
+ // Register commands
107
+ program
108
+ .command("init [project-name]")
109
+ .description("Initialize a new SwallowKit project")
110
+ .option("--template <template>", "Template to use", "default")
111
+ .option("--next-version <version>", "Next.js version to install (e.g., 16.0.7, latest)", "latest")
112
+ .option("--package-manager <manager>", "Package manager: npm | pnpm (defaults to invocation context)")
113
+ .option("--cicd <provider>", "CI/CD provider: github | azure | skip")
114
+ .option("--backend-language <language>", "Azure Functions backend language: typescript | csharp | python")
115
+ .option("--cosmos-db-mode <mode>", "Cosmos DB mode: freetier | serverless")
116
+ .option("--vnet <option>", "Network security: outbound | none")
117
+ .option("--swa-plan <plan>", "Azure Static Web Apps plan: free | standard")
118
+ .action((projectName, options) => {
119
+ initCommand({
120
+ name: projectName || "swallowkit-app",
121
+ template: options.template,
122
+ nextVersion: options.nextVersion,
123
+ packageManager: options.packageManager,
124
+ cicd: options.cicd,
125
+ backendLanguage: options.backendLanguage,
126
+ cosmosDbMode: options.cosmosDbMode,
127
+ vnet: options.vnet,
128
+ swaPlan: options.swaPlan,
129
+ });
40
130
  });
41
- });
42
131
 
43
- program.addCommand(devCommand);
44
- program.addCommand(devSeedsCommand);
132
+ program.addCommand(devCommandOverride);
133
+ program.addCommand(devSeedsCommand);
45
134
 
46
- program.addCommand(provisionCommand);
135
+ program.addCommand(provisionCommand);
47
136
 
48
- program
49
- .command("create-model <names...>")
50
- .description("Create model template files with id, createdAt, and updatedAt fields")
51
- .option("--models-dir <dir>", "Models directory", "shared/models")
52
- .action((names, options) => {
53
- createModelCommand({
54
- names,
55
- modelsDir: options.modelsDir,
137
+ program
138
+ .command("create-model <names...>")
139
+ .description("Create model template files with id, createdAt, and updatedAt fields")
140
+ .option("--models-dir <dir>", "Models directory", "shared/models")
141
+ .option("--connector <name>", "Associate model with a connector defined in swallowkit.config.js")
142
+ .action((names, options) => {
143
+ createModelCommand({
144
+ names,
145
+ modelsDir: options.modelsDir,
146
+ connector: options.connector,
147
+ });
56
148
  });
57
- });
58
149
 
59
- program
60
- .command("scaffold <model>")
61
- .description("Generate CRUD code for Azure Functions and Next.js BFF from Zod models")
62
- .option("--functions-dir <dir>", "Azure Functions directory", "functions")
63
- .option("--api-dir <dir>", "Next.js API routes directory", "app/api")
64
- .option("--api-only", "Generate API only, skip UI components", false)
65
- .action((model, options) => {
66
- scaffoldCommand({
67
- model,
68
- functionsDir: options.functionsDir,
69
- apiDir: options.apiDir,
70
- apiOnly: options.apiOnly,
150
+ program
151
+ .command("scaffold <model>")
152
+ .description("Generate CRUD code for Azure Functions and Next.js BFF from Zod models")
153
+ .option("--functions-dir <dir>", "Azure Functions directory", "functions")
154
+ .option("--api-dir <dir>", "Next.js API routes directory", "app/api")
155
+ .option("--api-only", "Skip UI components; still update Functions, BFF routes, OpenAPI, and native schema assets", false)
156
+ .option("--dry-run", "Show planned file changes without writing anything", false)
157
+ .action(async (model, options) => {
158
+ if (options.dryRun) {
159
+ await runScaffoldDryRun({
160
+ model,
161
+ functionsDir: options.functionsDir,
162
+ apiDir: options.apiDir,
163
+ apiOnly: options.apiOnly,
164
+ });
165
+ return;
166
+ }
167
+
168
+ await scaffoldCommand({
169
+ model,
170
+ functionsDir: options.functionsDir,
171
+ apiDir: options.apiDir,
172
+ apiOnly: options.apiOnly,
173
+ });
71
174
  });
72
- });
73
175
 
74
- program.parse();
176
+ program
177
+ .command("add-connector <name>")
178
+ .description("Add an external data source connector configuration")
179
+ .requiredOption("--type <type>", "Connector type: rdb | api")
180
+ .option("--provider <provider>", "RDB provider: mysql | postgres | sqlserver", "mysql")
181
+ .action((name, options) => {
182
+ addConnectorCommand({
183
+ name,
184
+ type: options.type,
185
+ provider: options.provider,
186
+ });
187
+ });
188
+
189
+ program
190
+ .command("add-auth")
191
+ .description("Add authentication and authorization to the project")
192
+ .option("--provider <provider>", "Auth provider: custom-jwt | swa | external-token | swa-custom | none", "custom-jwt")
193
+ .option("--scheme <name>", "Add a named authentication scheme")
194
+ .option("--allowed-providers <list>", "Comma-separated SWA identity providers to allow (e.g. github,aad); defaults to aad")
195
+ .action((options) => {
196
+ addAuthCommand({
197
+ provider: options.provider,
198
+ scheme: options.scheme,
199
+ allowedProviders: options.allowedProviders ? String(options.allowedProviders).split(",") : undefined,
200
+ });
201
+ });
202
+
203
+ program
204
+ .command("status")
205
+ .description("Show generated artifact status and drift against the current project state")
206
+ .option("--artifacts", "List all tracked artifacts with ownership", false)
207
+ .action(async (options) => {
208
+ await runStatusCommand({ artifacts: options.artifacts });
209
+ });
210
+
211
+ program
212
+ .command("verify")
213
+ .description("Run verification checks (structure, drift, typecheck)")
214
+ .option("--checks <ids>", "Comma-separated check ids to run (structure,drift,typecheck)")
215
+ .action(async (options) => {
216
+ await runVerifyCommand({ checks: options.checks });
217
+ });
218
+
219
+ return program;
220
+ }
221
+
222
+ async function runScaffoldDryRun(options: { model: string; functionsDir?: string; apiDir?: string; apiOnly?: boolean }): Promise<void> {
223
+ console.log("🔍 Scaffold dry-run — no files will be written.\n");
224
+
225
+ const plan = await planScaffoldOperation(options);
226
+
227
+ const actionIcons: Record<string, string> = {
228
+ create: "➕",
229
+ update: "♻️ ",
230
+ overwrite: "⚠️ ",
231
+ append: "🔗",
232
+ delete: "🗑️ ",
233
+ skip: "⏭️ ",
234
+ };
235
+
236
+ for (const operation of plan.operations) {
237
+ if (operation.action === "skip") continue;
238
+ const icon = actionIcons[operation.action] ?? "•";
239
+ const conflictNote = operation.conflict ? ` (conflict: ${operation.conflictReason})` : "";
240
+ console.log(` ${icon} ${operation.action.padEnd(9)} ${operation.path} [${operation.ownership}]${conflictNote}`);
241
+ }
242
+
243
+ const skipped = plan.operations.filter((operation) => operation.action === "skip").length;
244
+ if (skipped > 0) {
245
+ console.log(` ⏭️ ${skipped} file(s) unchanged (skipped)`);
246
+ }
247
+
248
+ if (plan.warnings.length > 0) {
249
+ console.log("\n⚠️ Warnings:");
250
+ for (const warning of plan.warnings) {
251
+ console.log(` - ${warning}`);
252
+ }
253
+ }
254
+
255
+ console.log(`\n📋 Plan ${plan.planId} saved.`);
256
+ if (plan.requiresApproval) {
257
+ console.log(`⚠️ ${plan.conflicts.length} file(s) were modified after generation and would be overwritten.`);
258
+ console.log(` Review the conflicts, then run: swallowkit scaffold ${options.model} (or machine apply scaffold --plan ${plan.planId} --approve)`);
259
+ } else {
260
+ console.log(` Run without --dry-run to apply: swallowkit scaffold ${options.model}`);
261
+ }
262
+ }
263
+
264
+ async function runStatusCommand(options: { artifacts?: boolean }): Promise<void> {
265
+ const inspection = inspectArtifacts();
266
+ const drift = await detectDrift();
267
+
268
+ console.log("📦 SwallowKit project status\n");
269
+
270
+ if (!inspection.ledgerFound) {
271
+ console.log("ℹ️ No artifact ledger (.swallowkit/artifacts.json) found yet.");
272
+ console.log(" Run scaffold once with this SwallowKit version to start tracking artifacts.\n");
273
+ } else {
274
+ const total = inspection.artifacts.length;
275
+ const missing = inspection.artifacts.filter((artifact) => !artifact.exists).length;
276
+ const modified = inspection.artifacts.filter((artifact) => artifact.modified).length;
277
+ console.log(` Tracked artifacts: ${total}`);
278
+ console.log(` Modified after generation: ${modified}`);
279
+ console.log(` Missing: ${missing}\n`);
280
+
281
+ if (options.artifacts) {
282
+ for (const artifact of inspection.artifacts) {
283
+ const flags = [
284
+ artifact.modified ? "modified" : null,
285
+ !artifact.exists ? "missing" : null,
286
+ ].filter(Boolean);
287
+ const suffix = flags.length > 0 ? ` (${flags.join(", ")})` : "";
288
+ console.log(` - ${artifact.path} [${artifact.ownership}]${suffix}`);
289
+ }
290
+ console.log("");
291
+ }
292
+ }
293
+
294
+ if (drift.findings.length === 0) {
295
+ console.log("✅ No drift detected.");
296
+ return;
297
+ }
298
+
299
+ console.log(`⚠️ Drift findings (${drift.findings.length}):`);
300
+ for (const finding of drift.findings) {
301
+ console.log(` [${finding.severity}] ${finding.kind}: ${finding.message}`);
302
+ console.log(` → ${finding.repairAction}`);
303
+ }
304
+ }
305
+
306
+ async function runVerifyCommand(options: { checks?: string }): Promise<void> {
307
+ console.log("🔎 Running verification checks...\n");
308
+
309
+ const result = await runVerify(options.checks ? options.checks.split(",") : undefined);
310
+
311
+ const statusIcons: Record<string, string> = {
312
+ pass: "✅",
313
+ fail: "❌",
314
+ skip: "⏭️ ",
315
+ error: "💥",
316
+ };
317
+
318
+ for (const check of result.checks) {
319
+ console.log(` ${statusIcons[check.status]} ${check.id.padEnd(10)} ${check.title} (${check.durationMs}ms)`);
320
+ if (check.status === "fail" || check.status === "error") {
321
+ for (const action of check.suggestedActions) {
322
+ console.log(` → ${action}`);
323
+ }
324
+ }
325
+ }
326
+
327
+ console.log(
328
+ `\n${result.summary.done ? "✅" : "❌"} ${result.summary.passed} passed, ${result.summary.failed} failed, ${result.summary.skipped} skipped, ${result.summary.errors} error(s)`
329
+ );
330
+
331
+ if (!result.summary.done) {
332
+ console.log(' Details: swallowkit machine explain failure');
333
+ process.exitCode = 1;
334
+ }
335
+ }
336
+
337
+ export async function runCli(argv: string[] = process.argv): Promise<void> {
338
+ ensureUtf8ConsoleOnWindows();
339
+ if (isMachineCommand(argv)) {
340
+ await runMachineCli(argv);
341
+ return;
342
+ }
343
+
344
+ await createProgram().parseAsync(normalizeDevCommandArgv(argv));
345
+ }
346
+
347
+ if (require.main === module) {
348
+ void runCli().catch((error) => {
349
+ console.error(error);
350
+ process.exitCode = 1;
351
+ });
352
+ }
@@ -1,8 +1,20 @@
1
1
  import * as fs from "fs";
2
+ import { createRequire } from "module";
2
3
  import * as path from "path";
3
- import { BackendLanguage, SwallowKitConfig } from "../types";
4
+ import { AuthConfig, AuthProvider, BackendLanguage, ConnectorDefinition, NormalizedAuthConfig, SwallowKitConfig } from "../types";
4
5
  import { detectFromProject, getCommands } from "../utils/package-manager";
5
6
 
7
+ const requireFromHere = createRequire(__filename);
8
+
9
+ function unwrapConfigModule(
10
+ loadedConfig: Partial<SwallowKitConfig> | { default?: Partial<SwallowKitConfig> }
11
+ ): Partial<SwallowKitConfig> {
12
+ if (typeof loadedConfig === "object" && loadedConfig !== null && "default" in loadedConfig) {
13
+ return loadedConfig.default ?? {};
14
+ }
15
+ return loadedConfig as Partial<SwallowKitConfig>;
16
+ }
17
+
6
18
  const VALID_BACKEND_LANGUAGES: BackendLanguage[] = ["typescript", "csharp", "python"];
7
19
 
8
20
  /**
@@ -25,7 +37,7 @@ const DEFAULT_CONFIG: SwallowKitConfig = {
25
37
  /**
26
38
  * 設定ファイルを読み込み
27
39
  */
28
- export function loadConfig(configPath?: string): SwallowKitConfig {
40
+ export function loadConfig(configPath?: string, throwOnError = false, quiet = false): SwallowKitConfig {
29
41
  const defaultPaths = [
30
42
  "swallowkit.config.json",
31
43
  "swallowkit.config.js",
@@ -39,24 +51,25 @@ export function loadConfig(configPath?: string): SwallowKitConfig {
39
51
 
40
52
  if (fs.existsSync(fullPath)) {
41
53
  try {
42
- console.log(`📋 設定ファイルを読み込み: ${filePath}`);
54
+ if (!quiet) console.log(`📋 設定ファイルを読み込み: ${filePath}`);
43
55
 
44
56
  if (filePath.endsWith(".json")) {
45
57
  const configData = fs.readFileSync(fullPath, "utf-8");
46
58
  const userConfig = JSON.parse(configData);
47
59
  return mergeConfig(DEFAULT_CONFIG, userConfig);
48
60
  } else if (filePath.endsWith(".js")) {
49
- delete require.cache[fullPath];
50
- const userConfig = require(fullPath);
51
- return mergeConfig(DEFAULT_CONFIG, userConfig.default || userConfig);
61
+ delete requireFromHere.cache[fullPath];
62
+ const loadedConfig = requireFromHere(fullPath) as Partial<SwallowKitConfig> | { default?: Partial<SwallowKitConfig> };
63
+ return mergeConfig(DEFAULT_CONFIG, unwrapConfigModule(loadedConfig));
52
64
  }
53
65
  } catch (error) {
54
- console.warn(`⚠️ 設定ファイルの読み込みに失敗: ${filePath}`, error);
66
+ if (throwOnError) throw error;
67
+ if (!quiet) console.warn(`⚠️ 設定ファイルの読み込みに失敗: ${filePath}`, error);
55
68
  }
56
69
  }
57
70
  }
58
71
 
59
- console.log("📋 デフォルト設定を使用");
72
+ if (!quiet) console.log("📋 デフォルト設定を使用");
60
73
  return DEFAULT_CONFIG;
61
74
  }
62
75
 
@@ -81,6 +94,11 @@ function mergeConfig(defaultConfig: SwallowKitConfig, userConfig: Partial<Swallo
81
94
  ...userConfig.api?.cors,
82
95
  },
83
96
  },
97
+ connectors: userConfig.connectors ?? defaultConfig.connectors,
98
+ auth: userConfig.auth
99
+ ? { ...defaultConfig.auth, ...userConfig.auth }
100
+ : defaultConfig.auth,
101
+ verify: userConfig.verify ?? defaultConfig.verify,
84
102
  };
85
103
  }
86
104
 
@@ -168,14 +186,104 @@ export function getFullConfig(configPath?: string): SwallowKitConfig {
168
186
  return mergeConfig(fileConfig, envConfig);
169
187
  }
170
188
 
189
+ /**
190
+ * コネクタ定義を名前で取得
191
+ */
192
+ export function getConnectorDefinition(connectorName: string, configPath?: string): ConnectorDefinition | undefined {
193
+ const config = getFullConfig(configPath);
194
+ return config.connectors?.[connectorName];
195
+ }
196
+
197
+ /**
198
+ * 認証設定を取得
199
+ */
200
+ export function getAuthConfig(configPath?: string): AuthConfig | undefined {
201
+ const config = getFullConfig(configPath);
202
+ return config.auth;
203
+ }
204
+
205
+ /** Load configuration for mutating/generation commands and fail instead of silently falling back. */
206
+ export function getValidatedFullConfig(configPath?: string): SwallowKitConfig {
207
+ const config = mergeConfig(loadConfig(configPath, true), loadConfigFromEnv());
208
+ const validation = validateConfig(config);
209
+ if (!validation.valid) throw new Error(`Invalid SwallowKit configuration:\n${validation.errors.map(error => `- ${error}`).join("\n")}`);
210
+ return config;
211
+ }
212
+
213
+ const VALID_AUTH_PROVIDERS: AuthProvider[] = ["custom-jwt", "swa", "external-token", "swa-custom", "none"];
214
+ const AUTH_NAME = /^[A-Za-z][A-Za-z0-9_-]*$/;
215
+
216
+ /** Convert the legacy single-provider shape to the canonical named-scheme shape. */
217
+ export function normalizeAuthConfig(auth?: AuthConfig): NormalizedAuthConfig | undefined {
218
+ if (!auth) return undefined;
219
+ const schemes = { ...(auth.schemes ?? {}) };
220
+ if (auth.provider && auth.provider !== "none" && !schemes.default) {
221
+ schemes.default = { provider: auth.provider, customJwt: auth.customJwt, swa: auth.swa };
222
+ }
223
+ const rawDefault = auth.authorization?.defaultPolicy ?? (Object.keys(schemes).length ? "authenticated" : "anonymous");
224
+ return {
225
+ ...auth,
226
+ schemes,
227
+ authorization: {
228
+ ...auth.authorization,
229
+ defaultPolicy: rawDefault === "public" ? "anonymous" : rawDefault,
230
+ policies: { ...(auth.authorization?.policies ?? {}) },
231
+ },
232
+ };
233
+ }
234
+
235
+ export function getNormalizedAuthConfig(configPath?: string): NormalizedAuthConfig | undefined {
236
+ return normalizeAuthConfig(getAuthConfig(configPath));
237
+ }
238
+
171
239
  /**
172
240
  * 設定の検証
173
241
  */
242
+ const VALID_CONNECTOR_TYPES = ["rdb", "api"];
243
+ const VALID_RDB_PROVIDERS = ["mysql", "postgres", "sqlserver"];
244
+ const VALID_API_AUTH_TYPES = ["apiKey", "bearer", "oauth2"];
245
+
246
+ function hasConfiguredDatabase(config: SwallowKitConfig): boolean {
247
+ return Boolean(config.database && Object.keys(config.database).length > 0);
248
+ }
249
+
174
250
  export function validateConfig(config: SwallowKitConfig): { valid: boolean; errors: string[] } {
175
251
  const errors: string[] = [];
176
252
 
253
+ const auth = normalizeAuthConfig(config.auth);
254
+ if (auth) {
255
+ for (const [name, scheme] of Object.entries(auth.schemes)) {
256
+ const base = `auth.schemes.${name}`;
257
+ if (!AUTH_NAME.test(name)) errors.push(`${base}: scheme name must match ${AUTH_NAME}; rename the scheme`);
258
+ if (!VALID_AUTH_PROVIDERS.includes(scheme.provider)) errors.push(`${base}.provider: unsupported provider '${scheme.provider}'; use ${VALID_AUTH_PROVIDERS.join(", ")}`);
259
+ if (scheme.provider === "none" || scheme.provider === "swa-custom") errors.push(`${base}.provider: '${scheme.provider}' cannot be used as a named scheme; use swa, external-token, or custom-jwt`);
260
+ if (scheme.provider === "custom-jwt") {
261
+ const jwt = scheme.customJwt ?? (name === "default" ? auth.customJwt : undefined);
262
+ for (const key of ["userConnector", "userTable", "loginIdColumn", "passwordHashColumn", "rolesColumn"] as const) {
263
+ if (!jwt?.[key]) errors.push(`${base}.customJwt.${key}: required for custom-jwt; configure this value`);
264
+ }
265
+ }
266
+ }
267
+ for (const [name, policy] of Object.entries(auth.authorization.policies)) {
268
+ const base = `auth.authorization.policies.${name}`;
269
+ if (!AUTH_NAME.test(name)) errors.push(`${base}: policy name must match ${AUTH_NAME}; rename the policy`);
270
+ if (!Array.isArray(policy.schemes) || policy.schemes.length === 0) errors.push(`${base}.schemes: provide at least one named scheme`);
271
+ for (const scheme of policy.schemes ?? []) if (!auth.schemes[scheme]) errors.push(`${base}.schemes: undefined scheme '${scheme}'; define auth.schemes.${scheme}`);
272
+ const sources = new Map<string, string>();
273
+ for (const schemeName of policy.schemes ?? []) {
274
+ const provider = auth.schemes[schemeName]?.provider;
275
+ const source = provider === "swa" ? "x-ms-client-principal" : provider === "custom-jwt" || provider === "external-token" ? "authorization-bearer" : provider;
276
+ if (source && sources.has(source)) errors.push(`${base}.schemes: '${sources.get(source)}' and '${schemeName}' both use ${source}; split the policy so credential selection is deterministic`);
277
+ else if (source) sources.set(source, schemeName);
278
+ }
279
+ }
280
+ const defaultPolicy = auth.authorization.defaultPolicy;
281
+ if (defaultPolicy === "authenticated" && !auth.schemes.default && Object.keys(auth.schemes).length !== 1) errors.push("auth.authorization.defaultPolicy: 'authenticated' requires auth.schemes.default (or exactly one scheme); set a default scheme or use a named policy");
282
+ else if (!["anonymous", "authenticated"].includes(defaultPolicy) && !auth.authorization.policies[defaultPolicy]) errors.push(`auth.authorization.defaultPolicy: undefined policy '${defaultPolicy}'; define auth.authorization.policies.${defaultPolicy}`);
283
+ }
284
+
177
285
  // データベース設定の検証
178
- if (config.database && !config.database.connectionString) {
286
+ if (hasConfiguredDatabase(config) && !config.database?.connectionString) {
179
287
  errors.push("Cosmos DB connection string is required");
180
288
  }
181
289
 
@@ -188,6 +296,34 @@ export function validateConfig(config: SwallowKitConfig): { valid: boolean; erro
188
296
  errors.push("Backend language must be one of: typescript, csharp, python");
189
297
  }
190
298
 
299
+ // コネクタ設定の検証
300
+ if (config.connectors) {
301
+ for (const [name, connector] of Object.entries(config.connectors)) {
302
+ if (!VALID_CONNECTOR_TYPES.includes(connector.type)) {
303
+ errors.push(`Connector '${name}': type must be one of: ${VALID_CONNECTOR_TYPES.join(", ")}`);
304
+ continue;
305
+ }
306
+
307
+ if (connector.type === "rdb") {
308
+ if (!connector.provider || !VALID_RDB_PROVIDERS.includes(connector.provider)) {
309
+ errors.push(`Connector '${name}': provider must be one of: ${VALID_RDB_PROVIDERS.join(", ")}`);
310
+ }
311
+ if (!connector.connectionEnvVar) {
312
+ errors.push(`Connector '${name}': connectionEnvVar is required`);
313
+ }
314
+ }
315
+
316
+ if (connector.type === "api") {
317
+ if (!connector.baseUrlEnvVar) {
318
+ errors.push(`Connector '${name}': baseUrlEnvVar is required`);
319
+ }
320
+ if (connector.auth && !VALID_API_AUTH_TYPES.includes(connector.auth.type)) {
321
+ errors.push(`Connector '${name}': auth.type must be one of: ${VALID_API_AUTH_TYPES.join(", ")}`);
322
+ }
323
+ }
324
+ }
325
+ }
326
+
191
327
  return {
192
328
  valid: errors.length === 0,
193
329
  errors,