@codaijs/keel 0.2.2 → 0.2.4

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 (80) hide show
  1. package/dist/__tests__/sail-installer.test.js +25 -25
  2. package/dist/sail-installer.js +174 -174
  3. package/dist/scaffold.js +68 -68
  4. package/package.json +58 -58
  5. package/sails/_template/addon.json +20 -20
  6. package/sails/_template/install.ts +402 -402
  7. package/sails/admin-dashboard/README.md +117 -117
  8. package/sails/admin-dashboard/addon.json +28 -28
  9. package/sails/admin-dashboard/files/backend/middleware/admin.ts +34 -34
  10. package/sails/admin-dashboard/files/backend/routes/admin.ts +243 -243
  11. package/sails/admin-dashboard/files/frontend/components/admin/StatsCard.tsx +40 -40
  12. package/sails/admin-dashboard/files/frontend/components/admin/UsersTable.tsx +240 -240
  13. package/sails/admin-dashboard/files/frontend/hooks/useAdmin.ts +149 -149
  14. package/sails/admin-dashboard/files/frontend/pages/admin/Dashboard.tsx +173 -173
  15. package/sails/admin-dashboard/files/frontend/pages/admin/UserDetail.tsx +203 -203
  16. package/sails/admin-dashboard/install.ts +305 -305
  17. package/sails/analytics/README.md +178 -178
  18. package/sails/analytics/addon.json +27 -27
  19. package/sails/analytics/files/frontend/components/AnalyticsProvider.tsx +58 -58
  20. package/sails/analytics/files/frontend/hooks/useAnalytics.ts +64 -64
  21. package/sails/analytics/files/frontend/lib/analytics.ts +103 -103
  22. package/sails/analytics/install.ts +297 -297
  23. package/sails/file-uploads/addon.json +30 -30
  24. package/sails/file-uploads/files/backend/routes/files.ts +198 -198
  25. package/sails/file-uploads/files/backend/schema/files.ts +36 -36
  26. package/sails/file-uploads/files/backend/services/file-storage.ts +128 -128
  27. package/sails/file-uploads/files/frontend/components/FileList.tsx +248 -248
  28. package/sails/file-uploads/files/frontend/components/FileUploadButton.tsx +147 -147
  29. package/sails/file-uploads/files/frontend/hooks/useFileUpload.ts +106 -106
  30. package/sails/file-uploads/files/frontend/hooks/useFiles.ts +118 -118
  31. package/sails/file-uploads/files/frontend/pages/Files.tsx +37 -37
  32. package/sails/file-uploads/install.ts +466 -466
  33. package/sails/gdpr/README.md +174 -174
  34. package/sails/gdpr/addon.json +27 -27
  35. package/sails/gdpr/files/backend/routes/gdpr.ts +140 -140
  36. package/sails/gdpr/files/backend/services/gdpr.ts +293 -293
  37. package/sails/gdpr/files/frontend/components/auth/ConsentCheckboxes.tsx +97 -97
  38. package/sails/gdpr/files/frontend/components/gdpr/AccountDeletionRequest.tsx +192 -192
  39. package/sails/gdpr/files/frontend/components/gdpr/DataExportButton.tsx +75 -75
  40. package/sails/gdpr/files/frontend/pages/PrivacyPolicy.tsx +186 -186
  41. package/sails/gdpr/install.ts +756 -756
  42. package/sails/google-oauth/README.md +121 -121
  43. package/sails/google-oauth/addon.json +22 -22
  44. package/sails/google-oauth/files/GoogleButton.tsx +50 -50
  45. package/sails/google-oauth/install.ts +252 -252
  46. package/sails/i18n/README.md +193 -193
  47. package/sails/i18n/addon.json +30 -30
  48. package/sails/i18n/files/frontend/components/LanguageSwitcher.tsx +108 -108
  49. package/sails/i18n/files/frontend/hooks/useLanguage.ts +31 -31
  50. package/sails/i18n/files/frontend/lib/i18n.ts +32 -32
  51. package/sails/i18n/files/frontend/locales/de/common.json +44 -44
  52. package/sails/i18n/files/frontend/locales/en/common.json +44 -44
  53. package/sails/i18n/install.ts +407 -407
  54. package/sails/push-notifications/README.md +163 -163
  55. package/sails/push-notifications/addon.json +31 -31
  56. package/sails/push-notifications/files/backend/routes/notifications.ts +153 -153
  57. package/sails/push-notifications/files/backend/schema/notifications.ts +31 -31
  58. package/sails/push-notifications/files/backend/services/notifications.ts +117 -117
  59. package/sails/push-notifications/files/frontend/components/PushNotificationInit.tsx +12 -12
  60. package/sails/push-notifications/files/frontend/hooks/usePushNotifications.ts +154 -154
  61. package/sails/push-notifications/install.ts +384 -384
  62. package/sails/r2-storage/addon.json +29 -29
  63. package/sails/r2-storage/files/backend/services/storage.ts +71 -71
  64. package/sails/r2-storage/files/frontend/components/ProfilePictureUpload.tsx +167 -167
  65. package/sails/r2-storage/install.ts +412 -412
  66. package/sails/rate-limiting/addon.json +20 -20
  67. package/sails/rate-limiting/files/backend/middleware/rate-limit-store.ts +104 -104
  68. package/sails/rate-limiting/files/backend/middleware/rate-limit.ts +137 -137
  69. package/sails/rate-limiting/install.ts +300 -300
  70. package/sails/registry.json +107 -107
  71. package/sails/stripe/README.md +214 -214
  72. package/sails/stripe/addon.json +24 -24
  73. package/sails/stripe/files/backend/routes/stripe.ts +154 -154
  74. package/sails/stripe/files/backend/schema/stripe.ts +74 -74
  75. package/sails/stripe/files/backend/services/stripe.ts +224 -224
  76. package/sails/stripe/files/frontend/components/SubscriptionStatus.tsx +135 -135
  77. package/sails/stripe/files/frontend/hooks/useSubscription.ts +86 -86
  78. package/sails/stripe/files/frontend/pages/Checkout.tsx +116 -116
  79. package/sails/stripe/files/frontend/pages/Pricing.tsx +226 -226
  80. package/sails/stripe/install.ts +378 -378
