create-frootkit 1.2.9 → 1.2.11
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/addons/database.d.ts +13 -5
- package/dist/addons/database.d.ts.map +1 -1
- package/dist/addons/database.js +540 -94
- package/dist/addons/database.js.map +1 -1
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +5 -1
- package/dist/scaffold.js.map +1 -1
- package/package.json +1 -1
- package/templates/auth/src/app/api/auth/route.ts +17 -12
- package/templates/auth/src/lib/auth.ts +123 -0
- package/templates/blog/src/app/api/posts/route.ts +10 -34
- package/templates/blog/src/app/blog/[slug]/page.tsx +3 -3
- package/templates/blog/src/app/page.tsx +4 -3
- package/templates/blog/src/data/posts.ts +48 -0
- package/templates/ecommerce/src/app/api/products/route.ts +10 -26
- package/templates/ecommerce/src/app/page.tsx +5 -4
- package/templates/ecommerce/src/app/products/[id]/page.tsx +3 -3
- package/templates/ecommerce/src/data/products.ts +49 -11
package/dist/addons/database.js
CHANGED
|
@@ -1,8 +1,226 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Database & ORM Add-on for create-frootkit
|
|
3
3
|
* Injects Drizzle ORM or Prisma into generated FrootKit projects with
|
|
4
|
-
* template-tailored models and seeding (zero C++ dependencies via @libsql/client).
|
|
4
|
+
* template-tailored models and real production seeding (zero C++ dependencies via @libsql/client).
|
|
5
5
|
*/
|
|
6
|
+
import * as path from 'node:path';
|
|
7
|
+
import * as fs from 'node:fs';
|
|
8
|
+
import { createRequire } from 'node:module';
|
|
9
|
+
const require = createRequire(import.meta.url);
|
|
10
|
+
export const ADMIN_PASSWORD_HASH = 'pbkdf2:sha256:100000:cbbb127ce3a186856aed9805e890e625:284c9b9655dc6cdd14da488ee33dd9296e188514c59e960bb6c7137e9a6f468c';
|
|
11
|
+
/**
|
|
12
|
+
* Returns raw SQL DDL for initial table creation
|
|
13
|
+
*/
|
|
14
|
+
export function getTemplateDdl(templateId) {
|
|
15
|
+
if (templateId === 'ecommerce') {
|
|
16
|
+
return `
|
|
17
|
+
CREATE TABLE IF NOT EXISTS products (
|
|
18
|
+
id TEXT PRIMARY KEY,
|
|
19
|
+
name TEXT NOT NULL,
|
|
20
|
+
price REAL NOT NULL,
|
|
21
|
+
category TEXT NOT NULL,
|
|
22
|
+
rating REAL NOT NULL DEFAULT 5.0,
|
|
23
|
+
reviews_count INTEGER NOT NULL DEFAULT 0,
|
|
24
|
+
image TEXT NOT NULL,
|
|
25
|
+
description TEXT NOT NULL,
|
|
26
|
+
features TEXT NOT NULL,
|
|
27
|
+
in_stock INTEGER NOT NULL DEFAULT 1,
|
|
28
|
+
created_at TEXT NOT NULL
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
CREATE TABLE IF NOT EXISTS orders (
|
|
32
|
+
id TEXT PRIMARY KEY,
|
|
33
|
+
customer_email TEXT NOT NULL,
|
|
34
|
+
total REAL NOT NULL,
|
|
35
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
36
|
+
items_json TEXT NOT NULL,
|
|
37
|
+
created_at TEXT NOT NULL
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
CREATE TABLE IF NOT EXISTS cart_items (
|
|
41
|
+
id TEXT PRIMARY KEY,
|
|
42
|
+
session_id TEXT NOT NULL,
|
|
43
|
+
product_id TEXT NOT NULL REFERENCES products(id),
|
|
44
|
+
quantity INTEGER NOT NULL DEFAULT 1
|
|
45
|
+
);
|
|
46
|
+
`;
|
|
47
|
+
}
|
|
48
|
+
if (templateId === 'blog') {
|
|
49
|
+
return `
|
|
50
|
+
CREATE TABLE IF NOT EXISTS authors (
|
|
51
|
+
id TEXT PRIMARY KEY,
|
|
52
|
+
name TEXT NOT NULL,
|
|
53
|
+
role TEXT NOT NULL,
|
|
54
|
+
avatar TEXT NOT NULL,
|
|
55
|
+
bio TEXT,
|
|
56
|
+
created_at TEXT NOT NULL
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
CREATE TABLE IF NOT EXISTS posts (
|
|
60
|
+
id TEXT PRIMARY KEY,
|
|
61
|
+
slug TEXT NOT NULL UNIQUE,
|
|
62
|
+
title TEXT NOT NULL,
|
|
63
|
+
summary TEXT NOT NULL,
|
|
64
|
+
content TEXT NOT NULL,
|
|
65
|
+
category TEXT NOT NULL DEFAULT 'General',
|
|
66
|
+
author_id TEXT REFERENCES authors(id),
|
|
67
|
+
read_time TEXT NOT NULL DEFAULT '3 min read',
|
|
68
|
+
published_at TEXT NOT NULL
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
CREATE TABLE IF NOT EXISTS tags (
|
|
72
|
+
id TEXT PRIMARY KEY,
|
|
73
|
+
name TEXT NOT NULL UNIQUE,
|
|
74
|
+
slug TEXT NOT NULL UNIQUE
|
|
75
|
+
);
|
|
76
|
+
`;
|
|
77
|
+
}
|
|
78
|
+
if (templateId === 'auth') {
|
|
79
|
+
return `
|
|
80
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
81
|
+
id TEXT PRIMARY KEY,
|
|
82
|
+
email TEXT NOT NULL UNIQUE,
|
|
83
|
+
password_hash TEXT NOT NULL,
|
|
84
|
+
name TEXT NOT NULL,
|
|
85
|
+
role TEXT NOT NULL DEFAULT 'user',
|
|
86
|
+
created_at TEXT NOT NULL
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
90
|
+
id TEXT PRIMARY KEY,
|
|
91
|
+
token TEXT NOT NULL UNIQUE,
|
|
92
|
+
user_id TEXT NOT NULL REFERENCES users(id),
|
|
93
|
+
expires_at TEXT NOT NULL,
|
|
94
|
+
created_at TEXT NOT NULL
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
CREATE TABLE IF NOT EXISTS workspaces (
|
|
98
|
+
id TEXT PRIMARY KEY,
|
|
99
|
+
name TEXT NOT NULL,
|
|
100
|
+
owner_id TEXT NOT NULL REFERENCES users(id),
|
|
101
|
+
plan TEXT NOT NULL DEFAULT 'starter',
|
|
102
|
+
created_at TEXT NOT NULL
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
CREATE TABLE IF NOT EXISTS activity_logs (
|
|
106
|
+
id TEXT PRIMARY KEY,
|
|
107
|
+
user_id TEXT NOT NULL REFERENCES users(id),
|
|
108
|
+
action TEXT NOT NULL,
|
|
109
|
+
ip_address TEXT,
|
|
110
|
+
created_at TEXT NOT NULL
|
|
111
|
+
);
|
|
112
|
+
`;
|
|
113
|
+
}
|
|
114
|
+
// starter / empty
|
|
115
|
+
return `
|
|
116
|
+
CREATE TABLE IF NOT EXISTS metrics (
|
|
117
|
+
id TEXT PRIMARY KEY,
|
|
118
|
+
name TEXT NOT NULL,
|
|
119
|
+
value TEXT NOT NULL,
|
|
120
|
+
unit TEXT NOT NULL,
|
|
121
|
+
recorded_at TEXT NOT NULL
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
CREATE TABLE IF NOT EXISTS system_health (
|
|
125
|
+
id TEXT PRIMARY KEY,
|
|
126
|
+
status TEXT NOT NULL,
|
|
127
|
+
uptime REAL NOT NULL,
|
|
128
|
+
latency_ms REAL NOT NULL,
|
|
129
|
+
checked_at TEXT NOT NULL
|
|
130
|
+
);
|
|
131
|
+
`;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Returns raw SQL statements for seeding actual data (not dummy!)
|
|
135
|
+
*/
|
|
136
|
+
export function getTemplateSeedSql(templateId) {
|
|
137
|
+
if (templateId === 'ecommerce') {
|
|
138
|
+
return `
|
|
139
|
+
INSERT OR IGNORE INTO products (id, name, price, category, rating, reviews_count, image, description, features, in_stock, created_at) VALUES
|
|
140
|
+
('nordic-mechanical-keyboard', 'Nordic Minimal Mechanical Keyboard', 149.0, 'Keyboards', 4.9, 128, 'https://images.unsplash.com/photo-1595225476474-87563907a212?w=800&auto=format&fit=crop&q=80', 'Aircraft-grade CNC anodized aluminum chassis with custom factory-lubed linear switches and sound-dampening silicone base.', '["Hot-swappable switches", "Bluetooth 5.3 & 2.4GHz wireless", "CNC Aluminum Unibody", "PBT Dye-Sub Keycaps"]', 1, datetime('now')),
|
|
141
|
+
('studio-monitor-headphones', 'Studio Master Pro Headphones', 289.0, 'Audio', 4.8, 94, 'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=800&auto=format&fit=crop&q=80', 'Precision 50mm dynamic drivers engineered for ultra-flat frequency response, critical mixing, and executive focus.', '["Planar Magnetic Drivers", "Memory Foam Cushions", "Detachable Braided Cable", "Hard Travel Case"]', 1, datetime('now')),
|
|
142
|
+
('ergo-master-mouse', 'ErgoMaster Wireless Precision Mouse', 89.0, 'Peripherals', 4.7, 210, 'https://images.unsplash.com/photo-1615663245857-ac93bb7c39e7?w=800&auto=format&fit=crop&q=80', 'Sculpted ergonomic contour designed to eliminate wrist fatigue during extended development sessions.', '["26,000 DPI Optical Sensor", "70-day Battery Life", "Infinite Magnetic Scroll Wheel", "Silent Click Switches"]', 1, datetime('now')),
|
|
143
|
+
('walnut-monitor-riser', 'Solid Walnut Monitor Riser Stand', 119.0, 'Desk Setup', 5.0, 62, 'https://images.unsplash.com/photo-1586775490184-b79f0621891f?w=800&auto=format&fit=crop&q=80', 'Handcrafted from single-origin American walnut with cork feet and an integrated aluminum tray for desk accessories.', '["Sustainable American Walnut", "Holds up to 60 lbs", "Integrated Cable Troughs", "Hand-finished Matte Oil"]', 1, datetime('now')),
|
|
144
|
+
('titanium-desk-pad', 'Matte Slate Vegan Leather Desk Mat', 49.0, 'Desk Setup', 4.6, 175, 'https://images.unsplash.com/photo-1581291518857-4e27b48ff24e?w=800&auto=format&fit=crop&q=80', 'Water-resistant, stain-proof premium desk surface that provides effortless mouse tracking and protects your workspace.', '["Water-resistant PU Leather", "Anti-slip Suede Backing", "90cm x 45cm Large Size", "Easy to Clean"]', 1, datetime('now')),
|
|
145
|
+
('usb-c-magnetic-cable', '240W Braided Kevlar Charging Cable', 29.0, 'Accessories', 4.9, 340, 'https://images.unsplash.com/photo-1583863788434-e58a36330cf0?w=800&auto=format&fit=crop&q=80', 'Ultra-durable Kevlar-reinforced charging cable supporting up to 240W Power Delivery and 40Gbps Thunderbolt data transfer.', '["240W Power Delivery", "E-Marker Smart Chip", "Braided Kevlar Weave", "2-meter Length"]', 1, datetime('now'));
|
|
146
|
+
|
|
147
|
+
INSERT OR IGNORE INTO orders (id, customer_email, total, status, items_json, created_at) VALUES
|
|
148
|
+
('ord_demo_101', 'developer@frootkit.dev', 238.0, 'completed', '[{"productId": "nordic-mechanical-keyboard", "quantity": 1}, {"productId": "ergo-master-mouse", "quantity": 1}]', datetime('now'));
|
|
149
|
+
`;
|
|
150
|
+
}
|
|
151
|
+
if (templateId === 'blog') {
|
|
152
|
+
return `
|
|
153
|
+
INSERT OR IGNORE INTO authors (id, name, role, avatar, bio, created_at) VALUES
|
|
154
|
+
('auth_alex', 'Alex Rivera', 'Staff Systems Architect', 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=100&h=100&fit=crop&crop=faces', 'Pioneering sub-millisecond server rendering and edge runtimes.', datetime('now')),
|
|
155
|
+
('auth_elena', 'Elena Rostova', 'Design Systems Lead', 'https://images.unsplash.com/photo-1517841905240-472988babdf9?w=100&h=100&fit=crop&crop=faces', 'Architect of modern design tokens and component systems.', datetime('now')),
|
|
156
|
+
('auth_marcus', 'Marcus Chen', 'Core Runtime Engineer', 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100&h=100&fit=crop&crop=faces', 'Optimizing V8 hidden classes and low-latency networking.', datetime('now'));
|
|
157
|
+
|
|
158
|
+
INSERT OR IGNORE INTO posts (id, slug, title, summary, content, category, author_id, read_time, published_at) VALUES
|
|
159
|
+
('post_welcome', 'welcome-to-frootkit', 'Introducing FrootKit: High-Performance Next.js Alternative', 'Why we combined Preact SSR, native Tailwind CSS v4, and the Froot runtime for maximum developer velocity and sub-millisecond page loads.', '["Modern web frameworks have grown increasingly heavy. Between massive runtime bundles, complex hydration mismatches, and multi-megabyte node_modules dependencies, developers often spend more time debugging tooling than writing features.", "FrootKit takes an unapologetically streamlined approach: sub-millisecond Preact server-side rendering, zero-config Tailwind CSS v4 compilation, and Next.js-style App Router conventions without the heavyweight lock-in.", "By decoupling static site generation from bloated polyfills and leveraging monomorphic V8 dispatch trees, FrootKit achieves sub-millisecond render times while keeping memory usage under 20MB."]', 'Architecture', 'auth_alex', '4 min read', datetime('now')),
|
|
160
|
+
('post_tailwind', 'tailwind-v4-without-configs', 'Zero Configuration Tailwind CSS v4 in Practice', 'Say goodbye to tailwind.config.js and postcss.config.js. Explore how native CSS @theme variables simplify fullstack design tokens.', '["Tailwind CSS v4 revolutionizes how utility styling is processed. Instead of relying on JavaScript configuration files, everything is declared directly inside modern CSS using the @theme directive.", "In FrootKit, the Tailwind CLI is integrated natively into the development lifecycle. When you run frootkit dev, stylesheets are recompiled in single-digit milliseconds without requiring external bundlers."]', 'Styling', 'auth_elena', '3 min read', datetime('now')),
|
|
161
|
+
('post_apis', 'building-sub-millisecond-apis', 'Crafting Dynamic Fullstack APIs with Standard Web Standards', 'A deep dive into Next.js-style route handlers powered by standard Request and Response interfaces on top of Froot.', '["Web standards have matured dramatically. Standard Fetch API primitives like Request, Response, Headers, and URL are now universal across Node.js, Bun, Deno, and browsers.", "FrootKit route handlers in src/app/api/**/route.ts take direct advantage of these standards. You export standard HTTP verb functions like GET, POST, PUT, DELETE, and receive native Request objects with zero middleware baggage."]', 'Backend', 'auth_marcus', '5 min read', datetime('now'));
|
|
162
|
+
|
|
163
|
+
INSERT OR IGNORE INTO tags (id, name, slug) VALUES
|
|
164
|
+
('tag_ssr', 'SSR', 'ssr'),
|
|
165
|
+
('tag_preact', 'Preact', 'preact'),
|
|
166
|
+
('tag_perf', 'Performance', 'performance'),
|
|
167
|
+
('tag_tailwind', 'TailwindCSS', 'tailwindcss');
|
|
168
|
+
`;
|
|
169
|
+
}
|
|
170
|
+
if (templateId === 'auth') {
|
|
171
|
+
return `
|
|
172
|
+
INSERT OR IGNORE INTO users (id, email, password_hash, name, role, created_at) VALUES
|
|
173
|
+
('usr_admin', 'admin@frootkit.dev', '${ADMIN_PASSWORD_HASH}', 'Sarah Connor', 'Owner', datetime('now')),
|
|
174
|
+
('usr_marcus', 'marcus@infra.org', '${ADMIN_PASSWORD_HASH}', 'Marcus Chen', 'Developer', datetime('now')),
|
|
175
|
+
('usr_elena', 'elena@design.io', '${ADMIN_PASSWORD_HASH}', 'Elena Rostova', 'Staff Eng', datetime('now')),
|
|
176
|
+
('usr_alex', 'alex@systems.net', '${ADMIN_PASSWORD_HASH}', 'Alex Rivera', 'Admin', datetime('now'));
|
|
177
|
+
|
|
178
|
+
INSERT OR IGNORE INTO workspaces (id, name, owner_id, plan, created_at) VALUES
|
|
179
|
+
('ws_production', 'Orbit Workspace', 'usr_admin', 'Enterprise', datetime('now'));
|
|
180
|
+
|
|
181
|
+
INSERT OR IGNORE INTO activity_logs (id, user_id, action, ip_address, created_at) VALUES
|
|
182
|
+
('act_1', 'usr_admin', 'Signed in to US-East Production Cluster', '192.168.1.1', datetime('now', '-5 minutes')),
|
|
183
|
+
('act_2', 'usr_marcus', 'Rotated Web Crypto PBKDF2 signing key', '10.0.0.4', datetime('now', '-2 hours')),
|
|
184
|
+
('act_3', 'usr_elena', 'Deployed Linear design system tokens', '172.16.0.2', datetime('now', '-1 day')),
|
|
185
|
+
('act_4', 'usr_alex', 'Provisioned SQLite session replica', '10.0.0.8', datetime('now', '-3 days'));
|
|
186
|
+
`;
|
|
187
|
+
}
|
|
188
|
+
// starter / default
|
|
189
|
+
return `
|
|
190
|
+
INSERT OR IGNORE INTO metrics (id, name, value, unit, recorded_at) VALUES
|
|
191
|
+
('met_1', 'P99 SSR Latency', '0.12', 'ms', datetime('now')),
|
|
192
|
+
('met_2', 'Memory Footprint', '14.2', 'MB', datetime('now')),
|
|
193
|
+
('met_3', 'V8 Cache Hit Ratio', '99.8', '%', datetime('now')),
|
|
194
|
+
('met_4', 'Uptime', '99.99', '%', datetime('now'));
|
|
195
|
+
|
|
196
|
+
INSERT OR IGNORE INTO system_health (id, status, uptime, latency_ms, checked_at) VALUES
|
|
197
|
+
('sh_prod', 'healthy', 99.99, 0.12, datetime('now'));
|
|
198
|
+
`;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Directly seeds the physical SQLite database file at scaffold time using node:sqlite.
|
|
202
|
+
*/
|
|
203
|
+
export function seedPhysicalDatabase(targetDir, dbOption, templateId) {
|
|
204
|
+
try {
|
|
205
|
+
// Dynamic require so older environments don't fail parsing
|
|
206
|
+
const { DatabaseSync } = require('node:sqlite');
|
|
207
|
+
const dbPath = dbOption === 'prisma'
|
|
208
|
+
? path.join(targetDir, 'prisma', 'dev.db')
|
|
209
|
+
: path.join(targetDir, 'sqlite.db');
|
|
210
|
+
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
211
|
+
const db = new DatabaseSync(dbPath);
|
|
212
|
+
const ddl = getTemplateDdl(templateId);
|
|
213
|
+
const seed = getTemplateSeedSql(templateId);
|
|
214
|
+
if (ddl)
|
|
215
|
+
db.exec(ddl);
|
|
216
|
+
if (seed)
|
|
217
|
+
db.exec(seed);
|
|
218
|
+
db.close();
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
// Graceful fallback: node:sqlite not supported or file in use
|
|
222
|
+
}
|
|
223
|
+
}
|
|
6
224
|
export function applyDatabaseAddon(files, dbOption, templateId = 'starter') {
|
|
7
225
|
if (dbOption === 'none')
|
|
8
226
|
return;
|
|
@@ -41,10 +259,10 @@ const client = createClient({
|
|
|
41
259
|
|
|
42
260
|
export const db = drizzle(client, { schema });
|
|
43
261
|
`;
|
|
44
|
-
// Initialize
|
|
262
|
+
// Initialize physical SQLite file placeholder
|
|
45
263
|
files['sqlite.db'] = '';
|
|
46
264
|
if (templateId === 'blog') {
|
|
47
|
-
files['src/db/schema.ts'] = `import { sqliteTable, text
|
|
265
|
+
files['src/db/schema.ts'] = `import { sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
|
48
266
|
|
|
49
267
|
export const authors = sqliteTable('authors', {
|
|
50
268
|
id: text('id').primaryKey(),
|
|
@@ -60,7 +278,7 @@ export const posts = sqliteTable('posts', {
|
|
|
60
278
|
slug: text('slug').notNull().unique(),
|
|
61
279
|
title: text('title').notNull(),
|
|
62
280
|
summary: text('summary').notNull(),
|
|
63
|
-
content: text('content').notNull(), // JSON string
|
|
281
|
+
content: text('content').notNull(), // JSON string array of paragraphs
|
|
64
282
|
category: text('category').notNull().default('General'),
|
|
65
283
|
authorId: text('author_id').references(() => authors.id),
|
|
66
284
|
readTime: text('read_time').notNull().default('3 min read'),
|
|
@@ -80,46 +298,83 @@ export type Author = typeof authors.$inferSelect;
|
|
|
80
298
|
import { authors, posts, tags } from './schema.js';
|
|
81
299
|
|
|
82
300
|
async function seed() {
|
|
83
|
-
console.log('Seeding blog database...');
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
301
|
+
console.log('Seeding blog database with real engineering articles...');
|
|
302
|
+
|
|
303
|
+
await db.insert(authors).values([
|
|
304
|
+
{
|
|
305
|
+
id: 'auth_alex',
|
|
306
|
+
name: 'Alex Rivera',
|
|
307
|
+
role: 'Staff Systems Architect',
|
|
308
|
+
avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=100&h=100&fit=crop&crop=faces',
|
|
309
|
+
bio: 'Pioneering sub-millisecond server rendering and edge runtimes.',
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
id: 'auth_elena',
|
|
313
|
+
name: 'Elena Rostova',
|
|
314
|
+
role: 'Design Systems Lead',
|
|
315
|
+
avatar: 'https://images.unsplash.com/photo-1517841905240-472988babdf9?w=100&h=100&fit=crop&crop=faces',
|
|
316
|
+
bio: 'Architect of modern design tokens and component systems.',
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
id: 'auth_marcus',
|
|
320
|
+
name: 'Marcus Chen',
|
|
321
|
+
role: 'Core Runtime Engineer',
|
|
322
|
+
avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100&h=100&fit=crop&crop=faces',
|
|
323
|
+
bio: 'Optimizing V8 hidden classes and low-latency networking.',
|
|
324
|
+
},
|
|
325
|
+
]).onConflictDoNothing();
|
|
93
326
|
|
|
94
327
|
await db.insert(posts).values([
|
|
95
328
|
{
|
|
96
|
-
id: '
|
|
329
|
+
id: 'post_welcome',
|
|
97
330
|
slug: 'welcome-to-frootkit',
|
|
98
331
|
title: 'Introducing FrootKit: High-Performance Next.js Alternative',
|
|
99
|
-
summary: 'Why we combined Preact SSR, native Tailwind CSS v4, and the Froot runtime for maximum velocity.',
|
|
332
|
+
summary: 'Why we combined Preact SSR, native Tailwind CSS v4, and the Froot runtime for maximum developer velocity and sub-millisecond page loads.',
|
|
100
333
|
content: JSON.stringify([
|
|
101
|
-
'Modern web frameworks have grown increasingly heavy.',
|
|
102
|
-
'FrootKit
|
|
334
|
+
'Modern web frameworks have grown increasingly heavy. Between massive runtime bundles, complex hydration mismatches, and multi-megabyte node_modules dependencies, developers often spend more time debugging tooling than writing features.',
|
|
335
|
+
'FrootKit takes an unapologetically streamlined approach: sub-millisecond Preact server-side rendering, zero-config Tailwind CSS v4 compilation, and Next.js-style App Router conventions without the heavyweight lock-in.',
|
|
336
|
+
'By decoupling static site generation from bloated polyfills and leveraging monomorphic V8 dispatch trees, FrootKit achieves sub-millisecond render times while keeping memory usage under 20MB.',
|
|
103
337
|
]),
|
|
104
338
|
category: 'Architecture',
|
|
105
|
-
authorId,
|
|
339
|
+
authorId: 'auth_alex',
|
|
106
340
|
readTime: '4 min read',
|
|
107
341
|
},
|
|
108
342
|
{
|
|
109
|
-
id: '
|
|
343
|
+
id: 'post_tailwind',
|
|
110
344
|
slug: 'tailwind-v4-without-configs',
|
|
111
345
|
title: 'Zero Configuration Tailwind CSS v4 in Practice',
|
|
112
|
-
summary: 'Explore how native CSS @theme variables simplify fullstack design tokens.',
|
|
346
|
+
summary: 'Say goodbye to tailwind.config.js and postcss.config.js. Explore how native CSS @theme variables simplify fullstack design tokens.',
|
|
113
347
|
content: JSON.stringify([
|
|
114
|
-
'Tailwind CSS v4
|
|
348
|
+
'Tailwind CSS v4 revolutionizes how utility styling is processed. Instead of relying on JavaScript configuration files, everything is declared directly inside modern CSS using the @theme directive.',
|
|
349
|
+
'In FrootKit, the Tailwind CLI is integrated natively into the development lifecycle. When you run frootkit dev, stylesheets are recompiled in single-digit milliseconds without requiring external bundlers.',
|
|
115
350
|
]),
|
|
116
351
|
category: 'Styling',
|
|
117
|
-
authorId,
|
|
352
|
+
authorId: 'auth_elena',
|
|
118
353
|
readTime: '3 min read',
|
|
119
354
|
},
|
|
355
|
+
{
|
|
356
|
+
id: 'post_apis',
|
|
357
|
+
slug: 'building-sub-millisecond-apis',
|
|
358
|
+
title: 'Crafting Dynamic Fullstack APIs with Standard Web Standards',
|
|
359
|
+
summary: 'A deep dive into Next.js-style route handlers powered by standard Request and Response interfaces on top of Froot.',
|
|
360
|
+
content: JSON.stringify([
|
|
361
|
+
'Web standards have matured dramatically. Standard Fetch API primitives like Request, Response, Headers, and URL are now universal across Node.js, Bun, Deno, and browsers.',
|
|
362
|
+
'FrootKit route handlers in src/app/api/**/route.ts take direct advantage of these standards. You export standard HTTP verb functions like GET, POST, PUT, DELETE, and receive native Request objects with zero middleware baggage.',
|
|
363
|
+
]),
|
|
364
|
+
category: 'Backend',
|
|
365
|
+
authorId: 'auth_marcus',
|
|
366
|
+
readTime: '5 min read',
|
|
367
|
+
},
|
|
120
368
|
]).onConflictDoNothing();
|
|
121
369
|
|
|
122
|
-
|
|
370
|
+
await db.insert(tags).values([
|
|
371
|
+
{ id: 'tag_ssr', name: 'SSR', slug: 'ssr' },
|
|
372
|
+
{ id: 'tag_preact', name: 'Preact', slug: 'preact' },
|
|
373
|
+
{ id: 'tag_perf', name: 'Performance', slug: 'performance' },
|
|
374
|
+
{ id: 'tag_tailwind', name: 'TailwindCSS', slug: 'tailwindcss' },
|
|
375
|
+
]).onConflictDoNothing();
|
|
376
|
+
|
|
377
|
+
console.log('✓ Seeding complete! 3 articles and authors populated.');
|
|
123
378
|
}
|
|
124
379
|
|
|
125
380
|
seed().catch(console.error);
|
|
@@ -162,10 +417,10 @@ export type Product = typeof products.$inferSelect;
|
|
|
162
417
|
export type Order = typeof orders.$inferSelect;
|
|
163
418
|
`;
|
|
164
419
|
files['src/db/seed.ts'] = `import { db } from './index.js';
|
|
165
|
-
import { products } from './schema.js';
|
|
420
|
+
import { products, orders } from './schema.js';
|
|
166
421
|
|
|
167
422
|
async function seed() {
|
|
168
|
-
console.log('Seeding e-commerce store database...');
|
|
423
|
+
console.log('Seeding e-commerce store database with all 6 tactile instruments...');
|
|
169
424
|
|
|
170
425
|
await db.insert(products).values([
|
|
171
426
|
{
|
|
@@ -175,9 +430,9 @@ async function seed() {
|
|
|
175
430
|
category: 'Keyboards',
|
|
176
431
|
rating: 4.9,
|
|
177
432
|
reviewsCount: 128,
|
|
178
|
-
image: 'https://images.unsplash.com/photo-
|
|
179
|
-
description: 'Aircraft-grade anodized aluminum
|
|
180
|
-
features: JSON.stringify(['Hot-swappable switches', 'Bluetooth 5.3', 'CNC Aluminum
|
|
433
|
+
image: 'https://images.unsplash.com/photo-1595225476474-87563907a212?w=800&auto=format&fit=crop&q=80',
|
|
434
|
+
description: 'Aircraft-grade CNC anodized aluminum chassis with custom factory-lubed linear switches and sound-dampening silicone base.',
|
|
435
|
+
features: JSON.stringify(['Hot-swappable switches', 'Bluetooth 5.3 & 2.4GHz wireless', 'CNC Aluminum Unibody', 'PBT Dye-Sub Keycaps']),
|
|
181
436
|
inStock: true,
|
|
182
437
|
},
|
|
183
438
|
{
|
|
@@ -187,14 +442,70 @@ async function seed() {
|
|
|
187
442
|
category: 'Audio',
|
|
188
443
|
rating: 4.8,
|
|
189
444
|
reviewsCount: 94,
|
|
190
|
-
image: 'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=
|
|
191
|
-
description: 'Precision 50mm dynamic drivers engineered for ultra-flat frequency response.',
|
|
192
|
-
features: JSON.stringify(['Planar Magnetic
|
|
445
|
+
image: 'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=800&auto=format&fit=crop&q=80',
|
|
446
|
+
description: 'Precision 50mm dynamic drivers engineered for ultra-flat frequency response, critical mixing, and executive focus.',
|
|
447
|
+
features: JSON.stringify(['Planar Magnetic Drivers', 'Memory Foam Cushions', 'Detachable Braided Cable', 'Hard Travel Case']),
|
|
448
|
+
inStock: true,
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
id: 'ergo-master-mouse',
|
|
452
|
+
name: 'ErgoMaster Wireless Precision Mouse',
|
|
453
|
+
price: 89.0,
|
|
454
|
+
category: 'Peripherals',
|
|
455
|
+
rating: 4.7,
|
|
456
|
+
reviewsCount: 210,
|
|
457
|
+
image: 'https://images.unsplash.com/photo-1615663245857-ac93bb7c39e7?w=800&auto=format&fit=crop&q=80',
|
|
458
|
+
description: 'Sculpted ergonomic contour designed to eliminate wrist fatigue during extended development sessions.',
|
|
459
|
+
features: JSON.stringify(['26,000 DPI Optical Sensor', '70-day Battery Life', 'Infinite Magnetic Scroll Wheel', 'Silent Click Switches']),
|
|
460
|
+
inStock: true,
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
id: 'walnut-monitor-riser',
|
|
464
|
+
name: 'Solid Walnut Monitor Riser Stand',
|
|
465
|
+
price: 119.0,
|
|
466
|
+
category: 'Desk Setup',
|
|
467
|
+
rating: 5.0,
|
|
468
|
+
reviewsCount: 62,
|
|
469
|
+
image: 'https://images.unsplash.com/photo-1586775490184-b79f0621891f?w=800&auto=format&fit=crop&q=80',
|
|
470
|
+
description: 'Handcrafted from single-origin American walnut with cork feet and an integrated aluminum tray for desk accessories.',
|
|
471
|
+
features: JSON.stringify(['Sustainable American Walnut', 'Holds up to 60 lbs', 'Integrated Cable Troughs', 'Hand-finished Matte Oil']),
|
|
472
|
+
inStock: true,
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
id: 'titanium-desk-pad',
|
|
476
|
+
name: 'Matte Slate Vegan Leather Desk Mat',
|
|
477
|
+
price: 49.0,
|
|
478
|
+
category: 'Desk Setup',
|
|
479
|
+
rating: 4.6,
|
|
480
|
+
reviewsCount: 175,
|
|
481
|
+
image: 'https://images.unsplash.com/photo-1581291518857-4e27b48ff24e?w=800&auto=format&fit=crop&q=80',
|
|
482
|
+
description: 'Water-resistant, stain-proof premium desk surface that provides effortless mouse tracking and protects your workspace.',
|
|
483
|
+
features: JSON.stringify(['Water-resistant PU Leather', 'Anti-slip Suede Backing', '90cm x 45cm Large Size', 'Easy to Clean']),
|
|
484
|
+
inStock: true,
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
id: 'usb-c-magnetic-cable',
|
|
488
|
+
name: '240W Braided Kevlar Charging Cable',
|
|
489
|
+
price: 29.0,
|
|
490
|
+
category: 'Accessories',
|
|
491
|
+
rating: 4.9,
|
|
492
|
+
reviewsCount: 340,
|
|
493
|
+
image: 'https://images.unsplash.com/photo-1583863788434-e58a36330cf0?w=800&auto=format&fit=crop&q=80',
|
|
494
|
+
description: 'Ultra-durable Kevlar-reinforced charging cable supporting up to 240W Power Delivery and 40Gbps Thunderbolt data transfer.',
|
|
495
|
+
features: JSON.stringify(['240W Power Delivery', 'E-Marker Smart Chip', 'Braided Kevlar Weave', '2-meter Length']),
|
|
193
496
|
inStock: true,
|
|
194
497
|
},
|
|
195
498
|
]).onConflictDoNothing();
|
|
196
499
|
|
|
197
|
-
|
|
500
|
+
await db.insert(orders).values({
|
|
501
|
+
id: 'ord_demo_101',
|
|
502
|
+
customerEmail: 'developer@frootkit.dev',
|
|
503
|
+
total: 238.0,
|
|
504
|
+
status: 'completed',
|
|
505
|
+
itemsJson: JSON.stringify([{ productId: 'nordic-mechanical-keyboard', quantity: 1 }, { productId: 'ergo-master-mouse', quantity: 1 }]),
|
|
506
|
+
}).onConflictDoNothing();
|
|
507
|
+
|
|
508
|
+
console.log('✓ Seeding complete! All 6 products inserted.');
|
|
198
509
|
}
|
|
199
510
|
|
|
200
511
|
seed().catch(console.error);
|
|
@@ -228,34 +539,93 @@ export const workspaces = sqliteTable('workspaces', {
|
|
|
228
539
|
createdAt: text('created_at').notNull().$defaultFn(() => new Date().toISOString()),
|
|
229
540
|
});
|
|
230
541
|
|
|
542
|
+
export const activityLogs = sqliteTable('activity_logs', {
|
|
543
|
+
id: text('id').primaryKey(),
|
|
544
|
+
userId: text('user_id').notNull().references(() => users.id),
|
|
545
|
+
action: text('action').notNull(),
|
|
546
|
+
ipAddress: text('ip_address'),
|
|
547
|
+
createdAt: text('created_at').notNull().$defaultFn(() => new Date().toISOString()),
|
|
548
|
+
});
|
|
549
|
+
|
|
231
550
|
export type User = typeof users.$inferSelect;
|
|
232
551
|
export type Session = typeof sessions.$inferSelect;
|
|
552
|
+
export type Workspace = typeof workspaces.$inferSelect;
|
|
553
|
+
export type ActivityLog = typeof activityLogs.$inferSelect;
|
|
233
554
|
`;
|
|
234
555
|
files['src/db/seed.ts'] = `import { db } from './index.js';
|
|
235
|
-
import { users, workspaces } from './schema.js';
|
|
236
|
-
import { hashPassword } from '../lib/auth.js';
|
|
556
|
+
import { users, workspaces, activityLogs } from './schema.js';
|
|
237
557
|
|
|
238
558
|
async function seed() {
|
|
239
|
-
console.log('Seeding SaaS database with
|
|
559
|
+
console.log('Seeding SaaS database with team members and admin account...');
|
|
240
560
|
|
|
241
|
-
|
|
242
|
-
const
|
|
561
|
+
// Standard pre-hashed PBKDF2 hash of "password123"
|
|
562
|
+
const passwordHash = '${ADMIN_PASSWORD_HASH}';
|
|
243
563
|
|
|
244
|
-
await db.insert(users).values(
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
564
|
+
await db.insert(users).values([
|
|
565
|
+
{
|
|
566
|
+
id: 'usr_admin',
|
|
567
|
+
email: 'admin@frootkit.dev',
|
|
568
|
+
passwordHash,
|
|
569
|
+
name: 'Sarah Connor',
|
|
570
|
+
role: 'Owner',
|
|
571
|
+
},
|
|
572
|
+
{
|
|
573
|
+
id: 'usr_marcus',
|
|
574
|
+
email: 'marcus@infra.org',
|
|
575
|
+
passwordHash,
|
|
576
|
+
name: 'Marcus Chen',
|
|
577
|
+
role: 'Developer',
|
|
578
|
+
},
|
|
579
|
+
{
|
|
580
|
+
id: 'usr_elena',
|
|
581
|
+
email: 'elena@design.io',
|
|
582
|
+
passwordHash,
|
|
583
|
+
name: 'Elena Rostova',
|
|
584
|
+
role: 'Staff Eng',
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
id: 'usr_alex',
|
|
588
|
+
email: 'alex@systems.net',
|
|
589
|
+
passwordHash,
|
|
590
|
+
name: 'Alex Rivera',
|
|
591
|
+
role: 'Admin',
|
|
592
|
+
},
|
|
593
|
+
]).onConflictDoNothing();
|
|
251
594
|
|
|
252
595
|
await db.insert(workspaces).values({
|
|
253
|
-
id: '
|
|
254
|
-
name:
|
|
255
|
-
ownerId:
|
|
256
|
-
plan: '
|
|
596
|
+
id: 'ws_production',
|
|
597
|
+
name: 'Orbit Workspace',
|
|
598
|
+
ownerId: 'usr_admin',
|
|
599
|
+
plan: 'Enterprise',
|
|
257
600
|
}).onConflictDoNothing();
|
|
258
601
|
|
|
602
|
+
await db.insert(activityLogs).values([
|
|
603
|
+
{
|
|
604
|
+
id: 'act_1',
|
|
605
|
+
userId: 'usr_admin',
|
|
606
|
+
action: 'Signed in to US-East Production Cluster',
|
|
607
|
+
ipAddress: '192.168.1.1',
|
|
608
|
+
},
|
|
609
|
+
{
|
|
610
|
+
id: 'act_2',
|
|
611
|
+
userId: 'usr_marcus',
|
|
612
|
+
action: 'Rotated Web Crypto PBKDF2 signing key',
|
|
613
|
+
ipAddress: '10.0.0.4',
|
|
614
|
+
},
|
|
615
|
+
{
|
|
616
|
+
id: 'act_3',
|
|
617
|
+
userId: 'usr_elena',
|
|
618
|
+
action: 'Deployed Linear design system tokens',
|
|
619
|
+
ipAddress: '172.16.0.2',
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
id: 'act_4',
|
|
623
|
+
userId: 'usr_alex',
|
|
624
|
+
action: 'Provisioned SQLite session replica',
|
|
625
|
+
ipAddress: '10.0.0.8',
|
|
626
|
+
},
|
|
627
|
+
]).onConflictDoNothing();
|
|
628
|
+
|
|
259
629
|
console.log('✓ Seeding complete! Demo account created: admin@frootkit.dev / password123');
|
|
260
630
|
}
|
|
261
631
|
|
|
@@ -264,30 +634,46 @@ seed().catch(console.error);
|
|
|
264
634
|
}
|
|
265
635
|
else {
|
|
266
636
|
// Starter / default
|
|
267
|
-
files['src/db/schema.ts'] = `import { sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
|
637
|
+
files['src/db/schema.ts'] = `import { sqliteTable, text, real } from 'drizzle-orm/sqlite-core';
|
|
268
638
|
|
|
269
|
-
export const
|
|
639
|
+
export const metrics = sqliteTable('metrics', {
|
|
270
640
|
id: text('id').primaryKey(),
|
|
271
641
|
name: text('name').notNull(),
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
642
|
+
value: text('value').notNull(),
|
|
643
|
+
unit: text('unit').notNull(),
|
|
644
|
+
recordedAt: text('recorded_at').notNull().$defaultFn(() => new Date().toISOString()),
|
|
275
645
|
});
|
|
276
646
|
|
|
277
|
-
export
|
|
278
|
-
|
|
647
|
+
export const systemHealth = sqliteTable('system_health', {
|
|
648
|
+
id: text('id').primaryKey(),
|
|
649
|
+
status: text('status').notNull(),
|
|
650
|
+
uptime: real('uptime').notNull(),
|
|
651
|
+
latencyMs: real('latency_ms').notNull(),
|
|
652
|
+
checkedAt: text('checked_at').notNull().$defaultFn(() => new Date().toISOString()),
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
export type Metric = typeof metrics.$inferSelect;
|
|
656
|
+
export type SystemHealth = typeof systemHealth.$inferSelect;
|
|
279
657
|
`;
|
|
280
658
|
files['src/db/seed.ts'] = `import { db } from './index.js';
|
|
281
|
-
import {
|
|
659
|
+
import { metrics, systemHealth } from './schema.js';
|
|
282
660
|
|
|
283
661
|
async function seed() {
|
|
284
|
-
console.log('Seeding
|
|
285
|
-
await db.insert(
|
|
286
|
-
id: '
|
|
287
|
-
name: '
|
|
288
|
-
|
|
289
|
-
|
|
662
|
+
console.log('Seeding starter telemetry metrics...');
|
|
663
|
+
await db.insert(metrics).values([
|
|
664
|
+
{ id: 'met_1', name: 'P99 SSR Latency', value: '0.12', unit: 'ms' },
|
|
665
|
+
{ id: 'met_2', name: 'Memory Footprint', value: '14.2', unit: 'MB' },
|
|
666
|
+
{ id: 'met_3', name: 'V8 Cache Hit Ratio', value: '99.8', unit: '%' },
|
|
667
|
+
{ id: 'met_4', name: 'Cluster Uptime', value: '99.99', unit: '%' },
|
|
668
|
+
]).onConflictDoNothing();
|
|
669
|
+
|
|
670
|
+
await db.insert(systemHealth).values({
|
|
671
|
+
id: 'sh_prod',
|
|
672
|
+
status: 'healthy',
|
|
673
|
+
uptime: 99.99,
|
|
674
|
+
latencyMs: 0.12,
|
|
290
675
|
}).onConflictDoNothing();
|
|
676
|
+
|
|
291
677
|
console.log('✓ Seeding complete!');
|
|
292
678
|
}
|
|
293
679
|
|
|
@@ -306,7 +692,6 @@ seed().catch(console.error);
|
|
|
306
692
|
|
|
307
693
|
export const prisma = new PrismaClient();
|
|
308
694
|
`;
|
|
309
|
-
// Initialize the visible Prisma SQLite database file inside prisma/ directory
|
|
310
695
|
files['prisma/dev.db'] = '';
|
|
311
696
|
if (templateId === 'blog') {
|
|
312
697
|
files['prisma/schema.prisma'] = `datasource db {
|
|
@@ -319,7 +704,7 @@ generator client {
|
|
|
319
704
|
}
|
|
320
705
|
|
|
321
706
|
model Author {
|
|
322
|
-
id String @id
|
|
707
|
+
id String @id
|
|
323
708
|
name String
|
|
324
709
|
role String
|
|
325
710
|
avatar String
|
|
@@ -329,7 +714,7 @@ model Author {
|
|
|
329
714
|
}
|
|
330
715
|
|
|
331
716
|
model Post {
|
|
332
|
-
id String @id
|
|
717
|
+
id String @id
|
|
333
718
|
slug String @unique
|
|
334
719
|
title String
|
|
335
720
|
summary String
|
|
@@ -342,7 +727,7 @@ model Post {
|
|
|
342
727
|
}
|
|
343
728
|
|
|
344
729
|
model Tag {
|
|
345
|
-
id String @id
|
|
730
|
+
id String @id
|
|
346
731
|
name String @unique
|
|
347
732
|
slug String @unique
|
|
348
733
|
}
|
|
@@ -351,14 +736,14 @@ model Tag {
|
|
|
351
736
|
|
|
352
737
|
async function seed() {
|
|
353
738
|
console.log('Seeding blog database with Prisma...');
|
|
354
|
-
|
|
739
|
+
await prisma.author.upsert({
|
|
355
740
|
where: { id: 'auth_alex' },
|
|
356
741
|
update: {},
|
|
357
742
|
create: {
|
|
358
743
|
id: 'auth_alex',
|
|
359
744
|
name: 'Alex Rivera',
|
|
360
745
|
role: 'Staff Systems Architect',
|
|
361
|
-
avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=100&h=100&fit=crop',
|
|
746
|
+
avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=100&h=100&fit=crop&crop=faces',
|
|
362
747
|
bio: 'Pioneering sub-millisecond server rendering.',
|
|
363
748
|
},
|
|
364
749
|
});
|
|
@@ -367,11 +752,12 @@ async function seed() {
|
|
|
367
752
|
where: { slug: 'welcome-to-frootkit' },
|
|
368
753
|
update: {},
|
|
369
754
|
create: {
|
|
755
|
+
id: 'post_welcome',
|
|
370
756
|
slug: 'welcome-to-frootkit',
|
|
371
757
|
title: 'Introducing FrootKit: High-Performance Next.js Alternative',
|
|
372
758
|
summary: 'Why we combined Preact SSR and Tailwind CSS v4.',
|
|
373
|
-
content: 'FrootKit provides sub-millisecond server rendering.',
|
|
374
|
-
authorId:
|
|
759
|
+
content: JSON.stringify(['Modern web frameworks have grown increasingly heavy.', 'FrootKit provides sub-millisecond server rendering with zero configuration.']),
|
|
760
|
+
authorId: 'auth_alex',
|
|
375
761
|
category: 'Architecture',
|
|
376
762
|
},
|
|
377
763
|
});
|
|
@@ -419,22 +805,82 @@ model Order {
|
|
|
419
805
|
|
|
420
806
|
async function seed() {
|
|
421
807
|
console.log('Seeding e-commerce store with Prisma...');
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
update: {},
|
|
425
|
-
create: {
|
|
808
|
+
const items = [
|
|
809
|
+
{
|
|
426
810
|
id: 'nordic-mechanical-keyboard',
|
|
427
811
|
name: 'Nordic Minimal Mechanical Keyboard',
|
|
428
812
|
price: 149.0,
|
|
429
813
|
category: 'Keyboards',
|
|
430
814
|
rating: 4.9,
|
|
431
815
|
reviewsCount: 128,
|
|
432
|
-
image: 'https://images.unsplash.com/photo-
|
|
433
|
-
description: 'Aircraft-grade anodized aluminum
|
|
434
|
-
features: '
|
|
435
|
-
inStock: true,
|
|
816
|
+
image: 'https://images.unsplash.com/photo-1595225476474-87563907a212?w=800&auto=format&fit=crop&q=80',
|
|
817
|
+
description: 'Aircraft-grade CNC anodized aluminum chassis with custom factory-lubed linear switches.',
|
|
818
|
+
features: JSON.stringify(['Hot-swappable switches', 'Bluetooth 5.3 & 2.4GHz wireless']),
|
|
436
819
|
},
|
|
437
|
-
|
|
820
|
+
{
|
|
821
|
+
id: 'studio-monitor-headphones',
|
|
822
|
+
name: 'Studio Master Pro Headphones',
|
|
823
|
+
price: 289.0,
|
|
824
|
+
category: 'Audio',
|
|
825
|
+
rating: 4.8,
|
|
826
|
+
reviewsCount: 94,
|
|
827
|
+
image: 'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=800&auto=format&fit=crop&q=80',
|
|
828
|
+
description: 'Precision 50mm dynamic drivers engineered for ultra-flat frequency response.',
|
|
829
|
+
features: JSON.stringify(['Planar Magnetic Drivers', 'Memory Foam Cushions']),
|
|
830
|
+
},
|
|
831
|
+
{
|
|
832
|
+
id: 'ergo-master-mouse',
|
|
833
|
+
name: 'ErgoMaster Wireless Precision Mouse',
|
|
834
|
+
price: 89.0,
|
|
835
|
+
category: 'Peripherals',
|
|
836
|
+
rating: 4.7,
|
|
837
|
+
reviewsCount: 210,
|
|
838
|
+
image: 'https://images.unsplash.com/photo-1615663245857-ac93bb7c39e7?w=800&auto=format&fit=crop&q=80',
|
|
839
|
+
description: 'Sculpted ergonomic contour designed to eliminate wrist fatigue during extended development sessions.',
|
|
840
|
+
features: JSON.stringify(['26,000 DPI Optical Sensor', '70-day Battery Life']),
|
|
841
|
+
},
|
|
842
|
+
{
|
|
843
|
+
id: 'walnut-monitor-riser',
|
|
844
|
+
name: 'Solid Walnut Monitor Riser Stand',
|
|
845
|
+
price: 119.0,
|
|
846
|
+
category: 'Desk Setup',
|
|
847
|
+
rating: 5.0,
|
|
848
|
+
reviewsCount: 62,
|
|
849
|
+
image: 'https://images.unsplash.com/photo-1586775490184-b79f0621891f?w=800&auto=format&fit=crop&q=80',
|
|
850
|
+
description: 'Handcrafted from single-origin American walnut with cork feet and an integrated aluminum tray.',
|
|
851
|
+
features: JSON.stringify(['Sustainable American Walnut', 'Holds up to 60 lbs']),
|
|
852
|
+
},
|
|
853
|
+
{
|
|
854
|
+
id: 'titanium-desk-pad',
|
|
855
|
+
name: 'Matte Slate Vegan Leather Desk Mat',
|
|
856
|
+
price: 49.0,
|
|
857
|
+
category: 'Desk Setup',
|
|
858
|
+
rating: 4.6,
|
|
859
|
+
reviewsCount: 175,
|
|
860
|
+
image: 'https://images.unsplash.com/photo-1581291518857-4e27b48ff24e?w=800&auto=format&fit=crop&q=80',
|
|
861
|
+
description: 'Water-resistant, stain-proof premium desk surface that provides effortless mouse tracking.',
|
|
862
|
+
features: JSON.stringify(['Water-resistant PU Leather', '90cm x 45cm Large Size']),
|
|
863
|
+
},
|
|
864
|
+
{
|
|
865
|
+
id: 'usb-c-magnetic-cable',
|
|
866
|
+
name: '240W Braided Kevlar Charging Cable',
|
|
867
|
+
price: 29.0,
|
|
868
|
+
category: 'Accessories',
|
|
869
|
+
rating: 4.9,
|
|
870
|
+
reviewsCount: 340,
|
|
871
|
+
image: 'https://images.unsplash.com/photo-1583863788434-e58a36330cf0?w=800&auto=format&fit=crop&q=80',
|
|
872
|
+
description: 'Ultra-durable Kevlar-reinforced charging cable supporting up to 240W Power Delivery.',
|
|
873
|
+
features: JSON.stringify(['240W Power Delivery', 'Braided Kevlar Weave']),
|
|
874
|
+
},
|
|
875
|
+
];
|
|
876
|
+
|
|
877
|
+
for (const item of items) {
|
|
878
|
+
await prisma.product.upsert({
|
|
879
|
+
where: { id: item.id },
|
|
880
|
+
update: {},
|
|
881
|
+
create: item,
|
|
882
|
+
});
|
|
883
|
+
}
|
|
438
884
|
console.log('✓ Seeding complete!');
|
|
439
885
|
}
|
|
440
886
|
|
|
@@ -481,11 +927,10 @@ model Workspace {
|
|
|
481
927
|
}
|
|
482
928
|
`;
|
|
483
929
|
files['src/db/seed.ts'] = `import { prisma } from './index.js';
|
|
484
|
-
import { hashPassword } from '../lib/auth.js';
|
|
485
930
|
|
|
486
931
|
async function seed() {
|
|
487
932
|
console.log('Seeding auth database with Prisma...');
|
|
488
|
-
const passwordHash =
|
|
933
|
+
const passwordHash = '${ADMIN_PASSWORD_HASH}';
|
|
489
934
|
|
|
490
935
|
await prisma.user.upsert({
|
|
491
936
|
where: { email: 'admin@frootkit.dev' },
|
|
@@ -494,7 +939,7 @@ async function seed() {
|
|
|
494
939
|
email: 'admin@frootkit.dev',
|
|
495
940
|
passwordHash,
|
|
496
941
|
name: 'Sarah Connor',
|
|
497
|
-
role: '
|
|
942
|
+
role: 'Owner',
|
|
498
943
|
},
|
|
499
944
|
});
|
|
500
945
|
|
|
@@ -514,25 +959,26 @@ generator client {
|
|
|
514
959
|
provider = "prisma-client-js"
|
|
515
960
|
}
|
|
516
961
|
|
|
517
|
-
model
|
|
518
|
-
id
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
962
|
+
model Metric {
|
|
963
|
+
id String @id
|
|
964
|
+
name String
|
|
965
|
+
value String
|
|
966
|
+
unit String
|
|
967
|
+
recordedAt DateTime @default(now())
|
|
523
968
|
}
|
|
524
969
|
`;
|
|
525
970
|
files['src/db/seed.ts'] = `import { prisma } from './index.js';
|
|
526
971
|
|
|
527
972
|
async function seed() {
|
|
528
973
|
console.log('Seeding starter database with Prisma...');
|
|
529
|
-
await prisma.
|
|
530
|
-
where: {
|
|
974
|
+
await prisma.metric.upsert({
|
|
975
|
+
where: { id: 'met_1' },
|
|
531
976
|
update: {},
|
|
532
977
|
create: {
|
|
533
|
-
|
|
534
|
-
name: '
|
|
535
|
-
|
|
978
|
+
id: 'met_1',
|
|
979
|
+
name: 'P99 SSR Latency',
|
|
980
|
+
value: '0.12',
|
|
981
|
+
unit: 'ms',
|
|
536
982
|
},
|
|
537
983
|
});
|
|
538
984
|
console.log('✓ Seeding complete!');
|