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 CHANGED
@@ -3,16 +3,14 @@
3
3
 
4
4
  CLI para gerar READMEs e arquivos Markdown
5
5
 
6
- ## 🔖 Project Info
7
- - **Version:** 1.1.0
8
- - **Package Manager:** npm
9
- - **Type:** Node.js Project
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
- ## 📁 Project Structure
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
- Generated by **mdSmith**
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 readline = require("readline");
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
- treeContent += `${" ".repeat(padding*2)} 📄 ${entry.name}\n`
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 perguntar(pergunta) {
88
- return new Promise(resolve => {
89
- rl.question(pergunta, resolve);
90
- });
91
- }
92
-
93
- async function buscarNome(nome){
94
- if(!nome || nome == "" || nome == false){
95
-
96
- let respostaValida = false
97
- let resposta = ""
98
- while(!respostaValida){
99
- resposta = await perguntar(`
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
- if(resposta == "n"){
114
- console.log("\nProject name will be set as Unnamed Project.\n")
115
- return "Unnamed Project"
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
- return nome
225
+ return nome;
127
226
  }
128
227
 
129
- async function buscarDescricao(descricao){
130
- if(!descricao || descricao == "" || descricao == false){
131
-
132
- let respostaValida = false
133
- let resposta = ""
134
- while(!respostaValida){
135
- resposta = await perguntar(`
136
- Project description not found.
137
- Would you like to provide one? (y/n):
138
- `)
139
- resposta = resposta.trim().toLowerCase()
140
-
141
- if(resposta == "y" || resposta == "n"){
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
- if(resposta == "n"){
150
- console.log("\nDescription placeholder added.\n")
151
- return "*Project description goes here*"
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
- return descricao
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
- ## 🔖 Project Info
201
- - **Version:** ${packageJson.version || "Not specified"}
202
- - **Package Manager:** npm
203
- - **Type:** ${detectarTipoProjeto(packageJson)}
519
+ ## ${config.emojis ? "🔖" : ""} ${t.projectInfo}
520
+ - **${t.version}:** ${packageJson.version || "Not specified"}
521
+ - **${t.packageManager}:** ${packageManager}
522
+ - **${t.type}:** ${detectarTipoProjeto(packageJson)}
204
523
 
205
- ## 🚀 Dependencies
206
- ${formatDependencies(packageJson.dependencies)}
524
+ ${techSection}
207
525
 
208
- ## 📁 Project Structure
526
+ ## ${config.emojis ? "📁" : ""} ${t.projectStructure}
209
527
  \`\`\`
210
528
  ${tree}
211
529
  \`\`\`
212
530
 
213
- ## 📜 Available Scripts
214
- ${formatScripts(packageJson.scripts)}
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
- Generated by **mdSmith**
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
- let respostaValida = false
317
- let confirm = ""
318
-
319
- while (!respostaValida) {
320
- confirm = await perguntar("\nGenerate README-MDSMITH.md? (y/n): ")
321
- confirm = confirm.trim().toLowerCase()
929
+ const confirmResponse = await prompts({
930
+ type: 'confirm',
931
+ name: 'generate',
932
+ message: 'Generate README-MDSMITH.md?',
933
+ initial: true
934
+ });
322
935
 
323
- if (confirm === "y" || confirm === "n") {
324
- respostaValida = true
325
- } else {
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.
@@ -4,5 +4,6 @@
4
4
  ".git",
5
5
  ".vscode"
6
6
  ],
7
- "depth": 1
7
+ "depth": null,
8
+ "emojis": true
8
9
  }
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.1.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
  }