generatesaas 0.7.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -329,7 +329,7 @@ export default handle(app);
329
329
  `}var kr={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"}]},Dr={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 Ne(e,t){return t?e.map(r=>{let i=t[r.key];return i?{...r,defaultValue:i,comment:void 0}:r}):e}function Ve(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 Et(e){return Array.from(crypto.getRandomValues(new Uint8Array(e))).map(t=>t.toString(16).padStart(2,"0")).join("")}function Cr(e){let t=crypto.randomUUID(),r=Et(32),i=Et(32),o=["# API Configuration",`API_URL=${e.architecture==="fullstack"?"http://localhost:3000/api":"http://localhost:3010"}`];o.push("","# Database"),Ve(Ne(F[e.databaseProvider].envVars,e.credentials),o),o.push("","# Cache"),Ve(Ne(K[e.cacheProvider].envVars,e.credentials),o),o.push("","# Authentication",`BETTER_AUTH_SECRET=${t}`,"","# Job Queue - Inngest","INNGEST_APP_ID=api",`INNGEST_EVENT_KEY=${r}`,`INNGEST_SIGNING_KEY=${i}`,"INNGEST_BASE_URL=http://127.0.0.1:8288"),e.architecture==="separate"&&o.push("","# API Port (standalone backend)","API_PORT=3010");let a=kr[e.emailProvider];if(a&&(o.push("","# Email"),Ve(Ne(a,e.credentials),o)),e.paymentProvider!=="none"){let s=Dr[e.paymentProvider];s&&(o.push("","# Payment"),Ve(Ne(s,e.credentials),o))}return o.push(""),o.join(`
330
330
  `)}function xr(e){return["# API Configuration",`NUXT_PUBLIC_API_URL=${e.architecture==="fullstack"?"http://localhost:3000/api":"http://localhost:3010"}`,""].join(`
331
331
  `)}async function It(e){let t=Cr(e),r=xr(e);if(e.architecture==="fullstack"){let i=r+`
332
- `+t;await d(`${e.projectDir}/apps/web-nuxt/.env`,i)}else await d(`${e.projectDir}/apps/web-nuxt/.env`,r),await d(`${e.projectDir}/apps/backend/.env`,t)}import{readdir as $r}from"fs/promises";import{join as qe,relative as bt}from"path";import{createHash as At}from"crypto";import{readFile as Or}from"fs/promises";async function Ue(e){let t=await Or(e);return At("sha256").update(t).digest("hex")}function wt(e){return At("sha256").update(e).digest("hex")}var Lr=new Set([".git","node_modules",".pnpm-store",".env","data",V]);function jr(e){let t=e.split("/");for(let r of t)if(Lr.has(r)||r.startsWith(".env")&&!r.includes("example"))return!0;return!1}async function _t(e,t){let r=[],i=await $r(e,{withFileTypes:!0});for(let n of i){let o=qe(e,n.name),a=bt(t,o);jr(a)||(n.isDirectory()?r.push(...await _t(o,t)):n.isFile()&&r.push(o))}return r}async function Pt(e,t){let i=(await _t(e.projectDir,e.projectDir)).sort(),n=await Promise.all(i.map(async s=>[bt(e.projectDir,s),await Ue(s)])),o=Object.fromEntries(n),a={version:e.version,initialVersion:e.version,repo:"Duzbee/GenerateSaaS",frontend:e.frontend,aiTools:e.aiTools,deploymentTarget:e.deploymentTarget,databaseProvider:e.databaseProvider,cacheProvider:e.cacheProvider,revenueSharing:e.revenueSharing,...t&&{licenseToken:t.token,licenseKeyHash:t.keyHash,installId:t.installId}};await d(qe(e.projectDir,q),JSON.stringify(a,null," ")+`
332
+ `+t;await d(`${e.projectDir}/apps/web-nuxt/.env`,i)}else await d(`${e.projectDir}/apps/web-nuxt/.env`,r),await d(`${e.projectDir}/apps/backend/.env`,t)}import{readdir as $r}from"fs/promises";import{join as qe,relative as bt}from"path";import{createHash as At}from"crypto";import{readFile as Or}from"fs/promises";async function Ue(e){let t=await Or(e);return At("sha256").update(t).digest("hex")}function wt(e){return At("sha256").update(e).digest("hex")}var Lr=new Set([".git","node_modules",".pnpm-store",".env",".env.test",".turbo",".nuxt",".output",".data","dist","data",".next",".svelte-kit",".netlify",".wrangler",".devcontainer","playwright-report","test-results",V]);function jr(e){let t=e.split("/");for(let r of t)if(Lr.has(r)||r.startsWith(".env")&&!r.includes("example"))return!0;return!1}async function _t(e,t){let r=[],i=await $r(e,{withFileTypes:!0});for(let n of i){let o=qe(e,n.name),a=bt(t,o);jr(a)||(n.isDirectory()?r.push(...await _t(o,t)):n.isFile()&&r.push(o))}return r}async function Pt(e,t){let i=(await _t(e.projectDir,e.projectDir)).sort(),n=await Promise.all(i.map(async s=>[bt(e.projectDir,s),await Ue(s)])),o=Object.fromEntries(n),a={version:e.version,initialVersion:e.version,repo:"Duzbee/GenerateSaaS",frontend:e.frontend,aiTools:e.aiTools,deploymentTarget:e.deploymentTarget,databaseProvider:e.databaseProvider,cacheProvider:e.cacheProvider,revenueSharing:e.revenueSharing,...t&&{licenseToken:t.token,licenseKeyHash:t.keyHash,installId:t.installId}};await d(qe(e.projectDir,q),JSON.stringify(a,null," ")+`
333
333
  `),await d(qe(e.projectDir,st),JSON.stringify(o,null," ")+`
