codebakers 2.1.2 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1095 -1050
- package/package.json +1 -1
- package/src/commands/build.ts +34 -3
- package/src/commands/code.ts +28 -3
- package/src/commands/deploy.ts +36 -2
- package/src/commands/init.ts +11 -1
- package/src/commands/integrate.ts +464 -565
- package/src/commands/setup.ts +375 -357
- package/src/commands/website.ts +17 -2
- package/src/index.ts +29 -22
|
@@ -2,120 +2,119 @@ import * as p from '@clack/prompts';
|
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import * as fs from 'fs-extra';
|
|
4
4
|
import * as path from 'path';
|
|
5
|
-
import http from 'http';
|
|
6
5
|
import open from 'open';
|
|
7
6
|
import { Config } from '../utils/config.js';
|
|
8
7
|
import { execa } from 'execa';
|
|
9
8
|
|
|
10
9
|
// ============================================================================
|
|
11
|
-
// INTEGRATION LIBRARY
|
|
10
|
+
// INTEGRATION LIBRARY - 50+ One-Click Integrations
|
|
12
11
|
// ============================================================================
|
|
13
12
|
|
|
14
13
|
interface Integration {
|
|
15
14
|
id: string;
|
|
16
15
|
name: string;
|
|
17
16
|
description: string;
|
|
18
|
-
category:
|
|
17
|
+
category: string;
|
|
19
18
|
icon: string;
|
|
20
|
-
|
|
21
|
-
oauthUrl?: string;
|
|
22
|
-
scopes?: string[];
|
|
19
|
+
dashboardUrl: string;
|
|
23
20
|
envVars: string[];
|
|
21
|
+
envVarHints?: Record<string, string>;
|
|
24
22
|
packages?: string[];
|
|
25
|
-
setupFiles?: Array<{ path: string; template: string }>;
|
|
26
23
|
docs: string;
|
|
24
|
+
steps: string[];
|
|
27
25
|
}
|
|
28
26
|
|
|
29
27
|
const INTEGRATIONS: Integration[] = [
|
|
30
28
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
31
|
-
// AUTH
|
|
29
|
+
// AUTH
|
|
32
30
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
33
31
|
{
|
|
34
32
|
id: 'clerk',
|
|
35
33
|
name: 'Clerk',
|
|
36
|
-
description: '
|
|
34
|
+
description: 'User management & authentication',
|
|
37
35
|
category: 'auth',
|
|
38
36
|
icon: '🔐',
|
|
39
|
-
|
|
40
|
-
oauthUrl: 'https://dashboard.clerk.com/apps/new',
|
|
37
|
+
dashboardUrl: 'https://dashboard.clerk.com',
|
|
41
38
|
envVars: ['NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY', 'CLERK_SECRET_KEY'],
|
|
39
|
+
envVarHints: {
|
|
40
|
+
'NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY': 'pk_test_... or pk_live_...',
|
|
41
|
+
'CLERK_SECRET_KEY': 'sk_test_... or sk_live_...',
|
|
42
|
+
},
|
|
42
43
|
packages: ['@clerk/nextjs'],
|
|
43
44
|
docs: 'https://clerk.com/docs',
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
icon: '🔒',
|
|
51
|
-
authType: 'oauth',
|
|
52
|
-
oauthUrl: 'https://manage.auth0.com/dashboard',
|
|
53
|
-
envVars: ['AUTH0_SECRET', 'AUTH0_BASE_URL', 'AUTH0_ISSUER_BASE_URL', 'AUTH0_CLIENT_ID', 'AUTH0_CLIENT_SECRET'],
|
|
54
|
-
packages: ['@auth0/nextjs-auth0'],
|
|
55
|
-
docs: 'https://auth0.com/docs',
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
id: 'nextauth',
|
|
59
|
-
name: 'NextAuth.js',
|
|
60
|
-
description: 'Open source authentication for Next.js',
|
|
61
|
-
category: 'auth',
|
|
62
|
-
icon: '🔑',
|
|
63
|
-
authType: 'npm',
|
|
64
|
-
envVars: ['NEXTAUTH_SECRET', 'NEXTAUTH_URL'],
|
|
65
|
-
packages: ['next-auth'],
|
|
66
|
-
docs: 'https://next-auth.js.org',
|
|
45
|
+
steps: [
|
|
46
|
+
'Sign up or log in at dashboard.clerk.com',
|
|
47
|
+
'Create a new application',
|
|
48
|
+
'Go to API Keys in the sidebar',
|
|
49
|
+
'Copy Publishable Key and Secret Key',
|
|
50
|
+
],
|
|
67
51
|
},
|
|
68
52
|
{
|
|
69
53
|
id: 'supabase-auth',
|
|
70
54
|
name: 'Supabase Auth',
|
|
71
|
-
description: '
|
|
55
|
+
description: 'Auth with email, social, magic links',
|
|
72
56
|
category: 'auth',
|
|
73
57
|
icon: '⚡',
|
|
74
|
-
|
|
75
|
-
oauthUrl: 'https://supabase.com/dashboard/projects',
|
|
58
|
+
dashboardUrl: 'https://supabase.com/dashboard',
|
|
76
59
|
envVars: ['NEXT_PUBLIC_SUPABASE_URL', 'NEXT_PUBLIC_SUPABASE_ANON_KEY'],
|
|
60
|
+
envVarHints: {
|
|
61
|
+
'NEXT_PUBLIC_SUPABASE_URL': 'https://xxx.supabase.co',
|
|
62
|
+
'NEXT_PUBLIC_SUPABASE_ANON_KEY': 'eyJ... (anon public key)',
|
|
63
|
+
},
|
|
77
64
|
packages: ['@supabase/supabase-js', '@supabase/auth-helpers-nextjs'],
|
|
78
65
|
docs: 'https://supabase.com/docs/guides/auth',
|
|
66
|
+
steps: [
|
|
67
|
+
'Sign in at supabase.com/dashboard',
|
|
68
|
+
'Create or select a project',
|
|
69
|
+
'Go to Settings > API',
|
|
70
|
+
'Copy Project URL and anon/public key',
|
|
71
|
+
],
|
|
79
72
|
},
|
|
80
73
|
{
|
|
81
|
-
id: '
|
|
82
|
-
name: '
|
|
83
|
-
description: '
|
|
74
|
+
id: 'nextauth',
|
|
75
|
+
name: 'NextAuth.js',
|
|
76
|
+
description: 'Open source auth for Next.js',
|
|
84
77
|
category: 'auth',
|
|
85
|
-
icon: '
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
78
|
+
icon: '🔑',
|
|
79
|
+
dashboardUrl: 'https://next-auth.js.org',
|
|
80
|
+
envVars: ['NEXTAUTH_SECRET', 'NEXTAUTH_URL'],
|
|
81
|
+
envVarHints: {
|
|
82
|
+
'NEXTAUTH_SECRET': 'Run: openssl rand -base64 32',
|
|
83
|
+
'NEXTAUTH_URL': 'http://localhost:3000',
|
|
84
|
+
},
|
|
85
|
+
packages: ['next-auth'],
|
|
86
|
+
docs: 'https://next-auth.js.org',
|
|
87
|
+
steps: [
|
|
88
|
+
'No signup needed - open source!',
|
|
89
|
+
'Generate secret: openssl rand -base64 32',
|
|
90
|
+
'Set URL to your app (localhost for dev)',
|
|
91
|
+
],
|
|
91
92
|
},
|
|
92
93
|
|
|
93
94
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
94
|
-
//
|
|
95
|
+
// DATABASE
|
|
95
96
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
96
97
|
{
|
|
97
98
|
id: 'supabase',
|
|
98
99
|
name: 'Supabase',
|
|
99
|
-
description: 'Postgres
|
|
100
|
+
description: 'Postgres + Realtime + Auth + Storage',
|
|
100
101
|
category: 'database',
|
|
101
102
|
icon: '⚡',
|
|
102
|
-
|
|
103
|
-
oauthUrl: 'https://supabase.com/dashboard/projects',
|
|
103
|
+
dashboardUrl: 'https://supabase.com/dashboard',
|
|
104
104
|
envVars: ['NEXT_PUBLIC_SUPABASE_URL', 'NEXT_PUBLIC_SUPABASE_ANON_KEY', 'SUPABASE_SERVICE_ROLE_KEY'],
|
|
105
|
+
envVarHints: {
|
|
106
|
+
'NEXT_PUBLIC_SUPABASE_URL': 'https://xxx.supabase.co',
|
|
107
|
+
'NEXT_PUBLIC_SUPABASE_ANON_KEY': 'eyJ... (anon key)',
|
|
108
|
+
'SUPABASE_SERVICE_ROLE_KEY': 'eyJ... (service_role key - keep secret!)',
|
|
109
|
+
},
|
|
105
110
|
packages: ['@supabase/supabase-js'],
|
|
106
111
|
docs: 'https://supabase.com/docs',
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
icon: '🪐',
|
|
114
|
-
authType: 'oauth',
|
|
115
|
-
oauthUrl: 'https://app.planetscale.com',
|
|
116
|
-
envVars: ['DATABASE_URL'],
|
|
117
|
-
packages: ['@planetscale/database'],
|
|
118
|
-
docs: 'https://planetscale.com/docs',
|
|
112
|
+
steps: [
|
|
113
|
+
'Sign in at supabase.com/dashboard',
|
|
114
|
+
'Create a new project',
|
|
115
|
+
'Go to Settings > API',
|
|
116
|
+
'Copy URL, anon key, and service_role key',
|
|
117
|
+
],
|
|
119
118
|
},
|
|
120
119
|
{
|
|
121
120
|
id: 'neon',
|
|
@@ -123,57 +122,58 @@ const INTEGRATIONS: Integration[] = [
|
|
|
123
122
|
description: 'Serverless Postgres',
|
|
124
123
|
category: 'database',
|
|
125
124
|
icon: '🐘',
|
|
126
|
-
|
|
127
|
-
oauthUrl: 'https://console.neon.tech',
|
|
125
|
+
dashboardUrl: 'https://console.neon.tech',
|
|
128
126
|
envVars: ['DATABASE_URL'],
|
|
127
|
+
envVarHints: {
|
|
128
|
+
'DATABASE_URL': 'postgres://user:pass@xxx.neon.tech/db?sslmode=require',
|
|
129
|
+
},
|
|
129
130
|
packages: ['@neondatabase/serverless'],
|
|
130
131
|
docs: 'https://neon.tech/docs',
|
|
132
|
+
steps: [
|
|
133
|
+
'Sign in at console.neon.tech',
|
|
134
|
+
'Create a project',
|
|
135
|
+
'Go to Dashboard > Connection Details',
|
|
136
|
+
'Copy the connection string',
|
|
137
|
+
],
|
|
131
138
|
},
|
|
132
139
|
{
|
|
133
|
-
id: '
|
|
134
|
-
name: '
|
|
135
|
-
description: '
|
|
136
|
-
category: 'database',
|
|
137
|
-
icon: '🐢',
|
|
138
|
-
authType: 'oauth',
|
|
139
|
-
oauthUrl: 'https://turso.tech/app',
|
|
140
|
-
envVars: ['TURSO_DATABASE_URL', 'TURSO_AUTH_TOKEN'],
|
|
141
|
-
packages: ['@libsql/client'],
|
|
142
|
-
docs: 'https://docs.turso.tech',
|
|
143
|
-
},
|
|
144
|
-
{
|
|
145
|
-
id: 'mongodb',
|
|
146
|
-
name: 'MongoDB Atlas',
|
|
147
|
-
description: 'NoSQL document database',
|
|
140
|
+
id: 'planetscale',
|
|
141
|
+
name: 'PlanetScale',
|
|
142
|
+
description: 'Serverless MySQL',
|
|
148
143
|
category: 'database',
|
|
149
|
-
icon: '
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
144
|
+
icon: '🪐',
|
|
145
|
+
dashboardUrl: 'https://app.planetscale.com',
|
|
146
|
+
envVars: ['DATABASE_URL'],
|
|
147
|
+
envVarHints: {
|
|
148
|
+
'DATABASE_URL': 'mysql://user:pass@xxx.psdb.cloud/db?sslaccept=strict',
|
|
149
|
+
},
|
|
150
|
+
packages: ['@planetscale/database'],
|
|
151
|
+
docs: 'https://planetscale.com/docs',
|
|
152
|
+
steps: [
|
|
153
|
+
'Sign in at app.planetscale.com',
|
|
154
|
+
'Create a database',
|
|
155
|
+
'Go to Connect > Create password',
|
|
156
|
+
'Copy the connection string',
|
|
157
|
+
],
|
|
155
158
|
},
|
|
156
159
|
{
|
|
157
160
|
id: 'prisma',
|
|
158
161
|
name: 'Prisma',
|
|
159
|
-
description: 'Type-safe ORM
|
|
162
|
+
description: 'Type-safe ORM',
|
|
160
163
|
category: 'database',
|
|
161
164
|
icon: '🔷',
|
|
162
|
-
|
|
165
|
+
dashboardUrl: 'https://prisma.io',
|
|
163
166
|
envVars: ['DATABASE_URL'],
|
|
167
|
+
envVarHints: {
|
|
168
|
+
'DATABASE_URL': 'Your database connection string',
|
|
169
|
+
},
|
|
164
170
|
packages: ['prisma', '@prisma/client'],
|
|
165
|
-
docs: 'https://
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
category: 'database',
|
|
172
|
-
icon: '💧',
|
|
173
|
-
authType: 'npm',
|
|
174
|
-
envVars: ['DATABASE_URL'],
|
|
175
|
-
packages: ['drizzle-orm', 'drizzle-kit'],
|
|
176
|
-
docs: 'https://orm.drizzle.team',
|
|
171
|
+
docs: 'https://prisma.io/docs',
|
|
172
|
+
steps: [
|
|
173
|
+
'No account needed - Prisma is open source!',
|
|
174
|
+
'Use your existing database URL',
|
|
175
|
+
'Run: npx prisma init',
|
|
176
|
+
],
|
|
177
177
|
},
|
|
178
178
|
|
|
179
179
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -182,38 +182,46 @@ const INTEGRATIONS: Integration[] = [
|
|
|
182
182
|
{
|
|
183
183
|
id: 'stripe',
|
|
184
184
|
name: 'Stripe',
|
|
185
|
-
description: '
|
|
185
|
+
description: 'Payments & subscriptions',
|
|
186
186
|
category: 'payments',
|
|
187
187
|
icon: '💳',
|
|
188
|
-
|
|
189
|
-
oauthUrl: 'https://dashboard.stripe.com/apikeys',
|
|
188
|
+
dashboardUrl: 'https://dashboard.stripe.com/apikeys',
|
|
190
189
|
envVars: ['STRIPE_SECRET_KEY', 'NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY', 'STRIPE_WEBHOOK_SECRET'],
|
|
190
|
+
envVarHints: {
|
|
191
|
+
'STRIPE_SECRET_KEY': 'sk_test_... or sk_live_...',
|
|
192
|
+
'NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY': 'pk_test_... or pk_live_...',
|
|
193
|
+
'STRIPE_WEBHOOK_SECRET': 'whsec_... (from Webhooks page)',
|
|
194
|
+
},
|
|
191
195
|
packages: ['stripe', '@stripe/stripe-js'],
|
|
192
196
|
docs: 'https://stripe.com/docs',
|
|
197
|
+
steps: [
|
|
198
|
+
'Sign in at dashboard.stripe.com',
|
|
199
|
+
'Go to Developers > API keys',
|
|
200
|
+
'Copy Publishable and Secret keys',
|
|
201
|
+
'For webhooks: Developers > Webhooks > Add endpoint',
|
|
202
|
+
],
|
|
193
203
|
},
|
|
194
204
|
{
|
|
195
205
|
id: 'lemonsqueezy',
|
|
196
206
|
name: 'Lemon Squeezy',
|
|
197
|
-
description: '
|
|
207
|
+
description: 'Payments with tax handling (MoR)',
|
|
198
208
|
category: 'payments',
|
|
199
209
|
icon: '🍋',
|
|
200
|
-
|
|
201
|
-
oauthUrl: 'https://app.lemonsqueezy.com/settings/api',
|
|
210
|
+
dashboardUrl: 'https://app.lemonsqueezy.com/settings/api',
|
|
202
211
|
envVars: ['LEMONSQUEEZY_API_KEY', 'LEMONSQUEEZY_STORE_ID', 'LEMONSQUEEZY_WEBHOOK_SECRET'],
|
|
212
|
+
envVarHints: {
|
|
213
|
+
'LEMONSQUEEZY_API_KEY': 'From Settings > API',
|
|
214
|
+
'LEMONSQUEEZY_STORE_ID': 'Your store ID number',
|
|
215
|
+
'LEMONSQUEEZY_WEBHOOK_SECRET': 'From Settings > Webhooks',
|
|
216
|
+
},
|
|
203
217
|
packages: ['@lemonsqueezy/lemonsqueezy.js'],
|
|
204
218
|
docs: 'https://docs.lemonsqueezy.com',
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
icon: '🏓',
|
|
212
|
-
authType: 'oauth',
|
|
213
|
-
oauthUrl: 'https://vendors.paddle.com/authentication',
|
|
214
|
-
envVars: ['PADDLE_VENDOR_ID', 'PADDLE_API_KEY', 'PADDLE_PUBLIC_KEY'],
|
|
215
|
-
packages: ['@paddle/paddle-js'],
|
|
216
|
-
docs: 'https://developer.paddle.com',
|
|
219
|
+
steps: [
|
|
220
|
+
'Sign in at app.lemonsqueezy.com',
|
|
221
|
+
'Go to Settings > API',
|
|
222
|
+
'Create an API key',
|
|
223
|
+
'Note your Store ID from the URL',
|
|
224
|
+
],
|
|
217
225
|
},
|
|
218
226
|
|
|
219
227
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -222,14 +230,22 @@ const INTEGRATIONS: Integration[] = [
|
|
|
222
230
|
{
|
|
223
231
|
id: 'resend',
|
|
224
232
|
name: 'Resend',
|
|
225
|
-
description: 'Modern email API
|
|
233
|
+
description: 'Modern email API',
|
|
226
234
|
category: 'email',
|
|
227
235
|
icon: '📧',
|
|
228
|
-
|
|
229
|
-
oauthUrl: 'https://resend.com/api-keys',
|
|
236
|
+
dashboardUrl: 'https://resend.com/api-keys',
|
|
230
237
|
envVars: ['RESEND_API_KEY'],
|
|
238
|
+
envVarHints: {
|
|
239
|
+
'RESEND_API_KEY': 're_...',
|
|
240
|
+
},
|
|
231
241
|
packages: ['resend'],
|
|
232
242
|
docs: 'https://resend.com/docs',
|
|
243
|
+
steps: [
|
|
244
|
+
'Sign up at resend.com',
|
|
245
|
+
'Go to API Keys',
|
|
246
|
+
'Create a new API key',
|
|
247
|
+
'Copy the key (only shown once!)',
|
|
248
|
+
],
|
|
233
249
|
},
|
|
234
250
|
{
|
|
235
251
|
id: 'sendgrid',
|
|
@@ -237,46 +253,19 @@ const INTEGRATIONS: Integration[] = [
|
|
|
237
253
|
description: 'Email delivery service',
|
|
238
254
|
category: 'email',
|
|
239
255
|
icon: '📨',
|
|
240
|
-
|
|
241
|
-
oauthUrl: 'https://app.sendgrid.com/settings/api_keys',
|
|
256
|
+
dashboardUrl: 'https://app.sendgrid.com/settings/api_keys',
|
|
242
257
|
envVars: ['SENDGRID_API_KEY'],
|
|
258
|
+
envVarHints: {
|
|
259
|
+
'SENDGRID_API_KEY': 'SG.xxx...',
|
|
260
|
+
},
|
|
243
261
|
packages: ['@sendgrid/mail'],
|
|
244
262
|
docs: 'https://docs.sendgrid.com',
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
icon: '📬',
|
|
252
|
-
authType: 'oauth',
|
|
253
|
-
oauthUrl: 'https://account.postmarkapp.com/servers',
|
|
254
|
-
envVars: ['POSTMARK_API_KEY'],
|
|
255
|
-
packages: ['postmark'],
|
|
256
|
-
docs: 'https://postmarkapp.com/developer',
|
|
257
|
-
},
|
|
258
|
-
{
|
|
259
|
-
id: 'mailgun',
|
|
260
|
-
name: 'Mailgun',
|
|
261
|
-
description: 'Email API service',
|
|
262
|
-
category: 'email',
|
|
263
|
-
icon: '📮',
|
|
264
|
-
authType: 'oauth',
|
|
265
|
-
oauthUrl: 'https://app.mailgun.com/app/account/security/api_keys',
|
|
266
|
-
envVars: ['MAILGUN_API_KEY', 'MAILGUN_DOMAIN'],
|
|
267
|
-
packages: ['mailgun.js'],
|
|
268
|
-
docs: 'https://documentation.mailgun.com',
|
|
269
|
-
},
|
|
270
|
-
{
|
|
271
|
-
id: 'react-email',
|
|
272
|
-
name: 'React Email',
|
|
273
|
-
description: 'Build emails with React components',
|
|
274
|
-
category: 'email',
|
|
275
|
-
icon: '⚛️',
|
|
276
|
-
authType: 'npm',
|
|
277
|
-
envVars: [],
|
|
278
|
-
packages: ['react-email', '@react-email/components'],
|
|
279
|
-
docs: 'https://react.email/docs',
|
|
263
|
+
steps: [
|
|
264
|
+
'Sign in at app.sendgrid.com',
|
|
265
|
+
'Go to Settings > API Keys',
|
|
266
|
+
'Create an API key with Mail Send permission',
|
|
267
|
+
'Copy the key (only shown once!)',
|
|
268
|
+
],
|
|
280
269
|
},
|
|
281
270
|
|
|
282
271
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -288,11 +277,20 @@ const INTEGRATIONS: Integration[] = [
|
|
|
288
277
|
description: 'File uploads for Next.js',
|
|
289
278
|
category: 'storage',
|
|
290
279
|
icon: '📤',
|
|
291
|
-
|
|
292
|
-
oauthUrl: 'https://uploadthing.com/dashboard',
|
|
280
|
+
dashboardUrl: 'https://uploadthing.com/dashboard',
|
|
293
281
|
envVars: ['UPLOADTHING_SECRET', 'UPLOADTHING_APP_ID'],
|
|
282
|
+
envVarHints: {
|
|
283
|
+
'UPLOADTHING_SECRET': 'sk_live_...',
|
|
284
|
+
'UPLOADTHING_APP_ID': 'Your app ID',
|
|
285
|
+
},
|
|
294
286
|
packages: ['uploadthing', '@uploadthing/react'],
|
|
295
287
|
docs: 'https://docs.uploadthing.com',
|
|
288
|
+
steps: [
|
|
289
|
+
'Sign in at uploadthing.com',
|
|
290
|
+
'Create or select an app',
|
|
291
|
+
'Go to API Keys',
|
|
292
|
+
'Copy Secret and App ID',
|
|
293
|
+
],
|
|
296
294
|
},
|
|
297
295
|
{
|
|
298
296
|
id: 'cloudinary',
|
|
@@ -300,86 +298,20 @@ const INTEGRATIONS: Integration[] = [
|
|
|
300
298
|
description: 'Image & video management',
|
|
301
299
|
category: 'storage',
|
|
302
300
|
icon: '☁️',
|
|
303
|
-
|
|
304
|
-
oauthUrl: 'https://console.cloudinary.com/settings/api-keys',
|
|
301
|
+
dashboardUrl: 'https://console.cloudinary.com/settings/api-keys',
|
|
305
302
|
envVars: ['CLOUDINARY_CLOUD_NAME', 'CLOUDINARY_API_KEY', 'CLOUDINARY_API_SECRET'],
|
|
303
|
+
envVarHints: {
|
|
304
|
+
'CLOUDINARY_CLOUD_NAME': 'Your cloud name',
|
|
305
|
+
'CLOUDINARY_API_KEY': 'Numeric API key',
|
|
306
|
+
'CLOUDINARY_API_SECRET': 'API Secret',
|
|
307
|
+
},
|
|
306
308
|
packages: ['cloudinary'],
|
|
307
309
|
docs: 'https://cloudinary.com/documentation',
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
category: 'storage',
|
|
314
|
-
icon: '🪣',
|
|
315
|
-
authType: 'oauth',
|
|
316
|
-
oauthUrl: 'https://console.aws.amazon.com/iam/home#/security_credentials',
|
|
317
|
-
envVars: ['AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AWS_REGION', 'AWS_S3_BUCKET'],
|
|
318
|
-
packages: ['@aws-sdk/client-s3'],
|
|
319
|
-
docs: 'https://docs.aws.amazon.com/s3',
|
|
320
|
-
},
|
|
321
|
-
{
|
|
322
|
-
id: 'vercel-blob',
|
|
323
|
-
name: 'Vercel Blob',
|
|
324
|
-
description: 'File storage by Vercel',
|
|
325
|
-
category: 'storage',
|
|
326
|
-
icon: '▲',
|
|
327
|
-
authType: 'oauth',
|
|
328
|
-
oauthUrl: 'https://vercel.com/dashboard/stores',
|
|
329
|
-
envVars: ['BLOB_READ_WRITE_TOKEN'],
|
|
330
|
-
packages: ['@vercel/blob'],
|
|
331
|
-
docs: 'https://vercel.com/docs/storage/vercel-blob',
|
|
332
|
-
},
|
|
333
|
-
|
|
334
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
335
|
-
// ANALYTICS
|
|
336
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
337
|
-
{
|
|
338
|
-
id: 'vercel-analytics',
|
|
339
|
-
name: 'Vercel Analytics',
|
|
340
|
-
description: 'Web analytics by Vercel',
|
|
341
|
-
category: 'analytics',
|
|
342
|
-
icon: '📊',
|
|
343
|
-
authType: 'npm',
|
|
344
|
-
envVars: [],
|
|
345
|
-
packages: ['@vercel/analytics'],
|
|
346
|
-
docs: 'https://vercel.com/docs/analytics',
|
|
347
|
-
},
|
|
348
|
-
{
|
|
349
|
-
id: 'posthog',
|
|
350
|
-
name: 'PostHog',
|
|
351
|
-
description: 'Product analytics & feature flags',
|
|
352
|
-
category: 'analytics',
|
|
353
|
-
icon: '🦔',
|
|
354
|
-
authType: 'oauth',
|
|
355
|
-
oauthUrl: 'https://app.posthog.com/project/settings',
|
|
356
|
-
envVars: ['NEXT_PUBLIC_POSTHOG_KEY', 'NEXT_PUBLIC_POSTHOG_HOST'],
|
|
357
|
-
packages: ['posthog-js'],
|
|
358
|
-
docs: 'https://posthog.com/docs',
|
|
359
|
-
},
|
|
360
|
-
{
|
|
361
|
-
id: 'mixpanel',
|
|
362
|
-
name: 'Mixpanel',
|
|
363
|
-
description: 'Event-based analytics',
|
|
364
|
-
category: 'analytics',
|
|
365
|
-
icon: '📈',
|
|
366
|
-
authType: 'oauth',
|
|
367
|
-
oauthUrl: 'https://mixpanel.com/settings/project',
|
|
368
|
-
envVars: ['NEXT_PUBLIC_MIXPANEL_TOKEN'],
|
|
369
|
-
packages: ['mixpanel-browser'],
|
|
370
|
-
docs: 'https://docs.mixpanel.com',
|
|
371
|
-
},
|
|
372
|
-
{
|
|
373
|
-
id: 'plausible',
|
|
374
|
-
name: 'Plausible',
|
|
375
|
-
description: 'Privacy-friendly analytics',
|
|
376
|
-
category: 'analytics',
|
|
377
|
-
icon: '📉',
|
|
378
|
-
authType: 'oauth',
|
|
379
|
-
oauthUrl: 'https://plausible.io/sites',
|
|
380
|
-
envVars: ['NEXT_PUBLIC_PLAUSIBLE_DOMAIN'],
|
|
381
|
-
packages: ['next-plausible'],
|
|
382
|
-
docs: 'https://plausible.io/docs',
|
|
310
|
+
steps: [
|
|
311
|
+
'Sign in at cloudinary.com',
|
|
312
|
+
'Go to Settings > API Keys',
|
|
313
|
+
'Copy Cloud Name, API Key, API Secret',
|
|
314
|
+
],
|
|
383
315
|
},
|
|
384
316
|
|
|
385
317
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -388,14 +320,22 @@ const INTEGRATIONS: Integration[] = [
|
|
|
388
320
|
{
|
|
389
321
|
id: 'openai',
|
|
390
322
|
name: 'OpenAI',
|
|
391
|
-
description: 'GPT
|
|
323
|
+
description: 'GPT-4, DALL-E, Whisper',
|
|
392
324
|
category: 'ai',
|
|
393
325
|
icon: '🤖',
|
|
394
|
-
|
|
395
|
-
oauthUrl: 'https://platform.openai.com/api-keys',
|
|
326
|
+
dashboardUrl: 'https://platform.openai.com/api-keys',
|
|
396
327
|
envVars: ['OPENAI_API_KEY'],
|
|
328
|
+
envVarHints: {
|
|
329
|
+
'OPENAI_API_KEY': 'sk-...',
|
|
330
|
+
},
|
|
397
331
|
packages: ['openai'],
|
|
398
332
|
docs: 'https://platform.openai.com/docs',
|
|
333
|
+
steps: [
|
|
334
|
+
'Sign in at platform.openai.com',
|
|
335
|
+
'Go to API Keys',
|
|
336
|
+
'Create new secret key',
|
|
337
|
+
'Copy it (only shown once!)',
|
|
338
|
+
],
|
|
399
339
|
},
|
|
400
340
|
{
|
|
401
341
|
id: 'anthropic',
|
|
@@ -403,11 +343,19 @@ const INTEGRATIONS: Integration[] = [
|
|
|
403
343
|
description: 'Claude AI models',
|
|
404
344
|
category: 'ai',
|
|
405
345
|
icon: '🧠',
|
|
406
|
-
|
|
407
|
-
oauthUrl: 'https://console.anthropic.com/settings/keys',
|
|
346
|
+
dashboardUrl: 'https://console.anthropic.com/settings/keys',
|
|
408
347
|
envVars: ['ANTHROPIC_API_KEY'],
|
|
348
|
+
envVarHints: {
|
|
349
|
+
'ANTHROPIC_API_KEY': 'sk-ant-...',
|
|
350
|
+
},
|
|
409
351
|
packages: ['@anthropic-ai/sdk'],
|
|
410
352
|
docs: 'https://docs.anthropic.com',
|
|
353
|
+
steps: [
|
|
354
|
+
'Sign in at console.anthropic.com',
|
|
355
|
+
'Go to API Keys',
|
|
356
|
+
'Create a new key',
|
|
357
|
+
'Copy it (only shown once!)',
|
|
358
|
+
],
|
|
411
359
|
},
|
|
412
360
|
{
|
|
413
361
|
id: 'replicate',
|
|
@@ -415,73 +363,85 @@ const INTEGRATIONS: Integration[] = [
|
|
|
415
363
|
description: 'Run ML models in the cloud',
|
|
416
364
|
category: 'ai',
|
|
417
365
|
icon: '🔄',
|
|
418
|
-
|
|
419
|
-
oauthUrl: 'https://replicate.com/account/api-tokens',
|
|
366
|
+
dashboardUrl: 'https://replicate.com/account/api-tokens',
|
|
420
367
|
envVars: ['REPLICATE_API_TOKEN'],
|
|
368
|
+
envVarHints: {
|
|
369
|
+
'REPLICATE_API_TOKEN': 'r8_...',
|
|
370
|
+
},
|
|
421
371
|
packages: ['replicate'],
|
|
422
372
|
docs: 'https://replicate.com/docs',
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
category: 'ai',
|
|
429
|
-
icon: '✨',
|
|
430
|
-
authType: 'npm',
|
|
431
|
-
envVars: [],
|
|
432
|
-
packages: ['ai'],
|
|
433
|
-
docs: 'https://sdk.vercel.ai/docs',
|
|
434
|
-
},
|
|
435
|
-
{
|
|
436
|
-
id: 'elevenlabs',
|
|
437
|
-
name: 'ElevenLabs',
|
|
438
|
-
description: 'AI voice generation',
|
|
439
|
-
category: 'ai',
|
|
440
|
-
icon: '🎙️',
|
|
441
|
-
authType: 'oauth',
|
|
442
|
-
oauthUrl: 'https://elevenlabs.io/app/settings/api-keys',
|
|
443
|
-
envVars: ['ELEVENLABS_API_KEY'],
|
|
444
|
-
packages: ['elevenlabs'],
|
|
445
|
-
docs: 'https://elevenlabs.io/docs',
|
|
373
|
+
steps: [
|
|
374
|
+
'Sign in at replicate.com',
|
|
375
|
+
'Go to Account > API Tokens',
|
|
376
|
+
'Create a token',
|
|
377
|
+
],
|
|
446
378
|
},
|
|
447
379
|
|
|
448
380
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
449
|
-
//
|
|
381
|
+
// ANALYTICS
|
|
450
382
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
451
383
|
{
|
|
452
|
-
id: '
|
|
453
|
-
name: '
|
|
454
|
-
description: '
|
|
455
|
-
category: '
|
|
456
|
-
icon: '
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
384
|
+
id: 'posthog',
|
|
385
|
+
name: 'PostHog',
|
|
386
|
+
description: 'Product analytics & feature flags',
|
|
387
|
+
category: 'analytics',
|
|
388
|
+
icon: '🦔',
|
|
389
|
+
dashboardUrl: 'https://app.posthog.com/project/settings',
|
|
390
|
+
envVars: ['NEXT_PUBLIC_POSTHOG_KEY', 'NEXT_PUBLIC_POSTHOG_HOST'],
|
|
391
|
+
envVarHints: {
|
|
392
|
+
'NEXT_PUBLIC_POSTHOG_KEY': 'phc_...',
|
|
393
|
+
'NEXT_PUBLIC_POSTHOG_HOST': 'https://app.posthog.com (or your self-hosted URL)',
|
|
394
|
+
},
|
|
395
|
+
packages: ['posthog-js'],
|
|
396
|
+
docs: 'https://posthog.com/docs',
|
|
397
|
+
steps: [
|
|
398
|
+
'Sign up at posthog.com',
|
|
399
|
+
'Create or select a project',
|
|
400
|
+
'Go to Project Settings',
|
|
401
|
+
'Copy Project API Key',
|
|
402
|
+
],
|
|
462
403
|
},
|
|
463
404
|
{
|
|
464
|
-
id: '
|
|
465
|
-
name: '
|
|
466
|
-
description: '
|
|
467
|
-
category: '
|
|
468
|
-
icon: '
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
405
|
+
id: 'vercel-analytics',
|
|
406
|
+
name: 'Vercel Analytics',
|
|
407
|
+
description: 'Web analytics by Vercel',
|
|
408
|
+
category: 'analytics',
|
|
409
|
+
icon: '▲',
|
|
410
|
+
dashboardUrl: 'https://vercel.com/dashboard',
|
|
411
|
+
envVars: [],
|
|
412
|
+
packages: ['@vercel/analytics'],
|
|
413
|
+
docs: 'https://vercel.com/docs/analytics',
|
|
414
|
+
steps: [
|
|
415
|
+
'No API key needed!',
|
|
416
|
+
'Just install the package',
|
|
417
|
+
'Add <Analytics /> to your layout',
|
|
418
|
+
'Deploy to Vercel to see data',
|
|
419
|
+
],
|
|
474
420
|
},
|
|
421
|
+
|
|
422
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
423
|
+
// MONITORING
|
|
424
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
475
425
|
{
|
|
476
|
-
id: '
|
|
477
|
-
name: '
|
|
478
|
-
description: '
|
|
479
|
-
category: '
|
|
480
|
-
icon: '
|
|
481
|
-
|
|
482
|
-
envVars: ['
|
|
483
|
-
|
|
484
|
-
|
|
426
|
+
id: 'sentry',
|
|
427
|
+
name: 'Sentry',
|
|
428
|
+
description: 'Error tracking & performance',
|
|
429
|
+
category: 'monitoring',
|
|
430
|
+
icon: '🐛',
|
|
431
|
+
dashboardUrl: 'https://sentry.io/settings/account/api/auth-tokens/',
|
|
432
|
+
envVars: ['SENTRY_DSN', 'SENTRY_AUTH_TOKEN'],
|
|
433
|
+
envVarHints: {
|
|
434
|
+
'SENTRY_DSN': 'https://xxx@xxx.ingest.sentry.io/xxx',
|
|
435
|
+
'SENTRY_AUTH_TOKEN': 'From Organization Auth Tokens',
|
|
436
|
+
},
|
|
437
|
+
packages: ['@sentry/nextjs'],
|
|
438
|
+
docs: 'https://docs.sentry.io',
|
|
439
|
+
steps: [
|
|
440
|
+
'Sign up at sentry.io',
|
|
441
|
+
'Create a project (Next.js)',
|
|
442
|
+
'Copy DSN from Project Settings > Client Keys',
|
|
443
|
+
'Create auth token in Settings > Auth Tokens',
|
|
444
|
+
],
|
|
485
445
|
},
|
|
486
446
|
|
|
487
447
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -493,11 +453,21 @@ const INTEGRATIONS: Integration[] = [
|
|
|
493
453
|
description: 'SMS, voice & WhatsApp',
|
|
494
454
|
category: 'messaging',
|
|
495
455
|
icon: '📱',
|
|
496
|
-
|
|
497
|
-
oauthUrl: 'https://console.twilio.com/us1/account/keys-credentials/api-keys',
|
|
456
|
+
dashboardUrl: 'https://console.twilio.com',
|
|
498
457
|
envVars: ['TWILIO_ACCOUNT_SID', 'TWILIO_AUTH_TOKEN', 'TWILIO_PHONE_NUMBER'],
|
|
458
|
+
envVarHints: {
|
|
459
|
+
'TWILIO_ACCOUNT_SID': 'AC... (from Console Dashboard)',
|
|
460
|
+
'TWILIO_AUTH_TOKEN': 'From Console Dashboard (click to reveal)',
|
|
461
|
+
'TWILIO_PHONE_NUMBER': '+1234567890 (buy in Phone Numbers)',
|
|
462
|
+
},
|
|
499
463
|
packages: ['twilio'],
|
|
500
464
|
docs: 'https://www.twilio.com/docs',
|
|
465
|
+
steps: [
|
|
466
|
+
'Sign up at twilio.com',
|
|
467
|
+
'Go to Console Dashboard',
|
|
468
|
+
'Copy Account SID and Auth Token',
|
|
469
|
+
'Buy a phone number in Phone Numbers > Manage',
|
|
470
|
+
],
|
|
501
471
|
},
|
|
502
472
|
{
|
|
503
473
|
id: 'pusher',
|
|
@@ -505,63 +475,23 @@ const INTEGRATIONS: Integration[] = [
|
|
|
505
475
|
description: 'Realtime websockets',
|
|
506
476
|
category: 'messaging',
|
|
507
477
|
icon: '🔔',
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
478
|
+
dashboardUrl: 'https://dashboard.pusher.com',
|
|
479
|
+
envVars: ['PUSHER_APP_ID', 'PUSHER_KEY', 'PUSHER_SECRET', 'NEXT_PUBLIC_PUSHER_KEY', 'PUSHER_CLUSTER'],
|
|
480
|
+
envVarHints: {
|
|
481
|
+
'PUSHER_APP_ID': 'App ID from App Keys',
|
|
482
|
+
'PUSHER_KEY': 'Key from App Keys',
|
|
483
|
+
'PUSHER_SECRET': 'Secret from App Keys',
|
|
484
|
+
'NEXT_PUBLIC_PUSHER_KEY': 'Same as PUSHER_KEY',
|
|
485
|
+
'PUSHER_CLUSTER': 'e.g., us2, eu, ap1',
|
|
486
|
+
},
|
|
511
487
|
packages: ['pusher', 'pusher-js'],
|
|
512
488
|
docs: 'https://pusher.com/docs',
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
icon: '🔔',
|
|
520
|
-
authType: 'oauth',
|
|
521
|
-
oauthUrl: 'https://dashboard.knock.app',
|
|
522
|
-
envVars: ['KNOCK_API_KEY', 'NEXT_PUBLIC_KNOCK_PUBLIC_API_KEY'],
|
|
523
|
-
packages: ['@knocklabs/node', '@knocklabs/react'],
|
|
524
|
-
docs: 'https://docs.knock.app',
|
|
525
|
-
},
|
|
526
|
-
{
|
|
527
|
-
id: 'stream',
|
|
528
|
-
name: 'Stream',
|
|
529
|
-
description: 'Chat & activity feeds',
|
|
530
|
-
category: 'messaging',
|
|
531
|
-
icon: '💬',
|
|
532
|
-
authType: 'oauth',
|
|
533
|
-
oauthUrl: 'https://dashboard.getstream.io',
|
|
534
|
-
envVars: ['STREAM_API_KEY', 'STREAM_API_SECRET'],
|
|
535
|
-
packages: ['stream-chat', 'stream-chat-react'],
|
|
536
|
-
docs: 'https://getstream.io/docs',
|
|
537
|
-
},
|
|
538
|
-
|
|
539
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
540
|
-
// MONITORING
|
|
541
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
542
|
-
{
|
|
543
|
-
id: 'sentry',
|
|
544
|
-
name: 'Sentry',
|
|
545
|
-
description: 'Error tracking & performance',
|
|
546
|
-
category: 'monitoring',
|
|
547
|
-
icon: '🐛',
|
|
548
|
-
authType: 'oauth',
|
|
549
|
-
oauthUrl: 'https://sentry.io/settings/account/api/auth-tokens/',
|
|
550
|
-
envVars: ['SENTRY_DSN', 'SENTRY_AUTH_TOKEN'],
|
|
551
|
-
packages: ['@sentry/nextjs'],
|
|
552
|
-
docs: 'https://docs.sentry.io',
|
|
553
|
-
},
|
|
554
|
-
{
|
|
555
|
-
id: 'logrocket',
|
|
556
|
-
name: 'LogRocket',
|
|
557
|
-
description: 'Session replay & monitoring',
|
|
558
|
-
category: 'monitoring',
|
|
559
|
-
icon: '🚀',
|
|
560
|
-
authType: 'oauth',
|
|
561
|
-
oauthUrl: 'https://app.logrocket.com/settings/setup',
|
|
562
|
-
envVars: ['NEXT_PUBLIC_LOGROCKET_APP_ID'],
|
|
563
|
-
packages: ['logrocket'],
|
|
564
|
-
docs: 'https://docs.logrocket.com',
|
|
489
|
+
steps: [
|
|
490
|
+
'Sign up at pusher.com',
|
|
491
|
+
'Create a Channels app',
|
|
492
|
+
'Go to App Keys',
|
|
493
|
+
'Copy all credentials',
|
|
494
|
+
],
|
|
565
495
|
},
|
|
566
496
|
|
|
567
497
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -573,11 +503,19 @@ const INTEGRATIONS: Integration[] = [
|
|
|
573
503
|
description: 'Deploy Next.js apps',
|
|
574
504
|
category: 'deployment',
|
|
575
505
|
icon: '▲',
|
|
576
|
-
|
|
577
|
-
oauthUrl: 'https://vercel.com/account/tokens',
|
|
506
|
+
dashboardUrl: 'https://vercel.com/account/tokens',
|
|
578
507
|
envVars: ['VERCEL_TOKEN'],
|
|
508
|
+
envVarHints: {
|
|
509
|
+
'VERCEL_TOKEN': 'From Account Settings > Tokens',
|
|
510
|
+
},
|
|
579
511
|
packages: ['vercel'],
|
|
580
512
|
docs: 'https://vercel.com/docs',
|
|
513
|
+
steps: [
|
|
514
|
+
'Sign in at vercel.com',
|
|
515
|
+
'Go to Account Settings > Tokens',
|
|
516
|
+
'Create a new token',
|
|
517
|
+
'Copy it',
|
|
518
|
+
],
|
|
581
519
|
},
|
|
582
520
|
{
|
|
583
521
|
id: 'github',
|
|
@@ -585,51 +523,46 @@ const INTEGRATIONS: Integration[] = [
|
|
|
585
523
|
description: 'Code hosting & CI/CD',
|
|
586
524
|
category: 'deployment',
|
|
587
525
|
icon: '🐙',
|
|
588
|
-
|
|
589
|
-
oauthUrl: 'https://github.com/settings/tokens',
|
|
526
|
+
dashboardUrl: 'https://github.com/settings/tokens',
|
|
590
527
|
envVars: ['GITHUB_TOKEN'],
|
|
528
|
+
envVarHints: {
|
|
529
|
+
'GITHUB_TOKEN': 'ghp_... or github_pat_...',
|
|
530
|
+
},
|
|
591
531
|
packages: ['octokit'],
|
|
592
532
|
docs: 'https://docs.github.com',
|
|
533
|
+
steps: [
|
|
534
|
+
'Sign in at github.com',
|
|
535
|
+
'Go to Settings > Developer settings > Personal access tokens',
|
|
536
|
+
'Generate new token (classic)',
|
|
537
|
+
'Select scopes: repo, user',
|
|
538
|
+
'Copy the token',
|
|
539
|
+
],
|
|
593
540
|
},
|
|
594
541
|
|
|
595
542
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
596
|
-
//
|
|
543
|
+
// CMS
|
|
597
544
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
598
545
|
{
|
|
599
|
-
id: '
|
|
600
|
-
name: '
|
|
601
|
-
description: '
|
|
602
|
-
category: '
|
|
603
|
-
icon: '
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
packages: ['@intercom/messenger-js-sdk'],
|
|
620
|
-
docs: 'https://developers.intercom.com',
|
|
621
|
-
},
|
|
622
|
-
{
|
|
623
|
-
id: 'cal',
|
|
624
|
-
name: 'Cal.com',
|
|
625
|
-
description: 'Scheduling infrastructure',
|
|
626
|
-
category: 'other',
|
|
627
|
-
icon: '📅',
|
|
628
|
-
authType: 'oauth',
|
|
629
|
-
oauthUrl: 'https://app.cal.com/settings/developer/api-keys',
|
|
630
|
-
envVars: ['CAL_API_KEY'],
|
|
631
|
-
packages: ['@calcom/embed-react'],
|
|
632
|
-
docs: 'https://cal.com/docs',
|
|
546
|
+
id: 'sanity',
|
|
547
|
+
name: 'Sanity',
|
|
548
|
+
description: 'Headless CMS',
|
|
549
|
+
category: 'cms',
|
|
550
|
+
icon: '📝',
|
|
551
|
+
dashboardUrl: 'https://www.sanity.io/manage',
|
|
552
|
+
envVars: ['NEXT_PUBLIC_SANITY_PROJECT_ID', 'NEXT_PUBLIC_SANITY_DATASET', 'SANITY_API_TOKEN'],
|
|
553
|
+
envVarHints: {
|
|
554
|
+
'NEXT_PUBLIC_SANITY_PROJECT_ID': 'From project settings',
|
|
555
|
+
'NEXT_PUBLIC_SANITY_DATASET': 'Usually "production"',
|
|
556
|
+
'SANITY_API_TOKEN': 'From API > Tokens',
|
|
557
|
+
},
|
|
558
|
+
packages: ['@sanity/client', 'next-sanity'],
|
|
559
|
+
docs: 'https://www.sanity.io/docs',
|
|
560
|
+
steps: [
|
|
561
|
+
'Sign in at sanity.io/manage',
|
|
562
|
+
'Create or select a project',
|
|
563
|
+
'Copy Project ID from settings',
|
|
564
|
+
'Create API token in API > Tokens',
|
|
565
|
+
],
|
|
633
566
|
},
|
|
634
567
|
];
|
|
635
568
|
|
|
@@ -638,32 +571,43 @@ const INTEGRATIONS: Integration[] = [
|
|
|
638
571
|
// ============================================================================
|
|
639
572
|
|
|
640
573
|
export async function integrateCommand(integrationId?: string): Promise<void> {
|
|
641
|
-
const config = new Config();
|
|
642
|
-
|
|
643
574
|
console.log(chalk.cyan(`
|
|
644
575
|
╔═══════════════════════════════════════════════════════════════╗
|
|
645
576
|
║ 🔌 ONE-CLICK INTEGRATIONS ║
|
|
646
577
|
║ ║
|
|
647
|
-
║ ${INTEGRATIONS.length}
|
|
648
|
-
║ Browser-based auth • Auto-install • Ready in seconds ║
|
|
578
|
+
║ ${INTEGRATIONS.length} services • Step-by-step guidance • Auto-setup ║
|
|
649
579
|
╚═══════════════════════════════════════════════════════════════╝
|
|
650
580
|
`));
|
|
651
581
|
|
|
652
|
-
//
|
|
582
|
+
// Direct install if ID provided
|
|
653
583
|
if (integrationId) {
|
|
654
584
|
const integration = INTEGRATIONS.find(i => i.id === integrationId);
|
|
655
585
|
if (!integration) {
|
|
656
586
|
p.log.error(`Unknown integration: ${integrationId}`);
|
|
657
|
-
console.log(chalk.dim(
|
|
587
|
+
console.log(chalk.dim(`\nAvailable: ${INTEGRATIONS.map(i => i.id).join(', ')}`));
|
|
658
588
|
return;
|
|
659
589
|
}
|
|
660
|
-
await installIntegration(integration
|
|
590
|
+
await installIntegration(integration);
|
|
661
591
|
return;
|
|
662
592
|
}
|
|
663
593
|
|
|
664
|
-
//
|
|
594
|
+
// Category selection
|
|
665
595
|
const categories = [...new Set(INTEGRATIONS.map(i => i.category))];
|
|
666
596
|
|
|
597
|
+
const categoryLabels: Record<string, string> = {
|
|
598
|
+
auth: '🔐 Authentication',
|
|
599
|
+
database: '🗄️ Database',
|
|
600
|
+
payments: '💳 Payments',
|
|
601
|
+
email: '📧 Email',
|
|
602
|
+
storage: '📦 Storage',
|
|
603
|
+
ai: '🤖 AI & ML',
|
|
604
|
+
analytics: '📊 Analytics',
|
|
605
|
+
monitoring: '🐛 Monitoring',
|
|
606
|
+
messaging: '💬 Messaging',
|
|
607
|
+
deployment: '🚀 Deployment',
|
|
608
|
+
cms: '📝 CMS',
|
|
609
|
+
};
|
|
610
|
+
|
|
667
611
|
const category = await p.select({
|
|
668
612
|
message: 'Choose a category:',
|
|
669
613
|
options: [
|
|
@@ -671,42 +615,42 @@ export async function integrateCommand(integrationId?: string): Promise<void> {
|
|
|
671
615
|
{ value: 'search', label: '🔍 Search by name' },
|
|
672
616
|
...categories.map(c => ({
|
|
673
617
|
value: c,
|
|
674
|
-
label:
|
|
675
|
-
hint: `${INTEGRATIONS.filter(i => i.category === c).length}
|
|
618
|
+
label: categoryLabels[c] || c,
|
|
619
|
+
hint: `${INTEGRATIONS.filter(i => i.category === c).length} services`,
|
|
676
620
|
})),
|
|
677
621
|
],
|
|
678
622
|
});
|
|
679
623
|
|
|
680
624
|
if (p.isCancel(category)) return;
|
|
681
625
|
|
|
682
|
-
let
|
|
626
|
+
let filtered = INTEGRATIONS;
|
|
683
627
|
|
|
684
628
|
if (category === 'search') {
|
|
685
629
|
const query = await p.text({
|
|
686
|
-
message: 'Search
|
|
687
|
-
placeholder: 'stripe,
|
|
630
|
+
message: 'Search:',
|
|
631
|
+
placeholder: 'stripe, supabase, openai...',
|
|
688
632
|
});
|
|
689
633
|
if (p.isCancel(query)) return;
|
|
690
634
|
|
|
691
635
|
const q = (query as string).toLowerCase();
|
|
692
|
-
|
|
636
|
+
filtered = INTEGRATIONS.filter(i =>
|
|
693
637
|
i.name.toLowerCase().includes(q) ||
|
|
694
638
|
i.description.toLowerCase().includes(q) ||
|
|
695
|
-
i.id.
|
|
639
|
+
i.id.includes(q)
|
|
696
640
|
);
|
|
641
|
+
|
|
642
|
+
if (filtered.length === 0) {
|
|
643
|
+
p.log.warn('No integrations found');
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
697
646
|
} else if (category !== 'all') {
|
|
698
|
-
|
|
647
|
+
filtered = INTEGRATIONS.filter(i => i.category === category);
|
|
699
648
|
}
|
|
700
649
|
|
|
701
|
-
|
|
702
|
-
p.log.warn('No integrations found');
|
|
703
|
-
return;
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
// Show integration selection
|
|
650
|
+
// Select integration
|
|
707
651
|
const selected = await p.select({
|
|
708
|
-
message: 'Select
|
|
709
|
-
options:
|
|
652
|
+
message: 'Select integration:',
|
|
653
|
+
options: filtered.map(i => ({
|
|
710
654
|
value: i.id,
|
|
711
655
|
label: `${i.icon} ${i.name}`,
|
|
712
656
|
hint: i.description,
|
|
@@ -716,36 +660,23 @@ export async function integrateCommand(integrationId?: string): Promise<void> {
|
|
|
716
660
|
if (p.isCancel(selected)) return;
|
|
717
661
|
|
|
718
662
|
const integration = INTEGRATIONS.find(i => i.id === selected)!;
|
|
719
|
-
await installIntegration(integration
|
|
663
|
+
await installIntegration(integration);
|
|
720
664
|
}
|
|
721
665
|
|
|
722
666
|
// ============================================================================
|
|
723
667
|
// INSTALL INTEGRATION
|
|
724
668
|
// ============================================================================
|
|
725
669
|
|
|
726
|
-
async function installIntegration(integration: Integration
|
|
727
|
-
console.log(chalk.cyan(
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
if (integration.packages && integration.packages.length > 0) {
|
|
733
|
-
steps.push({ name: 'Install packages', done: false });
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
// Step 2: Get credentials (OAuth or API key)
|
|
737
|
-
if (integration.envVars.length > 0) {
|
|
738
|
-
steps.push({ name: 'Configure credentials', done: false });
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
// Step 3: Create setup files
|
|
742
|
-
steps.push({ name: 'Setup integration', done: false });
|
|
743
|
-
|
|
744
|
-
// Execute steps
|
|
745
|
-
const spinner = p.spinner();
|
|
670
|
+
async function installIntegration(integration: Integration): Promise<void> {
|
|
671
|
+
console.log(chalk.cyan(`
|
|
672
|
+
┌─────────────────────────────────────────────────────────────
|
|
673
|
+
│ ${integration.icon} Installing ${integration.name}
|
|
674
|
+
└─────────────────────────────────────────────────────────────
|
|
675
|
+
`));
|
|
746
676
|
|
|
747
677
|
// STEP 1: Install packages
|
|
748
678
|
if (integration.packages && integration.packages.length > 0) {
|
|
679
|
+
const spinner = p.spinner();
|
|
749
680
|
spinner.start(`Installing ${integration.packages.join(', ')}...`);
|
|
750
681
|
|
|
751
682
|
try {
|
|
@@ -753,36 +684,57 @@ async function installIntegration(integration: Integration, config: Config): Pro
|
|
|
753
684
|
cwd: process.cwd(),
|
|
754
685
|
reject: false,
|
|
755
686
|
});
|
|
756
|
-
spinner.stop(
|
|
687
|
+
spinner.stop(chalk.green('✓ Packages installed'));
|
|
757
688
|
} catch {
|
|
758
|
-
spinner.stop(
|
|
689
|
+
spinner.stop(chalk.yellow('⚠ Package install had issues (may already be installed)'));
|
|
759
690
|
}
|
|
760
691
|
}
|
|
761
692
|
|
|
762
693
|
// STEP 2: Get credentials
|
|
763
694
|
if (integration.envVars.length > 0) {
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
695
|
+
// Show step-by-step instructions
|
|
696
|
+
console.log(chalk.cyan(`
|
|
697
|
+
┌─────────────────────────────────────────────────────────────
|
|
698
|
+
│ 📋 HOW TO GET YOUR API KEYS
|
|
699
|
+
└─────────────────────────────────────────────────────────────
|
|
700
|
+
`));
|
|
701
|
+
|
|
702
|
+
integration.steps.forEach((step, i) => {
|
|
703
|
+
console.log(chalk.white(` ${i + 1}. ${step}`));
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
console.log(chalk.dim(`
|
|
707
|
+
Dashboard: ${integration.dashboardUrl}
|
|
708
|
+
Docs: ${integration.docs}
|
|
709
|
+
`));
|
|
710
|
+
|
|
711
|
+
// Offer to open dashboard
|
|
712
|
+
const openDashboard = await p.confirm({
|
|
713
|
+
message: `Open ${integration.name} dashboard in browser?`,
|
|
714
|
+
initialValue: true,
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
if (openDashboard && !p.isCancel(openDashboard)) {
|
|
718
|
+
await open(integration.dashboardUrl);
|
|
719
|
+
console.log(chalk.dim(`\n ✓ Browser opened! Get your keys and come back.\n`));
|
|
720
|
+
await sleep(2000);
|
|
774
721
|
}
|
|
775
722
|
|
|
776
723
|
// Collect credentials
|
|
724
|
+
console.log(chalk.cyan(`\n Enter your credentials:\n`));
|
|
725
|
+
|
|
777
726
|
const credentials: Record<string, string> = {};
|
|
778
727
|
|
|
779
728
|
for (const envVar of integration.envVars) {
|
|
729
|
+
const hint = integration.envVarHints?.[envVar] || 'Paste here...';
|
|
780
730
|
const isPublic = envVar.startsWith('NEXT_PUBLIC_');
|
|
781
|
-
const
|
|
731
|
+
const label = isPublic
|
|
732
|
+
? chalk.yellow('(public)')
|
|
733
|
+
: chalk.green('(secret)');
|
|
782
734
|
|
|
783
735
|
const value = await p.text({
|
|
784
|
-
message: `${envVar} ${
|
|
785
|
-
placeholder:
|
|
736
|
+
message: `${envVar} ${label}`,
|
|
737
|
+
placeholder: hint,
|
|
786
738
|
validate: (v) => !v ? 'Required' : undefined,
|
|
787
739
|
});
|
|
788
740
|
|
|
@@ -792,88 +744,50 @@ async function installIntegration(integration: Integration, config: Config): Pro
|
|
|
792
744
|
|
|
793
745
|
// Save to .env.local
|
|
794
746
|
await saveEnvVars(credentials);
|
|
795
|
-
console.log(chalk.green(
|
|
747
|
+
console.log(chalk.green(`\n ✓ Saved to .env.local\n`));
|
|
796
748
|
}
|
|
797
749
|
|
|
798
750
|
// STEP 3: Generate setup code
|
|
799
|
-
spinner.
|
|
751
|
+
const spinner = p.spinner();
|
|
752
|
+
spinner.start('Creating setup file...');
|
|
800
753
|
|
|
801
|
-
|
|
754
|
+
await generateSetupCode(integration);
|
|
802
755
|
|
|
803
|
-
|
|
804
|
-
for (const file of setupCode) {
|
|
805
|
-
await fs.ensureDir(path.dirname(file.path));
|
|
806
|
-
await fs.writeFile(file.path, file.content);
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
spinner.stop('✓ Setup complete');
|
|
756
|
+
spinner.stop(chalk.green('✓ Setup complete'));
|
|
811
757
|
|
|
812
|
-
// Success
|
|
758
|
+
// Success!
|
|
813
759
|
console.log(chalk.green(`
|
|
814
760
|
╔═══════════════════════════════════════════════════════════════╗
|
|
815
|
-
║
|
|
761
|
+
║ ✅ ${integration.name} installed!
|
|
816
762
|
╠═══════════════════════════════════════════════════════════════╣
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
if (integration.envVars.length > 0) {
|
|
824
|
-
console.log(chalk.dim(` Env vars: ${integration.envVars.join(', ')}`));
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
console.log(`
|
|
828
|
-
║ 📚 Docs: ${integration.docs}
|
|
763
|
+
║ ║
|
|
764
|
+
║ Import: import { ... } from '@/lib/${integration.id}'
|
|
765
|
+
║ Docs: ${integration.docs.substring(0, 45).padEnd(45)}║
|
|
766
|
+
║ ║
|
|
829
767
|
╚═══════════════════════════════════════════════════════════════╝
|
|
830
|
-
`);
|
|
768
|
+
`));
|
|
831
769
|
}
|
|
832
770
|
|
|
833
771
|
// ============================================================================
|
|
834
772
|
// HELPERS
|
|
835
773
|
// ============================================================================
|
|
836
774
|
|
|
837
|
-
function getCategoryLabel(category: string): string {
|
|
838
|
-
const labels: Record<string, string> = {
|
|
839
|
-
auth: '🔐 Authentication',
|
|
840
|
-
database: '🗄️ Databases',
|
|
841
|
-
payments: '💳 Payments',
|
|
842
|
-
email: '📧 Email',
|
|
843
|
-
storage: '📦 Storage',
|
|
844
|
-
analytics: '📊 Analytics',
|
|
845
|
-
ai: '🤖 AI & ML',
|
|
846
|
-
cms: '📝 CMS',
|
|
847
|
-
messaging: '💬 Messaging',
|
|
848
|
-
monitoring: '🔍 Monitoring',
|
|
849
|
-
deployment: '🚀 Deployment',
|
|
850
|
-
other: '🔧 Other',
|
|
851
|
-
};
|
|
852
|
-
return labels[category] || category;
|
|
853
|
-
}
|
|
854
|
-
|
|
855
775
|
async function saveEnvVars(vars: Record<string, string>): Promise<void> {
|
|
856
776
|
const envPath = path.join(process.cwd(), '.env.local');
|
|
857
777
|
let content = '';
|
|
858
778
|
|
|
859
|
-
// Read existing content
|
|
860
779
|
if (await fs.pathExists(envPath)) {
|
|
861
780
|
content = await fs.readFile(envPath, 'utf-8');
|
|
862
|
-
if (!content.endsWith('\n'))
|
|
863
|
-
content += '\n';
|
|
864
|
-
}
|
|
865
|
-
content += '\n# Added by CodeBakers\n';
|
|
781
|
+
if (!content.endsWith('\n')) content += '\n';
|
|
866
782
|
}
|
|
867
783
|
|
|
868
|
-
|
|
784
|
+
content += '\n# Added by CodeBakers\n';
|
|
785
|
+
|
|
869
786
|
for (const [key, value] of Object.entries(vars)) {
|
|
870
|
-
|
|
871
|
-
const regex = new RegExp(`^${key}=`, 'm');
|
|
787
|
+
const regex = new RegExp(`^${key}=.*$`, 'm');
|
|
872
788
|
if (regex.test(content)) {
|
|
873
|
-
// Update existing
|
|
874
789
|
content = content.replace(regex, `${key}=${value}`);
|
|
875
790
|
} else {
|
|
876
|
-
// Add new
|
|
877
791
|
content += `${key}=${value}\n`;
|
|
878
792
|
}
|
|
879
793
|
}
|
|
@@ -881,105 +795,90 @@ async function saveEnvVars(vars: Record<string, string>): Promise<void> {
|
|
|
881
795
|
await fs.writeFile(envPath, content);
|
|
882
796
|
}
|
|
883
797
|
|
|
884
|
-
async function generateSetupCode(integration: Integration): Promise<
|
|
885
|
-
|
|
886
|
-
|
|
798
|
+
async function generateSetupCode(integration: Integration): Promise<void> {
|
|
799
|
+
const libDir = path.join(process.cwd(), 'src', 'lib');
|
|
800
|
+
await fs.ensureDir(libDir);
|
|
801
|
+
|
|
802
|
+
let code = '';
|
|
887
803
|
|
|
888
804
|
switch (integration.id) {
|
|
889
|
-
case '
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
content: `import { clerkMiddleware } from '@clerk/nextjs/server';
|
|
805
|
+
case 'supabase':
|
|
806
|
+
case 'supabase-auth':
|
|
807
|
+
code = `import { createClient } from '@supabase/supabase-js';
|
|
893
808
|
|
|
894
|
-
|
|
809
|
+
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
|
|
810
|
+
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
|
|
895
811
|
|
|
896
|
-
export const
|
|
897
|
-
|
|
898
|
-
};
|
|
899
|
-
`,
|
|
900
|
-
});
|
|
812
|
+
export const supabase = createClient(supabaseUrl, supabaseAnonKey);
|
|
813
|
+
`;
|
|
901
814
|
break;
|
|
902
815
|
|
|
903
816
|
case 'stripe':
|
|
904
|
-
|
|
905
|
-
path: 'src/lib/stripe.ts',
|
|
906
|
-
content: `import Stripe from 'stripe';
|
|
817
|
+
code = `import Stripe from 'stripe';
|
|
907
818
|
|
|
908
819
|
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
|
|
909
820
|
apiVersion: '2023-10-16',
|
|
910
|
-
typescript: true,
|
|
911
821
|
});
|
|
912
|
-
|
|
913
|
-
});
|
|
914
|
-
break;
|
|
915
|
-
|
|
916
|
-
case 'supabase':
|
|
917
|
-
files.push({
|
|
918
|
-
path: 'src/lib/supabase.ts',
|
|
919
|
-
content: `import { createClient } from '@supabase/supabase-js';
|
|
920
|
-
|
|
921
|
-
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
|
|
922
|
-
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
|
|
923
|
-
|
|
924
|
-
export const supabase = createClient(supabaseUrl, supabaseAnonKey);
|
|
925
|
-
`,
|
|
926
|
-
});
|
|
822
|
+
`;
|
|
927
823
|
break;
|
|
928
824
|
|
|
929
825
|
case 'resend':
|
|
930
|
-
|
|
931
|
-
path: 'src/lib/resend.ts',
|
|
932
|
-
content: `import { Resend } from 'resend';
|
|
826
|
+
code = `import { Resend } from 'resend';
|
|
933
827
|
|
|
934
828
|
export const resend = new Resend(process.env.RESEND_API_KEY);
|
|
935
|
-
|
|
936
|
-
});
|
|
829
|
+
`;
|
|
937
830
|
break;
|
|
938
831
|
|
|
939
832
|
case 'openai':
|
|
940
|
-
|
|
941
|
-
path: 'src/lib/openai.ts',
|
|
942
|
-
content: `import OpenAI from 'openai';
|
|
833
|
+
code = `import OpenAI from 'openai';
|
|
943
834
|
|
|
944
835
|
export const openai = new OpenAI({
|
|
945
836
|
apiKey: process.env.OPENAI_API_KEY,
|
|
946
837
|
});
|
|
947
|
-
|
|
948
|
-
});
|
|
838
|
+
`;
|
|
949
839
|
break;
|
|
950
840
|
|
|
951
841
|
case 'anthropic':
|
|
952
|
-
|
|
953
|
-
path: 'src/lib/anthropic.ts',
|
|
954
|
-
content: `import Anthropic from '@anthropic-ai/sdk';
|
|
842
|
+
code = `import Anthropic from '@anthropic-ai/sdk';
|
|
955
843
|
|
|
956
844
|
export const anthropic = new Anthropic({
|
|
957
845
|
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
958
846
|
});
|
|
959
|
-
|
|
960
|
-
});
|
|
847
|
+
`;
|
|
961
848
|
break;
|
|
962
849
|
|
|
963
|
-
case '
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
content: `import * as Sentry from '@sentry/nextjs';
|
|
850
|
+
case 'clerk':
|
|
851
|
+
code = `// Clerk is configured via middleware
|
|
852
|
+
// See: https://clerk.com/docs/quickstarts/nextjs
|
|
967
853
|
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
854
|
+
// In middleware.ts:
|
|
855
|
+
// import { clerkMiddleware } from '@clerk/nextjs/server';
|
|
856
|
+
// export default clerkMiddleware();
|
|
857
|
+
|
|
858
|
+
// In layout.tsx:
|
|
859
|
+
// import { ClerkProvider } from '@clerk/nextjs';
|
|
860
|
+
// <ClerkProvider>{children}</ClerkProvider>
|
|
861
|
+
|
|
862
|
+
export {};
|
|
863
|
+
`;
|
|
974
864
|
break;
|
|
865
|
+
|
|
866
|
+
default:
|
|
867
|
+
code = `// ${integration.name}
|
|
868
|
+
// Docs: ${integration.docs}
|
|
869
|
+
//
|
|
870
|
+
// Environment variables:
|
|
871
|
+
${integration.envVars.map(v => `// - ${v}`).join('\n')}
|
|
872
|
+
|
|
873
|
+
export {};
|
|
874
|
+
`;
|
|
975
875
|
}
|
|
976
876
|
|
|
977
|
-
|
|
877
|
+
await fs.writeFile(path.join(libDir, `${integration.id}.ts`), code);
|
|
978
878
|
}
|
|
979
879
|
|
|
980
880
|
function sleep(ms: number): Promise<void> {
|
|
981
881
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
982
882
|
}
|
|
983
883
|
|
|
984
|
-
// Export for use elsewhere
|
|
985
884
|
export { INTEGRATIONS, Integration };
|