@shipindays/shipindays 0.1.19 → 0.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/index.js CHANGED
@@ -6,6 +6,8 @@ import fs from "fs-extra";
6
6
  import path from "path";
7
7
  import { execSync } from "child_process";
8
8
  import { fileURLToPath } from "url";
9
+ import figlet from "figlet";
10
+ import gradient from "gradient-string";
9
11
 
10
12
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
13
 
@@ -14,8 +16,14 @@ const BASE_DIR = path.join(TEMPLATES_DIR, "base");
14
16
  const BLOCKS_DIR = path.join(TEMPLATES_DIR, "blocks");
15
17
 
16
18
  function printBanner() {
19
+ const bannerText = figlet.textSync("Ship In Days", {
20
+ font: "Slant",
21
+ });
22
+
17
23
  console.log("\n");
18
- console.log(chalk.green(" ⚡ shipindays"));
24
+
25
+ // This creates a smooth transition from Orange to Purple
26
+ console.log(gradient(["#FF8C00", "#8A2BE2"])(bannerText));
19
27
  console.log(chalk.dim(" Ship your SaaS in days, not months."));
20
28
  console.log(chalk.dim(" https://shipindays.nikhilsai.in\n"));
21
29
  }
@@ -54,14 +62,38 @@ const PAYMENT_PROVIDERS = {
54
62
  },
55
63
  };
56
64
 
