generatesaas 1.12.0 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +109 -83
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import{Command as _s}from"commander";import*as jn from"@clack/prompts";import{existsSync as Jo,readdirSync as Wo,rmSync as qo}from"fs";import{join as Xo,resolve as Zo}from"path";import{Option as V}from"commander";import*as E from"@clack/prompts";import*as We from"@clack/prompts";import Tt from"picocolors";function Zt(e){let t=e?` GenerateSaaS v${e} `:" GenerateSaaS ";We.intro(Tt.bgYellow(Tt.black(t)))}function Qt(){We.outro(Tt.yellow("Happy building!"))}import*as m from"@clack/prompts";import g from"picocolors";var Le={nextjs:{label:"Next.js",hint:"React 19 + Next.js 16"},nuxt:{label:"Nuxt",hint:"Vue 3 + Nuxt 4"}},$e={fullstack:{label:"Fullstack",hint:"Frontend hosts the API - works on serverless or long-running"},separate:{label:"Separate",hint:"Standalone Hono backend - long-running runtimes only (Docker, Render, Fly.io, Railway, Coolify, Dokploy)"}},qe={stripe:{label:"Stripe"},polar:{label:"Polar"},none:{label:"None",hint:"disable payments"}},Xe={smtp:{label:"SMTP",hint:"Mailpit for local dev"},ses:{label:"Amazon SES"},resend:{label:"Resend"}},Ze={user:{label:"Per user",hint:"each user has their own subscription"},organization:{label:"Per organization",hint:"org subscription shared by members"}},be={postgres:{label:"PostgreSQL",hint:"port 5432",port:5432},redis:{label:"Redis",hint:"port 6379",port:6379},inngest:{label:"Inngest",hint:"port 8288",port:8288},mailpit:{label:"Mailpit",hint:"port 1025",port:1025}},Ue={"claude-code":{label:"Claude Code"},cursor:{label:"Cursor"},codex:{label:"Codex"},"gemini-cli":{label:"Gemini CLI"},windsurf:{label:"Windsurf"}};var se={USD:{symbol:"$",name:"US Dollar",place:"left",space:!1},EUR:{symbol:"\u20AC",name:"Euro",place:"right",space:!1},GBP:{symbol:"\xA3",name:"British Pound",place:"left",space:!1},CAD:{symbol:"CA$",name:"Canadian Dollar",place:"left",space:!1},AUD:{symbol:"A$",name:"Australian Dollar",place:"left",space:!1},BRL:{symbol:"R$",name:"Brazilian Real",place:"left",space:!1},JPY:{symbol:"\xA5",name:"Japanese Yen",place:"left",space:!1}};var B={node:{label:"Node.js / Docker",hint:"long-running runtime - Render, Fly.io, Railway, Coolify, Dokploy, VPS",edgeRuntime:!1},vercel:{label:"Vercel",hint:"serverless functions",edgeRuntime:!0}},N={postgres:{label:"PostgreSQL (self-hosted)",hint:"local Docker, drizzle-orm/node-postgres",managed:!1,envVars:[{key:"DATABASE_URL",defaultValue:"postgres://postgres:postgres@localhost:5432/saas"}]},neon:{label:"Neon",hint:"serverless Postgres",managed:!0,envVars:[{key:"DATABASE_URL",comment:"# TODO: Add your Neon connection string"}]},supabase:{label:"Supabase",hint:"managed Postgres",managed:!0,envVars:[{key:"DATABASE_URL",comment:"# TODO: Add your Supabase connection string"}]}},G={redis:{label:"Redis (self-hosted)",hint:"local Docker, ioredis",managed:!1,envVars:[{key:"REDIS_URL",defaultValue:"redis://localhost:6379"}]},upstash:{label:"Upstash",hint:"serverless Redis",managed:!0,envVars:[{key:"UPSTASH_REDIS_REST_URL",comment:"# TODO: Add your Upstash REST URL"},{key:"UPSTASH_REDIS_REST_TOKEN",comment:"# TODO: Add your Upstash REST token"}]}},je=[{target:"vercel",provider:"redis",reason:"Vercel serverless cannot maintain persistent Redis connections. Consider Upstash."}],Ve=[{architecture:"separate",target:"vercel",reason:"Standalone backends need a long-running runtime. Vercel's per-function TypeScript check conflicts with pnpm's isolated install. Use --architecture fullstack for serverless, or deploy the standalone backend to a long-running runtime (Render, Fly.io, Railway, Coolify, Dokploy, or your own VPS via Docker)."}],er=["Local file storage (sharp, geoip-lite)","SMTP email (use Resend or SES instead)","Content API git integration"];function Qe(e){let t=N[e.databaseProvider].managed,r=G[e.cacheProvider].managed;return e.dockerServices.some(n=>!(n==="postgres"&&t||n==="redis"&&r))}var W={google:{label:"Google",envVars:[{name:"GOOGLE_CLIENT_ID",secret:!1},{name:"GOOGLE_CLIENT_SECRET",secret:!0}]},github:{label:"GitHub",envVars:[{name:"GITHUB_CLIENT_ID",secret:!1},{name:"GITHUB_CLIENT_SECRET",secret:!0}]},facebook:{label:"Facebook",envVars:[{name:"FACEBOOK_CLIENT_ID",secret:!1},{name:"FACEBOOK_CLIENT_SECRET",secret:!0}]},discord:{label:"Discord",envVars:[{name:"DISCORD_CLIENT_ID",secret:!1},{name:"DISCORD_CLIENT_SECRET",secret:!0}]},x:{label:"X",envVars:[{name:"TWITTER_CLIENT_ID",secret:!1},{name:"TWITTER_CLIENT_SECRET",secret:!0}]}};var Me=["nextjs","nuxt"],et=["fullstack","separate"],tt=["stripe","polar","none"],rt=["smtp","ses","resend"],nt=["postgres","redis","inngest","mailpit"],it=["claude-code","cursor","codex","gemini-cli","windsurf"],ot=["user","organization"],ae=["USD","EUR","GBP","CAD","AUD","BRL","JPY"],ce=["node","vercel"],le=["postgres","neon","supabase"],pe=["redis","upstash"],st=["google","github","facebook","discord","x"];function at(e){return e.split("-").map(t=>t.charAt(0).toUpperCase()+t.slice(1)).join(" ")}function ct(e){return/^[a-z][a-z0-9-]*$/.test(e)}function I(e){return e instanceof Error?e.message:String(e)}function w(e){m.isCancel(e)&&(m.cancel("Setup cancelled."),process.exit(0))}function kt(e){let t=[];e.databaseProvider==="neon"&&t.push({key:"DATABASE_URL",message:"Neon connection string (optional):",placeholder:"postgres://...",secret:!0}),e.databaseProvider==="supabase"&&t.push({key:"DATABASE_URL",message:"Supabase connection string (optional):",placeholder:"postgres://...",secret:!0}),e.cacheProvider==="upstash"&&(t.push({key:"UPSTASH_REDIS_REST_URL",message:"Upstash REST URL (optional):",placeholder:"https://...",secret:!1}),t.push({key:"UPSTASH_REDIS_REST_TOKEN",message:"Upstash REST token (optional):",secret:!0})),e.paymentProvider==="stripe"&&(t.push({key:"STRIPE_SECRET_KEY",message:"Stripe secret key (optional):",placeholder:"sk_test_...",secret:!0}),t.push({key:"STRIPE_WEBHOOK_SECRET",message:"Stripe webhook secret (optional):",placeholder:"whsec_...",secret:!0})),e.paymentProvider==="polar"&&(t.push({key:"POLAR_ACCESS_TOKEN",message:"Polar access token (optional):",secret:!0}),t.push({key:"POLAR_WEBHOOK_SECRET",message:"Polar webhook secret (optional):",secret:!0})),e.emailProvider==="resend"&&t.push({key:"RESEND_API_KEY",message:"Resend API key (optional):",placeholder:"re_...",secret:!0}),e.emailProvider==="ses"&&(t.push({key:"AMAZON_SES_REGION",message:"Amazon SES region (optional):",placeholder:"us-east-1",secret:!1}),t.push({key:"AMAZON_SES_KEY",message:"Amazon SES access key (optional):",secret:!0}),t.push({key:"AMAZON_SES_SECRET",message:"Amazon SES secret (optional):",secret:!0}));for(let r of e.socialProviders){let n=W[r];for(let i of n.envVars)t.push({key:i.name,message:`${i.name} (${n.label}, optional):`,secret:i.secret})}return e.demo&&t.push({key:"TURNSTILE_SECRET_KEY",message:"Cloudflare Turnstile secret key (optional):",secret:!0}),t}async function tr(e){let t=!1;m.log.info(g.bold("Project"));let r=e?.projectName??await(async()=>{t=!0;let c=await m.text({message:"Project name:",placeholder:"my-saas",validate:p=>{if(!p?.trim())return"Project name is required.";if(!ct(p))return"Use lowercase letters, numbers, and hyphens only. Must start with a letter."}});return w(c),c})(),n=e?.appName??await(async()=>{t=!0;let c=await m.text({message:"App name:",initialValue:at(r),validate:p=>{if(!p?.trim())return"App name is required."}});return w(c),c})(),i=e?.projectDir??await(async()=>{t=!0;let c=await m.text({message:"Project location:",initialValue:`./${r}`});return w(c),c==="."?process.cwd():c})(),o=e?.frontend??await(async()=>{t=!0;let c=Object.keys(Le),p=await m.select({message:"Frontend framework:",options:c.map(v=>({value:v,label:Le[v].label,hint:Le[v].hint}))});return w(p),p})();m.log.info(g.bold("Infrastructure"));let s=e?.deploymentTarget??"node";if(e?.deploymentTarget===void 0){t=!0;let c=await m.select({message:"Deployment target:",options:ce.map(p=>({value:p,label:B[p].label,hint:B[p].hint}))});w(c),s=c}let a=e?.architecture?Ve.find(c=>c.architecture===e.architecture&&c.target===s):void 0;if(a)throw new Error(`Incompatible: --architecture ${a.architecture} + --deploy ${a.target}. ${a.reason}`);let d=new Set(Ve.filter(c=>c.target===s).map(c=>c.architecture)),y=et.filter(c=>!d.has(c)),h=e?.architecture??await(async()=>{if(y.length===1){let p=y[0];return m.log.info(`Auto-selected ${$e[p].label} architecture (only compatible option for ${B[s].label}).`),p}t=!0;let c=await m.select({message:"Architecture:",options:y.map(p=>({value:p,label:$e[p].label,hint:$e[p].hint}))});return w(c),c})(),f=e?.databaseProvider??await(async()=>{t=!0;let c=le.filter(v=>!je.some(D=>D.target===s&&D.provider===v));if(c.length===1){let v=c[0];return m.log.info(`Auto-selected ${N[v].label} (only compatible option for ${B[s].label}).`),v}let p=await m.select({message:"Database provider:",options:c.map(v=>({value:v,label:N[v].label,hint:N[v].hint}))});return w(p),p})(),S=e?.cacheProvider??await(async()=>{t=!0;let c=pe.filter(v=>!je.some(D=>D.target===s&&D.provider===v));if(c.length===1){let v=c[0];return m.log.info(`Auto-selected ${G[v].label} (only compatible option for ${B[s].label}).`),v}let p=await m.select({message:"Cache provider:",options:c.map(v=>({value:v,label:G[v].label,hint:G[v].hint}))});return w(p),p})();if(B[s]?.edgeRuntime){let c=er.map(p=>` - ${p}`).join(`
3
- `);m.note(c,"Unavailable on edge runtime")}m.log.info(g.bold("Features"));let k=e?.paymentProvider??await(async()=>{t=!0;let c=await m.select({message:"Payment provider:",options:tt.map(p=>({value:p,label:qe[p].label,hint:qe[p].hint}))});return w(c),c})(),ie=e?.defaultCurrency??await(async()=>{if(k==="none")return"USD";t=!0;let c=await m.select({message:"Default currency:",options:ae.map(p=>({value:p,label:p,hint:se[p].name}))});return w(c),c})(),x=e?.emailProvider??await(async()=>{t=!0;let c=await m.select({message:"Email provider:",options:rt.map(p=>({value:p,label:Xe[p].label,hint:Xe[p].hint}))});return w(c),c})(),$=e?.multiTenancy??await(async()=>{t=!0;let c=await m.confirm({message:"Enable multi-tenancy (organizations)?",initialValue:!1});return w(c),c})(),Ee=e?.billingScope??"user";if($&&e?.billingScope===void 0){t=!0;let c=await m.select({message:"Billing scope:",options:ot.map(p=>({value:p,label:Ze[p].label,hint:Ze[p].hint}))});w(c),Ee=c}let Z=e?.blog??await(async()=>{t=!0;let c=await m.confirm({message:"Enable blog?",initialValue:!0});return w(c),c})(),u=e?.docs??await(async()=>{t=!0;let c=await m.confirm({message:"Include docs app? (self-hosted Fumadocs documentation site)",initialValue:!1});return w(c),c})(),b=e?.revenueSharing??await(async()=>{t=!0;let c=await m.confirm({message:"Enable revenue sharing? (opt-in MRR leaderboard with dofollow backlinks)",initialValue:!1});return w(c),c})(),H=k==="none"?!1:e?.credits??await(async()=>{t=!0;let c=await m.confirm({message:"Enable credits? (metered usage on top of subscription plans)",initialValue:!0});return w(c),c})(),Y=e?.socialProviders??await(async()=>{t=!0;let c=st.map(v=>({value:v,label:W[v].label,hint:`requires ${W[v].envVars.map(D=>D.name).join(" / ")}`})),p=await m.multiselect({message:"Which social login providers should the sign-in screen show?",options:c,initialValues:[],required:!1});return w(p),p})();m.log.info(g.bold("Tooling"));let J=e?.dockerServices??await(async()=>{t=!0;let c=[...nt].filter(C=>C!=="mailpit");x==="smtp"&&c.push("mailpit");let p=c.map(C=>({value:C,label:be[C].label,hint:be[C].hint})),v=p.map(C=>C.value).filter(C=>!(C==="postgres"&&(f==="neon"||f==="supabase")||C==="redis"&&S==="upstash")),D=await m.multiselect({message:"Which services should we set up in Docker for you?",options:p,initialValues:v,required:!1});return w(D),D})(),we=e?.aiTools??await(async()=>{t=!0;let c=it.map(v=>({value:v,label:Ue[v].label})),p=await m.multiselect({message:"Which AI coding tools do you use?",options:c,initialValues:[],required:!1});return w(p),p})(),At=e?.demo,Je=kt({databaseProvider:f,cacheProvider:S,paymentProvider:k,emailProvider:x,socialProviders:Y,demo:At}),Ne={};if(Je.length>0&&t){m.log.info(g.bold("Credentials")+g.dim(" all optional - press Enter to skip, fill in .env later"));for(let c of Je)if(t=!0,c.secret){let p=await m.password({message:c.message,mask:"*"});w(p),typeof p=="string"&&p.trim()&&(Ne[c.key]=p.trim())}else{let p=await m.text({message:c.message,placeholder:c.placeholder});w(p),typeof p=="string"&&p.trim()&&(Ne[c.key]=p.trim())}}if(t){let c=[` Name: ${g.cyan(r)}`,` App name: ${g.cyan(n)}`,` Location: ${g.cyan(i)}`,` Frontend: ${g.cyan(Le[o].label)}`,` Architecture: ${g.cyan($e[h].label)}`].join(`
4
- `),p=[` Deploy target: ${g.cyan(B[s]?.label??"Node.js / Docker")}`,` Database: ${g.cyan(N[f].label)}`,` Cache: ${g.cyan(G[S].label)}`,J.length>0?` Docker: ${g.cyan(J.map(oe=>be[oe].label).join(", "))}`:` Docker: ${g.dim("none")}`].filter(Boolean).join(`
5
- `),v=[k!=="none"?` Payment: ${g.cyan(qe[k].label)} (${ie})`:` Payment: ${g.dim("none")}`,` Credits: ${H?g.cyan("Yes"):g.dim("No")}`,` Email: ${g.cyan(Xe[x].label)}`,` Multi-tenancy: ${$?g.cyan("Yes")+` (billing: ${Ze[Ee].label})`:g.dim("No")}`,` Blog: ${Z?g.cyan("Yes"):g.dim("No")}`,` Docs app: ${u?g.cyan("Yes"):g.dim("No")}`,` Rev. sharing: ${b?g.cyan("Yes"):g.dim("No")}`,Y.length>0?` Social login: ${g.cyan(Y.map(oe=>W[oe].label).join(", "))}`:` Social login: ${g.dim("none")}`,we.length>0?` AI tools: ${g.cyan(we.map(oe=>Ue[oe].label).join(", "))}`:` AI tools: ${g.dim("none")}`].join(`
6
- `),D=[g.bold("Project"),c,"",g.bold("Infrastructure"),p,"",g.bold("Features"),v];if(Je.length>0){let oe=Je.map(Xt=>{let Vn=Ne[Xt.key]?g.green("provided"):g.dim("skipped");return` ${Xt.key}: ${Vn}`}).join(`
7
- `);D.push("",g.bold("Credentials"),oe)}m.note(D.join(`
8
- `),"Summary");let C=await m.confirm({message:"Proceed with these settings?"});(m.isCancel(C)||!C)&&(m.cancel("Setup cancelled."),process.exit(0))}return{projectName:r,appName:n,projectDir:i,frontend:o,architecture:h,deploymentTarget:s,databaseProvider:f,cacheProvider:S,paymentProvider:k,emailProvider:x,multiTenancy:$,billingScope:Ee,blog:Z,docs:u,revenueSharing:b,credits:H,dockerServices:J,aiTools:we,socialProviders:Y,defaultCurrency:ie,...Object.keys(Ne).length>0?{credentials:Ne}:{},...e?.baseUrl!==void 0?{baseUrl:e.baseUrl}:{},...At!==void 0?{demo:At}:{}}}import{createReadStream as Jn}from"fs";import{mkdir as Wn}from"fs/promises";import{Readable as qn}from"stream";import{pipeline as lr}from"stream/promises";import{extract as Xn}from"tar";import{join as de}from"path";import{homedir as Mn}from"os";var Fe=process.env.GENERATESAAS_API_URL??"https://cli.generatesaas.com",U=".generatesaas",Q=de(U,"manifest.json"),rr=de(U,"hashes.json"),lt=de(U,"template-hashes.json"),pt=de(U,"template"),nr=de(U,"staging"),ir=de(U,"staging.json"),q=de(Mn(),".generatesaas");var R=class extends Error{constructor(r,n,i){super(n);this.status=r;this.body=i}status;body;name="ApiError"};function X(e){return{apiKey:e,baseUrl:Fe}}async function ee(e,t,r){let n=`${e.baseUrl}${t}`,i=await fetch(n,{...r,headers:{...r?.headers,Authorization:`Bearer ${e.apiKey}`,"User-Agent":"generatesaas-cli"}});if(!i.ok){let o,s;try{s=await i.json(),o=s.error??`API ${i.status}: ${t}`}catch{o=`API ${i.status}: ${t}`}throw new R(i.status,o,s)}return i}import{existsSync as Fn,readFileSync as Bn,writeFileSync as Gn,mkdirSync as Kn}from"fs";import{dirname as zn}from"path";import*as te from"@clack/prompts";function Be(){if(!Fn(q))return null;try{let e=JSON.parse(Bn(q,"utf-8"));return e.apiKey?e.apiKey:(e.token&&!e.apiKey&&te.log.warning(`Found old GitHub token in ${q}. Run 'generatesaas init' to set up your API key.`),null)}catch{return null}}function ue(e){Kn(zn(q),{recursive:!0}),Gn(q,JSON.stringify({apiKey:e},null," ")+`
9
- `,{mode:384})}async function Ae(e){if(e?.apiKey)return e.apiKey;let t=process.env.GENERATESAAS_API_KEY;if(t)return t;let r=Be();if(r)return r;if(!e?.prompt)throw new Error("API key not found. Set GENERATESAAS_API_KEY or run 'generatesaas init' to configure.");return Ge()}async function Ge(){let e=await te.text({message:"Enter your GenerateSaaS API key:",placeholder:"gs_live_...",validate:t=>{if(!t?.trim())return"API key is required."}});return te.isCancel(e)&&(te.cancel("Setup cancelled."),process.exit(0)),e.trim()}async function re(e){return process.env.GENERATESAAS_OFFLINE_LICENSE==="1"?{latest:"0.0.0-ci",versions:[{version:"0.0.0-ci",date:new Date().toISOString(),breaking:!1}]}:await(await ee(e,"/versions")).json()}async function It(e,t){try{return await(await ee(e,`/changelog/${encodeURIComponent(t)}`)).text()}catch(r){if(r instanceof R&&r.status===404)return null;throw r}}async function or(e,t){return await(await ee(e,`/skill/${encodeURIComponent(t)}`)).json()}function dt(e){if(!(e instanceof R)||e.status!==403)return null;let t=e.body;return!t||t.code!=="update_window_expired"?null:{message:e.message,lastAllowedVersion:t.lastAllowedVersion??null}}function sr(e){return{frontend:e.frontend,architecture:e.architecture,deployTarget:e.deploymentTarget,database:e.databaseProvider,cache:e.cacheProvider,payment:e.paymentProvider,email:e.emailProvider,multiTenancy:e.multiTenancy,billingScope:e.billingScope,blog:e.blog,docs:e.docs,credits:e.credits,revenueSharing:e.revenueSharing,socialProviders:e.socialProviders,aiTools:e.aiTools,currency:e.defaultCurrency}}async function Pt(e,t){return process.env.GENERATESAAS_OFFLINE_LICENSE==="1"?{token:"offline-test-token",licenseId:"offline-test-license-id"}:await(await ee(e,"/license/sign",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).json()}async function ar(e,t){return await(await ee(e,"/license/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).json()}async function _t(e,t){let r=await fetch(`${e}/license/verify`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)});if(!r.ok)throw new Error(`Verification service returned ${r.status}`);return await r.json()}async function cr(e,t,r){let n=await fetch(`${e}/license/inspect`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t}`},body:JSON.stringify(r)});if(!n.ok){let i=await n.json().catch(()=>null);throw new Error(i?.error??`Inspect endpoint returned ${n.status}`)}return await n.json()}var Rt=new Set([".git","node_modules",".pnpm-store",".env",".env.test",".turbo",".nuxt",".output",".data","dist",".next",".svelte-kit",".wrangler",".devcontainer","playwright-report","test-results"]),Ot=new Set(["pnpm-lock.yaml"]);function Te(e){if(xt(e))return!0;for(let t of e.split("/"))if(Ot.has(t))return!0;return!1}var Hn=new Set(["data","mksaas","references","scripts",".cursor",".agents",".codex",".generatesaas",".vscode",".mcp.json","README.md","TODO.md","OVERVIEW.md"]),Yn=["docs/superpowers","packages/cli","packages/cli-api","infra/docker-compose.yml",".claude/commands",".claude/skills/web-next-port",".claude/skills/web-next-port-workspace",".claude/settings.local.json",".claude/worktrees"];function xt(e){let t=e.split("/");for(let r of t)if(Rt.has(r))return!0;if(Hn.has(t[0]))return!0;for(let r of Yn)if(e===r||e.startsWith(r+"/"))return!0;return!1}async function ut(e,t,r){await Wn(r,{recursive:!0});let n=process.env.GENERATESAAS_TEMPLATE_TARBALL;if(n){await lr(Jn(n),pr(r));return}let i=await ee(e,`/template/${encodeURIComponent(t)}`);if(!i.body)throw new Error("Empty response body");let o=qn.fromWeb(i.body);await lr(o,pr(r))}function pr(e){return Xn({cwd:e,strip:1,filter:t=>{let r=t.replace(/^[^/]+\//,"");return r?!xt(r):!0},sync:!1})}import{readdir as Zn,readFile as Dt,rm as ur,writeFile as Ct}from"fs/promises";import{join as ke}from"path";var Qn=["apps/web-nuxt/public/images/blog","apps/web-next/public/images/blog","packages/content/en/blog","packages/content/ro/blog"];async function mr(e){await Promise.all(Qn.map(t=>ur(ke(e,t),{recursive:!0,force:!0})))}var ei="packages/config/src/blog.ts";async function fr(e){let t=ke(e,ei),r=await Dt(t,"utf-8"),n=dr(dr(r,"blogCategories",t),"blogAuthors",t);await Ct(t,n)}function dr(e,t,r){let n=new RegExp(`(export const ${t}\\b[^=]*=\\s*)\\[[\\s\\S]*?\\];`);if(!n.test(e))throw new Error(`emptyBlogConfig: could not find the \`export const ${t} = [...]\` declaration in ${r}. The boilerplate blog config may have been renamed or restructured; update the CLI strip in cleanup.ts.`);return e.replace(n,"$1[];")}async function gr(e){let t=ke(e,"packages/i18n/translations"),r;try{r=await Zn(t)}catch{return}for(let n of r){let i=ke(t,n,"web.json"),o;try{o=await Dt(i,"utf-8")}catch{continue}let s=JSON.parse(o);!s.blog||s.blog.categories===void 0||(s.blog.categories={},await Ct(i,JSON.stringify(s,null," ")+`
10
- `))}}async function hr(e,t){t.includes("claude-code")||await ur(ke(e,".claude"),{recursive:!0,force:!0})}async function yr(e,t){let r=ke(e,".claude","settings.json"),n;try{n=await Dt(r,"utf8")}catch{return}let i=JSON.parse(n);delete i.alwaysThinkingEnabled,delete i.enableAllProjectMcpServers,i.env&&(delete i.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS,Object.keys(i.env).length===0&&delete i.env),i.permissions?.allow&&(i.permissions.allow=i.permissions.allow.filter(o=>ti(o,t))),await Ct(r,JSON.stringify(i,null," ")+`
11
- `)}function ti(e,t){return!(e.startsWith("mcp__")||t!=="nuxt"&&e.includes("nuxt"))}import{join as vr}from"path";import{mkdir as ri,readdir as ni,rm as ii,rmdir as oi,writeFile as si}from"fs/promises";import{dirname as mt,join as ai,relative as ci,sep as li}from"path";function me(e){return e.split(li).join("/")}async function ft(e){await ri(e,{recursive:!0})}async function l(e,t){await ft(mt(e)),await si(e,t,"utf-8")}async function Nt(e,t){await ii(e,{force:!0});let r=mt(e);for(;r!==t&&r!==mt(r);){try{await oi(r)}catch{return}r=mt(r)}}async function fe(e,t,r){let n=[],i=await ni(e,{withFileTypes:!0});for(let o of i){let s=ai(e,o.name),a=me(ci(t,s));r(a)||(o.isDirectory()?n.push(...await fe(s,t,r)):o.isFile()&&n.push(s))}return n}var pi={postgres:"Postgres",neon:"Neon (managed Postgres)",supabase:"Supabase (managed Postgres)"},di={resend:"Resend",ses:"Amazon SES",smtp:"SMTP"},ui={redis:"Redis",upstash:"Upstash Redis"},mi={node:"Node.js / Docker",vercel:"Vercel"},fi={stripe:"Stripe",polar:"Polar"};function gi(e){let t=e.frontend==="nuxt",r=t?"Nuxt 4":"Next.js 16",n=t?"apps/web-nuxt":"apps/web-next",i=t?"`app/` pages + components + composables":"`app/` routes, `components/`, `lib/`",o=e.architecture==="fullstack",s=o?"(fullstack - Hono API mounted inside the app)":"(separate - standalone Hono backend)",a=o?`Mounted inside \`${n}\`. A standalone \`apps/backend\` is also kept (inert) so you can switch to separate later.`:"Runs from `apps/backend`; the frontend reaches it over HTTP.",d=ui[e.cacheProvider],y=mi[e.deploymentTarget],h=e.paymentProvider==="none"?"":`
12
- - **Payments:** ${fi[e.paymentProvider]}`,f=t?"`$t('key')` in templates (global helper); `useI18n()` from `vue-i18n` in `<script setup>` for `locale`/`setLocale`/`t()`.":"`useTranslations()` from `next-intl` in components; messages are loaded in `apps/web-next/i18n/request.ts`.",S=t?"**Navigation:** always use `localePath()` for paths (hardcoded paths break non-default locales); `await navigateTo()` in SSR.":"**Navigation:** `next/link` for links; `redirect()` from `next/navigation` for programmatic redirects in Server Components.";return`# AGENTS.md
2
+ import{Command as Cs}from"commander";import*as Gn from"@clack/prompts";import{existsSync as Qo,readdirSync as es,rmSync as ts}from"fs";import{join as rs,resolve as ns}from"path";import{Option as F}from"commander";import*as E from"@clack/prompts";import*as We from"@clack/prompts";import It from"picocolors";function er(e){let t=e?` GenerateSaaS v${e} `:" GenerateSaaS ";We.intro(It.bgYellow(It.black(t)))}function tr(){We.outro(It.yellow("Happy building!"))}import*as m from"@clack/prompts";import g from"picocolors";var $e={nextjs:{label:"Next.js",hint:"React 19 + Next.js 16"},nuxt:{label:"Nuxt",hint:"Vue 3 + Nuxt 4"}},je={fullstack:{label:"Fullstack",hint:"Frontend hosts the API - works on serverless or long-running"},separate:{label:"Separate",hint:"Standalone Hono backend - long-running runtimes only (Docker, Render, Fly.io, Railway, Coolify, Dokploy)"}},qe={stripe:{label:"Stripe"},polar:{label:"Polar"},none:{label:"None",hint:"disable payments"}},Xe={smtp:{label:"SMTP",hint:"Mailpit for local dev"},ses:{label:"Amazon SES"},resend:{label:"Resend"}},Ze={user:{label:"Per user",hint:"each user has their own subscription"},organization:{label:"Per organization",hint:"org subscription shared by members"}},ke={postgres:{label:"PostgreSQL",hint:"port 5432",port:5432},redis:{label:"Redis",hint:"port 6379",port:6379},inngest:{label:"Inngest",hint:"port 8288",port:8288},mailpit:{label:"Mailpit",hint:"port 1025",port:1025}},Ue={"claude-code":{label:"Claude Code"},cursor:{label:"Cursor"},codex:{label:"Codex"},"gemini-cli":{label:"Gemini CLI"},windsurf:{label:"Windsurf"}};var ce={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 z={node:{label:"Node.js / Docker",hint:"long-running runtime - Render, Fly.io, Railway, Coolify, Dokploy, VPS",edgeRuntime:!1},vercel:{label:"Vercel",hint:"serverless functions",edgeRuntime:!0}},$={postgres:{label:"PostgreSQL (self-hosted)",hint:"local Docker, drizzle-orm/node-postgres",managed:!1,envVars:[{key:"DATABASE_URL",defaultValue:"postgres://postgres:postgres@localhost:5432/saas"}]},neon:{label:"Neon",hint:"serverless Postgres",managed:!0,envVars:[{key:"DATABASE_URL",comment:"# TODO: Add your Neon connection string"}]},supabase:{label:"Supabase",hint:"managed Postgres",managed:!0,envVars:[{key:"DATABASE_URL",comment:"# TODO: Add your Supabase connection string"}]}},H={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"}]}},Ve=[{target:"vercel",provider:"redis",reason:"Vercel serverless cannot maintain persistent Redis connections. Consider Upstash."}],Me=[{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)."}],rr=["Local file storage (sharp, geoip-lite)","SMTP email (use Resend or SES instead)","Content API git integration"];function Qe(e){let t=$[e.databaseProvider].managed,r=H[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 Fe=["nextjs","nuxt"],et=["fullstack","separate"],tt=["stripe","polar","none"],rt=["smtp","ses","resend"],nt=["postgres","redis","inngest","mailpit"],it=["claude-code","cursor","codex","gemini-cli","windsurf"],ot=["user","organization"],le=["USD","EUR","GBP","CAD","AUD","BRL","JPY"],pe=["node","vercel"],de=["postgres","neon","supabase"],ue=["redis","upstash"],st=["google","github","facebook","discord","x"];function at(e){return e.split("-").map(t=>t.charAt(0).toUpperCase()+t.slice(1)).join(" ")}function ct(e){return/^[a-z][a-z0-9-]*$/.test(e)}function P(e){return e instanceof Error?e.message:String(e)}function w(e){m.isCancel(e)&&(m.cancel("Setup cancelled."),process.exit(0))}function Pt(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 nr(e){let t=!1;m.log.info(g.bold("Project"));let r=e?.projectName??await(async()=>{t=!0;let c=await m.text({message:"Project name:",placeholder:"my-saas",validate:p=>{if(!p?.trim())return"Project name is required.";if(!ct(p))return"Use lowercase letters, numbers, and hyphens only. Must start with a letter."}});return w(c),c})(),n=e?.appName??await(async()=>{t=!0;let c=await m.text({message:"App name:",initialValue:at(r),validate:p=>{if(!p?.trim())return"App name is required."}});return w(c),c})(),i=e?.projectDir??await(async()=>{t=!0;let c=await m.text({message:"Project location:",initialValue:`./${r}`});return w(c),c==="."?process.cwd():c})(),o=e?.frontend??await(async()=>{t=!0;let c=Object.keys($e),p=await m.select({message:"Frontend framework:",options:c.map(v=>({value:v,label:$e[v].label,hint:$e[v].hint}))});return w(p),p})();m.log.info(g.bold("Infrastructure"));let s=e?.deploymentTarget??"node";if(e?.deploymentTarget===void 0){t=!0;let c=await m.select({message:"Deployment target:",options:pe.map(p=>({value:p,label:z[p].label,hint:z[p].hint}))});w(c),s=c}let a=e?.architecture?Me.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(Me.filter(c=>c.target===s).map(c=>c.architecture)),h=et.filter(c=>!d.has(c)),y=e?.architecture??await(async()=>{if(h.length===1){let p=h[0];return m.log.info(`Auto-selected ${je[p].label} architecture (only compatible option for ${z[s].label}).`),p}t=!0;let c=await m.select({message:"Architecture:",options:h.map(p=>({value:p,label:je[p].label,hint:je[p].hint}))});return w(c),c})(),f=e?.databaseProvider??await(async()=>{t=!0;let c=de.filter(v=>!Ve.some(N=>N.target===s&&N.provider===v));if(c.length===1){let v=c[0];return m.log.info(`Auto-selected ${$[v].label} (only compatible option for ${z[s].label}).`),v}let p=await m.select({message:"Database provider:",options:c.map(v=>({value:v,label:$[v].label,hint:$[v].hint}))});return w(p),p})(),S=e?.cacheProvider??await(async()=>{t=!0;let c=ue.filter(v=>!Ve.some(N=>N.target===s&&N.provider===v));if(c.length===1){let v=c[0];return m.log.info(`Auto-selected ${H[v].label} (only compatible option for ${z[s].label}).`),v}let p=await m.select({message:"Cache provider:",options:c.map(v=>({value:v,label:H[v].label,hint:H[v].hint}))});return w(p),p})();if(z[s]?.edgeRuntime){let c=rr.map(p=>` - ${p}`).join(`
3
+ `);m.note(c,"Unavailable on edge runtime")}m.log.info(g.bold("Features"));let I=e?.paymentProvider??await(async()=>{t=!0;let c=await m.select({message:"Payment provider:",options:tt.map(p=>({value:p,label:qe[p].label,hint:qe[p].hint}))});return w(c),c})(),oe=e?.defaultCurrency??await(async()=>{if(I==="none")return"USD";t=!0;let c=await m.select({message:"Default currency:",options:le.map(p=>({value:p,label:p,hint:ce[p].name}))});return w(c),c})(),C=e?.emailProvider??await(async()=>{t=!0;let c=await m.select({message:"Email provider:",options:rt.map(p=>({value:p,label:Xe[p].label,hint:Xe[p].hint}))});return w(c),c})(),U=e?.multiTenancy??await(async()=>{t=!0;let c=await m.confirm({message:"Enable multi-tenancy (organizations)?",initialValue:!1});return w(c),c})(),we=e?.billingScope??"user";if(U&&e?.billingScope===void 0){t=!0;let c=await m.select({message:"Billing scope:",options:ot.map(p=>({value:p,label:Ze[p].label,hint:Ze[p].hint}))});w(c),we=c}let Q=e?.blog??await(async()=>{t=!0;let c=await m.confirm({message:"Enable blog?",initialValue:!0});return w(c),c})(),u=e?.docs??await(async()=>{t=!0;let c=await m.confirm({message:"Include docs app? (self-hosted Fumadocs documentation site)",initialValue:!1});return w(c),c})(),b=e?.desktop??await(async()=>{t=!0;let c=await m.confirm({message:"Include the Electron desktop app? (apps/desktop - cross-platform, device-auth)",initialValue:!1});return w(c),c})(),W=e?.revenueSharing??await(async()=>{t=!0;let c=await m.confirm({message:"Enable revenue sharing? (opt-in MRR leaderboard with dofollow backlinks)",initialValue:!1});return w(c),c})(),se=I==="none"?!1:e?.credits??await(async()=>{t=!0;let c=await m.confirm({message:"Enable credits? (metered usage on top of subscription plans)",initialValue:!0});return w(c),c})(),K=e?.socialProviders??await(async()=>{t=!0;let c=st.map(v=>({value:v,label:q[v].label,hint:`requires ${q[v].envVars.map(N=>N.name).join(" / ")}`})),p=await m.multiselect({message:"Which social login providers should the sign-in screen show?",options:c,initialValues:[],required:!1});return w(p),p})();m.log.info(g.bold("Tooling"));let be=e?.dockerServices??await(async()=>{t=!0;let c=[...nt].filter(L=>L!=="mailpit");C==="smtp"&&c.push("mailpit");let p=c.map(L=>({value:L,label:ke[L].label,hint:ke[L].hint})),v=p.map(L=>L.value).filter(L=>!(L==="postgres"&&(f==="neon"||f==="supabase")||L==="redis"&&S==="upstash")),N=await m.multiselect({message:"Which services should we set up in Docker for you?",options:p,initialValues:v,required:!1});return w(N),N})(),At=e?.aiTools??await(async()=>{t=!0;let c=it.map(v=>({value:v,label:Ue[v].label})),p=await m.multiselect({message:"Which AI coding tools do you use?",options:c,initialValues:[],required:!1});return w(p),p})(),Tt=e?.demo,Je=Pt({databaseProvider:f,cacheProvider:S,paymentProvider:I,emailProvider:C,socialProviders:K,demo:Tt}),Le={};if(Je.length>0&&t){m.log.info(g.bold("Credentials")+g.dim(" all optional - press Enter to skip, fill in .env later"));for(let c of Je)if(t=!0,c.secret){let p=await m.password({message:c.message,mask:"*"});w(p),typeof p=="string"&&p.trim()&&(Le[c.key]=p.trim())}else{let p=await m.text({message:c.message,placeholder:c.placeholder});w(p),typeof p=="string"&&p.trim()&&(Le[c.key]=p.trim())}}if(t){let c=[` Name: ${g.cyan(r)}`,` App name: ${g.cyan(n)}`,` Location: ${g.cyan(i)}`,` Frontend: ${g.cyan($e[o].label)}`,` Architecture: ${g.cyan(je[y].label)}`].join(`
4
+ `),p=[` Deploy target: ${g.cyan(z[s]?.label??"Node.js / Docker")}`,` Database: ${g.cyan($[f].label)}`,` Cache: ${g.cyan(H[S].label)}`,be.length>0?` Docker: ${g.cyan(be.map(ae=>ke[ae].label).join(", "))}`:` Docker: ${g.dim("none")}`].filter(Boolean).join(`
5
+ `),v=[I!=="none"?` Payment: ${g.cyan(qe[I].label)} (${oe})`:` Payment: ${g.dim("none")}`,` Credits: ${se?g.cyan("Yes"):g.dim("No")}`,` Email: ${g.cyan(Xe[C].label)}`,` Multi-tenancy: ${U?g.cyan("Yes")+` (billing: ${Ze[we].label})`:g.dim("No")}`,` Blog: ${Q?g.cyan("Yes"):g.dim("No")}`,` Docs app: ${u?g.cyan("Yes"):g.dim("No")}`,` Desktop app: ${b?g.cyan("Yes"):g.dim("No")}`,` Rev. sharing: ${W?g.cyan("Yes"):g.dim("No")}`,K.length>0?` Social login: ${g.cyan(K.map(ae=>q[ae].label).join(", "))}`:` Social login: ${g.dim("none")}`,At.length>0?` AI tools: ${g.cyan(At.map(ae=>Ue[ae].label).join(", "))}`:` AI tools: ${g.dim("none")}`].join(`
6
+ `),N=[g.bold("Project"),c,"",g.bold("Infrastructure"),p,"",g.bold("Features"),v];if(Je.length>0){let ae=Je.map(Qt=>{let Kn=Le[Qt.key]?g.green("provided"):g.dim("skipped");return` ${Qt.key}: ${Kn}`}).join(`
7
+ `);N.push("",g.bold("Credentials"),ae)}m.note(N.join(`
8
+ `),"Summary");let L=await m.confirm({message:"Proceed with these settings?"});(m.isCancel(L)||!L)&&(m.cancel("Setup cancelled."),process.exit(0))}return{projectName:r,appName:n,projectDir:i,frontend:o,architecture:y,deploymentTarget:s,databaseProvider:f,cacheProvider:S,paymentProvider:I,emailProvider:C,multiTenancy:U,billingScope:we,blog:Q,docs:u,desktop:b,revenueSharing:W,credits:se,dockerServices:be,aiTools:At,socialProviders:K,defaultCurrency:oe,...Object.keys(Le).length>0?{credentials:Le}:{},...e?.baseUrl!==void 0?{baseUrl:e.baseUrl}:{},...Tt!==void 0?{demo:Tt}:{}}}import{createReadStream as Qn}from"fs";import{mkdir as ei}from"fs/promises";import{Readable as ti}from"stream";import{pipeline as dr}from"stream/promises";import{extract as ri}from"tar";import{join as me}from"path";import{homedir as zn}from"os";var Be=process.env.GENERATESAAS_API_URL??"https://cli.generatesaas.com",V=".generatesaas",ee=me(V,"manifest.json"),ir=me(V,"hashes.json"),lt=me(V,"template-hashes.json"),pt=me(V,"template"),or=me(V,"staging"),sr=me(V,"staging.json"),X=me(zn(),".generatesaas");var x=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:Be}}async function te(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 x(i.status,o,s)}return i}import{existsSync as Hn,readFileSync as Yn,writeFileSync as Jn,mkdirSync as Wn}from"fs";import{dirname as qn}from"path";import*as re from"@clack/prompts";function Ge(){if(!Hn(X))return null;try{let e=JSON.parse(Yn(X,"utf-8"));return e.apiKey?e.apiKey:(e.token&&!e.apiKey&&re.log.warning(`Found old GitHub token in ${X}. Run 'generatesaas init' to set up your API key.`),null)}catch{return null}}function fe(e){Wn(qn(X),{recursive:!0}),Jn(X,JSON.stringify({apiKey:e},null," ")+`
9
+ `,{mode:384})}async function Ae(e){if(e?.apiKey)return e.apiKey;let t=process.env.GENERATESAAS_API_KEY;if(t)return t;let r=Ge();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 Ke()}async function Ke(){let e=await re.text({message:"Enter your GenerateSaaS API key:",placeholder:"gs_live_...",validate:t=>{if(!t?.trim())return"API key is required."}});return re.isCancel(e)&&(re.cancel("Setup cancelled."),process.exit(0)),e.trim()}async function ne(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 te(e,"/versions")).json()}async function _t(e,t){try{return await(await te(e,`/changelog/${encodeURIComponent(t)}`)).text()}catch(r){if(r instanceof x&&r.status===404)return null;throw r}}async function ar(e,t){return await(await te(e,`/skill/${encodeURIComponent(t)}`)).json()}function dt(e){if(!(e instanceof x)||e.status!==403)return null;let t=e.body;return!t||t.code!=="update_window_expired"?null:{message:e.message,lastAllowedVersion:t.lastAllowedVersion??null}}function cr(e){return{frontend:e.frontend,architecture:e.architecture,deployTarget:e.deploymentTarget,database:e.databaseProvider,cache:e.cacheProvider,payment:e.paymentProvider,email:e.emailProvider,multiTenancy:e.multiTenancy,billingScope:e.billingScope,blog:e.blog,docs:e.docs,desktop:e.desktop,credits:e.credits,revenueSharing:e.revenueSharing,socialProviders:e.socialProviders,aiTools:e.aiTools,currency:e.defaultCurrency}}async function Rt(e,t){return process.env.GENERATESAAS_OFFLINE_LICENSE==="1"?{token:"offline-test-token",licenseId:"offline-test-license-id"}:await(await te(e,"/license/sign",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).json()}async function lr(e,t){return await(await te(e,"/license/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).json()}async function xt(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 pr(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 Ot=new Set([".git","node_modules",".pnpm-store",".env",".env.test",".turbo",".nuxt",".output",".data","dist",".next",".svelte-kit",".wrangler",".devcontainer","playwright-report","test-results"]),Dt=new Set(["pnpm-lock.yaml"]);function Te(e){if(Ct(e))return!0;for(let t of e.split("/"))if(Dt.has(t))return!0;return!1}var Xn=new Set(["data","mksaas","references","scripts",".cursor",".agents",".codex",".generatesaas",".vscode",".mcp.json","README.md","TODO.md","OVERVIEW.md"]),Zn=["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 Ct(e){let t=e.split("/");for(let r of t)if(Ot.has(r))return!0;if(Xn.has(t[0]))return!0;for(let r of Zn)if(e===r||e.startsWith(r+"/"))return!0;return!1}async function ut(e,t,r){await ei(r,{recursive:!0});let n=process.env.GENERATESAAS_TEMPLATE_TARBALL;if(n){await dr(Qn(n),ur(r));return}let i=await te(e,`/template/${encodeURIComponent(t)}`);if(!i.body)throw new Error("Empty response body");let o=ti.fromWeb(i.body);await dr(o,ur(r))}function ur(e){return ri({cwd:e,strip:1,filter:t=>{let r=t.replace(/^[^/]+\//,"");return r?!Ct(r):!0},sync:!1})}import{readdir as ni,readFile as Nt,rm as fr,writeFile as Lt}from"fs/promises";import{join as Ie}from"path";var ii=["apps/web-nuxt/public/images/blog","apps/web-next/public/images/blog","packages/content/en/blog","packages/content/ro/blog"];async function gr(e){await Promise.all(ii.map(t=>fr(Ie(e,t),{recursive:!0,force:!0})))}var oi="packages/config/src/blog.ts";async function hr(e){let t=Ie(e,oi),r=await Nt(t,"utf-8"),n=mr(mr(r,"blogCategories",t),"blogAuthors",t);await Lt(t,n)}function mr(e,t,r){let n=new RegExp(`(export const ${t}\\b[^=]*=\\s*)\\[[\\s\\S]*?\\];`);if(!n.test(e))throw new Error(`emptyBlogConfig: could not find the \`export const ${t} = [...]\` declaration in ${r}. The boilerplate blog config may have been renamed or restructured; update the CLI strip in cleanup.ts.`);return e.replace(n,"$1[];")}async function yr(e){let t=Ie(e,"packages/i18n/translations"),r;try{r=await ni(t)}catch{return}for(let n of r){let i=Ie(t,n,"web.json"),o;try{o=await Nt(i,"utf-8")}catch{continue}let s=JSON.parse(o);!s.blog||s.blog.categories===void 0||(s.blog.categories={},await Lt(i,JSON.stringify(s,null," ")+`
10
+ `))}}async function vr(e,t){t.includes("claude-code")||await fr(Ie(e,".claude"),{recursive:!0,force:!0})}async function Sr(e,t){let r=Ie(e,".claude","settings.json"),n;try{n=await Nt(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=>si(o,t))),await Lt(r,JSON.stringify(i,null," ")+`
11
+ `)}function si(e,t){return!(e.startsWith("mcp__")||t!=="nuxt"&&e.includes("nuxt"))}import{join as Er}from"path";import{mkdir as ai,readdir as ci,rm as li,rmdir as pi,writeFile as di}from"fs/promises";import{dirname as mt,join as ui,relative as mi,sep as fi}from"path";function ge(e){return e.split(fi).join("/")}async function ft(e){await ai(e,{recursive:!0})}async function l(e,t){await ft(mt(e)),await di(e,t,"utf-8")}async function $t(e,t){await li(e,{force:!0});let r=mt(e);for(;r!==t&&r!==mt(r);){try{await pi(r)}catch{return}r=mt(r)}}async function he(e,t,r){let n=[],i=await ci(e,{withFileTypes:!0});for(let o of i){let s=ui(e,o.name),a=ge(mi(t,s));r(a)||(o.isDirectory()?n.push(...await he(s,t,r)):o.isFile()&&n.push(s))}return n}var gi={postgres:"Postgres",neon:"Neon (managed Postgres)",supabase:"Supabase (managed Postgres)"},hi={resend:"Resend",ses:"Amazon SES",smtp:"SMTP"},yi={redis:"Redis",upstash:"Upstash Redis"},vi={node:"Node.js / Docker",vercel:"Vercel"},Si={stripe:"Stripe",polar:"Polar"};function Ei(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=yi[e.cacheProvider],h=vi[e.deploymentTarget],y=e.paymentProvider==="none"?"":`
12
+ - **Payments:** ${Si[e.paymentProvider]}`,f=t?"`$t('key')` in templates (global helper); `useI18n()` from `vue-i18n` in `<script setup>` for `locale`/`setLocale`/`t()`.":"`useTranslations()` from `next-intl` in components; messages are loaded in `apps/web-next/i18n/request.ts`.",S=t?"**Navigation:** always use `localePath()` for paths (hardcoded paths break non-default locales); `await navigateTo()` in SSR.":"**Navigation:** `next/link` for links; `redirect()` from `next/navigation` for programmatic redirects in Server Components.";return`# AGENTS.md
13
13
 
14
14
  Guidelines for AI coding agents (Claude Code, Codex, Cursor, \u2026) in this project.
15
15
  Keep this file tight: add a rule only when it prevents a recurring mistake - too
@@ -37,11 +37,11 @@ flag, route, or translation is the most common and most costly mistake in this r
37
37
 
38
38
  - **Frontend:** ${r} ${s}
39
39
  - **API:** Hono, RPC-typed. ${a}
40
- - **Database:** Drizzle ORM + ${pi[e.databaseProvider]}
40
+ - **Database:** Drizzle ORM + ${gi[e.databaseProvider]}
41
41
  - **Cache + jobs:** ${d} + Inngest
42
- - **Auth:** Better Auth${h}
43
- - **Email:** ${di[e.emailProvider]}
44
- - **Deploy:** ${y}
42
+ - **Auth:** Better Auth${y}
43
+ - **Email:** ${hi[e.emailProvider]}
44
+ - **Deploy:** ${h}
45
45
 
46
46
  ## Where things live (extend these - don't reinvent)
47
47
 
@@ -81,12 +81,12 @@ In \`@repo/*\` backend packages prefer web-standard APIs: \`crypto.randomUUID()\
81
81
  ## This project
82
82
 
83
83
  Scaffolded from the GenerateSaaS boilerplate. To pull upstream boilerplate updates, ask your agent to **"update my GenerateSaaS project"**. To remove the license heartbeat + manifest: \`pnpm dlx generatesaas eject\`.
84
- `}async function Sr(e){await l(vr(e.projectDir,"AGENTS.md"),gi(e)),await l(vr(e.projectDir,"CLAUDE.md"),`@AGENTS.md
85
- `)}import{join as hi}from"path";var yi={postgres:"Postgres (self-hosted)",neon:"Neon (managed Postgres)",supabase:"Supabase (managed Postgres)"};function vi(e){let t=e.appName.trim()||e.projectName,r=e.frontend==="nuxt",n=r?"Nuxt 4":"Next.js 16",i=r?"apps/web-nuxt":"apps/web-next",o=yi[e.databaseProvider],s=Qe(e),a=e.architecture==="fullstack"?`${n} app at \`${i}/\` with the Hono API mounted inside it. A standalone \`apps/backend/\` is also included (inert in this fullstack setup) so you can split the API into a separate service later without re-scaffolding.`:`${n} app at \`${i}/\` and a separate Hono backend at \`apps/backend/\`.`,d=e.architecture==="fullstack"?`- App + API: http://localhost:3000
84
+ `}async function wr(e){await l(Er(e.projectDir,"AGENTS.md"),Ei(e)),await l(Er(e.projectDir,"CLAUDE.md"),`@AGENTS.md
85
+ `)}import{join as wi}from"path";var bi={postgres:"Postgres (self-hosted)",neon:"Neon (managed Postgres)",supabase:"Supabase (managed Postgres)"};function ki(e){let t=e.appName.trim()||e.projectName,r=e.frontend==="nuxt",n=r?"Nuxt 4":"Next.js 16",i=r?"apps/web-nuxt":"apps/web-next",o=bi[e.databaseProvider],s=Qe(e),a=e.architecture==="fullstack"?`${n} app at \`${i}/\` with the Hono API mounted inside it. A standalone \`apps/backend/\` is also included (inert in this fullstack setup) so you can split the API into a separate service later without re-scaffolding.`:`${n} app at \`${i}/\` and a separate Hono backend at \`apps/backend/\`.`,d=e.architecture==="fullstack"?`- App + API: http://localhost:3000
86
86
  - Inngest dev server: http://127.0.0.1:8288`:`- App: http://localhost:3000
87
87
  - API: http://localhost:3010
88
- - Inngest dev server: http://127.0.0.1:8288`,y=s?`pnpm infra # optional: starts local Docker services (Postgres / Redis / Inngest / Mailpit)
89
- `:"",h=e.deploymentTarget==="vercel"?"### Deployment\n\nDeploy to Vercel: `vercel deploy` (or connect the repo in the Vercel dashboard). Required environment variables are listed in `.env`.":`### Deployment
88
+ - Inngest dev server: http://127.0.0.1:8288`,h=s?`pnpm infra # optional: starts local Docker services (Postgres / Redis / Inngest / Mailpit)
89
+ `:"",y=e.deploymentTarget==="vercel"?"### Deployment\n\nDeploy to Vercel: `vercel deploy` (or connect the repo in the Vercel dashboard). Required environment variables are listed in `.env`.":`### Deployment
90
90
 
91
91
  This project ships with Dockerfiles for each app. Build images with \`docker build\` and deploy to your runtime of choice (Render / Fly.io / Railway / Coolify / Dokploy / your own VPS).${s?"\n\nThe `infra/` directory ships a Docker Compose file for the local-only services (Postgres / Redis / Inngest / Mailpit, filtered by your provider choices).":""}`,f=e.aiTools.length>0?"To pull the latest boilerplate changes into this project, open it in your AI coding agent and ask: `update my GenerateSaaS project`.":"To pull the latest boilerplate changes into this project, install an AI coding agent (Claude Code / Cursor / Codex / Gemini CLI / Windsurf) and ask: `update my GenerateSaaS project`. The skill bundle that drives the update lives under each tool's skill root.";return`# ${t}
92
92
 
@@ -108,7 +108,7 @@ pnpm install
108
108
  # A ready-to-run \`.env\` was generated for you - open it and fill in the
109
109
  # values marked \`# TODO\` (provider API keys). \`.env.example\` is the committed
110
110
  # reference. All apps load the single root \`.env\`.
111
- ${y}pnpm dev
111
+ ${h}pnpm dev
112
112
  \`\`\`
113
113
 
114
114
  ${d}
@@ -130,7 +130,7 @@ pnpm -F @repo/database studio # open Drizzle Studio
130
130
  pnpm auth:generate # regenerate Better Auth schema after config changes
131
131
  \`\`\`
132
132
 
133
- ${h}
133
+ ${y}
134
134
 
135
135
  ## Removing the GenerateSaaS license
136
136
 
@@ -143,11 +143,13 @@ pnpm dlx generatesaas eject
143
143
  ## Updates
144
144
 
145
145
  ${f}
146
- `}async function Er(e){await l(hi(e.projectDir,"README.md"),vi(e))}import{join as Si}from"path";function Ei(e){let t=e.split(".");return t.length>=3?t.slice(1).join("."):e}async function wr(e){let t=e.appName.replace(/\\/g,"\\\\").replace(/"/g,'\\"'),r=e.paymentProvider!=="none",n=e.baseUrl??"http://localhost:3000",i=e.baseUrl?new URL(e.baseUrl).hostname:"example.com",o=Ei(i),s=e.demo?"false":"true",a=e.demo?`
146
+ `}async function br(e){await l(wi(e.projectDir,"README.md"),ki(e))}import{join as gt}from"path";function Ai(e){let t=e.split(".");return t.length>=3?t.slice(1).join("."):e}function Ti(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"app"}function Ii(e,t){let r=Ti(e);return{appId:`${t.split(".").reverse().join(".")}.${r.replace(/-/g,"")}`,productName:e,protocol:r}}async function kr(e){let t=e.appName.replace(/\\/g,"\\\\").replace(/"/g,'\\"'),r=e.paymentProvider!=="none",n=e.baseUrl??"http://localhost:3000",i=e.baseUrl?new URL(e.baseUrl).hostname:"example.com",o=Ai(i),s=e.demo?"false":"true",a=e.demo?`
147
147
  support: {
148
148
  enableInDev: true,
149
149
  crisp: { websiteId: "7e221cec-ed61-46b7-b1b4-8cbc16557cca" }
150
- },`:"",d=e.frontend==="nextjs"&&e.architecture==="fullstack"?"false":"true",y=`import type { AppConfig } from "@repo/config/types";
150
+ },`:"",d=e.frontend==="nextjs"&&e.architecture==="fullstack"?"false":"true",h=Ii(t,o),y=`import type { AppConfig } from "@repo/config/types";
151
+ import { desktopConfig } from "./desktop.mjs";
152
+ import { tenancyConfig } from "./tenancy-flags.mjs";
151
153
 
152
154
  const trustedOrigins = process.env.TRUSTED_ORIGINS?.split(",").map((s) => s.trim()).filter(Boolean);
153
155
 
@@ -163,11 +165,7 @@ export const config: AppConfig = {
163
165
  },
164
166
  origins: trustedOrigins?.length ? trustedOrigins : ["http://localhost:3000", "http://localhost:5173"],
165
167
  waitlist: false,
166
- tenancy: ${e.multiTenancy?`{
167
- multiTenant: true,
168
- organizationLimit: 5,
169
- billingScope: "${e.billingScope}"
170
- }`:"{ multiTenant: false }"},
168
+ tenancy: tenancyConfig,
171
169
  theme: {
172
170
  default: "system",
173
171
  switcher: true,
@@ -256,7 +254,7 @@ export const config: AppConfig = {
256
254
  }`:`{
257
255
  base: "${e.defaultCurrency}",
258
256
  list: [
259
- { symbol: "${se[e.defaultCurrency].symbol}", name: "${se[e.defaultCurrency].name}", code: "${e.defaultCurrency}", place: "${se[e.defaultCurrency].place}", space: ${se[e.defaultCurrency].space} }
257
+ { symbol: "${ce[e.defaultCurrency].symbol}", name: "${ce[e.defaultCurrency].name}", code: "${e.defaultCurrency}", place: "${ce[e.defaultCurrency].place}", space: ${ce[e.defaultCurrency].space} }
260
258
  ],
261
259
  countryMap: {
262
260
  default: "${e.defaultCurrency}"
@@ -278,6 +276,7 @@ export const config: AppConfig = {
278
276
  url: "/docs",
279
277
  contentDir: "content/docs"
280
278
  },
279
+ desktop: desktopConfig,
281
280
  seo: {
282
281
  description: "Production-ready SaaS application",
283
282
  foundingDate: "${new Date().getFullYear()}-01-01"
@@ -309,8 +308,35 @@ export * from "./pricing";
309
308
  export * from "./roles";
310
309
  export * from "./section-tabs";
311
310
  export * from "./tenancy";
312
- `,h=Si(e.projectDir,"packages/config/src/index.ts");await l(h,y)}import{join as wi}from"path";function bi(e){return e==="stripe"?' stripePriceId: "",':' polarProductId: "",'}function Lt(e){let t=[];return e.withCredits&&(t.push(` credits: ${e.credits},`),t.push(" creditInterval: 30,")),t.push(` apiRateLimit: { maxRequests: ${e.rateLimit} }`),t.join(`
313
- `)}async function br(e){let t=wi(e.projectDir,"packages/config/src/pricing.ts"),r=e.defaultCurrency;if(e.paymentProvider==="none"){let f=`import type { PricingConfig } from "@repo/config/types";
311
+ `,f=gt(e.projectDir,"packages/config/src/index.ts");await l(f,y),await l(gt(e.projectDir,"packages/config/src/desktop.mjs"),`// Plain-JS desktop app identity. NO env reads, so tooling that cannot import the
312
+ // TypeScript config index reads it directly: the Electron renderer + main process
313
+ // (electron-vite bundles @repo/config/desktop) and electron-builder (Node ESM).
314
+ // Edit these values once to rebrand - they are the same in dev and production, so
315
+ // they live in committed config, not env. The config index re-exports this as
316
+ // config.desktop.
317
+
318
+ export const desktopConfig = {
319
+ enabled: ${e.desktop},
320
+ appId: "${h.appId}",
321
+ productName: "${h.productName}",
322
+ protocol: "${h.protocol}",
323
+ baseUrl: "${n}",
324
+ autoUpdate: { provider: "generic", url: "https://updates.${o}" }
325
+ };
326
+ `),await l(gt(e.projectDir,"packages/config/src/tenancy-flags.mjs"),`// Plain-JS tenancy flags. NO env reads, so tooling that cannot import the
327
+ // TypeScript config index reads it directly (the Electron renderer). Mirrors the
328
+ // desktop.mjs pattern. The config index re-exports this as config.tenancy.
329
+
330
+ export const tenancyConfig = ${e.multiTenancy?`{
331
+ multiTenant: true,
332
+ organizationLimit: 5,
333
+ billingScope: "${e.billingScope}"
334
+ }`:"{ multiTenant: false }"};
335
+ `),e.desktop&&await l(gt(e.projectDir,"apps/desktop/dev-app-update.yml"),`provider: generic
336
+ url: https://updates.${o}
337
+ updaterCacheDirName: ${h.protocol}-updater
338
+ `)}import{join as Pi}from"path";function _i(e){return e==="stripe"?' stripePriceId: "",':' polarProductId: "",'}function jt(e){let t=[];return e.withCredits&&(t.push(` credits: ${e.credits},`),t.push(" creditInterval: 30,")),t.push(` apiRateLimit: { maxRequests: ${e.rateLimit} }`),t.join(`
339
+ `)}async function Ar(e){let t=Pi(e.projectDir,"packages/config/src/pricing.ts"),r=e.defaultCurrency;if(e.paymentProvider==="none"){let f=`import type { PricingConfig } from "@repo/config/types";
314
340
 
315
341
  export const pricingConfig: PricingConfig = {
316
342
  defaultPlan: "free",
@@ -339,7 +365,7 @@ export const pricingConfig: PricingConfig = {
339
365
  credits: { enabled: false },
340
366
  products: { enabled: false, items: [] }
341
367
  };
342
- `;await l(t,f);return}let n=e.paymentProvider,i=bi(n),o=e.credits,s=Lt({credits:5,rateLimit:100,withCredits:o}),a=Lt({credits:10,rateLimit:1e3,withCredits:o}),d=Lt({credits:50,rateLimit:5e3,withCredits:o}),h=`import type { PricingConfig } from "@repo/config/types";
368
+ `;await l(t,f);return}let n=e.paymentProvider,i=_i(n),o=e.credits,s=jt({credits:5,rateLimit:100,withCredits:o}),a=jt({credits:10,rateLimit:1e3,withCredits:o}),d=jt({credits:50,rateLimit:5e3,withCredits:o}),y=`import type { PricingConfig } from "@repo/config/types";
343
369
 
344
370
  export const pricingConfig: PricingConfig = {
345
371
  defaultPlan: "free",
@@ -429,11 +455,11 @@ ${o?" credits: { enabled: true }":" credits: { enabled: false }"},
429
455
  items: []
430
456
  }
431
457
  };
432
- `;await l(t,h)}var Ai={smtp:[{key:"SMTP_HOST",defaultValue:"localhost"},{key:"SMTP_PORT",defaultValue:"1025"}],ses:[{key:"AMAZON_SES_REGION",comment:"# TODO: Configure Amazon SES credentials (e.g. us-east-1)"},{key:"AMAZON_SES_KEY"},{key:"AMAZON_SES_SECRET"}],resend:[{key:"RESEND_API_KEY",comment:"# TODO: Add your Resend API key"}]};function Ti(e){let t=W[e];return t.envVars.map((r,n)=>({key:r.name,...n===0?{comment:`# TODO: Add your ${t.label} OAuth credentials`}:{}}))}var ki={stripe:[{key:"STRIPE_SECRET_KEY",comment:"# TODO: Add your Stripe keys"},{key:"STRIPE_WEBHOOK_SECRET"}],polar:[{key:"POLAR_ACCESS_TOKEN",comment:"# TODO: Add your Polar keys"},{key:"POLAR_WEBHOOK_SECRET"}]};function Ie(e,t){return t?e.map(r=>{let n=t[r.key];return n?{...r,defaultValue:n,comment:void 0}:r}):e}function Pe(e,t){for(let r of e)r.comment&&t.push(r.comment),r.defaultValue!==void 0?t.push(`${r.key}=${r.defaultValue}`):t.push(`#${r.key}=`)}function $t(e){return Array.from(crypto.getRandomValues(new Uint8Array(e))).map(t=>t.toString(16).padStart(2,"0")).join("")}function Tr(e){return e.architecture==="fullstack"?{apiUrl:"http://localhost:3000/api",baseUrl:"http://localhost:3000"}:{apiUrl:"http://localhost:3010",baseUrl:"http://localhost:3000"}}function Ii(e){let{architecture:t,deploymentTarget:r}=e;return t==="fullstack"?r==="vercel"?{frontend:"https://your-app.vercel.app",backend:"https://your-app.vercel.app"}:r==="node"?{frontend:"https://your-app.example.com",backend:"https://your-app.example.com"}:null:{frontend:"https://your-app.example.com",backend:"https://your-app.example.com/api"}}function gt(e,t,r,n){e.push(n==="example"?`${t}=`:`${t}=${r}`)}function Ar(e,t){let r=t==="example"?void 0:e.credentials,{apiUrl:n,baseUrl:i}=Tr(e),o=[];e.architecture==="separate"?o.push("# API Configuration","# Standalone backend's own URL (the frontend reaches it via the public var above).",`API_URL=${n}`,`BASE_URL=${i}`):o.push("# App","# (API_URL is derived from the frontend's *_PUBLIC_API_URL by the runtime env schema.)",`BASE_URL=${i}`),o.push("","# Database"),Pe(Ie(N[e.databaseProvider].envVars,r),o),o.push("","# Cache"),Pe(Ie(G[e.cacheProvider].envVars,r),o),o.push("","# Authentication"),t==="example"&&o.push("# Generate a strong secret, e.g. `openssl rand -hex 32`"),gt(o,"BETTER_AUTH_SECRET",crypto.randomUUID(),t),o.push("","# Content API (random secret; required when contentApi feature is enabled in @repo/config)"),gt(o,"CONTENT_API_KEY",$t(32),t),o.push("","# Job Queue - Inngest","INNGEST_APP_ID=api"),gt(o,"INNGEST_EVENT_KEY",$t(32),t),gt(o,"INNGEST_SIGNING_KEY",$t(32),t),o.push("INNGEST_BASE_URL=http://127.0.0.1:8288"),e.architecture==="separate"&&(o.push("","# API Port (standalone backend)","API_PORT=3010"),o.push("","# CORS + cross-subdomain cookies (production only - leave unset for local dev)","# TRUSTED_ORIGINS=https://your-app.example.com","# AUTH_COOKIE_DOMAIN=.example.com"));let s=Ai[e.emailProvider];if(s&&(o.push("","# Email"),Pe(Ie(s,r),o)),e.paymentProvider!=="none"){let a=ki[e.paymentProvider];a&&(o.push("","# Payment"),Pe(Ie(a,r),o))}if(e.socialProviders.length>0){o.push("","# Social auth");for(let a of e.socialProviders)Pe(Ie(Ti(a),r),o)}return e.demo&&(o.push("","# Captcha (Cloudflare Turnstile)"),Pe(Ie([{key:"TURNSTILE_SECRET_KEY",comment:"# TODO: Add your Cloudflare Turnstile secret key"}],r),o)),o.push("","# Translations - OpenRouter (optional)","# Set to auto-translate en/* into your other locales with `pnpm translate`.","# The pre-commit hook runs it on commit; without a key it skips and untranslated","# locales fall back to the default locale. Get a key: https://openrouter.ai/keys","#OPENROUTER_API_KEY="),o.push(""),o.join(`
433
- `)}function Pi(e){let{apiUrl:t}=Tr(e),r=e.frontend==="nextjs"?"NEXT_PUBLIC_API_URL":"NUXT_PUBLIC_API_URL",n=["# API Configuration",`${r}=${t}`],i=Ii(e);return i&&e.architecture==="separate"&&n.push("","# Production (uncomment and replace with your deployed hostnames):",`# ${r}=${i.backend}`),n.push(""),n.join(`
434
- `)}async function kr(e){let t=Pi(e);await l(`${e.projectDir}/.env`,t+`
435
- `+Ar(e,"env")),await l(`${e.projectDir}/.env.example`,t+`
436
- `+Ar(e,"example"))}import{join as _i}from"path";async function Ir(e){let t=[...e.dockerServices];if(N[e.databaseProvider].managed&&(t=t.filter(o=>o!=="postgres")),G[e.cacheProvider].managed&&(t=t.filter(o=>o!=="redis")),t.length===0)return!1;let r=[],n=[];t.includes("postgres")&&(r.push(` postgres:
458
+ `;await l(t,y)}var Ri={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 xi(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 Oi={stripe:[{key:"STRIPE_SECRET_KEY",comment:"# TODO: Add your Stripe keys"},{key:"STRIPE_WEBHOOK_SECRET"}],polar:[{key:"POLAR_ACCESS_TOKEN",comment:"# TODO: Add your Polar keys"},{key:"POLAR_WEBHOOK_SECRET"}]};function Pe(e,t){return t?e.map(r=>{let n=t[r.key];return n?{...r,defaultValue:n,comment:void 0}:r}):e}function _e(e,t){for(let r of e)r.comment&&t.push(r.comment),r.defaultValue!==void 0?t.push(`${r.key}=${r.defaultValue}`):t.push(`#${r.key}=`)}function Ut(e){return Array.from(crypto.getRandomValues(new Uint8Array(e))).map(t=>t.toString(16).padStart(2,"0")).join("")}function Ir(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 ht(e,t,r,n){e.push(n==="example"?`${t}=`:`${t}=${r}`)}function Tr(e,t){let r=t==="example"?void 0:e.credentials,{apiUrl:n,baseUrl:i}=Ir(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"),_e(Pe($[e.databaseProvider].envVars,r),o),o.push("","# Cache"),_e(Pe(H[e.cacheProvider].envVars,r),o),o.push("","# Authentication"),t==="example"&&o.push("# Generate a strong secret, e.g. `openssl rand -hex 32`"),ht(o,"BETTER_AUTH_SECRET",crypto.randomUUID(),t),o.push("","# Content API (random secret; required when contentApi feature is enabled in @repo/config)"),ht(o,"CONTENT_API_KEY",Ut(32),t),o.push("","# Job Queue - Inngest","INNGEST_APP_ID=api"),ht(o,"INNGEST_EVENT_KEY",Ut(32),t),ht(o,"INNGEST_SIGNING_KEY",Ut(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=Ri[e.emailProvider];if(s&&(o.push("","# Email"),_e(Pe(s,r),o)),e.paymentProvider!=="none"){let a=Oi[e.paymentProvider];a&&(o.push("","# Payment"),_e(Pe(a,r),o))}if(e.socialProviders.length>0){o.push("","# Social auth");for(let a of e.socialProviders)_e(Pe(xi(a),r),o)}return e.demo&&(o.push("","# Captcha (Cloudflare Turnstile)"),_e(Pe([{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(`
459
+ `)}function Ci(e){let{apiUrl:t}=Ir(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(`
460
+ `)}async function Pr(e){let t=Ci(e);await l(`${e.projectDir}/.env`,t+`
461
+ `+Tr(e,"env")),await l(`${e.projectDir}/.env.example`,t+`
462
+ `+Tr(e,"example"))}import{join as Ni}from"path";async function _r(e){let t=[...e.dockerServices];if($[e.databaseProvider].managed&&(t=t.filter(o=>o!=="postgres")),H[e.cacheProvider].managed&&(t=t.filter(o=>o!=="redis")),t.length===0)return!1;let r=[],n=[];t.includes("postgres")&&(r.push(` postgres:
437
463
  image: postgres:18-alpine
438
464
  ports:
439
465
  - "\${POSTGRES_PORT:-5432}:5432"
@@ -477,8 +503,8 @@ ${r.join(`
477
503
  volumes:
478
504
  ${n.join(`
479
505
  `)}
480
- `),await l(_i(e.projectDir,"infra/docker-compose.yml"),i),!0}import{join as Ri}from"path";function Oi(e){let t=[];e.architecture==="separate"&&t.push({type:"node-terminal",request:"launch",name:"Backend",command:"pnpm dev",cwd:"${workspaceFolder}/apps/backend",skipFiles:["<node_internals>/**"],env:{NODE_ENV:"development"}});let r=e.frontend==="nextjs"?"Next.js":"Nuxt";if(t.push({type:"node-terminal",request:"launch",name:r,command:"pnpm dev",cwd:`\${workspaceFolder}/apps/web-${e.frontend==="nextjs"?"next":"nuxt"}`,skipFiles:["<node_internals>/**"],env:{NODE_ENV:"development"}}),e.docs&&t.push({type:"node-terminal",request:"launch",name:"Docs",command:"pnpm dev",cwd:"${workspaceFolder}/apps/docs",skipFiles:["<node_internals>/**"],env:{NODE_ENV:"development"}}),t.push({type:"node-terminal",request:"launch",name:"Inngest",command:"pnpm dev:inngest",cwd:"${workspaceFolder}",skipFiles:["<node_internals>/**"],env:{NODE_ENV:"development"}}),t.push({type:"node-terminal",request:"launch",name:"Mail",command:"pnpm dev:mail",cwd:"${workspaceFolder}",skipFiles:["<node_internals>/**"],env:{NODE_ENV:"development"}}),e.paymentProvider==="stripe"){let n=e.architecture==="separate"?"localhost:3010/auth/stripe/webhook":"localhost:3000/api/auth/stripe/webhook";t.push({type:"node-terminal",request:"launch",name:"Stripe",command:`stripe listen --forward-to ${n}`,cwd:"${workspaceFolder}",skipFiles:["<node_internals>/**"]})}return t}function xi(e){let t=e.frontend==="nextjs"?"Next.js":"Nuxt",r=[];return e.architecture==="separate"?r.push({name:`Dev (${t} + Backend + Inngest)`,configurations:["Backend",t,"Inngest"]}):r.push({name:`Dev (${t} + Inngest)`,configurations:[t,"Inngest"]}),r}async function Pr(e){let t={version:"0.2.0",configurations:Oi(e),compounds:xi(e)};await l(Ri(e.projectDir,".vscode/launch.json"),JSON.stringify(t,null," ")+`
481
- `)}import{join as Di}from"path";async function _r(e){if(e.architecture!=="separate")return;await l(Di(e.projectDir,"apps/backend/src/index.ts"),`import { serve } from "@hono/node-server";
506
+ `),await l(Ni(e.projectDir,"infra/docker-compose.yml"),i),!0}import{join as Li}from"path";function $i(e){let t=[];e.architecture==="separate"&&t.push({type:"node-terminal",request:"launch",name:"Backend",command:"pnpm dev",cwd:"${workspaceFolder}/apps/backend",skipFiles:["<node_internals>/**"],env:{NODE_ENV:"development"}});let r=e.frontend==="nextjs"?"Next.js":"Nuxt";if(t.push({type:"node-terminal",request:"launch",name:r,command:"pnpm dev",cwd:`\${workspaceFolder}/apps/web-${e.frontend==="nextjs"?"next":"nuxt"}`,skipFiles:["<node_internals>/**"],env:{NODE_ENV:"development"}}),e.docs&&t.push({type:"node-terminal",request:"launch",name:"Docs",command:"pnpm dev",cwd:"${workspaceFolder}/apps/docs",skipFiles:["<node_internals>/**"],env:{NODE_ENV:"development"}}),e.desktop){let n=e.architecture==="separate"?"http://localhost:3010":"http://localhost:3000/api";t.push({type:"node-terminal",request:"launch",name:"Desktop",command:"pnpm dev",cwd:"${workspaceFolder}/apps/desktop",skipFiles:["<node_internals>/**"],env:{NODE_ENV:"development",BASE_URL:"http://localhost:3000",PUBLIC_API_URL:n}})}if(t.push({type:"node-terminal",request:"launch",name:"Inngest",command:"pnpm dev:inngest",cwd:"${workspaceFolder}",skipFiles:["<node_internals>/**"],env:{NODE_ENV:"development"}}),t.push({type:"node-terminal",request:"launch",name:"Mail",command:"pnpm dev:mail",cwd:"${workspaceFolder}",skipFiles:["<node_internals>/**"],env:{NODE_ENV:"development"}}),e.paymentProvider==="stripe"){let n=e.architecture==="separate"?"localhost:3010/auth/stripe/webhook":"localhost:3000/api/auth/stripe/webhook";t.push({type:"node-terminal",request:"launch",name:"Stripe",command:`stripe listen --forward-to ${n}`,cwd:"${workspaceFolder}",skipFiles:["<node_internals>/**"]})}return t}function ji(e){let t=e.frontend==="nextjs"?"Next.js":"Nuxt",r=[];return e.architecture==="separate"?r.push({name:`Dev (${t} + Backend + Inngest)`,configurations:["Backend",t,"Inngest"]}):r.push({name:`Dev (${t} + Inngest)`,configurations:[t,"Inngest"]}),r}async function Rr(e){let t={version:"0.2.0",configurations:$i(e),compounds:ji(e)};await l(Li(e.projectDir,".vscode/launch.json"),JSON.stringify(t,null," ")+`
507
+ `)}import{join as Ui}from"path";async function xr(e){if(e.architecture!=="separate")return;await l(Ui(e.projectDir,"apps/backend/src/index.ts"),`import { serve } from "@hono/node-server";
482
508
  import app from "@repo/api";
483
509
  import { closeRedis, env, logger } from "@repo/runtime";
484
510
 
@@ -509,18 +535,18 @@ bootstrap().catch((error) => {
509
535
  logger.error("[Backend] Fatal error", error);
510
536
  process.exit(1);
511
537
  });
512
- `)}import{readFile as Ci}from"fs/promises";import{join as Ni}from"path";var Li=`export * from "./db/auth";
538
+ `)}import{readFile as Vi}from"fs/promises";import{join as Mi}from"path";var Fi=`export * from "./db/auth";
513
539
  export * from "./db/schema";
514
- export type { User, Account, Organization, Member } from "./db/auth";`;async function Rr(e){let t=Ni(e.projectDir,"packages/database/src/index.ts"),r=Li;try{let i=await Ci(t,"utf-8"),o=$i(i);o&&(r=o)}catch{}let n;switch(e.databaseProvider){case"postgres":n=Ui(r);break;case"neon":n=ji(r);break;case"supabase":n=Vi(r);break}await l(t,n)}function $i(e){return e.split(`
540
+ export type { User, Account, Organization, Member } from "./db/auth";`;async function Or(e){let t=Mi(e.projectDir,"packages/database/src/index.ts"),r=Fi;try{let i=await Vi(t,"utf-8"),o=Bi(i);o&&(r=o)}catch{}let n;switch(e.databaseProvider){case"postgres":n=Gi(r);break;case"neon":n=Ki(r);break;case"supabase":n=zi(r);break}await l(t,n)}function Bi(e){return e.split(`
515
541
  `).filter(n=>n.startsWith("export type ")||n.startsWith("export * from")).join(`
516
- `)}var Ut=`
542
+ `)}var Vt=`
517
543
  /** Extract affected-row count from a delete/update result (works for pg + postgres-js). */
518
544
  export function affectedRowCount(result: unknown): number {
519
545
  if (typeof result !== "object" || result === null) return 0;
520
546
  const r = result as { rowCount?: number; count?: number };
521
547
  return r.rowCount ?? r.count ?? 0;
522
548
  }
523
- `;function Ui(e){return`import { drizzle } from "drizzle-orm/node-postgres";
549
+ `;function Gi(e){return`import { drizzle } from "drizzle-orm/node-postgres";
524
550
  import { z } from "zod";
525
551
  import * as authSchema from "./db/auth";
526
552
  import * as appSchema from "./db/schema";
@@ -535,7 +561,7 @@ export const db = drizzle(parsed.data.DATABASE_URL, { schema });
535
561
  export const pool = db.$client;
536
562
 
537
563
  ${e}
538
- ${Ut}`}function ji(e){return`import { neon } from "@neondatabase/serverless";
564
+ ${Vt}`}function Ki(e){return`import { neon } from "@neondatabase/serverless";
539
565
  import { drizzle } from "drizzle-orm/neon-http";
540
566
  import { z } from "zod";
541
567
  import * as authSchema from "./db/auth";
@@ -551,7 +577,7 @@ const sql = neon(parsed.data.DATABASE_URL);
551
577
  export const db = drizzle(sql, { schema });
552
578
 
553
579
  ${e}
554
- ${Ut}`}function Vi(e){return`import { drizzle } from "drizzle-orm/postgres-js";
580
+ ${Vt}`}function zi(e){return`import { drizzle } from "drizzle-orm/postgres-js";
555
581
  import postgres from "postgres";
556
582
  import { z } from "zod";
557
583
  import * as authSchema from "./db/auth";
@@ -567,7 +593,7 @@ const client = postgres(parsed.data.DATABASE_URL);
567
593
  export const db = drizzle(client, { schema });
568
594
 
569
595
  ${e}
570
- ${Ut}`}import{join as ht}from"path";async function Or(e){switch(e.cacheProvider){case"redis":await Mi(e),await Fi(e);break;case"upstash":await Bi(e),await Gi(e);break}}async function Mi(e){await l(ht(e.projectDir,"packages/runtime/src/redis.ts"),`import type { Store } from "hono-rate-limiter";
596
+ ${Vt}`}import{join as yt}from"path";async function Dr(e){switch(e.cacheProvider){case"redis":await Hi(e),await Yi(e);break;case"upstash":await Ji(e),await Wi(e);break}}async function Hi(e){await l(yt(e.projectDir,"packages/runtime/src/redis.ts"),`import type { Store } from "hono-rate-limiter";
571
597
  import { Redis } from "ioredis";
572
598
  import { RedisStore, type RedisReply } from "rate-limit-redis";
573
599
  import { env } from "./env";
@@ -691,7 +717,7 @@ export async function closeRedis() {
691
717
  closed = true;
692
718
  }
693
719
  }
694
- `)}async function Fi(e){await l(ht(e.projectDir,"packages/runtime/src/mutex.ts"),`import { Mutex } from "redis-semaphore";
720
+ `)}async function Yi(e){await l(yt(e.projectDir,"packages/runtime/src/mutex.ts"),`import { Mutex } from "redis-semaphore";
695
721
  import { redis } from "./redis";
696
722
 
697
723
  export class MutexTimeoutError extends Error {
@@ -728,7 +754,7 @@ export async function withMutex<T>(
728
754
  await mutex.release();
729
755
  }
730
756
  }
731
- `)}async function Bi(e){await l(ht(e.projectDir,"packages/runtime/src/redis.ts"),`import { Redis } from "@upstash/redis";
757
+ `)}async function Ji(e){await l(yt(e.projectDir,"packages/runtime/src/redis.ts"),`import { Redis } from "@upstash/redis";
732
758
  import type { Store } from "hono-rate-limiter";
733
759
  import { env } from "./env";
734
760
 
@@ -841,7 +867,7 @@ export const limiterStore: Store = createLimiterStore();
841
867
 
842
868
  /** No persistent connection to close with Upstash REST. */
843
869
  export async function closeRedis(): Promise<void> {}
844
- `)}async function Gi(e){await l(ht(e.projectDir,"packages/runtime/src/mutex.ts"),`import { Lock } from "@upstash/lock";
870
+ `)}async function Wi(e){await l(yt(e.projectDir,"packages/runtime/src/mutex.ts"),`import { Lock } from "@upstash/lock";
845
871
  import { redis } from "./redis";
846
872
 
847
873
  export class MutexTimeoutError extends Error {
@@ -886,7 +912,7 @@ export async function withMutex<T>(
886
912
  await lock.release();
887
913
  }
888
914
  }
889
- `)}async function xr(e){await l(`${e.projectDir}/packages/runtime/src/env.ts`,Ki(e))}function Ki(e){return`import { z } from "zod";
915
+ `)}async function Cr(e){await l(`${e.projectDir}/packages/runtime/src/env.ts`,qi(e))}function qi(e){return`import { z } from "zod";
890
916
 
891
917
  const EnvSchema = z.object({
892
918
  NODE_ENV: z.enum(["development", "production"]).default("development"),
@@ -1004,13 +1030,13 @@ export const env = (() => {
1004
1030
  const parsedApiUrl = new URL(env.API_URL);
1005
1031
  export const apiBasePath = parsedApiUrl.pathname === "/" ? "" : parsedApiUrl.pathname;
1006
1032
  export const apiOrigin = parsedApiUrl.origin;
1007
- `}import{readFile as zi}from"fs/promises";import{join as K}from"path";var yt={"@upstash/redis":"^1.37.0","@upstash/lock":"^0.2.1","@neondatabase/serverless":"^1.0.1",postgres:"^3.4.7"};async function Re(e){let t=await zi(e,"utf-8");return JSON.parse(t)}async function Oe(e,t){await l(e,JSON.stringify(t,null," ")+`
1008
- `)}function Ke(e,t){for(let r of t)delete e.dependencies?.[r],delete e.devDependencies?.[r]}function _e(e,t,r,n=!1){let i=n?"devDependencies":"dependencies";e[i]||(e[i]={}),e[i][t]=r}async function Dr(e){await Hi(e),await Yi(e),await Ji(e),await Wi(e),e.frontend==="nextjs"?await Xi(e):await qi(e)}async function Hi(e){let t=K(e.projectDir,"packages/api/package.json"),r=await Re(t);Ke(r,["sharp","@types/sharp"]),await Oe(t,r)}async function Yi(e){let t=K(e.projectDir,"packages/runtime/package.json"),r=await Re(t);e.cacheProvider==="upstash"&&(Ke(r,["ioredis","rate-limit-redis","redis-semaphore"]),_e(r,"@upstash/redis",yt["@upstash/redis"]),_e(r,"@upstash/lock",yt["@upstash/lock"])),await Oe(t,r)}async function Ji(e){let t=K(e.projectDir,"packages/database/package.json"),r=await Re(t);e.databaseProvider==="neon"?(Ke(r,["pg","@types/pg"]),_e(r,"@neondatabase/serverless",yt["@neondatabase/serverless"])):e.databaseProvider==="supabase"&&(Ke(r,["pg","@types/pg"]),_e(r,"postgres",yt.postgres)),await Oe(t,r)}async function Wi(e){if(e.architecture!=="separate")return;let t=K(e.projectDir,"apps/backend/package.json"),r=await Re(t);e.deploymentTarget!=="node"&&Ke(r,["@hono/node-server"]),await Oe(t,r)}async function qi(e){if(e.architecture!=="separate")return;let t=K(e.projectDir,"apps/web-nuxt");await Nt(K(t,"server/api/[...paths].ts"),t);let r=K(t,"package.json"),n=await Re(r),i=n.dependencies?.["@repo/api"];i&&(delete n.dependencies?.["@repo/api"],_e(n,"@repo/api",i,!0)),await Oe(r,n)}async function Xi(e){if(e.architecture!=="separate")return;let t=K(e.projectDir,"apps/web-next");await Nt(K(t,"app/api/[[...rest]]/route.ts"),t);let r=K(t,"package.json"),n=await Re(r),i=n.dependencies?.["@repo/api"];i&&(delete n.dependencies?.["@repo/api"],_e(n,"@repo/api",i,!0)),await Oe(r,n)}import{readFile as Nr}from"fs/promises";import{join as Lr}from"path";async function $r(e){let t=Lr(e.projectDir,"turbo.json"),r;try{r=await Nr(t,"utf-8")}catch{return}let n=JSON.parse(r),i=n.tasks?.build;if(!i)return;let o=e.frontend==="nextjs"?"NUXT_PUBLIC_*":"NEXT_PUBLIC_*",s=e.frontend==="nextjs"?new Set([".nuxt/**",".output/**"]):new Set([".next/**","!.next/cache/**"]),a=!1;if(Array.isArray(i.env)){let d=i.env.filter(y=>y!==o);d.length!==i.env.length&&(i.env=d,a=!0)}if(Array.isArray(i.outputs)){let d=i.outputs.filter(y=>!s.has(y));d.length!==i.outputs.length&&(i.outputs=d,a=!0)}a&&await l(t,JSON.stringify(n,null," ")+`
1009
- `)}var Zi=["base.json","node.json","next.json"],Cr="GenerateSaaS ";async function Ur(e){if(!e.demo)for(let t of Zi){let r=Lr(e.projectDir,"tooling/typescript",t),n;try{n=await Nr(r,"utf-8")}catch{continue}let i=JSON.parse(n);typeof i.display!="string"||!i.display.startsWith(Cr)||(i.display=i.display.slice(Cr.length),await l(r,JSON.stringify(i,null," ")+`
1010
- `))}}import{readFile as Qi,rm as eo}from"fs/promises";import{existsSync as jr}from"fs";import{join as Vr}from"path";async function Mr(e){if(e.frontend==="nuxt")return;let t=Vr(e.projectDir,"packages/i18n/package.json");if(!jr(t))throw new Error(`pruneI18nNuxt: expected ${t} to exist - did the i18n package move?`);let r=JSON.parse(await Qi(t,"utf-8")),n=!!(r.exports?.["./module"]??r.exports?.["./nuxt"]),i=!1;if(r.exports)for(let s of["./module","./nuxt"])s in r.exports&&(delete r.exports[s],i=!0);r.devDependencies&&"@nuxt/kit"in r.devDependencies&&(delete r.devDependencies["@nuxt/kit"],i=!0),i&&await l(t,JSON.stringify(r,null," ")+`
1011
- `);let o=Vr(e.projectDir,"packages/i18n/nuxt");if(n&&!jr(o))throw new Error(`pruneI18nNuxt: packages/i18n declares a Nuxt export surface but ${o} is missing - did the i18n Nuxt module move?`);await eo(o,{recursive:!0,force:!0})}import{readFile as Fr,rm as to}from"fs/promises";import{join as jt}from"path";async function Br(e){e.cacheProvider==="upstash"&&await Promise.all([ro(e.projectDir),no(e.projectDir),to(jt(e.projectDir,"packages/runtime/tests/redis.test.ts"),{force:!0})])}async function ro(e){let t=jt(e,"packages/runtime/tests/setup.ts"),r;try{r=await Fr(t,"utf-8")}catch{return}let n=r.replace(/\tREDIS_URL:\s*"[^"]*",?\n/,` UPSTASH_REDIS_REST_URL: "https://test.upstash.io",
1033
+ `}import{readFile as Xi}from"fs/promises";import{join as Y}from"path";var vt={"@upstash/redis":"^1.37.0","@upstash/lock":"^0.2.1","@neondatabase/serverless":"^1.0.1",postgres:"^3.4.7"};async function xe(e){let t=await Xi(e,"utf-8");return JSON.parse(t)}async function Oe(e,t){await l(e,JSON.stringify(t,null," ")+`
1034
+ `)}function ze(e,t){for(let r of t)delete e.dependencies?.[r],delete e.devDependencies?.[r]}function Re(e,t,r,n=!1){let i=n?"devDependencies":"dependencies";e[i]||(e[i]={}),e[i][t]=r}async function Nr(e){await Zi(e),await Qi(e),await eo(e),await to(e),e.frontend==="nextjs"?await no(e):await ro(e)}async function Zi(e){let t=Y(e.projectDir,"packages/api/package.json"),r=await xe(t);ze(r,["sharp","@types/sharp"]),await Oe(t,r)}async function Qi(e){let t=Y(e.projectDir,"packages/runtime/package.json"),r=await xe(t);e.cacheProvider==="upstash"&&(ze(r,["ioredis","rate-limit-redis","redis-semaphore"]),Re(r,"@upstash/redis",vt["@upstash/redis"]),Re(r,"@upstash/lock",vt["@upstash/lock"])),await Oe(t,r)}async function eo(e){let t=Y(e.projectDir,"packages/database/package.json"),r=await xe(t);e.databaseProvider==="neon"?(ze(r,["pg","@types/pg"]),Re(r,"@neondatabase/serverless",vt["@neondatabase/serverless"])):e.databaseProvider==="supabase"&&(ze(r,["pg","@types/pg"]),Re(r,"postgres",vt.postgres)),await Oe(t,r)}async function to(e){if(e.architecture!=="separate")return;let t=Y(e.projectDir,"apps/backend/package.json"),r=await xe(t);e.deploymentTarget!=="node"&&ze(r,["@hono/node-server"]),await Oe(t,r)}async function ro(e){if(e.architecture!=="separate")return;let t=Y(e.projectDir,"apps/web-nuxt");await $t(Y(t,"server/api/[...paths].ts"),t);let r=Y(t,"package.json"),n=await xe(r),i=n.dependencies?.["@repo/api"];i&&(delete n.dependencies?.["@repo/api"],Re(n,"@repo/api",i,!0)),await Oe(r,n)}async function no(e){if(e.architecture!=="separate")return;let t=Y(e.projectDir,"apps/web-next");await $t(Y(t,"app/api/[[...rest]]/route.ts"),t);let r=Y(t,"package.json"),n=await xe(r),i=n.dependencies?.["@repo/api"];i&&(delete n.dependencies?.["@repo/api"],Re(n,"@repo/api",i,!0)),await Oe(r,n)}import{readFile as $r}from"fs/promises";import{join as jr}from"path";async function Ur(e){let t=jr(e.projectDir,"turbo.json"),r;try{r=await $r(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," ")+`
1035
+ `)}var io=["base.json","node.json","next.json"],Lr="GenerateSaaS ";async function Vr(e){if(!e.demo)for(let t of io){let r=jr(e.projectDir,"tooling/typescript",t),n;try{n=await $r(r,"utf-8")}catch{continue}let i=JSON.parse(n);typeof i.display!="string"||!i.display.startsWith(Lr)||(i.display=i.display.slice(Lr.length),await l(r,JSON.stringify(i,null," ")+`
1036
+ `))}}import{readFile as oo,rm as so}from"fs/promises";import{existsSync as Mr}from"fs";import{join as Fr}from"path";async function Br(e){if(e.frontend==="nuxt")return;let t=Fr(e.projectDir,"packages/i18n/package.json");if(!Mr(t))throw new Error(`pruneI18nNuxt: expected ${t} to exist - did the i18n package move?`);let r=JSON.parse(await oo(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," ")+`
1037
+ `);let o=Fr(e.projectDir,"packages/i18n/nuxt");if(n&&!Mr(o))throw new Error(`pruneI18nNuxt: packages/i18n declares a Nuxt export surface but ${o} is missing - did the i18n Nuxt module move?`);await so(o,{recursive:!0,force:!0})}import{readFile as Gr,rm as ao}from"fs/promises";import{join as Mt}from"path";async function Kr(e){e.cacheProvider==="upstash"&&await Promise.all([co(e.projectDir),lo(e.projectDir),ao(Mt(e.projectDir,"packages/runtime/tests/redis.test.ts"),{force:!0})])}async function co(e){let t=Mt(e,"packages/runtime/tests/setup.ts"),r;try{r=await Gr(t,"utf-8")}catch{return}let n=r.replace(/\tREDIS_URL:\s*"[^"]*",?\n/,` UPSTASH_REDIS_REST_URL: "https://test.upstash.io",
1012
1038
  UPSTASH_REDIS_REST_TOKEN: "test-token",
1013
- `);n!==r&&await l(t,n)}async function no(e){let t=jt(e,"packages/api/tests/setup.ts"),r;try{r=await Fr(t,"utf-8")}catch{return}let n=r;n=n.replace(/\tREDIS_URL:\s*"[^"]*",?\n/g,` UPSTASH_REDIS_REST_URL: "https://test.upstash.io",
1039
+ `);n!==r&&await l(t,n)}async function lo(e){let t=Mt(e,"packages/api/tests/setup.ts"),r;try{r=await Gr(t,"utf-8")}catch{return}let n=r;n=n.replace(/\tREDIS_URL:\s*"[^"]*",?\n/g,` UPSTASH_REDIS_REST_URL: "https://test.upstash.io",
1014
1040
  UPSTASH_REDIS_REST_TOKEN: "test-token",
1015
1041
  `),n=n.replace(/vi\.mock\("ioredis"[\s\S]*?\n\}\);\n\n?/,""),n=n.replace(/vi\.mock\("rate-limit-redis"[\s\S]*?\n\}\);\n\n?/,""),n=n.replace(/\t\t\tREDIS_URL:\s*"[^"]*",?\n/,` UPSTASH_REDIS_REST_URL: "https://test.upstash.io",
1016
1042
  UPSTASH_REDIS_REST_TOKEN: "test-token",
@@ -1031,8 +1057,8 @@ vi.mock("@upstash/lock", () => {
1031
1057
  return { Lock };
1032
1058
  });
1033
1059
 
1034
- $1`),n!==r&&await l(t,n)}import{readdir as io,readFile as oo,rm as ze}from"fs/promises";import{join as ge}from"path";var so=new Set(["ci.yml"]),ao=["cli","cli:clean","demo:bench","playground:regen","playground:test","playground:test:units"];async function Gr(e){let t=ge(e.projectDir,".github/workflows"),r=await io(t).catch(()=>[]);for(let n of r)so.has(n)||await ze(ge(t,n),{recursive:!0,force:!0})}async function Kr(e){let t=ge(e.projectDir,"package.json"),r=await oo(t,"utf-8"),n=JSON.parse(r),i=!1;if(n.scripts){for(let o of ao)o in n.scripts&&(delete n.scripts[o],i=!0);if(!Qe(e))for(let o of["infra","infra:stop"])o in n.scripts&&(delete n.scripts[o],i=!0)}i&&await l(t,JSON.stringify(n,null," ")+`
1035
- `)}async function zr(e){let t=e.frontend==="nextjs"?"apps/web-nuxt":"apps/web-next";await ze(ge(e.projectDir,t),{recursive:!0,force:!0})}async function Hr(e){e.docs||await ze(ge(e.projectDir,"apps/docs"),{recursive:!0})}async function Yr(e){let t=e.frontend==="nextjs"?"docs/nuxt":"docs/next";await ze(ge(e.projectDir,t),{recursive:!0,force:!0}),await ze(ge(e.projectDir,"docs/index.mdx"))}import{join as co}from"path";async function Jr(e){let t=lo(e);await l(co(e.projectDir,".github/workflows/ci.yml"),t)}function lo(e){let t=N[e.databaseProvider].managed,r=e.cacheProvider==="upstash",n=e.frontend==="nuxt",i=t?"":` services:
1060
+ $1`),n!==r&&await l(t,n)}import{readdir as zr,readFile as Hr,rm as O}from"fs/promises";import{join as k}from"path";var po=new Set(["ci.yml","desktop-release.yml"]),uo=["cli","cli:clean","demo:bench","playground:regen","playground:test","playground:test:units"];async function Yr(e){let t=k(e.projectDir,".github/workflows"),r=await zr(t).catch(()=>[]);for(let n of r)po.has(n)||await O(k(t,n),{recursive:!0,force:!0})}async function Jr(e){let t=k(e.projectDir,"package.json"),r=await Hr(t,"utf-8"),n=JSON.parse(r),i=!1;if(n.scripts){for(let o of uo)o in n.scripts&&(delete n.scripts[o],i=!0);if(!Qe(e))for(let o of["infra","infra:stop"])o in n.scripts&&(delete n.scripts[o],i=!0)}i&&await l(t,JSON.stringify(n,null," ")+`
1061
+ `)}async function Wr(e){let t=e.frontend==="nextjs"?"apps/web-nuxt":"apps/web-next";await O(k(e.projectDir,t),{recursive:!0,force:!0})}async function qr(e){e.docs||await O(k(e.projectDir,"apps/docs"),{recursive:!0})}async function Xr(e){let t=e.frontend==="nextjs"?"docs/nuxt":"docs/next";await O(k(e.projectDir,t),{recursive:!0,force:!0}),await O(k(e.projectDir,"docs/index.mdx"))}function mo(e,t,r){let n=e;for(let i of t){if(!new RegExp(`\\b${i}\\b`).test(n))throw new Error(`stripDesktopApp: expected "${i}" in ${r} (boilerplate drift)`);n=n.replace(new RegExp(`^\\s*${i},?\\r?\\n`,"m"),""),n=n.replace(new RegExp(`^\\s*${i}\\(\\),?\\r?\\n`,"m"),""),n=n.replace(new RegExp(`^\\s*${i}\\(\\{[\\s\\S]*?\\}\\),?\\r?\\n`,"m"),"")}return n}async function Zr(e){if(e.desktop)return;await O(k(e.projectDir,"apps/desktop"),{recursive:!0});let t=k(e.projectDir,"packages/auth/src/config.ts"),r=await Hr(t,"utf-8");await l(t,mo(r,["bearer"],"packages/auth/src/config.ts")),e.frontend==="nextjs"?(await O(k(e.projectDir,"apps/web-next/app/[locale]/(auth)/auth/device"),{recursive:!0}),await O(k(e.projectDir,"apps/web-next/components/auth/device-verify-form.tsx")),await O(k(e.projectDir,"apps/web-next/components/auth/device-approve-form.tsx")),await O(k(e.projectDir,"apps/web-next/e2e/device.spec.ts"),{force:!0})):(await O(k(e.projectDir,"apps/web-nuxt/app/pages/auth/device.vue")),await O(k(e.projectDir,"apps/web-nuxt/app/pages/auth/device"),{recursive:!0}),await O(k(e.projectDir,"apps/web-nuxt/e2e/device.spec.ts"),{force:!0}));let n=k(e.projectDir,"packages/i18n/translations");for(let i of await zr(n,{withFileTypes:!0}))i.isDirectory()&&await O(k(n,i.name,"desktop.json"),{force:!0});await O(k(e.projectDir,".github/workflows/desktop-release.yml"))}import{join as fo}from"path";async function Qr(e){let t=go(e);await l(fo(e.projectDir,".github/workflows/ci.yml"),t)}function go(e){let t=$[e.databaseProvider].managed,r=e.cacheProvider==="upstash",n=e.frontend==="nuxt",i=t?"":` services:
1036
1062
  postgres:
1037
1063
  image: postgres:18-alpine
1038
1064
  env:
@@ -1052,7 +1078,7 @@ $1`),n!==r&&await l(t,n)}import{readdir as io,readFile as oo,rm as ze}from"fs/pr
1052
1078
  STRIPE_SECRET_KEY: test
1053
1079
  STRIPE_WEBHOOK_SECRET: test`;case"polar":return`
1054
1080
  POLAR_ACCESS_TOKEN: test
1055
- POLAR_WEBHOOK_SECRET: test`;case"none":return"";default:{let f=e.paymentProvider;throw new Error(`buildCiYaml: unhandled payment provider "${String(f)}"`)}}})(),d=e.socialProviders.map(f=>W[f].envVars.map(S=>`
1081
+ POLAR_WEBHOOK_SECRET: test`;case"none":return"";default:{let f=e.paymentProvider;throw new Error(`buildCiYaml: unhandled payment provider "${String(f)}"`)}}})(),d=e.socialProviders.map(f=>q[f].envVars.map(S=>`
1056
1082
  ${S.name}: test`).join("")).join("");return`# Deployment is handled by the hosting platform (Vercel, Coolify, etc.)
1057
1083
  # which auto-deploys on push. CI runs in parallel as a quality gate.
1058
1084
  # For PR-based workflows, enable GitHub branch protection to require CI before merging.
@@ -1112,45 +1138,45 @@ ${i} env:
1112
1138
  run: echo "DATABASE_URL=${o}" > .env
1113
1139
 
1114
1140
  - run: pnpm test
1115
- `}import{readFile as Wr}from"fs/promises";import{existsSync as po}from"fs";import{join as Vt}from"path";var qr="@repo/database";function uo(e){return e?"pnpm -F @repo/database reset && pnpm -F @repo/database push --force":"pnpm -F @repo/database migrate"}function mo(e,t){switch(e){case"fullstack":return t==="nextjs"?"web-next":"web-nuxt";case"separate":return"backend";default:{let r=e;throw new Error(`schemaOwnerApp: unhandled architecture "${String(r)}"`)}}}async function fo(e,t,r){let n=await Wr(e,"utf-8"),i=JSON.parse(n),o=i.scripts?.[t];if(!o)throw new Error(`Cannot prepend to missing script "${t}" in ${e}`);o.includes(qr)||(i.scripts={...i.scripts,[t]:`${r} && ${o}`},await l(e,JSON.stringify(i,null," ")+`
1116
- `))}async function go(e,t){let r=po(e)?JSON.parse(await Wr(e,"utf-8")):{$schema:"https://openapi.vercel.sh/vercel.json"},n=r.buildCommand?.trim()||"pnpm build";n.includes(qr)||(r.buildCommand=`${t} && ${n}`,await l(e,JSON.stringify(r,null," ")+`
1117
- `))}async function Xr(e){let t=uo(e.demo===!0),r=mo(e.architecture,e.frontend),n=Vt(e.projectDir,"apps",r);switch(e.deploymentTarget){case"node":await fo(Vt(n,"package.json"),"start",t);return;case"vercel":await go(Vt(n,"vercel.json"),t);return;default:{let i=e.deploymentTarget;throw new Error(`generateDeployScripts: unhandled deployment target "${String(i)}"`)}}}import{readFile as ho}from"fs/promises";import{existsSync as yo}from"fs";import{join as vt}from"path";var vo=["stripe","polar"];async function Zr(e){let t=vt(e.projectDir,"packages/payments/src"),r=e.paymentProvider,n=`// Active payment provider. To switch, change the path below to
1141
+ `}import{readFile as en}from"fs/promises";import{existsSync as ho}from"fs";import{join as Ft}from"path";var tn="@repo/database";function yo(e){return e?"pnpm -F @repo/database reset && pnpm -F @repo/database push --force":"pnpm -F @repo/database migrate"}function vo(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 So(e,t,r){let n=await en(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(tn)||(i.scripts={...i.scripts,[t]:`${r} && ${o}`},await l(e,JSON.stringify(i,null," ")+`
1142
+ `))}async function Eo(e,t){let r=ho(e)?JSON.parse(await en(e,"utf-8")):{$schema:"https://openapi.vercel.sh/vercel.json"},n=r.buildCommand?.trim()||"pnpm build";n.includes(tn)||(r.buildCommand=`${t} && ${n}`,await l(e,JSON.stringify(r,null," ")+`
1143
+ `))}async function rn(e){let t=yo(e.demo===!0),r=vo(e.architecture,e.frontend),n=Ft(e.projectDir,"apps",r);switch(e.deploymentTarget){case"node":await So(Ft(n,"package.json"),"start",t);return;case"vercel":await Eo(Ft(n,"vercel.json"),t);return;default:{let i=e.deploymentTarget;throw new Error(`generateDeployScripts: unhandled deployment target "${String(i)}"`)}}}import{readFile as wo}from"fs/promises";import{existsSync as bo}from"fs";import{join as St}from"path";var ko=["stripe","polar"];async function nn(e){let t=St(e.projectDir,"packages/payments/src"),r=e.paymentProvider,n=`// Active payment provider. To switch, change the path below to
1118
1144
  // "./polar/index" or "./none/index" (other folders are kept in place).
1119
1145
  export { ops } from "./${r}/index";
1120
- `;await l(vt(t,"providers/index.ts"),n),await l(vt(t,"index.ts"),await So(t,r))}async function So(e,t){let r=vt(e,"index.ts");if(!yo(r))throw new Error(`generatePaymentBarrel: expected ${r} to exist - did packages/payments move?`);let n=await ho(r,"utf-8"),i=vo.filter(s=>s!==t);return n.split(`
1146
+ `;await l(St(t,"providers/index.ts"),n),await l(St(t,"index.ts"),await Ao(t,r))}async function Ao(e,t){let r=St(e,"index.ts");if(!bo(r))throw new Error(`generatePaymentBarrel: expected ${r} to exist - did packages/payments move?`);let n=await wo(r,"utf-8"),i=ko.filter(s=>s!==t);return n.split(`
1121
1147
  `).filter(s=>!i.some(a=>s.includes(`./providers/${a}/`))).join(`
1122
- `)}import{readdir as Eo,readFile as wo}from"fs/promises";import{join as Qr}from"path";async function en(e){if(e.demo)return;let t=e.appName.trim()||e.projectName,r=JSON.stringify(t).slice(1,-1),n=Qr(e.projectDir,"packages/i18n/translations"),i;try{i=await Eo(n)}catch{return}for(let o of i){let s=Qr(n,o,"web.json"),a;try{a=await wo(s,"utf-8")}catch{continue}let d=a.replaceAll("GenerateSaaS",r);d!==a&&await l(s,d)}}import{readFile as bo}from"fs/promises";import{join as tn}from"path";var Ao=[".nuxt/",".nuxt",".nitro/",".nitro",".output/",".output","_locales/"],To=[".next/",".next",".svelte-kit/",".svelte-kit",".wrangler/",".wrangler",".dev.vars"];async function nn(e){let r=e.frontend==="nuxt"?To:Ao;await rn(tn(e.projectDir,".gitignore"),r),await rn(tn(e.projectDir,".dockerignore"),r)}async function rn(e,t){let r;try{r=await bo(e,"utf-8")}catch{return}let n=new Set(t),i=r.split(`
1148
+ `)}import{readdir as To,readFile as Io}from"fs/promises";import{join as on}from"path";async function sn(e){if(e.demo)return;let t=e.appName.trim()||e.projectName,r=JSON.stringify(t).slice(1,-1),n=on(e.projectDir,"packages/i18n/translations"),i;try{i=await To(n)}catch{return}for(let o of i){let s=on(n,o,"web.json"),a;try{a=await Io(s,"utf-8")}catch{continue}let d=a.replaceAll("GenerateSaaS",r);d!==a&&await l(s,d)}}import{readFile as Po}from"fs/promises";import{join as an}from"path";var _o=[".nuxt/",".nuxt",".nitro/",".nitro",".output/",".output","_locales/"],Ro=[".next/",".next",".svelte-kit/",".svelte-kit",".wrangler/",".wrangler",".dev.vars"];async function ln(e){let r=e.frontend==="nuxt"?Ro:_o;await cn(an(e.projectDir,".gitignore"),r),await cn(an(e.projectDir,".dockerignore"),r)}async function cn(e,t){let r;try{r=await Po(e,"utf-8")}catch{return}let n=new Set(t),i=r.split(`
1123
1149
  `).filter(o=>!n.has(o.trim()));i.length!==r.split(`
1124
1150
  `).length&&await l(e,i.join(`
1125
- `))}async function St(e){let t=e.projectDir;e.demo||(await mr(t),await fr(t),await gr(t)),await hr(t,e.aiTools),await yr(t,e.frontend),await Sr(e),await Er(e),await wr(e),e.demo||await br(e),await kr(e);let r=await Ir(e);return await Pr(e),await _r(e),await Rr(e),await Or(e),await xr(e),await Dr(e),await $r(e),await Ur(e),await Mr(e),await Br(e),await Gr(e),await Jr(e),await Kr(e),await zr(e),await Hr(e),await Yr(e),await Xr(e),await Zr(e),await en(e),await nn(e),{dockerComposeGenerated:r}}import{basename as sn,join as an,relative as Io}from"path";import{createHash as on}from"crypto";import{readFile as ko}from"fs/promises";async function Et(e){let t=await ko(e);return on("sha256").update(t).digest("hex")}function Mt(e){return on("sha256").update(e).digest("hex")}var Po=new Set(["data",U]);function _o(e){let t=e.split("/");for(let r of t)if(Rt.has(r)||Ot.has(r)||Po.has(r)||r.startsWith(".env")&&!r.includes("example"))return!0;return!1}function cn(e,t){return{projectName:e.projectName??sn(t),appName:e.appName??e.projectName??sn(t),projectDir:t,frontend:e.frontend==="nextjs"?"nextjs":"nuxt",architecture:e.architecture??"fullstack",paymentProvider:e.paymentProvider??"none",emailProvider:e.emailProvider??"smtp",multiTenancy:e.multiTenancy??!1,billingScope:e.billingScope??"user",blog:e.blog??!1,docs:e.docs??!1,revenueSharing:e.revenueSharing??!1,credits:e.credits??!1,dockerServices:e.dockerServices??[],aiTools:e.aiTools??[],socialProviders:e.socialProviders??[],defaultCurrency:e.defaultCurrency??"USD",deploymentTarget:e.deploymentTarget??"node",databaseProvider:e.databaseProvider??"postgres",cacheProvider:e.cacheProvider??"redis",version:e.version,baseUrl:e.baseUrl,credentials:{},demo:!1}}async function ln(e,t){let n=(await fe(e.projectDir,e.projectDir,_o)).sort(),i=await Promise.all(n.map(async a=>[me(Io(e.projectDir,a)),await Et(a)])),o=Object.fromEntries(i),s={version:e.version,initialVersion:e.version,repo:"Duzbee/GenerateSaaS",appName:e.appName,projectName:e.projectName,frontend:e.frontend,architecture:e.architecture,paymentProvider:e.paymentProvider,emailProvider:e.emailProvider,multiTenancy:e.multiTenancy,billingScope:e.billingScope,blog:e.blog,docs:e.docs,credits:e.credits,revenueSharing:e.revenueSharing,defaultCurrency:e.defaultCurrency,dockerServices:e.dockerServices,socialProviders:e.socialProviders,deploymentTarget:e.deploymentTarget,databaseProvider:e.databaseProvider,cacheProvider:e.cacheProvider,aiTools:e.aiTools,...e.baseUrl?{baseUrl:e.baseUrl}:{},...t&&{licenseToken:t.token,licenseKeyHash:t.keyHash,installId:t.installId}};await l(an(e.projectDir,Q),JSON.stringify(s,null," ")+`
1126
- `),await l(an(e.projectDir,rr),JSON.stringify(o,null," ")+`
1127
- `)}import{relative as Ro}from"path";async function xe(e){let r=(await fe(e,e,Te)).sort(),n=await Promise.all(r.map(async i=>[me(Ro(e,i)),await Et(i)]));return Object.fromEntries(n)}import{copyFile as Oo,mkdir as xo,rm as Do}from"fs/promises";import{dirname as Co,join as pn,relative as No}from"path";import{existsSync as Lo}from"fs";async function Ft(e,t){Lo(t)&&await Do(t,{recursive:!0,force:!0});let r=await fe(e,e,Te);for(let n of r){let i=me(No(e,n)),o=pn(t,i);await xo(Co(o),{recursive:!0}),await Oo(n,o)}}async function dn(e,t){await Ft(e,pn(t,pt))}import{existsSync as $o}from"fs";import{readFile as un,readdir as Uo}from"fs/promises";import{join as z,dirname as jo,resolve as Vo,sep as Mo}from"path";import{fileURLToPath as Fo}from"url";var He={"claude-code":".claude/skills",cursor:".cursor/skills",codex:".agents/skills","gemini-cli":".gemini/skills",windsurf:".windsurf/skills"},Kl=Object.values(He),Bt="generatesaas-update",mn=jo(Fo(import.meta.url));function Bo(){let e=z(mn,"skill","content");return $o(e)?e:z(mn,"content")}function Gt(e){return!e||e.length===0?[]:e.map(t=>He[t])}async function Kt(e,t,r,n){let i=Gt(n);for(let o of i){let s=z(e,o,Bt),a=z(s,"scripts"),d=z(s,"references");await ft(a),await ft(d),await l(z(s,"SKILL.md"),t.replaceAll("__SKILL_ROOT__",o)),await l(z(d,".gitkeep"),"");for(let[y,h]of Object.entries(r)){let f=Vo(a,y);f.startsWith(a+Mo)&&await l(f,h)}}}async function fn(e,t){let r=Bo(),n=await un(z(r,"SKILL.md"),"utf-8"),i=z(r,"scripts"),o=await Uo(i),s={};for(let a of o)a!==".gitkeep"&&(s[a]=await un(z(i,a),"utf-8"));await Kt(e,n,s,t)}import{execFile as Go,execFileSync as Ko}from"child_process";import{access as gn,readFile as zo}from"fs/promises";import{join as zt}from"path";import*as P from"@clack/prompts";function ye(e){try{let t=process.platform==="win32"?"where":"which";return Ko(t,[e],{stdio:"ignore"}),!0}catch{return!1}}function he(e,t,r,n=3e5){return new Promise((i,o)=>{Go(e,t,{cwd:r,timeout:n},(s,a,d)=>{if(s){let y=String(a||"").trim(),f=[String(d||"").trim(),y].filter(Boolean).join(`
1151
+ `))}async function Et(e){let t=e.projectDir;e.demo||(await gr(t),await hr(t),await yr(t)),await vr(t,e.aiTools),await Sr(t,e.frontend),await wr(e),await br(e),await kr(e),e.demo||await Ar(e),await Pr(e);let r=await _r(e);return await Rr(e),await xr(e),await Or(e),await Dr(e),await Cr(e),await Nr(e),await Ur(e),await Vr(e),await Br(e),await Kr(e),await Yr(e),await Qr(e),await Jr(e),await Wr(e),await qr(e),await Xr(e),await Zr(e),await rn(e),await nn(e),await sn(e),await ln(e),{dockerComposeGenerated:r}}import{basename as dn,join as un,relative as Oo}from"path";import{createHash as pn}from"crypto";import{readFile as xo}from"fs/promises";async function wt(e){let t=await xo(e);return pn("sha256").update(t).digest("hex")}function Bt(e){return pn("sha256").update(e).digest("hex")}var Do=new Set(["data",V]);function Co(e){let t=e.split("/");for(let r of t)if(Ot.has(r)||Dt.has(r)||Do.has(r)||r.startsWith(".env")&&!r.includes("example"))return!0;return!1}function mn(e,t){return{projectName:e.projectName??dn(t),appName:e.appName??e.projectName??dn(t),projectDir:t,frontend:e.frontend==="nextjs"?"nextjs":"nuxt",architecture:e.architecture??"fullstack",paymentProvider:e.paymentProvider??"none",emailProvider:e.emailProvider??"smtp",multiTenancy:e.multiTenancy??!1,billingScope:e.billingScope??"user",blog:e.blog??!1,docs:e.docs??!1,desktop:e.desktop??!1,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 fn(e,t){let n=(await he(e.projectDir,e.projectDir,Co)).sort(),i=await Promise.all(n.map(async a=>[ge(Oo(e.projectDir,a)),await wt(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,desktop:e.desktop,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(un(e.projectDir,ee),JSON.stringify(s,null," ")+`
1152
+ `),await l(un(e.projectDir,ir),JSON.stringify(o,null," ")+`
1153
+ `)}import{relative as No}from"path";async function De(e){let r=(await he(e,e,Te)).sort(),n=await Promise.all(r.map(async i=>[ge(No(e,i)),await wt(i)]));return Object.fromEntries(n)}import{copyFile as Lo,mkdir as $o,rm as jo}from"fs/promises";import{dirname as Uo,join as gn,relative as Vo}from"path";import{existsSync as Mo}from"fs";async function Gt(e,t){Mo(t)&&await jo(t,{recursive:!0,force:!0});let r=await he(e,e,Te);for(let n of r){let i=ge(Vo(e,n)),o=gn(t,i);await $o(Uo(o),{recursive:!0}),await Lo(n,o)}}async function hn(e,t){await Gt(e,gn(t,pt))}import{existsSync as Fo}from"fs";import{readFile as yn,readdir as Bo}from"fs/promises";import{join as J,dirname as Go,resolve as Ko,sep as zo}from"path";import{fileURLToPath as Ho}from"url";var He={"claude-code":".claude/skills",cursor:".cursor/skills",codex:".agents/skills","gemini-cli":".gemini/skills",windsurf:".windsurf/skills"},Wl=Object.values(He),Kt="generatesaas-update",vn=Go(Ho(import.meta.url));function Yo(){let e=J(vn,"skill","content");return Fo(e)?e:J(vn,"content")}function zt(e){return!e||e.length===0?[]:e.map(t=>He[t])}async function Ht(e,t,r,n){let i=zt(n);for(let o of i){let s=J(e,o,Kt),a=J(s,"scripts"),d=J(s,"references");await ft(a),await ft(d),await l(J(s,"SKILL.md"),t.replaceAll("__SKILL_ROOT__",o)),await l(J(d,".gitkeep"),"");for(let[h,y]of Object.entries(r)){let f=Ko(a,h);f.startsWith(a+zo)&&await l(f,y)}}}async function Sn(e,t){let r=Yo(),n=await yn(J(r,"SKILL.md"),"utf-8"),i=J(r,"scripts"),o=await Bo(i),s={};for(let a of o)a!==".gitkeep"&&(s[a]=await yn(J(i,a),"utf-8"));await Ht(e,n,s,t)}import{execFile as Jo,execFileSync as Wo}from"child_process";import{access as En,readFile as qo}from"fs/promises";import{join as Yt}from"path";import*as _ from"@clack/prompts";function ve(e){try{let t=process.platform==="win32"?"where":"which";return Wo(t,[e],{stdio:"ignore"}),!0}catch{return!1}}function ye(e,t,r,n=3e5){return new Promise((i,o)=>{Jo(e,t,{cwd:r,timeout:n},(s,a,d)=>{if(s){let h=String(a||"").trim(),f=[String(d||"").trim(),h].filter(Boolean).join(`
1128
1154
  `);o(new Error(f?`${s.message}
1129
- ${f}`:s.message))}else i()})})}async function hn(e){if(!ye("pnpm"))return P.log.warn("pnpm not found. Skipping lockfile regeneration."),!1;try{return await he("pnpm",["install","--lockfile-only","--no-frozen-lockfile","--config.minimumReleaseAge=0"],e),!0}catch(t){let r=t instanceof Error?t.message:String(t);return P.log.warn(`Lockfile regeneration failed: ${r}`),P.log.warn("Deploys using --frozen-lockfile may fail."),!1}}async function yn(e){if(!ye("pnpm"))return P.log.warn("pnpm not found. Skipping dependency installation."),P.log.info("Install pnpm: https://pnpm.io/installation"),!1;let t=P.spinner();t.start("Installing dependencies (this may take a minute)...");try{return await he("pnpm",["install","--config.minimumReleaseAge=0"],e),t.stop("Dependencies installed."),!0}catch(r){t.stop("Dependency installation failed.");let n=r instanceof Error?r.message:String(r);return P.log.warn(`pnpm install failed: ${n}`),P.log.warn("You can run it manually later."),!1}}async function vn(e){if(!ye("pnpm"))return!1;let t=P.spinner();t.start("Generating baseline database migration...");try{return await he("pnpm",["-F","@repo/database","generate"],e),t.stop("Baseline migration generated."),!0}catch(r){t.stop("Baseline migration generation failed.");let n=r instanceof Error?r.message:String(r);return P.log.warn(`Could not generate baseline migration: ${n}`),P.log.warn("Run 'pnpm -F @repo/database generate' before your first deploy."),!1}}async function Sn(e){try{return await gn(zt(e,".git")),P.log.info("Git repository already exists, skipping init."),!0}catch{}if(!ye("git"))return P.log.warn("git not found. Skipping repository initialization."),!1;let t=P.spinner();t.start("Initializing git repository...");try{return await he("git",["init"],e),await he("git",["add","-A"],e),await he("git",["commit","--no-verify","-m","Initial commit from GenerateSaaS"],e),t.stop("Git repository initialized."),!0}catch{return t.stop("Git initialization failed."),P.log.warn("You can run git init manually later."),!1}}async function En(e){if(!ye("pnpm"))return!1;try{await gn(zt(e,".git"))}catch{return!1}try{let t=JSON.parse(await zo(zt(e,"package.json"),"utf-8")),r=!!t.devDependencies?.["simple-git-hooks"],n=!!t["simple-git-hooks"];if(!r||!n)return!1}catch{return!1}try{return await he("pnpm",["exec","simple-git-hooks"],e),!0}catch{return P.log.warn("Could not install git hooks. Run 'pnpm exec simple-git-hooks' manually."),!1}}import*as De from"@clack/prompts";import j from"picocolors";function wn(e,t){t.dockerComposeGenerated&&!t.dockerAvailable&&De.log.warn("Docker not found. Install Docker to run local services: https://docs.docker.com/get-docker/");let r=[];if(r.push(`cd ${e.projectDir}`),t.pnpmInstalled||r.push("pnpm install"),t.dockerComposeGenerated){let o=e.dockerServices.map(s=>be[s].label).join(", ");r.push(`pnpm infra ${j.dim(`# ${o}`)}`)}if(r.push(`pnpm dev ${j.dim("# http://localhost:3000")}`),t.skippedCredentials.length>0&&(r.push(""),r.push(j.dim("Fill in remaining TODO values in .env"))),De.note(r.join(`
1130
- `),j.yellow("Start Development")),t.dockerComposeGenerated){let o=[];o.push(`App ${j.cyan("http://localhost:3000")}`),e.architecture==="separate"&&o.push(`API ${j.cyan("http://localhost:3010")}`),e.dockerServices.includes("mailpit")&&o.push(`Mailpit ${j.cyan("http://localhost:8025")}`),e.dockerServices.includes("inngest")&&o.push(`Inngest ${j.cyan("http://localhost:8288")}`),De.note(o.join(`
1131
- `),j.yellow("Dev Tools"))}let n=[],i=Ho(e);i.length>0&&n.push(`Set in production: ${j.dim(i.join(", "))}`),n.push("pnpm db:push # Run database migrations"),n.push(Yo(e)),De.note(n.join(`
1132
- `),j.yellow("Deployment"))}function Ho(e){let t=["DATABASE_URL","BETTER_AUTH_SECRET"];return e.cacheProvider==="upstash"?t.push("UPSTASH_REDIS_REST_URL","UPSTASH_REDIS_REST_TOKEN"):t.push("REDIS_URL"),e.paymentProvider==="stripe"?t.push("STRIPE_SECRET_KEY","STRIPE_WEBHOOK_SECRET"):e.paymentProvider==="polar"&&t.push("POLAR_ACCESS_TOKEN","POLAR_WEBHOOK_SECRET"),e.emailProvider==="ses"?t.push("AMAZON_SES_REGION","AMAZON_SES_KEY","AMAZON_SES_SECRET"):e.emailProvider==="resend"?t.push("RESEND_API_KEY"):t.push("SMTP_HOST","SMTP_PORT"),t}function Yo(e){switch(e.deploymentTarget){case"node":return"Deploy with Docker or your preferred Node.js host";case"vercel":return"vercel deploy # Deploy to Vercel"}}function bn(e){let t={};if(e.name!==void 0){if(!ct(e.name))throw new Error(`Invalid project name "${e.name}". Use lowercase letters, numbers, and hyphens only. Must start with a letter.`);t.projectName=e.name}if(e.appName!==void 0){if(!e.appName.trim())throw new Error("App name cannot be empty.");t.appName=e.appName}if(e.location!==void 0?t.projectDir=e.location==="."?process.cwd():e.location:t.projectName!==void 0&&(t.projectDir=`./${t.projectName}`),e.frontend!==void 0){if(!Me.includes(e.frontend))throw new Error(`Invalid frontend "${e.frontend}". Valid values: ${Me.join(", ")}`);t.frontend=e.frontend}if(e.architecture!==void 0&&(t.architecture=e.architecture),e.payment!==void 0&&(t.paymentProvider=e.payment),e.email!==void 0&&(t.emailProvider=e.email),e.org!==void 0&&(t.multiTenancy=e.org),e.billingScope!==void 0){if(e.org===!1)throw new Error("--billing-scope requires --org to be enabled.");t.billingScope=e.billingScope}if(e.blog!==void 0&&(t.blog=e.blog),e.docs!==void 0&&(t.docs=e.docs),e.revenueSharing!==void 0&&(t.revenueSharing=e.revenueSharing),e.credits!==void 0){if(e.credits===!0&&e.payment==="none")throw new Error("--credits requires a payment provider (got --payment none).");t.credits=e.credits}if(e.docker!==void 0&&(t.dockerServices=Ht(e.docker,nt,"docker service")),e.aiTools!==void 0&&(t.aiTools=Ht(e.aiTools,it,"AI tool")),e.socialProviders!==void 0&&(t.socialProviders=Ht(e.socialProviders,st,"social provider")),e.currency!==void 0){if(!ae.includes(e.currency))throw new Error(`Invalid currency "${e.currency}". Valid values: ${ae.join(", ")}`);t.defaultCurrency=e.currency}if(e.deploy!==void 0){if(!ce.includes(e.deploy))throw new Error(`Invalid deployment target "${e.deploy}". Valid values: ${ce.join(", ")}`);t.deploymentTarget=e.deploy}if(e.database!==void 0){if(!le.includes(e.database))throw new Error(`Invalid database provider "${e.database}". Valid values: ${le.join(", ")}`);t.databaseProvider=e.database}if(e.cache!==void 0){if(!pe.includes(e.cache))throw new Error(`Invalid cache provider "${e.cache}". Valid values: ${pe.join(", ")}`);t.cacheProvider=e.cache}if(e.demo===!0&&(t.demo=!0),e.baseUrl!==void 0){let r=e.baseUrl.trim();if(r==="")throw new Error("--base-url cannot be empty. Provide an absolute URL like https://example.com.");let n;try{n=new URL(r)}catch{throw new Error(`Invalid --base-url "${e.baseUrl}". Must be an absolute URL like https://example.com.`)}if(n.protocol!=="http:"&&n.protocol!=="https:")throw new Error(`Invalid --base-url "${e.baseUrl}". Must use http or https.`);t.baseUrl=`${n.protocol}//${n.host}`}return t}var ne={projectName:"my-saas",frontend:"nextjs",architecture:"fullstack",paymentProvider:"stripe",emailProvider:"smtp",multiTenancy:!1,billingScope:"user",blog:!0,docs:!1,revenueSharing:!1,credits:!0,dockerServices:["postgres","redis","inngest"],aiTools:[],socialProviders:[],defaultCurrency:"USD",deploymentTarget:"node",databaseProvider:"postgres",cacheProvider:"redis"};function An(e){let t=e.projectName??ne.projectName,r=e.projectDir??`./${t}`,n=e.appName??at(t),i=e.deploymentTarget??ne.deploymentTarget,o=B[i]?.edgeRuntime??!1,s=e.databaseProvider??(o?"neon":ne.databaseProvider),a=e.cacheProvider??(o?"upstash":ne.cacheProvider),d=e.emailProvider??(o?"resend":ne.emailProvider),y=e.dockerServices??(o?ne.dockerServices.filter(f=>f!=="postgres"&&f!=="redis"):ne.dockerServices),h={...ne,...e,projectName:t,appName:n,projectDir:r,deploymentTarget:i,databaseProvider:s,cacheProvider:a,emailProvider:d,dockerServices:y};h.paymentProvider==="none"&&(h.credits=!1);for(let f of je){if(h.deploymentTarget!==f.target)continue;let S=h.databaseProvider===f.provider?"database":"cache";if(h.databaseProvider===f.provider||h.cacheProvider===f.provider)throw new Error(`Incompatible: --deploy ${f.target} + --${S} ${f.provider}. ${f.reason}`)}for(let f of Ve)if(h.architecture===f.architecture&&h.deploymentTarget===f.target)throw new Error(`Incompatible: --architecture ${f.architecture} + --deploy ${f.target}. ${f.reason}`);return h}function Ht(e,t,r){if(e.trim()==="")return[];let n=e.split(",").map(o=>o.trim()).filter(Boolean),i=n.filter(o=>!t.includes(o));if(i.length>0)throw new Error(`Invalid ${r}(s): ${i.join(", ")}. Valid values: ${t.join(", ")}`);return n}import Qo from"picocolors";var es="a10a6fb9d7cadde32e37dad52059d17b5d2b916b08c76d8fbcc99982e9a3d87f";function ts(e){if(e===void 0)return;let t=e.trim().replace(/^v/,"");if(!/^\d+\.\d+\.\d+$/.test(t))throw new Error(`Invalid template version "${e}". Use semver like 1.2.3.`);return t}function Tn(e){e.command("init").description("Scaffold a new GenerateSaaS project").argument("[apiKey]","license key (same as --api-key)").option("-n, --name <name>","project name (lowercase, hyphens, starts with letter)").option("--app-name <name>","display name for the app").option("-l, --location <path>","project directory (default: ./{name})").addOption(new V("--frontend <type>","frontend framework").choices([...Me])).addOption(new V("--architecture <type>","fullstack or separate").choices([...et])).addOption(new V("--payment <provider>","payment provider").choices([...tt])).addOption(new V("--email <provider>","email provider").choices([...rt])).option("--org","enable multi-tenancy (organizations)").option("--no-org","disable multi-tenancy").addOption(new V("--billing-scope <scope>","billing scope (requires --org)").choices([...ot])).option("--blog","enable blog").option("--no-blog","disable blog").option("--docs","include the docs app (apps/docs, Fumadocs)").option("--no-docs","exclude the docs app").option("--revenue-sharing","enable revenue sharing").option("--no-revenue-sharing","disable revenue sharing").option("--credits","enable credits system").option("--no-credits","disable credits system (subscription-only)").option("--docker <services>","comma-separated: postgres,redis,inngest,mailpit").option("--ai-tools <tools>","comma-separated: claude-code,cursor,codex,gemini-cli,windsurf").option("--social-providers <providers>","comma-separated: google,github,facebook,discord,x").addOption(new V("--currency <code>","default currency for billing").choices([...ae])).addOption(new V("--deploy <target>","deployment target").choices([...ce])).addOption(new V("--database <provider>","database provider").choices([...le])).addOption(new V("--cache <provider>","cache provider").choices([...pe])).option("--template-version <version>","specific template version to scaffold").option("--api-key <key>","API key (skips interactive prompt)").option("--base-url <url>","public base URL (e.g. https://example.com) - bakes into canonical/og/sitemap").option("-y, --yes","accept defaults for unspecified options (non-interactive)").addOption(new V("--demo","first-party demo build: keep sample content, mark site non-indexable - requires CI API key").hideHelp()).addOption(new V("--no-db-migration","skip generating the baseline DB migration (internal: demos/CI/playground)").hideHelp()).action(async(t,r)=>{await rs(t?{...r,apiKey:t}:r)})}async function rs(e){let t=performance.now();Zt("1.12.0");let r,n;try{r=bn(e),n=ts(e.templateVersion)}catch(u){E.cancel(I(u)),process.exit(1)}let i=E.spinner(),o;try{o=await Ae({apiKey:e.apiKey,prompt:!e.yes})}catch(u){E.cancel(I(u)),process.exit(1)}e.demo&&Mt(o)!==es&&(E.cancel("--demo is restricted to first-party demo deployments."),process.exit(1));let s=X(o),a=async()=>{let u=await re(s),b=u.latest,H=n??b;if(n&&!u.versions.some(Y=>Y.version===H))throw new Error(`Template version "${n}" is not available.`);return{latestVersion:b,selectedVersion:H}};i.start("Verifying access...");let d,y;try{({latestVersion:d,selectedVersion:y}=await a()),i.stop("Access verified."),ue(o)}catch(u){if(i.stop("Access verification failed."),u instanceof R&&u.status===401){e.yes&&(E.cancel("Invalid API key. Cannot prompt in non-interactive mode."),process.exit(1)),E.log.warning("Invalid API key."),o=await Ge(),s=X(o),i.start("Verifying access...");try{({latestVersion:d,selectedVersion:y}=await a()),i.stop("Access verified."),ue(o)}catch(b){i.stop("Access verification failed."),E.cancel(b instanceof R&&b.status===401?"Invalid API key.":I(b)),process.exit(1)}}else E.cancel(I(u)),process.exit(1)}E.log.success(`Latest version: ${d}`),y!==d&&E.log.success(`Using template version: ${y}`);let h;e.yes?h=An(r):h=await tr(r);let f;i.start("Activating license...");try{let u=crypto.randomUUID(),b=()=>({frontend:h.frontend,version:y,installId:u,projectName:h.projectName,options:sr(h)}),H;try{H=await Pt(s,b())}catch(Y){let J=dt(Y);if(!J?.lastAllowedVersion)throw Y;i.stop("License activation failed."),e.yes&&(E.cancel(`${J.message} Re-run with --template-version ${J.lastAllowedVersion}.`),process.exit(1));let we=await E.confirm({message:`Your update window has ended. Continue with v${J.lastAllowedVersion} (the last version your license covers)?`});(E.isCancel(we)||!we)&&(E.cancel("Setup cancelled."),process.exit(0)),y=J.lastAllowedVersion,i.start(`Activating license for v${y}...`),H=await Pt(s,b())}f={token:H.token,keyHash:Mt(o),installId:u},i.stop("License activated.")}catch(u){i.stop("License activation failed."),E.cancel(I(u)),process.exit(1)}let S=Zo(h.projectDir);if(Jo(S)&&Wo(S).length>0)if(e.yes)E.log.info(`Directory ${S} is not empty. Merging (keeping existing files, overwriting conflicts).`);else{let b=await E.select({message:`Directory ${S} is not empty.`,options:[{value:"merge",label:"Merge",hint:"keep existing files, overwrite conflicts"},{value:"overwrite",label:"Overwrite",hint:"delete everything and start fresh"},{value:"cancel",label:"Cancel"}]});(E.isCancel(b)||b==="cancel")&&(E.cancel("Setup cancelled."),process.exit(0)),b==="overwrite"&&qo(S,{recursive:!0,force:!0})}let k={...h,projectDir:S,version:y,...e.demo?{docs:!1}:{}};i.start("Downloading template...");try{await ut(s,y,S),i.stop("Template downloaded.")}catch(u){i.stop("Download failed."),E.cancel(I(u)),process.exit(1)}let ie;i.start("Generating project files...");try{if({dockerComposeGenerated:ie}=await St(k),!e.demo){let u=await xe(S);await l(Xo(S,lt),JSON.stringify(u,null," ")+`
1133
- `),await dn(S,S)}await fn(S,k.aiTools),await ln(k,f),i.stop("Project files generated.")}catch(u){i.stop("Generation failed."),E.cancel(I(u)),process.exit(1)}await hn(S);let x=await yn(S);x&&k.demo!==!0&&e.dbMigration!==!1&&await vn(S),await Sn(S),x&&await En(S);let $=ye("docker"),Z=kt(k).map(u=>u.key).filter(u=>!k.credentials?.[u]);wn(k,{pnpmInstalled:x,dockerComposeGenerated:ie,dockerAvailable:$,skippedCredentials:Z}),Qt(),E.log.info(Qo.dim(`Done in ${((performance.now()-t)/1e3).toFixed(1)}s`))}import{existsSync as In}from"fs";import{readFile as Pn}from"fs/promises";import{join as Ye,resolve as as}from"path";import*as _ from"@clack/prompts";import Ce from"picocolors";import{mkdtemp as ns,rm as is}from"fs/promises";import{tmpdir as os}from"os";import{join as ss}from"path";async function Yt(e,t,r,n){let i=await ns(ss(os(),"generatesaas-stage-"));try{await ut(e,t,i),await St({...r,projectDir:i}),await Ft(i,n)}finally{await is(i,{recursive:!0,force:!0})}}function kn(e){let r=(e.startsWith("v")?e.slice(1):e).match(/^(\d+)\.(\d+)\.(\d+)$/);return r?[Number(r[1]),Number(r[2]),Number(r[3])]:null}function wt(e,t){let r=kn(e),n=kn(t);if(!r||!n)return 0;for(let i=0;i<3;i++)if(r[i]!==n[i])return r[i]-n[i];return 0}function _n(e){e.command("update").description("Update AI skill files and stage template updates").option("--cwd <path>","project directory (default: current directory)").action(async t=>{let r=as(t.cwd??process.cwd()),n=Ye(r,Q),i;try{i=JSON.parse(await Pn(n,"utf-8"))}catch{_.cancel(".generatesaas/manifest.json not found. Run this from a GenerateSaaS project."),process.exit(1)}let o;try{o=await Ae()}catch(d){_.cancel(I(d)),process.exit(1)}let s=X(o),a=_.spinner();try{a.start("Verifying access...");let d;try{d=await re(s)}catch(u){throw u instanceof R&&u.status===401?new Error("Your saved API key was rejected. Run `generatesaas auth` to update it, or set GENERATESAAS_API_KEY."):u}a.stop("Access verified."),ue(o),a.start("Fetching latest skill files...");let y=await or(s,d.latest);await Kt(r,y.skillMd,y.scripts,i.aiTools);let h=Gt(i.aiTools);if(a.stop("Skills updated."),_.log.success(`Skill files installed to ${Ce.cyan(h.length.toString())} locations.`),i.version===d.latest){_.log.info(`Already on the latest version (${i.version}).`);return}if(i.licenseToken)try{let u=await ar(s,{currentToken:i.licenseToken,newVersion:d.latest});i.licenseToken=u.token,u.licenseKeyHash&&(i.licenseKeyHash=u.licenseKeyHash),await l(n,JSON.stringify(i,null," ")+`
1134
- `),_.log.success("License refreshed.")}catch(u){let b=dt(u);b&&(_.cancel(b.message),process.exit(1)),_.log.warn("License refresh skipped.")}let f=cn(i,r),S=Ye(r,nr);a.start(`Staging v${d.latest} (shaped for your config)...`),await Yt(s,d.latest,f,S),a.stop("Template staged.");let{text:k,title:ie}=await cs(s,d,i.version);k&&_.note(k,ie);let x=Ye(r,lt),$=Ye(r,pt),Ee=!In($),Z=!In(x);if(Ee){if(a.start("Building baseline template (one-time migration)..."),await Yt(s,i.version,f,$),Z){let u=await xe($);await l(x,JSON.stringify(u,null," ")+`
1135
- `)}if(a.stop("Baseline template stored."),!Z){let u=await ls(x,$);u>0&&_.log.warn(`Rebuilt baseline differs from the original for ${u} file(s) (the CLI's shaping evolved since this project was scaffolded). Classification still follows the committed template-hashes.json; upstream diffs for those files may include unrelated noise.`)}}else if(Z){a.start("Computing baseline template hashes...");let u=await xe($);await l(x,JSON.stringify(u,null," ")+`
1136
- `),a.stop("Baseline hashes computed.")}if(await l(Ye(r,ir),JSON.stringify({currentVersion:i.version,targetVersion:d.latest,changelog:k,stagedAt:new Date().toISOString()},null," ")+`
1137
- `),_.log.info(`Update staged: ${Ce.cyan(i.version)} \u2192 ${Ce.cyan(d.latest)}`),i.aiTools&&i.aiTools.length>0){let u=i.aiTools[0],b=Ue[u].label;_.log.info(`Open your project in ${Ce.cyan(b)} and ask: ${Ce.cyan("'update my GenerateSaaS project'")}`)}else _.log.info(`Ask your AI coding assistant to ${Ce.cyan("'update my GenerateSaaS project'")}.`)}catch(d){a.stop("Failed."),_.cancel(`Update failed: ${I(d)}`),process.exit(1)}})}async function cs(e,t,r){let n=t.latest,i=t.versions.filter(s=>wt(s.version,r)>0&&wt(s.version,n)<=0).sort((s,a)=>wt(s.version,a.version));if(i.length<=1)return{text:await It(e,n),title:`Changelog v${n}`};let o=[];for(let s of i){let a=null;try{a=await It(e,s.version)}catch{a=null}let d=s.date?` (${s.date.slice(0,10)})`:"",y=s.breaking?" [BREAKING]":"";o.push(`# v${s.version}${d}${y}
1155
+ ${f}`:s.message))}else i()})})}async function wn(e){if(!ve("pnpm"))return _.log.warn("pnpm not found. Skipping lockfile regeneration."),!1;try{return await ye("pnpm",["install","--lockfile-only","--no-frozen-lockfile","--config.minimumReleaseAge=0"],e),!0}catch(t){let r=t instanceof Error?t.message:String(t);return _.log.warn(`Lockfile regeneration failed: ${r}`),_.log.warn("Deploys using --frozen-lockfile may fail."),!1}}async function bn(e){if(!ve("pnpm"))return _.log.warn("pnpm not found. Skipping dependency installation."),_.log.info("Install pnpm: https://pnpm.io/installation"),!1;let t=_.spinner();t.start("Installing dependencies (this may take a minute)...");try{return await ye("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 _.log.warn(`pnpm install failed: ${n}`),_.log.warn("You can run it manually later."),!1}}async function kn(e){if(!ve("pnpm"))return!1;let t=_.spinner();t.start("Generating baseline database migration...");try{return await ye("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 _.log.warn(`Could not generate baseline migration: ${n}`),_.log.warn("Run 'pnpm -F @repo/database generate' before your first deploy."),!1}}async function An(e){try{return await En(Yt(e,".git")),_.log.info("Git repository already exists, skipping init."),!0}catch{}if(!ve("git"))return _.log.warn("git not found. Skipping repository initialization."),!1;let t=_.spinner();t.start("Initializing git repository...");try{return await ye("git",["init"],e),await ye("git",["add","-A"],e),await ye("git",["commit","--no-verify","-m","Initial commit from GenerateSaaS"],e),t.stop("Git repository initialized."),!0}catch{return t.stop("Git initialization failed."),_.log.warn("You can run git init manually later."),!1}}async function Tn(e){if(!ve("pnpm"))return!1;try{await En(Yt(e,".git"))}catch{return!1}try{let t=JSON.parse(await qo(Yt(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 ye("pnpm",["exec","simple-git-hooks"],e),!0}catch{return _.log.warn("Could not install git hooks. Run 'pnpm exec simple-git-hooks' manually."),!1}}import*as Ce from"@clack/prompts";import M from"picocolors";function In(e,t){t.dockerComposeGenerated&&!t.dockerAvailable&&Ce.log.warn("Docker not found. Install Docker to run local services: https://docs.docker.com/get-docker/");let r=[];if(r.push(`cd ${e.projectDir}`),t.pnpmInstalled||r.push("pnpm install"),t.dockerComposeGenerated){let o=e.dockerServices.map(s=>ke[s].label).join(", ");r.push(`pnpm infra ${M.dim(`# ${o}`)}`)}if(r.push(`pnpm dev ${M.dim("# http://localhost:3000")}`),t.skippedCredentials.length>0&&(r.push(""),r.push(M.dim("Fill in remaining TODO values in .env"))),Ce.note(r.join(`
1156
+ `),M.yellow("Start Development")),t.dockerComposeGenerated){let o=[];o.push(`App ${M.cyan("http://localhost:3000")}`),e.architecture==="separate"&&o.push(`API ${M.cyan("http://localhost:3010")}`),e.dockerServices.includes("mailpit")&&o.push(`Mailpit ${M.cyan("http://localhost:8025")}`),e.dockerServices.includes("inngest")&&o.push(`Inngest ${M.cyan("http://localhost:8288")}`),Ce.note(o.join(`
1157
+ `),M.yellow("Dev Tools"))}let n=[],i=Xo(e);i.length>0&&n.push(`Set in production: ${M.dim(i.join(", "))}`),n.push("pnpm db:push # Run database migrations"),n.push(Zo(e)),Ce.note(n.join(`
1158
+ `),M.yellow("Deployment"))}function Xo(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 Zo(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 Pn(e){let t={};if(e.name!==void 0){if(!ct(e.name))throw new Error(`Invalid project name "${e.name}". Use lowercase letters, numbers, and hyphens only. Must start with a letter.`);t.projectName=e.name}if(e.appName!==void 0){if(!e.appName.trim())throw new Error("App name cannot be empty.");t.appName=e.appName}if(e.location!==void 0?t.projectDir=e.location==="."?process.cwd():e.location:t.projectName!==void 0&&(t.projectDir=`./${t.projectName}`),e.frontend!==void 0){if(!Fe.includes(e.frontend))throw new Error(`Invalid frontend "${e.frontend}". Valid values: ${Fe.join(", ")}`);t.frontend=e.frontend}if(e.architecture!==void 0&&(t.architecture=e.architecture),e.payment!==void 0&&(t.paymentProvider=e.payment),e.email!==void 0&&(t.emailProvider=e.email),e.org!==void 0&&(t.multiTenancy=e.org),e.billingScope!==void 0){if(e.org===!1)throw new Error("--billing-scope requires --org to be enabled.");t.billingScope=e.billingScope}if(e.blog!==void 0&&(t.blog=e.blog),e.docs!==void 0&&(t.docs=e.docs),e.desktop!==void 0&&(t.desktop=e.desktop),e.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,nt,"docker service")),e.aiTools!==void 0&&(t.aiTools=Jt(e.aiTools,it,"AI tool")),e.socialProviders!==void 0&&(t.socialProviders=Jt(e.socialProviders,st,"social provider")),e.currency!==void 0){if(!le.includes(e.currency))throw new Error(`Invalid currency "${e.currency}". Valid values: ${le.join(", ")}`);t.defaultCurrency=e.currency}if(e.deploy!==void 0){if(!pe.includes(e.deploy))throw new Error(`Invalid deployment target "${e.deploy}". Valid values: ${pe.join(", ")}`);t.deploymentTarget=e.deploy}if(e.database!==void 0){if(!de.includes(e.database))throw new Error(`Invalid database provider "${e.database}". Valid values: ${de.join(", ")}`);t.databaseProvider=e.database}if(e.cache!==void 0){if(!ue.includes(e.cache))throw new Error(`Invalid cache provider "${e.cache}". Valid values: ${ue.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 ie={projectName:"my-saas",frontend:"nextjs",architecture:"fullstack",paymentProvider:"stripe",emailProvider:"smtp",multiTenancy:!1,billingScope:"user",blog:!0,docs:!1,desktop:!1,revenueSharing:!1,credits:!0,dockerServices:["postgres","redis","inngest"],aiTools:[],socialProviders:[],defaultCurrency:"USD",deploymentTarget:"node",databaseProvider:"postgres",cacheProvider:"redis"};function _n(e){let t=e.projectName??ie.projectName,r=e.projectDir??`./${t}`,n=e.appName??at(t),i=e.deploymentTarget??ie.deploymentTarget,o=z[i]?.edgeRuntime??!1,s=e.databaseProvider??(o?"neon":ie.databaseProvider),a=e.cacheProvider??(o?"upstash":ie.cacheProvider),d=e.emailProvider??(o?"resend":ie.emailProvider),h=e.dockerServices??(o?ie.dockerServices.filter(f=>f!=="postgres"&&f!=="redis"):ie.dockerServices),y={...ie,...e,projectName:t,appName:n,projectDir:r,deploymentTarget:i,databaseProvider:s,cacheProvider:a,emailProvider:d,dockerServices:h};y.paymentProvider==="none"&&(y.credits=!1);for(let f of Ve){if(y.deploymentTarget!==f.target)continue;let S=y.databaseProvider===f.provider?"database":"cache";if(y.databaseProvider===f.provider||y.cacheProvider===f.provider)throw new Error(`Incompatible: --deploy ${f.target} + --${S} ${f.provider}. ${f.reason}`)}for(let f of Me)if(y.architecture===f.architecture&&y.deploymentTarget===f.target)throw new Error(`Incompatible: --architecture ${f.architecture} + --deploy ${f.target}. ${f.reason}`);return y}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 is from"picocolors";var os="a10a6fb9d7cadde32e37dad52059d17b5d2b916b08c76d8fbcc99982e9a3d87f";function ss(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 Rn(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 F("--frontend <type>","frontend framework").choices([...Fe])).addOption(new F("--architecture <type>","fullstack or separate").choices([...et])).addOption(new F("--payment <provider>","payment provider").choices([...tt])).addOption(new F("--email <provider>","email provider").choices([...rt])).option("--org","enable multi-tenancy (organizations)").option("--no-org","disable multi-tenancy").addOption(new F("--billing-scope <scope>","billing scope (requires --org)").choices([...ot])).option("--blog","enable blog").option("--no-blog","disable blog").option("--docs","include the docs app (apps/docs, Fumadocs)").option("--no-docs","exclude the docs app").option("--desktop","include the Electron desktop app (apps/desktop)").option("--no-desktop","exclude the Electron desktop 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 F("--currency <code>","default currency for billing").choices([...le])).addOption(new F("--deploy <target>","deployment target").choices([...pe])).addOption(new F("--database <provider>","database provider").choices([...de])).addOption(new F("--cache <provider>","cache provider").choices([...ue])).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 F("--demo","first-party demo build: keep sample content, mark site non-indexable - requires CI API key").hideHelp()).addOption(new F("--no-db-migration","skip generating the baseline DB migration (internal: demos/CI/playground)").hideHelp()).action(async(t,r)=>{await as(t?{...r,apiKey:t}:r)})}async function as(e){let t=performance.now();er("1.13.0");let r,n;try{r=Pn(e),n=ss(e.templateVersion)}catch(u){E.cancel(P(u)),process.exit(1)}let i=E.spinner(),o;try{o=await Ae({apiKey:e.apiKey,prompt:!e.yes})}catch(u){E.cancel(P(u)),process.exit(1)}e.demo&&Bt(o)!==os&&(E.cancel("--demo is restricted to first-party demo deployments."),process.exit(1));let s=Z(o),a=async()=>{let u=await ne(s),b=u.latest,W=n??b;if(n&&!u.versions.some(se=>se.version===W))throw new Error(`Template version "${n}" is not available.`);return{latestVersion:b,selectedVersion:W}};i.start("Verifying access...");let d,h;try{({latestVersion:d,selectedVersion:h}=await a()),i.stop("Access verified."),fe(o)}catch(u){if(i.stop("Access verification failed."),u instanceof x&&u.status===401){e.yes&&(E.cancel("Invalid API key. Cannot prompt in non-interactive mode."),process.exit(1)),E.log.warning("Invalid API key."),o=await Ke(),s=Z(o),i.start("Verifying access...");try{({latestVersion:d,selectedVersion:h}=await a()),i.stop("Access verified."),fe(o)}catch(b){i.stop("Access verification failed."),E.cancel(b instanceof x&&b.status===401?"Invalid API key.":P(b)),process.exit(1)}}else E.cancel(P(u)),process.exit(1)}E.log.success(`Latest version: ${d}`),h!==d&&E.log.success(`Using template version: ${h}`);let y;e.yes?y=_n(r):y=await nr(r);let f;i.start("Activating license...");try{let u=crypto.randomUUID(),b=()=>({frontend:y.frontend,version:h,installId:u,projectName:y.projectName,options:cr(y)}),W;try{W=await Rt(s,b())}catch(se){let K=dt(se);if(!K?.lastAllowedVersion)throw se;i.stop("License activation failed."),e.yes&&(E.cancel(`${K.message} Re-run with --template-version ${K.lastAllowedVersion}.`),process.exit(1));let be=await E.confirm({message:`Your update window has ended. Continue with v${K.lastAllowedVersion} (the last version your license covers)?`});(E.isCancel(be)||!be)&&(E.cancel("Setup cancelled."),process.exit(0)),h=K.lastAllowedVersion,i.start(`Activating license for v${h}...`),W=await Rt(s,b())}f={token:W.token,keyHash:Bt(o),installId:u},i.stop("License activated.")}catch(u){i.stop("License activation failed."),E.cancel(P(u)),process.exit(1)}let S=ns(y.projectDir);if(Qo(S)&&es(S).length>0)if(e.yes)E.log.info(`Directory ${S} is not empty. Merging (keeping existing files, overwriting conflicts).`);else{let b=await E.select({message:`Directory ${S} is not empty.`,options:[{value:"merge",label:"Merge",hint:"keep existing files, overwrite conflicts"},{value:"overwrite",label:"Overwrite",hint:"delete everything and start fresh"},{value:"cancel",label:"Cancel"}]});(E.isCancel(b)||b==="cancel")&&(E.cancel("Setup cancelled."),process.exit(0)),b==="overwrite"&&ts(S,{recursive:!0,force:!0})}let I={...y,projectDir:S,version:h,...e.demo?{docs:!1}:{}};i.start("Downloading template...");try{await ut(s,h,S),i.stop("Template downloaded.")}catch(u){i.stop("Download failed."),E.cancel(P(u)),process.exit(1)}let oe;i.start("Generating project files...");try{if({dockerComposeGenerated:oe}=await Et(I),!e.demo){let u=await De(S);await l(rs(S,lt),JSON.stringify(u,null," ")+`
1159
+ `),await hn(S,S)}await Sn(S,I.aiTools),await fn(I,f),i.stop("Project files generated.")}catch(u){i.stop("Generation failed."),E.cancel(P(u)),process.exit(1)}await wn(S);let C=await bn(S);C&&I.demo!==!0&&e.dbMigration!==!1&&await kn(S),await An(S),C&&await Tn(S);let U=ve("docker"),Q=Pt(I).map(u=>u.key).filter(u=>!I.credentials?.[u]);In(I,{pnpmInstalled:C,dockerComposeGenerated:oe,dockerAvailable:U,skippedCredentials:Q}),tr(),E.log.info(is.dim(`Done in ${((performance.now()-t)/1e3).toFixed(1)}s`))}import{existsSync as On}from"fs";import{readFile as Dn}from"fs/promises";import{join as Ye,resolve as us}from"path";import*as R from"@clack/prompts";import Ne from"picocolors";import{mkdtemp as cs,rm as ls}from"fs/promises";import{tmpdir as ps}from"os";import{join as ds}from"path";async function Wt(e,t,r,n){let i=await cs(ds(ps(),"generatesaas-stage-"));try{await ut(e,t,i),await Et({...r,projectDir:i}),await Gt(i,n)}finally{await ls(i,{recursive:!0,force:!0})}}function xn(e){let r=(e.startsWith("v")?e.slice(1):e).match(/^(\d+)\.(\d+)\.(\d+)$/);return r?[Number(r[1]),Number(r[2]),Number(r[3])]:null}function bt(e,t){let r=xn(e),n=xn(t);if(!r||!n)return 0;for(let i=0;i<3;i++)if(r[i]!==n[i])return r[i]-n[i];return 0}function Cn(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=us(t.cwd??process.cwd()),n=Ye(r,ee),i;try{i=JSON.parse(await Dn(n,"utf-8"))}catch{R.cancel(".generatesaas/manifest.json not found. Run this from a GenerateSaaS project."),process.exit(1)}let o;try{o=await Ae()}catch(d){R.cancel(P(d)),process.exit(1)}let s=Z(o),a=R.spinner();try{a.start("Verifying access...");let d;try{d=await ne(s)}catch(u){throw u instanceof x&&u.status===401?new Error("Your saved API key was rejected. Run `generatesaas auth` to update it, or set GENERATESAAS_API_KEY."):u}a.stop("Access verified."),fe(o),a.start("Fetching latest skill files...");let h=await ar(s,d.latest);await Ht(r,h.skillMd,h.scripts,i.aiTools);let y=zt(i.aiTools);if(a.stop("Skills updated."),R.log.success(`Skill files installed to ${Ne.cyan(y.length.toString())} locations.`),i.version===d.latest){R.log.info(`Already on the latest version (${i.version}).`);return}if(i.licenseToken)try{let u=await lr(s,{currentToken:i.licenseToken,newVersion:d.latest});i.licenseToken=u.token,u.licenseKeyHash&&(i.licenseKeyHash=u.licenseKeyHash),await l(n,JSON.stringify(i,null," ")+`
1160
+ `),R.log.success("License refreshed.")}catch(u){let b=dt(u);b&&(R.cancel(b.message),process.exit(1)),R.log.warn("License refresh skipped.")}let f=mn(i,r),S=Ye(r,or);a.start(`Staging v${d.latest} (shaped for your config)...`),await Wt(s,d.latest,f,S),a.stop("Template staged.");let{text:I,title:oe}=await ms(s,d,i.version);I&&R.note(I,oe);let C=Ye(r,lt),U=Ye(r,pt),we=!On(U),Q=!On(C);if(we){if(a.start("Building baseline template (one-time migration)..."),await Wt(s,i.version,f,U),Q){let u=await De(U);await l(C,JSON.stringify(u,null," ")+`
1161
+ `)}if(a.stop("Baseline template stored."),!Q){let u=await fs(C,U);u>0&&R.log.warn(`Rebuilt baseline differs from the original for ${u} file(s) (the CLI's shaping evolved since this project was scaffolded). Classification still follows the committed template-hashes.json; upstream diffs for those files may include unrelated noise.`)}}else if(Q){a.start("Computing baseline template hashes...");let u=await De(U);await l(C,JSON.stringify(u,null," ")+`
1162
+ `),a.stop("Baseline hashes computed.")}if(await l(Ye(r,sr),JSON.stringify({currentVersion:i.version,targetVersion:d.latest,changelog:I,stagedAt:new Date().toISOString()},null," ")+`
1163
+ `),R.log.info(`Update staged: ${Ne.cyan(i.version)} \u2192 ${Ne.cyan(d.latest)}`),i.aiTools&&i.aiTools.length>0){let u=i.aiTools[0],b=Ue[u].label;R.log.info(`Open your project in ${Ne.cyan(b)} and ask: ${Ne.cyan("'update my GenerateSaaS project'")}`)}else R.log.info(`Ask your AI coding assistant to ${Ne.cyan("'update my GenerateSaaS project'")}.`)}catch(d){a.stop("Failed."),R.cancel(`Update failed: ${P(d)}`),process.exit(1)}})}async function ms(e,t,r){let n=t.latest,i=t.versions.filter(s=>bt(s.version,r)>0&&bt(s.version,n)<=0).sort((s,a)=>bt(s.version,a.version));if(i.length<=1)return{text:await _t(e,n),title:`Changelog v${n}`};let o=[];for(let s of i){let a=null;try{a=await _t(e,s.version)}catch{a=null}let d=s.date?` (${s.date.slice(0,10)})`:"",h=s.breaking?" [BREAKING]":"";o.push(`# v${s.version}${d}${h}
1138
1164
 
1139
1165
  ${a??"_No changelog available for this release._"}`)}return{text:o.join(`
1140
1166
 
1141
- `),title:`Changelog v${r} \u2192 v${n}`}}async function ls(e,t){let r=JSON.parse(await Pn(e,"utf-8")),n=await xe(t),i=0;for(let[o,s]of Object.entries(r))Te(o)||n[o]!==s&&i++;for(let o of Object.keys(n))o in r||i++;return i}import*as L from"@clack/prompts";import M from"picocolors";import{readFile as ps}from"fs/promises";import{join as ds,resolve as us}from"path";function Rn(e){e.command("status").description("Show project status and check for updates").option("--cwd <path>","project directory (default: current directory)").action(async t=>{let r=us(t.cwd??process.cwd()),n=ds(r,Q),i;try{i=JSON.parse(await ps(n,"utf-8"))}catch{L.cancel(".generatesaas/manifest.json not found. Run this from a GenerateSaaS project."),process.exit(1)}let o=[`Version: ${M.cyan(i.version)}`,`Frontend: ${M.cyan(i.frontend)}`,i.deploymentTarget?`Deploy target: ${M.cyan(i.deploymentTarget)}`:null,i.databaseProvider?`Database: ${M.cyan(i.databaseProvider)}`:null,i.cacheProvider?`Cache: ${M.cyan(i.cacheProvider)}`:null,i.aiTools&&i.aiTools.length>0?`AI tools: ${M.cyan(i.aiTools.join(", "))}`:null].filter(Boolean).join(`
1142
- `);L.note(o,M.bold("Project Status"));let s=L.spinner();s.start("Checking for updates...");try{let a=await Ae(),d=X(a),h=(await re(d)).latest;i.version===h?(s.stop("Up to date."),L.log.success(`Already on the latest version (${M.green(h)})`)):(s.stop("Update available."),L.log.warning(`Update available: ${M.yellow(i.version)} \u2192 ${M.green(h)}`),L.log.info(`Open this project in your AI coding agent and ask it to ${M.cyan("update my GenerateSaaS project")} - it fetches and applies the update for you.`))}catch(a){s.stop("Check failed."),a instanceof R&&a.status===401?L.log.warning("Invalid API key. Run `generatesaas auth` to update it, or set GENERATESAAS_API_KEY."):L.log.warning(`Could not check for updates: ${I(a)}`)}})}import{readFile as ms}from"fs/promises";import*as T from"@clack/prompts";import A from"picocolors";function fs(){return process.env.GENERATESAAS_API_KEY??Be()}function gs(e){return{verdict:e.verdict,plan:e.license?.plan??e.domainInstalls.find(t=>t.ownerPlan)?.ownerPlan??null,mismatchDomain:e.install?.domain??null,ejectedAt:e.install?.ejectedAt??e.domainInstalls.find(t=>t.ejectedAt)?.ejectedAt??null}}function hs(e){return{verdict:e.verdict??"unknown",ejectedAt:e.ejectedAt??null}}function ys(e){switch(e.verdict){case"licensed":return T.log.success(`${A.green("LICENSED")} - resolves to an account with an active${e.plan?` ${e.plan}`:""} license.`),!0;case"ejected":return T.log.success(`${A.green("EJECTED")} - a licensed buyer opted this install out of telemetry${e.ejectedAt?` on ${e.ejectedAt.slice(0,10)}`:""}. The site is legitimate.`),!0;case"revoked":return T.log.error(`${A.red("REVOKED")} - the owning account no longer holds a plan (refund or chargeback). This deployment is no longer licensed.`),!1;case"token_domain_mismatch":return T.log.error(`${A.red("LEAKED TOKEN")} - this license belongs to a different deployment${e.mismatchDomain?` (${A.cyan(e.mismatchDomain)})`:""}, not this site. The token was copied from a licensed project.`),!1;case"no_license_history":return T.log.error(`${A.red("NO LICENSE HISTORY")} - no license has ever been associated with this site. If it runs GenerateSaaS, treat it as unlicensed.`),!1;default:return T.log.warn(`${A.yellow("UNKNOWN")} - could not cross-reference the records right now. Try again shortly.`),!1}}async function On(e,t){let r=process.env.GENERATESAAS_API_URL??Fe,n=fs();e.start("Cross-referencing license records...");try{let i=n?gs(await cr(r,n,{lkh:t.lkh,nid:t.nid,domain:t.domain})):hs(await _t(r,{token:t.token,domain:t.domain}));return e.stop(`${A.green("Checked")} - records cross-referenced`),ys(i)}catch(i){return e.stop(`${A.yellow("Skipped")} - ${I(i)}`),null}}function vs(e){let t=e.split(".");if(t.length!==3||!t[1])throw new Error("Invalid JWT format");let r=Buffer.from(t[1],"base64url").toString("utf-8");return JSON.parse(r)}function Jt(e){return typeof e!="number"?"unknown":new Date(e*1e3).toISOString().split("T")[0]}function xn(e){T.note([`License ID: ${A.cyan(String(e.lid??"unknown"))}`,`Version: ${A.cyan(String(e.ver??"unknown"))}`,`Init version: ${String(e.iver??"unknown")}`,`Frontend: ${String(e.fe??"unknown")}`,`Created: ${Jt(e.pat)}`,`Last updated: ${Jt(e.uat)}`,`Expires: ${Jt(e.exp)}`,`Install ID: ${String(e.nid??"unknown")}`].join(`
1143
- `),A.yellow("License Details"))}function Ss(e){let r=(/^https?:\/\//i.test(e)?e:`https://${e}`).replace(/\/+$/,"");if(r.endsWith("/api"))return[`${r}/license`];try{if(new URL(r).pathname!=="/")return[`${r}/license`]}catch{return[`${r}/license`]}return[`${r}/api/license`,`${r}/license`]}async function Dn(e){let t=T.spinner(),r=null,n="no candidates";for(let s of Ss(e)){t.start(`Checking ${s}...`);try{let a=await fetch(s);if(!a.ok){n=`${s} returned ${a.status}`,t.stop(`${A.yellow("Not here")} - ${n}`);continue}let d=(await a.text()).trim();if(!d||d.split(".").length!==3){n=`${s} did not return a JWT`,t.stop(`${A.yellow("Not here")} - ${n}`);continue}r=d,t.stop(`${A.green("Found")} - license endpoint responded`);break}catch(a){n=`${s}: ${I(a)}`,t.stop(`${A.yellow("Unreachable")} - ${n}`)}}if(r===null){T.log.warn(`No license endpoint found (last: ${n}). The site may be ejected, not a GenerateSaaS app, or serving its API elsewhere.`);let s=Cn(e);return s?await On(t,{domain:s})??!1:!1}let i;try{i=vs(r)}catch{return T.log.error("Could not decode JWT payload."),!1}t.start("Verifying signature...");try{let s=process.env.GENERATESAAS_API_URL??Fe,a=await _t(s,{token:r});if(a.valid)t.stop(`${A.green("Valid")} - signature verified`);else return t.stop(`${A.red("Invalid")} - ${a.reason}`),!1}catch{return t.stop(`${A.yellow("Skipped")} - could not reach verification service`),T.log.warn("Signature not verified. Displaying unverified claims:"),xn(i),!1}return xn(i),await On(t,{token:r,lkh:typeof i.lkh=="string"?i.lkh:void 0,nid:typeof i.nid=="string"?i.nid:void 0,domain:Cn(e)})??!0}function Cn(e){try{return new URL(/^https?:\/\//i.test(e)?e:`https://${e}`).hostname}catch{return}}function Nn(e){e.command("verify").description("Verify a GenerateSaaS license on a deployed site").argument("[url]","URL of the site to verify (e.g. https://example.com or https://example.com/api)").option("--file <path>","file with URLs to check, one per line").action(async(t,r)=>{if(!t&&!r.file&&(T.cancel("Provide a URL or --file <path>."),process.exit(1)),r.file){let i=(await ms(r.file,"utf-8")).split(`
1144
- `).map(s=>s.trim()).filter(s=>s&&!s.startsWith("#"));i.length===0&&(T.cancel("No URLs found in file."),process.exit(1));let o=0;for(let s of i)await Dn(s)&&o++,T.log.info("");T.log.success(`${o}/${i.length} sites verified.`)}else await Dn(t)||process.exit(1)})}import{existsSync as Es,rmSync as ws}from"fs";import*as F from"@clack/prompts";function Ln(e){e.command("auth").description("Set or update your GenerateSaaS API key").option("--clear","remove saved API key").action(async t=>{if(t.clear){Es(q)?(ws(q),F.log.success("API key removed.")):F.log.info("No API key configured.");return}let r=Be();r?F.log.info(`Current API key: ****${r.slice(-4)}`):F.log.info("No API key configured.");let n=await Ge(),i=X(n),o=F.spinner();o.start("Verifying API key...");try{await re(i),o.stop("API key verified."),ue(n),F.log.success("API key saved.")}catch(s){o.stop("Verification failed."),s instanceof R&&s.status===401?F.cancel("Invalid API key."):F.cancel(I(s)),process.exit(1)}})}import{existsSync as bt,rmSync as bs,readFileSync as qt,writeFileSync as $n}from"fs";import{join as ve}from"path";import*as O from"@clack/prompts";var As=["packages/api/src/functions/maintenance/license-heartbeat.ts","packages/api/src/lib/manifest.ts","packages/api/src/routes/internal/license.ts"],Ts=[{file:"packages/api/src/routes/inngest.ts",removals:[`import { licenseHeartbeatFunction } from "../functions/maintenance/license-heartbeat";
1167
+ `),title:`Changelog v${r} \u2192 v${n}`}}async function fs(e,t){let r=JSON.parse(await Dn(e,"utf-8")),n=await De(t),i=0;for(let[o,s]of Object.entries(r))Te(o)||n[o]!==s&&i++;for(let o of Object.keys(n))o in r||i++;return i}import*as j from"@clack/prompts";import B from"picocolors";import{readFile as gs}from"fs/promises";import{join as hs,resolve as ys}from"path";function Nn(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=ys(t.cwd??process.cwd()),n=hs(r,ee),i;try{i=JSON.parse(await gs(n,"utf-8"))}catch{j.cancel(".generatesaas/manifest.json not found. Run this from a GenerateSaaS project."),process.exit(1)}let o=[`Version: ${B.cyan(i.version)}`,`Frontend: ${B.cyan(i.frontend)}`,i.deploymentTarget?`Deploy target: ${B.cyan(i.deploymentTarget)}`:null,i.databaseProvider?`Database: ${B.cyan(i.databaseProvider)}`:null,i.cacheProvider?`Cache: ${B.cyan(i.cacheProvider)}`:null,i.aiTools&&i.aiTools.length>0?`AI tools: ${B.cyan(i.aiTools.join(", "))}`:null].filter(Boolean).join(`
1168
+ `);j.note(o,B.bold("Project Status"));let s=j.spinner();s.start("Checking for updates...");try{let a=await Ae(),d=Z(a),y=(await ne(d)).latest;i.version===y?(s.stop("Up to date."),j.log.success(`Already on the latest version (${B.green(y)})`)):(s.stop("Update available."),j.log.warning(`Update available: ${B.yellow(i.version)} \u2192 ${B.green(y)}`),j.log.info(`Open this project in your AI coding agent and ask it to ${B.cyan("update my GenerateSaaS project")} - it fetches and applies the update for you.`))}catch(a){s.stop("Check failed."),a instanceof x&&a.status===401?j.log.warning("Invalid API key. Run `generatesaas auth` to update it, or set GENERATESAAS_API_KEY."):j.log.warning(`Could not check for updates: ${P(a)}`)}})}import{readFile as vs}from"fs/promises";import*as T from"@clack/prompts";import A from"picocolors";function Ss(){return process.env.GENERATESAAS_API_KEY??Ge()}function Es(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 ws(e){return{verdict:e.verdict??"unknown",ejectedAt:e.ejectedAt??null}}function bs(e){switch(e.verdict){case"licensed":return T.log.success(`${A.green("LICENSED")} - resolves to an account with an active${e.plan?` ${e.plan}`:""} license.`),!0;case"ejected":return T.log.success(`${A.green("EJECTED")} - a licensed buyer opted this install out of telemetry${e.ejectedAt?` on ${e.ejectedAt.slice(0,10)}`:""}. The site is legitimate.`),!0;case"revoked":return T.log.error(`${A.red("REVOKED")} - the owning account no longer holds a plan (refund or chargeback). This deployment is no longer licensed.`),!1;case"token_domain_mismatch":return T.log.error(`${A.red("LEAKED TOKEN")} - this license belongs to a different deployment${e.mismatchDomain?` (${A.cyan(e.mismatchDomain)})`:""}, not this site. The token was copied from a licensed project.`),!1;case"no_license_history":return T.log.error(`${A.red("NO LICENSE HISTORY")} - no license has ever been associated with this site. If it runs GenerateSaaS, treat it as unlicensed.`),!1;default:return T.log.warn(`${A.yellow("UNKNOWN")} - could not cross-reference the records right now. Try again shortly.`),!1}}async function Ln(e,t){let r=process.env.GENERATESAAS_API_URL??Be,n=Ss();e.start("Cross-referencing license records...");try{let i=n?Es(await pr(r,n,{lkh:t.lkh,nid:t.nid,domain:t.domain})):ws(await xt(r,{token:t.token,domain:t.domain}));return e.stop(`${A.green("Checked")} - records cross-referenced`),bs(i)}catch(i){return e.stop(`${A.yellow("Skipped")} - ${P(i)}`),null}}function ks(e){let t=e.split(".");if(t.length!==3||!t[1])throw new Error("Invalid JWT format");let r=Buffer.from(t[1],"base64url").toString("utf-8");return JSON.parse(r)}function qt(e){return typeof e!="number"?"unknown":new Date(e*1e3).toISOString().split("T")[0]}function $n(e){T.note([`License ID: ${A.cyan(String(e.lid??"unknown"))}`,`Version: ${A.cyan(String(e.ver??"unknown"))}`,`Init version: ${String(e.iver??"unknown")}`,`Frontend: ${String(e.fe??"unknown")}`,`Created: ${qt(e.pat)}`,`Last updated: ${qt(e.uat)}`,`Expires: ${qt(e.exp)}`,`Install ID: ${String(e.nid??"unknown")}`].join(`
1169
+ `),A.yellow("License Details"))}function As(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 jn(e){let t=T.spinner(),r=null,n="no candidates";for(let s of As(e)){t.start(`Checking ${s}...`);try{let a=await fetch(s);if(!a.ok){n=`${s} returned ${a.status}`,t.stop(`${A.yellow("Not here")} - ${n}`);continue}let d=(await a.text()).trim();if(!d||d.split(".").length!==3){n=`${s} did not return a JWT`,t.stop(`${A.yellow("Not here")} - ${n}`);continue}r=d,t.stop(`${A.green("Found")} - license endpoint responded`);break}catch(a){n=`${s}: ${P(a)}`,t.stop(`${A.yellow("Unreachable")} - ${n}`)}}if(r===null){T.log.warn(`No license endpoint found (last: ${n}). The site may be ejected, not a GenerateSaaS app, or serving its API elsewhere.`);let s=Un(e);return s?await Ln(t,{domain:s})??!1:!1}let i;try{i=ks(r)}catch{return T.log.error("Could not decode JWT payload."),!1}t.start("Verifying signature...");try{let s=process.env.GENERATESAAS_API_URL??Be,a=await xt(s,{token:r});if(a.valid)t.stop(`${A.green("Valid")} - signature verified`);else return t.stop(`${A.red("Invalid")} - ${a.reason}`),!1}catch{return t.stop(`${A.yellow("Skipped")} - could not reach verification service`),T.log.warn("Signature not verified. Displaying unverified claims:"),$n(i),!1}return $n(i),await Ln(t,{token:r,lkh:typeof i.lkh=="string"?i.lkh:void 0,nid:typeof i.nid=="string"?i.nid:void 0,domain:Un(e)})??!0}function Un(e){try{return new URL(/^https?:\/\//i.test(e)?e:`https://${e}`).hostname}catch{return}}function Vn(e){e.command("verify").description("Verify a GenerateSaaS license on a deployed site").argument("[url]","URL of the site to verify (e.g. https://example.com or https://example.com/api)").option("--file <path>","file with URLs to check, one per line").action(async(t,r)=>{if(!t&&!r.file&&(T.cancel("Provide a URL or --file <path>."),process.exit(1)),r.file){let i=(await vs(r.file,"utf-8")).split(`
1170
+ `).map(s=>s.trim()).filter(s=>s&&!s.startsWith("#"));i.length===0&&(T.cancel("No URLs found in file."),process.exit(1));let o=0;for(let s of i)await jn(s)&&o++,T.log.info("");T.log.success(`${o}/${i.length} sites verified.`)}else await jn(t)||process.exit(1)})}import{existsSync as Ts,rmSync as Is}from"fs";import*as G from"@clack/prompts";function Mn(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)?(Is(X),G.log.success("API key removed.")):G.log.info("No API key configured.");return}let r=Ge();r?G.log.info(`Current API key: ****${r.slice(-4)}`):G.log.info("No API key configured.");let n=await Ke(),i=Z(n),o=G.spinner();o.start("Verifying API key...");try{await ne(i),o.stop("API key verified."),fe(n),G.log.success("API key saved.")}catch(s){o.stop("Verification failed."),s instanceof x&&s.status===401?G.cancel("Invalid API key."):G.cancel(P(s)),process.exit(1)}})}import{existsSync as kt,rmSync as Ps,readFileSync as Zt,writeFileSync as Fn}from"fs";import{join as Se}from"path";import*as D from"@clack/prompts";var _s=["packages/api/src/functions/maintenance/license-heartbeat.ts","packages/api/src/lib/cron-spread.ts","packages/api/src/lib/manifest.ts","packages/api/src/routes/internal/license.ts"],Rs=[{file:"packages/api/src/routes/inngest.ts",removals:[`import { licenseHeartbeatFunction } from "../functions/maintenance/license-heartbeat";
1145
1171
  `,` licenseHeartbeatFunction,
1146
1172
  `]},{file:"packages/api/src/routes/internal/index.ts",removals:[`import licenseRoutes from "./license";
1147
1173
  `,` .route("/license", licenseRoutes)
1148
- `]}];function ks(e){return(e&&e.length>0?e.map(r=>He[r]):Object.values(He)).map(r=>ve(r,Bt))}function Wt(e){return bt(e)?(bs(e,{recursive:!0}),!0):!1}function Is(e,t){if(!bt(e))return!1;let r=qt(e,"utf-8"),n=r;for(let i of t)n=n.replace(i,"");return n===r?!1:($n(e,n,"utf-8"),!0)}function Ps(e){let t=ve(e,".gitignore");if(!bt(t))return!1;let r=qt(t,"utf-8"),n=r.split(`
1174
+ `]}];function xs(e){return(e&&e.length>0?e.map(r=>He[r]):Object.values(He)).map(r=>Se(r,Kt))}function Xt(e){return kt(e)?(Ps(e,{recursive:!0}),!0):!1}function Os(e,t){if(!kt(e))return!1;let r=Zt(e,"utf-8"),n=r;for(let i of t)n=n.replace(i,"");return n===r?!1:(Fn(e,n,"utf-8"),!0)}function Ds(e){let t=Se(e,".gitignore");if(!kt(t))return!1;let r=Zt(t,"utf-8"),n=r.split(`
1149
1175
  `).filter(i=>!i.includes(".generatesaas")).join(`
1150
- `);return n===r?!1:($n(t,n,"utf-8"),!0)}function Un(e){e.command("eject").description("Remove all GenerateSaaS ties - manifest, license, heartbeat, skills").action(async()=>{let t=process.cwd(),r=ve(t,Q),n;try{n=JSON.parse(qt(r,"utf-8"))}catch{O.cancel("No GenerateSaaS project found in this directory."),process.exit(1)}let i=await O.text({message:'Type "eject" to confirm (this cannot be undone):',validate:a=>{if(a!=="eject")return'Type "eject" to confirm, or press Ctrl+C to cancel.'}});if(O.isCancel(i)&&(O.cancel("Eject cancelled."),process.exit(0)),n.licenseToken)try{await fetch("https://generatesaas.com/api/v1/heartbeat",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${n.licenseToken}`},body:JSON.stringify({event:"eject",version:n.version,frontend:n.frontend}),signal:AbortSignal.timeout(5e3)}),O.log.info("Recorded the opt-out with generatesaas.com (final event - nothing is sent after this).")}catch{O.log.warn("Could not reach generatesaas.com to record the opt-out. Ejecting anyway.")}let o=[],s=[];for(let a of ks(n.aiTools))Wt(ve(t,a))&&o.push(a);for(let a of As)Wt(ve(t,a))&&o.push(a);Wt(ve(t,U))&&o.push(U+"/");for(let a of Ts){let d=ve(t,a.file);Is(d,a.removals)?s.push(a.file):bt(d)&&O.log.warn(`Could not auto-modify ${a.file} - manually remove license/heartbeat references.`)}Ps(t)&&s.push(".gitignore");for(let a of o)O.log.info(`Deleted ${a}`);for(let a of s)O.log.info(`Modified ${a}`);O.log.success("Ejected successfully. This project is now fully standalone.")})}var Se=new _s().name("generatesaas").description("CLI for scaffolding and managing GenerateSaaS projects").version("1.12.0").addHelpText("after",`
1176
+ `);return n===r?!1:(Fn(t,n,"utf-8"),!0)}function Bn(e){e.command("eject").description("Remove all GenerateSaaS ties - manifest, license, heartbeat, skills").action(async()=>{let t=process.cwd(),r=Se(t,ee),n;try{n=JSON.parse(Zt(r,"utf-8"))}catch{D.cancel("No GenerateSaaS project found in this directory."),process.exit(1)}let i=await D.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(D.isCancel(i)&&(D.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)}),D.log.info("Recorded the opt-out with generatesaas.com (final event - nothing is sent after this).")}catch{D.log.warn("Could not reach generatesaas.com to record the opt-out. Ejecting anyway.")}let o=[],s=[];for(let a of xs(n.aiTools))Xt(Se(t,a))&&o.push(a);for(let a of _s)Xt(Se(t,a))&&o.push(a);Xt(Se(t,V))&&o.push(V+"/");for(let a of Rs){let d=Se(t,a.file);Os(d,a.removals)?s.push(a.file):kt(d)&&D.log.warn(`Could not auto-modify ${a.file} - manually remove license/heartbeat references.`)}Ds(t)&&s.push(".gitignore");for(let a of o)D.log.info(`Deleted ${a}`);for(let a of s)D.log.info(`Modified ${a}`);D.log.success("Ejected successfully. This project is now fully standalone.")})}var Ee=new Cs().name("generatesaas").description("CLI for scaffolding and managing GenerateSaaS projects").version("1.13.0").addHelpText("after",`
1151
1177
  Examples:
1152
1178
  $ generatesaas init Interactive setup
1153
1179
  $ generatesaas init -n my-app -y Quick setup with defaults
1154
1180
  $ generatesaas status Check for updates
1155
1181
  $ generatesaas auth Set or update API key
1156
- `);Tn(Se);_n(Se);Rn(Se);Nn(Se);Ln(Se);Un(Se);Se.parseAsync().catch(e=>{jn.cancel("An unexpected error occurred."),console.error(e),process.exit(1)});
1182
+ `);Rn(Ee);Cn(Ee);Nn(Ee);Vn(Ee);Mn(Ee);Bn(Ee);Ee.parseAsync().catch(e=>{Gn.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.12.0",
3
+ "version": "1.13.0",
4
4
  "type": "module",
5
5
  "description": "CLI for scaffolding and managing GenerateSaaS projects",
6
6
  "bin": {