generatesaas 1.6.0 → 1.7.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 +2 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1102,7 +1102,7 @@ export { ops } from "./${r}/index";
|
|
|
1102
1102
|
${m}`:s.message))}else n()})})}async function on(e){if(!ye("pnpm"))return A.log.warn("pnpm not found. Skipping lockfile regeneration."),!1;try{return await he("pnpm",["install","--lockfile-only","--no-frozen-lockfile","--config.minimumReleaseAge=0"],e),!0}catch(t){let r=t instanceof Error?t.message:String(t);return A.log.warn(`Lockfile regeneration failed: ${r}`),A.log.warn("Deploys using --frozen-lockfile may fail."),!1}}async function sn(e){if(!ye("pnpm"))return A.log.warn("pnpm not found. Skipping dependency installation."),A.log.info("Install pnpm: https://pnpm.io/installation"),!1;let t=A.spinner();t.start("Installing dependencies (this may take a minute)...");try{return await he("pnpm",["install","--config.minimumReleaseAge=0"],e),t.stop("Dependencies installed."),!0}catch(r){t.stop("Dependency installation failed.");let i=r instanceof Error?r.message:String(r);return A.log.warn(`pnpm install failed: ${i}`),A.log.warn("You can run it manually later."),!1}}async function an(e){if(!ye("pnpm"))return!1;let t=A.spinner();t.start("Generating baseline database migration...");try{return await he("pnpm",["-F","@repo/database","generate"],e),t.stop("Baseline migration generated."),!0}catch(r){t.stop("Baseline migration generation failed.");let i=r instanceof Error?r.message:String(r);return A.log.warn(`Could not generate baseline migration: ${i}`),A.log.warn("Run 'pnpm -F @repo/database generate' before your first deploy."),!1}}async function cn(e){try{return await nn($t(e,".git")),A.log.info("Git repository already exists, skipping init."),!0}catch{}if(!ye("git"))return A.log.warn("git not found. Skipping repository initialization."),!1;let t=A.spinner();t.start("Initializing git repository...");try{return await he("git",["init"],e),await he("git",["add","-A"],e),await he("git",["commit","--no-verify","-m","Initial commit from GenerateSaaS"],e),t.stop("Git repository initialized."),!0}catch{return t.stop("Git initialization failed."),A.log.warn("You can run git init manually later."),!1}}async function ln(e){if(!ye("pnpm"))return!1;try{await nn($t(e,".git"))}catch{return!1}try{let t=JSON.parse(await Ao($t(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 he("pnpm",["exec","simple-git-hooks"],e),!0}catch{return A.log.warn("Could not install git hooks. Run 'pnpm exec simple-git-hooks' manually."),!1}}import*as Re from"@clack/prompts";import U from"picocolors";function pn(e,t){t.dockerComposeGenerated&&!t.dockerAvailable&&Re.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=>we[s].label).join(", ");r.push(`pnpm infra ${U.dim(`# ${o}`)}`)}if(r.push(`pnpm dev ${U.dim("# http://localhost:3000")}`),t.skippedCredentials.length>0&&(r.push(""),r.push(U.dim("Fill in remaining TODO values in .env"))),Re.note(r.join(`
|
|
1103
1103
|
`),U.yellow("Start Development")),t.dockerComposeGenerated){let o=[];o.push(`App ${U.cyan("http://localhost:3000")}`),e.architecture==="separate"&&o.push(`API ${U.cyan("http://localhost:3010")}`),e.dockerServices.includes("mailpit")&&o.push(`Mailpit ${U.cyan("http://localhost:8025")}`),e.dockerServices.includes("inngest")&&o.push(`Inngest ${U.cyan("http://localhost:8288")}`),Re.note(o.join(`
|
|
1104
1104
|
`),U.yellow("Dev Tools"))}let i=[],n=Io(e);n.length>0&&i.push(`Set in production: ${U.dim(n.join(", "))}`),i.push("pnpm db:push # Run database migrations"),i.push(Po(e)),Re.note(i.join(`
|
|
1105
|
-
`),U.yellow("Deployment"))}function Io(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 Po(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(!it(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(!Ue.includes(e.frontend))throw new Error(`Invalid frontend "${e.frontend}". Valid values: ${Ue.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=Ut(e.docker,Qe,"docker service")),e.aiTools!==void 0&&(t.aiTools=Ut(e.aiTools,et,"AI tool")),e.socialProviders!==void 0&&(t.socialProviders=Ut(e.socialProviders,rt,"social provider")),e.currency!==void 0){if(!ce.includes(e.currency))throw new Error(`Invalid currency "${e.currency}". Valid values: ${ce.join(", ")}`);t.defaultCurrency=e.currency}if(e.deploy!==void 0){if(!le.includes(e.deploy))throw new Error(`Invalid deployment target "${e.deploy}". Valid values: ${le.join(", ")}`);t.deploymentTarget=e.deploy}if(e.database!==void 0){if(!pe.includes(e.database))throw new Error(`Invalid database provider "${e.database}". Valid values: ${pe.join(", ")}`);t.databaseProvider=e.database}if(e.cache!==void 0){if(!de.includes(e.cache))throw new Error(`Invalid cache provider "${e.cache}". Valid values: ${de.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 ne={projectName:"my-saas",frontend:"nextjs",architecture:"fullstack",paymentProvider:"stripe",emailProvider:"smtp",multiTenancy:!1,billingScope:"user",blog:!0,docs:!1,revenueSharing:!1,credits:!0,dockerServices:["postgres","redis","inngest"],aiTools:[],socialProviders:[],defaultCurrency:"USD",deploymentTarget:"node",databaseProvider:"postgres",cacheProvider:"redis"};function un(e){let t=e.projectName??ne.projectName,r=e.projectDir??`./${t}`,i=e.appName??nt(t),n=e.deploymentTarget??ne.deploymentTarget,o=F[n]?.edgeRuntime??!1,s=e.databaseProvider??(o?"neon":ne.databaseProvider),a=e.cacheProvider??(o?"upstash":ne.cacheProvider),d=e.emailProvider??(o?"resend":ne.emailProvider),h=e.dockerServices??(o?ne.dockerServices.filter(m=>m!=="postgres"&&m!=="redis"):ne.dockerServices),g={...ne,...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 Le){if(g.deploymentTarget!==m.target)continue;let S=g.databaseProvider===m.provider?"database":"cache";if(g.databaseProvider===m.provider||g.cacheProvider===m.provider)throw new Error(`Incompatible: --deploy ${m.target} + --${S} ${m.provider}. ${m.reason}`)}for(let m of $e)if(g.architecture===m.architecture&&g.deploymentTarget===m.target)throw new Error(`Incompatible: --architecture ${m.architecture} + --deploy ${m.target}. ${m.reason}`);return g}function Ut(e,t,r){if(e.trim()==="")return[];let i=e.split(",").map(o=>o.trim()).filter(Boolean),n=i.filter(o=>!t.includes(o));if(n.length>0)throw new Error(`Invalid ${r}(s): ${n.join(", ")}. Valid values: ${t.join(", ")}`);return i}import Do from"picocolors";var Co="a10a6fb9d7cadde32e37dad52059d17b5d2b916b08c76d8fbcc99982e9a3d87f";function No(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 mn(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([...Ue])).addOption(new j("--architecture <type>","fullstack or separate").choices([...We])).addOption(new j("--payment <provider>","payment provider").choices([...Xe])).addOption(new j("--email <provider>","email provider").choices([...Ze])).option("--org","enable multi-tenancy (organizations)").option("--no-org","disable multi-tenancy").addOption(new j("--billing-scope <scope>","billing scope (requires --org)").choices([...tt])).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([...ce])).addOption(new j("--deploy <target>","deployment target").choices([...le])).addOption(new j("--database <provider>","database provider").choices([...pe])).addOption(new j("--cache <provider>","cache provider").choices([...de])).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 Lo(t)})}async function Lo(e){let t=performance.now();Gt("1.6.0");let r,i;try{r=dn(e),i=No(e.templateVersion)}catch(y){E.cancel(I(y)),process.exit(1)}let n=E.spinner(),o;try{o=await be({apiKey:e.apiKey,prompt:!e.yes})}catch(y){E.cancel(I(y)),process.exit(1)}e.demo&&xt(o)!==Co&&(E.cancel("--demo is restricted to first-party demo deployments."),process.exit(1));let s=Z(o),a=async()=>{let y=await re(s),k=y.latest,Y=i??k;if(i&&!y.versions.some(J=>J.version===Y))throw new Error(`Template version "${i}" is not available.`);return{latestVersion:k,selectedVersion:Y}};n.start("Verifying access...");let d,h;try{({latestVersion:d,selectedVersion:h}=await a()),n.stop("Access verified."),me(o)}catch(y){if(n.stop("Access verification failed."),y instanceof _&&y.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 je(),s=Z(o),n.start("Verifying access...");try{({latestVersion:d,selectedVersion:h}=await a()),n.stop("Access verified."),me(o)}catch(k){n.stop("Access verification failed."),E.cancel(k instanceof _&&k.status===401?"Invalid API key.":I(k)),process.exit(1)}}else E.cancel(I(y)),process.exit(1)}E.log.success(`Latest version: ${d}`),h!==d&&E.log.success(`Using template version: ${h}`);let g;e.yes?g=un(r):g=await Ht(r);let m;n.start("Activating license...");try{let y=crypto.randomUUID(),k=()=>({frontend:g.frontend,version:h,installId:y,projectName:g.projectName,options:Zt(g)}),Y;try{Y=await bt(s,k())}catch(J){let q=ct(J);if(!q?.lastAllowedVersion)throw J;n.stop("License activation failed."),e.yes&&(E.cancel(`${q.message} Re-run with --template-version ${q.lastAllowedVersion}.`),process.exit(1));let Ee=await E.confirm({message:`Your update window has ended. Continue with v${q.lastAllowedVersion} (the last version your license covers)?`});(E.isCancel(Ee)||!Ee)&&(E.cancel("Setup cancelled."),process.exit(0)),h=q.lastAllowedVersion,n.start(`Activating license for v${h}...`),Y=await bt(s,k())}m={token:Y.token,keyHash:xt(o),installId:y},n.stop("License activated.")}catch(y){n.stop("License activation failed."),E.cancel(I(y)),process.exit(1)}let S=xo(g.projectDir);if(ko(S)&&_o(S).length>0)if(e.yes)E.log.info(`Directory ${S} is not empty. Merging (keeping existing files, overwriting conflicts).`);else{let k=await E.select({message:`Directory ${S} is not empty.`,options:[{value:"merge",label:"Merge",hint:"keep existing files, overwrite conflicts"},{value:"overwrite",label:"Overwrite",hint:"delete everything and start fresh"},{value:"cancel",label:"Cancel"}]});(E.isCancel(k)||k==="cancel")&&(E.cancel("Setup cancelled."),process.exit(0)),k==="overwrite"&&Ro(S,{recursive:!0,force:!0})}let b={...g,projectDir:S,version:h,...e.demo?{docs:!1}:{}};n.start("Downloading template...");try{await lt(s,h,S),n.stop("Template downloaded.")}catch(y){n.stop("Download failed."),E.cancel(I(y)),process.exit(1)}let H;n.start("Generating project files...");try{if({dockerComposeGenerated:H}=await gt(b),!e.demo){let y=await Fe(S);await l(Oo(S,st),JSON.stringify(y,null," ")+`
|
|
1105
|
+
`),U.yellow("Deployment"))}function Io(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 Po(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(!it(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(!Ue.includes(e.frontend))throw new Error(`Invalid frontend "${e.frontend}". Valid values: ${Ue.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=Ut(e.docker,Qe,"docker service")),e.aiTools!==void 0&&(t.aiTools=Ut(e.aiTools,et,"AI tool")),e.socialProviders!==void 0&&(t.socialProviders=Ut(e.socialProviders,rt,"social provider")),e.currency!==void 0){if(!ce.includes(e.currency))throw new Error(`Invalid currency "${e.currency}". Valid values: ${ce.join(", ")}`);t.defaultCurrency=e.currency}if(e.deploy!==void 0){if(!le.includes(e.deploy))throw new Error(`Invalid deployment target "${e.deploy}". Valid values: ${le.join(", ")}`);t.deploymentTarget=e.deploy}if(e.database!==void 0){if(!pe.includes(e.database))throw new Error(`Invalid database provider "${e.database}". Valid values: ${pe.join(", ")}`);t.databaseProvider=e.database}if(e.cache!==void 0){if(!de.includes(e.cache))throw new Error(`Invalid cache provider "${e.cache}". Valid values: ${de.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 ne={projectName:"my-saas",frontend:"nextjs",architecture:"fullstack",paymentProvider:"stripe",emailProvider:"smtp",multiTenancy:!1,billingScope:"user",blog:!0,docs:!1,revenueSharing:!1,credits:!0,dockerServices:["postgres","redis","inngest"],aiTools:[],socialProviders:[],defaultCurrency:"USD",deploymentTarget:"node",databaseProvider:"postgres",cacheProvider:"redis"};function un(e){let t=e.projectName??ne.projectName,r=e.projectDir??`./${t}`,i=e.appName??nt(t),n=e.deploymentTarget??ne.deploymentTarget,o=F[n]?.edgeRuntime??!1,s=e.databaseProvider??(o?"neon":ne.databaseProvider),a=e.cacheProvider??(o?"upstash":ne.cacheProvider),d=e.emailProvider??(o?"resend":ne.emailProvider),h=e.dockerServices??(o?ne.dockerServices.filter(m=>m!=="postgres"&&m!=="redis"):ne.dockerServices),g={...ne,...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 Le){if(g.deploymentTarget!==m.target)continue;let S=g.databaseProvider===m.provider?"database":"cache";if(g.databaseProvider===m.provider||g.cacheProvider===m.provider)throw new Error(`Incompatible: --deploy ${m.target} + --${S} ${m.provider}. ${m.reason}`)}for(let m of $e)if(g.architecture===m.architecture&&g.deploymentTarget===m.target)throw new Error(`Incompatible: --architecture ${m.architecture} + --deploy ${m.target}. ${m.reason}`);return g}function Ut(e,t,r){if(e.trim()==="")return[];let i=e.split(",").map(o=>o.trim()).filter(Boolean),n=i.filter(o=>!t.includes(o));if(n.length>0)throw new Error(`Invalid ${r}(s): ${n.join(", ")}. Valid values: ${t.join(", ")}`);return i}import Do from"picocolors";var Co="a10a6fb9d7cadde32e37dad52059d17b5d2b916b08c76d8fbcc99982e9a3d87f";function No(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 mn(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 j("--frontend <type>","frontend framework").choices([...Ue])).addOption(new j("--architecture <type>","fullstack or separate").choices([...We])).addOption(new j("--payment <provider>","payment provider").choices([...Xe])).addOption(new j("--email <provider>","email provider").choices([...Ze])).option("--org","enable multi-tenancy (organizations)").option("--no-org","disable multi-tenancy").addOption(new j("--billing-scope <scope>","billing scope (requires --org)").choices([...tt])).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([...ce])).addOption(new j("--deploy <target>","deployment target").choices([...le])).addOption(new j("--database <provider>","database provider").choices([...pe])).addOption(new j("--cache <provider>","cache provider").choices([...de])).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,r)=>{await Lo(t?{...r,apiKey:t}:r)})}async function Lo(e){let t=performance.now();Gt("1.7.0");let r,i;try{r=dn(e),i=No(e.templateVersion)}catch(y){E.cancel(I(y)),process.exit(1)}let n=E.spinner(),o;try{o=await be({apiKey:e.apiKey,prompt:!e.yes})}catch(y){E.cancel(I(y)),process.exit(1)}e.demo&&xt(o)!==Co&&(E.cancel("--demo is restricted to first-party demo deployments."),process.exit(1));let s=Z(o),a=async()=>{let y=await re(s),k=y.latest,Y=i??k;if(i&&!y.versions.some(J=>J.version===Y))throw new Error(`Template version "${i}" is not available.`);return{latestVersion:k,selectedVersion:Y}};n.start("Verifying access...");let d,h;try{({latestVersion:d,selectedVersion:h}=await a()),n.stop("Access verified."),me(o)}catch(y){if(n.stop("Access verification failed."),y instanceof _&&y.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 je(),s=Z(o),n.start("Verifying access...");try{({latestVersion:d,selectedVersion:h}=await a()),n.stop("Access verified."),me(o)}catch(k){n.stop("Access verification failed."),E.cancel(k instanceof _&&k.status===401?"Invalid API key.":I(k)),process.exit(1)}}else E.cancel(I(y)),process.exit(1)}E.log.success(`Latest version: ${d}`),h!==d&&E.log.success(`Using template version: ${h}`);let g;e.yes?g=un(r):g=await Ht(r);let m;n.start("Activating license...");try{let y=crypto.randomUUID(),k=()=>({frontend:g.frontend,version:h,installId:y,projectName:g.projectName,options:Zt(g)}),Y;try{Y=await bt(s,k())}catch(J){let q=ct(J);if(!q?.lastAllowedVersion)throw J;n.stop("License activation failed."),e.yes&&(E.cancel(`${q.message} Re-run with --template-version ${q.lastAllowedVersion}.`),process.exit(1));let Ee=await E.confirm({message:`Your update window has ended. Continue with v${q.lastAllowedVersion} (the last version your license covers)?`});(E.isCancel(Ee)||!Ee)&&(E.cancel("Setup cancelled."),process.exit(0)),h=q.lastAllowedVersion,n.start(`Activating license for v${h}...`),Y=await bt(s,k())}m={token:Y.token,keyHash:xt(o),installId:y},n.stop("License activated.")}catch(y){n.stop("License activation failed."),E.cancel(I(y)),process.exit(1)}let S=xo(g.projectDir);if(ko(S)&&_o(S).length>0)if(e.yes)E.log.info(`Directory ${S} is not empty. Merging (keeping existing files, overwriting conflicts).`);else{let k=await E.select({message:`Directory ${S} is not empty.`,options:[{value:"merge",label:"Merge",hint:"keep existing files, overwrite conflicts"},{value:"overwrite",label:"Overwrite",hint:"delete everything and start fresh"},{value:"cancel",label:"Cancel"}]});(E.isCancel(k)||k==="cancel")&&(E.cancel("Setup cancelled."),process.exit(0)),k==="overwrite"&&Ro(S,{recursive:!0,force:!0})}let b={...g,projectDir:S,version:h,...e.demo?{docs:!1}:{}};n.start("Downloading template...");try{await lt(s,h,S),n.stop("Template downloaded.")}catch(y){n.stop("Download failed."),E.cancel(I(y)),process.exit(1)}let H;n.start("Generating project files...");try{if({dockerComposeGenerated:H}=await gt(b),!e.demo){let y=await Fe(S);await l(Oo(S,st),JSON.stringify(y,null," ")+`
|
|
1106
1106
|
`),await Qr(S,S)}await rn(S,b.aiTools),await Xr(b,m),n.stop("Project files generated.")}catch(y){n.stop("Generation failed."),E.cancel(I(y)),process.exit(1)}await on(S);let R=await sn(S);R&&b.demo!==!0&&e.dbMigration!==!1&&await an(S),await cn(S),R&&await ln(S);let ie=ye("docker"),T=Et(b).map(y=>y.key).filter(y=>!b.credentials?.[y]);pn(b,{pnpmInstalled:R,dockerComposeGenerated:H,dockerAvailable:ie,skippedCredentials:T}),Kt(),E.log.info(Do.dim(`Done in ${((performance.now()-t)/1e3).toFixed(1)}s`))}import{existsSync as fn}from"fs";import{readFile as Vo}from"fs/promises";import{join as Ge,resolve as Fo}from"path";import*as P from"@clack/prompts";import Oe from"picocolors";import{mkdtemp as $o,rm as Uo}from"fs/promises";import{tmpdir as jo}from"os";import{join as Mo}from"path";async function jt(e,t,r,i){let n=await $o(Mo(jo(),"generatesaas-stage-"));try{await lt(e,t,n),await gt({...r,projectDir:n}),await Dt(n,i)}finally{await Uo(n,{recursive:!0,force:!0})}}function gn(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=Fo(t.cwd??process.cwd()),i=Ge(r,Q),n;try{n=JSON.parse(await Vo(i,"utf-8"))}catch{P.cancel(".generatesaas/manifest.json not found. Run this from a GenerateSaaS project."),process.exit(1)}let o;try{o=await be()}catch(d){P.cancel(I(d)),process.exit(1)}let s=Z(o),a=P.spinner();try{a.start("Verifying access...");let d;try{d=await re(s)}catch(T){throw T instanceof _&&T.status===401?new Error("Your saved API key was rejected. Run `generatesaas auth` to update it, or set GENERATESAAS_API_KEY."):T}a.stop("Access verified."),me(o),a.start("Fetching latest skill files...");let h=await Xt(s,d.latest);await Lt(r,h.skillMd,h.scripts,n.aiTools);let g=Nt(n.aiTools);if(a.stop("Skills updated."),P.log.success(`Skill files installed to ${Oe.cyan(g.length.toString())} locations.`),n.version===d.latest){P.log.info(`Already on the latest version (${n.version}).`);return}if(n.licenseToken)try{let T=await Qt(s,{currentToken:n.licenseToken,newVersion:d.latest});n.licenseToken=T.token,await l(i,JSON.stringify(n,null," ")+`
|
|
1107
1107
|
`),P.log.success("License refreshed.")}catch(T){let y=ct(T);y&&(P.cancel(y.message),process.exit(1)),P.log.warn("License refresh skipped.")}let m=Wr(n,r),S=Ge(r,Jt);a.start(`Staging v${d.latest} (shaped for your config)...`),await jt(s,d.latest,m,S),a.stop("Template staged.");let b=await Wt(s,d.latest);b&&P.note(b,`Changelog v${d.latest}`);let H=Ge(r,st),R=Ge(r,at),ie=!fn(R),oe=!fn(H);if(ie){if(a.start("Building baseline template (one-time migration)..."),await jt(s,n.version,m,R),oe){let T=await Fe(R);await l(H,JSON.stringify(T,null," ")+`
|
|
1108
1108
|
`)}a.stop("Baseline template stored.")}else if(oe){a.start("Computing baseline template hashes...");let T=await Fe(R);await l(H,JSON.stringify(T,null," ")+`
|
|
@@ -1116,7 +1116,7 @@ ${m}`:s.message))}else n()})})}async function on(e){if(!ye("pnpm"))return A.log.
|
|
|
1116
1116
|
`,` .route("/license", licenseRoutes)
|
|
1117
1117
|
`]}];function Zo(e){return(e&&e.length>0?e.map(r=>Be[r]):Object.values(Be)).map(r=>ve(r,Ct))}function Vt(e){return yt(e)?(qo(e,{recursive:!0}),!0):!1}function Qo(e,t){if(!yt(e))return!1;let r=Ft(e,"utf-8"),i=r;for(let n of t)i=i.replace(n,"");return i===r?!1:(wn(e,i,"utf-8"),!0)}function es(e){let t=ve(e,".gitignore");if(!yt(t))return!1;let r=Ft(t,"utf-8"),i=r.split(`
|
|
1118
1118
|
`).filter(n=>!n.includes(".generatesaas")).join(`
|
|
1119
|
-
`);return i===r?!1:(wn(t,i,"utf-8"),!0)}function bn(e){e.command("eject").description("Remove all GenerateSaaS ties - manifest, license, heartbeat, skills").action(async()=>{let t=process.cwd(),r=ve(t,Q),i;try{i=JSON.parse(Ft(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 Zo(i.aiTools))Vt(ve(t,a))&&o.push(a);for(let a of Wo)Vt(ve(t,a))&&o.push(a);Vt(ve(t,$))&&o.push($+"/");for(let a of Xo){let d=ve(t,a.file);Qo(d,a.removals)?s.push(a.file):yt(d)&&L.log.warn(`Could not auto-modify ${a.file} - manually remove license/heartbeat references.`)}es(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 Se=new ts().name("generatesaas").description("CLI for scaffolding and managing GenerateSaaS projects").version("1.
|
|
1119
|
+
`);return i===r?!1:(wn(t,i,"utf-8"),!0)}function bn(e){e.command("eject").description("Remove all GenerateSaaS ties - manifest, license, heartbeat, skills").action(async()=>{let t=process.cwd(),r=ve(t,Q),i;try{i=JSON.parse(Ft(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 Zo(i.aiTools))Vt(ve(t,a))&&o.push(a);for(let a of Wo)Vt(ve(t,a))&&o.push(a);Vt(ve(t,$))&&o.push($+"/");for(let a of Xo){let d=ve(t,a.file);Qo(d,a.removals)?s.push(a.file):yt(d)&&L.log.warn(`Could not auto-modify ${a.file} - manually remove license/heartbeat references.`)}es(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 Se=new ts().name("generatesaas").description("CLI for scaffolding and managing GenerateSaaS projects").version("1.7.0").addHelpText("after",`
|
|
1120
1120
|
Examples:
|
|
1121
1121
|
$ generatesaas init Interactive setup
|
|
1122
1122
|
$ generatesaas init -n my-app -y Quick setup with defaults
|