generatesaas 0.9.2 → 0.10.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 +9 -2
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -630,6 +630,13 @@ INNGEST_BASE_URL = "https://${e.projectName}.pages.dev/api"
630
630
 
631
631
  # Required secrets (set via: wrangler pages secret put <NAME> --project-name=${e.projectName}):
632
632
  ${t}
633
+
634
+ # D1 database for Nuxt Content (create with: wrangler d1 create ${e.projectName}-content)
635
+ # Then replace YOUR_D1_DATABASE_ID with the returned ID.
636
+ [[d1_databases]]
637
+ binding = "DB"
638
+ database_name = "${e.projectName}-content"
639
+ database_id = "YOUR_D1_DATABASE_ID"
633
640
  `;await l(`${e.projectDir}/apps/web-nuxt/wrangler.toml`,r)}async function mn(e){let t=JSON.stringify({rewrites:[{source:"/(.*)",destination:"/api/index"}]},null," ")+`
634
641
  `;await l(`${e.projectDir}/apps/backend/vercel.json`,t),await l(`${e.projectDir}/apps/backend/api/index.ts`,`export { default } from "../src/index.js";
635
642
  `)}async function un(e){await l(`${e.projectDir}/apps/backend/netlify.toml`,`[build]
@@ -845,7 +852,7 @@ ${n}
845
852
  `)}import{relative as vn}from"path";async function Ie(e){let r=(await ce(e,e,fe)).sort(),i=await Promise.all(r.map(async n=>[vn(e,n),await Ge(n)]));return Object.fromEntries(i)}import{copyFile as Sn,mkdir as _n,rm as An}from"fs/promises";import{dirname as In,join as Kt,relative as Tn}from"path";import{existsSync as Pn}from"fs";async function zt(e,t){let r=Kt(t,Fe);Pn(r)&&await An(r,{recursive:!0,force:!0});let i=await ce(e,e,fe);for(let n of i){let o=Tn(e,n),s=Kt(r,o);await _n(In(s),{recursive:!0}),await Sn(n,s)}}import{existsSync as wn}from"fs";import{readFile as Bt,readdir as Rn}from"fs/promises";import{join as M,dirname as bn,resolve as kn,sep as Dn}from"path";import{fileURLToPath as On}from"url";var Te={"claude-code":".claude/skills",cursor:".cursor/skills",codex:".agents/skills","gemini-cli":".gemini/skills",windsurf:".windsurf/skills"},Cn=Object.values(Te),it="generatesaas-update",Gt=bn(On(import.meta.url));function xn(){let e=M(Gt,"skill","content");return wn(e)?e:M(Gt,"content")}function ot(e){return!e||e.length===0?Cn:e.map(t=>Te[t])}async function st(e,t,r,i){let n=ot(i);for(let o of n){let s=M(e,o,it),a=M(s,"scripts"),m=M(s,"references");await Ke(a),await Ke(m),await l(M(s,"SKILL.md"),t.replaceAll("__SKILL_ROOT__",o)),await l(M(m,".gitkeep"),"");for(let[v,S]of Object.entries(r)){let y=kn(a,v);y.startsWith(a+Dn)&&await l(y,S)}}}async function Ht(e,t){let r=xn(),i=await Bt(M(r,"SKILL.md"),"utf-8"),n=M(r,"scripts"),o=await Rn(n),s={};for(let a of o)a!==".gitkeep"&&(s[a]=await Bt(M(n,a),"utf-8"));await st(e,i,s,t)}import{execFile as Nn,execFileSync as Ln}from"child_process";import{access as jn}from"fs/promises";import{join as $n}from"path";import*as F from"@clack/prompts";function Ze(e){try{let t=process.platform==="win32"?"where":"which";return Ln(t,[e],{stdio:"ignore"}),!0}catch{return!1}}function Xe(e,t,r,i=3e5){return new Promise((n,o)=>{Nn(e,t,{cwd:r,timeout:i},s=>{s?o(s):n()})})}async function Yt(e){if(!Ze("pnpm"))return F.log.warn("pnpm not found. Skipping dependency installation."),F.log.info("Install pnpm: https://pnpm.io/installation"),!1;let t=F.spinner();t.start("Installing dependencies (this may take a minute)...");try{return await Xe("pnpm",["install"],e),t.stop("Dependencies installed."),!0}catch{return t.stop("Dependency installation failed."),F.log.warn("pnpm install failed. You can run it manually later."),!1}}async function qt(e){try{return await jn($n(e,".git")),F.log.info("Git repository already exists, skipping init."),!0}catch{}if(!Ze("git"))return F.log.warn("git not found. Skipping repository initialization."),!1;let t=F.spinner();t.start("Initializing git repository...");try{return await Xe("git",["init"],e),await Xe("git",["add","-A"],e),await Xe("git",["commit","-m","Initial commit from GenerateSaaS"],e),t.stop("Git repository initialized."),!0}catch{return t.stop("Git initialization failed."),F.log.warn("You can run git init manually later."),!1}}import*as ge from"@clack/prompts";import L from"picocolors";function Wt(e,t){t.dockerComposeGenerated&&!t.dockerAvailable&&ge.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=>me[s].label).join(", ");r.push(`pnpm infra ${L.dim(`# ${o}`)}`)}if(r.push(`pnpm dev ${L.dim("# http://localhost:3000")}`),t.skippedCredentials.length>0&&(r.push(""),r.push(L.dim("Fill in remaining TODO values in .env"))),ge.note(r.join(`
