swallowkit 1.0.0-beta.57 → 1.0.0-beta.59

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 (73) hide show
  1. package/README.ja.md +1 -0
  2. package/README.md +1 -0
  3. package/dist/cli/commands/add-auth.d.ts +3 -1
  4. package/dist/cli/commands/add-auth.d.ts.map +1 -1
  5. package/dist/cli/commands/add-auth.js +55 -21
  6. package/dist/cli/commands/add-auth.js.map +1 -1
  7. package/dist/cli/commands/init.d.ts.map +1 -1
  8. package/dist/cli/commands/init.js +12 -498
  9. package/dist/cli/commands/init.js.map +1 -1
  10. package/dist/cli/index.d.ts.map +1 -1
  11. package/dist/cli/index.js +2 -0
  12. package/dist/cli/index.js.map +1 -1
  13. package/dist/core/operations/auth-operations.d.ts +4 -0
  14. package/dist/core/operations/auth-operations.d.ts.map +1 -1
  15. package/dist/core/operations/auth-operations.js +5 -2
  16. package/dist/core/operations/auth-operations.js.map +1 -1
  17. package/dist/core/operations/create-model.d.ts.map +1 -1
  18. package/dist/core/operations/create-model.js +6 -2
  19. package/dist/core/operations/create-model.js.map +1 -1
  20. package/dist/core/project/agent-instructions.d.ts +29 -0
  21. package/dist/core/project/agent-instructions.d.ts.map +1 -0
  22. package/dist/core/project/agent-instructions.js +159 -0
  23. package/dist/core/project/agent-instructions.js.map +1 -0
  24. package/dist/core/project/capabilities.d.ts +58 -0
  25. package/dist/core/project/capabilities.d.ts.map +1 -0
  26. package/dist/core/project/capabilities.js +135 -0
  27. package/dist/core/project/capabilities.js.map +1 -0
  28. package/dist/core/project/validation.d.ts.map +1 -1
  29. package/dist/core/project/validation.js +48 -0
  30. package/dist/core/project/validation.js.map +1 -1
  31. package/dist/core/project/workflows.d.ts.map +1 -1
  32. package/dist/core/project/workflows.js +46 -2
  33. package/dist/core/project/workflows.js.map +1 -1
  34. package/dist/core/scaffold/auth-generator.d.ts.map +1 -1
  35. package/dist/core/scaffold/auth-generator.js +6 -2
  36. package/dist/core/scaffold/auth-generator.js.map +1 -1
  37. package/dist/core/verify/index.d.ts +7 -0
  38. package/dist/core/verify/index.d.ts.map +1 -1
  39. package/dist/core/verify/index.js +69 -0
  40. package/dist/core/verify/index.js.map +1 -1
  41. package/dist/machine/index.d.ts.map +1 -1
  42. package/dist/machine/index.js +117 -41
  43. package/dist/machine/index.js.map +1 -1
  44. package/dist/mcp/index.d.ts.map +1 -1
  45. package/dist/mcp/index.js +32 -10
  46. package/dist/mcp/index.js.map +1 -1
  47. package/package.json +1 -1
  48. package/src/__tests__/agent-instructions.test.ts +237 -0
  49. package/src/__tests__/agent-loop-phase34.test.ts +66 -2
  50. package/src/__tests__/auth.test.ts +9 -0
  51. package/src/__tests__/init.test.ts +2 -0
  52. package/src/__tests__/machine.test.ts +15 -0
  53. package/src/__tests__/multi-auth.test.ts +12 -0
  54. package/src/__tests__/plan-apply.test.ts +38 -0
  55. package/src/cli/commands/add-auth.ts +59 -21
  56. package/src/cli/commands/init.ts +19 -525
  57. package/src/cli/index.ts +2 -0
  58. package/src/core/operations/auth-operations.ts +9 -2
  59. package/src/core/operations/create-model.ts +7 -2
  60. package/src/core/project/agent-instructions.ts +167 -0
  61. package/src/core/project/agent-templates/AGENTS.md +502 -0
  62. package/src/core/project/agent-templates/CLAUDE.md +31 -0
  63. package/src/core/project/agent-templates/azure-functions.instructions.md +22 -0
  64. package/src/core/project/agent-templates/bff-routes.instructions.md +18 -0
  65. package/src/core/project/agent-templates/copilot-instructions.md +27 -0
  66. package/src/core/project/agent-templates/shared-models.instructions.md +72 -0
  67. package/src/core/project/capabilities.ts +193 -0
  68. package/src/core/project/validation.ts +50 -0
  69. package/src/core/project/workflows.ts +48 -2
  70. package/src/core/scaffold/auth-generator.ts +7 -2
  71. package/src/core/verify/index.ts +76 -0
  72. package/src/machine/index.ts +133 -45
  73. package/src/mcp/index.ts +28 -10
