create-frootkit 1.1.4 → 1.2.1
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/README.md +50 -16
- package/dist/addons/database.d.ts +3 -2
- package/dist/addons/database.d.ts.map +1 -1
- package/dist/addons/database.js +472 -13
- package/dist/addons/database.js.map +1 -1
- package/dist/index.js +93 -18
- package/dist/index.js.map +1 -1
- package/dist/scaffold.d.ts +1 -0
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +3 -3
- package/dist/scaffold.js.map +1 -1
- package/dist/template.d.ts +4 -5
- package/dist/template.d.ts.map +1 -1
- package/dist/template.js +6 -404
- package/dist/template.js.map +1 -1
- package/dist/templates/auth.d.ts +3 -0
- package/dist/templates/auth.d.ts.map +1 -0
- package/dist/templates/auth.js +487 -0
- package/dist/templates/auth.js.map +1 -0
- package/dist/templates/blog.d.ts +3 -0
- package/dist/templates/blog.d.ts.map +1 -0
- package/dist/templates/blog.js +369 -0
- package/dist/templates/blog.js.map +1 -0
- package/dist/templates/common.d.ts +5 -0
- package/dist/templates/common.d.ts.map +1 -0
- package/dist/templates/common.js +116 -0
- package/dist/templates/common.js.map +1 -0
- package/dist/templates/ecommerce.d.ts +3 -0
- package/dist/templates/ecommerce.d.ts.map +1 -0
- package/dist/templates/ecommerce.js +474 -0
- package/dist/templates/ecommerce.js.map +1 -0
- package/dist/templates/empty.d.ts +3 -0
- package/dist/templates/empty.d.ts.map +1 -0
- package/dist/templates/empty.js +85 -0
- package/dist/templates/empty.js.map +1 -0
- package/dist/templates/index.d.ts +23 -0
- package/dist/templates/index.d.ts.map +1 -0
- package/dist/templates/index.js +100 -0
- package/dist/templates/index.js.map +1 -0
- package/dist/templates/starter.d.ts +3 -0
- package/dist/templates/starter.d.ts.map +1 -0
- package/dist/templates/starter.js +308 -0
- package/dist/templates/starter.js.map +1 -0
- package/dist/templates/types.d.ts +13 -0
- package/dist/templates/types.d.ts.map +1 -0
- package/dist/templates/types.js +5 -0
- package/dist/templates/types.js.map +1 -0
- package/package.json +2 -1
- package/templates/auth/README.md +11 -0
- package/templates/auth/package.json +24 -0
- package/templates/auth/src/app/api/auth/route.ts +148 -0
- package/templates/auth/src/app/dashboard/layout.tsx +34 -0
- package/templates/auth/src/app/dashboard/page.tsx +72 -0
- package/templates/auth/src/app/layout.tsx +31 -0
- package/templates/auth/src/app/login/page.tsx +18 -0
- package/templates/auth/src/app/page.tsx +60 -0
- package/templates/auth/src/app/signup/page.tsx +18 -0
- package/templates/auth/src/components/AuthForm.tsx +77 -0
- package/templates/auth/src/components/DashboardNav.tsx +51 -0
- package/templates/auth/src/components/MetricCard.tsx +26 -0
- package/templates/auth/src/components/Navbar.tsx +29 -0
- package/templates/auth/src/lib/auth.ts +145 -0
- package/templates/auth/src/styles.css +19 -0
- package/templates/auth/tsconfig.json +13 -0
- package/templates/blog/README.md +11 -0
- package/templates/blog/package.json +24 -0
- package/templates/blog/src/app/api/posts/route.ts +36 -0
- package/templates/blog/src/app/blog/[slug]/page.tsx +74 -0
- package/templates/blog/src/app/layout.tsx +31 -0
- package/templates/blog/src/app/page.tsx +44 -0
- package/templates/blog/src/components/Footer.tsx +16 -0
- package/templates/blog/src/components/Navbar.tsx +31 -0
- package/templates/blog/src/components/Newsletter.tsx +32 -0
- package/templates/blog/src/components/PostCard.tsx +47 -0
- package/templates/blog/src/data/posts.ts +75 -0
- package/templates/blog/src/styles.css +19 -0
- package/templates/blog/tsconfig.json +13 -0
- package/templates/ecommerce/README.md +11 -0
- package/templates/ecommerce/package.json +24 -0
- package/templates/ecommerce/src/app/api/checkout/route.ts +19 -0
- package/templates/ecommerce/src/app/api/products/route.ts +28 -0
- package/templates/ecommerce/src/app/cart/page.tsx +22 -0
- package/templates/ecommerce/src/app/layout.tsx +31 -0
- package/templates/ecommerce/src/app/page.tsx +59 -0
- package/templates/ecommerce/src/app/products/[id]/page.tsx +88 -0
- package/templates/ecommerce/src/components/CartDrawer.tsx +64 -0
- package/templates/ecommerce/src/components/Footer.tsx +16 -0
- package/templates/ecommerce/src/components/Navbar.tsx +32 -0
- package/templates/ecommerce/src/components/ProductCard.tsx +52 -0
- package/templates/ecommerce/src/data/products.ts +87 -0
- package/templates/ecommerce/src/styles.css +19 -0
- package/templates/ecommerce/tsconfig.json +13 -0
- package/templates/empty/README.md +10 -0
- package/templates/empty/package.json +24 -0
- package/templates/empty/src/app/layout.tsx +29 -0
- package/templates/empty/src/app/page.tsx +38 -0
- package/templates/empty/src/styles.css +19 -0
- package/templates/empty/tsconfig.json +13 -0
- package/templates/starter/README.md +22 -0
- package/templates/starter/package.json +24 -0
- package/templates/starter/src/app/api/health/route.ts +23 -0
- package/templates/starter/src/app/layout.tsx +31 -0
- package/templates/starter/src/app/page.tsx +122 -0
- package/templates/starter/src/components/FeatureCard.tsx +25 -0
- package/templates/starter/src/components/Footer.tsx +16 -0
- package/templates/starter/src/components/Header.tsx +45 -0
- package/templates/starter/src/components/TerminalPreview.tsx +56 -0
- package/templates/starter/src/styles.css +19 -0
- package/templates/starter/tsconfig.json +13 -0
package/README.md
CHANGED
|
@@ -1,41 +1,75 @@
|
|
|
1
1
|
# create-frootkit
|
|
2
2
|
|
|
3
|
-
> Official dedicated scaffolder CLI for [FrootKit](https://github.com/orbitnorth/frootkit) — The Zero-Overhead Next.js Alternative.
|
|
3
|
+
> Official dedicated scaffolder CLI for [FrootKit](https://github.com/orbitnorth/frootkit) — The Zero-Overhead Next.js Alternative for Preact.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/create-frootkit)
|
|
6
6
|
[](https://github.com/orbitnorth/create-frootkit/blob/main/LICENSE)
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
-
## ⚡
|
|
10
|
+
## ⚡ Quick Start
|
|
11
11
|
|
|
12
|
-
Scaffold a production-ready FrootKit application
|
|
12
|
+
Scaffold a production-ready FrootKit application interactively or via CLI flags:
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
15
|
# Using npm
|
|
16
|
-
npm create frootkit@latest
|
|
16
|
+
npm create frootkit@latest
|
|
17
17
|
|
|
18
|
-
# Using pnpm
|
|
19
|
-
pnpm create frootkit
|
|
18
|
+
# Using pnpm (recommended)
|
|
19
|
+
pnpm create frootkit
|
|
20
20
|
|
|
21
21
|
# Using bun
|
|
22
|
-
bun create frootkit
|
|
22
|
+
bun create frootkit
|
|
23
|
+
|
|
24
|
+
# Using npx
|
|
25
|
+
npx create-frootkit my-app --template blog --db drizzle
|
|
23
26
|
```
|
|
24
27
|
|
|
25
|
-
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 🎨 Official Templates
|
|
31
|
+
|
|
32
|
+
`create-frootkit` comes with 5 production-ready templates organized in modular directories with polished component architecture and responsive Tailwind CSS v4 styling:
|
|
26
33
|
|
|
27
|
-
|
|
|
34
|
+
| Template | Flag | Description |
|
|
28
35
|
| :--- | :--- | :--- |
|
|
29
|
-
|
|
|
30
|
-
| `--
|
|
31
|
-
| `--
|
|
32
|
-
|
|
|
33
|
-
|
|
|
34
|
-
|
|
36
|
+
| **Starter** *(Recommended)* | `--template starter` | Full developer showcase with hero section, feature cards, and `/api/health` route. |
|
|
37
|
+
| **Blog & Content** | `--template blog` | High-performance engineering journal with dynamic `[slug]` pages, post cards, author bios, and `/api/posts` search API. |
|
|
38
|
+
| **E-Commerce Store** | `--template ecommerce` | Digital storefront with product catalog, dynamic `[id]` detail pages, shopping cart view, and `/api/checkout` API. |
|
|
39
|
+
| **SaaS & Authentication** | `--template auth` | Complete SaaS platform with Web Crypto PBKDF2 password hashing, secure session tokens, login/signup forms, and protected `/dashboard` layout. |
|
|
40
|
+
| **Minimal Canvas** | `--template empty` | Clean, blank slate with Preact SSR and Tailwind CSS for custom architectures. |
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## 🗄️ Database & ORM Integrations
|
|
45
|
+
|
|
46
|
+
Choose your database layer with zero native compilation dependencies (`@libsql/client`):
|
|
47
|
+
|
|
48
|
+
- **Drizzle ORM (`--db drizzle`):** Tailored TypeScript schema (`posts`, `products`, `users`, `sessions`), migrations, Drizzle Studio, and seeding scripts (`pnpm db:seed`).
|
|
49
|
+
- **Prisma (`--db prisma`):** Declarative `prisma/schema.prisma` with matching models, client generation, and seeding.
|
|
50
|
+
- **None (`--db none`):** In-memory mock stores and zero setup required.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## ⚙️ CLI Options & Flags
|
|
55
|
+
|
|
56
|
+
| Flag | Shorthand | Description | Default |
|
|
57
|
+
| :--- | :--- | :--- | :--- |
|
|
58
|
+
| `--template <name>` | `-t <name>` | Template (`starter`, `blog`, `ecommerce`, `auth`, `empty`) | `starter` |
|
|
59
|
+
| `--pm <manager>` | | Package manager (`pnpm`, `npm`, `bun`, `yarn`) | Prompt |
|
|
60
|
+
| `--db <orm>` | | Database ORM (`none`, `drizzle`, `prisma`) | `none` |
|
|
61
|
+
| `--linter <tool>` | | Code quality tool (`none`, `biome`, `eslint`) | `none` |
|
|
62
|
+
| `--docker` / `--no-docker` | | Multi-stage Docker container setup | `false` |
|
|
63
|
+
| `--agent` / `--no-agent` | | AI coding guidelines (`AGENT.md` & `.cursorrules`) | `true` |
|
|
64
|
+
| `--git` / `--no-git` | | Initialize Git repository | `true` |
|
|
65
|
+
| `--install` / `--no-install` | | Automatically install dependencies | Prompt |
|
|
66
|
+
| `--schema` | | Output machine-readable JSON schema for AI agents | |
|
|
67
|
+
| `-y`, `--yes` | | Accept all defaults non-interactively | `false` |
|
|
68
|
+
| `-v`, `--version` | | Display version | |
|
|
69
|
+
| `-h`, `--help` | | Display help documentation | |
|
|
35
70
|
|
|
36
71
|
---
|
|
37
72
|
|
|
38
73
|
## 📄 License
|
|
39
74
|
|
|
40
75
|
MIT © [OrbitNorth LLC](https://github.com/orbitnorth)
|
|
41
|
-
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Database & ORM Add-on for create-frootkit
|
|
3
|
-
* Injects Drizzle ORM or Prisma into generated FrootKit projects with
|
|
3
|
+
* Injects Drizzle ORM or Prisma into generated FrootKit projects with
|
|
4
|
+
* template-tailored models and seeding (zero C++ dependencies via @libsql/client).
|
|
4
5
|
*/
|
|
5
6
|
export type DatabaseOption = 'none' | 'drizzle' | 'prisma';
|
|
6
7
|
export interface PackageJsonLike {
|
|
@@ -9,5 +10,5 @@ export interface PackageJsonLike {
|
|
|
9
10
|
devDependencies?: Record<string, string>;
|
|
10
11
|
[key: string]: unknown;
|
|
11
12
|
}
|
|
12
|
-
export declare function applyDatabaseAddon(files: Record<string, string>, dbOption: DatabaseOption): void;
|
|
13
|
+
export declare function applyDatabaseAddon(files: Record<string, string>, dbOption: DatabaseOption, templateId?: string): void;
|
|
13
14
|
//# sourceMappingURL=database.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/addons/database.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/addons/database.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE3D,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC7B,QAAQ,EAAE,cAAc,EACxB,UAAU,GAAE,MAAkB,GAC7B,IAAI,CAkiBN"}
|
package/dist/addons/database.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Database & ORM Add-on for create-frootkit
|
|
3
|
-
* Injects Drizzle ORM or Prisma into generated FrootKit projects with
|
|
3
|
+
* Injects Drizzle ORM or Prisma into generated FrootKit projects with
|
|
4
|
+
* template-tailored models and seeding (zero C++ dependencies via @libsql/client).
|
|
4
5
|
*/
|
|
5
|
-
export function applyDatabaseAddon(files, dbOption) {
|
|
6
|
+
export function applyDatabaseAddon(files, dbOption, templateId = 'starter') {
|
|
6
7
|
if (dbOption === 'none')
|
|
7
8
|
return;
|
|
8
9
|
const pkgJsonStr = files['package.json'];
|
|
@@ -18,6 +19,7 @@ export function applyDatabaseAddon(files, dbOption) {
|
|
|
18
19
|
pkg.devDependencies['drizzle-kit'] = '^0.30.0';
|
|
19
20
|
pkg.scripts['db:push'] = 'drizzle-kit push';
|
|
20
21
|
pkg.scripts['db:studio'] = 'drizzle-kit studio';
|
|
22
|
+
pkg.scripts['db:seed'] = 'tsx src/db/seed.ts';
|
|
21
23
|
files['drizzle.config.ts'] = `import { defineConfig } from 'drizzle-kit';
|
|
22
24
|
|
|
23
25
|
export default defineConfig({
|
|
@@ -29,28 +31,267 @@ export default defineConfig({
|
|
|
29
31
|
},
|
|
30
32
|
});
|
|
31
33
|
`;
|
|
32
|
-
files['src/db/
|
|
34
|
+
files['src/db/index.ts'] = `import { createClient } from '@libsql/client';
|
|
35
|
+
import { drizzle } from 'drizzle-orm/libsql';
|
|
36
|
+
import * as schema from './schema.js';
|
|
37
|
+
|
|
38
|
+
const client = createClient({
|
|
39
|
+
url: process.env.DATABASE_URL || 'file:sqlite.db',
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export const db = drizzle(client, { schema });
|
|
43
|
+
`;
|
|
44
|
+
if (templateId === 'blog') {
|
|
45
|
+
files['src/db/schema.ts'] = `import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
|
|
46
|
+
|
|
47
|
+
export const authors = sqliteTable('authors', {
|
|
48
|
+
id: text('id').primaryKey(),
|
|
49
|
+
name: text('name').notNull(),
|
|
50
|
+
role: text('role').notNull(),
|
|
51
|
+
avatar: text('avatar').notNull(),
|
|
52
|
+
bio: text('bio'),
|
|
53
|
+
createdAt: text('created_at').notNull().$defaultFn(() => new Date().toISOString()),
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
export const posts = sqliteTable('posts', {
|
|
57
|
+
id: text('id').primaryKey(),
|
|
58
|
+
slug: text('slug').notNull().unique(),
|
|
59
|
+
title: text('title').notNull(),
|
|
60
|
+
summary: text('summary').notNull(),
|
|
61
|
+
content: text('content').notNull(), // JSON string or markdown
|
|
62
|
+
category: text('category').notNull().default('General'),
|
|
63
|
+
authorId: text('author_id').references(() => authors.id),
|
|
64
|
+
readTime: text('read_time').notNull().default('3 min read'),
|
|
65
|
+
publishedAt: text('published_at').notNull().$defaultFn(() => new Date().toISOString()),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
export const tags = sqliteTable('tags', {
|
|
69
|
+
id: text('id').primaryKey(),
|
|
70
|
+
name: text('name').notNull().unique(),
|
|
71
|
+
slug: text('slug').notNull().unique(),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
export type Post = typeof posts.$inferSelect;
|
|
75
|
+
export type Author = typeof authors.$inferSelect;
|
|
76
|
+
`;
|
|
77
|
+
files['src/db/seed.ts'] = `import { db } from './index.js';
|
|
78
|
+
import { authors, posts, tags } from './schema.js';
|
|
79
|
+
|
|
80
|
+
async function seed() {
|
|
81
|
+
console.log('Seeding blog database...');
|
|
82
|
+
|
|
83
|
+
const authorId = 'auth_alex';
|
|
84
|
+
await db.insert(authors).values({
|
|
85
|
+
id: authorId,
|
|
86
|
+
name: 'Alex Rivera',
|
|
87
|
+
role: 'Staff Systems Architect',
|
|
88
|
+
avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=100&h=100&fit=crop',
|
|
89
|
+
bio: 'Pioneering sub-millisecond server rendering and edge runtimes.',
|
|
90
|
+
}).onConflictDoNothing();
|
|
91
|
+
|
|
92
|
+
await db.insert(posts).values([
|
|
93
|
+
{
|
|
94
|
+
id: 'post_1',
|
|
95
|
+
slug: 'welcome-to-frootkit',
|
|
96
|
+
title: 'Introducing FrootKit: High-Performance Next.js Alternative',
|
|
97
|
+
summary: 'Why we combined Preact SSR, native Tailwind CSS v4, and the Froot runtime for maximum velocity.',
|
|
98
|
+
content: JSON.stringify([
|
|
99
|
+
'Modern web frameworks have grown increasingly heavy.',
|
|
100
|
+
'FrootKit provides sub-millisecond server rendering with zero configuration.',
|
|
101
|
+
]),
|
|
102
|
+
category: 'Architecture',
|
|
103
|
+
authorId,
|
|
104
|
+
readTime: '4 min read',
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: 'post_2',
|
|
108
|
+
slug: 'tailwind-v4-without-configs',
|
|
109
|
+
title: 'Zero Configuration Tailwind CSS v4 in Practice',
|
|
110
|
+
summary: 'Explore how native CSS @theme variables simplify fullstack design tokens.',
|
|
111
|
+
content: JSON.stringify([
|
|
112
|
+
'Tailwind CSS v4 redefines utility styling directly inside CSS without js config files.',
|
|
113
|
+
]),
|
|
114
|
+
category: 'Styling',
|
|
115
|
+
authorId,
|
|
116
|
+
readTime: '3 min read',
|
|
117
|
+
},
|
|
118
|
+
]).onConflictDoNothing();
|
|
119
|
+
|
|
120
|
+
console.log('✓ Seeding complete!');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
seed().catch(console.error);
|
|
124
|
+
`;
|
|
125
|
+
}
|
|
126
|
+
else if (templateId === 'ecommerce') {
|
|
127
|
+
files['src/db/schema.ts'] = `import { sqliteTable, text, real, integer } from 'drizzle-orm/sqlite-core';
|
|
128
|
+
|
|
129
|
+
export const products = sqliteTable('products', {
|
|
130
|
+
id: text('id').primaryKey(),
|
|
131
|
+
name: text('name').notNull(),
|
|
132
|
+
price: real('price').notNull(),
|
|
133
|
+
category: text('category').notNull(),
|
|
134
|
+
rating: real('rating').notNull().default(5.0),
|
|
135
|
+
reviewsCount: integer('reviews_count').notNull().default(0),
|
|
136
|
+
image: text('image').notNull(),
|
|
137
|
+
description: text('description').notNull(),
|
|
138
|
+
features: text('features').notNull(), // JSON string array
|
|
139
|
+
inStock: integer('in_stock', { mode: 'boolean' }).notNull().default(true),
|
|
140
|
+
createdAt: text('created_at').notNull().$defaultFn(() => new Date().toISOString()),
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
export const orders = sqliteTable('orders', {
|
|
144
|
+
id: text('id').primaryKey(),
|
|
145
|
+
customerEmail: text('customer_email').notNull(),
|
|
146
|
+
total: real('total').notNull(),
|
|
147
|
+
status: text('status').notNull().default('pending'),
|
|
148
|
+
itemsJson: text('items_json').notNull(),
|
|
149
|
+
createdAt: text('created_at').notNull().$defaultFn(() => new Date().toISOString()),
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
export const cartItems = sqliteTable('cart_items', {
|
|
153
|
+
id: text('id').primaryKey(),
|
|
154
|
+
sessionId: text('session_id').notNull(),
|
|
155
|
+
productId: text('product_id').notNull().references(() => products.id),
|
|
156
|
+
quantity: integer('quantity').notNull().default(1),
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
export type Product = typeof products.$inferSelect;
|
|
160
|
+
export type Order = typeof orders.$inferSelect;
|
|
161
|
+
`;
|
|
162
|
+
files['src/db/seed.ts'] = `import { db } from './index.js';
|
|
163
|
+
import { products } from './schema.js';
|
|
164
|
+
|
|
165
|
+
async function seed() {
|
|
166
|
+
console.log('Seeding e-commerce store database...');
|
|
167
|
+
|
|
168
|
+
await db.insert(products).values([
|
|
169
|
+
{
|
|
170
|
+
id: 'nordic-mechanical-keyboard',
|
|
171
|
+
name: 'Nordic Minimal Mechanical Keyboard',
|
|
172
|
+
price: 149.0,
|
|
173
|
+
category: 'Keyboards',
|
|
174
|
+
rating: 4.9,
|
|
175
|
+
reviewsCount: 128,
|
|
176
|
+
image: 'https://images.unsplash.com/photo-1587829741301-dc798b83add3?w=600&h=450&fit=crop',
|
|
177
|
+
description: 'Aircraft-grade anodized aluminum body with custom hot-swappable linear switches.',
|
|
178
|
+
features: JSON.stringify(['Hot-swappable switches', 'Bluetooth 5.3', 'CNC Aluminum Chassis']),
|
|
179
|
+
inStock: true,
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
id: 'studio-monitor-headphones',
|
|
183
|
+
name: 'Studio Master Pro Headphones',
|
|
184
|
+
price: 289.0,
|
|
185
|
+
category: 'Audio',
|
|
186
|
+
rating: 4.8,
|
|
187
|
+
reviewsCount: 94,
|
|
188
|
+
image: 'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=600&h=450&fit=crop',
|
|
189
|
+
description: 'Precision 50mm dynamic drivers engineered for ultra-flat frequency response.',
|
|
190
|
+
features: JSON.stringify(['Planar Magnetic Sound', 'Memory Foam Cushions', 'Detachable Silver Cable']),
|
|
191
|
+
inStock: true,
|
|
192
|
+
},
|
|
193
|
+
]).onConflictDoNothing();
|
|
194
|
+
|
|
195
|
+
console.log('✓ Seeding complete!');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
seed().catch(console.error);
|
|
199
|
+
`;
|
|
200
|
+
}
|
|
201
|
+
else if (templateId === 'auth') {
|
|
202
|
+
files['src/db/schema.ts'] = `import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
|
|
203
|
+
|
|
204
|
+
export const users = sqliteTable('users', {
|
|
205
|
+
id: text('id').primaryKey(),
|
|
206
|
+
email: text('email').notNull().unique(),
|
|
207
|
+
passwordHash: text('password_hash').notNull(),
|
|
208
|
+
name: text('name').notNull(),
|
|
209
|
+
role: text('role').notNull().default('user'),
|
|
210
|
+
createdAt: text('created_at').notNull().$defaultFn(() => new Date().toISOString()),
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
export const sessions = sqliteTable('sessions', {
|
|
214
|
+
id: text('id').primaryKey(),
|
|
215
|
+
token: text('token').notNull().unique(),
|
|
216
|
+
userId: text('user_id').notNull().references(() => users.id),
|
|
217
|
+
expiresAt: text('expires_at').notNull(),
|
|
218
|
+
createdAt: text('created_at').notNull().$defaultFn(() => new Date().toISOString()),
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
export const workspaces = sqliteTable('workspaces', {
|
|
222
|
+
id: text('id').primaryKey(),
|
|
223
|
+
name: text('name').notNull(),
|
|
224
|
+
ownerId: text('owner_id').notNull().references(() => users.id),
|
|
225
|
+
plan: text('plan').notNull().default('starter'),
|
|
226
|
+
createdAt: text('created_at').notNull().$defaultFn(() => new Date().toISOString()),
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
export type User = typeof users.$inferSelect;
|
|
230
|
+
export type Session = typeof sessions.$inferSelect;
|
|
231
|
+
`;
|
|
232
|
+
files['src/db/seed.ts'] = `import { db } from './index.js';
|
|
233
|
+
import { users, workspaces } from './schema.js';
|
|
234
|
+
import { hashPassword } from '../lib/auth.js';
|
|
235
|
+
|
|
236
|
+
async function seed() {
|
|
237
|
+
console.log('Seeding SaaS database with demo user...');
|
|
238
|
+
|
|
239
|
+
const passwordHash = await hashPassword('password123');
|
|
240
|
+
const adminId = 'usr_admin_demo';
|
|
241
|
+
|
|
242
|
+
await db.insert(users).values({
|
|
243
|
+
id: adminId,
|
|
244
|
+
email: 'admin@frootkit.dev',
|
|
245
|
+
passwordHash,
|
|
246
|
+
name: 'Sarah Connor',
|
|
247
|
+
role: 'admin',
|
|
248
|
+
}).onConflictDoNothing();
|
|
249
|
+
|
|
250
|
+
await db.insert(workspaces).values({
|
|
251
|
+
id: 'ws_demo_01',
|
|
252
|
+
name: "Sarah's Production Workspace",
|
|
253
|
+
ownerId: adminId,
|
|
254
|
+
plan: 'enterprise',
|
|
255
|
+
}).onConflictDoNothing();
|
|
256
|
+
|
|
257
|
+
console.log('✓ Seeding complete! Demo account created: admin@frootkit.dev / password123');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
seed().catch(console.error);
|
|
261
|
+
`;
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
// Starter / default
|
|
265
|
+
files['src/db/schema.ts'] = `import { sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
|
33
266
|
|
|
34
267
|
export const users = sqliteTable('users', {
|
|
35
268
|
id: text('id').primaryKey(),
|
|
36
269
|
name: text('name').notNull(),
|
|
37
270
|
email: text('email').notNull().unique(),
|
|
271
|
+
role: text('role').notNull().default('user'),
|
|
38
272
|
createdAt: text('created_at').notNull().$defaultFn(() => new Date().toISOString()),
|
|
39
273
|
});
|
|
40
274
|
|
|
41
275
|
export type User = typeof users.$inferSelect;
|
|
42
276
|
export type NewUser = typeof users.$inferInsert;
|
|
43
277
|
`;
|
|
44
|
-
|
|
45
|
-
import {
|
|
46
|
-
import * as schema from './schema.js';
|
|
278
|
+
files['src/db/seed.ts'] = `import { db } from './index.js';
|
|
279
|
+
import { users } from './schema.js';
|
|
47
280
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
281
|
+
async function seed() {
|
|
282
|
+
console.log('Seeding database...');
|
|
283
|
+
await db.insert(users).values({
|
|
284
|
+
id: 'usr_starter_1',
|
|
285
|
+
name: 'Developer',
|
|
286
|
+
email: 'developer@frootkit.dev',
|
|
287
|
+
role: 'admin',
|
|
288
|
+
}).onConflictDoNothing();
|
|
289
|
+
console.log('✓ Seeding complete!');
|
|
290
|
+
}
|
|
51
291
|
|
|
52
|
-
|
|
292
|
+
seed().catch(console.error);
|
|
53
293
|
`;
|
|
294
|
+
}
|
|
54
295
|
}
|
|
55
296
|
else if (dbOption === 'prisma') {
|
|
56
297
|
pkg.dependencies['@prisma/client'] = '^6.4.0';
|
|
@@ -58,7 +299,209 @@ export const db = drizzle(client, { schema });
|
|
|
58
299
|
pkg.scripts['db:generate'] = 'prisma generate';
|
|
59
300
|
pkg.scripts['db:push'] = 'prisma db push';
|
|
60
301
|
pkg.scripts['db:studio'] = 'prisma studio';
|
|
61
|
-
|
|
302
|
+
pkg.scripts['db:seed'] = 'tsx src/db/seed.ts';
|
|
303
|
+
files['src/db/index.ts'] = `import { PrismaClient } from '@prisma/client';
|
|
304
|
+
|
|
305
|
+
export const prisma = new PrismaClient();
|
|
306
|
+
`;
|
|
307
|
+
if (templateId === 'blog') {
|
|
308
|
+
files['prisma/schema.prisma'] = `datasource db {
|
|
309
|
+
provider = "sqlite"
|
|
310
|
+
url = "file:./dev.db"
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
generator client {
|
|
314
|
+
provider = "prisma-client-js"
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
model Author {
|
|
318
|
+
id String @id @default(uuid())
|
|
319
|
+
name String
|
|
320
|
+
role String
|
|
321
|
+
avatar String
|
|
322
|
+
bio String?
|
|
323
|
+
posts Post[]
|
|
324
|
+
createdAt DateTime @default(now())
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
model Post {
|
|
328
|
+
id String @id @default(uuid())
|
|
329
|
+
slug String @unique
|
|
330
|
+
title String
|
|
331
|
+
summary String
|
|
332
|
+
content String
|
|
333
|
+
category String @default("General")
|
|
334
|
+
authorId String?
|
|
335
|
+
author Author? @relation(fields: [authorId], references: [id])
|
|
336
|
+
readTime String @default("3 min read")
|
|
337
|
+
publishedAt DateTime @default(now())
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
model Tag {
|
|
341
|
+
id String @id @default(uuid())
|
|
342
|
+
name String @unique
|
|
343
|
+
slug String @unique
|
|
344
|
+
}
|
|
345
|
+
`;
|
|
346
|
+
files['src/db/seed.ts'] = `import { prisma } from './index.js';
|
|
347
|
+
|
|
348
|
+
async function seed() {
|
|
349
|
+
console.log('Seeding blog database with Prisma...');
|
|
350
|
+
const author = await prisma.author.upsert({
|
|
351
|
+
where: { id: 'auth_alex' },
|
|
352
|
+
update: {},
|
|
353
|
+
create: {
|
|
354
|
+
id: 'auth_alex',
|
|
355
|
+
name: 'Alex Rivera',
|
|
356
|
+
role: 'Staff Systems Architect',
|
|
357
|
+
avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=100&h=100&fit=crop',
|
|
358
|
+
bio: 'Pioneering sub-millisecond server rendering.',
|
|
359
|
+
},
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
await prisma.post.upsert({
|
|
363
|
+
where: { slug: 'welcome-to-frootkit' },
|
|
364
|
+
update: {},
|
|
365
|
+
create: {
|
|
366
|
+
slug: 'welcome-to-frootkit',
|
|
367
|
+
title: 'Introducing FrootKit: High-Performance Next.js Alternative',
|
|
368
|
+
summary: 'Why we combined Preact SSR and Tailwind CSS v4.',
|
|
369
|
+
content: 'FrootKit provides sub-millisecond server rendering.',
|
|
370
|
+
authorId: author.id,
|
|
371
|
+
category: 'Architecture',
|
|
372
|
+
},
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
console.log('✓ Seeding complete!');
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
seed().catch(console.error);
|
|
379
|
+
`;
|
|
380
|
+
}
|
|
381
|
+
else if (templateId === 'ecommerce') {
|
|
382
|
+
files['prisma/schema.prisma'] = `datasource db {
|
|
383
|
+
provider = "sqlite"
|
|
384
|
+
url = "file:./dev.db"
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
generator client {
|
|
388
|
+
provider = "prisma-client-js"
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
model Product {
|
|
392
|
+
id String @id
|
|
393
|
+
name String
|
|
394
|
+
price Float
|
|
395
|
+
category String
|
|
396
|
+
rating Float @default(5.0)
|
|
397
|
+
reviewsCount Int @default(0)
|
|
398
|
+
image String
|
|
399
|
+
description String
|
|
400
|
+
features String
|
|
401
|
+
inStock Boolean @default(true)
|
|
402
|
+
createdAt DateTime @default(now())
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
model Order {
|
|
406
|
+
id String @id @default(uuid())
|
|
407
|
+
customerEmail String
|
|
408
|
+
total Float
|
|
409
|
+
status String @default("pending")
|
|
410
|
+
itemsJson String
|
|
411
|
+
createdAt DateTime @default(now())
|
|
412
|
+
}
|
|
413
|
+
`;
|
|
414
|
+
files['src/db/seed.ts'] = `import { prisma } from './index.js';
|
|
415
|
+
|
|
416
|
+
async function seed() {
|
|
417
|
+
console.log('Seeding e-commerce store with Prisma...');
|
|
418
|
+
await prisma.product.upsert({
|
|
419
|
+
where: { id: 'nordic-mechanical-keyboard' },
|
|
420
|
+
update: {},
|
|
421
|
+
create: {
|
|
422
|
+
id: 'nordic-mechanical-keyboard',
|
|
423
|
+
name: 'Nordic Minimal Mechanical Keyboard',
|
|
424
|
+
price: 149.0,
|
|
425
|
+
category: 'Keyboards',
|
|
426
|
+
rating: 4.9,
|
|
427
|
+
reviewsCount: 128,
|
|
428
|
+
image: 'https://images.unsplash.com/photo-1587829741301-dc798b83add3?w=600&h=450&fit=crop',
|
|
429
|
+
description: 'Aircraft-grade anodized aluminum body with custom hot-swappable linear switches.',
|
|
430
|
+
features: '["Hot-swappable switches", "Bluetooth 5.3", "CNC Aluminum Chassis"]',
|
|
431
|
+
inStock: true,
|
|
432
|
+
},
|
|
433
|
+
});
|
|
434
|
+
console.log('✓ Seeding complete!');
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
seed().catch(console.error);
|
|
438
|
+
`;
|
|
439
|
+
}
|
|
440
|
+
else if (templateId === 'auth') {
|
|
441
|
+
files['prisma/schema.prisma'] = `datasource db {
|
|
442
|
+
provider = "sqlite"
|
|
443
|
+
url = "file:./dev.db"
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
generator client {
|
|
447
|
+
provider = "prisma-client-js"
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
model User {
|
|
451
|
+
id String @id @default(uuid())
|
|
452
|
+
email String @unique
|
|
453
|
+
passwordHash String
|
|
454
|
+
name String
|
|
455
|
+
role String @default("user")
|
|
456
|
+
sessions Session[]
|
|
457
|
+
workspaces Workspace[]
|
|
458
|
+
createdAt DateTime @default(now())
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
model Session {
|
|
462
|
+
id String @id @default(uuid())
|
|
463
|
+
token String @unique
|
|
464
|
+
userId String
|
|
465
|
+
user User @relation(fields: [userId], references: [id])
|
|
466
|
+
expiresAt DateTime
|
|
467
|
+
createdAt DateTime @default(now())
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
model Workspace {
|
|
471
|
+
id String @id @default(uuid())
|
|
472
|
+
name String
|
|
473
|
+
ownerId String
|
|
474
|
+
owner User @relation(fields: [ownerId], references: [id])
|
|
475
|
+
plan String @default("starter")
|
|
476
|
+
createdAt DateTime @default(now())
|
|
477
|
+
}
|
|
478
|
+
`;
|
|
479
|
+
files['src/db/seed.ts'] = `import { prisma } from './index.js';
|
|
480
|
+
import { hashPassword } from '../lib/auth.js';
|
|
481
|
+
|
|
482
|
+
async function seed() {
|
|
483
|
+
console.log('Seeding auth database with Prisma...');
|
|
484
|
+
const passwordHash = await hashPassword('password123');
|
|
485
|
+
|
|
486
|
+
await prisma.user.upsert({
|
|
487
|
+
where: { email: 'admin@frootkit.dev' },
|
|
488
|
+
update: {},
|
|
489
|
+
create: {
|
|
490
|
+
email: 'admin@frootkit.dev',
|
|
491
|
+
passwordHash,
|
|
492
|
+
name: 'Sarah Connor',
|
|
493
|
+
role: 'admin',
|
|
494
|
+
},
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
console.log('✓ Seeding complete! Demo credentials: admin@frootkit.dev / password123');
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
seed().catch(console.error);
|
|
501
|
+
`;
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
files['prisma/schema.prisma'] = `datasource db {
|
|
62
505
|
provider = "sqlite"
|
|
63
506
|
url = "file:./dev.db"
|
|
64
507
|
}
|
|
@@ -71,13 +514,29 @@ model User {
|
|
|
71
514
|
id String @id @default(uuid())
|
|
72
515
|
email String @unique
|
|
73
516
|
name String
|
|
517
|
+
role String @default("user")
|
|
74
518
|
createdAt DateTime @default(now())
|
|
75
519
|
}
|
|
76
520
|
`;
|
|
77
|
-
|
|
521
|
+
files['src/db/seed.ts'] = `import { prisma } from './index.js';
|
|
78
522
|
|
|
79
|
-
|
|
523
|
+
async function seed() {
|
|
524
|
+
console.log('Seeding starter database with Prisma...');
|
|
525
|
+
await prisma.user.upsert({
|
|
526
|
+
where: { email: 'developer@frootkit.dev' },
|
|
527
|
+
update: {},
|
|
528
|
+
create: {
|
|
529
|
+
email: 'developer@frootkit.dev',
|
|
530
|
+
name: 'Developer',
|
|
531
|
+
role: 'admin',
|
|
532
|
+
},
|
|
533
|
+
});
|
|
534
|
+
console.log('✓ Seeding complete!');
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
seed().catch(console.error);
|
|
80
538
|
`;
|
|
539
|
+
}
|
|
81
540
|
}
|
|
82
541
|
files['package.json'] = JSON.stringify(pkg, null, 2) + '\n';
|
|
83
542
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../src/addons/database.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../src/addons/database.ts"],"names":[],"mappings":"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"}
|