generatesaas 1.17.0 → 1.18.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 +4 -3
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -337,7 +337,8 @@ export const desktopConfig = {
337
337
  productName: "${g.productName}",
338
338
  protocol: "${g.protocol}",
339
339
  baseUrl: "${n}",
340
- autoUpdate: { provider: "generic", url: "https://cdn.${i}/desktop" }
340
+ autoUpdate: { provider: "generic", url: "https://cdn.${i}/desktop" },
341
+ agents: { enabled: true, modelRegistry: { enabled: true } }
341
342
  };
342
343
  `),await l(yt(e.projectDir,"packages/config/src/tenancy-flags.mjs"),`// Plain-JS tenancy flags. NO env reads, so tooling that cannot import the
343
344
  // TypeScript config index reads it directly (the Electron renderer). Mirrors the
@@ -1185,7 +1186,7 @@ export { ops } from "./${r}/index";
1185
1186
  ${m}`:s.message))}else o()})})}async function Tn(e){if(!Ee("pnpm"))return T.log.warn("pnpm not found. Skipping lockfile regeneration."),!1;try{return await Se("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 Pn(e){if(!Ee("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 Se("pnpm",["install","--config.minimumReleaseAge=0"],e),t.stop("Dependencies installed."),!0}catch(r){t.stop("Dependency installation failed.");let n=r instanceof Error?r.message:String(r);return T.log.warn(`pnpm install failed: ${n}`),T.log.warn("You can run it manually later."),!1}}async function _n(e){if(!Ee("pnpm"))return!1;let t=T.spinner();t.start("Generating baseline database migration...");try{return await Se("pnpm",["-F","@repo/database","generate"],e),t.stop("Baseline migration generated."),!0}catch(r){t.stop("Baseline migration generation failed.");let n=r instanceof Error?r.message:String(r);return T.log.warn(`Could not generate baseline migration: ${n}`),T.log.warn("Run 'pnpm -F @repo/database generate' before your first deploy."),!1}}async function Rn(e){try{return await In(qt(e,".git")),T.log.info("Git repository already exists, skipping init."),!0}catch{}if(!Ee("git"))return T.log.warn("git not found. Skipping repository initialization."),!1;let t=T.spinner();t.start("Initializing git repository...");try{return await Se("git",["init"],e),await Se("git",["add","-A"],e),await Se("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 On(e){if(!Ee("pnpm"))return!1;try{await In(qt(e,".git"))}catch{return!1}try{let t=JSON.parse(await os(qt(e,"package.json"),"utf-8")),r=!!t.devDependencies?.["simple-git-hooks"],n=!!t["simple-git-hooks"];if(!r||!n)return!1}catch{return!1}try{return await Se("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 Ce from"@clack/prompts";import F from"picocolors";function xn(e,t){t.dockerComposeGenerated&&!t.dockerAvailable&&Ce.log.warn("Docker not found. Install Docker to run local services: https://docs.docker.com/get-docker/");let r=[];if(r.push(`cd ${e.projectDir}`),t.pnpmInstalled||r.push("pnpm install"),t.dockerComposeGenerated){let i=e.dockerServices.map(s=>ke[s].label).join(", ");r.push(`pnpm infra ${F.dim(`# ${i}`)}`)}if(r.push(`pnpm dev ${F.dim("# http://localhost:3000")}`),t.skippedCredentials.length>0&&(r.push(""),r.push(F.dim("Fill in remaining TODO values in .env"))),Ce.note(r.join(`
1186
1187
  `),F.yellow("Start Development")),t.dockerComposeGenerated){let i=[];i.push(`App ${F.cyan("http://localhost:3000")}`),e.architecture==="separate"&&i.push(`API ${F.cyan("http://localhost:3010")}`),e.dockerServices.includes("mailpit")&&i.push(`Mailpit ${F.cyan("http://localhost:8025")}`),e.dockerServices.includes("inngest")&&i.push(`Inngest ${F.cyan("http://localhost:8288")}`),Ce.note(i.join(`
1187
1188
  `),F.yellow("Dev Tools"))}let n=[],o=is(e);o.length>0&&n.push(`Set in production: ${F.dim(o.join(", "))}`),n.push("pnpm db:push # Run database migrations"),n.push(ss(e)),Ce.note(n.join(`
