@nimbuslab/cli 0.7.0 → 0.9.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/docs/CI-CD.md CHANGED
@@ -161,12 +161,21 @@ O npm suporta autenticacao via OIDC, eliminando necessidade de tokens.
161
161
 
162
162
  ### 2026-01-26: Publicacao falhando silenciosamente
163
163
 
164
- **Problema**: CI mostrava sucesso mas versao nao aparecia no npm.
164
+ **Problema**: CI mostrava sucesso (`+ @nimbuslab/cli@0.6.x`) mas versao nao aparecia no npm. Tarball retornava 404 com mensagem "Access token expired or revoked".
165
165
 
166
- **Causa**: OIDC Trusted Publishing falhando silenciosamente (config no npmjs.com ou bug do npm).
166
+ **Causa**: OIDC Trusted Publishing falhando silenciosamente. Os tarballs eram criados mas ficavam inacessiveis.
167
167
 
168
168
  **Solucao**: Adicionar `NODE_AUTH_TOKEN` como fallback junto com OIDC.
169
169
 
170
+ ```yaml
171
+ - name: Publish to npm
172
+ run: npm publish --access public
173
+ env:
174
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
175
+ ```
176
+
177
+ **Importante**: Mesmo com OIDC configurado, SEMPRE manter o `NODE_AUTH_TOKEN` como fallback. O token deve ser do tipo "Granular Access Token" (nao "Classic", que foi deprecado em dez/2025).
178
+
170
179
  ---
171
180
 