334
334
  `)}async function Rt(e){if(!(e.architecture!=="separate"||e.deploymentTarget==="node"))switch(e.deploymentTarget){case"cloudflare":await Nr(e);break;case"vercel":await Vr(e);break;case"netlify":await Ur(e);break}}async function Nr(e){let t=`name = "${e.projectName}-api"
335
335
  main = "src/index.ts"
@@ -551,7 +551,7 @@ ${n}
551
551
  `)}import{readdir as Hr}from"fs/promises";import{join as zr,relative as Dt}from"path";async function Ct(e,t){let r=[],i=await Hr(e,{withFileTypes:!0});for(let n of i){let o=zr(e,n.name),a=Dt(t,o);Le(a)||(n.isDirectory()?r.push(...await Ct(o,t)):n.isFile()&&r.push(o))}return r}async function Me(e){let r=(await Ct(e,e)).sort(),i=await Promise.all(r.map(async n=>[Dt(e,n),await Ue(n)]));return Object.fromEntries(i)}import{existsSync as Yr}from"fs";import{readFile as xt,readdir as qr}from"fs/promises";import{join as x,dirname as Wr}from"path";import{fileURLToPath as Jr}from"url";var he={"claude-code":".claude/skills",cursor:".cursor/skills",codex:".agents/skills","gemini-cli":".gemini/skills",windsurf:".windsurf/skills"},Zr=Object.values(he),We="generatesaas-update",Ot=Wr(Jr(import.meta.url));function Xr(){let e=x(Ot,"skill","content");return Yr(e)?e:x(Ot,"content")}function Je(e){return!e||e.length===0?Zr:e.map(t=>he[t])}async function Ze(e,t,r,i){let n=Je(i);for(let o of n){let a=x(e,o,We),s=x(a,"scripts"),m=x(a,"references");await je(s),await je(m),await d(x(a,"SKILL.md"),t.replaceAll("__SKILL_ROOT__",o)),await d(x(m,".gitkeep"),"");for(let[y,g]of Object.entries(r))await d(x(s,y),g)}}async function $t(e,t){let r=Xr(),i=await xt(x(r,"SKILL.md"),"utf-8"),n=x(r,"scripts"),o=await qr(n),a={};for(let s of o)s!==".gitkeep"&&(a[s]=await xt(x(n,s),"utf-8"));await Ze(e,i,a,t)}import{execFile as Qr,execFileSync as en}from"child_process";import{access as tn}from"fs/promises";import{join as rn}from"path";import*as U from"@clack/prompts";function Ke(e){try{let t=process.platform==="win32"?"where":"which";return en(t,[e],{stdio:"ignore"}),!0}catch{return!1}}function Fe(e,t,r,i=3e5){return new Promise((n,o)=>{Qr(e,t,{cwd:r,timeout:i},a=>{a?o(a):n()})})}async function Lt(e){if(!Ke("pnpm"))return U.log.warn("pnpm not found. Skipping dependency installation."),U.log.info("Install pnpm: https://pnpm.io/installation"),!1;let t=U.spinner();t.start("Installing dependencies (this may take a minute)...");try{return await Fe("pnpm",["install"],e),t.stop("Dependencies installed."),!0}catch{return t.stop("Dependency installation failed."),U.log.warn("pnpm install failed. You can run it manually later."),!1}}async function jt(e){try{return await tn(rn(e,".git")),U.log.info("Git repository already exists, skipping init."),!0}catch{}if(!Ke("git"))return U.log.warn("git not found. Skipping repository initialization."),!1;let t=U.spinner();t.start("Initializing git repository...");try{return await Fe("git",["init"],e),await Fe("git",["add","-A"],e),await Fe("git",["commit","-m","Initial commit from GenerateSaaS"],e),t.stop("Git repository initialized."),!0}catch{return t.stop("Git initialization failed."),U.log.warn("You can run git init manually later."),!1}}import*as le from"@clack/prompts";import O from"picocolors";function Nt(e,t){t.dockerComposeGenerated&&!t.dockerAvailable&&le.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(a=>ae[a].label).join(", ");r.push(`pnpm infra ${O.dim(`# ${o}`)}`)}if(r.push(`pnpm dev ${O.dim("# http://localhost:3000")}`),t.skippedCredentials.length>0&&(r.push(""),r.push(O.dim("Fill in remaining TODO values in .env"))),le.note(r.join(`
