create-sailor 0.1.0 → 1.0.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 +66 -19
  2. package/package.json +7 -2
package/dist/index.js CHANGED
@@ -1,16 +1,52 @@
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 we from"fs";import ee from"path";import*as b from"@clack/prompts";import{Command as ke}from"commander";import w from"picocolors";import Pe from"update-notifier";import le from"cfonts";import ue from"picocolors";var B="1.0.0";function U(){if(process.env.NO_COLOR||!process.stdout.isTTY){process.stdout.write([`Sailor v${B}`,"AI-Native SaaS Unicorn Template","by Nebutra \xB7 https://nebutra.com","",""].join(`
3
+ `));return}le.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${B} \xB7 nebutra.com
5
4
 
6
- NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY="${String(c.clerkPublishable)}"
7
- CLERK_SECRET_KEY="${String(c.clerkSecret)}"
5
+ `))}import x from"picocolors";function me(){return!process.env.NO_COLOR&&!!process.stdout.isTTY}function W(t){let e=me(),i=e?"\u2693":"-",s=e?"\u25B8":"->",r=e?"\u2605":"*",y=e?"\u{1F4E7}":"*",u=["",` ${e?x.bold(`${i} Done in ${t.elapsedSec}s \xB7 ${t.targetDir}/`):`${i} Done in ${t.elapsedSec}s \xB7 ${t.targetDir}/`}`,"",` ${e?x.bold("Next:"):"Next:"}`,` ${s} cd ${t.targetDir}`];t.skippedInstall&&u.push(` ${s} pnpm install (if --no-install was set)`),u.push(` ${s} pnpm dev ${e?x.dim("\u2192 http://localhost:3000"):"-> http://localhost:3000"}`,"",` ${e?x.bold("Customize:"):"Customize:"}`,` ${s} pnpm sailor brand init ${e?x.dim("\u2192 your colors, logo, domain"):"-> your colors, logo, domain"}`,` ${s} pnpm sailor preset apply ${e?x.dim("\u2192 toggle features"):"-> toggle features"}`,` ${s} pnpm sailor add <feat> ${e?x.dim("\u2192 add queue/search/cache/..."):"-> add queue/search/cache/..."}`,"",` ${e?x.bold("Ship faster:"):"Ship faster:"}`,` ${r} Star us https://github.com/nebutra/nebutra-sailor`,` ${y} Free license https://nebutra.com/get-license`,""),process.stdout.write(u.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
+ -t, --template <id> saas | full | ecommerce | web3 | ai-saas
14
+ -p, --pm <id> npm | pnpm | yarn | bun (auto-detected)
15
+ --orm <id> prisma | drizzle | none
16
+ --db <id> postgres | mysql | sqlite | none
17
+ --auth <id> clerk | betterauth | none
18
+ --payment <id> stripe | lemon | wechat | alipay | none
19
+ --ai <ids> comma-separated provider ids (e.g. openai,anthropic)
20
+ --deploy <target> vercel | railway | cloudflare | selfhost
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 --template=ai-saas --auth=clerk
36
+ $ npm create sailor@latest --dry-run
37
+ `;function _(){process.stdout.write(de)}import R from"picocolors";function J(){return!process.env.NO_COLOR&&!!process.stdout.isTTY}function ye(t){let e=J()?"\u25B8":"->",i=`[${t.index}/${t.total}]`,s=t.label.padEnd(18," ");return J()?`${R.dim(i)} ${R.bold(s)} ${R.dim(e)} ${R.cyan(t.value)}`:`${i} ${s} ${e} ${t.value}`}function M(t){process.stdout.write(ye(t)+`
38
+ `)}import fe from"fs";import ge from"path";async function V(t,e){let i=ge.join(t,"nebutra.config.json");fe.writeFileSync(i,JSON.stringify(e,null,2)+`
39
+ `)}import O from"fs";import Y from"path";async function G(t,e){let i=`# Automatically injected by create-sailor
40
+ DATABASE_URL="${String(e.databaseUrl)}"
41
+
42
+ NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY="${String(e.clerkPublishable)}"
43
+ CLERK_SECRET_KEY="${String(e.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=Y.join(t,".env"),r=Y.join(t,".env.local");!O.existsSync(s)&&!O.existsSync(r)?O.writeFileSync(r,i):O.appendFileSync(r,`
47
+ `+i)}import{execSync as he}from"child_process";import v from"fs";import N from"path";import be from"ignore";var xe="https://github.com/Nebutra/Nebutra-Sailor/archive/refs/heads/main.tar.gz";function K(t,e,i){let s=v.readdirSync(e,{withFileTypes:!0});for(let r of s){if(r.name==="node_modules"||r.name===".git")continue;let y=N.join(e,r.name),f=N.relative(t,y).split(N.sep).join("/");r.isDirectory()?(i.push(f+"/"),K(t,y,i)):i.push(f)}}function ve(t){let e=N.join(t,".templateignore");if(!v.existsSync(e))return;let i=v.readFileSync(e,"utf8"),s=be().add(i),r=[];K(t,t,r);let y=r.map(a=>a.endsWith("/")?a.slice(0,-1):a),f=new Set(s.filter(y)),u=y.filter(a=>!f.has(a)).sort((a,h)=>h.split("/").length-a.split("/").length);for(let a of u){let h=N.join(t,a);try{v.existsSync(h)&&v.rmSync(h,{recursive:!0,force:!0})}catch{}}try{v.existsSync(e)&&v.rmSync(e,{force:!0})}catch{}}async function H(t){try{v.mkdirSync(t,{recursive:!0}),he(`set -o pipefail; curl -sSL ${xe} | tar -xz -C "${t}" --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.")}try{ve(t)}catch(e){throw new Error(`Template cloned, but .templateignore processing failed: ${e instanceof Error?e.message:String(e)}`)}}import I from"fs";import Se from"path";async function Q(t,e){let i=Se.join(t,"package.json");if(I.existsSync(i)){let s=I.readFileSync(i,"utf8"),r=JSON.parse(s);r.name=e,r.version="0.1.0",I.writeFileSync(i,JSON.stringify(r,null,2)+`
48
+ `)}}import o from"fs";import l from"path";function X(t,e){if(!o.existsSync(t))return;let i=o.readdirSync(t);for(let s of i){let r=l.join(t,s);o.statSync(r).isDirectory()?X(r,e):e(r)}}async function Z(t,e){let i=l.join(t,"package.json");if(!o.existsSync(i))return;let s=JSON.parse(o.readFileSync(i,"utf8"));if(e.orm!=="prisma"){let r=l.join(t,"packages","db");if(o.existsSync(r)){let y=l.join(r,"prisma");o.existsSync(y)&&o.rmSync(y,{recursive:!0,force:!0});let f=l.join(r,"package.json");if(o.existsSync(f)){let a=JSON.parse(o.readFileSync(f,"utf8"));a.dependencies&&delete a.dependencies["@prisma/client"],a.devDependencies&&delete a.devDependencies.prisma,o.writeFileSync(f,JSON.stringify(a,null,2)+`
49
+ `)}let u=l.join(r,"src","index.ts");o.existsSync(u)&&o.writeFileSync(u,`
14
50
  export const prisma = {} as any;
15
51
  export const db = {} as any;
16
52
  export type PrismaClient = any;
@@ -56,7 +92,7 @@ export type UsageAggregate = any;
56
92
  export type UsageLedgerEntry = any;
57
93
  export type UsageType = any;
58
94
  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,`
95
+ `)}s.scripts?.postinstall&&(s.scripts.postinstall=s.scripts.postinstall.replace(/prisma generate/g,'echo "ORM Disabled"'))}if(!e.i18n){let r=l.join(t,"apps");if(o.existsSync(r)){let y=o.readdirSync(r);for(let f of y){let u=l.join(r,f);if(!o.statSync(u).isDirectory())continue;let a=l.join(u,"src","app");if(!o.existsSync(a))continue;["[lang]","[locale]"].forEach(c=>{let n=l.join(a,c);if(o.existsSync(n)){let d=o.readdirSync(n);for(let S of d)o.renameSync(l.join(n,S),l.join(a,S));o.rmdirSync(n)}}),["src/middleware.ts","middleware.ts"].forEach(c=>{let n=l.join(u,c);o.existsSync(n)&&o.rmSync(n)});let h=l.join(u,"src","lib");o.existsSync(h)||o.mkdirSync(h,{recursive:!0});let q=l.join(h,"mock-i18n.ts");o.writeFileSync(q,`
60
96
  import React from 'react';
61
97
  export function useTranslations(namespace?: string): any { return (key: string) => key; }
62
98
  export function useLocale() { return 'en'; }
@@ -74,7 +110,7 @@ export const useRouter = () => ({ push: (...args: any[]) => {}, replace: (...arg
74
110
  export default function createMiddleware(...args: any[]) { return (req: any) => req; }
75
111
  export function createNavigation(...args: any[]) { return { Link, redirect, usePathname, useRouter, getPathname: () => '/' }; }
76
112
  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' {
113
+ `);let T=l.join(h,"mock-i18n.d.ts");o.writeFileSync(T,`declare module '@/lib/mock-i18n' {
78
114
  export function useTranslations(namespace?: string): any;
79
115
  export function useLocale(): string;
80
116
  export const NextIntlClientProvider: React.FC<any>;
@@ -92,14 +128,25 @@ export function defineRouting<T>(config: T): T { return config; }
92
128
  export function createNavigation(...args: any[]): any;
93
129
  export function defineRouting<T>(config: T): T;
94
130
  }
95
- `),t.writeFileSync(i.join(f,"mock-i18n-index.ts"),`
131
+ `),o.writeFileSync(l.join(h,"mock-i18n-index.ts"),`
96
132
  export * from './mock-i18n';
97
133
  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": {
134
+ `);let k=l.join(u,"tsconfig.json");if(o.existsSync(k))try{let c=o.readFileSync(k,"utf8");if(c.includes('"paths": {')){let n=c.replace(/"paths":\s*\{/,`"paths": {
99
135
  "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);
136
+ "next-intl/*": ["./src/lib/mock-i18n"],`);o.writeFileSync(k,n)}}catch{}let C=l.join(u,"src");o.existsSync(C)&&X(C,c=>{if(!c.endsWith(".ts")&&!c.endsWith(".tsx"))return;let n=o.readFileSync(c,"utf8"),d=!1;c.endsWith("src/app/layout.tsx")&&n.includes("../providers")&&(n=n.replace(/from\s+['"]\.\.\/providers['"]/g,"from './providers'"),d=!0),n=n.replace(/params:\s*Promise<\{[^}]*(lang|locale)[^}]*\}>/g,"params: any"),n=n.replace(/\(\{\s*params\s*\}\s*:\s*\{\s*params:\s*any\s*\}\)/g,"({ params }: any)"),n.match(/locale\s*:\s*any/)&&(n=n.replace(/locale\s*:\s*any/g,"locale: string"),d=!0),n.match(/lang\s*:\s*any/)&&(n=n.replace(/lang\s*:\s*any/g,"lang: string"),d=!0),n.match(/requestLocale\s*:\s*any/)&&(n=n.replace(/requestLocale\s*:\s*any/g,"requestLocale: string"),d=!0),n.match(/\(locale\s*=\s*(.*)\)/)&&(n=n.replace(/\(locale\s*=\s*(.*)\)/g,"() "),d=!0),n.match(/export\s+default\s+async\s+function\s+LangLayout/)&&(n=n.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 })"),d=!0),(c.endsWith("src/app/layout.tsx")||c.endsWith("src/app/sitemap.ts")||c.endsWith("src/app/robots.ts")||c.endsWith("src/components/ui/locale-switcher.tsx")||c.endsWith("src/app/(marketing)/blog/[slug]/page.tsx"))&&(n=n.replace(/const\s+\{\s*lang\s*\}\s*=\s*(await\s+)?params;?/g,'const lang = "en";'),n=n.replace(/const\s+\{\s*locale\s*\}\s*=\s*(await\s+)?params;?/g,'const locale = "en";'),n=n.replace(/const\s+locale\s*=\s*lang\s+as\s+Locale;?/g,'const locale = "en";'),n=n.replace(/\(l\)\s*=>/g,"(l: any) =>"),d=!0),c.endsWith("src/i18n/request.ts")&&(n=n.replace(/async\s*\(\{\s*requestLocale\s*\}\)/g,"async ({ requestLocale }: any)"),d=!0),(n.match(/locale\s*:/)||n.match(/lang\s*:/))&&(d=!0),d&&o.writeFileSync(c,n)});let P=l.join(u,"package.json");if(o.existsSync(P)){let c=JSON.parse(o.readFileSync(P,"utf8"));c.dependencies&&delete c.dependencies["next-intl"],o.writeFileSync(P,JSON.stringify(c,null,2)+`
137
+ `)}}}}if(e.applicationType!=="full"){let r=[];e.applicationType==="saas"&&r.push("contracts");for(let y of r){let f=l.join(t,"packages",y);o.existsSync(f)&&o.rmSync(f,{recursive:!0,force:!0})}}o.writeFileSync(i,JSON.stringify(s,null,2)+`
138
+ `)}var A="1.0.0",te="create-sailor";function g(t,e){t&&process.stdout.write(JSON.stringify(e)+`
139
+ `)}function Ce(){let t=process.env.npm_config_user_agent??"";return t.startsWith("pnpm")?"pnpm":t.startsWith("yarn")?"yarn":t.startsWith("bun")?"bun":"npm"}function ne(t){switch(t){case"full":return"full";case"ecommerce":return"ecommerce";case"web3":return"web3";default:return"saas"}}function se(t){switch(t){case"postgres":case"postgresql":return"postgresql";case"mysql":return"mysql";case"sqlite":return"sqlite";case"none":return"none";default:return"postgresql"}}function oe(t){return t==="drizzle"?"drizzle":t==="none"?"none":"prisma"}function re(t){return t==="lemon"||t==="lemonsqueezy"?"lemonsqueezy":t==="none"?"none":"stripe"}function ae(t){if(!t)return"openai";let e=t.split(",").map(i=>i.trim().toLowerCase());return e.includes("anthropic")?"anthropic":e.includes("none")?"none":"openai"}async function Ne(){let t=new ke;t.name(te).description("Nebutra-Sailor \u2014 AI-Native SaaS template").version(A,"-v, --version").helpOption(!1).argument("[name]","project directory",void 0).option("-t, --template <id>","saas | full | ecommerce | web3 | ai-saas").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("--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"),t.parse(process.argv);let e=t.opts(),[i]=t.args;e.help&&(_(),process.exit(0));let s=!!e.json,r=!!e.dryRun,y=!!e.yes,f=y||!process.stdin.isTTY;s||U(),g(s,{event:"start",version:A});let u=i??(y?"./my-saas-app":void 0),a;if(u)a=u;else if(f)a="./my-saas-app";else{let p=await b.group({name:()=>b.text({message:"Where should we create your project?",placeholder:"./my-saas-app",defaultValue:"./my-saas-app",validate:m=>{if(m.length===0)return"Please enter a path."}})},{onCancel:()=>{process.stdout.write(w.red(`\u2718 Cancelled
140
+ `)),process.exit(130)}});a=String(p.name)}let h=ee.basename(ee.resolve(a)),q=e.pm??Ce(),F=!!e.template,T=!!e.orm,k=!!e.db,C=!!e.payment,P=!!e.ai,c=e.i18n!==void 0,n,d,S,j,$,L;if(f)n=ne(e.template),d=oe(e.orm),S=se(e.db),j=re(e.payment),$=ae(e.ai),L=c?!!e.i18n:!0;else{let p={};F||(p.applicationType=()=>b.select({message:"What kind of application are you building?",options:[{value:"saas",label:"Standard B2B/B2C SaaS"},{value:"full",label:"Full Monorepo"},{value:"ecommerce",label:"E-Commerce Suite"},{value:"web3",label:"Web3 DApp"}],initialValue:"saas"})),T||(p.orm=()=>b.select({message:"Which ORM?",options:[{value:"prisma",label:"Prisma"},{value:"drizzle",label:"Drizzle"},{value:"none",label:"None"}],initialValue:"prisma"})),k||(p.database=()=>b.select({message:"Database?",options:[{value:"postgresql",label:"PostgreSQL"},{value:"mysql",label:"MySQL"},{value:"sqlite",label:"SQLite"},{value:"none",label:"None"}],initialValue:"postgresql"})),C||(p.payment=()=>b.select({message:"Payment?",options:[{value:"stripe",label:"Stripe"},{value:"lemonsqueezy",label:"Lemon Squeezy"},{value:"none",label:"None"}],initialValue:"stripe"})),P||(p.aiProvider=()=>b.select({message:"Default AI Provider?",options:[{value:"openai",label:"OpenAI"},{value:"anthropic",label:"Anthropic"},{value:"none",label:"None"}],initialValue:"openai"})),c||(p.i18n=()=>b.confirm({message:"Enable i18n?",initialValue:!0}));let m=Object.keys(p).length>0?await b.group(p,{onCancel:()=>{process.stdout.write(w.red(`\u2718 Cancelled
141
+ `)),process.exit(130)}}):{};n=F?ne(e.template):m.applicationType,d=T?oe(e.orm):m.orm,S=k?se(e.db):m.database,j=C?re(e.payment):m.payment,$=P?ae(e.ai):m.aiProvider,L=c?!!e.i18n:!!m.i18n}let z={applicationType:n,orm:d,database:S,payment:j,aiProvider:$,i18n:L},E=[["Project name",h],["Template",n],["ORM",d],["Database",S],["Payment",j],["AI",$]];s?E.forEach(([p,m],D)=>{g(!0,{event:"step",step:p,value:m,index:D+1,total:E.length})}):E.forEach(([p,m],D)=>{M({index:D+1,total:E.length,label:p,value:m})});let ie={databaseUrl:"postgresql://postgres:postgres@localhost:5432/nebutra",clerkPublishable:"",clerkSecret:""},ce=Date.now();if(r){let p=[`clone template \u2192 ${a}`,"write nebutra.config.json",`prune template (orm=${d}, i18n=${L}, app=${n})`,"inject .env.local",e.install===!1?"skip install":`run ${q} install`,e.git===!1?"skip git init":"run git init"];if(s)p.forEach(m=>g(!0,{event:"plan",action:m})),g(!0,{event:"done",dryRun:!0});else{process.stdout.write(`
142
+ `+w.bold(`Dry run \u2014 planned actions:
143
+ `));for(let m of p)process.stdout.write(` \u2022 ${m}
144
+ `);process.stdout.write(w.dim(`
145
+ No files were written.
146
+ `))}process.exit(0)}let pe=()=>{process.stdout.write(`
147
+ `+w.red(`\u2718 Cancelled
148
+ `));try{we.existsSync(a)&&process.stdout.write(w.dim(` Partial directory left at ${a} \u2014 remove manually if needed.
149
+ `))}catch{}process.exit(130)};process.on("SIGINT",pe);try{g(s,{event:"step",step:"clone",status:"start"}),await H(a),g(s,{event:"step",step:"clone",status:"ok"}),g(s,{event:"step",step:"package",status:"start"}),await Q(a,h),g(s,{event:"step",step:"package",status:"ok"}),g(s,{event:"step",step:"config",status:"start"}),await V(a,z),g(s,{event:"step",step:"config",status:"ok"}),g(s,{event:"step",step:"prune",status:"start"}),await Z(a,z),g(s,{event:"step",step:"prune",status:"ok"}),g(s,{event:"step",step:"env",status:"start"}),await G(a,ie),g(s,{event:"step",step:"env",status:"ok"});let p=Math.max(1,Math.round((Date.now()-ce)/1e3));s?g(!0,{event:"done",status:"ok",elapsedSec:p,targetDir:a}):W({elapsedSec:p,targetDir:a,skippedInstall:e.install===!1});try{Pe({pkg:{name:te,version:A},updateCheckInterval:1e3*60*60*24}).notify({defer:!1,isGlobal:!0})}catch{}process.exit(0)}catch(p){let m=p instanceof Error?p.message:String(p);s?g(!0,{event:"error",message:m}):process.stdout.write(w.red(`
150
+ \u2718 Failed: ${m}
151
+ `)),process.exit(1)}}Ne().catch(t=>{process.stderr.write(String(t)+`
152
+ `),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.0.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
  },