172
181
  *Ultima atualizacao: 2026-01-26*
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nimbuslab/cli",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "CLI para criar projetos nimbuslab",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,210 @@
1
+ import * as p from "@clack/prompts"
2
+ import pc from "picocolors"
3
+ import { existsSync, readFileSync } from "node:fs"
4
+ import { join } from "node:path"
5
+
6
+ interface AnalysisResult {
7
+ name: string
8
+ version: string
9
+ framework: string | null
10
+ frameworkVersion: string | null
11
+ styling: string[]
12
+ packageManager: string
13
+ monorepo: string | null
14
+ auth: string | null
15
+ database: string | null
16
+ typescript: boolean
17
+ dependencies: Record<string, string>
18
+ devDependencies: Record<string, string>
19
+ recommendations: string[]
20
+ }
21
+
22
+ function detectPackageManager(dir: string): string {
23
+ if (existsSync(join(dir, "bun.lockb"))) return "bun"
24
+ if (existsSync(join(dir, "pnpm-lock.yaml"))) return "pnpm"
25
+ if (existsSync(join(dir, "yarn.lock"))) return "yarn"
26
+ if (existsSync(join(dir, "package-lock.json"))) return "npm"
27
+ return "unknown"
28
+ }
29
+
30
+ function detectMonorepo(dir: string, pkg: any): string | null {
31
+ if (existsSync(join(dir, "turbo.json"))) return "turborepo"
32
+ if (existsSync(join(dir, "nx.json"))) return "nx"
33
+ if (existsSync(join(dir, "lerna.json"))) return "lerna"
34
+ if (pkg.workspaces) return "workspaces"
35
+ return null
36
+ }
37
+
38
+ function detectFramework(deps: Record<string, string>): { name: string | null; version: string | null } {
39
+ if (deps["next"]) return { name: "nextjs", version: deps["next"] }
40
+ if (deps["@angular/core"]) return { name: "angular", version: deps["@angular/core"] }
41
+ if (deps["vue"]) return { name: "vue", version: deps["vue"] }
42
+ if (deps["svelte"]) return { name: "svelte", version: deps["svelte"] }
43
+ if (deps["react"] && !deps["next"]) return { name: "react", version: deps["react"] }
44
+ return { name: null, version: null }
45
+ }
46
+
47
+ function detectStyling(deps: Record<string, string>, devDeps: Record<string, string>): string[] {
48
+ const styling: string[] = []
49
+ const allDeps = { ...deps, ...devDeps }
50
+
51
+ if (allDeps["tailwindcss"]) styling.push(`tailwind@${allDeps["tailwindcss"]}`)
52
+ if (allDeps["styled-components"]) styling.push("styled-components")
53
+ if (allDeps["@emotion/react"]) styling.push("emotion")
54
+ if (allDeps["sass"]) styling.push("sass")
55
+ if (allDeps["less"]) styling.push("less")
56
+
57
+ return styling.length > 0 ? styling : ["css"]
58
+ }
59
+
60
+ function detectAuth(deps: Record<string, string>): string | null {
61
+ if (deps["better-auth"]) return "better-auth"
62
+ if (deps["next-auth"]) return "next-auth"
63
+ if (deps["@clerk/nextjs"]) return "clerk"
64
+ if (deps["@auth0/nextjs-auth0"]) return "auth0"
65
+ if (deps["@supabase/supabase-js"]) return "supabase"
66
+ return null
67
+ }
68
+
69
+ function detectDatabase(deps: Record<string, string>): string | null {
70
+ if (deps["drizzle-orm"]) return "drizzle"
71
+ if (deps["@prisma/client"]) return "prisma"
72
+ if (deps["typeorm"]) return "typeorm"
73
+ if (deps["mongoose"]) return "mongoose"
74
+ if (deps["pg"]) return "pg"
75
+ if (deps["mysql2"]) return "mysql"
76
+ return null
77
+ }
78
+
79
+ function generateRecommendations(result: AnalysisResult): string[] {
80
+ const recs: string[] = []
81
+
82
+ // Package manager
83
+ if (result.packageManager !== "bun") {
84
+ recs.push(`Migrar ${result.packageManager} -> bun (nimbus codemod bun)`)
85
+ }
86
+
87
+ // Framework version
88
+ if (result.framework === "nextjs" && result.frameworkVersion) {
89
+ const majorVersion = parseInt(result.frameworkVersion.replace(/[^0-9]/g, "").slice(0, 2))
90
+ if (majorVersion < 16) {
91
+ recs.push(`Atualizar Next.js ${result.frameworkVersion} -> 16 (nimbus upgrade next)`)
92
+ }
93
+ }
94
+
95
+ // Tailwind
96
+ const tailwind = result.styling.find(s => s.startsWith("tailwind"))
97
+ if (tailwind) {
98
+ const version = tailwind.split("@")[1] || ""
99
+ if (version.startsWith("3")) {
100
+ recs.push(`Atualizar Tailwind 3 -> 4 (nimbus upgrade tailwind)`)
101
+ }
102
+ } else if (!result.styling.includes("tailwind")) {
103
+ recs.push(`Considerar adicionar Tailwind CSS (nimbus add tailwind)`)
104
+ }
105
+
106
+ // React version
107
+ if (result.dependencies["react"]) {
108
+ const reactVersion = result.dependencies["react"]
109
+ if (reactVersion.startsWith("18") || reactVersion.startsWith("^18")) {
110
+ recs.push(`Atualizar React 18 -> 19 (nimbus upgrade react)`)
111
+ }
112
+ }
113
+
114
+ // Database
115
+ if (result.database === "prisma") {
116
+ recs.push(`Considerar migrar Prisma -> Drizzle (nimbus codemod drizzle)`)
117
+ } else if (!result.database && result.framework === "nextjs") {
118
+ recs.push(`Considerar adicionar banco de dados (nimbus add db)`)
119
+ }
120
+
121
+ // Auth
122
+ if (!result.auth && result.framework === "nextjs") {
123
+ recs.push(`Considerar adicionar autenticacao (nimbus add auth)`)
124
+ } else if (result.auth === "next-auth") {
125
+ recs.push(`Considerar migrar NextAuth -> Better Auth`)
126
+ }
127
+
128
+ // Monorepo
129
+ if (result.monorepo === "workspaces" && !result.monorepo) {
130
+ recs.push(`Considerar usar Turborepo para monorepo (nimbus add monorepo)`)
131
+ }
132
+
133
+ return recs
134
+ }
135
+
136
+ export async function analyze(args: string[]) {
137
+ const targetDir = args[0] || "."
138
+ const absoluteDir = targetDir.startsWith("/") ? targetDir : join(process.cwd(), targetDir)
139
+
140
+ console.log()
141
+ console.log(pc.cyan(" Analisando projeto..."))
142
+ console.log()
143
+
144
+ // Check if package.json exists
145
+ const pkgPath = join(absoluteDir, "package.json")
146
+ if (!existsSync(pkgPath)) {
147
+ console.log(pc.red(" Erro: package.json nao encontrado"))
148
+ console.log(pc.dim(` Diretorio: ${absoluteDir}`))
149
+ process.exit(1)
150
+ }
151
+
152
+ // Read package.json
153
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"))
154
+ const deps = pkg.dependencies || {}
155
+ const devDeps = pkg.devDependencies || {}
156
+
157
+ // Detect everything
158
+ const framework = detectFramework(deps)
159
+ const result: AnalysisResult = {
160
+ name: pkg.name || "unknown",
161
+ version: pkg.version || "0.0.0",
162
+ framework: framework.name,
163
+ frameworkVersion: framework.version,
164
+ styling: detectStyling(deps, devDeps),
165
+ packageManager: detectPackageManager(absoluteDir),
166
+ monorepo: detectMonorepo(absoluteDir, pkg),
167
+ auth: detectAuth(deps),
168
+ database: detectDatabase(deps),
169
+ typescript: existsSync(join(absoluteDir, "tsconfig.json")),
170
+ dependencies: deps,
171
+ devDependencies: devDeps,
172
+ recommendations: [],
173
+ }
174
+
175
+ // Generate recommendations
176
+ result.recommendations = generateRecommendations(result)
177
+
178
+ // Display results
179
+ console.log(pc.bold(" Projeto: ") + pc.cyan(result.name) + pc.dim(` v${result.version}`))
180
+ console.log()
181
+
182
+ console.log(pc.bold(" Stack Detectada:"))
183
+ console.log(` Framework: ${result.framework ? pc.green(result.framework + "@" + result.frameworkVersion) : pc.dim("nenhum")}`)
184
+ console.log(` Styling: ${result.styling.map(s => pc.green(s)).join(", ")}`)
185
+ console.log(` Package Manager: ${result.packageManager === "bun" ? pc.green(result.packageManager) : pc.yellow(result.packageManager)}`)
186
+ console.log(` TypeScript: ${result.typescript ? pc.green("sim") : pc.dim("nao")}`)
187
+ console.log(` Monorepo: ${result.monorepo ? pc.green(result.monorepo) : pc.dim("nao")}`)
188
+ console.log(` Auth: ${result.auth ? pc.green(result.auth) : pc.dim("nenhum")}`)
189
+ console.log(` Database: ${result.database ? pc.green(result.database) : pc.dim("nenhum")}`)
190
+ console.log()
191
+
192
+ if (result.recommendations.length > 0) {
193
+ console.log(pc.bold(" Recomendacoes:"))
194
+ result.recommendations.forEach((rec, i) => {
195
+ console.log(pc.yellow(` ${i + 1}. ${rec}`))
196
+ })
197
+ console.log()
198
+ } else {
199
+ console.log(pc.green(" Projeto ja esta na stack recomendada!"))
200
+ console.log()
201
+ }
202
+
203
+ // JSON output option
204
+ if (args.includes("--json")) {
205
+ console.log(pc.dim(" JSON:"))
206
+ console.log(JSON.stringify(result, null, 2))
207
+ }
208
+
209
+ return result
210
+ }