generatesaas 1.9.1 → 1.9.2

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 +38 -32
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
- import{Command as ls}from"commander";import*as kn from"@clack/prompts";import{existsSync as Oo,readdirSync as xo,rmSync as Do}from"fs";import{join as Co,resolve as No}from"path";import{Option as V}from"commander";import*as E from"@clack/prompts";import*as Ye from"@clack/prompts";import Et from"picocolors";function Kt(e){let t=e?` GenerateSaaS v${e} `:" GenerateSaaS ";Ye.intro(Et.bgYellow(Et.black(t)))}function zt(){Ye.outro(Et.yellow("Happy building!"))}import*as u from"@clack/prompts";import f from"picocolors";var De={nextjs:{label:"Next.js",hint:"React 19 + Next.js 16"},nuxt:{label:"Nuxt",hint:"Vue 3 + Nuxt 4"}},Ce={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)"}},Je={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"}},We={user:{label:"Per user",hint:"each user has their own subscription"},organization:{label:"Per organization",hint:"org subscription shared by members"}},we={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}},Ne={"claude-code":{label:"Claude Code"},cursor:{label:"Cursor"},codex:{label:"Codex"},"gemini-cli":{label:"Gemini CLI"},windsurf:{label:"Windsurf"}};var ae={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}},L={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"}]}},Le=[{target:"vercel",provider:"redis",reason:"Vercel serverless cannot maintain persistent Redis connections. Consider Upstash."}],$e=[{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)."}],Ht=["Local file storage (sharp, geoip-lite)","SMTP email (use Resend or SES instead)","Content API git integration"];function Xe(e){let t=L[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 Ue=["nextjs","nuxt"],Ze=["fullstack","separate"],Qe=["stripe","polar","none"],et=["smtp","ses","resend"],tt=["postgres","redis","inngest","mailpit"],rt=["claude-code","cursor","codex","gemini-cli","windsurf"],nt=["user","organization"],ce=["USD","EUR","GBP","CAD","AUD","BRL","JPY"],le=["node","vercel"],pe=["postgres","neon","supabase"],de=["redis","upstash"],it=["google","github","facebook","discord","x"];function ot(e){return e.split("-").map(t=>t.charAt(0).toUpperCase()+t.slice(1)).join(" ")}function st(e){return/^[a-z][a-z0-9-]*$/.test(e)}function k(e){return e instanceof Error?e.message:String(e)}function w(e){u.isCancel(e)&&(u.cancel("Setup cancelled."),process.exit(0))}function wt(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 Yt(e){let t=!1;u.log.info(f.bold("Project"));let r=e?.projectName??await(async()=>{t=!0;let c=await u.text({message:"Project name:",placeholder:"my-saas",validate:p=>{if(!p?.trim())return"Project name is required.";if(!st(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 u.text({message:"App name:",initialValue:ot(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 u.text({message:"Project location:",initialValue:`./${r}`});return w(c),c==="."?process.cwd():c})(),o=e?.frontend??await(async()=>{t=!0;let c=Object.keys(De),p=await u.select({message:"Frontend framework:",options:c.map(v=>({value:v,label:De[v].label,hint:De[v].hint}))});return w(p),p})();u.log.info(f.bold("Infrastructure"));let s=e?.deploymentTarget??"node";if(e?.deploymentTarget===void 0){t=!0;let c=await u.select({message:"Deployment target:",options:le.map(p=>({value:p,label:B[p].label,hint:B[p].hint}))});w(c),s=c}let a=e?.architecture?$e.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($e.filter(c=>c.target===s).map(c=>c.architecture)),h=Ze.filter(c=>!d.has(c)),g=e?.architecture??await(async()=>{if(h.length===1){let p=h[0];return u.log.info(`Auto-selected ${Ce[p].label} architecture (only compatible option for ${B[s].label}).`),p}t=!0;let c=await u.select({message:"Architecture:",options:h.map(p=>({value:p,label:Ce[p].label,hint:Ce[p].hint}))});return w(c),c})(),m=e?.databaseProvider??await(async()=>{t=!0;let c=pe.filter(v=>!Le.some(C=>C.target===s&&C.provider===v));if(c.length===1){let v=c[0];return u.log.info(`Auto-selected ${L[v].label} (only compatible option for ${B[s].label}).`),v}let p=await u.select({message:"Database provider:",options:c.map(v=>({value:v,label:L[v].label,hint:L[v].hint}))});return w(p),p})(),S=e?.cacheProvider??await(async()=>{t=!0;let c=de.filter(v=>!Le.some(C=>C.target===s&&C.provider===v));if(c.length===1){let v=c[0];return u.log.info(`Auto-selected ${G[v].label} (only compatible option for ${B[s].label}).`),v}let p=await u.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=Ht.map(p=>` - ${p}`).join(`
3
- `);u.note(c,"Unavailable on edge runtime")}u.log.info(f.bold("Features"));let T=e?.paymentProvider??await(async()=>{t=!0;let c=await u.select({message:"Payment provider:",options:Qe.map(p=>({value:p,label:Je[p].label,hint:Je[p].hint}))});return w(c),c})(),H=e?.defaultCurrency??await(async()=>{if(T==="none")return"USD";t=!0;let c=await u.select({message:"Default currency:",options:ce.map(p=>({value:p,label:p,hint:ae[p].name}))});return w(c),c})(),D=e?.emailProvider??await(async()=>{t=!0;let c=await u.select({message:"Email provider:",options:et.map(p=>({value:p,label:qe[p].label,hint:qe[p].hint}))});return w(c),c})(),ie=e?.multiTenancy??await(async()=>{t=!0;let c=await u.confirm({message:"Enable multi-tenancy (organizations)?",initialValue:!1});return w(c),c})(),oe=e?.billingScope??"user";if(ie&&e?.billingScope===void 0){t=!0;let c=await u.select({message:"Billing scope:",options:nt.map(p=>({value:p,label:We[p].label,hint:We[p].hint}))});w(c),oe=c}let I=e?.blog??await(async()=>{t=!0;let c=await u.confirm({message:"Enable blog?",initialValue:!0});return w(c),c})(),y=e?.docs??await(async()=>{t=!0;let c=await u.confirm({message:"Include docs app? (self-hosted Fumadocs documentation site)",initialValue:!1});return w(c),c})(),R=e?.revenueSharing??await(async()=>{t=!0;let c=await u.confirm({message:"Enable revenue sharing? (opt-in MRR leaderboard with dofollow backlinks)",initialValue:!1});return w(c),c})(),Y=T==="none"?!1:e?.credits??await(async()=>{t=!0;let c=await u.confirm({message:"Enable credits? (metered usage on top of subscription plans)",initialValue:!0});return w(c),c})(),J=e?.socialProviders??await(async()=>{t=!0;let c=it.map(v=>({value:v,label:W[v].label,hint:`requires ${W[v].envVars.map(C=>C.name).join(" / ")}`})),p=await u.multiselect({message:"Which social login providers should the sign-in screen show?",options:c,initialValues:[],required:!1});return w(p),p})();u.log.info(f.bold("Tooling"));let q=e?.dockerServices??await(async()=>{t=!0;let c=[...tt].filter(N=>N!=="mailpit");D==="smtp"&&c.push("mailpit");let p=c.map(N=>({value:N,label:we[N].label,hint:we[N].hint})),v=p.map(N=>N.value).filter(N=>!(N==="postgres"&&(m==="neon"||m==="supabase")||N==="redis"&&S==="upstash")),C=await u.multiselect({message:"Which services should we set up in Docker for you?",options:p,initialValues:v,required:!1});return w(C),C})(),Ee=e?.aiTools??await(async()=>{t=!0;let c=rt.map(v=>({value:v,label:Ne[v].label})),p=await u.multiselect({message:"Which AI coding tools do you use?",options:c,initialValues:[],required:!1});return w(p),p})(),St=e?.demo,He=wt({databaseProvider:m,cacheProvider:S,paymentProvider:T,emailProvider:D,socialProviders:J,demo:St}),xe={};if(He.length>0&&t){u.log.info(f.bold("Credentials")+f.dim(" all optional - press Enter to skip, fill in .env later"));for(let c of He)if(t=!0,c.secret){let p=await u.password({message:c.message,mask:"*"});w(p),typeof p=="string"&&p.trim()&&(xe[c.key]=p.trim())}else{let p=await u.text({message:c.message,placeholder:c.placeholder});w(p),typeof p=="string"&&p.trim()&&(xe[c.key]=p.trim())}}if(t){let c=[` Name: ${f.cyan(r)}`,` App name: ${f.cyan(n)}`,` Location: ${f.cyan(i)}`,` Frontend: ${f.cyan(De[o].label)}`,` Architecture: ${f.cyan(Ce[g].label)}`].join(`
4
- `),p=[` Deploy target: ${f.cyan(B[s]?.label??"Node.js / Docker")}`,` Database: ${f.cyan(L[m].label)}`,` Cache: ${f.cyan(G[S].label)}`,q.length>0?` Docker: ${f.cyan(q.map(se=>we[se].label).join(", "))}`:` Docker: ${f.dim("none")}`].filter(Boolean).join(`
5
- `),v=[T!=="none"?` Payment: ${f.cyan(Je[T].label)} (${H})`:` Payment: ${f.dim("none")}`,` Credits: ${Y?f.cyan("Yes"):f.dim("No")}`,` Email: ${f.cyan(qe[D].label)}`,` Multi-tenancy: ${ie?f.cyan("Yes")+` (billing: ${We[oe].label})`:f.dim("No")}`,` Blog: ${I?f.cyan("Yes"):f.dim("No")}`,` Docs app: ${y?f.cyan("Yes"):f.dim("No")}`,` Rev. sharing: ${R?f.cyan("Yes"):f.dim("No")}`,J.length>0?` Social login: ${f.cyan(J.map(se=>W[se].label).join(", "))}`:` Social login: ${f.dim("none")}`,Ee.length>0?` AI tools: ${f.cyan(Ee.map(se=>Ne[se].label).join(", "))}`:` AI tools: ${f.dim("none")}`].join(`
2
+ import{Command as ls}from"commander";import*as In from"@clack/prompts";import{existsSync as Oo,readdirSync as xo,rmSync as Do}from"fs";import{join as Co,resolve as No}from"path";import{Option as V}from"commander";import*as E from"@clack/prompts";import*as Ye from"@clack/prompts";import Et from"picocolors";function Kt(e){let t=e?` GenerateSaaS v${e} `:" GenerateSaaS ";Ye.intro(Et.bgYellow(Et.black(t)))}function zt(){Ye.outro(Et.yellow("Happy building!"))}import*as u from"@clack/prompts";import f from"picocolors";var De={nextjs:{label:"Next.js",hint:"React 19 + Next.js 16"},nuxt:{label:"Nuxt",hint:"Vue 3 + Nuxt 4"}},Ce={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)"}},Je={stripe:{label:"Stripe"},polar:{label:"Polar"},none:{label:"None",hint:"disable payments"}},We={smtp:{label:"SMTP",hint:"Mailpit for local dev"},ses:{label:"Amazon SES"},resend:{label:"Resend"}},qe={user:{label:"Per user",hint:"each user has their own subscription"},organization:{label:"Per organization",hint:"org subscription shared by members"}},we={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}},Ne={"claude-code":{label:"Claude Code"},cursor:{label:"Cursor"},codex:{label:"Codex"},"gemini-cli":{label:"Gemini CLI"},windsurf:{label:"Windsurf"}};var ae={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}},L={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"}]}},Le=[{target:"vercel",provider:"redis",reason:"Vercel serverless cannot maintain persistent Redis connections. Consider Upstash."}],$e=[{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)."}],Ht=["Local file storage (sharp, geoip-lite)","SMTP email (use Resend or SES instead)","Content API git integration"];function Xe(e){let t=L[e.databaseProvider].managed,r=G[e.cacheProvider].managed;return e.dockerServices.some(n=>!(n==="postgres"&&t||n==="redis"&&r))}var q={google:{label:"Google",envVars:[{name:"GOOGLE_CLIENT_ID",secret:!1},{name:"GOOGLE_CLIENT_SECRET",secret:!0}]},github:{label:"GitHub",envVars:[{name:"GITHUB_CLIENT_ID",secret:!1},{name:"GITHUB_CLIENT_SECRET",secret:!0}]},facebook:{label:"Facebook",envVars:[{name:"FACEBOOK_CLIENT_ID",secret:!1},{name:"FACEBOOK_CLIENT_SECRET",secret:!0}]},discord:{label:"Discord",envVars:[{name:"DISCORD_CLIENT_ID",secret:!1},{name:"DISCORD_CLIENT_SECRET",secret:!0}]},x:{label:"X",envVars:[{name:"TWITTER_CLIENT_ID",secret:!1},{name:"TWITTER_CLIENT_SECRET",secret:!0}]}};var Ue=["nextjs","nuxt"],Ze=["fullstack","separate"],Qe=["stripe","polar","none"],et=["smtp","ses","resend"],tt=["postgres","redis","inngest","mailpit"],rt=["claude-code","cursor","codex","gemini-cli","windsurf"],nt=["user","organization"],ce=["USD","EUR","GBP","CAD","AUD","BRL","JPY"],le=["node","vercel"],pe=["postgres","neon","supabase"],de=["redis","upstash"],it=["google","github","facebook","discord","x"];function ot(e){return e.split("-").map(t=>t.charAt(0).toUpperCase()+t.slice(1)).join(" ")}function st(e){return/^[a-z][a-z0-9-]*$/.test(e)}function I(e){return e instanceof Error?e.message:String(e)}function w(e){u.isCancel(e)&&(u.cancel("Setup cancelled."),process.exit(0))}function wt(e){let t=[];e.databaseProvider==="neon"&&t.push({key:"DATABASE_URL",message:"Neon connection string (optional):",placeholder:"postgres://...",secret:!0}),e.databaseProvider==="supabase"&&t.push({key:"DATABASE_URL",message:"Supabase connection string (optional):",placeholder:"postgres://...",secret:!0}),e.cacheProvider==="upstash"&&(t.push({key:"UPSTASH_REDIS_REST_URL",message:"Upstash REST URL (optional):",placeholder:"https://...",secret:!1}),t.push({key:"UPSTASH_REDIS_REST_TOKEN",message:"Upstash REST token (optional):",secret:!0})),e.paymentProvider==="stripe"&&(t.push({key:"STRIPE_SECRET_KEY",message:"Stripe secret key (optional):",placeholder:"sk_test_...",secret:!0}),t.push({key:"STRIPE_WEBHOOK_SECRET",message:"Stripe webhook secret (optional):",placeholder:"whsec_...",secret:!0})),e.paymentProvider==="polar"&&(t.push({key:"POLAR_ACCESS_TOKEN",message:"Polar access token (optional):",secret:!0}),t.push({key:"POLAR_WEBHOOK_SECRET",message:"Polar webhook secret (optional):",secret:!0})),e.emailProvider==="resend"&&t.push({key:"RESEND_API_KEY",message:"Resend API key (optional):",placeholder:"re_...",secret:!0}),e.emailProvider==="ses"&&(t.push({key:"AMAZON_SES_REGION",message:"Amazon SES region (optional):",placeholder:"us-east-1",secret:!1}),t.push({key:"AMAZON_SES_KEY",message:"Amazon SES access key (optional):",secret:!0}),t.push({key:"AMAZON_SES_SECRET",message:"Amazon SES secret (optional):",secret:!0}));for(let r of e.socialProviders){let n=q[r];for(let 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 Yt(e){let t=!1;u.log.info(f.bold("Project"));let r=e?.projectName??await(async()=>{t=!0;let c=await u.text({message:"Project name:",placeholder:"my-saas",validate:p=>{if(!p?.trim())return"Project name is required.";if(!st(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 u.text({message:"App name:",initialValue:ot(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 u.text({message:"Project location:",initialValue:`./${r}`});return w(c),c==="."?process.cwd():c})(),o=e?.frontend??await(async()=>{t=!0;let c=Object.keys(De),p=await u.select({message:"Frontend framework:",options:c.map(v=>({value:v,label:De[v].label,hint:De[v].hint}))});return w(p),p})();u.log.info(f.bold("Infrastructure"));let s=e?.deploymentTarget??"node";if(e?.deploymentTarget===void 0){t=!0;let c=await u.select({message:"Deployment target:",options:le.map(p=>({value:p,label:B[p].label,hint:B[p].hint}))});w(c),s=c}let a=e?.architecture?$e.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($e.filter(c=>c.target===s).map(c=>c.architecture)),h=Ze.filter(c=>!d.has(c)),g=e?.architecture??await(async()=>{if(h.length===1){let p=h[0];return u.log.info(`Auto-selected ${Ce[p].label} architecture (only compatible option for ${B[s].label}).`),p}t=!0;let c=await u.select({message:"Architecture:",options:h.map(p=>({value:p,label:Ce[p].label,hint:Ce[p].hint}))});return w(c),c})(),m=e?.databaseProvider??await(async()=>{t=!0;let c=pe.filter(v=>!Le.some(C=>C.target===s&&C.provider===v));if(c.length===1){let v=c[0];return u.log.info(`Auto-selected ${L[v].label} (only compatible option for ${B[s].label}).`),v}let p=await u.select({message:"Database provider:",options:c.map(v=>({value:v,label:L[v].label,hint:L[v].hint}))});return w(p),p})(),S=e?.cacheProvider??await(async()=>{t=!0;let c=de.filter(v=>!Le.some(C=>C.target===s&&C.provider===v));if(c.length===1){let v=c[0];return u.log.info(`Auto-selected ${G[v].label} (only compatible option for ${B[s].label}).`),v}let p=await u.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=Ht.map(p=>` - ${p}`).join(`
3
+ `);u.note(c,"Unavailable on edge runtime")}u.log.info(f.bold("Features"));let T=e?.paymentProvider??await(async()=>{t=!0;let c=await u.select({message:"Payment provider:",options:Qe.map(p=>({value:p,label:Je[p].label,hint:Je[p].hint}))});return w(c),c})(),H=e?.defaultCurrency??await(async()=>{if(T==="none")return"USD";t=!0;let c=await u.select({message:"Default currency:",options:ce.map(p=>({value:p,label:p,hint:ae[p].name}))});return w(c),c})(),D=e?.emailProvider??await(async()=>{t=!0;let c=await u.select({message:"Email provider:",options:et.map(p=>({value:p,label:We[p].label,hint:We[p].hint}))});return w(c),c})(),ie=e?.multiTenancy??await(async()=>{t=!0;let c=await u.confirm({message:"Enable multi-tenancy (organizations)?",initialValue:!1});return w(c),c})(),oe=e?.billingScope??"user";if(ie&&e?.billingScope===void 0){t=!0;let c=await u.select({message:"Billing scope:",options:nt.map(p=>({value:p,label:qe[p].label,hint:qe[p].hint}))});w(c),oe=c}let k=e?.blog??await(async()=>{t=!0;let c=await u.confirm({message:"Enable blog?",initialValue:!0});return w(c),c})(),y=e?.docs??await(async()=>{t=!0;let c=await u.confirm({message:"Include docs app? (self-hosted Fumadocs documentation site)",initialValue:!1});return w(c),c})(),R=e?.revenueSharing??await(async()=>{t=!0;let c=await u.confirm({message:"Enable revenue sharing? (opt-in MRR leaderboard with dofollow backlinks)",initialValue:!1});return w(c),c})(),Y=T==="none"?!1:e?.credits??await(async()=>{t=!0;let c=await u.confirm({message:"Enable credits? (metered usage on top of subscription plans)",initialValue:!0});return w(c),c})(),J=e?.socialProviders??await(async()=>{t=!0;let c=it.map(v=>({value:v,label:q[v].label,hint:`requires ${q[v].envVars.map(C=>C.name).join(" / ")}`})),p=await u.multiselect({message:"Which social login providers should the sign-in screen show?",options:c,initialValues:[],required:!1});return w(p),p})();u.log.info(f.bold("Tooling"));let W=e?.dockerServices??await(async()=>{t=!0;let c=[...tt].filter(N=>N!=="mailpit");D==="smtp"&&c.push("mailpit");let p=c.map(N=>({value:N,label:we[N].label,hint:we[N].hint})),v=p.map(N=>N.value).filter(N=>!(N==="postgres"&&(m==="neon"||m==="supabase")||N==="redis"&&S==="upstash")),C=await u.multiselect({message:"Which services should we set up in Docker for you?",options:p,initialValues:v,required:!1});return w(C),C})(),Ee=e?.aiTools??await(async()=>{t=!0;let c=rt.map(v=>({value:v,label:Ne[v].label})),p=await u.multiselect({message:"Which AI coding tools do you use?",options:c,initialValues:[],required:!1});return w(p),p})(),St=e?.demo,He=wt({databaseProvider:m,cacheProvider:S,paymentProvider:T,emailProvider:D,socialProviders:J,demo:St}),xe={};if(He.length>0&&t){u.log.info(f.bold("Credentials")+f.dim(" all optional - press Enter to skip, fill in .env later"));for(let c of He)if(t=!0,c.secret){let p=await u.password({message:c.message,mask:"*"});w(p),typeof p=="string"&&p.trim()&&(xe[c.key]=p.trim())}else{let p=await u.text({message:c.message,placeholder:c.placeholder});w(p),typeof p=="string"&&p.trim()&&(xe[c.key]=p.trim())}}if(t){let c=[` Name: ${f.cyan(r)}`,` App name: ${f.cyan(n)}`,` Location: ${f.cyan(i)}`,` Frontend: ${f.cyan(De[o].label)}`,` Architecture: ${f.cyan(Ce[g].label)}`].join(`
4
+ `),p=[` Deploy target: ${f.cyan(B[s]?.label??"Node.js / Docker")}`,` Database: ${f.cyan(L[m].label)}`,` Cache: ${f.cyan(G[S].label)}`,W.length>0?` Docker: ${f.cyan(W.map(se=>we[se].label).join(", "))}`:` Docker: ${f.dim("none")}`].filter(Boolean).join(`
5
+ `),v=[T!=="none"?` Payment: ${f.cyan(Je[T].label)} (${H})`:` Payment: ${f.dim("none")}`,` Credits: ${Y?f.cyan("Yes"):f.dim("No")}`,` Email: ${f.cyan(We[D].label)}`,` Multi-tenancy: ${ie?f.cyan("Yes")+` (billing: ${qe[oe].label})`:f.dim("No")}`,` Blog: ${k?f.cyan("Yes"):f.dim("No")}`,` Docs app: ${y?f.cyan("Yes"):f.dim("No")}`,` Rev. sharing: ${R?f.cyan("Yes"):f.dim("No")}`,J.length>0?` Social login: ${f.cyan(J.map(se=>q[se].label).join(", "))}`:` Social login: ${f.dim("none")}`,Ee.length>0?` AI tools: ${f.cyan(Ee.map(se=>Ne[se].label).join(", "))}`:` AI tools: ${f.dim("none")}`].join(`
6
6
  `),C=[f.bold("Project"),c,"",f.bold("Infrastructure"),p,"",f.bold("Features"),v];if(He.length>0){let se=He.map(Gt=>{let Pn=xe[Gt.key]?f.green("provided"):f.dim("skipped");return` ${Gt.key}: ${Pn}`}).join(`
7
7
  `);C.push("",f.bold("Credentials"),se)}u.note(C.join(`
8
- `),"Summary");let N=await u.confirm({message:"Proceed with these settings?"});(u.isCancel(N)||!N)&&(u.cancel("Setup cancelled."),process.exit(0))}return{projectName:r,appName:n,projectDir:i,frontend:o,architecture:g,deploymentTarget:s,databaseProvider:m,cacheProvider:S,paymentProvider:T,emailProvider:D,multiTenancy:ie,billingScope:oe,blog:I,docs:y,revenueSharing:R,credits:Y,dockerServices:q,aiTools:Ee,socialProviders:J,defaultCurrency:H,...Object.keys(xe).length>0?{credentials:xe}:{},...e?.baseUrl!==void 0?{baseUrl:e.baseUrl}:{},...St!==void 0?{demo:St}:{}}}import{createReadStream as $n}from"fs";import{mkdir as Un}from"fs/promises";import{Readable as jn}from"stream";import{pipeline as rr}from"stream/promises";import{extract as Vn}from"tar";import{join as ue}from"path";import{homedir as _n}from"os";var je=process.env.GENERATESAAS_API_URL??"https://cli.generatesaas.com",U=".generatesaas",Q=ue(U,"manifest.json"),Jt=ue(U,"hashes.json"),at=ue(U,"template-hashes.json"),ct=ue(U,"template"),qt=ue(U,"staging"),Wt=ue(U,"staging.json"),X=ue(_n(),".generatesaas");var O=class extends Error{constructor(r,n,i){super(n);this.status=r;this.body=i}status;body;name="ApiError"};function Z(e){return{apiKey:e,baseUrl:je}}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 O(i.status,o,s)}return i}import{existsSync as Rn,readFileSync as On,writeFileSync as xn,mkdirSync as Dn}from"fs";import{dirname as Cn}from"path";import*as te from"@clack/prompts";function Ve(){if(!Rn(X))return null;try{let e=JSON.parse(On(X,"utf-8"));return e.apiKey?e.apiKey:(e.token&&!e.apiKey&&te.log.warning(`Found old GitHub token in ${X}. Run 'generatesaas init' to set up your API key.`),null)}catch{return null}}function me(e){Dn(Cn(X),{recursive:!0}),xn(X,JSON.stringify({apiKey:e},null," ")+`
9
- `,{mode:384})}async function be(e){if(e?.apiKey)return e.apiKey;let t=process.env.GENERATESAAS_API_KEY;if(t)return t;let r=Ve();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 Me()}async function Me(){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 Xt(e,t){try{return await(await ee(e,`/changelog/${encodeURIComponent(t)}`)).text()}catch(r){if(r instanceof O&&r.status===404)return null;throw r}}async function Zt(e,t){return await(await ee(e,`/skill/${encodeURIComponent(t)}`)).json()}function lt(e){if(!(e instanceof O)||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 Qt(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 bt(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 er(e,t){return await(await ee(e,"/license/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).json()}async function At(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 tr(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 Tt=new Set([".git","node_modules",".pnpm-store",".env",".env.test",".turbo",".nuxt",".output",".data","dist",".next",".svelte-kit",".devcontainer","playwright-report","test-results"]),Nn=new Set(["data","mksaas","references","scripts",".cursor",".agents",".codex",".generatesaas",".vscode",".mcp.json","README.md","TODO.md","OVERVIEW.md"]),Ln=["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 Ae(e){let t=e.split("/");for(let r of t)if(Tt.has(r))return!0;if(Nn.has(t[0]))return!0;for(let r of Ln)if(e===r||e.startsWith(r+"/"))return!0;return!1}async function pt(e,t,r){await Un(r,{recursive:!0});let n=process.env.GENERATESAAS_TEMPLATE_TARBALL;if(n){await rr($n(n),nr(r));return}let i=await ee(e,`/template/${encodeURIComponent(t)}`);if(!i.body)throw new Error("Empty response body");let o=jn.fromWeb(i.body);await rr(o,nr(r))}function nr(e){return Vn({cwd:e,strip:1,filter:t=>{let r=t.replace(/^[^/]+\//,"");return r?!Ae(r):!0},sync:!1})}import{readFile as Mn,rm as ir,writeFile as Fn}from"fs/promises";import{join as It}from"path";var Bn=["apps/web-nuxt/public/images/blog","apps/web-next/public/images/blog"];async function or(e){await Promise.all(Bn.map(t=>ir(It(e,t),{recursive:!0,force:!0})))}async function sr(e,t){t.includes("claude-code")||await ir(It(e,".claude"),{recursive:!0,force:!0})}async function ar(e,t){let r=It(e,".claude","settings.json"),n;try{n=await Mn(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=>Gn(o,t))),await Fn(r,JSON.stringify(i,null," ")+`
10
- `)}function Gn(e,t){return!(e.startsWith("mcp__")||t!=="nuxt"&&e.includes("nuxt"))}import{join as cr}from"path";import{mkdir as Kn,readdir as zn,rm as Hn,rmdir as Yn,writeFile as Jn}from"fs/promises";import{dirname as dt,join as qn,relative as Wn}from"path";async function ut(e){await Kn(e,{recursive:!0})}async function l(e,t){await ut(dt(e)),await Jn(e,t,"utf-8")}async function kt(e,t){await Hn(e,{force:!0});let r=dt(e);for(;r!==t&&r!==dt(r);){try{await Yn(r)}catch{return}r=dt(r)}}async function fe(e,t,r){let n=[],i=await zn(e,{withFileTypes:!0});for(let o of i){let s=qn(e,o.name),a=Wn(t,s);r(a)||(o.isDirectory()?n.push(...await fe(s,t,r)):o.isFile()&&n.push(s))}return n}var Xn={postgres:"Postgres",neon:"Neon (managed Postgres)",supabase:"Supabase (managed Postgres)"},Zn={resend:"Resend",ses:"Amazon SES",smtp:"SMTP"},Qn={redis:"Redis",upstash:"Upstash Redis"},ei={node:"Node.js / Docker",vercel:"Vercel"},ti={stripe:"Stripe",polar:"Polar"};function ri(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=Qn[e.cacheProvider],h=ei[e.deploymentTarget],g=e.paymentProvider==="none"?"":`
8
+ `),"Summary");let N=await u.confirm({message:"Proceed with these settings?"});(u.isCancel(N)||!N)&&(u.cancel("Setup cancelled."),process.exit(0))}return{projectName:r,appName:n,projectDir:i,frontend:o,architecture:g,deploymentTarget:s,databaseProvider:m,cacheProvider:S,paymentProvider:T,emailProvider:D,multiTenancy:ie,billingScope:oe,blog:k,docs:y,revenueSharing:R,credits:Y,dockerServices:W,aiTools:Ee,socialProviders:J,defaultCurrency:H,...Object.keys(xe).length>0?{credentials:xe}:{},...e?.baseUrl!==void 0?{baseUrl:e.baseUrl}:{},...St!==void 0?{demo:St}:{}}}import{createReadStream as $n}from"fs";import{mkdir as Un}from"fs/promises";import{Readable as jn}from"stream";import{pipeline as rr}from"stream/promises";import{extract as Vn}from"tar";import{join as ue}from"path";import{homedir as _n}from"os";var je=process.env.GENERATESAAS_API_URL??"https://cli.generatesaas.com",U=".generatesaas",Q=ue(U,"manifest.json"),Jt=ue(U,"hashes.json"),at=ue(U,"template-hashes.json"),ct=ue(U,"template"),Wt=ue(U,"staging"),qt=ue(U,"staging.json"),X=ue(_n(),".generatesaas");var O=class extends Error{constructor(r,n,i){super(n);this.status=r;this.body=i}status;body;name="ApiError"};function Z(e){return{apiKey:e,baseUrl:je}}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 O(i.status,o,s)}return i}import{existsSync as Rn,readFileSync as On,writeFileSync as xn,mkdirSync as Dn}from"fs";import{dirname as Cn}from"path";import*as te from"@clack/prompts";function Ve(){if(!Rn(X))return null;try{let e=JSON.parse(On(X,"utf-8"));return e.apiKey?e.apiKey:(e.token&&!e.apiKey&&te.log.warning(`Found old GitHub token in ${X}. Run 'generatesaas init' to set up your API key.`),null)}catch{return null}}function me(e){Dn(Cn(X),{recursive:!0}),xn(X,JSON.stringify({apiKey:e},null," ")+`
9
+ `,{mode:384})}async function be(e){if(e?.apiKey)return e.apiKey;let t=process.env.GENERATESAAS_API_KEY;if(t)return t;let r=Ve();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 Me()}async function Me(){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 Xt(e,t){try{return await(await ee(e,`/changelog/${encodeURIComponent(t)}`)).text()}catch(r){if(r instanceof O&&r.status===404)return null;throw r}}async function Zt(e,t){return await(await ee(e,`/skill/${encodeURIComponent(t)}`)).json()}function lt(e){if(!(e instanceof O)||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 Qt(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 bt(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 er(e,t){return await(await ee(e,"/license/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).json()}async function At(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 tr(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 Tt=new Set([".git","node_modules",".pnpm-store",".env",".env.test",".turbo",".nuxt",".output",".data","dist",".next",".svelte-kit",".devcontainer","playwright-report","test-results"]),Nn=new Set(["data","mksaas","references","scripts",".cursor",".agents",".codex",".generatesaas",".vscode",".mcp.json","README.md","TODO.md","OVERVIEW.md"]),Ln=["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 Ae(e){let t=e.split("/");for(let r of t)if(Tt.has(r))return!0;if(Nn.has(t[0]))return!0;for(let r of Ln)if(e===r||e.startsWith(r+"/"))return!0;return!1}async function pt(e,t,r){await Un(r,{recursive:!0});let n=process.env.GENERATESAAS_TEMPLATE_TARBALL;if(n){await rr($n(n),nr(r));return}let i=await ee(e,`/template/${encodeURIComponent(t)}`);if(!i.body)throw new Error("Empty response body");let o=jn.fromWeb(i.body);await rr(o,nr(r))}function nr(e){return Vn({cwd:e,strip:1,filter:t=>{let r=t.replace(/^[^/]+\//,"");return r?!Ae(r):!0},sync:!1})}import{readFile as Mn,rm as ir,writeFile as Fn}from"fs/promises";import{join as kt}from"path";var Bn=["apps/web-nuxt/public/images/blog","apps/web-next/public/images/blog"];async function or(e){await Promise.all(Bn.map(t=>ir(kt(e,t),{recursive:!0,force:!0})))}async function sr(e,t){t.includes("claude-code")||await ir(kt(e,".claude"),{recursive:!0,force:!0})}async function ar(e,t){let r=kt(e,".claude","settings.json"),n;try{n=await Mn(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=>Gn(o,t))),await Fn(r,JSON.stringify(i,null," ")+`
10
+ `)}function Gn(e,t){return!(e.startsWith("mcp__")||t!=="nuxt"&&e.includes("nuxt"))}import{join as cr}from"path";import{mkdir as Kn,readdir as zn,rm as Hn,rmdir as Yn,writeFile as Jn}from"fs/promises";import{dirname as dt,join as Wn,relative as qn}from"path";async function ut(e){await Kn(e,{recursive:!0})}async function l(e,t){await ut(dt(e)),await Jn(e,t,"utf-8")}async function It(e,t){await Hn(e,{force:!0});let r=dt(e);for(;r!==t&&r!==dt(r);){try{await Yn(r)}catch{return}r=dt(r)}}async function fe(e,t,r){let n=[],i=await zn(e,{withFileTypes:!0});for(let o of i){let s=Wn(e,o.name),a=qn(t,s);r(a)||(o.isDirectory()?n.push(...await fe(s,t,r)):o.isFile()&&n.push(s))}return n}var Xn={postgres:"Postgres",neon:"Neon (managed Postgres)",supabase:"Supabase (managed Postgres)"},Zn={resend:"Resend",ses:"Amazon SES",smtp:"SMTP"},Qn={redis:"Redis",upstash:"Upstash Redis"},ei={node:"Node.js / Docker",vercel:"Vercel"},ti={stripe:"Stripe",polar:"Polar"};function ri(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=Qn[e.cacheProvider],h=ei[e.deploymentTarget],g=e.paymentProvider==="none"?"":`
11
11
  - **Payments:** ${ti[e.paymentProvider]}`,m=t?"`$t('key')` in templates (global helper); `useI18n()` from `vue-i18n` in `<script setup>` for `locale`/`setLocale`/`t()`.":"`useTranslations()` from `next-intl` in components; messages are loaded in `apps/web-next/i18n/request.ts`.",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
12
 
13
13
  Guidelines for AI coding agents (Claude Code, Codex, Cursor, \u2026) in this project.
@@ -427,7 +427,7 @@ ${o?" credits: { enabled: true }":" credits: { enabled: false }"},
427
427
  items: []
428
428
  }
429
429
  };
430
- `;await l(t,g)}var ci={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 li(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 pi={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 Te(e,t){return t?e.map(r=>{let n=t[r.key];return n?{...r,defaultValue:n,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 _t(e){return Array.from(crypto.getRandomValues(new Uint8Array(e))).map(t=>t.toString(16).padStart(2,"0")).join("")}function fr(e){return e.architecture==="fullstack"?{apiUrl:"http://localhost:3000/api",baseUrl:"http://localhost:3000"}:{apiUrl:"http://localhost:3010",baseUrl:"http://localhost:3000"}}function di(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 mt(e,t,r,n){e.push(n==="example"?`${t}=`:`${t}=${r}`)}function mr(e,t){let r=t==="example"?void 0:e.credentials,{apiUrl:n,baseUrl:i}=fr(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"),Ie(Te(L[e.databaseProvider].envVars,r),o),o.push("","# Cache"),Ie(Te(G[e.cacheProvider].envVars,r),o),o.push("","# Authentication"),t==="example"&&o.push("# Generate a strong secret, e.g. `openssl rand -hex 32`"),mt(o,"BETTER_AUTH_SECRET",crypto.randomUUID(),t),o.push("","# Content API (random secret; required when contentApi feature is enabled in @repo/config)"),mt(o,"CONTENT_API_KEY",_t(32),t),o.push("","# Job Queue - Inngest","INNGEST_APP_ID=api"),mt(o,"INNGEST_EVENT_KEY",_t(32),t),mt(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=ci[e.emailProvider];if(s&&(o.push("","# Email"),Ie(Te(s,r),o)),e.paymentProvider!=="none"){let a=pi[e.paymentProvider];a&&(o.push("","# Payment"),Ie(Te(a,r),o))}if(e.socialProviders.length>0){o.push("","# Social auth");for(let a of e.socialProviders)Ie(Te(li(a),r),o)}return e.demo&&(o.push("","# Captcha (Cloudflare Turnstile)"),Ie(Te([{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(`
430
+ `;await l(t,g)}var ci={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 li(e){let t=q[e];return t.envVars.map((r,n)=>({key:r.name,...n===0?{comment:`# TODO: Add your ${t.label} OAuth credentials`}:{}}))}var pi={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 Te(e,t){return t?e.map(r=>{let n=t[r.key];return n?{...r,defaultValue:n,comment:void 0}:r}):e}function ke(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 fr(e){return e.architecture==="fullstack"?{apiUrl:"http://localhost:3000/api",baseUrl:"http://localhost:3000"}:{apiUrl:"http://localhost:3010",baseUrl:"http://localhost:3000"}}function di(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 mt(e,t,r,n){e.push(n==="example"?`${t}=`:`${t}=${r}`)}function mr(e,t){let r=t==="example"?void 0:e.credentials,{apiUrl:n,baseUrl:i}=fr(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"),ke(Te(L[e.databaseProvider].envVars,r),o),o.push("","# Cache"),ke(Te(G[e.cacheProvider].envVars,r),o),o.push("","# Authentication"),t==="example"&&o.push("# Generate a strong secret, e.g. `openssl rand -hex 32`"),mt(o,"BETTER_AUTH_SECRET",crypto.randomUUID(),t),o.push("","# Content API (random secret; required when contentApi feature is enabled in @repo/config)"),mt(o,"CONTENT_API_KEY",_t(32),t),o.push("","# Job Queue - Inngest","INNGEST_APP_ID=api"),mt(o,"INNGEST_EVENT_KEY",_t(32),t),mt(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=ci[e.emailProvider];if(s&&(o.push("","# Email"),ke(Te(s,r),o)),e.paymentProvider!=="none"){let a=pi[e.paymentProvider];a&&(o.push("","# Payment"),ke(Te(a,r),o))}if(e.socialProviders.length>0){o.push("","# Social auth");for(let a of e.socialProviders)ke(Te(li(a),r),o)}return e.demo&&(o.push("","# Captcha (Cloudflare Turnstile)"),ke(Te([{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(`
431
431
  `)}function ui(e){let{apiUrl:t}=fr(e),r=e.frontend==="nextjs"?"NEXT_PUBLIC_API_URL":"NUXT_PUBLIC_API_URL",n=["# API Configuration",`${r}=${t}`],i=di(e);return i&&e.architecture==="separate"&&n.push("","# Production (uncomment and replace with your deployed hostnames):",`# ${r}=${i.backend}`),n.push(""),n.join(`
432
432
  `)}async function gr(e){let t=ui(e);await l(`${e.projectDir}/.env`,t+`
433
433
  `+mr(e,"env")),await l(`${e.projectDir}/.env.example`,t+`
@@ -565,7 +565,7 @@ const client = postgres(parsed.data.DATABASE_URL);
565
565
  export const db = drizzle(client, { schema });
566
566
 
567
567
  ${e}
568
- ${Rt}`}async function Er(e){switch(e.cacheProvider){case"redis":await bi(e),await Ai(e);break;case"upstash":await Ti(e),await Ii(e);break}}async function bi(e){await l(`${e.projectDir}/packages/runtime/src/redis.ts`,`import type { Store } from "hono-rate-limiter";
568
+ ${Rt}`}async function Er(e){switch(e.cacheProvider){case"redis":await bi(e),await Ai(e);break;case"upstash":await Ti(e),await ki(e);break}}async function bi(e){await l(`${e.projectDir}/packages/runtime/src/redis.ts`,`import type { Store } from "hono-rate-limiter";
569
569
  import { Redis } from "ioredis";
570
570
  import { RedisStore, type RedisReply } from "rate-limit-redis";
571
571
  import { env } from "./env";
@@ -596,7 +596,13 @@ export interface BoilerplateRedisHelpers {
596
596
  cacheScan(opts: { match: string; count?: number }): AsyncIterable<string>;
597
597
  }
598
598
 
599
- const ioredis = new Redis(env.REDIS_URL);
599
+ // \`lazyConnect\` keeps the client from dialing Redis the moment this module is
600
+ // imported. Without it, a build/prerender (or any process that merely imports
601
+ // @repo/runtime) eagerly opens a connection, and if Redis isn't reachable yet -
602
+ // e.g. a Docker/Dokploy build where the Redis host only resolves at runtime -
603
+ // ioredis retries the DNS lookup forever and the build hangs. With lazyConnect
604
+ // the connection opens on the first real command instead, at runtime.
605
+ const ioredis = new Redis(env.REDIS_URL, { lazyConnect: true });
600
606
 
601
607
  /**
602
608
  * Native ioredis client + the three boilerplate helpers. Every native
@@ -818,7 +824,7 @@ export const limiterStore: Store = createLimiterStore();
818
824
 
819
825
  /** No persistent connection to close with Upstash REST. */
820
826
  export async function closeRedis(): Promise<void> {}
821
- `)}async function Ii(e){await l(`${e.projectDir}/packages/runtime/src/mutex.ts`,`import { Lock } from "@upstash/lock";
827
+ `)}async function ki(e){await l(`${e.projectDir}/packages/runtime/src/mutex.ts`,`import { Lock } from "@upstash/lock";
822
828
  import { redis } from "./redis";
823
829
 
824
830
  export class MutexTimeoutError extends Error {
@@ -863,7 +869,7 @@ export async function withMutex<T>(
863
869
  await lock.release();
864
870
  }
865
871
  }
866
- `)}async function wr(e){await l(`${e.projectDir}/packages/runtime/src/env.ts`,ki(e))}function ki(e){return`import { z } from "zod";
872
+ `)}async function wr(e){await l(`${e.projectDir}/packages/runtime/src/env.ts`,Ii(e))}function Ii(e){return`import { z } from "zod";
867
873
 
868
874
  const EnvSchema = z.object({
869
875
  NODE_ENV: z.enum(["development", "production"]).default("development"),
@@ -982,8 +988,8 @@ const parsedApiUrl = new URL(env.API_URL);
982
988
  export const apiBasePath = parsedApiUrl.pathname === "/" ? "" : parsedApiUrl.pathname;
983
989
  export const apiOrigin = parsedApiUrl.origin;
984
990
  `}import{readFile as Pi}from"fs/promises";import{join as K}from"path";var ft={"@upstash/redis":"^1.37.0","@upstash/lock":"^0.2.1","@neondatabase/serverless":"^1.0.1",postgres:"^3.4.7"};async function Pe(e){let t=await Pi(e,"utf-8");return JSON.parse(t)}async function _e(e,t){await l(e,JSON.stringify(t,null," ")+`
985
- `)}function Fe(e,t){for(let r of t)delete e.dependencies?.[r],delete e.devDependencies?.[r]}function ke(e,t,r,n=!1){let i=n?"devDependencies":"dependencies";e[i]||(e[i]={}),e[i][t]=r}async function br(e){await _i(e),await Ri(e),await Oi(e),await xi(e),e.frontend==="nextjs"?await Ci(e):await Di(e)}async function _i(e){let t=K(e.projectDir,"packages/api/package.json"),r=await Pe(t);Fe(r,["sharp","@types/sharp"]),await _e(t,r)}async function Ri(e){let t=K(e.projectDir,"packages/runtime/package.json"),r=await Pe(t);e.cacheProvider==="upstash"&&(Fe(r,["ioredis","rate-limit-redis","redis-semaphore"]),ke(r,"@upstash/redis",ft["@upstash/redis"]),ke(r,"@upstash/lock",ft["@upstash/lock"])),await _e(t,r)}async function Oi(e){let t=K(e.projectDir,"packages/database/package.json"),r=await Pe(t);e.databaseProvider==="neon"?(Fe(r,["pg","@types/pg"]),ke(r,"@neondatabase/serverless",ft["@neondatabase/serverless"])):e.databaseProvider==="supabase"&&(Fe(r,["pg","@types/pg"]),ke(r,"postgres",ft.postgres)),await _e(t,r)}async function xi(e){if(e.architecture!=="separate")return;let t=K(e.projectDir,"apps/backend/package.json"),r=await Pe(t);e.deploymentTarget!=="node"&&Fe(r,["@hono/node-server"]),await _e(t,r)}async function Di(e){if(e.architecture!=="separate")return;let t=K(e.projectDir,"apps/web-nuxt");await kt(K(t,"server/api/[...paths].ts"),t);let r=K(t,"package.json"),n=await Pe(r),i=n.dependencies?.["@repo/api"];i&&(delete n.dependencies?.["@repo/api"],ke(n,"@repo/api",i,!0)),await _e(r,n)}async function Ci(e){if(e.architecture!=="separate")return;let t=K(e.projectDir,"apps/web-next");await kt(K(t,"app/api/[[...rest]]/route.ts"),t);let r=K(t,"package.json"),n=await Pe(r),i=n.dependencies?.["@repo/api"];i&&(delete n.dependencies?.["@repo/api"],ke(n,"@repo/api",i,!0)),await _e(r,n)}import{readFile as Tr}from"fs/promises";import{join as Ir}from"path";async function kr(e){let t=Ir(e.projectDir,"turbo.json"),r;try{r=await Tr(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(h=>h!==o);d.length!==i.env.length&&(i.env=d,a=!0)}if(Array.isArray(i.outputs)){let d=i.outputs.filter(h=>!s.has(h));d.length!==i.outputs.length&&(i.outputs=d,a=!0)}a&&await l(t,JSON.stringify(n,null," ")+`
986
- `)}var Ni=["base.json","node.json","next.json"],Ar="GenerateSaaS ";async function Pr(e){if(!e.demo)for(let t of Ni){let r=Ir(e.projectDir,"tooling/typescript",t),n;try{n=await Tr(r,"utf-8")}catch{continue}let i=JSON.parse(n);typeof i.display!="string"||!i.display.startsWith(Ar)||(i.display=i.display.slice(Ar.length),await l(r,JSON.stringify(i,null," ")+`
991
+ `)}function Fe(e,t){for(let r of t)delete e.dependencies?.[r],delete e.devDependencies?.[r]}function Ie(e,t,r,n=!1){let i=n?"devDependencies":"dependencies";e[i]||(e[i]={}),e[i][t]=r}async function br(e){await _i(e),await Ri(e),await Oi(e),await xi(e),e.frontend==="nextjs"?await Ci(e):await Di(e)}async function _i(e){let t=K(e.projectDir,"packages/api/package.json"),r=await Pe(t);Fe(r,["sharp","@types/sharp"]),await _e(t,r)}async function Ri(e){let t=K(e.projectDir,"packages/runtime/package.json"),r=await Pe(t);e.cacheProvider==="upstash"&&(Fe(r,["ioredis","rate-limit-redis","redis-semaphore"]),Ie(r,"@upstash/redis",ft["@upstash/redis"]),Ie(r,"@upstash/lock",ft["@upstash/lock"])),await _e(t,r)}async function Oi(e){let t=K(e.projectDir,"packages/database/package.json"),r=await Pe(t);e.databaseProvider==="neon"?(Fe(r,["pg","@types/pg"]),Ie(r,"@neondatabase/serverless",ft["@neondatabase/serverless"])):e.databaseProvider==="supabase"&&(Fe(r,["pg","@types/pg"]),Ie(r,"postgres",ft.postgres)),await _e(t,r)}async function xi(e){if(e.architecture!=="separate")return;let t=K(e.projectDir,"apps/backend/package.json"),r=await Pe(t);e.deploymentTarget!=="node"&&Fe(r,["@hono/node-server"]),await _e(t,r)}async function Di(e){if(e.architecture!=="separate")return;let t=K(e.projectDir,"apps/web-nuxt");await It(K(t,"server/api/[...paths].ts"),t);let r=K(t,"package.json"),n=await Pe(r),i=n.dependencies?.["@repo/api"];i&&(delete n.dependencies?.["@repo/api"],Ie(n,"@repo/api",i,!0)),await _e(r,n)}async function Ci(e){if(e.architecture!=="separate")return;let t=K(e.projectDir,"apps/web-next");await It(K(t,"app/api/[[...rest]]/route.ts"),t);let r=K(t,"package.json"),n=await Pe(r),i=n.dependencies?.["@repo/api"];i&&(delete n.dependencies?.["@repo/api"],Ie(n,"@repo/api",i,!0)),await _e(r,n)}import{readFile as Tr}from"fs/promises";import{join as kr}from"path";async function Ir(e){let t=kr(e.projectDir,"turbo.json"),r;try{r=await Tr(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(h=>h!==o);d.length!==i.env.length&&(i.env=d,a=!0)}if(Array.isArray(i.outputs)){let d=i.outputs.filter(h=>!s.has(h));d.length!==i.outputs.length&&(i.outputs=d,a=!0)}a&&await l(t,JSON.stringify(n,null," ")+`
992
+ `)}var Ni=["base.json","node.json","next.json"],Ar="GenerateSaaS ";async function Pr(e){if(!e.demo)for(let t of Ni){let r=kr(e.projectDir,"tooling/typescript",t),n;try{n=await Tr(r,"utf-8")}catch{continue}let i=JSON.parse(n);typeof i.display!="string"||!i.display.startsWith(Ar)||(i.display=i.display.slice(Ar.length),await l(r,JSON.stringify(i,null," ")+`
987
993
  `))}}import{readFile as Li,rm as $i}from"fs/promises";import{existsSync as _r}from"fs";import{join as Rr}from"path";async function Or(e){if(e.frontend==="nuxt")return;let t=Rr(e.projectDir,"packages/i18n/package.json");if(!_r(t))throw new Error(`pruneI18nNuxt: expected ${t} to exist - did the i18n package move?`);let r=JSON.parse(await Li(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," ")+`
988
994
  `);let o=Rr(e.projectDir,"packages/i18n/nuxt");if(n&&!_r(o))throw new Error(`pruneI18nNuxt: packages/i18n declares a Nuxt export surface but ${o} is missing - did the i18n Nuxt module move?`);await $i(o,{recursive:!0,force:!0})}import{readFile as xr,rm as Ui}from"fs/promises";import{join as Ot}from"path";async function Dr(e){e.cacheProvider==="upstash"&&await Promise.all([ji(e.projectDir),Vi(e.projectDir),Ui(Ot(e.projectDir,"packages/runtime/tests/redis.test.ts"),{force:!0})])}async function ji(e){let t=Ot(e,"packages/runtime/tests/setup.ts"),r;try{r=await xr(t,"utf-8")}catch{return}let n=r.replace(/\tREDIS_URL:\s*"[^"]*",?\n/,` UPSTASH_REDIS_REST_URL: "https://test.upstash.io",
989
995
  UPSTASH_REDIS_REST_TOKEN: "test-token",
@@ -1029,7 +1035,7 @@ $1`),n!==r&&await l(t,n)}import{readdir as Mi,readFile as Fi,rm as Be}from"fs/pr
1029
1035
  STRIPE_SECRET_KEY: test
1030
1036
  STRIPE_WEBHOOK_SECRET: test`;case"polar":return`
1031
1037
  POLAR_ACCESS_TOKEN: test
1032
- POLAR_WEBHOOK_SECRET: test`;case"none":return"";default:{let m=e.paymentProvider;throw new Error(`buildCiYaml: unhandled payment provider "${String(m)}"`)}}})(),d=e.socialProviders.map(m=>W[m].envVars.map(S=>`
1038
+ POLAR_WEBHOOK_SECRET: test`;case"none":return"";default:{let m=e.paymentProvider;throw new Error(`buildCiYaml: unhandled payment provider "${String(m)}"`)}}})(),d=e.socialProviders.map(m=>q[m].envVars.map(S=>`
1033
1039
  ${S.name}: test`).join("")).join("");return`# Deployment is handled by the hosting platform (Vercel, Coolify, etc.)
1034
1040
  # which auto-deploys on push. CI runs in parallel as a quality gate.
1035
1041
  # For PR-based workflows, enable GitHub branch protection to require CI before merging.
@@ -1089,9 +1095,9 @@ ${i} env:
1089
1095
  run: echo "DATABASE_URL=${o}" > .env
1090
1096
 
1091
1097
  - run: pnpm test
1092
- `}import{readFile as Vr}from"fs/promises";import{existsSync as Hi}from"fs";import{join as xt}from"path";var Mr="@repo/database";function Yi(e){return e?"pnpm -F @repo/database reset && pnpm -F @repo/database push --force":"pnpm -F @repo/database migrate"}function Ji(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 qi(e,t,r){let n=await Vr(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(Mr)||(i.scripts={...i.scripts,[t]:`${r} && ${o}`},await l(e,JSON.stringify(i,null," ")+`
1093
- `))}async function Wi(e,t){let r=Hi(e)?JSON.parse(await Vr(e,"utf-8")):{$schema:"https://openapi.vercel.sh/vercel.json"},n=r.buildCommand?.trim()||"pnpm build";n.includes(Mr)||(r.buildCommand=`${t} && ${n}`,await l(e,JSON.stringify(r,null," ")+`
1094
- `))}async function Fr(e){let t=Yi(e.demo===!0),r=Ji(e.architecture,e.frontend),n=xt(e.projectDir,"apps",r);switch(e.deploymentTarget){case"node":await qi(xt(n,"package.json"),"start",t);return;case"vercel":await Wi(xt(n,"vercel.json"),t);return;default:{let i=e.deploymentTarget;throw new Error(`generateDeployScripts: unhandled deployment target "${String(i)}"`)}}}import{readFile as Xi}from"fs/promises";import{existsSync as Zi}from"fs";import{join as gt}from"path";var Qi=["stripe","polar"];async function Br(e){let t=gt(e.projectDir,"packages/payments/src"),r=e.paymentProvider,n=`// Active payment provider. To switch, change the path below to
1098
+ `}import{readFile as Vr}from"fs/promises";import{existsSync as Hi}from"fs";import{join as xt}from"path";var Mr="@repo/database";function Yi(e){return e?"pnpm -F @repo/database reset && pnpm -F @repo/database push --force":"pnpm -F @repo/database migrate"}function Ji(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 Wi(e,t,r){let n=await Vr(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(Mr)||(i.scripts={...i.scripts,[t]:`${r} && ${o}`},await l(e,JSON.stringify(i,null," ")+`
1099
+ `))}async function qi(e,t){let r=Hi(e)?JSON.parse(await Vr(e,"utf-8")):{$schema:"https://openapi.vercel.sh/vercel.json"},n=r.buildCommand?.trim()||"pnpm build";n.includes(Mr)||(r.buildCommand=`${t} && ${n}`,await l(e,JSON.stringify(r,null," ")+`
1100
+ `))}async function Fr(e){let t=Yi(e.demo===!0),r=Ji(e.architecture,e.frontend),n=xt(e.projectDir,"apps",r);switch(e.deploymentTarget){case"node":await Wi(xt(n,"package.json"),"start",t);return;case"vercel":await qi(xt(n,"vercel.json"),t);return;default:{let i=e.deploymentTarget;throw new Error(`generateDeployScripts: unhandled deployment target "${String(i)}"`)}}}import{readFile as Xi}from"fs/promises";import{existsSync as Zi}from"fs";import{join as gt}from"path";var Qi=["stripe","polar"];async function Br(e){let t=gt(e.projectDir,"packages/payments/src"),r=e.paymentProvider,n=`// Active payment provider. To switch, change the path below to
1095
1101
  // "./polar/index" or "./none/index" (other folders are kept in place).
1096
1102
  export { ops } from "./${r}/index";
1097
1103
  `;await l(gt(t,"providers/index.ts"),n),await l(gt(t,"index.ts"),await eo(t,r))}async function eo(e,t){let r=gt(e,"index.ts");if(!Zi(r))throw new Error(`generatePaymentBarrel: expected ${r} to exist - did packages/payments move?`);let n=await Xi(r,"utf-8"),i=Qi.filter(s=>s!==t);return n.split(`
@@ -1099,31 +1105,31 @@ export { ops } from "./${r}/index";
1099
1105
  `)}import{readdir as to,readFile as ro}from"fs/promises";import{join as Gr}from"path";async function Kr(e){if(e.demo)return;let t=e.appName.trim()||e.projectName,r=JSON.stringify(t).slice(1,-1),n=Gr(e.projectDir,"packages/i18n/translations"),i;try{i=await to(n)}catch{return}for(let o of i){let s=Gr(n,o,"web.json"),a;try{a=await ro(s,"utf-8")}catch{continue}let d=a.replaceAll("GenerateSaaS",r);d!==a&&await l(s,d)}}import{readFile as no}from"fs/promises";import{join as zr}from"path";var io=[".nuxt/",".nuxt",".nitro/",".nitro",".output/",".output","_locales/"],oo=[".next/",".next",".svelte-kit/",".svelte-kit",".wrangler/",".wrangler",".dev.vars"];async function Yr(e){let r=e.frontend==="nuxt"?oo:io;await Hr(zr(e.projectDir,".gitignore"),r),await Hr(zr(e.projectDir,".dockerignore"),r)}async function Hr(e,t){let r;try{r=await no(e,"utf-8")}catch{return}let n=new Set(t),i=r.split(`
1100
1106
  `).filter(o=>!n.has(o.trim()));i.length!==r.split(`
1101
1107
  `).length&&await l(e,i.join(`
1102
- `))}async function ht(e){let t=e.projectDir;e.demo||await or(t),await sr(t,e.aiTools),await ar(t,e.frontend),await lr(e),await pr(e),await dr(e),e.demo||await ur(e),await gr(e);let r=await hr(e);return await yr(e),await vr(e),await Sr(e),await Er(e),await wr(e),await br(e),await kr(e),await Pr(e),await Or(e),await Dr(e),await Cr(e),await jr(e),await Nr(e),await Lr(e),await $r(e),await Ur(e),await Fr(e),await Br(e),await Kr(e),await Yr(e),{dockerComposeGenerated:r}}import{basename as qr,join as Wr,relative as ao}from"path";import{createHash as Jr}from"crypto";import{readFile as so}from"fs/promises";async function yt(e){let t=await so(e);return Jr("sha256").update(t).digest("hex")}function Dt(e){return Jr("sha256").update(e).digest("hex")}var co=new Set(["data",U]);function lo(e){let t=e.split("/");for(let r of t)if(Tt.has(r)||co.has(r)||r.startsWith(".env")&&!r.includes("example"))return!0;return!1}function Xr(e,t){return{projectName:e.projectName??qr(t),appName:e.appName??e.projectName??qr(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 Zr(e,t){let n=(await fe(e.projectDir,e.projectDir,lo)).sort(),i=await Promise.all(n.map(async a=>[ao(e.projectDir,a),await yt(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(Wr(e.projectDir,Q),JSON.stringify(s,null," ")+`
1103
- `),await l(Wr(e.projectDir,Jt),JSON.stringify(o,null," ")+`
1104
- `)}import{relative as po}from"path";async function Ge(e){let r=(await fe(e,e,Ae)).sort(),n=await Promise.all(r.map(async i=>[po(e,i),await yt(i)]));return Object.fromEntries(n)}import{copyFile as uo,mkdir as mo,rm as fo}from"fs/promises";import{dirname as go,join as Qr,relative as ho}from"path";import{existsSync as yo}from"fs";async function Ct(e,t){yo(t)&&await fo(t,{recursive:!0,force:!0});let r=await fe(e,e,Ae);for(let n of r){let i=ho(e,n),o=Qr(t,i);await mo(go(o),{recursive:!0}),await uo(n,o)}}async function en(e,t){await Ct(e,Qr(t,ct))}import{existsSync as vo}from"fs";import{readFile as tn,readdir as So}from"fs/promises";import{join as z,dirname as Eo,resolve as wo,sep as bo}from"path";import{fileURLToPath as Ao}from"url";var Ke={"claude-code":".claude/skills",cursor:".cursor/skills",codex:".agents/skills","gemini-cli":".gemini/skills",windsurf:".windsurf/skills"},Sl=Object.values(Ke),Nt="generatesaas-update",rn=Eo(Ao(import.meta.url));function To(){let e=z(rn,"skill","content");return vo(e)?e:z(rn,"content")}function Lt(e){return!e||e.length===0?[]:e.map(t=>Ke[t])}async function $t(e,t,r,n){let i=Lt(n);for(let o of i){let s=z(e,o,Nt),a=z(s,"scripts"),d=z(s,"references");await ut(a),await ut(d),await l(z(s,"SKILL.md"),t.replaceAll("__SKILL_ROOT__",o)),await l(z(d,".gitkeep"),"");for(let[h,g]of Object.entries(r)){let m=wo(a,h);m.startsWith(a+bo)&&await l(m,g)}}}async function nn(e,t){let r=To(),n=await tn(z(r,"SKILL.md"),"utf-8"),i=z(r,"scripts"),o=await So(i),s={};for(let a of o)a!==".gitkeep"&&(s[a]=await tn(z(i,a),"utf-8"));await $t(e,n,s,t)}import{execFile as Io,execFileSync as ko}from"child_process";import{access as on,readFile as Po}from"fs/promises";import{join as Ut}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)=>{Io(e,t,{cwd:r,timeout:n},(s,a,d)=>{if(s){let h=String(a||"").trim(),m=[String(d||"").trim(),h].filter(Boolean).join(`
1108
+ `))}async function ht(e){let t=e.projectDir;e.demo||await or(t),await sr(t,e.aiTools),await ar(t,e.frontend),await lr(e),await pr(e),await dr(e),e.demo||await ur(e),await gr(e);let r=await hr(e);return await yr(e),await vr(e),await Sr(e),await Er(e),await wr(e),await br(e),await Ir(e),await Pr(e),await Or(e),await Dr(e),await Cr(e),await jr(e),await Nr(e),await Lr(e),await $r(e),await Ur(e),await Fr(e),await Br(e),await Kr(e),await Yr(e),{dockerComposeGenerated:r}}import{basename as Wr,join as qr,relative as ao}from"path";import{createHash as Jr}from"crypto";import{readFile as so}from"fs/promises";async function yt(e){let t=await so(e);return Jr("sha256").update(t).digest("hex")}function Dt(e){return Jr("sha256").update(e).digest("hex")}var co=new Set(["data",U]);function lo(e){let t=e.split("/");for(let r of t)if(Tt.has(r)||co.has(r)||r.startsWith(".env")&&!r.includes("example"))return!0;return!1}function Xr(e,t){return{projectName:e.projectName??Wr(t),appName:e.appName??e.projectName??Wr(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 Zr(e,t){let n=(await fe(e.projectDir,e.projectDir,lo)).sort(),i=await Promise.all(n.map(async a=>[ao(e.projectDir,a),await yt(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(qr(e.projectDir,Q),JSON.stringify(s,null," ")+`
1109
+ `),await l(qr(e.projectDir,Jt),JSON.stringify(o,null," ")+`
1110
+ `)}import{relative as po}from"path";async function Ge(e){let r=(await fe(e,e,Ae)).sort(),n=await Promise.all(r.map(async i=>[po(e,i),await yt(i)]));return Object.fromEntries(n)}import{copyFile as uo,mkdir as mo,rm as fo}from"fs/promises";import{dirname as go,join as Qr,relative as ho}from"path";import{existsSync as yo}from"fs";async function Ct(e,t){yo(t)&&await fo(t,{recursive:!0,force:!0});let r=await fe(e,e,Ae);for(let n of r){let i=ho(e,n),o=Qr(t,i);await mo(go(o),{recursive:!0}),await uo(n,o)}}async function en(e,t){await Ct(e,Qr(t,ct))}import{existsSync as vo}from"fs";import{readFile as tn,readdir as So}from"fs/promises";import{join as z,dirname as Eo,resolve as wo,sep as bo}from"path";import{fileURLToPath as Ao}from"url";var Ke={"claude-code":".claude/skills",cursor:".cursor/skills",codex:".agents/skills","gemini-cli":".gemini/skills",windsurf:".windsurf/skills"},Sl=Object.values(Ke),Nt="generatesaas-update",rn=Eo(Ao(import.meta.url));function To(){let e=z(rn,"skill","content");return vo(e)?e:z(rn,"content")}function Lt(e){return!e||e.length===0?[]:e.map(t=>Ke[t])}async function $t(e,t,r,n){let i=Lt(n);for(let o of i){let s=z(e,o,Nt),a=z(s,"scripts"),d=z(s,"references");await ut(a),await ut(d),await l(z(s,"SKILL.md"),t.replaceAll("__SKILL_ROOT__",o)),await l(z(d,".gitkeep"),"");for(let[h,g]of Object.entries(r)){let m=wo(a,h);m.startsWith(a+bo)&&await l(m,g)}}}async function nn(e,t){let r=To(),n=await tn(z(r,"SKILL.md"),"utf-8"),i=z(r,"scripts"),o=await So(i),s={};for(let a of o)a!==".gitkeep"&&(s[a]=await tn(z(i,a),"utf-8"));await $t(e,n,s,t)}import{execFile as ko,execFileSync as Io}from"child_process";import{access as on,readFile as Po}from"fs/promises";import{join as Ut}from"path";import*as P from"@clack/prompts";function ye(e){try{let t=process.platform==="win32"?"where":"which";return Io(t,[e],{stdio:"ignore"}),!0}catch{return!1}}function he(e,t,r,n=3e5){return new Promise((i,o)=>{ko(e,t,{cwd:r,timeout:n},(s,a,d)=>{if(s){let h=String(a||"").trim(),m=[String(d||"").trim(),h].filter(Boolean).join(`
1105
1111
  `);o(new Error(m?`${s.message}
1106
1112
  ${m}`:s.message))}else i()})})}async function sn(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 an(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 cn(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 ln(e){try{return await on(Ut(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 pn(e){if(!ye("pnpm"))return!1;try{await on(Ut(e,".git"))}catch{return!1}try{let t=JSON.parse(await Po(Ut(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 Re from"@clack/prompts";import j from"picocolors";function dn(e,t){t.dockerComposeGenerated&&!t.dockerAvailable&&Re.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=>we[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"))),Re.note(r.join(`
1107
1113
  `),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")}`),Re.note(o.join(`
1108
1114
  `),j.yellow("Dev Tools"))}let n=[],i=_o(e);i.length>0&&n.push(`Set in production: ${j.dim(i.join(", "))}`),n.push("pnpm db:push # Run database migrations"),n.push(Ro(e)),Re.note(n.join(`
1109
- `),j.yellow("Deployment"))}function _o(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 Ro(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 un(e){let t={};if(e.name!==void 0){if(!st(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(!Ue.includes(e.frontend))throw new Error(`Invalid frontend "${e.frontend}". Valid values: ${Ue.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=jt(e.docker,tt,"docker service")),e.aiTools!==void 0&&(t.aiTools=jt(e.aiTools,rt,"AI tool")),e.socialProviders!==void 0&&(t.socialProviders=jt(e.socialProviders,it,"social provider")),e.currency!==void 0){if(!ce.includes(e.currency))throw new Error(`Invalid currency "${e.currency}". Valid values: ${ce.join(", ")}`);t.defaultCurrency=e.currency}if(e.deploy!==void 0){if(!le.includes(e.deploy))throw new Error(`Invalid deployment target "${e.deploy}". Valid values: ${le.join(", ")}`);t.deploymentTarget=e.deploy}if(e.database!==void 0){if(!pe.includes(e.database))throw new Error(`Invalid database provider "${e.database}". Valid values: ${pe.join(", ")}`);t.databaseProvider=e.database}if(e.cache!==void 0){if(!de.includes(e.cache))throw new Error(`Invalid cache provider "${e.cache}". Valid values: ${de.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 mn(e){let t=e.projectName??ne.projectName,r=e.projectDir??`./${t}`,n=e.appName??ot(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),h=e.dockerServices??(o?ne.dockerServices.filter(m=>m!=="postgres"&&m!=="redis"):ne.dockerServices),g={...ne,...e,projectName:t,appName:n,projectDir:r,deploymentTarget:i,databaseProvider:s,cacheProvider:a,emailProvider:d,dockerServices:h};g.paymentProvider==="none"&&(g.credits=!1);for(let m of Le){if(g.deploymentTarget!==m.target)continue;let S=g.databaseProvider===m.provider?"database":"cache";if(g.databaseProvider===m.provider||g.cacheProvider===m.provider)throw new Error(`Incompatible: --deploy ${m.target} + --${S} ${m.provider}. ${m.reason}`)}for(let m of $e)if(g.architecture===m.architecture&&g.deploymentTarget===m.target)throw new Error(`Incompatible: --architecture ${m.architecture} + --deploy ${m.target}. ${m.reason}`);return g}function jt(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 Lo from"picocolors";var $o="a10a6fb9d7cadde32e37dad52059d17b5d2b916b08c76d8fbcc99982e9a3d87f";function Uo(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 fn(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([...Ue])).addOption(new V("--architecture <type>","fullstack or separate").choices([...Ze])).addOption(new V("--payment <provider>","payment provider").choices([...Qe])).addOption(new V("--email <provider>","email provider").choices([...et])).option("--org","enable multi-tenancy (organizations)").option("--no-org","disable multi-tenancy").addOption(new V("--billing-scope <scope>","billing scope (requires --org)").choices([...nt])).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([...ce])).addOption(new V("--deploy <target>","deployment target").choices([...le])).addOption(new V("--database <provider>","database provider").choices([...pe])).addOption(new V("--cache <provider>","cache provider").choices([...de])).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 jo(t?{...r,apiKey:t}:r)})}async function jo(e){let t=performance.now();Kt("1.9.1");let r,n;try{r=un(e),n=Uo(e.templateVersion)}catch(y){E.cancel(k(y)),process.exit(1)}let i=E.spinner(),o;try{o=await be({apiKey:e.apiKey,prompt:!e.yes})}catch(y){E.cancel(k(y)),process.exit(1)}e.demo&&Dt(o)!==$o&&(E.cancel("--demo is restricted to first-party demo deployments."),process.exit(1));let s=Z(o),a=async()=>{let y=await re(s),R=y.latest,Y=n??R;if(n&&!y.versions.some(J=>J.version===Y))throw new Error(`Template version "${n}" is not available.`);return{latestVersion:R,selectedVersion:Y}};i.start("Verifying access...");let d,h;try{({latestVersion:d,selectedVersion:h}=await a()),i.stop("Access verified."),me(o)}catch(y){if(i.stop("Access verification failed."),y instanceof O&&y.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 Me(),s=Z(o),i.start("Verifying access...");try{({latestVersion:d,selectedVersion:h}=await a()),i.stop("Access verified."),me(o)}catch(R){i.stop("Access verification failed."),E.cancel(R instanceof O&&R.status===401?"Invalid API key.":k(R)),process.exit(1)}}else E.cancel(k(y)),process.exit(1)}E.log.success(`Latest version: ${d}`),h!==d&&E.log.success(`Using template version: ${h}`);let g;e.yes?g=mn(r):g=await Yt(r);let m;i.start("Activating license...");try{let y=crypto.randomUUID(),R=()=>({frontend:g.frontend,version:h,installId:y,projectName:g.projectName,options:Qt(g)}),Y;try{Y=await bt(s,R())}catch(J){let q=lt(J);if(!q?.lastAllowedVersion)throw J;i.stop("License activation failed."),e.yes&&(E.cancel(`${q.message} Re-run with --template-version ${q.lastAllowedVersion}.`),process.exit(1));let Ee=await E.confirm({message:`Your update window has ended. Continue with v${q.lastAllowedVersion} (the last version your license covers)?`});(E.isCancel(Ee)||!Ee)&&(E.cancel("Setup cancelled."),process.exit(0)),h=q.lastAllowedVersion,i.start(`Activating license for v${h}...`),Y=await bt(s,R())}m={token:Y.token,keyHash:Dt(o),installId:y},i.stop("License activated.")}catch(y){i.stop("License activation failed."),E.cancel(k(y)),process.exit(1)}let S=No(g.projectDir);if(Oo(S)&&xo(S).length>0)if(e.yes)E.log.info(`Directory ${S} is not empty. Merging (keeping existing files, overwriting conflicts).`);else{let R=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(R)||R==="cancel")&&(E.cancel("Setup cancelled."),process.exit(0)),R==="overwrite"&&Do(S,{recursive:!0,force:!0})}let T={...g,projectDir:S,version:h,...e.demo?{docs:!1}:{}};i.start("Downloading template...");try{await pt(s,h,S),i.stop("Template downloaded.")}catch(y){i.stop("Download failed."),E.cancel(k(y)),process.exit(1)}let H;i.start("Generating project files...");try{if({dockerComposeGenerated:H}=await ht(T),!e.demo){let y=await Ge(S);await l(Co(S,at),JSON.stringify(y,null," ")+`
1110
- `),await en(S,S)}await nn(S,T.aiTools),await Zr(T,m),i.stop("Project files generated.")}catch(y){i.stop("Generation failed."),E.cancel(k(y)),process.exit(1)}await sn(S);let D=await an(S);D&&T.demo!==!0&&e.dbMigration!==!1&&await cn(S),await ln(S),D&&await pn(S);let ie=ye("docker"),I=wt(T).map(y=>y.key).filter(y=>!T.credentials?.[y]);dn(T,{pnpmInstalled:D,dockerComposeGenerated:H,dockerAvailable:ie,skippedCredentials:I}),zt(),E.log.info(Lo.dim(`Done in ${((performance.now()-t)/1e3).toFixed(1)}s`))}import{existsSync as gn}from"fs";import{readFile as Go}from"fs/promises";import{join as ze,resolve as Ko}from"path";import*as _ from"@clack/prompts";import Oe from"picocolors";import{mkdtemp as Vo,rm as Mo}from"fs/promises";import{tmpdir as Fo}from"os";import{join as Bo}from"path";async function Vt(e,t,r,n){let i=await Vo(Bo(Fo(),"generatesaas-stage-"));try{await pt(e,t,i),await ht({...r,projectDir:i}),await Ct(i,n)}finally{await Mo(i,{recursive:!0,force:!0})}}function hn(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=Ko(t.cwd??process.cwd()),n=ze(r,Q),i;try{i=JSON.parse(await Go(n,"utf-8"))}catch{_.cancel(".generatesaas/manifest.json not found. Run this from a GenerateSaaS project."),process.exit(1)}let o;try{o=await be()}catch(d){_.cancel(k(d)),process.exit(1)}let s=Z(o),a=_.spinner();try{a.start("Verifying access...");let d;try{d=await re(s)}catch(I){throw I instanceof O&&I.status===401?new Error("Your saved API key was rejected. Run `generatesaas auth` to update it, or set GENERATESAAS_API_KEY."):I}a.stop("Access verified."),me(o),a.start("Fetching latest skill files...");let h=await Zt(s,d.latest);await $t(r,h.skillMd,h.scripts,i.aiTools);let g=Lt(i.aiTools);if(a.stop("Skills updated."),_.log.success(`Skill files installed to ${Oe.cyan(g.length.toString())} locations.`),i.version===d.latest){_.log.info(`Already on the latest version (${i.version}).`);return}if(i.licenseToken)try{let I=await er(s,{currentToken:i.licenseToken,newVersion:d.latest});i.licenseToken=I.token,await l(n,JSON.stringify(i,null," ")+`
1111
- `),_.log.success("License refreshed.")}catch(I){let y=lt(I);y&&(_.cancel(y.message),process.exit(1)),_.log.warn("License refresh skipped.")}let m=Xr(i,r),S=ze(r,qt);a.start(`Staging v${d.latest} (shaped for your config)...`),await Vt(s,d.latest,m,S),a.stop("Template staged.");let T=await Xt(s,d.latest);T&&_.note(T,`Changelog v${d.latest}`);let H=ze(r,at),D=ze(r,ct),ie=!gn(D),oe=!gn(H);if(ie){if(a.start("Building baseline template (one-time migration)..."),await Vt(s,i.version,m,D),oe){let I=await Ge(D);await l(H,JSON.stringify(I,null," ")+`
1112
- `)}a.stop("Baseline template stored.")}else if(oe){a.start("Computing baseline template hashes...");let I=await Ge(D);await l(H,JSON.stringify(I,null," ")+`
1113
- `),a.stop("Baseline hashes computed.")}if(await l(ze(r,Wt),JSON.stringify({currentVersion:i.version,targetVersion:d.latest,changelog:T,stagedAt:new Date().toISOString()},null," ")+`
1114
- `),_.log.info(`Update staged: ${Oe.cyan(i.version)} \u2192 ${Oe.cyan(d.latest)}`),i.aiTools&&i.aiTools.length>0){let I=i.aiTools[0],y=Ne[I].label;_.log.info(`Open your project in ${Oe.cyan(y)} and ask: ${Oe.cyan("'update my GenerateSaaS project'")}`)}else _.log.info(`Ask your AI coding assistant to ${Oe.cyan("'update my GenerateSaaS project'")}.`)}catch(d){a.stop("Failed."),_.cancel(`Update failed: ${k(d)}`),process.exit(1)}})}import*as $ from"@clack/prompts";import M from"picocolors";import{readFile as zo}from"fs/promises";import{join as Ho,resolve as Yo}from"path";function yn(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=Yo(t.cwd??process.cwd()),n=Ho(r,Q),i;try{i=JSON.parse(await zo(n,"utf-8"))}catch{$.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(`
1115
- `);$.note(o,M.bold("Project Status"));let s=$.spinner();s.start("Checking for updates...");try{let a=await be(),d=Z(a),g=(await re(d)).latest;i.version===g?(s.stop("Up to date."),$.log.success(`Already on the latest version (${M.green(g)})`)):(s.stop("Update available."),$.log.warning(`Update available: ${M.yellow(i.version)} \u2192 ${M.green(g)}`),$.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 O&&a.status===401?$.log.warning("Invalid API key. Run `generatesaas auth` to update it, or set GENERATESAAS_API_KEY."):$.log.warning(`Could not check for updates: ${k(a)}`)}})}import{readFile as Jo}from"fs/promises";import*as A from"@clack/prompts";import b from"picocolors";function qo(){return process.env.GENERATESAAS_API_KEY??Ve()}function Wo(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 Xo(e){return{verdict:e.verdict??"unknown",ejectedAt:e.ejectedAt??null}}function Zo(e){switch(e.verdict){case"licensed":return A.log.success(`${b.green("LICENSED")} - resolves to an account with an active${e.plan?` ${e.plan}`:""} license.`),!0;case"ejected":return A.log.success(`${b.green("EJECTED")} - a licensed buyer opted this install out of telemetry${e.ejectedAt?` on ${e.ejectedAt.slice(0,10)}`:""}. The site is legitimate.`),!0;case"revoked":return A.log.error(`${b.red("REVOKED")} - the owning account no longer holds a plan (refund or chargeback). This deployment is no longer licensed.`),!1;case"token_domain_mismatch":return A.log.error(`${b.red("LEAKED TOKEN")} - this license belongs to a different deployment${e.mismatchDomain?` (${b.cyan(e.mismatchDomain)})`:""}, not this site. The token was copied from a licensed project.`),!1;case"no_license_history":return A.log.error(`${b.red("NO LICENSE HISTORY")} - no license has ever been associated with this site. If it runs GenerateSaaS, treat it as unlicensed.`),!1;default:return A.log.warn(`${b.yellow("UNKNOWN")} - could not cross-reference the records right now. Try again shortly.`),!1}}async function vn(e,t){let r=process.env.GENERATESAAS_API_URL??je,n=qo();e.start("Cross-referencing license records...");try{let i=n?Wo(await tr(r,n,{lkh:t.lkh,nid:t.nid,domain:t.domain})):Xo(await At(r,{token:t.token,domain:t.domain}));return e.stop(`${b.green("Checked")} - records cross-referenced`),Zo(i)}catch(i){return e.stop(`${b.yellow("Skipped")} - ${k(i)}`),null}}function Qo(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 Mt(e){return typeof e!="number"?"unknown":new Date(e*1e3).toISOString().split("T")[0]}function Sn(e){A.note([`License ID: ${b.cyan(String(e.lid??"unknown"))}`,`Version: ${b.cyan(String(e.ver??"unknown"))}`,`Init version: ${String(e.iver??"unknown")}`,`Frontend: ${String(e.fe??"unknown")}`,`Created: ${Mt(e.pat)}`,`Last updated: ${Mt(e.uat)}`,`Expires: ${Mt(e.exp)}`,`Install ID: ${String(e.nid??"unknown")}`].join(`
1116
- `),b.yellow("License Details"))}function es(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 En(e){let t=A.spinner(),r=null,n="no candidates";for(let s of es(e)){t.start(`Checking ${s}...`);try{let a=await fetch(s);if(!a.ok){n=`${s} returned ${a.status}`,t.stop(`${b.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(`${b.yellow("Not here")} - ${n}`);continue}r=d,t.stop(`${b.green("Found")} - license endpoint responded`);break}catch(a){n=`${s}: ${k(a)}`,t.stop(`${b.yellow("Unreachable")} - ${n}`)}}if(r===null){A.log.warn(`No license endpoint found (last: ${n}). The site may be ejected, not a GenerateSaaS app, or serving its API elsewhere.`);let s=wn(e);return s?await vn(t,{domain:s})??!1:!1}let i;try{i=Qo(r)}catch{return A.log.error("Could not decode JWT payload."),!1}t.start("Verifying signature...");try{let s=process.env.GENERATESAAS_API_URL??je,a=await At(s,{token:r});if(a.valid)t.stop(`${b.green("Valid")} - signature verified`);else return t.stop(`${b.red("Invalid")} - ${a.reason}`),!1}catch{return t.stop(`${b.yellow("Skipped")} - could not reach verification service`),A.log.warn("Signature not verified. Displaying unverified claims:"),Sn(i),!1}return Sn(i),await vn(t,{token:r,lkh:typeof i.lkh=="string"?i.lkh:void 0,nid:typeof i.nid=="string"?i.nid:void 0,domain:wn(e)})??!0}function wn(e){try{return new URL(/^https?:\/\//i.test(e)?e:`https://${e}`).hostname}catch{return}}function bn(e){e.command("verify").description("Verify a GenerateSaaS license on a deployed site").argument("[url]","URL of the site to verify (e.g. https://example.com or https://example.com/api)").option("--file <path>","file with URLs to check, one per line").action(async(t,r)=>{if(!t&&!r.file&&(A.cancel("Provide a URL or --file <path>."),process.exit(1)),r.file){let i=(await Jo(r.file,"utf-8")).split(`
1117
- `).map(s=>s.trim()).filter(s=>s&&!s.startsWith("#"));i.length===0&&(A.cancel("No URLs found in file."),process.exit(1));let o=0;for(let s of i)await En(s)&&o++,A.log.info("");A.log.success(`${o}/${i.length} sites verified.`)}else await En(t)||process.exit(1)})}import{existsSync as ts,rmSync as rs}from"fs";import*as F from"@clack/prompts";function An(e){e.command("auth").description("Set or update your GenerateSaaS API key").option("--clear","remove saved API key").action(async t=>{if(t.clear){ts(X)?(rs(X),F.log.success("API key removed.")):F.log.info("No API key configured.");return}let r=Ve();r?F.log.info(`Current API key: ****${r.slice(-4)}`):F.log.info("No API key configured.");let n=await Me(),i=Z(n),o=F.spinner();o.start("Verifying API key...");try{await re(i),o.stop("API key verified."),me(n),F.log.success("API key saved.")}catch(s){o.stop("Verification failed."),s instanceof O&&s.status===401?F.cancel("Invalid API key."):F.cancel(k(s)),process.exit(1)}})}import{existsSync as vt,rmSync as ns,readFileSync as Bt,writeFileSync as Tn}from"fs";import{join as ve}from"path";import*as x from"@clack/prompts";var is=["packages/api/src/functions/maintenance/license-heartbeat.ts","packages/api/src/lib/manifest.ts","packages/api/src/routes/internal/license.ts"],os=[{file:"packages/api/src/routes/inngest.ts",removals:[`import { licenseHeartbeatFunction } from "../functions/maintenance/license-heartbeat";
1115
+ `),j.yellow("Deployment"))}function _o(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 Ro(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 un(e){let t={};if(e.name!==void 0){if(!st(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(!Ue.includes(e.frontend))throw new Error(`Invalid frontend "${e.frontend}". Valid values: ${Ue.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=jt(e.docker,tt,"docker service")),e.aiTools!==void 0&&(t.aiTools=jt(e.aiTools,rt,"AI tool")),e.socialProviders!==void 0&&(t.socialProviders=jt(e.socialProviders,it,"social provider")),e.currency!==void 0){if(!ce.includes(e.currency))throw new Error(`Invalid currency "${e.currency}". Valid values: ${ce.join(", ")}`);t.defaultCurrency=e.currency}if(e.deploy!==void 0){if(!le.includes(e.deploy))throw new Error(`Invalid deployment target "${e.deploy}". Valid values: ${le.join(", ")}`);t.deploymentTarget=e.deploy}if(e.database!==void 0){if(!pe.includes(e.database))throw new Error(`Invalid database provider "${e.database}". Valid values: ${pe.join(", ")}`);t.databaseProvider=e.database}if(e.cache!==void 0){if(!de.includes(e.cache))throw new Error(`Invalid cache provider "${e.cache}". Valid values: ${de.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 mn(e){let t=e.projectName??ne.projectName,r=e.projectDir??`./${t}`,n=e.appName??ot(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),h=e.dockerServices??(o?ne.dockerServices.filter(m=>m!=="postgres"&&m!=="redis"):ne.dockerServices),g={...ne,...e,projectName:t,appName:n,projectDir:r,deploymentTarget:i,databaseProvider:s,cacheProvider:a,emailProvider:d,dockerServices:h};g.paymentProvider==="none"&&(g.credits=!1);for(let m of Le){if(g.deploymentTarget!==m.target)continue;let S=g.databaseProvider===m.provider?"database":"cache";if(g.databaseProvider===m.provider||g.cacheProvider===m.provider)throw new Error(`Incompatible: --deploy ${m.target} + --${S} ${m.provider}. ${m.reason}`)}for(let m of $e)if(g.architecture===m.architecture&&g.deploymentTarget===m.target)throw new Error(`Incompatible: --architecture ${m.architecture} + --deploy ${m.target}. ${m.reason}`);return g}function jt(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 Lo from"picocolors";var $o="a10a6fb9d7cadde32e37dad52059d17b5d2b916b08c76d8fbcc99982e9a3d87f";function Uo(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 fn(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([...Ue])).addOption(new V("--architecture <type>","fullstack or separate").choices([...Ze])).addOption(new V("--payment <provider>","payment provider").choices([...Qe])).addOption(new V("--email <provider>","email provider").choices([...et])).option("--org","enable multi-tenancy (organizations)").option("--no-org","disable multi-tenancy").addOption(new V("--billing-scope <scope>","billing scope (requires --org)").choices([...nt])).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([...ce])).addOption(new V("--deploy <target>","deployment target").choices([...le])).addOption(new V("--database <provider>","database provider").choices([...pe])).addOption(new V("--cache <provider>","cache provider").choices([...de])).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 jo(t?{...r,apiKey:t}:r)})}async function jo(e){let t=performance.now();Kt("1.9.2");let r,n;try{r=un(e),n=Uo(e.templateVersion)}catch(y){E.cancel(I(y)),process.exit(1)}let i=E.spinner(),o;try{o=await be({apiKey:e.apiKey,prompt:!e.yes})}catch(y){E.cancel(I(y)),process.exit(1)}e.demo&&Dt(o)!==$o&&(E.cancel("--demo is restricted to first-party demo deployments."),process.exit(1));let s=Z(o),a=async()=>{let y=await re(s),R=y.latest,Y=n??R;if(n&&!y.versions.some(J=>J.version===Y))throw new Error(`Template version "${n}" is not available.`);return{latestVersion:R,selectedVersion:Y}};i.start("Verifying access...");let d,h;try{({latestVersion:d,selectedVersion:h}=await a()),i.stop("Access verified."),me(o)}catch(y){if(i.stop("Access verification failed."),y instanceof O&&y.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 Me(),s=Z(o),i.start("Verifying access...");try{({latestVersion:d,selectedVersion:h}=await a()),i.stop("Access verified."),me(o)}catch(R){i.stop("Access verification failed."),E.cancel(R instanceof O&&R.status===401?"Invalid API key.":I(R)),process.exit(1)}}else E.cancel(I(y)),process.exit(1)}E.log.success(`Latest version: ${d}`),h!==d&&E.log.success(`Using template version: ${h}`);let g;e.yes?g=mn(r):g=await Yt(r);let m;i.start("Activating license...");try{let y=crypto.randomUUID(),R=()=>({frontend:g.frontend,version:h,installId:y,projectName:g.projectName,options:Qt(g)}),Y;try{Y=await bt(s,R())}catch(J){let W=lt(J);if(!W?.lastAllowedVersion)throw J;i.stop("License activation failed."),e.yes&&(E.cancel(`${W.message} Re-run with --template-version ${W.lastAllowedVersion}.`),process.exit(1));let Ee=await E.confirm({message:`Your update window has ended. Continue with v${W.lastAllowedVersion} (the last version your license covers)?`});(E.isCancel(Ee)||!Ee)&&(E.cancel("Setup cancelled."),process.exit(0)),h=W.lastAllowedVersion,i.start(`Activating license for v${h}...`),Y=await bt(s,R())}m={token:Y.token,keyHash:Dt(o),installId:y},i.stop("License activated.")}catch(y){i.stop("License activation failed."),E.cancel(I(y)),process.exit(1)}let S=No(g.projectDir);if(Oo(S)&&xo(S).length>0)if(e.yes)E.log.info(`Directory ${S} is not empty. Merging (keeping existing files, overwriting conflicts).`);else{let R=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(R)||R==="cancel")&&(E.cancel("Setup cancelled."),process.exit(0)),R==="overwrite"&&Do(S,{recursive:!0,force:!0})}let T={...g,projectDir:S,version:h,...e.demo?{docs:!1}:{}};i.start("Downloading template...");try{await pt(s,h,S),i.stop("Template downloaded.")}catch(y){i.stop("Download failed."),E.cancel(I(y)),process.exit(1)}let H;i.start("Generating project files...");try{if({dockerComposeGenerated:H}=await ht(T),!e.demo){let y=await Ge(S);await l(Co(S,at),JSON.stringify(y,null," ")+`
1116
+ `),await en(S,S)}await nn(S,T.aiTools),await Zr(T,m),i.stop("Project files generated.")}catch(y){i.stop("Generation failed."),E.cancel(I(y)),process.exit(1)}await sn(S);let D=await an(S);D&&T.demo!==!0&&e.dbMigration!==!1&&await cn(S),await ln(S),D&&await pn(S);let ie=ye("docker"),k=wt(T).map(y=>y.key).filter(y=>!T.credentials?.[y]);dn(T,{pnpmInstalled:D,dockerComposeGenerated:H,dockerAvailable:ie,skippedCredentials:k}),zt(),E.log.info(Lo.dim(`Done in ${((performance.now()-t)/1e3).toFixed(1)}s`))}import{existsSync as gn}from"fs";import{readFile as Go}from"fs/promises";import{join as ze,resolve as Ko}from"path";import*as _ from"@clack/prompts";import Oe from"picocolors";import{mkdtemp as Vo,rm as Mo}from"fs/promises";import{tmpdir as Fo}from"os";import{join as Bo}from"path";async function Vt(e,t,r,n){let i=await Vo(Bo(Fo(),"generatesaas-stage-"));try{await pt(e,t,i),await ht({...r,projectDir:i}),await Ct(i,n)}finally{await Mo(i,{recursive:!0,force:!0})}}function hn(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=Ko(t.cwd??process.cwd()),n=ze(r,Q),i;try{i=JSON.parse(await Go(n,"utf-8"))}catch{_.cancel(".generatesaas/manifest.json not found. Run this from a GenerateSaaS project."),process.exit(1)}let o;try{o=await be()}catch(d){_.cancel(I(d)),process.exit(1)}let s=Z(o),a=_.spinner();try{a.start("Verifying access...");let d;try{d=await re(s)}catch(k){throw k instanceof O&&k.status===401?new Error("Your saved API key was rejected. Run `generatesaas auth` to update it, or set GENERATESAAS_API_KEY."):k}a.stop("Access verified."),me(o),a.start("Fetching latest skill files...");let h=await Zt(s,d.latest);await $t(r,h.skillMd,h.scripts,i.aiTools);let g=Lt(i.aiTools);if(a.stop("Skills updated."),_.log.success(`Skill files installed to ${Oe.cyan(g.length.toString())} locations.`),i.version===d.latest){_.log.info(`Already on the latest version (${i.version}).`);return}if(i.licenseToken)try{let k=await er(s,{currentToken:i.licenseToken,newVersion:d.latest});i.licenseToken=k.token,await l(n,JSON.stringify(i,null," ")+`
1117
+ `),_.log.success("License refreshed.")}catch(k){let y=lt(k);y&&(_.cancel(y.message),process.exit(1)),_.log.warn("License refresh skipped.")}let m=Xr(i,r),S=ze(r,Wt);a.start(`Staging v${d.latest} (shaped for your config)...`),await Vt(s,d.latest,m,S),a.stop("Template staged.");let T=await Xt(s,d.latest);T&&_.note(T,`Changelog v${d.latest}`);let H=ze(r,at),D=ze(r,ct),ie=!gn(D),oe=!gn(H);if(ie){if(a.start("Building baseline template (one-time migration)..."),await Vt(s,i.version,m,D),oe){let k=await Ge(D);await l(H,JSON.stringify(k,null," ")+`
1118
+ `)}a.stop("Baseline template stored.")}else if(oe){a.start("Computing baseline template hashes...");let k=await Ge(D);await l(H,JSON.stringify(k,null," ")+`
1119
+ `),a.stop("Baseline hashes computed.")}if(await l(ze(r,qt),JSON.stringify({currentVersion:i.version,targetVersion:d.latest,changelog:T,stagedAt:new Date().toISOString()},null," ")+`
1120
+ `),_.log.info(`Update staged: ${Oe.cyan(i.version)} \u2192 ${Oe.cyan(d.latest)}`),i.aiTools&&i.aiTools.length>0){let k=i.aiTools[0],y=Ne[k].label;_.log.info(`Open your project in ${Oe.cyan(y)} and ask: ${Oe.cyan("'update my GenerateSaaS project'")}`)}else _.log.info(`Ask your AI coding assistant to ${Oe.cyan("'update my GenerateSaaS project'")}.`)}catch(d){a.stop("Failed."),_.cancel(`Update failed: ${I(d)}`),process.exit(1)}})}import*as $ from"@clack/prompts";import M from"picocolors";import{readFile as zo}from"fs/promises";import{join as Ho,resolve as Yo}from"path";function yn(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=Yo(t.cwd??process.cwd()),n=Ho(r,Q),i;try{i=JSON.parse(await zo(n,"utf-8"))}catch{$.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(`
1121
+ `);$.note(o,M.bold("Project Status"));let s=$.spinner();s.start("Checking for updates...");try{let a=await be(),d=Z(a),g=(await re(d)).latest;i.version===g?(s.stop("Up to date."),$.log.success(`Already on the latest version (${M.green(g)})`)):(s.stop("Update available."),$.log.warning(`Update available: ${M.yellow(i.version)} \u2192 ${M.green(g)}`),$.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 O&&a.status===401?$.log.warning("Invalid API key. Run `generatesaas auth` to update it, or set GENERATESAAS_API_KEY."):$.log.warning(`Could not check for updates: ${I(a)}`)}})}import{readFile as Jo}from"fs/promises";import*as A from"@clack/prompts";import b from"picocolors";function Wo(){return process.env.GENERATESAAS_API_KEY??Ve()}function qo(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 Xo(e){return{verdict:e.verdict??"unknown",ejectedAt:e.ejectedAt??null}}function Zo(e){switch(e.verdict){case"licensed":return A.log.success(`${b.green("LICENSED")} - resolves to an account with an active${e.plan?` ${e.plan}`:""} license.`),!0;case"ejected":return A.log.success(`${b.green("EJECTED")} - a licensed buyer opted this install out of telemetry${e.ejectedAt?` on ${e.ejectedAt.slice(0,10)}`:""}. The site is legitimate.`),!0;case"revoked":return A.log.error(`${b.red("REVOKED")} - the owning account no longer holds a plan (refund or chargeback). This deployment is no longer licensed.`),!1;case"token_domain_mismatch":return A.log.error(`${b.red("LEAKED TOKEN")} - this license belongs to a different deployment${e.mismatchDomain?` (${b.cyan(e.mismatchDomain)})`:""}, not this site. The token was copied from a licensed project.`),!1;case"no_license_history":return A.log.error(`${b.red("NO LICENSE HISTORY")} - no license has ever been associated with this site. If it runs GenerateSaaS, treat it as unlicensed.`),!1;default:return A.log.warn(`${b.yellow("UNKNOWN")} - could not cross-reference the records right now. Try again shortly.`),!1}}async function vn(e,t){let r=process.env.GENERATESAAS_API_URL??je,n=Wo();e.start("Cross-referencing license records...");try{let i=n?qo(await tr(r,n,{lkh:t.lkh,nid:t.nid,domain:t.domain})):Xo(await At(r,{token:t.token,domain:t.domain}));return e.stop(`${b.green("Checked")} - records cross-referenced`),Zo(i)}catch(i){return e.stop(`${b.yellow("Skipped")} - ${I(i)}`),null}}function Qo(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 Mt(e){return typeof e!="number"?"unknown":new Date(e*1e3).toISOString().split("T")[0]}function Sn(e){A.note([`License ID: ${b.cyan(String(e.lid??"unknown"))}`,`Version: ${b.cyan(String(e.ver??"unknown"))}`,`Init version: ${String(e.iver??"unknown")}`,`Frontend: ${String(e.fe??"unknown")}`,`Created: ${Mt(e.pat)}`,`Last updated: ${Mt(e.uat)}`,`Expires: ${Mt(e.exp)}`,`Install ID: ${String(e.nid??"unknown")}`].join(`
1122
+ `),b.yellow("License Details"))}function es(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 En(e){let t=A.spinner(),r=null,n="no candidates";for(let s of es(e)){t.start(`Checking ${s}...`);try{let a=await fetch(s);if(!a.ok){n=`${s} returned ${a.status}`,t.stop(`${b.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(`${b.yellow("Not here")} - ${n}`);continue}r=d,t.stop(`${b.green("Found")} - license endpoint responded`);break}catch(a){n=`${s}: ${I(a)}`,t.stop(`${b.yellow("Unreachable")} - ${n}`)}}if(r===null){A.log.warn(`No license endpoint found (last: ${n}). The site may be ejected, not a GenerateSaaS app, or serving its API elsewhere.`);let s=wn(e);return s?await vn(t,{domain:s})??!1:!1}let i;try{i=Qo(r)}catch{return A.log.error("Could not decode JWT payload."),!1}t.start("Verifying signature...");try{let s=process.env.GENERATESAAS_API_URL??je,a=await At(s,{token:r});if(a.valid)t.stop(`${b.green("Valid")} - signature verified`);else return t.stop(`${b.red("Invalid")} - ${a.reason}`),!1}catch{return t.stop(`${b.yellow("Skipped")} - could not reach verification service`),A.log.warn("Signature not verified. Displaying unverified claims:"),Sn(i),!1}return Sn(i),await vn(t,{token:r,lkh:typeof i.lkh=="string"?i.lkh:void 0,nid:typeof i.nid=="string"?i.nid:void 0,domain:wn(e)})??!0}function wn(e){try{return new URL(/^https?:\/\//i.test(e)?e:`https://${e}`).hostname}catch{return}}function bn(e){e.command("verify").description("Verify a GenerateSaaS license on a deployed site").argument("[url]","URL of the site to verify (e.g. https://example.com or https://example.com/api)").option("--file <path>","file with URLs to check, one per line").action(async(t,r)=>{if(!t&&!r.file&&(A.cancel("Provide a URL or --file <path>."),process.exit(1)),r.file){let i=(await Jo(r.file,"utf-8")).split(`
1123
+ `).map(s=>s.trim()).filter(s=>s&&!s.startsWith("#"));i.length===0&&(A.cancel("No URLs found in file."),process.exit(1));let o=0;for(let s of i)await En(s)&&o++,A.log.info("");A.log.success(`${o}/${i.length} sites verified.`)}else await En(t)||process.exit(1)})}import{existsSync as ts,rmSync as rs}from"fs";import*as F from"@clack/prompts";function An(e){e.command("auth").description("Set or update your GenerateSaaS API key").option("--clear","remove saved API key").action(async t=>{if(t.clear){ts(X)?(rs(X),F.log.success("API key removed.")):F.log.info("No API key configured.");return}let r=Ve();r?F.log.info(`Current API key: ****${r.slice(-4)}`):F.log.info("No API key configured.");let n=await Me(),i=Z(n),o=F.spinner();o.start("Verifying API key...");try{await re(i),o.stop("API key verified."),me(n),F.log.success("API key saved.")}catch(s){o.stop("Verification failed."),s instanceof O&&s.status===401?F.cancel("Invalid API key."):F.cancel(I(s)),process.exit(1)}})}import{existsSync as vt,rmSync as ns,readFileSync as Bt,writeFileSync as Tn}from"fs";import{join as ve}from"path";import*as x from"@clack/prompts";var is=["packages/api/src/functions/maintenance/license-heartbeat.ts","packages/api/src/lib/manifest.ts","packages/api/src/routes/internal/license.ts"],os=[{file:"packages/api/src/routes/inngest.ts",removals:[`import { licenseHeartbeatFunction } from "../functions/maintenance/license-heartbeat";
1118
1124
  `,` licenseHeartbeatFunction,
1119
1125
  `]},{file:"packages/api/src/routes/internal/index.ts",removals:[`import licenseRoutes from "./license";
1120
1126
  `,` .route("/license", licenseRoutes)
1121
1127
  `]}];function ss(e){return(e&&e.length>0?e.map(r=>Ke[r]):Object.values(Ke)).map(r=>ve(r,Nt))}function Ft(e){return vt(e)?(ns(e,{recursive:!0}),!0):!1}function as(e,t){if(!vt(e))return!1;let r=Bt(e,"utf-8"),n=r;for(let i of t)n=n.replace(i,"");return n===r?!1:(Tn(e,n,"utf-8"),!0)}function cs(e){let t=ve(e,".gitignore");if(!vt(t))return!1;let r=Bt(t,"utf-8"),n=r.split(`
1122
1128
  `).filter(i=>!i.includes(".generatesaas")).join(`
1123
- `);return n===r?!1:(Tn(t,n,"utf-8"),!0)}function In(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(Bt(r,"utf-8"))}catch{x.cancel("No GenerateSaaS project found in this directory."),process.exit(1)}let i=await x.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(x.isCancel(i)&&(x.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)}),x.log.info("Recorded the opt-out with generatesaas.com (final event - nothing is sent after this).")}catch{x.log.warn("Could not reach generatesaas.com to record the opt-out. Ejecting anyway.")}let o=[],s=[];for(let a of ss(n.aiTools))Ft(ve(t,a))&&o.push(a);for(let a of is)Ft(ve(t,a))&&o.push(a);Ft(ve(t,U))&&o.push(U+"/");for(let a of os){let d=ve(t,a.file);as(d,a.removals)?s.push(a.file):vt(d)&&x.log.warn(`Could not auto-modify ${a.file} - manually remove license/heartbeat references.`)}cs(t)&&s.push(".gitignore");for(let a of o)x.log.info(`Deleted ${a}`);for(let a of s)x.log.info(`Modified ${a}`);x.log.success("Ejected successfully. This project is now fully standalone.")})}var Se=new ls().name("generatesaas").description("CLI for scaffolding and managing GenerateSaaS projects").version("1.9.1").addHelpText("after",`
1129
+ `);return n===r?!1:(Tn(t,n,"utf-8"),!0)}function kn(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(Bt(r,"utf-8"))}catch{x.cancel("No GenerateSaaS project found in this directory."),process.exit(1)}let i=await x.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(x.isCancel(i)&&(x.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)}),x.log.info("Recorded the opt-out with generatesaas.com (final event - nothing is sent after this).")}catch{x.log.warn("Could not reach generatesaas.com to record the opt-out. Ejecting anyway.")}let o=[],s=[];for(let a of ss(n.aiTools))Ft(ve(t,a))&&o.push(a);for(let a of is)Ft(ve(t,a))&&o.push(a);Ft(ve(t,U))&&o.push(U+"/");for(let a of os){let d=ve(t,a.file);as(d,a.removals)?s.push(a.file):vt(d)&&x.log.warn(`Could not auto-modify ${a.file} - manually remove license/heartbeat references.`)}cs(t)&&s.push(".gitignore");for(let a of o)x.log.info(`Deleted ${a}`);for(let a of s)x.log.info(`Modified ${a}`);x.log.success("Ejected successfully. This project is now fully standalone.")})}var Se=new ls().name("generatesaas").description("CLI for scaffolding and managing GenerateSaaS projects").version("1.9.2").addHelpText("after",`
1124
1130
  Examples:
1125
1131
  $ generatesaas init Interactive setup
1126
1132
  $ generatesaas init -n my-app -y Quick setup with defaults
1127
1133
  $ generatesaas status Check for updates
1128
1134
  $ generatesaas auth Set or update API key
1129
- `);fn(Se);hn(Se);yn(Se);bn(Se);An(Se);In(Se);Se.parseAsync().catch(e=>{kn.cancel("An unexpected error occurred."),console.error(e),process.exit(1)});
1135
+ `);fn(Se);hn(Se);yn(Se);bn(Se);An(Se);kn(Se);Se.parseAsync().catch(e=>{In.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.9.1",
3
+ "version": "1.9.2",
4
4
  "type": "module",
5
5
  "description": "CLI for scaffolding and managing GenerateSaaS projects",
6
6
  "bin": {