846
853
  `),L.yellow("Start Development")),t.dockerComposeGenerated){let o=[];o.push(`App ${L.cyan("http://localhost:3000")}`),e.architecture==="separate"&&o.push(`API ${L.cyan("http://localhost:3010")}`),e.dockerServices.includes("mailpit")&&o.push(`Mailpit ${L.cyan("http://localhost:8025")}`),e.dockerServices.includes("inngest")&&o.push(`Inngest ${L.cyan("http://localhost:8288")}`),ge.note(o.join(`
847
854
  `),L.yellow("Dev Tools"))}let i=[],n=Un(e);n.length>0&&i.push(`Set in production: ${L.dim(n.join(", "))}`),i.push("pnpm db:push # Run database migrations"),i.push(Vn(e)),ge.note(i.join(`
848
- `),L.yellow("Deployment"))}function Un(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 Vn(e){switch(e.deploymentTarget){case"node":return"Deploy with Docker or your preferred Node.js host";case"cloudflare":return e.architecture==="fullstack"?"wrangler pages deploy .output/public # Deploy to Cloudflare Pages":"wrangler deploy # Deploy to Cloudflare Workers";case"vercel":return"vercel deploy # Deploy to Vercel";case"netlify":return"netlify deploy # Deploy to Netlify"}}function Xt(e){let t={};if(e.name!==void 0){if(!Ue(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=Jt(e.docker,Ne,"docker service")),e.aiTools!==void 0&&(t.aiTools=Jt(e.aiTools,Le,"AI tool")),e.currency!==void 0){if(!ne.includes(e.currency))throw new Error(`Invalid currency "${e.currency}". Valid values: ${ne.join(", ")}`);t.defaultCurrency=e.currency}if(e.deploy!==void 0){if(!ie.includes(e.deploy))throw new Error(`Invalid deployment target "${e.deploy}". Valid values: ${ie.join(", ")}`);t.deploymentTarget=e.deploy}if(e.database!==void 0){if(!oe.includes(e.database))throw new Error(`Invalid database provider "${e.database}". Valid values: ${oe.join(", ")}`);t.databaseProvider=e.database}if(e.cache!==void 0){if(!se.includes(e.cache))throw new Error(`Invalid cache provider "${e.cache}". Valid values: ${se.join(", ")}`);t.cacheProvider=e.cache}return t}var Z={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 Zt(e){let t=e.projectName??Z.projectName,r=e.projectDir??`./${t}`,i=e.appName??$e(t),n=e.deploymentTarget??Z.deploymentTarget,o=z[n]?.edgeRuntime??!1,s=e.databaseProvider??(o?"neon":Z.databaseProvider),a=e.cacheProvider??(o?"upstash":Z.cacheProvider),m=e.emailProvider??(o?"resend":Z.emailProvider),v=e.dockerServices??(o?Z.dockerServices.filter(y=>y!=="postgres"&&y!=="redis"):Z.dockerServices),S={...Z,...e,projectName:t,appName:i,projectDir:r,deploymentTarget:n,databaseProvider:s,cacheProvider:a,emailProvider:m,dockerServices:v};for(let y of Se){if(S.deploymentTarget!==y.target)continue;let h=S.databaseProvider===y.provider?"database":"cache";if(S.databaseProvider===y.provider||S.cacheProvider===y.provider)throw new Error(`Incompatible: --deploy ${y.target} + --${h} ${y.provider}. ${y.reason}`)}return S}function Jt(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 Gn from"picocolors";function Hn(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 Qt(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 Q("--architecture <type>","fullstack or separate").choices([...Oe])).addOption(new Q("--payment <provider>","payment provider").choices([...Ce])).addOption(new Q("--email <provider>","email provider").choices([...xe])).option("--org","enable multi-tenancy (organizations)").option("--no-org","disable multi-tenancy").addOption(new Q("--billing-scope <scope>","billing scope (requires --org)").choices([...je])).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 Q("--currency <code>","default currency for billing").choices([...ne])).addOption(new Q("--deploy <target>","deployment target").choices([...ie])).addOption(new Q("--database <provider>","database provider").choices([...oe])).addOption(new Q("--cache <provider>","cache provider").choices([...se])).option("--template-version <version>","specific template version to scaffold").option("--api-key <key>","API key (skips interactive prompt)").option("-y, --yes","accept defaults for unspecified options (non-interactive)").action(async t=>{await Yn(t)})}async function Yn(e){let t=performance.now();dt("0.9.2");let r,i;try{r=Xt(e),i=Hn(e.templateVersion)}catch(f){_.cancel(T(f)),process.exit(1)}let n=_.spinner(),o;try{o=await ue({apiKey:e.apiKey,prompt:!e.yes})}catch(f){_.cancel(T(f)),process.exit(1)}let s=N(o),a=async()=>{let f=await V(s),I=f.latest,c=i??I;if(i&&!f.versions.some(p=>p.version===c))throw new Error(`Template version "${i}" is not available.`);return{latestVersion:I,selectedVersion:c}};n.start("Verifying access...");let m,v;try{({latestVersion:m,selectedVersion:v}=await a()),n.stop("Access verified."),Y(o)}catch(f){if(n.stop("Access verification failed."),f instanceof R&&f.status===401){e.yes&&(_.cancel("Invalid API key. Cannot prompt in non-interactive mode."),process.exit(1)),_.log.warning("Invalid API key."),o=await q(),s=N(o),n.start("Verifying access...");try{({latestVersion:m,selectedVersion:v}=await a()),n.stop("Access verified."),Y(o)}catch(I){n.stop("Access verification failed."),_.cancel(I instanceof R&&I.status===401?"Invalid API key.":T(I)),process.exit(1)}}else _.cancel(T(f)),process.exit(1)}_.log.success(`Latest version: ${m}`),v!==m&&_.log.success(`Using template version: ${v}`);let S;n.start("Activating license...");try{let f=crypto.randomUUID();S={token:(await _t(s,{frontend:"nuxt",version:v,installId:f})).token,keyHash:Ct(o),installId:f},n.stop("License activated.")}catch(f){n.stop("License activation failed."),_.cancel(T(f)),process.exit(1)}let y;e.yes?y=Zt(r):y=await ft(r);let h=Bn(y.projectDir);if(Mn(h)&&Fn(h).length>0)if(e.yes)_.log.info(`Directory ${h} is not empty. Merging (keeping existing files, overwriting conflicts).`);else{let I=await _.select({message:`Directory ${h} 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"}]});(_.isCancel(I)||I==="cancel")&&(_.cancel("Setup cancelled."),process.exit(0)),I==="overwrite"&&Kn(h,{recursive:!0,force:!0})}let E={...y,projectDir:h,version:v};n.start("Downloading template...");try{await _e(s,v,h);let f=await Ie(h);await l(zn(h,Me),JSON.stringify(f,null," ")+`
855
+ `),L.yellow("Deployment"))}function Un(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 Vn(e){switch(e.deploymentTarget){case"node":return"Deploy with Docker or your preferred Node.js host";case"cloudflare":return e.architecture==="fullstack"?"wrangler pages deploy .output/public # Deploy to Cloudflare Pages":"wrangler deploy # Deploy to Cloudflare Workers";case"vercel":return"vercel deploy # Deploy to Vercel";case"netlify":return"netlify deploy # Deploy to Netlify"}}function Xt(e){let t={};if(e.name!==void 0){if(!Ue(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=Jt(e.docker,Ne,"docker service")),e.aiTools!==void 0&&(t.aiTools=Jt(e.aiTools,Le,"AI tool")),e.currency!==void 0){if(!ne.includes(e.currency))throw new Error(`Invalid currency "${e.currency}". Valid values: ${ne.join(", ")}`);t.defaultCurrency=e.currency}if(e.deploy!==void 0){if(!ie.includes(e.deploy))throw new Error(`Invalid deployment target "${e.deploy}". Valid values: ${ie.join(", ")}`);t.deploymentTarget=e.deploy}if(e.database!==void 0){if(!oe.includes(e.database))throw new Error(`Invalid database provider "${e.database}". Valid values: ${oe.join(", ")}`);t.databaseProvider=e.database}if(e.cache!==void 0){if(!se.includes(e.cache))throw new Error(`Invalid cache provider "${e.cache}". Valid values: ${se.join(", ")}`);t.cacheProvider=e.cache}return t}var Z={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 Zt(e){let t=e.projectName??Z.projectName,r=e.projectDir??`./${t}`,i=e.appName??$e(t),n=e.deploymentTarget??Z.deploymentTarget,o=z[n]?.edgeRuntime??!1,s=e.databaseProvider??(o?"neon":Z.databaseProvider),a=e.cacheProvider??(o?"upstash":Z.cacheProvider),m=e.emailProvider??(o?"resend":Z.emailProvider),v=e.dockerServices??(o?Z.dockerServices.filter(y=>y!=="postgres"&&y!=="redis"):Z.dockerServices),S={...Z,...e,projectName:t,appName:i,projectDir:r,deploymentTarget:n,databaseProvider:s,cacheProvider:a,emailProvider:m,dockerServices:v};for(let y of Se){if(S.deploymentTarget!==y.target)continue;let h=S.databaseProvider===y.provider?"database":"cache";if(S.databaseProvider===y.provider||S.cacheProvider===y.provider)throw new Error(`Incompatible: --deploy ${y.target} + --${h} ${y.provider}. ${y.reason}`)}return S}function Jt(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 Gn from"picocolors";function Hn(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 Qt(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 Q("--architecture <type>","fullstack or separate").choices([...Oe])).addOption(new Q("--payment <provider>","payment provider").choices([...Ce])).addOption(new Q("--email <provider>","email provider").choices([...xe])).option("--org","enable multi-tenancy (organizations)").option("--no-org","disable multi-tenancy").addOption(new Q("--billing-scope <scope>","billing scope (requires --org)").choices([...je])).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 Q("--currency <code>","default currency for billing").choices([...ne])).addOption(new Q("--deploy <target>","deployment target").choices([...ie])).addOption(new Q("--database <provider>","database provider").choices([...oe])).addOption(new Q("--cache <provider>","cache provider").choices([...se])).option("--template-version <version>","specific template version to scaffold").option("--api-key <key>","API key (skips interactive prompt)").option("-y, --yes","accept defaults for unspecified options (non-interactive)").action(async t=>{await Yn(t)})}async function Yn(e){let t=performance.now();dt("0.10.0");let r,i;try{r=Xt(e),i=Hn(e.templateVersion)}catch(f){_.cancel(T(f)),process.exit(1)}let n=_.spinner(),o;try{o=await ue({apiKey:e.apiKey,prompt:!e.yes})}catch(f){_.cancel(T(f)),process.exit(1)}let s=N(o),a=async()=>{let f=await V(s),I=f.latest,c=i??I;if(i&&!f.versions.some(p=>p.version===c))throw new Error(`Template version "${i}" is not available.`);return{latestVersion:I,selectedVersion:c}};n.start("Verifying access...");let m,v;try{({latestVersion:m,selectedVersion:v}=await a()),n.stop("Access verified."),Y(o)}catch(f){if(n.stop("Access verification failed."),f instanceof R&&f.status===401){e.yes&&(_.cancel("Invalid API key. Cannot prompt in non-interactive mode."),process.exit(1)),_.log.warning("Invalid API key."),o=await q(),s=N(o),n.start("Verifying access...");try{({latestVersion:m,selectedVersion:v}=await a()),n.stop("Access verified."),Y(o)}catch(I){n.stop("Access verification failed."),_.cancel(I instanceof R&&I.status===401?"Invalid API key.":T(I)),process.exit(1)}}else _.cancel(T(f)),process.exit(1)}_.log.success(`Latest version: ${m}`),v!==m&&_.log.success(`Using template version: ${v}`);let S;n.start("Activating license...");try{let f=crypto.randomUUID();S={token:(await _t(s,{frontend:"nuxt",version:v,installId:f})).token,keyHash:Ct(o),installId:f},n.stop("License activated.")}catch(f){n.stop("License activation failed."),_.cancel(T(f)),process.exit(1)}let y;e.yes?y=Zt(r):y=await ft(r);let h=Bn(y.projectDir);if(Mn(h)&&Fn(h).length>0)if(e.yes)_.log.info(`Directory ${h} is not empty. Merging (keeping existing files, overwriting conflicts).`);else{let I=await _.select({message:`Directory ${h} 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"}]});(_.isCancel(I)||I==="cancel")&&(_.cancel("Setup cancelled."),process.exit(0)),I==="overwrite"&&Kn(h,{recursive:!0,force:!0})}let E={...y,projectDir:h,version:v};n.start("Downloading template...");try{await _e(s,v,h);let f=await Ie(h);await l(zn(h,Me),JSON.stringify(f,null," ")+`
849
856
  `),await zt(h,h),await gt(h),n.stop("Template downloaded.")}catch(f){n.stop("Download failed."),_.cancel(T(f)),process.exit(1)}let O;n.start("Generating project files...");try{await Pt(E),await Mt(E),await Dt(E),O=await Rt(E),await Ft(E),await bt(E),await wt(E),await Tt(E),await jt(E),await Ut(E),await $t(E),await Ht(h,E.aiTools),await Lt(E),await Nt(E,S),n.stop("Project files generated.")}catch(f){n.stop("Generation failed."),_.cancel(T(f)),process.exit(1)}let de=await Yt(h);await qt(h);let ee=Ze("docker"),te=tt(E).map(f=>f.key).filter(f=>!E.credentials?.[f]);Wt(E,{pnpmInstalled:de,dockerComposeGenerated:O,dockerAvailable:ee,skippedCredentials:te}),mt(),_.log.info(Gn.dim(`Done in ${((performance.now()-t)/1e3).toFixed(1)}s`))}import{existsSync as er}from"fs";import{readFile as qn}from"fs/promises";import{join as Pe,resolve as Wn}from"path";import*as w from"@clack/prompts";import ye from"picocolors";function tr(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=Wn(t.cwd??process.cwd()),i=Pe(r,W),n;try{n=JSON.parse(await qn(i,"utf-8"))}catch{w.cancel(".generatesaas/manifest.json not found. Run this from a GenerateSaaS project."),process.exit(1)}let o;try{o=await ue()}catch(m){w.cancel(T(m)),process.exit(1)}let s=N(o),a=w.spinner();try{a.start("Verifying access...");let m;try{m=await V(s)}catch(P){if(P instanceof R&&P.status===401)a.stop("Invalid API key."),o=await q(),s=N(o),a.start("Verifying access..."),m=await V(s);else throw P}a.stop("Access verified."),Y(o),a.start("Fetching latest skill files...");let v=await St(s,m.latest);await st(r,v.skillMd,v.scripts,n.aiTools);let S=ot(n.aiTools);if(a.stop("Skills updated."),w.log.success(`Skill files installed to ${ye.cyan(S.length.toString())} locations.`),n.licenseToken)try{let P=await At(s,{currentToken:n.licenseToken,newVersion:n.version});n.licenseToken=P.token,await l(i,JSON.stringify(n,null," ")+`
