mdsmith 1.1.1 → 1.2.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/README-MDSMITH.md +21 -10
- package/bin/index.js +717 -110
- package/mdsmith.config.json +2 -1
- package/package.json +4 -1
package/README-MDSMITH.md
CHANGED
|
@@ -3,16 +3,14 @@
|
|
|
3
3
|
|
|
4
4
|
CLI para gerar READMEs e arquivos Markdown
|
|
5
5
|
|
|
6
|
-
## 🔖
|
|
7
|
-
- **
|
|
8
|
-
- **
|
|
9
|
-
- **
|
|
6
|
+
## 🔖 Informações do Projeto
|
|
7
|
+
- **Versão:** 1.1.2
|
|
8
|
+
- **Gerenciador de Pacotes:** npm
|
|
9
|
+
- **Tipo:** Node.js Project
|
|
10
10
|
|
|
11
|
-
## 🚀 Dependencies
|
|
12
|
-
No dependencies found.
|
|
13
11
|
|
|
14
12
|
|
|
15
|
-
## 📁
|
|
13
|
+
## 📁 Estrutura do Projeto
|
|
16
14
|
```
|
|
17
15
|
📂 bin
|
|
18
16
|
📄 beta.js
|
|
@@ -24,8 +22,21 @@ No dependencies found.
|
|
|
24
22
|
|
|
25
23
|
```
|
|
26
24
|
|
|
27
|
-
## 📜 Available Scripts
|
|
28
|
-
No scripts available.
|
|
29
25
|
|
|
26
|
+
## 🚀 Getting Started
|
|
30
27
|
|
|
31
|
-
|
|
28
|
+
```bash
|
|
29
|
+
npm install
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
Gerado por **mdSmith**
|
package/bin/index.js
CHANGED
|
@@ -5,18 +5,86 @@
|
|
|
5
5
|
// npx mdsmith --tree # show project structure
|
|
6
6
|
// npx mdsmith --deps # list dependencies
|
|
7
7
|
// npx mdsmith --readme # generate README
|
|
8
|
+
// npx mdsmith --analyze # project analyze
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
const TECH_MAP = {
|
|
12
|
+
// Frameworks
|
|
13
|
+
express: "Express",
|
|
14
|
+
fastify: "Fastify",
|
|
15
|
+
koa: "Koa",
|
|
16
|
+
hapi: "Hapi",
|
|
17
|
+
"@nestjs/core": "NestJS",
|
|
18
|
+
|
|
19
|
+
// ORMs
|
|
20
|
+
prisma: "Prisma",
|
|
21
|
+
"@prisma/client": "Prisma",
|
|
22
|
+
sequelize: "Sequelize",
|
|
23
|
+
mongoose: "Mongoose",
|
|
24
|
+
typeorm: "TypeORM",
|
|
25
|
+
drizzle: "Drizzle ORM",
|
|
26
|
+
|
|
27
|
+
// Databases
|
|
28
|
+
pg: "PostgreSQL",
|
|
29
|
+
mysql: "MySQL",
|
|
30
|
+
mysql2: "MySQL",
|
|
31
|
+
sqlite3: "SQLite",
|
|
32
|
+
mongodb: "MongoDB",
|
|
33
|
+
|
|
34
|
+
// Auth
|
|
35
|
+
jsonwebtoken: "JWT",
|
|
36
|
+
passport: "Passport",
|
|
37
|
+
|
|
38
|
+
// AI
|
|
39
|
+
openai: "OpenAI",
|
|
40
|
+
langchain: "LangChain",
|
|
41
|
+
|
|
42
|
+
// Infra / Tools
|
|
43
|
+
docker: "Docker",
|
|
44
|
+
"swagger-ui-express": "Swagger",
|
|
45
|
+
puppeteer: "Puppeteer",
|
|
46
|
+
nodemailer: "Nodemailer"
|
|
47
|
+
}
|
|
48
|
+
|
|
8
49
|
|
|
50
|
+
const i18n = {
|
|
51
|
+
en: {
|
|
52
|
+
projectInfo: "Project Info",
|
|
53
|
+
dependencies: "Dependencies",
|
|
54
|
+
projectStructure: "Project Structure",
|
|
55
|
+
availableScripts: "Available Scripts",
|
|
56
|
+
version: "Version",
|
|
57
|
+
packageManager: "Package Manager",
|
|
58
|
+
type: "Type",
|
|
59
|
+
generatedBy: "Generated by"
|
|
60
|
+
},
|
|
61
|
+
pt: {
|
|
62
|
+
projectInfo: "Informações do Projeto",
|
|
63
|
+
dependencies: "Dependências",
|
|
64
|
+
projectStructure: "Estrutura do Projeto",
|
|
65
|
+
availableScripts: "Scripts Disponíveis",
|
|
66
|
+
version: "Versão",
|
|
67
|
+
packageManager: "Gerenciador de Pacotes",
|
|
68
|
+
type: "Tipo",
|
|
69
|
+
generatedBy: "Gerado por"
|
|
70
|
+
},
|
|
71
|
+
es: {
|
|
72
|
+
projectInfo: "Información del Proyecto",
|
|
73
|
+
dependencies: "Dependencias",
|
|
74
|
+
projectStructure: "Estructura del Proyecto",
|
|
75
|
+
availableScripts: "Scripts Disponibles",
|
|
76
|
+
version: "Versión",
|
|
77
|
+
packageManager: "Administrador de Paquetes",
|
|
78
|
+
type: "Tipo",
|
|
79
|
+
generatedBy: "Generado por"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
9
82
|
|
|
10
83
|
const fs = require("fs");
|
|
11
84
|
|
|
12
85
|
const path = require("path");
|
|
13
86
|
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
const rl = readline.createInterface({
|
|
17
|
-
input: process.stdin,
|
|
18
|
-
output: process.stdout
|
|
19
|
-
});
|
|
87
|
+
const prompts = require('prompts');
|
|
20
88
|
|
|
21
89
|
const projectRoot = process.cwd();
|
|
22
90
|
|
|
@@ -34,7 +102,49 @@ const args = process.argv.slice(2)
|
|
|
34
102
|
|
|
35
103
|
const defaultConfig = {
|
|
36
104
|
ignore: ["node_modules", ".git", ".vscode"],
|
|
37
|
-
depth: Infinity
|
|
105
|
+
depth: Infinity,
|
|
106
|
+
emojis: true
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function healthChecks(packageJson, projectRoot, analysis) {
|
|
110
|
+
const checks = []
|
|
111
|
+
|
|
112
|
+
if (!analysis.testing) {
|
|
113
|
+
checks.push({
|
|
114
|
+
type: "warning",
|
|
115
|
+
message: "No test runner configured."
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!analysis.linting) {
|
|
120
|
+
checks.push({
|
|
121
|
+
type: "info",
|
|
122
|
+
message: "No ESLint detected."
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!analysis.formatting) {
|
|
127
|
+
checks.push({
|
|
128
|
+
type: "info",
|
|
129
|
+
message: "No Prettier detected."
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (!packageJson.engines?.node) {
|
|
134
|
+
checks.push({
|
|
135
|
+
type: "critical",
|
|
136
|
+
message: "Node version not defined in package.json engines."
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (!fs.existsSync(path.join(projectRoot, "Dockerfile"))) {
|
|
141
|
+
checks.push({
|
|
142
|
+
type: "info",
|
|
143
|
+
message: "No Dockerfile found."
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return checks
|
|
38
148
|
}
|
|
39
149
|
|
|
40
150
|
|
|
@@ -77,89 +187,69 @@ function scanDir(dirPath, padding, config) {
|
|
|
77
187
|
}
|
|
78
188
|
}
|
|
79
189
|
} else {
|
|
80
|
-
|
|
190
|
+
if (config.ignore.includes(entry.name)){
|
|
191
|
+
continue
|
|
192
|
+
} else{
|
|
193
|
+
treeContent += `${" ".repeat(padding*2)} 📄 ${entry.name}\n`
|
|
194
|
+
}
|
|
81
195
|
}
|
|
82
196
|
}
|
|
83
197
|
|
|
84
198
|
return treeContent
|
|
85
199
|
}
|
|
86
200
|
|
|
87
|
-
function
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
Project name not found in package.json.
|
|
101
|
-
Would you like to provide one? (y/n):
|
|
102
|
-
`)
|
|
103
|
-
resposta = resposta.trim().toLowerCase()
|
|
104
|
-
|
|
105
|
-
if(resposta == "y" || resposta == "n"){
|
|
106
|
-
respostaValida = true
|
|
107
|
-
}else {
|
|
108
|
-
console.log("Please type y or n.")
|
|
109
|
-
}
|
|
110
|
-
}
|
|
201
|
+
async function buscarNome(nome) {
|
|
202
|
+
if (!nome) {
|
|
203
|
+
const confirm = await prompts({
|
|
204
|
+
type: 'confirm',
|
|
205
|
+
name: 'value',
|
|
206
|
+
message: 'Project name not found in package.json. Provide one?',
|
|
207
|
+
initial: true
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
if (!confirm.value) {
|
|
211
|
+
console.log("\nProject name will be set as Unnamed Project.\n");
|
|
212
|
+
return "Unnamed Project";
|
|
213
|
+
}
|
|
111
214
|
|
|
215
|
+
const response = await prompts({
|
|
216
|
+
type: 'text',
|
|
217
|
+
name: 'value',
|
|
218
|
+
message: 'Enter the project name:'
|
|
219
|
+
});
|
|
112
220
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}if(resposta == "y"){
|
|
117
|
-
let nome = await perguntar("Enter the project name: ")
|
|
118
|
-
console.log(`Project name set to "${nome}".`)
|
|
119
|
-
return nome
|
|
120
|
-
}else{
|
|
121
|
-
console.error("Failed to generate project name.")
|
|
122
|
-
process.exit(1)
|
|
123
|
-
}
|
|
124
|
-
}
|
|
221
|
+
console.log(`\nProject name set to "${response.value}".\n`);
|
|
222
|
+
return response.value || "Unnamed Project";
|
|
223
|
+
}
|
|
125
224
|
|
|
126
|
-
|
|
225
|
+
return nome;
|
|
127
226
|
}
|
|
128
227
|
|
|
129
|
-
async function buscarDescricao(descricao){
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
respostaValida = true
|
|
143
|
-
}else {
|
|
144
|
-
console.log("Please type y or n.")
|
|
145
|
-
}
|
|
146
|
-
}
|
|
228
|
+
async function buscarDescricao(descricao) {
|
|
229
|
+
if (!descricao) {
|
|
230
|
+
const confirm = await prompts({
|
|
231
|
+
type: 'confirm',
|
|
232
|
+
name: 'value',
|
|
233
|
+
message: 'Project description not found. Provide one?',
|
|
234
|
+
initial: true
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
if (!confirm.value) {
|
|
238
|
+
console.log("\nDescription placeholder added.\n");
|
|
239
|
+
return "*Project description goes here*";
|
|
240
|
+
}
|
|
147
241
|
|
|
242
|
+
const response = await prompts({
|
|
243
|
+
type: 'text',
|
|
244
|
+
name: 'value',
|
|
245
|
+
message: 'Enter the project description:'
|
|
246
|
+
});
|
|
148
247
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}if(resposta == "y"){
|
|
153
|
-
let descricao = await perguntar("Enter the project description: ")
|
|
154
|
-
console.log("\nDescription saved.\n")
|
|
155
|
-
return descricao
|
|
156
|
-
}else{
|
|
157
|
-
console.error("Failed to generate project description.")
|
|
158
|
-
process.exit(1)
|
|
159
|
-
}
|
|
160
|
-
}
|
|
248
|
+
console.log("\nDescription saved.\n");
|
|
249
|
+
return response.value || "*Project description goes here*";
|
|
250
|
+
}
|
|
161
251
|
|
|
162
|
-
|
|
252
|
+
return descricao;
|
|
163
253
|
}
|
|
164
254
|
|
|
165
255
|
function detectarTipoProjeto(packageJson) {
|
|
@@ -186,40 +276,330 @@ function formatScripts(scripts) {
|
|
|
186
276
|
.join("\n");
|
|
187
277
|
}
|
|
188
278
|
|
|
279
|
+
function detectarEnvVars(projectRoot) {
|
|
280
|
+
const envPath = path.join(projectRoot, ".env")
|
|
281
|
+
|
|
282
|
+
if (!fs.existsSync(envPath)) return []
|
|
283
|
+
|
|
284
|
+
const content = fs.readFileSync(envPath, "utf-8")
|
|
285
|
+
|
|
286
|
+
const vars = content
|
|
287
|
+
.split("\n")
|
|
288
|
+
.map(line => line.trim())
|
|
289
|
+
.filter(line =>
|
|
290
|
+
line &&
|
|
291
|
+
!line.startsWith("#") &&
|
|
292
|
+
line.includes("=")
|
|
293
|
+
)
|
|
294
|
+
.map(line => line.split("=")[0].trim())
|
|
295
|
+
|
|
296
|
+
return vars
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function gerarEnvSection(vars) {
|
|
300
|
+
if (!vars.length) return ""
|
|
301
|
+
|
|
302
|
+
let section = "## 🔑 Environment Variables\n\n"
|
|
303
|
+
section += "Create a `.env` file in the project root with the following variables:\n\n"
|
|
304
|
+
|
|
305
|
+
vars.forEach(v => {
|
|
306
|
+
section += `- ${v}\n`
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
return section + "\n"
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function detectarPorta(scanData) {
|
|
313
|
+
const filesToScan = [...scanData.jsFiles, ...scanData.tsFiles]
|
|
314
|
+
|
|
315
|
+
for (const filePath of filesToScan) {
|
|
316
|
+
try {
|
|
317
|
+
const content = fs.readFileSync(filePath, "utf-8")
|
|
318
|
+
|
|
319
|
+
// 🔎 procurar qualquer .listen(...)
|
|
320
|
+
const listenRegex = /\.listen\s*\(\s*([^)]+)\)/
|
|
321
|
+
const listenMatch = content.match(listenRegex)
|
|
322
|
+
|
|
323
|
+
if (!listenMatch) continue
|
|
324
|
+
|
|
325
|
+
const rawArgument = listenMatch[1].split(",")[0].trim()
|
|
326
|
+
|
|
327
|
+
// =============================
|
|
328
|
+
// ✅ 1️⃣ Número direto
|
|
329
|
+
// =============================
|
|
330
|
+
if (/^\d+$/.test(rawArgument)) {
|
|
331
|
+
return rawArgument
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// =============================
|
|
335
|
+
// ✅ 2️⃣ Fallback inline
|
|
336
|
+
// ex: process.env.PORT || 3000
|
|
337
|
+
// =============================
|
|
338
|
+
const inlineFallback = rawArgument.match(/\|\|\s*(\d+)/)
|
|
339
|
+
if (inlineFallback) {
|
|
340
|
+
return inlineFallback[1]
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// =============================
|
|
344
|
+
// ✅ 3️⃣ Variável
|
|
345
|
+
// ex: app.listen(port)
|
|
346
|
+
// =============================
|
|
347
|
+
const variableName = rawArgument.replace(/[^a-zA-Z0-9_]/g, "")
|
|
348
|
+
|
|
349
|
+
if (!variableName) continue
|
|
350
|
+
|
|
351
|
+
const variableRegex = new RegExp(
|
|
352
|
+
`(const|let|var)\\s+${variableName}\\s*=\\s*([^;]+)`
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
const variableMatch = content.match(variableRegex)
|
|
356
|
+
|
|
357
|
+
if (variableMatch) {
|
|
358
|
+
const variableValue = variableMatch[2]
|
|
359
|
+
|
|
360
|
+
// fallback dentro da variável
|
|
361
|
+
const fallbackMatch = variableValue.match(/\|\|\s*(\d+)/)
|
|
362
|
+
if (fallbackMatch) {
|
|
363
|
+
return fallbackMatch[1]
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// número direto dentro da variável
|
|
367
|
+
const numberMatch = variableValue.match(/\d+/)
|
|
368
|
+
if (numberMatch) {
|
|
369
|
+
return numberMatch[0]
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// =============================
|
|
374
|
+
// ✅ 4️⃣ process.env.PORT puro
|
|
375
|
+
// =============================
|
|
376
|
+
if (rawArgument.includes("process.env.PORT")) {
|
|
377
|
+
return "process.env.PORT"
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
} catch (err) {
|
|
381
|
+
// evita quebrar o CLI se algum arquivo falhar
|
|
382
|
+
continue
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return null
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function scanProjectFiles(projectRoot) {
|
|
390
|
+
const result = {
|
|
391
|
+
allFiles: [],
|
|
392
|
+
jsFiles: [],
|
|
393
|
+
tsFiles: [],
|
|
394
|
+
envFiles: [],
|
|
395
|
+
hasDockerfile: false,
|
|
396
|
+
hasPrisma: false,
|
|
397
|
+
hasSrcFolder: false,
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function walk(dir) {
|
|
401
|
+
const files = fs.readdirSync(dir)
|
|
402
|
+
|
|
403
|
+
for (const file of files) {
|
|
404
|
+
const fullPath = path.join(dir, file)
|
|
405
|
+
|
|
406
|
+
// ignorar node_modules e .git
|
|
407
|
+
if (
|
|
408
|
+
fullPath.includes("node_modules") ||
|
|
409
|
+
fullPath.includes(".git")
|
|
410
|
+
) continue
|
|
411
|
+
|
|
412
|
+
const stat = fs.statSync(fullPath)
|
|
413
|
+
|
|
414
|
+
if (stat.isDirectory()) {
|
|
415
|
+
if (file === "src") {
|
|
416
|
+
result.hasSrcFolder = true
|
|
417
|
+
}
|
|
418
|
+
walk(fullPath)
|
|
419
|
+
} else {
|
|
420
|
+
result.allFiles.push(fullPath)
|
|
421
|
+
|
|
422
|
+
if (file.endsWith(".js")) result.jsFiles.push(fullPath)
|
|
423
|
+
if (file.endsWith(".ts")) result.tsFiles.push(fullPath)
|
|
424
|
+
if (file.startsWith(".env")) result.envFiles.push(fullPath)
|
|
425
|
+
|
|
426
|
+
if (file === "Dockerfile") result.hasDockerfile = true
|
|
427
|
+
if (fullPath.includes("prisma")) result.hasPrisma = true
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
walk(projectRoot)
|
|
433
|
+
|
|
434
|
+
return result
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function gerarGettingStarted(packageManager = "npm", scripts = {}, hasPrisma) {
|
|
438
|
+
const lines = []
|
|
439
|
+
|
|
440
|
+
lines.push(
|
|
441
|
+
packageManager === "npm"
|
|
442
|
+
? "npm install"
|
|
443
|
+
: `${packageManager} install`
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
if (hasPrisma) {
|
|
447
|
+
lines.push("npx prisma migrate dev")
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (scripts.dev) {
|
|
451
|
+
lines.push(getRunCommand("dev", packageManager))
|
|
452
|
+
} else if (scripts.start) {
|
|
453
|
+
lines.push(getRunCommand("start", packageManager))
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
return `
|
|
457
|
+
## 🚀 Getting Started
|
|
458
|
+
|
|
459
|
+
\`\`\`bash
|
|
460
|
+
${lines.join("\n")}
|
|
461
|
+
\`\`\`
|
|
462
|
+
|
|
463
|
+
`
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
function getRunCommand(script, packageManager) {
|
|
468
|
+
if (packageManager === "yarn") return `yarn ${script}`
|
|
469
|
+
if (packageManager === "pnpm") return `pnpm ${script}`
|
|
470
|
+
return `npm run ${script}`
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
async function generateReadme(packageJson, tree, config){
|
|
476
|
+
|
|
477
|
+
const deps = {
|
|
478
|
+
...packageJson.dependencies,
|
|
479
|
+
...packageJson.devDependencies
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const techStack = detectarTechStack(deps)
|
|
483
|
+
const techSection = gerarTechStackSection(techStack)
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
const envVars = detectarEnvVars(projectRoot)
|
|
489
|
+
const envSection = gerarEnvSection(envVars)
|
|
490
|
+
|
|
491
|
+
const nomeProjeto = await buscarNome(packageJson.name);
|
|
492
|
+
const descricaoProjeto = await buscarDescricao(packageJson.description);
|
|
493
|
+
const idioma = await escolherLingua()
|
|
494
|
+
const t = i18n[idioma] || i18n.en
|
|
495
|
+
|
|
496
|
+
const packageManager = detectarPackageManager(projectRoot)
|
|
497
|
+
|
|
498
|
+
const scanData = scanProjectFiles(projectRoot)
|
|
499
|
+
const port = detectarPorta(scanData)
|
|
500
|
+
|
|
501
|
+
const commands = analisarScripts(packageJson.scripts, packageManager)
|
|
502
|
+
|
|
503
|
+
const hasPrisma = detectarPrisma(projectRoot, packageJson)
|
|
504
|
+
const dbSection = hasPrisma ? gerarDatabaseSection(packageManager) : ""
|
|
505
|
+
|
|
506
|
+
const gettingStartedCommands = gerarGettingStarted(
|
|
507
|
+
packageManager,
|
|
508
|
+
packageJson.scripts,
|
|
509
|
+
hasPrisma
|
|
510
|
+
)
|
|
189
511
|
|
|
190
|
-
async function generateReadme(packageJson, tree){
|
|
191
512
|
|
|
192
|
-
const nomeProjeto = await buscarNome(packageJson.name);
|
|
193
|
-
const descricaoProjeto = await buscarDescricao(packageJson.description);
|
|
194
513
|
|
|
195
514
|
const content = `
|
|
196
515
|
# ${nomeProjeto}
|
|
197
516
|
|
|
198
517
|
${descricaoProjeto}
|
|
199
518
|
|
|
200
|
-
## 🔖
|
|
201
|
-
-
|
|
202
|
-
-
|
|
203
|
-
-
|
|
519
|
+
## ${config.emojis ? "🔖" : ""} ${t.projectInfo}
|
|
520
|
+
- **${t.version}:** ${packageJson.version || "Not specified"}
|
|
521
|
+
- **${t.packageManager}:** ${packageManager}
|
|
522
|
+
- **${t.type}:** ${detectarTipoProjeto(packageJson)}
|
|
204
523
|
|
|
205
|
-
|
|
206
|
-
${formatDependencies(packageJson.dependencies)}
|
|
524
|
+
${techSection}
|
|
207
525
|
|
|
208
|
-
## 📁
|
|
526
|
+
## ${config.emojis ? "📁" : ""} ${t.projectStructure}
|
|
209
527
|
\`\`\`
|
|
210
528
|
${tree}
|
|
211
529
|
\`\`\`
|
|
212
530
|
|
|
213
|
-
|
|
214
|
-
|
|
531
|
+
${gettingStartedCommands}
|
|
532
|
+
|
|
533
|
+
${envSection}
|
|
534
|
+
${dbSection}
|
|
535
|
+
|
|
536
|
+
${port ? `
|
|
537
|
+
## 🌐 Server
|
|
538
|
+
|
|
539
|
+
After starting, the server will run at:
|
|
540
|
+
|
|
541
|
+
http://localhost:${port}
|
|
542
|
+
|
|
543
|
+
` : ""}
|
|
544
|
+
|
|
215
545
|
|
|
216
|
-
|
|
546
|
+
---
|
|
547
|
+
|
|
548
|
+
${t.generatedBy} **mdSmith**
|
|
217
549
|
`
|
|
218
550
|
|
|
219
551
|
return content
|
|
220
552
|
|
|
221
553
|
}
|
|
222
554
|
|
|
555
|
+
function detectarPrisma(projectRoot, packageJson) {
|
|
556
|
+
const deps = {
|
|
557
|
+
...packageJson.dependencies,
|
|
558
|
+
...packageJson.devDependencies
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const hasPrismaDep = deps.prisma || deps["@prisma/client"]
|
|
562
|
+
const hasPrismaFolder = fs.existsSync(path.join(projectRoot, "prisma"))
|
|
563
|
+
|
|
564
|
+
return hasPrismaDep && hasPrismaFolder
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function gerarDatabaseSection(packageManager) {
|
|
568
|
+
let section = "## 🗄 Database Setup\n\n"
|
|
569
|
+
section += "Run Prisma migrations:\n\n"
|
|
570
|
+
section += "```bash\n"
|
|
571
|
+
|
|
572
|
+
if (packageManager === "npm") {
|
|
573
|
+
section += "npx prisma migrate dev\n"
|
|
574
|
+
section += "npx prisma generate\n"
|
|
575
|
+
} else {
|
|
576
|
+
section += `${packageManager} prisma migrate dev\n`
|
|
577
|
+
section += `${packageManager} prisma generate\n`
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
section += "```\n\n"
|
|
581
|
+
|
|
582
|
+
return section
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
async function escolherLingua() {
|
|
588
|
+
const response = await prompts({
|
|
589
|
+
type: 'select',
|
|
590
|
+
name: 'lang',
|
|
591
|
+
message: 'Choose README language',
|
|
592
|
+
choices: [
|
|
593
|
+
{ title: 'English', value: 'en' },
|
|
594
|
+
{ title: 'Português', value: 'pt' },
|
|
595
|
+
{ title: 'Español', value: 'es' }
|
|
596
|
+
],
|
|
597
|
+
initial: 0
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
return response.lang || 'en';
|
|
601
|
+
}
|
|
602
|
+
|
|
223
603
|
function loadConfig(projectRoot) {
|
|
224
604
|
const configPath = path.join(projectRoot, "mdsmith.config.json")
|
|
225
605
|
|
|
@@ -247,6 +627,209 @@ function loadConfig(projectRoot) {
|
|
|
247
627
|
}
|
|
248
628
|
}
|
|
249
629
|
|
|
630
|
+
function analisarProjeto(packageJson, projectRoot) {
|
|
631
|
+
const deps = {
|
|
632
|
+
...packageJson.dependencies,
|
|
633
|
+
...packageJson.devDependencies
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const analysis = {
|
|
637
|
+
frontend: deps.react ? "React" :
|
|
638
|
+
deps.next ? "Next.js" :
|
|
639
|
+
null,
|
|
640
|
+
|
|
641
|
+
backend: deps.express ? "Express" :
|
|
642
|
+
deps.fastify ? "Fastify" :
|
|
643
|
+
null,
|
|
644
|
+
|
|
645
|
+
language: deps.typescript ? "TypeScript" : "JavaScript",
|
|
646
|
+
|
|
647
|
+
bundler: deps.vite ? "Vite" :
|
|
648
|
+
deps.webpack ? "Webpack" :
|
|
649
|
+
null,
|
|
650
|
+
|
|
651
|
+
styling: deps.tailwindcss ? "TailwindCSS" :
|
|
652
|
+
deps.sass ? "Sass" :
|
|
653
|
+
null,
|
|
654
|
+
|
|
655
|
+
database: deps.mongoose ? "MongoDB" :
|
|
656
|
+
deps.prisma ? "Prisma" :
|
|
657
|
+
deps.pg ? "PostgreSQL" :
|
|
658
|
+
null,
|
|
659
|
+
|
|
660
|
+
testing: deps.jest ? "Jest" :
|
|
661
|
+
deps.vitest ? "Vitest" :
|
|
662
|
+
null,
|
|
663
|
+
|
|
664
|
+
linting: deps.eslint ? "ESLint" : null,
|
|
665
|
+
|
|
666
|
+
formatting: deps.prettier ? "Prettier" : null,
|
|
667
|
+
|
|
668
|
+
node: detectarNodeVersion(projectRoot, packageJson)
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if (analysis.frontend && analysis.backend) {
|
|
672
|
+
analysis.architecture = "Fullstack"
|
|
673
|
+
} else if (analysis.frontend) {
|
|
674
|
+
analysis.architecture = "Frontend"
|
|
675
|
+
} else if (analysis.backend) {
|
|
676
|
+
analysis.architecture = "Backend"
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
return analysis
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
function detectarPackageManager(projectRoot) {
|
|
683
|
+
if (fs.existsSync(path.join(projectRoot, "pnpm-lock.yaml"))) {
|
|
684
|
+
return "pnpm"
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
if (fs.existsSync(path.join(projectRoot, "yarn.lock"))) {
|
|
688
|
+
return "yarn"
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
if (fs.existsSync(path.join(projectRoot, "package-lock.json"))) {
|
|
692
|
+
return "npm"
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
return "npm"
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
function analisarScripts(scripts = {}, packageManager) {
|
|
699
|
+
const commands = {
|
|
700
|
+
install: packageManager === "npm"
|
|
701
|
+
? "npm install"
|
|
702
|
+
: `${packageManager} install`
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
if (scripts.dev) {
|
|
706
|
+
commands.dev = `${packageManager} run dev`
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
if (scripts.build) {
|
|
710
|
+
commands.build = `${packageManager} run build`
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
if (scripts.start) {
|
|
714
|
+
commands.start = packageManager === "npm"
|
|
715
|
+
? "npm start"
|
|
716
|
+
: `${packageManager} start`
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
if (scripts.preview) {
|
|
720
|
+
commands.preview = `${packageManager} run preview`
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
return commands
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
function diagnosticarExecucao(commands) {
|
|
727
|
+
const warnings = []
|
|
728
|
+
|
|
729
|
+
if (!commands.dev && !commands.start) {
|
|
730
|
+
warnings.push("No development or start script found.")
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
if (commands.build && !commands.start) {
|
|
734
|
+
warnings.push("Build script found but no start script for production.")
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
return warnings
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
function detectarNodeVersion(projectRoot, packageJson) {
|
|
741
|
+
if (packageJson.engines && packageJson.engines.node) {
|
|
742
|
+
return packageJson.engines.node
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
const nvmrcPath = path.join(projectRoot, ".nvmrc")
|
|
746
|
+
if (fs.existsSync(nvmrcPath)) {
|
|
747
|
+
return fs.readFileSync(nvmrcPath, "utf-8").trim()
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
const nodeVersionPath = path.join(projectRoot, ".node-version")
|
|
751
|
+
if (fs.existsSync(nodeVersionPath)) {
|
|
752
|
+
return fs.readFileSync(nodeVersionPath, "utf-8").trim()
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
return process.version
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
function detectarTechStack(deps = {}) {
|
|
759
|
+
const stack = new Set()
|
|
760
|
+
|
|
761
|
+
for (const dep in deps) {
|
|
762
|
+
if (TECH_MAP[dep]) {
|
|
763
|
+
stack.add(TECH_MAP[dep])
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
return Array.from(stack).sort()
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
function gerarTechStackSection(stack) {
|
|
771
|
+
if (!stack.length) return ""
|
|
772
|
+
|
|
773
|
+
let section = "## 🛠 Tech Stack\n\n"
|
|
774
|
+
|
|
775
|
+
stack.forEach(tech => {
|
|
776
|
+
section += `- ${tech}\n`
|
|
777
|
+
})
|
|
778
|
+
|
|
779
|
+
return section + "\n"
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
|
|
783
|
+
function formatarAnalise(analysis) {
|
|
784
|
+
console.log(`
|
|
785
|
+
Project Analysis
|
|
786
|
+
────────────────────────
|
|
787
|
+
`)
|
|
788
|
+
|
|
789
|
+
const entries = Object.entries(analysis)
|
|
790
|
+
|
|
791
|
+
for (const [key, value] of entries) {
|
|
792
|
+
if (value) {
|
|
793
|
+
const label = key.charAt(0).toUpperCase() + key.slice(1)
|
|
794
|
+
console.log(`✓ ${label}: ${value}`)
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
console.log("")
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
function formatarExecucao(commands, warnings) {
|
|
802
|
+
console.log(`Project Execution Strategy
|
|
803
|
+
────────────────────────`)
|
|
804
|
+
|
|
805
|
+
console.log(`Install:`)
|
|
806
|
+
console.log(` ${commands.install}\n`)
|
|
807
|
+
|
|
808
|
+
if (commands.dev) {
|
|
809
|
+
console.log(`Development:`)
|
|
810
|
+
console.log(` ${commands.dev}\n`)
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
if (commands.build && commands.start) {
|
|
814
|
+
console.log(`Production:`)
|
|
815
|
+
console.log(` ${commands.build}`)
|
|
816
|
+
console.log(` ${commands.start}\n`)
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
if (commands.preview) {
|
|
820
|
+
console.log(`Preview:`)
|
|
821
|
+
console.log(` ${commands.preview}\n`)
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
if (warnings.length > 0) {
|
|
825
|
+
console.log(`Warnings:`)
|
|
826
|
+
warnings.forEach(w => console.log(` ⚠ ${w}`))
|
|
827
|
+
console.log()
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
|
|
250
833
|
async function main() {
|
|
251
834
|
const config = loadConfig(projectRoot)
|
|
252
835
|
|
|
@@ -286,6 +869,7 @@ mdSmith — Available Commands
|
|
|
286
869
|
--tree Show project structure
|
|
287
870
|
--deps List dependencies
|
|
288
871
|
--readme Generate README
|
|
872
|
+
--analyze Project analysis
|
|
289
873
|
`)
|
|
290
874
|
process.exit(0)
|
|
291
875
|
} else if (args.includes("--tree")){
|
|
@@ -295,16 +879,45 @@ Project Structure
|
|
|
295
879
|
`);
|
|
296
880
|
console.log(scanDir(projectRoot, 0, config))
|
|
297
881
|
process.exit(0)
|
|
882
|
+
} else if (args.includes("--analyze")){
|
|
883
|
+
const analysis = analisarProjeto(packageJson, projectRoot)
|
|
884
|
+
const packageManager = detectarPackageManager(projectRoot)
|
|
885
|
+
|
|
886
|
+
formatarAnalise(analysis)
|
|
887
|
+
|
|
888
|
+
const commands = analisarScripts(packageJson.scripts, packageManager)
|
|
889
|
+
const warnings = diagnosticarExecucao(commands)
|
|
890
|
+
|
|
891
|
+
formatarExecucao(commands, warnings)
|
|
892
|
+
|
|
893
|
+
const issues = healthChecks(packageJson, projectRoot, analysis)
|
|
894
|
+
|
|
895
|
+
if (issues.length > 0) {
|
|
896
|
+
console.log("Health Checks")
|
|
897
|
+
console.log("────────────────────────")
|
|
898
|
+
issues.forEach(issue => {
|
|
899
|
+
let icon = "⚠"
|
|
900
|
+
|
|
901
|
+
if (issue.type === "critical") icon = "❌"
|
|
902
|
+
if (issue.type === "info") icon = "ℹ"
|
|
903
|
+
|
|
904
|
+
console.log(`${icon} ${issue.message}`)
|
|
905
|
+
})
|
|
906
|
+
console.log()
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
process.exit(0)
|
|
298
910
|
} else if (args.includes("--deps")){
|
|
299
911
|
console.log(`
|
|
300
912
|
Dependencies
|
|
301
913
|
────────────────────────
|
|
302
914
|
`)
|
|
303
915
|
const deps = formatDependencies(packageJson.dependencies)
|
|
304
|
-
console.log(deps)
|
|
916
|
+
console.log(`${deps}\n`)
|
|
917
|
+
console.log()
|
|
305
918
|
process.exit(0)
|
|
306
919
|
} else if (args.includes("--readme")){
|
|
307
|
-
const content = await generateReadme(packageJson, scanDir(projectRoot, 0, config))
|
|
920
|
+
const content = await generateReadme(packageJson, scanDir(projectRoot, 0, config), config)
|
|
308
921
|
|
|
309
922
|
console.log(`
|
|
310
923
|
README Preview
|
|
@@ -313,28 +926,22 @@ README Preview
|
|
|
313
926
|
|
|
314
927
|
console.log(content)
|
|
315
928
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
929
|
+
const confirmResponse = await prompts({
|
|
930
|
+
type: 'confirm',
|
|
931
|
+
name: 'generate',
|
|
932
|
+
message: 'Generate README-MDSMITH.md?',
|
|
933
|
+
initial: true
|
|
934
|
+
});
|
|
322
935
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
console.log("\nPlease type y or n.")
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
if (confirm === "y") {
|
|
331
|
-
fs.writeFileSync("README-MDSMITH.md", content)
|
|
332
|
-
console.log("\nREADME-MDSMITH.md created.\n")
|
|
333
|
-
process.exit(0)
|
|
936
|
+
if (confirmResponse.generate) {
|
|
937
|
+
fs.writeFileSync("README-MDSMITH.md", content);
|
|
938
|
+
console.log("\nREADME-MDSMITH.md created.\n");
|
|
334
939
|
} else {
|
|
335
|
-
console.log("\nOperation cancelled.\n")
|
|
336
|
-
process.exit(0)
|
|
940
|
+
console.log("\nOperation cancelled.\n");
|
|
337
941
|
}
|
|
942
|
+
|
|
943
|
+
process.exit(0);
|
|
944
|
+
|
|
338
945
|
} else {
|
|
339
946
|
console.log(`
|
|
340
947
|
Unknown command.
|
package/mdsmith.config.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "mdsmith",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.2.0",
|
|
5
5
|
"description": "CLI para gerar READMEs e arquivos Markdown",
|
|
6
6
|
"bin": {
|
|
7
7
|
"mdsmith": "./bin/index.js"
|
|
@@ -10,5 +10,8 @@
|
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"engines": {
|
|
12
12
|
"node": ">=18"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"prompts": "^2.4.2"
|
|
13
16
|
}
|
|
14
17
|
}
|