@@ -51,6 +51,29 @@ import { buildSharedTsConfig } from "./init";
51
51
  interface AddAuthOptions {
52
52
  provider?: string;
53
53
  scheme?: string;
54
+ /** SWA の許可する identity provider (例: ['github'])。未指定時は ['aad']。 */
55
+ allowedProviders?: string[];
56
+ }
57
+
58
+ const SWA_PROVIDER_LABELS: Record<string, string> = {
59
+ aad: "Microsoft",
60
+ github: "GitHub",
61
+ google: "Google",
62
+ twitter: "X",
63
+ apple: "Apple",
64
+ facebook: "Facebook",
65
+ };
66
+
67
+ function swaProviderLabel(provider: string): string {
68
+ return SWA_PROVIDER_LABELS[provider] ?? provider.charAt(0).toUpperCase() + provider.slice(1);
69
+ }
70
+
71
+ function normalizeSwaProviders(allowedProviders?: string[]): string[] {
72
+ const providers = (allowedProviders ?? []).map((p) => p.trim().toLowerCase()).filter(Boolean);
73
+ for (const provider of providers) {
74
+ if (!/^[a-z0-9-]+$/.test(provider)) throw new Error(`Invalid SWA identity provider name: ${provider}`);
75
+ }
76
+ return providers.length > 0 ? providers : ["aad"];
54
77
  }
55
78
 
56
79
  const AUTH_GENERATOR = "add-auth";
@@ -75,7 +98,8 @@ function defaultCustomJwtConfig(): CustomJwtConfig {
75
98
  };
76
99
  }
77
100
 
