create-sailor 0.1.0 → 1.1.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 +77 -19
  2. package/package.json +7 -2
package/dist/index.js CHANGED
@@ -1,16 +1,61 @@
1
1
  #!/usr/bin/env node
2
- import T from"path";import*as n from"@clack/prompts";import{Command as U}from"commander";import d from"picocolors";import q from"fs";import O from"path";async function P(o,c){let a=O.join(o,"nebutra.config.json");q.writeFileSync(a,JSON.stringify(c,null,2)+`
3
- `)}import x from"fs";import w from"path";async function C(o,c){let a=`# Automatically injected by create-sailor
4
- DATABASE_URL="${String(c.databaseUrl)}"
2
+ import ct from"fs";import Q from"path";import*as h from"@clack/prompts";import{Command as pt}from"commander";import I from"picocolors";import lt from"update-notifier";import qe from"cfonts";import Ue from"picocolors";var ne="1.0.0";function se(){if(process.env.NO_COLOR||!process.stdout.isTTY){process.stdout.write([`Sailor v${ne}`,"AI-Native SaaS Unicorn Template","by Nebutra \xB7 https://nebutra.com","",""].join(`
3
+ `));return}qe.say("Sailor",{font:"block",align:"left",colors:["#0033FE","#0BF1C3"],gradient:["#0033FE","#0BF1C3"],transitionGradient:!0,space:!1}),process.stdout.write(Ue.dim(` AI-Native SaaS Template \xB7 v${ne} \xB7 nebutra.com
5
4
 
6
- NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY="${String(c.clerkPublishable)}"
7
- CLERK_SECRET_KEY="${String(c.clerkSecret)}"
5
+ `))}import k from"picocolors";function Fe(){return!process.env.NO_COLOR&&!!process.stdout.isTTY}function re(e){let t=Fe(),n=t?"\u2693":"-",s=t?"\u25B8":"->",r=t?"\u2605":"*",i=t?"\u{1F4E7}":"*",c=["",` ${t?k.bold(`${n} Done in ${e.elapsedSec}s \xB7 ${e.targetDir}/`):`${n} Done in ${e.elapsedSec}s \xB7 ${e.targetDir}/`}`,"",` ${t?k.bold("Next:"):"Next:"}`,` ${s} cd ${e.targetDir}`];e.skippedInstall&&c.push(` ${s} pnpm install (if --no-install was set)`),c.push(` ${s} pnpm dev ${t?k.dim("\u2192 http://localhost:3000"):"-> http://localhost:3000"}`,"",` ${t?k.bold("Customize:"):"Customize:"}`,` ${s} pnpm sailor brand init ${t?k.dim("\u2192 your colors, logo, domain"):"-> your colors, logo, domain"}`,` ${s} pnpm sailor preset apply ${t?k.dim("\u2192 toggle features"):"-> toggle features"}`,` ${s} pnpm sailor add <feat> ${t?k.dim("\u2192 add queue/search/cache/..."):"-> add queue/search/cache/..."}`,"",` ${t?k.bold("Ship faster:"):"Ship faster:"}`,` ${r} Star us https://github.com/nebutra/nebutra-sailor`,` ${i} Free license https://nebutra.com/get-license`,""),process.stdout.write(c.join(`
6
+ `)+`
7
+ `)}var De=`Usage: create-sailor [name] [options]
8
+
9
+ Arguments:
10
+ name project directory (default: ./my-saas-app)
11
+
12
+ Options:
13
+ -p, --pm <id> npm | pnpm | yarn | bun (auto-detected)
14
+ --orm <id> prisma | drizzle | none
15
+ --db <id> postgres | mysql | sqlite | none
16
+ --auth <id> clerk | betterauth | none
17
+ --payment <id> stripe | lemon | wechat | alipay | none
18
+ --ai <ids> comma-separated provider ids (e.g. openai,anthropic)
19
+ --deploy <target> vercel | railway | cloudflare | selfhost
20
+ --docs <id> fumadocs | mintlify | docusaurus | nextra | vitepress | none
21
+ --i18n enable i18n (default: true)
22
+ --no-i18n disable
23
+ --no-install skip package install
24
+ --no-git skip git init
25
+ -y, --yes accept all defaults (non-interactive)
26
+ --dry-run preview actions without writing files
27
+ --json machine-readable output
28
+ --no-color disable color output
29
+ -h, --help show this help
30
+ -v, --version show version
31
+
32
+ Examples:
33
+ $ npm create sailor@latest
34
+ $ npm create sailor@latest my-app -y
35
+ $ npm create sailor@latest my-app --auth=clerk --ai=openai,deepseek
36
+ $ npm create sailor@latest --dry-run
37
+ `;function oe(){process.stdout.write(De)}import B from"picocolors";function ae(){return!process.env.NO_COLOR&&!!process.stdout.isTTY}function Ye(e){let t=ae()?"\u25B8":"->",n=`[${e.index}/${e.total}]`,s=e.label.padEnd(18," ");return ae()?`${B.dim(n)} ${B.bold(s)} ${B.dim(t)} ${B.cyan(e.value)}`:`${n} ${s} ${t} ${e.value}`}function ie(e){process.stdout.write(Ye(e)+`
38
+ `)}var G=[{id:"anthropic",name:"Anthropic",category:"\u76F4\u63A5\u5B9E\u9A8C\u5BA4",status:"opencode",docs:"https://docs.anthropic.com",envVarPrefix:"ANTHROPIC",requiredEnvVars:["ANTHROPIC_API_KEY"]},{id:"openai",name:"OpenAI",category:"\u76F4\u63A5\u5B9E\u9A8C\u5BA4",status:"opencode",docs:"https://platform.openai.com/docs",envVarPrefix:"OPENAI",requiredEnvVars:["OPENAI_API_KEY"]},{id:"deepseek",name:"DeepSeek",category:"\u76F4\u63A5\u5B9E\u9A8C\u5BA4",status:"opencode",docs:"https://platform.deepseek.com/api-docs",envVarPrefix:"DEEPSEEK",requiredEnvVars:["DEEPSEEK_API_KEY"]},{id:"xai",name:"xAI (Grok)",category:"\u76F4\u63A5\u5B9E\u9A8C\u5BA4",status:"opencode",docs:"https://docs.x.ai/api",envVarPrefix:"XAI",requiredEnvVars:["XAI_API_KEY"]},{id:"moonshot",name:"Moonshot (Kimi)",category:"\u76F4\u63A5\u5B9E\u9A8C\u5BA4",status:"opencode",docs:"https://platform.moonshot.cn/docs",envVarPrefix:"MOONSHOT",requiredEnvVars:["MOONSHOT_API_KEY","MOONSHOT_BASE_URL"]},{id:"google",name:"Google Gemini",category:"\u76F4\u63A5\u5B9E\u9A8C\u5BA4",status:"ai-sdk",docs:"https://ai.google.dev/gemini-api/docs",envVarPrefix:"GOOGLE_GENERATIVE_AI",requiredEnvVars:["GOOGLE_GENERATIVE_AI_API_KEY"]},{id:"mistral",name:"Mistral AI",category:"\u76F4\u63A5\u5B9E\u9A8C\u5BA4",status:"ai-sdk",docs:"https://docs.mistral.ai",envVarPrefix:"MISTRAL",requiredEnvVars:["MISTRAL_API_KEY"]},{id:"cohere",name:"Cohere",category:"\u76F4\u63A5\u5B9E\u9A8C\u5BA4",status:"ai-sdk",docs:"https://docs.cohere.com",envVarPrefix:"COHERE",requiredEnvVars:["COHERE_API_KEY"]},{id:"perplexity",name:"Perplexity",category:"\u76F4\u63A5\u5B9E\u9A8C\u5BA4",status:"cn-compatible",docs:"https://docs.perplexity.ai",baseURL:"https://api.perplexity.ai",envVarPrefix:"PERPLEXITY",requiredEnvVars:["PERPLEXITY_API_KEY"]},{id:"ai21",name:"AI21 Labs",category:"\u76F4\u63A5\u5B9E\u9A8C\u5BA4",status:"cn-compatible",docs:"https://docs.ai21.com",baseURL:"https://api.ai21.com/studio/v1",envVarPrefix:"AI21",requiredEnvVars:["AI21_API_KEY"]},{id:"upstage",name:"Upstage (Solar)",category:"\u76F4\u63A5\u5B9E\u9A8C\u5BA4",status:"cn-compatible",docs:"https://developers.upstage.ai/docs",baseURL:"https://api.upstage.ai/v1/solar",envVarPrefix:"UPSTAGE",requiredEnvVars:["UPSTAGE_API_KEY"]},{id:"siliconflow",name:"\u7845\u57FA\u6D41\u52A8 SiliconFlow",category:"\u56FD\u5185\u5E73\u53F0",status:"cn-compatible",docs:"https://docs.siliconflow.cn",baseURL:"https://api.siliconflow.cn/v1",envVarPrefix:"SILICONFLOW",requiredEnvVars:["SILICONFLOW_API_KEY"]},{id:"volcengine-ark",name:"\u706B\u5C71\u5F15\u64CE ARK (\u8C46\u5305)",category:"\u56FD\u5185\u5E73\u53F0",status:"cn-compatible",docs:"https://www.volcengine.com/docs/82379",baseURL:"https://ark.cn-beijing.volces.com/api/v3",envVarPrefix:"ARK",requiredEnvVars:["ARK_API_KEY"]},{id:"bailian",name:"\u767E\u70BC Bailian (\u901A\u4E49)",category:"\u56FD\u5185\u5E73\u53F0",status:"cn-compatible",docs:"https://help.aliyun.com/zh/model-studio",baseURL:"https://dashscope.aliyuncs.com/compatible-mode/v1",envVarPrefix:"DASHSCOPE",requiredEnvVars:["DASHSCOPE_API_KEY"]},{id:"zhipu",name:"\u667A\u8C31 AI Zhipu",category:"\u56FD\u5185\u5E73\u53F0",status:"ai-sdk",docs:"https://docs.bigmodel.cn",baseURL:"https://open.bigmodel.cn/api/paas/v4",envVarPrefix:"ZHIPU",requiredEnvVars:["ZHIPU_API_KEY"]},{id:"baichuan",name:"\u767E\u5DDD\u667A\u80FD Baichuan",category:"\u56FD\u5185\u5E73\u53F0",status:"cn-compatible",docs:"https://platform.baichuan-ai.com/docs",baseURL:"https://api.baichuan-ai.com/v1",envVarPrefix:"BAICHUAN",requiredEnvVars:["BAICHUAN_API_KEY"]},{id:"minimax",name:"MiniMax (ABAB)",category:"\u56FD\u5185\u5E73\u53F0",status:"cn-compatible",docs:"https://platform.minimaxi.com/document/guides",baseURL:"https://api.minimax.chat/v1",envVarPrefix:"MINIMAX",requiredEnvVars:["MINIMAX_API_KEY","MINIMAX_GROUP_ID"]},{id:"stepfun",name:"\u9636\u8DC3\u661F\u8FB0 StepFun",category:"\u56FD\u5185\u5E73\u53F0",status:"cn-compatible",docs:"https://platform.stepfun.com/docs",baseURL:"https://api.stepfun.com/v1",envVarPrefix:"STEPFUN",requiredEnvVars:["STEPFUN_API_KEY"]},{id:"sensetime",name:"\u5546\u6C64 SenseNova",category:"\u56FD\u5185\u5E73\u53F0",status:"cn-compatible",docs:"https://platform.sensetime.com",baseURL:"https://api.sensenova.cn/v1",envVarPrefix:"SENSENOVA",requiredEnvVars:["SENSENOVA_API_KEY"]},{id:"tencent",name:"\u817E\u8BAF\u6DF7\u5143 Hunyuan",category:"\u56FD\u5185\u5E73\u53F0",status:"cn-compatible",docs:"https://cloud.tencent.com/document/product/1729",baseURL:"https://api.hunyuan.cloud.tencent.com/v1",envVarPrefix:"HUNYUAN",requiredEnvVars:["HUNYUAN_API_KEY"]},{id:"lingyi",name:"\u96F6\u4E00\u4E07\u7269 Yi",category:"\u56FD\u5185\u5E73\u53F0",status:"cn-compatible",docs:"https://platform.lingyiwanwu.com/docs",baseURL:"https://api.lingyiwanwu.com/v1",envVarPrefix:"YI",requiredEnvVars:["YI_API_KEY"]},{id:"openrouter",name:"OpenRouter",category:"\u7EDF\u4E00\u7F51\u5173",status:"opencode",docs:"https://openrouter.ai/docs",envVarPrefix:"OPENROUTER",requiredEnvVars:["OPENROUTER_API_KEY"]},{id:"vercel-gateway",name:"Vercel AI Gateway",category:"\u7EDF\u4E00\u7F51\u5173",status:"opencode",docs:"https://vercel.com/docs/ai-gateway",envVarPrefix:"AI_GATEWAY",requiredEnvVars:["AI_GATEWAY_API_KEY"]},{id:"litellm",name:"LiteLLM Proxy",category:"\u7EDF\u4E00\u7F51\u5173",status:"cn-compatible",docs:"https://docs.litellm.ai",baseURL:"http://localhost:4000/v1",envVarPrefix:"LITELLM",requiredEnvVars:["LITELLM_API_KEY"]},{id:"portkey",name:"Portkey",category:"\u7EDF\u4E00\u7F51\u5173",status:"cn-compatible",docs:"https://portkey.ai/docs",baseURL:"https://api.portkey.ai/v1",envVarPrefix:"PORTKEY",requiredEnvVars:["PORTKEY_API_KEY"]},{id:"aws-bedrock",name:"AWS Bedrock",category:"\u4E91\u5E73\u53F0",status:"ai-sdk",docs:"https://aws.amazon.com/bedrock/",envVarPrefix:"AWS_BEDROCK",requiredEnvVars:["AWS_ACCESS_KEY_ID","AWS_SECRET_ACCESS_KEY","AWS_REGION"]},{id:"azure-openai",name:"Azure OpenAI",category:"\u4E91\u5E73\u53F0",status:"ai-sdk",docs:"https://azure.microsoft.com/en-us/products/cognitive-services/openai-service/",envVarPrefix:"AZURE_OPENAI",requiredEnvVars:["AZURE_OPENAI_API_KEY","AZURE_OPENAI_ENDPOINT"]},{id:"gcp-vertex",name:"GCP Vertex AI",category:"\u4E91\u5E73\u53F0",status:"ai-sdk",docs:"https://cloud.google.com/vertex-ai",envVarPrefix:"GOOGLE_VERTEX",requiredEnvVars:["GOOGLE_APPLICATION_CREDENTIALS"]},{id:"groq",name:"Groq",category:"\u63A8\u7406\u52A0\u901F",status:"opencode",docs:"https://console.groq.com/docs",envVarPrefix:"GROQ",requiredEnvVars:["GROQ_API_KEY"]},{id:"fireworks",name:"Fireworks AI",category:"\u63A8\u7406\u52A0\u901F",status:"opencode",docs:"https://docs.fireworks.ai",envVarPrefix:"FIREWORKS",requiredEnvVars:["FIREWORKS_API_KEY"]},{id:"together",name:"Together AI",category:"\u63A8\u7406\u52A0\u901F",status:"opencode",docs:"https://docs.together.ai",envVarPrefix:"TOGETHER",requiredEnvVars:["TOGETHER_API_KEY"]},{id:"huggingface",name:"Hugging Face",category:"\u63A8\u7406\u52A0\u901F",status:"opencode",docs:"https://huggingface.co/docs/inference-providers",envVarPrefix:"HF",requiredEnvVars:["HF_API_KEY"]},{id:"replicate",name:"Replicate",category:"\u63A8\u7406\u52A0\u901F",status:"cn-compatible",docs:"https://replicate.com/docs",envVarPrefix:"REPLICATE",requiredEnvVars:["REPLICATE_API_TOKEN"]},{id:"lepton",name:"Lepton AI",category:"\u63A8\u7406\u52A0\u901F",status:"cn-compatible",docs:"https://www.lepton.ai/docs",baseURL:"https://api.lepton.ai/v1",envVarPrefix:"LEPTON",requiredEnvVars:["LEPTON_API_KEY"]},{id:"anyscale",name:"Anyscale Endpoints",category:"\u63A8\u7406\u52A0\u901F",status:"cn-compatible",docs:"https://docs.endpoints.anyscale.com/",baseURL:"https://api.endpoints.anyscale.com/v1",envVarPrefix:"ANYSCALE",requiredEnvVars:["ANYSCALE_API_KEY"]},{id:"octoai",name:"OctoAI",category:"\u63A8\u7406\u52A0\u901F",status:"cn-compatible",docs:"https://octoai.cloud/docs",baseURL:"https://text.octoai.run/v1",envVarPrefix:"OCTOAI",requiredEnvVars:["OCTOAI_TOKEN"]},{id:"deepinfra",name:"DeepInfra",category:"\u63A8\u7406\u52A0\u901F",status:"cn-compatible",docs:"https://deepinfra.com/docs",baseURL:"https://api.deepinfra.com/v1/openai",envVarPrefix:"DEEPINFRA",requiredEnvVars:["DEEPINFRA_API_KEY"]},{id:"novita",name:"Novita AI",category:"\u63A8\u7406\u52A0\u901F",status:"cn-compatible",docs:"https://novita.ai/docs",baseURL:"https://api.novita.ai/v3/openai",envVarPrefix:"NOVITA",requiredEnvVars:["NOVITA_API_KEY"]},{id:"black-forest-labs",name:"Black Forest Labs (FLUX)",category:"\u591A\u6A21\u6001",status:"ai-sdk",docs:"https://docs.bfl.ml",envVarPrefix:"BFL",requiredEnvVars:["BFL_API_KEY"]},{id:"elevenlabs",name:"ElevenLabs (TTS)",category:"\u591A\u6A21\u6001",status:"ai-sdk",docs:"https://elevenlabs.io/docs",envVarPrefix:"ELEVENLABS",requiredEnvVars:["ELEVENLABS_API_KEY"]},{id:"runway",name:"Runway (Video)",category:"\u591A\u6A21\u6001",status:"pending",docs:"https://runwayml.com",envVarPrefix:"RUNWAY",requiredEnvVars:["RUNWAY_API_KEY"]},{id:"luma",name:"Luma (Video)",category:"\u591A\u6A21\u6001",status:"pending",docs:"https://lumalabs.ai",envVarPrefix:"LUMA",requiredEnvVars:["LUMA_API_KEY"]},{id:"midjourney",name:"Midjourney",category:"\u591A\u6A21\u6001",status:"pending",docs:"https://docs.midjourney.com",envVarPrefix:"MIDJOURNEY",requiredEnvVars:[]},{id:"ollama",name:"Ollama (\u672C\u5730)",category:"\u672C\u5730\u90E8\u7F72",status:"opencode",docs:"https://github.com/ollama/ollama",baseURL:"http://localhost:11434/v1",envVarPrefix:"OLLAMA",requiredEnvVars:["OLLAMA_BASE_URL"]},{id:"lmstudio",name:"LM Studio",category:"\u672C\u5730\u90E8\u7F72",status:"cn-compatible",docs:"https://lmstudio.ai/docs",baseURL:"http://localhost:1234/v1",envVarPrefix:"LMSTUDIO",requiredEnvVars:[]},{id:"vllm",name:"vLLM",category:"\u672C\u5730\u90E8\u7F72",status:"cn-compatible",docs:"https://docs.vllm.ai",baseURL:"http://localhost:8000/v1",envVarPrefix:"VLLM",requiredEnvVars:[]},{id:"tgi",name:"HF TGI",category:"\u672C\u5730\u90E8\u7F72",status:"cn-compatible",docs:"https://huggingface.co/docs/text-generation-inference",baseURL:"http://localhost:8080/v1",envVarPrefix:"TGI",requiredEnvVars:[]}],xt=G.reduce((e,t)=>(e[t.category]||(e[t.category]=[]),e[t.category].push(t),e),{});import Me from"fs";import Ke from"path";async function ce(e,t){let n=Ke.join(e,"nebutra.config.json");Me.writeFileSync(n,JSON.stringify(t,null,2)+`
39
+ `)}import Y from"fs";import x from"path";import{fileURLToPath as $e}from"url";var We=$e(import.meta.url),Be=x.dirname(We);async function pe(e,t){if(t==="none")return;let n=x.join(Be,"../../templates/deploy");t==="vercel"?await Y.promises.copyFile(x.join(n,"vercel.json"),x.join(e,"vercel.json")):t==="railway"?await Y.promises.copyFile(x.join(n,"railway.toml"),x.join(e,"railway.toml")):t==="cloudflare"?await Y.promises.copyFile(x.join(n,"wrangler.toml"),x.join(e,"wrangler.toml")):t==="selfhost"&&(await Y.promises.copyFile(x.join(n,"docker-compose.yml"),x.join(e,"docker-compose.yml")),await Y.promises.copyFile(x.join(n,"Dockerfile.web"),x.join(e,"Dockerfile.web")))}import _ from"fs";import O from"path";import{fileURLToPath as Ge}from"url";var ze=Ge(import.meta.url),le=O.dirname(ze),He=/\.(md|mdx|json|ts|tsx|js|mjs|cjs|yaml|yml)$/;function Je(e){let t=[O.join(le,"..","templates","docs",e),O.join(le,"..","..","templates","docs",e)];for(let n of t)if(_.existsSync(n))return n;return t[0]}async function de(e,t){if(t.framework==="none")return;let n=(t.framework==="fumadocs","fumadocs"),s=Je(n),r=O.join(e,"apps","docs");if(!_.existsSync(s))throw new Error(`Docs template not found: ${s}`);ue(s,r),me(r,{"{PRODUCT_NAME}":t.projectName})}function ue(e,t){_.mkdirSync(t,{recursive:!0});for(let n of _.readdirSync(e,{withFileTypes:!0})){let s=O.join(e,n.name),r=O.join(t,n.name);n.isDirectory()?ue(s,r):_.copyFileSync(s,r)}}function me(e,t){for(let n of _.readdirSync(e,{withFileTypes:!0})){let s=O.join(e,n.name);if(n.isDirectory())me(s,t);else if(He.test(n.name)){let r=_.readFileSync(s,"utf-8");for(let[i,l]of Object.entries(t))r=r.split(i).join(l);_.writeFileSync(s,r)}}}import z from"fs";import fe from"path";async function ye(e,t){let n=`# Automatically injected by create-sailor
40
+ DATABASE_URL="${String(t.databaseUrl)}"
41
+
42
+ NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY="${String(t.clerkPublishable)}"
43
+ CLERK_SECRET_KEY="${String(t.clerkSecret)}"
8
44
 
9
45
  NEXT_PUBLIC_SITE_URL="http://localhost:3000"
10
- `,y=w.join(o,".env"),r=w.join(o,".env.local");!x.existsSync(y)&&!x.existsSync(r)?x.writeFileSync(r,a):x.appendFileSync(r,`
11
- `+a)}import{execSync as I}from"child_process";import A from"fs";async function L(o){try{let c="https://github.com/Nebutra/Nebutra-Sailor/archive/refs/heads/main.tar.gz";A.mkdirSync(o,{recursive:!0}),I(`set -o pipefail; curl -sSL ${c} | tar -xz -C "${o}" --strip-components=1`,{stdio:"ignore",shell:"bash"})}catch{throw new Error("Failed to download the template repository. Please ensure you have internet access and curl/tar installed.")}}import S from"fs";import D from"path";async function j(o,c){let a=D.join(o,"package.json");if(S.existsSync(a)){let y=S.readFileSync(a,"utf8"),r=JSON.parse(y);r.name=c,r.version="0.1.0",S.writeFileSync(a,JSON.stringify(r,null,2)+`
12
- `)}}import t from"fs";import i from"path";function N(o,c){if(!t.existsSync(o))return;let a=t.readdirSync(o);for(let y of a){let r=i.join(o,y);t.statSync(r).isDirectory()?N(r,c):c(r)}}async function R(o,c){let a=i.join(o,"package.json");if(!t.existsSync(a))return;let y=JSON.parse(t.readFileSync(a,"utf8"));if(c.orm!=="prisma"){let r=i.join(o,"packages","db");if(t.existsSync(r)){let m=i.join(r,"prisma");t.existsSync(m)&&t.rmSync(m,{recursive:!0,force:!0});let g=i.join(r,"package.json");if(t.existsSync(g)){let l=JSON.parse(t.readFileSync(g,"utf8"));l.dependencies&&delete l.dependencies["@prisma/client"],l.devDependencies&&delete l.devDependencies.prisma,t.writeFileSync(g,JSON.stringify(l,null,2)+`
13
- `)}let p=i.join(r,"src","index.ts");t.existsSync(p)&&t.writeFileSync(p,`
46
+ `,s=fe.join(e,".env"),r=fe.join(e,".env.local");!z.existsSync(s)&&!z.existsSync(r)?z.writeFileSync(r,n):z.appendFileSync(r,`
47
+ `+n)}import{execSync as Xe}from"child_process";import w from"fs";import q from"path";import Ze from"ignore";var Qe={mirror:{repo:process.env.SAILOR_TEMPLATE_REPO||"Nebutra/Sailor-Template",ref:process.env.SAILOR_TEMPLATE_REF||"main",applyIgnore:!1},main:{repo:"Nebutra/Nebutra-Sailor",ref:"main",applyIgnore:!0}};function et(e,t){return`https://github.com/${e}/archive/refs/heads/${t}.tar.gz`}function ge(e,t,n){let s=w.readdirSync(t,{withFileTypes:!0});for(let r of s){if(r.name==="node_modules"||r.name===".git")continue;let i=q.join(t,r.name),l=q.relative(e,i).split(q.sep).join("/");r.isDirectory()?(n.push(l+"/"),ge(e,i,n)):n.push(l)}}function tt(e){let t=q.join(e,".templateignore");if(!w.existsSync(t))return;let n=w.readFileSync(t,"utf8"),s=Ze().add(n),r=[];ge(e,e,r);let i=r.map(a=>a.endsWith("/")?a.slice(0,-1):a),l=new Set(s.filter(i)),c=i.filter(a=>!l.has(a)).sort((a,d)=>d.split("/").length-a.split("/").length);for(let a of c){let d=q.join(e,a);try{w.existsSync(d)&&w.rmSync(d,{recursive:!0,force:!0})}catch{}}try{w.existsSync(t)&&w.rmSync(t,{force:!0})}catch{}}function nt(e,t){Xe(`set -o pipefail; curl -sSfL ${e} | tar -xz -C "${t}" --strip-components=1`,{stdio:"ignore",shell:"bash"})}async function ve(e){w.mkdirSync(e,{recursive:!0});let n=process.env.SAILOR_TEMPLATE_SOURCE==="main"?["main"]:["mirror","main"],s;for(let r of n){let i=Qe[r],l=et(i.repo,i.ref);try{if(nt(l,e),i.applyIgnore)try{tt(e)}catch(c){throw new Error(`Template cloned, but .templateignore processing failed: ${c instanceof Error?c.message:String(c)}`)}return}catch(c){s=c instanceof Error?c:new Error(String(c));try{let a=w.readdirSync(e);for(let d of a)w.rmSync(q.join(e,d),{recursive:!0,force:!0})}catch{}}}throw new Error(`Failed to download the template from any source. Last error: ${s?.message??"unknown"}
48
+
49
+ Ensure you have internet access and curl/tar installed.
50
+ You can override the source with SAILOR_TEMPLATE_REPO / SAILOR_TEMPLATE_REF env vars.`)}import X from"fs";import st from"path";async function he(e,t){let n=st.join(e,"package.json");if(X.existsSync(n)){let s=X.readFileSync(n,"utf8"),r=JSON.parse(s);r.name=t,r.version="0.1.0",X.writeFileSync(n,JSON.stringify(r,null,2)+`
51
+ `)}}import R from"fs";import U from"path";var Pe=new Set(["siliconflow","volcengine-ark","bailian","moonshot"]),Z=/@sailor:([^\s*]+)/;function rt(e,t){let{providerIds:n,customEndpoint:s}=t;if(e.startsWith("provider:")){let r=e.slice(9);return{marker:e,keep:n.includes(r)}}if(e==="cn-compatible"){let r=n.some(i=>Pe.has(i));return{marker:e,keep:r||!!s}}if(e.startsWith("cn-instance:")){let r=e.slice(12);return{marker:e,keep:n.includes(r)}}return e==="custom-endpoint"?{marker:e,keep:!!s}:e==="registry-entries"?{marker:e,keep:!0}:{marker:e,keep:!0}}function Ee(e){return e.replace(/\s*\/\/\s*@sailor:[^\n]*$/,"")}function be(e){return e.replace(/\s*#\s*@sailor:[^\n]*$/,"")}function xe(e,t){let n=e.split(`
52
+ `),s=[],r=e.includes("#"),i=0;for(;i<n.length;){let l=n[i],c=l.match(Z);if(!c){s.push(l),i++;continue}let{marker:a,keep:d}=rt(c[1],t);if(a==="registry-entries"){s.push(r?be(l):Ee(l)),i++;continue}if(d){let m=r?be(l):Ee(l);m.trim().length>0&&s.push(m);let E=i+1;for(;E<n.length&&n[E].trim()!==""&&!Z.test(n[E]);)s.push(n[E]),E++;E<n.length&&n[E].trim()===""&&(s.push(n[E]),E++),i=E}else{let m=i+1;for(;m<n.length&&n[m].trim()!==""&&!Z.test(n[m]);)m++;m<n.length&&n[m].trim()===""&&m++,i=m}}return ot(s.join(`
53
+ `),t)}function ot(e,t){let{providerIds:n,customEndpoint:s}=t,r=new Set;for(let d of n)["openai","anthropic","google","mistral","xai"].includes(d)&&r.add(d),r.add(d.replace(/-/g,"_")),r.add(d);s&&r.add("custom");let i=/createProviderRegistry\(\{([\s\S]*?)\}\)/,l=e.match(i);if(!l)return e;let a=l[1].split(`
54
+ `).filter(d=>{let m=d.trim();if(m===""||m.startsWith("//"))return!0;let E=m.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*,?$/);return E?r.has(E[1]):!0}).join(`
55
+ `);return e.replace(i,`createProviderRegistry({${a}})`)}function at(e,t){let n=U.join(t,"registry.ts.template"),s=R.readFileSync(n,"utf-8");return xe(s,e)}function it(e,t){let n=U.join(t,".env.example.template"),s=R.readFileSync(n,"utf-8");return xe(s,e)}async function Se(e,t,n){let s=at(t,n),r=it(t,n),i=U.join(e,"packages/ai-sdk/src");await R.promises.mkdir(i,{recursive:!0}),await R.promises.writeFile(U.join(i,"registry.ts"),s),await R.promises.appendFile(U.join(e,".env.example"),`
56
+ `+r);let l={ai:"latest"},{providerIds:c,customEndpoint:a}=t;(c.includes("openai")||a||c.some(m=>Pe.has(m)))&&(l["@ai-sdk/openai"]="latest",l["@ai-sdk/openai-compatible"]="latest"),c.includes("anthropic")&&(l["@ai-sdk/anthropic"]="latest"),c.includes("google")&&(l["@ai-sdk/google"]="latest"),c.includes("mistral")&&(l["@ai-sdk/mistral"]="latest"),c.includes("xai")&&(l["@ai-sdk/openai"]="latest");let d=U.join(e,"package.json");if(R.existsSync(d)){let m=JSON.parse(await R.promises.readFile(d,"utf-8"));m.dependencies||(m.dependencies={});for(let[E,N]of Object.entries(l))m.dependencies[E]=N;await R.promises.writeFile(d,JSON.stringify(m,null,2)+`
57
+ `)}}import p from"fs";import v from"path";function Ae(e,t){if(!p.existsSync(e))return;let n=p.readdirSync(e);for(let s of n){let r=v.join(e,s);p.statSync(r).isDirectory()?Ae(r,t):t(r)}}async function we(e,t){let n=v.join(e,"package.json");if(!p.existsSync(n))return;let s=JSON.parse(p.readFileSync(n,"utf8"));if(t.orm!=="prisma"){let r=v.join(e,"packages","db");if(p.existsSync(r)){let i=v.join(r,"prisma");p.existsSync(i)&&p.rmSync(i,{recursive:!0,force:!0});let l=v.join(r,"package.json");if(p.existsSync(l)){let a=JSON.parse(p.readFileSync(l,"utf8"));a.dependencies&&delete a.dependencies["@prisma/client"],a.devDependencies&&delete a.devDependencies.prisma,p.writeFileSync(l,JSON.stringify(a,null,2)+`
58
+ `)}let c=v.join(r,"src","index.ts");p.existsSync(c)&&p.writeFileSync(c,`
14
59
  export const prisma = {} as any;
15
60
  export const db = {} as any;
16
61
  export type PrismaClient = any;
@@ -56,7 +101,7 @@ export type UsageAggregate = any;
56
101
  export type UsageLedgerEntry = any;
57
102
  export type UsageType = any;
58
103
  export type UsageLedgerSource = any;
59
- `)}y.scripts?.postinstall&&(y.scripts.postinstall=y.scripts.postinstall.replace(/prisma generate/g,'echo "ORM Disabled"'))}if(!c.i18n){let r=i.join(o,"apps");if(t.existsSync(r)){let m=t.readdirSync(r);for(let g of m){let p=i.join(r,g);if(!t.statSync(p).isDirectory())continue;let l=i.join(p,"src","app");if(!t.existsSync(l))continue;["[lang]","[locale]"].forEach(s=>{let e=i.join(l,s);if(t.existsSync(e)){let u=t.readdirSync(e);for(let v of u)t.renameSync(i.join(e,v),i.join(l,v));t.rmdirSync(e)}}),["src/middleware.ts","middleware.ts"].forEach(s=>{let e=i.join(p,s);t.existsSync(e)&&t.rmSync(e)});let f=i.join(p,"src","lib");t.existsSync(f)||t.mkdirSync(f,{recursive:!0});let F=i.join(f,"mock-i18n.ts");t.writeFileSync(F,`
104
+ `)}s.scripts?.postinstall&&(s.scripts.postinstall=s.scripts.postinstall.replace(/prisma generate/g,'echo "ORM Disabled"'))}if(!t.i18n){let r=v.join(e,"apps");if(p.existsSync(r)){let i=p.readdirSync(r);for(let l of i){let c=v.join(r,l);if(!p.statSync(c).isDirectory())continue;let a=v.join(c,"src","app");if(!p.existsSync(a))continue;["[lang]","[locale]"].forEach(y=>{let o=v.join(a,y);if(p.existsSync(o)){let b=p.readdirSync(o);for(let L of b)p.renameSync(v.join(o,L),v.join(a,L));p.rmdirSync(o)}}),["src/middleware.ts","middleware.ts"].forEach(y=>{let o=v.join(c,y);p.existsSync(o)&&p.rmSync(o)});let d=v.join(c,"src","lib");p.existsSync(d)||p.mkdirSync(d,{recursive:!0});let m=v.join(d,"mock-i18n.ts");p.writeFileSync(m,`
60
105
  import React from 'react';
61
106
  export function useTranslations(namespace?: string): any { return (key: string) => key; }
62
107
  export function useLocale() { return 'en'; }
@@ -74,7 +119,7 @@ export const useRouter = () => ({ push: (...args: any[]) => {}, replace: (...arg
74
119
  export default function createMiddleware(...args: any[]) { return (req: any) => req; }
75
120
  export function createNavigation(...args: any[]) { return { Link, redirect, usePathname, useRouter, getPathname: () => '/' }; }
76
121
  export function defineRouting<T>(config: T): T { return config; }
77
- `);let E=i.join(f,"mock-i18n.d.ts");t.writeFileSync(E,`declare module '@/lib/mock-i18n' {
122
+ `);let N=v.join(d,"mock-i18n.d.ts");p.writeFileSync(N,`declare module '@/lib/mock-i18n' {
78
123
  export function useTranslations(namespace?: string): any;
79
124
  export function useLocale(): string;
80
125
  export const NextIntlClientProvider: React.FC<any>;
@@ -92,14 +137,27 @@ export function defineRouting<T>(config: T): T { return config; }
92
137
  export function createNavigation(...args: any[]): any;
93
138
  export function defineRouting<T>(config: T): T;
94
139
  }
95
- `),t.writeFileSync(i.join(f,"mock-i18n-index.ts"),`
140
+ `),p.writeFileSync(v.join(d,"mock-i18n-index.ts"),`
96
141
  export * from './mock-i18n';
97
142
  export { default } from './mock-i18n';
98
- `);let b=i.join(p,"tsconfig.json");if(t.existsSync(b))try{let s=t.readFileSync(b,"utf8");if(s.includes('"paths": {')){let e=s.replace(/"paths":\s*\{/,`"paths": {
143
+ `);let C=v.join(c,"tsconfig.json");if(p.existsSync(C))try{let y=p.readFileSync(C,"utf8");if(y.includes('"paths": {')){let o=y.replace(/"paths":\s*\{/,`"paths": {
99
144
  "next-intl": ["./src/lib/mock-i18n"],
100
- "next-intl/*": ["./src/lib/mock-i18n"],`);t.writeFileSync(b,e)}}catch{}let k=i.join(p,"src");t.existsSync(k)&&N(k,s=>{if(!s.endsWith(".ts")&&!s.endsWith(".tsx"))return;let e=t.readFileSync(s,"utf8"),u=!1;s.endsWith("src/app/layout.tsx")&&e.includes("../providers")&&(e=e.replace(/from\s+['"]\.\.\/providers['"]/g,"from './providers'"),u=!0),e=e.replace(/params:\s*Promise<\{[^}]*(lang|locale)[^}]*\}>/g,"params: any"),e=e.replace(/\(\{\s*params\s*\}\s*:\s*\{\s*params:\s*any\s*\}\)/g,"({ params }: any)"),e.match(/locale\s*:\s*any/)&&(e=e.replace(/locale\s*:\s*any/g,"locale: string"),u=!0),e.match(/lang\s*:\s*any/)&&(e=e.replace(/lang\s*:\s*any/g,"lang: string"),u=!0),e.match(/requestLocale\s*:\s*any/)&&(e=e.replace(/requestLocale\s*:\s*any/g,"requestLocale: string"),u=!0),e.match(/\(locale\s*=\s*(.*)\)/)&&(e=e.replace(/\(locale\s*=\s*(.*)\)/g,"() "),u=!0),e.match(/export\s+default\s+async\s+function\s+LangLayout/)&&(e=e.replace(/export\s+default\s+async\s+function\s+LangLayout\s*\(\s*\{\s*children\s*,\s*params\s*\}\s*:\s*LangLayoutProps\s*\)/g,"export default async function LangLayout({ children }: { children: React.ReactNode })"),u=!0),(s.endsWith("src/app/layout.tsx")||s.endsWith("src/app/sitemap.ts")||s.endsWith("src/app/robots.ts")||s.endsWith("src/components/ui/locale-switcher.tsx")||s.endsWith("src/app/(marketing)/blog/[slug]/page.tsx"))&&(e=e.replace(/const\s+\{\s*lang\s*\}\s*=\s*(await\s+)?params;?/g,'const lang = "en";'),e=e.replace(/const\s+\{\s*locale\s*\}\s*=\s*(await\s+)?params;?/g,'const locale = "en";'),e=e.replace(/const\s+locale\s*=\s*lang\s+as\s+Locale;?/g,'const locale = "en";'),e=e.replace(/\(l\)\s*=>/g,"(l: any) =>"),u=!0),s.endsWith("src/i18n/request.ts")&&(e=e.replace(/async\s*\(\{\s*requestLocale\s*\}\)/g,"async ({ requestLocale }: any)"),u=!0),(e.match(/locale\s*:/)||e.match(/lang\s*:/))&&(u=!0),u&&t.writeFileSync(s,e)});let h=i.join(p,"package.json");if(t.existsSync(h)){let s=JSON.parse(t.readFileSync(h,"utf8"));s.dependencies&&delete s.dependencies["next-intl"],t.writeFileSync(h,JSON.stringify(s,null,2)+`
101
- `)}}}}if(c.applicationType!=="full"){let r=[];c.applicationType==="saas"&&r.push("contracts");for(let m of r){let g=i.join(o,"packages",m);t.existsSync(g)&&t.rmSync(g,{recursive:!0,force:!0})}}t.writeFileSync(a,JSON.stringify(y,null,2)+`
102
- `)}async function W(){let o=new U;o.name("create-sailor").description("CLI to bootstrap Nebutra-Sailor scaffolding").version("0.1.0").argument("[dir]","Directory to initialize the project in"),o.parse(process.argv);let c=o.args;n.intro(d.bgCyan(d.black(" create-sailor ")));let a=c[0];a||(a=(await n.group({name:()=>n.text({message:"Where should we create your project?",placeholder:"./my-saas-app",defaultValue:"./my-saas-app",validate:l=>{if(l.length===0)return"Please enter a path."}})},{onCancel:()=>{n.cancel("Operation cancelled."),process.exit(0)}})).name);let y=T.basename(T.resolve(a)),m=await n.group({applicationType:()=>n.select({message:"What kind of application are you building?",options:[{value:"saas",label:"Standard B2B/B2C SaaS",hint:"Optimized, UI+DB"},{value:"full",label:"Full Monorepo",hint:"Includes Python AI workflows & Web3"},{value:"ecommerce",label:"E-Commerce Suite"},{value:"web3",label:"Web3 DApp"}],initialValue:"saas"}),orm:()=>n.select({message:"Which ORM would you like to use?",options:[{value:"prisma",label:"Prisma",hint:"Recommended"},{value:"drizzle",label:"Drizzle ORM"},{value:"none",label:"None (I will configure my own)"}],initialValue:"prisma"}),database:()=>n.select({message:"Which database provider?",options:[{value:"postgresql",label:"PostgreSQL (Supabase, Vercel, Neon, etc)"},{value:"mysql",label:"MySQL / PlanetScale"},{value:"sqlite",label:"SQLite / Turso"},{value:"none",label:"None"}],initialValue:"postgresql"}),payment:()=>n.select({message:"Which payment provider?",options:[{value:"stripe",label:"Stripe"},{value:"lemonsqueezy",label:"Lemon Squeezy"},{value:"none",label:"None"}],initialValue:"stripe"}),aiProvider:()=>n.select({message:"Default AI Provider?",options:[{value:"openai",label:"OpenAI"},{value:"anthropic",label:"Anthropic"},{value:"none",label:"None"}],initialValue:"openai"}),i18n:()=>n.confirm({message:"Enable Internationalization (i18n)?",initialValue:!0})},{onCancel:()=>{n.cancel("Operation cancelled."),process.exit(0)}}),g=await n.group({databaseUrl:()=>n.text({message:"Database URL (leave empty to skip):",placeholder:"postgresql://postgres:postgres@localhost:5432/nebutra",defaultValue:"postgresql://postgres:postgres@localhost:5432/nebutra"}),clerkPublishable:()=>n.text({message:"Clerk Publishable Key (leave empty to skip):",placeholder:"pk_test_...",defaultValue:""}),clerkSecret:()=>n.text({message:"Clerk Secret Key (leave empty to skip):",placeholder:"sk_test_...",defaultValue:""})},{onCancel:()=>{n.cancel("Operation cancelled."),process.exit(0)}});n.spinner().start(`Cloning Nebutra-Sailor template into ${a}...`);try{await L(a),await j(a,y),await P(a,m),await R(a,m),await C(a,g),n.spinner().stop(d.green(`Project ${y} successfully initialized!`)),n.outro(d.cyan(`Next steps:
103
- cd ${a}
104
- pnpm install
105
- pnpm dev`)),process.exit(0)}catch(p){let l=p instanceof Error?p.message:String(p);n.spinner().stop(d.red(`Failed to bootstrap project: ${l}`)),process.exit(1)}}W().catch(console.error);
145
+ "next-intl/*": ["./src/lib/mock-i18n"],`);p.writeFileSync(C,o)}}catch{}let F=v.join(c,"src");p.existsSync(F)&&Ae(F,y=>{if(!y.endsWith(".ts")&&!y.endsWith(".tsx"))return;let o=p.readFileSync(y,"utf8"),b=!1;y.endsWith("src/app/layout.tsx")&&o.includes("../providers")&&(o=o.replace(/from\s+['"]\.\.\/providers['"]/g,"from './providers'"),b=!0),o=o.replace(/params:\s*Promise<\{[^}]*(lang|locale)[^}]*\}>/g,"params: any"),o=o.replace(/\(\{\s*params\s*\}\s*:\s*\{\s*params:\s*any\s*\}\)/g,"({ params }: any)"),o.match(/locale\s*:\s*any/)&&(o=o.replace(/locale\s*:\s*any/g,"locale: string"),b=!0),o.match(/lang\s*:\s*any/)&&(o=o.replace(/lang\s*:\s*any/g,"lang: string"),b=!0),o.match(/requestLocale\s*:\s*any/)&&(o=o.replace(/requestLocale\s*:\s*any/g,"requestLocale: string"),b=!0),o.match(/\(locale\s*=\s*(.*)\)/)&&(o=o.replace(/\(locale\s*=\s*(.*)\)/g,"() "),b=!0),o.match(/export\s+default\s+async\s+function\s+LangLayout/)&&(o=o.replace(/export\s+default\s+async\s+function\s+LangLayout\s*\(\s*\{\s*children\s*,\s*params\s*\}\s*:\s*LangLayoutProps\s*\)/g,"export default async function LangLayout({ children }: { children: React.ReactNode })"),b=!0),(y.endsWith("src/app/layout.tsx")||y.endsWith("src/app/sitemap.ts")||y.endsWith("src/app/robots.ts")||y.endsWith("src/components/ui/locale-switcher.tsx")||y.endsWith("src/app/(marketing)/blog/[slug]/page.tsx"))&&(o=o.replace(/const\s+\{\s*lang\s*\}\s*=\s*(await\s+)?params;?/g,'const lang = "en";'),o=o.replace(/const\s+\{\s*locale\s*\}\s*=\s*(await\s+)?params;?/g,'const locale = "en";'),o=o.replace(/const\s+locale\s*=\s*lang\s+as\s+Locale;?/g,'const locale = "en";'),o=o.replace(/\(l\)\s*=>/g,"(l: any) =>"),b=!0),y.endsWith("src/i18n/request.ts")&&(o=o.replace(/async\s*\(\{\s*requestLocale\s*\}\)/g,"async ({ requestLocale }: any)"),b=!0),(o.match(/locale\s*:/)||o.match(/lang\s*:/))&&(b=!0),b&&p.writeFileSync(y,o)});let T=v.join(c,"package.json");if(p.existsSync(T)){let y=JSON.parse(p.readFileSync(T,"utf8"));y.dependencies&&delete y.dependencies["next-intl"],p.writeFileSync(T,JSON.stringify(y,null,2)+`
146
+ `)}}}}p.writeFileSync(n,JSON.stringify(s,null,2)+`
147
+ `)}var ee="1.0.0",Ie="create-sailor";function g(e,t){e&&process.stdout.write(JSON.stringify(t)+`
148
+ `)}function dt(){let e=process.env.npm_config_user_agent??"";return e.startsWith("pnpm")?"pnpm":e.startsWith("yarn")?"yarn":e.startsWith("bun")?"bun":"npm"}function ke(e){switch(e){case"postgres":case"postgresql":return"postgresql";case"mysql":return"mysql";case"sqlite":return"sqlite";case"none":return"none";default:return"postgresql"}}function _e(e){return e==="drizzle"?"drizzle":e==="none"?"none":"prisma"}function Re(e){return e==="lemon"||e==="lemonsqueezy"?"lemonsqueezy":e==="none"?"none":"stripe"}function Le(e){if(!e)return["openai"];let t=e.split(",").map(n=>n.trim().toLowerCase());return t.includes("none")?[]:t}var Oe={mintlify:"Mintlify",docusaurus:"Docusaurus",nextra:"Nextra",vitepress:"VitePress"};function Ne(e,t){if(!e)return"fumadocs";let n=e.toLowerCase();if(n==="fumadocs"||n==="none")return n;if(n in Oe){let s=Oe[n];return t?g(!0,{event:"notice",kind:"docs-fallback",requested:n,effective:"fumadocs"}):process.stdout.write(I.yellow(`\u26A0 ${s} support is coming in v1.2. Falling back to fumadocs.
149
+ `)+I.dim(` Track progress: https://github.com/Nebutra/Nebutra-Sailor/issues
150
+ `)),"fumadocs"}return"fumadocs"}function Ce(e){switch(e){case"vercel":return"vercel";case"railway":return"railway";case"cloudflare":return"cloudflare";case"selfhost":return"selfhost";case"none":return"none";default:return"vercel"}}async function ut(){let e=new pt;e.name(Ie).description("Nebutra-Sailor \u2014 AI-Native SaaS template").version(ee,"-v, --version").helpOption(!1).argument("[name]","project directory",void 0).option("-p, --pm <id>","npm | pnpm | yarn | bun").option("--orm <id>","prisma | drizzle | none").option("--db <id>","postgres | mysql | sqlite | none").option("--auth <id>","clerk | betterauth | none").option("--payment <id>","stripe | lemon | wechat | alipay | none").option("--ai <ids>","comma-separated provider ids").option("--deploy <target>","vercel | railway | cloudflare | selfhost").option("--docs <id>","fumadocs | mintlify | docusaurus | nextra | vitepress | none").option("--i18n","enable i18n").option("--no-i18n","disable i18n").option("--no-install","skip package install").option("--no-git","skip git init").option("-y, --yes","accept all defaults (non-interactive)").option("--dry-run","preview actions without writing files").option("--json","machine-readable output").option("--no-color","disable color output").option("-h, --help","show help"),e.parse(process.argv);let t=e.opts(),[n]=e.args;t.help&&(oe(),process.exit(0));let s=!!t.json,r=!!t.dryRun,i=!!t.yes,l=i||!process.stdin.isTTY;s||se(),g(s,{event:"start",version:ee});let c=n??(i?"./my-saas-app":void 0),a;if(c)a=c;else if(l)a="./my-saas-app";else{let u=await h.group({name:()=>h.text({message:"Where should we create your project?",placeholder:"./my-saas-app",defaultValue:"./my-saas-app",validate:f=>{if(f.length===0)return"Please enter a path."}})},{onCancel:()=>{process.stdout.write(I.red(`\u2718 Cancelled
151
+ `)),process.exit(130)}});a=String(u.name)}let d=Q.basename(Q.resolve(a)),m=t.pm??dt(),E=!!t.orm,N=!!t.db,C=!!t.payment,F=!!t.ai,T=!!t.deploy,y=!!t.docs,o=t.i18n!==void 0,b,L,M,V,H,j,S,K;if(l)b=_e(t.orm),L=ke(t.db),M=Re(t.payment),V=Le(t.ai),j=Ce(t.deploy),S=Ne(t.docs,s),K=o?!!t.i18n:!0;else{let u={};if(E||(u.orm=()=>h.select({message:"Which ORM?",options:[{value:"prisma",label:"Prisma"},{value:"drizzle",label:"Drizzle"},{value:"none",label:"None"}],initialValue:"prisma"})),N||(u.database=()=>h.select({message:"Database?",options:[{value:"postgresql",label:"PostgreSQL"},{value:"mysql",label:"MySQL"},{value:"sqlite",label:"SQLite"},{value:"none",label:"None"}],initialValue:"postgresql"})),C||(u.payment=()=>h.select({message:"Payment?",options:[{value:"stripe",label:"Stripe"},{value:"lemonsqueezy",label:"Lemon Squeezy"},{value:"none",label:"None"}],initialValue:"stripe"})),!F){let A=Array.from(new Set(G.map(P=>P.category)));u.aiCategories=()=>h.multiselect({message:"Which AI Provider categories do you want to explore? (Select multiple)",options:A.map(P=>({value:P,label:P})),initialValues:["\u76F4\u63A5\u5B9E\u9A8C\u5BA4","\u7EDF\u4E00\u7F51\u5173"]}),u.aiProviders=({results:P})=>{let J=P.aiCategories||[],te=G.filter(D=>J.includes(D.category)).map(D=>({value:D.id,label:`[${D.category}] ${D.name}`}));return te.push({value:"custom",label:"[\u81EA\u5B9A\u4E49] Custom OpenAI-compatible endpoint"}),h.multiselect({message:"Select AI Providers (Space to select, Enter to submit)",options:te,initialValues:["openai","anthropic"],required:!1})},u.enableCustom=({results:P})=>P.aiProviders?.includes("custom")?h.confirm({message:"Configure custom OpenAI-compatible endpoint?",initialValue:!0}):Promise.resolve(!1),u.customAiName=({results:P})=>P.enableCustom?h.text({message:"Custom endpoint name (e.g. proxy, local):",defaultValue:"custom",placeholder:"custom"}):Promise.resolve(void 0),u.customAiBaseUrl=({results:P})=>P.enableCustom?h.text({message:"Custom endpoint base URL (e.g. https://api.proxy.com/v1):",validate:J=>{if(J.length===0)return"Base URL is required."}}):Promise.resolve(void 0),u.customAiApiKeyEnv=({results:P})=>P.enableCustom?h.text({message:"Environment variable name for the API Key:",defaultValue:"CUSTOM_AI_API_KEY",placeholder:"CUSTOM_AI_API_KEY"}):Promise.resolve(void 0)}T||(u.deployTarget=()=>h.select({message:"Where will you deploy?",options:[{value:"vercel",label:"Vercel"},{value:"railway",label:"Railway"},{value:"cloudflare",label:"Cloudflare Workers"},{value:"selfhost",label:"Self-host / Docker"},{value:"none",label:"None for now"}],initialValue:"vercel"})),y||(u.docs=()=>h.select({message:"What documentation framework? (affects `apps/docs`)",options:[{value:"fumadocs",label:"Fumadocs \u2014 Next.js monorepo native (Recommended)"},{value:"mintlify",label:"Mintlify \u2014 managed docs SaaS (Coming in v1.2)"},{value:"docusaurus",label:"Docusaurus \u2014 Meta's framework (Coming in v1.2)"},{value:"nextra",label:"Nextra \u2014 Next.js minimal (Coming in v1.2)"},{value:"vitepress",label:"VitePress \u2014 Vue ecosystem, separate repo (Experimental)"},{value:"none",label:"None \u2014 skip docs"}],initialValue:"fumadocs"})),o||(u.i18n=()=>h.confirm({message:"Enable i18n?",initialValue:!0}));let f=Object.keys(u).length>0?await h.group(u,{onCancel:()=>{process.stdout.write(I.red(`\u2718 Cancelled
152
+ `)),process.exit(130)}}):{};b=E?_e(t.orm):f.orm,L=N?ke(t.db):f.database,M=C?Re(t.payment):f.payment,V=F?Le(t.ai):f.aiProviders?.filter(A=>A!=="custom")??[],f.enableCustom&&(H={name:f.customAiName,baseURL:f.customAiBaseUrl,apiKeyEnvName:f.customAiApiKeyEnv}),j=T?Ce(t.deploy):f.deployTarget,S=Ne(y?t.docs:f.docs,s),K=o?!!t.i18n:!!f.i18n}let $={orm:b,database:L,payment:M,aiProviders:V,customAiEndpoint:H,deployTarget:j,docs:S,i18n:K},W=[["Project name",d],["ORM",b],["Database",L],["Payment",M],["AI",Array.isArray(V)?V.join(", ")+(H?", custom":""):String(V)],["Deploy Target",j],["Docs Framework",S]];s?W.forEach(([u,f],A)=>{g(!0,{event:"step",step:u,value:f,index:A+1,total:W.length})}):W.forEach(([u,f],A)=>{ie({index:A+1,total:W.length,label:u,value:f})});let Te={databaseUrl:"postgresql://postgres:postgres@localhost:5432/nebutra",clerkPublishable:"",clerkSecret:""},Ve=Date.now();if(r){let u=[`clone template \u2192 ${a}`,"write nebutra.config.json",`prune template (orm=${b}, i18n=${K})`,...S!=="none"?[`docs \u2192 scaffold apps/docs (${S==="fumadocs"?"fumadocs":`${S} \u2192 fumadocs fallback`})`]:[],"inject .env.local",t.install===!1?"skip install":`run ${m} install`,t.git===!1?"skip git init":"run git init"];if(s)u.forEach(f=>g(!0,{event:"plan",action:f})),g(!0,{event:"done",dryRun:!0});else{process.stdout.write(`
153
+ `+I.bold(`Dry run \u2014 planned actions:
154
+ `));for(let f of u)process.stdout.write(` \u2022 ${f}
155
+ `);process.stdout.write(I.dim(`
156
+ No files were written.
157
+ `))}process.exit(0)}let je=()=>{process.stdout.write(`
158
+ `+I.red(`\u2718 Cancelled
159
+ `));try{ct.existsSync(a)&&process.stdout.write(I.dim(` Partial directory left at ${a} \u2014 remove manually if needed.
160
+ `))}catch{}process.exit(130)};process.on("SIGINT",je);try{g(s,{event:"step",step:"clone",status:"start"}),await ve(a),g(s,{event:"step",step:"clone",status:"ok"}),g(s,{event:"step",step:"package",status:"start"}),await he(a,d),g(s,{event:"step",step:"package",status:"ok"}),g(s,{event:"step",step:"config",status:"start"}),await ce(a,$),g(s,{event:"step",step:"config",status:"ok"}),g(s,{event:"step",step:"prune",status:"start"}),await we(a,$),g(s,{event:"step",step:"prune",status:"ok"}),g(s,{event:"step",step:"ai-providers",status:"start"});let u={providerIds:$.aiProviders,customEndpoint:$.customAiEndpoint},f=Q.join(a,"packages/ai-providers/templates");await Se(a,u,f),g(s,{event:"step",step:"ai-providers",status:"ok"}),g(s,{event:"step",step:"docs",status:"start"}),S!=="none"?(await de(a,{framework:S,projectName:d}),g(s,{event:"step",step:"docs",framework:"fumadocs",requested:S,status:"ok"})):g(s,{event:"step",step:"docs",status:"skip"}),g(s,{event:"step",step:"deploy-target",status:"start"}),j!=="none"&&await pe(a,j),g(s,{event:"step",step:"deploy-target",status:"ok"}),g(s,{event:"step",step:"env",status:"start"}),await ye(a,Te),g(s,{event:"step",step:"env",status:"ok"});let A=Math.max(1,Math.round((Date.now()-Ve)/1e3));s?g(!0,{event:"done",status:"ok",elapsedSec:A,targetDir:a}):re({elapsedSec:A,targetDir:a,skippedInstall:t.install===!1});try{lt({pkg:{name:Ie,version:ee},updateCheckInterval:1e3*60*60*24}).notify({defer:!1,isGlobal:!0})}catch{}process.exit(0)}catch(u){let f=u instanceof Error?u.message:String(u);s?g(!0,{event:"error",message:f}):process.stdout.write(I.red(`
161
+ \u2718 Failed: ${f}
162
+ `)),process.exit(1)}}ut().catch(e=>{process.stderr.write(String(e)+`
163
+ `),process.exit(1)});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-sailor",
3
- "version": "0.1.0",
3
+ "version": "1.1.0",
4
4
  "description": "AI-Native SaaS unicorn template. Bootstrap a production-ready Next.js + Hono + Prisma monorepo with Multi-Agent, white-labeling, billing, auth, and 50+ engineering primitives in seconds.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -20,11 +20,16 @@
20
20
  },
21
21
  "dependencies": {
22
22
  "@clack/prompts": "^0.7.0",
23
+ "cfonts": "^3.3.0",
23
24
  "commander": "^12.1.0",
24
- "picocolors": "^1.1.0"
25
+ "gradient-string": "^3.0.0",
26
+ "ignore": "^5.3.2",
27
+ "picocolors": "^1.1.0",
28
+ "update-notifier": "^7.3.1"
25
29
  },
26
30
  "devDependencies": {
27
31
  "@types/node": "^20.0.0",
32
+ "@types/update-notifier": "^6.0.8",
28
33
  "tsup": "^8.0.0",
29
34
  "typescript": "^5.4.0"
30
35
  },