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
@@ -0,0 +1,500 @@
1
+ /**
2
+ * SwallowKit Add-Auth コマンド
3
+ * 認証認可基盤ファイルを生成する
4
+ */
5
+
6
+ import * as fs from "fs";
7
+ import * as path from "path";
8
+ import { ensureSwallowKitProject, getBackendLanguage, getConnectorDefinition, getFullConfig } from "../../core/config";
9
+ import { AuthProvider, BackendLanguage, CustomJwtConfig, RdbConnectorConfig } from "../../types";
10
+ import {
11
+ generateAuthModels,
12
+ generateAuthFunctionsTS,
13
+ generateJwtHelperTS,
14
+ generateAuthFunctionsCSharp,
15
+ generateJwtHelperCSharp,
16
+ generateAuthFunctionsPython,
17
+ generateJwtHelperPython,
18
+ generateBFFAuthLoginRoute,
19
+ generateBFFAuthLogoutRoute,
20
+ generateBFFAuthMeRoute,
21
+ generateProxy,
22
+ generateLoginPage,
23
+ generateAuthContext,
24
+ generateBFFCallFunctionWithAuth,
25
+ } from "../../core/scaffold/auth-generator";
26
+ import { syncProjectManifest } from "../../core/project/manifest";
27
+
28
+ interface AddAuthOptions {
29
+ provider?: string;
30
+ }
31
+
32
+ export async function addAuthCommand(options: AddAuthOptions) {
33
+ ensureSwallowKitProject("add-auth");
34
+
35
+ console.log(" SwallowKit Add-Auth: Setting up authentication...\n");
36
+
37
+ const provider = (options.provider || "custom-jwt") as AuthProvider;
38
+ if (!["custom-jwt", "swa", "swa-custom", "none"].includes(provider)) {
39
+ console.error(` Unknown provider: ${provider}. Use: custom-jwt | swa | swa-custom | none`);
40
+ process.exit(1);
41
+ }
42
+
43
+ const backendLanguage = getBackendLanguage();
44
+ const config = getFullConfig();
45
+ const cwd = process.cwd();
46
+
47
+ // Read project name from package.json
48
+ const pkgPath = path.join(cwd, "package.json");
49
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
50
+ const projectName = pkg.name || "app";
51
+
52
+ // Read shared package name
53
+ const sharedPkgPath = path.join(cwd, "shared", "package.json");
54
+ let sharedPackageName = `@${projectName}/shared`;
55
+ if (fs.existsSync(sharedPkgPath)) {
56
+ const sharedPkg = JSON.parse(fs.readFileSync(sharedPkgPath, "utf-8"));
57
+ sharedPackageName = sharedPkg.name || sharedPackageName;
58
+ }
59
+
60
+ // Default custom-jwt config
61
+ const customJwtConfig: CustomJwtConfig = config.auth?.customJwt || {
62
+ userConnector: "mysql",
63
+ userTable: "users",
64
+ loginIdColumn: "login_id",
65
+ passwordHashColumn: "password_hash",
66
+ rolesColumn: "roles",
67
+ jwtSecretEnv: "JWT_SECRET",
68
+ tokenExpiry: "24h",
69
+ };
70
+
71
+ // 1. Generate shared/models/auth.ts
72
+ console.log(" Generating auth models...");
73
+ const modelsDir = path.join(cwd, "shared", "models");
74
+ fs.mkdirSync(modelsDir, { recursive: true });
75
+ const authModelPath = path.join(modelsDir, "auth.ts");
76
+ fs.writeFileSync(authModelPath, generateAuthModels(), "utf-8");
77
+ console.log(` Created: shared/models/auth.ts`);
78
+
79
+ // Ensure shared package has build infrastructure (tsconfig, build script)
80
+ ensureSharedBuildInfrastructure(cwd);
81
+
82
+ // Update shared/index.ts to re-export auth
83
+ updateSharedIndex(cwd);
84
+
85
+ // Resolve RDB provider for dependency installation
86
+ const connDef = getConnectorDefinition(customJwtConfig.userConnector);
87
+ const rdbProvider = (connDef as RdbConnectorConfig | undefined)?.provider ?? "mysql";
88
+
89
+ // 2. Generate Functions auth code
90
+ console.log("\n Generating auth functions...");
91
+ generateFunctionsAuth(cwd, backendLanguage, sharedPackageName, customJwtConfig);
92
+
93
+ // 3. Generate BFF auth routes
94
+ console.log("\n Generating BFF auth routes...");
95
+ generateBFFAuth(cwd, projectName, sharedPackageName);
96
+
97
+ // 4. Generate proxy
98
+ console.log("\n Generating proxy...");
99
+ const proxyPath = path.join(cwd, "proxy.ts");
100
+ fs.writeFileSync(proxyPath, generateProxy(projectName), "utf-8");
101
+ console.log(` Created: proxy.ts`);
102
+
103
+ // 5. Generate login page
104
+ console.log("\n Generating login page...");
105
+ const loginDir = path.join(cwd, "app", "login");
106
+ fs.mkdirSync(loginDir, { recursive: true });
107
+ fs.writeFileSync(path.join(loginDir, "page.tsx"), generateLoginPage(), "utf-8");
108
+ console.log(` Created: app/login/page.tsx`);
109
+
110
+ // 6. Generate auth context
111
+ console.log("\n Generating auth context...");
112
+ const authLibDir = path.join(cwd, "lib", "auth");
113
+ fs.mkdirSync(authLibDir, { recursive: true });
114
+ fs.writeFileSync(path.join(authLibDir, "auth-context.tsx"), generateAuthContext(), "utf-8");
115
+ console.log(`✅ Created: lib/auth/auth-context.tsx`);
116
+
117
+ // 7. Update callFunction with auth support
118
+ console.log("\n Updating callFunction with auth support...");
119
+ const callFnPath = path.join(cwd, "lib", "api", "call-function.ts");
120
+ const callFnDir = path.dirname(callFnPath);
121
+ fs.mkdirSync(callFnDir, { recursive: true });
122
+ fs.writeFileSync(callFnPath, generateBFFCallFunctionWithAuth(), "utf-8");
123
+ console.log(` Updated: lib/api/call-function.ts`);
124
+
125
+ // 8. Update swallowkit.config.js
126
+ console.log("\n Updating configuration...");
127
+ updateConfigWithAuth(cwd, provider, customJwtConfig);
128
+
129
+ // 9. Update environment files
130
+ console.log("\n Updating environment files...");
131
+ updateEnvironmentFiles(cwd);
132
+
133
+ // 10. Install dependencies
134
+ console.log("\n Installing auth dependencies...");
135
+ await installAuthDependencies(cwd, backendLanguage, rdbProvider);
136
+ await syncProjectManifest();
137
+
138
+ console.log("\n Authentication setup complete!");
139
+ console.log("\n Next steps:");
140
+ console.log(" 1. Review the generated files");
141
+ console.log(" 2. Set JWT_SECRET in functions/local.settings.json");
142
+ if (provider === "custom-jwt") {
143
+ console.log(" 3. Ensure your user database table matches the config");
144
+ console.log(" 4. Add authPolicy to your models for role-based access control:");
145
+ console.log(" export const authPolicy = { roles: ['admin'] };");
146
+ }
147
+ console.log(` 5. Run scaffold to regenerate functions with auth guards`);
148
+ }
149
+
150
+ /**
151
+ * Ensure the shared package has proper build infrastructure
152
+ * (tsconfig.json, build script, typescript devDependency).
153
+ * Required for `dev` command which runs `npm run --workspace=shared build`.
154
+ */
155
+ function ensureSharedBuildInfrastructure(cwd: string): void {
156
+ const sharedDir = path.join(cwd, "shared");
157
+ const pkgPath = path.join(sharedDir, "package.json");
158
+ if (!fs.existsSync(pkgPath)) return;
159
+
160
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
161
+ let updated = false;
162
+
163
+ // Ensure scripts.build exists
164
+ if (!pkg.scripts?.build) {
165
+ if (!pkg.scripts) pkg.scripts = {};
166
+ pkg.scripts.build = "tsc";
167
+ pkg.scripts.watch = "tsc --watch";
168
+ updated = true;
169
+ }
170
+
171
+ // Ensure main points to compiled output
172
+ if (!pkg.main || pkg.main === "index.ts") {
173
+ pkg.main = "dist/index.js";
174
+ pkg.types = "dist/index.d.ts";
175
+ updated = true;
176
+ }
177
+
178
+ // Ensure typescript devDependency
179
+ if (!pkg.devDependencies?.typescript) {
180
+ if (!pkg.devDependencies) pkg.devDependencies = {};
181
+ pkg.devDependencies.typescript = "^5.0.0";
182
+ updated = true;
183
+ }
184
+
185
+ if (updated) {
186
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2), "utf-8");
187
+ console.log(` Updated: shared/package.json (added build infrastructure)`);
188
+ }
189
+
190
+ // Ensure tsconfig.json exists
191
+ const tsconfigPath = path.join(sharedDir, "tsconfig.json");
192
+ if (!fs.existsSync(tsconfigPath)) {
193
+ const tsconfig = {
194
+ compilerOptions: {
195
+ target: "ES2020",
196
+ module: "commonjs",
197
+ moduleResolution: "node",
198
+ lib: ["ES2020"],
199
+ outDir: "dist",
200
+ rootDir: ".",
201
+ declaration: true,
202
+ declarationMap: true,
203
+ sourceMap: true,
204
+ strict: true,
205
+ esModuleInterop: true,
206
+ skipLibCheck: true,
207
+ forceConsistentCasingInFileNames: true,
208
+ },
209
+ include: ["index.ts", "models/**/*"],
210
+ exclude: ["node_modules", "dist"],
211
+ };
212
+ fs.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2), "utf-8");
213
+ console.log(` Created: shared/tsconfig.json`);
214
+ }
215
+ }
216
+
217
+ function updateSharedIndex(cwd: string): void {
218
+ const indexPath = path.join(cwd, "shared", "index.ts");
219
+ if (fs.existsSync(indexPath)) {
220
+ let content = fs.readFileSync(indexPath, "utf-8");
221
+ if (!content.includes("./models/auth")) {
222
+ content += `\nexport { LoginRequest, AuthUser, LoginResponse } from './models/auth';\n`;
223
+ fs.writeFileSync(indexPath, content, "utf-8");
224
+ console.log(` Updated: shared/index.ts`);
225
+ }
226
+ } else {
227
+ fs.writeFileSync(indexPath, `export { LoginRequest, AuthUser, LoginResponse } from './models/auth';\n`, "utf-8");
228
+ console.log(` Created: shared/index.ts`);
229
+ }
230
+ }
231
+
232
+ function generateFunctionsAuth(
233
+ cwd: string,
234
+ backendLanguage: BackendLanguage,
235
+ sharedPackageName: string,
236
+ config: CustomJwtConfig,
237
+ ): void {
238
+ const functionsDir = path.join(cwd, "functions");
239
+
240
+ // Resolve the RDB provider from the connector definition
241
+ const connDef = getConnectorDefinition(config.userConnector);
242
+ const provider = (connDef as RdbConnectorConfig | undefined)?.provider ?? "mysql";
243
+
244
+ if (backendLanguage === "typescript") {
245
+ // Auth functions
246
+ const srcDir = path.join(functionsDir, "src");
247
+ fs.mkdirSync(srcDir, { recursive: true });
248
+ fs.writeFileSync(
249
+ path.join(srcDir, "auth.ts"),
250
+ generateAuthFunctionsTS(sharedPackageName, config, provider),
251
+ "utf-8"
252
+ );
253
+ console.log(` Created: functions/src/auth.ts`);
254
+
255
+ // JWT helper
256
+ const authDir = path.join(srcDir, "auth");
257
+ fs.mkdirSync(authDir, { recursive: true });
258
+ fs.writeFileSync(
259
+ path.join(authDir, "jwt-helper.ts"),
260
+ generateJwtHelperTS(),
261
+ "utf-8"
262
+ );
263
+ console.log(` Created: functions/src/auth/jwt-helper.ts`);
264
+ } else if (backendLanguage === "csharp") {
265
+ const authDir = path.join(functionsDir, "Auth");
266
+ fs.mkdirSync(authDir, { recursive: true });
267
+ fs.writeFileSync(
268
+ path.join(authDir, "AuthFunctions.cs"),
269
+ generateAuthFunctionsCSharp(config, provider),
270
+ "utf-8"
271
+ );
272
+ console.log(` Created: functions/Auth/AuthFunctions.cs`);
273
+ fs.writeFileSync(
274
+ path.join(authDir, "JwtHelper.cs"),
275
+ generateJwtHelperCSharp(),
276
+ "utf-8"
277
+ );
278
+ console.log(` Created: functions/Auth/JwtHelper.cs`);
279
+ } else if (backendLanguage === "python") {
280
+ const blueprintsDir = path.join(functionsDir, "blueprints");
281
+ fs.mkdirSync(blueprintsDir, { recursive: true });
282
+ fs.writeFileSync(
283
+ path.join(blueprintsDir, "auth.py"),
284
+ generateAuthFunctionsPython(config, provider),
285
+ "utf-8"
286
+ );
287
+ console.log(` Created: functions/blueprints/auth.py`);
288
+
289
+ const authDir = path.join(functionsDir, "auth");
290
+ fs.mkdirSync(authDir, { recursive: true });
291
+ fs.writeFileSync(
292
+ path.join(authDir, "jwt_helper.py"),
293
+ generateJwtHelperPython(),
294
+ "utf-8"
295
+ );
296
+ console.log(` Created: functions/auth/jwt_helper.py`);
297
+
298
+ // __init__.py
299
+ fs.writeFileSync(path.join(authDir, "__init__.py"), "", "utf-8");
300
+
301
+ // Register auth blueprint in function_app.py
302
+ const functionAppPath = path.join(functionsDir, "function_app.py");
303
+ if (fs.existsSync(functionAppPath)) {
304
+ const content = fs.readFileSync(functionAppPath, "utf-8");
305
+ const authImport = "from blueprints.auth import bp as auth_bp";
306
+ const authRegister = "app.register_blueprint(auth_bp)";
307
+ if (!content.includes(authImport)) {
308
+ const marker = "# SwallowKit scaffold registrations";
309
+ if (content.includes(marker)) {
310
+ const updated = content.replace(
311
+ marker,
312
+ `${authImport}\n${authRegister}\n${marker}`
313
+ );
314
+ fs.writeFileSync(functionAppPath, updated, "utf-8");
315
+ console.log(` Updated: functions/function_app.py (registered auth blueprint)`);
316
+ }
317
+ }
318
+ }
319
+ }
320
+ }
321
+
322
+ function generateBFFAuth(cwd: string, projectName: string, sharedPackageName: string): void {
323
+ const authApiDir = path.join(cwd, "app", "api", "auth");
324
+
325
+ // Login route
326
+ const loginDir = path.join(authApiDir, "login");
327
+ fs.mkdirSync(loginDir, { recursive: true });
328
+ fs.writeFileSync(
329
+ path.join(loginDir, "route.ts"),
330
+ generateBFFAuthLoginRoute(projectName, sharedPackageName),
331
+ "utf-8"
332
+ );
333
+ console.log(` Created: app/api/auth/login/route.ts`);
334
+
335
+ // Logout route
336
+ const logoutDir = path.join(authApiDir, "logout");
337
+ fs.mkdirSync(logoutDir, { recursive: true });
338
+ fs.writeFileSync(
339
+ path.join(logoutDir, "route.ts"),
340
+ generateBFFAuthLogoutRoute(projectName),
341
+ "utf-8"
342
+ );
343
+ console.log(` Created: app/api/auth/logout/route.ts`);
344
+
345
+ // Me route
346
+ const meDir = path.join(authApiDir, "me");
347
+ fs.mkdirSync(meDir, { recursive: true });
348
+ fs.writeFileSync(
349
+ path.join(meDir, "route.ts"),
350
+ generateBFFAuthMeRoute(),
351
+ "utf-8"
352
+ );
353
+ console.log(` Created: app/api/auth/me/route.ts`);
354
+ }
355
+
356
+ function updateConfigWithAuth(cwd: string, provider: AuthProvider, config: CustomJwtConfig): void {
357
+ const configPath = path.join(cwd, "swallowkit.config.js");
358
+ if (!fs.existsSync(configPath)) {
359
+ console.warn(" swallowkit.config.js not found. Please add auth config manually.");
360
+ return;
361
+ }
362
+
363
+ const content = fs.readFileSync(configPath, "utf-8");
364
+
365
+ if (content.includes("auth:") || content.includes("auth :")) {
366
+ console.log(" 'auth' section already exists in swallowkit.config.js");
367
+ return;
368
+ }
369
+
370
+ // Find the last property before the closing of module.exports
371
+ const closingBraceIdx = content.lastIndexOf("}");
372
+ if (closingBraceIdx === -1) {
373
+ console.error(" Could not parse config file structure.");
374
+ return;
375
+ }
376
+
377
+ const beforeClosing = content.substring(0, closingBraceIdx).trimEnd();
378
+ const needsComma = !beforeClosing.endsWith(",") && !beforeClosing.endsWith("{");
379
+
380
+ const authBlock = `${needsComma ? "," : ""}
381
+ // 認証認可設定
382
+ auth: {
383
+ provider: '${provider}',
384
+ customJwt: {
385
+ userConnector: '${config.userConnector}',
386
+ userTable: '${config.userTable}',
387
+ loginIdColumn: '${config.loginIdColumn}',
388
+ passwordHashColumn: '${config.passwordHashColumn}',
389
+ rolesColumn: '${config.rolesColumn}',
390
+ jwtSecretEnv: '${config.jwtSecretEnv || "JWT_SECRET"}',
391
+ tokenExpiry: '${config.tokenExpiry || "24h"}',
392
+ },
393
+ authorization: {
394
+ defaultPolicy: 'authenticated',
395
+ },
396
+ },
397
+ `;
398
+
399
+ const newContent = content.substring(0, closingBraceIdx) + authBlock + content.substring(closingBraceIdx);
400
+ fs.writeFileSync(configPath, newContent, "utf-8");
401
+ console.log(` Updated: swallowkit.config.js`);
402
+ }
403
+
404
+ function updateEnvironmentFiles(cwd: string): void {
405
+ // Update functions/local.settings.json
406
+ const localSettingsPath = path.join(cwd, "functions", "local.settings.json");
407
+ if (fs.existsSync(localSettingsPath)) {
408
+ const settings = JSON.parse(fs.readFileSync(localSettingsPath, "utf-8"));
409
+ if (!settings.Values) settings.Values = {};
410
+ if (!settings.Values.JWT_SECRET) {
411
+ settings.Values.JWT_SECRET = "dev-jwt-secret-change-in-production-min-32-chars!!";
412
+ }
413
+ fs.writeFileSync(localSettingsPath, JSON.stringify(settings, null, 2), "utf-8");
414
+ console.log(` Updated: functions/local.settings.json`);
415
+ }
416
+
417
+ // Update .env.example
418
+ const envExamplePath = path.join(cwd, ".env.example");
419
+ if (fs.existsSync(envExamplePath)) {
420
+ let content = fs.readFileSync(envExamplePath, "utf-8");
421
+ if (!content.includes("JWT_SECRET")) {
422
+ content += "\n# Authentication\nJWT_SECRET=your-jwt-secret-key-at-least-32-chars\n";
423
+ fs.writeFileSync(envExamplePath, content, "utf-8");
424
+ console.log(` Updated: .env.example`);
425
+ }
426
+ }
427
+ }
428
+
429
+ async function installAuthDependencies(cwd: string, backendLanguage: BackendLanguage, provider: "mysql" | "postgres" | "sqlserver" = "mysql"): Promise<void> {
430
+ if (backendLanguage === "typescript") {
431
+ const funcPkgPath = path.join(cwd, "functions", "package.json");
432
+ if (fs.existsSync(funcPkgPath)) {
433
+ const funcPkg = JSON.parse(fs.readFileSync(funcPkgPath, "utf-8"));
434
+ if (!funcPkg.dependencies) funcPkg.dependencies = {};
435
+ funcPkg.dependencies["jsonwebtoken"] = "^9.0.0";
436
+ funcPkg.dependencies["bcryptjs"] = "^2.4.3";
437
+ // RDB driver based on provider
438
+ if (provider === "mysql") funcPkg.dependencies["mysql2"] = "^3.11.0";
439
+ else if (provider === "postgres") funcPkg.dependencies["pg"] = "^8.13.0";
440
+ else funcPkg.dependencies["mssql"] = "^11.0.0";
441
+ if (!funcPkg.devDependencies) funcPkg.devDependencies = {};
442
+ funcPkg.devDependencies["@types/jsonwebtoken"] = "^9.0.0";
443
+ funcPkg.devDependencies["@types/bcryptjs"] = "^2.4.0";
444
+ if (provider === "postgres") funcPkg.devDependencies["@types/pg"] = "^8.11.0";
445
+ fs.writeFileSync(funcPkgPath, JSON.stringify(funcPkg, null, 2), "utf-8");
446
+ console.log(` Updated: functions/package.json with auth dependencies (${provider})`);
447
+ }
448
+ } else if (backendLanguage === "csharp") {
449
+ // Add NuGet package references to .csproj
450
+ const functionsDir = path.join(cwd, "functions");
451
+ const csprojFiles = fs.readdirSync(functionsDir).filter((f: string) => f.endsWith(".csproj"));
452
+ if (csprojFiles.length > 0) {
453
+ const csprojPath = path.join(functionsDir, csprojFiles[0]);
454
+ let csprojContent = fs.readFileSync(csprojPath, "utf-8");
455
+ const nugetPackages: { name: string; version: string }[] = [
456
+ { name: "System.IdentityModel.Tokens.Jwt", version: "7.0.0" },
457
+ { name: "Microsoft.IdentityModel.Tokens", version: "7.0.0" },
458
+ { name: "BCrypt.Net-Next", version: "4.0.3" },
459
+ ];
460
+ // RDB driver based on provider
461
+ if (provider === "mysql") nugetPackages.push({ name: "MySqlConnector", version: "2.3.0" });
462
+ else if (provider === "postgres") nugetPackages.push({ name: "Npgsql", version: "8.0.0" });
463
+ else nugetPackages.push({ name: "Microsoft.Data.SqlClient", version: "5.2.0" });
464
+ for (const pkg of nugetPackages) {
465
+ if (!csprojContent.includes(`"${pkg.name}"`)) {
466
+ const insertPoint = csprojContent.lastIndexOf("</ItemGroup>");
467
+ if (insertPoint >= 0) {
468
+ csprojContent =
469
+ csprojContent.slice(0, insertPoint) +
470
+ ` <PackageReference Include="${pkg.name}" Version="${pkg.version}" />\n ` +
471
+ csprojContent.slice(insertPoint);
472
+ }
473
+ }
474
+ }
475
+ fs.writeFileSync(csprojPath, csprojContent, "utf-8");
476
+ console.log(` Updated: ${csprojFiles[0]} with auth NuGet packages (${provider})`);
477
+ }
478
+ } else if (backendLanguage === "python") {
479
+ // Add Python dependencies to requirements.txt
480
+ const requirementsPath = path.join(cwd, "functions", "requirements.txt");
481
+ const baseDeps = ["PyJWT>=2.8.0", "bcrypt>=4.1.0"];
482
+ // RDB driver based on provider
483
+ if (provider === "mysql") baseDeps.push("mysql-connector-python>=8.3.0");
484
+ else if (provider === "postgres") baseDeps.push("psycopg2-binary>=2.9.0");
485
+ else baseDeps.push("pymssql>=2.2.0");
486
+ if (fs.existsSync(requirementsPath)) {
487
+ let content = fs.readFileSync(requirementsPath, "utf-8");
488
+ for (const dep of baseDeps) {
489
+ const pkgName = dep.split(">=")[0].split("==")[0];
490
+ if (!content.includes(pkgName)) {
491
+ content += `${dep}\n`;
492
+ }
493
+ }
494
+ fs.writeFileSync(requirementsPath, content, "utf-8");
495
+ } else {
496
+ fs.writeFileSync(requirementsPath, baseDeps.join("\n") + "\n", "utf-8");
497
+ }
498
+ console.log(` Updated: functions/requirements.txt with auth dependencies`);
499
+ }
500
+ }
@@ -0,0 +1,158 @@
1
+ /**
2
+ * SwallowKit Add-Connector コマンド
3
+ * swallowkit.config.js にコネクタ設定を追加する
4
+ */
5
+
6
+ import * as fs from "fs";
7
+ import * as path from "path";
8
+ import { ensureSwallowKitProject } from "../../core/config";
9
+ import { ApiConnectorConfig, ConnectorDefinition } from "../../types";
10
+ import { syncProjectManifest } from "../../core/project/manifest";
11
+
12
+ interface AddConnectorOptions {
13
+ name: string;
14
+ type: "rdb" | "api";
15
+ provider?: "mysql" | "postgres" | "sqlserver";
16
+ }
17
+
18
+ /**
19
+ * add-connector コマンド
20
+ */
21
+ export async function addConnectorCommand(options: AddConnectorOptions) {
22
+ ensureSwallowKitProject("add-connector");
23
+
24
+ console.log(`🔌 SwallowKit Add-Connector: Adding '${options.name}' connector...\n`);
25
+
26
+ const configPath = findConfigFile();
27
+ if (!configPath) {
28
+ console.error("❌ swallowkit.config.js not found. Run 'swallowkit init' first.");
29
+ process.exit(1);
30
+ }
31
+
32
+ // Build connector definition
33
+ const connectorDef = buildConnectorDefinition(options);
34
+
35
+ // Update config file
36
+ updateConfigWithConnector(configPath, options.name, connectorDef);
37
+ await syncProjectManifest();
38
+
39
+ console.log(`\n✅ Connector '${options.name}' added successfully!`);
40
+ console.log("\n📝 Next steps:");
41
+ console.log(` 1. Review the connector settings in ${configPath}`);
42
+ console.log(` 2. Set the required environment variables in functions/local.settings.json`);
43
+ console.log(` 3. Create models with: npx swallowkit create-model <name> --connector=${options.name}`);
44
+ }
45
+
46
+ function findConfigFile(): string | null {
47
+ const candidates = ["swallowkit.config.js", "swallowkit.config.json", ".swallowkitrc.json"];
48
+ for (const candidate of candidates) {
49
+ const fullPath = path.resolve(process.cwd(), candidate);
50
+ if (fs.existsSync(fullPath)) {
51
+ return fullPath;
52
+ }
53
+ }
54
+ return null;
55
+ }
56
+
57
+ function buildConnectorDefinition(options: AddConnectorOptions): ConnectorDefinition {
58
+ const name = options.name.toUpperCase().replace(/[^A-Z0-9]/g, "_");
59
+
60
+ if (options.type === "rdb") {
61
+ return {
62
+ type: "rdb",
63
+ provider: options.provider || "mysql",
64
+ connectionEnvVar: `${name}_CONNECTION_STRING`,
65
+ };
66
+ }
67
+
68
+ return {
69
+ type: "api",
70
+ baseUrlEnvVar: `${name}_API_BASE_URL`,
71
+ auth: {
72
+ type: "apiKey",
73
+ envVar: `${name}_API_KEY`,
74
+ placement: "query",
75
+ paramName: "apiKey",
76
+ },
77
+ };
78
+ }
79
+
80
+ /**
81
+ * swallowkit.config.js にコネクタ設定を追加
82
+ */
83
+ export function updateConfigWithConnector(
84
+ configPath: string,
85
+ connectorName: string,
86
+ connectorDef: ConnectorDefinition
87
+ ): void {
88
+ const content = fs.readFileSync(configPath, "utf-8");
89
+
90
+ if (configPath.endsWith(".json")) {
91
+ // JSON config
92
+ const config = JSON.parse(content);
93
+ if (!config.connectors) {
94
+ config.connectors = {};
95
+ }
96
+ config.connectors[connectorName] = connectorDef;
97
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
98
+ console.log(`✅ Updated: ${configPath}`);
99
+ return;
100
+ }
101
+
102
+ // JS config — append connectors section
103
+ if (content.includes("connectors:") || content.includes("connectors :")) {
104
+ console.log(`⚠️ 'connectors' section already exists in ${configPath}. Please add the connector manually:`);
105
+ console.log(formatConnectorSnippet(connectorName, connectorDef));
106
+ return;
107
+ }
108
+
109
+ // Find the last property before the closing of module.exports
110
+ const closingBraceIdx = content.lastIndexOf("}");
111
+ if (closingBraceIdx === -1) {
112
+ console.error("❌ Could not parse config file structure. Please add the connector manually:");
113
+ console.log(formatConnectorSnippet(connectorName, connectorDef));
114
+ return;
115
+ }
116
+
117
+ // Check if we need a comma before inserting
118
+ const beforeClosing = content.substring(0, closingBraceIdx).trimEnd();
119
+ const needsComma = !beforeClosing.endsWith(",") && !beforeClosing.endsWith("{");
120
+
121
+ const connectorBlock = generateConnectorJSBlock(connectorName, connectorDef);
122
+ const insertion = `${needsComma ? "," : ""}\n // コネクタ定義\n connectors: {\n${connectorBlock}\n },\n`;
123
+
124
+ const newContent = content.substring(0, closingBraceIdx) + insertion + content.substring(closingBraceIdx);
125
+ fs.writeFileSync(configPath, newContent, "utf-8");
126
+ console.log(`✅ Updated: ${configPath}`);
127
+ }
128
+
129
+ function generateConnectorJSBlock(name: string, def: ConnectorDefinition): string {
130
+ if (def.type === "rdb") {
131
+ return ` ${name}: {
132
+ type: 'rdb',
133
+ provider: '${def.provider}',
134
+ connectionEnvVar: '${def.connectionEnvVar}',
135
+ },`;
136
+ }
137
+
138
+ const apiDef: ApiConnectorConfig = def;
139
+ let authBlock = "";
140
+ if (apiDef.auth) {
141
+ authBlock = `
142
+ auth: {
143
+ type: '${apiDef.auth.type}',
144
+ envVar: '${apiDef.auth.envVar}',
145
+ placement: '${apiDef.auth.placement || "query"}',
146
+ paramName: '${apiDef.auth.paramName || "apiKey"}',
147
+ },`;
148
+ }
149
+
150
+ return ` ${name}: {
151
+ type: 'api',
152
+ baseUrlEnvVar: '${apiDef.baseUrlEnvVar}',${authBlock}
153
+ },`;
154
+ }
155
+
156
+ function formatConnectorSnippet(name: string, def: ConnectorDefinition): string {
157
+ return `\n ${name}: ${JSON.stringify(def, null, 4)}\n`;
158
+ }