@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/MIGRATION-ROADMAP.md +201 -0
- package/dist/index.js +801 -69
- package/docs/CI-CD.md +11 -2
- package/package.json +1 -1
- package/src/commands/analyze.ts +210 -0
- package/src/commands/create.ts +432 -59
- package/src/commands/upgrade.ts +251 -0
- package/src/index.ts +19 -3
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
|
|
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
|
@@ -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
|
+
}
|