@nimbuslab/cli 0.16.3 → 0.16.5

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.
@@ -1,1323 +0,0 @@
1
- import * as p from "@clack/prompts"
2
- import pc from "picocolors"
3
- import { $ } from "bun"
4
- import { rm, mkdir } from "node:fs/promises"
5
- import { join } from "node:path"
6
-
7
- // AI Assistant configs
8
- const AI_CONFIGS: Record<string, { filename: string; content: (type: string) => string }> = {
9
- claude: {
10
- filename: "CLAUDE.md",
11
- content: (type) => `# ${type === "landing" ? "Landing Page" : type === "app" ? "Web App" : "Monorepo"}
12
-
13
- ## Stack
14
- - Next.js 16 (App Router, Turbopack)
15
- - React 19 (Server Components)
16
- - TypeScript (strict)
17
- - Tailwind CSS 4
18
- - shadcn/ui
19
- - Bun
20
- ${type === "app" ? "- Better Auth\n- Drizzle + PostgreSQL" : ""}
21
-
22
- ## Commands
23
- \`\`\`bash
24
- bun dev # Start development
25
- bun build # Production build
26
- bun lint # Run ESLint
27
- ${type === "app" ? "bun setup # Setup database" : ""}
28
- \`\`\`
29
-
30
- ## Conventions
31
- - Use \`bun\` for all package operations
32
- - Server Components by default
33
- - Dark mode first design
34
- - Use \`cn()\` for conditional classes
35
- - Add components: \`bunx --bun shadcn@latest add [component]\`
36
- `,
37
- },
38
- cursor: {
39
- filename: ".cursorrules",
40
- content: (type) => `# Cursor Rules
41
-
42
- Stack: Next.js 16, React 19, TypeScript, Tailwind CSS 4, shadcn/ui, Bun
43
- ${type === "app" ? "Auth: Better Auth | DB: Drizzle + PostgreSQL" : ""}
44
-
45
- - Server Components by default
46
- - "use client" only when needed
47
- - Tailwind utility classes
48
- - cn() for conditional classes
49
- - Dark mode first
50
- `,
51
- },
52
- gemini: {
53
- filename: ".gemini/GEMINI.md",
54
- content: (type) => `# ${type === "landing" ? "Landing Page" : type === "app" ? "Web App" : "Monorepo"}
55
-
56
- ## Stack
57
- - Next.js 16 (App Router, Turbopack)
58
- - React 19 (Server Components)
59
- - TypeScript (strict)
60
- - Tailwind CSS 4
61
- - shadcn/ui
62
- - Bun
63
- ${type === "app" ? "- Better Auth\n- Drizzle + PostgreSQL" : ""}
64
-
65
- ## Conventions
66
- - Use \`bun\` for all package operations
67
- - Server Components by default
68
- - Dark mode first design
69
- - Use \`cn()\` for conditional classes
70
- `,
71
- },
72
- copilot: {
73
- filename: ".github/copilot-instructions.md",
74
- content: (type) => `# GitHub Copilot Instructions
75
-
76
- Stack: Next.js 16, React 19, TypeScript, Tailwind CSS 4, shadcn/ui, Bun
77
- ${type === "app" ? "Auth: Better Auth | DB: Drizzle + PostgreSQL" : ""}
78
-
79
- ## Do
80
- - Use TypeScript strict mode
81
- - Prefer Server Components
82
- - Use Tailwind for styling
83
- - Use cn() for class merging
84
-
85
- ## Don't
86
- - Use CSS modules or styled-components
87
- - Use class components
88
- - Add unnecessary dependencies
89
- `,
90
- },
91
- windsurf: {
92
- filename: ".windsurfrules",
93
- content: (type) => `# Windsurf Rules
94
-
95
- Stack: Next.js 16, React 19, TypeScript, Tailwind CSS 4, shadcn/ui, Bun
96
- ${type === "app" ? "Auth: Better Auth | DB: Drizzle + PostgreSQL" : ""}
97
-
98
- - Server Components by default
99
- - "use client" only when needed
100
- - Tailwind utility classes
101
- - cn() for conditional classes
102
- - Dark mode first
103
- `,
104
- },
105
- }
106
-
107
- // GitHub CLI check
108
- async function checkGitHubCli(): Promise<{
109
- installed: boolean
110
- authenticated: boolean
111
- username: string | null
112
- orgs: string[]
113
- }> {
114
- const checkCmd = process.platform === "win32" ? "where" : "which"
115
- try {
116
- const hasGh = await $`${checkCmd} gh`.quiet().nothrow()
117
- if (hasGh.exitCode !== 0) {
118
- return { installed: false, authenticated: false, username: null, orgs: [] }
119
- }
120
-
121
- const authStatus = await $`gh auth status`.quiet().nothrow()
122
- if (authStatus.exitCode !== 0) {
123
- return { installed: true, authenticated: false, username: null, orgs: [] }
124
- }
125
-
126
- const username = (await $`gh api user --jq '.login'`.quiet().text()).trim()
127
- const orgsJson = await $`gh api user/orgs --jq '.[].login'`.quiet().text()
128
- const orgs = orgsJson.trim().split("\n").filter(Boolean)
129
-
130
- return { installed: true, authenticated: true, username, orgs }
131
- } catch {
132
- return { installed: false, authenticated: false, username: null, orgs: [] }
133
- }
134
- }
135
-
136
- // Templates privados (nimbuslab-templates) - uso interno
137
- const PRIVATE_TEMPLATES = {
138
- "fast": "nimbuslab-templates/fast-template",
139
- "fast+": "nimbuslab-templates/fastplus-template",
140
- "fast+-monorepo": "nimbuslab-templates/fastplus-monorepo-template",
141
- "nimbus-core": "nimbuslab/nimbus-core",
142
- }
143
-
144
- // Templates públicos (nimbuslab)
145
- const PUBLIC_TEMPLATES = {
146
- "landing": "nimbuslab/create-next-landing",
147
- "app": "nimbuslab/create-next-app",
148
- "turborepo": "nimbuslab/create-turborepo",
149
- }
150
-
151
- // Todos os templates
152
- const TEMPLATES = { ...PRIVATE_TEMPLATES, ...PUBLIC_TEMPLATES }
153
-
154
- // Dominios nimbuslab para deteccao de membros
155
- const NIMBUSLAB_DOMAINS = ["@nimbuslab.com.br", "@nimbuslab.net.br"]
156
-
157
- // Detectar se é membro da nimbuslab (verifica apenas dominio do email)
158
- async function isNimbuslabMember(): Promise<{ isMember: boolean; user: string | null; email: string | null }> {
159
- try {
160
- const user = (await $`git config user.name`.text()).trim()
161
- const email = (await $`git config user.email`.text()).trim()
162
-
163
- // Verifica se email termina com dominio nimbuslab
164
- const isMember = NIMBUSLAB_DOMAINS.some(domain => email.endsWith(domain))
165
-
166
- return { isMember, user, email }
167
- } catch {
168
- return { isMember: false, user: null, email: null }
169
- }
170
- }
171
-
172
- type ProjectType = "fast" | "fast+" | "landing" | "app" | "turborepo" | "nimbus-core"
173
-
174
- interface ProjectConfig {
175
- name: string
176
- type: ProjectType
177
- monorepo: boolean
178
- git: boolean
179
- install: boolean
180
- github: boolean
181
- githubOrg: string | null
182
- githubDescription: string
183
- // Public template configs
184
- theme: "dark" | "light" | "system"
185
- aiAssistant: string | null
186
- // M26: Número do contrato (fast only)
187
- contractNumber: string
188
- // M21-M23: Configs de infra
189
- resendApiKey: string
190
- resendFromEmail: string
191
- contactEmail: string
192
- // M30: Railway via CLI
193
- railwayProject: string
194
- railwayToken: string
195
- stagingUrl: string
196
- productionUrl: string
197
- // Template customizado
198
- customTemplate: string | null
199
- // nimbus-core: URL do repo do cliente
200
- clientRepoUrl?: string | null
201
- }
202
-
203
- interface CreateFlags {
204
- yes: boolean
205
- // Templates
206
- landing: boolean
207
- app: boolean
208
- turborepo: boolean
209
- // Templates internos nimbuslab
210
- fast: boolean
211
- fastPlus: boolean
212
- fastTurborepo: boolean
213
- core: boolean
214
- // Outros
215
- noGit: boolean
216
- noInstall: boolean
217
- railway: boolean
218
- template: string | null
219
- }
220
-
221
- function parseFlags(args: string[]): { flags: CreateFlags; projectName: string | undefined } {
222
- const flags: CreateFlags = {
223
- yes: false,
224
- landing: false,
225
- app: false,
226
- turborepo: false,
227
- fast: false,
228
- fastPlus: false,
229
- fastTurborepo: false,
230
- core: false,
231
- noGit: false,
232
- noInstall: false,
233
- railway: false,
234
- template: null,
235
- }
236
-
237
- let projectName: string | undefined
238
- let i = 0
239
-
240
- while (i < args.length) {
241
- const arg = args[i]!
242
-
243
- if (arg === "-y" || arg === "--yes") {
244
- flags.yes = true
245
- } else if (arg === "--landing") {
246
- flags.landing = true
247
- } else if (arg === "--app") {
248
- flags.app = true
249
- } else if (arg === "--turborepo") {
250
- flags.turborepo = true
251
- } else if (arg === "--fast") {
252
- flags.fast = true
253
- } else if (arg === "--fast-plus") {
254
- flags.fastPlus = true
255
- } else if (arg === "--fast-turborepo") {
256
- flags.fastTurborepo = true
257
- } else if (arg === "--core") {
258
- flags.core = true
259
- } else if (arg === "--no-git") {
260
- flags.noGit = true
261
- } else if (arg === "--no-install") {
262
- flags.noInstall = true
263
- } else if (arg === "--railway") {
264
- flags.railway = true
265
- } else if (arg === "--template") {
266
- i++
267
- flags.template = args[i] ?? null
268
- } else if (!arg.startsWith("-")) {
269
- projectName = arg
270
- }
271
-
272
- i++
273
- }
274
-
275
- return { flags, projectName }
276
- }
277
-
278
- // M30: Verificar e instalar Railway CLI
279
- async function ensureRailwayCli(): Promise<boolean> {
280
- const checkCmd = process.platform === "win32" ? "where" : "which"
281
- const hasRailway = await $`${checkCmd} railway`.quiet().then(() => true).catch(() => false)
282
-
283
- if (hasRailway) return true
284
-
285
- console.log(pc.yellow("Railway CLI not found. Installing..."))
286
- console.log()
287
-
288
- try {
289
- if (process.platform === "win32") {
290
- await $`powershell -c "iwr https://railway.app/install.ps1 -useb | iex"`.quiet()
291
- } else {
292
- await $`curl -fsSL https://railway.app/install.sh | sh`.quiet()
293
- }
294
- console.log(pc.green("Railway CLI installed successfully!"))
295
- return true
296
- } catch (error) {
297
- console.log(pc.red("Error installing Railway CLI."))
298
- console.log(pc.dim("Install manually: https://docs.railway.app/guides/cli"))
299
- return false
300
- }
301
- }
302
-
303
- // M30: Listar projetos Railway via CLI
304
- async function listRailwayProjects(): Promise<string[]> {
305
- try {
306
- const result = await $`railway list`.text()
307
- // Parse output: primeira linha é o team, demais são projetos
308
- const lines = result.trim().split("\n").filter(l => l.trim())
309
- // Remove primeira linha (team name) e extrai nomes dos projetos
310
- return lines.slice(1).map(l => l.trim())
311
- } catch {
312
- return []
313
- }
314
- }
315
-
316
- // M30: Verificar autenticacao Railway
317
- async function isRailwayAuthenticated(): Promise<boolean> {
318
- try {
319
- await $`railway whoami`.quiet()
320
- return true
321
- } catch {
322
- return false
323
- }
324
- }
325
-
326
- export async function create(args: string[]) {
327
- // Verifica dependencias (cross-platform)
328
- const checkCmd = process.platform === "win32" ? "where" : "which"
329
- const hasBun = await $`${checkCmd} bun`.quiet().then(() => true).catch(() => false)
330
- const hasGit = await $`${checkCmd} git`.quiet().then(() => true).catch(() => false)
331
- const hasGh = await $`${checkCmd} gh`.quiet().then(() => true).catch(() => false)
332
-
333
- if (!hasBun) {
334
- console.log(pc.red("Error: Bun not found."))
335
- console.log(pc.dim("Install from: https://bun.sh"))
336
- console.log()
337
- if (process.platform === "win32") {
338
- console.log(pc.cyan('powershell -c "irm bun.sh/install.ps1 | iex"'))
339
- } else {
340
- console.log(pc.cyan("curl -fsSL https://bun.sh/install | bash"))
341
- }
342
- console.log()
343
- process.exit(1)
344
- }
345
-
346
- if (!hasGit) {
347
- console.log(pc.red("Error: Git not found."))
348
- console.log(pc.dim("Install git to continue."))
349
- process.exit(1)
350
- }
351
-
352
- // GitHub CLI is optional for public templates
353
- // Will be checked later if user wants to create a repo
354
- if (!hasGh) {
355
- console.log(pc.dim(" GitHub CLI not found (repo creation will be skipped)"))
356
- console.log(pc.dim(" Install from: https://cli.github.com"))
357
- console.log()
358
- }
359
-
360
- // M30: Verificar/instalar Railway CLI
361
- const hasRailway = await ensureRailwayCli()
362
- if (hasRailway) {
363
- const railwayAuth = await isRailwayAuthenticated()
364
- if (!railwayAuth) {
365
- console.log(pc.yellow("Railway CLI not authenticated."))
366
- console.log(pc.dim("Run: railway login"))
367
- console.log()
368
- }
369
- }
370
-
371
- const { flags, projectName } = parseFlags(args)
372
-
373
- p.intro(pc.bgCyan(pc.black(" New nimbuslab Project ")))
374
-
375
- let config: ProjectConfig | symbol
376
-
377
- // Determina tipo baseado nas flags
378
- const hasTypeFlag = flags.landing || flags.app || flags.turborepo || flags.fast || flags.fastPlus || flags.fastTurborepo || flags.core
379
- const typeFromFlag: ProjectType | null = flags.landing ? "landing"
380
- : flags.app ? "app"
381
- : flags.turborepo ? "turborepo"
382
- : flags.fastTurborepo ? "fast+"
383
- : flags.fastPlus ? "fast+"
384
- : flags.fast ? "fast"
385
- : flags.core ? "nimbus-core"
386
- : null
387
- const monorepoFromFlag = flags.fastTurborepo
388
-
389
- // Modo automatico: -y com nome OU flags de tipo com nome
390
- if ((flags.yes || hasTypeFlag) && projectName) {
391
- // Templates públicos usam "landing" como default
392
- const defaultType: ProjectType = flags.landing || flags.app || flags.turborepo ? "landing" : "fast"
393
- config = {
394
- name: projectName,
395
- type: typeFromFlag || defaultType,
396
- monorepo: monorepoFromFlag,
397
- git: !flags.noGit,
398
- install: !flags.noInstall,
399
- github: false,
400
- githubOrg: null,
401
- githubDescription: "",
402
- theme: "dark" as const,
403
- aiAssistant: null,
404
- contractNumber: "",
405
- resendApiKey: "",
406
- resendFromEmail: "",
407
- contactEmail: "",
408
- railwayProject: "",
409
- railwayToken: "",
410
- stagingUrl: "",
411
- productionUrl: "",
412
- customTemplate: flags.template,
413
- }
414
-
415
- const typeLabel = flags.turborepo ? "fast+ (monorepo)" : config.type
416
- console.log(pc.dim(` Project: ${projectName}`))
417
- console.log(pc.dim(` Type: ${typeLabel}`))
418
- console.log(pc.dim(` Git: ${config.git ? "yes" : "no"}`))
419
- console.log(pc.dim(` Install: ${config.install ? "yes" : "no"}`))
420
- if (flags.railway) console.log(pc.dim(` Railway: configurar`))
421
- if (flags.template) console.log(pc.dim(` Template: ${flags.template}`))
422
- console.log()
423
-
424
- // Railway automatico se flag --railway
425
- if (flags.railway) {
426
- const railwayAuthenticated = await isRailwayAuthenticated()
427
- if (railwayAuthenticated) {
428
- if (config.type === "fast") {
429
- const projects = await listRailwayProjects()
430
- const fastProject = projects.find(p => p.toLowerCase().includes("fast by nimbuslab"))
431
- if (fastProject) {
432
- config.railwayProject = fastProject
433
- console.log(pc.green(` Railway: ${fastProject}`))
434
- }
435
- } else {
436
- // Fast+: cria projeto novo automaticamente
437
- console.log(pc.dim(` Creating project Railway: ${projectName}...`))
438
- try {
439
- const result = await $`echo "" | railway init -n ${projectName} -w nimbuslab --json`.text()
440
- const newProject = JSON.parse(result)
441
- config.railwayProject = newProject.name || projectName
442
- console.log(pc.green(` Railway: ${config.railwayProject} criado`))
443
- } catch {
444
- console.log(pc.yellow(` Railway: erro ao criar projeto`))
445
- }
446
- }
447
- } else {
448
- console.log(pc.yellow(` Railway: not authenticated (railway login)`))
449
- }
450
- console.log()
451
- }
452
- } else {
453
- config = await promptConfig(projectName, flags)
454
- }
455
-
456
- if (p.isCancel(config)) {
457
- p.cancel("Operation cancelled")
458
- process.exit(0)
459
- }
460
-
461
- await createProject(config as ProjectConfig)
462
-
463
- p.outro(pc.green("Project created successfully!"))
464
-
465
- showNextSteps(config as ProjectConfig)
466
- }
467
-
468
- async function promptConfig(initialName?: string, flags?: CreateFlags): Promise<ProjectConfig | symbol> {
469
- // Detectar usuario git e se é membro da nimbuslab
470
- const { isMember, user } = await isNimbuslabMember()
471
-
472
- // Saudacao
473
- const greeting = user ? `Hello, ${user}!` : "Hello!"
474
- console.log(pc.dim(` ${greeting}`))
475
- console.log()
476
-
477
- const name = await p.text({
478
- message: "Project name:",
479
- placeholder: "meu-projeto",
480
- initialValue: initialName,
481
- validate: (value) => {
482
- if (!value) return "Name is required"
483
- if (!/^[a-z0-9-]+$/.test(value)) {
484
- return "Use only lowercase letters, numbers and hyphens"
485
- }
486
- return undefined
487
- },
488
- })
489
-
490
- if (p.isCancel(name)) return name
491
-
492
- // Opções públicas (todos veem)
493
- const publicOptions = [
494
- {
495
- value: "landing",
496
- label: "Landing Page",
497
- hint: "Next.js 16 + Tailwind 4 + shadcn",
498
- },
499
- {
500
- value: "app",
501
- label: "Web App",
502
- hint: "Landing + Better Auth + Drizzle",
503
- },
504
- {
505
- value: "turborepo",
506
- label: "Monorepo",
507
- hint: "Turborepo + apps/packages",
508
- },
509
- ]
510
-
511
- // Opções privadas (só membros nimbuslab)
512
- const privateOptions = isMember ? [
513
- {
514
- value: "fast",
515
- label: "fast",
516
- hint: "Landing page fast methodology",
517
- },
518
- {
519
- value: "fast+",
520
- label: "fast+",
521
- hint: "Complete SaaS",
522
- },
523
- {
524
- value: "nimbus-core",
525
- label: "nimbus-core",
526
- hint: "External projects (stealth mode)",
527
- },
528
- ] : []
529
-
530
- const type = await p.select({
531
- message: "Project type:",
532
- options: [...publicOptions, ...privateOptions],
533
- })
534
-
535
- if (p.isCancel(type)) return type
536
-
537
- // Verifica se é template público (não precisa de auth nimbuslab)
538
- const isPublicTemplate = ["landing", "app", "turborepo"].includes(type as string)
539
-
540
- // Se não é membro e tentou usar template privado (via flag), bloqueia
541
- if (!isMember && !isPublicTemplate) {
542
- console.log(pc.red("Error: Template available only for nimbuslab members"))
543
- process.exit(1)
544
- }
545
-
546
- // Fluxo especial para nimbus-core (projetos externos)
547
- if (type === "nimbus-core") {
548
- console.log()
549
- console.log(pc.dim(" nimbus-core: Motor para projetos externos (stealth mode)"))
550
- console.log()
551
-
552
- // Perguntar se quer criar repo (GitHub ou nenhum)
553
- const repoOption = await p.select({
554
- message: "Create repository for this project?",
555
- options: [
556
- { value: "github", label: "GitHub (nimbuslab, private)", hint: "recommended" },
557
- { value: "none", label: "No repository", hint: "just local" },
558
- ],
559
- })
560
- if (p.isCancel(repoOption)) return repoOption
561
-
562
- // Perguntar URL do repo do cliente para clonar no workspace
563
- const clientRepo = await p.text({
564
- message: "Client repo URL (to clone in workspace):",
565
- placeholder: "git@github.com:client/repo.git ou Azure DevOps URL",
566
- })
567
- if (p.isCancel(clientRepo)) return clientRepo
568
-
569
- return {
570
- name: name as string,
571
- type: "nimbus-core" as ProjectType,
572
- monorepo: false,
573
- git: true, // sempre init git
574
- install: false, // nimbus-core não tem package.json
575
- github: repoOption === "github",
576
- githubOrg: "nimbuslab", // sempre na org nimbuslab
577
- githubDescription: `nimbus-core for ${name} - external project`,
578
- theme: "dark" as const,
579
- aiAssistant: null,
580
- contractNumber: "",
581
- resendApiKey: "",
582
- resendFromEmail: "",
583
- contactEmail: "",
584
- railwayProject: "",
585
- railwayToken: "",
586
- stagingUrl: "",
587
- productionUrl: "",
588
- customTemplate: null, // não usa template customizado
589
- clientRepoUrl: (clientRepo as string) || null, // URL do cliente separada
590
- }
591
- }
592
-
593
- // Pergunta sobre monorepo apenas para fast+
594
- let monorepo = false
595
- if (type === "fast+") {
596
- const useMonorepo = await p.confirm({
597
- message: "Use monorepo (Turborepo)?",
598
- initialValue: false,
599
- })
600
- if (p.isCancel(useMonorepo)) return useMonorepo
601
- monorepo = useMonorepo as boolean
602
- }
603
-
604
- const git = await p.confirm({
605
- message: "Initialize Git repository?",
606
- initialValue: true,
607
- })
608
-
609
- if (p.isCancel(git)) return git
610
-
611
- // GitHub options (only if git is enabled)
612
- let github = false
613
- let githubOrg: string | null = null
614
- let githubDescription = ""
615
-
616
- if (git) {
617
- const createGithub = await p.confirm({
618
- message: "Create GitHub repository?",
619
- initialValue: false,
620
- })
621
-
622
- if (p.isCancel(createGithub)) return createGithub
623
-
624
- github = createGithub as boolean
625
-
626
- if (github) {
627
- // M13: Selecao de organizacao GitHub
628
- const org = await p.select({
629
- message: "GitHub organization:",
630
- options: [
631
- { value: "nimbuslab", label: "nimbuslab", hint: "Main org" },
632
- { value: "fast-by-nimbuslab", label: "fast-by-nimbuslab", hint: "Client projects" },
633
- { value: "nimbuslab-templates", label: "nimbuslab-templates", hint: "Templates" },
634
- { value: null, label: "Pessoal", hint: "Personal" },
635
- ],
636
- })
637
-
638
- if (p.isCancel(org)) return org
639
- githubOrg = org as string | null
640
-
641
- // M14: Descricao do repo
642
- const description = await p.text({
643
- message: "Repository description:",
644
- placeholder: "Landing page para cliente X",
645
- initialValue: type === "fast" ? "Landing page fast by nimbuslab" : "SaaS fast+ by nimbuslab",
646
- })
647
-
648
- if (p.isCancel(description)) return description
649
- githubDescription = description as string
650
- }
651
- }
652
-
653
- // M26: Número do contrato (apenas para fast - privado)
654
- let contractNumber = ""
655
- if (type === "fast") {
656
- const contract = await p.text({
657
- message: "Contract number (ex: 001):",
658
- placeholder: "001",
659
- validate: (v) => v ? undefined : "Contract number is required for fast",
660
- })
661
- if (p.isCancel(contract)) return contract
662
- contractNumber = contract as string
663
- }
664
-
665
- // Templates públicos têm fluxo simplificado mas com configs globais
666
- if (isPublicTemplate) {
667
- // Theme selection
668
- const theme = await p.select({
669
- message: "Default theme:",
670
- options: [
671
- { value: "dark", label: "Dark", hint: "recommended" },
672
- { value: "light", label: "Light" },
673
- { value: "system", label: "System", hint: "follows OS preference" },
674
- ],
675
- })
676
- if (p.isCancel(theme)) return theme
677
-
678
- // AI Assistant selection
679
- const aiAssistant = await p.select({
680
- message: "Which AI assistant do you use?",
681
- options: [
682
- { value: "claude", label: "Claude Code", hint: "Anthropic" },
683
- { value: "cursor", label: "Cursor", hint: "AI-first editor" },
684
- { value: "gemini", label: "Gemini CLI", hint: "Google" },
685
- { value: "copilot", label: "GitHub Copilot" },
686
- { value: "windsurf", label: "Windsurf", hint: "Codeium" },
687
- { value: "none", label: "None", hint: "skip AI config" },
688
- ],
689
- })
690
- if (p.isCancel(aiAssistant)) return aiAssistant
691
-
692
- // GitHub repo for public templates (uses user's orgs)
693
- let publicGithub = false
694
- let publicGithubOrg: string | null = null
695
-
696
- if (git) {
697
- const gh = await checkGitHubCli()
698
-
699
- if (gh.installed && gh.authenticated) {
700
- const createRepo = await p.confirm({
701
- message: "Create GitHub repository?",
702
- initialValue: false,
703
- })
704
- if (p.isCancel(createRepo)) return createRepo
705
-
706
- publicGithub = createRepo as boolean
707
-
708
- if (publicGithub) {
709
- const repoOptions: { value: string | null; label: string; hint?: string }[] = [
710
- { value: gh.username, label: gh.username!, hint: "personal account" },
711
- ...gh.orgs.map((org) => ({ value: org, label: org })),
712
- ]
713
-
714
- const repoOwner = await p.select({
715
- message: "Where to create the repository?",
716
- options: repoOptions,
717
- })
718
- if (p.isCancel(repoOwner)) return repoOwner
719
- publicGithubOrg = repoOwner as string | null
720
-
721
- const repoVisibility = await p.select({
722
- message: "Repository visibility:",
723
- options: [
724
- { value: "private", label: "Private", hint: "recommended" },
725
- { value: "public", label: "Public" },
726
- ],
727
- })
728
- if (p.isCancel(repoVisibility)) return repoVisibility
729
- githubDescription = repoVisibility as string // reusing for visibility
730
- }
731
- }
732
- }
733
-
734
- const install = await p.confirm({
735
- message: "Install dependencies?",
736
- initialValue: true,
737
- })
738
-
739
- if (p.isCancel(install)) return install
740
-
741
- return {
742
- name: name as string,
743
- type: type as ProjectType,
744
- monorepo: false,
745
- git: git as boolean,
746
- install: install as boolean,
747
- github: publicGithub,
748
- githubOrg: publicGithubOrg,
749
- githubDescription,
750
- theme: theme as "dark" | "light" | "system",
751
- aiAssistant: aiAssistant === "none" ? null : aiAssistant as string,
752
- contractNumber: "",
753
- resendApiKey: "",
754
- resendFromEmail: "",
755
- contactEmail: "",
756
- railwayProject: "",
757
- railwayToken: "",
758
- stagingUrl: "",
759
- productionUrl: "",
760
- customTemplate: flags?.template || null,
761
- }
762
- }
763
-
764
- // Configuracoes de infra (separadas)
765
- let resendApiKey = ""
766
- let resendFromEmail = ""
767
- let contactEmail = ""
768
- let railwayProject = ""
769
- let railwayToken = ""
770
- let stagingUrl = ""
771
- let productionUrl = ""
772
-
773
- const currentYear = new Date().getFullYear().toString().slice(-3) // 025, 026, etc
774
-
775
- // Defaults
776
- const defaultStagingUrl = type === "fast"
777
- ? `https://fast-${contractNumber}-${currentYear}.nimbuslab.net.br`
778
- : `https://${name}.nimbuslab.net.br`
779
- const defaultFromEmail = "no-reply@nimbuslab.com.br"
780
- const defaultContactEmail = type === "fast"
781
- ? "fast@nimbuslab.com.br"
782
- : "suporte@nimbuslab.com.br"
783
-
784
- // Menu de configuracoes opcionais
785
- const infraOptions = await p.multiselect({
786
- message: "What do you want to configure now?",
787
- options: [
788
- { value: "urls", label: "URLs", hint: "staging and production" },
789
- { value: "resend", label: "Resend", hint: "form emails" },
790
- { value: "railway", label: "Railway", hint: "deploy and hosting" },
791
- ],
792
- required: false,
793
- })
794
-
795
- if (p.isCancel(infraOptions)) return infraOptions
796
-
797
- const configItems = infraOptions as string[]
798
-
799
- // URLs
800
- if (configItems.includes("urls")) {
801
- console.log()
802
- console.log(pc.dim(" Project URLs"))
803
-
804
- const staging = await p.text({
805
- message: "Staging URL:",
806
- placeholder: defaultStagingUrl,
807
- initialValue: defaultStagingUrl,
808
- })
809
- if (p.isCancel(staging)) return staging
810
- stagingUrl = staging as string
811
-
812
- const production = await p.text({
813
- message: "Production URL:",
814
- placeholder: defaultStagingUrl.replace('.nimbuslab.net.br', '.com.br'),
815
- initialValue: "",
816
- })
817
- if (p.isCancel(production)) return production
818
- productionUrl = production as string
819
- }
820
-
821
- // Resend
822
- if (configItems.includes("resend")) {
823
- console.log()
824
- console.log(pc.dim(" Resend (Email)"))
825
-
826
- const resendKey = await p.text({
827
- message: "RESEND_API_KEY:",
828
- placeholder: "re_xxxxxxxxxxxx",
829
- })
830
- if (p.isCancel(resendKey)) return resendKey
831
- resendApiKey = resendKey as string
832
-
833
- const fromEmail = await p.text({
834
- message: "From email:",
835
- placeholder: defaultFromEmail,
836
- initialValue: defaultFromEmail,
837
- })
838
- if (p.isCancel(fromEmail)) return fromEmail
839
- resendFromEmail = fromEmail as string
840
-
841
- const contact = await p.text({
842
- message: "Contact email (receives forms):",
843
- placeholder: defaultContactEmail,
844
- initialValue: defaultContactEmail,
845
- })
846
- if (p.isCancel(contact)) return contact
847
- contactEmail = contact as string
848
- }
849
-
850
- // Railway
851
- if (configItems.includes("railway")) {
852
- // M30: Railway via CLI
853
- const railwayAuthenticated = await isRailwayAuthenticated()
854
-
855
- if (railwayAuthenticated) {
856
- console.log()
857
- console.log(pc.dim(" Railway"))
858
-
859
- const projects = await listRailwayProjects()
860
-
861
- if (type === "fast") {
862
- // Fast usa projeto compartilhado "Fast by nimbuslab"
863
- const fastProject = projects.find(p => p.toLowerCase().includes("fast by nimbuslab"))
864
- if (fastProject) {
865
- railwayProject = fastProject
866
- console.log(pc.green(` Project: ${fastProject} (automatico)`))
867
- } else {
868
- console.log(pc.yellow(" Project 'Fast by nimbuslab' not found."))
869
- console.log(pc.dim(" Configure manually in .env"))
870
- }
871
- } else {
872
- // Fast+ pode escolher projeto existente ou criar novo
873
- const projectOptions = [
874
- ...projects.map(proj => ({ value: proj, label: proj })),
875
- { value: "__new__", label: "Create new project", hint: "via railway init" },
876
- { value: "__skip__", label: "Skip", hint: "Configure later" },
877
- ]
878
-
879
- const selectedProject = await p.select({
880
- message: "Railway project for this SaaS:",
881
- options: projectOptions,
882
- })
883
-
884
- if (p.isCancel(selectedProject)) return selectedProject
885
-
886
- if (selectedProject === "__new__") {
887
- // Criar projeto via Railway CLI
888
- const projectNameForRailway = name as string
889
- console.log(pc.dim(` Creating project "${projectNameForRailway}" on Railway...`))
890
- try {
891
- // Usa echo para passar input vazio e aceitar defaults (workspace)
892
- const result = await $`echo "" | railway init -n ${projectNameForRailway} -w nimbuslab --json`.text()
893
- const newProject = JSON.parse(result)
894
- railwayProject = newProject.name || projectNameForRailway
895
- console.log(pc.green(` Projeto "${railwayProject}" created successfully!`))
896
- console.log(pc.dim(` ID: ${newProject.id || "N/A"}`))
897
- } catch (error) {
898
- console.log(pc.yellow(" Error creating project via CLI."))
899
- console.log(pc.dim(" Create manually at: https://railway.app/new"))
900
- }
901
- } else if (selectedProject !== "__skip__") {
902
- railwayProject = selectedProject as string
903
- console.log(pc.green(` Project selected: ${railwayProject}`))
904
- }
905
- }
906
- } else {
907
- console.log()
908
- console.log(pc.yellow(" Railway: not authenticated (railway login)"))
909
- console.log(pc.dim(" Configure manually in .env"))
910
- }
911
- }
912
-
913
- const install = await p.confirm({
914
- message: "Install dependencies?",
915
- initialValue: true,
916
- })
917
-
918
- if (p.isCancel(install)) return install
919
-
920
- return {
921
- name: name as string,
922
- type: type as "fast" | "fast+",
923
- monorepo,
924
- git: git as boolean,
925
- install: install as boolean,
926
- github,
927
- githubOrg,
928
- githubDescription,
929
- theme: "dark" as const,
930
- aiAssistant: null,
931
- contractNumber,
932
- resendApiKey,
933
- resendFromEmail,
934
- contactEmail,
935
- railwayProject,
936
- railwayToken,
937
- stagingUrl,
938
- productionUrl,
939
- customTemplate: flags?.template || null,
940
- }
941
- }
942
-
943
- async function createProject(config: ProjectConfig) {
944
- const s = p.spinner()
945
-
946
- // Determina template: customizado ou padrao
947
- let templateRepo: string
948
- let templateLabel: string
949
-
950
- if (config.customTemplate) {
951
- templateRepo = config.customTemplate
952
- templateLabel = `customizado (${config.customTemplate})`
953
- } else {
954
- // fast+ com monorepo usa template específico
955
- const templateKey = config.type === "fast+" && config.monorepo ? "fast+-monorepo" : config.type
956
- templateRepo = TEMPLATES[templateKey as keyof typeof TEMPLATES]
957
- templateLabel = config.monorepo ? `${config.type} (monorepo)` : config.type
958
- }
959
-
960
- // Verifica se é template público
961
- const isPublicTemplate = ["landing", "app", "turborepo"].includes(config.type)
962
-
963
- s.start(`Cloning template ${templateLabel}...`)
964
- try {
965
- if (isPublicTemplate) {
966
- // Templates públicos: usa HTTPS (não precisa de auth)
967
- const httpsUrl = `https://github.com/${templateRepo}.git`
968
- await $`git clone --depth 1 ${httpsUrl} ${config.name}`.quiet()
969
- } else {
970
- // Templates privados: usa gh (precisa de auth)
971
- await $`gh repo clone ${templateRepo} ${config.name} -- --depth 1`.quiet()
972
- }
973
- await rm(join(config.name, ".git"), { recursive: true, force: true })
974
- s.stop(`Template cloned`)
975
- } catch (error) {
976
- s.stop("Error cloning template")
977
- throw new Error(`Failed to clone template ${templateRepo}. Check your connection or repository access.`)
978
- }
979
-
980
- // nimbus-core: clone client repo in workspace if provided
981
- if (config.type === "nimbus-core" && config.clientRepoUrl) {
982
- const clientRepoUrl = config.clientRepoUrl
983
- s.start(`Cloning client repo in workspace...`)
984
- try {
985
- // Extrair nome do projeto da URL (funciona com GitHub, GitLab, Azure DevOps)
986
- let projectName = "client-project"
987
- if (clientRepoUrl.includes("visualstudio.com") || clientRepoUrl.includes("dev.azure.com")) {
988
- // Azure DevOps: serel@vs-ssh.visualstudio.com:v3/serel/Project/Repo
989
- const parts = clientRepoUrl.split("/")
990
- projectName = parts[parts.length - 1] || "client-project"
991
- } else {
992
- // GitHub/GitLab: git@github.com:org/repo.git
993
- projectName = clientRepoUrl.split("/").pop()?.replace(".git", "") || "client-project"
994
- }
995
- await $`git clone ${clientRepoUrl} ${config.name}/workspace/${projectName}`.quiet()
996
- s.stop(`Client repo cloned: workspace/${projectName}`)
997
- } catch (error) {
998
- s.stop("Error cloning client repo")
999
- console.log(pc.dim(" You can clone manually: cd workspace && git clone <url>"))
1000
- }
1001
- }
1002
-
1003
- // Update package.json (skip for nimbus-core)
1004
- if (config.type !== "nimbus-core") {
1005
- s.start("Configuring project...")
1006
- try {
1007
- const pkgPath = `${config.name}/package.json`
1008
- const pkg = await Bun.file(pkgPath).json()
1009
- pkg.name = config.name
1010
- await Bun.write(pkgPath, JSON.stringify(pkg, null, 2))
1011
- s.stop("Project configured")
1012
- } catch (error) {
1013
- s.stop("Error configuring")
1014
- // Continue anyway
1015
- }
1016
- }
1017
-
1018
- // Apply theme config (public templates only)
1019
- if (isPublicTemplate && config.theme) {
1020
- s.start(`Setting theme to ${config.theme}...`)
1021
- try {
1022
- const layoutPath = `${config.name}/src/app/layout.tsx`
1023
- let layout = await Bun.file(layoutPath).text()
1024
- layout = layout.replace(
1025
- /defaultTheme="(dark|light|system)"/,
1026
- `defaultTheme="${config.theme}"`
1027
- )
1028
- await Bun.write(layoutPath, layout)
1029
- s.stop(`Theme set to ${config.theme}`)
1030
- } catch {
1031
- s.stop("Theme config skipped")
1032
- }
1033
- }
1034
-
1035
- // Generate AI config (public templates only)
1036
- if (isPublicTemplate && config.aiAssistant) {
1037
- const aiConfig = AI_CONFIGS[config.aiAssistant]
1038
- if (aiConfig) {
1039
- s.start(`Generating ${config.aiAssistant} config...`)
1040
- try {
1041
- const content = aiConfig.content(config.type)
1042
- const filePath = `${config.name}/${aiConfig.filename}`
1043
-
1044
- // Create directory if needed
1045
- if (aiConfig.filename.includes("/")) {
1046
- const dir = aiConfig.filename.split("/").slice(0, -1).join("/")
1047
- await mkdir(`${config.name}/${dir}`, { recursive: true })
1048
- }
1049
-
1050
- await Bun.write(filePath, content)
1051
- s.stop(`${aiConfig.filename} created`)
1052
- } catch {
1053
- s.stop("AI config skipped")
1054
- }
1055
- }
1056
- }
1057
-
1058
- // Setup fast+ if selected
1059
- if (config.type === "fast+") {
1060
- s.start("Configurando fast+ (SaaS)...")
1061
- // O setup real será feito pelo bun setup do próprio template
1062
- s.stop("Configuracao fast+ preparada")
1063
- }
1064
-
1065
- // Git init with full branch flow: main → staging → develop
1066
- if (config.git) {
1067
- s.start("Initializing Git...")
1068
- try {
1069
- const cwd = config.name
1070
- // Init with main branch (not master)
1071
- await $`git init -b main`.cwd(cwd).quiet()
1072
- await $`git add -A`.cwd(cwd).quiet()
1073
- await $`git commit -m "chore: setup inicial via nimbus create"`.cwd(cwd).quiet()
1074
-
1075
- // Create branch flow: main → staging → develop
1076
- await $`git checkout -b staging`.cwd(cwd).quiet()
1077
- await $`git checkout -b develop`.cwd(cwd).quiet()
1078
-
1079
- s.stop("Git initialized (main -> staging -> develop)")
1080
- } catch (error) {
1081
- s.stop("Error initializing Git")
1082
- }
1083
-
1084
- // Create GitHub repo if requested (M13 + M14)
1085
- if (config.github) {
1086
- s.start("Creating GitHub repository...")
1087
- try {
1088
- const cwd = config.name
1089
- const repoName = config.githubOrg
1090
- ? `${config.githubOrg}/${config.name}`
1091
- : config.name
1092
-
1093
- // For public templates, githubDescription contains visibility (private/public)
1094
- // For private templates, use org-based visibility
1095
- // nimbus-core is ALWAYS private
1096
- let visibility: string
1097
- if (config.type === "nimbus-core") {
1098
- visibility = "--private" // nimbus-core sempre privado
1099
- } else if (isPublicTemplate) {
1100
- visibility = config.githubDescription === "public" ? "--public" : "--private"
1101
- } else {
1102
- visibility = config.githubOrg === "fast-by-nimbuslab" ? "--private" : "--public"
1103
- }
1104
-
1105
- // Create repo
1106
- if (isPublicTemplate) {
1107
- await $`gh repo create ${repoName} ${visibility} --source . --remote origin`.cwd(cwd).quiet()
1108
- } else {
1109
- await $`gh repo create ${repoName} ${visibility} --description ${config.githubDescription} --source . --remote origin`.cwd(cwd).quiet()
1110
- }
1111
-
1112
- // Push todas as branches na ordem correta: main -> staging -> develop
1113
- await $`git checkout main`.cwd(cwd).quiet()
1114
- await $`git push -u origin main`.cwd(cwd).quiet()
1115
- await $`git checkout staging`.cwd(cwd).quiet()
1116
- await $`git push -u origin staging`.cwd(cwd).quiet()
1117
- await $`git checkout develop`.cwd(cwd).quiet()
1118
- await $`git push -u origin develop`.cwd(cwd).quiet()
1119
-
1120
- s.stop(`GitHub: ${repoName}`)
1121
- } catch (error) {
1122
- s.stop("Error creating GitHub repository")
1123
- console.log(pc.dim(" You can create manually with: gh repo create"))
1124
- }
1125
- }
1126
- }
1127
-
1128
- // Railway link (se projeto foi selecionado/criado)
1129
- if (config.railwayProject) {
1130
- s.start(`Linkando Railway: ${config.railwayProject}...`)
1131
- try {
1132
- await $`railway link -p ${config.railwayProject}`.cwd(config.name).quiet()
1133
- s.stop(`Railway linkado: ${config.railwayProject}`)
1134
- } catch (error) {
1135
- s.stop("Error linking Railway")
1136
- console.log(pc.dim(" Run manually: railway link"))
1137
- }
1138
- }
1139
-
1140
- // M23: Gerar .env se configs foram fornecidas
1141
- if (config.resendApiKey || config.stagingUrl) {
1142
- s.start("Gerando arquivo .env...")
1143
- try {
1144
- const envContent = generateEnvFile(config)
1145
- await Bun.write(`${config.name}/.env`, envContent)
1146
- s.stop("Arquivo .env criado")
1147
- } catch (error) {
1148
- s.stop("Error creating .env")
1149
- }
1150
- }
1151
-
1152
- // Install deps
1153
- if (config.install) {
1154
- s.start("Installing dependencies (pode demorar)...")
1155
- try {
1156
- await $`bun install`.cwd(config.name).quiet()
1157
- s.stop("Dependencies installed")
1158
- } catch (error) {
1159
- s.stop("Error installing dependencies")
1160
- }
1161
- }
1162
- }
1163
-
1164
- // M23: Gerar conteudo do .env
1165
- function generateEnvFile(config: ProjectConfig): string {
1166
- const lines = [
1167
- "# Gerado automaticamente pelo nimbus-cli",
1168
- "# Nao commitar este arquivo!",
1169
- "",
1170
- "# App",
1171
- `NODE_ENV=development`,
1172
- "",
1173
- "# URLs",
1174
- `NEXT_PUBLIC_APP_URL=${config.productionUrl || "http://localhost:3000"}`,
1175
- `STAGING_URL=${config.stagingUrl || ""}`,
1176
- `PRODUCTION_URL=${config.productionUrl || ""}`,
1177
- "",
1178
- "# Resend (Email)",
1179
- `RESEND_API_KEY=${config.resendApiKey || ""}`,
1180
- `RESEND_FROM_EMAIL=${config.resendFromEmail || ""}`,
1181
- `CONTACT_EMAIL=${config.contactEmail || ""}`,
1182
- ]
1183
-
1184
- // Railway (projeto e token)
1185
- if (config.railwayProject || config.railwayToken) {
1186
- lines.push("")
1187
- lines.push("# Railway")
1188
- if (config.railwayProject) {
1189
- lines.push(`# Project: ${config.railwayProject}`)
1190
- }
1191
- lines.push(`RAILWAY_TOKEN=${config.railwayToken || "# Configure com: railway link"}`)
1192
- }
1193
-
1194
- // Fast+ specific vars
1195
- if (config.type === "fast+") {
1196
- lines.push("")
1197
- lines.push("# Database (fast+)")
1198
- lines.push("DATABASE_URL=postgresql://postgres:postgres@localhost:5432/app")
1199
- lines.push("")
1200
- lines.push("# Auth (fast+)")
1201
- lines.push("BETTER_AUTH_SECRET=")
1202
- lines.push("BETTER_AUTH_URL=http://localhost:3000")
1203
- }
1204
-
1205
- return lines.join("\n") + "\n"
1206
- }
1207
-
1208
- function showNextSteps(config: ProjectConfig) {
1209
- const isPublicTemplate = ["landing", "app", "turborepo"].includes(config.type)
1210
- const needsSetup = config.type === "app" // app needs bun setup for DB
1211
-
1212
- console.log()
1213
- console.log(pc.bold("Next steps:"))
1214
- console.log()
1215
- console.log(` ${pc.cyan("cd")} ${config.name}`)
1216
-
1217
- // nimbus-core tem fluxo especial
1218
- if (config.type === "nimbus-core") {
1219
- console.log()
1220
- console.log(pc.dim(" nimbus-core: Motor para projetos externos"))
1221
- console.log()
1222
- console.log(pc.dim(" Para usar a Lola:"))
1223
- console.log(` ${pc.cyan("lola")}`)
1224
- console.log()
1225
- console.log(pc.yellow(" STEALTH MODE: Commits sem mencao a nimbuslab/Lola/IA"))
1226
- console.log()
1227
-
1228
- // GitHub info
1229
- if (config.github) {
1230
- const repoUrl = `https://github.com/nimbuslab/${config.name}`
1231
- console.log(pc.green(` GitHub (private): ${repoUrl}`))
1232
- console.log()
1233
- }
1234
-
1235
- console.log(pc.dim(" Docs: See README.md for full instructions"))
1236
- console.log()
1237
- return
1238
- }
1239
-
1240
- if (!config.install) {
1241
- console.log(` ${pc.cyan("bun")} install`)
1242
- }
1243
-
1244
- // Templates que precisam de setup adicional
1245
- if (!isPublicTemplate || needsSetup) {
1246
- console.log(` ${pc.cyan("bun")} setup`)
1247
- }
1248
-
1249
- console.log(` ${pc.cyan("bun")} dev`)
1250
- console.log()
1251
-
1252
- // Explicar o que o bun setup faz para o app
1253
- if (needsSetup && isPublicTemplate) {
1254
- console.log(pc.dim(" bun setup will:"))
1255
- console.log(pc.dim(" - Start PostgreSQL with Docker"))
1256
- console.log(pc.dim(" - Run database migrations"))
1257
- console.log(pc.dim(" - Create demo user (demo@example.com / demo1234)"))
1258
- console.log()
1259
- }
1260
-
1261
- // Git flow info
1262
- if (config.git) {
1263
- console.log(pc.dim(" Git: main -> staging -> develop (current branch)"))
1264
-
1265
- // GitHub info
1266
- if (config.github) {
1267
- const repoUrl = config.githubOrg
1268
- ? `https://github.com/${config.githubOrg}/${config.name}`
1269
- : `https://github.com/${config.name}`
1270
- console.log(pc.green(` GitHub: ${repoUrl}`))
1271
- }
1272
- console.log()
1273
- }
1274
-
1275
- // Theme e AI info
1276
- if (isPublicTemplate) {
1277
- if (config.theme !== "dark") {
1278
- console.log(pc.dim(` Theme: ${config.theme}`))
1279
- }
1280
- if (config.aiAssistant) {
1281
- const aiConfig = AI_CONFIGS[config.aiAssistant]
1282
- if (aiConfig) {
1283
- console.log(pc.dim(` AI config: ${aiConfig.filename}`))
1284
- }
1285
- }
1286
- if (config.theme !== "dark" || config.aiAssistant) {
1287
- console.log()
1288
- }
1289
- }
1290
-
1291
- if (config.type === "fast+") {
1292
- console.log(pc.dim(" bun setup will:"))
1293
- console.log(pc.dim(" - Start PostgreSQL with Docker"))
1294
- console.log(pc.dim(" - Run database migrations"))
1295
- console.log(pc.dim(" - Create demo user (demo@example.com / demo1234)"))
1296
- console.log()
1297
- console.log(pc.dim(" Tip: Configure DATABASE_URL and BETTER_AUTH_SECRET in .env"))
1298
- if (!config.railwayToken) {
1299
- console.log(pc.dim(" Railway: Create a project at https://railway.app/new"))
1300
- }
1301
- console.log()
1302
- }
1303
-
1304
- // Info sobre .env (templates privados)
1305
- if (!isPublicTemplate) {
1306
- if (config.resendApiKey || config.stagingUrl) {
1307
- console.log(pc.green(" .env configured!"))
1308
- console.log()
1309
- } else {
1310
- console.log(pc.yellow(" Tip: Configure .env manually or use 'bun setup'."))
1311
- console.log()
1312
- }
1313
- }
1314
-
1315
- // Info sobre templates
1316
- if (isPublicTemplate) {
1317
- console.log(pc.dim(" Open source template (MIT) by nimbuslab"))
1318
- console.log(pc.dim(` https://github.com/nimbuslab/create-next-${config.type === "turborepo" ? "turborepo" : config.type}`))
1319
- } else {
1320
- console.log(pc.dim(" https://github.com/nimbuslab-templates"))
1321
- }
1322
- console.log()
1323
- }