850
857
  `),w.log.success("License refreshed.")}catch{w.log.warn("License refresh skipped.")}if(n.version===m.latest){w.log.info(`Already on the latest version (${n.version}).`);return}let y=Pe(r,ht);a.start(`Downloading v${m.latest} template...`),await _e(s,m.latest,y),a.stop("Template staged.");let h=await vt(s,m.latest);h&&w.note(h,`Changelog v${m.latest}`);let E=Pe(r,Me),O=Pe(r,Fe),de=!er(O),ee=!er(E);if(de){if(a.start("Downloading baseline template (one-time migration)..."),await _e(s,n.version,O),ee){let P=await Ie(O);await l(E,JSON.stringify(P,null," ")+`
851
858
  `)}a.stop("Baseline template stored.")}else if(ee){a.start("Computing baseline template hashes...");let P=await Ie(O);await l(E,JSON.stringify(P,null," ")+`
@@ -859,7 +866,7 @@ ${n}
859
866
  `,` .route("/license", licenseRoutes)
860
867
  `]}];function si(e){return(e&&e.length>0?e.map(r=>Te[r]):Object.values(Te)).map(r=>pe(r,it))}function ct(e){return Qe(e)?(ni(e,{recursive:!0}),!0):!1}function ai(e,t){if(!Qe(e))return!1;let r=pt(e,"utf-8"),i=r;for(let n of t)i=i.replace(n,"");return i===r?!1:(ar(e,i,"utf-8"),!0)}function ci(e){let t=pe(e,".gitignore");if(!Qe(t))return!1;let r=pt(t,"utf-8"),i=r.split(`
