trackfw 1.0.2 → 1.0.4

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.
@@ -0,0 +1,172 @@
1
+ 'use strict'
2
+
3
+ const fs = require('fs')
4
+ const path = require('path')
5
+
6
+ /**
7
+ * Converte uma string em slug: lowercase + espaços → hifens.
8
+ * @param {string} s
9
+ * @returns {string}
10
+ */
11
+ function toSlug(s) {
12
+ return s.toLowerCase().replace(/ /g, '-')
13
+ }
14
+
15
+ /**
16
+ * Converte slug com hifens em Title Case.
17
+ * Ex: "authentication-strategy" → "Authentication Strategy"
18
+ * @param {string} slug
19
+ * @returns {string}
20
+ */
21
+ function slugToTitle(slug) {
22
+ return slug
23
+ .split('-')
24
+ .map((w) => (w.length > 0 ? w[0].toUpperCase() + w.slice(1) : w))
25
+ .join(' ')
26
+ }
27
+
28
+ /**
29
+ * Retorna a data atual no formato YYYY-MM-DD.
30
+ * @returns {string}
31
+ */
32
+ function today() {
33
+ return new Date().toISOString().slice(0, 10)
34
+ }
35
+
36
+ /**
37
+ * Cria um novo ADR em docs/adr/ADR-YYYY-MM-DD-<slug>.md.
38
+ * Campos vazios recebem placeholder HTML.
39
+ * @param {{ title: string, context?: string, decision?: string, consequences?: string, alternatives?: string }} content
40
+ * @returns {Promise<void>}
41
+ */
42
+ async function newADR(content) {
43
+ fs.mkdirSync('docs/adr', { recursive: true })
44
+
45
+ const slug = toSlug(content.title)
46
+ const date = today()
47
+ const filename = `docs/adr/ADR-${date}-${slug}.md`
48
+
49
+ const contextSection = content.context || '<!-- What is the situation that motivates this decision? -->'
50
+ const decisionSection = content.decision || '<!-- What was decided? -->'
51
+ const consequencesSection = content.consequences || '<!-- What are the positive and negative consequences of this decision? -->'
52
+ const alternativesSection = content.alternatives || '<!-- What other options were evaluated and why were they rejected? -->'
53
+
54
+ const body = `# ADR: ${content.title}
55
+
56
+ > Date: ${date} | Status: Proposed
57
+
58
+ ## Context
59
+ ${contextSection}
60
+
61
+ ## Decision
62
+ ${decisionSection}
63
+
64
+ ## Consequences
65
+ ${consequencesSection}
66
+
67
+ ## Alternatives Considered
68
+ ${alternativesSection}
69
+ `
70
+
71
+ fs.writeFileSync(filename, body, 'utf8')
72
+ console.log(`created ${filename}`)
73
+ }
74
+
75
+ /**
76
+ * Lista todos os ADRs (.md) em dir, imprimindo filename e status (coluna 60 chars).
77
+ * @param {string} dir
78
+ * @returns {Promise<void>}
79
+ */
80
+ async function listADRs(dir) {
81
+ if (!fs.existsSync(dir)) {
82
+ console.log(`No ADRs found in ${dir}`)
83
+ return
84
+ }
85
+
86
+ const files = fs.readdirSync(dir).filter((f) => f.endsWith('.md')).sort()
87
+
88
+ if (files.length === 0) {
89
+ console.log(`No ADRs found in ${dir}`)
90
+ return
91
+ }
92
+
93
+ for (const file of files) {
94
+ const filepath = path.join(dir, file)
95
+ const status = parseADRStatus(filepath)
96
+ const padded = file.padEnd(60)
97
+ console.log(`${padded} ${status}`)
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Extrai o status de um arquivo ADR markdown.
103
+ * Procura pela linha "> Date: ... | Status: ..."
104
+ * @param {string} filepath
105
+ * @returns {string}
106
+ */
107
+ function parseADRStatus(filepath) {
108
+ try {
109
+ const content = fs.readFileSync(filepath, 'utf8')
110
+ const lines = content.split('\n')
111
+ for (const line of lines) {
112
+ const idx = line.indexOf('| Status: ')
113
+ if (idx >= 0) {
114
+ let rest = line.slice(idx + '| Status: '.length)
115
+ rest = rest.replace(/[ >|]+$/, '').trim()
116
+ return rest
117
+ }
118
+ }
119
+ } catch (_) {
120
+ // ignorar erros de leitura
121
+ }
122
+ return 'unknown'
123
+ }
124
+
125
+ /**
126
+ * Cria um ADR com Status: Draft a partir de um slug.
127
+ * Idempotente: se já existe ADR-*-<slug>.md, pula e imprime mensagem.
128
+ * @param {string} slug
129
+ * @returns {Promise<string>} basename do arquivo criado
130
+ */
131
+ async function newADRDraft(slug) {
132
+ fs.mkdirSync('docs/adr', { recursive: true })
133
+
134
+ // Verificar idempotência: buscar arquivo existente com o mesmo slug
135
+ const adrDir = 'docs/adr'
136
+ const existing = fs.existsSync(adrDir)
137
+ ? fs.readdirSync(adrDir).find((f) => f.match(new RegExp(`^ADR-.*-${slug}\\.md$`)))
138
+ : null
139
+
140
+ if (existing) {
141
+ console.log(`skipped ${existing} (already exists)`)
142
+ return existing
143
+ }
144
+
145
+ const date = today()
146
+ const filename = `ADR-${date}-${slug}.md`
147
+ const filepath = path.join('docs/adr', filename)
148
+ const title = slugToTitle(slug)
149
+
150
+ const body = `# ADR: ${title}
151
+
152
+ > Date: ${date} | Status: Draft
153
+
154
+ ## Context
155
+ <!-- What is the situation that motivates this decision? -->
156
+
157
+ ## Decision
158
+ <!-- What was decided? -->
159
+
160
+ ## Consequences
161
+ <!-- What are the positive and negative consequences of this decision? -->
162
+
163
+ ## Alternatives Considered
164
+ <!-- What other options were evaluated and why were they rejected? -->
165
+ `
166
+
167
+ fs.writeFileSync(filepath, body, 'utf8')
168
+ console.log(`created ${filename}`)
169
+ return filename
170
+ }
171
+
172
+ module.exports = { newADR, listADRs, newADRDraft, toSlug }