mdsmith 1.1.2 β†’ 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
@@ -1,30 +1,42 @@
1
1
 
2
- # Docuapi
2
+ # mdsmith
3
3
 
4
4
  CLI para gerar READMEs e arquivos Markdown
5
5
 
6
- ## πŸ”– Project Info
7
- - **Version:** 1.1.1
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
19
17
  πŸ“„ index.js
20
18
  πŸ“„ mdsmith.config.json
19
+ πŸ“„ package-lock.json
21
20
  πŸ“„ package.json
22
21
  πŸ“„ README-MDSMITH.md
23
22
 
24
23
  ```
25
24
 
26
- ## πŸ“œ Available Scripts
27
- No scripts available.
28
25
 
26
+ ## πŸš€ Getting Started
29
27
 
30
- 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
 
@@ -88,80 +198,58 @@ function scanDir(dirPath, padding, config) {
88
198
  return treeContent
89
199
  }
90
200
 
91
- function perguntar(pergunta) {
92
- return new Promise(resolve => {
93
- rl.question(pergunta, resolve);
94
- });
95
- }
96
-
97
- async function buscarNome(nome){
98
- if(!nome || nome == "" || nome == false){
99
-
100
- let respostaValida = false
101
- let resposta = ""
102
- while(!respostaValida){
103
- resposta = await perguntar(`
104
- Project name not found in package.json.
105
- Would you like to provide one? (y/n): `)
106
- resposta = resposta.trim().toLowerCase()
107
-
108
- if(resposta == "y" || resposta == "n"){
109
- respostaValida = true
110
- }else {
111
- console.log("Please type y or n.")
112
- }
113
- }
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
+ }
114
214
 
215
+ const response = await prompts({
216
+ type: 'text',
217
+ name: 'value',
218
+ message: 'Enter the project name:'
219
+ });
115
220
 
116
- if(resposta == "n"){
117
- console.log("\nProject name will be set as Unnamed Project.\n")
118
- return "Unnamed Project"
119
- }if(resposta == "y"){
120
- let nome = await perguntar("Enter the project name: ")
121
- console.log(`Project name set to "${nome}".`)
122
- return nome
123
- }else{
124
- console.error("Failed to generate project name.")
125
- process.exit(1)
126
- }
127
- }
221
+ console.log(`\nProject name set to "${response.value}".\n`);
222
+ return response.value || "Unnamed Project";
223
+ }
128
224
 
129
- return nome
225
+ return nome;
130
226
  }
131
227
 
132
- async function buscarDescricao(descricao){
133
- if(!descricao || descricao == "" || descricao == false){
134
-
135
- let respostaValida = false
136
- let resposta = ""
137
- while(!respostaValida){
138
- resposta = await perguntar(`
139
- Project description not found.
140
- Would you like to provide one? (y/n): `)
141
- resposta = resposta.trim().toLowerCase()
142
-
143
- if(resposta == "y" || resposta == "n"){
144
- respostaValida = true
145
- }else {
146
- console.log("Please type y or n.")
147
- }
148
- }
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
+ }
149
241
 
242
+ const response = await prompts({
243
+ type: 'text',
244
+ name: 'value',
245
+ message: 'Enter the project description:'
246
+ });
150
247
 
151
- if(resposta == "n"){
152
- console.log("\nDescription placeholder added.\n")
153
- return "*Project description goes here*"
154
- }if(resposta == "y"){
155
- let descricao = await perguntar("Enter the project description: ")
156
- console.log("\nDescription saved.\n")
157
- return descricao
158
- }else{
159
- console.error("Failed to generate project description.")
160
- process.exit(1)
161
- }
162
- }
248
+ console.log("\nDescription saved.\n");
249
+ return response.value || "*Project description goes here*";
250
+ }
163
251
 
164
- return descricao
252
+ return descricao;
165
253
  }
166
254
 
167
255
  function detectarTipoProjeto(packageJson) {
@@ -188,40 +276,330 @@ function formatScripts(scripts) {
188
276
  .join("\n");
189
277
  }
190
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
+ )
191
511
 
192
- async function generateReadme(packageJson, tree){
193
512
 
194
- const nomeProjeto = await buscarNome(packageJson.name);
195
- const descricaoProjeto = await buscarDescricao(packageJson.description);
196
513
 
197
514
  const content = `
198
515
  # ${nomeProjeto}
199
516
 
200
517
  ${descricaoProjeto}
201
518
 
