create-sailor 1.10.1 → 2.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 (76) hide show
  1. package/README.md +34 -135
  2. package/dist/index.js +108 -2010
  3. package/package.json +16 -17
  4. package/templates/backends/python/README.md +0 -16
  5. package/templates/backends/python/_shared/__init__.py +0 -5
  6. package/templates/backends/python/pyproject.toml +0 -13
  7. package/templates/deploy/railway.toml +0 -13
  8. package/templates/deploy/vercel.json +0 -37
  9. package/templates/deploy/wrangler.gateway.toml +0 -11
  10. package/templates/deploy/wrangler.toml +0 -8
  11. package/templates/docs/docusaurus/README.md +0 -24
  12. package/templates/docs/docusaurus/docs/getting-started/develop.md +0 -20
  13. package/templates/docs/docusaurus/docs/getting-started/install.md +0 -11
  14. package/templates/docs/docusaurus/docs/intro.md +0 -15
  15. package/templates/docs/docusaurus/docusaurus.config.ts +0 -66
  16. package/templates/docs/docusaurus/package.json +0 -44
  17. package/templates/docs/docusaurus/sidebars.ts +0 -14
  18. package/templates/docs/docusaurus/src/css/custom.css +0 -22
  19. package/templates/docs/docusaurus/tsconfig.json +0 -7
  20. package/templates/docs/fumadocs/README.md +0 -46
  21. package/templates/docs/fumadocs/app/(home)/page.tsx +0 -22
  22. package/templates/docs/fumadocs/app/api/search/route.ts +0 -4
  23. package/templates/docs/fumadocs/app/docs/[[...slug]]/page.tsx +0 -39
  24. package/templates/docs/fumadocs/app/docs/layout.tsx +0 -11
  25. package/templates/docs/fumadocs/app/global.css +0 -10
  26. package/templates/docs/fumadocs/app/layout.tsx +0 -18
  27. package/templates/docs/fumadocs/content/docs/api.mdx +0 -31
  28. package/templates/docs/fumadocs/content/docs/concepts.mdx +0 -19
  29. package/templates/docs/fumadocs/content/docs/getting-started.mdx +0 -56
  30. package/templates/docs/fumadocs/content/docs/index.mdx +0 -17
  31. package/templates/docs/fumadocs/content/docs/meta.json +0 -4
  32. package/templates/docs/fumadocs/lib/source.ts +0 -7
  33. package/templates/docs/fumadocs/next.config.mjs +0 -20
  34. package/templates/docs/fumadocs/package.json +0 -33
  35. package/templates/docs/fumadocs/source.config.ts +0 -17
  36. package/templates/docs/fumadocs/tsconfig.json +0 -25
  37. package/templates/docs/mintlify/README.md +0 -25
  38. package/templates/docs/mintlify/api-reference/introduction.mdx +0 -25
  39. package/templates/docs/mintlify/introduction.mdx +0 -21
  40. package/templates/docs/mintlify/mint.json +0 -37
  41. package/templates/docs/mintlify/package.json +0 -14
  42. package/templates/docs/mintlify/quickstart.mdx +0 -26
  43. package/templates/docs/nextra/README.md +0 -35
  44. package/templates/docs/nextra/app/[[...mdxPath]]/page.tsx +0 -23
  45. package/templates/docs/nextra/app/layout.tsx +0 -36
  46. package/templates/docs/nextra/content/_meta.ts +0 -4
  47. package/templates/docs/nextra/content/getting-started.mdx +0 -35
  48. package/templates/docs/nextra/content/index.mdx +0 -18
  49. package/templates/docs/nextra/mdx-components.tsx +0 -10
  50. package/templates/docs/nextra/next.config.mjs +0 -9
  51. package/templates/docs/nextra/package.json +0 -25
  52. package/templates/docs/nextra/tsconfig.json +0 -21
  53. package/templates/docs/vitepress/.vitepress/config.ts +0 -35
  54. package/templates/docs/vitepress/README.md +0 -23
  55. package/templates/docs/vitepress/guide/getting-started.md +0 -26
  56. package/templates/docs/vitepress/guide/introduction.md +0 -12
  57. package/templates/docs/vitepress/index.md +0 -23
  58. package/templates/docs/vitepress/package.json +0 -18
  59. package/templates/docs/vitepress/tsconfig.json +0 -15
  60. package/templates/orm/drizzle/README.md +0 -58
  61. package/templates/orm/drizzle/drizzle.config.ts +0 -16
  62. package/templates/orm/drizzle/migrations/.gitkeep +0 -0
  63. package/templates/orm/drizzle/package.json +0 -30
  64. package/templates/orm/drizzle/src/index.ts +0 -31
  65. package/templates/orm/drizzle/src/schema/auth.ts +0 -73
  66. package/templates/orm/drizzle/src/schema/billing.ts +0 -37
  67. package/templates/orm/drizzle/src/schema/index.ts +0 -3
  68. package/templates/orm/drizzle/src/schema/tenant.ts +0 -57
  69. package/templates/orm/drizzle/tsconfig.json +0 -16
  70. package/templates/workflows/README.md +0 -9
  71. package/templates/workflows/inngest/README.md +0 -3
  72. package/templates/workflows/inngest/welcome-email.ts +0 -30
  73. package/templates/workflows/n8n/README.md +0 -3
  74. package/templates/workflows/n8n/example-flow.json +0 -6
  75. package/templates/workflows/pusher/README.md +0 -3
  76. package/templates/workflows/pusher/events.ts +0 -6
package/dist/index.js CHANGED
@@ -1,488 +1,13 @@
1
1
  #!/usr/bin/env node
2
- import{execSync as Rp}from"child_process";import us from"fs";import Dn from"path";import*as ms from"@clack/prompts";import x from"picocolors";import{Command as hs}from"commander";import Yn from"fs";import Zt from"path";import{fileURLToPath as ys}from"url";function Ss(){try{let t=Zt.dirname(ys(import.meta.url));for(let n=0;n<4;n++){let o=Zt.join(t,"package.json");if(Yn.existsSync(o)){let r=Yn.readFileSync(o,"utf-8"),s=JSON.parse(r);if(s.name==="create-sailor"&&typeof s.version=="string")return s.version}t=Zt.dirname(t)}return"0.0.0-dev"}catch{return"0.0.0-dev"}}var Q=Ss();var Es="create-sailor";function Gn(){let e=new hs;return e.name(Es).description("Nebutra-Sailor \u2014 AI-Native SaaS template").version(Q,"-v, --version").helpOption(!1).argument("[name]","project name or path (default: my-app; use . for current directory)",void 0).option("-p, --pm <id>","npm | pnpm | yarn | bun").option("--region <id>","global | cn | hybrid").option("--orm <id>","prisma (default, primary) | drizzle (dual-ORM: adds db-drizzle alongside Prisma \u2014 Postgres only)").option("--db <id>","postgres | mysql | sqlite | none \u2014 engine (Prisma provider)").option("--db-host <id>","local | supabase | neon | vercel-postgres | planetscale | railway | aliyun-rds | tencent-cdb | none \u2014 managed-provider (region default: supabase global, local cn)").option("--auth <id>","clerk | betterauth | nextauth | supabase | none").option("--social-login <ids>","CN social login providers \u2014 wechat | qq | dingtalk | workweixin | feishu | weibo (comma-separated)").option("--payment <id>","stripe | lemon | wechat | alipay | none").option("--ai <mode-or-ids>","topology shorthand (gateway | direct | custom | none) OR comma-separated provider ids (e.g. openai,anthropic)").option("--deploy <target>","vercel | railway | cloudflare | selfhost").option("--docs <id>","fumadocs | mintlify | docusaurus | nextra | vitepress | none").option("--email <id>","resend | postmark | ses | aliyun-dm | tencent-ses | netease | none").option("--storage <id>","r2 | s3 | supabase-storage | aliyun-oss | tencent-cos | qiniu | none").option("--monitoring <id>","sentry | datadog | aliyun-arms | tingyun | none").option("--analytics <id>","posthog | plausible | umami | baidu | sensors | none").option("--sms <id>","twilio | aliyun-sms | tencent-sms | yunpian | none").option("--queue <id>","qstash | bullmq | sqs | none").option("--search <id>","meilisearch | typesense | algolia | pgvector | none").option("--cache <id>","upstash-redis | vercel-kv | redis | dragonfly | none").option("--notifications <id>","novu | knock | custom | none").option("--webhooks <id>","svix | custom | none").option("--cms <id>","sanity | contentful | strapi | none").option("--feature-flags <id>","vercel-flags | growthbook | configcat | none").option("--captcha <id>","turnstile | hcaptcha | aliyun-slide | none").option("--mcp <mode>","on | off (default: on)").option("--metering <mode>","auto | on | off (default: auto \u2014 auto-on when payment is set)").option("--billing-mode <mode>","usage | seat | credits (default: usage)").option("--idp <id>","clerk | oauth-server (default: clerk)").option("--access-gate <mode>","none | invite (default: none)").option("--cron-jobs <bool>","true | false \u2014 scaffold scheduled cron handlers (default: true)").option("--audit-log <bool>","true | false \u2014 enable /settings/audit-log + arch test (default: false \u2014 @nebutra/audit is WIP)").option("--api-keys <bool>","true | false \u2014 enable /settings/api-keys page (default: true)").option("--command-palette <bool>","true | false \u2014 enable \u2318K command palette (default: true)").option("--cookie-consent <bool>","true | false \u2014 enable GDPR/CCPA cookie banner (default: true)").option("--legal-pages <bool>","true | false \u2014 enable dynamic /legal/[slug] route (default: true)").option("--china-compliance <bool>","true | false \u2014 enable @nebutra/china-compliance + ICP footer (default: true when --region=cn, otherwise false)").option("--i18n","enable i18n").option("--no-i18n","disable i18n").option("--with-workflows","scaffold workflows/ (inngest + n8n + pusher stubs)").option("--with-python-backend","scaffold backends/python/ (FastAPI stub \u2014 only when TS-by-Default ADR exception applies)").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}import Wn from"picocolors";var en=[{id:"local",name:"Local / Self-hosted Docker",region:"both",supportedEngines:["postgresql","mysql","sqlite"],envVars:[{name:"DATABASE_URL",placeholder:"postgresql://postgres:postgres@localhost:5432/$PROJECT",comment:"Local Postgres via docker-compose (pnpm infra:up)"}],docs:"https://www.postgresql.org/docs/",description:"Docker on localhost. You operate it.",tier:"self-hosted"},{id:"supabase",name:"Supabase",region:"global",supportedEngines:["postgresql"],forcedEngine:"postgresql",keepDirectUrl:!0,envVars:[{name:"DATABASE_URL",placeholder:"postgresql://postgres.<project-ref>:<password>@aws-0-<region>.pooler.supabase.com:5432/postgres?sslmode=require",comment:"Supabase session pooler \u2014 use for runtime queries"},{name:"DIRECT_URL",placeholder:"postgresql://postgres:<password>@db.<project-ref>.supabase.co:5432/postgres?sslmode=require",comment:"Supabase direct \u2014 use for Prisma migrations"},{name:"NEXT_PUBLIC_SUPABASE_URL",placeholder:"https://<project>.supabase.co"},{name:"NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY",placeholder:""},{name:"NEXT_PUBLIC_SUPABASE_ANON_KEY",placeholder:""},{name:"SUPABASE_PUBLISHABLE_KEY",placeholder:""},{name:"SUPABASE_SERVICE_ROLE_KEY",placeholder:""}],docs:"https://supabase.com/docs",description:"Managed Postgres + auth/storage/realtime/edge-functions.",tier:"managed"},{id:"neon",name:"Neon",region:"global",supportedEngines:["postgresql"],forcedEngine:"postgresql",keepDirectUrl:!0,envVars:[{name:"DATABASE_URL",placeholder:"postgresql://<user>:<pwd>@ep-<branch>-pooler.<region>.neon.tech/<db>?sslmode=require",comment:"Neon pooled \u2014 runtime queries"},{name:"DIRECT_URL",placeholder:"postgresql://<user>:<pwd>@ep-<branch>.<region>.neon.tech/<db>?sslmode=require",comment:"Neon direct (no -pooler) \u2014 migrations"}],docs:"https://neon.tech/docs",description:"Serverless Postgres with branching and scale-to-zero.",tier:"managed"},{id:"vercel-postgres",name:"Vercel Postgres",region:"global",supportedEngines:["postgresql"],forcedEngine:"postgresql",keepDirectUrl:!0,envVars:[{name:"DATABASE_URL",placeholder:"$POSTGRES_PRISMA_URL",comment:"Vercel Marketplace injects POSTGRES_PRISMA_URL automatically"},{name:"DIRECT_URL",placeholder:"$POSTGRES_URL_NON_POOLING",comment:"Vercel Marketplace injects POSTGRES_URL_NON_POOLING automatically"}],docs:"https://vercel.com/docs/storage/vercel-postgres",description:"Neon-backed, auto-provisioned via Vercel Marketplace.",tier:"managed"},{id:"planetscale",name:"PlanetScale Postgres",region:"global",supportedEngines:["postgresql"],forcedEngine:"postgresql",keepDirectUrl:!0,envVars:[{name:"DATABASE_URL",placeholder:"postgresql://postgres.<branch-id>:<password>@<host>.horizon.psdb.cloud:6432/postgres?sslmode=require",comment:"PlanetScale Postgres pooled/PgBouncer URL \u2014 runtime queries"},{name:"DIRECT_URL",placeholder:"postgresql://postgres.<branch-id>:<password>@<host>.horizon.psdb.cloud:5432/postgres?sslmode=require",comment:"PlanetScale Postgres direct URL \u2014 Prisma migrations"}],docs:"https://planetscale.com/docs/postgres",description:"Managed Postgres with branching; compatible with Sailor migrations.",tier:"managed"},{id:"railway",name:"Railway",region:"global",supportedEngines:["postgresql","mysql"],envVars:[{name:"DATABASE_URL",placeholder:"$DATABASE_URL",comment:"Railway service env var; injected when you link a Postgres service"}],docs:"https://docs.railway.app/databases/postgresql",description:"Generalist managed infra; standard TCP Postgres/MySQL.",tier:"managed"},{id:"aliyun-rds",name:"Aliyun RDS",region:"cn",supportedEngines:["postgresql","mysql"],envVars:[{name:"DATABASE_URL",placeholder:"postgresql://<user>:<pwd>@<rds-instance>.rds.aliyuncs.com:5432/<db>",comment:"Aliyun RDS Postgres \u2014 internal VPC endpoint recommended"}],docs:"https://www.alibabacloud.com/help/en/rds",description:"Aliyun RDS \u2014 China compliance + intranet routing.",tier:"managed"},{id:"tencent-cdb",name:"Tencent CloudDB (CDB)",region:"cn",supportedEngines:["mysql","postgresql"],envVars:[{name:"DATABASE_URL",placeholder:"mysql://<user>:<pwd>@<cdb-instance>.tencentcdb.com:3306/<db>",comment:"Tencent CloudDB \u2014 VPC endpoint recommended"}],docs:"https://intl.cloud.tencent.com/document/product/236",description:"Tencent CloudDB \u2014 China compliance.",tier:"managed"},{id:"none",name:"None",region:"both",supportedEngines:["postgresql","mysql","sqlite"],envVars:[],docs:"",description:"Skip database scaffolding.",tier:"self-hosted"}];function ft(e){return en.find(t=>t.id===e)}function qn(e){return e==="cn"||e==="hybrid"?"local":"supabase"}var ke=[{id:"r2",name:"Cloudflare R2",region:"global",envVars:["R2_ACCOUNT_ID","R2_ACCESS_KEY_ID","R2_SECRET_ACCESS_KEY","R2_BUCKET"],docs:"https://developers.cloudflare.com/r2/api/s3/api/",pkg:"@aws-sdk/client-s3",pkgVersion:"^3.658.0"},{id:"s3",name:"AWS S3",region:"global",envVars:["AWS_ACCESS_KEY_ID","AWS_SECRET_ACCESS_KEY","AWS_S3_REGION","S3_BUCKET"],docs:"https://docs.aws.amazon.com/s3/",pkg:"@aws-sdk/client-s3",pkgVersion:"^3.658.0"},{id:"supabase-storage",name:"Supabase Storage",region:"global",envVars:["SUPABASE_URL","SUPABASE_SERVICE_ROLE_KEY","SUPABASE_BUCKET"],docs:"https://supabase.com/docs/guides/storage",pkg:"@supabase/supabase-js",pkgVersion:"^2.45.4"},{id:"aliyun-oss",name:"\u963F\u91CC\u4E91 OSS",region:"cn",envVars:["ALIYUN_OSS_ACCESS_KEY","ALIYUN_OSS_ACCESS_SECRET","ALIYUN_OSS_REGION","ALIYUN_OSS_BUCKET"],docs:"https://help.aliyun.com/product/31815.html",pkg:"ali-oss",pkgVersion:"^6.21.0"},{id:"tencent-cos",name:"\u817E\u8BAF\u4E91 COS",region:"cn",envVars:["TENCENT_COS_SECRET_ID","TENCENT_COS_SECRET_KEY","TENCENT_COS_BUCKET","TENCENT_COS_REGION"],docs:"https://cloud.tencent.com/document/product/436",pkg:"cos-nodejs-sdk-v5",pkgVersion:"^2.13.7"},{id:"qiniu",name:"\u4E03\u725B\u4E91 Kodo",region:"cn",envVars:["QINIU_ACCESS_KEY","QINIU_SECRET_KEY","QINIU_BUCKET","QINIU_DOMAIN"],docs:"https://developer.qiniu.com/kodo",pkg:"qiniu",pkgVersion:"^7.14.0"},{id:"none",name:"None",region:"global",envVars:[],docs:""}];function tn(e){let t=e==="supabase"?"supabase-storage":e;return ke.some(n=>n.id===t)?t:void 0}function Hn(e){let t=tn(e);return t?ke.find(n=>n.id===t):void 0}var $p=ke.reduce((e,t)=>(e[t.region]||(e[t.region]=[]),e[t.region].push(t),e),{});function zn(){let e=process.env.npm_config_user_agent??"";return e.startsWith("pnpm")?"pnpm":e.startsWith("yarn")?"yarn":e.startsWith("bun")?"bun":"npm"}function nn(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 on(e,t,n){let o=e?.trim().toLowerCase()??qn(t),r=ft(o);if(!r){let i=en.map(a=>a.id).join(", ");throw new Error(`Unknown --db-host="${o}". Valid: ${i}`)}let s=n;return r.forcedEngine&&s!=="none"&&s!==r.forcedEngine&&(s=r.forcedEngine),{hostId:r.id,engine:s}}function rn(e){return e==="drizzle"?"drizzle":"prisma"}function yt(e){return e==="lemon"||e==="lemonsqueezy"?"lemon":e==="wechat"?"wechat":e==="alipay"?"alipay":e==="none"?"none":"stripe"}function St(e){if(!e)return"stripe";let t=e.toLowerCase();return t==="lemon"||t==="lemonsqueezy"?"lemon":t==="wechat"?"wechat":t==="alipay"?"alipay":t==="none"?"none":"stripe"}function sn(e){if(!e)return"clerk";let t=e.toLowerCase();return t==="betterauth"||t==="better-auth"?"betterauth":t==="nextauth"||t==="next-auth"||t==="authjs"||t==="auth.js"?"nextauth":t==="supabase"||t==="supabase-auth"?"supabase":t==="none"?"none":"clerk"}var vs=new Set(["gateway","direct","custom","none"]);function an(e){if(!e)return!1;let t=e.trim().toLowerCase();return vs.has(t)}function cn(e){if(!e)return["openai"];if(an(e))return[];let t=e.split(",").map(n=>n.trim().toLowerCase());return t.includes("none")?[]:t}function Jn(e,t){let n=e??t;return tn(n)??n}var Xn={mintlify:"Mintlify",docusaurus:"Docusaurus",nextra:"Nextra",vitepress:"VitePress"};function pn(e,t){if(!e)return"fumadocs";let n=e.toLowerCase();if(n==="fumadocs"||n==="none")return n;if(n in Xn){let o=Xn[n];return t?process.stdout.write(JSON.stringify({event:"notice",kind:"docs-fallback",requested:n,effective:"fumadocs"})+`
3
- `):process.stdout.write(Wn.yellow(`\u26A0 ${o} support is coming in v1.2. Falling back to fumadocs.
4
- `)+Wn.dim(` Track progress: https://github.com/Nebutra/Nebutra-Sailor/issues
5
- `)),"fumadocs"}return"fumadocs"}function ht(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"}}function ln(e){if(!e)return"global";let t=e.toLowerCase();return t==="cn"?"cn":t==="hybrid"?"hybrid":"global"}function Qn(e){return{...e==="cn"?{email:"aliyun-dm",storage:"aliyun-oss",monitoring:"sentry",analytics:"baidu",sms:"aliyun-sms"}:e==="hybrid"?{email:"resend",storage:"aliyun-oss",monitoring:"sentry",analytics:"posthog",sms:"aliyun-sms"}:{email:"resend",storage:"r2",monitoring:"sentry",analytics:"posthog",sms:"twilio"},queue:"none",search:"none",cache:e==="global"?"upstash-redis":e==="cn"?"redis":"upstash-redis",notifications:"none",webhooks:"none",cms:"none",featureFlags:"none",captcha:"none",mcp:"on",metering:"auto",billingMode:"usage",idp:"clerk",accessGate:"none"}}function dn(e){return e==="cn"?"wechat":"stripe"}import*as W from"@clack/prompts";import js from"picocolors";var un=[{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.sensenova.cn/docs",baseURL:"https://token.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:[]}],Hp=un.reduce((e,t)=>(e[t.category]||(e[t.category]=[]),e[t.category].push(t),e),{});var bs=["openai","anthropic","google"],_s=["openai"];function mn(e){let t=e.providerIds??[];switch(e.mode){case"gateway":{let n=t.length>0?t:[...bs];return{mode:"gateway",providerIds:n,customEndpoint:e.customEndpoint,routing:{profile:"multi-provider-gateway",providerSeed:n,runtimeGovernance:!0}}}case"direct":{let n=t.length>0?t:[..._s];return{mode:"direct",providerIds:n,customEndpoint:e.customEndpoint,routing:{profile:"direct-adapters",providerSeed:n,runtimeGovernance:!1}}}case"custom":return{mode:"custom",providerIds:t,customEndpoint:e.customEndpoint,routing:{profile:"openai-compatible",providerSeed:t,runtimeGovernance:!0}};case"none":return{mode:"none",providerIds:[],routing:{profile:"disabled",providerSeed:[],runtimeGovernance:!1}}}}var et=[{id:"wechat",name:"\u5FAE\u4FE1\u5F00\u653E\u5E73\u53F0",region:"cn",envVars:["WECHAT_APP_ID","WECHAT_APP_SECRET"],docs:"https://open.weixin.qq.com/cgi-bin/showdocument",callbackUrl:"/api/auth/callback/wechat",tokenEndpoint:"https://api.weixin.qq.com/sns/oauth2/access_token"},{id:"qq",name:"QQ \u767B\u5F55",region:"cn",envVars:["QQ_APP_ID","QQ_APP_KEY"],docs:"https://wiki.connect.qq.com",callbackUrl:"/api/auth/callback/qq",tokenEndpoint:"https://graph.qq.com/oauth2.0/token"},{id:"dingtalk",name:"\u9489\u9489",region:"cn",envVars:["DINGTALK_APP_ID","DINGTALK_APP_SECRET"],docs:"https://open.dingtalk.com/document/",callbackUrl:"/api/auth/callback/dingtalk",tokenEndpoint:"https://api.dingtalk.com/v1.0/oauth2/userAccessToken"},{id:"workweixin",name:"\u4F01\u4E1A\u5FAE\u4FE1",region:"cn",envVars:["WORKWEIXIN_CORP_ID","WORKWEIXIN_AGENT_ID","WORKWEIXIN_SECRET"],docs:"https://developer.work.weixin.qq.com",callbackUrl:"/api/auth/callback/workweixin",tokenEndpoint:"https://qyapi.weixin.qq.com/cgi-bin/gettoken"},{id:"feishu",name:"\u98DE\u4E66 / Lark",region:"cn",envVars:["FEISHU_APP_ID","FEISHU_APP_SECRET"],docs:"https://open.feishu.cn/document/",callbackUrl:"/api/auth/callback/feishu",tokenEndpoint:"https://open.feishu.cn/open-apis/authen/v1/access_token"},{id:"weibo",name:"\u5FAE\u535A",region:"cn",envVars:["WEIBO_APP_KEY","WEIBO_APP_SECRET"],docs:"https://open.weibo.com/wiki/",callbackUrl:"/api/auth/callback/weibo",tokenEndpoint:"https://api.weibo.com/oauth2/access_token"}],ws=new Set(et.map(e=>e.id));function Zn(e){return et.find(t=>t.id===e)}function eo(e){if(!e)return[];let t=e.split(",").map(r=>r.trim().toLowerCase()).filter(Boolean);if(t.includes("none"))return[];let n=[],o=new Set;for(let r of t)ws.has(r)&&!o.has(r)&&(n.push(r),o.add(r));return n}import re from"fs";import k from"path";import{fileURLToPath as Rs}from"url";var to=["web","landing","auth","admin","design-docs","sailor-docs","router","forge","typelens","gateway","python-ai"],Ps={web:"frontend",landing:"frontend",auth:"frontend",admin:"frontend","design-docs":"frontend","sailor-docs":"frontend",router:"frontend",forge:"frontend",typelens:"frontend",gateway:"edgeGateway","python-ai":"originBackend"},As={frontend:"vercel",edgeGateway:"cloudflare-workers",originBackend:"ecs-docker"};function Cs(e){return to.includes(e)}function Is(e){if(!Cs(e))throw new Error(`Unknown deploy service '${e}'. Deploy targets apply only to apps/backends, not packages.`)}function Ts(e){return Is(e),Ps[e]}function xs(e){let t=Ts(e);return As[t]}function no(){return Object.fromEntries(to.map(e=>[e,xs(e)]))}var ks=Rs(import.meta.url),oo=k.dirname(ks),Ns="cloudflare-pages",Os="standalone",Ls="railway";function gn(e,t,n,o){return{...e,web:t,landing:t,auth:t,admin:t,"design-docs":t,"sailor-docs":t,router:t,forge:t,typelens:t,gateway:n,"python-ai":o}}function Us(){let e=[k.join(oo,"..","templates","deploy"),k.join(oo,"..","..","templates","deploy")];for(let t of e)if(re.existsSync(t))return t;return e[0]}function xt(e){let t=no();switch(e){case"cloudflare":return gn(t,Ns,"cloudflare-workers","ecs-docker");case"selfhost":return gn(t,Os,"ecs-docker","ecs-docker");case"railway":return gn(t,Ls,"railway","railway");case"none":case"vercel":return t}}function Ds(e){return`DEPLOY_TARGET_${e.toUpperCase().replace(/-/g,"_")}`}async function ro(e,t){let n=k.join(e,"backends","gateway");re.existsSync(n)&&await re.promises.copyFile(k.join(t,"wrangler.gateway.toml"),k.join(n,"wrangler.toml"))}async function so(e,t){let n=k.join(e,".env.example"),o;try{o=await re.promises.readFile(n,"utf8")}catch(i){if(i.code==="ENOENT")return;throw i}if(o.includes("# Deployment Targets"))return;let s=["","# Deployment Targets",...Object.entries(t).map(([i,a])=>`${Ds(i)}="${a}"`)].join(`
6
- `);await re.promises.appendFile(n,`${s}
7
- `)}async function io(e,t){if(t==="none")return;let n=Us();t==="vercel"?(await re.promises.copyFile(k.join(n,"vercel.json"),k.join(e,"vercel.json")),await ro(e,n)):t==="railway"?await re.promises.copyFile(k.join(n,"railway.toml"),k.join(e,"railway.toml")):t==="cloudflare"?(await re.promises.copyFile(k.join(n,"wrangler.toml"),k.join(e,"wrangler.toml")),await ro(e,n)):t==="selfhost"&&(await re.promises.copyFile(k.join(n,"docker-compose.yml"),k.join(e,"docker-compose.yml")),await re.promises.copyFile(k.join(n,"Dockerfile.web"),k.join(e,"Dockerfile.web")))}var Ms={qstash:"foundation",bullmq:"foundation",upstash:"foundation",sqs:"foundation",meilisearch:"foundation",typesense:"foundation",algolia:"foundation",pgvector:"foundation","vercel-flags":"foundation",growthbook:"foundation",configcat:"foundation",turnstile:"wip",hcaptcha:"wip","aliyun-slide":"wip",invite:"foundation"};function Fs(e){return e?Ms[e]??"stable":"stable"}function tt(e){switch(e){case"stable":return"";case"foundation":return"[Foundation]";case"wip":return"[WIP]";case"deprecated":return"[Deprecated]"}}function Ne(e){switch(e){case"stable":return"Production-ready.";case"foundation":return"Foundation status \u2014 core contracts work, but production use still requires provider credentials, adapter wiring, or a verified integration contract.";case"wip":return"WIP \u2014 actively being built, should not be used in production.";case"deprecated":return"Deprecated \u2014 scheduled for removal, do not use."}}function Rt(e){let t=[];for(let{flag:n,provider:o}of e){if(!o||o==="none")continue;let r=Fs(o);r!=="stable"&&t.push({flag:n,provider:o,status:r})}return t}function Pe(e,t){if(e===void 0)return t;let n=String(e).trim().toLowerCase();return n==="true"||n==="1"||n==="yes"||n==="on"?!0:n==="false"||n==="0"||n==="no"||n==="off"?!1:t}function ao(e,t){let n=t==="cn";return{cronJobs:Pe(e.cronJobs,!0),auditLog:Pe(e.auditLog,!1),apiKeys:Pe(e.apiKeys,!0),webhooks:Pe(e.webhooks,!0),commandPalette:Pe(e.commandPalette,!0),cookieConsent:Pe(e.cookieConsent,!0),legalPages:Pe(e.legalPages,!0),chinaCompliance:Pe(e.chinaCompliance,n)}}async function Bs(e,t,n,o){let r={};return t||(r.region=()=>W.select({message:"Target region?",options:[{value:"global",label:"Global",hint:"Stripe \xB7 Resend \xB7 R2 defaults"},{value:"cn",label:"China",hint:"WeChat Pay \xB7 Aliyun DM/OSS defaults"},{value:"hybrid",label:"Hybrid",hint:"global + CN dual track"}],initialValue:"global"})),n||(r.auth=()=>W.select({message:"Auth provider?",options:[{value:"clerk",label:"Clerk",hint:"managed, fastest setup"},{value:"betterauth",label:"Better Auth",hint:"self-hosted, modern (2FA / passkeys / RBAC)"},{value:"nextauth",label:"NextAuth (Auth.js v5)",hint:"self-hosted, mature, large ecosystem"},{value:"supabase",label:"Supabase Auth",hint:"managed auth with Supabase storage/realtime ecosystem"},{value:"none",label:"None"}],initialValue:"clerk"})),o||(r.aiMode=()=>W.select({message:"AI topology?",options:[{value:"gateway",label:"Multi-provider AI Gateway / router",hint:"recommended"},{value:"direct",label:"Direct SDK/provider adapters"},{value:"custom",label:"OpenAI-compatible endpoint"},{value:"none",label:"Skip AI"}],initialValue:"gateway"}),r.aiProviders=({results:s})=>{if(s.aiMode!=="direct")return Promise.resolve([]);let i=un.map(a=>({value:a.id,label:`[${a.category}] ${a.name}`}));return W.multiselect({message:"Select direct AI provider adapters (expert path)",options:i,initialValues:["openai"],required:!1})},r.customAiName=({results:s})=>s.aiMode==="custom"?W.text({message:"Custom endpoint name (e.g. proxy, local):",defaultValue:"custom",placeholder:"custom"}):Promise.resolve(void 0),r.customAiBaseUrl=({results:s})=>s.aiMode==="custom"?W.text({message:"Custom endpoint base URL (e.g. https://api.proxy.com/v1):",validate:i=>{if(!i?.length)return"Base URL is required."}}):Promise.resolve(void 0),r.customAiApiKeyEnv=({results:s})=>s.aiMode==="custom"?W.text({message:"Environment variable name for the API Key:",defaultValue:"CUSTOM_AI_API_KEY",placeholder:"CUSTOM_AI_API_KEY"}):Promise.resolve(void 0)),Object.keys(r).length===0?{}:await W.group(r,{onCancel:()=>{process.stdout.write(js.red(`\u2718 Cancelled
8
- `)),process.exit(130)}})}async function co(e,t){let n=!!e.region,o=!!e.auth,r=!!e.payment,s=!!e.ai,i=!!e.email,a=!!e.storage,c=!!e.monitoring,p=!!e.analytics,m=!!e.sms,d=e.i18n!==void 0,g=!!e.yes||!process.stdin.isTTY,h,P,E,l,y,U,R,ve=eo(e.socialLogin),ue,oe,G,q,I,z,V;if(g){h=ln(e.region),P=rn(e.orm);{let H=nn(e.db),Tt=on(e.dbHost,h,H);E=Tt.engine,l=Tt.hostId}let J=r?e.payment:dn(h);y=yt(J),U=St(J),R=sn(e.auth);let Ze="gateway";if(s){let H=e.ai?.trim().toLowerCase()??"";an(H)?Ze=H:Ze="direct"}let gt=mn({mode:Ze,providerIds:s?cn(e.ai):void 0});ue=gt.mode,oe=gt.routing,G=gt.providerIds,I=ht(e.deploy),z=pn(e.docs,t),V=d?!!e.i18n:!0}else{let J=await Bs(e,n,o,s);h=ln(n?e.region:J.region),P=rn(e.orm);{let Tt=nn(e.db),Kn=on(e.dbHost,h,Tt);E=Kn.engine,l=Kn.hostId}let Ze=r?e.payment:dn(h);y=yt(Ze),U=St(Ze),R=sn(o?e.auth:J.auth);let gt=J.aiMode==="custom"?{name:J.customAiName??"custom",baseURL:J.customAiBaseUrl??"",apiKeyEnvName:J.customAiApiKeyEnv??"CUSTOM_AI_API_KEY"}:void 0,H=mn({mode:s?e.ai?.trim().toLowerCase()==="none"?"none":"direct":J.aiMode??"gateway",providerIds:s?cn(e.ai):J.aiProviders,customEndpoint:gt});ue=H.mode,oe=H.routing,G=H.providerIds,H.customEndpoint&&(q={name:H.customEndpoint.name,baseURL:H.customEndpoint.baseURL,apiKeyEnvName:H.customEndpoint.apiKeyEnvName}),I=ht(e.deploy),z=pn(e.docs,t),V=d?!!e.i18n:!0}let A=Qn(h),Re=i?e.email:A.email,be=Jn(a?e.storage:void 0,A.storage),Je=c?e.monitoring:A.monitoring,Qe=p?e.analytics:A.analytics,_e=m?e.sms:A.sms,b=e.queue??A.queue,D=e.search??A.search,_=e.cache??A.cache,me=e.notifications??A.notifications,Xt=e.webhooks??A.webhooks,Mn=e.cms??A.cms,zt=e.featureFlags??A.featureFlags,Jt=e.captcha??A.captcha,Fn=e.mcp??A.mcp,jn=e.metering??A.metering,Bn=e.billingMode??A.billingMode,$n=e.idp??A.idp,Qt=e.accessGate==="invite"?"invite":A.accessGate,Vn=xt(I),we=ao({cronJobs:e.cronJobs,auditLog:e.auditLog,apiKeys:e.apiKeys,commandPalette:e.commandPalette,cookieConsent:e.cookieConsent,legalPages:e.legalPages,chinaCompliance:e.chinaCompliance},h),gs=Rt([{flag:"queue",provider:b},{flag:"search",provider:D},{flag:"notifications",provider:me},{flag:"webhooks",provider:Xt},{flag:"feature-flags",provider:zt},{flag:"captcha",provider:Jt},{flag:"access-gate",provider:Qt}]),fs={region:h,orm:P,database:E,databaseHost:l,payment:y,aiMode:ue,aiRouting:oe,aiProviders:G,customAiEndpoint:q,deployTarget:I,deployTargets:Vn,docs:z,i18n:V,email:Re,storage:be,monitoring:Je,analytics:Qe,sms:_e,queue:b,search:D,cache:_,notifications:me,webhooks:Xt,cms:Mn,featureFlags:zt,captcha:Jt,mcp:Fn,metering:jn,billingMode:Bn,idp:$n,accessGate:Qt,cronJobs:we.cronJobs,auditLog:we.auditLog,apiKeys:we.apiKeys,commandPalette:we.commandPalette,cookieConsent:we.cookieConsent,legalPages:we.legalPages,chinaCompliance:we.chinaCompliance};return{region:h,orm:P,database:E,databaseHost:l,payment:y,paymentChoice:U,auth:R,socialLoginIds:ve,aiMode:ue,aiRouting:oe,aiProviders:G,customAiEndpoint:q,deployTarget:I,deployTargets:Vn,docs:z,i18n:V,email:Re,storage:be,monitoring:Je,analytics:Qe,sms:_e,queue:b,search:D,cache:_,notifications:me,webhooks:Xt,cms:Mn,featureFlags:zt,captcha:Jt,mcp:Fn,metering:jn,billingMode:Bn,idp:$n,accessGate:Qt,waveToggles:we,previewSelections:gs,config:fs}}import{execFileSync as qt}from"child_process";import Ht from"fs";import ze from"path";import F from"picocolors";import pp from"update-notifier";import nt from"picocolors";function $s(){return!process.env.NO_COLOR&&!!process.stdout.isTTY}function po(e){let t=$s(),n=t?"\u2693":"-",o=t?"\u25B8":"->",r=e.packageManager??"pnpm",s=e.hasDatabase!==!1,i=e.targetDir==="."||e.targetDir==="./"||e.targetDir===".\\",a=i?".":e.targetDir.endsWith("/")?e.targetDir:`${e.targetDir}/`,c=t?nt.bold(`${n} Done in ${e.elapsedSec}s \xB7 ${a}`):`${n} Done in ${e.elapsedSec}s \xB7 ${a}`,p=g=>t?nt.dim(g):g,m=g=>t?nt.bold(g):g,d=["",` ${c}`,"",` ${m("Next:")}`];i||d.push(` ${o} cd ${e.targetDir}`),e.skippedInstall&&d.push(` ${o} ${r} install`),d.push(` ${o} fill .env.local ${p("\u2192 provider keys from .env.example")}`),s&&d.push(` ${o} ${r} infra:up ${p("\u2192 local Postgres (optional)")}`,` ${o} ${r} db:migrate ${p("\u2192 apply Prisma schema")}`),d.push(` ${o} ${r} dev ${p("\u2192 http://localhost:3000")}`,"",` ${p("More:")} ${p("nebutra doctor")} ${p("\xB7")} ${p("docs.nebutra.com")}`);let v=e.previewSelections??[];if(v.length>0){let g=t?nt.bold(nt.yellow("\u26A0 Preview / foundation providers:")):"!! Preview / foundation providers:";d.push("",` ${g}`);for(let h of v){let P=tt(h.status),E=` ${o} ${h.flag}=${h.provider} ${P}`;d.push(t?nt.yellow(E):E)}d.push(` ${p(Ne("foundation"))}`)}d.push(""),process.stdout.write(d.join(`
2
+ import{execSync as er}from"child_process";import ct from"fs";import me from"path";import*as lt from"@clack/prompts";import v from"picocolors";import{Command as dt}from"commander";import fe from"fs";import Q from"path";import{fileURLToPath as pt}from"url";function ut(){try{let t=Q.dirname(pt(import.meta.url));for(let n=0;n<4;n++){let r=Q.join(t,"package.json");if(fe.existsSync(r)){let s=fe.readFileSync(r,"utf-8"),o=JSON.parse(s);if(o.name==="create-sailor"&&typeof o.version=="string")return o.version}t=Q.dirname(t)}return"0.0.0-dev"}catch{return"0.0.0-dev"}}var x=ut();var mt="create-sailor";function ge(){let e=new dt;return e.name(mt).description("Nebutra-Sailor \u2014 AI-Native SaaS template").version(x,"-v, --version").helpOption(!1).argument("[name]","project name or path (default: my-app; use . for current directory)",void 0).option("-p, --pm <id>","npm | pnpm | yarn | bun").option("--no-install","skip package install").option("--no-git","skip git init").option("-y, --yes","non-interactive (use ./my-app when no name is given)").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}import{execFileSync as Z}from"child_process";import S from"picocolors";import Mn from"update-notifier";import ee from"picocolors";function ft(){return!process.env.NO_COLOR&&!!process.stdout.isTTY}function ye(e){let t=ft(),n=t?"\u2693":"-",r=t?"\u25B8":"->",s=e.packageManager??"pnpm",o=e.hasDatabase!==!1,i=e.targetDir==="."||e.targetDir==="./"||e.targetDir===".\\",c=i?".":e.targetDir.endsWith("/")?e.targetDir:`${e.targetDir}/`,a=t?ee.bold(`${n} Done in ${e.elapsedSec}s \xB7 ${c}`):`${n} Done in ${e.elapsedSec}s \xB7 ${c}`,l=y=>t?ee.dim(y):y,d=y=>t?ee.bold(y):y,u=["",` ${a}`,"",` ${d("Next:")}`];i||u.push(` ${r} cd ${e.targetDir}`),e.skippedInstall&&u.push(` ${r} ${s} install`),u.push(` ${r} nebutra status ${l("\u2192 what is live, what needs a key")}`),o&&u.push(` ${r} ${s} infra:up ${l("\u2192 local Postgres (optional)")}`,` ${r} ${s} db:migrate ${l("\u2192 apply Prisma schema")}`),u.push(` ${r} ${s} dev ${l("\u2192 http://localhost:3000")}`,"",` ${l("More:")} ${l("nebutra status")} ${l("\xB7")} ${l("nebutra.com/docs")}`),u.push(""),process.stdout.write(u.join(`
9
3
  `)+`
10
- `)}import ot from"fs";import K from"path";var lo=[{id:"posthog",name:"PostHog",region:"both",envVars:["POSTHOG_KEY","POSTHOG_HOST","NEXT_PUBLIC_POSTHOG_KEY","NEXT_PUBLIC_POSTHOG_HOST"],docs:"https://posthog.com/docs",pkgs:["posthog-js","posthog-node"],providerFile:"apps/web/src/lib/analytics/posthog.client.ts"},{id:"plausible",name:"Plausible",region:"global",envVars:["NEXT_PUBLIC_PLAUSIBLE_DOMAIN","NEXT_PUBLIC_PLAUSIBLE_SRC"],docs:"https://plausible.io/docs",providerFile:"apps/web/src/components/analytics/PlausibleScript.tsx"},{id:"umami",name:"Umami",region:"both",envVars:["NEXT_PUBLIC_UMAMI_WEBSITE_ID","NEXT_PUBLIC_UMAMI_SRC"],docs:"https://umami.is/docs",providerFile:"apps/web/src/components/analytics/UmamiScript.tsx"},{id:"mixpanel",name:"Mixpanel",region:"global",envVars:["MIXPANEL_TOKEN","NEXT_PUBLIC_MIXPANEL_TOKEN"],docs:"https://docs.mixpanel.com",pkgs:["mixpanel-browser","mixpanel"],providerFile:"apps/web/src/lib/analytics/mixpanel.client.ts"},{id:"baidu",name:"\u767E\u5EA6\u7EDF\u8BA1 Baidu Tongji",region:"cn",envVars:["NEXT_PUBLIC_BAIDU_STATS_ID"],docs:"https://tongji.baidu.com",providerFile:"apps/web/src/components/analytics/BaiduStatsScript.tsx"},{id:"sensors",name:"\u795E\u7B56 Sensors Analytics",region:"cn",envVars:["NEXT_PUBLIC_SENSORS_SERVER_URL","NEXT_PUBLIC_SENSORS_APP_JS_URL"],docs:"https://manual.sensorsdata.cn",pkgs:["sa-sdk-javascript"],providerFile:"apps/web/src/lib/analytics/sensors.client.ts"},{id:"growingio",name:"GrowingIO",region:"cn",envVars:["NEXT_PUBLIC_GROWINGIO_ACCOUNT_ID"],docs:"https://growingio.com/docs",providerFile:"apps/web/src/components/analytics/GrowingIoScript.tsx"},{id:"none",name:"None",region:"both",envVars:[],docs:""}],hl=lo.reduce((e,t)=>(e[t.region]||(e[t.region]=[]),e[t.region].push(t),e),{});function uo(e){return lo.find(t=>t.id===e)}function Vs(e){ot.mkdirSync(e,{recursive:!0})}function se(e,t){Vs(K.dirname(e)),ot.writeFileSync(e,t)}function Oe(e,t){let n=K.join(e,".env.example");ot.existsSync(n)?ot.appendFileSync(n,`
11
- `+t):ot.writeFileSync(n,t)}function Ks(e){let t=`"use client";
12
- /**
13
- * PostHog browser SDK \u2014 lazy-initialized on first access.
14
- */
15
- import posthog from "posthog-js";
16
-
17
- let initialized = false;
18
-
19
- export function initPostHog(): void {
20
- if (initialized || typeof window === "undefined") return;
21
- const key = process.env.NEXT_PUBLIC_POSTHOG_KEY;
22
- if (!key) return;
23
-
24
- posthog.init(key, {
25
- api_host:
26
- process.env.NEXT_PUBLIC_POSTHOG_HOST ?? "https://us.i.posthog.com",
27
- person_profiles: "identified_only",
28
- capture_pageview: true,
29
- capture_pageleave: true,
30
- autocapture: true,
31
- });
32
- initialized = true;
33
- }
34
-
35
- export { posthog };
36
- `,n=`import { PostHog } from "posthog-node";
37
-
38
- /**
39
- * PostHog server-side client. Remember to await client.shutdown() at the end
40
- * of serverless requests so events are flushed before the invocation dies.
41
- */
42
- let client: PostHog | null = null;
43
-
44
- export function getPostHogServerClient(): PostHog | null {
45
- if (client) return client;
46
- const key = process.env.POSTHOG_KEY ?? process.env.NEXT_PUBLIC_POSTHOG_KEY;
47
- if (!key) return null;
48
-
49
- client = new PostHog(key, {
50
- host: process.env.POSTHOG_HOST ?? process.env.NEXT_PUBLIC_POSTHOG_HOST ?? "https://us.i.posthog.com",
51
- flushAt: 1,
52
- flushInterval: 0,
53
- });
54
- return client;
55
- }
56
-
57
- export async function captureServerEvent(
58
- distinctId: string,
59
- event: string,
60
- properties?: Record<string, unknown>,
61
- ): Promise<void> {
62
- const ph = getPostHogServerClient();
63
- if (!ph) return;
64
- ph.capture({ distinctId, event, properties });
65
- await ph.shutdown();
66
- }
67
- `,o=`"use client";
68
- /**
69
- * <AnalyticsProvider /> \u2014 wrap the app root to enable PostHog + auto page views.
70
- */
71
- import { useEffect } from "react";
72
- import { usePathname, useSearchParams } from "next/navigation";
73
- import { initPostHog, posthog } from "../../lib/analytics/posthog.client";
74
-
75
- export function AnalyticsProvider({ children }: { children: React.ReactNode }) {
76
- const pathname = usePathname();
77
- const searchParams = useSearchParams();
78
-
79
- useEffect(() => {
80
- initPostHog();
81
- }, []);
82
-
83
- useEffect(() => {
84
- if (!pathname) return;
85
- const url =
86
- window.location.origin +
87
- pathname +
88
- (searchParams?.toString() ? \`?\${searchParams.toString()}\` : "");
89
- posthog.capture("$pageview", { $current_url: url });
90
- }, [pathname, searchParams]);
91
-
92
- return <>{children}</>;
93
- }
94
- `;se(K.join(e,"src","lib","analytics","posthog.client.ts"),t),se(K.join(e,"src","lib","analytics","posthog.server.ts"),n),se(K.join(e,"src","components","analytics","AnalyticsProvider.tsx"),o)}function Ys(){return`# PostHog \u2014 product analytics
95
- POSTHOG_KEY=
96
- POSTHOG_HOST=https://us.i.posthog.com
97
- NEXT_PUBLIC_POSTHOG_KEY=
98
- NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
99
- `}function Gs(e){se(K.join(e,"src","components","analytics","PlausibleScript.tsx"),`"use client";
100
- /**
101
- * Plausible script tag \u2014 lightweight, privacy-friendly analytics.
102
- */
103
- import Script from "next/script";
104
-
105
- export function PlausibleScript() {
106
- const domain = process.env.NEXT_PUBLIC_PLAUSIBLE_DOMAIN;
107
- const src =
108
- process.env.NEXT_PUBLIC_PLAUSIBLE_SRC ?? "https://plausible.io/js/script.js";
109
- if (!domain) return null;
110
- return (
111
- <Script
112
- id="plausible"
113
- strategy="afterInteractive"
114
- data-domain={domain}
115
- src={src}
116
- />
117
- );
118
- }
119
- `)}function qs(){return`# Plausible \u2014 privacy-friendly analytics
120
- NEXT_PUBLIC_PLAUSIBLE_DOMAIN=
121
- NEXT_PUBLIC_PLAUSIBLE_SRC=https://plausible.io/js/script.js
122
- `}function Hs(e){se(K.join(e,"src","components","analytics","UmamiScript.tsx"),`"use client";
123
- /**
124
- * Umami analytics \u2014 self-hosted OR umami.is cloud.
125
- */
126
- import Script from "next/script";
127
-
128
- export function UmamiScript() {
129
- const websiteId = process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID;
130
- const src =
131
- process.env.NEXT_PUBLIC_UMAMI_SRC ?? "https://cloud.umami.is/script.js";
132
- if (!websiteId) return null;
133
- return (
134
- <Script
135
- id="umami"
136
- strategy="afterInteractive"
137
- src={src}
138
- data-website-id={websiteId}
139
- />
140
- );
141
- }
142
- `)}function Ws(){return`# Umami \u2014 self-hostable analytics
143
- NEXT_PUBLIC_UMAMI_WEBSITE_ID=
144
- NEXT_PUBLIC_UMAMI_SRC=https://cloud.umami.is/script.js
145
- `}function Xs(e){let t=`"use client";
146
- import mixpanel from "mixpanel-browser";
147
-
148
- let initialized = false;
149
-
150
- export function initMixpanel(): void {
151
- if (initialized || typeof window === "undefined") return;
152
- const token = process.env.NEXT_PUBLIC_MIXPANEL_TOKEN;
153
- if (!token) return;
154
- mixpanel.init(token, {
155
- debug: process.env.NODE_ENV !== "production",
156
- track_pageview: true,
157
- persistence: "localStorage",
158
- });
159
- initialized = true;
160
- }
161
-
162
- export { mixpanel };
163
- `,n=`import Mixpanel from "mixpanel";
164
-
165
- let client: Mixpanel.Mixpanel | null = null;
166
-
167
- export function getMixpanelServerClient(): Mixpanel.Mixpanel | null {
168
- if (client) return client;
169
- const token = process.env.MIXPANEL_TOKEN ?? process.env.NEXT_PUBLIC_MIXPANEL_TOKEN;
170
- if (!token) return null;
171
- client = Mixpanel.init(token);
172
- return client;
173
- }
174
-
175
- export function trackServer(
176
- distinctId: string,
177
- event: string,
178
- properties?: Record<string, unknown>,
179
- ): void {
180
- const c = getMixpanelServerClient();
181
- if (!c) return;
182
- c.track(event, { distinct_id: distinctId, ...properties });
183
- }
184
- `;se(K.join(e,"src","lib","analytics","mixpanel.client.ts"),t),se(K.join(e,"src","lib","analytics","mixpanel.server.ts"),n)}function zs(){return`# Mixpanel \u2014 product analytics
185
- MIXPANEL_TOKEN=
186
- NEXT_PUBLIC_MIXPANEL_TOKEN=
187
- `}function Js(e){se(K.join(e,"src","components","analytics","BaiduStatsScript.tsx"),`"use client";
188
- /**
189
- * \u767E\u5EA6\u7EDF\u8BA1 Baidu Tongji \u2014 hm.js \u57CB\u70B9\u811A\u672C.
190
- */
191
- import Script from "next/script";
192
-
193
- export function BaiduStatsScript() {
194
- const id = process.env.NEXT_PUBLIC_BAIDU_STATS_ID;
195
- if (!id) return null;
196
- const inline = \`
197
- var _hmt = _hmt || [];
198
- (function() {
199
- var hm = document.createElement("script");
200
- hm.src = "https://hm.baidu.com/hm.js?\${id}";
201
- var s = document.getElementsByTagName("script")[0];
202
- s.parentNode.insertBefore(hm, s);
203
- })();
204
- \`;
205
- return (
206
- <Script
207
- id="baidu-stats"
208
- strategy="afterInteractive"
209
- dangerouslySetInnerHTML={{ __html: inline }}
210
- />
211
- );
212
- }
213
- `)}function Qs(){return`# \u767E\u5EA6\u7EDF\u8BA1 \u2014 \u4E2D\u56FD\u533A\u7F51\u7AD9\u5206\u6790
214
- NEXT_PUBLIC_BAIDU_STATS_ID=
215
- `}function Zs(e){se(K.join(e,"src","lib","analytics","sensors.client.ts"),`"use client";
216
- /**
217
- * \u795E\u7B56\u5206\u6790 (Sensors Analytics) \u2014 sa-sdk-javascript \u5C01\u88C5.
218
- */
219
- import sensors from "sa-sdk-javascript";
220
-
221
- let initialized = false;
222
-
223
- export function initSensors(): void {
224
- if (initialized || typeof window === "undefined") return;
225
- const serverUrl = process.env.NEXT_PUBLIC_SENSORS_SERVER_URL;
226
- if (!serverUrl) return;
227
-
228
- sensors.init({
229
- server_url: serverUrl,
230
- is_track_single_page: true,
231
- use_client_time: true,
232
- send_type: "beacon",
233
- heatmap: {
234
- clickmap: "default",
235
- scroll_notice_map: "default",
236
- },
237
- });
238
- sensors.quick("autoTrack");
239
- initialized = true;
240
- }
241
-
242
- export { sensors };
243
- `)}function ei(){return`# \u795E\u7B56\u5206\u6790 Sensors Analytics
244
- NEXT_PUBLIC_SENSORS_SERVER_URL=
245
- NEXT_PUBLIC_SENSORS_APP_JS_URL=
246
- `}function ti(e){se(K.join(e,"src","components","analytics","GrowingIoScript.tsx"),`"use client";
247
- /**
248
- * GrowingIO CDP \u2014 inline snippet installer.
249
- */
250
- import Script from "next/script";
251
-
252
- export function GrowingIoScript() {
253
- const accountId = process.env.NEXT_PUBLIC_GROWINGIO_ACCOUNT_ID;
254
- if (!accountId) return null;
255
- const inline = \`
256
- !(function(e,t,n,g,i){e[i]=e[i]||function(){(e[i].q=e[i].q||[]).push(arguments)};
257
- var a=t.createElement(n);a.async=!0;a.src=g;
258
- var s=t.getElementsByTagName(n)[0];s.parentNode.insertBefore(a,s);
259
- })(window,document,"script","https://assets.giocdn.com/vds.js","gdp");
260
- gdp("init", "\${accountId}", {});
261
- gdp("send");
262
- \`;
263
- return (
264
- <Script
265
- id="growingio"
266
- strategy="afterInteractive"
267
- dangerouslySetInnerHTML={{ __html: inline }}
268
- />
269
- );
270
- }
271
- `)}function ni(){return`# GrowingIO \u2014 \u5168\u57CB\u70B9 CDP (\u4E2D\u56FD\u533A)
272
- NEXT_PUBLIC_GROWINGIO_ACCOUNT_ID=
273
- `}async function mo(e,t,n="global"){let o=uo(t);if(!o||o.id==="none")return;let r=K.join(e,"apps","web");if(!ot.existsSync(r))return;let s=o.id;try{switch(s){case"posthog":Ks(r),Oe(e,Ys());return;case"plausible":Gs(r),Oe(e,qs());return;case"umami":Hs(r),Oe(e,Ws());return;case"mixpanel":Xs(r),Oe(e,zs());return;case"baidu":Js(r),Oe(e,Qs());return;case"sensors":Zs(r),Oe(e,ei());return;case"growingio":ti(r),Oe(e,ni());return;default:return}}catch(i){throw console.error(`Failed to apply analytics selection "${t}":`,i),new Error(`Analytics scaffold for "${t}" failed \u2014 see stderr for details.`)}}var oi={name:"Nebutra",nameCn:"\u4E91\u6BD3\u667A\u80FD",nameFull:"\u65E0\u9521\u4E91\u6BD3\u667A\u80FD\u79D1\u6280\u6709\u9650\u516C\u53F8",nameFullEn:"Wuxi Nebutra Intelligence Technology Co., Ltd.",tagline:"Ship AI products, not boilerplate.",taglineCn:"AI\u539F\u751F\xB7\u5FEB\u901F\u51FA\u6D77\xB7\u5373\u523B\u4EA4\u4ED8",description:"Production-ready Next.js monorepo template for AI SaaS products. Auth, billing, multi-tenancy, AI services, design system, and enterprise infrastructure \u2014 pre-configured.",descriptionCn:"\u9762\u5411AI\u521B\u4E1A\u8005\u7684\u4E00\u4F53\u5316SaaS\u57FA\u7840\u8BBE\u65BD\u6A21\u677F\uFF0C\u8986\u76D6\u8BA4\u8BC1\u3001\u8BA1\u8D39\u3001\u591A\u79DF\u6237\u3001AI\u670D\u52A1\u4E0E\u8BBE\u8BA1\u7CFB\u7EDF\uFF0C\u5F00\u7BB1\u5373\u4EA7\u54C1",story:{concept:"Logo\u4EE5\u9996\u5B57\u6BCDN\u7684\u57FA\u7840\u9020\u578B\u6982\u5FF5\u4E3A\u4E3B\u8981\u8BBE\u8BA1\u6846\u67B6\uFF0C\u901A\u8FC7\u51E0\u4F55\u6B63\u8D1F\u7A7A\u95F4\u6784\u5EFA\u9690\u5F62'N'\uFF0C\u5F62\u6210\u8FD1\u4F3C\u516D\u8FB9\u5F62\u7684\u7A33\u5B9A\u7ED3\u6784",colorMeaning:"\u84DD\u7EFF\u6E10\u53D8\u4F53\u73B0\u672A\u6765\u611F\u4E0E\u79D1\u6280\u950B\u8292\uFF0C'\u4E91'\u4EE3\u8868\u4E91\u7AEF\u5E73\u53F0\uFF0C'\u6BD3'\u5BD3\u610F\u5B55\u80B2\u4E0E\u8F6C\u5316",values:["AI Native","Ship Fast","Open by Default","Global-Ready","Enterprise-Grade"],missionStatement:"Help AI founders and SaaS teams go from idea to production 10x faster by providing the infrastructure layer they shouldn't have to build."},domains:{landing:"nebutra.com",app:"app.nebutra.com",api:"api.nebutra.com",auth:"auth.nebutra.com",sso:"sso.nebutra.com",docs:"docs.nebutra.com",studio:"studio.nebutra.com",cdn:"cdn.nebutra.com",router:"router.nebutra.com",forge:"forge.nebutra.com",design:"design.nebutra.com",status:"status.nebutra.com",open:"open.nebutra.com",admin:"admin.nebutra.com",analytics:"analytics.nebutra.com",pebble:"pebble.nebutra.com",carina:"carina.nebutra.com",kuanlan:"kuanlan.nebutra.com",origin:"origin.nebutra.com"},social:{twitter:"https://twitter.com/nebutra",github:"https://github.com/nebutra",discord:"https://discord.gg/nebutra",linkedin:"https://linkedin.com/company/nebutra"}};function go(e){let t=oi.domains[e];if(!t?.trim())throw new Error(`brand.domains.${String(e)} is empty \u2014 run brand:apply`);return`https://${t.replace(/^https?:\/\//,"").replace(/\/+$/,"")}`}var ri=go("analytics");function si(e={}){if(e.noTelemetry===!0)return!0;let t=process.env.NEBUTRA_TELEMETRY;return t==="0"||t==="false"}function fo(e,t={}){si(t)||(async()=>{try{let n=await import("@nebutra/analytics");if(typeof n.createProductAnalyticsClient!="function")return;let o=n.createProductAnalyticsClient({posthog:{apiKey:process.env.POSTHOG_KEY??process.env.NEXT_PUBLIC_POSTHOG_KEY??process.env.NEBUTRA_POSTHOG_KEY??"",host:process.env.POSTHOG_HOST??process.env.NEXT_PUBLIC_POSTHOG_HOST??process.env.NEBUTRA_POSTHOG_HOST??ri},onError:()=>{}});if(typeof o?.track!="function")return;let r=o.track("scaffold.completed",e);r&&typeof r.then=="function"&&await r.catch(()=>{})}catch{}})()}import X from"fs";import N from"path";function Y(e){X.existsSync(e)&&X.rmSync(e,{recursive:!0,force:!0})}function kt(e,t){let n=N.join(e,".env.example");X.existsSync(n)?X.appendFileSync(n,`
274
- `+t):X.writeFileSync(n,t)}function Nt(e,t){if(!X.existsSync(e))return;let n=X.readFileSync(e,"utf8"),o=n.replace(/export type AuthProviderId\s*=\s*(?:"[^"]+"\s*\|\s*)+"[^"]+";/,`export type AuthProviderId = "${t}";`);o!==n&&X.writeFileSync(e,o)}function ii(e,t){let n=N.join(e,"README.md");if(!X.existsSync(n)){X.writeFileSync(n,t);return}X.appendFileSync(n,`
275
- `+t)}async function yo(e,t){let n=N.join(e,"packages","iam","auth");if(!X.existsSync(n))return;if(t==="none"){Y(n),ii(e,`
276
- ## Auth
277
-
278
- Auth was skipped at scaffold time (\`--auth=none\`). Add your preferred auth provider manually (e.g. Clerk, Better Auth, NextAuth, Supabase).
279
- `);return}let o=N.join(n,"src","providers"),r=N.join(n,"src","types.ts");if(t==="clerk"){Y(N.join(o,"better-auth.ts")),Y(N.join(o,"nextauth.ts")),Y(N.join(o,"supabase.ts")),Nt(r,"clerk"),kt(e,`# Clerk auth
280
- CLERK_PUBLISHABLE_KEY=
281
- CLERK_SECRET_KEY=
282
- `);return}if(t==="betterauth"){Y(N.join(o,"clerk.ts")),Y(N.join(o,"nextauth.ts")),Y(N.join(o,"supabase.ts")),Nt(r,"better-auth"),kt(e,`# Better Auth
283
- BETTER_AUTH_SECRET=
284
- BETTER_AUTH_URL=http://localhost:3000
285
- `);return}if(t==="nextauth"){Y(N.join(o,"clerk.ts")),Y(N.join(o,"better-auth.ts")),Y(N.join(o,"supabase.ts")),Nt(r,"nextauth"),kt(e,`# NextAuth (Auth.js v5)
286
- AUTH_SECRET=
287
- NEXTAUTH_URL=http://localhost:3000
288
- # OAuth (optional \u2014 leave blank to disable that provider)
289
- GOOGLE_CLIENT_ID=
290
- GOOGLE_CLIENT_SECRET=
291
- GITHUB_CLIENT_ID=
292
- GITHUB_CLIENT_SECRET=
293
- `);return}if(t==="supabase"){Y(N.join(o,"clerk.ts")),Y(N.join(o,"better-auth.ts")),Y(N.join(o,"nextauth.ts")),Nt(r,"supabase"),kt(e,`# Supabase Auth
294
- NEXT_PUBLIC_SUPABASE_URL=
295
- NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=
296
- NEXT_PUBLIC_SUPABASE_ANON_KEY=
297
- SUPABASE_URL=
298
- SUPABASE_PUBLISHABLE_KEY=
299
- SUPABASE_ANON_KEY=
300
- SUPABASE_SERVICE_ROLE_KEY=
301
- `);return}}import Ue from"fs";import Le from"path";function ai(e,t){let n=`// apps/web/src/app/api/auth/callback/${e.id}/route.ts
302
- // Generated by create-sailor \u2014 OAuth callback for ${e.name}
303
- // Primary auth provider: ${t}
304
- // Docs: ${e.docs}`,o={wechat:` const tokenRes = await fetch(
305
- "${e.tokenEndpoint}?" +
306
- new URLSearchParams({
307
- appid: process.env.WECHAT_APP_ID ?? "",
308
- secret: process.env.WECHAT_APP_SECRET ?? "",
309
- code,
310
- grant_type: "authorization_code",
311
- }),
312
- );
313
- const token = (await tokenRes.json()) as {
314
- access_token?: string;
315
- openid?: string;
316
- errcode?: number;
317
- errmsg?: string;
318
- };
319
- if (!token.access_token || !token.openid) {
320
- return NextResponse.redirect(new URL("/login?error=wechat_token", req.url));
321
- }`,qq:` const tokenRes = await fetch(
322
- "${e.tokenEndpoint}?" +
323
- new URLSearchParams({
324
- client_id: process.env.QQ_APP_ID ?? "",
325
- client_secret: process.env.QQ_APP_KEY ?? "",
326
- code,
327
- grant_type: "authorization_code",
328
- redirect_uri: new URL("${e.callbackUrl}", req.url).toString(),
329
- fmt: "json",
330
- }),
331
- );
332
- const token = (await tokenRes.json()) as { access_token?: string; openid?: string };
333
- if (!token.access_token) {
334
- return NextResponse.redirect(new URL("/login?error=qq_token", req.url));
335
- }`,dingtalk:` const tokenRes = await fetch("${e.tokenEndpoint}", {
336
- method: "POST",
337
- headers: { "Content-Type": "application/json" },
338
- body: JSON.stringify({
339
- clientId: process.env.DINGTALK_APP_ID ?? "",
340
- clientSecret: process.env.DINGTALK_APP_SECRET ?? "",
341
- code,
342
- grantType: "authorization_code",
343
- }),
344
- });
345
- const token = (await tokenRes.json()) as { accessToken?: string };
346
- if (!token.accessToken) {
347
- return NextResponse.redirect(new URL("/login?error=dingtalk_token", req.url));
348
- }`,workweixin:` // Enterprise WeChat returns an access_token by pairing corpId + secret
349
- const tokenRes = await fetch(
350
- "${e.tokenEndpoint}?" +
351
- new URLSearchParams({
352
- corpid: process.env.WORKWEIXIN_CORP_ID ?? "",
353
- corpsecret: process.env.WORKWEIXIN_SECRET ?? "",
354
- }),
355
- );
356
- const token = (await tokenRes.json()) as {
357
- access_token?: string;
358
- errcode?: number;
359
- errmsg?: string;
360
- };
361
- if (!token.access_token) {
362
- return NextResponse.redirect(new URL("/login?error=workweixin_token", req.url));
363
- }`,feishu:` // Step 1: app_access_token
364
- const appTokenRes = await fetch(
365
- "https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal",
366
- {
367
- method: "POST",
368
- headers: { "Content-Type": "application/json" },
369
- body: JSON.stringify({
370
- app_id: process.env.FEISHU_APP_ID ?? "",
371
- app_secret: process.env.FEISHU_APP_SECRET ?? "",
372
- }),
373
- },
374
- );
375
- const appToken = (await appTokenRes.json()) as { app_access_token?: string };
376
- if (!appToken.app_access_token) {
377
- return NextResponse.redirect(new URL("/login?error=feishu_app_token", req.url));
378
- }
379
- // Step 2: user access_token
380
- const tokenRes = await fetch("${e.tokenEndpoint}", {
381
- method: "POST",
382
- headers: {
383
- "Content-Type": "application/json",
384
- Authorization: \`Bearer \${appToken.app_access_token}\`,
385
- },
386
- body: JSON.stringify({ grant_type: "authorization_code", code }),
387
- });
388
- const token = (await tokenRes.json()) as { data?: { access_token?: string } };
389
- if (!token.data?.access_token) {
390
- return NextResponse.redirect(new URL("/login?error=feishu_token", req.url));
391
- }`,weibo:` const tokenRes = await fetch("${e.tokenEndpoint}", {
392
- method: "POST",
393
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
394
- body: new URLSearchParams({
395
- client_id: process.env.WEIBO_APP_KEY ?? "",
396
- client_secret: process.env.WEIBO_APP_SECRET ?? "",
397
- grant_type: "authorization_code",
398
- code,
399
- redirect_uri: new URL("${e.callbackUrl}", req.url).toString(),
400
- }),
401
- });
402
- const token = (await tokenRes.json()) as { access_token?: string; uid?: string };
403
- if (!token.access_token) {
404
- return NextResponse.redirect(new URL("/login?error=weibo_token", req.url));
405
- }`};return`${n}
406
- import { type NextRequest, NextResponse } from "next/server";
407
-
408
- export async function GET(req: NextRequest) {
409
- const code = req.nextUrl.searchParams.get("code");
410
- const state = req.nextUrl.searchParams.get("state");
411
-
412
- if (!code) {
413
- return NextResponse.redirect(new URL("/login?error=no_code", req.url));
414
- }
415
-
416
- ${o[e.id]}
417
-
418
- // SAMPLE (intentional): fetch user profile from ${e.name} with the
419
- // access token, then upsert into primary auth (${t}) \u2014
420
- // e.g. clerk.users.createUser(...) or betterAuth.signIn.oauth(...).
421
- // Persist a session cookie before redirecting.
422
-
423
- void state;
424
- return NextResponse.redirect(new URL("/dashboard", req.url));
425
- }
426
- `}function ci(e){return`"use client";
427
-
428
- /**
429
- * SocialLoginButtons \u2014 generated by create-sailor.
430
- *
431
- * Renders a button per selected China-region OAuth provider. Clicking a
432
- * button navigates to the provider's authorization URL (\`/api/auth/oauth/<id>\`),
433
- * which the app is expected to implement and redirect to the upstream consent
434
- * page. The OAuth callback lives at \`/api/auth/callback/<id>\`.
435
- */
436
-
437
- import { Button } from "@nebutra/ui/primitives";
438
-
439
- interface SocialProvider {
440
- id: string;
441
- name: string;
442
- href: string;
443
- }
444
-
445
- const PROVIDERS: SocialProvider[] = [
446
- ${e.map(n=>` { id: ${JSON.stringify(n.id)}, name: ${JSON.stringify(n.name)}, href: ${JSON.stringify(`/api/auth/oauth/${n.id}`)} }`).join(`,
447
- `)},
448
- ];
449
-
450
- export function SocialLoginButtons() {
451
- return (
452
- <div className="space-y-2">
453
- {PROVIDERS.map((p) => (
454
- <Button
455
- key={p.id}
456
- type="button"
457
- variant="outline"
458
- className="w-full"
459
- onClick={() => {
460
- window.location.href = p.href;
461
- }}
462
- >
463
- Sign in with {p.name}
464
- </Button>
465
- ))}
466
- </div>
467
- );
468
- }
469
- `}function So(e){Ue.mkdirSync(e,{recursive:!0})}function pi(e,t){let n=Le.join(e,".env.example");Ue.existsSync(n)?Ue.appendFileSync(n,`
470
- `+t):Ue.writeFileSync(n,t)}function li(e){let t=`# ${e.name} (${e.id}) \u2014 ${e.docs}`,n=e.envVars.map(o=>`${o}=`).join(`
471
- `);return`${t}
472
- ${n}
473
- `}async function ho(e,t,n){if(!t||t.length===0)return;let o=Le.join(e,"apps","web");if(!Ue.existsSync(o))return;let r=new Set,s=[];for(let p of t){if(r.has(p))continue;r.add(p);let m=Zn(p);m&&s.push(m)}if(s.length===0)return;for(let p of s)pi(e,li(p));let i=Le.join(o,"src","app","api","auth","callback");for(let p of s){let m=Le.join(i,p.id);So(m);let d=Le.join(m,"route.ts");Ue.writeFileSync(d,ai(p,n))}let a=Le.join(o,"src","components","auth");So(a);let c=Le.join(a,"SocialLoginButtons.tsx");Ue.writeFileSync(c,ci(s))}import rt from"fs";import vo from"path";var Ot=[{id:"upstash-redis",name:"Upstash Redis",region:"both",envVars:["UPSTASH_REDIS_REST_URL","UPSTASH_REDIS_REST_TOKEN"],docs:"https://upstash.com/docs/redis",description:"Serverless Redis"},{id:"vercel-kv",name:"Vercel KV",region:"global",envVars:["KV_URL","KV_REST_API_URL","KV_REST_API_TOKEN","KV_REST_API_READ_ONLY_TOKEN"],docs:"https://vercel.com/docs/storage/vercel-kv",description:"Vercel native KV"},{id:"redis",name:"Redis (self-hosted)",region:"both",envVars:["REDIS_URL"],docs:"https://redis.io/docs/",description:"Self-hosted Redis"},{id:"dragonfly",name:"Dragonfly",region:"global",envVars:["DRAGONFLY_URL"],docs:"https://www.dragonflydb.io/docs",description:"Drop-in Redis replacement, faster"},{id:"none",name:"None",region:"both",envVars:[],docs:"",description:"Skip cache package"}];function Eo(e){return Ot.find(t=>t.id===e)}var Ll=Ot.reduce((e,t)=>(e[t.region]||(e[t.region]=[]),e[t.region].push(t),e),{});import ie from"fs";import Lt from"path";function ae(e,t){let n=Lt.join(e,".env.example");if(!ie.existsSync(n)||t.envVars.length===0)return;let r=`# ${t.category??t.name}: ${t.name}`;if(ie.readFileSync(n,"utf-8").includes(r))return;let a=["","# =============================================",r,t.docs?`# Docs: ${t.docs}`:null,"# =============================================",...t.envVars.map(c=>`${c}=""`),""].filter(c=>c!==null).join(`
474
- `);ie.appendFileSync(n,a)}function ce(e,t,n){let o=Lt.join(e,".env.example");if(!ie.existsSync(o))return;let r=ie.readFileSync(o,"utf-8"),s=`${t}="${n}"`,i=new RegExp(`^${t}=.*$`,"m");if(i.test(r)){ie.writeFileSync(o,r.replace(i,s));return}let a=r.endsWith(`
475
- `)?"":`
476
- `;ie.appendFileSync(o,`${a}${s}
477
- `)}var di=["design","iam","commerce","integrations","platform","ops","ai"];function De(e,t){for(let o of di){let r=Lt.join(e,"packages",o,t);if(ie.existsSync(r))return r}let n=Lt.join(e,"packages",t);return ie.existsSync(n)?n:null}function Z(e,t){let n=De(e,t);n&&ie.rmSync(n,{recursive:!0,force:!0})}function bo(e){return rt.existsSync(e)?rt.readFileSync(e,"utf-8"):null}function ui(e,t){let n=vo.join(e,".env.example"),o=bo(n);if(o===null)return;let r=`# Cache: ${t.name}`;if(o.includes(r))return;let s=["","# =============================================",r,`# Docs: ${t.docs}`,"# =============================================",...t.envVars.map(i=>`${i}=""`),""].join(`
478
- `);rt.appendFileSync(n,s)}function mi(e,t,n){let o=vo.join(e,".env.example"),r=bo(o);if(r===null)return;let s=new RegExp(`^${t}=.*$`,"m");s.test(r)?rt.writeFileSync(o,r.replace(s,`${t}="${n}"`)):rt.appendFileSync(o,`${t}="${n}"
479
- `)}async function _o(e,t,n){let o=De(e,"cache");if(t==="none"){o&&rt.rmSync(o,{recursive:!0,force:!0});return}let r=Eo(t);if(!r)throw new Error(`Unknown cache provider "${t}". Valid ids: ${Ot.map(s=>s.id).join(", ")}`);o&&(ui(e,r),mi(e,"CACHE_PROVIDER",t))}var Ut=[{id:"turnstile",name:"Cloudflare Turnstile",region:"global",envVars:["NEXT_PUBLIC_TURNSTILE_SITE_KEY","TURNSTILE_SECRET_KEY"],docs:"https://developers.cloudflare.com/turnstile/",description:"Free, privacy-focused captcha alternative"},{id:"hcaptcha",name:"hCaptcha",region:"global",envVars:["NEXT_PUBLIC_HCAPTCHA_SITE_KEY","HCAPTCHA_SECRET_KEY"],docs:"https://docs.hcaptcha.com/",description:"Privacy-respecting captcha"},{id:"aliyun-slide",name:"\u963F\u91CC\u4E91\u9A8C\u8BC1\u7801 2.0",region:"cn",envVars:["ALIYUN_CAPTCHA_ACCESS_KEY_ID","ALIYUN_CAPTCHA_ACCESS_KEY_SECRET","ALIYUN_CAPTCHA_APP_KEY","NEXT_PUBLIC_ALIYUN_CAPTCHA_APP_KEY"],docs:"https://help.aliyun.com/product/28308.html",description:"\u963F\u91CC\u4E91\u667A\u80FD\u9A8C\u8BC1 (\u6ED1\u5757/\u65E0\u75D5)"},{id:"none",name:"None",region:"both",envVars:[],docs:"",description:""}];function wo(e){return Ut.find(t=>t.id===e)}var Yl=Ut.reduce((e,t)=>(e[t.region]||(e[t.region]=[]),e[t.region].push(t),e),{});async function Po(e,t,n="global"){let o=wo(t);if(!o)throw new Error(`Unknown captcha provider "${t}". Valid ids: ${Ut.map(r=>r.id).join(", ")}`);if(o.id==="none"){Z(e,"captcha");return}o.envVars.length>0&&ae(e,{category:"Captcha",name:o.name,envVars:o.envVars,docs:o.docs}),ce(e,"CAPTCHA_PROVIDER",o.id)}import Mt from"fs";import gi from"path";var Dt=[{id:"sanity",name:"Sanity",region:"global",envVars:["SANITY_PROJECT_ID","SANITY_DATASET","SANITY_API_TOKEN","SANITY_API_VERSION"],docs:"https://www.sanity.io/docs",description:"Structured content platform with Studio"},{id:"contentful",name:"Contentful",region:"global",envVars:["CONTENTFUL_SPACE_ID","CONTENTFUL_ACCESS_TOKEN","CONTENTFUL_PREVIEW_ACCESS_TOKEN"],docs:"https://www.contentful.com/developers/docs/",description:"Headless CMS"},{id:"strapi",name:"Strapi",region:"both",envVars:["STRAPI_URL","STRAPI_API_TOKEN"],docs:"https://docs.strapi.io",description:"Open-source self-hosted CMS"},{id:"none",name:"None",region:"both",envVars:[],docs:"",description:""}];function Ao(e){return Dt.find(t=>t.id===e)}var Xl=Dt.reduce((e,t)=>(e[t.region]||(e[t.region]=[]),e[t.region].push(t),e),{});async function Co(e,t,n="global"){let o=Ao(t);if(!o)throw new Error(`Unknown CMS provider "${t}". Valid ids: ${Dt.map(s=>s.id).join(", ")}`);let r=gi.join(e,"apps","studio");if(o.id==="none"){Z(e,"sanity"),Mt.existsSync(r)&&Mt.rmSync(r,{recursive:!0,force:!0});return}o.id!=="sanity"&&(Z(e,"sanity"),Mt.existsSync(r)&&Mt.rmSync(r,{recursive:!0,force:!0})),o.envVars.length>0&&ae(e,{category:"CMS",name:o.name,envVars:o.envVars,docs:o.docs}),ce(e,"CMS_PROVIDER",o.id)}import M from"fs";import O from"path";var fi=O.join("apps","web"),fn=O.join("src","components","compliance"),Et=O.join("src","app","[locale]");function pe(e){return O.join(e,fi)}function yi(e){M.mkdirSync(e,{recursive:!0})}function ee(e,t){yi(O.dirname(e)),M.writeFileSync(e,t,"utf8")}function yn(e,t){let n=O.join(e,".env.example");if(M.existsSync(n)){if(M.readFileSync(n,"utf8").includes(t.split(`
480
- `)[0]??""))return;M.appendFileSync(n,`
4
+ `)}var gt={name:"Nebutra",nameCn:"\u4E91\u6BD3\u667A\u80FD",nameFull:"\u65E0\u9521\u4E91\u6BD3\u667A\u80FD\u79D1\u6280\u6709\u9650\u516C\u53F8",nameFullEn:"Wuxi Nebutra Intelligence Technology Co., Ltd.",tagline:"Ship AI products, not boilerplate.",taglineCn:"AI\u539F\u751F\xB7\u5FEB\u901F\u51FA\u6D77\xB7\u5373\u523B\u4EA4\u4ED8",description:"Production-ready Next.js monorepo template for AI SaaS products. Auth, billing, multi-tenancy, AI services, design system, and enterprise infrastructure \u2014 pre-configured.",descriptionCn:"\u9762\u5411AI\u521B\u4E1A\u8005\u7684\u4E00\u4F53\u5316SaaS\u57FA\u7840\u8BBE\u65BD\u6A21\u677F\uFF0C\u8986\u76D6\u8BA4\u8BC1\u3001\u8BA1\u8D39\u3001\u591A\u79DF\u6237\u3001AI\u670D\u52A1\u4E0E\u8BBE\u8BA1\u7CFB\u7EDF\uFF0C\u5F00\u7BB1\u5373\u4EA7\u54C1",story:{concept:"Logo\u4EE5\u9996\u5B57\u6BCDN\u7684\u57FA\u7840\u9020\u578B\u6982\u5FF5\u4E3A\u4E3B\u8981\u8BBE\u8BA1\u6846\u67B6\uFF0C\u901A\u8FC7\u51E0\u4F55\u6B63\u8D1F\u7A7A\u95F4\u6784\u5EFA\u9690\u5F62'N'\uFF0C\u5F62\u6210\u8FD1\u4F3C\u516D\u8FB9\u5F62\u7684\u7A33\u5B9A\u7ED3\u6784",colorMeaning:"\u84DD\u7EFF\u6E10\u53D8\u4F53\u73B0\u672A\u6765\u611F\u4E0E\u79D1\u6280\u950B\u8292\uFF0C'\u4E91'\u4EE3\u8868\u4E91\u7AEF\u5E73\u53F0\uFF0C'\u6BD3'\u5BD3\u610F\u5B55\u80B2\u4E0E\u8F6C\u5316",values:["AI Native","Ship Fast","Open by Default","Global-Ready","Enterprise-Grade"],missionStatement:"Help AI founders and SaaS teams go from idea to production 10x faster by providing the infrastructure layer they shouldn't have to build."},domains:{landing:"nebutra.com",app:"app.nebutra.com",api:"api.nebutra.com",auth:"auth.nebutra.com",sso:"sso.nebutra.com",studio:"studio.nebutra.com",cdn:"cdn.nebutra.com",router:"router.nebutra.com",forge:"forge.nebutra.com",design:"design.nebutra.com",status:"status.nebutra.com",open:"open.nebutra.com",admin:"admin.nebutra.com",analytics:"analytics.nebutra.com",pebble:"pebble.nebutra.com",carina:"carina.nebutra.com",kuanlan:"kuanlan.nebutra.com",origin:"origin.nebutra.com"},social:{twitter:"https://twitter.com/nebutra",github:"https://github.com/nebutra",discord:"https://discord.gg/nebutra",linkedin:"https://linkedin.com/company/nebutra"}};function he(e){let t=gt.domains[e];if(!t?.trim())throw new Error(`brand.domains.${String(e)} is empty \u2014 run brand:apply`);return`https://${t.replace(/^https?:\/\//,"").replace(/\/+$/,"")}`}var yt=he("analytics");function ht(e={}){if(e.noTelemetry===!0)return!0;let t=process.env.NEBUTRA_TELEMETRY;return t==="0"||t==="false"}function be(e,t={}){ht(t)||(async()=>{try{let n=await import("@nebutra/analytics");if(typeof n.createProductAnalyticsClient!="function")return;let r=n.createProductAnalyticsClient({posthog:{apiKey:process.env.POSTHOG_KEY??process.env.NEXT_PUBLIC_POSTHOG_KEY??process.env.NEBUTRA_POSTHOG_KEY??"",host:process.env.POSTHOG_HOST??process.env.NEXT_PUBLIC_POSTHOG_HOST??process.env.NEBUTRA_POSTHOG_HOST??yt},onError:()=>{}});if(typeof r?.track!="function")return;let s=r.track("scaffold.completed",e);s&&typeof s.then=="function"&&await s.catch(()=>{})}catch{}})()}import w from"fs";import h from"path";var bt=h.join("apps","web"),te=h.join("src","components","compliance"),H=h.join("src","app","[locale]");function P(e){return h.join(e,bt)}function Et(e){w.mkdirSync(e,{recursive:!0})}function T(e,t){Et(h.dirname(e)),w.writeFileSync(e,t,"utf8")}function ne(e,t){let n=h.join(e,".env.example");if(w.existsSync(n)){if(w.readFileSync(n,"utf8").includes(t.split(`
5
+ `)[0]??""))return;w.appendFileSync(n,`
481
6
  `+t+`
482
- `)}else M.writeFileSync(n,t+`
483
- `)}var vt=`> **TEMPLATE \u2014 NOT LEGAL ADVICE.** This document is a boilerplate scaffold only.
7
+ `)}else w.writeFileSync(n,t+`
8
+ `)}var W=`> **TEMPLATE \u2014 NOT LEGAL ADVICE.** This document is a boilerplate scaffold only.
484
9
  > Consult a qualified lawyer in every jurisdiction you operate in before publishing.
485
- > \u672C\u6587\u6863\u4E3A\u6A21\u677F\u811A\u624B\u67B6\uFF0C\u4E0D\u6784\u6210\u6CD5\u5F8B\u610F\u89C1\u3002\u53D1\u5E03\u524D\u8BF7\u54A8\u8BE2\u76F8\u5173\u53F8\u6CD5\u8F96\u533A\u7684\u5408\u683C\u5F8B\u5E08\u3002`,Si=`import Link from "next/link";
10
+ > \u672C\u6587\u6863\u4E3A\u6A21\u677F\u811A\u624B\u67B6\uFF0C\u4E0D\u6784\u6210\u6CD5\u5F8B\u610F\u89C1\u3002\u53D1\u5E03\u524D\u8BF7\u54A8\u8BE2\u76F8\u5173\u53F8\u6CD5\u8F96\u533A\u7684\u5408\u683C\u5F8B\u5E08\u3002`,wt=`import Link from "next/link";
486
11
  import { IcpFooter } from "@/components/compliance/IcpFooter";
487
12
  import { PublicSecurityFooter } from "@/components/compliance/PublicSecurityFooter";
488
13
 
@@ -533,7 +58,7 @@ export function Footer({ region = "global" }: FooterProps) {
533
58
  </footer>
534
59
  );
535
60
  }
536
- `,hi=`export function IcpFooter() {
61
+ `,vt=`export function IcpFooter() {
537
62
  const icp = process.env.NEXT_PUBLIC_ICP_BEIAN;
538
63
  if (!icp) return null;
539
64
  return (
@@ -547,7 +72,7 @@ export function Footer({ region = "global" }: FooterProps) {
547
72
  </a>
548
73
  );
549
74
  }
550
- `,Ei=`export function PublicSecurityFooter() {
75
+ `,St=`export function PublicSecurityFooter() {
551
76
  const psb = process.env.NEXT_PUBLIC_PSB_BEIAN;
552
77
  const psbCode = process.env.NEXT_PUBLIC_PSB_CODE;
553
78
  if (!psb) return null;
@@ -564,7 +89,7 @@ export function Footer({ region = "global" }: FooterProps) {
564
89
  </a>
565
90
  );
566
91
  }
567
- `,vi=`"use client";
92
+ `,Ct=`"use client";
568
93
 
569
94
  import { useEffect, useState } from "react";
570
95
 
@@ -718,7 +243,7 @@ export function CookieBanner() {
718
243
  </div>
719
244
  );
720
245
  }
721
- `,bi=`interface AigcDisclaimerProps {
246
+ `,At=`interface AigcDisclaimerProps {
722
247
  className?: string;
723
248
  }
724
249
 
@@ -738,13 +263,13 @@ export function AigcDisclaimer({ className }: AigcDisclaimerProps) {
738
263
  </div>
739
264
  );
740
265
  }
741
- `,_i=`---
266
+ `,xt=`---
742
267
  title: Privacy Policy / \u9690\u79C1\u653F\u7B56
743
268
  description: How {{COMPANY_NAME}} collects, uses, shares, and protects personal information.
744
269
  lastUpdated: 2026-01-01
745
270
  ---
746
271
 
747
- ${vt}
272
+ ${W}
748
273
 
749
274
  # Privacy Policy / \u9690\u79C1\u653F\u7B56
750
275
 
@@ -883,13 +408,13 @@ or in-app notification before the change takes effect.
883
408
  **Data Controller / \u6570\u636E\u63A7\u5236\u8005:** {{COMPANY_NAME}}
884
409
  **Address / \u5730\u5740:** {{COMPANY_ADDRESS}}
885
410
  **Email / \u90AE\u7BB1:** {{COMPANY_CONTACT_EMAIL}}
886
- `,wi=`---
411
+ `,Tt=`---
887
412
  title: Terms of Service / \u670D\u52A1\u6761\u6B3E
888
413
  description: Terms governing your use of {{COMPANY_NAME}}.
889
414
  lastUpdated: 2026-01-01
890
415
  ---
891
416
 
892
- ${vt}
417
+ ${W}
893
418
 
894
419
  # Terms of Service / \u670D\u52A1\u6761\u6B3E
895
420
 
@@ -1002,13 +527,13 @@ conflict-of-laws principles. Disputes shall be resolved in the competent courts
1002
527
  ## 11. Contact / \u8054\u7CFB\u65B9\u5F0F
1003
528
 
1004
529
  {{COMPANY_NAME}} \xB7 {{COMPANY_ADDRESS}} \xB7 {{COMPANY_CONTACT_EMAIL}}
1005
- `,Pi=`---
530
+ `,_t=`---
1006
531
  title: AIGC Disclosure / \u751F\u6210\u5F0F AI \u670D\u52A1\u8BF4\u660E
1007
532
  description: Disclosure required by \u7F51\u4FE1\u529E\u300A\u751F\u6210\u5F0F\u4EBA\u5DE5\u667A\u80FD\u670D\u52A1\u7BA1\u7406\u6682\u884C\u529E\u6CD5\u300B
1008
533
  lastUpdated: 2026-01-01
1009
534
  ---
1010
535
 
1011
- ${vt}
536
+ ${W}
1012
537
 
1013
538
  # AIGC Disclosure / \u751F\u6210\u5F0F AI \u670D\u52A1\u8BF4\u660E
1014
539
 
@@ -1084,13 +609,13 @@ human professional.
1084
609
 
1085
610
  AI \u8F93\u51FA\u53EF\u80FD\u4E0D\u51C6\u786E\u3001\u5B58\u5728\u504F\u89C1\u6216\u8FC7\u65F6\u3002**\u8BF7\u52FF**\u5728\u672A\u7ECF\u5408\u683C\u4E13\u4E1A\u4EBA\u58EB\u72EC\u7ACB\u6838\u5B9E\u7684\u60C5\u51B5\u4E0B\uFF0C\u5C06 AI \u8F93\u51FA
1086
611
  \u7528\u4E8E\u533B\u7597\u3001\u6CD5\u5F8B\u3001\u91D1\u878D\u6216\u5B89\u5168\u6538\u5173\u7684\u51B3\u7B56\u3002
1087
- `,Ai=`---
612
+ `,Nt=`---
1088
613
  title: Cross-Border Data Notice / \u6570\u636E\u51FA\u5883\u544A\u77E5
1089
614
  description: Notice of cross-border transfer of personal information, per PIPL.
1090
615
  lastUpdated: 2026-01-01
1091
616
  ---
1092
617
 
1093
- ${vt}
618
+ ${W}
1094
619
 
1095
620
  # Cross-Border Data Notice / \u6570\u636E\u51FA\u5883\u544A\u77E5
1096
621
 
@@ -1149,13 +674,13 @@ Refusal may limit your ability to use features that rely on overseas services.
1149
674
  - \u4F20\u8F93\u4E2D\u52A0\u5BC6 (TLS 1.2+) \u53CA\u9759\u6001\u52A0\u5BC6 (AES-256)\u3002
1150
675
  - \u5408\u540C\u6761\u6B3E\u8981\u6C42\u5883\u5916\u63A5\u6536\u65B9\u63D0\u4F9B\u7B49\u540C\u4E8E PIPL \u7684\u4FDD\u62A4\u3002
1151
676
  - \u8BBF\u95EE\u63A7\u5236\u3001\u5BA1\u8BA1\u65E5\u5FD7\u548C\u8FDD\u89C4\u901A\u77E5\u7A0B\u5E8F\u3002
1152
- `,Ci=`---
677
+ `,Pt=`---
1153
678
  title: Personal Information Collection List / \u4E2A\u4EBA\u4FE1\u606F\u6536\u96C6\u6E05\u5355
1154
679
  description: Itemized list of personal information collected, per PIPL Article 17.
1155
680
  lastUpdated: 2026-01-01
1156
681
  ---
1157
682
 
1158
- ${vt}
683
+ ${W}
1159
684
 
1160
685
  # Personal Information Collection List / \u4E2A\u4EBA\u4FE1\u606F\u6536\u96C6\u6E05\u5355
1161
686
 
@@ -1191,329 +716,24 @@ To exercise your rights (access, rectify, delete, export, withdraw consent), con
1191
716
  **{{COMPANY_CONTACT_EMAIL}}**.
1192
717
 
1193
718
  \u5982\u9700\u884C\u4F7F\u60A8\u7684\u6743\u5229\uFF08\u8BBF\u95EE\u3001\u66F4\u6B63\u3001\u5220\u9664\u3001\u5BFC\u51FA\u3001\u64A4\u56DE\u540C\u610F\uFF09\uFF0C\u8BF7\u8054\u7CFB **{{COMPANY_CONTACT_EMAIL}}**\u3002
1194
- `;async function Ii(e,t){let n=pe(e);if(!M.existsSync(n))return;let o=O.join(n,"src","components","compliance","Footer.tsx");ee(o,Si);let r=O.join(n,"src","components","compliance","README.md");ee(r,["# Compliance Components","",`Auto-generated by \`create-sailor\` for region: \`${t}\`.`,"","## Mounting the Footer","","```tsx",'import { Footer } from "@/components/compliance/Footer";','import { CookieBanner } from "@/components/compliance/CookieBanner";',"","export default function RootLayout({ children }) {"," return ("," <html>"," <body>"," {children}",` <Footer region="${t}" />`," <CookieBanner />"," </body>"," </html>"," );","}","```","","## Placeholders","","Policy MDX files contain `{{COMPANY_NAME}}`, `{{COMPANY_ADDRESS}}`,","`{{COMPANY_CONTACT_EMAIL}}`, `{{GOVERNING_JURISDICTION}}`, and","`{{AIGC_ALGORITHM_FILING_NUMBER}}`. These are filled by the `brand-apply` step.",""].join(`
1195
- `))}async function Ti(e,t){let n=pe(e);if(!M.existsSync(n))return;let o=O.join(n,"src","components","compliance","CookieBanner.tsx");ee(o,vi)}async function xi(e,t){let n=pe(e);if(!M.existsSync(n))return;let o=O.join(n,Et,"privacy","page.mdx");ee(o,_i)}async function Ri(e,t){let n=pe(e);if(!M.existsSync(n))return;let o=O.join(n,Et,"terms","page.mdx");ee(o,wi)}async function ki(e){let t=pe(e);if(!M.existsSync(t))return;let n=O.join(t,fn,"IcpFooter.tsx");ee(n,hi),yn(e,["# Chinese ICP filing (\u5DE5\u4FE1\u90E8 ICP \u5907\u6848) \u2014 required to legally host a","# public-facing site from mainland China. Apply at https://beian.miit.gov.cn/",'NEXT_PUBLIC_ICP_BEIAN="" # \u5982 \u82CFICP\u59072024XXXXXX\u53F7'].join(`
1196
- `))}async function Ni(e){let t=pe(e);if(!M.existsSync(t))return;let n=O.join(t,fn,"PublicSecurityFooter.tsx");ee(n,Ei),yn(e,["# Chinese Public Security Bureau filing (\u516C\u5B89\u8054\u7F51\u5907\u6848)","# Required in addition to ICP. Apply at http://www.beian.gov.cn/",'NEXT_PUBLIC_PSB_BEIAN="" # \u5982 \u82CF\u516C\u7F51\u5B89\u5907 32011402XXXXXX\u53F7','NEXT_PUBLIC_PSB_CODE="" # 14-digit record code'].join(`
1197
- `))}async function Oi(e){let t=pe(e);if(!M.existsSync(t))return;let n=O.join(t,fn,"AigcDisclaimer.tsx");ee(n,bi),yn(e,["# Generative AI algorithm filing (\u7B97\u6CD5\u5907\u6848\u53F7) \u2014 required by","# \u7F51\u4FE1\u529E\u300A\u751F\u6210\u5F0F\u4EBA\u5DE5\u667A\u80FD\u670D\u52A1\u7BA1\u7406\u6682\u884C\u529E\u6CD5\u300Bfor services offered in China.",'NEXT_PUBLIC_AIGC_FILING_NUMBER="" # e.g. \u7F51\u4FE1\u7B97\u5907XXXXXXXX\u53F7'].join(`
1198
- `))}async function Li(e){let t=pe(e);if(!M.existsSync(t))return;let n=O.join(t,Et,"aigc-disclosure","page.mdx");ee(n,Pi)}async function Ui(e){let t=pe(e);if(!M.existsSync(t))return;let n=O.join(t,Et,"cross-border-data","page.mdx");ee(n,Ai)}async function Di(e){let t=pe(e);if(!M.existsSync(t))return;let n=O.join(t,Et,"pii-collection","page.mdx");ee(n,Ci)}async function Io(e,t){try{await Ii(e,t),await Ti(e,t),await xi(e,t),await Ri(e,t),(t==="cn"||t==="hybrid")&&(await ki(e),await Ni(e),await Oi(e),await Li(e),await Ui(e),await Di(e))}catch(n){throw console.error("applyComplianceTemplates failed:",n),new Error(`Failed to apply compliance templates for region "${t}". Check that ${e} is writable.`)}}import Mi from"fs";import Fi from"path";async function To(e,t){let n=Fi.join(e,"nebutra.config.json");Mi.writeFileSync(n,JSON.stringify(t,null,2)+`
1199
- `)}import T from"fs";import Me from"path";function ji(e){T.existsSync(e)&&T.rmSync(e,{recursive:!0,force:!0})}function xo(e,t){return e.replace(/provider\s*=\s*"postgresql"/,`provider = "${t}"`)}function Bi(e){return e.replace(/^\s*directUrl\s*=\s*env\([^)]*\)\s*\n/gm,"")}function Ro(e){let t=e.replace(/^\s*extensions\s*=\s*\[[^\]]*\]\s*\n/gm,"");return t=t.replace(/previewFeatures\s*=\s*\[\s*"postgresqlExtensions"\s*\]\s*\n/g,""),t}function $i(e,t){let n=Me.join(e,".env.example");T.existsSync(n)?T.appendFileSync(n,`
1200
- `+t):T.writeFileSync(n,t)}async function ko(e,t,n){let o=Me.join(e,"packages","platform","db");if(t==="none"){ji(o);return}let r=Me.join(o,"prisma","schema.prisma"),s;if(t==="postgresql"?s=`postgresql://postgres:postgres@localhost:5432/${n}`:t==="mysql"?s=`mysql://root:root@localhost:3306/${n}`:s="file:./dev.db",$i(e,`# Database (${t})
1201
- DATABASE_URL="${s}"
1202
- `),!T.existsSync(r))return;let i=T.readFileSync(r,"utf8"),a=i;t==="mysql"?(a=xo(a,"mysql"),a=Ro(a)):t==="sqlite"&&(a=xo(a,"sqlite"),a=Bi(a),a=Ro(a)),a!==i&&T.writeFileSync(r,a)}function Vi(e){let t=Me.join(e,".env.example");return T.existsSync(t)?T.readFileSync(t,"utf8"):""}function Ki(e,t,n,o){let r=Me.join(e,".env.example"),s=Vi(e),i=o?`# ${o}
1203
- ${t}="${n}"
1204
- `:`${t}="${n}"
1205
- `,a=new RegExp(`^\\s*${t}\\s*=.*$`,"m");if(a.test(s)){T.writeFileSync(r,s.replace(a,`${t}="${n}"`));return}s?T.appendFileSync(r,`
1206
- ${i}`):T.writeFileSync(r,i)}function Yi(e,t){let n=Me.join(e,"packages","platform","db","prisma","schema.prisma");if(!T.existsSync(n))return;let o=T.readFileSync(n,"utf8"),r=/(datasource\s+db\s*\{[^}]*?)(\n})/;if(!o.match(r))return;let i=t.filter(p=>!o.includes(p.trim()));if(i.length===0)return;let a=i.map(p=>` ${p}`).join(`
1207
- `),c=o.replace(r,`$1
1208
- ${a}$2`);T.writeFileSync(n,c)}function Gi(e){let t=Me.join(e,"packages","platform","db","prisma","schema.prisma");if(!T.existsSync(t))return;let n=T.readFileSync(t,"utf8");if(/directUrl\s*=\s*env\(/.test(n))return;let o=/(datasource\s+db\s*\{[\s\S]*?url\s*=\s*env\(\s*"DATABASE_URL"\s*\)[^\n]*\n)/;o.test(n)&&T.writeFileSync(t,n.replace(o,`$1 directUrl = env("DIRECT_URL")
1209
- `))}async function No(e,t,n){if(!(t.id==="none"||t.id==="local")){for(let o of t.envVars){let r=o.placeholder.replace(/\$PROJECT/g,n);Ki(e,o.name,r,o.comment)}t.prismaDatasourceExtras&&t.prismaDatasourceExtras.length>0&&Yi(e,t.prismaDatasourceExtras),t.keepDirectUrl&&Gi(e)}}import Ae from"fs";import Fe from"path";import{fileURLToPath as qi}from"url";var Hi=qi(import.meta.url),Oo=Fe.dirname(Hi),Wi=/\.(md|mdx|json|ts|tsx|js|mjs|cjs|yaml|yml|css|html|vue)$/,Lo=["fumadocs","nextra","mintlify","docusaurus","vitepress"];function Xi(e){let t=[Fe.join(Oo,"..","templates","docs",e),Fe.join(Oo,"..","..","templates","docs",e)];for(let n of t)if(Ae.existsSync(n))return n;return t[0]}async function Uo(e,t){if(t.framework==="none")return;if(!Lo.includes(t.framework))throw new Error(`Unsupported docs framework: "${t.framework}". Supported: ${Lo.join(", ")}, none.`);let n=Xi(t.framework),o=Fe.join(e,"apps","docs");if(!Ae.existsSync(n))throw new Error(`Docs template not found: ${n}`);Do(n,o),Mo(o,{"{PRODUCT_NAME}":t.projectName})}function Do(e,t){Ae.mkdirSync(t,{recursive:!0});for(let n of Ae.readdirSync(e,{withFileTypes:!0})){let o=Fe.join(e,n.name),r=Fe.join(t,n.name);n.isDirectory()?Do(o,r):Ae.copyFileSync(o,r)}}function Mo(e,t){for(let n of Ae.readdirSync(e,{withFileTypes:!0})){let o=Fe.join(e,n.name);if(n.isDirectory())Mo(o,t);else if(Wi.test(n.name)){let r=Ae.readFileSync(o,"utf-8");for(let[s,i]of Object.entries(t))r=r.split(s).join(i);Ae.writeFileSync(o,r)}}}import Be from"fs";import je from"path";var st=[{id:"resend",name:"Resend",region:"global",envVars:["RESEND_API_KEY","RESEND_FROM"],docs:"https://resend.com/docs",pkg:"resend",pkgVersion:"^4.0.1"},{id:"postmark",name:"Postmark",region:"global",envVars:["POSTMARK_API_KEY","POSTMARK_FROM"],docs:"https://postmarkapp.com/developer",pkg:"postmark",pkgVersion:"^4.0.5"},{id:"ses",name:"AWS SES",region:"global",envVars:["AWS_ACCESS_KEY_ID","AWS_SECRET_ACCESS_KEY","AWS_SES_REGION","SES_FROM"],docs:"https://docs.aws.amazon.com/ses",pkg:"@aws-sdk/client-sesv2",pkgVersion:"^3.658.0"},{id:"aliyun-dm",name:"\u963F\u91CC\u4E91\u90AE\u4EF6\u63A8\u9001",region:"cn",envVars:["ALIYUN_DM_ACCESS_KEY","ALIYUN_DM_ACCESS_SECRET","ALIYUN_DM_ACCOUNT_NAME","ALIYUN_DM_FROM_ALIAS"],docs:"https://help.aliyun.com/product/29412.html",pkg:"@alicloud/dm20151123",pkgVersion:"^3.0.0"},{id:"tencent-ses",name:"\u817E\u8BAF\u4E91 SES",region:"cn",envVars:["TENCENT_SECRET_ID","TENCENT_SECRET_KEY","TENCENT_SES_FROM_EMAIL"],docs:"https://cloud.tencent.com/document/product/1288",pkg:"tencentcloud-sdk-nodejs-ses",pkgVersion:"^4.0.900"},{id:"netease",name:"\u7F51\u6613\u4F01\u4E1A\u90AE\u7BB1",region:"cn",envVars:["NETEASE_USER","NETEASE_PASS","NETEASE_FROM"],docs:"https://qiye.163.com/help",pkg:"nodemailer",pkgVersion:"^6.9.15"},{id:"none",name:"None",region:"global",envVars:[],docs:""}];function Fo(e){return st.find(t=>t.id===e)}var yd=st.reduce((e,t)=>(e[t.region]||(e[t.region]=[]),e[t.region].push(t),e),{});var $e="@nebutra/email";function Sn(e,t){Be.mkdirSync(je.dirname(e),{recursive:!0}),Be.writeFileSync(e,t)}function zi(e,t){let n=je.join(e,".env.example");Be.existsSync(n)?Be.appendFileSync(n,`
1210
- `+t):Be.writeFileSync(n,t)}function Ji(e){if(e.envVars.length===0)return"";let t=`# Email provider: ${e.name}`,n=e.envVars.map(o=>`${o}=`).join(`
1211
- `);return`${t}
1212
- ${n}
1213
- `}var Qi=`/**
1214
- * Public inputs accepted by the \`sendEmail\` wrapper.
1215
- * Keep the surface minimal \u2014 individual providers may accept more, but
1216
- * consumers should code against this shape for portability.
1217
- */
1218
- export interface SendEmailInput {
1219
- to: string | string[];
1220
- subject: string;
1221
- html?: string;
1222
- text?: string;
1223
- from?: string;
1224
- replyTo?: string;
1225
- }
1226
-
1227
- export interface SendEmailResult {
1228
- /** Provider-specific message identifier, when available. */
1229
- id?: string;
1230
- /** Raw provider response \u2014 useful for debugging, not a stable contract. */
1231
- raw?: unknown;
1232
- }
1233
- `;function Zi(){return`/**
1234
- * @${$e.slice(1)} \u2014 Resend transactional email wrapper.
1235
- *
1236
- * Docs: https://resend.com/docs
1237
- */
1238
- import { Resend } from "resend";
1239
-
1240
- import type { SendEmailInput, SendEmailResult } from "./types";
1241
-
1242
- const apiKey = process.env.RESEND_API_KEY;
1243
- if (!apiKey) {
1244
- throw new Error("RESEND_API_KEY not configured");
1245
- }
1246
-
1247
- const resend = new Resend(apiKey);
1248
-
1249
- export async function sendEmail(input: SendEmailInput): Promise<SendEmailResult> {
1250
- const from = input.from ?? process.env.RESEND_FROM;
1251
- if (!from) {
1252
- throw new Error("RESEND_FROM (or input.from) must be provided");
1253
- }
1254
-
1255
- const { data, error } = await resend.emails.send({
1256
- from,
1257
- to: Array.isArray(input.to) ? input.to : [input.to],
1258
- subject: input.subject,
1259
- html: input.html,
1260
- text: input.text,
1261
- ...(input.replyTo ? { replyTo: input.replyTo } : {}),
1262
- });
1263
-
1264
- if (error) {
1265
- throw new Error(\`Resend send failed: \${error.message}\`);
1266
- }
1267
-
1268
- return { id: data?.id, raw: data };
1269
- }
1270
-
1271
- export type { SendEmailInput, SendEmailResult } from "./types";
1272
- `}function ea(){return`/**
1273
- * @${$e.slice(1)} \u2014 Postmark transactional email wrapper.
1274
- *
1275
- * Docs: https://postmarkapp.com/developer
1276
- */
1277
- import { ServerClient } from "postmark";
1278
-
1279
- import type { SendEmailInput, SendEmailResult } from "./types";
1280
-
1281
- const apiKey = process.env.POSTMARK_API_KEY;
1282
- if (!apiKey) {
1283
- throw new Error("POSTMARK_API_KEY not configured");
1284
- }
1285
-
1286
- const client = new ServerClient(apiKey);
1287
-
1288
- export async function sendEmail(input: SendEmailInput): Promise<SendEmailResult> {
1289
- const from = input.from ?? process.env.POSTMARK_FROM;
1290
- if (!from) {
1291
- throw new Error("POSTMARK_FROM (or input.from) must be provided");
1292
- }
1293
-
1294
- const to = Array.isArray(input.to) ? input.to.join(", ") : input.to;
1295
-
1296
- const response = await client.sendEmail({
1297
- From: from,
1298
- To: to,
1299
- Subject: input.subject,
1300
- HtmlBody: input.html,
1301
- TextBody: input.text,
1302
- ReplyTo: input.replyTo,
1303
- MessageStream: "outbound",
1304
- });
1305
-
1306
- return { id: response.MessageID, raw: response };
1307
- }
1308
-
1309
- export type { SendEmailInput, SendEmailResult } from "./types";
1310
- `}function ta(){return`/**
1311
- * @${$e.slice(1)} \u2014 AWS SES v2 transactional email wrapper.
1312
- *
1313
- * Docs: https://docs.aws.amazon.com/ses
1314
- */
1315
- import {
1316
- SendEmailCommand,
1317
- SESv2Client,
1318
- } from "@aws-sdk/client-sesv2";
1319
-
1320
- import type { SendEmailInput, SendEmailResult } from "./types";
1321
-
1322
- const region = process.env.AWS_SES_REGION;
1323
- if (!region) {
1324
- throw new Error("AWS_SES_REGION not configured");
1325
- }
1326
-
1327
- const client = new SESv2Client({
1328
- region,
1329
- credentials:
1330
- process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY
1331
- ? {
1332
- accessKeyId: process.env.AWS_ACCESS_KEY_ID,
1333
- secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
1334
- }
1335
- : undefined,
1336
- });
1337
-
1338
- export async function sendEmail(input: SendEmailInput): Promise<SendEmailResult> {
1339
- const from = input.from ?? process.env.SES_FROM;
1340
- if (!from) {
1341
- throw new Error("SES_FROM (or input.from) must be provided");
1342
- }
1343
-
1344
- const toAddresses = Array.isArray(input.to) ? input.to : [input.to];
1345
-
1346
- const command = new SendEmailCommand({
1347
- FromEmailAddress: from,
1348
- Destination: { ToAddresses: toAddresses },
1349
- ReplyToAddresses: input.replyTo ? [input.replyTo] : undefined,
1350
- Content: {
1351
- Simple: {
1352
- Subject: { Data: input.subject, Charset: "UTF-8" },
1353
- Body: {
1354
- ...(input.html ? { Html: { Data: input.html, Charset: "UTF-8" } } : {}),
1355
- ...(input.text ? { Text: { Data: input.text, Charset: "UTF-8" } } : {}),
1356
- },
1357
- },
1358
- },
1359
- });
1360
-
1361
- const response = await client.send(command);
1362
- return { id: response.MessageId, raw: response };
1363
- }
1364
-
1365
- export type { SendEmailInput, SendEmailResult } from "./types";
1366
- `}function na(){return`/**
1367
- * @${$e.slice(1)} \u2014 \u963F\u91CC\u4E91\u90AE\u4EF6\u63A8\u9001 (Aliyun DirectMail) wrapper.
1368
- *
1369
- * Docs: https://help.aliyun.com/product/29412.html
1370
- */
1371
- import Dm20151123, * as $Dm20151123 from "@alicloud/dm20151123";
1372
- import * as $OpenApi from "@alicloud/openapi-client";
1373
-
1374
- import type { SendEmailInput, SendEmailResult } from "./types";
1375
-
1376
- const accessKeyId = process.env.ALIYUN_DM_ACCESS_KEY;
1377
- const accessKeySecret = process.env.ALIYUN_DM_ACCESS_SECRET;
1378
- if (!accessKeyId || !accessKeySecret) {
1379
- throw new Error(
1380
- "ALIYUN_DM_ACCESS_KEY and ALIYUN_DM_ACCESS_SECRET must be configured",
1381
- );
1382
- }
1383
-
1384
- const config = new $OpenApi.Config({
1385
- accessKeyId,
1386
- accessKeySecret,
1387
- endpoint: "dm.aliyuncs.com",
1388
- });
1389
-
1390
- const client = new Dm20151123.default(config);
1391
-
1392
- export async function sendEmail(input: SendEmailInput): Promise<SendEmailResult> {
1393
- const accountName = process.env.ALIYUN_DM_ACCOUNT_NAME;
1394
- if (!accountName) {
1395
- throw new Error("ALIYUN_DM_ACCOUNT_NAME must be configured");
1396
- }
1397
-
1398
- const toAddress = Array.isArray(input.to) ? input.to.join(",") : input.to;
1399
-
1400
- const req = new $Dm20151123.SingleSendMailRequest({
1401
- accountName,
1402
- addressType: 1,
1403
- replyToAddress: false,
1404
- toAddress,
1405
- subject: input.subject,
1406
- htmlBody: input.html,
1407
- textBody: input.text,
1408
- fromAlias: process.env.ALIYUN_DM_FROM_ALIAS,
1409
- });
1410
-
1411
- const response = await client.singleSendMail(req);
1412
- return { id: response.body?.requestId, raw: response };
1413
- }
1414
-
1415
- export type { SendEmailInput, SendEmailResult } from "./types";
1416
- `}function oa(){return`/**
1417
- * @${$e.slice(1)} \u2014 \u817E\u8BAF\u4E91 SES wrapper.
1418
- *
1419
- * Docs: https://cloud.tencent.com/document/product/1288
1420
- */
1421
- import tencentcloud from "tencentcloud-sdk-nodejs-ses";
1422
-
1423
- import type { SendEmailInput, SendEmailResult } from "./types";
1424
-
1425
- const secretId = process.env.TENCENT_SECRET_ID;
1426
- const secretKey = process.env.TENCENT_SECRET_KEY;
1427
- if (!secretId || !secretKey) {
1428
- throw new Error("TENCENT_SECRET_ID and TENCENT_SECRET_KEY must be configured");
1429
- }
1430
-
1431
- const SesClient = tencentcloud.ses.v20201002.Client;
1432
-
1433
- const client = new SesClient({
1434
- credential: { secretId, secretKey },
1435
- region: "ap-hongkong",
1436
- profile: {
1437
- httpProfile: { endpoint: "ses.tencentcloudapi.com" },
1438
- },
1439
- });
1440
-
1441
- export async function sendEmail(input: SendEmailInput): Promise<SendEmailResult> {
1442
- const from = input.from ?? process.env.TENCENT_SES_FROM_EMAIL;
1443
- if (!from) {
1444
- throw new Error("TENCENT_SES_FROM_EMAIL (or input.from) must be provided");
1445
- }
1446
-
1447
- const destination = Array.isArray(input.to) ? input.to : [input.to];
1448
-
1449
- const response = await client.SendEmail({
1450
- FromEmailAddress: from,
1451
- Destination: destination,
1452
- Subject: input.subject,
1453
- ReplyToAddresses: input.replyTo,
1454
- Simple: {
1455
- Html: input.html,
1456
- Text: input.text,
1457
- },
1458
- });
1459
-
1460
- return { id: response.MessageId, raw: response };
1461
- }
1462
-
1463
- export type { SendEmailInput, SendEmailResult } from "./types";
1464
- `}function ra(){return`/**
1465
- * @${$e.slice(1)} \u2014 \u7F51\u6613\u4F01\u4E1A\u90AE\u7BB1 via SMTP (nodemailer).
1466
- *
1467
- * Docs: https://qiye.163.com/help
1468
- */
1469
- import nodemailer from "nodemailer";
1470
-
1471
- import type { SendEmailInput, SendEmailResult } from "./types";
1472
-
1473
- const user = process.env.NETEASE_USER;
1474
- const pass = process.env.NETEASE_PASS;
1475
- if (!user || !pass) {
1476
- throw new Error("NETEASE_USER and NETEASE_PASS must be configured");
1477
- }
1478
-
1479
- const transporter = nodemailer.createTransport({
1480
- host: "smtp.qiye.163.com",
1481
- port: 465,
1482
- secure: true,
1483
- auth: { user, pass },
1484
- });
1485
-
1486
- export async function sendEmail(input: SendEmailInput): Promise<SendEmailResult> {
1487
- const from = input.from ?? process.env.NETEASE_FROM ?? user;
1488
-
1489
- const response = await transporter.sendMail({
1490
- from,
1491
- to: Array.isArray(input.to) ? input.to.join(", ") : input.to,
1492
- subject: input.subject,
1493
- html: input.html,
1494
- text: input.text,
1495
- replyTo: input.replyTo,
1496
- });
1497
-
1498
- return { id: response.messageId, raw: response };
1499
- }
1500
-
1501
- export type { SendEmailInput, SendEmailResult } from "./types";
1502
- `}function sa(e){switch(e){case"resend":return Zi();case"postmark":return ea();case"ses":return ta();case"aliyun-dm":return na();case"tencent-ses":return oa();case"netease":return ra();case"none":return`export {};
1503
- `}}function ia(e){let t={};e.pkg&&e.pkgVersion&&(t[e.pkg]=e.pkgVersion),e.id==="aliyun-dm"&&(t["@alicloud/openapi-client"]="^0.4.12");let n={name:$e,version:"0.1.0",private:!0,description:`Transactional email via ${e.name}`,type:"module",main:"./dist/index.js",exports:{".":{import:"./dist/index.js",types:"./dist/index.d.ts"}},scripts:{build:"tsup",typecheck:"tsc --noEmit",clean:"rm -rf dist .turbo"},dependencies:t,devDependencies:{tsup:"^8.5.0",typescript:"catalog:"}};return JSON.stringify(n,null,2)+`
1504
- `}async function jo(e,t,n){if(t==="none")return;let o=Fo(t);if(!o)throw new Error(`Unknown email provider "${t}". Valid ids: ${st.map(i=>i.id).join(", ")}`);let r=je.join(e,"packages","email"),s=je.join(r,"src");Be.existsSync(s)&&Be.rmSync(s,{recursive:!0,force:!0}),Sn(je.join(r,"package.json"),ia(o)),Sn(je.join(s,"types.ts"),Qi),Sn(je.join(s,"index.ts"),sa(o.id)),zi(e,Ji(o))}import ge from"fs";import Bo from"path";function aa(e,t){return new RegExp(`^\\s*${t}\\s*=`,"m").test(e)}function $o(e){return e.endsWith(`
719
+ `;async function kt(e,t){let n=P(e);if(!w.existsSync(n))return;let r=h.join(n,"src","components","compliance","Footer.tsx");T(r,wt);let s=h.join(n,"src","components","compliance","README.md");T(s,["# Compliance Components","",`Auto-generated by \`create-sailor\` for region: \`${t}\`.`,"","## Mounting the Footer","","```tsx",'import { Footer } from "@/components/compliance/Footer";','import { CookieBanner } from "@/components/compliance/CookieBanner";',"","export default function RootLayout({ children }) {"," return ("," <html>"," <body>"," {children}",` <Footer region="${t}" />`," <CookieBanner />"," </body>"," </html>"," );","}","```","","## Placeholders","","Policy MDX files contain `{{COMPANY_NAME}}`, `{{COMPANY_ADDRESS}}`,","`{{COMPANY_CONTACT_EMAIL}}`, `{{GOVERNING_JURISDICTION}}`, and","`{{AIGC_ALGORITHM_FILING_NUMBER}}`. These are filled by the `brand-apply` step.",""].join(`
720
+ `))}async function It(e,t){let n=P(e);if(!w.existsSync(n))return;let r=h.join(n,"src","components","compliance","CookieBanner.tsx");T(r,Ct)}async function Ot(e,t){let n=P(e);if(!w.existsSync(n))return;let r=h.join(n,H,"privacy","page.mdx");T(r,xt)}async function Rt(e,t){let n=P(e);if(!w.existsSync(n))return;let r=h.join(n,H,"terms","page.mdx");T(r,Tt)}async function Lt(e){let t=P(e);if(!w.existsSync(t))return;let n=h.join(t,te,"IcpFooter.tsx");T(n,vt),ne(e,["# Chinese ICP filing (\u5DE5\u4FE1\u90E8 ICP \u5907\u6848) \u2014 required to legally host a","# public-facing site from mainland China. Apply at https://beian.miit.gov.cn/",'NEXT_PUBLIC_ICP_BEIAN="" # \u5982 \u82CFICP\u59072024XXXXXX\u53F7'].join(`
721
+ `))}async function Ft(e){let t=P(e);if(!w.existsSync(t))return;let n=h.join(t,te,"PublicSecurityFooter.tsx");T(n,St),ne(e,["# Chinese Public Security Bureau filing (\u516C\u5B89\u8054\u7F51\u5907\u6848)","# Required in addition to ICP. Apply at http://www.beian.gov.cn/",'NEXT_PUBLIC_PSB_BEIAN="" # \u5982 \u82CF\u516C\u7F51\u5B89\u5907 32011402XXXXXX\u53F7','NEXT_PUBLIC_PSB_CODE="" # 14-digit record code'].join(`
722
+ `))}async function $t(e){let t=P(e);if(!w.existsSync(t))return;let n=h.join(t,te,"AigcDisclaimer.tsx");T(n,At),ne(e,["# Generative AI algorithm filing (\u7B97\u6CD5\u5907\u6848\u53F7) \u2014 required by","# \u7F51\u4FE1\u529E\u300A\u751F\u6210\u5F0F\u4EBA\u5DE5\u667A\u80FD\u670D\u52A1\u7BA1\u7406\u6682\u884C\u529E\u6CD5\u300Bfor services offered in China.",'NEXT_PUBLIC_AIGC_FILING_NUMBER="" # e.g. \u7F51\u4FE1\u7B97\u5907XXXXXXXX\u53F7'].join(`
723
+ `))}async function Dt(e){let t=P(e);if(!w.existsSync(t))return;let n=h.join(t,H,"aigc-disclosure","page.mdx");T(n,_t)}async function jt(e){let t=P(e);if(!w.existsSync(t))return;let n=h.join(t,H,"cross-border-data","page.mdx");T(n,Nt)}async function Mt(e){let t=P(e);if(!w.existsSync(t))return;let n=h.join(t,H,"pii-collection","page.mdx");T(n,Pt)}async function Ee(e,t){try{await kt(e,t),await It(e,t),await Ot(e,t),await Rt(e,t),(t==="cn"||t==="hybrid")&&(await Lt(e),await Ft(e),await $t(e),await Dt(e),await jt(e),await Mt(e))}catch(n){throw console.error("applyComplianceTemplates failed:",n),new Error(`Failed to apply compliance templates for region "${t}". Check that ${e} is writable.`)}}import Bt from"fs";import Ut from"path";var Gt=["auth","billing","email","storage","queue","cache","notifications","webhooks","ai","mcp"];function we(){return{stack:"sailor-2026-09",capabilities:[...Gt]}}async function ve(e,t){let n=Ut.join(e,"nebutra.config.json");Bt.writeFileSync(n,JSON.stringify(t,null,2)+`
724
+ `)}import I from"fs";import Se from"path";function Yt(e,t){return new RegExp(`^\\s*${t}\\s*=`,"m").test(e)}function Ce(e){return e.endsWith(`
1505
725
  `)?e:`${e}
1506
- `}async function Vo(e,t){let n=Bo.join(e,".env"),o=Bo.join(e,".env.local"),r=ge.existsSync(n)?ge.readFileSync(n,"utf8"):"",s=ge.existsSync(o)?ge.readFileSync(o,"utf8"):"",i=`${r}
1507
- ${s}`,a=[{name:"DATABASE_URL",value:String(t.databaseUrl)},{name:"NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY",value:String(t.clerkPublishable)},{name:"CLERK_SECRET_KEY",value:String(t.clerkSecret)},{name:"NEXT_PUBLIC_SITE_URL",value:"http://localhost:3000"}].filter(p=>!aa(i,p.name)).map(p=>`${p.name}="${p.value}"`);if(a.length===0)return;let c=`# Automatically injected by create-sailor
1508
- ${a.join(`
726
+ `}async function Ae(e,t){let n=Se.join(e,".env"),r=Se.join(e,".env.local"),s=I.existsSync(n)?I.readFileSync(n,"utf8"):"",o=I.existsSync(r)?I.readFileSync(r,"utf8"):"",i=`${s}
727
+ ${o}`,c=[{name:"DATABASE_URL",value:t.databaseUrl},{name:"AUTH_PROVIDER",value:"better-auth"},{name:"NEXT_PUBLIC_AUTH_PROVIDER",value:"better-auth"},{name:"NEXT_PUBLIC_SITE_URL",value:"http://localhost:3000"}].filter(l=>!Yt(i,l.name)).map(l=>`${l.name}="${l.value}"`);if(c.length===0)return;let a=`# Automatically injected by create-sailor
728
+ ${c.join(`
1509
729
  `)}
1510
- `;try{let p=ge.readFileSync(o,"utf8"),m=$o(p);ge.writeFileSync(o,`${m}
1511
- ${c}`)}catch{try{ge.writeFileSync(o,c,{flag:"wx"})}catch{let p=ge.readFileSync(o,"utf8");ge.writeFileSync(o,`${$o(p)}
1512
- ${c}`)}}}import Yo from"crypto";import bt from"fs";import Ko from"path";function Ve(e){return Yo.randomBytes(e).toString("base64").replace(/=+$/,"")}function ca(e){return Yo.randomBytes(e).toString("hex")}function pa(){return{AUTH_SECRET:Ve(32),NEXTAUTH_SECRET:Ve(32),BETTER_AUTH_SECRET:Ve(32),JWT_SECRET:Ve(32),WEBHOOK_SECRET:ca(16),ENCRYPTION_KEY:Ve(32),API_SECRET_KEY:Ve(32),SESSION_SECRET:Ve(32)}}function la(e){return e.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}function da(e,t,n){let o=new RegExp(`^(?!#)\\s*${t}=.*$`,"gm"),r=la(n);return e.replace(o,`${t}="${r}"`)}async function Go(e){let t=Ko.join(e,".env.example"),n=Ko.join(e,".env.local");if(!bt.existsSync(t))return null;try{let o=bt.readFileSync(t,"utf8"),r=pa(),i=bt.existsSync(n)?bt.readFileSync(n,"utf8"):o;for(let a of Object.keys(r))i=da(i,a,r[a]);return bt.writeFileSync(n,i),r}catch(o){let r=o instanceof Error?o.message:String(o);throw console.error(`[create-sailor] Failed to generate env secrets: ${r}`),new Error(`Unable to write .env.local: ${r}`)}}var Ft=[{id:"vercel-flags",name:"Vercel Flags SDK",region:"global",envVars:["FLAGS_SECRET"],docs:"https://vercel.com/docs/workflow-collaboration/feature-flags",description:"Native Vercel feature flags"},{id:"growthbook",name:"GrowthBook",region:"both",envVars:["GROWTHBOOK_API_HOST","GROWTHBOOK_CLIENT_KEY","GROWTHBOOK_DECRYPTION_KEY"],docs:"https://docs.growthbook.io",description:"Open-source A/B testing + flags"},{id:"configcat",name:"ConfigCat",region:"global",envVars:["CONFIGCAT_SDK_KEY"],docs:"https://configcat.com/docs/",description:"Feature flag & config management"},{id:"none",name:"None",region:"both",envVars:[],docs:"",description:""}];function qo(e){return Ft.find(t=>t.id===e)}var xd=Ft.reduce((e,t)=>(e[t.region]||(e[t.region]=[]),e[t.region].push(t),e),{});async function Ho(e,t,n="global"){let o=qo(t);if(!o)throw new Error(`Unknown feature-flags provider "${t}". Valid ids: ${Ft.map(r=>r.id).join(", ")}`);if(o.id==="none"){Z(e,"feature-flags");return}o.envVars.length>0&&ae(e,{category:"Feature Flags",name:o.name,envVars:o.envVars,docs:o.docs}),ce(e,"FEATURE_FLAGS_PROVIDER",o.id)}import{execFileSync as bn}from"child_process";import{once as ua}from"events";import C from"fs";import ma from"os";import te from"path";import ga from"ignore";var fa={mirror:{repo:process.env.SAILOR_TEMPLATE_REPO||"Nebutra/Sailor-Template",ref:process.env.SAILOR_TEMPLATE_REF||"main",applyIgnore:!1,allowMutableFallback:process.env.SAILOR_TEMPLATE_ALLOW_MUTABLE_FALLBACK==="1"},main:{repo:"Nebutra/Nebutra-Sailor",ref:"main",applyIgnore:!0,allowMutableFallback:process.env.SAILOR_TEMPLATE_ALLOW_MUTABLE_FALLBACK==="1"}},Wo=3e4,Xo=3;function hn(e){return`${e.repo}@${e.ref}`}function ya(){let e=process.env.SAILOR_TEMPLATE_LOCAL_DIR;if(!(!e||e.trim().length===0))return te.resolve(e.trim())}var Sa=new Set(["node_modules",".git","dist",".next",".turbo"]);function ha(e,t){if(!C.statSync(e).isDirectory())throw new Error(`SAILOR_TEMPLATE_LOCAL_DIR is not a directory: ${e}`);C.cpSync(e,t,{recursive:!0,dereference:!0,filter:o=>{let r=te.basename(o);return!Sa.has(r)}})}function Ea(e,t){return`https://github.com/${e}/archive/refs/heads/${t}.tar.gz`}function va(e,t){return`https://github.com/${e}/archive/${t}.tar.gz`}function er(e){return/^[0-9a-f]{7,40}$/i.test(e)}function zo(e,t){let n=`https://github.com/${e}.git`,o=[`refs/heads/${t}`,`refs/tags/${t}`];for(let r of o)try{let s=bn("git",["ls-remote",n,r],{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim();if(!s)continue;let[i]=s.split(/\s+/);if(er(i))return i}catch{}return null}async function ba(e,t){if(er(t))return t;let n;try{n=await fetch(`https://api.github.com/repos/${e}/commits/${encodeURIComponent(t)}`,{headers:{Accept:"application/vnd.github+json","User-Agent":"create-sailor"},signal:AbortSignal.timeout(2e4)})}catch(r){let s=zo(e,t);if(s)return s;throw new Error(`Cannot reach api.github.com to resolve ${e}@${t} (${En(r)})`)}if(!n.ok){let r=zo(e,t);if(r)return r;throw new Error(`Failed to resolve ${e}@${t} (GitHub API ${n.status})`)}let o=await n.json();if(typeof o.sha!="string"||o.sha.length===0)throw new Error(`GitHub API did not return a commit SHA for ${e}@${t}`);return o.sha}function tr(e,t,n){let o=C.readdirSync(t,{withFileTypes:!0});for(let r of o){if(r.name==="node_modules"||r.name===".git")continue;let s=te.join(t,r.name),i=te.relative(e,s).split(te.sep).join("/");r.isDirectory()?(n.push(i+"/"),tr(e,s,n)):n.push(i)}}function nr(e){let t=te.join(e,".templateignore");if(!C.existsSync(t))return;let n=C.readFileSync(t,"utf8"),o=ga().add(n),r=[];tr(e,e,r);let s=r.map(c=>c.endsWith("/")?c.slice(0,-1):c),i=new Set(o.filter(s)),a=s.filter(c=>!i.has(c)).sort((c,p)=>p.split("/").length-c.split("/").length);for(let c of a){let p=te.join(e,c);try{C.existsSync(p)&&C.rmSync(p,{recursive:!0,force:!0})}catch{}}try{C.existsSync(t)&&C.rmSync(t,{force:!0})}catch{}}function En(e){if(!(e instanceof Error))return String(e);let t=e.cause,n=t&&typeof t=="object"&&"code"in t?String(t.code):void 0,o=t instanceof Error?t.message:void 0;switch(n){case"ENOTFOUND":case"EAI_AGAIN":return"DNS lookup failed \u2014 check your connection or proxy";case"ECONNREFUSED":return"connection refused \u2014 a proxy or firewall may be blocking github.com";case"ECONNRESET":return"connection reset by peer mid-transfer";case"ETIMEDOUT":case"UND_ERR_CONNECT_TIMEOUT":return"connection timed out";case"UND_ERR_HEADERS_TIMEOUT":case"UND_ERR_BODY_TIMEOUT":return"the server stopped sending data mid-transfer";case"CERT_HAS_EXPIRED":case"UNABLE_TO_VERIFY_LEAF_SIGNATURE":case"SELF_SIGNED_CERT_IN_CHAIN":return"TLS certificate rejected \u2014 a corporate proxy may be intercepting HTTPS";default:break}return e.name==="TimeoutError"?"request timed out":e.name==="AbortError"?e.message||"transfer aborted":o?`${e.message}: ${o}`:e.message}function _a(e){return e===408||e===425||e===429||e>=500}async function wa(e,t,n){let o=new AbortController,r,s=()=>{r&&clearTimeout(r),r=setTimeout(()=>{o.abort(new Error(`no data received for ${Math.round(Wo/1e3)}s \u2014 transfer stalled`))},Wo)};s();try{let i=await fetch(e,{headers:{Accept:"application/octet-stream","User-Agent":"create-sailor"},signal:o.signal,redirect:"follow"});if(!i.ok){let g=new Error(`Failed to download archive (HTTP ${i.status} ${i.statusText})`);throw g.retryable=_a(i.status),g}if(!i.body)throw new Error("Download response had no body");let a=i.headers.get("content-length"),c=a&&/^\d+$/.test(a)?Number(a):void 0,p=C.createWriteStream(t),m=i.body.getReader(),d=0;try{for(;;){let{done:g,value:h}=await m.read();if(g)break;s(),d+=h.byteLength,p.write(h)||await ua(p,"drain"),n?.(d,c)}await new Promise((g,h)=>{p.end(P=>P?h(P):g())})}catch(g){throw p.destroy(),await m.cancel().catch(()=>{}),g}if(d<1024)throw new Error(`Downloaded archive is unexpectedly small (${d} bytes)`);if(c!==void 0&&d!==c){let g=new Error(`Download truncated \u2014 got ${it(d)} of ${it(c)}`);throw g.retryable=!0,g}let v=C.statSync(t);if(v.size!==d)throw new Error(`Archive write verification failed (${v.size} != ${d})`);return{bytes:d}}finally{r&&clearTimeout(r)}}async function Jo(e,t,n,o){let r;for(let s=1;s<=Xo;s++)try{return o?.({phase:"download",source:n,attempt:s,receivedBytes:0}),await wa(e,t,(i,a)=>{o?.({phase:"download",source:n,attempt:s,receivedBytes:i,totalBytes:a})})}catch(i){r=i instanceof Error?i:new Error(String(i));try{C.rmSync(t,{force:!0})}catch{}if(!(r.retryable??!0)||s===Xo)break;o?.({phase:"retry",source:n,attempt:s+1,reason:En(r)}),await new Promise(c=>setTimeout(c,s*1500))}throw new Error(En(r??new Error("unknown download failure")))}function Pa(e,t){try{bn("tar",["-xzf",e,"-C",t,"--strip-components=1"],{stdio:["ignore","ignore","pipe"]})}catch(n){let o=n.stderr?.toString().trim();throw new Error(`Failed to extract the template archive${o?`: ${o.split(`
1513
- `)[0]}`:""}`)}}function Aa(){try{bn("tar",["--version"],{stdio:"ignore"})}catch{throw new Error("`tar` was not found on PATH. Install it (macOS/Linux ship it; on Windows use Git Bash or WSL) and retry.")}}function Qo(e){let t=0,n=o=>{for(let r of C.readdirSync(o,{withFileTypes:!0})){let s=te.join(o,r.name);if(r.isDirectory())n(s);else if(r.isFile())try{t+=C.statSync(s).size}catch{}}};try{n(e)}catch{}return t}function it(e){if(e<1024)return`${e} B`;let t=e/1e6;return t<1?`${Math.round(e/1024)} KB`:t<1e3?`${t.toFixed(1)} MB`:`${(t/1e3).toFixed(2)} GB`}function Zo(e,t){let n=new Set(vn(t));return C.cpSync(e,t,{recursive:!0,force:!0}),vn(t).filter(o=>!n.has(o))}function vn(e){try{return C.readdirSync(e)}catch{return[]}}async function Ca(e,t,n,o){let r=hn(e);o?.({phase:"resolve",source:r});try{let s=await ba(e.repo,e.ref);await Jo(va(e.repo,s),n,r,o)}catch(s){if(!e.allowMutableFallback)throw s;await Jo(Ea(e.repo,e.ref),n,r,o)}if(o?.({phase:"extract",source:r}),Pa(n,t),e.applyIgnore){o?.({phase:"strip",source:r});try{nr(t)}catch(s){throw new Error(`Template downloaded, but .templateignore processing failed: ${s instanceof Error?s.message:String(s)}`)}}if(vn(t).length===0)throw new Error("The extracted template was empty")}async function or(e,t={}){let{onProgress:n}=t;Aa(),C.mkdirSync(e,{recursive:!0});let o=C.mkdtempSync(te.join(ma.tmpdir(),"create-sailor-")),r=te.join(o,"template.tar.gz"),s=te.join(o,"tree"),i=()=>{C.rmSync(s,{recursive:!0,force:!0}),C.mkdirSync(s,{recursive:!0})};try{let a=ya();if(a){if(!C.existsSync(a))throw new Error(`SAILOR_TEMPLATE_LOCAL_DIR does not exist: ${a}`);let d=`local:${a}`;n?.({phase:"extract",source:d}),i(),ha(a,s),n?.({phase:"strip",source:d});try{nr(s)}catch(g){throw new Error(`Local template copied, but .templateignore processing failed: ${g instanceof Error?g.message:String(g)}`)}n?.({phase:"install",source:d});let v=Qo(s);return{source:d,bytes:v,createdEntries:Zo(s,e)}}let p=process.env.SAILOR_TEMPLATE_SOURCE==="main"?["main"]:["mirror","main"],m=[];for(let d of p){let v=fa[d];try{i(),await Ca(v,s,r,n);let g=hn(v);n?.({phase:"install",source:g});let h=Qo(s);return{source:g,bytes:h,createdEntries:Zo(s,e)}}catch(g){m.push(` ${hn(v)} \u2014 ${g instanceof Error?g.message:String(g)}`)}}throw new Error(`Could not download the Sailor template.
730
+ `;try{let l=I.readFileSync(r,"utf8"),d=Ce(l);I.writeFileSync(r,`${d}
731
+ ${a}`)}catch{try{I.writeFileSync(r,a,{flag:"wx"})}catch{let l=I.readFileSync(r,"utf8");I.writeFileSync(r,`${Ce(l)}
732
+ ${a}`)}}}import Te from"crypto";import X from"fs";import xe from"path";function $(e){return Te.randomBytes(e).toString("base64").replace(/=+$/,"")}function Ht(e){return Te.randomBytes(e).toString("hex")}function Wt(){return{AUTH_SECRET:$(32),NEXTAUTH_SECRET:$(32),BETTER_AUTH_SECRET:$(32),JWT_SECRET:$(32),WEBHOOK_SECRET:Ht(16),ENCRYPTION_KEY:$(32),API_SECRET_KEY:$(32),SESSION_SECRET:$(32)}}function Xt(e){return e.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}function Jt(e,t,n){let r=new RegExp(`^(?!#)\\s*${t}=.*$`,"gm"),s=Xt(n);return e.replace(r,`${t}="${s}"`)}async function _e(e){let t=xe.join(e,".env.example"),n=xe.join(e,".env.local");if(!X.existsSync(t))return null;try{let r=X.readFileSync(t,"utf8"),s=Wt(),i=X.existsSync(n)?X.readFileSync(n,"utf8"):r;for(let c of Object.keys(s))i=Jt(i,c,s[c]);return X.writeFileSync(n,i),s}catch(r){let s=r instanceof Error?r.message:String(r);throw console.error(`[create-sailor] Failed to generate env secrets: ${s}`),new Error(`Unable to write .env.local: ${s}`)}}import{execFileSync as ie}from"child_process";import{once as Vt}from"events";import g from"fs";import zt from"os";import _ from"path";import qt from"ignore";var Kt={mirror:{repo:process.env.SAILOR_TEMPLATE_REPO||"Nebutra/Sailor-Template",ref:process.env.SAILOR_TEMPLATE_REF||"main",applyIgnore:!1,allowMutableFallback:process.env.SAILOR_TEMPLATE_ALLOW_MUTABLE_FALLBACK==="1"},main:{repo:"Nebutra/Nebutra-Sailor",ref:"main",applyIgnore:!0,allowMutableFallback:process.env.SAILOR_TEMPLATE_ALLOW_MUTABLE_FALLBACK==="1"}},Ne=3e4,Pe=3;function re(e){return`${e.repo}@${e.ref}`}function Zt(){let e=process.env.SAILOR_TEMPLATE_LOCAL_DIR;if(!(!e||e.trim().length===0))return _.resolve(e.trim())}var Qt=new Set(["node_modules",".git","dist",".next",".turbo"]);function en(e,t){if(!g.statSync(e).isDirectory())throw new Error(`SAILOR_TEMPLATE_LOCAL_DIR is not a directory: ${e}`);g.cpSync(e,t,{recursive:!0,dereference:!0,filter:r=>{let s=_.basename(r);return!Qt.has(s)}})}function tn(e,t){return`https://github.com/${e}/archive/refs/heads/${t}.tar.gz`}function nn(e,t){return`https://github.com/${e}/archive/${t}.tar.gz`}function Le(e){return/^[0-9a-f]{7,40}$/i.test(e)}function ke(e,t){let n=`https://github.com/${e}.git`,r=[`refs/heads/${t}`,`refs/tags/${t}`];for(let s of r)try{let o=ie("git",["ls-remote",n,s],{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim();if(!o)continue;let[i]=o.split(/\s+/);if(Le(i))return i}catch{}return null}async function rn(e,t){if(Le(t))return t;let n;try{n=await fetch(`https://api.github.com/repos/${e}/commits/${encodeURIComponent(t)}`,{headers:{Accept:"application/vnd.github+json","User-Agent":"create-sailor"},signal:AbortSignal.timeout(2e4)})}catch(s){let o=ke(e,t);if(o)return o;throw new Error(`Cannot reach api.github.com to resolve ${e}@${t} (${se(s)})`)}if(!n.ok){let s=ke(e,t);if(s)return s;throw new Error(`Failed to resolve ${e}@${t} (GitHub API ${n.status})`)}let r=await n.json();if(typeof r.sha!="string"||r.sha.length===0)throw new Error(`GitHub API did not return a commit SHA for ${e}@${t}`);return r.sha}function Fe(e,t,n){let r=g.readdirSync(t,{withFileTypes:!0});for(let s of r){if(s.name==="node_modules"||s.name===".git")continue;let o=_.join(t,s.name),i=_.relative(e,o).split(_.sep).join("/");s.isDirectory()?(n.push(i+"/"),Fe(e,o,n)):n.push(i)}}function $e(e){let t=_.join(e,".templateignore");if(!g.existsSync(t))return;let n=g.readFileSync(t,"utf8"),r=qt().add(n),s=[];Fe(e,e,s);let o=s.map(a=>a.endsWith("/")?a.slice(0,-1):a),i=new Set(r.filter(o)),c=o.filter(a=>!i.has(a)).sort((a,l)=>l.split("/").length-a.split("/").length);for(let a of c){let l=_.join(e,a);try{g.existsSync(l)&&g.rmSync(l,{recursive:!0,force:!0})}catch{}}try{g.existsSync(t)&&g.rmSync(t,{force:!0})}catch{}}function se(e){if(!(e instanceof Error))return String(e);let t=e.cause,n=t&&typeof t=="object"&&"code"in t?String(t.code):void 0,r=t instanceof Error?t.message:void 0;switch(n){case"ENOTFOUND":case"EAI_AGAIN":return"DNS lookup failed \u2014 check your connection or proxy";case"ECONNREFUSED":return"connection refused \u2014 a proxy or firewall may be blocking github.com";case"ECONNRESET":return"connection reset by peer mid-transfer";case"ETIMEDOUT":case"UND_ERR_CONNECT_TIMEOUT":return"connection timed out";case"UND_ERR_HEADERS_TIMEOUT":case"UND_ERR_BODY_TIMEOUT":return"the server stopped sending data mid-transfer";case"CERT_HAS_EXPIRED":case"UNABLE_TO_VERIFY_LEAF_SIGNATURE":case"SELF_SIGNED_CERT_IN_CHAIN":return"TLS certificate rejected \u2014 a corporate proxy may be intercepting HTTPS";default:break}return e.name==="TimeoutError"?"request timed out":e.name==="AbortError"?e.message||"transfer aborted":r?`${e.message}: ${r}`:e.message}function sn(e){return e===408||e===425||e===429||e>=500}async function on(e,t,n){let r=new AbortController,s,o=()=>{s&&clearTimeout(s),s=setTimeout(()=>{r.abort(new Error(`no data received for ${Math.round(Ne/1e3)}s \u2014 transfer stalled`))},Ne)};o();try{let i=await fetch(e,{headers:{Accept:"application/octet-stream","User-Agent":"create-sailor"},signal:r.signal,redirect:"follow"});if(!i.ok){let f=new Error(`Failed to download archive (HTTP ${i.status} ${i.statusText})`);throw f.retryable=sn(i.status),f}if(!i.body)throw new Error("Download response had no body");let c=i.headers.get("content-length"),a=c&&/^\d+$/.test(c)?Number(c):void 0,l=g.createWriteStream(t),d=i.body.getReader(),u=0;try{for(;;){let{done:f,value:A}=await d.read();if(f)break;o(),u+=A.byteLength,l.write(A)||await Vt(l,"drain"),n?.(u,a)}await new Promise((f,A)=>{l.end(p=>p?A(p):f())})}catch(f){throw l.destroy(),await d.cancel().catch(()=>{}),f}if(u<1024)throw new Error(`Downloaded archive is unexpectedly small (${u} bytes)`);if(a!==void 0&&u!==a){let f=new Error(`Download truncated \u2014 got ${B(u)} of ${B(a)}`);throw f.retryable=!0,f}let y=g.statSync(t);if(y.size!==u)throw new Error(`Archive write verification failed (${y.size} != ${u})`);return{bytes:u}}finally{s&&clearTimeout(s)}}async function Ie(e,t,n,r){let s;for(let o=1;o<=Pe;o++)try{return r?.({phase:"download",source:n,attempt:o,receivedBytes:0}),await on(e,t,(i,c)=>{r?.({phase:"download",source:n,attempt:o,receivedBytes:i,totalBytes:c})})}catch(i){s=i instanceof Error?i:new Error(String(i));try{g.rmSync(t,{force:!0})}catch{}if(!(s.retryable??!0)||o===Pe)break;r?.({phase:"retry",source:n,attempt:o+1,reason:se(s)}),await new Promise(a=>setTimeout(a,o*1500))}throw new Error(se(s??new Error("unknown download failure")))}function an(e,t){try{ie("tar",["-xzf",e,"-C",t,"--strip-components=1"],{stdio:["ignore","ignore","pipe"]})}catch(n){let r=n.stderr?.toString().trim();throw new Error(`Failed to extract the template archive${r?`: ${r.split(`
733
+ `)[0]}`:""}`)}}function cn(){try{ie("tar",["--version"],{stdio:"ignore"})}catch{throw new Error("`tar` was not found on PATH. Install it (macOS/Linux ship it; on Windows use Git Bash or WSL) and retry.")}}function Oe(e){let t=0,n=r=>{for(let s of g.readdirSync(r,{withFileTypes:!0})){let o=_.join(r,s.name);if(s.isDirectory())n(o);else if(s.isFile())try{t+=g.statSync(o).size}catch{}}};try{n(e)}catch{}return t}function B(e){if(e<1024)return`${e} B`;let t=e/1e6;return t<1?`${Math.round(e/1024)} KB`:t<1e3?`${t.toFixed(1)} MB`:`${(t/1e3).toFixed(2)} GB`}function Re(e,t){let n=new Set(oe(t));return g.cpSync(e,t,{recursive:!0,force:!0}),oe(t).filter(r=>!n.has(r))}function oe(e){try{return g.readdirSync(e)}catch{return[]}}async function ln(e,t,n,r){let s=re(e);r?.({phase:"resolve",source:s});try{let o=await rn(e.repo,e.ref);await Ie(nn(e.repo,o),n,s,r)}catch(o){if(!e.allowMutableFallback)throw o;await Ie(tn(e.repo,e.ref),n,s,r)}if(r?.({phase:"extract",source:s}),an(n,t),e.applyIgnore){r?.({phase:"strip",source:s});try{$e(t)}catch(o){throw new Error(`Template downloaded, but .templateignore processing failed: ${o instanceof Error?o.message:String(o)}`)}}if(oe(t).length===0)throw new Error("The extracted template was empty")}async function De(e,t={}){let{onProgress:n}=t;cn(),g.mkdirSync(e,{recursive:!0});let r=g.mkdtempSync(_.join(zt.tmpdir(),"create-sailor-")),s=_.join(r,"template.tar.gz"),o=_.join(r,"tree"),i=()=>{g.rmSync(o,{recursive:!0,force:!0}),g.mkdirSync(o,{recursive:!0})};try{let c=Zt();if(c){if(!g.existsSync(c))throw new Error(`SAILOR_TEMPLATE_LOCAL_DIR does not exist: ${c}`);let u=`local:${c}`;n?.({phase:"extract",source:u}),i(),en(c,o),n?.({phase:"strip",source:u});try{$e(o)}catch(f){throw new Error(`Local template copied, but .templateignore processing failed: ${f instanceof Error?f.message:String(f)}`)}n?.({phase:"install",source:u});let y=Oe(o);return{source:u,bytes:y,createdEntries:Re(o,e)}}let l=process.env.SAILOR_TEMPLATE_SOURCE==="main"?["main"]:["mirror","main"],d=[];for(let u of l){let y=Kt[u];try{i(),await ln(y,o,s,n);let f=re(y);n?.({phase:"install",source:f});let A=Oe(o);return{source:f,bytes:A,createdEntries:Re(o,e)}}catch(f){d.push(` ${re(y)} \u2014 ${f instanceof Error?f.message:String(f)}`)}}throw new Error(`Could not download the Sailor template.
1514
734
 
1515
735
  Tried:
1516
- ${m.join(`
736
+ ${d.join(`
1517
737
  `)}
1518
738
 
1519
739
  The template is a large archive, so an unstable or filtered connection is the
@@ -1524,360 +744,16 @@ usual cause. Things that help, in order:
1524
744
  SAILOR_TEMPLATE_LOCAL_DIR=./Sailor-Template npx create-sailor@latest
1525
745
  3. Pin a different source: SAILOR_TEMPLATE_REPO / SAILOR_TEMPLATE_REF.
1526
746
 
1527
- Nothing was written to your project directory.`)}finally{C.rmSync(o,{recursive:!0,force:!0})}}import jt from"fs";import _n from"path";var Ia="node scripts/governance/lint-no-raw-inputs.mjs",Ta="node scripts/governance/lint-repository-seam.mjs",xa="node scripts/governance/lint-brand-literals.mjs",Ra="node scripts/governance/lint-microcopy.mjs",ka=/\bnode\s+scripts\/(governance\/)?lint-[\w-]+\.mjs/g,Na=/\bnode\s+scripts\/lint-[\w-]+\.mjs/,Oa={scanRoots:["apps"],primitivesImport:"@nebutra/ui/primitives",whitelist:["/storybook/src/stories/","/design-docs/src/components/previews/","/sailor-docs/src/components/previews/","\\.test\\.tsx?$","/__tests__/"]},La={coreDomains:["^packages/.*/(billing|license|metering|auth|audit|permissions|identity|tenant)/","^backends/gateway/src/routes/(billing|ai|admin|legal|integrations|webhooks)/","^apps/web/src/app/api/"],seamPaths:["^packages/platform/repositories/","^packages/platform/db/"],dbAccessors:["getTenantDb","getSystemDb"],allowlist:["apps/web/src/app/api/invitations/[invitationId]/accept/route.ts","apps/web/src/app/api/onboarding/invite-members/route.ts","apps/web/src/app/api/startup-os/projects/[projectId]/runs/[runId]/execute/route.ts","apps/web/src/app/api/cofounder/room/[profileId]/form-team/route.ts","apps/web/src/app/api/provider-keys/route.ts","backends/gateway/src/routes/admin/index.ts","backends/gateway/src/routes/ai/api-keys.ts","backends/gateway/src/routes/ai/usage.ts","backends/gateway/src/routes/billing/index.ts","backends/gateway/src/routes/integrations/index.ts","backends/gateway/src/routes/legal/consent.ts","backends/gateway/src/routes/webhooks/clerk.ts","backends/gateway/src/routes/webhooks/stripe.ts","packages/commerce/license/src/issue-license.ts","packages/commerce/license/src/validate-license.ts","packages/commerce/billing/src/credits/service.ts","packages/commerce/billing/src/usage/ledger.ts","packages/iam/audit/src/index.ts","packages/iam/auth/src/plugins/passkey-plugin.ts"]},Ua={scanRoots:["apps/web/src"],excludePaths:["/api/","\\.test\\.tsx?$","/__tests__/","/storybook/src/stories/","/design-docs/","/sailor-docs/"],bannedPatterns:[],allowlist:[]},rr={governedPaths:["apps","packages/commerce","packages/integrations/email"],allowExpressions:["Nebutra","\u4E91\u6BD3\u667A\u80FD","\u4E91\u6BD3","^https?://([a-z0-9-]+\\.)?nebutra\\.com(?:/|$)","^https?://([a-z0-9-]+\\.)?nebutra\\.ai(?:/|$)","#0033FE","#0BF1C3"],knownExemptPatterns:["\\.stories\\.tsx?$","/__tests__/","\\.test\\.tsx?$","/previews/","^packages/design/brand/","^packages/design/tokens/","^packages/design/design-tokens/"],allowlist:[]},Da=["apps/design-docs/mdx-components.tsx","apps/design-docs/next.config.ts","apps/design-docs/src/__registry__/index.tsx","apps/design-docs/src/app/[lang]/docs/[[...slug]]/page.tsx","apps/design-docs/src/app/[lang]/docs/layout.tsx","apps/design-docs/src/app/[lang]/layout.tsx","apps/design-docs/src/app/[lang]/registry/[name]/page.tsx","apps/design-docs/src/app/[lang]/registry/page.tsx","apps/design-docs/src/app/og/docs/[...slug]/route.tsx","apps/design-docs/src/components/brand-overview-visuals.tsx","apps/design-docs/src/components/color-usage.tsx","apps/design-docs/src/components/component-preview.tsx","apps/design-docs/src/components/gradient-demos.tsx","apps/design-docs/src/components/introduction-hero.tsx","apps/design-docs/src/components/motion-demos.tsx","apps/design-docs/src/components/pattern-demos.tsx","apps/design-docs/src/components/registry/registry-card.tsx","apps/design-docs/src/lib/github.ts","apps/design-docs/src/lib/registry-strings.ts","apps/design-docs/src/lib/registry.ts","apps/idp/src/app/layout.tsx","apps/idp/src/app/oauth/authorize/page.tsx","apps/idp/src/app/page.tsx","apps/idp/src/lib/oidc.ts","apps/landing/src/app/[lang]/layout.tsx","apps/landing/src/app/api/blog/comments/route.ts","apps/landing/src/app/api/blog/reactions/route.ts","apps/landing/src/app/api/og/route.ts","apps/landing/src/app/apple-icon.tsx","apps/landing/src/components/auth/google-one-tap.tsx","apps/landing/src/components/cookie-consent-banner.tsx","apps/landing/src/components/marketing/ProductHuntSection.tsx","apps/landing/src/components/marketing/TestimonialsSection.tsx","apps/landing/src/i18n/request.ts","apps/landing/src/lib/analytics/emit.ts","apps/landing/src/lib/blog-fallback.ts","apps/landing/src/lib/constants/playbook-data.ts","apps/landing/src/lib/constants/resources-data.ts","apps/landing/src/lib/constants/solutions-data.ts","apps/landing/src/lib/docs-links.ts","apps/landing/src/lib/env.ts","apps/landing/src/lib/landing-content.ts","apps/landing/src/lib/seo/metadata.ts","apps/landing/src/lib/seo/site-routes.ts","apps/landing/src/lib/status-checks.ts","apps/landing/src/proxy.ts","apps/mail-preview/scripts/export.ts","apps/mail-preview/scripts/render-react-templates.ts","apps/mail-preview/src/app/api/send-test/route.ts","apps/mail-preview/src/app/layout.tsx","apps/mail-preview/src/lib/fixtures.ts","apps/web/next.config.ts","apps/web/src/app/(app)/checkout-return/page.tsx","apps/web/src/app/(app)/choose-plan/page.tsx","apps/web/src/app/(app)/settings/api-keys/page.tsx","apps/web/src/app/(app)/settings/notifications/page.tsx","apps/web/src/app/(app)/settings/provider-keys/page.tsx","apps/web/src/app/(auth)/desktop-auth/complete/page.tsx","apps/web/src/app/(public)/page.tsx","apps/web/src/app/[locale]/welcome/page.tsx","apps/web/src/app/api/account/email-change/route.ts","apps/web/src/app/api/admin/access-invites/route.ts","apps/web/src/app/api/blog/comments/route.ts","apps/web/src/app/api/blog/reactions/route.ts","apps/web/src/app/api/me/public/route.ts","apps/web/src/app/api/organizations/[orgId]/members/route.ts","apps/web/src/app/global-error.tsx","apps/web/src/components/ErrorBoundary.tsx","apps/web/src/components/api-keys/create-api-key-dialog.tsx","apps/web/src/components/appearance/store.ts","apps/web/src/components/auth/auth-banner.tsx","apps/web/src/components/auth/desktop-auth-complete-handoff.tsx","apps/web/src/components/auth/sign-up-form.tsx","apps/web/src/components/billing/billing-self-service.tsx","apps/web/src/components/navigation/public-page-chrome.tsx","apps/web/src/components/notifications/inbox-bell.tsx","apps/web/src/components/notifications/notification-inbox-preview.tsx","apps/web/src/components/notifications/notification-preference-matrix.tsx","apps/web/src/components/notifications/notification-runtime-banner.tsx","apps/web/src/components/settings/security/security-capabilities.ts","apps/web/src/components/settings/security/security-settings-client.tsx","apps/web/src/components/theme-playground/design-md-import.tsx","apps/web/src/components/theme-playground/theme-playground-workbench.tsx","apps/web/src/components/theme-playground/theme-token-data.ts","apps/web/src/lib/api.ts","apps/web/src/lib/api/client.ts","apps/web/src/lib/env.ts","apps/web/src/lib/integrations/catalog.tsx","apps/web/src/lib/permissions.ts","apps/web/src/lib/public-url-defaults.ts","apps/web/src/lib/session-hint.ts","apps/web/src/lib/startup-os/company-context/generate.ts","apps/web/src/lib/startup-os/company-context/projection.ts","apps/web/src/lib/startup-os/conversation.ts","apps/web/src/lib/startup-os/execution.ts","apps/web/src/lib/startup-os/files.ts","apps/web/src/proxy.ts","packages/commerce/billing/src/index.ts","packages/commerce/contracts/src/identity.ts","packages/commerce/legal/src/components/CookieBanner.tsx","packages/commerce/legal/src/documents/config.ts","packages/commerce/legal/src/index.ts","packages/commerce/marketing/src/components/ProductHuntBadge.tsx","packages/commerce/marketing/src/config/index.ts","packages/commerce/marketing/src/index.ts","packages/integrations/email/src/index.ts","packages/integrations/email/src/templates/_layout.ts","packages/integrations/email/src/templates/invitation.tsx"];function Ma(e,t){let n=(e??"").replace(ka,"").split("&&").map(r=>r.trim()).filter(Boolean).join(" && ");return[n.length>0?n:"biome check .",...t].join(" && ")}function Fa(e){let t=new RegExp(rr.allowExpressions.join("|"));return Da.filter(n=>{try{return t.test(jt.readFileSync(_n.join(e,n),"utf8"))}catch{return!1}})}async function sr(e,t){let n=t.database!=="none",o=[Ia];n&&o.push(Ta),o.push(xa),o.push(Ra);let r={...rr,allowlist:Fa(e)},s={rawInputs:Oa,brandLiterals:r,microcopyRules:Ua};n&&(s.repositorySeam=La);let i=_n.join(e,"governance.config.json");jt.writeFileSync(i,JSON.stringify(s,null,2)+`
1528
- `);let a=_n.join(e,"package.json"),c;try{c=jt.readFileSync(a,"utf8")}catch(p){if(p.code!=="ENOENT")throw p}if(c!==void 0){let p=JSON.parse(c);p.scripts=p.scripts??{};for(let[m,d]of Object.entries(p.scripts))m!=="lint"&&typeof d=="string"&&Na.test(d)&&delete p.scripts[m];p.scripts.lint=Ma(p.scripts.lint,o),jt.writeFileSync(a,JSON.stringify(p,null,2)+`
1529
- `)}return{lints:o,configWritten:!0}}import ne from"fs";import le from"path";import{fileURLToPath as ja}from"url";var Ba=ja(import.meta.url),ir=le.dirname(Ba),ar="scaffold-meta.json",cr=".nebutra";function $a(e){if(e)return e;let t=[le.join(ir,"..","templates"),le.join(ir,"..","..","templates")];for(let n of t)if(ne.existsSync(n))return n;return t[0]}function pr(e,t){let n=[],o=$a(t.templatesRoot),r=le.join(e,"LICENSE");if(ne.existsSync(r)){let p=ne.readFileSync(r,"utf-8").slice(0,256);(/GNU AFFERO GENERAL PUBLIC LICENSE/i.test(p)||/Functional Source License/i.test(p))&&(ne.renameSync(r,le.join(e,"LICENSE-UPSTREAM-REFERENCE.md")),n.push("LICENSE-UPSTREAM-REFERENCE.md (preserved upstream licence)"))}let s=le.join(o,"LICENSE-SCAFFOLD.md");if(!ne.existsSync(s))throw new Error(`LICENSE-SCAFFOLD.md template missing at ${s}; cannot emit license.`);ne.copyFileSync(s,le.join(e,"LICENSE")),n.push("LICENSE");let i={schemaVersion:1,cliVersion:t.cliVersion,scaffoldedAt:new Date().toISOString(),projectName:t.projectName,purpose:"Records which `create-sailor` version produced this project, for support triage. Provenance only: the MIT licence in LICENSE applies unconditionally and does not depend on this file. Deleting it costs you nothing.",license:{tier:"mit-scaffold",file:"LICENSE"}},a=le.join(e,cr);ne.mkdirSync(a,{recursive:!0}),ne.writeFileSync(le.join(a,ar),`${JSON.stringify(i,null,2)}
1530
- `),n.push(`${cr}/${ar}`);let c=le.join(e,"README.md");if(ne.existsSync(c)){let p=ne.readFileSync(c,"utf-8");p.includes("Scaffolded by `create-sailor`")||(ne.writeFileSync(c,`> **License notice:** Scaffolded by \`create-sailor\`. The Nebutra-Sailor
747
+ Nothing was written to your project directory.`)}finally{g.rmSync(r,{recursive:!0,force:!0})}}import K from"fs";import ae from"path";var pn="node scripts/governance/lint-no-raw-inputs.mjs",un="node scripts/governance/lint-repository-seam.mjs",dn="node scripts/governance/lint-brand-literals.mjs",mn="node scripts/governance/lint-microcopy.mjs",fn=/\bnode\s+scripts\/(governance\/)?lint-[\w-]+\.mjs/g,gn=/\bnode\s+scripts\/lint-[\w-]+\.mjs/,yn={scanRoots:["apps"],primitivesImport:"@nebutra/ui/primitives",whitelist:["/storybook/src/stories/","/design-docs/src/components/previews/","/sailor-docs/src/components/previews/","\\.test\\.tsx?$","/__tests__/"]},hn={coreDomains:["^packages/.*/(billing|license|metering|auth|audit|permissions|identity|tenant)/","^backends/gateway/src/routes/(billing|ai|admin|legal|integrations|webhooks)/","^apps/web/src/app/api/"],seamPaths:["^packages/platform/repositories/","^packages/platform/db/"],dbAccessors:["getTenantDb","getSystemDb"],allowlist:["apps/web/src/app/api/invitations/[invitationId]/accept/route.ts","apps/web/src/app/api/onboarding/invite-members/route.ts","apps/web/src/app/api/startup-os/projects/[projectId]/runs/[runId]/execute/route.ts","apps/web/src/app/api/cofounder/room/[profileId]/form-team/route.ts","apps/web/src/app/api/provider-keys/route.ts","backends/gateway/src/routes/admin/index.ts","backends/gateway/src/routes/ai/api-keys.ts","backends/gateway/src/routes/ai/usage.ts","backends/gateway/src/routes/billing/index.ts","backends/gateway/src/routes/integrations/index.ts","backends/gateway/src/routes/legal/consent.ts","backends/gateway/src/routes/webhooks/clerk.ts","backends/gateway/src/routes/webhooks/stripe.ts","packages/commerce/license/src/issue-license.ts","packages/commerce/license/src/validate-license.ts","packages/commerce/billing/src/credits/service.ts","packages/commerce/billing/src/usage/ledger.ts","packages/iam/audit/src/index.ts","packages/iam/auth/src/plugins/passkey-plugin.ts"]},bn={scanRoots:["apps/web/src"],excludePaths:["/api/","\\.test\\.tsx?$","/__tests__/","/storybook/src/stories/","/design-docs/","/sailor-docs/"],bannedPatterns:[],allowlist:[]},je={governedPaths:["apps","packages/commerce","packages/integrations/email"],allowExpressions:["Nebutra","\u4E91\u6BD3\u667A\u80FD","\u4E91\u6BD3","^https?://([a-z0-9-]+\\.)?nebutra\\.com(?:/|$)","^https?://([a-z0-9-]+\\.)?nebutra\\.ai(?:/|$)","#0033FE","#0BF1C3"],knownExemptPatterns:["\\.stories\\.tsx?$","/__tests__/","\\.test\\.tsx?$","/previews/","^packages/design/brand/","^packages/design/tokens/","^packages/design/design-tokens/"],allowlist:[]},En=["apps/design-docs/mdx-components.tsx","apps/design-docs/next.config.ts","apps/design-docs/src/__registry__/index.tsx","apps/design-docs/src/app/[lang]/docs/[[...slug]]/page.tsx","apps/design-docs/src/app/[lang]/docs/layout.tsx","apps/design-docs/src/app/[lang]/layout.tsx","apps/design-docs/src/app/[lang]/registry/[name]/page.tsx","apps/design-docs/src/app/[lang]/registry/page.tsx","apps/design-docs/src/app/og/docs/[...slug]/route.tsx","apps/design-docs/src/components/brand-overview-visuals.tsx","apps/design-docs/src/components/color-usage.tsx","apps/design-docs/src/components/component-preview.tsx","apps/design-docs/src/components/gradient-demos.tsx","apps/design-docs/src/components/introduction-hero.tsx","apps/design-docs/src/components/motion-demos.tsx","apps/design-docs/src/components/pattern-demos.tsx","apps/design-docs/src/components/registry/registry-card.tsx","apps/design-docs/src/lib/github.ts","apps/design-docs/src/lib/registry-strings.ts","apps/design-docs/src/lib/registry.ts","apps/idp/src/app/layout.tsx","apps/idp/src/app/oauth/authorize/page.tsx","apps/idp/src/app/page.tsx","apps/idp/src/lib/oidc.ts","apps/landing/src/app/[lang]/layout.tsx","apps/landing/src/app/api/blog/comments/route.ts","apps/landing/src/app/api/blog/reactions/route.ts","apps/landing/src/app/api/og/route.ts","apps/landing/src/app/apple-icon.tsx","apps/landing/src/components/auth/google-one-tap.tsx","apps/landing/src/components/cookie-consent-banner.tsx","apps/landing/src/components/marketing/ProductHuntSection.tsx","apps/landing/src/components/marketing/TestimonialsSection.tsx","apps/landing/src/i18n/request.ts","apps/landing/src/lib/analytics/emit.ts","apps/landing/src/lib/blog-fallback.ts","apps/landing/src/lib/constants/playbook-data.ts","apps/landing/src/lib/constants/resources-data.ts","apps/landing/src/lib/constants/solutions-data.ts","apps/landing/src/lib/docs-links.ts","apps/landing/src/lib/env.ts","apps/landing/src/lib/landing-content.ts","apps/landing/src/lib/seo/metadata.ts","apps/landing/src/lib/seo/site-routes.ts","apps/landing/src/lib/status-checks.ts","apps/landing/src/proxy.ts","apps/mail-preview/scripts/export.ts","apps/mail-preview/scripts/render-react-templates.ts","apps/mail-preview/src/app/api/send-test/route.ts","apps/mail-preview/src/app/layout.tsx","apps/mail-preview/src/lib/fixtures.ts","apps/web/next.config.ts","apps/web/src/app/(app)/checkout-return/page.tsx","apps/web/src/app/(app)/choose-plan/page.tsx","apps/web/src/app/(app)/settings/api-keys/page.tsx","apps/web/src/app/(app)/settings/notifications/page.tsx","apps/web/src/app/(app)/settings/provider-keys/page.tsx","apps/web/src/app/(auth)/desktop-auth/complete/page.tsx","apps/web/src/app/(public)/page.tsx","apps/web/src/app/[locale]/welcome/page.tsx","apps/web/src/app/api/account/email-change/route.ts","apps/web/src/app/api/admin/access-invites/route.ts","apps/web/src/app/api/blog/comments/route.ts","apps/web/src/app/api/blog/reactions/route.ts","apps/web/src/app/api/me/public/route.ts","apps/web/src/app/api/organizations/[orgId]/members/route.ts","apps/web/src/app/global-error.tsx","apps/web/src/components/ErrorBoundary.tsx","apps/web/src/components/api-keys/create-api-key-dialog.tsx","apps/web/src/components/appearance/store.ts","apps/web/src/components/auth/auth-banner.tsx","apps/web/src/components/auth/desktop-auth-complete-handoff.tsx","apps/web/src/components/auth/sign-up-form.tsx","apps/web/src/components/billing/billing-self-service.tsx","apps/web/src/components/navigation/public-page-chrome.tsx","apps/web/src/components/notifications/inbox-bell.tsx","apps/web/src/components/notifications/notification-inbox-preview.tsx","apps/web/src/components/notifications/notification-preference-matrix.tsx","apps/web/src/components/notifications/notification-runtime-banner.tsx","apps/web/src/components/settings/security/security-capabilities.ts","apps/web/src/components/settings/security/security-settings-client.tsx","apps/web/src/components/theme-playground/design-md-import.tsx","apps/web/src/components/theme-playground/theme-playground-workbench.tsx","apps/web/src/components/theme-playground/theme-token-data.ts","apps/web/src/lib/api.ts","apps/web/src/lib/api/client.ts","apps/web/src/lib/env.ts","apps/web/src/lib/integrations/catalog.tsx","apps/web/src/lib/permissions.ts","apps/web/src/lib/public-url-defaults.ts","apps/web/src/lib/session-hint.ts","apps/web/src/lib/startup-os/company-context/generate.ts","apps/web/src/lib/startup-os/company-context/projection.ts","apps/web/src/lib/startup-os/conversation.ts","apps/web/src/lib/startup-os/execution.ts","apps/web/src/lib/startup-os/files.ts","apps/web/src/proxy.ts","packages/commerce/billing/src/index.ts","packages/commerce/contracts/src/identity.ts","packages/commerce/legal/src/components/CookieBanner.tsx","packages/commerce/legal/src/documents/config.ts","packages/commerce/legal/src/index.ts","packages/commerce/marketing/src/components/ProductHuntBadge.tsx","packages/commerce/marketing/src/config/index.ts","packages/commerce/marketing/src/index.ts","packages/integrations/email/src/index.ts","packages/integrations/email/src/templates/_layout.ts","packages/integrations/email/src/templates/invitation.tsx"];function wn(e,t){let n=(e??"").replace(fn,"").split("&&").map(s=>s.trim()).filter(Boolean).join(" && ");return[n.length>0?n:"biome check .",...t].join(" && ")}function vn(e){let t=new RegExp(je.allowExpressions.join("|"));return En.filter(n=>{try{return t.test(K.readFileSync(ae.join(e,n),"utf8"))}catch{return!1}})}async function Me(e){let t=[pn,un,dn,mn],n={...je,allowlist:vn(e)},r={rawInputs:yn,brandLiterals:n,microcopyRules:bn,repositorySeam:hn},s=ae.join(e,"governance.config.json");K.writeFileSync(s,JSON.stringify(r,null,2)+`
748
+ `);let o=ae.join(e,"package.json"),i;try{i=K.readFileSync(o,"utf8")}catch(c){if(c.code!=="ENOENT")throw c}if(i!==void 0){let c=JSON.parse(i);c.scripts=c.scripts??{};for(let[a,l]of Object.entries(c.scripts))a!=="lint"&&typeof l=="string"&&gn.test(l)&&delete c.scripts[a];c.scripts.lint=wn(c.scripts.lint,t),K.writeFileSync(o,JSON.stringify(c,null,2)+`
749
+ `)}return{lints:t,configWritten:!0}}import{createHmac as Mr}from"crypto";import N from"fs";import k from"path";import{fileURLToPath as Sn}from"url";var Cn=Sn(import.meta.url),Be=k.dirname(Cn),Ue="scaffold-meta.json",Ge=".nebutra";function An(e){if(e)return e;let t=[k.join(Be,"..","templates"),k.join(Be,"..","..","templates")];for(let n of t)if(N.existsSync(n))return n;return t[0]}function Ye(e,t){let n=[],r=An(t.templatesRoot),s=k.join(e,"LICENSE");if(N.existsSync(s)){let l=N.readFileSync(s,"utf-8").slice(0,256);(/GNU AFFERO GENERAL PUBLIC LICENSE/i.test(l)||/Functional Source License/i.test(l))&&(N.renameSync(s,k.join(e,"LICENSE-UPSTREAM-REFERENCE.md")),n.push("LICENSE-UPSTREAM-REFERENCE.md (preserved upstream licence)"))}let o=k.join(r,"LICENSE-SCAFFOLD.md");if(!N.existsSync(o))throw new Error(`LICENSE-SCAFFOLD.md template missing at ${o}; cannot emit license.`);N.copyFileSync(o,k.join(e,"LICENSE")),n.push("LICENSE");let i={schemaVersion:1,cliVersion:t.cliVersion,scaffoldedAt:new Date().toISOString(),projectName:t.projectName,purpose:"Records which `create-sailor` version produced this project, for support triage. Provenance only: the MIT licence in LICENSE applies unconditionally and does not depend on this file. Deleting it costs you nothing.",license:{tier:"mit-scaffold",file:"LICENSE"}},c=k.join(e,Ge);N.mkdirSync(c,{recursive:!0}),N.writeFileSync(k.join(c,Ue),`${JSON.stringify(i,null,2)}
750
+ `),n.push(`${Ge}/${Ue}`);let a=k.join(e,"README.md");if(N.existsSync(a)){let l=N.readFileSync(a,"utf-8");l.includes("Scaffolded by `create-sailor`")||(N.writeFileSync(a,`> **License notice:** Scaffolded by \`create-sailor\`. The Nebutra-Sailor
1531
751
  > code in this project is [MIT licensed](./LICENSE) \u2014 commercial use, closed
1532
752
  > source, no fee, no attribution required. The upstream monorepo is
1533
753
  > FSL-1.1-ALv2 \u2014 see \`LICENSE-UPSTREAM-REFERENCE.md\`.
1534
754
 
1535
- `+p),n.push("README.md (prepended license notice)"))}return{wrote:n}}import Ke from"fs";import wn from"path";async function lr(e,t){let n=wn.join(e,"packages","mcp");if(t==="off"){Ke.existsSync(n)&&Ke.rmSync(n,{recursive:!0,force:!0});let r=wn.join(e,"packages","cli","src","commands","mcp-server.ts");Ke.existsSync(r)&&Ke.rmSync(r,{force:!0});return}let o=wn.join(e,".env.example");if(Ke.existsSync(o)){let r=Ke.readFileSync(o,"utf-8"),s="# MCP Server (Agent-friendly API)";if(!r.includes(s)){let i=["","# =============================================",s,"# Docs: https://modelcontextprotocol.io","# =============================================","# MCP server exposes your SaaS capabilities to AI agents via MCP protocol.","# Run: pnpm mcp-server (starts stdio server)","# No config required for stdio transport. HTTP transport needs:",'MCP_HTTP_PORT="3100"','MCP_API_KEY=""',""].join(`
1536
- `);Ke.appendFileSync(o,i)}}}import _t from"fs";import dr from"path";async function ur(e,t,n){let o=dr.join(e,"packages","metering"),r;if(t==="auto"?r=n!=="none"?"on":"off":t==="on"||t==="off"?r=t:r="off",r==="off"){_t.existsSync(o)&&_t.rmSync(o,{recursive:!0,force:!0});return}let s=dr.join(e,".env.example");if(!_t.existsSync(s))return;let i=_t.readFileSync(s,"utf-8"),a="# Metering (usage-based billing)";if(!i.includes(a)){let c=["","# =============================================",a,"# Docs: see packages/commerce/metering/README.md","# =============================================","# ClickHouse-based metering pipeline for consumption billing.",'CLICKHOUSE_URL=""','CLICKHOUSE_USER="default"','CLICKHOUSE_PASSWORD=""','CLICKHOUSE_DATABASE="metering"',""].join(`
1537
- `);_t.appendFileSync(s,c)}}import fe from"fs";import j from"path";var mr=[{id:"sentry",name:"Sentry",region:"both",envVars:["SENTRY_DSN","NEXT_PUBLIC_SENTRY_DSN"],docs:"https://docs.sentry.io",pkg:"@sentry/nextjs",instrumentationFile:"apps/web/instrumentation.ts"},{id:"datadog",name:"Datadog",region:"global",envVars:["DD_API_KEY","DD_APP_KEY","DD_SITE"],docs:"https://docs.datadoghq.com",pkg:"dd-trace",instrumentationFile:"apps/web/instrumentation.ts"},{id:"bugsnag",name:"Bugsnag",region:"global",envVars:["BUGSNAG_API_KEY","NEXT_PUBLIC_BUGSNAG_API_KEY"],docs:"https://docs.bugsnag.com",pkg:"@bugsnag/js",instrumentationFile:"apps/web/src/lib/monitoring/bugsnag.ts"},{id:"aliyun-arms",name:"\u963F\u91CC\u4E91 ARMS",region:"cn",envVars:["ALIYUN_ARMS_LICENSE_KEY","ALIYUN_ARMS_APP_NAME","NEXT_PUBLIC_ARMS_PID"],docs:"https://help.aliyun.com/product/34364.html",instrumentationFile:"apps/web/src/components/monitoring/ArmsScript.tsx"},{id:"tingyun",name:"\u542C\u4E91 TingYun",region:"cn",envVars:["TINGYUN_LICENSE_KEY","NEXT_PUBLIC_TINGYUN_APP_KEY"],docs:"https://www.tingyun.com/docs",instrumentationFile:"apps/web/src/components/monitoring/TingYunScript.tsx"},{id:"none",name:"None",region:"both",envVars:[],docs:""}],eu=mr.reduce((e,t)=>(e[t.region]||(e[t.region]=[]),e[t.region].push(t),e),{});function gr(e){return mr.find(t=>t.id===e)}var Pn={sentry:{"@sentry/nextjs":"^8.45.0"},datadog:{"dd-trace":"^5.32.0","@datadog/browser-rum":"^5.30.0"},bugsnag:{"@bugsnag/js":"^8.2.0","@bugsnag/plugin-react":"^8.2.0"}};function Va(e){fe.mkdirSync(e,{recursive:!0})}function An(e,t){let n=j.join(e,"package.json");if(!fe.existsSync(n))return;let o;try{o=JSON.parse(fe.readFileSync(n,"utf-8"))}catch(a){console.error(`Failed to parse ${n}:`,a);return}let s={...o.dependencies??{}};for(let[a,c]of Object.entries(t))s[a]||(s[a]=c);let i={...o,dependencies:s};fe.writeFileSync(n,JSON.stringify(i,null,2)+`
1538
- `)}function de(e,t){Va(j.dirname(e)),fe.writeFileSync(e,t)}function wt(e,t){let n=j.join(e,".env.example");fe.existsSync(n)?fe.appendFileSync(n,`
1539
- `+t):fe.writeFileSync(n,t)}function Ka(e){let t=`import * as Sentry from "@sentry/nextjs";
1540
-
1541
- Sentry.init({
1542
- dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
1543
- tracesSampleRate: 1.0,
1544
- replaysOnErrorSampleRate: 1.0,
1545
- replaysSessionSampleRate: 0.1,
1546
- integrations: [
1547
- Sentry.replayIntegration({
1548
- maskAllText: true,
1549
- blockAllMedia: true,
1550
- }),
1551
- ],
1552
- environment: process.env.NODE_ENV,
1553
- });
1554
- `,n=`import * as Sentry from "@sentry/nextjs";
1555
-
1556
- Sentry.init({
1557
- dsn: process.env.SENTRY_DSN ?? process.env.NEXT_PUBLIC_SENTRY_DSN,
1558
- tracesSampleRate: 1.0,
1559
- environment: process.env.NODE_ENV,
1560
- });
1561
- `,o=`import * as Sentry from "@sentry/nextjs";
1562
-
1563
- Sentry.init({
1564
- dsn: process.env.SENTRY_DSN ?? process.env.NEXT_PUBLIC_SENTRY_DSN,
1565
- tracesSampleRate: 1.0,
1566
- environment: process.env.NODE_ENV,
1567
- });
1568
- `,r=`/**
1569
- * Next.js instrumentation hook \u2014 wires Sentry into Node and Edge runtimes.
1570
- * See https://nextjs.org/docs/app/building-your-application/optimizing/instrumentation
1571
- */
1572
- export async function register() {
1573
- if (process.env.NEXT_RUNTIME === "nodejs") {
1574
- await import("./sentry.server.config");
1575
- }
1576
- if (process.env.NEXT_RUNTIME === "edge") {
1577
- await import("./sentry.edge.config");
1578
- }
1579
- }
1580
-
1581
- export { captureRequestError as onRequestError } from "@sentry/nextjs";
1582
- `;de(j.join(e,"sentry.client.config.ts"),t),de(j.join(e,"sentry.server.config.ts"),n),de(j.join(e,"sentry.edge.config.ts"),o),de(j.join(e,"instrumentation.ts"),r)}function Ya(){return`# Sentry \u2014 error tracking + performance monitoring
1583
- # Get your DSN from https://sentry.io/settings/projects/<project>/keys/
1584
- SENTRY_DSN=
1585
- NEXT_PUBLIC_SENTRY_DSN=
1586
- SENTRY_ORG=
1587
- SENTRY_PROJECT=
1588
- SENTRY_AUTH_TOKEN=
1589
- `}function Ga(e){let t=`/**
1590
- * Next.js instrumentation hook \u2014 wires Datadog APM (dd-trace) into Node runtime.
1591
- * Edge runtime is not supported by dd-trace; only nodejs is initialized.
1592
- */
1593
- export async function register() {
1594
- if (process.env.NEXT_RUNTIME === "nodejs") {
1595
- const tracer = (await import("dd-trace")).default;
1596
- tracer.init({
1597
- service: process.env.DD_SERVICE ?? "sailor-web",
1598
- env: process.env.DD_ENV ?? process.env.NODE_ENV,
1599
- version: process.env.DD_VERSION,
1600
- logInjection: true,
1601
- runtimeMetrics: true,
1602
- profiling: true,
1603
- });
1604
- }
1605
- }
1606
- `,n=`"use client";
1607
- /**
1608
- * Datadog RUM (Real User Monitoring) \u2014 browser-side tracking.
1609
- * Initialize once in your root layout via <DatadogRumProvider />.
1610
- */
1611
- import { datadogRum } from "@datadog/browser-rum";
1612
-
1613
- let initialized = false;
1614
-
1615
- export function initDatadogRum(): void {
1616
- if (initialized || typeof window === "undefined") return;
1617
- const applicationId = process.env.NEXT_PUBLIC_DD_APPLICATION_ID;
1618
- const clientToken = process.env.NEXT_PUBLIC_DD_CLIENT_TOKEN;
1619
- if (!applicationId || !clientToken) return;
1620
-
1621
- datadogRum.init({
1622
- applicationId,
1623
- clientToken,
1624
- site: process.env.NEXT_PUBLIC_DD_SITE ?? "datadoghq.com",
1625
- service: process.env.NEXT_PUBLIC_DD_SERVICE ?? "sailor-web",
1626
- env: process.env.NEXT_PUBLIC_DD_ENV ?? "production",
1627
- version: process.env.NEXT_PUBLIC_DD_VERSION,
1628
- sessionSampleRate: 100,
1629
- sessionReplaySampleRate: 20,
1630
- trackUserInteractions: true,
1631
- trackResources: true,
1632
- trackLongTasks: true,
1633
- defaultPrivacyLevel: "mask-user-input",
1634
- });
1635
- initialized = true;
1636
- }
1637
- `;de(j.join(e,"instrumentation.ts"),t),de(j.join(e,"src","lib","monitoring","datadog-rum.ts"),n)}function qa(){return`# Datadog \u2014 APM + RUM
1638
- # Server APM (dd-trace)
1639
- DD_API_KEY=
1640
- DD_APP_KEY=
1641
- DD_SITE=datadoghq.com
1642
- DD_SERVICE=sailor-web
1643
- DD_ENV=production
1644
- DD_VERSION=
1645
- # Browser RUM
1646
- NEXT_PUBLIC_DD_APPLICATION_ID=
1647
- NEXT_PUBLIC_DD_CLIENT_TOKEN=
1648
- NEXT_PUBLIC_DD_SITE=datadoghq.com
1649
- NEXT_PUBLIC_DD_SERVICE=sailor-web
1650
- NEXT_PUBLIC_DD_ENV=production
1651
- NEXT_PUBLIC_DD_VERSION=
1652
- `}function Ha(e){let t=`/**
1653
- * Bugsnag server-side initialization.
1654
- * Import this once at the top of your server entrypoint or a shared init module.
1655
- */
1656
- import Bugsnag from "@bugsnag/js";
1657
-
1658
- let started = false;
1659
-
1660
- export function startBugsnagServer(): void {
1661
- if (started) return;
1662
- const apiKey = process.env.BUGSNAG_API_KEY;
1663
- if (!apiKey) return;
1664
-
1665
- Bugsnag.start({
1666
- apiKey,
1667
- releaseStage: process.env.NODE_ENV,
1668
- appVersion: process.env.APP_VERSION,
1669
- });
1670
- started = true;
1671
- }
1672
- `,n=`"use client";
1673
- /**
1674
- * Bugsnag browser initialization \u2014 wrap your app root with the returned provider.
1675
- */
1676
- import Bugsnag from "@bugsnag/js";
1677
- import BugsnagPluginReact from "@bugsnag/plugin-react";
1678
- import React from "react";
1679
-
1680
- let errorBoundary: React.ComponentType<{
1681
- children: React.ReactNode;
1682
- FallbackComponent?: React.ComponentType<{ error: Error }>;
1683
- }> | null = null;
1684
-
1685
- export function getBugsnagErrorBoundary() {
1686
- if (errorBoundary) return errorBoundary;
1687
- const apiKey = process.env.NEXT_PUBLIC_BUGSNAG_API_KEY;
1688
- if (!apiKey || typeof window === "undefined") return null;
1689
-
1690
- Bugsnag.start({
1691
- apiKey,
1692
- plugins: [new BugsnagPluginReact()],
1693
- releaseStage: process.env.NODE_ENV,
1694
- });
1695
-
1696
- const plugin = Bugsnag.getPlugin("react");
1697
- if (!plugin) return null;
1698
- errorBoundary = plugin.createErrorBoundary(React) as typeof errorBoundary;
1699
- return errorBoundary;
1700
- }
1701
- `;de(j.join(e,"src","lib","monitoring","bugsnag.server.ts"),t),de(j.join(e,"src","lib","monitoring","bugsnag.client.tsx"),n)}function Wa(){return`# Bugsnag \u2014 error tracking
1702
- BUGSNAG_API_KEY=
1703
- NEXT_PUBLIC_BUGSNAG_API_KEY=
1704
- APP_VERSION=
1705
- `}function Xa(e){de(j.join(e,"src","components","monitoring","ArmsScript.tsx"),`"use client";
1706
- /**
1707
- * \u963F\u91CC\u4E91 ARMS (Application Real-time Monitoring Service) browser agent.
1708
- * Injects the official JS agent via next/script.
1709
- * Docs: https://help.aliyun.com/document_detail/58655.html
1710
- */
1711
- import Script from "next/script";
1712
-
1713
- export function ArmsScript() {
1714
- const pid = process.env.NEXT_PUBLIC_ARMS_PID;
1715
- if (!pid) return null;
1716
- return (
1717
- <Script
1718
- id="aliyun-arms"
1719
- strategy="afterInteractive"
1720
- dangerouslySetInnerHTML={{
1721
- __html: \`!(function(c,b,d,a){c[a]||(c[a]={});c[a].config={pid:"\${pid}",imgUrl:"https://arms-retcode.aliyuncs.com/r.png?",sendResource:true,enableLinkTrace:true,behavior:true};with(b)with(body)with(insertBefore(createElement("script"),firstChild))setAttribute("crossorigin","",src=d)})(window,document,"https://retcode.alicdn.com/retcode/bl.js","__bl");\`,
1722
- }}
1723
- />
1724
- );
1725
- }
1726
- `)}function za(){return`# \u963F\u91CC\u4E91 ARMS \u2014 APM (\u4E2D\u56FD\u533A)
1727
- ALIYUN_ARMS_LICENSE_KEY=
1728
- ALIYUN_ARMS_APP_NAME=
1729
- NEXT_PUBLIC_ARMS_PID=
1730
- `}function Ja(e){de(j.join(e,"src","components","monitoring","TingYunScript.tsx"),`"use client";
1731
- /**
1732
- * \u542C\u4E91 Browser Agent (\u524D\u7AEF\u6027\u80FD\u76D1\u63A7).
1733
- * Docs: https://www.tingyun.com/docs
1734
- */
1735
- import Script from "next/script";
1736
-
1737
- export function TingYunScript() {
1738
- const appKey = process.env.NEXT_PUBLIC_TINGYUN_APP_KEY;
1739
- if (!appKey) return null;
1740
- return (
1741
- <Script
1742
- id="tingyun-browser"
1743
- strategy="afterInteractive"
1744
- src={\`https://rs.tingyun.com/rum/\${appKey}.js\`}
1745
- />
1746
- );
1747
- }
1748
- `)}function Qa(){return`# \u542C\u4E91 TingYun \u2014 APM (\u4E2D\u56FD\u533A)
1749
- TINGYUN_LICENSE_KEY=
1750
- NEXT_PUBLIC_TINGYUN_APP_KEY=
1751
- `}async function fr(e,t,n="global"){let o=gr(t);if(!o||o.id==="none")return;let r=j.join(e,"apps","web");if(!fe.existsSync(r))return;let s=o.id;try{switch(s){case"sentry":Ka(r),An(r,Pn.sentry),wt(e,Ya());return;case"datadog":Ga(r),An(r,Pn.datadog),wt(e,qa());return;case"bugsnag":Ha(r),An(r,Pn.bugsnag),wt(e,Wa());return;case"aliyun-arms":Xa(r),wt(e,za());return;case"tingyun":Ja(r),wt(e,Qa());return;default:return}}catch(i){throw console.error(`Failed to apply monitoring selection "${t}":`,i),new Error(`Monitoring scaffold for "${t}" failed \u2014 see stderr for details.`)}}var Bt=[{id:"novu",name:"Novu",region:"both",envVars:["NOVU_API_KEY","NOVU_APPLICATION_IDENTIFIER"],docs:"https://docs.novu.co",description:"Multi-channel notification infrastructure"},{id:"knock",name:"Knock",region:"global",envVars:["KNOCK_API_KEY","KNOCK_SIGNING_KEY","KNOCK_PUBLIC_API_KEY"],docs:"https://docs.knock.app",description:"Product notifications platform"},{id:"custom",name:"Custom (direct dispatchers)",region:"both",envVars:[],docs:"",description:"Use email + sms + push directly"},{id:"none",name:"None",region:"both",envVars:[],docs:"",description:""}];function yr(e){return Bt.find(t=>t.id===e)}var iu=Bt.reduce((e,t)=>(e[t.region]||(e[t.region]=[]),e[t.region].push(t),e),{});async function Sr(e,t,n="global"){let o=yr(t);if(!o)throw new Error(`Unknown notifications provider "${t}". Valid ids: ${Bt.map(r=>r.id).join(", ")}`);if(o.id==="none"){Z(e,"notifications");return}o.envVars.length>0&&ae(e,{category:"Notifications",name:o.name,envVars:o.envVars,docs:o.docs}),ce(e,"NOTIFICATIONS_PROVIDER",o.id)}import Cn from"fs";import Za from"path";async function hr(e,t){let n=Za.join(e,"package.json");if(Cn.existsSync(n)){let o=Cn.readFileSync(n,"utf8"),r=JSON.parse(o);r.name=t,r.version="0.1.0",Cn.writeFileSync(n,JSON.stringify(r,null,2)+`
1752
- `)}}import $t from"fs";import at from"path";import{fileURLToPath as ec}from"url";var tc=ec(import.meta.url),Er=at.dirname(tc);function nc(){let e=[at.join(Er,"..","templates","orm","drizzle"),at.join(Er,"..","..","templates","orm","drizzle")];for(let t of e)if($t.existsSync(t))return t;return null}function vr(e,t){$t.mkdirSync(t,{recursive:!0});for(let n of $t.readdirSync(e,{withFileTypes:!0})){let o=at.join(e,n.name),r=at.join(t,n.name);n.isDirectory()?vr(o,r):$t.copyFileSync(o,r)}}async function br(e,t,n){if(t!=="drizzle")return{applied:!1};if(n!=="postgresql")return{applied:!1,reason:`Drizzle template currently targets Postgres only; --db=${n} keeps Prisma as sole ORM`};let o=nc();if(!o)return{applied:!1,reason:"Drizzle template directory not found in package \u2014 bundling error"};let r=at.join(e,"packages","platform","db-drizzle");return vr(o,r),{applied:!0}}import ye from"fs";import Pt from"path";var oc=["stripe","lemonsqueezy","chinapay","polar"];function _r(e){ye.existsSync(e)&&ye.rmSync(e,{recursive:!0,force:!0})}function rc(e){switch(e){case"stripe":return["stripe"];case"lemon":return["lemonsqueezy"];case"wechat":case"alipay":return["chinapay"]}}function sc(e,t){let n=Pt.join(e,".env.example");ye.existsSync(n)?ye.appendFileSync(n,`
1753
- `+t):ye.writeFileSync(n,t)}function ic(e){switch(e){case"stripe":return`# Stripe
1754
- STRIPE_SECRET_KEY=
1755
- STRIPE_WEBHOOK_SECRET=
1756
- `;case"lemon":return`# Lemon Squeezy
1757
- LEMONSQUEEZY_API_KEY=
1758
- LEMONSQUEEZY_STORE_ID=
1759
- LEMONSQUEEZY_WEBHOOK_SECRET=
1760
- `;case"wechat":return`# WeChat Pay (via ChinaPay aggregator)
1761
- CHINAPAY_APP_ID=
1762
- CHINAPAY_APP_SECRET=
1763
- CHINAPAY_WECHAT_ENABLED=true
1764
- `;case"alipay":return`# Alipay (via ChinaPay aggregator)
1765
- CHINAPAY_APP_ID=
1766
- CHINAPAY_APP_SECRET=
1767
- CHINAPAY_ALIPAY_ENABLED=true
1768
- `}}async function wr(e,t){let n=Pt.join(e,"packages","commerce","billing");if(!ye.existsSync(n))return;if(t==="none"){_r(n);return}let o=Pt.join(n,"src"),r=new Set(rc(t));for(let i of oc)r.has(i)||_r(Pt.join(o,i));let s=Pt.join(o,"checkout","types.ts");if(ye.existsSync(s)){let i=t==="stripe"?"stripe":t==="lemon"?"lemonsqueezy":"chinapay",a=ye.readFileSync(s,"utf8"),c=a.replace(/export type CheckoutProviderType\s*=\s*[^;]+;/,`export type CheckoutProviderType = "${i}";`);c!==a&&ye.writeFileSync(s,c)}sc(e,ic(t))}import Ce from"fs";import ct from"path";var Cr=new Set(["siliconflow","volcengine-ark","bailian","moonshot","sensetime"]),In=/@sailor:([^\s*]+)/;function ac(e,t){let{providerIds:n,customEndpoint:o}=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(s=>Cr.has(s));return{marker:e,keep:r||!!o}}if(e.startsWith("cn-instance:")){let r=e.slice(12);return{marker:e,keep:n.includes(r)}}return e==="custom-endpoint"?{marker:e,keep:!!o}:e==="registry-entries"?{marker:e,keep:!0}:{marker:e,keep:!0}}function Pr(e){return e.replace(/\s*\/\/\s*@sailor:[^\n]*$/,"")}function Ar(e){return e.replace(/\s*#\s*@sailor:[^\n]*$/,"")}function Ir(e,t){let n=e.split(`
1769
- `),o=[],r=e.includes("#"),s=0;for(;s<n.length;){let i=n[s],a=i.match(In);if(!a){o.push(i),s++;continue}let{marker:c,keep:p}=ac(a[1],t);if(c==="registry-entries"){o.push(r?Ar(i):Pr(i)),s++;continue}if(p){let m=r?Ar(i):Pr(i);m.trim().length>0&&o.push(m);let d=s+1;for(;d<n.length&&n[d].trim()!==""&&!In.test(n[d]);)o.push(n[d]),d++;d<n.length&&n[d].trim()===""&&(o.push(n[d]),d++),s=d}else{let m=s+1;for(;m<n.length&&n[m].trim()!==""&&!In.test(n[m]);)m++;m<n.length&&n[m].trim()===""&&m++,s=m}}return cc(o.join(`
1770
- `),t)}function cc(e,t){let{providerIds:n,customEndpoint:o}=t,r=new Set;for(let p of n)["openai","anthropic","google","mistral","xai"].includes(p)&&r.add(p),r.add(p.replace(/-/g,"_")),r.add(p);o&&r.add("custom");let s=/createProviderRegistry\(\{([\s\S]*?)\}\)/,i=e.match(s);if(!i)return e;let c=i[1].split(`
1771
- `).filter(p=>{let m=p.trim();if(m===""||m.startsWith("//"))return!0;let d=m.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*,?$/);return d?r.has(d[1]):!0}).join(`
1772
- `);return e.replace(s,`createProviderRegistry({${c}})`)}function pc(e,t){let n=ct.join(t,"registry.ts.template"),o=Ce.readFileSync(n,"utf-8");return Ir(o,e)}function lc(e,t){let n=ct.join(t,".env.example.template"),o=Ce.readFileSync(n,"utf-8");return Ir(o,e)}async function Tr(e,t,n){let o=pc(t,n),r=lc(t,n),s=ct.join(e,"packages/ai/agents/src");await Ce.promises.mkdir(s,{recursive:!0}),await Ce.promises.writeFile(ct.join(s,"registry.ts"),o),await Ce.promises.appendFile(ct.join(e,".env.example"),`
1773
- `+r);let i={ai:"latest"},{providerIds:a,customEndpoint:c}=t;(a.includes("openai")||c||a.some(m=>Cr.has(m)))&&(i["@ai-sdk/openai"]="latest",i["@ai-sdk/openai-compatible"]="latest"),a.includes("anthropic")&&(i["@ai-sdk/anthropic"]="latest"),a.includes("google")&&(i["@ai-sdk/google"]="latest"),a.includes("mistral")&&(i["@ai-sdk/mistral"]="latest"),a.includes("xai")&&(i["@ai-sdk/openai"]="latest");let p=ct.join(e,"package.json");if(Ce.existsSync(p)){let m=JSON.parse(await Ce.promises.readFile(p,"utf-8"));m.dependencies||(m.dependencies={});for(let[d,v]of Object.entries(i))m.dependencies[d]=v;await Ce.promises.writeFile(p,JSON.stringify(m,null,2)+`
1774
- `)}}import S from"fs";import f from"path";function xr(e,t){if(!S.existsSync(e))return;let n=S.readdirSync(e);for(let o of n){let r=f.join(e,o);S.statSync(r).isDirectory()?xr(r,t):t(r)}}async function Rr(e,t){let n=f.join(e,"package.json");if(!S.existsSync(n))return;let o=JSON.parse(S.readFileSync(n,"utf8"));if(t.orm!=="prisma"){let r=f.join(e,"packages","db");if(S.existsSync(r)){let s=f.join(r,"prisma");S.existsSync(s)&&S.rmSync(s,{recursive:!0,force:!0});let i=f.join(r,"package.json");if(S.existsSync(i)){let c=JSON.parse(S.readFileSync(i,"utf8"));c.dependencies&&delete c.dependencies["@prisma/client"],c.devDependencies&&delete c.devDependencies.prisma,S.writeFileSync(i,JSON.stringify(c,null,2)+`
1775
- `)}let a=f.join(r,"src","index.ts");S.existsSync(a)&&S.writeFileSync(a,`
1776
- export const prisma = {} as any;
1777
- export const db = {} as any;
1778
- export type PrismaClient = any;
1779
- export type Prisma = any;
1780
- export const withOrgContext = (cb: any) => cb(prisma);
1781
- export const withAdminContext = (cb: any) => cb(prisma);
1782
-
1783
- // Mock all possible schema types to let consumers compile
1784
- export type Organization = any;
1785
- export type User = any;
1786
- export type OrganizationMember = any;
1787
- export type Plan = any;
1788
- export type Role = any;
1789
- export type Content = any;
1790
- export type ContentTranslation = any;
1791
- export type ContentEmbedding = any;
1792
- export type ContentStatus = any;
1793
- export type Product = any;
1794
- export type Order = any;
1795
- export type OrderItem = any;
1796
- export type Integration = any;
1797
- export type OrderStatus = any;
1798
- export type IntegrationType = any;
1799
- export type Wallet = any;
1800
- export type Nft = any;
1801
- export type NftStatus = any;
1802
- export type UserActivity = any;
1803
- export type TenantUsage = any;
1804
- export type AIRequest = any;
1805
- export type AIProvider = any;
1806
- export type AIRequestType = any;
1807
- export type UserPreference = any;
1808
- export type Recommendation = any;
1809
- export type FeatureFlag = any;
1810
- export type FeatureFlagOverride = any;
1811
- export type FeatureFlagType = any;
1812
- export type WebhookEvent = any;
1813
- export type AuditLog = any;
1814
- export type AuditAction = any;
1815
- export type Subscription = any;
1816
- export type UsageRecord = any;
1817
- export type UsageAggregate = any;
1818
- export type UsageLedgerEntry = any;
1819
- export type UsageType = any;
1820
- export type UsageLedgerSource = any;
1821
- `)}o.scripts?.postinstall&&(o.scripts.postinstall=o.scripts.postinstall.replace(/prisma generate/g,'echo "ORM Disabled"'))}if(!t.i18n){let r=f.join(e,"apps");if(S.existsSync(r)){let s=S.readdirSync(r);for(let i of s){let a=f.join(r,i);if(!S.statSync(a).isDirectory())continue;let c=f.join(a,"src","app");if(!S.existsSync(c))continue;["[lang]","[locale]"].forEach(E=>{let l=f.join(c,E);if(S.existsSync(l)){let y=S.readdirSync(l);for(let U of y)S.renameSync(f.join(l,U),f.join(c,U));S.rmdirSync(l)}}),["src/middleware.ts","middleware.ts"].forEach(E=>{let l=f.join(a,E);S.existsSync(l)&&S.rmSync(l)});let p=f.join(a,"src","lib");S.existsSync(p)||S.mkdirSync(p,{recursive:!0});let m=f.join(p,"mock-i18n.ts");S.writeFileSync(m,`
1822
- import React from 'react';
1823
- export function useTranslations(namespace?: string): any { return (key: string) => key; }
1824
- export function useLocale() { return 'en'; }
1825
- export const NextIntlClientProvider = ({ children, ...props }: any) => React.createElement(React.Fragment, null, children);
1826
- export async function getTranslations(...args: any[]) { return (key: string) => key; }
1827
- export async function getMessages(...args: any[]) { return {}; }
1828
- export function setRequestLocale(locale: string) {}
1829
- export function hasLocale(locales: string[], locale: string) { return true; }
1830
- export function getRequestConfig(cb: any) { return cb; }
1831
- export const routing = { locales: ['en'], defaultLocale: 'en' };
1832
- export const Link = ({ children, href, ...props }: any) => React.createElement('a', { href, ...props }, children);
1833
- export const redirect = (url: string, ...args: any[]) => {};
1834
- export const usePathname = () => '/';
1835
- export const useRouter = () => ({ push: (...args: any[]) => {}, replace: (...args: any[]) => {} });
1836
- export default function createMiddleware(...args: any[]) { return (req: any) => req; }
1837
- export function createNavigation(...args: any[]) { return { Link, redirect, usePathname, useRouter, getPathname: () => '/' }; }
1838
- export function defineRouting<T>(config: T): T { return config; }
1839
- `);let v=f.join(p,"mock-i18n.d.ts");S.writeFileSync(v,`declare module '@/lib/mock-i18n' {
1840
- export function useTranslations(namespace?: string): any;
1841
- export function useLocale(): string;
1842
- export const NextIntlClientProvider: React.FC<any>;
1843
- export function getTranslations(...args: any[]): Promise<(key: string) => string>;
1844
- export function getMessages(...args: any[]): Promise<any>;
1845
- export function setRequestLocale(locale: string): void;
1846
- export function hasLocale(locales: string[], locale: string): boolean;
1847
- export function getRequestConfig(cb: any): any;
1848
- export const routing: { locales: string[], defaultLocale: string };
1849
- export const Link: React.FC<any>;
1850
- export function redirect(url: string, ...args: any[]): void;
1851
- export function usePathname(): string;
1852
- export function useRouter(): any;
1853
- export default function createMiddleware(...args: any[]): any;
1854
- export function createNavigation(...args: any[]): any;
1855
- export function defineRouting<T>(config: T): T;
1856
- }
1857
- `),S.writeFileSync(f.join(p,"mock-i18n-index.ts"),`
1858
- export * from './mock-i18n';
1859
- export { default } from './mock-i18n';
1860
- `);let g=f.join(a,"tsconfig.json");if(S.existsSync(g))try{let E=S.readFileSync(g,"utf8");if(E.includes('"paths": {')){let l=E.replace(/"paths":\s*\{/,`"paths": {
1861
- "next-intl": ["./src/lib/mock-i18n"],
1862
- "next-intl/*": ["./src/lib/mock-i18n"],`);S.writeFileSync(g,l)}}catch{}let h=f.join(a,"src");S.existsSync(h)&&xr(h,E=>{if(!E.endsWith(".ts")&&!E.endsWith(".tsx"))return;let l=S.readFileSync(E,"utf8"),y=!1;E.endsWith("src/app/layout.tsx")&&l.includes("../providers")&&(l=l.replace(/from\s+['"]\.\.\/providers['"]/g,"from './providers'"),y=!0),l=l.replace(/params:\s*Promise<\{[^}]*(lang|locale)[^}]*\}>/g,"params: any"),l=l.replace(/\(\{\s*params\s*\}\s*:\s*\{\s*params:\s*any\s*\}\)/g,"({ params }: any)"),l.match(/locale\s*:\s*any/)&&(l=l.replace(/locale\s*:\s*any/g,"locale: string"),y=!0),l.match(/lang\s*:\s*any/)&&(l=l.replace(/lang\s*:\s*any/g,"lang: string"),y=!0),l.match(/requestLocale\s*:\s*any/)&&(l=l.replace(/requestLocale\s*:\s*any/g,"requestLocale: string"),y=!0),l.match(/\(locale\s*=\s*(.*)\)/)&&(l=l.replace(/\(locale\s*=\s*(.*)\)/g,"() "),y=!0),l.match(/export\s+default\s+async\s+function\s+LangLayout/)&&(l=l.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 })"),y=!0),(E.endsWith("src/app/layout.tsx")||E.endsWith("src/app/sitemap.ts")||E.endsWith("src/app/robots.ts")||E.endsWith("src/components/ui/locale-switcher.tsx")||E.endsWith("src/app/(marketing)/blog/[slug]/page.tsx"))&&(l=l.replace(/const\s+\{\s*lang\s*\}\s*=\s*(await\s+)?params;?/g,'const lang = "en";'),l=l.replace(/const\s+\{\s*locale\s*\}\s*=\s*(await\s+)?params;?/g,'const locale = "en";'),l=l.replace(/const\s+locale\s*=\s*lang\s+as\s+Locale;?/g,'const locale = "en";'),l=l.replace(/\(l\)\s*=>/g,"(l: any) =>"),y=!0),E.endsWith("src/i18n/request.ts")&&(l=l.replace(/async\s*\(\{\s*requestLocale\s*\}\)/g,"async ({ requestLocale }: any)"),y=!0),(l.match(/locale\s*:/)||l.match(/lang\s*:/))&&(y=!0),y&&S.writeFileSync(E,l)});let P=f.join(a,"package.json");if(S.existsSync(P)){let E=JSON.parse(S.readFileSync(P,"utf8"));E.dependencies&&delete E.dependencies["next-intl"],S.writeFileSync(P,JSON.stringify(E,null,2)+`
1863
- `)}}}}S.writeFileSync(n,JSON.stringify(o,null,2)+`
1864
- `)}function w(e){try{S.rmSync(e,{recursive:!0,force:!0})}catch{}}function dc(e,t){if(!S.existsSync(e))return;let n;try{n=JSON.parse(S.readFileSync(e,"utf8"))}catch{return}let o=!1;if(n.dependencies&&t in n.dependencies){let{[t]:r,...s}=n.dependencies;n={...n,dependencies:s},o=!0}if(n.devDependencies&&t in n.devDependencies){let{[t]:r,...s}=n.devDependencies;n={...n,devDependencies:s},o=!0}o&&S.writeFileSync(e,JSON.stringify(n,null,2)+`
1865
- `)}function kr(e,t){t.cronJobs||(w(f.join(e,"apps/web/src/app/api/cron")),w(f.join(e,"packages/integrations/queue/src/scheduled"))),t.auditLog||(w(f.join(e,"apps/web/src/app/[locale]/(app)/settings/audit-log")),w(f.join(e,"apps/web/src/app/api/audit-logs")),w(f.join(e,"apps/web/src/components/audit"))),t.apiKeys||(w(f.join(e,"apps/web/src/app/[locale]/(app)/settings/api-keys")),w(f.join(e,"apps/web/src/app/api/api-keys")),w(f.join(e,"apps/web/src/components/api-keys"))),t.webhooks||(w(f.join(e,"apps/web/src/app/[locale]/(app)/settings/webhooks")),w(f.join(e,"apps/web/src/app/api/webhooks")),w(f.join(e,"apps/web/src/components/webhooks")),w(f.join(e,"packages/integrations/webhooks"))),t.commandPalette||(w(f.join(e,"apps/web/src/components/command-palette")),w(f.join(e,"apps/web/src/app/[locale]/providers/command-palette-mount.tsx"))),t.cookieConsent||(w(f.join(e,"apps/landing/src/components/cookie-consent-banner.tsx")),w(f.join(e,"apps/landing/src/components/__tests__/cookie-consent-banner.test.tsx")),w(f.join(e,"apps/landing/src/lib/cookie-consent.ts")),w(f.join(e,"apps/landing/src/lib/__tests__/cookie-consent.test.ts")),w(f.join(e,"apps/web/src/app/api/cookie-consent"))),t.legalPages||(w(f.join(e,"apps/landing/src/app/[lang]/(legal)/legal")),w(f.join(e,"apps/landing/src/lib/legal-documents.ts")),w(f.join(e,"apps/landing/src/lib/__tests__/legal-documents.test.ts"))),t.chinaCompliance||(w(f.join(e,"packages/ops/china-compliance")),w(f.join(e,"apps/landing/src/components/icp-footer.tsx")),w(f.join(e,"apps/landing/src/components/__tests__/icp-footer.test.tsx")),w(f.join(e,"apps/web/src/components/auth/wechat-login-button.tsx")),w(f.join(e,"apps/web/src/components/auth/__tests__/wechat-login-button.test.tsx")),dc(f.join(e,"apps/web/package.json"),"@nebutra/china-compliance"))}import pt from"fs";import Nr from"path";var Or=/^--\s*@conditional\(([A-Za-z][A-Za-z0-9_-]*)=([^)]+)\)\s*$/m;function uc(e){let t=Or.exec(e);if(!t)return null;let[,n,o]=t,r=o.split("|").map(s=>s.trim()).filter(Boolean);return{flag:n,values:r}}function mc(e){return e.replace(Or,"").replace(/^\n/,"")}function Tn(e,t){let n={kept:[],removed:[],skipped:[]};if(!pt.existsSync(e))return n;let o=pt.readdirSync(e,{withFileTypes:!0}).filter(r=>r.isDirectory()).sort((r,s)=>r.name.localeCompare(s.name));for(let r of o){let s=Nr.join(e,r.name),i=Nr.join(s,"migration.sql");if(!pt.existsSync(i)){n.skipped.push(r.name);continue}let a=pt.readFileSync(i,"utf8"),c=uc(a);if(!c){n.skipped.push(r.name);continue}let p=t[c.flag];if(p!==void 0&&c.values.includes(p)){pt.writeFileSync(i,mc(a)),n.kept.push(r.name);continue}pt.rmSync(s,{recursive:!0,force:!0}),n.removed.push(r.name)}return n}import{getSchema as gc,printSchema as fc}from"@mrleebo/prisma-ast";var yc=/^\/\/\/\s*@conditional\(([A-Za-z][A-Za-z0-9_-]*)=([^)]+)\)\s*$/;function Sc(e){return e.type==="comment"&&e.text.trim().startsWith("//")}function hc(e){let t=yc.exec(e.trim());if(!t)return null;let[,n,o]=t,r=o.split("|").map(s=>s.trim()).filter(s=>s.length>0);return{flag:n,values:r}}function Ec(e,t){for(let n=t-1;n>=0;n--){let o=e[n];if(o.type==="comment"){let r=hc(o.text);if(r)return{annIndex:n,parsed:r};continue}break}return null}function vc(e){return e.type==="model"||e.type==="enum"}function xn(e,t){let n=gc(e),o=new Set;for(let a=0;a<n.list.length;a++){let c=n.list[a];if(!vc(c))continue;let p=Ec(n.list,a);if(!p)continue;let m=t[p.parsed.flag];if(m!==void 0&&p.parsed.values.includes(m))o.add(p.annIndex);else{o.add(a);for(let g=a-1;g>=0;g--){let h=n.list[g];if(Sc(h)){o.add(g);continue}break}let v=n.list[a+1];v&&v.type==="break"&&o.add(a+1)}}let r=n.list.filter((a,c)=>!o.has(c)),s=[];for(let a of r){let c=s[s.length-1];a.type==="break"&&c&&c.type==="break"||s.push(a)}return fc({type:"schema",list:s})}import lt from"fs";import Ur from"path";var Vt=[{id:"qstash",name:"Upstash QStash",region:"both",envVars:["QSTASH_TOKEN","QSTASH_CURRENT_SIGNING_KEY","QSTASH_NEXT_SIGNING_KEY"],docs:"https://upstash.com/docs/qstash",description:"Serverless HTTP-based queue"},{id:"bullmq",name:"BullMQ",region:"both",envVars:["REDIS_URL"],docs:"https://docs.bullmq.io",description:"Redis-backed job queue"},{id:"sqs",name:"AWS SQS",region:"global",envVars:["AWS_SQS_QUEUE_URL","AWS_ACCESS_KEY_ID","AWS_SECRET_ACCESS_KEY","AWS_REGION"],docs:"https://docs.aws.amazon.com/AWSSimpleQueueService/",description:"AWS managed queue"},{id:"none",name:"None",region:"both",envVars:[],docs:"",description:"Skip queue package"}];function Lr(e){return Vt.find(t=>t.id===e)}var Nu=Vt.reduce((e,t)=>(e[t.region]||(e[t.region]=[]),e[t.region].push(t),e),{});function Dr(e){return lt.existsSync(e)?lt.readFileSync(e,"utf-8"):null}function bc(e,t){let n=Ur.join(e,".env.example"),o=Dr(n);if(o===null)return;let r=`# Queue: ${t.name}`;if(o.includes(r))return;let s=["","# =============================================",r,`# Docs: ${t.docs}`,"# =============================================",...t.envVars.map(i=>`${i}=""`),""].join(`
1866
- `);lt.appendFileSync(n,s)}function _c(e,t,n){let o=Ur.join(e,".env.example"),r=Dr(o);if(r===null)return;let s=new RegExp(`^${t}=.*$`,"m");s.test(r)?lt.writeFileSync(o,r.replace(s,`${t}="${n}"`)):lt.appendFileSync(o,`${t}="${n}"
1867
- `)}async function Mr(e,t,n){let o=De(e,"queue");if(t==="none"){o&&lt.rmSync(o,{recursive:!0,force:!0});return}let r=Lr(t);if(!r)throw new Error(`Unknown queue provider "${t}". Valid ids: ${Vt.map(s=>s.id).join(", ")}`);o&&(bc(e,r),_c(e,"QUEUE_PROVIDER",t))}import Se from"fs";import Ie from"path";import{fileURLToPath as wc}from"url";var Pc=wc(import.meta.url),Fr=Ie.dirname(Pc),Ac=/\.(md|mdx|json|ts|tsx|js|mjs|cjs|yaml|yml|css|html|toml|py)$/,Cc=["backends/gateway","infra","e2e","tests"];function Ic(){let e=[Ie.join(Fr,"..","templates"),Ie.join(Fr,"..","..","templates")];for(let t of e)if(Se.existsSync(t))return t;return e[0]}async function jr(e,t){let n=Ic(),o=[],r=[],s=[...Cc,...t.withWorkflows?["workflows"]:[],...t.withPythonBackend?["backends/python"]:[]];for(let i of s){let a=Ie.join(n,i);if(!Se.existsSync(a)){r.push(i);continue}let c=Ie.join(e,i);Br(a,c),$r(c,{"{PRODUCT_NAME}":t.projectName}),o.push(i)}return{applied:o,skipped:r}}function Br(e,t){Se.mkdirSync(t,{recursive:!0});for(let n of Se.readdirSync(e,{withFileTypes:!0})){let o=Ie.join(e,n.name),r=Ie.join(t,n.name);n.isDirectory()?Br(o,r):Se.existsSync(r)||Se.copyFileSync(o,r)}}function $r(e,t){for(let n of Se.readdirSync(e,{withFileTypes:!0})){let o=Ie.join(e,n.name);if(n.isDirectory()){$r(o,t);continue}if(!Ac.test(n.name))continue;let r=Se.readFileSync(o,"utf-8"),s=!1;for(let[i,a]of Object.entries(t))r.includes(i)&&(r=r.split(i).join(a),s=!0);s&&Se.writeFileSync(o,r)}}import dt from"fs";import Kr from"path";var Kt=[{id:"meilisearch",name:"Meilisearch",region:"both",envVars:["MEILISEARCH_HOST","MEILISEARCH_MASTER_KEY"],docs:"https://docs.meilisearch.com",description:"Self-hostable search"},{id:"typesense",name:"Typesense",region:"both",envVars:["TYPESENSE_HOST","TYPESENSE_PORT","TYPESENSE_PROTOCOL","TYPESENSE_API_KEY"],docs:"https://typesense.org/docs",description:"Open-source search"},{id:"algolia",name:"Algolia",region:"global",envVars:["ALGOLIA_APP_ID","ALGOLIA_ADMIN_API_KEY","ALGOLIA_SEARCH_API_KEY"],docs:"https://www.algolia.com/doc/",description:"Managed search as a service"},{id:"pgvector",name:"pgvector (Postgres)",region:"both",envVars:["DATABASE_URL"],docs:"https://github.com/pgvector/pgvector",description:"Postgres vector + full-text"},{id:"none",name:"None",region:"both",envVars:[],docs:"",description:"Skip search package"}];function Vr(e){return Kt.find(t=>t.id===e)}var Ku=Kt.reduce((e,t)=>(e[t.region]||(e[t.region]=[]),e[t.region].push(t),e),{});function Yr(e){return dt.existsSync(e)?dt.readFileSync(e,"utf-8"):null}function Tc(e,t){let n=Kr.join(e,".env.example"),o=Yr(n);if(o===null)return;let r=`# Search: ${t.name}`;if(o.includes(r))return;let s=["","# =============================================",r,`# Docs: ${t.docs}`,"# =============================================",...t.envVars.map(i=>`${i}=""`),""].join(`
1868
- `);dt.appendFileSync(n,s)}function xc(e,t,n){let o=Kr.join(e,".env.example"),r=Yr(o);if(r===null)return;let s=new RegExp(`^${t}=.*$`,"m");s.test(r)?dt.writeFileSync(o,r.replace(s,`${t}="${n}"`)):dt.appendFileSync(o,`${t}="${n}"
1869
- `)}async function Gr(e,t,n){let o=De(e,"search");if(t==="none"){o&&dt.rmSync(o,{recursive:!0,force:!0});return}let r=Vr(t);if(!r)throw new Error(`Unknown search provider "${t}". Valid ids: ${Kt.map(s=>s.id).join(", ")}`);o&&(Tc(e,r),xc(e,"SEARCH_PROVIDER",t))}import Te from"fs";import Ye from"path";function Rc(e){let t=[Ye.join(e,"packages","db"),Ye.join(e,"apps","web")];for(let n of t){let o=Ye.join(n,"prisma","schema.prisma");if(Te.existsSync(o))return{packageDir:n,seedPath:Ye.join(n,"prisma","seed.ts"),packageJsonPath:Ye.join(n,"package.json")}}return null}function kc(e){try{let t=Te.readFileSync(e,"utf8"),n=new Set,o=/^\s*model\s+(\w+)\s*\{/gm,r=o.exec(t);for(;r;)n.add(r[1]),r=o.exec(t);return n}catch{return new Set}}function Nc(e,t){return!t||e==="none"?` // Skipping admin user \u2014 auth=${JSON.stringify(e)} (or no User model).`:e==="clerk"?` try {
1870
- const admin = await prisma.user.create({
1871
- data: {
1872
- clerkId: "user_mock_admin",
1873
- email: "admin@example.com",
1874
- name: "Admin",
1875
- },
1876
- });
1877
- console.log("\\u2713 Created admin user:", admin.email);
1878
- } catch (err) {
1879
- console.warn("\\u26A0 Admin user creation skipped:", (err as Error).message);
1880
- }`:e==="betterauth"?` try {
755
+ `+l),n.push("README.md (prepended license notice)"))}return{wrote:n}}import ce from"fs";import xn from"path";async function He(e,t){let n=xn.join(e,"package.json");if(ce.existsSync(n)){let r=ce.readFileSync(n,"utf8"),s=JSON.parse(r);s.name=t,s.version="0.1.0",ce.writeFileSync(n,JSON.stringify(s,null,2)+`
756
+ `)}}import O from"fs";import L from"path";import{fileURLToPath as Tn}from"url";var _n=Tn(import.meta.url),We=L.dirname(_n),Nn=/\.(md|mdx|json|ts|tsx|js|mjs|cjs|yaml|yml|css|html|toml|py)$/,Pn=["backends/gateway","infra","e2e","tests","deploy"],Xe=new Set(["deploy"]);function kn(){let e=[L.join(We,"..","templates"),L.join(We,"..","..","templates")];for(let t of e)if(O.existsSync(t))return t;return e[0]}async function Je(e,t){let n=kn(),r=[],s=[];for(let o of Pn){let i=L.join(n,o);if(!O.existsSync(i)){s.push(o);continue}let c=Xe.has(o)?e:L.join(e,o);Ve(i,c),Xe.has(o)||ze(c,{"{PRODUCT_NAME}":t.projectName}),r.push(o)}return{applied:r,skipped:s}}function Ve(e,t){O.mkdirSync(t,{recursive:!0});for(let n of O.readdirSync(e,{withFileTypes:!0})){let r=L.join(e,n.name),s=L.join(t,n.name);n.isDirectory()?Ve(r,s):O.existsSync(s)||O.copyFileSync(r,s)}}function ze(e,t){for(let n of O.readdirSync(e,{withFileTypes:!0})){let r=L.join(e,n.name);if(n.isDirectory()){ze(r,t);continue}if(!Nn.test(n.name))continue;let s=O.readFileSync(r,"utf-8"),o=!1;for(let[i,c]of Object.entries(t))s.includes(i)&&(s=s.split(i).join(c),o=!0);o&&O.writeFileSync(r,s)}}import F from"fs";import D from"path";function In(e){let t=[D.join(e,"packages","db"),D.join(e,"apps","web")];for(let n of t){let r=D.join(n,"prisma","schema.prisma");if(F.existsSync(r))return{packageDir:n,seedPath:D.join(n,"prisma","seed.ts"),packageJsonPath:D.join(n,"package.json")}}return null}function On(e){try{let t=F.readFileSync(e,"utf8"),n=new Set,r=/^\s*model\s+(\w+)\s*\{/gm,s=r.exec(t);for(;s;)n.add(s[1]),s=r.exec(t);return n}catch{return new Set}}function Rn(e){return e?` try {
1881
757
  // NOTE: in production hash passwords with the project's password hasher
1882
758
  // (e.g. argon2id via @nebutra/auth). This is a scaffold placeholder.
1883
759
  const admin = await prisma.user.create({
@@ -1890,14 +766,7 @@ export { default } from './mock-i18n';
1890
766
  console.log("\\u2713 Created admin user:", admin.email);
1891
767
  } catch (err) {
1892
768
  console.warn("\\u26A0 Admin user creation skipped:", (err as Error).message);
1893
- }`:` try {
1894
- const admin = await prisma.user.create({
1895
- data: { email: "admin@example.com", name: "Admin" },
1896
- });
1897
- console.log("\\u2713 Created admin user:", admin.email);
1898
- } catch (err) {
1899
- console.warn("\\u26A0 Admin user creation skipped:", (err as Error).message);
1900
- }`}function Oc(e,t){let n=t.has("Tenant"),o=t.has("User"),r=t.has("Project"),s=Nc(e,o),i=n?` let tenants: Array<{ id: string; name: string }> = [];
769
+ }`:" // Skipping admin user \u2014 no User model."}function Ln(e){let t=e.has("Tenant"),n=e.has("User"),r=e.has("Project"),s=Rn(n),o=t?` let tenants: Array<{ id: string; name: string }> = [];
1901
770
  try {
1902
771
  tenants = await Promise.all([
1903
772
  prisma.tenant.create({
@@ -1914,7 +783,7 @@ export { default } from './mock-i18n';
1914
783
  } catch (err) {
1915
784
  console.warn("\\u26A0 Tenant creation skipped:", (err as Error).message);
1916
785
  }`:` const tenants: Array<{ id: string; name: string }> = [];
1917
- console.log("\\u2139 No Tenant model in schema \u2014 skipping tenant seed.");`,a=r&&n?` for (const tenant of tenants) {
786
+ console.log("\\u2139 No Tenant model in schema \u2014 skipping tenant seed.");`,i=r&&t?` for (const tenant of tenants) {
1918
787
  try {
1919
788
  await prisma.project.createMany({
1920
789
  data: [
@@ -1933,8 +802,8 @@ export { default } from './mock-i18n';
1933
802
  *
1934
803
  * Run with: pnpm db:seed
1935
804
  *
1936
- * Auth provider: ${e}
1937
- * Detected models at scaffold time: ${[...t].sort().join(", ")||"(none)"}
805
+ * Auth provider: Better Auth
806
+ * Detected models at scaffold time: ${[...e].sort().join(", ")||"(none)"}
1938
807
  *
1939
808
  * Each block is wrapped in try/catch so evolving the schema does not
1940
809
  * break the seed \u2014 missing models log a warning and move on.
@@ -1947,13 +816,13 @@ async function main(): Promise<void> {
1947
816
  console.log("\\uD83C\\uDF31 Seeding database...");
1948
817
 
1949
818
  // --- Tenants ---------------------------------------------------------
1950
- ${i}
819
+ ${o}
1951
820
 
1952
821
  // --- Admin user ------------------------------------------------------
1953
822
  ${s}
1954
823
 
1955
824
  // --- Sample projects per tenant --------------------------------------
1956
- ${a}
825
+ ${i}
1957
826
 
1958
827
  console.log("\\u2713 Seed complete");
1959
828
  }
@@ -1966,706 +835,8 @@ main()
1966
835
  .finally(async () => {
1967
836
  await prisma.$disconnect();
1968
837
  });
1969
- `}function Lc(e){if(Te.existsSync(e))try{let t=Te.readFileSync(e,"utf8"),n=JSON.parse(t),o=n.scripts??{},r=!1;o["db:seed"]||(o["db:seed"]="prisma db seed",r=!0);let s=n.prisma??{};if(s.seed||(s.seed="tsx prisma/seed.ts",r=!0),!r)return;let i={...n,scripts:o,prisma:s};Te.writeFileSync(e,JSON.stringify(i,null,2)+`
1970
- `)}catch(t){let n=t instanceof Error?t.message:String(t);console.warn(`[create-sailor] Failed to patch seed scripts in ${e}: ${n}`)}}async function qr(e,t){let n=Rc(e);if(n)try{let o=Ye.join(n.packageDir,"prisma","schema.prisma"),r=kc(o),s=Oc(t,r),i=Ye.dirname(n.seedPath);Te.existsSync(i)||Te.mkdirSync(i,{recursive:!0}),Te.writeFileSync(n.seedPath,s),Lc(n.packageJsonPath)}catch(o){let r=o instanceof Error?o.message:String(o);throw console.error(`[create-sailor] Failed to generate seed data: ${r}`),new Error(`Unable to write prisma/seed.ts: ${r}`)}}import xe from"fs";import Ge from"path";var Yt=[{id:"twilio",name:"Twilio",region:"global",envVars:["TWILIO_ACCOUNT_SID","TWILIO_AUTH_TOKEN","TWILIO_FROM_NUMBER"],docs:"https://www.twilio.com/docs/sms",pkgs:["twilio"]},{id:"messagebird",name:"MessageBird (Bird)",region:"global",envVars:["MESSAGEBIRD_ACCESS_KEY","MESSAGEBIRD_ORIGINATOR"],docs:"https://developers.messagebird.com/api/sms-messaging/",pkgs:["messagebird"]},{id:"plivo",name:"Plivo",region:"global",envVars:["PLIVO_AUTH_ID","PLIVO_AUTH_TOKEN","PLIVO_FROM_NUMBER"],docs:"https://www.plivo.com/docs/sms/",pkgs:["plivo"]},{id:"aliyun-sms",name:"\u963F\u91CC\u4E91\u77ED\u4FE1 Aliyun SMS",region:"cn",envVars:["ALIYUN_SMS_ACCESS_KEY","ALIYUN_SMS_ACCESS_SECRET","ALIYUN_SMS_SIGN_NAME","ALIYUN_SMS_TEMPLATE_CODE","ALIYUN_SMS_ENDPOINT"],docs:"https://help.aliyun.com/product/44282.html",pkgs:["@alicloud/dysmsapi20170525","@alicloud/openapi-client"]},{id:"tencent-sms",name:"\u817E\u8BAF\u4E91\u77ED\u4FE1 Tencent SMS",region:"cn",envVars:["TENCENT_SMS_SECRET_ID","TENCENT_SMS_SECRET_KEY","TENCENT_SMS_SDK_APP_ID","TENCENT_SMS_SIGN_NAME","TENCENT_SMS_TEMPLATE_ID","TENCENT_SMS_REGION"],docs:"https://cloud.tencent.com/document/product/382",pkgs:["tencentcloud-sdk-nodejs-sms"]},{id:"yunpian",name:"\u4E91\u7247 Yunpian",region:"cn",envVars:["YUNPIAN_API_KEY","YUNPIAN_SIGN"],docs:"https://www.yunpian.com/official/document/sms/zh_CN/introduction_product_intro",pkgs:["yunpian-nodejs-sdk"]},{id:"none",name:"None",region:"both",envVars:[],docs:""}],Zu=Yt.reduce((e,t)=>(e[t.region]||(e[t.region]=[]),e[t.region].push(t),e),{});function Hr(e){return Yt.find(t=>t.id===e)}var Uc="@nebutra/sms",Dc={twilio:"^5.3.6",messagebird:"^4.0.1",plivo:"^4.73.0","@alicloud/dysmsapi20170525":"^2.0.24","@alicloud/openapi-client":"^0.4.12","tencentcloud-sdk-nodejs-sms":"^4.0.970","yunpian-nodejs-sdk":"^0.0.2"};function Rn(e,t){xe.mkdirSync(Ge.dirname(e),{recursive:!0}),xe.writeFileSync(e,t)}function Mc(e,t){let n=Ge.join(e,".env.example");xe.existsSync(n)?xe.appendFileSync(n,`
1971
- `+t):xe.writeFileSync(n,t)}function Fc(e){if(e.envVars.length===0)return"";let t=`# SMS provider: ${e.name}`,n=e.envVars.map(o=>`${o}=`).join(`
1972
- `);return`${t}
1973
- ${n}
1974
- `}var jc=`/**
1975
- * Inputs for the \`sendSms\` wrapper. Keep this surface portable across
1976
- * providers \u2014 provider-specific options belong in the wrapper itself.
1977
- */
1978
- export interface SendSmsInput {
1979
- /** E.164 (+1415...) for global providers, or 11-digit string for CN providers. */
1980
- to: string;
1981
- /** Template code / ID registered with the provider. */
1982
- templateCode?: string;
1983
- /** Template variables \u2014 usually { code: "123456" } or { name, orderId, ... }. */
1984
- params?: Record<string, string | number>;
1985
- /** Free-form text body (only honored by providers that allow ad-hoc content). */
1986
- body?: string;
1987
- }
1988
-
1989
- export interface SendSmsResult {
1990
- messageId?: string;
1991
- provider: string;
1992
- raw?: unknown;
1993
- }
1994
- `;function Bc(){return`import twilio from "twilio";
1995
- import type { SendSmsInput, SendSmsResult } from "./types";
1996
-
1997
- let client: ReturnType<typeof twilio> | null = null;
1998
-
1999
- function getClient() {
2000
- if (client) return client;
2001
- const sid = process.env.TWILIO_ACCOUNT_SID;
2002
- const token = process.env.TWILIO_AUTH_TOKEN;
2003
- if (!sid || !token) {
2004
- throw new Error("TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN must be set");
2005
- }
2006
- client = twilio(sid, token);
2007
- return client;
2008
- }
2009
-
2010
- export async function sendSms(input: SendSmsInput): Promise<SendSmsResult> {
2011
- const from = process.env.TWILIO_FROM_NUMBER;
2012
- if (!from) throw new Error("TWILIO_FROM_NUMBER must be set");
2013
-
2014
- const body =
2015
- input.body ??
2016
- Object.values(input.params ?? {}).join(" ") ??
2017
- "";
2018
-
2019
- const message = await getClient().messages.create({
2020
- to: input.to,
2021
- from,
2022
- body,
2023
- });
2024
-
2025
- return { messageId: message.sid, provider: "twilio", raw: message };
2026
- }
2027
-
2028
- export type { SendSmsInput, SendSmsResult } from "./types";
2029
- `}function $c(){return`import messagebird from "messagebird";
2030
- import type { SendSmsInput, SendSmsResult } from "./types";
2031
-
2032
- let client: ReturnType<typeof messagebird> | null = null;
2033
-
2034
- function getClient() {
2035
- if (client) return client;
2036
- const key = process.env.MESSAGEBIRD_ACCESS_KEY;
2037
- if (!key) throw new Error("MESSAGEBIRD_ACCESS_KEY must be set");
2038
- client = messagebird(key);
2039
- return client;
2040
- }
2041
-
2042
- export async function sendSms(input: SendSmsInput): Promise<SendSmsResult> {
2043
- const originator = process.env.MESSAGEBIRD_ORIGINATOR;
2044
- if (!originator) throw new Error("MESSAGEBIRD_ORIGINATOR must be set");
2045
-
2046
- const body =
2047
- input.body ??
2048
- Object.values(input.params ?? {}).join(" ") ??
2049
- "";
2050
-
2051
- return await new Promise<SendSmsResult>((resolve, reject) => {
2052
- getClient().messages.create(
2053
- { originator, recipients: [input.to], body },
2054
- (err, response) => {
2055
- if (err) return reject(err);
2056
- resolve({
2057
- messageId: response?.id,
2058
- provider: "messagebird",
2059
- raw: response,
2060
- });
2061
- },
2062
- );
2063
- });
2064
- }
2065
-
2066
- export type { SendSmsInput, SendSmsResult } from "./types";
2067
- `}function Vc(){return`import plivo from "plivo";
2068
- import type { SendSmsInput, SendSmsResult } from "./types";
2069
-
2070
- let client: InstanceType<typeof plivo.Client> | null = null;
2071
-
2072
- function getClient() {
2073
- if (client) return client;
2074
- const authId = process.env.PLIVO_AUTH_ID;
2075
- const authToken = process.env.PLIVO_AUTH_TOKEN;
2076
- if (!authId || !authToken) {
2077
- throw new Error("PLIVO_AUTH_ID and PLIVO_AUTH_TOKEN must be set");
2078
- }
2079
- client = new plivo.Client(authId, authToken);
2080
- return client;
2081
- }
2082
-
2083
- export async function sendSms(input: SendSmsInput): Promise<SendSmsResult> {
2084
- const from = process.env.PLIVO_FROM_NUMBER;
2085
- if (!from) throw new Error("PLIVO_FROM_NUMBER must be set");
2086
-
2087
- const body =
2088
- input.body ??
2089
- Object.values(input.params ?? {}).join(" ") ??
2090
- "";
2091
-
2092
- const response = await getClient().messages.create({
2093
- src: from,
2094
- dst: input.to,
2095
- text: body,
2096
- });
2097
-
2098
- return {
2099
- messageId: response.messageUuid?.[0],
2100
- provider: "plivo",
2101
- raw: response,
2102
- };
2103
- }
2104
-
2105
- export type { SendSmsInput, SendSmsResult } from "./types";
2106
- `}function Kc(){return`import Dysmsapi, { SendSmsRequest } from "@alicloud/dysmsapi20170525";
2107
- import * as $OpenApi from "@alicloud/openapi-client";
2108
- import type { SendSmsInput, SendSmsResult } from "./types";
2109
-
2110
- let client: Dysmsapi | null = null;
2111
-
2112
- function getClient(): Dysmsapi {
2113
- if (client) return client;
2114
- const accessKeyId = process.env.ALIYUN_SMS_ACCESS_KEY;
2115
- const accessKeySecret = process.env.ALIYUN_SMS_ACCESS_SECRET;
2116
- if (!accessKeyId || !accessKeySecret) {
2117
- throw new Error(
2118
- "ALIYUN_SMS_ACCESS_KEY and ALIYUN_SMS_ACCESS_SECRET must be set",
2119
- );
2120
- }
2121
- const config = new $OpenApi.Config({ accessKeyId, accessKeySecret });
2122
- config.endpoint = process.env.ALIYUN_SMS_ENDPOINT ?? "dysmsapi.aliyuncs.com";
2123
- client = new Dysmsapi(config);
2124
- return client;
2125
- }
2126
-
2127
- export async function sendSms(input: SendSmsInput): Promise<SendSmsResult> {
2128
- const signName = process.env.ALIYUN_SMS_SIGN_NAME;
2129
- const templateCode = input.templateCode ?? process.env.ALIYUN_SMS_TEMPLATE_CODE;
2130
- if (!signName) throw new Error("ALIYUN_SMS_SIGN_NAME must be set");
2131
- if (!templateCode) throw new Error("templateCode or ALIYUN_SMS_TEMPLATE_CODE must be provided");
2132
-
2133
- const req = new SendSmsRequest({
2134
- phoneNumbers: input.to,
2135
- signName,
2136
- templateCode,
2137
- templateParam: JSON.stringify(input.params ?? {}),
2138
- });
2139
-
2140
- const response = await getClient().sendSms(req);
2141
- const body = response.body;
2142
-
2143
- if (body?.code !== "OK") {
2144
- throw new Error(\`Aliyun SMS failed: \${body?.code} \${body?.message}\`);
2145
- }
2146
-
2147
- return {
2148
- messageId: body?.bizId,
2149
- provider: "aliyun-sms",
2150
- raw: body,
2151
- };
2152
- }
2153
-
2154
- export type { SendSmsInput, SendSmsResult } from "./types";
2155
- `}function Yc(){return`import * as tencentcloud from "tencentcloud-sdk-nodejs-sms";
2156
- import type { SendSmsInput, SendSmsResult } from "./types";
2157
-
2158
- const SmsClient = tencentcloud.sms.v20210111.Client;
2159
- type SmsClientInstance = InstanceType<typeof SmsClient>;
2160
-
2161
- let client: SmsClientInstance | null = null;
2162
-
2163
- function getClient(): SmsClientInstance {
2164
- if (client) return client;
2165
- const secretId = process.env.TENCENT_SMS_SECRET_ID;
2166
- const secretKey = process.env.TENCENT_SMS_SECRET_KEY;
2167
- if (!secretId || !secretKey) {
2168
- throw new Error(
2169
- "TENCENT_SMS_SECRET_ID and TENCENT_SMS_SECRET_KEY must be set",
2170
- );
2171
- }
2172
-
2173
- client = new SmsClient({
2174
- credential: { secretId, secretKey },
2175
- region: process.env.TENCENT_SMS_REGION ?? "ap-guangzhou",
2176
- profile: {
2177
- httpProfile: { endpoint: "sms.tencentcloudapi.com" },
2178
- },
2179
- });
2180
- return client;
2181
- }
2182
-
2183
- export async function sendSms(input: SendSmsInput): Promise<SendSmsResult> {
2184
- const sdkAppId = process.env.TENCENT_SMS_SDK_APP_ID;
2185
- const signName = process.env.TENCENT_SMS_SIGN_NAME;
2186
- const templateId = input.templateCode ?? process.env.TENCENT_SMS_TEMPLATE_ID;
2187
-
2188
- if (!sdkAppId) throw new Error("TENCENT_SMS_SDK_APP_ID must be set");
2189
- if (!signName) throw new Error("TENCENT_SMS_SIGN_NAME must be set");
2190
- if (!templateId) {
2191
- throw new Error("templateCode or TENCENT_SMS_TEMPLATE_ID must be provided");
2192
- }
2193
-
2194
- // Tencent expects phone numbers with + prefix (E.164). Also expects ordered
2195
- // template params as a string array matching the template placeholders.
2196
- const to = input.to.startsWith("+") ? input.to : \`+\${input.to}\`;
2197
- const templateParamSet = Object.values(input.params ?? {}).map(String);
2198
-
2199
- const response = await getClient().SendSms({
2200
- SmsSdkAppId: sdkAppId,
2201
- SignName: signName,
2202
- TemplateId: templateId,
2203
- PhoneNumberSet: [to],
2204
- TemplateParamSet: templateParamSet,
2205
- });
2206
-
2207
- const first = response.SendStatusSet?.[0];
2208
- if (first && first.Code !== "Ok") {
2209
- throw new Error(\`Tencent SMS failed: \${first.Code} \${first.Message}\`);
2210
- }
2211
-
2212
- return {
2213
- messageId: first?.SerialNo,
2214
- provider: "tencent-sms",
2215
- raw: response,
2216
- };
2217
- }
2218
-
2219
- export type { SendSmsInput, SendSmsResult } from "./types";
2220
- `}function Gc(){return`// @ts-expect-error \u2014 yunpian-nodejs-sdk ships no type declarations
2221
- import Yunpian from "yunpian-nodejs-sdk";
2222
- import type { SendSmsInput, SendSmsResult } from "./types";
2223
-
2224
- let client: { sms: { single_send(opts: unknown): Promise<unknown>; tpl_single_send(opts: unknown): Promise<unknown> } } | null =
2225
- null;
2226
-
2227
- function getClient() {
2228
- if (client) return client;
2229
- const apiKey = process.env.YUNPIAN_API_KEY;
2230
- if (!apiKey) throw new Error("YUNPIAN_API_KEY must be set");
2231
- client = new Yunpian(apiKey);
2232
- return client!;
2233
- }
2234
-
2235
- /**
2236
- * Yunpian (\u4E91\u7247) supports two modes:
2237
- * - tpl_single_send: registered template + tpl_value
2238
- * - single_send: ad-hoc text (must include registered \u7B7E\u540D)
2239
- */
2240
- export async function sendSms(input: SendSmsInput): Promise<SendSmsResult> {
2241
- const sign = process.env.YUNPIAN_SIGN ?? "";
2242
-
2243
- if (input.templateCode) {
2244
- const tplValue = Object.entries(input.params ?? {})
2245
- .map(([k, v]) => \`#\${k}#=\${encodeURIComponent(String(v))}\`)
2246
- .join("&");
2247
-
2248
- const raw = await getClient().sms.tpl_single_send({
2249
- mobile: input.to,
2250
- tpl_id: input.templateCode,
2251
- tpl_value: tplValue,
2252
- });
2253
-
2254
- return { provider: "yunpian", raw };
2255
- }
2256
-
2257
- const text = input.body
2258
- ? input.body.startsWith("\u3010")
2259
- ? input.body
2260
- : \`\${sign}\${input.body}\`
2261
- : \`\${sign}\${Object.values(input.params ?? {}).join(" ")}\`;
2262
-
2263
- const raw = await getClient().sms.single_send({
2264
- mobile: input.to,
2265
- text,
2266
- });
2267
-
2268
- return { provider: "yunpian", raw };
2269
- }
2270
-
2271
- export type { SendSmsInput, SendSmsResult } from "./types";
2272
- `}function qc(e){switch(e){case"twilio":return Bc();case"messagebird":return $c();case"plivo":return Vc();case"aliyun-sms":return Kc();case"tencent-sms":return Yc();case"yunpian":return Gc();case"none":return`export {};
2273
- `}}function Hc(e){let t={};for(let o of e.pkgs??[]){let r=Dc[o];r&&(t[o]=r)}let n={name:Uc,version:"0.1.0",private:!0,description:`Transactional SMS via ${e.name}`,type:"module",main:"./dist/index.js",exports:{".":{import:"./dist/index.js",types:"./dist/index.d.ts"}},scripts:{build:"tsup",typecheck:"tsc --noEmit",clean:"rm -rf dist .turbo"},dependencies:t,devDependencies:{tsup:"^8.5.0",typescript:"catalog:"}};return JSON.stringify(n,null,2)+`
2274
- `}async function Wr(e,t,n="global"){if(t==="none")return;let o=Hr(t);if(!o)throw new Error(`Unknown SMS provider "${t}". Valid ids: ${Yt.map(i=>i.id).join(", ")}`);let r=Ge.join(e,"packages","sms");if(!xe.existsSync(r))return;let s=Ge.join(r,"src");xe.existsSync(s)&&xe.rmSync(s,{recursive:!0,force:!0});try{Rn(Ge.join(r,"package.json"),Hc(o)),Rn(Ge.join(s,"types.ts"),jc),Rn(Ge.join(s,"index.ts"),qc(o.id)),Mc(e,Fc(o))}catch(i){throw console.error(`Failed to apply SMS selection "${t}":`,i),new Error(`SMS scaffold for "${t}" failed \u2014 see stderr for details.`)}}import He from"fs";import qe from"path";var We="@nebutra/storage";function kn(e,t){He.mkdirSync(qe.dirname(e),{recursive:!0}),He.writeFileSync(e,t)}function Wc(e,t){let n=qe.join(e,".env.example");He.existsSync(n)?He.appendFileSync(n,`
2275
- `+t):He.writeFileSync(n,t)}function Xc(e){if(e.envVars.length===0)return"";let t=`# Storage provider: ${e.name}`,n=e.envVars.map(o=>`${o}=`).join(`
2276
- `);return`${t}
2277
- ${n}
2278
- `}var zc=`/**
2279
- * Inputs for the \`uploadFile\` wrapper. Keep this surface portable across
2280
- * providers \u2014 provider-specific options belong in the wrapper itself.
2281
- */
2282
- export interface UploadFileInput {
2283
- key: string;
2284
- body: Buffer | Uint8Array | string;
2285
- contentType?: string;
2286
- /** Optional access control. Most providers default to private. */
2287
- acl?: "private" | "public-read";
2288
- }
2289
-
2290
- export interface UploadFileResult {
2291
- key: string;
2292
- /** Public or signed URL, whichever the provider returns by default. */
2293
- url?: string;
2294
- raw?: unknown;
2295
- }
2296
-
2297
- export interface SignedUrlOptions {
2298
- /** Time-to-live in seconds. Defaults to 3600 (1h). */
2299
- expiresIn?: number;
2300
- /** Operation \u2014 "get" for download, "put" for direct upload. */
2301
- operation?: "get" | "put";
2302
- contentType?: string;
2303
- }
2304
- `;function Jc(){return`/**
2305
- * @${We.slice(1)} \u2014 Cloudflare R2 via the S3-compatible API.
2306
- *
2307
- * Docs: https://developers.cloudflare.com/r2/api/s3/api/
2308
- */
2309
- import {
2310
- GetObjectCommand,
2311
- PutObjectCommand,
2312
- S3Client,
2313
- } from "@aws-sdk/client-s3";
2314
- import { getSignedUrl as awsGetSignedUrl } from "@aws-sdk/s3-request-presigner";
2315
-
2316
- import type {
2317
- SignedUrlOptions,
2318
- UploadFileInput,
2319
- UploadFileResult,
2320
- } from "./types";
2321
-
2322
- const accountId = process.env.R2_ACCOUNT_ID;
2323
- const accessKeyId = process.env.R2_ACCESS_KEY_ID;
2324
- const secretAccessKey = process.env.R2_SECRET_ACCESS_KEY;
2325
- const bucket = process.env.R2_BUCKET;
2326
-
2327
- if (!accountId || !accessKeyId || !secretAccessKey || !bucket) {
2328
- throw new Error(
2329
- "R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET must be configured",
2330
- );
2331
- }
2332
-
2333
- const client = new S3Client({
2334
- region: "auto",
2335
- endpoint: \`https://\${accountId}.r2.cloudflarestorage.com\`,
2336
- credentials: { accessKeyId, secretAccessKey },
2337
- });
2338
-
2339
- export async function uploadFile(input: UploadFileInput): Promise<UploadFileResult> {
2340
- const response = await client.send(
2341
- new PutObjectCommand({
2342
- Bucket: bucket,
2343
- Key: input.key,
2344
- Body: input.body,
2345
- ContentType: input.contentType,
2346
- }),
2347
- );
2348
- return { key: input.key, raw: response };
2349
- }
2350
-
2351
- export async function getSignedUrl(
2352
- key: string,
2353
- opts: SignedUrlOptions = {},
2354
- ): Promise<string> {
2355
- const expiresIn = opts.expiresIn ?? 3600;
2356
- const command =
2357
- opts.operation === "put"
2358
- ? new PutObjectCommand({
2359
- Bucket: bucket,
2360
- Key: key,
2361
- ContentType: opts.contentType,
2362
- })
2363
- : new GetObjectCommand({ Bucket: bucket, Key: key });
2364
- return awsGetSignedUrl(client, command, { expiresIn });
2365
- }
2366
-
2367
- export type { UploadFileInput, UploadFileResult, SignedUrlOptions } from "./types";
2368
- `}function Qc(){return`/**
2369
- * @${We.slice(1)} \u2014 AWS S3 wrapper.
2370
- *
2371
- * Docs: https://docs.aws.amazon.com/s3/
2372
- */
2373
- import {
2374
- GetObjectCommand,
2375
- PutObjectCommand,
2376
- S3Client,
2377
- } from "@aws-sdk/client-s3";
2378
- import { getSignedUrl as awsGetSignedUrl } from "@aws-sdk/s3-request-presigner";
2379
-
2380
- import type {
2381
- SignedUrlOptions,
2382
- UploadFileInput,
2383
- UploadFileResult,
2384
- } from "./types";
2385
-
2386
- const region = process.env.AWS_S3_REGION;
2387
- const bucket = process.env.S3_BUCKET;
2388
- if (!region || !bucket) {
2389
- throw new Error("AWS_S3_REGION and S3_BUCKET must be configured");
2390
- }
2391
-
2392
- const client = new S3Client({
2393
- region,
2394
- credentials:
2395
- process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY
2396
- ? {
2397
- accessKeyId: process.env.AWS_ACCESS_KEY_ID,
2398
- secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
2399
- }
2400
- : undefined,
2401
- });
2402
-
2403
- export async function uploadFile(input: UploadFileInput): Promise<UploadFileResult> {
2404
- const response = await client.send(
2405
- new PutObjectCommand({
2406
- Bucket: bucket,
2407
- Key: input.key,
2408
- Body: input.body,
2409
- ContentType: input.contentType,
2410
- ACL: input.acl,
2411
- }),
2412
- );
2413
- return { key: input.key, raw: response };
2414
- }
2415
-
2416
- export async function getSignedUrl(
2417
- key: string,
2418
- opts: SignedUrlOptions = {},
2419
- ): Promise<string> {
2420
- const expiresIn = opts.expiresIn ?? 3600;
2421
- const command =
2422
- opts.operation === "put"
2423
- ? new PutObjectCommand({
2424
- Bucket: bucket,
2425
- Key: key,
2426
- ContentType: opts.contentType,
2427
- })
2428
- : new GetObjectCommand({ Bucket: bucket, Key: key });
2429
- return awsGetSignedUrl(client, command, { expiresIn });
2430
- }
2431
-
2432
- export type { UploadFileInput, UploadFileResult, SignedUrlOptions } from "./types";
2433
- `}function Zc(){return`/**
2434
- * @${We.slice(1)} \u2014 Supabase Storage wrapper.
2435
- *
2436
- * Docs: https://supabase.com/docs/guides/storage
2437
- */
2438
- import { createClient } from "@supabase/supabase-js";
2439
-
2440
- import type {
2441
- SignedUrlOptions,
2442
- UploadFileInput,
2443
- UploadFileResult,
2444
- } from "./types";
2445
-
2446
- const url = process.env.SUPABASE_URL;
2447
- const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
2448
- const bucket = process.env.SUPABASE_BUCKET;
2449
- if (!url || !serviceRoleKey || !bucket) {
2450
- throw new Error(
2451
- "SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, SUPABASE_BUCKET must be configured",
2452
- );
2453
- }
2454
-
2455
- const client = createClient(url, serviceRoleKey, {
2456
- auth: { persistSession: false },
2457
- });
2458
-
2459
- export async function uploadFile(input: UploadFileInput): Promise<UploadFileResult> {
2460
- const { data, error } = await client.storage
2461
- .from(bucket)
2462
- .upload(input.key, input.body, {
2463
- contentType: input.contentType,
2464
- upsert: true,
2465
- });
2466
- if (error) {
2467
- throw new Error(\`Supabase upload failed: \${error.message}\`);
2468
- }
2469
- return { key: data?.path ?? input.key, raw: data };
2470
- }
2471
-
2472
- export async function getSignedUrl(
2473
- key: string,
2474
- opts: SignedUrlOptions = {},
2475
- ): Promise<string> {
2476
- const expiresIn = opts.expiresIn ?? 3600;
2477
- if (opts.operation === "put") {
2478
- const { data, error } = await client.storage
2479
- .from(bucket)
2480
- .createSignedUploadUrl(key);
2481
- if (error || !data) {
2482
- throw new Error(\`Supabase signed upload url failed: \${error?.message ?? "unknown"}\`);
2483
- }
2484
- return data.signedUrl;
2485
- }
2486
- const { data, error } = await client.storage
2487
- .from(bucket)
2488
- .createSignedUrl(key, expiresIn);
2489
- if (error || !data) {
2490
- throw new Error(\`Supabase signed url failed: \${error?.message ?? "unknown"}\`);
2491
- }
2492
- return data.signedUrl;
2493
- }
2494
-
2495
- export type { UploadFileInput, UploadFileResult, SignedUrlOptions } from "./types";
2496
- `}function ep(){return`/**
2497
- * @${We.slice(1)} \u2014 \u963F\u91CC\u4E91 OSS wrapper via ali-oss.
2498
- *
2499
- * Docs: https://help.aliyun.com/product/31815.html
2500
- */
2501
- import OSS from "ali-oss";
2502
-
2503
- import type {
2504
- SignedUrlOptions,
2505
- UploadFileInput,
2506
- UploadFileResult,
2507
- } from "./types";
2508
-
2509
- const accessKeyId = process.env.ALIYUN_OSS_ACCESS_KEY;
2510
- const accessKeySecret = process.env.ALIYUN_OSS_ACCESS_SECRET;
2511
- const region = process.env.ALIYUN_OSS_REGION;
2512
- const bucket = process.env.ALIYUN_OSS_BUCKET;
2513
- if (!accessKeyId || !accessKeySecret || !region || !bucket) {
2514
- throw new Error(
2515
- "ALIYUN_OSS_ACCESS_KEY, ALIYUN_OSS_ACCESS_SECRET, ALIYUN_OSS_REGION, ALIYUN_OSS_BUCKET must be configured",
2516
- );
2517
- }
2518
-
2519
- const client = new OSS({ accessKeyId, accessKeySecret, region, bucket });
2520
-
2521
- export async function uploadFile(input: UploadFileInput): Promise<UploadFileResult> {
2522
- const body =
2523
- typeof input.body === "string" ? Buffer.from(input.body) : Buffer.from(input.body);
2524
- const response = await client.put(input.key, body, {
2525
- mime: input.contentType,
2526
- });
2527
- return { key: response.name, url: response.url, raw: response };
2528
- }
2529
-
2530
- export async function getSignedUrl(
2531
- key: string,
2532
- opts: SignedUrlOptions = {},
2533
- ): Promise<string> {
2534
- const expires = opts.expiresIn ?? 3600;
2535
- return client.signatureUrl(key, {
2536
- expires,
2537
- method: opts.operation === "put" ? "PUT" : "GET",
2538
- "Content-Type": opts.contentType,
2539
- });
2540
- }
2541
-
2542
- export type { UploadFileInput, UploadFileResult, SignedUrlOptions } from "./types";
2543
- `}function tp(){return`/**
2544
- * @${We.slice(1)} \u2014 \u817E\u8BAF\u4E91 COS wrapper via cos-nodejs-sdk-v5.
2545
- *
2546
- * Docs: https://cloud.tencent.com/document/product/436
2547
- */
2548
- import COS from "cos-nodejs-sdk-v5";
2549
-
2550
- import type {
2551
- SignedUrlOptions,
2552
- UploadFileInput,
2553
- UploadFileResult,
2554
- } from "./types";
2555
-
2556
- const secretId = process.env.TENCENT_COS_SECRET_ID;
2557
- const secretKey = process.env.TENCENT_COS_SECRET_KEY;
2558
- const bucket = process.env.TENCENT_COS_BUCKET;
2559
- const region = process.env.TENCENT_COS_REGION;
2560
- if (!secretId || !secretKey || !bucket || !region) {
2561
- throw new Error(
2562
- "TENCENT_COS_SECRET_ID, TENCENT_COS_SECRET_KEY, TENCENT_COS_BUCKET, TENCENT_COS_REGION must be configured",
2563
- );
2564
- }
2565
-
2566
- const cos = new COS({ SecretId: secretId, SecretKey: secretKey });
2567
-
2568
- export async function uploadFile(input: UploadFileInput): Promise<UploadFileResult> {
2569
- const body =
2570
- typeof input.body === "string" ? Buffer.from(input.body) : Buffer.from(input.body);
2571
- const response = await cos.putObject({
2572
- Bucket: bucket,
2573
- Region: region,
2574
- Key: input.key,
2575
- Body: body,
2576
- ContentType: input.contentType,
2577
- });
2578
- return { key: input.key, url: response.Location, raw: response };
2579
- }
2580
-
2581
- export async function getSignedUrl(
2582
- key: string,
2583
- opts: SignedUrlOptions = {},
2584
- ): Promise<string> {
2585
- const expires = opts.expiresIn ?? 3600;
2586
- const url = cos.getObjectUrl({
2587
- Bucket: bucket,
2588
- Region: region,
2589
- Key: key,
2590
- Sign: true,
2591
- Method: opts.operation === "put" ? "PUT" : "GET",
2592
- Expires: expires,
2593
- } as unknown as COS.GetObjectUrlParams);
2594
- return url as unknown as string;
2595
- }
2596
-
2597
- export type { UploadFileInput, UploadFileResult, SignedUrlOptions } from "./types";
2598
- `}function np(){return`/**
2599
- * @${We.slice(1)} \u2014 \u4E03\u725B\u4E91 Kodo wrapper.
2600
- *
2601
- * Docs: https://developer.qiniu.com/kodo
2602
- */
2603
- import qiniu from "qiniu";
2604
-
2605
- import type {
2606
- SignedUrlOptions,
2607
- UploadFileInput,
2608
- UploadFileResult,
2609
- } from "./types";
2610
-
2611
- const accessKey = process.env.QINIU_ACCESS_KEY;
2612
- const secretKey = process.env.QINIU_SECRET_KEY;
2613
- const bucket = process.env.QINIU_BUCKET;
2614
- const domain = process.env.QINIU_DOMAIN;
2615
- if (!accessKey || !secretKey || !bucket || !domain) {
2616
- throw new Error(
2617
- "QINIU_ACCESS_KEY, QINIU_SECRET_KEY, QINIU_BUCKET, QINIU_DOMAIN must be configured",
2618
- );
2619
- }
2620
-
2621
- const mac = new qiniu.auth.digest.Mac(accessKey, secretKey);
2622
- const config = new qiniu.conf.Config();
2623
- const formUploader = new qiniu.form_up.FormUploader(config);
2624
- const bucketManager = new qiniu.rs.BucketManager(mac, config);
2625
-
2626
- function uploadToken(key: string): string {
2627
- const putPolicy = new qiniu.rs.PutPolicy({ scope: \`\${bucket}:\${key}\` });
2628
- return putPolicy.uploadToken(mac);
2629
- }
2630
-
2631
- export async function uploadFile(input: UploadFileInput): Promise<UploadFileResult> {
2632
- const body =
2633
- typeof input.body === "string" ? Buffer.from(input.body) : Buffer.from(input.body);
2634
-
2635
- return new Promise<UploadFileResult>((resolve, reject) => {
2636
- formUploader.put(
2637
- uploadToken(input.key),
2638
- input.key,
2639
- body,
2640
- new qiniu.form_up.PutExtra(undefined, undefined, input.contentType),
2641
- (err, body, info) => {
2642
- if (err) return reject(err);
2643
- if (info.statusCode !== 200) {
2644
- return reject(new Error(\`Qiniu upload failed: \${info.statusCode}\`));
2645
- }
2646
- resolve({ key: input.key, raw: body });
2647
- },
2648
- );
2649
- });
2650
- }
2651
-
2652
- export async function getSignedUrl(
2653
- key: string,
2654
- opts: SignedUrlOptions = {},
2655
- ): Promise<string> {
2656
- const expires = opts.expiresIn ?? 3600;
2657
- if (opts.operation === "put") {
2658
- // Qiniu issues upload tokens, not signed PUT URLs.
2659
- return uploadToken(key);
2660
- }
2661
- const deadline = Math.floor(Date.now() / 1000) + expires;
2662
- return bucketManager.privateDownloadUrl(domain, key, deadline);
2663
- }
2664
-
2665
- export type { UploadFileInput, UploadFileResult, SignedUrlOptions } from "./types";
2666
- `}function op(e){switch(e){case"r2":return Jc();case"s3":return Qc();case"supabase-storage":return Zc();case"aliyun-oss":return ep();case"tencent-cos":return tp();case"qiniu":return np();case"none":return`export {};
2667
- `}}function rp(e){let t={};e.pkg&&e.pkgVersion&&(t[e.pkg]=e.pkgVersion),(e.id==="r2"||e.id==="s3")&&(t["@aws-sdk/s3-request-presigner"]="^3.658.0");let n={name:We,version:"0.1.0",private:!0,description:`Object storage via ${e.name}`,type:"module",main:"./dist/index.js",exports:{".":{import:"./dist/index.js",types:"./dist/index.d.ts"}},scripts:{build:"tsup",typecheck:"tsc --noEmit",clean:"rm -rf dist .turbo"},dependencies:t,devDependencies:{tsup:"^8.5.0",typescript:"catalog:"}};return JSON.stringify(n,null,2)+`
2668
- `}async function Xr(e,t,n){if(t==="none")return;let o=Hn(t);if(!o)throw new Error(`Unknown storage provider "${t}". Valid ids: ${ke.map(i=>i.id).join(", ")}`);let r=qe.join(e,"packages","storage"),s=qe.join(r,"src");He.existsSync(s)&&He.rmSync(s,{recursive:!0,force:!0}),kn(qe.join(r,"package.json"),rp(o)),kn(qe.join(s,"types.ts"),zc),kn(qe.join(s,"index.ts"),op(o.id)),Wc(e,Xc(o))}var Gt=[{id:"svix",name:"Svix",region:"global",envVars:["SVIX_API_TOKEN"],docs:"https://docs.svix.com",description:"Managed webhooks platform"},{id:"custom",name:"Custom (self-hosted)",region:"both",envVars:["WEBHOOK_SIGNING_SECRET"],docs:"",description:"Custom implementation with built-in package"},{id:"none",name:"None",region:"both",envVars:[],docs:"",description:""}];function zr(e){return Gt.find(t=>t.id===e)}var pm=Gt.reduce((e,t)=>(e[t.region]||(e[t.region]=[]),e[t.region].push(t),e),{});async function Jr(e,t,n="global"){let o=zr(t);if(!o)throw new Error(`Unknown webhooks provider "${t}". Valid ids: ${Gt.map(r=>r.id).join(", ")}`);if(o.id==="none"){Z(e,"webhooks");return}o.envVars.length>0&&ae(e,{category:"Webhooks",name:o.name,envVars:o.envVars,docs:o.docs}),ce(e,"WEBHOOKS_PROVIDER",o.id)}import Xe from"fs";import At from"path";function sp(e){return`import Link from "next/link";
838
+ `}function Fn(e){if(F.existsSync(e))try{let t=F.readFileSync(e,"utf8"),n=JSON.parse(t),r=n.scripts??{},s=!1;r["db:seed"]||(r["db:seed"]="prisma db seed",s=!0);let o=n.prisma??{};if(o.seed||(o.seed="tsx prisma/seed.ts",s=!0),!s)return;let i={...n,scripts:r,prisma:o};F.writeFileSync(e,JSON.stringify(i,null,2)+`
839
+ `)}catch(t){let n=t instanceof Error?t.message:String(t);console.warn(`[create-sailor] Failed to patch seed scripts in ${e}: ${n}`)}}async function qe(e){let t=In(e);if(t)try{let n=D.join(t.packageDir,"prisma","schema.prisma"),r=On(n),s=Ln(r),o=D.dirname(t.seedPath);F.existsSync(o)||F.mkdirSync(o,{recursive:!0}),F.writeFileSync(t.seedPath,s),Fn(t.packageJsonPath)}catch(n){let r=n instanceof Error?n.message:String(n);throw console.error(`[create-sailor] Failed to generate seed data: ${r}`),new Error(`Unable to write prisma/seed.ts: ${r}`)}}import j from"fs";import J from"path";function $n(e){return`import Link from "next/link";
2669
840
 
2670
841
  export default function WelcomePage() {
2671
842
  const projectName = ${JSON.stringify(e)};
@@ -2774,28 +945,12 @@ function Step({
2774
945
  </li>
2775
946
  );
2776
947
  }
2777
- `}function ip(e={}){return`
2778
- ## What you can do next
2779
-
2780
- ${[e.apiKeys===!1?"- Manage API keys at `/settings/api-keys` (disabled \u2014 re-enable with `--api-keys=true`)":"- Manage API keys at `/settings/api-keys`",e.auditLog===!1?"- View audit log at `/settings/audit-log` (disabled \u2014 re-enable with `--audit-log=true`)":"- View audit log at `/settings/audit-log`","- Configure webhooks at `/settings/webhooks`",e.commandPalette===!1?"- Press \u2318K to open the command palette (disabled \u2014 re-enable with `--command-palette=true`)":"- Press \u2318K to open the command palette","- Edit notification preferences at `/settings/notifications`","- Export your data at `/settings/account/export`","- For China deployments, see `packages/ops/china-compliance/README.md`"].join(`
2781
- `)}
2782
- `}function ap(e=[]){return e.length===0?"":`
2783
- ## Production readiness holds
2784
-
2785
- Do not enable these in production until provider credentials, adapter wiring, and the integration contract are verified:
2786
-
2787
- ${e.map(n=>{let o=tt(n.status);return`- ${n.flag}=${n.provider} ${o} \u2014 ${Ne(n.status)}`}).join(`
2788
- `)}
2789
-
2790
- Review the package readiness matrix before handoff: [docs/package-status.md](docs/package-status.md)
2791
- `}function cp(e,t,n=[],o={}){return`# Next steps \u2014 ${e}
2792
-
2793
- Region: ${t}
948
+ `}function Dn(){return"\n## What you can do next\n\n- See which capabilities are live and which keys they need: `nebutra status`\n- Manage API keys at `/settings/api-keys`\n- Configure webhooks at `/settings/webhooks`\n- Press \u2318K to open the command palette\n- For China deployments, set `NEBUTRA_LOCALE=cn` and see `packages/ops/china-compliance/README.md`\n"}function jn(e){return`# Next steps \u2014 ${e}
2794
949
 
2795
950
  Your AI-native SaaS scaffold is ready. Complete these four steps to go from template to running dev server:
2796
951
 
2797
952
  1. **Configure environment** \u2014 fill in \`.env.local\`
2798
- > Random cryptographic secrets have been generated for you. Add your provider keys (DB, auth, payments, AI).
953
+ > Random cryptographic secrets have been generated for you. Every capability runs locally without keys; add a provider key to take it live.
2799
954
 
2800
955
  2. **Run database migrations** \u2014 sync your Prisma schema with your database
2801
956
  \`\`\`bash
@@ -2814,147 +969,90 @@ Your AI-native SaaS scaffold is ready. Complete these four steps to go from temp
2814
969
  \`\`\`
2815
970
 
2816
971
  After setup, delete the welcome route at \`apps/web/src/app/[locale]/welcome/\`.
2817
- ${ip(o)}${ap(n)}
972
+ ${Dn()}
2818
973
 
2819
974
  ## Resources
2820
975
 
2821
976
  - [Documentation](/docs)
2822
977
  - [GitHub](https://github.com/nebutra/nebutra-sailor)
2823
978
  - [Licensing](https://nebutra.com/licensing)
2824
- `}async function Qr(e,t){let{projectName:n,region:o,previewSelections:r=[],waveFeatures:s={}}=t;try{let i=At.join(e,".sailor");Xe.existsSync(i)||Xe.mkdirSync(i,{recursive:!0}),Xe.writeFileSync(At.join(i,"next-steps.md"),cp(n,o,r,s))}catch(i){let a=i instanceof Error?i.message:String(i);console.warn(`[create-sailor] Failed to write .sailor/next-steps.md: ${a}`)}try{let i=At.join(e,"apps","web");if(!Xe.existsSync(i))return;let a=At.join(i,"src","app","[locale]","welcome");Xe.existsSync(a)||Xe.mkdirSync(a,{recursive:!0}),Xe.writeFileSync(At.join(a,"page.tsx"),sp(n))}catch(i){let a=i instanceof Error?i.message:String(i);console.warn(`[create-sailor] Failed to write welcome page: ${a}`)}}function u(e,t){e&&process.stdout.write(JSON.stringify(t)+`
2825
- `)}function Zr(e){return e!==null&&typeof e=="object"&&"code"in e&&e.code==="ENOENT"}function lp(e,t){for(let n of t)if(e)u(!0,{event:"warn",step:n.flag,provider:n.provider,packageStatus:n.status,message:Ne(n.status)});else{let o=tt(n.status);process.stdout.write(F.yellow(`\u26A0 ${n.flag}=${n.provider} ${o} \u2014 ${Ne(n.status)}
2826
- `))}}function dp(e,t,n){let o=ze.join(t,"packages/platform/db/prisma/schema.prisma"),r;try{r=Ht.readFileSync(o,"utf8")}catch(s){if(Zr(s)){u(e,{event:"step",step:"schema-prune",status:"skip"});return}let i=s instanceof Error?s.message:String(s);u(e,{event:"step",step:"schema-prune",status:"error",error:i});return}u(e,{event:"step",step:"schema-prune",status:"start"});try{let s=xn(r,n);Ht.writeFileSync(o,s),Tn(ze.join(ze.dirname(o),"migrations"),n),u(e,{event:"step",step:"schema-prune",status:"ok"})}catch(s){let i=s instanceof Error?s.message:String(s);u(e,{event:"step",step:"schema-prune",status:"error",error:i})}}function up(e){let t=0,n=process.stdout.isTTY===!0,o=r=>{if(!n){process.stdout.write(` ${r}
2827
- `);return}process.stdout.write(`\r\x1B[2K ${r}`)};return r=>{if(e){u(!0,{event:"step",step:"clone",status:"start",phase:r.phase,source:r.source,receivedBytes:r.receivedBytes,totalBytes:r.totalBytes,attempt:r.attempt});return}switch(r.phase){case"resolve":o(F.dim(`Resolving ${r.source}\u2026`));break;case"download":{let s=Date.now();if(r.receivedBytes&&s-t<120)return;t=s;let i=it(r.receivedBytes??0),a=r.totalBytes?` / ${it(r.totalBytes)}`:"",c=r.totalBytes&&r.receivedBytes?` (${Math.floor(r.receivedBytes/r.totalBytes*100)}%)`:"";o(F.dim(`Downloading template ${i}${a}${c}`));break}case"retry":n&&process.stdout.write("\r\x1B[2K"),process.stdout.write(F.yellow(` \u26A0 ${r.reason} \u2014 retrying (attempt ${r.attempt}/3)
2828
- `));break;case"extract":o(F.dim("Extracting template\u2026"));break;case"strip":o(F.dim("Stripping template-only files\u2026"));break;case"install":o(F.dim("Writing project files\u2026"));break}}}async function es(e){let{resolvedTarget:t,projectName:n,resolvedPm:o,opts:r,useJson:s,resolved:i,startedAt:a}=e,{region:c,orm:p,database:m,databaseHost:d,payment:v,paymentChoice:g,auth:h,socialLoginIds:P,aiProviders:E,deployTarget:l,docs:y,metering:U,billingMode:R,idp:ve,accessGate:ue,mcp:oe,waveToggles:G,previewSelections:q,config:I}=i,z={auth:h,payment:g,"billing-mode":R,idp:ve,template:"saas","access-gate":ue};u(s,{event:"step",step:"clone",status:"start"});let V=await or(t,{onProgress:up(s)});e.reportCreatedEntries?.(V.createdEntries),s?u(!0,{event:"step",step:"clone",status:"ok",source:V.source,bytes:V.bytes}):(process.stdout.isTTY===!0&&process.stdout.write("\r\x1B[2K"),process.stdout.write(F.green(" \u2713 Template ready")+F.dim(` (${it(V.bytes)} from ${V.source})
2829
- `))),u(s,{event:"step",step:"package",status:"start"}),await hr(t,n),u(s,{event:"step",step:"package",status:"ok"}),u(s,{event:"step",step:"config",status:"start"}),await To(t,I),u(s,{event:"step",step:"config",status:"ok"}),u(s,{event:"step",step:"governance-lints",status:"start"});let A=await sr(t,I);u(s,{event:"step",step:"governance-lints",status:"ok",lints:A.lints}),u(s,{event:"step",step:"prune",status:"start"}),await Rr(t,I),u(s,{event:"step",step:"prune",status:"ok"});{let b=ze.join(t,"packages/platform/db/prisma/schema.prisma"),D;try{D=Ht.readFileSync(b,"utf-8")}catch(_){if(Zr(_))u(s,{event:"step",step:"schema-prune",status:"skip"});else{let me=_ instanceof Error?_.message:String(_);u(s,{event:"step",step:"schema-prune",status:"error",error:me})}}if(D!==void 0){u(s,{event:"step",step:"schema-prune",status:"start"});try{let _=xn(D,z);Ht.writeFileSync(b,_),Tn(ze.join(ze.dirname(b),"migrations"),z),u(s,{event:"step",step:"schema-prune",status:"ok"})}catch(_){let me=_ instanceof Error?_.message:String(_);u(s,{event:"step",step:"schema-prune",status:"error",error:me})}}}if(u(s,{event:"step",step:"auth",choice:h,status:"start"}),await yo(t,h),u(s,{event:"step",step:"auth",choice:h,status:"ok"}),P.length>0?(u(s,{event:"step",step:"social-login",providers:P,status:"start"}),await ho(t,P,h),u(s,{event:"step",step:"social-login",providers:P,status:"ok"})):u(s,{event:"step",step:"social-login",status:"skip"}),u(s,{event:"step",step:"db",choice:m,status:"start"}),await ko(t,m,n),m!=="none"){let b=ft(d);b&&(await No(t,b,n),u(s,{event:"step",step:"db-host",choice:d,status:"ok"}))}if(u(s,{event:"step",step:"db",choice:m,status:"ok"}),p==="drizzle"){u(s,{event:"step",step:"orm",choice:"drizzle",status:"start"});let b=await br(t,"drizzle",m);u(s,{event:"step",step:"orm",choice:"drizzle",status:b.applied?"ok":"skip",...b.reason?{reason:b.reason}:{}})}if(u(s,{event:"step",step:"payment",choice:g,status:"start"}),await wr(t,g),u(s,{event:"step",step:"payment",choice:g,status:"ok"}),await jo(t,i.email,c),s&&u(!0,{event:"step",step:"email",choice:i.email,status:"ok"}),await Xr(t,i.storage,c),s&&u(!0,{event:"step",step:"storage",choice:i.storage,status:"ok"}),await fr(t,i.monitoring,c),s&&u(!0,{event:"step",step:"monitoring",choice:i.monitoring,status:"ok"}),await mo(t,i.analytics,c),s&&u(!0,{event:"step",step:"analytics",choice:i.analytics,status:"ok"}),await Wr(t,i.sms,c),s&&u(!0,{event:"step",step:"sms",choice:i.sms,status:"ok"}),await Mr(t,i.queue,c),s&&u(!0,{event:"step",step:"queue",choice:i.queue,status:"ok"}),await Gr(t,i.search,c),s&&u(!0,{event:"step",step:"search",choice:i.search,status:"ok"}),await _o(t,i.cache,c),s&&u(!0,{event:"step",step:"cache",choice:i.cache,status:"ok"}),await Sr(t,i.notifications,c),s&&u(!0,{event:"step",step:"notifications",choice:i.notifications,status:"ok"}),await Jr(t,i.webhooks,c),s&&u(!0,{event:"step",step:"webhooks",choice:i.webhooks,status:"ok"}),await Co(t,i.cms,c),s&&u(!0,{event:"step",step:"cms",choice:i.cms,status:"ok"}),await Ho(t,i.featureFlags,c),s&&u(!0,{event:"step",step:"feature-flags",choice:i.featureFlags,status:"ok"}),await Po(t,i.captcha,c),s&&u(!0,{event:"step",step:"captcha",choice:i.captcha,status:"ok"}),await lr(t,oe),s&&u(!0,{event:"step",step:"mcp",mode:oe,status:"ok"}),await ur(t,U,v),s&&u(!0,{event:"step",step:"metering",mode:U,status:"ok"}),u(s,{event:"step",step:"prune-wave-features",status:"start"}),kr(t,G),u(s,{event:"step",step:"prune-wave-features",status:"ok",toggles:G}),I.orm==="prisma"&&I.database!=="none"?dp(s,t,z):u(s,{event:"step",step:"schema-prune",status:"skip"}),u(s,{event:"step",step:"billing-mode",choice:R,status:"ok"}),u(s,{event:"step",step:"idp",choice:ve,status:"ok"}),await Io(t,c),s&&u(!0,{event:"step",step:"compliance",region:c,status:"ok"}),await Go(t),await qr(t,h),await Qr(t,{projectName:n,region:c,previewSelections:q,waveFeatures:G}),I.aiMode!=="none"){u(s,{event:"step",step:"ai-providers",status:"start"});let b={providerIds:I.aiProviders,customEndpoint:I.customAiEndpoint},D=ze.join(t,"packages/ai/ai-providers/templates");await Tr(t,b,D),u(s,{event:"step",step:"ai-providers",status:"ok"})}else u(s,{event:"step",step:"ai-providers",status:"skip"});u(s,{event:"step",step:"docs",status:"start"}),y!=="none"?(await Uo(t,{framework:y,projectName:n}),u(s,{event:"step",step:"docs",framework:"fumadocs",requested:y,status:"ok"})):u(s,{event:"step",step:"docs",status:"skip"}),u(s,{event:"step",step:"deploy-target",status:"start"}),l!=="none"&&await io(t,l),await so(t,I.deployTargets),u(s,{event:"step",step:"deploy-target",status:"ok",targets:I.deployTargets});let Re={databaseUrl:"postgresql://postgres:postgres@localhost:5432/nebutra",clerkPublishable:"",clerkSecret:""};u(s,{event:"step",step:"env",status:"start"}),await Vo(t,Re),u(s,{event:"step",step:"env",status:"ok"}),u(s,{event:"step",step:"scaffold-extras",status:"start"});let be=await jr(t,{projectName:n,withWorkflows:!!r.withWorkflows,withPythonBackend:!!r.withPythonBackend});u(s,{event:"step",step:"scaffold-extras",status:"ok",applied:be.applied,skipped:be.skipped}),u(s,{event:"step",step:"license",status:"start"});try{let b=pr(t,{projectName:n,cliVersion:Q});u(s,{event:"step",step:"license",status:"ok",tier:"mit-scaffold",wrote:b.wrote}),s||process.stdout.write(F.dim(` License: MIT \u2014 commercial use, closed source, no fee, no attribution.
979
+ `}async function Ke(e,t){let{projectName:n}=t;try{let r=J.join(e,".sailor");j.existsSync(r)||j.mkdirSync(r,{recursive:!0}),j.writeFileSync(J.join(r,"next-steps.md"),jn(n))}catch(r){let s=r instanceof Error?r.message:String(r);console.warn(`[create-sailor] Failed to write .sailor/next-steps.md: ${s}`)}try{let r=J.join(e,"apps","web");if(!j.existsSync(r))return;let s=J.join(r,"src","app","[locale]","welcome");j.existsSync(s)||j.mkdirSync(s,{recursive:!0}),j.writeFileSync(J.join(s,"page.tsx"),$n(n))}catch(r){let s=r instanceof Error?r.message:String(r);console.warn(`[create-sailor] Failed to write welcome page: ${s}`)}}function m(e,t){e&&process.stdout.write(JSON.stringify(t)+`
980
+ `)}function Bn(e){let t=0,n=process.stdout.isTTY===!0,r=s=>{if(!n){process.stdout.write(` ${s}
981
+ `);return}process.stdout.write(`\r\x1B[2K ${s}`)};return s=>{if(e){m(!0,{event:"step",step:"clone",status:"start",phase:s.phase,source:s.source,receivedBytes:s.receivedBytes,totalBytes:s.totalBytes,attempt:s.attempt});return}switch(s.phase){case"resolve":r(S.dim(`Resolving ${s.source}\u2026`));break;case"download":{let o=Date.now();if(s.receivedBytes&&o-t<120)return;t=o;let i=B(s.receivedBytes??0),c=s.totalBytes?` / ${B(s.totalBytes)}`:"",a=s.totalBytes&&s.receivedBytes?` (${Math.floor(s.receivedBytes/s.totalBytes*100)}%)`:"";r(S.dim(`Downloading template ${i}${c}${a}`));break}case"retry":n&&process.stdout.write("\r\x1B[2K"),process.stdout.write(S.yellow(` \u26A0 ${s.reason} \u2014 retrying (attempt ${s.attempt}/3)
982
+ `));break;case"extract":r(S.dim("Extracting template\u2026"));break;case"strip":r(S.dim("Stripping template-only files\u2026"));break;case"install":r(S.dim("Writing project files\u2026"));break}}}async function Ze(e){let{resolvedTarget:t,projectName:n,resolvedPm:r,opts:s,useJson:o,startedAt:i}=e,c=we();m(o,{event:"step",step:"clone",status:"start"});let a=await De(t,{onProgress:Bn(o)});e.reportCreatedEntries?.(a.createdEntries),o?m(!0,{event:"step",step:"clone",status:"ok",source:a.source,bytes:a.bytes}):(process.stdout.isTTY===!0&&process.stdout.write("\r\x1B[2K"),process.stdout.write(S.green(" \u2713 Template ready")+S.dim(` (${B(a.bytes)} from ${a.source})
983
+ `))),m(o,{event:"step",step:"package",status:"start"}),await He(t,n),m(o,{event:"step",step:"package",status:"ok"}),m(o,{event:"step",step:"config",status:"start"}),await ve(t,c),m(o,{event:"step",step:"config",status:"ok"}),m(o,{event:"step",step:"governance-lints",status:"start"});let l=await Me(t);m(o,{event:"step",step:"governance-lints",status:"ok",lints:l.lints}),await Ee(t,"global"),m(o,{event:"step",step:"compliance",status:"ok"}),await _e(t),await qe(t),await Ke(t,{projectName:n});let d={databaseUrl:"postgresql://postgres:postgres@localhost:5432/nebutra"};m(o,{event:"step",step:"env",status:"start"}),await Ae(t,d),m(o,{event:"step",step:"env",status:"ok"}),m(o,{event:"step",step:"scaffold-extras",status:"start"});let u=await Je(t,{projectName:n});m(o,{event:"step",step:"scaffold-extras",status:"ok",applied:u.applied,skipped:u.skipped}),m(o,{event:"step",step:"license",status:"start"});try{let p=Ye(t,{projectName:n,cliVersion:x});m(o,{event:"step",step:"license",status:"ok",tier:"mit-scaffold",wrote:p.wrote}),o||process.stdout.write(S.dim(` License: MIT \u2014 commercial use, closed source, no fee, no attribution.
2830
984
  Upstream terms preserved as LICENSE-UPSTREAM-REFERENCE.md.
2831
- `))}catch(b){u(s,{event:"step",step:"license",status:"warn",message:b instanceof Error?b.message:String(b)})}if(r.install!==!1){let b=o==="bun"?"bun":o,D=["install"];s?u(!0,{event:"step",step:"install",pm:o,status:"start"}):process.stdout.write(F.dim(` Installing dependencies with ${o}\u2026
2832
- `));try{qt(b,D,{cwd:t,stdio:s?"ignore":"inherit"}),u(s,{event:"step",step:"install",status:"ok"}),s||process.stdout.write(F.green(` \u2713 Dependencies installed (${o})
2833
- `))}catch(_){let me=_ instanceof Error?_.message:String(_);s?u(!0,{event:"step",step:"install",status:"error",error:me}):process.stdout.write(F.yellow(`
985
+ `))}catch(p){m(o,{event:"step",step:"license",status:"warn",message:p instanceof Error?p.message:String(p)})}if(s.install!==!1){let p=r==="bun"?"bun":r,E=["install"];o?m(!0,{event:"step",step:"install",pm:r,status:"start"}):process.stdout.write(S.dim(` Installing dependencies with ${r}\u2026
986
+ `));try{Z(p,E,{cwd:t,stdio:o?"ignore":"inherit"}),m(o,{event:"step",step:"install",status:"ok"}),o||process.stdout.write(S.green(` \u2713 Dependencies installed (${r})
987
+ `))}catch(q){let Y=q instanceof Error?q.message:String(q);o?m(!0,{event:"step",step:"install",status:"error",error:Y}):process.stdout.write(S.yellow(`
2834
988
  \u26A0 Install failed \u2014 the project files are ready, but deps are incomplete.
2835
- Fix: ${F.cyan(`cd ${t} && ${o} install`)}
989
+ Fix: ${S.cyan(`cd ${t} && ${r} install`)}
2836
990
 
2837
- `))}}else u(s,{event:"step",step:"install",status:"skip"});if(r.git!==!1){s&&u(!0,{event:"step",step:"git-init",status:"start"});try{qt("git",["init","-q"],{cwd:t,stdio:"ignore"}),qt("git",["add","-A"],{cwd:t,stdio:"ignore"}),qt("git",["-c","user.email=you@example.com","-c","user.name=You","commit","-q","-m","chore: initial scaffold from create-sailor"],{cwd:t,stdio:"ignore"}),u(s,{event:"step",step:"git-init",status:"ok"})}catch(b){let D=b instanceof Error?b.message:String(b);s?u(!0,{event:"step",step:"git-init",status:"error",error:D}):process.stdout.write(F.yellow(` \u26A0 git init skipped \u2014 not fatal.
2838
- `))}}else u(s,{event:"step",step:"git-init",status:"skip"});let _e=Math.max(1,Math.round((Date.now()-a)/1e3));fo({template_version:Q,package_manager:o,region:c,auth:h,payment:g,ai_providers:E,deploy_target:l,duration_ms:Date.now()-a}),q.length>0&&lp(s,q),s?u(!0,{event:"done",status:"ok",elapsedSec:_e,targetDir:t,previewSelections:q,waveFeatures:G}):po({elapsedSec:_e,targetDir:t,packageManager:o,skippedInstall:r.install===!1,hasDatabase:m!=="none",previewSelections:q});try{pp({pkg:{name:"create-sailor",version:Q},updateCheckInterval:1e3*60*60*24}).notify({defer:!1,isGlobal:!0})}catch{}}import mp from"cfonts";import gp from"picocolors";function ts(){if(process.env.NO_COLOR||!process.stdout.isTTY){process.stdout.write([`Sailor v${Q}`,"AI-Native SaaS Unicorn Template","by Nebutra \xB7 https://nebutra.com","",""].join(`
2839
- `));return}mp.say("Sailor",{font:"block",align:"left",colors:["#0033FE","#0BF1C3"],gradient:["#0033FE","#0BF1C3"],transitionGradient:!0,space:!1}),process.stdout.write(gp.dim(` AI-Native SaaS Template \xB7 v${Q} \xB7 nebutra.com
991
+ `))}}else m(o,{event:"step",step:"install",status:"skip"});if(s.git!==!1){o&&m(!0,{event:"step",step:"git-init",status:"start"});try{Z("git",["init","-q"],{cwd:t,stdio:"ignore"}),Z("git",["add","-A"],{cwd:t,stdio:"ignore"}),Z("git",["-c","user.email=you@example.com","-c","user.name=You","commit","-q","-m","chore: initial scaffold from create-sailor"],{cwd:t,stdio:"ignore"}),m(o,{event:"step",step:"git-init",status:"ok"})}catch(p){let E=p instanceof Error?p.message:String(p);o?m(!0,{event:"step",step:"git-init",status:"error",error:E}):process.stdout.write(S.yellow(` \u26A0 git init skipped \u2014 not fatal.
992
+ `))}}else m(o,{event:"step",step:"git-init",status:"skip"});let A=Math.max(1,Math.round((Date.now()-i)/1e3));be({template_version:x,package_manager:r,duration_ms:Date.now()-i}),o?m(!0,{event:"done",status:"ok",elapsedSec:A,targetDir:t}):ye({elapsedSec:A,targetDir:t,packageManager:r,skippedInstall:s.install===!1});try{Mn({pkg:{name:"create-sailor",version:x},updateCheckInterval:1e3*60*60*24}).notify({defer:!1,isGlobal:!0})}catch{}}function Qe(){let e=process.env.npm_config_user_agent??"";return e.startsWith("pnpm")?"pnpm":e.startsWith("yarn")?"yarn":e.startsWith("bun")?"bun":"npm"}import Un from"cfonts";import Gn from"picocolors";function et(){if(process.env.NO_COLOR||!process.stdout.isTTY){process.stdout.write([`Sailor v${x}`,"AI-Native SaaS Unicorn Template","by Nebutra \xB7 https://nebutra.com","",""].join(`
993
+ `));return}Un.say("Sailor",{font:"block",align:"left",colors:["#0033FE","#0BF1C3"],gradient:["#0033FE","#0BF1C3"],transitionGradient:!0,space:!1}),process.stdout.write(Gn.dim(` AI-Native SaaS Template \xB7 v${x} \xB7 nebutra.com
2840
994
 
2841
- `))}var fp=`Usage: create-sailor [name] [options]
995
+ `))}var Yn=`Usage: create-sailor [name] [options]
2842
996
 
2843
997
  Arguments:
2844
998
  name project name or path (default: my-app; use . for cwd)
2845
999
 
2846
- Core options:
1000
+ There is nothing to choose. Every project gets the same converged stack:
1001
+ Next.js + Hono gateway \xB7 Postgres + Prisma \xB7 Better Auth \xB7 Stripe (+ WeChat Pay /
1002
+ Alipay) \xB7 Resend \xB7 S3-compatible storage (R2 / OSS) \xB7 QStash \xB7 Redis \xB7 Sentry \xB7
1003
+ PostHog \xB7 Turnstile \xB7 MCP
1004
+
1005
+ Every capability runs locally with an empty .env. Add a provider key to take it
1006
+ live; \`nebutra status\` shows what is live and what each capability still needs.
1007
+ China deployments: set NEBUTRA_LOCALE=cn (ICP footer, AIGC disclosure, phone login).
1008
+
1009
+ Options:
2847
1010
  -p, --pm <id> npm | pnpm | yarn | bun (auto-detected)
2848
- --region <id> global | cn | hybrid (default: global)
2849
- --auth <id> clerk | betterauth | nextauth | supabase | none
2850
- --ai <ids> expert/non-interactive provider seed (e.g. openai,anthropic)
2851
-
2852
- Interactive AI topology:
2853
- Default first-run asks for topology, not individual provider enablement:
2854
- gateway Multi-provider AI Gateway / router (recommended)
2855
- direct Direct SDK/provider adapters
2856
- custom OpenAI-compatible endpoint
2857
- none Skip AI
2858
-
2859
- Stack flags (region-based smart defaults):
2860
- --orm <id> prisma | drizzle | none (default: prisma)
2861
- --db <id> postgres | mysql | sqlite | none (default: postgres)
2862
- --payment <id> stripe | lemon | wechat | alipay | none (default: region-based)
2863
- --deploy <target> vercel | railway | cloudflare | selfhost (default: vercel)
2864
- also writes per-service DEPLOY_TARGET_* selectors
2865
- --docs <id> fumadocs | mintlify | docusaurus | nextra | vitepress | none
2866
-
2867
- Feature flags (region-based smart defaults):
2868
- --email <id> resend | postmark | ses | aliyun-dm | tencent-ses | netease | none
2869
- --storage <id> r2 | s3 | supabase-storage | aliyun-oss | tencent-cos | qiniu | none
2870
- --monitoring <id> sentry | datadog | aliyun-arms | tingyun | none
2871
- --analytics <id> posthog | plausible | umami | baidu | sensors | none
2872
- --sms <id> twilio | aliyun-sms | tencent-sms | yunpian | none
2873
- --queue <id> qstash | bullmq | upstash | sqs | none [Foundation]
2874
- --search <id> meilisearch | typesense | algolia | pgvector | none [Foundation]
2875
- --cache <id> upstash-redis | vercel-kv | redis | dragonfly | none
2876
- --notifications <id> novu | knock | custom | none
2877
- --webhooks <id> svix | custom | none
2878
- --cms <id> sanity | contentful | strapi | none
2879
- --feature-flags <id> vercel-flags | growthbook | configcat | none [Foundation]
2880
- --captcha <id> turnstile | hcaptcha | aliyun-slide | none [WIP]
2881
- --billing-mode <mode> usage | seat | credits (default: usage)
2882
- --idp <id> clerk | oauth-server (default: clerk)
2883
- --access-gate <mode> none | invite (default: none)
2884
-
2885
- Preview statuses:
2886
- [Foundation] \u2014 core path works; provider credentials, adapter wiring, or integration verification still required
2887
- [WIP] \u2014 actively being built; do not use in production
2888
- See: https://github.com/Nebutra/Nebutra-Sailor/blob/main/docs/package-status.md
2889
-
2890
- Sailor features:
2891
- --mcp <mode> on | off (default: on \u2014 Agent-friendly MCP server)
2892
- --metering <mode> auto | on | off (default: auto)
2893
-
2894
- Toggles:
2895
- --i18n enable i18n (default: true)
2896
- --no-i18n disable
2897
1011
  --no-install skip package install
2898
1012
  --no-git skip git init
2899
- -y, --yes accept all defaults (non-interactive)
1013
+ -y, --yes non-interactive
2900
1014
  --dry-run preview actions without writing files
2901
- --json machine-readable output
1015
+ --json machine-readable output (NDJSON events)
2902
1016
  --no-color disable color output
2903
1017
  -h, --help show this help
2904
1018
  -v, --version show version
2905
1019
 
2906
1020
  Requires Node.js >= 22 (same floor as the monorepo).
2907
1021
 
2908
- Interactive flow:
2909
- 1. Location \u2014 new folder (name) or current directory
2910
- 2. Region / Auth / AI topology
2911
- 3. Plan summary \u2014 confirm or customize payment \xB7 email \xB7 storage \xB7 deploy
2912
- 4. Scaffold + golden-path next steps
2913
-
2914
1022
  Examples:
2915
- $ npx create-sailor@latest
2916
- $ npx create-sailor@latest my-app -y
2917
- $ npx create-sailor@latest . --region=cn
2918
- $ npx create-sailor@latest my-app --region=cn --auth=clerk
2919
- $ npx create-sailor@latest my-app --region=hybrid --ai=openai,deepseek
2920
- $ npx create-sailor@latest my-app --dry-run --region=cn
2921
- `;function ns(){process.stdout.write(fp)}import Wt from"picocolors";function os(){return!process.env.NO_COLOR&&!!process.stdout.isTTY}function yp(e){let t=os()?"\u25B8":"->",n=`[${e.index}/${e.total}]`,o=e.label.padEnd(18," ");return os()?`${Wt.dim(n)} ${Wt.bold(o)} ${Wt.dim(t)} ${Wt.cyan(e.value)}`:`${n} ${o} ${t} ${e.value}`}function rs(e){process.stdout.write(yp(e)+`
2922
- `)}import{existsSync as Sp,mkdirSync as hp,writeFileSync as Ep}from"fs";import{homedir as ss,platform as vp}from"os";import{join as Ct}from"path";import Nn from"picocolors";function bp(){if(vp()==="win32"){let t=process.env.LOCALAPPDATA||Ct(ss(),"AppData","Local");return Ct(t,"nebutra")}let e=process.env.XDG_CONFIG_HOME;return e?Ct(e,"nebutra"):Ct(ss(),".config","nebutra")}function _p(){let e=process.env.NEBUTRA_TELEMETRY;return e==="0"||e==="false"||e==="no"}function is(e,t){try{hp(e,{recursive:!0}),Ep(t,"")}catch{}}function as(){if(!process.stderr.isTTY||!process.stdin.isTTY||process.env.CI)return;let e=bp(),t=Ct(e,"first-run-acked");try{if(Sp(t))return}catch{return}if(_p()){is(e,t);return}let o=[`${Nn.cyan("\u2139")} Nebutra collects anonymous usage analytics to improve the scaffolder.`,` Opt out at any time: ${Nn.dim("export NEBUTRA_TELEMETRY=0")}`,` Privacy policy: ${Nn.dim("https://nebutra.com/legal/cli-analytics")}`,""];try{process.stderr.write(o.join(`
2923
- `))}catch{}is(e,t)}import mt from"fs";import wp from"os";import B from"path";import*as L from"@clack/prompts";var he="my-app",Pp=new Set([".DS_Store",".Spotlight-V100",".Trashes","Thumbs.db","desktop.ini"]),cs=new Set(["package.json","node_modules","pnpm-workspace.yaml","pnpm-lock.yaml","yarn.lock","package-lock.json","bun.lockb","turbo.json"]);function Ap(){let e=wp.homedir(),t=new Map,n=(o,r)=>{o&&t.set(B.resolve(o),r)};n(e,"your home directory"),n(B.parse(e||"/").root,"the filesystem root");for(let o of["Desktop","Documents","Downloads","Library","Movies","Music","Pictures","Public"])e&&n(B.join(e,o),`your ${o} folder`);return n(process.env.XDG_CONFIG_HOME,"your config directory"),e&&n(B.join(e,".config"),"your config directory"),t}function It(e){let t=B.resolve(e);return Ap().get(t)}function Un(e){let t=e.trim();return t==="."||t==="./"||t===".\\"}function On(e){if(!mt.existsSync(e))return!0;let t;try{t=mt.readdirSync(e)}catch{return!1}return t.every(n=>Pp.has(n)||n===".git")}function Ln(e){if(!mt.existsSync(e))return!1;let t;try{t=mt.readdirSync(e)}catch{return!0}return t.some(n=>cs.has(n))}function Cp(e=process.cwd()){return On(e)&&!Ln(e)}function ps(e){let t=e.trim();if(!t)return"Please enter a project name.";if(t.length>128)return"Name is too long (max 128 characters).";if(!Un(t)&&!(/[\\/]/.test(t)||B.isAbsolute(t))&&!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(t))return"Use letters, numbers, dots, underscores, or hyphens (start with a letter or number)."}function ut(e,t=process.cwd()){let n=e.trim()||he;if(Un(n)){let a=B.resolve(t);return{targetDir:".",projectName:B.basename(a)||he,absoluteDir:a}}if(B.isAbsolute(n)||n.startsWith("./")||n.startsWith(".\\")||n.startsWith("../")||n.startsWith("..\\")||n.includes("/")||n.includes("\\")){let a=B.resolve(t,n);return{targetDir:n,projectName:B.basename(a)||he,absoluteDir:a}}let r=ps(n);if(r)throw new Error(r);let s=`./${n}`,i=B.resolve(t,n);return{targetDir:s,projectName:n,absoluteDir:i}}async function ls(e={}){let t=e.cwd??process.cwd(),n=e.onCancel??(()=>{process.stdout.write(`Cancelled
2924
- `),process.exit(130)}),o=Cp(t),r=B.basename(t)||t,s=It(t),i=s?"new":await L.select({message:"Create the project\u2026",options:[{value:"new",label:"In a new folder",hint:o?void 0:"recommended from home or a parent directory"},{value:"current",label:"In the current directory",hint:o?`${r} looks empty \u2014 good to go`:t}],initialValue:o?"current":"new"});if(s&&L.log.info(`You're in ${s} \u2014 Sailor will create a subfolder here instead.`),L.isCancel(i)&&n(),i==="current"){let p=It(t);if(p&&(L.log.error(`Refusing to scaffold directly into ${p} (${t}). Run create-sailor from an empty folder, or pass a name: create-sailor my-app`),n()),Ln(t)){let m=mt.readdirSync(t).filter(d=>cs.has(d)).slice(0,4).join(", ");L.log.error(`Current directory already looks like a project (${m}). Choose a new folder, or pass a different path: create-sailor my-app`),n()}if(!On(t)){let m=await L.confirm({message:"Current directory is not empty. Scaffold files will be mixed in \u2014 continue?",initialValue:!1});(L.isCancel(m)||!m)&&n()}return ut(".",t)}let a=await L.text({message:"What should we call your project?",placeholder:he,defaultValue:he,validate:p=>{let m=(p??"").trim()||he;if(Un(m)||/[\\/]/.test(m)||B.isAbsolute(m))try{let d=ut(m,t),v=It(d.absoluteDir);return v?`That path is ${v} \u2014 choose a subfolder.`:void 0}catch(d){return d instanceof Error?d.message:"Invalid path."}return ps(m)}});L.isCancel(a)&&n();let c=ut(String(a).trim()||he,t);if(mt.existsSync(c.absoluteDir)&&!On(c.absoluteDir)){Ln(c.absoluteDir)&&(L.log.error(`Folder "${c.projectName}" already exists and looks like a project. Pick another name.`),n());let p=await L.confirm({message:`Folder "${c.projectName}" is not empty. Continue?`,initialValue:!1});(L.isCancel(p)||!p)&&n()}return c}import*as $ from"@clack/prompts";function Ip(e){return{...e.config,region:e.region,orm:e.orm,database:e.database,databaseHost:e.databaseHost,payment:e.payment,aiMode:e.aiMode,aiRouting:e.aiRouting,aiProviders:e.aiProviders,customAiEndpoint:e.customAiEndpoint,deployTarget:e.deployTarget,deployTargets:e.deployTargets,docs:e.docs,i18n:e.i18n,email:e.email,storage:e.storage,monitoring:e.monitoring,analytics:e.analytics,sms:e.sms,queue:e.queue,search:e.search,cache:e.cache,notifications:e.notifications,webhooks:e.webhooks,cms:e.cms,featureFlags:e.featureFlags,captcha:e.captcha,mcp:e.mcp,metering:e.metering,billingMode:e.billingMode,idp:e.idp,accessGate:e.accessGate}}function Tp(e){e.previewSelections=Rt([{flag:"queue",provider:e.queue},{flag:"search",provider:e.search},{flag:"notifications",provider:e.notifications},{flag:"webhooks",provider:e.webhooks},{flag:"feature-flags",provider:e.featureFlags},{flag:"captcha",provider:e.captcha},{flag:"access-gate",provider:e.accessGate}]),e.config=Ip(e)}async function xp(e,t){let n=await $.select({message:"Payment provider",options:[{value:"stripe",label:"Stripe",hint:"global default"},{value:"lemon",label:"Lemon Squeezy"},{value:"wechat",label:"WeChat Pay",hint:"CN"},{value:"alipay",label:"Alipay",hint:"CN"},{value:"none",label:"None"}],initialValue:e.paymentChoice});$.isCancel(n)&&t();let o=String(n);e.payment=yt(o),e.paymentChoice=St(o);let r=await $.select({message:"Email provider",options:st.map(a=>({value:a.id,label:a.name,hint:a.region==="cn"?"CN":a.id==="none"?void 0:"global"})),initialValue:e.email});$.isCancel(r)&&t(),e.email=String(r);let s=await $.select({message:"Object storage",options:ke.map(a=>({value:a.id,label:a.name,hint:a.region==="cn"?"CN":a.id==="none"?void 0:"global"})),initialValue:e.storage});$.isCancel(s)&&t(),e.storage=String(s);let i=await $.select({message:"Deploy target",options:[{value:"vercel",label:"Vercel",hint:"recommended for Next.js"},{value:"railway",label:"Railway"},{value:"cloudflare",label:"Cloudflare"},{value:"selfhost",label:"Self-host / Docker"}],initialValue:e.deployTarget});$.isCancel(i)&&t(),e.deployTarget=ht(String(i)),e.deployTargets=xt(e.deployTarget),Tp(e)}async function ds(e,t){let n={...e,config:{...e.config},aiProviders:[...e.aiProviders],socialLoginIds:[...e.socialLoginIds],previewSelections:[...e.previewSelections],deployTargets:{...e.deployTargets},waveToggles:{...e.waveToggles}},o=await $.select({message:"Create project with these settings?",options:[{value:"proceed",label:"Yes, create project",hint:"use region defaults above"},{value:"customize",label:"Customize key providers",hint:"payment \xB7 email \xB7 storage \xB7 deploy"},{value:"cancel",label:"Cancel"}],initialValue:"proceed"});return($.isCancel(o)||o==="cancel")&&t(),o==="customize"&&(await xp(n,t),$.log.success("Defaults updated \u2014 scaffolding with your choices.")),n}function Ee(e,t){e&&process.stdout.write(JSON.stringify(t)+`
2925
- `)}function kp(e,t,n,o,r){let{region:s,orm:i,database:a,paymentChoice:c,docs:p,socialLoginIds:m,aiMode:d,aiProviders:v,customAiEndpoint:g,deployTarget:h,metering:P,payment:E,billingMode:l,idp:y,mcp:U,waveToggles:R,previewSelections:ve}=t,{email:ue,storage:oe,monitoring:G,analytics:q,sms:I,queue:z,search:V,cache:A,notifications:Re,webhooks:be,cms:Je,featureFlags:Qe,captcha:_e}=t,b=v.length,D=[`clone template \u2192 ${n}`,"write nebutra.config.json",`governance-lints \u2192 write governance.config.json + wire pnpm lint (no-raw-inputs${a!=="none"?" + repository-seam":""})`,`prune template (orm=${i}, i18n=${t.i18n})`,`region \u2192 ${s}`,`auth \u2192 ${t.auth==="none"?"skip (remove packages/auth)":`configure ${t.auth}`}`,...m.length>0?[`social-login \u2192 generate ${m.length} callback route${m.length===1?"":"s"} + SocialLoginButtons.tsx (${m.join(", ")})`]:[],`db \u2192 ${a==="none"?"skip (remove packages/db)":`configure Prisma for ${a}`}`,...a!=="none"?[`db-host \u2192 configure ${t.databaseHost}`]:[],`payment \u2192 ${c==="none"?"skip (remove packages/billing)":`configure ${c}`}`,...p!=="none"?[`docs \u2192 scaffold apps/docs (${p==="fumadocs"?"fumadocs":`${p} \u2192 fumadocs fallback`})`]:[],...b>0||g?[`ai-providers \u2192 generate ${d} registry seed + env (${b}${g?" + custom":""} provider${b===1&&!g?"":"s"})`]:[],...ue!=="none"?[`email \u2192 configure ${ue}`]:[],...oe!=="none"?[`storage \u2192 configure ${oe}`]:[],...G!=="none"?[`monitoring \u2192 configure ${G}`]:[],...q!=="none"?[`analytics \u2192 configure ${q}`]:[],...I!=="none"?[`sms \u2192 configure ${I}`]:[],...z!=="none"?[`queue \u2192 configure ${z}`]:[],...V!=="none"?[`search \u2192 configure ${V}`]:[],...A!=="none"?[`cache \u2192 configure ${A}`]:[],...Re!=="none"?[`notifications \u2192 configure ${Re}`]:[],...be!=="none"?[`webhooks \u2192 configure ${be}`]:[],...Je!=="none"?[`cms \u2192 configure ${Je}`]:[],...Qe!=="none"?[`feature-flags \u2192 configure ${Qe}`]:[],..._e!=="none"?[`captcha \u2192 configure ${_e}`]:[],`mcp \u2192 ${U==="on"?"enable MCP server":"remove packages/mcp"}`,`metering \u2192 ${P==="off"?"disabled":P==="auto"?E!=="none"?"enabled (auto: payment set)":"disabled (auto: no payment)":"enabled"}`,...l!=="usage"?[`billing-mode \u2192 ${l}`]:[],...y!=="clerk"?[`idp \u2192 ${y}`]:[],`cron-jobs \u2192 ${R.cronJobs?"enabled":"disabled"}`,`audit-log \u2192 ${R.auditLog?"enabled":"disabled"}`,`api-keys \u2192 ${R.apiKeys?"enabled":"disabled"}`,`command-palette \u2192 ${R.commandPalette?"enabled":"disabled"}`,`cookie-consent \u2192 ${R.cookieConsent?"enabled":"disabled"}`,`legal-pages \u2192 ${R.legalPages?"enabled":"disabled"}`,`china-compliance \u2192 ${R.chinaCompliance?"enabled (@nebutra/china-compliance + ICP footer)":"disabled"}`,`compliance \u2192 inject ${s} boilerplate (ICP/Cookie/AIGC/Privacy)`,"welcome \u2192 generate dev welcome page","env \u2192 generate random secrets (AUTH_SECRET, JWT_SECRET)","seed \u2192 generate prisma/seed.ts (1 admin + 3 tenants)",...h!=="none"?[`deploy \u2192 inject ${h} config`]:[],"inject .env.local",r.install===!1?"skip install":`run ${o} install`,r.git===!1?"skip git init":"run git init"];if(e){for(let _ of D)Ee(!0,{event:"plan",action:_});for(let _ of ve)Ee(!0,{event:"warn",step:_.flag,provider:_.provider,packageStatus:_.status});Ee(!0,{event:"done",dryRun:!0})}else{process.stdout.write(`
2926
- `+x.bold(`Dry run \u2014 planned actions:
2927
- `));for(let _ of D)process.stdout.write(` \u2022 ${_}
2928
- `);if(ve.length>0){process.stdout.write(`
2929
- `+x.bold(x.yellow(`Preview-status providers selected:
2930
- `)));for(let _ of ve)process.stdout.write(x.yellow(`\u26A0 ${_.flag}=${_.provider}
2931
- `))}process.stdout.write(x.dim(`
1023
+ $ npx create-sailor@latest my-app
1024
+ $ npx create-sailor@latest .
1025
+ $ npx create-sailor@latest my-app --json --no-install
1026
+ `;function tt(){process.stdout.write(Yn)}import{existsSync as Hn,mkdirSync as Wn,writeFileSync as Xn}from"fs";import{homedir as nt,platform as Jn}from"os";import{join as V}from"path";import le from"picocolors";function Vn(){if(Jn()==="win32"){let t=process.env.LOCALAPPDATA||V(nt(),"AppData","Local");return V(t,"nebutra")}let e=process.env.XDG_CONFIG_HOME;return e?V(e,"nebutra"):V(nt(),".config","nebutra")}function zn(){let e=process.env.NEBUTRA_TELEMETRY;return e==="0"||e==="false"||e==="no"}function rt(e,t){try{Wn(e,{recursive:!0}),Xn(t,"")}catch{}}function st(){if(!process.stderr.isTTY||!process.stdin.isTTY||process.env.CI)return;let e=Vn(),t=V(e,"first-run-acked");try{if(Hn(t))return}catch{return}if(zn()){rt(e,t);return}let r=[`${le.cyan("\u2139")} Nebutra collects anonymous usage analytics to improve the scaffolder.`,` Opt out at any time: ${le.dim("export NEBUTRA_TELEMETRY=0")}`,` Privacy policy: ${le.dim("https://nebutra.com/legal/cli-analytics")}`,""];try{process.stderr.write(r.join(`
1027
+ `))}catch{}rt(e,t)}import G from"fs";import qn from"os";import C from"path";import*as b from"@clack/prompts";var R="my-app",Kn=new Set([".DS_Store",".Spotlight-V100",".Trashes","Thumbs.db","desktop.ini"]),ot=new Set(["package.json","node_modules","pnpm-workspace.yaml","pnpm-lock.yaml","yarn.lock","package-lock.json","bun.lockb","turbo.json"]);function Zn(){let e=qn.homedir(),t=new Map,n=(r,s)=>{r&&t.set(C.resolve(r),s)};n(e,"your home directory"),n(C.parse(e||"/").root,"the filesystem root");for(let r of["Desktop","Documents","Downloads","Library","Movies","Music","Pictures","Public"])e&&n(C.join(e,r),`your ${r} folder`);return n(process.env.XDG_CONFIG_HOME,"your config directory"),e&&n(C.join(e,".config"),"your config directory"),t}function z(e){let t=C.resolve(e);return Zn().get(t)}function de(e){let t=e.trim();return t==="."||t==="./"||t===".\\"}function pe(e){if(!G.existsSync(e))return!0;let t;try{t=G.readdirSync(e)}catch{return!1}return t.every(n=>Kn.has(n)||n===".git")}function ue(e){if(!G.existsSync(e))return!1;let t;try{t=G.readdirSync(e)}catch{return!0}return t.some(n=>ot.has(n))}function Qn(e=process.cwd()){return pe(e)&&!ue(e)}function it(e){let t=e.trim();if(!t)return"Please enter a project name.";if(t.length>128)return"Name is too long (max 128 characters).";if(!de(t)&&!(/[\\/]/.test(t)||C.isAbsolute(t))&&!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(t))return"Use letters, numbers, dots, underscores, or hyphens (start with a letter or number)."}function U(e,t=process.cwd()){let n=e.trim()||R;if(de(n)){let c=C.resolve(t);return{targetDir:".",projectName:C.basename(c)||R,absoluteDir:c}}if(C.isAbsolute(n)||n.startsWith("./")||n.startsWith(".\\")||n.startsWith("../")||n.startsWith("..\\")||n.includes("/")||n.includes("\\")){let c=C.resolve(t,n);return{targetDir:n,projectName:C.basename(c)||R,absoluteDir:c}}let s=it(n);if(s)throw new Error(s);let o=`./${n}`,i=C.resolve(t,n);return{targetDir:o,projectName:n,absoluteDir:i}}async function at(e={}){let t=e.cwd??process.cwd(),n=e.onCancel??(()=>{process.stdout.write(`Cancelled
1028
+ `),process.exit(130)}),r=Qn(t),s=C.basename(t)||t,o=z(t),i=o?"new":await b.select({message:"Create the project\u2026",options:[{value:"new",label:"In a new folder",hint:r?void 0:"recommended from home or a parent directory"},{value:"current",label:"In the current directory",hint:r?`${s} looks empty \u2014 good to go`:t}],initialValue:r?"current":"new"});if(o&&b.log.info(`You're in ${o} \u2014 Sailor will create a subfolder here instead.`),b.isCancel(i)&&n(),i==="current"){let l=z(t);if(l&&(b.log.error(`Refusing to scaffold directly into ${l} (${t}). Run create-sailor from an empty folder, or pass a name: create-sailor my-app`),n()),ue(t)){let d=G.readdirSync(t).filter(u=>ot.has(u)).slice(0,4).join(", ");b.log.error(`Current directory already looks like a project (${d}). Choose a new folder, or pass a different path: create-sailor my-app`),n()}if(!pe(t)){let d=await b.confirm({message:"Current directory is not empty. Scaffold files will be mixed in \u2014 continue?",initialValue:!1});(b.isCancel(d)||!d)&&n()}return U(".",t)}let c=await b.text({message:"What should we call your project?",placeholder:R,defaultValue:R,validate:l=>{let d=(l??"").trim()||R;if(de(d)||/[\\/]/.test(d)||C.isAbsolute(d))try{let u=U(d,t),y=z(u.absoluteDir);return y?`That path is ${y} \u2014 choose a subfolder.`:void 0}catch(u){return u instanceof Error?u.message:"Invalid path."}return it(d)}});b.isCancel(c)&&n();let a=U(String(c).trim()||R,t);if(G.existsSync(a.absoluteDir)&&!pe(a.absoluteDir)){ue(a.absoluteDir)&&(b.log.error(`Folder "${a.projectName}" already exists and looks like a project. Pick another name.`),n());let l=await b.confirm({message:`Folder "${a.projectName}" is not empty. Continue?`,initialValue:!1});(b.isCancel(l)||!l)&&n()}return a}function M(e,t){e&&process.stdout.write(JSON.stringify(t)+`
1029
+ `)}function tr(e,t,n,r){let s=[`copy template \u2192 ${t}`,"write nebutra.config.json (capabilities; providers come from env keys)","write governance.config.json + wire pnpm lint","inject compliance boilerplate (footer, cookie banner, privacy, terms)","copy backends/gateway, infra, e2e, tests, deploy (Dockerfile.web + docker-compose)","generate secrets, seed script and welcome page","inject .env.local (local Postgres, Better Auth)","write MIT scaffold license",r.install===!1?"skip install":`run ${n} install`,r.git===!1?"skip git init":"run git init"];if(e){for(let o of s)M(!0,{event:"plan",action:o});M(!0,{event:"done",dryRun:!0})}else{process.stdout.write(`
1030
+ `+v.bold(`Dry run \u2014 planned actions:
1031
+ `));for(let o of s)process.stdout.write(` \u2022 ${o}
1032
+ `);process.stdout.write(v.dim(`
2932
1033
  No files were written.
2933
- `))}}async function Np(){let e=Gn();e.parse(process.argv);let t=e.opts(),[n]=e.args;t.help&&(ns(),process.exit(0));let o=!!t.json,r=!!t.dryRun,i=!!t.yes||!process.stdin.isTTY;if(o||(as(),ts()),Ee(o,{event:"start",version:Q}),!t.pm)try{Rp("pnpm --version",{stdio:"ignore"})}catch{o||process.stderr.write(`
2934
- ${x.red("\u2718")} ${x.bold("pnpm is required")} but was not found on PATH.
1034
+ `))}}async function nr(){let e=ge();e.parse(process.argv);let t=e.opts(),[n]=e.args;t.help&&(tt(),process.exit(0));let r=!!t.json,s=!!t.dryRun,i=!!t.yes||!process.stdin.isTTY;if(r||(st(),et()),M(r,{event:"start",version:x}),!t.pm)try{er("pnpm --version",{stdio:"ignore"})}catch{r||process.stderr.write(`
1035
+ ${v.red("\u2718")} ${v.bold("pnpm is required")} but was not found on PATH.
2935
1036
 
2936
1037
  The scaffold uses pnpm workspaces + Turborepo. Install pnpm first:
2937
- ${x.cyan("npm i -g pnpm@10")}
2938
-
2939
- Then retry: ${x.cyan("pnpm dlx create-sailor@latest")}
2940
- (or pass ${x.dim("--pm=npm")} if you know workspace:* won't resolve in your setup)
2941
-
2942
- `),Ee(o,{event:"error",code:"PNPM_MISSING"}),process.exit(1)}let a=()=>{process.stdout.write(x.red(`\u2718 Cancelled
2943
- `)),process.exit(130)},c,p;if(n)try{let l=ut(n);c=l.targetDir,p=l.projectName}catch(l){let y=l instanceof Error?l.message:String(l);process.stderr.write(`
2944
- ${x.red("\u2718")} ${y}
2945
-
2946
- `),Ee(o,{event:"error",code:"INVALID_PROJECT_NAME",message:y}),process.exit(1)}else if(i){let l=ut(he);c=l.targetDir,p=l.projectName}else{let l=await ls({onCancel:a});c=l.targetDir,p=l.projectName,o||process.stdout.write(x.dim(` \u2192 ${l.absoluteDir}
2947
- `))}{let l=It(Dn.resolve(c));if(l){let y=`Refusing to scaffold into ${l} (${Dn.resolve(c)}).
2948
- Create a folder for the project instead: ${x.cyan("create-sailor my-app")}`;o||process.stderr.write(`
2949
- ${x.red("\u2718")} ${y}
2950
-
2951
- `),Ee(o,{event:"error",code:"UNSAFE_TARGET_DIR",message:y}),process.exit(1)}}let m=t.pm??zn(),d=await co(t,o),v=[["Project",p],["Region",d.region],["Auth",d.auth],["Payment",d.paymentChoice],["AI",`${d.aiMode}${d.aiProviders.length>0?` (${d.aiProviders.join(", ")})`:""}`],["Database",`${d.database} \xB7 ${d.databaseHost}`],["Email",d.email],["Storage",d.storage],["Deploy",d.deployTarget]];d.socialLoginIds.length>0&&v.push(["Social login",d.socialLoginIds.map(l=>et.find(y=>y.id===l)?.name??l).join(", ")]),o?v.forEach(([l,y],U)=>{Ee(!0,{event:"step",step:l,value:y,index:U+1,total:v.length})}):(process.stdout.write(x.dim(`
2952
- Plan
2953
- `)),v.forEach(([l,y],U)=>{rs({index:U+1,total:v.length,label:l,value:y})}),process.stdout.write(`
2954
- `)),!i&&!o&&(d=await ds(d,a)),r&&(kp(o,d,c,m,t),process.exit(0));let g=us.existsSync(c),h=[],P=()=>h,E=async()=>{process.stdout.write(`
2955
- `+x.red(`\u2718 Cancelled
2956
- `));let l=P();l.length===0&&g&&process.exit(130);let y=g?l.map(R=>Dn.join(c,R)):[c];if(await ms.confirm({message:g?`Remove the ${y.length} item${y.length===1?"":"s"} added to ${c}?`:`Remove the partial install at ${c}?`,initialValue:!0})===!0){for(let R of y)try{us.rmSync(R,{recursive:!0,force:!0})}catch{}process.stdout.write(x.dim(` \u2713 Cleaned up ${c}
2957
- `))}process.exit(130)};process.on("SIGINT",E);try{await es({resolvedTarget:c,projectName:p,resolvedPm:m,opts:t,useJson:o,resolved:d,startedAt:Date.now(),reportCreatedEntries:l=>{h=l}}),process.exit(0)}catch(l){let y=l instanceof Error?l.message:String(l);o?Ee(!0,{event:"error",message:y}):process.stdout.write(x.red(`
2958
- \u2718 Failed: ${y}
2959
- `)),process.exit(1)}}Np().catch(e=>{process.stderr.write(String(e)+`
1038
+ ${v.cyan("npm i -g pnpm@10")}
1039
+
1040
+ Then retry: ${v.cyan("pnpm dlx create-sailor@latest")}
1041
+ (or pass ${v.dim("--pm=npm")} if you know workspace:* won't resolve in your setup)
1042
+
1043
+ `),M(r,{event:"error",code:"PNPM_MISSING"}),process.exit(1)}let c=()=>{process.stdout.write(v.red(`\u2718 Cancelled
1044
+ `)),process.exit(130)},a,l;if(n)try{let p=U(n);a=p.targetDir,l=p.projectName}catch(p){let E=p instanceof Error?p.message:String(p);process.stderr.write(`
1045
+ ${v.red("\u2718")} ${E}
1046
+
1047
+ `),M(r,{event:"error",code:"INVALID_PROJECT_NAME",message:E}),process.exit(1)}else if(i){let p=U(R);a=p.targetDir,l=p.projectName}else{let p=await at({onCancel:c});a=p.targetDir,l=p.projectName,r||process.stdout.write(v.dim(` \u2192 ${p.absoluteDir}
1048
+ `))}{let p=z(me.resolve(a));if(p){let E=`Refusing to scaffold into ${p} (${me.resolve(a)}).
1049
+ Create a folder for the project instead: ${v.cyan("create-sailor my-app")}`;r||process.stderr.write(`
1050
+ ${v.red("\u2718")} ${E}
1051
+
1052
+ `),M(r,{event:"error",code:"UNSAFE_TARGET_DIR",message:E}),process.exit(1)}}let d=t.pm??Qe();s&&(tr(r,a,d,t),process.exit(0));let u=ct.existsSync(a),y=[],f=()=>y,A=async()=>{process.stdout.write(`
1053
+ `+v.red(`\u2718 Cancelled
1054
+ `));let p=f();p.length===0&&u&&process.exit(130);let E=u?p.map(Y=>me.join(a,Y)):[a];if(await lt.confirm({message:u?`Remove the ${E.length} item${E.length===1?"":"s"} added to ${a}?`:`Remove the partial install at ${a}?`,initialValue:!0})===!0){for(let Y of E)try{ct.rmSync(Y,{recursive:!0,force:!0})}catch{}process.stdout.write(v.dim(` \u2713 Cleaned up ${a}
1055
+ `))}process.exit(130)};process.on("SIGINT",A);try{await Ze({resolvedTarget:a,projectName:l,resolvedPm:d,opts:t,useJson:r,startedAt:Date.now(),reportCreatedEntries:p=>{y=p}}),process.exit(0)}catch(p){let E=p instanceof Error?p.message:String(p);r?M(!0,{event:"error",message:E}):process.stdout.write(v.red(`
1056
+ \u2718 Failed: ${E}
1057
+ `)),process.exit(1)}}nr().catch(e=>{process.stderr.write(String(e)+`
2960
1058
  `),process.exit(1)});