65
+ // DATABASE providers constants
66
+ const DATABASE_PROVIDERS = {
67
+ drizzle: {
68
+ label: "Drizzle ORM + Supabase PostgreSQL",
69
+ hint: "Lightweight SQL-first ORM using Supabase Postgres",
70
+ },
71
+ prisma: {
72
+ label: "Prisma ORM + PostgreSQL",
73
+ hint: "Type-safe ORM with migrations & powerful client",
74
+ },
75
+ };
76
+
57
77
  const ENV_VARS = {
58
78
  base: {
59
- "# App": [
60
- "NEXT_PUBLIC_APP_URL=http://localhost:3000",
61
- ],
79
+ "# App": ["NEXT_PUBLIC_APP_URL=http://localhost:3000"],
80
+ },
81
+
82
+ // 1. Move database to the top level (Fixes the missing Drizzle envs)
83
+ database: {
84
+ drizzle: {
85
+ "# Supabase database url! go to supabase -> connect -> transaction pooler": [
86
+ "DATABASE_URL=",
87
+ ],
88
+ },
89
+
90
+ prisma: {
91
+ "# PostgreSQL connection (Supabase or any provider)": [
92
+ "DATABASE_URL=",
93
+ ],
94
+ },
62
95
  },
63
96
 
64
- // auth env's
65
97
  auth: {
66
98
  supabase: {
67
99
  "# Supabase (supabase.com → project → settings → API)": [
@@ -71,10 +103,7 @@ const ENV_VARS = {
71
103
  ],
72
104
  },
73
105
  nextauth: {
74
- "# NextAuth": [
75
- "AUTH_SECRET=",
76
- "NEXTAUTH_URL=http://localhost:3000",
77
- ],
106
+ "# NextAuth": ["AUTH_SECRET=", "NEXTAUTH_URL=http://localhost:3000"],
78
107
  "# OAuth — Google (console.cloud.google.com)": [
79
108
  "AUTH_GOOGLE_ID=",
80
109
  "AUTH_GOOGLE_SECRET=",
@@ -82,12 +111,9 @@ const ENV_VARS = {
82
111
  },
83
112
  },
84
113
 
85
- // emails env's
86
114
  email: {
87
115
  resend: {
88
- "# Resend (resend.com → API Keys)": [
89
- "RESEND_API_KEY=",
90
- ],
116
+ "# Resend (resend.com → API Keys)": ["RESEND_API_KEY="],
91
117
  },
92
118
  mailgun: {
93
119
  "# Mailgun (mailgun.com → Sending → Domains)": [
@@ -95,36 +121,41 @@ const ENV_VARS = {
95
121
  "MAILGUN_DOMAIN=",
96
122
  ],
97
123
  },
124
+ },
98
125
 
99
- // payments env's
100
- payments: {
101
- stripe: {
102
- "# Stripe (dashboard.stripe.com → Developers → API keys)": [
103
- "STRIPE_SECRET_KEY=",
104
- "STRIPE_WEBHOOK_SECRET=",
105
- "NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=",
106
- ],
107
- "# Stripe Pricing (Create products in Stripe dashboard)": [
108
- "NEXT_PUBLIC_STRIPE_PRICE_ID_BASIC=",
109
- "NEXT_PUBLIC_STRIPE_PRICE_ID_PRO=",
110
- ],
111
- },
112
-
113
- dodopayments: {
114
- "# Dodo Payments (app.dodopayments.com → Developers → API Keys)": [
115
- "DODO_PAYMENTS_API_KEY=",
116
- "DODO_PAYMENTS_WEBHOOK_KEY=",
117
- ],
118
- "# Dodo Pricing (Create products in Dodo dashboard)": [
119
- "NEXT_PUBLIC_DODO_PRICE_ID_BASIC=",
120
- "NEXT_PUBLIC_DODO_PRICE_ID_PRO=",
121
- ],
122
- },
126
+ // 2. Move payments to the top level (Fixes the missing Dodo envs)
127
+ payments: {
128
+ stripe: {
129
+ "# Stripe (dashboard.stripe.com → Developers → API keys)": [
130
+ "STRIPE_SECRET_KEY=",
131
+ "STRIPE_WEBHOOK_SECRET=",
132
+ "NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=",
133
+ ],
134
+ "# Stripe Pricing": [
135
+ "NEXT_PUBLIC_STRIPE_PRICE_ID_BASIC=",
136
+ "NEXT_PUBLIC_STRIPE_PRICE_ID_PRO=",
137
+ ],
138
+ },
139
+ dodopayments: {
140
+ "# Dodo Payments (app.dodopayments.com → Developers → API Keys)": [
141
+ "DODO_PAYMENTS_API_KEY=",
142
+ "DODO_PAYMENTS_WEBHOOK_KEY=",
143
+ ],
144
+ "# Dodo Pricing": [
145
+ "NEXT_PUBLIC_DODO_PRICE_ID_BASIC=",
146
+ "NEXT_PUBLIC_DODO_PRICE_ID_PRO=",
147
+ ],
123
148
  },
124
-
125
149
  },
126
150
  };
127
151
 
152
+ // block mapping
153
+ // variable name : folder name
154
+ const DATABASE_BLOCK_MAP = {
155
+ drizzle: "drizzle-supabase",
156
+ prisma: "prisma-supabase",
157
+ };
158
+
128
159
  /**
129
160
  * RECURSIVE DIRECTORY MERGE
130
161
  * * This function walks through the source directory and copies files to the destination.
@@ -342,7 +373,16 @@ async function main() {
342
373
  }
343
374
  }
344
375
 
345
- // 2. Pick auth provider
376
+ // pick database provider
377
+ const dbProvider = await p.select({
378
+ message: "Database Provider",
379
+ options: Object.entries(DATABASE_PROVIDERS).map(([value, { label, hint }]) => ({
380
+ value, label, hint,
381
+ })),
382
+ })
383
+ if (p.isCancel(dbProvider)) { p.cancel("Cancelled."); process.exit(0); }
384
+
385
+ // Pick auth provider
346
386
  const authProvider = await p.select({
347
387
  message: "Auth provider",
348
388
  options: Object.entries(AUTH_PROVIDERS).map(([value, { label, hint }]) => ({
@@ -367,7 +407,12 @@ async function main() {
367
407
  })),
368
408
  });
369
409
 
370
- const choices = { auth: authProvider, email: emailProvider, payments: paymentProvider };
410
+ const choices = {
411
+ database: dbProvider,
412
+ auth: authProvider,
413
+ email: emailProvider,
414
+ payments: paymentProvider
415
+ };
371
416
 
372
417
  // 4. Git + install preferences
373
418
  const initGit = await p.confirm({
@@ -392,15 +437,21 @@ async function main() {
392
437
 
393
438
  // 6. Inject Blocks
394
439
  // The injectBlock function now handles the internal recursion correctly.
395
- const features = ["auth", "email", "payments"];
440
+ const features = ["database", "auth", "email", "payments"];
441
+
396
442
  for (const feature of features) {
397
- const provider = choices[feature];
398
- if (provider) {
399
- spin.start(`Injecting ${feature}: ${provider}...`);
400
- await injectBlock(feature, provider, targetPath);
401
- await mergePackageJson(targetPath, feature, provider);
402
- spin.stop(`${feature} injected ✓`);
443
+ let provider = choices[feature];
444
+ if (!provider) continue;
445
+
446
+ // map database providers to actual folder names
447
+ if (feature === "database") {
448
+ provider = DATABASE_BLOCK_MAP[provider];
403
449
  }
450
+
451
+ spin.start(`Injecting ${feature}: ${provider}...`);
452
+ await injectBlock(feature, provider, targetPath);
453
+ await mergePackageJson(targetPath, feature, provider);
454
+ spin.stop(`${feature} injected ✓`);
404
455
  }
405
456
 
406
457
  // 8. Write .env.example
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipindays/shipindays",
3
- "version": "0.1.19",
3
+ "version": "0.2.0",
4
4
  "description": "Open source CLI to scaffold a Next.js SaaS. Pick your auth, email, and payments wired up and ready to ship.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -30,9 +30,14 @@
30
30
  "dependencies": {
31
31
  "@clack/prompts": "^0.9.1",
32
32
  "chalk": "^5.3.0",
33
- "fs-extra": "^11.2.0"
33
+ "figlet": "^1.11.0",
34
+ "fs-extra": "^11.2.0",
35
+ "gradient-string": "^3.0.0"
34
36
  },
35
37
  "engines": {
36
38
  "node": ">=18.0.0"
39
+ },
40
+ "devDependencies": {
41
+ "@types/figlet": "^1.7.0"
37
42
  }
38
43
  }
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from "drizzle-kit";
2
+
3
+ export default defineConfig({
4
+ schema: "./src/db/schema.ts",
5
+ out: "./drizzle",
6
+ dialect: "postgresql",
7
+ dbCredentials: {
8
+ url: process.env.DATABASE_URL!,
9
+ },
10
+ });
@@ -0,0 +1,16 @@
1
+ {
2
+ "scripts": {
3
+ "db:generate": "drizzle-kit generate",
4
+ "db:migrate": "drizzle-kit migrate",
5
+ "db:push": "drizzle-kit push",
6
+ "db:studio": "drizzle-kit studio"
7
+ },
8
+ "dependencies": {
9
+ "drizzle-orm": "^1.0.0-beta.19",
10
+ "postgres": "^3.4.4"
11
+ },
12
+ "devDependencies": {
13
+ "dotenv": "^17.2.3",
14
+ "drizzle-kit": "^0.31.8"
15
+ }
16
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "scripts": {
3
+ "db:generate": "prisma generate",
4
+ "db:push": "prisma db push",
5
+ "db:migrate": "prisma migrate dev",
6
+ "db:studio": "prisma studio"
7
+ },
8
+ "dependencies": {
9
+ "@prisma/client": "^5.0.0"
10
+ },
11
+ "devDependencies": {
12
+ "prisma": "^5.0.0"
13
+ }
14
+ }
@@ -0,0 +1,94 @@
1
+ generator client {
2
+ provider = "prisma-client-js"
3
+ }
4
+
5
+ datasource db {
6
+ provider = "postgresql"
7
+ url = env("DATABASE_URL")
8
+ }
9
+
10
+ model User {
11
+ id String @id @default(uuid())
12
+ email String @unique @db.VarChar(255)
13
+ name String? @db.VarChar(255)
14
+ image String?
15
+
16
+ // Auth
17
+ authId String @unique @map("auth_id") @db.VarChar(255)
18
+
19
+ // Tracking
20
+ loginCount Int @default(0) @map("login_count")
21
+ lastLoginAt DateTime? @map("last_login_at")
22
+
23
+ // Timestamps
24
+ createdAt DateTime @default(now()) @map("created_at")
25
+ updatedAt DateTime @updatedAt @map("updated_at")
26
+
27
+ // Relations
28
+ subscription Subscription?
29
+ payments Payment[]
30
+ notifications Notification[]
31
+
32
+ @@index([email], map: "email_idx")
33
+ @@index([authId], map: "auth_id_idx")
34
+ @@map("users")
35
+ }
36
+
37
+ model Subscription {
38
+ id String @id @db.VarChar(255)
39
+ userId String @unique @map("user_id")
40
+
41
+ planId String @map("plan_id") @db.VarChar(255)
42
+ status String @db.VarChar(50)
43
+
44
+ currentPeriodEnd DateTime @map("current_period_end")
45
+ cancelAtPeriodEnd Boolean @default(false) @map("cancel_at_period_end")
46
+
47
+ createdAt DateTime @default(now()) @map("created_at")
48
+ updatedAt DateTime @updatedAt @map("updated_at")
49
+
50
+ // Relations
51
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
52
+
53
+ @@map("subscriptions")
54
+ }
55
+
56
+ model Payment {
57
+ id String @id @db.VarChar(255)
58
+ userId String @map("user_id")
59
+
60
+ amount Int
61
+ currency String @default("USD") @db.VarChar(10)
62
+ status String @db.VarChar(50)
63
+
64
+ createdAt DateTime @default(now()) @map("created_at")
65
+
66
+ // Relations
67
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
68
+
69
+ @@index([userId], map: "payment_user_idx")
70
+ @@map("payments")
71
+ }
72
+
73
+ model WebhookEvent {
74
+ id String @id @db.VarChar(255)
75
+ type String @db.VarChar(100)
76
+
77
+ createdAt DateTime @default(now()) @map("created_at")
78
+
79
+ @@map("webhook_events")
80
+ }
81
+
82
+ model Notification {
83
+ id String @id @default(uuid())
84
+ userId String @map("user_id")
85
+
86
+ type String @db.VarChar(100)
87
+ sentAt DateTime @default(now()) @map("sent_at")
88
+
89
+ // Relations
90
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
91
+
92
+ @@index([userId], map: "notification_user_idx")
93
+ @@map("notifications")
94
+ }
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "dependencies": {
3
- "dodopayments": "^latest"
3
+ "@dodopayments/nextjs": "^0.3.4"
4
4
  }
5
5
  }
@@ -1,14 +0,0 @@
1
- import { DodoPayments } from "dodopayments";
2
-
3
- const dodo = new DodoPayments({
4
- apiKey: process.env.DODO_PAYMENTS_API_KEY,
5
- });
6
-
7
- export async function createCheckout(priceId: string, customerEmail: string) {
8
- return await dodo.checkouts.create({
9
- product_id: priceId,
10
- customer: { email: customerEmail },
11
- billing_address: { country: "US" }, // Or handle dynamically
12
- return_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard`,
13
- });
14
- }
@@ -1,31 +0,0 @@
1
- import Stripe from "stripe";
2
-
3
- export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
4
- apiVersion: "2023-10-16", // Use the latest stable version
5
- typescript: true,
6
- });
7
-
8
- export async function createCheckoutSession({
9
- customerId,
10
- priceId,
11
- mode = "subscription", // "subscription" or "payment" (one-time)
12
- successUrl,
13
- cancelUrl,
14
- }: {
15
- customerId?: string;
16
- priceId: string;
17
- mode?: "subscription" | "payment";
18
- successUrl: string;
19
- cancelUrl: string;
20
- }) {
21
- return await stripe.checkout.sessions.create({
22
- customer: customerId,
23
- line_items: [{ price: priceId, quantity: 1 }],
24
- mode: mode,
25
- success_url: successUrl,
26
- cancel_url: cancelUrl,
27
- subscription_data: mode === "subscription" ? {
28
- metadata: { /* add user info here */ },
29
- } : undefined,
30
- });
31
- }