552
552
  `),O.yellow("Start Development")),t.dockerComposeGenerated){let o=[];o.push(`App ${O.cyan("http://localhost:3000")}`),e.architecture==="separate"&&o.push(`API ${O.cyan("http://localhost:3010")}`),e.dockerServices.includes("mailpit")&&o.push(`Mailpit ${O.cyan("http://localhost:8025")}`),e.dockerServices.includes("inngest")&&o.push(`Inngest ${O.cyan("http://localhost:8288")}`),le.note(o.join(`
553
553
  `),O.yellow("Dev Tools"))}let i=[],n=nn(e);n.length>0&&i.push(`Set in production: ${O.dim(n.join(", "))}`),i.push("pnpm db:push # Run database migrations"),i.push(on(e)),le.note(i.join(`
554
- `),O.yellow("Deployment"))}function nn(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 on(e){switch(e.deploymentTarget){case"node":return"Deploy with Docker or your preferred Node.js host";case"cloudflare":return"wrangler deploy # Deploy to Cloudflare Workers";case"vercel":return"vercel deploy # Deploy to Vercel";case"netlify":return"netlify deploy # Deploy to Netlify"}}function Mt(e){let t={};if(e.name!==void 0){if(!xe(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&&(t.frontend=e.frontend),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.revenueSharing!==void 0&&(t.revenueSharing=e.revenueSharing),e.docker!==void 0&&(t.dockerServices=Ut(e.docker,Te,"docker service")),e.aiTools!==void 0&&(t.aiTools=Ut(e.aiTools,ke,"AI tool")),e.currency!==void 0){if(!ee.includes(e.currency))throw new Error(`Invalid currency "${e.currency}". Valid values: ${ee.join(", ")}`);t.defaultCurrency=e.currency}if(e.deploy!==void 0){if(!te.includes(e.deploy))throw new Error(`Invalid deployment target "${e.deploy}". Valid values: ${te.join(", ")}`);t.deploymentTarget=e.deploy}if(e.database!==void 0){if(!re.includes(e.database))throw new Error(`Invalid database provider "${e.database}". Valid values: ${re.join(", ")}`);t.databaseProvider=e.database}if(e.cache!==void 0){if(!ne.includes(e.cache))throw new Error(`Invalid cache provider "${e.cache}". Valid values: ${ne.join(", ")}`);t.cacheProvider=e.cache}return t}var Vt={projectName:"my-saas",frontend:"nuxt",architecture:"fullstack",paymentProvider:"stripe",emailProvider:"smtp",multiTenancy:!1,billingScope:"user",blog:!1,revenueSharing:!1,dockerServices:["postgres","redis","inngest"],aiTools:[],defaultCurrency:"USD",deploymentTarget:"node",databaseProvider:"postgres",cacheProvider:"redis"};function Ft(e){let t=e.projectName??Vt.projectName,r=e.projectDir??`./${t}`,i=e.appName??Ce(t),n={...Vt,...e,projectName:t,appName:i,projectDir:r};for(let o of ge){if(n.deploymentTarget!==o.target)continue;let a=n.databaseProvider===o.provider?"database":"cache";if(n.databaseProvider===o.provider||n.cacheProvider===o.provider)throw new Error(`Incompatible: --deploy ${o.target} + --${a} ${o.provider}. ${o.reason}`)}return n}function Ut(e,t,r){if(e.trim()==="")return[];let i=e.split(",").map(o=>o.trim()).filter(Boolean),n=i.filter(o=>!t.includes(o));if(n.length>0)throw new Error(`Invalid ${r}(s): ${n.join(", ")}. Valid values: ${t.join(", ")}`);return i}import dn from"picocolors";function Kt(e){e.command("init").description("Scaffold a new GenerateSaaS project").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 Z("--architecture <type>","fullstack or separate").choices([..._e])).addOption(new Z("--payment <provider>","payment provider").choices([...Pe])).addOption(new Z("--email <provider>","email provider").choices([...Re])).option("--org","enable multi-tenancy (organizations)").option("--no-org","disable multi-tenancy").addOption(new Z("--billing-scope <scope>","billing scope (requires --org)").choices([...De])).option("--blog","enable blog").option("--no-blog","disable blog").option("--revenue-sharing","enable revenue sharing").option("--no-revenue-sharing","disable revenue sharing").option("--docker <services>","comma-separated: postgres,redis,inngest,mailpit").option("--ai-tools <tools>","comma-separated: claude-code,cursor,codex,gemini-cli,windsurf").addOption(new Z("--currency <code>","default currency for billing").choices([...ee])).addOption(new Z("--deploy <target>","deployment target").choices([...te])).addOption(new Z("--database <provider>","database provider").choices([...re])).addOption(new Z("--cache <provider>","cache provider").choices([...ne])).option("--api-key <key>","API key (skips interactive prompt)").option("-y, --yes","accept defaults for unspecified options (non-interactive)").action(async t=>{await mn(t)})}async function mn(e){let t=performance.now();rt("0.7.0");let r;try{r=Mt(e)}catch(h){v.cancel(E(h)),process.exit(1)}let i=v.spinner(),n;try{n=await ce({apiKey:e.apiKey,prompt:!e.yes})}catch(h){v.cancel(E(h)),process.exit(1)}let o=D(n);i.start("Verifying access...");let a;try{a=(await C(o)).latest,i.stop("Access verified."),B(n)}catch(h){if(i.stop("Access verification failed."),h instanceof w&&h.status===401){v.log.warning("Invalid API key."),n=await H(),o=D(n),i.start("Verifying access...");try{a=(await C(o)).latest,i.stop("Access verified."),B(n)}catch(b){i.stop("Access verification failed."),v.cancel(b instanceof w&&b.status===401?"Invalid API key.":E(b)),process.exit(1)}}else v.cancel(E(h)),process.exit(1)}v.log.success(`Latest version: ${a}`);let s;i.start("Activating license...");try{let h=crypto.randomUUID();s={token:(await mt(o,{frontend:"nuxt",version:a,installId:h})).token,keyHash:wt(n),installId:h},i.stop("License activated.")}catch(h){i.stop("License activation failed."),v.cancel(E(h)),process.exit(1)}let m;e.yes?m=Ft(r):m=await ot(r);let y=pn(m.projectDir);if(an(y)&&sn(y).length>0)if(e.yes)v.log.info(`Directory ${y} is not empty. Merging (keeping existing files, overwriting conflicts).`);else{let b=await v.select({message:`Directory ${y} 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"}]});(v.isCancel(b)||b==="cancel")&&(v.cancel("Setup cancelled."),process.exit(0)),b==="overwrite"&&cn(y,{recursive:!0,force:!0})}let g={...m,projectDir:y,version:a};i.start("Downloading template...");try{await ye(o,a,y);let h=await Me(y);await d(ln(y,$e),JSON.stringify(h,null," ")+`
554
+ `),O.yellow("Deployment"))}function nn(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 on(e){switch(e.deploymentTarget){case"node":return"Deploy with Docker or your preferred Node.js host";case"cloudflare":return"wrangler deploy # Deploy to Cloudflare Workers";case"vercel":return"vercel deploy # Deploy to Vercel";case"netlify":return"netlify deploy # Deploy to Netlify"}}function Mt(e){let t={};if(e.name!==void 0){if(!xe(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&&(t.frontend=e.frontend),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.revenueSharing!==void 0&&(t.revenueSharing=e.revenueSharing),e.docker!==void 0&&(t.dockerServices=Ut(e.docker,Te,"docker service")),e.aiTools!==void 0&&(t.aiTools=Ut(e.aiTools,ke,"AI tool")),e.currency!==void 0){if(!ee.includes(e.currency))throw new Error(`Invalid currency "${e.currency}". Valid values: ${ee.join(", ")}`);t.defaultCurrency=e.currency}if(e.deploy!==void 0){if(!te.includes(e.deploy))throw new Error(`Invalid deployment target "${e.deploy}". Valid values: ${te.join(", ")}`);t.deploymentTarget=e.deploy}if(e.database!==void 0){if(!re.includes(e.database))throw new Error(`Invalid database provider "${e.database}". Valid values: ${re.join(", ")}`);t.databaseProvider=e.database}if(e.cache!==void 0){if(!ne.includes(e.cache))throw new Error(`Invalid cache provider "${e.cache}". Valid values: ${ne.join(", ")}`);t.cacheProvider=e.cache}return t}var Vt={projectName:"my-saas",frontend:"nuxt",architecture:"fullstack",paymentProvider:"stripe",emailProvider:"smtp",multiTenancy:!1,billingScope:"user",blog:!1,revenueSharing:!1,dockerServices:["postgres","redis","inngest"],aiTools:[],defaultCurrency:"USD",deploymentTarget:"node",databaseProvider:"postgres",cacheProvider:"redis"};function Ft(e){let t=e.projectName??Vt.projectName,r=e.projectDir??`./${t}`,i=e.appName??Ce(t),n={...Vt,...e,projectName:t,appName:i,projectDir:r};for(let o of ge){if(n.deploymentTarget!==o.target)continue;let a=n.databaseProvider===o.provider?"database":"cache";if(n.databaseProvider===o.provider||n.cacheProvider===o.provider)throw new Error(`Incompatible: --deploy ${o.target} + --${a} ${o.provider}. ${o.reason}`)}return n}function Ut(e,t,r){if(e.trim()==="")return[];let i=e.split(",").map(o=>o.trim()).filter(Boolean),n=i.filter(o=>!t.includes(o));if(n.length>0)throw new Error(`Invalid ${r}(s): ${n.join(", ")}. Valid values: ${t.join(", ")}`);return i}import dn from"picocolors";function Kt(e){e.command("init").description("Scaffold a new GenerateSaaS project").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 Z("--architecture <type>","fullstack or separate").choices([..._e])).addOption(new Z("--payment <provider>","payment provider").choices([...Pe])).addOption(new Z("--email <provider>","email provider").choices([...Re])).option("--org","enable multi-tenancy (organizations)").option("--no-org","disable multi-tenancy").addOption(new Z("--billing-scope <scope>","billing scope (requires --org)").choices([...De])).option("--blog","enable blog").option("--no-blog","disable blog").option("--revenue-sharing","enable revenue sharing").option("--no-revenue-sharing","disable revenue sharing").option("--docker <services>","comma-separated: postgres,redis,inngest,mailpit").option("--ai-tools <tools>","comma-separated: claude-code,cursor,codex,gemini-cli,windsurf").addOption(new Z("--currency <code>","default currency for billing").choices([...ee])).addOption(new Z("--deploy <target>","deployment target").choices([...te])).addOption(new Z("--database <provider>","database provider").choices([...re])).addOption(new Z("--cache <provider>","cache provider").choices([...ne])).option("--api-key <key>","API key (skips interactive prompt)").option("-y, --yes","accept defaults for unspecified options (non-interactive)").action(async t=>{await mn(t)})}async function mn(e){let t=performance.now();rt("0.7.1");let r;try{r=Mt(e)}catch(h){v.cancel(E(h)),process.exit(1)}let i=v.spinner(),n;try{n=await ce({apiKey:e.apiKey,prompt:!e.yes})}catch(h){v.cancel(E(h)),process.exit(1)}let o=D(n);i.start("Verifying access...");let a;try{a=(await C(o)).latest,i.stop("Access verified."),B(n)}catch(h){if(i.stop("Access verification failed."),h instanceof w&&h.status===401){v.log.warning("Invalid API key."),n=await H(),o=D(n),i.start("Verifying access...");try{a=(await C(o)).latest,i.stop("Access verified."),B(n)}catch(b){i.stop("Access verification failed."),v.cancel(b instanceof w&&b.status===401?"Invalid API key.":E(b)),process.exit(1)}}else v.cancel(E(h)),process.exit(1)}v.log.success(`Latest version: ${a}`);let s;i.start("Activating license...");try{let h=crypto.randomUUID();s={token:(await mt(o,{frontend:"nuxt",version:a,installId:h})).token,keyHash:wt(n),installId:h},i.stop("License activated.")}catch(h){i.stop("License activation failed."),v.cancel(E(h)),process.exit(1)}let m;e.yes?m=Ft(r):m=await ot(r);let y=pn(m.projectDir);if(an(y)&&sn(y).length>0)if(e.yes)v.log.info(`Directory ${y} is not empty. Merging (keeping existing files, overwriting conflicts).`);else{let b=await v.select({message:`Directory ${y} 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"}]});(v.isCancel(b)||b==="cancel")&&(v.cancel("Setup cancelled."),process.exit(0)),b==="overwrite"&&cn(y,{recursive:!0,force:!0})}let g={...m,projectDir:y,version:a};i.start("Downloading template...");try{await ye(o,a,y);let h=await Me(y);await d(ln(y,$e),JSON.stringify(h,null," ")+`
555
555
  `),await at(y),i.stop("Template downloaded.")}catch(h){i.stop("Download failed."),v.cancel(E(h)),process.exit(1)}let T;i.start("Generating project files...");try{await yt(g),await Tt(g),await It(g),T=await vt(g),await kt(g),await St(g),await ht(g),await gt(g),await Rt(g),await $t(y,g.aiTools),await Pt(g,s),i.stop("Project files generated.")}catch(h){i.stop("Generation failed."),v.cancel(E(h)),process.exit(1)}let N=await Lt(y);await jt(y);let z=Ke("docker"),Y=ze(g).map(h=>h.key).filter(h=>!g.credentials?.[h]);Nt(g,{pnpmInstalled:N,dockerComposeGenerated:T,dockerAvailable:z,skippedCredentials:Y}),nt(),v.log.info(dn.dim(`Done in ${((performance.now()-t)/1e3).toFixed(1)}s`))}import{existsSync as un}from"fs";import{readFile as fn,rm as gn}from"fs/promises";import{join as ve,resolve as yn}from"path";import{mkdtemp as hn}from"fs/promises";import{tmpdir as vn}from"os";import*as A from"@clack/prompts";import pe from"picocolors";function Gt(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=yn(t.cwd??process.cwd()),i=ve(r,q),n;try{n=JSON.parse(await fn(i,"utf-8"))}catch{A.cancel(".generatesaas/manifest.json not found. Run this from a GenerateSaaS project."),process.exit(1)}let o;try{o=await ce()}catch(m){A.cancel(E(m)),process.exit(1)}let a=D(o),s=A.spinner();try{s.start("Verifying access...");let m;try{m=await C(a)}catch(I){if(I instanceof w&&I.status===401)s.stop("Invalid API key."),o=await H(),a=D(o),s.start("Verifying access..."),m=await C(a);else throw I}s.stop("Access verified."),B(o),s.start("Fetching latest skill files...");let y=await dt(a,m.latest);await Ze(r,y.skillMd,y.scripts,n.aiTools);let g=Je(n.aiTools);if(s.stop("Skills updated."),A.log.success(`Skill files installed to ${pe.cyan(g.length.toString())} locations.`),n.licenseToken)try{let I=await ut(a,{currentToken:n.licenseToken,newVersion:n.version});n.licenseToken=I.token,await d(i,JSON.stringify(n,null," ")+`
