generatesaas 1.16.0 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +43 -22
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
- import{Command as Fs}from"commander";import*as Jn from"@clack/prompts";import{existsSync as as,readdirSync as cs,rmSync as ls}from"fs";import{join as ps,resolve as ds}from"path";import{Option as B}from"commander";import*as S from"@clack/prompts";import*as Xe from"@clack/prompts";import Rt from"picocolors";function nr(e){let t=e?` GenerateSaaS v${e} `:" GenerateSaaS ";Xe.intro(Rt.bgYellow(Rt.black(t)))}function or(){Xe.outro(Rt.yellow("Happy building!"))}import*as u from"@clack/prompts";import f from"picocolors";var je={nextjs:{label:"Next.js",hint:"React 19 + Next.js 16"},nuxt:{label:"Nuxt",hint:"Vue 3 + Nuxt 4"}},Ue={fullstack:{label:"Fullstack",hint:"Frontend hosts the API - works on serverless or long-running"},separate:{label:"Separate",hint:"Standalone Hono backend - long-running runtimes only (Docker, Render, Fly.io, Railway, Coolify, Dokploy)"}},Ze={stripe:{label:"Stripe"},polar:{label:"Polar"},none:{label:"None",hint:"disable payments"}},Qe={smtp:{label:"SMTP",hint:"Mailpit for local dev"},ses:{label:"Amazon SES"},resend:{label:"Resend"}},et={user:{label:"Per user",hint:"each user has their own subscription"},organization:{label:"Per organization",hint:"org subscription shared by members"}},ke={postgres:{label:"PostgreSQL",hint:"port 5432",port:5432},redis:{label:"Redis",hint:"port 6379",port:6379},inngest:{label:"Inngest",hint:"port 8288",port:8288},mailpit:{label:"Mailpit",hint:"port 1025",port:1025}},Ve={"claude-code":{label:"Claude Code"},cursor:{label:"Cursor"},codex:{label:"Codex"},"gemini-cli":{label:"Gemini CLI"},windsurf:{label:"Windsurf"}};var pe={USD:{symbol:"$",name:"US Dollar",place:"left",space:!1},EUR:{symbol:"\u20AC",name:"Euro",place:"right",space:!1},GBP:{symbol:"\xA3",name:"British Pound",place:"left",space:!1},CAD:{symbol:"CA$",name:"Canadian Dollar",place:"left",space:!1},AUD:{symbol:"A$",name:"Australian Dollar",place:"left",space:!1},BRL:{symbol:"R$",name:"Brazilian Real",place:"left",space:!1},JPY:{symbol:"\xA5",name:"Japanese Yen",place:"left",space:!1}};var H={node:{label:"Node.js / Docker",hint:"long-running runtime - Render, Fly.io, Railway, Coolify, Dokploy, VPS",edgeRuntime:!1},vercel:{label:"Vercel",hint:"serverless functions",edgeRuntime:!0}},$={postgres:{label:"PostgreSQL (self-hosted)",hint:"local Docker, drizzle-orm/node-postgres",managed:!1,envVars:[{key:"DATABASE_URL",defaultValue:"postgres://postgres:postgres@localhost:5432/saas"}]},neon:{label:"Neon",hint:"serverless Postgres",managed:!0,envVars:[{key:"DATABASE_URL",comment:"# TODO: Add your Neon connection string"}]},supabase:{label:"Supabase",hint:"managed Postgres",managed:!0,envVars:[{key:"DATABASE_URL",comment:"# TODO: Add your Supabase connection string"}]}},Y={redis:{label:"Redis (self-hosted)",hint:"local Docker, ioredis",managed:!1,envVars:[{key:"REDIS_URL",defaultValue:"redis://localhost:6379"}]},upstash:{label:"Upstash",hint:"serverless Redis",managed:!0,envVars:[{key:"UPSTASH_REDIS_REST_URL",comment:"# TODO: Add your Upstash REST URL"},{key:"UPSTASH_REDIS_REST_TOKEN",comment:"# TODO: Add your Upstash REST token"}]}},Me=[{target:"vercel",provider:"redis",reason:"Vercel serverless cannot maintain persistent Redis connections. Consider Upstash."}],Fe=[{architecture:"separate",target:"vercel",reason:"Standalone backends need a long-running runtime. Vercel's per-function TypeScript check conflicts with pnpm's isolated install. Use --architecture fullstack for serverless, or deploy the standalone backend to a long-running runtime (Render, Fly.io, Railway, Coolify, Dokploy, or your own VPS via Docker)."}],ir=["Local file storage (sharp, geoip-lite)","SMTP email (use Resend or SES instead)","Content API git integration"];function tt(e){let t=$[e.databaseProvider].managed,r=Y[e.cacheProvider].managed;return e.dockerServices.some(n=>!(n==="postgres"&&t||n==="redis"&&r))}var q={google:{label:"Google",envVars:[{name:"GOOGLE_CLIENT_ID",secret:!1},{name:"GOOGLE_CLIENT_SECRET",secret:!0}]},github:{label:"GitHub",envVars:[{name:"GITHUB_CLIENT_ID",secret:!1},{name:"GITHUB_CLIENT_SECRET",secret:!0}]},facebook:{label:"Facebook",envVars:[{name:"FACEBOOK_CLIENT_ID",secret:!1},{name:"FACEBOOK_CLIENT_SECRET",secret:!0}]},discord:{label:"Discord",envVars:[{name:"DISCORD_CLIENT_ID",secret:!1},{name:"DISCORD_CLIENT_SECRET",secret:!0}]},x:{label:"X",envVars:[{name:"TWITTER_CLIENT_ID",secret:!1},{name:"TWITTER_CLIENT_SECRET",secret:!0}]}};var Be=["nextjs","nuxt"],rt=["fullstack","separate"],nt=["stripe","polar","none"],ot=["smtp","ses","resend"],it=["postgres","redis","inngest","mailpit"],st=["claude-code","cursor","codex","gemini-cli","windsurf"],at=["user","organization"],de=["USD","EUR","GBP","CAD","AUD","BRL","JPY"],ue=["node","vercel"],me=["postgres","neon","supabase"],fe=["redis","upstash"],ct=["google","github","facebook","discord","x"];function lt(e){return e.split("-").map(t=>t.charAt(0).toUpperCase()+t.slice(1)).join(" ")}function pt(e){return/^[a-z][a-z0-9-]*$/.test(e)}function I(e){return e instanceof Error?e.message:String(e)}function w(e){u.isCancel(e)&&(u.cancel("Setup cancelled."),process.exit(0))}function Ot(e){let t=[];e.databaseProvider==="neon"&&t.push({key:"DATABASE_URL",message:"Neon connection string (optional):",placeholder:"postgres://...",secret:!0}),e.databaseProvider==="supabase"&&t.push({key:"DATABASE_URL",message:"Supabase connection string (optional):",placeholder:"postgres://...",secret:!0}),e.cacheProvider==="upstash"&&(t.push({key:"UPSTASH_REDIS_REST_URL",message:"Upstash REST URL (optional):",placeholder:"https://...",secret:!1}),t.push({key:"UPSTASH_REDIS_REST_TOKEN",message:"Upstash REST token (optional):",secret:!0})),e.paymentProvider==="stripe"&&(t.push({key:"STRIPE_SECRET_KEY",message:"Stripe secret key (optional):",placeholder:"sk_test_...",secret:!0}),t.push({key:"STRIPE_WEBHOOK_SECRET",message:"Stripe webhook secret (optional):",placeholder:"whsec_...",secret:!0})),e.paymentProvider==="polar"&&(t.push({key:"POLAR_ACCESS_TOKEN",message:"Polar access token (optional):",secret:!0}),t.push({key:"POLAR_WEBHOOK_SECRET",message:"Polar webhook secret (optional):",secret:!0})),e.emailProvider==="resend"&&t.push({key:"RESEND_API_KEY",message:"Resend API key (optional):",placeholder:"re_...",secret:!0}),e.emailProvider==="ses"&&(t.push({key:"AMAZON_SES_REGION",message:"Amazon SES region (optional):",placeholder:"us-east-1",secret:!1}),t.push({key:"AMAZON_SES_KEY",message:"Amazon SES access key (optional):",secret:!0}),t.push({key:"AMAZON_SES_SECRET",message:"Amazon SES secret (optional):",secret:!0}));for(let r of e.socialProviders){let n=q[r];for(let o of n.envVars)t.push({key:o.name,message:`${o.name} (${n.label}, optional):`,secret:o.secret})}return e.demo&&t.push({key:"TURNSTILE_SECRET_KEY",message:"Cloudflare Turnstile secret key (optional):",secret:!0}),t}async function sr(e,t){let r=!1;u.log.info(f.bold("Project"));let n=e?.projectName??await(async()=>{r=!0;let c=await u.text({message:"Project name:",placeholder:"my-saas",validate:p=>{if(!p?.trim())return"Project name is required.";if(!pt(p))return"Use lowercase letters, numbers, and hyphens only. Must start with a letter."}});return w(c),c})(),o=e?.appName??await(async()=>{r=!0;let c=await u.text({message:"App name:",initialValue:lt(n),validate:p=>{if(!p?.trim())return"App name is required."}});return w(c),c})(),i=e?.projectDir??await(async()=>{r=!0;let c=await u.text({message:"Project location:",initialValue:`./${n}`});return w(c),c==="."?process.cwd():c})(),s=e?.frontend??await(async()=>{r=!0;let c=Object.keys(je),p=await u.select({message:"Frontend framework:",options:c.map(v=>({value:v,label:je[v].label,hint:je[v].hint}))});return w(p),p})();u.log.info(f.bold("Infrastructure"));let a=e?.deploymentTarget??"node";if(e?.deploymentTarget===void 0){r=!0;let c=await u.select({message:"Deployment target:",options:ue.map(p=>({value:p,label:H[p].label,hint:H[p].hint}))});w(c),a=c}let d=e?.architecture?Fe.find(c=>c.architecture===e.architecture&&c.target===a):void 0;if(d)throw new Error(`Incompatible: --architecture ${d.architecture} + --deploy ${d.target}. ${d.reason}`);let g=new Set(Fe.filter(c=>c.target===a).map(c=>c.architecture)),h=rt.filter(c=>!g.has(c)),m=e?.architecture??await(async()=>{if(h.length===1){let p=h[0];return u.log.info(`Auto-selected ${Ue[p].label} architecture (only compatible option for ${H[a].label}).`),p}r=!0;let c=await u.select({message:"Architecture:",options:h.map(p=>({value:p,label:Ue[p].label,hint:Ue[p].hint}))});return w(c),c})(),_=e?.databaseProvider??await(async()=>{r=!0;let c=me.filter(v=>!Me.some(N=>N.target===a&&N.provider===v));if(c.length===1){let v=c[0];return u.log.info(`Auto-selected ${$[v].label} (only compatible option for ${H[a].label}).`),v}let p=await u.select({message:"Database provider:",options:c.map(v=>({value:v,label:$[v].label,hint:$[v].hint}))});return w(p),p})(),E=e?.cacheProvider??await(async()=>{r=!0;let c=fe.filter(v=>!Me.some(N=>N.target===a&&N.provider===v));if(c.length===1){let v=c[0];return u.log.info(`Auto-selected ${Y[v].label} (only compatible option for ${H[a].label}).`),v}let p=await u.select({message:"Cache provider:",options:c.map(v=>({value:v,label:Y[v].label,hint:Y[v].hint}))});return w(p),p})();if(H[a]?.edgeRuntime){let c=ir.map(p=>` - ${p}`).join(`
3
- `);u.note(c,"Unavailable on edge runtime")}u.log.info(f.bold("Features"));let R=e?.paymentProvider??await(async()=>{r=!0;let c=await u.select({message:"Payment provider:",options:nt.map(p=>({value:p,label:Ze[p].label,hint:Ze[p].hint}))});return w(c),c})(),z=e?.defaultCurrency??await(async()=>{if(R==="none")return"USD";r=!0;let c=await u.select({message:"Default currency:",options:de.map(p=>({value:p,label:p,hint:pe[p].name}))});return w(c),c})(),D=e?.emailProvider??await(async()=>{r=!0;let c=await u.select({message:"Email provider:",options:ot.map(p=>({value:p,label:Qe[p].label,hint:Qe[p].hint}))});return w(c),c})(),ae=e?.multiTenancy??await(async()=>{r=!0;let c=await u.confirm({message:"Enable multi-tenancy (organizations)?",initialValue:!1});return w(c),c})(),ee=e?.billingScope??"user";if(ae&&e?.billingScope===void 0){r=!0;let c=await u.select({message:"Billing scope:",options:at.map(p=>({value:p,label:et[p].label,hint:et[p].hint}))});w(c),ee=c}let b=e?.blog??await(async()=>{r=!0;let c=await u.confirm({message:"Enable blog?",initialValue:!0});return w(c),c})(),y=e?.docs??await(async()=>{r=!0;let c=await u.confirm({message:"Include docs app? (self-hosted Fumadocs documentation site)",initialValue:!1});return w(c),c})(),O=t?.desktopAllowed!==!1,V=e?.desktop??(O?await(async()=>{r=!0;let c=await u.confirm({message:"Include the Electron desktop app? (apps/desktop - cross-platform, device-auth)",initialValue:!1});return w(c),c})():(u.log.info(f.dim("Desktop app: available on the Pro plan and up - skipped.")),!1)),ce=V?e?.desktopAutoRelease??await(async()=>{r=!0;let c=await u.select({message:"Desktop release trigger:",options:[{value:!1,label:"Manual only",hint:"run from the Actions tab (saves CI minutes)"},{value:!0,label:"Automatic",hint:"release on every CI success on main"}],initialValue:!1});return w(c),c})():!1,te=e?.revenueSharing??await(async()=>{r=!0;let c=await u.confirm({message:"Enable revenue sharing? (opt-in MRR leaderboard with dofollow backlinks)",initialValue:!1});return w(c),c})(),Le=R==="none"?!1:e?.credits??await(async()=>{r=!0;let c=await u.confirm({message:"Enable credits? (metered usage on top of subscription plans)",initialValue:!0});return w(c),c})(),We=e?.socialProviders??await(async()=>{r=!0;let c=ct.map(v=>({value:v,label:q[v].label,hint:`requires ${q[v].envVars.map(N=>N.name).join(" / ")}`})),p=await u.multiselect({message:"Which social login providers should the sign-in screen show?",options:c,initialValues:[],required:!1});return w(p),p})();u.log.info(f.bold("Tooling"));let Tt=e?.dockerServices??await(async()=>{r=!0;let c=[...it].filter(L=>L!=="mailpit");D==="smtp"&&c.push("mailpit");let p=c.map(L=>({value:L,label:ke[L].label,hint:ke[L].hint})),v=p.map(L=>L.value).filter(L=>!(L==="postgres"&&(_==="neon"||_==="supabase")||L==="redis"&&E==="upstash")),N=await u.multiselect({message:"Which services should we set up in Docker for you?",options:p,initialValues:v,required:!1});return w(N),N})(),Pt=e?.aiTools??await(async()=>{r=!0;let c=st.map(v=>({value:v,label:Ve[v].label})),p=await u.multiselect({message:"Which AI coding tools do you use?",options:c,initialValues:[],required:!1});return w(p),p})(),_t=e?.demo,qe=Ot({databaseProvider:_,cacheProvider:E,paymentProvider:R,emailProvider:D,socialProviders:We,demo:_t}),$e={};if(qe.length>0&&r){u.log.info(f.bold("Credentials")+f.dim(" all optional - press Enter to skip, fill in .env later"));for(let c of qe)if(r=!0,c.secret){let p=await u.password({message:c.message,mask:"*"});w(p),typeof p=="string"&&p.trim()&&($e[c.key]=p.trim())}else{let p=await u.text({message:c.message,placeholder:c.placeholder});w(p),typeof p=="string"&&p.trim()&&($e[c.key]=p.trim())}}if(r){let c=[` Name: ${f.cyan(n)}`,` App name: ${f.cyan(o)}`,` Location: ${f.cyan(i)}`,` Frontend: ${f.cyan(je[s].label)}`,` Architecture: ${f.cyan(Ue[m].label)}`].join(`
2
+ import{Command as Fs}from"commander";import*as Jn from"@clack/prompts";import{existsSync as as,readdirSync as cs,rmSync as ls}from"fs";import{join as ps,resolve as ds}from"path";import{Option as B}from"commander";import*as S from"@clack/prompts";import*as Xe from"@clack/prompts";import Rt from"picocolors";function nr(e){let t=e?` GenerateSaaS v${e} `:" GenerateSaaS ";Xe.intro(Rt.bgYellow(Rt.black(t)))}function or(){Xe.outro(Rt.yellow("Happy building!"))}import*as u from"@clack/prompts";import f from"picocolors";var Ue={nextjs:{label:"Next.js",hint:"React 19 + Next.js 16"},nuxt:{label:"Nuxt",hint:"Vue 3 + Nuxt 4"}},je={fullstack:{label:"Fullstack",hint:"Frontend hosts the API - works on serverless or long-running"},separate:{label:"Separate",hint:"Standalone Hono backend - long-running runtimes only (Docker, Render, Fly.io, Railway, Coolify, Dokploy)"}},Ze={stripe:{label:"Stripe"},polar:{label:"Polar"},none:{label:"None",hint:"disable payments"}},Qe={smtp:{label:"SMTP",hint:"Mailpit for local dev"},ses:{label:"Amazon SES"},resend:{label:"Resend"}},et={user:{label:"Per user",hint:"each user has their own subscription"},organization:{label:"Per organization",hint:"org subscription shared by members"}},ke={postgres:{label:"PostgreSQL",hint:"port 5432",port:5432},redis:{label:"Redis",hint:"port 6379",port:6379},inngest:{label:"Inngest",hint:"port 8288",port:8288},mailpit:{label:"Mailpit",hint:"port 1025",port:1025}},Ve={"claude-code":{label:"Claude Code"},cursor:{label:"Cursor"},codex:{label:"Codex"},"gemini-cli":{label:"Gemini CLI"},windsurf:{label:"Windsurf"}};var pe={USD:{symbol:"$",name:"US Dollar",place:"left",space:!1},EUR:{symbol:"\u20AC",name:"Euro",place:"right",space:!1},GBP:{symbol:"\xA3",name:"British Pound",place:"left",space:!1},CAD:{symbol:"CA$",name:"Canadian Dollar",place:"left",space:!1},AUD:{symbol:"A$",name:"Australian Dollar",place:"left",space:!1},BRL:{symbol:"R$",name:"Brazilian Real",place:"left",space:!1},JPY:{symbol:"\xA5",name:"Japanese Yen",place:"left",space:!1}};var H={node:{label:"Node.js / Docker",hint:"long-running runtime - Render, Fly.io, Railway, Coolify, Dokploy, VPS",edgeRuntime:!1},vercel:{label:"Vercel",hint:"serverless functions",edgeRuntime:!0}},$={postgres:{label:"PostgreSQL (self-hosted)",hint:"local Docker, drizzle-orm/node-postgres",managed:!1,envVars:[{key:"DATABASE_URL",defaultValue:"postgres://postgres:postgres@localhost:5432/saas"}]},neon:{label:"Neon",hint:"serverless Postgres",managed:!0,envVars:[{key:"DATABASE_URL",comment:"# TODO: Add your Neon connection string"}]},supabase:{label:"Supabase",hint:"managed Postgres",managed:!0,envVars:[{key:"DATABASE_URL",comment:"# TODO: Add your Supabase connection string"}]}},Y={redis:{label:"Redis (self-hosted)",hint:"local Docker, ioredis",managed:!1,envVars:[{key:"REDIS_URL",defaultValue:"redis://localhost:6379"}]},upstash:{label:"Upstash",hint:"serverless Redis",managed:!0,envVars:[{key:"UPSTASH_REDIS_REST_URL",comment:"# TODO: Add your Upstash REST URL"},{key:"UPSTASH_REDIS_REST_TOKEN",comment:"# TODO: Add your Upstash REST token"}]}},Me=[{target:"vercel",provider:"redis",reason:"Vercel serverless cannot maintain persistent Redis connections. Consider Upstash."}],Fe=[{architecture:"separate",target:"vercel",reason:"Standalone backends need a long-running runtime. Vercel's per-function TypeScript check conflicts with pnpm's isolated install. Use --architecture fullstack for serverless, or deploy the standalone backend to a long-running runtime (Render, Fly.io, Railway, Coolify, Dokploy, or your own VPS via Docker)."}],ir=["Local file storage (sharp, geoip-lite)","SMTP email (use Resend or SES instead)","Content API git integration"];function tt(e){let t=$[e.databaseProvider].managed,r=Y[e.cacheProvider].managed;return e.dockerServices.some(n=>!(n==="postgres"&&t||n==="redis"&&r))}var q={google:{label:"Google",envVars:[{name:"GOOGLE_CLIENT_ID",secret:!1},{name:"GOOGLE_CLIENT_SECRET",secret:!0}]},github:{label:"GitHub",envVars:[{name:"GITHUB_CLIENT_ID",secret:!1},{name:"GITHUB_CLIENT_SECRET",secret:!0}]},facebook:{label:"Facebook",envVars:[{name:"FACEBOOK_CLIENT_ID",secret:!1},{name:"FACEBOOK_CLIENT_SECRET",secret:!0}]},discord:{label:"Discord",envVars:[{name:"DISCORD_CLIENT_ID",secret:!1},{name:"DISCORD_CLIENT_SECRET",secret:!0}]},x:{label:"X",envVars:[{name:"TWITTER_CLIENT_ID",secret:!1},{name:"TWITTER_CLIENT_SECRET",secret:!0}]}};var Be=["nextjs","nuxt"],rt=["fullstack","separate"],nt=["stripe","polar","none"],ot=["smtp","ses","resend"],it=["postgres","redis","inngest","mailpit"],st=["claude-code","cursor","codex","gemini-cli","windsurf"],at=["user","organization"],de=["USD","EUR","GBP","CAD","AUD","BRL","JPY"],ue=["node","vercel"],me=["postgres","neon","supabase"],fe=["redis","upstash"],ct=["google","github","facebook","discord","x"];function lt(e){return e.split("-").map(t=>t.charAt(0).toUpperCase()+t.slice(1)).join(" ")}function pt(e){return/^[a-z][a-z0-9-]*$/.test(e)}function I(e){return e instanceof Error?e.message:String(e)}function w(e){u.isCancel(e)&&(u.cancel("Setup cancelled."),process.exit(0))}function Ot(e){let t=[];e.databaseProvider==="neon"&&t.push({key:"DATABASE_URL",message:"Neon connection string (optional):",placeholder:"postgres://...",secret:!0}),e.databaseProvider==="supabase"&&t.push({key:"DATABASE_URL",message:"Supabase connection string (optional):",placeholder:"postgres://...",secret:!0}),e.cacheProvider==="upstash"&&(t.push({key:"UPSTASH_REDIS_REST_URL",message:"Upstash REST URL (optional):",placeholder:"https://...",secret:!1}),t.push({key:"UPSTASH_REDIS_REST_TOKEN",message:"Upstash REST token (optional):",secret:!0})),e.paymentProvider==="stripe"&&(t.push({key:"STRIPE_SECRET_KEY",message:"Stripe secret key (optional):",placeholder:"sk_test_...",secret:!0}),t.push({key:"STRIPE_WEBHOOK_SECRET",message:"Stripe webhook secret (optional):",placeholder:"whsec_...",secret:!0})),e.paymentProvider==="polar"&&(t.push({key:"POLAR_ACCESS_TOKEN",message:"Polar access token (optional):",secret:!0}),t.push({key:"POLAR_WEBHOOK_SECRET",message:"Polar webhook secret (optional):",secret:!0})),e.emailProvider==="resend"&&t.push({key:"RESEND_API_KEY",message:"Resend API key (optional):",placeholder:"re_...",secret:!0}),e.emailProvider==="ses"&&(t.push({key:"AMAZON_SES_REGION",message:"Amazon SES region (optional):",placeholder:"us-east-1",secret:!1}),t.push({key:"AMAZON_SES_KEY",message:"Amazon SES access key (optional):",secret:!0}),t.push({key:"AMAZON_SES_SECRET",message:"Amazon SES secret (optional):",secret:!0}));for(let r of e.socialProviders){let n=q[r];for(let o of n.envVars)t.push({key:o.name,message:`${o.name} (${n.label}, optional):`,secret:o.secret})}return e.demo&&t.push({key:"TURNSTILE_SECRET_KEY",message:"Cloudflare Turnstile secret key (optional):",secret:!0}),t}async function sr(e,t){let r=!1;u.log.info(f.bold("Project"));let n=e?.projectName??await(async()=>{r=!0;let c=await u.text({message:"Project name:",placeholder:"my-saas",validate:p=>{if(!p?.trim())return"Project name is required.";if(!pt(p))return"Use lowercase letters, numbers, and hyphens only. Must start with a letter."}});return w(c),c})(),o=e?.appName??await(async()=>{r=!0;let c=await u.text({message:"App name:",initialValue:lt(n),validate:p=>{if(!p?.trim())return"App name is required."}});return w(c),c})(),i=e?.projectDir??await(async()=>{r=!0;let c=await u.text({message:"Project location:",initialValue:`./${n}`});return w(c),c==="."?process.cwd():c})(),s=e?.frontend??await(async()=>{r=!0;let c=Object.keys(Ue),p=await u.select({message:"Frontend framework:",options:c.map(v=>({value:v,label:Ue[v].label,hint:Ue[v].hint}))});return w(p),p})();u.log.info(f.bold("Infrastructure"));let a=e?.deploymentTarget??"node";if(e?.deploymentTarget===void 0){r=!0;let c=await u.select({message:"Deployment target:",options:ue.map(p=>({value:p,label:H[p].label,hint:H[p].hint}))});w(c),a=c}let d=e?.architecture?Fe.find(c=>c.architecture===e.architecture&&c.target===a):void 0;if(d)throw new Error(`Incompatible: --architecture ${d.architecture} + --deploy ${d.target}. ${d.reason}`);let g=new Set(Fe.filter(c=>c.target===a).map(c=>c.architecture)),h=rt.filter(c=>!g.has(c)),m=e?.architecture??await(async()=>{if(h.length===1){let p=h[0];return u.log.info(`Auto-selected ${je[p].label} architecture (only compatible option for ${H[a].label}).`),p}r=!0;let c=await u.select({message:"Architecture:",options:h.map(p=>({value:p,label:je[p].label,hint:je[p].hint}))});return w(c),c})(),_=e?.databaseProvider??await(async()=>{r=!0;let c=me.filter(v=>!Me.some(N=>N.target===a&&N.provider===v));if(c.length===1){let v=c[0];return u.log.info(`Auto-selected ${$[v].label} (only compatible option for ${H[a].label}).`),v}let p=await u.select({message:"Database provider:",options:c.map(v=>({value:v,label:$[v].label,hint:$[v].hint}))});return w(p),p})(),E=e?.cacheProvider??await(async()=>{r=!0;let c=fe.filter(v=>!Me.some(N=>N.target===a&&N.provider===v));if(c.length===1){let v=c[0];return u.log.info(`Auto-selected ${Y[v].label} (only compatible option for ${H[a].label}).`),v}let p=await u.select({message:"Cache provider:",options:c.map(v=>({value:v,label:Y[v].label,hint:Y[v].hint}))});return w(p),p})();if(H[a]?.edgeRuntime){let c=ir.map(p=>` - ${p}`).join(`
3
+ `);u.note(c,"Unavailable on edge runtime")}u.log.info(f.bold("Features"));let R=e?.paymentProvider??await(async()=>{r=!0;let c=await u.select({message:"Payment provider:",options:nt.map(p=>({value:p,label:Ze[p].label,hint:Ze[p].hint}))});return w(c),c})(),z=e?.defaultCurrency??await(async()=>{if(R==="none")return"USD";r=!0;let c=await u.select({message:"Default currency:",options:de.map(p=>({value:p,label:p,hint:pe[p].name}))});return w(c),c})(),D=e?.emailProvider??await(async()=>{r=!0;let c=await u.select({message:"Email provider:",options:ot.map(p=>({value:p,label:Qe[p].label,hint:Qe[p].hint}))});return w(c),c})(),ae=e?.multiTenancy??await(async()=>{r=!0;let c=await u.confirm({message:"Enable multi-tenancy (organizations)?",initialValue:!1});return w(c),c})(),ee=e?.billingScope??"user";if(ae&&e?.billingScope===void 0){r=!0;let c=await u.select({message:"Billing scope:",options:at.map(p=>({value:p,label:et[p].label,hint:et[p].hint}))});w(c),ee=c}let b=e?.blog??await(async()=>{r=!0;let c=await u.confirm({message:"Enable blog?",initialValue:!0});return w(c),c})(),y=e?.docs??await(async()=>{r=!0;let c=await u.confirm({message:"Include docs app? (self-hosted Fumadocs documentation site)",initialValue:!1});return w(c),c})(),O=t?.desktopAllowed!==!1,V=e?.desktop??(O?await(async()=>{r=!0;let c=await u.confirm({message:"Include the Electron desktop app? (apps/desktop - cross-platform, device-auth)",initialValue:!1});return w(c),c})():(u.log.info(f.dim("Desktop app: available on the Pro plan and up - skipped.")),!1)),ce=V?e?.desktopAutoRelease??await(async()=>{r=!0;let c=await u.select({message:"Desktop release trigger:",options:[{value:!1,label:"Manual only",hint:"run from the Actions tab (saves CI minutes)"},{value:!0,label:"Automatic",hint:"release on every CI success on main"}],initialValue:!1});return w(c),c})():!1,te=e?.revenueSharing??await(async()=>{r=!0;let c=await u.confirm({message:"Enable revenue sharing? (opt-in MRR leaderboard with dofollow backlinks)",initialValue:!1});return w(c),c})(),Le=R==="none"?!1:e?.credits??await(async()=>{r=!0;let c=await u.confirm({message:"Enable credits? (metered usage on top of subscription plans)",initialValue:!0});return w(c),c})(),We=e?.socialProviders??await(async()=>{r=!0;let c=ct.map(v=>({value:v,label:q[v].label,hint:`requires ${q[v].envVars.map(N=>N.name).join(" / ")}`})),p=await u.multiselect({message:"Which social login providers should the sign-in screen show?",options:c,initialValues:[],required:!1});return w(p),p})();u.log.info(f.bold("Tooling"));let Tt=e?.dockerServices??await(async()=>{r=!0;let c=[...it].filter(L=>L!=="mailpit");D==="smtp"&&c.push("mailpit");let p=c.map(L=>({value:L,label:ke[L].label,hint:ke[L].hint})),v=p.map(L=>L.value).filter(L=>!(L==="postgres"&&(_==="neon"||_==="supabase")||L==="redis"&&E==="upstash")),N=await u.multiselect({message:"Which services should we set up in Docker for you?",options:p,initialValues:v,required:!1});return w(N),N})(),Pt=e?.aiTools??await(async()=>{r=!0;let c=st.map(v=>({value:v,label:Ve[v].label})),p=await u.multiselect({message:"Which AI coding tools do you use?",options:c,initialValues:[],required:!1});return w(p),p})(),_t=e?.demo,qe=Ot({databaseProvider:_,cacheProvider:E,paymentProvider:R,emailProvider:D,socialProviders:We,demo:_t}),$e={};if(qe.length>0&&r){u.log.info(f.bold("Credentials")+f.dim(" all optional - press Enter to skip, fill in .env later"));for(let c of qe)if(r=!0,c.secret){let p=await u.password({message:c.message,mask:"*"});w(p),typeof p=="string"&&p.trim()&&($e[c.key]=p.trim())}else{let p=await u.text({message:c.message,placeholder:c.placeholder});w(p),typeof p=="string"&&p.trim()&&($e[c.key]=p.trim())}}if(r){let c=[` Name: ${f.cyan(n)}`,` App name: ${f.cyan(o)}`,` Location: ${f.cyan(i)}`,` Frontend: ${f.cyan(Ue[s].label)}`,` Architecture: ${f.cyan(je[m].label)}`].join(`
4
4
  `),p=[` Deploy target: ${f.cyan(H[a]?.label??"Node.js / Docker")}`,` Database: ${f.cyan($[_].label)}`,` Cache: ${f.cyan(Y[E].label)}`,Tt.length>0?` Docker: ${f.cyan(Tt.map(le=>ke[le].label).join(", "))}`:` Docker: ${f.dim("none")}`].filter(Boolean).join(`
5
5
  `),v=[R!=="none"?` Payment: ${f.cyan(Ze[R].label)} (${z})`:` Payment: ${f.dim("none")}`,` Credits: ${Le?f.cyan("Yes"):f.dim("No")}`,` Email: ${f.cyan(Qe[D].label)}`,` Multi-tenancy: ${ae?f.cyan("Yes")+` (billing: ${et[ee].label})`:f.dim("No")}`,` Blog: ${b?f.cyan("Yes"):f.dim("No")}`,` Docs app: ${y?f.cyan("Yes"):f.dim("No")}`,` Desktop app: ${V?f.cyan("Yes"):f.dim("No")}`,...V?[` Releases: ${f.cyan(ce?"Automatic on CI":"Manual only")}`]:[],` Rev. sharing: ${te?f.cyan("Yes"):f.dim("No")}`,We.length>0?` Social login: ${f.cyan(We.map(le=>q[le].label).join(", "))}`:` Social login: ${f.dim("none")}`,Pt.length>0?` AI tools: ${f.cyan(Pt.map(le=>Ve[le].label).join(", "))}`:` AI tools: ${f.dim("none")}`].join(`
6
6
  `),N=[f.bold("Project"),c,"",f.bold("Infrastructure"),p,"",f.bold("Features"),v];if(qe.length>0){let le=qe.map(rr=>{let Wn=$e[rr.key]?f.green("provided"):f.dim("skipped");return` ${rr.key}: ${Wn}`}).join(`
7
7
  `);N.push("",f.bold("Credentials"),le)}u.note(N.join(`
8
8
  `),"Summary");let L=await u.confirm({message:"Proceed with these settings?"});(u.isCancel(L)||!L)&&(u.cancel("Setup cancelled."),process.exit(0))}return{projectName:n,appName:o,projectDir:i,frontend:s,architecture:m,deploymentTarget:a,databaseProvider:_,cacheProvider:E,paymentProvider:R,emailProvider:D,multiTenancy:ae,billingScope:ee,blog:b,docs:y,desktop:V,desktopAutoRelease:ce,revenueSharing:te,credits:Le,dockerServices:Tt,aiTools:Pt,socialProviders:We,defaultCurrency:z,...Object.keys($e).length>0?{credentials:$e}:{},...e?.baseUrl!==void 0?{baseUrl:e.baseUrl}:{},..._t!==void 0?{demo:_t}:{}}}import{createReadStream as oo}from"fs";import{mkdir as io}from"fs/promises";import{Readable as so}from"stream";import{pipeline as fr}from"stream/promises";import{extract as ao}from"tar";import{join as ge}from"path";import{homedir as qn}from"os";var Ge=process.env.GENERATESAAS_API_URL??"https://cli.generatesaas.com",M=".generatesaas",re=ge(M,"manifest.json"),ar=ge(M,"hashes.json"),dt=ge(M,"template-hashes.json"),ut=ge(M,"template"),cr=ge(M,"staging"),lr=ge(M,"staging.json"),X=ge(qn(),".generatesaas");var x=class extends Error{constructor(r,n,o){super(n);this.status=r;this.body=o}status;body;name="ApiError"};function Z(e){return{apiKey:e,baseUrl:Ge}}async function ne(e,t,r){let n=`${e.baseUrl}${t}`,o=await fetch(n,{...r,headers:{...r?.headers,Authorization:`Bearer ${e.apiKey}`,"User-Agent":"generatesaas-cli"}});if(!o.ok){let i,s;try{s=await o.json(),i=s.error??`API ${o.status}: ${t}`}catch{i=`API ${o.status}: ${t}`}throw new x(o.status,i,s)}return o}import{existsSync as Xn,readFileSync as Zn,writeFileSync as Qn,mkdirSync as eo}from"fs";import{dirname as to}from"path";import*as oe from"@clack/prompts";function Ke(){if(!Xn(X))return null;try{let e=JSON.parse(Zn(X,"utf-8"));return e.apiKey?e.apiKey:(e.token&&!e.apiKey&&oe.log.warning(`Found old GitHub token in ${X}. Run 'generatesaas init' to set up your API key.`),null)}catch{return null}}function he(e){eo(to(X),{recursive:!0}),Qn(X,JSON.stringify({apiKey:e},null," ")+`
9
- `,{mode:384})}async function Ae(e){if(e?.apiKey)return e.apiKey;let t=process.env.GENERATESAAS_API_KEY;if(t)return t;let r=Ke();if(r)return r;if(!e?.prompt)throw new Error("API key not found. Set GENERATESAAS_API_KEY or run 'generatesaas init' to configure.");return ze()}async function ze(){let e=await oe.text({message:"Enter your GenerateSaaS API key:",placeholder:"gs_live_...",validate:t=>{if(!t?.trim())return"API key is required."}});return oe.isCancel(e)&&(oe.cancel("Setup cancelled."),process.exit(0)),e.trim()}async function ie(e){return process.env.GENERATESAAS_OFFLINE_LICENSE==="1"?{latest:"0.0.0-ci",versions:[{version:"0.0.0-ci",date:new Date().toISOString(),breaking:!1}],entitlements:null}:await(await ne(e,"/versions")).json()}async function xt(e,t){try{return await(await ne(e,`/changelog/${encodeURIComponent(t)}`)).text()}catch(r){if(r instanceof x&&r.status===404)return null;throw r}}async function pr(e,t){return await(await ne(e,`/skill/${encodeURIComponent(t)}`)).json()}function mt(e){if(!(e instanceof x)||e.status!==403)return null;let t=e.body;return!t||t.code!=="update_window_expired"?null:{message:e.message,lastAllowedVersion:t.lastAllowedVersion??null}}function dr(e){return{frontend:e.frontend,architecture:e.architecture,deployTarget:e.deploymentTarget,database:e.databaseProvider,cache:e.cacheProvider,payment:e.paymentProvider,email:e.emailProvider,multiTenancy:e.multiTenancy,billingScope:e.billingScope,blog:e.blog,docs:e.docs,desktop:e.desktop,credits:e.credits,revenueSharing:e.revenueSharing,socialProviders:e.socialProviders,aiTools:e.aiTools,currency:e.defaultCurrency}}async function Dt(e,t){return process.env.GENERATESAAS_OFFLINE_LICENSE==="1"?{token:"offline-test-token",licenseId:"offline-test-license-id"}:await(await ne(e,"/license/sign",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).json()}async function ur(e,t){return await(await ne(e,"/license/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).json()}async function Ct(e,t){let r=await fetch(`${e}/license/verify`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)});if(!r.ok)throw new Error(`Verification service returned ${r.status}`);return await r.json()}async function mr(e,t,r){let n=await fetch(`${e}/license/inspect`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t}`},body:JSON.stringify(r)});if(!n.ok){let o=await n.json().catch(()=>null);throw new Error(o?.error??`Inspect endpoint returned ${n.status}`)}return await n.json()}var Nt=new Set([".git","node_modules",".pnpm-store",".env",".env.test",".turbo",".nuxt",".output",".data","dist",".next",".svelte-kit",".wrangler",".devcontainer","playwright-report","test-results"]),Lt=new Set(["pnpm-lock.yaml"]);function Ie(e){if($t(e))return!0;for(let t of e.split("/"))if(Lt.has(t))return!0;return!1}var ro=new Set(["data","mksaas","references","scripts",".cursor",".agents",".codex",".generatesaas",".vscode",".mcp.json","README.md","TODO.md","OVERVIEW.md"]),no=["docs/superpowers","packages/cli","packages/cli-api","infra/docker-compose.yml",".claude/commands",".claude/skills/web-next-port",".claude/skills/web-next-port-workspace",".claude/settings.local.json",".claude/worktrees"];function $t(e){let t=e.split("/");for(let r of t)if(Nt.has(r))return!0;if(ro.has(t[0]))return!0;for(let r of no)if(e===r||e.startsWith(r+"/"))return!0;return!1}async function ft(e,t,r){await io(r,{recursive:!0});let n=process.env.GENERATESAAS_TEMPLATE_TARBALL;if(n){await fr(oo(n),gr(r));return}let o=await ne(e,`/template/${encodeURIComponent(t)}`);if(!o.body)throw new Error("Empty response body");let i=so.fromWeb(o.body);await fr(i,gr(r))}function gr(e){return ao({cwd:e,strip:1,filter:t=>{let r=t.replace(/^[^/]+\//,"");return r?!$t(r):!0},sync:!1})}import{readdir as co,readFile as jt,rm as yr,writeFile as Ut}from"fs/promises";import{join as Te}from"path";var lo=["apps/web-nuxt/public/images/blog","apps/web-next/public/images/blog","packages/content/en/blog","packages/content/ro/blog"];async function vr(e){await Promise.all(lo.map(t=>yr(Te(e,t),{recursive:!0,force:!0})))}var po="packages/config/src/blog.ts";async function Sr(e){let t=Te(e,po),r=await jt(t,"utf-8"),n=hr(hr(r,"blogCategories",t),"blogAuthors",t);await Ut(t,n)}function hr(e,t,r){let n=new RegExp(`(export const ${t}\\b[^=]*=\\s*)\\[[\\s\\S]*?\\];`);if(!n.test(e))throw new Error(`emptyBlogConfig: could not find the \`export const ${t} = [...]\` declaration in ${r}. The boilerplate blog config may have been renamed or restructured; update the CLI strip in cleanup.ts.`);return e.replace(n,"$1[];")}async function Er(e){let t=Te(e,"packages/i18n/translations"),r;try{r=await co(t)}catch{return}for(let n of r){let o=Te(t,n,"web.json"),i;try{i=await jt(o,"utf-8")}catch{continue}let s=JSON.parse(i);!s.blog||s.blog.categories===void 0||(s.blog.categories={},await Ut(o,JSON.stringify(s,null," ")+`
10
- `))}}async function wr(e,t){t.includes("claude-code")||await yr(Te(e,".claude"),{recursive:!0,force:!0})}async function br(e,t){let r=Te(e,".claude","settings.json"),n;try{n=await jt(r,"utf8")}catch{return}let o=JSON.parse(n);delete o.alwaysThinkingEnabled,delete o.enableAllProjectMcpServers,o.env&&(delete o.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS,Object.keys(o.env).length===0&&delete o.env),o.permissions?.allow&&(o.permissions.allow=o.permissions.allow.filter(i=>uo(i,t))),await Ut(r,JSON.stringify(o,null," ")+`
9
+ `,{mode:384})}async function Ae(e){if(e?.apiKey)return e.apiKey;let t=process.env.GENERATESAAS_API_KEY;if(t)return t;let r=Ke();if(r)return r;if(!e?.prompt)throw new Error("API key not found. Set GENERATESAAS_API_KEY or run 'generatesaas init' to configure.");return ze()}async function ze(){let e=await oe.text({message:"Enter your GenerateSaaS API key:",placeholder:"gs_live_...",validate:t=>{if(!t?.trim())return"API key is required."}});return oe.isCancel(e)&&(oe.cancel("Setup cancelled."),process.exit(0)),e.trim()}async function ie(e){return process.env.GENERATESAAS_OFFLINE_LICENSE==="1"?{latest:"0.0.0-ci",versions:[{version:"0.0.0-ci",date:new Date().toISOString(),breaking:!1}],entitlements:null}:await(await ne(e,"/versions")).json()}async function xt(e,t){try{return await(await ne(e,`/changelog/${encodeURIComponent(t)}`)).text()}catch(r){if(r instanceof x&&r.status===404)return null;throw r}}async function pr(e,t){return await(await ne(e,`/skill/${encodeURIComponent(t)}`)).json()}function mt(e){if(!(e instanceof x)||e.status!==403)return null;let t=e.body;return!t||t.code!=="update_window_expired"?null:{message:e.message,lastAllowedVersion:t.lastAllowedVersion??null}}function dr(e){return{frontend:e.frontend,architecture:e.architecture,deployTarget:e.deploymentTarget,database:e.databaseProvider,cache:e.cacheProvider,payment:e.paymentProvider,email:e.emailProvider,multiTenancy:e.multiTenancy,billingScope:e.billingScope,blog:e.blog,docs:e.docs,desktop:e.desktop,credits:e.credits,revenueSharing:e.revenueSharing,socialProviders:e.socialProviders,aiTools:e.aiTools,currency:e.defaultCurrency}}async function Dt(e,t){return process.env.GENERATESAAS_OFFLINE_LICENSE==="1"?{token:"offline-test-token",licenseId:"offline-test-license-id"}:await(await ne(e,"/license/sign",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).json()}async function ur(e,t){return await(await ne(e,"/license/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).json()}async function Ct(e,t){let r=await fetch(`${e}/license/verify`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)});if(!r.ok)throw new Error(`Verification service returned ${r.status}`);return await r.json()}async function mr(e,t,r){let n=await fetch(`${e}/license/inspect`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t}`},body:JSON.stringify(r)});if(!n.ok){let o=await n.json().catch(()=>null);throw new Error(o?.error??`Inspect endpoint returned ${n.status}`)}return await n.json()}var Nt=new Set([".git","node_modules",".pnpm-store",".env",".env.test",".turbo",".nuxt",".output",".data","dist",".next",".svelte-kit",".wrangler",".devcontainer","playwright-report","test-results"]),Lt=new Set(["pnpm-lock.yaml"]);function Ie(e){if($t(e))return!0;for(let t of e.split("/"))if(Lt.has(t))return!0;return!1}var ro=new Set(["data","mksaas","references","scripts",".cursor",".agents",".codex",".generatesaas",".vscode",".mcp.json","README.md","TODO.md","OVERVIEW.md"]),no=["docs/superpowers","packages/cli","packages/cli-api","infra/docker-compose.yml",".claude/commands",".claude/skills/web-next-port",".claude/skills/web-next-port-workspace",".claude/settings.local.json",".claude/worktrees"];function $t(e){let t=e.split("/");for(let r of t)if(Nt.has(r))return!0;if(ro.has(t[0]))return!0;for(let r of no)if(e===r||e.startsWith(r+"/"))return!0;return!1}async function ft(e,t,r){await io(r,{recursive:!0});let n=process.env.GENERATESAAS_TEMPLATE_TARBALL;if(n){await fr(oo(n),gr(r));return}let o=await ne(e,`/template/${encodeURIComponent(t)}`);if(!o.body)throw new Error("Empty response body");let i=so.fromWeb(o.body);await fr(i,gr(r))}function gr(e){return ao({cwd:e,strip:1,filter:t=>{let r=t.replace(/^[^/]+\//,"");return r?!$t(r):!0},sync:!1})}import{readdir as co,readFile as Ut,rm as yr,writeFile as jt}from"fs/promises";import{join as Te}from"path";var lo=["apps/web-nuxt/public/images/blog","apps/web-next/public/images/blog","packages/content/en/blog","packages/content/ro/blog"];async function vr(e){await Promise.all(lo.map(t=>yr(Te(e,t),{recursive:!0,force:!0})))}var po="packages/config/src/blog.ts";async function Sr(e){let t=Te(e,po),r=await Ut(t,"utf-8"),n=hr(hr(r,"blogCategories",t),"blogAuthors",t);await jt(t,n)}function hr(e,t,r){let n=new RegExp(`(export const ${t}\\b[^=]*=\\s*)\\[[\\s\\S]*?\\];`);if(!n.test(e))throw new Error(`emptyBlogConfig: could not find the \`export const ${t} = [...]\` declaration in ${r}. The boilerplate blog config may have been renamed or restructured; update the CLI strip in cleanup.ts.`);return e.replace(n,"$1[];")}async function Er(e){let t=Te(e,"packages/i18n/translations"),r;try{r=await co(t)}catch{return}for(let n of r){let o=Te(t,n,"web.json"),i;try{i=await Ut(o,"utf-8")}catch{continue}let s=JSON.parse(i);!s.blog||s.blog.categories===void 0||(s.blog.categories={},await jt(o,JSON.stringify(s,null," ")+`
10
+ `))}}async function wr(e,t){t.includes("claude-code")||await yr(Te(e,".claude"),{recursive:!0,force:!0})}async function br(e,t){let r=Te(e,".claude","settings.json"),n;try{n=await Ut(r,"utf8")}catch{return}let o=JSON.parse(n);delete o.alwaysThinkingEnabled,delete o.enableAllProjectMcpServers,o.env&&(delete o.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS,Object.keys(o.env).length===0&&delete o.env),o.permissions?.allow&&(o.permissions.allow=o.permissions.allow.filter(i=>uo(i,t))),await jt(r,JSON.stringify(o,null," ")+`
11
11
  `)}function uo(e,t){return!(e.startsWith("mcp__")||t!=="nuxt"&&e.includes("nuxt"))}import{join as kr}from"path";import{mkdir as mo,readdir as fo,rm as go,rmdir as ho,writeFile as yo}from"fs/promises";import{dirname as gt,join as vo,relative as So,sep as Eo}from"path";function ye(e){return e.split(Eo).join("/")}async function ht(e){await mo(e,{recursive:!0})}async function l(e,t){await ht(gt(e)),await yo(e,t,"utf-8")}async function Vt(e,t){await go(e,{force:!0});let r=gt(e);for(;r!==t&&r!==gt(r);){try{await ho(r)}catch{return}r=gt(r)}}async function ve(e,t,r){let n=[],o=await fo(e,{withFileTypes:!0});for(let i of o){let s=vo(e,i.name),a=ye(So(t,s));r(a)||(i.isDirectory()?n.push(...await ve(s,t,r)):i.isFile()&&n.push(s))}return n}var wo={postgres:"Postgres",neon:"Neon (managed Postgres)",supabase:"Supabase (managed Postgres)"},bo={resend:"Resend",ses:"Amazon SES",smtp:"SMTP"},ko={redis:"Redis",upstash:"Upstash Redis"},Ao={node:"Node.js / Docker",vercel:"Vercel"},Io={stripe:"Stripe",polar:"Polar"};function To(e){let t=e.frontend==="nuxt",r=t?"Nuxt 4":"Next.js 16",n=t?"apps/web-nuxt":"apps/web-next",o=t?"`app/` pages + components + composables":"`app/` routes, `components/`, `lib/`",i=e.architecture==="fullstack",s=i?"(fullstack - Hono API mounted inside the app)":"(separate - standalone Hono backend)",a=i?`Mounted inside \`${n}\`. A standalone \`apps/backend\` is also kept (inert) so you can switch to separate later.`:"Runs from `apps/backend`; the frontend reaches it over HTTP.",d=ko[e.cacheProvider],g=Ao[e.deploymentTarget],h=e.paymentProvider==="none"?"":`
12
12
  - **Payments:** ${Io[e.paymentProvider]}`,m=t?"`$t('key')` in templates (global helper); `useI18n()` from `vue-i18n` in `<script setup>` for `locale`/`setLocale`/`t()`.":"`useTranslations()` from `next-intl` in components; messages are loaded in `apps/web-next/i18n/request.ts`.",_=t?"**Navigation:** always use `localePath()` for paths (hardcoded paths break non-default locales); `await navigateTo()` in SSR.":"**Navigation:** `next/link` for links; `redirect()` from `next/navigation` for programmatic redirects in Server Components.";return`# AGENTS.md
13
13
 
@@ -276,6 +276,22 @@ export const config: AppConfig = {
276
276
  url: "/docs",
277
277
  contentDir: "content/docs"
278
278
  },
279
+ ai: {
280
+ enabled: false,
281
+ defaultModel: "claude-opus-4-8",
282
+ models: {
283
+ // EXAMPLE entry - set provider/modelId for your model and update the
284
+ // rates below to the provider's CURRENT pricing. These numbers are
285
+ // NOT maintained by the boilerplate.
286
+ "claude-opus-4-8": {
287
+ provider: "anthropic",
288
+ modelId: "claude-opus-4-8",
289
+ inputPerMTok: 5,
290
+ outputPerMTok: 25
291
+ }
292
+ },
293
+ credits: { creditsPerUsd: 100, margin: 1, fallbackRatePerMTok: { input: 5, output: 25 } }
294
+ },
279
295
  desktop: desktopConfig,
280
296
  seo: {
281
297
  description: "Production-ready SaaS application",
@@ -321,7 +337,8 @@ export const desktopConfig = {
321
337
  productName: "${g.productName}",
322
338
  protocol: "${g.protocol}",
323
339
  baseUrl: "${n}",
324
- autoUpdate: { provider: "generic", url: "https://cdn.${i}/desktop" }
340
+ autoUpdate: { provider: "generic", url: "https://cdn.${i}/desktop" },
341
+ agents: { enabled: true, modelRegistry: { enabled: true } }
325
342
  };
326
343
  `),await l(yt(e.projectDir,"packages/config/src/tenancy-flags.mjs"),`// Plain-JS tenancy flags. NO env reads, so tooling that cannot import the
327
344
  // TypeScript config index reads it directly (the Electron renderer). Mirrors the
@@ -455,8 +472,8 @@ ${i?" credits: { enabled: true }":" credits: { enabled: false }"},
455
472
  items: []
456
473
  }
457
474
  };
458
- `;await l(t,h)}var Lo={smtp:[{key:"SMTP_HOST",defaultValue:"localhost"},{key:"SMTP_PORT",defaultValue:"1025"}],ses:[{key:"AMAZON_SES_REGION",comment:"# TODO: Configure Amazon SES credentials (e.g. us-east-1)"},{key:"AMAZON_SES_KEY"},{key:"AMAZON_SES_SECRET"}],resend:[{key:"RESEND_API_KEY",comment:"# TODO: Add your Resend API key"}]};function $o(e){let t=q[e];return t.envVars.map((r,n)=>({key:r.name,...n===0?{comment:`# TODO: Add your ${t.label} OAuth credentials`}:{}}))}var jo={stripe:[{key:"STRIPE_SECRET_KEY",comment:"# TODO: Add your Stripe keys"},{key:"STRIPE_WEBHOOK_SECRET"}],polar:[{key:"POLAR_ACCESS_TOKEN",comment:"# TODO: Add your Polar keys"},{key:"POLAR_WEBHOOK_SECRET"}]};function Pe(e,t){return t?e.map(r=>{let n=t[r.key];return n?{...r,defaultValue:n,comment:void 0}:r}):e}function _e(e,t){for(let r of e)r.comment&&t.push(r.comment),r.defaultValue!==void 0?t.push(`${r.key}=${r.defaultValue}`):t.push(`#${r.key}=`)}function Ft(e){return Array.from(crypto.getRandomValues(new Uint8Array(e))).map(t=>t.toString(16).padStart(2,"0")).join("")}function Rr(e){return e.architecture==="fullstack"?{apiUrl:"http://localhost:3000/api",baseUrl:"http://localhost:3000"}:{apiUrl:"http://localhost:3010",baseUrl:"http://localhost:3000"}}function Uo(e){let{architecture:t,deploymentTarget:r}=e;return t==="fullstack"?r==="vercel"?{frontend:"https://your-app.vercel.app",backend:"https://your-app.vercel.app"}:r==="node"?{frontend:"https://your-app.example.com",backend:"https://your-app.example.com"}:null:{frontend:"https://your-app.example.com",backend:"https://your-app.example.com/api"}}function vt(e,t,r,n){e.push(n==="example"?`${t}=`:`${t}=${r}`)}function _r(e,t){let r=t==="example"?void 0:e.credentials,{apiUrl:n,baseUrl:o}=Rr(e),i=[];e.architecture==="separate"?i.push("# API Configuration","# Standalone backend's own URL (the frontend reaches it via the public var above).",`API_URL=${n}`,`BASE_URL=${o}`):i.push("# App","# (API_URL is derived from the frontend's *_PUBLIC_API_URL by the runtime env schema.)",`BASE_URL=${o}`),i.push("","# Database"),_e(Pe($[e.databaseProvider].envVars,r),i),i.push("","# Cache"),_e(Pe(Y[e.cacheProvider].envVars,r),i),i.push("","# Authentication"),t==="example"&&i.push("# Generate a strong secret, e.g. `openssl rand -hex 32`"),vt(i,"BETTER_AUTH_SECRET",crypto.randomUUID(),t),i.push("","# Content API (random secret; required when contentApi feature is enabled in @repo/config)"),vt(i,"CONTENT_API_KEY",Ft(32),t),i.push("","# Job Queue - Inngest","INNGEST_APP_ID=api"),vt(i,"INNGEST_EVENT_KEY",Ft(32),t),vt(i,"INNGEST_SIGNING_KEY",Ft(32),t),i.push("INNGEST_BASE_URL=http://127.0.0.1:8288"),e.architecture==="separate"&&(i.push("","# API Port (standalone backend)","API_PORT=3010"),i.push("","# CORS + cross-subdomain cookies (production only - leave unset for local dev)","# TRUSTED_ORIGINS=https://your-app.example.com","# AUTH_COOKIE_DOMAIN=.example.com"));let s=Lo[e.emailProvider];if(s&&(i.push("","# Email"),_e(Pe(s,r),i)),e.paymentProvider!=="none"){let a=jo[e.paymentProvider];a&&(i.push("","# Payment"),_e(Pe(a,r),i))}if(e.socialProviders.length>0){i.push("","# Social auth");for(let a of e.socialProviders)_e(Pe($o(a),r),i)}return e.demo&&(i.push("","# Captcha (Cloudflare Turnstile)"),_e(Pe([{key:"TURNSTILE_SECRET_KEY",comment:"# TODO: Add your Cloudflare Turnstile secret key"}],r),i)),i.push("","# Translations - OpenRouter (optional)","# Set to auto-translate en/* into your other locales with `pnpm translate`.","# The pre-commit hook runs it on commit; without a key it skips and untranslated","# locales fall back to the default locale. Get a key: https://openrouter.ai/keys","#OPENROUTER_API_KEY="),i.push(""),i.join(`
459
- `)}function Vo(e){let{apiUrl:t}=Rr(e),r=e.frontend==="nextjs"?"NEXT_PUBLIC_API_URL":"NUXT_PUBLIC_API_URL",n=["# API Configuration",`${r}=${t}`],o=Uo(e);return o&&e.architecture==="separate"&&n.push("","# Production (uncomment and replace with your deployed hostnames):",`# ${r}=${o.backend}`),n.push(""),n.join(`
475
+ `;await l(t,h)}var Lo={smtp:[{key:"SMTP_HOST",defaultValue:"localhost"},{key:"SMTP_PORT",defaultValue:"1025"}],ses:[{key:"AMAZON_SES_REGION",comment:"# TODO: Configure Amazon SES credentials (e.g. us-east-1)"},{key:"AMAZON_SES_KEY"},{key:"AMAZON_SES_SECRET"}],resend:[{key:"RESEND_API_KEY",comment:"# TODO: Add your Resend API key"}]};function $o(e){let t=q[e];return t.envVars.map((r,n)=>({key:r.name,...n===0?{comment:`# TODO: Add your ${t.label} OAuth credentials`}:{}}))}var Uo={stripe:[{key:"STRIPE_SECRET_KEY",comment:"# TODO: Add your Stripe keys"},{key:"STRIPE_WEBHOOK_SECRET"}],polar:[{key:"POLAR_ACCESS_TOKEN",comment:"# TODO: Add your Polar keys"},{key:"POLAR_WEBHOOK_SECRET"}]};function Pe(e,t){return t?e.map(r=>{let n=t[r.key];return n?{...r,defaultValue:n,comment:void 0}:r}):e}function _e(e,t){for(let r of e)r.comment&&t.push(r.comment),r.defaultValue!==void 0?t.push(`${r.key}=${r.defaultValue}`):t.push(`#${r.key}=`)}function Ft(e){return Array.from(crypto.getRandomValues(new Uint8Array(e))).map(t=>t.toString(16).padStart(2,"0")).join("")}function Rr(e){return e.architecture==="fullstack"?{apiUrl:"http://localhost:3000/api",baseUrl:"http://localhost:3000"}:{apiUrl:"http://localhost:3010",baseUrl:"http://localhost:3000"}}function jo(e){let{architecture:t,deploymentTarget:r}=e;return t==="fullstack"?r==="vercel"?{frontend:"https://your-app.vercel.app",backend:"https://your-app.vercel.app"}:r==="node"?{frontend:"https://your-app.example.com",backend:"https://your-app.example.com"}:null:{frontend:"https://your-app.example.com",backend:"https://your-app.example.com/api"}}function vt(e,t,r,n){e.push(n==="example"?`${t}=`:`${t}=${r}`)}function _r(e,t){let r=t==="example"?void 0:e.credentials,{apiUrl:n,baseUrl:o}=Rr(e),i=[];e.architecture==="separate"?i.push("# API Configuration","# Standalone backend's own URL (the frontend reaches it via the public var above).",`API_URL=${n}`,`BASE_URL=${o}`):i.push("# App","# (API_URL is derived from the frontend's *_PUBLIC_API_URL by the runtime env schema.)",`BASE_URL=${o}`),i.push("","# Database"),_e(Pe($[e.databaseProvider].envVars,r),i),i.push("","# Cache"),_e(Pe(Y[e.cacheProvider].envVars,r),i),i.push("","# Authentication"),t==="example"&&i.push("# Generate a strong secret, e.g. `openssl rand -hex 32`"),vt(i,"BETTER_AUTH_SECRET",crypto.randomUUID(),t),i.push("","# Content API (random secret; required when contentApi feature is enabled in @repo/config)"),vt(i,"CONTENT_API_KEY",Ft(32),t),i.push("","# Job Queue - Inngest","INNGEST_APP_ID=api"),vt(i,"INNGEST_EVENT_KEY",Ft(32),t),vt(i,"INNGEST_SIGNING_KEY",Ft(32),t),i.push("INNGEST_BASE_URL=http://127.0.0.1:8288"),e.architecture==="separate"&&(i.push("","# API Port (standalone backend)","API_PORT=3010"),i.push("","# CORS + cross-subdomain cookies (production only - leave unset for local dev)","# TRUSTED_ORIGINS=https://your-app.example.com","# AUTH_COOKIE_DOMAIN=.example.com"));let s=Lo[e.emailProvider];if(s&&(i.push("","# Email"),_e(Pe(s,r),i)),e.paymentProvider!=="none"){let a=Uo[e.paymentProvider];a&&(i.push("","# Payment"),_e(Pe(a,r),i))}if(e.socialProviders.length>0){i.push("","# Social auth");for(let a of e.socialProviders)_e(Pe($o(a),r),i)}return e.demo&&(i.push("","# Captcha (Cloudflare Turnstile)"),_e(Pe([{key:"TURNSTILE_SECRET_KEY",comment:"# TODO: Add your Cloudflare Turnstile secret key"}],r),i)),i.push("","# Translations - OpenRouter (optional)","# Set to auto-translate en/* into your other locales with `pnpm translate`.","# The pre-commit hook runs it on commit; without a key it skips and untranslated","# locales fall back to the default locale. Get a key: https://openrouter.ai/keys","#OPENROUTER_API_KEY="),i.push("","# AI base - provider API keys (optional)","# Enable config.ai in packages/config and set the key for your chosen provider.","# OPENROUTER_API_KEY above also works as an AI provider key.","#ANTHROPIC_API_KEY=","#OPENAI_API_KEY="),i.push(""),i.join(`
476
+ `)}function Vo(e){let{apiUrl:t}=Rr(e),r=e.frontend==="nextjs"?"NEXT_PUBLIC_API_URL":"NUXT_PUBLIC_API_URL",n=["# API Configuration",`${r}=${t}`],o=jo(e);return o&&e.architecture==="separate"&&n.push("","# Production (uncomment and replace with your deployed hostnames):",`# ${r}=${o.backend}`),n.push(""),n.join(`
460
477
  `)}async function Or(e){let t=Vo(e);await l(`${e.projectDir}/.env`,t+`
461
478
  `+_r(e,"env")),await l(`${e.projectDir}/.env.example`,t+`
462
479
  `+_r(e,"example"))}import{join as Mo}from"path";async function xr(e){let t=[...e.dockerServices];if($[e.databaseProvider].managed&&(t=t.filter(i=>i!=="postgres")),Y[e.cacheProvider].managed&&(t=t.filter(i=>i!=="redis")),t.length===0)return!1;let r=[],n=[];t.includes("postgres")&&(r.push(` postgres:
@@ -1000,7 +1017,11 @@ ${e.cacheProvider==="upstash"?` UPSTASH_REDIS_REST_URL: z.string(),
1000
1017
 
1001
1018
  TURNSTILE_SECRET_KEY: z.string().optional(),
1002
1019
 
1003
- CONTENT_API_KEY: z.string().min(16).optional()
1020
+ CONTENT_API_KEY: z.string().min(16).optional(),
1021
+
1022
+ ANTHROPIC_API_KEY: z.string().optional(),
1023
+ OPENAI_API_KEY: z.string().optional(),
1024
+ OPENROUTER_API_KEY: z.string().optional()
1004
1025
  });
1005
1026
 
1006
1027
  export const env = (() => {
@@ -1028,8 +1049,8 @@ const parsedApiUrl = new URL(env.API_URL);
1028
1049
  export const apiBasePath = parsedApiUrl.pathname === "/" ? "" : parsedApiUrl.pathname;
1029
1050
  export const apiOrigin = parsedApiUrl.origin;
1030
1051
  `}import{readFile as ni}from"fs/promises";import{join as J}from"path";var Et={"@upstash/redis":"^1.37.0","@upstash/lock":"^0.2.1","@neondatabase/serverless":"^1.0.1",postgres:"^3.4.7"};async function Oe(e){let t=await ni(e,"utf-8");return JSON.parse(t)}async function xe(e,t){await l(e,JSON.stringify(t,null," ")+`
1031
- `)}function He(e,t){for(let r of t)delete e.dependencies?.[r],delete e.devDependencies?.[r]}function Re(e,t,r,n=!1){let o=n?"devDependencies":"dependencies";e[o]||(e[o]={}),e[o][t]=r}async function jr(e){await oi(e),await ii(e),await si(e),await ai(e),e.frontend==="nextjs"?await li(e):await ci(e)}async function oi(e){let t=J(e.projectDir,"packages/api/package.json"),r=await Oe(t);He(r,["sharp","@types/sharp"]),await xe(t,r)}async function ii(e){let t=J(e.projectDir,"packages/runtime/package.json"),r=await Oe(t);e.cacheProvider==="upstash"&&(He(r,["ioredis","rate-limit-redis","redis-semaphore"]),Re(r,"@upstash/redis",Et["@upstash/redis"]),Re(r,"@upstash/lock",Et["@upstash/lock"])),await xe(t,r)}async function si(e){let t=J(e.projectDir,"packages/database/package.json"),r=await Oe(t);e.databaseProvider==="neon"?(He(r,["pg","@types/pg"]),Re(r,"@neondatabase/serverless",Et["@neondatabase/serverless"])):e.databaseProvider==="supabase"&&(He(r,["pg","@types/pg"]),Re(r,"postgres",Et.postgres)),await xe(t,r)}async function ai(e){if(e.architecture!=="separate")return;let t=J(e.projectDir,"apps/backend/package.json"),r=await Oe(t);e.deploymentTarget!=="node"&&He(r,["@hono/node-server"]),await xe(t,r)}async function ci(e){if(e.architecture!=="separate")return;let t=J(e.projectDir,"apps/web-nuxt");await Vt(J(t,"server/api/[...paths].ts"),t);let r=J(t,"package.json"),n=await Oe(r),o=n.dependencies?.["@repo/api"];o&&(delete n.dependencies?.["@repo/api"],Re(n,"@repo/api",o,!0)),await xe(r,n)}async function li(e){if(e.architecture!=="separate")return;let t=J(e.projectDir,"apps/web-next");await Vt(J(t,"app/api/[[...rest]]/route.ts"),t);let r=J(t,"package.json"),n=await Oe(r),o=n.dependencies?.["@repo/api"];o&&(delete n.dependencies?.["@repo/api"],Re(n,"@repo/api",o,!0)),await xe(r,n)}import{readFile as Vr}from"fs/promises";import{join as Mr}from"path";async function Fr(e){let t=Mr(e.projectDir,"turbo.json"),r;try{r=await Vr(t,"utf-8")}catch{return}let n=JSON.parse(r),o=n.tasks?.build;if(!o)return;let i=e.frontend==="nextjs"?"NUXT_PUBLIC_*":"NEXT_PUBLIC_*",s=e.frontend==="nextjs"?new Set([".nuxt/**",".output/**"]):new Set([".next/**","!.next/cache/**"]),a=!1;if(Array.isArray(o.env)){let d=o.env.filter(g=>g!==i);d.length!==o.env.length&&(o.env=d,a=!0)}if(Array.isArray(o.outputs)){let d=o.outputs.filter(g=>!s.has(g));d.length!==o.outputs.length&&(o.outputs=d,a=!0)}a&&await l(t,JSON.stringify(n,null," ")+`
1032
- `)}var pi=["base.json","node.json","next.json"],Ur="GenerateSaaS ";async function Br(e){if(!e.demo)for(let t of pi){let r=Mr(e.projectDir,"tooling/typescript",t),n;try{n=await Vr(r,"utf-8")}catch{continue}let o=JSON.parse(n);typeof o.display!="string"||!o.display.startsWith(Ur)||(o.display=o.display.slice(Ur.length),await l(r,JSON.stringify(o,null," ")+`
1052
+ `)}function He(e,t){for(let r of t)delete e.dependencies?.[r],delete e.devDependencies?.[r]}function Re(e,t,r,n=!1){let o=n?"devDependencies":"dependencies";e[o]||(e[o]={}),e[o][t]=r}async function Ur(e){await oi(e),await ii(e),await si(e),await ai(e),e.frontend==="nextjs"?await li(e):await ci(e)}async function oi(e){let t=J(e.projectDir,"packages/api/package.json"),r=await Oe(t);He(r,["sharp","@types/sharp"]),await xe(t,r)}async function ii(e){let t=J(e.projectDir,"packages/runtime/package.json"),r=await Oe(t);e.cacheProvider==="upstash"&&(He(r,["ioredis","rate-limit-redis","redis-semaphore"]),Re(r,"@upstash/redis",Et["@upstash/redis"]),Re(r,"@upstash/lock",Et["@upstash/lock"])),await xe(t,r)}async function si(e){let t=J(e.projectDir,"packages/database/package.json"),r=await Oe(t);e.databaseProvider==="neon"?(He(r,["pg","@types/pg"]),Re(r,"@neondatabase/serverless",Et["@neondatabase/serverless"])):e.databaseProvider==="supabase"&&(He(r,["pg","@types/pg"]),Re(r,"postgres",Et.postgres)),await xe(t,r)}async function ai(e){if(e.architecture!=="separate")return;let t=J(e.projectDir,"apps/backend/package.json"),r=await Oe(t);e.deploymentTarget!=="node"&&He(r,["@hono/node-server"]),await xe(t,r)}async function ci(e){if(e.architecture!=="separate")return;let t=J(e.projectDir,"apps/web-nuxt");await Vt(J(t,"server/api/[...paths].ts"),t);let r=J(t,"package.json"),n=await Oe(r),o=n.dependencies?.["@repo/api"];o&&(delete n.dependencies?.["@repo/api"],Re(n,"@repo/api",o,!0)),await xe(r,n)}async function li(e){if(e.architecture!=="separate")return;let t=J(e.projectDir,"apps/web-next");await Vt(J(t,"app/api/[[...rest]]/route.ts"),t);let r=J(t,"package.json"),n=await Oe(r),o=n.dependencies?.["@repo/api"];o&&(delete n.dependencies?.["@repo/api"],Re(n,"@repo/api",o,!0)),await xe(r,n)}import{readFile as Vr}from"fs/promises";import{join as Mr}from"path";async function Fr(e){let t=Mr(e.projectDir,"turbo.json"),r;try{r=await Vr(t,"utf-8")}catch{return}let n=JSON.parse(r),o=n.tasks?.build;if(!o)return;let i=e.frontend==="nextjs"?"NUXT_PUBLIC_*":"NEXT_PUBLIC_*",s=e.frontend==="nextjs"?new Set([".nuxt/**",".output/**"]):new Set([".next/**","!.next/cache/**"]),a=!1;if(Array.isArray(o.env)){let d=o.env.filter(g=>g!==i);d.length!==o.env.length&&(o.env=d,a=!0)}if(Array.isArray(o.outputs)){let d=o.outputs.filter(g=>!s.has(g));d.length!==o.outputs.length&&(o.outputs=d,a=!0)}a&&await l(t,JSON.stringify(n,null," ")+`
1053
+ `)}var pi=["base.json","node.json","next.json"],jr="GenerateSaaS ";async function Br(e){if(!e.demo)for(let t of pi){let r=Mr(e.projectDir,"tooling/typescript",t),n;try{n=await Vr(r,"utf-8")}catch{continue}let o=JSON.parse(n);typeof o.display!="string"||!o.display.startsWith(jr)||(o.display=o.display.slice(jr.length),await l(r,JSON.stringify(o,null," ")+`
1033
1054
  `))}}import{readFile as di,rm as ui}from"fs/promises";import{existsSync as Gr}from"fs";import{join as Kr}from"path";async function zr(e){if(e.frontend==="nuxt")return;let t=Kr(e.projectDir,"packages/i18n/package.json");if(!Gr(t))throw new Error(`pruneI18nNuxt: expected ${t} to exist - did the i18n package move?`);let r=JSON.parse(await di(t,"utf-8")),n=!!(r.exports?.["./module"]??r.exports?.["./nuxt"]),o=!1;if(r.exports)for(let s of["./module","./nuxt"])s in r.exports&&(delete r.exports[s],o=!0);r.devDependencies&&"@nuxt/kit"in r.devDependencies&&(delete r.devDependencies["@nuxt/kit"],o=!0),o&&await l(t,JSON.stringify(r,null," ")+`
1034
1055
  `);let i=Kr(e.projectDir,"packages/i18n/nuxt");if(n&&!Gr(i))throw new Error(`pruneI18nNuxt: packages/i18n declares a Nuxt export surface but ${i} is missing - did the i18n Nuxt module move?`);await ui(i,{recursive:!0,force:!0})}import{readFile as Hr,rm as mi}from"fs/promises";import{join as Gt}from"path";async function Yr(e){e.cacheProvider==="upstash"&&await Promise.all([fi(e.projectDir),gi(e.projectDir),mi(Gt(e.projectDir,"packages/runtime/tests/redis.test.ts"),{force:!0})])}async function fi(e){let t=Gt(e,"packages/runtime/tests/setup.ts"),r;try{r=await Hr(t,"utf-8")}catch{return}let n=r.replace(/\tREDIS_URL:\s*"[^"]*",?\n/,` UPSTASH_REDIS_REST_URL: "https://test.upstash.io",
1035
1056
  UPSTASH_REDIS_REST_TOKEN: "test-token",
@@ -1054,8 +1075,8 @@ vi.mock("@upstash/lock", () => {
1054
1075
  return { Lock };
1055
1076
  });
1056
1077
 
1057
- $1`),n!==r&&await l(t,n)}import{readdir as Jr,readFile as hi,rm as Q}from"fs/promises";import{join as j}from"path";var yi=new Set(["ci.yml","desktop-release.yml"]),vi=["cli","cli:clean","demo:bench","playground:regen","playground:test","playground:test:units","playground:test:build"];async function Wr(e){let t=j(e.projectDir,".github/workflows"),r=await Jr(t).catch(()=>[]);for(let n of r)yi.has(n)||await Q(j(t,n),{recursive:!0,force:!0})}async function qr(e){let t=j(e.projectDir,"package.json"),r=await hi(t,"utf-8"),n=JSON.parse(r),o=!1;if(n.scripts){for(let i of vi)i in n.scripts&&(delete n.scripts[i],o=!0);if(!tt(e))for(let i of["infra","infra:stop"])i in n.scripts&&(delete n.scripts[i],o=!0)}o&&await l(t,JSON.stringify(n,null," ")+`
1058
- `)}async function Xr(e){let t=e.frontend==="nextjs"?"apps/web-nuxt":"apps/web-next";await Q(j(e.projectDir,t),{recursive:!0,force:!0})}async function Zr(e){e.docs||await Q(j(e.projectDir,"apps/docs"),{recursive:!0})}async function Qr(e){let t=e.frontend==="nextjs"?"docs/nuxt":"docs/next";await Q(j(e.projectDir,t),{recursive:!0,force:!0}),await Q(j(e.projectDir,"docs/index.mdx"))}async function en(e){if(e.desktop)return;await Q(j(e.projectDir,"apps/desktop"),{recursive:!0});let t=j(e.projectDir,"packages/i18n/translations");for(let r of await Jr(t,{withFileTypes:!0}))r.isDirectory()&&await Q(j(t,r.name,"desktop.json"),{force:!0});await Q(j(e.projectDir,".github/workflows/desktop-release.yml"))}async function tn(e){e.frontend==="nextjs"||e.desktop||await Q(j(e.projectDir,"packages/ui-next"),{recursive:!0})}import{readFile as Si}from"fs/promises";import{join as Ei}from"path";var rn=`on:
1078
+ $1`),n!==r&&await l(t,n)}import{readdir as Jr,readFile as hi,rm as Q}from"fs/promises";import{join as U}from"path";var yi=new Set(["ci.yml","desktop-release.yml"]),vi=["cli","cli:clean","demo:bench","playground:regen","playground:test","playground:test:units","playground:test:build"];async function Wr(e){let t=U(e.projectDir,".github/workflows"),r=await Jr(t).catch(()=>[]);for(let n of r)yi.has(n)||await Q(U(t,n),{recursive:!0,force:!0})}async function qr(e){let t=U(e.projectDir,"package.json"),r=await hi(t,"utf-8"),n=JSON.parse(r),o=!1;if(n.scripts){for(let i of vi)i in n.scripts&&(delete n.scripts[i],o=!0);if(!tt(e))for(let i of["infra","infra:stop"])i in n.scripts&&(delete n.scripts[i],o=!0)}o&&await l(t,JSON.stringify(n,null," ")+`
1079
+ `)}async function Xr(e){let t=e.frontend==="nextjs"?"apps/web-nuxt":"apps/web-next";await Q(U(e.projectDir,t),{recursive:!0,force:!0})}async function Zr(e){e.docs||await Q(U(e.projectDir,"apps/docs"),{recursive:!0})}async function Qr(e){let t=e.frontend==="nextjs"?"docs/nuxt":"docs/next";await Q(U(e.projectDir,t),{recursive:!0,force:!0}),await Q(U(e.projectDir,"docs/index.mdx"))}async function en(e){if(e.desktop)return;await Q(U(e.projectDir,"apps/desktop"),{recursive:!0});let t=U(e.projectDir,"packages/i18n/translations");for(let r of await Jr(t,{withFileTypes:!0}))r.isDirectory()&&await Q(U(t,r.name,"desktop.json"),{force:!0});await Q(U(e.projectDir,".github/workflows/desktop-release.yml"))}async function tn(e){e.frontend==="nextjs"||e.desktop||await Q(U(e.projectDir,"packages/ui-next"),{recursive:!0})}import{readFile as Si}from"fs/promises";import{join as Ei}from"path";var rn=`on:
1059
1080
  workflow_run:
1060
1081
  workflows: ["CI"]
1061
1082
  branches: [main]
@@ -1155,18 +1176,18 @@ ${o} env:
1155
1176
  export { ops } from "./${r}/index";
1156
1177
  `;await l(wt(t,"providers/index.ts"),n),await l(wt(t,"index.ts"),await Di(t,r))}async function Di(e,t){let r=wt(e,"index.ts");if(!Oi(r))throw new Error(`generatePaymentBarrel: expected ${r} to exist - did packages/payments move?`);let n=await Ri(r,"utf-8"),o=xi.filter(s=>s!==t);return n.split(`
1157
1178
  `).filter(s=>!o.some(a=>s.includes(`./providers/${a}/`))).join(`
1158
- `)}import{readdir as Ci,readFile as Ni}from"fs/promises";import{join as pn}from"path";async function dn(e){if(e.demo)return;let t=e.appName.trim()||e.projectName,r=JSON.stringify(t).slice(1,-1),n=pn(e.projectDir,"packages/i18n/translations"),o;try{o=await Ci(n)}catch{return}for(let i of o){let s=pn(n,i,"web.json"),a;try{a=await Ni(s,"utf-8")}catch{continue}let d=a.replaceAll("GenerateSaaS",r);d!==a&&await l(s,d)}}import{readFile as Li}from"fs/promises";import{join as un}from"path";var $i=[".nuxt/",".nuxt",".nitro/",".nitro",".output/",".output","_locales/"],ji=[".next/",".next",".svelte-kit/",".svelte-kit",".wrangler/",".wrangler",".dev.vars"];async function fn(e){let r=e.frontend==="nuxt"?ji:$i;await mn(un(e.projectDir,".gitignore"),r),await mn(un(e.projectDir,".dockerignore"),r)}async function mn(e,t){let r;try{r=await Li(e,"utf-8")}catch{return}let n=new Set(t),o=r.split(`
1179
+ `)}import{readdir as Ci,readFile as Ni}from"fs/promises";import{join as pn}from"path";async function dn(e){if(e.demo)return;let t=e.appName.trim()||e.projectName,r=JSON.stringify(t).slice(1,-1),n=pn(e.projectDir,"packages/i18n/translations"),o;try{o=await Ci(n)}catch{return}for(let i of o){let s=pn(n,i,"web.json"),a;try{a=await Ni(s,"utf-8")}catch{continue}let d=a.replaceAll("GenerateSaaS",r);d!==a&&await l(s,d)}}import{readFile as Li}from"fs/promises";import{join as un}from"path";var $i=[".nuxt/",".nuxt",".nitro/",".nitro",".output/",".output","_locales/"],Ui=[".next/",".next",".svelte-kit/",".svelte-kit",".wrangler/",".wrangler",".dev.vars"];async function fn(e){let r=e.frontend==="nuxt"?Ui:$i;await mn(un(e.projectDir,".gitignore"),r),await mn(un(e.projectDir,".dockerignore"),r)}async function mn(e,t){let r;try{r=await Li(e,"utf-8")}catch{return}let n=new Set(t),o=r.split(`
1159
1180
  `).filter(i=>!n.has(i.trim()));o.length!==r.split(`
1160
1181
  `).length&&await l(e,o.join(`
1161
- `))}async function bt(e){let t=e.projectDir;e.demo||(await vr(t),await Sr(t),await Er(t)),await wr(t,e.aiTools),await br(t,e.frontend),await Ar(e),await Ir(e),await Tr(e),e.demo||await Pr(e),await Or(e);let r=await xr(e);return await Dr(e),await Cr(e),await Nr(e),await Lr(e),await $r(e),await jr(e),await Fr(e),await Br(e),await zr(e),await Yr(e),await Wr(e),await on(e),await qr(e),await Xr(e),await Zr(e),await Qr(e),await en(e),await nn(e),await tn(e),await cn(e),await ln(e),await dn(e),await fn(e),{dockerComposeGenerated:r}}import{basename as hn,join as yn,relative as Vi}from"path";import{createHash as gn}from"crypto";import{readFile as Ui}from"fs/promises";async function kt(e){let t=await Ui(e);return gn("sha256").update(t).digest("hex")}function zt(e){return gn("sha256").update(e).digest("hex")}var Mi=new Set(["data",M]);function Fi(e){let t=e.split("/");for(let r of t)if(Nt.has(r)||Lt.has(r)||Mi.has(r)||r.startsWith(".env")&&!r.includes("example"))return!0;return!1}function vn(e,t){return{projectName:e.projectName??hn(t),appName:e.appName??e.projectName??hn(t),projectDir:t,frontend:e.frontend==="nextjs"?"nextjs":"nuxt",architecture:e.architecture??"fullstack",paymentProvider:e.paymentProvider??"none",emailProvider:e.emailProvider??"smtp",multiTenancy:e.multiTenancy??!1,billingScope:e.billingScope??"user",blog:e.blog??!1,docs:e.docs??!1,desktop:e.desktop??!1,desktopAutoRelease:e.desktopAutoRelease??!0,revenueSharing:e.revenueSharing??!1,credits:e.credits??!1,dockerServices:e.dockerServices??[],aiTools:e.aiTools??[],socialProviders:e.socialProviders??[],defaultCurrency:e.defaultCurrency??"USD",deploymentTarget:e.deploymentTarget??"node",databaseProvider:e.databaseProvider??"postgres",cacheProvider:e.cacheProvider??"redis",version:e.version,baseUrl:e.baseUrl,credentials:{},demo:!1}}async function Sn(e,t){let n=(await ve(e.projectDir,e.projectDir,Fi)).sort(),o=await Promise.all(n.map(async a=>[ye(Vi(e.projectDir,a)),await kt(a)])),i=Object.fromEntries(o),s={version:e.version,initialVersion:e.version,repo:"Duzbee/GenerateSaaS",appName:e.appName,projectName:e.projectName,frontend:e.frontend,architecture:e.architecture,paymentProvider:e.paymentProvider,emailProvider:e.emailProvider,multiTenancy:e.multiTenancy,billingScope:e.billingScope,blog:e.blog,docs:e.docs,desktop:e.desktop,desktopAutoRelease:e.desktopAutoRelease,credits:e.credits,revenueSharing:e.revenueSharing,defaultCurrency:e.defaultCurrency,dockerServices:e.dockerServices,socialProviders:e.socialProviders,deploymentTarget:e.deploymentTarget,databaseProvider:e.databaseProvider,cacheProvider:e.cacheProvider,aiTools:e.aiTools,...e.baseUrl?{baseUrl:e.baseUrl}:{},...t&&{licenseToken:t.token,licenseKeyHash:t.keyHash,installId:t.installId}};await l(yn(e.projectDir,re),JSON.stringify(s,null," ")+`
1182
+ `))}async function bt(e){let t=e.projectDir;e.demo||(await vr(t),await Sr(t),await Er(t)),await wr(t,e.aiTools),await br(t,e.frontend),await Ar(e),await Ir(e),await Tr(e),e.demo||await Pr(e),await Or(e);let r=await xr(e);return await Dr(e),await Cr(e),await Nr(e),await Lr(e),await $r(e),await Ur(e),await Fr(e),await Br(e),await zr(e),await Yr(e),await Wr(e),await on(e),await qr(e),await Xr(e),await Zr(e),await Qr(e),await en(e),await nn(e),await tn(e),await cn(e),await ln(e),await dn(e),await fn(e),{dockerComposeGenerated:r}}import{basename as hn,join as yn,relative as Vi}from"path";import{createHash as gn}from"crypto";import{readFile as ji}from"fs/promises";async function kt(e){let t=await ji(e);return gn("sha256").update(t).digest("hex")}function zt(e){return gn("sha256").update(e).digest("hex")}var Mi=new Set(["data",M]);function Fi(e){let t=e.split("/");for(let r of t)if(Nt.has(r)||Lt.has(r)||Mi.has(r)||r.startsWith(".env")&&!r.includes("example"))return!0;return!1}function vn(e,t){return{projectName:e.projectName??hn(t),appName:e.appName??e.projectName??hn(t),projectDir:t,frontend:e.frontend==="nextjs"?"nextjs":"nuxt",architecture:e.architecture??"fullstack",paymentProvider:e.paymentProvider??"none",emailProvider:e.emailProvider??"smtp",multiTenancy:e.multiTenancy??!1,billingScope:e.billingScope??"user",blog:e.blog??!1,docs:e.docs??!1,desktop:e.desktop??!1,desktopAutoRelease:e.desktopAutoRelease??!0,revenueSharing:e.revenueSharing??!1,credits:e.credits??!1,dockerServices:e.dockerServices??[],aiTools:e.aiTools??[],socialProviders:e.socialProviders??[],defaultCurrency:e.defaultCurrency??"USD",deploymentTarget:e.deploymentTarget??"node",databaseProvider:e.databaseProvider??"postgres",cacheProvider:e.cacheProvider??"redis",version:e.version,baseUrl:e.baseUrl,credentials:{},demo:!1}}async function Sn(e,t){let n=(await ve(e.projectDir,e.projectDir,Fi)).sort(),o=await Promise.all(n.map(async a=>[ye(Vi(e.projectDir,a)),await kt(a)])),i=Object.fromEntries(o),s={version:e.version,initialVersion:e.version,repo:"Duzbee/GenerateSaaS",appName:e.appName,projectName:e.projectName,frontend:e.frontend,architecture:e.architecture,paymentProvider:e.paymentProvider,emailProvider:e.emailProvider,multiTenancy:e.multiTenancy,billingScope:e.billingScope,blog:e.blog,docs:e.docs,desktop:e.desktop,desktopAutoRelease:e.desktopAutoRelease,credits:e.credits,revenueSharing:e.revenueSharing,defaultCurrency:e.defaultCurrency,dockerServices:e.dockerServices,socialProviders:e.socialProviders,deploymentTarget:e.deploymentTarget,databaseProvider:e.databaseProvider,cacheProvider:e.cacheProvider,aiTools:e.aiTools,...e.baseUrl?{baseUrl:e.baseUrl}:{},...t&&{licenseToken:t.token,licenseKeyHash:t.keyHash,installId:t.installId}};await l(yn(e.projectDir,re),JSON.stringify(s,null," ")+`
1162
1183
  `),await l(yn(e.projectDir,ar),JSON.stringify(i,null," ")+`
1163
1184
  `)}import{relative as Bi}from"path";async function De(e){let r=(await ve(e,e,Ie)).sort(),n=await Promise.all(r.map(async o=>[ye(Bi(e,o)),await kt(o)]));return Object.fromEntries(n)}import{copyFile as Gi,mkdir as Ki,rm as zi}from"fs/promises";import{dirname as Hi,join as En,relative as Yi}from"path";import{existsSync as Ji}from"fs";async function Ht(e,t){Ji(t)&&await zi(t,{recursive:!0,force:!0});let r=await ve(e,e,Ie);for(let n of r){let o=ye(Yi(e,n)),i=En(t,o);await Ki(Hi(i),{recursive:!0}),await Gi(n,i)}}async function wn(e,t){await Ht(e,En(t,ut))}import{existsSync as Wi}from"fs";import{readFile as bn,readdir as qi}from"fs/promises";import{join as W,dirname as Xi,resolve as Zi,sep as Qi}from"path";import{fileURLToPath as es}from"url";var Ye={"claude-code":".claude/skills",cursor:".cursor/skills",codex:".agents/skills","gemini-cli":".gemini/skills",windsurf:".windsurf/skills"},cp=Object.values(Ye),Yt="generatesaas-update",kn=Xi(es(import.meta.url));function ts(){let e=W(kn,"skill","content");return Wi(e)?e:W(kn,"content")}function Jt(e){return!e||e.length===0?[]:e.map(t=>Ye[t])}async function Wt(e,t,r,n){let o=Jt(n);for(let i of o){let s=W(e,i,Yt),a=W(s,"scripts"),d=W(s,"references");await ht(a),await ht(d),await l(W(s,"SKILL.md"),t.replaceAll("__SKILL_ROOT__",i)),await l(W(d,".gitkeep"),"");for(let[g,h]of Object.entries(r)){let m=Zi(a,g);m.startsWith(a+Qi)&&await l(m,h)}}}async function An(e,t){let r=ts(),n=await bn(W(r,"SKILL.md"),"utf-8"),o=W(r,"scripts"),i=await qi(o),s={};for(let a of i)a!==".gitkeep"&&(s[a]=await bn(W(o,a),"utf-8"));await Wt(e,n,s,t)}import{execFile as rs,execFileSync as ns}from"child_process";import{access as In,readFile as os}from"fs/promises";import{join as qt}from"path";import*as T from"@clack/prompts";function Ee(e){try{let t=process.platform==="win32"?"where":"which";return ns(t,[e],{stdio:"ignore"}),!0}catch{return!1}}function Se(e,t,r,n=3e5){return new Promise((o,i)=>{rs(e,t,{cwd:r,timeout:n},(s,a,d)=>{if(s){let g=String(a||"").trim(),m=[String(d||"").trim(),g].filter(Boolean).join(`
1164
1185
  `);i(new Error(m?`${s.message}
1165
1186
  ${m}`:s.message))}else o()})})}async function Tn(e){if(!Ee("pnpm"))return T.log.warn("pnpm not found. Skipping lockfile regeneration."),!1;try{return await Se("pnpm",["install","--lockfile-only","--no-frozen-lockfile","--config.minimumReleaseAge=0"],e),!0}catch(t){let r=t instanceof Error?t.message:String(t);return T.log.warn(`Lockfile regeneration failed: ${r}`),T.log.warn("Deploys using --frozen-lockfile may fail."),!1}}async function Pn(e){if(!Ee("pnpm"))return T.log.warn("pnpm not found. Skipping dependency installation."),T.log.info("Install pnpm: https://pnpm.io/installation"),!1;let t=T.spinner();t.start("Installing dependencies (this may take a minute)...");try{return await Se("pnpm",["install","--config.minimumReleaseAge=0"],e),t.stop("Dependencies installed."),!0}catch(r){t.stop("Dependency installation failed.");let n=r instanceof Error?r.message:String(r);return T.log.warn(`pnpm install failed: ${n}`),T.log.warn("You can run it manually later."),!1}}async function _n(e){if(!Ee("pnpm"))return!1;let t=T.spinner();t.start("Generating baseline database migration...");try{return await Se("pnpm",["-F","@repo/database","generate"],e),t.stop("Baseline migration generated."),!0}catch(r){t.stop("Baseline migration generation failed.");let n=r instanceof Error?r.message:String(r);return T.log.warn(`Could not generate baseline migration: ${n}`),T.log.warn("Run 'pnpm -F @repo/database generate' before your first deploy."),!1}}async function Rn(e){try{return await In(qt(e,".git")),T.log.info("Git repository already exists, skipping init."),!0}catch{}if(!Ee("git"))return T.log.warn("git not found. Skipping repository initialization."),!1;let t=T.spinner();t.start("Initializing git repository...");try{return await Se("git",["init"],e),await Se("git",["add","-A"],e),await Se("git",["commit","--no-verify","-m","Initial commit from GenerateSaaS"],e),t.stop("Git repository initialized."),!0}catch{return t.stop("Git initialization failed."),T.log.warn("You can run git init manually later."),!1}}async function On(e){if(!Ee("pnpm"))return!1;try{await In(qt(e,".git"))}catch{return!1}try{let t=JSON.parse(await os(qt(e,"package.json"),"utf-8")),r=!!t.devDependencies?.["simple-git-hooks"],n=!!t["simple-git-hooks"];if(!r||!n)return!1}catch{return!1}try{return await Se("pnpm",["exec","simple-git-hooks"],e),!0}catch{return T.log.warn("Could not install git hooks. Run 'pnpm exec simple-git-hooks' manually."),!1}}import*as Ce from"@clack/prompts";import F from"picocolors";function xn(e,t){t.dockerComposeGenerated&&!t.dockerAvailable&&Ce.log.warn("Docker not found. Install Docker to run local services: https://docs.docker.com/get-docker/");let r=[];if(r.push(`cd ${e.projectDir}`),t.pnpmInstalled||r.push("pnpm install"),t.dockerComposeGenerated){let i=e.dockerServices.map(s=>ke[s].label).join(", ");r.push(`pnpm infra ${F.dim(`# ${i}`)}`)}if(r.push(`pnpm dev ${F.dim("# http://localhost:3000")}`),t.skippedCredentials.length>0&&(r.push(""),r.push(F.dim("Fill in remaining TODO values in .env"))),Ce.note(r.join(`
1166
1187
  `),F.yellow("Start Development")),t.dockerComposeGenerated){let i=[];i.push(`App ${F.cyan("http://localhost:3000")}`),e.architecture==="separate"&&i.push(`API ${F.cyan("http://localhost:3010")}`),e.dockerServices.includes("mailpit")&&i.push(`Mailpit ${F.cyan("http://localhost:8025")}`),e.dockerServices.includes("inngest")&&i.push(`Inngest ${F.cyan("http://localhost:8288")}`),Ce.note(i.join(`
1167
1188
  `),F.yellow("Dev Tools"))}let n=[],o=is(e);o.length>0&&n.push(`Set in production: ${F.dim(o.join(", "))}`),n.push("pnpm db:push # Run database migrations"),n.push(ss(e)),Ce.note(n.join(`
1168
- `),F.yellow("Deployment"))}function is(e){let t=["DATABASE_URL","BETTER_AUTH_SECRET"];return e.cacheProvider==="upstash"?t.push("UPSTASH_REDIS_REST_URL","UPSTASH_REDIS_REST_TOKEN"):t.push("REDIS_URL"),e.paymentProvider==="stripe"?t.push("STRIPE_SECRET_KEY","STRIPE_WEBHOOK_SECRET"):e.paymentProvider==="polar"&&t.push("POLAR_ACCESS_TOKEN","POLAR_WEBHOOK_SECRET"),e.emailProvider==="ses"?t.push("AMAZON_SES_REGION","AMAZON_SES_KEY","AMAZON_SES_SECRET"):e.emailProvider==="resend"?t.push("RESEND_API_KEY"):t.push("SMTP_HOST","SMTP_PORT"),t}function ss(e){switch(e.deploymentTarget){case"node":return"Deploy with Docker or your preferred Node.js host";case"vercel":return"vercel deploy # Deploy to Vercel"}}function Dn(e){let t={};if(e.name!==void 0){if(!pt(e.name))throw new Error(`Invalid project name "${e.name}". Use lowercase letters, numbers, and hyphens only. Must start with a letter.`);t.projectName=e.name}if(e.appName!==void 0){if(!e.appName.trim())throw new Error("App name cannot be empty.");t.appName=e.appName}if(e.location!==void 0?t.projectDir=e.location==="."?process.cwd():e.location:t.projectName!==void 0&&(t.projectDir=`./${t.projectName}`),e.frontend!==void 0){if(!Be.includes(e.frontend))throw new Error(`Invalid frontend "${e.frontend}". Valid values: ${Be.join(", ")}`);t.frontend=e.frontend}if(e.architecture!==void 0&&(t.architecture=e.architecture),e.payment!==void 0&&(t.paymentProvider=e.payment),e.email!==void 0&&(t.emailProvider=e.email),e.org!==void 0&&(t.multiTenancy=e.org),e.billingScope!==void 0){if(e.org===!1)throw new Error("--billing-scope requires --org to be enabled.");t.billingScope=e.billingScope}if(e.blog!==void 0&&(t.blog=e.blog),e.docs!==void 0&&(t.docs=e.docs),e.desktop!==void 0&&(t.desktop=e.desktop),e.desktopAuto!==void 0&&(t.desktopAutoRelease=e.desktopAuto),e.revenueSharing!==void 0&&(t.revenueSharing=e.revenueSharing),e.credits!==void 0){if(e.credits===!0&&e.payment==="none")throw new Error("--credits requires a payment provider (got --payment none).");t.credits=e.credits}if(e.docker!==void 0&&(t.dockerServices=Xt(e.docker,it,"docker service")),e.aiTools!==void 0&&(t.aiTools=Xt(e.aiTools,st,"AI tool")),e.socialProviders!==void 0&&(t.socialProviders=Xt(e.socialProviders,ct,"social provider")),e.currency!==void 0){if(!de.includes(e.currency))throw new Error(`Invalid currency "${e.currency}". Valid values: ${de.join(", ")}`);t.defaultCurrency=e.currency}if(e.deploy!==void 0){if(!ue.includes(e.deploy))throw new Error(`Invalid deployment target "${e.deploy}". Valid values: ${ue.join(", ")}`);t.deploymentTarget=e.deploy}if(e.database!==void 0){if(!me.includes(e.database))throw new Error(`Invalid database provider "${e.database}". Valid values: ${me.join(", ")}`);t.databaseProvider=e.database}if(e.cache!==void 0){if(!fe.includes(e.cache))throw new Error(`Invalid cache provider "${e.cache}". Valid values: ${fe.join(", ")}`);t.cacheProvider=e.cache}if(e.demo===!0&&(t.demo=!0),e.baseUrl!==void 0){let r=e.baseUrl.trim();if(r==="")throw new Error("--base-url cannot be empty. Provide an absolute URL like https://example.com.");let n;try{n=new URL(r)}catch{throw new Error(`Invalid --base-url "${e.baseUrl}". Must be an absolute URL like https://example.com.`)}if(n.protocol!=="http:"&&n.protocol!=="https:")throw new Error(`Invalid --base-url "${e.baseUrl}". Must use http or https.`);t.baseUrl=`${n.protocol}//${n.host}`}return t}var se={projectName:"my-saas",frontend:"nextjs",architecture:"fullstack",paymentProvider:"stripe",emailProvider:"smtp",multiTenancy:!1,billingScope:"user",blog:!0,docs:!1,desktop:!1,desktopAutoRelease:!1,revenueSharing:!1,credits:!0,dockerServices:["postgres","redis","inngest"],aiTools:[],socialProviders:[],defaultCurrency:"USD",deploymentTarget:"node",databaseProvider:"postgres",cacheProvider:"redis"};function Cn(e){let t=e.projectName??se.projectName,r=e.projectDir??`./${t}`,n=e.appName??lt(t),o=e.deploymentTarget??se.deploymentTarget,i=H[o]?.edgeRuntime??!1,s=e.databaseProvider??(i?"neon":se.databaseProvider),a=e.cacheProvider??(i?"upstash":se.cacheProvider),d=e.emailProvider??(i?"resend":se.emailProvider),g=e.dockerServices??(i?se.dockerServices.filter(m=>m!=="postgres"&&m!=="redis"):se.dockerServices),h={...se,...e,projectName:t,appName:n,projectDir:r,deploymentTarget:o,databaseProvider:s,cacheProvider:a,emailProvider:d,dockerServices:g};h.paymentProvider==="none"&&(h.credits=!1);for(let m of Me){if(h.deploymentTarget!==m.target)continue;let _=h.databaseProvider===m.provider?"database":"cache";if(h.databaseProvider===m.provider||h.cacheProvider===m.provider)throw new Error(`Incompatible: --deploy ${m.target} + --${_} ${m.provider}. ${m.reason}`)}for(let m of Fe)if(h.architecture===m.architecture&&h.deploymentTarget===m.target)throw new Error(`Incompatible: --architecture ${m.architecture} + --deploy ${m.target}. ${m.reason}`);return h}function Xt(e,t,r){if(e.trim()==="")return[];let n=e.split(",").map(i=>i.trim()).filter(Boolean),o=n.filter(i=>!t.includes(i));if(o.length>0)throw new Error(`Invalid ${r}(s): ${o.join(", ")}. Valid values: ${t.join(", ")}`);return n}import us from"picocolors";var ms="a10a6fb9d7cadde32e37dad52059d17b5d2b916b08c76d8fbcc99982e9a3d87f";function fs(e){if(e===void 0)return;let t=e.trim().replace(/^v/,"");if(!/^\d+\.\d+\.\d+$/.test(t))throw new Error(`Invalid template version "${e}". Use semver like 1.2.3.`);return t}function Nn(e){e.command("init").description("Scaffold a new GenerateSaaS project").argument("[apiKey]","license key (same as --api-key)").option("-n, --name <name>","project name (lowercase, hyphens, starts with letter)").option("--app-name <name>","display name for the app").option("-l, --location <path>","project directory (default: ./{name})").addOption(new B("--frontend <type>","frontend framework").choices([...Be])).addOption(new B("--architecture <type>","fullstack or separate").choices([...rt])).addOption(new B("--payment <provider>","payment provider").choices([...nt])).addOption(new B("--email <provider>","email provider").choices([...ot])).option("--org","enable multi-tenancy (organizations)").option("--no-org","disable multi-tenancy").addOption(new B("--billing-scope <scope>","billing scope (requires --org)").choices([...at])).option("--blog","enable blog").option("--no-blog","disable blog").option("--docs","include the docs app (apps/docs, Fumadocs)").option("--no-docs","exclude the docs app").option("--desktop","include the Electron desktop app (apps/desktop)").option("--no-desktop","exclude the Electron desktop app").option("--desktop-auto","auto-trigger the desktop release workflow on CI success (default: manual-only)").option("--no-desktop-auto","manual-only desktop releases (workflow_dispatch)").option("--revenue-sharing","enable revenue sharing").option("--no-revenue-sharing","disable revenue sharing").option("--credits","enable credits system").option("--no-credits","disable credits system (subscription-only)").option("--docker <services>","comma-separated: postgres,redis,inngest,mailpit").option("--ai-tools <tools>","comma-separated: claude-code,cursor,codex,gemini-cli,windsurf").option("--social-providers <providers>","comma-separated: google,github,facebook,discord,x").addOption(new B("--currency <code>","default currency for billing").choices([...de])).addOption(new B("--deploy <target>","deployment target").choices([...ue])).addOption(new B("--database <provider>","database provider").choices([...me])).addOption(new B("--cache <provider>","cache provider").choices([...fe])).option("--template-version <version>","specific template version to scaffold").option("--api-key <key>","API key (skips interactive prompt)").option("--base-url <url>","public base URL (e.g. https://example.com) - bakes into canonical/og/sitemap").option("-y, --yes","accept defaults for unspecified options (non-interactive)").addOption(new B("--demo","first-party demo build: keep sample content, mark site non-indexable - requires CI API key").hideHelp()).addOption(new B("--no-db-migration","skip generating the baseline DB migration (internal: demos/CI/playground)").hideHelp()).action(async(t,r)=>{await gs(t?{...r,apiKey:t}:r)})}async function gs(e){let t=performance.now();nr("1.16.0");let r,n;try{r=Dn(e),n=fs(e.templateVersion)}catch(y){S.cancel(I(y)),process.exit(1)}let o=S.spinner(),i;try{i=await Ae({apiKey:e.apiKey,prompt:!e.yes})}catch(y){S.cancel(I(y)),process.exit(1)}e.demo&&zt(i)!==ms&&(S.cancel("--demo is restricted to first-party demo deployments."),process.exit(1));let s=Z(i),a=async()=>{let y=await ie(s),O=y.latest,V=n??O;if(n&&!y.versions.some(ce=>ce.version===V))throw new Error(`Template version "${n}" is not available.`);return{latestVersion:O,selectedVersion:V,desktopAllowed:y.entitlements?.desktopAllowed??!0}};o.start("Verifying access...");let d,g,h=!0;try{({latestVersion:d,selectedVersion:g,desktopAllowed:h}=await a()),o.stop("Access verified."),he(i)}catch(y){if(o.stop("Access verification failed."),y instanceof x&&y.status===401){e.yes&&(S.cancel("Invalid API key. Cannot prompt in non-interactive mode."),process.exit(1)),S.log.warning("Invalid API key."),i=await ze(),s=Z(i),o.start("Verifying access...");try{({latestVersion:d,selectedVersion:g,desktopAllowed:h}=await a()),o.stop("Access verified."),he(i)}catch(O){o.stop("Access verification failed."),S.cancel(O instanceof x&&O.status===401?"Invalid API key.":I(O)),process.exit(1)}}else S.cancel(I(y)),process.exit(1)}S.log.success(`Latest version: ${d}`),g!==d&&S.log.success(`Using template version: ${g}`),r.desktop===!0&&!h&&(S.cancel("The desktop app is available on the Pro plan and up. Upgrade your plan at generatesaas.com."),process.exit(1));let m;e.yes?m=Cn(h?r:{...r,desktop:!1}):m=await sr(r,{desktopAllowed:h});let _;o.start("Activating license...");try{let y=crypto.randomUUID(),O=()=>({frontend:m.frontend,version:g,installId:y,projectName:m.projectName,options:dr(m)}),V;try{V=await Dt(s,O())}catch(ce){let te=mt(ce);if(!te?.lastAllowedVersion)throw ce;o.stop("License activation failed."),e.yes&&(S.cancel(`${te.message} Re-run with --template-version ${te.lastAllowedVersion}.`),process.exit(1));let Le=await S.confirm({message:`Your update window has ended. Continue with v${te.lastAllowedVersion} (the last version your license covers)?`});(S.isCancel(Le)||!Le)&&(S.cancel("Setup cancelled."),process.exit(0)),g=te.lastAllowedVersion,o.start(`Activating license for v${g}...`),V=await Dt(s,O())}_={token:V.token,keyHash:zt(i),installId:y},o.stop("License activated.")}catch(y){o.stop("License activation failed."),S.cancel(I(y)),process.exit(1)}let E=ds(m.projectDir);if(as(E)&&cs(E).length>0)if(e.yes)S.log.info(`Directory ${E} is not empty. Merging (keeping existing files, overwriting conflicts).`);else{let O=await S.select({message:`Directory ${E} is not empty.`,options:[{value:"merge",label:"Merge",hint:"keep existing files, overwrite conflicts"},{value:"overwrite",label:"Overwrite",hint:"delete everything and start fresh"},{value:"cancel",label:"Cancel"}]});(S.isCancel(O)||O==="cancel")&&(S.cancel("Setup cancelled."),process.exit(0)),O==="overwrite"&&ls(E,{recursive:!0,force:!0})}let R={...m,projectDir:E,version:g,...e.demo?{docs:!1}:{}};o.start("Downloading template...");try{await ft(s,g,E),o.stop("Template downloaded.")}catch(y){o.stop("Download failed."),S.cancel(I(y)),process.exit(1)}let z;o.start("Generating project files...");try{if({dockerComposeGenerated:z}=await bt(R),!e.demo){let y=await De(E);await l(ps(E,dt),JSON.stringify(y,null," ")+`
1169
- `),await wn(E,E)}await An(E,R.aiTools),await Sn(R,_),o.stop("Project files generated.")}catch(y){o.stop("Generation failed."),S.cancel(I(y)),process.exit(1)}await Tn(E);let D=await Pn(E);D&&R.demo!==!0&&e.dbMigration!==!1&&await _n(E),await Rn(E),D&&await On(E);let ae=Ee("docker"),b=Ot(R).map(y=>y.key).filter(y=>!R.credentials?.[y]);xn(R,{pnpmInstalled:D,dockerComposeGenerated:z,dockerAvailable:ae,skippedCredentials:b}),or(),S.log.info(us.dim(`Done in ${((performance.now()-t)/1e3).toFixed(1)}s`))}import{existsSync as $n}from"fs";import{readFile as jn}from"fs/promises";import{join as Je,resolve as Es}from"path";import*as P from"@clack/prompts";import Ne from"picocolors";import{mkdtemp as hs,rm as ys}from"fs/promises";import{tmpdir as vs}from"os";import{join as Ss}from"path";async function Zt(e,t,r,n){let o=await hs(Ss(vs(),"generatesaas-stage-"));try{await ft(e,t,o),await bt({...r,projectDir:o}),await Ht(o,n)}finally{await ys(o,{recursive:!0,force:!0})}}function Ln(e){let r=(e.startsWith("v")?e.slice(1):e).match(/^(\d+)\.(\d+)\.(\d+)$/);return r?[Number(r[1]),Number(r[2]),Number(r[3])]:null}function At(e,t){let r=Ln(e),n=Ln(t);if(!r||!n)return 0;for(let o=0;o<3;o++)if(r[o]!==n[o])return r[o]-n[o];return 0}function Un(e){e.command("update").description("Update AI skill files and stage template updates").option("--cwd <path>","project directory (default: current directory)").action(async t=>{let r=Es(t.cwd??process.cwd()),n=Je(r,re),o;try{o=JSON.parse(await jn(n,"utf-8"))}catch{P.cancel(".generatesaas/manifest.json not found. Run this from a GenerateSaaS project."),process.exit(1)}let i;try{i=await Ae()}catch(d){P.cancel(I(d)),process.exit(1)}let s=Z(i),a=P.spinner();try{a.start("Verifying access...");let d;try{d=await ie(s)}catch(b){throw b instanceof x&&b.status===401?new Error("Your saved API key was rejected. Run `generatesaas auth` to update it, or set GENERATESAAS_API_KEY."):b}a.stop("Access verified."),he(i),a.start("Fetching latest skill files...");let g=await pr(s,d.latest);await Wt(r,g.skillMd,g.scripts,o.aiTools);let h=Jt(o.aiTools);if(a.stop("Skills updated."),P.log.success(`Skill files installed to ${Ne.cyan(h.length.toString())} locations.`),o.version===d.latest){P.log.info(`Already on the latest version (${o.version}).`);return}if(o.licenseToken)try{let b=await ur(s,{currentToken:o.licenseToken,newVersion:d.latest});o.licenseToken=b.token,b.licenseKeyHash&&(o.licenseKeyHash=b.licenseKeyHash),await l(n,JSON.stringify(o,null," ")+`
1189
+ `),F.yellow("Deployment"))}function is(e){let t=["DATABASE_URL","BETTER_AUTH_SECRET"];return e.cacheProvider==="upstash"?t.push("UPSTASH_REDIS_REST_URL","UPSTASH_REDIS_REST_TOKEN"):t.push("REDIS_URL"),e.paymentProvider==="stripe"?t.push("STRIPE_SECRET_KEY","STRIPE_WEBHOOK_SECRET"):e.paymentProvider==="polar"&&t.push("POLAR_ACCESS_TOKEN","POLAR_WEBHOOK_SECRET"),e.emailProvider==="ses"?t.push("AMAZON_SES_REGION","AMAZON_SES_KEY","AMAZON_SES_SECRET"):e.emailProvider==="resend"?t.push("RESEND_API_KEY"):t.push("SMTP_HOST","SMTP_PORT"),t}function ss(e){switch(e.deploymentTarget){case"node":return"Deploy with Docker or your preferred Node.js host";case"vercel":return"vercel deploy # Deploy to Vercel"}}function Dn(e){let t={};if(e.name!==void 0){if(!pt(e.name))throw new Error(`Invalid project name "${e.name}". Use lowercase letters, numbers, and hyphens only. Must start with a letter.`);t.projectName=e.name}if(e.appName!==void 0){if(!e.appName.trim())throw new Error("App name cannot be empty.");t.appName=e.appName}if(e.location!==void 0?t.projectDir=e.location==="."?process.cwd():e.location:t.projectName!==void 0&&(t.projectDir=`./${t.projectName}`),e.frontend!==void 0){if(!Be.includes(e.frontend))throw new Error(`Invalid frontend "${e.frontend}". Valid values: ${Be.join(", ")}`);t.frontend=e.frontend}if(e.architecture!==void 0&&(t.architecture=e.architecture),e.payment!==void 0&&(t.paymentProvider=e.payment),e.email!==void 0&&(t.emailProvider=e.email),e.org!==void 0&&(t.multiTenancy=e.org),e.billingScope!==void 0){if(e.org===!1)throw new Error("--billing-scope requires --org to be enabled.");t.billingScope=e.billingScope}if(e.blog!==void 0&&(t.blog=e.blog),e.docs!==void 0&&(t.docs=e.docs),e.desktop!==void 0&&(t.desktop=e.desktop),e.desktopAuto!==void 0&&(t.desktopAutoRelease=e.desktopAuto),e.revenueSharing!==void 0&&(t.revenueSharing=e.revenueSharing),e.credits!==void 0){if(e.credits===!0&&e.payment==="none")throw new Error("--credits requires a payment provider (got --payment none).");t.credits=e.credits}if(e.docker!==void 0&&(t.dockerServices=Xt(e.docker,it,"docker service")),e.aiTools!==void 0&&(t.aiTools=Xt(e.aiTools,st,"AI tool")),e.socialProviders!==void 0&&(t.socialProviders=Xt(e.socialProviders,ct,"social provider")),e.currency!==void 0){if(!de.includes(e.currency))throw new Error(`Invalid currency "${e.currency}". Valid values: ${de.join(", ")}`);t.defaultCurrency=e.currency}if(e.deploy!==void 0){if(!ue.includes(e.deploy))throw new Error(`Invalid deployment target "${e.deploy}". Valid values: ${ue.join(", ")}`);t.deploymentTarget=e.deploy}if(e.database!==void 0){if(!me.includes(e.database))throw new Error(`Invalid database provider "${e.database}". Valid values: ${me.join(", ")}`);t.databaseProvider=e.database}if(e.cache!==void 0){if(!fe.includes(e.cache))throw new Error(`Invalid cache provider "${e.cache}". Valid values: ${fe.join(", ")}`);t.cacheProvider=e.cache}if(e.demo===!0&&(t.demo=!0),e.baseUrl!==void 0){let r=e.baseUrl.trim();if(r==="")throw new Error("--base-url cannot be empty. Provide an absolute URL like https://example.com.");let n;try{n=new URL(r)}catch{throw new Error(`Invalid --base-url "${e.baseUrl}". Must be an absolute URL like https://example.com.`)}if(n.protocol!=="http:"&&n.protocol!=="https:")throw new Error(`Invalid --base-url "${e.baseUrl}". Must use http or https.`);t.baseUrl=`${n.protocol}//${n.host}`}return t}var se={projectName:"my-saas",frontend:"nextjs",architecture:"fullstack",paymentProvider:"stripe",emailProvider:"smtp",multiTenancy:!1,billingScope:"user",blog:!0,docs:!1,desktop:!1,desktopAutoRelease:!1,revenueSharing:!1,credits:!0,dockerServices:["postgres","redis","inngest"],aiTools:[],socialProviders:[],defaultCurrency:"USD",deploymentTarget:"node",databaseProvider:"postgres",cacheProvider:"redis"};function Cn(e){let t=e.projectName??se.projectName,r=e.projectDir??`./${t}`,n=e.appName??lt(t),o=e.deploymentTarget??se.deploymentTarget,i=H[o]?.edgeRuntime??!1,s=e.databaseProvider??(i?"neon":se.databaseProvider),a=e.cacheProvider??(i?"upstash":se.cacheProvider),d=e.emailProvider??(i?"resend":se.emailProvider),g=e.dockerServices??(i?se.dockerServices.filter(m=>m!=="postgres"&&m!=="redis"):se.dockerServices),h={...se,...e,projectName:t,appName:n,projectDir:r,deploymentTarget:o,databaseProvider:s,cacheProvider:a,emailProvider:d,dockerServices:g};h.paymentProvider==="none"&&(h.credits=!1);for(let m of Me){if(h.deploymentTarget!==m.target)continue;let _=h.databaseProvider===m.provider?"database":"cache";if(h.databaseProvider===m.provider||h.cacheProvider===m.provider)throw new Error(`Incompatible: --deploy ${m.target} + --${_} ${m.provider}. ${m.reason}`)}for(let m of Fe)if(h.architecture===m.architecture&&h.deploymentTarget===m.target)throw new Error(`Incompatible: --architecture ${m.architecture} + --deploy ${m.target}. ${m.reason}`);return h}function Xt(e,t,r){if(e.trim()==="")return[];let n=e.split(",").map(i=>i.trim()).filter(Boolean),o=n.filter(i=>!t.includes(i));if(o.length>0)throw new Error(`Invalid ${r}(s): ${o.join(", ")}. Valid values: ${t.join(", ")}`);return n}import us from"picocolors";var ms="a10a6fb9d7cadde32e37dad52059d17b5d2b916b08c76d8fbcc99982e9a3d87f";function fs(e){if(e===void 0)return;let t=e.trim().replace(/^v/,"");if(!/^\d+\.\d+\.\d+$/.test(t))throw new Error(`Invalid template version "${e}". Use semver like 1.2.3.`);return t}function Nn(e){e.command("init").description("Scaffold a new GenerateSaaS project").argument("[apiKey]","license key (same as --api-key)").option("-n, --name <name>","project name (lowercase, hyphens, starts with letter)").option("--app-name <name>","display name for the app").option("-l, --location <path>","project directory (default: ./{name})").addOption(new B("--frontend <type>","frontend framework").choices([...Be])).addOption(new B("--architecture <type>","fullstack or separate").choices([...rt])).addOption(new B("--payment <provider>","payment provider").choices([...nt])).addOption(new B("--email <provider>","email provider").choices([...ot])).option("--org","enable multi-tenancy (organizations)").option("--no-org","disable multi-tenancy").addOption(new B("--billing-scope <scope>","billing scope (requires --org)").choices([...at])).option("--blog","enable blog").option("--no-blog","disable blog").option("--docs","include the docs app (apps/docs, Fumadocs)").option("--no-docs","exclude the docs app").option("--desktop","include the Electron desktop app (apps/desktop)").option("--no-desktop","exclude the Electron desktop app").option("--desktop-auto","auto-trigger the desktop release workflow on CI success (default: manual-only)").option("--no-desktop-auto","manual-only desktop releases (workflow_dispatch)").option("--revenue-sharing","enable revenue sharing").option("--no-revenue-sharing","disable revenue sharing").option("--credits","enable credits system").option("--no-credits","disable credits system (subscription-only)").option("--docker <services>","comma-separated: postgres,redis,inngest,mailpit").option("--ai-tools <tools>","comma-separated: claude-code,cursor,codex,gemini-cli,windsurf").option("--social-providers <providers>","comma-separated: google,github,facebook,discord,x").addOption(new B("--currency <code>","default currency for billing").choices([...de])).addOption(new B("--deploy <target>","deployment target").choices([...ue])).addOption(new B("--database <provider>","database provider").choices([...me])).addOption(new B("--cache <provider>","cache provider").choices([...fe])).option("--template-version <version>","specific template version to scaffold").option("--api-key <key>","API key (skips interactive prompt)").option("--base-url <url>","public base URL (e.g. https://example.com) - bakes into canonical/og/sitemap").option("-y, --yes","accept defaults for unspecified options (non-interactive)").addOption(new B("--demo","first-party demo build: keep sample content, mark site non-indexable - requires CI API key").hideHelp()).addOption(new B("--no-db-migration","skip generating the baseline DB migration (internal: demos/CI/playground)").hideHelp()).action(async(t,r)=>{await gs(t?{...r,apiKey:t}:r)})}async function gs(e){let t=performance.now();nr("1.18.0");let r,n;try{r=Dn(e),n=fs(e.templateVersion)}catch(y){S.cancel(I(y)),process.exit(1)}let o=S.spinner(),i;try{i=await Ae({apiKey:e.apiKey,prompt:!e.yes})}catch(y){S.cancel(I(y)),process.exit(1)}e.demo&&zt(i)!==ms&&(S.cancel("--demo is restricted to first-party demo deployments."),process.exit(1));let s=Z(i),a=async()=>{let y=await ie(s),O=y.latest,V=n??O;if(n&&!y.versions.some(ce=>ce.version===V))throw new Error(`Template version "${n}" is not available.`);return{latestVersion:O,selectedVersion:V,desktopAllowed:y.entitlements?.desktopAllowed??!0}};o.start("Verifying access...");let d,g,h=!0;try{({latestVersion:d,selectedVersion:g,desktopAllowed:h}=await a()),o.stop("Access verified."),he(i)}catch(y){if(o.stop("Access verification failed."),y instanceof x&&y.status===401){e.yes&&(S.cancel("Invalid API key. Cannot prompt in non-interactive mode."),process.exit(1)),S.log.warning("Invalid API key."),i=await ze(),s=Z(i),o.start("Verifying access...");try{({latestVersion:d,selectedVersion:g,desktopAllowed:h}=await a()),o.stop("Access verified."),he(i)}catch(O){o.stop("Access verification failed."),S.cancel(O instanceof x&&O.status===401?"Invalid API key.":I(O)),process.exit(1)}}else S.cancel(I(y)),process.exit(1)}S.log.success(`Latest version: ${d}`),g!==d&&S.log.success(`Using template version: ${g}`),r.desktop===!0&&!h&&(S.cancel("The desktop app is available on the Pro plan and up. Upgrade your plan at generatesaas.com."),process.exit(1));let m;e.yes?m=Cn(h?r:{...r,desktop:!1}):m=await sr(r,{desktopAllowed:h});let _;o.start("Activating license...");try{let y=crypto.randomUUID(),O=()=>({frontend:m.frontend,version:g,installId:y,projectName:m.projectName,options:dr(m)}),V;try{V=await Dt(s,O())}catch(ce){let te=mt(ce);if(!te?.lastAllowedVersion)throw ce;o.stop("License activation failed."),e.yes&&(S.cancel(`${te.message} Re-run with --template-version ${te.lastAllowedVersion}.`),process.exit(1));let Le=await S.confirm({message:`Your update window has ended. Continue with v${te.lastAllowedVersion} (the last version your license covers)?`});(S.isCancel(Le)||!Le)&&(S.cancel("Setup cancelled."),process.exit(0)),g=te.lastAllowedVersion,o.start(`Activating license for v${g}...`),V=await Dt(s,O())}_={token:V.token,keyHash:zt(i),installId:y},o.stop("License activated.")}catch(y){o.stop("License activation failed."),S.cancel(I(y)),process.exit(1)}let E=ds(m.projectDir);if(as(E)&&cs(E).length>0)if(e.yes)S.log.info(`Directory ${E} is not empty. Merging (keeping existing files, overwriting conflicts).`);else{let O=await S.select({message:`Directory ${E} is not empty.`,options:[{value:"merge",label:"Merge",hint:"keep existing files, overwrite conflicts"},{value:"overwrite",label:"Overwrite",hint:"delete everything and start fresh"},{value:"cancel",label:"Cancel"}]});(S.isCancel(O)||O==="cancel")&&(S.cancel("Setup cancelled."),process.exit(0)),O==="overwrite"&&ls(E,{recursive:!0,force:!0})}let R={...m,projectDir:E,version:g,...e.demo?{docs:!1}:{}};o.start("Downloading template...");try{await ft(s,g,E),o.stop("Template downloaded.")}catch(y){o.stop("Download failed."),S.cancel(I(y)),process.exit(1)}let z;o.start("Generating project files...");try{if({dockerComposeGenerated:z}=await bt(R),!e.demo){let y=await De(E);await l(ps(E,dt),JSON.stringify(y,null," ")+`
1190
+ `),await wn(E,E)}await An(E,R.aiTools),await Sn(R,_),o.stop("Project files generated.")}catch(y){o.stop("Generation failed."),S.cancel(I(y)),process.exit(1)}await Tn(E);let D=await Pn(E);D&&R.demo!==!0&&e.dbMigration!==!1&&await _n(E),await Rn(E),D&&await On(E);let ae=Ee("docker"),b=Ot(R).map(y=>y.key).filter(y=>!R.credentials?.[y]);xn(R,{pnpmInstalled:D,dockerComposeGenerated:z,dockerAvailable:ae,skippedCredentials:b}),or(),S.log.info(us.dim(`Done in ${((performance.now()-t)/1e3).toFixed(1)}s`))}import{existsSync as $n}from"fs";import{readFile as Un}from"fs/promises";import{join as Je,resolve as Es}from"path";import*as P from"@clack/prompts";import Ne from"picocolors";import{mkdtemp as hs,rm as ys}from"fs/promises";import{tmpdir as vs}from"os";import{join as Ss}from"path";async function Zt(e,t,r,n){let o=await hs(Ss(vs(),"generatesaas-stage-"));try{await ft(e,t,o),await bt({...r,projectDir:o}),await Ht(o,n)}finally{await ys(o,{recursive:!0,force:!0})}}function Ln(e){let r=(e.startsWith("v")?e.slice(1):e).match(/^(\d+)\.(\d+)\.(\d+)$/);return r?[Number(r[1]),Number(r[2]),Number(r[3])]:null}function At(e,t){let r=Ln(e),n=Ln(t);if(!r||!n)return 0;for(let o=0;o<3;o++)if(r[o]!==n[o])return r[o]-n[o];return 0}function jn(e){e.command("update").description("Update AI skill files and stage template updates").option("--cwd <path>","project directory (default: current directory)").action(async t=>{let r=Es(t.cwd??process.cwd()),n=Je(r,re),o;try{o=JSON.parse(await Un(n,"utf-8"))}catch{P.cancel(".generatesaas/manifest.json not found. Run this from a GenerateSaaS project."),process.exit(1)}let i;try{i=await Ae()}catch(d){P.cancel(I(d)),process.exit(1)}let s=Z(i),a=P.spinner();try{a.start("Verifying access...");let d;try{d=await ie(s)}catch(b){throw b instanceof x&&b.status===401?new Error("Your saved API key was rejected. Run `generatesaas auth` to update it, or set GENERATESAAS_API_KEY."):b}a.stop("Access verified."),he(i),a.start("Fetching latest skill files...");let g=await pr(s,d.latest);await Wt(r,g.skillMd,g.scripts,o.aiTools);let h=Jt(o.aiTools);if(a.stop("Skills updated."),P.log.success(`Skill files installed to ${Ne.cyan(h.length.toString())} locations.`),o.version===d.latest){P.log.info(`Already on the latest version (${o.version}).`);return}if(o.licenseToken)try{let b=await ur(s,{currentToken:o.licenseToken,newVersion:d.latest});o.licenseToken=b.token,b.licenseKeyHash&&(o.licenseKeyHash=b.licenseKeyHash),await l(n,JSON.stringify(o,null," ")+`
1170
1191
  `),P.log.success("License refreshed.")}catch(b){let y=mt(b);y&&(P.cancel(y.message),process.exit(1)),P.log.warn("License refresh skipped.")}let m=vn(o,r),_=Je(r,cr);a.start(`Staging v${d.latest} (shaped for your config)...`),await Zt(s,d.latest,m,_),a.stop("Template staged.");let{text:E,title:R}=await ws(s,d,o.version);E&&P.note(E,R);let z=Je(r,dt),D=Je(r,ut),ae=!$n(D),ee=!$n(z);if(ae){if(a.start("Building baseline template (one-time migration)..."),await Zt(s,o.version,m,D),ee){let b=await De(D);await l(z,JSON.stringify(b,null," ")+`
1171
1192
  `)}if(a.stop("Baseline template stored."),!ee){let b=await bs(z,D);b>0&&P.log.warn(`Rebuilt baseline differs from the original for ${b} file(s) (the CLI's shaping evolved since this project was scaffolded). Classification still follows the committed template-hashes.json; upstream diffs for those files may include unrelated noise.`)}}else if(ee){a.start("Computing baseline template hashes...");let b=await De(D);await l(z,JSON.stringify(b,null," ")+`
1172
1193
  `),a.stop("Baseline hashes computed.")}if(await l(Je(r,lr),JSON.stringify({currentVersion:o.version,targetVersion:d.latest,changelog:E,stagedAt:new Date().toISOString()},null," ")+`
@@ -1174,19 +1195,19 @@ ${m}`:s.message))}else o()})})}async function Tn(e){if(!Ee("pnpm"))return T.log.
1174
1195
 
1175
1196
  ${a??"_No changelog available for this release._"}`)}return{text:i.join(`
1176
1197
 
1177
- `),title:`Changelog v${r} \u2192 v${n}`}}async function bs(e,t){let r=JSON.parse(await jn(e,"utf-8")),n=await De(t),o=0;for(let[i,s]of Object.entries(r))Ie(i)||n[i]!==s&&o++;for(let i of Object.keys(n))i in r||o++;return o}import*as U from"@clack/prompts";import G from"picocolors";import{readFile as ks}from"fs/promises";import{join as As,resolve as Is}from"path";function Vn(e){e.command("status").description("Show project status and check for updates").option("--cwd <path>","project directory (default: current directory)").action(async t=>{let r=Is(t.cwd??process.cwd()),n=As(r,re),o;try{o=JSON.parse(await ks(n,"utf-8"))}catch{U.cancel(".generatesaas/manifest.json not found. Run this from a GenerateSaaS project."),process.exit(1)}let i=[`Version: ${G.cyan(o.version)}`,`Frontend: ${G.cyan(o.frontend)}`,o.deploymentTarget?`Deploy target: ${G.cyan(o.deploymentTarget)}`:null,o.databaseProvider?`Database: ${G.cyan(o.databaseProvider)}`:null,o.cacheProvider?`Cache: ${G.cyan(o.cacheProvider)}`:null,o.aiTools&&o.aiTools.length>0?`AI tools: ${G.cyan(o.aiTools.join(", "))}`:null].filter(Boolean).join(`
1178
- `);U.note(i,G.bold("Project Status"));let s=U.spinner();s.start("Checking for updates...");try{let a=await Ae(),d=Z(a),h=(await ie(d)).latest;o.version===h?(s.stop("Up to date."),U.log.success(`Already on the latest version (${G.green(h)})`)):(s.stop("Update available."),U.log.warning(`Update available: ${G.yellow(o.version)} \u2192 ${G.green(h)}`),U.log.info(`Open this project in your AI coding agent and ask it to ${G.cyan("update my GenerateSaaS project")} - it fetches and applies the update for you.`))}catch(a){s.stop("Check failed."),a instanceof x&&a.status===401?U.log.warning("Invalid API key. Run `generatesaas auth` to update it, or set GENERATESAAS_API_KEY."):U.log.warning(`Could not check for updates: ${I(a)}`)}})}import{readFile as Ts}from"fs/promises";import*as A from"@clack/prompts";import k from"picocolors";function Ps(){return process.env.GENERATESAAS_API_KEY??Ke()}function _s(e){return{verdict:e.verdict,plan:e.license?.plan??e.domainInstalls.find(t=>t.ownerPlan)?.ownerPlan??null,mismatchDomain:e.install?.domain??null,ejectedAt:e.install?.ejectedAt??e.domainInstalls.find(t=>t.ejectedAt)?.ejectedAt??null}}function Rs(e){return{verdict:e.verdict??"unknown",ejectedAt:e.ejectedAt??null}}function Os(e){switch(e.verdict){case"licensed":return A.log.success(`${k.green("LICENSED")} - resolves to an account with an active${e.plan?` ${e.plan}`:""} license.`),!0;case"ejected":return A.log.success(`${k.green("EJECTED")} - a licensed buyer opted this install out of telemetry${e.ejectedAt?` on ${e.ejectedAt.slice(0,10)}`:""}. The site is legitimate.`),!0;case"revoked":return A.log.error(`${k.red("REVOKED")} - the owning account no longer holds a plan (refund or chargeback). This deployment is no longer licensed.`),!1;case"token_domain_mismatch":return A.log.error(`${k.red("LEAKED TOKEN")} - this license belongs to a different deployment${e.mismatchDomain?` (${k.cyan(e.mismatchDomain)})`:""}, not this site. The token was copied from a licensed project.`),!1;case"no_license_history":return A.log.error(`${k.red("NO LICENSE HISTORY")} - no license has ever been associated with this site. If it runs GenerateSaaS, treat it as unlicensed.`),!1;default:return A.log.warn(`${k.yellow("UNKNOWN")} - could not cross-reference the records right now. Try again shortly.`),!1}}async function Mn(e,t){let r=process.env.GENERATESAAS_API_URL??Ge,n=Ps();e.start("Cross-referencing license records...");try{let o=n?_s(await mr(r,n,{lkh:t.lkh,nid:t.nid,domain:t.domain})):Rs(await Ct(r,{token:t.token,domain:t.domain}));return e.stop(`${k.green("Checked")} - records cross-referenced`),Os(o)}catch(o){return e.stop(`${k.yellow("Skipped")} - ${I(o)}`),null}}function xs(e){let t=e.split(".");if(t.length!==3||!t[1])throw new Error("Invalid JWT format");let r=Buffer.from(t[1],"base64url").toString("utf-8");return JSON.parse(r)}function Qt(e){return typeof e!="number"?"unknown":new Date(e*1e3).toISOString().split("T")[0]}function Fn(e){A.note([`License ID: ${k.cyan(String(e.lid??"unknown"))}`,`Version: ${k.cyan(String(e.ver??"unknown"))}`,`Init version: ${String(e.iver??"unknown")}`,`Frontend: ${String(e.fe??"unknown")}`,`Created: ${Qt(e.pat)}`,`Last updated: ${Qt(e.uat)}`,`Expires: ${Qt(e.exp)}`,`Install ID: ${String(e.nid??"unknown")}`].join(`
1198
+ `),title:`Changelog v${r} \u2192 v${n}`}}async function bs(e,t){let r=JSON.parse(await Un(e,"utf-8")),n=await De(t),o=0;for(let[i,s]of Object.entries(r))Ie(i)||n[i]!==s&&o++;for(let i of Object.keys(n))i in r||o++;return o}import*as j from"@clack/prompts";import G from"picocolors";import{readFile as ks}from"fs/promises";import{join as As,resolve as Is}from"path";function Vn(e){e.command("status").description("Show project status and check for updates").option("--cwd <path>","project directory (default: current directory)").action(async t=>{let r=Is(t.cwd??process.cwd()),n=As(r,re),o;try{o=JSON.parse(await ks(n,"utf-8"))}catch{j.cancel(".generatesaas/manifest.json not found. Run this from a GenerateSaaS project."),process.exit(1)}let i=[`Version: ${G.cyan(o.version)}`,`Frontend: ${G.cyan(o.frontend)}`,o.deploymentTarget?`Deploy target: ${G.cyan(o.deploymentTarget)}`:null,o.databaseProvider?`Database: ${G.cyan(o.databaseProvider)}`:null,o.cacheProvider?`Cache: ${G.cyan(o.cacheProvider)}`:null,o.aiTools&&o.aiTools.length>0?`AI tools: ${G.cyan(o.aiTools.join(", "))}`:null].filter(Boolean).join(`
1199
+ `);j.note(i,G.bold("Project Status"));let s=j.spinner();s.start("Checking for updates...");try{let a=await Ae(),d=Z(a),h=(await ie(d)).latest;o.version===h?(s.stop("Up to date."),j.log.success(`Already on the latest version (${G.green(h)})`)):(s.stop("Update available."),j.log.warning(`Update available: ${G.yellow(o.version)} \u2192 ${G.green(h)}`),j.log.info(`Open this project in your AI coding agent and ask it to ${G.cyan("update my GenerateSaaS project")} - it fetches and applies the update for you.`))}catch(a){s.stop("Check failed."),a instanceof x&&a.status===401?j.log.warning("Invalid API key. Run `generatesaas auth` to update it, or set GENERATESAAS_API_KEY."):j.log.warning(`Could not check for updates: ${I(a)}`)}})}import{readFile as Ts}from"fs/promises";import*as A from"@clack/prompts";import k from"picocolors";function Ps(){return process.env.GENERATESAAS_API_KEY??Ke()}function _s(e){return{verdict:e.verdict,plan:e.license?.plan??e.domainInstalls.find(t=>t.ownerPlan)?.ownerPlan??null,mismatchDomain:e.install?.domain??null,ejectedAt:e.install?.ejectedAt??e.domainInstalls.find(t=>t.ejectedAt)?.ejectedAt??null}}function Rs(e){return{verdict:e.verdict??"unknown",ejectedAt:e.ejectedAt??null}}function Os(e){switch(e.verdict){case"licensed":return A.log.success(`${k.green("LICENSED")} - resolves to an account with an active${e.plan?` ${e.plan}`:""} license.`),!0;case"ejected":return A.log.success(`${k.green("EJECTED")} - a licensed buyer opted this install out of telemetry${e.ejectedAt?` on ${e.ejectedAt.slice(0,10)}`:""}. The site is legitimate.`),!0;case"revoked":return A.log.error(`${k.red("REVOKED")} - the owning account no longer holds a plan (refund or chargeback). This deployment is no longer licensed.`),!1;case"token_domain_mismatch":return A.log.error(`${k.red("LEAKED TOKEN")} - this license belongs to a different deployment${e.mismatchDomain?` (${k.cyan(e.mismatchDomain)})`:""}, not this site. The token was copied from a licensed project.`),!1;case"no_license_history":return A.log.error(`${k.red("NO LICENSE HISTORY")} - no license has ever been associated with this site. If it runs GenerateSaaS, treat it as unlicensed.`),!1;default:return A.log.warn(`${k.yellow("UNKNOWN")} - could not cross-reference the records right now. Try again shortly.`),!1}}async function Mn(e,t){let r=process.env.GENERATESAAS_API_URL??Ge,n=Ps();e.start("Cross-referencing license records...");try{let o=n?_s(await mr(r,n,{lkh:t.lkh,nid:t.nid,domain:t.domain})):Rs(await Ct(r,{token:t.token,domain:t.domain}));return e.stop(`${k.green("Checked")} - records cross-referenced`),Os(o)}catch(o){return e.stop(`${k.yellow("Skipped")} - ${I(o)}`),null}}function xs(e){let t=e.split(".");if(t.length!==3||!t[1])throw new Error("Invalid JWT format");let r=Buffer.from(t[1],"base64url").toString("utf-8");return JSON.parse(r)}function Qt(e){return typeof e!="number"?"unknown":new Date(e*1e3).toISOString().split("T")[0]}function Fn(e){A.note([`License ID: ${k.cyan(String(e.lid??"unknown"))}`,`Version: ${k.cyan(String(e.ver??"unknown"))}`,`Init version: ${String(e.iver??"unknown")}`,`Frontend: ${String(e.fe??"unknown")}`,`Created: ${Qt(e.pat)}`,`Last updated: ${Qt(e.uat)}`,`Expires: ${Qt(e.exp)}`,`Install ID: ${String(e.nid??"unknown")}`].join(`
1179
1200
  `),k.yellow("License Details"))}function Ds(e){let r=(/^https?:\/\//i.test(e)?e:`https://${e}`).replace(/\/+$/,"");if(r.endsWith("/api"))return[`${r}/license`];try{if(new URL(r).pathname!=="/")return[`${r}/license`]}catch{return[`${r}/license`]}return[`${r}/api/license`,`${r}/license`]}async function Bn(e){let t=A.spinner(),r=null,n="no candidates";for(let s of Ds(e)){t.start(`Checking ${s}...`);try{let a=await fetch(s);if(!a.ok){n=`${s} returned ${a.status}`,t.stop(`${k.yellow("Not here")} - ${n}`);continue}let d=(await a.text()).trim();if(!d||d.split(".").length!==3){n=`${s} did not return a JWT`,t.stop(`${k.yellow("Not here")} - ${n}`);continue}r=d,t.stop(`${k.green("Found")} - license endpoint responded`);break}catch(a){n=`${s}: ${I(a)}`,t.stop(`${k.yellow("Unreachable")} - ${n}`)}}if(r===null){A.log.warn(`No license endpoint found (last: ${n}). The site may be ejected, not a GenerateSaaS app, or serving its API elsewhere.`);let s=Gn(e);return s?await Mn(t,{domain:s})??!1:!1}let o;try{o=xs(r)}catch{return A.log.error("Could not decode JWT payload."),!1}t.start("Verifying signature...");try{let s=process.env.GENERATESAAS_API_URL??Ge,a=await Ct(s,{token:r});if(a.valid)t.stop(`${k.green("Valid")} - signature verified`);else return t.stop(`${k.red("Invalid")} - ${a.reason}`),!1}catch{return t.stop(`${k.yellow("Skipped")} - could not reach verification service`),A.log.warn("Signature not verified. Displaying unverified claims:"),Fn(o),!1}return Fn(o),await Mn(t,{token:r,lkh:typeof o.lkh=="string"?o.lkh:void 0,nid:typeof o.nid=="string"?o.nid:void 0,domain:Gn(e)})??!0}function Gn(e){try{return new URL(/^https?:\/\//i.test(e)?e:`https://${e}`).hostname}catch{return}}function Kn(e){e.command("verify").description("Verify a GenerateSaaS license on a deployed site").argument("[url]","URL of the site to verify (e.g. https://example.com or https://example.com/api)").option("--file <path>","file with URLs to check, one per line").action(async(t,r)=>{if(!t&&!r.file&&(A.cancel("Provide a URL or --file <path>."),process.exit(1)),r.file){let o=(await Ts(r.file,"utf-8")).split(`
1180
- `).map(s=>s.trim()).filter(s=>s&&!s.startsWith("#"));o.length===0&&(A.cancel("No URLs found in file."),process.exit(1));let i=0;for(let s of o)await Bn(s)&&i++,A.log.info("");A.log.success(`${i}/${o.length} sites verified.`)}else await Bn(t)||process.exit(1)})}import{existsSync as Cs,rmSync as Ns}from"fs";import*as K from"@clack/prompts";function zn(e){e.command("auth").description("Set or update your GenerateSaaS API key").option("--clear","remove saved API key").action(async t=>{if(t.clear){Cs(X)?(Ns(X),K.log.success("API key removed.")):K.log.info("No API key configured.");return}let r=Ke();r?K.log.info(`Current API key: ****${r.slice(-4)}`):K.log.info("No API key configured.");let n=await ze(),o=Z(n),i=K.spinner();i.start("Verifying API key...");try{await ie(o),i.stop("API key verified."),he(n),K.log.success("API key saved.")}catch(s){i.stop("Verification failed."),s instanceof x&&s.status===401?K.cancel("Invalid API key."):K.cancel(I(s)),process.exit(1)}})}import{existsSync as It,rmSync as Ls,readFileSync as tr,writeFileSync as Hn}from"fs";import{join as we}from"path";import*as C from"@clack/prompts";var $s=["packages/api/src/functions/maintenance/license-heartbeat.ts","packages/api/src/lib/cron-spread.ts","packages/api/src/lib/manifest.ts","packages/api/src/routes/internal/license.ts"],js=[{file:"packages/api/src/routes/inngest.ts",removals:[`import { licenseHeartbeatFunction } from "../functions/maintenance/license-heartbeat";
1201
+ `).map(s=>s.trim()).filter(s=>s&&!s.startsWith("#"));o.length===0&&(A.cancel("No URLs found in file."),process.exit(1));let i=0;for(let s of o)await Bn(s)&&i++,A.log.info("");A.log.success(`${i}/${o.length} sites verified.`)}else await Bn(t)||process.exit(1)})}import{existsSync as Cs,rmSync as Ns}from"fs";import*as K from"@clack/prompts";function zn(e){e.command("auth").description("Set or update your GenerateSaaS API key").option("--clear","remove saved API key").action(async t=>{if(t.clear){Cs(X)?(Ns(X),K.log.success("API key removed.")):K.log.info("No API key configured.");return}let r=Ke();r?K.log.info(`Current API key: ****${r.slice(-4)}`):K.log.info("No API key configured.");let n=await ze(),o=Z(n),i=K.spinner();i.start("Verifying API key...");try{await ie(o),i.stop("API key verified."),he(n),K.log.success("API key saved.")}catch(s){i.stop("Verification failed."),s instanceof x&&s.status===401?K.cancel("Invalid API key."):K.cancel(I(s)),process.exit(1)}})}import{existsSync as It,rmSync as Ls,readFileSync as tr,writeFileSync as Hn}from"fs";import{join as we}from"path";import*as C from"@clack/prompts";var $s=["packages/api/src/functions/maintenance/license-heartbeat.ts","packages/api/src/lib/cron-spread.ts","packages/api/src/lib/manifest.ts","packages/api/src/routes/internal/license.ts"],Us=[{file:"packages/api/src/routes/inngest.ts",removals:[`import { licenseHeartbeatFunction } from "../functions/maintenance/license-heartbeat";
1181
1202
  `,` licenseHeartbeatFunction,
1182
1203
  `]},{file:"packages/api/src/routes/internal/index.ts",removals:[`import licenseRoutes from "./license";
1183
1204
  `,` .route("/license", licenseRoutes)
1184
- `]}];function Us(e){return(e&&e.length>0?e.map(r=>Ye[r]):Object.values(Ye)).map(r=>we(r,Yt))}function er(e){return It(e)?(Ls(e,{recursive:!0}),!0):!1}function Vs(e,t){if(!It(e))return!1;let r=tr(e,"utf-8"),n=r;for(let o of t)n=n.replace(o,"");return n===r?!1:(Hn(e,n,"utf-8"),!0)}function Ms(e){let t=we(e,".gitignore");if(!It(t))return!1;let r=tr(t,"utf-8"),n=r.split(`
1205
+ `]}];function js(e){return(e&&e.length>0?e.map(r=>Ye[r]):Object.values(Ye)).map(r=>we(r,Yt))}function er(e){return It(e)?(Ls(e,{recursive:!0}),!0):!1}function Vs(e,t){if(!It(e))return!1;let r=tr(e,"utf-8"),n=r;for(let o of t)n=n.replace(o,"");return n===r?!1:(Hn(e,n,"utf-8"),!0)}function Ms(e){let t=we(e,".gitignore");if(!It(t))return!1;let r=tr(t,"utf-8"),n=r.split(`
1185
1206
  `).filter(o=>!o.includes(".generatesaas")).join(`
1186
- `);return n===r?!1:(Hn(t,n,"utf-8"),!0)}function Yn(e){e.command("eject").description("Remove all GenerateSaaS ties - manifest, license, heartbeat, skills").action(async()=>{let t=process.cwd(),r=we(t,re),n;try{n=JSON.parse(tr(r,"utf-8"))}catch{C.cancel("No GenerateSaaS project found in this directory."),process.exit(1)}let o=await C.text({message:'Type "eject" to confirm (this cannot be undone):',validate:a=>{if(a!=="eject")return'Type "eject" to confirm, or press Ctrl+C to cancel.'}});if(C.isCancel(o)&&(C.cancel("Eject cancelled."),process.exit(0)),n.licenseToken)try{await fetch("https://generatesaas.com/api/v1/heartbeat",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${n.licenseToken}`},body:JSON.stringify({event:"eject",version:n.version,frontend:n.frontend}),signal:AbortSignal.timeout(5e3)}),C.log.info("Recorded the opt-out with generatesaas.com (final event - nothing is sent after this).")}catch{C.log.warn("Could not reach generatesaas.com to record the opt-out. Ejecting anyway.")}let i=[],s=[];for(let a of Us(n.aiTools))er(we(t,a))&&i.push(a);for(let a of $s)er(we(t,a))&&i.push(a);er(we(t,M))&&i.push(M+"/");for(let a of js){let d=we(t,a.file);Vs(d,a.removals)?s.push(a.file):It(d)&&C.log.warn(`Could not auto-modify ${a.file} - manually remove license/heartbeat references.`)}Ms(t)&&s.push(".gitignore");for(let a of i)C.log.info(`Deleted ${a}`);for(let a of s)C.log.info(`Modified ${a}`);C.log.success("Ejected successfully. This project is now fully standalone.")})}var be=new Fs().name("generatesaas").description("CLI for scaffolding and managing GenerateSaaS projects").version("1.16.0").addHelpText("after",`
1207
+ `);return n===r?!1:(Hn(t,n,"utf-8"),!0)}function Yn(e){e.command("eject").description("Remove all GenerateSaaS ties - manifest, license, heartbeat, skills").action(async()=>{let t=process.cwd(),r=we(t,re),n;try{n=JSON.parse(tr(r,"utf-8"))}catch{C.cancel("No GenerateSaaS project found in this directory."),process.exit(1)}let o=await C.text({message:'Type "eject" to confirm (this cannot be undone):',validate:a=>{if(a!=="eject")return'Type "eject" to confirm, or press Ctrl+C to cancel.'}});if(C.isCancel(o)&&(C.cancel("Eject cancelled."),process.exit(0)),n.licenseToken)try{await fetch("https://generatesaas.com/api/v1/heartbeat",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${n.licenseToken}`},body:JSON.stringify({event:"eject",version:n.version,frontend:n.frontend}),signal:AbortSignal.timeout(5e3)}),C.log.info("Recorded the opt-out with generatesaas.com (final event - nothing is sent after this).")}catch{C.log.warn("Could not reach generatesaas.com to record the opt-out. Ejecting anyway.")}let i=[],s=[];for(let a of js(n.aiTools))er(we(t,a))&&i.push(a);for(let a of $s)er(we(t,a))&&i.push(a);er(we(t,M))&&i.push(M+"/");for(let a of Us){let d=we(t,a.file);Vs(d,a.removals)?s.push(a.file):It(d)&&C.log.warn(`Could not auto-modify ${a.file} - manually remove license/heartbeat references.`)}Ms(t)&&s.push(".gitignore");for(let a of i)C.log.info(`Deleted ${a}`);for(let a of s)C.log.info(`Modified ${a}`);C.log.success("Ejected successfully. This project is now fully standalone.")})}var be=new Fs().name("generatesaas").description("CLI for scaffolding and managing GenerateSaaS projects").version("1.18.0").addHelpText("after",`
1187
1208
  Examples:
1188
1209
  $ generatesaas init Interactive setup
1189
1210
  $ generatesaas init -n my-app -y Quick setup with defaults
1190
1211
  $ generatesaas status Check for updates
1191
1212
  $ generatesaas auth Set or update API key
1192
- `);Nn(be);Un(be);Vn(be);Kn(be);zn(be);Yn(be);be.parseAsync().catch(e=>{Jn.cancel("An unexpected error occurred."),console.error(e),process.exit(1)});
1213
+ `);Nn(be);jn(be);Vn(be);Kn(be);zn(be);Yn(be);be.parseAsync().catch(e=>{Jn.cancel("An unexpected error occurred."),console.error(e),process.exit(1)});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "generatesaas",
3
- "version": "1.16.0",
3
+ "version": "1.18.0",
4
4
  "type": "module",
5
5
  "description": "CLI for scaffolding and managing GenerateSaaS projects",
6
6
  "license": "UNLICENSED",