@nimbuslab/cli 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -846,17 +846,37 @@ var import_picocolors3 = __toESM(require_picocolors(), 1);
846
846
  var {$: $2 } = globalThis.Bun;
847
847
  import { rm } from "fs/promises";
848
848
  import { join } from "path";
849
- var TEMPLATES = {
849
+ var PRIVATE_TEMPLATES = {
850
850
  fast: "nimbuslab-templates/fast-template",
851
851
  "fast+": "nimbuslab-templates/fastplus-template",
852
852
  "fast+-monorepo": "nimbuslab-templates/fastplus-monorepo-template"
853
853
  };
854
+ var PUBLIC_TEMPLATES = {
855
+ landing: "nimbuslab/create-next-landing",
856
+ app: "nimbuslab/create-next-app",
857
+ turborepo: "nimbuslab/create-turborepo"
858
+ };
859
+ var TEMPLATES = { ...PRIVATE_TEMPLATES, ...PUBLIC_TEMPLATES };
860
+ var NIMBUSLAB_DOMAINS = ["@nimbuslab.com.br", "@nimbuslab.net.br"];
861
+ async function isNimbuslabMember() {
862
+ try {
863
+ const user = (await $2`git config user.name`.text()).trim();
864
+ const email = (await $2`git config user.email`.text()).trim();
865
+ const isMember = NIMBUSLAB_DOMAINS.some((domain) => email.endsWith(domain));
866
+ return { isMember, user, email };
867
+ } catch {
868
+ return { isMember: false, user: null, email: null };
869
+ }
870
+ }
854
871
  function parseFlags(args) {
855
872
  const flags = {
856
873
  yes: false,
874
+ landing: false,
875
+ app: false,
876
+ turborepo: false,
857
877
  fast: false,
858
878
  fastPlus: false,
859
- turborepo: false,
879
+ fastTurborepo: false,
860
880
  noGit: false,
861
881
  noInstall: false,
862
882
  railway: false,
@@ -868,12 +888,18 @@ function parseFlags(args) {
868
888
  const arg = args[i];
869
889
  if (arg === "-y" || arg === "--yes") {
870
890
  flags.yes = true;
891
+ } else if (arg === "--landing") {
892
+ flags.landing = true;
893
+ } else if (arg === "--app") {
894
+ flags.app = true;
895
+ } else if (arg === "--turborepo") {
896
+ flags.turborepo = true;
871
897
  } else if (arg === "--fast") {
872
898
  flags.fast = true;
873
899
  } else if (arg === "--fast-plus") {
874
900
  flags.fastPlus = true;
875
- } else if (arg === "--turborepo") {
876
- flags.turborepo = true;
901
+ } else if (arg === "--fast-turborepo") {
902
+ flags.fastTurborepo = true;
877
903
  } else if (arg === "--no-git") {
878
904
  flags.noGit = true;
879
905
  } else if (arg === "--no-install") {
@@ -981,13 +1007,14 @@ async function create(args) {
981
1007
  const { flags, projectName } = parseFlags(args);
982
1008
  Ie(import_picocolors3.default.bgCyan(import_picocolors3.default.black(" Novo Projeto nimbuslab ")));
983
1009
  let config;
984
- const hasTypeFlag = flags.fast || flags.fastPlus || flags.turborepo;
985
- const typeFromFlag = flags.turborepo ? "fast+" : flags.fastPlus ? "fast+" : flags.fast ? "fast" : null;
986
- const monorepoFromFlag = flags.turborepo;
1010
+ const hasTypeFlag = flags.landing || flags.app || flags.turborepo || flags.fast || flags.fastPlus || flags.fastTurborepo;
1011
+ const typeFromFlag = flags.landing ? "landing" : flags.app ? "app" : flags.turborepo ? "turborepo" : flags.fastTurborepo ? "fast+" : flags.fastPlus ? "fast+" : flags.fast ? "fast" : null;
1012
+ const monorepoFromFlag = flags.fastTurborepo;
987
1013
  if ((flags.yes || hasTypeFlag) && projectName) {
1014
+ const defaultType = flags.landing || flags.app || flags.turborepo ? "landing" : "fast";
988
1015
  config = {
989
1016
  name: projectName,
990
- type: typeFromFlag || "fast",
1017
+ type: typeFromFlag || defaultType,
991
1018
  monorepo: monorepoFromFlag,
992
1019
  git: !flags.noGit,
993
1020
  install: !flags.noInstall,
@@ -1052,6 +1079,10 @@ async function create(args) {
1052
1079
  showNextSteps(config);
1053
1080
  }
1054
1081
  async function promptConfig(initialName, flags) {
1082
+ const { isMember, user } = await isNimbuslabMember();
1083
+ const greeting = user ? `Ola, ${user}!` : "Ola!";
1084
+ console.log(import_picocolors3.default.dim(` ${greeting}`));
1085
+ console.log();
1055
1086
  const name = await he({
1056
1087
  message: "Nome do projeto:",
1057
1088
  placeholder: "meu-projeto",
@@ -1067,23 +1098,46 @@ async function promptConfig(initialName, flags) {
1067
1098
  });
1068
1099
  if (pD(name))
1069
1100
  return name;
1101
+ const publicOptions = [
1102
+ {
1103
+ value: "landing",
1104
+ label: "Landing Page",
1105
+ hint: "Next.js 16 + Tailwind 4 + shadcn"
1106
+ },
1107
+ {
1108
+ value: "app",
1109
+ label: "Web App",
1110
+ hint: "Landing + Better Auth + Prisma"
1111
+ },
1112
+ {
1113
+ value: "turborepo",
1114
+ label: "Monorepo",
1115
+ hint: "Turborepo + apps/packages"
1116
+ }
1117
+ ];
1118
+ const privateOptions = isMember ? [
1119
+ {
1120
+ value: "fast",
1121
+ label: "fast",
1122
+ hint: "Landing page metodologia fast"
1123
+ },
1124
+ {
1125
+ value: "fast+",
1126
+ label: "fast+",
1127
+ hint: "SaaS completo"
1128
+ }
1129
+ ] : [];
1070
1130
  const type = await ve({
1071
1131
  message: "Tipo de projeto:",
1072
- options: [
1073
- {
1074
- value: "fast",
1075
- label: "fast",
1076
- hint: "Landing page em 6 dias"
1077
- },
1078
- {
1079
- value: "fast+",
1080
- label: "fast+",
1081
- hint: "SaaS completo com backend"
1082
- }
1083
- ]
1132
+ options: [...publicOptions, ...privateOptions]
1084
1133
  });
1085
1134
  if (pD(type))
1086
1135
  return type;
1136
+ const isPublicTemplate = ["landing", "app", "turborepo"].includes(type);
1137
+ if (!isMember && !isPublicTemplate) {
1138
+ console.log(import_picocolors3.default.red("Erro: Template disponivel apenas para membros nimbuslab"));
1139
+ process.exit(1);
1140
+ }
1087
1141
  let monorepo = false;
1088
1142
  if (type === "fast+") {
1089
1143
  const useMonorepo = await ye({
@@ -1145,6 +1199,33 @@ async function promptConfig(initialName, flags) {
1145
1199
  return contract;
1146
1200
  contractNumber = contract;
1147
1201
  }
1202
+ if (isPublicTemplate) {
1203
+ const install2 = await ye({
1204
+ message: "Instalar dependencias?",
1205
+ initialValue: true
1206
+ });
1207
+ if (pD(install2))
1208
+ return install2;
1209
+ return {
1210
+ name,
1211
+ type,
1212
+ monorepo: false,
1213
+ git,
1214
+ install: install2,
1215
+ github,
1216
+ githubOrg,
1217
+ githubDescription,
1218
+ contractNumber: "",
1219
+ resendApiKey: "",
1220
+ resendFromEmail: "",
1221
+ contactEmail: "",
1222
+ railwayProject: "",
1223
+ railwayToken: "",
1224
+ stagingUrl: "",
1225
+ productionUrl: "",
1226
+ customTemplate: flags?.template || null
1227
+ };
1228
+ }
1148
1229
  let resendApiKey = "";
1149
1230
  let resendFromEmail = "";
1150
1231
  let contactEmail = "";
@@ -1304,14 +1385,20 @@ async function createProject(config) {
1304
1385
  templateRepo = TEMPLATES[templateKey];
1305
1386
  templateLabel = config.monorepo ? `${config.type} (monorepo)` : config.type;
1306
1387
  }
1388
+ const isPublicTemplate = ["landing", "app", "turborepo"].includes(config.type);
1307
1389
  s.start(`Clonando template ${templateLabel}...`);
1308
1390
  try {
1309
- await $2`gh repo clone ${templateRepo} ${config.name} -- --depth 1`.quiet();
1391
+ if (isPublicTemplate) {
1392
+ const httpsUrl = `https://github.com/${templateRepo}.git`;
1393
+ await $2`git clone --depth 1 ${httpsUrl} ${config.name}`.quiet();
1394
+ } else {
1395
+ await $2`gh repo clone ${templateRepo} ${config.name} -- --depth 1`.quiet();
1396
+ }
1310
1397
  await rm(join(config.name, ".git"), { recursive: true, force: true });
1311
1398
  s.stop(`Template ${templateLabel} clonado`);
1312
1399
  } catch (error) {
1313
1400
  s.stop("Erro ao clonar template");
1314
- throw new Error(`Falha ao clonar template ${templateRepo}. Verifique se tem acesso ao repositorio.`);
1401
+ throw new Error(`Falha ao clonar template ${templateRepo}. Verifique sua conexao ou acesso ao repositorio.`);
1315
1402
  }
1316
1403
  s.start("Configurando projeto...");
1317
1404
  try {
@@ -1430,6 +1517,7 @@ function generateEnvFile(config) {
1430
1517
  `;
1431
1518
  }
1432
1519
  function showNextSteps(config) {
1520
+ const isPublicTemplate = ["landing", "app", "turborepo"].includes(config.type);
1433
1521
  console.log();
1434
1522
  console.log(import_picocolors3.default.bold("Proximos passos:"));
1435
1523
  console.log();
@@ -1437,7 +1525,9 @@ function showNextSteps(config) {
1437
1525
  if (!config.install) {
1438
1526
  console.log(` ${import_picocolors3.default.cyan("bun")} install`);
1439
1527
  }
1440
- console.log(` ${import_picocolors3.default.cyan("bun")} setup`);
1528
+ if (!isPublicTemplate) {
1529
+ console.log(` ${import_picocolors3.default.cyan("bun")} setup`);
1530
+ }
1441
1531
  console.log(` ${import_picocolors3.default.cyan("bun")} dev`);
1442
1532
  console.log();
1443
1533
  if (config.git) {
@@ -1466,11 +1556,18 @@ function showNextSteps(config) {
1466
1556
  console.log(import_picocolors3.default.yellow(" Dica: Configure .env manualmente ou use 'bun setup'."));
1467
1557
  console.log();
1468
1558
  }
1469
- console.log(import_picocolors3.default.dim(" Documentacao: https://github.com/nimbuslab-templates"));
1559
+ if (isPublicTemplate) {
1560
+ console.log(import_picocolors3.default.dim(" Template open source (MIT) by nimbuslab"));
1561
+ console.log(import_picocolors3.default.dim(` Documentacao: https://github.com/nimbuslab/create-next-${config.type === "turborepo" ? "turborepo" : config.type}`));
1562
+ } else {
1563
+ console.log(import_picocolors3.default.dim(" Documentacao: https://github.com/nimbuslab-templates"));
1564
+ }
1470
1565
  console.log();
1471
1566
  }
1472
1567
 
1473
1568
  // src/index.ts
1569
+ var PACKAGE_NAME = "@nimbuslab/cli";
1570
+ var CURRENT_VERSION = "0.6.0";
1474
1571
  var LOGO = `
1475
1572
  \u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
1476
1573
  \u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D
@@ -1478,6 +1575,40 @@ var LOGO = `
1478
1575
  \u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551
1479
1576
  \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551
1480
1577
  \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D`;
1578
+ async function checkForUpdates() {
1579
+ try {
1580
+ const controller = new AbortController;
1581
+ const timeout = setTimeout(() => controller.abort(), 3000);
1582
+ const res = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, {
1583
+ signal: controller.signal
1584
+ });
1585
+ clearTimeout(timeout);
1586
+ if (!res.ok)
1587
+ return null;
1588
+ const data = await res.json();
1589
+ const latestVersion = data.version;
1590
+ if (latestVersion && latestVersion !== CURRENT_VERSION) {
1591
+ return latestVersion;
1592
+ }
1593
+ return null;
1594
+ } catch {
1595
+ return null;
1596
+ }
1597
+ }
1598
+ function showUpdateNotice(latestVersion) {
1599
+ const current = CURRENT_VERSION;
1600
+ const latest = latestVersion;
1601
+ const command = `bun add -g ${PACKAGE_NAME}`;
1602
+ const line1 = ` Nova versao disponivel: ${current} \u2192 ${latest}`;
1603
+ const line2 = ` Atualize com: ${command}`;
1604
+ const maxLen = Math.max(line1.length, line2.length);
1605
+ const border = "\u2500".repeat(maxLen + 2);
1606
+ console.log(import_picocolors4.default.yellow(` \u250C${border}\u2510`));
1607
+ console.log(import_picocolors4.default.yellow(` \u2502`) + import_picocolors4.default.white(line1.padEnd(maxLen + 1)) + import_picocolors4.default.yellow(`\u2502`));
1608
+ console.log(import_picocolors4.default.yellow(` \u2502`) + import_picocolors4.default.cyan(line2.padEnd(maxLen + 1)) + import_picocolors4.default.yellow(`\u2502`));
1609
+ console.log(import_picocolors4.default.yellow(` \u2514${border}\u2518`));
1610
+ console.log();
1611
+ }
1481
1612
  async function main() {
1482
1613
  const args = process.argv.slice(2);
1483
1614
  const command = args[0];
@@ -1485,6 +1616,10 @@ async function main() {
1485
1616
  console.log(import_picocolors4.default.white(" CLI da nimbuslab"));
1486
1617
  console.log(import_picocolors4.default.dim(" Crie projetos incriveis"));
1487
1618
  console.log();
1619
+ const latestVersion = await checkForUpdates();
1620
+ if (latestVersion) {
1621
+ showUpdateNotice(latestVersion);
1622
+ }
1488
1623
  if (!command || command === "create") {
1489
1624
  await create(args.slice(1));
1490
1625
  } else if (command === "help" || command === "--help" || command === "-h") {
@@ -1502,33 +1637,29 @@ function showHelp() {
1502
1637
  ${import_picocolors4.default.bold("Uso:")} nimbus [comando] [opcoes]
1503
1638
 
1504
1639
  ${import_picocolors4.default.bold("Comandos:")}
1505
- create [nome] Criar novo projeto nimbuslab
1640
+ create [nome] Criar novo projeto
1506
1641
  help Mostrar esta ajuda
1507
1642
  version Mostrar versao
1508
1643
 
1509
- ${import_picocolors4.default.bold("Tipo de projeto:")}
1510
- --fast Landing page (template fast)
1511
- --fast-plus SaaS single-repo (template fast+)
1512
- --turborepo SaaS monorepo (template fast+ turborepo)
1644
+ ${import_picocolors4.default.bold("Templates:")}
1645
+ --landing Landing page (Next.js 16 + Tailwind 4 + shadcn)
1646
+ --app Web app (Landing + Better Auth + Prisma)
1647
+ --turborepo Monorepo (Turborepo + apps/packages)
1513
1648
 
1514
1649
  ${import_picocolors4.default.bold("Opcoes:")}
1515
1650
  -y, --yes Aceitar defaults
1516
1651
  --no-git Nao inicializar Git
1517
1652
  --no-install Nao instalar dependencias
1518
- --railway Configurar Railway automaticamente
1519
1653
  --template <url> Usar template customizado
1520
1654
 
1521
1655
  ${import_picocolors4.default.bold("Exemplos:")}
1522
- ${import_picocolors4.default.dim("$")} nimbus create meu-projeto
1523
- ${import_picocolors4.default.dim("$")} nimbus create meu-projeto -y
1524
- ${import_picocolors4.default.dim("$")} nimbus create meu-projeto --fast
1525
- ${import_picocolors4.default.dim("$")} nimbus create meu-saas --fast-plus --railway
1526
- ${import_picocolors4.default.dim("$")} nimbus create meu-app --turborepo --no-install
1527
- ${import_picocolors4.default.dim("$")} nimbus create custom --template user/repo
1656
+ ${import_picocolors4.default.dim("$")} nimbus create minha-landing --landing
1657
+ ${import_picocolors4.default.dim("$")} nimbus create meu-app --app
1658
+ ${import_picocolors4.default.dim("$")} nimbus create meu-monorepo --turborepo
1528
1659
  `);
1529
1660
  }
1530
1661
  function showVersion() {
1531
- console.log(`@nimbuslab/cli v0.1.0`);
1662
+ console.log(`${PACKAGE_NAME} v${CURRENT_VERSION}`);
1532
1663
  }
1533
1664
  main().catch((err) => {
1534
1665
  console.error(import_picocolors4.default.red("Erro:"), err.message);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nimbuslab/cli",
3
- "version": "0.4.1",
3
+ "version": "0.6.0",
4
4
  "description": "CLI para criar projetos nimbuslab",
5
5
  "type": "module",
6
6
  "bin": {
@@ -21,11 +21,11 @@
21
21
  "private": false,
22
22
  "repository": {
23
23
  "type": "git",
24
- "url": "git+https://github.com/nimbuslab/nimbus-cli.git"
24
+ "url": "git+https://github.com/nimbuslab/cli.git"
25
25
  },
26
26
  "homepage": "https://nimbuslab.com.br",
27
27
  "bugs": {
28
- "url": "https://github.com/nimbuslab/nimbus-cli/issues"
28
+ "url": "https://github.com/nimbuslab/cli/issues"
29
29
  },
30
30
  "engines": {
31
31
  "node": ">=18"
@@ -4,16 +4,46 @@ import { $} from "bun"
4
4
  import { rm } from "node:fs/promises"
5
5
  import { join } from "node:path"
6
6
 
7
- // Templates separados por tipo
8
- const TEMPLATES = {
7
+ // Templates privados (nimbuslab-templates) - uso interno
8
+ const PRIVATE_TEMPLATES = {
9
9
  "fast": "nimbuslab-templates/fast-template",
10
10
  "fast+": "nimbuslab-templates/fastplus-template",
11
11
  "fast+-monorepo": "nimbuslab-templates/fastplus-monorepo-template",
12
12
  }
13
13
 
14
+ // Templates públicos (nimbuslab)
15
+ const PUBLIC_TEMPLATES = {
16
+ "landing": "nimbuslab/create-next-landing",
17
+ "app": "nimbuslab/create-next-app",
18
+ "turborepo": "nimbuslab/create-turborepo",
19
+ }
20
+
21
+ // Todos os templates
22
+ const TEMPLATES = { ...PRIVATE_TEMPLATES, ...PUBLIC_TEMPLATES }
23
+
24
+ // Dominios nimbuslab para deteccao de membros
25
+ const NIMBUSLAB_DOMAINS = ["@nimbuslab.com.br", "@nimbuslab.net.br"]
26
+
27
+ // Detectar se é membro da nimbuslab (verifica apenas dominio do email)
28
+ async function isNimbuslabMember(): Promise<{ isMember: boolean; user: string | null; email: string | null }> {
29
+ try {
30
+ const user = (await $`git config user.name`.text()).trim()
31
+ const email = (await $`git config user.email`.text()).trim()
32
+
33
+ // Verifica se email termina com dominio nimbuslab
34
+ const isMember = NIMBUSLAB_DOMAINS.some(domain => email.endsWith(domain))
35
+
36
+ return { isMember, user, email }
37
+ } catch {
38
+ return { isMember: false, user: null, email: null }
39
+ }
40
+ }
41
+
42
+ type ProjectType = "fast" | "fast+" | "landing" | "app" | "turborepo"
43
+
14
44
  interface ProjectConfig {
15
45
  name: string
16
- type: "fast" | "fast+"
46
+ type: ProjectType
17
47
  monorepo: boolean
18
48
  git: boolean
19
49
  install: boolean
@@ -37,9 +67,15 @@ interface ProjectConfig {
37
67
 
38
68
  interface CreateFlags {
39
69
  yes: boolean
70
+ // Templates
71
+ landing: boolean
72
+ app: boolean
73
+ turborepo: boolean
74
+ // Templates internos nimbuslab
40
75
  fast: boolean
41
76
  fastPlus: boolean
42
- turborepo: boolean
77
+ fastTurborepo: boolean
78
+ // Outros
43
79
  noGit: boolean
44
80
  noInstall: boolean
45
81
  railway: boolean
@@ -49,9 +85,12 @@ interface CreateFlags {
49
85
  function parseFlags(args: string[]): { flags: CreateFlags; projectName: string | undefined } {
50
86
  const flags: CreateFlags = {
51
87
  yes: false,
88
+ landing: false,
89
+ app: false,
90
+ turborepo: false,
52
91
  fast: false,
53
92
  fastPlus: false,
54
- turborepo: false,
93
+ fastTurborepo: false,
55
94
  noGit: false,
56
95
  noInstall: false,
57
96
  railway: false,
@@ -66,12 +105,18 @@ function parseFlags(args: string[]): { flags: CreateFlags; projectName: string |
66
105
 
67
106
  if (arg === "-y" || arg === "--yes") {
68
107
  flags.yes = true
108
+ } else if (arg === "--landing") {
109
+ flags.landing = true
110
+ } else if (arg === "--app") {
111
+ flags.app = true
112
+ } else if (arg === "--turborepo") {
113
+ flags.turborepo = true
69
114
  } else if (arg === "--fast") {
70
115
  flags.fast = true
71
116
  } else if (arg === "--fast-plus") {
72
117
  flags.fastPlus = true
73
- } else if (arg === "--turborepo") {
74
- flags.turborepo = true
118
+ } else if (arg === "--fast-turborepo") {
119
+ flags.fastTurborepo = true
75
120
  } else if (arg === "--no-git") {
76
121
  flags.noGit = true
77
122
  } else if (arg === "--no-install") {
@@ -204,15 +249,23 @@ export async function create(args: string[]) {
204
249
  let config: ProjectConfig | symbol
205
250
 
206
251
  // Determina tipo baseado nas flags
207
- const hasTypeFlag = flags.fast || flags.fastPlus || flags.turborepo
208
- const typeFromFlag = flags.turborepo ? "fast+" : flags.fastPlus ? "fast+" : flags.fast ? "fast" : null
209
- const monorepoFromFlag = flags.turborepo
252
+ const hasTypeFlag = flags.landing || flags.app || flags.turborepo || flags.fast || flags.fastPlus || flags.fastTurborepo
253
+ const typeFromFlag: ProjectType | null = flags.landing ? "landing"
254
+ : flags.app ? "app"
255
+ : flags.turborepo ? "turborepo"
256
+ : flags.fastTurborepo ? "fast+"
257
+ : flags.fastPlus ? "fast+"
258
+ : flags.fast ? "fast"
259
+ : null
260
+ const monorepoFromFlag = flags.fastTurborepo
210
261
 
211
262
  // Modo automatico: -y com nome OU flags de tipo com nome
212
263
  if ((flags.yes || hasTypeFlag) && projectName) {
264
+ // Templates públicos usam "landing" como default
265
+ const defaultType: ProjectType = flags.landing || flags.app || flags.turborepo ? "landing" : "fast"
213
266
  config = {
214
267
  name: projectName,
215
- type: typeFromFlag || "fast",
268
+ type: typeFromFlag || defaultType,
216
269
  monorepo: monorepoFromFlag,
217
270
  git: !flags.noGit,
218
271
  install: !flags.noInstall,
@@ -284,6 +337,14 @@ export async function create(args: string[]) {
284
337
  }
285
338
 
286
339
  async function promptConfig(initialName?: string, flags?: CreateFlags): Promise<ProjectConfig | symbol> {
340
+ // Detectar usuario git e se é membro da nimbuslab
341
+ const { isMember, user } = await isNimbuslabMember()
342
+
343
+ // Saudacao
344
+ const greeting = user ? `Ola, ${user}!` : "Ola!"
345
+ console.log(pc.dim(` ${greeting}`))
346
+ console.log()
347
+
287
348
  const name = await p.text({
288
349
  message: "Nome do projeto:",
289
350
  placeholder: "meu-projeto",
@@ -299,24 +360,55 @@ async function promptConfig(initialName?: string, flags?: CreateFlags): Promise<
299
360
 
300
361
  if (p.isCancel(name)) return name
301
362
 
363
+ // Opções públicas (todos veem)
364
+ const publicOptions = [
365
+ {
366
+ value: "landing",
367
+ label: "Landing Page",
368
+ hint: "Next.js 16 + Tailwind 4 + shadcn",
369
+ },
370
+ {
371
+ value: "app",
372
+ label: "Web App",
373
+ hint: "Landing + Better Auth + Prisma",
374
+ },
375
+ {
376
+ value: "turborepo",
377
+ label: "Monorepo",
378
+ hint: "Turborepo + apps/packages",
379
+ },
380
+ ]
381
+
382
+ // Opções privadas (só membros nimbuslab)
383
+ const privateOptions = isMember ? [
384
+ {
385
+ value: "fast",
386
+ label: "fast",
387
+ hint: "Landing page metodologia fast",
388
+ },
389
+ {
390
+ value: "fast+",
391
+ label: "fast+",
392
+ hint: "SaaS completo",
393
+ },
394
+ ] : []
395
+
302
396
  const type = await p.select({
303
397
  message: "Tipo de projeto:",
304
- options: [
305
- {
306
- value: "fast",
307
- label: "fast",
308
- hint: "Landing page em 6 dias",
309
- },
310
- {
311
- value: "fast+",
312
- label: "fast+",
313
- hint: "SaaS completo com backend",
314
- },
315
- ],
398
+ options: [...publicOptions, ...privateOptions],
316
399
  })
317
400
 
318
401
  if (p.isCancel(type)) return type
319
402
 
403
+ // Verifica se é template público (não precisa de auth nimbuslab)
404
+ const isPublicTemplate = ["landing", "app", "turborepo"].includes(type as string)
405
+
406
+ // Se não é membro e tentou usar template privado (via flag), bloqueia
407
+ if (!isMember && !isPublicTemplate) {
408
+ console.log(pc.red("Erro: Template disponivel apenas para membros nimbuslab"))
409
+ process.exit(1)
410
+ }
411
+
320
412
  // Pergunta sobre monorepo apenas para fast+
321
413
  let monorepo = false
322
414
  if (type === "fast+") {
@@ -377,7 +469,7 @@ async function promptConfig(initialName?: string, flags?: CreateFlags): Promise<
377
469
  }
378
470
  }
379
471
 
380
- // M26: Número do contrato (apenas para fast)
472
+ // M26: Número do contrato (apenas para fast - privado)
381
473
  let contractNumber = ""
382
474
  if (type === "fast") {
383
475
  const contract = await p.text({
@@ -389,6 +481,36 @@ async function promptConfig(initialName?: string, flags?: CreateFlags): Promise<
389
481
  contractNumber = contract as string
390
482
  }
391
483
 
484
+ // Templates públicos têm fluxo simplificado
485
+ if (isPublicTemplate) {
486
+ const install = await p.confirm({
487
+ message: "Instalar dependencias?",
488
+ initialValue: true,
489
+ })
490
+
491
+ if (p.isCancel(install)) return install
492
+
493
+ return {
494
+ name: name as string,
495
+ type: type as ProjectType,
496
+ monorepo: false,
497
+ git: git as boolean,
498
+ install: install as boolean,
499
+ github,
500
+ githubOrg,
501
+ githubDescription,
502
+ contractNumber: "",
503
+ resendApiKey: "",
504
+ resendFromEmail: "",
505
+ contactEmail: "",
506
+ railwayProject: "",
507
+ railwayToken: "",
508
+ stagingUrl: "",
509
+ productionUrl: "",
510
+ customTemplate: flags?.template || null,
511
+ }
512
+ }
513
+
392
514
  // Configuracoes de infra (separadas)
393
515
  let resendApiKey = ""
394
516
  let resendFromEmail = ""
@@ -577,19 +699,30 @@ async function createProject(config: ProjectConfig) {
577
699
  templateRepo = config.customTemplate
578
700
  templateLabel = `customizado (${config.customTemplate})`
579
701
  } else {
702
+ // fast+ com monorepo usa template específico
580
703
  const templateKey = config.type === "fast+" && config.monorepo ? "fast+-monorepo" : config.type
581
- templateRepo = TEMPLATES[templateKey]
704
+ templateRepo = TEMPLATES[templateKey as keyof typeof TEMPLATES]
582
705
  templateLabel = config.monorepo ? `${config.type} (monorepo)` : config.type
583
706
  }
584
707
 
708
+ // Verifica se é template público
709
+ const isPublicTemplate = ["landing", "app", "turborepo"].includes(config.type)
710
+
585
711
  s.start(`Clonando template ${templateLabel}...`)
586
712
  try {
587
- await $`gh repo clone ${templateRepo} ${config.name} -- --depth 1`.quiet()
713
+ if (isPublicTemplate) {
714
+ // Templates públicos: usa HTTPS (não precisa de auth)
715
+ const httpsUrl = `https://github.com/${templateRepo}.git`
716
+ await $`git clone --depth 1 ${httpsUrl} ${config.name}`.quiet()
717
+ } else {
718
+ // Templates privados: usa gh (precisa de auth)
719
+ await $`gh repo clone ${templateRepo} ${config.name} -- --depth 1`.quiet()
720
+ }
588
721
  await rm(join(config.name, ".git"), { recursive: true, force: true })
589
722
  s.stop(`Template ${templateLabel} clonado`)
590
723
  } catch (error) {
591
724
  s.stop("Erro ao clonar template")
592
- throw new Error(`Falha ao clonar template ${templateRepo}. Verifique se tem acesso ao repositorio.`)
725
+ throw new Error(`Falha ao clonar template ${templateRepo}. Verifique sua conexao ou acesso ao repositorio.`)
593
726
  }
594
727
 
595
728
  // Update package.json
@@ -743,6 +876,8 @@ function generateEnvFile(config: ProjectConfig): string {
743
876
  }
744
877
 
745
878
  function showNextSteps(config: ProjectConfig) {
879
+ const isPublicTemplate = ["landing", "app", "turborepo"].includes(config.type)
880
+
746
881
  console.log()
747
882
  console.log(pc.bold("Proximos passos:"))
748
883
  console.log()
@@ -752,7 +887,10 @@ function showNextSteps(config: ProjectConfig) {
752
887
  console.log(` ${pc.cyan("bun")} install`)
753
888
  }
754
889
 
755
- console.log(` ${pc.cyan("bun")} setup`)
890
+ // Templates públicos não têm bun setup
891
+ if (!isPublicTemplate) {
892
+ console.log(` ${pc.cyan("bun")} setup`)
893
+ }
756
894
  console.log(` ${pc.cyan("bun")} dev`)
757
895
  console.log()
758
896
 
@@ -791,6 +929,12 @@ function showNextSteps(config: ProjectConfig) {
791
929
  console.log()
792
930
  }
793
931
 
794
- console.log(pc.dim(" Documentacao: https://github.com/nimbuslab-templates"))
932
+ // Info sobre templates públicos
933
+ if (isPublicTemplate) {
934
+ console.log(pc.dim(" Template open source (MIT) by nimbuslab"))
935
+ console.log(pc.dim(` Documentacao: https://github.com/nimbuslab/create-next-${config.type === "turborepo" ? "turborepo" : config.type}`))
936
+ } else {
937
+ console.log(pc.dim(" Documentacao: https://github.com/nimbuslab-templates"))
938
+ }
795
939
  console.log()
796
940
  }
package/src/index.ts CHANGED
@@ -4,6 +4,9 @@ import * as p from "@clack/prompts"
4
4
  import pc from "picocolors"
5
5
  import { create } from "./commands/create"
6
6
 
7
+ const PACKAGE_NAME = "@nimbuslab/cli"
8
+ const CURRENT_VERSION = "0.6.0"
9
+
7
10
  const LOGO = `
8
11
  ███╗ ██╗██╗███╗ ███╗██████╗ ██╗ ██╗███████╗
9
12
  ████╗ ██║██║████╗ ████║██╔══██╗██║ ██║██╔════╝
@@ -12,6 +15,48 @@ const LOGO = `
12
15
  ██║ ╚████║██║██║ ╚═╝ ██║██████╔╝╚██████╔╝███████║
13
16
  ╚═╝ ╚═══╝╚═╝╚═╝ ╚═╝╚═════╝ ╚═════╝ ╚══════╝`
14
17
 
18
+ async function checkForUpdates(): Promise<string | null> {
19
+ try {
20
+ const controller = new AbortController()
21
+ const timeout = setTimeout(() => controller.abort(), 3000)
22
+
23
+ const res = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, {
24
+ signal: controller.signal,
25
+ })
26
+ clearTimeout(timeout)
27
+
28
+ if (!res.ok) return null
29
+
30
+ const data = await res.json() as { version?: string }
31
+ const latestVersion = data.version
32
+
33
+ if (latestVersion && latestVersion !== CURRENT_VERSION) {
34
+ return latestVersion
35
+ }
36
+ return null
37
+ } catch {
38
+ return null
39
+ }
40
+ }
41
+
42
+ function showUpdateNotice(latestVersion: string) {
43
+ const current = CURRENT_VERSION
44
+ const latest = latestVersion
45
+ const command = `bun add -g ${PACKAGE_NAME}`
46
+
47
+ const line1 = ` Nova versao disponivel: ${current} → ${latest}`
48
+ const line2 = ` Atualize com: ${command}`
49
+
50
+ const maxLen = Math.max(line1.length, line2.length)
51
+ const border = "─".repeat(maxLen + 2)
52
+
53
+ console.log(pc.yellow(` ┌${border}┐`))
54
+ console.log(pc.yellow(` │`) + pc.white(line1.padEnd(maxLen + 1)) + pc.yellow(`│`))
55
+ console.log(pc.yellow(` │`) + pc.cyan(line2.padEnd(maxLen + 1)) + pc.yellow(`│`))
56
+ console.log(pc.yellow(` └${border}┘`))
57
+ console.log()
58
+ }
59
+
15
60
  async function main() {
16
61
  const args = process.argv.slice(2)
17
62
  const command = args[0]
@@ -21,6 +66,12 @@ async function main() {
21
66
  console.log(pc.dim(" Crie projetos incriveis"))
22
67
  console.log()
23
68
 
69
+ // Check for updates (non-blocking)
70
+ const latestVersion = await checkForUpdates()
71
+ if (latestVersion) {
72
+ showUpdateNotice(latestVersion)
73
+ }
74
+
24
75
  if (!command || command === "create") {
25
76
  await create(args.slice(1))
26
77
  } else if (command === "help" || command === "--help" || command === "-h") {
@@ -39,34 +90,30 @@ function showHelp() {
39
90
  ${pc.bold("Uso:")} nimbus [comando] [opcoes]
40
91
 
41
92
  ${pc.bold("Comandos:")}
42
- create [nome] Criar novo projeto nimbuslab
93
+ create [nome] Criar novo projeto
43
94
  help Mostrar esta ajuda
44
95
  version Mostrar versao
45
96
 
46
- ${pc.bold("Tipo de projeto:")}
47
- --fast Landing page (template fast)
48
- --fast-plus SaaS single-repo (template fast+)
49
- --turborepo SaaS monorepo (template fast+ turborepo)
97
+ ${pc.bold("Templates:")}
98
+ --landing Landing page (Next.js 16 + Tailwind 4 + shadcn)
99
+ --app Web app (Landing + Better Auth + Prisma)
100
+ --turborepo Monorepo (Turborepo + apps/packages)
50
101
 
51
102
  ${pc.bold("Opcoes:")}
52
103
  -y, --yes Aceitar defaults
53
104
  --no-git Nao inicializar Git
54
105
  --no-install Nao instalar dependencias
55
- --railway Configurar Railway automaticamente
56
106
  --template <url> Usar template customizado
57
107
 
58
108
  ${pc.bold("Exemplos:")}
59
- ${pc.dim("$")} nimbus create meu-projeto
60
- ${pc.dim("$")} nimbus create meu-projeto -y
61
- ${pc.dim("$")} nimbus create meu-projeto --fast
62
- ${pc.dim("$")} nimbus create meu-saas --fast-plus --railway
63
- ${pc.dim("$")} nimbus create meu-app --turborepo --no-install
64
- ${pc.dim("$")} nimbus create custom --template user/repo
109
+ ${pc.dim("$")} nimbus create minha-landing --landing
110
+ ${pc.dim("$")} nimbus create meu-app --app
111
+ ${pc.dim("$")} nimbus create meu-monorepo --turborepo
65
112
  `)
66
113
  }
67
114
 
68
115
  function showVersion() {
69
- console.log(`@nimbuslab/cli v0.1.0`)
116
+ console.log(`${PACKAGE_NAME} v${CURRENT_VERSION}`)
70
117
  }
71
118
 
72
119
  main().catch((err) => {