1188
- `),F.yellow("Deployment"))}function is(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 ss(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 Dn(e){let t={};if(e.name!==void 0){if(!pt(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(!Be.includes(e.frontend))throw new Error(`Invalid frontend "${e.frontend}". Valid values: ${Be.join(", ")}`);t.frontend=e.frontend}if(e.architecture!==void 0&&(t.architecture=e.architecture),e.payment!==void 0&&(t.paymentProvider=e.payment),e.email!==void 0&&(t.emailProvider=e.email),e.org!==void 0&&(t.multiTenancy=e.org),e.billingScope!==void 0){if(e.org===!1)throw new Error("--billing-scope requires --org to be enabled.");t.billingScope=e.billingScope}if(e.blog!==void 0&&(t.blog=e.blog),e.docs!==void 0&&(t.docs=e.docs),e.desktop!==void 0&&(t.desktop=e.desktop),e.desktopAuto!==void 0&&(t.desktopAutoRelease=e.desktopAuto),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=Xt(e.docker,it,"docker service")),e.aiTools!==void 0&&(t.aiTools=Xt(e.aiTools,st,"AI tool")),e.socialProviders!==void 0&&(t.socialProviders=Xt(e.socialProviders,ct,"social provider")),e.currency!==void 0){if(!de.includes(e.currency))throw new Error(`Invalid currency "${e.currency}". Valid values: ${de.join(", ")}`);t.defaultCurrency=e.currency}if(e.deploy!==void 0){if(!ue.includes(e.deploy))throw new Error(`Invalid deployment target "${e.deploy}". Valid values: ${ue.join(", ")}`);t.deploymentTarget=e.deploy}if(e.database!==void 0){if(!me.includes(e.database))throw new Error(`Invalid database provider "${e.database}". Valid values: ${me.join(", ")}`);t.databaseProvider=e.database}if(e.cache!==void 0){if(!fe.includes(e.cache))throw new Error(`Invalid cache provider "${e.cache}". Valid values: ${fe.join(", ")}`);t.cacheProvider=e.cache}if(e.demo===!0&&(t.demo=!0),e.baseUrl!==void 0){let r=e.baseUrl.trim();if(r==="")throw new Error("--base-url cannot be empty. Provide an absolute URL like https://example.com.");let n;try{n=new URL(r)}catch{throw new Error(`Invalid --base-url "${e.baseUrl}". Must be an absolute URL like https://example.com.`)}if(n.protocol!=="http:"&&n.protocol!=="https:")throw new Error(`Invalid --base-url "${e.baseUrl}". Must use http or https.`);t.baseUrl=`${n.protocol}//${n.host}`}return t}var se={projectName:"my-saas",frontend:"nextjs",architecture:"fullstack",paymentProvider:"stripe",emailProvider:"smtp",multiTenancy:!1,billingScope:"user",blog:!0,docs:!1,desktop:!1,desktopAutoRelease:!1,revenueSharing:!1,credits:!0,dockerServices:["postgres","redis","inngest"],aiTools:[],socialProviders:[],defaultCurrency:"USD",deploymentTarget:"node",databaseProvider:"postgres",cacheProvider:"redis"};function Cn(e){let t=e.projectName??se.projectName,r=e.projectDir??`./${t}`,n=e.appName??lt(t),o=e.deploymentTarget??se.deploymentTarget,i=H[o]?.edgeRuntime??!1,s=e.databaseProvider??(i?"neon":se.databaseProvider),a=e.cacheProvider??(i?"upstash":se.cacheProvider),d=e.emailProvider??(i?"resend":se.emailProvider),g=e.dockerServices??(i?se.dockerServices.filter(m=>m!=="postgres"&&m!=="redis"):se.dockerServices),h={...se,...e,projectName:t,appName:n,projectDir:r,deploymentTarget:o,databaseProvider:s,cacheProvider:a,emailProvider:d,dockerServices:g};h.paymentProvider==="none"&&(h.credits=!1);for(let m of Me){if(h.deploymentTarget!==m.target)continue;let _=h.databaseProvider===m.provider?"database":"cache";if(h.databaseProvider===m.provider||h.cacheProvider===m.provider)throw new Error(`Incompatible: --deploy ${m.target} + --${_} ${m.provider}. ${m.reason}`)}for(let m of Fe)if(h.architecture===m.architecture&&h.deploymentTarget===m.target)throw new Error(`Incompatible: --architecture ${m.architecture} + --deploy ${m.target}. ${m.reason}`);return h}function Xt(e,t,r){if(e.trim()==="")return[];let n=e.split(",").map(i=>i.trim()).filter(Boolean),o=n.filter(i=>!t.includes(i));if(o.length>0)throw new Error(`Invalid ${r}(s): ${o.join(", ")}. Valid values: ${t.join(", ")}`);return n}import us from"picocolors";var ms="a10a6fb9d7cadde32e37dad52059d17b5d2b916b08c76d8fbcc99982e9a3d87f";function fs(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 Nn(e){e.command("init").description("Scaffold a new GenerateSaaS project").argument("[apiKey]","license key (same as --api-key)").option("-n, --name <name>","project name (lowercase, hyphens, starts with letter)").option("--app-name <name>","display name for the app").option("-l, --location <path>","project directory (default: ./{name})").addOption(new B("--frontend <type>","frontend framework").choices([...Be])).addOption(new B("--architecture <type>","fullstack or separate").choices([...rt])).addOption(new B("--payment <provider>","payment provider").choices([...nt])).addOption(new B("--email <provider>","email provider").choices([...ot])).option("--org","enable multi-tenancy (organizations)").option("--no-org","disable multi-tenancy").addOption(new B("--billing-scope <scope>","billing scope (requires --org)").choices([...at])).option("--blog","enable blog").option("--no-blog","disable blog").option("--docs","include the docs app (apps/docs, Fumadocs)").option("--no-docs","exclude the docs app").option("--desktop","include the Electron desktop app (apps/desktop)").option("--no-desktop","exclude the Electron desktop app").option("--desktop-auto","auto-trigger the desktop release workflow on CI success (default: manual-only)").option("--no-desktop-auto","manual-only desktop releases (workflow_dispatch)").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 B("--currency <code>","default currency for billing").choices([...de])).addOption(new B("--deploy <target>","deployment target").choices([...ue])).addOption(new B("--database <provider>","database provider").choices([...me])).addOption(new B("--cache <provider>","cache provider").choices([...fe])).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 B("--demo","first-party demo build: keep sample content, mark site non-indexable - requires CI API key").hideHelp()).addOption(new B("--no-db-migration","skip generating the baseline DB migration (internal: demos/CI/playground)").hideHelp()).action(async(t,r)=>{await gs(t?{...r,apiKey:t}:r)})}async function gs(e){let t=performance.now();nr("1.17.0");let r,n;try{r=Dn(e),n=fs(e.templateVersion)}catch(y){S.cancel(I(y)),process.exit(1)}let o=S.spinner(),i;try{i=await Ae({apiKey:e.apiKey,prompt:!e.yes})}catch(y){S.cancel(I(y)),process.exit(1)}e.demo&&zt(i)!==ms&&(S.cancel("--demo is restricted to first-party demo deployments."),process.exit(1));let s=Z(i),a=async()=>{let y=await ie(s),O=y.latest,V=n??O;if(n&&!y.versions.some(ce=>ce.version===V))throw new Error(`Template version "${n}" is not available.`);return{latestVersion:O,selectedVersion:V,desktopAllowed:y.entitlements?.desktopAllowed??!0}};o.start("Verifying access...");let d,g,h=!0;try{({latestVersion:d,selectedVersion:g,desktopAllowed:h}=await a()),o.stop("Access verified."),he(i)}catch(y){if(o.stop("Access verification failed."),y instanceof x&&y.status===401){e.yes&&(S.cancel("Invalid API key. Cannot prompt in non-interactive mode."),process.exit(1)),S.log.warning("Invalid API key."),i=await ze(),s=Z(i),o.start("Verifying access...");try{({latestVersion:d,selectedVersion:g,desktopAllowed:h}=await a()),o.stop("Access verified."),he(i)}catch(O){o.stop("Access verification failed."),S.cancel(O instanceof x&&O.status===401?"Invalid API key.":I(O)),process.exit(1)}}else S.cancel(I(y)),process.exit(1)}S.log.success(`Latest version: ${d}`),g!==d&&S.log.success(`Using template version: ${g}`),r.desktop===!0&&!h&&(S.cancel("The desktop app is available on the Pro plan and up. Upgrade your plan at generatesaas.com."),process.exit(1));let m;e.yes?m=Cn(h?r:{...r,desktop:!1}):m=await sr(r,{desktopAllowed:h});let _;o.start("Activating license...");try{let y=crypto.randomUUID(),O=()=>({frontend:m.frontend,version:g,installId:y,projectName:m.projectName,options:dr(m)}),V;try{V=await Dt(s,O())}catch(ce){let te=mt(ce);if(!te?.lastAllowedVersion)throw ce;o.stop("License activation failed."),e.yes&&(S.cancel(`${te.message} Re-run with --template-version ${te.lastAllowedVersion}.`),process.exit(1));let Le=await S.confirm({message:`Your update window has ended. Continue with v${te.lastAllowedVersion} (the last version your license covers)?`});(S.isCancel(Le)||!Le)&&(S.cancel("Setup cancelled."),process.exit(0)),g=te.lastAllowedVersion,o.start(`Activating license for v${g}...`),V=await Dt(s,O())}_={token:V.token,keyHash:zt(i),installId:y},o.stop("License activated.")}catch(y){o.stop("License activation failed."),S.cancel(I(y)),process.exit(1)}let E=ds(m.projectDir);if(as(E)&&cs(E).length>0)if(e.yes)S.log.info(`Directory ${E} is not empty. Merging (keeping existing files, overwriting conflicts).`);else{let O=await S.select({message:`Directory ${E} 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"}]});(S.isCancel(O)||O==="cancel")&&(S.cancel("Setup cancelled."),process.exit(0)),O==="overwrite"&&ls(E,{recursive:!0,force:!0})}let R={...m,projectDir:E,version:g,...e.demo?{docs:!1}:{}};o.start("Downloading template...");try{await ft(s,g,E),o.stop("Template downloaded.")}catch(y){o.stop("Download failed."),S.cancel(I(y)),process.exit(1)}let z;o.start("Generating project files...");try{if({dockerComposeGenerated:z}=await bt(R),!e.demo){let y=await De(E);await l(ps(E,dt),JSON.stringify(y,null," ")+`
1189
+ `),F.yellow("Deployment"))}function is(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 ss(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 Dn(e){let t={};if(e.name!==void 0){if(!pt(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(!Be.includes(e.frontend))throw new Error(`Invalid frontend "${e.frontend}". Valid values: ${Be.join(", ")}`);t.frontend=e.frontend}if(e.architecture!==void 0&&(t.architecture=e.architecture),e.payment!==void 0&&(t.paymentProvider=e.payment),e.email!==void 0&&(t.emailProvider=e.email),e.org!==void 0&&(t.multiTenancy=e.org),e.billingScope!==void 0){if(e.org===!1)throw new Error("--billing-scope requires --org to be enabled.");t.billingScope=e.billingScope}if(e.blog!==void 0&&(t.blog=e.blog),e.docs!==void 0&&(t.docs=e.docs),e.desktop!==void 0&&(t.desktop=e.desktop),e.desktopAuto!==void 0&&(t.desktopAutoRelease=e.desktopAuto),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=Xt(e.docker,it,"docker service")),e.aiTools!==void 0&&(t.aiTools=Xt(e.aiTools,st,"AI tool")),e.socialProviders!==void 0&&(t.socialProviders=Xt(e.socialProviders,ct,"social provider")),e.currency!==void 0){if(!de.includes(e.currency))throw new Error(`Invalid currency "${e.currency}". Valid values: ${de.join(", ")}`);t.defaultCurrency=e.currency}if(e.deploy!==void 0){if(!ue.includes(e.deploy))throw new Error(`Invalid deployment target "${e.deploy}". Valid values: ${ue.join(", ")}`);t.deploymentTarget=e.deploy}if(e.database!==void 0){if(!me.includes(e.database))throw new Error(`Invalid database provider "${e.database}". Valid values: ${me.join(", ")}`);t.databaseProvider=e.database}if(e.cache!==void 0){if(!fe.includes(e.cache))throw new Error(`Invalid cache provider "${e.cache}". Valid values: ${fe.join(", ")}`);t.cacheProvider=e.cache}if(e.demo===!0&&(t.demo=!0),e.baseUrl!==void 0){let r=e.baseUrl.trim();if(r==="")throw new Error("--base-url cannot be empty. Provide an absolute URL like https://example.com.");let n;try{n=new URL(r)}catch{throw new Error(`Invalid --base-url "${e.baseUrl}". Must be an absolute URL like https://example.com.`)}if(n.protocol!=="http:"&&n.protocol!=="https:")throw new Error(`Invalid --base-url "${e.baseUrl}". Must use http or https.`);t.baseUrl=`${n.protocol}//${n.host}`}return t}var se={projectName:"my-saas",frontend:"nextjs",architecture:"fullstack",paymentProvider:"stripe",emailProvider:"smtp",multiTenancy:!1,billingScope:"user",blog:!0,docs:!1,desktop:!1,desktopAutoRelease:!1,revenueSharing:!1,credits:!0,dockerServices:["postgres","redis","inngest"],aiTools:[],socialProviders:[],defaultCurrency:"USD",deploymentTarget:"node",databaseProvider:"postgres",cacheProvider:"redis"};function Cn(e){let t=e.projectName??se.projectName,r=e.projectDir??`./${t}`,n=e.appName??lt(t),o=e.deploymentTarget??se.deploymentTarget,i=H[o]?.edgeRuntime??!1,s=e.databaseProvider??(i?"neon":se.databaseProvider),a=e.cacheProvider??(i?"upstash":se.cacheProvider),d=e.emailProvider??(i?"resend":se.emailProvider),g=e.dockerServices??(i?se.dockerServices.filter(m=>m!=="postgres"&&m!=="redis"):se.dockerServices),h={...se,...e,projectName:t,appName:n,projectDir:r,deploymentTarget:o,databaseProvider:s,cacheProvider:a,emailProvider:d,dockerServices:g};h.paymentProvider==="none"&&(h.credits=!1);for(let m of Me){if(h.deploymentTarget!==m.target)continue;let _=h.databaseProvider===m.provider?"database":"cache";if(h.databaseProvider===m.provider||h.cacheProvider===m.provider)throw new Error(`Incompatible: --deploy ${m.target} + --${_} ${m.provider}. ${m.reason}`)}for(let m of Fe)if(h.architecture===m.architecture&&h.deploymentTarget===m.target)throw new Error(`Incompatible: --architecture ${m.architecture} + --deploy ${m.target}. ${m.reason}`);return h}function Xt(e,t,r){if(e.trim()==="")return[];let n=e.split(",").map(i=>i.trim()).filter(Boolean),o=n.filter(i=>!t.includes(i));if(o.length>0)throw new Error(`Invalid ${r}(s): ${o.join(", ")}. Valid values: ${t.join(", ")}`);return n}import us from"picocolors";var ms="a10a6fb9d7cadde32e37dad52059d17b5d2b916b08c76d8fbcc99982e9a3d87f";function fs(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 Nn(e){e.command("init").description("Scaffold a new GenerateSaaS project").argument("[apiKey]","license key (same as --api-key)").option("-n, --name <name>","project name (lowercase, hyphens, starts with letter)").option("--app-name <name>","display name for the app").option("-l, --location <path>","project directory (default: ./{name})").addOption(new B("--frontend <type>","frontend framework").choices([...Be])).addOption(new B("--architecture <type>","fullstack or separate").choices([...rt])).addOption(new B("--payment <provider>","payment provider").choices([...nt])).addOption(new B("--email <provider>","email provider").choices([...ot])).option("--org","enable multi-tenancy (organizations)").option("--no-org","disable multi-tenancy").addOption(new B("--billing-scope <scope>","billing scope (requires --org)").choices([...at])).option("--blog","enable blog").option("--no-blog","disable blog").option("--docs","include the docs app (apps/docs, Fumadocs)").option("--no-docs","exclude the docs app").option("--desktop","include the Electron desktop app (apps/desktop)").option("--no-desktop","exclude the Electron desktop app").option("--desktop-auto","auto-trigger the desktop release workflow on CI success (default: manual-only)").option("--no-desktop-auto","manual-only desktop releases (workflow_dispatch)").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 B("--currency <code>","default currency for billing").choices([...de])).addOption(new B("--deploy <target>","deployment target").choices([...ue])).addOption(new B("--database <provider>","database provider").choices([...me])).addOption(new B("--cache <provider>","cache provider").choices([...fe])).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 B("--demo","first-party demo build: keep sample content, mark site non-indexable - requires CI API key").hideHelp()).addOption(new B("--no-db-migration","skip generating the baseline DB migration (internal: demos/CI/playground)").hideHelp()).action(async(t,r)=>{await gs(t?{...r,apiKey:t}:r)})}async function gs(e){let t=performance.now();nr("1.18.0");let r,n;try{r=Dn(e),n=fs(e.templateVersion)}catch(y){S.cancel(I(y)),process.exit(1)}let o=S.spinner(),i;try{i=await Ae({apiKey:e.apiKey,prompt:!e.yes})}catch(y){S.cancel(I(y)),process.exit(1)}e.demo&&zt(i)!==ms&&(S.cancel("--demo is restricted to first-party demo deployments."),process.exit(1));let s=Z(i),a=async()=>{let y=await ie(s),O=y.latest,V=n??O;if(n&&!y.versions.some(ce=>ce.version===V))throw new Error(`Template version "${n}" is not available.`);return{latestVersion:O,selectedVersion:V,desktopAllowed:y.entitlements?.desktopAllowed??!0}};o.start("Verifying access...");let d,g,h=!0;try{({latestVersion:d,selectedVersion:g,desktopAllowed:h}=await a()),o.stop("Access verified."),he(i)}catch(y){if(o.stop("Access verification failed."),y instanceof x&&y.status===401){e.yes&&(S.cancel("Invalid API key. Cannot prompt in non-interactive mode."),process.exit(1)),S.log.warning("Invalid API key."),i=await ze(),s=Z(i),o.start("Verifying access...");try{({latestVersion:d,selectedVersion:g,desktopAllowed:h}=await a()),o.stop("Access verified."),he(i)}catch(O){o.stop("Access verification failed."),S.cancel(O instanceof x&&O.status===401?"Invalid API key.":I(O)),process.exit(1)}}else S.cancel(I(y)),process.exit(1)}S.log.success(`Latest version: ${d}`),g!==d&&S.log.success(`Using template version: ${g}`),r.desktop===!0&&!h&&(S.cancel("The desktop app is available on the Pro plan and up. Upgrade your plan at generatesaas.com."),process.exit(1));let m;e.yes?m=Cn(h?r:{...r,desktop:!1}):m=await sr(r,{desktopAllowed:h});let _;o.start("Activating license...");try{let y=crypto.randomUUID(),O=()=>({frontend:m.frontend,version:g,installId:y,projectName:m.projectName,options:dr(m)}),V;try{V=await Dt(s,O())}catch(ce){let te=mt(ce);if(!te?.lastAllowedVersion)throw ce;o.stop("License activation failed."),e.yes&&(S.cancel(`${te.message} Re-run with --template-version ${te.lastAllowedVersion}.`),process.exit(1));let Le=await S.confirm({message:`Your update window has ended. Continue with v${te.lastAllowedVersion} (the last version your license covers)?`});(S.isCancel(Le)||!Le)&&(S.cancel("Setup cancelled."),process.exit(0)),g=te.lastAllowedVersion,o.start(`Activating license for v${g}...`),V=await Dt(s,O())}_={token:V.token,keyHash:zt(i),installId:y},o.stop("License activated.")}catch(y){o.stop("License activation failed."),S.cancel(I(y)),process.exit(1)}let E=ds(m.projectDir);if(as(E)&&cs(E).length>0)if(e.yes)S.log.info(`Directory ${E} is not empty. Merging (keeping existing files, overwriting conflicts).`);else{let O=await S.select({message:`Directory ${E} 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"}]});(S.isCancel(O)||O==="cancel")&&(S.cancel("Setup cancelled."),process.exit(0)),O==="overwrite"&&ls(E,{recursive:!0,force:!0})}let R={...m,projectDir:E,version:g,...e.demo?{docs:!1}:{}};o.start("Downloading template...");try{await ft(s,g,E),o.stop("Template downloaded.")}catch(y){o.stop("Download failed."),S.cancel(I(y)),process.exit(1)}let z;o.start("Generating project files...");try{if({dockerComposeGenerated:z}=await bt(R),!e.demo){let y=await De(E);await l(ps(E,dt),JSON.stringify(y,null," ")+`
1189
1190
  `),await wn(E,E)}await An(E,R.aiTools),await Sn(R,_),o.stop("Project files generated.")}catch(y){o.stop("Generation failed."),S.cancel(I(y)),process.exit(1)}await Tn(E);let D=await Pn(E);D&&R.demo!==!0&&e.dbMigration!==!1&&await _n(E),await Rn(E),D&&await On(E);let ae=Ee("docker"),b=Ot(R).map(y=>y.key).filter(y=>!R.credentials?.[y]);xn(R,{pnpmInstalled:D,dockerComposeGenerated:z,dockerAvailable:ae,skippedCredentials:b}),or(),S.log.info(us.dim(`Done in ${((performance.now()-t)/1e3).toFixed(1)}s`))}import{existsSync as $n}from"fs";import{readFile as Un}from"fs/promises";import{join as Je,resolve as Es}from"path";import*as P from"@clack/prompts";import Ne from"picocolors";import{mkdtemp as hs,rm as ys}from"fs/promises";import{tmpdir as vs}from"os";import{join as Ss}from"path";async function Zt(e,t,r,n){let o=await hs(Ss(vs(),"generatesaas-stage-"));try{await ft(e,t,o),await bt({...r,projectDir:o}),await Ht(o,n)}finally{await ys(o,{recursive:!0,force:!0})}}function Ln(e){let r=(e.startsWith("v")?e.slice(1):e).match(/^(\d+)\.(\d+)\.(\d+)$/);return r?[Number(r[1]),Number(r[2]),Number(r[3])]:null}function At(e,t){let r=Ln(e),n=Ln(t);if(!r||!n)return 0;for(let o=0;o<3;o++)if(r[o]!==n[o])return r[o]-n[o];return 0}function jn(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=Es(t.cwd??process.cwd()),n=Je(r,re),o;try{o=JSON.parse(await Un(n,"utf-8"))}catch{P.cancel(".generatesaas/manifest.json not found. Run this from a GenerateSaaS project."),process.exit(1)}let i;try{i=await Ae()}catch(d){P.cancel(I(d)),process.exit(1)}let s=Z(i),a=P.spinner();try{a.start("Verifying access...");let d;try{d=await ie(s)}catch(b){throw b instanceof x&&b.status===401?new Error("Your saved API key was rejected. Run `generatesaas auth` to update it, or set GENERATESAAS_API_KEY."):b}a.stop("Access verified."),he(i),a.start("Fetching latest skill files...");let g=await pr(s,d.latest);await Wt(r,g.skillMd,g.scripts,o.aiTools);let h=Jt(o.aiTools);if(a.stop("Skills updated."),P.log.success(`Skill files installed to ${Ne.cyan(h.length.toString())} locations.`),o.version===d.latest){P.log.info(`Already on the latest version (${o.version}).`);return}if(o.licenseToken)try{let b=await ur(s,{currentToken:o.licenseToken,newVersion:d.latest});o.licenseToken=b.token,b.licenseKeyHash&&(o.licenseKeyHash=b.licenseKeyHash),await l(n,JSON.stringify(o,null," ")+`
