@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.
- package/dist/__tests__/sail-installer.test.js +25 -25
- package/dist/sail-installer.js +174 -174
- package/dist/scaffold.js +68 -68
- package/package.json +58 -58
- package/sails/_template/addon.json +20 -20
- package/sails/_template/install.ts +402 -402
- package/sails/admin-dashboard/README.md +117 -117
- package/sails/admin-dashboard/addon.json +28 -28
- package/sails/admin-dashboard/files/backend/middleware/admin.ts +34 -34
- package/sails/admin-dashboard/files/backend/routes/admin.ts +243 -243
- package/sails/admin-dashboard/files/frontend/components/admin/StatsCard.tsx +40 -40
- package/sails/admin-dashboard/files/frontend/components/admin/UsersTable.tsx +240 -240
- package/sails/admin-dashboard/files/frontend/hooks/useAdmin.ts +149 -149
- package/sails/admin-dashboard/files/frontend/pages/admin/Dashboard.tsx +173 -173
- package/sails/admin-dashboard/files/frontend/pages/admin/UserDetail.tsx +203 -203
- package/sails/admin-dashboard/install.ts +305 -305
- package/sails/analytics/README.md +178 -178
- package/sails/analytics/addon.json +27 -27
- package/sails/analytics/files/frontend/components/AnalyticsProvider.tsx +58 -58
- package/sails/analytics/files/frontend/hooks/useAnalytics.ts +64 -64
- package/sails/analytics/files/frontend/lib/analytics.ts +103 -103
- package/sails/analytics/install.ts +297 -297
- package/sails/file-uploads/addon.json +30 -30
- package/sails/file-uploads/files/backend/routes/files.ts +198 -198
- package/sails/file-uploads/files/backend/schema/files.ts +36 -36
- package/sails/file-uploads/files/backend/services/file-storage.ts +128 -128
- package/sails/file-uploads/files/frontend/components/FileList.tsx +248 -248
- package/sails/file-uploads/files/frontend/components/FileUploadButton.tsx +147 -147
- package/sails/file-uploads/files/frontend/hooks/useFileUpload.ts +106 -106
- package/sails/file-uploads/files/frontend/hooks/useFiles.ts +118 -118
- package/sails/file-uploads/files/frontend/pages/Files.tsx +37 -37
- package/sails/file-uploads/install.ts +466 -466
- package/sails/gdpr/README.md +174 -174
- package/sails/gdpr/addon.json +27 -27
- package/sails/gdpr/files/backend/routes/gdpr.ts +140 -140
- package/sails/gdpr/files/backend/services/gdpr.ts +293 -293
- package/sails/gdpr/files/frontend/components/auth/ConsentCheckboxes.tsx +97 -97
- package/sails/gdpr/files/frontend/components/gdpr/AccountDeletionRequest.tsx +192 -192
- package/sails/gdpr/files/frontend/components/gdpr/DataExportButton.tsx +75 -75
- package/sails/gdpr/files/frontend/pages/PrivacyPolicy.tsx +186 -186
- package/sails/gdpr/install.ts +756 -756
- package/sails/google-oauth/README.md +121 -121
- package/sails/google-oauth/addon.json +22 -22
- package/sails/google-oauth/files/GoogleButton.tsx +50 -50
- package/sails/google-oauth/install.ts +252 -252
- package/sails/i18n/README.md +193 -193
- package/sails/i18n/addon.json +30 -30
- package/sails/i18n/files/frontend/components/LanguageSwitcher.tsx +108 -108
- package/sails/i18n/files/frontend/hooks/useLanguage.ts +31 -31
- package/sails/i18n/files/frontend/lib/i18n.ts +32 -32
- package/sails/i18n/files/frontend/locales/de/common.json +44 -44
- package/sails/i18n/files/frontend/locales/en/common.json +44 -44
- package/sails/i18n/install.ts +407 -407
- package/sails/push-notifications/README.md +163 -163
- package/sails/push-notifications/addon.json +31 -31
- package/sails/push-notifications/files/backend/routes/notifications.ts +153 -153
- package/sails/push-notifications/files/backend/schema/notifications.ts +31 -31
- package/sails/push-notifications/files/backend/services/notifications.ts +117 -117
- package/sails/push-notifications/files/frontend/components/PushNotificationInit.tsx +12 -12
- package/sails/push-notifications/files/frontend/hooks/usePushNotifications.ts +154 -154
- package/sails/push-notifications/install.ts +384 -384
- package/sails/r2-storage/addon.json +29 -29
- package/sails/r2-storage/files/backend/services/storage.ts +71 -71
- package/sails/r2-storage/files/frontend/components/ProfilePictureUpload.tsx +167 -167
- package/sails/r2-storage/install.ts +412 -412
- package/sails/rate-limiting/addon.json +20 -20
- package/sails/rate-limiting/files/backend/middleware/rate-limit-store.ts +104 -104
- package/sails/rate-limiting/files/backend/middleware/rate-limit.ts +137 -137
- package/sails/rate-limiting/install.ts +300 -300
- package/sails/registry.json +107 -107
- package/sails/stripe/README.md +214 -214
- package/sails/stripe/addon.json +24 -24
- package/sails/stripe/files/backend/routes/stripe.ts +154 -154
- package/sails/stripe/files/backend/schema/stripe.ts +74 -74
- package/sails/stripe/files/backend/services/stripe.ts +224 -224
- package/sails/stripe/files/frontend/components/SubscriptionStatus.tsx +135 -135
- package/sails/stripe/files/frontend/hooks/useSubscription.ts +86 -86
- package/sails/stripe/files/frontend/pages/Checkout.tsx +116 -116
- package/sails/stripe/files/frontend/pages/Pricing.tsx +226 -226
- 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); });
|