mdsmith 1.1.2 → 1.2.1

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.
@@ -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.1",
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
  }
@@ -0,0 +1,149 @@
1
+
2
+
3
+ const fs = require("fs")
4
+ const path = require("path")
5
+ const prompts = require('prompts');
6
+
7
+ const detectors = require("../../src/detectors")
8
+
9
+ function analisarProjeto(packageJson, projectRoot) {
10
+ const deps = {
11
+ ...packageJson.dependencies,
12
+ ...packageJson.devDependencies
13
+ }
14
+
15
+ const analysis = {
16
+ frontend: deps.react ? "React" :
17
+ deps.next ? "Next.js" :
18
+ null,
19
+
20
+ backend: deps.express ? "Express" :
21
+ deps.fastify ? "Fastify" :
22
+ null,
23
+
24
+ language: deps.typescript ? "TypeScript" : "JavaScript",
25
+
26
+ bundler: deps.vite ? "Vite" :
27
+ deps.webpack ? "Webpack" :
28
+ null,
29
+
30
+ styling: deps.tailwindcss ? "TailwindCSS" :
31
+ deps.sass ? "Sass" :
32
+ null,
33
+
34
+ database: deps.mongoose ? "MongoDB" :
35
+ deps.prisma ? "Prisma" :
36
+ deps.pg ? "PostgreSQL" :
37
+ null,
38
+
39
+ testing: deps.jest ? "Jest" :
40
+ deps.vitest ? "Vitest" :
41
+ null,
42
+
43
+ linting: deps.eslint ? "ESLint" : null,
44
+
45
+ formatting: deps.prettier ? "Prettier" : null,
46
+
47
+ node: detectors.detectarNodeVersion(projectRoot, packageJson)
48
+ }
49
+
50
+ if (analysis.frontend && analysis.backend) {
51
+ analysis.architecture = "Fullstack"
52
+ } else if (analysis.frontend) {
53
+ analysis.architecture = "Frontend"
54
+ } else if (analysis.backend) {
55
+ analysis.architecture = "Backend"
56
+ }
57
+
58
+ return analysis
59
+ }
60
+
61
+ function diagnosticarExecucao(commands) {
62
+ const warnings = []
63
+
64
+ if (!commands.dev && !commands.start) {
65
+ warnings.push("No development or start script found.")
66
+ }
67
+
68
+ if (commands.build && !commands.start) {
69
+ warnings.push("Build script found but no start script for production.")
70
+ }
71
+
72
+ return warnings
73
+ }
74
+
75
+ function healthChecks(packageJson, projectRoot, analysis) {
76
+ const checks = []
77
+
78
+ if (!analysis.testing) {
79
+ checks.push({
80
+ type: "warning",
81
+ message: "No test runner configured."
82
+ })
83
+ }
84
+
85
+ if (!analysis.linting) {
86
+ checks.push({
87
+ type: "info",
88
+ message: "No ESLint detected."
89
+ })
90
+ }
91
+
92
+ if (!analysis.formatting) {
93
+ checks.push({
94
+ type: "info",
95
+ message: "No Prettier detected."
96
+ })
97
+ }
98
+
99
+ if (!packageJson.engines?.node) {
100
+ checks.push({
101
+ type: "critical",
102
+ message: "Node version not defined in package.json engines."
103
+ })
104
+ }
105
+
106
+ if (!fs.existsSync(path.join(projectRoot, "Dockerfile"))) {
107
+ checks.push({
108
+ type: "info",
109
+ message: "No Dockerfile found."
110
+ })
111
+ }
112
+
113
+ return checks
114
+ }
115
+
116
+ function analisarScripts(scripts = {}, packageManager) {
117
+ const commands = {
118
+ install: packageManager === "npm"
119
+ ? "npm install"
120
+ : `${packageManager} install`
121
+ }
122
+
123
+ if (scripts.dev) {
124
+ commands.dev = `${packageManager} run dev`
125
+ }
126
+
127
+ if (scripts.build) {
128
+ commands.build = `${packageManager} run build`
129
+ }
130
+
131
+ if (scripts.start) {
132
+ commands.start = packageManager === "npm"
133
+ ? "npm start"
134
+ : `${packageManager} start`
135
+ }
136
+
137
+ if (scripts.preview) {
138
+ commands.preview = `${packageManager} run preview`
139
+ }
140
+
141
+ return commands
142
+ }
143
+
144
+ module.exports = {
145
+ analisarProjeto,
146
+ diagnosticarExecucao,
147
+ healthChecks,
148
+ analisarScripts
149
+ }
@@ -0,0 +1,43 @@
1
+
2
+ const fs = require("fs")
3
+ const path = require("path")
4
+ const prompts = require('prompts');
5
+
6
+ const defaultConfig = {
7
+ ignore: ["node_modules", ".git", ".vscode"],
8
+ depth: Infinity,
9
+ emojis: true
10
+ }
11
+
12
+
13
+ function loadConfig(projectRoot) {
14
+ const configPath = path.join(projectRoot, "mdsmith.config.json")
15
+
16
+ if (!fs.existsSync(configPath)) {
17
+ return defaultConfig
18
+ }
19
+
20
+ try {
21
+ const fileContent = fs.readFileSync(configPath, "utf-8")
22
+ const userConfig = JSON.parse(fileContent)
23
+
24
+ return {
25
+ ...defaultConfig,
26
+ ...userConfig,
27
+ ignore: [
28
+ ...defaultConfig.ignore,
29
+ ...(userConfig.ignore || [])
30
+ ]
31
+ }
32
+
33
+
34
+ } catch (error) {
35
+ console.log("Failed to read mdsmith.config.json. Using default configuration.")
36
+ return defaultConfig
37
+ }
38
+ }
39
+
40
+ module.exports = {
41
+ defaultConfig,
42
+ loadConfig
43
+ }
@@ -0,0 +1,203 @@
1
+ const fs = require("fs")
2
+ const path = require("path")
3
+
4
+ const TECH_MAP = {
5
+ // Frameworks
6
+ express: "Express",
7
+ fastify: "Fastify",
8
+ koa: "Koa",
9
+ hapi: "Hapi",
10
+ "@nestjs/core": "NestJS",
11
+
12
+ // ORMs
13
+ prisma: "Prisma",
14
+ "@prisma/client": "Prisma",
15
+ sequelize: "Sequelize",
16
+ mongoose: "Mongoose",
17
+ typeorm: "TypeORM",
18
+ drizzle: "Drizzle ORM",
19
+
20
+ // Databases
21
+ pg: "PostgreSQL",
22
+ mysql: "MySQL",
23
+ mysql2: "MySQL",
24
+ sqlite3: "SQLite",
25
+ mongodb: "MongoDB",
26
+
27
+ // Auth
28
+ jsonwebtoken: "JWT",
29
+ passport: "Passport",
30
+
31
+ // AI
32
+ openai: "OpenAI",
33
+ langchain: "LangChain",
34
+
35
+ // Infra / Tools
36
+ docker: "Docker",
37
+ "swagger-ui-express": "Swagger",
38
+ puppeteer: "Puppeteer",
39
+ nodemailer: "Nodemailer"
40
+ }
41
+
42
+ function detectarTechStack(deps = {}) {
43
+ const stack = new Set()
44
+
45
+ for (const dep in deps) {
46
+ if (TECH_MAP[dep]) {
47
+ stack.add(TECH_MAP[dep])
48
+ }
49
+ }
50
+
51
+ return Array.from(stack).sort()
52
+ }
53
+
54
+
55
+ function detectarEnvVars(projectRoot) {
56
+ const envPath = path.join(projectRoot, ".env")
57
+
58
+ if (!fs.existsSync(envPath)) return []
59
+
60
+ const content = fs.readFileSync(envPath, "utf-8")
61
+
62
+ const vars = content
63
+ .split("\n")
64
+ .map(line => line.trim())
65
+ .filter(line =>
66
+ line &&
67
+ !line.startsWith("#") &&
68
+ line.includes("=")
69
+ )
70
+ .map(line => line.replace(/^export\s+/, ""))
71
+ .map(line => line.split("=")[0].trim())
72
+
73
+ return vars
74
+ }
75
+
76
+ function detectarPorta(scanData) {
77
+ const filesToScan = [...scanData.jsFiles, ...scanData.tsFiles]
78
+
79
+ for (const filePath of filesToScan) {
80
+ try {
81
+ const content = fs.readFileSync(filePath, "utf-8")
82
+
83
+ // 🔎 Regex robusta: pega .listen(...) mesmo com quebra de linha
84
+ const listenRegex = /\.listen\s*\(\s*([\s\S]*?)\)/g
85
+
86
+ let match
87
+
88
+ while ((match = listenRegex.exec(content)) !== null) {
89
+ const rawArgument = match[1].split(",")[0].trim()
90
+
91
+ // =============================
92
+ // ✅ 1️⃣ Número direto
93
+ // =============================
94
+ const directNumber = rawArgument.match(/^\d+$/)
95
+ if (directNumber) {
96
+ return directNumber[0]
97
+ }
98
+
99
+ // =============================
100
+ // ✅ 2️⃣ Fallback inline
101
+ // =============================
102
+ const inlineFallback = rawArgument.match(/\|\|\s*(\d+)/)
103
+ if (inlineFallback) {
104
+ return inlineFallback[1]
105
+ }
106
+
107
+ // =============================
108
+ // ✅ 3️⃣ process.env.PORT
109
+ // =============================
110
+ if (rawArgument.includes("process.env.PORT")) {
111
+ return "process.env.PORT"
112
+ }
113
+
114
+ // =============================
115
+ // ✅ 4️⃣ Resolver variável
116
+ // =============================
117
+ const variableName = rawArgument.replace(/[^a-zA-Z0-9_]/g, "")
118
+
119
+ if (!variableName) continue
120
+
121
+ const variableRegex = new RegExp(
122
+ `(const|let|var)\\s+${variableName}\\s*=\\s*([\\s\\S]*?);`
123
+ )
124
+
125
+ const variableMatch = content.match(variableRegex)
126
+
127
+ if (variableMatch) {
128
+ const variableValue = variableMatch[2]
129
+
130
+ const fallbackMatch = variableValue.match(/\|\|\s*(\d+)/)
131
+ if (fallbackMatch) {
132
+ return fallbackMatch[1]
133
+ }
134
+
135
+ const numberMatch = variableValue.match(/\d+/)
136
+ if (numberMatch) {
137
+ return numberMatch[0]
138
+ }
139
+ }
140
+ }
141
+
142
+ } catch (err) {
143
+ continue
144
+ }
145
+ }
146
+
147
+ return null
148
+ }
149
+
150
+ function detectarPackageManager(projectRoot) {
151
+ if (fs.existsSync(path.join(projectRoot, "pnpm-lock.yaml"))) {
152
+ return "pnpm"
153
+ }
154
+
155
+ if (fs.existsSync(path.join(projectRoot, "yarn.lock"))) {
156
+ return "yarn"
157
+ }
158
+
159
+ if (fs.existsSync(path.join(projectRoot, "package-lock.json"))) {
160
+ return "npm"
161
+ }
162
+
163
+ return "npm"
164
+ }
165
+
166
+ function detectarNodeVersion(projectRoot, packageJson) {
167
+ if (packageJson.engines && packageJson.engines.node) {
168
+ return packageJson.engines.node
169
+ }
170
+
171
+ const nvmrcPath = path.join(projectRoot, ".nvmrc")
172
+ if (fs.existsSync(nvmrcPath)) {
173
+ return fs.readFileSync(nvmrcPath, "utf-8").trim()
174
+ }
175
+
176
+ const nodeVersionPath = path.join(projectRoot, ".node-version")
177
+ if (fs.existsSync(nodeVersionPath)) {
178
+ return fs.readFileSync(nodeVersionPath, "utf-8").trim()
179
+ }
180
+
181
+ return process.version
182
+ }
183
+
184
+ function detectarPrisma(projectRoot, packageJson) {
185
+ const deps = {
186
+ ...packageJson.dependencies,
187
+ ...packageJson.devDependencies
188
+ }
189
+
190
+ const hasPrismaDep = deps.prisma || deps["@prisma/client"]
191
+ const hasPrismaFolder = fs.existsSync(path.join(projectRoot, "prisma"))
192
+
193
+ return hasPrismaDep && hasPrismaFolder
194
+ }
195
+
196
+ module.exports = {
197
+ detectarEnvVars,
198
+ detectarPorta,
199
+ detectarTechStack,
200
+ detectarPackageManager,
201
+ detectarNodeVersion,
202
+ detectarPrisma
203
+ }
@@ -0,0 +1,53 @@
1
+
2
+ function formatarAnalise(analysis) {
3
+ console.log(`
4
+ Project Analysis
5
+ ────────────────────────
6
+ `)
7
+
8
+ const entries = Object.entries(analysis)
9
+
10
+ for (const [key, value] of entries) {
11
+ if (value) {
12
+ const label = key.charAt(0).toUpperCase() + key.slice(1)
13
+ console.log(`✓ ${label}: ${value}`)
14
+ }
15
+ }
16
+
17
+ console.log("")
18
+ }
19
+
20
+ function formatarExecucao(commands, warnings) {
21
+ console.log(`Project Execution Strategy
22
+ ────────────────────────`)
23
+
24
+ console.log(`Install:`)
25
+ console.log(` ${commands.install}\n`)
26
+
27
+ if (commands.dev) {
28
+ console.log(`Development:`)
29
+ console.log(` ${commands.dev}\n`)
30
+ }
31
+
32
+ if (commands.build && commands.start) {
33
+ console.log(`Production:`)
34
+ console.log(` ${commands.build}`)
35
+ console.log(` ${commands.start}\n`)
36
+ }
37
+
38
+ if (commands.preview) {
39
+ console.log(`Preview:`)
40
+ console.log(` ${commands.preview}\n`)
41
+ }
42
+
43
+ if (warnings.length > 0) {
44
+ console.log(`Warnings:`)
45
+ warnings.forEach(w => console.log(` ⚠ ${w}`))
46
+ console.log()
47
+ }
48
+ }
49
+
50
+ module.exports = {
51
+ formatarAnalise,
52
+ formatarExecucao
53
+ }
@@ -0,0 +1,27 @@
1
+ const fs = require("fs")
2
+ const path = require("path")
3
+
4
+ function formatDependencies(deps) {
5
+ if (!deps || Object.keys(deps).length === 0) {
6
+ return "No dependencies found.\n";
7
+ }
8
+
9
+ return Object.entries(deps)
10
+ .map(([name, version]) => `- ${name} ${version}`)
11
+ .join("\n");
12
+ }
13
+
14
+ function formatScripts(scripts) {
15
+ if (!scripts || Object.keys(scripts).length === 0) {
16
+ return "No scripts available.\n";
17
+ }
18
+
19
+ return Object.entries(scripts)
20
+ .map(([name, script]) => `- ${name} -> ${script}`)
21
+ .join("\n");
22
+ }
23
+
24
+ module.exports = {
25
+ formatDependencies,
26
+ formatScripts
27
+ }
@@ -0,0 +1,95 @@
1
+ const fs = require("fs")
2
+ const path = require("path")
3
+ const prompts = require('prompts');
4
+
5
+ async function buscarNome(nome) {
6
+ if (!nome) {
7
+ const confirm = await prompts({
8
+ type: 'confirm',
9
+ name: 'value',
10
+ message: 'Project name not found in package.json. Provide one?',
11
+ initial: true
12
+ });
13
+
14
+ if (!confirm.value) {
15
+ console.log("\nProject name will be set as Unnamed Project.\n");
16
+ return "Unnamed Project";
17
+ }
18
+
19
+ const response = await prompts({
20
+ type: 'text',
21
+ name: 'value',
22
+ message: 'Enter the project name:'
23
+ });
24
+
25
+ console.log(`\nProject name set to "${response.value}".\n`);
26
+ return response.value || "Unnamed Project";
27
+ }
28
+
29
+ return nome;
30
+ }
31
+
32
+ async function buscarDescricao(descricao) {
33
+ if (!descricao) {
34
+ const confirm = await prompts({
35
+ type: 'confirm',
36
+ name: 'value',
37
+ message: 'Project description not found. Provide one?',
38
+ initial: true
39
+ });
40
+
41
+ if (!confirm.value) {
42
+ console.log("\nDescription placeholder added.\n");
43
+ return "*Project description goes here*";
44
+ }
45
+
46
+ const response = await prompts({
47
+ type: 'text',
48
+ name: 'value',
49
+ message: 'Enter the project description:'
50
+ });
51
+
52
+ console.log("\nDescription saved.\n");
53
+ return response.value || "*Project description goes here*";
54
+ }
55
+
56
+ return descricao;
57
+ }
58
+
59
+ async function escolherLingua() {
60
+ const response = await prompts({
61
+ type: 'select',
62
+ name: 'lang',
63
+ message: 'Choose README language',
64
+ choices: [
65
+ { title: 'English', value: 'en' },
66
+ { title: 'Português', value: 'pt' },
67
+ { title: 'Español', value: 'es' }
68
+ ],
69
+ initial: 0
70
+ });
71
+
72
+ return response.lang || 'en';
73
+ }
74
+
75
+
76
+ async function perguntarPortaManual() {
77
+ const response = await prompts({
78
+ type: "text",
79
+ name: "port",
80
+ message: "⚠️ Could not automatically detect the port.\nEnter the API port (or leave empty to skip):",
81
+ validate: value => {
82
+ if (!value) return true
83
+ return /^\d+$/.test(value) || "Please enter numbers only"
84
+ }
85
+ })
86
+
87
+ return response.port || null
88
+ }
89
+
90
+ module.exports = {
91
+ buscarNome,
92
+ buscarDescricao,
93
+ escolherLingua,
94
+ perguntarPortaManual
95
+ }