78
- function setupSwaAuth(cwd: string, backendLanguage: BackendLanguage, session: FileOperationSession): void {
101
+ function setupSwaAuth(cwd: string, backendLanguage: BackendLanguage, session: FileOperationSession, allowedProviders?: string[]): void {
102
+ const providers = normalizeSwaProviders(allowedProviders);
79
103
  const helperPath = backendLanguage === "typescript"
80
104
  ? path.join(cwd, "functions", "src", "auth", "jwt-helper.ts")
81
105
  : backendLanguage === "csharp"
@@ -92,11 +116,11 @@ function setupSwaAuth(cwd: string, backendLanguage: BackendLanguage, session: Fi
92
116
  session.writeFile(callFnPath, generateBFFCallFunctionWithSwaAuth(), managedMeta);
93
117
 
94
118
  const authDir = path.join(cwd, "lib", "auth");
95
- session.writeFile(path.join(authDir, "auth-context.tsx"), generateSwaAuthContext(), managedMeta);
119
+ session.writeFile(path.join(authDir, "auth-context.tsx"), generateSwaAuthContext(providers), managedMeta);
96
120
  updateRootLayoutWithAuthProvider(cwd, session);
97
121
  const loginDir = path.join(cwd, "app", "login");
98
- session.writeFile(path.join(loginDir, "page.tsx"), generateSwaLoginPage(), managedMeta);
99
- updateSwaRouteConfig(cwd, session);
122
+ session.writeFile(path.join(loginDir, "page.tsx"), generateSwaLoginPage(providers), managedMeta);
123
+ updateSwaRouteConfig(cwd, session, providers);
100
124
  }
101
125
 
102
126
  function setupExternalTokenAuth(cwd: string, backendLanguage: BackendLanguage, session: FileOperationSession): void {
@@ -143,15 +167,21 @@ function updatePythonAuthMeRegistration(cwd: string, session: FileOperationSessi
143
167
  session.writeFile(appPath, content, extensionMeta);
144
168
  }
145
169
 
146
- function generateSwaLoginPage(): string {
170
+ function generateSwaLoginPage(allowedProviders?: string[]): string {
171
+ const providers = normalizeSwaProviders(allowedProviders);
172
+ const links = providers
173
+ .map((provider) => `<a href="/.auth/login/${provider}?post_login_redirect_uri=/">Sign in with ${swaProviderLabel(provider)}</a>`)
174
+ .join("");
147
175
  return `'use client';
148
176
  export default function LoginPage() {
149
- return <main><h1>Sign in</h1><a href="/.auth/login/aad?post_login_redirect_uri=/">Sign in with Microsoft</a></main>;
177
+ return <main><h1>Sign in</h1>${links}</main>;
150
178
  }
151
179
  `;
152
180
  }
153
181
 
154
- export function generateSwaAuthContext(): string {
182
+ export function generateSwaAuthContext(allowedProviders?: string[]): string {
183
+ const providers = normalizeSwaProviders(allowedProviders);
184
+ const primaryProvider = providers[0];
155
185
  return `'use client';
156
186
  import { createContext, useContext, useEffect, useState, type ReactNode } from 'react';
157
187
 
@@ -184,7 +214,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
184
214
  .catch(() => setUser(null))
185
215
  .finally(() => setLoading(false));
186
216
  }, []);
187
- const login = () => { window.location.href = '/.auth/login/aad?post_login_redirect_uri=/'; };
217
+ const login = () => { window.location.href = '/.auth/login/${primaryProvider}?post_login_redirect_uri=/'; };
188
218
  const logout = () => { window.location.href = '/.auth/logout?post_logout_redirect_uri=/'; };
189
219
  const hasRole = (role: string) => user?.roles.includes(role) ?? false;
190
220
  return <AuthContext.Provider value={{ user, loading, login, logout, hasRole, hasAnyRole: roles => roles.some(hasRole) }}>{children}</AuthContext.Provider>;
@@ -219,14 +249,15 @@ function updateRootLayoutWithAuthProvider(cwd: string, session: FileOperationSes
219
249
  if (updated !== layout) session.writeFile(layoutPath, updated, extensionMeta);
220
250
  }
221
251
 
222
- function updateSwaRouteConfig(cwd: string, session: FileOperationSession): void {
252
+ function updateSwaRouteConfig(cwd: string, session: FileOperationSession, allowedProviders?: string[]): void {
253
+ const primaryProvider = normalizeSwaProviders(allowedProviders)[0];
223
254
  const configPath = path.join(cwd, "staticwebapp.config.json");
224
255
  const config = session.fileExists(configPath) ? JSON.parse(session.readFile(configPath)) : {};
225
256
  config.routes = Array.isArray(config.routes)
226
257
  ? config.routes.filter((route: { route?: string }) => route.route !== "/api/*")
227
258
  : [];
228
259
  config.routes.unshift({ route: "/api/*", allowedRoles: ["authenticated"] });
229
- config.responseOverrides = { ...(config.responseOverrides || {}), "401": { statusCode: 302, redirect: "/.auth/login/aad?post_login_redirect_uri=.referrer" } };
260
+ config.responseOverrides = { ...(config.responseOverrides || {}), "401": { statusCode: 302, redirect: `/.auth/login/${primaryProvider}?post_login_redirect_uri=.referrer` } };
230
261
  session.writeFile(configPath, JSON.stringify(config, null, 2), extensionMeta);
231
262
  }
232
263
 
@@ -257,7 +288,7 @@ export async function addAuthCommand(options: AddAuthOptions) {
257
288
  if (options.scheme) {
258
289
  if (!/^[A-Za-z][A-Za-z0-9_-]*$/.test(options.scheme)) throw new Error("--scheme must start with a letter and contain only letters, digits, '_' or '-'");
259
290
  if (provider === "none" || provider === "swa-custom") throw new Error(`Provider '${provider}' cannot be added as a named scheme`);
260
- addNamedScheme(cwd, options.scheme, provider, backendLanguage, session);
291
+ addNamedScheme(cwd, options.scheme, provider, backendLanguage, session, options.allowedProviders);
261
292
  await finalize();
262
293
  console.log(`\n Named authentication scheme '${options.scheme}' added.`);
263
294
  return;
@@ -273,8 +304,8 @@ export async function addAuthCommand(options: AddAuthOptions) {
273
304
  return;
274
305
  }
275
306
  if (provider === "swa") {
276
- setupSwaAuth(cwd, backendLanguage, session);
277
- updateConfigWithAuth(cwd, provider, config.auth?.customJwt || defaultCustomJwtConfig(), session);
307
+ setupSwaAuth(cwd, backendLanguage, session, options.allowedProviders);
308
+ updateConfigWithAuth(cwd, provider, config.auth?.customJwt || defaultCustomJwtConfig(), session, options.allowedProviders);
278
309
  await finalize();
279
310
  console.log("\n SWA built-in authentication setup complete!");
280
311
  return;
@@ -392,17 +423,22 @@ function findObjectEnd(content: string, open: number): number {
392
423
  return -1;
393
424
  }
394
425
 
395
- function updateConfigWithNamedScheme(cwd: string, scheme: string, provider: AuthProvider, session: FileOperationSession): void {
426
+ function updateConfigWithNamedScheme(cwd: string, scheme: string, provider: AuthProvider, session: FileOperationSession, allowedProviders?: string[]): void {
396
427
  const jsPath = path.join(cwd, "swallowkit.config.js");
397
428
  const jsonPath = path.join(cwd, "swallowkit.config.json");
398
429
  const configPath = session.fileExists(jsPath) ? jsPath : jsonPath;
399
430
  if (!session.fileExists(configPath)) throw new Error("swallowkit.config.js or swallowkit.config.json is required");
431
+ const swaProviders = provider === "swa" ? normalizeSwaProviders(allowedProviders) : undefined;
400
432
  if (configPath.endsWith(".json")) {
401
433
  const value = JSON.parse(session.readFile(configPath));
402
434
  value.auth ??= {};
403
435
  value.auth.schemes ??= {};
404
436
  if (value.auth.schemes[scheme]) throw new Error(`auth.schemes.${scheme} already exists; no files were changed`);
405
- value.auth.schemes[scheme] = provider === "custom-jwt" ? { provider, customJwt: defaultCustomJwtConfig() } : { provider };
437
+ value.auth.schemes[scheme] = provider === "custom-jwt"
438
+ ? { provider, customJwt: defaultCustomJwtConfig() }
439
+ : swaProviders
440
+ ? { provider, swa: { allowedProviders: swaProviders } }
441
+ : { provider };
406
442
  value.auth.authorization ??= { defaultPolicy: "anonymous", policies: {} };
407
443
  session.writeFile(configPath, JSON.stringify(value, null, 2) + "\n", extensionMeta);
408
444
  return;
@@ -412,7 +448,9 @@ function updateConfigWithNamedScheme(cwd: string, scheme: string, provider: Auth
412
448
  const customJwt = defaultCustomJwtConfig();
413
449
  const entry = provider === "custom-jwt"
414
450
  ? `\n ${scheme}: { provider: 'custom-jwt', customJwt: ${JSON.stringify(customJwt)} },`
415
- : `\n ${scheme}: { provider: '${provider}' },`;
451
+ : swaProviders
452
+ ? `\n ${scheme}: { provider: '${provider}', swa: { allowedProviders: [${swaProviders.map((p) => `'${p}'`).join(", ")}] } },`
453
+ : `\n ${scheme}: { provider: '${provider}' },`;
416
454
  if (!authMatch) {
417
455
  const rootEnd = content.lastIndexOf("}");
418
456
  const beforeRootEnd = content.slice(0, rootEnd).trimEnd();
@@ -436,9 +474,9 @@ function updateConfigWithNamedScheme(cwd: string, scheme: string, provider: Auth
436
474
  session.writeFile(configPath, content, extensionMeta);
437
475
  }
438
476
 
439
- function addNamedScheme(cwd: string, scheme: string, provider: AuthProvider, language: BackendLanguage, session: FileOperationSession): void {
477
+ function addNamedScheme(cwd: string, scheme: string, provider: AuthProvider, language: BackendLanguage, session: FileOperationSession, allowedProviders?: string[]): void {
440
478
  // Validate/update config before creating files, so duplicate names stop safely.
441
- updateConfigWithNamedScheme(cwd, scheme, provider, session);
479
+ updateConfigWithNamedScheme(cwd, scheme, provider, session, allowedProviders);
442
480
  const slug = scheme.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/_/g, "-").toLowerCase();
443
481
  if (language === "typescript") {
444
482
  const dir = path.join(cwd, "functions", "src", "auth", "schemes", slug);
@@ -462,7 +500,7 @@ function addNamedScheme(cwd: string, scheme: string, provider: AuthProvider, lan
462
500
  writeIfMissing(session, path.join(clientDir, "authenticated-fetch.ts"), generateAuthenticatedFetch().replace("'./external-token-adapter'", "'./token-adapter'"));
463
501
  writeIfMissing(session, path.join(clientDir, "auth-context.tsx"), generateExternalTokenAuthContext().replace(/external-token-adapter/g, "token-adapter"));
464
502
  } else if (provider === "swa") {
465
- writeIfMissing(session, path.join(clientDir, "auth-context.tsx"), generateSwaAuthContext());
503
+ writeIfMissing(session, path.join(clientDir, "auth-context.tsx"), generateSwaAuthContext(allowedProviders));
466
504
  } else if (provider === "custom-jwt") {
467
505
  writeIfMissing(session, path.join(clientDir, "auth-context.tsx"), generateAuthContext());
468
506
  }
@@ -659,7 +697,7 @@ function generateBFFAuth(cwd: string, projectName: string, sharedPackageName: st
659
697
  console.log(` Created: app/api/auth/me/route.ts`);
660
698
  }
661
699
 
662
- function updateConfigWithAuth(cwd: string, provider: AuthProvider, config: CustomJwtConfig, session: FileOperationSession): void {
700
+ function updateConfigWithAuth(cwd: string, provider: AuthProvider, config: CustomJwtConfig, session: FileOperationSession, allowedProviders?: string[]): void {
663
701
  const configPath = path.join(cwd, "swallowkit.config.js");
664
702
  if (!session.fileExists(configPath)) {
665
703
  console.warn(" swallowkit.config.js not found. Please add auth config manually.");
@@ -694,7 +732,7 @@ function updateConfigWithAuth(cwd: string, provider: AuthProvider, config: Custo
694
732
  tokenExpiry: '${config.tokenExpiry || "24h"}',
695
733
  },` : provider === "swa" ? `
696
734
  swa: {
697
- allowedProviders: ['aad'],
735
+ allowedProviders: [${normalizeSwaProviders(allowedProviders).map((p) => `'${p}'`).join(", ")}],
698
736
  roleSource: 'swa-roles',
699
737
  },` : "";
700
738