bake canonical build harness (deterministic /ship for forks) [music]

This commit is contained in:
vibeasite-bot
2026-08-22 06:21:17 +01:00
parent a35c110aca
commit 9c74008baa
77 changed files with 6596 additions and 143 deletions
+21
View File
@@ -0,0 +1,21 @@
// ---------------------------------------------------------------------------
// AnyDB Client Stub — auto-included in every AUTO_DB React project
// ---------------------------------------------------------------------------
// When AnyDB is connected, this file is replaced by the real client
// generated by anydbService.getClientCode(). Until then, this stub
// returns empty results so components don't crash.
// ---------------------------------------------------------------------------
const STUB_COLLECTION = {
find: async (_filter?: any, _opts?: any) => ({ data: [], total: 0, page: 1, limit: 50, pages: 0 }),
insertOne: async (doc: any) => ({ ...doc, _id: `stub_${Date.now()}` }),
updateOne: async (_filter: any, _update: any) => ({ modifiedCount: 0 }),
deleteOne: async (_filter: any) => ({ deletedCount: 0 }),
aggregate: async (_pipeline: any[]) => [],
};
const anydb = {
collection: async (_name: string) => STUB_COLLECTION,
};
export default anydb;
+121
View File
@@ -0,0 +1,121 @@
// ---------------------------------------------------------------------------
// OddSockets Client — realtime pub/sub + presence for generated sites
// ---------------------------------------------------------------------------
// The realtime leg of the tyga backend bundle (alongside lib/anydb.ts data +
// lib/tygarun.ts SaaS). Channels, presence and history via OddSockets.
//
// This stub ships UNCONFIGURED. When the project connects OddSockets, the
// builder replaces ODDSOCKETS_CONFIG with real values (like lib/tygarun.ts)
// and this file is swapped for the real client (oddsocketsService.getClientCode,
// which imports `oddsockets-js`). Until then isConfigured() is false and the
// hooks fall back to a LOCAL in-memory demo bus so the UI is alive out of the
// box. The demo bus is clearly ephemeral — a broken real connect must surface
// as an error, never be masked by the demo state.
// ---------------------------------------------------------------------------
const ODDSOCKETS_CONFIG = {
// Always connect the manager LB — it assigns the worker and the SDK follows
// the handoff. NEVER hardcode a worker URL.
connectUrl: "__ODDSOCKETS_CONNECT_URL__", // e.g. https://connect.oddsockets.tyga.network
// Per-site key (Authorization: Bearer ak_live_...). Isolation boundary = the
// key owner (account). Provisioned per site by our reseller rail on connect.
apiKey: "__ODDSOCKETS_KEY__",
};
export function isConfigured(): boolean {
return !ODDSOCKETS_CONFIG.apiKey.startsWith("__");
}
export const CONNECT_URL = ODDSOCKETS_CONFIG.connectUrl;
export interface PresenceMember {
id: string;
state?: Record<string, any>;
}
export interface ChannelMessage {
id: string;
data: any;
at: string;
}
export interface OddSocketsChannel {
subscribe(cb: (msg: ChannelMessage) => void, opts?: Record<string, any>): () => void;
publish(data: any, opts?: Record<string, any>): Promise<void>;
getHistory(opts?: { count?: number; start?: string; end?: string }): Promise<ChannelMessage[]>;
getPresence(): Promise<PresenceMember[]>;
updateState(state: Record<string, any>): Promise<void>;
unsubscribe(): void;
}
export interface OddSocketsClient {
channel(name: string): OddSocketsChannel;
connected: boolean;
}
// ── Local in-memory demo bus (used until OddSockets is connected) ────────────
// Simulates pub/sub + presence within the tab so realtime UIs render live in
// demo mode. Not shared across clients — purely local, seed/demo only.
type Listener = (msg: ChannelMessage) => void;
function createDemoBus(): OddSocketsClient {
const listeners: Record<string, Set<Listener>> = {};
const history: Record<string, ChannelMessage[]> = {};
const presence: Record<string, Record<string, PresenceMember>> = {};
const selfId = `demo_${Math.random().toString(36).slice(2, 8)}`;
function channel(name: string): OddSocketsChannel {
listeners[name] = listeners[name] || new Set();
history[name] = history[name] || [];
presence[name] = presence[name] || {};
return {
subscribe(cb, _opts) {
listeners[name].add(cb);
// announce demo self-presence
presence[name][selfId] = { id: selfId, state: {} };
return () => listeners[name].delete(cb);
},
async publish(data, _opts) {
const msg: ChannelMessage = {
id: `m_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
data,
at: new Date().toISOString(),
};
history[name].push(msg);
if (history[name].length > 200) history[name].shift();
listeners[name].forEach((cb) => cb(msg));
},
async getHistory(opts = {}) {
const all = history[name] || [];
return opts.count ? all.slice(-opts.count) : all.slice();
},
async getPresence() {
return Object.values(presence[name] || {});
},
async updateState(state) {
presence[name][selfId] = { id: selfId, state };
},
unsubscribe() {
listeners[name].clear();
delete presence[name][selfId];
},
};
}
return { channel, connected: false };
}
// Singleton client — demo bus in the stub; the real client (oddsockets-js)
// replaces this whole file on connect via oddsocketsService.getClientCode.
const client: OddSocketsClient = createDemoBus();
export default client;
// Usage:
// import oddsockets, { isConfigured } from '@/lib/oddsockets';
// const ch = oddsockets.channel('room:lobby');
// const off = ch.subscribe((m) => console.log(m.data));
// await ch.publish({ text: 'hello' });
// await ch.updateState({ typing: true });
// const members = await ch.getPresence();
+89
View File
@@ -0,0 +1,89 @@
// ---------------------------------------------------------------------------
// safe-icons.ts — Verified lucide-react icon re-exports
// ---------------------------------------------------------------------------
// PRE-INSTALLED in every React project. CodeWriterAgent imports from here
// instead of directly from lucide-react, preventing non-existent icon errors.
// import { ArrowRight, Star, Menu, X } from "@/lib/safe-icons"
// ---------------------------------------------------------------------------
export {
// Navigation & UI
Menu, X, ChevronDown, ChevronUp, ChevronLeft, ChevronRight,
ChevronsUpDown, ArrowRight, ArrowLeft, ArrowUp, ArrowDown,
ArrowUpRight, ExternalLink, MoreHorizontal, MoreVertical,
Search, Filter, SlidersHorizontal, Loader2, RefreshCw,
// Actions
Plus, Minus, Check, CheckCircle, CheckCircle2, XCircle,
Copy, Download, Upload, Share2, Send, Save, Trash2,
Edit, PenLine, Pencil, Eye, EyeOff, LogIn, LogOut,
Settings, Settings2, Sliders,
// Communication
Mail, Phone, MessageCircle, MessageSquare, Bell, BellRing,
Inbox, Send as SendIcon, AtSign,
// Media
Image, Camera, Video, Music, Play, Pause, Volume2, Mic,
PlayCircle, Film,
// Social
Github, Twitter, Linkedin, Instagram, Facebook, Youtube,
Globe, Globe2, Rss,
// Business
Briefcase, Building2, CreditCard, DollarSign, ShoppingCart,
ShoppingBag, Package, Receipt, Wallet, PiggyBank,
TrendingUp, TrendingDown, BarChart3, PieChart, LineChart,
// People
User, Users, UserPlus, UserCheck, UserCircle, Contact,
Heart, ThumbsUp, Star, Award, Trophy, Medal, Crown,
// Content
FileText, File, Folder, BookOpen, Book, Newspaper,
Bookmark, Tag, Hash, Quote, Type, AlignLeft,
// Tech
Code, Code2, Terminal, Database, Server, Cloud, Wifi,
Smartphone, Monitor, Laptop, Globe as GlobeIcon,
Cpu, Zap, Bolt, Plug, Key, Lock, Unlock, Shield,
ShieldCheck, Bug, Rocket, Wand2,
// Layout
LayoutDashboard, LayoutGrid, Grid, Layers, Sidebar,
PanelLeft, PanelRight, Columns2, Rows2, Table,
// Time & Calendar
Clock, Calendar, CalendarDays, Timer, Hourglass,
Sunrise, Sunset, Moon, Sun,
// Location
MapPin, Map, Compass, Navigation, Home, Building2 as BuildingIcon,
Flag, Route,
// Health & Science
Heart as HeartIcon, HeartPulse, Activity, Stethoscope,
Pill, Syringe, Thermometer, Microscope, Atom, Brain,
// Nature
Leaf, TreePine, Trees, Flower, Sprout, Mountain,
Waves, Wind, Snowflake, Sun as SunIcon, Cloud as CloudIcon,
// Food & Drink
Utensils, Coffee, Pizza, Wine, CookingPot, Cherry,
Apple, Cake, IceCream,
// Transport
Car, Plane, Train, Bus, Bike, Truck, Ship, Sailboat,
// Education
GraduationCap, School, Library, BookOpen as BookOpenIcon,
Lightbulb, Puzzle, Target,
// Misc
Sparkles, Flame, Gift, Ticket, Scissors, Wrench,
Hammer, Paintbrush, Palette, Gem, Diamond, Ribbon,
PartyPopper, Smile, Frown, AlertTriangle, Info,
HelpCircle, CircleAlert, AlertCircle,
} from "lucide-react";
+115
View File
@@ -0,0 +1,115 @@
// ---------------------------------------------------------------------------
// TygaRun Client — ready-made SaaS APIs for generated sites
// ---------------------------------------------------------------------------
// Universal client for ALL sdk.tyga.run modules: auth, scheduling (bookings/
// eventtypes/availability), crm, ecommerce, billing, notifications, webhooks...
// Where Lovable gives you raw tables, tyga.run gives you ready-made APIs.
//
// This stub ships unconfigured. When the project connects tyga.run, the
// builder replaces TYGARUN_CONFIG with real values (like lib/anydb.ts).
// Until then isConfigured() is false and hooks fall back to seed data.
// ---------------------------------------------------------------------------
const TYGARUN_CONFIG = {
baseUrl: "__TYGARUN_SDK_URL__", // e.g. https://sdk.tyga.run
// Browser-safe `tc_pub_` publishable key (Supabase-anon model): origin-locked,
// sandbox-pinned, module-restricted server-side. Safe to ship in public code.
publishableKey: "__TYGARUN_ANON_KEY__",
sandbox: "__TYGARUN_SANDBOX__", // "true" | "false"
};
export function isConfigured(): boolean {
return !TYGARUN_CONFIG.publishableKey.startsWith("__");
}
const TOKEN_KEY = "tygarun_token";
export function getToken(): string | null {
try { return localStorage.getItem(TOKEN_KEY); } catch { return null; }
}
export function setToken(token: string | null) {
try {
if (token) localStorage.setItem(TOKEN_KEY, token);
else localStorage.removeItem(TOKEN_KEY);
} catch { /* SSR / private mode */ }
}
export interface TygaRunError extends Error {
statusCode?: number;
body?: any;
}
/**
* Universal call to any tyga.run module endpoint.
* tygarun("scheduling", "/bookings", { query: { page: 1 } })
* tygarun("auth", "/signin", { method: "POST", body: { email, password } })
* tygarun("crm", "/contacts", { method: "POST", body: contact })
*/
export async function tygarun<T = any>(
module: string,
endpoint = "/",
opts: {
method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
body?: any;
query?: Record<string, any>;
headers?: Record<string, string>;
} = {}
): Promise<T> {
if (!isConfigured()) {
const err = new Error("tyga.run not connected") as TygaRunError;
err.statusCode = 0;
throw err;
}
const { method = "GET", body, query = {}, headers = {} } = opts;
const path = endpoint.startsWith("/") ? endpoint : `/${endpoint}`;
const url = new URL(`${TYGARUN_CONFIG.baseUrl.replace(/\/+$/, "")}/${module}${path}`);
Object.entries(query).forEach(([k, v]) => {
if (v !== null && v !== undefined) url.searchParams.append(k, String(v));
});
const token = getToken();
const res = await fetch(url.toString(), {
method,
headers: {
"Content-Type": "application/json",
Accept: "application/json",
// Publishable key ALWAYS authenticates the app (never the user).
Authorization: `Bearer ${TYGARUN_CONFIG.publishableKey}`,
// End-user identity (their JWT from auth.signin) rides separately.
...(token ? { "X-User-Token": token } : {}),
"X-Sandbox-Mode": TYGARUN_CONFIG.sandbox,
...headers,
},
body: body && method !== "GET" && method !== "DELETE" ? JSON.stringify(body) : undefined,
});
const text = await res.text();
let json: any = {};
try { json = text ? JSON.parse(text) : {}; } catch { /* non-JSON error body */ }
if (!res.ok) {
const err = new Error(json.error || json.message || `tyga.run ${module} error ${res.status}`) as TygaRunError;
err.statusCode = res.status;
err.body = json;
throw err;
}
return json as T;
}
// ── Auth convenience (JWT stored for user-scoped calls) ─────────────────────
export const auth = {
async signup(data: { email: string; password: string; firstName?: string; lastName?: string; [k: string]: any }) {
const res = await tygarun<any>("auth", "/signup", { method: "POST", body: data });
if (res.token) setToken(res.token);
return res;
},
async signin(email: string, password: string, rememberMe = false) {
const res = await tygarun<any>("auth", "/signin", { method: "POST", body: { email, password, rememberMe } });
if (res.token) setToken(res.token);
return res;
},
signout() { setToken(null); },
isSignedIn(): boolean { return Boolean(getToken()); },
};
export default tygarun;
+6 -3
View File
@@ -1,3 +1,6 @@
export function cn(...inputs: Array<string | number | null | undefined | false>) {
return inputs.filter(Boolean).join(" ");
}
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}