generatesaas 1.3.0 → 1.4.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.
- package/dist/index.js +3 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import{Command as Qo}from"commander";import*as wn from"@clack/prompts";import{ex
|
|
|
6
6
|
`),O=[f.bold("Project"),c,"",f.bold("Infrastructure"),p,"",f.bold("Features"),y];if(Fe.length>0){let ne=Fe.map(Vt=>{let bn=Re[Vt.key]?f.green("provided"):f.dim("skipped");return` ${Vt.key}: ${bn}`}).join(`
|
|
7
7
|
`);O.push("",f.bold("Credentials"),ne)}u.note(O.join(`
|
|
8
8
|
`),"Summary");let D=await u.confirm({message:"Proceed with these settings?"});(u.isCancel(D)||!D)&&(u.cancel("Setup cancelled."),process.exit(0))}return{projectName:r,appName:i,projectDir:n,frontend:o,architecture:g,deploymentTarget:s,databaseProvider:m,cacheProvider:v,paymentProvider:b,emailProvider:R,multiTenancy:te,billingScope:re,blog:A,docs:S,revenueSharing:P,credits:_e,dockerServices:gt,aiTools:ht,socialProviders:he,defaultCurrency:H,...Object.keys(Re).length>0?{credentials:Re}:{},...e?.baseUrl!==void 0?{baseUrl:e.baseUrl}:{},...yt!==void 0?{demo:yt}:{}}}import{createReadStream as Dn}from"fs";import{mkdir as xn}from"fs/promises";import{Readable as Cn}from"stream";import{pipeline as Qt}from"stream/promises";import{extract as Nn}from"tar";import{join as le}from"path";import{homedir as Tn}from"os";var rt=process.env.GENERATESAAS_API_URL??"https://cli.generatesaas.com",U=".generatesaas",W=le(U,"manifest.json"),zt=le(U,"hashes.json"),nt=le(U,"template-hashes.json"),it=le(U,"template"),Ht=le(U,"staging"),Yt=le(U,"staging.json"),J=le(Tn(),".generatesaas");var _=class extends Error{constructor(r,i){super(i);this.status=r}status;name="ApiError"};function q(e){return{apiKey:e,baseUrl:rt}}async function X(e,t,r){let i=`${e.baseUrl}${t}`,n=await fetch(i,{...r,headers:{...r?.headers,Authorization:`Bearer ${e.apiKey}`,"User-Agent":"generatesaas-cli"}});if(!n.ok){let o;try{o=(await n.json()).error??`API ${n.status}: ${t}`}catch{o=`API ${n.status}: ${t}`}throw new _(n.status,o)}return n}import{existsSync as In,readFileSync as An,writeFileSync as kn,mkdirSync as Pn}from"fs";import{dirname as _n}from"path";import*as Z from"@clack/prompts";function Et(){if(!In(J))return null;try{let e=JSON.parse(An(J,"utf-8"));return e.apiKey?e.apiKey:(e.token&&!e.apiKey&&Z.log.warning(`Found old GitHub token in ${J}. Run 'generatesaas init' to set up your API key.`),null)}catch{return null}}function pe(e){Pn(_n(J),{recursive:!0}),kn(J,JSON.stringify({apiKey:e},null," ")+`
|
|
9
|
-
`,{mode:384})}async function ve(e){if(e?.apiKey)return e.apiKey;let t=process.env.GENERATESAAS_API_KEY;if(t)return t;let r=Et();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 Ue()}async function Ue(){let e=await Z.text({message:"Enter your GenerateSaaS API key:",placeholder:"gs_live_...",validate:t=>{if(!t?.trim())return"API key is required."}});return Z.isCancel(e)&&(Z.cancel("Setup cancelled."),process.exit(0)),e.trim()}async function Q(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 X(e,"/versions")).json()}async function Jt(e,t){try{return await(await X(e,`/changelog/${encodeURIComponent(t)}`)).text()}catch(r){if(r instanceof _&&r.status===404)return null;throw r}}async function qt(e,t){return await(await X(e,`/skill/${encodeURIComponent(t)}`)).json()}async function Wt(e,t){return process.env.GENERATESAAS_OFFLINE_LICENSE==="1"?{token:"offline-test-token",licenseId:"offline-test-license-id"}:await(await X(e,"/license/sign",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).json()}async function Xt(e,t){return await(await X(e,"/license/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).json()}async function Zt(e,t){let r=await fetch(`${e}/license/verify`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:t})});if(!r.ok)throw new Error(`Verification service returned ${r.status}`);return await r.json()}var wt=new Set([".git","node_modules",".pnpm-store",".env",".env.test",".turbo",".nuxt",".output",".data","dist",".next",".svelte-kit",".devcontainer","playwright-report","test-results"]),Rn=new Set(["data","mksaas","references","scripts",".cursor",".agents",".codex",".generatesaas",".vscode",".mcp.json","
|
|
9
|
+
`,{mode:384})}async function ve(e){if(e?.apiKey)return e.apiKey;let t=process.env.GENERATESAAS_API_KEY;if(t)return t;let r=Et();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 Ue()}async function Ue(){let e=await Z.text({message:"Enter your GenerateSaaS API key:",placeholder:"gs_live_...",validate:t=>{if(!t?.trim())return"API key is required."}});return Z.isCancel(e)&&(Z.cancel("Setup cancelled."),process.exit(0)),e.trim()}async function Q(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 X(e,"/versions")).json()}async function Jt(e,t){try{return await(await X(e,`/changelog/${encodeURIComponent(t)}`)).text()}catch(r){if(r instanceof _&&r.status===404)return null;throw r}}async function qt(e,t){return await(await X(e,`/skill/${encodeURIComponent(t)}`)).json()}async function Wt(e,t){return process.env.GENERATESAAS_OFFLINE_LICENSE==="1"?{token:"offline-test-token",licenseId:"offline-test-license-id"}:await(await X(e,"/license/sign",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).json()}async function Xt(e,t){return await(await X(e,"/license/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).json()}async function Zt(e,t){let r=await fetch(`${e}/license/verify`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:t})});if(!r.ok)throw new Error(`Verification service returned ${r.status}`);return await r.json()}var wt=new Set([".git","node_modules",".pnpm-store",".env",".env.test",".turbo",".nuxt",".output",".data","dist",".next",".svelte-kit",".devcontainer","playwright-report","test-results"]),Rn=new Set(["data","mksaas","references","scripts",".cursor",".agents",".codex",".generatesaas",".vscode",".mcp.json","README.md","TODO.md","OVERVIEW.md"]),On=["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 Se(e){let t=e.split("/");for(let r of t)if(wt.has(r))return!0;if(Rn.has(t[0]))return!0;for(let r of On)if(e===r||e.startsWith(r+"/"))return!0;return!1}async function ot(e,t,r){await xn(r,{recursive:!0});let i=process.env.GENERATESAAS_TEMPLATE_TARBALL;if(i){await Qt(Dn(i),er(r));return}let n=await X(e,`/template/${encodeURIComponent(t)}`);if(!n.body)throw new Error("Empty response body");let o=Cn.fromWeb(n.body);await Qt(o,er(r))}function er(e){return Nn({cwd:e,strip:1,filter:t=>{let r=t.replace(/^[^/]+\//,"");return r?!Se(r):!0},sync:!1})}import{readFile as Ln,rm as tr,writeFile as Un}from"fs/promises";import{join as bt}from"path";var $n=["apps/web-nuxt/public/images/blog","apps/web-next/public/images/blog"];async function rr(e){await Promise.all($n.map(t=>tr(bt(e,t),{recursive:!0,force:!0})))}async function nr(e,t){t.includes("claude-code")||await tr(bt(e,".claude"),{recursive:!0,force:!0})}async function ir(e,t){let r=bt(e,".claude","settings.json"),i;try{i=await Ln(r,"utf8")}catch{return}let n=JSON.parse(i);delete n.alwaysThinkingEnabled,delete n.enableAllProjectMcpServers,n.env&&(delete n.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS,Object.keys(n.env).length===0&&delete n.env),n.permissions?.allow&&(n.permissions.allow=n.permissions.allow.filter(o=>jn(o,t))),await Un(r,JSON.stringify(n,null," ")+`
|
|
10
10
|
`)}function jn(e,t){return!(e.startsWith("mcp__")||t!=="nuxt"&&e.includes("nuxt"))}import{join as or}from"path";import{mkdir as Mn,readdir as Vn,rm as Fn,rmdir as Bn,writeFile as Gn}from"fs/promises";import{dirname as st,join as Kn,relative as zn}from"path";async function at(e){await Mn(e,{recursive:!0})}async function l(e,t){await at(st(e)),await Gn(e,t,"utf-8")}async function Tt(e,t){await Fn(e,{force:!0});let r=st(e);for(;r!==t&&r!==st(r);){try{await Bn(r)}catch{return}r=st(r)}}async function de(e,t,r){let i=[],n=await Vn(e,{withFileTypes:!0});for(let o of n){let s=Kn(e,o.name),a=zn(t,s);r(a)||(o.isDirectory()?i.push(...await de(s,t,r)):o.isFile()&&i.push(s))}return i}var Hn={postgres:"Postgres",neon:"Neon (managed Postgres)",supabase:"Supabase (managed Postgres)"},Yn={resend:"Resend",ses:"Amazon SES",smtp:"SMTP"},Jn={redis:"Redis",upstash:"Upstash Redis"},qn={node:"Node.js / Docker",vercel:"Vercel"},Wn={stripe:"Stripe",polar:"Polar"};function Xn(e){let t=e.frontend==="nuxt",r=t?"Nuxt 4":"Next.js 16",i=t?"apps/web-nuxt":"apps/web-next",n=t?"`app/` pages + components + composables":"`app/` routes, `components/`, `lib/`",o=e.architecture==="fullstack",s=o?"(fullstack - Hono API mounted inside the app)":"(separate - standalone Hono backend)",a=o?`Mounted inside \`${i}\`. A standalone \`apps/backend\` is also kept (inert) so you can switch to separate later.`:"Runs from `apps/backend`; the frontend reaches it over HTTP.",d=Jn[e.cacheProvider],h=qn[e.deploymentTarget],g=e.paymentProvider==="none"?"":`
|
|
11
11
|
- **Payments:** ${Wn[e.paymentProvider]}`,m=t?"`$t('key')` in templates (global helper); `useI18n()` from `vue-i18n` in `<script setup>` for `locale`/`setLocale`/`t()`.":"`useTranslations()` from `next-intl` in components; messages are loaded in `apps/web-next/i18n/request.ts`.",v=t?"**Navigation:** always use `localePath()` for paths (hardcoded paths break non-default locales); `await navigateTo()` in SSR.":"**Navigation:** `next/link` for links; `redirect()` from `next/navigation` for programmatic redirects in Server Components.";return`# AGENTS.md
|
|
12
12
|
|
|
@@ -1108,7 +1108,7 @@ export { ops } from "./${r}/index";
|
|
|
1108
1108
|
${m}`:s.message))}else n()})})}async function rn(e){if(!me("pnpm"))return T.log.warn("pnpm not found. Skipping lockfile regeneration."),!1;try{return await ue("pnpm",["install","--lockfile-only","--no-frozen-lockfile","--config.minimumReleaseAge=0"],e),!0}catch(t){let r=t instanceof Error?t.message:String(t);return T.log.warn(`Lockfile regeneration failed: ${r}`),T.log.warn("Deploys using --frozen-lockfile may fail."),!1}}async function nn(e){if(!me("pnpm"))return T.log.warn("pnpm not found. Skipping dependency installation."),T.log.info("Install pnpm: https://pnpm.io/installation"),!1;let t=T.spinner();t.start("Installing dependencies (this may take a minute)...");try{return await ue("pnpm",["install","--config.minimumReleaseAge=0"],e),t.stop("Dependencies installed."),!0}catch(r){t.stop("Dependency installation failed.");let i=r instanceof Error?r.message:String(r);return T.log.warn(`pnpm install failed: ${i}`),T.log.warn("You can run it manually later."),!1}}async function on(e){if(!me("pnpm"))return!1;let t=T.spinner();t.start("Generating baseline database migration...");try{return await ue("pnpm",["-F","@repo/database","generate"],e),t.stop("Baseline migration generated."),!0}catch(r){t.stop("Baseline migration generation failed.");let i=r instanceof Error?r.message:String(r);return T.log.warn(`Could not generate baseline migration: ${i}`),T.log.warn("Run 'pnpm -F @repo/database generate' before your first deploy."),!1}}async function sn(e){try{return await tn(Nt(e,".git")),T.log.info("Git repository already exists, skipping init."),!0}catch{}if(!me("git"))return T.log.warn("git not found. Skipping repository initialization."),!1;let t=T.spinner();t.start("Initializing git repository...");try{return await ue("git",["init"],e),await ue("git",["add","-A"],e),await ue("git",["commit","--no-verify","-m","Initial commit from GenerateSaaS"],e),t.stop("Git repository initialized."),!0}catch{return t.stop("Git initialization failed."),T.log.warn("You can run git init manually later."),!1}}async function an(e){if(!me("pnpm"))return!1;try{await tn(Nt(e,".git"))}catch{return!1}try{let t=JSON.parse(await bo(Nt(e,"package.json"),"utf-8")),r=!!t.devDependencies?.["simple-git-hooks"],i=!!t["simple-git-hooks"];if(!r||!i)return!1}catch{return!1}try{return await ue("pnpm",["exec","simple-git-hooks"],e),!0}catch{return T.log.warn("Could not install git hooks. Run 'pnpm exec simple-git-hooks' manually."),!1}}import*as ke from"@clack/prompts";import $ from"picocolors";function cn(e,t){t.dockerComposeGenerated&&!t.dockerAvailable&&ke.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=>ye[s].label).join(", ");r.push(`pnpm infra ${$.dim(`# ${o}`)}`)}if(r.push(`pnpm dev ${$.dim("# http://localhost:3000")}`),t.skippedCredentials.length>0&&(r.push(""),r.push($.dim("Fill in remaining TODO values in .env"))),ke.note(r.join(`
|
|
1109
1109
|
`),$.yellow("Start Development")),t.dockerComposeGenerated){let o=[];o.push(`App ${$.cyan("http://localhost:3000")}`),e.architecture==="separate"&&o.push(`API ${$.cyan("http://localhost:3010")}`),e.dockerServices.includes("mailpit")&&o.push(`Mailpit ${$.cyan("http://localhost:8025")}`),e.dockerServices.includes("inngest")&&o.push(`Inngest ${$.cyan("http://localhost:8288")}`),ke.note(o.join(`
|
|
1110
1110
|
`),$.yellow("Dev Tools"))}let i=[],n=To(e);n.length>0&&i.push(`Set in production: ${$.dim(n.join(", "))}`),i.push("pnpm db:push # Run database migrations"),i.push(Io(e)),ke.note(i.join(`
|
|
1111
|
-
`),$.yellow("Deployment"))}function To(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 Io(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 ln(e){let t={};if(e.name!==void 0){if(!tt(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(!Le.includes(e.frontend))throw new Error(`Invalid frontend "${e.frontend}". Valid values: ${Le.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=Lt(e.docker,We,"docker service")),e.aiTools!==void 0&&(t.aiTools=Lt(e.aiTools,Xe,"AI tool")),e.socialProviders!==void 0&&(t.socialProviders=Lt(e.socialProviders,Qe,"social provider")),e.currency!==void 0){if(!oe.includes(e.currency))throw new Error(`Invalid currency "${e.currency}". Valid values: ${oe.join(", ")}`);t.defaultCurrency=e.currency}if(e.deploy!==void 0){if(!se.includes(e.deploy))throw new Error(`Invalid deployment target "${e.deploy}". Valid values: ${se.join(", ")}`);t.deploymentTarget=e.deploy}if(e.database!==void 0){if(!ae.includes(e.database))throw new Error(`Invalid database provider "${e.database}". Valid values: ${ae.join(", ")}`);t.databaseProvider=e.database}if(e.cache!==void 0){if(!ce.includes(e.cache))throw new Error(`Invalid cache provider "${e.cache}". Valid values: ${ce.join(", ")}`);t.cacheProvider=e.cache}if(e.demo===!0&&(t.demo=!0),e.baseUrl!==void 0){let r=e.baseUrl.trim();if(r==="")throw new Error("--base-url cannot be empty. Provide an absolute URL like https://example.com.");let i;try{i=new URL(r)}catch{throw new Error(`Invalid --base-url "${e.baseUrl}". Must be an absolute URL like https://example.com.`)}if(i.protocol!=="http:"&&i.protocol!=="https:")throw new Error(`Invalid --base-url "${e.baseUrl}". Must use http or https.`);t.baseUrl=`${i.protocol}//${i.host}`}return t}var ee={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 pn(e){let t=e.projectName??ee.projectName,r=e.projectDir??`./${t}`,i=e.appName??et(t),n=e.deploymentTarget??ee.deploymentTarget,o=F[n]?.edgeRuntime??!1,s=e.databaseProvider??(o?"neon":ee.databaseProvider),a=e.cacheProvider??(o?"upstash":ee.cacheProvider),d=e.emailProvider??(o?"resend":ee.emailProvider),h=e.dockerServices??(o?ee.dockerServices.filter(m=>m!=="postgres"&&m!=="redis"):ee.dockerServices),g={...ee,...e,projectName:t,appName:i,projectDir:r,deploymentTarget:n,databaseProvider:s,cacheProvider:a,emailProvider:d,dockerServices:h};g.paymentProvider==="none"&&(g.credits=!1);for(let m of Ce){if(g.deploymentTarget!==m.target)continue;let v=g.databaseProvider===m.provider?"database":"cache";if(g.databaseProvider===m.provider||g.cacheProvider===m.provider)throw new Error(`Incompatible: --deploy ${m.target} + --${v} ${m.provider}. ${m.reason}`)}for(let m of Ne)if(g.architecture===m.architecture&&g.deploymentTarget===m.target)throw new Error(`Incompatible: --architecture ${m.architecture} + --deploy ${m.target}. ${m.reason}`);return g}function Lt(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 Oo from"picocolors";var Do="c81635e3715dc7274f33a745e24a394d93d3d081a4004408585c3de86e1aac35";function xo(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 dn(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 j("--frontend <type>","frontend framework").choices([...Le])).addOption(new j("--architecture <type>","fullstack or separate").choices([...Ye])).addOption(new j("--payment <provider>","payment provider").choices([...Je])).addOption(new j("--email <provider>","email provider").choices([...qe])).option("--org","enable multi-tenancy (organizations)").option("--no-org","disable multi-tenancy").addOption(new j("--billing-scope <scope>","billing scope (requires --org)").choices([...Ze])).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 j("--currency <code>","default currency for billing").choices([...oe])).addOption(new j("--deploy <target>","deployment target").choices([...se])).addOption(new j("--database <provider>","database provider").choices([...ae])).addOption(new j("--cache <provider>","cache provider").choices([...ce])).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 j("--demo","first-party demo build: keep sample content, mark site non-indexable - requires CI API key").hideHelp()).addOption(new j("--no-db-migration","skip generating the baseline DB migration (internal: demos/CI/playground)").hideHelp()).action(async t=>{await Co(t)})}async function Co(e){let t=performance.now();Ft("1.3.0");let r,i;try{r=ln(e),i=xo(e.templateVersion)}catch(S){E.cancel(I(S)),process.exit(1)}let n=E.spinner(),o;try{o=await ve({apiKey:e.apiKey,prompt:!e.yes})}catch(S){E.cancel(I(S)),process.exit(1)}e.demo&&Rt(o)!==Do&&(E.cancel("--demo is restricted to first-party demo deployments."),process.exit(1));let s=q(o),a=async()=>{let S=await Q(s),P=S.latest,_e=i??P;if(i&&!S.versions.some(he=>he.version===_e))throw new Error(`Template version "${i}" is not available.`);return{latestVersion:P,selectedVersion:_e}};n.start("Verifying access...");let d,h;try{({latestVersion:d,selectedVersion:h}=await a()),n.stop("Access verified."),pe(o)}catch(S){if(n.stop("Access verification failed."),S instanceof _&&S.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 Ue(),s=q(o),n.start("Verifying access...");try{({latestVersion:d,selectedVersion:h}=await a()),n.stop("Access verified."),pe(o)}catch(P){n.stop("Access verification failed."),E.cancel(P instanceof _&&P.status===401?"Invalid API key.":I(P)),process.exit(1)}}else E.cancel(I(S)),process.exit(1)}E.log.success(`Latest version: ${d}`),h!==d&&E.log.success(`Using template version: ${h}`);let g;e.yes?g=pn(r):g=await Kt(r);let m;n.start("Activating license...");try{let S=crypto.randomUUID();m={token:(await Wt(s,{frontend:g.frontend,version:h,installId:S})).token,keyHash:Rt(o),installId:S},n.stop("License activated.")}catch(S){n.stop("License activation failed."),E.cancel(I(S)),process.exit(1)}let v=Ro(g.projectDir);if(Ao(v)&&ko(v).length>0)if(e.yes)E.log.info(`Directory ${v} is not empty. Merging (keeping existing files, overwriting conflicts).`);else{let P=await E.select({message:`Directory ${v} 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(P)||P==="cancel")&&(E.cancel("Setup cancelled."),process.exit(0)),P==="overwrite"&&Po(v,{recursive:!0,force:!0})}let b={...g,projectDir:v,version:h,...e.demo?{docs:!1}:{}};n.start("Downloading template...");try{await ot(s,h,v),n.stop("Template downloaded.")}catch(S){n.stop("Download failed."),E.cancel(I(S)),process.exit(1)}let H;n.start("Generating project files...");try{if({dockerComposeGenerated:H}=await ut(b),!e.demo){let S=await je(v);await l(_o(v,nt),JSON.stringify(S,null," ")+`
|
|
1111
|
+
`),$.yellow("Deployment"))}function To(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 Io(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 ln(e){let t={};if(e.name!==void 0){if(!tt(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(!Le.includes(e.frontend))throw new Error(`Invalid frontend "${e.frontend}". Valid values: ${Le.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=Lt(e.docker,We,"docker service")),e.aiTools!==void 0&&(t.aiTools=Lt(e.aiTools,Xe,"AI tool")),e.socialProviders!==void 0&&(t.socialProviders=Lt(e.socialProviders,Qe,"social provider")),e.currency!==void 0){if(!oe.includes(e.currency))throw new Error(`Invalid currency "${e.currency}". Valid values: ${oe.join(", ")}`);t.defaultCurrency=e.currency}if(e.deploy!==void 0){if(!se.includes(e.deploy))throw new Error(`Invalid deployment target "${e.deploy}". Valid values: ${se.join(", ")}`);t.deploymentTarget=e.deploy}if(e.database!==void 0){if(!ae.includes(e.database))throw new Error(`Invalid database provider "${e.database}". Valid values: ${ae.join(", ")}`);t.databaseProvider=e.database}if(e.cache!==void 0){if(!ce.includes(e.cache))throw new Error(`Invalid cache provider "${e.cache}". Valid values: ${ce.join(", ")}`);t.cacheProvider=e.cache}if(e.demo===!0&&(t.demo=!0),e.baseUrl!==void 0){let r=e.baseUrl.trim();if(r==="")throw new Error("--base-url cannot be empty. Provide an absolute URL like https://example.com.");let i;try{i=new URL(r)}catch{throw new Error(`Invalid --base-url "${e.baseUrl}". Must be an absolute URL like https://example.com.`)}if(i.protocol!=="http:"&&i.protocol!=="https:")throw new Error(`Invalid --base-url "${e.baseUrl}". Must use http or https.`);t.baseUrl=`${i.protocol}//${i.host}`}return t}var ee={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 pn(e){let t=e.projectName??ee.projectName,r=e.projectDir??`./${t}`,i=e.appName??et(t),n=e.deploymentTarget??ee.deploymentTarget,o=F[n]?.edgeRuntime??!1,s=e.databaseProvider??(o?"neon":ee.databaseProvider),a=e.cacheProvider??(o?"upstash":ee.cacheProvider),d=e.emailProvider??(o?"resend":ee.emailProvider),h=e.dockerServices??(o?ee.dockerServices.filter(m=>m!=="postgres"&&m!=="redis"):ee.dockerServices),g={...ee,...e,projectName:t,appName:i,projectDir:r,deploymentTarget:n,databaseProvider:s,cacheProvider:a,emailProvider:d,dockerServices:h};g.paymentProvider==="none"&&(g.credits=!1);for(let m of Ce){if(g.deploymentTarget!==m.target)continue;let v=g.databaseProvider===m.provider?"database":"cache";if(g.databaseProvider===m.provider||g.cacheProvider===m.provider)throw new Error(`Incompatible: --deploy ${m.target} + --${v} ${m.provider}. ${m.reason}`)}for(let m of Ne)if(g.architecture===m.architecture&&g.deploymentTarget===m.target)throw new Error(`Incompatible: --architecture ${m.architecture} + --deploy ${m.target}. ${m.reason}`);return g}function Lt(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 Oo from"picocolors";var Do="c81635e3715dc7274f33a745e24a394d93d3d081a4004408585c3de86e1aac35";function xo(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 dn(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 j("--frontend <type>","frontend framework").choices([...Le])).addOption(new j("--architecture <type>","fullstack or separate").choices([...Ye])).addOption(new j("--payment <provider>","payment provider").choices([...Je])).addOption(new j("--email <provider>","email provider").choices([...qe])).option("--org","enable multi-tenancy (organizations)").option("--no-org","disable multi-tenancy").addOption(new j("--billing-scope <scope>","billing scope (requires --org)").choices([...Ze])).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 j("--currency <code>","default currency for billing").choices([...oe])).addOption(new j("--deploy <target>","deployment target").choices([...se])).addOption(new j("--database <provider>","database provider").choices([...ae])).addOption(new j("--cache <provider>","cache provider").choices([...ce])).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 j("--demo","first-party demo build: keep sample content, mark site non-indexable - requires CI API key").hideHelp()).addOption(new j("--no-db-migration","skip generating the baseline DB migration (internal: demos/CI/playground)").hideHelp()).action(async t=>{await Co(t)})}async function Co(e){let t=performance.now();Ft("1.4.0");let r,i;try{r=ln(e),i=xo(e.templateVersion)}catch(S){E.cancel(I(S)),process.exit(1)}let n=E.spinner(),o;try{o=await ve({apiKey:e.apiKey,prompt:!e.yes})}catch(S){E.cancel(I(S)),process.exit(1)}e.demo&&Rt(o)!==Do&&(E.cancel("--demo is restricted to first-party demo deployments."),process.exit(1));let s=q(o),a=async()=>{let S=await Q(s),P=S.latest,_e=i??P;if(i&&!S.versions.some(he=>he.version===_e))throw new Error(`Template version "${i}" is not available.`);return{latestVersion:P,selectedVersion:_e}};n.start("Verifying access...");let d,h;try{({latestVersion:d,selectedVersion:h}=await a()),n.stop("Access verified."),pe(o)}catch(S){if(n.stop("Access verification failed."),S instanceof _&&S.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 Ue(),s=q(o),n.start("Verifying access...");try{({latestVersion:d,selectedVersion:h}=await a()),n.stop("Access verified."),pe(o)}catch(P){n.stop("Access verification failed."),E.cancel(P instanceof _&&P.status===401?"Invalid API key.":I(P)),process.exit(1)}}else E.cancel(I(S)),process.exit(1)}E.log.success(`Latest version: ${d}`),h!==d&&E.log.success(`Using template version: ${h}`);let g;e.yes?g=pn(r):g=await Kt(r);let m;n.start("Activating license...");try{let S=crypto.randomUUID();m={token:(await Wt(s,{frontend:g.frontend,version:h,installId:S})).token,keyHash:Rt(o),installId:S},n.stop("License activated.")}catch(S){n.stop("License activation failed."),E.cancel(I(S)),process.exit(1)}let v=Ro(g.projectDir);if(Ao(v)&&ko(v).length>0)if(e.yes)E.log.info(`Directory ${v} is not empty. Merging (keeping existing files, overwriting conflicts).`);else{let P=await E.select({message:`Directory ${v} 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(P)||P==="cancel")&&(E.cancel("Setup cancelled."),process.exit(0)),P==="overwrite"&&Po(v,{recursive:!0,force:!0})}let b={...g,projectDir:v,version:h,...e.demo?{docs:!1}:{}};n.start("Downloading template...");try{await ot(s,h,v),n.stop("Template downloaded.")}catch(S){n.stop("Download failed."),E.cancel(I(S)),process.exit(1)}let H;n.start("Generating project files...");try{if({dockerComposeGenerated:H}=await ut(b),!e.demo){let S=await je(v);await l(_o(v,nt),JSON.stringify(S,null," ")+`
|
|
1112
1112
|
`),await Xr(v,v)}await en(v,b.aiTools),await qr(b,m),n.stop("Project files generated.")}catch(S){n.stop("Generation failed."),E.cancel(I(S)),process.exit(1)}await rn(v);let R=await nn(v);R&&b.demo!==!0&&e.dbMigration!==!1&&await on(v),await sn(v),R&&await an(v);let te=me("docker"),A=St(b).map(S=>S.key).filter(S=>!b.credentials?.[S]);cn(b,{pnpmInstalled:R,dockerComposeGenerated:H,dockerAvailable:te,skippedCredentials:A}),Bt(),E.log.info(Oo.dim(`Done in ${((performance.now()-t)/1e3).toFixed(1)}s`))}import{existsSync as un}from"fs";import{readFile as jo}from"fs/promises";import{join as Ve,resolve as Mo}from"path";import*as k from"@clack/prompts";import Pe from"picocolors";import{mkdtemp as No,rm as Lo}from"fs/promises";import{tmpdir as Uo}from"os";import{join as $o}from"path";async function Ut(e,t,r,i){let n=await No($o(Uo(),"generatesaas-stage-"));try{await ot(e,t,n),await ut({...r,projectDir:n}),await Ot(n,i)}finally{await Lo(n,{recursive:!0,force:!0})}}function mn(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=Mo(t.cwd??process.cwd()),i=Ve(r,W),n;try{n=JSON.parse(await jo(i,"utf-8"))}catch{k.cancel(".generatesaas/manifest.json not found. Run this from a GenerateSaaS project."),process.exit(1)}let o;try{o=await ve()}catch(d){k.cancel(I(d)),process.exit(1)}let s=q(o),a=k.spinner();try{a.start("Verifying access...");let d;try{d=await Q(s)}catch(A){throw A instanceof _&&A.status===401?new Error("Your saved API key was rejected. Run `generatesaas auth` to update it, or set GENERATESAAS_API_KEY."):A}a.stop("Access verified."),pe(o),a.start("Fetching latest skill files...");let h=await qt(s,d.latest);await Ct(r,h.skillMd,h.scripts,n.aiTools);let g=xt(n.aiTools);if(a.stop("Skills updated."),k.log.success(`Skill files installed to ${Pe.cyan(g.length.toString())} locations.`),n.version===d.latest){k.log.info(`Already on the latest version (${n.version}).`);return}if(n.licenseToken)try{let A=await Xt(s,{currentToken:n.licenseToken,newVersion:n.version});n.licenseToken=A.token,await l(i,JSON.stringify(n,null," ")+`
|
|
1113
1113
|
`),k.log.success("License refreshed.")}catch{k.log.warn("License refresh skipped.")}let m=Jr(n,r),v=Ve(r,Ht);a.start(`Staging v${d.latest} (shaped for your config)...`),await Ut(s,d.latest,m,v),a.stop("Template staged.");let b=await Jt(s,d.latest);b&&k.note(b,`Changelog v${d.latest}`);let H=Ve(r,nt),R=Ve(r,it),te=!un(R),re=!un(H);if(te){if(a.start("Building baseline template (one-time migration)..."),await Ut(s,n.version,m,R),re){let A=await je(R);await l(H,JSON.stringify(A,null," ")+`
|
|
1114
1114
|
`)}a.stop("Baseline template stored.")}else if(re){a.start("Computing baseline template hashes...");let A=await je(R);await l(H,JSON.stringify(A,null," ")+`
|
|
@@ -1122,7 +1122,7 @@ ${m}`:s.message))}else n()})})}async function rn(e){if(!me("pnpm"))return T.log.
|
|
|
1122
1122
|
`,` .route("/license", licenseRoutes)
|
|
1123
1123
|
`]}];function Wo(e){return(e&&e.length>0?e.map(r=>Me[r]):Object.values(Me)).map(r=>fe(r,Dt))}function jt(e){return ft(e)?(Yo(e,{recursive:!0}),!0):!1}function Xo(e,t){if(!ft(e))return!1;let r=Mt(e,"utf-8"),i=r;for(let n of t)i=i.replace(n,"");return i===r?!1:(Sn(e,i,"utf-8"),!0)}function Zo(e){let t=fe(e,".gitignore");if(!ft(t))return!1;let r=Mt(t,"utf-8"),i=r.split(`
|
|
1124
1124
|
`).filter(n=>!n.includes(".generatesaas")).join(`
|
|
1125
|
-
`);return i===r?!1:(Sn(t,i,"utf-8"),!0)}function En(e){e.command("eject").description("Remove all GenerateSaaS ties - manifest, license, heartbeat, skills").action(async()=>{let t=process.cwd(),r=fe(t,W),i;try{i=JSON.parse(Mt(r,"utf-8"))}catch{L.cancel("No GenerateSaaS project found in this directory."),process.exit(1)}let n=await L.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.'}});L.isCancel(n)&&(L.cancel("Eject cancelled."),process.exit(0));let o=[],s=[];for(let a of Wo(i.aiTools))jt(fe(t,a))&&o.push(a);for(let a of Jo)jt(fe(t,a))&&o.push(a);jt(fe(t,U))&&o.push(U+"/");for(let a of qo){let d=fe(t,a.file);Xo(d,a.removals)?s.push(a.file):ft(d)&&L.log.warn(`Could not auto-modify ${a.file} - manually remove license/heartbeat references.`)}Zo(t)&&s.push(".gitignore");for(let a of o)L.log.info(`Deleted ${a}`);for(let a of s)L.log.info(`Modified ${a}`);L.log.success("Ejected successfully. This project is now fully standalone.")})}var ge=new Qo().name("generatesaas").description("CLI for scaffolding and managing GenerateSaaS projects").version("1.
|
|
1125
|
+
`);return i===r?!1:(Sn(t,i,"utf-8"),!0)}function En(e){e.command("eject").description("Remove all GenerateSaaS ties - manifest, license, heartbeat, skills").action(async()=>{let t=process.cwd(),r=fe(t,W),i;try{i=JSON.parse(Mt(r,"utf-8"))}catch{L.cancel("No GenerateSaaS project found in this directory."),process.exit(1)}let n=await L.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.'}});L.isCancel(n)&&(L.cancel("Eject cancelled."),process.exit(0));let o=[],s=[];for(let a of Wo(i.aiTools))jt(fe(t,a))&&o.push(a);for(let a of Jo)jt(fe(t,a))&&o.push(a);jt(fe(t,U))&&o.push(U+"/");for(let a of qo){let d=fe(t,a.file);Xo(d,a.removals)?s.push(a.file):ft(d)&&L.log.warn(`Could not auto-modify ${a.file} - manually remove license/heartbeat references.`)}Zo(t)&&s.push(".gitignore");for(let a of o)L.log.info(`Deleted ${a}`);for(let a of s)L.log.info(`Modified ${a}`);L.log.success("Ejected successfully. This project is now fully standalone.")})}var ge=new Qo().name("generatesaas").description("CLI for scaffolding and managing GenerateSaaS projects").version("1.4.0").addHelpText("after",`
|
|
1126
1126
|
Examples:
|
|
1127
1127
|
$ generatesaas init Interactive setup
|
|
1128
1128
|
$ generatesaas init -n my-app -y Quick setup with defaults
|