861
868
  `).filter(n=>!n.includes(".generatesaas")).join(`
862
- `);return i===r?!1:(ar(t,i,"utf-8"),!0)}function cr(e){e.command("eject").description("Remove all GenerateSaaS ties \u2014 manifest, license, heartbeat, skills").action(async()=>{let t=process.cwd(),r=pe(t,W),i;try{i=JSON.parse(pt(r,"utf-8"))}catch{D.cancel("No GenerateSaaS project found in this directory."),process.exit(1)}let n=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.'}});D.isCancel(n)&&(D.cancel("Eject cancelled."),process.exit(0));let o=[],s=[];for(let a of si(i.aiTools))ct(pe(t,a))&&o.push(a);for(let a of ii)ct(pe(t,a))&&o.push(a);ct(pe(t,x))&&o.push(x+"/");for(let a of oi){let m=pe(t,a.file);ai(m,a.removals)?s.push(a.file):Qe(m)&&D.log.warn(`Could not auto-modify ${a.file} \u2014 manually remove license/heartbeat references.`)}ci(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 le=new pi().name("generatesaas").description("CLI for scaffolding and managing GenerateSaaS projects").version("0.9.2").addHelpText("after",`
869
+ `);return i===r?!1:(ar(t,i,"utf-8"),!0)}function cr(e){e.command("eject").description("Remove all GenerateSaaS ties \u2014 manifest, license, heartbeat, skills").action(async()=>{let t=process.cwd(),r=pe(t,W),i;try{i=JSON.parse(pt(r,"utf-8"))}catch{D.cancel("No GenerateSaaS project found in this directory."),process.exit(1)}let n=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.'}});D.isCancel(n)&&(D.cancel("Eject cancelled."),process.exit(0));let o=[],s=[];for(let a of si(i.aiTools))ct(pe(t,a))&&o.push(a);for(let a of ii)ct(pe(t,a))&&o.push(a);ct(pe(t,x))&&o.push(x+"/");for(let a of oi){let m=pe(t,a.file);ai(m,a.removals)?s.push(a.file):Qe(m)&&D.log.warn(`Could not auto-modify ${a.file} \u2014 manually remove license/heartbeat references.`)}ci(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 le=new pi().name("generatesaas").description("CLI for scaffolding and managing GenerateSaaS projects").version("0.10.0").addHelpText("after",`
863
870
  Examples:
864
871
  $ generatesaas init Interactive setup
865
872
  $ generatesaas init -n my-app -y Quick setup with defaults
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "generatesaas",
3
- "version": "0.9.2",
3
+ "version": "0.10.0",
4
4
  "type": "module",
5
5
  "description": "CLI for scaffolding and managing GenerateSaaS projects",
6
6
  "bin": {