556
556
  `),A.log.success("License refreshed.")}catch{A.log.warn("License refresh skipped.")}if(n.version===m.latest){A.log.info(`Already on the latest version (${n.version}).`);return}let T=ve(r,ct);s.start(`Downloading v${m.latest} template...`),await ye(a,m.latest,T),s.stop("Template staged.");let N=await pt(a,m.latest);N&&A.note(N,`Changelog v${m.latest}`);let z=ve(r,$e);if(!un(z)){s.start("Computing baseline template hashes (one-time migration)...");let I=await hn(ve(vn(),"gs-update-"));try{await ye(a,n.version,I);let Y=await Me(I);await d(z,JSON.stringify(Y,null," ")+`
557
557
  `)}finally{await gn(I,{recursive:!0,force:!0})}s.stop("Baseline hashes computed.")}if(await d(ve(r,lt),JSON.stringify({currentVersion:n.version,targetVersion:m.latest,changelog:N,stagedAt:new Date().toISOString()},null," ")+`
@@ -564,7 +564,7 @@ ${n}
564
564
  `,` .route("/license", licenseRoutes)
565
565
  `]}];function kn(e){return(e&&e.length>0?e.map(r=>he[r]):Object.values(he)).map(r=>ie(r,We))}function Qe(e){return Ge(e)?(Pn(e,{recursive:!0}),!0):!1}function Dn(e,t){if(!Ge(e))return!1;let r=et(e,"utf-8"),i=r;for(let n of t)i=i.replace(n,"");return i===r?!1:(Wt(e,i,"utf-8"),!0)}function Cn(e){let t=ie(e,".gitignore");if(!Ge(t))return!1;let r=et(t,"utf-8"),i=r.split(`