202
- ## πŸ”– Project Info
203
- - **Version:** ${packageJson.version || "Not specified"}
204
- - **Package Manager:** npm
205
- - **Type:** ${detectarTipoProjeto(packageJson)}
519
+ ## ${config.emojis ? "πŸ”–" : ""} ${t.projectInfo}
520
+ - **${t.version}:** ${packageJson.version || "Not specified"}
521
+ - **${t.packageManager}:** ${packageManager}
522
+ - **${t.type}:** ${detectarTipoProjeto(packageJson)}
206
523
 
207
- ## πŸš€ Dependencies
208
- ${formatDependencies(packageJson.dependencies)}
524
+ ${techSection}
209
525
 
210
- ## πŸ“ Project Structure
526
+ ## ${config.emojis ? "πŸ“" : ""} ${t.projectStructure}
211
527
  \`\`\`
212
528
  ${tree}
213
529
  \`\`\`
214
530
 
215
- ## πŸ“œ Available Scripts
216
- ${formatScripts(packageJson.scripts)}
531
+ ${gettingStartedCommands}
532
+
533
+ ${envSection}
534
+ ${dbSection}
217
535
 
218
- Generated by **mdSmith**
536
+ ${port ? `
537
+ ## 🌐 Server
538
+
539
+ After starting, the server will run at:
540
+
541
+ http://localhost:${port}
542
+
543
+ ` : ""}
544
+
545
+
546
+ ---
547
+
548
+ ${t.generatedBy} **mdSmith**
219
549
  `
220
550
 
221
551
  return content
222
552
 
223
553
  }
224
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
+
225
603
  function loadConfig(projectRoot) {
226
604
  const configPath = path.join(projectRoot, "mdsmith.config.json")
227
605
 
@@ -249,6 +627,209 @@ function loadConfig(projectRoot) {
249
627
  }
250
628
  }
251
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
+
252
833
  async function main() {
253
834
  const config = loadConfig(projectRoot)
254
835
 
@@ -288,6 +869,7 @@ mdSmith β€” Available Commands
288
869
  --tree Show project structure
289
870
  --deps List dependencies
290
871
  --readme Generate README
872
+ --analyze Project analysis
291
873
  `)
292
874
  process.exit(0)
293
875
  } else if (args.includes("--tree")){
@@ -297,16 +879,45 @@ Project Structure
297
879
  `);
298
880
  console.log(scanDir(projectRoot, 0, config))
299
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)
300
910
  } else if (args.includes("--deps")){
301
911
  console.log(`
302
912
  Dependencies
303
913
  ────────────────────────
304
914
  `)
305
915
  const deps = formatDependencies(packageJson.dependencies)
306
- console.log(deps)
916
+ console.log(`${deps}\n`)
917
+ console.log()
307
918
  process.exit(0)
308
919
  } else if (args.includes("--readme")){
309
- const content = await generateReadme(packageJson, scanDir(projectRoot, 0, config))
920
+ const content = await generateReadme(packageJson, scanDir(projectRoot, 0, config), config)
310
921
 
311
922
  console.log(`
312
923
  README Preview
@@ -315,28 +926,22 @@ README Preview
315
926
 
316
927
  console.log(content)
317
928
 
318
- let respostaValida = false
319
- let confirm = ""
320
-
321
- while (!respostaValida) {
322
- confirm = await perguntar("\nGenerate README-MDSMITH.md? (y/n): ")
323
- confirm = confirm.trim().toLowerCase()
324
-
325
- if (confirm === "y" || confirm === "n") {
326
- respostaValida = true
327
- } else {
328
- console.log("\nPlease type y or n.")
329
- }
330
- }
929
+ const confirmResponse = await prompts({
930
+ type: 'confirm',
931
+ name: 'generate',
932
+ message: 'Generate README-MDSMITH.md?',
933
+ initial: true
934
+ });
331
935
 
332
- if (confirm === "y") {
333
- fs.writeFileSync("README-MDSMITH.md", content)
334
- console.log("\nREADME-MDSMITH.md created.\n")
335
- process.exit(0)
936
+ if (confirmResponse.generate) {
937
+ fs.writeFileSync("README-MDSMITH.md", content);
938
+ console.log("\nREADME-MDSMITH.md created.\n");
336
939
  } else {
337
- console.log("\nOperation cancelled.\n")
338
- process.exit(0)
940
+ console.log("\nOperation cancelled.\n");
339
941
  }
942
+
943
+ process.exit(0);
944
+
340
945
  } else {
341
946
  console.log(`
342
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.2",
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
  }