@@ -1,252 +1,252 @@
1
- /**
2
- * Google OAuth Sail Installer
3
- *
4
- * Adds Google OAuth sign-in via BetterAuth social provider.
5
- * Features a full interactive setup wizard that guides the user through
6
- * Google Cloud project configuration, credential collection, and installation.
7
- *
8
- * Usage:
9
- * npx tsx sails/google-oauth/install.ts
10
- */
11
-
12
- import {
13
- readFileSync,
14
- writeFileSync,
15
- copyFileSync,
16
- existsSync,
17
- mkdirSync,
18
- } from "node:fs";
19
- import { resolve, dirname, join } from "node:path";
20
- import { input, confirm } from "@inquirer/prompts";
21
-
22
- // ---------------------------------------------------------------------------
23
- // Paths
24
- // ---------------------------------------------------------------------------
25
-
26
- const SAIL_DIR = dirname(new URL(import.meta.url).pathname);
27
- const PROJECT_ROOT = resolve(SAIL_DIR, "../..");
28
- const BACKEND_ROOT = join(PROJECT_ROOT, "packages/backend");
29
- const FRONTEND_ROOT = join(PROJECT_ROOT, "packages/frontend");
30
-
31
- // ---------------------------------------------------------------------------
32
- // Helpers
33
- // ---------------------------------------------------------------------------
34
-
35
- interface SailManifest {
36
- name: string;
37
- displayName: string;
38
- version: string;
39
- requiredEnvVars: { key: string; description: string }[];
40
- }
41
-
42
- function loadManifest(): SailManifest {
43
- return JSON.parse(readFileSync(join(SAIL_DIR, "addon.json"), "utf-8"));
44
- }
45
-
46
- function insertAtMarker(filePath: string, marker: string, code: string): void {
47
- if (!existsSync(filePath)) {
48
- console.warn(` Warning: File not found: ${filePath}`);
49
- return;
50
- }
51
- let content = readFileSync(filePath, "utf-8");
52
- if (!content.includes(marker)) {
53
- console.warn(` Warning: Marker "${marker}" not found in ${filePath}`);
54
- return;
55
- }
56
- if (content.includes(code.trim())) {
57
- console.log(` Skipped (already present) -> ${filePath}`);
58
- return;
59
- }
60
- content = content.replace(marker, `${marker}\n${code}`);
61
- writeFileSync(filePath, content, "utf-8");
62
- console.log(` Modified -> ${filePath}`);
63
- }
64
-
65
- function appendToEnvExample(entries: Record<string, string>): void {
66
- const envPath = join(PROJECT_ROOT, ".env.example");
67
- if (!existsSync(envPath)) {
68
- console.warn(" Warning: .env.example not found");
69
- return;
70
- }
71
- let content = readFileSync(envPath, "utf-8");
72
- const lines: string[] = [];
73
- for (const [key, val] of Object.entries(entries)) {
74
- if (!content.includes(key)) {
75
- lines.push(`${key}=${val}`);
76
- }
77
- }
78
- if (lines.length > 0) {
79
- content += `\n# Google OAuth\n${lines.join("\n")}\n`;
80
- writeFileSync(envPath, content, "utf-8");
81
- console.log(" Updated .env.example");
82
- }
83
- }
84
-
85
- // ---------------------------------------------------------------------------
86
- // Main
87
- // ---------------------------------------------------------------------------
88
-
89
- async function main(): Promise<void> {
90
- const manifest = loadManifest();
91
-
92
- // -- Step 1: Welcome message -------------------------------------------------
93
- console.log("\n------------------------------------------------------------");
94
- console.log(` Google OAuth Sail Installer (v${manifest.version})`);
95
- console.log("------------------------------------------------------------");
96
- console.log();
97
- console.log(" This sail integrates Google sign-in into your project");
98
- console.log(" using BetterAuth's social provider system. After installation,");
99
- console.log(" users will be able to sign in with their Google account via a");
100
- console.log(' "Sign in with Google" button on your login and signup pages.');
101
- console.log();
102
-
103
- const pkgPath = join(PROJECT_ROOT, "package.json");
104
- if (existsSync(pkgPath)) {
105
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
106
- console.log(` Template version: ${pkg.version ?? "unknown"}`);
107
- console.log();
108
- }
109
-
110
- // -- Step 2: Prerequisites check ---------------------------------------------
111
- const hasProject = await confirm({
112
- message: "Do you already have a Google Cloud project with OAuth configured?",
113
- default: false,
114
- });
115
-
116
- if (!hasProject) {
117
- console.log();
118
- console.log(" Follow these steps to create a Google OAuth client:");
119
- console.log();
120
- console.log(" 1. Go to https://console.cloud.google.com/");
121
- console.log(" 2. Create a new project (or select an existing one)");
122
- console.log(" 3. Go to APIs & Services > Credentials");
123
- console.log(' 4. Click "Create Credentials" > "OAuth client ID"');
124
- console.log(' 5. Set application type to "Web application"');
125
- console.log(" 6. Add authorized redirect URI:");
126
- console.log(" {BACKEND_URL}/api/auth/callback/google");
127
- console.log(" For local development, use:");
128
- console.log(" http://localhost:3000/api/auth/callback/google");
129
- console.log(" 7. Copy the Client ID and Client Secret");
130
- console.log();
131
-
132
- await confirm({
133
- message: "I have completed the steps above and have my credentials ready",
134
- default: false,
135
- });
136
- }
137
-
138
- const googleClientId = await input({
139
- message: "Google OAuth Client ID:",
140
- validate: (value) => {
141
- if (!value || value.trim().length === 0) return "Client ID is required.";
142
- return true;
143
- },
144
- });
145
-
146
- const googleClientSecret = await input({
147
- message: "Google OAuth Client Secret:",
148
- validate: (value) => {
149
- if (!value || value.trim().length === 0) return "Client Secret is required.";
150
- return true;
151
- },
152
- });
153
-
154
- // -- Step 5: Show summary ----------------------------------------------------
155
- console.log();
156
- console.log(" Summary of changes:");
157
- console.log(" -------------------");
158
- console.log(" Files to copy:");
159
- console.log(" + packages/frontend/src/components/auth/GoogleButton.tsx");
160
- console.log();
161
- console.log(" Files to modify:");
162
- console.log(" ~ packages/backend/src/auth/index.ts (add Google provider)");
163
- console.log(" ~ packages/backend/src/env.ts (add env validation)");
164
- console.log(" ~ packages/frontend/src/components/auth/LoginForm.tsx");
165
- console.log(" ~ packages/frontend/src/components/auth/SignupForm.tsx");
166
- console.log(" ~ .env.example");
167
- console.log(" ~ .env (if exists)");
168
- console.log();
169
- console.log(" Environment variables:");
170
- console.log(` GOOGLE_CLIENT_ID=${googleClientId}`);
171
- console.log(` GOOGLE_CLIENT_SECRET=${googleClientSecret.slice(0, 8)}...`);
172
- console.log();
173
-
174
- const proceed = await confirm({ message: "Proceed with installation?", default: true });
175
- if (!proceed) { console.log("\n Installation cancelled.\n"); process.exit(0); }
176
-
177
- // -- Step 7: Execute installation --------------------------------------------
178
- console.log();
179
- console.log(" Installing...");
180
- console.log();
181
-
182
- console.log(" Copying files...");
183
- const srcFile = join(SAIL_DIR, "files/GoogleButton.tsx");
184
- const destDir = join(FRONTEND_ROOT, "src/components/auth");
185
- mkdirSync(destDir, { recursive: true });
186
- copyFileSync(srcFile, join(destDir, "GoogleButton.tsx"));
187
- console.log(" Copied -> src/components/auth/GoogleButton.tsx");
188
-
189
- console.log();
190
- console.log(" Modifying backend files...");
191
-
192
- insertAtMarker(
193
- join(BACKEND_ROOT, "src/auth/index.ts"),
194
- "// [SAIL_SOCIAL_PROVIDERS]",
195
- ` google: {\n clientId: process.env.GOOGLE_CLIENT_ID!,\n clientSecret: process.env.GOOGLE_CLIENT_SECRET!,\n },`
196
- );
197
-
198
- insertAtMarker(
199
- join(BACKEND_ROOT, "src/env.ts"),
200
- "// [SAIL_ENV_VARS]",
201
- ` GOOGLE_CLIENT_ID: z.string().min(1, "GOOGLE_CLIENT_ID is required"),\n GOOGLE_CLIENT_SECRET: z.string().min(1, "GOOGLE_CLIENT_SECRET is required"),`
202
- );
203
-
204
- console.log();
205
- console.log(" Modifying frontend files...");
206
-
207
- for (const form of ["LoginForm.tsx", "SignupForm.tsx"]) {
208
- const formPath = join(FRONTEND_ROOT, "src/components/auth", form);
209
- insertAtMarker(formPath, "// [SAIL_IMPORTS]", 'import { GoogleButton } from "./GoogleButton";');
210
- insertAtMarker(formPath, "{/* [SAIL_SOCIAL_BUTTONS] */}", " <GoogleButton />");
211
- }
212
-
213
- console.log();
214
- console.log(" Updating environment files...");
215
- appendToEnvExample({ GOOGLE_CLIENT_ID: googleClientId, GOOGLE_CLIENT_SECRET: googleClientSecret });
216
-
217
- const dotEnvPath = join(PROJECT_ROOT, ".env");
218
- if (existsSync(dotEnvPath)) {
219
- let dotEnv = readFileSync(dotEnvPath, "utf-8");
220
- if (!dotEnv.includes("GOOGLE_CLIENT_ID")) {
221
- dotEnv += `\n# Google OAuth\nGOOGLE_CLIENT_ID=${googleClientId}\nGOOGLE_CLIENT_SECRET=${googleClientSecret}\n`;
222
- writeFileSync(dotEnvPath, dotEnv, "utf-8");
223
- console.log(" Updated .env");
224
- }
225
- }
226
-
227
- console.log();
228
- console.log("------------------------------------------------------------");
229
- console.log(" Google OAuth installed successfully!");
230
- console.log("------------------------------------------------------------");
231
- console.log();
232
- console.log(" Next steps:");
233
- console.log();
234
- console.log(" 1. Verify your Google Cloud Console settings:");
235
- console.log(" https://console.cloud.google.com/apis/credentials");
236
- console.log();
237
- console.log(" 2. Ensure these redirect URIs are configured:");
238
- console.log(" Development: http://localhost:3000/api/auth/callback/google");
239
- console.log(" Production: https://yourdomain.com/api/auth/callback/google");
240
- console.log();
241
- console.log(" 3. Restart your dev server:");
242
- console.log(" npm run dev");
243
- console.log();
244
- console.log(" Testing:");
245
- console.log(' - Visit your login page and click "Sign in with Google"');
246
- console.log(" - You should be redirected to Google's consent screen");
247
- console.log(" - After authorizing, you should be redirected back and signed in");
248
- console.log(" - Check the users table in your database for the new account");
249
- console.log();
250
- }
251
-
252
- main().catch((err) => { console.error("Installation failed:", err); process.exit(1); });
1
+ /**
2
+ * Google OAuth Sail Installer
3
+ *
4
+ * Adds Google OAuth sign-in via BetterAuth social provider.
5
+ * Features a full interactive setup wizard that guides the user through
6
+ * Google Cloud project configuration, credential collection, and installation.
7
+ *
8
+ * Usage:
9
+ * npx tsx sails/google-oauth/install.ts
10
+ */
11
+
12
+ import {
13
+ readFileSync,
14
+ writeFileSync,
15
+ copyFileSync,
16
+ existsSync,
17
+ mkdirSync,
18
+ } from "node:fs";
19
+ import { resolve, dirname, join } from "node:path";
20
+ import { input, confirm } from "@inquirer/prompts";
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Paths
24
+ // ---------------------------------------------------------------------------
25
+
26
+ const SAIL_DIR = dirname(new URL(import.meta.url).pathname);
27
+ const PROJECT_ROOT = resolve(SAIL_DIR, "../..");
28
+ const BACKEND_ROOT = join(PROJECT_ROOT, "packages/backend");
29
+ const FRONTEND_ROOT = join(PROJECT_ROOT, "packages/frontend");
30
+
31
+ // ---------------------------------------------------------------------------
32
+ // Helpers
33
+ // ---------------------------------------------------------------------------
34
+
35
+ interface SailManifest {
36
+ name: string;
37
+ displayName: string;
38
+ version: string;
39
+ requiredEnvVars: { key: string; description: string }[];
40
+ }
41
+
42
+ function loadManifest(): SailManifest {
43
+ return JSON.parse(readFileSync(join(SAIL_DIR, "addon.json"), "utf-8"));
44
+ }
45
+
46
+ function insertAtMarker(filePath: string, marker: string, code: string): void {
47
+ if (!existsSync(filePath)) {
48
+ console.warn(` Warning: File not found: ${filePath}`);
49
+ return;
50
+ }
51
+ let content = readFileSync(filePath, "utf-8");
52
+ if (!content.includes(marker)) {
53
+ console.warn(` Warning: Marker "${marker}" not found in ${filePath}`);
54
+ return;
55
+ }
56
+ if (content.includes(code.trim())) {
57
+ console.log(` Skipped (already present) -> ${filePath}`);
58
+ return;
59
+ }
60
+ content = content.replace(marker, `${marker}\n${code}`);
61
+ writeFileSync(filePath, content, "utf-8");
62
+ console.log(` Modified -> ${filePath}`);
63
+ }
64
+
65
+ function appendToEnvExample(entries: Record<string, string>): void {
66
+ const envPath = join(PROJECT_ROOT, ".env.example");
67
+ if (!existsSync(envPath)) {
68
+ console.warn(" Warning: .env.example not found");
69
+ return;
70
+ }
71
+ let content = readFileSync(envPath, "utf-8");
72
+ const lines: string[] = [];
73
+ for (const [key, val] of Object.entries(entries)) {
74
+ if (!content.includes(key)) {
75
+ lines.push(`${key}=${val}`);
76
+ }
77
+ }
78
+ if (lines.length > 0) {
79
+ content += `\n# Google OAuth\n${lines.join("\n")}\n`;
80
+ writeFileSync(envPath, content, "utf-8");
81
+ console.log(" Updated .env.example");
82
+ }
83
+ }
84
+
85
+ // ---------------------------------------------------------------------------
86
+ // Main
87
+ // ---------------------------------------------------------------------------
88
+
89
+ async function main(): Promise<void> {
90
+ const manifest = loadManifest();
91
+
92
+ // -- Step 1: Welcome message -------------------------------------------------
93
+ console.log("\n------------------------------------------------------------");
94
+ console.log(` Google OAuth Sail Installer (v${manifest.version})`);
95
+ console.log("------------------------------------------------------------");
96
+ console.log();
97
+ console.log(" This sail integrates Google sign-in into your project");
98
+ console.log(" using BetterAuth's social provider system. After installation,");
99
+ console.log(" users will be able to sign in with their Google account via a");
100
+ console.log(' "Sign in with Google" button on your login and signup pages.');
101
+ console.log();
102
+
103
+ const pkgPath = join(PROJECT_ROOT, "package.json");
104
+ if (existsSync(pkgPath)) {
105
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
106
+ console.log(` Template version: ${pkg.version ?? "unknown"}`);
107
+ console.log();
108
+ }
109
+
110
+ // -- Step 2: Prerequisites check ---------------------------------------------
111
+ const hasProject = await confirm({
112
+ message: "Do you already have a Google Cloud project with OAuth configured?",
113
+ default: false,
114
+ });
115
+
116
+ if (!hasProject) {
117
+ console.log();
118
+ console.log(" Follow these steps to create a Google OAuth client:");
119
+ console.log();
120
+ console.log(" 1. Go to https://console.cloud.google.com/");
121
+ console.log(" 2. Create a new project (or select an existing one)");
122
+ console.log(" 3. Go to APIs & Services > Credentials");
123
+ console.log(' 4. Click "Create Credentials" > "OAuth client ID"');
124
+ console.log(' 5. Set application type to "Web application"');
125
+ console.log(" 6. Add authorized redirect URI:");
126
+ console.log(" {BACKEND_URL}/api/auth/callback/google");
127
+ console.log(" For local development, use:");
128
+ console.log(" http://localhost:3000/api/auth/callback/google");
129
+ console.log(" 7. Copy the Client ID and Client Secret");
130
+ console.log();
131
+
132
+ await confirm({
133
+ message: "I have completed the steps above and have my credentials ready",
134
+ default: false,
135
+ });
136
+ }
137
+
138
+ const googleClientId = await input({
139
+ message: "Google OAuth Client ID:",
140
+ validate: (value) => {
141
+ if (!value || value.trim().length === 0) return "Client ID is required.";
142
+ return true;
143
+ },
144
+ });
145
+
146
+ const googleClientSecret = await input({
147
+ message: "Google OAuth Client Secret:",
148
+ validate: (value) => {
149
+ if (!value || value.trim().length === 0) return "Client Secret is required.";
150
+ return true;
151
+ },
152
+ });
153
+
154
+ // -- Step 5: Show summary ----------------------------------------------------
155
+ console.log();
156
+ console.log(" Summary of changes:");
157
+ console.log(" -------------------");
158
+ console.log(" Files to copy:");
159
+ console.log(" + packages/frontend/src/components/auth/GoogleButton.tsx");
160
+ console.log();
161
+ console.log(" Files to modify:");
162
+ console.log(" ~ packages/backend/src/auth/index.ts (add Google provider)");
163
+ console.log(" ~ packages/backend/src/env.ts (add env validation)");
164
+ console.log(" ~ packages/frontend/src/components/auth/LoginForm.tsx");
165
+ console.log(" ~ packages/frontend/src/components/auth/SignupForm.tsx");
166
+ console.log(" ~ .env.example");
167
+ console.log(" ~ .env (if exists)");
168
+ console.log();
169
+ console.log(" Environment variables:");
170
+ console.log(` GOOGLE_CLIENT_ID=${googleClientId}`);
171
+ console.log(` GOOGLE_CLIENT_SECRET=${googleClientSecret.slice(0, 8)}...`);
172
+ console.log();
173
+
174
+ const proceed = await confirm({ message: "Proceed with installation?", default: true });
175
+ if (!proceed) { console.log("\n Installation cancelled.\n"); process.exit(0); }
176
+
177
+ // -- Step 7: Execute installation --------------------------------------------
178
+ console.log();
179
+ console.log(" Installing...");
180
+ console.log();
181
+
182
+ console.log(" Copying files...");
183
+ const srcFile = join(SAIL_DIR, "files/GoogleButton.tsx");
184
+ const destDir = join(FRONTEND_ROOT, "src/components/auth");
185
+ mkdirSync(destDir, { recursive: true });
186
+ copyFileSync(srcFile, join(destDir, "GoogleButton.tsx"));
187
+ console.log(" Copied -> src/components/auth/GoogleButton.tsx");
188
+
189
+ console.log();
190
+ console.log(" Modifying backend files...");
191
+
192
+ insertAtMarker(
193
+ join(BACKEND_ROOT, "src/auth/index.ts"),
194
+ "// [SAIL_SOCIAL_PROVIDERS]",
195
+ ` google: {\n clientId: process.env.GOOGLE_CLIENT_ID!,\n clientSecret: process.env.GOOGLE_CLIENT_SECRET!,\n },`
196
+ );
197
+
198
+ insertAtMarker(
199
+ join(BACKEND_ROOT, "src/env.ts"),
200
+ "// [SAIL_ENV_VARS]",
201
+ ` GOOGLE_CLIENT_ID: z.string().min(1, "GOOGLE_CLIENT_ID is required"),\n GOOGLE_CLIENT_SECRET: z.string().min(1, "GOOGLE_CLIENT_SECRET is required"),`
202
+ );
203
+
204
+ console.log();
205
+ console.log(" Modifying frontend files...");
206
+
207
+ for (const form of ["LoginForm.tsx", "SignupForm.tsx"]) {
208
+ const formPath = join(FRONTEND_ROOT, "src/components/auth", form);
209
+ insertAtMarker(formPath, "// [SAIL_IMPORTS]", 'import { GoogleButton } from "./GoogleButton";');
210
+ insertAtMarker(formPath, "{/* [SAIL_SOCIAL_BUTTONS] */}", " <GoogleButton />");
211
+ }
212
+
213
+ console.log();
214
+ console.log(" Updating environment files...");
215
+ appendToEnvExample({ GOOGLE_CLIENT_ID: googleClientId, GOOGLE_CLIENT_SECRET: googleClientSecret });
216
+
217
+ const dotEnvPath = join(PROJECT_ROOT, ".env");
218
+ if (existsSync(dotEnvPath)) {
219
+ let dotEnv = readFileSync(dotEnvPath, "utf-8");
220
+ if (!dotEnv.includes("GOOGLE_CLIENT_ID")) {
221
+ dotEnv += `\n# Google OAuth\nGOOGLE_CLIENT_ID=${googleClientId}\nGOOGLE_CLIENT_SECRET=${googleClientSecret}\n`;
222
+ writeFileSync(dotEnvPath, dotEnv, "utf-8");
223
+ console.log(" Updated .env");
224
+ }
225
+ }
226
+
227
+ console.log();
228
+ console.log("------------------------------------------------------------");
229
+ console.log(" Google OAuth installed successfully!");
230
+ console.log("------------------------------------------------------------");
231
+ console.log();
232
+ console.log(" Next steps:");
233
+ console.log();
234
+ console.log(" 1. Verify your Google Cloud Console settings:");
235
+ console.log(" https://console.cloud.google.com/apis/credentials");
236
+ console.log();
237
+ console.log(" 2. Ensure these redirect URIs are configured:");
238
+ console.log(" Development: http://localhost:3000/api/auth/callback/google");
239
+ console.log(" Production: https://yourdomain.com/api/auth/callback/google");
240
+ console.log();
241
+ console.log(" 3. Restart your dev server:");
242
+ console.log(" npm run dev");
243
+ console.log();
244
+ console.log(" Testing:");
245
+ console.log(' - Visit your login page and click "Sign in with Google"');
246
+ console.log(" - You should be redirected to Google's consent screen");
247
+ console.log(" - After authorizing, you should be redirected back and signed in");
248
+ console.log(" - Check the users table in your database for the new account");
249
+ console.log();
250
+ }
251
+
252
+ main().catch((err) => { console.error("Installation failed:", err); process.exit(1); });