566
566
  `).filter(n=>!n.includes(".generatesaas")).join(`
567
- `);return i===r?!1:(Wt(t,i,"utf-8"),!0)}function Jt(e){e.command("eject").description("Remove all GenerateSaaS ties \u2014 manifest, license, heartbeat, skills").action(async()=>{let t=process.cwd(),r=ie(t,q),i;try{i=JSON.parse(et(r,"utf-8"))}catch{R.cancel("No GenerateSaaS project found in this directory."),process.exit(1)}let n=await R.text({message:'Type "eject" to confirm (this cannot be undone):',validate:s=>{if(s!=="eject")return'Type "eject" to confirm, or press Ctrl+C to cancel.'}});R.isCancel(n)&&(R.cancel("Eject cancelled."),process.exit(0));let o=[],a=[];for(let s of kn(i.aiTools))Qe(ie(t,s))&&o.push(s);for(let s of Rn)Qe(ie(t,s))&&o.push(s);Qe(ie(t,V))&&o.push(V+"/");for(let s of Tn){let m=ie(t,s.file);Dn(m,s.removals)?a.push(s.file):Ge(m)&&R.log.warn(`Could not auto-modify ${s.file} \u2014 manually remove license/heartbeat references.`)}Cn(t)&&a.push(".gitignore");for(let s of o)R.log.info(`Deleted ${s}`);for(let s of a)R.log.info(`Modified ${s}`);R.log.success("Ejected successfully. This project is now fully standalone.")})}var oe=new xn().name("generatesaas").description("CLI for scaffolding and managing GenerateSaaS projects").version("0.7.0").addHelpText("after",`
567
+ `);return i===r?!1:(Wt(t,i,"utf-8"),!0)}function Jt(e){e.command("eject").description("Remove all GenerateSaaS ties \u2014 manifest, license, heartbeat, skills").action(async()=>{let t=process.cwd(),r=ie(t,q),i;try{i=JSON.parse(et(r,"utf-8"))}catch{R.cancel("No GenerateSaaS project found in this directory."),process.exit(1)}let n=await R.text({message:'Type "eject" to confirm (this cannot be undone):',validate:s=>{if(s!=="eject")return'Type "eject" to confirm, or press Ctrl+C to cancel.'}});R.isCancel(n)&&(R.cancel("Eject cancelled."),process.exit(0));let o=[],a=[];for(let s of kn(i.aiTools))Qe(ie(t,s))&&o.push(s);for(let s of Rn)Qe(ie(t,s))&&o.push(s);Qe(ie(t,V))&&o.push(V+"/");for(let s of Tn){let m=ie(t,s.file);Dn(m,s.removals)?a.push(s.file):Ge(m)&&R.log.warn(`Could not auto-modify ${s.file} \u2014 manually remove license/heartbeat references.`)}Cn(t)&&a.push(".gitignore");for(let s of o)R.log.info(`Deleted ${s}`);for(let s of a)R.log.info(`Modified ${s}`);R.log.success("Ejected successfully. This project is now fully standalone.")})}var oe=new xn().name("generatesaas").description("CLI for scaffolding and managing GenerateSaaS projects").version("0.7.1").addHelpText("after",`
568
568
  Examples:
