@nimbuslab/cli 0.2.0 → 0.2.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.
package/dist/index.js CHANGED
@@ -774,6 +774,45 @@ var TEMPLATES = {
774
774
  fast: "nimbuslab-templates/fast-template",
775
775
  "fast+": "nimbuslab-templates/fastplus-template"
776
776
  };
777
+ async function ensureRailwayCli() {
778
+ const checkCmd = process.platform === "win32" ? "where" : "which";
779
+ const hasRailway = await $2`${checkCmd} railway`.quiet().then(() => true).catch(() => false);
780
+ if (hasRailway)
781
+ return true;
782
+ console.log(import_picocolors3.default.yellow("Railway CLI nao encontrado. Instalando..."));
783
+ console.log();
784
+ try {
785
+ if (process.platform === "win32") {
786
+ await $2`powershell -c "iwr https://railway.app/install.ps1 -useb | iex"`.quiet();
787
+ } else {
788
+ await $2`curl -fsSL https://railway.app/install.sh | sh`.quiet();
789
+ }
790
+ console.log(import_picocolors3.default.green("Railway CLI instalado com sucesso!"));
791
+ return true;
792
+ } catch (error) {
793
+ console.log(import_picocolors3.default.red("Erro ao instalar Railway CLI."));
794
+ console.log(import_picocolors3.default.dim("Instale manualmente: https://docs.railway.app/guides/cli"));
795
+ return false;
796
+ }
797
+ }
798
+ async function listRailwayProjects() {
799
+ try {
800
+ const result = await $2`railway list`.text();
801
+ const lines = result.trim().split(`
802
+ `).filter((l2) => l2.trim());
803
+ return lines.slice(1).map((l2) => l2.trim());
804
+ } catch {
805
+ return [];
806
+ }
807
+ }
808
+ async function isRailwayAuthenticated() {
809
+ try {
810
+ await $2`railway whoami`.quiet();
811
+ return true;
812
+ } catch {
813
+ return false;
814
+ }
815
+ }
777
816
  async function create(args) {
778
817
  const checkCmd = process.platform === "win32" ? "where" : "which";
779
818
  const hasBun = await $2`${checkCmd} bun`.quiet().then(() => true).catch(() => false);
@@ -814,6 +853,15 @@ async function create(args) {
814
853
  console.log(import_picocolors3.default.dim("Execute: gh auth login"));
815
854
  process.exit(1);
816
855
  }
856
+ const hasRailway = await ensureRailwayCli();
857
+ if (hasRailway) {
858
+ const railwayAuth = await isRailwayAuthenticated();
859
+ if (!railwayAuth) {
860
+ console.log(import_picocolors3.default.yellow("Railway CLI nao autenticado."));
861
+ console.log(import_picocolors3.default.dim("Execute: railway login"));
862
+ console.log();
863
+ }
864
+ }
817
865
  const hasYes = args.includes("-y") || args.includes("--yes");
818
866
  const projectName = args.find((a) => !a.startsWith("-"));
819
867
  Ie(import_picocolors3.default.bgCyan(import_picocolors3.default.black(" Novo Projeto nimbuslab ")));
@@ -827,9 +875,11 @@ async function create(args) {
827
875
  github: false,
828
876
  githubOrg: null,
829
877
  githubDescription: "",
878
+ contractNumber: "",
830
879
  resendApiKey: "",
831
880
  resendFromEmail: "",
832
881
  contactEmail: "",
882
+ railwayProject: "",
833
883
  railwayToken: "",
834
884
  stagingUrl: "",
835
885
  productionUrl: ""
@@ -838,7 +888,7 @@ async function create(args) {
838
888
  console.log(import_picocolors3.default.dim(` Tipo: fast`));
839
889
  console.log(import_picocolors3.default.dim(` Git: sim`));
840
890
  console.log(import_picocolors3.default.dim(` GitHub: nao`));
841
- console.log(import_picocolors3.default.dim(` Infra: configurar depois com 'bun setup'`));
891
+ console.log(import_picocolors3.default.dim(` Infra: configurar depois`));
842
892
  console.log(import_picocolors3.default.dim(` Instalar: sim`));
843
893
  console.log();
844
894
  } else {
@@ -925,6 +975,17 @@ async function promptConfig(initialName) {
925
975
  githubDescription = description;
926
976
  }
927
977
  }
978
+ let contractNumber = "";
979
+ if (type === "fast") {
980
+ const contract = await he({
981
+ message: "Numero do contrato (ex: 001):",
982
+ placeholder: "001",
983
+ validate: (v2) => v2 ? undefined : "Numero do contrato e obrigatorio para fast"
984
+ });
985
+ if (pD(contract))
986
+ return contract;
987
+ contractNumber = contract;
988
+ }
928
989
  const configureInfra = await ye({
929
990
  message: "Configurar infra agora? (Resend, URLs)",
930
991
  initialValue: true
@@ -934,11 +995,15 @@ async function promptConfig(initialName) {
934
995
  let resendApiKey = "";
935
996
  let resendFromEmail = "";
936
997
  let contactEmail = "";
998
+ let railwayProject = "";
937
999
  let railwayToken = "";
938
1000
  let stagingUrl = "";
939
1001
  let productionUrl = "";
940
1002
  if (configureInfra) {
941
- const suggestedDomain = `${name}.com.br`;
1003
+ const currentYear = new Date().getFullYear().toString().slice(-3);
1004
+ const defaultStagingUrl = type === "fast" ? `https://fast-${contractNumber}-${currentYear}.nimbuslab.net.br` : `https://${name}.nimbuslab.net.br`;
1005
+ const defaultFromEmail = "no-reply@nimbuslab.com.br";
1006
+ const defaultContactEmail = type === "fast" ? "fast@nimbuslab.com.br" : "suporte@nimbuslab.com.br";
942
1007
  console.log();
943
1008
  console.log(import_picocolors3.default.dim(" Resend (Email)"));
944
1009
  const resendKey = await he({
@@ -950,16 +1015,16 @@ async function promptConfig(initialName) {
950
1015
  resendApiKey = resendKey;
951
1016
  const fromEmail = await he({
952
1017
  message: "Email de envio (from):",
953
- placeholder: `contato@${suggestedDomain}`,
954
- initialValue: `contato@${suggestedDomain}`
1018
+ placeholder: defaultFromEmail,
1019
+ initialValue: defaultFromEmail
955
1020
  });
956
1021
  if (pD(fromEmail))
957
1022
  return fromEmail;
958
1023
  resendFromEmail = fromEmail;
959
1024
  const contact = await he({
960
1025
  message: "Email de contato (recebe formularios):",
961
- placeholder: `contato@${suggestedDomain}`,
962
- initialValue: `contato@${suggestedDomain}`
1026
+ placeholder: defaultContactEmail,
1027
+ initialValue: defaultContactEmail
963
1028
  });
964
1029
  if (pD(contact))
965
1030
  return contact;
@@ -968,34 +1033,68 @@ async function promptConfig(initialName) {
968
1033
  console.log(import_picocolors3.default.dim(" URLs do projeto"));
969
1034
  const staging = await he({
970
1035
  message: "URL de staging:",
971
- placeholder: `https://${name}-staging.up.railway.app`,
972
- initialValue: `https://${name}-staging.up.railway.app`
1036
+ placeholder: defaultStagingUrl,
1037
+ initialValue: defaultStagingUrl
973
1038
  });
974
1039
  if (pD(staging))
975
1040
  return staging;
976
1041
  stagingUrl = staging;
977
1042
  const production = await he({
978
1043
  message: "URL de producao:",
979
- placeholder: `https://${suggestedDomain}`,
980
- initialValue: `https://${suggestedDomain}`
1044
+ placeholder: defaultStagingUrl.replace(".nimbuslab.net.br", ".com.br"),
1045
+ initialValue: ""
981
1046
  });
982
1047
  if (pD(production))
983
1048
  return production;
984
1049
  productionUrl = production;
985
- const configRailway = await ye({
986
- message: "Configurar Railway token?",
987
- initialValue: false
988
- });
989
- if (pD(configRailway))
990
- return configRailway;
991
- if (configRailway) {
992
- const railwayTk = await he({
993
- message: "RAILWAY_TOKEN:",
994
- placeholder: "railway_xxxxxxxxxxxx"
995
- });
996
- if (pD(railwayTk))
997
- return railwayTk;
998
- railwayToken = railwayTk;
1050
+ const railwayAuthenticated = await isRailwayAuthenticated();
1051
+ if (railwayAuthenticated) {
1052
+ console.log();
1053
+ console.log(import_picocolors3.default.dim(" Railway"));
1054
+ const projects = await listRailwayProjects();
1055
+ if (type === "fast") {
1056
+ const fastProject = projects.find((p2) => p2.toLowerCase().includes("fast by nimbuslab"));
1057
+ if (fastProject) {
1058
+ railwayProject = fastProject;
1059
+ console.log(import_picocolors3.default.green(` Projeto: ${fastProject} (automatico)`));
1060
+ } else {
1061
+ console.log(import_picocolors3.default.yellow(" Projeto 'Fast by nimbuslab' nao encontrado."));
1062
+ console.log(import_picocolors3.default.dim(" Configure RAILWAY_TOKEN manualmente no .env"));
1063
+ }
1064
+ } else {
1065
+ const projectOptions = [
1066
+ ...projects.map((proj) => ({ value: proj, label: proj })),
1067
+ { value: "__new__", label: "Criar novo projeto", hint: "via railway init" },
1068
+ { value: "__skip__", label: "Pular", hint: "Configurar depois" }
1069
+ ];
1070
+ const selectedProject = await ve({
1071
+ message: "Projeto Railway para este SaaS:",
1072
+ options: projectOptions
1073
+ });
1074
+ if (pD(selectedProject))
1075
+ return selectedProject;
1076
+ if (selectedProject === "__new__") {
1077
+ const projectNameForRailway = name;
1078
+ console.log(import_picocolors3.default.dim(` Criando projeto "${projectNameForRailway}" no Railway...`));
1079
+ try {
1080
+ const result = await $2`railway init -n ${projectNameForRailway} --json`.text();
1081
+ const newProject = JSON.parse(result);
1082
+ railwayProject = newProject.name || projectNameForRailway;
1083
+ console.log(import_picocolors3.default.green(` Projeto "${railwayProject}" criado com sucesso!`));
1084
+ console.log(import_picocolors3.default.dim(` ID: ${newProject.id || "N/A"}`));
1085
+ } catch (error) {
1086
+ console.log(import_picocolors3.default.yellow(" Erro ao criar projeto via CLI."));
1087
+ console.log(import_picocolors3.default.dim(" Crie manualmente em: https://railway.app/new"));
1088
+ }
1089
+ } else if (selectedProject !== "__skip__") {
1090
+ railwayProject = selectedProject;
1091
+ console.log(import_picocolors3.default.green(` Projeto selecionado: ${railwayProject}`));
1092
+ }
1093
+ }
1094
+ } else {
1095
+ console.log();
1096
+ console.log(import_picocolors3.default.yellow(" Railway: nao autenticado (railway login)"));
1097
+ console.log(import_picocolors3.default.dim(" Configure RAILWAY_TOKEN manualmente no .env"));
999
1098
  }
1000
1099
  }
1001
1100
  const install = await ye({
@@ -1012,9 +1111,11 @@ async function promptConfig(initialName) {
1012
1111
  github,
1013
1112
  githubOrg,
1014
1113
  githubDescription,
1114
+ contractNumber,
1015
1115
  resendApiKey,
1016
1116
  resendFromEmail,
1017
1117
  contactEmail,
1118
+ railwayProject,
1018
1119
  railwayToken,
1019
1120
  stagingUrl,
1020
1121
  productionUrl
@@ -1065,8 +1166,12 @@ async function createProject(config) {
1065
1166
  const cwd = config.name;
1066
1167
  const repoName = config.githubOrg ? `${config.githubOrg}/${config.name}` : config.name;
1067
1168
  const visibility = config.githubOrg === "fast-by-nimbuslab" ? "--private" : "--public";
1068
- await $2`gh repo create ${repoName} ${visibility} --description ${config.githubDescription} --source . --remote origin --push`.cwd(cwd).quiet();
1169
+ await $2`gh repo create ${repoName} ${visibility} --description ${config.githubDescription} --source . --remote origin`.cwd(cwd).quiet();
1170
+ await $2`git checkout main`.cwd(cwd).quiet();
1171
+ await $2`git push -u origin main`.cwd(cwd).quiet();
1172
+ await $2`git checkout staging`.cwd(cwd).quiet();
1069
1173
  await $2`git push -u origin staging`.cwd(cwd).quiet();
1174
+ await $2`git checkout develop`.cwd(cwd).quiet();
1070
1175
  await $2`git push -u origin develop`.cwd(cwd).quiet();
1071
1176
  s.stop(`GitHub: ${repoName} criado`);
1072
1177
  } catch (error) {
@@ -1075,6 +1180,16 @@ async function createProject(config) {
1075
1180
  }
1076
1181
  }
1077
1182
  }
1183
+ if (config.railwayProject) {
1184
+ s.start(`Linkando Railway: ${config.railwayProject}...`);
1185
+ try {
1186
+ await $2`railway link -p ${config.railwayProject}`.cwd(config.name).quiet();
1187
+ s.stop(`Railway linkado: ${config.railwayProject}`);
1188
+ } catch (error) {
1189
+ s.stop("Erro ao linkar Railway");
1190
+ console.log(import_picocolors3.default.dim(" Execute manualmente: railway link"));
1191
+ }
1192
+ }
1078
1193
  if (config.resendApiKey || config.stagingUrl) {
1079
1194
  s.start("Gerando arquivo .env...");
1080
1195
  try {
@@ -1113,10 +1228,13 @@ function generateEnvFile(config) {
1113
1228
  `RESEND_FROM_EMAIL=${config.resendFromEmail || ""}`,
1114
1229
  `CONTACT_EMAIL=${config.contactEmail || ""}`
1115
1230
  ];
1116
- if (config.railwayToken) {
1231
+ if (config.railwayProject || config.railwayToken) {
1117
1232
  lines.push("");
1118
1233
  lines.push("# Railway");
1119
- lines.push(`RAILWAY_TOKEN=${config.railwayToken}`);
1234
+ if (config.railwayProject) {
1235
+ lines.push(`# Projeto: ${config.railwayProject}`);
1236
+ }
1237
+ lines.push(`RAILWAY_TOKEN=${config.railwayToken || "# Configure com: railway link"}`);
1120
1238
  }
1121
1239
  if (config.type === "fast+") {
1122
1240
  lines.push("");
@@ -1156,6 +1274,9 @@ function showNextSteps(config) {
1156
1274
  }
1157
1275
  if (config.type === "fast+") {
1158
1276
  console.log(import_picocolors3.default.dim(" Dica: Para fast+, configure DATABASE_URL e BETTER_AUTH_SECRET no .env"));
1277
+ if (!config.railwayToken) {
1278
+ console.log(import_picocolors3.default.dim(" Railway: Crie um projeto em https://railway.app/new"));
1279
+ }
1159
1280
  console.log();
1160
1281
  }
1161
1282
  if (config.resendApiKey || config.stagingUrl) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nimbuslab/cli",
3
- "version": "0.2.0",
3
+ "version": "0.2.5",
4
4
  "description": "CLI para criar projetos nimbuslab",
5
5
  "type": "module",
6
6
  "bin": {
@@ -18,15 +18,67 @@ interface ProjectConfig {
18
18
  github: boolean
19
19
  githubOrg: string | null
20
20
  githubDescription: string
21
+ // M26: Número do contrato (fast only)
22
+ contractNumber: string
21
23
  // M21-M23: Configs de infra
22
24
  resendApiKey: string
23
25
  resendFromEmail: string
24
26
  contactEmail: string
27
+ // M30: Railway via CLI
28
+ railwayProject: string
25
29
  railwayToken: string
26
30
  stagingUrl: string
27
31
  productionUrl: string
28
32
  }
29
33
 
34
+ // M30: Verificar e instalar Railway CLI
35
+ async function ensureRailwayCli(): Promise<boolean> {
36
+ const checkCmd = process.platform === "win32" ? "where" : "which"
37
+ const hasRailway = await $`${checkCmd} railway`.quiet().then(() => true).catch(() => false)
38
+
39
+ if (hasRailway) return true
40
+
41
+ console.log(pc.yellow("Railway CLI nao encontrado. Instalando..."))
42
+ console.log()
43
+
44
+ try {
45
+ if (process.platform === "win32") {
46
+ await $`powershell -c "iwr https://railway.app/install.ps1 -useb | iex"`.quiet()
47
+ } else {
48
+ await $`curl -fsSL https://railway.app/install.sh | sh`.quiet()
49
+ }
50
+ console.log(pc.green("Railway CLI instalado com sucesso!"))
51
+ return true
52
+ } catch (error) {
53
+ console.log(pc.red("Erro ao instalar Railway CLI."))
54
+ console.log(pc.dim("Instale manualmente: https://docs.railway.app/guides/cli"))
55
+ return false
56
+ }
57
+ }
58
+
59
+ // M30: Listar projetos Railway via CLI
60
+ async function listRailwayProjects(): Promise<string[]> {
61
+ try {
62
+ const result = await $`railway list`.text()
63
+ // Parse output: primeira linha é o team, demais são projetos
64
+ const lines = result.trim().split("\n").filter(l => l.trim())
65
+ // Remove primeira linha (team name) e extrai nomes dos projetos
66
+ return lines.slice(1).map(l => l.trim())
67
+ } catch {
68
+ return []
69
+ }
70
+ }
71
+
72
+ // M30: Verificar autenticacao Railway
73
+ async function isRailwayAuthenticated(): Promise<boolean> {
74
+ try {
75
+ await $`railway whoami`.quiet()
76
+ return true
77
+ } catch {
78
+ return false
79
+ }
80
+ }
81
+
30
82
  export async function create(args: string[]) {
31
83
  // Verifica dependencias (cross-platform)
32
84
  const checkCmd = process.platform === "win32" ? "where" : "which"
@@ -74,6 +126,17 @@ export async function create(args: string[]) {
74
126
  process.exit(1)
75
127
  }
76
128
 
129
+ // M30: Verificar/instalar Railway CLI
130
+ const hasRailway = await ensureRailwayCli()
131
+ if (hasRailway) {
132
+ const railwayAuth = await isRailwayAuthenticated()
133
+ if (!railwayAuth) {
134
+ console.log(pc.yellow("Railway CLI nao autenticado."))
135
+ console.log(pc.dim("Execute: railway login"))
136
+ console.log()
137
+ }
138
+ }
139
+
77
140
  const hasYes = args.includes("-y") || args.includes("--yes")
78
141
  const projectName = args.find(a => !a.startsWith("-"))
79
142
 
@@ -91,9 +154,11 @@ export async function create(args: string[]) {
91
154
  github: false,
92
155
  githubOrg: null,
93
156
  githubDescription: "",
157
+ contractNumber: "",
94
158
  resendApiKey: "",
95
159
  resendFromEmail: "",
96
160
  contactEmail: "",
161
+ railwayProject: "",
97
162
  railwayToken: "",
98
163
  stagingUrl: "",
99
164
  productionUrl: "",
@@ -102,7 +167,7 @@ export async function create(args: string[]) {
102
167
  console.log(pc.dim(` Tipo: fast`))
103
168
  console.log(pc.dim(` Git: sim`))
104
169
  console.log(pc.dim(` GitHub: nao`))
105
- console.log(pc.dim(` Infra: configurar depois com 'bun setup'`))
170
+ console.log(pc.dim(` Infra: configurar depois`))
106
171
  console.log(pc.dim(` Instalar: sim`))
107
172
  console.log()
108
173
  } else {
@@ -204,6 +269,18 @@ async function promptConfig(initialName?: string): Promise<ProjectConfig | symbo
204
269
  }
205
270
  }
206
271
 
272
+ // M26: Número do contrato (apenas para fast)
273
+ let contractNumber = ""
274
+ if (type === "fast") {
275
+ const contract = await p.text({
276
+ message: "Numero do contrato (ex: 001):",
277
+ placeholder: "001",
278
+ validate: (v) => v ? undefined : "Numero do contrato e obrigatorio para fast",
279
+ })
280
+ if (p.isCancel(contract)) return contract
281
+ contractNumber = contract as string
282
+ }
283
+
207
284
  // M21-M23: Configuracoes de infra (opcional)
208
285
  const configureInfra = await p.confirm({
209
286
  message: "Configurar infra agora? (Resend, URLs)",
@@ -215,15 +292,31 @@ async function promptConfig(initialName?: string): Promise<ProjectConfig | symbo
215
292
  let resendApiKey = ""
216
293
  let resendFromEmail = ""
217
294
  let contactEmail = ""
295
+ let railwayProject = ""
218
296
  let railwayToken = ""
219
297
  let stagingUrl = ""
220
298
  let productionUrl = ""
221
299
 
222
300
  if (configureInfra) {
223
- // Extrair dominio do nome para sugestoes
224
- const suggestedDomain = `${name}.com.br`
225
-
226
- // M22: Resend config
301
+ const currentYear = new Date().getFullYear().toString().slice(-3) // 025, 026, etc
302
+
303
+ // M26: URLs padrão baseadas no tipo
304
+ // fast: fast-{contrato}-{ano}.nimbuslab.net.br
305
+ // fast+: {nome}.nimbuslab.net.br
306
+ const defaultStagingUrl = type === "fast"
307
+ ? `https://fast-${contractNumber}-${currentYear}.nimbuslab.net.br`
308
+ : `https://${name}.nimbuslab.net.br`
309
+
310
+ // M27: Emails padrão
311
+ // from: no-reply@nimbuslab.com.br
312
+ // to fast: fast@nimbuslab.com.br
313
+ // to fast+: suporte@nimbuslab.com.br
314
+ const defaultFromEmail = "no-reply@nimbuslab.com.br"
315
+ const defaultContactEmail = type === "fast"
316
+ ? "fast@nimbuslab.com.br"
317
+ : "suporte@nimbuslab.com.br"
318
+
319
+ // M27: Resend config
227
320
  console.log()
228
321
  console.log(pc.dim(" Resend (Email)"))
229
322
 
@@ -236,54 +329,97 @@ async function promptConfig(initialName?: string): Promise<ProjectConfig | symbo
236
329
 
237
330
  const fromEmail = await p.text({
238
331
  message: "Email de envio (from):",
239
- placeholder: `contato@${suggestedDomain}`,
240
- initialValue: `contato@${suggestedDomain}`,
332
+ placeholder: defaultFromEmail,
333
+ initialValue: defaultFromEmail,
241
334
  })
242
335
  if (p.isCancel(fromEmail)) return fromEmail
243
336
  resendFromEmail = fromEmail as string
244
337
 
245
338
  const contact = await p.text({
246
339
  message: "Email de contato (recebe formularios):",
247
- placeholder: `contato@${suggestedDomain}`,
248
- initialValue: `contato@${suggestedDomain}`,
340
+ placeholder: defaultContactEmail,
341
+ initialValue: defaultContactEmail,
249
342
  })
250
343
  if (p.isCancel(contact)) return contact
251
344
  contactEmail = contact as string
252
345
 
253
- // URLs
346
+ // M26: URLs
254
347
  console.log()
255
348
  console.log(pc.dim(" URLs do projeto"))
256
349
 
257
350
  const staging = await p.text({
258
351
  message: "URL de staging:",
259
- placeholder: `https://${name}-staging.up.railway.app`,
260
- initialValue: `https://${name}-staging.up.railway.app`,
352
+ placeholder: defaultStagingUrl,
353
+ initialValue: defaultStagingUrl,
261
354
  })
262
355
  if (p.isCancel(staging)) return staging
263
356
  stagingUrl = staging as string
264
357
 
265
358
  const production = await p.text({
266
359
  message: "URL de producao:",
267
- placeholder: `https://${suggestedDomain}`,
268
- initialValue: `https://${suggestedDomain}`,
360
+ placeholder: defaultStagingUrl.replace('.nimbuslab.net.br', '.com.br'),
361
+ initialValue: "",
269
362
  })
270
363
  if (p.isCancel(production)) return production
271
364
  productionUrl = production as string
272
365
 
273
- // Railway token (opcional)
274
- const configRailway = await p.confirm({
275
- message: "Configurar Railway token?",
276
- initialValue: false,
277
- })
278
- if (p.isCancel(configRailway)) return configRailway
366
+ // M30: Railway via CLI
367
+ const railwayAuthenticated = await isRailwayAuthenticated()
279
368
 
280
- if (configRailway) {
281
- const railwayTk = await p.text({
282
- message: "RAILWAY_TOKEN:",
283
- placeholder: "railway_xxxxxxxxxxxx",
284
- })
285
- if (p.isCancel(railwayTk)) return railwayTk
286
- railwayToken = railwayTk as string
369
+ if (railwayAuthenticated) {
370
+ console.log()
371
+ console.log(pc.dim(" Railway"))
372
+
373
+ const projects = await listRailwayProjects()
374
+
375
+ if (type === "fast") {
376
+ // Fast usa projeto compartilhado "Fast by nimbuslab"
377
+ const fastProject = projects.find(p => p.toLowerCase().includes("fast by nimbuslab"))
378
+ if (fastProject) {
379
+ railwayProject = fastProject
380
+ console.log(pc.green(` Projeto: ${fastProject} (automatico)`))
381
+ } else {
382
+ console.log(pc.yellow(" Projeto 'Fast by nimbuslab' nao encontrado."))
383
+ console.log(pc.dim(" Configure RAILWAY_TOKEN manualmente no .env"))
384
+ }
385
+ } else {
386
+ // Fast+ pode escolher projeto existente ou criar novo
387
+ const projectOptions = [
388
+ ...projects.map(proj => ({ value: proj, label: proj })),
389
+ { value: "__new__", label: "Criar novo projeto", hint: "via railway init" },
390
+ { value: "__skip__", label: "Pular", hint: "Configurar depois" },
391
+ ]
392
+
393
+ const selectedProject = await p.select({
394
+ message: "Projeto Railway para este SaaS:",
395
+ options: projectOptions,
396
+ })
397
+
398
+ if (p.isCancel(selectedProject)) return selectedProject
399
+
400
+ if (selectedProject === "__new__") {
401
+ // Criar projeto via Railway CLI
402
+ const projectNameForRailway = name as string
403
+ console.log(pc.dim(` Criando projeto "${projectNameForRailway}" no Railway...`))
404
+ try {
405
+ const result = await $`railway init -n ${projectNameForRailway} --json`.text()
406
+ const newProject = JSON.parse(result)
407
+ railwayProject = newProject.name || projectNameForRailway
408
+ console.log(pc.green(` Projeto "${railwayProject}" criado com sucesso!`))
409
+ console.log(pc.dim(` ID: ${newProject.id || "N/A"}`))
410
+ } catch (error) {
411
+ console.log(pc.yellow(" Erro ao criar projeto via CLI."))
412
+ console.log(pc.dim(" Crie manualmente em: https://railway.app/new"))
413
+ }
414
+ } else if (selectedProject !== "__skip__") {
415
+ railwayProject = selectedProject as string
416
+ console.log(pc.green(` Projeto selecionado: ${railwayProject}`))
417
+ }
418
+ }
419
+ } else {
420
+ console.log()
421
+ console.log(pc.yellow(" Railway: nao autenticado (railway login)"))
422
+ console.log(pc.dim(" Configure RAILWAY_TOKEN manualmente no .env"))
287
423
  }
288
424
  }
289
425
 
@@ -302,9 +438,11 @@ async function promptConfig(initialName?: string): Promise<ProjectConfig | symbo
302
438
  github,
303
439
  githubOrg,
304
440
  githubDescription,
441
+ contractNumber,
305
442
  resendApiKey,
306
443
  resendFromEmail,
307
444
  contactEmail,
445
+ railwayProject,
308
446
  railwayToken,
309
447
  stagingUrl,
310
448
  productionUrl,
@@ -376,10 +514,16 @@ async function createProject(config: ProjectConfig) {
376
514
 
377
515
  // Create repo with description (private by default for client projects)
378
516
  const visibility = config.githubOrg === "fast-by-nimbuslab" ? "--private" : "--public"
379
- await $`gh repo create ${repoName} ${visibility} --description ${config.githubDescription} --source . --remote origin --push`.cwd(cwd).quiet()
380
517
 
381
- // Push all branches
518
+ // Criar repo sem push automático
519
+ await $`gh repo create ${repoName} ${visibility} --description ${config.githubDescription} --source . --remote origin`.cwd(cwd).quiet()
520
+
521
+ // Push todas as branches na ordem correta: main -> staging -> develop
522
+ await $`git checkout main`.cwd(cwd).quiet()
523
+ await $`git push -u origin main`.cwd(cwd).quiet()
524
+ await $`git checkout staging`.cwd(cwd).quiet()
382
525
  await $`git push -u origin staging`.cwd(cwd).quiet()
526
+ await $`git checkout develop`.cwd(cwd).quiet()
383
527
  await $`git push -u origin develop`.cwd(cwd).quiet()
384
528
 
385
529
  s.stop(`GitHub: ${repoName} criado`)
@@ -390,6 +534,18 @@ async function createProject(config: ProjectConfig) {
390
534
  }
391
535
  }
392
536
 
537
+ // Railway link (se projeto foi selecionado/criado)
538
+ if (config.railwayProject) {
539
+ s.start(`Linkando Railway: ${config.railwayProject}...`)
540
+ try {
541
+ await $`railway link -p ${config.railwayProject}`.cwd(config.name).quiet()
542
+ s.stop(`Railway linkado: ${config.railwayProject}`)
543
+ } catch (error) {
544
+ s.stop("Erro ao linkar Railway")
545
+ console.log(pc.dim(" Execute manualmente: railway link"))
546
+ }
547
+ }
548
+
393
549
  // M23: Gerar .env se configs foram fornecidas
394
550
  if (config.resendApiKey || config.stagingUrl) {
395
551
  s.start("Gerando arquivo .env...")
@@ -434,11 +590,14 @@ function generateEnvFile(config: ProjectConfig): string {
434
590
  `CONTACT_EMAIL=${config.contactEmail || ""}`,
435
591
  ]
436
592
 
437
- // Railway token (se fornecido)
438
- if (config.railwayToken) {
593
+ // Railway (projeto e token)
594
+ if (config.railwayProject || config.railwayToken) {
439
595
  lines.push("")
440
596
  lines.push("# Railway")
441
- lines.push(`RAILWAY_TOKEN=${config.railwayToken}`)
597
+ if (config.railwayProject) {
598
+ lines.push(`# Projeto: ${config.railwayProject}`)
599
+ }
600
+ lines.push(`RAILWAY_TOKEN=${config.railwayToken || "# Configure com: railway link"}`)
442
601
  }
443
602
 
444
603
  // Fast+ specific vars
@@ -489,6 +648,9 @@ function showNextSteps(config: ProjectConfig) {
489
648
 
490
649
  if (config.type === "fast+") {
491
650
  console.log(pc.dim(" Dica: Para fast+, configure DATABASE_URL e BETTER_AUTH_SECRET no .env"))
651
+ if (!config.railwayToken) {
652
+ console.log(pc.dim(" Railway: Crie um projeto em https://railway.app/new"))
653
+ }
492
654
  console.log()
493
655
  }
494
656