generatesaas 1.11.2 → 1.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +90 -89
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,14 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import{Command as As}from"commander";import*as Cn from"@clack/prompts";import{existsSync as Go,readdirSync as Ko,rmSync as zo}from"fs";import{join as Ho,resolve as Yo}from"path";import{Option as V}from"commander";import*as E from"@clack/prompts";import*as Je from"@clack/prompts";import At from"picocolors";function qt(e){let t=e?` GenerateSaaS v${e} `:" GenerateSaaS ";Je.intro(At.bgYellow(At.black(t)))}function Xt(){Je.outro(At.yellow("Happy building!"))}import*as m from"@clack/prompts";import g from"picocolors";var Ne={nextjs:{label:"Next.js",hint:"React 19 + Next.js 16"},nuxt:{label:"Nuxt",hint:"Vue 3 + Nuxt 4"}},Le={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)"}},We={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"}},Xe={user:{label:"Per user",hint:"each user has their own subscription"},organization:{label:"Per organization",hint:"org subscription shared by members"}},be={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}},$e={"claude-code":{label:"Claude Code"},cursor:{label:"Cursor"},codex:{label:"Codex"},"gemini-cli":{label:"Gemini CLI"},windsurf:{label:"Windsurf"}};var se={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 B={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}},N={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"}]}},G={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"}]}},Ue=[{target:"vercel",provider:"redis",reason:"Vercel serverless cannot maintain persistent Redis connections. Consider Upstash."}],je=[{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)."}],Zt=["Local file storage (sharp, geoip-lite)","SMTP email (use Resend or SES instead)","Content API git integration"];function Ze(e){let t=N[e.databaseProvider].managed,r=G[e.cacheProvider].managed;return e.dockerServices.some(i=>!(i==="postgres"&&t||i==="redis"&&r))}var W={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 Ve=["nextjs","nuxt"],Qe=["fullstack","separate"],et=["stripe","polar","none"],tt=["smtp","ses","resend"],rt=["postgres","redis","inngest","mailpit"],nt=["claude-code","cursor","codex","gemini-cli","windsurf"],it=["user","organization"],ae=["USD","EUR","GBP","CAD","AUD","BRL","JPY"],ce=["node","vercel"],le=["postgres","neon","supabase"],pe=["redis","upstash"],ot=["google","github","facebook","discord","x"];function st(e){return e.split("-").map(t=>t.charAt(0).toUpperCase()+t.slice(1)).join(" ")}function at(e){return/^[a-z][a-z0-9-]*$/.test(e)}function I(e){return e instanceof Error?e.message:String(e)}function w(e){m.isCancel(e)&&(m.cancel("Setup cancelled."),process.exit(0))}function Tt(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 i=W[r];for(let n of i.envVars)t.push({key:n.name,message:`${n.name} (${i.label}, optional):`,secret:n.secret})}return e.demo&&t.push({key:"TURNSTILE_SECRET_KEY",message:"Cloudflare Turnstile secret key (optional):",secret:!0}),t}async function Qt(e){let t=!1;m.log.info(g.bold("Project"));let r=e?.projectName??await(async()=>{t=!0;let c=await m.text({message:"Project name:",placeholder:"my-saas",validate:p=>{if(!p?.trim())return"Project name is required.";if(!at(p))return"Use lowercase letters, numbers, and hyphens only. Must start with a letter."}});return w(c),c})(),i=e?.appName??await(async()=>{t=!0;let c=await m.text({message:"App name:",initialValue:st(r),validate:p=>{if(!p?.trim())return"App name is required."}});return w(c),c})(),n=e?.projectDir??await(async()=>{t=!0;let c=await m.text({message:"Project location:",initialValue:`./${r}`});return w(c),c==="."?process.cwd():c})(),o=e?.frontend??await(async()=>{t=!0;let c=Object.keys(Ne),p=await m.select({message:"Frontend framework:",options:c.map(v=>({value:v,label:Ne[v].label,hint:Ne[v].hint}))});return w(p),p})();m.log.info(g.bold("Infrastructure"));let s=e?.deploymentTarget??"node";if(e?.deploymentTarget===void 0){t=!0;let c=await m.select({message:"Deployment target:",options:ce.map(p=>({value:p,label:B[p].label,hint:B[p].hint}))});w(c),s=c}let a=e?.architecture?je.find(c=>c.architecture===e.architecture&&c.target===s):void 0;if(a)throw new Error(`Incompatible: --architecture ${a.architecture} + --deploy ${a.target}. ${a.reason}`);let d=new Set(je.filter(c=>c.target===s).map(c=>c.architecture)),y=Qe.filter(c=>!d.has(c)),h=e?.architecture??await(async()=>{if(y.length===1){let p=y[0];return m.log.info(`Auto-selected ${Le[p].label} architecture (only compatible option for ${B[s].label}).`),p}t=!0;let c=await m.select({message:"Architecture:",options:y.map(p=>({value:p,label:Le[p].label,hint:Le[p].hint}))});return w(c),c})(),f=e?.databaseProvider??await(async()=>{t=!0;let c=le.filter(v=>!Ue.some(D=>D.target===s&&D.provider===v));if(c.length===1){let v=c[0];return m.log.info(`Auto-selected ${N[v].label} (only compatible option for ${B[s].label}).`),v}let p=await m.select({message:"Database provider:",options:c.map(v=>({value:v,label:N[v].label,hint:N[v].hint}))});return w(p),p})(),S=e?.cacheProvider??await(async()=>{t=!0;let c=pe.filter(v=>!Ue.some(D=>D.target===s&&D.provider===v));if(c.length===1){let v=c[0];return m.log.info(`Auto-selected ${G[v].label} (only compatible option for ${B[s].label}).`),v}let p=await m.select({message:"Cache provider:",options:c.map(v=>({value:v,label:G[v].label,hint:G[v].hint}))});return w(p),p})();if(B[s]?.edgeRuntime){let c=Zt.map(p=>` - ${p}`).join(`
3
- `);m.note(c,"Unavailable on edge runtime")}m.log.info(g.bold("Features"));let k=e?.paymentProvider??await(async()=>{t=!0;let c=await m.select({message:"Payment provider:",options:et.map(p=>({value:p,label:We[p].label,hint:We[p].hint}))});return w(c),c})(),ie=e?.defaultCurrency??await(async()=>{if(k==="none")return"USD";t=!0;let c=await m.select({message:"Default currency:",options:ae.map(p=>({value:p,label:p,hint:se[p].name}))});return w(c),c})(),x=e?.emailProvider??await(async()=>{t=!0;let c=await m.select({message:"Email provider:",options:tt.map(p=>({value:p,label:qe[p].label,hint:qe[p].hint}))});return w(c),c})(),$=e?.multiTenancy??await(async()=>{t=!0;let c=await m.confirm({message:"Enable multi-tenancy (organizations)?",initialValue:!1});return w(c),c})(),Ee=e?.billingScope??"user";if($&&e?.billingScope===void 0){t=!0;let c=await m.select({message:"Billing scope:",options:it.map(p=>({value:p,label:Xe[p].label,hint:Xe[p].hint}))});w(c),Ee=c}let Z=e?.blog??await(async()=>{t=!0;let c=await m.confirm({message:"Enable blog?",initialValue:!0});return w(c),c})(),u=e?.docs??await(async()=>{t=!0;let c=await m.confirm({message:"Include docs app? (self-hosted Fumadocs documentation site)",initialValue:!1});return w(c),c})(),b=e?.revenueSharing??await(async()=>{t=!0;let c=await m.confirm({message:"Enable revenue sharing? (opt-in MRR leaderboard with dofollow backlinks)",initialValue:!1});return w(c),c})(),H=k==="none"?!1:e?.credits??await(async()=>{t=!0;let c=await m.confirm({message:"Enable credits? (metered usage on top of subscription plans)",initialValue:!0});return w(c),c})(),Y=e?.socialProviders??await(async()=>{t=!0;let c=ot.map(v=>({value:v,label:W[v].label,hint:`requires ${W[v].envVars.map(D=>D.name).join(" / ")}`})),p=await m.multiselect({message:"Which social login providers should the sign-in screen show?",options:c,initialValues:[],required:!1});return w(p),p})();m.log.info(g.bold("Tooling"));let J=e?.dockerServices??await(async()=>{t=!0;let c=[...rt].filter(C=>C!=="mailpit");x==="smtp"&&c.push("mailpit");let p=c.map(C=>({value:C,label:be[C].label,hint:be[C].hint})),v=p.map(C=>C.value).filter(C=>!(C==="postgres"&&(f==="neon"||f==="supabase")||C==="redis"&&S==="upstash")),D=await m.multiselect({message:"Which services should we set up in Docker for you?",options:p,initialValues:v,required:!1});return w(D),D})(),we=e?.aiTools??await(async()=>{t=!0;let c=nt.map(v=>({value:v,label:$e[v].label})),p=await m.multiselect({message:"Which AI coding tools do you use?",options:c,initialValues:[],required:!1});return w(p),p})(),bt=e?.demo,Ye=Tt({databaseProvider:f,cacheProvider:S,paymentProvider:k,emailProvider:x,socialProviders:Y,demo:bt}),Ce={};if(Ye.length>0&&t){m.log.info(g.bold("Credentials")+g.dim(" all optional - press Enter to skip, fill in .env later"));for(let c of Ye)if(t=!0,c.secret){let p=await m.password({message:c.message,mask:"*"});w(p),typeof p=="string"&&p.trim()&&(Ce[c.key]=p.trim())}else{let p=await m.text({message:c.message,placeholder:c.placeholder});w(p),typeof p=="string"&&p.trim()&&(Ce[c.key]=p.trim())}}if(t){let c=[` Name: ${g.cyan(r)}`,` App name: ${g.cyan(i)}`,` Location: ${g.cyan(n)}`,` Frontend: ${g.cyan(Ne[o].label)}`,` Architecture: ${g.cyan(Le[h].label)}`].join(`
2
+ import{Command as _s}from"commander";import*as jn from"@clack/prompts";import{existsSync as Jo,readdirSync as Wo,rmSync as qo}from"fs";import{join as Xo,resolve as Zo}from"path";import{Option as V}from"commander";import*as E from"@clack/prompts";import*as We from"@clack/prompts";import Tt from"picocolors";function Zt(e){let t=e?` GenerateSaaS v${e} `:" GenerateSaaS ";We.intro(Tt.bgYellow(Tt.black(t)))}function Qt(){We.outro(Tt.yellow("Happy building!"))}import*as m from"@clack/prompts";import g from"picocolors";var Le={nextjs:{label:"Next.js",hint:"React 19 + Next.js 16"},nuxt:{label:"Nuxt",hint:"Vue 3 + Nuxt 4"}},$e={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)"}},qe={stripe:{label:"Stripe"},polar:{label:"Polar"},none:{label:"None",hint:"disable payments"}},Xe={smtp:{label:"SMTP",hint:"Mailpit for local dev"},ses:{label:"Amazon SES"},resend:{label:"Resend"}},Ze={user:{label:"Per user",hint:"each user has their own subscription"},organization:{label:"Per organization",hint:"org subscription shared by members"}},be={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}},Ue={"claude-code":{label:"Claude Code"},cursor:{label:"Cursor"},codex:{label:"Codex"},"gemini-cli":{label:"Gemini CLI"},windsurf:{label:"Windsurf"}};var se={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 B={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}},N={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"}]}},G={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"}]}},je=[{target:"vercel",provider:"redis",reason:"Vercel serverless cannot maintain persistent Redis connections. Consider Upstash."}],Ve=[{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)."}],er=["Local file storage (sharp, geoip-lite)","SMTP email (use Resend or SES instead)","Content API git integration"];function Qe(e){let t=N[e.databaseProvider].managed,r=G[e.cacheProvider].managed;return e.dockerServices.some(n=>!(n==="postgres"&&t||n==="redis"&&r))}var W={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 Me=["nextjs","nuxt"],et=["fullstack","separate"],tt=["stripe","polar","none"],rt=["smtp","ses","resend"],nt=["postgres","redis","inngest","mailpit"],it=["claude-code","cursor","codex","gemini-cli","windsurf"],ot=["user","organization"],ae=["USD","EUR","GBP","CAD","AUD","BRL","JPY"],ce=["node","vercel"],le=["postgres","neon","supabase"],pe=["redis","upstash"],st=["google","github","facebook","discord","x"];function at(e){return e.split("-").map(t=>t.charAt(0).toUpperCase()+t.slice(1)).join(" ")}function ct(e){return/^[a-z][a-z0-9-]*$/.test(e)}function I(e){return e instanceof Error?e.message:String(e)}function w(e){m.isCancel(e)&&(m.cancel("Setup cancelled."),process.exit(0))}function kt(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=W[r];for(let i of n.envVars)t.push({key:i.name,message:`${i.name} (${n.label}, optional):`,secret:i.secret})}return e.demo&&t.push({key:"TURNSTILE_SECRET_KEY",message:"Cloudflare Turnstile secret key (optional):",secret:!0}),t}async function tr(e){let t=!1;m.log.info(g.bold("Project"));let r=e?.projectName??await(async()=>{t=!0;let c=await m.text({message:"Project name:",placeholder:"my-saas",validate:p=>{if(!p?.trim())return"Project name is required.";if(!ct(p))return"Use lowercase letters, numbers, and hyphens only. Must start with a letter."}});return w(c),c})(),n=e?.appName??await(async()=>{t=!0;let c=await m.text({message:"App name:",initialValue:at(r),validate:p=>{if(!p?.trim())return"App name is required."}});return w(c),c})(),i=e?.projectDir??await(async()=>{t=!0;let c=await m.text({message:"Project location:",initialValue:`./${r}`});return w(c),c==="."?process.cwd():c})(),o=e?.frontend??await(async()=>{t=!0;let c=Object.keys(Le),p=await m.select({message:"Frontend framework:",options:c.map(v=>({value:v,label:Le[v].label,hint:Le[v].hint}))});return w(p),p})();m.log.info(g.bold("Infrastructure"));let s=e?.deploymentTarget??"node";if(e?.deploymentTarget===void 0){t=!0;let c=await m.select({message:"Deployment target:",options:ce.map(p=>({value:p,label:B[p].label,hint:B[p].hint}))});w(c),s=c}let a=e?.architecture?Ve.find(c=>c.architecture===e.architecture&&c.target===s):void 0;if(a)throw new Error(`Incompatible: --architecture ${a.architecture} + --deploy ${a.target}. ${a.reason}`);let d=new Set(Ve.filter(c=>c.target===s).map(c=>c.architecture)),y=et.filter(c=>!d.has(c)),h=e?.architecture??await(async()=>{if(y.length===1){let p=y[0];return m.log.info(`Auto-selected ${$e[p].label} architecture (only compatible option for ${B[s].label}).`),p}t=!0;let c=await m.select({message:"Architecture:",options:y.map(p=>({value:p,label:$e[p].label,hint:$e[p].hint}))});return w(c),c})(),f=e?.databaseProvider??await(async()=>{t=!0;let c=le.filter(v=>!je.some(D=>D.target===s&&D.provider===v));if(c.length===1){let v=c[0];return m.log.info(`Auto-selected ${N[v].label} (only compatible option for ${B[s].label}).`),v}let p=await m.select({message:"Database provider:",options:c.map(v=>({value:v,label:N[v].label,hint:N[v].hint}))});return w(p),p})(),S=e?.cacheProvider??await(async()=>{t=!0;let c=pe.filter(v=>!je.some(D=>D.target===s&&D.provider===v));if(c.length===1){let v=c[0];return m.log.info(`Auto-selected ${G[v].label} (only compatible option for ${B[s].label}).`),v}let p=await m.select({message:"Cache provider:",options:c.map(v=>({value:v,label:G[v].label,hint:G[v].hint}))});return w(p),p})();if(B[s]?.edgeRuntime){let c=er.map(p=>` - ${p}`).join(`
3
+ `);m.note(c,"Unavailable on edge runtime")}m.log.info(g.bold("Features"));let k=e?.paymentProvider??await(async()=>{t=!0;let c=await m.select({message:"Payment provider:",options:tt.map(p=>({value:p,label:qe[p].label,hint:qe[p].hint}))});return w(c),c})(),ie=e?.defaultCurrency??await(async()=>{if(k==="none")return"USD";t=!0;let c=await m.select({message:"Default currency:",options:ae.map(p=>({value:p,label:p,hint:se[p].name}))});return w(c),c})(),x=e?.emailProvider??await(async()=>{t=!0;let c=await m.select({message:"Email provider:",options:rt.map(p=>({value:p,label:Xe[p].label,hint:Xe[p].hint}))});return w(c),c})(),$=e?.multiTenancy??await(async()=>{t=!0;let c=await m.confirm({message:"Enable multi-tenancy (organizations)?",initialValue:!1});return w(c),c})(),Ee=e?.billingScope??"user";if($&&e?.billingScope===void 0){t=!0;let c=await m.select({message:"Billing scope:",options:ot.map(p=>({value:p,label:Ze[p].label,hint:Ze[p].hint}))});w(c),Ee=c}let Z=e?.blog??await(async()=>{t=!0;let c=await m.confirm({message:"Enable blog?",initialValue:!0});return w(c),c})(),u=e?.docs??await(async()=>{t=!0;let c=await m.confirm({message:"Include docs app? (self-hosted Fumadocs documentation site)",initialValue:!1});return w(c),c})(),b=e?.revenueSharing??await(async()=>{t=!0;let c=await m.confirm({message:"Enable revenue sharing? (opt-in MRR leaderboard with dofollow backlinks)",initialValue:!1});return w(c),c})(),H=k==="none"?!1:e?.credits??await(async()=>{t=!0;let c=await m.confirm({message:"Enable credits? (metered usage on top of subscription plans)",initialValue:!0});return w(c),c})(),Y=e?.socialProviders??await(async()=>{t=!0;let c=st.map(v=>({value:v,label:W[v].label,hint:`requires ${W[v].envVars.map(D=>D.name).join(" / ")}`})),p=await m.multiselect({message:"Which social login providers should the sign-in screen show?",options:c,initialValues:[],required:!1});return w(p),p})();m.log.info(g.bold("Tooling"));let J=e?.dockerServices??await(async()=>{t=!0;let c=[...nt].filter(C=>C!=="mailpit");x==="smtp"&&c.push("mailpit");let p=c.map(C=>({value:C,label:be[C].label,hint:be[C].hint})),v=p.map(C=>C.value).filter(C=>!(C==="postgres"&&(f==="neon"||f==="supabase")||C==="redis"&&S==="upstash")),D=await m.multiselect({message:"Which services should we set up in Docker for you?",options:p,initialValues:v,required:!1});return w(D),D})(),we=e?.aiTools??await(async()=>{t=!0;let c=it.map(v=>({value:v,label:Ue[v].label})),p=await m.multiselect({message:"Which AI coding tools do you use?",options:c,initialValues:[],required:!1});return w(p),p})(),At=e?.demo,Je=kt({databaseProvider:f,cacheProvider:S,paymentProvider:k,emailProvider:x,socialProviders:Y,demo:At}),Ne={};if(Je.length>0&&t){m.log.info(g.bold("Credentials")+g.dim(" all optional - press Enter to skip, fill in .env later"));for(let c of Je)if(t=!0,c.secret){let p=await m.password({message:c.message,mask:"*"});w(p),typeof p=="string"&&p.trim()&&(Ne[c.key]=p.trim())}else{let p=await m.text({message:c.message,placeholder:c.placeholder});w(p),typeof p=="string"&&p.trim()&&(Ne[c.key]=p.trim())}}if(t){let c=[` Name: ${g.cyan(r)}`,` App name: ${g.cyan(n)}`,` Location: ${g.cyan(i)}`,` Frontend: ${g.cyan(Le[o].label)}`,` Architecture: ${g.cyan($e[h].label)}`].join(`
4
4
  `),p=[` Deploy target: ${g.cyan(B[s]?.label??"Node.js / Docker")}`,` Database: ${g.cyan(N[f].label)}`,` Cache: ${g.cyan(G[S].label)}`,J.length>0?` Docker: ${g.cyan(J.map(oe=>be[oe].label).join(", "))}`:` Docker: ${g.dim("none")}`].filter(Boolean).join(`
5
- `),v=[k!=="none"?` Payment: ${g.cyan(We[k].label)} (${ie})`:` Payment: ${g.dim("none")}`,` Credits: ${H?g.cyan("Yes"):g.dim("No")}`,` Email: ${g.cyan(qe[x].label)}`,` Multi-tenancy: ${$?g.cyan("Yes")+` (billing: ${Xe[Ee].label})`:g.dim("No")}`,` Blog: ${Z?g.cyan("Yes"):g.dim("No")}`,` Docs app: ${u?g.cyan("Yes"):g.dim("No")}`,` Rev. sharing: ${b?g.cyan("Yes"):g.dim("No")}`,Y.length>0?` Social login: ${g.cyan(Y.map(oe=>W[oe].label).join(", "))}`:` Social login: ${g.dim("none")}`,we.length>0?` AI tools: ${g.cyan(we.map(oe=>$e[oe].label).join(", "))}`:` AI tools: ${g.dim("none")}`].join(`
6
- `),D=[g.bold("Project"),c,"",g.bold("Infrastructure"),p,"",g.bold("Features"),v];if(Ye.length>0){let oe=Ye.map(Wt=>{let Nn=Ce[Wt.key]?g.green("provided"):g.dim("skipped");return` ${Wt.key}: ${Nn}`}).join(`
5
+ `),v=[k!=="none"?` Payment: ${g.cyan(qe[k].label)} (${ie})`:` Payment: ${g.dim("none")}`,` Credits: ${H?g.cyan("Yes"):g.dim("No")}`,` Email: ${g.cyan(Xe[x].label)}`,` Multi-tenancy: ${$?g.cyan("Yes")+` (billing: ${Ze[Ee].label})`:g.dim("No")}`,` Blog: ${Z?g.cyan("Yes"):g.dim("No")}`,` Docs app: ${u?g.cyan("Yes"):g.dim("No")}`,` Rev. sharing: ${b?g.cyan("Yes"):g.dim("No")}`,Y.length>0?` Social login: ${g.cyan(Y.map(oe=>W[oe].label).join(", "))}`:` Social login: ${g.dim("none")}`,we.length>0?` AI tools: ${g.cyan(we.map(oe=>Ue[oe].label).join(", "))}`:` AI tools: ${g.dim("none")}`].join(`
6
+ `),D=[g.bold("Project"),c,"",g.bold("Infrastructure"),p,"",g.bold("Features"),v];if(Je.length>0){let oe=Je.map(Xt=>{let Vn=Ne[Xt.key]?g.green("provided"):g.dim("skipped");return` ${Xt.key}: ${Vn}`}).join(`
7
7
  `);D.push("",g.bold("Credentials"),oe)}m.note(D.join(`
8
- `),"Summary");let C=await m.confirm({message:"Proceed with these settings?"});(m.isCancel(C)||!C)&&(m.cancel("Setup cancelled."),process.exit(0))}return{projectName:r,appName:i,projectDir:n,frontend:o,architecture:h,deploymentTarget:s,databaseProvider:f,cacheProvider:S,paymentProvider:k,emailProvider:x,multiTenancy:$,billingScope:Ee,blog:Z,docs:u,revenueSharing:b,credits:H,dockerServices:J,aiTools:we,socialProviders:Y,defaultCurrency:ie,...Object.keys(Ce).length>0?{credentials:Ce}:{},...e?.baseUrl!==void 0?{baseUrl:e.baseUrl}:{},...bt!==void 0?{demo:bt}:{}}}import{createReadStream as Gn}from"fs";import{mkdir as Kn}from"fs/promises";import{Readable as zn}from"stream";import{pipeline as ar}from"stream/promises";import{extract as Hn}from"tar";import{join as de}from"path";import{homedir as Ln}from"os";var Me=process.env.GENERATESAAS_API_URL??"https://cli.generatesaas.com",U=".generatesaas",Q=de(U,"manifest.json"),er=de(U,"hashes.json"),ct=de(U,"template-hashes.json"),lt=de(U,"template"),tr=de(U,"staging"),rr=de(U,"staging.json"),q=de(Ln(),".generatesaas");var R=class extends Error{constructor(r,i,n){super(i);this.status=r;this.body=n}status;body;name="ApiError"};function X(e){return{apiKey:e,baseUrl:Me}}async function ee(e,t,r){let i=`${e.baseUrl}${t}`,n=await fetch(i,{...r,headers:{...r?.headers,Authorization:`Bearer ${e.apiKey}`,"User-Agent":"generatesaas-cli"}});if(!n.ok){let o,s;try{s=await n.json(),o=s.error??`API ${n.status}: ${t}`}catch{o=`API ${n.status}: ${t}`}throw new R(n.status,o,s)}return n}import{existsSync as $n,readFileSync as Un,writeFileSync as jn,mkdirSync as Vn}from"fs";import{dirname as Mn}from"path";import*as te from"@clack/prompts";function Fe(){if(!$n(q))return null;try{let e=JSON.parse(Un(q,"utf-8"));return e.apiKey?e.apiKey:(e.token&&!e.apiKey&&te.log.warning(`Found old GitHub token in ${q}. Run 'generatesaas init' to set up your API key.`),null)}catch{return null}}function ue(e){Vn(Mn(q),{recursive:!0}),jn(q,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=Fe();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 Be()}async function Be(){let e=await te.text({message:"Enter your GenerateSaaS API key:",placeholder:"gs_live_...",validate:t=>{if(!t?.trim())return"API key is required."}});return te.isCancel(e)&&(te.cancel("Setup cancelled."),process.exit(0)),e.trim()}async function re(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}]}:await(await ee(e,"/versions")).json()}async function kt(e,t){try{return await(await ee(e,`/changelog/${encodeURIComponent(t)}`)).text()}catch(r){if(r instanceof R&&r.status===404)return null;throw r}}async function nr(e,t){return await(await ee(e,`/skill/${encodeURIComponent(t)}`)).json()}function pt(e){if(!(e instanceof R)||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 ir(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,credits:e.credits,revenueSharing:e.revenueSharing,socialProviders:e.socialProviders,aiTools:e.aiTools,currency:e.defaultCurrency}}async function It(e,t){return process.env.GENERATESAAS_OFFLINE_LICENSE==="1"?{token:"offline-test-token",licenseId:"offline-test-license-id"}:await(await ee(e,"/license/sign",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).json()}async function or(e,t){return await(await ee(e,"/license/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).json()}async function Pt(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 sr(e,t,r){let i=await fetch(`${e}/license/inspect`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t}`},body:JSON.stringify(r)});if(!i.ok){let n=await i.json().catch(()=>null);throw new Error(n?.error??`Inspect endpoint returned ${i.status}`)}return await i.json()}var _t=new Set([".git","node_modules",".pnpm-store",".env",".env.test",".turbo",".nuxt",".output",".data","dist",".next",".svelte-kit",".wrangler",".devcontainer","playwright-report","test-results"]),Rt=new Set(["pnpm-lock.yaml"]);function Te(e){if(Ot(e))return!0;for(let t of e.split("/"))if(Rt.has(t))return!0;return!1}var Fn=new Set(["data","mksaas","references","scripts",".cursor",".agents",".codex",".generatesaas",".vscode",".mcp.json","README.md","TODO.md","OVERVIEW.md"]),Bn=["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 Ot(e){let t=e.split("/");for(let r of t)if(_t.has(r))return!0;if(Fn.has(t[0]))return!0;for(let r of Bn)if(e===r||e.startsWith(r+"/"))return!0;return!1}async function dt(e,t,r){await Kn(r,{recursive:!0});let i=process.env.GENERATESAAS_TEMPLATE_TARBALL;if(i){await ar(Gn(i),cr(r));return}let n=await ee(e,`/template/${encodeURIComponent(t)}`);if(!n.body)throw new Error("Empty response body");let o=zn.fromWeb(n.body);await ar(o,cr(r))}function cr(e){return Hn({cwd:e,strip:1,filter:t=>{let r=t.replace(/^[^/]+\//,"");return r?!Ot(r):!0},sync:!1})}import{readFile as Yn,rm as lr,writeFile as Jn}from"fs/promises";import{join as xt}from"path";var Wn=["apps/web-nuxt/public/images/blog","apps/web-next/public/images/blog","packages/content/en/blog","packages/content/ro/blog"];async function pr(e){await Promise.all(Wn.map(t=>lr(xt(e,t),{recursive:!0,force:!0})))}async function dr(e,t){t.includes("claude-code")||await lr(xt(e,".claude"),{recursive:!0,force:!0})}async function ur(e,t){let r=xt(e,".claude","settings.json"),i;try{i=await Yn(r,"utf8")}catch{return}let n=JSON.parse(i);delete n.alwaysThinkingEnabled,delete n.enableAllProjectMcpServers,n.env&&(delete n.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS,Object.keys(n.env).length===0&&delete n.env),n.permissions?.allow&&(n.permissions.allow=n.permissions.allow.filter(o=>qn(o,t))),await Jn(r,JSON.stringify(n,null," ")+`
10
- `)}function qn(e,t){return!(e.startsWith("mcp__")||t!=="nuxt"&&e.includes("nuxt"))}import{join as mr}from"path";import{mkdir as Xn,readdir as Zn,rm as Qn,rmdir as ei,writeFile as ti}from"fs/promises";import{dirname as ut,join as ri,relative as ni,sep as ii}from"path";function me(e){return e.split(ii).join("/")}async function mt(e){await Xn(e,{recursive:!0})}async function l(e,t){await mt(ut(e)),await ti(e,t,"utf-8")}async function Dt(e,t){await Qn(e,{force:!0});let r=ut(e);for(;r!==t&&r!==ut(r);){try{await ei(r)}catch{return}r=ut(r)}}async function fe(e,t,r){let i=[],n=await Zn(e,{withFileTypes:!0});for(let o of n){let s=ri(e,o.name),a=me(ni(t,s));r(a)||(o.isDirectory()?i.push(...await fe(s,t,r)):o.isFile()&&i.push(s))}return i}var oi={postgres:"Postgres",neon:"Neon (managed Postgres)",supabase:"Supabase (managed Postgres)"},si={resend:"Resend",ses:"Amazon SES",smtp:"SMTP"},ai={redis:"Redis",upstash:"Upstash Redis"},ci={node:"Node.js / Docker",vercel:"Vercel"},li={stripe:"Stripe",polar:"Polar"};function pi(e){let t=e.frontend==="nuxt",r=t?"Nuxt 4":"Next.js 16",i=t?"apps/web-nuxt":"apps/web-next",n=t?"`app/` pages + components + composables":"`app/` routes, `components/`, `lib/`",o=e.architecture==="fullstack",s=o?"(fullstack - Hono API mounted inside the app)":"(separate - standalone Hono backend)",a=o?`Mounted inside \`${i}\`. 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=ai[e.cacheProvider],y=ci[e.deploymentTarget],h=e.paymentProvider==="none"?"":`
11
- - **Payments:** ${li[e.paymentProvider]}`,f=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`.",S=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
8
+ `),"Summary");let C=await m.confirm({message:"Proceed with these settings?"});(m.isCancel(C)||!C)&&(m.cancel("Setup cancelled."),process.exit(0))}return{projectName:r,appName:n,projectDir:i,frontend:o,architecture:h,deploymentTarget:s,databaseProvider:f,cacheProvider:S,paymentProvider:k,emailProvider:x,multiTenancy:$,billingScope:Ee,blog:Z,docs:u,revenueSharing:b,credits:H,dockerServices:J,aiTools:we,socialProviders:Y,defaultCurrency:ie,...Object.keys(Ne).length>0?{credentials:Ne}:{},...e?.baseUrl!==void 0?{baseUrl:e.baseUrl}:{},...At!==void 0?{demo:At}:{}}}import{createReadStream as Jn}from"fs";import{mkdir as Wn}from"fs/promises";import{Readable as qn}from"stream";import{pipeline as lr}from"stream/promises";import{extract as Xn}from"tar";import{join as de}from"path";import{homedir as Mn}from"os";var Fe=process.env.GENERATESAAS_API_URL??"https://cli.generatesaas.com",U=".generatesaas",Q=de(U,"manifest.json"),rr=de(U,"hashes.json"),lt=de(U,"template-hashes.json"),pt=de(U,"template"),nr=de(U,"staging"),ir=de(U,"staging.json"),q=de(Mn(),".generatesaas");var R=class extends Error{constructor(r,n,i){super(n);this.status=r;this.body=i}status;body;name="ApiError"};function X(e){return{apiKey:e,baseUrl:Fe}}async function ee(e,t,r){let n=`${e.baseUrl}${t}`,i=await fetch(n,{...r,headers:{...r?.headers,Authorization:`Bearer ${e.apiKey}`,"User-Agent":"generatesaas-cli"}});if(!i.ok){let o,s;try{s=await i.json(),o=s.error??`API ${i.status}: ${t}`}catch{o=`API ${i.status}: ${t}`}throw new R(i.status,o,s)}return i}import{existsSync as Fn,readFileSync as Bn,writeFileSync as Gn,mkdirSync as Kn}from"fs";import{dirname as zn}from"path";import*as te from"@clack/prompts";function Be(){if(!Fn(q))return null;try{let e=JSON.parse(Bn(q,"utf-8"));return e.apiKey?e.apiKey:(e.token&&!e.apiKey&&te.log.warning(`Found old GitHub token in ${q}. Run 'generatesaas init' to set up your API key.`),null)}catch{return null}}function ue(e){Kn(zn(q),{recursive:!0}),Gn(q,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=Be();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 Ge()}async function Ge(){let e=await te.text({message:"Enter your GenerateSaaS API key:",placeholder:"gs_live_...",validate:t=>{if(!t?.trim())return"API key is required."}});return te.isCancel(e)&&(te.cancel("Setup cancelled."),process.exit(0)),e.trim()}async function re(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}]}:await(await ee(e,"/versions")).json()}async function It(e,t){try{return await(await ee(e,`/changelog/${encodeURIComponent(t)}`)).text()}catch(r){if(r instanceof R&&r.status===404)return null;throw r}}async function or(e,t){return await(await ee(e,`/skill/${encodeURIComponent(t)}`)).json()}function dt(e){if(!(e instanceof R)||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 sr(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,credits:e.credits,revenueSharing:e.revenueSharing,socialProviders:e.socialProviders,aiTools:e.aiTools,currency:e.defaultCurrency}}async function Pt(e,t){return process.env.GENERATESAAS_OFFLINE_LICENSE==="1"?{token:"offline-test-token",licenseId:"offline-test-license-id"}:await(await ee(e,"/license/sign",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).json()}async function ar(e,t){return await(await ee(e,"/license/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).json()}async function _t(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 cr(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 i=await n.json().catch(()=>null);throw new Error(i?.error??`Inspect endpoint returned ${n.status}`)}return await n.json()}var Rt=new Set([".git","node_modules",".pnpm-store",".env",".env.test",".turbo",".nuxt",".output",".data","dist",".next",".svelte-kit",".wrangler",".devcontainer","playwright-report","test-results"]),Ot=new Set(["pnpm-lock.yaml"]);function Te(e){if(xt(e))return!0;for(let t of e.split("/"))if(Ot.has(t))return!0;return!1}var Hn=new Set(["data","mksaas","references","scripts",".cursor",".agents",".codex",".generatesaas",".vscode",".mcp.json","README.md","TODO.md","OVERVIEW.md"]),Yn=["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 xt(e){let t=e.split("/");for(let r of t)if(Rt.has(r))return!0;if(Hn.has(t[0]))return!0;for(let r of Yn)if(e===r||e.startsWith(r+"/"))return!0;return!1}async function ut(e,t,r){await Wn(r,{recursive:!0});let n=process.env.GENERATESAAS_TEMPLATE_TARBALL;if(n){await lr(Jn(n),pr(r));return}let i=await ee(e,`/template/${encodeURIComponent(t)}`);if(!i.body)throw new Error("Empty response body");let o=qn.fromWeb(i.body);await lr(o,pr(r))}function pr(e){return Xn({cwd:e,strip:1,filter:t=>{let r=t.replace(/^[^/]+\//,"");return r?!xt(r):!0},sync:!1})}import{readdir as Zn,readFile as Dt,rm as ur,writeFile as Ct}from"fs/promises";import{join as ke}from"path";var Qn=["apps/web-nuxt/public/images/blog","apps/web-next/public/images/blog","packages/content/en/blog","packages/content/ro/blog"];async function mr(e){await Promise.all(Qn.map(t=>ur(ke(e,t),{recursive:!0,force:!0})))}var ei="packages/config/src/blog.ts";async function fr(e){let t=ke(e,ei),r=await Dt(t,"utf-8"),n=dr(dr(r,"blogCategories",t),"blogAuthors",t);await Ct(t,n)}function dr(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 gr(e){let t=ke(e,"packages/i18n/translations"),r;try{r=await Zn(t)}catch{return}for(let n of r){let i=ke(t,n,"web.json"),o;try{o=await Dt(i,"utf-8")}catch{continue}let s=JSON.parse(o);!s.blog||s.blog.categories===void 0||(s.blog.categories={},await Ct(i,JSON.stringify(s,null," ")+`
10
+ `))}}async function hr(e,t){t.includes("claude-code")||await ur(ke(e,".claude"),{recursive:!0,force:!0})}async function yr(e,t){let r=ke(e,".claude","settings.json"),n;try{n=await Dt(r,"utf8")}catch{return}let i=JSON.parse(n);delete i.alwaysThinkingEnabled,delete i.enableAllProjectMcpServers,i.env&&(delete i.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS,Object.keys(i.env).length===0&&delete i.env),i.permissions?.allow&&(i.permissions.allow=i.permissions.allow.filter(o=>ti(o,t))),await Ct(r,JSON.stringify(i,null," ")+`
11
+ `)}function ti(e,t){return!(e.startsWith("mcp__")||t!=="nuxt"&&e.includes("nuxt"))}import{join as vr}from"path";import{mkdir as ri,readdir as ni,rm as ii,rmdir as oi,writeFile as si}from"fs/promises";import{dirname as mt,join as ai,relative as ci,sep as li}from"path";function me(e){return e.split(li).join("/")}async function ft(e){await ri(e,{recursive:!0})}async function l(e,t){await ft(mt(e)),await si(e,t,"utf-8")}async function Nt(e,t){await ii(e,{force:!0});let r=mt(e);for(;r!==t&&r!==mt(r);){try{await oi(r)}catch{return}r=mt(r)}}async function fe(e,t,r){let n=[],i=await ni(e,{withFileTypes:!0});for(let o of i){let s=ai(e,o.name),a=me(ci(t,s));r(a)||(o.isDirectory()?n.push(...await fe(s,t,r)):o.isFile()&&n.push(s))}return n}var pi={postgres:"Postgres",neon:"Neon (managed Postgres)",supabase:"Supabase (managed Postgres)"},di={resend:"Resend",ses:"Amazon SES",smtp:"SMTP"},ui={redis:"Redis",upstash:"Upstash Redis"},mi={node:"Node.js / Docker",vercel:"Vercel"},fi={stripe:"Stripe",polar:"Polar"};function gi(e){let t=e.frontend==="nuxt",r=t?"Nuxt 4":"Next.js 16",n=t?"apps/web-nuxt":"apps/web-next",i=t?"`app/` pages + components + composables":"`app/` routes, `components/`, `lib/`",o=e.architecture==="fullstack",s=o?"(fullstack - Hono API mounted inside the app)":"(separate - standalone Hono backend)",a=o?`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=ui[e.cacheProvider],y=mi[e.deploymentTarget],h=e.paymentProvider==="none"?"":`
12
+ - **Payments:** ${fi[e.paymentProvider]}`,f=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`.",S=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
12
13
 
13
14
  Guidelines for AI coding agents (Claude Code, Codex, Cursor, \u2026) in this project.
14
15
  Keep this file tight: add a rule only when it prevents a recurring mistake - too
@@ -22,7 +23,7 @@ and go find it:
22
23
 
23
24
  1. \`docs/${t?"nuxt":"next"}/index.mdx\` - the catalog of every shipped feature and its config flag.
24
25
  2. \`packages/config/src/index.ts\` - the flag that turns the feature on/off (check it before rendering).
25
- 3. Search \`packages/\` and \`${i}/\` for the name before creating anything.
26
+ 3. Search \`packages/\` and \`${n}/\` for the name before creating anything.
26
27
 
27
28
  Already built - extend these, never re-implement: auth (email / OAuth / 2FA / passkeys),
28
29
  billing & subscriptions, credits, organizations & teams, notifications, email, SMS,
@@ -36,10 +37,10 @@ flag, route, or translation is the most common and most costly mistake in this r
36
37
 
37
38
  - **Frontend:** ${r} ${s}
38
39
  - **API:** Hono, RPC-typed. ${a}
39
- - **Database:** Drizzle ORM + ${oi[e.databaseProvider]}
40
+ - **Database:** Drizzle ORM + ${pi[e.databaseProvider]}
40
41
  - **Cache + jobs:** ${d} + Inngest
41
42
  - **Auth:** Better Auth${h}
42
- - **Email:** ${si[e.emailProvider]}
43
+ - **Email:** ${di[e.emailProvider]}
43
44
  - **Deploy:** ${y}
44
45
 
45
46
  ## Where things live (extend these - don't reinvent)
@@ -50,7 +51,7 @@ flag, route, or translation is the most common and most costly mistake in this r
50
51
  - \`packages/auth/src/config.ts\` - Better Auth config.
51
52
  - \`packages/runtime/src/env.ts\` - the validated (Zod) env schema. New env var \u2192 add it here **and** to \`.env.example\` (the committed reference); set the local value in \`.env\`.
52
53
  - \`packages/{payments,mail,sms,storage,notifications}\` - config-gated integrations. Every provider's files stay even when its feature is off, so flipping a flag is enough to enable it.
53
- - \`${i}\` - the ${r} app (${n}).
54
+ - \`${n}\` - the ${r} app (${i}).
54
55
  - \`docs/${t?"nuxt":"next"}/\` - feature & architecture documentation for this stack (Markdown). Consult it before searching from scratch; it cites real config keys, package names, and file paths you can act on.
55
56
 
56
57
  ## Code style
@@ -80,8 +81,8 @@ In \`@repo/*\` backend packages prefer web-standard APIs: \`crypto.randomUUID()\
80
81
  ## This project
81
82
 
82
83
  Scaffolded from the GenerateSaaS boilerplate. To pull upstream boilerplate updates, ask your agent to **"update my GenerateSaaS project"**. To remove the license heartbeat + manifest: \`pnpm dlx generatesaas eject\`.
83
- `}async function fr(e){await l(mr(e.projectDir,"AGENTS.md"),pi(e)),await l(mr(e.projectDir,"CLAUDE.md"),`@AGENTS.md
84
- `)}import{join as di}from"path";var ui={postgres:"Postgres (self-hosted)",neon:"Neon (managed Postgres)",supabase:"Supabase (managed Postgres)"};function mi(e){let t=e.appName.trim()||e.projectName,r=e.frontend==="nuxt",i=r?"Nuxt 4":"Next.js 16",n=r?"apps/web-nuxt":"apps/web-next",o=ui[e.databaseProvider],s=Ze(e),a=e.architecture==="fullstack"?`${i} app at \`${n}/\` with the Hono API mounted inside it. A standalone \`apps/backend/\` is also included (inert in this fullstack setup) so you can split the API into a separate service later without re-scaffolding.`:`${i} app at \`${n}/\` and a separate Hono backend at \`apps/backend/\`.`,d=e.architecture==="fullstack"?`- App + API: http://localhost:3000
84
+ `}async function Sr(e){await l(vr(e.projectDir,"AGENTS.md"),gi(e)),await l(vr(e.projectDir,"CLAUDE.md"),`@AGENTS.md
85
+ `)}import{join as hi}from"path";var yi={postgres:"Postgres (self-hosted)",neon:"Neon (managed Postgres)",supabase:"Supabase (managed Postgres)"};function vi(e){let t=e.appName.trim()||e.projectName,r=e.frontend==="nuxt",n=r?"Nuxt 4":"Next.js 16",i=r?"apps/web-nuxt":"apps/web-next",o=yi[e.databaseProvider],s=Qe(e),a=e.architecture==="fullstack"?`${n} app at \`${i}/\` with the Hono API mounted inside it. A standalone \`apps/backend/\` is also included (inert in this fullstack setup) so you can split the API into a separate service later without re-scaffolding.`:`${n} app at \`${i}/\` and a separate Hono backend at \`apps/backend/\`.`,d=e.architecture==="fullstack"?`- App + API: http://localhost:3000
85
86
  - Inngest dev server: http://127.0.0.1:8288`:`- App: http://localhost:3000
86
87
  - API: http://localhost:3010
87
88
  - Inngest dev server: http://127.0.0.1:8288`,y=s?`pnpm infra # optional: starts local Docker services (Postgres / Redis / Inngest / Mailpit)
@@ -93,7 +94,7 @@ ${a}
93
94
 
94
95
  ## Stack
95
96
 
96
- - **Frontend:** ${i}
97
+ - **Frontend:** ${n}
97
98
  - **Backend:** Hono (TypeScript, RPC-typed)
98
99
  - **Auth:** Better Auth
99
100
  - **ORM / DB:** Drizzle + ${o}
@@ -142,7 +143,7 @@ pnpm dlx generatesaas eject
142
143
  ## Updates
143
144
 
144
145
  ${f}
145
- `}async function gr(e){await l(di(e.projectDir,"README.md"),mi(e))}import{join as fi}from"path";function gi(e){let t=e.split(".");return t.length>=3?t.slice(1).join("."):e}async function hr(e){let t=e.appName.replace(/\\/g,"\\\\").replace(/"/g,'\\"'),r=e.paymentProvider!=="none",i=e.baseUrl??"http://localhost:3000",n=e.baseUrl?new URL(e.baseUrl).hostname:"example.com",o=gi(n),s=e.demo?"false":"true",a=e.demo?`
146
+ `}async function Er(e){await l(hi(e.projectDir,"README.md"),vi(e))}import{join as Si}from"path";function Ei(e){let t=e.split(".");return t.length>=3?t.slice(1).join("."):e}async function wr(e){let t=e.appName.replace(/\\/g,"\\\\").replace(/"/g,'\\"'),r=e.paymentProvider!=="none",n=e.baseUrl??"http://localhost:3000",i=e.baseUrl?new URL(e.baseUrl).hostname:"example.com",o=Ei(i),s=e.demo?"false":"true",a=e.demo?`
146
147
  support: {
147
148
  enableInDev: true,
148
149
  crisp: { websiteId: "7e221cec-ed61-46b7-b1b4-8cbc16557cca" }
@@ -153,8 +154,8 @@ const trustedOrigins = process.env.TRUSTED_ORIGINS?.split(",").map((s) => s.trim
153
154
  export const config: AppConfig = {
154
155
  siteName: "${t}",
155
156
  fullSiteName: "${t}",
156
- domain: "${n}",
157
- baseUrl: process.env.BASE_URL ?? "${i}",
157
+ domain: "${i}",
158
+ baseUrl: process.env.BASE_URL ?? "${n}",
158
159
  indexable: ${s},
159
160
  logo: {
160
161
  main: "/images/logo.svg",
@@ -308,8 +309,8 @@ export * from "./pricing";
308
309
  export * from "./roles";
309
310
  export * from "./section-tabs";
310
311
  export * from "./tenancy";
311
- `,h=fi(e.projectDir,"packages/config/src/index.ts");await l(h,y)}import{join as hi}from"path";function yi(e){return e==="stripe"?' stripePriceId: "",':' polarProductId: "",'}function Ct(e){let t=[];return e.withCredits&&(t.push(` credits: ${e.credits},`),t.push(" creditInterval: 30,")),t.push(` apiRateLimit: { maxRequests: ${e.rateLimit} }`),t.join(`
312
- `)}async function yr(e){let t=hi(e.projectDir,"packages/config/src/pricing.ts"),r=e.defaultCurrency;if(e.paymentProvider==="none"){let f=`import type { PricingConfig } from "@repo/config/types";
312
+ `,h=Si(e.projectDir,"packages/config/src/index.ts");await l(h,y)}import{join as wi}from"path";function bi(e){return e==="stripe"?' stripePriceId: "",':' polarProductId: "",'}function Lt(e){let t=[];return e.withCredits&&(t.push(` credits: ${e.credits},`),t.push(" creditInterval: 30,")),t.push(` apiRateLimit: { maxRequests: ${e.rateLimit} }`),t.join(`
313
+ `)}async function br(e){let t=wi(e.projectDir,"packages/config/src/pricing.ts"),r=e.defaultCurrency;if(e.paymentProvider==="none"){let f=`import type { PricingConfig } from "@repo/config/types";
313
314
 
314
315
  export const pricingConfig: PricingConfig = {
315
316
  defaultPlan: "free",
@@ -338,7 +339,7 @@ export const pricingConfig: PricingConfig = {
338
339
  credits: { enabled: false },
339
340
  products: { enabled: false, items: [] }
340
341
  };
341
- `;await l(t,f);return}let i=e.paymentProvider,n=yi(i),o=e.credits,s=Ct({credits:5,rateLimit:100,withCredits:o}),a=Ct({credits:10,rateLimit:1e3,withCredits:o}),d=Ct({credits:50,rateLimit:5e3,withCredits:o}),h=`import type { PricingConfig } from "@repo/config/types";
342
+ `;await l(t,f);return}let n=e.paymentProvider,i=bi(n),o=e.credits,s=Lt({credits:5,rateLimit:100,withCredits:o}),a=Lt({credits:10,rateLimit:1e3,withCredits:o}),d=Lt({credits:50,rateLimit:5e3,withCredits:o}),h=`import type { PricingConfig } from "@repo/config/types";
342
343
 
343
344
  export const pricingConfig: PricingConfig = {
344
345
  defaultPlan: "free",
@@ -377,12 +378,12 @@ ${s}
377
378
  ],
378
379
  prices: [
379
380
  {
380
- ${n}
381
+ ${i}
381
382
  interval: "month",
382
383
  amounts: { ${r}: 9 }
383
384
  },
384
385
  {
385
- ${n}
386
+ ${i}
386
387
  interval: "year",
387
388
  amounts: { ${r}: 90 },
388
389
  anchorAmounts: { ${r}: 109 }
@@ -406,14 +407,14 @@ ${a}
406
407
  ],
407
408
  prices: [
408
409
  {
409
- ${n}
410
+ ${i}
410
411
  interval: "month",
411
412
  amounts: { ${r}: 29 },
412
413
  anchorAmounts: { ${r}: 39 },
413
414
  featured: true
414
415
  },
415
416
  {
416
- ${n}
417
+ ${i}
417
418
  interval: "year",
418
419
  amounts: { ${r}: 290 },
419
420
  anchorAmounts: { ${r}: 349 }
@@ -428,11 +429,11 @@ ${o?" credits: { enabled: true }":" credits: { enabled: false }"},
428
429
  items: []
429
430
  }
430
431
  };
431
- `;await l(t,h)}var vi={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 Si(e){let t=W[e];return t.envVars.map((r,i)=>({key:r.name,...i===0?{comment:`# TODO: Add your ${t.label} OAuth credentials`}:{}}))}var Ei={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 ke(e,t){return t?e.map(r=>{let i=t[r.key];return i?{...r,defaultValue:i,comment:void 0}:r}):e}function Ie(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 Nt(e){return Array.from(crypto.getRandomValues(new Uint8Array(e))).map(t=>t.toString(16).padStart(2,"0")).join("")}function Sr(e){return e.architecture==="fullstack"?{apiUrl:"http://localhost:3000/api",baseUrl:"http://localhost:3000"}:{apiUrl:"http://localhost:3010",baseUrl:"http://localhost:3000"}}function wi(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 ft(e,t,r,i){e.push(i==="example"?`${t}=`:`${t}=${r}`)}function vr(e,t){let r=t==="example"?void 0:e.credentials,{apiUrl:i,baseUrl:n}=Sr(e),o=[];e.architecture==="separate"?o.push("# API Configuration","# Standalone backend's own URL (the frontend reaches it via the public var above).",`API_URL=${i}`,`BASE_URL=${n}`):o.push("# App","# (API_URL is derived from the frontend's *_PUBLIC_API_URL by the runtime env schema.)",`BASE_URL=${n}`),o.push("","# Database"),Ie(ke(N[e.databaseProvider].envVars,r),o),o.push("","# Cache"),Ie(ke(G[e.cacheProvider].envVars,r),o),o.push("","# Authentication"),t==="example"&&o.push("# Generate a strong secret, e.g. `openssl rand -hex 32`"),ft(o,"BETTER_AUTH_SECRET",crypto.randomUUID(),t),o.push("","# Content API (random secret; required when contentApi feature is enabled in @repo/config)"),ft(o,"CONTENT_API_KEY",Nt(32),t),o.push("","# Job Queue - Inngest","INNGEST_APP_ID=api"),ft(o,"INNGEST_EVENT_KEY",Nt(32),t),ft(o,"INNGEST_SIGNING_KEY",Nt(32),t),o.push("INNGEST_BASE_URL=http://127.0.0.1:8288"),e.architecture==="separate"&&(o.push("","# API Port (standalone backend)","API_PORT=3010"),o.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=vi[e.emailProvider];if(s&&(o.push("","# Email"),Ie(ke(s,r),o)),e.paymentProvider!=="none"){let a=Ei[e.paymentProvider];a&&(o.push("","# Payment"),Ie(ke(a,r),o))}if(e.socialProviders.length>0){o.push("","# Social auth");for(let a of e.socialProviders)Ie(ke(Si(a),r),o)}return e.demo&&(o.push("","# Captcha (Cloudflare Turnstile)"),Ie(ke([{key:"TURNSTILE_SECRET_KEY",comment:"# TODO: Add your Cloudflare Turnstile secret key"}],r),o)),o.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="),o.push(""),o.join(`
432
- `)}function bi(e){let{apiUrl:t}=Sr(e),r=e.frontend==="nextjs"?"NEXT_PUBLIC_API_URL":"NUXT_PUBLIC_API_URL",i=["# API Configuration",`${r}=${t}`],n=wi(e);return n&&e.architecture==="separate"&&i.push("","# Production (uncomment and replace with your deployed hostnames):",`# ${r}=${n.backend}`),i.push(""),i.join(`
433
- `)}async function Er(e){let t=bi(e);await l(`${e.projectDir}/.env`,t+`
434
- `+vr(e,"env")),await l(`${e.projectDir}/.env.example`,t+`
435
- `+vr(e,"example"))}import{join as Ai}from"path";async function wr(e){let t=[...e.dockerServices];if(N[e.databaseProvider].managed&&(t=t.filter(o=>o!=="postgres")),G[e.cacheProvider].managed&&(t=t.filter(o=>o!=="redis")),t.length===0)return!1;let r=[],i=[];t.includes("postgres")&&(r.push(` postgres:
432
+ `;await l(t,h)}var Ai={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 Ti(e){let t=W[e];return t.envVars.map((r,n)=>({key:r.name,...n===0?{comment:`# TODO: Add your ${t.label} OAuth credentials`}:{}}))}var ki={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 Ie(e,t){return t?e.map(r=>{let n=t[r.key];return n?{...r,defaultValue:n,comment:void 0}:r}):e}function Pe(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 $t(e){return Array.from(crypto.getRandomValues(new Uint8Array(e))).map(t=>t.toString(16).padStart(2,"0")).join("")}function Tr(e){return e.architecture==="fullstack"?{apiUrl:"http://localhost:3000/api",baseUrl:"http://localhost:3000"}:{apiUrl:"http://localhost:3010",baseUrl:"http://localhost:3000"}}function Ii(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 gt(e,t,r,n){e.push(n==="example"?`${t}=`:`${t}=${r}`)}function Ar(e,t){let r=t==="example"?void 0:e.credentials,{apiUrl:n,baseUrl:i}=Tr(e),o=[];e.architecture==="separate"?o.push("# API Configuration","# Standalone backend's own URL (the frontend reaches it via the public var above).",`API_URL=${n}`,`BASE_URL=${i}`):o.push("# App","# (API_URL is derived from the frontend's *_PUBLIC_API_URL by the runtime env schema.)",`BASE_URL=${i}`),o.push("","# Database"),Pe(Ie(N[e.databaseProvider].envVars,r),o),o.push("","# Cache"),Pe(Ie(G[e.cacheProvider].envVars,r),o),o.push("","# Authentication"),t==="example"&&o.push("# Generate a strong secret, e.g. `openssl rand -hex 32`"),gt(o,"BETTER_AUTH_SECRET",crypto.randomUUID(),t),o.push("","# Content API (random secret; required when contentApi feature is enabled in @repo/config)"),gt(o,"CONTENT_API_KEY",$t(32),t),o.push("","# Job Queue - Inngest","INNGEST_APP_ID=api"),gt(o,"INNGEST_EVENT_KEY",$t(32),t),gt(o,"INNGEST_SIGNING_KEY",$t(32),t),o.push("INNGEST_BASE_URL=http://127.0.0.1:8288"),e.architecture==="separate"&&(o.push("","# API Port (standalone backend)","API_PORT=3010"),o.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=Ai[e.emailProvider];if(s&&(o.push("","# Email"),Pe(Ie(s,r),o)),e.paymentProvider!=="none"){let a=ki[e.paymentProvider];a&&(o.push("","# Payment"),Pe(Ie(a,r),o))}if(e.socialProviders.length>0){o.push("","# Social auth");for(let a of e.socialProviders)Pe(Ie(Ti(a),r),o)}return e.demo&&(o.push("","# Captcha (Cloudflare Turnstile)"),Pe(Ie([{key:"TURNSTILE_SECRET_KEY",comment:"# TODO: Add your Cloudflare Turnstile secret key"}],r),o)),o.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="),o.push(""),o.join(`
433
+ `)}function Pi(e){let{apiUrl:t}=Tr(e),r=e.frontend==="nextjs"?"NEXT_PUBLIC_API_URL":"NUXT_PUBLIC_API_URL",n=["# API Configuration",`${r}=${t}`],i=Ii(e);return i&&e.architecture==="separate"&&n.push("","# Production (uncomment and replace with your deployed hostnames):",`# ${r}=${i.backend}`),n.push(""),n.join(`
434
+ `)}async function kr(e){let t=Pi(e);await l(`${e.projectDir}/.env`,t+`
435
+ `+Ar(e,"env")),await l(`${e.projectDir}/.env.example`,t+`
436
+ `+Ar(e,"example"))}import{join as _i}from"path";async function Ir(e){let t=[...e.dockerServices];if(N[e.databaseProvider].managed&&(t=t.filter(o=>o!=="postgres")),G[e.cacheProvider].managed&&(t=t.filter(o=>o!=="redis")),t.length===0)return!1;let r=[],n=[];t.includes("postgres")&&(r.push(` postgres:
436
437
  image: postgres:18-alpine
437
438
  ports:
438
439
  - "\${POSTGRES_PORT:-5432}:5432"
@@ -447,7 +448,7 @@ ${o?" credits: { enabled: true }":" credits: { enabled: false }"},
447
448
  test: ["CMD-SHELL", "pg_isready -U postgres"]
448
449
  interval: 5s
449
450
  timeout: 5s
450
- retries: 5`),i.push(" postgres_data:")),t.includes("redis")&&(r.push(` redis:
451
+ retries: 5`),n.push(" postgres_data:")),t.includes("redis")&&(r.push(` redis:
451
452
  image: redis:8-alpine
452
453
  ports:
453
454
  - "\${REDIS_PORT:-6379}:6379"
@@ -457,7 +458,7 @@ ${o?" credits: { enabled: true }":" credits: { enabled: false }"},
457
458
  test: ["CMD", "redis-cli", "ping"]
458
459
  interval: 5s
459
460
  timeout: 5s
460
- retries: 5`),i.push(" redis_data:")),t.includes("mailpit")&&r.push(` mailpit:
461
+ retries: 5`),n.push(" redis_data:")),t.includes("mailpit")&&r.push(` mailpit:
461
462
  image: axllent/mailpit
462
463
  ports:
463
464
  - "\${MAILPIT_SMTP_PORT:-1025}:1025"
@@ -468,16 +469,16 @@ ${o?" credits: { enabled: true }":" credits: { enabled: false }"},
468
469
  image: inngest/inngest:v1.17.4
469
470
  ports:
470
471
  - "\${INNGEST_PORT:-8288}:8288"
471
- command: inngest dev`);let n=`services:
472
+ command: inngest dev`);let i=`services:
472
473
  ${r.join(`
473
474
 
474
475
  `)}
475
- `;return i.length>0&&(n+=`
476
+ `;return n.length>0&&(i+=`
476
477
  volumes:
477
- ${i.join(`
478
+ ${n.join(`
478
479
  `)}
479
- `),await l(Ai(e.projectDir,"infra/docker-compose.yml"),n),!0}import{join as Ti}from"path";function ki(e){let t=[];e.architecture==="separate"&&t.push({type:"node-terminal",request:"launch",name:"Backend",command:"pnpm dev",cwd:"${workspaceFolder}/apps/backend",skipFiles:["<node_internals>/**"],env:{NODE_ENV:"development"}});let r=e.frontend==="nextjs"?"Next.js":"Nuxt";if(t.push({type:"node-terminal",request:"launch",name:r,command:"pnpm dev",cwd:`\${workspaceFolder}/apps/web-${e.frontend==="nextjs"?"next":"nuxt"}`,skipFiles:["<node_internals>/**"],env:{NODE_ENV:"development"}}),e.docs&&t.push({type:"node-terminal",request:"launch",name:"Docs",command:"pnpm dev",cwd:"${workspaceFolder}/apps/docs",skipFiles:["<node_internals>/**"],env:{NODE_ENV:"development"}}),t.push({type:"node-terminal",request:"launch",name:"Inngest",command:"pnpm dev:inngest",cwd:"${workspaceFolder}",skipFiles:["<node_internals>/**"],env:{NODE_ENV:"development"}}),t.push({type:"node-terminal",request:"launch",name:"Mail",command:"pnpm dev:mail",cwd:"${workspaceFolder}",skipFiles:["<node_internals>/**"],env:{NODE_ENV:"development"}}),e.paymentProvider==="stripe"){let i=e.architecture==="separate"?"localhost:3010/auth/stripe/webhook":"localhost:3000/api/auth/stripe/webhook";t.push({type:"node-terminal",request:"launch",name:"Stripe",command:`stripe listen --forward-to ${i}`,cwd:"${workspaceFolder}",skipFiles:["<node_internals>/**"]})}return t}function Ii(e){let t=e.frontend==="nextjs"?"Next.js":"Nuxt",r=[];return e.architecture==="separate"?r.push({name:`Dev (${t} + Backend + Inngest)`,configurations:["Backend",t,"Inngest"]}):r.push({name:`Dev (${t} + Inngest)`,configurations:[t,"Inngest"]}),r}async function br(e){let t={version:"0.2.0",configurations:ki(e),compounds:Ii(e)};await l(Ti(e.projectDir,".vscode/launch.json"),JSON.stringify(t,null," ")+`
480
- `)}import{join as Pi}from"path";async function Ar(e){if(e.architecture!=="separate")return;await l(Pi(e.projectDir,"apps/backend/src/index.ts"),`import { serve } from "@hono/node-server";
480
+ `),await l(_i(e.projectDir,"infra/docker-compose.yml"),i),!0}import{join as Ri}from"path";function Oi(e){let t=[];e.architecture==="separate"&&t.push({type:"node-terminal",request:"launch",name:"Backend",command:"pnpm dev",cwd:"${workspaceFolder}/apps/backend",skipFiles:["<node_internals>/**"],env:{NODE_ENV:"development"}});let r=e.frontend==="nextjs"?"Next.js":"Nuxt";if(t.push({type:"node-terminal",request:"launch",name:r,command:"pnpm dev",cwd:`\${workspaceFolder}/apps/web-${e.frontend==="nextjs"?"next":"nuxt"}`,skipFiles:["<node_internals>/**"],env:{NODE_ENV:"development"}}),e.docs&&t.push({type:"node-terminal",request:"launch",name:"Docs",command:"pnpm dev",cwd:"${workspaceFolder}/apps/docs",skipFiles:["<node_internals>/**"],env:{NODE_ENV:"development"}}),t.push({type:"node-terminal",request:"launch",name:"Inngest",command:"pnpm dev:inngest",cwd:"${workspaceFolder}",skipFiles:["<node_internals>/**"],env:{NODE_ENV:"development"}}),t.push({type:"node-terminal",request:"launch",name:"Mail",command:"pnpm dev:mail",cwd:"${workspaceFolder}",skipFiles:["<node_internals>/**"],env:{NODE_ENV:"development"}}),e.paymentProvider==="stripe"){let n=e.architecture==="separate"?"localhost:3010/auth/stripe/webhook":"localhost:3000/api/auth/stripe/webhook";t.push({type:"node-terminal",request:"launch",name:"Stripe",command:`stripe listen --forward-to ${n}`,cwd:"${workspaceFolder}",skipFiles:["<node_internals>/**"]})}return t}function xi(e){let t=e.frontend==="nextjs"?"Next.js":"Nuxt",r=[];return e.architecture==="separate"?r.push({name:`Dev (${t} + Backend + Inngest)`,configurations:["Backend",t,"Inngest"]}):r.push({name:`Dev (${t} + Inngest)`,configurations:[t,"Inngest"]}),r}async function Pr(e){let t={version:"0.2.0",configurations:Oi(e),compounds:xi(e)};await l(Ri(e.projectDir,".vscode/launch.json"),JSON.stringify(t,null," ")+`
481
+ `)}import{join as Di}from"path";async function _r(e){if(e.architecture!=="separate")return;await l(Di(e.projectDir,"apps/backend/src/index.ts"),`import { serve } from "@hono/node-server";
481
482
  import app from "@repo/api";
482
483
  import { closeRedis, env, logger } from "@repo/runtime";
483
484
 
@@ -508,18 +509,18 @@ bootstrap().catch((error) => {
508
509
  logger.error("[Backend] Fatal error", error);
509
510
  process.exit(1);
510
511
  });
511
- `)}import{readFile as _i}from"fs/promises";import{join as Ri}from"path";var Oi=`export * from "./db/auth";
512
+ `)}import{readFile as Ci}from"fs/promises";import{join as Ni}from"path";var Li=`export * from "./db/auth";
512
513
  export * from "./db/schema";
513
- export type { User, Account, Organization, Member } from "./db/auth";`;async function Tr(e){let t=Ri(e.projectDir,"packages/database/src/index.ts"),r=Oi;try{let n=await _i(t,"utf-8"),o=xi(n);o&&(r=o)}catch{}let i;switch(e.databaseProvider){case"postgres":i=Di(r);break;case"neon":i=Ci(r);break;case"supabase":i=Ni(r);break}await l(t,i)}function xi(e){return e.split(`
514
- `).filter(i=>i.startsWith("export type ")||i.startsWith("export * from")).join(`
515
- `)}var Lt=`
514
+ export type { User, Account, Organization, Member } from "./db/auth";`;async function Rr(e){let t=Ni(e.projectDir,"packages/database/src/index.ts"),r=Li;try{let i=await Ci(t,"utf-8"),o=$i(i);o&&(r=o)}catch{}let n;switch(e.databaseProvider){case"postgres":n=Ui(r);break;case"neon":n=ji(r);break;case"supabase":n=Vi(r);break}await l(t,n)}function $i(e){return e.split(`
515
+ `).filter(n=>n.startsWith("export type ")||n.startsWith("export * from")).join(`
516
+ `)}var Ut=`
516
517
  /** Extract affected-row count from a delete/update result (works for pg + postgres-js). */
517
518
  export function affectedRowCount(result: unknown): number {
518
519
  if (typeof result !== "object" || result === null) return 0;
519
520
  const r = result as { rowCount?: number; count?: number };
520
521
  return r.rowCount ?? r.count ?? 0;
521
522
  }
522
- `;function Di(e){return`import { drizzle } from "drizzle-orm/node-postgres";
523
+ `;function Ui(e){return`import { drizzle } from "drizzle-orm/node-postgres";
523
524
  import { z } from "zod";
524
525
  import * as authSchema from "./db/auth";
525
526
  import * as appSchema from "./db/schema";
@@ -534,7 +535,7 @@ export const db = drizzle(parsed.data.DATABASE_URL, { schema });
534
535
  export const pool = db.$client;
535
536
 
536
537
  ${e}
537
- ${Lt}`}function Ci(e){return`import { neon } from "@neondatabase/serverless";
538
+ ${Ut}`}function ji(e){return`import { neon } from "@neondatabase/serverless";
538
539
  import { drizzle } from "drizzle-orm/neon-http";
539
540
  import { z } from "zod";
540
541
  import * as authSchema from "./db/auth";
@@ -550,7 +551,7 @@ const sql = neon(parsed.data.DATABASE_URL);
550
551
  export const db = drizzle(sql, { schema });
551
552
 
552
553
  ${e}
553
- ${Lt}`}function Ni(e){return`import { drizzle } from "drizzle-orm/postgres-js";
554
+ ${Ut}`}function Vi(e){return`import { drizzle } from "drizzle-orm/postgres-js";
554
555
  import postgres from "postgres";
555
556
  import { z } from "zod";
556
557
  import * as authSchema from "./db/auth";
@@ -566,7 +567,7 @@ const client = postgres(parsed.data.DATABASE_URL);
566
567
  export const db = drizzle(client, { schema });
567
568
 
568
569
  ${e}
569
- ${Lt}`}import{join as gt}from"path";async function kr(e){switch(e.cacheProvider){case"redis":await Li(e),await $i(e);break;case"upstash":await Ui(e),await ji(e);break}}async function Li(e){await l(gt(e.projectDir,"packages/runtime/src/redis.ts"),`import type { Store } from "hono-rate-limiter";
570
+ ${Ut}`}import{join as ht}from"path";async function Or(e){switch(e.cacheProvider){case"redis":await Mi(e),await Fi(e);break;case"upstash":await Bi(e),await Gi(e);break}}async function Mi(e){await l(ht(e.projectDir,"packages/runtime/src/redis.ts"),`import type { Store } from "hono-rate-limiter";
570
571
  import { Redis } from "ioredis";
571
572
  import { RedisStore, type RedisReply } from "rate-limit-redis";
572
573
  import { env } from "./env";
@@ -690,7 +691,7 @@ export async function closeRedis() {
690
691
  closed = true;
691
692
  }
692
693
  }
693
- `)}async function $i(e){await l(gt(e.projectDir,"packages/runtime/src/mutex.ts"),`import { Mutex } from "redis-semaphore";
694
+ `)}async function Fi(e){await l(ht(e.projectDir,"packages/runtime/src/mutex.ts"),`import { Mutex } from "redis-semaphore";
694
695
  import { redis } from "./redis";
695
696
 
696
697
  export class MutexTimeoutError extends Error {
@@ -727,7 +728,7 @@ export async function withMutex<T>(
727
728
  await mutex.release();
728
729
  }
729
730
  }
730
- `)}async function Ui(e){await l(gt(e.projectDir,"packages/runtime/src/redis.ts"),`import { Redis } from "@upstash/redis";
731
+ `)}async function Bi(e){await l(ht(e.projectDir,"packages/runtime/src/redis.ts"),`import { Redis } from "@upstash/redis";
731
732
  import type { Store } from "hono-rate-limiter";
732
733
  import { env } from "./env";
733
734
 
@@ -840,7 +841,7 @@ export const limiterStore: Store = createLimiterStore();
840
841
 
841
842
  /** No persistent connection to close with Upstash REST. */
842
843
  export async function closeRedis(): Promise<void> {}
843
- `)}async function ji(e){await l(gt(e.projectDir,"packages/runtime/src/mutex.ts"),`import { Lock } from "@upstash/lock";
844
+ `)}async function Gi(e){await l(ht(e.projectDir,"packages/runtime/src/mutex.ts"),`import { Lock } from "@upstash/lock";
844
845
  import { redis } from "./redis";
845
846
 
846
847
  export class MutexTimeoutError extends Error {
@@ -885,7 +886,7 @@ export async function withMutex<T>(
885
886
  await lock.release();
886
887
  }
887
888
  }
888
- `)}async function Ir(e){await l(`${e.projectDir}/packages/runtime/src/env.ts`,Vi(e))}function Vi(e){return`import { z } from "zod";
889
+ `)}async function xr(e){await l(`${e.projectDir}/packages/runtime/src/env.ts`,Ki(e))}function Ki(e){return`import { z } from "zod";
889
890
 
890
891
  const EnvSchema = z.object({
891
892
  NODE_ENV: z.enum(["development", "production"]).default("development"),
@@ -1003,17 +1004,17 @@ export const env = (() => {
1003
1004
  const parsedApiUrl = new URL(env.API_URL);
1004
1005
  export const apiBasePath = parsedApiUrl.pathname === "/" ? "" : parsedApiUrl.pathname;
1005
1006
  export const apiOrigin = parsedApiUrl.origin;
1006
- `}import{readFile as Mi}from"fs/promises";import{join as K}from"path";var ht={"@upstash/redis":"^1.37.0","@upstash/lock":"^0.2.1","@neondatabase/serverless":"^1.0.1",postgres:"^3.4.7"};async function _e(e){let t=await Mi(e,"utf-8");return JSON.parse(t)}async function Re(e,t){await l(e,JSON.stringify(t,null," ")+`
1007
- `)}function Ge(e,t){for(let r of t)delete e.dependencies?.[r],delete e.devDependencies?.[r]}function Pe(e,t,r,i=!1){let n=i?"devDependencies":"dependencies";e[n]||(e[n]={}),e[n][t]=r}async function Pr(e){await Fi(e),await Bi(e),await Gi(e),await Ki(e),e.frontend==="nextjs"?await Hi(e):await zi(e)}async function Fi(e){let t=K(e.projectDir,"packages/api/package.json"),r=await _e(t);Ge(r,["sharp","@types/sharp"]),await Re(t,r)}async function Bi(e){let t=K(e.projectDir,"packages/runtime/package.json"),r=await _e(t);e.cacheProvider==="upstash"&&(Ge(r,["ioredis","rate-limit-redis","redis-semaphore"]),Pe(r,"@upstash/redis",ht["@upstash/redis"]),Pe(r,"@upstash/lock",ht["@upstash/lock"])),await Re(t,r)}async function Gi(e){let t=K(e.projectDir,"packages/database/package.json"),r=await _e(t);e.databaseProvider==="neon"?(Ge(r,["pg","@types/pg"]),Pe(r,"@neondatabase/serverless",ht["@neondatabase/serverless"])):e.databaseProvider==="supabase"&&(Ge(r,["pg","@types/pg"]),Pe(r,"postgres",ht.postgres)),await Re(t,r)}async function Ki(e){if(e.architecture!=="separate")return;let t=K(e.projectDir,"apps/backend/package.json"),r=await _e(t);e.deploymentTarget!=="node"&&Ge(r,["@hono/node-server"]),await Re(t,r)}async function zi(e){if(e.architecture!=="separate")return;let t=K(e.projectDir,"apps/web-nuxt");await Dt(K(t,"server/api/[...paths].ts"),t);let r=K(t,"package.json"),i=await _e(r),n=i.dependencies?.["@repo/api"];n&&(delete i.dependencies?.["@repo/api"],Pe(i,"@repo/api",n,!0)),await Re(r,i)}async function Hi(e){if(e.architecture!=="separate")return;let t=K(e.projectDir,"apps/web-next");await Dt(K(t,"app/api/[[...rest]]/route.ts"),t);let r=K(t,"package.json"),i=await _e(r),n=i.dependencies?.["@repo/api"];n&&(delete i.dependencies?.["@repo/api"],Pe(i,"@repo/api",n,!0)),await Re(r,i)}import{readFile as Rr}from"fs/promises";import{join as Or}from"path";async function xr(e){let t=Or(e.projectDir,"turbo.json"),r;try{r=await Rr(t,"utf-8")}catch{return}let i=JSON.parse(r),n=i.tasks?.build;if(!n)return;let o=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(n.env)){let d=n.env.filter(y=>y!==o);d.length!==n.env.length&&(n.env=d,a=!0)}if(Array.isArray(n.outputs)){let d=n.outputs.filter(y=>!s.has(y));d.length!==n.outputs.length&&(n.outputs=d,a=!0)}a&&await l(t,JSON.stringify(i,null," ")+`
1008
- `)}var Yi=["base.json","node.json","next.json"],_r="GenerateSaaS ";async function Dr(e){if(!e.demo)for(let t of Yi){let r=Or(e.projectDir,"tooling/typescript",t),i;try{i=await Rr(r,"utf-8")}catch{continue}let n=JSON.parse(i);typeof n.display!="string"||!n.display.startsWith(_r)||(n.display=n.display.slice(_r.length),await l(r,JSON.stringify(n,null," ")+`
1009
- `))}}import{readFile as Ji,rm as Wi}from"fs/promises";import{existsSync as Cr}from"fs";import{join as Nr}from"path";async function Lr(e){if(e.frontend==="nuxt")return;let t=Nr(e.projectDir,"packages/i18n/package.json");if(!Cr(t))throw new Error(`pruneI18nNuxt: expected ${t} to exist - did the i18n package move?`);let r=JSON.parse(await Ji(t,"utf-8")),i=!!(r.exports?.["./module"]??r.exports?.["./nuxt"]),n=!1;if(r.exports)for(let s of["./module","./nuxt"])s in r.exports&&(delete r.exports[s],n=!0);r.devDependencies&&"@nuxt/kit"in r.devDependencies&&(delete r.devDependencies["@nuxt/kit"],n=!0),n&&await l(t,JSON.stringify(r,null," ")+`
1010
- `);let o=Nr(e.projectDir,"packages/i18n/nuxt");if(i&&!Cr(o))throw new Error(`pruneI18nNuxt: packages/i18n declares a Nuxt export surface but ${o} is missing - did the i18n Nuxt module move?`);await Wi(o,{recursive:!0,force:!0})}import{readFile as $r,rm as qi}from"fs/promises";import{join as $t}from"path";async function Ur(e){e.cacheProvider==="upstash"&&await Promise.all([Xi(e.projectDir),Zi(e.projectDir),qi($t(e.projectDir,"packages/runtime/tests/redis.test.ts"),{force:!0})])}async function Xi(e){let t=$t(e,"packages/runtime/tests/setup.ts"),r;try{r=await $r(t,"utf-8")}catch{return}let i=r.replace(/\tREDIS_URL:\s*"[^"]*",?\n/,` UPSTASH_REDIS_REST_URL: "https://test.upstash.io",
1007
+ `}import{readFile as zi}from"fs/promises";import{join as K}from"path";var yt={"@upstash/redis":"^1.37.0","@upstash/lock":"^0.2.1","@neondatabase/serverless":"^1.0.1",postgres:"^3.4.7"};async function Re(e){let t=await zi(e,"utf-8");return JSON.parse(t)}async function Oe(e,t){await l(e,JSON.stringify(t,null," ")+`
1008
+ `)}function Ke(e,t){for(let r of t)delete e.dependencies?.[r],delete e.devDependencies?.[r]}function _e(e,t,r,n=!1){let i=n?"devDependencies":"dependencies";e[i]||(e[i]={}),e[i][t]=r}async function Dr(e){await Hi(e),await Yi(e),await Ji(e),await Wi(e),e.frontend==="nextjs"?await Xi(e):await qi(e)}async function Hi(e){let t=K(e.projectDir,"packages/api/package.json"),r=await Re(t);Ke(r,["sharp","@types/sharp"]),await Oe(t,r)}async function Yi(e){let t=K(e.projectDir,"packages/runtime/package.json"),r=await Re(t);e.cacheProvider==="upstash"&&(Ke(r,["ioredis","rate-limit-redis","redis-semaphore"]),_e(r,"@upstash/redis",yt["@upstash/redis"]),_e(r,"@upstash/lock",yt["@upstash/lock"])),await Oe(t,r)}async function Ji(e){let t=K(e.projectDir,"packages/database/package.json"),r=await Re(t);e.databaseProvider==="neon"?(Ke(r,["pg","@types/pg"]),_e(r,"@neondatabase/serverless",yt["@neondatabase/serverless"])):e.databaseProvider==="supabase"&&(Ke(r,["pg","@types/pg"]),_e(r,"postgres",yt.postgres)),await Oe(t,r)}async function Wi(e){if(e.architecture!=="separate")return;let t=K(e.projectDir,"apps/backend/package.json"),r=await Re(t);e.deploymentTarget!=="node"&&Ke(r,["@hono/node-server"]),await Oe(t,r)}async function qi(e){if(e.architecture!=="separate")return;let t=K(e.projectDir,"apps/web-nuxt");await Nt(K(t,"server/api/[...paths].ts"),t);let r=K(t,"package.json"),n=await Re(r),i=n.dependencies?.["@repo/api"];i&&(delete n.dependencies?.["@repo/api"],_e(n,"@repo/api",i,!0)),await Oe(r,n)}async function Xi(e){if(e.architecture!=="separate")return;let t=K(e.projectDir,"apps/web-next");await Nt(K(t,"app/api/[[...rest]]/route.ts"),t);let r=K(t,"package.json"),n=await Re(r),i=n.dependencies?.["@repo/api"];i&&(delete n.dependencies?.["@repo/api"],_e(n,"@repo/api",i,!0)),await Oe(r,n)}import{readFile as Nr}from"fs/promises";import{join as Lr}from"path";async function $r(e){let t=Lr(e.projectDir,"turbo.json"),r;try{r=await Nr(t,"utf-8")}catch{return}let n=JSON.parse(r),i=n.tasks?.build;if(!i)return;let o=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(i.env)){let d=i.env.filter(y=>y!==o);d.length!==i.env.length&&(i.env=d,a=!0)}if(Array.isArray(i.outputs)){let d=i.outputs.filter(y=>!s.has(y));d.length!==i.outputs.length&&(i.outputs=d,a=!0)}a&&await l(t,JSON.stringify(n,null," ")+`
1009
+ `)}var Zi=["base.json","node.json","next.json"],Cr="GenerateSaaS ";async function Ur(e){if(!e.demo)for(let t of Zi){let r=Lr(e.projectDir,"tooling/typescript",t),n;try{n=await Nr(r,"utf-8")}catch{continue}let i=JSON.parse(n);typeof i.display!="string"||!i.display.startsWith(Cr)||(i.display=i.display.slice(Cr.length),await l(r,JSON.stringify(i,null," ")+`
1010
+ `))}}import{readFile as Qi,rm as eo}from"fs/promises";import{existsSync as jr}from"fs";import{join as Vr}from"path";async function Mr(e){if(e.frontend==="nuxt")return;let t=Vr(e.projectDir,"packages/i18n/package.json");if(!jr(t))throw new Error(`pruneI18nNuxt: expected ${t} to exist - did the i18n package move?`);let r=JSON.parse(await Qi(t,"utf-8")),n=!!(r.exports?.["./module"]??r.exports?.["./nuxt"]),i=!1;if(r.exports)for(let s of["./module","./nuxt"])s in r.exports&&(delete r.exports[s],i=!0);r.devDependencies&&"@nuxt/kit"in r.devDependencies&&(delete r.devDependencies["@nuxt/kit"],i=!0),i&&await l(t,JSON.stringify(r,null," ")+`
1011
+ `);let o=Vr(e.projectDir,"packages/i18n/nuxt");if(n&&!jr(o))throw new Error(`pruneI18nNuxt: packages/i18n declares a Nuxt export surface but ${o} is missing - did the i18n Nuxt module move?`);await eo(o,{recursive:!0,force:!0})}import{readFile as Fr,rm as to}from"fs/promises";import{join as jt}from"path";async function Br(e){e.cacheProvider==="upstash"&&await Promise.all([ro(e.projectDir),no(e.projectDir),to(jt(e.projectDir,"packages/runtime/tests/redis.test.ts"),{force:!0})])}async function ro(e){let t=jt(e,"packages/runtime/tests/setup.ts"),r;try{r=await Fr(t,"utf-8")}catch{return}let n=r.replace(/\tREDIS_URL:\s*"[^"]*",?\n/,` UPSTASH_REDIS_REST_URL: "https://test.upstash.io",
1011
1012
  UPSTASH_REDIS_REST_TOKEN: "test-token",
1012
- `);i!==r&&await l(t,i)}async function Zi(e){let t=$t(e,"packages/api/tests/setup.ts"),r;try{r=await $r(t,"utf-8")}catch{return}let i=r;i=i.replace(/\tREDIS_URL:\s*"[^"]*",?\n/g,` UPSTASH_REDIS_REST_URL: "https://test.upstash.io",
1013
+ `);n!==r&&await l(t,n)}async function no(e){let t=jt(e,"packages/api/tests/setup.ts"),r;try{r=await Fr(t,"utf-8")}catch{return}let n=r;n=n.replace(/\tREDIS_URL:\s*"[^"]*",?\n/g,` UPSTASH_REDIS_REST_URL: "https://test.upstash.io",
1013
1014
  UPSTASH_REDIS_REST_TOKEN: "test-token",
1014
- `),i=i.replace(/vi\.mock\("ioredis"[\s\S]*?\n\}\);\n\n?/,""),i=i.replace(/vi\.mock\("rate-limit-redis"[\s\S]*?\n\}\);\n\n?/,""),i=i.replace(/\t\t\tREDIS_URL:\s*"[^"]*",?\n/,` UPSTASH_REDIS_REST_URL: "https://test.upstash.io",
1015
+ `),n=n.replace(/vi\.mock\("ioredis"[\s\S]*?\n\}\);\n\n?/,""),n=n.replace(/vi\.mock\("rate-limit-redis"[\s\S]*?\n\}\);\n\n?/,""),n=n.replace(/\t\t\tREDIS_URL:\s*"[^"]*",?\n/,` UPSTASH_REDIS_REST_URL: "https://test.upstash.io",
1015
1016
  UPSTASH_REDIS_REST_TOKEN: "test-token",
1016
- `),i=i.replace(/(^vi\.mock\("@repo\/runtime")/m,`vi.mock("@upstash/redis", () => {
1017
+ `),n=n.replace(/(^vi\.mock\("@repo\/runtime")/m,`vi.mock("@upstash/redis", () => {
1017
1018
  class Redis {
1018
1019
  get = vi.fn().mockResolvedValue(null);
1019
1020
  set = vi.fn().mockResolvedValue("OK");
@@ -1030,8 +1031,8 @@ vi.mock("@upstash/lock", () => {
1030
1031
  return { Lock };
1031
1032
  });
1032
1033
 
1033
- $1`),i!==r&&await l(t,i)}import{readdir as Qi,readFile as eo,rm as Ke}from"fs/promises";import{join as ge}from"path";var to=new Set(["ci.yml"]),ro=["cli","cli:clean","demo:bench","playground:regen","playground:test","playground:test:units"];async function jr(e){let t=ge(e.projectDir,".github/workflows"),r=await Qi(t).catch(()=>[]);for(let i of r)to.has(i)||await Ke(ge(t,i),{recursive:!0,force:!0})}async function Vr(e){let t=ge(e.projectDir,"package.json"),r=await eo(t,"utf-8"),i=JSON.parse(r),n=!1;if(i.scripts){for(let o of ro)o in i.scripts&&(delete i.scripts[o],n=!0);if(!Ze(e))for(let o of["infra","infra:stop"])o in i.scripts&&(delete i.scripts[o],n=!0)}n&&await l(t,JSON.stringify(i,null," ")+`
1034
- `)}async function Mr(e){let t=e.frontend==="nextjs"?"apps/web-nuxt":"apps/web-next";await Ke(ge(e.projectDir,t),{recursive:!0,force:!0})}async function Fr(e){e.docs||await Ke(ge(e.projectDir,"apps/docs"),{recursive:!0})}async function Br(e){let t=e.frontend==="nextjs"?"docs/nuxt":"docs/next";await Ke(ge(e.projectDir,t),{recursive:!0,force:!0}),await Ke(ge(e.projectDir,"docs/index.mdx"))}import{join as no}from"path";async function Gr(e){let t=io(e);await l(no(e.projectDir,".github/workflows/ci.yml"),t)}function io(e){let t=N[e.databaseProvider].managed,r=e.cacheProvider==="upstash",i=e.frontend==="nuxt",n=t?"":` services:
1034
+ $1`),n!==r&&await l(t,n)}import{readdir as io,readFile as oo,rm as ze}from"fs/promises";import{join as ge}from"path";var so=new Set(["ci.yml"]),ao=["cli","cli:clean","demo:bench","playground:regen","playground:test","playground:test:units"];async function Gr(e){let t=ge(e.projectDir,".github/workflows"),r=await io(t).catch(()=>[]);for(let n of r)so.has(n)||await ze(ge(t,n),{recursive:!0,force:!0})}async function Kr(e){let t=ge(e.projectDir,"package.json"),r=await oo(t,"utf-8"),n=JSON.parse(r),i=!1;if(n.scripts){for(let o of ao)o in n.scripts&&(delete n.scripts[o],i=!0);if(!Qe(e))for(let o of["infra","infra:stop"])o in n.scripts&&(delete n.scripts[o],i=!0)}i&&await l(t,JSON.stringify(n,null," ")+`
1035
+ `)}async function zr(e){let t=e.frontend==="nextjs"?"apps/web-nuxt":"apps/web-next";await ze(ge(e.projectDir,t),{recursive:!0,force:!0})}async function Hr(e){e.docs||await ze(ge(e.projectDir,"apps/docs"),{recursive:!0})}async function Yr(e){let t=e.frontend==="nextjs"?"docs/nuxt":"docs/next";await ze(ge(e.projectDir,t),{recursive:!0,force:!0}),await ze(ge(e.projectDir,"docs/index.mdx"))}import{join as co}from"path";async function Jr(e){let t=lo(e);await l(co(e.projectDir,".github/workflows/ci.yml"),t)}function lo(e){let t=N[e.databaseProvider].managed,r=e.cacheProvider==="upstash",n=e.frontend==="nuxt",i=t?"":` services:
1035
1036
  postgres:
1036
1037
  image: postgres:18-alpine
1037
1038
  env:
@@ -1085,7 +1086,7 @@ jobs:
1085
1086
  steps:
1086
1087
  - uses: actions/checkout@v6
1087
1088
  - uses: ./.github/actions/setup
1088
- ${i?` # vue-tsc on web-nuxt OOMs on the GitHub runner's default heap once
1089
+ ${n?` # vue-tsc on web-nuxt OOMs on the GitHub runner's default heap once
1089
1090
  # the type graph (Nuxt + Pinia + vue-i18n + content collections)
1090
1091
  # crosses a threshold. Bump to 6 GB; ubuntu-latest has ~7 GB RAM.
1091
1092
  - run: pnpm check-types
@@ -1096,7 +1097,7 @@ ${i?` # vue-tsc on web-nuxt OOMs on the GitHub runner's default heap once
1096
1097
  name: Test
1097
1098
  runs-on: ubuntu-latest
1098
1099
  timeout-minutes: 10
1099
- ${n} env:
1100
+ ${i} env:
1100
1101
  CONTENT_API_KEY: test-contentapi-key-16chars${s}${a}${d}
1101
1102
  STORAGE_REGION: test
1102
1103
  STORAGE_ENDPOINT: http://test
@@ -1111,45 +1112,45 @@ ${n} env:
1111
1112
  run: echo "DATABASE_URL=${o}" > .env
1112
1113
 
1113
1114
  - run: pnpm test
1114
- `}import{readFile as Kr}from"fs/promises";import{existsSync as oo}from"fs";import{join as Ut}from"path";var zr="@repo/database";function so(e){return e?"pnpm -F @repo/database reset && pnpm -F @repo/database push --force":"pnpm -F @repo/database migrate"}function ao(e,t){switch(e){case"fullstack":return t==="nextjs"?"web-next":"web-nuxt";case"separate":return"backend";default:{let r=e;throw new Error(`schemaOwnerApp: unhandled architecture "${String(r)}"`)}}}async function co(e,t,r){let i=await Kr(e,"utf-8"),n=JSON.parse(i),o=n.scripts?.[t];if(!o)throw new Error(`Cannot prepend to missing script "${t}" in ${e}`);o.includes(zr)||(n.scripts={...n.scripts,[t]:`${r} && ${o}`},await l(e,JSON.stringify(n,null," ")+`
1115
- `))}async function lo(e,t){let r=oo(e)?JSON.parse(await Kr(e,"utf-8")):{$schema:"https://openapi.vercel.sh/vercel.json"},i=r.buildCommand?.trim()||"pnpm build";i.includes(zr)||(r.buildCommand=`${t} && ${i}`,await l(e,JSON.stringify(r,null," ")+`
1116
- `))}async function Hr(e){let t=so(e.demo===!0),r=ao(e.architecture,e.frontend),i=Ut(e.projectDir,"apps",r);switch(e.deploymentTarget){case"node":await co(Ut(i,"package.json"),"start",t);return;case"vercel":await lo(Ut(i,"vercel.json"),t);return;default:{let n=e.deploymentTarget;throw new Error(`generateDeployScripts: unhandled deployment target "${String(n)}"`)}}}import{readFile as po}from"fs/promises";import{existsSync as uo}from"fs";import{join as yt}from"path";var mo=["stripe","polar"];async function Yr(e){let t=yt(e.projectDir,"packages/payments/src"),r=e.paymentProvider,i=`// Active payment provider. To switch, change the path below to
1115
+ `}import{readFile as Wr}from"fs/promises";import{existsSync as po}from"fs";import{join as Vt}from"path";var qr="@repo/database";function uo(e){return e?"pnpm -F @repo/database reset && pnpm -F @repo/database push --force":"pnpm -F @repo/database migrate"}function mo(e,t){switch(e){case"fullstack":return t==="nextjs"?"web-next":"web-nuxt";case"separate":return"backend";default:{let r=e;throw new Error(`schemaOwnerApp: unhandled architecture "${String(r)}"`)}}}async function fo(e,t,r){let n=await Wr(e,"utf-8"),i=JSON.parse(n),o=i.scripts?.[t];if(!o)throw new Error(`Cannot prepend to missing script "${t}" in ${e}`);o.includes(qr)||(i.scripts={...i.scripts,[t]:`${r} && ${o}`},await l(e,JSON.stringify(i,null," ")+`
1116
+ `))}async function go(e,t){let r=po(e)?JSON.parse(await Wr(e,"utf-8")):{$schema:"https://openapi.vercel.sh/vercel.json"},n=r.buildCommand?.trim()||"pnpm build";n.includes(qr)||(r.buildCommand=`${t} && ${n}`,await l(e,JSON.stringify(r,null," ")+`
1117
+ `))}async function Xr(e){let t=uo(e.demo===!0),r=mo(e.architecture,e.frontend),n=Vt(e.projectDir,"apps",r);switch(e.deploymentTarget){case"node":await fo(Vt(n,"package.json"),"start",t);return;case"vercel":await go(Vt(n,"vercel.json"),t);return;default:{let i=e.deploymentTarget;throw new Error(`generateDeployScripts: unhandled deployment target "${String(i)}"`)}}}import{readFile as ho}from"fs/promises";import{existsSync as yo}from"fs";import{join as vt}from"path";var vo=["stripe","polar"];async function Zr(e){let t=vt(e.projectDir,"packages/payments/src"),r=e.paymentProvider,n=`// Active payment provider. To switch, change the path below to
1117
1118
  // "./polar/index" or "./none/index" (other folders are kept in place).
1118
1119
  export { ops } from "./${r}/index";
1119
- `;await l(yt(t,"providers/index.ts"),i),await l(yt(t,"index.ts"),await fo(t,r))}async function fo(e,t){let r=yt(e,"index.ts");if(!uo(r))throw new Error(`generatePaymentBarrel: expected ${r} to exist - did packages/payments move?`);let i=await po(r,"utf-8"),n=mo.filter(s=>s!==t);return i.split(`
1120
- `).filter(s=>!n.some(a=>s.includes(`./providers/${a}/`))).join(`
1121
- `)}import{readdir as go,readFile as ho}from"fs/promises";import{join as Jr}from"path";async function Wr(e){if(e.demo)return;let t=e.appName.trim()||e.projectName,r=JSON.stringify(t).slice(1,-1),i=Jr(e.projectDir,"packages/i18n/translations"),n;try{n=await go(i)}catch{return}for(let o of n){let s=Jr(i,o,"web.json"),a;try{a=await ho(s,"utf-8")}catch{continue}let d=a.replaceAll("GenerateSaaS",r);d!==a&&await l(s,d)}}import{readFile as yo}from"fs/promises";import{join as qr}from"path";var vo=[".nuxt/",".nuxt",".nitro/",".nitro",".output/",".output","_locales/"],So=[".next/",".next",".svelte-kit/",".svelte-kit",".wrangler/",".wrangler",".dev.vars"];async function Zr(e){let r=e.frontend==="nuxt"?So:vo;await Xr(qr(e.projectDir,".gitignore"),r),await Xr(qr(e.projectDir,".dockerignore"),r)}async function Xr(e,t){let r;try{r=await yo(e,"utf-8")}catch{return}let i=new Set(t),n=r.split(`
1122
- `).filter(o=>!i.has(o.trim()));n.length!==r.split(`
1123
- `).length&&await l(e,n.join(`
1124
- `))}async function vt(e){let t=e.projectDir;e.demo||await pr(t),await dr(t,e.aiTools),await ur(t,e.frontend),await fr(e),await gr(e),await hr(e),e.demo||await yr(e),await Er(e);let r=await wr(e);return await br(e),await Ar(e),await Tr(e),await kr(e),await Ir(e),await Pr(e),await xr(e),await Dr(e),await Lr(e),await Ur(e),await jr(e),await Gr(e),await Vr(e),await Mr(e),await Fr(e),await Br(e),await Hr(e),await Yr(e),await Wr(e),await Zr(e),{dockerComposeGenerated:r}}import{basename as en,join as tn,relative as wo}from"path";import{createHash as Qr}from"crypto";import{readFile as Eo}from"fs/promises";async function St(e){let t=await Eo(e);return Qr("sha256").update(t).digest("hex")}function jt(e){return Qr("sha256").update(e).digest("hex")}var bo=new Set(["data",U]);function Ao(e){let t=e.split("/");for(let r of t)if(_t.has(r)||Rt.has(r)||bo.has(r)||r.startsWith(".env")&&!r.includes("example"))return!0;return!1}function rn(e,t){return{projectName:e.projectName??en(t),appName:e.appName??e.projectName??en(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,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 nn(e,t){let i=(await fe(e.projectDir,e.projectDir,Ao)).sort(),n=await Promise.all(i.map(async a=>[me(wo(e.projectDir,a)),await St(a)])),o=Object.fromEntries(n),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,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(tn(e.projectDir,Q),JSON.stringify(s,null," ")+`
1125
- `),await l(tn(e.projectDir,er),JSON.stringify(o,null," ")+`
1126
- `)}import{relative as To}from"path";async function Oe(e){let r=(await fe(e,e,Te)).sort(),i=await Promise.all(r.map(async n=>[me(To(e,n)),await St(n)]));return Object.fromEntries(i)}import{copyFile as ko,mkdir as Io,rm as Po}from"fs/promises";import{dirname as _o,join as on,relative as Ro}from"path";import{existsSync as Oo}from"fs";async function Vt(e,t){Oo(t)&&await Po(t,{recursive:!0,force:!0});let r=await fe(e,e,Te);for(let i of r){let n=me(Ro(e,i)),o=on(t,n);await Io(_o(o),{recursive:!0}),await ko(i,o)}}async function sn(e,t){await Vt(e,on(t,lt))}import{existsSync as xo}from"fs";import{readFile as an,readdir as Do}from"fs/promises";import{join as z,dirname as Co,resolve as No,sep as Lo}from"path";import{fileURLToPath as $o}from"url";var ze={"claude-code":".claude/skills",cursor:".cursor/skills",codex:".agents/skills","gemini-cli":".gemini/skills",windsurf:".windsurf/skills"},Vl=Object.values(ze),Mt="generatesaas-update",cn=Co($o(import.meta.url));function Uo(){let e=z(cn,"skill","content");return xo(e)?e:z(cn,"content")}function Ft(e){return!e||e.length===0?[]:e.map(t=>ze[t])}async function Bt(e,t,r,i){let n=Ft(i);for(let o of n){let s=z(e,o,Mt),a=z(s,"scripts"),d=z(s,"references");await mt(a),await mt(d),await l(z(s,"SKILL.md"),t.replaceAll("__SKILL_ROOT__",o)),await l(z(d,".gitkeep"),"");for(let[y,h]of Object.entries(r)){let f=No(a,y);f.startsWith(a+Lo)&&await l(f,h)}}}async function ln(e,t){let r=Uo(),i=await an(z(r,"SKILL.md"),"utf-8"),n=z(r,"scripts"),o=await Do(n),s={};for(let a of o)a!==".gitkeep"&&(s[a]=await an(z(n,a),"utf-8"));await Bt(e,i,s,t)}import{execFile as jo,execFileSync as Vo}from"child_process";import{access as pn,readFile as Mo}from"fs/promises";import{join as Gt}from"path";import*as P from"@clack/prompts";function ye(e){try{let t=process.platform==="win32"?"where":"which";return Vo(t,[e],{stdio:"ignore"}),!0}catch{return!1}}function he(e,t,r,i=3e5){return new Promise((n,o)=>{jo(e,t,{cwd:r,timeout:i},(s,a,d)=>{if(s){let y=String(a||"").trim(),f=[String(d||"").trim(),y].filter(Boolean).join(`
1120
+ `;await l(vt(t,"providers/index.ts"),n),await l(vt(t,"index.ts"),await So(t,r))}async function So(e,t){let r=vt(e,"index.ts");if(!yo(r))throw new Error(`generatePaymentBarrel: expected ${r} to exist - did packages/payments move?`);let n=await ho(r,"utf-8"),i=vo.filter(s=>s!==t);return n.split(`
1121
+ `).filter(s=>!i.some(a=>s.includes(`./providers/${a}/`))).join(`
1122
+ `)}import{readdir as Eo,readFile as wo}from"fs/promises";import{join as Qr}from"path";async function en(e){if(e.demo)return;let t=e.appName.trim()||e.projectName,r=JSON.stringify(t).slice(1,-1),n=Qr(e.projectDir,"packages/i18n/translations"),i;try{i=await Eo(n)}catch{return}for(let o of i){let s=Qr(n,o,"web.json"),a;try{a=await wo(s,"utf-8")}catch{continue}let d=a.replaceAll("GenerateSaaS",r);d!==a&&await l(s,d)}}import{readFile as bo}from"fs/promises";import{join as tn}from"path";var Ao=[".nuxt/",".nuxt",".nitro/",".nitro",".output/",".output","_locales/"],To=[".next/",".next",".svelte-kit/",".svelte-kit",".wrangler/",".wrangler",".dev.vars"];async function nn(e){let r=e.frontend==="nuxt"?To:Ao;await rn(tn(e.projectDir,".gitignore"),r),await rn(tn(e.projectDir,".dockerignore"),r)}async function rn(e,t){let r;try{r=await bo(e,"utf-8")}catch{return}let n=new Set(t),i=r.split(`
1123
+ `).filter(o=>!n.has(o.trim()));i.length!==r.split(`
1124
+ `).length&&await l(e,i.join(`
1125
+ `))}async function St(e){let t=e.projectDir;e.demo||(await mr(t),await fr(t),await gr(t)),await hr(t,e.aiTools),await yr(t,e.frontend),await Sr(e),await Er(e),await wr(e),e.demo||await br(e),await kr(e);let r=await Ir(e);return await Pr(e),await _r(e),await Rr(e),await Or(e),await xr(e),await Dr(e),await $r(e),await Ur(e),await Mr(e),await Br(e),await Gr(e),await Jr(e),await Kr(e),await zr(e),await Hr(e),await Yr(e),await Xr(e),await Zr(e),await en(e),await nn(e),{dockerComposeGenerated:r}}import{basename as sn,join as an,relative as Io}from"path";import{createHash as on}from"crypto";import{readFile as ko}from"fs/promises";async function Et(e){let t=await ko(e);return on("sha256").update(t).digest("hex")}function Mt(e){return on("sha256").update(e).digest("hex")}var Po=new Set(["data",U]);function _o(e){let t=e.split("/");for(let r of t)if(Rt.has(r)||Ot.has(r)||Po.has(r)||r.startsWith(".env")&&!r.includes("example"))return!0;return!1}function cn(e,t){return{projectName:e.projectName??sn(t),appName:e.appName??e.projectName??sn(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,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 ln(e,t){let n=(await fe(e.projectDir,e.projectDir,_o)).sort(),i=await Promise.all(n.map(async a=>[me(Io(e.projectDir,a)),await Et(a)])),o=Object.fromEntries(i),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,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(an(e.projectDir,Q),JSON.stringify(s,null," ")+`
1126
+ `),await l(an(e.projectDir,rr),JSON.stringify(o,null," ")+`
1127
+ `)}import{relative as Ro}from"path";async function xe(e){let r=(await fe(e,e,Te)).sort(),n=await Promise.all(r.map(async i=>[me(Ro(e,i)),await Et(i)]));return Object.fromEntries(n)}import{copyFile as Oo,mkdir as xo,rm as Do}from"fs/promises";import{dirname as Co,join as pn,relative as No}from"path";import{existsSync as Lo}from"fs";async function Ft(e,t){Lo(t)&&await Do(t,{recursive:!0,force:!0});let r=await fe(e,e,Te);for(let n of r){let i=me(No(e,n)),o=pn(t,i);await xo(Co(o),{recursive:!0}),await Oo(n,o)}}async function dn(e,t){await Ft(e,pn(t,pt))}import{existsSync as $o}from"fs";import{readFile as un,readdir as Uo}from"fs/promises";import{join as z,dirname as jo,resolve as Vo,sep as Mo}from"path";import{fileURLToPath as Fo}from"url";var He={"claude-code":".claude/skills",cursor:".cursor/skills",codex:".agents/skills","gemini-cli":".gemini/skills",windsurf:".windsurf/skills"},Kl=Object.values(He),Bt="generatesaas-update",mn=jo(Fo(import.meta.url));function Bo(){let e=z(mn,"skill","content");return $o(e)?e:z(mn,"content")}function Gt(e){return!e||e.length===0?[]:e.map(t=>He[t])}async function Kt(e,t,r,n){let i=Gt(n);for(let o of i){let s=z(e,o,Bt),a=z(s,"scripts"),d=z(s,"references");await ft(a),await ft(d),await l(z(s,"SKILL.md"),t.replaceAll("__SKILL_ROOT__",o)),await l(z(d,".gitkeep"),"");for(let[y,h]of Object.entries(r)){let f=Vo(a,y);f.startsWith(a+Mo)&&await l(f,h)}}}async function fn(e,t){let r=Bo(),n=await un(z(r,"SKILL.md"),"utf-8"),i=z(r,"scripts"),o=await Uo(i),s={};for(let a of o)a!==".gitkeep"&&(s[a]=await un(z(i,a),"utf-8"));await Kt(e,n,s,t)}import{execFile as Go,execFileSync as Ko}from"child_process";import{access as gn,readFile as zo}from"fs/promises";import{join as zt}from"path";import*as P from"@clack/prompts";function ye(e){try{let t=process.platform==="win32"?"where":"which";return Ko(t,[e],{stdio:"ignore"}),!0}catch{return!1}}function he(e,t,r,n=3e5){return new Promise((i,o)=>{Go(e,t,{cwd:r,timeout:n},(s,a,d)=>{if(s){let y=String(a||"").trim(),f=[String(d||"").trim(),y].filter(Boolean).join(`
1127
1128
  `);o(new Error(f?`${s.message}
1128
- ${f}`:s.message))}else n()})})}async function dn(e){if(!ye("pnpm"))return P.log.warn("pnpm not found. Skipping lockfile regeneration."),!1;try{return await he("pnpm",["install","--lockfile-only","--no-frozen-lockfile","--config.minimumReleaseAge=0"],e),!0}catch(t){let r=t instanceof Error?t.message:String(t);return P.log.warn(`Lockfile regeneration failed: ${r}`),P.log.warn("Deploys using --frozen-lockfile may fail."),!1}}async function un(e){if(!ye("pnpm"))return P.log.warn("pnpm not found. Skipping dependency installation."),P.log.info("Install pnpm: https://pnpm.io/installation"),!1;let t=P.spinner();t.start("Installing dependencies (this may take a minute)...");try{return await he("pnpm",["install","--config.minimumReleaseAge=0"],e),t.stop("Dependencies installed."),!0}catch(r){t.stop("Dependency installation failed.");let i=r instanceof Error?r.message:String(r);return P.log.warn(`pnpm install failed: ${i}`),P.log.warn("You can run it manually later."),!1}}async function mn(e){if(!ye("pnpm"))return!1;let t=P.spinner();t.start("Generating baseline database migration...");try{return await he("pnpm",["-F","@repo/database","generate"],e),t.stop("Baseline migration generated."),!0}catch(r){t.stop("Baseline migration generation failed.");let i=r instanceof Error?r.message:String(r);return P.log.warn(`Could not generate baseline migration: ${i}`),P.log.warn("Run 'pnpm -F @repo/database generate' before your first deploy."),!1}}async function fn(e){try{return await pn(Gt(e,".git")),P.log.info("Git repository already exists, skipping init."),!0}catch{}if(!ye("git"))return P.log.warn("git not found. Skipping repository initialization."),!1;let t=P.spinner();t.start("Initializing git repository...");try{return await he("git",["init"],e),await he("git",["add","-A"],e),await he("git",["commit","--no-verify","-m","Initial commit from GenerateSaaS"],e),t.stop("Git repository initialized."),!0}catch{return t.stop("Git initialization failed."),P.log.warn("You can run git init manually later."),!1}}async function gn(e){if(!ye("pnpm"))return!1;try{await pn(Gt(e,".git"))}catch{return!1}try{let t=JSON.parse(await Mo(Gt(e,"package.json"),"utf-8")),r=!!t.devDependencies?.["simple-git-hooks"],i=!!t["simple-git-hooks"];if(!r||!i)return!1}catch{return!1}try{return await he("pnpm",["exec","simple-git-hooks"],e),!0}catch{return P.log.warn("Could not install git hooks. Run 'pnpm exec simple-git-hooks' manually."),!1}}import*as xe from"@clack/prompts";import j from"picocolors";function hn(e,t){t.dockerComposeGenerated&&!t.dockerAvailable&&xe.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 o=e.dockerServices.map(s=>be[s].label).join(", ");r.push(`pnpm infra ${j.dim(`# ${o}`)}`)}if(r.push(`pnpm dev ${j.dim("# http://localhost:3000")}`),t.skippedCredentials.length>0&&(r.push(""),r.push(j.dim("Fill in remaining TODO values in .env"))),xe.note(r.join(`
1129
- `),j.yellow("Start Development")),t.dockerComposeGenerated){let o=[];o.push(`App ${j.cyan("http://localhost:3000")}`),e.architecture==="separate"&&o.push(`API ${j.cyan("http://localhost:3010")}`),e.dockerServices.includes("mailpit")&&o.push(`Mailpit ${j.cyan("http://localhost:8025")}`),e.dockerServices.includes("inngest")&&o.push(`Inngest ${j.cyan("http://localhost:8288")}`),xe.note(o.join(`
1130
- `),j.yellow("Dev Tools"))}let i=[],n=Fo(e);n.length>0&&i.push(`Set in production: ${j.dim(n.join(", "))}`),i.push("pnpm db:push # Run database migrations"),i.push(Bo(e)),xe.note(i.join(`
1131
- `),j.yellow("Deployment"))}function Fo(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 Bo(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 yn(e){let t={};if(e.name!==void 0){if(!at(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(!Ve.includes(e.frontend))throw new Error(`Invalid frontend "${e.frontend}". Valid values: ${Ve.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.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=Kt(e.docker,rt,"docker service")),e.aiTools!==void 0&&(t.aiTools=Kt(e.aiTools,nt,"AI tool")),e.socialProviders!==void 0&&(t.socialProviders=Kt(e.socialProviders,ot,"social provider")),e.currency!==void 0){if(!ae.includes(e.currency))throw new Error(`Invalid currency "${e.currency}". Valid values: ${ae.join(", ")}`);t.defaultCurrency=e.currency}if(e.deploy!==void 0){if(!ce.includes(e.deploy))throw new Error(`Invalid deployment target "${e.deploy}". Valid values: ${ce.join(", ")}`);t.deploymentTarget=e.deploy}if(e.database!==void 0){if(!le.includes(e.database))throw new Error(`Invalid database provider "${e.database}". Valid values: ${le.join(", ")}`);t.databaseProvider=e.database}if(e.cache!==void 0){if(!pe.includes(e.cache))throw new Error(`Invalid cache provider "${e.cache}". Valid values: ${pe.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 i;try{i=new URL(r)}catch{throw new Error(`Invalid --base-url "${e.baseUrl}". Must be an absolute URL like https://example.com.`)}if(i.protocol!=="http:"&&i.protocol!=="https:")throw new Error(`Invalid --base-url "${e.baseUrl}". Must use http or https.`);t.baseUrl=`${i.protocol}//${i.host}`}return t}var ne={projectName:"my-saas",frontend:"nextjs",architecture:"fullstack",paymentProvider:"stripe",emailProvider:"smtp",multiTenancy:!1,billingScope:"user",blog:!0,docs:!1,revenueSharing:!1,credits:!0,dockerServices:["postgres","redis","inngest"],aiTools:[],socialProviders:[],defaultCurrency:"USD",deploymentTarget:"node",databaseProvider:"postgres",cacheProvider:"redis"};function vn(e){let t=e.projectName??ne.projectName,r=e.projectDir??`./${t}`,i=e.appName??st(t),n=e.deploymentTarget??ne.deploymentTarget,o=B[n]?.edgeRuntime??!1,s=e.databaseProvider??(o?"neon":ne.databaseProvider),a=e.cacheProvider??(o?"upstash":ne.cacheProvider),d=e.emailProvider??(o?"resend":ne.emailProvider),y=e.dockerServices??(o?ne.dockerServices.filter(f=>f!=="postgres"&&f!=="redis"):ne.dockerServices),h={...ne,...e,projectName:t,appName:i,projectDir:r,deploymentTarget:n,databaseProvider:s,cacheProvider:a,emailProvider:d,dockerServices:y};h.paymentProvider==="none"&&(h.credits=!1);for(let f of Ue){if(h.deploymentTarget!==f.target)continue;let S=h.databaseProvider===f.provider?"database":"cache";if(h.databaseProvider===f.provider||h.cacheProvider===f.provider)throw new Error(`Incompatible: --deploy ${f.target} + --${S} ${f.provider}. ${f.reason}`)}for(let f of je)if(h.architecture===f.architecture&&h.deploymentTarget===f.target)throw new Error(`Incompatible: --architecture ${f.architecture} + --deploy ${f.target}. ${f.reason}`);return h}function Kt(e,t,r){if(e.trim()==="")return[];let i=e.split(",").map(o=>o.trim()).filter(Boolean),n=i.filter(o=>!t.includes(o));if(n.length>0)throw new Error(`Invalid ${r}(s): ${n.join(", ")}. Valid values: ${t.join(", ")}`);return i}import Jo from"picocolors";var Wo="a10a6fb9d7cadde32e37dad52059d17b5d2b916b08c76d8fbcc99982e9a3d87f";function qo(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 Sn(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 V("--frontend <type>","frontend framework").choices([...Ve])).addOption(new V("--architecture <type>","fullstack or separate").choices([...Qe])).addOption(new V("--payment <provider>","payment provider").choices([...et])).addOption(new V("--email <provider>","email provider").choices([...tt])).option("--org","enable multi-tenancy (organizations)").option("--no-org","disable multi-tenancy").addOption(new V("--billing-scope <scope>","billing scope (requires --org)").choices([...it])).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("--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 V("--currency <code>","default currency for billing").choices([...ae])).addOption(new V("--deploy <target>","deployment target").choices([...ce])).addOption(new V("--database <provider>","database provider").choices([...le])).addOption(new V("--cache <provider>","cache provider").choices([...pe])).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 V("--demo","first-party demo build: keep sample content, mark site non-indexable - requires CI API key").hideHelp()).addOption(new V("--no-db-migration","skip generating the baseline DB migration (internal: demos/CI/playground)").hideHelp()).action(async(t,r)=>{await Xo(t?{...r,apiKey:t}:r)})}async function Xo(e){let t=performance.now();qt("1.11.2");let r,i;try{r=yn(e),i=qo(e.templateVersion)}catch(u){E.cancel(I(u)),process.exit(1)}let n=E.spinner(),o;try{o=await Ae({apiKey:e.apiKey,prompt:!e.yes})}catch(u){E.cancel(I(u)),process.exit(1)}e.demo&&jt(o)!==Wo&&(E.cancel("--demo is restricted to first-party demo deployments."),process.exit(1));let s=X(o),a=async()=>{let u=await re(s),b=u.latest,H=i??b;if(i&&!u.versions.some(Y=>Y.version===H))throw new Error(`Template version "${i}" is not available.`);return{latestVersion:b,selectedVersion:H}};n.start("Verifying access...");let d,y;try{({latestVersion:d,selectedVersion:y}=await a()),n.stop("Access verified."),ue(o)}catch(u){if(n.stop("Access verification failed."),u instanceof R&&u.status===401){e.yes&&(E.cancel("Invalid API key. Cannot prompt in non-interactive mode."),process.exit(1)),E.log.warning("Invalid API key."),o=await Be(),s=X(o),n.start("Verifying access...");try{({latestVersion:d,selectedVersion:y}=await a()),n.stop("Access verified."),ue(o)}catch(b){n.stop("Access verification failed."),E.cancel(b instanceof R&&b.status===401?"Invalid API key.":I(b)),process.exit(1)}}else E.cancel(I(u)),process.exit(1)}E.log.success(`Latest version: ${d}`),y!==d&&E.log.success(`Using template version: ${y}`);let h;e.yes?h=vn(r):h=await Qt(r);let f;n.start("Activating license...");try{let u=crypto.randomUUID(),b=()=>({frontend:h.frontend,version:y,installId:u,projectName:h.projectName,options:ir(h)}),H;try{H=await It(s,b())}catch(Y){let J=pt(Y);if(!J?.lastAllowedVersion)throw Y;n.stop("License activation failed."),e.yes&&(E.cancel(`${J.message} Re-run with --template-version ${J.lastAllowedVersion}.`),process.exit(1));let we=await E.confirm({message:`Your update window has ended. Continue with v${J.lastAllowedVersion} (the last version your license covers)?`});(E.isCancel(we)||!we)&&(E.cancel("Setup cancelled."),process.exit(0)),y=J.lastAllowedVersion,n.start(`Activating license for v${y}...`),H=await It(s,b())}f={token:H.token,keyHash:jt(o),installId:u},n.stop("License activated.")}catch(u){n.stop("License activation failed."),E.cancel(I(u)),process.exit(1)}let S=Yo(h.projectDir);if(Go(S)&&Ko(S).length>0)if(e.yes)E.log.info(`Directory ${S} is not empty. Merging (keeping existing files, overwriting conflicts).`);else{let b=await E.select({message:`Directory ${S} 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"}]});(E.isCancel(b)||b==="cancel")&&(E.cancel("Setup cancelled."),process.exit(0)),b==="overwrite"&&zo(S,{recursive:!0,force:!0})}let k={...h,projectDir:S,version:y,...e.demo?{docs:!1}:{}};n.start("Downloading template...");try{await dt(s,y,S),n.stop("Template downloaded.")}catch(u){n.stop("Download failed."),E.cancel(I(u)),process.exit(1)}let ie;n.start("Generating project files...");try{if({dockerComposeGenerated:ie}=await vt(k),!e.demo){let u=await Oe(S);await l(Ho(S,ct),JSON.stringify(u,null," ")+`
1132
- `),await sn(S,S)}await ln(S,k.aiTools),await nn(k,f),n.stop("Project files generated.")}catch(u){n.stop("Generation failed."),E.cancel(I(u)),process.exit(1)}await dn(S);let x=await un(S);x&&k.demo!==!0&&e.dbMigration!==!1&&await mn(S),await fn(S),x&&await gn(S);let $=ye("docker"),Z=Tt(k).map(u=>u.key).filter(u=>!k.credentials?.[u]);hn(k,{pnpmInstalled:x,dockerComposeGenerated:ie,dockerAvailable:$,skippedCredentials:Z}),Xt(),E.log.info(Jo.dim(`Done in ${((performance.now()-t)/1e3).toFixed(1)}s`))}import{existsSync as wn}from"fs";import{readFile as bn}from"fs/promises";import{join as He,resolve as rs}from"path";import*as _ from"@clack/prompts";import De from"picocolors";import{mkdtemp as Zo,rm as Qo}from"fs/promises";import{tmpdir as es}from"os";import{join as ts}from"path";async function zt(e,t,r,i){let n=await Zo(ts(es(),"generatesaas-stage-"));try{await dt(e,t,n),await vt({...r,projectDir:n}),await Vt(n,i)}finally{await Qo(n,{recursive:!0,force:!0})}}function En(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 Et(e,t){let r=En(e),i=En(t);if(!r||!i)return 0;for(let n=0;n<3;n++)if(r[n]!==i[n])return r[n]-i[n];return 0}function An(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=rs(t.cwd??process.cwd()),i=He(r,Q),n;try{n=JSON.parse(await bn(i,"utf-8"))}catch{_.cancel(".generatesaas/manifest.json not found. Run this from a GenerateSaaS project."),process.exit(1)}let o;try{o=await Ae()}catch(d){_.cancel(I(d)),process.exit(1)}let s=X(o),a=_.spinner();try{a.start("Verifying access...");let d;try{d=await re(s)}catch(u){throw u instanceof R&&u.status===401?new Error("Your saved API key was rejected. Run `generatesaas auth` to update it, or set GENERATESAAS_API_KEY."):u}a.stop("Access verified."),ue(o),a.start("Fetching latest skill files...");let y=await nr(s,d.latest);await Bt(r,y.skillMd,y.scripts,n.aiTools);let h=Ft(n.aiTools);if(a.stop("Skills updated."),_.log.success(`Skill files installed to ${De.cyan(h.length.toString())} locations.`),n.version===d.latest){_.log.info(`Already on the latest version (${n.version}).`);return}if(n.licenseToken)try{let u=await or(s,{currentToken:n.licenseToken,newVersion:d.latest});n.licenseToken=u.token,await l(i,JSON.stringify(n,null," ")+`
1133
- `),_.log.success("License refreshed.")}catch(u){let b=pt(u);b&&(_.cancel(b.message),process.exit(1)),_.log.warn("License refresh skipped.")}let f=rn(n,r),S=He(r,tr);a.start(`Staging v${d.latest} (shaped for your config)...`),await zt(s,d.latest,f,S),a.stop("Template staged.");let{text:k,title:ie}=await ns(s,d,n.version);k&&_.note(k,ie);let x=He(r,ct),$=He(r,lt),Ee=!wn($),Z=!wn(x);if(Ee){if(a.start("Building baseline template (one-time migration)..."),await zt(s,n.version,f,$),Z){let u=await Oe($);await l(x,JSON.stringify(u,null," ")+`
1134
- `)}if(a.stop("Baseline template stored."),!Z){let u=await is(x,$);u>0&&_.log.warn(`Rebuilt baseline differs from the original for ${u} 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(Z){a.start("Computing baseline template hashes...");let u=await Oe($);await l(x,JSON.stringify(u,null," ")+`
1135
- `),a.stop("Baseline hashes computed.")}if(await l(He(r,rr),JSON.stringify({currentVersion:n.version,targetVersion:d.latest,changelog:k,stagedAt:new Date().toISOString()},null," ")+`
1136
- `),_.log.info(`Update staged: ${De.cyan(n.version)} \u2192 ${De.cyan(d.latest)}`),n.aiTools&&n.aiTools.length>0){let u=n.aiTools[0],b=$e[u].label;_.log.info(`Open your project in ${De.cyan(b)} and ask: ${De.cyan("'update my GenerateSaaS project'")}`)}else _.log.info(`Ask your AI coding assistant to ${De.cyan("'update my GenerateSaaS project'")}.`)}catch(d){a.stop("Failed."),_.cancel(`Update failed: ${I(d)}`),process.exit(1)}})}async function ns(e,t,r){let i=t.latest,n=t.versions.filter(s=>Et(s.version,r)>0&&Et(s.version,i)<=0).sort((s,a)=>Et(s.version,a.version));if(n.length<=1)return{text:await kt(e,i),title:`Changelog v${i}`};let o=[];for(let s of n){let a=null;try{a=await kt(e,s.version)}catch{a=null}let d=s.date?` (${s.date.slice(0,10)})`:"",y=s.breaking?" [BREAKING]":"";o.push(`# v${s.version}${d}${y}
1129
+ ${f}`:s.message))}else i()})})}async function hn(e){if(!ye("pnpm"))return P.log.warn("pnpm not found. Skipping lockfile regeneration."),!1;try{return await he("pnpm",["install","--lockfile-only","--no-frozen-lockfile","--config.minimumReleaseAge=0"],e),!0}catch(t){let r=t instanceof Error?t.message:String(t);return P.log.warn(`Lockfile regeneration failed: ${r}`),P.log.warn("Deploys using --frozen-lockfile may fail."),!1}}async function yn(e){if(!ye("pnpm"))return P.log.warn("pnpm not found. Skipping dependency installation."),P.log.info("Install pnpm: https://pnpm.io/installation"),!1;let t=P.spinner();t.start("Installing dependencies (this may take a minute)...");try{return await he("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 P.log.warn(`pnpm install failed: ${n}`),P.log.warn("You can run it manually later."),!1}}async function vn(e){if(!ye("pnpm"))return!1;let t=P.spinner();t.start("Generating baseline database migration...");try{return await he("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 P.log.warn(`Could not generate baseline migration: ${n}`),P.log.warn("Run 'pnpm -F @repo/database generate' before your first deploy."),!1}}async function Sn(e){try{return await gn(zt(e,".git")),P.log.info("Git repository already exists, skipping init."),!0}catch{}if(!ye("git"))return P.log.warn("git not found. Skipping repository initialization."),!1;let t=P.spinner();t.start("Initializing git repository...");try{return await he("git",["init"],e),await he("git",["add","-A"],e),await he("git",["commit","--no-verify","-m","Initial commit from GenerateSaaS"],e),t.stop("Git repository initialized."),!0}catch{return t.stop("Git initialization failed."),P.log.warn("You can run git init manually later."),!1}}async function En(e){if(!ye("pnpm"))return!1;try{await gn(zt(e,".git"))}catch{return!1}try{let t=JSON.parse(await zo(zt(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 he("pnpm",["exec","simple-git-hooks"],e),!0}catch{return P.log.warn("Could not install git hooks. Run 'pnpm exec simple-git-hooks' manually."),!1}}import*as De from"@clack/prompts";import j from"picocolors";function wn(e,t){t.dockerComposeGenerated&&!t.dockerAvailable&&De.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 o=e.dockerServices.map(s=>be[s].label).join(", ");r.push(`pnpm infra ${j.dim(`# ${o}`)}`)}if(r.push(`pnpm dev ${j.dim("# http://localhost:3000")}`),t.skippedCredentials.length>0&&(r.push(""),r.push(j.dim("Fill in remaining TODO values in .env"))),De.note(r.join(`
1130
+ `),j.yellow("Start Development")),t.dockerComposeGenerated){let o=[];o.push(`App ${j.cyan("http://localhost:3000")}`),e.architecture==="separate"&&o.push(`API ${j.cyan("http://localhost:3010")}`),e.dockerServices.includes("mailpit")&&o.push(`Mailpit ${j.cyan("http://localhost:8025")}`),e.dockerServices.includes("inngest")&&o.push(`Inngest ${j.cyan("http://localhost:8288")}`),De.note(o.join(`
1131
+ `),j.yellow("Dev Tools"))}let n=[],i=Ho(e);i.length>0&&n.push(`Set in production: ${j.dim(i.join(", "))}`),n.push("pnpm db:push # Run database migrations"),n.push(Yo(e)),De.note(n.join(`
1132
+ `),j.yellow("Deployment"))}function Ho(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 Yo(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 bn(e){let t={};if(e.name!==void 0){if(!ct(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(!Me.includes(e.frontend))throw new Error(`Invalid frontend "${e.frontend}". Valid values: ${Me.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.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=Ht(e.docker,nt,"docker service")),e.aiTools!==void 0&&(t.aiTools=Ht(e.aiTools,it,"AI tool")),e.socialProviders!==void 0&&(t.socialProviders=Ht(e.socialProviders,st,"social provider")),e.currency!==void 0){if(!ae.includes(e.currency))throw new Error(`Invalid currency "${e.currency}". Valid values: ${ae.join(", ")}`);t.defaultCurrency=e.currency}if(e.deploy!==void 0){if(!ce.includes(e.deploy))throw new Error(`Invalid deployment target "${e.deploy}". Valid values: ${ce.join(", ")}`);t.deploymentTarget=e.deploy}if(e.database!==void 0){if(!le.includes(e.database))throw new Error(`Invalid database provider "${e.database}". Valid values: ${le.join(", ")}`);t.databaseProvider=e.database}if(e.cache!==void 0){if(!pe.includes(e.cache))throw new Error(`Invalid cache provider "${e.cache}". Valid values: ${pe.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 ne={projectName:"my-saas",frontend:"nextjs",architecture:"fullstack",paymentProvider:"stripe",emailProvider:"smtp",multiTenancy:!1,billingScope:"user",blog:!0,docs:!1,revenueSharing:!1,credits:!0,dockerServices:["postgres","redis","inngest"],aiTools:[],socialProviders:[],defaultCurrency:"USD",deploymentTarget:"node",databaseProvider:"postgres",cacheProvider:"redis"};function An(e){let t=e.projectName??ne.projectName,r=e.projectDir??`./${t}`,n=e.appName??at(t),i=e.deploymentTarget??ne.deploymentTarget,o=B[i]?.edgeRuntime??!1,s=e.databaseProvider??(o?"neon":ne.databaseProvider),a=e.cacheProvider??(o?"upstash":ne.cacheProvider),d=e.emailProvider??(o?"resend":ne.emailProvider),y=e.dockerServices??(o?ne.dockerServices.filter(f=>f!=="postgres"&&f!=="redis"):ne.dockerServices),h={...ne,...e,projectName:t,appName:n,projectDir:r,deploymentTarget:i,databaseProvider:s,cacheProvider:a,emailProvider:d,dockerServices:y};h.paymentProvider==="none"&&(h.credits=!1);for(let f of je){if(h.deploymentTarget!==f.target)continue;let S=h.databaseProvider===f.provider?"database":"cache";if(h.databaseProvider===f.provider||h.cacheProvider===f.provider)throw new Error(`Incompatible: --deploy ${f.target} + --${S} ${f.provider}. ${f.reason}`)}for(let f of Ve)if(h.architecture===f.architecture&&h.deploymentTarget===f.target)throw new Error(`Incompatible: --architecture ${f.architecture} + --deploy ${f.target}. ${f.reason}`);return h}function Ht(e,t,r){if(e.trim()==="")return[];let n=e.split(",").map(o=>o.trim()).filter(Boolean),i=n.filter(o=>!t.includes(o));if(i.length>0)throw new Error(`Invalid ${r}(s): ${i.join(", ")}. Valid values: ${t.join(", ")}`);return n}import Qo from"picocolors";var es="a10a6fb9d7cadde32e37dad52059d17b5d2b916b08c76d8fbcc99982e9a3d87f";function ts(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 Tn(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 V("--frontend <type>","frontend framework").choices([...Me])).addOption(new V("--architecture <type>","fullstack or separate").choices([...et])).addOption(new V("--payment <provider>","payment provider").choices([...tt])).addOption(new V("--email <provider>","email provider").choices([...rt])).option("--org","enable multi-tenancy (organizations)").option("--no-org","disable multi-tenancy").addOption(new V("--billing-scope <scope>","billing scope (requires --org)").choices([...ot])).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("--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 V("--currency <code>","default currency for billing").choices([...ae])).addOption(new V("--deploy <target>","deployment target").choices([...ce])).addOption(new V("--database <provider>","database provider").choices([...le])).addOption(new V("--cache <provider>","cache provider").choices([...pe])).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 V("--demo","first-party demo build: keep sample content, mark site non-indexable - requires CI API key").hideHelp()).addOption(new V("--no-db-migration","skip generating the baseline DB migration (internal: demos/CI/playground)").hideHelp()).action(async(t,r)=>{await rs(t?{...r,apiKey:t}:r)})}async function rs(e){let t=performance.now();Zt("1.12.1");let r,n;try{r=bn(e),n=ts(e.templateVersion)}catch(u){E.cancel(I(u)),process.exit(1)}let i=E.spinner(),o;try{o=await Ae({apiKey:e.apiKey,prompt:!e.yes})}catch(u){E.cancel(I(u)),process.exit(1)}e.demo&&Mt(o)!==es&&(E.cancel("--demo is restricted to first-party demo deployments."),process.exit(1));let s=X(o),a=async()=>{let u=await re(s),b=u.latest,H=n??b;if(n&&!u.versions.some(Y=>Y.version===H))throw new Error(`Template version "${n}" is not available.`);return{latestVersion:b,selectedVersion:H}};i.start("Verifying access...");let d,y;try{({latestVersion:d,selectedVersion:y}=await a()),i.stop("Access verified."),ue(o)}catch(u){if(i.stop("Access verification failed."),u instanceof R&&u.status===401){e.yes&&(E.cancel("Invalid API key. Cannot prompt in non-interactive mode."),process.exit(1)),E.log.warning("Invalid API key."),o=await Ge(),s=X(o),i.start("Verifying access...");try{({latestVersion:d,selectedVersion:y}=await a()),i.stop("Access verified."),ue(o)}catch(b){i.stop("Access verification failed."),E.cancel(b instanceof R&&b.status===401?"Invalid API key.":I(b)),process.exit(1)}}else E.cancel(I(u)),process.exit(1)}E.log.success(`Latest version: ${d}`),y!==d&&E.log.success(`Using template version: ${y}`);let h;e.yes?h=An(r):h=await tr(r);let f;i.start("Activating license...");try{let u=crypto.randomUUID(),b=()=>({frontend:h.frontend,version:y,installId:u,projectName:h.projectName,options:sr(h)}),H;try{H=await Pt(s,b())}catch(Y){let J=dt(Y);if(!J?.lastAllowedVersion)throw Y;i.stop("License activation failed."),e.yes&&(E.cancel(`${J.message} Re-run with --template-version ${J.lastAllowedVersion}.`),process.exit(1));let we=await E.confirm({message:`Your update window has ended. Continue with v${J.lastAllowedVersion} (the last version your license covers)?`});(E.isCancel(we)||!we)&&(E.cancel("Setup cancelled."),process.exit(0)),y=J.lastAllowedVersion,i.start(`Activating license for v${y}...`),H=await Pt(s,b())}f={token:H.token,keyHash:Mt(o),installId:u},i.stop("License activated.")}catch(u){i.stop("License activation failed."),E.cancel(I(u)),process.exit(1)}let S=Zo(h.projectDir);if(Jo(S)&&Wo(S).length>0)if(e.yes)E.log.info(`Directory ${S} is not empty. Merging (keeping existing files, overwriting conflicts).`);else{let b=await E.select({message:`Directory ${S} 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"}]});(E.isCancel(b)||b==="cancel")&&(E.cancel("Setup cancelled."),process.exit(0)),b==="overwrite"&&qo(S,{recursive:!0,force:!0})}let k={...h,projectDir:S,version:y,...e.demo?{docs:!1}:{}};i.start("Downloading template...");try{await ut(s,y,S),i.stop("Template downloaded.")}catch(u){i.stop("Download failed."),E.cancel(I(u)),process.exit(1)}let ie;i.start("Generating project files...");try{if({dockerComposeGenerated:ie}=await St(k),!e.demo){let u=await xe(S);await l(Xo(S,lt),JSON.stringify(u,null," ")+`
1133
+ `),await dn(S,S)}await fn(S,k.aiTools),await ln(k,f),i.stop("Project files generated.")}catch(u){i.stop("Generation failed."),E.cancel(I(u)),process.exit(1)}await hn(S);let x=await yn(S);x&&k.demo!==!0&&e.dbMigration!==!1&&await vn(S),await Sn(S),x&&await En(S);let $=ye("docker"),Z=kt(k).map(u=>u.key).filter(u=>!k.credentials?.[u]);wn(k,{pnpmInstalled:x,dockerComposeGenerated:ie,dockerAvailable:$,skippedCredentials:Z}),Qt(),E.log.info(Qo.dim(`Done in ${((performance.now()-t)/1e3).toFixed(1)}s`))}import{existsSync as In}from"fs";import{readFile as Pn}from"fs/promises";import{join as Ye,resolve as as}from"path";import*as _ from"@clack/prompts";import Ce from"picocolors";import{mkdtemp as ns,rm as is}from"fs/promises";import{tmpdir as os}from"os";import{join as ss}from"path";async function Yt(e,t,r,n){let i=await ns(ss(os(),"generatesaas-stage-"));try{await ut(e,t,i),await St({...r,projectDir:i}),await Ft(i,n)}finally{await is(i,{recursive:!0,force:!0})}}function kn(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 wt(e,t){let r=kn(e),n=kn(t);if(!r||!n)return 0;for(let i=0;i<3;i++)if(r[i]!==n[i])return r[i]-n[i];return 0}function _n(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=as(t.cwd??process.cwd()),n=Ye(r,Q),i;try{i=JSON.parse(await Pn(n,"utf-8"))}catch{_.cancel(".generatesaas/manifest.json not found. Run this from a GenerateSaaS project."),process.exit(1)}let o;try{o=await Ae()}catch(d){_.cancel(I(d)),process.exit(1)}let s=X(o),a=_.spinner();try{a.start("Verifying access...");let d;try{d=await re(s)}catch(u){throw u instanceof R&&u.status===401?new Error("Your saved API key was rejected. Run `generatesaas auth` to update it, or set GENERATESAAS_API_KEY."):u}a.stop("Access verified."),ue(o),a.start("Fetching latest skill files...");let y=await or(s,d.latest);await Kt(r,y.skillMd,y.scripts,i.aiTools);let h=Gt(i.aiTools);if(a.stop("Skills updated."),_.log.success(`Skill files installed to ${Ce.cyan(h.length.toString())} locations.`),i.version===d.latest){_.log.info(`Already on the latest version (${i.version}).`);return}if(i.licenseToken)try{let u=await ar(s,{currentToken:i.licenseToken,newVersion:d.latest});i.licenseToken=u.token,u.licenseKeyHash&&(i.licenseKeyHash=u.licenseKeyHash),await l(n,JSON.stringify(i,null," ")+`
1134
+ `),_.log.success("License refreshed.")}catch(u){let b=dt(u);b&&(_.cancel(b.message),process.exit(1)),_.log.warn("License refresh skipped.")}let f=cn(i,r),S=Ye(r,nr);a.start(`Staging v${d.latest} (shaped for your config)...`),await Yt(s,d.latest,f,S),a.stop("Template staged.");let{text:k,title:ie}=await cs(s,d,i.version);k&&_.note(k,ie);let x=Ye(r,lt),$=Ye(r,pt),Ee=!In($),Z=!In(x);if(Ee){if(a.start("Building baseline template (one-time migration)..."),await Yt(s,i.version,f,$),Z){let u=await xe($);await l(x,JSON.stringify(u,null," ")+`
1135
+ `)}if(a.stop("Baseline template stored."),!Z){let u=await ls(x,$);u>0&&_.log.warn(`Rebuilt baseline differs from the original for ${u} 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(Z){a.start("Computing baseline template hashes...");let u=await xe($);await l(x,JSON.stringify(u,null," ")+`
1136
+ `),a.stop("Baseline hashes computed.")}if(await l(Ye(r,ir),JSON.stringify({currentVersion:i.version,targetVersion:d.latest,changelog:k,stagedAt:new Date().toISOString()},null," ")+`
1137
+ `),_.log.info(`Update staged: ${Ce.cyan(i.version)} \u2192 ${Ce.cyan(d.latest)}`),i.aiTools&&i.aiTools.length>0){let u=i.aiTools[0],b=Ue[u].label;_.log.info(`Open your project in ${Ce.cyan(b)} and ask: ${Ce.cyan("'update my GenerateSaaS project'")}`)}else _.log.info(`Ask your AI coding assistant to ${Ce.cyan("'update my GenerateSaaS project'")}.`)}catch(d){a.stop("Failed."),_.cancel(`Update failed: ${I(d)}`),process.exit(1)}})}async function cs(e,t,r){let n=t.latest,i=t.versions.filter(s=>wt(s.version,r)>0&&wt(s.version,n)<=0).sort((s,a)=>wt(s.version,a.version));if(i.length<=1)return{text:await It(e,n),title:`Changelog v${n}`};let o=[];for(let s of i){let a=null;try{a=await It(e,s.version)}catch{a=null}let d=s.date?` (${s.date.slice(0,10)})`:"",y=s.breaking?" [BREAKING]":"";o.push(`# v${s.version}${d}${y}
1137
1138
 
1138
1139
  ${a??"_No changelog available for this release._"}`)}return{text:o.join(`
1139
1140
 
1140
- `),title:`Changelog v${r} \u2192 v${i}`}}async function is(e,t){let r=JSON.parse(await bn(e,"utf-8")),i=await Oe(t),n=0;for(let[o,s]of Object.entries(r))Te(o)||i[o]!==s&&n++;for(let o of Object.keys(i))o in r||n++;return n}import*as L from"@clack/prompts";import M from"picocolors";import{readFile as os}from"fs/promises";import{join as ss,resolve as as}from"path";function Tn(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=as(t.cwd??process.cwd()),i=ss(r,Q),n;try{n=JSON.parse(await os(i,"utf-8"))}catch{L.cancel(".generatesaas/manifest.json not found. Run this from a GenerateSaaS project."),process.exit(1)}let o=[`Version: ${M.cyan(n.version)}`,`Frontend: ${M.cyan(n.frontend)}`,n.deploymentTarget?`Deploy target: ${M.cyan(n.deploymentTarget)}`:null,n.databaseProvider?`Database: ${M.cyan(n.databaseProvider)}`:null,n.cacheProvider?`Cache: ${M.cyan(n.cacheProvider)}`:null,n.aiTools&&n.aiTools.length>0?`AI tools: ${M.cyan(n.aiTools.join(", "))}`:null].filter(Boolean).join(`
1141
- `);L.note(o,M.bold("Project Status"));let s=L.spinner();s.start("Checking for updates...");try{let a=await Ae(),d=X(a),h=(await re(d)).latest;n.version===h?(s.stop("Up to date."),L.log.success(`Already on the latest version (${M.green(h)})`)):(s.stop("Update available."),L.log.warning(`Update available: ${M.yellow(n.version)} \u2192 ${M.green(h)}`),L.log.info(`Open this project in your AI coding agent and ask it to ${M.cyan("update my GenerateSaaS project")} - it fetches and applies the update for you.`))}catch(a){s.stop("Check failed."),a instanceof R&&a.status===401?L.log.warning("Invalid API key. Run `generatesaas auth` to update it, or set GENERATESAAS_API_KEY."):L.log.warning(`Could not check for updates: ${I(a)}`)}})}import{readFile as cs}from"fs/promises";import*as T from"@clack/prompts";import A from"picocolors";function ls(){return process.env.GENERATESAAS_API_KEY??Fe()}function ps(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 ds(e){return{verdict:e.verdict??"unknown",ejectedAt:e.ejectedAt??null}}function us(e){switch(e.verdict){case"licensed":return T.log.success(`${A.green("LICENSED")} - resolves to an account with an active${e.plan?` ${e.plan}`:""} license.`),!0;case"ejected":return T.log.success(`${A.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 T.log.error(`${A.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 T.log.error(`${A.red("LEAKED TOKEN")} - this license belongs to a different deployment${e.mismatchDomain?` (${A.cyan(e.mismatchDomain)})`:""}, not this site. The token was copied from a licensed project.`),!1;case"no_license_history":return T.log.error(`${A.red("NO LICENSE HISTORY")} - no license has ever been associated with this site. If it runs GenerateSaaS, treat it as unlicensed.`),!1;default:return T.log.warn(`${A.yellow("UNKNOWN")} - could not cross-reference the records right now. Try again shortly.`),!1}}async function kn(e,t){let r=process.env.GENERATESAAS_API_URL??Me,i=ls();e.start("Cross-referencing license records...");try{let n=i?ps(await sr(r,i,{lkh:t.lkh,nid:t.nid,domain:t.domain})):ds(await Pt(r,{token:t.token,domain:t.domain}));return e.stop(`${A.green("Checked")} - records cross-referenced`),us(n)}catch(n){return e.stop(`${A.yellow("Skipped")} - ${I(n)}`),null}}function ms(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 Ht(e){return typeof e!="number"?"unknown":new Date(e*1e3).toISOString().split("T")[0]}function In(e){T.note([`License ID: ${A.cyan(String(e.lid??"unknown"))}`,`Version: ${A.cyan(String(e.ver??"unknown"))}`,`Init version: ${String(e.iver??"unknown")}`,`Frontend: ${String(e.fe??"unknown")}`,`Created: ${Ht(e.pat)}`,`Last updated: ${Ht(e.uat)}`,`Expires: ${Ht(e.exp)}`,`Install ID: ${String(e.nid??"unknown")}`].join(`
1142
- `),A.yellow("License Details"))}function fs(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 Pn(e){let t=T.spinner(),r=null,i="no candidates";for(let s of fs(e)){t.start(`Checking ${s}...`);try{let a=await fetch(s);if(!a.ok){i=`${s} returned ${a.status}`,t.stop(`${A.yellow("Not here")} - ${i}`);continue}let d=(await a.text()).trim();if(!d||d.split(".").length!==3){i=`${s} did not return a JWT`,t.stop(`${A.yellow("Not here")} - ${i}`);continue}r=d,t.stop(`${A.green("Found")} - license endpoint responded`);break}catch(a){i=`${s}: ${I(a)}`,t.stop(`${A.yellow("Unreachable")} - ${i}`)}}if(r===null){T.log.warn(`No license endpoint found (last: ${i}). The site may be ejected, not a GenerateSaaS app, or serving its API elsewhere.`);let s=_n(e);return s?await kn(t,{domain:s})??!1:!1}let n;try{n=ms(r)}catch{return T.log.error("Could not decode JWT payload."),!1}t.start("Verifying signature...");try{let s=process.env.GENERATESAAS_API_URL??Me,a=await Pt(s,{token:r});if(a.valid)t.stop(`${A.green("Valid")} - signature verified`);else return t.stop(`${A.red("Invalid")} - ${a.reason}`),!1}catch{return t.stop(`${A.yellow("Skipped")} - could not reach verification service`),T.log.warn("Signature not verified. Displaying unverified claims:"),In(n),!1}return In(n),await kn(t,{token:r,lkh:typeof n.lkh=="string"?n.lkh:void 0,nid:typeof n.nid=="string"?n.nid:void 0,domain:_n(e)})??!0}function _n(e){try{return new URL(/^https?:\/\//i.test(e)?e:`https://${e}`).hostname}catch{return}}function Rn(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&&(T.cancel("Provide a URL or --file <path>."),process.exit(1)),r.file){let n=(await cs(r.file,"utf-8")).split(`
1143
- `).map(s=>s.trim()).filter(s=>s&&!s.startsWith("#"));n.length===0&&(T.cancel("No URLs found in file."),process.exit(1));let o=0;for(let s of n)await Pn(s)&&o++,T.log.info("");T.log.success(`${o}/${n.length} sites verified.`)}else await Pn(t)||process.exit(1)})}import{existsSync as gs,rmSync as hs}from"fs";import*as F from"@clack/prompts";function On(e){e.command("auth").description("Set or update your GenerateSaaS API key").option("--clear","remove saved API key").action(async t=>{if(t.clear){gs(q)?(hs(q),F.log.success("API key removed.")):F.log.info("No API key configured.");return}let r=Fe();r?F.log.info(`Current API key: ****${r.slice(-4)}`):F.log.info("No API key configured.");let i=await Be(),n=X(i),o=F.spinner();o.start("Verifying API key...");try{await re(n),o.stop("API key verified."),ue(i),F.log.success("API key saved.")}catch(s){o.stop("Verification failed."),s instanceof R&&s.status===401?F.cancel("Invalid API key."):F.cancel(I(s)),process.exit(1)}})}import{existsSync as wt,rmSync as ys,readFileSync as Jt,writeFileSync as xn}from"fs";import{join as ve}from"path";import*as O from"@clack/prompts";var vs=["packages/api/src/functions/maintenance/license-heartbeat.ts","packages/api/src/lib/manifest.ts","packages/api/src/routes/internal/license.ts"],Ss=[{file:"packages/api/src/routes/inngest.ts",removals:[`import { licenseHeartbeatFunction } from "../functions/maintenance/license-heartbeat";
1141
+ `),title:`Changelog v${r} \u2192 v${n}`}}async function ls(e,t){let r=JSON.parse(await Pn(e,"utf-8")),n=await xe(t),i=0;for(let[o,s]of Object.entries(r))Te(o)||n[o]!==s&&i++;for(let o of Object.keys(n))o in r||i++;return i}import*as L from"@clack/prompts";import M from"picocolors";import{readFile as ps}from"fs/promises";import{join as ds,resolve as us}from"path";function Rn(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=us(t.cwd??process.cwd()),n=ds(r,Q),i;try{i=JSON.parse(await ps(n,"utf-8"))}catch{L.cancel(".generatesaas/manifest.json not found. Run this from a GenerateSaaS project."),process.exit(1)}let o=[`Version: ${M.cyan(i.version)}`,`Frontend: ${M.cyan(i.frontend)}`,i.deploymentTarget?`Deploy target: ${M.cyan(i.deploymentTarget)}`:null,i.databaseProvider?`Database: ${M.cyan(i.databaseProvider)}`:null,i.cacheProvider?`Cache: ${M.cyan(i.cacheProvider)}`:null,i.aiTools&&i.aiTools.length>0?`AI tools: ${M.cyan(i.aiTools.join(", "))}`:null].filter(Boolean).join(`
1142
+ `);L.note(o,M.bold("Project Status"));let s=L.spinner();s.start("Checking for updates...");try{let a=await Ae(),d=X(a),h=(await re(d)).latest;i.version===h?(s.stop("Up to date."),L.log.success(`Already on the latest version (${M.green(h)})`)):(s.stop("Update available."),L.log.warning(`Update available: ${M.yellow(i.version)} \u2192 ${M.green(h)}`),L.log.info(`Open this project in your AI coding agent and ask it to ${M.cyan("update my GenerateSaaS project")} - it fetches and applies the update for you.`))}catch(a){s.stop("Check failed."),a instanceof R&&a.status===401?L.log.warning("Invalid API key. Run `generatesaas auth` to update it, or set GENERATESAAS_API_KEY."):L.log.warning(`Could not check for updates: ${I(a)}`)}})}import{readFile as ms}from"fs/promises";import*as T from"@clack/prompts";import A from"picocolors";function fs(){return process.env.GENERATESAAS_API_KEY??Be()}function gs(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 hs(e){return{verdict:e.verdict??"unknown",ejectedAt:e.ejectedAt??null}}function ys(e){switch(e.verdict){case"licensed":return T.log.success(`${A.green("LICENSED")} - resolves to an account with an active${e.plan?` ${e.plan}`:""} license.`),!0;case"ejected":return T.log.success(`${A.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 T.log.error(`${A.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 T.log.error(`${A.red("LEAKED TOKEN")} - this license belongs to a different deployment${e.mismatchDomain?` (${A.cyan(e.mismatchDomain)})`:""}, not this site. The token was copied from a licensed project.`),!1;case"no_license_history":return T.log.error(`${A.red("NO LICENSE HISTORY")} - no license has ever been associated with this site. If it runs GenerateSaaS, treat it as unlicensed.`),!1;default:return T.log.warn(`${A.yellow("UNKNOWN")} - could not cross-reference the records right now. Try again shortly.`),!1}}async function On(e,t){let r=process.env.GENERATESAAS_API_URL??Fe,n=fs();e.start("Cross-referencing license records...");try{let i=n?gs(await cr(r,n,{lkh:t.lkh,nid:t.nid,domain:t.domain})):hs(await _t(r,{token:t.token,domain:t.domain}));return e.stop(`${A.green("Checked")} - records cross-referenced`),ys(i)}catch(i){return e.stop(`${A.yellow("Skipped")} - ${I(i)}`),null}}function vs(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 Jt(e){return typeof e!="number"?"unknown":new Date(e*1e3).toISOString().split("T")[0]}function xn(e){T.note([`License ID: ${A.cyan(String(e.lid??"unknown"))}`,`Version: ${A.cyan(String(e.ver??"unknown"))}`,`Init version: ${String(e.iver??"unknown")}`,`Frontend: ${String(e.fe??"unknown")}`,`Created: ${Jt(e.pat)}`,`Last updated: ${Jt(e.uat)}`,`Expires: ${Jt(e.exp)}`,`Install ID: ${String(e.nid??"unknown")}`].join(`
1143
+ `),A.yellow("License Details"))}function Ss(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 Dn(e){let t=T.spinner(),r=null,n="no candidates";for(let s of Ss(e)){t.start(`Checking ${s}...`);try{let a=await fetch(s);if(!a.ok){n=`${s} returned ${a.status}`,t.stop(`${A.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(`${A.yellow("Not here")} - ${n}`);continue}r=d,t.stop(`${A.green("Found")} - license endpoint responded`);break}catch(a){n=`${s}: ${I(a)}`,t.stop(`${A.yellow("Unreachable")} - ${n}`)}}if(r===null){T.log.warn(`No license endpoint found (last: ${n}). The site may be ejected, not a GenerateSaaS app, or serving its API elsewhere.`);let s=Cn(e);return s?await On(t,{domain:s})??!1:!1}let i;try{i=vs(r)}catch{return T.log.error("Could not decode JWT payload."),!1}t.start("Verifying signature...");try{let s=process.env.GENERATESAAS_API_URL??Fe,a=await _t(s,{token:r});if(a.valid)t.stop(`${A.green("Valid")} - signature verified`);else return t.stop(`${A.red("Invalid")} - ${a.reason}`),!1}catch{return t.stop(`${A.yellow("Skipped")} - could not reach verification service`),T.log.warn("Signature not verified. Displaying unverified claims:"),xn(i),!1}return xn(i),await On(t,{token:r,lkh:typeof i.lkh=="string"?i.lkh:void 0,nid:typeof i.nid=="string"?i.nid:void 0,domain:Cn(e)})??!0}function Cn(e){try{return new URL(/^https?:\/\//i.test(e)?e:`https://${e}`).hostname}catch{return}}function Nn(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&&(T.cancel("Provide a URL or --file <path>."),process.exit(1)),r.file){let i=(await ms(r.file,"utf-8")).split(`
1144
+ `).map(s=>s.trim()).filter(s=>s&&!s.startsWith("#"));i.length===0&&(T.cancel("No URLs found in file."),process.exit(1));let o=0;for(let s of i)await Dn(s)&&o++,T.log.info("");T.log.success(`${o}/${i.length} sites verified.`)}else await Dn(t)||process.exit(1)})}import{existsSync as Es,rmSync as ws}from"fs";import*as F from"@clack/prompts";function Ln(e){e.command("auth").description("Set or update your GenerateSaaS API key").option("--clear","remove saved API key").action(async t=>{if(t.clear){Es(q)?(ws(q),F.log.success("API key removed.")):F.log.info("No API key configured.");return}let r=Be();r?F.log.info(`Current API key: ****${r.slice(-4)}`):F.log.info("No API key configured.");let n=await Ge(),i=X(n),o=F.spinner();o.start("Verifying API key...");try{await re(i),o.stop("API key verified."),ue(n),F.log.success("API key saved.")}catch(s){o.stop("Verification failed."),s instanceof R&&s.status===401?F.cancel("Invalid API key."):F.cancel(I(s)),process.exit(1)}})}import{existsSync as bt,rmSync as bs,readFileSync as qt,writeFileSync as $n}from"fs";import{join as ve}from"path";import*as O from"@clack/prompts";var As=["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"],Ts=[{file:"packages/api/src/routes/inngest.ts",removals:[`import { licenseHeartbeatFunction } from "../functions/maintenance/license-heartbeat";
1144
1145
  `,` licenseHeartbeatFunction,
1145
1146
  `]},{file:"packages/api/src/routes/internal/index.ts",removals:[`import licenseRoutes from "./license";
1146
1147
  `,` .route("/license", licenseRoutes)
1147
- `]}];function Es(e){return(e&&e.length>0?e.map(r=>ze[r]):Object.values(ze)).map(r=>ve(r,Mt))}function Yt(e){return wt(e)?(ys(e,{recursive:!0}),!0):!1}function ws(e,t){if(!wt(e))return!1;let r=Jt(e,"utf-8"),i=r;for(let n of t)i=i.replace(n,"");return i===r?!1:(xn(e,i,"utf-8"),!0)}function bs(e){let t=ve(e,".gitignore");if(!wt(t))return!1;let r=Jt(t,"utf-8"),i=r.split(`
1148
- `).filter(n=>!n.includes(".generatesaas")).join(`
1149
- `);return i===r?!1:(xn(t,i,"utf-8"),!0)}function Dn(e){e.command("eject").description("Remove all GenerateSaaS ties - manifest, license, heartbeat, skills").action(async()=>{let t=process.cwd(),r=ve(t,Q),i;try{i=JSON.parse(Jt(r,"utf-8"))}catch{O.cancel("No GenerateSaaS project found in this directory."),process.exit(1)}let n=await O.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(O.isCancel(n)&&(O.cancel("Eject cancelled."),process.exit(0)),i.licenseToken)try{await fetch("https://generatesaas.com/api/v1/heartbeat",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${i.licenseToken}`},body:JSON.stringify({event:"eject",version:i.version,frontend:i.frontend}),signal:AbortSignal.timeout(5e3)}),O.log.info("Recorded the opt-out with generatesaas.com (final event - nothing is sent after this).")}catch{O.log.warn("Could not reach generatesaas.com to record the opt-out. Ejecting anyway.")}let o=[],s=[];for(let a of Es(i.aiTools))Yt(ve(t,a))&&o.push(a);for(let a of vs)Yt(ve(t,a))&&o.push(a);Yt(ve(t,U))&&o.push(U+"/");for(let a of Ss){let d=ve(t,a.file);ws(d,a.removals)?s.push(a.file):wt(d)&&O.log.warn(`Could not auto-modify ${a.file} - manually remove license/heartbeat references.`)}bs(t)&&s.push(".gitignore");for(let a of o)O.log.info(`Deleted ${a}`);for(let a of s)O.log.info(`Modified ${a}`);O.log.success("Ejected successfully. This project is now fully standalone.")})}var Se=new As().name("generatesaas").description("CLI for scaffolding and managing GenerateSaaS projects").version("1.11.2").addHelpText("after",`
1148
+ `]}];function ks(e){return(e&&e.length>0?e.map(r=>He[r]):Object.values(He)).map(r=>ve(r,Bt))}function Wt(e){return bt(e)?(bs(e,{recursive:!0}),!0):!1}function Is(e,t){if(!bt(e))return!1;let r=qt(e,"utf-8"),n=r;for(let i of t)n=n.replace(i,"");return n===r?!1:($n(e,n,"utf-8"),!0)}function Ps(e){let t=ve(e,".gitignore");if(!bt(t))return!1;let r=qt(t,"utf-8"),n=r.split(`
1149
+ `).filter(i=>!i.includes(".generatesaas")).join(`
1150
+ `);return n===r?!1:($n(t,n,"utf-8"),!0)}function Un(e){e.command("eject").description("Remove all GenerateSaaS ties - manifest, license, heartbeat, skills").action(async()=>{let t=process.cwd(),r=ve(t,Q),n;try{n=JSON.parse(qt(r,"utf-8"))}catch{O.cancel("No GenerateSaaS project found in this directory."),process.exit(1)}let i=await O.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(O.isCancel(i)&&(O.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)}),O.log.info("Recorded the opt-out with generatesaas.com (final event - nothing is sent after this).")}catch{O.log.warn("Could not reach generatesaas.com to record the opt-out. Ejecting anyway.")}let o=[],s=[];for(let a of ks(n.aiTools))Wt(ve(t,a))&&o.push(a);for(let a of As)Wt(ve(t,a))&&o.push(a);Wt(ve(t,U))&&o.push(U+"/");for(let a of Ts){let d=ve(t,a.file);Is(d,a.removals)?s.push(a.file):bt(d)&&O.log.warn(`Could not auto-modify ${a.file} - manually remove license/heartbeat references.`)}Ps(t)&&s.push(".gitignore");for(let a of o)O.log.info(`Deleted ${a}`);for(let a of s)O.log.info(`Modified ${a}`);O.log.success("Ejected successfully. This project is now fully standalone.")})}var Se=new _s().name("generatesaas").description("CLI for scaffolding and managing GenerateSaaS projects").version("1.12.1").addHelpText("after",`
1150
1151
  Examples:
1151
1152
  $ generatesaas init Interactive setup
1152
1153
  $ generatesaas init -n my-app -y Quick setup with defaults
1153
1154
  $ generatesaas status Check for updates
1154
1155
  $ generatesaas auth Set or update API key
1155
- `);Sn(Se);An(Se);Tn(Se);Rn(Se);On(Se);Dn(Se);Se.parseAsync().catch(e=>{Cn.cancel("An unexpected error occurred."),console.error(e),process.exit(1)});
1156
+ `);Tn(Se);_n(Se);Rn(Se);Nn(Se);Ln(Se);Un(Se);Se.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.11.2",
3
+ "version": "1.12.1",
4
4
  "type": "module",
5
5
  "description": "CLI for scaffolding and managing GenerateSaaS projects",
6
6
  "bin": {