569
569
  $ generatesaas init Interactive setup
570
570
  $ generatesaas init -n my-app -y Quick setup with defaults
@@ -1,6 +1,7 @@
1
1
  ---
2
- name: generatesaas-update
2
+ name: update
3
3
  description: Update GenerateSaaS project to latest boilerplate version. Handles version checks, file classification, generates an update plan for user review, and applies changes while preserving user customizations.
4
+ disable-model-invocation: true
4
5
  ---
5
6
 
6
7
  # GenerateSaaS Update
@@ -10,7 +10,13 @@ const fs = require("node:fs");
10
10
  const path = require("node:path");
11
11
  const { findProjectRoot, hashFile, walkDir, MANIFEST_FILE, HASHES_FILE, TEMPLATE_HASHES_FILE, STAGING_DIR, STAGING_META_FILE, INTERNAL_DIR } = require("./_helpers.js");
12
12
 
13
- const HASH_EXCLUSIONS = new Set([".git", "node_modules", ".pnpm-store", ".env", "data", INTERNAL_DIR]);
13
+ const HASH_EXCLUSIONS = new Set([
14
+ ".git", "node_modules", ".pnpm-store", ".env", ".env.test",
15
+ ".turbo", ".nuxt", ".output", ".data", "dist", "data",
16
+ ".next", ".svelte-kit", ".netlify", ".wrangler",
17
+ ".devcontainer", "playwright-report", "test-results",
18
+ INTERNAL_DIR,
19
+ ]);
14
20
 
15
21
  /** Check if a relative path should be excluded from hashing. */
16
22
  function shouldExcludeFromHash(relativePath) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "generatesaas",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "description": "CLI for scaffolding and managing GenerateSaaS projects",
6
6
  "bin": {