@vaiftech/cli 1.7.4 → 1.7.6
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 +3 -1
- package/dist/{chunk-CMWHBLLZ.js → chunk-T7GOLIY4.js} +134 -51
- package/dist/cli.cjs +160 -77
- package/dist/cli.js +2 -2
- package/dist/index.cjs +138 -55
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@vaiftech/cli)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-
Command-line tools for [VAIF Studio](https://vaif.studio) (v1.7.
|
|
6
|
+
Command-line tools for [VAIF Studio](https://vaif.studio) (v1.7.6) — scaffold full projects from templates with feature selection, browser-based authentication, manage schemas, deploy functions, generate TypeScript types, and more.
|
|
7
7
|
|
|
8
8
|
## Installation
|
|
9
9
|
|
|
@@ -286,6 +286,8 @@ The CLI automatically loads `.env` files from the current directory. You can set
|
|
|
286
286
|
| `VAIF_API_URL` | API base URL (default: `https://api.vaif.studio`) |
|
|
287
287
|
| `VAIF_TOKEN` | API token (alternative to `vaif login`) |
|
|
288
288
|
| `VAIF_PROJECT_ID` | Default project ID (also read from `vaif.config.json`) |
|
|
289
|
+
| `VITE_VAIF_PROJECT_ID` | Project ID for Vite-based templates (React SPA) |
|
|
290
|
+
| `NEXT_PUBLIC_VAIF_PROJECT_ID` | Project ID for Next.js templates |
|
|
289
291
|
|
|
290
292
|
Project ID resolution order: `--project-id` flag > `vaif.config.json` > `VAIF_PROJECT_ID` env var > login session.
|
|
291
293
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
Could not connect to VAIF API. Please try again later.`)),process.exit(1));let
|
|
3
|
-
Could not connect to VAIF API.`)),console.log(
|
|
4
|
-
The authentication session expired. Please try again.`)),process.exit(1));continue}let
|
|
5
|
-
Timed out waiting for browser authentication.`)),console.log(
|
|
6
|
-
No email provided. Login cancelled.`)),process.exit(1));let n=await
|
|
7
|
-
No password provided. Login cancelled.`)),process.exit(1));let e=
|
|
8
|
-
${
|
|
9
|
-
Could not connect to VAIF API. Please try again later.`)),process.exit(1);}}async function
|
|
10
|
-
Your session has expired. Please login again.`)),process.exit(1)),t.succeed("Authenticated"),console.log(""),console.log(
|
|
1
|
+
import b from'fs';import k from'path';import Z from'dotenv';import ee from'os';import {exec}from'child_process';import L from'ora';import l from'chalk';import $ from'readline';import ue from'pg';import pe from'prettier';Z.config();async function q(a){let t=k.resolve(a);if(!b.existsSync(t))return null;try{let n=b.readFileSync(t,"utf-8"),e=JSON.parse(n);return e.database?.url&&(e.database.url=B(e.database.url)),e.api?.apiKey&&(e.api.apiKey=B(e.api.apiKey)),e}catch{throw new Error(`Failed to parse config file: ${a}`)}}function B(a){return a.replace(/\$\{([^}]+)\}/g,(t,n)=>process.env[n]||t)}var C=k.join(ee.homedir(),".vaif"),_=k.join(C,"auth.json"),R=process.env.VAIF_API_URL||"https://api.vaif.studio";function ae(){b.existsSync(C)||b.mkdirSync(C,{recursive:true});}function G(a){ae(),b.writeFileSync(_,JSON.stringify(a,null,2),"utf-8"),b.chmodSync(_,384);}function V(){if(!b.existsSync(_))return null;try{let a=b.readFileSync(_,"utf-8");return JSON.parse(a)}catch{return null}}function ne(a){let t=$.createInterface({input:process.stdin,output:process.stdout});return new Promise(n=>{t.question(a,e=>{t.close(),n(e);});})}function ie(a){return new Promise(t=>{let n=$.createInterface({input:process.stdin,output:process.stdout});process.stdin;let r=process.stdout.write.bind(process.stdout),i=false;process.stdout.write=((...c)=>i?true:r(...c)),n.question(a,c=>{i=false,process.stdout.write=r,console.log(""),n.close(),t(c);}),i=true;})}function oe(a){let t=process.platform,n;t==="darwin"?n=`open "${a}"`:t==="win32"?n=`start "" "${a}"`:n=`xdg-open "${a}"`,exec(n,e=>{});}function re(a){return new Promise(t=>setTimeout(t,a))}async function se(a){try{let t=await fetch(`${R}/auth/me`,{headers:{Authorization:`Bearer ${a}`}});if(t.ok){let n=await t.json();return {valid:!0,email:n.user?.email||n.email}}return {valid:!1}}catch{return {valid:false}}}async function le(a){let t=L();t.start("Setting up authentication...");let n,e;try{let u=await fetch(`${R}/auth/cli/authorize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({})});u.ok||(t.fail("Failed to initiate authentication"),console.log(l.red(`
|
|
2
|
+
Could not connect to VAIF API. Please try again later.`)),process.exit(1));let o=await u.json();n=o.code,e=o.url;}catch{t.fail("Failed to connect to VAIF API"),console.log(l.red(`
|
|
3
|
+
Could not connect to VAIF API.`)),console.log(l.gray("Check your internet connection or try: vaif login --email")),process.exit(1);}t.stop(),console.log(l.cyan(" Opening browser for authentication...")),console.log(""),console.log(l.gray(" If the browser doesn't open, visit this URL:")),console.log(l.white(` ${e}`)),console.log(""),oe(e),t.start("Waiting for browser authentication...");let r=12e4,i=2e3,c=Date.now();for(;Date.now()-c<r;){await re(i);try{let u=await fetch(`${R}/auth/cli/token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({code:n})});if(!u.ok){let s=await u.json();(s.error==="ExpiredCode"||s.error==="InvalidCode")&&(t.fail("Authentication expired"),console.log(l.red(`
|
|
4
|
+
The authentication session expired. Please try again.`)),process.exit(1));continue}let o=await u.json();if(o.ok&&o.accessToken){let s={token:o.accessToken,email:o.user?.email,projectId:a,expiresAt:new Date(Date.now()+o.expiresIn*1e3).toISOString()};G(s),t.succeed("Logged in successfully"),console.log(""),o.user?.email&&console.log(l.green(` Authenticated as: ${o.user.email}`)),console.log(l.gray(` Config saved to: ${_}`)),console.log("");return}}catch{}}t.fail("Authentication timed out"),console.log(l.red(`
|
|
5
|
+
Timed out waiting for browser authentication.`)),console.log(l.gray("Try again or use: vaif login --email")),process.exit(1);}async function ce(a){console.log(""),console.log(l.bold("VAIF CLI Login")),console.log(l.gray("Enter your VAIF account credentials")),console.log("");let t=await ne(l.cyan(" Email: "));(!t||t.trim()==="")&&(console.log(l.red(`
|
|
6
|
+
No email provided. Login cancelled.`)),process.exit(1));let n=await ie(l.cyan(" Password: "));(!n||n.trim()==="")&&(console.log(l.red(`
|
|
7
|
+
No password provided. Login cancelled.`)),process.exit(1));let e=L("Authenticating...").start();try{let r=await fetch(`${R}/auth/cli/login`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:t.trim(),password:n})}),i=await r.json();(!r.ok||!i.ok)&&(e.fail("Login failed"),console.log(l.red(`
|
|
8
|
+
${i.message||"Invalid email or password."}`)),process.exit(1));let c={token:i.accessToken,email:i.user?.email,projectId:a,expiresAt:new Date(Date.now()+i.expiresIn*1e3).toISOString()};G(c),e.succeed("Logged in successfully"),console.log(""),i.user?.email&&console.log(l.green(` Authenticated as: ${i.user.email}`)),console.log(l.gray(` Config saved to: ${_}`)),console.log("");}catch{e.fail("Failed to connect to VAIF API"),console.log(l.red(`
|
|
9
|
+
Could not connect to VAIF API. Please try again later.`)),process.exit(1);}}async function Ue(a){console.log(""),console.log(l.bold("Welcome to VAIF CLI")),console.log(l.gray("Authenticate to access your VAIF projects")),console.log(""),a.email?await ce(a.projectId):await le(a.projectId),console.log(l.gray("You can now use VAIF CLI commands like:")),console.log(l.gray(" vaif pull - Pull remote schema")),console.log(l.gray(" vaif push - Push schema changes")),console.log(l.gray(" vaif generate - Generate TypeScript types")),console.log("");}async function De(){b.existsSync(_)?(b.unlinkSync(_),console.log(l.green("Logged out successfully"))):console.log(l.yellow("Not currently logged in"));}async function ke(){let a=V();(!a||!a.token)&&(console.log(l.yellow("Not logged in")),console.log(l.gray("Run `vaif login` to authenticate")),process.exit(1));let t=L("Checking authentication...").start(),{valid:n,email:e}=await se(a.token);n||(t.fail("Session expired"),console.log(l.yellow(`
|
|
10
|
+
Your session has expired. Please login again.`)),process.exit(1)),t.succeed("Authenticated"),console.log(""),console.log(l.green(` Email: ${e||a.email||"Unknown"}`)),a.projectId&&console.log(l.green(` Project: ${a.projectId}`)),console.log("");}var me=process.env.VAIF_API_URL||"https://api.vaif.studio";async function fe(a,t){let n=await fetch(`${me}/schema-engine/introspect/${t}`,{headers:{Authorization:`Bearer ${a}`,"Content-Type":"application/json"}});if(!n.ok){let u=await n.text();throw new Error(`API introspection failed: ${u}`)}let e=await n.json();if(!e.ok||!e.schemaExists)throw new Error("Project schema does not exist yet. Push a migration first with `vaif db push`.");let r=new Map,i=[];for(let u of e.tables){let o=u.columns.map(s=>({column_name:s.name,data_type:s.type,is_nullable:s.nullable?"YES":"NO",column_default:s.default,udt_name:s.type,is_identity:s.primaryKey&&s.default?.includes("gen_random_uuid")?"YES":"NO",character_maximum_length:null,numeric_precision:null,numeric_scale:null}));r.set(u.name,o);for(let s of u.foreignKeys)i.push({constraint_name:s.constraintName,table_name:u.name,column_name:s.columnName,foreign_table_name:s.refTable,foreign_column_name:s.refColumn});}return {tables:r,enums:new Map,foreignKeys:i}}async function ge(a,t){let n=await a.query(`
|
|
11
11
|
SELECT table_name, table_type
|
|
12
12
|
FROM information_schema.tables
|
|
13
13
|
WHERE table_schema = $1
|
|
@@ -44,7 +44,7 @@ Your session has expired. Please login again.`)),process.exit(1)),t.succeed("Aut
|
|
|
44
44
|
AND ccu.table_schema = tc.table_schema
|
|
45
45
|
WHERE tc.constraint_type = 'FOREIGN KEY'
|
|
46
46
|
AND tc.table_schema = $1
|
|
47
|
-
`,[t]),
|
|
47
|
+
`,[t]),i=await a.query(`
|
|
48
48
|
SELECT
|
|
49
49
|
t.typname as enum_name,
|
|
50
50
|
e.enumlabel as enum_value
|
|
@@ -53,24 +53,24 @@ Your session has expired. Please login again.`)),process.exit(1)),t.succeed("Aut
|
|
|
53
53
|
JOIN pg_namespace n ON n.oid = t.typnamespace
|
|
54
54
|
WHERE n.nspname = $1
|
|
55
55
|
ORDER BY t.typname, e.enumsortorder
|
|
56
|
-
`,[t]),
|
|
56
|
+
`,[t]),c=new Map;for(let o of n.rows)c.set(o.table_name,[]);for(let o of e.rows){let s=c.get(o.table_name);s&&s.push(o);}let u=new Map;for(let o of i.rows){let s=u.get(o.enum_name)||[];s.push(o.enum_value),u.set(o.enum_name,s);}return {tables:c,enums:u,foreignKeys:r.rows}}var U={smallint:"number",integer:"number",bigint:"string",int2:"number",int4:"number",int8:"string",decimal:"string",numeric:"string",real:"number",float4:"number",float8:"number","double precision":"number",money:"string",boolean:"boolean",bool:"boolean",text:"string",varchar:"string",char:"string",character:"string","character varying":"string",name:"string",citext:"string",date:"string",time:"string",timetz:"string","time without time zone":"string","time with time zone":"string",timestamp:"string",timestamptz:"string","timestamp without time zone":"string","timestamp with time zone":"string",interval:"string",bytea:"Buffer",uuid:"string",json:"unknown",jsonb:"unknown",inet:"string",cidr:"string",macaddr:"string",macaddr8:"string",point:"{ x: number; y: number }",line:"string",lseg:"string",box:"string",path:"string",polygon:"string",circle:"string",ARRAY:"unknown[]"};function he(a,t){let{data_type:n,udt_name:e,is_nullable:r}=a;if(t.has(e)){let u=t.get(e).map(o=>`"${o}"`).join(" | ");return r==="YES"?`(${u}) | null`:u}if(n==="ARRAY"){let c=e.replace(/^_/,"");if(t.has(c)){let s=t.get(c).map(f=>`"${f}"`).join(" | ");return r==="YES"?`(${s})[] | null`:`(${s})[]`}let u=U[c]||"unknown";return r==="YES"?`${u}[] | null`:`${u}[]`}let i=U[n]||U[e]||"unknown";return r==="YES"&&(i=`${i} | null`),i}function D(a){return a.split(/[_\-\s]+/).map(t=>t.charAt(0).toUpperCase()+t.slice(1).toLowerCase()).join("")}function ve(a,t){let n=D(a),e=t.map(r=>` | "${r}"`).join(`
|
|
57
57
|
`);return `export type ${n} =
|
|
58
|
-
${e};`}function
|
|
58
|
+
${e};`}function ye(a,t,n){let e=D(a),r=[],i=[],c=[];for(let f of t){let h=he(f,n),d=f.column_name,m=f.column_default!==null||f.is_identity==="YES",I=f.is_nullable==="YES";r.push(` ${d}: ${h};`),m||f.column_name==="id"?i.push(` ${d}?: ${h.replace(" | null","")} | null;`):I?i.push(` ${d}?: ${h};`):i.push(` ${d}: ${h.replace(" | null","")};`),c.push(` ${d}?: ${h.replace(" | null","")} | null;`);}let u=`export interface ${e} {
|
|
59
59
|
${r.join(`
|
|
60
60
|
`)}
|
|
61
|
-
}`,
|
|
62
|
-
${
|
|
61
|
+
}`,o=`export interface ${e}Insert {
|
|
62
|
+
${i.join(`
|
|
63
63
|
`)}
|
|
64
|
-
}`,
|
|
65
|
-
${
|
|
64
|
+
}`,s=`export interface ${e}Update {
|
|
65
|
+
${c.join(`
|
|
66
66
|
`)}
|
|
67
|
-
}`;return {base:u,insert:
|
|
68
|
-
`)}async function
|
|
69
|
-
Either:`)),console.log(
|
|
70
|
-
Set projectId in vaif.config.json or use VAIF_PROJECT_ID env var.`)),process.exit(1)),t.text="Introspecting schema via API...",{tables:
|
|
71
|
-
Push a migration first: vaif db push`));return}t.text=`Generating types for ${
|
|
72
|
-
Error: ${n.message}`)),n.message.includes("ECONNREFUSED")&&console.log(
|
|
73
|
-
Make sure your database is running and accessible.`))),process.exit(1);}}var
|
|
67
|
+
}`;return {base:u,insert:o,update:s}}function be(a,t,n){let e=["/**"," * Auto-generated TypeScript types from database schema"," * Generated by @vaiftech/cli",` * Generated at: ${new Date().toISOString()}`," * "," * DO NOT EDIT MANUALLY - changes will be overwritten"," */",""];if(t.size>0){e.push("// ============ ENUMS ============"),e.push("");for(let[i,c]of t)e.push(ve(i,c)),e.push("");}e.push("// ============ TABLES ============"),e.push("");let r=[];for(let[i,c]of a){let{base:u,insert:o,update:s}=ye(i,c,t);r.push(i),e.push(u),e.push(""),e.push(o),e.push(""),e.push(s),e.push("");}e.push("// ============ DATABASE SCHEMA ============"),e.push(""),e.push("export interface Database {");for(let i of r){let c=D(i);e.push(` ${i}: {`),e.push(` Row: ${c};`),e.push(` Insert: ${c}Insert;`),e.push(` Update: ${c}Update;`),e.push(" };");}return e.push("}"),e.push(""),e.push("export type TableName = keyof Database;"),e.push(""),e.push("// ============ HELPER TYPES ============"),e.push(""),e.push('export type Row<T extends TableName> = Database[T]["Row"];'),e.push('export type Insert<T extends TableName> = Database[T]["Insert"];'),e.push('export type Update<T extends TableName> = Database[T]["Update"];'),e.push(""),e.join(`
|
|
68
|
+
`)}async function He(a){let t=L("Loading configuration...").start();try{let n=await q(a.config),e=a.connection||n?.database?.url||process.env.DATABASE_URL,r=e&&!e.includes("${"),i,c,u;if(r){t.text="Connecting to database...";let d=new ue.Client({connectionString:e});await d.connect(),t.text="Introspecting schema...",{tables:i,enums:c,foreignKeys:u}=await ge(d,a.schema),await d.end();}else {let d=V();(!d||!d.token)&&(t.fail("No database connection and not logged in"),console.log(l.yellow(`
|
|
69
|
+
Either:`)),console.log(l.gray(" 1. Run `vaif login` to authenticate (no DATABASE_URL needed)")),console.log(l.gray(" 2. Set DATABASE_URL in your .env file")),console.log(l.gray(" 3. Pass --connection postgresql://user:pass@host:5432/db")),process.exit(1));let m=n?.projectId||process.env.VAIF_PROJECT_ID||d.projectId;m||(t.fail("No project ID specified"),console.log(l.yellow(`
|
|
70
|
+
Set projectId in vaif.config.json or use VAIF_PROJECT_ID env var.`)),process.exit(1)),t.text="Introspecting schema via API...",{tables:i,enums:c,foreignKeys:u}=await fe(d.token,m);}if(i.size===0){t.warn("No tables found"),console.log(l.yellow(`
|
|
71
|
+
Push a migration first: vaif db push`));return}t.text=`Generating types for ${i.size} tables...`;let o=be(i,c,u),s=await pe.format(o,{parser:"typescript",semi:!0,singleQuote:!1,trailingComma:"es5",printWidth:100});if(a.dryRun){t.succeed("Generated types (dry run):"),console.log(""),console.log(l.gray("\u2500".repeat(60))),console.log(s),console.log(l.gray("\u2500".repeat(60)));return}let f=k.resolve(a.output),h=k.dirname(f);b.existsSync(h)||b.mkdirSync(h,{recursive:!0}),b.writeFileSync(f,s,"utf-8"),t.succeed(`Generated types for ${i.size} tables \u2192 ${l.cyan(a.output)}`),console.log(""),console.log(l.green("Generated:")),console.log(l.gray(` Tables: ${i.size}`)),console.log(l.gray(` Enums: ${c.size}`)),console.log(""),console.log(l.gray("Import in your code:")),console.log(l.cyan(` import type { Database, Row, Insert, Update } from "${a.output.replace(/\.ts$/,"")}";`));}catch(n){t.fail("Failed to generate types"),n instanceof Error&&(console.error(l.red(`
|
|
72
|
+
Error: ${n.message}`)),n.message.includes("ECONNREFUSED")&&console.log(l.yellow(`
|
|
73
|
+
Make sure your database is running and accessible.`))),process.exit(1);}}var T=[{name:"database",label:"Database",description:"CRUD queries, type-safe operations"},{name:"auth",label:"Authentication",description:"login, signup, OAuth, sessions"},{name:"realtime",label:"Realtime",description:"live subscriptions, presence"},{name:"storage",label:"Storage",description:"file uploads, signed URLs"},{name:"functions",label:"Functions",description:"serverless function calls"}],W={"nextjs-fullstack":{name:"Next.js Full-Stack",description:"Next.js app with server/client VAIF client, auth middleware, and React hooks",tag:"Next.js",defaultFeatures:["database","auth"],files:[{path:"package.json",content:`{
|
|
74
74
|
"name": "my-vaif-app",
|
|
75
75
|
"private": true,
|
|
76
76
|
"version": "0.1.0",
|
|
@@ -167,6 +167,7 @@ body {
|
|
|
167
167
|
// Browser client \u2013 safe to use in Client Components
|
|
168
168
|
export const vaif = createVaifClient({
|
|
169
169
|
baseUrl: process.env.NEXT_PUBLIC_VAIF_API_URL || 'https://api.vaif.studio',
|
|
170
|
+
projectId: process.env.NEXT_PUBLIC_VAIF_PROJECT_ID,
|
|
170
171
|
apiKey: process.env.NEXT_PUBLIC_VAIF_API_KEY!,
|
|
171
172
|
});
|
|
172
173
|
|
|
@@ -174,6 +175,7 @@ export const vaif = createVaifClient({
|
|
|
174
175
|
export function createVaifServer() {
|
|
175
176
|
return createVaifClient({
|
|
176
177
|
baseUrl: process.env.NEXT_PUBLIC_VAIF_API_URL || 'https://api.vaif.studio',
|
|
178
|
+
projectId: process.env.VAIF_PROJECT_ID,
|
|
177
179
|
apiKey: process.env.VAIF_SECRET_KEY!,
|
|
178
180
|
});
|
|
179
181
|
}
|
|
@@ -181,6 +183,7 @@ export function createVaifServer() {
|
|
|
181
183
|
# Get these values from https://vaif.studio/app/security/api-keys \u2192 Project Settings \u2192 API Keys
|
|
182
184
|
|
|
183
185
|
NEXT_PUBLIC_VAIF_API_URL=https://api.vaif.studio
|
|
186
|
+
NEXT_PUBLIC_VAIF_PROJECT_ID=your-project-id
|
|
184
187
|
NEXT_PUBLIC_VAIF_API_KEY=your-api-key
|
|
185
188
|
VAIF_SECRET_KEY=your-secret-key
|
|
186
189
|
|
|
@@ -287,7 +290,21 @@ A full-stack Next.js application powered by [VAIF Studio](https://vaif.studio),
|
|
|
287
290
|
## Documentation
|
|
288
291
|
|
|
289
292
|
Full documentation is available at <https://docs.vaif.studio>.
|
|
290
|
-
`}],featureFiles:{auth:[{path:"
|
|
293
|
+
`}],featureFiles:{auth:[{path:"app/page.tsx",content:`import Link from "next/link";
|
|
294
|
+
|
|
295
|
+
export default function Home() {
|
|
296
|
+
return (
|
|
297
|
+
<main style={{ maxWidth: 600, margin: "80px auto", textAlign: "center" }}>
|
|
298
|
+
<h1>Welcome to VAIF</h1>
|
|
299
|
+
<p>Your Next.js app is ready. Start building!</p>
|
|
300
|
+
<p style={{ marginTop: 24 }}>
|
|
301
|
+
<Link href="/login" style={{ marginRight: 16 }}>Log in</Link>
|
|
302
|
+
<Link href="/signup">Sign up</Link>
|
|
303
|
+
</p>
|
|
304
|
+
</main>
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
`},{path:"middleware.ts",content:`import { NextResponse, type NextRequest } from "next/server";
|
|
291
308
|
import { createVaifClient } from "@vaiftech/client";
|
|
292
309
|
import { authMiddleware } from "@vaiftech/auth/nextjs";
|
|
293
310
|
|
|
@@ -300,6 +317,7 @@ export async function middleware(request: NextRequest) {
|
|
|
300
317
|
|
|
301
318
|
const vaif = createVaifClient({
|
|
302
319
|
baseUrl: process.env.NEXT_PUBLIC_VAIF_API_URL || 'https://api.vaif.studio',
|
|
320
|
+
projectId: process.env.VAIF_PROJECT_ID,
|
|
303
321
|
apiKey: process.env.VAIF_SECRET_KEY!,
|
|
304
322
|
});
|
|
305
323
|
|
|
@@ -662,12 +680,14 @@ function Home() {
|
|
|
662
680
|
|
|
663
681
|
export const vaif = createVaifClient({
|
|
664
682
|
baseUrl: import.meta.env.VITE_VAIF_API_URL || 'https://api.vaif.studio',
|
|
683
|
+
projectId: import.meta.env.VITE_VAIF_PROJECT_ID,
|
|
665
684
|
apiKey: import.meta.env.VITE_VAIF_API_KEY,
|
|
666
685
|
});
|
|
667
686
|
`},{path:"src/vite-env.d.ts",content:`/// <reference types="vite/client" />
|
|
668
687
|
|
|
669
688
|
interface ImportMetaEnv {
|
|
670
689
|
readonly VITE_VAIF_API_URL: string;
|
|
690
|
+
readonly VITE_VAIF_PROJECT_ID: string;
|
|
671
691
|
readonly VITE_VAIF_API_KEY: string;
|
|
672
692
|
}
|
|
673
693
|
|
|
@@ -678,6 +698,7 @@ interface ImportMeta {
|
|
|
678
698
|
# Get these values from https://vaif.studio/app/security/api-keys
|
|
679
699
|
|
|
680
700
|
VITE_VAIF_API_URL=https://api.vaif.studio
|
|
701
|
+
VITE_VAIF_PROJECT_ID=your-project-id
|
|
681
702
|
VITE_VAIF_API_KEY=your-api-key
|
|
682
703
|
`},{path:".gitignore",content:`node_modules
|
|
683
704
|
dist
|
|
@@ -777,7 +798,33 @@ A single-page React application built with [Vite](https://vite.dev/) and powered
|
|
|
777
798
|
## Documentation
|
|
778
799
|
|
|
779
800
|
Full documentation is available at <https://docs.vaif.studio>.
|
|
780
|
-
`}],featureFiles:{auth:[{path:"src/
|
|
801
|
+
`}],featureFiles:{auth:[{path:"src/App.tsx",content:`import { Routes, Route, Link } from "react-router-dom";
|
|
802
|
+
import Login from "./pages/Login";
|
|
803
|
+
import Signup from "./pages/Signup";
|
|
804
|
+
|
|
805
|
+
export default function App() {
|
|
806
|
+
return (
|
|
807
|
+
<Routes>
|
|
808
|
+
<Route path="/" element={<Home />} />
|
|
809
|
+
<Route path="/login" element={<Login />} />
|
|
810
|
+
<Route path="/signup" element={<Signup />} />
|
|
811
|
+
</Routes>
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
function Home() {
|
|
816
|
+
return (
|
|
817
|
+
<div style={{ maxWidth: 600, margin: "80px auto", textAlign: "center" }}>
|
|
818
|
+
<h1>Welcome to VAIF</h1>
|
|
819
|
+
<p>Your app is ready. Start building!</p>
|
|
820
|
+
<p style={{ marginTop: 24 }}>
|
|
821
|
+
<Link to="/login" style={{ marginRight: 16 }}>Log in</Link>
|
|
822
|
+
<Link to="/signup">Sign up</Link>
|
|
823
|
+
</p>
|
|
824
|
+
</div>
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
`},{path:"src/pages/Login.tsx",content:`import { useState } from "react";
|
|
781
828
|
import { useNavigate } from "react-router-dom";
|
|
782
829
|
import { vaif } from "../lib/vaif";
|
|
783
830
|
|
|
@@ -882,21 +929,15 @@ export function AuthGuard({ children, fallback }: AuthGuardProps) {
|
|
|
882
929
|
const [authenticated, setAuthenticated] = useState(false);
|
|
883
930
|
|
|
884
931
|
useEffect(() => {
|
|
885
|
-
vaif.auth.
|
|
886
|
-
|
|
932
|
+
vaif.auth.getUser()
|
|
933
|
+
.then(() => {
|
|
887
934
|
setAuthenticated(true);
|
|
888
|
-
|
|
935
|
+
setLoading(false);
|
|
936
|
+
})
|
|
937
|
+
.catch(() => {
|
|
889
938
|
navigate("/login");
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
});
|
|
893
|
-
|
|
894
|
-
const { data: { subscription } } = vaif.auth.onAuthStateChange((_event, session) => {
|
|
895
|
-
setAuthenticated(!!session);
|
|
896
|
-
if (!session) navigate("/login");
|
|
897
|
-
});
|
|
898
|
-
|
|
899
|
-
return () => subscription.unsubscribe();
|
|
939
|
+
setLoading(false);
|
|
940
|
+
});
|
|
900
941
|
}, [navigate]);
|
|
901
942
|
|
|
902
943
|
if (loading) return fallback ?? <div>Loading...</div>;
|
|
@@ -1444,7 +1485,39 @@ A React Native / Expo mobile application powered by [VAIF Studio](https://vaif.s
|
|
|
1444
1485
|
## Documentation
|
|
1445
1486
|
|
|
1446
1487
|
Full documentation is available at <https://docs.vaif.studio>.
|
|
1447
|
-
`}],featureFiles:{auth:[{path:"app/
|
|
1488
|
+
`}],featureFiles:{auth:[{path:"app/index.tsx",content:`import { View, Text, StyleSheet, TouchableOpacity } from "react-native";
|
|
1489
|
+
import { useRouter } from "expo-router";
|
|
1490
|
+
|
|
1491
|
+
export default function HomeScreen() {
|
|
1492
|
+
const router = useRouter();
|
|
1493
|
+
|
|
1494
|
+
return (
|
|
1495
|
+
<View style={styles.container}>
|
|
1496
|
+
<Text style={styles.title}>Welcome to VAIF</Text>
|
|
1497
|
+
<Text style={styles.subtitle}>Your mobile app is ready. Start building!</Text>
|
|
1498
|
+
<View style={styles.buttons}>
|
|
1499
|
+
<TouchableOpacity style={styles.button} onPress={() => router.push("/(auth)/login")}>
|
|
1500
|
+
<Text style={styles.buttonText}>Log In</Text>
|
|
1501
|
+
</TouchableOpacity>
|
|
1502
|
+
<TouchableOpacity style={[styles.button, styles.secondaryButton]} onPress={() => router.push("/(auth)/signup")}>
|
|
1503
|
+
<Text style={[styles.buttonText, styles.secondaryText]}>Sign Up</Text>
|
|
1504
|
+
</TouchableOpacity>
|
|
1505
|
+
</View>
|
|
1506
|
+
</View>
|
|
1507
|
+
);
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
const styles = StyleSheet.create({
|
|
1511
|
+
container: { flex: 1, alignItems: "center", justifyContent: "center", padding: 24 },
|
|
1512
|
+
title: { fontSize: 28, fontWeight: "bold", marginBottom: 8 },
|
|
1513
|
+
subtitle: { fontSize: 16, color: "#666", textAlign: "center", marginBottom: 32 },
|
|
1514
|
+
buttons: { gap: 12, width: "100%" },
|
|
1515
|
+
button: { backgroundColor: "#0070f3", borderRadius: 8, padding: 14, alignItems: "center" },
|
|
1516
|
+
secondaryButton: { backgroundColor: "transparent", borderWidth: 1, borderColor: "#0070f3" },
|
|
1517
|
+
buttonText: { color: "#fff", fontSize: 16, fontWeight: "600" },
|
|
1518
|
+
secondaryText: { color: "#0070f3" },
|
|
1519
|
+
});
|
|
1520
|
+
`},{path:"app/(auth)/login.tsx",content:`import { useState } from "react";
|
|
1448
1521
|
import { View, Text, TextInput, TouchableOpacity, StyleSheet, Alert } from "react-native";
|
|
1449
1522
|
import { useRouter } from "expo-router";
|
|
1450
1523
|
import { vaif } from "../../lib/vaif";
|
|
@@ -2626,6 +2699,7 @@ func HelloHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
2626
2699
|
|
|
2627
2700
|
export const vaif = createVaifClient({
|
|
2628
2701
|
baseUrl: import.meta.env.VITE_VAIF_API_URL || 'https://api.vaif.studio',
|
|
2702
|
+
projectId: import.meta.env.VITE_VAIF_PROJECT_ID,
|
|
2629
2703
|
apiKey: import.meta.env.VITE_VAIF_API_KEY,
|
|
2630
2704
|
});
|
|
2631
2705
|
|
|
@@ -2680,6 +2754,7 @@ export async function deleteTodo(id: string): Promise<void> {
|
|
|
2680
2754
|
# Get these values from https://vaif.studio/app/security/api-keys
|
|
2681
2755
|
|
|
2682
2756
|
VITE_VAIF_API_URL=https://api.vaif.studio
|
|
2757
|
+
VITE_VAIF_PROJECT_ID=your-project-id
|
|
2683
2758
|
VITE_VAIF_API_KEY=your-api-key
|
|
2684
2759
|
`},{path:"README.md",content:`# Todo App \u2014 VAIF Starter
|
|
2685
2760
|
|
|
@@ -2817,6 +2892,7 @@ export const posts = pgTable("posts", {
|
|
|
2817
2892
|
|
|
2818
2893
|
export const vaif = createVaifClient({
|
|
2819
2894
|
baseUrl: import.meta.env.VITE_VAIF_API_URL || 'https://api.vaif.studio',
|
|
2895
|
+
projectId: import.meta.env.VITE_VAIF_PROJECT_ID,
|
|
2820
2896
|
apiKey: import.meta.env.VITE_VAIF_API_KEY,
|
|
2821
2897
|
});
|
|
2822
2898
|
|
|
@@ -2958,6 +3034,7 @@ export function useRealtimeMessages({
|
|
|
2958
3034
|
# Get these values from https://vaif.studio/app/security/api-keys
|
|
2959
3035
|
|
|
2960
3036
|
VITE_VAIF_API_URL=https://api.vaif.studio
|
|
3037
|
+
VITE_VAIF_PROJECT_ID=your-project-id
|
|
2961
3038
|
VITE_VAIF_API_KEY=your-api-key
|
|
2962
3039
|
`},{path:"README.md",content:`# Realtime Chat \u2014 VAIF Starter
|
|
2963
3040
|
|
|
@@ -3110,6 +3187,7 @@ export const posts = pgTable("posts", {
|
|
|
3110
3187
|
// Browser client \u2013 use in Client Components
|
|
3111
3188
|
export const vaif = createVaifClient({
|
|
3112
3189
|
baseUrl: process.env.NEXT_PUBLIC_VAIF_API_URL || 'https://api.vaif.studio',
|
|
3190
|
+
projectId: process.env.NEXT_PUBLIC_VAIF_PROJECT_ID,
|
|
3113
3191
|
apiKey: process.env.NEXT_PUBLIC_VAIF_API_KEY!,
|
|
3114
3192
|
});
|
|
3115
3193
|
|
|
@@ -3117,6 +3195,7 @@ export const vaif = createVaifClient({
|
|
|
3117
3195
|
export function createVaifServer() {
|
|
3118
3196
|
return createVaifClient({
|
|
3119
3197
|
baseUrl: process.env.NEXT_PUBLIC_VAIF_API_URL || 'https://api.vaif.studio',
|
|
3198
|
+
projectId: process.env.VAIF_PROJECT_ID,
|
|
3120
3199
|
apiKey: process.env.VAIF_SECRET_KEY!,
|
|
3121
3200
|
});
|
|
3122
3201
|
}
|
|
@@ -3271,6 +3350,7 @@ export async function requireTeamRole(
|
|
|
3271
3350
|
# Get these values from https://vaif.studio/app/security/api-keys \u2192 Project Settings \u2192 API Keys
|
|
3272
3351
|
|
|
3273
3352
|
NEXT_PUBLIC_VAIF_API_URL=https://api.vaif.studio
|
|
3353
|
+
NEXT_PUBLIC_VAIF_PROJECT_ID=your-project-id
|
|
3274
3354
|
NEXT_PUBLIC_VAIF_API_KEY=your-api-key
|
|
3275
3355
|
VAIF_SECRET_KEY=your-secret-key
|
|
3276
3356
|
|
|
@@ -3423,6 +3503,7 @@ export const posts = pgTable("posts", {
|
|
|
3423
3503
|
// Browser client
|
|
3424
3504
|
export const vaif = createVaifClient({
|
|
3425
3505
|
baseUrl: process.env.NEXT_PUBLIC_VAIF_API_URL || 'https://api.vaif.studio',
|
|
3506
|
+
projectId: process.env.NEXT_PUBLIC_VAIF_PROJECT_ID,
|
|
3426
3507
|
apiKey: process.env.NEXT_PUBLIC_VAIF_API_KEY!,
|
|
3427
3508
|
});
|
|
3428
3509
|
|
|
@@ -3430,6 +3511,7 @@ export const vaif = createVaifClient({
|
|
|
3430
3511
|
export function createVaifServer() {
|
|
3431
3512
|
return createVaifClient({
|
|
3432
3513
|
baseUrl: process.env.NEXT_PUBLIC_VAIF_API_URL || 'https://api.vaif.studio',
|
|
3514
|
+
projectId: process.env.VAIF_PROJECT_ID,
|
|
3433
3515
|
apiKey: process.env.VAIF_SECRET_KEY!,
|
|
3434
3516
|
});
|
|
3435
3517
|
}
|
|
@@ -3546,6 +3628,7 @@ function getContentType(fileName: string): string {
|
|
|
3546
3628
|
# Get these values from https://vaif.studio/app/security/api-keys \u2192 Project Settings \u2192 API Keys
|
|
3547
3629
|
|
|
3548
3630
|
NEXT_PUBLIC_VAIF_API_URL=https://api.vaif.studio
|
|
3631
|
+
NEXT_PUBLIC_VAIF_PROJECT_ID=your-project-id
|
|
3549
3632
|
NEXT_PUBLIC_VAIF_API_KEY=your-api-key
|
|
3550
3633
|
VAIF_SECRET_KEY=your-secret-key
|
|
3551
3634
|
|
|
@@ -3691,16 +3774,16 @@ export const posts = pgTable("posts", {
|
|
|
3691
3774
|
|
|
3692
3775
|
return Response.json({ message: \`Hello, \${name}!\` });
|
|
3693
3776
|
}
|
|
3694
|
-
`}]},dependencies:["@vaiftech/client","@vaiftech/auth"],postInstructions:["Copy .env.example to .env.local and fill in your project credentials","Create a 'product-images' storage bucket in your VAIF dashboard","Import storage helpers from '@/lib/storage' in your API routes","Use uploadProductImage() in your product creation flow","Run: npx vaif generate to generate TypeScript types"]}};function
|
|
3695
|
-
? Which VAIF features do you want to include?`)),
|
|
3696
|
-
Unknown template: ${a}`)),console.log(
|
|
3697
|
-
`)),process.exit(1));let e;t.features&&t.features.length>0?e=t.features.filter(
|
|
3698
|
-
No features specified.`)),console.log(
|
|
3699
|
-
`,"utf-8"),console.log(
|
|
3700
|
-
`,"utf-8");}catch{}(n.dependencies?.length||n.devDependencies?.length)&&(console.log(""),console.log(
|
|
3701
|
-
--add-features requires --template to know which template to use.`)),console.log(
|
|
3702
|
-
Use --force to overwrite existing configuration.`)),process.exit(1));try{if(
|
|
3777
|
+
`}]},dependencies:["@vaiftech/client","@vaiftech/auth"],postInstructions:["Copy .env.example to .env.local and fill in your project credentials","Create a 'product-images' storage bucket in your VAIF dashboard","Import storage helpers from '@/lib/storage' in your API routes","Use uploadProductImage() in your product creation flow","Run: npx vaif generate to generate TypeScript types"]}};function tt(){console.log(""),console.log(l.bold("Available project templates")),console.log("");let a=26,t=22;console.log(` ${l.gray("Template".padEnd(a))}${l.gray("Stack".padEnd(t))}${l.gray("Description")}`),console.log(l.gray(" "+"-".repeat(a+t+40)));for(let[n,e]of Object.entries(W))console.log(` ${l.cyan(n.padEnd(a))}${l.yellow(e.tag.padEnd(t))}${l.white(e.description)}`);console.log(""),console.log(l.gray("Usage:")),console.log(l.gray(" npx @vaiftech/cli init --template <name>")),console.log(l.gray(" npx @vaiftech/cli init -t nextjs-fullstack")),console.log(l.gray(" npx @vaiftech/cli init -t react-spa --features auth,database,realtime")),console.log(""),console.log(l.gray("Available features: auth, database, realtime, storage, functions")),console.log("");}async function Ie(a){if(!process.stdin.isTTY||!process.stdout.isTTY)return a;let t=new Set(a.map(e=>T.findIndex(r=>r.name===e)).filter(e=>e>=0)),n=0;return new Promise(e=>{let r=$.createInterface({input:process.stdin,output:process.stdout});$.emitKeypressEvents(process.stdin,r),process.stdin.setRawMode&&process.stdin.setRawMode(true);function i(){let u=T.length+2;process.stdout.write(`\x1B[${u}A`),c();}function c(){console.log(l.bold(`
|
|
3778
|
+
? Which VAIF features do you want to include?`)),T.forEach((u,o)=>{let s=t.has(o)?l.green("[x]"):"[ ]",f=o===n?l.cyan("> "):" ";console.log(`${f}${s} ${u.label} ${l.gray(`(${u.description})`)}`);}),console.log(l.gray(" (up/down to move, space to toggle, enter to confirm)"));}c(),process.stdin.on("keypress",(u,o)=>{if(o.name==="up"&&n>0)n--,i();else if(o.name==="down"&&n<T.length-1)n++,i();else if(o.name==="space")t.has(n)?t.delete(n):t.add(n),i();else if(o.name==="return"){process.stdin.setRawMode&&process.stdin.setRawMode(false),r.close();let s=[...t].sort().map(f=>T[f].name);e(s.length>0?s:a);}else o.name==="c"&&o.ctrl&&(process.stdin.setRawMode&&process.stdin.setRawMode(false),r.close(),process.exit(0));});})}async function j(a,t={}){let n=W[a];n||(console.log(l.red(`
|
|
3779
|
+
Unknown template: ${a}`)),console.log(l.yellow(`Run 'vaif templates' to see available templates.
|
|
3780
|
+
`)),process.exit(1));let e;t.features&&t.features.length>0?e=t.features.filter(d=>T.some(m=>m.name===d)):t.addOnly?(console.log(l.red(`
|
|
3781
|
+
No features specified.`)),console.log(l.yellow("Usage: vaif init --template <name> --add-features <features>")),console.log(l.gray("Available features: auth, database, realtime, storage, functions")),process.exit(1)):n.featureFiles&&Object.keys(n.featureFiles).length>0?e=await Ie(n.defaultFeatures??["database","auth"]):e=n.defaultFeatures??[],t.addOnly?(console.log(""),console.log(l.bold(`Adding features to ${l.cyan(n.name)} project...`)),console.log(l.gray(` Features: ${e.join(", ")}`)),console.log("")):(console.log(""),console.log(l.bold(`Scaffolding ${l.cyan(n.name)} template...`)),e.length>0&&console.log(l.gray(` Features: ${e.join(", ")}`)),console.log(""));let r=t.addOnly?[]:[...n.files],i=new Set,c=[];if(n.featureFiles)for(let d of e){let m=n.featureFiles[d];if(m)for(let I of m)i.add(I.path),c.push(I);}let u=r.filter(d=>!i.has(d.path)).concat(c),o=0,s=0;for(let d of u){let m=k.resolve(d.path),I=k.dirname(m);if(b.existsSync(I)||b.mkdirSync(I,{recursive:true}),d.path==="package.json"&&b.existsSync(m)&&!t.force)try{let y=JSON.parse(b.readFileSync(m,"utf-8")),E=JSON.parse(d.content),S=M=>{if(!M)return {};let K={};for(let[X,w]of Object.entries(M))!w.startsWith("workspace:")&&!w.startsWith("link:")&&!w.startsWith("file:")&&(K[X]=w);return K};y.dependencies={...S(y.dependencies),...E.dependencies||{}},y.devDependencies={...S(y.devDependencies),...E.devDependencies||{}},E.scripts&&(y.scripts={...y.scripts||{},...E.scripts}),b.writeFileSync(m,JSON.stringify(y,null,2)+`
|
|
3782
|
+
`,"utf-8"),console.log(l.green(` merge ${d.path} (added dependencies)`)),o++;continue}catch{}if(b.existsSync(m)&&!t.force){console.log(l.yellow(` skip ${d.path} (already exists)`)),s++;continue}b.writeFileSync(m,d.content,"utf-8"),console.log(l.green(` create ${d.path}`)),o++;}console.log(""),o>0&&console.log(l.green(`Created ${o} file${o!==1?"s":""}.`)),s>0&&console.log(l.yellow(`Skipped ${s} file${s!==1?"s":""} (use --force to overwrite).`));let f={auth:{"@vaiftech/auth":"^1.0.0"},database:{},realtime:{},storage:{},functions:{}},h=k.resolve("package.json");if(b.existsSync(h)&&e.length>0)try{let d=JSON.parse(b.readFileSync(h,"utf-8")),m=!1;for(let I of e){let y=f[I];if(y)for(let[E,S]of Object.entries(y))d.dependencies?.[E]||(d.dependencies=d.dependencies||{},d.dependencies[E]=S,m=!0);}m&&b.writeFileSync(h,JSON.stringify(d,null,2)+`
|
|
3783
|
+
`,"utf-8");}catch{}(n.dependencies?.length||n.devDependencies?.length)&&(console.log(""),console.log(l.bold("Install dependencies:")),n.dependencies?.length&&console.log(l.cyan(` npm install ${n.dependencies.join(" ")}`)),n.devDependencies?.length&&console.log(l.cyan(` npm install -D ${n.devDependencies.join(" ")}`))),console.log(""),console.log(l.bold.green("Project scaffolded successfully!")),console.log(""),console.log(l.bold(" Next steps:")),n.postInstructions.forEach(d=>{console.log(l.gray(` ${d}`));}),console.log(""),console.log(l.gray(" Get your project credentials at https://vaif.studio/app/security/api-keys")),console.log("");}var _e={$schema:"https://vaif.studio/schemas/config.json",projectId:"",database:{url:"${DATABASE_URL}",schema:"public"},types:{output:"./src/types/database.ts"},api:{baseUrl:"https://api.vaif.studio"}};async function ct(a){if(a.addFeatures){a.template||(console.log(l.red(`
|
|
3784
|
+
--add-features requires --template to know which template to use.`)),console.log(l.gray("Example: vaif init --template react-spa --add-features functions,storage")),process.exit(1));let e=a.addFeatures.split(",").map(r=>r.trim());await j(a.template,{force:a.force,features:e,addOnly:true});return}let t=L("Initializing VAIF configuration...").start(),n=k.resolve("vaif.config.json");b.existsSync(n)&&!a.force&&(t.fail("vaif.config.json already exists"),console.log(l.yellow(`
|
|
3785
|
+
Use --force to overwrite existing configuration.`)),process.exit(1));try{if(b.writeFileSync(n,JSON.stringify(_e,null,2),"utf-8"),t.succeed("Created vaif.config.json"),a.template){let e=a.features?a.features.split(",").map(r=>r.trim()):void 0;await j(a.template,{force:a.force,features:e});}else {let e=k.resolve(".env.example");if(b.existsSync(e)||(b.writeFileSync(e,`# VAIF Configuration
|
|
3703
3786
|
DATABASE_URL=postgresql://user:password@localhost:5432/database
|
|
3704
3787
|
VAIF_API_KEY=your-api-key
|
|
3705
|
-
`,"utf-8"),console.log(
|
|
3706
|
-
Error: ${e.message}`)),process.exit(1);}}export{
|
|
3788
|
+
`,"utf-8"),console.log(l.gray("Created .env.example"))),a.typescript){let r=k.resolve("src/types");b.existsSync(r)||(b.mkdirSync(r,{recursive:!0}),console.log(l.gray("Created src/types directory")));}console.log(""),console.log(l.green("VAIF initialized successfully!")),console.log(""),console.log(l.gray("Next steps:")),console.log(l.gray(" 1. Update vaif.config.json with your project ID")),console.log(l.gray(" 2. Set DATABASE_URL in your environment")),console.log(l.gray(" 3. Run: npx vaif generate")),console.log("");}}catch(e){t.fail("Failed to initialize"),e instanceof Error&&console.error(l.red(`
|
|
3789
|
+
Error: ${e.message}`)),process.exit(1);}}export{q as a,V as b,Ue as c,De as d,ke as e,He as f,tt as g,ct as h};
|