1190
1191
  `),P.log.success("License refreshed.")}catch(b){let y=mt(b);y&&(P.cancel(y.message),process.exit(1)),P.log.warn("License refresh skipped.")}let m=vn(o,r),_=Je(r,cr);a.start(`Staging v${d.latest} (shaped for your config)...`),await Zt(s,d.latest,m,_),a.stop("Template staged.");let{text:E,title:R}=await ws(s,d,o.version);E&&P.note(E,R);let z=Je(r,dt),D=Je(r,ut),ae=!$n(D),ee=!$n(z);if(ae){if(a.start("Building baseline template (one-time migration)..."),await Zt(s,o.version,m,D),ee){let b=await De(D);await l(z,JSON.stringify(b,null," ")+`
1191
1192
  `)}if(a.stop("Baseline template stored."),!ee){let b=await bs(z,D);b>0&&P.log.warn(`Rebuilt baseline differs from the original for ${b} file(s) (the CLI's shaping evolved since this project was scaffolded). Classification still follows the committed template-hashes.json; upstream diffs for those files may include unrelated noise.`)}}else if(ee){a.start("Computing baseline template hashes...");let b=await De(D);await l(z,JSON.stringify(b,null," ")+`
@@ -1203,7 +1204,7 @@ ${a??"_No changelog available for this release._"}`)}return{text:i.join(`
1203
1204
  `,` .route("/license", licenseRoutes)
1204
1205
  `]}];function js(e){return(e&&e.length>0?e.map(r=>Ye[r]):Object.values(Ye)).map(r=>we(r,Yt))}function er(e){return It(e)?(Ls(e,{recursive:!0}),!0):!1}function Vs(e,t){if(!It(e))return!1;let r=tr(e,"utf-8"),n=r;for(let o of t)n=n.replace(o,"");return n===r?!1:(Hn(e,n,"utf-8"),!0)}function Ms(e){let t=we(e,".gitignore");if(!It(t))return!1;let r=tr(t,"utf-8"),n=r.split(`
1205
1206
  `).filter(o=>!o.includes(".generatesaas")).join(`
1206
- `);return n===r?!1:(Hn(t,n,"utf-8"),!0)}function Yn(e){e.command("eject").description("Remove all GenerateSaaS ties - manifest, license, heartbeat, skills").action(async()=>{let t=process.cwd(),r=we(t,re),n;try{n=JSON.parse(tr(r,"utf-8"))}catch{C.cancel("No GenerateSaaS project found in this directory."),process.exit(1)}let o=await C.text({message:'Type "eject" to confirm (this cannot be undone):',validate:a=>{if(a!=="eject")return'Type "eject" to confirm, or press Ctrl+C to cancel.'}});if(C.isCancel(o)&&(C.cancel("Eject cancelled."),process.exit(0)),n.licenseToken)try{await fetch("https://generatesaas.com/api/v1/heartbeat",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${n.licenseToken}`},body:JSON.stringify({event:"eject",version:n.version,frontend:n.frontend}),signal:AbortSignal.timeout(5e3)}),C.log.info("Recorded the opt-out with generatesaas.com (final event - nothing is sent after this).")}catch{C.log.warn("Could not reach generatesaas.com to record the opt-out. Ejecting anyway.")}let i=[],s=[];for(let a of js(n.aiTools))er(we(t,a))&&i.push(a);for(let a of $s)er(we(t,a))&&i.push(a);er(we(t,M))&&i.push(M+"/");for(let a of Us){let d=we(t,a.file);Vs(d,a.removals)?s.push(a.file):It(d)&&C.log.warn(`Could not auto-modify ${a.file} - manually remove license/heartbeat references.`)}Ms(t)&&s.push(".gitignore");for(let a of i)C.log.info(`Deleted ${a}`);for(let a of s)C.log.info(`Modified ${a}`);C.log.success("Ejected successfully. This project is now fully standalone.")})}var be=new Fs().name("generatesaas").description("CLI for scaffolding and managing GenerateSaaS projects").version("1.17.0").addHelpText("after",`
1207
+ `);return n===r?!1:(Hn(t,n,"utf-8"),!0)}function Yn(e){e.command("eject").description("Remove all GenerateSaaS ties - manifest, license, heartbeat, skills").action(async()=>{let t=process.cwd(),r=we(t,re),n;try{n=JSON.parse(tr(r,"utf-8"))}catch{C.cancel("No GenerateSaaS project found in this directory."),process.exit(1)}let o=await C.text({message:'Type "eject" to confirm (this cannot be undone):',validate:a=>{if(a!=="eject")return'Type "eject" to confirm, or press Ctrl+C to cancel.'}});if(C.isCancel(o)&&(C.cancel("Eject cancelled."),process.exit(0)),n.licenseToken)try{await fetch("https://generatesaas.com/api/v1/heartbeat",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${n.licenseToken}`},body:JSON.stringify({event:"eject",version:n.version,frontend:n.frontend}),signal:AbortSignal.timeout(5e3)}),C.log.info("Recorded the opt-out with generatesaas.com (final event - nothing is sent after this).")}catch{C.log.warn("Could not reach generatesaas.com to record the opt-out. Ejecting anyway.")}let i=[],s=[];for(let a of js(n.aiTools))er(we(t,a))&&i.push(a);for(let a of $s)er(we(t,a))&&i.push(a);er(we(t,M))&&i.push(M+"/");for(let a of Us){let d=we(t,a.file);Vs(d,a.removals)?s.push(a.file):It(d)&&C.log.warn(`Could not auto-modify ${a.file} - manually remove license/heartbeat references.`)}Ms(t)&&s.push(".gitignore");for(let a of i)C.log.info(`Deleted ${a}`);for(let a of s)C.log.info(`Modified ${a}`);C.log.success("Ejected successfully. This project is now fully standalone.")})}var be=new Fs().name("generatesaas").description("CLI for scaffolding and managing GenerateSaaS projects").version("1.18.0").addHelpText("after",`
1207
1208
  Examples:
1208
1209
  $ generatesaas init Interactive setup
1209
1210
  $ 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": "1.17.0",
3
+ "version": "1.18.0",
4
4
  "type": "module",
5
5
  "description": "CLI for scaffolding and managing GenerateSaaS projects",
6
6
  "license": "UNLICENSED",